deepcodex 0.1.1 → 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.
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +11 -14
- package/bin/deepcodex.js +5 -4
- package/package.json +1 -1
- package/scripts/desktop.js +134 -10
package/README.md
CHANGED
|
@@ -50,14 +50,6 @@ is `com.deepcodex.router`. Existing private paths (`~/.config/opencodex`,
|
|
|
50
50
|
`~/.local/share/opencodex`) and provider IDs are retained, reusing your saved
|
|
51
51
|
key and backup.
|
|
52
52
|
|
|
53
|
-
If the previous `com.opencodex.router` LaunchAgent is installed, unload it and
|
|
54
|
-
remove its plist before running `deepcodex install` to avoid competing services:
|
|
55
|
-
|
|
56
|
-
```sh
|
|
57
|
-
launchctl bootout "gui/$(id -u)/com.opencodex.router"
|
|
58
|
-
rm ~/Library/LaunchAgents/com.opencodex.router.plist
|
|
59
|
-
```
|
|
60
|
-
|
|
61
53
|
Run `deepcodex doctor` and `deepcodex install` to refresh the runtime; there is
|
|
62
54
|
no need to run `configure` again if your key is already saved.
|
|
63
55
|
|
|
@@ -100,7 +92,10 @@ result confirms local prerequisites, not provider authentication or account cred
|
|
|
100
92
|
`127.0.0.1:4207`;
|
|
101
93
|
- updates `~/.codex/config.toml` with the local provider, the subagent defaults
|
|
102
94
|
and the generated model catalog, keeping a pre-install backup at
|
|
103
|
-
`~/.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.
|
|
104
99
|
|
|
105
100
|
The installer refuses to run when an unrelated custom provider is active or when
|
|
106
101
|
an endpoint override is already configured, and it leaves your configuration
|
|
@@ -111,10 +106,11 @@ that were already open keep the provider and model catalog they started with.
|
|
|
111
106
|
|
|
112
107
|
## Plugin skill
|
|
113
108
|
|
|
114
|
-
The delegation skill lives in `skills/delegate-flash/SKILL.md`
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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.
|
|
118
114
|
|
|
119
115
|
## Commands
|
|
120
116
|
|
|
@@ -122,7 +118,8 @@ checkout is not a marketplace source by itself.
|
|
|
122
118
|
| --- | --- |
|
|
123
119
|
| `deepcodex configure` | Prompt for the DeepSeek API key and write `~/.config/opencodex/.env`. |
|
|
124
120
|
| `deepcodex doctor` | Check Codex CLI compatibility and credential presence without inference. |
|
|
125
|
-
| `deepcodex install` | Install the router runtime,
|
|
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`. |
|
|
126
123
|
| `deepcodex status` | Query the installed router health endpoint without inference. |
|
|
127
124
|
| `deepcodex run --cwd PATH --task-file PATH [--write]` | Run one bounded isolated worker ticket against DeepSeek. Read-only unless `--write` is given. |
|
|
128
125
|
| `deepcodex pilot` | Run the opt-in live native-delegation test; consumes Codex and DeepSeek usage. |
|
package/bin/deepcodex.js
CHANGED
|
@@ -7,7 +7,8 @@ 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
|
|
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)
|
|
@@ -15,8 +16,8 @@ Commands:
|
|
|
15
16
|
|
|
16
17
|
Requires macOS, Node.js >=22.15 and a compatible Codex CLI (PATH or Desktop bundle).
|
|
17
18
|
Run deepcodex configure to save the DeepSeek key in ~/.config/opencodex/.env.
|
|
18
|
-
|
|
19
|
-
|
|
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.
|
|
20
21
|
Use deepcodex <command> --help for command options.
|
|
21
22
|
`;
|
|
22
23
|
|
|
@@ -26,7 +27,7 @@ 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);
|
package/package.json
CHANGED
package/scripts/desktop.js
CHANGED
|
@@ -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 },
|
|
@@ -104,6 +176,8 @@ export async function install() {
|
|
|
104
176
|
loadCredentials(env, original);
|
|
105
177
|
const diagnosis = doctor(original, env);
|
|
106
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,7 +205,7 @@ 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' },
|
|
@@ -139,21 +214,26 @@ export async function install() {
|
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
156
|
-
|
|
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
260
|
console.log(JSON.stringify({ ...report, service: label, port: config.port, cwd: runtime, owner: 'DeepCodex LaunchAgent',
|
|
172
|
-
native_models: nativeModels.length, subagent_model: child.slug, backup: path.join(STATE, 'config.before.toml'), restart_desktop_required: true }));
|
|
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: deepcodex <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 }));
|