akm-cli 0.0.23 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Agent Kit Manager
2
2
 
3
- > Agent-i-Kit
3
+ > **akm** — Agent Kit Manager
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/akm-cli)](https://www.npmjs.com/package/akm-cli)
6
6
  [![CI](https://github.com/itlackey/agentikit/actions/workflows/ci.yml/badge.svg)](https://github.com/itlackey/agentikit/actions/workflows/ci.yml)
@@ -25,7 +25,7 @@ Upgrade in place with `akm upgrade`.
25
25
  ## Quick Start
26
26
 
27
27
  ```sh
28
- akm init # Initialize your stash
28
+ akm init # Initialize your working stash
29
29
  akm add github:owner/repo # Add a kit from GitHub
30
30
  akm search "deploy" # Find assets
31
31
  akm show script:deploy.sh # View details and run command
@@ -89,7 +89,7 @@ Registries are indexes of available kits. The official
89
89
  ```sh
90
90
  akm registry search "code review" # Search registries
91
91
  akm registry add https://example.com/registry/index.json --name team # Add a registry
92
- akm sources add http://host:1933 --provider openviking \
92
+ akm stash add http://host:1933 --provider openviking \
93
93
  --options '{"apiKey":"key"}' # Add an OpenViking stash source
94
94
  akm registry list # List configured registries
95
95
  akm show viking://resources/my-doc # Fetch remote content from OpenViking
@@ -130,7 +130,7 @@ See the [Kit Maker's Guide](docs/kit-makers.md) for a full walkthrough.
130
130
  | [Getting Started](docs/getting-started.md) | Quick setup guide |
131
131
  | [CLI Reference](docs/cli.md) | All commands and flags |
132
132
  | [Configuration](docs/configuration.md) | Settings, providers, and Ollama setup |
133
- | [Concepts](docs/concepts.md) | Asset types, classification, stash model |
133
+ | [Concepts](docs/concepts.md) | Stashes, kits, registries, asset types |
134
134
  | [Kit Maker's Guide](docs/kit-makers.md) | Build and share kits |
135
135
  | [Registry](docs/registry.md) | Registries, search, and the v2 index format |
136
136
 
@@ -79,7 +79,7 @@ export function _setAssetTypeHooks(rendererHook, actionBuilderHook) {
79
79
  _registerActionBuilder = actionBuilderHook;
80
80
  }
81
81
  /**
82
- * Register a custom asset type with the Agentikit asset system.
82
+ * Register a custom asset type with the akm asset system.
83
83
  *
84
84
  * ## Full extension registration API
85
85
  *
package/dist/cli.js CHANGED
@@ -6,18 +6,18 @@ import { resolveStashDir } from "./common";
6
6
  import { DEFAULT_CONFIG, getConfigPath, loadConfig, saveConfig } from "./config";
7
7
  import { getConfigValue, listConfig, setConfigValue, unsetConfigValue } from "./config-cli";
8
8
  import { ConfigError, NotFoundError, UsageError } from "./errors";
9
- import { agentikitIndex } from "./indexer";
10
- import { agentikitInit } from "./init";
11
- import { agentikitList, agentikitRemove, agentikitUpdate } from "./installed-kits";
9
+ import { akmIndex } from "./indexer";
10
+ import { akmInit } from "./init";
11
+ import { akmList, akmRemove, akmUpdate } from "./installed-kits";
12
12
  import { getCacheDir, getDbPath, getDefaultStashDir } from "./paths";
13
13
  import { buildRegistryIndex, writeRegistryIndex } from "./registry-build-index";
14
14
  import { searchRegistry } from "./registry-search";
15
15
  import { checkForUpdate, performUpgrade } from "./self-update";
16
- import { agentikitAdd } from "./stash-add";
17
- import { agentikitClone } from "./stash-clone";
18
- import { agentikitSearch, parseSearchSource } from "./stash-search";
19
- import { agentikitShowUnified } from "./stash-show";
20
- import { addStashSource, listStashSources, removeStashSource } from "./stash-source-manage";
16
+ import { akmAdd, akmKitAdd } from "./stash-add";
17
+ import { akmClone } from "./stash-clone";
18
+ import { akmSearch, parseSearchSource } from "./stash-search";
19
+ import { akmShowUnified } from "./stash-show";
20
+ import { addStash, listStashes, removeStash } from "./stash-source-manage";
21
21
  import { setQuiet, warn } from "./warn";
22
22
  // Version: prefer compile-time define, then package.json, then fallback
23
23
  const pkgVersion = (() => {
@@ -405,14 +405,14 @@ function formatSearchPlain(r, detail) {
405
405
  const initCommand = defineCommand({
406
406
  meta: {
407
407
  name: "init",
408
- description: "Initialize Agent-i-Kit's working stash directory and persist stashDir in config",
408
+ description: "Initialize akm's working stash directory and persist stashDir in config",
409
409
  },
410
410
  args: {
411
411
  dir: { type: "string", description: "Custom stash directory path (default: ~/akm)" },
412
412
  },
413
413
  async run({ args }) {
414
414
  await runWithJsonErrors(async () => {
415
- const result = await agentikitInit({ dir: args.dir });
415
+ const result = await akmInit({ dir: args.dir });
416
416
  output("init", result);
417
417
  });
418
418
  },
@@ -424,7 +424,7 @@ const indexCommand = defineCommand({
424
424
  },
425
425
  async run({ args }) {
426
426
  await runWithJsonErrors(async () => {
427
- const result = await agentikitIndex({ full: args.full });
427
+ const result = await akmIndex({ full: args.full });
428
428
  output("index", result);
429
429
  });
430
430
  },
@@ -451,7 +451,7 @@ const searchCommand = defineCommand({
451
451
  }
452
452
  const limit = limitRaw;
453
453
  const source = parseSearchSource(args.source);
454
- const result = await agentikitSearch({ query: args.query, type, limit, source });
454
+ const result = await akmSearch({ query: args.query, type, limit, source });
455
455
  output("search", result);
456
456
  });
457
457
  },
@@ -467,7 +467,7 @@ const addCommand = defineCommand({
467
467
  },
468
468
  async run({ args }) {
469
469
  await runWithJsonErrors(async () => {
470
- const result = await agentikitAdd({ ref: args.ref });
470
+ const result = await akmAdd({ ref: args.ref });
471
471
  output("add", result);
472
472
  });
473
473
  },
@@ -476,7 +476,7 @@ const listCommand = defineCommand({
476
476
  meta: { name: "list", description: "List installed kits" },
477
477
  async run() {
478
478
  await runWithJsonErrors(async () => {
479
- const result = await agentikitList();
479
+ const result = await akmList();
480
480
  output("list", result);
481
481
  });
482
482
  },
@@ -488,7 +488,7 @@ const removeCommand = defineCommand({
488
488
  },
489
489
  async run({ args }) {
490
490
  await runWithJsonErrors(async () => {
491
- const result = await agentikitRemove({ target: args.target });
491
+ const result = await akmRemove({ target: args.target });
492
492
  output("remove", result);
493
493
  });
494
494
  },
@@ -502,11 +502,36 @@ const updateCommand = defineCommand({
502
502
  },
503
503
  async run({ args }) {
504
504
  await runWithJsonErrors(async () => {
505
- const result = await agentikitUpdate({ target: args.target, all: args.all, force: args.force });
505
+ const result = await akmUpdate({ target: args.target, all: args.all, force: args.force });
506
506
  output("update", result);
507
507
  });
508
508
  },
509
509
  });
510
+ const kitAddCommand = defineCommand({
511
+ meta: { name: "add", description: "Install a kit from npm, GitHub, or any git host" },
512
+ args: {
513
+ ref: {
514
+ type: "positional",
515
+ description: "Registry ref (npm package, owner/repo, or git URL)",
516
+ required: true,
517
+ },
518
+ },
519
+ async run({ args }) {
520
+ await runWithJsonErrors(async () => {
521
+ const result = await akmKitAdd({ ref: args.ref });
522
+ output("add", result);
523
+ });
524
+ },
525
+ });
526
+ const kitCommand = defineCommand({
527
+ meta: { name: "kit", description: "Manage installed kits" },
528
+ subCommands: {
529
+ add: kitAddCommand,
530
+ list: listCommand,
531
+ remove: removeCommand,
532
+ update: updateCommand,
533
+ },
534
+ });
510
535
  const upgradeCommand = defineCommand({
511
536
  meta: { name: "upgrade", description: "Upgrade akm to the latest release" },
512
537
  args: {
@@ -568,7 +593,7 @@ const showCommand = defineCommand({
568
593
  throw new UsageError(`Unknown view mode: ${args.akmView}. Expected one of: full|toc|frontmatter|section|lines`);
569
594
  }
570
595
  }
571
- const result = await agentikitShowUnified({ ref: args.ref, view });
596
+ const result = await akmShowUnified({ ref: args.ref, view });
572
597
  output("show", result);
573
598
  });
574
599
  },
@@ -672,7 +697,7 @@ const configCommand = defineCommand({
672
697
  const cloneCommand = defineCommand({
673
698
  meta: {
674
699
  name: "clone",
675
- description: "Clone an asset from any stash source into the working stash or a custom destination",
700
+ description: "Clone an asset from any source into the working stash or a custom destination",
676
701
  },
677
702
  args: {
678
703
  ref: { type: "positional", description: "Asset ref (e.g. npm:@scope/pkg//script:deploy.sh)", required: true },
@@ -682,7 +707,7 @@ const cloneCommand = defineCommand({
682
707
  },
683
708
  async run({ args }) {
684
709
  await runWithJsonErrors(async () => {
685
- const result = await agentikitClone({
710
+ const result = await akmClone({
686
711
  sourceRef: args.ref,
687
712
  newName: args.name,
688
713
  force: args.force,
@@ -815,21 +840,20 @@ const registryCommand = defineCommand({
815
840
  },
816
841
  });
817
842
  /**
818
- * Shared subcommand definitions for stash source management.
819
- * Used by both `akm stash` (preferred) and `akm sources` (legacy alias).
843
+ * Subcommand definitions for managing additional stashes.
820
844
  */
821
845
  function buildSourceSubCommands(outputPrefix) {
822
846
  return {
823
847
  list: defineCommand({
824
- meta: { name: "list", description: "List all stash sources" },
848
+ meta: { name: "list", description: "List all stashes in search order" },
825
849
  run() {
826
850
  return runWithJsonErrors(() => {
827
- output(`${outputPrefix}`, listStashSources());
851
+ output(`${outputPrefix}`, listStashes());
828
852
  });
829
853
  },
830
854
  }),
831
855
  add: defineCommand({
832
- meta: { name: "add", description: "Add a stash source (filesystem path or remote URL)" },
856
+ meta: { name: "add", description: "Register an additional stash (filesystem path or remote URL)" },
833
857
  args: {
834
858
  target: { type: "positional", description: "Path or URL to add", required: true },
835
859
  name: { type: "string", description: "Human-friendly name for the source" },
@@ -856,7 +880,7 @@ function buildSourceSubCommands(outputPrefix) {
856
880
  throw new UsageError("--options must be valid JSON");
857
881
  }
858
882
  }
859
- const result = addStashSource({
883
+ const result = addStash({
860
884
  target: args.target,
861
885
  name: args.name,
862
886
  providerType: args.provider,
@@ -867,13 +891,13 @@ function buildSourceSubCommands(outputPrefix) {
867
891
  },
868
892
  }),
869
893
  remove: defineCommand({
870
- meta: { name: "remove", description: "Remove a stash source by URL, path, or name" },
894
+ meta: { name: "remove", description: "Remove an additional stash by URL, path, or name" },
871
895
  args: {
872
896
  target: { type: "positional", description: "Source URL, path, or name to remove", required: true },
873
897
  },
874
898
  run({ args }) {
875
899
  return runWithJsonErrors(() => {
876
- const result = removeStashSource(args.target);
900
+ const result = removeStash(args.target);
877
901
  output(`${outputPrefix}-remove`, result);
878
902
  });
879
903
  },
@@ -881,13 +905,9 @@ function buildSourceSubCommands(outputPrefix) {
881
905
  };
882
906
  }
883
907
  const stashCommand = defineCommand({
884
- meta: { name: "stash", description: "Manage stash sources (local paths and remote providers)" },
908
+ meta: { name: "stash", description: "Manage additional stashes (local directories and remote providers)" },
885
909
  subCommands: buildSourceSubCommands("stash"),
886
910
  });
887
- const sourcesCommand = defineCommand({
888
- meta: { name: "sources", description: "Manage stash sources (alias for 'akm stash')" },
889
- subCommands: buildSourceSubCommands("sources"),
890
- });
891
911
  const hintsCommand = defineCommand({
892
912
  meta: {
893
913
  name: "hints",
@@ -905,7 +925,7 @@ const main = defineCommand({
905
925
  meta: {
906
926
  name: "akm",
907
927
  version: pkgVersion,
908
- description: "CLI tool to search, open, and manage assets from Agent-i-Kit stash.",
928
+ description: "Agent Kit Manager search, show, and manage assets from your stash.",
909
929
  },
910
930
  args: {
911
931
  format: { type: "string", description: "Output format (json|text|yaml)" },
@@ -919,12 +939,12 @@ const main = defineCommand({
919
939
  list: listCommand,
920
940
  remove: removeCommand,
921
941
  update: updateCommand,
942
+ kit: kitCommand,
922
943
  upgrade: upgradeCommand,
923
944
  search: searchCommand,
924
945
  show: showCommand,
925
946
  clone: cloneCommand,
926
947
  stash: stashCommand,
927
- sources: sourcesCommand,
928
948
  registry: registryCommand,
929
949
  config: configCommand,
930
950
  hints: hintsCommand,
@@ -1079,14 +1099,14 @@ function loadHints(detail = "normal") {
1079
1099
  }
1080
1100
  const EMBEDDED_HINTS = `# akm CLI
1081
1101
 
1082
- You have access to a searchable library of scripts, skills, commands, agents, and knowledge documents via \`akm\`. Search the stash first before writing something from scratch.
1102
+ You have access to a searchable library of scripts, skills, commands, agents, and knowledge documents via \`akm\`. Search your stashes first before writing something from scratch.
1083
1103
 
1084
1104
  ## Quick Reference
1085
1105
 
1086
1106
  \`\`\`sh
1087
- akm search "<query>" # Search for assets
1107
+ akm search "<query>" # Search your stashes and installed kits
1088
1108
  akm search "<query>" --type skill # Filter by type
1089
- akm search "<query>" --source both # Search stash providers and registries for assets
1109
+ akm search "<query>" --source both # Also search registries for installable kits
1090
1110
  akm show <ref> # View asset details
1091
1111
  akm add <ref> # Install a kit (npm, GitHub, git, local)
1092
1112
  akm clone <ref> # Copy an asset to the working stash (optional --dest arg to clone to specific location)
@@ -1107,14 +1127,14 @@ Run \`akm -h\` for the full command reference.
1107
1127
  `;
1108
1128
  const EMBEDDED_HINTS_FULL = `# akm CLI — Full Reference
1109
1129
 
1110
- You have access to a searchable library of scripts, skills, commands, agents, and knowledge documents via \`akm\`. Search the stash first before writing something from scratch.
1130
+ You have access to a searchable library of scripts, skills, commands, agents, and knowledge documents via \`akm\`. Search your stashes first before writing something from scratch.
1111
1131
 
1112
1132
  ## Search
1113
1133
 
1114
1134
  \`\`\`sh
1115
- akm search "<query>" # Search local stash
1135
+ akm search "<query>" # Search your stashes and installed kits
1116
1136
  akm search "<query>" --type skill # Filter by asset type
1117
- akm search "<query>" --source both # Search all stash providers and registries
1137
+ akm search "<query>" --source both # Also search registries for installable kits
1118
1138
  akm search "<query>" --source registry # Search registries only
1119
1139
  akm search "<query>" --limit 10 # Limit results
1120
1140
  akm search "<query>" --detail full # Include scores, paths, timing
@@ -1155,10 +1175,14 @@ akm show viking://resources/my-doc # Show remote OpenViking content
1155
1175
  ## Install & Manage Kits
1156
1176
 
1157
1177
  \`\`\`sh
1158
- akm add <ref> # Install a kit
1178
+ akm add <ref> # Install a kit (smart router: local dirs become stash adds)
1159
1179
  akm add @scope/kit # From npm
1160
1180
  akm add owner/repo # From GitHub
1161
- akm add ./path/to/local/kit # From local directory
1181
+ akm add ./path/to/local/kit # From local directory (adds as stash)
1182
+ akm kit add <ref> # Install a kit (explicit)
1183
+ akm kit list # List installed kits
1184
+ akm kit remove <target> # Remove a kit
1185
+ akm kit update --all # Update all kits
1162
1186
  akm list # List installed kits
1163
1187
  akm remove <target> # Remove by id or ref
1164
1188
  akm update --all # Update all installed kits
@@ -1206,10 +1230,11 @@ akm config path --all # Show all config paths
1206
1230
  ## Other Commands
1207
1231
 
1208
1232
  \`\`\`sh
1209
- akm init # Initialize stash directory
1233
+ akm init # Initialize working stash
1210
1234
  akm index # Rebuild search index
1211
1235
  akm index --full # Full reindex
1212
- akm sources # List stash search paths
1236
+ akm stash # List all stashes
1237
+ akm kit # Kit management (add, list, remove, update)
1213
1238
  akm upgrade # Upgrade akm binary
1214
1239
  akm upgrade --check # Check for updates
1215
1240
  akm hints # Print this reference
@@ -9,16 +9,6 @@ export function parseConfigValue(key, value) {
9
9
  throw new UsageError(`Invalid value for semanticSearch: expected "true" or "false"`);
10
10
  }
11
11
  return { semanticSearch: value === "true" };
12
- case "searchPaths":
13
- try {
14
- const parsed = JSON.parse(value);
15
- if (!Array.isArray(parsed))
16
- throw new UsageError("expected JSON array");
17
- return { searchPaths: parsed.filter((d) => typeof d === "string") };
18
- }
19
- catch {
20
- throw new UsageError(`Invalid value for searchPaths: expected JSON array (e.g. '["/path/a","/path/b"]')`);
21
- }
22
12
  case "embedding":
23
13
  return { embedding: parseEmbeddingConnectionValue(value) };
24
14
  case "llm":
@@ -41,8 +31,6 @@ export function getConfigValue(config, key) {
41
31
  return config.stashDir ?? null;
42
32
  case "semanticSearch":
43
33
  return config.semanticSearch;
44
- case "searchPaths":
45
- return [...config.searchPaths];
46
34
  case "embedding":
47
35
  return config.embedding ?? null;
48
36
  case "llm":
@@ -63,7 +51,6 @@ export function setConfigValue(config, key, rawValue) {
63
51
  switch (key) {
64
52
  case "stashDir":
65
53
  case "semanticSearch":
66
- case "searchPaths":
67
54
  case "embedding":
68
55
  case "llm":
69
56
  case "registries":
@@ -108,8 +95,6 @@ export function listConfig(config) {
108
95
  result.embedding = config.embedding;
109
96
  if (config.llm)
110
97
  result.llm = config.llm;
111
- if (config.searchPaths?.length)
112
- result.searchPaths = config.searchPaths;
113
98
  return result;
114
99
  }
115
100
  function mergeConfigValue(config, partial) {
package/dist/config.js CHANGED
@@ -4,7 +4,6 @@ import { getConfigDir as _getConfigDir, getConfigPath as _getConfigPath } from "
4
4
  // ── Defaults ────────────────────────────────────────────────────────────────
5
5
  export const DEFAULT_CONFIG = {
6
6
  semanticSearch: true,
7
- searchPaths: [],
8
7
  registries: [
9
8
  { url: "https://raw.githubusercontent.com/itlackey/akm-registry/main/index.json", name: "official" },
10
9
  { url: "https://skills.sh", name: "skills.sh", provider: "skills-sh" },
@@ -93,8 +92,6 @@ function sanitizeConfigForWrite(config) {
93
92
  sanitized.llm = rest;
94
93
  }
95
94
  // Drop empty keys to keep config clean
96
- if (!config.searchPaths?.length)
97
- delete sanitized.searchPaths;
98
95
  return sanitized;
99
96
  }
100
97
  export function updateConfig(partial) {
@@ -125,8 +122,18 @@ function pickKnownKeys(raw) {
125
122
  if (typeof raw.semanticSearch === "boolean") {
126
123
  config.semanticSearch = raw.semanticSearch;
127
124
  }
125
+ // Migrate legacy searchPaths into stashes
128
126
  if (Array.isArray(raw.searchPaths)) {
129
- config.searchPaths = raw.searchPaths.filter((d) => typeof d === "string");
127
+ const legacyPaths = raw.searchPaths.filter((d) => typeof d === "string");
128
+ if (legacyPaths.length > 0) {
129
+ const existing = config.stashes ?? [];
130
+ const migrated = legacyPaths
131
+ .filter((p) => !existing.some((s) => s.type === "filesystem" && s.path === p))
132
+ .map((p) => ({ type: "filesystem", path: p }));
133
+ if (migrated.length > 0) {
134
+ config.stashes = [...existing, ...migrated];
135
+ }
136
+ }
130
137
  }
131
138
  const embedding = parseEmbeddingConfig(raw.embedding);
132
139
  if (embedding)
@@ -270,7 +277,7 @@ function parseEmbeddingConfig(value) {
270
277
  if (typeof obj.endpoint !== "string" || !obj.endpoint)
271
278
  return undefined;
272
279
  if (!obj.endpoint.startsWith("http://") && !obj.endpoint.startsWith("https://")) {
273
- console.warn(`[agentikit] Ignoring embedding config: endpoint must start with http:// or https://, got "${obj.endpoint}"`);
280
+ console.warn(`[akm] Ignoring embedding config: endpoint must start with http:// or https://, got "${obj.endpoint}"`);
274
281
  return undefined;
275
282
  }
276
283
  if (typeof obj.model !== "string" || !obj.model)
@@ -303,7 +310,7 @@ function parseLlmConfig(value) {
303
310
  if (typeof obj.endpoint !== "string" || !obj.endpoint)
304
311
  return undefined;
305
312
  if (!obj.endpoint.startsWith("http://") && !obj.endpoint.startsWith("https://")) {
306
- console.warn(`[agentikit] Ignoring llm config: endpoint must start with http:// or https://, got "${obj.endpoint}"`);
313
+ console.warn(`[akm] Ignoring llm config: endpoint must start with http:// or https://, got "${obj.endpoint}"`);
307
314
  return undefined;
308
315
  }
309
316
  if (typeof obj.model !== "string" || !obj.model)
package/dist/github.js CHANGED
@@ -1,12 +1,31 @@
1
1
  export const GITHUB_API_BASE = "https://api.github.com";
2
- export function githubHeaders() {
2
+ const GITHUB_TOKEN_DOMAINS = new Set(["api.github.com", "github.com", "uploads.github.com"]);
3
+ /**
4
+ * Build headers for GitHub API requests.
5
+ * When a `url` is provided, the Authorization header is only included if the
6
+ * URL points to a known GitHub domain, preventing token leakage on redirects
7
+ * to third-party hosts.
8
+ */
9
+ export function githubHeaders(url) {
3
10
  const token = process.env.GITHUB_TOKEN?.trim();
4
11
  const headers = {
5
12
  Accept: "application/vnd.github+json",
6
13
  "User-Agent": "akm-registry",
7
14
  };
8
- if (token)
9
- headers.Authorization = `Bearer ${token}`;
15
+ if (token) {
16
+ let includeToken = true;
17
+ if (url) {
18
+ try {
19
+ const hostname = new URL(url).hostname;
20
+ includeToken = GITHUB_TOKEN_DOMAINS.has(hostname);
21
+ }
22
+ catch {
23
+ includeToken = false;
24
+ }
25
+ }
26
+ if (includeToken)
27
+ headers.Authorization = `Bearer ${token}`;
28
+ }
10
29
  return headers;
11
30
  }
12
31
  export function asRecord(value) {
package/dist/indexer.js CHANGED
@@ -7,12 +7,12 @@ import { getDbPath } from "./paths";
7
7
  import { walkStashFlat } from "./walker";
8
8
  import { warn } from "./warn";
9
9
  // ── Indexer ──────────────────────────────────────────────────────────────────
10
- export async function agentikitIndex(options) {
10
+ export async function akmIndex(options) {
11
11
  const stashDir = options?.stashDir || resolveStashDir();
12
12
  // Load config and resolve all stash sources
13
13
  const { loadConfig } = await import("./config.js");
14
14
  const config = loadConfig();
15
- const { resolveAllStashDirs } = await import("./stash-source.js");
15
+ const { resolveAllStashDirs } = await import("./search-source.js");
16
16
  const allStashDirs = resolveAllStashDirs(stashDir);
17
17
  const t0 = Date.now();
18
18
  // Open database — pass embedding dimension from config if available
package/dist/init.js CHANGED
@@ -10,7 +10,7 @@ import { TYPE_DIRS } from "./asset-spec";
10
10
  import { getConfigPath, loadConfig, saveConfig } from "./config";
11
11
  import { getBinDir, getDefaultStashDir } from "./paths";
12
12
  import { ensureRg } from "./ripgrep-install";
13
- export async function agentikitInit(options) {
13
+ export async function akmInit(options) {
14
14
  const stashDir = options?.dir ? path.resolve(options.dir) : getDefaultStashDir();
15
15
  let created = false;
16
16
  if (!fs.existsSync(stashDir)) {
@@ -13,11 +13,11 @@ import fs from "node:fs";
13
13
  import { resolveStashDir } from "./common";
14
14
  import { loadConfig } from "./config";
15
15
  import { NotFoundError, UsageError } from "./errors";
16
- import { agentikitIndex } from "./indexer";
16
+ import { akmIndex } from "./indexer";
17
17
  import { removeLockEntry, upsertLockEntry } from "./lockfile";
18
18
  import { installRegistryRef, removeInstalledRegistryEntry, upsertInstalledRegistryEntry } from "./registry-install";
19
19
  import { parseRegistryRef } from "./registry-resolve";
20
- export async function agentikitList(input) {
20
+ export async function akmList(input) {
21
21
  const stashDir = input?.stashDir ?? resolveStashDir();
22
22
  const config = loadConfig();
23
23
  const installed = config.installed ?? [];
@@ -34,22 +34,22 @@ export async function agentikitList(input) {
34
34
  totalInstalled: installed.length,
35
35
  };
36
36
  }
37
- export async function agentikitRemove(input) {
37
+ export async function akmRemove(input) {
38
38
  const target = input.target.trim();
39
39
  if (!target)
40
- throw new UsageError("Target is required.");
40
+ throw new UsageError("Target is required. Provide the kit id or ref (e.g. `akm remove npm:@scope/kit` or `akm remove owner/repo`).");
41
41
  const stashDir = input.stashDir ?? resolveStashDir();
42
42
  const config = loadConfig();
43
43
  const installed = config.installed ?? [];
44
44
  const entry = resolveInstalledTarget(installed, target);
45
45
  const updatedConfig = removeInstalledRegistryEntry(entry.id);
46
- removeLockEntry(entry.id);
46
+ await removeLockEntry(entry.id);
47
47
  // Only clean up cache for non-local sources — local sources point to the
48
48
  // user's real directory on disk and must never be deleted.
49
49
  if (entry.source !== "local") {
50
50
  cleanupDirectoryBestEffort(entry.cacheDir);
51
51
  }
52
- const index = await agentikitIndex({ stashDir });
52
+ const index = await akmIndex({ stashDir });
53
53
  return {
54
54
  schemaVersion: 1,
55
55
  stashDir,
@@ -62,7 +62,7 @@ export async function agentikitRemove(input) {
62
62
  stashRoot: entry.stashRoot,
63
63
  },
64
64
  config: {
65
- searchPaths: updatedConfig.searchPaths,
65
+ stashCount: updatedConfig.stashes?.length ?? 0,
66
66
  installedKitCount: updatedConfig.installed?.length ?? 0,
67
67
  },
68
68
  index: {
@@ -73,7 +73,7 @@ export async function agentikitRemove(input) {
73
73
  },
74
74
  };
75
75
  }
76
- export async function agentikitUpdate(input) {
76
+ export async function akmUpdate(input) {
77
77
  const stashDir = input?.stashDir ?? resolveStashDir();
78
78
  const target = input?.target?.trim();
79
79
  const all = input?.all === true;
@@ -117,7 +117,7 @@ export async function agentikitUpdate(input) {
117
117
  },
118
118
  });
119
119
  }
120
- const index = await agentikitIndex({ stashDir });
120
+ const index = await akmIndex({ stashDir });
121
121
  const config = loadConfig();
122
122
  return {
123
123
  schemaVersion: 1,
@@ -126,7 +126,7 @@ export async function agentikitUpdate(input) {
126
126
  all,
127
127
  processed,
128
128
  config: {
129
- searchPaths: config.searchPaths,
129
+ stashCount: config.stashes?.length ?? 0,
130
130
  installedKitCount: config.installed?.length ?? 0,
131
131
  },
132
132
  index: {
@@ -2,8 +2,8 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { isWithin } from "./common";
4
4
  // ── Helpers ─────────────────────────────────────────────────────────────────
5
- /** Keys to check in package.json for akm include configuration. */
6
- const INCLUDE_CONFIG_KEYS = ["akm", "agentikit"];
5
+ /** Key to check in package.json for akm include configuration. */
6
+ const INCLUDE_CONFIG_KEYS = ["akm"];
7
7
  function readPackageJsonAt(dirPath) {
8
8
  try {
9
9
  const raw = fs.readFileSync(path.join(dirPath, "package.json"), "utf8");
@@ -39,7 +39,7 @@ function extractIncludeList(pkg) {
39
39
  // ── Public API ───────────────────────────────────────────────────────────────
40
40
  /**
41
41
  * Walk up the directory tree from `startDir` to `boundary` (inclusive) looking
42
- * for a package.json that declares an `akm.include` or `agentikit.include` list.
42
+ * for a package.json that declares an `akm.include` list.
43
43
  * Returns the first config found, or `undefined` if none is found within the
44
44
  * boundary.
45
45
  */
@@ -15,8 +15,8 @@ import { getRenderer } from "./file-context";
15
15
  import { buildSearchText } from "./indexer";
16
16
  import { generateMetadataFlat, loadStashFile } from "./metadata";
17
17
  import { getDbPath } from "./paths";
18
+ import { buildEditHint, findSourceForPath, isEditable } from "./search-source";
18
19
  import { makeAssetRef } from "./stash-ref";
19
- import { buildEditHint, findSourceForPath, isEditable } from "./stash-source";
20
20
  import { walkStashFlat } from "./walker";
21
21
  import { warn } from "./warn";
22
22
  // ── Type renderer/action maps (re-exported so stash-search.ts can register) ──
package/dist/lockfile.js CHANGED
@@ -23,6 +23,10 @@ async function acquireLockSentinel() {
23
23
  catch (err) {
24
24
  if (err.code !== "EEXIST")
25
25
  throw err;
26
+ // Check for stale lock — if the owning PID is no longer running, reclaim it
27
+ if (tryReclaimStaleSentinel(sentinelPath)) {
28
+ continue; // Sentinel removed — retry immediately
29
+ }
26
30
  // Another process holds the lock — wait briefly before retrying
27
31
  if (attempt < LOCK_MAX_RETRIES - 1) {
28
32
  await new Promise((resolve) => setTimeout(resolve, LOCK_RETRY_DELAY_MS));
@@ -32,6 +36,34 @@ async function acquireLockSentinel() {
32
36
  // Best-effort: proceed without the lock rather than failing the install
33
37
  return false;
34
38
  }
39
+ /**
40
+ * Check if the sentinel was left by a dead process and remove it if so.
41
+ * Returns true if the sentinel was reclaimed (removed).
42
+ */
43
+ function tryReclaimStaleSentinel(sentinelPath) {
44
+ try {
45
+ const content = fs.readFileSync(sentinelPath, "utf8").trim();
46
+ const pid = parseInt(content, 10);
47
+ if (Number.isNaN(pid) || pid <= 0) {
48
+ // Invalid PID in sentinel — reclaim it
49
+ fs.unlinkSync(sentinelPath);
50
+ return true;
51
+ }
52
+ // Check if the process is still alive (signal 0 doesn't kill, just checks)
53
+ try {
54
+ process.kill(pid, 0);
55
+ return false; // Process is alive — lock is valid
56
+ }
57
+ catch {
58
+ // Process is dead — reclaim the stale lock
59
+ fs.unlinkSync(sentinelPath);
60
+ return true;
61
+ }
62
+ }
63
+ catch {
64
+ return false; // Can't read or remove — leave it alone
65
+ }
66
+ }
35
67
  function releaseLockSentinel() {
36
68
  try {
37
69
  fs.unlinkSync(getLockSentinelPath());
@@ -84,9 +116,16 @@ export async function upsertLockEntry(entry) {
84
116
  releaseLockSentinel();
85
117
  }
86
118
  }
87
- export function removeLockEntry(id) {
88
- const entries = readLockfile();
89
- writeLockfile(entries.filter((e) => e.id !== id));
119
+ export async function removeLockEntry(id) {
120
+ const acquired = await acquireLockSentinel();
121
+ try {
122
+ const entries = readLockfile();
123
+ writeLockfile(entries.filter((e) => e.id !== id));
124
+ }
125
+ finally {
126
+ if (acquired)
127
+ releaseLockSentinel();
128
+ }
90
129
  }
91
130
  // ── Helpers ─────────────────────────────────────────────────────────────────
92
131
  function isValidLockfileEntry(value) {
@@ -11,17 +11,10 @@ import { walkStashFlat } from "./walker";
11
11
  const DEFAULT_NPM_REGISTRY_BASE = "https://registry.npmjs.org";
12
12
  const DEFAULT_MANUAL_ENTRIES_PATH = path.resolve("manual-entries.json");
13
13
  const DEFAULT_OUTPUT_PATH = path.resolve("index.json");
14
- const REQUIRED_KEYWORDS = ["agentikit", "akm-kit"];
15
- const GITHUB_TOPICS = ["agentikit", "akm-kit"];
16
- const EXCLUDED_REPOS = new Set(["itlackey/agentikit-plugins", "itlackey/agentikit"]);
17
- const EXCLUDED_NPM_PACKAGES = new Set([
18
- "agentikit",
19
- "agentikit-claude",
20
- "agentikit-opencode",
21
- "agentikit-plugins",
22
- "akm-cli",
23
- "akm-opencode",
24
- ]);
14
+ const REQUIRED_KEYWORDS = ["akm-kit"];
15
+ const GITHUB_TOPICS = ["akm-kit"];
16
+ const EXCLUDED_REPOS = new Set(["itlackey/agentikit"]);
17
+ const EXCLUDED_NPM_PACKAGES = new Set(["akm-cli"]);
25
18
  const EMPTY_INSPECTION = {};
26
19
  export async function buildRegistryIndex(options) {
27
20
  const manualEntriesPath = path.resolve(options?.manualEntriesPath ?? DEFAULT_MANUAL_ENTRIES_PATH);
@@ -60,7 +60,7 @@ export async function installRegistryRef(ref, options) {
60
60
  integrity = await computeFileHash(archivePath);
61
61
  extractTarGzSecure(archivePath, extractedDir);
62
62
  provisionalKitRoot = detectStashRoot(extractedDir);
63
- installRoot = applyAgentikitIncludeConfig(provisionalKitRoot, cacheDir, extractedDir) ?? provisionalKitRoot;
63
+ installRoot = applyAkmIncludeConfig(provisionalKitRoot, cacheDir, extractedDir) ?? provisionalKitRoot;
64
64
  stashRoot = detectStashRoot(installRoot);
65
65
  }
66
66
  catch (err) {
@@ -118,7 +118,7 @@ async function installGitRegistryRef(parsed, options) {
118
118
  if (isDirectory(extractedDir)) {
119
119
  try {
120
120
  const provisionalKitRoot = detectStashRoot(extractedDir);
121
- const installRoot = applyAgentikitIncludeConfig(provisionalKitRoot, cacheDir, extractedDir) ?? provisionalKitRoot;
121
+ const installRoot = applyAkmIncludeConfig(provisionalKitRoot, cacheDir, extractedDir) ?? provisionalKitRoot;
122
122
  const stashRoot = detectStashRoot(installRoot);
123
123
  if (stashRoot) {
124
124
  return {
@@ -164,7 +164,7 @@ async function installGitRegistryRef(parsed, options) {
164
164
  // Clean up the clone dir
165
165
  fs.rmSync(cloneDir, { recursive: true, force: true });
166
166
  provisionalKitRoot = detectStashRoot(extractedDir);
167
- installRoot = applyAgentikitIncludeConfig(provisionalKitRoot, cacheDir, extractedDir) ?? provisionalKitRoot;
167
+ installRoot = applyAkmIncludeConfig(provisionalKitRoot, cacheDir, extractedDir) ?? provisionalKitRoot;
168
168
  stashRoot = detectStashRoot(installRoot);
169
169
  }
170
170
  catch (err) {
@@ -226,10 +226,6 @@ export function detectStashRoot(extractedDir) {
226
226
  if (hasStashDirs(root)) {
227
227
  return root;
228
228
  }
229
- const opencodeDir = path.join(root, "opencode");
230
- if (hasStashDirs(opencodeDir)) {
231
- return opencodeDir;
232
- }
233
229
  const shallowest = findShallowestStashRoot(root);
234
230
  if (shallowest)
235
231
  return shallowest;
@@ -242,7 +238,7 @@ function buildInstallCacheDir(cacheRootDir, source, id, version) {
242
238
  : (version?.replace(/[^a-zA-Z0-9_.-]+/g, "-") ?? `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`);
243
239
  return path.join(cacheRootDir, slug || source, versionSlug);
244
240
  }
245
- function applyAgentikitIncludeConfig(sourceRoot, cacheDir, searchRoot = sourceRoot) {
241
+ function applyAkmIncludeConfig(sourceRoot, cacheDir, searchRoot = sourceRoot) {
246
242
  const includeConfig = findNearestIncludeConfig(sourceRoot, searchRoot);
247
243
  if (!includeConfig)
248
244
  return undefined;
@@ -400,13 +396,15 @@ function countStashDirs(dirPath) {
400
396
  *
401
397
  * Skips `root` itself since the caller already checked it via `hasStashDirs`.
402
398
  */
399
+ const BFS_MAX_DEPTH = 5;
403
400
  function findShallowestStashRoot(root) {
404
- const queue = [root];
401
+ const queue = [{ dir: root, depth: 0 }];
405
402
  while (queue.length > 0) {
406
- const current = queue.shift();
407
- if (!current) {
403
+ const item = queue.shift();
404
+ if (!item) {
408
405
  continue;
409
406
  }
407
+ const { dir: current, depth } = item;
410
408
  if (current !== root) {
411
409
  // .stash directory is a strong stash marker
412
410
  if (isDirectory(path.join(current, ".stash"))) {
@@ -418,6 +416,8 @@ function findShallowestStashRoot(root) {
418
416
  return current;
419
417
  }
420
418
  }
419
+ if (depth >= BFS_MAX_DEPTH)
420
+ continue;
421
421
  let children;
422
422
  try {
423
423
  children = fs.readdirSync(current, { withFileTypes: true });
@@ -430,7 +430,7 @@ function findShallowestStashRoot(root) {
430
430
  continue;
431
431
  if (child.name === ".git" || child.name === "node_modules")
432
432
  continue;
433
- queue.push(path.join(current, child.name));
433
+ queue.push({ dir: path.join(current, child.name), depth: depth + 1 });
434
434
  }
435
435
  }
436
436
  return undefined;
@@ -422,7 +422,7 @@ function fileUriToPath(ref) {
422
422
  }
423
423
  /**
424
424
  * Build a human-readable local ID from an absolute path.
425
- * /home/user/.hyphn/skills ~/.hyphn/skills
425
+ * /home/user/akm/skills ~/akm/skills
426
426
  * /tmp/my-kit → /tmp/my-kit
427
427
  */
428
428
  function toReadableLocalId(absolutePath) {
@@ -70,7 +70,7 @@ export function resolveRegistries(configRegistries) {
70
70
  if (!url)
71
71
  continue;
72
72
  if (!url.startsWith("http://") && !url.startsWith("https://")) {
73
- console.warn(`[agentikit] Ignoring AKM_REGISTRY_URL entry: must start with http:// or https://, got "${url}"`);
73
+ console.warn(`[akm] Ignoring AKM_REGISTRY_URL entry: must start with http:// or https://, got "${url}"`);
74
74
  continue;
75
75
  }
76
76
  entries.push({ url });
@@ -5,13 +5,13 @@ import { loadConfig } from "./config";
5
5
  import { warn } from "./warn";
6
6
  // ── Resolution ──────────────────────────────────────────────────────────────
7
7
  /**
8
- * Build the ordered list of stash sources (search paths):
8
+ * Build the ordered list of stash sources:
9
9
  * 1. Primary stash dir (user's own, destination for clone)
10
- * 2. Additional search paths (user-configured)
10
+ * 2. Additional stashes (filesystem and remote providers)
11
11
  * 3. Installed kit paths (cache-managed, from registry)
12
12
  *
13
13
  * The first entry is always the primary stash. Additional entries come
14
- * from `searchPaths` config and `installed` kit entries.
14
+ * from `stashes` config and `installed` kit entries.
15
15
  */
16
16
  export function resolveStashSources(overrideStashDir, existingConfig) {
17
17
  const stashDir = overrideStashDir ?? resolveStashDir();
@@ -30,10 +30,6 @@ export function resolveStashSources(overrideStashDir, existingConfig) {
30
30
  sources.push({ path: resolved, ...(registryId ? { registryId } : {}) });
31
31
  }
32
32
  };
33
- // Legacy: searchPaths[]
34
- for (const dir of config.searchPaths) {
35
- addSource(dir);
36
- }
37
33
  // Filesystem entries from stashes[]
38
34
  for (const entry of config.stashes ?? []) {
39
35
  if (entry.type === "filesystem" && entry.path && entry.enabled !== false) {
@@ -78,7 +74,7 @@ export function getPrimarySource(sources) {
78
74
  * managed by the package manager (`installed[].cacheDir`). These
79
75
  * will be overwritten by `akm update` without warning.
80
76
  *
81
- * Everything else — working stash, search paths, local project dirs — is
77
+ * Everything else — working stash, additional stashes, local project dirs — is
82
78
  * the user's domain to manage.
83
79
  */
84
80
  export function isEditable(filePath, config) {
package/dist/stash-add.js CHANGED
@@ -3,14 +3,33 @@ import path from "node:path";
3
3
  import { resolveStashDir } from "./common";
4
4
  import { loadConfig, saveConfig } from "./config";
5
5
  import { UsageError } from "./errors";
6
- import { agentikitIndex } from "./indexer";
6
+ import { akmIndex } from "./indexer";
7
7
  import { upsertLockEntry } from "./lockfile";
8
8
  import { detectStashRoot, installRegistryRef, upsertInstalledRegistryEntry } from "./registry-install";
9
9
  import { parseRegistryRef } from "./registry-resolve";
10
- export async function agentikitAdd(input) {
10
+ export async function akmKitAdd(input) {
11
11
  const ref = input.ref.trim();
12
12
  if (!ref)
13
- throw new UsageError("Install ref or local directory is required.");
13
+ throw new UsageError("Registry ref is required. " + "Examples: `akm kit add @scope/kit`, `akm kit add github:owner/repo`");
14
+ const stashDir = resolveStashDir();
15
+ try {
16
+ const parsed = parseRegistryRef(ref);
17
+ if (parsed.source === "local") {
18
+ throw new UsageError(`Local directories should be added as stashes, not kits. Use \`akm stash add ${ref}\` instead.`);
19
+ }
20
+ }
21
+ catch (err) {
22
+ if (err instanceof UsageError)
23
+ throw err;
24
+ // Not a local ref — fall through to registry install
25
+ }
26
+ return addRegistryKit(ref, stashDir);
27
+ }
28
+ export async function akmAdd(input) {
29
+ const ref = input.ref.trim();
30
+ if (!ref)
31
+ throw new UsageError("Install ref or local directory is required. " +
32
+ "Examples: `akm add @scope/kit`, `akm add github:owner/repo`, `akm add ./local/path`");
14
33
  const stashDir = resolveStashDir();
15
34
  // Detect local directory refs and route them to stashes[] instead of installed[]
16
35
  try {
@@ -44,7 +63,7 @@ async function addLocalStashSource(ref, sourcePath, stashDir) {
44
63
  stashes.push(entry);
45
64
  saveConfig({ ...config, stashes });
46
65
  }
47
- const index = await agentikitIndex({ stashDir });
66
+ const index = await akmIndex({ stashDir });
48
67
  const updatedConfig = loadConfig();
49
68
  return {
50
69
  schemaVersion: 1,
@@ -57,7 +76,7 @@ async function addLocalStashSource(ref, sourcePath, stashDir) {
57
76
  stashRoot: resolvedPath,
58
77
  },
59
78
  config: {
60
- searchPaths: updatedConfig.searchPaths,
79
+ stashCount: updatedConfig.stashes?.length ?? 0,
61
80
  installedKitCount: updatedConfig.installed?.length ?? 0,
62
81
  },
63
82
  index: {
@@ -102,7 +121,7 @@ async function addRegistryKit(ref, stashDir) {
102
121
  // Best-effort cleanup only.
103
122
  }
104
123
  }
105
- const index = await agentikitIndex({ stashDir });
124
+ const index = await akmIndex({ stashDir });
106
125
  return {
107
126
  schemaVersion: 1,
108
127
  stashDir,
@@ -120,7 +139,7 @@ async function addRegistryKit(ref, stashDir) {
120
139
  installedAt: installed.installedAt,
121
140
  },
122
141
  config: {
123
- searchPaths: config.searchPaths,
142
+ stashCount: config.stashes?.length ?? 0,
124
143
  installedKitCount: config.installed?.length ?? 0,
125
144
  },
126
145
  index: {
@@ -1,12 +1,13 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { TYPE_DIRS } from "./asset-spec";
4
+ import { UsageError } from "./errors";
4
5
  import { isRemoteOrigin, resolveSourcesForOrigin } from "./origin-resolve";
5
6
  import { installRegistryRef } from "./registry-install";
7
+ import { findSourceForPath, getPrimarySource, resolveStashSources } from "./search-source";
6
8
  import { makeAssetRef, parseAssetRef } from "./stash-ref";
7
9
  import { resolveAssetPath } from "./stash-resolve";
8
- import { findSourceForPath, getPrimarySource, resolveStashSources } from "./stash-source";
9
- export async function agentikitClone(options) {
10
+ export async function akmClone(options) {
10
11
  const parsed = parseAssetRef(options.sourceRef);
11
12
  // When --dest is provided, the working stash is optional
12
13
  let allSources;
@@ -61,6 +62,31 @@ export async function agentikitClone(options) {
61
62
  const sourceSource = findSourceForPath(sourcePath, allSources);
62
63
  const destName = options.newName ?? parsed.name;
63
64
  const typeDir = TYPE_DIRS[parsed.type];
65
+ // Validate destName to prevent path traversal (parsed.name is already
66
+ // validated by parseAssetRef, but newName comes directly from user input).
67
+ // Run whenever newName is provided, including empty string.
68
+ if (options.newName !== undefined) {
69
+ if (destName === "") {
70
+ throw new UsageError("Clone name must not be empty.");
71
+ }
72
+ const normalized = path.posix.normalize(destName.replace(/\\/g, "/"));
73
+ if (path.isAbsolute(destName) ||
74
+ normalized === "." ||
75
+ normalized.startsWith("../") ||
76
+ normalized === ".." ||
77
+ destName.includes("\0")) {
78
+ throw new UsageError(`Unsafe clone name "${destName}": must not contain path traversal or absolute paths.`);
79
+ }
80
+ // Ensure the resolved destination is strictly inside the type directory,
81
+ // not equal to it (which can happen with crafted multi-segment names).
82
+ // path.relative() is used instead of startsWith() for cross-platform safety.
83
+ const destTypeDir = path.resolve(path.join(destRoot, typeDir));
84
+ const resolvedDest = path.resolve(path.join(destRoot, typeDir, destName));
85
+ const rel = path.relative(destTypeDir, resolvedDest);
86
+ if (rel === "" || rel.startsWith("..")) {
87
+ throw new UsageError(`Unsafe clone name "${destName}": resolves outside the target type directory.`);
88
+ }
89
+ }
64
90
  const destLabel = options.dest ? "at destination" : "in working stash";
65
91
  // Guard against self-clone
66
92
  if (parsed.type === "skill") {
@@ -1,9 +1,9 @@
1
1
  import { resolveStashDir } from "../common";
2
2
  import { loadConfig } from "../config";
3
3
  import { searchLocal } from "../local-search";
4
+ import { resolveStashSources } from "../search-source";
4
5
  import { registerStashProvider } from "../stash-provider-factory";
5
6
  import { showLocal } from "../stash-show";
6
- import { resolveStashSources } from "../stash-source";
7
7
  class FilesystemStashProvider {
8
8
  type = "filesystem";
9
9
  name;
@@ -16,7 +16,7 @@ const QUERY_CACHE_TTL_MS = 5 * 60 * 1000;
16
16
  /** Maximum age before query cache is considered stale but still usable (1 hour). */
17
17
  const QUERY_CACHE_STALE_MS = 60 * 60 * 1000;
18
18
  /**
19
- * Single source of truth for OpenViking type → agentikit asset type mapping.
19
+ * Single source of truth for OpenViking type → akm asset type mapping.
20
20
  * Used by both search hit mapping and show response mapping.
21
21
  */
22
22
  const OV_TYPE_MAP = {
@@ -5,9 +5,9 @@ import { resolveStashProviders } from "./stash-provider-factory";
5
5
  import "./stash-providers/index";
6
6
  import { UsageError } from "./errors";
7
7
  import { searchRegistry } from "./registry-search";
8
- import { resolveStashSources } from "./stash-source";
8
+ import { resolveStashSources } from "./search-source";
9
9
  const DEFAULT_LIMIT = 20;
10
- export async function agentikitSearch(input) {
10
+ export async function akmSearch(input) {
11
11
  const t0 = Date.now();
12
12
  const query = input.query.trim();
13
13
  const normalizedQuery = query.toLowerCase();
@@ -24,7 +24,7 @@ export async function agentikitSearch(input) {
24
24
  stashDir: "",
25
25
  source,
26
26
  hits: [],
27
- warnings: ["No stash sources configured. Run `akm init` first."],
27
+ warnings: ["No stashes configured. Run `akm init` to create your working stash."],
28
28
  timing: { totalMs: Date.now() - t0 },
29
29
  };
30
30
  }
@@ -2,17 +2,17 @@ import { loadConfig } from "./config";
2
2
  import { NotFoundError, UsageError } from "./errors";
3
3
  import { buildFileContext, buildRenderContext, getRenderer, runMatchers } from "./file-context";
4
4
  import { resolveSourcesForOrigin } from "./origin-resolve";
5
+ import { buildEditHint, findSourceForPath, isEditable, resolveStashSources } from "./search-source";
5
6
  import { resolveStashProviders } from "./stash-provider-factory";
6
7
  import { parseAssetRef } from "./stash-ref";
7
8
  import { resolveAssetPath } from "./stash-resolve";
8
- import { buildEditHint, findSourceForPath, isEditable, resolveStashSources } from "./stash-source";
9
9
  // Eagerly import stash providers to trigger self-registration
10
10
  import "./stash-providers/index";
11
11
  /**
12
12
  * Unified show: routes to the first stash provider that can handle the ref.
13
13
  * viking:// refs are handled by OpenViking provider; everything else by filesystem show.
14
14
  */
15
- export async function agentikitShowUnified(input) {
15
+ export async function akmShowUnified(input) {
16
16
  const ref = input.ref.trim();
17
17
  // Try stash providers first (e.g. OpenViking for viking:// URIs)
18
18
  const config = loadConfig();
@@ -23,7 +23,7 @@ export async function agentikitShowUnified(input) {
23
23
  // Default: local filesystem show
24
24
  return showLocal(input);
25
25
  }
26
- /** @internal Use agentikitShowUnified() for all external callers. */
26
+ /** @internal Use akmShowUnified() for all external callers. */
27
27
  export async function showLocal(input) {
28
28
  const parsed = parseAssetRef(input.ref);
29
29
  const displayType = parsed.type;
@@ -48,12 +48,15 @@ export async function showLocal(input) {
48
48
  `Kit "${parsed.origin}" is not installed. Run: ${installCmd}`);
49
49
  }
50
50
  if (!assetPath) {
51
- throw lastError ?? new NotFoundError(`Stash asset not found for ref: ${displayType}:${parsed.name}`);
51
+ throw (lastError ??
52
+ new NotFoundError(`Stash asset not found for ref: ${displayType}:${parsed.name}. ` +
53
+ "Check the name with `akm search` or verify the asset exists in your stash."));
52
54
  }
53
55
  const source = findSourceForPath(assetPath, allSources);
54
56
  const sourceStashDir = source?.path ?? allStashDirs[0];
55
57
  if (!sourceStashDir) {
56
- throw new UsageError(`Could not determine stash root for asset: ${displayType}:${parsed.name}`);
58
+ throw new UsageError(`Could not determine stash root for asset: ${displayType}:${parsed.name}. ` +
59
+ "Run `akm init` to create the stash directory, or check `akm stash list` for configured paths.");
57
60
  }
58
61
  const fileCtx = buildFileContext(sourceStashDir, assetPath);
59
62
  const match = await runMatchers(fileCtx);
@@ -1,7 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { loadConfig, saveConfig } from "./config";
3
3
  import { UsageError } from "./errors";
4
- import { resolveStashSources } from "./stash-source";
4
+ import { resolveStashSources } from "./search-source";
5
5
  // ── Operations ──────────────────────────────────────────────────────────────
6
6
  /**
7
7
  * Add a stash source (filesystem path or remote provider URL) to config.
@@ -10,7 +10,7 @@ import { resolveStashSources } from "./stash-source";
10
10
  * `http://` or `https://`. URL sources require a `providerType` option
11
11
  * (e.g. "openviking").
12
12
  */
13
- export function addStashSource(opts) {
13
+ export function addStash(opts) {
14
14
  const { target, name, providerType, options: providerOptions } = opts;
15
15
  const config = loadConfig();
16
16
  const stashes = [...(config.stashes ?? [])];
@@ -48,7 +48,7 @@ export function addStashSource(opts) {
48
48
  * Remove a stash source by URL, path, or name.
49
49
  * Match priority: URL > path > name (most specific first).
50
50
  */
51
- export function removeStashSource(target) {
51
+ export function removeStash(target) {
52
52
  const config = loadConfig();
53
53
  const stashes = [...(config.stashes ?? [])];
54
54
  const isUrl = target.startsWith("http://") || target.startsWith("https://");
@@ -74,7 +74,7 @@ export function removeStashSource(target) {
74
74
  /**
75
75
  * List all stash sources (local filesystem + configured stashes).
76
76
  */
77
- export function listStashSources() {
77
+ export function listStashes() {
78
78
  const config = loadConfig();
79
79
  const localSources = resolveStashSources();
80
80
  const stashes = config.stashes ?? [];
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "akm-cli",
3
- "version": "0.0.23",
3
+ "version": "0.1.0",
4
4
  "type": "module",
5
5
  "description": "CLI tool to search, open, and run extension assets from an akm stash directory.",
6
6
  "keywords": [
7
7
  "akm",
8
- "agent-i-kit",
8
+ "akm-kit",
9
9
  "ai-agent",
10
10
  "agent-framework",
11
11
  "developer-tools",