├── .github ├── ISSUE_TEMPLATE │ ├── 2-bug-report.yml │ └── 3-docs-issue.yml ├── demo.gif └── workflows │ ├── ci.yml │ └── cla.yml ├── .gitignore ├── .husky └── pre-commit ├── .npmrc ├── .prettierignore ├── .prettierrc.toml ├── CHANGELOG.md ├── LICENSE ├── NOTICE ├── PNPM.md ├── README.md ├── cliff.toml ├── codex-cli ├── .dockerignore ├── .editorconfig ├── .eslintrc.cjs ├── Dockerfile ├── HUSKY.md ├── bin │ └── codex.js ├── build.mjs ├── examples │ ├── README.md │ ├── build-codex-demo │ │ ├── run.sh │ │ ├── runs │ │ │ └── .gitkeep │ │ └── task.yaml │ ├── camerascii │ │ ├── run.sh │ │ ├── runs │ │ │ └── .gitkeep │ │ ├── task.yaml │ │ └── template │ │ │ └── screenshot_details.md │ ├── impossible-pong │ │ ├── run.sh │ │ ├── runs │ │ │ └── .gitkeep │ │ ├── task.yaml │ │ └── template │ │ │ └── index.html │ ├── prompt-analyzer │ │ ├── run.sh │ │ ├── runs │ │ │ └── .gitkeep │ │ ├── task.yaml │ │ └── template │ │ │ ├── Clustering.ipynb │ │ │ ├── README.md │ │ │ ├── analysis.md │ │ │ ├── analysis_dbscan.md │ │ │ ├── cluster_prompts.py │ │ │ ├── plots │ │ │ ├── cluster_sizes.png │ │ │ └── tsne.png │ │ │ ├── plots_dbscan │ │ │ ├── cluster_sizes.png │ │ │ └── tsne.png │ │ │ └── prompts.csv │ └── prompting_guide.md ├── ignore-react-devtools-plugin.js ├── package.json ├── require-shim.js ├── scripts │ ├── build_container.sh │ ├── init_firewall.sh │ └── run_in_container.sh ├── src │ ├── app.tsx │ ├── approvals.ts │ ├── cli-singlepass.tsx │ ├── cli.tsx │ ├── components │ │ ├── approval-mode-overlay.tsx │ │ ├── chat │ │ │ ├── message-history.tsx │ │ │ ├── multiline-editor.tsx │ │ │ ├── terminal-chat-command-review.tsx │ │ │ ├── terminal-chat-completions.tsx │ │ │ ├── terminal-chat-input-thinking.tsx │ │ │ ├── terminal-chat-input.tsx │ │ │ ├── terminal-chat-new-input.tsx │ │ │ ├── terminal-chat-past-rollout.tsx │ │ │ ├── terminal-chat-response-item.tsx │ │ │ ├── terminal-chat-tool-call-command.tsx │ │ │ ├── terminal-chat.tsx │ │ │ ├── terminal-header.tsx │ │ │ ├── terminal-message-history.tsx │ │ │ └── use-message-grouping.ts │ │ ├── diff-overlay.tsx │ │ ├── help-overlay.tsx │ │ ├── history-overlay.tsx │ │ ├── model-overlay.tsx │ │ ├── onboarding │ │ │ └── onboarding-approval-mode.tsx │ │ ├── select-input │ │ │ ├── indicator.tsx │ │ │ ├── item.tsx │ │ │ └── select-input.tsx │ │ ├── singlepass-cli-app.tsx │ │ ├── typeahead-overlay.tsx │ │ └── vendor │ │ │ ├── cli-spinners │ │ │ └── index.js │ │ │ ├── ink-select │ │ │ ├── index.js │ │ │ ├── option-map.js │ │ │ ├── select-option.js │ │ │ ├── select.js │ │ │ ├── theme.js │ │ │ ├── use-select-state.js │ │ │ └── use-select.js │ │ │ ├── ink-spinner.tsx │ │ │ └── ink-text-input.tsx │ ├── format-command.ts │ ├── hooks │ │ ├── use-confirmation.ts │ │ └── use-terminal-size.ts │ ├── parse-apply-patch.ts │ ├── shims-external.d.ts │ ├── text-buffer.ts │ ├── typings.d.ts │ └── utils │ │ ├── agent │ │ ├── agent-loop.ts │ │ ├── apply-patch.ts │ │ ├── exec.ts │ │ ├── handle-exec-command.ts │ │ ├── parse-apply-patch.ts │ │ ├── platform-commands.ts │ │ ├── review.ts │ │ └── sandbox │ │ │ ├── create-truncating-collector.ts │ │ │ ├── interface.ts │ │ │ ├── macos-seatbelt.ts │ │ │ └── raw-exec.ts │ │ ├── approximate-tokens-used.ts │ │ ├── auto-approval-mode.js │ │ ├── auto-approval-mode.ts │ │ ├── bug-report.ts │ │ ├── check-in-git.ts │ │ ├── check-updates.ts │ │ ├── compact-summary.ts │ │ ├── config.ts │ │ ├── extract-applied-patches.ts │ │ ├── file-system-suggestions.ts │ │ ├── get-diff.ts │ │ ├── input-utils.ts │ │ ├── logger │ │ └── log.ts │ │ ├── model-info.ts │ │ ├── model-utils.ts │ │ ├── package-manager-detector.ts │ │ ├── parsers.ts │ │ ├── providers.ts │ │ ├── responses.ts │ │ ├── session.ts │ │ ├── short-path.ts │ │ ├── singlepass │ │ ├── code_diff.ts │ │ ├── context.ts │ │ ├── context_files.ts │ │ ├── context_limit.ts │ │ └── file_ops.ts │ │ ├── slash-commands.ts │ │ ├── storage │ │ ├── command-history.ts │ │ └── save-rollout.ts │ │ ├── terminal-chat-utils.ts │ │ └── terminal.ts ├── tests │ ├── __fixtures__ │ │ ├── a.txt │ │ └── b.txt │ ├── __snapshots__ │ │ └── check-updates.test.ts.snap │ ├── agent-cancel-early.test.ts │ ├── agent-cancel-prev-response.test.ts │ ├── agent-cancel-race.test.ts │ ├── agent-cancel.test.ts │ ├── agent-function-call-id.test.ts │ ├── agent-generic-network-error.test.ts │ ├── agent-interrupt-continue.test.ts │ ├── agent-invalid-request-error.test.ts │ ├── agent-max-tokens-error.test.ts │ ├── agent-network-errors.test.ts │ ├── agent-project-doc.test.ts │ ├── agent-rate-limit-error.test.ts │ ├── agent-server-retry.test.ts │ ├── agent-terminate.test.ts │ ├── agent-thinking-time.test.ts │ ├── api-key.test.ts │ ├── apply-patch.test.ts │ ├── approvals.test.ts │ ├── cancel-exec.test.ts │ ├── check-updates.test.ts │ ├── clear-command.test.tsx │ ├── config.test.tsx │ ├── create-truncating-collector.test.ts │ ├── dummy.test.ts │ ├── exec-apply-patch.test.ts │ ├── external-editor.test.ts │ ├── file-system-suggestions.test.ts │ ├── fixed-requires-shell.test.ts │ ├── format-command.test.ts │ ├── history-overlay.test.tsx │ ├── input-utils.test.ts │ ├── invalid-command-handling.test.ts │ ├── markdown.test.tsx │ ├── model-info.test.ts │ ├── model-utils-network-error.test.ts │ ├── model-utils.test.ts │ ├── multiline-ctrl-enter-submit.test.tsx │ ├── multiline-dynamic-width.test.tsx │ ├── multiline-enter-submit-cr.test.tsx │ ├── multiline-external-editor-shortcut.test.tsx │ ├── multiline-history-behavior.test.tsx │ ├── multiline-input-test.ts │ ├── multiline-newline.test.tsx │ ├── multiline-shift-enter-crlf.test.tsx │ ├── multiline-shift-enter-mod1.test.tsx │ ├── multiline-shift-enter.test.tsx │ ├── package-manager-detector.test.ts │ ├── parse-apply-patch.test.ts │ ├── pipe-command.test.ts │ ├── project-doc.test.ts │ ├── raw-exec-process-group.test.ts │ ├── requires-shell.test.ts │ ├── responses-chat-completions.test.ts │ ├── slash-commands.test.ts │ ├── terminal-chat-completions.test.tsx │ ├── terminal-chat-input-compact.test.tsx │ ├── terminal-chat-input-multiline.test.tsx │ ├── terminal-chat-response-item.test.tsx │ ├── text-buffer-copy-paste.test.ts │ ├── text-buffer-crlf.test.ts │ ├── text-buffer-gaps.test.ts │ ├── text-buffer-word.test.ts │ ├── text-buffer.test.ts │ ├── typeahead-scroll.test.tsx │ └── ui-test-helpers.tsx ├── tsconfig.json └── vite.config.ts ├── docs └── CLA.md ├── flake.lock ├── flake.nix ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml └── scripts ├── asciicheck.py └── readme_toc.py /.github/ISSUE_TEMPLATE/2-bug-report.yml: -------------------------------------------------------------------------------- 1 | name: 🪲 Bug Report 2 | description: Report an issue that should be fixed 3 | labels: 4 | - bug 5 | - needs triage 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | Thank you for submitting a bug report! It helps make Codex better for everyone. 11 | 12 | If you need help or support using Codex, and are not reporting a bug, please post on [codex/discussions](https://github.com/openai/codex/discussions), where you can ask questions or engage with others on ideas for how to improve codex. 13 | 14 | Make sure you are running the [latest](https://npmjs.com/package/@openai/codex) version of Codex CLI. The bug you are experiencing may already have been fixed. 15 | 16 | Please try to include as much information as possible. 17 | 18 | - type: input 19 | id: version 20 | attributes: 21 | label: What version of Codex is running? 22 | description: Copy the output of `codex --revision` 23 | - type: input 24 | id: model 25 | attributes: 26 | label: Which model were you using? 27 | description: Like `gpt-4.1`, `o4-mini`, `o3`, etc. 28 | - type: input 29 | id: platform 30 | attributes: 31 | label: What platform is your computer? 32 | description: | 33 | For MacOS and Linux: copy the output of `uname -mprs` 34 | For Windows: copy the output of `"$([Environment]::OSVersion | ForEach-Object VersionString) $(if ([Environment]::Is64BitOperatingSystem) { "x64" } else { "x86" })"` in the PowerShell console 35 | - type: textarea 36 | id: steps 37 | attributes: 38 | label: What steps can reproduce the bug? 39 | description: Explain the bug and provide a code snippet that can reproduce it. 40 | validations: 41 | required: true 42 | - type: textarea 43 | id: expected 44 | attributes: 45 | label: What is the expected behavior? 46 | description: If possible, please provide text instead of a screenshot. 47 | - type: textarea 48 | id: actual 49 | attributes: 50 | label: What do you see instead? 51 | description: If possible, please provide text instead of a screenshot. 52 | - type: textarea 53 | id: notes 54 | attributes: 55 | label: Additional information 56 | description: Is there anything else you think we should know? 57 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/3-docs-issue.yml: -------------------------------------------------------------------------------- 1 | name: 📗 Documentation Issue 2 | description: Tell us if there is missing or incorrect documentation 3 | labels: [docs] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thank you for submitting a documentation request. It helps make Codex better. 9 | - type: dropdown 10 | attributes: 11 | label: What is the type of issue? 12 | multiple: true 13 | options: 14 | - Documentation is missing 15 | - Documentation is incorrect 16 | - Documentation is confusing 17 | - Example code is not working 18 | - Something else 19 | - type: textarea 20 | attributes: 21 | label: What is the issue? 22 | validations: 23 | required: true 24 | - type: textarea 25 | attributes: 26 | label: Where did you find it? 27 | description: If possible, please provide the URL(s) where you found this issue. -------------------------------------------------------------------------------- /.github/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openai/codex/ad1e39c9037c12e3a7756f1be33d7f3019ec52d2/.github/demo.gif -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | pull_request: { branches: [main] } 5 | push: { branches: [main] } 6 | 7 | jobs: 8 | build-test: 9 | runs-on: ubuntu-latest 10 | timeout-minutes: 10 11 | env: 12 | NODE_OPTIONS: --max-old-space-size=4096 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v4 16 | 17 | - name: Setup Node.js 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: 22 21 | 22 | - name: Setup pnpm 23 | uses: pnpm/action-setup@v4 24 | with: 25 | version: 10.8.1 26 | run_install: false 27 | 28 | - name: Get pnpm store directory 29 | id: pnpm-cache 30 | shell: bash 31 | run: | 32 | echo "store_path=$(pnpm store path --silent)" >> $GITHUB_OUTPUT 33 | 34 | - name: Setup pnpm cache 35 | uses: actions/cache@v4 36 | with: 37 | path: ${{ steps.pnpm-cache.outputs.store_path }} 38 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 39 | restore-keys: | 40 | ${{ runner.os }}-pnpm-store- 41 | 42 | - name: Install dependencies 43 | run: pnpm install 44 | 45 | # Run all tasks using workspace filters 46 | 47 | - name: Check TypeScript code formatting 48 | working-directory: codex-cli 49 | run: pnpm run format 50 | 51 | - name: Check Markdown and config file formatting 52 | run: pnpm run format 53 | 54 | - name: Run tests 55 | run: pnpm run test 56 | 57 | - name: Lint 58 | run: | 59 | pnpm --filter @openai/codex exec -- eslint src tests --ext ts --ext tsx \ 60 | --report-unused-disable-directives \ 61 | --rule "no-console:error" \ 62 | --rule "no-debugger:error" \ 63 | --max-warnings=-1 64 | 65 | - name: Type-check 66 | run: pnpm run typecheck 67 | 68 | - name: Build 69 | run: pnpm run build 70 | 71 | - name: Ensure README.md contains only ASCII and certain Unicode code points 72 | run: ./scripts/asciicheck.py README.md 73 | - name: Check README ToC 74 | run: python3 scripts/readme_toc.py README.md 75 | -------------------------------------------------------------------------------- /.github/workflows/cla.yml: -------------------------------------------------------------------------------- 1 | name: CLA Assistant 2 | on: 3 | issue_comment: 4 | types: [created] 5 | pull_request_target: 6 | types: [opened, closed, synchronize] 7 | 8 | permissions: 9 | actions: write 10 | contents: write 11 | pull-requests: write 12 | statuses: write 13 | 14 | jobs: 15 | cla: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: contributor-assistant/github-action@v2.6.1 19 | if: | 20 | github.event_name == 'pull_request_target' || 21 | github.event.comment.body == 'recheck' || 22 | github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA' 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | with: 26 | path-to-document: docs/CLA.md 27 | path-to-signatures: signatures/cla.json 28 | branch: cla-signatures 29 | allowlist: dependabot[bot] 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # deps 2 | # Node.js dependencies 3 | node_modules 4 | .pnpm-store 5 | .pnpm-debug.log 6 | 7 | # Keep pnpm-lock.yaml 8 | !pnpm-lock.yaml 9 | 10 | # build 11 | dist/ 12 | build/ 13 | out/ 14 | storybook-static/ 15 | 16 | # ignore README for publishing 17 | codex-cli/README.md 18 | 19 | # ignore Nix derivation results 20 | result 21 | 22 | # editor 23 | .vscode/ 24 | .idea/ 25 | .history/ 26 | .zed/ 27 | *.swp 28 | *~ 29 | 30 | # cli tools 31 | CLAUDE.md 32 | .claude/ 33 | 34 | # caches 35 | .cache/ 36 | .turbo/ 37 | .parcel-cache/ 38 | .eslintcache 39 | .nyc_output/ 40 | .jest/ 41 | *.tsbuildinfo 42 | 43 | # logs 44 | *.log 45 | npm-debug.log* 46 | yarn-debug.log* 47 | yarn-error.log* 48 | 49 | # env 50 | .env* 51 | !.env.example 52 | 53 | # package 54 | *.tgz 55 | 56 | # ci 57 | .vercel/ 58 | .netlify/ 59 | 60 | # patches 61 | apply_patch/ 62 | 63 | # coverage 64 | coverage/ 65 | 66 | # os 67 | .DS_Store 68 | Thumbs.db 69 | Icon? 70 | .Spotlight-V100/ 71 | 72 | # Unwanted package managers 73 | .yarn/ 74 | yarn.lock 75 | 76 | # release 77 | package.json-e 78 | session.ts-e 79 | CHANGELOG.ignore.md 80 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpm lint-staged -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | strict-peer-dependencies=false 3 | node-linker=hoisted 4 | prefer-workspace-packages=true 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /codex-cli/dist 2 | /codex-cli/node_modules 3 | pnpm-lock.yaml 4 | -------------------------------------------------------------------------------- /.prettierrc.toml: -------------------------------------------------------------------------------- 1 | printWidth = 80 2 | quoteProps = "consistent" 3 | semi = true 4 | tabWidth = 2 5 | trailingComma = "all" 6 | 7 | # Preserve existing behavior for markdown/text wrapping. 8 | proseWrap = "preserve" 9 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | OpenAI Codex 2 | Copyright 2025 OpenAI 3 | -------------------------------------------------------------------------------- /PNPM.md: -------------------------------------------------------------------------------- 1 | # Migration to pnpm 2 | 3 | This project has been migrated from npm to pnpm to improve dependency management and developer experience. 4 | 5 | ## Why pnpm? 6 | 7 | - **Faster installation**: pnpm is significantly faster than npm and yarn 8 | - **Disk space savings**: pnpm uses a content-addressable store to avoid duplication 9 | - **Phantom dependency prevention**: pnpm creates a strict node_modules structure 10 | - **Native workspaces support**: simplified monorepo management 11 | 12 | ## How to use pnpm 13 | 14 | ### Installation 15 | 16 | ```bash 17 | # Global installation of pnpm 18 | npm install -g pnpm@10.8.1 19 | 20 | # Or with corepack (available with Node.js 22+) 21 | corepack enable 22 | corepack prepare pnpm@10.8.1 --activate 23 | ``` 24 | 25 | ### Common commands 26 | 27 | | npm command | pnpm equivalent | 28 | | --------------- | ---------------- | 29 | | `npm install` | `pnpm install` | 30 | | `npm run build` | `pnpm run build` | 31 | | `npm test` | `pnpm test` | 32 | | `npm run lint` | `pnpm run lint` | 33 | 34 | ### Workspace-specific commands 35 | 36 | | Action | Command | 37 | | ------------------------------------------ | ---------------------------------------- | 38 | | Run a command in a specific package | `pnpm --filter @openai/codex run build` | 39 | | Install a dependency in a specific package | `pnpm --filter @openai/codex add lodash` | 40 | | Run a command in all packages | `pnpm -r run test` | 41 | 42 | ## Monorepo structure 43 | 44 | ``` 45 | codex/ 46 | ├── pnpm-workspace.yaml # Workspace configuration 47 | ├── .npmrc # pnpm configuration 48 | ├── package.json # Root dependencies and scripts 49 | ├── codex-cli/ # Main package 50 | │ └── package.json # codex-cli specific dependencies 51 | └── docs/ # Documentation (future package) 52 | ``` 53 | 54 | ## Configuration files 55 | 56 | - **pnpm-workspace.yaml**: Defines the packages included in the monorepo 57 | - **.npmrc**: Configures pnpm behavior 58 | - **Root package.json**: Contains shared scripts and dependencies 59 | 60 | ## CI/CD 61 | 62 | CI/CD workflows have been updated to use pnpm instead of npm. Make sure your CI environments use pnpm 10.8.1 or higher. 63 | 64 | ## Known issues 65 | 66 | If you encounter issues with pnpm, try the following solutions: 67 | 68 | 1. Remove the `node_modules` folder and `pnpm-lock.yaml` file, then run `pnpm install` 69 | 2. Make sure you're using pnpm 10.8.1 or higher 70 | 3. Verify that Node.js 22 or higher is installed 71 | -------------------------------------------------------------------------------- /cliff.toml: -------------------------------------------------------------------------------- 1 | # https://git-cliff.org/docs/configuration 2 | 3 | [changelog] 4 | header = """ 5 | # Changelog 6 | 7 | You can install any of these versions: `npm install -g codex@version` 8 | """ 9 | 10 | body = """ 11 | {% if version -%} 12 | ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} 13 | {%- else %} 14 | ## [unreleased] 15 | {% endif %} 16 | 17 | {%- for group, commits in commits | group_by(attribute="group") %} 18 | ### {{ group | striptags | trim }} 19 | 20 | {% for commit in commits %}- {% if commit.scope %}*({{ commit.scope }})* {% endif %}{% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }} 21 | {% endfor %} 22 | 23 | {%- endfor -%} 24 | """ 25 | 26 | footer = """ 27 | 28 | """ 29 | 30 | trim = true 31 | postprocessors = [] 32 | 33 | [git] 34 | conventional_commits = true 35 | 36 | commit_parsers = [ 37 | { message = "^feat", group = "🚀 Features" }, 38 | { message = "^fix", group = "🐛 Bug Fixes" }, 39 | { message = "^bump", group = "🛳️ Release" }, 40 | # Fallback – skip anything that didn't match the above rules. 41 | { message = ".*", group = "💼 Other" }, 42 | ] 43 | 44 | filter_unconventional = false 45 | sort_commits = "oldest" 46 | topo_order = false -------------------------------------------------------------------------------- /codex-cli/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /codex-cli/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | 7 | [*.{js,ts,jsx,tsx}] 8 | indent_style = space 9 | indent_size = 2 -------------------------------------------------------------------------------- /codex-cli/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20-slim 2 | 3 | ARG TZ 4 | ENV TZ="$TZ" 5 | 6 | # Install basic development tools, ca-certificates, and iptables/ipset, then clean up apt cache to reduce image size 7 | RUN apt-get update && apt-get install -y --no-install-recommends \ 8 | aggregate \ 9 | ca-certificates \ 10 | curl \ 11 | dnsutils \ 12 | fzf \ 13 | gh \ 14 | git \ 15 | gnupg2 \ 16 | iproute2 \ 17 | ipset \ 18 | iptables \ 19 | jq \ 20 | less \ 21 | man-db \ 22 | procps \ 23 | sudo \ 24 | unzip \ 25 | ripgrep \ 26 | zsh \ 27 | && rm -rf /var/lib/apt/lists/* 28 | 29 | # Ensure default node user has access to /usr/local/share 30 | RUN mkdir -p /usr/local/share/npm-global && \ 31 | chown -R node:node /usr/local/share 32 | 33 | ARG USERNAME=node 34 | 35 | # Set up non-root user 36 | USER node 37 | 38 | # Install global packages 39 | ENV NPM_CONFIG_PREFIX=/usr/local/share/npm-global 40 | ENV PATH=$PATH:/usr/local/share/npm-global/bin 41 | 42 | # Install codex 43 | COPY dist/codex.tgz codex.tgz 44 | RUN npm install -g codex.tgz \ 45 | && npm cache clean --force \ 46 | && rm -rf /usr/local/share/npm-global/lib/node_modules/codex-cli/node_modules/.cache \ 47 | && rm -rf /usr/local/share/npm-global/lib/node_modules/codex-cli/tests \ 48 | && rm -rf /usr/local/share/npm-global/lib/node_modules/codex-cli/docs 49 | 50 | # Copy and set up firewall script 51 | COPY scripts/init_firewall.sh /usr/local/bin/ 52 | USER root 53 | RUN chmod +x /usr/local/bin/init_firewall.sh && \ 54 | echo "node ALL=(root) NOPASSWD: /usr/local/bin/init_firewall.sh" > /etc/sudoers.d/node-firewall && \ 55 | chmod 0440 /etc/sudoers.d/node-firewall 56 | USER node 57 | -------------------------------------------------------------------------------- /codex-cli/HUSKY.md: -------------------------------------------------------------------------------- 1 | # Husky Git Hooks 2 | 3 | This project uses [Husky](https://typicode.github.io/husky/) to enforce code quality checks before commits and pushes. 4 | 5 | ## What's Included 6 | 7 | - **Pre-commit Hook**: Runs lint-staged to check files that are about to be committed. 8 | 9 | - Lints and formats TypeScript/TSX files using ESLint and Prettier 10 | - Formats JSON, MD, and YML files using Prettier 11 | 12 | - **Pre-push Hook**: Runs tests and type checking before pushing to the remote repository. 13 | - Executes `npm test` to run all tests 14 | - Executes `npm run typecheck` to check TypeScript types 15 | 16 | ## Benefits 17 | 18 | - Ensures consistent code style across the project 19 | - Prevents pushing code with failing tests or type errors 20 | - Reduces the need for style-related code review comments 21 | - Improves overall code quality 22 | 23 | ## For Contributors 24 | 25 | You don't need to do anything special to use these hooks. They will automatically run when you commit or push code. 26 | 27 | If you need to bypass the hooks in exceptional cases: 28 | 29 | ```bash 30 | # Skip pre-commit hooks 31 | git commit -m "Your message" --no-verify 32 | 33 | # Skip pre-push hooks 34 | git push --no-verify 35 | ``` 36 | 37 | Note: Please use these bypass options sparingly and only when absolutely necessary. 38 | 39 | ## Troubleshooting 40 | 41 | If you encounter any issues with the hooks: 42 | 43 | 1. Make sure you have the latest dependencies installed: `npm install` 44 | 2. Ensure the hook scripts are executable (Unix systems): `chmod +x .husky/pre-commit .husky/pre-push` 45 | 3. Check if there are any ESLint or Prettier configuration issues in your code 46 | -------------------------------------------------------------------------------- /codex-cli/bin/codex.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // Unified entry point for Codex CLI on all platforms 4 | // Dynamically loads the compiled ESM bundle in dist/cli.js 5 | 6 | import path from 'path'; 7 | import { fileURLToPath, pathToFileURL } from 'url'; 8 | 9 | // Determine this script's directory 10 | const __filename = fileURLToPath(import.meta.url); 11 | const __dirname = path.dirname(__filename); 12 | 13 | // Resolve the path to the compiled CLI bundle 14 | const cliPath = path.resolve(__dirname, '../dist/cli.js'); 15 | const cliUrl = pathToFileURL(cliPath).href; 16 | 17 | // Load and execute the CLI 18 | (async () => { 19 | try { 20 | await import(cliUrl); 21 | } catch (err) { 22 | // eslint-disable-next-line no-console 23 | console.error(err); 24 | // eslint-disable-next-line no-undef 25 | process.exit(1); 26 | } 27 | })(); 28 | -------------------------------------------------------------------------------- /codex-cli/build.mjs: -------------------------------------------------------------------------------- 1 | import * as esbuild from "esbuild"; 2 | import * as fs from "fs"; 3 | import * as path from "path"; 4 | 5 | const OUT_DIR = 'dist' 6 | /** 7 | * ink attempts to import react-devtools-core in an ESM-unfriendly way: 8 | * 9 | * https://github.com/vadimdemedes/ink/blob/eab6ef07d4030606530d58d3d7be8079b4fb93bb/src/reconciler.ts#L22-L45 10 | * 11 | * to make this work, we have to strip the import out of the build. 12 | */ 13 | const ignoreReactDevToolsPlugin = { 14 | name: "ignore-react-devtools", 15 | setup(build) { 16 | // When an import for 'react-devtools-core' is encountered, 17 | // return an empty module. 18 | build.onResolve({ filter: /^react-devtools-core$/ }, (args) => { 19 | return { path: args.path, namespace: "ignore-devtools" }; 20 | }); 21 | build.onLoad({ filter: /.*/, namespace: "ignore-devtools" }, () => { 22 | return { contents: "", loader: "js" }; 23 | }); 24 | }, 25 | }; 26 | 27 | // ---------------------------------------------------------------------------- 28 | // Build mode detection (production vs development) 29 | // 30 | // • production (default): minified, external telemetry shebang handling. 31 | // • development (--dev|NODE_ENV=development|CODEX_DEV=1): 32 | // – no minification 33 | // – inline source maps for better stacktraces 34 | // – shebang tweaked to enable Node's source‑map support at runtime 35 | // ---------------------------------------------------------------------------- 36 | 37 | const isDevBuild = 38 | process.argv.includes("--dev") || 39 | process.env.CODEX_DEV === "1" || 40 | process.env.NODE_ENV === "development"; 41 | 42 | const plugins = [ignoreReactDevToolsPlugin]; 43 | 44 | // Build Hygiene, ensure we drop previous dist dir and any leftover files 45 | const outPath = path.resolve(OUT_DIR); 46 | if (fs.existsSync(outPath)) { 47 | fs.rmSync(outPath, { recursive: true, force: true }); 48 | } 49 | 50 | // Add a shebang that enables source‑map support for dev builds so that stack 51 | // traces point to the original TypeScript lines without requiring callers to 52 | // remember to set NODE_OPTIONS manually. 53 | if (isDevBuild) { 54 | const devShebangLine = 55 | "#!/usr/bin/env -S NODE_OPTIONS=--enable-source-maps node\n"; 56 | const devShebangPlugin = { 57 | name: "dev-shebang", 58 | setup(build) { 59 | build.onEnd(async () => { 60 | const outFile = path.resolve(isDevBuild ? `${OUT_DIR}/cli-dev.js` : `${OUT_DIR}/cli.js`); 61 | let code = await fs.promises.readFile(outFile, "utf8"); 62 | if (code.startsWith("#!")) { 63 | code = code.replace(/^#!.*\n/, devShebangLine); 64 | await fs.promises.writeFile(outFile, code, "utf8"); 65 | } 66 | }); 67 | }, 68 | }; 69 | plugins.push(devShebangPlugin); 70 | } 71 | 72 | esbuild 73 | .build({ 74 | entryPoints: ["src/cli.tsx"], 75 | bundle: true, 76 | format: "esm", 77 | platform: "node", 78 | tsconfig: "tsconfig.json", 79 | outfile: isDevBuild ? `${OUT_DIR}/cli-dev.js` : `${OUT_DIR}/cli.js`, 80 | minify: !isDevBuild, 81 | sourcemap: isDevBuild ? "inline" : true, 82 | plugins, 83 | inject: ["./require-shim.js"], 84 | }) 85 | .catch(() => process.exit(1)); 86 | -------------------------------------------------------------------------------- /codex-cli/examples/README.md: -------------------------------------------------------------------------------- 1 | # Quick start examples 2 | 3 | This directory bundles some self‑contained examples using the Codex CLI. If you have never used the Codex CLI before, and want to see it complete a sample task, start with running **camerascii**. You'll see your webcam feed turned into animated ASCII art in a few minutes. 4 | 5 | If you want to get started using the Codex CLI directly, skip this and refer to the prompting guide. 6 | 7 | ## Structure 8 | 9 | Each example contains the following: 10 | ``` 11 | example‑name/ 12 | ├── run.sh # helper script that launches a new Codex session for the task 13 | ├── task.yaml # task spec containing a prompt passed to Codex 14 | ├── template/ # (optional) starter files copied into each run 15 | └── runs/ # work directories created by run.sh 16 | ``` 17 | 18 | **run.sh**: a convenience wrapper that does three things: 19 | - Creates `runs/run_N`, where *N* is the number of a run. 20 | - Copies the contents of `template/` into that folder (if present). 21 | - Launches the Codex CLI with the description from `task.yaml`. 22 | 23 | **template/**: any existing files or markdown instructions you would like Codex to see before it starts working. 24 | 25 | **runs/**: the directories produced by `run.sh`. 26 | 27 | ## Running an example 28 | 29 | 1. **Run the helper script**: 30 | ``` 31 | cd camerascii 32 | ./run.sh 33 | ``` 34 | 2. **Interact with the Codex CLI**: the CLI will open with the prompt: “*Take a look at the screenshot details and implement a webpage that uses a webcam to style the video feed accordingly…*” Confirm the commands Codex CLI requests to generate `index.html`. 35 | 36 | 3. **Check its work**: when Codex is done, open ``runs/run_1/index.html`` in a browser. Your webcam feed should now be rendered as a cascade of ASCII glyphs. If the outcome isn't what you expect, try running it again, or adjust the task prompt. 37 | 38 | 39 | ## Other examples 40 | Besides **camerascii**, you can experiment with: 41 | 42 | - **build‑codex‑demo**: recreate the original 2021 Codex YouTube demo. 43 | - **impossible‑pong**: where Codex creates more difficult levels. 44 | - **prompt‑analyzer**: make a data science app for clustering [prompts](https://github.com/f/awesome-chatgpt-prompts). 45 | -------------------------------------------------------------------------------- /codex-cli/examples/build-codex-demo/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # run.sh — Create a new run_N directory for a Codex task, optionally bootstrapped from a template, 4 | # then launch Codex with the task description from task.yaml. 5 | # 6 | # Usage: 7 | # ./run.sh # Prompts to confirm new run 8 | # ./run.sh --auto-confirm # Skips confirmation 9 | # 10 | # Assumes: 11 | # - yq and jq are installed 12 | # - ../task.yaml exists (with .name and .description fields) 13 | # - ../template/ exists (optional, for bootstrapping new runs) 14 | 15 | # Enable auto-confirm mode if flag is passed 16 | auto_mode=false 17 | [[ "$1" == "--auto-confirm" ]] && auto_mode=true 18 | 19 | # Move into the working directory 20 | cd runs || exit 1 21 | 22 | # Grab task name for logging 23 | task_name=$(yq -o=json '.' ../task.yaml | jq -r '.name') 24 | echo "Checking for runs for task: $task_name" 25 | 26 | # Find existing run_N directories 27 | shopt -s nullglob 28 | run_dirs=(run_[0-9]*) 29 | shopt -u nullglob 30 | 31 | if [ ${#run_dirs[@]} -eq 0 ]; then 32 | echo "There are 0 runs." 33 | new_run_number=1 34 | else 35 | max_run_number=0 36 | for d in "${run_dirs[@]}"; do 37 | [[ "$d" =~ ^run_([0-9]+)$ ]] && (( ${BASH_REMATCH[1]} > max_run_number )) && max_run_number=${BASH_REMATCH[1]} 38 | done 39 | new_run_number=$((max_run_number + 1)) 40 | echo "There are $max_run_number runs." 41 | fi 42 | 43 | # Confirm creation unless in auto mode 44 | if [ "$auto_mode" = false ]; then 45 | read -p "Create run_$new_run_number? (Y/N): " choice 46 | [[ "$choice" != [Yy] ]] && echo "Exiting." && exit 1 47 | fi 48 | 49 | # Create the run directory 50 | mkdir "run_$new_run_number" 51 | 52 | # Check if the template directory exists and copy its contents 53 | if [ -d "../template" ]; then 54 | cp -r ../template/* "run_$new_run_number" 55 | echo "Initialized run_$new_run_number from template/" 56 | else 57 | echo "Template directory does not exist. Skipping initialization from template." 58 | fi 59 | 60 | cd "run_$new_run_number" 61 | 62 | # Launch Codex 63 | echo "Launching..." 64 | description=$(yq -o=json '.' ../../task.yaml | jq -r '.description') 65 | codex "$description" 66 | -------------------------------------------------------------------------------- /codex-cli/examples/build-codex-demo/runs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openai/codex/ad1e39c9037c12e3a7756f1be33d7f3019ec52d2/codex-cli/examples/build-codex-demo/runs/.gitkeep -------------------------------------------------------------------------------- /codex-cli/examples/camerascii/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # run.sh — Create a new run_N directory for a Codex task, optionally bootstrapped from a template, 4 | # then launch Codex with the task description from task.yaml. 5 | # 6 | # Usage: 7 | # ./run.sh # Prompts to confirm new run 8 | # ./run.sh --auto-confirm # Skips confirmation 9 | # 10 | # Assumes: 11 | # - yq and jq are installed 12 | # - ../task.yaml exists (with .name and .description fields) 13 | # - ../template/ exists (optional, for bootstrapping new runs) 14 | 15 | # Enable auto-confirm mode if flag is passed 16 | auto_mode=false 17 | [[ "$1" == "--auto-confirm" ]] && auto_mode=true 18 | 19 | # Create the runs directory if it doesn't exist 20 | mkdir -p runs 21 | 22 | # Move into the working directory 23 | cd runs || exit 1 24 | 25 | # Grab task name for logging 26 | task_name=$(yq -o=json '.' ../task.yaml | jq -r '.name') 27 | echo "Checking for runs for task: $task_name" 28 | 29 | # Find existing run_N directories 30 | shopt -s nullglob 31 | run_dirs=(run_[0-9]*) 32 | shopt -u nullglob 33 | 34 | if [ ${#run_dirs[@]} -eq 0 ]; then 35 | echo "There are 0 runs." 36 | new_run_number=1 37 | else 38 | max_run_number=0 39 | for d in "${run_dirs[@]}"; do 40 | [[ "$d" =~ ^run_([0-9]+)$ ]] && (( ${BASH_REMATCH[1]} > max_run_number )) && max_run_number=${BASH_REMATCH[1]} 41 | done 42 | new_run_number=$((max_run_number + 1)) 43 | echo "There are $max_run_number runs." 44 | fi 45 | 46 | # Confirm creation unless in auto mode 47 | if [ "$auto_mode" = false ]; then 48 | read -p "Create run_$new_run_number? (Y/N): " choice 49 | [[ "$choice" != [Yy] ]] && echo "Exiting." && exit 1 50 | fi 51 | 52 | # Create the run directory 53 | mkdir "run_$new_run_number" 54 | 55 | # Check if the template directory exists and copy its contents 56 | if [ -d "../template" ]; then 57 | cp -r ../template/* "run_$new_run_number" 58 | echo "Initialized run_$new_run_number from template/" 59 | else 60 | echo "Template directory does not exist. Skipping initialization from template." 61 | fi 62 | 63 | cd "run_$new_run_number" 64 | 65 | # Launch Codex 66 | echo "Launching..." 67 | description=$(yq -o=json '.' ../../task.yaml | jq -r '.description') 68 | codex "$description" 69 | -------------------------------------------------------------------------------- /codex-cli/examples/camerascii/runs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openai/codex/ad1e39c9037c12e3a7756f1be33d7f3019ec52d2/codex-cli/examples/camerascii/runs/.gitkeep -------------------------------------------------------------------------------- /codex-cli/examples/camerascii/task.yaml: -------------------------------------------------------------------------------- 1 | name: "camerascii" 2 | description: | 3 | Take a look at the screenshot details and implement a webpage that uses webcam 4 | to style the video feed accordingly (i.e. as ASCII art). Add some of the relevant features 5 | from the screenshot to the webpage in index.html. 6 | -------------------------------------------------------------------------------- /codex-cli/examples/camerascii/template/screenshot_details.md: -------------------------------------------------------------------------------- 1 | ### Screenshot Description 2 | 3 | The image is a full–page screenshot of a single post on the social‑media site X (formerly Twitter). 4 | 5 | 1. **Header row** 6 | * At the very top‑left is a small circular avatar. The photo shows the side profile of a person whose face is softly lit in bluish‑purple tones; only the head and part of the neck are visible. 7 | * In the far upper‑right corner sit two standard X / Twitter interface icons: a circle containing a diagonal line (the “Mute / Block” indicator) and a three‑dot overflow menu. 8 | 9 | 2. **Tweet body text** 10 | * Below the header, in regular type, the author writes: 11 | 12 | “Okay, OpenAI’s o3 is insane. Spent an hour messing with it and built an image‑to‑ASCII art converter, the exact tool I’ve always wanted. And it works so well” 13 | 14 | 3. **Embedded media** 15 | * The majority of the screenshot is occupied by an embedded 12‑second video of the converter UI. The video window has rounded corners and a dark theme. 16 | * **Left panel (tool controls)** – a slim vertical sidebar with the following labeled sections and blue–accented UI controls: 17 | * Theme selector (“Dark” is chosen). 18 | * A small checkbox labeled “Ignore White”. 19 | * **Upload Image** button area that shows the chosen file name. 20 | * **Image Processing** sliders: 21 | * “ASCII Width” (value ≈ 143) 22 | * “Brightness” (‑65) 23 | * “Contrast” (58) 24 | * “Blur (px)” (0.5) 25 | * A square checkbox for “Invert Colors”. 26 | * **Dithering** subsection with a checkbox (“Enable Dithering”) and a dropdown for the algorithm (value: “Noise”). 27 | * **Character Set** dropdown (value: “Detailed (Default)”). 28 | * **Display** slider labeled “Zoom (%)” (value ≈ 170) and a “Reset” button. 29 | 30 | * **Main preview area (right side)** – a dark gray canvas that renders the selected image as white ASCII characters. The preview clearly depicts a stylized **palm tree**: a skinny trunk rises from the bottom centre, and a crown of splayed fronds fills the upper right quadrant. 31 | * A small black badge showing **“0:12”** overlays the bottom‑left corner of the media frame, indicating the video’s duration. 32 | * In the top‑right area of the media window are two pill‑shaped buttons: a heart‑shaped “Save” button and a cog‑shaped “Settings” button. 33 | 34 | Overall, the screenshot shows the user excitedly announcing the success of their custom “Image to ASCII” converter created with OpenAI’s “o3”, accompanied by a short video demonstration of the tool converting a palm‑tree photo into ASCII art. 35 | -------------------------------------------------------------------------------- /codex-cli/examples/impossible-pong/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # run.sh — Create a new run_N directory for a Codex task, optionally bootstrapped from a template, 4 | # then launch Codex with the task description from task.yaml. 5 | # 6 | # Usage: 7 | # ./run.sh # Prompts to confirm new run 8 | # ./run.sh --auto-confirm # Skips confirmation 9 | # 10 | # Assumes: 11 | # - yq and jq are installed 12 | # - ../task.yaml exists (with .name and .description fields) 13 | # - ../template/ exists (optional, for bootstrapping new runs) 14 | 15 | # Enable auto-confirm mode if flag is passed 16 | auto_mode=false 17 | [[ "$1" == "--auto-confirm" ]] && auto_mode=true 18 | 19 | # Create the runs directory if it doesn't exist 20 | mkdir -p runs 21 | 22 | # Move into the working directory 23 | cd runs || exit 1 24 | 25 | # Grab task name for logging 26 | task_name=$(yq -o=json '.' ../task.yaml | jq -r '.name') 27 | echo "Checking for runs for task: $task_name" 28 | 29 | # Find existing run_N directories 30 | shopt -s nullglob 31 | run_dirs=(run_[0-9]*) 32 | shopt -u nullglob 33 | 34 | if [ ${#run_dirs[@]} -eq 0 ]; then 35 | echo "There are 0 runs." 36 | new_run_number=1 37 | else 38 | max_run_number=0 39 | for d in "${run_dirs[@]}"; do 40 | [[ "$d" =~ ^run_([0-9]+)$ ]] && (( ${BASH_REMATCH[1]} > max_run_number )) && max_run_number=${BASH_REMATCH[1]} 41 | done 42 | new_run_number=$((max_run_number + 1)) 43 | echo "There are $max_run_number runs." 44 | fi 45 | 46 | # Confirm creation unless in auto mode 47 | if [ "$auto_mode" = false ]; then 48 | read -p "Create run_$new_run_number? (Y/N): " choice 49 | [[ "$choice" != [Yy] ]] && echo "Exiting." && exit 1 50 | fi 51 | 52 | # Create the run directory 53 | mkdir "run_$new_run_number" 54 | 55 | # Check if the template directory exists and copy its contents 56 | if [ -d "../template" ]; then 57 | cp -r ../template/* "run_$new_run_number" 58 | echo "Initialized run_$new_run_number from template/" 59 | else 60 | echo "Template directory does not exist. Skipping initialization from template." 61 | fi 62 | 63 | cd "run_$new_run_number" 64 | 65 | # Launch Codex 66 | echo "Launching..." 67 | description=$(yq -o=json '.' ../../task.yaml | jq -r '.description') 68 | codex "$description" 69 | -------------------------------------------------------------------------------- /codex-cli/examples/impossible-pong/runs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openai/codex/ad1e39c9037c12e3a7756f1be33d7f3019ec52d2/codex-cli/examples/impossible-pong/runs/.gitkeep -------------------------------------------------------------------------------- /codex-cli/examples/impossible-pong/task.yaml: -------------------------------------------------------------------------------- 1 | name: "impossible-pong" 2 | description: | 3 | Update index.html with the following features: 4 | - Add an overlayed styled popup to start the game on first load 5 | - Between each point, show a 3 second countdown (this should be skipped if a player wins) 6 | - After each game the AI wins, display text at the bottom of the screen with lighthearted insults for the player 7 | - Add a leaderboard to the right of the court that shows how many games each player has won. 8 | - When a player wins, a styled popup appears with the winner's name and the option to play again. The leaderboard should update. 9 | - Add an "even more insane" difficulty mode that adds spin to the ball that makes it harder to predict. 10 | - Add an "even more(!!) insane" difficulty mode where the ball does a spin mid court and then picks a random (reasonable) direction to go in (this should only advantage the AI player) 11 | - Let the user choose which difficulty mode they want to play in on the popup that appears when the game starts. 12 | -------------------------------------------------------------------------------- /codex-cli/examples/prompt-analyzer/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # run.sh — Create a new run_N directory for a Codex task, optionally bootstrapped from a template, 4 | # then launch Codex with the task description from task.yaml. 5 | # 6 | # Usage: 7 | # ./run.sh # Prompts to confirm new run 8 | # ./run.sh --auto-confirm # Skips confirmation 9 | # 10 | # Assumes: 11 | # - yq and jq are installed 12 | # - ../task.yaml exists (with .name and .description fields) 13 | # - ../template/ exists (optional, for bootstrapping new runs) 14 | 15 | # Enable auto-confirm mode if flag is passed 16 | auto_mode=false 17 | [[ "$1" == "--auto-confirm" ]] && auto_mode=true 18 | 19 | # Create the runs directory if it doesn't exist 20 | mkdir -p runs 21 | 22 | # Move into the working directory 23 | cd runs || exit 1 24 | 25 | # Grab task name for logging 26 | task_name=$(yq -o=json '.' ../task.yaml | jq -r '.name') 27 | echo "Checking for runs for task: $task_name" 28 | 29 | # Find existing run_N directories 30 | shopt -s nullglob 31 | run_dirs=(run_[0-9]*) 32 | shopt -u nullglob 33 | 34 | if [ ${#run_dirs[@]} -eq 0 ]; then 35 | echo "There are 0 runs." 36 | new_run_number=1 37 | else 38 | max_run_number=0 39 | for d in "${run_dirs[@]}"; do 40 | [[ "$d" =~ ^run_([0-9]+)$ ]] && (( ${BASH_REMATCH[1]} > max_run_number )) && max_run_number=${BASH_REMATCH[1]} 41 | done 42 | new_run_number=$((max_run_number + 1)) 43 | echo "There are $max_run_number runs." 44 | fi 45 | 46 | # Confirm creation unless in auto mode 47 | if [ "$auto_mode" = false ]; then 48 | read -p "Create run_$new_run_number? (Y/N): " choice 49 | [[ "$choice" != [Yy] ]] && echo "Exiting." && exit 1 50 | fi 51 | 52 | # Create the run directory 53 | mkdir "run_$new_run_number" 54 | 55 | # Check if the template directory exists and copy its contents 56 | if [ -d "../template" ]; then 57 | cp -r ../template/* "run_$new_run_number" 58 | echo "Initialized run_$new_run_number from template/" 59 | else 60 | echo "Template directory does not exist. Skipping initialization from template." 61 | fi 62 | 63 | cd "run_$new_run_number" 64 | 65 | # Launch Codex 66 | echo "Launching..." 67 | description=$(yq -o=json '.' ../../task.yaml | jq -r '.description') 68 | codex "$description" 69 | -------------------------------------------------------------------------------- /codex-cli/examples/prompt-analyzer/runs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openai/codex/ad1e39c9037c12e3a7756f1be33d7f3019ec52d2/codex-cli/examples/prompt-analyzer/runs/.gitkeep -------------------------------------------------------------------------------- /codex-cli/examples/prompt-analyzer/task.yaml: -------------------------------------------------------------------------------- 1 | name: "prompt-analyzer" 2 | description: | 3 | I have some existing work here (embedding prompts, clustering them, generating 4 | summaries with GPT). I want to make it more interactive and reusable. 5 | 6 | Objective: create an interactive cluster explorer 7 | - Build a lightweight streamlit app UI 8 | - Allow users to upload a CSV of prompts 9 | - Display clustered prompts with auto-generated cluster names and summaries 10 | - Click "cluster" and see progress stream in a small window (primarily for aesthetic reasons) 11 | - Let users browse examples by cluster, view outliers, and inspect individual prompts 12 | - See generated analysis rendered in the app, along with the plots displayed nicely 13 | - Support selecting clustering algorithms (e.g. DBSCAN, KMeans, etc) and "recluster" 14 | - Include token count + histogram of prompt lengths 15 | - Add interactive filters in UI (e.g. filter by token length, keyword, or cluster) 16 | 17 | When you're done, update the README.md with a changelog and instructions for how to run the app. 18 | -------------------------------------------------------------------------------- /codex-cli/examples/prompt-analyzer/template/analysis.md: -------------------------------------------------------------------------------- 1 | # Prompt Clustering Report 2 | 3 | Generated by `cluster_prompts.py` – 2025-04-16 4 | 5 | 6 | ## Overview 7 | 8 | * Total prompts: **213** 9 | * Clustering method: **kmeans** 10 | * k (K‑Means): **2** 11 | * Silhouette score: **0.042** 12 | * Final clusters (excluding noise): **2** 13 | 14 | 15 | | label | name | #prompts | description | 16 | |-------|------|---------:|-------------| 17 | | 0 | Creative Guidance Roles | 121 | This cluster encompasses a variety of roles where individuals provide expert advice, suggestions, and creative ideas across different fields. Each role, be it interior decorator, comedian, IT architect, or artist advisor, focuses on enhancing the expertise and creativity of others by tailoring advice to specific requests and contexts. | 18 | | 1 | Role Customization Requests | 92 | This cluster contains various requests for role-specific assistance across different domains, including web development, language processing, IT troubleshooting, and creative endeavors. Each snippet illustrates a unique role that a user wishes to engage with, focusing on specific tasks without requiring explanations. | 19 | 20 | --- 21 | ## Plots 22 | 23 | The directory `plots/` contains a bar chart of the cluster sizes and a t‑SNE scatter plot coloured by cluster. 24 | -------------------------------------------------------------------------------- /codex-cli/examples/prompt-analyzer/template/analysis_dbscan.md: -------------------------------------------------------------------------------- 1 | # Prompt Clustering Report 2 | 3 | Generated by `cluster_prompts.py` – 2025-04-16 4 | 5 | 6 | ## Overview 7 | 8 | * Total prompts: **213** 9 | * Clustering method: **dbscan** 10 | * Final clusters (excluding noise): **1** 11 | 12 | 13 | | label | name | #prompts | description | 14 | |-------|------|---------:|-------------| 15 | | -1 | Noise / Outlier | 10 | Prompts that do not cleanly belong to any cluster. | 16 | | 0 | Role Simulation Tasks | 203 | This cluster consists of varied role-playing scenarios where users request an AI to assume specific professional roles, such as composer, dream interpreter, doctor, or IT architect. Each snippet showcases tasks that involve creating content, providing advice, or performing analytical functions based on user-defined themes or prompts. | 17 | 18 | --- 19 | 20 | ## Plots 21 | 22 | The directory `plots/` contains a bar chart of the cluster sizes and a t‑SNE scatter plot coloured by cluster. 23 | -------------------------------------------------------------------------------- /codex-cli/examples/prompt-analyzer/template/plots/cluster_sizes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openai/codex/ad1e39c9037c12e3a7756f1be33d7f3019ec52d2/codex-cli/examples/prompt-analyzer/template/plots/cluster_sizes.png -------------------------------------------------------------------------------- /codex-cli/examples/prompt-analyzer/template/plots/tsne.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openai/codex/ad1e39c9037c12e3a7756f1be33d7f3019ec52d2/codex-cli/examples/prompt-analyzer/template/plots/tsne.png -------------------------------------------------------------------------------- /codex-cli/examples/prompt-analyzer/template/plots_dbscan/cluster_sizes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openai/codex/ad1e39c9037c12e3a7756f1be33d7f3019ec52d2/codex-cli/examples/prompt-analyzer/template/plots_dbscan/cluster_sizes.png -------------------------------------------------------------------------------- /codex-cli/examples/prompt-analyzer/template/plots_dbscan/tsne.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openai/codex/ad1e39c9037c12e3a7756f1be33d7f3019ec52d2/codex-cli/examples/prompt-analyzer/template/plots_dbscan/tsne.png -------------------------------------------------------------------------------- /codex-cli/ignore-react-devtools-plugin.js: -------------------------------------------------------------------------------- 1 | // ignore-react-devtools-plugin.js 2 | const ignoreReactDevToolsPlugin = { 3 | name: "ignore-react-devtools", 4 | setup(build) { 5 | // When an import for 'react-devtools-core' is encountered, 6 | // return an empty module. 7 | build.onResolve({ filter: /^react-devtools-core$/ }, (args) => { 8 | return { path: args.path, namespace: "ignore-devtools" }; 9 | }); 10 | build.onLoad({ filter: /.*/, namespace: "ignore-devtools" }, () => { 11 | return { contents: "", loader: "js" }; 12 | }); 13 | }, 14 | }; 15 | 16 | module.exports = ignoreReactDevToolsPlugin; 17 | -------------------------------------------------------------------------------- /codex-cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@openai/codex", 3 | "version": "0.1.2504221401", 4 | "license": "Apache-2.0", 5 | "bin": { 6 | "codex": "bin/codex.js" 7 | }, 8 | "type": "module", 9 | "engines": { 10 | "node": ">=22" 11 | }, 12 | "scripts": { 13 | "format": "prettier --check src tests", 14 | "format:fix": "prettier --write src tests", 15 | "dev": "tsc --watch", 16 | "lint": "eslint src tests --ext ts --ext tsx --report-unused-disable-directives --max-warnings 0", 17 | "lint:fix": "eslint src tests --ext ts --ext tsx --fix", 18 | "test": "vitest run", 19 | "test:watch": "vitest --watch", 20 | "typecheck": "tsc --noEmit", 21 | "build": "node build.mjs", 22 | "build:dev": "NODE_ENV=development node build.mjs --dev && NODE_OPTIONS=--enable-source-maps node dist/cli-dev.js", 23 | "release:readme": "cp ../README.md ./README.md", 24 | "release:version": "TS=$(date +%y%m%d%H%M) && sed -E -i'' -e \"s/\\\"0\\.1\\.[0-9]{10}\\\"/\\\"0.1.${TS}\\\"/g\" package.json src/utils/session.ts", 25 | "release:build-and-publish": "pnpm run build && npm publish", 26 | "release": "pnpm run release:readme && pnpm run release:version && pnpm install && pnpm run release:build-and-publish" 27 | }, 28 | "files": [ 29 | "dist" 30 | ], 31 | "dependencies": { 32 | "@inkjs/ui": "^2.0.0", 33 | "chalk": "^5.2.0", 34 | "diff": "^7.0.0", 35 | "dotenv": "^16.1.4", 36 | "fast-deep-equal": "^3.1.3", 37 | "fast-npm-meta": "^0.4.2", 38 | "figures": "^6.1.0", 39 | "file-type": "^20.1.0", 40 | "ink": "^5.2.0", 41 | "js-yaml": "^4.1.0", 42 | "marked": "^15.0.7", 43 | "marked-terminal": "^7.3.0", 44 | "meow": "^13.2.0", 45 | "open": "^10.1.0", 46 | "openai": "^4.95.1", 47 | "package-manager-detector": "^1.2.0", 48 | "react": "^18.2.0", 49 | "shell-quote": "^1.8.2", 50 | "strip-ansi": "^7.1.0", 51 | "to-rotated": "^1.0.0", 52 | "use-interval": "1.4.0", 53 | "zod": "^3.24.3" 54 | }, 55 | "devDependencies": { 56 | "@eslint/js": "^9.22.0", 57 | "@types/diff": "^7.0.2", 58 | "@types/js-yaml": "^4.0.9", 59 | "@types/marked-terminal": "^6.1.1", 60 | "@types/react": "^18.0.32", 61 | "@types/semver": "^7.7.0", 62 | "@types/shell-quote": "^1.7.5", 63 | "@types/which": "^3.0.4", 64 | "@typescript-eslint/eslint-plugin": "^7.18.0", 65 | "@typescript-eslint/parser": "^7.18.0", 66 | "boxen": "^8.0.1", 67 | "esbuild": "^0.25.2", 68 | "eslint-plugin-import": "^2.31.0", 69 | "eslint-plugin-react": "^7.32.2", 70 | "eslint-plugin-react-hooks": "^4.6.0", 71 | "eslint-plugin-react-refresh": "^0.4.19", 72 | "husky": "^9.1.7", 73 | "ink-testing-library": "^3.0.0", 74 | "prettier": "^2.8.7", 75 | "punycode": "^2.3.1", 76 | "semver": "^7.7.1", 77 | "ts-node": "^10.9.1", 78 | "typescript": "^5.0.3", 79 | "vitest": "^3.0.9", 80 | "whatwg-url": "^14.2.0", 81 | "which": "^5.0.0" 82 | }, 83 | "repository": { 84 | "type": "git", 85 | "url": "https://github.com/openai/codex" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /codex-cli/require-shim.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is necessary because we have transitive dependencies on CommonJS modules 3 | * that use require() conditionally: 4 | * 5 | * https://github.com/tapjs/signal-exit/blob/v3.0.7/index.js#L26-L27 6 | * 7 | * This is not compatible with ESM, so we need to shim require() to use the 8 | * CommonJS module loader. 9 | */ 10 | import { createRequire } from "module"; 11 | globalThis.require = createRequire(import.meta.url); 12 | -------------------------------------------------------------------------------- /codex-cli/scripts/build_container.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | SCRIPT_DIR=$(realpath "$(dirname "$0")") 6 | trap "popd >> /dev/null" EXIT 7 | pushd "$SCRIPT_DIR/.." >> /dev/null || { 8 | echo "Error: Failed to change directory to $SCRIPT_DIR/.." 9 | exit 1 10 | } 11 | npm install 12 | npm run build 13 | rm -rf ./dist/openai-codex-*.tgz 14 | npm pack --pack-destination ./dist 15 | mv ./dist/openai-codex-*.tgz ./dist/codex.tgz 16 | docker build -t codex -f "./Dockerfile" . 17 | -------------------------------------------------------------------------------- /codex-cli/scripts/run_in_container.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Usage: 5 | # ./run_in_container.sh [--work_dir directory] "COMMAND" 6 | # 7 | # Examples: 8 | # ./run_in_container.sh --work_dir project/code "ls -la" 9 | # ./run_in_container.sh "echo Hello, world!" 10 | 11 | # Default the work directory to WORKSPACE_ROOT_DIR if not provided. 12 | WORK_DIR="${WORKSPACE_ROOT_DIR:-$(pwd)}" 13 | 14 | # Parse optional flag. 15 | if [ "$1" = "--work_dir" ]; then 16 | if [ -z "$2" ]; then 17 | echo "Error: --work_dir flag provided but no directory specified." 18 | exit 1 19 | fi 20 | WORK_DIR="$2" 21 | shift 2 22 | fi 23 | 24 | WORK_DIR=$(realpath "$WORK_DIR") 25 | 26 | # Generate a unique container name based on the normalized work directory 27 | CONTAINER_NAME="codex_$(echo "$WORK_DIR" | sed 's/\//_/g' | sed 's/[^a-zA-Z0-9_-]//g')" 28 | 29 | # Define cleanup to remove the container on script exit, ensuring no leftover containers 30 | cleanup() { 31 | docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true 32 | } 33 | # Trap EXIT to invoke cleanup regardless of how the script terminates 34 | trap cleanup EXIT 35 | 36 | # Ensure a command is provided. 37 | if [ "$#" -eq 0 ]; then 38 | echo "Usage: $0 [--work_dir directory] \"COMMAND\"" 39 | exit 1 40 | fi 41 | 42 | # Check if WORK_DIR is set. 43 | if [ -z "$WORK_DIR" ]; then 44 | echo "Error: No work directory provided and WORKSPACE_ROOT_DIR is not set." 45 | exit 1 46 | fi 47 | 48 | # Kill any existing container for the working directory using cleanup(), centralizing removal logic. 49 | cleanup 50 | 51 | # Run the container with the specified directory mounted at the same path inside the container. 52 | docker run --name "$CONTAINER_NAME" -d \ 53 | -e OPENAI_API_KEY \ 54 | --cap-add=NET_ADMIN \ 55 | --cap-add=NET_RAW \ 56 | -v "$WORK_DIR:/app$WORK_DIR" \ 57 | codex \ 58 | sleep infinity 59 | 60 | # Initialize the firewall inside the container. 61 | docker exec "$CONTAINER_NAME" bash -c "sudo /usr/local/bin/init_firewall.sh" 62 | 63 | # Execute the provided command in the container, ensuring it runs in the work directory. 64 | # We use a parameterized bash command to safely handle the command and directory. 65 | 66 | quoted_args="" 67 | for arg in "$@"; do 68 | quoted_args+=" $(printf '%q' "$arg")" 69 | done 70 | docker exec -it "$CONTAINER_NAME" bash -c "cd \"/app$WORK_DIR\" && codex --full-auto ${quoted_args}" 71 | -------------------------------------------------------------------------------- /codex-cli/src/app.tsx: -------------------------------------------------------------------------------- 1 | import type { ApprovalPolicy } from "./approvals"; 2 | import type { AppConfig } from "./utils/config"; 3 | import type { ResponseItem } from "openai/resources/responses/responses"; 4 | 5 | import TerminalChat from "./components/chat/terminal-chat"; 6 | import TerminalChatPastRollout from "./components/chat/terminal-chat-past-rollout"; 7 | import { checkInGit } from "./utils/check-in-git"; 8 | import { CLI_VERSION, type TerminalChatSession } from "./utils/session.js"; 9 | import { onExit } from "./utils/terminal"; 10 | import { ConfirmInput } from "@inkjs/ui"; 11 | import { Box, Text, useApp, useStdin } from "ink"; 12 | import React, { useMemo, useState } from "react"; 13 | 14 | export type AppRollout = { 15 | session: TerminalChatSession; 16 | items: Array; 17 | }; 18 | 19 | type Props = { 20 | prompt?: string; 21 | config: AppConfig; 22 | imagePaths?: Array; 23 | rollout?: AppRollout; 24 | approvalPolicy: ApprovalPolicy; 25 | additionalWritableRoots: ReadonlyArray; 26 | fullStdout: boolean; 27 | }; 28 | 29 | export default function App({ 30 | prompt, 31 | config, 32 | rollout, 33 | imagePaths, 34 | approvalPolicy, 35 | additionalWritableRoots, 36 | fullStdout, 37 | }: Props): JSX.Element { 38 | const app = useApp(); 39 | const [accepted, setAccepted] = useState(() => false); 40 | const [cwd, inGitRepo] = useMemo( 41 | () => [process.cwd(), checkInGit(process.cwd())], 42 | [], 43 | ); 44 | const { internal_eventEmitter } = useStdin(); 45 | internal_eventEmitter.setMaxListeners(20); 46 | 47 | if (rollout) { 48 | return ( 49 | 53 | ); 54 | } 55 | 56 | if (!inGitRepo && !accepted) { 57 | return ( 58 | 59 | 60 | 61 | ● OpenAI Codex{" "} 62 | 63 | (research preview) v{CLI_VERSION} 64 | 65 | 66 | 67 | 73 | 74 | Warning! It can be dangerous to run a 75 | coding agent outside of a git repo in case there are changes that 76 | you want to revert. Do you want to continue? 77 | 78 | {cwd} 79 | { 82 | app.exit(); 83 | onExit(); 84 | // eslint-disable-next-line 85 | console.error( 86 | "Quitting! Run again to accept or from inside a git repo", 87 | ); 88 | }} 89 | onConfirm={() => setAccepted(true)} 90 | /> 91 | 92 | 93 | ); 94 | } 95 | 96 | return ( 97 | 105 | ); 106 | } 107 | -------------------------------------------------------------------------------- /codex-cli/src/cli-singlepass.tsx: -------------------------------------------------------------------------------- 1 | import type { AppConfig } from "./utils/config"; 2 | 3 | import { SinglePassApp } from "./components/singlepass-cli-app"; 4 | import { render } from "ink"; 5 | import React from "react"; 6 | 7 | export async function runSinglePass({ 8 | originalPrompt, 9 | config, 10 | rootPath, 11 | }: { 12 | originalPrompt?: string; 13 | config: AppConfig; 14 | rootPath: string; 15 | }): Promise { 16 | return new Promise((resolve) => { 17 | render( 18 | resolve()} 23 | />, 24 | ); 25 | }); 26 | } 27 | 28 | export default {}; 29 | -------------------------------------------------------------------------------- /codex-cli/src/components/approval-mode-overlay.tsx: -------------------------------------------------------------------------------- 1 | import TypeaheadOverlay from "./typeahead-overlay.js"; 2 | import { AutoApprovalMode } from "../utils/auto-approval-mode.js"; 3 | import { Text } from "ink"; 4 | import React from "react"; 5 | 6 | type Props = { 7 | currentMode: string; 8 | onSelect: (mode: string) => void; 9 | onExit: () => void; 10 | }; 11 | 12 | /** 13 | * Overlay to switch between the different automatic‑approval policies. 14 | * 15 | * The list of available modes is derived from the AutoApprovalMode enum so we 16 | * stay in sync with the core agent behaviour. It re‑uses the generic 17 | * TypeaheadOverlay component for the actual UI/UX. 18 | */ 19 | export default function ApprovalModeOverlay({ 20 | currentMode, 21 | onSelect, 22 | onExit, 23 | }: Props): JSX.Element { 24 | const items = React.useMemo( 25 | () => 26 | Object.values(AutoApprovalMode).map((m) => ({ 27 | label: m, 28 | value: m, 29 | })), 30 | [], 31 | ); 32 | 33 | return ( 34 | 38 | Current mode: {currentMode} 39 | 40 | } 41 | initialItems={items} 42 | currentValue={currentMode} 43 | onSelect={onSelect} 44 | onExit={onExit} 45 | /> 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /codex-cli/src/components/chat/message-history.tsx: -------------------------------------------------------------------------------- 1 | import type { TerminalHeaderProps } from "./terminal-header.js"; 2 | import type { GroupedResponseItem } from "./use-message-grouping.js"; 3 | import type { ResponseItem } from "openai/resources/responses/responses.mjs"; 4 | 5 | import TerminalChatResponseItem from "./terminal-chat-response-item.js"; 6 | import TerminalHeader from "./terminal-header.js"; 7 | import { Box, Static } from "ink"; 8 | import React from "react"; 9 | 10 | // A batch entry can either be a standalone response item or a grouped set of 11 | // items (e.g. auto‑approved tool‑call batches) that should be rendered 12 | // together. 13 | type BatchEntry = { item?: ResponseItem; group?: GroupedResponseItem }; 14 | type MessageHistoryProps = { 15 | batch: Array; 16 | groupCounts: Record; 17 | items: Array; 18 | userMsgCount: number; 19 | confirmationPrompt: React.ReactNode; 20 | loading: boolean; 21 | headerProps: TerminalHeaderProps; 22 | }; 23 | 24 | const MessageHistory: React.FC = ({ 25 | batch, 26 | headerProps, 27 | }) => { 28 | const messages = batch.map(({ item }) => item!); 29 | 30 | return ( 31 | 32 | {/* 33 | * The Static component receives a mixed array of the literal string 34 | * "header" plus the streamed ResponseItem objects. After filtering out 35 | * the header entry we can safely treat the remaining values as 36 | * ResponseItem, however TypeScript cannot infer the refined type from 37 | * the runtime check and therefore reports property‑access errors. 38 | * 39 | * A short cast after the refinement keeps the implementation tidy while 40 | * preserving type‑safety. 41 | */} 42 | 43 | {(item, index) => { 44 | if (item === "header") { 45 | return ; 46 | } 47 | 48 | // After the guard above `item` can only be a ResponseItem. 49 | const message = item as ResponseItem; 50 | return ( 51 | 71 | 72 | 73 | ); 74 | }} 75 | 76 | 77 | ); 78 | }; 79 | 80 | export default React.memo(MessageHistory); 81 | -------------------------------------------------------------------------------- /codex-cli/src/components/chat/terminal-chat-completions.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Text } from "ink"; 2 | import React, { useMemo } from "react"; 3 | 4 | type TextCompletionProps = { 5 | /** 6 | * Array of text completion options to display in the list 7 | */ 8 | completions: Array; 9 | 10 | /** 11 | * Maximum number of completion items to show at once in the view 12 | */ 13 | displayLimit: number; 14 | 15 | /** 16 | * Index of the currently selected completion in the completions array 17 | */ 18 | selectedCompletion: number; 19 | }; 20 | 21 | function TerminalChatCompletions({ 22 | completions, 23 | selectedCompletion, 24 | displayLimit, 25 | }: TextCompletionProps): JSX.Element { 26 | const visibleItems = useMemo(() => { 27 | // Try to keep selection centered in view 28 | let startIndex = Math.max( 29 | 0, 30 | selectedCompletion - Math.floor(displayLimit / 2), 31 | ); 32 | 33 | // Fix window position when at the end of the list 34 | if (completions.length - startIndex < displayLimit) { 35 | startIndex = Math.max(0, completions.length - displayLimit); 36 | } 37 | 38 | const endIndex = Math.min(completions.length, startIndex + displayLimit); 39 | 40 | return completions.slice(startIndex, endIndex).map((completion, index) => ({ 41 | completion, 42 | originalIndex: index + startIndex, 43 | })); 44 | }, [completions, selectedCompletion, displayLimit]); 45 | 46 | return ( 47 | 48 | {visibleItems.map(({ completion, originalIndex }) => ( 49 | 57 | {completion} 58 | 59 | ))} 60 | 61 | ); 62 | } 63 | 64 | export default TerminalChatCompletions; 65 | -------------------------------------------------------------------------------- /codex-cli/src/components/chat/terminal-chat-past-rollout.tsx: -------------------------------------------------------------------------------- 1 | import type { TerminalChatSession } from "../../utils/session.js"; 2 | import type { ResponseItem } from "openai/resources/responses/responses"; 3 | 4 | import TerminalChatResponseItem from "./terminal-chat-response-item"; 5 | import { Box, Text } from "ink"; 6 | import React from "react"; 7 | 8 | export default function TerminalChatPastRollout({ 9 | session, 10 | items, 11 | }: { 12 | session: TerminalChatSession; 13 | items: Array; 14 | }): React.ReactElement { 15 | const { version, id: sessionId, model } = session; 16 | return ( 17 | 18 | 19 | 20 | ● OpenAI Codex{" "} 21 | 22 | (research preview) v{version} 23 | 24 | 25 | 26 | 33 | 34 | localhost{" "} 35 | · session:{" "} 36 | 37 | {sessionId} 38 | 39 | 40 | 41 | When / Who:{" "} 42 | 43 | {session.timestamp} / {session.user} 44 | 45 | 46 | 47 | model: {model} 48 | 49 | 50 | 51 | {React.useMemo( 52 | () => 53 | items.map((item, key) => ( 54 | 55 | )), 56 | [items], 57 | )} 58 | 59 | 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /codex-cli/src/components/chat/terminal-header.tsx: -------------------------------------------------------------------------------- 1 | import type { AgentLoop } from "../../utils/agent/agent-loop.js"; 2 | 3 | import { Box, Text } from "ink"; 4 | import path from "node:path"; 5 | import React from "react"; 6 | 7 | export interface TerminalHeaderProps { 8 | terminalRows: number; 9 | version: string; 10 | PWD: string; 11 | model: string; 12 | provider?: string; 13 | approvalPolicy: string; 14 | colorsByPolicy: Record; 15 | agent?: AgentLoop; 16 | initialImagePaths?: Array; 17 | flexModeEnabled?: boolean; 18 | } 19 | 20 | const TerminalHeader: React.FC = ({ 21 | terminalRows, 22 | version, 23 | PWD, 24 | model, 25 | provider = "openai", 26 | approvalPolicy, 27 | colorsByPolicy, 28 | agent, 29 | initialImagePaths, 30 | flexModeEnabled = false, 31 | }) => { 32 | return ( 33 | <> 34 | {terminalRows < 10 ? ( 35 | // Compact header for small terminal windows 36 | 37 | ● Codex v{version} - {PWD} - {model} ({provider}) -{" "} 38 | {approvalPolicy} 39 | {flexModeEnabled ? " - flex-mode" : ""} 40 | 41 | ) : ( 42 | <> 43 | 44 | 45 | ● OpenAI Codex{" "} 46 | 47 | (research preview) v{version} 48 | 49 | 50 | 51 | 58 | 59 | localhost session:{" "} 60 | 61 | {agent?.sessionId ?? ""} 62 | 63 | 64 | 65 | workdir: {PWD} 66 | 67 | 68 | model: {model} 69 | 70 | 71 | provider:{" "} 72 | {provider} 73 | 74 | 75 | approval:{" "} 76 | 77 | {approvalPolicy} 78 | 79 | 80 | {flexModeEnabled && ( 81 | 82 | flex-mode:{" "} 83 | enabled 84 | 85 | )} 86 | {initialImagePaths?.map((img, idx) => ( 87 | 88 | image:{" "} 89 | {path.basename(img)} 90 | 91 | ))} 92 | 93 | 94 | )} 95 | 96 | ); 97 | }; 98 | 99 | export default TerminalHeader; 100 | -------------------------------------------------------------------------------- /codex-cli/src/components/chat/terminal-message-history.tsx: -------------------------------------------------------------------------------- 1 | import type { OverlayModeType } from "./terminal-chat.js"; 2 | import type { TerminalHeaderProps } from "./terminal-header.js"; 3 | import type { GroupedResponseItem } from "./use-message-grouping.js"; 4 | import type { ResponseItem } from "openai/resources/responses/responses.mjs"; 5 | 6 | import TerminalChatResponseItem from "./terminal-chat-response-item.js"; 7 | import TerminalHeader from "./terminal-header.js"; 8 | import { Box, Static } from "ink"; 9 | import React, { useMemo } from "react"; 10 | 11 | // A batch entry can either be a standalone response item or a grouped set of 12 | // items (e.g. auto‑approved tool‑call batches) that should be rendered 13 | // together. 14 | type BatchEntry = { item?: ResponseItem; group?: GroupedResponseItem }; 15 | type TerminalMessageHistoryProps = { 16 | batch: Array; 17 | groupCounts: Record; 18 | items: Array; 19 | userMsgCount: number; 20 | confirmationPrompt: React.ReactNode; 21 | loading: boolean; 22 | thinkingSeconds: number; 23 | headerProps: TerminalHeaderProps; 24 | fullStdout: boolean; 25 | setOverlayMode: React.Dispatch>; 26 | }; 27 | 28 | const TerminalMessageHistory: React.FC = ({ 29 | batch, 30 | headerProps, 31 | // `loading` and `thinkingSeconds` handled by input component now. 32 | loading: _loading, 33 | thinkingSeconds: _thinkingSeconds, 34 | fullStdout, 35 | setOverlayMode, 36 | }) => { 37 | // Flatten batch entries to response items. 38 | const messages = useMemo(() => batch.map(({ item }) => item!), [batch]); 39 | 40 | return ( 41 | 42 | {/* The dedicated thinking indicator in the input area now displays the 43 | elapsed time, so we no longer render a separate counter here. */} 44 | 45 | {(item, index) => { 46 | if (item === "header") { 47 | return ; 48 | } 49 | 50 | // After the guard above, item is a ResponseItem 51 | const message = item as ResponseItem; 52 | // Suppress empty reasoning updates (i.e. items with an empty summary). 53 | const msg = message as unknown as { summary?: Array }; 54 | if (msg.summary?.length === 0) { 55 | return null; 56 | } 57 | return ( 58 | 68 | 73 | 74 | ); 75 | }} 76 | 77 | 78 | ); 79 | }; 80 | 81 | export default React.memo(TerminalMessageHistory); 82 | -------------------------------------------------------------------------------- /codex-cli/src/components/chat/use-message-grouping.ts: -------------------------------------------------------------------------------- 1 | import type { ResponseItem } from "openai/resources/responses/responses.mjs"; 2 | 3 | /** 4 | * Represents a grouped sequence of response items (e.g., function call batches). 5 | */ 6 | export type GroupedResponseItem = { 7 | label: string; 8 | items: Array; 9 | }; 10 | -------------------------------------------------------------------------------- /codex-cli/src/components/diff-overlay.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Text, useInput } from "ink"; 2 | import React, { useState } from "react"; 3 | 4 | /** 5 | * Simple scrollable view for displaying a diff. 6 | * The component is intentionally lightweight and mirrors the UX of 7 | * HistoryOverlay: Up/Down or j/k to scroll, PgUp/PgDn for paging and Esc to 8 | * close. The caller is responsible for computing the diff text. 9 | */ 10 | export default function DiffOverlay({ 11 | diffText, 12 | onExit, 13 | }: { 14 | diffText: string; 15 | onExit: () => void; 16 | }): JSX.Element { 17 | const lines = diffText.length > 0 ? diffText.split("\n") : ["(no changes)"]; 18 | 19 | const [cursor, setCursor] = useState(0); 20 | 21 | // Determine how many rows we can display – similar to HistoryOverlay. 22 | const rows = process.stdout.rows || 24; 23 | const headerRows = 2; 24 | const footerRows = 1; 25 | const maxVisible = Math.max(4, rows - headerRows - footerRows); 26 | 27 | useInput((input, key) => { 28 | if (key.escape || input === "q") { 29 | onExit(); 30 | return; 31 | } 32 | 33 | if (key.downArrow || input === "j") { 34 | setCursor((c) => Math.min(lines.length - 1, c + 1)); 35 | } else if (key.upArrow || input === "k") { 36 | setCursor((c) => Math.max(0, c - 1)); 37 | } else if (key.pageDown) { 38 | setCursor((c) => Math.min(lines.length - 1, c + maxVisible)); 39 | } else if (key.pageUp) { 40 | setCursor((c) => Math.max(0, c - maxVisible)); 41 | } else if (input === "g") { 42 | setCursor(0); 43 | } else if (input === "G") { 44 | setCursor(lines.length - 1); 45 | } 46 | }); 47 | 48 | const firstVisible = Math.min( 49 | Math.max(0, cursor - Math.floor(maxVisible / 2)), 50 | Math.max(0, lines.length - maxVisible), 51 | ); 52 | const visible = lines.slice(firstVisible, firstVisible + maxVisible); 53 | 54 | // Very small helper to colorize diff lines in a basic way. 55 | function renderLine(line: string, idx: number): JSX.Element { 56 | let color: "green" | "red" | "cyan" | undefined = undefined; 57 | if (line.startsWith("+")) { 58 | color = "green"; 59 | } else if (line.startsWith("-")) { 60 | color = "red"; 61 | } else if (line.startsWith("@@") || line.startsWith("diff --git")) { 62 | color = "cyan"; 63 | } 64 | return ( 65 | 66 | {line === "" ? " " : line} 67 | 68 | ); 69 | } 70 | 71 | return ( 72 | 78 | 79 | Working tree diff ({lines.length} lines) 80 | 81 | 82 | 83 | {visible.map((line, idx) => { 84 | return renderLine(line, firstVisible + idx); 85 | })} 86 | 87 | 88 | 89 | esc Close ↑↓ Scroll PgUp/PgDn g/G First/Last 90 | 91 | 92 | ); 93 | } 94 | -------------------------------------------------------------------------------- /codex-cli/src/components/help-overlay.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Text, useInput } from "ink"; 2 | import React from "react"; 3 | 4 | /** 5 | * An overlay that lists the available slash‑commands and their description. 6 | * The overlay is purely informational and can be dismissed with the Escape 7 | * key. Keeping the implementation extremely small avoids adding any new 8 | * dependencies or complex state handling. 9 | */ 10 | export default function HelpOverlay({ 11 | onExit, 12 | }: { 13 | onExit: () => void; 14 | }): JSX.Element { 15 | useInput((input, key) => { 16 | if (key.escape || input === "q") { 17 | onExit(); 18 | } 19 | }); 20 | 21 | return ( 22 | 28 | 29 | Available commands 30 | 31 | 32 | 33 | 34 | Slash‑commands 35 | 36 | 37 | /help – show this help overlay 38 | 39 | 40 | /model – switch the LLM model in‑session 41 | 42 | 43 | /approval – switch auto‑approval mode 44 | 45 | 46 | /history – show command & file history 47 | for this session 48 | 49 | 50 | /clear – clear screen & context 51 | 52 | 53 | /clearhistory – clear command history 54 | 55 | 56 | /bug – file a bug report with session log 57 | 58 | 59 | /diff – view working tree git diff 60 | 61 | 62 | /compact – condense context into a summary 63 | 64 | 65 | 66 | 67 | Keyboard shortcuts 68 | 69 | 70 | 71 | Enter – send message 72 | 73 | 74 | Ctrl+J – insert newline 75 | 76 | {/* Re-enable once we re-enable new input */} 77 | {/* 78 | 79 | Ctrl+X/Ctrl+E 80 |  – open external editor ($EDITOR) 81 | 82 | */} 83 | 84 | Up/Down – scroll prompt history 85 | 86 | 87 | 88 | Esc(✕2) 89 | {" "} 90 | – interrupt current action 91 | 92 | 93 | Ctrl+C – quit Codex 94 | 95 | 96 | 97 | 98 | esc or q to close 99 | 100 | 101 | ); 102 | } 103 | -------------------------------------------------------------------------------- /codex-cli/src/components/onboarding/onboarding-approval-mode.tsx: -------------------------------------------------------------------------------- 1 | // @ts-expect-error select.js is JavaScript and has no types 2 | import { Select } from "../vendor/ink-select/select"; 3 | import { Box, Text } from "ink"; 4 | import React from "react"; 5 | import { AutoApprovalMode } from "src/utils/auto-approval-mode"; 6 | 7 | // TODO: figure out why `cli-spinners` fails on Node v20.9.0 8 | // which is why we have to do this in the first place 9 | 10 | export function OnboardingApprovalMode(): React.ReactElement { 11 | return ( 12 | 13 | Choose what you want to have to approve: 14 |