agentikit 0.0.14 → 0.0.15

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 (46) hide show
  1. package/README.md +14 -7
  2. package/dist/asset-spec.js +11 -2
  3. package/dist/asset-type-handler.js +4 -3
  4. package/dist/cli.js +100 -62
  5. package/dist/common.js +6 -6
  6. package/dist/config-cli.js +23 -25
  7. package/dist/config.js +3 -1
  8. package/dist/db.js +28 -30
  9. package/dist/errors.js +28 -0
  10. package/dist/file-context.js +36 -6
  11. package/dist/frontmatter.js +1 -1
  12. package/dist/github.js +1 -3
  13. package/dist/handlers/agent-handler.js +6 -13
  14. package/dist/handlers/command-handler.js +8 -13
  15. package/dist/handlers/handler-bridge.js +51 -0
  16. package/dist/handlers/index.js +12 -10
  17. package/dist/handlers/knowledge-handler.js +7 -31
  18. package/dist/handlers/script-handler.js +9 -45
  19. package/dist/handlers/skill-handler.js +5 -6
  20. package/dist/handlers/tool-handler.js +8 -24
  21. package/dist/indexer.js +21 -21
  22. package/dist/init.js +3 -3
  23. package/dist/llm.js +6 -11
  24. package/dist/lockfile.js +9 -4
  25. package/dist/matchers.js +11 -5
  26. package/dist/metadata.js +25 -16
  27. package/dist/paths.js +5 -4
  28. package/dist/registry-install.js +9 -5
  29. package/dist/registry-resolve.js +12 -8
  30. package/dist/registry-search.js +5 -5
  31. package/dist/renderers.js +24 -14
  32. package/dist/ripgrep-install.js +3 -22
  33. package/dist/ripgrep.js +1 -1
  34. package/dist/self-update.js +15 -9
  35. package/dist/stash-add.js +4 -3
  36. package/dist/stash-clone.js +4 -4
  37. package/dist/stash-ref.js +10 -9
  38. package/dist/stash-registry.js +6 -5
  39. package/dist/stash-resolve.js +10 -9
  40. package/dist/stash-search.js +27 -24
  41. package/dist/stash-show.js +8 -7
  42. package/dist/stash-source.js +10 -11
  43. package/dist/submit.js +26 -21
  44. package/dist/tool-runner.js +1 -5
  45. package/dist/warn.js +20 -0
  46. package/package.json +7 -3
package/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Agent-i-Kit
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/agentikit)](https://www.npmjs.com/package/agentikit)
4
+ [![CI](https://github.com/itlackey/agentikit/actions/workflows/ci.yml/badge.svg)](https://github.com/itlackey/agentikit/actions/workflows/ci.yml)
5
+ [![license](https://img.shields.io/npm/l/agentikit)](LICENSE)
6
+
3
7
  A package manager for AI agent capabilities — tools, skills, commands, agents,
4
8
  knowledge, and scripts — that works with any AI coding assistant that can run
5
9
  shell commands.
@@ -9,6 +13,11 @@ organize them into a searchable **stash**, share them as installable **kits**,
9
13
  and give any model a way to discover and use them through `akm` (Agent Kit
10
14
  Manager). No plugins required — just CLI output any tool-calling model can read.
11
15
 
16
+ ## Requirements
17
+
18
+ Agent-i-Kit requires [Bun](https://bun.sh) v1.0+ as its runtime. It uses
19
+ Bun-specific APIs (`bun:sqlite`) that are **not available in Node.js**.
20
+
12
21
  ## Quick Start
13
22
 
14
23
  ```sh
@@ -46,10 +55,9 @@ Add this to your `AGENTS.md`, `CLAUDE.md`, system prompt, or any instruction
46
55
  file to give your agent access to your stash without any additional setup:
47
56
 
48
57
  ~~~markdown
49
- ## Agent-i-Kit
50
58
 
51
59
  You have access to a searchable library of tools, skills, commands, agents,
52
- and knowledge documents via `akm` (Agent Kit Manager). Use it to find and
60
+ and knowledge documents via `akm`. Use it to find and
53
61
  use capabilities before writing something from scratch.
54
62
 
55
63
  **Finding assets:**
@@ -277,11 +285,10 @@ akm upgrade --check # Check for updates without installing
277
285
 
278
286
  ## Status
279
287
 
280
- Agent-i-Kit is in early development (v0.0.x). The core CLI, stash model, and
281
- registry are functional and in daily use. Feedback, issues, and PRs welcome —
282
- especially around real-world usage patterns and integrations with different
283
- AI coding assistants.
288
+ Agent-i-Kit is approaching v1.0. The core CLI, stash model, and registry are
289
+ stable and in daily use. Feedback, issues, and PRs welcome — especially around
290
+ real-world usage patterns and integrations with different AI coding assistants.
284
291
 
285
292
  ## License
286
293
 
287
- [CC-BY-4.0](LICENSE)
294
+ [MPL-2.0](LICENSE)
@@ -1,6 +1,6 @@
1
1
  import path from "node:path";
2
- import { toPosix } from "./common";
3
2
  import { tryGetHandler } from "./asset-type-handler";
3
+ import { toPosix } from "./common";
4
4
  export const SCRIPT_EXTENSIONS = new Set([".sh", ".ts", ".js", ".ps1", ".cmd", ".bat"]);
5
5
  const markdownSpec = {
6
6
  isRelevantFile: (fileName) => path.extname(fileName).toLowerCase() === ".md",
@@ -10,7 +10,16 @@ const markdownSpec = {
10
10
  /** Extended set of script extensions for the script asset type */
11
11
  export const SCRIPT_EXTENSIONS_BROAD = new Set([
12
12
  ...SCRIPT_EXTENSIONS,
13
- ".py", ".rb", ".go", ".pl", ".php", ".lua", ".r", ".swift", ".kt", ".kts",
13
+ ".py",
14
+ ".rb",
15
+ ".go",
16
+ ".pl",
17
+ ".php",
18
+ ".lua",
19
+ ".r",
20
+ ".swift",
21
+ ".kt",
22
+ ".kts",
14
23
  ]);
15
24
  export const ASSET_SPECS = {
16
25
  tool: {
@@ -1,3 +1,5 @@
1
+ import { UsageError } from "./errors";
2
+ import { registerBuiltinHandlers } from "./handlers/index";
1
3
  // ── Registry ─────────────────────────────────────────────────────────────────
2
4
  const handlers = new Map();
3
5
  let handlersInitialized = false;
@@ -5,8 +7,7 @@ function ensureHandlersRegistered() {
5
7
  if (handlersInitialized)
6
8
  return;
7
9
  handlersInitialized = true;
8
- // Import handler registrations
9
- require("./handlers/index");
10
+ registerBuiltinHandlers();
10
11
  }
11
12
  export function registerAssetType(handler) {
12
13
  handlers.set(handler.typeName, handler);
@@ -15,7 +16,7 @@ export function getHandler(type) {
15
16
  ensureHandlersRegistered();
16
17
  const handler = handlers.get(type);
17
18
  if (!handler) {
18
- throw new Error(`Unknown asset type: "${type}"`);
19
+ throw new UsageError(`Unknown asset type: "${type}"`);
19
20
  }
20
21
  return handler;
21
22
  }
package/dist/cli.js CHANGED
@@ -2,27 +2,56 @@
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { defineCommand, runMain } from "citty";
5
+ import { resolveStashDir } from "./common";
6
+ import { getConfigPath, loadConfig, saveConfig } from "./config";
7
+ import { getConfigValue, listConfig, listProviders, parseConfigValue, setConfigValue, unsetConfigValue, useProvider, } from "./config-cli";
8
+ import { ConfigError, NotFoundError, UsageError } from "./errors";
9
+ import { agentikitIndex } from "./indexer";
10
+ import { agentikitInit } from "./init";
11
+ import { getCacheDir, getDbPath, getDefaultStashDir } from "./paths";
12
+ import { checkForUpdate, performUpgrade } from "./self-update";
5
13
  import { agentikitAdd } from "./stash-add";
14
+ import { agentikitClone } from "./stash-clone";
6
15
  import { agentikitList, agentikitRemove, agentikitUpdate } from "./stash-registry";
7
16
  import { agentikitSearch } from "./stash-search";
8
17
  import { agentikitShow } from "./stash-show";
9
- import { checkForUpdate, performUpgrade } from "./self-update";
10
- import { agentikitInit } from "./init";
11
- import { agentikitIndex } from "./indexer";
12
- import { agentikitClone } from "./stash-clone";
13
- import { agentikitSubmit } from "./submit";
14
18
  import { resolveStashSources } from "./stash-source";
15
- import { loadConfig, saveConfig, getConfigPath } from "./config";
16
- import { getConfigValue, listConfig, listProviders, parseConfigValue, setConfigValue, unsetConfigValue, useProvider, } from "./config-cli";
17
- import { getCacheDir, getDbPath, getDefaultStashDir } from "./paths";
18
- import { resolveStashDir } from "./common";
19
- // Read version from package.json
20
- const pkgPath = path.resolve(import.meta.dir ?? __dirname, "../package.json");
21
- const pkgVersion = JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version;
19
+ import { agentikitSubmit } from "./submit";
20
+ import { setQuiet, warn } from "./warn";
21
+ // Version: prefer compile-time define, then package.json, then fallback
22
+ const pkgVersion = (() => {
23
+ // Injected at compile time via `bun build --define`
24
+ if (typeof AKM_VERSION !== "undefined")
25
+ return AKM_VERSION;
26
+ try {
27
+ const pkgPath = path.resolve(import.meta.dir ?? __dirname, "../package.json");
28
+ if (fs.existsSync(pkgPath)) {
29
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
30
+ if (typeof pkg.version === "string")
31
+ return pkg.version;
32
+ }
33
+ }
34
+ catch {
35
+ // swallow — running as compiled binary without package.json
36
+ }
37
+ return "0.0.0-dev";
38
+ })();
22
39
  /** Check whether --json flag is present in argv */
23
40
  function isJsonMode() {
24
41
  return process.argv.includes("--json");
25
42
  }
43
+ function hasBunYAML(b) {
44
+ // biome-ignore lint/suspicious/noExplicitAny: type guard for runtime feature detection
45
+ return typeof b.YAML?.stringify === "function";
46
+ }
47
+ /** Try Bun.YAML.stringify; fall back to JSON if the API is unavailable */
48
+ function yamlStringify(obj) {
49
+ if (hasBunYAML(Bun)) {
50
+ return Bun.YAML.stringify(obj);
51
+ }
52
+ warn("YAML output not available, using JSON");
53
+ return JSON.stringify(obj, null, 2);
54
+ }
26
55
  /** Output result: JSON if --json flag set, otherwise YAML (default) */
27
56
  function output(command, result) {
28
57
  if (isJsonMode()) {
@@ -35,7 +64,7 @@ function output(command, result) {
35
64
  console.log(plain);
36
65
  return;
37
66
  }
38
- console.log(Bun.YAML.stringify(result, null, 4));
67
+ console.log(yamlStringify(result));
39
68
  }
40
69
  /**
41
70
  * Return a plain-text string for commands that are better as short messages,
@@ -117,7 +146,7 @@ function formatPlain(command, result) {
117
146
  const lines = [
118
147
  `Dry run: prepared registry entry ${entry?.name ?? entry?.id ?? "unknown"}`,
119
148
  "",
120
- Bun.YAML.stringify(entry, null, 4),
149
+ yamlStringify(entry),
121
150
  ];
122
151
  if (commands.length > 0) {
123
152
  lines.push("", "Would run:");
@@ -139,7 +168,10 @@ function formatPlain(command, result) {
139
168
  }
140
169
  }
141
170
  const initCommand = defineCommand({
142
- meta: { name: "init", description: "Initialize Agent-i-Kit's working stash directory and persist stashDir in config" },
171
+ meta: {
172
+ name: "init",
173
+ description: "Initialize Agent-i-Kit's working stash directory and persist stashDir in config",
174
+ },
143
175
  args: {
144
176
  dir: { type: "string", description: "Custom stash directory path (default: ~/agentikit)" },
145
177
  },
@@ -281,7 +313,7 @@ const showCommand = defineCommand({
281
313
  view = { mode: args.view };
282
314
  break;
283
315
  default:
284
- throw new Error(`Unknown view mode: ${args.view}. Expected one of: full|toc|frontmatter|section|lines`);
316
+ throw new UsageError(`Unknown view mode: ${args.view}. Expected one of: full|toc|frontmatter|section|lines`);
285
317
  }
286
318
  }
287
319
  const result = await agentikitShow({ ref: args.ref, view });
@@ -424,7 +456,7 @@ const configCommand = defineCommand({
424
456
  if (args.set) {
425
457
  const eqIndex = args.set.indexOf("=");
426
458
  if (eqIndex === -1) {
427
- throw new Error("--set expects key=value format");
459
+ throw new UsageError("--set expects key=value format");
428
460
  }
429
461
  const key = args.set.slice(0, eqIndex);
430
462
  const value = args.set.slice(eqIndex + 1);
@@ -440,7 +472,10 @@ const configCommand = defineCommand({
440
472
  },
441
473
  });
442
474
  const cloneCommand = defineCommand({
443
- meta: { name: "clone", description: "Clone an asset from any stash source into the working stash or a custom destination" },
475
+ meta: {
476
+ name: "clone",
477
+ description: "Clone an asset from any stash source into the working stash or a custom destination",
478
+ },
444
479
  args: {
445
480
  ref: { type: "positional", description: "Asset ref (e.g. @installed:pkg/tool:script.sh)", required: true },
446
481
  name: { type: "string", description: "New name for the cloned asset" },
@@ -462,7 +497,11 @@ const cloneCommand = defineCommand({
462
497
  const submitCommand = defineCommand({
463
498
  meta: { name: "submit", description: "Submit a kit to agentikit-registry by opening a pull request" },
464
499
  args: {
465
- ref: { type: "positional", description: "Public ref to submit (npm package, owner/repo, or local kit directory)", required: false },
500
+ ref: {
501
+ type: "positional",
502
+ description: "Public ref to submit (npm package, owner/repo, or local kit directory)",
503
+ required: false,
504
+ },
466
505
  name: { type: "string", description: "Display name for the registry entry" },
467
506
  description: { type: "string", description: "Short description for the registry entry" },
468
507
  tags: { type: "string", description: "Comma-separated tags" },
@@ -470,8 +509,16 @@ const submitCommand = defineCommand({
470
509
  author: { type: "string", description: "Author name" },
471
510
  license: { type: "string", description: "License identifier" },
472
511
  homepage: { type: "string", description: "Homepage URL" },
473
- "dry-run": { type: "boolean", description: "Preview the entry and gh commands without creating a pull request", default: false },
474
- "cleanup-fork": { type: "boolean", description: "Show the fork cleanup command after the pull request is created (run it after the PR is merged)", default: false },
512
+ "dry-run": {
513
+ type: "boolean",
514
+ description: "Preview the entry and gh commands without creating a pull request",
515
+ default: false,
516
+ },
517
+ "cleanup-fork": {
518
+ type: "boolean",
519
+ description: "Show the fork cleanup command after the pull request is created (run it after the PR is merged)",
520
+ default: false,
521
+ },
475
522
  },
476
523
  async run({ args }) {
477
524
  await runWithJsonErrors(async () => {
@@ -509,6 +556,7 @@ const main = defineCommand({
509
556
  },
510
557
  args: {
511
558
  json: { type: "boolean", description: "Output in JSON format", default: false },
559
+ quiet: { type: "boolean", alias: "q", description: "Suppress stderr warnings", default: false },
512
560
  },
513
561
  subCommands: {
514
562
  init: initCommand,
@@ -531,48 +579,43 @@ const SEARCH_SOURCES = ["local", "registry", "both"];
531
579
  const CONFIG_SUBCOMMAND_SET = new Set(["path", "list", "get", "set", "unset", "providers", "use"]);
532
580
  // citty reads process.argv directly and does not accept a custom argv array,
533
581
  // so we must replace process.argv with the normalized version before runMain.
534
- const normalizedArgv = [...process.argv];
535
- normalizeConfigArgv(normalizedArgv);
536
- process.argv = normalizedArgv;
582
+ process.argv = normalizeConfigArgv(process.argv);
537
583
  runMain(main);
538
584
  function parseSearchUsageMode(value) {
539
585
  if (SEARCH_USAGE_MODES.includes(value))
540
586
  return value;
541
- throw new Error(`Invalid value for --usage: ${value}. Expected one of: ${SEARCH_USAGE_MODES.join("|")}`);
587
+ throw new UsageError(`Invalid value for --usage: ${value}. Expected one of: ${SEARCH_USAGE_MODES.join("|")}`);
542
588
  }
543
589
  function parseSearchSource(value) {
544
590
  if (SEARCH_SOURCES.includes(value))
545
591
  return value;
546
- throw new Error(`Invalid value for --source: ${value}. Expected one of: ${SEARCH_SOURCES.join("|")}`);
592
+ throw new UsageError(`Invalid value for --source: ${value}. Expected one of: ${SEARCH_SOURCES.join("|")}`);
547
593
  }
548
594
  // ── Exit codes ──────────────────────────────────────────────────────────────
549
595
  const EXIT_GENERAL = 1;
550
596
  const EXIT_USAGE = 2;
551
597
  const EXIT_CONFIG = 78;
552
- function classifyExitCode(message) {
553
- // Usage / argument errors
554
- if (message.includes("required") ||
555
- message.includes("Invalid value for") ||
556
- message.includes("Expected one of") ||
557
- message.includes("expected JSON object")) {
598
+ function classifyExitCode(error) {
599
+ if (error instanceof UsageError)
558
600
  return EXIT_USAGE;
559
- }
560
- // Configuration errors
561
- if (message.includes("No stash directory found") ||
562
- message.includes("Unable to determine") ||
563
- message.includes("config")) {
601
+ if (error instanceof ConfigError)
564
602
  return EXIT_CONFIG;
565
- }
603
+ if (error instanceof NotFoundError)
604
+ return EXIT_GENERAL;
566
605
  return EXIT_GENERAL;
567
606
  }
568
607
  async function runWithJsonErrors(fn) {
569
608
  try {
609
+ // Apply --quiet flag early so warnings inside the command are suppressed
610
+ if (process.argv.includes("--quiet") || process.argv.includes("-q")) {
611
+ setQuiet(true);
612
+ }
570
613
  await fn();
571
614
  }
572
615
  catch (error) {
573
616
  const message = error instanceof Error ? error.message : String(error);
574
617
  const hint = buildHint(message);
575
- const exitCode = classifyExitCode(message);
618
+ const exitCode = classifyExitCode(error);
576
619
  console.error(JSON.stringify({ ok: false, error: message, hint }, null, 2));
577
620
  process.exit(exitCode);
578
621
  }
@@ -600,11 +643,12 @@ function buildHint(message) {
600
643
  return "Check that the npm package is published or the GitHub repository is public, then retry.";
601
644
  if (message.includes("already exists in agentikit-registry"))
602
645
  return "Update the existing registry entry instead of creating a duplicate, or choose a different public ref.";
603
- if (message.includes("Unable to infer a public npm or GitHub ref") || message.includes("Unable to infer a publicly accessible npm package or GitHub repository")) {
646
+ if (message.includes("Unable to infer a public npm or GitHub ref") ||
647
+ message.includes("Unable to infer a publicly accessible npm package or GitHub repository")) {
604
648
  return "Run `akm submit <package-or-owner/repo>` explicitly, or add name/repository metadata to package.json.";
605
649
  }
606
650
  if (message.includes("expected JSON object with endpoint and model")) {
607
- return "Quote JSON values in your shell, for example: akm config set embedding '{\"endpoint\":\"http://localhost:11434/v1/embeddings\",\"model\":\"nomic-embed-text\"}'.";
651
+ return 'Quote JSON values in your shell, for example: akm config set embedding \'{"endpoint":"http://localhost:11434/v1/embeddings","model":"nomic-embed-text"}\'.';
608
652
  }
609
653
  return undefined;
610
654
  }
@@ -618,7 +662,7 @@ function buildGhInstallHint() {
618
662
  function parseProviderScope(value) {
619
663
  if (value === "embedding" || value === "llm")
620
664
  return value;
621
- throw new Error(`Invalid provider scope: ${value}. Expected one of: embedding|llm`);
665
+ throw new UsageError(`Invalid provider scope: ${value}. Expected one of: embedding|llm`);
622
666
  }
623
667
  function hasConfigSubcommand(args) {
624
668
  const command = Array.isArray(args._) ? args._[0] : undefined;
@@ -629,43 +673,37 @@ function hasConfigSubcommand(args) {
629
673
  * `akm config llm.maxTokens 512` and `akm config --get llm.maxTokens`
630
674
  * are normalized into the existing config subcommands.
631
675
  *
632
- * Operates on a copy of process.argv; the caller replaces process.argv
633
- * with the normalized result (safer than in-place splice).
676
+ * Returns a new array; the input is never modified.
634
677
  */
635
678
  function normalizeConfigArgv(argv) {
636
- // Global flags (like --json) should not be treated as config subcommand arguments.
679
+ // Global flags (like --json, --quiet) should not be treated as config subcommand arguments.
637
680
  // We strip them from the analysis portion, normalize, then re-append them.
638
- const GLOBAL_FLAGS = new Set(["--json"]);
681
+ const GLOBAL_FLAGS = new Set(["--json", "--quiet", "-q"]);
639
682
  const globalFlags = argv.slice(3).filter((a) => GLOBAL_FLAGS.has(a));
640
683
  const configArgs = argv.slice(3).filter((a) => !GLOBAL_FLAGS.has(a));
641
684
  const [command, argAfterCommand, argAfterKey, ...rest] = [argv[2], ...configArgs];
642
685
  if (command !== "config")
643
- return;
686
+ return argv;
644
687
  if (!argAfterCommand)
645
- return;
646
- const replaceArgs = (...newArgs) => {
647
- argv.splice(3, argv.length - 3, ...newArgs, ...globalFlags);
648
- };
688
+ return argv;
689
+ const prefix = argv.slice(0, 3);
690
+ const buildResult = (...newArgs) => [...prefix, ...newArgs, ...globalFlags];
649
691
  if (argAfterCommand === "--list") {
650
- replaceArgs("list");
651
- return;
692
+ return buildResult("list");
652
693
  }
653
694
  if (argAfterCommand === "--get" && argAfterKey) {
654
- replaceArgs("get", argAfterKey, ...rest);
655
- return;
695
+ return buildResult("get", argAfterKey, ...rest);
656
696
  }
657
697
  if (argAfterCommand === "--unset" && argAfterKey) {
658
- replaceArgs("unset", argAfterKey, ...rest);
659
- return;
698
+ return buildResult("unset", argAfterKey, ...rest);
660
699
  }
661
700
  if (argAfterCommand.startsWith("-"))
662
- return;
701
+ return argv;
663
702
  if (CONFIG_SUBCOMMAND_SET.has(argAfterCommand))
664
- return;
703
+ return argv;
665
704
  // A single arg after `config` behaves like `git config <key>` and reads the value.
666
705
  if (argAfterKey === undefined) {
667
- replaceArgs("get", argAfterCommand);
668
- return;
706
+ return buildResult("get", argAfterCommand);
669
707
  }
670
- replaceArgs("set", argAfterCommand, argAfterKey, ...rest);
708
+ return buildResult("set", argAfterCommand, argAfterKey, ...rest);
671
709
  }
package/dist/common.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { TYPE_DIRS } from "./asset-spec";
4
+ import { ConfigError } from "./errors";
4
5
  import { getConfigPath, getDefaultStashDir } from "./paths";
5
6
  // ── Constants ───────────────────────────────────────────────────────────────
6
7
  export const IS_WINDOWS = process.platform === "win32";
7
- export { SCRIPT_EXTENSIONS, TYPE_DIRS } from "./asset-spec";
8
8
  // ── Validators ──────────────────────────────────────────────────────────────
9
9
  export function isAssetType(type) {
10
10
  return type in TYPE_DIRS;
@@ -36,7 +36,7 @@ export function resolveStashDir(options) {
36
36
  if (isValidDirectory(defaultDir)) {
37
37
  return defaultDir;
38
38
  }
39
- throw new Error(`No stash directory found. Run "akm init" to create one at ${defaultDir}, ` +
39
+ throw new ConfigError(`No stash directory found. Run "akm init" to create one at ${defaultDir}, ` +
40
40
  `or set stashDir in ${getConfigPath()}.`);
41
41
  }
42
42
  function validateStashDir(raw) {
@@ -46,10 +46,10 @@ function validateStashDir(raw) {
46
46
  stat = fs.statSync(stashDir);
47
47
  }
48
48
  catch {
49
- throw new Error(`Unable to read stash directory at "${stashDir}".`);
49
+ throw new ConfigError(`Unable to read stash directory at "${stashDir}".`);
50
50
  }
51
51
  if (!stat.isDirectory()) {
52
- throw new Error(`Stash path must point to a directory: "${stashDir}".`);
52
+ throw new ConfigError(`Stash path must point to a directory: "${stashDir}".`);
53
53
  }
54
54
  return stashDir;
55
55
  }
@@ -165,7 +165,7 @@ export async function fetchWithRetry(url, init, options) {
165
165
  const response = await fetchWithTimeout(url, init, timeout);
166
166
  if (attempt < maxRetries && shouldRetry(response.status)) {
167
167
  const retryAfter = parseRetryAfter(response);
168
- const delay = retryAfter ?? baseDelay * Math.pow(2, attempt) * (0.5 + Math.random() * 0.5);
168
+ const delay = retryAfter ?? baseDelay * 2 ** attempt * (0.5 + Math.random() * 0.5);
169
169
  await new Promise((r) => setTimeout(r, delay));
170
170
  continue;
171
171
  }
@@ -174,7 +174,7 @@ export async function fetchWithRetry(url, init, options) {
174
174
  catch (err) {
175
175
  if (attempt >= maxRetries)
176
176
  throw err;
177
- const delay = baseDelay * Math.pow(2, attempt) * (0.5 + Math.random() * 0.5);
177
+ const delay = baseDelay * 2 ** attempt * (0.5 + Math.random() * 0.5);
178
178
  await new Promise((r) => setTimeout(r, delay));
179
179
  }
180
180
  }
@@ -1,5 +1,6 @@
1
1
  import { DEFAULT_CONFIG, } from "./config";
2
2
  import { EMBEDDING_DIM } from "./db";
3
+ import { ConfigError, UsageError } from "./errors";
3
4
  const LOCAL_EMBEDDING_MODEL = "Xenova/all-MiniLM-L6-v2";
4
5
  const DEFAULT_LLM_TEMPERATURE = 0.3;
5
6
  const DEFAULT_LLM_MAX_TOKENS = 512;
@@ -63,25 +64,25 @@ export function parseConfigValue(key, value) {
63
64
  return { stashDir: requireNonEmptyString(value, key) };
64
65
  case "semanticSearch":
65
66
  if (value !== "true" && value !== "false") {
66
- throw new Error(`Invalid value for semanticSearch: expected "true" or "false"`);
67
+ throw new UsageError(`Invalid value for semanticSearch: expected "true" or "false"`);
67
68
  }
68
69
  return { semanticSearch: value === "true" };
69
70
  case "searchPaths":
70
71
  try {
71
72
  const parsed = JSON.parse(value);
72
73
  if (!Array.isArray(parsed))
73
- throw new Error("expected JSON array");
74
+ throw new UsageError("expected JSON array");
74
75
  return { searchPaths: parsed.filter((d) => typeof d === "string") };
75
76
  }
76
77
  catch {
77
- throw new Error(`Invalid value for searchPaths: expected JSON array (e.g. '["/path/a","/path/b"]')`);
78
+ throw new UsageError(`Invalid value for searchPaths: expected JSON array (e.g. '["/path/a","/path/b"]')`);
78
79
  }
79
80
  case "embedding":
80
81
  return { embedding: parseEmbeddingConnectionValue(value) };
81
82
  case "llm":
82
83
  return { llm: parseLlmConnectionValue(value) };
83
84
  default:
84
- throw new Error(`Unknown config key: ${key}`);
85
+ throw new UsageError(`Unknown config key: ${key}`);
85
86
  }
86
87
  }
87
88
  export function getConfigValue(config, key) {
@@ -119,7 +120,7 @@ export function getConfigValue(config, key) {
119
120
  case "llm.apiKey":
120
121
  return maskSecret(getLlmDisplayConfig(config).apiKey) ?? null;
121
122
  default:
122
- throw new Error(`Unknown config key: ${key}`);
123
+ throw new UsageError(`Unknown config key: ${key}`);
123
124
  }
124
125
  }
125
126
  export function setConfigValue(config, key, rawValue) {
@@ -207,7 +208,7 @@ export function setConfigValue(config, key, rawValue) {
207
208
  },
208
209
  };
209
210
  default:
210
- throw new Error(`Unknown config key: ${key}`);
211
+ throw new UsageError(`Unknown config key: ${key}`);
211
212
  }
212
213
  }
213
214
  export function unsetConfigValue(config, key) {
@@ -247,7 +248,7 @@ export function unsetConfigValue(config, key) {
247
248
  return config;
248
249
  return { ...config, llm: omitKey(config.llm, "provider") };
249
250
  default:
250
- throw new Error(`Unknown or unsupported unset key: ${key}`);
251
+ throw new UsageError(`Unknown or unsupported unset key: ${key}`);
251
252
  }
252
253
  }
253
254
  export function listConfig(config) {
@@ -273,7 +274,7 @@ export function useProvider(config, scope, providerName) {
273
274
  if (scope === "embedding") {
274
275
  const preset = EMBEDDING_PROVIDER_PRESETS[providerName];
275
276
  if (!preset) {
276
- throw new Error(`Unknown embedding provider: ${providerName}`);
277
+ throw new UsageError(`Unknown embedding provider: ${providerName}`);
277
278
  }
278
279
  if (!preset.config) {
279
280
  return { ...config, embedding: undefined };
@@ -282,7 +283,7 @@ export function useProvider(config, scope, providerName) {
282
283
  }
283
284
  const preset = LLM_PROVIDER_PRESETS[providerName];
284
285
  if (!preset) {
285
- throw new Error(`Unknown llm provider: ${providerName}`);
286
+ throw new UsageError(`Unknown llm provider: ${providerName}`);
286
287
  }
287
288
  if (!preset.config) {
288
289
  return { ...config, llm: undefined };
@@ -388,68 +389,65 @@ function parseJsonObject(value, key, example) {
388
389
  parsed = JSON.parse(value);
389
390
  }
390
391
  catch {
391
- throw new Error(`Invalid value for ${key}: expected JSON object with endpoint and model`
392
- + ` (e.g. '{"endpoint":"${example.endpoint}","model":"${example.model}"}')`);
392
+ throw new UsageError(`Invalid value for ${key}: expected JSON object with endpoint and model` +
393
+ ` (e.g. '{"endpoint":"${example.endpoint}","model":"${example.model}"}')`);
393
394
  }
394
395
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
395
- throw new Error(`Invalid value for ${key}: expected a JSON object`);
396
+ throw new UsageError(`Invalid value for ${key}: expected a JSON object`);
396
397
  }
397
398
  return parsed;
398
399
  }
399
400
  function asRequiredString(value, key, field) {
400
401
  if (typeof value !== "string" || !value) {
401
- throw new Error(`Invalid value for ${key}: "${field}" is a required string field`);
402
+ throw new UsageError(`Invalid value for ${key}: "${field}" is a required string field`);
402
403
  }
403
404
  return value;
404
405
  }
405
406
  function requireEmbeddingConfig(config) {
406
407
  if (!config.embedding) {
407
- throw new Error("Embedding provider is using the built-in local default. Run `akm config use embedding <provider>` first.");
408
+ throw new ConfigError("Embedding provider is using the built-in local default. Run `akm config use embedding <provider>` first.");
408
409
  }
409
410
  return config.embedding;
410
411
  }
411
412
  function requireLlmConfig(config) {
412
413
  if (!config.llm) {
413
- throw new Error("LLM provider is disabled. Run `akm config use llm <provider>` first.");
414
+ throw new ConfigError("LLM provider is disabled. Run `akm config use llm <provider>` first.");
414
415
  }
415
416
  return config.llm;
416
417
  }
417
418
  function requireNonEmptyString(value, key) {
418
419
  if (!value) {
419
- throw new Error(`Invalid value for ${key}: expected a non-empty string`);
420
+ throw new UsageError(`Invalid value for ${key}: expected a non-empty string`);
420
421
  }
421
422
  return value;
422
423
  }
423
424
  function parseNumber(value, key) {
424
425
  const parsed = Number(value);
425
426
  if (!Number.isFinite(parsed)) {
426
- throw new Error(`Invalid value for ${key}: expected a number`);
427
+ throw new UsageError(`Invalid value for ${key}: expected a number`);
427
428
  }
428
429
  return parsed;
429
430
  }
430
431
  function parsePositiveInteger(value, key) {
431
432
  const trimmed = value.trim();
432
433
  if (!/^[1-9]\d*$/.test(trimmed)) {
433
- throw new Error(`Invalid value for ${key}: expected a positive integer`);
434
+ throw new UsageError(`Invalid value for ${key}: expected a positive integer`);
434
435
  }
435
436
  const parsed = Number(trimmed);
436
437
  if (!Number.isFinite(parsed) || !Number.isInteger(parsed) || parsed <= 0) {
437
- throw new Error(`Invalid value for ${key}: expected a positive integer`);
438
+ throw new UsageError(`Invalid value for ${key}: expected a positive integer`);
438
439
  }
439
440
  return parsed;
440
441
  }
441
442
  function parseUnknownNumber(value, key) {
442
443
  if (typeof value !== "number" || !Number.isFinite(value)) {
443
- throw new Error(`Invalid value for ${key}: expected a number`);
444
+ throw new UsageError(`Invalid value for ${key}: expected a number`);
444
445
  }
445
446
  return value;
446
447
  }
447
448
  function parseUnknownPositiveInteger(value, key) {
448
- if (typeof value !== "number" ||
449
- !Number.isFinite(value) ||
450
- !Number.isInteger(value) ||
451
- value <= 0) {
452
- throw new Error(`Invalid value for ${key}: expected a positive integer`);
449
+ if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || value <= 0) {
450
+ throw new UsageError(`Invalid value for ${key}: expected a positive integer`);
453
451
  }
454
452
  return value;
455
453
  }
package/dist/config.js CHANGED
@@ -68,7 +68,9 @@ export function saveConfig(config) {
68
68
  try {
69
69
  fs.unlinkSync(tmpPath);
70
70
  }
71
- catch { /* ignore cleanup failure */ }
71
+ catch {
72
+ /* ignore cleanup failure */
73
+ }
72
74
  throw err;
73
75
  }
74
76
  }