@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
package/lib/runner.mjs
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
function jsonErrorMessage(output) {
|
|
4
|
+
const candidates = [output, ...String(output).split("\n").reverse()];
|
|
5
|
+
for (const candidate of candidates) {
|
|
6
|
+
try {
|
|
7
|
+
const value = JSON.parse(candidate);
|
|
8
|
+
const messages = [
|
|
9
|
+
value?.error?.message,
|
|
10
|
+
typeof value?.error === "string" ? value.error : null,
|
|
11
|
+
value?.message,
|
|
12
|
+
value?.data?.error?.message,
|
|
13
|
+
typeof value?.data?.error === "string" ? value.data.error : null,
|
|
14
|
+
value?.data?.message,
|
|
15
|
+
value?.errors?.[0]?.message,
|
|
16
|
+
];
|
|
17
|
+
const message = messages.find((item) => typeof item === "string" && item.trim());
|
|
18
|
+
if (message) return message.trim();
|
|
19
|
+
} catch {
|
|
20
|
+
// A command may emit NDJSON mixed with ordinary diagnostic lines.
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function conciseErrorMessage(message) {
|
|
27
|
+
const concise = String(message)
|
|
28
|
+
.replace(/^Error:\s*/i, "")
|
|
29
|
+
.replace(/^(?:init|install|update):\s*/i, "")
|
|
30
|
+
.replace(/^lifecycle preflight:\s*/i, "")
|
|
31
|
+
.trim();
|
|
32
|
+
return concise.length > 160 ? `${concise.slice(0, 157)}…` : concise;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function summarizeCommandFailure(stdout, stderr, fallback = "Command failed") {
|
|
36
|
+
const jsonMessage = jsonErrorMessage(stderr.trim()) || jsonErrorMessage(stdout.trim());
|
|
37
|
+
if (jsonMessage) return conciseErrorMessage(jsonMessage);
|
|
38
|
+
const lines = `${stderr}\n${stdout}`
|
|
39
|
+
.replaceAll(/\u001B\[[0-?]*[ -/]*[@-~]/g, "")
|
|
40
|
+
.split("\n")
|
|
41
|
+
.map((line) => line.trim())
|
|
42
|
+
.filter(Boolean)
|
|
43
|
+
.filter((line) => !/^[╭╰].*[╮╯]$/.test(line))
|
|
44
|
+
.map((line) => line.replace(/^│\s?/, "").replace(/\s?│$/, "").trim())
|
|
45
|
+
.filter((line) => line !== "Command failed.")
|
|
46
|
+
.filter((line) => !/^→?\s*Re-run with --verbose/i.test(line));
|
|
47
|
+
const explicitError = lines.find((line) => /^Error:/i.test(line));
|
|
48
|
+
if (explicitError) return conciseErrorMessage(explicitError);
|
|
49
|
+
return lines.slice(0, 4).map(conciseErrorMessage).join("\n") || fallback;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function requiresForceConsent(error) {
|
|
53
|
+
if (error?.exitCode === 6) return true;
|
|
54
|
+
const diagnostic = `${error?.message || ""}\n${error?.stderr || ""}`;
|
|
55
|
+
return /target directory already exists|drifted files detected/i.test(diagnostic) &&
|
|
56
|
+
/--force/i.test(diagnostic);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function run(binary, args, { cwd = process.cwd(), stdio = "inherit" } = {}) {
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
const child = spawn(binary, args, {
|
|
62
|
+
cwd,
|
|
63
|
+
stdio,
|
|
64
|
+
shell: false,
|
|
65
|
+
env: process.env,
|
|
66
|
+
});
|
|
67
|
+
child.once("error", (error) => {
|
|
68
|
+
error.command = { binary, args: [...args], cwd };
|
|
69
|
+
reject(error);
|
|
70
|
+
});
|
|
71
|
+
child.once("exit", (code, signal) => {
|
|
72
|
+
if (signal) {
|
|
73
|
+
const error = new Error(`${binary} terminated by ${signal}`);
|
|
74
|
+
error.command = { binary, args: [...args], cwd };
|
|
75
|
+
reject(error);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (code !== 0) {
|
|
79
|
+
const error = new Error(`${binary} exited with status ${code}`);
|
|
80
|
+
error.exitCode = code;
|
|
81
|
+
error.command = { binary, args: [...args], cwd };
|
|
82
|
+
reject(error);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
resolve();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function runCapture(binary, args, { cwd = process.cwd() } = {}) {
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
const child = spawn(binary, args, {
|
|
93
|
+
cwd,
|
|
94
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
95
|
+
shell: false,
|
|
96
|
+
env: process.env,
|
|
97
|
+
});
|
|
98
|
+
let stdout = "";
|
|
99
|
+
let stderr = "";
|
|
100
|
+
child.stdout.on("data", (chunk) => { stdout += chunk; });
|
|
101
|
+
child.stderr.on("data", (chunk) => { stderr += chunk; });
|
|
102
|
+
child.once("error", (error) => {
|
|
103
|
+
error.command = { binary, args: [...args], cwd };
|
|
104
|
+
reject(error);
|
|
105
|
+
});
|
|
106
|
+
child.once("exit", (code, signal) => {
|
|
107
|
+
if (signal || code !== 0) {
|
|
108
|
+
const fallback = signal
|
|
109
|
+
? `${binary} terminated by ${signal}`
|
|
110
|
+
: `${binary} exited with status ${code}`;
|
|
111
|
+
const error = new Error(summarizeCommandFailure(stdout, stderr, fallback));
|
|
112
|
+
error.exitCode = code || 1;
|
|
113
|
+
error.command = { binary, args: [...args], cwd };
|
|
114
|
+
error.stdout = stdout.trim();
|
|
115
|
+
error.stderr = stderr.trim();
|
|
116
|
+
reject(error);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
resolve({ stdout: stdout.trim(), stderr: stderr.trim() });
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function runPipeline(
|
|
125
|
+
sourceBinary,
|
|
126
|
+
sourceArgs,
|
|
127
|
+
targetBinary,
|
|
128
|
+
targetArgs,
|
|
129
|
+
{ cwd = process.cwd(), targetEnv = {} } = {},
|
|
130
|
+
) {
|
|
131
|
+
return new Promise((resolve, reject) => {
|
|
132
|
+
const source = spawn(sourceBinary, sourceArgs, {
|
|
133
|
+
cwd,
|
|
134
|
+
stdio: ["ignore", "pipe", "inherit"],
|
|
135
|
+
shell: false,
|
|
136
|
+
env: process.env,
|
|
137
|
+
});
|
|
138
|
+
const target = spawn(targetBinary, targetArgs, {
|
|
139
|
+
cwd,
|
|
140
|
+
stdio: ["pipe", "inherit", "inherit"],
|
|
141
|
+
shell: false,
|
|
142
|
+
env: { ...process.env, ...targetEnv },
|
|
143
|
+
});
|
|
144
|
+
source.stdout.pipe(target.stdin);
|
|
145
|
+
const command = {
|
|
146
|
+
binary: sourceBinary,
|
|
147
|
+
args: [...sourceArgs],
|
|
148
|
+
cwd,
|
|
149
|
+
pipeline: { binary: targetBinary, args: [...targetArgs] },
|
|
150
|
+
};
|
|
151
|
+
let sourceDone = false;
|
|
152
|
+
let targetDone = false;
|
|
153
|
+
let settled = false;
|
|
154
|
+
|
|
155
|
+
function fail(message, exitCode = 1) {
|
|
156
|
+
if (settled) return;
|
|
157
|
+
settled = true;
|
|
158
|
+
source.kill();
|
|
159
|
+
target.kill();
|
|
160
|
+
const error = new Error(message);
|
|
161
|
+
error.exitCode = exitCode;
|
|
162
|
+
error.command = command;
|
|
163
|
+
reject(error);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function finish() {
|
|
167
|
+
if (!settled && sourceDone && targetDone) {
|
|
168
|
+
settled = true;
|
|
169
|
+
resolve();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
source.once("error", (error) => fail(error.message));
|
|
174
|
+
target.once("error", (error) => fail(error.message));
|
|
175
|
+
source.once("exit", (code, signal) => {
|
|
176
|
+
if (signal || code !== 0) {
|
|
177
|
+
fail(signal ? `${sourceBinary} terminated by ${signal}` : `${sourceBinary} exited with status ${code}`, code || 1);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
sourceDone = true;
|
|
181
|
+
finish();
|
|
182
|
+
});
|
|
183
|
+
target.once("exit", (code, signal) => {
|
|
184
|
+
if (signal || code !== 0) {
|
|
185
|
+
fail(signal ? `${targetBinary} terminated by ${signal}` : `${targetBinary} exited with status ${code}`, code || 1);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
targetDone = true;
|
|
189
|
+
finish();
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export async function ensureAk(binary) {
|
|
195
|
+
try {
|
|
196
|
+
return (await runCapture(binary, ["--version"])).stdout;
|
|
197
|
+
} catch (error) {
|
|
198
|
+
if (error.code !== "ENOENT") {
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
const lines = [
|
|
202
|
+
"The ak CLI is not available on PATH.",
|
|
203
|
+
"Install it from the official AgentKit endpoint, then open a new terminal:",
|
|
204
|
+
"",
|
|
205
|
+
];
|
|
206
|
+
if (process.platform === "win32") {
|
|
207
|
+
lines.push(" irm https://agentkit.best/install.ps1 | iex");
|
|
208
|
+
} else {
|
|
209
|
+
lines.push(" curl -fsSL https://agentkit.best/install.sh | sh");
|
|
210
|
+
}
|
|
211
|
+
throw new Error(lines.join("\n"));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const SEMVER = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
2
|
+
|
|
3
|
+
export function releaseChannelForVersion(output) {
|
|
4
|
+
const version = String(output || "").trim().replace(/^ak\s+/i, "");
|
|
5
|
+
const match = SEMVER.exec(version);
|
|
6
|
+
if (!match) return null;
|
|
7
|
+
return match[4] ? "beta" : "stable";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function comparePrerelease(left, right) {
|
|
11
|
+
if (left === right) return 0;
|
|
12
|
+
if (!left) return 1;
|
|
13
|
+
if (!right) return -1;
|
|
14
|
+
const leftParts = left.split(".");
|
|
15
|
+
const rightParts = right.split(".");
|
|
16
|
+
for (let index = 0; index < Math.max(leftParts.length, rightParts.length); index += 1) {
|
|
17
|
+
if (leftParts[index] === undefined) return -1;
|
|
18
|
+
if (rightParts[index] === undefined) return 1;
|
|
19
|
+
if (leftParts[index] === rightParts[index]) continue;
|
|
20
|
+
const leftNumber = /^\d+$/.test(leftParts[index]) ? Number(leftParts[index]) : null;
|
|
21
|
+
const rightNumber = /^\d+$/.test(rightParts[index]) ? Number(rightParts[index]) : null;
|
|
22
|
+
if (leftNumber !== null && rightNumber !== null) return Math.sign(leftNumber - rightNumber);
|
|
23
|
+
if (leftNumber !== null) return -1;
|
|
24
|
+
if (rightNumber !== null) return 1;
|
|
25
|
+
return leftParts[index] < rightParts[index] ? -1 : 1;
|
|
26
|
+
}
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function compareVersions(left, right) {
|
|
31
|
+
const leftMatch = SEMVER.exec(left);
|
|
32
|
+
const rightMatch = SEMVER.exec(right);
|
|
33
|
+
if (!leftMatch || !rightMatch) throw new Error("invalid self-update version contract");
|
|
34
|
+
for (let index = 1; index <= 3; index += 1) {
|
|
35
|
+
const comparison = Number(leftMatch[index]) - Number(rightMatch[index]);
|
|
36
|
+
if (comparison !== 0) return Math.sign(comparison);
|
|
37
|
+
}
|
|
38
|
+
return comparePrerelease(leftMatch[4] || "", rightMatch[4] || "");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function parseSelfUpdateOutput(output) {
|
|
42
|
+
const value = JSON.parse(output);
|
|
43
|
+
const data = value?.data;
|
|
44
|
+
if (
|
|
45
|
+
value?.schema_version !== 1 || value?.kind !== "self_update" ||
|
|
46
|
+
typeof data?.current_version !== "string" || typeof data?.latest_version !== "string" ||
|
|
47
|
+
typeof data?.available !== "boolean" || typeof data?.status !== "string"
|
|
48
|
+
) {
|
|
49
|
+
throw new Error("unsupported ak self-update JSON contract");
|
|
50
|
+
}
|
|
51
|
+
return data;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function classifySelfUpdate(result) {
|
|
55
|
+
if (result.available) return "update";
|
|
56
|
+
if (result.status !== "current") return "unavailable";
|
|
57
|
+
const comparison = compareVersions(result.latest_version, result.current_version);
|
|
58
|
+
if (comparison < 0) return "downgrade";
|
|
59
|
+
return comparison === 0 ? "current" : "unavailable";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function assertInstallerVersion(version) {
|
|
63
|
+
if (!SEMVER.test(version)) throw new Error("refusing invalid installer version");
|
|
64
|
+
return version.replace(/^v/, "");
|
|
65
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@thieung/agentkit-helper",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Community helper for installing and updating AgentKit kits by runtime and channel",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"agentkit-helper": "bin/agentkit-helper.mjs",
|
|
8
|
+
"akh": "bin/agentkit-helper.mjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin/",
|
|
12
|
+
"lib/",
|
|
13
|
+
"assets/",
|
|
14
|
+
"README.md",
|
|
15
|
+
"README.vi.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"test": "node --test --test-concurrency=1 test/*.test.mjs",
|
|
20
|
+
"check": "node --check bin/agentkit-helper.mjs && node --check lib/*.mjs"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=20.12"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"agentkit",
|
|
27
|
+
"coding-agent",
|
|
28
|
+
"claude-code",
|
|
29
|
+
"codex",
|
|
30
|
+
"cursor"
|
|
31
|
+
],
|
|
32
|
+
"author": "Thieu Nguyen",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/thieung/agentkit-helper.git"
|
|
37
|
+
},
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/thieung/agentkit-helper/issues"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/thieung/agentkit-helper#readme",
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@clack/prompts": "^1.7.0"
|
|
47
|
+
}
|
|
48
|
+
}
|