deepcodex 0.1.0 → 0.1.2

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.
@@ -1,24 +1,26 @@
1
1
  {
2
- "name": "opencodex",
3
- "version": "0.1.0+codex.20260917024812",
2
+ "name": "deepcodex",
3
+ "version": "0.1.2",
4
4
  "description": "Delegates tasks to native DeepSeek Flash subagents inside Codex through a local router.",
5
5
  "author": {
6
- "name": "OpenCodex"
6
+ "name": "DeepCodex"
7
7
  },
8
8
  "skills": "./skills/",
9
9
  "interface": {
10
- "displayName": "OpenCodex",
10
+ "displayName": "DeepCodex",
11
+ "composerIcon": "./assets/deepcodex.png",
12
+ "logo": "./assets/deepcodex.png",
11
13
  "shortDescription": "Astra coordinates; DeepSeek Flash executes.",
12
14
  "longDescription": "Keeps the coordinator model and connects DeepSeek Flash as a native subagent, with tracking, tools, and results inside Codex.",
13
- "developerName": "OpenCodex",
15
+ "developerName": "DeepCodex",
14
16
  "category": "Productivity",
15
17
  "capabilities": [
16
18
  "Read",
17
19
  "Write"
18
20
  ],
19
21
  "defaultPrompt": [
20
- "Check whether OpenCodex is ready.",
21
- "Delegate this implementation to Flash with OpenCodex."
22
+ "Check whether DeepCodex is ready.",
23
+ "Delegate this implementation to Flash with DeepCodex."
22
24
  ]
23
25
  }
24
26
  }
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # OpenCodex
1
+ # DeepCodex
2
2
 
3
- OpenCodex keeps the coordinator on your selected native Codex model and runs
3
+ DeepCodex keeps the coordinator on your selected native Codex model and runs
4
4
  DeepSeek Flash as an actual Codex subagent through a local loopback router. The
5
5
  subagent appears in the normal collaboration tools (`spawn_agent`,
6
6
  `followup_task`, `wait_agent`, `interrupt_agent`) under the model name
@@ -12,9 +12,13 @@ native coordinator model is not replaced.
12
12
  - macOS. The installer and the LaunchAgent service are macOS-only.
13
13
  - Node.js >= 22.15. The worker uses `node:sqlite`, `fetch` and
14
14
  `import.meta.resolve`.
15
- - A Codex CLI on `PATH` whose `codex exec` supports `--ignore-user-config`,
16
- `--ephemeral`, `--json` and `--strict-config`. `opencodex doctor` verifies
17
- this before anything else is activated.
15
+ - A compatible Codex CLI, either on `PATH` or bundled with Codex Desktop.
16
+ When no CLI is on `PATH`, DeepCodex checks `Codex.app` and `ChatGPT.app`
17
+ under `~/Applications` and `/Applications`, in that order. No separate CLI
18
+ installation is needed when Desktop includes a compatible binary.
19
+ `codex exec` must support `--ignore-user-config`, `--ephemeral`, `--json`
20
+ and `--strict-config`; `deepcodex doctor` verifies this before activation.
21
+ A CLI on `PATH` takes priority, even if it is incompatible.
18
22
  - A DeepSeek API key.
19
23
 
20
24
  ## Install globally
@@ -22,24 +26,34 @@ native coordinator model is not replaced.
22
26
  Once the package is published to npm:
23
27
 
24
28
  ```sh
25
- npm install --global opencodex
26
- opencodex configure
27
- opencodex doctor
28
- opencodex install
29
+ npm install --global deepcodex
30
+ deepcodex configure
31
+ deepcodex doctor
32
+ deepcodex install
29
33
  ```
30
34
 
31
- The global installation adds `opencodex` to npm's global bin directory, which
35
+ The global installation adds `deepcodex` to npm's global bin directory, which
32
36
  must be on your `PATH`. Run it from any directory; no checkout path is needed.
33
37
 
34
38
  To install the local checkout globally before publication, run these commands
35
- from the repository, then use the same `opencodex` commands above:
39
+ from the repository, then use the same `deepcodex` commands above:
36
40
 
37
41
  ```sh
38
42
  pnpm install --frozen-lockfile
39
43
  npm install --global .
40
44
  ```
41
45
 
42
- ### 1. `opencodex configure`
46
+ ### Upgrading from OpenCodex
47
+
48
+ The npm package, command and plugin are now named `deepcodex`, and the service
49
+ is `com.deepcodex.router`. Existing private paths (`~/.config/opencodex`,
50
+ `~/.local/share/opencodex`) and provider IDs are retained, reusing your saved
51
+ key and backup.
52
+
53
+ Run `deepcodex doctor` and `deepcodex install` to refresh the runtime; there is
54
+ no need to run `configure` again if your key is already saved.
55
+
56
+ ### 1. `deepcodex configure`
43
57
 
44
58
  `configure` asks for the DeepSeek API key with hidden input on the terminal and
45
59
  stores it in `~/.config/opencodex/.env`. It creates the directory with mode
@@ -57,7 +71,7 @@ the file, as does the installed desktop service. Credentials stay outside the
57
71
  repository. The file permissions restrict other users, but programs running as
58
72
  your user can still read it.
59
73
 
60
- ### 2. `opencodex doctor`
74
+ ### 2. `deepcodex doctor`
61
75
 
62
76
  `doctor` checks the local prerequisites without running inference: that a
63
77
  compatible Codex CLI is reachable and that the DeepSeek credential is present.
@@ -66,7 +80,7 @@ unless the doctor reports `ready`.
66
80
  Neither `configure` nor `doctor` contacts DeepSeek to validate the key. A `ready`
67
81
  result confirms local prerequisites, not provider authentication or account credit.
68
82
 
69
- ### 3. `opencodex install`
83
+ ### 3. `deepcodex install`
70
84
 
71
85
  `install` activates the desktop integration:
72
86
 
@@ -74,11 +88,14 @@ result confirms local prerequisites, not provider authentication or account cred
74
88
  dependency) to `~/.local/share/opencodex/runtime`;
75
89
  - writes private state, the model catalog and receipts to
76
90
  `~/.config/opencodex/desktop`;
77
- - installs and starts the `com.opencodex.router` LaunchAgent, bound only to
91
+ - installs and starts the `com.deepcodex.router` LaunchAgent, bound only to
78
92
  `127.0.0.1:4207`;
79
93
  - updates `~/.codex/config.toml` with the local provider, the subagent defaults
80
94
  and the generated model catalog, keeping a pre-install backup at
81
- `~/.config/opencodex/desktop/config.before.toml`.
95
+ `~/.config/opencodex/desktop/config.before.toml`;
96
+ - registers and installs the bundled DeepCodex plugin in the personal marketplace,
97
+ including its current name, description, icon and delegation skill. An existing
98
+ local OpenCodex plugin in that marketplace is replaced after DeepCodex installs.
82
99
 
83
100
  The installer refuses to run when an unrelated custom provider is active or when
84
101
  an endpoint override is already configured, and it leaves your configuration
@@ -89,22 +106,24 @@ that were already open keep the provider and model catalog they started with.
89
106
 
90
107
  ## Plugin skill
91
108
 
92
- The delegation skill lives in `skills/delegate-flash/SKILL.md` and is loaded by
93
- installing the OpenCodex plugin through a Codex marketplace. Installing the npm
94
- package or running the commands above does not install the skill, and this
95
- checkout is not a marketplace source by itself.
109
+ The delegation skill lives in `skills/delegate-flash/SKILL.md`. `deepcodex install`
110
+ copies the plugin to `~/plugins/deepcodex`, registers it in
111
+ `~/.agents/plugins/marketplace.json`, and installs it through the Codex CLI.
112
+ Repeating the command refreshes the plugin cache even when the package version
113
+ has not changed. Other personal marketplace entries are preserved.
96
114
 
97
115
  ## Commands
98
116
 
99
117
  | Command | Purpose |
100
118
  | --- | --- |
101
- | `opencodex configure` | Prompt for the DeepSeek API key and write `~/.config/opencodex/.env`. |
102
- | `opencodex doctor` | Check Codex CLI compatibility and credential presence without inference. |
103
- | `opencodex install` | Install the router runtime, the LaunchAgent and the user-level Codex settings. |
104
- | `opencodex status` | Query the installed router health endpoint without inference. |
105
- | `opencodex run --cwd PATH --task-file PATH [--write]` | Run one bounded isolated worker ticket against DeepSeek. Read-only unless `--write` is given. |
106
- | `opencodex pilot` | Run the opt-in live native-delegation test; consumes Codex and DeepSeek usage. |
107
- | `opencodex --version`, `opencodex --help` | Print the package version or the command list. |
119
+ | `deepcodex configure` | Prompt for the DeepSeek API key and write `~/.config/opencodex/.env`. |
120
+ | `deepcodex doctor` | Check Codex CLI compatibility and credential presence without inference. |
121
+ | `deepcodex install` | Install the router runtime, LaunchAgent, Codex settings and current DeepCodex plugin. |
122
+ | `deepcodex uninstall` | Stop and remove the router, restoring the saved Codex configuration. Credentials are preserved. Refuses if config changed since installation. Restart Codex afterward; remove the npm package separately with `npm uninstall -g deepcodex`. |
123
+ | `deepcodex status` | Query the installed router health endpoint without inference. |
124
+ | `deepcodex run --cwd PATH --task-file PATH [--write]` | Run one bounded isolated worker ticket against DeepSeek. Read-only unless `--write` is given. |
125
+ | `deepcodex pilot` | Run the opt-in live native-delegation test; consumes Codex and DeepSeek usage. |
126
+ | `deepcodex --version`, `deepcodex --help` | Print the package version or the command list. |
108
127
 
109
128
  `run` executes a single ticket and consumes DeepSeek API usage. It takes an
110
129
  exclusive per-user lock, so only one worker runs at a time.
Binary file
@@ -3,21 +3,22 @@ import { readFileSync } from 'node:fs';
3
3
 
4
4
  const args = process.argv.slice(2);
5
5
  const command = args.shift();
6
- const help = `Usage: opencodex <command>
6
+ const help = `Usage: deepcodex <command>
7
7
 
8
8
  Commands:
9
9
  configure Save the DeepSeek key using a hidden terminal prompt
10
- install Activate the local macOS router and configure Codex
10
+ install Activate the local macOS router and install the Codex plugin
11
+ uninstall Remove the local router and restore Codex settings
11
12
  status Check the installed router without inference
12
13
  doctor Check worker prerequisites without inference
13
14
  run Run an isolated worker ticket (consumes DeepSeek usage)
14
15
  pilot Test native delegation (consumes Codex and DeepSeek usage)
15
16
 
16
- Requires macOS, Node.js >=22.15 and a compatible Codex CLI.
17
- Run opencodex configure to save the DeepSeek key in ~/.config/opencodex/.env.
18
- Installing this npm package does not activate the router or install the skill
19
- in Codex. Install the plugin through a Codex marketplace to load its skill.
20
- Use opencodex <command> --help for command options.
17
+ Requires macOS, Node.js >=22.15 and a compatible Codex CLI (PATH or Desktop bundle).
18
+ Run deepcodex configure to save the DeepSeek key in ~/.config/opencodex/.env.
19
+ Run deepcodex install after installing this npm package to activate the router
20
+ and install or update the DeepCodex plugin, including its icon and skill.
21
+ Use deepcodex <command> --help for command options.
21
22
  `;
22
23
 
23
24
  if (!command || command === '--help' || command === '-h') {
@@ -26,24 +27,24 @@ if (!command || command === '--help' || command === '-h') {
26
27
  const manifest = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
27
28
  console.log(manifest.version);
28
29
  } else {
29
- const scripts = { configure: 'credentials.js', install: 'desktop.js', status: 'desktop.js', doctor: 'worker.js', run: 'worker.js', pilot: 'pilot.js' };
30
+ const scripts = { configure: 'credentials.js', install: 'desktop.js', uninstall: 'desktop.js', status: 'desktop.js', doctor: 'worker.js', run: 'worker.js', pilot: 'pilot.js' };
30
31
  if (!Object.hasOwn(scripts, command)) {
31
32
  console.error(`Unknown command: ${command}\n${help}`);
32
33
  process.exit(2);
33
34
  }
34
35
  if (command === 'pilot' && args.length) {
35
36
  if (args.length === 1 && ['--help', '-h'].includes(args[0])) {
36
- console.log('Usage: opencodex pilot\nRuns a live native delegation test; consumes Codex and DeepSeek usage.');
37
+ console.log('Usage: deepcodex pilot\nRuns a live native delegation test; consumes Codex and DeepSeek usage.');
37
38
  process.exit(0);
38
39
  }
39
- console.error('Usage: opencodex pilot (no arguments)');
40
+ console.error('Usage: deepcodex pilot (no arguments)');
40
41
  process.exit(2);
41
42
  }
42
43
  try {
43
44
  const script = await import(new URL(`../scripts/${scripts[command]}`, import.meta.url));
44
45
  process.exitCode = command === 'pilot' ? await script.run() : await script.main([command, ...args]);
45
46
  } catch (error) {
46
- console.error(`OpenCodex failed: ${error.message}`);
47
+ console.error(`DeepCodex failed: ${error.message}`);
47
48
  process.exitCode = 1;
48
49
  }
49
50
  }
@@ -4,6 +4,6 @@
4
4
  "request_timeout_ms": 300000,
5
5
  "max_request_bytes": 134217728,
6
6
  "max_response_bytes": 33554432,
7
- "service_label": "com.opencodex.router",
7
+ "service_label": "com.deepcodex.router",
8
8
  "service_throttle_seconds": 10
9
9
  }
@@ -39,7 +39,7 @@
39
39
  "sandbox_workspace_write": {"network_access": false}
40
40
  },
41
41
  "model_metadata": {
42
- "display_name": "DeepSeek Flash (OpenCodex)",
42
+ "display_name": "DeepSeek Flash",
43
43
  "description": "Text worker using the direct DeepSeek API.",
44
44
  "default_reasoning_level": "high",
45
45
  "supported_reasoning_levels": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepcodex",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Native DeepSeek Flash subagents for Codex through a local router.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -17,11 +17,12 @@
17
17
  ],
18
18
  "type": "module",
19
19
  "bin": {
20
- "deepcodex": "bin/opencodex.js"
20
+ "deepcodex": "bin/deepcodex.js"
21
21
  },
22
22
  "files": [
23
23
  "bin/*.js",
24
24
  ".codex-plugin/plugin.json",
25
+ "assets/deepcodex.png",
25
26
  "skills/delegate-flash/SKILL.md",
26
27
  "config/*.json",
27
28
  "prompts/worker.md",
package/prompts/worker.md CHANGED
@@ -1,4 +1,4 @@
1
- You are the OpenCodex worker. Complete only the ticket you received and return
1
+ You are the DeepCodex worker. Complete only the ticket you received and return
2
2
  evidence to the coordinator. Follow the communication language stated in the
3
3
  ticket for your reports; keep the language of the code unchanged.
4
4
 
@@ -87,16 +87,16 @@ export function saveCredentials(file, name, secret) {
87
87
 
88
88
  export async function main(args) {
89
89
  if (args.length === 2 && ['--help', '-h'].includes(args[1])) {
90
- console.log('Usage: opencodex configure\nEnter the DeepSeek key in a hidden terminal prompt. Saves a private plaintext file outside the repository.');
90
+ console.log('Usage: deepcodex configure\nEnter the DeepSeek key in a hidden terminal prompt. Saves a private plaintext file outside the repository.');
91
91
  return 0;
92
92
  }
93
- if (args.length !== 1 || args[0] !== 'configure') throw new Error('Usage: opencodex configure (no key arguments accepted)');
93
+ if (args.length !== 1 || args[0] !== 'configure') throw new Error('Usage: deepcodex configure (no key arguments accepted)');
94
94
  const config = loadConfig();
95
95
  const name = config.codex.model_providers[config.codex.model_provider].env_key;
96
96
  const configured = config.credentials.env_file;
97
97
  const file = configured.startsWith('~/') ? path.join(os.homedir(), configured.slice(2)) : configured;
98
98
  const secret = await readSecret();
99
99
  saveCredentials(file, name, secret);
100
- console.log('DeepSeek key saved with owner-only permissions. Run opencodex install to activate it or reload an existing router.');
100
+ console.log('DeepSeek key saved with owner-only permissions. Run deepcodex install to activate it or reload an existing router.');
101
101
  return 0;
102
102
  }
@@ -11,6 +11,7 @@ import { parseToml } from './toml.js';
11
11
  import { startPilot } from './pilot-router.js';
12
12
 
13
13
  const STATE = path.join(os.homedir(), '.config/opencodex/desktop');
14
+ const LSREGISTER = '/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister';
14
15
  const readJson = filename => JSON.parse(fs.readFileSync(filename, 'utf8'));
15
16
  const expandHome = filename => filename.startsWith('~/') ? path.join(os.homedir(), filename.slice(2)) : filename;
16
17
 
@@ -78,7 +79,7 @@ export function plistDocument(value) {
78
79
 
79
80
  export function copyRuntime(root, runtime) {
80
81
  fs.mkdirSync(runtime, { recursive: true, mode: 0o700 });
81
- for (const directory of ['scripts', 'config', 'prompts', 'vendor']) {
82
+ for (const directory of ['scripts', 'config', 'prompts', 'vendor', '.codex-plugin', 'assets', 'skills']) {
82
83
  fs.cpSync(path.join(root, directory), path.join(runtime, directory), { recursive: true,
83
84
  filter: source => !['__pycache__', '.env', '.DS_Store'].includes(path.basename(source)) && !/\.py[cod]?$/.test(source) });
84
85
  }
@@ -87,6 +88,77 @@ export function copyRuntime(root, runtime) {
87
88
  fs.cpSync(dependency, path.join(runtime, 'node_modules/smol-toml'), { recursive: true });
88
89
  }
89
90
 
91
+ function registerServiceApp(app, action) {
92
+ const result = spawnSync(LSREGISTER, [action, app], { encoding: 'utf8' });
93
+ if (result.error || result.status !== 0) throw new Error(`Cannot ${action === '-u' ? 'unregister' : 'register'} DeepCodex app: ${result.error?.message || result.stderr.trim() || `lsregister exited ${result.status}`}`);
94
+ }
95
+
96
+ function createServiceApp(runtime, label) {
97
+ const app = path.join(runtime, 'DeepCodex.app');
98
+ const contents = path.join(app, 'Contents');
99
+ const executable = path.join(contents, 'MacOS/DeepCodex');
100
+ fs.mkdirSync(path.dirname(executable), { recursive: true });
101
+ fs.mkdirSync(path.join(contents, 'Resources'), { recursive: true });
102
+ const version = readJson(path.join(runtime, 'package.json')).version;
103
+ privateWrite(path.join(contents, 'Info.plist'), plistDocument({
104
+ CFBundleIdentifier: label, CFBundleName: 'DeepCodex', CFBundleDisplayName: 'DeepCodex',
105
+ CFBundleExecutable: 'DeepCodex', CFBundlePackageType: 'APPL', CFBundleIconFile: 'DeepCodex.icns',
106
+ CFBundleVersion: version, CFBundleShortVersionString: version, LSUIElement: true,
107
+ }));
108
+ const command = [process.execPath, path.join(runtime, 'scripts/desktop.js')]
109
+ .map(value => "'" + value.replaceAll("'", "'\\''") + "'").join(' ');
110
+ privateWrite(executable, `#!/bin/sh\nexec ${command} "$@"\n`);
111
+ fs.chmodSync(executable, 0o700);
112
+ const icon = spawnSync('/usr/bin/sips', ['-z', '512', '512', '-s', 'format', 'icns',
113
+ path.join(runtime, 'assets/deepcodex.png'), '--out', path.join(contents, 'Resources/DeepCodex.icns')], { encoding: 'utf8' });
114
+ if (icon.error || icon.status !== 0) throw new Error(`Cannot create DeepCodex app icon: ${icon.error?.message || icon.stderr.trim() || `sips exited ${icon.status}`}`);
115
+ registerServiceApp(app, '-f');
116
+ return executable;
117
+ }
118
+
119
+ export function installPlugin(root, codex, env) {
120
+ const home = env.HOME || os.homedir();
121
+ const marketplacePath = path.join(home, '.agents/plugins/marketplace.json');
122
+ const marketplace = fs.existsSync(marketplacePath) ? readJson(marketplacePath) : {
123
+ name: 'personal', interface: { displayName: 'Personal' }, plugins: [],
124
+ };
125
+ if (typeof marketplace.name !== 'string' || !/^[A-Za-z0-9_-]+$/.test(marketplace.name) || !Array.isArray(marketplace.plugins)) {
126
+ throw new Error('Invalid personal plugin marketplace');
127
+ }
128
+ const manifest = readJson(path.join(root, '.codex-plugin/plugin.json'));
129
+ if (manifest.name !== 'deepcodex' || typeof manifest.version !== 'string') throw new Error('Invalid DeepCodex plugin manifest');
130
+ const pluginPath = path.join(home, 'plugins', manifest.name);
131
+ copyRuntime(root, pluginPath);
132
+ manifest.version = `${manifest.version.split('+')[0]}+codex.${new Date().toISOString().replace(/\D/g, '')}`;
133
+ privateWrite(path.join(pluginPath, '.codex-plugin/plugin.json'), JSON.stringify(manifest, null, 2) + '\n');
134
+ const entry = {
135
+ name: manifest.name, source: { source: 'local', path: `./plugins/${manifest.name}` },
136
+ policy: { installation: 'AVAILABLE', authentication: 'ON_INSTALL' }, category: manifest.interface.category,
137
+ };
138
+ const existing = marketplace.plugins.findIndex(plugin => plugin.name === manifest.name);
139
+ if (existing < 0) marketplace.plugins.push(entry);
140
+ else marketplace.plugins[existing] = { ...marketplace.plugins[existing], source: entry.source };
141
+ fs.mkdirSync(path.dirname(marketplacePath), { recursive: true });
142
+ privateWrite(marketplacePath, JSON.stringify(marketplace, null, 2) + '\n');
143
+ const run = (command, selector) => {
144
+ const result = spawnSync(codex, ['plugin', command, selector, '--json'], { env, encoding: 'utf8' });
145
+ if (result.error || result.status !== 0) throw new Error(`Cannot ${command} Codex plugin ${selector}; rerun deepcodex install after checking Codex plugin support`);
146
+ return JSON.parse(result.stdout);
147
+ };
148
+ const selector = `${manifest.name}@${marketplace.name}`;
149
+ const result = run('add', selector);
150
+ const configPath = path.join(env.CODEX_HOME || path.join(home, '.codex'), 'config.toml');
151
+ const config = parseToml(fs.readFileSync(configPath, 'utf8'));
152
+ const legacySelector = `opencodex@${marketplace.name}`;
153
+ if (Object.hasOwn(config.plugins || {}, legacySelector)) run('remove', legacySelector);
154
+ const legacy = marketplace.plugins.find(plugin => plugin.name === 'opencodex' && plugin.source?.source === 'local');
155
+ if (legacy) {
156
+ marketplace.plugins = marketplace.plugins.filter(plugin => plugin !== legacy);
157
+ privateWrite(marketplacePath, JSON.stringify(marketplace, null, 2) + '\n');
158
+ }
159
+ return result;
160
+ }
161
+
90
162
  export async function health(state) {
91
163
  const response = await fetch(`http://127.0.0.1:${state.config.port}/health`, {
92
164
  headers: { 'x-opencodex-pilot': state.capability },
@@ -103,7 +175,9 @@ export async function install() {
103
175
  const env = workerEnvironment(process.env);
104
176
  loadCredentials(env, original);
105
177
  const diagnosis = doctor(original, env);
106
- if (diagnosis.status !== 'ready') throw new Error('OpenCodex worker doctor must be ready before installation');
178
+ if (diagnosis.status !== 'ready') throw new Error(`DeepCodex prerequisites are not ready: ${diagnosis.errors.join(' ')}`);
179
+ const pluginSupport = spawnSync(diagnosis.codex, ['plugin', 'add', '--help'], { env, encoding: 'utf8' });
180
+ if (pluginSupport.error || pluginSupport.status !== 0) throw new Error('Codex CLI must support plugin add; update Codex Desktop before installing DeepCodex');
107
181
  const configPath = path.join(env.CODEX_HOME || path.join(os.homedir(), '.codex'), 'config.toml');
108
182
  const before = fs.readFileSync(configPath, 'utf8');
109
183
  const parsed = parseToml(before);
@@ -113,6 +187,7 @@ export async function install() {
113
187
  fs.chmodSync(STATE, 0o700);
114
188
  const runtime = path.join(os.homedir(), '.local/share/opencodex/runtime');
115
189
  copyRuntime(ROOT, runtime);
190
+ const executable = createServiceApp(runtime, config.service_label);
116
191
  let catalog;
117
192
  if (parsed.model_catalog_json && path.resolve(expandHome(parsed.model_catalog_json)) !== path.join(STATE, 'models.json')) {
118
193
  catalog = readJson(expandHome(parsed.model_catalog_json));
@@ -130,30 +205,35 @@ export async function install() {
130
205
  receipts: path.join(STATE, 'receipts.jsonl'), markers: [] });
131
206
  const statePath = path.join(STATE, 'state.json');
132
207
  const capability = fs.existsSync(statePath) ? readJson(statePath).capability : randomBytes(32).toString('base64url');
133
- const state = { config, capability, node: process.execPath, runtime };
208
+ const state = { config, capability, node: process.execPath, runtime, configPath };
134
209
  const sections = {
135
210
  '': { model_provider: 'opencodex', model_catalog_json: path.join(STATE, 'models.json') },
136
211
  agents: { enabled: true, default_subagent_model: child.slug, default_subagent_reasoning_effort: 'high' },
137
212
  features: { multi_agent: true, multi_agent_v2: true },
138
- 'model_providers.opencodex': { name: 'OpenCodex', base_url: `http://127.0.0.1:${config.port}`, wire_api: 'responses',
213
+ 'model_providers.opencodex': { name: 'DeepCodex', base_url: `http://127.0.0.1:${config.port}`, wire_api: 'responses',
139
214
  requires_openai_auth: true, supports_websockets: false, request_max_retries: 0, stream_max_retries: 0, stream_idle_timeout_ms: config.request_timeout_ms },
140
215
  'model_providers.opencodex.http_headers': { 'x-opencodex-pilot': capability },
141
216
  };
142
- const updated = mergeConfig(before, sections);
217
+ let updated = mergeConfig(before, sections);
143
218
  privateWrite(path.join(STATE, 'config.proposed.toml'), updated);
144
219
  if (!fs.existsSync(path.join(STATE, 'config.before.toml'))) privateWrite(path.join(STATE, 'config.before.toml'), before);
145
220
  privateWrite(statePath, JSON.stringify(state));
146
221
  const label = config.service_label;
147
222
  const plist = path.join(os.homedir(), 'Library/LaunchAgents', label + '.plist');
148
223
  fs.mkdirSync(path.dirname(plist), { recursive: true });
149
- privateWrite(plist, plistDocument({ Label: label, ProgramArguments: [process.execPath, path.join(runtime, 'scripts/desktop.js'), 'serve'],
224
+ privateWrite(plist, plistDocument({ Label: label, AssociatedBundleIdentifiers: [label], ProgramArguments: [executable, 'serve'],
150
225
  WorkingDirectory: runtime, RunAtLoad: true, KeepAlive: true, ThrottleInterval: config.service_throttle_seconds,
151
226
  StandardOutPath: path.join(STATE, 'service.log'), StandardErrorPath: path.join(STATE, 'service-errors.log'),
152
227
  EnvironmentVariables: { HOME: os.homedir(), PATH: env.PATH } }));
153
228
  const domain = `gui/${process.getuid()}`;
154
- spawnSync('launchctl', ['bootout', `${domain}/${label}`]);
155
- const bootstrap = spawnSync('launchctl', ['bootstrap', domain, plist]);
156
- if (bootstrap.error || bootstrap.status !== 0) throw new Error('Cannot start OpenCodex LaunchAgent');
229
+ const stopped = spawnSync('launchctl', ['bootout', `${domain}/${label}`], { encoding: 'utf8' });
230
+ if (stopped.error || (stopped.status !== 0 && stopped.status !== 3)) {
231
+ throw new Error(`Cannot stop ${label}: ${stopped.error?.message || stopped.stderr.trim() || `launchctl exited ${stopped.status}`}`);
232
+ }
233
+ const bootstrap = spawnSync('launchctl', ['bootstrap', domain, plist], { encoding: 'utf8' });
234
+ if (bootstrap.error || bootstrap.status !== 0) {
235
+ throw new Error(`Cannot start DeepCodex LaunchAgent: ${bootstrap.error?.message || bootstrap.stderr.trim() || `launchctl exited ${bootstrap.status}`}`);
236
+ }
157
237
  const deadline = Date.now() + config.startup_timeout_seconds * 1000;
158
238
  let report;
159
239
  while (!report) {
@@ -167,18 +247,62 @@ export async function install() {
167
247
  }
168
248
  }
169
249
  if (fs.readFileSync(configPath, 'utf8') !== before) throw new Error('Codex config changed during installation; proposed config was not applied');
250
+ let plugin;
251
+ try {
252
+ plugin = installPlugin(ROOT, diagnosis.codex, env);
253
+ updated = mergeConfig(fs.readFileSync(configPath, 'utf8'), sections);
254
+ privateWrite(path.join(STATE, 'config.proposed.toml'), updated);
255
+ } catch (error) {
256
+ spawnSync('launchctl', ['bootout', `${domain}/${label}`]);
257
+ throw error;
258
+ }
170
259
  privateWrite(configPath, updated);
171
- console.log(JSON.stringify({ ...report, service: label, port: config.port, cwd: runtime, owner: 'OpenCodex LaunchAgent',
172
- native_models: nativeModels.length, subagent_model: child.slug, backup: path.join(STATE, 'config.before.toml'), restart_desktop_required: true }));
260
+ console.log(JSON.stringify({ ...report, service: label, port: config.port, cwd: runtime, owner: 'DeepCodex LaunchAgent',
261
+ native_models: nativeModels.length, subagent_model: child.slug, plugin, backup: path.join(STATE, 'config.before.toml'), restart_desktop_required: true }));
262
+ }
263
+
264
+ export function uninstall() {
265
+ if (process.platform !== 'darwin') throw new Error('The Desktop uninstaller requires macOS');
266
+ const statePath = path.join(STATE, 'state.json');
267
+ if (!fs.existsSync(statePath)) {
268
+ console.log(JSON.stringify({ status: 'not_installed' }));
269
+ return;
270
+ }
271
+ const state = readJson(statePath);
272
+ const configPath = state.configPath || path.join(process.env.CODEX_HOME || path.join(os.homedir(), '.codex'), 'config.toml');
273
+ const before = fs.readFileSync(path.join(STATE, 'config.before.toml'), 'utf8');
274
+ const proposed = fs.readFileSync(path.join(STATE, 'config.proposed.toml'), 'utf8');
275
+ const current = fs.readFileSync(configPath, 'utf8');
276
+ if (current !== proposed && current !== before) {
277
+ throw new Error('Codex configuration changed after installation; uninstall stopped without changes. Reconcile config.toml with the backup and proposed config in ' + STATE);
278
+ }
279
+ const runtime = path.join(os.homedir(), '.local/share/opencodex/runtime');
280
+ if (state.runtime !== runtime) throw new Error('Unexpected router runtime path; uninstall stopped');
281
+ const label = readJson(path.join(ROOT, 'config/desktop.json')).service_label;
282
+ if (state.config.service_label !== label) throw new Error('Unexpected router service label; uninstall stopped');
283
+ const service = `gui/${process.getuid()}/${label}`;
284
+ // Restore connectivity before stopping the router; a failed stop can be retried.
285
+ if (current !== before) privateWrite(configPath, before);
286
+ const stopped = spawnSync('launchctl', ['bootout', service]);
287
+ if (stopped.error || (stopped.status !== 0 && stopped.status !== 3)) {
288
+ throw new Error('Cannot stop DeepCodex LaunchAgent; Codex settings restored, runtime retained. Retry uninstall.');
289
+ }
290
+ fs.rmSync(path.join(os.homedir(), 'Library/LaunchAgents', label + '.plist'), { force: true });
291
+ const app = path.join(runtime, 'DeepCodex.app');
292
+ if (fs.existsSync(app)) registerServiceApp(app, '-u');
293
+ fs.rmSync(runtime, { recursive: true, force: true });
294
+ fs.rmSync(STATE, { recursive: true, force: true });
295
+ console.log(JSON.stringify({ status: 'uninstalled', restart_desktop_required: true, credentials_preserved: true }));
173
296
  }
174
297
 
175
298
  export async function main(args = process.argv.slice(2)) {
176
299
  if (args.includes('--help') || args.includes('-h')) {
177
- console.log('Usage: opencodex <install|status>\nActivate or inspect the local macOS Desktop router.');
300
+ console.log('Usage: deepcodex <install|uninstall|status>\nActivate or inspect the local macOS Desktop router.');
178
301
  return 0;
179
302
  }
180
- if (args.length !== 1 || !['install', 'serve', 'status'].includes(args[0])) throw new Error('Expected install, serve or status');
303
+ if (args.length !== 1 || !['install', 'uninstall', 'serve', 'status'].includes(args[0])) throw new Error('Expected install, uninstall, serve or status');
181
304
  if (args[0] === 'install') await install();
305
+ else if (args[0] === 'uninstall') uninstall();
182
306
  else {
183
307
  const state = readJson(path.join(STATE, 'state.json'));
184
308
  if (args[0] === 'status') console.log(JSON.stringify({ ...await health(state), port: state.config.port, cwd: state.runtime }));
@@ -223,7 +223,7 @@ export async function startPilot(config, deepseekKey, capability) {
223
223
  return;
224
224
  }
225
225
  receipt({ route: 'error', message: String(error.message).replaceAll(deepseekKey, '[REDACTED]').replaceAll(capability, '[REDACTED]') });
226
- if (!response.headersSent) response.writeHead(502, { 'content-type': 'application/json' }).end(JSON.stringify({ error: { message: 'OpenCodex request failed; inspect local receipts.' } }));
226
+ if (!response.headersSent) response.writeHead(502, { 'content-type': 'application/json' }).end(JSON.stringify({ error: { message: 'DeepCodex request failed; inspect local receipts.' } }));
227
227
  else response.destroy();
228
228
  } finally { clearTimeout(timer); }
229
229
  });
package/scripts/pilot.js CHANGED
@@ -120,7 +120,7 @@ export async function run() {
120
120
  const sourceEnv = workerEnvironment(process.env);
121
121
  loadCredentials(sourceEnv, workerConfig);
122
122
  const diagnosis = doctor(workerConfig, sourceEnv);
123
- if (diagnosis.status !== 'ready') throw new Error('OpenCodex doctor is not ready');
123
+ if (diagnosis.status !== 'ready') throw new Error('DeepCodex doctor is not ready');
124
124
  const authSource = authFile(sourceEnv);
125
125
  if (!statSync(authSource, { throwIfNoEntry: false })?.isFile()) {
126
126
  throw new Error('Pilot requires the existing Codex auth.json login');
@@ -165,7 +165,7 @@ export async function run() {
165
165
  { env, cwd: workspace, stdio: ['pipe', 'pipe', routerErrors], detached: true });
166
166
  router.stdin.end(JSON.stringify({ config, capability }));
167
167
  const ready = await readRouterReadiness(router, config.startup_timeout_seconds * 1000, signals.signal);
168
- emit({ event: 'pilot.router_started', ...ready, cwd: workspace, owner: 'OpenCodex isolated pilot',
168
+ emit({ event: 'pilot.router_started', ...ready, cwd: workspace, owner: 'DeepCodex isolated pilot',
169
169
  artifacts: runDir });
170
170
  const values = { ...workerConfig.codex };
171
171
  Object.assign(values, { model: config.parent_model, model_provider: 'opencodex-pilot',
@@ -175,7 +175,7 @@ export async function run() {
175
175
  values.features = { ...values.features, multi_agent: true, multi_agent_v2: true,
176
176
  enable_request_compression: false };
177
177
  values.model_providers = { 'opencodex-pilot': {
178
- name: 'OpenCodex isolated pilot',
178
+ name: 'DeepCodex isolated pilot',
179
179
  base_url: `http://127.0.0.1:${ready.port}`,
180
180
  wire_api: 'responses',
181
181
  requires_openai_auth: true,
package/scripts/worker.js CHANGED
@@ -99,8 +99,23 @@ export function redact(value, secret) {
99
99
  return value;
100
100
  }
101
101
 
102
- export function doctor(config, env) {
102
+ // Desktop bundles the CLI but does not necessarily expose it on PATH.
103
+ export function resolveCodex(env, { platform = process.platform, applicationDirs = [
104
+ path.join(env.HOME || os.homedir(), 'Applications'), '/Applications',
105
+ ] } = {}) {
103
106
  const binary = which('codex', env.PATH);
107
+ if (binary || platform !== 'darwin') return binary;
108
+ for (const directory of applicationDirs) {
109
+ for (const app of ['Codex.app', 'ChatGPT.app']) {
110
+ const bundled = which('codex', path.join(directory, app, 'Contents', 'Resources'));
111
+ if (bundled) return bundled;
112
+ }
113
+ }
114
+ return undefined;
115
+ }
116
+
117
+ export function doctor(config, env, discoveryOptions) {
118
+ const binary = resolveCodex(env, discoveryOptions);
104
119
  const report = { status: 'ready', codex: binary ?? null, api_key_present: Boolean(env.DEEPSEEK_API_KEY) };
105
120
  if (binary) {
106
121
  const timeout = config.limits.version_timeout_seconds * 1000;
@@ -110,7 +125,14 @@ export function doctor(config, env) {
110
125
  report.compatible_cli = version.status === 0 && help.status === 0
111
126
  && REQUIRED_CLI_FLAGS.every(flag => help.stdout.includes(flag));
112
127
  }
113
- if (!binary || !report.compatible_cli || !report.api_key_present) report.status = 'not_ready';
128
+ const errors = [];
129
+ if (!binary) errors.push('Codex CLI not found on PATH or in Codex.app/ChatGPT.app under ~/Applications or /Applications. Install Codex Desktop or add a compatible codex CLI to PATH.');
130
+ else if (!report.compatible_cli) errors.push(`Codex CLI at ${binary} is incompatible; required exec flags: ${REQUIRED_CLI_FLAGS.join(', ')}. Update Codex Desktop or the CLI on PATH.`);
131
+ if (!report.api_key_present) errors.push('DeepSeek API key is missing. Run deepcodex configure.');
132
+ if (errors.length) {
133
+ report.status = 'not_ready';
134
+ report.errors = errors;
135
+ }
114
136
  return report;
115
137
  }
116
138
 
@@ -147,7 +169,7 @@ export async function workerLock() {
147
169
  database.exec('BEGIN EXCLUSIVE');
148
170
  } catch (error) {
149
171
  database.close();
150
- if (error.errcode === SQLITE_BUSY) throw new Error('Another OpenCodex worker is running; wait for it or cancel that run');
172
+ if (error.errcode === SQLITE_BUSY) throw new Error('Another DeepCodex worker is running; wait for it or cancel that run');
151
173
  throw error;
152
174
  }
153
175
  let closed = false;
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  name: delegate-flash
3
- description: Delegate bounded coding tasks to native DeepSeek Flash subagents in Codex through OpenCodex. Use when OpenCodex or Flash delegation is requested, or when diagnosing the Desktop integration.
3
+ description: Delegate bounded coding tasks to native DeepSeek Flash subagents in Codex through DeepCodex. Use when DeepCodex or Flash delegation is requested, or when diagnosing the Desktop integration.
4
4
  ---
5
5
 
6
- # OpenCodex
6
+ # DeepCodex
7
7
 
8
8
  Keep the coordinator on the user's selected native model. DeepSeek Flash runs as
9
- an actual Codex subagent through the local OpenCodex router. Use the native
9
+ an actual Codex subagent through the local DeepCodex router. Use the native
10
10
  collaboration tools for spawning, follow-ups, waiting, and interruption; the
11
11
  normal delegation path does not launch a separate `codex exec` worker.
12
12
 
@@ -42,7 +42,7 @@ is in `config/desktop.json`, shared transport defaults in `config/pilot.json`,
42
42
  and the DeepSeek model and credential path in `config/worker.json`.
43
43
 
44
44
  Run `node <plugin>/scripts/desktop.js status` to check the local service without
45
- inference. The service is `com.opencodex.router`, bound only to loopback. Private
45
+ inference. The service is `com.deepcodex.router`, bound only to loopback. Private
46
46
  state and bounded metadata receipts live in `~/.config/opencodex/desktop`.
47
47
  Receipts contain routing and tool names, not prompts, tool arguments or tokens.
48
48
  Do not print `state.json`, provider headers, credential files or full user config.