akm-cli 0.5.0 → 0.6.0-rc1
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/CHANGELOG.md +32 -5
- package/dist/asset-registry.js +29 -5
- package/dist/asset-spec.js +12 -5
- package/dist/cli-hints.js +300 -0
- package/dist/cli.js +218 -1357
- package/dist/common.js +147 -50
- package/dist/config.js +224 -13
- package/dist/create-provider-registry.js +1 -1
- package/dist/curate.js +258 -0
- package/dist/{local-search.js → db-search.js} +30 -19
- package/dist/db.js +168 -62
- package/dist/embedder.js +49 -273
- package/dist/embedders/cache.js +47 -0
- package/dist/embedders/local.js +152 -0
- package/dist/embedders/remote.js +121 -0
- package/dist/embedders/types.js +39 -0
- package/dist/errors.js +14 -3
- package/dist/frontmatter.js +61 -7
- package/dist/indexer.js +38 -7
- package/dist/info.js +2 -2
- package/dist/install-audit.js +16 -1
- package/dist/{installed-kits.js → installed-stashes.js} +48 -22
- package/dist/llm-client.js +92 -0
- package/dist/llm.js +14 -126
- package/dist/lockfile.js +28 -1
- package/dist/matchers.js +1 -1
- package/dist/metadata-enhance.js +53 -0
- package/dist/migration-help.js +75 -44
- package/dist/output-context.js +77 -0
- package/dist/output-shapes.js +198 -0
- package/dist/output-text.js +520 -0
- package/dist/paths.js +4 -4
- package/dist/providers/index.js +11 -0
- package/dist/providers/skills-sh.js +1 -1
- package/dist/providers/static-index.js +47 -45
- package/dist/registry-build-index.js +36 -29
- package/dist/registry-factory.js +2 -2
- package/dist/registry-resolve.js +8 -4
- package/dist/registry-search.js +62 -5
- package/dist/remember.js +172 -0
- package/dist/renderers.js +52 -0
- package/dist/search-source.js +73 -42
- package/dist/setup-steps.js +45 -0
- package/dist/setup.js +149 -76
- package/dist/stash-add.js +94 -38
- package/dist/stash-clone.js +4 -4
- package/dist/stash-provider-factory.js +2 -2
- package/dist/stash-provider.js +3 -1
- package/dist/stash-providers/filesystem.js +31 -1
- package/dist/stash-providers/git.js +209 -8
- package/dist/stash-providers/index.js +1 -0
- package/dist/stash-providers/npm.js +159 -0
- package/dist/stash-providers/provider-utils.js +162 -0
- package/dist/stash-providers/sync-from-ref.js +45 -0
- package/dist/stash-providers/tar-utils.js +151 -0
- package/dist/stash-providers/website.js +80 -4
- package/dist/stash-resolve.js +5 -5
- package/dist/stash-search.js +4 -4
- package/dist/stash-show.js +3 -3
- package/dist/wiki.js +6 -6
- package/dist/workflow-authoring.js +12 -4
- package/dist/workflow-markdown.js +9 -0
- package/dist/workflow-runs.js +12 -2
- package/docs/README.md +30 -0
- package/docs/migration/release-notes/0.0.13.md +4 -0
- package/docs/migration/release-notes/0.1.0.md +6 -0
- package/docs/migration/release-notes/0.2.0.md +6 -0
- package/docs/migration/release-notes/0.3.0.md +5 -0
- package/docs/migration/release-notes/0.5.0.md +6 -0
- package/docs/migration/release-notes/0.6.0.md +29 -0
- package/docs/migration/release-notes/README.md +21 -0
- package/package.json +3 -2
- package/dist/registry-install.js +0 -532
- /package/dist/{kit-include.js → stash-include.js} +0 -0
package/dist/cli.js
CHANGED
|
@@ -3,21 +3,26 @@ import fs from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { defineCommand, runMain } from "citty";
|
|
5
5
|
import { deriveCanonicalAssetName, resolveAssetPathFromName } from "./asset-spec";
|
|
6
|
-
import {
|
|
6
|
+
import { EMBEDDED_HINTS, EMBEDDED_HINTS_FULL } from "./cli-hints";
|
|
7
|
+
import { isWithin, resolveStashDir, tryReadStdinText } from "./common";
|
|
7
8
|
import { generateBashCompletions, installBashCompletions } from "./completions";
|
|
8
9
|
import { DEFAULT_CONFIG, getConfigPath, loadConfig, loadUserConfig, saveConfig } from "./config";
|
|
9
10
|
import { getConfigValue, listConfig, setConfigValue, unsetConfigValue } from "./config-cli";
|
|
11
|
+
import { akmCurate } from "./curate";
|
|
10
12
|
import { closeDatabase, openDatabase } from "./db";
|
|
11
13
|
import { ConfigError, NotFoundError, UsageError } from "./errors";
|
|
12
14
|
import { akmIndex } from "./indexer";
|
|
13
15
|
import { assembleInfo } from "./info";
|
|
14
16
|
import { akmInit } from "./init";
|
|
15
|
-
import {
|
|
16
|
-
import { akmListSources, akmRemove, akmUpdate } from "./installed-kits";
|
|
17
|
+
import { akmListSources, akmRemove, akmUpdate } from "./installed-stashes";
|
|
17
18
|
import { renderMigrationHelp } from "./migration-help";
|
|
19
|
+
import { getOutputMode, initOutputMode, parseFlagValue } from "./output-context";
|
|
20
|
+
import { shapeForCommand } from "./output-shapes";
|
|
21
|
+
import { formatPlain, outputJsonl } from "./output-text";
|
|
18
22
|
import { getCacheDir, getDbPath, getDefaultStashDir } from "./paths";
|
|
19
23
|
import { buildRegistryIndex, writeRegistryIndex } from "./registry-build-index";
|
|
20
24
|
import { searchRegistry } from "./registry-search";
|
|
25
|
+
import { buildMemoryFrontmatter, parseDuration, readMemoryContent, runAutoHeuristics, runLlmEnrich } from "./remember";
|
|
21
26
|
import { checkForUpdate, performUpgrade } from "./self-update";
|
|
22
27
|
import { akmAdd } from "./stash-add";
|
|
23
28
|
import { akmClone } from "./stash-clone";
|
|
@@ -32,54 +37,32 @@ import { setQuiet, warn } from "./warn";
|
|
|
32
37
|
import { createWorkflowAsset, getWorkflowTemplate } from "./workflow-authoring";
|
|
33
38
|
import { hasWorkflowSubcommand, parseWorkflowJsonObject, parseWorkflowStepState, WORKFLOW_STEP_STATES, } from "./workflow-cli";
|
|
34
39
|
import { completeWorkflowStep, getNextWorkflowStep, getWorkflowStatus, listWorkflowRuns, resumeWorkflowRun, startWorkflowRun, } from "./workflow-runs";
|
|
35
|
-
const OUTPUT_FORMATS = ["json", "yaml", "text", "jsonl"];
|
|
36
|
-
const DETAIL_LEVELS = ["brief", "normal", "full", "summary"];
|
|
37
|
-
const NORMAL_DESCRIPTION_LIMIT = 250;
|
|
38
40
|
const MAX_CAPTURED_ASSET_SLUG_LENGTH = 64;
|
|
39
|
-
const CONTEXT_HUB_ALIAS_REF = "context-hub";
|
|
40
|
-
const CONTEXT_HUB_ALIAS_URL = "https://github.com/andrewyng/context-hub";
|
|
41
41
|
const SKILLS_SH_NAME = "skills.sh";
|
|
42
42
|
const SKILLS_SH_URL = "https://skills.sh";
|
|
43
43
|
const SKILLS_SH_PROVIDER = "skills-sh";
|
|
44
44
|
import { stringify as yamlStringify } from "yaml";
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (!value)
|
|
54
|
-
return undefined;
|
|
55
|
-
if (DETAIL_LEVELS.includes(value))
|
|
56
|
-
return value;
|
|
57
|
-
throw new UsageError(`Invalid value for --detail: ${value}. Expected one of: ${DETAIL_LEVELS.join("|")}`);
|
|
58
|
-
}
|
|
59
|
-
function parseFlagValue(flag) {
|
|
45
|
+
/**
|
|
46
|
+
* Collect all occurrences of a repeatable flag from process.argv.
|
|
47
|
+
* Citty's StringArgDef only exposes the last value when a flag is repeated,
|
|
48
|
+
* so for repeatable CLI args (like `--tag foo --tag bar`) we read argv directly.
|
|
49
|
+
* Supports both `--flag value` and `--flag=value` forms.
|
|
50
|
+
*/
|
|
51
|
+
function parseAllFlagValues(flag) {
|
|
52
|
+
const values = [];
|
|
60
53
|
for (let i = 0; i < process.argv.length; i++) {
|
|
61
54
|
const arg = process.argv[i];
|
|
62
|
-
if (arg === flag)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
55
|
+
if (arg === flag && i + 1 < process.argv.length) {
|
|
56
|
+
values.push(process.argv[i + 1]);
|
|
57
|
+
}
|
|
58
|
+
else if (arg.startsWith(`${flag}=`)) {
|
|
59
|
+
values.push(arg.slice(flag.length + 1));
|
|
60
|
+
}
|
|
66
61
|
}
|
|
67
|
-
return
|
|
68
|
-
}
|
|
69
|
-
// Uses process.argv directly because the global output() function (called by all
|
|
70
|
-
// commands) needs this flag but doesn't have access to citty's parsed args.
|
|
71
|
-
function hasBooleanFlag(flag) {
|
|
72
|
-
return process.argv.some((arg) => arg === flag || arg === `${flag}=true`);
|
|
73
|
-
}
|
|
74
|
-
function resolveOutputMode() {
|
|
75
|
-
const config = loadConfig();
|
|
76
|
-
const format = parseOutputFormat(parseFlagValue("--format")) ?? config.output?.format ?? "json";
|
|
77
|
-
const detail = parseDetailLevel(parseFlagValue("--detail")) ?? config.output?.detail ?? "brief";
|
|
78
|
-
const forAgent = hasBooleanFlag("--for-agent");
|
|
79
|
-
return { format, detail, forAgent };
|
|
62
|
+
return values;
|
|
80
63
|
}
|
|
81
64
|
function output(command, result) {
|
|
82
|
-
const mode =
|
|
65
|
+
const mode = getOutputMode();
|
|
83
66
|
const shaped = shapeForCommand(command, result, mode.detail, mode.forAgent);
|
|
84
67
|
if (mode.format === "jsonl") {
|
|
85
68
|
outputJsonl(command, shaped);
|
|
@@ -99,936 +82,12 @@ function output(command, result) {
|
|
|
99
82
|
}
|
|
100
83
|
}
|
|
101
84
|
}
|
|
102
|
-
function outputJsonl(command, shaped) {
|
|
103
|
-
if (command === "search" || command === "registry-search") {
|
|
104
|
-
const r = shaped;
|
|
105
|
-
const hits = Array.isArray(r.hits) ? r.hits : [];
|
|
106
|
-
for (const hit of hits) {
|
|
107
|
-
console.log(JSON.stringify(hit));
|
|
108
|
-
}
|
|
109
|
-
const registryHits = Array.isArray(r.registryHits) ? r.registryHits : [];
|
|
110
|
-
for (const hit of registryHits) {
|
|
111
|
-
console.log(JSON.stringify(hit));
|
|
112
|
-
}
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
// For non-search commands, output the whole object as a single JSONL line
|
|
116
|
-
console.log(JSON.stringify(shaped));
|
|
117
|
-
}
|
|
118
|
-
function shapeForCommand(command, result, detail, forAgent = false) {
|
|
119
|
-
switch (command) {
|
|
120
|
-
case "search":
|
|
121
|
-
return shapeSearchOutput(result, detail, forAgent);
|
|
122
|
-
case "registry-search":
|
|
123
|
-
return shapeRegistrySearchOutput(result, detail);
|
|
124
|
-
case "show":
|
|
125
|
-
return shapeShowOutput(result, detail, forAgent);
|
|
126
|
-
default:
|
|
127
|
-
return result;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
function shapeSearchOutput(result, detail, forAgent = false) {
|
|
131
|
-
const hits = Array.isArray(result.hits) ? result.hits : [];
|
|
132
|
-
const registryHits = Array.isArray(result.registryHits) ? result.registryHits : [];
|
|
133
|
-
const shapedHits = forAgent
|
|
134
|
-
? hits.map((hit) => shapeSearchHitForAgent(hit))
|
|
135
|
-
: hits.map((hit) => shapeSearchHit(hit, detail));
|
|
136
|
-
const shapedRegistryHits = forAgent
|
|
137
|
-
? registryHits.map((hit) => shapeSearchHitForAgent(hit))
|
|
138
|
-
: registryHits.map((hit) => shapeSearchHit(hit, detail));
|
|
139
|
-
if (forAgent) {
|
|
140
|
-
return {
|
|
141
|
-
hits: shapedHits,
|
|
142
|
-
...(shapedRegistryHits.length > 0 ? { registryHits: shapedRegistryHits } : {}),
|
|
143
|
-
...(result.tip ? { tip: result.tip } : {}),
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
if (detail === "full") {
|
|
147
|
-
return {
|
|
148
|
-
schemaVersion: result.schemaVersion,
|
|
149
|
-
stashDir: result.stashDir,
|
|
150
|
-
source: result.source,
|
|
151
|
-
hits: shapedHits,
|
|
152
|
-
...(shapedRegistryHits.length > 0 ? { registryHits: shapedRegistryHits } : {}),
|
|
153
|
-
...(result.semanticSearch ? { semanticSearch: result.semanticSearch } : {}),
|
|
154
|
-
...(result.tip ? { tip: result.tip } : {}),
|
|
155
|
-
...(result.warnings ? { warnings: result.warnings } : {}),
|
|
156
|
-
...(result.timing ? { timing: result.timing } : {}),
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
return {
|
|
160
|
-
hits: shapedHits,
|
|
161
|
-
...(shapedRegistryHits.length > 0 ? { registryHits: shapedRegistryHits } : {}),
|
|
162
|
-
...(Array.isArray(result.warnings) && result.warnings.length > 0 ? { warnings: result.warnings } : {}),
|
|
163
|
-
...(result.tip ? { tip: result.tip } : {}),
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
function shapeRegistrySearchOutput(result, detail) {
|
|
167
|
-
const hits = Array.isArray(result.hits) ? result.hits : [];
|
|
168
|
-
const assetHits = Array.isArray(result.assetHits) ? result.assetHits : [];
|
|
169
|
-
// Shape kit hits as registry type
|
|
170
|
-
const shapedKitHits = hits.map((hit) => shapeSearchHit({ ...hit, type: "registry" }, detail));
|
|
171
|
-
// Shape asset hits by detail level
|
|
172
|
-
const shapedAssetHits = assetHits.map((hit) => shapeAssetHit(hit, detail));
|
|
173
|
-
const shaped = {
|
|
174
|
-
hits: shapedKitHits,
|
|
175
|
-
...(shapedAssetHits.length > 0 ? { assetHits: shapedAssetHits } : {}),
|
|
176
|
-
...(Array.isArray(result.warnings) && result.warnings.length > 0 ? { warnings: result.warnings } : {}),
|
|
177
|
-
};
|
|
178
|
-
if (detail === "full") {
|
|
179
|
-
shaped.query = result.query;
|
|
180
|
-
}
|
|
181
|
-
return shaped;
|
|
182
|
-
}
|
|
183
|
-
function shapeAssetHit(hit, detail) {
|
|
184
|
-
if (detail === "brief")
|
|
185
|
-
return pickFields(hit, ["assetName", "assetType", "action", "estimatedTokens"]);
|
|
186
|
-
if (detail === "normal") {
|
|
187
|
-
return capDescription(pickFields(hit, ["assetName", "assetType", "description", "kit", "action", "estimatedTokens"]), NORMAL_DESCRIPTION_LIMIT);
|
|
188
|
-
}
|
|
189
|
-
return hit;
|
|
190
|
-
}
|
|
191
|
-
function shapeSearchHit(hit, detail) {
|
|
192
|
-
if (hit.type === "registry") {
|
|
193
|
-
if (detail === "brief")
|
|
194
|
-
return pickFields(hit, ["name", "action"]);
|
|
195
|
-
if (detail === "normal") {
|
|
196
|
-
return capDescription(pickFields(hit, ["name", "description", "action", "curated"]), NORMAL_DESCRIPTION_LIMIT);
|
|
197
|
-
}
|
|
198
|
-
return hit;
|
|
199
|
-
}
|
|
200
|
-
// Stash hit (local or remote)
|
|
201
|
-
if (detail === "brief")
|
|
202
|
-
return pickFields(hit, ["type", "name", "action", "estimatedTokens"]);
|
|
203
|
-
if (detail === "normal") {
|
|
204
|
-
return capDescription(pickFields(hit, ["type", "name", "description", "action", "score", "estimatedTokens"]), NORMAL_DESCRIPTION_LIMIT);
|
|
205
|
-
}
|
|
206
|
-
return hit;
|
|
207
|
-
}
|
|
208
|
-
/** Agent-optimized search hit: only fields an LLM agent needs to decide and act */
|
|
209
|
-
function shapeSearchHitForAgent(hit) {
|
|
210
|
-
const picked = pickFields(hit, ["name", "ref", "type", "description", "action", "score", "estimatedTokens"]);
|
|
211
|
-
return capDescription(picked, NORMAL_DESCRIPTION_LIMIT);
|
|
212
|
-
}
|
|
213
|
-
function capDescription(hit, limit) {
|
|
214
|
-
if (typeof hit.description !== "string")
|
|
215
|
-
return hit;
|
|
216
|
-
return { ...hit, description: truncateDescription(hit.description, limit) };
|
|
217
|
-
}
|
|
218
|
-
function truncateDescription(description, limit) {
|
|
219
|
-
const normalized = description.replace(/\s+/g, " ").trim();
|
|
220
|
-
if (normalized.length <= limit)
|
|
221
|
-
return normalized;
|
|
222
|
-
const truncated = normalized.slice(0, limit - 1);
|
|
223
|
-
const lastSpace = truncated.lastIndexOf(" ");
|
|
224
|
-
const safe = lastSpace >= Math.floor(limit * 0.6) ? truncated.slice(0, lastSpace) : truncated;
|
|
225
|
-
return `${safe.trimEnd()}...`;
|
|
226
|
-
}
|
|
227
|
-
function shapeShowOutput(result, detail, forAgent = false) {
|
|
228
|
-
if (forAgent) {
|
|
229
|
-
return pickFields(result, [
|
|
230
|
-
"type",
|
|
231
|
-
"name",
|
|
232
|
-
"description",
|
|
233
|
-
"action",
|
|
234
|
-
"content",
|
|
235
|
-
"template",
|
|
236
|
-
"prompt",
|
|
237
|
-
"run",
|
|
238
|
-
"setup",
|
|
239
|
-
"cwd",
|
|
240
|
-
"toolPolicy",
|
|
241
|
-
"modelHint",
|
|
242
|
-
"agent",
|
|
243
|
-
"parameters",
|
|
244
|
-
"workflowTitle",
|
|
245
|
-
"workflowParameters",
|
|
246
|
-
"steps",
|
|
247
|
-
"keys",
|
|
248
|
-
"comments",
|
|
249
|
-
]);
|
|
250
|
-
}
|
|
251
|
-
if (detail === "summary") {
|
|
252
|
-
return pickFields(result, [
|
|
253
|
-
"type",
|
|
254
|
-
"name",
|
|
255
|
-
"description",
|
|
256
|
-
"tags",
|
|
257
|
-
"parameters",
|
|
258
|
-
"workflowTitle",
|
|
259
|
-
"action",
|
|
260
|
-
"run",
|
|
261
|
-
"origin",
|
|
262
|
-
"keys",
|
|
263
|
-
"comments",
|
|
264
|
-
]);
|
|
265
|
-
}
|
|
266
|
-
const base = pickFields(result, [
|
|
267
|
-
"type",
|
|
268
|
-
"name",
|
|
269
|
-
"origin",
|
|
270
|
-
"action",
|
|
271
|
-
"description",
|
|
272
|
-
"tags",
|
|
273
|
-
"content",
|
|
274
|
-
"template",
|
|
275
|
-
"prompt",
|
|
276
|
-
"toolPolicy",
|
|
277
|
-
"modelHint",
|
|
278
|
-
"agent",
|
|
279
|
-
"parameters",
|
|
280
|
-
"workflowTitle",
|
|
281
|
-
"workflowParameters",
|
|
282
|
-
"steps",
|
|
283
|
-
"run",
|
|
284
|
-
"setup",
|
|
285
|
-
"cwd",
|
|
286
|
-
"keys",
|
|
287
|
-
"comments",
|
|
288
|
-
]);
|
|
289
|
-
if (detail !== "full") {
|
|
290
|
-
return base;
|
|
291
|
-
}
|
|
292
|
-
return {
|
|
293
|
-
schemaVersion: 1,
|
|
294
|
-
...base,
|
|
295
|
-
...pickFields(result, ["path", "editable", "editHint"]),
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
function pickFields(source, fields) {
|
|
299
|
-
const result = {};
|
|
300
|
-
for (const field of fields) {
|
|
301
|
-
if (source[field] !== undefined) {
|
|
302
|
-
result[field] = source[field];
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
return result;
|
|
306
|
-
}
|
|
307
|
-
/**
|
|
308
|
-
* Return a plain-text string for commands that are better as short messages,
|
|
309
|
-
* or null to fall through to YAML output.
|
|
310
|
-
*/
|
|
311
|
-
function formatPlain(command, result, detail) {
|
|
312
|
-
const r = result;
|
|
313
|
-
switch (command) {
|
|
314
|
-
case "init": {
|
|
315
|
-
let out = `Stash initialized at ${r.stashDir ?? "unknown"}`;
|
|
316
|
-
if (r.configPath)
|
|
317
|
-
out += `\nConfig saved to ${r.configPath}`;
|
|
318
|
-
return out;
|
|
319
|
-
}
|
|
320
|
-
case "index": {
|
|
321
|
-
const indexResult = result;
|
|
322
|
-
let out = `Indexed ${indexResult.totalEntries ?? 0} entries from ${indexResult.directoriesScanned ?? 0} directories (mode: ${indexResult.mode ?? "unknown"})`;
|
|
323
|
-
const warnings = indexResult.warnings;
|
|
324
|
-
if (Array.isArray(warnings) && warnings.length > 0) {
|
|
325
|
-
out += `\nWarnings (${warnings.length}):`;
|
|
326
|
-
for (const message of warnings)
|
|
327
|
-
out += `\n - ${String(message)}`;
|
|
328
|
-
}
|
|
329
|
-
const verification = indexResult.verification;
|
|
330
|
-
if (verification?.ok === false && verification.message) {
|
|
331
|
-
out += `\nVerification: ${String(verification.message)}`;
|
|
332
|
-
}
|
|
333
|
-
return out;
|
|
334
|
-
}
|
|
335
|
-
case "show": {
|
|
336
|
-
const lines = [];
|
|
337
|
-
if (r.type || r.name) {
|
|
338
|
-
lines.push(`# ${String(r.type ?? "asset")}: ${String(r.name ?? "unknown")}`);
|
|
339
|
-
}
|
|
340
|
-
if (r.origin !== undefined)
|
|
341
|
-
lines.push(`# origin: ${String(r.origin)}`);
|
|
342
|
-
if (r.action)
|
|
343
|
-
lines.push(`# ${String(r.action)}`);
|
|
344
|
-
if (r.description)
|
|
345
|
-
lines.push(`description: ${String(r.description)}`);
|
|
346
|
-
if (r.workflowTitle)
|
|
347
|
-
lines.push(`workflowTitle: ${String(r.workflowTitle)}`);
|
|
348
|
-
if (r.agent)
|
|
349
|
-
lines.push(`agent: ${String(r.agent)}`);
|
|
350
|
-
if (Array.isArray(r.parameters) && r.parameters.length > 0)
|
|
351
|
-
lines.push(`parameters: ${r.parameters.join(", ")}`);
|
|
352
|
-
if (Array.isArray(r.workflowParameters) && r.workflowParameters.length > 0) {
|
|
353
|
-
lines.push("workflowParameters:");
|
|
354
|
-
for (const parameter of r.workflowParameters) {
|
|
355
|
-
const name = typeof parameter.name === "string" ? parameter.name : "unknown";
|
|
356
|
-
const description = typeof parameter.description === "string" && parameter.description.trim()
|
|
357
|
-
? `: ${parameter.description}`
|
|
358
|
-
: "";
|
|
359
|
-
lines.push(` - ${name}${description}`);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
if (r.modelHint != null)
|
|
363
|
-
lines.push(`modelHint: ${String(r.modelHint)}`);
|
|
364
|
-
if (r.toolPolicy != null)
|
|
365
|
-
lines.push(`toolPolicy: ${JSON.stringify(r.toolPolicy)}`);
|
|
366
|
-
if (r.run)
|
|
367
|
-
lines.push(`run: ${String(r.run)}`);
|
|
368
|
-
if (r.setup)
|
|
369
|
-
lines.push(`setup: ${String(r.setup)}`);
|
|
370
|
-
if (r.cwd)
|
|
371
|
-
lines.push(`cwd: ${String(r.cwd)}`);
|
|
372
|
-
if (detail === "full") {
|
|
373
|
-
if (r.path)
|
|
374
|
-
lines.push(`path: ${String(r.path)}`);
|
|
375
|
-
if (r.editable !== undefined)
|
|
376
|
-
lines.push(`editable: ${String(r.editable)}`);
|
|
377
|
-
if (r.editHint)
|
|
378
|
-
lines.push(`editHint: ${String(r.editHint)}`);
|
|
379
|
-
if (r.schemaVersion !== undefined)
|
|
380
|
-
lines.push(`schemaVersion: ${String(r.schemaVersion)}`);
|
|
381
|
-
}
|
|
382
|
-
const payloads = [r.content, r.template, r.prompt].filter((value) => value != null).map(String);
|
|
383
|
-
if (Array.isArray(r.steps) && r.steps.length > 0) {
|
|
384
|
-
if (lines.length > 0)
|
|
385
|
-
lines.push("");
|
|
386
|
-
lines.push("steps:");
|
|
387
|
-
for (const [index, step] of r.steps.entries()) {
|
|
388
|
-
const title = typeof step.title === "string" ? step.title : "Untitled step";
|
|
389
|
-
const id = typeof step.id === "string" ? step.id : "unknown";
|
|
390
|
-
lines.push(` ${index + 1}. ${title} [${id}]`);
|
|
391
|
-
if (typeof step.instructions === "string" && step.instructions.trim()) {
|
|
392
|
-
lines.push(` instructions: ${step.instructions.replace(/\n+/g, " ").trim()}`);
|
|
393
|
-
}
|
|
394
|
-
if (Array.isArray(step.completionCriteria) && step.completionCriteria.length > 0) {
|
|
395
|
-
lines.push(" completion:");
|
|
396
|
-
for (const criterion of step.completionCriteria) {
|
|
397
|
-
lines.push(` - ${String(criterion)}`);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
if (payloads.length > 0) {
|
|
403
|
-
if (lines.length > 0)
|
|
404
|
-
lines.push("");
|
|
405
|
-
lines.push(...payloads);
|
|
406
|
-
}
|
|
407
|
-
return lines.length > 0 ? lines.join("\n") : null;
|
|
408
|
-
}
|
|
409
|
-
case "search": {
|
|
410
|
-
return formatSearchPlain(r, detail);
|
|
411
|
-
}
|
|
412
|
-
case "curate": {
|
|
413
|
-
return formatCuratePlain(r, detail);
|
|
414
|
-
}
|
|
415
|
-
case "wiki-list": {
|
|
416
|
-
return formatWikiListPlain(r);
|
|
417
|
-
}
|
|
418
|
-
case "wiki-show": {
|
|
419
|
-
return formatWikiShowPlain(r);
|
|
420
|
-
}
|
|
421
|
-
case "wiki-create": {
|
|
422
|
-
return formatWikiCreatePlain(r);
|
|
423
|
-
}
|
|
424
|
-
case "wiki-remove": {
|
|
425
|
-
return formatWikiRemovePlain(r);
|
|
426
|
-
}
|
|
427
|
-
case "wiki-pages": {
|
|
428
|
-
return formatWikiPagesPlain(r);
|
|
429
|
-
}
|
|
430
|
-
case "wiki-stash": {
|
|
431
|
-
return formatWikiStashPlain(r);
|
|
432
|
-
}
|
|
433
|
-
case "wiki-lint": {
|
|
434
|
-
return formatWikiLintPlain(r);
|
|
435
|
-
}
|
|
436
|
-
case "wiki-ingest": {
|
|
437
|
-
return formatWikiIngestPlain(r);
|
|
438
|
-
}
|
|
439
|
-
case "workflow-start":
|
|
440
|
-
case "workflow-status":
|
|
441
|
-
case "workflow-complete": {
|
|
442
|
-
return formatWorkflowStatusPlain(r);
|
|
443
|
-
}
|
|
444
|
-
case "workflow-next": {
|
|
445
|
-
return formatWorkflowNextPlain(r);
|
|
446
|
-
}
|
|
447
|
-
case "workflow-list": {
|
|
448
|
-
return formatWorkflowListPlain(r);
|
|
449
|
-
}
|
|
450
|
-
case "workflow-create": {
|
|
451
|
-
if (r.ref && r.path) {
|
|
452
|
-
return `Created ${String(r.ref)} at ${String(r.path)}`;
|
|
453
|
-
}
|
|
454
|
-
return null;
|
|
455
|
-
}
|
|
456
|
-
case "list": {
|
|
457
|
-
const sources = Array.isArray(r.sources) ? r.sources : [];
|
|
458
|
-
if (sources.length === 0)
|
|
459
|
-
return "No sources configured. Use `akm add` to add a source.";
|
|
460
|
-
const lines = [];
|
|
461
|
-
for (const src of sources) {
|
|
462
|
-
const kind = typeof src.kind === "string" ? src.kind : "unknown";
|
|
463
|
-
const name = typeof src.name === "string" ? src.name : "unnamed";
|
|
464
|
-
const ver = typeof src.version === "string" ? ` v${src.version}` : "";
|
|
465
|
-
const prov = typeof src.provider === "string" ? ` (${src.provider})` : "";
|
|
466
|
-
const flags = [];
|
|
467
|
-
if (typeof src.wiki === "string")
|
|
468
|
-
flags.push(`wiki:${src.wiki}`);
|
|
469
|
-
if (src.updatable === true)
|
|
470
|
-
flags.push("updatable");
|
|
471
|
-
if (src.writable === true)
|
|
472
|
-
flags.push("writable");
|
|
473
|
-
const flagText = flags.length > 0 ? ` [${flags.join(", ")}]` : "";
|
|
474
|
-
lines.push(`[${kind}] ${name}${ver}${prov}${flagText}`);
|
|
475
|
-
}
|
|
476
|
-
return lines.join("\n");
|
|
477
|
-
}
|
|
478
|
-
case "add": {
|
|
479
|
-
const index = r.index;
|
|
480
|
-
const scanned = index?.directoriesScanned ?? 0;
|
|
481
|
-
const total = index?.totalEntries ?? 0;
|
|
482
|
-
const lines = [`Installed ${r.ref} (${scanned} directories scanned, ${total} total assets indexed)`];
|
|
483
|
-
const warnings = index?.warnings;
|
|
484
|
-
if (Array.isArray(warnings) && warnings.length > 0) {
|
|
485
|
-
lines.push(`Warnings (${warnings.length}):`);
|
|
486
|
-
for (const message of warnings)
|
|
487
|
-
lines.push(` - ${String(message)}`);
|
|
488
|
-
}
|
|
489
|
-
const installed = r.installed;
|
|
490
|
-
const audit = installed?.audit;
|
|
491
|
-
if (audit && typeof audit === "object") {
|
|
492
|
-
lines.push(formatInstallAuditSummary(audit));
|
|
493
|
-
}
|
|
494
|
-
return lines.join("\n");
|
|
495
|
-
}
|
|
496
|
-
case "remove": {
|
|
497
|
-
const target = r.target ?? r.ref ?? "";
|
|
498
|
-
const ok = r.ok !== false ? "OK" : "FAILED";
|
|
499
|
-
return `remove: ${target} ${ok}`;
|
|
500
|
-
}
|
|
501
|
-
case "update": {
|
|
502
|
-
const processed = r.processed;
|
|
503
|
-
if (!processed?.length)
|
|
504
|
-
return `update: nothing to update`;
|
|
505
|
-
const lines = processed.map((item) => {
|
|
506
|
-
const changed = item.changed;
|
|
507
|
-
const installed = item.installed;
|
|
508
|
-
const previous = item.previous;
|
|
509
|
-
if (changed?.any) {
|
|
510
|
-
const prev = previous?.resolvedVersion ?? "unknown";
|
|
511
|
-
const next = installed?.resolvedVersion ?? "unknown";
|
|
512
|
-
return `update: ${item.id} v${prev} → v${next}`;
|
|
513
|
-
}
|
|
514
|
-
return `update: ${item.id} (unchanged)`;
|
|
515
|
-
});
|
|
516
|
-
return lines.join("\n");
|
|
517
|
-
}
|
|
518
|
-
case "upgrade": {
|
|
519
|
-
if (r.upgraded === true) {
|
|
520
|
-
return `akm upgraded: v${r.currentVersion} → v${r.newVersion}`;
|
|
521
|
-
}
|
|
522
|
-
if (r.updateAvailable === true) {
|
|
523
|
-
return `akm v${r.currentVersion} → v${r.latestVersion} available (run 'akm upgrade' to install)`;
|
|
524
|
-
}
|
|
525
|
-
if (r.updateAvailable === false && r.latestVersion) {
|
|
526
|
-
return `akm v${r.currentVersion} is already the latest version`;
|
|
527
|
-
}
|
|
528
|
-
if (r.message)
|
|
529
|
-
return String(r.message);
|
|
530
|
-
return null;
|
|
531
|
-
}
|
|
532
|
-
case "clone": {
|
|
533
|
-
const dst = r.destination?.path ?? "unknown";
|
|
534
|
-
const remote = r.remoteFetched ? " (fetched from remote)" : "";
|
|
535
|
-
const over = r.overwritten ? " (overwritten)" : "";
|
|
536
|
-
return `Cloned${remote} → ${dst}${over}`;
|
|
537
|
-
}
|
|
538
|
-
default:
|
|
539
|
-
return null; // fall through to YAML
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
function formatWorkflowListPlain(result) {
|
|
543
|
-
const runs = Array.isArray(result.runs) ? result.runs : [];
|
|
544
|
-
if (runs.length === 0)
|
|
545
|
-
return "No workflow runs found.";
|
|
546
|
-
return runs
|
|
547
|
-
.map((run) => {
|
|
548
|
-
const id = typeof run.id === "string" ? run.id : "unknown";
|
|
549
|
-
const ref = typeof run.workflowRef === "string" ? run.workflowRef : "workflow:unknown";
|
|
550
|
-
const status = typeof run.status === "string" ? run.status : "unknown";
|
|
551
|
-
const currentStep = typeof run.currentStepId === "string" ? ` (current: ${run.currentStepId})` : "";
|
|
552
|
-
return `${id} ${ref} [${status}]${currentStep}`;
|
|
553
|
-
})
|
|
554
|
-
.join("\n");
|
|
555
|
-
}
|
|
556
|
-
function formatWorkflowStatusPlain(result) {
|
|
557
|
-
const run = typeof result.run === "object" && result.run !== null ? result.run : undefined;
|
|
558
|
-
const workflow = typeof result.workflow === "object" && result.workflow !== null
|
|
559
|
-
? result.workflow
|
|
560
|
-
: undefined;
|
|
561
|
-
if (!run || !workflow)
|
|
562
|
-
return null;
|
|
563
|
-
const lines = [
|
|
564
|
-
`workflow: ${String(workflow.ref ?? "workflow:unknown")}`,
|
|
565
|
-
`run: ${String(run.id ?? "unknown")}`,
|
|
566
|
-
`title: ${String(run.workflowTitle ?? workflow.title ?? "Workflow")}`,
|
|
567
|
-
`status: ${String(run.status ?? "unknown")}`,
|
|
568
|
-
];
|
|
569
|
-
if (run.currentStepId)
|
|
570
|
-
lines.push(`currentStep: ${String(run.currentStepId)}`);
|
|
571
|
-
const steps = Array.isArray(workflow.steps) ? workflow.steps : [];
|
|
572
|
-
if (steps.length > 0) {
|
|
573
|
-
lines.push("steps:");
|
|
574
|
-
for (const step of steps) {
|
|
575
|
-
const title = typeof step.title === "string" ? step.title : "Untitled step";
|
|
576
|
-
const id = typeof step.id === "string" ? step.id : "unknown";
|
|
577
|
-
const status = typeof step.status === "string" ? step.status : "unknown";
|
|
578
|
-
lines.push(` - ${title} [${id}] (${status})`);
|
|
579
|
-
if (typeof step.notes === "string" && step.notes.trim()) {
|
|
580
|
-
lines.push(` notes: ${step.notes}`);
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
return lines.join("\n");
|
|
585
|
-
}
|
|
586
|
-
function formatWorkflowNextPlain(result) {
|
|
587
|
-
const base = formatWorkflowStatusPlain(result);
|
|
588
|
-
const step = typeof result.step === "object" && result.step !== null ? result.step : undefined;
|
|
589
|
-
if (!step)
|
|
590
|
-
return base;
|
|
591
|
-
const lines = base ? [base, "", "next:"] : ["next:"];
|
|
592
|
-
lines.push(` ${String(step.title ?? "Untitled step")} [${String(step.id ?? "unknown")}]`);
|
|
593
|
-
if (typeof step.instructions === "string" && step.instructions.trim()) {
|
|
594
|
-
lines.push(` instructions: ${step.instructions.replace(/\n+/g, " ").trim()}`);
|
|
595
|
-
}
|
|
596
|
-
const completion = Array.isArray(step.completionCriteria) ? step.completionCriteria : [];
|
|
597
|
-
if (completion.length > 0) {
|
|
598
|
-
lines.push(" completion:");
|
|
599
|
-
for (const criterion of completion) {
|
|
600
|
-
lines.push(` - ${String(criterion)}`);
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
return lines.join("\n");
|
|
604
|
-
}
|
|
605
|
-
function formatSearchPlain(r, detail) {
|
|
606
|
-
const hits = r.hits ?? [];
|
|
607
|
-
const registryHits = r.registryHits ?? [];
|
|
608
|
-
const allHits = [...hits, ...registryHits];
|
|
609
|
-
if (allHits.length === 0) {
|
|
610
|
-
return r.tip ? String(r.tip) : "No results found.";
|
|
611
|
-
}
|
|
612
|
-
const lines = [];
|
|
613
|
-
for (const hit of allHits) {
|
|
614
|
-
const type = hit.type ?? "unknown";
|
|
615
|
-
const name = hit.name ?? "unnamed";
|
|
616
|
-
const score = hit.score != null ? ` (score: ${hit.score})` : "";
|
|
617
|
-
const desc = hit.description ? ` ${hit.description}` : "";
|
|
618
|
-
lines.push(`${type}: ${name}${score}`);
|
|
619
|
-
if (desc)
|
|
620
|
-
lines.push(desc);
|
|
621
|
-
if (hit.id)
|
|
622
|
-
lines.push(` id: ${String(hit.id)}`);
|
|
623
|
-
if (hit.ref)
|
|
624
|
-
lines.push(` ref: ${String(hit.ref)}`);
|
|
625
|
-
if (hit.origin !== undefined)
|
|
626
|
-
lines.push(` origin: ${String(hit.origin)}`);
|
|
627
|
-
if (hit.size)
|
|
628
|
-
lines.push(` size: ${String(hit.size)}`);
|
|
629
|
-
if (hit.action)
|
|
630
|
-
lines.push(` action: ${String(hit.action)}`);
|
|
631
|
-
if (hit.run)
|
|
632
|
-
lines.push(` run: ${String(hit.run)}`);
|
|
633
|
-
if (Array.isArray(hit.tags) && hit.tags.length > 0)
|
|
634
|
-
lines.push(` tags: ${hit.tags.join(", ")}`);
|
|
635
|
-
if (hit.curated !== undefined)
|
|
636
|
-
lines.push(` curated: ${String(hit.curated)}`);
|
|
637
|
-
if (detail === "full") {
|
|
638
|
-
if (hit.path)
|
|
639
|
-
lines.push(` path: ${String(hit.path)}`);
|
|
640
|
-
if (hit.editable != null)
|
|
641
|
-
lines.push(` editable: ${String(hit.editable)}`);
|
|
642
|
-
if (hit.editHint)
|
|
643
|
-
lines.push(` editHint: ${String(hit.editHint)}`);
|
|
644
|
-
const whyMatched = hit.whyMatched;
|
|
645
|
-
if (whyMatched && whyMatched.length > 0) {
|
|
646
|
-
lines.push(` whyMatched: ${whyMatched.join(", ")}`);
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
lines.push(""); // blank line between hits
|
|
650
|
-
}
|
|
651
|
-
if (detail === "full" && r.timing) {
|
|
652
|
-
const timing = r.timing;
|
|
653
|
-
const parts = [];
|
|
654
|
-
if (timing.totalMs != null)
|
|
655
|
-
parts.push(`total: ${timing.totalMs}ms`);
|
|
656
|
-
if (timing.rankMs != null)
|
|
657
|
-
parts.push(`rank: ${timing.rankMs}ms`);
|
|
658
|
-
if (timing.embedMs != null)
|
|
659
|
-
parts.push(`embed: ${timing.embedMs}ms`);
|
|
660
|
-
if (parts.length > 0)
|
|
661
|
-
lines.push(`timing: ${parts.join(", ")}`);
|
|
662
|
-
}
|
|
663
|
-
return lines.join("\n").trimEnd();
|
|
664
|
-
}
|
|
665
|
-
function formatWikiListPlain(r) {
|
|
666
|
-
const wikis = Array.isArray(r.wikis) ? r.wikis : [];
|
|
667
|
-
if (wikis.length === 0)
|
|
668
|
-
return "No wikis. Create one with `akm wiki create <name>` or register one with `akm wiki register <name> <path-or-repo>`.";
|
|
669
|
-
const lines = ["NAME\tPAGES\tRAWS\tLAST-MODIFIED"];
|
|
670
|
-
for (const w of wikis) {
|
|
671
|
-
const name = typeof w.name === "string" ? w.name : "?";
|
|
672
|
-
const pages = typeof w.pages === "number" ? w.pages : 0;
|
|
673
|
-
const raws = typeof w.raws === "number" ? w.raws : 0;
|
|
674
|
-
const modified = typeof w.lastModified === "string" ? w.lastModified : "-";
|
|
675
|
-
lines.push(`${name}\t${pages}\t${raws}\t${modified}`);
|
|
676
|
-
}
|
|
677
|
-
return lines.join("\n");
|
|
678
|
-
}
|
|
679
|
-
function formatWikiShowPlain(r) {
|
|
680
|
-
const lines = [];
|
|
681
|
-
if (r.name)
|
|
682
|
-
lines.push(`# wiki: ${String(r.name)}`);
|
|
683
|
-
if (r.path)
|
|
684
|
-
lines.push(`path: ${String(r.path)}`);
|
|
685
|
-
if (r.description)
|
|
686
|
-
lines.push(`description: ${String(r.description)}`);
|
|
687
|
-
if (typeof r.pages === "number")
|
|
688
|
-
lines.push(`pages: ${r.pages}`);
|
|
689
|
-
if (typeof r.raws === "number")
|
|
690
|
-
lines.push(`raws: ${r.raws}`);
|
|
691
|
-
if (r.lastModified)
|
|
692
|
-
lines.push(`lastModified: ${String(r.lastModified)}`);
|
|
693
|
-
const recentLog = Array.isArray(r.recentLog) ? r.recentLog : [];
|
|
694
|
-
if (recentLog.length > 0) {
|
|
695
|
-
lines.push("", "recent log:");
|
|
696
|
-
for (const entry of recentLog) {
|
|
697
|
-
lines.push(entry);
|
|
698
|
-
lines.push("");
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
return lines.join("\n").trimEnd();
|
|
702
|
-
}
|
|
703
|
-
function formatWikiCreatePlain(r) {
|
|
704
|
-
const created = Array.isArray(r.created) ? r.created : [];
|
|
705
|
-
const skipped = Array.isArray(r.skipped) ? r.skipped : [];
|
|
706
|
-
const lines = [`Created wiki ${String(r.ref ?? r.name)} at ${String(r.path ?? "?")}`];
|
|
707
|
-
if (created.length > 0)
|
|
708
|
-
lines.push(` created: ${created.length} file(s)`);
|
|
709
|
-
if (skipped.length > 0)
|
|
710
|
-
lines.push(` skipped: ${skipped.length} existing file(s)`);
|
|
711
|
-
return lines.join("\n");
|
|
712
|
-
}
|
|
713
|
-
function formatWikiRemovePlain(r) {
|
|
714
|
-
const preserved = r.preservedRaw === true;
|
|
715
|
-
const removed = Array.isArray(r.removed) ? r.removed.length : 0;
|
|
716
|
-
const base = `Removed wiki ${String(r.name ?? "?")} (${removed} path(s))`;
|
|
717
|
-
return preserved ? `${base}; preserved ${String(r.rawPath ?? "raw/")}` : base;
|
|
718
|
-
}
|
|
719
|
-
function formatWikiPagesPlain(r) {
|
|
720
|
-
const pages = Array.isArray(r.pages) ? r.pages : [];
|
|
721
|
-
if (pages.length === 0)
|
|
722
|
-
return `No pages in wiki:${String(r.wiki ?? "?")}.`;
|
|
723
|
-
const lines = [];
|
|
724
|
-
for (const p of pages) {
|
|
725
|
-
const ref = String(p.ref ?? "?");
|
|
726
|
-
const kind = typeof p.pageKind === "string" ? ` [${p.pageKind}]` : "";
|
|
727
|
-
const desc = typeof p.description === "string" && p.description ? ` — ${p.description}` : "";
|
|
728
|
-
lines.push(`${ref}${kind}${desc}`);
|
|
729
|
-
}
|
|
730
|
-
return lines.join("\n");
|
|
731
|
-
}
|
|
732
|
-
function formatWikiStashPlain(r) {
|
|
733
|
-
const slug = String(r.slug ?? "?");
|
|
734
|
-
const pathValue = String(r.path ?? "?");
|
|
735
|
-
return `Stashed ${slug} → ${pathValue}`;
|
|
736
|
-
}
|
|
737
|
-
function formatWikiLintPlain(r) {
|
|
738
|
-
const findings = Array.isArray(r.findings) ? r.findings : [];
|
|
739
|
-
const pagesScanned = typeof r.pagesScanned === "number" ? r.pagesScanned : 0;
|
|
740
|
-
const rawsScanned = typeof r.rawsScanned === "number" ? r.rawsScanned : 0;
|
|
741
|
-
const header = `${findings.length} finding(s) in wiki:${String(r.wiki ?? "?")} (${pagesScanned} page(s), ${rawsScanned} raw(s))`;
|
|
742
|
-
if (findings.length === 0)
|
|
743
|
-
return `${header} — clean.`;
|
|
744
|
-
const lines = [header];
|
|
745
|
-
for (const f of findings) {
|
|
746
|
-
const kind = String(f.kind ?? "?");
|
|
747
|
-
const message = String(f.message ?? "");
|
|
748
|
-
lines.push(`- [${kind}] ${message}`);
|
|
749
|
-
}
|
|
750
|
-
return lines.join("\n");
|
|
751
|
-
}
|
|
752
|
-
function formatWikiIngestPlain(r) {
|
|
753
|
-
if (typeof r.workflow === "string")
|
|
754
|
-
return r.workflow;
|
|
755
|
-
return JSON.stringify(r, null, 2);
|
|
756
|
-
}
|
|
757
|
-
function formatCuratePlain(r, detail) {
|
|
758
|
-
const query = typeof r.query === "string" ? r.query : "";
|
|
759
|
-
const summary = typeof r.summary === "string" ? r.summary : "";
|
|
760
|
-
const items = Array.isArray(r.items) ? r.items : [];
|
|
761
|
-
const lines = [`Curated results for "${query}"`];
|
|
762
|
-
if (summary)
|
|
763
|
-
lines.push(summary);
|
|
764
|
-
if (items.length === 0) {
|
|
765
|
-
if (r.tip)
|
|
766
|
-
lines.push(String(r.tip));
|
|
767
|
-
return lines.join("\n");
|
|
768
|
-
}
|
|
769
|
-
for (const item of items) {
|
|
770
|
-
const type = typeof item.type === "string" ? item.type : "unknown";
|
|
771
|
-
const name = typeof item.name === "string" ? item.name : "unnamed";
|
|
772
|
-
lines.push("");
|
|
773
|
-
lines.push(`[${type}] ${name}`);
|
|
774
|
-
if (item.description)
|
|
775
|
-
lines.push(` ${String(item.description)}`);
|
|
776
|
-
if (item.preview)
|
|
777
|
-
lines.push(` preview: ${String(item.preview)}`);
|
|
778
|
-
if (item.ref)
|
|
779
|
-
lines.push(` ref: ${String(item.ref)}`);
|
|
780
|
-
if (item.id)
|
|
781
|
-
lines.push(` id: ${String(item.id)}`);
|
|
782
|
-
if (Array.isArray(item.parameters) && item.parameters.length > 0) {
|
|
783
|
-
lines.push(` parameters: ${item.parameters.join(", ")}`);
|
|
784
|
-
}
|
|
785
|
-
if (item.run)
|
|
786
|
-
lines.push(` run: ${String(item.run)}`);
|
|
787
|
-
if (item.followUp)
|
|
788
|
-
lines.push(` show: ${String(item.followUp)}`);
|
|
789
|
-
if (detail !== "brief" && item.reason)
|
|
790
|
-
lines.push(` why: ${String(item.reason)}`);
|
|
791
|
-
}
|
|
792
|
-
const warnings = Array.isArray(r.warnings) ? r.warnings : [];
|
|
793
|
-
if (warnings.length > 0) {
|
|
794
|
-
lines.push("");
|
|
795
|
-
lines.push("Warnings:");
|
|
796
|
-
for (const warning of warnings) {
|
|
797
|
-
lines.push(`- ${String(warning)}`);
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
return lines.join("\n");
|
|
801
|
-
}
|
|
802
|
-
const CURATE_FALLBACK_FILTER_WORDS = new Set([
|
|
803
|
-
"a",
|
|
804
|
-
"an",
|
|
805
|
-
"and",
|
|
806
|
-
"for",
|
|
807
|
-
"how",
|
|
808
|
-
"i",
|
|
809
|
-
"in",
|
|
810
|
-
"of",
|
|
811
|
-
"or",
|
|
812
|
-
"the",
|
|
813
|
-
"to",
|
|
814
|
-
"with",
|
|
815
|
-
]);
|
|
816
|
-
const CURATED_TYPE_FALLBACK_ORDER = ["skill", "command", "script", "knowledge", "agent", "memory"];
|
|
817
|
-
const CURATED_TYPE_FALLBACK_INDEX = new Map(CURATED_TYPE_FALLBACK_ORDER.map((type, index) => [type, index]));
|
|
818
|
-
const MIN_CURATE_FALLBACK_TOKEN_LENGTH = 3;
|
|
819
|
-
const MAX_CURATE_FALLBACK_KEYWORDS = 6;
|
|
820
|
-
const CURATE_SEARCH_LIMIT_MULTIPLIER = 4;
|
|
821
|
-
const MIN_CURATE_SEARCH_LIMIT = 12;
|
|
822
|
-
async function curateSearchResults(query, result, limit, selectedType) {
|
|
823
|
-
const stashHits = result.hits.filter((hit) => hit.type !== "registry");
|
|
824
|
-
const registryHits = result.registryHits ?? [];
|
|
825
|
-
let selectedStashHits;
|
|
826
|
-
if (selectedType && selectedType !== "any") {
|
|
827
|
-
selectedStashHits = stashHits.slice(0, limit);
|
|
828
|
-
}
|
|
829
|
-
else {
|
|
830
|
-
const bestByType = new Map();
|
|
831
|
-
for (const hit of stashHits) {
|
|
832
|
-
if (!bestByType.has(hit.type))
|
|
833
|
-
bestByType.set(hit.type, hit);
|
|
834
|
-
}
|
|
835
|
-
const orderedTypes = orderCuratedTypes(query, Array.from(bestByType.keys()));
|
|
836
|
-
selectedStashHits = orderedTypes
|
|
837
|
-
.map((type) => bestByType.get(type))
|
|
838
|
-
.filter((hit) => Boolean(hit));
|
|
839
|
-
}
|
|
840
|
-
const selectedRegistryHits = selectedStashHits.length >= limit ? [] : registryHits.slice(0, Math.min(2, limit - selectedStashHits.length));
|
|
841
|
-
const items = [
|
|
842
|
-
...(await Promise.all(selectedStashHits.slice(0, limit).map((hit) => enrichCuratedStashHit(query, hit)))),
|
|
843
|
-
...selectedRegistryHits.map((hit) => buildCuratedRegistryItem(query, hit)),
|
|
844
|
-
].slice(0, limit);
|
|
845
|
-
return {
|
|
846
|
-
query,
|
|
847
|
-
summary: buildCurateSummary(query, items),
|
|
848
|
-
items,
|
|
849
|
-
...(result.warnings?.length ? { warnings: result.warnings } : {}),
|
|
850
|
-
...(result.tip ? { tip: result.tip } : {}),
|
|
851
|
-
};
|
|
852
|
-
}
|
|
853
|
-
function orderCuratedTypes(query, types) {
|
|
854
|
-
const lower = query.toLowerCase();
|
|
855
|
-
const boosts = new Map();
|
|
856
|
-
const addBoost = (type, amount) => boosts.set(type, (boosts.get(type) ?? 0) + amount);
|
|
857
|
-
if (/(run|script|bash|shell|cli|execute|automation|deploy|build|test|lint)/.test(lower)) {
|
|
858
|
-
addBoost("script", 6);
|
|
859
|
-
addBoost("command", 4);
|
|
860
|
-
}
|
|
861
|
-
if (/(guide|docs?|readme|reference|how|explain|learn|why)/.test(lower)) {
|
|
862
|
-
addBoost("knowledge", 6);
|
|
863
|
-
addBoost("skill", 4);
|
|
864
|
-
}
|
|
865
|
-
if (/(agent|assistant|planner|review|analy[sz]e|architect|prompt)/.test(lower)) {
|
|
866
|
-
addBoost("agent", 6);
|
|
867
|
-
addBoost("skill", 3);
|
|
868
|
-
}
|
|
869
|
-
if (/(config|template|release|generate|command)/.test(lower)) {
|
|
870
|
-
addBoost("command", 5);
|
|
871
|
-
}
|
|
872
|
-
if (/(memory|context|recall|remember)/.test(lower)) {
|
|
873
|
-
addBoost("memory", 6);
|
|
874
|
-
}
|
|
875
|
-
return [...types].sort((a, b) => {
|
|
876
|
-
const boostDiff = (boosts.get(b) ?? 0) - (boosts.get(a) ?? 0);
|
|
877
|
-
if (boostDiff !== 0)
|
|
878
|
-
return boostDiff;
|
|
879
|
-
return ((CURATED_TYPE_FALLBACK_INDEX.get(a) ?? Number.MAX_SAFE_INTEGER) -
|
|
880
|
-
(CURATED_TYPE_FALLBACK_INDEX.get(b) ?? Number.MAX_SAFE_INTEGER));
|
|
881
|
-
});
|
|
882
|
-
}
|
|
883
|
-
async function enrichCuratedStashHit(query, hit) {
|
|
884
|
-
let shown;
|
|
885
|
-
try {
|
|
886
|
-
shown = await akmShowUnified({ ref: hit.ref });
|
|
887
|
-
}
|
|
888
|
-
catch {
|
|
889
|
-
shown = undefined;
|
|
890
|
-
}
|
|
891
|
-
const description = shown?.description ?? hit.description;
|
|
892
|
-
const preview = buildCuratedPreview(shown, hit);
|
|
893
|
-
return {
|
|
894
|
-
source: "stash",
|
|
895
|
-
type: shown?.type ?? hit.type,
|
|
896
|
-
name: shown?.name ?? hit.name,
|
|
897
|
-
ref: hit.ref,
|
|
898
|
-
...(description ? { description } : {}),
|
|
899
|
-
...(preview ? { preview } : {}),
|
|
900
|
-
...(shown?.parameters?.length ? { parameters: shown.parameters } : {}),
|
|
901
|
-
...(shown?.run ? { run: shown.run } : {}),
|
|
902
|
-
followUp: `akm show ${hit.ref}`,
|
|
903
|
-
reason: buildCuratedReason(query, shown?.type ?? hit.type),
|
|
904
|
-
...(hit.score !== undefined ? { score: hit.score } : {}),
|
|
905
|
-
};
|
|
906
|
-
}
|
|
907
|
-
function buildCuratedRegistryItem(query, hit) {
|
|
908
|
-
return {
|
|
909
|
-
source: "registry",
|
|
910
|
-
type: "registry",
|
|
911
|
-
name: hit.name,
|
|
912
|
-
id: hit.id,
|
|
913
|
-
...(hit.description ? { description: hit.description } : {}),
|
|
914
|
-
followUp: hit.action ?? `akm add ${hit.id}`,
|
|
915
|
-
reason: `Useful external source to explore for ${query}.`,
|
|
916
|
-
...(hit.score !== undefined ? { score: hit.score } : {}),
|
|
917
|
-
};
|
|
918
|
-
}
|
|
919
|
-
function firstNonEmpty(values) {
|
|
920
|
-
return values.find((value) => typeof value === "string" && value.trim().length > 0);
|
|
921
|
-
}
|
|
922
|
-
function buildCuratedPreview(shown, hit) {
|
|
923
|
-
if (shown?.run)
|
|
924
|
-
return truncateDescription(`run ${shown.run}`, 160);
|
|
925
|
-
const payload = firstNonEmpty([shown?.template, shown?.prompt, shown?.content, hit.description])
|
|
926
|
-
?.replace(/\s+/g, " ")
|
|
927
|
-
.trim();
|
|
928
|
-
return payload ? truncateDescription(payload, 160) : undefined;
|
|
929
|
-
}
|
|
930
|
-
function buildCuratedReason(query, type) {
|
|
931
|
-
switch (type) {
|
|
932
|
-
case "script":
|
|
933
|
-
return `Best runnable script match for "${query}".`;
|
|
934
|
-
case "command":
|
|
935
|
-
return `Best reusable command/template match for "${query}".`;
|
|
936
|
-
case "knowledge":
|
|
937
|
-
return `Best reference document match for "${query}".`;
|
|
938
|
-
case "skill":
|
|
939
|
-
return `Best instructions/workflow match for "${query}".`;
|
|
940
|
-
case "agent":
|
|
941
|
-
return `Best specialized agent prompt match for "${query}".`;
|
|
942
|
-
case "memory":
|
|
943
|
-
return `Best saved context match for "${query}".`;
|
|
944
|
-
default:
|
|
945
|
-
return `Best ${type} match for "${query}".`;
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
function buildCurateSummary(query, items) {
|
|
949
|
-
if (items.length === 0) {
|
|
950
|
-
return `No curated assets were selected for "${query}".`;
|
|
951
|
-
}
|
|
952
|
-
const labels = items.map((item) => `${item.type}:${item.name}`);
|
|
953
|
-
return `Selected ${items.length} high-signal result${items.length === 1 ? "" : "s"}: ${labels.join(", ")}.`;
|
|
954
|
-
}
|
|
955
|
-
function hasSearchResults(result) {
|
|
956
|
-
return result.hits.length > 0 || (result.registryHits?.length ?? 0) > 0;
|
|
957
|
-
}
|
|
958
|
-
/**
|
|
959
|
-
* Extract a small set of fallback keywords when a prompt-style curate query
|
|
960
|
-
* returns no hits as a whole phrase.
|
|
961
|
-
*
|
|
962
|
-
* We keep up to MAX_CURATE_FALLBACK_KEYWORDS distinct keywords and drop short
|
|
963
|
-
* or common filler words so follow-up searches stay inexpensive while focusing
|
|
964
|
-
* on higher-signal terms.
|
|
965
|
-
*/
|
|
966
|
-
function deriveCurateFallbackQueries(query) {
|
|
967
|
-
return Array.from(new Set(query
|
|
968
|
-
.toLowerCase()
|
|
969
|
-
.split(/[^a-z0-9]+/)
|
|
970
|
-
.map((token) => token.trim())
|
|
971
|
-
// Keep longer tokens so fallback stays focused on higher-signal terms
|
|
972
|
-
// and avoids broad one- and two-letter matches that overwhelm curation.
|
|
973
|
-
.filter((token) => token.length >= MIN_CURATE_FALLBACK_TOKEN_LENGTH && !CURATE_FALLBACK_FILTER_WORDS.has(token)))).slice(0, MAX_CURATE_FALLBACK_KEYWORDS);
|
|
974
|
-
}
|
|
975
|
-
function mergeCurateSearchResponses(base, extras) {
|
|
976
|
-
const hitsByRef = new Map();
|
|
977
|
-
for (const hit of base.hits.filter((entry) => entry.type !== "registry")) {
|
|
978
|
-
hitsByRef.set(hit.ref, hit);
|
|
979
|
-
}
|
|
980
|
-
for (const result of extras) {
|
|
981
|
-
for (const hit of result.hits.filter((entry) => entry.type !== "registry")) {
|
|
982
|
-
const existing = hitsByRef.get(hit.ref);
|
|
983
|
-
if (!existing || (hit.score ?? 0) > (existing.score ?? 0)) {
|
|
984
|
-
hitsByRef.set(hit.ref, hit);
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
const registryById = new Map();
|
|
989
|
-
for (const hit of base.registryHits ?? []) {
|
|
990
|
-
registryById.set(hit.id, hit);
|
|
991
|
-
}
|
|
992
|
-
for (const result of extras) {
|
|
993
|
-
for (const hit of result.registryHits ?? []) {
|
|
994
|
-
const existing = registryById.get(hit.id);
|
|
995
|
-
if (!existing || (hit.score ?? 0) > (existing.score ?? 0)) {
|
|
996
|
-
registryById.set(hit.id, hit);
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
const warnings = Array.from(new Set([...(base.warnings ?? []), ...extras.flatMap((result) => result.warnings ?? [])]));
|
|
1001
|
-
const mergedHits = [...hitsByRef.values()].sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
|
|
1002
|
-
const mergedRegistryHits = [...registryById.values()].sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
|
|
1003
|
-
return {
|
|
1004
|
-
...base,
|
|
1005
|
-
hits: mergedHits,
|
|
1006
|
-
...(mergedRegistryHits.length > 0 ? { registryHits: mergedRegistryHits } : {}),
|
|
1007
|
-
...(warnings.length > 0 ? { warnings } : {}),
|
|
1008
|
-
...(mergedHits.length > 0 || mergedRegistryHits.length > 0 ? { tip: undefined } : {}),
|
|
1009
|
-
};
|
|
1010
|
-
}
|
|
1011
|
-
async function searchForCuration(input) {
|
|
1012
|
-
const initial = await akmSearch(input);
|
|
1013
|
-
if (hasSearchResults(initial))
|
|
1014
|
-
return initial;
|
|
1015
|
-
const fallbackQueries = deriveCurateFallbackQueries(input.query);
|
|
1016
|
-
if (fallbackQueries.length <= 1)
|
|
1017
|
-
return initial;
|
|
1018
|
-
const fallbackResults = await Promise.all(fallbackQueries.map((token) => akmSearch({
|
|
1019
|
-
query: token,
|
|
1020
|
-
type: input.type,
|
|
1021
|
-
limit: input.limit,
|
|
1022
|
-
source: input.source,
|
|
1023
|
-
})));
|
|
1024
|
-
return mergeCurateSearchResponses(initial, fallbackResults);
|
|
1025
|
-
}
|
|
1026
85
|
/**
|
|
1027
86
|
* Module Naming:
|
|
1028
87
|
* - stash-* : Asset operations (search, show, add, clone)
|
|
1029
88
|
* - stash-provider-* : Runtime data source providers (filesystem, openviking)
|
|
1030
89
|
* - registry-* : Discovery from remote registries (npm, GitHub)
|
|
1031
|
-
* - installed-
|
|
90
|
+
* - installed-stashes : Unified source operations (list, remove, update)
|
|
1032
91
|
*/
|
|
1033
92
|
const setupCommand = defineCommand({
|
|
1034
93
|
meta: {
|
|
@@ -1093,7 +152,7 @@ const searchCommand = defineCommand({
|
|
|
1093
152
|
limit: { type: "string", description: "Maximum number of results" },
|
|
1094
153
|
source: { type: "string", description: "Search source (stash|registry|both)", default: "stash" },
|
|
1095
154
|
format: { type: "string", description: "Output format (json|jsonl|text|yaml)" },
|
|
1096
|
-
detail: { type: "string", description: "Detail level (brief|normal|full|summary)" },
|
|
155
|
+
detail: { type: "string", description: "Detail level (brief|normal|full|summary|agent)" },
|
|
1097
156
|
},
|
|
1098
157
|
async run({ args }) {
|
|
1099
158
|
await runWithJsonErrors(async () => {
|
|
@@ -1129,15 +188,7 @@ const curateCommand = defineCommand({
|
|
|
1129
188
|
}
|
|
1130
189
|
const limit = limitRaw && limitRaw > 0 ? limitRaw : 4;
|
|
1131
190
|
const source = parseSearchSource(args.source ?? "stash");
|
|
1132
|
-
const
|
|
1133
|
-
query: args.query,
|
|
1134
|
-
type,
|
|
1135
|
-
// Search deeper than the final curated count so we can pick one strong
|
|
1136
|
-
// match per type and still have room for fallback retries.
|
|
1137
|
-
limit: Math.max(limit * CURATE_SEARCH_LIMIT_MULTIPLIER, MIN_CURATE_SEARCH_LIMIT),
|
|
1138
|
-
source,
|
|
1139
|
-
});
|
|
1140
|
-
const curated = await curateSearchResults(args.query, searchResult, limit, type);
|
|
191
|
+
const curated = await akmCurate({ query: args.query, type, limit, source });
|
|
1141
192
|
output("curate", curated);
|
|
1142
193
|
});
|
|
1143
194
|
},
|
|
@@ -1176,16 +227,6 @@ const addCommand = defineCommand({
|
|
|
1176
227
|
async run({ args }) {
|
|
1177
228
|
await runWithJsonErrors(async () => {
|
|
1178
229
|
const ref = args.ref.trim();
|
|
1179
|
-
// Context-hub convenience alias
|
|
1180
|
-
if (ref === CONTEXT_HUB_ALIAS_REF) {
|
|
1181
|
-
const result = addStash({
|
|
1182
|
-
target: CONTEXT_HUB_ALIAS_URL,
|
|
1183
|
-
providerType: "git",
|
|
1184
|
-
name: "context-hub",
|
|
1185
|
-
});
|
|
1186
|
-
output("stash-add", result);
|
|
1187
|
-
return;
|
|
1188
|
-
}
|
|
1189
230
|
// URL with --provider → stash source (remote or git provider)
|
|
1190
231
|
if (args.provider) {
|
|
1191
232
|
if (shouldWarnOnPlainHttp(ref)) {
|
|
@@ -1213,7 +254,7 @@ const addCommand = defineCommand({
|
|
|
1213
254
|
options: parsedOptions,
|
|
1214
255
|
writable: args.writable,
|
|
1215
256
|
});
|
|
1216
|
-
output("
|
|
257
|
+
output("add", result);
|
|
1217
258
|
return;
|
|
1218
259
|
}
|
|
1219
260
|
if (shouldWarnOnPlainHttp(ref)) {
|
|
@@ -1324,7 +365,7 @@ const upgradeCommand = defineCommand({
|
|
|
1324
365
|
args: {
|
|
1325
366
|
check: { type: "boolean", description: "Check for updates without installing", default: false },
|
|
1326
367
|
force: { type: "boolean", description: "Force upgrade even if on latest", default: false },
|
|
1327
|
-
|
|
368
|
+
"skip-checksum": {
|
|
1328
369
|
type: "boolean",
|
|
1329
370
|
description: "Skip checksum verification (not recommended)",
|
|
1330
371
|
default: false,
|
|
@@ -1337,7 +378,8 @@ const upgradeCommand = defineCommand({
|
|
|
1337
378
|
output("upgrade", check);
|
|
1338
379
|
return;
|
|
1339
380
|
}
|
|
1340
|
-
const
|
|
381
|
+
const skipChecksum = Boolean(args["skip-checksum"]);
|
|
382
|
+
const result = await performUpgrade(check, { force: args.force, skipChecksum });
|
|
1341
383
|
output("upgrade", result);
|
|
1342
384
|
});
|
|
1343
385
|
},
|
|
@@ -1350,38 +392,43 @@ const showCommand = defineCommand({
|
|
|
1350
392
|
args: {
|
|
1351
393
|
ref: { type: "positional", description: "Asset ref (type:name)", required: true },
|
|
1352
394
|
format: { type: "string", description: "Output format (json|jsonl|text|yaml)" },
|
|
1353
|
-
detail: { type: "string", description: "Detail level (brief|normal|full|summary)" },
|
|
1354
|
-
akmView: { type: "string", description: "Internal positional knowledge view mode parser" },
|
|
1355
|
-
akmHeading: { type: "string", description: "Internal positional section heading parser" },
|
|
1356
|
-
akmStart: { type: "string", description: "Internal positional start-line parser" },
|
|
1357
|
-
akmEnd: { type: "string", description: "Internal positional end-line parser" },
|
|
395
|
+
detail: { type: "string", description: "Detail level (brief|normal|full|summary|agent)" },
|
|
1358
396
|
},
|
|
1359
397
|
async run({ args }) {
|
|
1360
398
|
await runWithJsonErrors(async () => {
|
|
399
|
+
// The knowledge-view positional syntax (`akm show knowledge:foo section "Auth"`)
|
|
400
|
+
// is rewritten to `--akmView` / `--akmHeading` / `--akmStart` / `--akmEnd`
|
|
401
|
+
// by `normalizeShowArgv` before citty parses argv. We read those values
|
|
402
|
+
// directly via `parseFlagValue` so the flags don't surface as user-facing
|
|
403
|
+
// options in `akm show --help`.
|
|
404
|
+
const akmView = parseFlagValue(process.argv, "--akmView");
|
|
405
|
+
const akmHeading = parseFlagValue(process.argv, "--akmHeading");
|
|
406
|
+
const akmStart = parseFlagValue(process.argv, "--akmStart");
|
|
407
|
+
const akmEnd = parseFlagValue(process.argv, "--akmEnd");
|
|
1361
408
|
let view;
|
|
1362
|
-
if (
|
|
1363
|
-
switch (
|
|
409
|
+
if (akmView) {
|
|
410
|
+
switch (akmView) {
|
|
1364
411
|
case "section":
|
|
1365
|
-
view = { mode: "section", heading:
|
|
412
|
+
view = { mode: "section", heading: akmHeading ?? "" };
|
|
1366
413
|
break;
|
|
1367
414
|
case "lines":
|
|
1368
415
|
view = {
|
|
1369
416
|
mode: "lines",
|
|
1370
|
-
start: Number(
|
|
1371
|
-
end:
|
|
417
|
+
start: Number(akmStart ?? "1"),
|
|
418
|
+
end: akmEnd ? parseInt(akmEnd, 10) : Number.MAX_SAFE_INTEGER,
|
|
1372
419
|
};
|
|
1373
420
|
break;
|
|
1374
421
|
case "toc":
|
|
1375
422
|
case "frontmatter":
|
|
1376
423
|
case "full":
|
|
1377
|
-
view = { mode:
|
|
424
|
+
view = { mode: akmView };
|
|
1378
425
|
break;
|
|
1379
426
|
default:
|
|
1380
|
-
throw new UsageError(`Unknown view mode: ${
|
|
427
|
+
throw new UsageError(`Unknown view mode: ${akmView}. Expected one of: full|toc|frontmatter|section|lines`);
|
|
1381
428
|
}
|
|
1382
429
|
}
|
|
1383
430
|
// Map CLI detail level to ShowDetailLevel for the show function
|
|
1384
|
-
const cliDetail =
|
|
431
|
+
const cliDetail = getOutputMode().detail;
|
|
1385
432
|
const showDetail = cliDetail === "summary" ? "summary" : undefined;
|
|
1386
433
|
const result = await akmShowUnified({ ref: args.ref, view, detail: showDetail });
|
|
1387
434
|
output("show", result);
|
|
@@ -1510,7 +557,7 @@ const saveCommand = defineCommand({
|
|
|
1510
557
|
// before any standalone occurrence of the same value in the save
|
|
1511
558
|
// subcommand's argv slice. This preserves legitimate invocations
|
|
1512
559
|
// like `akm save json --format json`.
|
|
1513
|
-
const parsedFormat = parseFlagValue("--format");
|
|
560
|
+
const parsedFormat = parseFlagValue(process.argv, "--format");
|
|
1514
561
|
const effectiveName = args.name !== undefined &&
|
|
1515
562
|
parsedFormat !== undefined &&
|
|
1516
563
|
args.name === parsedFormat &&
|
|
@@ -1593,7 +640,7 @@ const cloneCommand = defineCommand({
|
|
|
1593
640
|
},
|
|
1594
641
|
});
|
|
1595
642
|
const registryCommand = defineCommand({
|
|
1596
|
-
meta: { name: "registry", description: "Manage
|
|
643
|
+
meta: { name: "registry", description: "Manage stash registries" },
|
|
1597
644
|
subCommands: {
|
|
1598
645
|
list: defineCommand({
|
|
1599
646
|
meta: { name: "list", description: "List configured registries" },
|
|
@@ -1612,6 +659,11 @@ const registryCommand = defineCommand({
|
|
|
1612
659
|
name: { type: "string", description: "Human-friendly name for the registry" },
|
|
1613
660
|
provider: { type: "string", description: "Provider type (e.g. static-index, skills-sh)" },
|
|
1614
661
|
options: { type: "string", description: 'Provider options as JSON (e.g. \'{"apiKey":"key"}\').' },
|
|
662
|
+
"allow-insecure": {
|
|
663
|
+
type: "boolean",
|
|
664
|
+
description: "Allow a plain HTTP registry URL (otherwise rejected)",
|
|
665
|
+
default: false,
|
|
666
|
+
},
|
|
1615
667
|
},
|
|
1616
668
|
run({ args }) {
|
|
1617
669
|
return runWithJsonErrors(() => {
|
|
@@ -1619,7 +671,12 @@ const registryCommand = defineCommand({
|
|
|
1619
671
|
throw new UsageError("Registry URL must start with http:// or https://");
|
|
1620
672
|
}
|
|
1621
673
|
if (args.url.startsWith("http://")) {
|
|
1622
|
-
|
|
674
|
+
const allowInsecure = Boolean(args["allow-insecure"]);
|
|
675
|
+
if (!allowInsecure) {
|
|
676
|
+
throw new UsageError("Registry URL uses plain HTTP (not HTTPS). An on-path attacker could substitute a malicious index. " +
|
|
677
|
+
"Use https:// or pass --allow-insecure if you have explicitly accepted the risk.");
|
|
678
|
+
}
|
|
679
|
+
warn("Warning: registry URL uses plain HTTP (not HTTPS). --allow-insecure was set; an on-path attacker could substitute a malicious index.");
|
|
1623
680
|
}
|
|
1624
681
|
const config = loadUserConfig();
|
|
1625
682
|
const registries = [...(config.registries ?? [])];
|
|
@@ -1668,7 +725,7 @@ const registryCommand = defineCommand({
|
|
|
1668
725
|
},
|
|
1669
726
|
}),
|
|
1670
727
|
search: defineCommand({
|
|
1671
|
-
meta: { name: "search", description: "Search enabled registries for
|
|
728
|
+
meta: { name: "search", description: "Search enabled registries for stashes" },
|
|
1672
729
|
args: {
|
|
1673
730
|
query: { type: "positional", description: "Search query", required: true },
|
|
1674
731
|
limit: { type: "string", description: "Maximum number of results" },
|
|
@@ -1690,15 +747,16 @@ const registryCommand = defineCommand({
|
|
|
1690
747
|
args: {
|
|
1691
748
|
out: { type: "string", description: "Output path for the generated index", default: "index.json" },
|
|
1692
749
|
manual: { type: "string", description: "Manual entries JSON file", default: "manual-entries.json" },
|
|
1693
|
-
|
|
1694
|
-
|
|
750
|
+
"npm-registry": { type: "string", description: "Override npm registry base URL" },
|
|
751
|
+
"github-api": { type: "string", description: "Override GitHub API base URL" },
|
|
1695
752
|
},
|
|
1696
753
|
async run({ args }) {
|
|
1697
754
|
await runWithJsonErrors(async () => {
|
|
755
|
+
const argsRecord = args;
|
|
1698
756
|
const result = await buildRegistryIndex({
|
|
1699
757
|
manualEntriesPath: args.manual,
|
|
1700
|
-
npmRegistryBase:
|
|
1701
|
-
githubApiBase:
|
|
758
|
+
npmRegistryBase: typeof argsRecord["npm-registry"] === "string" ? argsRecord["npm-registry"] : undefined,
|
|
759
|
+
githubApiBase: typeof argsRecord["github-api"] === "string" ? argsRecord["github-api"] : undefined,
|
|
1702
760
|
});
|
|
1703
761
|
const outPath = writeRegistryIndex(result.index, args.out);
|
|
1704
762
|
output("registry-build-index", {
|
|
@@ -1755,12 +813,6 @@ const feedbackCommand = defineCommand({
|
|
|
1755
813
|
});
|
|
1756
814
|
},
|
|
1757
815
|
});
|
|
1758
|
-
function tryReadStdinText() {
|
|
1759
|
-
if (process.stdin.isTTY)
|
|
1760
|
-
return undefined;
|
|
1761
|
-
const input = fs.readFileSync(0, "utf8");
|
|
1762
|
-
return input.length > 0 ? input : undefined;
|
|
1763
|
-
}
|
|
1764
816
|
function normalizeMarkdownAssetName(name, fallback) {
|
|
1765
817
|
const trimmed = (name ?? fallback)
|
|
1766
818
|
.trim()
|
|
@@ -1792,13 +844,6 @@ function inferAssetName(content, fallbackPrefix, preferred) {
|
|
|
1792
844
|
const basis = preferred?.trim() || firstNonEmptyLine || fallbackPrefix;
|
|
1793
845
|
return slugifyAssetName(basis, fallbackPrefix);
|
|
1794
846
|
}
|
|
1795
|
-
function readMemoryContent(contentArg) {
|
|
1796
|
-
const content = contentArg ?? tryReadStdinText();
|
|
1797
|
-
if (!content?.trim()) {
|
|
1798
|
-
throw new UsageError("Memory content is required. Pass quoted text or pipe markdown into stdin.");
|
|
1799
|
-
}
|
|
1800
|
-
return content;
|
|
1801
|
-
}
|
|
1802
847
|
function readKnowledgeContent(source) {
|
|
1803
848
|
if (source === "-") {
|
|
1804
849
|
const content = tryReadStdinText();
|
|
@@ -1928,11 +973,11 @@ const workflowStatusCommand = defineCommand({
|
|
|
1928
973
|
const ref = `${parsed.origin ? `${parsed.origin}//` : ""}workflow:${parsed.name}`;
|
|
1929
974
|
const { runs } = listWorkflowRuns({ workflowRef: ref });
|
|
1930
975
|
if (runs.length === 0) {
|
|
1931
|
-
throw new NotFoundError(`No workflow runs found for ${ref}
|
|
976
|
+
throw new NotFoundError(`No workflow runs found for ${ref}`, "WORKFLOW_NOT_FOUND");
|
|
1932
977
|
}
|
|
1933
978
|
const mostRecent = runs[0];
|
|
1934
979
|
if (!mostRecent)
|
|
1935
|
-
throw new NotFoundError(`No workflow runs found for ${ref}
|
|
980
|
+
throw new NotFoundError(`No workflow runs found for ${ref}`, "WORKFLOW_NOT_FOUND");
|
|
1936
981
|
const result = getWorkflowStatus(mostRecent.id);
|
|
1937
982
|
output("workflow-status", result);
|
|
1938
983
|
}
|
|
@@ -2016,7 +1061,7 @@ const workflowResumeCommand = defineCommand({
|
|
|
2016
1061
|
run({ args }) {
|
|
2017
1062
|
return runWithJsonErrors(() => {
|
|
2018
1063
|
const result = resumeWorkflowRun(args.runId);
|
|
2019
|
-
output("workflow-
|
|
1064
|
+
output("workflow-resume", result);
|
|
2020
1065
|
});
|
|
2021
1066
|
},
|
|
2022
1067
|
});
|
|
@@ -2063,12 +1108,110 @@ const rememberCommand = defineCommand({
|
|
|
2063
1108
|
description: "Overwrite an existing memory with the same name",
|
|
2064
1109
|
default: false,
|
|
2065
1110
|
},
|
|
1111
|
+
tag: {
|
|
1112
|
+
type: "string",
|
|
1113
|
+
description: "Tag to add to the memory (repeatable: --tag foo --tag bar)",
|
|
1114
|
+
},
|
|
1115
|
+
expires: {
|
|
1116
|
+
type: "string",
|
|
1117
|
+
description: "Expiry duration shorthand (e.g. 30d, 12h, 6m). Resolved to an ISO date.",
|
|
1118
|
+
},
|
|
1119
|
+
source: {
|
|
1120
|
+
type: "string",
|
|
1121
|
+
description: "Source reference (URL, asset ref, file path, or any free-form string)",
|
|
1122
|
+
},
|
|
1123
|
+
auto: {
|
|
1124
|
+
type: "boolean",
|
|
1125
|
+
description: "Apply heuristic tagging (code, subjective, source, observed_at) from the body",
|
|
1126
|
+
default: false,
|
|
1127
|
+
},
|
|
1128
|
+
enrich: {
|
|
1129
|
+
type: "boolean",
|
|
1130
|
+
description: "Call the configured LLM to propose tags and description (requires LLM config)",
|
|
1131
|
+
default: false,
|
|
1132
|
+
},
|
|
2066
1133
|
},
|
|
2067
|
-
run({ args }) {
|
|
2068
|
-
return runWithJsonErrors(() => {
|
|
1134
|
+
async run({ args }) {
|
|
1135
|
+
return runWithJsonErrors(async () => {
|
|
1136
|
+
const body = readMemoryContent(args.content);
|
|
1137
|
+
// Determine if the user has requested any structured metadata mode.
|
|
1138
|
+
// Collect all --tag occurrences directly from process.argv because citty
|
|
1139
|
+
// only exposes the last value for repeated string flags.
|
|
1140
|
+
const rawTags = parseAllFlagValues("--tag");
|
|
1141
|
+
const hasStructuredArgs = rawTags.length > 0 || !!args.expires || !!args.source || args.auto || args.enrich;
|
|
1142
|
+
// Zero-flag path: write bare memory (no frontmatter). Preserve existing behaviour.
|
|
1143
|
+
if (!hasStructuredArgs) {
|
|
1144
|
+
const result = writeMarkdownAsset({
|
|
1145
|
+
type: "memory",
|
|
1146
|
+
content: body,
|
|
1147
|
+
name: args.name,
|
|
1148
|
+
fallbackPrefix: "memory",
|
|
1149
|
+
force: args.force,
|
|
1150
|
+
});
|
|
1151
|
+
output("remember", { ok: true, ...result });
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
// ── Accumulate metadata from all three modes ──────────────────────────
|
|
1155
|
+
// Start with CLI args (Mode 1: always)
|
|
1156
|
+
const tags = [...rawTags];
|
|
1157
|
+
let description;
|
|
1158
|
+
let source = args.source;
|
|
1159
|
+
let observed_at;
|
|
1160
|
+
let expires;
|
|
1161
|
+
let subjective;
|
|
1162
|
+
// Resolve --expires to an ISO date string
|
|
1163
|
+
if (args.expires) {
|
|
1164
|
+
const durationMs = parseDuration(args.expires);
|
|
1165
|
+
const expiresDate = new Date(Date.now() + durationMs);
|
|
1166
|
+
expires = expiresDate.toISOString().slice(0, 10);
|
|
1167
|
+
}
|
|
1168
|
+
// Mode 2: --auto heuristics
|
|
1169
|
+
if (args.auto) {
|
|
1170
|
+
const auto = runAutoHeuristics(body);
|
|
1171
|
+
for (const t of auto.tags) {
|
|
1172
|
+
if (!tags.includes(t))
|
|
1173
|
+
tags.push(t);
|
|
1174
|
+
}
|
|
1175
|
+
if (!source && auto.source)
|
|
1176
|
+
source = auto.source;
|
|
1177
|
+
if (!observed_at && auto.observed_at)
|
|
1178
|
+
observed_at = auto.observed_at;
|
|
1179
|
+
if (!subjective && auto.subjective)
|
|
1180
|
+
subjective = auto.subjective;
|
|
1181
|
+
}
|
|
1182
|
+
// Mode 3: --enrich LLM (fail-soft)
|
|
1183
|
+
if (args.enrich) {
|
|
1184
|
+
const enriched = await runLlmEnrich(body);
|
|
1185
|
+
for (const t of enriched.tags) {
|
|
1186
|
+
if (!tags.includes(t))
|
|
1187
|
+
tags.push(t);
|
|
1188
|
+
}
|
|
1189
|
+
if (!description && enriched.description)
|
|
1190
|
+
description = enriched.description;
|
|
1191
|
+
if (!observed_at && enriched.observed_at)
|
|
1192
|
+
observed_at = enriched.observed_at;
|
|
1193
|
+
}
|
|
1194
|
+
// ── Required-field check (before any write) ───────────────────────────
|
|
1195
|
+
const missing = [];
|
|
1196
|
+
if (tags.length === 0)
|
|
1197
|
+
missing.push("tags");
|
|
1198
|
+
if (missing.length > 0) {
|
|
1199
|
+
throw new UsageError(`Memory is missing required frontmatter field(s): ${missing.join(", ")}. ` +
|
|
1200
|
+
"Provide them via --tag <value>, --auto (heuristics), or --enrich (LLM).");
|
|
1201
|
+
}
|
|
1202
|
+
// ── Build frontmatter and write ───────────────────────────────────────
|
|
1203
|
+
const frontmatterBlock = buildMemoryFrontmatter({
|
|
1204
|
+
description,
|
|
1205
|
+
tags,
|
|
1206
|
+
source,
|
|
1207
|
+
observed_at,
|
|
1208
|
+
expires,
|
|
1209
|
+
subjective,
|
|
1210
|
+
});
|
|
1211
|
+
const contentWithFrontmatter = `${frontmatterBlock}\n${body}`;
|
|
2069
1212
|
const result = writeMarkdownAsset({
|
|
2070
1213
|
type: "memory",
|
|
2071
|
-
content:
|
|
1214
|
+
content: contentWithFrontmatter,
|
|
2072
1215
|
name: args.name,
|
|
2073
1216
|
fallbackPrefix: "memory",
|
|
2074
1217
|
force: args.force,
|
|
@@ -2122,8 +1265,10 @@ const hintsCommand = defineCommand({
|
|
|
2122
1265
|
detail: { type: "string", description: "Detail level (normal|full)", default: "normal" },
|
|
2123
1266
|
},
|
|
2124
1267
|
run({ args }) {
|
|
2125
|
-
|
|
2126
|
-
|
|
1268
|
+
if (args.detail !== "normal" && args.detail !== "full") {
|
|
1269
|
+
throw new UsageError(`Invalid value for --detail: ${args.detail}. Expected one of: normal|full.`);
|
|
1270
|
+
}
|
|
1271
|
+
process.stdout.write(loadHints(args.detail));
|
|
2127
1272
|
},
|
|
2128
1273
|
});
|
|
2129
1274
|
const helpCommand = defineCommand({
|
|
@@ -2135,12 +1280,12 @@ const helpCommand = defineCommand({
|
|
|
2135
1280
|
migrate: defineCommand({
|
|
2136
1281
|
meta: {
|
|
2137
1282
|
name: "migrate",
|
|
2138
|
-
description: "Print release notes and migration guidance for a version",
|
|
1283
|
+
description: "Print release notes and migration guidance for a version. Bundled notes live in docs/migration/release-notes/<version>.md; an unknown version lists what's available.",
|
|
2139
1284
|
},
|
|
2140
1285
|
args: {
|
|
2141
1286
|
version: {
|
|
2142
1287
|
type: "positional",
|
|
2143
|
-
description: "Version to review (for example 0.
|
|
1288
|
+
description: "Version to review (for example 0.6.0, v0.6.0, 0.6.0-rc1, or latest)",
|
|
2144
1289
|
required: true,
|
|
2145
1290
|
},
|
|
2146
1291
|
},
|
|
@@ -2186,9 +1331,10 @@ function normalizeToggleTarget(target) {
|
|
|
2186
1331
|
const normalized = target.trim().toLowerCase();
|
|
2187
1332
|
if (normalized === "skills.sh" || normalized === "skills-sh")
|
|
2188
1333
|
return "skills.sh";
|
|
2189
|
-
if (normalized === "context-hub")
|
|
2190
|
-
|
|
2191
|
-
|
|
1334
|
+
if (normalized === "context-hub") {
|
|
1335
|
+
throw new UsageError('The "context-hub" component is no longer toggleable. Run `akm add github:andrewyng/context-hub --name context-hub` to add it as a git stash.');
|
|
1336
|
+
}
|
|
1337
|
+
throw new UsageError(`Unsupported target "${target}". Supported targets: skills.sh`);
|
|
2192
1338
|
}
|
|
2193
1339
|
function toggleSkillsShRegistry(enabled) {
|
|
2194
1340
|
const config = loadUserConfig();
|
|
@@ -2211,34 +1357,17 @@ function toggleSkillsShRegistry(enabled) {
|
|
|
2211
1357
|
saveConfig({ ...config, registries });
|
|
2212
1358
|
return { changed: true, component: SKILLS_SH_NAME, enabled: true };
|
|
2213
1359
|
}
|
|
2214
|
-
function toggleContextHubStash(enabled) {
|
|
2215
|
-
const config = loadUserConfig();
|
|
2216
|
-
const stashes = [...(config.stashes ?? [])];
|
|
2217
|
-
const idx = stashes.findIndex((stash) => stash.name === CONTEXT_HUB_ALIAS_REF || stash.url === CONTEXT_HUB_ALIAS_URL);
|
|
2218
|
-
if (idx >= 0) {
|
|
2219
|
-
const existing = stashes[idx];
|
|
2220
|
-
const wasEnabled = existing.enabled !== false;
|
|
2221
|
-
existing.enabled = enabled;
|
|
2222
|
-
saveConfig({ ...config, stashes });
|
|
2223
|
-
return { changed: wasEnabled !== enabled, component: CONTEXT_HUB_ALIAS_REF, enabled };
|
|
2224
|
-
}
|
|
2225
|
-
if (!enabled) {
|
|
2226
|
-
return { changed: false, component: CONTEXT_HUB_ALIAS_REF, enabled: false };
|
|
2227
|
-
}
|
|
2228
|
-
stashes.push({ type: "git", url: CONTEXT_HUB_ALIAS_URL, name: CONTEXT_HUB_ALIAS_REF, enabled: true });
|
|
2229
|
-
saveConfig({ ...config, stashes });
|
|
2230
|
-
return { changed: true, component: CONTEXT_HUB_ALIAS_REF, enabled: true };
|
|
2231
|
-
}
|
|
2232
1360
|
function toggleComponent(targetRaw, enabled) {
|
|
2233
1361
|
const target = normalizeToggleTarget(targetRaw);
|
|
2234
1362
|
if (target === "skills.sh")
|
|
2235
1363
|
return toggleSkillsShRegistry(enabled);
|
|
2236
|
-
|
|
1364
|
+
// normalizeToggleTarget throws for any unsupported target; this is unreachable.
|
|
1365
|
+
throw new UsageError(`Unsupported target "${targetRaw}". Supported targets: skills.sh`);
|
|
2237
1366
|
}
|
|
2238
1367
|
const enableCommand = defineCommand({
|
|
2239
|
-
meta: { name: "enable", description: "Enable an optional component (skills.sh
|
|
1368
|
+
meta: { name: "enable", description: "Enable an optional component (skills.sh)" },
|
|
2240
1369
|
args: {
|
|
2241
|
-
target: { type: "positional", description: "Component to enable (skills.sh
|
|
1370
|
+
target: { type: "positional", description: "Component to enable (skills.sh)", required: true },
|
|
2242
1371
|
},
|
|
2243
1372
|
run({ args }) {
|
|
2244
1373
|
return runWithJsonErrors(() => {
|
|
@@ -2248,9 +1377,9 @@ const enableCommand = defineCommand({
|
|
|
2248
1377
|
},
|
|
2249
1378
|
});
|
|
2250
1379
|
const disableCommand = defineCommand({
|
|
2251
|
-
meta: { name: "disable", description: "Disable an optional component (skills.sh
|
|
1380
|
+
meta: { name: "disable", description: "Disable an optional component (skills.sh)" },
|
|
2252
1381
|
args: {
|
|
2253
|
-
target: { type: "positional", description: "Component to disable (skills.sh
|
|
1382
|
+
target: { type: "positional", description: "Component to disable (skills.sh)", required: true },
|
|
2254
1383
|
},
|
|
2255
1384
|
run({ args }) {
|
|
2256
1385
|
return runWithJsonErrors(() => {
|
|
@@ -2725,7 +1854,7 @@ const main = defineCommand({
|
|
|
2725
1854
|
},
|
|
2726
1855
|
args: {
|
|
2727
1856
|
format: { type: "string", description: "Output format (json|jsonl|text|yaml)" },
|
|
2728
|
-
detail: { type: "string", description: "Detail level (brief|normal|full|summary)" },
|
|
1857
|
+
detail: { type: "string", description: "Detail level (brief|normal|full|summary|agent)" },
|
|
2729
1858
|
quiet: { type: "boolean", alias: "q", description: "Suppress stderr warnings", default: false },
|
|
2730
1859
|
},
|
|
2731
1860
|
subCommands: {
|
|
@@ -2773,14 +1902,32 @@ const WIKI_SUBCOMMAND_SET = new Set([
|
|
|
2773
1902
|
"ingest",
|
|
2774
1903
|
]);
|
|
2775
1904
|
const SHOW_VIEW_MODES = new Set(["toc", "frontmatter", "full", "section", "lines"]);
|
|
2776
|
-
// citty reads process.argv directly and does not accept a custom argv array,
|
|
2777
|
-
// so we must replace process.argv with the normalized version before runMain.
|
|
2778
|
-
process.argv = normalizeShowArgv(process.argv);
|
|
2779
|
-
runMain(main);
|
|
2780
1905
|
// ── Exit codes ──────────────────────────────────────────────────────────────
|
|
2781
1906
|
const EXIT_GENERAL = 1;
|
|
2782
1907
|
const EXIT_USAGE = 2;
|
|
2783
1908
|
const EXIT_CONFIG = 78;
|
|
1909
|
+
// citty reads process.argv directly and does not accept a custom argv array,
|
|
1910
|
+
// so we must replace process.argv with the normalized version before runMain.
|
|
1911
|
+
process.argv = normalizeShowArgv(process.argv);
|
|
1912
|
+
// Resolve output mode once at startup from the (normalized) argv and persisted
|
|
1913
|
+
// config. All subsequent output() calls read from this in-memory singleton.
|
|
1914
|
+
// `initOutputMode` can throw a UsageError when --format/--detail values are
|
|
1915
|
+
// invalid; surface it through the same JSON-error path the rest of the CLI uses
|
|
1916
|
+
// rather than letting the raw exception escape with a stack trace.
|
|
1917
|
+
try {
|
|
1918
|
+
initOutputMode(process.argv, loadConfig().output ?? {});
|
|
1919
|
+
}
|
|
1920
|
+
catch (error) {
|
|
1921
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1922
|
+
const hint = buildHint(message);
|
|
1923
|
+
const exitCode = classifyExitCode(error);
|
|
1924
|
+
const code = error instanceof UsageError || error instanceof ConfigError || error instanceof NotFoundError
|
|
1925
|
+
? error.code
|
|
1926
|
+
: undefined;
|
|
1927
|
+
console.error(JSON.stringify({ ok: false, error: message, ...(code ? { code } : {}), hint }, null, 2));
|
|
1928
|
+
process.exit(exitCode);
|
|
1929
|
+
}
|
|
1930
|
+
runMain(main);
|
|
2784
1931
|
function classifyExitCode(error) {
|
|
2785
1932
|
if (error instanceof UsageError)
|
|
2786
1933
|
return EXIT_USAGE;
|
|
@@ -2802,7 +1949,12 @@ async function runWithJsonErrors(fn) {
|
|
|
2802
1949
|
const message = error instanceof Error ? error.message : String(error);
|
|
2803
1950
|
const hint = buildHint(message);
|
|
2804
1951
|
const exitCode = classifyExitCode(error);
|
|
2805
|
-
|
|
1952
|
+
// Surface machine-readable error code from typed errors when present so
|
|
1953
|
+
// scripts can branch on `.code` instead of message-string matching.
|
|
1954
|
+
const code = error instanceof UsageError || error instanceof ConfigError || error instanceof NotFoundError
|
|
1955
|
+
? error.code
|
|
1956
|
+
: undefined;
|
|
1957
|
+
console.error(JSON.stringify({ ok: false, error: message, ...(code ? { code } : {}), hint }, null, 2));
|
|
2806
1958
|
process.exit(exitCode);
|
|
2807
1959
|
}
|
|
2808
1960
|
}
|
|
@@ -2822,7 +1974,7 @@ function buildHint(message) {
|
|
|
2822
1974
|
if (message.includes("Invalid value for --format"))
|
|
2823
1975
|
return "Pick one of: json, jsonl, text, yaml.";
|
|
2824
1976
|
if (message.includes("Invalid value for --detail"))
|
|
2825
|
-
return "Pick one of: brief, normal, full, summary.";
|
|
1977
|
+
return "Pick one of: brief, normal, full, summary, agent.";
|
|
2826
1978
|
if (message.includes("expected JSON object with endpoint and model")) {
|
|
2827
1979
|
return 'Quote JSON values in your shell, for example: akm config set embedding \'{"endpoint":"http://localhost:11434/v1/embeddings","model":"nomic-embed-text"}\'.';
|
|
2828
1980
|
}
|
|
@@ -2926,294 +2078,3 @@ function loadHints(detail = "normal") {
|
|
|
2926
2078
|
// Fallback for compiled binary — inline content
|
|
2927
2079
|
return fallback;
|
|
2928
2080
|
}
|
|
2929
|
-
const EMBEDDED_HINTS = `# akm CLI
|
|
2930
|
-
|
|
2931
|
-
You have access to a searchable library of scripts, skills, commands, agents, knowledge documents, workflows, wikis, and memories via \`akm\`. Search your sources first before writing something from scratch.
|
|
2932
|
-
|
|
2933
|
-
## Quick Reference
|
|
2934
|
-
|
|
2935
|
-
\`\`\`sh
|
|
2936
|
-
akm search "<query>" # Search all sources
|
|
2937
|
-
akm curate "<task>" # Curate the best matches for a task
|
|
2938
|
-
akm search "<query>" --type workflow # Filter to workflow assets
|
|
2939
|
-
akm search "<query>" --source both # Also search registries
|
|
2940
|
-
akm show <ref> # View asset details
|
|
2941
|
-
akm workflow next <ref> # Start or resume a workflow
|
|
2942
|
-
akm remember "Deployment needs VPN access" # Record a memory in your stash
|
|
2943
|
-
akm import ./notes/release-checklist.md # Import a knowledge doc into your stash
|
|
2944
|
-
akm wiki list # List available wikis
|
|
2945
|
-
akm wiki ingest <name> # Print the ingest workflow for a wiki
|
|
2946
|
-
akm feedback <ref> --positive|--negative # Record whether an asset helped
|
|
2947
|
-
akm add <ref> # Add a source (npm, GitHub, git, local dir)
|
|
2948
|
-
akm clone <ref> # Copy an asset to the working stash (optional --dest arg to clone to specific location)
|
|
2949
|
-
akm save # Commit (and push if writable remote) changes in the primary stash
|
|
2950
|
-
akm registry search "<query>" # Search all registries
|
|
2951
|
-
\`\`\`
|
|
2952
|
-
|
|
2953
|
-
## Primary Asset Types
|
|
2954
|
-
|
|
2955
|
-
| Type | What \`akm show\` returns |
|
|
2956
|
-
| --- | --- |
|
|
2957
|
-
| script | A \`run\` command you can execute directly |
|
|
2958
|
-
| skill | Instructions to follow (read the full content) |
|
|
2959
|
-
| command | A prompt template with placeholders to fill in |
|
|
2960
|
-
| agent | A system prompt with model and tool hints |
|
|
2961
|
-
| knowledge | A reference doc (use \`toc\` or \`section "..."\` to navigate) |
|
|
2962
|
-
| workflow | Parsed steps plus workflow-specific execution commands |
|
|
2963
|
-
| memory | Recalled context (read the content for background information) |
|
|
2964
|
-
| vault | Key names only; use vault commands to inspect or load values safely |
|
|
2965
|
-
| wiki | A page in a multi-wiki knowledge base. For any wiki task, start with \`akm wiki list\`, then \`akm wiki ingest <name>\` for the workflow. Run \`akm wiki -h\` for the full surface. |
|
|
2966
|
-
|
|
2967
|
-
When an asset meaningfully helps or fails, record that with \`akm feedback\` so
|
|
2968
|
-
future search ranking can learn from real usage.
|
|
2969
|
-
|
|
2970
|
-
Run \`akm -h\` for the full command reference.
|
|
2971
|
-
`;
|
|
2972
|
-
const EMBEDDED_HINTS_FULL = `# akm CLI — Full Reference
|
|
2973
|
-
|
|
2974
|
-
You have access to a searchable library of scripts, skills, commands, agents, knowledge documents, workflows, wikis, and memories via \`akm\`. Search your sources first before writing something from scratch.
|
|
2975
|
-
|
|
2976
|
-
## Search
|
|
2977
|
-
|
|
2978
|
-
\`\`\`sh
|
|
2979
|
-
akm search "<query>" # Search all sources
|
|
2980
|
-
akm curate "<task>" # Curate the best matches for a task
|
|
2981
|
-
akm search "<query>" --type workflow # Filter by asset type
|
|
2982
|
-
akm search "<query>" --source both # Also search registries
|
|
2983
|
-
akm search "<query>" --source registry # Search registries only
|
|
2984
|
-
akm search "<query>" --limit 10 # Limit results
|
|
2985
|
-
akm search "<query>" --detail full # Include scores, paths, timing
|
|
2986
|
-
\`\`\`
|
|
2987
|
-
|
|
2988
|
-
| Flag | Values | Default |
|
|
2989
|
-
| --- | --- | --- |
|
|
2990
|
-
| \`--type\` | \`skill\`, \`command\`, \`agent\`, \`knowledge\`, \`workflow\`, \`script\`, \`memory\`, \`vault\`, \`wiki\`, \`any\` | \`any\` |
|
|
2991
|
-
| \`--source\` | \`stash\`, \`registry\`, \`both\` | \`stash\` |
|
|
2992
|
-
| \`--limit\` | number | \`20\` |
|
|
2993
|
-
| \`--format\` | \`json\`, \`jsonl\`, \`text\`, \`yaml\` | \`json\` |
|
|
2994
|
-
| \`--detail\` | \`brief\`, \`normal\`, \`full\`, \`summary\` | \`brief\` |
|
|
2995
|
-
| \`--for-agent\` | boolean | \`false\` |
|
|
2996
|
-
|
|
2997
|
-
## Curate
|
|
2998
|
-
|
|
2999
|
-
Combine search + follow-up hints into a dense summary for a task or prompt.
|
|
3000
|
-
|
|
3001
|
-
\`\`\`sh
|
|
3002
|
-
akm curate "plan a release" # Pick top matches across asset types
|
|
3003
|
-
akm curate "deploy a Bun app" --limit 3 # Keep the summary shorter
|
|
3004
|
-
akm curate "review architecture" --type workflow # Restrict to one asset type
|
|
3005
|
-
\`\`\`
|
|
3006
|
-
|
|
3007
|
-
## Show
|
|
3008
|
-
|
|
3009
|
-
Display an asset by ref. Knowledge assets support view modes as positional arguments.
|
|
3010
|
-
|
|
3011
|
-
\`\`\`sh
|
|
3012
|
-
akm show script:deploy.sh # Show script (returns run command)
|
|
3013
|
-
akm show skill:code-review # Show skill (returns full content)
|
|
3014
|
-
akm show command:release # Show command (returns template)
|
|
3015
|
-
akm show agent:architect # Show agent (returns system prompt)
|
|
3016
|
-
akm show workflow:ship-release # Show parsed workflow steps
|
|
3017
|
-
akm show knowledge:guide toc # Table of contents
|
|
3018
|
-
akm show knowledge:guide section "Auth" # Specific section
|
|
3019
|
-
akm show knowledge:guide lines 10 30 # Line range
|
|
3020
|
-
akm show knowledge:my-doc # Show content (local or remote)
|
|
3021
|
-
\`\`\`
|
|
3022
|
-
|
|
3023
|
-
| Type | Key fields returned |
|
|
3024
|
-
| --- | --- |
|
|
3025
|
-
| script | \`run\`, \`setup\`, \`cwd\` |
|
|
3026
|
-
| skill | \`content\` (full SKILL.md) |
|
|
3027
|
-
| command | \`template\`, \`description\`, \`parameters\` |
|
|
3028
|
-
| agent | \`prompt\`, \`description\`, \`modelHint\`, \`toolPolicy\` |
|
|
3029
|
-
| knowledge | \`content\` (with view modes: \`full\`, \`toc\`, \`frontmatter\`, \`section\`, \`lines\`) |
|
|
3030
|
-
| workflow | \`workflowTitle\`, \`workflowParameters\`, \`steps\` |
|
|
3031
|
-
| memory | \`content\` (recalled context) |
|
|
3032
|
-
| vault | \`keys\`, \`comments\` |
|
|
3033
|
-
| wiki | \`content\` (same view modes as knowledge). For any wiki task, run \`akm wiki list\` then \`akm wiki ingest <name>\` for the workflow. |
|
|
3034
|
-
|
|
3035
|
-
## Capture Knowledge While You Work
|
|
3036
|
-
|
|
3037
|
-
\`\`\`sh
|
|
3038
|
-
akm remember "Deployment needs VPN access" # Record a memory in your stash
|
|
3039
|
-
akm remember --name release-retro < notes.md # Save multiline memory from stdin
|
|
3040
|
-
akm import ./docs/auth-flow.md # Import a file as knowledge
|
|
3041
|
-
akm import - --name scratch-notes < notes.md # Import stdin as a knowledge doc
|
|
3042
|
-
akm workflow create ship-release # Create a workflow asset in the stash
|
|
3043
|
-
akm workflow next workflow:ship-release # Start or resume the next workflow step
|
|
3044
|
-
akm feedback skill:code-review --positive # Record that an asset helped
|
|
3045
|
-
akm feedback agent:reviewer --negative # Record that an asset missed the mark
|
|
3046
|
-
\`\`\`
|
|
3047
|
-
|
|
3048
|
-
Use \`akm feedback\` whenever an asset materially helps or fails so future search
|
|
3049
|
-
ranking can learn from actual usage.
|
|
3050
|
-
|
|
3051
|
-
## Wikis
|
|
3052
|
-
|
|
3053
|
-
Multi-wiki knowledge bases (Karpathy-style). A stash-owned wiki lives at
|
|
3054
|
-
\`<stashDir>/wikis/<name>/\`; external directories or repos can also be registered
|
|
3055
|
-
as first-class wikis. akm owns lifecycle + raw-slug + lint + index regeneration
|
|
3056
|
-
for stash-owned wikis; page edits use your native Read/Write/Edit tools.
|
|
3057
|
-
|
|
3058
|
-
\`\`\`sh
|
|
3059
|
-
akm wiki list # List wikis (name, pages, raws, last-modified)
|
|
3060
|
-
akm wiki create research # Scaffold a new wiki
|
|
3061
|
-
akm wiki register ics-docs ~/code/ics-documentation # Register an external wiki
|
|
3062
|
-
akm wiki show research # Path, description, counts, last 3 log entries
|
|
3063
|
-
akm wiki pages research # Page refs + descriptions (excludes schema/index/log/raw)
|
|
3064
|
-
akm wiki search research "attention" # Scoped search (equivalent to --type wiki --wiki research)
|
|
3065
|
-
akm wiki stash research ./paper.md # Copy source into raw/<slug>.md (never overwrites)
|
|
3066
|
-
echo "..." | akm wiki stash research - # stdin form
|
|
3067
|
-
akm wiki lint research # Structural checks: orphans, broken xrefs, uncited raws, stale index
|
|
3068
|
-
akm wiki ingest research # Print the ingest workflow for this wiki (no action)
|
|
3069
|
-
akm wiki remove research --force # Delete pages/schema/index/log; preserves raw/
|
|
3070
|
-
akm wiki remove research --force --with-sources # Full nuke, including raw/
|
|
3071
|
-
\`\`\`
|
|
3072
|
-
|
|
3073
|
-
**For any wiki task, start with \`akm wiki list\`, then \`akm wiki ingest <name>\`
|
|
3074
|
-
to get the step-by-step workflow.** Wiki pages are also addressable as
|
|
3075
|
-
\`wiki:<name>/<page-path>\` and show up in stash-wide \`akm search\` as
|
|
3076
|
-
\`type: wiki\`. Files under \`raw/\` and the wiki root infrastructure files
|
|
3077
|
-
\`schema.md\`, \`index.md\`, and \`log.md\` are not indexed and do not appear in
|
|
3078
|
-
search results. No \`--llm\` anywhere — akm never reasons about page content.
|
|
3079
|
-
|
|
3080
|
-
## Vaults
|
|
3081
|
-
|
|
3082
|
-
Encrypted-at-rest key/value stores for secrets. Each vault is a \`.env\`-format
|
|
3083
|
-
file at \`<stashDir>/vaults/<name>.env\`.
|
|
3084
|
-
|
|
3085
|
-
\`\`\`sh
|
|
3086
|
-
akm vault create prod # Create a new vault
|
|
3087
|
-
akm vault set prod DB_URL postgres://... # Set a key (or KEY=VALUE combined form)
|
|
3088
|
-
akm vault set prod DB_URL=postgres://... # Combined KEY=VALUE form also works
|
|
3089
|
-
akm vault unset prod DB_URL # Remove a key
|
|
3090
|
-
akm vault list vault:prod # List key names (no values)
|
|
3091
|
-
akm vault show vault:prod # Same as list (alias)
|
|
3092
|
-
akm vault load vault:prod # Print export statements to source
|
|
3093
|
-
\`\`\`
|
|
3094
|
-
|
|
3095
|
-
## Workflows
|
|
3096
|
-
|
|
3097
|
-
Step-based workflows stored as \`<stashDir>/workflows/<name>.md\`.
|
|
3098
|
-
|
|
3099
|
-
\`\`\`sh
|
|
3100
|
-
akm workflow template # Print a starter workflow template
|
|
3101
|
-
akm workflow create ship-release # Scaffold a new workflow asset
|
|
3102
|
-
akm workflow start workflow:ship-release # Start a new run
|
|
3103
|
-
akm workflow next workflow:ship-release # Advance to the next step (or auto-start)
|
|
3104
|
-
akm workflow complete <run-id> # Mark a step complete and advance
|
|
3105
|
-
akm workflow status <run-id> # Show current run status
|
|
3106
|
-
akm workflow resume <run-id> # Resume a blocked or failed run
|
|
3107
|
-
akm workflow list # List all workflow runs
|
|
3108
|
-
\`\`\`
|
|
3109
|
-
|
|
3110
|
-
## Clone
|
|
3111
|
-
|
|
3112
|
-
Copy an asset to the working stash or a custom destination for editing.
|
|
3113
|
-
|
|
3114
|
-
\`\`\`sh
|
|
3115
|
-
akm clone <ref> # Clone to working stash
|
|
3116
|
-
akm clone <ref> --name new-name # Rename on clone
|
|
3117
|
-
akm clone <ref> --dest ./project/.claude # Clone to custom location
|
|
3118
|
-
akm clone <ref> --force # Overwrite existing
|
|
3119
|
-
akm clone "npm:@scope/pkg//script:deploy.sh" # Clone from remote package
|
|
3120
|
-
\`\`\`
|
|
3121
|
-
|
|
3122
|
-
When \`--dest\` is provided, \`akm init\` is not required first.
|
|
3123
|
-
|
|
3124
|
-
## Save
|
|
3125
|
-
|
|
3126
|
-
Commit local changes in a git-backed stash. Behaviour adapts automatically:
|
|
3127
|
-
|
|
3128
|
-
- **Not a git repo** — no-op (silent skip)
|
|
3129
|
-
- **Git repo, no remote** — stage and commit only (the default stash always falls here)
|
|
3130
|
-
- **Git repo, has remote, not writable** — stage and commit only
|
|
3131
|
-
- **Git repo, has remote, \`writable: true\`** — stage, commit, and push
|
|
3132
|
-
|
|
3133
|
-
\`\`\`sh
|
|
3134
|
-
akm save # Save primary stash (timestamp message)
|
|
3135
|
-
akm save -m "Add deploy skill" # Save with explicit message
|
|
3136
|
-
akm save my-skills # Save a named writable git stash
|
|
3137
|
-
akm save my-skills -m "Update patterns" # Save named stash with message
|
|
3138
|
-
\`\`\`
|
|
3139
|
-
|
|
3140
|
-
The \`--writable\` flag on \`akm add\` opts a remote git stash into push-on-save:
|
|
3141
|
-
|
|
3142
|
-
\`\`\`sh
|
|
3143
|
-
akm add git@github.com:org/skills.git --provider git --name my-skills --writable
|
|
3144
|
-
\`\`\`
|
|
3145
|
-
|
|
3146
|
-
## Add & Manage Sources
|
|
3147
|
-
|
|
3148
|
-
\`\`\`sh
|
|
3149
|
-
akm add <ref> # Add a source
|
|
3150
|
-
akm add @scope/kit # From npm (managed)
|
|
3151
|
-
akm add owner/repo # From GitHub (managed)
|
|
3152
|
-
akm add ./path/to/local/kit # Local directory
|
|
3153
|
-
akm add git@github.com:org/repo.git --provider git --name my-skills --writable
|
|
3154
|
-
akm enable skills.sh # Enable the skills.sh registry
|
|
3155
|
-
akm disable skills.sh # Disable the skills.sh registry
|
|
3156
|
-
akm enable context-hub # Add/enable the context-hub source
|
|
3157
|
-
akm disable context-hub # Disable the context-hub source
|
|
3158
|
-
akm list # List all sources
|
|
3159
|
-
akm list --kind managed # List managed sources only
|
|
3160
|
-
akm remove <target> # Remove by id, ref, path, or name
|
|
3161
|
-
akm update --all # Update all managed sources
|
|
3162
|
-
akm update <target> --force # Force re-download
|
|
3163
|
-
\`\`\`
|
|
3164
|
-
|
|
3165
|
-
## Registries
|
|
3166
|
-
|
|
3167
|
-
\`\`\`sh
|
|
3168
|
-
akm registry list # List configured registries
|
|
3169
|
-
akm registry add <url> # Add a registry
|
|
3170
|
-
akm registry add <url> --name my-team # Add with label
|
|
3171
|
-
akm registry add <url> --provider skills-sh # Specify provider type
|
|
3172
|
-
akm registry remove <url-or-name> # Remove a registry
|
|
3173
|
-
akm registry search "<query>" # Search all registries
|
|
3174
|
-
akm registry search "<query>" --assets # Include asset-level results
|
|
3175
|
-
akm registry build-index # Build ./index.json
|
|
3176
|
-
akm registry build-index --out dist/index.json # Build to a custom path
|
|
3177
|
-
\`\`\`
|
|
3178
|
-
|
|
3179
|
-
## Configuration
|
|
3180
|
-
|
|
3181
|
-
\`\`\`sh
|
|
3182
|
-
akm config list # Show current config
|
|
3183
|
-
akm config get <key> # Read a value
|
|
3184
|
-
akm config set <key> <value> # Set a value
|
|
3185
|
-
akm config unset <key> # Remove a key
|
|
3186
|
-
akm config path --all # Show all config paths
|
|
3187
|
-
\`\`\`
|
|
3188
|
-
|
|
3189
|
-
## Other Commands
|
|
3190
|
-
|
|
3191
|
-
\`\`\`sh
|
|
3192
|
-
akm init # Initialize working stash
|
|
3193
|
-
akm index # Rebuild search index
|
|
3194
|
-
akm index --full # Full reindex
|
|
3195
|
-
akm list # List all sources
|
|
3196
|
-
akm upgrade # Upgrade akm using its install method
|
|
3197
|
-
akm upgrade --check # Check for updates
|
|
3198
|
-
akm help migrate 0.5.0 # Print migration notes for a release
|
|
3199
|
-
akm hints # Print this reference
|
|
3200
|
-
akm completions # Print bash completion script
|
|
3201
|
-
akm completions --install # Install completions
|
|
3202
|
-
\`\`\`
|
|
3203
|
-
|
|
3204
|
-
## Output Control
|
|
3205
|
-
|
|
3206
|
-
All commands accept \`--format\` and \`--detail\` flags:
|
|
3207
|
-
|
|
3208
|
-
- \`--format json\` (default) — structured JSON
|
|
3209
|
-
- \`--format jsonl\` — one JSON object per line (streaming-friendly)
|
|
3210
|
-
- \`--format text\` — human-readable plain text
|
|
3211
|
-
- \`--format yaml\` — YAML output
|
|
3212
|
-
- \`--detail brief\` (default) — compact output
|
|
3213
|
-
- \`--detail normal\` — adds tags, refs, origins
|
|
3214
|
-
- \`--detail full\` — includes scores, paths, timing, debug info
|
|
3215
|
-
- \`--detail summary\` — metadata only (no content/template/prompt), under 200 tokens
|
|
3216
|
-
- \`--for-agent\` — agent-optimized output: strips non-actionable fields (takes precedence over \`--detail\`)
|
|
3217
|
-
|
|
3218
|
-
Run \`akm -h\` or \`akm <command> -h\` for per-command help.
|
|
3219
|
-
`;
|