agentikit 0.0.13 → 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 (156) hide show
  1. package/LICENSE +385 -0
  2. package/README.md +187 -110
  3. package/dist/{src/asset-spec.js → asset-spec.js} +11 -2
  4. package/dist/{src/asset-type-handler.js → asset-type-handler.js} +4 -3
  5. package/dist/cli.js +709 -0
  6. package/dist/common.js +192 -0
  7. package/dist/{src/config-cli.js → config-cli.js} +36 -30
  8. package/dist/{src/config.js → config.js} +95 -25
  9. package/dist/{src/db.js → db.js} +123 -51
  10. package/dist/{src/embedder.js → embedder.js} +57 -2
  11. package/dist/errors.js +28 -0
  12. package/dist/file-context.js +188 -0
  13. package/dist/{src/frontmatter.js → frontmatter.js} +1 -1
  14. package/dist/{src/github.js → github.js} +1 -3
  15. package/dist/handlers/agent-handler.js +19 -0
  16. package/dist/handlers/command-handler.js +20 -0
  17. package/dist/handlers/handler-bridge.js +51 -0
  18. package/dist/handlers/index.js +19 -0
  19. package/dist/handlers/knowledge-handler.js +32 -0
  20. package/dist/handlers/script-handler.js +42 -0
  21. package/dist/{src/handlers → handlers}/skill-handler.js +5 -6
  22. package/dist/{src/handlers → handlers}/tool-handler.js +8 -24
  23. package/dist/{src/indexer.js → indexer.js} +50 -26
  24. package/dist/init.js +43 -0
  25. package/dist/{src/llm.js → llm.js} +6 -11
  26. package/dist/lockfile.js +60 -0
  27. package/dist/matchers.js +163 -0
  28. package/dist/{src/metadata.js → metadata.js} +36 -16
  29. package/dist/{src/origin-resolve.js → origin-resolve.js} +10 -9
  30. package/dist/paths.js +83 -0
  31. package/dist/{src/registry-install.js → registry-install.js} +151 -19
  32. package/dist/{src/registry-resolve.js → registry-resolve.js} +190 -26
  33. package/dist/{src/registry-search.js → registry-search.js} +13 -21
  34. package/dist/renderers.js +286 -0
  35. package/dist/{src/ripgrep-install.js → ripgrep-install.js} +8 -27
  36. package/dist/{src/ripgrep-resolve.js → ripgrep-resolve.js} +21 -11
  37. package/dist/ripgrep.js +2 -0
  38. package/dist/self-update.js +226 -0
  39. package/dist/{src/stash-add.js → stash-add.js} +14 -4
  40. package/dist/stash-clone.js +115 -0
  41. package/dist/{src/stash-ref.js → stash-ref.js} +10 -9
  42. package/dist/{src/stash-registry.js → stash-registry.js} +21 -46
  43. package/dist/{src/stash-resolve.js → stash-resolve.js} +10 -9
  44. package/dist/{src/stash-search.js → stash-search.js} +89 -74
  45. package/dist/stash-show.js +74 -0
  46. package/dist/stash-source.js +127 -0
  47. package/dist/submit.js +557 -0
  48. package/dist/{src/tool-runner.js → tool-runner.js} +1 -5
  49. package/dist/{src/walker.js → walker.js} +38 -0
  50. package/dist/warn.js +20 -0
  51. package/package.json +13 -18
  52. package/dist/index.d.ts +0 -28
  53. package/dist/index.js +0 -15
  54. package/dist/src/asset-spec.d.ts +0 -16
  55. package/dist/src/asset-type-handler.d.ts +0 -27
  56. package/dist/src/cli.d.ts +0 -2
  57. package/dist/src/cli.js +0 -399
  58. package/dist/src/common.d.ts +0 -13
  59. package/dist/src/common.js +0 -60
  60. package/dist/src/config-cli.d.ts +0 -9
  61. package/dist/src/config.d.ts +0 -50
  62. package/dist/src/db.d.ts +0 -46
  63. package/dist/src/embedder.d.ts +0 -10
  64. package/dist/src/frontmatter.d.ts +0 -30
  65. package/dist/src/github.d.ts +0 -4
  66. package/dist/src/handlers/agent-handler.d.ts +0 -2
  67. package/dist/src/handlers/agent-handler.js +0 -26
  68. package/dist/src/handlers/command-handler.d.ts +0 -2
  69. package/dist/src/handlers/command-handler.js +0 -23
  70. package/dist/src/handlers/index.d.ts +0 -6
  71. package/dist/src/handlers/index.js +0 -23
  72. package/dist/src/handlers/knowledge-handler.d.ts +0 -2
  73. package/dist/src/handlers/knowledge-handler.js +0 -56
  74. package/dist/src/handlers/markdown-helpers.d.ts +0 -7
  75. package/dist/src/handlers/script-handler.d.ts +0 -2
  76. package/dist/src/handlers/script-handler.js +0 -78
  77. package/dist/src/handlers/skill-handler.d.ts +0 -2
  78. package/dist/src/handlers/tool-handler.d.ts +0 -2
  79. package/dist/src/indexer.d.ts +0 -22
  80. package/dist/src/init.d.ts +0 -19
  81. package/dist/src/init.js +0 -99
  82. package/dist/src/llm.d.ts +0 -15
  83. package/dist/src/markdown.d.ts +0 -18
  84. package/dist/src/metadata.d.ts +0 -41
  85. package/dist/src/origin-resolve.d.ts +0 -19
  86. package/dist/src/registry-install.d.ts +0 -11
  87. package/dist/src/registry-resolve.d.ts +0 -3
  88. package/dist/src/registry-search.d.ts +0 -27
  89. package/dist/src/registry-types.d.ts +0 -62
  90. package/dist/src/ripgrep-install.d.ts +0 -12
  91. package/dist/src/ripgrep-resolve.d.ts +0 -13
  92. package/dist/src/ripgrep.d.ts +0 -3
  93. package/dist/src/ripgrep.js +0 -2
  94. package/dist/src/stash-add.d.ts +0 -4
  95. package/dist/src/stash-clone.d.ts +0 -22
  96. package/dist/src/stash-clone.js +0 -83
  97. package/dist/src/stash-ref.d.ts +0 -31
  98. package/dist/src/stash-registry.d.ts +0 -18
  99. package/dist/src/stash-resolve.d.ts +0 -2
  100. package/dist/src/stash-search.d.ts +0 -8
  101. package/dist/src/stash-show.d.ts +0 -5
  102. package/dist/src/stash-show.js +0 -46
  103. package/dist/src/stash-source.d.ts +0 -24
  104. package/dist/src/stash-source.js +0 -81
  105. package/dist/src/stash-types.d.ts +0 -227
  106. package/dist/src/stash.d.ts +0 -16
  107. package/dist/src/stash.js +0 -9
  108. package/dist/src/tool-runner.d.ts +0 -35
  109. package/dist/src/walker.d.ts +0 -19
  110. package/src/asset-spec.ts +0 -85
  111. package/src/asset-type-handler.ts +0 -77
  112. package/src/cli.ts +0 -427
  113. package/src/common.ts +0 -76
  114. package/src/config-cli.ts +0 -499
  115. package/src/config.ts +0 -305
  116. package/src/db.ts +0 -411
  117. package/src/embedder.ts +0 -128
  118. package/src/frontmatter.ts +0 -95
  119. package/src/github.ts +0 -21
  120. package/src/handlers/agent-handler.ts +0 -32
  121. package/src/handlers/command-handler.ts +0 -29
  122. package/src/handlers/index.ts +0 -25
  123. package/src/handlers/knowledge-handler.ts +0 -62
  124. package/src/handlers/markdown-helpers.ts +0 -19
  125. package/src/handlers/script-handler.ts +0 -92
  126. package/src/handlers/skill-handler.ts +0 -37
  127. package/src/handlers/tool-handler.ts +0 -71
  128. package/src/indexer.ts +0 -392
  129. package/src/init.ts +0 -114
  130. package/src/llm.ts +0 -125
  131. package/src/markdown.ts +0 -106
  132. package/src/metadata.ts +0 -333
  133. package/src/origin-resolve.ts +0 -67
  134. package/src/registry-install.ts +0 -361
  135. package/src/registry-resolve.ts +0 -341
  136. package/src/registry-search.ts +0 -335
  137. package/src/registry-types.ts +0 -72
  138. package/src/ripgrep-install.ts +0 -200
  139. package/src/ripgrep-resolve.ts +0 -72
  140. package/src/ripgrep.ts +0 -3
  141. package/src/stash-add.ts +0 -63
  142. package/src/stash-clone.ts +0 -127
  143. package/src/stash-ref.ts +0 -99
  144. package/src/stash-registry.ts +0 -259
  145. package/src/stash-resolve.ts +0 -50
  146. package/src/stash-search.ts +0 -613
  147. package/src/stash-show.ts +0 -55
  148. package/src/stash-source.ts +0 -103
  149. package/src/stash-types.ts +0 -231
  150. package/src/stash.ts +0 -39
  151. package/src/tool-runner.ts +0 -142
  152. package/src/walker.ts +0 -53
  153. /package/dist/{src/handlers → handlers}/markdown-helpers.js +0 -0
  154. /package/dist/{src/markdown.js → markdown.js} +0 -0
  155. /package/dist/{src/registry-types.js → registry-types.js} +0 -0
  156. /package/dist/{src/stash-types.js → stash-types.js} +0 -0
@@ -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
+ }
@@ -0,0 +1,19 @@
1
+ import { registerAssetType } from "../asset-type-handler";
2
+ import { agentHandler } from "./agent-handler";
3
+ import { commandHandler } from "./command-handler";
4
+ import { knowledgeHandler } from "./knowledge-handler";
5
+ import { scriptHandler } from "./script-handler";
6
+ import { skillHandler } from "./skill-handler";
7
+ import { toolHandler } from "./tool-handler";
8
+ /**
9
+ * Register all built-in asset type handlers.
10
+ * Called once from ensureHandlersRegistered in asset-type-handler.ts.
11
+ */
12
+ export function registerBuiltinHandlers() {
13
+ registerAssetType(toolHandler);
14
+ registerAssetType(skillHandler);
15
+ registerAssetType(commandHandler);
16
+ registerAssetType(agentHandler);
17
+ registerAssetType(knowledgeHandler);
18
+ registerAssetType(scriptHandler);
19
+ }
@@ -0,0 +1,32 @@
1
+ import fs from "node:fs";
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";
6
+ export const knowledgeHandler = {
7
+ typeName: "knowledge",
8
+ stashDir: "knowledge",
9
+ isRelevantFile: isMarkdownFile,
10
+ toCanonicalName: markdownCanonicalName,
11
+ toAssetPath: markdownAssetPath,
12
+ buildShowResponse(input) {
13
+ const renderer = getRenderer("knowledge-md");
14
+ const ctx = showInputToRenderContext(input, "knowledge-md");
15
+ return renderer.buildShowResponse(ctx);
16
+ },
17
+ defaultUsageGuide: [
18
+ "Use `akm show <openRef>` to read the document; start with `--view toc` for large files.",
19
+ "Use `--view section` or `--view lines` to load only the part you need.",
20
+ ],
21
+ extractTypeMetadata(entry, file) {
22
+ try {
23
+ const mdContent = fs.readFileSync(file, "utf8");
24
+ const toc = parseMarkdownToc(mdContent);
25
+ if (toc.headings.length > 0)
26
+ entry.toc = toc.headings;
27
+ }
28
+ catch {
29
+ // Non-fatal: skip TOC if file can't be read
30
+ }
31
+ },
32
+ };
@@ -0,0 +1,42 @@
1
+ import path from "node:path";
2
+ import { SCRIPT_EXTENSIONS_BROAD } from "../asset-spec";
3
+ import { toPosix } from "../common";
4
+ import { getRenderer } from "../file-context";
5
+ import { extractDescriptionFromComments } from "../metadata";
6
+ import { showInputToRenderContext } from "./handler-bridge";
7
+ export const scriptHandler = {
8
+ typeName: "script",
9
+ stashDir: "scripts",
10
+ isRelevantFile(fileName) {
11
+ return SCRIPT_EXTENSIONS_BROAD.has(path.extname(fileName).toLowerCase());
12
+ },
13
+ toCanonicalName(typeRoot, filePath) {
14
+ return toPosix(path.relative(typeRoot, filePath));
15
+ },
16
+ toAssetPath(typeRoot, name) {
17
+ return path.join(typeRoot, name);
18
+ },
19
+ buildShowResponse(input) {
20
+ const renderer = getRenderer("script-source");
21
+ const ctx = showInputToRenderContext(input, "script-source");
22
+ return renderer.buildShowResponse(ctx);
23
+ },
24
+ enrichSearchHit(hit, stashDir) {
25
+ const renderer = getRenderer("script-source");
26
+ renderer.enrichSearchHit(hit, stashDir);
27
+ },
28
+ defaultUsageGuide: [
29
+ "Use the hit's runCmd for execution when available, or run the script directly with the appropriate interpreter.",
30
+ "Use `akm show <openRef>` to inspect the script before running it.",
31
+ ],
32
+ extractTypeMetadata(entry, file, ext) {
33
+ if (ext !== ".md") {
34
+ const commentDesc = extractDescriptionFromComments(file);
35
+ if (commentDesc && !entry.description) {
36
+ entry.description = commentDesc;
37
+ entry.source = "comments";
38
+ entry.confidence = 0.7;
39
+ }
40
+ }
41
+ },
42
+ };
@@ -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.",
@@ -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, 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();
@@ -26,14 +28,23 @@ export async function agentikitIndex(options) {
26
28
  const builtAtMs = isIncremental ? new Date(prevBuiltAt).getTime() : 0;
27
29
  if (options?.full || !isIncremental) {
28
30
  // Wipe all entries for full rebuild or stashDir change
29
- db.exec("DELETE FROM entries");
30
- db.exec("DELETE FROM entries_fts");
31
- if (isVecAvailable()) {
31
+ // Delete from child tables first to respect foreign key constraints
32
+ try {
33
+ db.exec("DELETE FROM embeddings");
34
+ }
35
+ catch {
36
+ /* ignore */
37
+ }
38
+ if (isVecAvailable(db)) {
32
39
  try {
33
40
  db.exec("DELETE FROM entries_vec");
34
41
  }
35
- catch { /* ignore */ }
42
+ catch {
43
+ /* ignore */
44
+ }
36
45
  }
46
+ db.exec("DELETE FROM entries_fts");
47
+ db.exec("DELETE FROM entries");
37
48
  }
38
49
  const tWalkStart = Date.now();
39
50
  // Walk stash dirs and index entries
@@ -54,6 +65,8 @@ export async function agentikitIndex(options) {
54
65
  setMeta(db, "stashDirs", JSON.stringify(allStashDirs));
55
66
  setMeta(db, "hasEmbeddings", hasEmbeddings ? "1" : "0");
56
67
  const totalEntries = getEntryCount(db);
68
+ // Warn on every index run if using JS fallback with many entries
69
+ warnIfVecMissing(db);
57
70
  const tEnd = Date.now();
58
71
  return {
59
72
  stashDir,
@@ -117,6 +130,18 @@ function indexEntries(db, allStashDirs, stashDir, isIncremental, builtAtMs) {
117
130
  stash = migration.stash;
118
131
  writeStashFile(dirPath, stash);
119
132
  }
133
+ // Check for files on disk that aren't covered by existing .stash.json entries.
134
+ // This handles the case where new files are added after the initial index.
135
+ const coveredFiles = new Set(stash.entries.map((e) => e.entry).filter((e) => !!e));
136
+ const uncoveredFiles = files.filter((f) => !coveredFiles.has(path.basename(f)));
137
+ if (uncoveredFiles.length > 0) {
138
+ const generated = generateMetadata(dirPath, assetType, uncoveredFiles, typeRoot);
139
+ if (generated.entries.length > 0) {
140
+ stash = { entries: [...stash.entries, ...generated.entries] };
141
+ writeStashFile(dirPath, stash);
142
+ generatedCount += generated.entries.length;
143
+ }
144
+ }
120
145
  }
121
146
  if (!stash) {
122
147
  // Generate metadata heuristically
@@ -128,9 +153,7 @@ function indexEntries(db, allStashDirs, stashDir, isIncremental, builtAtMs) {
128
153
  }
129
154
  if (stash) {
130
155
  for (const entry of stash.entries) {
131
- const entryPath = entry.entry
132
- ? path.join(dirPath, entry.entry)
133
- : files[0] || dirPath;
156
+ const entryPath = entry.entry ? path.join(dirPath, entry.entry) : files[0] || dirPath;
134
157
  const entryKey = `${currentStashDir}:${entry.type}:${entry.name}`;
135
158
  const searchText = buildSearchText(entry);
136
159
  upsertEntry(db, entryKey, dirPath, entryPath, currentStashDir, entry, searchText);
@@ -166,19 +189,22 @@ async function enhanceDirsWithLlm(db, config, dirsNeedingLlm) {
166
189
  }
167
190
  }
168
191
  async function generateEmbeddingsForDb(db, config) {
169
- if (!config.semanticSearch || !isVecAvailable())
192
+ if (!config.semanticSearch)
170
193
  return false;
171
194
  try {
172
- const { embed } = await import("./embedder.js");
195
+ const { embedBatch } = await import("./embedder.js");
173
196
  const allEntries = getAllEntriesForEmbedding(db);
174
- for (const { id, searchText } of allEntries) {
175
- const embedding = await embed(searchText, config.embedding);
176
- upsertEmbedding(db, id, embedding);
197
+ if (allEntries.length === 0)
198
+ return true;
199
+ const texts = allEntries.map((e) => e.searchText);
200
+ const embeddings = await embedBatch(texts, config.embedding);
201
+ for (let i = 0; i < allEntries.length; i++) {
202
+ upsertEmbedding(db, allEntries[i].id, embeddings[i]);
177
203
  }
178
204
  return true;
179
205
  }
180
206
  catch (error) {
181
- 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));
182
208
  return false;
183
209
  }
184
210
  }
@@ -187,15 +213,13 @@ function getAllEntriesForEmbedding(db) {
187
213
  return db
188
214
  .prepare(`
189
215
  SELECT e.id, e.search_text AS searchText FROM entries e
190
- WHERE NOT EXISTS (SELECT 1 FROM entries_vec v WHERE v.id = e.id)
216
+ WHERE NOT EXISTS (SELECT 1 FROM embeddings b WHERE b.id = e.id)
191
217
  `)
192
218
  .all();
193
219
  }
194
220
  function isDirStale(dirPath, currentFiles, previousEntries, builtAtMs) {
195
221
  // Check if file set changed (additions or deletions)
196
- const prevFileNames = new Set(previousEntries
197
- .map((ie) => ie.entry.entry)
198
- .filter((e) => !!e));
222
+ const prevFileNames = new Set(previousEntries.map((ie) => ie.entry.entry).filter((e) => !!e));
199
223
  const currFileNames = new Set(currentFiles.map((f) => path.basename(f)));
200
224
  if (prevFileNames.size !== currFileNames.size)
201
225
  return true;
@@ -216,11 +240,11 @@ function isDirStale(dirPath, currentFiles, previousEntries, builtAtMs) {
216
240
  // Check .stash.json modification time
217
241
  const stashPath = path.join(dirPath, ".stash.json");
218
242
  try {
219
- if (fs.existsSync(stashPath) && fs.statSync(stashPath).mtimeMs > builtAtMs)
243
+ if (fs.statSync(stashPath).mtimeMs > builtAtMs)
220
244
  return true;
221
245
  }
222
246
  catch {
223
- // ignore
247
+ // file doesn't exist, not stale
224
248
  }
225
249
  return false;
226
250
  }
@@ -253,15 +277,15 @@ async function enhanceStashWithLlm(llmConfig, stash, dirPath, files) {
253
277
  const enhanced = [];
254
278
  for (const entry of stash.entries) {
255
279
  try {
256
- const entryFile = entry.entry
257
- ? files.find((f) => path.basename(f) === entry.entry) ?? files[0]
258
- : files[0];
280
+ const entryFile = entry.entry ? (files.find((f) => path.basename(f) === entry.entry) ?? files[0]) : files[0];
259
281
  let fileContent;
260
282
  if (entryFile) {
261
283
  try {
262
284
  fileContent = fs.readFileSync(entryFile, "utf8");
263
285
  }
264
- catch { /* ignore unreadable files */ }
286
+ catch {
287
+ /* ignore unreadable files */
288
+ }
265
289
  }
266
290
  const improvements = await enhanceMetadata(llmConfig, entry, fileContent);
267
291
  const updated = { ...entry };
package/dist/init.js ADDED
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Agentikit initialization logic.
3
+ *
4
+ * Creates the working stash directory structure, persists the stashDir
5
+ * in config.json, and ensures ripgrep is available.
6
+ */
7
+ import fs from "node:fs";
8
+ import path from "node:path";
9
+ import { TYPE_DIRS } from "./asset-spec";
10
+ import { getConfigPath, loadConfig, saveConfig } from "./config";
11
+ import { getBinDir, getDefaultStashDir } from "./paths";
12
+ import { ensureRg } from "./ripgrep-install";
13
+ export async function agentikitInit(options) {
14
+ const stashDir = options?.dir ? path.resolve(options.dir) : getDefaultStashDir();
15
+ let created = false;
16
+ if (!fs.existsSync(stashDir)) {
17
+ fs.mkdirSync(stashDir, { recursive: true });
18
+ created = true;
19
+ }
20
+ for (const sub of Object.values(TYPE_DIRS)) {
21
+ const subDir = path.join(stashDir, sub);
22
+ if (!fs.existsSync(subDir)) {
23
+ fs.mkdirSync(subDir, { recursive: true });
24
+ }
25
+ }
26
+ // Persist stashDir in config.json
27
+ const configPath = getConfigPath();
28
+ const existing = loadConfig();
29
+ if (!existing.stashDir || existing.stashDir !== stashDir) {
30
+ saveConfig({ ...existing, stashDir });
31
+ }
32
+ // Ensure ripgrep is available (install to cache/bin if needed)
33
+ let ripgrep;
34
+ try {
35
+ const binDir = getBinDir();
36
+ const rgResult = ensureRg(binDir);
37
+ ripgrep = rgResult;
38
+ }
39
+ catch {
40
+ // Non-fatal: ripgrep is optional, search works without it
41
+ }
42
+ return { stashDir, created, configPath, ripgrep };
43
+ }
@@ -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 {
@@ -0,0 +1,60 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { getConfigDir } from "./config";
4
+ // ── Paths ───────────────────────────────────────────────────────────────────
5
+ function getLockfilePath() {
6
+ return path.join(getConfigDir(), "stash.lock");
7
+ }
8
+ // ── Read / Write ────────────────────────────────────────────────────────────
9
+ export function readLockfile() {
10
+ const lockfilePath = getLockfilePath();
11
+ try {
12
+ const raw = JSON.parse(fs.readFileSync(lockfilePath, "utf8"));
13
+ if (!Array.isArray(raw))
14
+ return [];
15
+ return raw.filter(isValidLockfileEntry);
16
+ }
17
+ catch {
18
+ return [];
19
+ }
20
+ }
21
+ export function writeLockfile(entries) {
22
+ const lockfilePath = getLockfilePath();
23
+ const dir = path.dirname(lockfilePath);
24
+ fs.mkdirSync(dir, { recursive: true });
25
+ const tmpPath = lockfilePath + `.tmp.${process.pid}`;
26
+ try {
27
+ fs.writeFileSync(tmpPath, JSON.stringify(entries, null, 2) + "\n", "utf8");
28
+ fs.renameSync(tmpPath, lockfilePath);
29
+ }
30
+ catch (err) {
31
+ try {
32
+ fs.unlinkSync(tmpPath);
33
+ }
34
+ catch {
35
+ /* ignore cleanup failure */
36
+ }
37
+ throw err;
38
+ }
39
+ }
40
+ export function upsertLockEntry(entry) {
41
+ const entries = readLockfile();
42
+ const withoutExisting = entries.filter((e) => e.id !== entry.id);
43
+ writeLockfile([...withoutExisting, entry]);
44
+ }
45
+ export function removeLockEntry(id) {
46
+ const entries = readLockfile();
47
+ writeLockfile(entries.filter((e) => e.id !== id));
48
+ }
49
+ // ── Helpers ─────────────────────────────────────────────────────────────────
50
+ function isValidLockfileEntry(value) {
51
+ if (typeof value !== "object" || value === null || Array.isArray(value))
52
+ return false;
53
+ const obj = value;
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 !== "");
60
+ }