akm-cli 0.4.1 → 0.5.0-rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/asset-registry.js +7 -0
- package/dist/asset-spec.js +35 -0
- package/dist/cli.js +1120 -31
- package/dist/completions.js +2 -2
- package/dist/config-cli.js +41 -0
- package/dist/config.js +62 -0
- package/dist/file-context.js +2 -1
- package/dist/github.js +20 -1
- package/dist/indexer.js +55 -5
- package/dist/init.js +11 -0
- package/dist/install-audit.js +53 -8
- package/dist/installed-kits.js +2 -0
- package/dist/llm.js +64 -23
- package/dist/matchers.js +56 -4
- package/dist/metadata.js +68 -4
- package/dist/paths.js +3 -0
- package/dist/registry-install.js +36 -7
- package/dist/registry-resolve.js +25 -0
- package/dist/renderers.js +182 -2
- package/dist/search-fields.js +4 -0
- package/dist/search-source.js +12 -8
- package/dist/setup.js +158 -33
- package/dist/stash-add.js +84 -11
- package/dist/stash-providers/git.js +182 -44
- package/dist/stash-show.js +56 -1
- package/dist/stash-source-manage.js +14 -4
- package/dist/templates/wiki-templates.js +100 -0
- package/dist/vault.js +290 -0
- package/dist/wiki.js +886 -0
- package/dist/workflow-authoring.js +131 -0
- package/dist/workflow-cli.js +44 -0
- package/dist/workflow-db.js +55 -0
- package/dist/workflow-markdown.js +251 -0
- package/dist/workflow-runs.js +364 -0
- package/package.json +2 -1
- package/LICENSE +0 -374
package/dist/matchers.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Built-in asset matchers for the akm file classification system.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Five matchers are registered at module load time, each at a different
|
|
5
5
|
* specificity level. Extension and content determine type; directories are
|
|
6
6
|
* optional specificity boosts, not requirements.
|
|
7
7
|
*
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
* and body content for agent/command signals; falls back to "knowledge"
|
|
16
16
|
* at specificity 5 when no signals are found. Command signals (`agent`
|
|
17
17
|
* frontmatter, `$ARGUMENTS`/`$1`-`$3` placeholders) return 18.
|
|
18
|
+
* - `wikiMatcher` (20) -- classifies any `.md` under `wikis/<name>/…` as
|
|
19
|
+
* `wiki`. Registered last so the later-wins tiebreaker beats agent at 20.
|
|
18
20
|
*/
|
|
19
21
|
import { SCRIPT_EXTENSIONS } from "./asset-spec";
|
|
20
22
|
import { registerMatcher } from "./file-context";
|
|
@@ -32,7 +34,9 @@ import { registerMatcher } from "./file-context";
|
|
|
32
34
|
export function extensionMatcher(ctx) {
|
|
33
35
|
// SKILL.md is a skill regardless of location — high specificity beats
|
|
34
36
|
// smartMdMatcher's knowledge fallback and all directory-based matchers.
|
|
35
|
-
|
|
37
|
+
// Exception: files under wikis/<name>/… are always wiki pages; the wiki
|
|
38
|
+
// directory is an authoritative signal that outranks the filename.
|
|
39
|
+
if (ctx.fileName === "SKILL.md" && !ctx.ancestorDirs.includes("wikis")) {
|
|
36
40
|
return { type: "skill", specificity: 25, renderer: "skill-md" };
|
|
37
41
|
}
|
|
38
42
|
// Known script extensions (excluding .md, handled by smartMdMatcher)
|
|
@@ -68,9 +72,15 @@ export function directoryMatcher(ctx) {
|
|
|
68
72
|
if (dir === "knowledge" && ext === ".md") {
|
|
69
73
|
return { type: "knowledge", specificity: 10, renderer: "knowledge-md" };
|
|
70
74
|
}
|
|
75
|
+
if (dir === "workflows" && ext === ".md") {
|
|
76
|
+
return { type: "workflow", specificity: 10, renderer: "workflow-md" };
|
|
77
|
+
}
|
|
71
78
|
if (dir === "memories" && ext === ".md") {
|
|
72
79
|
return { type: "memory", specificity: 10, renderer: "memory-md" };
|
|
73
80
|
}
|
|
81
|
+
if (dir === "vaults" && (ctx.fileName === ".env" || ctx.fileName.endsWith(".env"))) {
|
|
82
|
+
return { type: "vault", specificity: 10, renderer: "vault-env" };
|
|
83
|
+
}
|
|
74
84
|
}
|
|
75
85
|
return null;
|
|
76
86
|
}
|
|
@@ -98,9 +108,15 @@ export function parentDirHintMatcher(ctx) {
|
|
|
98
108
|
if (parentDir === "knowledge" && ext === ".md") {
|
|
99
109
|
return { type: "knowledge", specificity: 15, renderer: "knowledge-md" };
|
|
100
110
|
}
|
|
111
|
+
if (parentDir === "workflows" && ext === ".md") {
|
|
112
|
+
return { type: "workflow", specificity: 15, renderer: "workflow-md" };
|
|
113
|
+
}
|
|
101
114
|
if (parentDir === "memories" && ext === ".md") {
|
|
102
115
|
return { type: "memory", specificity: 15, renderer: "memory-md" };
|
|
103
116
|
}
|
|
117
|
+
if (parentDir === "vaults" && (fileName === ".env" || fileName.endsWith(".env"))) {
|
|
118
|
+
return { type: "vault", specificity: 15, renderer: "vault-env" };
|
|
119
|
+
}
|
|
104
120
|
return null;
|
|
105
121
|
}
|
|
106
122
|
// ── smartMdMatcher (specificity: 20 / 18 / 8 / 5) ──────────────────────────
|
|
@@ -123,6 +139,14 @@ const COMMAND_PLACEHOLDER_RE = /\$ARGUMENTS|\$[123]\b/;
|
|
|
123
139
|
export function smartMdMatcher(ctx) {
|
|
124
140
|
if (ctx.ext !== ".md")
|
|
125
141
|
return null;
|
|
142
|
+
const body = ctx.content();
|
|
143
|
+
const hasWorkflowSignals = /^#\s+Workflow:\s+/m.test(body) &&
|
|
144
|
+
/^##\s+Step:\s+/m.test(body) &&
|
|
145
|
+
/^Step ID:\s+/m.test(body) &&
|
|
146
|
+
/^###\s+Instructions\s*$/m.test(body);
|
|
147
|
+
if (hasWorkflowSignals) {
|
|
148
|
+
return { type: "workflow", specificity: 19, renderer: "workflow-md" };
|
|
149
|
+
}
|
|
126
150
|
const fm = ctx.frontmatter();
|
|
127
151
|
if (fm) {
|
|
128
152
|
// Agent-exclusive indicators: toolPolicy or tools
|
|
@@ -138,7 +162,6 @@ export function smartMdMatcher(ctx) {
|
|
|
138
162
|
}
|
|
139
163
|
// Command signal: body contains $ARGUMENTS or $1/$2/$3 placeholders.
|
|
140
164
|
// These are definitively command template patterns (OpenCode convention).
|
|
141
|
-
const body = ctx.content();
|
|
142
165
|
if (COMMAND_PLACEHOLDER_RE.test(body)) {
|
|
143
166
|
return { type: "command", specificity: 18, renderer: "command-md" };
|
|
144
167
|
}
|
|
@@ -154,9 +177,38 @@ export function smartMdMatcher(ctx) {
|
|
|
154
177
|
// Weak fallback: any .md file is assumed to be knowledge
|
|
155
178
|
return { type: "knowledge", specificity: 5, renderer: "knowledge-md" };
|
|
156
179
|
}
|
|
180
|
+
// ── wikiMatcher (specificity: 20) ──────────────────────────────────────────
|
|
181
|
+
/**
|
|
182
|
+
* Classify any `.md` file that lives under `wikis/<name>/…` as `wiki`.
|
|
183
|
+
*
|
|
184
|
+
* Registered AFTER `smartMdMatcher` so the registered-later-wins tiebreaker
|
|
185
|
+
* puts wiki ahead of agent at specificity 20. That means a wiki page with
|
|
186
|
+
* agent-style frontmatter (e.g. `tools:`) still classifies as a wiki page,
|
|
187
|
+
* not an agent. That's intentional — the directory is the authoritative
|
|
188
|
+
* signal: files under `wikis/` are wiki content.
|
|
189
|
+
*
|
|
190
|
+
* Requires at least one path segment after `wikis/` (the wiki name) — a
|
|
191
|
+
* stray `.md` at the bare `wikis/` root is not a wiki page.
|
|
192
|
+
*/
|
|
193
|
+
export function wikiMatcher(ctx) {
|
|
194
|
+
if (ctx.ext !== ".md")
|
|
195
|
+
return null;
|
|
196
|
+
const idx = ctx.ancestorDirs.indexOf("wikis");
|
|
197
|
+
if (idx < 0)
|
|
198
|
+
return null;
|
|
199
|
+
if (idx + 1 >= ctx.ancestorDirs.length)
|
|
200
|
+
return null;
|
|
201
|
+
return { type: "wiki", specificity: 20, renderer: "wiki-md" };
|
|
202
|
+
}
|
|
157
203
|
// ── Registration ────────────────────────────────────────────────────────────
|
|
158
204
|
/** All built-in matchers in registration order (later wins ties). */
|
|
159
|
-
const builtinMatchers = [
|
|
205
|
+
const builtinMatchers = [
|
|
206
|
+
extensionMatcher,
|
|
207
|
+
directoryMatcher,
|
|
208
|
+
parentDirHintMatcher,
|
|
209
|
+
smartMdMatcher,
|
|
210
|
+
wikiMatcher,
|
|
211
|
+
];
|
|
160
212
|
/**
|
|
161
213
|
* Register all built-in matchers with the file-context registry.
|
|
162
214
|
* Called once from the CLI entry point (or ensureBuiltinsRegistered).
|
package/dist/metadata.js
CHANGED
|
@@ -133,6 +133,30 @@ export function validateStashEntry(entry) {
|
|
|
133
133
|
result.cwd = e.cwd.trim();
|
|
134
134
|
if (typeof e.fileSize === "number" && Number.isFinite(e.fileSize) && e.fileSize >= 0)
|
|
135
135
|
result.fileSize = e.fileSize;
|
|
136
|
+
if (e.wikiRole === "schema" ||
|
|
137
|
+
e.wikiRole === "index" ||
|
|
138
|
+
e.wikiRole === "log" ||
|
|
139
|
+
e.wikiRole === "raw" ||
|
|
140
|
+
e.wikiRole === "page") {
|
|
141
|
+
result.wikiRole = e.wikiRole;
|
|
142
|
+
}
|
|
143
|
+
if (typeof e.pageKind === "string" && e.pageKind.trim().length > 0) {
|
|
144
|
+
result.pageKind = e.pageKind.trim();
|
|
145
|
+
}
|
|
146
|
+
if (Array.isArray(e.xrefs)) {
|
|
147
|
+
const filtered = e.xrefs
|
|
148
|
+
.filter((x) => typeof x === "string" && x.trim().length > 0)
|
|
149
|
+
.map((x) => x.trim());
|
|
150
|
+
if (filtered.length > 0)
|
|
151
|
+
result.xrefs = filtered;
|
|
152
|
+
}
|
|
153
|
+
if (Array.isArray(e.sources)) {
|
|
154
|
+
const filtered = e.sources
|
|
155
|
+
.filter((s) => typeof s === "string" && s.trim().length > 0)
|
|
156
|
+
.map((s) => s.trim());
|
|
157
|
+
if (filtered.length > 0)
|
|
158
|
+
result.sources = filtered;
|
|
159
|
+
}
|
|
136
160
|
if (Array.isArray(e.parameters)) {
|
|
137
161
|
const validated = e.parameters
|
|
138
162
|
.filter((p) => {
|
|
@@ -196,6 +220,36 @@ export function extractCommandParameters(template) {
|
|
|
196
220
|
}
|
|
197
221
|
return params.length > 0 ? params : undefined;
|
|
198
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* Extract wiki frontmatter fields (wikiRole, pageKind, xrefs, sources) from a parsed
|
|
225
|
+
* frontmatter block and apply them to the entry. Tolerates missing or malformed values.
|
|
226
|
+
*/
|
|
227
|
+
export function applyWikiFrontmatter(entry, fmData) {
|
|
228
|
+
const role = fmData.wikiRole;
|
|
229
|
+
if (role === "schema" || role === "index" || role === "log" || role === "raw" || role === "page") {
|
|
230
|
+
entry.wikiRole = role;
|
|
231
|
+
}
|
|
232
|
+
const pageKind = fmData.pageKind;
|
|
233
|
+
if (typeof pageKind === "string" && pageKind.trim().length > 0) {
|
|
234
|
+
entry.pageKind = pageKind.trim();
|
|
235
|
+
}
|
|
236
|
+
const xrefs = fmData.xrefs;
|
|
237
|
+
if (Array.isArray(xrefs)) {
|
|
238
|
+
const filtered = xrefs
|
|
239
|
+
.filter((x) => typeof x === "string" && x.trim().length > 0)
|
|
240
|
+
.map((x) => x.trim());
|
|
241
|
+
if (filtered.length > 0)
|
|
242
|
+
entry.xrefs = filtered;
|
|
243
|
+
}
|
|
244
|
+
const sources = fmData.sources;
|
|
245
|
+
if (Array.isArray(sources)) {
|
|
246
|
+
const filtered = sources
|
|
247
|
+
.filter((s) => typeof s === "string" && s.trim().length > 0)
|
|
248
|
+
.map((s) => s.trim());
|
|
249
|
+
if (filtered.length > 0)
|
|
250
|
+
entry.sources = filtered;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
199
253
|
/**
|
|
200
254
|
* Extract `@param` JSDoc tags from a script file's leading comment block.
|
|
201
255
|
*
|
|
@@ -316,6 +370,8 @@ export async function generateMetadata(dirPath, assetType, files, typeRoot = dir
|
|
|
316
370
|
const fmParams = extractFrontmatterParameters(parsed.data);
|
|
317
371
|
if (fmParams)
|
|
318
372
|
entry.parameters = fmParams;
|
|
373
|
+
// Pass wiki-pattern frontmatter through onto the entry
|
|
374
|
+
applyWikiFrontmatter(entry, parsed.data);
|
|
319
375
|
// Extract parameters from template placeholders ($1, $ARGUMENTS, {{named}})
|
|
320
376
|
if (entry.type === "command") {
|
|
321
377
|
const cmdParams = extractCommandParameters(parsed.content);
|
|
@@ -324,8 +380,11 @@ export async function generateMetadata(dirPath, assetType, files, typeRoot = dir
|
|
|
324
380
|
}
|
|
325
381
|
}
|
|
326
382
|
}
|
|
327
|
-
// Extract @param from script files
|
|
328
|
-
|
|
383
|
+
// Extract @param from script files.
|
|
384
|
+
// Vault files (.env) are deliberately excluded — their contents are secrets
|
|
385
|
+
// and must never be parsed for @param or any other metadata that could
|
|
386
|
+
// embed a value into the entry.
|
|
387
|
+
if (ext !== ".md" && assetType !== "vault") {
|
|
329
388
|
const scriptParams = extractScriptParameters(file);
|
|
330
389
|
if (scriptParams)
|
|
331
390
|
entry.parameters = scriptParams;
|
|
@@ -418,6 +477,8 @@ export async function generateMetadataFlat(stashRoot, files) {
|
|
|
418
477
|
const fmParams = extractFrontmatterParameters(parsed.data);
|
|
419
478
|
if (fmParams)
|
|
420
479
|
entry.parameters = fmParams;
|
|
480
|
+
// Pass wiki-pattern frontmatter through onto the entry
|
|
481
|
+
applyWikiFrontmatter(entry, parsed.data);
|
|
421
482
|
// Extract parameters from template placeholders ($1, $ARGUMENTS, {{named}})
|
|
422
483
|
if (entry.type === "command") {
|
|
423
484
|
const cmdParams = extractCommandParameters(parsed.content);
|
|
@@ -426,8 +487,11 @@ export async function generateMetadataFlat(stashRoot, files) {
|
|
|
426
487
|
}
|
|
427
488
|
}
|
|
428
489
|
}
|
|
429
|
-
// Extract @param from script files
|
|
430
|
-
|
|
490
|
+
// Extract @param from script files.
|
|
491
|
+
// Vault files (.env) are deliberately excluded — their contents are secrets
|
|
492
|
+
// and must never be parsed for @param or any other metadata that could
|
|
493
|
+
// embed a value into the entry.
|
|
494
|
+
if (ext !== ".md" && assetType !== "vault") {
|
|
431
495
|
const scriptParams = extractScriptParameters(file, ctx.content());
|
|
432
496
|
if (scriptParams)
|
|
433
497
|
entry.parameters = scriptParams;
|
package/dist/paths.js
CHANGED
|
@@ -68,6 +68,9 @@ export function getCacheDir() {
|
|
|
68
68
|
export function getDbPath() {
|
|
69
69
|
return path.join(getCacheDir(), "index.db");
|
|
70
70
|
}
|
|
71
|
+
export function getWorkflowDbPath() {
|
|
72
|
+
return path.join(getCacheDir(), "workflow.db");
|
|
73
|
+
}
|
|
71
74
|
export function getSemanticStatusPath() {
|
|
72
75
|
return path.join(getCacheDir(), "semantic-status.json");
|
|
73
76
|
}
|
package/dist/registry-install.js
CHANGED
|
@@ -20,6 +20,9 @@ export async function installRegistryRef(ref, options) {
|
|
|
20
20
|
if (parsed.source === "git") {
|
|
21
21
|
return installGitRegistryRef(parsed, config, options);
|
|
22
22
|
}
|
|
23
|
+
if (parsed.source === "github") {
|
|
24
|
+
return installGithubRegistryRef(parsed, config, options);
|
|
25
|
+
}
|
|
23
26
|
const resolved = await resolveRegistryArtifact(parsed);
|
|
24
27
|
const registryLabels = deriveRegistryLabels({
|
|
25
28
|
source: resolved.source,
|
|
@@ -38,7 +41,7 @@ export async function installRegistryRef(ref, options) {
|
|
|
38
41
|
const cachedStashRoot = detectStashRoot(extractedDir);
|
|
39
42
|
if (cachedStashRoot) {
|
|
40
43
|
const integrity = fs.existsSync(archivePath) ? await computeFileHash(archivePath) : undefined;
|
|
41
|
-
const audit = runInstallAuditOrThrow(extractedDir, resolved.source, resolved.ref, registryLabels, config);
|
|
44
|
+
const audit = runInstallAuditOrThrow(extractedDir, resolved.source, resolved.ref, registryLabels, config, options);
|
|
42
45
|
return {
|
|
43
46
|
id: resolved.id,
|
|
44
47
|
source: resolved.source,
|
|
@@ -51,6 +54,7 @@ export async function installRegistryRef(ref, options) {
|
|
|
51
54
|
extractedDir,
|
|
52
55
|
stashRoot: cachedStashRoot,
|
|
53
56
|
integrity,
|
|
57
|
+
writable: options?.writable,
|
|
54
58
|
audit,
|
|
55
59
|
};
|
|
56
60
|
}
|
|
@@ -70,7 +74,7 @@ export async function installRegistryRef(ref, options) {
|
|
|
70
74
|
verifyArchiveIntegrity(archivePath, resolved.resolvedRevision, resolved.source);
|
|
71
75
|
integrity = await computeFileHash(archivePath);
|
|
72
76
|
extractTarGzSecure(archivePath, extractedDir);
|
|
73
|
-
audit = runInstallAuditOrThrow(extractedDir, resolved.source, resolved.ref, registryLabels, config);
|
|
77
|
+
audit = runInstallAuditOrThrow(extractedDir, resolved.source, resolved.ref, registryLabels, config, options);
|
|
74
78
|
provisionalKitRoot = detectStashRoot(extractedDir);
|
|
75
79
|
installRoot = applyAkmIncludeConfig(provisionalKitRoot, cacheDir, extractedDir) ?? provisionalKitRoot;
|
|
76
80
|
stashRoot = detectStashRoot(installRoot);
|
|
@@ -98,9 +102,24 @@ export async function installRegistryRef(ref, options) {
|
|
|
98
102
|
extractedDir,
|
|
99
103
|
stashRoot,
|
|
100
104
|
integrity,
|
|
105
|
+
writable: options?.writable,
|
|
101
106
|
audit,
|
|
102
107
|
};
|
|
103
108
|
}
|
|
109
|
+
async function installGithubRegistryRef(parsed, config, options) {
|
|
110
|
+
const gitParsed = {
|
|
111
|
+
source: "git",
|
|
112
|
+
ref: parsed.ref,
|
|
113
|
+
id: parsed.id,
|
|
114
|
+
url: `https://github.com/${parsed.owner}/${parsed.repo}.git`,
|
|
115
|
+
requestedRef: parsed.requestedRef,
|
|
116
|
+
};
|
|
117
|
+
const installed = await installGitRegistryRef(gitParsed, config, options);
|
|
118
|
+
return {
|
|
119
|
+
...installed,
|
|
120
|
+
source: "github",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
104
123
|
async function installLocalRegistryRef(parsed, config, options) {
|
|
105
124
|
const resolved = await resolveRegistryArtifact(parsed);
|
|
106
125
|
const installedAt = (options?.now ?? new Date()).toISOString();
|
|
@@ -109,7 +128,7 @@ async function installLocalRegistryRef(parsed, config, options) {
|
|
|
109
128
|
ref: resolved.ref,
|
|
110
129
|
artifactUrl: resolved.artifactUrl,
|
|
111
130
|
});
|
|
112
|
-
const audit = runInstallAuditOrThrow(parsed.sourcePath, resolved.source, resolved.ref, registryLabels, config);
|
|
131
|
+
const audit = runInstallAuditOrThrow(parsed.sourcePath, resolved.source, resolved.ref, registryLabels, config, options);
|
|
113
132
|
// For local directories, detect the stash root within the source path.
|
|
114
133
|
// If no nested stash is found, the source path itself is used.
|
|
115
134
|
const stashRoot = detectStashRoot(parsed.sourcePath);
|
|
@@ -124,6 +143,7 @@ async function installLocalRegistryRef(parsed, config, options) {
|
|
|
124
143
|
cacheDir: parsed.sourcePath,
|
|
125
144
|
extractedDir: parsed.sourcePath,
|
|
126
145
|
stashRoot,
|
|
146
|
+
writable: options?.writable,
|
|
127
147
|
audit,
|
|
128
148
|
};
|
|
129
149
|
}
|
|
@@ -148,7 +168,7 @@ async function installGitRegistryRef(parsed, config, options) {
|
|
|
148
168
|
const installRoot = applyAkmIncludeConfig(provisionalKitRoot, cacheDir, extractedDir) ?? provisionalKitRoot;
|
|
149
169
|
const stashRoot = detectStashRoot(installRoot);
|
|
150
170
|
if (stashRoot) {
|
|
151
|
-
const audit = runInstallAuditOrThrow(extractedDir, resolved.source, resolved.ref, registryLabels, config);
|
|
171
|
+
const audit = runInstallAuditOrThrow(extractedDir, resolved.source, resolved.ref, registryLabels, config, options);
|
|
152
172
|
return {
|
|
153
173
|
id: resolved.id,
|
|
154
174
|
source: resolved.source,
|
|
@@ -160,6 +180,7 @@ async function installGitRegistryRef(parsed, config, options) {
|
|
|
160
180
|
cacheDir,
|
|
161
181
|
extractedDir,
|
|
162
182
|
stashRoot,
|
|
183
|
+
writable: options?.writable,
|
|
163
184
|
audit,
|
|
164
185
|
};
|
|
165
186
|
}
|
|
@@ -193,7 +214,7 @@ async function installGitRegistryRef(parsed, config, options) {
|
|
|
193
214
|
copyDirectoryContents(cloneDir, extractedDir);
|
|
194
215
|
// Clean up the clone dir
|
|
195
216
|
fs.rmSync(cloneDir, { recursive: true, force: true });
|
|
196
|
-
audit = runInstallAuditOrThrow(extractedDir, resolved.source, resolved.ref, registryLabels, config);
|
|
217
|
+
audit = runInstallAuditOrThrow(extractedDir, resolved.source, resolved.ref, registryLabels, config, options);
|
|
197
218
|
provisionalKitRoot = detectStashRoot(extractedDir);
|
|
198
219
|
installRoot = applyAkmIncludeConfig(provisionalKitRoot, cacheDir, extractedDir) ?? provisionalKitRoot;
|
|
199
220
|
stashRoot = detectStashRoot(installRoot);
|
|
@@ -220,6 +241,7 @@ async function installGitRegistryRef(parsed, config, options) {
|
|
|
220
241
|
cacheDir,
|
|
221
242
|
extractedDir,
|
|
222
243
|
stashRoot,
|
|
244
|
+
writable: options?.writable,
|
|
223
245
|
audit,
|
|
224
246
|
};
|
|
225
247
|
}
|
|
@@ -494,8 +516,15 @@ async function computeFileHash(filePath) {
|
|
|
494
516
|
const hash = createHash("sha256").update(data).digest("hex");
|
|
495
517
|
return `sha256:${hash}`;
|
|
496
518
|
}
|
|
497
|
-
function runInstallAuditOrThrow(rootDir, source, ref, registryLabels, config) {
|
|
498
|
-
const audit = auditInstallCandidate({
|
|
519
|
+
function runInstallAuditOrThrow(rootDir, source, ref, registryLabels, config, options) {
|
|
520
|
+
const audit = auditInstallCandidate({
|
|
521
|
+
rootDir,
|
|
522
|
+
source,
|
|
523
|
+
ref,
|
|
524
|
+
registryLabels,
|
|
525
|
+
config,
|
|
526
|
+
trustThisInstall: options?.trustThisInstall,
|
|
527
|
+
});
|
|
499
528
|
if (audit.blocked) {
|
|
500
529
|
throw new Error(formatInstallAuditFailure(ref, audit));
|
|
501
530
|
}
|
package/dist/registry-resolve.js
CHANGED
|
@@ -273,6 +273,20 @@ async function resolveNpmArtifact(parsed) {
|
|
|
273
273
|
};
|
|
274
274
|
}
|
|
275
275
|
async function resolveGithubArtifact(parsed) {
|
|
276
|
+
const gitUrl = `https://github.com/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}.git`;
|
|
277
|
+
// Prefer git-backed installs so private GitHub repos work with the user's
|
|
278
|
+
// normal git credential helper rather than requiring API-specific auth.
|
|
279
|
+
const gitResolvedRevision = resolveGitRevisionFromRemote(gitUrl, parsed.requestedRef);
|
|
280
|
+
if (gitResolvedRevision) {
|
|
281
|
+
return {
|
|
282
|
+
id: parsed.id,
|
|
283
|
+
source: parsed.source,
|
|
284
|
+
ref: parsed.ref,
|
|
285
|
+
artifactUrl: gitUrl,
|
|
286
|
+
resolvedVersion: parsed.requestedRef,
|
|
287
|
+
resolvedRevision: gitResolvedRevision,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
276
290
|
const headers = githubHeaders();
|
|
277
291
|
if (parsed.requestedRef) {
|
|
278
292
|
const commit = await tryFetchJson(`${GITHUB_API_BASE}/repos/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}/commits/${encodeURIComponent(parsed.requestedRef)}`, headers);
|
|
@@ -315,6 +329,17 @@ async function resolveGithubArtifact(parsed) {
|
|
|
315
329
|
resolvedRevision: asString(commit?.sha) ?? defaultBranch,
|
|
316
330
|
};
|
|
317
331
|
}
|
|
332
|
+
function resolveGitRevisionFromRemote(url, requestedRef) {
|
|
333
|
+
validateGitUrl(url);
|
|
334
|
+
const ref = requestedRef ?? "HEAD";
|
|
335
|
+
if (requestedRef)
|
|
336
|
+
validateGitRef(requestedRef);
|
|
337
|
+
const result = spawnSync("git", ["ls-remote", url, ref], { encoding: "utf8", timeout: 30_000 });
|
|
338
|
+
if (result.status !== 0)
|
|
339
|
+
return undefined;
|
|
340
|
+
const firstLine = result.stdout.trim().split(/\r?\n/)[0];
|
|
341
|
+
return firstLine?.split(/\s/)[0] || undefined;
|
|
342
|
+
}
|
|
318
343
|
async function resolveGitArtifact(parsed) {
|
|
319
344
|
validateGitUrl(parsed.url);
|
|
320
345
|
const ref = parsed.requestedRef ?? "HEAD";
|
package/dist/renderers.js
CHANGED
|
@@ -9,10 +9,14 @@
|
|
|
9
9
|
import fs from "node:fs";
|
|
10
10
|
import path from "node:path";
|
|
11
11
|
import { hasErrnoCode } from "./common";
|
|
12
|
+
import { UsageError } from "./errors";
|
|
12
13
|
import { registerRenderer } from "./file-context";
|
|
13
14
|
import { parseFrontmatter, toStringOrUndefined } from "./frontmatter";
|
|
14
15
|
import { extractFrontmatterOnly, extractLineRange, extractSection, formatToc, parseMarkdownToc } from "./markdown";
|
|
15
16
|
import { extractDescriptionFromComments, loadStashFile } from "./metadata";
|
|
17
|
+
import { makeAssetRef } from "./stash-ref";
|
|
18
|
+
import { listKeys as listVaultKeys } from "./vault";
|
|
19
|
+
import { parseWorkflowMarkdown, WorkflowValidationError } from "./workflow-markdown";
|
|
16
20
|
// ── Interpreter auto-detection map ───────────────────────────────────────────
|
|
17
21
|
const INTERPRETER_MAP = {
|
|
18
22
|
".sh": "bash",
|
|
@@ -149,6 +153,12 @@ function deriveName(ctx) {
|
|
|
149
153
|
const ext = path.extname(ctx.relPath);
|
|
150
154
|
return ext ? ctx.relPath.slice(0, -ext.length) : ctx.relPath;
|
|
151
155
|
}
|
|
156
|
+
function shellQuote(value) {
|
|
157
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
158
|
+
}
|
|
159
|
+
export function buildWorkflowAction(ref) {
|
|
160
|
+
return `Resume the active run or start a new run with \`akm workflow next ${shellQuote(ref)}\`.`;
|
|
161
|
+
}
|
|
152
162
|
/**
|
|
153
163
|
* Load the matching StashEntry for a file path from the directory's .stash.json.
|
|
154
164
|
*/
|
|
@@ -309,6 +319,85 @@ const knowledgeMdRenderer = {
|
|
|
309
319
|
}
|
|
310
320
|
},
|
|
311
321
|
};
|
|
322
|
+
// ── 4b. wiki-md ──────────────────────────────────────────────────────────────
|
|
323
|
+
const WIKI_PAGE_ACTION = "Wiki page — read below. Use 'toc' to scan, 'section <heading>' for depth.";
|
|
324
|
+
const wikiMdRenderer = {
|
|
325
|
+
name: "wiki-md",
|
|
326
|
+
buildShowResponse(ctx) {
|
|
327
|
+
const name = deriveName(ctx);
|
|
328
|
+
const v = ctx.matchResult.meta?.view ?? { mode: "full" };
|
|
329
|
+
const content = ctx.content();
|
|
330
|
+
switch (v.mode) {
|
|
331
|
+
case "toc": {
|
|
332
|
+
const toc = parseMarkdownToc(content);
|
|
333
|
+
return {
|
|
334
|
+
type: "wiki",
|
|
335
|
+
name,
|
|
336
|
+
path: ctx.absPath,
|
|
337
|
+
action: WIKI_PAGE_ACTION,
|
|
338
|
+
content: formatToc(toc),
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
case "frontmatter": {
|
|
342
|
+
const fm = extractFrontmatterOnly(content);
|
|
343
|
+
return {
|
|
344
|
+
type: "wiki",
|
|
345
|
+
name,
|
|
346
|
+
path: ctx.absPath,
|
|
347
|
+
action: WIKI_PAGE_ACTION,
|
|
348
|
+
content: fm ?? "(no frontmatter)",
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
case "section": {
|
|
352
|
+
const section = extractSection(content, v.heading);
|
|
353
|
+
if (!section) {
|
|
354
|
+
return {
|
|
355
|
+
type: "wiki",
|
|
356
|
+
name,
|
|
357
|
+
path: ctx.absPath,
|
|
358
|
+
action: WIKI_PAGE_ACTION,
|
|
359
|
+
content: `Section "${v.heading}" not found in ${name}. Try \`akm show wiki:${name} toc\` to discover available headings.`,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
type: "wiki",
|
|
364
|
+
name,
|
|
365
|
+
path: ctx.absPath,
|
|
366
|
+
action: WIKI_PAGE_ACTION,
|
|
367
|
+
content: section.content,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
case "lines": {
|
|
371
|
+
return {
|
|
372
|
+
type: "wiki",
|
|
373
|
+
name,
|
|
374
|
+
path: ctx.absPath,
|
|
375
|
+
action: WIKI_PAGE_ACTION,
|
|
376
|
+
content: extractLineRange(content, v.start, v.end),
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
default: {
|
|
380
|
+
return {
|
|
381
|
+
type: "wiki",
|
|
382
|
+
name,
|
|
383
|
+
path: ctx.absPath,
|
|
384
|
+
action: WIKI_PAGE_ACTION,
|
|
385
|
+
content,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
extractMetadata(entry, ctx) {
|
|
391
|
+
try {
|
|
392
|
+
const toc = parseMarkdownToc(ctx.content());
|
|
393
|
+
if (toc.headings.length > 0)
|
|
394
|
+
entry.toc = toc.headings;
|
|
395
|
+
}
|
|
396
|
+
catch {
|
|
397
|
+
// Non-fatal: skip TOC if file can't be read
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
};
|
|
312
401
|
// ── 5. memory-md ─────────────────────────────────────────────────────────────
|
|
313
402
|
const memoryMdRenderer = {
|
|
314
403
|
name: "memory-md",
|
|
@@ -323,7 +412,58 @@ const memoryMdRenderer = {
|
|
|
323
412
|
};
|
|
324
413
|
},
|
|
325
414
|
};
|
|
326
|
-
// ── 6.
|
|
415
|
+
// ── 6. workflow-md ───────────────────────────────────────────────────────────
|
|
416
|
+
const workflowMdRenderer = {
|
|
417
|
+
name: "workflow-md",
|
|
418
|
+
buildShowResponse(ctx) {
|
|
419
|
+
const name = deriveName(ctx);
|
|
420
|
+
const workflow = parseWorkflowForRendering(ctx.content());
|
|
421
|
+
const ref = makeAssetRef("workflow", name, ctx.origin);
|
|
422
|
+
return {
|
|
423
|
+
type: "workflow",
|
|
424
|
+
name,
|
|
425
|
+
path: ctx.absPath,
|
|
426
|
+
action: buildWorkflowAction(ref),
|
|
427
|
+
description: workflow.description,
|
|
428
|
+
workflowTitle: workflow.title,
|
|
429
|
+
parameters: workflow.parameters?.map((parameter) => parameter.name),
|
|
430
|
+
workflowParameters: workflow.parameters,
|
|
431
|
+
steps: workflow.steps,
|
|
432
|
+
};
|
|
433
|
+
},
|
|
434
|
+
extractMetadata(entry, ctx) {
|
|
435
|
+
const workflow = parseWorkflowForRendering(ctx.content());
|
|
436
|
+
const hints = new Set(entry.searchHints ?? []);
|
|
437
|
+
hints.add(workflow.title);
|
|
438
|
+
for (const step of workflow.steps) {
|
|
439
|
+
hints.add(step.title);
|
|
440
|
+
hints.add(step.id);
|
|
441
|
+
hints.add(step.instructions);
|
|
442
|
+
for (const criterion of step.completionCriteria ?? []) {
|
|
443
|
+
hints.add(criterion);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
entry.searchHints = Array.from(hints).filter(Boolean);
|
|
447
|
+
if (workflow.parameters?.length) {
|
|
448
|
+
entry.parameters = workflow.parameters.map((parameter) => ({
|
|
449
|
+
name: parameter.name,
|
|
450
|
+
...(parameter.description ? { description: parameter.description } : {}),
|
|
451
|
+
}));
|
|
452
|
+
}
|
|
453
|
+
},
|
|
454
|
+
};
|
|
455
|
+
function parseWorkflowForRendering(content) {
|
|
456
|
+
try {
|
|
457
|
+
return parseWorkflowMarkdown(content);
|
|
458
|
+
}
|
|
459
|
+
catch (error) {
|
|
460
|
+
if (error instanceof WorkflowValidationError) {
|
|
461
|
+
throw new UsageError(error.message);
|
|
462
|
+
}
|
|
463
|
+
throw error;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
// ── 7. script-source ─────────────────────────────────────────────────────────
|
|
327
467
|
const scriptSourceRenderer = {
|
|
328
468
|
name: "script-source",
|
|
329
469
|
buildShowResponse(ctx) {
|
|
@@ -379,6 +519,43 @@ const scriptSourceRenderer = {
|
|
|
379
519
|
}
|
|
380
520
|
},
|
|
381
521
|
};
|
|
522
|
+
// ── 8. vault-env ─────────────────────────────────────────────────────────────
|
|
523
|
+
/**
|
|
524
|
+
* Vault renderer. Returns ONLY key names and start-of-line comments — never
|
|
525
|
+
* values. Deliberately omits content/template/prompt so vault values cannot
|
|
526
|
+
* leak through `akm show`.
|
|
527
|
+
*/
|
|
528
|
+
const vaultEnvRenderer = {
|
|
529
|
+
name: "vault-env",
|
|
530
|
+
buildShowResponse(ctx) {
|
|
531
|
+
const name = deriveName(ctx);
|
|
532
|
+
const { keys, comments } = listVaultKeys(ctx.absPath);
|
|
533
|
+
return {
|
|
534
|
+
type: "vault",
|
|
535
|
+
name,
|
|
536
|
+
path: ctx.absPath,
|
|
537
|
+
action: 'Vault — keys + comments only. Use `eval "$(akm vault load <ref>)"` to load values into the current shell. Values stay on disk and are never written to akm\'s stdout.',
|
|
538
|
+
description: comments.length > 0 ? comments.join("\n") : undefined,
|
|
539
|
+
keys,
|
|
540
|
+
comments,
|
|
541
|
+
};
|
|
542
|
+
},
|
|
543
|
+
extractMetadata(entry, ctx) {
|
|
544
|
+
// Re-derive from the file directly to guarantee no value ever transits
|
|
545
|
+
// through any other code path. Caller already short-circuits in
|
|
546
|
+
// generateMetadata{,Flat}, but this is defense in depth.
|
|
547
|
+
const { keys, comments } = listVaultKeys(ctx.absPath);
|
|
548
|
+
if (comments.length > 0 && !entry.description) {
|
|
549
|
+
entry.description = comments.join(" ").slice(0, 500);
|
|
550
|
+
entry.source = "comments";
|
|
551
|
+
entry.confidence = 0.7;
|
|
552
|
+
}
|
|
553
|
+
if (keys.length > 0) {
|
|
554
|
+
entry.searchHints = keys;
|
|
555
|
+
}
|
|
556
|
+
entry.tags = Array.from(new Set([...(entry.tags ?? []), "vault", "secrets"]));
|
|
557
|
+
},
|
|
558
|
+
};
|
|
382
559
|
// ── Registration ─────────────────────────────────────────────────────────────
|
|
383
560
|
/** All built-in renderers. */
|
|
384
561
|
const builtinRenderers = [
|
|
@@ -386,8 +563,11 @@ const builtinRenderers = [
|
|
|
386
563
|
commandMdRenderer,
|
|
387
564
|
agentMdRenderer,
|
|
388
565
|
knowledgeMdRenderer,
|
|
566
|
+
wikiMdRenderer,
|
|
389
567
|
memoryMdRenderer,
|
|
568
|
+
workflowMdRenderer,
|
|
390
569
|
scriptSourceRenderer,
|
|
570
|
+
vaultEnvRenderer,
|
|
391
571
|
];
|
|
392
572
|
/**
|
|
393
573
|
* Register all built-in renderers with the file-context registry.
|
|
@@ -399,4 +579,4 @@ export function registerBuiltinRenderers() {
|
|
|
399
579
|
}
|
|
400
580
|
}
|
|
401
581
|
// ── Named exports for testing ────────────────────────────────────────────────
|
|
402
|
-
export { agentMdRenderer, commandMdRenderer, INTERPRETER_MAP, knowledgeMdRenderer, memoryMdRenderer, SETUP_SIGNALS, scriptSourceRenderer, skillMdRenderer, };
|
|
582
|
+
export { agentMdRenderer, commandMdRenderer, INTERPRETER_MAP, knowledgeMdRenderer, memoryMdRenderer, SETUP_SIGNALS, scriptSourceRenderer, skillMdRenderer, vaultEnvRenderer, wikiMdRenderer, workflowMdRenderer, };
|
package/dist/search-fields.js
CHANGED
|
@@ -41,6 +41,10 @@ export function buildSearchFields(entry) {
|
|
|
41
41
|
if (entry.intent.output)
|
|
42
42
|
hintParts.push(entry.intent.output);
|
|
43
43
|
}
|
|
44
|
+
if (entry.xrefs)
|
|
45
|
+
hintParts.push(entry.xrefs.join(" "));
|
|
46
|
+
if (entry.pageKind)
|
|
47
|
+
hintParts.push(entry.pageKind);
|
|
44
48
|
const hints = hintParts.join(" ").toLowerCase();
|
|
45
49
|
const contentParts = [];
|
|
46
50
|
if (entry.toc) {
|