@thieung/agentkit-helper 0.1.0 → 0.1.1
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 +7 -1
- package/README.vi.md +6 -1
- package/bin/agentkit-helper.mjs +157 -35
- package/lib/commands.mjs +23 -2
- package/lib/i18n.mjs +26 -2
- package/lib/pi-profiles.mjs +102 -0
- package/lib/prompts.mjs +33 -11
- package/lib/runner.mjs +30 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ In the TUI, use:
|
|
|
28
28
|
- ↑/↓ to navigate
|
|
29
29
|
- Space to select multiple runtimes
|
|
30
30
|
- Enter to confirm
|
|
31
|
-
-
|
|
31
|
+
- Esc to return to the previous step
|
|
32
32
|
|
|
33
33
|
Choose a project or user/global scope, Engineer or Marketing Kit, one or more
|
|
34
34
|
runtimes, and the Stable or Beta channel. AgentKit remains responsible for Kit
|
|
@@ -40,6 +40,12 @@ separate consent, defaulting to No. It retries with `--force` only after you
|
|
|
40
40
|
choose Yes. Global update is preserve-only: user-modified files are skipped
|
|
41
41
|
and the helper never adds `--force`.
|
|
42
42
|
|
|
43
|
+
Platform status: macOS is verified locally. Linux is a supported target for the
|
|
44
|
+
portable Node.js path. Native Windows PowerShell support is experimental until
|
|
45
|
+
it passes a real-machine smoke test; the repository includes Windows-oriented
|
|
46
|
+
command handling and CI coverage, but does not yet claim provider-backed E2E
|
|
47
|
+
verification.
|
|
48
|
+
|
|
43
49
|
<details>
|
|
44
50
|
<summary><strong>Advanced CLI usage</strong></summary>
|
|
45
51
|
|
package/README.vi.md
CHANGED
|
@@ -27,7 +27,7 @@ Trong TUI, dùng:
|
|
|
27
27
|
- ↑/↓ để di chuyển
|
|
28
28
|
- Space để chọn nhiều runtime
|
|
29
29
|
- Enter để xác nhận
|
|
30
|
-
-
|
|
30
|
+
- Esc để trở về bước trước
|
|
31
31
|
|
|
32
32
|
Bạn chỉ cần chọn scope project hoặc user/global, Engineer hoặc Marketing Kit,
|
|
33
33
|
một hay nhiều runtime và release channel Stable hoặc Beta. AgentKit vẫn quản
|
|
@@ -38,6 +38,11 @@ tồn tại hoặc có drift, TUI hiển thị WARNING và hỏi consent riêng,
|
|
|
38
38
|
Chỉ khi chọn Yes, helper mới retry bằng `--force`. Global update luôn dùng
|
|
39
39
|
preserve-only: file do user chỉnh sửa được bỏ qua và helper không thêm `--force`.
|
|
40
40
|
|
|
41
|
+
Trạng thái platform: macOS đã được verify local. Linux là target được hỗ trợ
|
|
42
|
+
qua code path Node.js portable. Native Windows PowerShell đang ở mức
|
|
43
|
+
experimental cho đến khi được smoke-test trên máy thật; repository đã có xử lý
|
|
44
|
+
command và CI dành cho Windows nhưng chưa claim provider-backed E2E.
|
|
45
|
+
|
|
41
46
|
<details>
|
|
42
47
|
<summary><strong>Cách dùng CLI nâng cao</strong></summary>
|
|
43
48
|
|
package/bin/agentkit-helper.mjs
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
import {
|
|
18
18
|
exportArgs,
|
|
19
19
|
formatCommand,
|
|
20
|
+
formatEnvironmentAssignments,
|
|
20
21
|
globalUpdateApplyArgs,
|
|
21
22
|
globalUpdatePreviewArgs,
|
|
22
23
|
installArgs,
|
|
@@ -45,6 +46,12 @@ import {
|
|
|
45
46
|
} from "../lib/github-issue.mjs";
|
|
46
47
|
import { t } from "../lib/i18n.mjs";
|
|
47
48
|
import { isUnsafeProjectPath, resolveProjectPath } from "../lib/project.mjs";
|
|
49
|
+
import {
|
|
50
|
+
classifyPiProfiles,
|
|
51
|
+
parsePiProfileInventory,
|
|
52
|
+
piProfileManagerInvocation,
|
|
53
|
+
piProfileProcessOptions,
|
|
54
|
+
} from "../lib/pi-profiles.mjs";
|
|
48
55
|
import { BACK, walkSelections } from "../lib/navigation.mjs";
|
|
49
56
|
import {
|
|
50
57
|
ask,
|
|
@@ -78,11 +85,15 @@ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
|
78
85
|
const metadata = JSON.parse(await readFile(resolve(packageRoot, "package.json"), "utf8"));
|
|
79
86
|
const akBinary = process.env.AK_HELPER_AK_BIN || "ak";
|
|
80
87
|
const ghBinary = process.env.AK_HELPER_GH_BIN || "gh";
|
|
88
|
+
const piProfileManager = piProfileManagerInvocation(
|
|
89
|
+
process.env.AK_HELPER_PPM_BIN || "pi-profile-manager",
|
|
90
|
+
);
|
|
81
91
|
const installerUrl = "https://agentkit.best/install.sh";
|
|
82
92
|
const windowsInstallerUrl = "https://agentkit.best/install.ps1";
|
|
83
93
|
let activeLanguage = "en";
|
|
84
94
|
let activeAction = null;
|
|
85
95
|
let installedAkVersion = "";
|
|
96
|
+
let binaryUpdateAvailable = false;
|
|
86
97
|
|
|
87
98
|
function ui(key, values) {
|
|
88
99
|
return t(activeLanguage, key, values);
|
|
@@ -172,38 +183,74 @@ Target groups:
|
|
|
172
183
|
`);
|
|
173
184
|
}
|
|
174
185
|
|
|
175
|
-
function printPlan(args, cwd) {
|
|
186
|
+
function printPlan(args, cwd, envOverrides = {}) {
|
|
176
187
|
const suffix = cwd && cwd !== process.cwd() ? ` (cwd: ${cwd})` : "";
|
|
177
|
-
|
|
188
|
+
const environment = formatEnvironmentAssignments(envOverrides);
|
|
189
|
+
const prefix = environment ? `${environment} ` : "";
|
|
190
|
+
process.stdout.write(`${colorText(` ${prefix}${formatCommand(akBinary, args)}${suffix}`, "command")}\n`);
|
|
178
191
|
}
|
|
179
192
|
|
|
180
193
|
function printSection(message) {
|
|
181
194
|
process.stdout.write(`\n${colorText(message, "section")}\n`);
|
|
182
195
|
}
|
|
183
196
|
|
|
184
|
-
async function runAkCommand(
|
|
197
|
+
async function runAkCommand(
|
|
198
|
+
args,
|
|
199
|
+
{ cwd = process.cwd(), envOverrides = {}, envUnset = [] } = {},
|
|
200
|
+
) {
|
|
185
201
|
const result = await withSpinner(
|
|
186
202
|
ui("runningAkCommand"),
|
|
187
203
|
ui("akCommandComplete"),
|
|
188
|
-
() => runCapture(akBinary, args, { cwd }),
|
|
204
|
+
() => runCapture(akBinary, args, { cwd, envOverrides, envUnset }),
|
|
189
205
|
);
|
|
190
206
|
if (result.stdout) process.stdout.write(`${result.stdout}\n`);
|
|
191
207
|
if (result.stderr) process.stderr.write(`${result.stderr}\n`);
|
|
192
208
|
}
|
|
193
209
|
|
|
210
|
+
async function checkInteractiveBinaryUpdate() {
|
|
211
|
+
const channel = releaseChannelForVersion(installedAkVersion) || "stable";
|
|
212
|
+
try {
|
|
213
|
+
const result = await withSpinner(
|
|
214
|
+
ui("checkingBinaryUpdate"),
|
|
215
|
+
ui("checkedBinaryUpdate"),
|
|
216
|
+
() => runCapture(akBinary, selfUpdateJsonCheckArgs(channel)),
|
|
217
|
+
);
|
|
218
|
+
const check = parseSelfUpdateOutput(result.stdout);
|
|
219
|
+
const classification = classifySelfUpdate(check);
|
|
220
|
+
binaryUpdateAvailable = classification === "update";
|
|
221
|
+
if (binaryUpdateAvailable) {
|
|
222
|
+
warning(`│ ${ui("binaryUpdateAvailable", {
|
|
223
|
+
current: check.current_version,
|
|
224
|
+
latest: check.latest_version,
|
|
225
|
+
channel: check.channel || channel,
|
|
226
|
+
})}`);
|
|
227
|
+
} else if (classification === "current") {
|
|
228
|
+
process.stdout.write(`${colorText(`│ ${ui("binaryUpToDate", {
|
|
229
|
+
version: check.current_version,
|
|
230
|
+
channel: check.channel || channel,
|
|
231
|
+
})}`, "binary")}\n`);
|
|
232
|
+
} else {
|
|
233
|
+
warning(`│ ${ui("binaryUpdateStatusUnknown", { status: check.status })}`);
|
|
234
|
+
}
|
|
235
|
+
} catch (error) {
|
|
236
|
+
binaryUpdateAvailable = false;
|
|
237
|
+
warning(`│ ${ui("binaryUpdateCheckFailed", { message: error.message })}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
194
241
|
async function chooseCommand(allowLanguageBack = false) {
|
|
195
242
|
const choices = [
|
|
243
|
+
{ label: ui("selfUpdateAction"), value: "self-update" },
|
|
196
244
|
{ label: ui("installAction"), value: "install" },
|
|
197
245
|
{ label: ui("updateAction"), value: "update" },
|
|
198
|
-
{ label: ui("selfUpdateAction"), value: "self-update" },
|
|
199
246
|
{ label: ui("updateAllAction"), value: "update-all" },
|
|
200
247
|
{ label: ui("exportAction"), value: "export" },
|
|
201
248
|
{ label: ui("doctorAction"), value: "doctor" },
|
|
202
249
|
];
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
250
|
+
const defaultIndex = binaryUpdateAvailable ? 0 : 1;
|
|
251
|
+
return allowLanguageBack
|
|
252
|
+
? chooseWithBack(ui("commandPrompt"), choices, defaultIndex, ui("escapeBack"))
|
|
253
|
+
: choose(ui("commandPrompt"), choices, defaultIndex);
|
|
207
254
|
}
|
|
208
255
|
|
|
209
256
|
function kitName(kit) {
|
|
@@ -231,7 +278,7 @@ async function selectLanguage(options, forcePrompt = false) {
|
|
|
231
278
|
|
|
232
279
|
function chooseLocalized(allowBack, message, choices, defaultIndex = 0) {
|
|
233
280
|
return allowBack
|
|
234
|
-
? chooseWithBack(message, choices, defaultIndex, ui("
|
|
281
|
+
? chooseWithBack(message, choices, defaultIndex, ui("escapeBack"))
|
|
235
282
|
: choose(message, choices, defaultIndex);
|
|
236
283
|
}
|
|
237
284
|
|
|
@@ -331,8 +378,7 @@ async function selectTarget(
|
|
|
331
378
|
disabled: true,
|
|
332
379
|
});
|
|
333
380
|
}
|
|
334
|
-
const
|
|
335
|
-
const initialValues = configuredTargets.length > 0 ? configuredTargets : ["codex"];
|
|
381
|
+
const initialValues = [];
|
|
336
382
|
const selected = await (allowBack ? multiChooseWithBack : multiChoose)(
|
|
337
383
|
ui(promptKey, promptValues),
|
|
338
384
|
choices,
|
|
@@ -715,14 +761,14 @@ async function update(commandOptions, allowBack = false) {
|
|
|
715
761
|
}
|
|
716
762
|
|
|
717
763
|
function updateAllPreviewArgs(candidate, channel) {
|
|
718
|
-
if (candidate.kind === "global") {
|
|
764
|
+
if (candidate.kind === "global" || candidate.kind === "profile") {
|
|
719
765
|
return globalUpdatePreviewArgs(channel, candidate.runtimes, candidate.kit);
|
|
720
766
|
}
|
|
721
767
|
return projectUpdatePreviewArgs(candidate.path, candidate.runtime, channel, candidate.kit);
|
|
722
768
|
}
|
|
723
769
|
|
|
724
770
|
function updateAllApplyArgs(candidate, channel) {
|
|
725
|
-
if (candidate.kind === "global") {
|
|
771
|
+
if (candidate.kind === "global" || candidate.kind === "profile") {
|
|
726
772
|
return globalUpdateApplyArgs(channel, candidate.runtimes, candidate.kit);
|
|
727
773
|
}
|
|
728
774
|
return projectUpdateApplyArgs(candidate.path, candidate.runtime, channel, candidate.kit);
|
|
@@ -732,7 +778,12 @@ async function askDeepScanRoots() {
|
|
|
732
778
|
return [await askDirectory(ui("deepScanRootsPrompt"), { root: homedir() })];
|
|
733
779
|
}
|
|
734
780
|
|
|
735
|
-
function buildUpdateCandidates(
|
|
781
|
+
function buildUpdateCandidates(
|
|
782
|
+
discovery,
|
|
783
|
+
globalInstalls,
|
|
784
|
+
piProfiles = [],
|
|
785
|
+
claimedProfileRuntimes = new Set(),
|
|
786
|
+
) {
|
|
736
787
|
const projects = discovery.projects.flatMap((project) => project.installs.map(({ kit, runtime }) => ({
|
|
737
788
|
...project,
|
|
738
789
|
id: `${project.id}:${kit}:${runtime}`,
|
|
@@ -745,16 +796,40 @@ function buildUpdateCandidates(discovery, globalInstalls) {
|
|
|
745
796
|
kit: kitName(kit),
|
|
746
797
|
}),
|
|
747
798
|
})));
|
|
799
|
+
const profileCandidates = piProfiles.map((profile) => ({
|
|
800
|
+
id: `profile:${profile.runtime}:${profile.id}`,
|
|
801
|
+
kind: "profile",
|
|
802
|
+
kit: "engineer",
|
|
803
|
+
runtime: profile.runtime,
|
|
804
|
+
runtimes: [profile.runtime],
|
|
805
|
+
profile,
|
|
806
|
+
...piProfileProcessOptions(profile),
|
|
807
|
+
label: ui("piProfileCandidate", {
|
|
808
|
+
profile: profile.id,
|
|
809
|
+
runtime: profile.runtime,
|
|
810
|
+
}),
|
|
811
|
+
}));
|
|
812
|
+
const ordinaryGlobalInstalls = globalInstalls
|
|
813
|
+
.map((install) => install.kit === "engineer"
|
|
814
|
+
? {
|
|
815
|
+
...install,
|
|
816
|
+
runtimes: install.runtimes.filter((runtime) => !claimedProfileRuntimes.has(runtime)),
|
|
817
|
+
}
|
|
818
|
+
: install)
|
|
819
|
+
.filter((install) => install.runtimes.length > 0);
|
|
748
820
|
return {
|
|
749
821
|
registered: projects.filter((project) => project.sources.includes("registry")),
|
|
750
822
|
unregistered: projects.filter((project) => !project.sources.includes("registry")),
|
|
751
|
-
other:
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
823
|
+
other: [
|
|
824
|
+
...profileCandidates,
|
|
825
|
+
...ordinaryGlobalInstalls.map(({ kit, runtimes }) => ({
|
|
826
|
+
id: `global:${kit}`,
|
|
827
|
+
kind: "global",
|
|
828
|
+
kit,
|
|
829
|
+
runtimes,
|
|
830
|
+
label: ui("globalCandidate", { kit: kitName(kit), runtimes: runtimes.join(", ") }),
|
|
831
|
+
})),
|
|
832
|
+
],
|
|
758
833
|
};
|
|
759
834
|
}
|
|
760
835
|
|
|
@@ -779,7 +854,7 @@ function printUpdateInventory(groups) {
|
|
|
779
854
|
}
|
|
780
855
|
|
|
781
856
|
function orderUpdateCandidates(candidates) {
|
|
782
|
-
const priority = {
|
|
857
|
+
const priority = { profile: 0, global: 1, project: 2 };
|
|
783
858
|
return [...candidates].sort((left, right) => priority[left.kind] - priority[right.kind]);
|
|
784
859
|
}
|
|
785
860
|
|
|
@@ -804,7 +879,46 @@ async function loadUpdateInventory(commandOptions, deepScanRoots) {
|
|
|
804
879
|
supportedRuntimes: UPDATE_TARGETS,
|
|
805
880
|
kits: KITS,
|
|
806
881
|
});
|
|
807
|
-
|
|
882
|
+
const profileRuntimeInstalls = new Set(globalInstalls
|
|
883
|
+
.filter((install) => install.kit === "engineer")
|
|
884
|
+
.flatMap((install) => install.runtimes)
|
|
885
|
+
.filter((runtime) => runtime === "pi" || runtime === "omp"));
|
|
886
|
+
let updateableProfiles = [];
|
|
887
|
+
const claimedProfileRuntimes = new Set();
|
|
888
|
+
try {
|
|
889
|
+
const inventory = await runCapture(piProfileManager.binary, [
|
|
890
|
+
...piProfileManager.prefixArgs,
|
|
891
|
+
"profiles", "list", "--json",
|
|
892
|
+
]);
|
|
893
|
+
const profiles = parsePiProfileInventory(inventory.stdout);
|
|
894
|
+
for (const profile of profiles.filter((profile) => profile.agentkitEnabled)) {
|
|
895
|
+
claimedProfileRuntimes.add(profile.runtime);
|
|
896
|
+
}
|
|
897
|
+
const classified = classifyPiProfiles(profiles);
|
|
898
|
+
updateableProfiles = classified.updateable;
|
|
899
|
+
for (const profile of classified.skipped) {
|
|
900
|
+
discovery.warnings.push(ui("piProfileSkipped", {
|
|
901
|
+
profile: profile.id,
|
|
902
|
+
reason: !profile.agentkitEnabled
|
|
903
|
+
? ui("piProfileNoAgentKit")
|
|
904
|
+
: ui("piProfileNotSafe"),
|
|
905
|
+
}));
|
|
906
|
+
}
|
|
907
|
+
} catch (error) {
|
|
908
|
+
if (error.code !== "ENOENT") {
|
|
909
|
+
for (const runtime of profileRuntimeInstalls) claimedProfileRuntimes.add(runtime);
|
|
910
|
+
discovery.warnings.push(ui("piProfileDiscoveryUnavailable", { message: error.message }));
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
return {
|
|
914
|
+
discovery,
|
|
915
|
+
groups: buildUpdateCandidates(
|
|
916
|
+
discovery,
|
|
917
|
+
globalInstalls,
|
|
918
|
+
updateableProfiles,
|
|
919
|
+
claimedProfileRuntimes,
|
|
920
|
+
),
|
|
921
|
+
};
|
|
808
922
|
}
|
|
809
923
|
|
|
810
924
|
async function updateAll(commandOptions, allowBack = false) {
|
|
@@ -847,8 +961,9 @@ async function updateAll(commandOptions, allowBack = false) {
|
|
|
847
961
|
{ label: ui("chooseUpdates"), value: "choose" },
|
|
848
962
|
{ label: ui("customDeepScanAction"), value: "custom-deep-scan" },
|
|
849
963
|
];
|
|
850
|
-
|
|
851
|
-
|
|
964
|
+
const action = allowBack
|
|
965
|
+
? await chooseWithBack(ui("selectUpdateAll"), actionChoices, 0, ui("escapeBack"))
|
|
966
|
+
: await choose(ui("selectUpdateAll"), actionChoices);
|
|
852
967
|
if (action === BACK) break;
|
|
853
968
|
if (action === "custom-deep-scan") {
|
|
854
969
|
scanBaselinePaths = new Set(projectCandidates.map((candidate) => candidate.path));
|
|
@@ -871,10 +986,12 @@ async function updateAll(commandOptions, allowBack = false) {
|
|
|
871
986
|
ui("backFromTargetSelection"),
|
|
872
987
|
);
|
|
873
988
|
if (ids === BACK) continue;
|
|
874
|
-
const selectedAction = await
|
|
875
|
-
|
|
876
|
-
{ label: ui("
|
|
877
|
-
|
|
989
|
+
const selectedAction = await chooseWithBack(
|
|
990
|
+
ui("confirmSelectedUpdates", { count: ids.length }),
|
|
991
|
+
[{ label: ui("previewSelectedUpdates", { count: ids.length }), value: "continue" }],
|
|
992
|
+
0,
|
|
993
|
+
ui("escapeBack"),
|
|
994
|
+
);
|
|
878
995
|
if (selectedAction === BACK) continue;
|
|
879
996
|
selected = candidates.filter((candidate) => ids.includes(candidate.id));
|
|
880
997
|
}
|
|
@@ -884,7 +1001,7 @@ async function updateAll(commandOptions, allowBack = false) {
|
|
|
884
1001
|
if (!selected) continue;
|
|
885
1002
|
if (selected.length === 0) throw new Error(ui("noUpdateCandidates"));
|
|
886
1003
|
selected = orderUpdateCandidates(selected);
|
|
887
|
-
if (selected.some((candidate) => candidate.kind === "global")) {
|
|
1004
|
+
if (selected.some((candidate) => candidate.kind === "global" || candidate.kind === "profile")) {
|
|
888
1005
|
warning(ui("globalUpdateSafety"));
|
|
889
1006
|
}
|
|
890
1007
|
if (channel === "beta" && selected.length > 0) {
|
|
@@ -895,10 +1012,12 @@ async function updateAll(commandOptions, allowBack = false) {
|
|
|
895
1012
|
printSection(ui("updateAllPreview"));
|
|
896
1013
|
for (const candidate of selected) {
|
|
897
1014
|
process.stdout.write(`\n${colorText(candidate.label, "target")}\n`);
|
|
898
|
-
await runAkCommand(updateAllPreviewArgs(candidate, channel));
|
|
1015
|
+
await runAkCommand(updateAllPreviewArgs(candidate, channel), candidate);
|
|
899
1016
|
}
|
|
900
1017
|
printSection(ui("updateAllApplyPlan"));
|
|
901
|
-
for (const candidate of selected)
|
|
1018
|
+
for (const candidate of selected) {
|
|
1019
|
+
printPlan(updateAllApplyArgs(candidate, channel), null, candidate.envOverrides);
|
|
1020
|
+
}
|
|
902
1021
|
if (commandOptions.dryRun) {
|
|
903
1022
|
process.stdout.write(`\n${ui("dryRunFiles")}\n`);
|
|
904
1023
|
return;
|
|
@@ -913,7 +1032,7 @@ async function updateAll(commandOptions, allowBack = false) {
|
|
|
913
1032
|
total: selected.length,
|
|
914
1033
|
label: candidate.label,
|
|
915
1034
|
})}\n`);
|
|
916
|
-
await runAkCommand(updateAllApplyArgs(candidate, channel));
|
|
1035
|
+
await runAkCommand(updateAllApplyArgs(candidate, channel), candidate);
|
|
917
1036
|
}
|
|
918
1037
|
process.stdout.write(`${ui("updateAllComplete")}\n`);
|
|
919
1038
|
return;
|
|
@@ -1006,7 +1125,10 @@ async function main() {
|
|
|
1006
1125
|
const interactiveRoot = !options.command;
|
|
1007
1126
|
const languageCanChange = interactiveRoot && !options.language;
|
|
1008
1127
|
installedAkVersion = await ensureAk(akBinary);
|
|
1009
|
-
if (interactiveRoot)
|
|
1128
|
+
if (interactiveRoot) {
|
|
1129
|
+
showCurrentBinary();
|
|
1130
|
+
await checkInteractiveBinaryUpdate();
|
|
1131
|
+
}
|
|
1010
1132
|
while (true) {
|
|
1011
1133
|
if (!options.command) {
|
|
1012
1134
|
const command = await chooseCommand(languageCanChange);
|
package/lib/commands.mjs
CHANGED
|
@@ -101,6 +101,27 @@ function quote(value) {
|
|
|
101
101
|
: `'${value.replaceAll("'", `'\\''`)}'`;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
return [
|
|
104
|
+
function quotePowerShell(value) {
|
|
105
|
+
return /^[A-Za-z0-9_./:@=\\-]+$/.test(value)
|
|
106
|
+
? value
|
|
107
|
+
: `'${value.replaceAll("'", "''")}'`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function formatCommand(binary, args, { platform = process.platform } = {}) {
|
|
111
|
+
const quoteValue = platform === "win32" ? quotePowerShell : quote;
|
|
112
|
+
return [binary, ...args].map(quoteValue).join(" ");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function formatEnvironmentAssignments(
|
|
116
|
+
values,
|
|
117
|
+
{ platform = process.platform } = {},
|
|
118
|
+
) {
|
|
119
|
+
if (platform === "win32") {
|
|
120
|
+
return Object.entries(values)
|
|
121
|
+
.map(([name, value]) => `$env:${name} = ${quotePowerShell(String(value))};`)
|
|
122
|
+
.join(" ");
|
|
123
|
+
}
|
|
124
|
+
return Object.entries(values)
|
|
125
|
+
.map(([name, value]) => `${name}=${quote(String(value))}`)
|
|
126
|
+
.join(" ");
|
|
106
127
|
}
|
package/lib/i18n.mjs
CHANGED
|
@@ -5,6 +5,12 @@ const messages = {
|
|
|
5
5
|
languagePrompt: "Choose interface language / Chọn ngôn ngữ giao diện",
|
|
6
6
|
commandPrompt: "What do you want to do?",
|
|
7
7
|
currentBinary: "Current ak binary: {version} ({channel})",
|
|
8
|
+
checkingBinaryUpdate: "Checking for ak binary updates…",
|
|
9
|
+
checkedBinaryUpdate: "Checked ak binary updates.",
|
|
10
|
+
binaryUpdateAvailable: "UPDATE AVAILABLE: ak {current} → {latest} ({channel}). The update action is selected for you.",
|
|
11
|
+
binaryUpToDate: "UP TO DATE: ak {version} is the latest version on the {channel} channel.",
|
|
12
|
+
binaryUpdateStatusUnknown: "Could not determine binary update availability (status: {status}).",
|
|
13
|
+
binaryUpdateCheckFailed: "Could not check for ak binary updates: {message}. Continuing without blocking the helper.",
|
|
8
14
|
installAction: "Install a Kit",
|
|
9
15
|
updateAction: "Update a Kit",
|
|
10
16
|
selfUpdateAction: "Update ak binary",
|
|
@@ -12,6 +18,7 @@ const messages = {
|
|
|
12
18
|
exportAction: "Export Kit (not a runtime install)",
|
|
13
19
|
doctorAction: "Run health checks",
|
|
14
20
|
backToLanguage: "← Back to interface language",
|
|
21
|
+
escapeBack: "Esc: Back",
|
|
15
22
|
chooseProject: "Choose a project directory",
|
|
16
23
|
currentProjectScope: "Use current project ({path})",
|
|
17
24
|
globalScope: "Use user/global Kit scope",
|
|
@@ -84,12 +91,17 @@ const messages = {
|
|
|
84
91
|
unregisteredProjects: "Other discovered projects:",
|
|
85
92
|
otherInstalls: "Global scope:",
|
|
86
93
|
globalCandidate: "{kit} global scope ({runtimes})",
|
|
94
|
+
piProfileCandidate: "Engineer Kit — {runtime} profile: {profile}",
|
|
95
|
+
piProfileSkipped: "Pi Profile Manager skipped {profile}: {reason}",
|
|
96
|
+
piProfileNoAgentKit: "AgentKit is not enabled",
|
|
97
|
+
piProfileNotSafe: "profile is unmanaged, unhealthy, or drifted",
|
|
98
|
+
piProfileDiscoveryUnavailable: "Pi profile discovery is unavailable: {message}",
|
|
87
99
|
projectCandidate: "{name} — {kit} ({runtime}) — {path}",
|
|
88
100
|
selectUpdateAll: "Select what to update (Space toggles, Enter confirms)",
|
|
89
101
|
updateEverything: "Update all discovered Kit installs: global + projects ({count})",
|
|
90
102
|
updateAllProjects: "Update projects only ({count})",
|
|
91
103
|
chooseUpdates: "Choose individual targets…",
|
|
92
|
-
backFromTargetSelection: "
|
|
104
|
+
backFromTargetSelection: "Esc: Back",
|
|
93
105
|
confirmSelectedUpdates: "What should happen with the {count} selected target(s)?",
|
|
94
106
|
previewSelectedUpdates: "Continue to preview ({count})",
|
|
95
107
|
noProjectsInRegistry: "No updateable registered projects were found.",
|
|
@@ -128,6 +140,12 @@ const messages = {
|
|
|
128
140
|
languagePrompt: "Chọn ngôn ngữ giao diện / Choose interface language",
|
|
129
141
|
commandPrompt: "Bạn muốn làm gì?",
|
|
130
142
|
currentBinary: "ak binary hiện tại: {version} ({channel})",
|
|
143
|
+
checkingBinaryUpdate: "Đang kiểm tra bản cập nhật ak binary…",
|
|
144
|
+
checkedBinaryUpdate: "Đã kiểm tra cập nhật ak binary.",
|
|
145
|
+
binaryUpdateAvailable: "CÓ BẢN MỚI: ak {current} → {latest} ({channel}). Helper đã focus sẵn action cập nhật.",
|
|
146
|
+
binaryUpToDate: "ĐÃ LÀ BẢN MỚI NHẤT: ak {version} trên channel {channel}.",
|
|
147
|
+
binaryUpdateStatusUnknown: "Không thể xác định trạng thái cập nhật binary (status: {status}).",
|
|
148
|
+
binaryUpdateCheckFailed: "Không thể kiểm tra cập nhật ak binary: {message}. Helper vẫn tiếp tục hoạt động.",
|
|
131
149
|
installAction: "Cài Kit",
|
|
132
150
|
updateAction: "Cập nhật Kit",
|
|
133
151
|
selfUpdateAction: "Cập nhật ak binary",
|
|
@@ -135,6 +153,7 @@ const messages = {
|
|
|
135
153
|
exportAction: "Export Kit (không cài vào runtime)",
|
|
136
154
|
doctorAction: "Chạy health check",
|
|
137
155
|
backToLanguage: "← Quay lại chọn ngôn ngữ",
|
|
156
|
+
escapeBack: "Esc: Quay lại",
|
|
138
157
|
chooseProject: "Chọn thư mục project",
|
|
139
158
|
currentProjectScope: "Dùng project hiện tại ({path})",
|
|
140
159
|
globalScope: "Dùng Kit ở scope user/global",
|
|
@@ -207,12 +226,17 @@ const messages = {
|
|
|
207
226
|
unregisteredProjects: "Project khác đã discover:",
|
|
208
227
|
otherInstalls: "Global scope:",
|
|
209
228
|
globalCandidate: "{kit} global scope ({runtimes})",
|
|
229
|
+
piProfileCandidate: "Engineer Kit — profile {runtime}: {profile}",
|
|
230
|
+
piProfileSkipped: "Pi Profile Manager bỏ qua {profile}: {reason}",
|
|
231
|
+
piProfileNoAgentKit: "chưa bật AgentKit",
|
|
232
|
+
piProfileNotSafe: "profile không được quản lý, không healthy hoặc đã drift",
|
|
233
|
+
piProfileDiscoveryUnavailable: "Không thể discover Pi profile: {message}",
|
|
210
234
|
projectCandidate: "{name} — {kit} ({runtime}) — {path}",
|
|
211
235
|
selectUpdateAll: "Chọn mục cần update (Space bật/tắt, Enter xác nhận)",
|
|
212
236
|
updateEverything: "Update mọi Kit đã detect: global + project ({count})",
|
|
213
237
|
updateAllProjects: "Chỉ update các project đã discover ({count})",
|
|
214
238
|
chooseUpdates: "Chọn từng target…",
|
|
215
|
-
backFromTargetSelection: "
|
|
239
|
+
backFromTargetSelection: "Esc: Quay lại",
|
|
216
240
|
confirmSelectedUpdates: "Xử lý thế nào với {count} target đã chọn?",
|
|
217
241
|
previewSelectedUpdates: "Tiếp tục preview ({count})",
|
|
218
242
|
noProjectsInRegistry: "Không tìm thấy registered project nào có thể update.",
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { extname, isAbsolute, resolve, win32 } from "node:path";
|
|
4
|
+
|
|
5
|
+
function validPath(value) {
|
|
6
|
+
return typeof value === "string" && (isAbsolute(value) || win32.isAbsolute(value));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function piProfileManagerInvocation(
|
|
10
|
+
configuredBinary = "pi-profile-manager",
|
|
11
|
+
{
|
|
12
|
+
platform = process.platform,
|
|
13
|
+
home = homedir(),
|
|
14
|
+
fileExists = existsSync,
|
|
15
|
+
nodeBinary = process.execPath,
|
|
16
|
+
} = {},
|
|
17
|
+
) {
|
|
18
|
+
if (platform !== "win32") return { binary: configuredBinary, prefixArgs: [] };
|
|
19
|
+
|
|
20
|
+
const extension = extname(configuredBinary).toLowerCase();
|
|
21
|
+
if ([".js", ".cjs", ".mjs"].includes(extension)) {
|
|
22
|
+
return { binary: nodeBinary, prefixArgs: [configuredBinary] };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const siblingModule = extension === ".cmd"
|
|
26
|
+
? configuredBinary.slice(0, -extension.length) + ".mjs"
|
|
27
|
+
: null;
|
|
28
|
+
const managedModule = (platform === "win32" ? win32.resolve : resolve)(
|
|
29
|
+
home,
|
|
30
|
+
"bin",
|
|
31
|
+
"pi-profile-manager.mjs",
|
|
32
|
+
);
|
|
33
|
+
const modulePath = siblingModule && fileExists(siblingModule)
|
|
34
|
+
? siblingModule
|
|
35
|
+
: (!configuredBinary.includes("/") && !configuredBinary.includes("\\") && fileExists(managedModule)
|
|
36
|
+
? managedModule
|
|
37
|
+
: null);
|
|
38
|
+
return modulePath
|
|
39
|
+
? { binary: nodeBinary, prefixArgs: [modulePath] }
|
|
40
|
+
: { binary: configuredBinary, prefixArgs: [] };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function parsePiProfileInventory(output) {
|
|
44
|
+
const value = JSON.parse(output);
|
|
45
|
+
if (value?.schemaVersion !== 1 || !Array.isArray(value.profiles)) {
|
|
46
|
+
throw new Error("unsupported pi-profile-manager profile inventory contract");
|
|
47
|
+
}
|
|
48
|
+
const ids = new Set();
|
|
49
|
+
return value.profiles.map((profile) => {
|
|
50
|
+
const sessionIsValid = profile?.runtime === "omp"
|
|
51
|
+
? profile.sessionDir === null
|
|
52
|
+
: validPath(profile?.sessionDir);
|
|
53
|
+
if (
|
|
54
|
+
!profile || typeof profile.id !== "string" || !profile.id.trim() ||
|
|
55
|
+
!["pi", "omp"].includes(profile.runtime) || !validPath(profile.agentDir) ||
|
|
56
|
+
!sessionIsValid || typeof profile.agentkitEnabled !== "boolean" ||
|
|
57
|
+
typeof profile.managed !== "boolean" || typeof profile.healthy !== "boolean"
|
|
58
|
+
) {
|
|
59
|
+
throw new Error("invalid pi-profile-manager profile inventory entry");
|
|
60
|
+
}
|
|
61
|
+
if (ids.has(profile.id)) {
|
|
62
|
+
throw new Error("duplicate pi-profile-manager profile inventory entry");
|
|
63
|
+
}
|
|
64
|
+
ids.add(profile.id);
|
|
65
|
+
return {
|
|
66
|
+
id: profile.id,
|
|
67
|
+
runtime: profile.runtime,
|
|
68
|
+
agentDir: profile.agentDir,
|
|
69
|
+
sessionDir: profile.sessionDir,
|
|
70
|
+
agentkitEnabled: profile.agentkitEnabled,
|
|
71
|
+
managed: profile.managed,
|
|
72
|
+
healthy: profile.healthy,
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function piProfileProcessOptions(profile) {
|
|
78
|
+
if (profile.runtime === "omp") {
|
|
79
|
+
return {
|
|
80
|
+
envOverrides: { AGENTKIT_OMP_HOME: profile.agentDir },
|
|
81
|
+
envUnset: ["PI_CODING_AGENT_DIR", "PI_CODING_AGENT_SESSION_DIR", "OMP_HOME"],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
envOverrides: {
|
|
86
|
+
PI_CODING_AGENT_DIR: profile.agentDir,
|
|
87
|
+
PI_CODING_AGENT_SESSION_DIR: profile.sessionDir,
|
|
88
|
+
},
|
|
89
|
+
envUnset: ["AGENTKIT_OMP_HOME", "OMP_HOME"],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function classifyPiProfiles(profiles) {
|
|
94
|
+
return {
|
|
95
|
+
updateable: profiles.filter((profile) => (
|
|
96
|
+
profile.agentkitEnabled && profile.managed && profile.healthy
|
|
97
|
+
)),
|
|
98
|
+
skipped: profiles.filter((profile) => !(
|
|
99
|
+
profile.agentkitEnabled && profile.managed && profile.healthy
|
|
100
|
+
)),
|
|
101
|
+
};
|
|
102
|
+
}
|
package/lib/prompts.mjs
CHANGED
|
@@ -60,11 +60,31 @@ export async function choose(message, choices, defaultIndex = 0) {
|
|
|
60
60
|
}));
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
async function escapeBack(prompt) {
|
|
64
|
+
let cancelKey = null;
|
|
65
|
+
const trackCancelKey = (chunk) => {
|
|
66
|
+
const input = String(chunk);
|
|
67
|
+
if (input === "\u001b") cancelKey = "escape";
|
|
68
|
+
if (input === "\u0003") cancelKey = "interrupt";
|
|
69
|
+
};
|
|
70
|
+
stdin.on("data", trackCancelKey);
|
|
71
|
+
try {
|
|
72
|
+
const value = await prompt();
|
|
73
|
+
if (!isCancel(value)) return value;
|
|
74
|
+
if (cancelKey === "escape") return BACK;
|
|
75
|
+
return unwrap(value);
|
|
76
|
+
} finally {
|
|
77
|
+
stdin.off("data", trackCancelKey);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function chooseWithBack(message, choices, defaultIndex = 0, backLabel = "Esc: Back") {
|
|
82
|
+
startSession();
|
|
83
|
+
return escapeBack(() => select({
|
|
84
|
+
message: highlightPrompt(`${message} · ${backLabel}`),
|
|
85
|
+
options: choices,
|
|
86
|
+
initialValue: choices[defaultIndex]?.value,
|
|
87
|
+
}));
|
|
68
88
|
}
|
|
69
89
|
|
|
70
90
|
export async function multiChoose(message, choices, initialValues = choices.map((choice) => choice.value)) {
|
|
@@ -81,13 +101,15 @@ export async function multiChooseWithBack(
|
|
|
81
101
|
message,
|
|
82
102
|
choices,
|
|
83
103
|
initialValues = choices.map((choice) => choice.value),
|
|
84
|
-
backLabel = "
|
|
104
|
+
backLabel = "Esc: Back",
|
|
85
105
|
) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
{
|
|
89
|
-
|
|
90
|
-
|
|
106
|
+
startSession();
|
|
107
|
+
return escapeBack(() => multiselect({
|
|
108
|
+
message: highlightPrompt(`${message} · ${backLabel}`),
|
|
109
|
+
options: choices,
|
|
110
|
+
initialValues,
|
|
111
|
+
required: true,
|
|
112
|
+
}));
|
|
91
113
|
}
|
|
92
114
|
|
|
93
115
|
export async function ask(message) {
|
package/lib/runner.mjs
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { extname } from "node:path";
|
|
3
|
+
|
|
4
|
+
export function spawnInvocation(binary, args, platform = process.platform) {
|
|
5
|
+
if (platform === "win32" && [".js", ".cjs", ".mjs"].includes(extname(binary).toLowerCase())) {
|
|
6
|
+
return { binary: process.execPath, args: [binary, ...args] };
|
|
7
|
+
}
|
|
8
|
+
return { binary, args };
|
|
9
|
+
}
|
|
2
10
|
|
|
3
11
|
function jsonErrorMessage(output) {
|
|
4
12
|
const candidates = [output, ...String(output).split("\n").reverse()];
|
|
@@ -56,13 +64,24 @@ export function requiresForceConsent(error) {
|
|
|
56
64
|
/--force/i.test(diagnostic);
|
|
57
65
|
}
|
|
58
66
|
|
|
59
|
-
|
|
67
|
+
function childEnv(envOverrides = {}, envUnset = []) {
|
|
68
|
+
const env = { ...process.env, ...envOverrides };
|
|
69
|
+
for (const name of envUnset) delete env[name];
|
|
70
|
+
return env;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function run(
|
|
74
|
+
binary,
|
|
75
|
+
args,
|
|
76
|
+
{ cwd = process.cwd(), stdio = "inherit", envOverrides = {}, envUnset = [] } = {},
|
|
77
|
+
) {
|
|
60
78
|
return new Promise((resolve, reject) => {
|
|
61
|
-
const
|
|
79
|
+
const invocation = spawnInvocation(binary, args);
|
|
80
|
+
const child = spawn(invocation.binary, invocation.args, {
|
|
62
81
|
cwd,
|
|
63
82
|
stdio,
|
|
64
83
|
shell: false,
|
|
65
|
-
env:
|
|
84
|
+
env: childEnv(envOverrides, envUnset),
|
|
66
85
|
});
|
|
67
86
|
child.once("error", (error) => {
|
|
68
87
|
error.command = { binary, args: [...args], cwd };
|
|
@@ -87,13 +106,18 @@ export function run(binary, args, { cwd = process.cwd(), stdio = "inherit" } = {
|
|
|
87
106
|
});
|
|
88
107
|
}
|
|
89
108
|
|
|
90
|
-
export function runCapture(
|
|
109
|
+
export function runCapture(
|
|
110
|
+
binary,
|
|
111
|
+
args,
|
|
112
|
+
{ cwd = process.cwd(), envOverrides = {}, envUnset = [] } = {},
|
|
113
|
+
) {
|
|
91
114
|
return new Promise((resolve, reject) => {
|
|
92
|
-
const
|
|
115
|
+
const invocation = spawnInvocation(binary, args);
|
|
116
|
+
const child = spawn(invocation.binary, invocation.args, {
|
|
93
117
|
cwd,
|
|
94
118
|
stdio: ["ignore", "pipe", "pipe"],
|
|
95
119
|
shell: false,
|
|
96
|
-
env:
|
|
120
|
+
env: childEnv(envOverrides, envUnset),
|
|
97
121
|
});
|
|
98
122
|
let stdout = "";
|
|
99
123
|
let stderr = "";
|