@ran-sh/dsh-crew 1.0.1 → 1.0.3
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 +28 -20
- package/README.zh.md +25 -18
- package/lib/client.js +336 -16
- package/official-web-bridge/lib/client.js +336 -16
- package/package.json +166 -166
- package/src/client/index.tsx +853 -716
- package/src/credential-reference.mjs +38 -0
- package/src/hub/index.mjs +890 -65
- package/src/install/install-legacy.mjs +184 -185
- package/src/install/npx-lifecycle.mjs +456 -398
- package/src/model-catalog.mjs +8 -1
- package/src/policy-legacy.mjs +2 -2
- package/src/provider-delete-adapters.mjs +1934 -451
- package/src/provider-inventory.mjs +184 -132
- package/src/provider-layer-migration-adapters.mjs +759 -0
- package/src/provider-layer-migration.mjs +198 -0
- package/src/provider-lifecycle-state.mjs +103 -95
- package/src/provider-lifecycle.mjs +252 -214
- package/src/provider-profile-store.mjs +378 -171
- package/src/provider-settings-store.mjs +291 -0
- package/src/provider-store-lock.mjs +67 -0
- package/src/runtime-identity.mjs +73 -72
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
// render Codex agent roles with real paths. Called from the CLI entry or the
|
|
3
3
|
// DSH settings page. All edits are backed up and idempotent.
|
|
4
4
|
|
|
5
|
-
import { readFileSync, writeFileSync, mkdirSync, copyFileSync, existsSync, readdirSync, rmSync, statSync } from 'node:fs';
|
|
5
|
+
import { readFileSync, writeFileSync, mkdirSync, copyFileSync, existsSync, readdirSync, rmSync, statSync } from 'node:fs';
|
|
6
6
|
import { dirname, join, resolve, relative } from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
import { homedir } from 'node:os';
|
|
9
|
-
import { normalizeModelPriority } from '../model-routing.mjs';
|
|
10
|
-
import { zcodeStatus } from './zcode.mjs';
|
|
9
|
+
import { normalizeModelPriority } from '../model-routing.mjs';
|
|
10
|
+
import { zcodeStatus } from './zcode.mjs';
|
|
11
11
|
|
|
12
12
|
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
13
13
|
const MARKETPLACE_NAME = 'dsh-crew';
|
|
14
|
-
const PLUGIN_KEY = `dsh-crew@${MARKETPLACE_NAME}`;
|
|
15
|
-
const POLICY_START = '<!-- DSH CREW MANAGED POLICY:START -->';
|
|
16
|
-
const POLICY_END = '<!-- DSH CREW MANAGED POLICY:END -->';
|
|
14
|
+
const PLUGIN_KEY = `dsh-crew@${MARKETPLACE_NAME}`;
|
|
15
|
+
const POLICY_START = '<!-- DSH CREW MANAGED POLICY:START -->';
|
|
16
|
+
const POLICY_END = '<!-- DSH CREW MANAGED POLICY:END -->';
|
|
17
17
|
// dsh_worker_config is included so the session commands (/dsh-crew:config,
|
|
18
18
|
// /dsh-config) and any orchestrator policy lookup run without an extra
|
|
19
19
|
// authorization prompt.
|
|
@@ -28,120 +28,120 @@ function backup(file) {
|
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
function readJson(file, fallback) {
|
|
32
|
-
try { return JSON.parse(readFileSync(file, 'utf8')); } catch { return fallback; }
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function readText(file) {
|
|
36
|
-
try { return readFileSync(file, 'utf8'); } catch { return null; }
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function managedPolicyBlock(root) {
|
|
40
|
-
const policy = readText(join(root, 'codex', 'AGENTS.md'))?.trim();
|
|
41
|
-
if (!policy) return null;
|
|
42
|
-
return `${POLICY_START}\n${policy}\n${POLICY_END}`;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function installGlobalCodexPolicy({ home, root }) {
|
|
46
|
-
const file = join(home, '.codex', 'AGENTS.md');
|
|
47
|
-
const block = managedPolicyBlock(root);
|
|
48
|
-
if (!block) return { ok: false, action: 'global policy template missing' };
|
|
49
|
-
mkdirSync(dirname(file), { recursive: true });
|
|
50
|
-
const current = readText(file) ?? '';
|
|
51
|
-
const managed = new RegExp(`${POLICY_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?${POLICY_END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 'm');
|
|
52
|
-
const template = readText(join(root, 'codex', 'AGENTS.md'))?.trim() ?? '';
|
|
53
|
-
let next;
|
|
54
|
-
if (managed.test(current)) next = current.replace(managed, block);
|
|
55
|
-
else if (current.trim() === template) next = `${block}\n`;
|
|
56
|
-
else next = `${current.trimEnd()}${current.trim() ? '\n\n' : ''}${block}\n`;
|
|
57
|
-
if (next !== current) {
|
|
58
|
-
backup(file);
|
|
59
|
-
writeFileSync(file, next);
|
|
60
|
-
}
|
|
61
|
-
return { ok: true, action: `global policy: ${file}` };
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function globalCodexPolicyReady({ home, root = ROOT }) {
|
|
65
|
-
const block = managedPolicyBlock(root);
|
|
66
|
-
const text = readText(join(home, '.codex', 'AGENTS.md'));
|
|
67
|
-
return !!block && typeof text === 'string' && text.includes(block);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function uninstallGlobalCodexPolicy({ home }) {
|
|
71
|
-
const file = join(home, '.codex', 'AGENTS.md');
|
|
72
|
-
const current = readText(file);
|
|
73
|
-
if (typeof current !== 'string') return null;
|
|
74
|
-
const managed = new RegExp(`(?:\\r?\\n){0,2}${POLICY_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?${POLICY_END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?:\\r?\\n)?`, 'm');
|
|
75
|
-
if (!managed.test(current)) return null;
|
|
76
|
-
const next = current.replace(managed, '').trimEnd();
|
|
77
|
-
backup(file);
|
|
78
|
-
if (next.trim()) writeFileSync(file, `${next}\n`);
|
|
79
|
-
else rmSync(file);
|
|
80
|
-
return `codex global policy: removed managed block`;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function tomlSection(text, name) {
|
|
84
|
-
if (typeof text !== 'string') return null;
|
|
85
|
-
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
86
|
-
const header = new RegExp(`^\\s*\\[${escaped}\\]\\s*(?:#.*)?$`, 'm').exec(text);
|
|
87
|
-
if (!header) return null;
|
|
88
|
-
const rest = text.slice(header.index + header[0].length);
|
|
89
|
-
const nextHeader = rest.search(/^\s*\[[^\]\r\n]+\]\s*(?:#.*)?$/m);
|
|
90
|
-
return nextHeader === -1 ? rest : rest.slice(0, nextHeader);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function existingServerTarget(block) {
|
|
94
|
-
if (typeof block !== 'string' || !/^\s*command\s*=\s*"node"\s*$/m.test(block)) return null;
|
|
95
|
-
const match = /^\s*args\s*=\s*\[\s*"([^"\r\n]*server\.mjs)"\s*\]\s*$/m.exec(block);
|
|
96
|
-
if (!match) return null;
|
|
97
|
-
try {
|
|
98
|
-
if (!statSync(match[1]).isFile()) return null;
|
|
99
|
-
const target = resolve(match[1]);
|
|
100
|
-
return process.platform === 'win32' ? target.toLowerCase() : target;
|
|
101
|
-
} catch { return null; }
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function codexRoleTarget(file, expectedName) {
|
|
105
|
-
const text = readText(file);
|
|
106
|
-
if (typeof text !== 'string'
|
|
107
|
-
|| !new RegExp(`^\\s*name\\s*=\\s*"${expectedName}"\\s*$`, 'm').test(text)) return null;
|
|
108
|
-
return existingServerTarget(tomlSection(text, 'mcp_servers.dsh-crew'));
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function codexMcpTarget(configText) {
|
|
112
|
-
const section = tomlSection(configText, 'mcp_servers');
|
|
113
|
-
if (!section) return null;
|
|
114
|
-
const entry = /^\s*dsh-crew\s*=\s*\{\s*command\s*=\s*"node"\s*,\s*args\s*=\s*\[\s*"([^"\r\n]*server\.mjs)"\s*\]\s*\}\s*(?:#.*)?$/m.exec(section);
|
|
115
|
-
if (!entry) return null;
|
|
116
|
-
try {
|
|
117
|
-
if (!statSync(entry[1]).isFile()) return null;
|
|
118
|
-
const target = resolve(entry[1]);
|
|
119
|
-
return process.platform === 'win32' ? target.toLowerCase() : target;
|
|
120
|
-
} catch { return null; }
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function isFile(file) {
|
|
124
|
-
try { return statSync(file).isFile(); } catch { return false; }
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function claudePluginRootReady(root) {
|
|
128
|
-
return typeof root === 'string' && root.trim() !== ''
|
|
129
|
-
&& isFile(join(root, '.claude-plugin', 'plugin.json'))
|
|
130
|
-
&& isFile(join(root, '.mcp.json'))
|
|
131
|
-
&& isFile(join(root, 'src', 'server.mjs'));
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function claudeSnapshotReady(home) {
|
|
135
|
-
const installed = readJson(join(home, '.claude', 'plugins', 'installed_plugins.json'), {});
|
|
136
|
-
const record = installed?.plugins?.[PLUGIN_KEY];
|
|
137
|
-
const entries = Array.isArray(record) ? record : [record];
|
|
138
|
-
return entries.some((entry) => claudePluginRootReady(entry?.installPath));
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function claudePermissionsReady(settings) {
|
|
142
|
-
const allowed = Array.isArray(settings?.permissions?.allow) ? settings.permissions.allow : [];
|
|
143
|
-
return MCP_TOOLS.every((tool) => allowed.includes(`mcp__plugin_dsh-crew_dsh-crew__${tool}`));
|
|
144
|
-
}
|
|
31
|
+
function readJson(file, fallback) {
|
|
32
|
+
try { return JSON.parse(readFileSync(file, 'utf8')); } catch { return fallback; }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function readText(file) {
|
|
36
|
+
try { return readFileSync(file, 'utf8'); } catch { return null; }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function managedPolicyBlock(root) {
|
|
40
|
+
const policy = readText(join(root, 'codex', 'AGENTS.md'))?.trim();
|
|
41
|
+
if (!policy) return null;
|
|
42
|
+
return `${POLICY_START}\n${policy}\n${POLICY_END}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function installGlobalCodexPolicy({ home, root }) {
|
|
46
|
+
const file = join(home, '.codex', 'AGENTS.md');
|
|
47
|
+
const block = managedPolicyBlock(root);
|
|
48
|
+
if (!block) return { ok: false, action: 'global policy template missing' };
|
|
49
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
50
|
+
const current = readText(file) ?? '';
|
|
51
|
+
const managed = new RegExp(`${POLICY_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?${POLICY_END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 'm');
|
|
52
|
+
const template = readText(join(root, 'codex', 'AGENTS.md'))?.trim() ?? '';
|
|
53
|
+
let next;
|
|
54
|
+
if (managed.test(current)) next = current.replace(managed, block);
|
|
55
|
+
else if (current.trim() === template) next = `${block}\n`;
|
|
56
|
+
else next = `${current.trimEnd()}${current.trim() ? '\n\n' : ''}${block}\n`;
|
|
57
|
+
if (next !== current) {
|
|
58
|
+
backup(file);
|
|
59
|
+
writeFileSync(file, next);
|
|
60
|
+
}
|
|
61
|
+
return { ok: true, action: `global policy: ${file}` };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function globalCodexPolicyReady({ home, root = ROOT }) {
|
|
65
|
+
const block = managedPolicyBlock(root);
|
|
66
|
+
const text = readText(join(home, '.codex', 'AGENTS.md'));
|
|
67
|
+
return !!block && typeof text === 'string' && text.includes(block);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function uninstallGlobalCodexPolicy({ home }) {
|
|
71
|
+
const file = join(home, '.codex', 'AGENTS.md');
|
|
72
|
+
const current = readText(file);
|
|
73
|
+
if (typeof current !== 'string') return null;
|
|
74
|
+
const managed = new RegExp(`(?:\\r?\\n){0,2}${POLICY_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?${POLICY_END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?:\\r?\\n)?`, 'm');
|
|
75
|
+
if (!managed.test(current)) return null;
|
|
76
|
+
const next = current.replace(managed, '').trimEnd();
|
|
77
|
+
backup(file);
|
|
78
|
+
if (next.trim()) writeFileSync(file, `${next}\n`);
|
|
79
|
+
else rmSync(file);
|
|
80
|
+
return `codex global policy: removed managed block`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function tomlSection(text, name) {
|
|
84
|
+
if (typeof text !== 'string') return null;
|
|
85
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
86
|
+
const header = new RegExp(`^\\s*\\[${escaped}\\]\\s*(?:#.*)?$`, 'm').exec(text);
|
|
87
|
+
if (!header) return null;
|
|
88
|
+
const rest = text.slice(header.index + header[0].length);
|
|
89
|
+
const nextHeader = rest.search(/^\s*\[[^\]\r\n]+\]\s*(?:#.*)?$/m);
|
|
90
|
+
return nextHeader === -1 ? rest : rest.slice(0, nextHeader);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function existingServerTarget(block) {
|
|
94
|
+
if (typeof block !== 'string' || !/^\s*command\s*=\s*"node"\s*$/m.test(block)) return null;
|
|
95
|
+
const match = /^\s*args\s*=\s*\[\s*"([^"\r\n]*server\.mjs)"\s*\]\s*$/m.exec(block);
|
|
96
|
+
if (!match) return null;
|
|
97
|
+
try {
|
|
98
|
+
if (!statSync(match[1]).isFile()) return null;
|
|
99
|
+
const target = resolve(match[1]);
|
|
100
|
+
return process.platform === 'win32' ? target.toLowerCase() : target;
|
|
101
|
+
} catch { return null; }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function codexRoleTarget(file, expectedName) {
|
|
105
|
+
const text = readText(file);
|
|
106
|
+
if (typeof text !== 'string'
|
|
107
|
+
|| !new RegExp(`^\\s*name\\s*=\\s*"${expectedName}"\\s*$`, 'm').test(text)) return null;
|
|
108
|
+
return existingServerTarget(tomlSection(text, 'mcp_servers.dsh-crew'));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function codexMcpTarget(configText) {
|
|
112
|
+
const section = tomlSection(configText, 'mcp_servers');
|
|
113
|
+
if (!section) return null;
|
|
114
|
+
const entry = /^\s*dsh-crew\s*=\s*\{\s*command\s*=\s*"node"\s*,\s*args\s*=\s*\[\s*"([^"\r\n]*server\.mjs)"\s*\]\s*\}\s*(?:#.*)?$/m.exec(section);
|
|
115
|
+
if (!entry) return null;
|
|
116
|
+
try {
|
|
117
|
+
if (!statSync(entry[1]).isFile()) return null;
|
|
118
|
+
const target = resolve(entry[1]);
|
|
119
|
+
return process.platform === 'win32' ? target.toLowerCase() : target;
|
|
120
|
+
} catch { return null; }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function isFile(file) {
|
|
124
|
+
try { return statSync(file).isFile(); } catch { return false; }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function claudePluginRootReady(root) {
|
|
128
|
+
return typeof root === 'string' && root.trim() !== ''
|
|
129
|
+
&& isFile(join(root, '.claude-plugin', 'plugin.json'))
|
|
130
|
+
&& isFile(join(root, '.mcp.json'))
|
|
131
|
+
&& isFile(join(root, 'src', 'server.mjs'));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function claudeSnapshotReady(home) {
|
|
135
|
+
const installed = readJson(join(home, '.claude', 'plugins', 'installed_plugins.json'), {});
|
|
136
|
+
const record = installed?.plugins?.[PLUGIN_KEY];
|
|
137
|
+
const entries = Array.isArray(record) ? record : [record];
|
|
138
|
+
return entries.some((entry) => claudePluginRootReady(entry?.installPath));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function claudePermissionsReady(settings) {
|
|
142
|
+
const allowed = Array.isArray(settings?.permissions?.allow) ? settings.permissions.allow : [];
|
|
143
|
+
return MCP_TOOLS.every((tool) => allowed.includes(`mcp__plugin_dsh-crew_dsh-crew__${tool}`));
|
|
144
|
+
}
|
|
145
145
|
|
|
146
146
|
// One-time migration from the pre-rename config dir (dsh-workers → dsh-crew).
|
|
147
147
|
try {
|
|
@@ -186,12 +186,11 @@ export const GLOBAL_CONFIG_DEFAULTS = {
|
|
|
186
186
|
// this on; balanced/custom only when this flag is set).
|
|
187
187
|
pro_reviews_flash: false,
|
|
188
188
|
// Worker provider routing for HUB workers: which DSH provider backs each
|
|
189
|
-
// worker session.
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
// kept the default so upgraded configs never silently switch providers).
|
|
189
|
+
// worker session. Fresh installs stay on the built-in DeepSeek provider;
|
|
190
|
+
// follow-dsh is an explicit opt-in that uses the provider selected in DSH
|
|
191
|
+
// Models (Flash/Pro still map to the selected Harness models).
|
|
193
192
|
// Standalone mode always uses deepseek-official + DEEPSEEK_API_KEY.
|
|
194
|
-
worker_provider_mode: '
|
|
193
|
+
worker_provider_mode: 'deepseek-official',
|
|
195
194
|
// Ordered Harness provider/model selections. The configured flags preserve
|
|
196
195
|
// the distinction between a fresh recommendation and a user-cleared list.
|
|
197
196
|
flash_model_priority: [],
|
|
@@ -208,9 +207,9 @@ export const GLOBAL_CONFIG_DEFAULTS = {
|
|
|
208
207
|
imagegen_enabled: false,
|
|
209
208
|
// Multimodal bridge: which subscription CLI lends the text-only DS model
|
|
210
209
|
// eyes (describe_image) and a brush (generate_image).
|
|
211
|
-
vision_provider: '
|
|
210
|
+
vision_provider: 'off', // claude-code | codex | grok | agy | <custom id> | off
|
|
212
211
|
vision_model: 'haiku', // model passed to the vision CLI; free-form
|
|
213
|
-
imagegen_provider: '
|
|
212
|
+
imagegen_provider: 'off', // codex | agy | grok | <custom id> | off
|
|
214
213
|
// User-defined providers, each either an OpenAI-compatible HTTP endpoint or a
|
|
215
214
|
// local command. Entry shape:
|
|
216
215
|
// { id, name, type: 'api' | 'cli', models: [],
|
|
@@ -303,52 +302,52 @@ export function writeGlobalConfig(patch) {
|
|
|
303
302
|
}
|
|
304
303
|
|
|
305
304
|
/** What is currently installed where — drives the settings-page buttons. */
|
|
306
|
-
export function installStatus({ home = homedir(), root } = {}) {
|
|
307
|
-
const settings = readJson(join(home, '.claude', 'settings.json'), {});
|
|
308
|
-
const enabled = settings.enabledPlugins;
|
|
309
|
-
const claudeInstalled = !!(enabled && !Array.isArray(enabled) && enabled[PLUGIN_KEY]);
|
|
310
|
-
const hudWired = typeof settings.statusLine?.command === 'string'
|
|
311
|
-
&& settings.statusLine.command.includes('worker-segment.sh');
|
|
312
|
-
const claudeComponents = {
|
|
313
|
-
enabled: claudeInstalled,
|
|
314
|
-
marketplace: claudePluginRootReady(settings?.extraKnownMarketplaces?.[MARKETPLACE_NAME]?.source?.path),
|
|
315
|
-
snapshot: claudeSnapshotReady(home),
|
|
316
|
-
permissions: claudePermissionsReady(settings),
|
|
317
|
-
};
|
|
318
|
-
const claudeMissing = Object.entries(claudeComponents).filter(([, present]) => !present).map(([key]) => key);
|
|
319
|
-
const codexRoot = join(home, '.codex');
|
|
320
|
-
const configFile = join(codexRoot, 'config.toml');
|
|
321
|
-
const configText = readText(configFile) ?? '';
|
|
322
|
-
const workerTarget = codexRoleTarget(join(codexRoot, 'agents', 'ds-worker.toml'), 'ds-worker');
|
|
323
|
-
const reviewerTarget = codexRoleTarget(join(codexRoot, 'agents', 'ds-reviewer.toml'), 'ds-reviewer');
|
|
324
|
-
const mcpTarget = codexMcpTarget(configText);
|
|
325
|
-
const components = {
|
|
326
|
-
worker_role: !!workerTarget,
|
|
327
|
-
reviewer_role: !!reviewerTarget,
|
|
328
|
-
config_prompt: !!readText(join(codexRoot, 'prompts', 'dsh-config.md'))?.trim(),
|
|
329
|
-
status_prompt: !!readText(join(codexRoot, 'prompts', 'dsh-status.md'))?.trim(),
|
|
330
|
-
mcp: !!mcpTarget,
|
|
331
|
-
target_alignment: !!workerTarget && workerTarget === reviewerTarget && workerTarget === mcpTarget,
|
|
332
|
-
global_policy: globalCodexPolicyReady({ home }),
|
|
333
|
-
};
|
|
334
|
-
const codexInstalled = Object.values(components).some(Boolean)
|
|
335
|
-
|| existsSync(join(codexRoot, 'agents', 'ds-flash.toml'))
|
|
336
|
-
|| existsSync(join(codexRoot, 'agents', 'ds-pro.toml'));
|
|
337
|
-
const missing = Object.entries(components).filter(([, present]) => !present).map(([key]) => key);
|
|
338
|
-
return {
|
|
339
|
-
claude: {
|
|
340
|
-
installed: claudeInstalled,
|
|
341
|
-
ready: claudeMissing.length === 0,
|
|
342
|
-
hud: hudWired,
|
|
343
|
-
components: claudeComponents,
|
|
344
|
-
missing: claudeMissing,
|
|
345
|
-
},
|
|
346
|
-
codex: { installed: codexInstalled, ready: missing.length === 0, components, missing },
|
|
347
|
-
zcode: zcodeStatus({ home, ...(root ? { root } : {}) }),
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
export function uninstallCodex({ home = homedir() } = {}) {
|
|
305
|
+
export function installStatus({ home = homedir(), root } = {}) {
|
|
306
|
+
const settings = readJson(join(home, '.claude', 'settings.json'), {});
|
|
307
|
+
const enabled = settings.enabledPlugins;
|
|
308
|
+
const claudeInstalled = !!(enabled && !Array.isArray(enabled) && enabled[PLUGIN_KEY]);
|
|
309
|
+
const hudWired = typeof settings.statusLine?.command === 'string'
|
|
310
|
+
&& settings.statusLine.command.includes('worker-segment.sh');
|
|
311
|
+
const claudeComponents = {
|
|
312
|
+
enabled: claudeInstalled,
|
|
313
|
+
marketplace: claudePluginRootReady(settings?.extraKnownMarketplaces?.[MARKETPLACE_NAME]?.source?.path),
|
|
314
|
+
snapshot: claudeSnapshotReady(home),
|
|
315
|
+
permissions: claudePermissionsReady(settings),
|
|
316
|
+
};
|
|
317
|
+
const claudeMissing = Object.entries(claudeComponents).filter(([, present]) => !present).map(([key]) => key);
|
|
318
|
+
const codexRoot = join(home, '.codex');
|
|
319
|
+
const configFile = join(codexRoot, 'config.toml');
|
|
320
|
+
const configText = readText(configFile) ?? '';
|
|
321
|
+
const workerTarget = codexRoleTarget(join(codexRoot, 'agents', 'ds-worker.toml'), 'ds-worker');
|
|
322
|
+
const reviewerTarget = codexRoleTarget(join(codexRoot, 'agents', 'ds-reviewer.toml'), 'ds-reviewer');
|
|
323
|
+
const mcpTarget = codexMcpTarget(configText);
|
|
324
|
+
const components = {
|
|
325
|
+
worker_role: !!workerTarget,
|
|
326
|
+
reviewer_role: !!reviewerTarget,
|
|
327
|
+
config_prompt: !!readText(join(codexRoot, 'prompts', 'dsh-config.md'))?.trim(),
|
|
328
|
+
status_prompt: !!readText(join(codexRoot, 'prompts', 'dsh-status.md'))?.trim(),
|
|
329
|
+
mcp: !!mcpTarget,
|
|
330
|
+
target_alignment: !!workerTarget && workerTarget === reviewerTarget && workerTarget === mcpTarget,
|
|
331
|
+
global_policy: globalCodexPolicyReady({ home }),
|
|
332
|
+
};
|
|
333
|
+
const codexInstalled = Object.values(components).some(Boolean)
|
|
334
|
+
|| existsSync(join(codexRoot, 'agents', 'ds-flash.toml'))
|
|
335
|
+
|| existsSync(join(codexRoot, 'agents', 'ds-pro.toml'));
|
|
336
|
+
const missing = Object.entries(components).filter(([, present]) => !present).map(([key]) => key);
|
|
337
|
+
return {
|
|
338
|
+
claude: {
|
|
339
|
+
installed: claudeInstalled,
|
|
340
|
+
ready: claudeMissing.length === 0,
|
|
341
|
+
hud: hudWired,
|
|
342
|
+
components: claudeComponents,
|
|
343
|
+
missing: claudeMissing,
|
|
344
|
+
},
|
|
345
|
+
codex: { installed: codexInstalled, ready: missing.length === 0, components, missing },
|
|
346
|
+
zcode: zcodeStatus({ home, ...(root ? { root } : {}) }),
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export function uninstallCodex({ home = homedir() } = {}) {
|
|
352
351
|
const actions = [];
|
|
353
352
|
// Both the v0.2 roles (ds-worker / ds-reviewer) and the deprecated v0.1
|
|
354
353
|
// aliases (ds-flash / ds-pro) are dsh-crew managed; uninstall removes only
|
|
@@ -357,12 +356,12 @@ export function uninstallCodex({ home = homedir() } = {}) {
|
|
|
357
356
|
const p = join(home, '.codex', 'agents', f);
|
|
358
357
|
if (existsSync(p)) { backup(p); rmSync(p); actions.push(`removed: ${p} (backup kept)`); }
|
|
359
358
|
}
|
|
360
|
-
for (const f of ['dsh-config.md', 'dsh-status.md']) {
|
|
359
|
+
for (const f of ['dsh-config.md', 'dsh-status.md']) {
|
|
361
360
|
const p = join(home, '.codex', 'prompts', f);
|
|
362
361
|
if (existsSync(p)) { rmSync(p); actions.push(`removed: ${p}`); }
|
|
363
|
-
}
|
|
364
|
-
const policyAction = uninstallGlobalCodexPolicy({ home });
|
|
365
|
-
if (policyAction) actions.push(policyAction);
|
|
362
|
+
}
|
|
363
|
+
const policyAction = uninstallGlobalCodexPolicy({ home });
|
|
364
|
+
if (policyAction) actions.push(policyAction);
|
|
366
365
|
// Remove only the dsh-crew entry from [mcp_servers], keeping any other
|
|
367
366
|
// MCP servers the user configured.
|
|
368
367
|
const configFile = join(home, '.codex', 'config.toml');
|
|
@@ -479,7 +478,7 @@ export async function installClaudeCode({ home = homedir(), statusline = false,
|
|
|
479
478
|
return { ok: true, actions };
|
|
480
479
|
}
|
|
481
480
|
|
|
482
|
-
export function installCodex({ home = homedir(), scope, root = ROOT } = {}) {
|
|
481
|
+
export function installCodex({ home = homedir(), scope, root = ROOT } = {}) {
|
|
483
482
|
const actions = [];
|
|
484
483
|
const agentsDir = scope === 'project' ? join(process.cwd(), '.codex', 'agents') : join(home, '.codex', 'agents');
|
|
485
484
|
mkdirSync(agentsDir, { recursive: true });
|
|
@@ -506,13 +505,13 @@ export function installCodex({ home = homedir(), scope, root = ROOT } = {}) {
|
|
|
506
505
|
writeFileSync(join(promptsDir, f), readFileSync(join(promptsSrc, f), 'utf8'));
|
|
507
506
|
actions.push(`prompt: ${join(promptsDir, f)}`);
|
|
508
507
|
}
|
|
509
|
-
if (scope !== 'project') {
|
|
510
|
-
const act = writeGlobalCodexMcpServer(home, renderedPath);
|
|
511
|
-
actions.push(...act);
|
|
512
|
-
const policy = installGlobalCodexPolicy({ home, root });
|
|
513
|
-
if (!policy.ok) return { ok: false, actions: [...actions, policy.action] };
|
|
514
|
-
actions.push(policy.action);
|
|
515
|
-
}
|
|
508
|
+
if (scope !== 'project') {
|
|
509
|
+
const act = writeGlobalCodexMcpServer(home, renderedPath);
|
|
510
|
+
actions.push(...act);
|
|
511
|
+
const policy = installGlobalCodexPolicy({ home, root });
|
|
512
|
+
if (!policy.ok) return { ok: false, actions: [...actions, policy.action] };
|
|
513
|
+
actions.push(policy.action);
|
|
514
|
+
}
|
|
516
515
|
return { ok: true, actions };
|
|
517
516
|
}
|
|
518
517
|
|