akm-cli 0.5.0 → 0.6.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.
Files changed (74) hide show
  1. package/CHANGELOG.md +32 -5
  2. package/dist/asset-registry.js +29 -5
  3. package/dist/asset-spec.js +12 -5
  4. package/dist/cli-hints.js +300 -0
  5. package/dist/cli.js +218 -1357
  6. package/dist/common.js +147 -50
  7. package/dist/config.js +224 -13
  8. package/dist/create-provider-registry.js +1 -1
  9. package/dist/curate.js +258 -0
  10. package/dist/{local-search.js → db-search.js} +30 -19
  11. package/dist/db.js +168 -62
  12. package/dist/embedder.js +49 -273
  13. package/dist/embedders/cache.js +47 -0
  14. package/dist/embedders/local.js +152 -0
  15. package/dist/embedders/remote.js +121 -0
  16. package/dist/embedders/types.js +39 -0
  17. package/dist/errors.js +14 -3
  18. package/dist/frontmatter.js +61 -7
  19. package/dist/indexer.js +38 -7
  20. package/dist/info.js +2 -2
  21. package/dist/install-audit.js +16 -1
  22. package/dist/{installed-kits.js → installed-stashes.js} +48 -22
  23. package/dist/llm-client.js +92 -0
  24. package/dist/llm.js +14 -126
  25. package/dist/lockfile.js +28 -1
  26. package/dist/matchers.js +1 -1
  27. package/dist/metadata-enhance.js +53 -0
  28. package/dist/migration-help.js +75 -44
  29. package/dist/output-context.js +77 -0
  30. package/dist/output-shapes.js +198 -0
  31. package/dist/output-text.js +520 -0
  32. package/dist/paths.js +4 -4
  33. package/dist/providers/index.js +11 -0
  34. package/dist/providers/skills-sh.js +1 -1
  35. package/dist/providers/static-index.js +47 -45
  36. package/dist/registry-build-index.js +36 -29
  37. package/dist/registry-factory.js +2 -2
  38. package/dist/registry-resolve.js +8 -4
  39. package/dist/registry-search.js +62 -5
  40. package/dist/remember.js +172 -0
  41. package/dist/renderers.js +52 -0
  42. package/dist/search-source.js +73 -42
  43. package/dist/setup-steps.js +45 -0
  44. package/dist/setup.js +149 -76
  45. package/dist/stash-add.js +94 -38
  46. package/dist/stash-clone.js +4 -4
  47. package/dist/stash-provider-factory.js +2 -2
  48. package/dist/stash-provider.js +3 -1
  49. package/dist/stash-providers/filesystem.js +31 -1
  50. package/dist/stash-providers/git.js +209 -8
  51. package/dist/stash-providers/index.js +1 -0
  52. package/dist/stash-providers/npm.js +159 -0
  53. package/dist/stash-providers/provider-utils.js +162 -0
  54. package/dist/stash-providers/sync-from-ref.js +45 -0
  55. package/dist/stash-providers/tar-utils.js +151 -0
  56. package/dist/stash-providers/website.js +80 -4
  57. package/dist/stash-resolve.js +5 -5
  58. package/dist/stash-search.js +4 -4
  59. package/dist/stash-show.js +3 -3
  60. package/dist/wiki.js +6 -6
  61. package/dist/workflow-authoring.js +12 -4
  62. package/dist/workflow-markdown.js +9 -0
  63. package/dist/workflow-runs.js +12 -2
  64. package/docs/README.md +30 -0
  65. package/docs/migration/release-notes/0.0.13.md +4 -0
  66. package/docs/migration/release-notes/0.1.0.md +6 -0
  67. package/docs/migration/release-notes/0.2.0.md +6 -0
  68. package/docs/migration/release-notes/0.3.0.md +5 -0
  69. package/docs/migration/release-notes/0.5.0.md +6 -0
  70. package/docs/migration/release-notes/0.6.0.md +29 -0
  71. package/docs/migration/release-notes/README.md +21 -0
  72. package/package.json +3 -2
  73. package/dist/registry-install.js +0 -532
  74. /package/dist/{kit-include.js → stash-include.js} +0 -0
@@ -30,20 +30,20 @@ function resolveInTypeDir(stashDir, typeDir, type, name) {
30
30
  const resolvedRoot = resolveAndValidateTypeRoot(root, type, name);
31
31
  const resolvedTarget = path.resolve(target);
32
32
  if (!isWithin(resolvedTarget, resolvedRoot)) {
33
- throw new UsageError("Ref resolves outside the stash root.");
33
+ throw new UsageError("Ref resolves outside the stash root.", "PATH_ESCAPE_VIOLATION");
34
34
  }
35
35
  if (!fs.existsSync(resolvedTarget) || !fs.statSync(resolvedTarget).isFile()) {
36
- throw new NotFoundError(`Stash asset not found for ref: ${type}:${name}`);
36
+ throw new NotFoundError(`Stash asset not found for ref: ${type}:${name}`, "ASSET_NOT_FOUND");
37
37
  }
38
38
  const realTarget = fs.realpathSync(resolvedTarget);
39
39
  if (!isWithin(realTarget, resolvedRoot)) {
40
- throw new UsageError("Ref resolves outside the stash root.");
40
+ throw new UsageError("Ref resolves outside the stash root.", "PATH_ESCAPE_VIOLATION");
41
41
  }
42
42
  if (!isRelevantAssetFile(type, path.basename(resolvedTarget))) {
43
43
  if (type === "script") {
44
44
  throw new NotFoundError("Script ref must resolve to a file with a supported script extension. Refer to the akm documentation for the complete list of supported script extensions.");
45
45
  }
46
- throw new NotFoundError(`Stash asset not found for ref: ${type}:${name}`);
46
+ throw new NotFoundError(`Stash asset not found for ref: ${type}:${name}`, "ASSET_NOT_FOUND");
47
47
  }
48
48
  return realTarget;
49
49
  }
@@ -77,7 +77,7 @@ async function resolveByCanonicalName(stashDir, type, name) {
77
77
  const realTarget = fs.realpathSync(ctx.absPath);
78
78
  const resolvedRoot = fs.realpathSync(stashDir);
79
79
  if (!isWithin(realTarget, resolvedRoot)) {
80
- throw new UsageError("Ref resolves outside the stash root.");
80
+ throw new UsageError("Ref resolves outside the stash root.", "PATH_ESCAPE_VIOLATION");
81
81
  }
82
82
  return realTarget;
83
83
  }
@@ -1,6 +1,6 @@
1
1
  import { loadConfig } from "./config";
2
2
  import { closeDatabase, openDatabase } from "./db";
3
- import { searchLocal } from "./local-search";
3
+ import { searchLocal } from "./db-search";
4
4
  import { resolveStashProviders } from "./stash-provider-factory";
5
5
  // Eagerly import stash providers to trigger self-registration
6
6
  import "./stash-providers/index";
@@ -36,9 +36,9 @@ export async function akmSearch(input) {
36
36
  // stash root. Safe because the empty-sources case is handled above.
37
37
  const stashDir = sources[0].path;
38
38
  // Resolve additional stash providers (e.g. OpenViking) from config.
39
- // Exclude filesystem (handled by resolveStashSources) and context-hub/github
40
- // (content now indexed through the unified FTS5 pipeline).
41
- const additionalStashProviders = resolveStashProviders(config).filter((p) => p.type !== "filesystem" && p.type !== "context-hub" && p.type !== "git");
39
+ // Exclude filesystem (handled by resolveStashSources) and git (content
40
+ // now indexed through the unified FTS5 pipeline).
41
+ const additionalStashProviders = resolveStashProviders(config).filter((p) => p.type !== "filesystem" && p.type !== "git");
42
42
  const localResult = source === "registry"
43
43
  ? undefined
44
44
  : await searchLocal({
@@ -66,14 +66,14 @@ function resolveRegisteredWikiAssetPath(wikiRoot, wikiName, assetName) {
66
66
  const candidate = path.resolve(wikiRoot, `${pageName}.md`);
67
67
  const resolvedRoot = fs.realpathSync(wikiRoot);
68
68
  if (!candidate.startsWith(resolvedRoot + path.sep)) {
69
- throw new UsageError("Ref resolves outside the stash root.");
69
+ throw new UsageError("Ref resolves outside the stash root.", "PATH_ESCAPE_VIOLATION");
70
70
  }
71
71
  if (!fs.existsSync(candidate) || !fs.statSync(candidate).isFile()) {
72
72
  throw new NotFoundError(`Stash asset not found for ref: wiki:${assetName}`);
73
73
  }
74
74
  const realTarget = fs.realpathSync(candidate);
75
75
  if (!realTarget.startsWith(resolvedRoot + path.sep)) {
76
- throw new UsageError("Ref resolves outside the stash root.");
76
+ throw new UsageError("Ref resolves outside the stash root.", "PATH_ESCAPE_VIOLATION");
77
77
  }
78
78
  return realTarget;
79
79
  }
@@ -203,7 +203,7 @@ export async function showLocal(input) {
203
203
  if (!assetPath && parsed.origin && searchSources.length === 0) {
204
204
  const installCmd = `akm add ${parsed.origin}`;
205
205
  throw new NotFoundError(`Stash asset not found for ref: ${displayType}:${parsed.name}. ` +
206
- `Kit "${parsed.origin}" is not installed. Run: ${installCmd}`);
206
+ `Stash "${parsed.origin}" is not installed. Run: ${installCmd}`);
207
207
  }
208
208
  if (!assetPath) {
209
209
  throw (lastError ??
package/dist/wiki.js CHANGED
@@ -82,17 +82,17 @@ export function resolveWikiSource(stashDir, name) {
82
82
  const external = registeredWikiSources(stashDir).find((source) => source.name === name);
83
83
  if (external)
84
84
  return external;
85
- throw new NotFoundError(wikiNotFoundMessage(name));
85
+ throw new NotFoundError(wikiNotFoundMessage(name), "STASH_NOT_FOUND");
86
86
  }
87
87
  export function ensureWikiNameAvailable(stashDir, name) {
88
88
  validateWikiName(name);
89
89
  const wikiDir = resolveWikiDir(stashDir, name);
90
90
  if (fs.existsSync(wikiDir)) {
91
- throw new UsageError(`Wiki already exists: ${name}.`);
91
+ throw new UsageError(`Wiki already exists: ${name}.`, "RESOURCE_ALREADY_EXISTS");
92
92
  }
93
93
  const external = registeredWikiSources(stashDir).find((source) => source.name === name);
94
94
  if (external) {
95
- throw new UsageError(`Wiki already registered: ${name}.`);
95
+ throw new UsageError(`Wiki already registered: ${name}.`, "RESOURCE_ALREADY_EXISTS");
96
96
  }
97
97
  }
98
98
  /**
@@ -305,7 +305,7 @@ export function showWiki(stashDir, name) {
305
305
  export function createWiki(stashDir, name) {
306
306
  const existing = registeredWikiSources(stashDir).find((source) => source.name === name);
307
307
  if (existing) {
308
- throw new UsageError(`Wiki already registered: ${name}.`);
308
+ throw new UsageError(`Wiki already registered: ${name}.`, "RESOURCE_ALREADY_EXISTS");
309
309
  }
310
310
  const wikiDir = resolveWikiDir(stashDir, name);
311
311
  fs.mkdirSync(wikiDir, { recursive: true });
@@ -373,11 +373,11 @@ export function removeWiki(stashDir, name, options = {}) {
373
373
  };
374
374
  }
375
375
  if (!fs.existsSync(wikiDir)) {
376
- throw new NotFoundError(`Wiki not found: ${name}.`);
376
+ throw new NotFoundError(`Wiki not found: ${name}.`, "STASH_NOT_FOUND");
377
377
  }
378
378
  const wikisRoot = resolveWikisRoot(stashDir);
379
379
  if (!isWithin(wikiDir, wikisRoot)) {
380
- throw new UsageError(`Refusing to remove a path outside the wikis root: ${wikiDir}`);
380
+ throw new UsageError(`Refusing to remove a path outside the wikis root: ${wikiDir}`, "PATH_ESCAPE_VIOLATION");
381
381
  }
382
382
  const removed = [];
383
383
  const rawDir = path.join(wikiDir, RAW_SUBDIR);
@@ -3,6 +3,7 @@ import path from "node:path";
3
3
  import { resolveAssetPathFromName } from "./asset-spec";
4
4
  import { isWithin, resolveStashDir } from "./common";
5
5
  import { UsageError } from "./errors";
6
+ import { warn } from "./warn";
6
7
  import { parseWorkflowMarkdown, WorkflowValidationError } from "./workflow-markdown";
7
8
  const DEFAULT_WORKFLOW_TEMPLATE = renderWorkflowTemplate({
8
9
  title: "Example Workflow",
@@ -32,13 +33,13 @@ export function createWorkflowAsset(input) {
32
33
  const normalizedName = normalizeWorkflowName(input.name);
33
34
  const assetPath = resolveAssetPathFromName("workflow", typeRoot, normalizedName);
34
35
  if (!isWithin(assetPath, typeRoot)) {
35
- throw new UsageError(`Resolved workflow path escapes the stash: "${normalizedName}"`);
36
+ throw new UsageError(`Resolved workflow path escapes the stash: "${normalizedName}"`, "PATH_ESCAPE_VIOLATION");
36
37
  }
37
38
  if (fs.existsSync(assetPath) && !input.force) {
38
- throw new UsageError(`Workflow "${normalizedName}" already exists. Re-run with --force to overwrite it.`);
39
+ throw new UsageError(`Workflow "${normalizedName}" already exists. Re-run with --force to overwrite it.`, "RESOURCE_ALREADY_EXISTS");
39
40
  }
40
41
  const content = input.from
41
- ? readWorkflowSource(input.from)
42
+ ? readWorkflowSource(input.from, stashDir)
42
43
  : (input.content ?? buildWorkflowTemplate(normalizedName));
43
44
  try {
44
45
  parseWorkflowMarkdown(content);
@@ -57,7 +58,7 @@ export function createWorkflowAsset(input) {
57
58
  stashDir,
58
59
  };
59
60
  }
60
- function readWorkflowSource(source) {
61
+ function readWorkflowSource(source, stashDir) {
61
62
  const resolved = path.resolve(source);
62
63
  let stat;
63
64
  try {
@@ -69,6 +70,13 @@ function readWorkflowSource(source) {
69
70
  if (!stat.isFile()) {
70
71
  throw new UsageError(`Workflow source must be a file: "${source}".`);
71
72
  }
73
+ // The user is allowed to import any readable file as a workflow body, but
74
+ // an import from outside the stash is unusual enough to warn about. Anyone
75
+ // running `akm workflow create --from /etc/passwd` deserves a heads-up.
76
+ if (!isWithin(resolved, stashDir)) {
77
+ warn(`Importing workflow content from outside the stash: ${resolved}\n ` +
78
+ `If this was unintentional, abort and re-run with a --from path inside ${stashDir}.`);
79
+ }
72
80
  return fs.readFileSync(resolved, "utf8");
73
81
  }
74
82
  function normalizeWorkflowName(name) {
@@ -92,6 +92,15 @@ function extractWorkflowSteps(body) {
92
92
  }
93
93
  const steps = [];
94
94
  let index = titleLineIndex + 1;
95
+ // Skip optional intro prose before the first ## Step: section.
96
+ while (index < lines.length && !/^##\s+Step:\s+/.test((lines[index] ?? "").trim())) {
97
+ const line = lines[index] ?? "";
98
+ const trimmed = line.trim();
99
+ if (trimmed.startsWith("# ") && !/^#\s+Workflow:\s+/.test(trimmed)) {
100
+ throw new WorkflowValidationError(`Unexpected top-level heading after workflow title: "${trimmed}".`);
101
+ }
102
+ index++;
103
+ }
95
104
  while (index < lines.length) {
96
105
  const line = lines[index] ?? "";
97
106
  const trimmed = line.trim();
@@ -108,9 +108,19 @@ export function resumeWorkflowRun(runId) {
108
108
  const steps = readWorkflowRunSteps(workflowDb, run.id);
109
109
  return buildWorkflowRunDetail(run, steps);
110
110
  }
111
- // blocked or failed → flip back to active
111
+ // blocked or failed → flip back to active and re-open the current step so
112
+ // it can be reclassified (completed, failed, skipped) after resuming.
112
113
  const now = new Date().toISOString();
113
- workflowDb.prepare("UPDATE workflow_runs SET status = 'active', updated_at = ? WHERE id = ?").run(now, run.id);
114
+ workflowDb.transaction(() => {
115
+ if (run.current_step_id) {
116
+ workflowDb
117
+ .prepare(`UPDATE workflow_run_steps
118
+ SET status = 'pending', notes = NULL, evidence_json = NULL, completed_at = NULL
119
+ WHERE run_id = ? AND step_id = ? AND status IN ('blocked', 'failed')`)
120
+ .run(run.id, run.current_step_id);
121
+ }
122
+ workflowDb.prepare("UPDATE workflow_runs SET status = 'active', updated_at = ? WHERE id = ?").run(now, run.id);
123
+ })();
114
124
  const updated = { ...run, status: "active", updated_at: now };
115
125
  const steps = readWorkflowRunSteps(workflowDb, run.id);
116
126
  return buildWorkflowRunDetail(updated, steps);
package/docs/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # Documentation
2
+
3
+ ## Getting Started
4
+
5
+ - [Concepts](concepts.md) -- Stashes, registries, asset types, and refs
6
+ - [Getting Started](getting-started.md) -- Quick setup guide
7
+ - [Agent Install Guide](agents/agent-install.md) -- Step-by-step automated install for agents
8
+ - [Stash Maker's Guide](stash-makers.md) -- Build and share a stash on GitHub, npm, or a network directory
9
+
10
+ ## Upgrading
11
+
12
+ - [v0.5 → v0.6 migration guide](migration/v0.5-to-v0.6.md) -- Every breaking change with before/after code, publisher checklist, and troubleshooting
13
+
14
+ ## Reference
15
+
16
+ - [CLI](cli.md) -- All `akm` commands and flags
17
+ - [Registry](registry.md) -- Registries, search, hosting, and managing sources
18
+ - [Configuration](configuration.md) -- Providers, settings, and Ollama setup
19
+ - [Filesystem](technical/filesystem.md) -- Directory layout and `.stash.json` schema
20
+
21
+ ## Internals
22
+
23
+ - [Search](technical/search.md) -- Hybrid search architecture and scoring
24
+ - [Indexing](technical/indexing.md) -- How the search index is built
25
+ - [Classification](technical/classification.md) -- Matcher and renderer behavior
26
+ - [Show Response](technical/show-response.md) -- `akm show` output fields by asset type
27
+ - [Testing Workflow](technical/testing-workflow.md) -- End-to-end, Docker, deployment, and upgrade validation
28
+ - [Ref Format](technical/ref.md) -- Wire format for asset references
29
+ - [Test Coverage Guide](technical/test-coverage-guide.md) -- High-value testing areas
30
+ - [Core Principles](technical/akm-core-principles.md) -- Design principles and constraints
@@ -0,0 +1,4 @@
1
+ Migration notes for akm v0.0.13
2
+
3
+ - Initial public release.
4
+ - No migration steps are required for earlier akm versions.
@@ -0,0 +1,6 @@
1
+ Migration notes for akm v0.1.0
2
+
3
+ - The package and project were rebranded from Agent-i-Kit to akm.
4
+ - Update package references from `agent-i-kit` to `akm-cli`.
5
+ - Update config, registry, plugin, path, and environment-variable references from `agent-i-kit` / `AGENT_I_KIT_*` to `akm` / `AKM_*`.
6
+ - The `tool` asset type and `submit` command were removed.
@@ -0,0 +1,6 @@
1
+ Migration notes for akm v0.2.0
2
+
3
+ - Asset refs are user-facing `type:name` values; do not rely on URI-style refs.
4
+ - The old fixed asset-type union was replaced by an extensible asset type system.
5
+ - `tool` assets were removed; use `script` assets instead.
6
+ - Config and docs should treat remote provider scores and local scores as part of one shared search pipeline.
@@ -0,0 +1,5 @@
1
+ Migration notes for akm v0.3.0
2
+
3
+ - The old `stash` and `kit` command groups were folded into the top-level CLI.
4
+ - Use `akm add`, `akm list`, and `akm remove` instead of the older split command surfaces.
5
+ - Documentation and examples from older releases should be updated to the unified source model.
@@ -0,0 +1,6 @@
1
+ Migration notes for akm v0.5.0
2
+
3
+ - New top-level surfaces: `akm wiki …`, `akm workflow …`, `akm vault …`, and `akm save`.
4
+ - If you tried the unreleased single-wiki LLM prototype, move to the new `akm wiki …` workflow.
5
+ - Removed from the prototype surface: `akm lint`, `akm import --llm`, `akm import --dry-run`, `knowledge.pageKinds`, and the old ingest/lint LLM prompts.
6
+ - Existing raw wiki-like content should be moved into `wikis/<name>/raw/` and then managed with the new wiki commands.
@@ -0,0 +1,29 @@
1
+ Migration notes for akm v0.6.0
2
+
3
+ This release is a clean break: the runtime "kit" / "source" concept is renamed to **stash**, the registry wire format moves from `kits[]` to `stashes[]` (schema v3), and the discovery keyword/topic is renamed from `akm-kit` to `akm-stash`.
4
+
5
+ Automatic (no action required):
6
+ - `stash.lock` is renamed to `akm.lock` on startup.
7
+ - `config.installed[]` entries are mapped to `config.stashes[]` + `akm.lock` records.
8
+ - `stashDir` is loaded as an implicit `primary: true` filesystem stash entry.
9
+ - Stash type aliases `"context-hub"` and `"github"` normalize to `"git"` in memory.
10
+
11
+ Manual actions required:
12
+ - Replace `akm enable context-hub` / `akm disable context-hub` with
13
+ `akm add github:andrewyng/context-hub --name context-hub`.
14
+ - Switch error-handling scripts from string-matching the `error` message
15
+ to comparing the new machine-readable `code` field.
16
+ - Replace `--for-agent` with `--detail=agent` (old flag is retained as a
17
+ deprecated alias for one release cycle).
18
+ - Replace `disableGlobalStashes: true` with `stashInheritance: "replace"`.
19
+
20
+ Publishers:
21
+ - The discovery keyword/topic was renamed from `akm-kit` to `akm-stash`.
22
+ Update your npm `keywords` and GitHub topics. Legacy keywords are no
23
+ longer honored — clean break, no transition window.
24
+
25
+ Self-hosted registries:
26
+ - The wire format `kits[]` is renamed to `stashes[]`. Schema is bumped
27
+ to v3 and `akm-cli >= 0.6.0` only parses v3.
28
+
29
+ Full migration guide: https://github.com/itlackey/akm/blob/main/docs/migration/v0.5-to-v0.6.md
@@ -0,0 +1,21 @@
1
+ # Release-notes corpus for `akm help migrate`
2
+
3
+ This directory holds one `.md` file per release. Each file is the short,
4
+ focused migration note that `akm help migrate <version>` prints to the
5
+ terminal. The longform cross-release guides (e.g. `v0.5-to-v0.6.md`)
6
+ live one level up in `docs/migration/`.
7
+
8
+ ## Adding notes for a new release
9
+
10
+ 1. Create `<version>.md` in this directory (e.g. `0.7.0.md`).
11
+ 2. Write plain text — the content is printed verbatim. Start the body
12
+ with a line like `Migration notes for akm v0.7.0`, then list the
13
+ automatic migrations, manual actions, publisher changes, etc.
14
+ 3. The file ships with the published package via `package.json` →
15
+ `files[]` and is resolved at runtime from either `src/` or `dist/`,
16
+ so no code change is required.
17
+ 4. Link to the longform guide in the last paragraph if one exists.
18
+
19
+ Keep each note self-contained: it should tell a user everything they
20
+ need to upgrade without requiring them to open a browser (the longform
21
+ guide link is the last resort, not the first).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akm-cli",
3
- "version": "0.5.0",
3
+ "version": "0.6.0-rc1",
4
4
  "type": "module",
5
5
  "description": "akm (Agent Kit Manager) — A package manager for AI agent skills, commands, tools, and knowledge. Works with Claude Code, OpenCode, Cursor, and any AI coding assistant.",
6
6
  "keywords": [
@@ -35,7 +35,8 @@
35
35
  "dist",
36
36
  "README.md",
37
37
  "CHANGELOG.md",
38
- "LICENSE"
38
+ "LICENSE",
39
+ "docs/migration/release-notes"
39
40
  ],
40
41
  "bin": {
41
42
  "akm": "dist/cli.js"