@delta-infra/cli 1.0.80 → 1.0.82

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.
Files changed (32) hide show
  1. package/README.md +429 -429
  2. package/checksums.txt +5 -5
  3. package/package.json +1 -1
  4. package/scripts/install-wizard.js +625 -625
  5. package/scripts/install.js +417 -417
  6. package/scripts/run.js +65 -65
  7. package/skills/delta-sandbox/SKILL.md +356 -356
  8. package/skills/delta-sandbox/agents/openai.yaml +4 -4
  9. package/skills/delta-sandbox/references/commands.md +56 -56
  10. package/skills/delta-sandbox/references/lifecycle.md +92 -92
  11. package/skills/delta-sandbox/references/recipes.md +133 -133
  12. package/skills/delta-science/SKILL.md +136 -150
  13. package/skills/delta-science/agents/openai.yaml +4 -4
  14. package/skills/delta-science/references/antbo.md +100 -100
  15. package/skills/delta-science/references/cli-contract.md +53 -83
  16. package/skills/delta-science/references/delta-bo.md +89 -90
  17. package/skills/delta-science/references/gsasii.md +116 -116
  18. package/skills/delta-science/references/lammps.md +66 -66
  19. package/skills/delta-science/references/ldm-bo.md +129 -130
  20. package/skills/delta-science/references/ldm.md +73 -72
  21. package/skills/delta-science/references/pubchem.md +126 -127
  22. package/skills/delta-science/references/pymatgen.md +68 -68
  23. package/skills/delta-science/references/rdkit.md +138 -139
  24. package/skills/delta-science/references/routing-index.md +36 -36
  25. package/skills/delta-science/references/strbo.md +87 -87
  26. package/skills/delta-science/references/synbo.md +198 -197
  27. package/skills/delta-science/references/workflows.md +61 -61
  28. package/skills/delta-shared/SKILL.md +95 -95
  29. package/skills/delta-shared/agents/openai.yaml +4 -4
  30. package/skills/manifest.json +19 -19
  31. package/skills/delta-science/scripts/catalog.py +0 -92
  32. package/skills/delta-science/scripts/invoke.py +0 -346
@@ -1,625 +1,625 @@
1
- #!/usr/bin/env node
2
- // Copyright (c) 2026 Delta Infra Authors
3
- // SPDX-License-Identifier: MIT
4
-
5
- const fs = require("fs");
6
- const path = require("path");
7
- const { execFileSync, execFile } = require("child_process");
8
- const p = require("@clack/prompts");
9
-
10
- const PKG = "@delta-infra/cli";
11
- const SKILLS_REPO = "yzailab/delta-infra-cli";
12
- const NPM_REGISTRIES = ["https://registry.npmmirror.com", "https://registry.npmjs.org"];
13
- const GH_MIRRORS = ["https://gh.ddlc.top", "https://ghproxy.net", "https://gh-proxy.com"];
14
- const CONFIG_DIR = path.join(osHomedir(), ".delta-infra");
15
- const CONFIG_VERSION = 1;
16
- const DEFAULT_BASE_URL = "https://delta-infra-nacos-test.yangtzeailab.com/sandbox/api/v1";
17
- const DEFAULT_SCIENCE_BASE_URL = "http://8.141.101.94:8080/science_tool";
18
- const isWindows = process.platform === "win32";
19
-
20
- const PLATFORM_PATHS = {
21
- agents: [".agents", "skills"],
22
- claude: [".claude", "skills"],
23
- opencode: [".config", "opencode", "skills"],
24
- cursor: [".cursor", "skills"],
25
- mementos: ["memento_s", "skills"],
26
- };
27
-
28
- const PLATFORM_LABELS = {
29
- zh: {
30
- agents: "通用 Agent 目录 (~/.agents/skills) — Codex / Cursor / OpenCode",
31
- claude: "Claude Code (~/.claude/skills)",
32
- opencode: "OpenCode 原生目录 (~/.config/opencode/skills)",
33
- cursor: "Cursor (~/.cursor/skills)",
34
- mementos: "Memento-S 开发模式 (~/memento_s/skills)",
35
- },
36
- en: {
37
- agents: "Generic agent dir (~/.agents/skills) — Codex / Cursor / OpenCode",
38
- claude: "Claude Code (~/.claude/skills)",
39
- opencode: "OpenCode native dir (~/.config/opencode/skills)",
40
- cursor: "Cursor (~/.cursor/skills)",
41
- mementos: "Memento-S dev mode (~/memento_s/skills)",
42
- },
43
- };
44
-
45
- const skillManifest = require("../skills/manifest.json");
46
- const SKILL_NAMES = skillManifest.skills;
47
- const RETIRED_SKILL_NAMES = skillManifest.retired_skills || [];
48
- const LANG = "zh";
49
-
50
- function platformSkillDir(platform) {
51
- return path.join(osHomedir(), ...PLATFORM_PATHS[platform]);
52
- }
53
-
54
- // ── i18n ────────────────────────────────────────────────────────────────────
55
-
56
- const messages = {
57
- zh: {
58
- setup: "正在设置 Delta Infra CLI...",
59
- upgrade: "正在升级 %s (v%s → v%s)...",
60
- step1: "正在安装 %s...",
61
- step1Skip: "已安装 (v%s),跳过",
62
- step1Done: "已全局安装",
63
- step1Upgraded: "已升级到 v%s",
64
- step1Fail: "全局安装失败。运行以下命令重试: npm install -g %s",
65
- step2: "安装 AI Skills",
66
- step2SelectPlatforms: "选择需要安装 Delta Skills 的 AI 工具平台(可多选)",
67
- step2Spinner: "正在安装 Skills...",
68
- step2Done: "Skills 已安装",
69
- step2DoneFor: "已安装至: %s",
70
- step2Skip: "已安装,跳过",
71
- step2Fail: "Skills 安装失败。运行以下命令重试: npx skills add %s -y -g",
72
- step3: "正在初始化配置...",
73
- step3BaseUrl: "Sandbox API 地址",
74
- step3ScienceUrl:"Science API 地址",
75
- step3Skip: "跳过配置初始化",
76
- step3Done: "配置已初始化",
77
- step3Fail: "配置初始化失败。运行以下命令重试: delta-cli config init",
78
- step4: "身份认证",
79
- step4Confirm: "是否进行身份认证?",
80
- step4Skip: "跳过身份认证。后续运行 delta-cli auth login 完成",
81
- step4Done: "认证完成",
82
- step4Fail: "认证失败。运行以下命令重试: delta-cli auth login",
83
- done: "安装完成!\n现在可以让你的 AI 工具使用 delta-cli 管理 Delta Sandbox 资源或调用 Science 工具。",
84
- cancelled: "安装已取消",
85
- nonTtyHint: "要完成配置,请在终端中运行:\n delta-cli config init\n delta-cli auth login",
86
- },
87
- en: {
88
- setup: "Setting up Delta Infra CLI...",
89
- upgrade: "Upgrading %s (v%s → v%s)...",
90
- step1: "Installing %s globally...",
91
- step1Skip: "Already installed (v%s). Skipped",
92
- step1Done: "Installed globally",
93
- step1Upgraded: "Upgraded to v%s",
94
- step1Fail: "Failed to install globally. Run manually: npm install -g %s",
95
- step2: "Install AI skills",
96
- step2SelectPlatforms: "Select AI tool platforms to install Delta Skills (multi-select)",
97
- step2Spinner: "Installing skills...",
98
- step2Done: "Skills installed",
99
- step2DoneFor: "Installed to: %s",
100
- step2Skip: "Already installed. Skipped",
101
- step2Fail: "Failed to install skills. Run manually: npx skills add %s -y -g",
102
- step3: "Initializing config...",
103
- step3BaseUrl: "Sandbox API URL",
104
- step3ScienceUrl:"Science API URL",
105
- step3Skip: "Skipped config initialization",
106
- step3Done: "Config initialized",
107
- step3Fail: "Failed to init config. Run manually: delta-cli config init",
108
- step4: "Authorization",
109
- step4Confirm: "Would you like to authenticate now?",
110
- step4Skip: "Skipped. Run delta-cli auth login to authorize later",
111
- step4Done: "Authorization complete",
112
- step4Fail: "Failed to authorize. Run delta-cli auth login to retry",
113
- done: "You are all set!\nYour AI tool can now use delta-cli to manage Delta Sandbox resources or invoke Science tools.",
114
- cancelled: "Installation cancelled",
115
- nonTtyHint: "To complete setup, run interactively:\n delta-cli config init\n delta-cli auth login",
116
- },
117
- };
118
-
119
- // ── Helpers ─────────────────────────────────────────────────────────────────
120
-
121
- function handleCancel(value, msg) {
122
- if (p.isCancel(value)) {
123
- p.cancel(msg.cancelled);
124
- process.exit(0);
125
- }
126
- return value;
127
- }
128
-
129
- function execCmd(cmd, args, opts) {
130
- if (isWindows) {
131
- return execFileSync("cmd.exe", ["/c", cmd, ...args], opts);
132
- }
133
- return execFileSync(cmd, args, opts);
134
- }
135
-
136
- function run(cmd, args, opts = {}) {
137
- execCmd(cmd, args, { stdio: "inherit", ...opts });
138
- }
139
-
140
- function runSilent(cmd, args, opts = {}) {
141
- return execCmd(cmd, args, {
142
- stdio: ["ignore", "pipe", "pipe"],
143
- ...opts,
144
- });
145
- }
146
-
147
- function runSilentAsync(cmd, args, opts = {}) {
148
- const actualCmd = isWindows ? "cmd.exe" : cmd;
149
- const actualArgs = isWindows ? ["/c", cmd, ...args] : args;
150
- return new Promise((resolve, reject) => {
151
- execFile(actualCmd, actualArgs, {
152
- stdio: ["ignore", "pipe", "pipe"],
153
- ...opts,
154
- }, (err, stdout, stderr) => {
155
- if (err) {
156
- err.stdout = stdout;
157
- err.stderr = stderr;
158
- reject(err);
159
- }
160
- else resolve(stdout);
161
- });
162
- });
163
- }
164
-
165
- function fmt(template, ...values) {
166
- let i = 0;
167
- return template.replace(/%s/g, () => values[i++] ?? "");
168
- }
169
-
170
- function formatExecErr(err) {
171
- if (!err) return "";
172
- if (err.stderr) return err.stderr.toString().trim();
173
- if (err.stdout) return err.stdout.toString().trim();
174
- return err.message ? err.message.trim() : String(err);
175
- }
176
-
177
- function osHomedir() {
178
- return process.env.HOME ||
179
- process.env.USERPROFILE ||
180
- (process.env.HOMEDRIVE && process.env.HOMEPATH && process.env.HOMEDRIVE + process.env.HOMEPATH) ||
181
- "/root";
182
- }
183
-
184
- function isInteractiveEnv() {
185
- return !!process.stdin.isTTY &&
186
- !!process.stdout.isTTY &&
187
- !process.env.CI &&
188
- !process.env.NO_COLOR &&
189
- process.env.TERM !== "dumb";
190
- }
191
-
192
- function createSpinner() {
193
- if (isInteractiveEnv()) return p.spinner();
194
- return {
195
- start: (msg) => { if (msg) console.log(msg); },
196
- stop: (msg) => { if (msg) console.log(msg); },
197
- };
198
- }
199
-
200
- function whichDeltaCli() {
201
- try {
202
- const prefix = execFileSync("npm", ["prefix", "-g"], {
203
- stdio: ["ignore", "pipe", "pipe"],
204
- }).toString().trim();
205
- const bin = isWindows
206
- ? path.join(prefix, "delta-cli.cmd")
207
- : path.join(prefix, "bin", "delta-cli");
208
- if (fs.existsSync(bin)) return bin;
209
- } catch {}
210
- try {
211
- const cmd = isWindows ? "where" : "which";
212
- return execFileSync(cmd, ["delta-cli"], { stdio: ["ignore", "pipe", "pipe"] })
213
- .toString().split("\n")[0].trim();
214
- } catch {
215
- return null;
216
- }
217
- }
218
-
219
- function getLatestVersion() {
220
- try {
221
- const out = runSilent("npm", ["view", PKG, "version"], { timeout: 15000 });
222
- const ver = out.toString().trim();
223
- return /^\d+\.\d+\.\d+/.test(ver) ? ver : null;
224
- } catch { return null; }
225
- }
226
-
227
- function semverLessThan(a, b) {
228
- const pa = a.replace(/-.*$/, "").split(".").map(Number);
229
- const pb = b.replace(/-.*$/, "").split(".").map(Number);
230
- for (let i = 0; i < 3; i++) {
231
- if ((pa[i] || 0) < (pb[i] || 0)) return true;
232
- if ((pa[i] || 0) > (pb[i] || 0)) return false;
233
- }
234
- return false;
235
- }
236
-
237
- function getGloballyInstalledVersion() {
238
- try {
239
- const out = runSilent("npm", ["list", "-g", PKG], { timeout: 15000 });
240
- const match = out.toString().match(/@(\d+\.\d+\.\d+[^\s]*)/);
241
- return match ? match[1] : "unknown";
242
- } catch { return null; }
243
- }
244
-
245
- function getExistingConfig() {
246
- try {
247
- const configPath = path.join(CONFIG_DIR, "config.json");
248
- if (!fs.existsSync(configPath)) return null;
249
- return JSON.parse(fs.readFileSync(configPath, "utf8"));
250
- } catch { return null; }
251
- }
252
-
253
- function writeConfig({ baseUrl, scienceUrl, existing = null } = {}) {
254
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
255
- const config = {
256
- version: CONFIG_VERSION,
257
- base_url: baseUrl || DEFAULT_BASE_URL,
258
- science_base_url: scienceUrl || existing?.science_base_url || DEFAULT_SCIENCE_BASE_URL,
259
- };
260
- if (existing && existing.token) {
261
- config.token = existing.token;
262
- }
263
- fs.writeFileSync(path.join(CONFIG_DIR, "config.json"), JSON.stringify(config, null, 2) + "\n", { mode: 0o600 });
264
- }
265
-
266
- // ── Steps ───────────────────────────────────────────────────────────────────
267
-
268
- async function stepInstallGlobally(msg) {
269
- const installedVer = getGloballyInstalledVersion();
270
- const latestVer = getLatestVersion();
271
- const needsUpgrade = installedVer && latestVer && semverLessThan(installedVer, latestVer);
272
-
273
- if (installedVer && !needsUpgrade) {
274
- p.log.info(fmt(msg.step1Skip, installedVer));
275
- return false;
276
- }
277
-
278
- const targetVer = latestVer || installedVer;
279
- const s = createSpinner();
280
- if (needsUpgrade) {
281
- s.start(fmt(msg.upgrade, PKG, installedVer, latestVer));
282
- } else {
283
- s.start(fmt(msg.step1, PKG));
284
- }
285
- let lastInstallErr = null;
286
- try {
287
- let installed = false;
288
- for (const registry of NPM_REGISTRIES) {
289
- try {
290
- await runSilentAsync("npm", ["install", "-g", PKG, "--registry", registry], {
291
- timeout: 300000,
292
- env: { ...process.env, DELTA_CLI_MIRROR: GH_MIRRORS[0] },
293
- });
294
- installed = true;
295
- break;
296
- } catch (e) {
297
- lastInstallErr = e;
298
- const reason = formatExecErr(e).slice(0, 400);
299
- if (reason) p.log.error(`npm registry ${registry} 安装失败: ${reason}`);
300
- }
301
- }
302
- if (!installed) throw lastInstallErr || new Error("all npm registries failed");
303
-
304
- if (targetVer) {
305
- for (const mirror of GH_MIRRORS) {
306
- try {
307
- await runSilentAsync(process.execPath, [
308
- path.join(__dirname, "install.js"),
309
- ], {
310
- timeout: 300000,
311
- env: { ...process.env, DELTA_CLI_RUN: "true", DELTA_CLI_VERSION: targetVer, DELTA_CLI_MIRROR: mirror },
312
- });
313
- break;
314
- } catch { }
315
- }
316
- }
317
- s.stop(needsUpgrade ? fmt(msg.step1Upgraded, latestVer) : msg.step1Done);
318
- return needsUpgrade;
319
- } catch (e) {
320
- s.stop(fmt(msg.step1Fail, PKG));
321
- const reason = formatExecErr(e).slice(0, 1200);
322
- if (reason) p.log.error(`失败原因:\n${reason}`);
323
- process.exit(1);
324
- }
325
- }
326
-
327
- function findPackageSkillsDir() {
328
- const candidates = [];
329
- try {
330
- const npmRoot = runSilent("npm", ["root", "-g"], { timeout: 10000 })
331
- .toString().trim();
332
- candidates.push(path.join(npmRoot, PKG, "skills"));
333
- } catch {}
334
- candidates.push(path.join(__dirname, "..", "skills"));
335
-
336
- for (const c of candidates) {
337
- if (fs.existsSync(path.join(c, "delta-sandbox", "SKILL.md"))) {
338
- return c;
339
- }
340
- }
341
- return null;
342
- }
343
-
344
- function installSkillsFromLocalPackage(platforms) {
345
- const pkgSkillsDir = findPackageSkillsDir();
346
- if (!pkgSkillsDir) return false;
347
-
348
- for (const platform of platforms) {
349
- const targetDir = platformSkillDir(platform);
350
- fs.mkdirSync(targetDir, { recursive: true });
351
- for (const skill of RETIRED_SKILL_NAMES) {
352
- try { fs.rmSync(path.join(targetDir, skill), { recursive: true, force: true }); } catch {}
353
- }
354
- for (const skill of SKILL_NAMES) {
355
- const src = path.join(pkgSkillsDir, skill);
356
- if (!fs.existsSync(src)) continue;
357
- const dst = path.join(targetDir, skill);
358
- try { fs.rmSync(dst, { recursive: true, force: true }); } catch {}
359
- fs.cpSync(src, dst, { recursive: true });
360
- }
361
- }
362
- return true;
363
- }
364
-
365
- async function stepSelectPlatforms(msg) {
366
- if (!isInteractiveEnv()) {
367
- return ["agents"];
368
- }
369
- const labels = PLATFORM_LABELS[LANG];
370
- const defaultPlatforms = fs.existsSync(path.dirname(platformSkillDir("claude")))
371
- ? ["agents", "claude", "mementos"]
372
- : ["agents", "mementos"];
373
- const selected = await p.multiselect({
374
- message: msg.step2SelectPlatforms,
375
- options: Object.keys(PLATFORM_PATHS).map((key) => ({
376
- value: key,
377
- label: labels[key],
378
- })),
379
- initialValues: defaultPlatforms,
380
- required: true,
381
- });
382
- handleCancel(selected, msg);
383
- return selected;
384
- }
385
-
386
- async function stepInstallSkills(msg, platforms) {
387
- const s = createSpinner();
388
- s.start(msg.step2Spinner);
389
- try {
390
- try {
391
- if (installSkillsFromLocalPackage(platforms)) {
392
- s.stop(msg.step2Done);
393
- p.log.info(fmt(msg.step2DoneFor, platforms.join(", ")));
394
- return;
395
- }
396
- } catch { }
397
-
398
- if (!platforms.includes("agents")) {
399
- throw new Error("local skills package not found and agent dir not selected");
400
- }
401
-
402
- const urls = [
403
- `https://gh.ddlc.top/https://github.com/${SKILLS_REPO}`,
404
- `https://ghproxy.net/https://github.com/${SKILLS_REPO}`,
405
- `https://github.com/${SKILLS_REPO}`,
406
- ];
407
- let lastErr;
408
- for (const url of urls) {
409
- try {
410
- s.start(`${msg.step2Spinner} (try ${url.split("/")[2]})`);
411
- await runSilentAsync("npx", ["-y", "skills", "add", url, "-y", "-g"], {
412
- timeout: 15000,
413
- env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
414
- });
415
- s.stop(msg.step2Done);
416
- p.log.info(fmt(msg.step2DoneFor, "agents"));
417
- return;
418
- } catch (e) {
419
- lastErr = e;
420
- if (e && e.stderr) console.error(`[skills mirror failed] ${url}: ${e.stderr.toString().trim().slice(0, 200)}`);
421
- else if (e && e.message) console.error(`[skills mirror failed] ${url}: ${e.message}`);
422
- }
423
- }
424
- throw lastErr;
425
- } catch {
426
- s.stop(fmt(msg.step2Fail, SKILLS_REPO));
427
- process.exit(1);
428
- }
429
- }
430
-
431
- async function stepConfigInit(msg) {
432
- const s = createSpinner();
433
- s.start(msg.step3);
434
-
435
- const existingConfig = getExistingConfig();
436
- s.stop(msg.step3);
437
-
438
- if (existingConfig && existingConfig.base_url) {
439
- const reuse = await p.confirm({
440
- message: `发现已有配置 (Server: ${existingConfig.base_url}),继续使用?`,
441
- });
442
- if (p.isCancel(reuse)) {
443
- handleCancel(reuse, msg);
444
- return;
445
- }
446
- if (reuse) {
447
- p.log.info(msg.step3Skip);
448
- return;
449
- }
450
- }
451
-
452
- const baseUrl = handleCancel(await p.text({
453
- message: msg.step3BaseUrl,
454
- initialValue: existingConfig && existingConfig.base_url
455
- ? existingConfig.base_url
456
- : DEFAULT_BASE_URL,
457
- }), msg);
458
-
459
- const scienceUrl = handleCancel(await p.text({
460
- message: msg.step3ScienceUrl,
461
- initialValue: existingConfig && existingConfig.science_base_url
462
- ? existingConfig.science_base_url
463
- : DEFAULT_SCIENCE_BASE_URL,
464
- }), msg);
465
-
466
- try {
467
- writeConfig({ baseUrl, scienceUrl, existing: existingConfig });
468
- p.log.success(msg.step3Done);
469
- } catch (e) {
470
- p.log.error(e.message || String(e));
471
- p.log.error(msg.step3Fail);
472
- process.exit(1);
473
- }
474
- }
475
-
476
- async function stepAuthLogin(msg) {
477
- const deltaCli = whichDeltaCli();
478
- if (!deltaCli) {
479
- p.log.warn("delta-cli not found, skipping authorization");
480
- return;
481
- }
482
-
483
- const yes = await p.confirm({
484
- message: msg.step4Confirm,
485
- });
486
- if (p.isCancel(yes)) {
487
- p.cancel(msg.cancelled);
488
- process.exit(0);
489
- }
490
- if (!yes) {
491
- p.log.info(msg.step4Skip);
492
- return;
493
- }
494
-
495
- p.log.step(msg.step4);
496
- try {
497
- run(deltaCli, ["auth", "login"]);
498
- p.log.success(msg.step4Done);
499
- } catch {
500
- p.log.warn(msg.step4Fail);
501
- }
502
- }
503
-
504
- // ── Uninstall steps ─────────────────────────────────────────────────────────
505
-
506
- async function stepUninstallSkills(msg) {
507
- const s = createSpinner();
508
- s.start("Removing AI skills...");
509
-
510
- let removedLocally = false;
511
- for (const platform of Object.keys(PLATFORM_PATHS)) {
512
- const dir = platformSkillDir(platform);
513
- for (const skill of [...SKILL_NAMES, ...RETIRED_SKILL_NAMES]) {
514
- try {
515
- fs.rmSync(path.join(dir, skill), { recursive: true, force: true });
516
- removedLocally = true;
517
- } catch { }
518
- }
519
- }
520
-
521
- if (removedLocally) {
522
- s.stop("AI skills removed");
523
- return;
524
- }
525
-
526
- try {
527
- await runSilentAsync(
528
- "npx",
529
- ["-y", "skills", "remove", ...SKILL_NAMES, ...RETIRED_SKILL_NAMES, "-g"],
530
- {
531
- timeout: 60000,
532
- },
533
- );
534
- s.stop("AI skills removed");
535
- } catch {
536
- s.stop("Warning: skill removal failed. You can retry:");
537
- p.log.info(` npx skills remove ${[...SKILL_NAMES, ...RETIRED_SKILL_NAMES].join(" ")} -g`);
538
- }
539
- }
540
-
541
- function stepRemoveConfig() {
542
- if (!fs.existsSync(CONFIG_DIR)) {
543
- return;
544
- }
545
- fs.rmSync(CONFIG_DIR, { recursive: true, force: true });
546
- }
547
-
548
- async function stepUninstallPackage(msg) {
549
- const installedVer = getGloballyInstalledVersion();
550
- if (!installedVer) {
551
- return;
552
- }
553
- const s = createSpinner();
554
- s.start("Removing global package...");
555
- try {
556
- await runSilentAsync("npm", ["uninstall", "-g", PKG], { timeout: 60000 });
557
- s.stop("Package removed");
558
- } catch {
559
- s.stop("Failed to remove global package");
560
- p.log.info(` npm uninstall -g ${PKG}`);
561
- }
562
- }
563
-
564
- // ── Main ────────────────────────────────────────────────────────────────────
565
-
566
- async function doInstall() {
567
- const isInteractive = isInteractiveEnv();
568
-
569
- if (isInteractive) {
570
- p.intro(messages.zh.setup);
571
- await stepInstallGlobally(messages.zh);
572
- const platforms = await stepSelectPlatforms(messages.zh);
573
- await stepInstallSkills(messages.zh, platforms);
574
- await stepConfigInit(messages.zh);
575
- await stepAuthLogin(messages.zh);
576
- p.outro(messages.zh.done);
577
- } else {
578
- console.log(messages.zh.setup);
579
- await stepInstallGlobally(messages.zh);
580
- const platforms = ["agents"];
581
- await stepInstallSkills(messages.zh, platforms);
582
- console.log(messages.zh.nonTtyHint);
583
- }
584
- }
585
-
586
- async function doUninstall() {
587
- const isInteractive = isInteractiveEnv();
588
-
589
- if (isInteractive) {
590
- const ok = await p.confirm({
591
- message: "将移除全局包、AI Skills 和配置文件。是否继续?",
592
- });
593
- if (p.isCancel(ok) || !ok) {
594
- p.cancel("卸载已取消");
595
- process.exit(0);
596
- }
597
- }
598
-
599
- await stepRemoveConfig();
600
- await stepUninstallSkills();
601
- await stepUninstallPackage();
602
-
603
- if (isInteractive) {
604
- p.outro("卸载完成");
605
- } else {
606
- console.log("\nUninstall complete.");
607
- }
608
- }
609
-
610
- function main() {
611
- const subcommand = process.argv[2];
612
- if (subcommand === "uninstall") {
613
- doUninstall().catch((e) => {
614
- console.error("[delta-cli] Uninstall failed:", e.message);
615
- process.exit(1);
616
- });
617
- } else {
618
- doInstall().catch((e) => {
619
- console.error("[delta-cli] Install failed:", e.message);
620
- process.exit(1);
621
- });
622
- }
623
- }
624
-
625
- main();
1
+ #!/usr/bin/env node
2
+ // Copyright (c) 2026 Delta Infra Authors
3
+ // SPDX-License-Identifier: MIT
4
+
5
+ const fs = require("fs");
6
+ const path = require("path");
7
+ const { execFileSync, execFile } = require("child_process");
8
+ const p = require("@clack/prompts");
9
+
10
+ const PKG = "@delta-infra/cli";
11
+ const SKILLS_REPO = "yzailab/delta-infra-cli";
12
+ const NPM_REGISTRIES = ["https://registry.npmmirror.com", "https://registry.npmjs.org"];
13
+ const GH_MIRRORS = ["https://gh.ddlc.top", "https://ghproxy.net", "https://gh-proxy.com"];
14
+ const CONFIG_DIR = path.join(osHomedir(), ".delta-infra");
15
+ const CONFIG_VERSION = 1;
16
+ const DEFAULT_BASE_URL = "https://delta-infra-nacos-test.yangtzeailab.com/sandbox/api/v1";
17
+ const DEFAULT_SCIENCE_BASE_URL = "http://8.141.101.94:8080/science_tool";
18
+ const isWindows = process.platform === "win32";
19
+
20
+ const PLATFORM_PATHS = {
21
+ agents: [".agents", "skills"],
22
+ claude: [".claude", "skills"],
23
+ opencode: [".config", "opencode", "skills"],
24
+ cursor: [".cursor", "skills"],
25
+ mementos: ["memento_s", "skills"],
26
+ };
27
+
28
+ const PLATFORM_LABELS = {
29
+ zh: {
30
+ agents: "通用 Agent 目录 (~/.agents/skills) — Codex / Cursor / OpenCode",
31
+ claude: "Claude Code (~/.claude/skills)",
32
+ opencode: "OpenCode 原生目录 (~/.config/opencode/skills)",
33
+ cursor: "Cursor (~/.cursor/skills)",
34
+ mementos: "Memento-S 开发模式 (~/memento_s/skills)",
35
+ },
36
+ en: {
37
+ agents: "Generic agent dir (~/.agents/skills) — Codex / Cursor / OpenCode",
38
+ claude: "Claude Code (~/.claude/skills)",
39
+ opencode: "OpenCode native dir (~/.config/opencode/skills)",
40
+ cursor: "Cursor (~/.cursor/skills)",
41
+ mementos: "Memento-S dev mode (~/memento_s/skills)",
42
+ },
43
+ };
44
+
45
+ const skillManifest = require("../skills/manifest.json");
46
+ const SKILL_NAMES = skillManifest.skills;
47
+ const RETIRED_SKILL_NAMES = skillManifest.retired_skills || [];
48
+ const LANG = "zh";
49
+
50
+ function platformSkillDir(platform) {
51
+ return path.join(osHomedir(), ...PLATFORM_PATHS[platform]);
52
+ }
53
+
54
+ // ── i18n ────────────────────────────────────────────────────────────────────
55
+
56
+ const messages = {
57
+ zh: {
58
+ setup: "正在设置 Delta Infra CLI...",
59
+ upgrade: "正在升级 %s (v%s → v%s)...",
60
+ step1: "正在安装 %s...",
61
+ step1Skip: "已安装 (v%s),跳过",
62
+ step1Done: "已全局安装",
63
+ step1Upgraded: "已升级到 v%s",
64
+ step1Fail: "全局安装失败。运行以下命令重试: npm install -g %s",
65
+ step2: "安装 AI Skills",
66
+ step2SelectPlatforms: "选择需要安装 Delta Skills 的 AI 工具平台(可多选)",
67
+ step2Spinner: "正在安装 Skills...",
68
+ step2Done: "Skills 已安装",
69
+ step2DoneFor: "已安装至: %s",
70
+ step2Skip: "已安装,跳过",
71
+ step2Fail: "Skills 安装失败。运行以下命令重试: npx skills add %s -y -g",
72
+ step3: "正在初始化配置...",
73
+ step3BaseUrl: "Sandbox API 地址",
74
+ step3ScienceUrl:"Science API 地址",
75
+ step3Skip: "跳过配置初始化",
76
+ step3Done: "配置已初始化",
77
+ step3Fail: "配置初始化失败。运行以下命令重试: delta-cli config init",
78
+ step4: "身份认证",
79
+ step4Confirm: "是否进行身份认证?",
80
+ step4Skip: "跳过身份认证。后续运行 delta-cli auth login 完成",
81
+ step4Done: "认证完成",
82
+ step4Fail: "认证失败。运行以下命令重试: delta-cli auth login",
83
+ done: "安装完成!\n现在可以让你的 AI 工具使用 delta-cli 管理 Delta Sandbox 资源或调用 Science 工具。",
84
+ cancelled: "安装已取消",
85
+ nonTtyHint: "要完成配置,请在终端中运行:\n delta-cli config init\n delta-cli auth login",
86
+ },
87
+ en: {
88
+ setup: "Setting up Delta Infra CLI...",
89
+ upgrade: "Upgrading %s (v%s → v%s)...",
90
+ step1: "Installing %s globally...",
91
+ step1Skip: "Already installed (v%s). Skipped",
92
+ step1Done: "Installed globally",
93
+ step1Upgraded: "Upgraded to v%s",
94
+ step1Fail: "Failed to install globally. Run manually: npm install -g %s",
95
+ step2: "Install AI skills",
96
+ step2SelectPlatforms: "Select AI tool platforms to install Delta Skills (multi-select)",
97
+ step2Spinner: "Installing skills...",
98
+ step2Done: "Skills installed",
99
+ step2DoneFor: "Installed to: %s",
100
+ step2Skip: "Already installed. Skipped",
101
+ step2Fail: "Failed to install skills. Run manually: npx skills add %s -y -g",
102
+ step3: "Initializing config...",
103
+ step3BaseUrl: "Sandbox API URL",
104
+ step3ScienceUrl:"Science API URL",
105
+ step3Skip: "Skipped config initialization",
106
+ step3Done: "Config initialized",
107
+ step3Fail: "Failed to init config. Run manually: delta-cli config init",
108
+ step4: "Authorization",
109
+ step4Confirm: "Would you like to authenticate now?",
110
+ step4Skip: "Skipped. Run delta-cli auth login to authorize later",
111
+ step4Done: "Authorization complete",
112
+ step4Fail: "Failed to authorize. Run delta-cli auth login to retry",
113
+ done: "You are all set!\nYour AI tool can now use delta-cli to manage Delta Sandbox resources or invoke Science tools.",
114
+ cancelled: "Installation cancelled",
115
+ nonTtyHint: "To complete setup, run interactively:\n delta-cli config init\n delta-cli auth login",
116
+ },
117
+ };
118
+
119
+ // ── Helpers ─────────────────────────────────────────────────────────────────
120
+
121
+ function handleCancel(value, msg) {
122
+ if (p.isCancel(value)) {
123
+ p.cancel(msg.cancelled);
124
+ process.exit(0);
125
+ }
126
+ return value;
127
+ }
128
+
129
+ function execCmd(cmd, args, opts) {
130
+ if (isWindows) {
131
+ return execFileSync("cmd.exe", ["/c", cmd, ...args], opts);
132
+ }
133
+ return execFileSync(cmd, args, opts);
134
+ }
135
+
136
+ function run(cmd, args, opts = {}) {
137
+ execCmd(cmd, args, { stdio: "inherit", ...opts });
138
+ }
139
+
140
+ function runSilent(cmd, args, opts = {}) {
141
+ return execCmd(cmd, args, {
142
+ stdio: ["ignore", "pipe", "pipe"],
143
+ ...opts,
144
+ });
145
+ }
146
+
147
+ function runSilentAsync(cmd, args, opts = {}) {
148
+ const actualCmd = isWindows ? "cmd.exe" : cmd;
149
+ const actualArgs = isWindows ? ["/c", cmd, ...args] : args;
150
+ return new Promise((resolve, reject) => {
151
+ execFile(actualCmd, actualArgs, {
152
+ stdio: ["ignore", "pipe", "pipe"],
153
+ ...opts,
154
+ }, (err, stdout, stderr) => {
155
+ if (err) {
156
+ err.stdout = stdout;
157
+ err.stderr = stderr;
158
+ reject(err);
159
+ }
160
+ else resolve(stdout);
161
+ });
162
+ });
163
+ }
164
+
165
+ function fmt(template, ...values) {
166
+ let i = 0;
167
+ return template.replace(/%s/g, () => values[i++] ?? "");
168
+ }
169
+
170
+ function formatExecErr(err) {
171
+ if (!err) return "";
172
+ if (err.stderr) return err.stderr.toString().trim();
173
+ if (err.stdout) return err.stdout.toString().trim();
174
+ return err.message ? err.message.trim() : String(err);
175
+ }
176
+
177
+ function osHomedir() {
178
+ return process.env.HOME ||
179
+ process.env.USERPROFILE ||
180
+ (process.env.HOMEDRIVE && process.env.HOMEPATH && process.env.HOMEDRIVE + process.env.HOMEPATH) ||
181
+ "/root";
182
+ }
183
+
184
+ function isInteractiveEnv() {
185
+ return !!process.stdin.isTTY &&
186
+ !!process.stdout.isTTY &&
187
+ !process.env.CI &&
188
+ !process.env.NO_COLOR &&
189
+ process.env.TERM !== "dumb";
190
+ }
191
+
192
+ function createSpinner() {
193
+ if (isInteractiveEnv()) return p.spinner();
194
+ return {
195
+ start: (msg) => { if (msg) console.log(msg); },
196
+ stop: (msg) => { if (msg) console.log(msg); },
197
+ };
198
+ }
199
+
200
+ function whichDeltaCli() {
201
+ try {
202
+ const prefix = execFileSync("npm", ["prefix", "-g"], {
203
+ stdio: ["ignore", "pipe", "pipe"],
204
+ }).toString().trim();
205
+ const bin = isWindows
206
+ ? path.join(prefix, "delta-cli.cmd")
207
+ : path.join(prefix, "bin", "delta-cli");
208
+ if (fs.existsSync(bin)) return bin;
209
+ } catch {}
210
+ try {
211
+ const cmd = isWindows ? "where" : "which";
212
+ return execFileSync(cmd, ["delta-cli"], { stdio: ["ignore", "pipe", "pipe"] })
213
+ .toString().split("\n")[0].trim();
214
+ } catch {
215
+ return null;
216
+ }
217
+ }
218
+
219
+ function getLatestVersion() {
220
+ try {
221
+ const out = runSilent("npm", ["view", PKG, "version"], { timeout: 15000 });
222
+ const ver = out.toString().trim();
223
+ return /^\d+\.\d+\.\d+/.test(ver) ? ver : null;
224
+ } catch { return null; }
225
+ }
226
+
227
+ function semverLessThan(a, b) {
228
+ const pa = a.replace(/-.*$/, "").split(".").map(Number);
229
+ const pb = b.replace(/-.*$/, "").split(".").map(Number);
230
+ for (let i = 0; i < 3; i++) {
231
+ if ((pa[i] || 0) < (pb[i] || 0)) return true;
232
+ if ((pa[i] || 0) > (pb[i] || 0)) return false;
233
+ }
234
+ return false;
235
+ }
236
+
237
+ function getGloballyInstalledVersion() {
238
+ try {
239
+ const out = runSilent("npm", ["list", "-g", PKG], { timeout: 15000 });
240
+ const match = out.toString().match(/@(\d+\.\d+\.\d+[^\s]*)/);
241
+ return match ? match[1] : "unknown";
242
+ } catch { return null; }
243
+ }
244
+
245
+ function getExistingConfig() {
246
+ try {
247
+ const configPath = path.join(CONFIG_DIR, "config.json");
248
+ if (!fs.existsSync(configPath)) return null;
249
+ return JSON.parse(fs.readFileSync(configPath, "utf8"));
250
+ } catch { return null; }
251
+ }
252
+
253
+ function writeConfig({ baseUrl, scienceUrl, existing = null } = {}) {
254
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
255
+ const config = {
256
+ version: CONFIG_VERSION,
257
+ base_url: baseUrl || DEFAULT_BASE_URL,
258
+ science_base_url: scienceUrl || existing?.science_base_url || DEFAULT_SCIENCE_BASE_URL,
259
+ };
260
+ if (existing && existing.token) {
261
+ config.token = existing.token;
262
+ }
263
+ fs.writeFileSync(path.join(CONFIG_DIR, "config.json"), JSON.stringify(config, null, 2) + "\n", { mode: 0o600 });
264
+ }
265
+
266
+ // ── Steps ───────────────────────────────────────────────────────────────────
267
+
268
+ async function stepInstallGlobally(msg) {
269
+ const installedVer = getGloballyInstalledVersion();
270
+ const latestVer = getLatestVersion();
271
+ const needsUpgrade = installedVer && latestVer && semverLessThan(installedVer, latestVer);
272
+
273
+ if (installedVer && !needsUpgrade) {
274
+ p.log.info(fmt(msg.step1Skip, installedVer));
275
+ return false;
276
+ }
277
+
278
+ const targetVer = latestVer || installedVer;
279
+ const s = createSpinner();
280
+ if (needsUpgrade) {
281
+ s.start(fmt(msg.upgrade, PKG, installedVer, latestVer));
282
+ } else {
283
+ s.start(fmt(msg.step1, PKG));
284
+ }
285
+ let lastInstallErr = null;
286
+ try {
287
+ let installed = false;
288
+ for (const registry of NPM_REGISTRIES) {
289
+ try {
290
+ await runSilentAsync("npm", ["install", "-g", PKG, "--registry", registry], {
291
+ timeout: 300000,
292
+ env: { ...process.env, DELTA_CLI_MIRROR: GH_MIRRORS[0] },
293
+ });
294
+ installed = true;
295
+ break;
296
+ } catch (e) {
297
+ lastInstallErr = e;
298
+ const reason = formatExecErr(e).slice(0, 400);
299
+ if (reason) p.log.error(`npm registry ${registry} 安装失败: ${reason}`);
300
+ }
301
+ }
302
+ if (!installed) throw lastInstallErr || new Error("all npm registries failed");
303
+
304
+ if (targetVer) {
305
+ for (const mirror of GH_MIRRORS) {
306
+ try {
307
+ await runSilentAsync(process.execPath, [
308
+ path.join(__dirname, "install.js"),
309
+ ], {
310
+ timeout: 300000,
311
+ env: { ...process.env, DELTA_CLI_RUN: "true", DELTA_CLI_VERSION: targetVer, DELTA_CLI_MIRROR: mirror },
312
+ });
313
+ break;
314
+ } catch { }
315
+ }
316
+ }
317
+ s.stop(needsUpgrade ? fmt(msg.step1Upgraded, latestVer) : msg.step1Done);
318
+ return needsUpgrade;
319
+ } catch (e) {
320
+ s.stop(fmt(msg.step1Fail, PKG));
321
+ const reason = formatExecErr(e).slice(0, 1200);
322
+ if (reason) p.log.error(`失败原因:\n${reason}`);
323
+ process.exit(1);
324
+ }
325
+ }
326
+
327
+ function findPackageSkillsDir() {
328
+ const candidates = [];
329
+ try {
330
+ const npmRoot = runSilent("npm", ["root", "-g"], { timeout: 10000 })
331
+ .toString().trim();
332
+ candidates.push(path.join(npmRoot, PKG, "skills"));
333
+ } catch {}
334
+ candidates.push(path.join(__dirname, "..", "skills"));
335
+
336
+ for (const c of candidates) {
337
+ if (fs.existsSync(path.join(c, "delta-sandbox", "SKILL.md"))) {
338
+ return c;
339
+ }
340
+ }
341
+ return null;
342
+ }
343
+
344
+ function installSkillsFromLocalPackage(platforms) {
345
+ const pkgSkillsDir = findPackageSkillsDir();
346
+ if (!pkgSkillsDir) return false;
347
+
348
+ for (const platform of platforms) {
349
+ const targetDir = platformSkillDir(platform);
350
+ fs.mkdirSync(targetDir, { recursive: true });
351
+ for (const skill of RETIRED_SKILL_NAMES) {
352
+ try { fs.rmSync(path.join(targetDir, skill), { recursive: true, force: true }); } catch {}
353
+ }
354
+ for (const skill of SKILL_NAMES) {
355
+ const src = path.join(pkgSkillsDir, skill);
356
+ if (!fs.existsSync(src)) continue;
357
+ const dst = path.join(targetDir, skill);
358
+ try { fs.rmSync(dst, { recursive: true, force: true }); } catch {}
359
+ fs.cpSync(src, dst, { recursive: true });
360
+ }
361
+ }
362
+ return true;
363
+ }
364
+
365
+ async function stepSelectPlatforms(msg) {
366
+ if (!isInteractiveEnv()) {
367
+ return ["agents"];
368
+ }
369
+ const labels = PLATFORM_LABELS[LANG];
370
+ const defaultPlatforms = fs.existsSync(path.dirname(platformSkillDir("claude")))
371
+ ? ["agents", "claude", "mementos"]
372
+ : ["agents", "mementos"];
373
+ const selected = await p.multiselect({
374
+ message: msg.step2SelectPlatforms,
375
+ options: Object.keys(PLATFORM_PATHS).map((key) => ({
376
+ value: key,
377
+ label: labels[key],
378
+ })),
379
+ initialValues: defaultPlatforms,
380
+ required: true,
381
+ });
382
+ handleCancel(selected, msg);
383
+ return selected;
384
+ }
385
+
386
+ async function stepInstallSkills(msg, platforms) {
387
+ const s = createSpinner();
388
+ s.start(msg.step2Spinner);
389
+ try {
390
+ try {
391
+ if (installSkillsFromLocalPackage(platforms)) {
392
+ s.stop(msg.step2Done);
393
+ p.log.info(fmt(msg.step2DoneFor, platforms.join(", ")));
394
+ return;
395
+ }
396
+ } catch { }
397
+
398
+ if (!platforms.includes("agents")) {
399
+ throw new Error("local skills package not found and agent dir not selected");
400
+ }
401
+
402
+ const urls = [
403
+ `https://gh.ddlc.top/https://github.com/${SKILLS_REPO}`,
404
+ `https://ghproxy.net/https://github.com/${SKILLS_REPO}`,
405
+ `https://github.com/${SKILLS_REPO}`,
406
+ ];
407
+ let lastErr;
408
+ for (const url of urls) {
409
+ try {
410
+ s.start(`${msg.step2Spinner} (try ${url.split("/")[2]})`);
411
+ await runSilentAsync("npx", ["-y", "skills", "add", url, "-y", "-g"], {
412
+ timeout: 15000,
413
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
414
+ });
415
+ s.stop(msg.step2Done);
416
+ p.log.info(fmt(msg.step2DoneFor, "agents"));
417
+ return;
418
+ } catch (e) {
419
+ lastErr = e;
420
+ if (e && e.stderr) console.error(`[skills mirror failed] ${url}: ${e.stderr.toString().trim().slice(0, 200)}`);
421
+ else if (e && e.message) console.error(`[skills mirror failed] ${url}: ${e.message}`);
422
+ }
423
+ }
424
+ throw lastErr;
425
+ } catch {
426
+ s.stop(fmt(msg.step2Fail, SKILLS_REPO));
427
+ process.exit(1);
428
+ }
429
+ }
430
+
431
+ async function stepConfigInit(msg) {
432
+ const s = createSpinner();
433
+ s.start(msg.step3);
434
+
435
+ const existingConfig = getExistingConfig();
436
+ s.stop(msg.step3);
437
+
438
+ if (existingConfig && existingConfig.base_url) {
439
+ const reuse = await p.confirm({
440
+ message: `发现已有配置 (Server: ${existingConfig.base_url}),继续使用?`,
441
+ });
442
+ if (p.isCancel(reuse)) {
443
+ handleCancel(reuse, msg);
444
+ return;
445
+ }
446
+ if (reuse) {
447
+ p.log.info(msg.step3Skip);
448
+ return;
449
+ }
450
+ }
451
+
452
+ const baseUrl = handleCancel(await p.text({
453
+ message: msg.step3BaseUrl,
454
+ initialValue: existingConfig && existingConfig.base_url
455
+ ? existingConfig.base_url
456
+ : DEFAULT_BASE_URL,
457
+ }), msg);
458
+
459
+ const scienceUrl = handleCancel(await p.text({
460
+ message: msg.step3ScienceUrl,
461
+ initialValue: existingConfig && existingConfig.science_base_url
462
+ ? existingConfig.science_base_url
463
+ : DEFAULT_SCIENCE_BASE_URL,
464
+ }), msg);
465
+
466
+ try {
467
+ writeConfig({ baseUrl, scienceUrl, existing: existingConfig });
468
+ p.log.success(msg.step3Done);
469
+ } catch (e) {
470
+ p.log.error(e.message || String(e));
471
+ p.log.error(msg.step3Fail);
472
+ process.exit(1);
473
+ }
474
+ }
475
+
476
+ async function stepAuthLogin(msg) {
477
+ const deltaCli = whichDeltaCli();
478
+ if (!deltaCli) {
479
+ p.log.warn("delta-cli not found, skipping authorization");
480
+ return;
481
+ }
482
+
483
+ const yes = await p.confirm({
484
+ message: msg.step4Confirm,
485
+ });
486
+ if (p.isCancel(yes)) {
487
+ p.cancel(msg.cancelled);
488
+ process.exit(0);
489
+ }
490
+ if (!yes) {
491
+ p.log.info(msg.step4Skip);
492
+ return;
493
+ }
494
+
495
+ p.log.step(msg.step4);
496
+ try {
497
+ run(deltaCli, ["auth", "login"]);
498
+ p.log.success(msg.step4Done);
499
+ } catch {
500
+ p.log.warn(msg.step4Fail);
501
+ }
502
+ }
503
+
504
+ // ── Uninstall steps ─────────────────────────────────────────────────────────
505
+
506
+ async function stepUninstallSkills(msg) {
507
+ const s = createSpinner();
508
+ s.start("Removing AI skills...");
509
+
510
+ let removedLocally = false;
511
+ for (const platform of Object.keys(PLATFORM_PATHS)) {
512
+ const dir = platformSkillDir(platform);
513
+ for (const skill of [...SKILL_NAMES, ...RETIRED_SKILL_NAMES]) {
514
+ try {
515
+ fs.rmSync(path.join(dir, skill), { recursive: true, force: true });
516
+ removedLocally = true;
517
+ } catch { }
518
+ }
519
+ }
520
+
521
+ if (removedLocally) {
522
+ s.stop("AI skills removed");
523
+ return;
524
+ }
525
+
526
+ try {
527
+ await runSilentAsync(
528
+ "npx",
529
+ ["-y", "skills", "remove", ...SKILL_NAMES, ...RETIRED_SKILL_NAMES, "-g"],
530
+ {
531
+ timeout: 60000,
532
+ },
533
+ );
534
+ s.stop("AI skills removed");
535
+ } catch {
536
+ s.stop("Warning: skill removal failed. You can retry:");
537
+ p.log.info(` npx skills remove ${[...SKILL_NAMES, ...RETIRED_SKILL_NAMES].join(" ")} -g`);
538
+ }
539
+ }
540
+
541
+ function stepRemoveConfig() {
542
+ if (!fs.existsSync(CONFIG_DIR)) {
543
+ return;
544
+ }
545
+ fs.rmSync(CONFIG_DIR, { recursive: true, force: true });
546
+ }
547
+
548
+ async function stepUninstallPackage(msg) {
549
+ const installedVer = getGloballyInstalledVersion();
550
+ if (!installedVer) {
551
+ return;
552
+ }
553
+ const s = createSpinner();
554
+ s.start("Removing global package...");
555
+ try {
556
+ await runSilentAsync("npm", ["uninstall", "-g", PKG], { timeout: 60000 });
557
+ s.stop("Package removed");
558
+ } catch {
559
+ s.stop("Failed to remove global package");
560
+ p.log.info(` npm uninstall -g ${PKG}`);
561
+ }
562
+ }
563
+
564
+ // ── Main ────────────────────────────────────────────────────────────────────
565
+
566
+ async function doInstall() {
567
+ const isInteractive = isInteractiveEnv();
568
+
569
+ if (isInteractive) {
570
+ p.intro(messages.zh.setup);
571
+ await stepInstallGlobally(messages.zh);
572
+ const platforms = await stepSelectPlatforms(messages.zh);
573
+ await stepInstallSkills(messages.zh, platforms);
574
+ await stepConfigInit(messages.zh);
575
+ await stepAuthLogin(messages.zh);
576
+ p.outro(messages.zh.done);
577
+ } else {
578
+ console.log(messages.zh.setup);
579
+ await stepInstallGlobally(messages.zh);
580
+ const platforms = ["agents"];
581
+ await stepInstallSkills(messages.zh, platforms);
582
+ console.log(messages.zh.nonTtyHint);
583
+ }
584
+ }
585
+
586
+ async function doUninstall() {
587
+ const isInteractive = isInteractiveEnv();
588
+
589
+ if (isInteractive) {
590
+ const ok = await p.confirm({
591
+ message: "将移除全局包、AI Skills 和配置文件。是否继续?",
592
+ });
593
+ if (p.isCancel(ok) || !ok) {
594
+ p.cancel("卸载已取消");
595
+ process.exit(0);
596
+ }
597
+ }
598
+
599
+ await stepRemoveConfig();
600
+ await stepUninstallSkills();
601
+ await stepUninstallPackage();
602
+
603
+ if (isInteractive) {
604
+ p.outro("卸载完成");
605
+ } else {
606
+ console.log("\nUninstall complete.");
607
+ }
608
+ }
609
+
610
+ function main() {
611
+ const subcommand = process.argv[2];
612
+ if (subcommand === "uninstall") {
613
+ doUninstall().catch((e) => {
614
+ console.error("[delta-cli] Uninstall failed:", e.message);
615
+ process.exit(1);
616
+ });
617
+ } else {
618
+ doInstall().catch((e) => {
619
+ console.error("[delta-cli] Install failed:", e.message);
620
+ process.exit(1);
621
+ });
622
+ }
623
+ }
624
+
625
+ main();