@ran-sh/dsh-crew 2.2.0 → 2.2.1
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/.claude-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/scripts/setup.mjs +6 -27
- package/scripts/verify-npm-install.mjs +2 -2
- package/src/adaptive-routing.mjs +2 -2
- package/src/ci-evidence.mjs +1 -1
- package/src/client/quick-panel.tsx +0 -1
- package/src/credential-lifecycle.mjs +0 -3
- package/src/credential-purge-state.mjs +1 -5
- package/src/credential-reference.mjs +0 -8
- package/src/crew-workspaces.mjs +1 -1
- package/src/delivery.mjs +2 -2
- package/src/dsh-cli-runtime.mjs +18 -22
- package/src/extension-contract.mjs +1 -1
- package/src/failure-classification.mjs +0 -10
- package/src/hub/entry.mjs +1 -1
- package/src/hub/index.mjs +0 -1
- package/src/i18n.mjs +0 -2
- package/src/install/crew-skill.mjs +1 -1
- package/src/install/install-legacy.mjs +3 -3
- package/src/install/npx-lifecycle.mjs +60 -112
- package/src/install/official-web.mjs +1 -1
- package/src/install/windows-startup.mjs +5 -5
- package/src/install/windows-supervisor-adapter.mjs +3 -2
- package/src/install/windows-supervisor-lifecycle.mjs +2 -2
- package/src/install/zcode.mjs +0 -2
- package/src/job-contracts.mjs +1 -1
- package/src/job-identity.mjs +2 -2
- package/src/jobs.mjs +3 -4
- package/src/model-routing.mjs +3 -4
- package/src/model-schedule.mjs +0 -1
- package/src/policy-legacy.mjs +10 -12
- package/src/provider-inventory.mjs +1 -1
- package/src/provider-layer-migration.mjs +0 -2
- package/src/readiness-matrix.mjs +1 -1
- package/src/release-in-use.mjs +1 -1
- package/src/role-profiles.mjs +2 -2
- package/src/runtime-controls.mjs +1 -1
- package/src/runtime-identity.mjs +1 -1
- package/src/status-shard.mjs +1 -1
- package/src/supervisor/restart-request.mjs +4 -4
- package/src/workflow-runtime.mjs +1 -2
- package/src/workspace-context.mjs +2 -2
- package/src/workspace-isolation.mjs +4 -4
- package/src/workspace-lock.mjs +2 -3
- package/scripts/policy-probe.mjs +0 -101
- package/scripts/verify-crew-ui-polish.mjs +0 -145
- package/scripts/verify-history-ui.mjs +0 -98
- package/scripts/verify-installer-fix.mjs +0 -26
package/package.json
CHANGED
package/scripts/setup.mjs
CHANGED
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
ensureCrewDshRuntime,
|
|
17
17
|
ensureCrewPluginRegistration,
|
|
18
18
|
resolveDshCli,
|
|
19
|
-
runResolvedDsh,
|
|
20
19
|
describeDshCli,
|
|
21
20
|
removeCrewPluginRegistration,
|
|
22
21
|
TARGET_DSH_VERSION,
|
|
@@ -81,11 +80,12 @@ function run(cmd, args, opts = {}) {
|
|
|
81
80
|
* may use `npx -y` to fetch DSH, while status/uninstall probes never trigger a
|
|
82
81
|
* network download merely by checking availability.
|
|
83
82
|
*
|
|
84
|
-
* The returned descriptor is the CLI base only
|
|
85
|
-
* `
|
|
86
|
-
*
|
|
83
|
+
* The returned descriptor is the CLI base only, reported to the operator as
|
|
84
|
+
* `description`; the dedicated dsh-crew profile and the Crew-owned DSH_HOME
|
|
85
|
+
* are supplied by the install flow. The official ``web`` profile is never a
|
|
86
|
+
* target.
|
|
87
87
|
*/
|
|
88
|
-
|
|
88
|
+
function detectDsh({ allowDownload = false, home = homedir(), includeCompatibility = true } = {}) {
|
|
89
89
|
const resolved = resolveDshCli({
|
|
90
90
|
home,
|
|
91
91
|
allowDownload,
|
|
@@ -95,18 +95,6 @@ export function detectDsh({ allowDownload = false, home = homedir(), includeComp
|
|
|
95
95
|
return { ...resolved, cli: resolved.command, description: describeDshCli(resolved) };
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
/**
|
|
99
|
-
* Run a DSH plugin command against the dedicated Crew profile under the
|
|
100
|
-
* Crew-owned DSH_HOME. `home` is the "user home" base used to derive that
|
|
101
|
-
* isolated home, so tests can point it at a disposable root.
|
|
102
|
-
*/
|
|
103
|
-
export function runDsh(dsh, args, { home = homedir() } = {}) {
|
|
104
|
-
const resolved = dsh?.command
|
|
105
|
-
? dsh
|
|
106
|
-
: { kind: dsh?.kind ?? 'legacy', command: dsh?.cli ?? dsh, args: [] };
|
|
107
|
-
return runResolvedDsh(resolved, ['plugin', '--profile', CREW_PROFILE_NAME, ...args], { home });
|
|
108
|
-
}
|
|
109
|
-
|
|
110
98
|
export function readPackageName(root = ROOT) {
|
|
111
99
|
try { return JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).name; } catch { return null; }
|
|
112
100
|
}
|
|
@@ -117,20 +105,11 @@ export function checkRoot(root = ROOT) {
|
|
|
117
105
|
&& existsSync(join(root, 'cordis.patch.yml'));
|
|
118
106
|
}
|
|
119
107
|
|
|
120
|
-
|
|
108
|
+
function depsPresent(root = ROOT) {
|
|
121
109
|
return existsSync(join(root, 'node_modules', '@modelcontextprotocol', 'sdk'))
|
|
122
110
|
&& existsSync(join(root, 'node_modules', '.bin', 'tsdown'));
|
|
123
111
|
}
|
|
124
112
|
|
|
125
|
-
export function profileHasPackage(home, name, profileRoot = crewProfileDir({ home })) {
|
|
126
|
-
const pkgFile = join(profileRoot, 'package.json');
|
|
127
|
-
if (!existsSync(pkgFile)) return false;
|
|
128
|
-
try {
|
|
129
|
-
const pkg = JSON.parse(readFileSync(pkgFile, 'utf8'));
|
|
130
|
-
return Boolean(pkg.dependencies?.[name]) || Boolean((pkg.dsh?.profile?.bundles ?? []).includes(name));
|
|
131
|
-
} catch { return false; }
|
|
132
|
-
}
|
|
133
|
-
|
|
134
113
|
function mark(log, ok, text) {
|
|
135
114
|
log(`${ok ? '✓' : '✗'} ${text}`);
|
|
136
115
|
return ok;
|
|
@@ -24,7 +24,7 @@ const DSH_PACKAGE = '@deepseek-ai/dsh';
|
|
|
24
24
|
const DSH_PREFIX = '@deepseek-ai/dsh-';
|
|
25
25
|
const MAX_DIRECT_PEERS = 64;
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
function resolveNpmInvocation({
|
|
28
28
|
platform = process.platform,
|
|
29
29
|
nodePath = process.execPath,
|
|
30
30
|
fileExists = existsSync,
|
|
@@ -144,7 +144,7 @@ function manifestUrl(registryUrl, name, version) {
|
|
|
144
144
|
return `${registryUrl.replace(/\/+$/, '')}/${encodedName}/${encodeURIComponent(version)}`;
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
|
|
147
|
+
async function fetchPublicManifest(name, version, {
|
|
148
148
|
registryUrl = registry,
|
|
149
149
|
fetchImpl = globalThis.fetch,
|
|
150
150
|
} = {}) {
|
package/src/adaptive-routing.mjs
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
// caller may record only outcomes Crew already observed, and routing decisions
|
|
6
6
|
// expose bounded evidence suitable for model-selection trace metadata.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
const ADAPTIVE_ROUTING_VERSION = 1;
|
|
9
|
+
const ADAPTIVE_ROUTING_DEFAULTS = Object.freeze({
|
|
10
10
|
enabled: false,
|
|
11
11
|
window_size: 8,
|
|
12
12
|
min_samples: 2,
|
package/src/ci-evidence.mjs
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
// one; nothing here reads credentials from disk, and the value is never logged,
|
|
30
30
|
// returned, or included in an evidence record.
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
const CI_EVIDENCE_REPO = 'Ran-sh/dsh-crew';
|
|
33
33
|
|
|
34
34
|
// The row each CI job validates. A platform with no job in the workflow cannot
|
|
35
35
|
// be evidenced by any run, so `macos_smoke` is deliberately absent rather than
|
|
@@ -9,7 +9,6 @@ import { PanelHeader, PanelStyles } from './panel-chrome';
|
|
|
9
9
|
// (/_dsh/dsh-crew/quick-config, /quick-status). Runtime maintenance stays on 3210.
|
|
10
10
|
|
|
11
11
|
const API = '/_dsh/dsh-crew';
|
|
12
|
-
const CREW_CONTROL_PLANE_URL = 'http://127.0.0.1:3210/';
|
|
13
12
|
const FULL = 'http://127.0.0.1:3210/';
|
|
14
13
|
|
|
15
14
|
type ModelEntry = { provider: string; model: string };
|
|
@@ -4,9 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
import { createHash, randomUUID } from 'node:crypto';
|
|
6
6
|
|
|
7
|
-
export const CREDENTIAL_PURGE_STATES = Object.freeze([
|
|
8
|
-
'PLANNED', 'REFERENCE_RECHECKED', 'PURGE_PENDING_CONFIRMATION', 'PURGED', 'VERIFIED', 'FAILED',
|
|
9
|
-
]);
|
|
10
7
|
const ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,191}$/;
|
|
11
8
|
const REVISION_PATTERN = /^[a-f0-9]{64}$/;
|
|
12
9
|
|
|
@@ -36,7 +36,7 @@ export function markCredentialPurged(input, plan) {
|
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
function markCredentialPurgeUnverified(input, plan) {
|
|
40
40
|
const state = normalizeCredentialPurgeState(input);
|
|
41
41
|
if (!validId(plan?.reference_id) || !validId(plan?.plan_id)) return state;
|
|
42
42
|
const purged = { ...state.purged };
|
|
@@ -53,7 +53,3 @@ export function recordCredentialPurgeOutcome(input, plan, result) {
|
|
|
53
53
|
if (result?.state === 'PURGED') return markCredentialPurgeUnverified(input, plan);
|
|
54
54
|
return normalizeCredentialPurgeState(input);
|
|
55
55
|
}
|
|
56
|
-
|
|
57
|
-
export function isCredentialPurged(input, referenceId) {
|
|
58
|
-
return Object.prototype.hasOwnProperty.call(normalizeCredentialPurgeState(input).purged, referenceId);
|
|
59
|
-
}
|
|
@@ -28,11 +28,3 @@ export function classifyCredentialReference(value, { kind = 'env' } = {}) {
|
|
|
28
28
|
: HANDLE_REFERENCE.test(raw) && !SECRET_LIKE.test(raw) && !EMBEDDED_SECRET.test(raw);
|
|
29
29
|
return { present: true, value: valid ? raw : null, redacted: !valid };
|
|
30
30
|
}
|
|
31
|
-
|
|
32
|
-
export function sanitizeCredentialReference(value, options = {}) {
|
|
33
|
-
return classifyCredentialReference(value, options).value;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function hasCredentialReference(value, options = {}) {
|
|
37
|
-
return classifyCredentialReference(value, options).present;
|
|
38
|
-
}
|
package/src/crew-workspaces.mjs
CHANGED
|
@@ -32,7 +32,7 @@ import { acquireWorkspaceLock, DEFAULT_LOCK_WAIT_MS } from './workspace-lock.mjs
|
|
|
32
32
|
|
|
33
33
|
export const CREW_WORKSPACE_WORKER = 'dsh-crew-worker';
|
|
34
34
|
export const CREW_WORKSPACE_REVIEW = 'dsh-crew-review';
|
|
35
|
-
|
|
35
|
+
const CREW_WORKSPACE_NAMES = Object.freeze([CREW_WORKSPACE_WORKER, CREW_WORKSPACE_REVIEW]);
|
|
36
36
|
export const WORKSPACE_CONFLICT = 'WORKSPACE_CONFLICT';
|
|
37
37
|
|
|
38
38
|
/** The workspace a role runs in. Only the reviewer is separated out. */
|
package/src/delivery.mjs
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
export const DELIVERY_SECTIONS = ['Diff', 'Tests', 'Risks'];
|
|
13
13
|
export const OPTIONAL_DELIVERY_SECTIONS = ['Unverified'];
|
|
14
|
-
|
|
14
|
+
const ALL_DELIVERY_SECTIONS = [...DELIVERY_SECTIONS, ...OPTIONAL_DELIVERY_SECTIONS];
|
|
15
15
|
|
|
16
16
|
export const REVIEW_SECTIONS = ['Review Findings', 'Evidence', 'Risks', 'Verdict'];
|
|
17
17
|
|
|
@@ -129,7 +129,7 @@ export function appendDeliveryInstructions(task, { tier, isReview } = {}) {
|
|
|
129
129
|
* visible evidence. Two parsers gave two answers about one section; there is now
|
|
130
130
|
* one, and it lives here.
|
|
131
131
|
*/
|
|
132
|
-
|
|
132
|
+
function parseTestRow(line) {
|
|
133
133
|
const normalized = String(line ?? '')
|
|
134
134
|
.replace(/^\s*[-*+]\s+/, '')
|
|
135
135
|
.replace(/^\|/, '')
|
package/src/dsh-cli-runtime.mjs
CHANGED
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
// Re-exported so existing importers keep working while the cohort value now
|
|
36
36
|
// lives in exactly one place (src/dsh-cohort.mjs).
|
|
37
37
|
export { DSH_CLI_PACKAGE, TARGET_DSH_VERSION, TARGET_DSH_SPEC };
|
|
38
|
-
|
|
38
|
+
const CREW_DSH_RUNTIME_DIRNAME = 'runtime';
|
|
39
39
|
const CREW_PROFILE_DEFAULT_BUNDLES = ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app'];
|
|
40
40
|
const PROFILE_PATCH_TEMPLATE = '[]\n';
|
|
41
41
|
const PROFILE_PNPM_WORKSPACE = 'packages:\n - .\n\nnodeLinker: hoisted\nautoInstallPeers: false\n';
|
|
@@ -73,11 +73,6 @@ export function crewDshRuntimeVersionDir({ home = homedir(), version = TARGET_DS
|
|
|
73
73
|
return join(crewDshHome({ home }), `runtime-${version}`);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
export function crewDshRuntimeEntry({ home = homedir(), platform = process.platform } = {}) {
|
|
77
|
-
const suffix = platform === 'win32' ? '.cmd' : '';
|
|
78
|
-
return join(crewDshRuntimeRoot({ home }), 'node_modules', '.bin', `dsh${suffix}`);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
76
|
export function crewDshRuntimeModule({ home = homedir() } = {}) {
|
|
82
77
|
return join(crewDshRuntimeRoot({ home }), 'node_modules', '@deepseek-ai', 'dsh', 'lib', 'bin.js');
|
|
83
78
|
}
|
|
@@ -736,35 +731,36 @@ export async function restoreRetainedRuntime({
|
|
|
736
731
|
}
|
|
737
732
|
|
|
738
733
|
// GC retained runtimes that no release pins. A retained cohort is needed only
|
|
739
|
-
// while some managed release
|
|
740
|
-
//
|
|
741
|
-
|
|
734
|
+
// while some managed release resolves to it, so the caller passes the resolved
|
|
735
|
+
// cohort versions (see resolveReleaseCohort: manifest pin, then the
|
|
736
|
+
// release-cohort.json sidecar). Best-effort; never throws — a root that is
|
|
737
|
+
// missing, unreadable or not a directory prunes nothing rather than failing
|
|
738
|
+
// the update that just committed.
|
|
739
|
+
export function gcRetainedRuntimes({ home = homedir(), cohorts = [], log = () => {} } = {}) {
|
|
742
740
|
const root = retainedRuntimesRoot({ home });
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
const spec = payloadDshSpec(release);
|
|
747
|
-
if (spec && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(spec)) needed.add(spec);
|
|
748
|
-
}
|
|
741
|
+
const needed = new Set(cohorts.filter((spec) => typeof spec === 'string' && EXACT_COHORT_RE.test(spec)));
|
|
742
|
+
let names;
|
|
743
|
+
try { names = readdirSync(root); } catch { return []; }
|
|
749
744
|
const removed = [];
|
|
750
|
-
for (const name of
|
|
745
|
+
for (const name of names) {
|
|
746
|
+
if (needed.has(name)) continue;
|
|
751
747
|
const dir = join(root, name);
|
|
752
|
-
|
|
753
|
-
try { rmSync(dir, { recursive: true, force: true }); removed.push(name); } catch { /* best effort */ }
|
|
754
|
-
}
|
|
748
|
+
try { rmSync(dir, { recursive: true, force: true }); removed.push(name); } catch { /* best effort */ }
|
|
755
749
|
}
|
|
756
750
|
return removed;
|
|
757
751
|
}
|
|
758
752
|
|
|
753
|
+
const EXACT_COHORT_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
754
|
+
|
|
759
755
|
// Read the exact @deepseek-ai/dsh pin from a payload manifest (dependencies
|
|
760
756
|
// first, then peerDependencies). Exact pins only: a range or absence yields
|
|
761
757
|
// null so callers fail closed instead of guessing a cohort.
|
|
762
758
|
function payloadDshSpec(manifest) {
|
|
763
759
|
if (!manifest || typeof manifest !== 'object') return null;
|
|
764
760
|
const direct = manifest.dependencies?.['@deepseek-ai/dsh'];
|
|
765
|
-
if (typeof direct === 'string' &&
|
|
761
|
+
if (typeof direct === 'string' && EXACT_COHORT_RE.test(direct)) return direct;
|
|
766
762
|
const peer = manifest.peerDependencies?.['@deepseek-ai/dsh'];
|
|
767
|
-
if (typeof peer === 'string' &&
|
|
763
|
+
if (typeof peer === 'string' && EXACT_COHORT_RE.test(peer)) return peer;
|
|
768
764
|
return null;
|
|
769
765
|
}
|
|
770
766
|
|
|
@@ -964,7 +960,7 @@ function ensureDirectoryLink(linkPath, targetRoot) {
|
|
|
964
960
|
* command. The profile manifest and one loader-visible directory link are the
|
|
965
961
|
* only state changed; no dependency resolution or policy bypass is attempted.
|
|
966
962
|
*/
|
|
967
|
-
|
|
963
|
+
function ensurePluginRegistration({
|
|
968
964
|
profileRoot,
|
|
969
965
|
root,
|
|
970
966
|
name,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { validateModelCallabilityV2 } from './model-callability-contract.mjs';
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
const EXTENSION_CONTRACT_SCHEMA_VERSION = 1;
|
|
8
8
|
|
|
9
9
|
function row(matrix, id) {
|
|
10
10
|
return Array.isArray(matrix?.rows) ? matrix.rows.find((entry) => entry?.id === id) : undefined;
|
|
@@ -3,16 +3,6 @@
|
|
|
3
3
|
// Classification consumes structured status/codes/outcomes only. It never
|
|
4
4
|
// parses raw exception text, provider responses, credentials, quotas or logs.
|
|
5
5
|
|
|
6
|
-
export const FAILURE_CATEGORIES = Object.freeze([
|
|
7
|
-
'none',
|
|
8
|
-
'policy',
|
|
9
|
-
'compatibility',
|
|
10
|
-
'provider',
|
|
11
|
-
'runtime',
|
|
12
|
-
'verification',
|
|
13
|
-
'cancelled',
|
|
14
|
-
]);
|
|
15
|
-
|
|
16
6
|
export const FAILURE_REASON_CODES = Object.freeze({
|
|
17
7
|
NONE: 'NONE',
|
|
18
8
|
CANCELLED: 'CANCELLED',
|
package/src/hub/entry.mjs
CHANGED
|
@@ -68,7 +68,7 @@ export function recordAdaptiveJobOutcome(job, store = getProcessAdaptiveHealthSt
|
|
|
68
68
|
* a chained promise preserves all legacy completion/waiter behavior and runs
|
|
69
69
|
* this observer only after the Hub's own finalizer populated endedAt/status.
|
|
70
70
|
*/
|
|
71
|
-
|
|
71
|
+
function installAdaptiveHealthObserver() {
|
|
72
72
|
if (WorkerRegistry.prototype[ADAPTIVE_OBSERVER_INSTALLED] === true) return false;
|
|
73
73
|
const originalSpawn = WorkerRegistry.prototype.spawn;
|
|
74
74
|
Object.defineProperty(WorkerRegistry.prototype, ADAPTIVE_OBSERVER_INSTALLED, {
|
package/src/hub/index.mjs
CHANGED
|
@@ -119,7 +119,6 @@ export const QUICK_CONFIG_KEYS = Object.freeze([
|
|
|
119
119
|
const LEGACY_TIER_MODELS = { flash: 'deepseek-v4-flash', pro: 'deepseek-v4-pro' };
|
|
120
120
|
// Local copy (the hub must not import jobs.mjs, which pulls the DSH SDK into
|
|
121
121
|
// the profile realm): a valid dispatch role set.
|
|
122
|
-
const ROLES = { worker: true, reviewer: true };
|
|
123
122
|
const CONFIG_DIR = join(homedir(), '.config', 'dsh-crew');
|
|
124
123
|
const CREDENTIAL_PURGE_STATE_FILE = join(CONFIG_DIR, 'credential-purge-lifecycle.json');
|
|
125
124
|
const RECOVERY_ACTION_PATTERN = /^[a-f0-9]{32}$/u;
|
package/src/i18n.mjs
CHANGED
|
@@ -42,7 +42,7 @@ export function crewSkillFiles({ home = homedir(), env = process.env } = {}) {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/** The skill template shipped in the payload. */
|
|
45
|
-
|
|
45
|
+
function crewSkillSource({ root }) {
|
|
46
46
|
return join(root, 'skills', CREW_SKILL_NAME, 'SKILL.md');
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -57,7 +57,7 @@ const REGEX_MAY_FOLLOW = new Set([
|
|
|
57
57
|
* from the raw text, so a keyword inside a comment or a string is not a
|
|
58
58
|
* declaration and a quote inside a regex is not a string.
|
|
59
59
|
*/
|
|
60
|
-
|
|
60
|
+
function tokenizeModuleSource(source) {
|
|
61
61
|
const tokens = [];
|
|
62
62
|
const text = String(source ?? '');
|
|
63
63
|
let i = 0;
|
|
@@ -748,7 +748,7 @@ export function pickClaudeCommand(candidates, { platform = process.platform } =
|
|
|
748
748
|
?? list[0];
|
|
749
749
|
}
|
|
750
750
|
|
|
751
|
-
|
|
751
|
+
function resolveClaudeCommand({ platform = process.platform } = {}) {
|
|
752
752
|
const probe = spawnSync(platform === 'win32' ? 'where' : 'which', ['claude'], {
|
|
753
753
|
encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
|
|
754
754
|
});
|
|
@@ -1019,7 +1019,7 @@ export async function installClaudeCode({ home = homedir(), statusline = false,
|
|
|
1019
1019
|
}
|
|
1020
1020
|
|
|
1021
1021
|
// Keep CLI transaction ordering testable without changing the operator's home.
|
|
1022
|
-
|
|
1022
|
+
async function refreshClaudePlugin({ home, root, actions = [], claudeCommand, runStep = runClaudeStep }) {
|
|
1023
1023
|
// Each step runs to completion — including ending the whole process tree on a
|
|
1024
1024
|
// timeout — before the next one starts, and the settle window is the widest any
|
|
1025
1025
|
// step asks for. Two things this replaces: an `uninstall` left running past its
|