@researai/deepscientist 1.5.15 → 1.5.17
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 +385 -104
- package/bin/ds.js +1241 -110
- package/docs/en/00_QUICK_START.md +100 -19
- package/docs/en/01_SETTINGS_REFERENCE.md +34 -1
- package/docs/en/02_START_RESEARCH_GUIDE.md +7 -0
- package/docs/en/05_TUI_GUIDE.md +6 -0
- package/docs/en/06_RUNTIME_AND_CANVAS.md +4 -3
- package/docs/en/09_DOCTOR.md +25 -8
- package/docs/en/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +63 -13
- package/docs/en/15_CODEX_PROVIDER_SETUP.md +37 -11
- package/docs/en/19_EXTERNAL_CONTROLLER_GUIDE.md +226 -0
- package/docs/en/19_LOCAL_BROWSER_AUTH.md +70 -0
- package/docs/en/20_WORKSPACE_MODES_GUIDE.md +250 -0
- package/docs/en/21_LOCAL_MODEL_BACKENDS_GUIDE.md +283 -0
- package/docs/en/91_DEVELOPMENT.md +237 -0
- package/docs/en/README.md +24 -2
- package/docs/zh/00_QUICK_START.md +89 -19
- package/docs/zh/01_SETTINGS_REFERENCE.md +34 -1
- package/docs/zh/02_START_RESEARCH_GUIDE.md +7 -0
- package/docs/zh/05_TUI_GUIDE.md +6 -0
- package/docs/zh/09_DOCTOR.md +26 -9
- package/docs/zh/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +63 -13
- package/docs/zh/15_CODEX_PROVIDER_SETUP.md +37 -11
- package/docs/zh/19_EXTERNAL_CONTROLLER_GUIDE.md +226 -0
- package/docs/zh/19_LOCAL_BROWSER_AUTH.md +68 -0
- package/docs/zh/20_WORKSPACE_MODES_GUIDE.md +251 -0
- package/docs/zh/21_LOCAL_MODEL_BACKENDS_GUIDE.md +281 -0
- package/docs/zh/README.md +24 -2
- package/install.sh +46 -4
- package/package.json +2 -1
- package/pyproject.toml +1 -1
- package/src/deepscientist/__init__.py +1 -1
- package/src/deepscientist/acp/envelope.py +6 -0
- package/src/deepscientist/artifact/service.py +647 -22
- package/src/deepscientist/bash_exec/service.py +234 -9
- package/src/deepscientist/bridges/connectors.py +8 -2
- package/src/deepscientist/cli.py +115 -19
- package/src/deepscientist/codex_cli_compat.py +367 -22
- package/src/deepscientist/config/models.py +2 -1
- package/src/deepscientist/config/service.py +183 -13
- package/src/deepscientist/daemon/api/handlers.py +255 -31
- package/src/deepscientist/daemon/api/router.py +9 -0
- package/src/deepscientist/daemon/app.py +1146 -105
- package/src/deepscientist/diagnostics/__init__.py +6 -0
- package/src/deepscientist/diagnostics/runner_failures.py +130 -0
- package/src/deepscientist/doctor.py +207 -3
- package/src/deepscientist/gitops/__init__.py +10 -1
- package/src/deepscientist/gitops/diff.py +129 -0
- package/src/deepscientist/gitops/service.py +4 -1
- package/src/deepscientist/mcp/server.py +39 -0
- package/src/deepscientist/prompts/builder.py +275 -34
- package/src/deepscientist/quest/layout.py +15 -2
- package/src/deepscientist/quest/service.py +707 -55
- package/src/deepscientist/quest/stage_views.py +6 -1
- package/src/deepscientist/runners/codex.py +143 -43
- package/src/deepscientist/shared.py +19 -0
- package/src/deepscientist/skills/__init__.py +2 -2
- package/src/deepscientist/skills/installer.py +196 -5
- package/src/deepscientist/skills/registry.py +66 -0
- package/src/prompts/connectors/qq.md +18 -8
- package/src/prompts/connectors/weixin.md +16 -6
- package/src/prompts/contracts/shared_interaction.md +14 -2
- package/src/prompts/system.md +23 -5
- package/src/prompts/system_copilot.md +56 -0
- package/src/skills/analysis-campaign/SKILL.md +1 -0
- package/src/skills/baseline/SKILL.md +8 -0
- package/src/skills/decision/SKILL.md +8 -0
- package/src/skills/experiment/SKILL.md +8 -0
- package/src/skills/figure-polish/SKILL.md +1 -0
- package/src/skills/finalize/SKILL.md +1 -0
- package/src/skills/idea/SKILL.md +1 -0
- package/src/skills/intake-audit/SKILL.md +8 -0
- package/src/skills/mentor/SKILL.md +217 -0
- package/src/skills/mentor/references/correction-rules.md +210 -0
- package/src/skills/mentor/references/knowledge-profile.md +91 -0
- package/src/skills/mentor/references/persona-profile.md +138 -0
- package/src/skills/mentor/references/taste-profile.md +128 -0
- package/src/skills/mentor/references/thought-style-profile.md +138 -0
- package/src/skills/mentor/references/work-profile.md +289 -0
- package/src/skills/mentor/references/workflow-profile.md +240 -0
- package/src/skills/optimize/SKILL.md +1 -0
- package/src/skills/rebuttal/SKILL.md +1 -0
- package/src/skills/review/SKILL.md +1 -0
- package/src/skills/scout/SKILL.md +8 -0
- package/src/skills/write/SKILL.md +1 -0
- package/src/tui/dist/app/AppContainer.js +19 -11
- package/src/tui/dist/index.js +4 -1
- package/src/tui/dist/lib/api.js +33 -3
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/AiManusChatView-Bv-Z8YpU.js +204 -0
- package/src/ui/dist/assets/AnalysisPlugin-BCKAfjba.js +1 -0
- package/src/ui/dist/assets/CliPlugin-BCKcpc35.js +109 -0
- package/src/ui/dist/assets/CodeEditorPlugin-DbOfSJ8K.js +2 -0
- package/src/ui/dist/assets/CodeViewerPlugin-CbaFRrUU.js +270 -0
- package/src/ui/dist/assets/DocViewerPlugin-DAjLVeQD.js +7 -0
- package/src/ui/dist/assets/GitCommitViewerPlugin-CIUqbUDO.js +1 -0
- package/src/ui/dist/assets/GitDiffViewerPlugin-CQACjoAA.js +6 -0
- package/src/ui/dist/assets/GitSnapshotViewer-0r4nLPke.js +30 -0
- package/src/ui/dist/assets/ImageViewerPlugin-nBOmI2v_.js +26 -0
- package/src/ui/dist/assets/LabCopilotPanel-BHxOxF4z.js +14 -0
- package/src/ui/dist/assets/LabPlugin-BKoZGs95.js +22 -0
- package/src/ui/dist/assets/LatexPlugin-ZwtV8pIp.js +25 -0
- package/src/ui/dist/assets/MarkdownViewerPlugin-DKqVfKyW.js +128 -0
- package/src/ui/dist/assets/MarketplacePlugin-BwxStZ9D.js +13 -0
- package/src/ui/dist/assets/NotebookEditor-BEQhaQbt.js +81 -0
- package/src/ui/dist/assets/{NotebookEditor-CccQYZjX.css → NotebookEditor-BHH8rdGj.css} +1 -1
- package/src/ui/dist/assets/NotebookEditor-BOr3x3Ej.css +1 -0
- package/src/ui/dist/assets/NotebookEditor-DB9N_T9q.js +361 -0
- package/src/ui/dist/assets/PdfLoader-Cy5jtWrr.css +1 -0
- package/src/ui/dist/assets/PdfLoader-eWBONbQP.js +16 -0
- package/src/ui/dist/assets/PdfMarkdownPlugin-D22YOZL3.js +1 -0
- package/src/ui/dist/assets/PdfViewerPlugin-c-RK9DLM.js +17 -0
- package/src/ui/dist/assets/PdfViewerPlugin-nwwE-fjJ.css +1 -0
- package/src/ui/dist/assets/SearchPlugin-CxF9ytAx.js +16 -0
- package/src/ui/dist/assets/SearchPlugin-DA4en4hK.css +1 -0
- package/src/ui/dist/assets/TextViewerPlugin-C5xqeeUH.js +54 -0
- package/src/ui/dist/assets/VNCViewer-BoLGLnHz.js +11 -0
- package/src/ui/dist/assets/bot-DREQOxzP.js +6 -0
- package/src/ui/dist/assets/browser-CTB2jwNe.js +8 -0
- package/src/ui/dist/assets/chevron-up-C9Qpx4DE.js +6 -0
- package/src/ui/dist/assets/code-WlFHE7z_.js +6 -0
- package/src/ui/dist/assets/file-content-BZMz3RYp.js +1 -0
- package/src/ui/dist/assets/file-diff-panel-CQhw0jS2.js +1 -0
- package/src/ui/dist/assets/file-jump-queue-DA-SdG__.js +1 -0
- package/src/ui/dist/assets/file-socket-CfQPKQKj.js +1 -0
- package/src/ui/dist/assets/git-commit-horizontal-DxZ8DCZh.js +6 -0
- package/src/ui/dist/assets/image-Bgl4VIyx.js +6 -0
- package/src/ui/dist/assets/index-BpV6lusQ.css +33 -0
- package/src/ui/dist/assets/index-CBNVuWcP.js +2496 -0
- package/src/ui/dist/assets/index-CwNu1aH4.js +11 -0
- package/src/ui/dist/assets/index-DrUnlf6K.js +1 -0
- package/src/ui/dist/assets/index-NW-h8VzN.js +1 -0
- package/src/ui/dist/assets/monaco-CiHMMNH_.js +1 -0
- package/src/ui/dist/assets/pdf-effect-queue-J8OnM0jE.js +6 -0
- package/src/ui/dist/assets/plugin-monaco-C8UgLomw.js +19 -0
- package/src/ui/dist/assets/plugin-notebook-HbW2K-1c.js +169 -0
- package/src/ui/dist/assets/plugin-pdf-CR8hgQBV.js +357 -0
- package/src/ui/dist/assets/plugin-terminal-MXFIPun8.js +227 -0
- package/src/ui/dist/assets/popover-CLc0pPP8.js +1 -0
- package/src/ui/dist/assets/project-sync-C9IdzdZW.js +1 -0
- package/src/ui/dist/assets/select-Cs2PmzwL.js +11 -0
- package/src/ui/dist/assets/sigma-ClKcHAXm.js +6 -0
- package/src/ui/dist/assets/trash-DwpbFr3w.js +11 -0
- package/src/ui/dist/assets/useCliAccess-NQ8m0Let.js +1 -0
- package/src/ui/dist/assets/useFileDiffOverlay-FuhcnKiw.js +1 -0
- package/src/ui/dist/assets/wrap-text-BC-Hltpd.js +11 -0
- package/src/ui/dist/assets/zoom-out-E_gaeAxL.js +11 -0
- package/src/ui/dist/index.html +5 -2
- package/src/ui/dist/assets/AiManusChatView-DDjbFnbt.js +0 -26597
- package/src/ui/dist/assets/AnalysisPlugin-Yb5IdmaU.js +0 -123
- package/src/ui/dist/assets/CliPlugin-e64sreyu.js +0 -31037
- package/src/ui/dist/assets/CodeEditorPlugin-C4D2TIkU.js +0 -427
- package/src/ui/dist/assets/CodeViewerPlugin-BVoNZIvC.js +0 -905
- package/src/ui/dist/assets/DocViewerPlugin-CLChbllo.js +0 -278
- package/src/ui/dist/assets/GitDiffViewerPlugin-C4xeFyFQ.js +0 -2661
- package/src/ui/dist/assets/ImageViewerPlugin-OiMUAcLi.js +0 -500
- package/src/ui/dist/assets/LabCopilotPanel-BjD2ThQF.js +0 -4104
- package/src/ui/dist/assets/LabPlugin-DQPg-NrB.js +0 -2677
- package/src/ui/dist/assets/LatexPlugin-CI05XAV9.js +0 -1792
- package/src/ui/dist/assets/MarkdownViewerPlugin-DpeBLYZf.js +0 -308
- package/src/ui/dist/assets/MarketplacePlugin-DolE58Q2.js +0 -413
- package/src/ui/dist/assets/NotebookEditor-7Qm2rSWD.js +0 -4214
- package/src/ui/dist/assets/NotebookEditor-C1kWaxKi.js +0 -84873
- package/src/ui/dist/assets/NotebookEditor-C3VQ7ylN.css +0 -1405
- package/src/ui/dist/assets/PdfLoader-BfOHw8Zw.js +0 -25468
- package/src/ui/dist/assets/PdfLoader-C-Y707R3.css +0 -49
- package/src/ui/dist/assets/PdfMarkdownPlugin-BulDREv1.js +0 -409
- package/src/ui/dist/assets/PdfViewerPlugin-C-daaOaL.js +0 -3095
- package/src/ui/dist/assets/PdfViewerPlugin-DQ11QcSf.css +0 -3627
- package/src/ui/dist/assets/SearchPlugin-CjpaiJ3A.js +0 -741
- package/src/ui/dist/assets/SearchPlugin-DDMrGDkh.css +0 -379
- package/src/ui/dist/assets/TextViewerPlugin-BxIyqPQC.js +0 -472
- package/src/ui/dist/assets/VNCViewer-HAg9mF7M.js +0 -18821
- package/src/ui/dist/assets/awareness-C0NPR2Dj.js +0 -292
- package/src/ui/dist/assets/bot-0DYntytV.js +0 -21
- package/src/ui/dist/assets/browser-BAcuE0Xj.js +0 -2895
- package/src/ui/dist/assets/code-B20Slj_w.js +0 -17
- package/src/ui/dist/assets/file-content-DT24KFma.js +0 -377
- package/src/ui/dist/assets/file-diff-panel-DK13YPql.js +0 -92
- package/src/ui/dist/assets/file-jump-queue-r5XKgJEV.js +0 -16
- package/src/ui/dist/assets/file-socket-B4T2o4nR.js +0 -58
- package/src/ui/dist/assets/function-B5QZkkHC.js +0 -1895
- package/src/ui/dist/assets/image-DSeR_sDS.js +0 -18
- package/src/ui/dist/assets/index-BrFje2Uk.js +0 -120
- package/src/ui/dist/assets/index-BwRJaoTl.js +0 -25
- package/src/ui/dist/assets/index-D_E4281X.js +0 -221322
- package/src/ui/dist/assets/index-DnYB3xb1.js +0 -159
- package/src/ui/dist/assets/index-G7AcWcMu.css +0 -12594
- package/src/ui/dist/assets/monaco-LExaAN3Y.js +0 -623
- package/src/ui/dist/assets/pdf-effect-queue-BJk5okWJ.js +0 -47
- package/src/ui/dist/assets/pdf_viewer-e0g1is2C.js +0 -8206
- package/src/ui/dist/assets/popover-D3Gg_FoV.js +0 -476
- package/src/ui/dist/assets/project-sync-C_ygLlVU.js +0 -297
- package/src/ui/dist/assets/select-CpAK6uWm.js +0 -1690
- package/src/ui/dist/assets/sigma-DEccaSgk.js +0 -22
- package/src/ui/dist/assets/square-check-big-uUfyVsbD.js +0 -17
- package/src/ui/dist/assets/trash-CXvwwSe8.js +0 -32
- package/src/ui/dist/assets/useCliAccess-Bnop4mgR.js +0 -957
- package/src/ui/dist/assets/useFileDiffOverlay-B8eUAX0I.js +0 -53
- package/src/ui/dist/assets/wrap-text-9vbOBpkW.js +0 -35
- package/src/ui/dist/assets/yjs-DncrqiZ8.js +0 -11243
- package/src/ui/dist/assets/zoom-out-BgVMmOW4.js +0 -34
package/bin/ds.js
CHANGED
|
@@ -36,7 +36,7 @@ const pythonCommands = new Set([
|
|
|
36
36
|
const UPDATE_PACKAGE_NAME = String(packageJson.name || '@researai/deepscientist').trim() || '@researai/deepscientist';
|
|
37
37
|
const UPDATE_CHECK_TTL_MS = 12 * 60 * 60 * 1000;
|
|
38
38
|
|
|
39
|
-
const optionsWithValues = new Set(['--home', '--host', '--port', '--quest-id', '--mode', '--proxy', '--codex-profile', '--codex']);
|
|
39
|
+
const optionsWithValues = new Set(['--home', '--host', '--port', '--quest-id', '--mode', '--proxy', '--codex-profile', '--codex', '--auth']);
|
|
40
40
|
|
|
41
41
|
function buildCodexOverrideEnv({ yolo = true, profile = null, binary = null } = {}) {
|
|
42
42
|
const normalizedProfile = typeof profile === 'string' ? profile.trim() : '';
|
|
@@ -77,6 +77,33 @@ function parseBooleanFlagValue(rawValue) {
|
|
|
77
77
|
return null;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
function parseCodexCliVersion(text) {
|
|
81
|
+
const match = String(text || '').match(/codex-cli\s+(\d+)\.(\d+)\.(\d+)/i);
|
|
82
|
+
if (!match) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function formatCodexCliVersion(version) {
|
|
89
|
+
if (!Array.isArray(version) || version.length !== 3) {
|
|
90
|
+
return '';
|
|
91
|
+
}
|
|
92
|
+
return version.join('.');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function compareCodexCliVersion(left, right) {
|
|
96
|
+
const leftParts = Array.isArray(left) ? left : [0, 0, 0];
|
|
97
|
+
const rightParts = Array.isArray(right) ? right : [0, 0, 0];
|
|
98
|
+
for (let index = 0; index < 3; index += 1) {
|
|
99
|
+
const delta = Number(leftParts[index] || 0) - Number(rightParts[index] || 0);
|
|
100
|
+
if (delta !== 0) {
|
|
101
|
+
return delta;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
80
107
|
function parseYoloArg(args, index, currentValue = true) {
|
|
81
108
|
const arg = args[index];
|
|
82
109
|
if (arg === '--yolo') {
|
|
@@ -112,6 +139,7 @@ Usage:
|
|
|
112
139
|
ds update
|
|
113
140
|
ds update --check
|
|
114
141
|
ds update --yes
|
|
142
|
+
ds uninstall
|
|
115
143
|
ds migrate /data/DeepScientist
|
|
116
144
|
ds --here
|
|
117
145
|
ds --yolo --port 20999 --here
|
|
@@ -130,6 +158,7 @@ Usage:
|
|
|
130
158
|
Launcher flags:
|
|
131
159
|
--host <host> Bind host for the local web daemon
|
|
132
160
|
--port <port> Bind port for the local web daemon
|
|
161
|
+
--auth [true|false] Require a 16-character local browser password. Default is false
|
|
133
162
|
--tui Start the terminal workspace only
|
|
134
163
|
--both Start web + terminal workspace together
|
|
135
164
|
--no-browser Do not auto-open the browser
|
|
@@ -154,6 +183,9 @@ Migration:
|
|
|
154
183
|
ds migrate <target> Move the DeepScientist home/install root to a new absolute path
|
|
155
184
|
ds migrate <target> --yes --restart
|
|
156
185
|
|
|
186
|
+
Uninstall:
|
|
187
|
+
ds uninstall Remove code/runtime only and preserve local data
|
|
188
|
+
|
|
157
189
|
Runtime:
|
|
158
190
|
DeepScientist uses uv to manage a locked local Python runtime.
|
|
159
191
|
If uv is missing, ds bootstraps a local copy under the DeepScientist home automatically.
|
|
@@ -192,6 +224,47 @@ function normalizeProxyUrl(rawValue) {
|
|
|
192
224
|
return value || null;
|
|
193
225
|
}
|
|
194
226
|
|
|
227
|
+
function normalizeLegacyHostFlagArgs(argv) {
|
|
228
|
+
const args = [];
|
|
229
|
+
let warned = false;
|
|
230
|
+
let legacyValue = null;
|
|
231
|
+
|
|
232
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
233
|
+
const arg = argv[index];
|
|
234
|
+
if (arg === '--ip') {
|
|
235
|
+
warned = true;
|
|
236
|
+
legacyValue = argv[index + 1] || legacyValue;
|
|
237
|
+
args.push('--host');
|
|
238
|
+
if (argv[index + 1]) {
|
|
239
|
+
args.push(argv[index + 1]);
|
|
240
|
+
index += 1;
|
|
241
|
+
}
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (typeof arg === 'string' && arg.startsWith('--ip=')) {
|
|
245
|
+
warned = true;
|
|
246
|
+
legacyValue = arg.slice('--ip='.length) || legacyValue;
|
|
247
|
+
args.push('--host', arg.slice('--ip='.length));
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
args.push(arg);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (!warned) {
|
|
254
|
+
return { args, warnings: [] };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const normalizedValue = String(legacyValue || '').trim();
|
|
258
|
+
const bindHint =
|
|
259
|
+
normalizedValue && ['0.0.0.0', '::', '[::]'].includes(normalizedValue)
|
|
260
|
+
? ' Note: bind-all addresses such as 0.0.0.0 are valid for `--host`, but local browser access still uses 127.0.0.1.'
|
|
261
|
+
: '';
|
|
262
|
+
return {
|
|
263
|
+
args,
|
|
264
|
+
warnings: [`Launcher note: \`--ip\` is deprecated. Use \`--host\` instead.${bindHint}`],
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
195
268
|
function applyLauncherProxy(proxyUrl) {
|
|
196
269
|
const normalized = normalizeProxyUrl(proxyUrl);
|
|
197
270
|
if (!normalized) {
|
|
@@ -562,6 +635,20 @@ function bindUiUrl(host, port) {
|
|
|
562
635
|
return `http://${formatHttpHost(normalized)}:${port}`;
|
|
563
636
|
}
|
|
564
637
|
|
|
638
|
+
function generateBrowserAuthToken() {
|
|
639
|
+
return crypto.randomBytes(8).toString('hex');
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function appendBrowserAuthToken(url, authToken) {
|
|
643
|
+
const normalized = typeof authToken === 'string' ? authToken.trim() : '';
|
|
644
|
+
if (!normalized) {
|
|
645
|
+
return url;
|
|
646
|
+
}
|
|
647
|
+
const target = new URL(url);
|
|
648
|
+
target.searchParams.set('token', normalized);
|
|
649
|
+
return target.toString();
|
|
650
|
+
}
|
|
651
|
+
|
|
565
652
|
function normalizeMode(value) {
|
|
566
653
|
const normalized = String(value || '')
|
|
567
654
|
.trim()
|
|
@@ -588,6 +675,66 @@ function parseBooleanSetting(rawValue, fallback = false) {
|
|
|
588
675
|
return fallback;
|
|
589
676
|
}
|
|
590
677
|
|
|
678
|
+
function shouldCompileRuntimeBytecode() {
|
|
679
|
+
return parseBooleanSetting(process.env.DEEPSCIENTIST_RUNTIME_COMPILE_BYTECODE, false);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
function readRequiredOptionValue(args, index, optionName) {
|
|
683
|
+
const value = args[index + 1];
|
|
684
|
+
if (!value || String(value).startsWith('--')) {
|
|
685
|
+
return {
|
|
686
|
+
ok: false,
|
|
687
|
+
error: `Missing value for ${optionName}.`,
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
return {
|
|
691
|
+
ok: true,
|
|
692
|
+
value,
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function parseStrictBooleanOption(rawValue, optionName) {
|
|
697
|
+
const parsed = parseBooleanFlagValue(rawValue);
|
|
698
|
+
if (parsed === null) {
|
|
699
|
+
return {
|
|
700
|
+
ok: false,
|
|
701
|
+
error: `Invalid value for ${optionName}: ${rawValue}. Use true or false.`,
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
return {
|
|
705
|
+
ok: true,
|
|
706
|
+
value: parsed,
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function parseStrictPortOption(rawValue, optionName) {
|
|
711
|
+
const port = Number(rawValue);
|
|
712
|
+
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
|
713
|
+
return {
|
|
714
|
+
ok: false,
|
|
715
|
+
error: `Invalid value for ${optionName}: ${rawValue}. Expected an integer between 1 and 65535.`,
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
return {
|
|
719
|
+
ok: true,
|
|
720
|
+
value: port,
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function parseStrictModeOption(rawValue, optionName) {
|
|
725
|
+
const normalized = String(rawValue || '').trim().toLowerCase();
|
|
726
|
+
if (!['web', 'tui', 'both'].includes(normalized)) {
|
|
727
|
+
return {
|
|
728
|
+
ok: false,
|
|
729
|
+
error: `Invalid value for ${optionName}: ${rawValue}. Expected one of: web, tui, both.`,
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
return {
|
|
733
|
+
ok: true,
|
|
734
|
+
value: normalized,
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
|
|
591
738
|
function supportsAnsi() {
|
|
592
739
|
return Boolean(process.stdout.isTTY && process.env.TERM !== 'dumb');
|
|
593
740
|
}
|
|
@@ -623,6 +770,64 @@ function colorize(code, text) {
|
|
|
623
770
|
return `${code}${text}\u001B[0m`;
|
|
624
771
|
}
|
|
625
772
|
|
|
773
|
+
function readCodexProviderMetadata(configDir, profile) {
|
|
774
|
+
const normalizedProfile = String(profile || '').trim();
|
|
775
|
+
const expandedDir = expandUserPath(configDir || path.join(os.homedir(), '.codex'));
|
|
776
|
+
const configPath = path.join(expandedDir, 'config.toml');
|
|
777
|
+
if (!normalizedProfile || !fs.existsSync(configPath)) {
|
|
778
|
+
return {
|
|
779
|
+
provider: null,
|
|
780
|
+
model: null,
|
|
781
|
+
envKey: null,
|
|
782
|
+
baseUrl: null,
|
|
783
|
+
wireApi: null,
|
|
784
|
+
requiresOpenAiAuth: null,
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
const text = fs.readFileSync(configPath, 'utf8');
|
|
788
|
+
const profileBlock = text.match(new RegExp(`\\[profiles\\.${normalizedProfile.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\$&')}\\]([\\s\\S]*?)(?:\\n\\[|$)`));
|
|
789
|
+
const provider = profileBlock?.[1]?.match(/^\s*model_provider\s*=\s*["']([^"']+)["']/m)?.[1]?.trim() || text.match(/^\s*model_provider\s*=\s*["']([^"']+)["']/m)?.[1]?.trim() || null;
|
|
790
|
+
const model = profileBlock?.[1]?.match(/^\s*model\s*=\s*["']([^"']+)["']/m)?.[1]?.trim() || text.match(/^\s*model\s*=\s*["']([^"']+)["']/m)?.[1]?.trim() || null;
|
|
791
|
+
if (!provider) {
|
|
792
|
+
return {
|
|
793
|
+
provider: null,
|
|
794
|
+
model,
|
|
795
|
+
envKey: null,
|
|
796
|
+
baseUrl: null,
|
|
797
|
+
wireApi: null,
|
|
798
|
+
requiresOpenAiAuth: null,
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
const providerBlock = text.match(new RegExp(`\\[model_providers\\.${provider.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\$&')}\\]([\\s\\S]*?)(?:\\n\\[|$)`));
|
|
802
|
+
const providerText = providerBlock?.[1] || '';
|
|
803
|
+
const envKey = providerText.match(/^\s*env_key\s*=\s*["']([^"']+)["']/m)?.[1]?.trim() || null;
|
|
804
|
+
const baseUrl = providerText.match(/^\s*base_url\s*=\s*["']([^"']+)["']/m)?.[1]?.trim() || null;
|
|
805
|
+
const wireApi = providerText.match(/^\s*wire_api\s*=\s*["']([^"']+)["']/m)?.[1]?.trim() || null;
|
|
806
|
+
const requiresOpenAiAuthRaw = providerText.match(/^\s*requires_openai_auth\s*=\s*(true|false)\s*$/m)?.[1] || null;
|
|
807
|
+
const requiresOpenAiAuth = requiresOpenAiAuthRaw === null ? null : requiresOpenAiAuthRaw === 'true';
|
|
808
|
+
return {
|
|
809
|
+
provider,
|
|
810
|
+
model,
|
|
811
|
+
envKey,
|
|
812
|
+
baseUrl,
|
|
813
|
+
wireApi,
|
|
814
|
+
requiresOpenAiAuth,
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
function installedCodexCliVersion(binaryPath) {
|
|
819
|
+
const resolved = resolveExecutableOnPath(binaryPath || 'codex') || binaryPath || 'codex';
|
|
820
|
+
try {
|
|
821
|
+
const result = spawnSync(resolved, ['--version'], syncSpawnOptions({ encoding: 'utf8' }));
|
|
822
|
+
if (result.status !== 0) {
|
|
823
|
+
return null;
|
|
824
|
+
}
|
|
825
|
+
return parseCodexCliVersion(`${result.stdout || ''}\n${result.stderr || ''}`);
|
|
826
|
+
} catch {
|
|
827
|
+
return null;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
626
831
|
const OFFICIAL_REPOSITORY_URL = 'https://github.com/ResearAI/DeepScientist';
|
|
627
832
|
|
|
628
833
|
function officialRepositoryLine() {
|
|
@@ -685,7 +890,7 @@ function pythonVersionText(probe) {
|
|
|
685
890
|
return version;
|
|
686
891
|
}
|
|
687
892
|
|
|
688
|
-
function renderLaunchHints({ home, url, bindUrl, pythonSelection, yolo }) {
|
|
893
|
+
function renderLaunchHints({ home, url, bindUrl, pythonSelection, yolo, authEnabled, authToken }) {
|
|
689
894
|
const runtimeRows = [
|
|
690
895
|
['Version', packageJson.version],
|
|
691
896
|
['Home', truncateMiddle(home)],
|
|
@@ -694,6 +899,9 @@ function renderLaunchHints({ home, url, bindUrl, pythonSelection, yolo }) {
|
|
|
694
899
|
['Python', truncateMiddle(pythonVersionText(pythonSelection))],
|
|
695
900
|
['Codex mode', yolo ? 'YOLO (never + danger-full-access)' : 'Default (on-request + workspace-write)'],
|
|
696
901
|
];
|
|
902
|
+
if (authEnabled && authToken) {
|
|
903
|
+
runtimeRows.splice(4, 0, ['Auth token', authToken]);
|
|
904
|
+
}
|
|
697
905
|
if (pythonSelection && pythonSelection.sourceLabel) {
|
|
698
906
|
runtimeRows.push(['Python source', pythonSelection.sourceLabel]);
|
|
699
907
|
}
|
|
@@ -706,6 +914,7 @@ function renderLaunchHints({ home, url, bindUrl, pythonSelection, yolo }) {
|
|
|
706
914
|
['ds --yolo --port 20999 --here', 'Start in ./DeepScientist under the current directory with YOLO Codex access'],
|
|
707
915
|
['ds --port 21000', 'Change the web port'],
|
|
708
916
|
['ds --host 0.0.0.0 --port 21000', 'Bind on all interfaces'],
|
|
917
|
+
['ds --auth true', 'Enable the local browser password for this launch'],
|
|
709
918
|
['ds --here', 'Use ./DeepScientist under the current directory as home'],
|
|
710
919
|
['ds --both', 'Start web + TUI together'],
|
|
711
920
|
['ds --tui', 'Start the terminal workspace only'],
|
|
@@ -729,6 +938,8 @@ function printLaunchCard({
|
|
|
729
938
|
home,
|
|
730
939
|
pythonSelection,
|
|
731
940
|
yolo,
|
|
941
|
+
authEnabled,
|
|
942
|
+
authToken,
|
|
732
943
|
}) {
|
|
733
944
|
const width = Math.max(72, Math.min(process.stdout.columns || 100, 108));
|
|
734
945
|
const divider = colorize('\u001B[38;5;245m', '─'.repeat(Math.max(36, width - 6)));
|
|
@@ -787,13 +998,18 @@ function printLaunchCard({
|
|
|
787
998
|
console.log(centerText(colorize('\u001B[1m', workspaceMode), width));
|
|
788
999
|
console.log(centerText(urlLabel, width));
|
|
789
1000
|
console.log(centerText(divider, width));
|
|
1001
|
+
if (authEnabled && authToken) {
|
|
1002
|
+
console.log('');
|
|
1003
|
+
console.log(centerText(colorize('\u001B[1;38;5;214m', authToken), width));
|
|
1004
|
+
console.log('');
|
|
1005
|
+
}
|
|
790
1006
|
console.log(centerText(browserLine, width));
|
|
791
1007
|
console.log(centerText(nextStep, width));
|
|
792
1008
|
console.log(centerText('Run ds --stop to stop the managed daemon.', width));
|
|
793
1009
|
console.log(centerText('Need to move this installation later? Use ds migrate /new/path.', width));
|
|
794
1010
|
console.log(centerText(officialRepositoryLine(), width));
|
|
795
1011
|
console.log('');
|
|
796
|
-
renderLaunchHints({ home, url, bindUrl, pythonSelection, yolo });
|
|
1012
|
+
renderLaunchHints({ home, url, bindUrl, pythonSelection, yolo, authEnabled, authToken });
|
|
797
1013
|
}
|
|
798
1014
|
|
|
799
1015
|
function escapeHtml(value) {
|
|
@@ -816,10 +1032,10 @@ function writeCodexPreflightReport(home, probe) {
|
|
|
816
1032
|
const profile = typeof details.profile === 'string' ? details.profile.trim() : '';
|
|
817
1033
|
const intro = profile
|
|
818
1034
|
? `DeepScientist blocked startup because the Codex hello probe did not pass for profile \`${profile}\`. Verify that \`codex --profile ${profile}\` works on this machine and that the profile's provider-specific API key, Base URL, and model configuration are already set up.`
|
|
819
|
-
: 'DeepScientist blocked startup because the Codex hello probe did not pass. In most installs, `npm install -g @researai/deepscientist` also installs the bundled Codex dependency. If `codex` is still missing, repair it with `npm install -g @openai/codex`. Then run `codex
|
|
1035
|
+
: 'DeepScientist blocked startup because the Codex hello probe did not pass. In most installs, `npm install -g @researai/deepscientist` also installs the bundled Codex dependency. If `codex` is still missing, repair it with `npm install -g @openai/codex`. Then run `codex login` (or just `codex`), finish authentication, run `ds doctor`, and launch `ds` again.';
|
|
820
1036
|
const introZh = profile
|
|
821
1037
|
? `DeepScientist 启动前进行了 Codex 可用性检查,但 profile \`${profile}\` 的 hello 探测没有通过。请先确认 \`codex --profile ${profile}\` 在当前机器上可以正常启动,并确保该 profile 依赖的 provider API Key、Base URL 和模型配置都已经在 Codex 中配置好。`
|
|
822
|
-
: 'DeepScientist 启动前进行了 Codex 可用性检查,但 hello 探测没有通过。正常情况下,`npm install -g @researai/deepscientist` 也会一并安装 bundled Codex 依赖;如果此后 `codex` 仍不可用,请再执行 `npm install -g @openai/codex` 修复。然后运行 `codex
|
|
1038
|
+
: 'DeepScientist 启动前进行了 Codex 可用性检查,但 hello 探测没有通过。正常情况下,`npm install -g @researai/deepscientist` 也会一并安装 bundled Codex 依赖;如果此后 `codex` 仍不可用,请再执行 `npm install -g @openai/codex` 修复。然后运行 `codex login`(或直接运行 `codex`)完成认证,再执行 `ds doctor`,最后重新启动 `ds`。';
|
|
823
1039
|
const renderItems = (items, tone) =>
|
|
824
1040
|
items
|
|
825
1041
|
.map(
|
|
@@ -1020,6 +1236,7 @@ function parseLauncherArgs(argv) {
|
|
|
1020
1236
|
let daemonOnly = false;
|
|
1021
1237
|
let skipUpdateCheck = false;
|
|
1022
1238
|
let yolo = true;
|
|
1239
|
+
let auth = null;
|
|
1023
1240
|
let codexProfile = null;
|
|
1024
1241
|
let codexBinary = null;
|
|
1025
1242
|
|
|
@@ -1040,21 +1257,71 @@ function parseLauncherArgs(argv) {
|
|
|
1040
1257
|
else if (arg === '--open-browser') openBrowser = true;
|
|
1041
1258
|
else if (arg === '--daemon-only') daemonOnly = true;
|
|
1042
1259
|
else if (arg === '--skip-update-check') skipUpdateCheck = true;
|
|
1260
|
+
else if (arg === '--here') continue;
|
|
1043
1261
|
else {
|
|
1044
1262
|
const parsedYolo = parseYoloArg(args, index, yolo);
|
|
1045
1263
|
if (parsedYolo.matched) {
|
|
1046
1264
|
yolo = parsedYolo.value;
|
|
1047
1265
|
index += Math.max(0, parsedYolo.consumed - 1);
|
|
1048
|
-
} else if (arg === '--
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
else if (arg === '
|
|
1266
|
+
} else if (arg === '--auth') {
|
|
1267
|
+
const next = readRequiredOptionValue(args, index, '--auth');
|
|
1268
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1269
|
+
const parsed = parseStrictBooleanOption(next.value, '--auth');
|
|
1270
|
+
if (!parsed.ok) return { help: false, error: parsed.error };
|
|
1271
|
+
auth = parsed.value;
|
|
1272
|
+
index += 1;
|
|
1273
|
+
} else if (typeof arg === 'string' && arg.startsWith('--auth=')) {
|
|
1274
|
+
const parsed = parseStrictBooleanOption(arg.slice('--auth='.length), '--auth');
|
|
1275
|
+
if (!parsed.ok) return { help: false, error: parsed.error };
|
|
1276
|
+
auth = parsed.value;
|
|
1277
|
+
} else if (arg === '--codex-profile') {
|
|
1278
|
+
const next = readRequiredOptionValue(args, index, '--codex-profile');
|
|
1279
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1280
|
+
codexProfile = next.value;
|
|
1281
|
+
index += 1;
|
|
1282
|
+
} else if (arg === '--codex') {
|
|
1283
|
+
const next = readRequiredOptionValue(args, index, '--codex');
|
|
1284
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1285
|
+
codexBinary = next.value;
|
|
1286
|
+
index += 1;
|
|
1287
|
+
} else if (arg === '--host') {
|
|
1288
|
+
const next = readRequiredOptionValue(args, index, '--host');
|
|
1289
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1290
|
+
host = next.value;
|
|
1291
|
+
index += 1;
|
|
1292
|
+
} else if (arg === '--port') {
|
|
1293
|
+
const next = readRequiredOptionValue(args, index, '--port');
|
|
1294
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1295
|
+
const parsed = parseStrictPortOption(next.value, '--port');
|
|
1296
|
+
if (!parsed.ok) return { help: false, error: parsed.error };
|
|
1297
|
+
port = parsed.value;
|
|
1298
|
+
index += 1;
|
|
1299
|
+
} else if (arg === '--home') {
|
|
1300
|
+
const next = readRequiredOptionValue(args, index, '--home');
|
|
1301
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1302
|
+
home = path.resolve(next.value);
|
|
1303
|
+
index += 1;
|
|
1304
|
+
} else if (arg === '--proxy') {
|
|
1305
|
+
const next = readRequiredOptionValue(args, index, '--proxy');
|
|
1306
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1307
|
+
proxy = next.value;
|
|
1308
|
+
index += 1;
|
|
1309
|
+
} else if (arg === '--quest-id') {
|
|
1310
|
+
const next = readRequiredOptionValue(args, index, '--quest-id');
|
|
1311
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1312
|
+
questId = next.value;
|
|
1313
|
+
index += 1;
|
|
1314
|
+
} else if (arg === '--mode') {
|
|
1315
|
+
const next = readRequiredOptionValue(args, index, '--mode');
|
|
1316
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1317
|
+
const parsed = parseStrictModeOption(next.value, '--mode');
|
|
1318
|
+
if (!parsed.ok) return { help: false, error: parsed.error };
|
|
1319
|
+
mode = parsed.value;
|
|
1320
|
+
index += 1;
|
|
1321
|
+
}
|
|
1056
1322
|
else if (arg === '--help' || arg === '-h') return { help: true };
|
|
1057
|
-
else if (
|
|
1323
|
+
else if (arg.startsWith('--')) return { help: false, error: `Unknown launcher flag: ${arg}` };
|
|
1324
|
+
else return { help: false, error: `Unexpected launcher argument: ${arg}` };
|
|
1058
1325
|
}
|
|
1059
1326
|
}
|
|
1060
1327
|
|
|
@@ -1073,8 +1340,10 @@ function parseLauncherArgs(argv) {
|
|
|
1073
1340
|
daemonOnly,
|
|
1074
1341
|
skipUpdateCheck,
|
|
1075
1342
|
yolo,
|
|
1343
|
+
auth,
|
|
1076
1344
|
codexProfile,
|
|
1077
1345
|
codexBinary,
|
|
1346
|
+
error: null,
|
|
1078
1347
|
};
|
|
1079
1348
|
}
|
|
1080
1349
|
|
|
@@ -1116,6 +1385,26 @@ Flags:
|
|
|
1116
1385
|
`);
|
|
1117
1386
|
}
|
|
1118
1387
|
|
|
1388
|
+
function printUninstallHelp() {
|
|
1389
|
+
console.log(`DeepScientist uninstall
|
|
1390
|
+
|
|
1391
|
+
Usage:
|
|
1392
|
+
ds uninstall
|
|
1393
|
+
ds uninstall --home /absolute/home/path
|
|
1394
|
+
ds uninstall --yes
|
|
1395
|
+
|
|
1396
|
+
Behavior:
|
|
1397
|
+
- removes DeepScientist code, launcher wrappers, and local runtime code
|
|
1398
|
+
- preserves local data such as quests, memory, config, logs, plugins, and cache
|
|
1399
|
+
- if this command is run from the globally installed npm package, it also removes the npm package itself
|
|
1400
|
+
|
|
1401
|
+
Flags:
|
|
1402
|
+
--yes Skip the interactive confirmation prompt
|
|
1403
|
+
--home <path> Override the target DeepScientist home/root
|
|
1404
|
+
--origin <value> Internal use for npm uninstall integration
|
|
1405
|
+
`);
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1119
1408
|
function parseUpdateArgs(argv) {
|
|
1120
1409
|
const args = [...argv];
|
|
1121
1410
|
if (args[0] === 'update') {
|
|
@@ -1148,12 +1437,32 @@ function parseUpdateArgs(argv) {
|
|
|
1148
1437
|
else if (arg === '--worker') worker = true;
|
|
1149
1438
|
else if (arg === '--restart-daemon') restartDaemon = true;
|
|
1150
1439
|
else if (arg === '--skip-update-check') skipUpdateCheck = true;
|
|
1151
|
-
else if (arg === '--home'
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1440
|
+
else if (arg === '--home') {
|
|
1441
|
+
const next = readRequiredOptionValue(args, index, '--home');
|
|
1442
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1443
|
+
home = path.resolve(next.value);
|
|
1444
|
+
index += 1;
|
|
1445
|
+
} else if (arg === '--host') {
|
|
1446
|
+
const next = readRequiredOptionValue(args, index, '--host');
|
|
1447
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1448
|
+
host = next.value;
|
|
1449
|
+
index += 1;
|
|
1450
|
+
} else if (arg === '--port') {
|
|
1451
|
+
const next = readRequiredOptionValue(args, index, '--port');
|
|
1452
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1453
|
+
const parsed = parseStrictPortOption(next.value, '--port');
|
|
1454
|
+
if (!parsed.ok) return { help: false, error: parsed.error };
|
|
1455
|
+
port = parsed.value;
|
|
1456
|
+
index += 1;
|
|
1457
|
+
} else if (arg === '--proxy') {
|
|
1458
|
+
const next = readRequiredOptionValue(args, index, '--proxy');
|
|
1459
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1460
|
+
proxy = next.value;
|
|
1461
|
+
index += 1;
|
|
1462
|
+
}
|
|
1155
1463
|
else if (arg === '--help' || arg === '-h') return { help: true };
|
|
1156
|
-
else if (
|
|
1464
|
+
else if (arg.startsWith('--')) return { help: false, error: `Unknown update flag: ${arg}` };
|
|
1465
|
+
else return { help: false, error: `Unexpected update argument: ${arg}` };
|
|
1157
1466
|
}
|
|
1158
1467
|
|
|
1159
1468
|
return {
|
|
@@ -1172,6 +1481,7 @@ function parseUpdateArgs(argv) {
|
|
|
1172
1481
|
proxy,
|
|
1173
1482
|
restartDaemon,
|
|
1174
1483
|
skipUpdateCheck,
|
|
1484
|
+
error: null,
|
|
1175
1485
|
};
|
|
1176
1486
|
}
|
|
1177
1487
|
|
|
@@ -1189,15 +1499,23 @@ function parseMigrateArgs(argv) {
|
|
|
1189
1499
|
const arg = args[index];
|
|
1190
1500
|
if (arg === '--yes') yes = true;
|
|
1191
1501
|
else if (arg === '--restart') restart = true;
|
|
1192
|
-
else if (arg === '--home'
|
|
1502
|
+
else if (arg === '--home') {
|
|
1503
|
+
const next = readRequiredOptionValue(args, index, '--home');
|
|
1504
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1505
|
+
home = path.resolve(expandUserPath(next.value));
|
|
1506
|
+
index += 1;
|
|
1507
|
+
}
|
|
1193
1508
|
else if (arg === '--help' || arg === '-h') return { help: true };
|
|
1194
|
-
else if (arg.startsWith('--')) return
|
|
1509
|
+
else if (arg.startsWith('--')) return { help: false, error: `Unknown migrate flag: ${arg}` };
|
|
1195
1510
|
else if (!target) target = path.resolve(expandUserPath(arg));
|
|
1196
|
-
else return
|
|
1511
|
+
else return { help: false, error: `Unexpected migrate argument: ${arg}` };
|
|
1197
1512
|
}
|
|
1198
1513
|
|
|
1199
1514
|
if (!target) {
|
|
1200
|
-
return
|
|
1515
|
+
return {
|
|
1516
|
+
help: false,
|
|
1517
|
+
error: 'Missing migration target path.',
|
|
1518
|
+
};
|
|
1201
1519
|
}
|
|
1202
1520
|
|
|
1203
1521
|
return {
|
|
@@ -1206,6 +1524,44 @@ function parseMigrateArgs(argv) {
|
|
|
1206
1524
|
target,
|
|
1207
1525
|
yes,
|
|
1208
1526
|
restart,
|
|
1527
|
+
error: null,
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
function parseUninstallArgs(argv) {
|
|
1532
|
+
const args = [...argv];
|
|
1533
|
+
if (args[0] === 'uninstall') {
|
|
1534
|
+
args.shift();
|
|
1535
|
+
}
|
|
1536
|
+
let home = null;
|
|
1537
|
+
let yes = false;
|
|
1538
|
+
let origin = null;
|
|
1539
|
+
|
|
1540
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1541
|
+
const arg = args[index];
|
|
1542
|
+
if (arg === '--yes') yes = true;
|
|
1543
|
+
else if (arg === '--home') {
|
|
1544
|
+
const next = readRequiredOptionValue(args, index, '--home');
|
|
1545
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1546
|
+
home = path.resolve(expandUserPath(next.value));
|
|
1547
|
+
index += 1;
|
|
1548
|
+
} else if (arg === '--origin') {
|
|
1549
|
+
const next = readRequiredOptionValue(args, index, '--origin');
|
|
1550
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1551
|
+
origin = String(next.value || '').trim().toLowerCase() || null;
|
|
1552
|
+
index += 1;
|
|
1553
|
+
}
|
|
1554
|
+
else if (arg === '--help' || arg === '-h') return { help: true };
|
|
1555
|
+
else if (arg.startsWith('--')) return { help: false, error: `Unknown uninstall flag: ${arg}` };
|
|
1556
|
+
else return { help: false, error: `Unexpected uninstall argument: ${arg}` };
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
return {
|
|
1560
|
+
help: false,
|
|
1561
|
+
home,
|
|
1562
|
+
yes,
|
|
1563
|
+
origin,
|
|
1564
|
+
error: null,
|
|
1209
1565
|
};
|
|
1210
1566
|
}
|
|
1211
1567
|
|
|
@@ -2152,13 +2508,34 @@ function ensureUvBinary(home) {
|
|
|
2152
2508
|
}
|
|
2153
2509
|
|
|
2154
2510
|
function buildUvRuntimeEnv(home, extraEnv = {}) {
|
|
2155
|
-
|
|
2511
|
+
const env = {
|
|
2156
2512
|
...process.env,
|
|
2157
2513
|
UV_CACHE_DIR: runtimeUvCachePath(home),
|
|
2158
2514
|
UV_PROJECT_ENVIRONMENT: runtimePythonEnvPath(home),
|
|
2159
2515
|
UV_PYTHON_INSTALL_DIR: runtimeUvPythonInstallPath(home),
|
|
2160
2516
|
...extraEnv,
|
|
2161
2517
|
};
|
|
2518
|
+
for (const key of ['PYTHONPATH', 'PYTHONHOME', 'VIRTUAL_ENV', '__PYVENV_LAUNCHER__']) {
|
|
2519
|
+
delete env[key];
|
|
2520
|
+
}
|
|
2521
|
+
for (const key of Object.keys(env)) {
|
|
2522
|
+
if (key === 'CONDA_EXE' || key === 'CONDA_PYTHON_EXE' || key === '_CE_CONDA' || key === '_CE_M') {
|
|
2523
|
+
delete env[key];
|
|
2524
|
+
continue;
|
|
2525
|
+
}
|
|
2526
|
+
if (key === 'MAMBA_EXE' || key === 'MAMBA_ROOT_PREFIX') {
|
|
2527
|
+
delete env[key];
|
|
2528
|
+
continue;
|
|
2529
|
+
}
|
|
2530
|
+
if (key === 'CONDA_PREFIX' || key === 'CONDA_DEFAULT_ENV' || key === 'CONDA_PROMPT_MODIFIER' || key === 'CONDA_SHLVL') {
|
|
2531
|
+
delete env[key];
|
|
2532
|
+
continue;
|
|
2533
|
+
}
|
|
2534
|
+
if (/^CONDA_PREFIX_\d+$/.test(key)) {
|
|
2535
|
+
delete env[key];
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
return env;
|
|
2162
2539
|
}
|
|
2163
2540
|
|
|
2164
2541
|
function ensureUvLockPresent() {
|
|
@@ -2171,6 +2548,43 @@ function ensureUvLockPresent() {
|
|
|
2171
2548
|
process.exit(1);
|
|
2172
2549
|
}
|
|
2173
2550
|
|
|
2551
|
+
function buildUvSyncFailureGuidance({ installMode = detectInstallMode(repoRoot), env = process.env } = {}) {
|
|
2552
|
+
const guidance = [];
|
|
2553
|
+
if (installMode === 'source-checkout') {
|
|
2554
|
+
guidance.push('If you changed Python dependencies in a source checkout, run `uv lock` and try again.');
|
|
2555
|
+
} else {
|
|
2556
|
+
guidance.push('This npm install already includes a locked `uv.lock`, so this is usually a local Python or network environment issue rather than a missing lockfile.');
|
|
2557
|
+
guidance.push('Re-run `ds` in a clean shell first. If you have an active conda or virtualenv, try deactivating it before starting DeepScientist.');
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
const hasPythonEnv =
|
|
2561
|
+
Boolean(String(env.VIRTUAL_ENV || '').trim())
|
|
2562
|
+
|| Boolean(String(env.CONDA_PREFIX || '').trim())
|
|
2563
|
+
|| Boolean(String(env.PYTHONPATH || '').trim())
|
|
2564
|
+
|| Boolean(String(env.PYTHONHOME || '').trim());
|
|
2565
|
+
if (hasPythonEnv) {
|
|
2566
|
+
guidance.push('An active Python environment was detected. `VIRTUAL_ENV`, `CONDA_PREFIX`, `PYTHONPATH`, or `PYTHONHOME` can interfere with uv runtime bootstrap.');
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
const hasCustomIndex =
|
|
2570
|
+
Object.keys(env).some((key) => /^PIP_/i.test(key))
|
|
2571
|
+
|| Boolean(String(env.UV_INDEX_URL || '').trim())
|
|
2572
|
+
|| Boolean(String(env.UV_EXTRA_INDEX_URL || '').trim());
|
|
2573
|
+
if (hasCustomIndex) {
|
|
2574
|
+
guidance.push('Custom package index settings were detected. Check `PIP_*`, `UV_INDEX_URL`, or `UV_EXTRA_INDEX_URL` if uv could not download packages.');
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
const hasProxyOrCert =
|
|
2578
|
+
['HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY', 'http_proxy', 'https_proxy', 'all_proxy', 'SSL_CERT_FILE', 'REQUESTS_CA_BUNDLE']
|
|
2579
|
+
.some((key) => Boolean(String(env[key] || '').trim()));
|
|
2580
|
+
if (hasProxyOrCert) {
|
|
2581
|
+
guidance.push('Proxy or certificate overrides were detected. If uv reported TLS, certificate, or download errors above, verify those settings and try again.');
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
guidance.push('Look at the uv error printed above this message. That original uv output is the real failure reason.');
|
|
2585
|
+
return guidance;
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2174
2588
|
function resolveUvVersion(uvBinary) {
|
|
2175
2589
|
const result = runSync(uvBinary, ['--version'], { capture: true, allowFailure: true });
|
|
2176
2590
|
if (result.status !== 0) {
|
|
@@ -2237,7 +2651,10 @@ function resolveBackgroundPythonExecutable(runtimePython) {
|
|
|
2237
2651
|
}
|
|
2238
2652
|
|
|
2239
2653
|
function syncUvProjectEnvironment(home, uvBinary, pythonTarget, editable) {
|
|
2240
|
-
const args = ['sync', '--frozen', '--no-dev', '--
|
|
2654
|
+
const args = ['sync', '--frozen', '--no-dev', '--python', pythonTarget];
|
|
2655
|
+
if (shouldCompileRuntimeBytecode()) {
|
|
2656
|
+
args.splice(3, 0, '--compile-bytecode');
|
|
2657
|
+
}
|
|
2241
2658
|
if (!editable) {
|
|
2242
2659
|
args.push('--no-editable');
|
|
2243
2660
|
}
|
|
@@ -2250,7 +2667,9 @@ function syncUvProjectEnvironment(home, uvBinary, pythonTarget, editable) {
|
|
|
2250
2667
|
return;
|
|
2251
2668
|
}
|
|
2252
2669
|
console.error('DeepScientist could not sync the locked Python environment with uv.');
|
|
2253
|
-
|
|
2670
|
+
for (const line of buildUvSyncFailureGuidance()) {
|
|
2671
|
+
console.error(line);
|
|
2672
|
+
}
|
|
2254
2673
|
process.exit(result.status ?? 1);
|
|
2255
2674
|
}
|
|
2256
2675
|
|
|
@@ -2629,6 +3048,374 @@ function removeDaemonState(home) {
|
|
|
2629
3048
|
}
|
|
2630
3049
|
}
|
|
2631
3050
|
|
|
3051
|
+
function installIndexPath() {
|
|
3052
|
+
return path.join(os.homedir(), '.deepscientist', 'install-index.json');
|
|
3053
|
+
}
|
|
3054
|
+
|
|
3055
|
+
function readInstallIndex() {
|
|
3056
|
+
const indexPath = installIndexPath();
|
|
3057
|
+
if (!fs.existsSync(indexPath)) {
|
|
3058
|
+
return { installs: [] };
|
|
3059
|
+
}
|
|
3060
|
+
try {
|
|
3061
|
+
const payload = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
|
|
3062
|
+
const installs = Array.isArray(payload?.installs) ? payload.installs.filter((item) => item && typeof item === 'object') : [];
|
|
3063
|
+
return { installs };
|
|
3064
|
+
} catch {
|
|
3065
|
+
return { installs: [] };
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
function writeInstallIndex(payload) {
|
|
3070
|
+
const normalized = {
|
|
3071
|
+
installs: Array.isArray(payload?.installs) ? payload.installs : [],
|
|
3072
|
+
};
|
|
3073
|
+
const targetPath = installIndexPath();
|
|
3074
|
+
ensureDir(path.dirname(targetPath));
|
|
3075
|
+
fs.writeFileSync(targetPath, `${JSON.stringify(normalized, null, 2)}\n`, 'utf8');
|
|
3076
|
+
}
|
|
3077
|
+
|
|
3078
|
+
function normalizeInstallRecord(record) {
|
|
3079
|
+
const normalizedHome = normalizeHomePath(record?.home || '');
|
|
3080
|
+
if (!normalizedHome) {
|
|
3081
|
+
return null;
|
|
3082
|
+
}
|
|
3083
|
+
const installMode = String(record?.install_mode || '').trim() || null;
|
|
3084
|
+
const installDir = record?.install_dir ? normalizeHomePath(record.install_dir) : null;
|
|
3085
|
+
const packageRoot = record?.package_root ? normalizeHomePath(record.package_root) : null;
|
|
3086
|
+
const launcherPath = record?.launcher_path ? path.resolve(String(record.launcher_path)) : null;
|
|
3087
|
+
const wrapperPaths = Array.isArray(record?.wrapper_paths)
|
|
3088
|
+
? [...new Set(record.wrapper_paths.map((item) => String(item || '').trim()).filter(Boolean).map((item) => path.resolve(item)))]
|
|
3089
|
+
: [];
|
|
3090
|
+
const createdAt = String(record?.created_at || '').trim() || new Date().toISOString();
|
|
3091
|
+
return {
|
|
3092
|
+
home: normalizedHome,
|
|
3093
|
+
install_mode: installMode,
|
|
3094
|
+
install_dir: installDir,
|
|
3095
|
+
package_root: packageRoot,
|
|
3096
|
+
launcher_path: launcherPath,
|
|
3097
|
+
wrapper_paths: wrapperPaths,
|
|
3098
|
+
created_at: createdAt,
|
|
3099
|
+
updated_at: new Date().toISOString(),
|
|
3100
|
+
};
|
|
3101
|
+
}
|
|
3102
|
+
|
|
3103
|
+
function installRecordMatches(left, right) {
|
|
3104
|
+
return left.home === right.home
|
|
3105
|
+
&& (left.install_dir || null) === (right.install_dir || null)
|
|
3106
|
+
&& (left.package_root || null) === (right.package_root || null)
|
|
3107
|
+
&& (left.install_mode || null) === (right.install_mode || null);
|
|
3108
|
+
}
|
|
3109
|
+
|
|
3110
|
+
function upsertInstallRecord(record) {
|
|
3111
|
+
const normalized = normalizeInstallRecord(record);
|
|
3112
|
+
if (!normalized) {
|
|
3113
|
+
return null;
|
|
3114
|
+
}
|
|
3115
|
+
const index = readInstallIndex();
|
|
3116
|
+
const installs = index.installs
|
|
3117
|
+
.map((item) => normalizeInstallRecord(item))
|
|
3118
|
+
.filter(Boolean);
|
|
3119
|
+
const nextInstalls = installs.filter((item) => !installRecordMatches(item, normalized));
|
|
3120
|
+
nextInstalls.push(normalized);
|
|
3121
|
+
nextInstalls.sort((left, right) => String(left.updated_at || '').localeCompare(String(right.updated_at || '')));
|
|
3122
|
+
writeInstallIndex({ installs: nextInstalls });
|
|
3123
|
+
return normalized;
|
|
3124
|
+
}
|
|
3125
|
+
|
|
3126
|
+
function removeInstallRecords(predicate) {
|
|
3127
|
+
const index = readInstallIndex();
|
|
3128
|
+
const installs = index.installs
|
|
3129
|
+
.map((item) => normalizeInstallRecord(item))
|
|
3130
|
+
.filter(Boolean);
|
|
3131
|
+
const nextInstalls = installs.filter((item) => !predicate(item));
|
|
3132
|
+
writeInstallIndex({ installs: nextInstalls });
|
|
3133
|
+
return nextInstalls;
|
|
3134
|
+
}
|
|
3135
|
+
|
|
3136
|
+
function parseManagedWrapperCandidate(candidatePath) {
|
|
3137
|
+
let stat = null;
|
|
3138
|
+
try {
|
|
3139
|
+
stat = fs.lstatSync(candidatePath);
|
|
3140
|
+
} catch {
|
|
3141
|
+
return null;
|
|
3142
|
+
}
|
|
3143
|
+
|
|
3144
|
+
if (!stat.isFile() && !stat.isSymbolicLink()) {
|
|
3145
|
+
return null;
|
|
3146
|
+
}
|
|
3147
|
+
|
|
3148
|
+
let text = '';
|
|
3149
|
+
try {
|
|
3150
|
+
text = fs.readFileSync(candidatePath, 'utf8');
|
|
3151
|
+
} catch {
|
|
3152
|
+
return null;
|
|
3153
|
+
}
|
|
3154
|
+
|
|
3155
|
+
const homeMatch = text.match(/export DEEPSCIENTIST_HOME="([^"\n]+)"/);
|
|
3156
|
+
const execMatch = text.match(/exec "([^"\n]+)" "\$@"/);
|
|
3157
|
+
return {
|
|
3158
|
+
path: path.resolve(candidatePath),
|
|
3159
|
+
home: homeMatch ? normalizeHomePath(homeMatch[1]) : null,
|
|
3160
|
+
execPath: execMatch ? path.resolve(execMatch[1]) : null,
|
|
3161
|
+
};
|
|
3162
|
+
}
|
|
3163
|
+
|
|
3164
|
+
function collectManagedWrapperPaths({ home, installDir = null, explicitWrapperPaths = [] }) {
|
|
3165
|
+
const normalizedHome = normalizeHomePath(home);
|
|
3166
|
+
const normalizedInstallDir = installDir ? normalizeHomePath(installDir) : null;
|
|
3167
|
+
const candidates = new Set(explicitWrapperPaths.map((item) => path.resolve(String(item))));
|
|
3168
|
+
for (const commandName of launcherWrapperCommands) {
|
|
3169
|
+
for (const candidate of candidateWrapperPathsForCommand(commandName)) {
|
|
3170
|
+
candidates.add(candidate);
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
const matched = [];
|
|
3174
|
+
for (const candidate of candidates) {
|
|
3175
|
+
const parsed = parseManagedWrapperCandidate(candidate);
|
|
3176
|
+
if (!parsed) {
|
|
3177
|
+
continue;
|
|
3178
|
+
}
|
|
3179
|
+
if (parsed.home && parsed.home === normalizedHome) {
|
|
3180
|
+
matched.push(parsed.path);
|
|
3181
|
+
continue;
|
|
3182
|
+
}
|
|
3183
|
+
if (normalizedInstallDir && parsed.execPath && parsed.execPath.startsWith(path.join(normalizedInstallDir, 'bin'))) {
|
|
3184
|
+
matched.push(parsed.path);
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
return [...new Set(matched)].sort();
|
|
3188
|
+
}
|
|
3189
|
+
|
|
3190
|
+
function buildCodeOnlyUninstallPlan({ home, installDir = null, wrapperPaths = [] }) {
|
|
3191
|
+
const normalizedHome = normalizeHomePath(home);
|
|
3192
|
+
const normalizedInstallDir = installDir ? normalizeHomePath(installDir) : null;
|
|
3193
|
+
const removePaths = [
|
|
3194
|
+
path.join(normalizedHome, 'runtime', 'python-env'),
|
|
3195
|
+
path.join(normalizedHome, 'runtime', 'python'),
|
|
3196
|
+
path.join(normalizedHome, 'runtime', 'tools'),
|
|
3197
|
+
path.join(normalizedHome, 'runtime', 'bundle'),
|
|
3198
|
+
path.join(normalizedHome, 'runtime', 'daemon.json'),
|
|
3199
|
+
];
|
|
3200
|
+
if (normalizedInstallDir && normalizedInstallDir !== normalizeHomePath(repoRoot)) {
|
|
3201
|
+
removePaths.push(normalizedInstallDir);
|
|
3202
|
+
}
|
|
3203
|
+
return {
|
|
3204
|
+
remove_paths: [...new Set(removePaths.map((item) => path.resolve(item)))].sort(),
|
|
3205
|
+
preserve_paths: [
|
|
3206
|
+
path.join(normalizedHome, 'quests'),
|
|
3207
|
+
path.join(normalizedHome, 'memory'),
|
|
3208
|
+
path.join(normalizedHome, 'config'),
|
|
3209
|
+
path.join(normalizedHome, 'logs'),
|
|
3210
|
+
path.join(normalizedHome, 'plugins'),
|
|
3211
|
+
path.join(normalizedHome, 'cache'),
|
|
3212
|
+
].sort(),
|
|
3213
|
+
wrapper_paths: [...new Set(wrapperPaths.map((item) => path.resolve(item)))].sort(),
|
|
3214
|
+
};
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
function removePathEntry(targetPath) {
|
|
3218
|
+
if (!targetPath || !fs.existsSync(targetPath)) {
|
|
3219
|
+
return false;
|
|
3220
|
+
}
|
|
3221
|
+
const stat = fs.lstatSync(targetPath);
|
|
3222
|
+
if (stat.isDirectory() && !stat.isSymbolicLink()) {
|
|
3223
|
+
fs.rmSync(targetPath, { recursive: true, force: true });
|
|
3224
|
+
return true;
|
|
3225
|
+
}
|
|
3226
|
+
fs.rmSync(targetPath, { force: true });
|
|
3227
|
+
return true;
|
|
3228
|
+
}
|
|
3229
|
+
|
|
3230
|
+
function currentInstallRecord(home) {
|
|
3231
|
+
const installMode = detectInstallMode(repoRoot);
|
|
3232
|
+
return normalizeInstallRecord({
|
|
3233
|
+
home,
|
|
3234
|
+
install_mode: installMode,
|
|
3235
|
+
install_dir: installMode === 'source-checkout' ? null : null,
|
|
3236
|
+
package_root: normalizeHomePath(repoRoot),
|
|
3237
|
+
launcher_path: resolveLauncherPath() || path.join(repoRoot, 'bin', 'ds.js'),
|
|
3238
|
+
wrapper_paths: [],
|
|
3239
|
+
});
|
|
3240
|
+
}
|
|
3241
|
+
|
|
3242
|
+
function registerCurrentInstall(home) {
|
|
3243
|
+
const record = currentInstallRecord(home);
|
|
3244
|
+
if (!record) {
|
|
3245
|
+
return null;
|
|
3246
|
+
}
|
|
3247
|
+
return upsertInstallRecord(record);
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3250
|
+
function dedupeUninstallRecords(records) {
|
|
3251
|
+
const seen = new Set();
|
|
3252
|
+
const deduped = [];
|
|
3253
|
+
for (const record of records) {
|
|
3254
|
+
const normalized = normalizeInstallRecord(record);
|
|
3255
|
+
if (!normalized) {
|
|
3256
|
+
continue;
|
|
3257
|
+
}
|
|
3258
|
+
const key = JSON.stringify([
|
|
3259
|
+
normalized.home,
|
|
3260
|
+
normalized.install_mode || null,
|
|
3261
|
+
normalized.install_dir || null,
|
|
3262
|
+
normalized.package_root || null,
|
|
3263
|
+
]);
|
|
3264
|
+
if (seen.has(key)) {
|
|
3265
|
+
continue;
|
|
3266
|
+
}
|
|
3267
|
+
seen.add(key);
|
|
3268
|
+
deduped.push(normalized);
|
|
3269
|
+
}
|
|
3270
|
+
return deduped;
|
|
3271
|
+
}
|
|
3272
|
+
|
|
3273
|
+
function resolveUninstallRecords({ home, origin }) {
|
|
3274
|
+
const normalizedHome = normalizeHomePath(home);
|
|
3275
|
+
const currentPackageRoot = normalizeHomePath(repoRoot);
|
|
3276
|
+
const index = readInstallIndex();
|
|
3277
|
+
const installs = index.installs.map((item) => normalizeInstallRecord(item)).filter(Boolean);
|
|
3278
|
+
if (origin === 'npm') {
|
|
3279
|
+
const matching = installs.filter((item) => item.package_root === currentPackageRoot);
|
|
3280
|
+
if (matching.length > 0) {
|
|
3281
|
+
return dedupeUninstallRecords(matching);
|
|
3282
|
+
}
|
|
3283
|
+
return dedupeUninstallRecords([currentInstallRecord(normalizedHome)]);
|
|
3284
|
+
}
|
|
3285
|
+
const matching = installs.filter((item) => item.home === normalizedHome);
|
|
3286
|
+
if (matching.length > 0) {
|
|
3287
|
+
return dedupeUninstallRecords(matching);
|
|
3288
|
+
}
|
|
3289
|
+
const inferredInstallDir = fs.existsSync(path.join(normalizedHome, 'cli')) ? path.join(normalizedHome, 'cli') : null;
|
|
3290
|
+
return dedupeUninstallRecords([
|
|
3291
|
+
{
|
|
3292
|
+
...currentInstallRecord(normalizedHome),
|
|
3293
|
+
install_dir: inferredInstallDir,
|
|
3294
|
+
},
|
|
3295
|
+
]);
|
|
3296
|
+
}
|
|
3297
|
+
|
|
3298
|
+
function aggregateCodeOnlyUninstallPlan(records) {
|
|
3299
|
+
const removePaths = new Set();
|
|
3300
|
+
const preservePaths = new Set();
|
|
3301
|
+
const wrapperPaths = new Set();
|
|
3302
|
+
for (const record of records) {
|
|
3303
|
+
const installDir = record.install_dir || (fs.existsSync(path.join(record.home, 'cli')) ? path.join(record.home, 'cli') : null);
|
|
3304
|
+
const matchedWrappers = collectManagedWrapperPaths({
|
|
3305
|
+
home: record.home,
|
|
3306
|
+
installDir,
|
|
3307
|
+
explicitWrapperPaths: record.wrapper_paths || [],
|
|
3308
|
+
});
|
|
3309
|
+
const plan = buildCodeOnlyUninstallPlan({
|
|
3310
|
+
home: record.home,
|
|
3311
|
+
installDir,
|
|
3312
|
+
wrapperPaths: matchedWrappers,
|
|
3313
|
+
});
|
|
3314
|
+
for (const targetPath of plan.remove_paths) removePaths.add(targetPath);
|
|
3315
|
+
for (const targetPath of plan.preserve_paths) preservePaths.add(targetPath);
|
|
3316
|
+
for (const targetPath of plan.wrapper_paths) wrapperPaths.add(targetPath);
|
|
3317
|
+
}
|
|
3318
|
+
return {
|
|
3319
|
+
remove_paths: [...removePaths].sort(),
|
|
3320
|
+
preserve_paths: [...preservePaths].sort(),
|
|
3321
|
+
wrapper_paths: [...wrapperPaths].sort(),
|
|
3322
|
+
};
|
|
3323
|
+
}
|
|
3324
|
+
|
|
3325
|
+
async function promptUninstallConfirmation({ records, plan }) {
|
|
3326
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
3327
|
+
throw new Error('DeepScientist uninstall needs a TTY for confirmation. Re-run with `--yes` to continue non-interactively.');
|
|
3328
|
+
}
|
|
3329
|
+
console.log('');
|
|
3330
|
+
console.log('DeepScientist uninstall');
|
|
3331
|
+
console.log('');
|
|
3332
|
+
console.log('This removes code and runtime directories, but preserves local data.');
|
|
3333
|
+
console.log('');
|
|
3334
|
+
for (const record of records) {
|
|
3335
|
+
console.log(`Home: ${record.home}`);
|
|
3336
|
+
}
|
|
3337
|
+
console.log('');
|
|
3338
|
+
console.log('Code/runtime paths to remove:');
|
|
3339
|
+
for (const targetPath of plan.remove_paths) {
|
|
3340
|
+
console.log(`- ${targetPath}`);
|
|
3341
|
+
}
|
|
3342
|
+
console.log('');
|
|
3343
|
+
console.log('Preserved data paths:');
|
|
3344
|
+
for (const targetPath of plan.preserve_paths) {
|
|
3345
|
+
console.log(`- ${targetPath}`);
|
|
3346
|
+
}
|
|
3347
|
+
const answer = await new Promise((resolve) => {
|
|
3348
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
3349
|
+
rl.question('Type UNINSTALL to continue: ', (value) => {
|
|
3350
|
+
rl.close();
|
|
3351
|
+
resolve(String(value || '').trim());
|
|
3352
|
+
});
|
|
3353
|
+
});
|
|
3354
|
+
return answer === 'UNINSTALL';
|
|
3355
|
+
}
|
|
3356
|
+
|
|
3357
|
+
function runGlobalNpmUninstall() {
|
|
3358
|
+
const configuredPrefix = String(process.env.npm_config_prefix || process.env.NPM_CONFIG_PREFIX || '').trim();
|
|
3359
|
+
let uninstallPrefix = configuredPrefix || null;
|
|
3360
|
+
if (!uninstallPrefix && detectInstallMode(repoRoot) === 'npm-package') {
|
|
3361
|
+
let cursor = path.resolve(repoRoot);
|
|
3362
|
+
while (cursor && cursor !== path.dirname(cursor)) {
|
|
3363
|
+
if (path.basename(cursor) === 'node_modules') {
|
|
3364
|
+
const container = path.dirname(cursor);
|
|
3365
|
+
uninstallPrefix =
|
|
3366
|
+
path.basename(container) === 'lib'
|
|
3367
|
+
? path.dirname(container)
|
|
3368
|
+
: container;
|
|
3369
|
+
break;
|
|
3370
|
+
}
|
|
3371
|
+
cursor = path.dirname(cursor);
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
const npmBinary =
|
|
3375
|
+
resolveExecutableOnPath(process.platform === 'win32' ? 'npm.cmd' : 'npm')
|
|
3376
|
+
|| resolveExecutableOnPath('npm');
|
|
3377
|
+
if (!npmBinary) {
|
|
3378
|
+
return {
|
|
3379
|
+
ok: false,
|
|
3380
|
+
message: 'Global npm package removal was skipped because `npm` is not available on PATH.',
|
|
3381
|
+
};
|
|
3382
|
+
}
|
|
3383
|
+
const result = spawnSync(
|
|
3384
|
+
npmBinary,
|
|
3385
|
+
['uninstall', '-g', UPDATE_PACKAGE_NAME, ...(uninstallPrefix ? ['--prefix', uninstallPrefix] : [])],
|
|
3386
|
+
syncSpawnOptions({
|
|
3387
|
+
stdio: 'inherit',
|
|
3388
|
+
env: process.env,
|
|
3389
|
+
})
|
|
3390
|
+
);
|
|
3391
|
+
if (result.error) {
|
|
3392
|
+
return {
|
|
3393
|
+
ok: false,
|
|
3394
|
+
message: result.error.message,
|
|
3395
|
+
};
|
|
3396
|
+
}
|
|
3397
|
+
return {
|
|
3398
|
+
ok: result.status === 0,
|
|
3399
|
+
message: result.status === 0 ? null : `npm uninstall exited with status ${result.status ?? 1}.`,
|
|
3400
|
+
};
|
|
3401
|
+
}
|
|
3402
|
+
|
|
3403
|
+
function buildDaemonStatusPayload({ home, url, state, health, launcherPath = null }) {
|
|
3404
|
+
const healthy = Boolean(health && health.status === 'ok');
|
|
3405
|
+
const identityMatch = state ? healthMatchesManagedState({ health, state, home }) : false;
|
|
3406
|
+
return {
|
|
3407
|
+
healthy,
|
|
3408
|
+
identity_match: identityMatch,
|
|
3409
|
+
managed: Boolean(state),
|
|
3410
|
+
home,
|
|
3411
|
+
url,
|
|
3412
|
+
daemon_state_path: daemonStatePath(home),
|
|
3413
|
+
launcher_path: launcherPath || resolveLauncherPath() || null,
|
|
3414
|
+
daemon: state,
|
|
3415
|
+
health,
|
|
3416
|
+
};
|
|
3417
|
+
}
|
|
3418
|
+
|
|
2632
3419
|
function daemonSupervisorLogPath(home) {
|
|
2633
3420
|
return path.join(home, 'logs', 'daemon-supervisor.log');
|
|
2634
3421
|
}
|
|
@@ -2678,9 +3465,25 @@ function decodeSupervisorEnvPayload(rawValue) {
|
|
|
2678
3465
|
}
|
|
2679
3466
|
}
|
|
2680
3467
|
|
|
2681
|
-
function spawnManagedDaemonProcess({
|
|
3468
|
+
function spawnManagedDaemonProcess({
|
|
3469
|
+
home,
|
|
3470
|
+
runtimePython,
|
|
3471
|
+
host,
|
|
3472
|
+
port,
|
|
3473
|
+
proxy = null,
|
|
3474
|
+
envOverrides = {},
|
|
3475
|
+
daemonId = null,
|
|
3476
|
+
authEnabled = false,
|
|
3477
|
+
authToken = null,
|
|
3478
|
+
}) {
|
|
2682
3479
|
const browserUrl = browserUiUrl(host, port);
|
|
2683
3480
|
const daemonBindUrl = bindUiUrl(host, port);
|
|
3481
|
+
const resolvedAuthEnabled = authEnabled !== false;
|
|
3482
|
+
const resolvedAuthToken = resolvedAuthEnabled
|
|
3483
|
+
? (typeof authToken === 'string' && authToken.trim() ? authToken.trim() : generateBrowserAuthToken())
|
|
3484
|
+
: null;
|
|
3485
|
+
const launchUrl = browserUrl;
|
|
3486
|
+
const bindLaunchUrl = daemonBindUrl;
|
|
2684
3487
|
const logPath = path.join(home, 'logs', 'daemon.log');
|
|
2685
3488
|
ensureDir(path.dirname(logPath));
|
|
2686
3489
|
const out = fs.openSync(logPath, 'a');
|
|
@@ -2700,6 +3503,9 @@ function spawnManagedDaemonProcess({ home, runtimePython, host, port, proxy = nu
|
|
|
2700
3503
|
host,
|
|
2701
3504
|
'--port',
|
|
2702
3505
|
String(port),
|
|
3506
|
+
'--auth',
|
|
3507
|
+
resolvedAuthEnabled ? 'true' : 'false',
|
|
3508
|
+
...(resolvedAuthEnabled && resolvedAuthToken ? ['--auth-token', resolvedAuthToken] : []),
|
|
2703
3509
|
],
|
|
2704
3510
|
detachedSpawnOptions({
|
|
2705
3511
|
cwd: repoRoot,
|
|
@@ -2712,6 +3518,8 @@ function spawnManagedDaemonProcess({ home, runtimePython, host, port, proxy = nu
|
|
|
2712
3518
|
DEEPSCIENTIST_LAUNCHER_PATH: launcherPath,
|
|
2713
3519
|
DS_DAEMON_ID: resolvedDaemonId,
|
|
2714
3520
|
DS_DAEMON_MANAGED_BY: 'ds-launcher',
|
|
3521
|
+
DS_UI_AUTH_ENABLED: resolvedAuthEnabled ? '1' : '0',
|
|
3522
|
+
...(resolvedAuthEnabled && resolvedAuthToken ? { DS_UI_AUTH_TOKEN: resolvedAuthToken } : {}),
|
|
2715
3523
|
},
|
|
2716
3524
|
})
|
|
2717
3525
|
);
|
|
@@ -2722,10 +3530,14 @@ function spawnManagedDaemonProcess({ home, runtimePython, host, port, proxy = nu
|
|
|
2722
3530
|
port,
|
|
2723
3531
|
url: browserUrl,
|
|
2724
3532
|
bind_url: daemonBindUrl,
|
|
3533
|
+
launch_url: launchUrl,
|
|
3534
|
+
bind_launch_url: bindLaunchUrl,
|
|
2725
3535
|
log_path: logPath,
|
|
2726
3536
|
started_at: new Date().toISOString(),
|
|
2727
3537
|
home: normalizeHomePath(home),
|
|
2728
3538
|
daemon_id: resolvedDaemonId,
|
|
3539
|
+
auth_enabled: resolvedAuthEnabled,
|
|
3540
|
+
auth_token: resolvedAuthToken,
|
|
2729
3541
|
};
|
|
2730
3542
|
writeDaemonState(home, statePayload);
|
|
2731
3543
|
return {
|
|
@@ -2733,6 +3545,8 @@ function spawnManagedDaemonProcess({ home, runtimePython, host, port, proxy = nu
|
|
|
2733
3545
|
statePayload,
|
|
2734
3546
|
browserUrl,
|
|
2735
3547
|
bindUrl: daemonBindUrl,
|
|
3548
|
+
launchUrl,
|
|
3549
|
+
bindLaunchUrl,
|
|
2736
3550
|
logPath,
|
|
2737
3551
|
};
|
|
2738
3552
|
}
|
|
@@ -2787,19 +3601,54 @@ function parseDaemonSupervisorArgs(argv) {
|
|
|
2787
3601
|
|
|
2788
3602
|
for (let index = 0; index < args.length; index += 1) {
|
|
2789
3603
|
const arg = args[index];
|
|
2790
|
-
if (arg === '--home'
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
else if (arg === '--
|
|
2796
|
-
|
|
3604
|
+
if (arg === '--home') {
|
|
3605
|
+
const next = readRequiredOptionValue(args, index, '--home');
|
|
3606
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
3607
|
+
home = path.resolve(next.value);
|
|
3608
|
+
index += 1;
|
|
3609
|
+
} else if (arg === '--runtime-python') {
|
|
3610
|
+
const next = readRequiredOptionValue(args, index, '--runtime-python');
|
|
3611
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
3612
|
+
runtimePython = next.value;
|
|
3613
|
+
index += 1;
|
|
3614
|
+
} else if (arg === '--host') {
|
|
3615
|
+
const next = readRequiredOptionValue(args, index, '--host');
|
|
3616
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
3617
|
+
host = next.value;
|
|
3618
|
+
index += 1;
|
|
3619
|
+
} else if (arg === '--port') {
|
|
3620
|
+
const next = readRequiredOptionValue(args, index, '--port');
|
|
3621
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
3622
|
+
const parsed = parseStrictPortOption(next.value, '--port');
|
|
3623
|
+
if (!parsed.ok) return { help: false, error: parsed.error };
|
|
3624
|
+
port = parsed.value;
|
|
3625
|
+
index += 1;
|
|
3626
|
+
} else if (arg === '--proxy') {
|
|
3627
|
+
const next = readRequiredOptionValue(args, index, '--proxy');
|
|
3628
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
3629
|
+
proxy = next.value;
|
|
3630
|
+
index += 1;
|
|
3631
|
+
} else if (arg === '--daemon-id') {
|
|
3632
|
+
const next = readRequiredOptionValue(args, index, '--daemon-id');
|
|
3633
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
3634
|
+
daemonId = next.value;
|
|
3635
|
+
index += 1;
|
|
3636
|
+
} else if (arg === '--env-json') {
|
|
3637
|
+
const next = readRequiredOptionValue(args, index, '--env-json');
|
|
3638
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
3639
|
+
envJson = next.value;
|
|
3640
|
+
index += 1;
|
|
3641
|
+
}
|
|
2797
3642
|
else if (arg === '--help' || arg === '-h') return { help: true };
|
|
2798
|
-
else return
|
|
3643
|
+
else if (arg.startsWith('--')) return { help: false, error: `Unknown daemon supervisor flag: ${arg}` };
|
|
3644
|
+
else return { help: false, error: `Unexpected daemon supervisor argument: ${arg}` };
|
|
2799
3645
|
}
|
|
2800
3646
|
|
|
2801
3647
|
if (!home || !runtimePython || !daemonId || !Number.isFinite(port) || port <= 0) {
|
|
2802
|
-
return
|
|
3648
|
+
return {
|
|
3649
|
+
help: false,
|
|
3650
|
+
error: 'Daemon supervisor requires --home, --runtime-python, --daemon-id, and a valid --port.',
|
|
3651
|
+
};
|
|
2803
3652
|
}
|
|
2804
3653
|
|
|
2805
3654
|
return {
|
|
@@ -2811,18 +3660,19 @@ function parseDaemonSupervisorArgs(argv) {
|
|
|
2811
3660
|
proxy,
|
|
2812
3661
|
daemonId,
|
|
2813
3662
|
envOverrides: decodeSupervisorEnvPayload(envJson),
|
|
3663
|
+
error: null,
|
|
2814
3664
|
};
|
|
2815
3665
|
}
|
|
2816
3666
|
|
|
2817
3667
|
async function daemonSupervisorMain(rawArgs) {
|
|
2818
3668
|
const options = parseDaemonSupervisorArgs(rawArgs);
|
|
2819
|
-
if (!options) {
|
|
2820
|
-
console.error('Invalid daemon supervisor arguments.');
|
|
2821
|
-
process.exit(1);
|
|
2822
|
-
}
|
|
2823
3669
|
if (options.help) {
|
|
2824
3670
|
process.exit(0);
|
|
2825
3671
|
}
|
|
3672
|
+
if (options.error) {
|
|
3673
|
+
console.error(options.error);
|
|
3674
|
+
process.exit(1);
|
|
3675
|
+
}
|
|
2826
3676
|
|
|
2827
3677
|
const home = options.home;
|
|
2828
3678
|
let trackedDaemonId = String(options.daemonId || '').trim();
|
|
@@ -2849,7 +3699,8 @@ async function daemonSupervisorMain(rawArgs) {
|
|
|
2849
3699
|
appendDaemonSupervisorLog(home, `daemon id changed to ${stateDaemonId}; supervisor exiting`);
|
|
2850
3700
|
return;
|
|
2851
3701
|
}
|
|
2852
|
-
const
|
|
3702
|
+
const authToken = typeof state.auth_token === 'string' ? state.auth_token.trim() : '';
|
|
3703
|
+
const health = await fetchHealth(state.url || browserUiUrl(options.host, options.port), authToken);
|
|
2853
3704
|
if (health && health.status === 'ok' && healthMatchesManagedState({ health, state, home })) {
|
|
2854
3705
|
restartBackoffMs = 1000;
|
|
2855
3706
|
await sleep(2500);
|
|
@@ -2872,6 +3723,8 @@ async function daemonSupervisorMain(rawArgs) {
|
|
|
2872
3723
|
port: options.port,
|
|
2873
3724
|
proxy: options.proxy,
|
|
2874
3725
|
envOverrides: options.envOverrides,
|
|
3726
|
+
authEnabled: state.auth_enabled !== false,
|
|
3727
|
+
authToken,
|
|
2875
3728
|
});
|
|
2876
3729
|
trackedDaemonId = String(restarted.statePayload.daemon_id || '').trim();
|
|
2877
3730
|
observeManagedDaemonChild(home, restarted.child, trackedDaemonId);
|
|
@@ -2896,14 +3749,19 @@ function sleep(ms) {
|
|
|
2896
3749
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2897
3750
|
}
|
|
2898
3751
|
|
|
2899
|
-
async function isHealthy(url) {
|
|
2900
|
-
const payload = await fetchHealth(url);
|
|
3752
|
+
async function isHealthy(url, authToken = null) {
|
|
3753
|
+
const payload = await fetchHealth(url, authToken);
|
|
2901
3754
|
return Boolean(payload && payload.status === 'ok');
|
|
2902
3755
|
}
|
|
2903
3756
|
|
|
2904
|
-
async function fetchHealth(url) {
|
|
3757
|
+
async function fetchHealth(url, authToken = null) {
|
|
2905
3758
|
try {
|
|
2906
|
-
const
|
|
3759
|
+
const headers = {};
|
|
3760
|
+
const normalizedAuthToken = typeof authToken === 'string' ? authToken.trim() : '';
|
|
3761
|
+
if (normalizedAuthToken) {
|
|
3762
|
+
headers.Authorization = `Bearer ${normalizedAuthToken}`;
|
|
3763
|
+
}
|
|
3764
|
+
const response = await fetch(`${url}/api/health`, { headers });
|
|
2907
3765
|
if (!response.ok) {
|
|
2908
3766
|
return null;
|
|
2909
3767
|
}
|
|
@@ -2953,13 +3811,18 @@ function daemonIdentityError({ url, home, health, state }) {
|
|
|
2953
3811
|
].join('\n');
|
|
2954
3812
|
}
|
|
2955
3813
|
|
|
2956
|
-
async function requestDaemonShutdown(url, daemonId) {
|
|
3814
|
+
async function requestDaemonShutdown(url, daemonId, authToken = null) {
|
|
2957
3815
|
try {
|
|
3816
|
+
const headers = {
|
|
3817
|
+
'Content-Type': 'application/json',
|
|
3818
|
+
};
|
|
3819
|
+
const normalizedAuthToken = typeof authToken === 'string' ? authToken.trim() : '';
|
|
3820
|
+
if (normalizedAuthToken) {
|
|
3821
|
+
headers.Authorization = `Bearer ${normalizedAuthToken}`;
|
|
3822
|
+
}
|
|
2958
3823
|
const response = await fetch(`${url}/api/admin/shutdown`, {
|
|
2959
3824
|
method: 'POST',
|
|
2960
|
-
headers
|
|
2961
|
-
'Content-Type': 'application/json',
|
|
2962
|
-
},
|
|
3825
|
+
headers,
|
|
2963
3826
|
body: JSON.stringify({ source: 'ds-launcher', daemon_id: daemonId || null }),
|
|
2964
3827
|
});
|
|
2965
3828
|
if (!response.ok) {
|
|
@@ -2972,6 +3835,31 @@ async function requestDaemonShutdown(url, daemonId) {
|
|
|
2972
3835
|
}
|
|
2973
3836
|
}
|
|
2974
3837
|
|
|
3838
|
+
async function requestDaemonAuthRotate(url, authToken = null) {
|
|
3839
|
+
try {
|
|
3840
|
+
const headers = {
|
|
3841
|
+
'Content-Type': 'application/json',
|
|
3842
|
+
};
|
|
3843
|
+
const normalizedAuthToken = typeof authToken === 'string' ? authToken.trim() : '';
|
|
3844
|
+
if (normalizedAuthToken) {
|
|
3845
|
+
headers.Authorization = `Bearer ${normalizedAuthToken}`;
|
|
3846
|
+
}
|
|
3847
|
+
const response = await fetch(`${url}/api/auth/rotate`, {
|
|
3848
|
+
method: 'POST',
|
|
3849
|
+
headers,
|
|
3850
|
+
body: '{}',
|
|
3851
|
+
});
|
|
3852
|
+
if (!response.ok) {
|
|
3853
|
+
return null;
|
|
3854
|
+
}
|
|
3855
|
+
const payload = await response.json().catch(() => ({}));
|
|
3856
|
+
const token = typeof payload?.token === 'string' ? payload.token.trim() : '';
|
|
3857
|
+
return token || null;
|
|
3858
|
+
} catch {
|
|
3859
|
+
return null;
|
|
3860
|
+
}
|
|
3861
|
+
}
|
|
3862
|
+
|
|
2975
3863
|
function isPidAlive(pid) {
|
|
2976
3864
|
if (!pid) return false;
|
|
2977
3865
|
try {
|
|
@@ -3005,9 +3893,9 @@ function killManagedProcess(pid, signal) {
|
|
|
3005
3893
|
}
|
|
3006
3894
|
}
|
|
3007
3895
|
|
|
3008
|
-
async function waitForDaemonStop({ url, pid, attempts = 20, delayMs = 200 }) {
|
|
3896
|
+
async function waitForDaemonStop({ url, pid, authToken = null, attempts = 20, delayMs = 200 }) {
|
|
3009
3897
|
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
3010
|
-
const healthy = url ? await isHealthy(url) : false;
|
|
3898
|
+
const healthy = url ? await isHealthy(url, authToken) : false;
|
|
3011
3899
|
const alive = pid ? isPidAlive(pid) : false;
|
|
3012
3900
|
if (!healthy && !alive) {
|
|
3013
3901
|
return true;
|
|
@@ -3032,7 +3920,8 @@ async function stopDaemon(home) {
|
|
|
3032
3920
|
const state = readDaemonState(home);
|
|
3033
3921
|
const configured = readConfiguredUiAddressFromFile(home);
|
|
3034
3922
|
const url = state?.url || browserUiUrl(state?.host || configured.host, state?.port || configured.port);
|
|
3035
|
-
const
|
|
3923
|
+
const authToken = typeof state?.auth_token === 'string' ? state.auth_token.trim() : '';
|
|
3924
|
+
const healthBefore = await fetchHealth(url, authToken);
|
|
3036
3925
|
const healthyBefore = Boolean(healthBefore && healthBefore.status === 'ok');
|
|
3037
3926
|
const sameHomeHealthy = healthMatchesHome({ health: healthBefore, home });
|
|
3038
3927
|
const pid = state?.pid || (sameHomeHealthy ? healthBefore?.pid : null);
|
|
@@ -3073,21 +3962,21 @@ async function stopDaemon(home) {
|
|
|
3073
3962
|
let stopped = false;
|
|
3074
3963
|
|
|
3075
3964
|
if (healthyBefore) {
|
|
3076
|
-
await requestDaemonShutdown(url, shutdownDaemonId || null);
|
|
3077
|
-
stopped = await waitForDaemonStop({ url, pid, attempts: 20, delayMs: 200 });
|
|
3965
|
+
await requestDaemonShutdown(url, shutdownDaemonId || null, authToken);
|
|
3966
|
+
stopped = await waitForDaemonStop({ url, pid, authToken, attempts: 20, delayMs: 200 });
|
|
3078
3967
|
}
|
|
3079
3968
|
|
|
3080
3969
|
if (!stopped && pid && isPidAlive(pid)) {
|
|
3081
3970
|
killManagedProcess(pid, 'SIGTERM');
|
|
3082
|
-
stopped = await waitForDaemonStop({ url, pid, attempts: 30, delayMs: 200 });
|
|
3971
|
+
stopped = await waitForDaemonStop({ url, pid, authToken, attempts: 30, delayMs: 200 });
|
|
3083
3972
|
}
|
|
3084
3973
|
|
|
3085
3974
|
if (!stopped && pid && isPidAlive(pid)) {
|
|
3086
3975
|
killManagedProcess(pid, 'SIGKILL');
|
|
3087
|
-
stopped = await waitForDaemonStop({ url, pid, attempts: 20, delayMs: 150 });
|
|
3976
|
+
stopped = await waitForDaemonStop({ url, pid, authToken, attempts: 20, delayMs: 150 });
|
|
3088
3977
|
}
|
|
3089
3978
|
|
|
3090
|
-
const stillHealthy = await isHealthy(url);
|
|
3979
|
+
const stillHealthy = await isHealthy(url, authToken);
|
|
3091
3980
|
if (!stopped && (stillHealthy || (pid && isPidAlive(pid)))) {
|
|
3092
3981
|
console.error('DeepScientist daemon is still running after shutdown attempts.');
|
|
3093
3982
|
process.exit(1);
|
|
@@ -3097,6 +3986,86 @@ async function stopDaemon(home) {
|
|
|
3097
3986
|
console.log('DeepScientist daemon stopped.');
|
|
3098
3987
|
}
|
|
3099
3988
|
|
|
3989
|
+
async function uninstallMain(rawArgs) {
|
|
3990
|
+
const options = parseUninstallArgs(rawArgs);
|
|
3991
|
+
if (options.help) {
|
|
3992
|
+
printUninstallHelp();
|
|
3993
|
+
process.exit(0);
|
|
3994
|
+
}
|
|
3995
|
+
if (options.error) {
|
|
3996
|
+
console.error(options.error);
|
|
3997
|
+
console.error('Run `ds uninstall --help` for usage.');
|
|
3998
|
+
process.exit(1);
|
|
3999
|
+
}
|
|
4000
|
+
|
|
4001
|
+
const home = normalizeHomePath(options.home || resolveHome(rawArgs));
|
|
4002
|
+
const records = resolveUninstallRecords({ home, origin: options.origin });
|
|
4003
|
+
const plan = aggregateCodeOnlyUninstallPlan(records);
|
|
4004
|
+
|
|
4005
|
+
if (!options.yes && options.origin !== 'npm') {
|
|
4006
|
+
const confirmed = await promptUninstallConfirmation({ records, plan });
|
|
4007
|
+
if (!confirmed) {
|
|
4008
|
+
console.log('DeepScientist uninstall cancelled.');
|
|
4009
|
+
process.exit(1);
|
|
4010
|
+
}
|
|
4011
|
+
}
|
|
4012
|
+
|
|
4013
|
+
for (const record of records) {
|
|
4014
|
+
try {
|
|
4015
|
+
await stopDaemon(record.home);
|
|
4016
|
+
} catch (error) {
|
|
4017
|
+
console.warn(`DeepScientist could not fully stop the daemon for ${record.home}: ${error instanceof Error ? error.message : String(error)}`);
|
|
4018
|
+
}
|
|
4019
|
+
}
|
|
4020
|
+
|
|
4021
|
+
const removed = [];
|
|
4022
|
+
for (const targetPath of plan.remove_paths) {
|
|
4023
|
+
if (removePathEntry(targetPath)) {
|
|
4024
|
+
removed.push(targetPath);
|
|
4025
|
+
}
|
|
4026
|
+
}
|
|
4027
|
+
for (const targetPath of plan.wrapper_paths) {
|
|
4028
|
+
if (removePathEntry(targetPath)) {
|
|
4029
|
+
removed.push(targetPath);
|
|
4030
|
+
}
|
|
4031
|
+
}
|
|
4032
|
+
|
|
4033
|
+
removeInstallRecords((record) => records.some((candidate) => installRecordMatches(candidate, record)));
|
|
4034
|
+
|
|
4035
|
+
let npmRemovalMessage = null;
|
|
4036
|
+
if (options.origin !== 'npm' && detectInstallMode(repoRoot) === 'npm-package') {
|
|
4037
|
+
const npmRemoval = runGlobalNpmUninstall();
|
|
4038
|
+
if (!npmRemoval.ok) {
|
|
4039
|
+
npmRemovalMessage = npmRemoval.message;
|
|
4040
|
+
}
|
|
4041
|
+
}
|
|
4042
|
+
|
|
4043
|
+
console.log('');
|
|
4044
|
+
console.log('DeepScientist code uninstall completed.');
|
|
4045
|
+
if (removed.length > 0) {
|
|
4046
|
+
console.log('');
|
|
4047
|
+
console.log('Removed:');
|
|
4048
|
+
for (const targetPath of removed) {
|
|
4049
|
+
console.log(`- ${targetPath}`);
|
|
4050
|
+
}
|
|
4051
|
+
}
|
|
4052
|
+
console.log('');
|
|
4053
|
+
console.log('Preserved local data:');
|
|
4054
|
+
for (const targetPath of plan.preserve_paths) {
|
|
4055
|
+
console.log(`- ${targetPath}`);
|
|
4056
|
+
}
|
|
4057
|
+
console.log('');
|
|
4058
|
+
for (const record of records) {
|
|
4059
|
+
console.log(`If you also want to delete local data manually: rm -rf ${record.home}`);
|
|
4060
|
+
}
|
|
4061
|
+
if (npmRemovalMessage) {
|
|
4062
|
+
console.log('');
|
|
4063
|
+
console.warn(`Global npm package removal did not complete automatically: ${npmRemovalMessage}`);
|
|
4064
|
+
console.warn(`Run: npm uninstall -g ${UPDATE_PACKAGE_NAME}`);
|
|
4065
|
+
}
|
|
4066
|
+
process.exit(0);
|
|
4067
|
+
}
|
|
4068
|
+
|
|
3100
4069
|
function writeUpdateLog(home, content) {
|
|
3101
4070
|
const logPath = path.join(home, 'logs', 'update.log');
|
|
3102
4071
|
ensureDir(path.dirname(logPath));
|
|
@@ -3362,19 +4331,23 @@ async function performSelfUpdate(home, options = {}) {
|
|
|
3362
4331
|
log_path: installResult.logPath,
|
|
3363
4332
|
};
|
|
3364
4333
|
}
|
|
4334
|
+
const restartArgs = [
|
|
4335
|
+
launcherPath,
|
|
4336
|
+
'--home',
|
|
4337
|
+
home,
|
|
4338
|
+
'--host',
|
|
4339
|
+
String(host),
|
|
4340
|
+
'--port',
|
|
4341
|
+
String(port),
|
|
4342
|
+
'--daemon-only',
|
|
4343
|
+
'--no-browser',
|
|
4344
|
+
'--skip-update-check',
|
|
4345
|
+
];
|
|
4346
|
+
if (daemonState && daemonState.auth_enabled === false) {
|
|
4347
|
+
restartArgs.push('--auth', 'false');
|
|
4348
|
+
}
|
|
3365
4349
|
spawnDetachedNode(
|
|
3366
|
-
|
|
3367
|
-
launcherPath,
|
|
3368
|
-
'--home',
|
|
3369
|
-
home,
|
|
3370
|
-
'--host',
|
|
3371
|
-
String(host),
|
|
3372
|
-
'--port',
|
|
3373
|
-
String(port),
|
|
3374
|
-
'--daemon-only',
|
|
3375
|
-
'--no-browser',
|
|
3376
|
-
'--skip-update-check',
|
|
3377
|
-
],
|
|
4350
|
+
restartArgs,
|
|
3378
4351
|
{
|
|
3379
4352
|
cwd: repoRoot,
|
|
3380
4353
|
env: process.env,
|
|
@@ -3569,17 +4542,88 @@ async function startBackgroundUpdateWorker(home, options = {}) {
|
|
|
3569
4542
|
};
|
|
3570
4543
|
}
|
|
3571
4544
|
|
|
4545
|
+
async function maybeHandleMiniMaxCodexVersion(home, runtimePython, options = {}) {
|
|
4546
|
+
const configuredRunners = (() => {
|
|
4547
|
+
try {
|
|
4548
|
+
const result = runPythonCli(runtimePython, ['--home', home, 'config', 'show', 'runners'], {
|
|
4549
|
+
capture: true,
|
|
4550
|
+
allowFailure: true,
|
|
4551
|
+
});
|
|
4552
|
+
return String(result.stdout || '');
|
|
4553
|
+
} catch {
|
|
4554
|
+
return '';
|
|
4555
|
+
}
|
|
4556
|
+
})();
|
|
4557
|
+
const profileFromConfig =
|
|
4558
|
+
configuredRunners.match(/^\s*profile:\s*["']?([^"'\n]+)["']?\s*$/m)?.[1]?.trim() || '';
|
|
4559
|
+
const binaryFromConfig =
|
|
4560
|
+
configuredRunners.match(/^\s*binary:\s*["']?([^"'\n]+)["']?\s*$/m)?.[1]?.trim() || 'codex';
|
|
4561
|
+
const configDirFromConfig =
|
|
4562
|
+
configuredRunners.match(/^\s*config_dir:\s*["']?([^"'\n]+)["']?\s*$/m)?.[1]?.trim() || '~/.codex';
|
|
4563
|
+
|
|
4564
|
+
const effectiveProfile = String(options.codexProfile || profileFromConfig || '').trim();
|
|
4565
|
+
if (!effectiveProfile) {
|
|
4566
|
+
return false;
|
|
4567
|
+
}
|
|
4568
|
+
const metadata = readCodexProviderMetadata(configDirFromConfig, effectiveProfile);
|
|
4569
|
+
if (String(metadata.provider || '').trim().toLowerCase() !== 'minimax') {
|
|
4570
|
+
return false;
|
|
4571
|
+
}
|
|
4572
|
+
const version = installedCodexCliVersion(options.codexBinary || binaryFromConfig || 'codex');
|
|
4573
|
+
const expected = [0, 57, 0];
|
|
4574
|
+
if (!version || compareCodexCliVersion(version, expected) === 0) {
|
|
4575
|
+
return false;
|
|
4576
|
+
}
|
|
4577
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
4578
|
+
console.log(
|
|
4579
|
+
`MiniMax profile \`${effectiveProfile}\` is configured, but installed Codex CLI is ${formatCodexCliVersion(version)}. MiniMax currently requires Codex CLI 0.57.0 for the documented path.`
|
|
4580
|
+
);
|
|
4581
|
+
console.log('Install it manually with `npm install -g @openai/codex@0.57.0` before continuing.');
|
|
4582
|
+
return false;
|
|
4583
|
+
}
|
|
4584
|
+
|
|
4585
|
+
console.log('');
|
|
4586
|
+
console.log(colorize('\u001B[1;38;5;214m', 'MiniMax compatibility check'));
|
|
4587
|
+
console.log(
|
|
4588
|
+
`DeepScientist detected MiniMax profile \`${effectiveProfile}\`, but installed Codex CLI is ${formatCodexCliVersion(version)}.`
|
|
4589
|
+
);
|
|
4590
|
+
console.log('MiniMax currently requires Codex CLI 0.57.0 for the documented DeepScientist path.');
|
|
4591
|
+
const confirmed = await promptYesNo('Reinstall Codex CLI to 0.57.0 now? [y/N]: ', {
|
|
4592
|
+
defaultValue: false,
|
|
4593
|
+
});
|
|
4594
|
+
if (!confirmed) {
|
|
4595
|
+
return false;
|
|
4596
|
+
}
|
|
4597
|
+
const npmBinary = resolveNpmBinary();
|
|
4598
|
+
if (!npmBinary) {
|
|
4599
|
+
console.error('`npm` is unavailable; cannot reinstall Codex CLI automatically.');
|
|
4600
|
+
process.exit(1);
|
|
4601
|
+
}
|
|
4602
|
+
const result = spawnSync(
|
|
4603
|
+
npmBinary,
|
|
4604
|
+
['install', '-g', '@openai/codex@0.57.0'],
|
|
4605
|
+
syncSpawnOptions({ stdio: 'inherit' })
|
|
4606
|
+
);
|
|
4607
|
+
if (result.status !== 0) {
|
|
4608
|
+
console.error('Failed to reinstall Codex CLI 0.57.0 automatically.');
|
|
4609
|
+
process.exit(result.status ?? 1);
|
|
4610
|
+
}
|
|
4611
|
+
return true;
|
|
4612
|
+
}
|
|
4613
|
+
|
|
3572
4614
|
async function readConfiguredUiAddress(home, runtimePython, fallbackHost, fallbackPort) {
|
|
3573
4615
|
try {
|
|
3574
4616
|
const result = runPythonCli(runtimePython, ['--home', home, 'config', 'show', 'config'], { capture: true, allowFailure: true });
|
|
3575
4617
|
const text = result.stdout || '';
|
|
3576
4618
|
const hostMatch = text.match(/^\s*host:\s*["']?([^"'\n]+)["']?\s*$/m);
|
|
3577
4619
|
const portMatch = text.match(/^\s*port:\s*(\d+)\s*$/m);
|
|
4620
|
+
const authMatch = text.match(/^\s*auth_enabled:\s*([^\n]+)\s*$/m);
|
|
3578
4621
|
const modeMatch = text.match(/^\s*default_mode:\s*["']?([^"'\n]+)["']?\s*$/m);
|
|
3579
4622
|
const autoOpenMatch = text.match(/^\s*auto_open_browser:\s*([^\n]+)\s*$/m);
|
|
3580
4623
|
return {
|
|
3581
4624
|
host: fallbackHost || (hostMatch ? hostMatch[1].trim() : '0.0.0.0'),
|
|
3582
4625
|
port: fallbackPort || (portMatch ? Number(portMatch[1]) : 20999),
|
|
4626
|
+
authEnabled: parseBooleanSetting(authMatch ? authMatch[1].trim() : false, false),
|
|
3583
4627
|
defaultMode: normalizeMode(modeMatch ? modeMatch[1].trim() : 'web'),
|
|
3584
4628
|
autoOpenBrowser: parseBooleanSetting(autoOpenMatch ? autoOpenMatch[1].trim() : true, true),
|
|
3585
4629
|
};
|
|
@@ -3587,6 +4631,7 @@ async function readConfiguredUiAddress(home, runtimePython, fallbackHost, fallba
|
|
|
3587
4631
|
return {
|
|
3588
4632
|
host: fallbackHost || '0.0.0.0',
|
|
3589
4633
|
port: fallbackPort || 20999,
|
|
4634
|
+
authEnabled: false,
|
|
3590
4635
|
defaultMode: 'web',
|
|
3591
4636
|
autoOpenBrowser: true,
|
|
3592
4637
|
};
|
|
@@ -3599,6 +4644,7 @@ function readConfiguredUiAddressFromFile(home, fallbackHost, fallbackPort) {
|
|
|
3599
4644
|
return {
|
|
3600
4645
|
host: fallbackHost || '0.0.0.0',
|
|
3601
4646
|
port: fallbackPort || 20999,
|
|
4647
|
+
authEnabled: false,
|
|
3602
4648
|
defaultMode: 'web',
|
|
3603
4649
|
autoOpenBrowser: true,
|
|
3604
4650
|
};
|
|
@@ -3607,11 +4653,13 @@ function readConfiguredUiAddressFromFile(home, fallbackHost, fallbackPort) {
|
|
|
3607
4653
|
const text = fs.readFileSync(configPath, 'utf8');
|
|
3608
4654
|
const hostMatch = text.match(/^\s*host:\s*["']?([^"'\n]+)["']?\s*$/m);
|
|
3609
4655
|
const portMatch = text.match(/^\s*port:\s*(\d+)\s*$/m);
|
|
4656
|
+
const authMatch = text.match(/^\s*auth_enabled:\s*([^\n]+)\s*$/m);
|
|
3610
4657
|
const modeMatch = text.match(/^\s*default_mode:\s*["']?([^"'\n]+)["']?\s*$/m);
|
|
3611
4658
|
const autoOpenMatch = text.match(/^\s*auto_open_browser:\s*([^\n]+)\s*$/m);
|
|
3612
4659
|
return {
|
|
3613
4660
|
host: fallbackHost || (hostMatch ? hostMatch[1].trim() : '0.0.0.0'),
|
|
3614
4661
|
port: fallbackPort || (portMatch ? Number(portMatch[1]) : 20999),
|
|
4662
|
+
authEnabled: parseBooleanSetting(authMatch ? authMatch[1].trim() : false, false),
|
|
3615
4663
|
defaultMode: normalizeMode(modeMatch ? modeMatch[1].trim() : 'web'),
|
|
3616
4664
|
autoOpenBrowser: parseBooleanSetting(autoOpenMatch ? autoOpenMatch[1].trim() : true, true),
|
|
3617
4665
|
};
|
|
@@ -3619,20 +4667,51 @@ function readConfiguredUiAddressFromFile(home, fallbackHost, fallbackPort) {
|
|
|
3619
4667
|
return {
|
|
3620
4668
|
host: fallbackHost || '0.0.0.0',
|
|
3621
4669
|
port: fallbackPort || 20999,
|
|
4670
|
+
authEnabled: false,
|
|
3622
4671
|
defaultMode: 'web',
|
|
3623
4672
|
autoOpenBrowser: true,
|
|
3624
4673
|
};
|
|
3625
4674
|
}
|
|
3626
4675
|
}
|
|
3627
4676
|
|
|
3628
|
-
async function startDaemon(home, runtimePython, host, port, proxy = null, envOverrides = {}) {
|
|
4677
|
+
async function startDaemon(home, runtimePython, host, port, proxy = null, envOverrides = {}, authEnabled = false) {
|
|
3629
4678
|
const browserUrl = browserUiUrl(host, port);
|
|
3630
4679
|
const daemonBindUrl = bindUiUrl(host, port);
|
|
3631
4680
|
const state = readDaemonState(home);
|
|
3632
|
-
const
|
|
4681
|
+
const desiredAuthToken = authEnabled ? generateBrowserAuthToken() : null;
|
|
4682
|
+
const launchUrl = browserUrl;
|
|
4683
|
+
const bindLaunchUrl = daemonBindUrl;
|
|
4684
|
+
const existingHealth = await fetchHealth(browserUrl, typeof state?.auth_token === 'string' ? state.auth_token.trim() : '');
|
|
3633
4685
|
if (existingHealth && existingHealth.status === 'ok') {
|
|
3634
4686
|
if (state && healthMatchesManagedState({ health: existingHealth, state, home })) {
|
|
3635
|
-
|
|
4687
|
+
const stateAuthEnabled = state.auth_enabled !== false;
|
|
4688
|
+
const stateAuthToken = typeof state.auth_token === 'string' ? state.auth_token.trim() : '';
|
|
4689
|
+
let resolvedAuthToken = stateAuthToken || null;
|
|
4690
|
+
if (stateAuthEnabled) {
|
|
4691
|
+
const rotatedAuthToken = await requestDaemonAuthRotate(browserUrl, stateAuthToken);
|
|
4692
|
+
if (!rotatedAuthToken) {
|
|
4693
|
+
console.error('Managed daemon is healthy, but the browser auth token could not be rotated.');
|
|
4694
|
+
console.error('Restart the daemon with `ds --restart` if this keeps happening.');
|
|
4695
|
+
process.exit(1);
|
|
4696
|
+
}
|
|
4697
|
+
resolvedAuthToken = rotatedAuthToken;
|
|
4698
|
+
writeDaemonState(home, {
|
|
4699
|
+
...state,
|
|
4700
|
+
auth_enabled: true,
|
|
4701
|
+
auth_token: rotatedAuthToken,
|
|
4702
|
+
url: browserUrl,
|
|
4703
|
+
bind_url: daemonBindUrl,
|
|
4704
|
+
launch_url: launchUrl,
|
|
4705
|
+
bind_launch_url: bindLaunchUrl,
|
|
4706
|
+
});
|
|
4707
|
+
}
|
|
4708
|
+
return {
|
|
4709
|
+
url: browserUrl,
|
|
4710
|
+
bindUrl: daemonBindUrl,
|
|
4711
|
+
reused: true,
|
|
4712
|
+
authEnabled: stateAuthEnabled,
|
|
4713
|
+
authToken: resolvedAuthToken,
|
|
4714
|
+
};
|
|
3636
4715
|
}
|
|
3637
4716
|
console.error(
|
|
3638
4717
|
state
|
|
@@ -3666,11 +4745,13 @@ async function startDaemon(home, runtimePython, host, port, proxy = null, envOve
|
|
|
3666
4745
|
port,
|
|
3667
4746
|
proxy,
|
|
3668
4747
|
envOverrides,
|
|
4748
|
+
authEnabled,
|
|
4749
|
+
authToken: desiredAuthToken,
|
|
3669
4750
|
});
|
|
3670
4751
|
const logPath = startedProcess.logPath;
|
|
3671
4752
|
|
|
3672
4753
|
for (let attempt = 0; attempt < 60; attempt += 1) {
|
|
3673
|
-
const health = await fetchHealth(browserUrl);
|
|
4754
|
+
const health = await fetchHealth(browserUrl, desiredAuthToken);
|
|
3674
4755
|
if (health && health.status === 'ok') {
|
|
3675
4756
|
const liveState = readDaemonState(home);
|
|
3676
4757
|
if (!healthMatchesManagedState({ health, state: liveState, home })) {
|
|
@@ -3689,12 +4770,26 @@ async function startDaemon(home, runtimePython, host, port, proxy = null, envOve
|
|
|
3689
4770
|
if (supervisorPid) {
|
|
3690
4771
|
appendDaemonSupervisorLog(home, `supervisor started with pid ${supervisorPid}`);
|
|
3691
4772
|
}
|
|
3692
|
-
return {
|
|
4773
|
+
return {
|
|
4774
|
+
url: launchUrl,
|
|
4775
|
+
bindUrl: bindLaunchUrl,
|
|
4776
|
+
reused: false,
|
|
4777
|
+
authEnabled,
|
|
4778
|
+
authToken: desiredAuthToken,
|
|
4779
|
+
};
|
|
3693
4780
|
}
|
|
3694
4781
|
await sleep(250);
|
|
3695
4782
|
}
|
|
3696
4783
|
|
|
3697
4784
|
console.error('DeepScientist daemon failed to become healthy.');
|
|
4785
|
+
console.error(`Expected local URL: ${launchUrl}`);
|
|
4786
|
+
console.error(`Daemon bind URL: ${bindLaunchUrl}`);
|
|
4787
|
+
if (authEnabled && desiredAuthToken) {
|
|
4788
|
+
console.error(`Auth token: ${desiredAuthToken}`);
|
|
4789
|
+
}
|
|
4790
|
+
if (['0.0.0.0', '::', '[::]'].includes(String(host || '').trim())) {
|
|
4791
|
+
console.error(`Hint: ${String(host || '').trim() || '0.0.0.0'} is a bind address. Local browser and health probes use ${browserUrl}.`);
|
|
4792
|
+
}
|
|
3698
4793
|
const logTail = tailLog(logPath);
|
|
3699
4794
|
if (logTail) {
|
|
3700
4795
|
console.error(logTail);
|
|
@@ -3766,7 +4861,7 @@ function handleCodexPreflightFailure(error) {
|
|
|
3766
4861
|
: [
|
|
3767
4862
|
'In most installs, `npm install -g @researai/deepscientist` also installs the bundled Codex dependency.',
|
|
3768
4863
|
'If `codex` is still missing, run `npm install -g @openai/codex`.',
|
|
3769
|
-
'Run `codex
|
|
4864
|
+
'Run `codex login` (or just `codex`) and finish authentication.',
|
|
3770
4865
|
'Run `ds doctor` and confirm the Codex check passes.',
|
|
3771
4866
|
'Run `ds` again.',
|
|
3772
4867
|
];
|
|
@@ -3778,12 +4873,15 @@ function handleCodexPreflightFailure(error) {
|
|
|
3778
4873
|
return true;
|
|
3779
4874
|
}
|
|
3780
4875
|
|
|
3781
|
-
function launchTui(url, questId, home, runtimePython) {
|
|
4876
|
+
function launchTui(url, questId, home, runtimePython, authToken = null) {
|
|
3782
4877
|
const entry = ensureNodeBundle('src/tui', 'dist/index.js');
|
|
3783
4878
|
const args = [entry, '--base-url', url];
|
|
3784
4879
|
if (questId) {
|
|
3785
4880
|
args.push('--quest-id', questId);
|
|
3786
4881
|
}
|
|
4882
|
+
if (typeof authToken === 'string' && authToken.trim()) {
|
|
4883
|
+
args.push('--auth-token', authToken.trim());
|
|
4884
|
+
}
|
|
3787
4885
|
const child = spawn(process.execPath, args, {
|
|
3788
4886
|
cwd: repoRoot,
|
|
3789
4887
|
stdio: 'inherit',
|
|
@@ -3801,14 +4899,15 @@ function launchTui(url, questId, home, runtimePython) {
|
|
|
3801
4899
|
|
|
3802
4900
|
async function updateMain(rawArgs) {
|
|
3803
4901
|
const options = parseUpdateArgs(rawArgs);
|
|
3804
|
-
if (!options) {
|
|
3805
|
-
printUpdateHelp();
|
|
3806
|
-
process.exit(1);
|
|
3807
|
-
}
|
|
3808
4902
|
if (options.help) {
|
|
3809
4903
|
printUpdateHelp();
|
|
3810
4904
|
process.exit(0);
|
|
3811
4905
|
}
|
|
4906
|
+
if (options.error) {
|
|
4907
|
+
console.error(options.error);
|
|
4908
|
+
console.error('Run `ds update --help` for update usage.');
|
|
4909
|
+
process.exit(1);
|
|
4910
|
+
}
|
|
3812
4911
|
|
|
3813
4912
|
const home = options.home || resolveHome(rawArgs);
|
|
3814
4913
|
applyLauncherProxy(options.proxy);
|
|
@@ -3923,14 +5022,15 @@ async function updateMain(rawArgs) {
|
|
|
3923
5022
|
|
|
3924
5023
|
async function migrateMain(rawArgs) {
|
|
3925
5024
|
const options = parseMigrateArgs(rawArgs);
|
|
3926
|
-
if (!options) {
|
|
3927
|
-
printMigrateHelp();
|
|
3928
|
-
process.exit(1);
|
|
3929
|
-
}
|
|
3930
5025
|
if (options.help) {
|
|
3931
5026
|
printMigrateHelp();
|
|
3932
5027
|
process.exit(0);
|
|
3933
5028
|
}
|
|
5029
|
+
if (options.error) {
|
|
5030
|
+
console.error(options.error);
|
|
5031
|
+
console.error('Run `ds migrate --help` for migration usage.');
|
|
5032
|
+
process.exit(1);
|
|
5033
|
+
}
|
|
3934
5034
|
|
|
3935
5035
|
const sourceHome = realpathOrSelf(options.home || resolveHome(rawArgs));
|
|
3936
5036
|
const targetHome = path.resolve(options.target);
|
|
@@ -4051,20 +5151,27 @@ async function migrateMain(rawArgs) {
|
|
|
4051
5151
|
|
|
4052
5152
|
async function launcherMain(rawArgs) {
|
|
4053
5153
|
const options = parseLauncherArgs(rawArgs);
|
|
4054
|
-
if (!options) {
|
|
4055
|
-
return false;
|
|
4056
|
-
}
|
|
4057
5154
|
if (options.help) {
|
|
4058
5155
|
printLauncherHelp();
|
|
4059
5156
|
process.exit(0);
|
|
4060
5157
|
}
|
|
5158
|
+
if (options.error) {
|
|
5159
|
+
console.error(options.error);
|
|
5160
|
+
console.error('Run `ds --help` for launcher usage.');
|
|
5161
|
+
process.exit(1);
|
|
5162
|
+
}
|
|
4061
5163
|
|
|
4062
5164
|
const home = options.home || resolveHome(rawArgs);
|
|
4063
5165
|
applyLauncherProxy(options.proxy);
|
|
4064
5166
|
ensureDir(home);
|
|
5167
|
+
registerCurrentInstall(home);
|
|
5168
|
+
const forceWrapperRepair =
|
|
5169
|
+
detectInstallMode(repoRoot) !== 'npm-package'
|
|
5170
|
+
&& Boolean(options.home || process.env.DEEPSCIENTIST_HOME);
|
|
4065
5171
|
repairLegacyPathWrappers({
|
|
4066
5172
|
home,
|
|
4067
5173
|
launcherPath: resolveLauncherPath(),
|
|
5174
|
+
force: forceWrapperRepair,
|
|
4068
5175
|
});
|
|
4069
5176
|
|
|
4070
5177
|
if (options.stop) {
|
|
@@ -4075,26 +5182,21 @@ async function launcherMain(rawArgs) {
|
|
|
4075
5182
|
if (options.status) {
|
|
4076
5183
|
const state = readDaemonState(home);
|
|
4077
5184
|
const configured = readConfiguredUiAddressFromFile(home, options.host, options.port);
|
|
4078
|
-
const url = state?.url || browserUiUrl(configured.host, configured.port);
|
|
4079
|
-
const
|
|
4080
|
-
const
|
|
4081
|
-
const
|
|
5185
|
+
const url = state?.launch_url || state?.url || browserUiUrl(configured.host, configured.port);
|
|
5186
|
+
const authToken = typeof state?.auth_token === 'string' ? state.auth_token.trim() : '';
|
|
5187
|
+
const probeUrl = state?.url || browserUiUrl(configured.host, configured.port);
|
|
5188
|
+
const health = await fetchHealth(probeUrl, authToken);
|
|
5189
|
+
const statusPayload = buildDaemonStatusPayload({
|
|
5190
|
+
home,
|
|
5191
|
+
url,
|
|
5192
|
+
state,
|
|
5193
|
+
health,
|
|
5194
|
+
launcherPath: resolveLauncherPath(),
|
|
5195
|
+
});
|
|
4082
5196
|
console.log(
|
|
4083
|
-
JSON.stringify(
|
|
4084
|
-
{
|
|
4085
|
-
healthy,
|
|
4086
|
-
identity_match: identityMatch,
|
|
4087
|
-
managed: Boolean(state),
|
|
4088
|
-
home,
|
|
4089
|
-
url,
|
|
4090
|
-
daemon: state,
|
|
4091
|
-
health,
|
|
4092
|
-
},
|
|
4093
|
-
null,
|
|
4094
|
-
2
|
|
4095
|
-
)
|
|
5197
|
+
JSON.stringify(statusPayload, null, 2)
|
|
4096
5198
|
);
|
|
4097
|
-
process.exit(healthy && (!state ||
|
|
5199
|
+
process.exit(statusPayload.healthy && (!state || statusPayload.identity_match) ? 0 : 1);
|
|
4098
5200
|
}
|
|
4099
5201
|
|
|
4100
5202
|
const pythonRuntime = ensurePythonRuntime(home);
|
|
@@ -4105,6 +5207,7 @@ async function launcherMain(rawArgs) {
|
|
|
4105
5207
|
binary: options.codexBinary,
|
|
4106
5208
|
});
|
|
4107
5209
|
ensureInitialized(home, runtimePython);
|
|
5210
|
+
await maybeHandleMiniMaxCodexVersion(home, runtimePython, options);
|
|
4108
5211
|
if (await maybeHandleStartupUpdate(home, rawArgs, options)) {
|
|
4109
5212
|
return true;
|
|
4110
5213
|
}
|
|
@@ -4113,20 +5216,27 @@ async function launcherMain(rawArgs) {
|
|
|
4113
5216
|
const configuredUi = await readConfiguredUiAddress(home, runtimePython, options.host, options.port);
|
|
4114
5217
|
const host = configuredUi.host;
|
|
4115
5218
|
const port = configuredUi.port;
|
|
5219
|
+
const authEnabled = options.auth === null ? false : options.auth !== false;
|
|
4116
5220
|
const mode = normalizeMode(options.mode ?? 'web');
|
|
4117
5221
|
const shouldOpenBrowser = options.daemonOnly
|
|
4118
5222
|
? false
|
|
4119
5223
|
: options.openBrowser === null
|
|
4120
5224
|
? configuredUi.autoOpenBrowser !== false && mode !== 'tui'
|
|
4121
5225
|
: options.openBrowser;
|
|
4122
|
-
|
|
5226
|
+
const existingState = readDaemonState(home);
|
|
5227
|
+
const existingAuthEnabled = existingState ? existingState.auth_enabled !== false : null;
|
|
5228
|
+
const existingAuthToken = typeof existingState?.auth_token === 'string' ? existingState.auth_token.trim() : '';
|
|
5229
|
+
const authStateMismatch = existingState && (
|
|
5230
|
+
existingAuthEnabled !== authEnabled || (authEnabled && !existingAuthToken)
|
|
5231
|
+
);
|
|
5232
|
+
if (options.restart || authStateMismatch) {
|
|
4123
5233
|
await stopDaemon(home);
|
|
4124
5234
|
}
|
|
4125
5235
|
|
|
4126
5236
|
step(4, 4, 'Starting local daemon and UI surfaces');
|
|
4127
5237
|
let started;
|
|
4128
5238
|
try {
|
|
4129
|
-
started = await startDaemon(home, runtimePython, host, port, options.proxy, codexOverrideEnv);
|
|
5239
|
+
started = await startDaemon(home, runtimePython, host, port, options.proxy, codexOverrideEnv, authEnabled);
|
|
4130
5240
|
} catch (error) {
|
|
4131
5241
|
if (handleCodexPreflightFailure(error)) return true;
|
|
4132
5242
|
throw error;
|
|
@@ -4142,6 +5252,8 @@ async function launcherMain(rawArgs) {
|
|
|
4142
5252
|
home,
|
|
4143
5253
|
pythonSelection: pythonRuntime.runtimeProbe,
|
|
4144
5254
|
yolo: options.yolo,
|
|
5255
|
+
authEnabled: started.authEnabled,
|
|
5256
|
+
authToken: started.authToken,
|
|
4145
5257
|
});
|
|
4146
5258
|
|
|
4147
5259
|
if (options.daemonOnly) {
|
|
@@ -4150,12 +5262,16 @@ async function launcherMain(rawArgs) {
|
|
|
4150
5262
|
if (mode === 'web') {
|
|
4151
5263
|
process.exit(0);
|
|
4152
5264
|
}
|
|
4153
|
-
launchTui(
|
|
5265
|
+
launchTui(browserUiUrl(host, port), options.questId, home, runtimePython, started.authToken);
|
|
4154
5266
|
return true;
|
|
4155
5267
|
}
|
|
4156
5268
|
|
|
4157
5269
|
async function main() {
|
|
4158
|
-
const
|
|
5270
|
+
const normalizedArgState = normalizeLegacyHostFlagArgs(process.argv.slice(2));
|
|
5271
|
+
const args = normalizedArgState.args;
|
|
5272
|
+
for (const warning of normalizedArgState.warnings) {
|
|
5273
|
+
console.warn(warning);
|
|
5274
|
+
}
|
|
4159
5275
|
if (args[0] === '--daemon-supervisor') {
|
|
4160
5276
|
await daemonSupervisorMain(args.slice(1));
|
|
4161
5277
|
return;
|
|
@@ -4169,7 +5285,15 @@ async function main() {
|
|
|
4169
5285
|
await migrateMain(args);
|
|
4170
5286
|
return;
|
|
4171
5287
|
}
|
|
4172
|
-
if (
|
|
5288
|
+
if (positional && positional.value === 'uninstall') {
|
|
5289
|
+
await uninstallMain(args);
|
|
5290
|
+
return;
|
|
5291
|
+
}
|
|
5292
|
+
if (
|
|
5293
|
+
args.length === 0
|
|
5294
|
+
|| args[0] === 'ui'
|
|
5295
|
+
|| (args[0]?.startsWith('--') && (!positional || !pythonCommands.has(positional.value)))
|
|
5296
|
+
) {
|
|
4173
5297
|
await launcherMain(args);
|
|
4174
5298
|
return;
|
|
4175
5299
|
}
|
|
@@ -4225,19 +5349,26 @@ module.exports = {
|
|
|
4225
5349
|
resolveUvBinary,
|
|
4226
5350
|
resolveHome,
|
|
4227
5351
|
parseLauncherArgs,
|
|
5352
|
+
generateBrowserAuthToken,
|
|
5353
|
+
appendBrowserAuthToken,
|
|
4228
5354
|
normalizeProxyUrl,
|
|
5355
|
+
buildCodeOnlyUninstallPlan,
|
|
4229
5356
|
parseMigrateArgs,
|
|
4230
5357
|
parseLegacyWrapperCandidate,
|
|
4231
5358
|
repairLegacyPathWrappers,
|
|
4232
5359
|
useEditableProjectInstall,
|
|
4233
5360
|
compareVersions,
|
|
4234
5361
|
detectInstallMode,
|
|
5362
|
+
buildUvSyncFailureGuidance,
|
|
4235
5363
|
updateManualCommand,
|
|
4236
5364
|
buildUpdateStatus,
|
|
5365
|
+
buildDaemonStatusPayload,
|
|
4237
5366
|
parseYesNoAnswer,
|
|
4238
5367
|
normalizeLauncherRelaunchArgs,
|
|
4239
5368
|
officialRepositoryLine,
|
|
4240
5369
|
stripAnsi,
|
|
5370
|
+
normalizeLegacyHostFlagArgs,
|
|
5371
|
+
runGlobalNpmUninstall,
|
|
4241
5372
|
},
|
|
4242
5373
|
};
|
|
4243
5374
|
|