akm-cli 0.0.0 → 0.0.17
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 +385 -0
- package/README.md +249 -6
- package/dist/asset-spec.js +70 -0
- package/dist/cli.js +934 -0
- package/dist/common.js +192 -0
- package/dist/config-cli.js +233 -0
- package/dist/config.js +338 -0
- package/dist/db.js +371 -0
- package/dist/embedder.js +150 -0
- package/dist/errors.js +28 -0
- package/dist/file-context.js +162 -0
- package/dist/frontmatter.js +86 -0
- package/dist/github.js +17 -0
- package/dist/indexer.js +311 -0
- package/dist/init.js +43 -0
- package/dist/llm.js +87 -0
- package/dist/lockfile.js +60 -0
- package/dist/markdown.js +77 -0
- package/dist/matchers.js +159 -0
- package/dist/metadata.js +408 -0
- package/dist/origin-resolve.js +54 -0
- package/dist/paths.js +92 -0
- package/dist/registry-install.js +459 -0
- package/dist/registry-resolve.js +486 -0
- package/dist/registry-search.js +365 -0
- package/dist/registry-types.js +1 -0
- package/dist/renderers.js +386 -0
- package/dist/ripgrep-install.js +155 -0
- package/dist/ripgrep-resolve.js +78 -0
- package/dist/ripgrep.js +2 -0
- package/dist/self-update.js +226 -0
- package/dist/stash-add.js +71 -0
- package/dist/stash-clone.js +115 -0
- package/dist/stash-ref.js +73 -0
- package/dist/stash-registry.js +206 -0
- package/dist/stash-resolve.js +55 -0
- package/dist/stash-search.js +490 -0
- package/dist/stash-show.js +58 -0
- package/dist/stash-source.js +130 -0
- package/dist/stash-types.js +1 -0
- package/dist/walker.js +163 -0
- package/dist/warn.js +20 -0
- package/package.json +53 -7
- package/index.js +0 -4
package/dist/cli.js
ADDED
|
@@ -0,0 +1,934 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { defineCommand, runMain } from "citty";
|
|
5
|
+
import { resolveStashDir } from "./common";
|
|
6
|
+
import { getConfigPath, loadConfig, saveConfig } from "./config";
|
|
7
|
+
import { getConfigValue, listConfig, setConfigValue, unsetConfigValue } from "./config-cli";
|
|
8
|
+
import { ConfigError, NotFoundError, UsageError } from "./errors";
|
|
9
|
+
import { agentikitIndex } from "./indexer";
|
|
10
|
+
import { agentikitInit } from "./init";
|
|
11
|
+
import { getCacheDir, getDbPath, getDefaultStashDir } from "./paths";
|
|
12
|
+
import { searchRegistry } from "./registry-search";
|
|
13
|
+
import { checkForUpdate, performUpgrade } from "./self-update";
|
|
14
|
+
import { agentikitAdd } from "./stash-add";
|
|
15
|
+
import { agentikitClone } from "./stash-clone";
|
|
16
|
+
import { agentikitList, agentikitRemove, agentikitUpdate } from "./stash-registry";
|
|
17
|
+
import { agentikitSearch } from "./stash-search";
|
|
18
|
+
import { agentikitShow } from "./stash-show";
|
|
19
|
+
import { resolveStashSources } from "./stash-source";
|
|
20
|
+
import { setQuiet, warn } from "./warn";
|
|
21
|
+
// Version: prefer compile-time define, then package.json, then fallback
|
|
22
|
+
const pkgVersion = (() => {
|
|
23
|
+
// Injected at compile time via `bun build --define`
|
|
24
|
+
if (typeof AKM_VERSION !== "undefined")
|
|
25
|
+
return AKM_VERSION;
|
|
26
|
+
try {
|
|
27
|
+
const pkgPath = path.resolve(import.meta.dir ?? __dirname, "../package.json");
|
|
28
|
+
if (fs.existsSync(pkgPath)) {
|
|
29
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
30
|
+
if (typeof pkg.version === "string")
|
|
31
|
+
return pkg.version;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// swallow — running as compiled binary without package.json
|
|
36
|
+
}
|
|
37
|
+
return "0.0.0-dev";
|
|
38
|
+
})();
|
|
39
|
+
const OUTPUT_FORMATS = ["json", "yaml", "text"];
|
|
40
|
+
const DETAIL_LEVELS = ["brief", "normal", "full"];
|
|
41
|
+
const BRIEF_DESCRIPTION_LIMIT = 160;
|
|
42
|
+
function hasBunYAML(b) {
|
|
43
|
+
// biome-ignore lint/suspicious/noExplicitAny: type guard for runtime feature detection
|
|
44
|
+
return typeof b.YAML?.stringify === "function";
|
|
45
|
+
}
|
|
46
|
+
/** Try Bun.YAML.stringify; fall back to JSON if the API is unavailable */
|
|
47
|
+
function yamlStringify(obj) {
|
|
48
|
+
if (hasBunYAML(Bun)) {
|
|
49
|
+
return Bun.YAML.stringify(obj);
|
|
50
|
+
}
|
|
51
|
+
warn("YAML output not available, using JSON");
|
|
52
|
+
return JSON.stringify(obj, null, 2);
|
|
53
|
+
}
|
|
54
|
+
function parseOutputFormat(value) {
|
|
55
|
+
if (!value)
|
|
56
|
+
return undefined;
|
|
57
|
+
if (OUTPUT_FORMATS.includes(value))
|
|
58
|
+
return value;
|
|
59
|
+
throw new UsageError(`Invalid value for --format: ${value}. Expected one of: ${OUTPUT_FORMATS.join("|")}`);
|
|
60
|
+
}
|
|
61
|
+
function parseDetailLevel(value) {
|
|
62
|
+
if (!value)
|
|
63
|
+
return undefined;
|
|
64
|
+
if (DETAIL_LEVELS.includes(value))
|
|
65
|
+
return value;
|
|
66
|
+
throw new UsageError(`Invalid value for --detail: ${value}. Expected one of: ${DETAIL_LEVELS.join("|")}`);
|
|
67
|
+
}
|
|
68
|
+
function parseFlagValue(flag) {
|
|
69
|
+
for (let i = 0; i < process.argv.length; i++) {
|
|
70
|
+
const arg = process.argv[i];
|
|
71
|
+
if (arg === flag)
|
|
72
|
+
return process.argv[i + 1];
|
|
73
|
+
if (arg.startsWith(`${flag}=`))
|
|
74
|
+
return arg.slice(flag.length + 1);
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
function resolveOutputMode() {
|
|
79
|
+
const config = loadConfig();
|
|
80
|
+
const format = parseOutputFormat(parseFlagValue("--format")) ?? config.output?.format ?? "json";
|
|
81
|
+
const detail = parseDetailLevel(parseFlagValue("--detail")) ?? config.output?.detail ?? "brief";
|
|
82
|
+
return { format, detail };
|
|
83
|
+
}
|
|
84
|
+
function output(command, result) {
|
|
85
|
+
const mode = resolveOutputMode();
|
|
86
|
+
const shaped = shapeForCommand(command, result, mode.detail);
|
|
87
|
+
switch (mode.format) {
|
|
88
|
+
case "json":
|
|
89
|
+
console.log(JSON.stringify(shaped, null, 2));
|
|
90
|
+
return;
|
|
91
|
+
case "yaml":
|
|
92
|
+
console.log(yamlStringify(shaped));
|
|
93
|
+
return;
|
|
94
|
+
case "text": {
|
|
95
|
+
const plain = formatPlain(command, shaped, mode.detail);
|
|
96
|
+
console.log(plain ?? JSON.stringify(shaped, null, 2));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function shapeForCommand(command, result, detail) {
|
|
102
|
+
switch (command) {
|
|
103
|
+
case "search":
|
|
104
|
+
return shapeSearchOutput(result, detail);
|
|
105
|
+
case "registry-search":
|
|
106
|
+
return shapeRegistrySearchOutput(result, detail);
|
|
107
|
+
case "show":
|
|
108
|
+
return shapeShowOutput(result, detail);
|
|
109
|
+
default:
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function shapeSearchOutput(result, detail) {
|
|
114
|
+
const hits = Array.isArray(result.hits) ? result.hits : [];
|
|
115
|
+
const shapedHits = hits.map((hit) => shapeSearchHit(hit, detail));
|
|
116
|
+
if (detail === "full") {
|
|
117
|
+
return {
|
|
118
|
+
schemaVersion: result.schemaVersion,
|
|
119
|
+
stashDir: result.stashDir,
|
|
120
|
+
source: result.source,
|
|
121
|
+
hits: shapedHits,
|
|
122
|
+
...(result.tip ? { tip: result.tip } : {}),
|
|
123
|
+
...(result.warnings ? { warnings: result.warnings } : {}),
|
|
124
|
+
...(result.timing ? { timing: result.timing } : {}),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
hits: shapedHits,
|
|
129
|
+
...(result.tip ? { tip: result.tip } : {}),
|
|
130
|
+
...(Array.isArray(result.warnings) && result.warnings.length > 0 ? { warnings: result.warnings } : {}),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function shapeRegistrySearchOutput(result, detail) {
|
|
134
|
+
const hits = Array.isArray(result.hits) ? result.hits : [];
|
|
135
|
+
const assetHits = Array.isArray(result.assetHits) ? result.assetHits : [];
|
|
136
|
+
// Shape kit hits as registry type
|
|
137
|
+
const shapedKitHits = hits.map((hit) => shapeSearchHit({ ...hit, type: "registry" }, detail));
|
|
138
|
+
const shapedAssetHits = assetHits.map((hit) => shapeSearchHit(hit, detail));
|
|
139
|
+
const shaped = {
|
|
140
|
+
hits: shapedKitHits,
|
|
141
|
+
...(shapedAssetHits.length > 0 ? { assetHits: shapedAssetHits } : {}),
|
|
142
|
+
...(Array.isArray(result.warnings) && result.warnings.length > 0 ? { warnings: result.warnings } : {}),
|
|
143
|
+
};
|
|
144
|
+
if (detail === "full") {
|
|
145
|
+
shaped.query = result.query;
|
|
146
|
+
}
|
|
147
|
+
return shaped;
|
|
148
|
+
}
|
|
149
|
+
function shapeSearchHit(hit, detail) {
|
|
150
|
+
// Keep local and registry hit models separate internally so search and
|
|
151
|
+
// ranking logic can carry source-specific metadata. Normalize the external
|
|
152
|
+
// contract here so default CLI output stays compact and consistent.
|
|
153
|
+
if (hit.type === "registry") {
|
|
154
|
+
const brief = withTruncatedDescription(pickFields(hit, ["type", "name", "id", "description", "action", "curated"]));
|
|
155
|
+
if (detail === "brief")
|
|
156
|
+
return brief;
|
|
157
|
+
if (detail === "normal")
|
|
158
|
+
return pickFields(hit, ["type", "name", "id", "description", "tags", "action", "curated"]);
|
|
159
|
+
return hit;
|
|
160
|
+
}
|
|
161
|
+
if (hit.type === "registry-asset") {
|
|
162
|
+
const brief = withTruncatedDescription(pickFields(hit, ["type", "assetType", "assetName", "description", "kit", "action"]));
|
|
163
|
+
if (detail === "brief")
|
|
164
|
+
return brief;
|
|
165
|
+
if (detail === "normal") {
|
|
166
|
+
return pickFields(hit, ["type", "assetType", "assetName", "description", "kit", "registryName", "action"]);
|
|
167
|
+
}
|
|
168
|
+
return hit;
|
|
169
|
+
}
|
|
170
|
+
const brief = withTruncatedDescription(pickFields(hit, ["type", "name", "description", "action"]));
|
|
171
|
+
if (detail === "brief")
|
|
172
|
+
return brief;
|
|
173
|
+
if (detail === "normal") {
|
|
174
|
+
return pickFields(hit, ["type", "name", "ref", "origin", "description", "tags", "size", "action", "run"]);
|
|
175
|
+
}
|
|
176
|
+
return hit;
|
|
177
|
+
}
|
|
178
|
+
function withTruncatedDescription(hit) {
|
|
179
|
+
if (typeof hit.description !== "string")
|
|
180
|
+
return hit;
|
|
181
|
+
return {
|
|
182
|
+
...hit,
|
|
183
|
+
description: truncateDescription(hit.description, BRIEF_DESCRIPTION_LIMIT),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function truncateDescription(description, limit) {
|
|
187
|
+
const normalized = description.replace(/\s+/g, " ").trim();
|
|
188
|
+
if (normalized.length <= limit)
|
|
189
|
+
return normalized;
|
|
190
|
+
const truncated = normalized.slice(0, limit - 1);
|
|
191
|
+
const lastSpace = truncated.lastIndexOf(" ");
|
|
192
|
+
const safe = lastSpace >= Math.floor(limit * 0.6) ? truncated.slice(0, lastSpace) : truncated;
|
|
193
|
+
return `${safe.trimEnd()}...`;
|
|
194
|
+
}
|
|
195
|
+
function shapeShowOutput(result, detail) {
|
|
196
|
+
const base = pickFields(result, [
|
|
197
|
+
"type",
|
|
198
|
+
"name",
|
|
199
|
+
"origin",
|
|
200
|
+
"action",
|
|
201
|
+
"description",
|
|
202
|
+
"content",
|
|
203
|
+
"template",
|
|
204
|
+
"prompt",
|
|
205
|
+
"toolPolicy",
|
|
206
|
+
"modelHint",
|
|
207
|
+
"agent",
|
|
208
|
+
"parameters",
|
|
209
|
+
"run",
|
|
210
|
+
"setup",
|
|
211
|
+
"cwd",
|
|
212
|
+
]);
|
|
213
|
+
if (detail !== "full") {
|
|
214
|
+
return base;
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
schemaVersion: 1,
|
|
218
|
+
...base,
|
|
219
|
+
...pickFields(result, ["path", "editable", "editHint"]),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function pickFields(source, fields) {
|
|
223
|
+
const result = {};
|
|
224
|
+
for (const field of fields) {
|
|
225
|
+
if (source[field] !== undefined) {
|
|
226
|
+
result[field] = source[field];
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Return a plain-text string for commands that are better as short messages,
|
|
233
|
+
* or null to fall through to YAML output.
|
|
234
|
+
*/
|
|
235
|
+
function formatPlain(command, result, detail) {
|
|
236
|
+
const r = result;
|
|
237
|
+
switch (command) {
|
|
238
|
+
case "init": {
|
|
239
|
+
let out = `Stash initialized at ${r.stashDir ?? "unknown"}`;
|
|
240
|
+
if (r.configPath)
|
|
241
|
+
out += `\nConfig saved to ${r.configPath}`;
|
|
242
|
+
return out;
|
|
243
|
+
}
|
|
244
|
+
case "index": {
|
|
245
|
+
return `Indexed ${r.totalEntries ?? 0} entries from ${r.directoriesScanned ?? 0} directories (mode: ${r.mode ?? "unknown"})`;
|
|
246
|
+
}
|
|
247
|
+
case "show": {
|
|
248
|
+
const lines = [];
|
|
249
|
+
if (r.type || r.name) {
|
|
250
|
+
lines.push(`# ${String(r.type ?? "asset")}: ${String(r.name ?? "unknown")}`);
|
|
251
|
+
}
|
|
252
|
+
if (r.origin !== undefined)
|
|
253
|
+
lines.push(`# origin: ${String(r.origin)}`);
|
|
254
|
+
if (r.action)
|
|
255
|
+
lines.push(`# ${String(r.action)}`);
|
|
256
|
+
if (r.description)
|
|
257
|
+
lines.push(`description: ${String(r.description)}`);
|
|
258
|
+
if (r.agent)
|
|
259
|
+
lines.push(`agent: ${String(r.agent)}`);
|
|
260
|
+
if (Array.isArray(r.parameters) && r.parameters.length > 0)
|
|
261
|
+
lines.push(`parameters: ${r.parameters.join(", ")}`);
|
|
262
|
+
if (r.modelHint != null)
|
|
263
|
+
lines.push(`modelHint: ${String(r.modelHint)}`);
|
|
264
|
+
if (r.toolPolicy != null)
|
|
265
|
+
lines.push(`toolPolicy: ${JSON.stringify(r.toolPolicy)}`);
|
|
266
|
+
if (r.run)
|
|
267
|
+
lines.push(`run: ${String(r.run)}`);
|
|
268
|
+
if (r.setup)
|
|
269
|
+
lines.push(`setup: ${String(r.setup)}`);
|
|
270
|
+
if (r.cwd)
|
|
271
|
+
lines.push(`cwd: ${String(r.cwd)}`);
|
|
272
|
+
if (detail === "full") {
|
|
273
|
+
if (r.path)
|
|
274
|
+
lines.push(`path: ${String(r.path)}`);
|
|
275
|
+
if (r.editable !== undefined)
|
|
276
|
+
lines.push(`editable: ${String(r.editable)}`);
|
|
277
|
+
if (r.editHint)
|
|
278
|
+
lines.push(`editHint: ${String(r.editHint)}`);
|
|
279
|
+
if (r.schemaVersion !== undefined)
|
|
280
|
+
lines.push(`schemaVersion: ${String(r.schemaVersion)}`);
|
|
281
|
+
}
|
|
282
|
+
const payloads = [r.content, r.template, r.prompt].filter((value) => value != null).map(String);
|
|
283
|
+
if (payloads.length > 0) {
|
|
284
|
+
if (lines.length > 0)
|
|
285
|
+
lines.push("");
|
|
286
|
+
lines.push(...payloads);
|
|
287
|
+
}
|
|
288
|
+
return lines.length > 0 ? lines.join("\n") : null;
|
|
289
|
+
}
|
|
290
|
+
case "search": {
|
|
291
|
+
return formatSearchPlain(r, detail);
|
|
292
|
+
}
|
|
293
|
+
case "add": {
|
|
294
|
+
const index = r.index;
|
|
295
|
+
const scanned = index?.directoriesScanned ?? 0;
|
|
296
|
+
const total = index?.totalEntries ?? 0;
|
|
297
|
+
return `Installed ${r.ref} (${scanned} directories scanned, ${total} total assets indexed)`;
|
|
298
|
+
}
|
|
299
|
+
case "remove": {
|
|
300
|
+
const target = r.target ?? r.ref ?? "";
|
|
301
|
+
const ok = r.ok !== false ? "OK" : "FAILED";
|
|
302
|
+
return `remove: ${target} ${ok}`;
|
|
303
|
+
}
|
|
304
|
+
case "update": {
|
|
305
|
+
const processed = r.processed;
|
|
306
|
+
if (!processed?.length)
|
|
307
|
+
return `update: nothing to update`;
|
|
308
|
+
const lines = processed.map((item) => {
|
|
309
|
+
const changed = item.changed;
|
|
310
|
+
const installed = item.installed;
|
|
311
|
+
const previous = item.previous;
|
|
312
|
+
if (changed?.any) {
|
|
313
|
+
const prev = previous?.resolvedVersion ?? "unknown";
|
|
314
|
+
const next = installed?.resolvedVersion ?? "unknown";
|
|
315
|
+
return `update: ${item.id} v${prev} → v${next}`;
|
|
316
|
+
}
|
|
317
|
+
return `update: ${item.id} (unchanged)`;
|
|
318
|
+
});
|
|
319
|
+
return lines.join("\n");
|
|
320
|
+
}
|
|
321
|
+
case "upgrade": {
|
|
322
|
+
if (r.upgraded === true) {
|
|
323
|
+
return `akm upgraded: v${r.currentVersion} → v${r.newVersion}`;
|
|
324
|
+
}
|
|
325
|
+
if (r.updateAvailable === true) {
|
|
326
|
+
return `akm v${r.currentVersion} → v${r.latestVersion} available (run 'akm upgrade' to install)`;
|
|
327
|
+
}
|
|
328
|
+
if (r.updateAvailable === false && r.latestVersion) {
|
|
329
|
+
return `akm v${r.currentVersion} is already the latest version`;
|
|
330
|
+
}
|
|
331
|
+
if (r.message)
|
|
332
|
+
return String(r.message);
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
case "clone": {
|
|
336
|
+
const dst = r.destination?.path ?? "unknown";
|
|
337
|
+
const remote = r.remoteFetched ? " (fetched from remote)" : "";
|
|
338
|
+
const over = r.overwritten ? " (overwritten)" : "";
|
|
339
|
+
return `Cloned${remote} → ${dst}${over}`;
|
|
340
|
+
}
|
|
341
|
+
default:
|
|
342
|
+
return null; // fall through to YAML
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function formatSearchPlain(r, detail) {
|
|
346
|
+
const hits = r.hits ?? [];
|
|
347
|
+
if (hits.length === 0) {
|
|
348
|
+
return r.tip ? String(r.tip) : "No results found.";
|
|
349
|
+
}
|
|
350
|
+
const lines = [];
|
|
351
|
+
for (const hit of hits) {
|
|
352
|
+
const type = hit.type ?? "unknown";
|
|
353
|
+
const name = hit.name ?? "unnamed";
|
|
354
|
+
const score = hit.score != null ? ` (score: ${hit.score})` : "";
|
|
355
|
+
const desc = hit.description ? ` ${hit.description}` : "";
|
|
356
|
+
lines.push(`${type}: ${name}${score}`);
|
|
357
|
+
if (desc)
|
|
358
|
+
lines.push(desc);
|
|
359
|
+
if (hit.id)
|
|
360
|
+
lines.push(` id: ${String(hit.id)}`);
|
|
361
|
+
if (hit.ref)
|
|
362
|
+
lines.push(` ref: ${String(hit.ref)}`);
|
|
363
|
+
if (hit.origin !== undefined)
|
|
364
|
+
lines.push(` origin: ${String(hit.origin)}`);
|
|
365
|
+
if (hit.size)
|
|
366
|
+
lines.push(` size: ${String(hit.size)}`);
|
|
367
|
+
if (hit.action)
|
|
368
|
+
lines.push(` action: ${String(hit.action)}`);
|
|
369
|
+
if (hit.run)
|
|
370
|
+
lines.push(` run: ${String(hit.run)}`);
|
|
371
|
+
if (Array.isArray(hit.tags) && hit.tags.length > 0)
|
|
372
|
+
lines.push(` tags: ${hit.tags.join(", ")}`);
|
|
373
|
+
if (hit.curated !== undefined)
|
|
374
|
+
lines.push(` curated: ${String(hit.curated)}`);
|
|
375
|
+
if (detail === "full") {
|
|
376
|
+
if (hit.path)
|
|
377
|
+
lines.push(` path: ${String(hit.path)}`);
|
|
378
|
+
if (hit.editable != null)
|
|
379
|
+
lines.push(` editable: ${String(hit.editable)}`);
|
|
380
|
+
if (hit.editHint)
|
|
381
|
+
lines.push(` editHint: ${String(hit.editHint)}`);
|
|
382
|
+
const whyMatched = hit.whyMatched;
|
|
383
|
+
if (whyMatched && whyMatched.length > 0) {
|
|
384
|
+
lines.push(` whyMatched: ${whyMatched.join(", ")}`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
lines.push(""); // blank line between hits
|
|
388
|
+
}
|
|
389
|
+
if (detail === "full" && r.timing) {
|
|
390
|
+
const timing = r.timing;
|
|
391
|
+
const parts = [];
|
|
392
|
+
if (timing.totalMs != null)
|
|
393
|
+
parts.push(`total: ${timing.totalMs}ms`);
|
|
394
|
+
if (timing.rankMs != null)
|
|
395
|
+
parts.push(`rank: ${timing.rankMs}ms`);
|
|
396
|
+
if (timing.embedMs != null)
|
|
397
|
+
parts.push(`embed: ${timing.embedMs}ms`);
|
|
398
|
+
if (parts.length > 0)
|
|
399
|
+
lines.push(`timing: ${parts.join(", ")}`);
|
|
400
|
+
}
|
|
401
|
+
return lines.join("\n").trimEnd();
|
|
402
|
+
}
|
|
403
|
+
const initCommand = defineCommand({
|
|
404
|
+
meta: {
|
|
405
|
+
name: "init",
|
|
406
|
+
description: "Initialize Agent-i-Kit's working stash directory and persist stashDir in config",
|
|
407
|
+
},
|
|
408
|
+
args: {
|
|
409
|
+
dir: { type: "string", description: "Custom stash directory path (default: ~/akm)" },
|
|
410
|
+
},
|
|
411
|
+
async run({ args }) {
|
|
412
|
+
await runWithJsonErrors(async () => {
|
|
413
|
+
const result = await agentikitInit({ dir: args.dir });
|
|
414
|
+
output("init", result);
|
|
415
|
+
});
|
|
416
|
+
},
|
|
417
|
+
});
|
|
418
|
+
const indexCommand = defineCommand({
|
|
419
|
+
meta: { name: "index", description: "Build search index (incremental by default; --full forces full reindex)" },
|
|
420
|
+
args: {
|
|
421
|
+
full: { type: "boolean", description: "Force full reindex", default: false },
|
|
422
|
+
},
|
|
423
|
+
async run({ args }) {
|
|
424
|
+
await runWithJsonErrors(async () => {
|
|
425
|
+
const result = await agentikitIndex({ full: args.full });
|
|
426
|
+
output("index", result);
|
|
427
|
+
});
|
|
428
|
+
},
|
|
429
|
+
});
|
|
430
|
+
const searchCommand = defineCommand({
|
|
431
|
+
meta: { name: "search", description: "Search the stash" },
|
|
432
|
+
args: {
|
|
433
|
+
query: { type: "positional", description: "Search query (omit to list all assets)", required: false, default: "" },
|
|
434
|
+
type: {
|
|
435
|
+
type: "string",
|
|
436
|
+
description: "Asset type filter (skill|command|agent|knowledge|script|any).",
|
|
437
|
+
},
|
|
438
|
+
limit: { type: "string", description: "Maximum number of results" },
|
|
439
|
+
source: { type: "string", description: "Search source (local|registry|both)", default: "local" },
|
|
440
|
+
format: { type: "string", description: "Output format (json|text|yaml)" },
|
|
441
|
+
detail: { type: "string", description: "Detail level (brief|normal|full)" },
|
|
442
|
+
},
|
|
443
|
+
async run({ args }) {
|
|
444
|
+
await runWithJsonErrors(async () => {
|
|
445
|
+
const type = args.type;
|
|
446
|
+
const limit = args.limit ? parseInt(args.limit, 10) : undefined;
|
|
447
|
+
const source = parseSearchSource(args.source);
|
|
448
|
+
const result = await agentikitSearch({ query: args.query, type, limit, source });
|
|
449
|
+
output("search", result);
|
|
450
|
+
});
|
|
451
|
+
},
|
|
452
|
+
});
|
|
453
|
+
const addCommand = defineCommand({
|
|
454
|
+
meta: { name: "add", description: "Install a kit from npm, GitHub, any git host, or a local directory" },
|
|
455
|
+
args: {
|
|
456
|
+
ref: {
|
|
457
|
+
type: "positional",
|
|
458
|
+
description: "Registry ref (npm package, owner/repo, git URL, or local directory)",
|
|
459
|
+
required: true,
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
async run({ args }) {
|
|
463
|
+
await runWithJsonErrors(async () => {
|
|
464
|
+
const result = await agentikitAdd({ ref: args.ref });
|
|
465
|
+
output("add", result);
|
|
466
|
+
});
|
|
467
|
+
},
|
|
468
|
+
});
|
|
469
|
+
const listCommand = defineCommand({
|
|
470
|
+
meta: { name: "list", description: "List installed kits" },
|
|
471
|
+
async run() {
|
|
472
|
+
await runWithJsonErrors(async () => {
|
|
473
|
+
const result = await agentikitList();
|
|
474
|
+
output("list", result);
|
|
475
|
+
});
|
|
476
|
+
},
|
|
477
|
+
});
|
|
478
|
+
const removeCommand = defineCommand({
|
|
479
|
+
meta: { name: "remove", description: "Remove an installed kit by id or ref" },
|
|
480
|
+
args: {
|
|
481
|
+
target: { type: "positional", description: "Installed target (id or ref)", required: true },
|
|
482
|
+
},
|
|
483
|
+
async run({ args }) {
|
|
484
|
+
await runWithJsonErrors(async () => {
|
|
485
|
+
const result = await agentikitRemove({ target: args.target });
|
|
486
|
+
output("remove", result);
|
|
487
|
+
});
|
|
488
|
+
},
|
|
489
|
+
});
|
|
490
|
+
const updateCommand = defineCommand({
|
|
491
|
+
meta: { name: "update", description: "Update one or all installed kits" },
|
|
492
|
+
args: {
|
|
493
|
+
target: { type: "positional", description: "Installed target (id or ref)", required: false },
|
|
494
|
+
all: { type: "boolean", description: "Update all installed entries", default: false },
|
|
495
|
+
force: { type: "boolean", description: "Force fresh download even if version is unchanged", default: false },
|
|
496
|
+
},
|
|
497
|
+
async run({ args }) {
|
|
498
|
+
await runWithJsonErrors(async () => {
|
|
499
|
+
const result = await agentikitUpdate({ target: args.target, all: args.all, force: args.force });
|
|
500
|
+
output("update", result);
|
|
501
|
+
});
|
|
502
|
+
},
|
|
503
|
+
});
|
|
504
|
+
const upgradeCommand = defineCommand({
|
|
505
|
+
meta: { name: "upgrade", description: "Upgrade akm to the latest release" },
|
|
506
|
+
args: {
|
|
507
|
+
check: { type: "boolean", description: "Check for updates without installing", default: false },
|
|
508
|
+
force: { type: "boolean", description: "Force upgrade even if on latest", default: false },
|
|
509
|
+
},
|
|
510
|
+
async run({ args }) {
|
|
511
|
+
await runWithJsonErrors(async () => {
|
|
512
|
+
const check = await checkForUpdate(pkgVersion);
|
|
513
|
+
if (args.check) {
|
|
514
|
+
output("upgrade", check);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
const result = await performUpgrade(check, { force: args.force });
|
|
518
|
+
output("upgrade", result);
|
|
519
|
+
});
|
|
520
|
+
},
|
|
521
|
+
});
|
|
522
|
+
const showCommand = defineCommand({
|
|
523
|
+
meta: {
|
|
524
|
+
name: "show",
|
|
525
|
+
description: "Show a stash asset by ref (e.g. akm show knowledge:guide.md toc, akm show knowledge:guide.md section 'Auth')",
|
|
526
|
+
},
|
|
527
|
+
args: {
|
|
528
|
+
ref: { type: "positional", description: "Asset ref (type:name)", required: true },
|
|
529
|
+
format: { type: "string", description: "Output format (json|text|yaml)" },
|
|
530
|
+
detail: { type: "string", description: "Detail level (brief|normal|full)" },
|
|
531
|
+
akmView: { type: "string", description: "Internal positional knowledge view mode parser" },
|
|
532
|
+
akmHeading: { type: "string", description: "Internal positional section heading parser" },
|
|
533
|
+
akmStart: { type: "string", description: "Internal positional start-line parser" },
|
|
534
|
+
akmEnd: { type: "string", description: "Internal positional end-line parser" },
|
|
535
|
+
},
|
|
536
|
+
async run({ args }) {
|
|
537
|
+
await runWithJsonErrors(async () => {
|
|
538
|
+
let view;
|
|
539
|
+
if (args.akmView) {
|
|
540
|
+
switch (args.akmView) {
|
|
541
|
+
case "section":
|
|
542
|
+
view = { mode: "section", heading: args.akmHeading ?? "" };
|
|
543
|
+
break;
|
|
544
|
+
case "lines":
|
|
545
|
+
view = {
|
|
546
|
+
mode: "lines",
|
|
547
|
+
start: Number(args.akmStart ?? "1"),
|
|
548
|
+
end: args.akmEnd ? parseInt(args.akmEnd, 10) : Number.MAX_SAFE_INTEGER,
|
|
549
|
+
};
|
|
550
|
+
break;
|
|
551
|
+
case "toc":
|
|
552
|
+
case "frontmatter":
|
|
553
|
+
case "full":
|
|
554
|
+
view = { mode: args.akmView };
|
|
555
|
+
break;
|
|
556
|
+
default:
|
|
557
|
+
throw new UsageError(`Unknown view mode: ${args.akmView}. Expected one of: full|toc|frontmatter|section|lines`);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
const result = await agentikitShow({ ref: args.ref, view });
|
|
561
|
+
output("show", result);
|
|
562
|
+
});
|
|
563
|
+
},
|
|
564
|
+
});
|
|
565
|
+
const configCommand = defineCommand({
|
|
566
|
+
meta: { name: "config", description: "Show and manage configuration" },
|
|
567
|
+
args: {
|
|
568
|
+
list: { type: "boolean", description: "List current configuration", default: false },
|
|
569
|
+
},
|
|
570
|
+
subCommands: {
|
|
571
|
+
path: defineCommand({
|
|
572
|
+
meta: { name: "path", description: "Show paths to config, stash, cache, and index" },
|
|
573
|
+
args: {
|
|
574
|
+
all: { type: "boolean", description: "Show all paths (config, stash, cache, index)", default: false },
|
|
575
|
+
},
|
|
576
|
+
run({ args }) {
|
|
577
|
+
return runWithJsonErrors(() => {
|
|
578
|
+
const configPath = getConfigPath();
|
|
579
|
+
if (args.all) {
|
|
580
|
+
let stashDir;
|
|
581
|
+
try {
|
|
582
|
+
stashDir = resolveStashDir({ readOnly: true });
|
|
583
|
+
}
|
|
584
|
+
catch {
|
|
585
|
+
stashDir = `${getDefaultStashDir()} (not initialized)`;
|
|
586
|
+
}
|
|
587
|
+
const cacheDir = getCacheDir();
|
|
588
|
+
const result = {
|
|
589
|
+
config: configPath,
|
|
590
|
+
stash: stashDir,
|
|
591
|
+
cache: cacheDir,
|
|
592
|
+
index: getDbPath(),
|
|
593
|
+
};
|
|
594
|
+
output("config", result);
|
|
595
|
+
}
|
|
596
|
+
else {
|
|
597
|
+
console.log(configPath);
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
},
|
|
601
|
+
}),
|
|
602
|
+
list: defineCommand({
|
|
603
|
+
meta: { name: "list", description: "List current configuration" },
|
|
604
|
+
run() {
|
|
605
|
+
return runWithJsonErrors(() => {
|
|
606
|
+
output("config", listConfig(loadConfig()));
|
|
607
|
+
});
|
|
608
|
+
},
|
|
609
|
+
}),
|
|
610
|
+
get: defineCommand({
|
|
611
|
+
meta: { name: "get", description: "Get a configuration value by key" },
|
|
612
|
+
args: {
|
|
613
|
+
key: { type: "positional", required: true, description: "Config key (for example: embedding, stashDir)" },
|
|
614
|
+
},
|
|
615
|
+
run({ args }) {
|
|
616
|
+
return runWithJsonErrors(() => {
|
|
617
|
+
output("config", getConfigValue(loadConfig(), args.key));
|
|
618
|
+
});
|
|
619
|
+
},
|
|
620
|
+
}),
|
|
621
|
+
set: defineCommand({
|
|
622
|
+
meta: { name: "set", description: "Set a configuration value by key" },
|
|
623
|
+
args: {
|
|
624
|
+
key: { type: "positional", required: true, description: "Config key (for example: embedding, llm)" },
|
|
625
|
+
value: { type: "positional", required: true, description: "Config value" },
|
|
626
|
+
},
|
|
627
|
+
run({ args }) {
|
|
628
|
+
return runWithJsonErrors(() => {
|
|
629
|
+
const updated = setConfigValue(loadConfig(), args.key, args.value);
|
|
630
|
+
saveConfig(updated);
|
|
631
|
+
output("config", listConfig(updated));
|
|
632
|
+
});
|
|
633
|
+
},
|
|
634
|
+
}),
|
|
635
|
+
unset: defineCommand({
|
|
636
|
+
meta: { name: "unset", description: "Unset an optional configuration key or whole embedding/llm section" },
|
|
637
|
+
args: {
|
|
638
|
+
key: { type: "positional", required: true, description: "Config key to unset" },
|
|
639
|
+
},
|
|
640
|
+
run({ args }) {
|
|
641
|
+
return runWithJsonErrors(() => {
|
|
642
|
+
const updated = unsetConfigValue(loadConfig(), args.key);
|
|
643
|
+
saveConfig(updated);
|
|
644
|
+
output("config", listConfig(updated));
|
|
645
|
+
});
|
|
646
|
+
},
|
|
647
|
+
}),
|
|
648
|
+
},
|
|
649
|
+
run({ args }) {
|
|
650
|
+
return runWithJsonErrors(() => {
|
|
651
|
+
if (hasConfigSubcommand(args))
|
|
652
|
+
return;
|
|
653
|
+
if (args.list) {
|
|
654
|
+
output("config", listConfig(loadConfig()));
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
output("config", listConfig(loadConfig()));
|
|
658
|
+
});
|
|
659
|
+
},
|
|
660
|
+
});
|
|
661
|
+
const cloneCommand = defineCommand({
|
|
662
|
+
meta: {
|
|
663
|
+
name: "clone",
|
|
664
|
+
description: "Clone an asset from any stash source into the working stash or a custom destination",
|
|
665
|
+
},
|
|
666
|
+
args: {
|
|
667
|
+
ref: { type: "positional", description: "Asset ref (e.g. npm:@scope/pkg//script:deploy.sh)", required: true },
|
|
668
|
+
name: { type: "string", description: "New name for the cloned asset" },
|
|
669
|
+
force: { type: "boolean", description: "Overwrite if asset already exists in working stash", default: false },
|
|
670
|
+
dest: { type: "string", description: "Destination directory (default: working stash)" },
|
|
671
|
+
},
|
|
672
|
+
async run({ args }) {
|
|
673
|
+
await runWithJsonErrors(async () => {
|
|
674
|
+
const result = await agentikitClone({
|
|
675
|
+
sourceRef: args.ref,
|
|
676
|
+
newName: args.name,
|
|
677
|
+
force: args.force,
|
|
678
|
+
dest: args.dest,
|
|
679
|
+
});
|
|
680
|
+
output("clone", result);
|
|
681
|
+
});
|
|
682
|
+
},
|
|
683
|
+
});
|
|
684
|
+
const registryCommand = defineCommand({
|
|
685
|
+
meta: { name: "registry", description: "Manage kit registries" },
|
|
686
|
+
subCommands: {
|
|
687
|
+
list: defineCommand({
|
|
688
|
+
meta: { name: "list", description: "List configured registries" },
|
|
689
|
+
run() {
|
|
690
|
+
return runWithJsonErrors(() => {
|
|
691
|
+
const config = loadConfig();
|
|
692
|
+
const registries = config.registries ?? [];
|
|
693
|
+
output("registry-list", { registries });
|
|
694
|
+
});
|
|
695
|
+
},
|
|
696
|
+
}),
|
|
697
|
+
add: defineCommand({
|
|
698
|
+
meta: { name: "add", description: "Add a registry by URL" },
|
|
699
|
+
args: {
|
|
700
|
+
url: { type: "positional", description: "Registry index URL", required: true },
|
|
701
|
+
name: { type: "string", description: "Human-friendly name for the registry" },
|
|
702
|
+
},
|
|
703
|
+
run({ args }) {
|
|
704
|
+
return runWithJsonErrors(() => {
|
|
705
|
+
if (!args.url.startsWith("http")) {
|
|
706
|
+
throw new UsageError("Registry URL must start with http:// or https://");
|
|
707
|
+
}
|
|
708
|
+
const config = loadConfig();
|
|
709
|
+
const registries = [...(config.registries ?? [])];
|
|
710
|
+
// Deduplicate by URL
|
|
711
|
+
if (registries.some((r) => r.url === args.url)) {
|
|
712
|
+
output("registry-add", { registries, added: false, message: "Registry URL already configured" });
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
const entry = { url: args.url };
|
|
716
|
+
if (args.name)
|
|
717
|
+
entry.name = args.name;
|
|
718
|
+
registries.push(entry);
|
|
719
|
+
saveConfig({ ...config, registries });
|
|
720
|
+
output("registry-add", { registries, added: true });
|
|
721
|
+
});
|
|
722
|
+
},
|
|
723
|
+
}),
|
|
724
|
+
remove: defineCommand({
|
|
725
|
+
meta: { name: "remove", description: "Remove a registry by URL or name" },
|
|
726
|
+
args: {
|
|
727
|
+
target: { type: "positional", description: "Registry URL or name to remove", required: true },
|
|
728
|
+
},
|
|
729
|
+
run({ args }) {
|
|
730
|
+
return runWithJsonErrors(() => {
|
|
731
|
+
const config = loadConfig();
|
|
732
|
+
const registries = [...(config.registries ?? [])];
|
|
733
|
+
const idx = registries.findIndex((r) => r.url === args.target || r.name === args.target);
|
|
734
|
+
if (idx === -1) {
|
|
735
|
+
output("registry-remove", { registries, removed: false, message: "No matching registry found" });
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
const removed = registries.splice(idx, 1)[0];
|
|
739
|
+
saveConfig({ ...config, registries });
|
|
740
|
+
output("registry-remove", { registries, removed: true, entry: removed });
|
|
741
|
+
});
|
|
742
|
+
},
|
|
743
|
+
}),
|
|
744
|
+
search: defineCommand({
|
|
745
|
+
meta: { name: "search", description: "Search enabled registries for kits" },
|
|
746
|
+
args: {
|
|
747
|
+
query: { type: "positional", description: "Search query", required: true },
|
|
748
|
+
limit: { type: "string", description: "Maximum number of results" },
|
|
749
|
+
assets: { type: "boolean", description: "Include asset-level search results", default: false },
|
|
750
|
+
},
|
|
751
|
+
async run({ args }) {
|
|
752
|
+
await runWithJsonErrors(async () => {
|
|
753
|
+
const limit = args.limit ? parseInt(args.limit, 10) : undefined;
|
|
754
|
+
const result = await searchRegistry(args.query, { limit, includeAssets: args.assets });
|
|
755
|
+
output("registry-search", result);
|
|
756
|
+
});
|
|
757
|
+
},
|
|
758
|
+
}),
|
|
759
|
+
},
|
|
760
|
+
});
|
|
761
|
+
const sourcesCommand = defineCommand({
|
|
762
|
+
meta: { name: "sources", description: "List all stash search paths and their status" },
|
|
763
|
+
run() {
|
|
764
|
+
return runWithJsonErrors(() => {
|
|
765
|
+
const config = loadConfig();
|
|
766
|
+
const sources = resolveStashSources();
|
|
767
|
+
const registries = config.registries ?? [];
|
|
768
|
+
output("sources", { sources, registries });
|
|
769
|
+
});
|
|
770
|
+
},
|
|
771
|
+
});
|
|
772
|
+
const main = defineCommand({
|
|
773
|
+
meta: {
|
|
774
|
+
name: "akm",
|
|
775
|
+
version: pkgVersion,
|
|
776
|
+
description: "CLI tool to search, open, and manage assets from Agent-i-Kit stash.",
|
|
777
|
+
},
|
|
778
|
+
args: {
|
|
779
|
+
format: { type: "string", description: "Output format (json|text|yaml)" },
|
|
780
|
+
detail: { type: "string", description: "Detail level (brief|normal|full)" },
|
|
781
|
+
quiet: { type: "boolean", alias: "q", description: "Suppress stderr warnings", default: false },
|
|
782
|
+
},
|
|
783
|
+
subCommands: {
|
|
784
|
+
init: initCommand,
|
|
785
|
+
index: indexCommand,
|
|
786
|
+
add: addCommand,
|
|
787
|
+
list: listCommand,
|
|
788
|
+
remove: removeCommand,
|
|
789
|
+
update: updateCommand,
|
|
790
|
+
upgrade: upgradeCommand,
|
|
791
|
+
search: searchCommand,
|
|
792
|
+
show: showCommand,
|
|
793
|
+
clone: cloneCommand,
|
|
794
|
+
sources: sourcesCommand,
|
|
795
|
+
registry: registryCommand,
|
|
796
|
+
config: configCommand,
|
|
797
|
+
},
|
|
798
|
+
});
|
|
799
|
+
const SEARCH_SOURCES = ["local", "registry", "both"];
|
|
800
|
+
const CONFIG_SUBCOMMAND_SET = new Set(["path", "list", "get", "set", "unset"]);
|
|
801
|
+
const SHOW_VIEW_MODES = new Set(["toc", "frontmatter", "full", "section", "lines"]);
|
|
802
|
+
// citty reads process.argv directly and does not accept a custom argv array,
|
|
803
|
+
// so we must replace process.argv with the normalized version before runMain.
|
|
804
|
+
process.argv = normalizeShowArgv(process.argv);
|
|
805
|
+
runMain(main);
|
|
806
|
+
function parseSearchSource(value) {
|
|
807
|
+
if (SEARCH_SOURCES.includes(value))
|
|
808
|
+
return value;
|
|
809
|
+
throw new UsageError(`Invalid value for --source: ${value}. Expected one of: ${SEARCH_SOURCES.join("|")}`);
|
|
810
|
+
}
|
|
811
|
+
// ── Exit codes ──────────────────────────────────────────────────────────────
|
|
812
|
+
const EXIT_GENERAL = 1;
|
|
813
|
+
const EXIT_USAGE = 2;
|
|
814
|
+
const EXIT_CONFIG = 78;
|
|
815
|
+
function classifyExitCode(error) {
|
|
816
|
+
if (error instanceof UsageError)
|
|
817
|
+
return EXIT_USAGE;
|
|
818
|
+
if (error instanceof ConfigError)
|
|
819
|
+
return EXIT_CONFIG;
|
|
820
|
+
if (error instanceof NotFoundError)
|
|
821
|
+
return EXIT_GENERAL;
|
|
822
|
+
return EXIT_GENERAL;
|
|
823
|
+
}
|
|
824
|
+
async function runWithJsonErrors(fn) {
|
|
825
|
+
try {
|
|
826
|
+
// Apply --quiet flag early so warnings inside the command are suppressed
|
|
827
|
+
if (process.argv.includes("--quiet") || process.argv.includes("-q")) {
|
|
828
|
+
setQuiet(true);
|
|
829
|
+
}
|
|
830
|
+
await fn();
|
|
831
|
+
}
|
|
832
|
+
catch (error) {
|
|
833
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
834
|
+
const hint = buildHint(message);
|
|
835
|
+
const exitCode = classifyExitCode(error);
|
|
836
|
+
console.error(JSON.stringify({ ok: false, error: message, hint }, null, 2));
|
|
837
|
+
process.exit(exitCode);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
function buildHint(message) {
|
|
841
|
+
if (message.includes("No stash directory found"))
|
|
842
|
+
return "Run `akm init` to create the default stash, or set stashDir in your config.";
|
|
843
|
+
if (message.includes("Either <target> or --all is required"))
|
|
844
|
+
return "Use `akm update --all` or pass a target like `akm update npm:@scope/pkg`.";
|
|
845
|
+
if (message.includes("Specify either <target> or --all"))
|
|
846
|
+
return "Use only one: a positional target or `--all`.";
|
|
847
|
+
if (message.includes("No installed kit matched target"))
|
|
848
|
+
return "Run `akm list` to view installed ids/refs, then retry with one of those values.";
|
|
849
|
+
if (message.includes("remote package fetched but asset not found"))
|
|
850
|
+
return "The remote package was fetched but doesn't contain the requested asset. Check the asset name and type.";
|
|
851
|
+
if (message.includes("Invalid value for --source"))
|
|
852
|
+
return "Pick one of: local, registry, both.";
|
|
853
|
+
if (message.includes("Invalid value for --format"))
|
|
854
|
+
return "Pick one of: json, text, yaml.";
|
|
855
|
+
if (message.includes("Invalid value for --detail"))
|
|
856
|
+
return "Pick one of: brief, normal, full.";
|
|
857
|
+
if (message.includes("expected JSON object with endpoint and model")) {
|
|
858
|
+
return 'Quote JSON values in your shell, for example: akm config set embedding \'{"endpoint":"http://localhost:11434/v1/embeddings","model":"nomic-embed-text"}\'.';
|
|
859
|
+
}
|
|
860
|
+
return undefined;
|
|
861
|
+
}
|
|
862
|
+
function hasConfigSubcommand(args) {
|
|
863
|
+
const command = Array.isArray(args._) ? args._[0] : undefined;
|
|
864
|
+
return typeof command === "string" && CONFIG_SUBCOMMAND_SET.has(command);
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Normalize argv so positional view-mode arguments after the asset ref
|
|
868
|
+
* are rewritten into internal flags that citty can parse.
|
|
869
|
+
*
|
|
870
|
+
* Converts:
|
|
871
|
+
* akm show knowledge:guide.md toc → akm show knowledge:guide.md --akmView toc
|
|
872
|
+
* akm show knowledge:guide.md section Auth → akm show knowledge:guide.md --akmView section --akmHeading Auth
|
|
873
|
+
* akm show knowledge:guide.md lines 1 50 → akm show knowledge:guide.md --akmView lines --akmStart 1 --akmEnd 50
|
|
874
|
+
*
|
|
875
|
+
* Legacy `--view` is intentionally unsupported.
|
|
876
|
+
* Returns a new array; the input is never modified.
|
|
877
|
+
*/
|
|
878
|
+
function normalizeShowArgv(argv) {
|
|
879
|
+
// argv[0]=bun argv[1]=script argv[2]=subcommand argv[3]=ref argv[4..]=rest
|
|
880
|
+
if (argv[2] !== "show")
|
|
881
|
+
return argv;
|
|
882
|
+
if (argv.includes("--view") || argv.includes("--heading") || argv.includes("--start") || argv.includes("--end")) {
|
|
883
|
+
throw new UsageError('Legacy show flags are no longer supported. Use positional syntax like `akm show knowledge:guide toc` or `akm show knowledge:guide section "Auth"`.');
|
|
884
|
+
}
|
|
885
|
+
// Separate global flags from positional/show-specific args
|
|
886
|
+
const prefix = argv.slice(0, 3); // [bun, script, show]
|
|
887
|
+
const rest = argv.slice(3);
|
|
888
|
+
const globalFlags = [];
|
|
889
|
+
const showArgs = [];
|
|
890
|
+
for (let i = 0; i < rest.length; i++) {
|
|
891
|
+
const arg = rest[i];
|
|
892
|
+
if (arg === "--quiet" || arg === "-q") {
|
|
893
|
+
globalFlags.push(arg);
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
if (arg.startsWith("--format=") || arg.startsWith("--detail=")) {
|
|
897
|
+
globalFlags.push(arg);
|
|
898
|
+
continue;
|
|
899
|
+
}
|
|
900
|
+
if (arg === "--format" || arg === "--detail") {
|
|
901
|
+
globalFlags.push(arg);
|
|
902
|
+
if (rest[i + 1] !== undefined) {
|
|
903
|
+
globalFlags.push(rest[i + 1]);
|
|
904
|
+
i++;
|
|
905
|
+
}
|
|
906
|
+
continue;
|
|
907
|
+
}
|
|
908
|
+
showArgs.push(arg);
|
|
909
|
+
}
|
|
910
|
+
// showArgs[0] = ref, showArgs[1] = potential view mode, showArgs[2..] = view params
|
|
911
|
+
const ref = showArgs[0];
|
|
912
|
+
const viewMode = showArgs[1];
|
|
913
|
+
if (!ref || !viewMode || !SHOW_VIEW_MODES.has(viewMode)) {
|
|
914
|
+
return argv;
|
|
915
|
+
}
|
|
916
|
+
const result = [...prefix, ref, "--akmView", viewMode];
|
|
917
|
+
if (viewMode === "section") {
|
|
918
|
+
// Next arg is the heading name; pass empty string when missing so the
|
|
919
|
+
// show handler can produce a clear "section not found" error.
|
|
920
|
+
const heading = showArgs[2] ?? "";
|
|
921
|
+
result.push("--akmHeading", heading);
|
|
922
|
+
}
|
|
923
|
+
else if (viewMode === "lines") {
|
|
924
|
+
// Next two args are start and end
|
|
925
|
+
const start = showArgs[2];
|
|
926
|
+
const end = showArgs[3];
|
|
927
|
+
if (start)
|
|
928
|
+
result.push("--akmStart", start);
|
|
929
|
+
if (end)
|
|
930
|
+
result.push("--akmEnd", end);
|
|
931
|
+
}
|
|
932
|
+
result.push(...globalFlags);
|
|
933
|
+
return result;
|
|
934
|
+
}
|