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/dist/indexer.js CHANGED
@@ -1,10 +1,12 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import { ASSET_TYPES, deriveCanonicalAssetName, TYPE_DIRS } from "./asset-spec";
3
4
  import { resolveStashDir } from "./common";
4
- import { ASSET_TYPES, TYPE_DIRS, deriveCanonicalAssetName } from "./asset-spec";
5
- import { loadStashFile, writeStashFile, generateMetadata, } from "./metadata";
5
+ import { closeDatabase, DB_VERSION, deleteEntriesByDir, getEntriesByDir, getEntryCount, getMeta, isVecAvailable, openDatabase, rebuildFts, setMeta, upsertEmbedding, upsertEntry, warnIfVecMissing, } from "./db";
6
+ import { generateMetadata, loadStashFile, writeStashFile } from "./metadata";
7
+ import { getDbPath } from "./paths";
6
8
  import { walkStash } from "./walker";
7
- import { openDatabase, closeDatabase, getDbPath, getMeta, setMeta, upsertEntry, deleteEntriesByDir, rebuildFts, upsertEmbedding, getEntriesByDir, getEntryCount, isVecAvailable, warnIfVecMissing, DB_VERSION, } from "./db";
9
+ import { warn } from "./warn";
8
10
  // ── Indexer ──────────────────────────────────────────────────────────────────
9
11
  export async function agentikitIndex(options) {
10
12
  const stashDir = options?.stashDir || resolveStashDir();
@@ -30,12 +32,16 @@ export async function agentikitIndex(options) {
30
32
  try {
31
33
  db.exec("DELETE FROM embeddings");
32
34
  }
33
- catch { /* ignore */ }
35
+ catch {
36
+ /* ignore */
37
+ }
34
38
  if (isVecAvailable(db)) {
35
39
  try {
36
40
  db.exec("DELETE FROM entries_vec");
37
41
  }
38
- catch { /* ignore */ }
42
+ catch {
43
+ /* ignore */
44
+ }
39
45
  }
40
46
  db.exec("DELETE FROM entries_fts");
41
47
  db.exec("DELETE FROM entries");
@@ -126,9 +132,7 @@ function indexEntries(db, allStashDirs, stashDir, isIncremental, builtAtMs) {
126
132
  }
127
133
  // Check for files on disk that aren't covered by existing .stash.json entries.
128
134
  // This handles the case where new files are added after the initial index.
129
- const coveredFiles = new Set(stash.entries
130
- .map((e) => e.entry)
131
- .filter((e) => !!e));
135
+ const coveredFiles = new Set(stash.entries.map((e) => e.entry).filter((e) => !!e));
132
136
  const uncoveredFiles = files.filter((f) => !coveredFiles.has(path.basename(f)));
133
137
  if (uncoveredFiles.length > 0) {
134
138
  const generated = generateMetadata(dirPath, assetType, uncoveredFiles, typeRoot);
@@ -149,9 +153,7 @@ function indexEntries(db, allStashDirs, stashDir, isIncremental, builtAtMs) {
149
153
  }
150
154
  if (stash) {
151
155
  for (const entry of stash.entries) {
152
- const entryPath = entry.entry
153
- ? path.join(dirPath, entry.entry)
154
- : files[0] || dirPath;
156
+ const entryPath = entry.entry ? path.join(dirPath, entry.entry) : files[0] || dirPath;
155
157
  const entryKey = `${currentStashDir}:${entry.type}:${entry.name}`;
156
158
  const searchText = buildSearchText(entry);
157
159
  upsertEntry(db, entryKey, dirPath, entryPath, currentStashDir, entry, searchText);
@@ -202,7 +204,7 @@ async function generateEmbeddingsForDb(db, config) {
202
204
  return true;
203
205
  }
204
206
  catch (error) {
205
- console.warn("Embedding generation failed, continuing without:", error instanceof Error ? error.message : String(error));
207
+ warn("Embedding generation failed, continuing without:", error instanceof Error ? error.message : String(error));
206
208
  return false;
207
209
  }
208
210
  }
@@ -217,9 +219,7 @@ function getAllEntriesForEmbedding(db) {
217
219
  }
218
220
  function isDirStale(dirPath, currentFiles, previousEntries, builtAtMs) {
219
221
  // Check if file set changed (additions or deletions)
220
- const prevFileNames = new Set(previousEntries
221
- .map((ie) => ie.entry.entry)
222
- .filter((e) => !!e));
222
+ const prevFileNames = new Set(previousEntries.map((ie) => ie.entry.entry).filter((e) => !!e));
223
223
  const currFileNames = new Set(currentFiles.map((f) => path.basename(f)));
224
224
  if (prevFileNames.size !== currFileNames.size)
225
225
  return true;
@@ -240,11 +240,11 @@ function isDirStale(dirPath, currentFiles, previousEntries, builtAtMs) {
240
240
  // Check .stash.json modification time
241
241
  const stashPath = path.join(dirPath, ".stash.json");
242
242
  try {
243
- if (fs.existsSync(stashPath) && fs.statSync(stashPath).mtimeMs > builtAtMs)
243
+ if (fs.statSync(stashPath).mtimeMs > builtAtMs)
244
244
  return true;
245
245
  }
246
246
  catch {
247
- // ignore
247
+ // file doesn't exist, not stale
248
248
  }
249
249
  return false;
250
250
  }
@@ -277,15 +277,15 @@ async function enhanceStashWithLlm(llmConfig, stash, dirPath, files) {
277
277
  const enhanced = [];
278
278
  for (const entry of stash.entries) {
279
279
  try {
280
- const entryFile = entry.entry
281
- ? files.find((f) => path.basename(f) === entry.entry) ?? files[0]
282
- : files[0];
280
+ const entryFile = entry.entry ? (files.find((f) => path.basename(f) === entry.entry) ?? files[0]) : files[0];
283
281
  let fileContent;
284
282
  if (entryFile) {
285
283
  try {
286
284
  fileContent = fs.readFileSync(entryFile, "utf8");
287
285
  }
288
- catch { /* ignore unreadable files */ }
286
+ catch {
287
+ /* ignore unreadable files */
288
+ }
289
289
  }
290
290
  const improvements = await enhanceMetadata(llmConfig, entry, fileContent);
291
291
  const updated = { ...entry };
package/dist/init.js CHANGED
@@ -6,10 +6,10 @@
6
6
  */
7
7
  import fs from "node:fs";
8
8
  import path from "node:path";
9
- import { TYPE_DIRS } from "./common";
9
+ import { TYPE_DIRS } from "./asset-spec";
10
+ import { getConfigPath, loadConfig, saveConfig } from "./config";
11
+ import { getBinDir, getDefaultStashDir } from "./paths";
10
12
  import { ensureRg } from "./ripgrep-install";
11
- import { loadConfig, saveConfig, getConfigPath } from "./config";
12
- import { getDefaultStashDir, getBinDir } from "./paths";
13
13
  export async function agentikitInit(options) {
14
14
  const stashDir = options?.dir ? path.resolve(options.dir) : getDefaultStashDir();
15
15
  let created = false;
package/dist/llm.js CHANGED
@@ -28,19 +28,14 @@ const SYSTEM_PROMPT = `You are a metadata generator for a developer tool registr
28
28
  * generate intents, and suggest tags.
29
29
  */
30
30
  export async function enhanceMetadata(config, entry, fileContent) {
31
- const contextParts = [
32
- `Name: ${entry.name}`,
33
- `Type: ${entry.type}`,
34
- ];
31
+ const contextParts = [`Name: ${entry.name}`, `Type: ${entry.type}`];
35
32
  if (entry.description)
36
33
  contextParts.push(`Current description: ${entry.description}`);
37
34
  if (entry.tags?.length)
38
35
  contextParts.push(`Current tags: ${entry.tags.join(", ")}`);
39
36
  if (fileContent) {
40
37
  // Limit content to first 2000 chars to stay within token limits
41
- const truncated = fileContent.length > 2000
42
- ? fileContent.slice(0, 2000) + "\n... (truncated)"
43
- : fileContent;
38
+ const truncated = fileContent.length > 2000 ? fileContent.slice(0, 2000) + "\n... (truncated)" : fileContent;
44
39
  contextParts.push(`File content:\n${truncated}`);
45
40
  }
46
41
  const userPrompt = `${contextParts.join("\n")}
@@ -64,7 +59,9 @@ Return ONLY the JSON object, no explanation.`;
64
59
  result.description = parsed.description;
65
60
  }
66
61
  if (Array.isArray(parsed.intents)) {
67
- result.intents = parsed.intents.filter((s) => typeof s === "string" && s.trim().length > 0).slice(0, 8);
62
+ result.intents = parsed.intents
63
+ .filter((s) => typeof s === "string" && s.trim().length > 0)
64
+ .slice(0, 8);
68
65
  }
69
66
  if (Array.isArray(parsed.tags)) {
70
67
  result.tags = parsed.tags.filter((s) => typeof s === "string" && s.trim().length > 0).slice(0, 10);
@@ -81,9 +78,7 @@ Return ONLY the JSON object, no explanation.`;
81
78
  */
82
79
  export async function isLlmAvailable(config) {
83
80
  try {
84
- const result = await chatCompletion(config, [
85
- { role: "user", content: "Respond with just the word: ok" },
86
- ]);
81
+ const result = await chatCompletion(config, [{ role: "user", content: "Respond with just the word: ok" }]);
87
82
  return result.length > 0;
88
83
  }
89
84
  catch {
package/dist/lockfile.js CHANGED
@@ -31,7 +31,9 @@ export function writeLockfile(entries) {
31
31
  try {
32
32
  fs.unlinkSync(tmpPath);
33
33
  }
34
- catch { /* ignore cleanup failure */ }
34
+ catch {
35
+ /* ignore cleanup failure */
36
+ }
35
37
  throw err;
36
38
  }
37
39
  }
@@ -49,7 +51,10 @@ function isValidLockfileEntry(value) {
49
51
  if (typeof value !== "object" || value === null || Array.isArray(value))
50
52
  return false;
51
53
  const obj = value;
52
- return (typeof obj.id === "string" && obj.id !== "" &&
53
- typeof obj.source === "string" && ["npm", "github", "git", "local"].includes(obj.source) &&
54
- typeof obj.ref === "string" && obj.ref !== "");
54
+ return (typeof obj.id === "string" &&
55
+ obj.id !== "" &&
56
+ typeof obj.source === "string" &&
57
+ ["npm", "github", "git", "local"].includes(obj.source) &&
58
+ typeof obj.ref === "string" &&
59
+ obj.ref !== "");
55
60
  }
package/dist/matchers.js CHANGED
@@ -150,8 +150,14 @@ export function smartMdMatcher(ctx) {
150
150
  return { type: "knowledge", specificity: 5, renderer: "knowledge-md" };
151
151
  }
152
152
  // ── Registration ────────────────────────────────────────────────────────────
153
- // Order matters: later registrations win ties at the same specificity.
154
- registerMatcher(extensionMatcher);
155
- registerMatcher(directoryMatcher);
156
- registerMatcher(parentDirHintMatcher);
157
- registerMatcher(smartMdMatcher);
153
+ /** All built-in matchers in registration order (later wins ties). */
154
+ const builtinMatchers = [extensionMatcher, directoryMatcher, parentDirHintMatcher, smartMdMatcher];
155
+ /**
156
+ * Register all built-in matchers with the file-context registry.
157
+ * Called once from the CLI entry point (or ensureBuiltinsRegistered).
158
+ */
159
+ export function registerBuiltinMatchers() {
160
+ for (const matcher of builtinMatchers) {
161
+ registerMatcher(matcher);
162
+ }
163
+ }
package/dist/metadata.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import { deriveCanonicalAssetName, isRelevantAssetFile } from "./asset-spec";
4
+ import { tryGetHandler } from "./asset-type-handler";
3
5
  import { isAssetType } from "./common";
4
- import { isRelevantAssetFile, deriveCanonicalAssetName } from "./asset-spec";
5
6
  import { parseFrontmatter, toStringOrUndefined } from "./frontmatter";
6
- import { tryGetHandler } from "./asset-type-handler";
7
+ import { warn } from "./warn";
7
8
  // ── Load / Write ────────────────────────────────────────────────────────────
8
9
  const STASH_FILENAME = ".stash.json";
9
10
  export function stashFilePath(dirPath) {
@@ -20,8 +21,15 @@ export function loadStashFile(dirPath) {
20
21
  const entries = [];
21
22
  for (const e of raw.entries) {
22
23
  const validated = validateStashEntry(e);
23
- if (validated)
24
+ if (validated) {
24
25
  entries.push(validated);
26
+ }
27
+ else {
28
+ const name = typeof e === "object" && e !== null && typeof e.name === "string"
29
+ ? e.name
30
+ : "(unknown)";
31
+ warn(`Warning: Skipping invalid entry "${name}" in ${filePath}`);
32
+ }
25
33
  }
26
34
  return entries.length > 0 ? { entries } : null;
27
35
  }
@@ -40,7 +48,9 @@ export function writeStashFile(dirPath, stash) {
40
48
  try {
41
49
  fs.unlinkSync(tmpPath);
42
50
  }
43
- catch { /* ignore cleanup failure */ }
51
+ catch {
52
+ /* ignore cleanup failure */
53
+ }
44
54
  throw err;
45
55
  }
46
56
  }
@@ -85,7 +95,8 @@ export function validateStashEntry(entry) {
85
95
  result.quality = e.quality;
86
96
  if (typeof e.confidence === "number" && Number.isFinite(e.confidence))
87
97
  result.confidence = Math.max(0, Math.min(1, e.confidence));
88
- if (typeof e.source === "string" && ["package", "frontmatter", "comments", "filename", "manual", "llm"].includes(e.source)) {
98
+ if (typeof e.source === "string" &&
99
+ ["package", "frontmatter", "comments", "filename", "manual", "llm"].includes(e.source)) {
89
100
  result.source = e.source;
90
101
  }
91
102
  if (Array.isArray(e.aliases)) {
@@ -98,9 +109,7 @@ export function validateStashEntry(entry) {
98
109
  if (typeof h !== "object" || h === null)
99
110
  return false;
100
111
  const rec = h;
101
- return typeof rec.level === "number"
102
- && typeof rec.text === "string"
103
- && typeof rec.line === "number";
112
+ return typeof rec.level === "number" && typeof rec.text === "string" && typeof rec.line === "number";
104
113
  });
105
114
  if (validated.length > 0)
106
115
  result.toc = validated;
@@ -134,9 +143,7 @@ export function generateMetadata(dirPath, assetType, files, typeRoot = dirPath)
134
143
  // Skip non-relevant files
135
144
  if (!isRelevantAssetFile(assetType, fileName))
136
145
  continue;
137
- const canonicalName = assetType === "skill"
138
- ? deriveCanonicalAssetName(assetType, typeRoot, file) ?? baseName
139
- : baseName;
146
+ const canonicalName = assetType === "skill" ? (deriveCanonicalAssetName(assetType, typeRoot, file) ?? baseName) : baseName;
140
147
  const entry = {
141
148
  name: canonicalName,
142
149
  type: assetType,
@@ -145,7 +152,7 @@ export function generateMetadata(dirPath, assetType, files, typeRoot = dirPath)
145
152
  confidence: 0.55,
146
153
  source: "filename",
147
154
  };
148
- // Priority 1: package.json metadata
155
+ // Priority 1: Package.json metadata
149
156
  if (pkgMeta) {
150
157
  if (pkgMeta.description && !entry.description) {
151
158
  entry.description = pkgMeta.description;
@@ -155,7 +162,7 @@ export function generateMetadata(dirPath, assetType, files, typeRoot = dirPath)
155
162
  if (pkgMeta.keywords && pkgMeta.keywords.length > 0)
156
163
  entry.tags = normalizeTerms(pkgMeta.keywords);
157
164
  }
158
- // Priority 2: Frontmatter (for .md files overrides package.json description)
165
+ // Priority 2: Frontmatter (for .md files -- overrides package.json description)
159
166
  if (ext === ".md") {
160
167
  const fm = extractFrontmatterDescription(file);
161
168
  if (fm) {
@@ -164,7 +171,7 @@ export function generateMetadata(dirPath, assetType, files, typeRoot = dirPath)
164
171
  entry.confidence = 0.9;
165
172
  }
166
173
  }
167
- // Type-specific metadata extraction (e.g. TOC for knowledge, comments for tools/scripts)
174
+ // Priority 3: Type-specific metadata extraction (e.g. TOC for knowledge, comments for tools/scripts)
168
175
  const handler = tryGetHandler(assetType);
169
176
  if (handler?.extractTypeMetadata) {
170
177
  handler.extractTypeMetadata(entry, file, ext);
@@ -226,7 +233,10 @@ export function extractDescriptionFromComments(filePath) {
226
233
  const line = lines[i];
227
234
  if (i > blockStart && /\*\//.test(line))
228
235
  break;
229
- const cleaned = line.replace(/^\s*\/?\*\*?\s?/, "").replace(/\*\/\s*$/, "").trim();
236
+ const cleaned = line
237
+ .replace(/^\s*\/?\*\*?\s?/, "")
238
+ .replace(/\*\/\s*$/, "")
239
+ .trim();
230
240
  if (cleaned)
231
241
  desc.push(cleaned);
232
242
  }
@@ -244,7 +254,6 @@ export function extractDescriptionFromComments(filePath) {
244
254
  hashLines.push(line.replace(/^#+\s*/, "").trim());
245
255
  }
246
256
  else if (line === "") {
247
- continue;
248
257
  }
249
258
  else {
250
259
  break;
package/dist/paths.js CHANGED
@@ -6,6 +6,7 @@
6
6
  * on Windows.
7
7
  */
8
8
  import path from "node:path";
9
+ import { ConfigError } from "./errors";
9
10
  const IS_WINDOWS = process.platform === "win32";
10
11
  // ── Config directory ─────────────────────────────────────────────────────────
11
12
  export function getConfigDir(env = process.env, platform = process.platform) {
@@ -15,7 +16,7 @@ export function getConfigDir(env = process.env, platform = process.platform) {
15
16
  return path.join(appData, "agentikit");
16
17
  const userProfile = env.USERPROFILE?.trim();
17
18
  if (!userProfile) {
18
- throw new Error("Unable to determine config directory. Set APPDATA or USERPROFILE.");
19
+ throw new ConfigError("Unable to determine config directory. Set APPDATA or USERPROFILE.");
19
20
  }
20
21
  return path.join(userProfile, "AppData", "Roaming", "agentikit");
21
22
  }
@@ -24,7 +25,7 @@ export function getConfigDir(env = process.env, platform = process.platform) {
24
25
  return path.join(xdgConfigHome, "agentikit");
25
26
  const home = env.HOME?.trim();
26
27
  if (!home) {
27
- throw new Error("Unable to determine config directory. Set XDG_CONFIG_HOME or HOME.");
28
+ throw new ConfigError("Unable to determine config directory. Set XDG_CONFIG_HOME or HOME.");
28
29
  }
29
30
  return path.join(home, ".config", "agentikit");
30
31
  }
@@ -42,7 +43,7 @@ export function getCacheDir() {
42
43
  return path.join(userProfile, "AppData", "Local", "agentikit");
43
44
  const appData = process.env.APPDATA?.trim();
44
45
  if (!appData) {
45
- throw new Error("Unable to determine cache directory. Set LOCALAPPDATA, USERPROFILE, or APPDATA.");
46
+ throw new ConfigError("Unable to determine cache directory. Set LOCALAPPDATA, USERPROFILE, or APPDATA.");
46
47
  }
47
48
  return path.join(appData, "..", "Local", "agentikit");
48
49
  }
@@ -76,7 +77,7 @@ export function getDefaultStashDir() {
76
77
  }
77
78
  const home = process.env.HOME?.trim();
78
79
  if (!home) {
79
- throw new Error("Unable to determine default stash directory. Set HOME.");
80
+ throw new ConfigError("Unable to determine default stash directory. Set HOME.");
80
81
  }
81
82
  return path.join(home, "agentikit");
82
83
  }
@@ -1,11 +1,12 @@
1
- import { createHash } from "node:crypto";
2
1
  import { spawnSync } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
- import { fetchWithRetry, isWithin, TYPE_DIRS } from "./common";
5
+ import { TYPE_DIRS } from "./asset-spec";
6
+ import { fetchWithRetry, isWithin } from "./common";
6
7
  import { loadConfig, saveConfig } from "./config";
7
- import { parseRegistryRef, resolveRegistryArtifact } from "./registry-resolve";
8
8
  import { getRegistryCacheDir as _getRegistryCacheDir } from "./paths";
9
+ import { parseRegistryRef, resolveRegistryArtifact } from "./registry-resolve";
9
10
  const REGISTRY_STASH_DIR_NAMES = new Set(Object.values(TYPE_DIRS));
10
11
  export async function installRegistryRef(ref, options) {
11
12
  const parsed = parseRegistryRef(ref);
@@ -231,7 +232,8 @@ async function downloadArchive(url, destination) {
231
232
  }
232
233
  // Stream response to disk instead of buffering the entire archive in memory.
233
234
  // Uses Bun.write which handles Response streaming natively.
234
- const BunRuntime = globalThis.Bun;
235
+ const BunRuntime = globalThis
236
+ .Bun;
235
237
  if (BunRuntime?.write) {
236
238
  await BunRuntime.write(destination, response);
237
239
  }
@@ -308,7 +310,9 @@ function validateTarEntries(listOutput) {
308
310
  if (!stripped)
309
311
  continue;
310
312
  const normalizedStripped = path.posix.normalize(stripped);
311
- if (normalizedStripped === ".." || normalizedStripped.startsWith("../") || path.posix.isAbsolute(normalizedStripped)) {
313
+ if (normalizedStripped === ".." ||
314
+ normalizedStripped.startsWith("../") ||
315
+ path.posix.isAbsolute(normalizedStripped)) {
312
316
  throw new Error(`Archive contains an unsafe entry after strip-components: ${entry}`);
313
317
  }
314
318
  }
@@ -3,7 +3,7 @@ import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import { fetchWithRetry } from "./common";
6
- import { GITHUB_API_BASE, githubHeaders, asRecord, asString } from "./github";
6
+ import { asRecord, asString, GITHUB_API_BASE, githubHeaders } from "./github";
7
7
  export function parseRegistryRef(rawRef) {
8
8
  const ref = rawRef.trim();
9
9
  if (!ref)
@@ -177,7 +177,7 @@ async function resolveNpmArtifact(parsed) {
177
177
  }
178
178
  }
179
179
  if (!resolvedVersion || !(resolvedVersion in versions)) {
180
- throw new Error(`Unable to resolve npm ref \"${parsed.ref}\".`);
180
+ throw new Error(`Unable to resolve npm ref "${parsed.ref}".`);
181
181
  }
182
182
  const versionMeta = asRecord(versions[resolvedVersion]);
183
183
  const dist = asRecord(versionMeta.dist);
@@ -287,16 +287,20 @@ function splitNpmNameAndVersion(input) {
287
287
  }
288
288
  function validateNpmPackageName(name) {
289
289
  if (!name)
290
- throw new Error('Invalid npm package name: name is required.');
290
+ throw new Error("Invalid npm package name: name is required.");
291
291
  if (name.length > 214)
292
292
  throw new Error(`Invalid npm package name: "${name}" exceeds 214 characters.`);
293
- if (name !== name.toLowerCase() && !name.startsWith('@')) {
293
+ if (name !== name.toLowerCase() && !name.startsWith("@")) {
294
294
  throw new Error(`Invalid npm package name: "${name}" must be lowercase.`);
295
295
  }
296
- if (name.startsWith('.') || name.startsWith('_')) {
296
+ if (name.startsWith(".") || name.startsWith("_")) {
297
297
  throw new Error(`Invalid npm package name: "${name}" cannot start with . or _.`);
298
298
  }
299
- if (/[~'!()*]/.test(name) || name.includes(' ') || encodeURIComponent(name).replace(/%40/g, '@').replace(/%2[Ff]/g, '/') !== name) {
299
+ if (/[~'!()*]/.test(name) ||
300
+ name.includes(" ") ||
301
+ encodeURIComponent(name)
302
+ .replace(/%40/g, "@")
303
+ .replace(/%2[Ff]/g, "/") !== name) {
300
304
  throw new Error(`Invalid npm package name: "${name}" contains invalid characters.`);
301
305
  }
302
306
  }
@@ -449,11 +453,11 @@ async function fetchJson(url, headers) {
449
453
  if (!response.ok) {
450
454
  throw new Error(`Request failed (${response.status}) for ${url}`);
451
455
  }
452
- return await response.json();
456
+ return (await response.json());
453
457
  }
454
458
  async function tryFetchJson(url, headers) {
455
459
  const response = await fetchWithRetry(url, { headers });
456
460
  if (!response.ok)
457
461
  return null;
458
- return await response.json();
462
+ return (await response.json());
459
463
  }
@@ -149,10 +149,7 @@ function parseKitEntry(raw) {
149
149
  }
150
150
  // ── Scoring ─────────────────────────────────────────────────────────────────
151
151
  function scoreKits(kits, query, limit) {
152
- const tokens = query
153
- .toLowerCase()
154
- .split(/\s+/)
155
- .filter(Boolean);
152
+ const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
156
153
  const scored = [];
157
154
  for (const kit of kits) {
158
155
  const score = scoreKit(kit, tokens);
@@ -226,7 +223,10 @@ function resolveRegistryUrls(override) {
226
223
  // Allow env var override (comma-separated)
227
224
  const envUrls = process.env.AKM_REGISTRY_URL?.trim();
228
225
  if (envUrls) {
229
- return envUrls.split(",").map((u) => u.trim()).filter(Boolean);
226
+ return envUrls
227
+ .split(",")
228
+ .map((u) => u.trim())
229
+ .filter(Boolean);
230
230
  }
231
231
  return [DEFAULT_REGISTRY_URL];
232
232
  }
package/dist/renderers.js CHANGED
@@ -6,15 +6,14 @@
6
6
  * from ./file-context. Renderers are registered at module-load time so that
7
7
  * importing this module is sufficient to make them available.
8
8
  */
9
- import fs from "node:fs";
10
9
  import path from "node:path";
11
- import { registerRenderer } from "./file-context";
12
- import { parseFrontmatter, toStringOrUndefined } from "./frontmatter";
13
10
  import { SCRIPT_EXTENSIONS } from "./asset-spec";
14
- import { buildToolInfo } from "./tool-runner";
15
11
  import { hasErrnoCode } from "./common";
12
+ import { registerRenderer } from "./file-context";
13
+ import { parseFrontmatter, toStringOrUndefined } from "./frontmatter";
14
+ import { extractFrontmatterOnly, extractLineRange, extractSection, formatToc, parseMarkdownToc } from "./markdown";
16
15
  import { extractDescriptionFromComments } from "./metadata";
17
- import { parseMarkdownToc, extractSection, extractLineRange, extractFrontmatterOnly, formatToc, } from "./markdown";
16
+ import { buildToolInfo } from "./tool-runner";
18
17
  // ── Helpers ──────────────────────────────────────────────────────────────────
19
18
  /**
20
19
  * Derive a display name from the RenderContext.
@@ -35,7 +34,7 @@ function deriveName(ctx) {
35
34
  * entry in the array when no prefix match is found.
36
35
  */
37
36
  function findContainingStashDir(stashDirs, filePath) {
38
- return (stashDirs.find((d) => path.resolve(filePath).startsWith(path.resolve(d) + path.sep)) ?? stashDirs[0]);
37
+ return stashDirs.find((d) => path.resolve(filePath).startsWith(path.resolve(d) + path.sep)) ?? stashDirs[0];
39
38
  }
40
39
  // ── 1. tool-script ───────────────────────────────────────────────────────────
41
40
  const toolScriptRenderer = {
@@ -186,8 +185,7 @@ const knowledgeMdRenderer = {
186
185
  },
187
186
  extractMetadata(entry, ctx) {
188
187
  try {
189
- const mdContent = fs.readFileSync(ctx.absPath, "utf8");
190
- const toc = parseMarkdownToc(mdContent);
188
+ const toc = parseMarkdownToc(ctx.content());
191
189
  if (toc.headings.length > 0)
192
190
  entry.toc = toc.headings;
193
191
  }
@@ -266,11 +264,23 @@ const scriptSourceRenderer = {
266
264
  ],
267
265
  };
268
266
  // ── Registration ─────────────────────────────────────────────────────────────
269
- registerRenderer(toolScriptRenderer);
270
- registerRenderer(skillMdRenderer);
271
- registerRenderer(commandMdRenderer);
272
- registerRenderer(agentMdRenderer);
273
- registerRenderer(knowledgeMdRenderer);
274
- registerRenderer(scriptSourceRenderer);
267
+ /** All built-in renderers. */
268
+ const builtinRenderers = [
269
+ toolScriptRenderer,
270
+ skillMdRenderer,
271
+ commandMdRenderer,
272
+ agentMdRenderer,
273
+ knowledgeMdRenderer,
274
+ scriptSourceRenderer,
275
+ ];
276
+ /**
277
+ * Register all built-in renderers with the file-context registry.
278
+ * Called once from the CLI entry point (or ensureBuiltinsRegistered).
279
+ */
280
+ export function registerBuiltinRenderers() {
281
+ for (const renderer of builtinRenderers) {
282
+ registerRenderer(renderer);
283
+ }
284
+ }
275
285
  // ── Named exports for testing ────────────────────────────────────────────────
276
286
  export { toolScriptRenderer, skillMdRenderer, commandMdRenderer, agentMdRenderer, knowledgeMdRenderer, scriptSourceRenderer, };
@@ -78,14 +78,7 @@ function downloadAndExtractTarGz(url, archiveName, destBinary) {
78
78
  throw new Error(`Failed to download ripgrep from ${url}: ${err}`);
79
79
  }
80
80
  // Extract the specific binary from the archive into destDir
81
- const tarResult = spawnSync("tar", [
82
- "xzf",
83
- tmpTarGz,
84
- "--strip-components=1",
85
- "-C",
86
- destDir,
87
- `${archiveName}/rg`,
88
- ], {
81
+ const tarResult = spawnSync("tar", ["xzf", tmpTarGz, "--strip-components=1", "-C", destDir, `${archiveName}/rg`], {
89
82
  encoding: "utf8",
90
83
  timeout: 60_000,
91
84
  });
@@ -124,13 +117,7 @@ function downloadAndExtractZip(url, archiveName, destBinary) {
124
117
  }
125
118
  // Extract the zip archive using separate spawnSync calls with argument arrays
126
119
  // to avoid shell injection via path interpolation in PowerShell -Command strings
127
- const expandResult = spawnSync("powershell", [
128
- "-Command",
129
- "Expand-Archive",
130
- "-Path", tmpZip,
131
- "-DestinationPath", destDir,
132
- "-Force",
133
- ], {
120
+ const expandResult = spawnSync("powershell", ["-Command", "Expand-Archive", "-Path", tmpZip, "-DestinationPath", destDir, "-Force"], {
134
121
  encoding: "utf8",
135
122
  timeout: 60_000,
136
123
  });
@@ -138,13 +125,7 @@ function downloadAndExtractZip(url, archiveName, destBinary) {
138
125
  throw new Error(expandResult.stderr?.trim() || "extraction failed");
139
126
  }
140
127
  const srcRgExe = path.join(destDir, archiveName, "rg.exe");
141
- const moveResult = spawnSync("powershell", [
142
- "-Command",
143
- "Move-Item",
144
- "-Force",
145
- "-Path", srcRgExe,
146
- "-Destination", destBinary,
147
- ], {
128
+ const moveResult = spawnSync("powershell", ["-Command", "Move-Item", "-Force", "-Path", srcRgExe, "-Destination", destBinary], {
148
129
  encoding: "utf8",
149
130
  timeout: 60_000,
150
131
  });
package/dist/ripgrep.js CHANGED
@@ -1,2 +1,2 @@
1
- export { resolveRg, isRgAvailable } from "./ripgrep-resolve";
2
1
  export { ensureRg } from "./ripgrep-install";
2
+ export { isRgAvailable, resolveRg } from "./ripgrep-resolve";