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