mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
merge main into code-branch-20250918-072155 before merge
This commit is contained in:
@@ -3,6 +3,13 @@
|
||||
> [!TIP]
|
||||
> We're constantly improving Code! This page documents the core changes. You can also check our [releases page](https://github.com/just-every/code/releases) for additional information.
|
||||
|
||||
## [0.2.155] - 2025-09-18
|
||||
|
||||
- Auth: fix onboarding auth prompt gating. (87a76d25)
|
||||
- CLI: add long-run calculator script. (b01e2b38)
|
||||
- TUI: add pulldown-cmark dependency to fix build. (f1718b03)
|
||||
- Docs: clarify config directories. (cc22fbd9)
|
||||
|
||||
## [0.2.154] - 2025-09-18
|
||||
|
||||
- TUI/Input: fix Shift+Tab crash. (354a6faa)
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
- 🧠 **Reasoning Control** - /reasoning for dynamic effort adjustment
|
||||
- 🔌 **MCP support** – Extend with filesystem, DBs, APIs, or your own tools.
|
||||
- 🔒 **Safety modes** – Read-only, approvals, and workspace sandboxing.
|
||||
- 🔁 **Backwards compatible** – Supports `~/.codex/*` or default `~/.coder/*`
|
||||
- 🔁 **Backwards compatible** – Reads both `~/.code/*` (primary) and legacy `~/.codex/*`; writes only to `~/.code/*`
|
||||
|
||||
 
|
||||
| <img src="docs/screenshots/simple.png" alt="Simple interface" width="100%"><br>Simple interface | <img src="docs/screenshots/diff.png" alt="Unified diff viewer" width="100%"><br>Unified diffs |
|
||||
@@ -51,7 +51,7 @@ Note: If another tool already provides a `code` command (e.g. VS Code), our CLI
|
||||
**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"
|
||||
- Stores creds locally at `~/.coder/auth.json` (also reads legacy `~/.codex/auth.json`)
|
||||
- Stores creds locally at `~/.code/auth.json` (still reads legacy `~/.codex/auth.json` if present)
|
||||
- **API key** (usage-based)
|
||||
- Set `export OPENAI_API_KEY=xyz` and run `code`
|
||||
|
||||
@@ -173,7 +173,7 @@ Code supports MCP for extended capabilities:
|
||||
- **API integrations**: Connect to external services
|
||||
- **Custom tools**: Build your own extensions
|
||||
|
||||
Configure MCP in `~/.codex/config.toml`. Define each server under a named table like `[mcp_servers.<name>]` (this maps to the JSON `mcpServers` object used by other clients):
|
||||
Configure MCP in `~/.code/config.toml` (legacy `~/.codex/config.toml` is still read if present). Define each server under a named table like `[mcp_servers.<name>]` (this maps to the JSON `mcpServers` object used by other clients):
|
||||
|
||||
```toml
|
||||
[mcp_servers.filesystem]
|
||||
@@ -184,7 +184,10 @@ args = ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/project"]
|
||||
 
|
||||
## Configuration
|
||||
|
||||
Main config file: `~/.codex/config.toml`
|
||||
Main config file: `~/.code/config.toml`
|
||||
|
||||
> [!NOTE]
|
||||
> Code reads from both `~/.code/` and `~/.codex/` for backwards compatibility, but it only writes updates to `~/.code/`. If you switch back to Codex and it fails to start, remove `~/.codex/config.toml`. If Code appears to miss settings after upgrading, copy your legacy `~/.codex/config.toml` into `~/.code/`.
|
||||
|
||||
```toml
|
||||
# Model settings
|
||||
@@ -222,7 +225,7 @@ model_reasoning_summary = "detailed"
|
||||
> 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! This fork is fully backward compatible with existing `~/.codex/` configurations.
|
||||
> Yes. 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.
|
||||
@@ -275,7 +278,7 @@ Using OpenAI, Anthropic or Google services through Code means you agree to **the
|
||||
- If you configure other model providers, you're responsible for their terms.
|
||||
|
||||
### Privacy
|
||||
- Your auth file lives at `~/.codex/auth.json`.
|
||||
- Your auth file lives at `~/.code/auth.json` (legacy `~/.codex/auth.json` is still read).
|
||||
- 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@just-every/code",
|
||||
"version": "0.2.154",
|
||||
"version": "0.2.155",
|
||||
"license": "Apache-2.0",
|
||||
"description": "Lightweight coding agent that runs in your terminal - fork of OpenAI Codex",
|
||||
"bin": {
|
||||
@@ -35,10 +35,10 @@
|
||||
"prettier": "^3.3.3"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@just-every/code-darwin-arm64": "0.2.154",
|
||||
"@just-every/code-darwin-x64": "0.2.154",
|
||||
"@just-every/code-linux-x64-musl": "0.2.154",
|
||||
"@just-every/code-linux-arm64-musl": "0.2.154",
|
||||
"@just-every/code-win32-x64": "0.2.154"
|
||||
"@just-every/code-darwin-arm64": "0.2.155",
|
||||
"@just-every/code-darwin-x64": "0.2.155",
|
||||
"@just-every/code-linux-x64-musl": "0.2.155",
|
||||
"@just-every/code-linux-arm64-musl": "0.2.155",
|
||||
"@just-every/code-win32-x64": "0.2.155"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -90,7 +90,7 @@ codex --sandbox workspace-write
|
||||
codex --sandbox danger-full-access
|
||||
```
|
||||
|
||||
The same setting can be persisted in `~/.codex/config.toml` via the top-level `sandbox_mode = "MODE"` key, e.g. `sandbox_mode = "workspace-write"`.
|
||||
The same setting can be persisted in `~/.code/config.toml` via the top-level `sandbox_mode = "MODE"` key (Code will also read legacy `~/.codex/config.toml`), e.g. `sandbox_mode = "workspace-write"`.
|
||||
|
||||
If you want to prevent the agent from updating Git metadata (e.g., local safety), you can opt‑out with a workspace‑write tweak:
|
||||
|
||||
@@ -125,4 +125,3 @@ This folder is the root of a Cargo workspace. It contains quite a bit of experim
|
||||
- [`exec/`](./exec) "headless" CLI for use in automation.
|
||||
- [`tui/`](./tui) CLI that launches a fullscreen TUI built with [Ratatui](https://ratatui.rs/).
|
||||
- [`cli/`](./cli) CLI multitool that provides the aforementioned CLIs via subcommands.
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ where
|
||||
|
||||
const ILLEGAL_ENV_VAR_PREFIX: &str = "CODEX_";
|
||||
|
||||
/// Load env vars from ~/.codex/.env and `$(pwd)/.env`.
|
||||
/// Load env vars from ~/.code/.env (legacy ~/.codex/.env is still read) and `$(pwd)/.env`.
|
||||
///
|
||||
/// Security: Do not allow `.env` files to create or modify any variables
|
||||
/// with names starting with `CODEX_`.
|
||||
|
||||
@@ -174,9 +174,11 @@ struct GenerateTsCommand {
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct OrderReplayArgs {
|
||||
/// Path to a response.json captured under ~/.codex/debug_logs/*_response.json
|
||||
/// Path to a response.json captured under ~/.code/debug_logs/*_response.json
|
||||
/// (legacy ~/.codex/debug_logs/ is still read).
|
||||
response_json: std::path::PathBuf,
|
||||
/// Path to codex-tui.log (typically ~/.codex/log/codex-tui.log)
|
||||
/// Path to codex-tui.log (typically ~/.code/log/codex-tui.log; legacy
|
||||
/// ~/.codex/log/codex-tui.log is still read).
|
||||
tui_log: std::path::PathBuf,
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ use codex_core::config_types::McpServerConfig;
|
||||
/// - `serve` — run the MCP server on stdio
|
||||
/// - `list` — list configured servers (with `--json`)
|
||||
/// - `get` — show a single server (with `--json`)
|
||||
/// - `add` — add a server launcher entry to `~/.codex/config.toml`
|
||||
/// - `add` — add a server launcher entry to `~/.code/config.toml` (Code also reads legacy `~/.codex/config.toml`)
|
||||
/// - `remove` — delete a server entry
|
||||
#[derive(Debug, clap::Parser)]
|
||||
pub struct McpCli {
|
||||
|
||||
@@ -18,7 +18,7 @@ use toml::Value;
|
||||
#[derive(Parser, Debug, Default, Clone)]
|
||||
pub struct CliConfigOverrides {
|
||||
/// Override a configuration value that would otherwise be loaded from
|
||||
/// `~/.codex/config.toml`. Use a dotted path (`foo.bar.baz`) to override
|
||||
/// `~/.code/config.toml` (Code also reads legacy `~/.codex/config.toml`). Use a dotted path (`foo.bar.baz`) to override
|
||||
/// nested values. The `value` portion is parsed as JSON. If it fails to
|
||||
/// parse as JSON, the raw string is used as a literal.
|
||||
///
|
||||
|
||||
+3
-3
@@ -8,7 +8,7 @@ This file has moved. Please see the latest configuration documentation here:
|
||||
- Values can contain objects, such as `--config shell_environment_policy.include_only=["PATH", "HOME", "USER"]`.
|
||||
- For consistency with `config.toml`, values are in TOML format rather than JSON format, so use `{a = 1, b = 2}` rather than `{"a": 1, "b": 2}`.
|
||||
- If `value` cannot be parsed as a valid TOML value, it is treated as a string value. This means that both `-c model="o3"` and `-c model=o3` are equivalent.
|
||||
- The `$CODEX_HOME/config.toml` configuration file where the `CODEX_HOME` environment value defaults to `~/.codex`. (Note `CODEX_HOME` will also be where logs and other Codex-related information are stored.)
|
||||
- The `$CODE_HOME/config.toml` configuration file. `CODE_HOME` defaults to `~/.code`; Code also reads from `$CODEX_HOME`/`~/.codex` for backwards compatibility but only writes to `~/.code`. (Logs and other state use the same directory.)
|
||||
|
||||
Both the `--config` flag and the `config.toml` file support the following options:
|
||||
|
||||
@@ -369,7 +369,7 @@ This config option is comparable to how Claude and Cursor define `mcpServers` in
|
||||
}
|
||||
```
|
||||
|
||||
Should be represented as follows in `~/.codex/config.toml`:
|
||||
Should be represented as follows in `~/.code/config.toml` (Code will also read the legacy `~/.codex/config.toml` if it exists):
|
||||
|
||||
```toml
|
||||
# IMPORTANT: the top-level key is `mcp_servers` rather than `mcpServers`.
|
||||
@@ -500,7 +500,7 @@ if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
```
|
||||
|
||||
To have Codex use this script for notifications, you would configure it via `notify` in `~/.codex/config.toml` using the appropriate path to `notify.py` on your computer:
|
||||
To have Codex use this script for notifications, you would configure it via `notify` in `~/.code/config.toml` (legacy `~/.codex/config.toml` is still read) using the appropriate path to `notify.py` on your computer:
|
||||
|
||||
```toml
|
||||
notify = ["python3", "/Users/mbolin/.codex/notify.py"]
|
||||
|
||||
+265
-176
@@ -17,6 +17,8 @@ use base64::Engine;
|
||||
use codex_apply_patch::ApplyPatchAction;
|
||||
use codex_apply_patch::MaybeApplyPatchVerified;
|
||||
use codex_apply_patch::maybe_parse_apply_patch_verified;
|
||||
use codex_browser::BrowserConfig as CodexBrowserConfig;
|
||||
use codex_browser::BrowserManager;
|
||||
// unused: AuthManager
|
||||
// unused: ConversationHistoryResponseEvent
|
||||
use codex_protocol::protocol::TurnAbortReason;
|
||||
@@ -3381,6 +3383,193 @@ async fn handle_web_fetch(sess: &Session, ctx: &ToolCallCtx, arguments: String)
|
||||
}
|
||||
};
|
||||
|
||||
struct BrowserFetchOutcome {
|
||||
html: String,
|
||||
final_url: Option<String>,
|
||||
headless: bool,
|
||||
}
|
||||
|
||||
async fn fetch_html_via_headless_browser(
|
||||
url: &str,
|
||||
timeout: Duration,
|
||||
) -> Result<BrowserFetchOutcome, String> {
|
||||
let mut config = CodexBrowserConfig::default();
|
||||
config.enabled = true;
|
||||
config.headless = true;
|
||||
config.fullpage = false;
|
||||
config.segments_max = 2;
|
||||
config.persist_profile = false;
|
||||
config.idle_timeout_ms = 10_000;
|
||||
|
||||
let manager = BrowserManager::new(config);
|
||||
manager.set_enabled_sync(true);
|
||||
|
||||
const CHECK_JS: &str = r#"(function(){
|
||||
const discuss = document.querySelectorAll('[data-test-selector=\"issue-comment-body\"]');
|
||||
const timeline = document.querySelectorAll('.js-timeline-item');
|
||||
const article = document.querySelectorAll('article, main');
|
||||
return (discuss.length + timeline.length + article.length);
|
||||
})()"#;
|
||||
const HTML_JS: &str =
|
||||
"(function(){ return { html: document.documentElement.outerHTML, title: document.title||'' }; })()";
|
||||
|
||||
let goto_result = match tokio::time::timeout(timeout, manager.goto(url)).await {
|
||||
Ok(Ok(res)) => res,
|
||||
Ok(Err(e)) => {
|
||||
let _ = manager.stop().await;
|
||||
return Err(format!("Headless goto failed: {e}"));
|
||||
}
|
||||
Err(_) => {
|
||||
let _ = manager.stop().await;
|
||||
return Err("Headless goto timed out".to_string());
|
||||
}
|
||||
};
|
||||
|
||||
for _ in 0..6 {
|
||||
match tokio::time::timeout(Duration::from_millis(1500), manager.execute_javascript(CHECK_JS)).await {
|
||||
Ok(Ok(val)) => {
|
||||
let count = val
|
||||
.get("value")
|
||||
.and_then(|v| v.as_i64())
|
||||
.unwrap_or(0);
|
||||
if count > 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
tracing::debug!("Headless readiness check failed: {}", e);
|
||||
break;
|
||||
}
|
||||
Err(_) => {
|
||||
tracing::debug!("Headless readiness check timed out");
|
||||
break;
|
||||
}
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(800)).await;
|
||||
}
|
||||
|
||||
let html_value = match tokio::time::timeout(timeout, manager.execute_javascript(HTML_JS)).await {
|
||||
Ok(Ok(val)) => val,
|
||||
Ok(Err(e)) => {
|
||||
let _ = manager.stop().await;
|
||||
return Err(format!("Headless HTML extraction failed: {e}"));
|
||||
}
|
||||
Err(_) => {
|
||||
let _ = manager.stop().await;
|
||||
return Err("Headless HTML extraction timed out".to_string());
|
||||
}
|
||||
};
|
||||
|
||||
let html = html_value
|
||||
.get("value")
|
||||
.and_then(|v| v.get("html"))
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
|
||||
if html.trim().is_empty() {
|
||||
let _ = manager.stop().await;
|
||||
return Err("Headless browser returned empty HTML".to_string());
|
||||
}
|
||||
|
||||
let final_url = Some(goto_result.url.clone());
|
||||
let _ = manager.stop().await;
|
||||
|
||||
Ok(BrowserFetchOutcome {
|
||||
html,
|
||||
final_url,
|
||||
headless: true,
|
||||
})
|
||||
}
|
||||
|
||||
async fn fetch_html_via_browser(
|
||||
url: &str,
|
||||
timeout: Duration,
|
||||
prefer_global: bool,
|
||||
) -> Option<BrowserFetchOutcome> {
|
||||
const HTML_JS: &str =
|
||||
"(function(){ return { html: document.documentElement.outerHTML, title: document.title||'' }; })()";
|
||||
const CHECK_JS: &str = r#"(function(){
|
||||
const discuss = document.querySelectorAll('[data-test-selector=\"issue-comment-body\"]');
|
||||
const timeline = document.querySelectorAll('.js-timeline-item');
|
||||
const article = document.querySelectorAll('article, main');
|
||||
return (discuss.length + timeline.length + article.length);
|
||||
})()"#;
|
||||
|
||||
if prefer_global {
|
||||
if let Some(manager) = codex_browser::global::get_browser_manager().await {
|
||||
if manager.is_enabled_sync() {
|
||||
match tokio::time::timeout(timeout, manager.goto(url)).await {
|
||||
Ok(Ok(res)) => {
|
||||
for _ in 0..6 {
|
||||
match tokio::time::timeout(Duration::from_millis(1500), manager.execute_javascript(CHECK_JS)).await {
|
||||
Ok(Ok(val)) => {
|
||||
let count = val
|
||||
.get("value")
|
||||
.and_then(|v| v.as_i64())
|
||||
.unwrap_or(0);
|
||||
if count > 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
tracing::debug!("Global browser readiness check failed: {}", e);
|
||||
break;
|
||||
}
|
||||
Err(_) => {
|
||||
tracing::debug!("Global browser readiness timed out");
|
||||
break;
|
||||
}
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(800)).await;
|
||||
}
|
||||
|
||||
match tokio::time::timeout(timeout, manager.execute_javascript(HTML_JS)).await {
|
||||
Ok(Ok(val)) => {
|
||||
if let Some(html) = val
|
||||
.get("value")
|
||||
.and_then(|v| v.get("html"))
|
||||
.and_then(|v| v.as_str())
|
||||
{
|
||||
if !html.trim().is_empty() {
|
||||
return Some(BrowserFetchOutcome {
|
||||
html: html.to_string(),
|
||||
final_url: Some(res.url.clone()),
|
||||
headless: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
tracing::debug!("Global browser HTML extraction failed: {}", e);
|
||||
}
|
||||
Err(_) => {
|
||||
tracing::debug!("Global browser HTML extraction timed out");
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
tracing::warn!("Global browser navigation failed: {}", e);
|
||||
}
|
||||
Err(_) => {
|
||||
tracing::warn!("Global browser navigation timed out");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::debug!("Global browser manager disabled; skipping UI fetch");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match fetch_html_via_headless_browser(url, timeout).await {
|
||||
Ok(outcome) => Some(outcome),
|
||||
Err(err) => {
|
||||
tracing::warn!("Headless browser fallback failed for {}: {}", url, err);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: build a client with a specific UA and common headers.
|
||||
async fn do_request(
|
||||
url: &str,
|
||||
@@ -3830,89 +4019,33 @@ async fn handle_web_fetch(sess: &Session, ctx: &ToolCallCtx, arguments: String)
|
||||
let timeout = Duration::from_millis(params.timeout_ms.unwrap_or(15000));
|
||||
let codex_ua = crate::default_client::get_codex_user_agent(Some("web_fetch"));
|
||||
|
||||
// Heuristic: some domains render key content client-side. Prefer a
|
||||
// quick browser render first so we capture comments/timelines.
|
||||
let is_dynamic_domain = {
|
||||
let u = params.url.to_lowercase();
|
||||
u.contains("github.com/")
|
||||
};
|
||||
|
||||
if !matches!(params.mode.as_deref(), Some("http")) && is_dynamic_domain {
|
||||
let browser_manager = codex_browser::global::get_or_create_browser_manager().await;
|
||||
if let Ok(res) = browser_manager.goto(¶ms.url).await {
|
||||
// Poll briefly for discussion/timeline elements to appear
|
||||
for _ in 0..6 {
|
||||
let js = r#"(function(){ const sel1 = document.querySelectorAll('[data-test-selector=\"issue-comment-body\"]'); const sel2 = document.querySelectorAll('.js-timeline-item'); return (sel1.length + sel2.length); })()"#;
|
||||
if let Ok(val) = browser_manager.execute_javascript(js).await {
|
||||
let n = val.get("value").and_then(|v| v.as_i64()).unwrap_or(0);
|
||||
if n > 0 { break; }
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(800)).await;
|
||||
}
|
||||
if let Ok(val) = browser_manager.execute_javascript(r#"(function(){ return { html: document.documentElement.outerHTML, title: document.title||'' }; })()"#).await {
|
||||
if let Some(html) = val.get("value").and_then(|v| v.get("html")).and_then(|v| v.as_str()) {
|
||||
let (markdown, truncated) = match convert_html_to_markdown_trimmed(html.to_string(), 120_000) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
return ResponseInputItem::FunctionCallOutput { call_id: call_id_clone, output: FunctionCallOutputPayload { content: format!("Markdown conversion failed: {e}"), success: Some(false) } };
|
||||
}
|
||||
};
|
||||
let body = serde_json::json!({
|
||||
"url": params.url,
|
||||
"status": 200,
|
||||
"final_url": res.url,
|
||||
"content_type": "text/html",
|
||||
"used_browser_ua": true,
|
||||
"via_browser": true,
|
||||
"truncated": truncated,
|
||||
"markdown": markdown,
|
||||
});
|
||||
return ResponseInputItem::FunctionCallOutput { call_id: call_id_clone, output: FunctionCallOutputPayload { content: body.to_string(), success: Some(true) } };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If explicit browser mode requested, try the internal browser first.
|
||||
if matches!(params.mode.as_deref(), Some("browser")) {
|
||||
{
|
||||
let browser_manager = codex_browser::global::get_or_create_browser_manager().await;
|
||||
if let Ok(res) = browser_manager.goto(¶ms.url).await {
|
||||
// Allow a few short settles for JS/cookie challenges to auto-resolve
|
||||
let mut html: Option<String> = None;
|
||||
for _ in 0..4 {
|
||||
if let Ok(val) = browser_manager.execute_javascript("(function(){ return { html: document.documentElement.outerHTML, title: document.title||'' }; })()").await {
|
||||
html = val.get("value").and_then(|v| v.get("html")).and_then(|v| v.as_str()).map(|s| s.to_string());
|
||||
let t_low = val.get("value").and_then(|v| v.get("title")).and_then(|v| v.as_str()).unwrap_or("").to_lowercase();
|
||||
if !(t_low.contains("just a moment") || t_low.contains("checking if") || t_low.contains("waiting for")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(1200)).await;
|
||||
}
|
||||
if let Some(html) = html {
|
||||
let (markdown, truncated) = match convert_html_to_markdown_trimmed(html, 120_000) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
return ResponseInputItem::FunctionCallOutput {
|
||||
call_id: call_id_clone,
|
||||
output: FunctionCallOutputPayload { content: format!("Markdown conversion failed: {e}"), success: Some(false) },
|
||||
};
|
||||
}
|
||||
if let Some(browser_fetch) = fetch_html_via_browser(¶ms.url, timeout, true).await {
|
||||
let (markdown, truncated) = match convert_html_to_markdown_trimmed(browser_fetch.html, 120_000) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
return ResponseInputItem::FunctionCallOutput {
|
||||
call_id: call_id_clone,
|
||||
output: FunctionCallOutputPayload { content: format!("Markdown conversion failed: {e}"), success: Some(false) },
|
||||
};
|
||||
let body = serde_json::json!({
|
||||
"url": params.url,
|
||||
"status": 200,
|
||||
"final_url": res.url,
|
||||
"content_type": "text/html",
|
||||
"used_browser_ua": true,
|
||||
"via_browser": true,
|
||||
"truncated": truncated,
|
||||
"markdown": markdown,
|
||||
});
|
||||
return ResponseInputItem::FunctionCallOutput { call_id: call_id_clone, output: FunctionCallOutputPayload { content: body.to_string(), success: Some(true) } };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let body = serde_json::json!({
|
||||
"url": params.url,
|
||||
"status": 200,
|
||||
"final_url": browser_fetch.final_url.unwrap_or_else(|| params.url.clone()),
|
||||
"content_type": "text/html",
|
||||
"used_browser_ua": true,
|
||||
"via_browser": true,
|
||||
"headless": browser_fetch.headless,
|
||||
"truncated": truncated,
|
||||
"markdown": markdown,
|
||||
});
|
||||
return ResponseInputItem::FunctionCallOutput {
|
||||
call_id: call_id_clone,
|
||||
output: FunctionCallOutputPayload { content: body.to_string(), success: Some(true) },
|
||||
};
|
||||
}
|
||||
}
|
||||
// Attempt 1: Codex UA + polite headers
|
||||
@@ -3990,71 +4123,37 @@ async fn handle_web_fetch(sess: &Session, ctx: &ToolCallCtx, arguments: String)
|
||||
if let Some(ra) = retry_after { diag["retry_after"] = serde_json::json!(ra); }
|
||||
if let Some(ray) = cf_ray { diag["cf_ray"] = serde_json::json!(ray); }
|
||||
|
||||
// Attempt a last-resort browser-based fetch if the live browser is available.
|
||||
{
|
||||
let browser_manager = codex_browser::global::get_or_create_browser_manager().await;
|
||||
browser_manager.set_enabled_sync(true);
|
||||
// Try navigate and extract outerHTML via JS (after SPA settles per manager config)
|
||||
if browser_manager.goto(¶ms.url).await.is_ok() {
|
||||
let js = "(function(){ return { html: document.documentElement.outerHTML, title: document.title||'' }; })()";
|
||||
if let Ok(val) = browser_manager.execute_javascript(js).await {
|
||||
let html = val.get("value").and_then(|v| v.get("html")).and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||
let title = val.get("value").and_then(|v| v.get("title")).and_then(|v| v.as_str()).unwrap_or("");
|
||||
if !html.is_empty() && title.to_lowercase() != "just a moment..." {
|
||||
let (markdown, truncated) = match convert_html_to_markdown_trimmed(html, 120_000) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
return ResponseInputItem::FunctionCallOutput {
|
||||
call_id: call_id_clone,
|
||||
output: FunctionCallOutputPayload { content: format!("Markdown conversion failed: {e}"), success: Some(false) },
|
||||
};
|
||||
}
|
||||
};
|
||||
diag["via_browser"] = serde_json::json!(true);
|
||||
let body = serde_json::json!({
|
||||
"url": params.url,
|
||||
"status": 200,
|
||||
"final_url": final_url,
|
||||
"content_type": content_type,
|
||||
"used_browser_ua": used_browser_ua,
|
||||
"truncated": truncated,
|
||||
"markdown": markdown,
|
||||
});
|
||||
return ResponseInputItem::FunctionCallOutput { call_id: call_id_clone, output: FunctionCallOutputPayload { content: body.to_string(), success: Some(true) } };
|
||||
}
|
||||
if let Some(browser_fetch) = fetch_html_via_browser(¶ms.url, timeout, false).await {
|
||||
let (markdown, truncated) = match convert_html_to_markdown_trimmed(browser_fetch.html, 120_000) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
return ResponseInputItem::FunctionCallOutput {
|
||||
call_id: call_id_clone,
|
||||
output: FunctionCallOutputPayload { content: format!("Markdown conversion failed: {e}"), success: Some(false) },
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// If JS extraction failed, try a CDP outerHTML of root
|
||||
let root = browser_manager.execute_cdp("DOM.getDocument", json!({"depth": 1})).await.ok();
|
||||
if let Some(root) = root {
|
||||
if let Some(node_id) = root.get("root").and_then(|r| r.get("nodeId")).and_then(|n| n.as_u64()) {
|
||||
if let Ok(outer) = browser_manager.execute_cdp("DOM.getOuterHTML", json!({"nodeId": node_id})).await {
|
||||
if let Some(html) = outer.get("outerHTML").and_then(|v| v.as_str()) {
|
||||
let (markdown, truncated) = match convert_html_to_markdown_trimmed(html.to_string(), 120_000) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
return ResponseInputItem::FunctionCallOutput {
|
||||
call_id: call_id_clone,
|
||||
output: FunctionCallOutputPayload { content: format!("Markdown conversion failed: {e}"), success: Some(false) },
|
||||
};
|
||||
}
|
||||
};
|
||||
diag["via_browser"] = serde_json::json!(true);
|
||||
let body = serde_json::json!({
|
||||
"url": params.url,
|
||||
"status": 200,
|
||||
"final_url": final_url,
|
||||
"content_type": content_type,
|
||||
"used_browser_ua": used_browser_ua,
|
||||
"truncated": truncated,
|
||||
"markdown": markdown,
|
||||
});
|
||||
return ResponseInputItem::FunctionCallOutput { call_id: call_id_clone, output: FunctionCallOutputPayload { content: body.to_string(), success: Some(true) } };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
diag["via_browser"] = serde_json::json!(true);
|
||||
if browser_fetch.headless {
|
||||
diag["headless"] = serde_json::json!(true);
|
||||
}
|
||||
|
||||
let body = serde_json::json!({
|
||||
"url": params.url,
|
||||
"status": 200,
|
||||
"final_url": browser_fetch.final_url.unwrap_or_else(|| final_url.clone()),
|
||||
"content_type": content_type,
|
||||
"used_browser_ua": true,
|
||||
"via_browser": true,
|
||||
"headless": browser_fetch.headless,
|
||||
"truncated": truncated,
|
||||
"markdown": markdown,
|
||||
});
|
||||
return ResponseInputItem::FunctionCallOutput {
|
||||
call_id: call_id_clone,
|
||||
output: FunctionCallOutputPayload { content: body.to_string(), success: Some(true) },
|
||||
};
|
||||
}
|
||||
|
||||
let (md_preview, _trunc) = match convert_html_to_markdown_trimmed(body_text, 2000) {
|
||||
@@ -4147,42 +4246,32 @@ async fn handle_web_fetch(sess: &Session, ctx: &ToolCallCtx, arguments: String)
|
||||
|
||||
// If the rendered markdown still looks like a challenge page, attempt browser fallback (unless http-only).
|
||||
if !matches!(params.mode.as_deref(), Some("http")) && looks_like_challenge_markdown(&markdown) {
|
||||
{
|
||||
let browser_manager = codex_browser::global::get_or_create_browser_manager().await;
|
||||
browser_manager.set_enabled_sync(true);
|
||||
if browser_manager.goto(¶ms.url).await.is_ok() {
|
||||
let js = "(function(){ return { html: document.documentElement.outerHTML, title: document.title||'' }; })()";
|
||||
let mut html: Option<String> = None;
|
||||
for _ in 0..3 {
|
||||
if let Ok(val) = browser_manager.execute_javascript(js).await {
|
||||
html = val.get("value").and_then(|v| v.get("html")).and_then(|v| v.as_str()).map(|s| s.to_string());
|
||||
let t_low = val.get("value").and_then(|v| v.get("title")).and_then(|v| v.as_str()).unwrap_or("").to_lowercase();
|
||||
if !(t_low.contains("just a moment") || t_low.contains("checking if") || t_low.contains("waiting for")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(1200)).await;
|
||||
}
|
||||
if let Some(html) = html {
|
||||
let (md2, truncated2) = match convert_html_to_markdown_trimmed(html, 120_000) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
return ResponseInputItem::FunctionCallOutput { call_id: call_id_clone, output: FunctionCallOutputPayload { content: format!("Markdown conversion failed: {e}"), success: Some(false) } };
|
||||
}
|
||||
if let Some(browser_fetch) = fetch_html_via_browser(¶ms.url, timeout, false).await {
|
||||
let (md2, truncated2) = match convert_html_to_markdown_trimmed(browser_fetch.html, 120_000) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
return ResponseInputItem::FunctionCallOutput {
|
||||
call_id: call_id_clone,
|
||||
output: FunctionCallOutputPayload { content: format!("Markdown conversion failed: {e}"), success: Some(false) },
|
||||
};
|
||||
let body = serde_json::json!({
|
||||
"url": params.url,
|
||||
"status": 200,
|
||||
"final_url": final_url,
|
||||
"content_type": content_type,
|
||||
"used_browser_ua": true,
|
||||
"via_browser": true,
|
||||
"truncated": truncated2,
|
||||
"markdown": md2,
|
||||
});
|
||||
return ResponseInputItem::FunctionCallOutput { call_id: call_id_clone, output: FunctionCallOutputPayload { content: body.to_string(), success: Some(true) } };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let body = serde_json::json!({
|
||||
"url": params.url,
|
||||
"status": 200,
|
||||
"final_url": browser_fetch.final_url.unwrap_or_else(|| final_url.clone()),
|
||||
"content_type": content_type,
|
||||
"used_browser_ua": true,
|
||||
"via_browser": true,
|
||||
"headless": browser_fetch.headless,
|
||||
"truncated": truncated2,
|
||||
"markdown": md2,
|
||||
});
|
||||
return ResponseInputItem::FunctionCallOutput {
|
||||
call_id: call_id_clone,
|
||||
output: FunctionCallOutputPayload { content: body.to_string(), success: Some(true) },
|
||||
};
|
||||
}
|
||||
|
||||
// If fallback not possible, return structured error rather than a useless challenge page
|
||||
|
||||
@@ -120,7 +120,8 @@ pub struct Config {
|
||||
/// appends one extra argument containing a JSON payload describing the
|
||||
/// event.
|
||||
///
|
||||
/// Example `~/.codex/config.toml` snippet:
|
||||
/// Example `~/.code/config.toml` snippet (Code also reads legacy
|
||||
/// `~/.codex/config.toml`):
|
||||
///
|
||||
/// ```toml
|
||||
/// notify = ["notify-send", "Codex"]
|
||||
@@ -156,11 +157,12 @@ pub struct Config {
|
||||
/// Maximum number of bytes to include from an AGENTS.md project doc file.
|
||||
pub project_doc_max_bytes: usize,
|
||||
|
||||
/// Directory containing all Codex state (defaults to `~/.codex` but can be
|
||||
/// overridden by the `CODEX_HOME` environment variable).
|
||||
/// Directory containing all Codex state (defaults to `~/.code`; can be
|
||||
/// overridden by the `CODE_HOME` or `CODEX_HOME` environment variables).
|
||||
pub codex_home: PathBuf,
|
||||
|
||||
/// Settings that govern if and what will be written to `~/.codex/history.jsonl`.
|
||||
/// Settings that govern if and what will be written to `~/.code/history.jsonl`
|
||||
/// (Code still reads legacy `~/.codex/history.jsonl`).
|
||||
pub history: History,
|
||||
|
||||
/// Optional URI-based file opener. If set, citations to files in the model
|
||||
@@ -239,7 +241,7 @@ impl Config {
|
||||
cli_overrides: Vec<(String, TomlValue)>,
|
||||
overrides: ConfigOverrides,
|
||||
) -> std::io::Result<Self> {
|
||||
// Resolve the directory that stores Codex state (e.g. ~/.codex or the
|
||||
// Resolve the directory that stores Codex state (e.g. ~/.code or the
|
||||
// value of $CODEX_HOME) so we can embed it into the resulting
|
||||
// `Config` instance.
|
||||
let codex_home = find_codex_home()?;
|
||||
@@ -1112,7 +1114,7 @@ fn apply_toml_override(root: &mut TomlValue, path: &str, value: TomlValue) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Base config deserialized from ~/.codex/config.toml.
|
||||
/// Base config deserialized from ~/.code/config.toml (legacy ~/.codex/config.toml is still read).
|
||||
#[derive(Deserialize, Debug, Clone, Default)]
|
||||
pub struct ConfigToml {
|
||||
/// Optional override of model selection.
|
||||
@@ -1181,7 +1183,8 @@ pub struct ConfigToml {
|
||||
#[serde(default)]
|
||||
pub profiles: HashMap<String, ConfigProfile>,
|
||||
|
||||
/// Settings that govern if and what will be written to `~/.codex/history.jsonl`.
|
||||
/// Settings that govern if and what will be written to `~/.code/history.jsonl`
|
||||
/// (Code still reads legacy `~/.codex/history.jsonl`).
|
||||
#[serde(default)]
|
||||
pub history: Option<History>,
|
||||
|
||||
|
||||
@@ -248,7 +248,8 @@ impl UriBasedFileOpener {
|
||||
}
|
||||
}
|
||||
|
||||
/// Settings that govern if and what will be written to `~/.codex/history.jsonl`.
|
||||
/// Settings that govern if and what will be written to `~/.code/history.jsonl`
|
||||
/// (Code still reads legacy `~/.codex/history.jsonl`).
|
||||
#[derive(Deserialize, Debug, Clone, PartialEq, Default)]
|
||||
pub struct History {
|
||||
/// If true, history entries will not be written to disk.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Persistence layer for the global, append-only *message history* file.
|
||||
//!
|
||||
//! The history is stored at `~/.codex/history.jsonl` with **one JSON object per
|
||||
//! The history is stored at `~/.code/history.jsonl` (Code still reads legacy
|
||||
//! `~/.codex/history.jsonl`) with **one JSON object per
|
||||
//! line** so that it can be efficiently appended to and parsed with standard
|
||||
//! JSON-Lines tooling. Each record has the following schema:
|
||||
//!
|
||||
@@ -35,7 +36,8 @@ use std::os::unix::fs::OpenOptionsExt;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
/// Filename that stores the message history inside `~/.codex`.
|
||||
/// Filename that stores the message history inside `~/.code` (legacy `~/.codex`
|
||||
/// is still read).
|
||||
const HISTORY_FILENAME: &str = "history.jsonl";
|
||||
|
||||
const MAX_RETRIES: usize = 10;
|
||||
@@ -70,7 +72,7 @@ pub(crate) async fn append_entry(text: &str, session_id: &Uuid, config: &Config)
|
||||
|
||||
// TODO: check `text` for sensitive patterns
|
||||
|
||||
// Resolve `~/.codex/history.jsonl` and ensure the parent directory exists.
|
||||
// Resolve `~/.code/history.jsonl` and ensure the parent directory exists.
|
||||
let path = history_filepath(config);
|
||||
if let Some(parent) = path.parent() {
|
||||
tokio::fs::create_dir_all(parent).await?;
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
//!
|
||||
//! Providers can be defined in two places:
|
||||
//! 1. Built-in defaults compiled into the binary so Codex works out-of-the-box.
|
||||
//! 2. User-defined entries inside `~/.codex/config.toml` under the `model_providers`
|
||||
//! 2. User-defined entries inside `~/.code/config.toml` under the `model_providers`
|
||||
//! table (Code also reads legacy `~/.codex/config.toml`).
|
||||
//! key. These override or extend the defaults at runtime.
|
||||
|
||||
use crate::CodexAuth;
|
||||
|
||||
@@ -117,7 +117,8 @@ pub(crate) async fn get_conversation(path: &Path) -> io::Result<String> {
|
||||
|
||||
/// Load conversation file paths from disk using directory traversal.
|
||||
///
|
||||
/// Directory layout: `~/.codex/sessions/YYYY/MM/DD/rollout-YYYY-MM-DDThh-mm-ss-<uuid>.jsonl`
|
||||
/// Directory layout: `~/.code/sessions/YYYY/MM/DD/rollout-YYYY-MM-DDThh-mm-ss-<uuid>.jsonl`
|
||||
/// (Code still reads legacy `~/.codex/sessions/...`).
|
||||
/// Returned newest (latest) first.
|
||||
async fn traverse_directories_for_paths(
|
||||
root: PathBuf,
|
||||
|
||||
@@ -57,8 +57,8 @@ pub struct SavedSession {
|
||||
/// Rollouts are recorded as JSONL and can be inspected with tools such as:
|
||||
///
|
||||
/// ```ignore
|
||||
/// $ jq -C . ~/.codex/sessions/rollout-2025-05-07T17-24-21-5973b6c0-94b8-487b-a530-2aeb6098ae0e.jsonl
|
||||
/// $ fx ~/.codex/sessions/rollout-2025-05-07T17-24-21-5973b6c0-94b8-487b-a530-2aeb6098ae0e.jsonl
|
||||
/// $ jq -C . ~/.code/sessions/rollout-2025-05-07T17-24-21-5973b6c0-94b8-487b-a530-2aeb6098ae0e.jsonl
|
||||
/// $ fx ~/.code/sessions/rollout-2025-05-07T17-24-21-5973b6c0-94b8-487b-a530-2aeb6098ae0e.jsonl
|
||||
/// ```
|
||||
#[derive(Clone)]
|
||||
pub struct RolloutRecorder {
|
||||
@@ -358,7 +358,8 @@ fn create_log_file(
|
||||
config: &Config,
|
||||
conversation_id: ConversationId,
|
||||
) -> std::io::Result<LogFileInfo> {
|
||||
// Resolve ~/.codex/sessions/YYYY/MM/DD and create it if missing.
|
||||
// Resolve ~/.code/sessions/YYYY/MM/DD and create it if missing (Code still
|
||||
// reads legacy ~/.codex/sessions/ paths).
|
||||
let timestamp = OffsetDateTime::now_local()
|
||||
.map_err(|e| IoError::other(format!("failed to get local time: {e}")))?;
|
||||
let mut dir = config.codex_home.clone();
|
||||
|
||||
@@ -9,7 +9,8 @@ use codex_core::config::ConfigToml;
|
||||
|
||||
/// Returns a default `Config` whose on-disk state is confined to the provided
|
||||
/// temporary directory. Using a per-test directory keeps tests hermetic and
|
||||
/// avoids clobbering a developer’s real `~/.codex`.
|
||||
/// avoids clobbering a developer’s real `~/.code` directory (legacy `~/.codex`
|
||||
/// is still read).
|
||||
pub fn load_default_config_for_test(codex_home: &TempDir) -> Config {
|
||||
Config::load_from_base_config_with_overrides(
|
||||
ConfigToml::default(),
|
||||
|
||||
@@ -1752,16 +1752,14 @@ impl App<'_> {
|
||||
}
|
||||
|
||||
fn should_show_onboarding(
|
||||
_login_status: crate::LoginStatus,
|
||||
login_status: crate::LoginStatus,
|
||||
_config: &Config,
|
||||
show_trust_screen: bool,
|
||||
) -> bool {
|
||||
if show_trust_screen {
|
||||
return true;
|
||||
}
|
||||
// Defer login screen visibility decision to onboarding screen logic.
|
||||
// Here we only gate on trust flow.
|
||||
false
|
||||
matches!(login_status, crate::LoginStatus::NotAuthenticated)
|
||||
}
|
||||
|
||||
fn should_show_login_screen(login_status: crate::LoginStatus, _config: &Config) -> bool {
|
||||
|
||||
@@ -24,7 +24,8 @@ pub struct ResumeCandidate {
|
||||
|
||||
// No fallback scan: meta parsing for rollout headers no longer needed here.
|
||||
|
||||
/// Return rollout files under ~/.codex/sessions matching the provided cwd.
|
||||
/// Return rollout files under ~/.code/sessions matching the provided cwd (Code
|
||||
/// still reads legacy ~/.codex/sessions directories).
|
||||
/// Reads only the first line of each file to avoid heavy IO.
|
||||
pub fn list_sessions_for_cwd(cwd: &Path, codex_home: &Path) -> Vec<ResumeCandidate> {
|
||||
use std::collections::HashMap;
|
||||
|
||||
+3
-3
@@ -50,10 +50,10 @@ Notes:
|
||||
|
||||
Because Codex is written in Rust, it honors the `RUST_LOG` environment variable to configure its logging behavior.
|
||||
|
||||
The TUI defaults to `RUST_LOG=codex_core=info,codex_tui=info` and log messages are written to `~/.codex/log/codex-tui.log`, so you can leave the following running in a separate terminal to monitor log messages as they are written:
|
||||
The TUI defaults to `RUST_LOG=codex_core=info,codex_tui=info` and log messages are written to `~/.code/log/codex-tui.log` (Code still reads the legacy `~/.codex/log/` path), so you can leave the following running in a separate terminal to monitor log messages as they are written:
|
||||
|
||||
```
|
||||
tail -F ~/.codex/log/codex-tui.log
|
||||
tail -F ~/.code/log/codex-tui.log
|
||||
```
|
||||
|
||||
By comparison, the non-interactive mode (`codex exec`) defaults to `RUST_LOG=error`, but messages are printed inline, so there is no need to monitor a separate file.
|
||||
@@ -62,7 +62,7 @@ See the Rust documentation on [`RUST_LOG`](https://docs.rs/env_logger/latest/env
|
||||
|
||||
## Model Context Protocol (MCP)
|
||||
|
||||
The Codex CLI can be configured to leverage MCP servers by defining an [`mcp_servers`](./config.md#mcp_servers) section in `~/.codex/config.toml`. It is intended to mirror how tools such as Claude and Cursor define `mcpServers` in their respective JSON config files, though the Codex format is slightly different since it uses TOML rather than JSON, e.g.:
|
||||
The Codex CLI can be configured to leverage MCP servers by defining an [`mcp_servers`](./config.md#mcp_servers) section in `~/.code/config.toml` (Code will also read a legacy `~/.codex/config.toml`). It is intended to mirror how tools such as Claude and Cursor define `mcpServers` in their respective JSON config files, though the Codex format is slightly different since it uses TOML rather than JSON, e.g.:
|
||||
|
||||
```toml
|
||||
# IMPORTANT: the top-level key is `mcp_servers` rather than `mcpServers`.
|
||||
|
||||
@@ -15,7 +15,7 @@ This key must, at minimum, have write access to the Responses API.
|
||||
If you've used the Codex CLI before with usage-based billing via an API key and want to switch to using your ChatGPT plan, follow these steps:
|
||||
|
||||
1. Update the CLI and ensure `codex --version` is `0.20.0` or later
|
||||
2. Delete `~/.codex/auth.json` (on Windows: `C:\\Users\\USERNAME\\.codex\\auth.json`)
|
||||
2. Delete `~/.code/auth.json` (and remove the legacy `~/.codex/auth.json` if it exists; on Windows these live under `C:\\Users\\USERNAME\\.code\\auth.json` and `C:\\Users\\USERNAME\\.codex\\auth.json`)
|
||||
3. Run `codex login` again
|
||||
|
||||
## Forcing a specific auth method (advanced)
|
||||
@@ -25,7 +25,7 @@ You can explicitly choose which authentication Codex should prefer when both are
|
||||
- To always use your API key (even when ChatGPT auth exists), set:
|
||||
|
||||
```toml
|
||||
# ~/.codex/config.toml
|
||||
# ~/.code/config.toml (Code also reads legacy ~/.codex/config.toml)
|
||||
preferred_auth_method = "apikey"
|
||||
```
|
||||
|
||||
@@ -38,7 +38,7 @@ codex --config preferred_auth_method="apikey"
|
||||
- To prefer ChatGPT auth (default), set:
|
||||
|
||||
```toml
|
||||
# ~/.codex/config.toml
|
||||
# ~/.code/config.toml (Code also reads legacy ~/.codex/config.toml)
|
||||
preferred_auth_method = "chatgpt"
|
||||
```
|
||||
|
||||
@@ -71,28 +71,28 @@ Today, the login process entails running a server on `localhost:1455`. If you ar
|
||||
|
||||
### Authenticate locally and copy your credentials to the "headless" machine
|
||||
|
||||
The easiest solution is likely to run through the `codex login` process on your local machine such that `localhost:1455` _is_ accessible in your web browser. When you complete the authentication process, an `auth.json` file should be available at `$CODEX_HOME/auth.json` (on Mac/Linux, `$CODEX_HOME` defaults to `~/.codex` whereas on Windows, it defaults to `%USERPROFILE%\\.codex`).
|
||||
The easiest solution is likely to run through the `codex login` process on your local machine such that `localhost:1455` _is_ accessible in your web browser. When you complete the authentication process, an `auth.json` file should be available at `$CODE_HOME/auth.json` (defaults to `~/.code/auth.json`; Code will still read `$CODEX_HOME`/`~/.codex/auth.json` if present).
|
||||
|
||||
Because the `auth.json` file is not tied to a specific host, once you complete the authentication flow locally, you can copy the `$CODEX_HOME/auth.json` file to the headless machine and then `codex` should "just work" on that machine. Note to copy a file to a Docker container, you can do:
|
||||
|
||||
```shell
|
||||
# substitute MY_CONTAINER with the name or id of your Docker container:
|
||||
CONTAINER_HOME=$(docker exec MY_CONTAINER printenv HOME)
|
||||
docker exec MY_CONTAINER mkdir -p "$CONTAINER_HOME/.codex"
|
||||
docker cp auth.json MY_CONTAINER:"$CONTAINER_HOME/.codex/auth.json"
|
||||
docker exec MY_CONTAINER mkdir -p "$CONTAINER_HOME/.code"
|
||||
docker cp auth.json MY_CONTAINER:"$CONTAINER_HOME/.code/auth.json"
|
||||
```
|
||||
|
||||
whereas if you are `ssh`'d into a remote machine, you likely want to use [`scp`](https://en.wikipedia.org/wiki/Secure_copy_protocol):
|
||||
|
||||
```shell
|
||||
ssh user@remote 'mkdir -p ~/.codex'
|
||||
scp ~/.codex/auth.json user@remote:~/.codex/auth.json
|
||||
ssh user@remote 'mkdir -p ~/.code'
|
||||
scp ~/.code/auth.json user@remote:~/.code/auth.json
|
||||
```
|
||||
|
||||
or try this one-liner:
|
||||
|
||||
```shell
|
||||
ssh user@remote 'mkdir -p ~/.codex && cat > ~/.codex/auth.json' < ~/.codex/auth.json
|
||||
ssh user@remote 'mkdir -p ~/.code && cat > ~/.code/auth.json' < ~/.code/auth.json
|
||||
```
|
||||
|
||||
### Connecting through VPS or remote
|
||||
|
||||
+3
-3
@@ -12,7 +12,7 @@ Codex supports several mechanisms for setting config values:
|
||||
- If `value` cannot be parsed as a valid TOML value, it is treated as a string value. This means that `-c model='"o3"'` and `-c model=o3` are equivalent.
|
||||
- In the first case, the value is the TOML string `"o3"`, while in the second the value is `o3`, which is not valid TOML and therefore treated as the TOML string `"o3"`.
|
||||
- Because quotes are interpreted by one's shell, `-c key="true"` will be correctly interpreted in TOML as `key = true` (a boolean) and not `key = "true"` (a string). If for some reason you needed the string `"true"`, you would need to use `-c key='"true"'` (note the two sets of quotes).
|
||||
- The `$CODEX_HOME/config.toml` configuration file where the `CODEX_HOME` environment value defaults to `~/.codex`. (Note `CODEX_HOME` will also be where logs and other Codex-related information are stored.)
|
||||
- The `$CODE_HOME/config.toml` configuration file. `CODE_HOME` defaults to `~/.code`; Code also reads from `$CODEX_HOME`/`~/.codex` for backwards compatibility but only writes to `~/.code`. (Logs and other state use the same directory.)
|
||||
|
||||
Both the `--config` flag and the `config.toml` file support the following options:
|
||||
|
||||
@@ -365,7 +365,7 @@ This config option is comparable to how Claude and Cursor define `mcpServers` in
|
||||
}
|
||||
```
|
||||
|
||||
Should be represented as follows in `~/.codex/config.toml`:
|
||||
Should be represented as follows in `~/.code/config.toml` (Code will also read the legacy `~/.codex/config.toml` if it exists):
|
||||
|
||||
```toml
|
||||
# IMPORTANT: the top-level key is `mcp_servers` rather than `mcpServers`.
|
||||
@@ -518,7 +518,7 @@ if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
```
|
||||
|
||||
To have Codex use this script for notifications, you would configure it via `notify` in `~/.codex/config.toml` using the appropriate path to `notify.py` on your computer:
|
||||
To have Codex use this script for notifications, you would configure it via `notify` in `~/.code/config.toml` (legacy `~/.codex/config.toml` is still read) using the appropriate path to `notify.py` on your computer:
|
||||
|
||||
```toml
|
||||
notify = ["python3", "/Users/mbolin/.codex/notify.py"]
|
||||
|
||||
@@ -44,7 +44,7 @@ Below are a few bite-size examples you can copy-paste. Replace the text in quote
|
||||
|
||||
You can give Codex extra instructions and guidance using `AGENTS.md` files. Codex looks for `AGENTS.md` files in the following places, and merges them top-down:
|
||||
|
||||
1. `~/.codex/AGENTS.md` - personal global guidance
|
||||
1. `~/.code/AGENTS.md` - personal global guidance (Code will also read a legacy `~/.codex/AGENTS.md` if present)
|
||||
2. `AGENTS.md` at repo root - shared project notes
|
||||
3. `AGENTS.md` in the current working directory - sub-folder/feature specifics
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
Save frequently used prompts as Markdown files and reuse them quickly from the slash menu.
|
||||
|
||||
- Location: Put files in `$CODEX_HOME/prompts/` (defaults to `~/.codex/prompts/`).
|
||||
- Location: Put files in `$CODE_HOME/prompts/` (defaults to `~/.code/prompts/`; Code also reads legacy `~/.codex/prompts/`).
|
||||
- File type: Only Markdown files with the `.md` extension are recognized.
|
||||
- Name: The filename without the `.md` extension becomes the slash entry. For a file named `my-prompt.md`, type `/my-prompt`.
|
||||
- Content: The file contents are sent as your message when you select the item in the slash popup and press Enter.
|
||||
|
||||
+2
-2
@@ -6,10 +6,10 @@ Codex CLI **does** support OpenAI organizations with [Zero Data Retention (ZDR)]
|
||||
OpenAI rejected the request. Error details: Status: 400, Code: unsupported_parameter, Type: invalid_request_error, Message: 400 Previous response cannot be used for this organization due to Zero Data Retention.
|
||||
```
|
||||
|
||||
Ensure you are running `codex` with `--config disable_response_storage=true` or add this line to `~/.codex/config.toml` to avoid specifying the command line option each time:
|
||||
Ensure you are running `codex` with `--config disable_response_storage=true` or add this line to `~/.code/config.toml` (Code still reads legacy `~/.codex/config.toml`) to avoid specifying the command line option each time:
|
||||
|
||||
```toml
|
||||
disable_response_storage = true
|
||||
```
|
||||
|
||||
See [the configuration documentation on `disable_response_storage`](./config.md#disable_response_storage) for details.
|
||||
See [the configuration documentation on `disable_response_storage`](./config.md#disable_response_storage) for details.
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
## @just-every/code v0.2.154
|
||||
## @just-every/code v0.2.155
|
||||
|
||||
This release fixes a Shift+Tab crash and polishes multi‑agent command UX.
|
||||
This release includes small fixes and improvements across auth, TUI, and docs.
|
||||
|
||||
### Changes
|
||||
- TUI/Input: fix Shift+Tab crash.
|
||||
- TUI/Agents: improve visibility for multi‑agent commands.
|
||||
- TUI/Slash: make @ shortcut work with /solve and /plan.
|
||||
- Auth: fix onboarding auth prompt gating.
|
||||
- CLI: add long-run calculator script.
|
||||
- TUI: add pulldown-cmark dependency to fix build.
|
||||
- Docs: clarify config directories.
|
||||
|
||||
### Install
|
||||
```
|
||||
@@ -13,4 +14,5 @@ npm install -g @just-every/code@latest
|
||||
code
|
||||
```
|
||||
|
||||
Compare: https://github.com/just-every/code/compare/v0.2.153...v0.2.154
|
||||
Compare: https://github.com/just-every/code/compare/v0.2.154...v0.2.155
|
||||
|
||||
|
||||
Reference in New Issue
Block a user