@ran-sh/dsh-crew 1.9.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +16 -16
- package/.claude-plugin/plugin.json +14 -14
- package/LICENSE +21 -21
- package/README.de.md +359 -359
- package/README.es.md +359 -359
- package/README.fr.md +359 -359
- package/README.hi.md +359 -359
- package/README.id.md +359 -359
- package/README.ja.md +359 -359
- package/README.ko.md +359 -359
- package/README.md +126 -126
- package/README.pt.md +359 -359
- package/README.ru.md +359 -359
- package/README.th.md +359 -359
- package/README.tr.md +359 -359
- package/README.vi.md +359 -359
- package/README.zh-TW.md +359 -359
- package/README.zh.md +116 -116
- package/agents/ds-flash.md +24 -24
- package/agents/ds-pro.md +24 -24
- package/agents/ds-reviewer.md +23 -23
- package/agents/ds-worker.md +23 -23
- package/bin/dsh-crew.mjs +16 -16
- package/codex/agents/ds-flash.toml +34 -34
- package/codex/agents/ds-pro.toml +34 -34
- package/codex/agents/ds-reviewer.toml +32 -32
- package/codex/agents/ds-worker.toml +32 -32
- package/codex/prompts/dsh-config.md +19 -19
- package/codex/prompts/dsh-status.md +5 -5
- package/commands/config.md +23 -23
- package/commands/off.md +5 -5
- package/commands/on.md +5 -5
- package/commands/status.md +9 -9
- package/cordis.patch.yml +4 -4
- package/docs/gpt-relay-extension.md +103 -103
- package/docs/installation.md +138 -138
- package/docs/job-contracts.md +103 -103
- package/docs/readiness-matrix.md +85 -85
- package/docs/ui-surfaces.md +107 -107
- package/official-web-bridge/cordis.patch.yml +4 -4
- package/official-web-bridge/entry.mjs +1 -1
- package/official-web-bridge/overlay-entry.mjs +59 -59
- package/official-web-bridge/package.json +25 -25
- package/package.json +3 -2
- package/scripts/build-client.mjs +49 -49
- package/scripts/live-crew-smoke.mjs +39 -39
- package/scripts/live-policy-matrix.mjs +177 -177
- package/scripts/policy-probe.mjs +101 -101
- package/scripts/remove-legacy-official-bridge.ps1 +89 -89
- package/scripts/setup.mjs +393 -393
- package/scripts/smoke-real.mjs +110 -110
- package/scripts/smoke.mjs +78 -78
- package/scripts/verify-crew-ui-polish.mjs +145 -145
- package/scripts/verify-history-ui.mjs +97 -97
- package/scripts/verify-installer-fix.mjs +26 -26
- package/scripts/verify-npm-install.mjs +311 -311
- package/scripts/verify-official-bridge-e2e.mjs +192 -192
- package/src/adaptive-routing.mjs +260 -260
- package/src/client/activation-summary.tsx +64 -64
- package/src/client/collapsible-sections.mjs +55 -55
- package/src/client/history-panel.tsx +108 -108
- package/src/client/host-readiness.mjs +71 -71
- package/src/client/index.tsx +1711 -1711
- package/src/client/model-callability-view.mjs +17 -17
- package/src/client/panel-chrome.tsx +40 -40
- package/src/client/quick-entry.tsx +10 -10
- package/src/client/quick-panel.tsx +275 -275
- package/src/client/readiness-envelope.mjs +34 -34
- package/src/client/surface-detection.mjs +43 -43
- package/src/config-readiness.mjs +226 -226
- package/src/credential-reference.mjs +38 -38
- package/src/delivery.mjs +205 -205
- package/src/dsh-cli-runtime.mjs +1021 -1021
- package/src/dsh-cohort.mjs +20 -20
- package/src/extension-contract.mjs +104 -104
- package/src/failure-classification.mjs +201 -201
- package/src/history/admission-gate.mjs +67 -67
- package/src/history/archive-store.mjs +272 -272
- package/src/history/cleanup-plan.mjs +89 -89
- package/src/history/http.mjs +32 -32
- package/src/history/operation.mjs +86 -86
- package/src/history/runner-detach.mjs +34 -34
- package/src/history/runner.mjs +52 -52
- package/src/history/runtime.mjs +36 -36
- package/src/history/service.mjs +177 -177
- package/src/history/state.mjs +27 -27
- package/src/hub/entry.mjs +104 -104
- package/src/hub/index.mjs +2694 -2694
- package/src/hub-client.mjs +154 -154
- package/src/hub-compatibility.mjs +42 -42
- package/src/i18n.mjs +19 -19
- package/src/information-flow.mjs +67 -67
- package/src/install/cli.mjs +28 -28
- package/src/install/install-legacy.mjs +711 -711
- package/src/install/install.mjs +483 -483
- package/src/install/npx-lifecycle.mjs +3456 -3456
- package/src/install/official-frontend-assets.mjs +78 -78
- package/src/install/official-web.mjs +95 -95
- package/src/install/payload-content.mjs +88 -88
- package/src/install/windows-startup.mjs +236 -236
- package/src/install/windows-supervisor-adapter.mjs +443 -443
- package/src/install/windows-supervisor-lifecycle.mjs +782 -782
- package/src/install/zcode.mjs +397 -397
- package/src/job-contracts.mjs +255 -255
- package/src/local-request-guard.mjs +60 -60
- package/src/mcp-runtime.mjs +340 -340
- package/src/model-callability-contract.mjs +79 -79
- package/src/model-catalog.mjs +180 -180
- package/src/model-routing.mjs +586 -586
- package/src/model-schedule.mjs +207 -207
- package/src/official-web-bridge.mjs +447 -447
- package/src/policy.mjs +235 -235
- package/src/provider-delete-adapters.mjs +1934 -1934
- package/src/provider-health.mjs +130 -130
- package/src/provider-inventory.mjs +182 -182
- package/src/provider-layer-migration-adapters.mjs +759 -759
- package/src/provider-layer-migration.mjs +198 -198
- package/src/provider-lifecycle-state.mjs +103 -103
- package/src/provider-lifecycle.mjs +252 -252
- package/src/provider-profile-store.mjs +390 -390
- package/src/provider-settings-store.mjs +633 -633
- package/src/provider-store-lock.mjs +67 -67
- package/src/readiness-matrix.mjs +181 -181
- package/src/removable-waiter.mjs +29 -29
- package/src/role-profiles.mjs +107 -107
- package/src/runtime-controls.mjs +84 -84
- package/src/runtime-identity-contract.mjs +34 -34
- package/src/runtime-identity.mjs +235 -235
- package/src/runtime-readiness-snapshot.mjs +285 -285
- package/src/server.mjs +581 -581
- package/src/session-origins.mjs +60 -60
- package/src/standalone-sdk.mjs +23 -23
- package/src/status-shard.mjs +63 -63
- package/src/structured-error-code.mjs +38 -38
- package/src/supervisor/restart-request.mjs +256 -256
- package/src/workflow-runtime.mjs +739 -739
- package/src/workflow.mjs +155 -155
- package/src/workspace-audit.mjs +231 -231
- package/src/workspace-context.mjs +146 -146
- package/src/workspace-isolation.mjs +463 -455
- package/src/workspace-readiness.mjs +32 -32
- package/statusline/statusline.sh +14 -14
- package/statusline/worker-segment.sh +35 -35
- package/windows/start-dsh-crew.cmd +57 -57
- package/windows/start-dsh-crew.ps1 +1302 -1302
- package/windows/supervisor-control.ps1 +467 -467
- package/worker.cordis.yml +67 -67
- package/zcode/agents/ds-reviewer.md +31 -31
- package/zcode/agents/ds-worker.md +31 -31
- package/zcode/commands/dsh-config.md +17 -17
- package/zcode/commands/dsh-status.md +5 -5
|
@@ -1,236 +1,236 @@
|
|
|
1
|
-
import {
|
|
2
|
-
copyFileSync,
|
|
3
|
-
existsSync,
|
|
4
|
-
mkdirSync,
|
|
5
|
-
readFileSync,
|
|
6
|
-
renameSync,
|
|
7
|
-
rmSync,
|
|
8
|
-
writeFileSync,
|
|
9
|
-
} from 'node:fs';
|
|
10
|
-
import { createHash } from 'node:crypto';
|
|
11
|
-
import { dirname, join, resolve } from 'node:path';
|
|
12
|
-
import { homedir } from 'node:os';
|
|
13
|
-
import { installOfficialFrontendAssets, officialFrontendAssetsReady } from './official-frontend-assets.mjs';
|
|
14
|
-
|
|
15
|
-
export const WINDOWS_STARTUP_FILENAME = 'DSH Crew.vbs';
|
|
16
|
-
export const WINDOWS_LAUNCHER_FILENAME = 'start-dsh-crew.cmd';
|
|
17
|
-
export const WINDOWS_HELPER_FILENAME = 'start-dsh-crew.ps1';
|
|
18
|
-
export const WINDOWS_CONTROL_FILENAME = 'supervisor-control.ps1';
|
|
19
|
-
export const WINDOWS_SUPERVISOR_ASSET_MANIFEST = 'supervisor-assets.json';
|
|
20
|
-
|
|
21
|
-
function defaultStartupDir({ home, env }) {
|
|
22
|
-
if (home === homedir() && env.APPDATA) {
|
|
23
|
-
return join(env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');
|
|
24
|
-
}
|
|
25
|
-
return join(home, 'AppData', 'Roaming', 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function paths({ home, startupDir, env }) {
|
|
29
|
-
const launcherFile = join(home, '.config', 'dsh-crew', 'launchers', WINDOWS_LAUNCHER_FILENAME);
|
|
30
|
-
const helperFile = join(home, '.config', 'dsh-crew', 'launchers', WINDOWS_HELPER_FILENAME);
|
|
31
|
-
const controlFile = join(home, '.config', 'dsh-crew', 'launchers', WINDOWS_CONTROL_FILENAME);
|
|
32
|
-
const supervisorManifestFile = join(home, '.config', 'dsh-crew', 'launchers', WINDOWS_SUPERVISOR_ASSET_MANIFEST);
|
|
33
|
-
const resolvedStartupDir = startupDir ?? defaultStartupDir({ home, env });
|
|
34
|
-
return {
|
|
35
|
-
launcherFile,
|
|
36
|
-
helperFile,
|
|
37
|
-
controlFile,
|
|
38
|
-
supervisorManifestFile,
|
|
39
|
-
startupFile: join(resolvedStartupDir, WINDOWS_STARTUP_FILENAME),
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function renderVbs(template, launcherFile) {
|
|
44
|
-
const escaped = launcherFile.replace(/"/g, '""');
|
|
45
|
-
return template.replace('__LAUNCHER__', escaped);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function isManagedStartupFile(file, resolved) {
|
|
49
|
-
try {
|
|
50
|
-
if (file === resolved.startupFile) {
|
|
51
|
-
const text = readFileSync(file, 'utf16le').replace(/^\uFEFF/, '');
|
|
52
|
-
return text.includes(resolved.launcherFile) && (/WScript\.Shell/i.test(text) || /--watch/i.test(text) || /--background/i.test(text));
|
|
53
|
-
}
|
|
54
|
-
if (file === resolved.launcherFile) {
|
|
55
|
-
const text = readFileSync(file, 'utf8');
|
|
56
|
-
return (/DSH Crew Launcher/i.test(text) || /start-dsh-crew\.ps1/i.test(text)) && /dsh-crew/i.test(text);
|
|
57
|
-
}
|
|
58
|
-
if (file === resolved.helperFile) {
|
|
59
|
-
const text = readFileSync(file, 'utf8');
|
|
60
|
-
return /DSH Crew managed Windows launcher/i.test(text) || /DSHCrewServiceSupervisor/i.test(text);
|
|
61
|
-
}
|
|
62
|
-
if (file === resolved.controlFile) {
|
|
63
|
-
const text = readFileSync(file, 'utf8');
|
|
64
|
-
return /DSH Crew Windows supervisor process control/i.test(text) && /Stop-ExactManagedWatcher/i.test(text);
|
|
65
|
-
}
|
|
66
|
-
if (file === resolved.supervisorManifestFile) {
|
|
67
|
-
const parsed = JSON.parse(readFileSync(file, 'utf8'));
|
|
68
|
-
return parsed?.schema_version === 1 && parsed.managed_by === 'dsh-crew';
|
|
69
|
-
}
|
|
70
|
-
} catch {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function startupTargetConflicts(resolved) {
|
|
77
|
-
return [resolved.startupFile, resolved.launcherFile, resolved.helperFile, resolved.controlFile, resolved.supervisorManifestFile]
|
|
78
|
-
.filter((file) => existsSync(file) && !isManagedStartupFile(file, resolved));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export function windowsStartupStatus({
|
|
82
|
-
home = homedir(),
|
|
83
|
-
root,
|
|
84
|
-
startupDir,
|
|
85
|
-
platform = process.platform,
|
|
86
|
-
env = process.env,
|
|
87
|
-
} = {}) {
|
|
88
|
-
if (platform !== 'win32') return { supported: false, installed: false, ready: false };
|
|
89
|
-
const resolved = paths({ home, startupDir, env });
|
|
90
|
-
const installed = existsSync(resolved.startupFile)
|
|
91
|
-
|| existsSync(resolved.launcherFile)
|
|
92
|
-
|| existsSync(resolved.helperFile)
|
|
93
|
-
|| existsSync(resolved.controlFile)
|
|
94
|
-
|| existsSync(resolved.supervisorManifestFile);
|
|
95
|
-
const components = {
|
|
96
|
-
startup_entry_content: false,
|
|
97
|
-
launcher_content: false,
|
|
98
|
-
helper_content: false,
|
|
99
|
-
control_content: false,
|
|
100
|
-
supervisor_manifest: false,
|
|
101
|
-
};
|
|
102
|
-
if (typeof root === 'string' && root.trim()) {
|
|
103
|
-
try {
|
|
104
|
-
const sourceLauncher = readFileSync(join(root, 'windows', WINDOWS_LAUNCHER_FILENAME), 'utf8');
|
|
105
|
-
const sourceHelper = readFileSync(join(root, 'windows', WINDOWS_HELPER_FILENAME), 'utf8');
|
|
106
|
-
if (sourceHelper.includes('Get-OfficialFrontendOverlay')) components.frontend_overlay = officialFrontendAssetsReady({ home, root });
|
|
107
|
-
const sourceControl = readFileSync(join(root, 'windows', WINDOWS_CONTROL_FILENAME), 'utf8');
|
|
108
|
-
const sourceVbs = readFileSync(join(root, 'windows', 'start-dsh-crew.vbs'), 'utf8');
|
|
109
|
-
const startup = readFileSync(resolved.startupFile, 'utf16le').replace(/^\uFEFF/, '');
|
|
110
|
-
components.startup_entry_content = startup === renderVbs(sourceVbs, resolved.launcherFile);
|
|
111
|
-
components.launcher_content = readFileSync(resolved.launcherFile, 'utf8') === sourceLauncher;
|
|
112
|
-
components.helper_content = readFileSync(resolved.helperFile, 'utf8') === sourceHelper;
|
|
113
|
-
components.control_content = readFileSync(resolved.controlFile, 'utf8') === sourceControl;
|
|
114
|
-
const assets = readWindowsSupervisorAssets({ home });
|
|
115
|
-
components.supervisor_manifest = assets.ok
|
|
116
|
-
&& assets.helper_hash === sha256File(join(root, 'windows', WINDOWS_HELPER_FILENAME))
|
|
117
|
-
&& assets.control_hash === sha256File(join(root, 'windows', WINDOWS_CONTROL_FILENAME));
|
|
118
|
-
} catch { /* missing or unreadable content remains false */ }
|
|
119
|
-
}
|
|
120
|
-
const missing = Object.entries(components).filter(([, ready]) => !ready).map(([key]) => key);
|
|
121
|
-
return { supported: true, installed, ready: missing.length === 0, components, missing, ...resolved };
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function sha256File(file) {
|
|
125
|
-
try { return createHash('sha256').update(readFileSync(file)).digest('hex'); } catch { return null; }
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function samePath(left, right) {
|
|
129
|
-
try { return resolve(left).toLowerCase() === resolve(right).toLowerCase(); } catch { return false; }
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function writeAtomic(file, content) {
|
|
133
|
-
const temp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
134
|
-
writeFileSync(temp, content, { encoding: 'utf8', flag: 'wx' });
|
|
135
|
-
try { renameSync(temp, file); } finally { try { rmSync(temp, { force: true }); } catch {} }
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export function readWindowsSupervisorAssets({ home = homedir() } = {}) {
|
|
139
|
-
const resolved = paths({ home, env: process.env });
|
|
140
|
-
try {
|
|
141
|
-
const manifest = JSON.parse(readFileSync(resolved.supervisorManifestFile, 'utf8'));
|
|
142
|
-
const helperHash = sha256File(resolved.helperFile);
|
|
143
|
-
const controlHash = sha256File(resolved.controlFile);
|
|
144
|
-
const valid = manifest?.schema_version === 1
|
|
145
|
-
&& manifest.managed_by === 'dsh-crew'
|
|
146
|
-
&& samePath(manifest.helper_path, resolved.helperFile)
|
|
147
|
-
&& samePath(manifest.control_path, resolved.controlFile)
|
|
148
|
-
&& /^[a-f0-9]{64}$/u.test(manifest.helper_hash ?? '')
|
|
149
|
-
&& /^[a-f0-9]{64}$/u.test(manifest.control_hash ?? '')
|
|
150
|
-
&& manifest.helper_hash === helperHash
|
|
151
|
-
&& manifest.control_hash === controlHash;
|
|
152
|
-
return valid
|
|
153
|
-
? { ok: true, helper_path: resolved.helperFile, helper_hash: helperHash, control_path: resolved.controlFile, control_hash: controlHash }
|
|
154
|
-
: { ok: false, code: 'SUPERVISOR_ASSET_MANIFEST_MISMATCH' };
|
|
155
|
-
} catch {
|
|
156
|
-
return { ok: false, code: 'SUPERVISOR_ASSET_MANIFEST_UNAVAILABLE' };
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
export function installWindowsStartup({
|
|
161
|
-
home = homedir(),
|
|
162
|
-
root,
|
|
163
|
-
startupDir,
|
|
164
|
-
platform = process.platform,
|
|
165
|
-
env = process.env,
|
|
166
|
-
} = {}) {
|
|
167
|
-
if (platform !== 'win32') return { ok: true, supported: false, changed: false };
|
|
168
|
-
if (!root) return { ok: false, supported: true, code: 'STARTUP_SOURCE_REQUIRED' };
|
|
169
|
-
const sourceLauncher = join(root, 'windows', WINDOWS_LAUNCHER_FILENAME);
|
|
170
|
-
const sourceHelper = join(root, 'windows', WINDOWS_HELPER_FILENAME);
|
|
171
|
-
const sourceControl = join(root, 'windows', WINDOWS_CONTROL_FILENAME);
|
|
172
|
-
const sourceVbs = join(root, 'windows', 'start-dsh-crew.vbs');
|
|
173
|
-
if (!existsSync(sourceLauncher) || !existsSync(sourceHelper) || !existsSync(sourceControl) || !existsSync(sourceVbs)) {
|
|
174
|
-
return { ok: false, supported: true, code: 'STARTUP_ASSET_MISSING' };
|
|
175
|
-
}
|
|
176
|
-
const resolved = paths({ home, startupDir, env });
|
|
177
|
-
const conflicts = startupTargetConflicts(resolved);
|
|
178
|
-
if (conflicts.length > 0) {
|
|
179
|
-
return { ok: false, supported: true, code: 'STARTUP_TARGET_COLLISION', conflicts, ...resolved };
|
|
180
|
-
}
|
|
181
|
-
const needsFrontend = readFileSync(sourceHelper, 'utf8').includes('Get-OfficialFrontendOverlay');
|
|
182
|
-
const frontend = needsFrontend ? installOfficialFrontendAssets({ home, root }) : null;
|
|
183
|
-
if (frontend && !frontend.ok) return { ...frontend, supported: true };
|
|
184
|
-
mkdirSync(dirname(resolved.launcherFile), { recursive: true });
|
|
185
|
-
mkdirSync(dirname(resolved.startupFile), { recursive: true });
|
|
186
|
-
const beforeLauncher = existsSync(resolved.launcherFile) ? readFileSync(resolved.launcherFile) : null;
|
|
187
|
-
const beforeHelper = existsSync(resolved.helperFile) ? readFileSync(resolved.helperFile) : null;
|
|
188
|
-
const beforeControl = existsSync(resolved.controlFile) ? readFileSync(resolved.controlFile) : null;
|
|
189
|
-
const beforeManifest = existsSync(resolved.supervisorManifestFile) ? readFileSync(resolved.supervisorManifestFile) : null;
|
|
190
|
-
const beforeStartup = existsSync(resolved.startupFile) ? readFileSync(resolved.startupFile) : null;
|
|
191
|
-
copyFileSync(sourceLauncher, resolved.launcherFile);
|
|
192
|
-
copyFileSync(sourceHelper, resolved.helperFile);
|
|
193
|
-
copyFileSync(sourceControl, resolved.controlFile);
|
|
194
|
-
const supervisorManifest = {
|
|
195
|
-
schema_version: 1,
|
|
196
|
-
managed_by: 'dsh-crew',
|
|
197
|
-
helper_path: resolved.helperFile,
|
|
198
|
-
helper_hash: sha256File(resolved.helperFile),
|
|
199
|
-
control_path: resolved.controlFile,
|
|
200
|
-
control_hash: sha256File(resolved.controlFile),
|
|
201
|
-
};
|
|
202
|
-
writeAtomic(resolved.supervisorManifestFile, `${JSON.stringify(supervisorManifest, null, 2)}\n`);
|
|
203
|
-
if (!readWindowsSupervisorAssets({ home }).ok) {
|
|
204
|
-
return { ok: false, supported: true, code: 'SUPERVISOR_ASSET_MANIFEST_VERIFY_FAILED', ...resolved };
|
|
205
|
-
}
|
|
206
|
-
const rendered = renderVbs(readFileSync(sourceVbs, 'utf8'), resolved.launcherFile);
|
|
207
|
-
writeFileSync(resolved.startupFile, `\uFEFF${rendered}`, 'utf16le');
|
|
208
|
-
const changed = frontend?.changed === true || !beforeLauncher?.equals(readFileSync(resolved.launcherFile))
|
|
209
|
-
|| !beforeHelper?.equals(readFileSync(resolved.helperFile))
|
|
210
|
-
|| !beforeControl?.equals(readFileSync(resolved.controlFile))
|
|
211
|
-
|| !beforeManifest?.equals(readFileSync(resolved.supervisorManifestFile))
|
|
212
|
-
|| !beforeStartup?.equals(readFileSync(resolved.startupFile));
|
|
213
|
-
return { ok: true, supported: true, changed, ...resolved };
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
export function uninstallWindowsStartup({
|
|
217
|
-
home = homedir(),
|
|
218
|
-
startupDir,
|
|
219
|
-
platform = process.platform,
|
|
220
|
-
env = process.env,
|
|
221
|
-
} = {}) {
|
|
222
|
-
if (platform !== 'win32') return { ok: true, supported: false, removed: false };
|
|
223
|
-
const resolved = paths({ home, startupDir, env });
|
|
224
|
-
let removed = false;
|
|
225
|
-
const preserved = [];
|
|
226
|
-
for (const file of [resolved.startupFile, resolved.launcherFile, resolved.helperFile, resolved.controlFile, resolved.supervisorManifestFile]) {
|
|
227
|
-
if (!existsSync(file)) continue;
|
|
228
|
-
if (!isManagedStartupFile(file, resolved)) {
|
|
229
|
-
preserved.push(file);
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
rmSync(file, { force: true });
|
|
233
|
-
removed = true;
|
|
234
|
-
}
|
|
235
|
-
return { ok: true, supported: true, removed, preserved, ...resolved };
|
|
236
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
copyFileSync,
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
renameSync,
|
|
7
|
+
rmSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
} from 'node:fs';
|
|
10
|
+
import { createHash } from 'node:crypto';
|
|
11
|
+
import { dirname, join, resolve } from 'node:path';
|
|
12
|
+
import { homedir } from 'node:os';
|
|
13
|
+
import { installOfficialFrontendAssets, officialFrontendAssetsReady } from './official-frontend-assets.mjs';
|
|
14
|
+
|
|
15
|
+
export const WINDOWS_STARTUP_FILENAME = 'DSH Crew.vbs';
|
|
16
|
+
export const WINDOWS_LAUNCHER_FILENAME = 'start-dsh-crew.cmd';
|
|
17
|
+
export const WINDOWS_HELPER_FILENAME = 'start-dsh-crew.ps1';
|
|
18
|
+
export const WINDOWS_CONTROL_FILENAME = 'supervisor-control.ps1';
|
|
19
|
+
export const WINDOWS_SUPERVISOR_ASSET_MANIFEST = 'supervisor-assets.json';
|
|
20
|
+
|
|
21
|
+
function defaultStartupDir({ home, env }) {
|
|
22
|
+
if (home === homedir() && env.APPDATA) {
|
|
23
|
+
return join(env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');
|
|
24
|
+
}
|
|
25
|
+
return join(home, 'AppData', 'Roaming', 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function paths({ home, startupDir, env }) {
|
|
29
|
+
const launcherFile = join(home, '.config', 'dsh-crew', 'launchers', WINDOWS_LAUNCHER_FILENAME);
|
|
30
|
+
const helperFile = join(home, '.config', 'dsh-crew', 'launchers', WINDOWS_HELPER_FILENAME);
|
|
31
|
+
const controlFile = join(home, '.config', 'dsh-crew', 'launchers', WINDOWS_CONTROL_FILENAME);
|
|
32
|
+
const supervisorManifestFile = join(home, '.config', 'dsh-crew', 'launchers', WINDOWS_SUPERVISOR_ASSET_MANIFEST);
|
|
33
|
+
const resolvedStartupDir = startupDir ?? defaultStartupDir({ home, env });
|
|
34
|
+
return {
|
|
35
|
+
launcherFile,
|
|
36
|
+
helperFile,
|
|
37
|
+
controlFile,
|
|
38
|
+
supervisorManifestFile,
|
|
39
|
+
startupFile: join(resolvedStartupDir, WINDOWS_STARTUP_FILENAME),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function renderVbs(template, launcherFile) {
|
|
44
|
+
const escaped = launcherFile.replace(/"/g, '""');
|
|
45
|
+
return template.replace('__LAUNCHER__', escaped);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isManagedStartupFile(file, resolved) {
|
|
49
|
+
try {
|
|
50
|
+
if (file === resolved.startupFile) {
|
|
51
|
+
const text = readFileSync(file, 'utf16le').replace(/^\uFEFF/, '');
|
|
52
|
+
return text.includes(resolved.launcherFile) && (/WScript\.Shell/i.test(text) || /--watch/i.test(text) || /--background/i.test(text));
|
|
53
|
+
}
|
|
54
|
+
if (file === resolved.launcherFile) {
|
|
55
|
+
const text = readFileSync(file, 'utf8');
|
|
56
|
+
return (/DSH Crew Launcher/i.test(text) || /start-dsh-crew\.ps1/i.test(text)) && /dsh-crew/i.test(text);
|
|
57
|
+
}
|
|
58
|
+
if (file === resolved.helperFile) {
|
|
59
|
+
const text = readFileSync(file, 'utf8');
|
|
60
|
+
return /DSH Crew managed Windows launcher/i.test(text) || /DSHCrewServiceSupervisor/i.test(text);
|
|
61
|
+
}
|
|
62
|
+
if (file === resolved.controlFile) {
|
|
63
|
+
const text = readFileSync(file, 'utf8');
|
|
64
|
+
return /DSH Crew Windows supervisor process control/i.test(text) && /Stop-ExactManagedWatcher/i.test(text);
|
|
65
|
+
}
|
|
66
|
+
if (file === resolved.supervisorManifestFile) {
|
|
67
|
+
const parsed = JSON.parse(readFileSync(file, 'utf8'));
|
|
68
|
+
return parsed?.schema_version === 1 && parsed.managed_by === 'dsh-crew';
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function startupTargetConflicts(resolved) {
|
|
77
|
+
return [resolved.startupFile, resolved.launcherFile, resolved.helperFile, resolved.controlFile, resolved.supervisorManifestFile]
|
|
78
|
+
.filter((file) => existsSync(file) && !isManagedStartupFile(file, resolved));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function windowsStartupStatus({
|
|
82
|
+
home = homedir(),
|
|
83
|
+
root,
|
|
84
|
+
startupDir,
|
|
85
|
+
platform = process.platform,
|
|
86
|
+
env = process.env,
|
|
87
|
+
} = {}) {
|
|
88
|
+
if (platform !== 'win32') return { supported: false, installed: false, ready: false };
|
|
89
|
+
const resolved = paths({ home, startupDir, env });
|
|
90
|
+
const installed = existsSync(resolved.startupFile)
|
|
91
|
+
|| existsSync(resolved.launcherFile)
|
|
92
|
+
|| existsSync(resolved.helperFile)
|
|
93
|
+
|| existsSync(resolved.controlFile)
|
|
94
|
+
|| existsSync(resolved.supervisorManifestFile);
|
|
95
|
+
const components = {
|
|
96
|
+
startup_entry_content: false,
|
|
97
|
+
launcher_content: false,
|
|
98
|
+
helper_content: false,
|
|
99
|
+
control_content: false,
|
|
100
|
+
supervisor_manifest: false,
|
|
101
|
+
};
|
|
102
|
+
if (typeof root === 'string' && root.trim()) {
|
|
103
|
+
try {
|
|
104
|
+
const sourceLauncher = readFileSync(join(root, 'windows', WINDOWS_LAUNCHER_FILENAME), 'utf8');
|
|
105
|
+
const sourceHelper = readFileSync(join(root, 'windows', WINDOWS_HELPER_FILENAME), 'utf8');
|
|
106
|
+
if (sourceHelper.includes('Get-OfficialFrontendOverlay')) components.frontend_overlay = officialFrontendAssetsReady({ home, root });
|
|
107
|
+
const sourceControl = readFileSync(join(root, 'windows', WINDOWS_CONTROL_FILENAME), 'utf8');
|
|
108
|
+
const sourceVbs = readFileSync(join(root, 'windows', 'start-dsh-crew.vbs'), 'utf8');
|
|
109
|
+
const startup = readFileSync(resolved.startupFile, 'utf16le').replace(/^\uFEFF/, '');
|
|
110
|
+
components.startup_entry_content = startup === renderVbs(sourceVbs, resolved.launcherFile);
|
|
111
|
+
components.launcher_content = readFileSync(resolved.launcherFile, 'utf8') === sourceLauncher;
|
|
112
|
+
components.helper_content = readFileSync(resolved.helperFile, 'utf8') === sourceHelper;
|
|
113
|
+
components.control_content = readFileSync(resolved.controlFile, 'utf8') === sourceControl;
|
|
114
|
+
const assets = readWindowsSupervisorAssets({ home });
|
|
115
|
+
components.supervisor_manifest = assets.ok
|
|
116
|
+
&& assets.helper_hash === sha256File(join(root, 'windows', WINDOWS_HELPER_FILENAME))
|
|
117
|
+
&& assets.control_hash === sha256File(join(root, 'windows', WINDOWS_CONTROL_FILENAME));
|
|
118
|
+
} catch { /* missing or unreadable content remains false */ }
|
|
119
|
+
}
|
|
120
|
+
const missing = Object.entries(components).filter(([, ready]) => !ready).map(([key]) => key);
|
|
121
|
+
return { supported: true, installed, ready: missing.length === 0, components, missing, ...resolved };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function sha256File(file) {
|
|
125
|
+
try { return createHash('sha256').update(readFileSync(file)).digest('hex'); } catch { return null; }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function samePath(left, right) {
|
|
129
|
+
try { return resolve(left).toLowerCase() === resolve(right).toLowerCase(); } catch { return false; }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function writeAtomic(file, content) {
|
|
133
|
+
const temp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
134
|
+
writeFileSync(temp, content, { encoding: 'utf8', flag: 'wx' });
|
|
135
|
+
try { renameSync(temp, file); } finally { try { rmSync(temp, { force: true }); } catch {} }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function readWindowsSupervisorAssets({ home = homedir() } = {}) {
|
|
139
|
+
const resolved = paths({ home, env: process.env });
|
|
140
|
+
try {
|
|
141
|
+
const manifest = JSON.parse(readFileSync(resolved.supervisorManifestFile, 'utf8'));
|
|
142
|
+
const helperHash = sha256File(resolved.helperFile);
|
|
143
|
+
const controlHash = sha256File(resolved.controlFile);
|
|
144
|
+
const valid = manifest?.schema_version === 1
|
|
145
|
+
&& manifest.managed_by === 'dsh-crew'
|
|
146
|
+
&& samePath(manifest.helper_path, resolved.helperFile)
|
|
147
|
+
&& samePath(manifest.control_path, resolved.controlFile)
|
|
148
|
+
&& /^[a-f0-9]{64}$/u.test(manifest.helper_hash ?? '')
|
|
149
|
+
&& /^[a-f0-9]{64}$/u.test(manifest.control_hash ?? '')
|
|
150
|
+
&& manifest.helper_hash === helperHash
|
|
151
|
+
&& manifest.control_hash === controlHash;
|
|
152
|
+
return valid
|
|
153
|
+
? { ok: true, helper_path: resolved.helperFile, helper_hash: helperHash, control_path: resolved.controlFile, control_hash: controlHash }
|
|
154
|
+
: { ok: false, code: 'SUPERVISOR_ASSET_MANIFEST_MISMATCH' };
|
|
155
|
+
} catch {
|
|
156
|
+
return { ok: false, code: 'SUPERVISOR_ASSET_MANIFEST_UNAVAILABLE' };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function installWindowsStartup({
|
|
161
|
+
home = homedir(),
|
|
162
|
+
root,
|
|
163
|
+
startupDir,
|
|
164
|
+
platform = process.platform,
|
|
165
|
+
env = process.env,
|
|
166
|
+
} = {}) {
|
|
167
|
+
if (platform !== 'win32') return { ok: true, supported: false, changed: false };
|
|
168
|
+
if (!root) return { ok: false, supported: true, code: 'STARTUP_SOURCE_REQUIRED' };
|
|
169
|
+
const sourceLauncher = join(root, 'windows', WINDOWS_LAUNCHER_FILENAME);
|
|
170
|
+
const sourceHelper = join(root, 'windows', WINDOWS_HELPER_FILENAME);
|
|
171
|
+
const sourceControl = join(root, 'windows', WINDOWS_CONTROL_FILENAME);
|
|
172
|
+
const sourceVbs = join(root, 'windows', 'start-dsh-crew.vbs');
|
|
173
|
+
if (!existsSync(sourceLauncher) || !existsSync(sourceHelper) || !existsSync(sourceControl) || !existsSync(sourceVbs)) {
|
|
174
|
+
return { ok: false, supported: true, code: 'STARTUP_ASSET_MISSING' };
|
|
175
|
+
}
|
|
176
|
+
const resolved = paths({ home, startupDir, env });
|
|
177
|
+
const conflicts = startupTargetConflicts(resolved);
|
|
178
|
+
if (conflicts.length > 0) {
|
|
179
|
+
return { ok: false, supported: true, code: 'STARTUP_TARGET_COLLISION', conflicts, ...resolved };
|
|
180
|
+
}
|
|
181
|
+
const needsFrontend = readFileSync(sourceHelper, 'utf8').includes('Get-OfficialFrontendOverlay');
|
|
182
|
+
const frontend = needsFrontend ? installOfficialFrontendAssets({ home, root }) : null;
|
|
183
|
+
if (frontend && !frontend.ok) return { ...frontend, supported: true };
|
|
184
|
+
mkdirSync(dirname(resolved.launcherFile), { recursive: true });
|
|
185
|
+
mkdirSync(dirname(resolved.startupFile), { recursive: true });
|
|
186
|
+
const beforeLauncher = existsSync(resolved.launcherFile) ? readFileSync(resolved.launcherFile) : null;
|
|
187
|
+
const beforeHelper = existsSync(resolved.helperFile) ? readFileSync(resolved.helperFile) : null;
|
|
188
|
+
const beforeControl = existsSync(resolved.controlFile) ? readFileSync(resolved.controlFile) : null;
|
|
189
|
+
const beforeManifest = existsSync(resolved.supervisorManifestFile) ? readFileSync(resolved.supervisorManifestFile) : null;
|
|
190
|
+
const beforeStartup = existsSync(resolved.startupFile) ? readFileSync(resolved.startupFile) : null;
|
|
191
|
+
copyFileSync(sourceLauncher, resolved.launcherFile);
|
|
192
|
+
copyFileSync(sourceHelper, resolved.helperFile);
|
|
193
|
+
copyFileSync(sourceControl, resolved.controlFile);
|
|
194
|
+
const supervisorManifest = {
|
|
195
|
+
schema_version: 1,
|
|
196
|
+
managed_by: 'dsh-crew',
|
|
197
|
+
helper_path: resolved.helperFile,
|
|
198
|
+
helper_hash: sha256File(resolved.helperFile),
|
|
199
|
+
control_path: resolved.controlFile,
|
|
200
|
+
control_hash: sha256File(resolved.controlFile),
|
|
201
|
+
};
|
|
202
|
+
writeAtomic(resolved.supervisorManifestFile, `${JSON.stringify(supervisorManifest, null, 2)}\n`);
|
|
203
|
+
if (!readWindowsSupervisorAssets({ home }).ok) {
|
|
204
|
+
return { ok: false, supported: true, code: 'SUPERVISOR_ASSET_MANIFEST_VERIFY_FAILED', ...resolved };
|
|
205
|
+
}
|
|
206
|
+
const rendered = renderVbs(readFileSync(sourceVbs, 'utf8'), resolved.launcherFile);
|
|
207
|
+
writeFileSync(resolved.startupFile, `\uFEFF${rendered}`, 'utf16le');
|
|
208
|
+
const changed = frontend?.changed === true || !beforeLauncher?.equals(readFileSync(resolved.launcherFile))
|
|
209
|
+
|| !beforeHelper?.equals(readFileSync(resolved.helperFile))
|
|
210
|
+
|| !beforeControl?.equals(readFileSync(resolved.controlFile))
|
|
211
|
+
|| !beforeManifest?.equals(readFileSync(resolved.supervisorManifestFile))
|
|
212
|
+
|| !beforeStartup?.equals(readFileSync(resolved.startupFile));
|
|
213
|
+
return { ok: true, supported: true, changed, ...resolved };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function uninstallWindowsStartup({
|
|
217
|
+
home = homedir(),
|
|
218
|
+
startupDir,
|
|
219
|
+
platform = process.platform,
|
|
220
|
+
env = process.env,
|
|
221
|
+
} = {}) {
|
|
222
|
+
if (platform !== 'win32') return { ok: true, supported: false, removed: false };
|
|
223
|
+
const resolved = paths({ home, startupDir, env });
|
|
224
|
+
let removed = false;
|
|
225
|
+
const preserved = [];
|
|
226
|
+
for (const file of [resolved.startupFile, resolved.launcherFile, resolved.helperFile, resolved.controlFile, resolved.supervisorManifestFile]) {
|
|
227
|
+
if (!existsSync(file)) continue;
|
|
228
|
+
if (!isManagedStartupFile(file, resolved)) {
|
|
229
|
+
preserved.push(file);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
rmSync(file, { force: true });
|
|
233
|
+
removed = true;
|
|
234
|
+
}
|
|
235
|
+
return { ok: true, supported: true, removed, preserved, ...resolved };
|
|
236
|
+
}
|