akm-cli 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/asset-registry.js +48 -0
- package/dist/asset-spec.js +11 -32
- package/dist/cli.js +174 -57
- package/dist/completions.js +4 -2
- package/dist/config.js +34 -6
- package/dist/db.js +178 -22
- package/dist/detect.js +120 -0
- package/dist/embedder.js +94 -13
- package/dist/file-context.js +3 -0
- package/dist/indexer.js +88 -37
- package/dist/info.js +92 -0
- package/dist/local-search.js +190 -90
- package/dist/manifest.js +172 -0
- package/dist/metadata.js +165 -2
- package/dist/providers/skills-sh.js +21 -12
- package/dist/providers/static-index.js +3 -1
- package/dist/registry-build-index.js +12 -1
- package/dist/registry-resolve.js +10 -7
- package/dist/search-fields.js +69 -0
- package/dist/search-source.js +42 -0
- package/dist/setup.js +506 -0
- package/dist/stash-clone.js +3 -1
- package/dist/stash-provider-factory.js +0 -2
- package/dist/stash-providers/filesystem.js +4 -5
- package/dist/stash-providers/git.js +140 -0
- package/dist/stash-providers/index.js +1 -1
- package/dist/stash-providers/openviking.js +36 -25
- package/dist/stash-providers/provider-utils.js +11 -0
- package/dist/stash-search.js +106 -90
- package/dist/stash-show.js +125 -9
- package/dist/usage-events.js +73 -0
- package/dist/version.js +20 -0
- package/dist/walker.js +1 -2
- package/package.json +4 -2
- package/dist/stash-providers/context-hub.js +0 -389
package/README.md
CHANGED
|
@@ -25,12 +25,15 @@ Upgrade in place with `akm upgrade`.
|
|
|
25
25
|
## Quick Start
|
|
26
26
|
|
|
27
27
|
```sh
|
|
28
|
-
akm
|
|
28
|
+
akm setup # Guided setup: configure, initialize, and index
|
|
29
29
|
akm add github:owner/repo # Add a kit from GitHub
|
|
30
30
|
akm search "deploy" # Find assets
|
|
31
31
|
akm show script:deploy.sh # View details and run command
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
+
If you want to skip the wizard, `akm init --dir ~/custom-stash` initializes the
|
|
35
|
+
working stash at a custom path.
|
|
36
|
+
|
|
34
37
|
## Features
|
|
35
38
|
|
|
36
39
|
### Works with Any AI Agent
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Central registry for asset type renderer and action builder maps.
|
|
3
|
+
*
|
|
4
|
+
* Previously these maps lived in `local-search.ts` and were wired into
|
|
5
|
+
* `asset-spec.ts` via a fragile `_setAssetTypeHooks` deferred callback
|
|
6
|
+
* pattern. If `local-search.ts` was imported after `registerAssetType()`
|
|
7
|
+
* calls, hooks would be silently dropped.
|
|
8
|
+
*
|
|
9
|
+
* This module is a simple singleton that both `asset-spec.ts` and
|
|
10
|
+
* `local-search.ts` import from, eliminating the import-order dependency
|
|
11
|
+
* entirely.
|
|
12
|
+
*/
|
|
13
|
+
/** Map asset types to their primary renderer names. */
|
|
14
|
+
export const TYPE_TO_RENDERER = {
|
|
15
|
+
script: "script-source",
|
|
16
|
+
skill: "skill-md",
|
|
17
|
+
command: "command-md",
|
|
18
|
+
agent: "agent-md",
|
|
19
|
+
knowledge: "knowledge-md",
|
|
20
|
+
memory: "memory-md",
|
|
21
|
+
};
|
|
22
|
+
/** Map asset types to action builder functions for search results. */
|
|
23
|
+
export const ACTION_BUILDERS = {
|
|
24
|
+
script: (ref) => `akm show ${ref} -> execute the run command`,
|
|
25
|
+
skill: (ref) => `akm show ${ref} -> follow the instructions`,
|
|
26
|
+
command: (ref) => `akm show ${ref} -> fill placeholders and dispatch`,
|
|
27
|
+
agent: (ref) => `akm show ${ref} -> dispatch with full prompt`,
|
|
28
|
+
knowledge: (ref) => `akm show ${ref} -> read reference material`,
|
|
29
|
+
memory: (ref) => `akm show ${ref} -> recall context`,
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Register a type-to-renderer mapping.
|
|
33
|
+
*
|
|
34
|
+
* Called by `registerAssetType()` in `asset-spec.ts` when a spec includes
|
|
35
|
+
* `rendererName`, or directly by extension code.
|
|
36
|
+
*/
|
|
37
|
+
export function registerTypeRenderer(type, rendererName) {
|
|
38
|
+
TYPE_TO_RENDERER[type] = rendererName;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Register an action builder for an asset type.
|
|
42
|
+
*
|
|
43
|
+
* Called by `registerAssetType()` in `asset-spec.ts` when a spec includes
|
|
44
|
+
* `actionBuilder`, or directly by extension code.
|
|
45
|
+
*/
|
|
46
|
+
export function registerActionBuilder(type, builder) {
|
|
47
|
+
ACTION_BUILDERS[type] = builder;
|
|
48
|
+
}
|
package/dist/asset-spec.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { registerActionBuilder, registerTypeRenderer } from "./asset-registry";
|
|
2
3
|
import { toPosix } from "./common";
|
|
3
4
|
const markdownSpec = {
|
|
4
5
|
isRelevantFile: (fileName) => path.extname(fileName).toLowerCase() === ".md",
|
|
@@ -56,28 +57,6 @@ const ASSET_SPECS_INTERNAL = {
|
|
|
56
57
|
memory: { stashDir: "memories", ...markdownSpec },
|
|
57
58
|
};
|
|
58
59
|
export const ASSET_SPECS = ASSET_SPECS_INTERNAL;
|
|
59
|
-
/**
|
|
60
|
-
* Deferred hooks set by `local-search.ts` at module init time to avoid a
|
|
61
|
-
* circular dependency (asset-spec → local-search → asset-spec).
|
|
62
|
-
*
|
|
63
|
-
* When `registerAssetType` is called with a spec that includes `rendererName`
|
|
64
|
-
* or `actionBuilder`, these hooks are invoked automatically so callers only
|
|
65
|
-
* need a single `registerAssetType(type, spec)` call to fully register a new
|
|
66
|
-
* asset type — no separate `registerTypeRenderer`/`registerActionBuilder` calls
|
|
67
|
-
* are required.
|
|
68
|
-
*/
|
|
69
|
-
let _registerTypeRenderer;
|
|
70
|
-
let _registerActionBuilder;
|
|
71
|
-
/**
|
|
72
|
-
* Called once by `local-search.ts` during module initialization to wire in the
|
|
73
|
-
* renderer and action-builder registration hooks.
|
|
74
|
-
*
|
|
75
|
-
* @internal — not part of the public extension API; use `registerAssetType` instead.
|
|
76
|
-
*/
|
|
77
|
-
export function _setAssetTypeHooks(rendererHook, actionBuilderHook) {
|
|
78
|
-
_registerTypeRenderer = rendererHook;
|
|
79
|
-
_registerActionBuilder = actionBuilderHook;
|
|
80
|
-
}
|
|
81
60
|
/**
|
|
82
61
|
* Register a custom asset type with the akm asset system.
|
|
83
62
|
*
|
|
@@ -109,27 +88,27 @@ export function _setAssetTypeHooks(rendererHook, actionBuilderHook) {
|
|
|
109
88
|
* });
|
|
110
89
|
* ```
|
|
111
90
|
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
* stored in the spec and will take effect once the hooks are set.
|
|
91
|
+
* Renderer and action builder registration is handled directly via the
|
|
92
|
+
* `asset-registry` singleton — no deferred hooks or import-order concerns.
|
|
115
93
|
*/
|
|
116
94
|
export function registerAssetType(type, spec) {
|
|
117
95
|
ASSET_SPECS_INTERNAL[type] = spec;
|
|
118
96
|
TYPE_DIRS[type] = spec.stashDir;
|
|
119
|
-
ASSET_TYPES =
|
|
97
|
+
ASSET_TYPES.length = 0;
|
|
98
|
+
ASSET_TYPES.push(...getAssetTypes());
|
|
120
99
|
// Auto-register renderer and action builder if provided in spec
|
|
121
|
-
if (spec.rendererName
|
|
122
|
-
|
|
100
|
+
if (spec.rendererName) {
|
|
101
|
+
registerTypeRenderer(type, spec.rendererName);
|
|
123
102
|
}
|
|
124
|
-
if (spec.actionBuilder
|
|
125
|
-
|
|
103
|
+
if (spec.actionBuilder) {
|
|
104
|
+
registerActionBuilder(type, spec.actionBuilder);
|
|
126
105
|
}
|
|
127
106
|
}
|
|
128
107
|
export function getAssetTypes() {
|
|
129
108
|
return Object.keys(ASSET_SPECS_INTERNAL);
|
|
130
109
|
}
|
|
131
|
-
/** Warning: mutable
|
|
132
|
-
export
|
|
110
|
+
/** Warning: mutable array — stale if captured before `registerAssetType()` calls. Prefer `getAssetTypes()`. */
|
|
111
|
+
export const ASSET_TYPES = getAssetTypes();
|
|
133
112
|
export const TYPE_DIRS = Object.fromEntries(Object.entries(ASSET_SPECS_INTERNAL).map(([type, spec]) => [type, spec.stashDir]));
|
|
134
113
|
export function isRelevantAssetFile(assetType, fileName) {
|
|
135
114
|
return ASSET_SPECS[assetType]?.isRelevantFile(fileName) ?? false;
|
package/dist/cli.js
CHANGED
|
@@ -6,8 +6,10 @@ import { resolveStashDir } from "./common";
|
|
|
6
6
|
import { generateBashCompletions, installBashCompletions } from "./completions";
|
|
7
7
|
import { DEFAULT_CONFIG, getConfigPath, loadConfig, saveConfig } from "./config";
|
|
8
8
|
import { getConfigValue, listConfig, setConfigValue, unsetConfigValue } from "./config-cli";
|
|
9
|
+
import { closeDatabase, openDatabase } from "./db";
|
|
9
10
|
import { ConfigError, NotFoundError, UsageError } from "./errors";
|
|
10
11
|
import { akmIndex } from "./indexer";
|
|
12
|
+
import { assembleInfo } from "./info";
|
|
11
13
|
import { akmInit } from "./init";
|
|
12
14
|
import { akmList, akmRemove, akmUpdate } from "./installed-kits";
|
|
13
15
|
import { getCacheDir, getDbPath, getDefaultStashDir } from "./paths";
|
|
@@ -19,42 +21,15 @@ import { akmClone } from "./stash-clone";
|
|
|
19
21
|
import { akmSearch, parseSearchSource } from "./stash-search";
|
|
20
22
|
import { akmShowUnified } from "./stash-show";
|
|
21
23
|
import { addStash, listStashes, removeStash } from "./stash-source-manage";
|
|
24
|
+
import { insertUsageEvent } from "./usage-events";
|
|
25
|
+
import { pkgVersion } from "./version";
|
|
22
26
|
import { setQuiet, warn } from "./warn";
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
// Injected at compile time via `bun build --define`
|
|
26
|
-
if (typeof AKM_VERSION !== "undefined")
|
|
27
|
-
return AKM_VERSION;
|
|
28
|
-
try {
|
|
29
|
-
const pkgPath = path.resolve(import.meta.dir ?? __dirname, "../package.json");
|
|
30
|
-
if (fs.existsSync(pkgPath)) {
|
|
31
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
32
|
-
if (typeof pkg.version === "string")
|
|
33
|
-
return pkg.version;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
catch {
|
|
37
|
-
// swallow — running as compiled binary without package.json
|
|
38
|
-
}
|
|
39
|
-
return "0.0.0-dev";
|
|
40
|
-
})();
|
|
41
|
-
const OUTPUT_FORMATS = ["json", "yaml", "text"];
|
|
42
|
-
const DETAIL_LEVELS = ["brief", "normal", "full"];
|
|
27
|
+
const OUTPUT_FORMATS = ["json", "yaml", "text", "jsonl"];
|
|
28
|
+
const DETAIL_LEVELS = ["brief", "normal", "full", "summary"];
|
|
43
29
|
const NORMAL_DESCRIPTION_LIMIT = 250;
|
|
44
30
|
const CONTEXT_HUB_ALIAS_REF = "context-hub";
|
|
45
31
|
const CONTEXT_HUB_ALIAS_URL = "https://github.com/andrewyng/context-hub";
|
|
46
|
-
|
|
47
|
-
// biome-ignore lint/suspicious/noExplicitAny: type guard for runtime feature detection
|
|
48
|
-
return typeof b.YAML?.stringify === "function";
|
|
49
|
-
}
|
|
50
|
-
/** Try Bun.YAML.stringify; fall back to JSON if the API is unavailable */
|
|
51
|
-
function yamlStringify(obj) {
|
|
52
|
-
if (hasBunYAML(Bun)) {
|
|
53
|
-
return Bun.YAML.stringify(obj);
|
|
54
|
-
}
|
|
55
|
-
warn("YAML output not available, using JSON");
|
|
56
|
-
return JSON.stringify(obj, null, 2);
|
|
57
|
-
}
|
|
32
|
+
import { stringify as yamlStringify } from "yaml";
|
|
58
33
|
function parseOutputFormat(value) {
|
|
59
34
|
if (!value)
|
|
60
35
|
return undefined;
|
|
@@ -79,15 +54,25 @@ function parseFlagValue(flag) {
|
|
|
79
54
|
}
|
|
80
55
|
return undefined;
|
|
81
56
|
}
|
|
57
|
+
// Uses process.argv directly because the global output() function (called by all
|
|
58
|
+
// commands) needs this flag but doesn't have access to citty's parsed args.
|
|
59
|
+
function hasBooleanFlag(flag) {
|
|
60
|
+
return process.argv.some((arg) => arg === flag || arg === `${flag}=true`);
|
|
61
|
+
}
|
|
82
62
|
function resolveOutputMode() {
|
|
83
63
|
const config = loadConfig();
|
|
84
64
|
const format = parseOutputFormat(parseFlagValue("--format")) ?? config.output?.format ?? "json";
|
|
85
65
|
const detail = parseDetailLevel(parseFlagValue("--detail")) ?? config.output?.detail ?? "brief";
|
|
86
|
-
|
|
66
|
+
const forAgent = hasBooleanFlag("--for-agent");
|
|
67
|
+
return { format, detail, forAgent };
|
|
87
68
|
}
|
|
88
69
|
function output(command, result) {
|
|
89
70
|
const mode = resolveOutputMode();
|
|
90
|
-
const shaped = shapeForCommand(command, result, mode.detail);
|
|
71
|
+
const shaped = shapeForCommand(command, result, mode.detail, mode.forAgent);
|
|
72
|
+
if (mode.format === "jsonl") {
|
|
73
|
+
outputJsonl(command, shaped);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
91
76
|
switch (mode.format) {
|
|
92
77
|
case "json":
|
|
93
78
|
console.log(JSON.stringify(shaped, null, 2));
|
|
@@ -102,27 +87,57 @@ function output(command, result) {
|
|
|
102
87
|
}
|
|
103
88
|
}
|
|
104
89
|
}
|
|
105
|
-
function
|
|
90
|
+
function outputJsonl(command, shaped) {
|
|
91
|
+
if (command === "search" || command === "registry-search") {
|
|
92
|
+
const r = shaped;
|
|
93
|
+
const hits = Array.isArray(r.hits) ? r.hits : [];
|
|
94
|
+
for (const hit of hits) {
|
|
95
|
+
console.log(JSON.stringify(hit));
|
|
96
|
+
}
|
|
97
|
+
const registryHits = Array.isArray(r.registryHits) ? r.registryHits : [];
|
|
98
|
+
for (const hit of registryHits) {
|
|
99
|
+
console.log(JSON.stringify(hit));
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
// For non-search commands, output the whole object as a single JSONL line
|
|
104
|
+
console.log(JSON.stringify(shaped));
|
|
105
|
+
}
|
|
106
|
+
function shapeForCommand(command, result, detail, forAgent = false) {
|
|
106
107
|
switch (command) {
|
|
107
108
|
case "search":
|
|
108
|
-
return shapeSearchOutput(result, detail);
|
|
109
|
+
return shapeSearchOutput(result, detail, forAgent);
|
|
109
110
|
case "registry-search":
|
|
110
111
|
return shapeRegistrySearchOutput(result, detail);
|
|
111
112
|
case "show":
|
|
112
|
-
return shapeShowOutput(result, detail);
|
|
113
|
+
return shapeShowOutput(result, detail, forAgent);
|
|
113
114
|
default:
|
|
114
115
|
return result;
|
|
115
116
|
}
|
|
116
117
|
}
|
|
117
|
-
function shapeSearchOutput(result, detail) {
|
|
118
|
+
function shapeSearchOutput(result, detail, forAgent = false) {
|
|
118
119
|
const hits = Array.isArray(result.hits) ? result.hits : [];
|
|
119
|
-
const
|
|
120
|
+
const registryHits = Array.isArray(result.registryHits) ? result.registryHits : [];
|
|
121
|
+
const shapedHits = forAgent
|
|
122
|
+
? hits.map((hit) => shapeSearchHitForAgent(hit))
|
|
123
|
+
: hits.map((hit) => shapeSearchHit(hit, detail));
|
|
124
|
+
const shapedRegistryHits = forAgent
|
|
125
|
+
? registryHits.map((hit) => shapeSearchHitForAgent(hit))
|
|
126
|
+
: registryHits.map((hit) => shapeSearchHit(hit, detail));
|
|
127
|
+
if (forAgent) {
|
|
128
|
+
return {
|
|
129
|
+
hits: shapedHits,
|
|
130
|
+
...(shapedRegistryHits.length > 0 ? { registryHits: shapedRegistryHits } : {}),
|
|
131
|
+
...(result.tip ? { tip: result.tip } : {}),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
120
134
|
if (detail === "full") {
|
|
121
135
|
return {
|
|
122
136
|
schemaVersion: result.schemaVersion,
|
|
123
137
|
stashDir: result.stashDir,
|
|
124
138
|
source: result.source,
|
|
125
139
|
hits: shapedHits,
|
|
140
|
+
...(shapedRegistryHits.length > 0 ? { registryHits: shapedRegistryHits } : {}),
|
|
126
141
|
...(result.tip ? { tip: result.tip } : {}),
|
|
127
142
|
...(result.warnings ? { warnings: result.warnings } : {}),
|
|
128
143
|
...(result.timing ? { timing: result.timing } : {}),
|
|
@@ -130,6 +145,7 @@ function shapeSearchOutput(result, detail) {
|
|
|
130
145
|
}
|
|
131
146
|
return {
|
|
132
147
|
hits: shapedHits,
|
|
148
|
+
...(shapedRegistryHits.length > 0 ? { registryHits: shapedRegistryHits } : {}),
|
|
133
149
|
...(result.tip ? { tip: result.tip } : {}),
|
|
134
150
|
...(Array.isArray(result.warnings) && result.warnings.length > 0 ? { warnings: result.warnings } : {}),
|
|
135
151
|
};
|
|
@@ -153,9 +169,9 @@ function shapeRegistrySearchOutput(result, detail) {
|
|
|
153
169
|
}
|
|
154
170
|
function shapeAssetHit(hit, detail) {
|
|
155
171
|
if (detail === "brief")
|
|
156
|
-
return pickFields(hit, ["assetName", "assetType", "action"]);
|
|
172
|
+
return pickFields(hit, ["assetName", "assetType", "action", "estimatedTokens"]);
|
|
157
173
|
if (detail === "normal") {
|
|
158
|
-
return capDescription(pickFields(hit, ["assetName", "assetType", "description", "kit", "action"]), NORMAL_DESCRIPTION_LIMIT);
|
|
174
|
+
return capDescription(pickFields(hit, ["assetName", "assetType", "description", "kit", "action", "estimatedTokens"]), NORMAL_DESCRIPTION_LIMIT);
|
|
159
175
|
}
|
|
160
176
|
return hit;
|
|
161
177
|
}
|
|
@@ -170,12 +186,17 @@ function shapeSearchHit(hit, detail) {
|
|
|
170
186
|
}
|
|
171
187
|
// Stash hit (local or remote)
|
|
172
188
|
if (detail === "brief")
|
|
173
|
-
return pickFields(hit, ["type", "name", "action"]);
|
|
189
|
+
return pickFields(hit, ["type", "name", "action", "estimatedTokens"]);
|
|
174
190
|
if (detail === "normal") {
|
|
175
|
-
return capDescription(pickFields(hit, ["type", "name", "description", "action", "score"]), NORMAL_DESCRIPTION_LIMIT);
|
|
191
|
+
return capDescription(pickFields(hit, ["type", "name", "description", "action", "score", "estimatedTokens"]), NORMAL_DESCRIPTION_LIMIT);
|
|
176
192
|
}
|
|
177
193
|
return hit;
|
|
178
194
|
}
|
|
195
|
+
/** Agent-optimized search hit: only fields an LLM agent needs to decide and act */
|
|
196
|
+
function shapeSearchHitForAgent(hit) {
|
|
197
|
+
const picked = pickFields(hit, ["name", "ref", "type", "description", "action", "score", "estimatedTokens"]);
|
|
198
|
+
return capDescription(picked, NORMAL_DESCRIPTION_LIMIT);
|
|
199
|
+
}
|
|
179
200
|
function capDescription(hit, limit) {
|
|
180
201
|
if (typeof hit.description !== "string")
|
|
181
202
|
return hit;
|
|
@@ -190,13 +211,35 @@ function truncateDescription(description, limit) {
|
|
|
190
211
|
const safe = lastSpace >= Math.floor(limit * 0.6) ? truncated.slice(0, lastSpace) : truncated;
|
|
191
212
|
return `${safe.trimEnd()}...`;
|
|
192
213
|
}
|
|
193
|
-
function shapeShowOutput(result, detail) {
|
|
214
|
+
function shapeShowOutput(result, detail, forAgent = false) {
|
|
215
|
+
if (forAgent) {
|
|
216
|
+
return pickFields(result, [
|
|
217
|
+
"type",
|
|
218
|
+
"name",
|
|
219
|
+
"description",
|
|
220
|
+
"action",
|
|
221
|
+
"content",
|
|
222
|
+
"template",
|
|
223
|
+
"prompt",
|
|
224
|
+
"run",
|
|
225
|
+
"setup",
|
|
226
|
+
"cwd",
|
|
227
|
+
"toolPolicy",
|
|
228
|
+
"modelHint",
|
|
229
|
+
"agent",
|
|
230
|
+
"parameters",
|
|
231
|
+
]);
|
|
232
|
+
}
|
|
233
|
+
if (detail === "summary") {
|
|
234
|
+
return pickFields(result, ["type", "name", "description", "tags", "parameters", "action", "run", "origin"]);
|
|
235
|
+
}
|
|
194
236
|
const base = pickFields(result, [
|
|
195
237
|
"type",
|
|
196
238
|
"name",
|
|
197
239
|
"origin",
|
|
198
240
|
"action",
|
|
199
241
|
"description",
|
|
242
|
+
"tags",
|
|
200
243
|
"content",
|
|
201
244
|
"template",
|
|
202
245
|
"prompt",
|
|
@@ -342,11 +385,13 @@ function formatPlain(command, result, detail) {
|
|
|
342
385
|
}
|
|
343
386
|
function formatSearchPlain(r, detail) {
|
|
344
387
|
const hits = r.hits ?? [];
|
|
345
|
-
|
|
388
|
+
const registryHits = r.registryHits ?? [];
|
|
389
|
+
const allHits = [...hits, ...registryHits];
|
|
390
|
+
if (allHits.length === 0) {
|
|
346
391
|
return r.tip ? String(r.tip) : "No results found.";
|
|
347
392
|
}
|
|
348
393
|
const lines = [];
|
|
349
|
-
for (const hit of
|
|
394
|
+
for (const hit of allHits) {
|
|
350
395
|
const type = hit.type ?? "unknown";
|
|
351
396
|
const name = hit.name ?? "unnamed";
|
|
352
397
|
const score = hit.score != null ? ` (score: ${hit.score})` : "";
|
|
@@ -405,6 +450,18 @@ function formatSearchPlain(r, detail) {
|
|
|
405
450
|
* - registry-* : Kit discovery from remote registries (npm, GitHub)
|
|
406
451
|
* - installed-kits : Management of kits already installed locally
|
|
407
452
|
*/
|
|
453
|
+
const setupCommand = defineCommand({
|
|
454
|
+
meta: {
|
|
455
|
+
name: "setup",
|
|
456
|
+
description: "Interactive configuration wizard for embeddings, LLM, registries, and stash sources",
|
|
457
|
+
},
|
|
458
|
+
async run() {
|
|
459
|
+
await runWithJsonErrors(async () => {
|
|
460
|
+
const { runSetupWizard } = await import("./setup");
|
|
461
|
+
await runSetupWizard();
|
|
462
|
+
});
|
|
463
|
+
},
|
|
464
|
+
});
|
|
408
465
|
const initCommand = defineCommand({
|
|
409
466
|
meta: {
|
|
410
467
|
name: "init",
|
|
@@ -432,6 +489,15 @@ const indexCommand = defineCommand({
|
|
|
432
489
|
});
|
|
433
490
|
},
|
|
434
491
|
});
|
|
492
|
+
const infoCommand = defineCommand({
|
|
493
|
+
meta: { name: "info", description: "Show system capabilities, configuration, and index stats as JSON" },
|
|
494
|
+
run() {
|
|
495
|
+
return runWithJsonErrors(() => {
|
|
496
|
+
const result = assembleInfo();
|
|
497
|
+
output("info", result);
|
|
498
|
+
});
|
|
499
|
+
},
|
|
500
|
+
});
|
|
435
501
|
const searchCommand = defineCommand({
|
|
436
502
|
meta: { name: "search", description: "Search the stash" },
|
|
437
503
|
args: {
|
|
@@ -442,8 +508,8 @@ const searchCommand = defineCommand({
|
|
|
442
508
|
},
|
|
443
509
|
limit: { type: "string", description: "Maximum number of results" },
|
|
444
510
|
source: { type: "string", description: "Search source (stash|registry|both)", default: "stash" },
|
|
445
|
-
format: { type: "string", description: "Output format (json|text|yaml)" },
|
|
446
|
-
detail: { type: "string", description: "Detail level (brief|normal|full)" },
|
|
511
|
+
format: { type: "string", description: "Output format (json|jsonl|text|yaml)" },
|
|
512
|
+
detail: { type: "string", description: "Detail level (brief|normal|full|summary)" },
|
|
447
513
|
},
|
|
448
514
|
async run({ args }) {
|
|
449
515
|
await runWithJsonErrors(async () => {
|
|
@@ -574,8 +640,8 @@ const showCommand = defineCommand({
|
|
|
574
640
|
},
|
|
575
641
|
args: {
|
|
576
642
|
ref: { type: "positional", description: "Asset ref (type:name)", required: true },
|
|
577
|
-
format: { type: "string", description: "Output format (json|text|yaml)" },
|
|
578
|
-
detail: { type: "string", description: "Detail level (brief|normal|full)" },
|
|
643
|
+
format: { type: "string", description: "Output format (json|jsonl|text|yaml)" },
|
|
644
|
+
detail: { type: "string", description: "Detail level (brief|normal|full|summary)" },
|
|
579
645
|
akmView: { type: "string", description: "Internal positional knowledge view mode parser" },
|
|
580
646
|
akmHeading: { type: "string", description: "Internal positional section heading parser" },
|
|
581
647
|
akmStart: { type: "string", description: "Internal positional start-line parser" },
|
|
@@ -605,7 +671,10 @@ const showCommand = defineCommand({
|
|
|
605
671
|
throw new UsageError(`Unknown view mode: ${args.akmView}. Expected one of: full|toc|frontmatter|section|lines`);
|
|
606
672
|
}
|
|
607
673
|
}
|
|
608
|
-
|
|
674
|
+
// Map CLI detail level to ShowDetailLevel for the show function
|
|
675
|
+
const cliDetail = resolveOutputMode().detail;
|
|
676
|
+
const showDetail = cliDetail === "summary" ? "summary" : undefined;
|
|
677
|
+
const result = await akmShowUnified({ ref: args.ref, view, detail: showDetail });
|
|
609
678
|
output("show", result);
|
|
610
679
|
});
|
|
611
680
|
},
|
|
@@ -920,6 +989,47 @@ const stashCommand = defineCommand({
|
|
|
920
989
|
meta: { name: "stash", description: "Manage additional stashes (local directories and remote providers)" },
|
|
921
990
|
subCommands: buildSourceSubCommands("stash"),
|
|
922
991
|
});
|
|
992
|
+
const feedbackCommand = defineCommand({
|
|
993
|
+
meta: {
|
|
994
|
+
name: "feedback",
|
|
995
|
+
description: "Record positive or negative feedback for a stash asset",
|
|
996
|
+
},
|
|
997
|
+
args: {
|
|
998
|
+
ref: { type: "positional", description: "Asset ref (type:name)", required: true },
|
|
999
|
+
positive: { type: "boolean", description: "Record positive feedback", default: false },
|
|
1000
|
+
negative: { type: "boolean", description: "Record negative feedback", default: false },
|
|
1001
|
+
note: { type: "string", description: "Optional note to attach to the feedback" },
|
|
1002
|
+
},
|
|
1003
|
+
run({ args }) {
|
|
1004
|
+
return runWithJsonErrors(() => {
|
|
1005
|
+
const ref = args.ref.trim();
|
|
1006
|
+
if (!ref) {
|
|
1007
|
+
throw new UsageError("Asset ref is required. Usage: akm feedback <ref> --positive|--negative");
|
|
1008
|
+
}
|
|
1009
|
+
if (args.positive && args.negative) {
|
|
1010
|
+
throw new UsageError("Specify either --positive or --negative, not both.");
|
|
1011
|
+
}
|
|
1012
|
+
if (!args.positive && !args.negative) {
|
|
1013
|
+
throw new UsageError("Specify --positive or --negative.");
|
|
1014
|
+
}
|
|
1015
|
+
const signal = args.positive ? "positive" : "negative";
|
|
1016
|
+
const metadata = args.note ? JSON.stringify({ note: args.note }) : undefined;
|
|
1017
|
+
const db = openDatabase();
|
|
1018
|
+
try {
|
|
1019
|
+
insertUsageEvent(db, {
|
|
1020
|
+
event_type: "feedback",
|
|
1021
|
+
entry_ref: ref,
|
|
1022
|
+
signal,
|
|
1023
|
+
metadata,
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
finally {
|
|
1027
|
+
closeDatabase(db);
|
|
1028
|
+
}
|
|
1029
|
+
output("feedback", { ok: true, ref, signal, note: args.note ?? null });
|
|
1030
|
+
});
|
|
1031
|
+
},
|
|
1032
|
+
});
|
|
923
1033
|
const hintsCommand = defineCommand({
|
|
924
1034
|
meta: {
|
|
925
1035
|
name: "hints",
|
|
@@ -977,8 +1087,10 @@ const main = defineCommand({
|
|
|
977
1087
|
quiet: { type: "boolean", alias: "q", description: "Suppress stderr warnings", default: false },
|
|
978
1088
|
},
|
|
979
1089
|
subCommands: {
|
|
1090
|
+
setup: setupCommand,
|
|
980
1091
|
init: initCommand,
|
|
981
1092
|
index: indexCommand,
|
|
1093
|
+
info: infoCommand,
|
|
982
1094
|
add: addCommand,
|
|
983
1095
|
list: listCommand,
|
|
984
1096
|
remove: removeCommand,
|
|
@@ -991,6 +1103,7 @@ const main = defineCommand({
|
|
|
991
1103
|
stash: stashCommand,
|
|
992
1104
|
registry: registryCommand,
|
|
993
1105
|
config: configCommand,
|
|
1106
|
+
feedback: feedbackCommand,
|
|
994
1107
|
hints: hintsCommand,
|
|
995
1108
|
completions: completionsCommand,
|
|
996
1109
|
},
|
|
@@ -1044,9 +1157,9 @@ function buildHint(message) {
|
|
|
1044
1157
|
if (message.includes("Invalid value for --source"))
|
|
1045
1158
|
return "Pick one of: stash, registry, both.";
|
|
1046
1159
|
if (message.includes("Invalid value for --format"))
|
|
1047
|
-
return "Pick one of: json, text, yaml.";
|
|
1160
|
+
return "Pick one of: json, jsonl, text, yaml.";
|
|
1048
1161
|
if (message.includes("Invalid value for --detail"))
|
|
1049
|
-
return "Pick one of: brief, normal, full.";
|
|
1162
|
+
return "Pick one of: brief, normal, full, summary.";
|
|
1050
1163
|
if (message.includes("expected JSON object with endpoint and model")) {
|
|
1051
1164
|
return 'Quote JSON values in your shell, for example: akm config set embedding \'{"endpoint":"http://localhost:11434/v1/embeddings","model":"nomic-embed-text"}\'.';
|
|
1052
1165
|
}
|
|
@@ -1082,7 +1195,7 @@ function normalizeShowArgv(argv) {
|
|
|
1082
1195
|
const showArgs = [];
|
|
1083
1196
|
for (let i = 0; i < rest.length; i++) {
|
|
1084
1197
|
const arg = rest[i];
|
|
1085
|
-
if (arg === "--quiet" || arg === "-q") {
|
|
1198
|
+
if (arg === "--quiet" || arg === "-q" || arg === "--for-agent" || arg === "--for-agent=true") {
|
|
1086
1199
|
globalFlags.push(arg);
|
|
1087
1200
|
continue;
|
|
1088
1201
|
}
|
|
@@ -1191,8 +1304,9 @@ akm search "<query>" --detail full # Include scores, paths, timing
|
|
|
1191
1304
|
| \`--type\` | \`skill\`, \`command\`, \`agent\`, \`knowledge\`, \`script\`, \`memory\`, \`any\` | \`any\` |
|
|
1192
1305
|
| \`--source\` | \`stash\`, \`registry\`, \`both\` | \`stash\` |
|
|
1193
1306
|
| \`--limit\` | number | \`20\` |
|
|
1194
|
-
| \`--format\` | \`json\`, \`text\`, \`yaml\` | \`json\` |
|
|
1195
|
-
| \`--detail\` | \`brief\`, \`normal\`, \`full\` | \`brief\` |
|
|
1307
|
+
| \`--format\` | \`json\`, \`jsonl\`, \`text\`, \`yaml\` | \`json\` |
|
|
1308
|
+
| \`--detail\` | \`brief\`, \`normal\`, \`full\`, \`summary\` | \`brief\` |
|
|
1309
|
+
| \`--for-agent\` | boolean | \`false\` |
|
|
1196
1310
|
|
|
1197
1311
|
## Show
|
|
1198
1312
|
|
|
@@ -1206,7 +1320,7 @@ akm show agent:architect # Show agent (returns system promp
|
|
|
1206
1320
|
akm show knowledge:guide toc # Table of contents
|
|
1207
1321
|
akm show knowledge:guide section "Auth" # Specific section
|
|
1208
1322
|
akm show knowledge:guide lines 10 30 # Line range
|
|
1209
|
-
akm show
|
|
1323
|
+
akm show knowledge:my-doc # Show content (local or remote)
|
|
1210
1324
|
\`\`\`
|
|
1211
1325
|
|
|
1212
1326
|
| Type | Key fields returned |
|
|
@@ -1294,11 +1408,14 @@ akm completions --install # Install completions
|
|
|
1294
1408
|
All commands accept \`--format\` and \`--detail\` flags:
|
|
1295
1409
|
|
|
1296
1410
|
- \`--format json\` (default) — structured JSON
|
|
1411
|
+
- \`--format jsonl\` — one JSON object per line (streaming-friendly)
|
|
1297
1412
|
- \`--format text\` — human-readable plain text
|
|
1298
1413
|
- \`--format yaml\` — YAML output
|
|
1299
1414
|
- \`--detail brief\` (default) — compact output
|
|
1300
1415
|
- \`--detail normal\` — adds tags, refs, origins
|
|
1301
1416
|
- \`--detail full\` — includes scores, paths, timing, debug info
|
|
1417
|
+
- \`--detail summary\` — metadata only (no content/template/prompt), under 200 tokens
|
|
1418
|
+
- \`--for-agent\` — agent-optimized output: strips non-actionable fields (takes precedence over \`--detail\`)
|
|
1302
1419
|
|
|
1303
1420
|
Run \`akm -h\` or \`akm <command> -h\` for per-command help.
|
|
1304
1421
|
`;
|
package/dist/completions.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { getAssetTypes } from "./asset-spec";
|
|
4
5
|
// ── Known flag values ────────────────────────────────────────────────────────
|
|
5
6
|
const FLAG_VALUES = {
|
|
6
7
|
"--format": ["json", "text", "yaml"],
|
|
7
8
|
"--detail": ["brief", "normal", "full"],
|
|
8
|
-
"--type":
|
|
9
|
+
"--type": () => [...getAssetTypes(), "any"],
|
|
9
10
|
"--source": ["stash", "registry", "both"],
|
|
10
11
|
"--shell": ["bash"],
|
|
11
12
|
};
|
|
@@ -57,7 +58,8 @@ export function generateBashCompletions(cmd) {
|
|
|
57
58
|
}
|
|
58
59
|
// Build flag-value completion cases
|
|
59
60
|
const valueCases = [];
|
|
60
|
-
for (const [flag,
|
|
61
|
+
for (const [flag, valuesOrFn] of Object.entries(FLAG_VALUES)) {
|
|
62
|
+
const values = typeof valuesOrFn === "function" ? valuesOrFn() : valuesOrFn;
|
|
61
63
|
valueCases.push(` ${flag})
|
|
62
64
|
COMPREPLY=( $(compgen -W "${values.join(" ")}" -- "\${cur}") )
|
|
63
65
|
return 0
|
package/dist/config.js
CHANGED
|
@@ -22,6 +22,9 @@ export function getConfigPath() {
|
|
|
22
22
|
}
|
|
23
23
|
// ── Load / Save / Update ────────────────────────────────────────────────────
|
|
24
24
|
let cachedConfig;
|
|
25
|
+
export function resetConfigCache() {
|
|
26
|
+
cachedConfig = undefined;
|
|
27
|
+
}
|
|
25
28
|
export function loadConfig() {
|
|
26
29
|
const configPath = getConfigPath();
|
|
27
30
|
let stat;
|
|
@@ -184,10 +187,11 @@ const URL_FIELD_NAMES = new Set(["url", "endpoint", "artifactUrl"]);
|
|
|
184
187
|
*/
|
|
185
188
|
function expandEnvVars(value, fieldName) {
|
|
186
189
|
if (typeof value === "string") {
|
|
187
|
-
// Skip URL-type fields by name or by value prefix
|
|
188
|
-
if (
|
|
189
|
-
|
|
190
|
-
|
|
190
|
+
// Skip URL-type fields by name or by value prefix, unless they contain ${VAR} syntax
|
|
191
|
+
if (!value.includes("${") &&
|
|
192
|
+
((fieldName !== undefined && URL_FIELD_NAMES.has(fieldName)) ||
|
|
193
|
+
value.startsWith("http://") ||
|
|
194
|
+
value.startsWith("https://"))) {
|
|
191
195
|
return value;
|
|
192
196
|
}
|
|
193
197
|
return value.replace(/\$\{([^}]+)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g, (_match, braced, bare) => {
|
|
@@ -274,14 +278,35 @@ function parseEmbeddingConfig(value) {
|
|
|
274
278
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
275
279
|
return undefined;
|
|
276
280
|
const obj = value;
|
|
277
|
-
|
|
281
|
+
// Extract localModel early — it's valid even without a remote endpoint
|
|
282
|
+
const localModel = typeof obj.localModel === "string" && obj.localModel ? obj.localModel : undefined;
|
|
283
|
+
// If no endpoint is provided, the config is only valid when localModel is set
|
|
284
|
+
// (local-only embedding configuration).
|
|
285
|
+
// Sentinel: { endpoint: "", model: "" } means "local-only" — use hasRemoteEndpoint()
|
|
286
|
+
// (in embedder.ts) to distinguish from a real remote config. Do NOT check
|
|
287
|
+
// endpoint/model directly in consuming code.
|
|
288
|
+
if (typeof obj.endpoint !== "string" || !obj.endpoint) {
|
|
289
|
+
if (localModel) {
|
|
290
|
+
return { endpoint: "", model: "", localModel };
|
|
291
|
+
}
|
|
278
292
|
return undefined;
|
|
293
|
+
}
|
|
279
294
|
if (!obj.endpoint.startsWith("http://") && !obj.endpoint.startsWith("https://")) {
|
|
280
295
|
console.warn(`[akm] Ignoring embedding config: endpoint must start with http:// or https://, got "${obj.endpoint}"`);
|
|
296
|
+
// Still return localModel-only config if localModel was set
|
|
297
|
+
if (localModel) {
|
|
298
|
+
return { endpoint: "", model: "", localModel };
|
|
299
|
+
}
|
|
281
300
|
return undefined;
|
|
282
301
|
}
|
|
283
|
-
if (typeof obj.model !== "string" || !obj.model)
|
|
302
|
+
if (typeof obj.model !== "string" || !obj.model) {
|
|
303
|
+
// No remote model, but localModel may still be valid
|
|
304
|
+
if (localModel) {
|
|
305
|
+
console.warn(`[akm] Embedding endpoint "${obj.endpoint}" ignored: model is required for remote embeddings. Using local model only.`);
|
|
306
|
+
return { endpoint: "", model: "", localModel };
|
|
307
|
+
}
|
|
284
308
|
return undefined;
|
|
309
|
+
}
|
|
285
310
|
const result = {
|
|
286
311
|
endpoint: obj.endpoint,
|
|
287
312
|
model: obj.model,
|
|
@@ -301,6 +326,9 @@ function parseEmbeddingConfig(value) {
|
|
|
301
326
|
if (typeof obj.apiKey === "string" && obj.apiKey) {
|
|
302
327
|
result.apiKey = obj.apiKey;
|
|
303
328
|
}
|
|
329
|
+
if (localModel) {
|
|
330
|
+
result.localModel = localModel;
|
|
331
|
+
}
|
|
304
332
|
return result;
|
|
305
333
|
}
|
|
306
334
|
function parseLlmConfig(value) {
|