akm-cli 0.6.0-rc1 → 0.6.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/CHANGELOG.md +33 -0
- package/README.md +9 -9
- package/dist/cli.js +199 -114
- package/dist/{completions.js → commands/completions.js} +1 -1
- package/dist/{config-cli.js → commands/config-cli.js} +109 -11
- package/dist/{curate.js → commands/curate.js} +8 -3
- package/dist/{info.js → commands/info.js} +15 -9
- package/dist/{init.js → commands/init.js} +4 -4
- package/dist/{install-audit.js → commands/install-audit.js} +4 -7
- package/dist/{installed-stashes.js → commands/installed-stashes.js} +77 -31
- package/dist/{migration-help.js → commands/migration-help.js} +2 -2
- package/dist/{registry-search.js → commands/registry-search.js} +8 -6
- package/dist/{remember.js → commands/remember.js} +55 -49
- package/dist/{stash-search.js → commands/search.js} +28 -69
- package/dist/{self-update.js → commands/self-update.js} +69 -3
- package/dist/{stash-show.js → commands/show.js} +104 -84
- package/dist/{stash-add.js → commands/source-add.js} +42 -32
- package/dist/{stash-clone.js → commands/source-clone.js} +12 -10
- 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} +1 -1
- package/dist/{asset-spec.js → core/asset-spec.js} +1 -1
- package/dist/{config.js → core/config.js} +133 -56
- package/dist/core/errors.js +90 -0
- package/dist/{frontmatter.js → core/frontmatter.js} +5 -3
- package/dist/core/write-source.js +280 -0
- package/dist/{db-search.js → indexer/db-search.js} +25 -19
- package/dist/{db.js → indexer/db.js} +79 -47
- package/dist/{file-context.js → indexer/file-context.js} +3 -3
- package/dist/{indexer.js → indexer/indexer.js} +132 -33
- package/dist/{manifest.js → indexer/manifest.js} +10 -10
- package/dist/{matchers.js → indexer/matchers.js} +3 -6
- package/dist/{metadata.js → indexer/metadata.js} +9 -5
- package/dist/{search-source.js → indexer/search-source.js} +52 -41
- 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} +1 -1
- package/dist/{llm-client.js → llm/client.js} +1 -1
- package/dist/{embedders → llm/embedders}/local.js +2 -2
- package/dist/{embedders → llm/embedders}/remote.js +1 -1
- package/dist/{embedders → llm/embedders}/types.js +1 -1
- package/dist/{metadata-enhance.js → llm/metadata-enhance.js} +2 -2
- package/dist/{cli-hints.js → output/cli-hints.js} +3 -0
- package/dist/{output-context.js → output/context.js} +21 -3
- package/dist/{renderers.js → output/renderers.js} +9 -65
- package/dist/{output-shapes.js → output/shapes.js} +18 -4
- package/dist/{output-text.js → output/text.js} +2 -2
- package/dist/{registry-build-index.js → registry/build-index.js} +16 -7
- 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/{providers → registry/providers}/index.js +1 -1
- package/dist/{providers → registry/providers}/skills-sh.js +59 -3
- package/dist/{providers → registry/providers}/static-index.js +80 -12
- package/dist/registry/providers/types.js +25 -0
- package/dist/{registry-resolve.js → registry/resolve.js} +3 -3
- 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} +16 -56
- package/dist/{stash-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 +53 -64
- package/dist/{stash-providers → sources/providers}/index.js +3 -4
- package/dist/sources/providers/install-types.js +14 -0
- package/dist/{stash-providers → sources/providers}/npm.js +42 -41
- package/dist/{stash-providers → sources/providers}/provider-utils.js +3 -3
- package/dist/{stash-providers → sources/providers}/sync-from-ref.js +2 -2
- package/dist/{stash-providers → sources/providers}/tar-utils.js +11 -8
- package/dist/{stash-providers → sources/providers}/website.js +29 -65
- package/dist/{stash-resolve.js → sources/resolve.js} +8 -7
- package/dist/{wiki.js → wiki/wiki.js} +34 -18
- package/dist/{workflow-authoring.js → workflows/authoring.js} +37 -14
- 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} +72 -28
- package/dist/workflows/schema.js +11 -0
- package/dist/workflows/validator.js +48 -0
- package/docs/migration/release-notes/0.6.0.md +91 -23
- package/package.json +1 -1
- package/dist/errors.js +0 -45
- package/dist/llm.js +0 -16
- package/dist/registry-factory.js +0 -19
- package/dist/ripgrep.js +0 -2
- package/dist/stash-provider-factory.js +0 -35
- package/dist/stash-provider.js +0 -3
- package/dist/stash-providers/filesystem.js +0 -71
- package/dist/stash-providers/openviking.js +0 -348
- package/dist/stash-types.js +0 -1
- package/dist/workflow-markdown.js +0 -260
- /package/dist/{common.js → core/common.js} +0 -0
- /package/dist/{markdown.js → core/markdown.js} +0 -0
- /package/dist/{paths.js → core/paths.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/{embedder.js → llm/embedder.js} +0 -0
- /package/dist/{embedders → llm/embedders}/cache.js +0 -0
- /package/dist/{registry-provider.js → registry/types.js} +0 -0
- /package/dist/{setup-steps.js → setup/steps.js} +0 -0
- /package/dist/{registry-types.js → sources/types.js} +0 -0
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Initialized from `cli.ts` before `runMain`.
|
|
10
10
|
*/
|
|
11
|
-
import { UsageError } from "
|
|
11
|
+
import { UsageError } from "../core/errors";
|
|
12
12
|
export const OUTPUT_FORMATS = ["json", "yaml", "text", "jsonl"];
|
|
13
13
|
export const DETAIL_LEVELS = ["brief", "normal", "full", "summary", "agent"];
|
|
14
14
|
export function parseOutputFormat(value) {
|
|
@@ -16,14 +16,14 @@ export function parseOutputFormat(value) {
|
|
|
16
16
|
return undefined;
|
|
17
17
|
if (OUTPUT_FORMATS.includes(value))
|
|
18
18
|
return value;
|
|
19
|
-
throw new UsageError(`Invalid value for --format: ${value}. Expected one of: ${OUTPUT_FORMATS.join("|")}
|
|
19
|
+
throw new UsageError(`Invalid value for --format: ${value}. Expected one of: ${OUTPUT_FORMATS.join("|")}`, "INVALID_FORMAT_VALUE");
|
|
20
20
|
}
|
|
21
21
|
export function parseDetailLevel(value) {
|
|
22
22
|
if (!value)
|
|
23
23
|
return undefined;
|
|
24
24
|
if (DETAIL_LEVELS.includes(value))
|
|
25
25
|
return value;
|
|
26
|
-
throw new UsageError(`Invalid value for --detail: ${value}. Expected one of: ${DETAIL_LEVELS.join("|")}
|
|
26
|
+
throw new UsageError(`Invalid value for --detail: ${value}. Expected one of: ${DETAIL_LEVELS.join("|")}`, "INVALID_DETAIL_VALUE");
|
|
27
27
|
}
|
|
28
28
|
export function parseFlagValue(argv, flag) {
|
|
29
29
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -38,6 +38,24 @@ export function parseFlagValue(argv, flag) {
|
|
|
38
38
|
export function hasBooleanFlag(argv, flag) {
|
|
39
39
|
return argv.some((arg) => arg === flag || arg === `${flag}=true`);
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Read a hyphenated arg out of citty's parsed `args` object.
|
|
43
|
+
*
|
|
44
|
+
* citty does not auto-camelise hyphenated arg keys (see `--max-pages`,
|
|
45
|
+
* `--with-sources` for the existing convention), so command handlers end up
|
|
46
|
+
* casting `args` to a string-indexed record at every read site. This helper
|
|
47
|
+
* encapsulates the cast.
|
|
48
|
+
*/
|
|
49
|
+
export function getHyphenatedArg(args, key) {
|
|
50
|
+
if (typeof args !== "object" || args === null)
|
|
51
|
+
return undefined;
|
|
52
|
+
const value = args[key];
|
|
53
|
+
return value === undefined ? undefined : value;
|
|
54
|
+
}
|
|
55
|
+
/** Boolean variant of {@link getHyphenatedArg} for `--<flag>` switches. */
|
|
56
|
+
export function getHyphenatedBoolean(args, key) {
|
|
57
|
+
return Boolean(getHyphenatedArg(args, key));
|
|
58
|
+
}
|
|
41
59
|
/**
|
|
42
60
|
* Resolve output mode from a synthetic argv array and config defaults.
|
|
43
61
|
* Pure function — no IO. Suitable for unit tests.
|
|
@@ -8,15 +8,13 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import fs from "node:fs";
|
|
10
10
|
import path from "node:path";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { extractDescriptionFromComments, loadStashFile } from "
|
|
17
|
-
import {
|
|
18
|
-
import { listKeys as listVaultKeys } from "./vault";
|
|
19
|
-
import { parseWorkflowMarkdown, WorkflowValidationError } from "./workflow-markdown";
|
|
11
|
+
import { listKeys as listVaultKeys } from "../commands/vault";
|
|
12
|
+
import { hasErrnoCode } from "../core/common";
|
|
13
|
+
import { parseFrontmatter, toStringOrUndefined } from "../core/frontmatter";
|
|
14
|
+
import { extractFrontmatterOnly, extractLineRange, extractSection, formatToc, parseMarkdownToc, } from "../core/markdown";
|
|
15
|
+
import { registerRenderer } from "../indexer/file-context";
|
|
16
|
+
import { extractDescriptionFromComments, loadStashFile } from "../indexer/metadata";
|
|
17
|
+
import { buildWorkflowAction, workflowMdRenderer } from "../workflows/renderer";
|
|
20
18
|
// ── Interpreter auto-detection map ───────────────────────────────────────────
|
|
21
19
|
const INTERPRETER_MAP = {
|
|
22
20
|
".sh": "bash",
|
|
@@ -153,12 +151,7 @@ function deriveName(ctx) {
|
|
|
153
151
|
const ext = path.extname(ctx.relPath);
|
|
154
152
|
return ext ? ctx.relPath.slice(0, -ext.length) : ctx.relPath;
|
|
155
153
|
}
|
|
156
|
-
|
|
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
|
-
}
|
|
154
|
+
export { buildWorkflowAction };
|
|
162
155
|
/**
|
|
163
156
|
* Load the matching StashEntry for a file path from the directory's .stash.json.
|
|
164
157
|
*/
|
|
@@ -465,56 +458,7 @@ const memoryMdRenderer = {
|
|
|
465
458
|
},
|
|
466
459
|
};
|
|
467
460
|
// ── 6. workflow-md ───────────────────────────────────────────────────────────
|
|
468
|
-
|
|
469
|
-
name: "workflow-md",
|
|
470
|
-
buildShowResponse(ctx) {
|
|
471
|
-
const name = deriveName(ctx);
|
|
472
|
-
const workflow = parseWorkflowForRendering(ctx.content());
|
|
473
|
-
const ref = makeAssetRef("workflow", name, ctx.origin);
|
|
474
|
-
return {
|
|
475
|
-
type: "workflow",
|
|
476
|
-
name,
|
|
477
|
-
path: ctx.absPath,
|
|
478
|
-
action: buildWorkflowAction(ref),
|
|
479
|
-
description: workflow.description,
|
|
480
|
-
workflowTitle: workflow.title,
|
|
481
|
-
parameters: workflow.parameters?.map((parameter) => parameter.name),
|
|
482
|
-
workflowParameters: workflow.parameters,
|
|
483
|
-
steps: workflow.steps,
|
|
484
|
-
};
|
|
485
|
-
},
|
|
486
|
-
extractMetadata(entry, ctx) {
|
|
487
|
-
const workflow = parseWorkflowForRendering(ctx.content());
|
|
488
|
-
const hints = new Set(entry.searchHints ?? []);
|
|
489
|
-
hints.add(workflow.title);
|
|
490
|
-
for (const step of workflow.steps) {
|
|
491
|
-
hints.add(step.title);
|
|
492
|
-
hints.add(step.id);
|
|
493
|
-
hints.add(step.instructions);
|
|
494
|
-
for (const criterion of step.completionCriteria ?? []) {
|
|
495
|
-
hints.add(criterion);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
entry.searchHints = Array.from(hints).filter(Boolean);
|
|
499
|
-
if (workflow.parameters?.length) {
|
|
500
|
-
entry.parameters = workflow.parameters.map((parameter) => ({
|
|
501
|
-
name: parameter.name,
|
|
502
|
-
...(parameter.description ? { description: parameter.description } : {}),
|
|
503
|
-
}));
|
|
504
|
-
}
|
|
505
|
-
},
|
|
506
|
-
};
|
|
507
|
-
function parseWorkflowForRendering(content) {
|
|
508
|
-
try {
|
|
509
|
-
return parseWorkflowMarkdown(content);
|
|
510
|
-
}
|
|
511
|
-
catch (error) {
|
|
512
|
-
if (error instanceof WorkflowValidationError) {
|
|
513
|
-
throw new UsageError(error.message);
|
|
514
|
-
}
|
|
515
|
-
throw error;
|
|
516
|
-
}
|
|
517
|
-
}
|
|
461
|
+
// Defined in src/workflows/renderer.ts and imported above.
|
|
518
462
|
// ── 7. script-source ─────────────────────────────────────────────────────────
|
|
519
463
|
const scriptSourceRenderer = {
|
|
520
464
|
name: "script-source",
|
|
@@ -81,10 +81,20 @@ export function shapeAssetHit(hit, detail) {
|
|
|
81
81
|
}
|
|
82
82
|
export function shapeSearchHit(hit, detail) {
|
|
83
83
|
if (hit.type === "registry") {
|
|
84
|
-
if (detail === "brief")
|
|
85
|
-
|
|
84
|
+
if (detail === "brief") {
|
|
85
|
+
// RegistrySearchHit uses `title` (not `name`); always project installRef
|
|
86
|
+
// and score so callers can use the result without --detail full (QA #28).
|
|
87
|
+
const out = pickFields(hit, ["title", "name", "installRef", "score"]);
|
|
88
|
+
// Normalise: if only title exists, expose it as `name` for consistency
|
|
89
|
+
if (out.title && !out.name)
|
|
90
|
+
out.name = out.title;
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
86
93
|
if (detail === "normal") {
|
|
87
|
-
|
|
94
|
+
const out = capDescription(pickFields(hit, ["title", "name", "description", "action", "installRef", "score", "curated"]), NORMAL_DESCRIPTION_LIMIT);
|
|
95
|
+
if (out.title && !out.name)
|
|
96
|
+
out.name = out.title;
|
|
97
|
+
return out;
|
|
88
98
|
}
|
|
89
99
|
return hit;
|
|
90
100
|
}
|
|
@@ -176,6 +186,10 @@ export function shapeShowOutput(result, detail, forAgent = false) {
|
|
|
176
186
|
"cwd",
|
|
177
187
|
"keys",
|
|
178
188
|
"comments",
|
|
189
|
+
// path and editable are always projected so JSON consumers can locate and
|
|
190
|
+
// edit the asset without needing --detail full (QA #7).
|
|
191
|
+
"path",
|
|
192
|
+
"editable",
|
|
179
193
|
]);
|
|
180
194
|
if (detail !== "full") {
|
|
181
195
|
return base;
|
|
@@ -183,7 +197,7 @@ export function shapeShowOutput(result, detail, forAgent = false) {
|
|
|
183
197
|
return {
|
|
184
198
|
schemaVersion: 1,
|
|
185
199
|
...base,
|
|
186
|
-
...pickFields(result, ["
|
|
200
|
+
...pickFields(result, ["editHint"]),
|
|
187
201
|
};
|
|
188
202
|
}
|
|
189
203
|
export function pickFields(source, fields) {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Pure functions — no IO.
|
|
7
7
|
*/
|
|
8
|
-
import { formatInstallAuditSummary } from "
|
|
8
|
+
import { formatInstallAuditSummary } from "../commands/install-audit";
|
|
9
9
|
export function outputJsonl(command, shaped) {
|
|
10
10
|
if (command === "search" || command === "registry-search") {
|
|
11
11
|
const r = shaped;
|
|
@@ -433,7 +433,7 @@ export function formatWikiRemovePlain(r) {
|
|
|
433
433
|
const preserved = r.preservedRaw === true;
|
|
434
434
|
const removed = Array.isArray(r.removed) ? r.removed.length : 0;
|
|
435
435
|
const base = `Removed wiki ${String(r.name ?? "?")} (${removed} path(s))`;
|
|
436
|
-
return preserved ? `${base}; preserved ${String(r.rawPath ?? "raw/")}` : base;
|
|
436
|
+
return preserved ? `${base}; raw/ preserved at ${String(r.rawPath ?? "raw/")}` : base;
|
|
437
437
|
}
|
|
438
438
|
export function formatWikiPagesPlain(r) {
|
|
439
439
|
const pages = Array.isArray(r.pages) ? r.pages : [];
|
|
@@ -1,14 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the v2 JSON registry index consumed by the `static-index` registry
|
|
3
|
+
* provider. This module emits artifacts that conform to the v2 schema; the
|
|
4
|
+
* schema itself is the input contract owned by `src/registry/providers/static-index.ts`
|
|
5
|
+
* (see v1 architecture spec §3.3 — "the v2 JSON index schema belongs to
|
|
6
|
+
* static-index"). When the schema changes, both the parser in `static-index.ts`
|
|
7
|
+
* and the JSON Schema in `docs/technical/registry-index.schema.json` must be
|
|
8
|
+
* updated together with this builder.
|
|
9
|
+
*/
|
|
1
10
|
import fs from "node:fs";
|
|
2
11
|
import os from "node:os";
|
|
3
12
|
import path from "node:path";
|
|
4
|
-
import { fetchWithRetry, jsonWithByteCap } from "
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
13
|
+
import { fetchWithRetry, jsonWithByteCap } from "../core/common";
|
|
14
|
+
import { generateMetadataFlat, loadStashFile } from "../indexer/metadata";
|
|
15
|
+
import { walkStashFlat } from "../indexer/walker";
|
|
16
|
+
import { asRecord, asString, GITHUB_API_BASE, githubHeaders } from "../integrations/github";
|
|
17
|
+
import { copyIncludedPaths, findNearestIncludeConfig } from "../sources/include";
|
|
18
|
+
import { detectStashRoot } from "../sources/providers/provider-utils";
|
|
19
|
+
import { extractTarGzSecure } from "../sources/providers/tar-utils";
|
|
7
20
|
import { parseRegistryIndex } from "./providers/static-index";
|
|
8
|
-
import { copyIncludedPaths, findNearestIncludeConfig } from "./stash-include";
|
|
9
|
-
import { detectStashRoot } from "./stash-providers/provider-utils";
|
|
10
|
-
import { extractTarGzSecure } from "./stash-providers/tar-utils";
|
|
11
|
-
import { walkStashFlat } from "./walker";
|
|
12
21
|
const DEFAULT_NPM_REGISTRY_BASE = "https://registry.npmjs.org";
|
|
13
22
|
const DEFAULT_MANUAL_ENTRIES_PATH = path.resolve("manual-entries.json");
|
|
14
23
|
const DEFAULT_OUTPUT_PATH = path.resolve("index.json");
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Generic factory-map utility.
|
|
3
3
|
*
|
|
4
4
|
* Creates a lightweight registry that maps string keys to factory functions.
|
|
5
|
-
* Both registry
|
|
6
|
-
* (
|
|
5
|
+
* Both registry/factory.ts (registry discovery) and sources/provider-factory.ts
|
|
6
|
+
* (source providers) are built on this utility.
|
|
7
7
|
*/
|
|
8
8
|
export function createProviderRegistry() {
|
|
9
9
|
const map = new Map();
|
|
@@ -14,5 +14,9 @@ export function createProviderRegistry() {
|
|
|
14
14
|
resolve(type) {
|
|
15
15
|
return map.get(type) ?? null;
|
|
16
16
|
},
|
|
17
|
+
/** Snapshot of all registered keys. Iteration order matches insertion order. */
|
|
18
|
+
list() {
|
|
19
|
+
return [...map.keys()];
|
|
20
|
+
},
|
|
17
21
|
};
|
|
18
22
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry provider factory map.
|
|
3
|
+
*
|
|
4
|
+
* Maps registry provider type identifiers (e.g. "static-index", "skills-sh")
|
|
5
|
+
* to factory functions that create RegistryProvider instances.
|
|
6
|
+
*
|
|
7
|
+
* "Registry" here refers to the kit discovery registries (static index files,
|
|
8
|
+
* skills.sh API) — not to be confused with the source provider factory map in
|
|
9
|
+
* `sources/provider-factory.ts` or the installed-source operations in
|
|
10
|
+
* `installed-stashes.ts`.
|
|
11
|
+
*
|
|
12
|
+
* Phase 6 (v1 architecture refactor): factories are now the
|
|
13
|
+
* `RegistryProviderFactory` type owned by `src/registry/providers/types.ts`.
|
|
14
|
+
* The legacy alias in `src/registry-provider.ts` is kept as a thin re-export
|
|
15
|
+
* for transitional callers and will be removed after the dust settles.
|
|
16
|
+
*/
|
|
17
|
+
import { createProviderRegistry } from "./create-provider-registry";
|
|
18
|
+
// ── Factory map ─────────────────────────────────────────────────────────────
|
|
19
|
+
const registry = createProviderRegistry();
|
|
20
|
+
export function registerProvider(type, factory) {
|
|
21
|
+
registry.register(type, factory);
|
|
22
|
+
}
|
|
23
|
+
export function resolveProviderFactory(type) {
|
|
24
|
+
return registry.resolve(type);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Iterate over all registered registry providers. Used by the orchestrator
|
|
28
|
+
* (`src/commands/registry-search.ts`) to fan out queries through the same
|
|
29
|
+
* `RegistryProvider` interface regardless of provider kind.
|
|
30
|
+
*/
|
|
31
|
+
export function listProviderTypes() {
|
|
32
|
+
return registry.list();
|
|
33
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* providers with the provider registry. This replaces the individual
|
|
6
6
|
* side-effect imports that were duplicated in registry-search.ts.
|
|
7
7
|
*
|
|
8
|
-
* Mirrors the pattern used by `
|
|
8
|
+
* Mirrors the pattern used by `sources/providers/index.ts`.
|
|
9
9
|
*/
|
|
10
10
|
import "./static-index";
|
|
11
11
|
import "./skills-sh";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { fetchWithRetry } from "
|
|
4
|
-
import { getRegistryIndexCacheDir } from "
|
|
5
|
-
import { registerProvider } from "../
|
|
3
|
+
import { fetchWithRetry } from "../../core/common";
|
|
4
|
+
import { getRegistryIndexCacheDir } from "../../core/paths";
|
|
5
|
+
import { registerProvider } from "../factory";
|
|
6
6
|
// ── Constants ───────────────────────────────────────────────────────────────
|
|
7
7
|
/** Per-query cache TTL in milliseconds (15 minutes). */
|
|
8
8
|
const QUERY_CACHE_TTL_MS = 15 * 60 * 1000;
|
|
@@ -32,6 +32,62 @@ class SkillsShProvider {
|
|
|
32
32
|
return { hits: [], warnings: [`Registry ${label}: ${message}`] };
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
+
// ── v1-spec §3.1 surface ────────────────────────────────────────────────
|
|
36
|
+
async searchKits(q) {
|
|
37
|
+
const result = await this.search({
|
|
38
|
+
query: q.text,
|
|
39
|
+
limit: q.limit ?? 20,
|
|
40
|
+
includeAssets: false,
|
|
41
|
+
});
|
|
42
|
+
return result.hits.map((hit) => ({
|
|
43
|
+
id: hit.id,
|
|
44
|
+
title: hit.title,
|
|
45
|
+
summary: hit.description,
|
|
46
|
+
installRef: hit.installRef,
|
|
47
|
+
score: hit.score,
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
async searchAssets(q) {
|
|
51
|
+
const result = await this.search({
|
|
52
|
+
query: q.text,
|
|
53
|
+
limit: q.limit ?? 20,
|
|
54
|
+
includeAssets: true,
|
|
55
|
+
});
|
|
56
|
+
return (result.assetHits ?? []).map((hit) => ({
|
|
57
|
+
kitId: hit.stash.id,
|
|
58
|
+
type: hit.assetType,
|
|
59
|
+
name: hit.assetName,
|
|
60
|
+
summary: hit.description,
|
|
61
|
+
cloneRef: hit.action.replace(/^akm add\s+/, ""),
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* skills.sh has no `getKit` API — every entry corresponds to a GitHub
|
|
66
|
+
* repository whose metadata we already include in the search result. We
|
|
67
|
+
* synthesize a manifest from the search hit when the caller knows the kit
|
|
68
|
+
* id; if not present in the most recent results, return null.
|
|
69
|
+
*/
|
|
70
|
+
async getKit(id) {
|
|
71
|
+
if (!id.startsWith("skills-sh:"))
|
|
72
|
+
return null;
|
|
73
|
+
const slug = id.slice("skills-sh:".length);
|
|
74
|
+
// Best-effort: the API gives us search-by-name; extract the leaf segment.
|
|
75
|
+
const segments = slug.split("/").filter(Boolean);
|
|
76
|
+
const leaf = segments[segments.length - 1] ?? slug;
|
|
77
|
+
const result = await this.search({ query: leaf, limit: 50, includeAssets: false });
|
|
78
|
+
const match = result.hits.find((hit) => hit.id === id);
|
|
79
|
+
if (!match)
|
|
80
|
+
return null;
|
|
81
|
+
return { id: match.id, installRef: match.installRef };
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* skills.sh entries are always GitHub repositories. Claim only refs whose
|
|
85
|
+
* parsed source is `github`; defer everything else (npm tarballs, local
|
|
86
|
+
* paths, raw git URLs) to other registries.
|
|
87
|
+
*/
|
|
88
|
+
canHandle(ref) {
|
|
89
|
+
return ref.source === "github";
|
|
90
|
+
}
|
|
35
91
|
async fetchSkills(query, limit) {
|
|
36
92
|
// Check per-query cache first
|
|
37
93
|
const cachePath = this.queryCachePath(query, limit);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { fetchWithRetry, jsonWithByteCap, toErrorMessage } from "
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { registerProvider } from "../
|
|
3
|
+
import { fetchWithRetry, jsonWithByteCap, toErrorMessage } from "../../core/common";
|
|
4
|
+
import { getRegistryIndexCacheDir } from "../../core/paths";
|
|
5
|
+
import { asString } from "../../integrations/github";
|
|
6
|
+
import { registerProvider } from "../factory";
|
|
7
7
|
// ── Constants ───────────────────────────────────────────────────────────────
|
|
8
8
|
/** Cache TTL in milliseconds (1 hour). */
|
|
9
9
|
const CACHE_TTL_MS = 60 * 60 * 1000;
|
|
@@ -18,6 +18,63 @@ class StaticIndexProvider {
|
|
|
18
18
|
}
|
|
19
19
|
async search(options) {
|
|
20
20
|
const warnings = [];
|
|
21
|
+
const allKits = await this.loadAllKits(warnings);
|
|
22
|
+
const hits = scoreKits(allKits, options.query, options.limit);
|
|
23
|
+
let assetHits;
|
|
24
|
+
if (options.includeAssets) {
|
|
25
|
+
const scored = scoreAssets(allKits, options.query, options.limit);
|
|
26
|
+
if (scored.length > 0)
|
|
27
|
+
assetHits = scored;
|
|
28
|
+
}
|
|
29
|
+
return { hits, assetHits, warnings: warnings.length > 0 ? warnings : undefined };
|
|
30
|
+
}
|
|
31
|
+
// ── v1-spec §3.1 surface ────────────────────────────────────────────────
|
|
32
|
+
async searchKits(q) {
|
|
33
|
+
const result = await this.search({
|
|
34
|
+
query: q.text,
|
|
35
|
+
limit: q.limit ?? 20,
|
|
36
|
+
includeAssets: false,
|
|
37
|
+
});
|
|
38
|
+
return result.hits.map(hitToKitResult);
|
|
39
|
+
}
|
|
40
|
+
async searchAssets(q) {
|
|
41
|
+
const result = await this.search({
|
|
42
|
+
query: q.text,
|
|
43
|
+
limit: q.limit ?? 20,
|
|
44
|
+
includeAssets: true,
|
|
45
|
+
});
|
|
46
|
+
return (result.assetHits ?? []).map(assetHitToPreview);
|
|
47
|
+
}
|
|
48
|
+
async getKit(id) {
|
|
49
|
+
const allKits = await this.loadAllKits([]);
|
|
50
|
+
const found = allKits.find(({ stash }) => stash.id === id);
|
|
51
|
+
if (!found)
|
|
52
|
+
return null;
|
|
53
|
+
const installRef = buildInstallRef(found.stash.source, found.stash.ref);
|
|
54
|
+
return {
|
|
55
|
+
id: found.stash.id,
|
|
56
|
+
installRef,
|
|
57
|
+
assets: found.stash.assets?.map((asset) => ({
|
|
58
|
+
kitId: found.stash.id,
|
|
59
|
+
type: asset.type,
|
|
60
|
+
name: asset.name,
|
|
61
|
+
summary: asset.description,
|
|
62
|
+
cloneRef: installRef,
|
|
63
|
+
})),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Static-index doesn't own a URL prefix — any `ParsedRegistryRef` could
|
|
68
|
+
* theoretically be backed by an entry in some static-index registry. We
|
|
69
|
+
* therefore claim every ref. The orchestrator picks the first matching
|
|
70
|
+
* provider, and `static-index` is registered first by `index.ts`, so this
|
|
71
|
+
* is effectively the default catch-all.
|
|
72
|
+
*/
|
|
73
|
+
canHandle(_ref) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
// ── Internals ───────────────────────────────────────────────────────────
|
|
77
|
+
async loadAllKits(warnings) {
|
|
21
78
|
const allKits = [];
|
|
22
79
|
try {
|
|
23
80
|
const index = await loadIndex(this.config);
|
|
@@ -32,16 +89,27 @@ class StaticIndexProvider {
|
|
|
32
89
|
const label = this.config.name ? `${this.config.name} (${this.config.url})` : this.config.url;
|
|
33
90
|
warnings.push(`Registry ${label}: ${toErrorMessage(err)}`);
|
|
34
91
|
}
|
|
35
|
-
|
|
36
|
-
let assetHits;
|
|
37
|
-
if (options.includeAssets) {
|
|
38
|
-
const scored = scoreAssets(allKits, options.query, options.limit);
|
|
39
|
-
if (scored.length > 0)
|
|
40
|
-
assetHits = scored;
|
|
41
|
-
}
|
|
42
|
-
return { hits, assetHits, warnings: warnings.length > 0 ? warnings : undefined };
|
|
92
|
+
return allKits;
|
|
43
93
|
}
|
|
44
94
|
}
|
|
95
|
+
function hitToKitResult(hit) {
|
|
96
|
+
return {
|
|
97
|
+
id: hit.id,
|
|
98
|
+
title: hit.title,
|
|
99
|
+
summary: hit.description,
|
|
100
|
+
installRef: hit.installRef,
|
|
101
|
+
score: hit.score,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function assetHitToPreview(hit) {
|
|
105
|
+
return {
|
|
106
|
+
kitId: hit.stash.id,
|
|
107
|
+
type: hit.assetType,
|
|
108
|
+
name: hit.assetName,
|
|
109
|
+
summary: hit.description,
|
|
110
|
+
cloneRef: hit.action.replace(/^akm add\s+/, ""),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
45
113
|
// ── Self-register ───────────────────────────────────────────────────────────
|
|
46
114
|
registerProvider("static-index", (config) => new StaticIndexProvider(config));
|
|
47
115
|
// ── Index loading with cache ────────────────────────────────────────────────
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry provider interface (v1 architecture spec §3.1).
|
|
3
|
+
*
|
|
4
|
+
* A `RegistryProvider` is a read-only catalog that lists installable kits and
|
|
5
|
+
* (optionally) previews assets within them. It is *not* a `SourceProvider`:
|
|
6
|
+
* registry providers do not materialise files to disk — they only answer
|
|
7
|
+
* discovery queries.
|
|
8
|
+
*
|
|
9
|
+
* The two built-in registry providers at v1 are:
|
|
10
|
+
*
|
|
11
|
+
* - `static-index` — reads the v2 JSON index schema (the official akm registry
|
|
12
|
+
* and any static-hosted team registry). The v2 schema is owned by this
|
|
13
|
+
* provider, not by core akm.
|
|
14
|
+
* - `skills-sh` — wraps the skills.sh REST API.
|
|
15
|
+
*
|
|
16
|
+
* Context Hub is **not** a registry provider — it is an ordinary git repository
|
|
17
|
+
* recommended via the official static-index registry (see CLAUDE.md).
|
|
18
|
+
*
|
|
19
|
+
* Note: the simple `search()` method is the v0.6 surface and remains the
|
|
20
|
+
* primary entry point used by the orchestrator. The `searchKits` /
|
|
21
|
+
* `searchAssets` / `getKit` / `canHandle` methods are the v1-spec contract
|
|
22
|
+
* (§3.1) which built-in providers also implement so the orchestrator can be
|
|
23
|
+
* iterated cleanly post-Phase 6 without reaching into provider-specific shapes.
|
|
24
|
+
*/
|
|
25
|
+
export {};
|
|
@@ -3,9 +3,9 @@ import fs from "node:fs";
|
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
|
-
import { fetchWithRetry, jsonWithByteCap } from "
|
|
7
|
-
import { UsageError } from "
|
|
8
|
-
import { asRecord, asString, GITHUB_API_BASE, githubHeaders } from "
|
|
6
|
+
import { fetchWithRetry, jsonWithByteCap } from "../core/common";
|
|
7
|
+
import { UsageError } from "../core/errors";
|
|
8
|
+
import { asRecord, asString, GITHUB_API_BASE, githubHeaders } from "../integrations/github";
|
|
9
9
|
/**
|
|
10
10
|
* Validate that a URL is safe to pass to git.
|
|
11
11
|
* Allowlists https:, http:, ssh:, git: schemes and git@ SSH shorthand.
|
|
@@ -91,30 +91,3 @@ export function detectAgentPlatforms() {
|
|
|
91
91
|
path: path.join(home, p.relPath),
|
|
92
92
|
}));
|
|
93
93
|
}
|
|
94
|
-
// ── OpenViking Detection ────────────────────────────────────────────────────
|
|
95
|
-
/**
|
|
96
|
-
* Check if an OpenViking server is reachable at the given URL.
|
|
97
|
-
* Uses the lightweight /api/v1/fs/stat endpoint (GET) rather than
|
|
98
|
-
* the search endpoint which requires a running search index.
|
|
99
|
-
*/
|
|
100
|
-
export async function detectOpenViking(url) {
|
|
101
|
-
const normalized = url.replace(/\/+$/, "");
|
|
102
|
-
try {
|
|
103
|
-
// Any HTTP response (even non-2xx) from the API endpoint means the server is reachable.
|
|
104
|
-
// Only network errors / timeouts indicate the server is truly unavailable.
|
|
105
|
-
await fetch(`${normalized}/api/v1/fs/stat?uri=${encodeURIComponent("viking://")}`, {
|
|
106
|
-
signal: AbortSignal.timeout(5000),
|
|
107
|
-
});
|
|
108
|
-
return { available: true, url: normalized };
|
|
109
|
-
}
|
|
110
|
-
catch {
|
|
111
|
-
// stat endpoint unreachable — try root URL as fallback
|
|
112
|
-
try {
|
|
113
|
-
await fetch(normalized, { signal: AbortSignal.timeout(5000) });
|
|
114
|
-
return { available: true, url: normalized };
|
|
115
|
-
}
|
|
116
|
-
catch {
|
|
117
|
-
return { available: false, url: normalized };
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { IS_WINDOWS } from "
|
|
4
|
+
import { IS_WINDOWS } from "../core/common";
|
|
5
5
|
import { RG_BINARY, resolveRg } from "./ripgrep-resolve";
|
|
6
6
|
/**
|
|
7
7
|
* Platform and architecture detection for ripgrep binary downloads.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { IS_WINDOWS } from "
|
|
4
|
-
import { getBinDir } from "
|
|
3
|
+
import { IS_WINDOWS } from "../core/common";
|
|
4
|
+
import { getBinDir } from "../core/paths";
|
|
5
5
|
export const RG_BINARY = IS_WINDOWS ? "rg.exe" : "rg";
|
|
6
6
|
function canExecute(filePath) {
|
|
7
7
|
if (!fs.existsSync(filePath))
|