agentikit 0.0.8 → 0.0.12

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 (112) hide show
  1. package/README.md +135 -117
  2. package/dist/index.d.ts +13 -3
  3. package/dist/index.js +7 -1
  4. package/dist/src/asset-spec.d.ts +2 -0
  5. package/dist/src/asset-spec.js +22 -3
  6. package/dist/src/asset-type-handler.d.ts +27 -0
  7. package/dist/src/asset-type-handler.js +33 -0
  8. package/dist/src/cli.js +335 -100
  9. package/dist/src/common.d.ts +6 -1
  10. package/dist/src/common.js +18 -4
  11. package/dist/src/config-cli.d.ts +9 -0
  12. package/dist/src/config-cli.js +473 -0
  13. package/dist/src/config.d.ts +25 -6
  14. package/dist/src/config.js +188 -28
  15. package/dist/src/db.d.ts +46 -0
  16. package/dist/src/db.js +299 -0
  17. package/dist/src/embedder.js +12 -7
  18. package/dist/src/github.d.ts +4 -0
  19. package/dist/src/github.js +19 -0
  20. package/dist/src/handlers/agent-handler.d.ts +2 -0
  21. package/dist/src/handlers/agent-handler.js +26 -0
  22. package/dist/src/handlers/command-handler.d.ts +2 -0
  23. package/dist/src/handlers/command-handler.js +23 -0
  24. package/dist/src/handlers/index.d.ts +6 -0
  25. package/dist/src/handlers/index.js +23 -0
  26. package/dist/src/handlers/knowledge-handler.d.ts +2 -0
  27. package/dist/src/handlers/knowledge-handler.js +56 -0
  28. package/dist/src/handlers/markdown-helpers.d.ts +7 -0
  29. package/dist/src/handlers/markdown-helpers.js +15 -0
  30. package/dist/src/handlers/script-handler.d.ts +2 -0
  31. package/dist/src/handlers/script-handler.js +78 -0
  32. package/dist/src/handlers/skill-handler.d.ts +2 -0
  33. package/dist/src/handlers/skill-handler.js +30 -0
  34. package/dist/src/handlers/tool-handler.d.ts +2 -0
  35. package/dist/src/handlers/tool-handler.js +58 -0
  36. package/dist/src/indexer.d.ts +1 -23
  37. package/dist/src/indexer.js +162 -155
  38. package/dist/src/init.d.ts +2 -2
  39. package/dist/src/init.js +21 -9
  40. package/dist/src/llm.js +4 -3
  41. package/dist/src/metadata.d.ts +1 -1
  42. package/dist/src/metadata.js +22 -64
  43. package/dist/src/origin-resolve.d.ts +19 -0
  44. package/dist/src/origin-resolve.js +53 -0
  45. package/dist/src/registry-install.d.ts +11 -0
  46. package/dist/src/registry-install.js +315 -0
  47. package/dist/src/registry-resolve.d.ts +3 -0
  48. package/dist/src/registry-resolve.js +299 -0
  49. package/dist/src/registry-search.d.ts +27 -0
  50. package/dist/src/registry-search.js +263 -0
  51. package/dist/src/registry-types.d.ts +62 -0
  52. package/dist/src/registry-types.js +1 -0
  53. package/dist/src/stash-add.d.ts +4 -0
  54. package/dist/src/stash-add.js +59 -0
  55. package/dist/src/stash-clone.d.ts +22 -0
  56. package/dist/src/stash-clone.js +83 -0
  57. package/dist/src/stash-ref.d.ts +27 -3
  58. package/dist/src/stash-ref.js +63 -24
  59. package/dist/src/stash-registry.d.ts +18 -0
  60. package/dist/src/stash-registry.js +221 -0
  61. package/dist/src/stash-resolve.js +3 -0
  62. package/dist/src/stash-search.d.ts +3 -1
  63. package/dist/src/stash-search.js +357 -138
  64. package/dist/src/stash-show.d.ts +1 -1
  65. package/dist/src/stash-show.js +28 -89
  66. package/dist/src/stash-source.d.ts +24 -0
  67. package/dist/src/stash-source.js +81 -0
  68. package/dist/src/stash-types.d.ts +175 -1
  69. package/dist/src/stash.d.ts +9 -1
  70. package/dist/src/stash.js +5 -0
  71. package/dist/src/tool-runner.d.ts +1 -1
  72. package/dist/src/tool-runner.js +18 -5
  73. package/package.json +7 -2
  74. package/src/asset-spec.ts +20 -4
  75. package/src/asset-type-handler.ts +77 -0
  76. package/src/cli.ts +354 -103
  77. package/src/common.ts +23 -5
  78. package/src/config-cli.ts +499 -0
  79. package/src/config.ts +218 -37
  80. package/src/db.ts +411 -0
  81. package/src/embedder.ts +22 -11
  82. package/src/github.ts +21 -0
  83. package/src/handlers/agent-handler.ts +32 -0
  84. package/src/handlers/command-handler.ts +29 -0
  85. package/src/handlers/index.ts +25 -0
  86. package/src/handlers/knowledge-handler.ts +62 -0
  87. package/src/handlers/markdown-helpers.ts +19 -0
  88. package/src/handlers/script-handler.ts +92 -0
  89. package/src/handlers/skill-handler.ts +37 -0
  90. package/src/handlers/tool-handler.ts +71 -0
  91. package/src/indexer.ts +208 -187
  92. package/src/init.ts +17 -9
  93. package/src/llm.ts +4 -3
  94. package/src/metadata.ts +21 -65
  95. package/src/origin-resolve.ts +67 -0
  96. package/src/registry-install.ts +361 -0
  97. package/src/registry-resolve.ts +341 -0
  98. package/src/registry-search.ts +335 -0
  99. package/src/registry-types.ts +72 -0
  100. package/src/stash-add.ts +63 -0
  101. package/src/stash-clone.ts +127 -0
  102. package/src/stash-ref.ts +84 -26
  103. package/src/stash-registry.ts +259 -0
  104. package/src/stash-resolve.ts +3 -0
  105. package/src/stash-search.ts +425 -155
  106. package/src/stash-show.ts +33 -82
  107. package/src/stash-source.ts +103 -0
  108. package/src/stash-types.ts +186 -1
  109. package/src/stash.ts +23 -0
  110. package/src/tool-runner.ts +18 -5
  111. package/dist/src/similarity.d.ts +0 -34
  112. package/src/similarity.ts +0 -271
@@ -3,185 +3,194 @@ import path from "node:path";
3
3
  import { resolveStashDir } from "./common";
4
4
  import { ASSET_TYPES, TYPE_DIRS, deriveCanonicalAssetName } from "./asset-spec";
5
5
  import { loadStashFile, writeStashFile, generateMetadata, } from "./metadata";
6
- import { TfIdfAdapter } from "./similarity";
7
6
  import { walkStash } from "./walker";
8
- // ── Constants ───────────────────────────────────────────────────────────────
9
- const INDEX_VERSION = 4;
10
- // ── Index Path ──────────────────────────────────────────────────────────────
11
- export function getIndexPath() {
12
- const cacheDir = process.env.XDG_CACHE_HOME
13
- || path.join(process.env.HOME || process.env.USERPROFILE || "", ".cache");
14
- return path.join(cacheDir, "agentikit", "index.json");
15
- }
16
- export function loadSearchIndex() {
17
- const indexPath = getIndexPath();
18
- if (!fs.existsSync(indexPath))
19
- return null;
20
- try {
21
- const raw = JSON.parse(fs.readFileSync(indexPath, "utf8"));
22
- if (raw?.version !== INDEX_VERSION)
23
- return null;
24
- return raw;
25
- }
26
- catch {
27
- return null;
28
- }
29
- }
7
+ import { openDatabase, closeDatabase, getDbPath, getMeta, setMeta, upsertEntry, deleteEntriesByDir, rebuildFts, upsertEmbedding, getEntriesByDir, getEntryCount, isVecAvailable, DB_VERSION, } from "./db";
30
8
  // ── Indexer ──────────────────────────────────────────────────────────────────
31
9
  export async function agentikitIndex(options) {
32
10
  const stashDir = options?.stashDir || resolveStashDir();
33
- // Load config to get additional stash dirs and semantic search setting
11
+ // Load config and resolve all stash sources
34
12
  const { loadConfig } = await import("./config.js");
35
- const config = loadConfig(stashDir);
36
- const allStashDirs = [stashDir];
37
- for (const d of config.additionalStashDirs) {
38
- try {
39
- if (fs.statSync(d).isDirectory() && !allStashDirs.includes(path.resolve(d))) {
40
- allStashDirs.push(path.resolve(d));
13
+ const config = loadConfig();
14
+ const { resolveAllStashDirs } = await import("./stash-source.js");
15
+ const allStashDirs = resolveAllStashDirs(stashDir);
16
+ const t0 = Date.now();
17
+ // Open database pass embedding dimension from config if available
18
+ const dbPath = getDbPath();
19
+ const embeddingDim = config.embedding?.dimension;
20
+ const db = openDatabase(dbPath, embeddingDim ? { embeddingDim } : undefined);
21
+ try {
22
+ // Check if we should do incremental
23
+ const prevStashDir = getMeta(db, "stashDir");
24
+ const prevBuiltAt = getMeta(db, "builtAt");
25
+ const isIncremental = !options?.full && prevStashDir === stashDir && !!prevBuiltAt;
26
+ const builtAtMs = isIncremental ? new Date(prevBuiltAt).getTime() : 0;
27
+ if (options?.full || !isIncremental) {
28
+ // 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()) {
32
+ try {
33
+ db.exec("DELETE FROM entries_vec");
34
+ }
35
+ catch { /* ignore */ }
41
36
  }
42
37
  }
43
- catch { /* skip nonexistent dirs */ }
38
+ const tWalkStart = Date.now();
39
+ // Walk stash dirs and index entries
40
+ const { scannedDirs, skippedDirs, generatedCount, dirsNeedingLlm } = indexEntries(db, allStashDirs, stashDir, isIncremental, builtAtMs);
41
+ // Enhance entries with LLM if configured
42
+ await enhanceDirsWithLlm(db, config, dirsNeedingLlm);
43
+ const tWalkEnd = Date.now();
44
+ // Rebuild FTS after all inserts
45
+ rebuildFts(db);
46
+ const tFtsEnd = Date.now();
47
+ // Generate embeddings if semantic search is enabled
48
+ const hasEmbeddings = await generateEmbeddingsForDb(db, config);
49
+ const tEmbedEnd = Date.now();
50
+ // Update metadata
51
+ setMeta(db, "version", String(DB_VERSION));
52
+ setMeta(db, "builtAt", new Date().toISOString());
53
+ setMeta(db, "stashDir", stashDir);
54
+ setMeta(db, "stashDirs", JSON.stringify(allStashDirs));
55
+ setMeta(db, "hasEmbeddings", hasEmbeddings ? "1" : "0");
56
+ const totalEntries = getEntryCount(db);
57
+ const tEnd = Date.now();
58
+ return {
59
+ stashDir,
60
+ totalEntries,
61
+ generatedMetadata: generatedCount,
62
+ indexPath: dbPath,
63
+ mode: isIncremental ? "incremental" : "full",
64
+ directoriesScanned: scannedDirs,
65
+ directoriesSkipped: skippedDirs,
66
+ timing: {
67
+ totalMs: tEnd - t0,
68
+ walkMs: tWalkEnd - tWalkStart,
69
+ embedMs: tEmbedEnd - tFtsEnd,
70
+ ftsMs: tFtsEnd - tWalkEnd,
71
+ },
72
+ };
44
73
  }
45
- const t0 = Date.now();
46
- const allEntries = [];
47
- let generatedCount = 0;
74
+ finally {
75
+ closeDatabase(db);
76
+ }
77
+ }
78
+ // ── Extracted helpers for agentikitIndex ─────────────────────────────────────
79
+ function indexEntries(db, allStashDirs, stashDir, isIncremental, builtAtMs) {
48
80
  let scannedDirs = 0;
49
81
  let skippedDirs = 0;
50
- // Load previous index for incremental mode
51
- const previousIndex = !options?.full ? loadSearchIndex() : null;
52
- const isIncremental = previousIndex !== null && previousIndex.stashDir === stashDir;
53
- const builtAtMs = isIncremental ? new Date(previousIndex.builtAt).getTime() : 0;
54
- // Build lookup of previous entries by dirPath
55
- const previousEntriesByDir = new Map();
56
- if (isIncremental) {
57
- for (const ie of previousIndex.entries) {
58
- const list = previousEntriesByDir.get(ie.dirPath) || [];
59
- list.push(ie);
60
- previousEntriesByDir.set(ie.dirPath, list);
61
- }
62
- }
82
+ let generatedCount = 0;
63
83
  const seenPaths = new Set();
64
- const tWalkStart = Date.now();
65
- for (const currentStashDir of allStashDirs) {
66
- for (const assetType of ASSET_TYPES) {
67
- const typeRoot = path.join(currentStashDir, TYPE_DIRS[assetType]);
68
- try {
69
- if (!fs.statSync(typeRoot).isDirectory())
70
- continue;
71
- }
72
- catch {
73
- continue;
74
- }
75
- // Group files by their immediate parent directory
76
- const dirGroups = walkStash(typeRoot, assetType);
77
- for (const { dirPath, files } of dirGroups) {
78
- // Deduplicate by dirPath across stash dirs
79
- if (seenPaths.has(path.resolve(dirPath)))
80
- continue;
81
- seenPaths.add(path.resolve(dirPath));
82
- // Incremental: skip directories that haven't changed
83
- const prevEntries = previousEntriesByDir.get(dirPath);
84
- if (isIncremental && prevEntries && !isDirStale(dirPath, files, prevEntries, builtAtMs)) {
85
- allEntries.push(...prevEntries);
86
- skippedDirs++;
84
+ const dirsNeedingLlm = [];
85
+ const insertTransaction = db.transaction(() => {
86
+ for (const currentStashDir of allStashDirs) {
87
+ for (const assetType of ASSET_TYPES) {
88
+ const typeRoot = path.join(currentStashDir, TYPE_DIRS[assetType]);
89
+ try {
90
+ if (!fs.statSync(typeRoot).isDirectory())
91
+ continue;
92
+ }
93
+ catch {
87
94
  continue;
88
95
  }
89
- scannedDirs++;
90
- // Try loading existing .stash.json
91
- let stash = loadStashFile(dirPath);
92
- if (stash) {
93
- const migration = migrateGeneratedSkillMetadata(stash, files, typeRoot);
94
- if (migration.changed) {
95
- stash = migration.stash;
96
- writeStashFile(dirPath, stash);
96
+ const dirGroups = walkStash(typeRoot, assetType);
97
+ for (const { dirPath, files } of dirGroups) {
98
+ if (seenPaths.has(path.resolve(dirPath)))
99
+ continue;
100
+ seenPaths.add(path.resolve(dirPath));
101
+ // Incremental: skip directories that haven't changed
102
+ if (isIncremental) {
103
+ const prevEntries = getEntriesByDir(db, dirPath);
104
+ if (prevEntries.length > 0 && !isDirStale(dirPath, files, prevEntries, builtAtMs)) {
105
+ skippedDirs++;
106
+ continue;
107
+ }
97
108
  }
98
- }
99
- if (!stash) {
100
- // Generate metadata
101
- stash = generateMetadata(dirPath, assetType, files, typeRoot);
102
- // Enhance with LLM if configured
103
- if (config.llm && stash.entries.length > 0) {
104
- stash = await enhanceStashWithLlm(config.llm, stash, dirPath, files);
109
+ scannedDirs++;
110
+ // Delete old entries for this dir (will be re-inserted)
111
+ deleteEntriesByDir(db, dirPath);
112
+ // Try loading existing .stash.json (user metadata overrides)
113
+ let stash = loadStashFile(dirPath);
114
+ if (stash) {
115
+ const migration = migrateGeneratedSkillMetadata(stash, files, typeRoot);
116
+ if (migration.changed) {
117
+ stash = migration.stash;
118
+ writeStashFile(dirPath, stash);
119
+ }
105
120
  }
106
- if (stash.entries.length > 0) {
107
- writeStashFile(dirPath, stash);
108
- generatedCount += stash.entries.length;
121
+ if (!stash) {
122
+ // Generate metadata heuristically
123
+ stash = generateMetadata(dirPath, assetType, files, typeRoot);
124
+ if (stash.entries.length > 0) {
125
+ writeStashFile(dirPath, stash);
126
+ generatedCount += stash.entries.length;
127
+ }
109
128
  }
110
- }
111
- if (stash) {
112
- for (const entry of stash.entries) {
113
- const entryPath = entry.entry
114
- ? path.join(dirPath, entry.entry)
115
- : files[0] || dirPath;
116
- allEntries.push({ entry, path: entryPath, dirPath });
129
+ if (stash) {
130
+ for (const entry of stash.entries) {
131
+ const entryPath = entry.entry
132
+ ? path.join(dirPath, entry.entry)
133
+ : files[0] || dirPath;
134
+ const entryKey = `${currentStashDir}:${entry.type}:${entry.name}`;
135
+ const searchText = buildSearchText(entry);
136
+ upsertEntry(db, entryKey, dirPath, entryPath, currentStashDir, entry, searchText);
137
+ }
138
+ // Collect dirs needing LLM enhancement during the first walk
139
+ if (stash.entries.some((e) => e.generated)) {
140
+ dirsNeedingLlm.push({ dirPath, files, assetType, currentStashDir });
141
+ }
117
142
  }
118
143
  }
119
144
  }
120
145
  }
121
- }
122
- const tWalkEnd = Date.now();
123
- // Build TF-IDF index
124
- const adapter = new TfIdfAdapter();
125
- const scoredEntries = allEntries.map((ie) => ({
126
- id: `${ie.entry.type}:${ie.entry.name}`,
127
- text: buildSearchText(ie.entry),
128
- entry: ie.entry,
129
- path: ie.path,
130
- }));
131
- adapter.buildIndex(scoredEntries);
132
- const tTfidfEnd = Date.now();
133
- // Generate embeddings if semantic search is enabled
134
- let hasEmbeddings = false;
135
- if (config.semanticSearch) {
136
- try {
137
- const { embed } = await import("./embedder.js");
138
- for (const ie of allEntries) {
139
- if (!ie.embedding) {
140
- const text = buildSearchText(ie.entry);
141
- ie.embedding = await embed(text, config.embedding);
142
- }
143
- }
144
- hasEmbeddings = true;
146
+ });
147
+ insertTransaction();
148
+ return { scannedDirs, skippedDirs, generatedCount, dirsNeedingLlm };
149
+ }
150
+ async function enhanceDirsWithLlm(db, config, dirsNeedingLlm) {
151
+ if (!config.llm || dirsNeedingLlm.length === 0)
152
+ return;
153
+ for (const { dirPath, files, currentStashDir } of dirsNeedingLlm) {
154
+ let stash = loadStashFile(dirPath);
155
+ if (!stash)
156
+ continue;
157
+ stash = await enhanceStashWithLlm(config.llm, stash, dirPath, files);
158
+ writeStashFile(dirPath, stash);
159
+ // Re-upsert enhanced entries
160
+ for (const entry of stash.entries) {
161
+ const entryPath = entry.entry ? path.join(dirPath, entry.entry) : files[0] || dirPath;
162
+ const entryKey = `${currentStashDir}:${entry.type}:${entry.name}`;
163
+ const searchText = buildSearchText(entry);
164
+ upsertEntry(db, entryKey, dirPath, entryPath, currentStashDir, entry, searchText);
145
165
  }
146
- catch {
147
- // Embedding provider not available, continue without embeddings
166
+ }
167
+ }
168
+ async function generateEmbeddingsForDb(db, config) {
169
+ if (!config.semanticSearch || !isVecAvailable())
170
+ return false;
171
+ try {
172
+ const { embed } = await import("./embedder.js");
173
+ const allEntries = getAllEntriesForEmbedding(db);
174
+ for (const { id, searchText } of allEntries) {
175
+ const embedding = await embed(searchText, config.embedding);
176
+ upsertEmbedding(db, id, embedding);
148
177
  }
178
+ return true;
149
179
  }
150
- const tEmbedEnd = Date.now();
151
- // Persist index
152
- const indexPath = getIndexPath();
153
- const indexDir = path.dirname(indexPath);
154
- if (!fs.existsSync(indexDir)) {
155
- fs.mkdirSync(indexDir, { recursive: true });
180
+ catch (error) {
181
+ console.warn("Embedding generation failed, continuing without:", error instanceof Error ? error.message : String(error));
182
+ return false;
156
183
  }
157
- const index = {
158
- version: INDEX_VERSION,
159
- builtAt: new Date().toISOString(),
160
- stashDir,
161
- stashDirs: allStashDirs,
162
- entries: allEntries,
163
- tfidf: adapter.serialize(),
164
- hasEmbeddings,
165
- };
166
- fs.writeFileSync(indexPath, JSON.stringify(index) + "\n", "utf8");
167
- const tEnd = Date.now();
168
- return {
169
- stashDir,
170
- totalEntries: allEntries.length,
171
- generatedMetadata: generatedCount,
172
- indexPath,
173
- mode: isIncremental ? "incremental" : "full",
174
- directoriesScanned: scannedDirs,
175
- directoriesSkipped: skippedDirs,
176
- timing: {
177
- totalMs: tEnd - t0,
178
- walkMs: tWalkEnd - tWalkStart, // includes metadata generation (interleaved)
179
- embedMs: tEmbedEnd - tTfidfEnd,
180
- tfidfMs: tTfidfEnd - tWalkEnd,
181
- },
182
- };
183
184
  }
184
185
  // ── Helpers ─────────────────────────────────────────────────────────────────
186
+ function getAllEntriesForEmbedding(db) {
187
+ return db
188
+ .prepare(`
189
+ 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)
191
+ `)
192
+ .all();
193
+ }
185
194
  function isDirStale(dirPath, currentFiles, previousEntries, builtAtMs) {
186
195
  // Check if file set changed (additions or deletions)
187
196
  const prevFileNames = new Set(previousEntries
@@ -244,7 +253,6 @@ async function enhanceStashWithLlm(llmConfig, stash, dirPath, files) {
244
253
  const enhanced = [];
245
254
  for (const entry of stash.entries) {
246
255
  try {
247
- // Find the file matching this entry for content context
248
256
  const entryFile = entry.entry
249
257
  ? files.find((f) => path.basename(f) === entry.entry) ?? files[0]
250
258
  : files[0];
@@ -266,7 +274,6 @@ async function enhanceStashWithLlm(llmConfig, stash, dirPath, files) {
266
274
  enhanced.push(updated);
267
275
  }
268
276
  catch {
269
- // LLM enhancement failed for this entry, keep original
270
277
  enhanced.push(entry);
271
278
  }
272
279
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Agentikit initialization logic.
3
3
  *
4
- * Creates the stash directory structure, sets the AGENTIKIT_STASH_DIR
4
+ * Creates the working stash directory structure, sets the AKM_STASH_DIR
5
5
  * environment variable, and ensures ripgrep is available.
6
6
  */
7
7
  export interface InitResponse {
@@ -16,4 +16,4 @@ export interface InitResponse {
16
16
  version: string;
17
17
  };
18
18
  }
19
- export declare function agentikitInit(): InitResponse;
19
+ export declare function agentikitInit(): Promise<InitResponse>;
package/dist/src/init.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Agentikit initialization logic.
3
3
  *
4
- * Creates the stash directory structure, sets the AGENTIKIT_STASH_DIR
4
+ * Creates the working stash directory structure, sets the AKM_STASH_DIR
5
5
  * environment variable, and ensures ripgrep is available.
6
6
  */
7
7
  import { spawnSync } from "node:child_process";
@@ -10,7 +10,7 @@ import path from "node:path";
10
10
  import { IS_WINDOWS, TYPE_DIRS } from "./common";
11
11
  import { ensureRg } from "./ripgrep-install";
12
12
  import { getConfigPath, saveConfig, DEFAULT_CONFIG } from "./config";
13
- export function agentikitInit() {
13
+ export async function agentikitInit() {
14
14
  let stashDir;
15
15
  if (IS_WINDOWS) {
16
16
  const userProfile = process.env.USERPROFILE?.trim();
@@ -41,7 +41,7 @@ export function agentikitInit() {
41
41
  let envSet = false;
42
42
  let profileUpdated;
43
43
  if (IS_WINDOWS) {
44
- const result = spawnSync("setx", ["AGENTIKIT_STASH_DIR", stashDir], {
44
+ const result = spawnSync("setx", ["AKM_STASH_DIR", stashDir], {
45
45
  encoding: "utf8",
46
46
  timeout: 10_000,
47
47
  });
@@ -60,20 +60,32 @@ export function agentikitInit() {
60
60
  else {
61
61
  profile = path.join(homeDir, ".profile");
62
62
  }
63
- const exportLine = `export AGENTIKIT_STASH_DIR="${stashDir}"`;
63
+ const exportLine = `export AKM_STASH_DIR="${stashDir}"`;
64
64
  const existing = fs.existsSync(profile) ? fs.readFileSync(profile, "utf8") : "";
65
- if (!existing.includes("AGENTIKIT_STASH_DIR")) {
66
- fs.appendFileSync(profile, `\n# Agentikit stash directory\n${exportLine}\n`);
65
+ if (!existing.includes("AKM_STASH_DIR")) {
66
+ const updated = existing + `\n# Agentikit working stash directory\n${exportLine}\n`;
67
+ const tmpPath = profile + `.tmp.${process.pid}`;
68
+ try {
69
+ fs.writeFileSync(tmpPath, updated, "utf8");
70
+ fs.renameSync(tmpPath, profile);
71
+ }
72
+ catch (err) {
73
+ try {
74
+ fs.unlinkSync(tmpPath);
75
+ }
76
+ catch { /* ignore */ }
77
+ throw err;
78
+ }
67
79
  envSet = true;
68
80
  profileUpdated = profile;
69
81
  }
70
82
  }
71
83
  // Create default config.json if it doesn't exist
72
- const configPath = getConfigPath(stashDir);
84
+ const configPath = getConfigPath();
73
85
  if (!fs.existsSync(configPath)) {
74
- saveConfig(DEFAULT_CONFIG, stashDir);
86
+ saveConfig(DEFAULT_CONFIG);
75
87
  }
76
- process.env.AGENTIKIT_STASH_DIR = stashDir;
88
+ process.env.AKM_STASH_DIR = stashDir;
77
89
  // Ensure ripgrep is available (install to stash/bin if needed)
78
90
  let ripgrep;
79
91
  try {
package/dist/src/llm.js CHANGED
@@ -1,16 +1,17 @@
1
+ import { fetchWithTimeout } from "./common";
1
2
  async function chatCompletion(config, messages) {
2
3
  const headers = { "Content-Type": "application/json" };
3
4
  if (config.apiKey) {
4
5
  headers["Authorization"] = `Bearer ${config.apiKey}`;
5
6
  }
6
- const response = await fetch(config.endpoint, {
7
+ const response = await fetchWithTimeout(config.endpoint, {
7
8
  method: "POST",
8
9
  headers,
9
10
  body: JSON.stringify({
10
11
  model: config.model,
11
12
  messages,
12
- temperature: 0.3,
13
- max_tokens: 512,
13
+ temperature: config.temperature ?? 0.3,
14
+ max_tokens: config.maxTokens ?? 512,
14
15
  }),
15
16
  });
16
17
  if (!response.ok) {
@@ -20,6 +20,7 @@ export interface StashEntry {
20
20
  source?: "package" | "frontmatter" | "comments" | "filename" | "manual" | "llm";
21
21
  aliases?: string[];
22
22
  toc?: TocHeading[];
23
+ usage?: string[];
23
24
  }
24
25
  export interface StashFile {
25
26
  entries: StashEntry[];
@@ -29,7 +30,6 @@ export declare function loadStashFile(dirPath: string): StashFile | null;
29
30
  export declare function writeStashFile(dirPath: string, stash: StashFile): void;
30
31
  export declare function validateStashEntry(entry: unknown): StashEntry | null;
31
32
  export declare function generateMetadata(dirPath: string, assetType: AgentikitAssetType, files: string[], typeRoot?: string): StashFile;
32
- export declare function generateIntents(description: string, tags: string[], name: string): string[];
33
33
  export declare function extractDescriptionFromComments(filePath: string): string | null;
34
34
  export declare function extractFrontmatterDescription(filePath: string): string | null;
35
35
  export declare function extractPackageMetadata(dirPath: string): {
@@ -1,9 +1,9 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { isAssetType } from "./common";
4
- import { SCRIPT_EXTENSIONS, isRelevantAssetFile, deriveCanonicalAssetName } from "./asset-spec";
4
+ import { isRelevantAssetFile, deriveCanonicalAssetName } from "./asset-spec";
5
5
  import { parseFrontmatter, toStringOrUndefined } from "./frontmatter";
6
- import { parseMarkdownToc } from "./markdown";
6
+ import { tryGetHandler } from "./asset-type-handler";
7
7
  // ── Load / Write ────────────────────────────────────────────────────────────
8
8
  const STASH_FILENAME = ".stash.json";
9
9
  export function stashFilePath(dirPath) {
@@ -94,8 +94,24 @@ export function validateStashEntry(entry) {
94
94
  if (validated.length > 0)
95
95
  result.toc = validated;
96
96
  }
97
+ const usage = normalizeNonEmptyStringList(e.usage);
98
+ if (usage)
99
+ result.usage = usage;
97
100
  return result;
98
101
  }
102
+ function normalizeNonEmptyStringList(value) {
103
+ if (typeof value === "string") {
104
+ const trimmed = value.trim();
105
+ return trimmed ? [trimmed] : undefined;
106
+ }
107
+ if (!Array.isArray(value))
108
+ return undefined;
109
+ const filtered = value
110
+ .filter((item) => typeof item === "string")
111
+ .map((item) => item.trim())
112
+ .filter((item) => item.length > 0);
113
+ return filtered.length > 0 ? filtered : undefined;
114
+ }
99
115
  // ── Metadata Generation ─────────────────────────────────────────────────────
100
116
  export function generateMetadata(dirPath, assetType, files, typeRoot = dirPath) {
101
117
  const entries = [];
@@ -137,26 +153,10 @@ export function generateMetadata(dirPath, assetType, files, typeRoot = dirPath)
137
153
  entry.confidence = 0.9;
138
154
  }
139
155
  }
140
- // Knowledge entries: generate TOC from headings
141
- if (assetType === "knowledge") {
142
- try {
143
- const mdContent = fs.readFileSync(file, "utf8");
144
- const toc = parseMarkdownToc(mdContent);
145
- if (toc.headings.length > 0)
146
- entry.toc = toc.headings;
147
- }
148
- catch {
149
- // Non-fatal: skip TOC if file can't be read
150
- }
151
- }
152
- // Priority 3: Code comments (for script files)
153
- if (SCRIPT_EXTENSIONS.has(ext) && ext !== ".md") {
154
- const commentDesc = extractDescriptionFromComments(file);
155
- if (commentDesc && !entry.description) {
156
- entry.description = commentDesc;
157
- entry.source = "comments";
158
- entry.confidence = 0.7;
159
- }
156
+ // Type-specific metadata extraction (e.g. TOC for knowledge, comments for tools/scripts)
157
+ const handler = tryGetHandler(assetType);
158
+ if (handler?.extractTypeMetadata) {
159
+ handler.extractTypeMetadata(entry, file, ext);
160
160
  }
161
161
  // Priority 4: Filename heuristics (fallback)
162
162
  if (!entry.description) {
@@ -198,48 +198,6 @@ function buildAliases(name, tags) {
198
198
  aliases.add(tags.join(" "));
199
199
  return Array.from(aliases);
200
200
  }
201
- // ── Intent Generation ────────────────────────────────────────────────────────
202
- export function generateIntents(description, tags, name) {
203
- const intents = new Set();
204
- // Split name on separators to extract tokens and potential verb
205
- const nameTokens = name
206
- .replace(/[-_]+/g, " ")
207
- .replace(/([a-z])([A-Z])/g, "$1 $2")
208
- .toLowerCase()
209
- .trim()
210
- .split(/\s+/)
211
- .filter((t) => t.length > 1);
212
- // Intent from name as phrase (e.g. "summarize diff")
213
- const namePhrase = nameTokens.join(" ");
214
- if (namePhrase.length > 2)
215
- intents.add(namePhrase);
216
- // Intent from description (lowercased)
217
- const desc = description.toLowerCase().trim();
218
- if (desc.length > 2)
219
- intents.add(desc);
220
- // Combine first name token (potential verb) with tags
221
- // e.g. name "summarize-diff", tags ["git"] → "summarize git diff"
222
- if (nameTokens.length >= 1 && tags.length > 0) {
223
- const verb = nameTokens[0];
224
- const rest = nameTokens.slice(1).join(" ");
225
- for (const tag of tags) {
226
- const tagLower = tag.toLowerCase();
227
- // verb + tag + rest (e.g. "summarize git diff")
228
- const parts = [verb, tagLower, rest].filter((p) => p.length > 0);
229
- const phrase = parts.join(" ");
230
- if (phrase !== namePhrase && phrase.length > 2)
231
- intents.add(phrase);
232
- }
233
- }
234
- // Join tag pairs (e.g. ["git", "diff"] → "git diff")
235
- if (tags.length >= 2) {
236
- const tagPhrase = tags.map((t) => t.toLowerCase()).join(" ");
237
- if (tagPhrase.length > 2)
238
- intents.add(tagPhrase);
239
- }
240
- // Cap at 8 intents
241
- return Array.from(intents).slice(0, 8);
242
- }
243
201
  export function extractDescriptionFromComments(filePath) {
244
202
  let content;
245
203
  try {
@@ -0,0 +1,19 @@
1
+ import type { StashSource } from "./stash-source";
2
+ /**
3
+ * Given an origin string (from an AssetRef) and the full list of stash
4
+ * sources, return the subset of sources to search.
5
+ *
6
+ * Resolution order:
7
+ * 1. undefined → all sources (working → mounted → installed)
8
+ * 2. "local" → working stash only
9
+ * 3. exact match → installed source whose registryId matches verbatim
10
+ * 4. parsed match → parse origin as a registry ref, match by parsed ID
11
+ * 5. path match → mounted source whose path matches
12
+ * 6. empty → indicates a remote/uninstalled origin (caller decides)
13
+ */
14
+ export declare function resolveSourcesForOrigin(origin: string | undefined, allSources: StashSource[]): StashSource[];
15
+ /**
16
+ * Check whether an origin refers to something that could be fetched remotely
17
+ * (i.e. it looks like a registry ref but isn't installed locally).
18
+ */
19
+ export declare function isRemoteOrigin(origin: string, allSources: StashSource[]): boolean;