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/db.js CHANGED
@@ -1,16 +1,13 @@
1
+ import { Database } from "bun:sqlite";
1
2
  import fs from "node:fs";
2
- import path from "node:path";
3
3
  import { createRequire } from "node:module";
4
- import { Database } from "bun:sqlite";
4
+ import path from "node:path";
5
5
  import { cosineSimilarity } from "./embedder";
6
- import { getDbPath as _getDbPath } from "./paths";
6
+ import { getDbPath } from "./paths";
7
+ import { warn } from "./warn";
7
8
  // ── Constants ───────────────────────────────────────────────────────────────
8
9
  export const DB_VERSION = 6;
9
10
  export const EMBEDDING_DIM = 384;
10
- // ── Path ────────────────────────────────────────────────────────────────────
11
- export function getDbPath() {
12
- return _getDbPath();
13
- }
14
11
  // ── Database lifecycle ──────────────────────────────────────────────────────
15
12
  export function openDatabase(dbPath, options) {
16
13
  const resolvedPath = dbPath ?? getDbPath();
@@ -63,12 +60,14 @@ export function warnIfVecMissing(db, { once } = { once: false }) {
63
60
  const row = db.prepare("SELECT COUNT(*) AS cnt FROM embeddings").get();
64
61
  const count = row?.cnt ?? 0;
65
62
  if (count >= VEC_FALLBACK_THRESHOLD) {
66
- console.warn("Semantic search is using JS fallback for %d entries. Install sqlite-vec for faster performance.\n See: %s", count, VEC_DOCS_URL);
63
+ warn("Semantic search is using JS fallback for %d entries. Install sqlite-vec for faster performance.\n See: %s", count, VEC_DOCS_URL);
67
64
  if (once)
68
65
  vecInitWarned = true;
69
66
  }
70
67
  }
71
- catch { /* embeddings table may not exist yet during init */ }
68
+ catch {
69
+ /* embeddings table may not exist yet during init */
70
+ }
72
71
  }
73
72
  // ── Schema ──────────────────────────────────────────────────────────────────
74
73
  function ensureSchema(db, embeddingDim) {
@@ -114,9 +113,7 @@ function ensureSchema(db, embeddingDim) {
114
113
  );
115
114
  `);
116
115
  // FTS5 table — standalone with explicit entry_id for joining
117
- const ftsExists = db
118
- .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='entries_fts'")
119
- .get();
116
+ const ftsExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='entries_fts'").get();
120
117
  if (!ftsExists) {
121
118
  db.exec(`
122
119
  CREATE VIRTUAL TABLE entries_fts USING fts5(
@@ -134,11 +131,11 @@ function ensureSchema(db, embeddingDim) {
134
131
  try {
135
132
  db.exec("DROP TABLE IF EXISTS entries_vec");
136
133
  }
137
- catch { /* ignore */ }
134
+ catch {
135
+ /* ignore */
136
+ }
138
137
  }
139
- const vecExists = db
140
- .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='entries_vec'")
141
- .get();
138
+ const vecExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='entries_vec'").get();
142
139
  if (!vecExists) {
143
140
  db.exec(`
144
141
  CREATE VIRTUAL TABLE entries_vec USING vec0(
@@ -182,19 +179,21 @@ export function upsertEntry(db, entryKey, dirPath, filePath, stashDir, entry, se
182
179
  return row.id;
183
180
  }
184
181
  export function deleteEntriesByDir(db, dirPath) {
185
- const ids = db
186
- .prepare("SELECT id FROM entries WHERE dir_path = ?")
187
- .all(dirPath);
182
+ const ids = db.prepare("SELECT id FROM entries WHERE dir_path = ?").all(dirPath);
188
183
  for (const { id } of ids) {
189
184
  try {
190
185
  db.prepare("DELETE FROM embeddings WHERE id = ?").run(id);
191
186
  }
192
- catch { /* ignore */ }
187
+ catch {
188
+ /* ignore */
189
+ }
193
190
  if (isVecAvailable(db)) {
194
191
  try {
195
192
  db.prepare("DELETE FROM entries_vec WHERE id = ?").run(id);
196
193
  }
197
- catch { /* ignore */ }
194
+ catch {
195
+ /* ignore */
196
+ }
198
197
  }
199
198
  }
200
199
  db.prepare("DELETE FROM entries WHERE dir_path = ?").run(dirPath);
@@ -213,7 +212,9 @@ export function upsertEmbedding(db, entryId, embedding) {
213
212
  try {
214
213
  db.prepare("DELETE FROM entries_vec WHERE id = ?").run(entryId);
215
214
  }
216
- catch { /* ignore */ }
215
+ catch {
216
+ /* ignore */
217
+ }
217
218
  db.prepare("INSERT INTO entries_vec (id, embedding) VALUES (?, ?)").run(entryId, buf);
218
219
  }
219
220
  }
@@ -243,9 +244,7 @@ function bufferToFloat32(buf) {
243
244
  }
244
245
  function searchBlobVec(db, queryEmbedding, k) {
245
246
  try {
246
- const rows = db
247
- .prepare("SELECT id, embedding FROM embeddings")
248
- .all();
247
+ const rows = db.prepare("SELECT id, embedding FROM embeddings").all();
249
248
  if (rows.length === 0)
250
249
  return [];
251
250
  const scored = [];
@@ -316,7 +315,7 @@ function sanitizeFtsQuery(query) {
316
315
  const tokens = query
317
316
  .replace(/[^a-zA-Z0-9\s]/g, " ")
318
317
  .split(/\s+/)
319
- .filter((t) => t.length > 1);
318
+ .filter((t) => t.length >= 1);
320
319
  if (tokens.length === 0)
321
320
  return "";
322
321
  // Use unquoted tokens so the porter stemmer can normalize word forms
@@ -327,7 +326,8 @@ export function getAllEntries(db, entryType) {
327
326
  let sql;
328
327
  let params;
329
328
  if (entryType && entryType !== "any") {
330
- sql = "SELECT id, entry_key, dir_path, file_path, stash_dir, entry_json, search_text FROM entries WHERE entry_type = ?";
329
+ sql =
330
+ "SELECT id, entry_key, dir_path, file_path, stash_dir, entry_json, search_text FROM entries WHERE entry_type = ?";
331
331
  params = [entryType];
332
332
  }
333
333
  else {
@@ -350,9 +350,7 @@ export function getEntryCount(db) {
350
350
  return row.cnt;
351
351
  }
352
352
  export function getEntryById(db, id) {
353
- const row = db
354
- .prepare("SELECT file_path, entry_json FROM entries WHERE id = ?")
355
- .get(id);
353
+ const row = db.prepare("SELECT file_path, entry_json FROM entries WHERE id = ?").get(id);
356
354
  if (!row)
357
355
  return undefined;
358
356
  return { filePath: row.file_path, entry: JSON.parse(row.entry_json) };
package/dist/errors.js ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Typed error classes for structured exit code classification.
3
+ *
4
+ * - ConfigError -> exit 78 (configuration / environment problems)
5
+ * - UsageError -> exit 2 (bad CLI arguments or invalid input)
6
+ * - NotFoundError -> exit 1 (requested resource missing)
7
+ */
8
+ /** Raised when configuration or environment is invalid or missing. */
9
+ export class ConfigError extends Error {
10
+ constructor(msg) {
11
+ super(msg);
12
+ this.name = "ConfigError";
13
+ }
14
+ }
15
+ /** Raised when the user supplies invalid arguments or input. */
16
+ export class UsageError extends Error {
17
+ constructor(msg) {
18
+ super(msg);
19
+ this.name = "UsageError";
20
+ }
21
+ }
22
+ /** Raised when a requested resource (asset, entry, file) is not found. */
23
+ export class NotFoundError extends Error {
24
+ constructor(msg) {
25
+ super(msg);
26
+ this.name = "NotFoundError";
27
+ }
28
+ }
@@ -6,8 +6,8 @@
6
6
  */
7
7
  import fs from "node:fs";
8
8
  import path from "node:path";
9
- import { parseFrontmatter } from "./frontmatter";
10
9
  import { toPosix } from "./common";
10
+ import { parseFrontmatter } from "./frontmatter";
11
11
  /**
12
12
  * Build a FileContext from a stash root and an absolute file path.
13
13
  *
@@ -50,8 +50,7 @@ export function buildFileContext(stashRoot, absPath) {
50
50
  if (!frontmatterComputed) {
51
51
  const raw = this.content();
52
52
  const parsed = parseFrontmatter(raw);
53
- cachedFrontmatter =
54
- Object.keys(parsed.data).length > 0 ? parsed.data : null;
53
+ cachedFrontmatter = Object.keys(parsed.data).length > 0 ? parsed.data : null;
55
54
  frontmatterComputed = true;
56
55
  }
57
56
  return cachedFrontmatter;
@@ -70,17 +69,48 @@ const matchers = [];
70
69
  /** Renderer lookup by name. */
71
70
  const renderers = new Map();
72
71
  let builtinsInitialized = false;
72
+ /** Pluggable initializer set via `setBuiltinRegistrar`. */
73
+ let builtinRegistrar = null;
74
+ /**
75
+ * Set the function that registers built-in matchers and renderers.
76
+ *
77
+ * This breaks the static import cycle: `file-context.ts` does not need to
78
+ * import `matchers.ts` or `renderers.ts` at the top level. Instead, a
79
+ * one-time call from outside (e.g. the test file or CLI entry) provides the
80
+ * registration callback.
81
+ *
82
+ * If no registrar is set by the time `ensureBuiltinsRegistered` runs, it
83
+ * falls back to a dynamic `require()` for backward compatibility.
84
+ */
85
+ export function setBuiltinRegistrar(fn) {
86
+ builtinRegistrar = fn;
87
+ }
73
88
  /**
74
89
  * Ensure that built-in matchers and renderers are registered.
75
90
  * Called lazily on first use of runMatchers/getRenderer.
91
+ *
92
+ * Uses the registrar set via `setBuiltinRegistrar`, or falls back to
93
+ * a direct import of the registration modules. The dynamic import
94
+ * avoids a static circular dependency between file-context, renderers,
95
+ * and asset-spec.
76
96
  */
77
97
  function ensureBuiltinsRegistered() {
78
98
  if (builtinsInitialized)
79
99
  return;
80
100
  builtinsInitialized = true;
81
- // Side-effect imports that register matchers/renderers at module load
82
- require("./matchers");
83
- require("./renderers");
101
+ if (builtinRegistrar) {
102
+ builtinRegistrar();
103
+ return;
104
+ }
105
+ // Lazy inline require avoids a top-level static import cycle:
106
+ // file-context -> renderers -> asset-spec -> asset-type-handler -> handlers -> file-context
107
+ // These are only evaluated once and only when no explicit registrar was set.
108
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
109
+ const { registerBuiltinMatchers } = require("./matchers");
110
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
111
+ const { registerBuiltinRenderers } = require("./renderers");
112
+ registerBuiltinMatchers();
113
+ registerBuiltinRenderers();
84
114
  }
85
115
  /**
86
116
  * Register an AssetMatcher.
@@ -18,7 +18,7 @@ export function parseFrontmatter(raw) {
18
18
  let currentKey = null;
19
19
  let nested = null;
20
20
  for (const line of parsedBlock.frontmatter.split(/\r?\n/)) {
21
- const indented = line.match(/^ (\w[\w-]*):\s*(.+)$/);
21
+ const indented = line.match(/^ {2}(\w[\w-]*):\s*(.+)$/);
22
22
  if (indented && currentKey && nested) {
23
23
  nested[indented[1]] = parseYamlScalar(indented[2].trim());
24
24
  continue;
package/dist/github.js CHANGED
@@ -10,9 +10,7 @@ export function githubHeaders() {
10
10
  return headers;
11
11
  }
12
12
  export function asRecord(value) {
13
- return typeof value === "object" && value !== null && !Array.isArray(value)
14
- ? value
15
- : {};
13
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
16
14
  }
17
15
  export function asString(value) {
18
16
  return typeof value === "string" && value ? value : undefined;
@@ -1,5 +1,6 @@
1
- import { parseFrontmatter, toStringOrUndefined } from "../frontmatter";
2
- import { isMarkdownFile, markdownCanonicalName, markdownAssetPath } from "./markdown-helpers";
1
+ import { getRenderer } from "../file-context";
2
+ import { showInputToRenderContext } from "./handler-bridge";
3
+ import { isMarkdownFile, markdownAssetPath, markdownCanonicalName } from "./markdown-helpers";
3
4
  export const agentHandler = {
4
5
  typeName: "agent",
5
6
  stashDir: "agents",
@@ -7,17 +8,9 @@ export const agentHandler = {
7
8
  toCanonicalName: markdownCanonicalName,
8
9
  toAssetPath: markdownAssetPath,
9
10
  buildShowResponse(input) {
10
- const parsedMd = parseFrontmatter(input.content);
11
- return {
12
- type: "agent",
13
- name: input.name,
14
- path: input.path,
15
- description: toStringOrUndefined(parsedMd.data.description),
16
- prompt: "Dispatching prompt must include the agent's full prompt content verbatim; summaries are non-compliant. \n\n"
17
- + parsedMd.content,
18
- toolPolicy: parsedMd.data.tools,
19
- modelHint: parsedMd.data.model,
20
- };
11
+ const renderer = getRenderer("agent-md");
12
+ const ctx = showInputToRenderContext(input, "agent-md");
13
+ return renderer.buildShowResponse(ctx);
21
14
  },
22
15
  defaultUsageGuide: [
23
16
  "Read the .md file and dispatch an agent using the content of the file. Use modelHint/toolPolicy when present to run the agent with compatible settings.",
@@ -1,5 +1,6 @@
1
- import { parseFrontmatter, toStringOrUndefined } from "../frontmatter";
2
- import { isMarkdownFile, markdownCanonicalName, markdownAssetPath } from "./markdown-helpers";
1
+ import { getRenderer } from "../file-context";
2
+ import { showInputToRenderContext } from "./handler-bridge";
3
+ import { isMarkdownFile, markdownAssetPath, markdownCanonicalName } from "./markdown-helpers";
3
4
  export const commandHandler = {
4
5
  typeName: "command",
5
6
  stashDir: "commands",
@@ -7,19 +8,13 @@ export const commandHandler = {
7
8
  toCanonicalName: markdownCanonicalName,
8
9
  toAssetPath: markdownAssetPath,
9
10
  buildShowResponse(input) {
10
- const parsedMd = parseFrontmatter(input.content);
11
- return {
12
- type: "command",
13
- name: input.name,
14
- path: input.path,
15
- description: toStringOrUndefined(parsedMd.data.description),
16
- template: parsedMd.content,
17
- modelHint: parsedMd.data.model,
18
- agent: toStringOrUndefined(parsedMd.data.agent),
19
- };
11
+ const renderer = getRenderer("command-md");
12
+ const ctx = showInputToRenderContext(input, "command-md");
13
+ return renderer.buildShowResponse(ctx);
20
14
  },
21
15
  defaultUsageGuide: [
22
- "Read the .md file, fill placeholders, and run it in the current repo context.",
16
+ "Read the .md file, fill $ARGUMENTS placeholders, and run it in the current repo context.",
23
17
  "Use `akm show <openRef>` to retrieve the command template body.",
18
+ "When `agent` is specified, dispatch the command to that agent.",
24
19
  ],
25
20
  };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Bridge utility that converts legacy ShowInput into a RenderContext,
3
+ * allowing handlers to delegate their buildShowResponse to renderers.
4
+ */
5
+ import path from "node:path";
6
+ import { toPosix } from "../common";
7
+ /**
8
+ * Convert a legacy ShowInput into a RenderContext suitable for passing
9
+ * to an AssetRenderer's buildShowResponse method.
10
+ *
11
+ * This avoids hitting the filesystem since ShowInput already carries
12
+ * the file content.
13
+ */
14
+ export function showInputToRenderContext(input, rendererName) {
15
+ const absPath = path.resolve(input.path);
16
+ const stashDirs = input.stashDirs ?? [];
17
+ // Derive a stash root from stashDirs if possible
18
+ const stashRoot = stashDirs.find((d) => absPath.startsWith(path.resolve(d) + path.sep)) ?? stashDirs[0] ?? path.dirname(absPath);
19
+ const relPath = toPosix(path.relative(stashRoot, absPath));
20
+ const ext = path.extname(absPath).toLowerCase();
21
+ const fileName = path.basename(absPath);
22
+ const parentDirAbs = path.dirname(absPath);
23
+ const parentDir = path.basename(parentDirAbs);
24
+ const relDir = toPosix(path.dirname(relPath));
25
+ const ancestorDirs = relDir === "." ? [] : relDir.split("/").filter((seg) => seg.length > 0);
26
+ // Cache the content from input (no filesystem read needed)
27
+ const cachedContent = input.content;
28
+ const matchResult = {
29
+ type: rendererName.split("-")[0], // e.g. "tool" from "tool-script"
30
+ specificity: 10,
31
+ renderer: rendererName,
32
+ meta: { name: input.name, view: input.view },
33
+ };
34
+ return {
35
+ absPath,
36
+ relPath,
37
+ ext,
38
+ fileName,
39
+ parentDir,
40
+ parentDirAbs,
41
+ ancestorDirs,
42
+ stashRoot,
43
+ content: () => cachedContent,
44
+ frontmatter: () => null, // Renderers parse frontmatter from content() themselves
45
+ stat() {
46
+ throw new Error("stat() not available in handler bridge context");
47
+ },
48
+ matchResult,
49
+ stashDirs,
50
+ };
51
+ }
@@ -1,17 +1,19 @@
1
1
  import { registerAssetType } from "../asset-type-handler";
2
- import { toolHandler } from "./tool-handler";
3
- import { skillHandler } from "./skill-handler";
4
- import { commandHandler } from "./command-handler";
5
2
  import { agentHandler } from "./agent-handler";
3
+ import { commandHandler } from "./command-handler";
6
4
  import { knowledgeHandler } from "./knowledge-handler";
7
5
  import { scriptHandler } from "./script-handler";
6
+ import { skillHandler } from "./skill-handler";
7
+ import { toolHandler } from "./tool-handler";
8
8
  /**
9
9
  * Register all built-in asset type handlers.
10
- * This must be called (imported) before any handler lookups.
10
+ * Called once from ensureHandlersRegistered in asset-type-handler.ts.
11
11
  */
12
- registerAssetType(toolHandler);
13
- registerAssetType(skillHandler);
14
- registerAssetType(commandHandler);
15
- registerAssetType(agentHandler);
16
- registerAssetType(knowledgeHandler);
17
- registerAssetType(scriptHandler);
12
+ export function registerBuiltinHandlers() {
13
+ registerAssetType(toolHandler);
14
+ registerAssetType(skillHandler);
15
+ registerAssetType(commandHandler);
16
+ registerAssetType(agentHandler);
17
+ registerAssetType(knowledgeHandler);
18
+ registerAssetType(scriptHandler);
19
+ }
@@ -1,6 +1,8 @@
1
1
  import fs from "node:fs";
2
- import { parseMarkdownToc, extractSection, extractLineRange, extractFrontmatterOnly, formatToc } from "../markdown";
3
- import { isMarkdownFile, markdownCanonicalName, markdownAssetPath } from "./markdown-helpers";
2
+ import { getRenderer } from "../file-context";
3
+ import { parseMarkdownToc } from "../markdown";
4
+ import { showInputToRenderContext } from "./handler-bridge";
5
+ import { isMarkdownFile, markdownAssetPath, markdownCanonicalName } from "./markdown-helpers";
4
6
  export const knowledgeHandler = {
5
7
  typeName: "knowledge",
6
8
  stashDir: "knowledge",
@@ -8,35 +10,9 @@ export const knowledgeHandler = {
8
10
  toCanonicalName: markdownCanonicalName,
9
11
  toAssetPath: markdownAssetPath,
10
12
  buildShowResponse(input) {
11
- const v = input.view ?? { mode: "full" };
12
- switch (v.mode) {
13
- case "toc": {
14
- const toc = parseMarkdownToc(input.content);
15
- return { type: "knowledge", name: input.name, path: input.path, content: formatToc(toc) };
16
- }
17
- case "frontmatter": {
18
- const fm = extractFrontmatterOnly(input.content);
19
- return { type: "knowledge", name: input.name, path: input.path, content: fm ?? "(no frontmatter)" };
20
- }
21
- case "section": {
22
- const section = extractSection(input.content, v.heading);
23
- if (!section) {
24
- return {
25
- type: "knowledge",
26
- name: input.name,
27
- path: input.path,
28
- content: `Section "${v.heading}" not found in ${input.name}. Try --view toc to discover available headings.`,
29
- };
30
- }
31
- return { type: "knowledge", name: input.name, path: input.path, content: section.content };
32
- }
33
- case "lines": {
34
- return { type: "knowledge", name: input.name, path: input.path, content: extractLineRange(input.content, v.start, v.end) };
35
- }
36
- default: {
37
- return { type: "knowledge", name: input.name, path: input.path, content: input.content };
38
- }
39
- }
13
+ const renderer = getRenderer("knowledge-md");
14
+ const ctx = showInputToRenderContext(input, "knowledge-md");
15
+ return renderer.buildShowResponse(ctx);
40
16
  },
41
17
  defaultUsageGuide: [
42
18
  "Use `akm show <openRef>` to read the document; start with `--view toc` for large files.",
@@ -1,10 +1,9 @@
1
1
  import path from "node:path";
2
- import { SCRIPT_EXTENSIONS, SCRIPT_EXTENSIONS_BROAD } from "../asset-spec";
3
- import { hasErrnoCode, toPosix } from "../common";
4
- import { buildToolInfo } from "../tool-runner";
2
+ import { SCRIPT_EXTENSIONS_BROAD } from "../asset-spec";
3
+ import { toPosix } from "../common";
4
+ import { getRenderer } from "../file-context";
5
5
  import { extractDescriptionFromComments } from "../metadata";
6
- /** Extensions that buildToolInfo can handle (tool-runner supported) */
7
- const RUNNABLE_EXTENSIONS = SCRIPT_EXTENSIONS;
6
+ import { showInputToRenderContext } from "./handler-bridge";
8
7
  export const scriptHandler = {
9
8
  typeName: "script",
10
9
  stashDir: "scripts",
@@ -18,48 +17,13 @@ export const scriptHandler = {
18
17
  return path.join(typeRoot, name);
19
18
  },
20
19
  buildShowResponse(input) {
21
- const ext = path.extname(input.path).toLowerCase();
22
- // For extensions supported by tool-runner, show runCmd
23
- if (RUNNABLE_EXTENSIONS.has(ext)) {
24
- const stashDirs = input.stashDirs ?? [];
25
- const assetStashDir = stashDirs.find((d) => path.resolve(input.path).startsWith(path.resolve(d) + path.sep)) ?? stashDirs[0];
26
- if (assetStashDir) {
27
- try {
28
- const toolInfo = buildToolInfo(assetStashDir, input.path);
29
- return {
30
- type: "script",
31
- name: input.name,
32
- path: input.path,
33
- runCmd: toolInfo.runCmd,
34
- kind: toolInfo.kind,
35
- };
36
- }
37
- catch {
38
- // Fall through to content display
39
- }
40
- }
41
- }
42
- // For other extensions or when buildToolInfo fails, show file content
43
- return {
44
- type: "script",
45
- name: input.name,
46
- path: input.path,
47
- content: input.content,
48
- };
20
+ const renderer = getRenderer("script-source");
21
+ const ctx = showInputToRenderContext(input, "script-source");
22
+ return renderer.buildShowResponse(ctx);
49
23
  },
50
24
  enrichSearchHit(hit, stashDir) {
51
- const ext = path.extname(hit.path).toLowerCase();
52
- if (!RUNNABLE_EXTENSIONS.has(ext))
53
- return;
54
- try {
55
- const toolInfo = buildToolInfo(stashDir, hit.path);
56
- hit.runCmd = toolInfo.runCmd;
57
- hit.kind = toolInfo.kind;
58
- }
59
- catch (error) {
60
- if (!hasErrnoCode(error, "ENOENT"))
61
- throw error;
62
- }
25
+ const renderer = getRenderer("script-source");
26
+ renderer.enrichSearchHit(hit, stashDir);
63
27
  },
64
28
  defaultUsageGuide: [
65
29
  "Use the hit's runCmd for execution when available, or run the script directly with the appropriate interpreter.",
@@ -1,5 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { toPosix } from "../common";
3
+ import { getRenderer } from "../file-context";
4
+ import { showInputToRenderContext } from "./handler-bridge";
3
5
  export const skillHandler = {
4
6
  typeName: "skill",
5
7
  stashDir: "skills",
@@ -16,12 +18,9 @@ export const skillHandler = {
16
18
  return path.join(typeRoot, name, "SKILL.md");
17
19
  },
18
20
  buildShowResponse(input) {
19
- return {
20
- type: "skill",
21
- name: input.name,
22
- path: input.path,
23
- content: input.content,
24
- };
21
+ const renderer = getRenderer("skill-md");
22
+ const ctx = showInputToRenderContext(input, "skill-md");
23
+ return renderer.buildShowResponse(ctx);
25
24
  },
26
25
  defaultUsageGuide: [
27
26
  "Read and apply the skill instructions as written, then adapt examples to your current repo state and task.",
@@ -1,8 +1,9 @@
1
1
  import path from "node:path";
2
2
  import { SCRIPT_EXTENSIONS } from "../asset-spec";
3
- import { hasErrnoCode, toPosix } from "../common";
4
- import { buildToolInfo } from "../tool-runner";
3
+ import { toPosix } from "../common";
4
+ import { getRenderer } from "../file-context";
5
5
  import { extractDescriptionFromComments } from "../metadata";
6
+ import { showInputToRenderContext } from "./handler-bridge";
6
7
  export const toolHandler = {
7
8
  typeName: "tool",
8
9
  stashDir: "tools",
@@ -16,30 +17,13 @@ export const toolHandler = {
16
17
  return path.join(typeRoot, name);
17
18
  },
18
19
  buildShowResponse(input) {
19
- const stashDirs = input.stashDirs ?? [];
20
- const assetStashDir = stashDirs.find((d) => path.resolve(input.path).startsWith(path.resolve(d) + path.sep)) ?? stashDirs[0];
21
- if (!assetStashDir) {
22
- return { type: "tool", name: input.name, path: input.path, content: input.content };
23
- }
24
- const toolInfo = buildToolInfo(assetStashDir, input.path);
25
- return {
26
- type: "tool",
27
- name: input.name,
28
- path: input.path,
29
- runCmd: toolInfo.runCmd,
30
- kind: toolInfo.kind,
31
- };
20
+ const renderer = getRenderer("tool-script");
21
+ const ctx = showInputToRenderContext(input, "tool-script");
22
+ return renderer.buildShowResponse(ctx);
32
23
  },
33
24
  enrichSearchHit(hit, stashDir) {
34
- try {
35
- const toolInfo = buildToolInfo(stashDir, hit.path);
36
- hit.runCmd = toolInfo.runCmd;
37
- hit.kind = toolInfo.kind;
38
- }
39
- catch (error) {
40
- if (!hasErrnoCode(error, "ENOENT"))
41
- throw error;
42
- }
25
+ const renderer = getRenderer("tool-script");
26
+ renderer.enrichSearchHit(hit, stashDir);
43
27
  },
44
28
  defaultUsageGuide: [
45
29
  "Use the hit's runCmd for execution so runtime and working directory stay correct.",