Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
04db226
Clarify global flag help descriptions and categories
saioai Oct 7, 2026
70a6e1c
Prepare clearer format help and recovery demos
saioai Oct 7, 2026
ad0dd6d
Show practical help examples and specific argument recovery
saioai Oct 7, 2026
3760a4d
Keep common image options readable in narrow terminals
saioai Oct 7, 2026
062a691
Label string file options as paths in help
saioai Oct 7, 2026
40e1888
Unify complete command help and preserve help routing
saioai Oct 7, 2026
859c216
Record complete help comparisons at bounded terminal sizes
saioai Oct 7, 2026
7705e33
Clarify command syntax and match gcloud help structure
saioai Oct 8, 2026
e27b661
Render help emphasis without altering literal arguments
saioai Oct 8, 2026
3005b1e
Keep quoted help references distinct from literal arguments
saioai Oct 8, 2026
5cdf026
fix(output): emit structured stream events without pager buffering
saioai Oct 8, 2026
b7ca4a3
fix(output): close owned event streams after presentation
saioai Oct 8, 2026
e151676
Match Windows invocation syntax in help index test
saioai Oct 8, 2026
c018021
feat(output): preserve data with quiet and verbose controls
saioai Oct 8, 2026
c9817b1
Recognize complete root help in dispatch test
saioai Oct 8, 2026
fced6ab
test(output): add reproducible pipe and verbosity demos
saioai Oct 8, 2026
2be4715
fix(output): skip verbose epilogues after interruption
saioai Oct 8, 2026
35feacc
test(output): cover native dispatch and interruption causes
saioai Oct 8, 2026
a41c226
Update process tests for diagnostic stream routing
saioai Oct 8, 2026
fc944da
Merge pinned main into complete help branch
saioai Oct 8, 2026
4e2d3e0
Merge complete help dependency
saioai Oct 8, 2026
d5ca5b4
Group output controls in complete help
saioai Oct 8, 2026
61979e0
[files] add upload paths, metadata and download routes
saioai Oct 8, 2026
8f99811
[files] preserve failure details and document recovery
saioai Oct 8, 2026
17fccc9
[files] improve workflow discovery and honor quiet output
saioai Oct 8, 2026
ee41a9a
[files] explain the workflow in complete help
saioai Oct 8, 2026
e7e86d8
[files] complete positional paths and preserve literal filenames
saioai Oct 8, 2026
62a8cb1
[files] preserve executable identity in recovery commands
saioai Oct 8, 2026
7b86e62
[files] preserve literal executable paths in receipt commands
saioai Oct 8, 2026
699b5d3
[files] record workflow discovery alongside command demos
saioai Oct 8, 2026
217f571
[files] allow wrapped help prose in discovery captures
saioai Oct 8, 2026
c9c3b0b
[files] keep workflow examples complete after help integration
saioai Oct 8, 2026
409d12f
[files] update help coverage for positional uploads
saioai Oct 8, 2026
1f6c0a8
Name help presentation tests for their behavior
saioai Oct 8, 2026
cd1449a
Merge help presentation test rename
saioai Oct 8, 2026
6cb987f
[files] integrate the coordinated help presentation test rename
saioai Oct 8, 2026
64ed675
[files] preserve receipt output and validate Windows hint destinations
saioai Oct 8, 2026
e4b31a4
Align output with merged complete help
saioai Oct 8, 2026
af73860
Complete verbose lifecycle and suppress quiet image progress
saioai Oct 8, 2026
59cf52f
Match output validation assets to current diagnostics
saioai Oct 8, 2026
61e8db6
Check image preference receipts on stderr
saioai Oct 8, 2026
7901147
Merge main and align output policy with Models, Images, and Shell
saioai Oct 9, 2026
5ea7752
[files] integrate current Output and merged input/save policies
saioai Oct 9, 2026
b06bae7
Fix Windows output lifecycle fixture cleanup
saioai Oct 9, 2026
520dc58
[files] preserve context in read-only receipt hints and fix Bash paths
saioai Oct 9, 2026
a94b509
[files] inherit the published Output Windows fixture correction
saioai Oct 9, 2026
2d482e3
[files] isolate copied-command assertions from startup cache files
saioai Oct 9, 2026
5b0fe1f
[files] clarify read-only hints and upload output guarantees
saioai Oct 9, 2026
cd44152
Route completed manpage receipts through output policy
saioai Oct 9, 2026
9241a6b
[files] inherit the current Output manpage policy
saioai Oct 9, 2026
3ae27da
[files] retain launcher hints and validate known receipt fields
saioai Oct 9, 2026
68a3e87
Merge main and preserve output policy for local utilities
saioai Oct 9, 2026
10c10cb
[files] integrate current Output and local utility composition
saioai Oct 9, 2026
ae2ad9e
[files] clarify explicit purpose input options
saioai Oct 9, 2026
44f7564
Honor output policy for compact table hints
saioai Oct 9, 2026
48aee72
Merge remote-tracking branch 'origin/codex/universal-output-20261008'…
saioai Oct 9, 2026
bcd5af5
Merge remote-tracking branch 'origin/main' into codex/sdk-1187-files-…
saioai Oct 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,19 @@ Use `--format json` or `--format jsonl` in scripts that parse API data.
See [reading command results](docs/readable-output.md) for extraction, binary
output, and the scope of readable presentation.

Upload a local file with an explicit purpose, inspect its metadata, then download its contents:

```sh
openai files upload "upload space.txt" --purpose user_data
openai files get file-example
openai files download file-example --output "downloaded copy.txt"
openai files download file-example > copy.txt
```

Replace `file-example` with the returned file ID. Upload takes a plain path, without an added `@`.
`create`, `retrieve`, `content`, and their existing flags remain available.
See [working with files](docs/files.md) for literal filenames, output formats, and script examples.

`openai images generate --prompt "A tiny orange robot"` saves images to
`~/Downloads/gpt-images/` and prints their paths, including in pipes. The CLI
uses `gpt-image-2.5-sunburst` when saving without an explicit model or legacy
Expand Down Expand Up @@ -232,7 +245,11 @@ presenting the client certificate during the proxy's own TLS handshake.

### Passing files as arguments

To pass files to your API, you can use the `@myfile.ext` syntax:
For JSON input values, `@myfile.ext` reads local file contents:

Binary upload flags use plain paths. For example, use `files upload --file "upload space.txt" --purpose user_data`.
Here, `--file "@upload space.txt"` names an actual file starting with `@`; it does not expand another file reference.
See [working with files](docs/files.md).

```bash
openai <command> --arg @abe.jpg
Expand Down
167 changes: 167 additions & 0 deletions cmd/openai/files_receipt_context_unix_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
//go:build !windows

package main

import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"strings"
"sync/atomic"
"testing"

"github.com/stretchr/testify/require"
)

// Execute the receipt through Bash after changing the upload source. The copied
// command must inspect metadata in the same request context without saving bytes.
func TestMainFilesReceiptFollowupPreservesSourceAndContext(t *testing.T) {
python, err := exec.LookPath("python3")
if err != nil {
t.Skip("Python 3 is required for Unix receipt checks")
}
bash, err := exec.LookPath("bash")
if err != nil {
t.Skip("Bash is required for Unix receipt checks")
}
for _, name := range []string{"default", "context before command", "context after command", "empty project", "processing error", "inspection fails", "API key override", "header override", "URL query"} {
t.Run(name, func(t *testing.T) {
work := t.TempDir()
const filename = "upload space.txt"
original := []byte("hello files!\n")
require.NoError(t, os.WriteFile(filepath.Join(work, filename), original, 0o600))
status := "uploaded"
if name == "processing error" {
status = "error"
}
response := fmt.Sprintf(`{"id":%q,"object":"file","bytes":13,"filename":%q,"purpose":"user_data","status":%q}`, filesWorkflowID, filename, status)
var uploads, inspections, downloads atomic.Int32
project, organization := "", ""
key := "sk-fake-files-receipt-test"
if strings.HasPrefix(name, "context ") || name == "processing error" {
project, organization = "project-'quoted", "org-one"
}
if name == "API key override" {
key = "sk-fake-private-override"
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("OpenAI-Project") != project || r.Header.Get("OpenAI-Organization") != organization {
t.Errorf("request context changed: project=%q organization=%q", r.Header.Get("OpenAI-Project"), r.Header.Get("OpenAI-Organization"))
}
if r.Header.Get("Authorization") != "Bearer "+key {
t.Error("unexpected synthetic credential selection")
}
switch {
case r.Method == http.MethodPost && r.URL.Path == "/files":
uploads.Add(1)
if err := r.ParseMultipartForm(1 << 20); err != nil {
t.Error(err)
return
}
defer r.MultipartForm.RemoveAll()
file, header, err := r.FormFile("file")
if err != nil {
t.Error(err)
return
}
defer file.Close()
body, err := io.ReadAll(file)
if err != nil || string(body) != string(original) || header.Filename != filename || r.FormValue("purpose") != "user_data" {
t.Error("multipart filename, purpose, or bytes changed")
}
case r.Method == http.MethodGet && r.URL.Path == "/files/"+filesWorkflowID:
inspections.Add(1)
if name == "inspection fails" {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotFound)
_, _ = io.WriteString(w, `{"error":{"message":"Synthetic file missing."}}`)
return
}
case r.Method == http.MethodGet && r.URL.Path == "/files/"+filesWorkflowID+"/content":
downloads.Add(1)
w.Header().Set("Content-Type", "application/octet-stream")
_, _ = w.Write(original)
return
default:
t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
}
w.Header().Set("Content-Type", "application/json")
_, _ = io.WriteString(w, response)
}))
defer server.Close()
inheritedEndpoint := server.URL
environment := map[string]string{}
var flags []string
if project != "" {
flags = []string{"--project=" + project, "--organization=" + organization, "--base-url=" + server.URL}
// A missing copied base URL must fail locally, without contacting another backend.
inheritedEndpoint = "http://127.0.0.1:1"
}
switch name {
case "empty project":
flags = []string{"--project="}
environment["OPENAI_PROJECT_ID"] = "project-inherited"
case "API key override":
flags = []string{"--api-key=" + key}
case "header override":
flags = []string{"--header=X-Synthetic: fake-private-header"}
case "URL query":
flags = []string{"--base-url=" + server.URL + "?token=fake-private-query"}
}
args := []string{"openai", "files", "upload", filename, "--purpose=user_data"}
if name == "context before command" {
args = append(append([]string{"openai"}, flags...), args[1:]...)
} else {
args = append(args, flags...)
}
receipt := runFilesReceiptPTY(t, python, bash, work, inheritedEndpoint, args, environment)
require.Zero(t, receipt.code, "%+v", receipt)
require.Empty(t, receipt.stderr)
require.Contains(t, receipt.stdout, "Uploaded "+filename)
require.EqualValues(t, 1, uploads.Load())
for _, private := range []string{"sk-fake-", "fake-private-header", "fake-private-query"} {
require.NotContains(t, receipt.stdout, private)
}
_, command, found := strings.Cut(receipt.stdout, "\nInspect it: ")
if name == "API key override" || name == "header override" || name == "URL query" {
require.False(t, found)
require.NotContains(t, receipt.stdout, "Download it:")
return
}
require.True(t, found, receipt.stdout)
require.NotContains(t, command, "--output")
require.NotContains(t, command, " download ")
changed := []byte("local edits after the upload\n")
require.NoError(t, os.WriteFile(filepath.Join(work, filename), changed, 0o600))
entriesBefore, err := os.ReadDir(work)
require.NoError(t, err)
inspection := runFilesReceiptPTYCommand(t, python, bash, work, inheritedEndpoint, strings.TrimSpace(command), nil, environment)
if name == "inspection fails" {
require.NotZero(t, inspection.code)
require.Contains(t, inspection.stderr, "HTTP 404:")
require.Contains(t, inspection.stderr, "Check the resource or model ID.")
require.Empty(t, inspection.stdout)
} else {
require.Zero(t, inspection.code, "%+v", inspection)
require.Empty(t, inspection.stderr)
if name == "processing error" {
require.JSONEq(t, response, inspection.stdout)
} else {
require.Contains(t, inspection.stdout, filesWorkflowID)
}
}
contents, err := os.ReadFile(filepath.Join(work, filename))
require.NoError(t, err)
require.Equal(t, changed, contents)
require.EqualValues(t, 1, inspections.Load())
require.Zero(t, downloads.Load())
entries, err := os.ReadDir(work)
require.NoError(t, err)
require.Equal(t, entriesBefore, entries, "read-only follow-up must not create a destination")
})
}
}
Loading
Loading