agentikit 0.0.9 → 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 (107) hide show
  1. package/README.md +129 -214
  2. package/dist/index.d.ts +8 -2
  3. package/dist/index.js +4 -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 +201 -75
  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 +19 -6
  14. package/dist/src/config.js +139 -29
  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 +0 -1
  42. package/dist/src/metadata.js +6 -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 +2 -2
  46. package/dist/src/registry-install.js +142 -35
  47. package/dist/src/registry-resolve.js +90 -22
  48. package/dist/src/registry-search.d.ts +22 -0
  49. package/dist/src/registry-search.js +231 -97
  50. package/dist/src/registry-types.d.ts +9 -2
  51. package/dist/src/stash-add.js +4 -4
  52. package/dist/src/stash-clone.d.ts +22 -0
  53. package/dist/src/stash-clone.js +83 -0
  54. package/dist/src/stash-ref.d.ts +27 -3
  55. package/dist/src/stash-ref.js +63 -24
  56. package/dist/src/stash-registry.js +12 -12
  57. package/dist/src/stash-resolve.js +3 -0
  58. package/dist/src/stash-search.js +168 -164
  59. package/dist/src/stash-show.d.ts +1 -1
  60. package/dist/src/stash-show.js +28 -96
  61. package/dist/src/stash-source.d.ts +24 -0
  62. package/dist/src/stash-source.js +81 -0
  63. package/dist/src/stash-types.d.ts +14 -4
  64. package/dist/src/stash.d.ts +6 -0
  65. package/dist/src/stash.js +3 -0
  66. package/dist/src/tool-runner.d.ts +1 -1
  67. package/dist/src/tool-runner.js +18 -5
  68. package/package.json +7 -2
  69. package/src/asset-spec.ts +20 -4
  70. package/src/asset-type-handler.ts +77 -0
  71. package/src/cli.ts +213 -82
  72. package/src/common.ts +23 -5
  73. package/src/config-cli.ts +499 -0
  74. package/src/config.ts +160 -38
  75. package/src/db.ts +411 -0
  76. package/src/embedder.ts +22 -11
  77. package/src/github.ts +21 -0
  78. package/src/handlers/agent-handler.ts +32 -0
  79. package/src/handlers/command-handler.ts +29 -0
  80. package/src/handlers/index.ts +25 -0
  81. package/src/handlers/knowledge-handler.ts +62 -0
  82. package/src/handlers/markdown-helpers.ts +19 -0
  83. package/src/handlers/script-handler.ts +92 -0
  84. package/src/handlers/skill-handler.ts +37 -0
  85. package/src/handlers/tool-handler.ts +71 -0
  86. package/src/indexer.ts +208 -187
  87. package/src/init.ts +17 -9
  88. package/src/llm.ts +4 -3
  89. package/src/metadata.ts +5 -65
  90. package/src/origin-resolve.ts +67 -0
  91. package/src/registry-install.ts +158 -42
  92. package/src/registry-resolve.ts +92 -23
  93. package/src/registry-search.ts +288 -98
  94. package/src/registry-types.ts +10 -2
  95. package/src/stash-add.ts +14 -17
  96. package/src/stash-clone.ts +127 -0
  97. package/src/stash-ref.ts +84 -26
  98. package/src/stash-registry.ts +12 -12
  99. package/src/stash-resolve.ts +3 -0
  100. package/src/stash-search.ts +202 -184
  101. package/src/stash-show.ts +33 -90
  102. package/src/stash-source.ts +103 -0
  103. package/src/stash-types.ts +14 -4
  104. package/src/stash.ts +8 -0
  105. package/src/tool-runner.ts +18 -5
  106. package/dist/src/similarity.d.ts +0 -34
  107. 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) {
@@ -30,7 +30,6 @@ export declare function loadStashFile(dirPath: string): StashFile | null;
30
30
  export declare function writeStashFile(dirPath: string, stash: StashFile): void;
31
31
  export declare function validateStashEntry(entry: unknown): StashEntry | null;
32
32
  export declare function generateMetadata(dirPath: string, assetType: AgentikitAssetType, files: string[], typeRoot?: string): StashFile;
33
- export declare function generateIntents(description: string, tags: string[], name: string): string[];
34
33
  export declare function extractDescriptionFromComments(filePath: string): string | null;
35
34
  export declare function extractFrontmatterDescription(filePath: string): string | null;
36
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) {
@@ -153,26 +153,10 @@ export function generateMetadata(dirPath, assetType, files, typeRoot = dirPath)
153
153
  entry.confidence = 0.9;
154
154
  }
155
155
  }
156
- // Knowledge entries: generate TOC from headings
157
- if (assetType === "knowledge") {
158
- try {
159
- const mdContent = fs.readFileSync(file, "utf8");
160
- const toc = parseMarkdownToc(mdContent);
161
- if (toc.headings.length > 0)
162
- entry.toc = toc.headings;
163
- }
164
- catch {
165
- // Non-fatal: skip TOC if file can't be read
166
- }
167
- }
168
- // Priority 3: Code comments (for script files)
169
- if (SCRIPT_EXTENSIONS.has(ext) && ext !== ".md") {
170
- const commentDesc = extractDescriptionFromComments(file);
171
- if (commentDesc && !entry.description) {
172
- entry.description = commentDesc;
173
- entry.source = "comments";
174
- entry.confidence = 0.7;
175
- }
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);
176
160
  }
177
161
  // Priority 4: Filename heuristics (fallback)
178
162
  if (!entry.description) {
@@ -214,48 +198,6 @@ function buildAliases(name, tags) {
214
198
  aliases.add(tags.join(" "));
215
199
  return Array.from(aliases);
216
200
  }
217
- // ── Intent Generation ────────────────────────────────────────────────────────
218
- export function generateIntents(description, tags, name) {
219
- const intents = new Set();
220
- // Split name on separators to extract tokens and potential verb
221
- const nameTokens = name
222
- .replace(/[-_]+/g, " ")
223
- .replace(/([a-z])([A-Z])/g, "$1 $2")
224
- .toLowerCase()
225
- .trim()
226
- .split(/\s+/)
227
- .filter((t) => t.length > 1);
228
- // Intent from name as phrase (e.g. "summarize diff")
229
- const namePhrase = nameTokens.join(" ");
230
- if (namePhrase.length > 2)
231
- intents.add(namePhrase);
232
- // Intent from description (lowercased)
233
- const desc = description.toLowerCase().trim();
234
- if (desc.length > 2)
235
- intents.add(desc);
236
- // Combine first name token (potential verb) with tags
237
- // e.g. name "summarize-diff", tags ["git"] → "summarize git diff"
238
- if (nameTokens.length >= 1 && tags.length > 0) {
239
- const verb = nameTokens[0];
240
- const rest = nameTokens.slice(1).join(" ");
241
- for (const tag of tags) {
242
- const tagLower = tag.toLowerCase();
243
- // verb + tag + rest (e.g. "summarize git diff")
244
- const parts = [verb, tagLower, rest].filter((p) => p.length > 0);
245
- const phrase = parts.join(" ");
246
- if (phrase !== namePhrase && phrase.length > 2)
247
- intents.add(phrase);
248
- }
249
- }
250
- // Join tag pairs (e.g. ["git", "diff"] → "git diff")
251
- if (tags.length >= 2) {
252
- const tagPhrase = tags.map((t) => t.toLowerCase()).join(" ");
253
- if (tagPhrase.length > 2)
254
- intents.add(tagPhrase);
255
- }
256
- // Cap at 8 intents
257
- return Array.from(intents).slice(0, 8);
258
- }
259
201
  export function extractDescriptionFromComments(filePath) {
260
202
  let content;
261
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;
@@ -0,0 +1,53 @@
1
+ import path from "node:path";
2
+ import { parseRegistryRef } from "./registry-resolve";
3
+ /**
4
+ * Given an origin string (from an AssetRef) and the full list of stash
5
+ * sources, return the subset of sources to search.
6
+ *
7
+ * Resolution order:
8
+ * 1. undefined → all sources (working → mounted → installed)
9
+ * 2. "local" → working stash only
10
+ * 3. exact match → installed source whose registryId matches verbatim
11
+ * 4. parsed match → parse origin as a registry ref, match by parsed ID
12
+ * 5. path match → mounted source whose path matches
13
+ * 6. empty → indicates a remote/uninstalled origin (caller decides)
14
+ */
15
+ export function resolveSourcesForOrigin(origin, allSources) {
16
+ if (!origin)
17
+ return allSources;
18
+ if (origin === "local") {
19
+ return allSources.filter((s) => s.kind === "working");
20
+ }
21
+ // Exact registryId match (e.g. origin is "npm:@scope/pkg")
22
+ const byExactId = allSources.filter((s) => s.kind === "installed" && s.registryId === origin);
23
+ if (byExactId.length > 0)
24
+ return byExactId;
25
+ // Parse origin as a registry ref and match by parsed ID.
26
+ // Allows shorthand: "owner/repo" matches "github:owner/repo",
27
+ // "@scope/pkg" matches "npm:@scope/pkg".
28
+ try {
29
+ const parsed = parseRegistryRef(origin);
30
+ const byParsedId = allSources.filter((s) => s.kind === "installed" && s.registryId === parsed.id);
31
+ if (byParsedId.length > 0)
32
+ return byParsedId;
33
+ }
34
+ catch {
35
+ // Not a valid registry ref — continue to path matching
36
+ }
37
+ // Mounted stash by resolved path
38
+ const resolvedOrigin = path.resolve(origin);
39
+ const byPath = allSources.filter((s) => s.kind === "mounted" && path.resolve(s.path) === resolvedOrigin);
40
+ if (byPath.length > 0)
41
+ return byPath;
42
+ // No match — origin may be remote/uninstalled
43
+ return [];
44
+ }
45
+ /**
46
+ * Check whether an origin refers to something that could be fetched remotely
47
+ * (i.e. it looks like a registry ref but isn't installed locally).
48
+ */
49
+ export function isRemoteOrigin(origin, allSources) {
50
+ if (origin === "local")
51
+ return false;
52
+ return resolveSourcesForOrigin(origin, allSources).length === 0;
53
+ }
@@ -5,7 +5,7 @@ export interface InstallRegistryRefOptions {
5
5
  now?: Date;
6
6
  }
7
7
  export declare function installRegistryRef(ref: string, options?: InstallRegistryRefOptions): Promise<RegistryInstallResult>;
8
- export declare function upsertInstalledRegistryEntry(entry: RegistryInstalledEntry, stashDir?: string): AgentikitConfig;
9
- export declare function removeInstalledRegistryEntry(id: string, stashDir?: string): AgentikitConfig;
8
+ export declare function upsertInstalledRegistryEntry(entry: RegistryInstalledEntry): AgentikitConfig;
9
+ export declare function removeInstalledRegistryEntry(id: string): AgentikitConfig;
10
10
  export declare function getRegistryCacheRootDir(): string;
11
11
  export declare function detectStashRoot(extractedDir: string): string;