akm-cli 0.5.0 → 0.6.0-rc2
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 +53 -5
- package/README.md +9 -9
- package/dist/cli.js +379 -1448
- package/dist/{completions.js → commands/completions.js} +1 -1
- package/dist/{config-cli.js → commands/config-cli.js} +109 -11
- package/dist/commands/curate.js +263 -0
- package/dist/{info.js → commands/info.js} +17 -11
- package/dist/{init.js → commands/init.js} +4 -4
- package/dist/{install-audit.js → commands/install-audit.js} +14 -2
- package/dist/{installed-kits.js → commands/installed-stashes.js} +122 -50
- package/dist/commands/migration-help.js +141 -0
- package/dist/{registry-search.js → commands/registry-search.js} +68 -9
- package/dist/commands/remember.js +178 -0
- package/dist/{stash-search.js → commands/search.js} +28 -69
- package/dist/{self-update.js → commands/self-update.js} +3 -3
- package/dist/{stash-show.js → commands/show.js} +106 -81
- package/dist/{stash-add.js → commands/source-add.js} +133 -67
- package/dist/{stash-clone.js → commands/source-clone.js} +15 -13
- package/dist/{stash-source-manage.js → commands/source-manage.js} +24 -24
- package/dist/{vault.js → commands/vault.js} +43 -0
- package/dist/{stash-ref.js → core/asset-ref.js} +4 -4
- package/dist/{asset-registry.js → core/asset-registry.js} +30 -6
- package/dist/{asset-spec.js → core/asset-spec.js} +13 -6
- package/dist/{common.js → core/common.js} +147 -50
- package/dist/{config.js → core/config.js} +288 -29
- package/dist/core/errors.js +90 -0
- package/dist/{frontmatter.js → core/frontmatter.js} +64 -8
- package/dist/{paths.js → core/paths.js} +4 -4
- package/dist/core/write-source.js +280 -0
- package/dist/{local-search.js → indexer/db-search.js} +49 -32
- package/dist/{db.js → indexer/db.js} +210 -81
- package/dist/{file-context.js → indexer/file-context.js} +3 -3
- package/dist/{indexer.js → indexer/indexer.js} +153 -30
- package/dist/{manifest.js → indexer/manifest.js} +10 -10
- package/dist/{matchers.js → indexer/matchers.js} +4 -7
- package/dist/{metadata.js → indexer/metadata.js} +9 -5
- package/dist/{search-source.js → indexer/search-source.js} +97 -55
- package/dist/{semantic-status.js → indexer/semantic-status.js} +2 -2
- package/dist/{walker.js → indexer/walker.js} +1 -1
- package/dist/{lockfile.js → integrations/lockfile.js} +29 -2
- package/dist/{llm.js → llm/client.js} +12 -48
- package/dist/llm/embedder.js +127 -0
- package/dist/llm/embedders/cache.js +47 -0
- package/dist/llm/embedders/local.js +152 -0
- package/dist/llm/embedders/remote.js +121 -0
- package/dist/llm/embedders/types.js +39 -0
- package/dist/llm/metadata-enhance.js +53 -0
- package/dist/output/cli-hints.js +301 -0
- package/dist/output/context.js +95 -0
- package/dist/{renderers.js → output/renderers.js} +57 -61
- package/dist/output/shapes.js +212 -0
- package/dist/output/text.js +520 -0
- package/dist/{registry-build-index.js → registry/build-index.js} +48 -32
- package/dist/{create-provider-registry.js → registry/create-provider-registry.js} +6 -2
- package/dist/registry/factory.js +33 -0
- package/dist/{origin-resolve.js → registry/origin-resolve.js} +1 -1
- package/dist/registry/providers/index.js +11 -0
- package/dist/{providers → registry/providers}/skills-sh.js +60 -4
- package/dist/{providers → registry/providers}/static-index.js +126 -56
- package/dist/registry/providers/types.js +25 -0
- package/dist/{registry-resolve.js → registry/resolve.js} +10 -6
- package/dist/{detect.js → setup/detect.js} +0 -27
- package/dist/{ripgrep-install.js → setup/ripgrep-install.js} +1 -1
- package/dist/{ripgrep-resolve.js → setup/ripgrep-resolve.js} +2 -2
- package/dist/{setup.js → setup/setup.js} +162 -129
- package/dist/setup/steps.js +45 -0
- package/dist/{kit-include.js → sources/include.js} +1 -1
- package/dist/sources/provider-factory.js +36 -0
- package/dist/sources/provider.js +21 -0
- package/dist/sources/providers/filesystem.js +35 -0
- package/dist/{stash-providers → sources/providers}/git.js +218 -28
- package/dist/{stash-providers → sources/providers}/index.js +4 -4
- package/dist/sources/providers/install-types.js +14 -0
- package/dist/sources/providers/npm.js +160 -0
- package/dist/sources/providers/provider-utils.js +173 -0
- package/dist/sources/providers/sync-from-ref.js +45 -0
- package/dist/sources/providers/tar-utils.js +154 -0
- package/dist/{stash-providers → sources/providers}/website.js +60 -20
- package/dist/{stash-resolve.js → sources/resolve.js} +13 -12
- package/dist/{wiki.js → wiki/wiki.js} +18 -17
- package/dist/{workflow-authoring.js → workflows/authoring.js} +48 -17
- package/dist/{workflow-cli.js → workflows/cli.js} +2 -1
- package/dist/{workflow-db.js → workflows/db.js} +1 -1
- package/dist/workflows/document-cache.js +20 -0
- package/dist/workflows/parser.js +379 -0
- package/dist/workflows/renderer.js +78 -0
- package/dist/{workflow-runs.js → workflows/runs.js} +84 -30
- package/dist/workflows/schema.js +11 -0
- package/dist/workflows/validator.js +48 -0
- 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 +75 -0
- package/docs/migration/release-notes/README.md +21 -0
- package/package.json +3 -2
- package/dist/embedder.js +0 -351
- package/dist/errors.js +0 -34
- package/dist/migration-help.js +0 -110
- package/dist/registry-factory.js +0 -19
- package/dist/registry-install.js +0 -532
- package/dist/ripgrep.js +0 -2
- package/dist/stash-provider-factory.js +0 -35
- package/dist/stash-provider.js +0 -1
- package/dist/stash-providers/filesystem.js +0 -41
- package/dist/stash-providers/openviking.js +0 -348
- package/dist/stash-providers/provider-utils.js +0 -11
- package/dist/stash-types.js +0 -1
- package/dist/workflow-markdown.js +0 -251
- /package/dist/{markdown.js → core/markdown.js} +0 -0
- /package/dist/{warn.js → core/warn.js} +0 -0
- /package/dist/{search-fields.js → indexer/search-fields.js} +0 -0
- /package/dist/{usage-events.js → indexer/usage-events.js} +0 -0
- /package/dist/{github.js → integrations/github.js} +0 -0
- /package/dist/{registry-provider.js → registry/types.js} +0 -0
- /package/dist/{registry-types.js → sources/types.js} +0 -0
package/dist/cli.js
CHANGED
|
@@ -2,84 +2,71 @@
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { defineCommand, runMain } from "citty";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
5
|
+
import { generateBashCompletions, installBashCompletions } from "./commands/completions";
|
|
6
|
+
import { getConfigValue, listConfig, setConfigValue, unsetConfigValue } from "./commands/config-cli";
|
|
7
|
+
import { akmCurate } from "./commands/curate";
|
|
8
|
+
import { assembleInfo } from "./commands/info";
|
|
9
|
+
import { akmInit } from "./commands/init";
|
|
10
|
+
import { akmListSources, akmRemove, akmUpdate } from "./commands/installed-stashes";
|
|
11
|
+
import { renderMigrationHelp } from "./commands/migration-help";
|
|
12
|
+
import { searchRegistry } from "./commands/registry-search";
|
|
13
|
+
import { buildMemoryFrontmatter, parseDuration, readMemoryContent, runAutoHeuristics, runLlmEnrich, } from "./commands/remember";
|
|
14
|
+
import { akmSearch, parseSearchSource } from "./commands/search";
|
|
15
|
+
import { checkForUpdate, performUpgrade } from "./commands/self-update";
|
|
16
|
+
import { akmShowUnified } from "./commands/show";
|
|
17
|
+
import { akmAdd } from "./commands/source-add";
|
|
18
|
+
import { akmClone } from "./commands/source-clone";
|
|
19
|
+
import { addStash } from "./commands/source-manage";
|
|
20
|
+
import { parseAssetRef } from "./core/asset-ref";
|
|
21
|
+
import { deriveCanonicalAssetName, resolveAssetPathFromName } from "./core/asset-spec";
|
|
22
|
+
import { isWithin, resolveStashDir, tryReadStdinText } from "./core/common";
|
|
23
|
+
import { DEFAULT_CONFIG, getConfigPath, loadConfig, loadUserConfig, saveConfig } from "./core/config";
|
|
24
|
+
import { ConfigError, NotFoundError, UsageError } from "./core/errors";
|
|
25
|
+
import { getCacheDir, getDbPath, getDefaultStashDir } from "./core/paths";
|
|
26
|
+
import { setQuiet, warn } from "./core/warn";
|
|
27
|
+
import { resolveWriteTarget, writeAssetToSource } from "./core/write-source";
|
|
28
|
+
import { closeDatabase, openDatabase } from "./indexer/db";
|
|
29
|
+
import { akmIndex } from "./indexer/indexer";
|
|
30
|
+
import { resolveSourceEntries } from "./indexer/search-source";
|
|
31
|
+
import { insertUsageEvent } from "./indexer/usage-events";
|
|
32
|
+
import { EMBEDDED_HINTS, EMBEDDED_HINTS_FULL } from "./output/cli-hints";
|
|
33
|
+
import { getHyphenatedArg, getHyphenatedBoolean, getOutputMode, initOutputMode, parseFlagValue, } from "./output/context";
|
|
34
|
+
import { shapeForCommand } from "./output/shapes";
|
|
35
|
+
import { formatPlain, outputJsonl } from "./output/text";
|
|
36
|
+
import { buildRegistryIndex, writeRegistryIndex } from "./registry/build-index";
|
|
37
|
+
import { resolveSourcesForOrigin } from "./registry/origin-resolve";
|
|
38
|
+
import { saveGitStash } from "./sources/providers/git";
|
|
39
|
+
import { resolveAssetPath } from "./sources/resolve";
|
|
30
40
|
import { pkgVersion } from "./version";
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
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;
|
|
41
|
+
import { createWorkflowAsset, formatWorkflowErrors, getWorkflowTemplate, validateWorkflowSource, } from "./workflows/authoring";
|
|
42
|
+
import { hasWorkflowSubcommand, parseWorkflowJsonObject, parseWorkflowStepState, WORKFLOW_STEP_STATES, } from "./workflows/cli";
|
|
43
|
+
import { completeWorkflowStep, getNextWorkflowStep, getWorkflowStatus, listWorkflowRuns, resumeWorkflowRun, startWorkflowRun, } from "./workflows/runs";
|
|
38
44
|
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
45
|
const SKILLS_SH_NAME = "skills.sh";
|
|
42
46
|
const SKILLS_SH_URL = "https://skills.sh";
|
|
43
47
|
const SKILLS_SH_PROVIDER = "skills-sh";
|
|
44
48
|
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) {
|
|
49
|
+
/**
|
|
50
|
+
* Collect all occurrences of a repeatable flag from process.argv.
|
|
51
|
+
* Citty's StringArgDef only exposes the last value when a flag is repeated,
|
|
52
|
+
* so for repeatable CLI args (like `--tag foo --tag bar`) we read argv directly.
|
|
53
|
+
* Supports both `--flag value` and `--flag=value` forms.
|
|
54
|
+
*/
|
|
55
|
+
function parseAllFlagValues(flag) {
|
|
56
|
+
const values = [];
|
|
60
57
|
for (let i = 0; i < process.argv.length; i++) {
|
|
61
58
|
const arg = process.argv[i];
|
|
62
|
-
if (arg === flag)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
59
|
+
if (arg === flag && i + 1 < process.argv.length) {
|
|
60
|
+
values.push(process.argv[i + 1]);
|
|
61
|
+
}
|
|
62
|
+
else if (arg.startsWith(`${flag}=`)) {
|
|
63
|
+
values.push(arg.slice(flag.length + 1));
|
|
64
|
+
}
|
|
66
65
|
}
|
|
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 };
|
|
66
|
+
return values;
|
|
80
67
|
}
|
|
81
68
|
function output(command, result) {
|
|
82
|
-
const mode =
|
|
69
|
+
const mode = getOutputMode();
|
|
83
70
|
const shaped = shapeForCommand(command, result, mode.detail, mode.forAgent);
|
|
84
71
|
if (mode.format === "jsonl") {
|
|
85
72
|
outputJsonl(command, shaped);
|
|
@@ -99,936 +86,12 @@ function output(command, result) {
|
|
|
99
86
|
}
|
|
100
87
|
}
|
|
101
88
|
}
|
|
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
89
|
/**
|
|
1027
90
|
* Module Naming:
|
|
1028
|
-
* -
|
|
1029
|
-
* -
|
|
1030
|
-
* - registry
|
|
1031
|
-
* - installed-
|
|
91
|
+
* - sources/* : Asset operations (search, show, add, clone)
|
|
92
|
+
* - sources/providers/* : Runtime data source providers (filesystem, git, website, npm)
|
|
93
|
+
* - registry/* : Discovery from remote registries (npm, GitHub)
|
|
94
|
+
* - installed-stashes : Unified source operations (list, remove, update)
|
|
1032
95
|
*/
|
|
1033
96
|
const setupCommand = defineCommand({
|
|
1034
97
|
meta: {
|
|
@@ -1037,7 +100,7 @@ const setupCommand = defineCommand({
|
|
|
1037
100
|
},
|
|
1038
101
|
async run() {
|
|
1039
102
|
await runWithJsonErrors(async () => {
|
|
1040
|
-
const { runSetupWizard } = await import("./setup");
|
|
103
|
+
const { runSetupWizard } = await import("./setup/setup");
|
|
1041
104
|
await runSetupWizard();
|
|
1042
105
|
});
|
|
1043
106
|
},
|
|
@@ -1093,10 +156,14 @@ const searchCommand = defineCommand({
|
|
|
1093
156
|
limit: { type: "string", description: "Maximum number of results" },
|
|
1094
157
|
source: { type: "string", description: "Search source (stash|registry|both)", default: "stash" },
|
|
1095
158
|
format: { type: "string", description: "Output format (json|jsonl|text|yaml)" },
|
|
1096
|
-
detail: { type: "string", description: "Detail level (brief|normal|full|summary)" },
|
|
159
|
+
detail: { type: "string", description: "Detail level (brief|normal|full|summary|agent)" },
|
|
1097
160
|
},
|
|
1098
161
|
async run({ args }) {
|
|
1099
162
|
await runWithJsonErrors(async () => {
|
|
163
|
+
const query = (args.query ?? "").trim();
|
|
164
|
+
if (!query) {
|
|
165
|
+
throw new UsageError('A search query is required. Usage: akm search "<query>" [--type <type>] [--limit <n>]', "MISSING_REQUIRED_ARGUMENT");
|
|
166
|
+
}
|
|
1100
167
|
const type = args.type;
|
|
1101
168
|
const limitRaw = args.limit ? parseInt(args.limit, 10) : undefined;
|
|
1102
169
|
if (limitRaw !== undefined && Number.isNaN(limitRaw)) {
|
|
@@ -1104,7 +171,7 @@ const searchCommand = defineCommand({
|
|
|
1104
171
|
}
|
|
1105
172
|
const limit = limitRaw;
|
|
1106
173
|
const source = parseSearchSource(args.source);
|
|
1107
|
-
const result = await akmSearch({ query
|
|
174
|
+
const result = await akmSearch({ query, type, limit, source });
|
|
1108
175
|
output("search", result);
|
|
1109
176
|
});
|
|
1110
177
|
},
|
|
@@ -1129,15 +196,7 @@ const curateCommand = defineCommand({
|
|
|
1129
196
|
}
|
|
1130
197
|
const limit = limitRaw && limitRaw > 0 ? limitRaw : 4;
|
|
1131
198
|
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);
|
|
199
|
+
const curated = await akmCurate({ query: args.query, type, limit, source });
|
|
1141
200
|
output("curate", curated);
|
|
1142
201
|
});
|
|
1143
202
|
},
|
|
@@ -1153,7 +212,7 @@ const addCommand = defineCommand({
|
|
|
1153
212
|
description: "Path, URL, or registry ref (website URL, npm package, owner/repo, git URL, or local directory)",
|
|
1154
213
|
required: true,
|
|
1155
214
|
},
|
|
1156
|
-
provider: { type: "string", description: "Provider type (e.g.
|
|
215
|
+
provider: { type: "string", description: "Provider type (e.g. website, npm). Required for URL sources." },
|
|
1157
216
|
options: { type: "string", description: 'Provider options as JSON (e.g. \'{"apiKey":"key"}\').' },
|
|
1158
217
|
name: { type: "string", description: "Human-friendly name for the source" },
|
|
1159
218
|
writable: {
|
|
@@ -1176,16 +235,6 @@ const addCommand = defineCommand({
|
|
|
1176
235
|
async run({ args }) {
|
|
1177
236
|
await runWithJsonErrors(async () => {
|
|
1178
237
|
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
238
|
// URL with --provider → stash source (remote or git provider)
|
|
1190
239
|
if (args.provider) {
|
|
1191
240
|
if (shouldWarnOnPlainHttp(ref)) {
|
|
@@ -1213,7 +262,7 @@ const addCommand = defineCommand({
|
|
|
1213
262
|
options: parsedOptions,
|
|
1214
263
|
writable: args.writable,
|
|
1215
264
|
});
|
|
1216
|
-
output("
|
|
265
|
+
output("add", result);
|
|
1217
266
|
return;
|
|
1218
267
|
}
|
|
1219
268
|
if (shouldWarnOnPlainHttp(ref)) {
|
|
@@ -1221,7 +270,7 @@ const addCommand = defineCommand({
|
|
|
1221
270
|
}
|
|
1222
271
|
const websiteOptions = buildWebsiteOptions(args);
|
|
1223
272
|
if (args.type === "wiki") {
|
|
1224
|
-
const { registerWikiSource } = await import("./
|
|
273
|
+
const { registerWikiSource } = await import("./commands/source-add");
|
|
1225
274
|
const result = await registerWikiSource({
|
|
1226
275
|
ref,
|
|
1227
276
|
name: args.name,
|
|
@@ -1324,7 +373,7 @@ const upgradeCommand = defineCommand({
|
|
|
1324
373
|
args: {
|
|
1325
374
|
check: { type: "boolean", description: "Check for updates without installing", default: false },
|
|
1326
375
|
force: { type: "boolean", description: "Force upgrade even if on latest", default: false },
|
|
1327
|
-
|
|
376
|
+
"skip-checksum": {
|
|
1328
377
|
type: "boolean",
|
|
1329
378
|
description: "Skip checksum verification (not recommended)",
|
|
1330
379
|
default: false,
|
|
@@ -1337,7 +386,8 @@ const upgradeCommand = defineCommand({
|
|
|
1337
386
|
output("upgrade", check);
|
|
1338
387
|
return;
|
|
1339
388
|
}
|
|
1340
|
-
const
|
|
389
|
+
const skipChecksum = getHyphenatedBoolean(args, "skip-checksum");
|
|
390
|
+
const result = await performUpgrade(check, { force: args.force, skipChecksum });
|
|
1341
391
|
output("upgrade", result);
|
|
1342
392
|
});
|
|
1343
393
|
},
|
|
@@ -1350,38 +400,42 @@ const showCommand = defineCommand({
|
|
|
1350
400
|
args: {
|
|
1351
401
|
ref: { type: "positional", description: "Asset ref (type:name)", required: true },
|
|
1352
402
|
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" },
|
|
403
|
+
detail: { type: "string", description: "Detail level (brief|normal|full|summary|agent)" },
|
|
1358
404
|
},
|
|
1359
405
|
async run({ args }) {
|
|
1360
406
|
await runWithJsonErrors(async () => {
|
|
407
|
+
// The knowledge-view positional syntax (`akm show knowledge:foo section "Auth"`)
|
|
408
|
+
// is rewritten to `--akmView` / `--akmHeading` / `--akmStart` / `--akmEnd`
|
|
409
|
+
// by `normalizeShowArgv` before citty parses argv. We read those values
|
|
410
|
+
// directly via `parseFlagValue` so the flags don't surface as user-facing
|
|
411
|
+
// options in `akm show --help`.
|
|
412
|
+
const akmView = parseFlagValue(process.argv, "--akmView");
|
|
413
|
+
const akmHeading = parseFlagValue(process.argv, "--akmHeading");
|
|
414
|
+
const akmStart = parseFlagValue(process.argv, "--akmStart");
|
|
415
|
+
const akmEnd = parseFlagValue(process.argv, "--akmEnd");
|
|
1361
416
|
let view;
|
|
1362
|
-
if (
|
|
1363
|
-
switch (
|
|
417
|
+
if (akmView) {
|
|
418
|
+
switch (akmView) {
|
|
1364
419
|
case "section":
|
|
1365
|
-
view = { mode: "section", heading:
|
|
420
|
+
view = { mode: "section", heading: akmHeading ?? "" };
|
|
1366
421
|
break;
|
|
1367
422
|
case "lines":
|
|
1368
423
|
view = {
|
|
1369
424
|
mode: "lines",
|
|
1370
|
-
start: Number(
|
|
1371
|
-
end:
|
|
425
|
+
start: Number(akmStart ?? "1"),
|
|
426
|
+
end: akmEnd ? parseInt(akmEnd, 10) : Number.MAX_SAFE_INTEGER,
|
|
1372
427
|
};
|
|
1373
428
|
break;
|
|
1374
429
|
case "toc":
|
|
1375
430
|
case "frontmatter":
|
|
1376
431
|
case "full":
|
|
1377
|
-
view = { mode:
|
|
432
|
+
view = { mode: akmView };
|
|
1378
433
|
break;
|
|
1379
434
|
default:
|
|
1380
|
-
throw new UsageError(`Unknown view mode: ${
|
|
435
|
+
throw new UsageError(`Unknown view mode: ${akmView}. Expected one of: full|toc|frontmatter|section|lines`);
|
|
1381
436
|
}
|
|
1382
437
|
}
|
|
1383
|
-
|
|
1384
|
-
const cliDetail = resolveOutputMode().detail;
|
|
438
|
+
const cliDetail = getOutputMode().detail;
|
|
1385
439
|
const showDetail = cliDetail === "summary" ? "summary" : undefined;
|
|
1386
440
|
const result = await akmShowUnified({ ref: args.ref, view, detail: showDetail });
|
|
1387
441
|
output("show", result);
|
|
@@ -1510,7 +564,7 @@ const saveCommand = defineCommand({
|
|
|
1510
564
|
// before any standalone occurrence of the same value in the save
|
|
1511
565
|
// subcommand's argv slice. This preserves legitimate invocations
|
|
1512
566
|
// like `akm save json --format json`.
|
|
1513
|
-
const parsedFormat = parseFlagValue("--format");
|
|
567
|
+
const parsedFormat = parseFlagValue(process.argv, "--format");
|
|
1514
568
|
const effectiveName = args.name !== undefined &&
|
|
1515
569
|
parsedFormat !== undefined &&
|
|
1516
570
|
args.name === parsedFormat &&
|
|
@@ -1593,7 +647,7 @@ const cloneCommand = defineCommand({
|
|
|
1593
647
|
},
|
|
1594
648
|
});
|
|
1595
649
|
const registryCommand = defineCommand({
|
|
1596
|
-
meta: { name: "registry", description: "Manage
|
|
650
|
+
meta: { name: "registry", description: "Manage stash registries" },
|
|
1597
651
|
subCommands: {
|
|
1598
652
|
list: defineCommand({
|
|
1599
653
|
meta: { name: "list", description: "List configured registries" },
|
|
@@ -1612,6 +666,11 @@ const registryCommand = defineCommand({
|
|
|
1612
666
|
name: { type: "string", description: "Human-friendly name for the registry" },
|
|
1613
667
|
provider: { type: "string", description: "Provider type (e.g. static-index, skills-sh)" },
|
|
1614
668
|
options: { type: "string", description: 'Provider options as JSON (e.g. \'{"apiKey":"key"}\').' },
|
|
669
|
+
"allow-insecure": {
|
|
670
|
+
type: "boolean",
|
|
671
|
+
description: "Allow a plain HTTP registry URL (otherwise rejected)",
|
|
672
|
+
default: false,
|
|
673
|
+
},
|
|
1615
674
|
},
|
|
1616
675
|
run({ args }) {
|
|
1617
676
|
return runWithJsonErrors(() => {
|
|
@@ -1619,7 +678,12 @@ const registryCommand = defineCommand({
|
|
|
1619
678
|
throw new UsageError("Registry URL must start with http:// or https://");
|
|
1620
679
|
}
|
|
1621
680
|
if (args.url.startsWith("http://")) {
|
|
1622
|
-
|
|
681
|
+
const allowInsecure = getHyphenatedBoolean(args, "allow-insecure");
|
|
682
|
+
if (!allowInsecure) {
|
|
683
|
+
throw new UsageError("Registry URL uses plain HTTP (not HTTPS). An on-path attacker could substitute a malicious index. " +
|
|
684
|
+
"Use https:// or pass --allow-insecure if you have explicitly accepted the risk.");
|
|
685
|
+
}
|
|
686
|
+
warn("Warning: registry URL uses plain HTTP (not HTTPS). --allow-insecure was set; an on-path attacker could substitute a malicious index.");
|
|
1623
687
|
}
|
|
1624
688
|
const config = loadUserConfig();
|
|
1625
689
|
const registries = [...(config.registries ?? [])];
|
|
@@ -1668,7 +732,7 @@ const registryCommand = defineCommand({
|
|
|
1668
732
|
},
|
|
1669
733
|
}),
|
|
1670
734
|
search: defineCommand({
|
|
1671
|
-
meta: { name: "search", description: "Search enabled registries for
|
|
735
|
+
meta: { name: "search", description: "Search enabled registries for stashes" },
|
|
1672
736
|
args: {
|
|
1673
737
|
query: { type: "positional", description: "Search query", required: true },
|
|
1674
738
|
limit: { type: "string", description: "Maximum number of results" },
|
|
@@ -1690,15 +754,15 @@ const registryCommand = defineCommand({
|
|
|
1690
754
|
args: {
|
|
1691
755
|
out: { type: "string", description: "Output path for the generated index", default: "index.json" },
|
|
1692
756
|
manual: { type: "string", description: "Manual entries JSON file", default: "manual-entries.json" },
|
|
1693
|
-
|
|
1694
|
-
|
|
757
|
+
"npm-registry": { type: "string", description: "Override npm registry base URL" },
|
|
758
|
+
"github-api": { type: "string", description: "Override GitHub API base URL" },
|
|
1695
759
|
},
|
|
1696
760
|
async run({ args }) {
|
|
1697
761
|
await runWithJsonErrors(async () => {
|
|
1698
762
|
const result = await buildRegistryIndex({
|
|
1699
763
|
manualEntriesPath: args.manual,
|
|
1700
|
-
npmRegistryBase: args
|
|
1701
|
-
githubApiBase: args
|
|
764
|
+
npmRegistryBase: getHyphenatedArg(args, "npm-registry"),
|
|
765
|
+
githubApiBase: getHyphenatedArg(args, "github-api"),
|
|
1702
766
|
});
|
|
1703
767
|
const outPath = writeRegistryIndex(result.index, args.out);
|
|
1704
768
|
output("registry-build-index", {
|
|
@@ -1755,12 +819,6 @@ const feedbackCommand = defineCommand({
|
|
|
1755
819
|
});
|
|
1756
820
|
},
|
|
1757
821
|
});
|
|
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
822
|
function normalizeMarkdownAssetName(name, fallback) {
|
|
1765
823
|
const trimmed = (name ?? fallback)
|
|
1766
824
|
.trim()
|
|
@@ -1792,13 +850,6 @@ function inferAssetName(content, fallbackPrefix, preferred) {
|
|
|
1792
850
|
const basis = preferred?.trim() || firstNonEmptyLine || fallbackPrefix;
|
|
1793
851
|
return slugifyAssetName(basis, fallbackPrefix);
|
|
1794
852
|
}
|
|
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
853
|
function readKnowledgeContent(source) {
|
|
1803
854
|
if (source === "-") {
|
|
1804
855
|
const content = tryReadStdinText();
|
|
@@ -1823,24 +874,30 @@ function readKnowledgeContent(source) {
|
|
|
1823
874
|
preferredName: path.basename(resolvedSource, path.extname(resolvedSource)),
|
|
1824
875
|
};
|
|
1825
876
|
}
|
|
1826
|
-
function writeMarkdownAsset(options) {
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
877
|
+
async function writeMarkdownAsset(options) {
|
|
878
|
+
// Resolve write target via the v1 precedence chain (`--target` →
|
|
879
|
+
// `defaultWriteTarget` → working stash). Per spec §10 step 5, this is the
|
|
880
|
+
// single dispatch point — `core/write-source.ts` owns all kind-branching.
|
|
881
|
+
const cfg = loadConfig();
|
|
882
|
+
const { source, config } = resolveWriteTarget(cfg, options.target);
|
|
883
|
+
const typeRoot = path.join(source.path, options.type === "knowledge" ? "knowledge" : "memories");
|
|
1830
884
|
const normalizedName = normalizeMarkdownAssetName(options.name, inferAssetName(options.content, options.fallbackPrefix, options.preferredName));
|
|
885
|
+
// Pre-flight: existence + force semantics. The helper itself overwrites
|
|
886
|
+
// unconditionally; the CLI surfaces a friendlier UsageError before any
|
|
887
|
+
// disk activity when --force is absent.
|
|
1831
888
|
const assetPath = resolveAssetPathFromName(options.type, typeRoot, normalizedName);
|
|
1832
889
|
if (!isWithin(assetPath, typeRoot)) {
|
|
1833
890
|
throw new UsageError(`Resolved ${options.type} path escapes the stash: "${normalizedName}"`);
|
|
1834
891
|
}
|
|
1835
892
|
if (fs.existsSync(assetPath) && !options.force) {
|
|
1836
|
-
throw new UsageError(`${options.type === "knowledge" ? "Knowledge" : "Memory"} "${normalizedName}" already exists. Re-run with --force to overwrite it
|
|
893
|
+
throw new UsageError(`${options.type === "knowledge" ? "Knowledge" : "Memory"} "${normalizedName}" already exists. Re-run with --force to overwrite it.`, "RESOURCE_ALREADY_EXISTS");
|
|
1837
894
|
}
|
|
1838
|
-
|
|
1839
|
-
|
|
895
|
+
// Delegate the actual write (and optional git commit/push) to the helper.
|
|
896
|
+
const result = await writeAssetToSource(source, config, { type: options.type, name: normalizedName }, options.content);
|
|
1840
897
|
return {
|
|
1841
|
-
ref:
|
|
1842
|
-
path:
|
|
1843
|
-
stashDir,
|
|
898
|
+
ref: result.ref,
|
|
899
|
+
path: result.path,
|
|
900
|
+
stashDir: source.path,
|
|
1844
901
|
};
|
|
1845
902
|
}
|
|
1846
903
|
const workflowStartCommand = defineCommand({
|
|
@@ -1928,11 +985,11 @@ const workflowStatusCommand = defineCommand({
|
|
|
1928
985
|
const ref = `${parsed.origin ? `${parsed.origin}//` : ""}workflow:${parsed.name}`;
|
|
1929
986
|
const { runs } = listWorkflowRuns({ workflowRef: ref });
|
|
1930
987
|
if (runs.length === 0) {
|
|
1931
|
-
throw new NotFoundError(`No workflow runs found for ${ref}
|
|
988
|
+
throw new NotFoundError(`No workflow runs found for ${ref}`, "WORKFLOW_NOT_FOUND");
|
|
1932
989
|
}
|
|
1933
990
|
const mostRecent = runs[0];
|
|
1934
991
|
if (!mostRecent)
|
|
1935
|
-
throw new NotFoundError(`No workflow runs found for ${ref}
|
|
992
|
+
throw new NotFoundError(`No workflow runs found for ${ref}`, "WORKFLOW_NOT_FOUND");
|
|
1936
993
|
const result = getWorkflowStatus(mostRecent.id);
|
|
1937
994
|
output("workflow-status", result);
|
|
1938
995
|
}
|
|
@@ -1978,8 +1035,8 @@ const workflowCreateCommand = defineCommand({
|
|
|
1978
1035
|
default: false,
|
|
1979
1036
|
},
|
|
1980
1037
|
},
|
|
1981
|
-
run({ args }) {
|
|
1982
|
-
return runWithJsonErrors(() => {
|
|
1038
|
+
async run({ args }) {
|
|
1039
|
+
return runWithJsonErrors(async () => {
|
|
1983
1040
|
const namePattern = /^[a-z0-9][a-z0-9._/-]*$/;
|
|
1984
1041
|
if (!namePattern.test(args.name)) {
|
|
1985
1042
|
throw new UsageError("Workflow name must start with a lowercase letter or digit and contain only lowercase letters, digits, hyphens, dots, underscores, and slashes.");
|
|
@@ -1992,6 +1049,10 @@ const workflowCreateCommand = defineCommand({
|
|
|
1992
1049
|
from: args.from,
|
|
1993
1050
|
force: args.force,
|
|
1994
1051
|
});
|
|
1052
|
+
// Index the newly-written workflow so `akm workflow start` can resolve
|
|
1053
|
+
// a workflowEntryId without requiring an explicit `akm index` call
|
|
1054
|
+
// first. Uses the same incremental index path that `akm add` uses.
|
|
1055
|
+
await akmIndex({ stashDir: result.stashDir });
|
|
1995
1056
|
output("workflow-create", { ok: true, ...result });
|
|
1996
1057
|
});
|
|
1997
1058
|
},
|
|
@@ -2005,6 +1066,55 @@ const workflowTemplateCommand = defineCommand({
|
|
|
2005
1066
|
process.stdout.write(getWorkflowTemplate());
|
|
2006
1067
|
},
|
|
2007
1068
|
});
|
|
1069
|
+
const workflowValidateCommand = defineCommand({
|
|
1070
|
+
meta: {
|
|
1071
|
+
name: "validate",
|
|
1072
|
+
description: "Validate a workflow markdown file or ref and print any errors",
|
|
1073
|
+
},
|
|
1074
|
+
args: {
|
|
1075
|
+
target: {
|
|
1076
|
+
type: "positional",
|
|
1077
|
+
description: "Workflow ref (workflow:<name>) or filesystem path to a workflow .md",
|
|
1078
|
+
required: true,
|
|
1079
|
+
},
|
|
1080
|
+
},
|
|
1081
|
+
async run({ args }) {
|
|
1082
|
+
return runWithJsonErrors(async () => {
|
|
1083
|
+
const filePath = await resolveWorkflowFilePath(args.target);
|
|
1084
|
+
const { parse } = validateWorkflowSource(filePath);
|
|
1085
|
+
if (parse.ok) {
|
|
1086
|
+
output("workflow-validate", {
|
|
1087
|
+
ok: true,
|
|
1088
|
+
path: filePath,
|
|
1089
|
+
title: parse.document.title,
|
|
1090
|
+
stepCount: parse.document.steps.length,
|
|
1091
|
+
});
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
throw new UsageError(formatWorkflowErrors(filePath, parse.errors));
|
|
1095
|
+
});
|
|
1096
|
+
},
|
|
1097
|
+
});
|
|
1098
|
+
async function resolveWorkflowFilePath(target) {
|
|
1099
|
+
if (!target.startsWith("workflow:"))
|
|
1100
|
+
return target;
|
|
1101
|
+
const parsed = parseAssetRef(target);
|
|
1102
|
+
if (parsed.type !== "workflow") {
|
|
1103
|
+
throw new UsageError(`Expected a workflow ref (workflow:<name>), got "${target}".`);
|
|
1104
|
+
}
|
|
1105
|
+
const config = loadConfig();
|
|
1106
|
+
const allSources = resolveSourceEntries(undefined, config);
|
|
1107
|
+
const searchSources = resolveSourcesForOrigin(parsed.origin, allSources);
|
|
1108
|
+
for (const source of searchSources) {
|
|
1109
|
+
try {
|
|
1110
|
+
return await resolveAssetPath(source.path, "workflow", parsed.name);
|
|
1111
|
+
}
|
|
1112
|
+
catch {
|
|
1113
|
+
/* try next source */
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
throw new UsageError(`Workflow not found for ref: workflow:${parsed.name}`);
|
|
1117
|
+
}
|
|
2008
1118
|
const workflowResumeCommand = defineCommand({
|
|
2009
1119
|
meta: {
|
|
2010
1120
|
name: "resume",
|
|
@@ -2016,7 +1126,7 @@ const workflowResumeCommand = defineCommand({
|
|
|
2016
1126
|
run({ args }) {
|
|
2017
1127
|
return runWithJsonErrors(() => {
|
|
2018
1128
|
const result = resumeWorkflowRun(args.runId);
|
|
2019
|
-
output("workflow-
|
|
1129
|
+
output("workflow-resume", result);
|
|
2020
1130
|
});
|
|
2021
1131
|
},
|
|
2022
1132
|
});
|
|
@@ -2034,6 +1144,7 @@ const workflowCommand = defineCommand({
|
|
|
2034
1144
|
create: workflowCreateCommand,
|
|
2035
1145
|
template: workflowTemplateCommand,
|
|
2036
1146
|
resume: workflowResumeCommand,
|
|
1147
|
+
validate: workflowValidateCommand,
|
|
2037
1148
|
},
|
|
2038
1149
|
run({ args }) {
|
|
2039
1150
|
return runWithJsonErrors(() => {
|
|
@@ -2063,15 +1174,123 @@ const rememberCommand = defineCommand({
|
|
|
2063
1174
|
description: "Overwrite an existing memory with the same name",
|
|
2064
1175
|
default: false,
|
|
2065
1176
|
},
|
|
1177
|
+
description: {
|
|
1178
|
+
type: "string",
|
|
1179
|
+
description: "Short description written to frontmatter (persisted as the memory's description field)",
|
|
1180
|
+
},
|
|
1181
|
+
tag: {
|
|
1182
|
+
type: "string",
|
|
1183
|
+
description: "Tag to add to the memory (repeatable: --tag foo --tag bar)",
|
|
1184
|
+
},
|
|
1185
|
+
expires: {
|
|
1186
|
+
type: "string",
|
|
1187
|
+
description: "Expiry duration shorthand (e.g. 30d, 12h, 6m). Resolved to an ISO date.",
|
|
1188
|
+
},
|
|
1189
|
+
source: {
|
|
1190
|
+
type: "string",
|
|
1191
|
+
description: "Source reference (URL, asset ref, file path, or any free-form string)",
|
|
1192
|
+
},
|
|
1193
|
+
auto: {
|
|
1194
|
+
type: "boolean",
|
|
1195
|
+
description: "Apply heuristic tagging (code, subjective, source, observed_at) from the body",
|
|
1196
|
+
default: false,
|
|
1197
|
+
},
|
|
1198
|
+
enrich: {
|
|
1199
|
+
type: "boolean",
|
|
1200
|
+
description: "Call the configured LLM to propose tags and description (requires LLM config)",
|
|
1201
|
+
default: false,
|
|
1202
|
+
},
|
|
1203
|
+
target: {
|
|
1204
|
+
type: "string",
|
|
1205
|
+
description: "Override the write destination. Accepts a source name from your config; falls back to defaultWriteTarget then the working stash.",
|
|
1206
|
+
},
|
|
2066
1207
|
},
|
|
2067
|
-
run({ args }) {
|
|
2068
|
-
return runWithJsonErrors(() => {
|
|
2069
|
-
const
|
|
1208
|
+
async run({ args }) {
|
|
1209
|
+
return runWithJsonErrors(async () => {
|
|
1210
|
+
const body = readMemoryContent(args.content);
|
|
1211
|
+
// Determine if the user has requested any structured metadata mode.
|
|
1212
|
+
// Collect all --tag occurrences directly from process.argv because citty
|
|
1213
|
+
// only exposes the last value for repeated string flags.
|
|
1214
|
+
const rawTags = parseAllFlagValues("--tag");
|
|
1215
|
+
const hasStructuredArgs = rawTags.length > 0 || !!args.expires || !!args.source || !!args.description || args.auto || args.enrich;
|
|
1216
|
+
if (!hasStructuredArgs) {
|
|
1217
|
+
const result = await writeMarkdownAsset({
|
|
1218
|
+
type: "memory",
|
|
1219
|
+
content: body,
|
|
1220
|
+
name: args.name,
|
|
1221
|
+
fallbackPrefix: "memory",
|
|
1222
|
+
force: args.force,
|
|
1223
|
+
target: args.target,
|
|
1224
|
+
});
|
|
1225
|
+
output("remember", { ok: true, ...result });
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
// ── Accumulate metadata from all three modes ──────────────────────────
|
|
1229
|
+
// Start with CLI args (Mode 1: always)
|
|
1230
|
+
const tags = [...rawTags];
|
|
1231
|
+
// --description is persisted as-is; LLM enrichment may fill it if absent.
|
|
1232
|
+
let description = args.description || undefined;
|
|
1233
|
+
let source = args.source;
|
|
1234
|
+
let observed_at;
|
|
1235
|
+
let expires;
|
|
1236
|
+
let subjective;
|
|
1237
|
+
// Resolve --expires to an ISO date string
|
|
1238
|
+
if (args.expires) {
|
|
1239
|
+
const durationMs = parseDuration(args.expires);
|
|
1240
|
+
const expiresDate = new Date(Date.now() + durationMs);
|
|
1241
|
+
expires = expiresDate.toISOString().slice(0, 10);
|
|
1242
|
+
}
|
|
1243
|
+
// Mode 2: --auto heuristics
|
|
1244
|
+
if (args.auto) {
|
|
1245
|
+
const auto = runAutoHeuristics(body);
|
|
1246
|
+
for (const t of auto.tags) {
|
|
1247
|
+
if (!tags.includes(t))
|
|
1248
|
+
tags.push(t);
|
|
1249
|
+
}
|
|
1250
|
+
if (!source && auto.source)
|
|
1251
|
+
source = auto.source;
|
|
1252
|
+
if (!observed_at && auto.observed_at)
|
|
1253
|
+
observed_at = auto.observed_at;
|
|
1254
|
+
if (!subjective && auto.subjective)
|
|
1255
|
+
subjective = auto.subjective;
|
|
1256
|
+
}
|
|
1257
|
+
// Mode 3: --enrich LLM (fail-soft)
|
|
1258
|
+
if (args.enrich) {
|
|
1259
|
+
const enriched = await runLlmEnrich(body);
|
|
1260
|
+
for (const t of enriched.tags) {
|
|
1261
|
+
if (!tags.includes(t))
|
|
1262
|
+
tags.push(t);
|
|
1263
|
+
}
|
|
1264
|
+
if (!description && enriched.description)
|
|
1265
|
+
description = enriched.description;
|
|
1266
|
+
if (!observed_at && enriched.observed_at)
|
|
1267
|
+
observed_at = enriched.observed_at;
|
|
1268
|
+
}
|
|
1269
|
+
// ── Required-field check (before any write) ───────────────────────────
|
|
1270
|
+
const missing = [];
|
|
1271
|
+
if (tags.length === 0)
|
|
1272
|
+
missing.push("tags");
|
|
1273
|
+
if (missing.length > 0) {
|
|
1274
|
+
throw new UsageError(`Memory is missing required frontmatter field(s): ${missing.join(", ")}. ` +
|
|
1275
|
+
"Provide them via --tag <value>, --auto (heuristics), or --enrich (LLM).");
|
|
1276
|
+
}
|
|
1277
|
+
// ── Build frontmatter and write ───────────────────────────────────────
|
|
1278
|
+
const frontmatterBlock = buildMemoryFrontmatter({
|
|
1279
|
+
description,
|
|
1280
|
+
tags,
|
|
1281
|
+
source,
|
|
1282
|
+
observed_at,
|
|
1283
|
+
expires,
|
|
1284
|
+
subjective,
|
|
1285
|
+
});
|
|
1286
|
+
const contentWithFrontmatter = `${frontmatterBlock}\n${body}`;
|
|
1287
|
+
const result = await writeMarkdownAsset({
|
|
2070
1288
|
type: "memory",
|
|
2071
|
-
content:
|
|
1289
|
+
content: contentWithFrontmatter,
|
|
2072
1290
|
name: args.name,
|
|
2073
1291
|
fallbackPrefix: "memory",
|
|
2074
1292
|
force: args.force,
|
|
1293
|
+
target: args.target,
|
|
2075
1294
|
});
|
|
2076
1295
|
output("remember", { ok: true, ...result });
|
|
2077
1296
|
});
|
|
@@ -2097,17 +1316,22 @@ const importKnowledgeCommand = defineCommand({
|
|
|
2097
1316
|
description: "Overwrite an existing knowledge document with the same name",
|
|
2098
1317
|
default: false,
|
|
2099
1318
|
},
|
|
1319
|
+
target: {
|
|
1320
|
+
type: "string",
|
|
1321
|
+
description: "Override the write destination. Accepts a source name from your config; falls back to defaultWriteTarget then the working stash.",
|
|
1322
|
+
},
|
|
2100
1323
|
},
|
|
2101
1324
|
async run({ args }) {
|
|
2102
1325
|
return runWithJsonErrors(async () => {
|
|
2103
1326
|
const { content, preferredName } = readKnowledgeContent(args.source);
|
|
2104
|
-
const result = writeMarkdownAsset({
|
|
1327
|
+
const result = await writeMarkdownAsset({
|
|
2105
1328
|
type: "knowledge",
|
|
2106
1329
|
content,
|
|
2107
1330
|
name: args.name,
|
|
2108
1331
|
fallbackPrefix: "knowledge",
|
|
2109
1332
|
preferredName,
|
|
2110
1333
|
force: args.force,
|
|
1334
|
+
target: args.target,
|
|
2111
1335
|
});
|
|
2112
1336
|
output("import", { ok: true, source: args.source, ...result });
|
|
2113
1337
|
});
|
|
@@ -2122,8 +1346,10 @@ const hintsCommand = defineCommand({
|
|
|
2122
1346
|
detail: { type: "string", description: "Detail level (normal|full)", default: "normal" },
|
|
2123
1347
|
},
|
|
2124
1348
|
run({ args }) {
|
|
2125
|
-
|
|
2126
|
-
|
|
1349
|
+
if (args.detail !== "normal" && args.detail !== "full") {
|
|
1350
|
+
throw new UsageError(`Invalid value for --detail: ${args.detail}. Expected one of: normal|full.`, "INVALID_DETAIL_VALUE");
|
|
1351
|
+
}
|
|
1352
|
+
process.stdout.write(loadHints(args.detail));
|
|
2127
1353
|
},
|
|
2128
1354
|
});
|
|
2129
1355
|
const helpCommand = defineCommand({
|
|
@@ -2135,12 +1361,12 @@ const helpCommand = defineCommand({
|
|
|
2135
1361
|
migrate: defineCommand({
|
|
2136
1362
|
meta: {
|
|
2137
1363
|
name: "migrate",
|
|
2138
|
-
description: "Print release notes and migration guidance for a version",
|
|
1364
|
+
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
1365
|
},
|
|
2140
1366
|
args: {
|
|
2141
1367
|
version: {
|
|
2142
1368
|
type: "positional",
|
|
2143
|
-
description: "Version to review (for example 0.
|
|
1369
|
+
description: "Version to review (for example 0.6.0, v0.6.0, 0.6.0-rc1, or latest)",
|
|
2144
1370
|
required: true,
|
|
2145
1371
|
},
|
|
2146
1372
|
},
|
|
@@ -2186,9 +1412,10 @@ function normalizeToggleTarget(target) {
|
|
|
2186
1412
|
const normalized = target.trim().toLowerCase();
|
|
2187
1413
|
if (normalized === "skills.sh" || normalized === "skills-sh")
|
|
2188
1414
|
return "skills.sh";
|
|
2189
|
-
if (normalized === "context-hub")
|
|
2190
|
-
|
|
2191
|
-
|
|
1415
|
+
if (normalized === "context-hub") {
|
|
1416
|
+
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.');
|
|
1417
|
+
}
|
|
1418
|
+
throw new UsageError(`Unsupported target "${target}". Supported targets: skills.sh`);
|
|
2192
1419
|
}
|
|
2193
1420
|
function toggleSkillsShRegistry(enabled) {
|
|
2194
1421
|
const config = loadUserConfig();
|
|
@@ -2211,34 +1438,17 @@ function toggleSkillsShRegistry(enabled) {
|
|
|
2211
1438
|
saveConfig({ ...config, registries });
|
|
2212
1439
|
return { changed: true, component: SKILLS_SH_NAME, enabled: true };
|
|
2213
1440
|
}
|
|
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
1441
|
function toggleComponent(targetRaw, enabled) {
|
|
2233
1442
|
const target = normalizeToggleTarget(targetRaw);
|
|
2234
1443
|
if (target === "skills.sh")
|
|
2235
1444
|
return toggleSkillsShRegistry(enabled);
|
|
2236
|
-
|
|
1445
|
+
// normalizeToggleTarget throws for any unsupported target; this is unreachable.
|
|
1446
|
+
throw new UsageError(`Unsupported target "${targetRaw}". Supported targets: skills.sh`);
|
|
2237
1447
|
}
|
|
2238
1448
|
const enableCommand = defineCommand({
|
|
2239
|
-
meta: { name: "enable", description: "Enable an optional component (skills.sh
|
|
1449
|
+
meta: { name: "enable", description: "Enable an optional component (skills.sh)" },
|
|
2240
1450
|
args: {
|
|
2241
|
-
target: { type: "positional", description: "Component to enable (skills.sh
|
|
1451
|
+
target: { type: "positional", description: "Component to enable (skills.sh)", required: true },
|
|
2242
1452
|
},
|
|
2243
1453
|
run({ args }) {
|
|
2244
1454
|
return runWithJsonErrors(() => {
|
|
@@ -2248,9 +1458,9 @@ const enableCommand = defineCommand({
|
|
|
2248
1458
|
},
|
|
2249
1459
|
});
|
|
2250
1460
|
const disableCommand = defineCommand({
|
|
2251
|
-
meta: { name: "disable", description: "Disable an optional component (skills.sh
|
|
1461
|
+
meta: { name: "disable", description: "Disable an optional component (skills.sh)" },
|
|
2252
1462
|
args: {
|
|
2253
|
-
target: { type: "positional", description: "Component to disable (skills.sh
|
|
1463
|
+
target: { type: "positional", description: "Component to disable (skills.sh)", required: true },
|
|
2254
1464
|
},
|
|
2255
1465
|
run({ args }) {
|
|
2256
1466
|
return runWithJsonErrors(() => {
|
|
@@ -2317,14 +1527,14 @@ const vaultListCommand = defineCommand({
|
|
|
2317
1527
|
},
|
|
2318
1528
|
run({ args }) {
|
|
2319
1529
|
return runWithJsonErrors(async () => {
|
|
2320
|
-
const { listKeys } = await import("./vault.js");
|
|
1530
|
+
const { listKeys, listEntries } = await import("./commands/vault.js");
|
|
2321
1531
|
if (args.ref) {
|
|
2322
1532
|
const { name, absPath } = resolveVaultPath(args.ref);
|
|
2323
1533
|
if (!fs.existsSync(absPath)) {
|
|
2324
1534
|
throw new NotFoundError(`Vault not found: vault:${name}`);
|
|
2325
1535
|
}
|
|
2326
|
-
const
|
|
2327
|
-
output("vault-list", { ref: `vault:${name}`, path: absPath,
|
|
1536
|
+
const entries = listEntries(absPath);
|
|
1537
|
+
output("vault-list", { ref: `vault:${name}`, path: absPath, entries });
|
|
2328
1538
|
return;
|
|
2329
1539
|
}
|
|
2330
1540
|
const vaults = listVaultsRecursive(listKeys);
|
|
@@ -2339,7 +1549,7 @@ const vaultCreateCommand = defineCommand({
|
|
|
2339
1549
|
},
|
|
2340
1550
|
run({ args }) {
|
|
2341
1551
|
return runWithJsonErrors(async () => {
|
|
2342
|
-
const { createVault } = await import("./vault.js");
|
|
1552
|
+
const { createVault } = await import("./commands/vault.js");
|
|
2343
1553
|
const { name, absPath } = resolveVaultPath(args.name);
|
|
2344
1554
|
createVault(absPath);
|
|
2345
1555
|
output("vault-create", { ref: `vault:${name}`, path: absPath });
|
|
@@ -2363,7 +1573,7 @@ const vaultSetCommand = defineCommand({
|
|
|
2363
1573
|
},
|
|
2364
1574
|
run({ args }) {
|
|
2365
1575
|
return runWithJsonErrors(async () => {
|
|
2366
|
-
const { setKey } = await import("./vault.js");
|
|
1576
|
+
const { setKey } = await import("./commands/vault.js");
|
|
2367
1577
|
const { name, absPath } = resolveVaultPath(args.ref);
|
|
2368
1578
|
let realKey;
|
|
2369
1579
|
let realValue;
|
|
@@ -2389,7 +1599,7 @@ const vaultUnsetCommand = defineCommand({
|
|
|
2389
1599
|
},
|
|
2390
1600
|
run({ args }) {
|
|
2391
1601
|
return runWithJsonErrors(async () => {
|
|
2392
|
-
const { unsetKey } = await import("./vault.js");
|
|
1602
|
+
const { unsetKey } = await import("./commands/vault.js");
|
|
2393
1603
|
const { name, absPath } = resolveVaultPath(args.ref);
|
|
2394
1604
|
if (!fs.existsSync(absPath)) {
|
|
2395
1605
|
throw new NotFoundError(`Vault not found: vault:${name}`);
|
|
@@ -2415,7 +1625,7 @@ const vaultLoadCommand = defineCommand({
|
|
|
2415
1625
|
if (!fs.existsSync(absPath)) {
|
|
2416
1626
|
throw new NotFoundError(`Vault not found: vault:${name}`);
|
|
2417
1627
|
}
|
|
2418
|
-
const { buildShellExportScript } = await import("./vault.js");
|
|
1628
|
+
const { buildShellExportScript } = await import("./commands/vault.js");
|
|
2419
1629
|
const crypto = await import("node:crypto");
|
|
2420
1630
|
const os = await import("node:os");
|
|
2421
1631
|
// Parse via dotenv (no expansion, no code execution) and build a
|
|
@@ -2446,13 +1656,13 @@ const vaultShowCommand = defineCommand({
|
|
|
2446
1656
|
},
|
|
2447
1657
|
run({ args }) {
|
|
2448
1658
|
return runWithJsonErrors(async () => {
|
|
2449
|
-
const {
|
|
1659
|
+
const { listEntries } = await import("./commands/vault.js");
|
|
2450
1660
|
const { name, absPath } = resolveVaultPath(args.ref);
|
|
2451
1661
|
if (!fs.existsSync(absPath)) {
|
|
2452
1662
|
throw new NotFoundError(`Vault not found: vault:${name}`);
|
|
2453
1663
|
}
|
|
2454
|
-
const
|
|
2455
|
-
output("vault-list", { ref: `vault:${name}`, path: absPath,
|
|
1664
|
+
const entries = listEntries(absPath);
|
|
1665
|
+
output("vault-list", { ref: `vault:${name}`, path: absPath, entries });
|
|
2456
1666
|
});
|
|
2457
1667
|
},
|
|
2458
1668
|
});
|
|
@@ -2474,7 +1684,7 @@ const vaultCommand = defineCommand({
|
|
|
2474
1684
|
if (hasVaultSubcommand(args))
|
|
2475
1685
|
return;
|
|
2476
1686
|
// Default action: list all vaults
|
|
2477
|
-
const { listKeys } = await import("./vault.js");
|
|
1687
|
+
const { listKeys } = await import("./commands/vault.js");
|
|
2478
1688
|
output("vault-list", { vaults: listVaultsRecursive(listKeys) });
|
|
2479
1689
|
});
|
|
2480
1690
|
},
|
|
@@ -2487,7 +1697,7 @@ const wikiCreateCommand = defineCommand({
|
|
|
2487
1697
|
},
|
|
2488
1698
|
run({ args }) {
|
|
2489
1699
|
return runWithJsonErrors(async () => {
|
|
2490
|
-
const { createWiki } = await import("./wiki.js");
|
|
1700
|
+
const { createWiki } = await import("./wiki/wiki.js");
|
|
2491
1701
|
const stashDir = resolveStashDir();
|
|
2492
1702
|
const result = createWiki(stashDir, args.name);
|
|
2493
1703
|
output("wiki-create", result);
|
|
@@ -2517,7 +1727,7 @@ const wikiRegisterCommand = defineCommand({
|
|
|
2517
1727
|
},
|
|
2518
1728
|
run({ args }) {
|
|
2519
1729
|
return runWithJsonErrors(async () => {
|
|
2520
|
-
const { registerWikiSource } = await import("./
|
|
1730
|
+
const { registerWikiSource } = await import("./commands/source-add");
|
|
2521
1731
|
const result = await registerWikiSource({
|
|
2522
1732
|
ref: args.ref.trim(),
|
|
2523
1733
|
name: args.name,
|
|
@@ -2533,7 +1743,7 @@ const wikiListCommand = defineCommand({
|
|
|
2533
1743
|
meta: { name: "list", description: "List wikis with page/raw counts and last-modified timestamps" },
|
|
2534
1744
|
run() {
|
|
2535
1745
|
return runWithJsonErrors(async () => {
|
|
2536
|
-
const { listWikis } = await import("./wiki.js");
|
|
1746
|
+
const { listWikis } = await import("./wiki/wiki.js");
|
|
2537
1747
|
const stashDir = resolveStashDir();
|
|
2538
1748
|
const wikis = listWikis(stashDir);
|
|
2539
1749
|
output("wiki-list", { wikis });
|
|
@@ -2547,7 +1757,7 @@ const wikiShowCommand = defineCommand({
|
|
|
2547
1757
|
},
|
|
2548
1758
|
run({ args }) {
|
|
2549
1759
|
return runWithJsonErrors(async () => {
|
|
2550
|
-
const { showWiki } = await import("./wiki.js");
|
|
1760
|
+
const { showWiki } = await import("./wiki/wiki.js");
|
|
2551
1761
|
const stashDir = resolveStashDir();
|
|
2552
1762
|
const result = showWiki(stashDir, args.name);
|
|
2553
1763
|
output("wiki-show", result);
|
|
@@ -2577,9 +1787,9 @@ const wikiRemoveCommand = defineCommand({
|
|
|
2577
1787
|
if (!args.force) {
|
|
2578
1788
|
throw new UsageError("Refusing to remove without --force. Pass `--force` to confirm.");
|
|
2579
1789
|
}
|
|
2580
|
-
const withSources =
|
|
2581
|
-
const { removeWiki } = await import("./wiki.js");
|
|
2582
|
-
const { akmIndex } = await import("./indexer");
|
|
1790
|
+
const withSources = getHyphenatedBoolean(args, "with-sources");
|
|
1791
|
+
const { removeWiki } = await import("./wiki/wiki.js");
|
|
1792
|
+
const { akmIndex } = await import("./indexer/indexer");
|
|
2583
1793
|
const stashDir = resolveStashDir();
|
|
2584
1794
|
const result = removeWiki(stashDir, args.name, { withSources });
|
|
2585
1795
|
await akmIndex({ stashDir });
|
|
@@ -2597,7 +1807,7 @@ const wikiPagesCommand = defineCommand({
|
|
|
2597
1807
|
},
|
|
2598
1808
|
run({ args }) {
|
|
2599
1809
|
return runWithJsonErrors(async () => {
|
|
2600
|
-
const { listPages } = await import("./wiki.js");
|
|
1810
|
+
const { listPages } = await import("./wiki/wiki.js");
|
|
2601
1811
|
const stashDir = resolveStashDir();
|
|
2602
1812
|
const pages = listPages(stashDir, args.name);
|
|
2603
1813
|
output("wiki-pages", { wiki: args.name, pages });
|
|
@@ -2616,7 +1826,7 @@ const wikiSearchCommand = defineCommand({
|
|
|
2616
1826
|
},
|
|
2617
1827
|
run({ args }) {
|
|
2618
1828
|
return runWithJsonErrors(async () => {
|
|
2619
|
-
const { resolveWikiSource, searchInWiki } = await import("./wiki.js");
|
|
1829
|
+
const { resolveWikiSource, searchInWiki } = await import("./wiki/wiki.js");
|
|
2620
1830
|
const stashDir = resolveStashDir();
|
|
2621
1831
|
resolveWikiSource(stashDir, args.name);
|
|
2622
1832
|
const parsedLimit = args.limit ? Number(args.limit) : undefined;
|
|
@@ -2638,7 +1848,7 @@ const wikiStashCommand = defineCommand({
|
|
|
2638
1848
|
},
|
|
2639
1849
|
run({ args }) {
|
|
2640
1850
|
return runWithJsonErrors(async () => {
|
|
2641
|
-
const { stashRaw } = await import("./wiki.js");
|
|
1851
|
+
const { stashRaw } = await import("./wiki/wiki.js");
|
|
2642
1852
|
const { content, preferredName } = readKnowledgeContent(args.source);
|
|
2643
1853
|
const stashDir = resolveStashDir();
|
|
2644
1854
|
const result = stashRaw({
|
|
@@ -2663,7 +1873,7 @@ const wikiLintCommand = defineCommand({
|
|
|
2663
1873
|
async run({ args }) {
|
|
2664
1874
|
let findingCount = 0;
|
|
2665
1875
|
await runWithJsonErrors(async () => {
|
|
2666
|
-
const { lintWiki } = await import("./wiki.js");
|
|
1876
|
+
const { lintWiki } = await import("./wiki/wiki.js");
|
|
2667
1877
|
const stashDir = resolveStashDir();
|
|
2668
1878
|
const report = lintWiki(stashDir, args.name);
|
|
2669
1879
|
output("wiki-lint", report);
|
|
@@ -2683,7 +1893,7 @@ const wikiIngestCommand = defineCommand({
|
|
|
2683
1893
|
},
|
|
2684
1894
|
run({ args }) {
|
|
2685
1895
|
return runWithJsonErrors(async () => {
|
|
2686
|
-
const { buildIngestWorkflow } = await import("./wiki.js");
|
|
1896
|
+
const { buildIngestWorkflow } = await import("./wiki/wiki.js");
|
|
2687
1897
|
const stashDir = resolveStashDir();
|
|
2688
1898
|
const result = buildIngestWorkflow(stashDir, args.name);
|
|
2689
1899
|
output("wiki-ingest", result);
|
|
@@ -2712,7 +1922,7 @@ const wikiCommand = defineCommand({
|
|
|
2712
1922
|
if (hasWikiSubcommand(args))
|
|
2713
1923
|
return;
|
|
2714
1924
|
// Default action: list wikis
|
|
2715
|
-
const { listWikis } = await import("./wiki.js");
|
|
1925
|
+
const { listWikis } = await import("./wiki/wiki.js");
|
|
2716
1926
|
output("wiki-list", { wikis: listWikis(resolveStashDir()) });
|
|
2717
1927
|
});
|
|
2718
1928
|
},
|
|
@@ -2725,7 +1935,7 @@ const main = defineCommand({
|
|
|
2725
1935
|
},
|
|
2726
1936
|
args: {
|
|
2727
1937
|
format: { type: "string", description: "Output format (json|jsonl|text|yaml)" },
|
|
2728
|
-
detail: { type: "string", description: "Detail level (brief|normal|full|summary)" },
|
|
1938
|
+
detail: { type: "string", description: "Detail level (brief|normal|full|summary|agent)" },
|
|
2729
1939
|
quiet: { type: "boolean", alias: "q", description: "Suppress stderr warnings", default: false },
|
|
2730
1940
|
},
|
|
2731
1941
|
subCommands: {
|
|
@@ -2773,14 +1983,32 @@ const WIKI_SUBCOMMAND_SET = new Set([
|
|
|
2773
1983
|
"ingest",
|
|
2774
1984
|
]);
|
|
2775
1985
|
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
1986
|
// ── Exit codes ──────────────────────────────────────────────────────────────
|
|
2781
1987
|
const EXIT_GENERAL = 1;
|
|
2782
1988
|
const EXIT_USAGE = 2;
|
|
2783
1989
|
const EXIT_CONFIG = 78;
|
|
1990
|
+
// citty reads process.argv directly and does not accept a custom argv array,
|
|
1991
|
+
// so we must replace process.argv with the normalized version before runMain.
|
|
1992
|
+
process.argv = normalizeShowArgv(process.argv);
|
|
1993
|
+
// Resolve output mode once at startup from the (normalized) argv and persisted
|
|
1994
|
+
// config. All subsequent output() calls read from this in-memory singleton.
|
|
1995
|
+
// `initOutputMode` can throw a UsageError when --format/--detail values are
|
|
1996
|
+
// invalid; surface it through the same JSON-error path the rest of the CLI uses
|
|
1997
|
+
// rather than letting the raw exception escape with a stack trace.
|
|
1998
|
+
try {
|
|
1999
|
+
initOutputMode(process.argv, loadConfig().output ?? {});
|
|
2000
|
+
}
|
|
2001
|
+
catch (error) {
|
|
2002
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2003
|
+
const hint = extractHint(error);
|
|
2004
|
+
const exitCode = classifyExitCode(error);
|
|
2005
|
+
const code = error instanceof UsageError || error instanceof ConfigError || error instanceof NotFoundError
|
|
2006
|
+
? error.code
|
|
2007
|
+
: undefined;
|
|
2008
|
+
console.error(JSON.stringify({ ok: false, error: message, ...(code ? { code } : {}), hint }, null, 2));
|
|
2009
|
+
process.exit(exitCode);
|
|
2010
|
+
}
|
|
2011
|
+
runMain(main);
|
|
2784
2012
|
function classifyExitCode(error) {
|
|
2785
2013
|
if (error instanceof UsageError)
|
|
2786
2014
|
return EXIT_USAGE;
|
|
@@ -2800,31 +2028,25 @@ async function runWithJsonErrors(fn) {
|
|
|
2800
2028
|
}
|
|
2801
2029
|
catch (error) {
|
|
2802
2030
|
const message = error instanceof Error ? error.message : String(error);
|
|
2803
|
-
const hint =
|
|
2031
|
+
const hint = extractHint(error);
|
|
2804
2032
|
const exitCode = classifyExitCode(error);
|
|
2805
|
-
|
|
2033
|
+
// Surface machine-readable error code from typed errors when present so
|
|
2034
|
+
// scripts can branch on `.code` instead of message-string matching.
|
|
2035
|
+
const code = error instanceof UsageError || error instanceof ConfigError || error instanceof NotFoundError
|
|
2036
|
+
? error.code
|
|
2037
|
+
: undefined;
|
|
2038
|
+
console.error(JSON.stringify({ ok: false, error: message, ...(code ? { code } : {}), hint }, null, 2));
|
|
2806
2039
|
process.exit(exitCode);
|
|
2807
2040
|
}
|
|
2808
2041
|
}
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
return "Run `akm list` to view your sources, then retry with one of those values.";
|
|
2818
|
-
if (message.includes("remote package fetched but asset not found"))
|
|
2819
|
-
return "The remote package was fetched but doesn't contain the requested asset. Check the asset name and type.";
|
|
2820
|
-
if (message.includes("Invalid value for --source"))
|
|
2821
|
-
return "Pick one of: stash, registry, both.";
|
|
2822
|
-
if (message.includes("Invalid value for --format"))
|
|
2823
|
-
return "Pick one of: json, jsonl, text, yaml.";
|
|
2824
|
-
if (message.includes("Invalid value for --detail"))
|
|
2825
|
-
return "Pick one of: brief, normal, full, summary.";
|
|
2826
|
-
if (message.includes("expected JSON object with endpoint and model")) {
|
|
2827
|
-
return 'Quote JSON values in your shell, for example: akm config set embedding \'{"endpoint":"http://localhost:11434/v1/embeddings","model":"nomic-embed-text"}\'.';
|
|
2042
|
+
/**
|
|
2043
|
+
* Extract an actionable hint from an error instance. Hints live on the error
|
|
2044
|
+
* classes themselves (see src/errors.ts) — either supplied explicitly at the
|
|
2045
|
+
* throw site, or derived from the error code via the per-class default mapping.
|
|
2046
|
+
*/
|
|
2047
|
+
function extractHint(error) {
|
|
2048
|
+
if (error instanceof Error && "hint" in error && typeof error.hint === "function") {
|
|
2049
|
+
return error.hint();
|
|
2828
2050
|
}
|
|
2829
2051
|
return undefined;
|
|
2830
2052
|
}
|
|
@@ -2926,294 +2148,3 @@ function loadHints(detail = "normal") {
|
|
|
2926
2148
|
// Fallback for compiled binary — inline content
|
|
2927
2149
|
return fallback;
|
|
2928
2150
|
}
|
|
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
|
-
`;
|