akm-cli 0.6.0-rc1 → 0.6.0-rc2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +9 -9
  3. package/dist/cli.js +181 -111
  4. package/dist/{completions.js → commands/completions.js} +1 -1
  5. package/dist/{config-cli.js → commands/config-cli.js} +109 -11
  6. package/dist/{curate.js → commands/curate.js} +8 -3
  7. package/dist/{info.js → commands/info.js} +15 -9
  8. package/dist/{init.js → commands/init.js} +4 -4
  9. package/dist/{install-audit.js → commands/install-audit.js} +4 -7
  10. package/dist/{installed-stashes.js → commands/installed-stashes.js} +77 -31
  11. package/dist/{migration-help.js → commands/migration-help.js} +2 -2
  12. package/dist/{registry-search.js → commands/registry-search.js} +8 -6
  13. package/dist/{remember.js → commands/remember.js} +55 -49
  14. package/dist/{stash-search.js → commands/search.js} +28 -69
  15. package/dist/{self-update.js → commands/self-update.js} +3 -3
  16. package/dist/{stash-show.js → commands/show.js} +103 -78
  17. package/dist/{stash-add.js → commands/source-add.js} +42 -32
  18. package/dist/{stash-clone.js → commands/source-clone.js} +12 -10
  19. package/dist/{stash-source-manage.js → commands/source-manage.js} +24 -24
  20. package/dist/{vault.js → commands/vault.js} +43 -0
  21. package/dist/{stash-ref.js → core/asset-ref.js} +4 -4
  22. package/dist/{asset-registry.js → core/asset-registry.js} +1 -1
  23. package/dist/{asset-spec.js → core/asset-spec.js} +1 -1
  24. package/dist/{config.js → core/config.js} +79 -31
  25. package/dist/core/errors.js +90 -0
  26. package/dist/{frontmatter.js → core/frontmatter.js} +5 -3
  27. package/dist/core/write-source.js +280 -0
  28. package/dist/{db-search.js → indexer/db-search.js} +25 -19
  29. package/dist/{db.js → indexer/db.js} +70 -47
  30. package/dist/{file-context.js → indexer/file-context.js} +3 -3
  31. package/dist/{indexer.js → indexer/indexer.js} +123 -31
  32. package/dist/{manifest.js → indexer/manifest.js} +10 -10
  33. package/dist/{matchers.js → indexer/matchers.js} +3 -6
  34. package/dist/{metadata.js → indexer/metadata.js} +9 -5
  35. package/dist/{search-source.js → indexer/search-source.js} +52 -41
  36. package/dist/{semantic-status.js → indexer/semantic-status.js} +2 -2
  37. package/dist/{walker.js → indexer/walker.js} +1 -1
  38. package/dist/{lockfile.js → integrations/lockfile.js} +1 -1
  39. package/dist/{llm-client.js → llm/client.js} +1 -1
  40. package/dist/{embedders → llm/embedders}/local.js +2 -2
  41. package/dist/{embedders → llm/embedders}/remote.js +1 -1
  42. package/dist/{embedders → llm/embedders}/types.js +1 -1
  43. package/dist/{metadata-enhance.js → llm/metadata-enhance.js} +2 -2
  44. package/dist/{cli-hints.js → output/cli-hints.js} +1 -0
  45. package/dist/{output-context.js → output/context.js} +21 -3
  46. package/dist/{renderers.js → output/renderers.js} +9 -65
  47. package/dist/{output-shapes.js → output/shapes.js} +18 -4
  48. package/dist/{output-text.js → output/text.js} +1 -1
  49. package/dist/{registry-build-index.js → registry/build-index.js} +16 -7
  50. package/dist/{create-provider-registry.js → registry/create-provider-registry.js} +6 -2
  51. package/dist/registry/factory.js +33 -0
  52. package/dist/{origin-resolve.js → registry/origin-resolve.js} +1 -1
  53. package/dist/{providers → registry/providers}/index.js +1 -1
  54. package/dist/{providers → registry/providers}/skills-sh.js +59 -3
  55. package/dist/{providers → registry/providers}/static-index.js +80 -12
  56. package/dist/registry/providers/types.js +25 -0
  57. package/dist/{registry-resolve.js → registry/resolve.js} +3 -3
  58. package/dist/{detect.js → setup/detect.js} +0 -27
  59. package/dist/{ripgrep-install.js → setup/ripgrep-install.js} +1 -1
  60. package/dist/{ripgrep-resolve.js → setup/ripgrep-resolve.js} +2 -2
  61. package/dist/{setup.js → setup/setup.js} +16 -56
  62. package/dist/{stash-include.js → sources/include.js} +1 -1
  63. package/dist/sources/provider-factory.js +36 -0
  64. package/dist/sources/provider.js +21 -0
  65. package/dist/sources/providers/filesystem.js +35 -0
  66. package/dist/{stash-providers → sources/providers}/git.js +53 -64
  67. package/dist/{stash-providers → sources/providers}/index.js +3 -4
  68. package/dist/sources/providers/install-types.js +14 -0
  69. package/dist/{stash-providers → sources/providers}/npm.js +42 -41
  70. package/dist/{stash-providers → sources/providers}/provider-utils.js +3 -3
  71. package/dist/{stash-providers → sources/providers}/sync-from-ref.js +2 -2
  72. package/dist/{stash-providers → sources/providers}/tar-utils.js +11 -8
  73. package/dist/{stash-providers → sources/providers}/website.js +29 -65
  74. package/dist/{stash-resolve.js → sources/resolve.js} +8 -7
  75. package/dist/{wiki.js → wiki/wiki.js} +12 -11
  76. package/dist/{workflow-authoring.js → workflows/authoring.js} +37 -14
  77. package/dist/{workflow-cli.js → workflows/cli.js} +2 -1
  78. package/dist/{workflow-db.js → workflows/db.js} +1 -1
  79. package/dist/workflows/document-cache.js +20 -0
  80. package/dist/workflows/parser.js +379 -0
  81. package/dist/workflows/renderer.js +78 -0
  82. package/dist/{workflow-runs.js → workflows/runs.js} +72 -28
  83. package/dist/workflows/schema.js +11 -0
  84. package/dist/workflows/validator.js +48 -0
  85. package/docs/migration/release-notes/0.6.0.md +69 -23
  86. package/package.json +1 -1
  87. package/dist/errors.js +0 -45
  88. package/dist/llm.js +0 -16
  89. package/dist/registry-factory.js +0 -19
  90. package/dist/ripgrep.js +0 -2
  91. package/dist/stash-provider-factory.js +0 -35
  92. package/dist/stash-provider.js +0 -3
  93. package/dist/stash-providers/filesystem.js +0 -71
  94. package/dist/stash-providers/openviking.js +0 -348
  95. package/dist/stash-types.js +0 -1
  96. package/dist/workflow-markdown.js +0 -260
  97. /package/dist/{common.js → core/common.js} +0 -0
  98. /package/dist/{markdown.js → core/markdown.js} +0 -0
  99. /package/dist/{paths.js → core/paths.js} +0 -0
  100. /package/dist/{warn.js → core/warn.js} +0 -0
  101. /package/dist/{search-fields.js → indexer/search-fields.js} +0 -0
  102. /package/dist/{usage-events.js → indexer/usage-events.js} +0 -0
  103. /package/dist/{github.js → integrations/github.js} +0 -0
  104. /package/dist/{embedder.js → llm/embedder.js} +0 -0
  105. /package/dist/{embedders → llm/embedders}/cache.js +0 -0
  106. /package/dist/{registry-provider.js → registry/types.js} +0 -0
  107. /package/dist/{setup-steps.js → setup/steps.js} +0 -0
  108. /package/dist/{registry-types.js → sources/types.js} +0 -0
@@ -1,8 +1,8 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { fetchWithRetry } from "../common";
4
- import { getRegistryIndexCacheDir } from "../paths";
5
- import { registerProvider } from "../registry-factory";
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 "../common";
4
- import { asString } from "../github";
5
- import { getRegistryIndexCacheDir } from "../paths";
6
- import { registerProvider } from "../registry-factory";
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
- const hits = scoreKits(allKits, options.query, options.limit);
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 "./common";
7
- import { UsageError } from "./errors";
8
- import { asRecord, asString, GITHUB_API_BASE, githubHeaders } from "./github";
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 "./common";
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 "./common";
4
- import { getBinDir } from "./paths";
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))
@@ -7,17 +7,17 @@
7
7
  */
8
8
  import path from "node:path";
9
9
  import * as p from "@clack/prompts";
10
- import { isHttpUrl } from "./common";
11
- import { DEFAULT_CONFIG, getConfigPath, loadUserConfig, saveConfig } from "./config";
12
- import { closeDatabase, isVecAvailable, openDatabase } from "./db";
13
- import { detectAgentPlatforms, detectOllama, detectOpenViking } from "./detect";
14
- import { checkEmbeddingAvailability, DEFAULT_LOCAL_MODEL, isTransformersAvailable } from "./embedder";
15
- import { akmIndex } from "./indexer";
16
- import { akmInit } from "./init";
17
- import { probeLlmCapabilities } from "./llm";
18
- import { getDefaultStashDir } from "./paths";
19
- import { clearSemanticStatus, deriveSemanticProviderFingerprint, writeSemanticStatus } from "./semantic-status";
20
- import { createSetupContext, runSetupSteps } from "./setup-steps";
10
+ import { akmInit } from "../commands/init";
11
+ import { isHttpUrl } from "../core/common";
12
+ import { DEFAULT_CONFIG, getConfigPath, loadUserConfig, saveConfig } from "../core/config";
13
+ import { getDefaultStashDir } from "../core/paths";
14
+ import { closeDatabase, isVecAvailable, openDatabase } from "../indexer/db";
15
+ import { akmIndex } from "../indexer/indexer";
16
+ import { clearSemanticStatus, deriveSemanticProviderFingerprint, writeSemanticStatus, } from "../indexer/semantic-status";
17
+ import { probeLlmCapabilities } from "../llm/client";
18
+ import { checkEmbeddingAvailability, DEFAULT_LOCAL_MODEL, isTransformersAvailable } from "../llm/embedder";
19
+ import { detectAgentPlatforms, detectOllama } from "./detect";
20
+ import { createSetupContext, runSetupSteps } from "./steps";
21
21
  // ── Constants ───────────────────────────────────────────────────────────────
22
22
  /**
23
23
  * Recommended GitHub repositories shown during setup.
@@ -141,7 +141,7 @@ async function prepareSemanticSearchAssets(config) {
141
141
  const spin = p.spinner();
142
142
  spin.start("Installing @huggingface/transformers...");
143
143
  try {
144
- const pkgRoot = path.resolve(import.meta.dir, "..");
144
+ const pkgRoot = path.resolve(import.meta.dir, "../..");
145
145
  const proc = Bun.spawn(["bun", "add", "@huggingface/transformers"], {
146
146
  cwd: pkgRoot,
147
147
  stdout: "pipe",
@@ -500,8 +500,8 @@ async function stepRegistries(current) {
500
500
  /**
501
501
  * @internal Exported for testing only.
502
502
  */
503
- export async function stepStashSources(current) {
504
- const stashes = [...(current.stashes ?? [])];
503
+ export async function stepAddSources(current) {
504
+ const stashes = [...(current.sources ?? current.stashes ?? [])];
505
505
  if (stashes.length > 0) {
506
506
  p.log.info(`You have ${stashes.length} existing stash source(s).`);
507
507
  }
@@ -547,7 +547,6 @@ export async function stepStashSources(current) {
547
547
  const action = await prompt(() => p.select({
548
548
  message: "Add another stash source?",
549
549
  options: [
550
- { value: "openviking", label: "OpenViking server", hint: "remote stash" },
551
550
  { value: "github-repo", label: "GitHub repository", hint: "custom URL" },
552
551
  { value: "filesystem", label: "Filesystem path", hint: "local directory" },
553
552
  { value: "done", label: "Done — no more sources" },
@@ -557,45 +556,6 @@ export async function stepStashSources(current) {
557
556
  addMore = false;
558
557
  break;
559
558
  }
560
- if (action === "openviking") {
561
- const url = await promptOrBack(() => p.text({
562
- message: "Enter the OpenViking server URL:",
563
- placeholder: "https://your-openviking-server.example.com",
564
- validate: (v) => {
565
- if (!v?.trim())
566
- return "URL cannot be empty";
567
- if (!v.startsWith("http://") && !v.startsWith("https://"))
568
- return "URL must start with http:// or https://";
569
- },
570
- }));
571
- if (url === null)
572
- continue;
573
- const spin = p.spinner();
574
- spin.start("Checking OpenViking server...");
575
- const result = await detectOpenViking(url.trim());
576
- if (result.available) {
577
- spin.stop("Server is reachable");
578
- }
579
- else {
580
- spin.stop("Server not reachable — adding anyway (it may be temporarily down)");
581
- }
582
- const name = await promptOrBack(() => p.text({
583
- message: "Give this stash a name (optional):",
584
- placeholder: "my-openviking",
585
- }));
586
- if (name === null)
587
- continue;
588
- // Use the normalized URL from detection (trailing slashes stripped)
589
- const entry = { type: "openviking", url: result.url };
590
- if (name.trim())
591
- entry.name = name.trim();
592
- if (!stashes.some((s) => s.url === entry.url)) {
593
- stashes.push(entry);
594
- }
595
- else {
596
- p.log.warn("This URL is already configured.");
597
- }
598
- }
599
559
  if (action === "github-repo") {
600
560
  const url = await promptOrBack(() => p.text({
601
561
  message: "Enter the GitHub repository URL:",
@@ -761,14 +721,14 @@ export function buildSetupSteps(options) {
761
721
  id: "stash-sources",
762
722
  label: "Stash Sources",
763
723
  async run(ctx) {
764
- const stashes = await stepStashSources(ctx.config);
724
+ const stashes = await stepAddSources(ctx.config);
765
725
  const platforms = await stepAgentPlatforms(ctx.config);
766
726
  const merged = [...stashes];
767
727
  for (const ps of platforms) {
768
728
  if (!merged.some((s) => s.path === ps.path))
769
729
  merged.push(ps);
770
730
  }
771
- ctx.apply({ stashes: merged.length > 0 ? merged : undefined });
731
+ ctx.apply({ sources: merged.length > 0 ? merged : undefined });
772
732
  },
773
733
  },
774
734
  ];
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { isWithin } from "./common";
3
+ import { isWithin } from "../core/common";
4
4
  // ── Helpers ─────────────────────────────────────────────────────────────────
5
5
  /** Key to check in package.json for akm include configuration. */
6
6
  const INCLUDE_CONFIG_KEYS = ["akm"];
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Source provider factory map.
3
+ *
4
+ * Maps source kind identifiers (e.g. "filesystem", "git", "website", "npm")
5
+ * to factory functions that build {@link SourceProvider} instances from a
6
+ * {@link SourceConfigEntry}.
7
+ *
8
+ * Distinct from the registry-discovery factory (`registry/factory.ts`).
9
+ * Both share `create-provider-registry.ts` for the underlying string→factory
10
+ * map.
11
+ */
12
+ import { createProviderRegistry } from "../registry/create-provider-registry";
13
+ // ── Factory map ─────────────────────────────────────────────────────────────
14
+ const registry = createProviderRegistry();
15
+ export function registerSourceProvider(type, factory) {
16
+ registry.register(type, factory);
17
+ }
18
+ export function resolveSourceProviderFactory(type) {
19
+ return registry.resolve(type);
20
+ }
21
+ /**
22
+ * Build a {@link SourceProvider} for every enabled source in the config that
23
+ * has a registered factory.
24
+ */
25
+ export function resolveSourceProviders(config) {
26
+ const providers = [];
27
+ for (const entry of config.sources ?? config.stashes ?? []) {
28
+ if (entry.enabled === false)
29
+ continue;
30
+ const factory = registry.resolve(entry.type);
31
+ if (factory) {
32
+ providers.push(factory(entry));
33
+ }
34
+ }
35
+ return providers;
36
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * SourceProvider — minimal v1 interface (spec §2.1).
3
+ *
4
+ * A SourceProvider gets files into a directory. The indexer walks `path()`
5
+ * and reads files from disk. Search and show go through the indexer, not
6
+ * through provider methods.
7
+ *
8
+ * Three required members + one optional:
9
+ * - name configured source name
10
+ * - kind "filesystem" | "git" | "website" | "npm"
11
+ * - init(ctx) called once after construction
12
+ * - path() the directory the indexer walks (stable for instance lifetime)
13
+ * - sync?() refresh the directory from upstream (no-op for filesystem)
14
+ *
15
+ * All other writing/reading concerns live outside this interface:
16
+ * - Writes: src/core/write-source.ts (Phase 5)
17
+ * - Reads: src/indexer.ts (Phase 4)
18
+ * - Install: src/sources/providers/sync-from-ref.ts (install-time helpers,
19
+ * separate from configured-source plumbing)
20
+ */
21
+ export {};
@@ -0,0 +1,35 @@
1
+ import { resolveStashDir } from "../../core/common";
2
+ import { ConfigError } from "../../core/errors";
3
+ import { registerSourceProvider } from "../provider-factory";
4
+ /**
5
+ * Filesystem source — points at a directory the user already manages.
6
+ *
7
+ * Implements the v1 {@link SourceProvider} interface (spec §2.1, §2.4):
8
+ * just `{ name, kind, init, path }`. No `sync()` — content is the user's
9
+ * own directory, never refreshed by akm.
10
+ */
11
+ class FilesystemSourceProvider {
12
+ kind = "filesystem";
13
+ name;
14
+ #stashDir;
15
+ constructor(entry) {
16
+ if (entry.type !== "filesystem") {
17
+ throw new ConfigError(`FilesystemSourceProvider invoked with type="${entry.type}"`);
18
+ }
19
+ this.#stashDir = entry.path ?? resolveStashDir();
20
+ if (!this.#stashDir) {
21
+ throw new ConfigError("filesystem source requires a `path`");
22
+ }
23
+ this.name = entry.name ?? this.#stashDir;
24
+ }
25
+ async init(_ctx) {
26
+ // Filesystem sources resolve their path eagerly in the constructor;
27
+ // init has nothing to do beyond letting the registry know we're ready.
28
+ }
29
+ path() {
30
+ return this.#stashDir;
31
+ }
32
+ }
33
+ // ── Self-register ───────────────────────────────────────────────────────────
34
+ registerSourceProvider("filesystem", (config) => new FilesystemSourceProvider(config));
35
+ export { FilesystemSourceProvider };