Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

### Added

- **Codex (OpenAI) support — `axme-code setup --ide=codex`.** Writes `.codex/config.toml` (`[mcp_servers.axme]` with an absolute, PATH-independent command), `.codex/hooks.json` (SessionStart + PreToolUse + PostToolUse + SessionEnd, Codex's Claude-Code-compatible hook protocol) and the `## AXME Code` section in `AGENTS.md` (Codex's CLAUDE.md analogue, including the `#!axme` git gate). New `codex` hook adapter normalizes Codex's `apply_patch` edits (one call, several files) into the IDE-agnostic file tracking and safety cores; new `hook session-start` subcommand prints the "call axme_context" nudge as Codex developer context. The background auditor keeps running on the Claude Agent SDK; Codex's own transcript is not yet parsed.
- **Codex (OpenAI) support — `axme-code setup --ide=codex`.** Writes `.codex/config.toml` (`[mcp_servers.axme]` with an absolute, PATH-independent command), `.codex/hooks.json` (SessionStart + PreToolUse + PostToolUse + SessionEnd, Codex's Claude-Code-compatible hook protocol) and the `## AXME Code` section in `AGENTS.md` (Codex's CLAUDE.md analogue, including the `#!axme` git gate). New `codex` hook adapter normalizes Codex's `apply_patch` edits (one call, several files) into the IDE-agnostic file tracking and safety cores; new `hook session-start` subcommand prints the "call axme_context" nudge as Codex developer context. The background auditor keeps running on the Claude Agent SDK; Codex's own transcript is not yet parsed. Verified live on codex-cli 0.166: the generated `[mcp_servers.axme]` table sets `default_tools_approval_mode = "approve"` so `axme_*` calls run without per-call prompts (`"auto"` still prompts and fails under `codex exec`); the SessionStart hook emits the documented `hookSpecificOutput.additionalContext` JSON (plain text starting with `[` is mis-parsed as JSON by Codex); project hooks are skipped until trusted via `/hooks`, and setup says so.
- **Shell-side denied-path enforcement.** `checkBash` now blocks shell reads of denied filesystem paths (`cat ~/.ssh/id_rsa`, `/etc/shadow`), quoted or chained. Codex routes every file read through the shell, so without this the filesystem deny rules did not apply there at all; on Claude Code and Cursor it closes the same gap for `Bash`. Only absolute and `~`-relative deny patterns participate — bare basename patterns (`.env`, `*.pem`) are deliberately excluded so commit messages and heredocs that mention such a file are not blocked.

## [0.6.3] - 2026-06-25
Expand Down
4 changes: 3 additions & 1 deletion docs/MULTI_CLIENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,11 @@ This writes three project-level files and leaves Claude Code's files alone:
- `.codex/hooks.json` — `SessionStart` (prints the "call axme_context" nudge as developer context), `PreToolUse` (safety + git gate), `PostToolUse` (file tracking from `apply_patch`), `SessionEnd` (spawns the detached auditor; Codex caps this hook at 3s, which is enough).
- `AGENTS.md` — the `## AXME Code` section (the CLAUDE.md analogue), including the `#!axme` commit/push gate rule.

Two Codex-specific notes:
Three Codex-specific notes:

- **Trust the project.** Codex loads a project's `.codex/` layer only for trusted projects. Accept the trust prompt the first time you run `codex` in the repo, or add `[projects."<absolute path>"]` with `trust_level = "trusted"` to `~/.codex/config.toml`.
- **Trust the hooks.** Codex runs non-managed hooks only after you review them: in your first session run `/hooks` and trust the four AXME entries. Trust is bound to each hook's hash, so after anything changes the hook command (a release that moves the binary, re-running setup from another install) `/hooks` asks again. Until then the hooks are skipped silently — no safety gate, no file tracking. `codex exec` never prompts; it has `--dangerously-bypass-hook-trust` for automation you have vetted.
- **MCP approvals are pre-granted.** The generated `[mcp_servers.axme]` table sets `default_tools_approval_mode = "approve"` (`"auto"` still prompts), so `axme_*` calls run without a per-call prompt (and work under `codex exec`, whose approval policy is `never`). The tools only read and write the project's `.axme-code/`.
- **Shell reads are policed.** Codex has no `Read` tool — every file read is a shell command — so the `PreToolUse` hook also blocks shell reads of denied paths (`cat ~/.ssh/id_rsa`, `/etc/shadow`, ...). Only absolute and `~`-relative deny patterns apply to the shell; bare basename patterns such as `.env` do not, so commit messages and heredocs that merely mention a file are not blocked.

The same `.axme-code/` knowledge base is shared with Claude Code, so you can switch between the two on one project.
Expand Down
3 changes: 2 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -728,7 +728,8 @@ async function main() {
console.log("\nDone! Run 'codex' in this project — AXME tools are now available.");
console.log(" Codex loads the project's .codex/ config and hooks only for TRUSTED projects:");
console.log(" accept the trust prompt on first launch (or set projects.\"<path>\".trust_level = \"trusted\"");
console.log(" in ~/.codex/config.toml), then start a new session.");
console.log(" in ~/.codex/config.toml). Then, in that first session, run /hooks and trust the");
console.log(" four AXME hooks — Codex skips project hooks silently until they are reviewed.");
} else {
console.log("\nDone! Run 'claude' to start using AXME tools.");
}
Expand Down
22 changes: 12 additions & 10 deletions src/hooks/session-start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
* written here — AGENTS.md already carries the full instructions; this is
* the per-session reminder that survives even when the agent skims it.
*
* Output is plain text on purpose (not the hookSpecificOutput JSON form):
* both reach the model, and plain text cannot be mis-parsed.
* Output uses the documented JSON form
* (`hookSpecificOutput.additionalContext`). Plain text would also reach the
* model — except that ours starts with "[", which Codex takes as the start
* of a JSON document, fails to parse, and reports the hook as Failed with
* no context delivered (seen live on codex-cli 0.166).
*/

import { configExists } from "../storage/config.js";
Expand All @@ -28,12 +31,11 @@ export async function runSessionStartHook(workspacePath?: string): Promise<void>
}

const root = workspacePath ?? cwdFromStdin ?? process.cwd();
if (configExists(root)) {
process.stdout.write("[AXME Code] Knowledge base ready. Call axme_context now.\n");
} else {
process.stdout.write(
"[AXME Code] Project not initialized. Ask the user to run `axme-code setup --ide=codex` " +
"in a terminal, then start a new Codex session.\n",
);
}
const additionalContext = configExists(root)
? "[AXME Code] Knowledge base ready. Call axme_context now."
: "[AXME Code] Project not initialized. Ask the user to run `axme-code setup --ide=codex` " +
"in a terminal, then start a new Codex session.";
process.stdout.write(
JSON.stringify({ hookSpecificOutput: { hookEventName: "SessionStart", additionalContext } }) + "\n",
);
}
16 changes: 16 additions & 0 deletions src/setup/codex-writers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,19 @@
* ~/.codex/config.toml). Codex offers to set that the first time it opens
* the repo; setup prints a reminder.
*
* Two more Codex gates, both verified live on codex-cli 0.166 (2026-11-02):
* - Hook trust. Non-managed hooks run only after the user reviews and
* trusts them via `/hooks` in an interactive session. Trust is recorded
* against the hook definition's hash, so any change to the command
* (e.g. the binary path after a release) needs a re-trust. Untrusted
* hooks are skipped silently — `codex exec` has
* `--dangerously-bypass-hook-trust` for automation.
* - MCP tool approval. Without `default_tools_approval_mode = "approve"`
* on the server table, every axme_* call prompts in the TUI and fails
* outright under `codex exec` ("requires approval, but approval policy
* is never"). "auto" is NOT enough — verified live, it still prompted.
* The writer sets "approve": AXME tools only touch .axme-code/.
*
* Codex's hooks.json is the Claude Code settings.json `hooks` shape
* verbatim (PascalCase events, `{ matcher?, hooks: [{ type, command,
* timeout }] }` groups), so the writer mirrors configureHooks() in cli.ts
Expand Down Expand Up @@ -140,6 +153,9 @@ function mcpServerTable(serve: ServeCommand): string {
"[mcp_servers.axme]",
`command = ${tomlString(serve.command)}`,
`args = [${serve.args.map(tomlString).join(", ")}]`,
"# AXME tools only read/write this project's .axme-code/ knowledge base:",
"# run them without a per-call approval prompt (required under `codex exec`).",
'default_tools_approval_mode = "approve"',
"",
].join("\n");
}
Expand Down
4 changes: 3 additions & 1 deletion test/codex-hooks-e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ describe("codex hooks end-to-end", () => {
assert.equal(r.status, 0, `stderr: ${r.stderr}`);
// ROOT has .axme-code/ but no config.yaml → "not initialized" branch; the
// real repo (REPO_ROOT) is initialized → ready branch. Check both texts.
assert.match(r.stdout, /\[AXME Code\] Project not initialized/);
const parsed = JSON.parse(r.stdout) as { hookSpecificOutput: { hookEventName: string; additionalContext: string } };
assert.equal(parsed.hookSpecificOutput.hookEventName, "SessionStart");
assert.match(parsed.hookSpecificOutput.additionalContext, /\[AXME Code\] Project not initialized/);
});
});
4 changes: 3 additions & 1 deletion test/codex-setup-writers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ describe("writeCodexConfigToml", () => {
it("creates .codex/config.toml with the axme table (absolute command + args)", () => {
writeCodexConfigToml(ROOT, SERVE);
const text = readFileSync(join(ROOT, ".codex", "config.toml"), "utf-8");
assert.equal(text, '[mcp_servers.axme]\ncommand = "/bin/node"\nargs = ["/usr/local/bin/axme-code", "serve"]\n');
assert.ok(text.startsWith('[mcp_servers.axme]\ncommand = "/bin/node"\nargs = ["/usr/local/bin/axme-code", "serve"]\n'), text);
// Without this every axme_* call prompts in the TUI and fails under codex exec.
assert.match(text, /^default_tools_approval_mode = "approve"$/m);
});

it("appends to an existing config without touching other tables or comments", () => {
Expand Down
Loading