@researai/deepscientist 1.5.16 → 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 +66 -23
- package/bin/ds.js +550 -19
- package/docs/en/00_QUICK_START.md +65 -5
- package/docs/en/01_SETTINGS_REFERENCE.md +1 -1
- package/docs/en/09_DOCTOR.md +14 -3
- package/docs/en/15_CODEX_PROVIDER_SETUP.md +12 -3
- package/docs/en/21_LOCAL_MODEL_BACKENDS_GUIDE.md +283 -0
- package/docs/en/91_DEVELOPMENT.md +237 -0
- package/docs/en/README.md +7 -3
- package/docs/zh/00_QUICK_START.md +54 -5
- package/docs/zh/01_SETTINGS_REFERENCE.md +1 -1
- package/docs/zh/09_DOCTOR.md +15 -4
- package/docs/zh/15_CODEX_PROVIDER_SETUP.md +12 -3
- package/docs/zh/21_LOCAL_MODEL_BACKENDS_GUIDE.md +281 -0
- package/docs/zh/README.md +7 -3
- 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/bridges/connectors.py +8 -2
- package/src/deepscientist/codex_cli_compat.py +185 -72
- package/src/deepscientist/config/service.py +154 -6
- package/src/deepscientist/daemon/api/handlers.py +130 -25
- package/src/deepscientist/daemon/api/router.py +5 -0
- package/src/deepscientist/daemon/app.py +446 -22
- 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/prompts/builder.py +22 -4
- package/src/deepscientist/quest/service.py +413 -13
- package/src/deepscientist/runners/codex.py +59 -14
- package/src/deepscientist/shared.py +19 -0
- package/src/prompts/contracts/shared_interaction.md +3 -2
- package/src/prompts/system.md +13 -0
- package/src/prompts/system_copilot.md +13 -0
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/{AiManusChatView-COFACy7V.js → AiManusChatView-Bv-Z8YpU.js} +44 -44
- package/src/ui/dist/assets/{AnalysisPlugin-DnSm0GZn.js → AnalysisPlugin-BCKAfjba.js} +1 -1
- package/src/ui/dist/assets/{CliPlugin-CvwCmDQ5.js → CliPlugin-BCKcpc35.js} +4 -4
- package/src/ui/dist/assets/{CodeEditorPlugin-cOqSa0xq.js → CodeEditorPlugin-DbOfSJ8K.js} +1 -1
- package/src/ui/dist/assets/{CodeViewerPlugin-itb0tltR.js → CodeViewerPlugin-CbaFRrUU.js} +3 -3
- package/src/ui/dist/assets/{DocViewerPlugin-DqKkiCI6.js → DocViewerPlugin-DAjLVeQD.js} +3 -3
- package/src/ui/dist/assets/{GitCommitViewerPlugin-DVgNHBCS.js → GitCommitViewerPlugin-CIUqbUDO.js} +1 -1
- package/src/ui/dist/assets/{GitDiffViewerPlugin-DxL2ezFG.js → GitDiffViewerPlugin-CQACjoAA.js} +1 -1
- package/src/ui/dist/assets/{GitSnapshotViewer-B_RQm1YZ.js → GitSnapshotViewer-0r4nLPke.js} +1 -1
- package/src/ui/dist/assets/{ImageViewerPlugin-tHqlXY3n.js → ImageViewerPlugin-nBOmI2v_.js} +3 -3
- package/src/ui/dist/assets/{LabCopilotPanel-ClMbq5Yu.js → LabCopilotPanel-BHxOxF4z.js} +1 -1
- package/src/ui/dist/assets/{LabPlugin-L_SuE8ow.js → LabPlugin-BKoZGs95.js} +1 -1
- package/src/ui/dist/assets/{LatexPlugin-B495DTXC.js → LatexPlugin-ZwtV8pIp.js} +1 -1
- package/src/ui/dist/assets/{MarkdownViewerPlugin-DG28-61B.js → MarkdownViewerPlugin-DKqVfKyW.js} +3 -3
- package/src/ui/dist/assets/{MarketplacePlugin-BiOGT-Kj.js → MarketplacePlugin-BwxStZ9D.js} +1 -1
- package/src/ui/dist/assets/{NotebookEditor-C-4Kt1p9.js → NotebookEditor-BEQhaQbt.js} +1 -1
- package/src/ui/dist/assets/{NotebookEditor-CVsj8h_T.js → NotebookEditor-DB9N_T9q.js} +23 -23
- package/src/ui/dist/assets/{PdfLoader-CASDQmxJ.js → PdfLoader-eWBONbQP.js} +1 -1
- package/src/ui/dist/assets/{PdfMarkdownPlugin-BFhwoKsY.js → PdfMarkdownPlugin-D22YOZL3.js} +1 -1
- package/src/ui/dist/assets/{PdfViewerPlugin-DcOzU9vd.js → PdfViewerPlugin-c-RK9DLM.js} +3 -3
- package/src/ui/dist/assets/{SearchPlugin-CHj7M58O.js → SearchPlugin-CxF9ytAx.js} +1 -1
- package/src/ui/dist/assets/{TextViewerPlugin-CB4DYfWO.js → TextViewerPlugin-C5xqeeUH.js} +2 -2
- package/src/ui/dist/assets/{VNCViewer-CjlbyCB3.js → VNCViewer-BoLGLnHz.js} +1 -1
- package/src/ui/dist/assets/{bot-CFkZY-JP.js → bot-DREQOxzP.js} +1 -1
- package/src/ui/dist/assets/{chevron-up-Dq5ofbht.js → chevron-up-C9Qpx4DE.js} +1 -1
- package/src/ui/dist/assets/{code-DLC6G24T.js → code-WlFHE7z_.js} +1 -1
- package/src/ui/dist/assets/{file-content-Dv4LoZec.js → file-content-BZMz3RYp.js} +1 -1
- package/src/ui/dist/assets/{file-diff-panel-Denq-lC3.js → file-diff-panel-CQhw0jS2.js} +1 -1
- package/src/ui/dist/assets/{file-socket-Cu4Qln7Y.js → file-socket-CfQPKQKj.js} +1 -1
- package/src/ui/dist/assets/{git-commit-horizontal-BUh6G52n.js → git-commit-horizontal-DxZ8DCZh.js} +1 -1
- package/src/ui/dist/assets/{image-B9HUUddG.js → image-Bgl4VIyx.js} +1 -1
- package/src/ui/dist/assets/{index-Cgla8biy.css → index-BpV6lusQ.css} +1 -1
- package/src/ui/dist/assets/{index-Gbl53BNp.js → index-CBNVuWcP.js} +363 -363
- package/src/ui/dist/assets/{index-wQ7RIIRd.js → index-CwNu1aH4.js} +1 -1
- package/src/ui/dist/assets/{index-B2B1sg-M.js → index-DrUnlf6K.js} +1 -1
- package/src/ui/dist/assets/{index-DRyx7vAc.js → index-NW-h8VzN.js} +1 -1
- package/src/ui/dist/assets/{pdf-effect-queue-ZtnHFCAi.js → pdf-effect-queue-J8OnM0jE.js} +1 -1
- package/src/ui/dist/assets/{popover-DL6h35vr.js → popover-CLc0pPP8.js} +1 -1
- package/src/ui/dist/assets/{project-sync-CsX08Qno.js → project-sync-C9IdzdZW.js} +1 -1
- package/src/ui/dist/assets/{select-DvmXt1yY.js → select-Cs2PmzwL.js} +1 -1
- package/src/ui/dist/assets/{sigma-7jpXazui.js → sigma-ClKcHAXm.js} +1 -1
- package/src/ui/dist/assets/{trash-xA7kFt8i.js → trash-DwpbFr3w.js} +1 -1
- package/src/ui/dist/assets/{useCliAccess-DsMwDjOp.js → useCliAccess-NQ8m0Let.js} +1 -1
- package/src/ui/dist/assets/{wrap-text-CwMn-iqb.js → wrap-text-BC-Hltpd.js} +1 -1
- package/src/ui/dist/assets/{zoom-out-R-GWEhzS.js → zoom-out-E_gaeAxL.js} +1 -1
- package/src/ui/dist/index.html +2 -2
package/bin/ds.js
CHANGED
|
@@ -139,6 +139,7 @@ Usage:
|
|
|
139
139
|
ds update
|
|
140
140
|
ds update --check
|
|
141
141
|
ds update --yes
|
|
142
|
+
ds uninstall
|
|
142
143
|
ds migrate /data/DeepScientist
|
|
143
144
|
ds --here
|
|
144
145
|
ds --yolo --port 20999 --here
|
|
@@ -182,6 +183,9 @@ Migration:
|
|
|
182
183
|
ds migrate <target> Move the DeepScientist home/install root to a new absolute path
|
|
183
184
|
ds migrate <target> --yes --restart
|
|
184
185
|
|
|
186
|
+
Uninstall:
|
|
187
|
+
ds uninstall Remove code/runtime only and preserve local data
|
|
188
|
+
|
|
185
189
|
Runtime:
|
|
186
190
|
DeepScientist uses uv to manage a locked local Python runtime.
|
|
187
191
|
If uv is missing, ds bootstraps a local copy under the DeepScientist home automatically.
|
|
@@ -1028,10 +1032,10 @@ function writeCodexPreflightReport(home, probe) {
|
|
|
1028
1032
|
const profile = typeof details.profile === 'string' ? details.profile.trim() : '';
|
|
1029
1033
|
const intro = profile
|
|
1030
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.`
|
|
1031
|
-
: '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.';
|
|
1032
1036
|
const introZh = profile
|
|
1033
1037
|
? `DeepScientist 启动前进行了 Codex 可用性检查,但 profile \`${profile}\` 的 hello 探测没有通过。请先确认 \`codex --profile ${profile}\` 在当前机器上可以正常启动,并确保该 profile 依赖的 provider API Key、Base URL 和模型配置都已经在 Codex 中配置好。`
|
|
1034
|
-
: '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`。';
|
|
1035
1039
|
const renderItems = (items, tone) =>
|
|
1036
1040
|
items
|
|
1037
1041
|
.map(
|
|
@@ -1381,6 +1385,26 @@ Flags:
|
|
|
1381
1385
|
`);
|
|
1382
1386
|
}
|
|
1383
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
|
+
|
|
1384
1408
|
function parseUpdateArgs(argv) {
|
|
1385
1409
|
const args = [...argv];
|
|
1386
1410
|
if (args[0] === 'update') {
|
|
@@ -1504,6 +1528,43 @@ function parseMigrateArgs(argv) {
|
|
|
1504
1528
|
};
|
|
1505
1529
|
}
|
|
1506
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,
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1507
1568
|
function findFirstPositionalArg(args) {
|
|
1508
1569
|
for (let index = 0; index < args.length; index += 1) {
|
|
1509
1570
|
const arg = args[index];
|
|
@@ -2457,6 +2518,23 @@ function buildUvRuntimeEnv(home, extraEnv = {}) {
|
|
|
2457
2518
|
for (const key of ['PYTHONPATH', 'PYTHONHOME', 'VIRTUAL_ENV', '__PYVENV_LAUNCHER__']) {
|
|
2458
2519
|
delete env[key];
|
|
2459
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
|
+
}
|
|
2460
2538
|
return env;
|
|
2461
2539
|
}
|
|
2462
2540
|
|
|
@@ -2970,6 +3048,374 @@ function removeDaemonState(home) {
|
|
|
2970
3048
|
}
|
|
2971
3049
|
}
|
|
2972
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
|
+
|
|
2973
3419
|
function daemonSupervisorLogPath(home) {
|
|
2974
3420
|
return path.join(home, 'logs', 'daemon-supervisor.log');
|
|
2975
3421
|
}
|
|
@@ -3540,6 +3986,86 @@ async function stopDaemon(home) {
|
|
|
3540
3986
|
console.log('DeepScientist daemon stopped.');
|
|
3541
3987
|
}
|
|
3542
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
|
+
|
|
3543
4069
|
function writeUpdateLog(home, content) {
|
|
3544
4070
|
const logPath = path.join(home, 'logs', 'update.log');
|
|
3545
4071
|
ensureDir(path.dirname(logPath));
|
|
@@ -4335,7 +4861,7 @@ function handleCodexPreflightFailure(error) {
|
|
|
4335
4861
|
: [
|
|
4336
4862
|
'In most installs, `npm install -g @researai/deepscientist` also installs the bundled Codex dependency.',
|
|
4337
4863
|
'If `codex` is still missing, run `npm install -g @openai/codex`.',
|
|
4338
|
-
'Run `codex
|
|
4864
|
+
'Run `codex login` (or just `codex`) and finish authentication.',
|
|
4339
4865
|
'Run `ds doctor` and confirm the Codex check passes.',
|
|
4340
4866
|
'Run `ds` again.',
|
|
4341
4867
|
];
|
|
@@ -4638,9 +5164,14 @@ async function launcherMain(rawArgs) {
|
|
|
4638
5164
|
const home = options.home || resolveHome(rawArgs);
|
|
4639
5165
|
applyLauncherProxy(options.proxy);
|
|
4640
5166
|
ensureDir(home);
|
|
5167
|
+
registerCurrentInstall(home);
|
|
5168
|
+
const forceWrapperRepair =
|
|
5169
|
+
detectInstallMode(repoRoot) !== 'npm-package'
|
|
5170
|
+
&& Boolean(options.home || process.env.DEEPSCIENTIST_HOME);
|
|
4641
5171
|
repairLegacyPathWrappers({
|
|
4642
5172
|
home,
|
|
4643
5173
|
launcherPath: resolveLauncherPath(),
|
|
5174
|
+
force: forceWrapperRepair,
|
|
4644
5175
|
});
|
|
4645
5176
|
|
|
4646
5177
|
if (options.stop) {
|
|
@@ -4655,24 +5186,17 @@ async function launcherMain(rawArgs) {
|
|
|
4655
5186
|
const authToken = typeof state?.auth_token === 'string' ? state.auth_token.trim() : '';
|
|
4656
5187
|
const probeUrl = state?.url || browserUiUrl(configured.host, configured.port);
|
|
4657
5188
|
const health = await fetchHealth(probeUrl, authToken);
|
|
4658
|
-
const
|
|
4659
|
-
|
|
5189
|
+
const statusPayload = buildDaemonStatusPayload({
|
|
5190
|
+
home,
|
|
5191
|
+
url,
|
|
5192
|
+
state,
|
|
5193
|
+
health,
|
|
5194
|
+
launcherPath: resolveLauncherPath(),
|
|
5195
|
+
});
|
|
4660
5196
|
console.log(
|
|
4661
|
-
JSON.stringify(
|
|
4662
|
-
{
|
|
4663
|
-
healthy,
|
|
4664
|
-
identity_match: identityMatch,
|
|
4665
|
-
managed: Boolean(state),
|
|
4666
|
-
home,
|
|
4667
|
-
url,
|
|
4668
|
-
daemon: state,
|
|
4669
|
-
health,
|
|
4670
|
-
},
|
|
4671
|
-
null,
|
|
4672
|
-
2
|
|
4673
|
-
)
|
|
5197
|
+
JSON.stringify(statusPayload, null, 2)
|
|
4674
5198
|
);
|
|
4675
|
-
process.exit(healthy && (!state ||
|
|
5199
|
+
process.exit(statusPayload.healthy && (!state || statusPayload.identity_match) ? 0 : 1);
|
|
4676
5200
|
}
|
|
4677
5201
|
|
|
4678
5202
|
const pythonRuntime = ensurePythonRuntime(home);
|
|
@@ -4761,6 +5285,10 @@ async function main() {
|
|
|
4761
5285
|
await migrateMain(args);
|
|
4762
5286
|
return;
|
|
4763
5287
|
}
|
|
5288
|
+
if (positional && positional.value === 'uninstall') {
|
|
5289
|
+
await uninstallMain(args);
|
|
5290
|
+
return;
|
|
5291
|
+
}
|
|
4764
5292
|
if (
|
|
4765
5293
|
args.length === 0
|
|
4766
5294
|
|| args[0] === 'ui'
|
|
@@ -4824,6 +5352,7 @@ module.exports = {
|
|
|
4824
5352
|
generateBrowserAuthToken,
|
|
4825
5353
|
appendBrowserAuthToken,
|
|
4826
5354
|
normalizeProxyUrl,
|
|
5355
|
+
buildCodeOnlyUninstallPlan,
|
|
4827
5356
|
parseMigrateArgs,
|
|
4828
5357
|
parseLegacyWrapperCandidate,
|
|
4829
5358
|
repairLegacyPathWrappers,
|
|
@@ -4833,11 +5362,13 @@ module.exports = {
|
|
|
4833
5362
|
buildUvSyncFailureGuidance,
|
|
4834
5363
|
updateManualCommand,
|
|
4835
5364
|
buildUpdateStatus,
|
|
5365
|
+
buildDaemonStatusPayload,
|
|
4836
5366
|
parseYesNoAnswer,
|
|
4837
5367
|
normalizeLauncherRelaunchArgs,
|
|
4838
5368
|
officialRepositoryLine,
|
|
4839
5369
|
stripAnsi,
|
|
4840
5370
|
normalizeLegacyHostFlagArgs,
|
|
5371
|
+
runGlobalNpmUninstall,
|
|
4841
5372
|
},
|
|
4842
5373
|
};
|
|
4843
5374
|
|