fix: publish hanzo-tunnel to crates.io, gate tunnel behind feature flag, fix CI

- hanzo-tunnel v0.1.0 published to crates.io (was path dep to ../../../tunnel)
- cli: tunnel + tunnel-bridge deps now optional behind `tunnel` feature flag
- cli: cloud module (ZT + relay) gated with #[cfg(feature = "tunnel")]
- tunnel-bridge: all hanzo_tunnel usage gated behind `tunnel` feature
- CI: fix prettier by scoping JS globs, excluding symlinked CLAUDE.md
- CI: format all workflows and docs with prettier
- Workspace compiles clean without tunnel feature (CI default)
- Local builds with `--features tunnel` still work with crates.io dep
This commit is contained in:
2026-03-11 20:28:33 -07:00
parent 43327c8b10
commit 1ba5db103e
34 changed files with 1294 additions and 776 deletions
+1 -2
View File
@@ -97,7 +97,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
node-version: "20"
- name: Cache npm (npx) downloads
uses: actions/cache@v4
@@ -234,7 +234,6 @@ jobs:
# Wait briefly for readiness
for i in {1..30}; do if nc -z 127.0.0.1 5057; then break; else sleep 0.2; fi; done || true
- name: Run Code agent (workspace-write)
id: run_agent
continue-on-error: true
-1
View File
@@ -292,7 +292,6 @@ jobs:
echo '### openai-proxy.log (tail)' >> "$GITHUB_STEP_SUMMARY"
{ echo '```'; tail -n 80 .github/auto/openai-proxy.log || true; echo '```'; } >> "$GITHUB_STEP_SUMMARY"
- name: Generate comment with Code (read-only)
id: gen
if: steps.meta.outputs.pr_number && env.OPENAI_API_KEY != ''
+4 -4
View File
@@ -62,9 +62,9 @@ jobs:
# Default is strict (no fallback) to preserve bot authorship.
# Authorship: PAT only (no fallbacks). Ensure CODE_GH_PAT has org repo write perms.
# Rate limiting configuration
RATE_WINDOW_HOURS: '24'
RATE_PER_ACTOR: '5' # Max issues per actor in window to auto-act
RATE_TOTAL_AUTO_PRS: '50' # Max auto PRs opened by bot in window
RATE_WINDOW_HOURS: "24"
RATE_PER_ACTOR: "5" # Max issues per actor in window to auto-act
RATE_TOTAL_AUTO_PRS: "50" # Max auto PRs opened by bot in window
# Path policy configuration
PROTECTED_GLOBS: |
.github/**
@@ -117,7 +117,7 @@ jobs:
timeout-minutes: 5
uses: actions/setup-node@v4
with:
node-version: '20'
node-version: "20"
# No Rust or heavy npm usage in triage; skip related caches and toolchains.
+18 -18
View File
@@ -2,18 +2,18 @@ name: Release
on:
push:
branches: [ main ]
branches: [main]
# Ignore common non-release paths, but DO NOT ignore release.yml so that
# edits to this workflow can intentionally trigger a new release run.
paths-ignore:
- '.github/workflows/issue-triage.yml'
- '.github/workflows/preview-build.yml'
- '.github/workflows/upstream-merge.yml'
- 'examples/**'
- '**/*.test.ts'
- 'test/**'
- 'CHANGELOG.md'
- 'docs/release-notes/**'
- ".github/workflows/issue-triage.yml"
- ".github/workflows/preview-build.yml"
- ".github/workflows/upstream-merge.yml"
- "examples/**"
- "**/*.test.ts"
- "test/**"
- "CHANGELOG.md"
- "docs/release-notes/**"
concurrency:
group: release-${{ github.ref }}
@@ -40,8 +40,8 @@ jobs:
- name: Setup Node.js for auth check
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
node-version: "20"
registry-url: "https://registry.npmjs.org"
- name: Validate npm authentication (must fail fast if missing/invalid)
env:
NODE_AUTH_TOKEN: ${{ env.NPM_TOKEN }}
@@ -83,7 +83,7 @@ jobs:
- name: Setup Python 3.12
uses: actions/setup-python@v5
with:
python-version: '3.12'
python-version: "3.12"
- name: Read Rust toolchain channel
id: rust_toolchain
@@ -172,8 +172,8 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
node-version: "20"
registry-url: "https://registry.npmjs.org"
- name: Determine next version (package.json is authoritative)
id: version
@@ -240,7 +240,7 @@ jobs:
- name: Setup Python 3.12
uses: actions/setup-python@v5
with:
python-version: '3.12'
python-version: "3.12"
- name: Read Rust toolchain channel
id: rust_toolchain
@@ -368,7 +368,7 @@ jobs:
- name: Build binaries (with timings)
shell: bash
env:
CARGO_INCREMENTAL: "0" # keep off in CI; release builds + sccache
CARGO_INCREMENTAL: "0" # keep off in CI; release builds + sccache
RUST_BACKTRACE: "1"
run: |
cd hanzo-dev
@@ -491,8 +491,8 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
node-version: "20"
registry-url: "https://registry.npmjs.org"
- name: Start local OpenAI proxy for release (hardened)
if: env.OPENAI_API_KEY != ''
+8 -8
View File
@@ -4,16 +4,16 @@ on:
push:
branches: [main]
paths:
- 'hanzo-dev/**'
- 'codex-rs/**'
- '**/Cargo.toml'
- '**/Cargo.lock'
- "hanzo-dev/**"
- "codex-rs/**"
- "**/Cargo.toml"
- "**/Cargo.lock"
pull_request:
paths:
- 'hanzo-dev/**'
- 'codex-rs/**'
- '**/Cargo.toml'
- '**/Cargo.lock'
- "hanzo-dev/**"
- "codex-rs/**"
- "**/Cargo.toml"
- "**/Cargo.lock"
workflow_dispatch:
jobs:
+11 -12
View File
@@ -3,17 +3,17 @@ name: Upstream Merge
on:
# Poll upstream regularly; also allow manual and external triggers
schedule:
- cron: '*/30 * * * *'
- cron: "*/30 * * * *"
workflow_dispatch:
inputs:
upstream_repo:
description: 'Upstream repo (owner/name)'
description: "Upstream repo (owner/name)"
required: false
default: 'openai/codex'
default: "openai/codex"
upstream_branch:
description: 'Upstream branch'
description: "Upstream branch"
required: false
default: 'main'
default: "main"
repository_dispatch:
types: [upstream-push]
@@ -35,9 +35,9 @@ env:
# skips due to no upstream changes and the branch is zero‑diff vs base.
# This used to be true when we reset the branch each run; now we carry
# forward the branch for incremental merges, so default this to false.
CLOSE_ZERO_DIFF_ON_SKIP: 'false'
RG_VERSION: '14.1.0'
JQ_VERSION: '1.7.1'
CLOSE_ZERO_DIFF_ON_SKIP: "false"
RG_VERSION: "14.1.0"
JQ_VERSION: "1.7.1"
jobs:
precheck:
@@ -354,7 +354,7 @@ jobs:
if: steps.check_upstream.outputs.skip != 'true'
uses: actions/setup-node@v4
with:
node-version: '20'
node-version: "20"
- name: Cache npm (npx) downloads
if: steps.check_upstream.outputs.skip != 'true'
@@ -383,7 +383,6 @@ jobs:
# Wait briefly for readiness
for i in {1..30}; do if nc -z 127.0.0.1 5055; then break; else sleep 0.2; fi; done || true
- name: Print proxy startup log tail
if: steps.check_upstream.outputs.skip != 'true'
run: |
@@ -603,7 +602,7 @@ jobs:
- If Git reports no merge-base between origin/DEFAULT_BRANCH and upstream/UPSTREAM_BRANCH, you may use `--allow-unrelated-histories` to graft histories and proceed with all policies/guards.
- If the initial `git merge --no-ff --no-commit upstream/UPSTREAM_BRANCH` fails with unrelated histories, re-run the merge in a separate command with `--allow-unrelated-histories` (do not chain with `||`).
- Use only the provided GH_TOKEN for push; do not echo it.
<tools>
- To search for API usages before removing exports:
rg -n "^(use\\s+codex_core::|codex_core::)(ModelClient|Prompt|Response(Event|Stream))\\b" codex-rs
@@ -835,7 +834,7 @@ jobs:
fi
popd >/dev/null
git worktree remove -f "$wt"
- name: Branding report (guide-only) and perma-deleted guard
if: steps.check_upstream.outputs.skip != 'true'
env:
+5 -6
View File
@@ -4,9 +4,8 @@ In the codex-rs folder where the rust code lives:
- Crate names are prefixed with `codex-`. For example, the `core` folder's crate is named `codex-core`
- When using format! and you can inline variables into {}, always do that.
<<<<<<< HEAD
- Treat `codex-rs` as a read-only mirror of `openai/codex:main`; edit Rust sources under `code-rs` instead.
=======
<<<<<<< HEAD
- # Treat `codex-rs` as a read-only mirror of `openai/codex:main`; edit Rust sources under `code-rs` instead.
- Install any commands the repo relies on (for example `just`, `rg`, or `cargo-insta`) if they aren't already available before running instructions here.
- Never add or modify any code related to `CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR` or `CODEX_SANDBOX_ENV_VAR`.
- You operate in a sandbox where `CODEX_SANDBOX_NETWORK_DISABLED=1` will be set whenever you use the `shell` tool. Any existing code that uses `CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR` was authored with this fact in mind. It is often used to early exit out of tests that the author knew you would not be able to run given your sandbox limitations.
@@ -34,7 +33,7 @@ In the codex-rs folder where the rust code lives:
`codex-rs/tui/src/bottom_pane/mod.rs`, and similarly central orchestration modules.
- When extracting code from a large module, move the related tests and module/type docs toward
the new implementation so the invariants stay close to the code that owns them.
>>>>>>> openai/main
> > > > > > > openai/main
Completion/build step
@@ -42,7 +41,7 @@ Completion/build step
- `./build-fast.sh` can take 20+min to run from a cold cache!!! Please use long timeout when running `./build-fast.sh` or waiting for it to complete.
- Policy: All errors AND all warnings must be fixed before you’re done. Treat any compiler warning as a failure and address it (rename unused vars with `_`, remove `mut`, delete dead code, etc.).
- Do not run additional format/lint/test commands on completion (e.g., `just fmt`, `just fix`, `cargo test`) unless explicitly requested for a specific task.
- ***NEVER run rustfmt***
- **_NEVER run rustfmt_**
- Before pushing to `main`, run `./pre-release.sh` to mirror the release preflight (dev-fast build, CLI smokes, workspace nextest).
Optional regression checks (recommended when touching the Rust workspace):
@@ -138,7 +137,7 @@ This architecture separates concerns between execution logic (core), UI state ma
Esc handler in `app.rs` should defer to Auto Drive, and
`ChatWidget::handle_key_event` owns the actual stop / pause behaviour. When
you need to tweak Esc semantics, update those two locations together.
- The approval pane must *never* swallow Esc. `code-rs/tui/src/bottom_pane/auto_coordinator_view.rs`
- The approval pane must _never_ swallow Esc. `code-rs/tui/src/bottom_pane/auto_coordinator_view.rs`
intentionally lets Esc (and the other approval shortcuts) bubble back to the
chat widget; keep this contract intact when editing the view layer.
- Avoid adding additional Esc handlers elsewhere for Auto Drive flows. Doing
+1 -1
View File
@@ -942,7 +942,7 @@
## [0.2.131] - 2025-09-12
- Core/HTTP: set explicit `Host` header from target URL to fix TLS SNI failures when using HTTP(S)_PROXY with Responses streaming. (6ad9cb283)
- Core/HTTP: set explicit `Host` header from target URL to fix TLS SNI failures when using HTTP(S)\_PROXY with Responses streaming. (6ad9cb283)
- Exec/Workflows: exit non‑zero on agent Error events so CI fails fast on real stream failures. (fec6aa0f0)
- Proxy: harden TLS forwarding (servername, Host reset, hop‑by‑hop header cleanup). (fec6aa0f0)
+38 -35
View File
@@ -42,27 +42,27 @@ Fork of OpenAI Codex CLI. Rust-based AI development assistant in the terminal.
## Key Crates
| Crate | Purpose |
|-------|---------|
| `cli` | CLI binary, arg parsing |
| `core` | Auth, config, model providers, inference |
| `tui` | Ratatui terminal interface |
| `exec` | Sandboxed command execution |
| `protocol` | Wire protocol definitions |
| `mcp-server` | MCP server implementation |
| `login` | OAuth flow for hanzo.id and OpenAI |
| `hooks` | Hook engine (session-start, stop, after-agent, after-tool-use) |
| `skills` | Skill discovery, loading, and marketplace |
| `app-server` | WebSocket/stdio app server for IDE integration |
| `otel` | OpenTelemetry tracing and metrics |
| Crate | Purpose |
| ------------ | -------------------------------------------------------------- |
| `cli` | CLI binary, arg parsing |
| `core` | Auth, config, model providers, inference |
| `tui` | Ratatui terminal interface |
| `exec` | Sandboxed command execution |
| `protocol` | Wire protocol definitions |
| `mcp-server` | MCP server implementation |
| `login` | OAuth flow for hanzo.id and OpenAI |
| `hooks` | Hook engine (session-start, stop, after-agent, after-tool-use) |
| `skills` | Skill discovery, loading, and marketplace |
| `app-server` | WebSocket/stdio app server for IDE integration |
| `otel` | OpenTelemetry tracing and metrics |
## Auth Modes
| Mode | Token Source | Display |
|------|-------------|---------|
| `ApiKey` | `HANZO_API_KEY` / `OPENAI_API_KEY` env | "API key (...xxxxx)" |
| `ChatGPT` | OpenAI OAuth JWT | "Logged in as [email]" |
| `Hanzo` | hanzo.id Casdoor JWT | "Logged in to Hanzo as [email]" |
| Mode | Token Source | Display |
| --------- | -------------------------------------- | ------------------------------- |
| `ApiKey` | `HANZO_API_KEY` / `OPENAI_API_KEY` env | "API key (...xxxxx)" |
| `ChatGPT` | OpenAI OAuth JWT | "Logged in as [email]" |
| `Hanzo` | hanzo.id Casdoor JWT | "Logged in to Hanzo as [email]" |
Default provider: `hanzo` at `https://api.hanzo.ai/v1`
@@ -117,6 +117,7 @@ just log [args] # Tail state SQLite logs
6. Then fix any compile errors in hanzo-dev workspace that depend on codex-rs types
**Remotes:**
- `origin` — `[email protected]:hanzoai/dev.git`
- `openai` — `/Users/z/work/openai/codex` (local mirror)
- `openai-upstream` — `https://github.com/openai/codex.git`
@@ -124,6 +125,7 @@ just log [args] # Tail state SQLite logs
## npm Distribution
`hanzo-node/` provides platform-specific binaries:
- `hanzo-node-darwin-arm64`, `hanzo-node-darwin-x64`
- `hanzo-node-linux-x64-musl`, `hanzo-node-linux-arm64-musl`
- `hanzo-node-win32-x64`
@@ -157,15 +159,15 @@ App → ZAP Client → ZT Transport → ZT Fabric → ZT Service
### Key Types
| Type | Module | Purpose |
|------|--------|---------|
| `ZtContext` | `context.rs` | Main entry: authenticate, dial, listen, services |
| `Config` / `ConfigBuilder` | `config.rs` | Controller URL, credentials, billing, timeouts |
| `HanzoJwtCredentials` | `auth.rs` | Resolves JWT from env/file, implements Credentials |
| `BillingGuard` | `billing.rs` | Balance check + usage recording (no free tier) |
| `ZtTransport` | `transport.rs` | ZAP Transport trait impl (feature-gated `zap`) |
| `ControllerClient` | `controller.rs` | REST client for `/edge/client/v1` |
| `ZtConnection` | `connection.rs` | AsyncRead + AsyncWrite over channels |
| Type | Module | Purpose |
| -------------------------- | --------------- | -------------------------------------------------- |
| `ZtContext` | `context.rs` | Main entry: authenticate, dial, listen, services |
| `Config` / `ConfigBuilder` | `config.rs` | Controller URL, credentials, billing, timeouts |
| `HanzoJwtCredentials` | `auth.rs` | Resolves JWT from env/file, implements Credentials |
| `BillingGuard` | `billing.rs` | Balance check + usage recording (no free tier) |
| `ZtTransport` | `transport.rs` | ZAP Transport trait impl (feature-gated `zap`) |
| `ControllerClient` | `controller.rs` | REST client for `/edge/client/v1` |
| `ZtConnection` | `connection.rs` | AsyncRead + AsyncWrite over channels |
### Features
@@ -175,6 +177,7 @@ App → ZAP Client → ZT Transport → ZT Fabric → ZT Service
### CLI Integration
`hanzo-dev/cli/src/cloud.rs` has `#[cfg(feature = "zt")]` branch:
- URLs starting with `zt://` use ZtContext.dial() instead of WebSocket
- Feature `zt = ["dep:hanzo-zt"]` in cli's Cargo.toml
@@ -189,14 +192,14 @@ cargo test -p hanzo-zt # 9 tests (auth, config, controller, doctest)
All SDKs follow the same pattern (ZT REST API + ZAP framing + Hanzo IAM + billing):
| Language | Location | Status |
|----------|----------|--------|
| **Rust** | `hanzo-dev/zt/` | 9 tests pass, 0 warnings |
| **Go** | `~/work/hanzozt/sdk-golang/{zap,auth/hanzo,billing}/` | 5 tests pass (fork deps need module path fix) |
| **TypeScript** | `~/work/hanzozt/zt-sdk-nodejs/src/{zap,auth,billing}/` | Complete |
| **Python** | `~/work/hanzozt/zt-sdk-py/hanzozt/{zap,auth,billing}/` | 11 tests pass |
| **C++** | `~/work/hanzozt/zt-sdk-cpp/` | Builds clean (CMake + libzt) |
| **C** | `~/work/hanzozt/zt-sdk-c/{includes/zt/zt_zap.h,library/zt_zap.c}` | Syntax checks pass |
| Language | Location | Status |
| -------------- | ----------------------------------------------------------------- | --------------------------------------------- |
| **Rust** | `hanzo-dev/zt/` | 9 tests pass, 0 warnings |
| **Go** | `~/work/hanzozt/sdk-golang/{zap,auth/hanzo,billing}/` | 5 tests pass (fork deps need module path fix) |
| **TypeScript** | `~/work/hanzozt/zt-sdk-nodejs/src/{zap,auth,billing}/` | Complete |
| **Python** | `~/work/hanzozt/zt-sdk-py/hanzozt/{zap,auth,billing}/` | 11 tests pass |
| **C++** | `~/work/hanzozt/zt-sdk-cpp/` | Builds clean (CMake + libzt) |
| **C** | `~/work/hanzozt/zt-sdk-c/{includes/zt/zt_zap.h,library/zt_zap.c}` | Syntax checks pass |
ZAP `zt://` scheme registered at `~/work/zap/zap/src/transport.rs` (7 tests pass, feature-gated).
+38 -8
View File
@@ -5,6 +5,7 @@
**Every Code** (Code for short) is a fast, local coding agent for your terminal. It's a community-driven fork of `openai/codex` focused on real developer ergonomics: Browser integration, multi-agents, theming, and reasoning control — all while staying compatible with upstream.
&ensp;
## What's new
- **Latest long-session stability sweep** (post-0.6): Auto Drive and Auto Review are now decoupled so background reviews no longer block the command flow. `Esc` returns control immediately and typing works while review finalization continues.
@@ -25,16 +26,16 @@
See commit `60727b068` and related Auto Drive hardening commits in git history for details.
- **Auto Review** – background ghost-commit watcher runs reviews in a separate worktree whenever a turn changes code; uses `codex-5.1-mini-high` and reports issues plus ready-to-apply fixes without blocking the main thread.
- **Code Bridge** – Sentry-style local bridge that streams errors, console, screenshots, and control from running apps into Code; ships an MCP server; install by asking Code to pull `https://github.com/just-every/code-bridge`.
- **Plays well with Auto Drive** – reviews run in parallel with long Auto Drive tasks so quality checks land while the flow keeps moving.
- **Quality-first focus** – the release shifts emphasis from "can the model write this file" to "did we verify it works".
- _From v0.5.0:_ rename to Every Code, upgraded `/auto` planning/recovery, unified `/settings`, faster streaming/history with card-based activity, and more reliable `/resume` + `/undo`.
[Read the full notes in RELEASE_NOTES.md](docs/release-notes/RELEASE_NOTES.md)
[Read the full notes in RELEASE_NOTES.md](docs/release-notes/RELEASE_NOTES.md)
&ensp;
## Why Every Code
- 🚀 **Auto Drive orchestration** – Multi-agent automation that now self-heals and ships complete tasks.
@@ -46,9 +47,11 @@
- 🔒 **Safety modes** – Read-only, approvals, and workspace sandboxing.
&ensp;
## AI Videos
&ensp;
<p align="center">
<a href="https://www.youtube.com/watch?v=Ra3q8IVpIOc">
<img src="docs/images/video-auto-review-play.jpg" alt="Play Auto Review video" width="100%">
@@ -57,6 +60,7 @@
</p>
&ensp;
<p align="center">
<a href="https://youtu.be/UOASHZPruQk">
<img src="docs/images/video-auto-drive-new-play.jpg" alt="Play Introducing Auto Drive video" width="100%">
@@ -65,6 +69,7 @@
</p>
&ensp;
<p align="center">
<a href="https://youtu.be/sV317OhiysQ">
<img src="docs/images/video-v03-play.jpg" alt="Play Multi-Agent Support video" width="100%">
@@ -72,9 +77,8 @@
<strong>Multi-Agent Promo</strong>
</p>
&ensp;
## Quickstart
### Run
@@ -93,6 +97,7 @@ code // or `coder` if you're using VS Code
Note: If another tool already provides a `code` command (e.g. VS Code), our CLI is also installed as `coder`. Use `coder` to avoid conflicts.
**Authenticate** (one of the following):
- **Sign in with ChatGPT** (Plus/Pro/Team; uses models available to your plan)
- Run `code` and pick "Sign in with ChatGPT"
- **API key** (usage-based)
@@ -124,9 +129,11 @@ qwen --version
> ℹ️ Add `export N_PREFIX="$HOME/.n"` and `export PATH="$N_PREFIX/bin:$PATH"` (plus the `npm_config_prefix` bin path) to your shell profile so the CLIs stay on `PATH` in future sessions.
&ensp;
## Commands
### Browser
```bash
# Connect code to external Chrome browser (running CDP)
/chrome # Connect with auto-detect port
@@ -138,6 +145,7 @@ qwen --version
```
### Agents
```bash
# Plan code changes (Claude, Gemini and GPT-5 consensus)
# All agents review task and create a consolidated plan
@@ -153,6 +161,7 @@ qwen --version
```
### Auto Drive
```bash
# Hand off a multi-step task; Auto Drive will coordinate agents and approvals
/auto "Refactor the auth flow and add device login"
@@ -162,6 +171,7 @@ qwen --version
```
### General
```bash
# Try a new theme!
/themes
@@ -196,21 +206,26 @@ Options:
Note: `--model` only changes the model name sent to the active provider. To use a different provider, set `model_provider` in `config.toml`. Providers must expose an OpenAI-compatible API (Chat Completions or Responses).
&ensp;
## Memory & project docs
Every Code can remember context across sessions:
1. **Create an `AGENTS.md` or `CLAUDE.md` file** in your project root:
```markdown
# Project Context
This is a React TypeScript application with:
- Authentication via JWT
- PostgreSQL database
- Express.js backend
## Key files:
- `/src/auth/` - Authentication logic
- `/src/api/` - API client code
- `/src/api/` - API client code
- `/server/` - Backend services
```
@@ -218,6 +233,7 @@ This is a React TypeScript application with:
3. **Codebase analysis**: Automatically understands project structure
&ensp;
## Non-interactive / CI mode
For automation and CI/CD:
@@ -234,6 +250,7 @@ code --config output_format=json "list all TODO comments"
```
&ensp;
## Model Context Protocol (MCP)
Every Code supports MCP for extended capabilities:
@@ -252,6 +269,7 @@ args = ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/project"]
```
&ensp;
## Configuration
Main config file: `~/.code/config.toml`
@@ -290,21 +308,27 @@ model_reasoning_summary = "detailed"
- `OPENAI_WIRE_API`: Force the built-in OpenAI provider to use `chat` or `responses` wiring
&ensp;
## FAQ
**How is this different from the original?**
> This fork adds browser integration, multi-agent commands (`/plan`, `/solve`, `/code`), theme system, and enhanced reasoning controls while maintaining full compatibility.
**Can I use my existing Codex configuration?**
> Yes. Every Code reads from both `~/.code/` (primary) and legacy `~/.codex/` directories. We only write to `~/.code/`, so Codex will keep running if you switch back; copy or remove legacy files if you notice conflicts.
**Does this work with ChatGPT Plus?**
> Absolutely. Use the same "Sign in with ChatGPT" flow as the original.
**Is my data secure?**
> Yes. Authentication stays on your machine, and we don't proxy your credentials or conversations.
&ensp;
## Contributing
We welcome contributions! Every Code maintains compatibility with upstream while adding community-requested features.
@@ -343,35 +367,41 @@ The `pre-push` hook runs `./pre-release.sh` automatically when pushing to `main`
5. Build successfully: `./build-fast.sh`
6. Submit a pull request
&ensp;
## Legal & Use
### License & attribution
- This project is a community fork of `openai/codex` under **Apache-2.0**. We preserve upstream LICENSE and NOTICE files.
- **Every Code** (Code) is **not** affiliated with, sponsored by, or endorsed by OpenAI.
### Your responsibilities
Using OpenAI, Anthropic or Google services through Every Code means you agree to **their Terms and policies**. In particular:
- **Don't** programmatically scrape/extract content outside intended flows.
- **Don't** bypass or interfere with rate limits, quotas, or safety mitigations.
- Use your **own** account; don't share or rotate accounts to evade limits.
- If you configure other model providers, you're responsible for their terms.
### Privacy
- Your auth file lives at `~/.code/auth.json`
- Inputs/outputs you send to AI providers are handled under their Terms and Privacy Policy; consult those documents (and any org-level data-sharing settings).
### Subject to change
AI providers can change eligibility, limits, models, or authentication flows. Every Code supports **both** ChatGPT sign-in and API-key modes so you can pick what fits (local/hobby vs CI/automation).
&ensp;
## License
Apache 2.0 - See [LICENSE](LICENSE) file for details.
Every Code is a community fork of the original Codex CLI. We maintain compatibility while adding enhanced features requested by the developer community.
&ensp;
---
## &ensp;
**Need help?** Open an issue on [GitHub](https://github.com/just-every/code/issues) or check our documentation.
+664 -380
View File
File diff suppressed because it is too large Load Diff
+45 -23
View File
@@ -2,68 +2,90 @@
// Windows-friendly preinstall: proactively free file locks from prior installs
// so npm/yarn/pnpm can stage the new package. No-ops on non-Windows.
import { platform } from 'os';
import { execSync } from 'child_process';
import { existsSync, readdirSync, rmSync, readFileSync, statSync } from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { platform } from "os";
import { execSync } from "child_process";
import { existsSync, readdirSync, rmSync, readFileSync, statSync } from "fs";
import path from "path";
import { fileURLToPath } from "url";
function isWSL() {
if (platform() !== 'linux') return false;
if (platform() !== "linux") return false;
try {
const rel = readFileSync('/proc/version', 'utf8').toLowerCase();
return rel.includes('microsoft') || !!process.env.WSL_DISTRO_NAME;
} catch { return false; }
const rel = readFileSync("/proc/version", "utf8").toLowerCase();
return rel.includes("microsoft") || !!process.env.WSL_DISTRO_NAME;
} catch {
return false;
}
}
const isWin = platform() === 'win32';
const isWin = platform() === "win32";
const wsl = isWSL();
const isWinLike = isWin || wsl;
// Scope: only run for global installs, unless explicitly forced. Allow opt-out.
const isGlobal = process.env.npm_config_global === 'true';
const force = process.env.CODE_FORCE_PREINSTALL === '1';
const skip = process.env.CODE_SKIP_PREINSTALL === '1';
const isGlobal = process.env.npm_config_global === "true";
const force = process.env.CODE_FORCE_PREINSTALL === "1";
const skip = process.env.CODE_SKIP_PREINSTALL === "1";
if (!isWinLike || skip || (!isGlobal && !force)) process.exit(0);
function tryExec(cmd, opts = {}) {
try { execSync(cmd, { stdio: ['ignore', 'ignore', 'ignore'], shell: true, ...opts }); } catch { /* ignore */ }
try {
execSync(cmd, {
stdio: ["ignore", "ignore", "ignore"],
shell: true,
...opts,
});
} catch {
/* ignore */
}
}
// 1) Stop our native binary if it is holding locks. Avoid killing unrelated tools.
// Only available on native Windows; skip entirely on WSL to avoid noise.
if (isWin) {
tryExec('taskkill /IM code-x86_64-pc-windows-msvc.exe /F');
tryExec("taskkill /IM code-x86_64-pc-windows-msvc.exe /F");
}
// 2) Remove stale staging dirs from previous failed installs under the global
// @hanzo scope, which npm will reuse (e.g., .code-XXXXX). Remove only
// old entries and never the current staging or live package.
try {
let scopeDir = '';
let scopeDir = "";
try {
const root = execSync('npm root -g', { stdio: ['ignore', 'pipe', 'ignore'], shell: true }).toString().trim();
scopeDir = path.join(root, '@hanzo');
const root = execSync("npm root -g", {
stdio: ["ignore", "pipe", "ignore"],
shell: true,
})
.toString()
.trim();
scopeDir = path.join(root, "@hanzo");
} catch {
// Fall back to guessing from this script location: <staging>\..\..\
const here = path.resolve(path.dirname(fileURLToPath(import.meta.url)));
scopeDir = path.resolve(here, '..');
scopeDir = path.resolve(here, "..");
}
if (existsSync(scopeDir)) {
const now = Date.now();
const maxAgeMs = 2 * 60 * 60 * 1000; // 2 hours
const currentDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const currentDir = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
"..",
);
for (const name of readdirSync(scopeDir)) {
if (!name.startsWith('.code-')) continue;
if (!name.startsWith(".code-")) continue;
const p = path.join(scopeDir, name);
if (path.resolve(p) === currentDir) continue; // never remove our current dir
try {
const st = statSync(p);
const age = now - st.mtimeMs;
if (age > maxAgeMs) rmSync(p, { recursive: true, force: true });
} catch { /* ignore */ }
} catch {
/* ignore */
}
}
}
} catch { /* ignore */ }
} catch {
/* ignore */
}
process.exit(0);
+85 -75
View File
@@ -3,106 +3,107 @@
## Overview
This document tracks parity between:
- **Rust**: `@hanzo/dev` (hanzo-dev crate) - npm package
- **Python**: `hanzo-mcp` (via hanzo-tools-* packages) - PyPI packages
- **Python**: `hanzo-mcp` (via hanzo-tools-\* packages) - PyPI packages
## Tool Comparison Matrix
### Core Execution Tools
| Tool | Rust (dev) | Python (hanzo-mcp) | Parity Status |
|------|------------|-------------------|---------------|
| Shell | `local_shell`, `shell` | `zsh`, `bash`, `ps` | ⚠️ Different schemas |
| File Read | via apply_patch | `read` | ✅ Equivalent |
| File Write | via apply_patch | `write` | ✅ Equivalent |
| File Edit | `apply_patch` (Lark) | `edit`, `multi_edit` | ⚠️ Different formats |
| File Tree | - | `tree` | ❌ Missing in Rust |
| File Find | - | `find` | ❌ Missing in Rust |
| File Search | - | `search`, `ast` | ❌ Missing in Rust |
| Tool | Rust (dev) | Python (hanzo-mcp) | Parity Status |
| ----------- | ---------------------- | -------------------- | -------------------- |
| Shell | `local_shell`, `shell` | `zsh`, `bash`, `ps` | ⚠️ Different schemas |
| File Read | via apply_patch | `read` | ✅ Equivalent |
| File Write | via apply_patch | `write` | ✅ Equivalent |
| File Edit | `apply_patch` (Lark) | `edit`, `multi_edit` | ⚠️ Different formats |
| File Tree | - | `tree` | ❌ Missing in Rust |
| File Find | - | `find` | ❌ Missing in Rust |
| File Search | - | `search`, `ast` | ❌ Missing in Rust |
### Agent & Planning Tools
| Tool | Rust (dev) | Python (hanzo-mcp) | Parity Status |
|------|------------|-------------------|---------------|
| Plan | `update_plan` | - | ❌ Missing in Python |
| Agent | `agent` (create/status/wait/result/cancel/list) | `agent`, `claude`, `codex`, `gemini`, `grok` | ⚠️ Different actions |
| Wait | `wait` (background polling) | - | ❌ Missing in Python |
| Kill | `kill` | `ps --kill` | ⚠️ Different interface |
| Tool | Rust (dev) | Python (hanzo-mcp) | Parity Status |
| ----- | ----------------------------------------------- | -------------------------------------------- | ---------------------- |
| Plan | `update_plan` | - | ❌ Missing in Python |
| Agent | `agent` (create/status/wait/result/cancel/list) | `agent`, `claude`, `codex`, `gemini`, `grok` | ⚠️ Different actions |
| Wait | `wait` (background polling) | - | ❌ Missing in Python |
| Kill | `kill` | `ps --kill` | ⚠️ Different interface |
### Memory & State Tools
| Tool | Rust (dev) | Python (hanzo-mcp) | Parity Status |
|------|------------|-------------------|---------------|
| Memory Recall | - | `recall_memories` | ❌ Missing in Rust |
| Memory Create | - | `create_memories` | ❌ Missing in Rust |
| Memory Update | - | `update_memories` | ❌ Missing in Rust |
| Memory Delete | - | `delete_memories` | ❌ Missing in Rust |
| Memory Manage | - | `manage_memories` | ❌ Missing in Rust |
| Facts | - | `recall_facts`, `store_facts` | ❌ Missing in Rust |
| Knowledge Bases | - | `manage_knowledge_bases` | ❌ Missing in Rust |
| Summarize | - | `summarize_to_memory` | ❌ Missing in Rust |
| Todo | - | `todo` | ❌ Missing in Rust |
| Tool | Rust (dev) | Python (hanzo-mcp) | Parity Status |
| --------------- | ---------- | ----------------------------- | ------------------ |
| Memory Recall | - | `recall_memories` | ❌ Missing in Rust |
| Memory Create | - | `create_memories` | ❌ Missing in Rust |
| Memory Update | - | `update_memories` | ❌ Missing in Rust |
| Memory Delete | - | `delete_memories` | ❌ Missing in Rust |
| Memory Manage | - | `manage_memories` | ❌ Missing in Rust |
| Facts | - | `recall_facts`, `store_facts` | ❌ Missing in Rust |
| Knowledge Bases | - | `manage_knowledge_bases` | ❌ Missing in Rust |
| Summarize | - | `summarize_to_memory` | ❌ Missing in Rust |
| Todo | - | `todo` | ❌ Missing in Rust |
### Reasoning Tools
| Tool | Rust (dev) | Python (hanzo-mcp) | Parity Status |
|------|------------|-------------------|---------------|
| Think | - | `think` | ❌ Missing in Rust |
| Critic | - | `critic` | ❌ Missing in Rust |
| Review | - | `review` | ❌ Missing in Rust |
| I Ching | - | `iching` | ❌ Missing in Rust |
| Tool | Rust (dev) | Python (hanzo-mcp) | Parity Status |
| ------- | ---------- | ------------------ | ------------------ |
| Think | - | `think` | ❌ Missing in Rust |
| Critic | - | `critic` | ❌ Missing in Rust |
| Review | - | `review` | ❌ Missing in Rust |
| I Ching | - | `iching` | ❌ Missing in Rust |
### Code Intelligence Tools
| Tool | Rust (dev) | Python (hanzo-mcp) | Parity Status |
|------|------------|-------------------|---------------|
| LSP | - | `lsp` | ❌ Missing in Rust |
| Refactor | - | `refactor` | ❌ Missing in Rust |
| Tool | Rust (dev) | Python (hanzo-mcp) | Parity Status |
| -------- | ---------- | ------------------ | ------------------ |
| LSP | - | `lsp` | ❌ Missing in Rust |
| Refactor | - | `refactor` | ❌ Missing in Rust |
### Browser & UI Tools
| Tool | Rust (dev) | Python (hanzo-mcp) | Parity Status |
|------|------------|-------------------|---------------|
| Browser | `browser` (Playwright) | `browser` (Playwright) | ⚠️ Different actions |
| Image View | `view_image` | - | ❌ Missing in Python |
| Screen | - | `screen` | ❌ Missing in Rust |
| Computer | - | `computer` | ❌ Missing in Rust |
| Tool | Rust (dev) | Python (hanzo-mcp) | Parity Status |
| ---------- | ---------------------- | ---------------------- | -------------------- |
| Browser | `browser` (Playwright) | `browser` (Playwright) | ⚠️ Different actions |
| Image View | `view_image` | - | ❌ Missing in Python |
| Screen | - | `screen` | ❌ Missing in Rust |
| Computer | - | `computer` | ❌ Missing in Rust |
### Network & API Tools
| Tool | Rust (dev) | Python (hanzo-mcp) | Parity Status |
|------|------------|-------------------|---------------|
| Web Search | `web_search` | - | ❌ Missing in Python |
| API | - | `api` | ❌ Missing in Rust |
| Curl | - | `curl` | ❌ Missing in Rust |
| Wget | - | `wget` | ❌ Missing in Rust |
| Tool | Rust (dev) | Python (hanzo-mcp) | Parity Status |
| ---------- | ------------ | ------------------ | -------------------- |
| Web Search | `web_search` | - | ❌ Missing in Python |
| API | - | `api` | ❌ Missing in Rust |
| Curl | - | `curl` | ❌ Missing in Rust |
| Wget | - | `wget` | ❌ Missing in Rust |
### Integration Tools
| Tool | Rust (dev) | Python (hanzo-mcp) | Parity Status |
|------|------------|-------------------|---------------|
| GH Actions Wait | `gh_actions_run_wait` | - | ❌ Missing in Python |
| Bridge | `bridge` | - | ❌ Missing in Python |
| NPX | - | `npx` | ❌ Missing in Rust |
| UVX | - | `uvx` | ❌ Missing in Rust |
| Open | - | `open` | ❌ Missing in Rust |
| JQ | - | `jq` | ❌ Missing in Rust |
| Tool | Rust (dev) | Python (hanzo-mcp) | Parity Status |
| --------------- | --------------------- | ------------------ | -------------------- |
| GH Actions Wait | `gh_actions_run_wait` | - | ❌ Missing in Python |
| Bridge | `bridge` | - | ❌ Missing in Python |
| NPX | - | `npx` | ❌ Missing in Rust |
| UVX | - | `uvx` | ❌ Missing in Rust |
| Open | - | `open` | ❌ Missing in Rust |
| JQ | - | `jq` | ❌ Missing in Rust |
### Configuration Tools
| Tool | Rust (dev) | Python (hanzo-mcp) | Parity Status |
|------|------------|-------------------|---------------|
| Config | - | `config` | ❌ Missing in Rust |
| Mode | - | `mode` | ❌ Missing in Rust |
| Version | - | `version` | ❌ Missing in Rust |
| Tool Mgmt | - | `tool` | ❌ Missing in Rust |
| Tool | Rust (dev) | Python (hanzo-mcp) | Parity Status |
| --------- | ---------- | ------------------ | ------------------ |
| Config | - | `config` | ❌ Missing in Rust |
| Mode | - | `mode` | ❌ Missing in Rust |
| Version | - | `version` | ❌ Missing in Rust |
| Tool Mgmt | - | `tool` | ❌ Missing in Rust |
### Database Tools
| Tool | Rust (dev) | Python (hanzo-mcp) | Parity Status |
|------|------------|-------------------|---------------|
| SQL | - | `sql_query`, `sql_stats` | ❌ Missing in Rust |
| Graph | - | `graph_*` | ❌ Missing in Rust |
| Tool | Rust (dev) | Python (hanzo-mcp) | Parity Status |
| ----- | ---------- | ------------------------ | ------------------ |
| SQL | - | `sql_query`, `sql_stats` | ❌ Missing in Rust |
| Graph | - | `graph_*` | ❌ Missing in Rust |
---
@@ -111,6 +112,7 @@ This document tracks parity between:
### 1. Shell Tool
**Rust Schema:**
```json
{
"command": ["array", "of", "strings"],
@@ -120,12 +122,13 @@ This document tracks parity between:
```
**Python Schema:**
```json
{
"command": "string", // Single string, shell parses
"command": "string", // Single string, shell parses
"cwd": "string",
"timeout": "number",
"env": {"key": "value"}
"env": { "key": "value" }
}
```
@@ -134,6 +137,7 @@ This document tracks parity between:
### 2. Apply Patch vs Edit
**Rust (apply_patch):** Lark grammar format
```
*** Begin Patch
*** Update File: path
@@ -144,6 +148,7 @@ This document tracks parity between:
```
**Python (edit):**
```json
{
"file_path": "string",
@@ -190,24 +195,27 @@ This document tracks parity between:
Rust dev connects to hanzo-mcp Python server, providing access to:
**Browser Tool (90+ actions):**
- Navigation: navigate, reload, go_back, go_forward, close
- Input: click, dblclick, type, fill, clear, press, select_option
- Forms: check, uncheck, upload
- Mouse: hover, drag, mouse_move, mouse_down, mouse_up, scroll
- Touch: tap, swipe, pinch
- Locators: get_by_role, get_by_text, get_by_label, get_by_test_id, etc.
- State: is_visible, is_enabled, is_checked, expect_* assertions
- State: is*visible, is_enabled, is_checked, expect*\* assertions
- Network: route, unroute (mock/block requests)
- Storage: cookies, clear_cookies, storage, storage_state
- Context: new_page, new_context, tabs, connect
**Extension Integration (ports 9223/9224):**
- CDP bridge for browser extensions
- Multi-client routing with namespaced targets
- Real-time WebSocket communication
- Falls back to Playwright if extension unavailable
**Computer Tool (40+ actions):**
- Mouse: click, double_click, right_click, move, drag, scroll
- Keyboard: type, write, press, key_down, key_up, hotkey
- Screen: screenshot, screenshot_region, get_screens
@@ -215,11 +223,13 @@ Rust dev connects to hanzo-mcp Python server, providing access to:
- Batch: Execute multiple actions atomically
**Screen Tool:**
- session: Record screen → analyze → extract keyframes
- record/stop: Manual recording control
- Native backends: Quartz (macOS), xdotool (Linux), win32 (Windows)
**Other MCP Tools:**
- Memory: recall/create/update/delete memories, facts, knowledge bases
- Reasoning: think, critic, review, iching
- LSP/Refactor: semantic code analysis
@@ -253,11 +263,11 @@ This provides Rust access to all Python tools via MCP protocol.
## Version Tracking
| Component | Current Version | Target Version |
|-----------|----------------|----------------|
| @hanzo/dev (npm) | 3.0.9 | 3.1.0 |
| hanzo-mcp (PyPI) | 0.11.7 | 0.12.0 |
| hanzo-tools (PyPI) | 0.3.0 | 0.4.0 |
| Component | Current Version | Target Version |
| ------------------ | --------------- | -------------- |
| @hanzo/dev (npm) | 3.0.9 | 3.1.0 |
| hanzo-mcp (PyPI) | 0.11.7 | 0.12.0 |
| hanzo-tools (PyPI) | 0.3.0 | 0.4.0 |
---
+2 -1
View File
@@ -94,5 +94,6 @@ env = { "API_KEY" = "value" }
```
## Using Code as an MCP Server
> [!TIP]
> It is somewhat experimental, but the Code CLI can also be run as an MCP _server_ via `code mcp`. If you launch it with an MCP client such as `npx @modelcontextprotocol/inspector code mcp` and send it a `tools/list` request, you will see that there is only one tool, `code`, that accepts a grab-bag of inputs, including a catch-all `config` map for anything you might want to override. Feel free to play around with it and provide feedback via GitHub issues.
> It is somewhat experimental, but the Code CLI can also be run as an MCP _server_ via `code mcp`. If you launch it with an MCP client such as `npx @modelcontextprotocol/inspector code mcp` and send it a `tools/list` request, you will see that there is only one tool, `code`, that accepts a grab-bag of inputs, including a catch-all `config` map for anything you might want to override. Feel free to play around with it and provide feedback via GitHub issues.
+15
View File
@@ -3,6 +3,7 @@
Every Code can launch external CLI “agents” and orchestrate them in multi-agent “subagent” flows such as `/plan`, `/solve`, and `/code`.
## Agent configuration (`[[agents]]` in `config.toml`)
```toml
[[agents]]
name = "code-gpt-5.3-codex" # slug or alias shown in pickers
@@ -16,14 +17,17 @@ enabled = true # hide from pickers when false
description = "Frontline coding agent"
instructions = "Preamble added to this agent’s prompt"
```
Field recap: `name` (slug/alias), `command` (absolute paths ok), `args*` (RO/RW lists override base), `env`, `read_only`, `enabled`, optional `description` and `instructions`.
### Built-in defaults
If no `[[agents]]` are configured, Code advertises built-ins (gated by env `CODE_ENABLE_CLOUD_AGENT_MODEL` for cloud variants): `code-gpt-5.4`, `code-gpt-5.3-codex`, `claude-opus-4.6`, `gemini-3-pro`, `code-gpt-5.1-codex-mini`, `claude-sonnet-4.5`, `gemini-3-flash`, `claude-haiku-4.5`, `qwen-3-coder`, `cloud-gpt-5.1-codex-max`. Built-ins strip any user `--model/-m` flags to avoid conflicts and inject their own.
Tip: `gemini` resolves to `gemini-3-flash` (fast/cheap). Use `gemini-3-pro` when you want the higher-capacity Gemini option.
## Subagents (`[[subagents.commands]]`)
```toml
[[subagents.commands]]
name = "plan" # slash name (/plan, /solve, /code, or custom)
@@ -32,6 +36,7 @@ agents = ["code-gpt-5.4", "claude-opus-4.6"] # falls back to enabled agents or
orchestrator_instructions = "Guidance for Code before spawning agents"
agent_instructions = "Preamble added to each spawned agent"
```
- `name`: slash command created/overridden.
- `read_only`: forces spawned agents to RO when true.
- `agents`: explicit list; empty → enabled `[[agents]]`; none configured → built-in roster.
@@ -41,37 +46,45 @@ agent_instructions = "Preamble added to each spawned agent"
The orchestrator fans out agents, waits for results, and merges reasoning according to your `hide_agent_reasoning` / `show_raw_agent_reasoning` settings.
## TUI controls
- `/agents` opens the settings overlay to the Agents section: toggle enabled/read-only, view defaults, and open editors.
- Agent editor: create or edit a single agent (enable/disable, read-only, instructions). Args/env come from `config.toml`.
- Subagent editor: configure per-command agent lists, read-only flag, and instructions. Built-in `/plan` `/solve` `/code` can be overridden the same way.
- Model pickers are modal and return to the invoking section after selection.
## Auto Drive interaction
- Auto Drive uses the `agents_enabled` toggle in its settings pane; when off, the coordinator skips agent batches.
- If no git repo exists, Auto Drive instructs all agents to run read-only.
- `AUTO_AGENTS.md` is read alongside `AGENTS.md` for Auto Drive–specific guidance.
## AGENTS.md and project memory
- Code loads AGENTS.md files along the path (global, repo root, cwd) up to 32 KiB total; deeper files override higher-level ones.
- Contents become system/developer instructions on the first turn; direct user/developer prompts still take precedence.
## Windows discovery tips
- On Windows, include extensions in `command` (`.exe`, `.cmd`, `.bat`, `.com`).
- NPM globals often live under `C:\\Users\\<you>\\AppData\\Roaming\\npm\\`.
- If PATH is unreliable, use absolute `command` paths in `[[agents]]`.
## Notifications and reasoning visibility
- `hide_agent_reasoning = true` removes agent reasoning streams in both the TUI and `code exec`.
- `show_raw_agent_reasoning = true` surfaces raw chains-of-thought when provided by the model.
- Notification filtering is controlled via `/notifications` or `config.toml` `notify` / `tui.notifications`.
## Headless `code exec`
- `code exec --json` streams JSONL events (agent turns included).
- `--output-schema <schema.json>` enforces structured JSON output; combine with `--output-last-message` to capture only the final payload.
- `code exec` defaults to read-only; add `--full-auto` plus a writable sandbox to permit edits.
## Quick examples
- Custom agent:
```toml
[[agents]]
name = "my-coder"
@@ -79,7 +92,9 @@ command = "/usr/local/bin/coder"
args_write = ["-s", "workspace-write", "--dangerously-bypass-approvals-and-sandbox", "exec", "--skip-git-repo-check"]
enabled = true
```
- Custom context sweep command:
```toml
[[subagents.commands]]
name = "context"
+14
View File
@@ -3,63 +3,77 @@
What Auto Drive is, how to start it, and how it behaves in Every Code.
## Start points
- TUI: `/auto <goal>`. If you omit the goal and there is recent history, Code proposes one for you. `/auto settings` jumps straight to the Auto Drive pane.
- CLI: `code exec --auto "<goal>"` or `code exec "/auto <goal>"`. A goal is required when launching headless.
- Precondition: Full Auto mode (danger-full-access + approval=never) must be selected in the TUI; otherwise you’ll see a warning and Auto Drive will not start.
## Goal handling
- Images passed on the CLI are attached before the first turn.
- If you supply no goal and there is nothing in history to derive from, Auto Drive will not start.
## How it runs
- Each turn Auto Drive drafts a plan, prepares commands, optionally assigns agents, and waits for your confirmation (or the countdown) before running.
- The transcript is kept in memory and compacted automatically; you’ll see a notice if history was trimmed.
- If an `AUTO_AGENTS.md` exists, its guidance is applied to the run alongside any AGENTS.md rules.
## Agents
- Auto Drive can spawn helper agents during a turn. Toggle this with `agents_enabled` in Auto Drive settings.
- Outside a git repo, Auto Drive forces those agents to run read-only to avoid unintended writes.
## Observer
- A lightweight watchdog reviews the run every `auto_drive_observer_cadence` turns (default 5). If it spots trouble it surfaces guidance in the banner. Set the cadence to `0` to disable.
## Sandbox and approvals
- TUI: needs `danger-full-access` plus `approval_policy=never` so turns aren’t blocked by prompts.
- CLI: `--auto` forces approvals off; add `--full-auto` (or `--dangerously-bypass-approvals-and-sandbox`) if you want Auto Drive to make edits and run commands.
## Continue and countdown modes
- `continue_mode`: `immediate`, `ten-seconds` (default), `sixty-seconds`, `manual`.
- In countdown modes, the Auto Drive card shows a timer; Enter proceeds early, Esc reopens the draft, 0 auto-submits.
- Manual mode pauses after each prepared prompt until you confirm.
## Stop and pause
- Press Esc while Auto Drive is active to pause or stop (context-dependent). Countdown modes show this hint in the footer.
- Approval dialogs never capture Esc; it always reaches Auto Drive.
## Review, QA, diagnostics
- `review_enabled` (default true) can insert a review gate; the card shows “Awaiting review.”
- `qa_automation_enabled` and `cross_check_enabled` (default true) allow diagnostics and cross-check turns before continuing.
- `auto_resolve_review_attempts` limits how many times Auto Drive will auto-resolve review feedback (default 5).
## Models
- Defaults: model `gpt-5.1`, reasoning effort `high`.
- Toggle “use chat model” in settings to reuse your current chat model/effort instead of the dedicated Auto Drive model.
## UI surfaces
- Auto Drive card shows status (Ready, Waiting, Thinking, Running, Awaiting review, Failed/Stopped), the goal, action log, token/time counters, countdown, and a celebration on success.
- Bottom pane header mirrors status and shows hints (Ctrl+S settings, Esc stop, whether agents/diagnostics are on).
## Resume and persistence
- History is kept in memory; there’s no Auto Drive–specific history file. If trimmed, you’ll see a note.
- You can resume a session as usual; Auto Drive can derive a goal from restored history.
- CLI `--output-last-message` still works here if you only need the final reply.
## Settings (config.toml)
- Top-level keys: `auto_drive_use_chat_model` (default false), `auto_drive_observer_cadence` (default 5).
- `[auto_drive]` defaults: `review_enabled=true`, `agents_enabled=true`, `qa_automation_enabled=true`, `cross_check_enabled=true`, `observer_enabled=true`, `coordinator_routing=true`, `continue_mode="ten-seconds"`, `model="gpt-5.1"`, `model_reasoning_effort="high"`, `auto_resolve_review_attempts=5`.
- All of these can be changed from `/auto settings` in the TUI or directly in `config.toml`.
## Tips
- Stay in the TUI if you want countdowns and visual status; use `code exec --auto` for CI or scripted flows.
- If Auto Drive stops because it couldn’t derive a goal, rerun `/auto <goal>` with a short, specific instruction.
- Turn off agents in `/auto settings` if you want a single-model run.
+63 -63
View File
@@ -570,13 +570,13 @@ set = { CI = "1" }
include_only = ["PATH", "HOME"]
```
| Field | Type | Default | Description |
| ------------------------- | -------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `inherit` | string | `all` | Starting template for the environment:<br>`all` (clone full parent env), `core` (`HOME`, `PATH`, `USER`, …), or `none` (start empty). |
| `ignore_default_excludes` | boolean | `true` | When `false`, Code removes any var whose **name** contains `KEY`, `SECRET`, or `TOKEN` (case-insensitive) before other rules run; defaults to `true` so this filter is disabled by default. |
| `exclude` | array<string> | `[]` | Case-insensitive glob patterns to drop after the default filter.<br>Examples: `"AWS_*"`, `"AZURE_*"`. |
| `set` | table<string,string> | `{}` | Explicit key/value overrides or additions – always win over inherited values. |
| `include_only` | array<string> | `[]` | If non-empty, a whitelist of patterns; only variables that match _one_ pattern survive the final step. (Generally used with `inherit = "all"`.) |
| Field | Type | Default | Description |
| ------------------------- | -------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `inherit` | string | `all` | Starting template for the environment:<br>`all` (clone full parent env), `core` (`HOME`, `PATH`, `USER`, …), or `none` (start empty). |
| `ignore_default_excludes` | boolean | `true` | When `false`, Code removes any var whose **name** contains `KEY`, `SECRET`, or `TOKEN` (case-insensitive) before other rules run; defaults to `true` so this filter is disabled by default. |
| `exclude` | array<string> | `[]` | Case-insensitive glob patterns to drop after the default filter.<br>Examples: `"AWS_*"`, `"AZURE_*"`. |
| `set` | table<string,string> | `{}` | Explicit key/value overrides or additions – always win over inherited values. |
| `include_only` | array<string> | `[]` | If non-empty, a whitelist of patterns; only variables that match _one_ pattern survive the final step. (Generally used with `inherit = "all"`.) |
The patterns are **glob style**, not full regular expressions: `*` matches any
number of characters, `?` matches exactly one, and character classes like
@@ -914,7 +914,7 @@ notifications = [ "approval-requested" ]
### Auto Drive Observer
Code keeps long-running Auto Drive sessions in check with a lightweight observer thread. Configure its cadence with the top-level `auto_drive_observer_cadence` key (default `5`). After every *n* completed requests the observer reviews the coordinator/CLI transcript, emits telemetry, and—if necessary—suggests a corrected prompt or follow-up guidance. Setting the value to `0` disables the observer entirely.
Code keeps long-running Auto Drive sessions in check with a lightweight observer thread. Configure its cadence with the top-level `auto_drive_observer_cadence` key (default `5`). After every _n_ completed requests the observer reviews the coordinator/CLI transcript, emits telemetry, and—if necessary—suggests a corrected prompt or follow-up guidance. Setting the value to `0` disables the observer entirely.
```toml
# Run the observer after every third Auto Drive request
@@ -988,61 +988,61 @@ Project commands appear in the TUI via `/cmd <name>` and run through the standar
## Config reference
| Key | Type / Values | Notes |
| --- | --- | --- |
| `model` | string | Model to use (e.g., `gpt-5.1-codex`). |
| `model_provider` | string | Provider id from `model_providers` (default: `openai`). |
| `model_context_window` | number | Context window tokens. |
| `model_max_output_tokens` | number | Max output tokens. |
| `approval_policy` | `untrusted` \| `on-failure` \| `on-request` \| `never` | When to prompt for approval. |
| `sandbox_mode` | `read-only` \| `workspace-write` \| `danger-full-access` | OS sandbox policy. |
| `sandbox_workspace_write.writable_roots` | array<string> | Extra writable roots in workspace‑write. |
| `sandbox_workspace_write.network_access` | boolean | Allow network in workspace‑write (default: false). |
| `sandbox_workspace_write.exclude_tmpdir_env_var` | boolean | Exclude `$TMPDIR` from writable roots (default: false). |
| `sandbox_workspace_write.exclude_slash_tmp` | boolean | Exclude `/tmp` from writable roots (default: false). |
| `disable_response_storage` | boolean | Required for ZDR orgs. |
| `notify` | array<string> | External program for notifications. |
| `instructions` | string | Currently ignored; use `experimental_instructions_file` or `AGENTS.md`. |
| `mcp_servers.<id>.command` | string | MCP server launcher command. |
| `mcp_servers.<id>.args` | array<string> | MCP server args. |
| `mcp_servers.<id>.env` | map<string,string> | MCP server env vars. |
| `mcp_servers.<id>.startup_timeout_sec` | number | Startup timeout in seconds (default: 10). Timeout is applied both for initializing MCP server and initially listing tools. |
| `mcp_servers.<id>.tool_timeout_sec` | number | Per-tool timeout in seconds (default: 60). Accepts fractional values; omit to use the default. |
| `model_providers.<id>.name` | string | Display name. |
| `model_providers.<id>.base_url` | string | API base URL. |
| `model_providers.<id>.env_key` | string | Env var for API key. |
| `model_providers.<id>.wire_api` | `chat` \| `responses` | Protocol used (default: `chat`). |
| `model_providers.<id>.query_params` | map<string,string> | Extra query params (e.g., Azure `api-version`). |
| `model_providers.<id>.http_headers` | map<string,string> | Additional static headers. |
| `model_providers.<id>.env_http_headers` | map<string,string> | Headers sourced from env vars. |
| `model_providers.<id>.request_max_retries` | number | Per‑provider HTTP retry count (default: 4). |
| `model_providers.<id>.stream_max_retries` | number | SSE stream retry count (default: 5). |
| `model_providers.<id>.stream_idle_timeout_ms` | number | SSE idle timeout (ms) (default: 300000). |
| `project_doc_max_bytes` | number | Max bytes to read from `AGENTS.md`. |
| `projects.<path>.trust_level` | string | Mark project/worktree as trusted (only `"trusted"` is recognized). |
| `projects.<path>.hooks` | array<table> | Lifecycle hooks for that workspace (see "Project Hooks"). |
| `projects.<path>.commands` | array<table> | Project commands exposed via `/cmd`. |
| `profile` | string | Active profile name. |
| `profiles.<name>.*` | various | Profile‑scoped overrides of the same keys. |
| `history.persistence` | `save-all` \| `none` | History file persistence (default: `save-all`). |
| `history.max_bytes` | number | Currently ignored (not enforced). |
| `file_opener` | `vscode` \| `vscode-insiders` \| `windsurf` \| `cursor` \| `none` | URI scheme for clickable citations (default: `vscode`). |
| `tui` | table | TUI‑specific options. |
| `tui.notifications` | boolean \| array<string> | Enable desktop notifications in the tui (default: false). |
| `hide_agent_reasoning` | boolean | Hide model reasoning events. |
| `show_raw_agent_reasoning` | boolean | Show raw reasoning (when available). |
| `model_reasoning_effort` | `minimal` \| `low` \| `medium` \| `high` | Responses API reasoning effort. |
| `model_reasoning_summary` | `auto` \| `concise` \| `detailed` \| `none` | Reasoning summaries. |
| `model_verbosity` | `low` \| `medium` \| `high` | GPT‑5 text verbosity (Responses API). |
| `model_supports_reasoning_summaries` | boolean | Force‑enable reasoning summaries. |
| `chatgpt_base_url` | string | Base URL for ChatGPT auth flow. |
| `experimental_resume` | string (path) | Resume JSONL path (internal/experimental). |
| `experimental_instructions_file` | string (path) | Replace built‑in instructions (experimental). |
| `experimental_use_exec_command_tool` | boolean | Use experimental exec command tool. |
| `use_experimental_reasoning_summary` | boolean | Use experimental summary for reasoning chain. |
| `responses_originator_header_internal_override` | string | Override `originator` header value. |
| `tools.web_search` | boolean | Enable web search tool (alias: `web_search_request`) (default: false). |
| `tools.web_search_allowed_domains` | array<string> | Optional allow-list for web search (filters.allowed_domains). |
| Key | Type / Values | Notes |
| ------------------------------------------------ | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `model` | string | Model to use (e.g., `gpt-5.1-codex`). |
| `model_provider` | string | Provider id from `model_providers` (default: `openai`). |
| `model_context_window` | number | Context window tokens. |
| `model_max_output_tokens` | number | Max output tokens. |
| `approval_policy` | `untrusted` \| `on-failure` \| `on-request` \| `never` | When to prompt for approval. |
| `sandbox_mode` | `read-only` \| `workspace-write` \| `danger-full-access` | OS sandbox policy. |
| `sandbox_workspace_write.writable_roots` | array<string> | Extra writable roots in workspace‑write. |
| `sandbox_workspace_write.network_access` | boolean | Allow network in workspace‑write (default: false). |
| `sandbox_workspace_write.exclude_tmpdir_env_var` | boolean | Exclude `$TMPDIR` from writable roots (default: false). |
| `sandbox_workspace_write.exclude_slash_tmp` | boolean | Exclude `/tmp` from writable roots (default: false). |
| `disable_response_storage` | boolean | Required for ZDR orgs. |
| `notify` | array<string> | External program for notifications. |
| `instructions` | string | Currently ignored; use `experimental_instructions_file` or `AGENTS.md`. |
| `mcp_servers.<id>.command` | string | MCP server launcher command. |
| `mcp_servers.<id>.args` | array<string> | MCP server args. |
| `mcp_servers.<id>.env` | map<string,string> | MCP server env vars. |
| `mcp_servers.<id>.startup_timeout_sec` | number | Startup timeout in seconds (default: 10). Timeout is applied both for initializing MCP server and initially listing tools. |
| `mcp_servers.<id>.tool_timeout_sec` | number | Per-tool timeout in seconds (default: 60). Accepts fractional values; omit to use the default. |
| `model_providers.<id>.name` | string | Display name. |
| `model_providers.<id>.base_url` | string | API base URL. |
| `model_providers.<id>.env_key` | string | Env var for API key. |
| `model_providers.<id>.wire_api` | `chat` \| `responses` | Protocol used (default: `chat`). |
| `model_providers.<id>.query_params` | map<string,string> | Extra query params (e.g., Azure `api-version`). |
| `model_providers.<id>.http_headers` | map<string,string> | Additional static headers. |
| `model_providers.<id>.env_http_headers` | map<string,string> | Headers sourced from env vars. |
| `model_providers.<id>.request_max_retries` | number | Per‑provider HTTP retry count (default: 4). |
| `model_providers.<id>.stream_max_retries` | number | SSE stream retry count (default: 5). |
| `model_providers.<id>.stream_idle_timeout_ms` | number | SSE idle timeout (ms) (default: 300000). |
| `project_doc_max_bytes` | number | Max bytes to read from `AGENTS.md`. |
| `projects.<path>.trust_level` | string | Mark project/worktree as trusted (only `"trusted"` is recognized). |
| `projects.<path>.hooks` | array<table> | Lifecycle hooks for that workspace (see "Project Hooks"). |
| `projects.<path>.commands` | array<table> | Project commands exposed via `/cmd`. |
| `profile` | string | Active profile name. |
| `profiles.<name>.*` | various | Profile‑scoped overrides of the same keys. |
| `history.persistence` | `save-all` \| `none` | History file persistence (default: `save-all`). |
| `history.max_bytes` | number | Currently ignored (not enforced). |
| `file_opener` | `vscode` \| `vscode-insiders` \| `windsurf` \| `cursor` \| `none` | URI scheme for clickable citations (default: `vscode`). |
| `tui` | table | TUI‑specific options. |
| `tui.notifications` | boolean \| array<string> | Enable desktop notifications in the tui (default: false). |
| `hide_agent_reasoning` | boolean | Hide model reasoning events. |
| `show_raw_agent_reasoning` | boolean | Show raw reasoning (when available). |
| `model_reasoning_effort` | `minimal` \| `low` \| `medium` \| `high` | Responses API reasoning effort. |
| `model_reasoning_summary` | `auto` \| `concise` \| `detailed` \| `none` | Reasoning summaries. |
| `model_verbosity` | `low` \| `medium` \| `high` | GPT‑5 text verbosity (Responses API). |
| `model_supports_reasoning_summaries` | boolean | Force‑enable reasoning summaries. |
| `chatgpt_base_url` | string | Base URL for ChatGPT auth flow. |
| `experimental_resume` | string (path) | Resume JSONL path (internal/experimental). |
| `experimental_instructions_file` | string (path) | Replace built‑in instructions (experimental). |
| `experimental_use_exec_command_tool` | boolean | Use experimental exec command tool. |
| `use_experimental_reasoning_summary` | boolean | Use experimental summary for reasoning chain. |
| `responses_originator_header_internal_override` | string | Override `originator` header value. |
| `tools.web_search` | boolean | Enable web search tool (alias: `web_search_request`) (default: false). |
| `tools.web_search_allowed_domains` | array<string> | Optional allow-list for web search (filters.allowed_domains). |
## Realtime start instructions
+5
View File
@@ -47,11 +47,13 @@ Replace `YourUser` with your actual Windows username.
**2. Find your npm global install location:**
Run this command to find where npm installs global packages:
```cmd
npm config get prefix
```
The executables will be in the returned directory. For example, if it returns `C:\Users\YourUser\AppData\Roaming\npm`, your agent commands will be at:
- `C:\Users\YourUser\AppData\Roaming\npm\claude.cmd`
- `C:\Users\YourUser\AppData\Roaming\npm\gemini.cmd`
- `C:\Users\YourUser\AppData\Roaming\npm\coder.cmd`
@@ -59,16 +61,19 @@ The executables will be in the returned directory. For example, if it returns `C
**3. Verify PATH includes npm directory:**
In PowerShell:
```powershell
$env:PATH -split ';' | Select-String "npm"
```
In Command Prompt:
```cmd
echo %PATH% | findstr npm
```
If npm's directory isn't in your PATH, you can either:
- Add it to your system PATH (requires restart)
- Use absolute paths in your config (recommended)
+12 -12
View File
@@ -2,10 +2,10 @@
### CLI usage
| Command | Purpose | Example |
| ---------------- | ---------------------------------- | ----------------------------- |
| `code` | Interactive TUI | `code` |
| `code "..."` | Initial prompt for interactive TUI | `code "fix lint errors"` |
| Command | Purpose | Example |
| ----------------- | ---------------------------------- | ------------------------------ |
| `code` | Interactive TUI | `code` |
| `code "..."` | Initial prompt for interactive TUI | `code "fix lint errors"` |
| `code exec "..."` | Non-interactive "automation mode" | `code exec "explain utils.ts"` |
Key flags: `--model/-m`, `--ask-for-approval/-a`.
@@ -30,15 +30,15 @@ they'll be committed to your working directory.
Below are a few bite-size examples you can copy-paste. Replace the text in quotes with your own task.
| ✨ | What you type | What happens |
| --- | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| 1 | `code "Refactor the Dashboard component to React Hooks"` | Code rewrites the class component, runs `npm test`, and shows the diff. |
| 2 | `code "Generate SQL migrations for adding a users table"` | Infers your ORM, creates migration files, and runs them in a sandboxed DB. |
| 3 | `code "Write unit tests for utils/date.ts"` | Generates tests, executes them, and iterates until they pass. |
| 4 | `code "Bulk-rename *.jpeg -> *.jpg with git mv"` | Safely renames files and updates imports/usages. |
| 5 | `code "Explain what this regex does: ^(?=.*[A-Z]).{8,}$"` | Outputs a step-by-step human explanation. |
| ✨ | What you type | What happens |
| --- | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------- |
| 1 | `code "Refactor the Dashboard component to React Hooks"` | Code rewrites the class component, runs `npm test`, and shows the diff. |
| 2 | `code "Generate SQL migrations for adding a users table"` | Infers your ORM, creates migration files, and runs them in a sandboxed DB. |
| 3 | `code "Write unit tests for utils/date.ts"` | Generates tests, executes them, and iterates until they pass. |
| 4 | `code "Bulk-rename *.jpeg -> *.jpg with git mv"` | Safely renames files and updates imports/usages. |
| 5 | `code "Explain what this regex does: ^(?=.*[A-Z]).{8,}$"` | Outputs a step-by-step human explanation. |
| 6 | `code "Carefully review this repo, and propose 3 high impact well-scoped PRs"` | Suggests impactful PRs in the current codebase. |
| 7 | `code "Look for vulnerabilities and create a security review report"` | Finds and explains security bugs. |
| 7 | `code "Look for vulnerabilities and create a security review report"` | Finds and explains security bugs. |
### Memory with AGENTS.md
+4 -5
View File
@@ -5,16 +5,16 @@ from the latest GitHub release artifacts. Publishing to Homebrew requires a
tap repository (for example, `hanzoai/homebrew-tap`). Once a tap exists,
you can generate and publish the formula like so:
1) Generate the formula for the latest version:
1. Generate the formula for the latest version:
```
scripts/generate-homebrew-formula.sh
```
2) Copy the generated `Code.rb` into your tap repo under `Formula/Code.rb`
and update the `url`/`sha256` if needed.
2. Copy the generated `Code.rb` into your tap repo under `Formula/Code.rb`
and update the `url`/`sha256` if needed.
3) Users can then install with:
3. Users can then install with:
```
brew tap hanzoai/tap
@@ -27,4 +27,3 @@ Notes
- `code-aarch64-apple-darwin.tar.gz`
- `code-x86_64-apple-darwin.tar.gz`
- The CLI is installed as `code` and `coder` shims for compatibility.
+3
View File
@@ -3,12 +3,14 @@
Use these docs to install, configure, and operate Every Code (Code).
## Start Here
- [Getting started](getting-started.md)
- [Install](install.md) · [Homebrew](homebrew.md)
- [Authentication](authentication.md)
- [FAQ](faq.md)
## Using Code
- [Executing commands](exec.md)
- [Execution policy](execpolicy.md)
- [Slash commands](slash-commands.md)
@@ -20,6 +22,7 @@ Use these docs to install, configure, and operate Every Code (Code).
- [Zed integration](integration-zed.md)
## Configuration & Security
- [Configuration](config.md)
- [Sandboxing overview](sandbox.md)
- [Platform sandboxing](platform-sandboxing.md)
+4 -3
View File
@@ -7,9 +7,9 @@ To point Zed at Every Code's (Code) ACP server, add this block to `settings.json
"agent_servers": {
"Code": {
"command": "npx",
"args": ["-y", "@hanzo/dev", "acp"]
}
}
"args": ["-y", "@hanzo/dev", "acp"],
},
},
}
```
@@ -35,6 +35,7 @@ write_text_file = { mcp_server = "zed", tool_name = "writeTextFile" }
```
Zed wires these tools automatically when you add the Code (Every Code) agent, so the identifiers above match the defaults.
- The CLI entry point (`npx @hanzo/dev acp`) is a thin wrapper over the Rust binary (`cargo run -p code-mcp-server -- --stdio`) that ships alongside the rest of Every Code. Build-from-source workflows plug in by swapping `command` for an absolute path to that binary.
## Tips and troubleshooting
+11 -6
View File
@@ -3,24 +3,29 @@
The mechanism Code uses to enforce sandboxing varies by OS.
## macOS 12+
- Uses Apple Seatbelt via `sandbox-exec` with a profile that matches the selected `--sandbox` mode.
## Linux
- Uses Landlock plus seccomp to apply the configured sandbox policy.
- In containerized environments (e.g., Docker) the host must support these APIs. If it does not, configure the container to provide the isolation you need and run Code with `--sandbox danger-full-access` (or `--dangerously-bypass-approvals-and-sandbox`) inside that container instead.
## Windows
Code launches commands with a restricted Windows token and an allowlist tied to declared workspace roots. Writes are blocked outside those roots (and `%TEMP%` when workspace-write is requested); common escape vectors like alternate data streams, UNC paths, and device handles are proactively denied. The CLI also inserts stub executables (for example, wrapping `ssh`) ahead of the host `PATH` to intercept risky tools before they escape the sandbox.
### Known limitations (smoketests)
Running `python windows-sandbox-rs/sandbox_smoketests.py` with full filesystem and network access currently passes **37/41** cases. The remaining high-value gaps are:
| Test | Purpose |
| --- | --- |
| ADS write denied (#32) | Alternate data streams can still be written inside the workspace (should be blocked). |
| Protected path case-variation denied (#33) | `.GiT` bypasses protections meant for `.git`. Case variants should be rejected. |
| PATH stub bypass denied (#35) | A workspace `ssh.bat` shim placed first on `PATH` is not reliably executed, so interception cannot be proven. |
| Start-Process https denied (#41) | `Start-Process 'https://…'` succeeds in read-only runs because Explorer handles the ShellExecute outside the sandbox. |
| Test | Purpose |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- |
| ADS write denied (#32) | Alternate data streams can still be written inside the workspace (should be blocked). |
| Protected path case-variation denied (#33) | `.GiT` bypasses protections meant for `.git`. Case variants should be rejected. |
| PATH stub bypass denied (#35) | A workspace `ssh.bat` shim placed first on `PATH` is not reliably executed, so interception cannot be proven. |
| Start-Process https denied (#41) | `Start-Process 'https://…'` succeeds in read-only runs because Explorer handles the ShellExecute outside the sandbox. |
### How to help
If you can iterate on Windows sandboxing, aim to close the four smoketest failures above and rerun `python windows-sandbox-rs/sandbox_smoketests.py` until all **41/41** pass.
+7 -7
View File
@@ -25,13 +25,13 @@ Yes, you can disable all approval prompts with `--ask-for-approval never`. This
### Common sandbox + approvals combinations
| Intent | Flags | Effect |
| ---------------------------------- | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| Safe read-only browsing | `--sandbox read-only --ask-for-approval on-request` | Code can read files and answer questions. Code requires approval to make edits, run commands, or access network. |
| Read-only non-interactive (CI) | `--sandbox read-only --ask-for-approval never` | Reads only; never escalates |
| Let it edit the repo, ask if risky | `--sandbox workspace-write --ask-for-approval on-request` | Code can read files, make edits, and run commands in the workspace. Code requires approval for actions outside the workspace or for network access. |
| Auto (preset) | `--full-auto` (equivalent to `--sandbox workspace-write` + `--ask-for-approval on-failure`) | Code can read files, make edits, and run commands in the workspace. Code requires approval when a sandboxed command fails or needs escalation. |
| YOLO (not recommended) | `--dangerously-bypass-approvals-and-sandbox` (alias: `--yolo`) | No sandbox; no prompts |
| Intent | Flags | Effect |
| ---------------------------------- | ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| Safe read-only browsing | `--sandbox read-only --ask-for-approval on-request` | Code can read files and answer questions. Code requires approval to make edits, run commands, or access network. |
| Read-only non-interactive (CI) | `--sandbox read-only --ask-for-approval never` | Reads only; never escalates |
| Let it edit the repo, ask if risky | `--sandbox workspace-write --ask-for-approval on-request` | Code can read files, make edits, and run commands in the workspace. Code requires approval for actions outside the workspace or for network access. |
| Auto (preset) | `--full-auto` (equivalent to `--sandbox workspace-write` + `--ask-for-approval on-failure`) | Code can read files, make edits, and run commands in the workspace. Code requires approval when a sandboxed command fails or needs escalation. |
| YOLO (not recommended) | `--dangerously-bypass-approvals-and-sandbox` (alias: `--yolo`) | No sandbox; no prompts |
> Note: In `workspace-write`, network is disabled by default unless enabled in config (`[sandbox_workspace_write].network_access = true`).
+6
View File
@@ -3,16 +3,19 @@
Full-screen settings panel for Every Code’s TUI. Use it to change models, themes, Auto Drive defaults, agents, notifications, and more without leaving the chat.
## Open & navigate
- `/settings` opens the overview; `/settings <section>` jumps directly (section names below). `/auto settings` and `/update` route into their sections.
- Keys: `↑/↓` or `j/k` to move, `Tab/Shift+Tab` to cycle sections, `Home/End` jump list ends. Enter opens/activates; Esc closes the current section, Esc again closes the overlay. `?` toggles inline help. Paste is forwarded to the active section when allowed.
- Overlay is modal: chat input is blocked while it is visible. It remembers the last active section on reopen (`pending_settings_return`).
## Persistence
- Changes write to `CODE_HOME/config.toml` when available; if that directory is missing you’ll see a warning and the changes remain session-only.
- Access mode can be stored per workspace; other settings apply globally unless your config file overrides them per project.
- Agent and MCP edits also live in the same config directory.
## Sections
- **Model**: pick the default chat model and reasoning effort.
- **Theme**: choose a theme and spinner; applies immediately.
- **Updates**: view upgrade channel/status. `/update` opens here before running installers.
@@ -28,16 +31,19 @@ Full-screen settings panel for Every Code’s TUI. Use it to change models, them
- **Notifications**: toggle all or set filters. `/notifications on|off|status` also lands here.
## Overlay lifecycle
- All keys route through `handle_settings_key` while open; composer/history ignore input until closed.
- Help overlay (`?`) closes before the main overlay when Esc is pressed.
- Sections mark completion via their content structs; the overlay closes when a section reports `is_complete` (e.g., Chrome option chosen).
## Scope reminders
- Global defaults live in `CODE_HOME/config.toml`.
- Workspace overrides are honored where setters accept `cwd` (access mode) or when project-level config files exist. The UI always renders merged effective values.
- Agent commands and MCP servers are stored under `CODE_HOME` and apply to all workspaces unless overridden by project config.
## Commands
- `/settings [section]`
- `/auto settings`
- `/update` (or `/update settings`)
+1
View File
@@ -63,6 +63,7 @@ Behavior:
---
# Optional body
Add instructions, references, examples, or scripts (kept on disk).
```
+2
View File
@@ -3150,6 +3150,8 @@ dependencies = [
[[package]]
name = "hanzo-tunnel"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "135ad710f2ae318e0c3812cffcc8ee21510727d7cebcd2d63fd452bccb96a8a0"
dependencies = [
"chrono",
"futures",
+3 -2
View File
@@ -44,8 +44,8 @@ hanzo-responses-api-proxy = { workspace = true }
hanzo-tui = { workspace = true }
hanzo-version = { path = "../code-version", version = "0.6.74" }
hanzo-cloud-tasks = { workspace = true }
hanzo-tunnel = { path = "../../../tunnel" }
hanzo-tunnel-bridge = { workspace = true }
hanzo-tunnel = { version = "0.1", optional = true }
hanzo-tunnel-bridge = { workspace = true, optional = true }
hanzo-zt = { workspace = true, optional = true }
ctor = { workspace = true }
futures = { workspace = true }
@@ -73,6 +73,7 @@ tracing = { workspace = true, features = ["log"] }
tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] }
[features]
tunnel = ["dep:hanzo-tunnel", "dep:hanzo-tunnel-bridge"]
zt = ["dep:hanzo-zt"]
[dev-dependencies]
+5
View File
@@ -24,7 +24,9 @@ use copilot_cmd::CopilotArgs;
use copilot_cmd::execute_copilot_command;
use hanzo_cloud_tasks::Cli as CloudTasksCli;
use hanzo_common::CliConfigOverrides;
#[cfg(feature = "tunnel")]
mod cloud;
#[cfg(feature = "tunnel")]
use cloud::CloudArgs;
use hanzo_core::SessionCatalog;
use hanzo_core::SessionQuery;
@@ -108,6 +110,7 @@ struct MultitoolCli {
/// Connect to the Hanzo cloud relay for remote management from app.hanzo.bot.
/// Also enabled by HANZO_CLOUD=1. Optionally pass a URL: --cloud wss://...
#[cfg(feature = "tunnel")]
#[clap(flatten)]
cloud: CloudArgs,
@@ -437,6 +440,7 @@ async fn cli_main(hanzo_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()
auto_drive,
demo_developer_message,
ignore_api_keys,
#[cfg(feature = "tunnel")]
cloud: cloud_args,
subcommand,
} = MultitoolCli::parse();
@@ -468,6 +472,7 @@ async fn cli_main(hanzo_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()
};
// Spawn cloud tunnel connection if --cloud or HANZO_CLOUD=1.
#[cfg(feature = "tunnel")]
let _cloud_handle = cloud::maybe_connect(&cloud_args).await;
match subcommand {
+4 -1
View File
@@ -7,7 +7,7 @@ repository = { workspace = true }
edition.workspace = true
[dependencies]
hanzo-tunnel = { path = "../../../tunnel" }
hanzo-tunnel = { version = "0.1", optional = true }
hanzo-app-server-protocol = { path = "../app-server-protocol", version = "0.6.74" }
mcp-types = { workspace = true }
serde = { workspace = true, features = ["derive"] }
@@ -16,6 +16,9 @@ tokio = { workspace = true, features = ["sync", "time"] }
tracing = { workspace = true }
uuid = { workspace = true, features = ["v4"] }
[features]
tunnel = ["dep:hanzo-tunnel"]
[dev-dependencies]
tokio = { workspace = true, features = ["rt-multi-thread", "macros", "test-util"] }
+43 -12
View File
@@ -6,18 +6,34 @@
//!
//! This crate is the thin adapter that makes dev remotely controllable from
//! `app.hanzo.bot` via the shared tunnel.
//!
//! Requires the `hanzo-tunnel` optional dependency to be enabled for full
//! functionality. Without it, only the translation helpers are available.
#[cfg(feature = "tunnel")]
use hanzo_tunnel::protocol::{CommandPayload, EventPayload, Frame};
use serde_json::Value;
#[cfg(feature = "tunnel")]
use std::sync::Arc;
#[cfg(feature = "tunnel")]
use tokio::sync::{Mutex, mpsc};
use tracing::{debug, warn};
use tracing::debug;
#[cfg(feature = "tunnel")]
use tracing::warn;
/// A JSON-RPC request to send to the app-server.
#[derive(Debug, Clone)]
pub struct JsonRpcRequest {
pub method: String,
pub params: Value,
}
/// Maps cloud command methods to app-server JSON-RPC methods.
///
/// Cloud commands are high-level ("chat.send", "exec.approve"), while
/// app-server uses specific JSON-RPC methods ("sendUserMessage",
/// "execCommandApproval", etc.).
#[cfg(feature = "tunnel")]
pub fn cloud_command_to_jsonrpc(
cmd: &CommandPayload,
conversation_id: &str,
@@ -87,8 +103,11 @@ pub fn cloud_command_to_jsonrpc(
///
/// App-server notifications use `codex/event/<EventType>` naming.
/// Cloud events use shorter `<category>.<action>` naming.
pub fn notification_to_cloud_event(method: &str, params: &Value) -> Option<Frame> {
let event_name = match method {
///
/// Returns the event name and the params as a tuple (without wrapping in a Frame)
/// so this helper is usable regardless of tunnel availability.
pub fn notification_event_name(method: &str) -> &str {
match method {
"codex/event/agentMessage" => "chat.message",
"codex/event/agentMessageDelta" => "chat.delta",
"codex/event/agentReasoning" => "chat.reasoning",
@@ -109,22 +128,21 @@ pub fn notification_to_cloud_event(method: &str, params: &Value) -> Option<Frame
debug!(method, "unmapped notification, forwarding raw");
method
}
};
}
}
/// Maps app-server notification methods to cloud event frames.
#[cfg(feature = "tunnel")]
pub fn notification_to_cloud_event(method: &str, params: &Value) -> Option<Frame> {
let event_name = notification_event_name(method);
Some(Frame::Event(EventPayload {
event: event_name.into(),
data: params.clone(),
}))
}
/// A JSON-RPC request to send to the app-server.
#[derive(Debug, Clone)]
pub struct JsonRpcRequest {
pub method: String,
pub params: Value,
}
/// Bridge state — tracks the active conversation and pending requests.
#[cfg(feature = "tunnel")]
pub struct TunnelBridge {
/// The active conversation ID (set after newConversation).
conversation_id: Arc<Mutex<Option<String>>>,
@@ -134,6 +152,7 @@ pub struct TunnelBridge {
next_id: Arc<std::sync::atomic::AtomicU64>,
}
#[cfg(feature = "tunnel")]
impl TunnelBridge {
/// Create a new bridge.
///
@@ -226,7 +245,7 @@ impl TunnelBridge {
}
}
#[cfg(test)]
#[cfg(all(test, feature = "tunnel"))]
mod tests {
use super::*;
@@ -379,3 +398,15 @@ mod tests {
assert_eq!(bridge.next_request_id(), 3);
}
}
#[cfg(test)]
mod basic_tests {
use super::*;
#[test]
fn event_name_mapping() {
assert_eq!(notification_event_name("codex/event/agentMessage"), "chat.message");
assert_eq!(notification_event_name("codex/event/taskStarted"), "turn.started");
assert_eq!(notification_event_name("unknown/event"), "unknown/event");
}
}
@@ -2,6 +2,10 @@
//!
//! These tests connect to a local bot gateway at ws://127.0.0.1:18789/v1/tunnel.
//! They will be skipped if the gateway is not running.
//!
//! Requires the `tunnel` feature to be enabled.
#![cfg(feature = "tunnel")]
use hanzo_tunnel::{AppKind, TunnelConfig};
use std::time::Duration;
+2 -2
View File
@@ -3,8 +3,8 @@
"private": true,
"description": "Tools for repo-wide maintenance.",
"scripts": {
"format": "prettier --check *.json *.md docs/*.md .github/workflows/*.yml **/*.js",
"format:fix": "prettier --write *.json *.md docs/*.md .github/workflows/*.yml **/*.js",
"format": "prettier --check '*.json' '!(CLAUDE|LLM|AGENTS).md' 'docs/*.md' '.github/workflows/*.yml' 'scripts/*.js' 'cli-proxy/*.js' 'codex-cli/*.js' 'codex-cli/scripts/*.js' 'console/*.js' 'dev-cli/*.js' 'hanzo-cli/*.js' 'hanzo-node/*.js'",
"format:fix": "prettier --write '*.json' '!(CLAUDE|LLM|AGENTS).md' 'docs/*.md' '.github/workflows/*.yml' 'scripts/*.js' 'cli-proxy/*.js' 'codex-cli/*.js' 'codex-cli/scripts/*.js' 'console/*.js' 'dev-cli/*.js' 'hanzo-cli/*.js' 'hanzo-node/*.js'",
"build": "cd hanzo-dev && cargo build --release --bin dev --bin dev-tui --bin dev-exec",
"build:dev": "cd hanzo-dev && cargo build --bin dev --bin dev-tui --bin dev-exec",
"build:quick": "cd hanzo-dev && cargo build --release --bin dev",
+166 -89
View File
@@ -29,15 +29,17 @@
//
// # Or run your usual `dev` subcommand (Exec/TUI) with the same env vars.
const http = require('http');
const https = require('https');
const crypto = require('crypto');
const { URL } = require('url');
const http = require("http");
const https = require("https");
const crypto = require("crypto");
const { URL } = require("url");
const PORT = Number(process.env.PORT || 5055);
const API_KEY = process.env.OPENAI_API_KEY || '';
const UPSTREAM = new URL(process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1');
const ALLOWED = ['/v1/chat/completions', '/v1/responses'];
const API_KEY = process.env.OPENAI_API_KEY || "";
const UPSTREAM = new URL(
process.env.OPENAI_BASE_URL || "https://api.openai.com/v1",
);
const ALLOWED = ["/v1/chat/completions", "/v1/responses"];
// CI options:
// EXIT_ON_5XX=1 -> Exit process non‑zero if a 5xx response head is seen
@@ -53,26 +55,26 @@ const ALLOWED = ['/v1/chat/completions', '/v1/responses'];
// LOG_ERROR_BODY_BYTES=1024 -> Max bytes to log from error body
// STRICT_HEADERS=1 -> Rebuild upstream headers from a minimal allowlist
// RESPONSES_BETA="responses=v1" (override beta header)
const EXIT_ON_5XX = process.env.EXIT_ON_5XX === '1' || false;
const READY_FILE = process.env.READY_FILE || '';
const LOG_DEST = (process.env.LOG_DEST || 'stdout').toLowerCase();
const DISABLE_KEEPALIVE = process.env.DISABLE_KEEPALIVE === '1' || false;
const FORCE_CLOSE = process.env.FORCE_CLOSE === '1' || false;
const NO_GZIP = process.env.NO_GZIP === '1' || false;
const STRIP_SESSION_ID = process.env.STRIP_SESSION_ID === '1' || false;
const IDEMPOTENCY_KEY = (process.env.IDEMPOTENCY_KEY || '').toLowerCase();
const LOG_ERROR_BODY = process.env.LOG_ERROR_BODY === '1' || false;
const EXIT_ON_5XX = process.env.EXIT_ON_5XX === "1" || false;
const READY_FILE = process.env.READY_FILE || "";
const LOG_DEST = (process.env.LOG_DEST || "stdout").toLowerCase();
const DISABLE_KEEPALIVE = process.env.DISABLE_KEEPALIVE === "1" || false;
const FORCE_CLOSE = process.env.FORCE_CLOSE === "1" || false;
const NO_GZIP = process.env.NO_GZIP === "1" || false;
const STRIP_SESSION_ID = process.env.STRIP_SESSION_ID === "1" || false;
const IDEMPOTENCY_KEY = (process.env.IDEMPOTENCY_KEY || "").toLowerCase();
const LOG_ERROR_BODY = process.env.LOG_ERROR_BODY === "1" || false;
const LOG_ERROR_BODY_BYTES = Number(process.env.LOG_ERROR_BODY_BYTES || 2048);
const STRICT_HEADERS = process.env.STRICT_HEADERS === '1' || false;
const RESPONSES_BETA = process.env.RESPONSES_BETA || 'responses=v1';
const STRICT_HEADERS = process.env.STRICT_HEADERS === "1" || false;
const RESPONSES_BETA = process.env.RESPONSES_BETA || "responses=v1";
function outWrite(s) {
if (LOG_DEST === 'stderr') process.stderr.write(s + '\n');
else process.stdout.write(s + '\n');
if (LOG_DEST === "stderr") process.stderr.write(s + "\n");
else process.stdout.write(s + "\n");
}
if (!API_KEY) {
console.error('[fatal] OPENAI_API_KEY missing');
console.error("[fatal] OPENAI_API_KEY missing");
process.exit(1);
}
@@ -80,119 +82,194 @@ function redactHeaders(h) {
const out = {};
for (const [k, v] of Object.entries(h || {})) {
const key = k.toLowerCase();
if (key === 'authorization' || key === 'proxy-authorization' || key === 'cookie' || key === 'set-cookie') {
out[key] = 'REDACTED';
if (
key === "authorization" ||
key === "proxy-authorization" ||
key === "cookie" ||
key === "set-cookie"
) {
out[key] = "REDACTED";
} else {
out[key] = Array.isArray(v) ? v.map(() => '<omitted>') : String(v);
out[key] = Array.isArray(v) ? v.map(() => "<omitted>") : String(v);
}
}
return out;
}
function log(ev) {
try { outWrite(JSON.stringify({ ts: new Date().toISOString(), ...ev })); } catch {}
try {
outWrite(JSON.stringify({ ts: new Date().toISOString(), ...ev }));
} catch {}
}
const server = http.createServer((req, res) => {
const rid = crypto.randomUUID();
if (!ALLOWED.some(p => req.url.startsWith(p))) {
res.writeHead(403, { 'content-type': 'application/json' });
res.end(JSON.stringify({ error: 'blocked', path: req.url }));
if (!ALLOWED.some((p) => req.url.startsWith(p))) {
res.writeHead(403, { "content-type": "application/json" });
res.end(JSON.stringify({ error: "blocked", path: req.url }));
return;
}
const chunks = [];
let size = 0;
req.on('data', c => { chunks.push(c); size += c.length; if (size > 2 * 1024 * 1024) { req.destroy(new Error('body too large')); } });
req.on('error', err => {
log({ level: 'error', rid, msg: 'client request error', err: String(err) });
if (!res.headersSent) res.writeHead(400, { 'content-type': 'application/json' });
res.end(JSON.stringify({ error: 'bad_request' }));
req.on("data", (c) => {
chunks.push(c);
size += c.length;
if (size > 2 * 1024 * 1024) {
req.destroy(new Error("body too large"));
}
});
req.on('end', () => {
req.on("error", (err) => {
log({ level: "error", rid, msg: "client request error", err: String(err) });
if (!res.headersSent)
res.writeHead(400, { "content-type": "application/json" });
res.end(JSON.stringify({ error: "bad_request" }));
});
req.on("end", () => {
const body = Buffer.concat(chunks);
const up = new URL(req.url, UPSTREAM);
let incoming;
if (STRICT_HEADERS) {
incoming = {};
incoming['authorization'] = `Bearer ${API_KEY}`;
incoming['content-type'] = 'application/json';
incoming['accept'] = 'text/event-stream';
if (NO_GZIP) incoming['accept-encoding'] = 'identity';
if (up.pathname.startsWith('/v1/responses')) incoming['openai-beta'] = RESPONSES_BETA;
incoming['user-agent'] = 'code-proxy/1.0';
incoming['originator'] = 'codex_cli_rs';
if (IDEMPOTENCY_KEY === 'auto') incoming['idempotency-key'] = crypto.randomUUID();
incoming["authorization"] = `Bearer ${API_KEY}`;
incoming["content-type"] = "application/json";
incoming["accept"] = "text/event-stream";
if (NO_GZIP) incoming["accept-encoding"] = "identity";
if (up.pathname.startsWith("/v1/responses"))
incoming["openai-beta"] = RESPONSES_BETA;
incoming["user-agent"] = "code-proxy/1.0";
incoming["originator"] = "codex_cli_rs";
if (IDEMPOTENCY_KEY === "auto")
incoming["idempotency-key"] = crypto.randomUUID();
} else {
incoming = { ...req.headers };
if (!incoming['content-type']) incoming['content-type'] = 'application/json';
if (!incoming['accept']) incoming['accept'] = 'text/event-stream';
if (NO_GZIP) incoming['accept-encoding'] = 'identity';
incoming['authorization'] = `Bearer ${API_KEY}`; // replace with real key
if (up.pathname.startsWith('/v1/responses')) {
if (!incoming['openai-beta']) incoming['openai-beta'] = RESPONSES_BETA;
if (!incoming["content-type"])
incoming["content-type"] = "application/json";
if (!incoming["accept"]) incoming["accept"] = "text/event-stream";
if (NO_GZIP) incoming["accept-encoding"] = "identity";
incoming["authorization"] = `Bearer ${API_KEY}`; // replace with real key
if (up.pathname.startsWith("/v1/responses")) {
if (!incoming["openai-beta"]) incoming["openai-beta"] = RESPONSES_BETA;
}
if (!incoming['originator']) incoming['originator'] = 'codex_cli_rs';
if (STRIP_SESSION_ID && 'session_id' in incoming) delete incoming['session_id'];
if (IDEMPOTENCY_KEY === 'auto') incoming['idempotency-key'] = crypto.randomUUID();
if (!incoming["originator"]) incoming["originator"] = "codex_cli_rs";
if (STRIP_SESSION_ID && "session_id" in incoming)
delete incoming["session_id"];
if (IDEMPOTENCY_KEY === "auto")
incoming["idempotency-key"] = crypto.randomUUID();
}
delete incoming['host']; incoming['host'] = up.host;
incoming['connection'] = FORCE_CLOSE ? 'close' : 'keep-alive';
if (incoming['content-length']) incoming['content-length'] = String(body.length);
delete incoming['proxy-connection'];
delete incoming['proxy-authorization'];
delete incoming["host"];
incoming["host"] = up.host;
incoming["connection"] = FORCE_CLOSE ? "close" : "keep-alive";
if (incoming["content-length"])
incoming["content-length"] = String(body.length);
delete incoming["proxy-connection"];
delete incoming["proxy-authorization"];
log({ level: 'info', rid, phase: 'request', method: req.method, url: up.toString(), headers: redactHeaders(incoming), body_bytes: body.length });
log({
level: "info",
rid,
phase: "request",
method: req.method,
url: up.toString(),
headers: redactHeaders(incoming),
body_bytes: body.length,
});
const opts = {
protocol: up.protocol,
hostname: up.hostname,
port: up.port || (up.protocol === 'https:' ? 443 : 80),
port: up.port || (up.protocol === "https:" ? 443 : 80),
path: up.pathname + up.search,
method: req.method,
headers: incoming,
servername: up.hostname,
agent: (up.protocol === 'https:'
? new https.Agent({ keepAlive: !DISABLE_KEEPALIVE, maxSockets: 64, servername: up.hostname })
: new http.Agent({ keepAlive: !DISABLE_KEEPALIVE, maxSockets: 64 }))
agent:
up.protocol === "https:"
? new https.Agent({
keepAlive: !DISABLE_KEEPALIVE,
maxSockets: 64,
servername: up.hostname,
})
: new http.Agent({ keepAlive: !DISABLE_KEEPALIVE, maxSockets: 64 }),
};
const upstream = (up.protocol === 'https:' ? https : http).request(opts, (upr) => {
const resHeaders = { ...upr.headers };
res.writeHead(upr.statusCode || 500, resHeaders);
log({ level: 'info', rid, phase: 'response_head', status: upr.statusCode, headers: redactHeaders(resHeaders) });
if (EXIT_ON_5XX && upr.statusCode && upr.statusCode >= 500) {
// Propagate response then schedule exit(2) once stream completes
upr.on('end', () => { try { process.exitCode = 2; } catch {} });
}
let total = 0;
const chunks = [];
upr.on('data', (chunk) => { total += chunk.length; if (LOG_ERROR_BODY && upr.statusCode && upr.statusCode >= 400) { if (chunks.length < 32 && total <= LOG_ERROR_BODY_BYTES) chunks.push(chunk); } });
upr.on('end', () => {
if (LOG_ERROR_BODY && upr.statusCode && upr.statusCode >= 400) {
let bodyStr = '';
try { bodyStr = Buffer.concat(chunks).toString('utf8'); } catch {}
log({ level: 'error', rid, phase: 'response_error_body', status: upr.statusCode, body: bodyStr.slice(0, LOG_ERROR_BODY_BYTES) });
const upstream = (up.protocol === "https:" ? https : http).request(
opts,
(upr) => {
const resHeaders = { ...upr.headers };
res.writeHead(upr.statusCode || 500, resHeaders);
log({
level: "info",
rid,
phase: "response_head",
status: upr.statusCode,
headers: redactHeaders(resHeaders),
});
if (EXIT_ON_5XX && upr.statusCode && upr.statusCode >= 500) {
// Propagate response then schedule exit(2) once stream completes
upr.on("end", () => {
try {
process.exitCode = 2;
} catch {}
});
}
log({ level: 'info', rid, phase: 'response_end', status: upr.statusCode, bytes: total, request_id: resHeaders['x-request-id'] || null });
});
upr.on('error', (e) => { log({ level: 'error', rid, phase: 'response_error', err: String(e) }); });
upr.pipe(res);
let total = 0;
const chunks = [];
upr.on("data", (chunk) => {
total += chunk.length;
if (LOG_ERROR_BODY && upr.statusCode && upr.statusCode >= 400) {
if (chunks.length < 32 && total <= LOG_ERROR_BODY_BYTES)
chunks.push(chunk);
}
});
upr.on("end", () => {
if (LOG_ERROR_BODY && upr.statusCode && upr.statusCode >= 400) {
let bodyStr = "";
try {
bodyStr = Buffer.concat(chunks).toString("utf8");
} catch {}
log({
level: "error",
rid,
phase: "response_error_body",
status: upr.statusCode,
body: bodyStr.slice(0, LOG_ERROR_BODY_BYTES),
});
}
log({
level: "info",
rid,
phase: "response_end",
status: upr.statusCode,
bytes: total,
request_id: resHeaders["x-request-id"] || null,
});
});
upr.on("error", (e) => {
log({ level: "error", rid, phase: "response_error", err: String(e) });
});
upr.pipe(res);
},
);
upstream.setTimeout(600000, () => {
upstream.destroy(new Error("upstream timeout"));
});
upstream.setTimeout(600000, () => { upstream.destroy(new Error('upstream timeout')); });
upstream.on('error', (e) => {
log({ level: 'error', rid, phase: 'upstream_error', err: String(e) });
if (!res.headersSent) res.writeHead(502, { 'content-type': 'application/json' });
res.end(JSON.stringify({ error: 'upstream_error' }));
upstream.on("error", (e) => {
log({ level: "error", rid, phase: "upstream_error", err: String(e) });
if (!res.headersSent)
res.writeHead(502, { "content-type": "application/json" });
res.end(JSON.stringify({ error: "upstream_error" }));
});
upstream.end(body);
});
});
server.headersTimeout = 650000;
server.keepAliveTimeout = 650000;
server.listen(PORT, '127.0.0.1', () => {
log({ level: 'info', msg: 'proxy listening', addr: '127.0.0.1', port: PORT });
server.listen(PORT, "127.0.0.1", () => {
log({ level: "info", msg: "proxy listening", addr: "127.0.0.1", port: PORT });
if (READY_FILE) {
try { require('fs').writeFileSync(READY_FILE, String(Date.now())); } catch {}
try {
require("fs").writeFileSync(READY_FILE, String(Date.now()));
} catch {}
}
});