@ory/codex 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,24 +16,20 @@ You don't need an Ory account or any prior Ory experience to start.
16
16
  No prior `npm install` required:
17
17
 
18
18
  ```bash
19
- npx @ory/codex install # current project
20
- npx @ory/codex install --global # all Codex projects
21
- npx @ory/codex uninstall
19
+ npx @ory/codex install # registers the Ory marketplace + plugin with `codex`
20
+ npx @ory/codex uninstall # removes both
22
21
  ```
23
22
 
24
- That's it — skills, hooks, and the Ory MCP server are now registered. Existing entries in your Codex config are preserved.
23
+ Under the hood the installer runs `codex plugin marketplace add <local-dir>` and `codex plugin add ory-codex@ory`, so the plugin is owned by Codex's plugin system — same as every other Codex marketplace plugin. Skills, slash commands, hooks, and the Ory MCP server are all part of the plugin payload. Re-running `install` is idempotent and refreshes the rendered skills + commands from the latest template.
25
24
 
26
- <details>
27
- <summary>Alternative install</summary>
28
-
29
- If the installer can't locate your Codex config, this writes it directly:
25
+ Verify the install with Codex's native tooling:
30
26
 
31
27
  ```bash
32
- npx -y -p @ory/codex ory-codex-setup
28
+ codex plugin list # shows ory-codex@ory installed + enabled
29
+ codex mcp list # shows the bundled `ory` MCP server
30
+ codex doctor # surfaces any config or MCP resolvability issues
33
31
  ```
34
32
 
35
- </details>
36
-
37
33
  ## Quickstart (≈ 3 minutes)
38
34
 
39
35
  From any project where you'd like Ory authentication, inside Codex:
@@ -48,6 +44,14 @@ From any project where you'd like Ory authentication, inside Codex:
48
44
 
49
45
  3. **Sign in.** Start your app, visit the login page Codex added, and sign in with the seeded credentials. You now have a real Ory session backed by a real Ory stack — locally, offline, with zero configuration.
50
46
 
47
+ 4. **Turn on Ory login for the Codex session itself.** *(Optional but recommended.)* Out of the box the plugin only governs your *app*. To also attach an Ory identity to *Codex's* session — so every tool call is attributed to you, not a fallback `session:<id>` subject — set:
48
+
49
+ ```bash
50
+ export ORY_AUTH_GATE=1
51
+ ```
52
+
53
+ Then restart Codex. On next session start, Codex opens an Ory login in your browser; sign in with the same seeded credentials from step 1. This is what makes `permissions enforce` (see [Agent security](#agent-security)) deny on the right identity later.
54
+
51
55
  That's the full Ory DX path. Stop here if you're just evaluating the plugin. Continue to [Agent security](#agent-security) when you're ready to enforce.
52
56
 
53
57
  ## What's included
@@ -146,7 +150,7 @@ After install the plugin runs in **observe mode**: every tool call is checked ag
146
150
  ## CLI reference
147
151
 
148
152
  ```
149
- npx -y -p @ory/codex ory-codex install | uninstall [--global]
153
+ npx -y -p @ory/codex ory-codex install | uninstall
150
154
  npx -y -p @ory/codex ory-codex configure [--project-url <url>] [--api-key <key>] [--audit-only]
151
155
  npx -y -p @ory/codex ory-codex agent <status|unregister> Manage the agent's OAuth2 identity
152
156
  npx -y -p @ory/codex ory-codex permissions <status|bootstrap|observe|enforce>
@@ -166,6 +170,8 @@ Highlights:
166
170
  - **`ory-local-up` fails.** Make sure Docker is running and ports `3000`, `4000`, `4100`, and `16686` are free.
167
171
  - **PKCE login loops.** Clear persisted state with `npx -y -p @ory/codex ory-codex agent unregister` and retry.
168
172
  - **`npx` fetches an old version.** Force a fresh fetch: `npx -y -p @ory/codex@latest ory-codex …`.
173
+ - **`npm install … ENOVERSIONS`.** If your `~/.npmrc` sets `min-release-age`, npm filters out package versions newer than that threshold. Override per-call with `npm_config_min_release_age=0 npx @ory/codex install`.
174
+ - **`codex doctor` reports `ory-mcp-server is not resolvable`.** The bundled MCP server resolves via `npx -y -p @ory/mcp-server` on demand — make sure `npm`/`npx` is on PATH. The first session start fetches the server tarball; subsequent sessions reuse the cache.
169
175
  - **Need more signal.** Set `ORY_AGENT_DEBUG=true` and `ORY_AGENT_LOG_FILE=/tmp/ory.log` to capture structured logs.
170
176
 
171
177
  ## Links
@@ -3,10 +3,12 @@
3
3
  * Main CLI for @ory/codex.
4
4
  *
5
5
  * Usage:
6
- * npx ory-codex install [--global] Install hooks into Codex config
7
- * npx ory-codex uninstall Remove hooks from Codex config
8
- * npx ory-codex configure Set or view Ory project URL and API key
9
- * npx ory-codex setup [--global] Write hooks to config directly (fallback)
10
- * npx ory-codex status Show plugin status
6
+ * npx ory-codex install Install the plugin via `codex plugin add`
7
+ * npx ory-codex uninstall Remove the plugin via `codex plugin remove`
8
+ * npx ory-codex configure Set or view Ory project URL and API key
9
+ * npx ory-codex agent <cmd> Manage the agent's OAuth2 identity
10
+ * npx ory-codex permissions <cmd> Manage permission mode and tool permissions
11
+ * npx ory-codex local <cmd> Manage local Ory dev environment
12
+ * npx ory-codex status Show plugin status and configuration
11
13
  */
12
14
  export {};
package/dist/cli/main.js CHANGED
@@ -4,11 +4,13 @@
4
4
  * Main CLI for @ory/codex.
5
5
  *
6
6
  * Usage:
7
- * npx ory-codex install [--global] Install hooks into Codex config
8
- * npx ory-codex uninstall Remove hooks from Codex config
9
- * npx ory-codex configure Set or view Ory project URL and API key
10
- * npx ory-codex setup [--global] Write hooks to config directly (fallback)
11
- * npx ory-codex status Show plugin status
7
+ * npx ory-codex install Install the plugin via `codex plugin add`
8
+ * npx ory-codex uninstall Remove the plugin via `codex plugin remove`
9
+ * npx ory-codex configure Set or view Ory project URL and API key
10
+ * npx ory-codex agent <cmd> Manage the agent's OAuth2 identity
11
+ * npx ory-codex permissions <cmd> Manage permission mode and tool permissions
12
+ * npx ory-codex local <cmd> Manage local Ory dev environment
13
+ * npx ory-codex status Show plugin status and configuration
12
14
  */
13
15
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
16
  if (k2 === undefined) k2 = k;
@@ -47,19 +49,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
47
49
  const path = __importStar(require("node:path"));
48
50
  const fs = __importStar(require("node:fs"));
49
51
  const argus_1 = require("@ory/argus");
52
+ const setup_js_1 = require("./setup.js");
50
53
  const PACKAGE_ROOT = path.resolve(__dirname, "..", "..");
51
54
  function main() {
52
55
  const [command, ...args] = process.argv.slice(2);
53
56
  switch (command) {
54
57
  case "install":
55
- install(args);
58
+ (0, setup_js_1.install)(args);
56
59
  postInstallPermissions("ory-codex", "codex").then(() => process.exit(0), (err) => {
57
60
  console.error(err.message ?? err);
58
61
  process.exit(1);
59
62
  });
60
63
  break;
61
64
  case "uninstall":
62
- uninstall(args);
65
+ (0, setup_js_1.uninstall)(args);
63
66
  break;
64
67
  case "configure":
65
68
  (0, argus_1.runConfigureCommand)("ory-codex", args);
@@ -76,11 +79,11 @@ function main() {
76
79
  process.exit(1);
77
80
  });
78
81
  break;
79
- case "setup":
80
- require("./setup.js");
81
- break;
82
82
  case "status":
83
- status();
83
+ status().then(() => process.exit(0), (err) => {
84
+ console.error(err.message ?? err);
85
+ process.exit(1);
86
+ });
84
87
  break;
85
88
  case "local":
86
89
  (0, argus_1.runLocalCommand)("ory-codex", args).catch((err) => {
@@ -100,39 +103,21 @@ function main() {
100
103
  process.exit(1);
101
104
  }
102
105
  }
103
- function install(args) {
104
- const isGlobal = args.includes("--global");
105
- console.log(`Installing Ory hooks for Codex (scope: ${isGlobal ? "global" : "project"})...`);
106
- manualSetup(isGlobal);
107
- }
108
- function uninstall(args) {
109
- const setupArgs = ["--uninstall", ...args];
110
- process.argv = ["node", "setup.js", ...setupArgs];
111
- require("./setup.js");
112
- }
113
- function manualSetup(isGlobal) {
114
- const setupArgs = isGlobal ? ["--global"] : [];
115
- process.argv = ["node", "setup.js", ...setupArgs];
116
- require("./setup.js");
117
- }
118
106
  async function postInstallPermissions(binName, harness) {
119
107
  const bootstrapped = await (0, argus_1.maybeAutoBootstrap)(binName, harness);
120
108
  (0, argus_1.printPermissionsOnboardingHelp)(binName, harness, {
121
109
  bootstrappedAutomatically: bootstrapped,
122
110
  });
123
111
  }
124
- function status() {
125
- console.log("Ory Agent Plugin Status (Codex)");
126
- console.log("================================");
127
- console.log("");
128
- (0, argus_1.printOryConfig)();
129
- (0, argus_1.printEnvironment)();
130
- // Plugin-specific status
131
- console.log("");
132
- console.log("Plugin:");
133
- console.log(` Directory: ${PACKAGE_ROOT}`);
134
- console.log(` Hook script: ${fs.existsSync(path.join(PACKAGE_ROOT, "dist", "hook.js")) ? "built" : "NOT BUILT (run pnpm build)"}`);
135
- (0, argus_1.printLogTail)();
112
+ async function status() {
113
+ await (0, argus_1.runStatusCommand)("ory-codex", "codex", {
114
+ title: "Codex",
115
+ printPluginSection: () => {
116
+ console.log("Hooks & plugin:");
117
+ console.log(` Directory: ${PACKAGE_ROOT}`);
118
+ console.log(` Hook script: ${fs.existsSync(path.join(PACKAGE_ROOT, "dist", "hook.js")) ? "built" : "NOT BUILT (run pnpm build)"}`);
119
+ },
120
+ });
136
121
  }
137
122
  function help() {
138
123
  console.log(`
@@ -142,20 +127,25 @@ Usage:
142
127
  npx ory-codex <command> [options]
143
128
 
144
129
  Commands:
145
- install [--global] Install the Ory hooks into Codex configuration
146
- uninstall Remove the Ory hooks from Codex configuration
147
- configure Set or view Ory project URL and API key
148
- permissions <cmd> Manage permission mode and tool permissions (status, bootstrap, observe, enforce)
149
- setup [--global] Write hooks directly to config (fallback)
150
- status Show plugin status and configuration
151
- local <cmd> Manage local Ory dev environment (up, down, status, seed, ...)
130
+ install Install via \`codex plugin marketplace add\` + \`codex plugin add\`
131
+ uninstall Remove via \`codex plugin remove\` + \`codex plugin marketplace remove\`
132
+ configure Set or view Ory project URL and API key
133
+ agent <cmd> Manage the agent's OAuth2 identity (status, unregister)
134
+ permissions <cmd> Manage permission mode and tool permissions
135
+ (status, bootstrap, observe, enforce)
136
+ local <cmd> Manage local Ory dev environment
137
+ (up, down, status, seed, logs, env, configure, reset)
138
+ status Show plugin status, config, and recent log lines
152
139
 
153
140
  Examples:
154
- npx ory-codex install # Install for current project
155
- npx ory-codex install --global # Install globally
156
- npx ory-codex configure --project-url https://your-project.projects.oryapis.com --api-key ory_pat_...
157
- npx ory-codex status # Check configuration
158
- npx ory-codex uninstall # Remove hooks
141
+ npx ory-codex install
142
+ npx ory-codex configure --project-url https://<id>.projects.oryapis.com \\
143
+ --api-key ory_pat_...
144
+ npx ory-codex permissions status
145
+ npx ory-codex status
146
+ npx ory-codex uninstall
147
+
148
+ Requires the \`codex\` CLI on PATH.
159
149
  `);
160
150
  }
161
151
  main();
@@ -1,16 +1,28 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Setup CLI for the Ory Codex plugin.
3
+ * Install/uninstall the Ory plugin via the `codex` CLI.
4
4
  *
5
- * Writes hooks to `.codex/hooks.json` and enables the codex_hooks feature
6
- * flag in `.codex/config.toml`. Codex does not read `.codex/config.json`
7
- * for hooks; that path is intentionally not used.
5
+ * Two-step model that mirrors how every other Codex marketplace plugin works:
6
+ *
7
+ * 1. Assemble a Codex marketplace under the shared per-harness data dir.
8
+ * The marketplace skeleton (manifest, MCP config, plugin metadata) is
9
+ * copied from `marketplace/` in this package; the dynamic parts
10
+ * (`hooks.json`, skills, commands, plugin version) are generated /
11
+ * rendered fresh from the shared `@ory/argus` templates.
12
+ *
13
+ * 2. Hand that directory to `codex` via `codex plugin marketplace add` +
14
+ * `codex plugin add ory-codex@ory`. Codex copies the plugin into its
15
+ * own cache (`~/.codex/plugins/cache/...`), so once installed the
16
+ * plugin no longer depends on the assembled marketplace dir for
17
+ * runtime — but Codex needs it to stay around for `marketplace upgrade`.
18
+ *
19
+ * Uninstall reverses both steps: `codex plugin remove` + `codex plugin
20
+ * marketplace remove`, then we delete the assembled dir.
8
21
  *
9
22
  * Usage:
10
- * npx ory-codex-setup # Auto-detect and configure
11
- * npx ory-codex-setup --project-dir /path/to/repo
12
- * npx ory-codex-setup --global # Install to ~/.codex/
13
- * npx ory-codex-setup --print # Print config to stdout without writing
14
- * npx ory-codex-setup --uninstall # Remove Ory hooks from config
23
+ * npx ory-codex-setup Install
24
+ * npx ory-codex-setup --uninstall Remove
25
+ * npx ory-codex-setup --help Show this help
15
26
  */
16
- export {};
27
+ export declare function install(_args: string[]): void;
28
+ export declare function uninstall(_args: string[]): void;
package/dist/cli/setup.js CHANGED
@@ -1,18 +1,29 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
  /**
4
- * Setup CLI for the Ory Codex plugin.
4
+ * Install/uninstall the Ory plugin via the `codex` CLI.
5
5
  *
6
- * Writes hooks to `.codex/hooks.json` and enables the codex_hooks feature
7
- * flag in `.codex/config.toml`. Codex does not read `.codex/config.json`
8
- * for hooks; that path is intentionally not used.
6
+ * Two-step model that mirrors how every other Codex marketplace plugin works:
7
+ *
8
+ * 1. Assemble a Codex marketplace under the shared per-harness data dir.
9
+ * The marketplace skeleton (manifest, MCP config, plugin metadata) is
10
+ * copied from `marketplace/` in this package; the dynamic parts
11
+ * (`hooks.json`, skills, commands, plugin version) are generated /
12
+ * rendered fresh from the shared `@ory/argus` templates.
13
+ *
14
+ * 2. Hand that directory to `codex` via `codex plugin marketplace add` +
15
+ * `codex plugin add ory-codex@ory`. Codex copies the plugin into its
16
+ * own cache (`~/.codex/plugins/cache/...`), so once installed the
17
+ * plugin no longer depends on the assembled marketplace dir for
18
+ * runtime — but Codex needs it to stay around for `marketplace upgrade`.
19
+ *
20
+ * Uninstall reverses both steps: `codex plugin remove` + `codex plugin
21
+ * marketplace remove`, then we delete the assembled dir.
9
22
  *
10
23
  * Usage:
11
- * npx ory-codex-setup # Auto-detect and configure
12
- * npx ory-codex-setup --project-dir /path/to/repo
13
- * npx ory-codex-setup --global # Install to ~/.codex/
14
- * npx ory-codex-setup --print # Print config to stdout without writing
15
- * npx ory-codex-setup --uninstall # Remove Ory hooks from config
24
+ * npx ory-codex-setup Install
25
+ * npx ory-codex-setup --uninstall Remove
26
+ * npx ory-codex-setup --help Show this help
16
27
  */
17
28
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
29
  if (k2 === undefined) k2 = k;
@@ -48,141 +59,241 @@ var __importStar = (this && this.__importStar) || (function () {
48
59
  };
49
60
  })();
50
61
  Object.defineProperty(exports, "__esModule", { value: true });
62
+ exports.install = install;
63
+ exports.uninstall = uninstall;
64
+ const node_child_process_1 = require("node:child_process");
51
65
  const fs = __importStar(require("node:fs"));
52
66
  const path = __importStar(require("node:path"));
53
67
  const argus_1 = require("@ory/argus");
54
- const BIN_NAME = "ory-codex-hook";
55
- const PACKAGE_NAME = "@ory/codex";
68
+ const PACKAGE_ROOT = path.resolve(__dirname, "..", "..");
69
+ const PACKAGE_VERSION = readPackageVersion();
70
+ const MARKETPLACE_NAME = "ory";
71
+ const PLUGIN_NAME = "ory-codex";
72
+ const PLUGIN_REF = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
56
73
  const RENDER_OPTS = {
57
74
  binName: "ory-codex",
58
75
  packageName: "@ory/codex",
59
76
  };
60
- function getCodexDir(args) {
61
- if (args.global) {
62
- const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
63
- return path.join(home, ".codex");
77
+ /**
78
+ * Where the assembled marketplace lives. Codex stores a reference to this
79
+ * path in its own config (`[marketplaces.ory].source`), so the location
80
+ * needs to survive across sessions — it can't be the throwaway npx cache.
81
+ */
82
+ const MARKETPLACE_ROOT = path.join((0, argus_1.getHarnessDataDir)("codex"), "marketplace");
83
+ const PLUGIN_ROOT = path.join(MARKETPLACE_ROOT, "plugins", PLUGIN_NAME);
84
+ /** Hook command that resolves via the npm registry on every invocation. */
85
+ const HOOK_COMMAND = "npx -y -p @ory/codex ory-codex-hook";
86
+ function readPackageVersion() {
87
+ try {
88
+ const pj = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, "package.json"), "utf-8"));
89
+ return pj.version ?? "0.0.0";
90
+ }
91
+ catch {
92
+ return "0.0.0";
64
93
  }
65
- return path.join(args.projectDir, ".codex");
66
94
  }
67
- function generateHooksConfig() {
68
- const cmd = (0, argus_1.resolveHookCommand)(PACKAGE_NAME, BIN_NAME);
69
- return {
70
- SessionStart: (0, argus_1.flatHookEntry)(cmd),
71
- PreToolUse: (0, argus_1.flatHookEntry)(cmd),
72
- PostToolUse: (0, argus_1.flatHookEntry)(cmd),
73
- PermissionRequest: (0, argus_1.flatHookEntry)(cmd),
74
- UserPromptSubmit: (0, argus_1.flatHookEntry)(cmd),
75
- Stop: (0, argus_1.flatHookEntry)(cmd),
76
- };
95
+ function runCodex(args) {
96
+ const result = (0, node_child_process_1.spawnSync)("codex", args, {
97
+ encoding: "utf-8",
98
+ stdio: ["pipe", "pipe", "pipe"],
99
+ });
100
+ const output = ((result.stdout ?? "") + (result.stderr ?? "")).trim();
101
+ return { ok: result.status === 0, output };
102
+ }
103
+ function checkCodexCli() {
104
+ const result = (0, node_child_process_1.spawnSync)("codex", ["--version"], {
105
+ encoding: "utf-8",
106
+ stdio: ["pipe", "pipe", "pipe"],
107
+ });
108
+ return result.status === 0;
77
109
  }
78
110
  /**
79
- * Ensure `[features] codex_hooks = true` is present in config.toml.
80
- * Hand-rolled TOML editing — we only manage one line; full TOML parsing
81
- * is not warranted. Idempotent.
111
+ * Copy the static marketplace skeleton from the package into the persistent
112
+ * location. Overwrites any prior copy so out-of-date bundled assets get
113
+ * replaced on every install.
82
114
  */
83
- function ensureCodexHooksFeature(configTomlPath) {
84
- let contents = "";
85
- if (fs.existsSync(configTomlPath)) {
86
- contents = fs.readFileSync(configTomlPath, "utf-8");
87
- }
88
- if (/^codex_hooks\s*=\s*true\b/m.test(contents)) {
89
- return;
90
- }
91
- if (/^\[features\]\s*$/m.test(contents)) {
92
- contents = contents.replace(/^\[features\]\s*$/m, (m) => `${m}\ncodex_hooks = true`);
115
+ function copyMarketplaceSkeleton() {
116
+ const src = path.join(PACKAGE_ROOT, "marketplace");
117
+ if (!fs.existsSync(src)) {
118
+ throw new Error(`Marketplace template missing at ${src}. The npm package is incomplete.`);
93
119
  }
94
- else {
95
- const sep = contents.endsWith("\n") || contents.length === 0 ? "" : "\n";
96
- contents += `${sep}\n[features]\ncodex_hooks = true\n`;
120
+ fs.rmSync(MARKETPLACE_ROOT, { recursive: true, force: true });
121
+ fs.cpSync(src, MARKETPLACE_ROOT, { recursive: true, force: true });
122
+ }
123
+ /**
124
+ * Patch the plugin manifest with the runtime values that depend on the
125
+ * installed npm package (currently just the version). Keeps the rest of the
126
+ * static manifest untouched.
127
+ */
128
+ function updatePluginManifest() {
129
+ const manifestPath = path.join(PLUGIN_ROOT, ".codex-plugin", "plugin.json");
130
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
131
+ manifest.version = PACKAGE_VERSION;
132
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
133
+ }
134
+ /**
135
+ * Render the shared skill catalog into the plugin's skills directory. Codex
136
+ * surfaces these in `/skills` and the model auto-invokes them by description.
137
+ * Wipe-and-rewrite so renames or removals in the shared templates take effect.
138
+ */
139
+ function renderSkills() {
140
+ const skillsDir = path.join(PLUGIN_ROOT, "skills");
141
+ fs.rmSync(skillsDir, { recursive: true, force: true });
142
+ (0, argus_1.writeSkillTree)(skillsDir, (0, argus_1.renderOrySkills)("codex", RENDER_OPTS));
143
+ }
144
+ /**
145
+ * Render the local-up / local-down commands as Codex slash commands. Codex
146
+ * picks up Markdown files under `<plugin>/commands/` with frontmatter that
147
+ * declares the command's description and (optionally) allowed tools.
148
+ */
149
+ function renderCommands() {
150
+ const commandsDir = path.join(PLUGIN_ROOT, "commands");
151
+ fs.rmSync(commandsDir, { recursive: true, force: true });
152
+ fs.mkdirSync(commandsDir, { recursive: true });
153
+ for (const cmd of (0, argus_1.renderOryCommands)("codex", RENDER_OPTS)) {
154
+ fs.writeFileSync(path.join(commandsDir, `${cmd.slug}.md`), (0, argus_1.commandToFrontmatterMarkdown)(cmd));
97
155
  }
98
- fs.mkdirSync(path.dirname(configTomlPath), { recursive: true });
99
- fs.writeFileSync(configTomlPath, contents);
100
156
  }
101
157
  /**
102
- * Materialize the Ory skill catalog (4 guide skills + 2 user-invocable
103
- * command-skills for local-up/local-down) into Codex's skills tree. Codex
104
- * surfaces them in the `/skills` picker and the model auto-invokes them
105
- * based on the SKILL.md descriptions.
106
- *
107
- * Codex's documented skill location is `~/.codex/skills/`; the `--global`
108
- * flag routes the install there. Project-scope installs land in
109
- * `<projectDir>/.codex/skills/` for parity with the hooks layout.
158
+ * Generate the hooks file. Hook command resolves the bin via `npx`, so the
159
+ * path is stable across npm cache evictions and version bumps.
110
160
  */
111
- function installOrySkills(codexDir) {
112
- const skillsDir = path.join(codexDir, "skills");
113
- const guideSkills = (0, argus_1.renderOrySkills)("codex", RENDER_OPTS);
114
- const commandSkills = (0, argus_1.renderOryCommands)("codex", RENDER_OPTS).map(argus_1.commandToSkill);
115
- (0, argus_1.writeSkillTree)(skillsDir, [...guideSkills, ...commandSkills]);
161
+ function writeHooks() {
162
+ const hooksPath = path.join(PLUGIN_ROOT, "hooks.json");
163
+ const entry = [{ type: "command", command: HOOK_COMMAND }];
164
+ fs.writeFileSync(hooksPath, JSON.stringify({
165
+ hooks: {
166
+ SessionStart: entry,
167
+ PreToolUse: entry,
168
+ PostToolUse: entry,
169
+ PermissionRequest: entry,
170
+ UserPromptSubmit: entry,
171
+ Stop: entry,
172
+ },
173
+ }, null, 2) + "\n");
116
174
  }
117
- /** Remove every skill this plugin installed; tidy the dir if it ends up empty. */
118
- function uninstallOrySkills(codexDir) {
119
- const skillsDir = path.join(codexDir, "skills");
120
- if (!fs.existsSync(skillsDir))
121
- return;
122
- (0, argus_1.removeSkillDirs)(skillsDir, [...argus_1.ORY_SKILL_NAMES, ...argus_1.ORY_COMMAND_SKILL_NAMES]);
123
- try {
124
- if (fs.readdirSync(skillsDir).length === 0) {
125
- fs.rmdirSync(skillsDir);
175
+ /**
176
+ * Assemble the full marketplace tree in the persistent location. Idempotent.
177
+ */
178
+ function assembleMarketplace() {
179
+ fs.mkdirSync(MARKETPLACE_ROOT, { recursive: true });
180
+ copyMarketplaceSkeleton();
181
+ updatePluginManifest();
182
+ renderSkills();
183
+ renderCommands();
184
+ writeHooks();
185
+ }
186
+ /**
187
+ * Register (or refresh) the local marketplace with Codex and install the
188
+ * plugin from it. Both steps are idempotent — re-running an already-installed
189
+ * plugin just refreshes it.
190
+ */
191
+ function registerWithCodex() {
192
+ const addMp = runCodex(["plugin", "marketplace", "add", MARKETPLACE_ROOT]);
193
+ if (!addMp.ok) {
194
+ const lower = addMp.output.toLowerCase();
195
+ if (lower.includes("already") || lower.includes("exists")) {
196
+ // Local marketplaces don't need `upgrade` — the source is on disk and
197
+ // we just rewrote it. Codex re-reads it on every `plugin add`.
198
+ }
199
+ else {
200
+ throw new Error(`Failed to register marketplace: ${addMp.output}`);
126
201
  }
127
202
  }
128
- catch {
129
- /* ignore — directory still in use is fine. */
203
+ const addPlugin = runCodex(["plugin", "add", PLUGIN_REF]);
204
+ if (!addPlugin.ok) {
205
+ const lower = addPlugin.output.toLowerCase();
206
+ if (!(lower.includes("already") || lower.includes("installed"))) {
207
+ throw new Error(`Failed to add plugin: ${addPlugin.output}`);
208
+ }
130
209
  }
131
210
  }
132
- /** Remove `codex_hooks = true` from `[features]`. Leaves other entries alone. */
133
- function removeCodexHooksFeature(configTomlPath) {
134
- if (!fs.existsSync(configTomlPath))
135
- return;
136
- const original = fs.readFileSync(configTomlPath, "utf-8");
137
- const updated = original.replace(/^codex_hooks\s*=\s*true\s*\n?/m, "");
138
- if (updated !== original) {
139
- fs.writeFileSync(configTomlPath, updated);
211
+ function install(_args) {
212
+ if (!checkCodexCli()) {
213
+ console.error("Error: 'codex' CLI not found in PATH.");
214
+ console.error("Install Codex first: https://github.com/openai/codex");
215
+ process.exit(1);
140
216
  }
217
+ console.log(`Assembling Ory plugin (skills, commands, hooks, MCP) at:`);
218
+ console.log(` ${PLUGIN_ROOT}`);
219
+ assembleMarketplace();
220
+ console.log(`Registering marketplace and installing ${PLUGIN_REF}...`);
221
+ registerWithCodex();
222
+ console.log(` Plugin installed.`);
223
+ printNextSteps();
141
224
  }
142
- function main() {
225
+ function uninstall(_args) {
226
+ if (!checkCodexCli()) {
227
+ console.error("Error: 'codex' CLI not found in PATH.");
228
+ process.exit(1);
229
+ }
230
+ console.log(`Uninstalling ${PLUGIN_REF}...`);
231
+ const rm = runCodex(["plugin", "remove", PLUGIN_REF]);
232
+ if (!rm.ok && !/not\s+installed|not\s+found/i.test(rm.output)) {
233
+ console.warn(` Warning: ${rm.output}`);
234
+ }
235
+ else {
236
+ console.log(" Plugin removed.");
237
+ }
238
+ const rmMp = runCodex(["plugin", "marketplace", "remove", MARKETPLACE_NAME]);
239
+ if (!rmMp.ok && !/not\s+found|no\s+such/i.test(rmMp.output)) {
240
+ console.warn(` Warning: ${rmMp.output}`);
241
+ }
242
+ else {
243
+ console.log(" Marketplace removed.");
244
+ }
245
+ if (fs.existsSync(MARKETPLACE_ROOT)) {
246
+ fs.rmSync(MARKETPLACE_ROOT, { recursive: true, force: true });
247
+ console.log(` Removed ${MARKETPLACE_ROOT}`);
248
+ }
249
+ }
250
+ function printNextSteps() {
251
+ console.log("");
252
+ console.log("Next steps:");
253
+ console.log(" 1. (Optional) Point at an Ory project — without this, the plugin");
254
+ console.log(" runs in pass-through mode (skills work, nothing is blocked):");
255
+ console.log(" npx ory-codex configure --project-url https://<id>.projects.oryapis.com \\");
256
+ console.log(" --api-key ory_pat_...");
257
+ console.log("");
258
+ console.log(" 2. Or spin up the local Ory stack from inside Codex (`/skills` -> `ory-local-up`).");
259
+ console.log("");
260
+ console.log(" 3. Turn on the interactive user login gate (opt-in):");
261
+ console.log(" export ORY_AUTH_GATE=1");
262
+ console.log(" Without this, Codex runs without a human Ory identity attached to the");
263
+ console.log(" session and permission checks fall back to a session:<id> subject.");
264
+ console.log("");
265
+ console.log(" 4. Start a Codex session — Ory hooks are now wired in.");
266
+ console.log("");
267
+ console.log("To uninstall: npx ory-codex uninstall");
268
+ }
269
+ // --- Standalone binary entry point (ory-codex-setup) ---
270
+ if (require.main === module) {
143
271
  if (process.argv.includes("--help") || process.argv.includes("-h")) {
144
- (0, argus_1.printSetupHelp)("ory-codex-setup", "Codex", {
145
- supportsGlobal: true,
146
- globalPath: "~/.codex/hooks.json",
147
- });
272
+ console.log(`
273
+ ory-codex-setup — Install or remove the Ory plugin for Codex
274
+
275
+ Usage:
276
+ npx ory-codex-setup [options]
277
+
278
+ Options:
279
+ --uninstall Remove the Ory plugin and marketplace
280
+ -h, --help Show this help
281
+
282
+ Registers a local marketplace at:
283
+ ${MARKETPLACE_ROOT}
284
+
285
+ and runs \`codex plugin add ${PLUGIN_REF}\` to install. The local
286
+ marketplace is the source of truth for upgrades — re-running install
287
+ re-renders skills + commands from the shared @ory/argus templates and
288
+ refreshes Codex's cache copy on the next session.
289
+ `);
148
290
  process.exit(0);
149
291
  }
150
- const args = (0, argus_1.parseSetupArgs)({ supportsGlobal: true });
151
- const hooksConfig = generateHooksConfig();
152
- if (args.print) {
153
- console.log(JSON.stringify({ hooks: hooksConfig }, null, 2));
154
- return;
155
- }
156
- const codexDir = getCodexDir(args);
157
- const hooksPath = path.join(codexDir, "hooks.json");
158
- const configTomlPath = path.join(codexDir, "config.toml");
159
- const existing = (0, argus_1.readJsonFile)(hooksPath);
160
- if (args.uninstall) {
161
- if (!fs.existsSync(codexDir)) {
162
- console.log("No Codex config directory found. Nothing to uninstall.");
163
- return;
164
- }
165
- let updated = (0, argus_1.removeFlatHooks)(existing, BIN_NAME);
166
- updated = (0, argus_1.removeMcpServer)(updated);
167
- (0, argus_1.writeJsonFile)(hooksPath, updated);
168
- removeCodexHooksFeature(configTomlPath);
169
- uninstallOrySkills(codexDir);
170
- console.log(`Removed Ory hooks and MCP server from ${hooksPath}`);
171
- console.log(`Removed codex_hooks feature flag from ${configTomlPath}`);
172
- console.log(`Removed Ory skills from ${path.join(codexDir, "skills")}`);
173
- return;
174
- }
175
- let merged = (0, argus_1.mergeFlatHooks)(existing, hooksConfig, BIN_NAME);
176
- merged = (0, argus_1.mergeMcpServer)(merged);
177
- (0, argus_1.writeJsonFile)(hooksPath, merged);
178
- ensureCodexHooksFeature(configTomlPath);
179
- installOrySkills(codexDir);
180
- console.log(`Ory hooks and MCP server installed to ${hooksPath}`);
181
- console.log(`Enabled [features] codex_hooks = true in ${configTomlPath}`);
182
- console.log(`Ory skills installed to ${path.join(codexDir, "skills")}`);
183
- if (!args.global) {
184
- console.log(" Tip: re-run with --global if Codex only discovers skills from ~/.codex/skills.");
185
- }
186
- (0, argus_1.printNextSteps)("Codex", "npx ory-codex-setup --uninstall");
292
+ const args = process.argv.slice(2);
293
+ if (args.includes("--uninstall")) {
294
+ uninstall(args);
295
+ }
296
+ else {
297
+ install(args);
298
+ }
187
299
  }
188
- main();
package/dist/index.d.ts CHANGED
@@ -1,2 +1 @@
1
1
  export { handleHookEvent } from "./handlers.js";
2
- export { generateSettings } from "./settings.js";
package/dist/index.js CHANGED
@@ -1,7 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.generateSettings = exports.handleHookEvent = void 0;
3
+ exports.handleHookEvent = void 0;
4
4
  var handlers_js_1 = require("./handlers.js");
5
5
  Object.defineProperty(exports, "handleHookEvent", { enumerable: true, get: function () { return handlers_js_1.handleHookEvent; } });
6
- var settings_js_1 = require("./settings.js");
7
- Object.defineProperty(exports, "generateSettings", { enumerable: true, get: function () { return settings_js_1.generateSettings; } });
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "ory",
3
+ "interface": {
4
+ "displayName": "Ory"
5
+ },
6
+ "plugins": [
7
+ {
8
+ "name": "ory-codex",
9
+ "source": {
10
+ "source": "local",
11
+ "path": "./plugins/ory-codex"
12
+ },
13
+ "policy": {
14
+ "installation": "AVAILABLE"
15
+ },
16
+ "category": "Security"
17
+ }
18
+ ]
19
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "ory-codex",
3
+ "version": "0.0.0",
4
+ "description": "Ory authentication, authorization, and audit for every Codex tool call.",
5
+ "author": {
6
+ "name": "Ory",
7
+ "url": "https://ory.com"
8
+ },
9
+ "homepage": "https://ory.com",
10
+ "repository": "https://github.com/ory/ory-agent-plugins",
11
+ "license": "Apache-2.0",
12
+ "keywords": [
13
+ "ory",
14
+ "authentication",
15
+ "authorization",
16
+ "permissions",
17
+ "audit",
18
+ "oauth2",
19
+ "zanzibar"
20
+ ],
21
+ "skills": "./skills/",
22
+ "mcpServers": "./.mcp.json",
23
+ "interface": {
24
+ "displayName": "Ory",
25
+ "shortDescription": "Authn/authz and audit for every Codex tool call",
26
+ "longDescription": "Adds Ory Identities, Permissions, and audit logging to Codex. Auth gate on session start, per-tool permission checks before every invocation, structured trace spans on every decision, and a bundled Ory MCP server for managing identities, OAuth2 clients, and permission tuples from inside Codex.",
27
+ "developerName": "Ory",
28
+ "category": "Security",
29
+ "capabilities": ["Write"],
30
+ "websiteURL": "https://ory.com",
31
+ "privacyPolicyURL": "https://www.ory.sh/privacy-policy",
32
+ "termsOfServiceURL": "https://www.ory.sh/tos",
33
+ "defaultPrompt": [
34
+ "Scaffold Ory auth into this app, or grant me 'use' on a tool"
35
+ ]
36
+ }
37
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "ory": {
4
+ "command": "npx",
5
+ "args": ["-y", "-p", "@ory/mcp-server", "ory-mcp-server"]
6
+ }
7
+ }
8
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ory/codex",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Ory plugin for Codex: scaffolding skills, a local Ory instance, and authentication, authorization, and audit for every tool call",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://ory.com",
@@ -55,7 +55,6 @@
55
55
  }
56
56
  },
57
57
  "bin": {
58
- "codex": "dist/cli/main.js",
59
58
  "ory-codex": "dist/cli/main.js",
60
59
  "ory-codex-hook": "dist/hook.js",
61
60
  "ory-codex-setup": "dist/cli/setup.js"
@@ -64,13 +63,13 @@
64
63
  "dist",
65
64
  "!dist/dev",
66
65
  "!dist/**/*.tsbuildinfo",
67
- ".codex-plugin"
66
+ "marketplace"
68
67
  ],
69
68
  "dependencies": {
70
- "@ory/argus": "0.3.0"
69
+ "@ory/argus": "0.4.0"
71
70
  },
72
71
  "engines": {
73
- "node": ">=24"
72
+ "node": ">=22"
74
73
  },
75
74
  "scripts": {
76
75
  "build": "tsc",
@@ -1,10 +0,0 @@
1
- {
2
- "name": "ory-codex",
3
- "version": "0.1.0",
4
- "description": "Ory authentication and authorization for Codex CLI — session auth, tool permissions, and audit logging",
5
- "author": {
6
- "name": "Ory",
7
- "url": "https://ory.com"
8
- },
9
- "homepage": "https://ory.com"
10
- }
@@ -1,16 +0,0 @@
1
- /**
2
- * Generate the hooks config that configures Ory hooks for Codex.
3
- *
4
- * Codex supports two hook events:
5
- * - SessionStart: fired when the agent session begins
6
- * - PreToolUse: fired before each tool call (Bash only)
7
- */
8
- export declare function generateSettings(hookScriptPath: string): object;
9
- /**
10
- * Generate settings assuming the plugin is installed via npm/pnpm.
11
- */
12
- export declare function generateInstalledSettings(): object;
13
- /**
14
- * Generate settings pointing to a local dev build.
15
- */
16
- export declare function generateDevSettings(packageDir: string): object;
package/dist/settings.js DELETED
@@ -1,69 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.generateSettings = generateSettings;
37
- exports.generateInstalledSettings = generateInstalledSettings;
38
- exports.generateDevSettings = generateDevSettings;
39
- const path = __importStar(require("node:path"));
40
- const argus_1 = require("@ory/argus");
41
- /**
42
- * Generate the hooks config that configures Ory hooks for Codex.
43
- *
44
- * Codex supports two hook events:
45
- * - SessionStart: fired when the agent session begins
46
- * - PreToolUse: fired before each tool call (Bash only)
47
- */
48
- function generateSettings(hookScriptPath) {
49
- const cmd = `node ${hookScriptPath}`;
50
- return {
51
- hooks: {
52
- SessionStart: (0, argus_1.flatHookEntry)(cmd),
53
- PreToolUse: (0, argus_1.flatHookEntry)(cmd),
54
- },
55
- };
56
- }
57
- /**
58
- * Generate settings assuming the plugin is installed via npm/pnpm.
59
- */
60
- function generateInstalledSettings() {
61
- return generateSettings("ory-codex-hook");
62
- }
63
- /**
64
- * Generate settings pointing to a local dev build.
65
- */
66
- function generateDevSettings(packageDir) {
67
- const hookPath = path.resolve(packageDir, "dist", "hook.js");
68
- return generateSettings(hookPath);
69
- }