@thieung/agentkit-helper 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +92 -0
- package/README.vi.md +90 -0
- package/assets/tui-preview.en.svg +34 -0
- package/assets/tui-preview.svg +34 -0
- package/bin/agentkit-helper.mjs +1109 -0
- package/lib/args.mjs +258 -0
- package/lib/colors.mjs +21 -0
- package/lib/commands.mjs +106 -0
- package/lib/config.mjs +68 -0
- package/lib/discovery.mjs +206 -0
- package/lib/github-issue.mjs +137 -0
- package/lib/i18n.mjs +255 -0
- package/lib/navigation.mjs +22 -0
- package/lib/project.mjs +37 -0
- package/lib/prompts.mjs +149 -0
- package/lib/runner.mjs +213 -0
- package/lib/self-update.mjs +65 -0
- package/package.json +48 -0
|
@@ -0,0 +1,1109 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFile, realpath } from "node:fs/promises";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { dirname, resolve } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import {
|
|
8
|
+
EXPORT_TARGETS,
|
|
9
|
+
INSTALL_TARGETS,
|
|
10
|
+
KITS,
|
|
11
|
+
parseArgs,
|
|
12
|
+
splitTargetSpec,
|
|
13
|
+
targetSpecIsSupported,
|
|
14
|
+
UPDATE_TARGETS,
|
|
15
|
+
validateForCommand,
|
|
16
|
+
} from "../lib/args.mjs";
|
|
17
|
+
import {
|
|
18
|
+
exportArgs,
|
|
19
|
+
formatCommand,
|
|
20
|
+
globalUpdateApplyArgs,
|
|
21
|
+
globalUpdatePreviewArgs,
|
|
22
|
+
installArgs,
|
|
23
|
+
projectUpdateApplyArgs,
|
|
24
|
+
projectUpdatePreviewArgs,
|
|
25
|
+
selfUpdateCheckArgs,
|
|
26
|
+
selfUpdateApplyArgs,
|
|
27
|
+
selfUpdateJsonApplyArgs,
|
|
28
|
+
selfUpdateJsonCheckArgs,
|
|
29
|
+
updateApplyArgs,
|
|
30
|
+
updatePreviewArgs,
|
|
31
|
+
} from "../lib/commands.mjs";
|
|
32
|
+
import { readProjectConfig, writeProjectConfig } from "../lib/config.mjs";
|
|
33
|
+
import { colorText } from "../lib/colors.mjs";
|
|
34
|
+
import {
|
|
35
|
+
discoverGlobalKitInstalls,
|
|
36
|
+
discoverProjectCandidates,
|
|
37
|
+
} from "../lib/discovery.mjs";
|
|
38
|
+
import {
|
|
39
|
+
buildIssueReport,
|
|
40
|
+
checkIssueRepository,
|
|
41
|
+
createGitHubIssue,
|
|
42
|
+
findDuplicateIssue,
|
|
43
|
+
isReportableAkError,
|
|
44
|
+
resolveIssueRepository,
|
|
45
|
+
} from "../lib/github-issue.mjs";
|
|
46
|
+
import { t } from "../lib/i18n.mjs";
|
|
47
|
+
import { isUnsafeProjectPath, resolveProjectPath } from "../lib/project.mjs";
|
|
48
|
+
import { BACK, walkSelections } from "../lib/navigation.mjs";
|
|
49
|
+
import {
|
|
50
|
+
ask,
|
|
51
|
+
askDirectory,
|
|
52
|
+
choose,
|
|
53
|
+
chooseWithBack,
|
|
54
|
+
confirm,
|
|
55
|
+
finishInteractive,
|
|
56
|
+
multiChoose,
|
|
57
|
+
multiChooseWithBack,
|
|
58
|
+
PromptCancelledError,
|
|
59
|
+
setPromptCopy,
|
|
60
|
+
warning,
|
|
61
|
+
withSpinner,
|
|
62
|
+
} from "../lib/prompts.mjs";
|
|
63
|
+
import {
|
|
64
|
+
ensureAk,
|
|
65
|
+
requiresForceConsent,
|
|
66
|
+
run,
|
|
67
|
+
runCapture,
|
|
68
|
+
runPipeline,
|
|
69
|
+
} from "../lib/runner.mjs";
|
|
70
|
+
import {
|
|
71
|
+
assertInstallerVersion,
|
|
72
|
+
classifySelfUpdate,
|
|
73
|
+
parseSelfUpdateOutput,
|
|
74
|
+
releaseChannelForVersion,
|
|
75
|
+
} from "../lib/self-update.mjs";
|
|
76
|
+
|
|
77
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
78
|
+
const metadata = JSON.parse(await readFile(resolve(packageRoot, "package.json"), "utf8"));
|
|
79
|
+
const akBinary = process.env.AK_HELPER_AK_BIN || "ak";
|
|
80
|
+
const ghBinary = process.env.AK_HELPER_GH_BIN || "gh";
|
|
81
|
+
const installerUrl = "https://agentkit.best/install.sh";
|
|
82
|
+
const windowsInstallerUrl = "https://agentkit.best/install.ps1";
|
|
83
|
+
let activeLanguage = "en";
|
|
84
|
+
let activeAction = null;
|
|
85
|
+
let installedAkVersion = "";
|
|
86
|
+
|
|
87
|
+
function ui(key, values) {
|
|
88
|
+
return t(activeLanguage, key, values);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function showCurrentBinary() {
|
|
92
|
+
const version = installedAkVersion.replace(/^ak\s+/i, "");
|
|
93
|
+
process.stdout.write(`${colorText(`│ ${ui("currentBinary", {
|
|
94
|
+
version,
|
|
95
|
+
channel: releaseChannelForVersion(installedAkVersion) || "stable",
|
|
96
|
+
})}`, "binary")}\n`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function usage(language = "en") {
|
|
100
|
+
if (language === "vi") {
|
|
101
|
+
process.stdout.write(`AgentKit Helper ${metadata.version}
|
|
102
|
+
|
|
103
|
+
Cách dùng:
|
|
104
|
+
agentkit-helper install [tùy chọn]
|
|
105
|
+
agentkit-helper update [tùy chọn]
|
|
106
|
+
agentkit-helper self-update [tùy chọn]
|
|
107
|
+
agentkit-helper update-all [tùy chọn]
|
|
108
|
+
agentkit-helper export [tùy chọn]
|
|
109
|
+
agentkit-helper doctor [--project <đường-dẫn>]
|
|
110
|
+
|
|
111
|
+
Tùy chọn:
|
|
112
|
+
--project <đường-dẫn> Dùng project scope tại đường dẫn này
|
|
113
|
+
--global Dùng scope user/global của runtime
|
|
114
|
+
--target <targets> Các runtime phân cách dấu phẩy cho install/update, hoặc một export target
|
|
115
|
+
--runtime <runtimes> Alias của --target nhưng chỉ cho runtime install/update
|
|
116
|
+
--kit <kit> engineer hoặc marketing
|
|
117
|
+
--channel <channel> stable hoặc beta
|
|
118
|
+
--language <vi|en> Ngôn ngữ giao diện helper
|
|
119
|
+
--out <đường-dẫn> Thư mục output cho portable export
|
|
120
|
+
--binary-only Chỉ cập nhật binary ak
|
|
121
|
+
--allow-downgrade Cho phép downgrade khi dùng cùng --yes
|
|
122
|
+
--deep-scan <thư-mục> Deep scan thư mục cha (có thể lặp lại; chỉ update-all)
|
|
123
|
+
--max-depth <1-20> Độ sâu deep scan, mặc định 5
|
|
124
|
+
--exclude <tên,...> Bỏ qua thêm các tên thư mục khi deep scan
|
|
125
|
+
--dry-run Lập kế hoạch/xem trước, không thay đổi
|
|
126
|
+
--yes, -y Bỏ qua bước xác nhận của helper
|
|
127
|
+
--no-save Không ghi .ak-kit.json
|
|
128
|
+
--help, -h Hiện trợ giúp
|
|
129
|
+
--version, -v Hiện phiên bản
|
|
130
|
+
|
|
131
|
+
Target groups:
|
|
132
|
+
Install claude-code, codex, cursor, dsh, grok, omp, pi
|
|
133
|
+
Update claude-code, codex, cursor, grok, omp, pi (dsh: unsupported)
|
|
134
|
+
Export agy, portable
|
|
135
|
+
`);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
process.stdout.write(`AgentKit Helper ${metadata.version}
|
|
139
|
+
|
|
140
|
+
Usage:
|
|
141
|
+
agentkit-helper install [options]
|
|
142
|
+
agentkit-helper update [options]
|
|
143
|
+
agentkit-helper self-update [options]
|
|
144
|
+
agentkit-helper update-all [options]
|
|
145
|
+
agentkit-helper export [options]
|
|
146
|
+
agentkit-helper doctor [--project <path>]
|
|
147
|
+
|
|
148
|
+
Options:
|
|
149
|
+
--project <path> Use project scope at this path
|
|
150
|
+
--global Use the runtime user/global scope
|
|
151
|
+
--target <targets> Comma-separated runtimes for install/update, or one export target
|
|
152
|
+
--runtime <runtimes> Alias of --target for install/update runtimes only
|
|
153
|
+
--kit <kit> engineer or marketing
|
|
154
|
+
--channel <channel> stable or beta
|
|
155
|
+
--language <vi|en> Helper interface language
|
|
156
|
+
--out <path> Output directory for portable export
|
|
157
|
+
--binary-only Update only the ak binary
|
|
158
|
+
--allow-downgrade Permit downgrade when also used with --yes
|
|
159
|
+
--deep-scan <path> Deep scan a parent path (repeatable; update-all only)
|
|
160
|
+
--max-depth <1-20> Deep scan depth, default 5
|
|
161
|
+
--exclude <name,...> Additional directory names to skip during deep scan
|
|
162
|
+
--dry-run Plan or preview without mutation
|
|
163
|
+
--yes, -y Skip helper confirmation
|
|
164
|
+
--no-save Do not write .ak-kit.json
|
|
165
|
+
--help, -h Show help
|
|
166
|
+
--version, -v Show version
|
|
167
|
+
|
|
168
|
+
Target groups:
|
|
169
|
+
Install claude-code, codex, cursor, dsh, grok, omp, pi
|
|
170
|
+
Update claude-code, codex, cursor, grok, omp, pi (dsh: unsupported)
|
|
171
|
+
Export agy, portable
|
|
172
|
+
`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function printPlan(args, cwd) {
|
|
176
|
+
const suffix = cwd && cwd !== process.cwd() ? ` (cwd: ${cwd})` : "";
|
|
177
|
+
process.stdout.write(`${colorText(` ${formatCommand(akBinary, args)}${suffix}`, "command")}\n`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function printSection(message) {
|
|
181
|
+
process.stdout.write(`\n${colorText(message, "section")}\n`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function runAkCommand(args, { cwd = process.cwd() } = {}) {
|
|
185
|
+
const result = await withSpinner(
|
|
186
|
+
ui("runningAkCommand"),
|
|
187
|
+
ui("akCommandComplete"),
|
|
188
|
+
() => runCapture(akBinary, args, { cwd }),
|
|
189
|
+
);
|
|
190
|
+
if (result.stdout) process.stdout.write(`${result.stdout}\n`);
|
|
191
|
+
if (result.stderr) process.stderr.write(`${result.stderr}\n`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function chooseCommand(allowLanguageBack = false) {
|
|
195
|
+
const choices = [
|
|
196
|
+
{ label: ui("installAction"), value: "install" },
|
|
197
|
+
{ label: ui("updateAction"), value: "update" },
|
|
198
|
+
{ label: ui("selfUpdateAction"), value: "self-update" },
|
|
199
|
+
{ label: ui("updateAllAction"), value: "update-all" },
|
|
200
|
+
{ label: ui("exportAction"), value: "export" },
|
|
201
|
+
{ label: ui("doctorAction"), value: "doctor" },
|
|
202
|
+
];
|
|
203
|
+
if (allowLanguageBack) {
|
|
204
|
+
choices.push({ label: ui("backToLanguage"), value: BACK });
|
|
205
|
+
}
|
|
206
|
+
return choose(ui("commandPrompt"), choices);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function kitName(kit) {
|
|
210
|
+
return ui(kit === "marketing" ? "marketingKit" : "engineerKit");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function selectKit(options, config, allowBack = false, forcePrompt = false) {
|
|
214
|
+
if (options.kit) return options.kit;
|
|
215
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) return config?.kit || "engineer";
|
|
216
|
+
if (config?.kit && !forcePrompt) return config.kit;
|
|
217
|
+
return chooseLocalized(allowBack, ui("kitPrompt"), [...KITS].map((kit) => ({
|
|
218
|
+
label: kitName(kit),
|
|
219
|
+
value: kit,
|
|
220
|
+
})));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function selectLanguage(options, forcePrompt = false) {
|
|
224
|
+
if (options.language && !forcePrompt) return options.language;
|
|
225
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) return "en";
|
|
226
|
+
return choose(t("en", "languagePrompt"), [
|
|
227
|
+
{ label: "Tiếng Việt", value: "vi" },
|
|
228
|
+
{ label: "English", value: "en" },
|
|
229
|
+
]);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function chooseLocalized(allowBack, message, choices, defaultIndex = 0) {
|
|
233
|
+
return allowBack
|
|
234
|
+
? chooseWithBack(message, choices, defaultIndex, ui("back"))
|
|
235
|
+
: choose(message, choices, defaultIndex);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function supportedList(targets) {
|
|
239
|
+
return [...targets].join(", ");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function needsScopePrompt(options) {
|
|
243
|
+
return !options.binaryOnly && !options.global && !options.project && (
|
|
244
|
+
(process.stdin.isTTY && process.stdout.isTTY) || isUnsafeProjectPath(process.cwd())
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function selectScope(command, options, allowBack = false) {
|
|
249
|
+
if (options.binaryOnly) {
|
|
250
|
+
return { binaryOnly: true, global: false, project: null };
|
|
251
|
+
}
|
|
252
|
+
if (options.global) {
|
|
253
|
+
return { binaryOnly: false, global: true, project: null };
|
|
254
|
+
}
|
|
255
|
+
if (options.project) {
|
|
256
|
+
return {
|
|
257
|
+
binaryOnly: false,
|
|
258
|
+
global: false,
|
|
259
|
+
project: await resolveProjectPath(options.project),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
const currentProjectIsSafe = !isUnsafeProjectPath(process.cwd());
|
|
263
|
+
if (currentProjectIsSafe && (!process.stdin.isTTY || !process.stdout.isTTY)) {
|
|
264
|
+
return {
|
|
265
|
+
binaryOnly: false,
|
|
266
|
+
global: false,
|
|
267
|
+
project: await resolveProjectPath(null),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const choices = [
|
|
272
|
+
...(currentProjectIsSafe ? [{
|
|
273
|
+
label: ui("currentProjectScope", { path: process.cwd() }),
|
|
274
|
+
value: "current-project",
|
|
275
|
+
}] : []),
|
|
276
|
+
{ label: ui("chooseProject"), value: "project" },
|
|
277
|
+
{ label: ui("globalScope"), value: "global" },
|
|
278
|
+
];
|
|
279
|
+
if (command === "update") {
|
|
280
|
+
choices.unshift({ label: ui("binaryScope"), value: "binary" });
|
|
281
|
+
}
|
|
282
|
+
const scope = await chooseLocalized(
|
|
283
|
+
allowBack,
|
|
284
|
+
currentProjectIsSafe ? ui("scopePrompt") : ui("unsafeCwd", { cwd: process.cwd() }),
|
|
285
|
+
choices,
|
|
286
|
+
);
|
|
287
|
+
if (scope === BACK) return BACK;
|
|
288
|
+
if (scope === "current-project") {
|
|
289
|
+
return {
|
|
290
|
+
binaryOnly: false,
|
|
291
|
+
global: false,
|
|
292
|
+
project: await resolveProjectPath(null),
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
if (scope === "binary") {
|
|
296
|
+
return { binaryOnly: true, global: false, project: null };
|
|
297
|
+
}
|
|
298
|
+
if (scope === "global") {
|
|
299
|
+
return { binaryOnly: false, global: true, project: null };
|
|
300
|
+
}
|
|
301
|
+
return {
|
|
302
|
+
binaryOnly: false,
|
|
303
|
+
global: false,
|
|
304
|
+
project: await resolveProjectPath(await askDirectory(ui("projectDirectory"), { root: homedir() })),
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function selectTarget(
|
|
309
|
+
options,
|
|
310
|
+
config,
|
|
311
|
+
targets,
|
|
312
|
+
promptKey,
|
|
313
|
+
allowBack = false,
|
|
314
|
+
forcePrompt = false,
|
|
315
|
+
promptValues = {},
|
|
316
|
+
) {
|
|
317
|
+
if (options.target) return options.target;
|
|
318
|
+
if (options.runtime) return options.runtime;
|
|
319
|
+
if (config?.target && targetSpecIsSupported(config.target, targets) && !forcePrompt) {
|
|
320
|
+
return config.target;
|
|
321
|
+
}
|
|
322
|
+
const allRuntimeValue = "__all_runtimes__";
|
|
323
|
+
const choices = [
|
|
324
|
+
{ label: ui("allRuntimes"), value: allRuntimeValue },
|
|
325
|
+
...[...targets].map((target) => ({ label: target, value: target })),
|
|
326
|
+
];
|
|
327
|
+
if (promptKey === "updateTargetPrompt") {
|
|
328
|
+
choices.push({
|
|
329
|
+
label: ui("dshUpdateUnsupported"),
|
|
330
|
+
value: "dsh",
|
|
331
|
+
disabled: true,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
const configuredTargets = splitTargetSpec(config?.target).filter((target) => targets.has(target));
|
|
335
|
+
const initialValues = configuredTargets.length > 0 ? configuredTargets : ["codex"];
|
|
336
|
+
const selected = await (allowBack ? multiChooseWithBack : multiChoose)(
|
|
337
|
+
ui(promptKey, promptValues),
|
|
338
|
+
choices,
|
|
339
|
+
initialValues,
|
|
340
|
+
...(allowBack ? [ui("backFromTargetSelection")] : []),
|
|
341
|
+
);
|
|
342
|
+
if (selected === BACK) return BACK;
|
|
343
|
+
if (selected.includes(allRuntimeValue)) return [...targets].join(",");
|
|
344
|
+
return [...targets].filter((target) => selected.includes(target)).join(",");
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
async function selectChannel(options, config, allowBack = false, forcePrompt = false) {
|
|
348
|
+
if (options.channel) return options.channel;
|
|
349
|
+
if (config?.channel && !forcePrompt) return config.channel;
|
|
350
|
+
const choices = [
|
|
351
|
+
{ label: "stable", value: "stable" },
|
|
352
|
+
{ label: "beta", value: "beta" },
|
|
353
|
+
];
|
|
354
|
+
const defaultChannel = releaseChannelForVersion(installedAkVersion) || config?.channel || "stable";
|
|
355
|
+
const defaultIndex = defaultChannel === "beta" ? 1 : 0;
|
|
356
|
+
return chooseLocalized(allowBack, ui("channelPrompt"), choices, defaultIndex);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async function approve(options, message) {
|
|
360
|
+
if (options.yes) return true;
|
|
361
|
+
return confirm(message);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function warnBetaLifecycle(channel) {
|
|
365
|
+
if (channel !== "beta") return;
|
|
366
|
+
const version = installedAkVersion.replace(/^ak\s+/i, "");
|
|
367
|
+
if (!version || version.includes("-")) return;
|
|
368
|
+
warning(`${ui("betaKitStableBinaryWarning", { version })}\n${ui("betaKitLifecycleReason")}`);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
async function prepareBetaBinary(commandOptions, { requiresPreview = false } = {}) {
|
|
372
|
+
const currentVersion = installedAkVersion.replace(/^ak\s+/i, "");
|
|
373
|
+
if (!currentVersion || currentVersion.includes("-")) {
|
|
374
|
+
return { proceed: true, updated: false };
|
|
375
|
+
}
|
|
376
|
+
warnBetaLifecycle("beta");
|
|
377
|
+
const check = parseSelfUpdateOutput((await runCapture(
|
|
378
|
+
akBinary, selfUpdateJsonCheckArgs("beta"),
|
|
379
|
+
)).stdout);
|
|
380
|
+
if (classifySelfUpdate(check) !== "update") {
|
|
381
|
+
return { proceed: true, updated: false };
|
|
382
|
+
}
|
|
383
|
+
warning(ui("binaryPreviewPrerequisite", {
|
|
384
|
+
channel: "beta",
|
|
385
|
+
version: check.latest_version,
|
|
386
|
+
}));
|
|
387
|
+
printSection(ui("binaryPrerequisitePlan"));
|
|
388
|
+
printPlan(selfUpdateApplyArgs("beta"));
|
|
389
|
+
if (commandOptions.dryRun) {
|
|
390
|
+
if (requiresPreview) {
|
|
391
|
+
process.stdout.write(`\n${ui("binaryPrerequisiteDryRun")}\n`);
|
|
392
|
+
return { proceed: false, updated: false };
|
|
393
|
+
}
|
|
394
|
+
return { proceed: true, updated: false };
|
|
395
|
+
}
|
|
396
|
+
if (!(await approve(commandOptions, ui("applyBinaryPrerequisite")))) {
|
|
397
|
+
process.stdout.write(`${ui("binaryPrerequisiteDeclined")}\n`);
|
|
398
|
+
return { proceed: false, updated: false };
|
|
399
|
+
}
|
|
400
|
+
const applied = parseSelfUpdateOutput((await runCapture(
|
|
401
|
+
akBinary, selfUpdateJsonApplyArgs("beta"),
|
|
402
|
+
)).stdout);
|
|
403
|
+
if (!applied.applied) throw new Error(ui("binaryNoChange"));
|
|
404
|
+
installedAkVersion = (await runCapture(akBinary, ["--version"])).stdout.trim();
|
|
405
|
+
const verifiedVersion = installedAkVersion.replace(/^ak\s+/i, "");
|
|
406
|
+
if (verifiedVersion !== applied.latest_version && verifiedVersion !== `v${applied.latest_version}`) {
|
|
407
|
+
throw new Error(`beta binary verification failed: expected ${applied.latest_version}, got ${verifiedVersion}`);
|
|
408
|
+
}
|
|
409
|
+
process.stdout.write(`${ui("binaryPrerequisiteComplete", {
|
|
410
|
+
version: verifiedVersion,
|
|
411
|
+
})}\n`);
|
|
412
|
+
return { proceed: true, updated: true };
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async function install(commandOptions, allowBack = false) {
|
|
416
|
+
let scope;
|
|
417
|
+
let route;
|
|
418
|
+
while (true) {
|
|
419
|
+
const scopeWasPrompted = needsScopePrompt(commandOptions);
|
|
420
|
+
const routeCanGoBack = allowBack || scopeWasPrompted;
|
|
421
|
+
scope = await selectScope("install", commandOptions, allowBack);
|
|
422
|
+
if (scope === BACK) return BACK;
|
|
423
|
+
const config = scope.project ? await readProjectConfig(scope.project) : null;
|
|
424
|
+
route = await walkSelections([
|
|
425
|
+
{ key: "kit", select: () => selectKit(commandOptions, config, routeCanGoBack, allowBack) },
|
|
426
|
+
{
|
|
427
|
+
key: "target",
|
|
428
|
+
select: (values) => selectTarget(
|
|
429
|
+
commandOptions, config, INSTALL_TARGETS, "targetPrompt", routeCanGoBack, allowBack,
|
|
430
|
+
{ kit: kitName(values.kit) },
|
|
431
|
+
),
|
|
432
|
+
},
|
|
433
|
+
{ key: "channel", select: () => selectChannel(commandOptions, config, routeCanGoBack, allowBack) },
|
|
434
|
+
]);
|
|
435
|
+
if (route !== BACK) break;
|
|
436
|
+
if (!scopeWasPrompted) return BACK;
|
|
437
|
+
}
|
|
438
|
+
const selection = { ...scope, ...route };
|
|
439
|
+
const installSelections = splitTargetSpec(selection.target)
|
|
440
|
+
.map((target) => ({ ...selection, target }));
|
|
441
|
+
|
|
442
|
+
printSection(ui("installPlan"));
|
|
443
|
+
for (const targetSelection of installSelections) {
|
|
444
|
+
printPlan(installArgs(targetSelection), targetSelection.project);
|
|
445
|
+
}
|
|
446
|
+
if (commandOptions.dryRun) {
|
|
447
|
+
process.stdout.write(`\n${ui("dryRunFiles")}\n`);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
if (selection.channel === "beta") {
|
|
451
|
+
const betaBinary = await prepareBetaBinary(commandOptions);
|
|
452
|
+
if (!betaBinary.proceed) return;
|
|
453
|
+
}
|
|
454
|
+
if (!(await approve(commandOptions, ui("runAk")))) {
|
|
455
|
+
process.stdout.write(`${ui("cancelledFiles")}\n`);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
for (const targetSelection of installSelections) {
|
|
460
|
+
const cwd = targetSelection.project || process.cwd();
|
|
461
|
+
try {
|
|
462
|
+
await runAkCommand(installArgs(targetSelection), { cwd });
|
|
463
|
+
} catch (error) {
|
|
464
|
+
if (!requiresForceConsent(error)) throw error;
|
|
465
|
+
warning(ui(targetSelection.global ? "globalForceWarning" : "projectForceWarning", {
|
|
466
|
+
target: targetSelection.target,
|
|
467
|
+
}));
|
|
468
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
469
|
+
error.message = `${error.message}\n${ui("forceNeedsConsent")}`;
|
|
470
|
+
throw error;
|
|
471
|
+
}
|
|
472
|
+
if (!(await confirm(ui("confirmForceInstall"), false))) {
|
|
473
|
+
process.stdout.write(`${ui("forceInstallDeclined")}\n`);
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
const forceArgs = installArgs(targetSelection, { force: true });
|
|
477
|
+
printSection(ui("forceInstallPlan"));
|
|
478
|
+
printPlan(forceArgs, targetSelection.project);
|
|
479
|
+
await runAkCommand(forceArgs, { cwd });
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
if (selection.project && !commandOptions.noSave) {
|
|
483
|
+
const path = await writeProjectConfig(selection.project, selection);
|
|
484
|
+
process.stdout.write(`${ui("savedChoice", { path })}\n`);
|
|
485
|
+
}
|
|
486
|
+
if (selection.project) {
|
|
487
|
+
try {
|
|
488
|
+
await run(akBinary, ["projects", "add", selection.project, "--yes"]);
|
|
489
|
+
} catch (error) {
|
|
490
|
+
process.stderr.write(`${ui("projectRegistrationFailed", { message: error.message })}\n`);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
process.stdout.write(`${ui("installComplete")}\n`);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
async function selfUpdate(commandOptions, allowBack = false) {
|
|
497
|
+
const channel = await selectChannel(commandOptions, null, allowBack);
|
|
498
|
+
if (channel === BACK) return BACK;
|
|
499
|
+
printSection(ui("binaryCheck"));
|
|
500
|
+
const check = parseSelfUpdateOutput((await runCapture(
|
|
501
|
+
akBinary, selfUpdateJsonCheckArgs(channel),
|
|
502
|
+
)).stdout);
|
|
503
|
+
process.stdout.write(`${ui("binaryVersions", {
|
|
504
|
+
current: check.current_version,
|
|
505
|
+
latest: check.latest_version,
|
|
506
|
+
channel: check.channel || channel,
|
|
507
|
+
})}\n`);
|
|
508
|
+
const classification = classifySelfUpdate(check);
|
|
509
|
+
if (classification === "current") {
|
|
510
|
+
process.stdout.write(`${ui("binaryAlreadyCurrent")}\n`);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
if (classification === "downgrade") {
|
|
514
|
+
await downgradeBinary(commandOptions, check);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
if (classification !== "update") {
|
|
518
|
+
throw new Error(check.message || `ak self-update status: ${check.status}`);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const applyArgs = selfUpdateJsonApplyArgs(channel);
|
|
522
|
+
printSection(ui("binaryPlan"));
|
|
523
|
+
printPlan(selfUpdateApplyArgs(channel));
|
|
524
|
+
if (commandOptions.dryRun) {
|
|
525
|
+
process.stdout.write(`\n${ui("dryRunBinary")}\n`);
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
if (!(await approve(commandOptions, ui("applyBinary")))) {
|
|
529
|
+
process.stdout.write(`${ui("cancelledBinary")}\n`);
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
const applied = parseSelfUpdateOutput((await runCapture(akBinary, applyArgs)).stdout);
|
|
533
|
+
process.stdout.write(`${applied.applied ? ui("binaryComplete") : ui("binaryNoChange")}\n`);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function printDowngradePlan(channel, version, installDir) {
|
|
537
|
+
if (process.platform === "win32") {
|
|
538
|
+
process.stdout.write(` $env:AK_CHANNEL='${channel}'; $env:AK_VERSION='${version}'; irm ${windowsInstallerUrl} | iex\n`);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
process.stdout.write(` curl -fsSL ${installerUrl} | ${formatCommand("env", [
|
|
542
|
+
`AK_CHANNEL=${channel}`,
|
|
543
|
+
`AK_VERSION=${version}`,
|
|
544
|
+
`AK_INSTALL_DIR=${installDir}`,
|
|
545
|
+
"sh",
|
|
546
|
+
])}\n`);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
async function resolveAkInstallDir() {
|
|
550
|
+
if (process.platform === "win32") return null;
|
|
551
|
+
const located = akBinary.includes("/")
|
|
552
|
+
? resolve(akBinary)
|
|
553
|
+
: (await runCapture("which", [akBinary])).stdout.split("\n")[0];
|
|
554
|
+
return dirname(await realpath(located));
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
async function applyOfficialInstaller(channel, version, installDir) {
|
|
558
|
+
if (process.platform === "win32") {
|
|
559
|
+
const script = `$env:AK_CHANNEL='${channel}'; $env:AK_VERSION='${version}'; irm ${windowsInstallerUrl} | iex`;
|
|
560
|
+
await run("powershell.exe", ["-NoProfile", "-Command", script]);
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
await runPipeline(
|
|
564
|
+
process.env.AK_HELPER_CURL_BIN || "curl",
|
|
565
|
+
["-fsSL", installerUrl],
|
|
566
|
+
process.env.AK_HELPER_SH_BIN || "sh",
|
|
567
|
+
[],
|
|
568
|
+
{ targetEnv: { AK_CHANNEL: channel, AK_VERSION: version, AK_INSTALL_DIR: installDir } },
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
async function downgradeBinary(commandOptions, check) {
|
|
573
|
+
const version = assertInstallerVersion(check.latest_version);
|
|
574
|
+
const channel = check.channel || commandOptions.channel;
|
|
575
|
+
const installDir = await resolveAkInstallDir();
|
|
576
|
+
process.stderr.write(`${ui("binaryDowngradeWarning", {
|
|
577
|
+
channel,
|
|
578
|
+
latest: version,
|
|
579
|
+
current: check.current_version,
|
|
580
|
+
})}\n`);
|
|
581
|
+
process.stderr.write(`${ui("binaryDowngradeRisk")}\n`);
|
|
582
|
+
printSection(ui("downgradePlan"));
|
|
583
|
+
printDowngradePlan(channel, version, installDir);
|
|
584
|
+
if (commandOptions.dryRun) {
|
|
585
|
+
process.stdout.write(`\n${ui("dryRunBinary")}\n`);
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
let approved = false;
|
|
590
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
591
|
+
approved = commandOptions.allowDowngrade && commandOptions.yes
|
|
592
|
+
? true
|
|
593
|
+
: await confirm(ui("confirmDowngrade", {
|
|
594
|
+
current: check.current_version,
|
|
595
|
+
latest: version,
|
|
596
|
+
}), false);
|
|
597
|
+
} else if (commandOptions.allowDowngrade && commandOptions.yes) {
|
|
598
|
+
approved = true;
|
|
599
|
+
} else {
|
|
600
|
+
throw new Error(ui("downgradeNeedsConsent"));
|
|
601
|
+
}
|
|
602
|
+
if (!approved) {
|
|
603
|
+
process.stdout.write(`${ui("cancelledDowngrade")}\n`);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
await applyOfficialInstaller(channel, version, installDir);
|
|
608
|
+
const verified = (await runCapture(akBinary, ["--version"])).stdout;
|
|
609
|
+
const installedVersion = verified.trim().replace(/^ak\s+/, "");
|
|
610
|
+
if (installedVersion !== version && installedVersion !== `v${version}`) {
|
|
611
|
+
throw new Error(`downgrade verification failed: expected ${version}`);
|
|
612
|
+
}
|
|
613
|
+
process.stdout.write(`${ui("downgradeComplete", { version })}\n`);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
async function update(commandOptions, allowBack = false) {
|
|
617
|
+
let scope;
|
|
618
|
+
let config;
|
|
619
|
+
let channel;
|
|
620
|
+
|
|
621
|
+
while (true) {
|
|
622
|
+
const scopeWasPrompted = needsScopePrompt(commandOptions);
|
|
623
|
+
const routeCanGoBack = allowBack || scopeWasPrompted;
|
|
624
|
+
scope = await selectScope("update", commandOptions, allowBack);
|
|
625
|
+
if (scope === BACK) return BACK;
|
|
626
|
+
config = scope.project ? await readProjectConfig(scope.project) : null;
|
|
627
|
+
|
|
628
|
+
if (
|
|
629
|
+
scope.project &&
|
|
630
|
+
!commandOptions.target &&
|
|
631
|
+
!commandOptions.runtime &&
|
|
632
|
+
config?.target &&
|
|
633
|
+
!targetSpecIsSupported(config.target, UPDATE_TARGETS) &&
|
|
634
|
+
(!process.stdin.isTTY || !process.stdout.isTTY)
|
|
635
|
+
) {
|
|
636
|
+
throw new Error(ui("savedTargetNeedsUpdateRuntime", {
|
|
637
|
+
target: config.target,
|
|
638
|
+
supported: supportedList(UPDATE_TARGETS),
|
|
639
|
+
}));
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
if (scope.binaryOnly) {
|
|
643
|
+
channel = await selectChannel(commandOptions, config, routeCanGoBack, allowBack);
|
|
644
|
+
if (channel !== BACK) break;
|
|
645
|
+
} else {
|
|
646
|
+
const route = await walkSelections([
|
|
647
|
+
{ key: "kit", select: () => selectKit(commandOptions, config, routeCanGoBack, allowBack) },
|
|
648
|
+
{ key: "channel", select: () => selectChannel(commandOptions, config, routeCanGoBack, allowBack) },
|
|
649
|
+
{
|
|
650
|
+
key: "target",
|
|
651
|
+
select: (values) => selectTarget(
|
|
652
|
+
commandOptions, config, UPDATE_TARGETS, "updateTargetPrompt", routeCanGoBack, allowBack,
|
|
653
|
+
{ kit: kitName(values.kit) },
|
|
654
|
+
),
|
|
655
|
+
},
|
|
656
|
+
]);
|
|
657
|
+
if (route !== BACK) {
|
|
658
|
+
channel = route.channel;
|
|
659
|
+
scope = { ...scope, ...route };
|
|
660
|
+
break;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
if (!scopeWasPrompted) return BACK;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
if (scope.binaryOnly) {
|
|
668
|
+
return selfUpdate({ ...commandOptions, channel });
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
const selection = {
|
|
672
|
+
...scope,
|
|
673
|
+
channel,
|
|
674
|
+
};
|
|
675
|
+
const updateSelections = selection.global
|
|
676
|
+
? [selection]
|
|
677
|
+
: splitTargetSpec(selection.target).map((target) => ({ ...selection, target }));
|
|
678
|
+
if (selection.global) warning(ui("globalUpdateSafety"));
|
|
679
|
+
if (channel === "beta") {
|
|
680
|
+
const betaBinary = await prepareBetaBinary(commandOptions, { requiresPreview: true });
|
|
681
|
+
if (!betaBinary.proceed) return;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
printSection(ui("updatePreview"));
|
|
685
|
+
for (const targetSelection of updateSelections) {
|
|
686
|
+
if (updateSelections.length > 1) {
|
|
687
|
+
process.stdout.write(`${colorText(targetSelection.target, "target")}\n`);
|
|
688
|
+
}
|
|
689
|
+
await runAkCommand(updatePreviewArgs(targetSelection), {
|
|
690
|
+
cwd: targetSelection.project || process.cwd(),
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
printSection(ui("applyPlan"));
|
|
694
|
+
for (const targetSelection of updateSelections) {
|
|
695
|
+
printPlan(updateApplyArgs(targetSelection), targetSelection.project);
|
|
696
|
+
}
|
|
697
|
+
if (commandOptions.dryRun) {
|
|
698
|
+
process.stdout.write(`\n${ui("dryRunFiles")}\n`);
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
if (!(await approve(commandOptions, ui("applyKit")))) {
|
|
702
|
+
process.stdout.write(`${ui("cancelledUpdate")}\n`);
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
for (const targetSelection of updateSelections) {
|
|
707
|
+
await runAkCommand(updateApplyArgs(targetSelection), {
|
|
708
|
+
cwd: targetSelection.project || process.cwd(),
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
if (selection.project && !commandOptions.noSave) {
|
|
712
|
+
await writeProjectConfig(selection.project, selection);
|
|
713
|
+
}
|
|
714
|
+
process.stdout.write(`${ui("updateComplete")}\n`);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function updateAllPreviewArgs(candidate, channel) {
|
|
718
|
+
if (candidate.kind === "global") {
|
|
719
|
+
return globalUpdatePreviewArgs(channel, candidate.runtimes, candidate.kit);
|
|
720
|
+
}
|
|
721
|
+
return projectUpdatePreviewArgs(candidate.path, candidate.runtime, channel, candidate.kit);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function updateAllApplyArgs(candidate, channel) {
|
|
725
|
+
if (candidate.kind === "global") {
|
|
726
|
+
return globalUpdateApplyArgs(channel, candidate.runtimes, candidate.kit);
|
|
727
|
+
}
|
|
728
|
+
return projectUpdateApplyArgs(candidate.path, candidate.runtime, channel, candidate.kit);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
async function askDeepScanRoots() {
|
|
732
|
+
return [await askDirectory(ui("deepScanRootsPrompt"), { root: homedir() })];
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function buildUpdateCandidates(discovery, globalInstalls) {
|
|
736
|
+
const projects = discovery.projects.flatMap((project) => project.installs.map(({ kit, runtime }) => ({
|
|
737
|
+
...project,
|
|
738
|
+
id: `${project.id}:${kit}:${runtime}`,
|
|
739
|
+
kit,
|
|
740
|
+
runtime,
|
|
741
|
+
label: ui("projectCandidate", {
|
|
742
|
+
name: project.name,
|
|
743
|
+
path: project.path,
|
|
744
|
+
runtime,
|
|
745
|
+
kit: kitName(kit),
|
|
746
|
+
}),
|
|
747
|
+
})));
|
|
748
|
+
return {
|
|
749
|
+
registered: projects.filter((project) => project.sources.includes("registry")),
|
|
750
|
+
unregistered: projects.filter((project) => !project.sources.includes("registry")),
|
|
751
|
+
other: globalInstalls.map(({ kit, runtimes }) => ({
|
|
752
|
+
id: `global:${kit}`,
|
|
753
|
+
kind: "global",
|
|
754
|
+
kit,
|
|
755
|
+
runtimes,
|
|
756
|
+
label: ui("globalCandidate", { kit: kitName(kit), runtimes: runtimes.join(", ") }),
|
|
757
|
+
})),
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function printCandidateGroup(title, candidates) {
|
|
762
|
+
process.stdout.write(`\n${colorText(title, "group")}\n`);
|
|
763
|
+
for (const candidate of candidates) {
|
|
764
|
+
process.stdout.write(` - ${colorText(candidate.label, "target")}\n`);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
function printUpdateInventory(groups) {
|
|
769
|
+
printSection(ui("inventory"));
|
|
770
|
+
if (groups.registered.length > 0) {
|
|
771
|
+
printCandidateGroup(ui("registeredProjects"), groups.registered);
|
|
772
|
+
} else {
|
|
773
|
+
process.stdout.write(` ${ui("noProjectsInRegistry")}\n`);
|
|
774
|
+
}
|
|
775
|
+
if (groups.unregistered.length > 0) {
|
|
776
|
+
printCandidateGroup(ui("unregisteredProjects"), groups.unregistered);
|
|
777
|
+
}
|
|
778
|
+
if (groups.other.length > 0) printCandidateGroup(ui("otherInstalls"), groups.other);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function orderUpdateCandidates(candidates) {
|
|
782
|
+
const priority = { global: 0, project: 1 };
|
|
783
|
+
return [...candidates].sort((left, right) => priority[left.kind] - priority[right.kind]);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
async function loadUpdateInventory(commandOptions, deepScanRoots) {
|
|
787
|
+
const registry = await withSpinner(
|
|
788
|
+
ui("loadingRegistry"),
|
|
789
|
+
ui("loadedRegistry"),
|
|
790
|
+
() => runCapture(akBinary, ["projects", "list", "--json"]),
|
|
791
|
+
);
|
|
792
|
+
const discover = () => discoverProjectCandidates({
|
|
793
|
+
registryOutput: registry.stdout,
|
|
794
|
+
deepScanRoots,
|
|
795
|
+
maxDepth: commandOptions.maxDepth,
|
|
796
|
+
excludes: commandOptions.excludes,
|
|
797
|
+
supportedRuntimes: UPDATE_TARGETS,
|
|
798
|
+
});
|
|
799
|
+
const discovery = deepScanRoots.length > 0
|
|
800
|
+
? await withSpinner(ui("deepScanning"), ui("deepScanComplete"), discover)
|
|
801
|
+
: await discover();
|
|
802
|
+
const globalInstalls = await discoverGlobalKitInstalls({
|
|
803
|
+
akHome: process.env.AGENTKIT_HOME,
|
|
804
|
+
supportedRuntimes: UPDATE_TARGETS,
|
|
805
|
+
kits: KITS,
|
|
806
|
+
});
|
|
807
|
+
return { discovery, groups: buildUpdateCandidates(discovery, globalInstalls) };
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
async function updateAll(commandOptions, allowBack = false) {
|
|
811
|
+
while (true) {
|
|
812
|
+
const channel = await selectChannel(commandOptions, null, allowBack, allowBack);
|
|
813
|
+
if (channel === BACK) return BACK;
|
|
814
|
+
let deepScanRoots = [...commandOptions.deepScanRoots];
|
|
815
|
+
let selected;
|
|
816
|
+
let scanBaselinePaths = null;
|
|
817
|
+
while (!selected) {
|
|
818
|
+
const { discovery, groups } = await loadUpdateInventory(commandOptions, deepScanRoots);
|
|
819
|
+
for (const warning of discovery.warnings) {
|
|
820
|
+
process.stderr.write(`${ui("discoveryWarning", { message: warning })}\n`);
|
|
821
|
+
}
|
|
822
|
+
printUpdateInventory(groups);
|
|
823
|
+
const candidates = [...groups.registered, ...groups.unregistered, ...groups.other];
|
|
824
|
+
const projectCandidates = [...groups.registered, ...groups.unregistered];
|
|
825
|
+
const projectCount = new Set(projectCandidates.map((candidate) => candidate.path)).size;
|
|
826
|
+
if (scanBaselinePaths) {
|
|
827
|
+
const addedPaths = new Set(projectCandidates
|
|
828
|
+
.map((candidate) => candidate.path)
|
|
829
|
+
.filter((path) => !scanBaselinePaths.has(path)));
|
|
830
|
+
process.stdout.write(`${ui("deepScanResult", { count: addedPaths.size })}\n`);
|
|
831
|
+
scanBaselinePaths = null;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY || commandOptions.yes) {
|
|
835
|
+
selected = candidates;
|
|
836
|
+
break;
|
|
837
|
+
}
|
|
838
|
+
let reloadInventory = false;
|
|
839
|
+
while (!selected && !reloadInventory) {
|
|
840
|
+
const actionChoices = [
|
|
841
|
+
{ label: ui("updateEverything", { count: candidates.length }), value: "all" },
|
|
842
|
+
{
|
|
843
|
+
label: ui("updateAllProjects", { count: projectCount }),
|
|
844
|
+
value: "projects",
|
|
845
|
+
disabled: projectCandidates.length === 0 ? ui("noUpdateCandidates") : false,
|
|
846
|
+
},
|
|
847
|
+
{ label: ui("chooseUpdates"), value: "choose" },
|
|
848
|
+
{ label: ui("customDeepScanAction"), value: "custom-deep-scan" },
|
|
849
|
+
];
|
|
850
|
+
if (allowBack) actionChoices.push({ label: ui("updateAllBack"), value: BACK });
|
|
851
|
+
const action = await choose(ui("selectUpdateAll"), actionChoices);
|
|
852
|
+
if (action === BACK) break;
|
|
853
|
+
if (action === "custom-deep-scan") {
|
|
854
|
+
scanBaselinePaths = new Set(projectCandidates.map((candidate) => candidate.path));
|
|
855
|
+
deepScanRoots = [...new Set([...deepScanRoots, ...await askDeepScanRoots()])];
|
|
856
|
+
reloadInventory = true;
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
859
|
+
if (action === "all") {
|
|
860
|
+
selected = candidates;
|
|
861
|
+
continue;
|
|
862
|
+
}
|
|
863
|
+
if (action === "projects") {
|
|
864
|
+
selected = projectCandidates;
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
867
|
+
const ids = await multiChooseWithBack(
|
|
868
|
+
ui("selectUpdateAll"),
|
|
869
|
+
candidates.map((candidate) => ({ label: candidate.label, value: candidate.id })),
|
|
870
|
+
candidates.map((candidate) => candidate.id),
|
|
871
|
+
ui("backFromTargetSelection"),
|
|
872
|
+
);
|
|
873
|
+
if (ids === BACK) continue;
|
|
874
|
+
const selectedAction = await choose(ui("confirmSelectedUpdates", { count: ids.length }), [
|
|
875
|
+
{ label: ui("previewSelectedUpdates", { count: ids.length }), value: "continue" },
|
|
876
|
+
{ label: ui("back"), value: BACK },
|
|
877
|
+
]);
|
|
878
|
+
if (selectedAction === BACK) continue;
|
|
879
|
+
selected = candidates.filter((candidate) => ids.includes(candidate.id));
|
|
880
|
+
}
|
|
881
|
+
if (reloadInventory) continue;
|
|
882
|
+
if (!selected) break;
|
|
883
|
+
}
|
|
884
|
+
if (!selected) continue;
|
|
885
|
+
if (selected.length === 0) throw new Error(ui("noUpdateCandidates"));
|
|
886
|
+
selected = orderUpdateCandidates(selected);
|
|
887
|
+
if (selected.some((candidate) => candidate.kind === "global")) {
|
|
888
|
+
warning(ui("globalUpdateSafety"));
|
|
889
|
+
}
|
|
890
|
+
if (channel === "beta" && selected.length > 0) {
|
|
891
|
+
const betaBinary = await prepareBetaBinary(commandOptions, { requiresPreview: true });
|
|
892
|
+
if (!betaBinary.proceed) return;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
printSection(ui("updateAllPreview"));
|
|
896
|
+
for (const candidate of selected) {
|
|
897
|
+
process.stdout.write(`\n${colorText(candidate.label, "target")}\n`);
|
|
898
|
+
await runAkCommand(updateAllPreviewArgs(candidate, channel));
|
|
899
|
+
}
|
|
900
|
+
printSection(ui("updateAllApplyPlan"));
|
|
901
|
+
for (const candidate of selected) printPlan(updateAllApplyArgs(candidate, channel));
|
|
902
|
+
if (commandOptions.dryRun) {
|
|
903
|
+
process.stdout.write(`\n${ui("dryRunFiles")}\n`);
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
if (!(await approve(commandOptions, ui("applyAll")))) {
|
|
907
|
+
process.stdout.write(`${ui("cancelledAll")}\n`);
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
for (const [index, candidate] of selected.entries()) {
|
|
911
|
+
process.stdout.write(`${ui("updateProgress", {
|
|
912
|
+
current: index + 1,
|
|
913
|
+
total: selected.length,
|
|
914
|
+
label: candidate.label,
|
|
915
|
+
})}\n`);
|
|
916
|
+
await runAkCommand(updateAllApplyArgs(candidate, channel));
|
|
917
|
+
}
|
|
918
|
+
process.stdout.write(`${ui("updateAllComplete")}\n`);
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
async function exportKit(commandOptions, allowBack = false) {
|
|
924
|
+
const choices = [
|
|
925
|
+
{ label: ui("agyExport"), value: "agy" },
|
|
926
|
+
{ label: ui("portableExport"), value: "portable" },
|
|
927
|
+
].filter((choice) => EXPORT_TARGETS.has(choice.value));
|
|
928
|
+
const route = await walkSelections([
|
|
929
|
+
{
|
|
930
|
+
key: "kit",
|
|
931
|
+
select: () => selectKit(commandOptions, null, allowBack),
|
|
932
|
+
},
|
|
933
|
+
{
|
|
934
|
+
key: "target",
|
|
935
|
+
select: () => commandOptions.target || chooseLocalized(
|
|
936
|
+
allowBack,
|
|
937
|
+
ui("exportTargetPrompt"),
|
|
938
|
+
choices,
|
|
939
|
+
),
|
|
940
|
+
},
|
|
941
|
+
{
|
|
942
|
+
key: "channel",
|
|
943
|
+
select: () => selectChannel(commandOptions, null, allowBack),
|
|
944
|
+
},
|
|
945
|
+
]);
|
|
946
|
+
if (route === BACK) return BACK;
|
|
947
|
+
const { kit, target, channel } = route;
|
|
948
|
+
if (target === "agy" && commandOptions.target && !commandOptions.global) {
|
|
949
|
+
throw new Error("agy export requires --global");
|
|
950
|
+
}
|
|
951
|
+
if (target === "agy" && commandOptions.out) {
|
|
952
|
+
throw new Error("agy export uses --global, not --out");
|
|
953
|
+
}
|
|
954
|
+
if (target === "portable" && commandOptions.global) {
|
|
955
|
+
throw new Error("portable export uses --out, not --global");
|
|
956
|
+
}
|
|
957
|
+
let out = null;
|
|
958
|
+
if (target === "portable") {
|
|
959
|
+
out = resolve(commandOptions.out || await ask(ui("exportDirectory")));
|
|
960
|
+
if (isUnsafeProjectPath(out)) {
|
|
961
|
+
throw new Error("portable export output cannot be the filesystem root or home directory");
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
const selection = { kit, target, channel, global: target === "agy", out };
|
|
965
|
+
const args = exportArgs(selection);
|
|
966
|
+
printSection(ui("exportPlan"));
|
|
967
|
+
printPlan(args);
|
|
968
|
+
if (commandOptions.dryRun) {
|
|
969
|
+
process.stdout.write(`\n${ui("dryRunFiles")}\n`);
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
if (channel === "beta") {
|
|
973
|
+
const betaBinary = await prepareBetaBinary(commandOptions);
|
|
974
|
+
if (!betaBinary.proceed) return;
|
|
975
|
+
}
|
|
976
|
+
if (!(await approve(commandOptions, ui("runExport")))) {
|
|
977
|
+
process.stdout.write(`${ui("cancelledFiles")}\n`);
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
await runAkCommand(args);
|
|
981
|
+
process.stdout.write(`${ui("exportComplete")}\n`);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
async function doctor(options) {
|
|
985
|
+
const cwd = options.project ? await resolveProjectPath(options.project) : process.cwd();
|
|
986
|
+
await runAkCommand(["doctor", "--exit-on-fail", "--verbose"], { cwd });
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
async function main() {
|
|
990
|
+
const options = parseArgs(process.argv.slice(2));
|
|
991
|
+
if (options.version) {
|
|
992
|
+
process.stdout.write(`${metadata.version}\n`);
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
if (options.help || options.command === "help") {
|
|
996
|
+
usage(options.language || "en");
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
if (!options.command && (!process.stdin.isTTY || !process.stdout.isTTY)) {
|
|
1000
|
+
throw new Error("interactive input is unavailable; pass explicit flags and --yes");
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
activeLanguage = await selectLanguage(options);
|
|
1004
|
+
setPromptCopy({ cancelled: ui("promptCancelled") });
|
|
1005
|
+
|
|
1006
|
+
const interactiveRoot = !options.command;
|
|
1007
|
+
const languageCanChange = interactiveRoot && !options.language;
|
|
1008
|
+
installedAkVersion = await ensureAk(akBinary);
|
|
1009
|
+
if (interactiveRoot) showCurrentBinary();
|
|
1010
|
+
while (true) {
|
|
1011
|
+
if (!options.command) {
|
|
1012
|
+
const command = await chooseCommand(languageCanChange);
|
|
1013
|
+
if (command === BACK) {
|
|
1014
|
+
activeLanguage = await selectLanguage(options, true);
|
|
1015
|
+
setPromptCopy({ cancelled: ui("promptCancelled") });
|
|
1016
|
+
showCurrentBinary();
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
options.command = command;
|
|
1020
|
+
}
|
|
1021
|
+
activeAction = options.command;
|
|
1022
|
+
validateForCommand(options);
|
|
1023
|
+
let result;
|
|
1024
|
+
switch (options.command) {
|
|
1025
|
+
case "install":
|
|
1026
|
+
result = await install(options, interactiveRoot);
|
|
1027
|
+
break;
|
|
1028
|
+
case "update":
|
|
1029
|
+
result = await update(options, interactiveRoot);
|
|
1030
|
+
break;
|
|
1031
|
+
case "self-update":
|
|
1032
|
+
result = await selfUpdate(options, interactiveRoot);
|
|
1033
|
+
break;
|
|
1034
|
+
case "update-all":
|
|
1035
|
+
result = await updateAll(options, interactiveRoot);
|
|
1036
|
+
break;
|
|
1037
|
+
case "export":
|
|
1038
|
+
result = await exportKit(options, interactiveRoot);
|
|
1039
|
+
break;
|
|
1040
|
+
case "doctor":
|
|
1041
|
+
result = await doctor(options);
|
|
1042
|
+
break;
|
|
1043
|
+
default:
|
|
1044
|
+
usage(activeLanguage);
|
|
1045
|
+
throw new Error(`unsupported command: ${options.command}`);
|
|
1046
|
+
}
|
|
1047
|
+
if (interactiveRoot && result === BACK) {
|
|
1048
|
+
options.command = null;
|
|
1049
|
+
continue;
|
|
1050
|
+
}
|
|
1051
|
+
break;
|
|
1052
|
+
}
|
|
1053
|
+
finishInteractive(ui("done"));
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
async function offerGitHubIssue(error) {
|
|
1057
|
+
if (!isReportableAkError(error, akBinary)) return;
|
|
1058
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) return;
|
|
1059
|
+
const repository = resolveIssueRepository();
|
|
1060
|
+
const report = buildIssueReport({
|
|
1061
|
+
error,
|
|
1062
|
+
helperVersion: metadata.version,
|
|
1063
|
+
action: activeAction,
|
|
1064
|
+
language: activeLanguage,
|
|
1065
|
+
cwd: error.command?.cwd || process.cwd(),
|
|
1066
|
+
});
|
|
1067
|
+
try {
|
|
1068
|
+
try {
|
|
1069
|
+
await checkIssueRepository({ repository, ghBinary });
|
|
1070
|
+
} catch {
|
|
1071
|
+
warning(ui("issueRepoUnavailable", { repo: repository }));
|
|
1072
|
+
process.stderr.write(`${ui("issueRepoSetup")}\n`);
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
if (!(await confirm(ui("createIssue", { repo: repository }), false))) {
|
|
1076
|
+
process.stdout.write(`${ui("issueSkipped")}\n`);
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
const duplicate = await findDuplicateIssue(report, { repository, ghBinary });
|
|
1080
|
+
if (duplicate) {
|
|
1081
|
+
process.stdout.write(`${ui("issueDuplicate", { url: duplicate.url })}\n`);
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
const url = await createGitHubIssue(report, { repository, ghBinary });
|
|
1085
|
+
process.stdout.write(`${ui("issueCreated", { url })}\n`);
|
|
1086
|
+
} catch (issueError) {
|
|
1087
|
+
process.stderr.write(`${ui("issueFailed", { message: issueError.message })}\n`);
|
|
1088
|
+
process.stderr.write(`${ui("issueManual")}\n`);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
try {
|
|
1093
|
+
await main();
|
|
1094
|
+
} catch (error) {
|
|
1095
|
+
if (error instanceof PromptCancelledError) {
|
|
1096
|
+
process.exitCode = 0;
|
|
1097
|
+
} else {
|
|
1098
|
+
const exitCode = error.exitCode || 1;
|
|
1099
|
+
process.stderr.write(`${ui("error", { message: error.message })}\n`);
|
|
1100
|
+
try {
|
|
1101
|
+
await offerGitHubIssue(error);
|
|
1102
|
+
} catch (reportError) {
|
|
1103
|
+
if (!(reportError instanceof PromptCancelledError)) {
|
|
1104
|
+
process.stderr.write(`${ui("issueFailed", { message: reportError.message })}\n`);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
process.exitCode = exitCode;
|
|
1108
|
+
}
|
|
1109
|
+
}
|