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.
- package/README.md +129 -214
- package/dist/index.d.ts +8 -2
- package/dist/index.js +4 -1
- package/dist/src/asset-spec.d.ts +2 -0
- package/dist/src/asset-spec.js +22 -3
- package/dist/src/asset-type-handler.d.ts +27 -0
- package/dist/src/asset-type-handler.js +33 -0
- package/dist/src/cli.js +201 -75
- package/dist/src/common.d.ts +6 -1
- package/dist/src/common.js +18 -4
- package/dist/src/config-cli.d.ts +9 -0
- package/dist/src/config-cli.js +473 -0
- package/dist/src/config.d.ts +19 -6
- package/dist/src/config.js +139 -29
- package/dist/src/db.d.ts +46 -0
- package/dist/src/db.js +299 -0
- package/dist/src/embedder.js +12 -7
- package/dist/src/github.d.ts +4 -0
- package/dist/src/github.js +19 -0
- package/dist/src/handlers/agent-handler.d.ts +2 -0
- package/dist/src/handlers/agent-handler.js +26 -0
- package/dist/src/handlers/command-handler.d.ts +2 -0
- package/dist/src/handlers/command-handler.js +23 -0
- package/dist/src/handlers/index.d.ts +6 -0
- package/dist/src/handlers/index.js +23 -0
- package/dist/src/handlers/knowledge-handler.d.ts +2 -0
- package/dist/src/handlers/knowledge-handler.js +56 -0
- package/dist/src/handlers/markdown-helpers.d.ts +7 -0
- package/dist/src/handlers/markdown-helpers.js +15 -0
- package/dist/src/handlers/script-handler.d.ts +2 -0
- package/dist/src/handlers/script-handler.js +78 -0
- package/dist/src/handlers/skill-handler.d.ts +2 -0
- package/dist/src/handlers/skill-handler.js +30 -0
- package/dist/src/handlers/tool-handler.d.ts +2 -0
- package/dist/src/handlers/tool-handler.js +58 -0
- package/dist/src/indexer.d.ts +1 -23
- package/dist/src/indexer.js +162 -155
- package/dist/src/init.d.ts +2 -2
- package/dist/src/init.js +21 -9
- package/dist/src/llm.js +4 -3
- package/dist/src/metadata.d.ts +0 -1
- package/dist/src/metadata.js +6 -64
- package/dist/src/origin-resolve.d.ts +19 -0
- package/dist/src/origin-resolve.js +53 -0
- package/dist/src/registry-install.d.ts +2 -2
- package/dist/src/registry-install.js +142 -35
- package/dist/src/registry-resolve.js +90 -22
- package/dist/src/registry-search.d.ts +22 -0
- package/dist/src/registry-search.js +231 -97
- package/dist/src/registry-types.d.ts +9 -2
- package/dist/src/stash-add.js +4 -4
- package/dist/src/stash-clone.d.ts +22 -0
- package/dist/src/stash-clone.js +83 -0
- package/dist/src/stash-ref.d.ts +27 -3
- package/dist/src/stash-ref.js +63 -24
- package/dist/src/stash-registry.js +12 -12
- package/dist/src/stash-resolve.js +3 -0
- package/dist/src/stash-search.js +168 -164
- package/dist/src/stash-show.d.ts +1 -1
- package/dist/src/stash-show.js +28 -96
- package/dist/src/stash-source.d.ts +24 -0
- package/dist/src/stash-source.js +81 -0
- package/dist/src/stash-types.d.ts +14 -4
- package/dist/src/stash.d.ts +6 -0
- package/dist/src/stash.js +3 -0
- package/dist/src/tool-runner.d.ts +1 -1
- package/dist/src/tool-runner.js +18 -5
- package/package.json +7 -2
- package/src/asset-spec.ts +20 -4
- package/src/asset-type-handler.ts +77 -0
- package/src/cli.ts +213 -82
- package/src/common.ts +23 -5
- package/src/config-cli.ts +499 -0
- package/src/config.ts +160 -38
- package/src/db.ts +411 -0
- package/src/embedder.ts +22 -11
- package/src/github.ts +21 -0
- package/src/handlers/agent-handler.ts +32 -0
- package/src/handlers/command-handler.ts +29 -0
- package/src/handlers/index.ts +25 -0
- package/src/handlers/knowledge-handler.ts +62 -0
- package/src/handlers/markdown-helpers.ts +19 -0
- package/src/handlers/script-handler.ts +92 -0
- package/src/handlers/skill-handler.ts +37 -0
- package/src/handlers/tool-handler.ts +71 -0
- package/src/indexer.ts +208 -187
- package/src/init.ts +17 -9
- package/src/llm.ts +4 -3
- package/src/metadata.ts +5 -65
- package/src/origin-resolve.ts +67 -0
- package/src/registry-install.ts +158 -42
- package/src/registry-resolve.ts +92 -23
- package/src/registry-search.ts +288 -98
- package/src/registry-types.ts +10 -2
- package/src/stash-add.ts +14 -17
- package/src/stash-clone.ts +127 -0
- package/src/stash-ref.ts +84 -26
- package/src/stash-registry.ts +12 -12
- package/src/stash-resolve.ts +3 -0
- package/src/stash-search.ts +202 -184
- package/src/stash-show.ts +33 -90
- package/src/stash-source.ts +103 -0
- package/src/stash-types.ts +14 -4
- package/src/stash.ts +8 -0
- package/src/tool-runner.ts +18 -5
- package/dist/src/similarity.d.ts +0 -34
- package/src/similarity.ts +0 -271
package/src/indexer.ts
CHANGED
|
@@ -9,33 +9,27 @@ import {
|
|
|
9
9
|
writeStashFile,
|
|
10
10
|
generateMetadata,
|
|
11
11
|
} from "./metadata"
|
|
12
|
-
import { TfIdfAdapter, type ScoredEntry, type SerializedTfIdf } from "./similarity"
|
|
13
12
|
import { walkStash } from "./walker"
|
|
14
|
-
import type { EmbeddingVector } from "./embedder"
|
|
15
13
|
import type { LlmConnectionConfig } from "./config"
|
|
14
|
+
import {
|
|
15
|
+
openDatabase,
|
|
16
|
+
closeDatabase,
|
|
17
|
+
getDbPath,
|
|
18
|
+
getMeta,
|
|
19
|
+
setMeta,
|
|
20
|
+
upsertEntry,
|
|
21
|
+
deleteEntriesByDir,
|
|
22
|
+
rebuildFts,
|
|
23
|
+
upsertEmbedding,
|
|
24
|
+
getEntriesByDir,
|
|
25
|
+
getEntryCount,
|
|
26
|
+
isVecAvailable,
|
|
27
|
+
DB_VERSION,
|
|
28
|
+
type DbIndexedEntry,
|
|
29
|
+
} from "./db"
|
|
16
30
|
|
|
17
31
|
// ── Types ───────────────────────────────────────────────────────────────────
|
|
18
32
|
|
|
19
|
-
export interface IndexedEntry {
|
|
20
|
-
entry: StashEntry
|
|
21
|
-
path: string
|
|
22
|
-
dirPath: string
|
|
23
|
-
embedding?: EmbeddingVector
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface SearchIndex {
|
|
27
|
-
version: number
|
|
28
|
-
builtAt: string
|
|
29
|
-
stashDir: string
|
|
30
|
-
/** All stash directories that were indexed (primary + additional) */
|
|
31
|
-
stashDirs?: string[]
|
|
32
|
-
entries: IndexedEntry[]
|
|
33
|
-
/** Serialized TF-IDF state (term frequencies, idf values) */
|
|
34
|
-
tfidf?: SerializedTfIdf
|
|
35
|
-
/** Whether embeddings are included in entries */
|
|
36
|
-
hasEmbeddings?: boolean
|
|
37
|
-
}
|
|
38
|
-
|
|
39
33
|
export interface IndexResponse {
|
|
40
34
|
stashDir: string
|
|
41
35
|
totalEntries: number
|
|
@@ -45,211 +39,240 @@ export interface IndexResponse {
|
|
|
45
39
|
directoriesScanned: number
|
|
46
40
|
directoriesSkipped: number
|
|
47
41
|
/** Timing counters in milliseconds */
|
|
48
|
-
timing?: { totalMs: number; walkMs: number; embedMs: number;
|
|
42
|
+
timing?: { totalMs: number; walkMs: number; embedMs: number; ftsMs: number }
|
|
49
43
|
}
|
|
50
44
|
|
|
51
|
-
// ──
|
|
45
|
+
// ── Indexer ──────────────────────────────────────────────────────────────────
|
|
52
46
|
|
|
53
|
-
|
|
47
|
+
export async function agentikitIndex(options?: { stashDir?: string; full?: boolean }): Promise<IndexResponse> {
|
|
48
|
+
const stashDir = options?.stashDir || resolveStashDir()
|
|
54
49
|
|
|
55
|
-
//
|
|
50
|
+
// Load config and resolve all stash sources
|
|
51
|
+
const { loadConfig } = await import("./config.js")
|
|
52
|
+
const config = loadConfig()
|
|
53
|
+
const { resolveAllStashDirs } = await import("./stash-source.js")
|
|
54
|
+
const allStashDirs = resolveAllStashDirs(stashDir)
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
const t0 = Date.now()
|
|
57
|
+
|
|
58
|
+
// Open database — pass embedding dimension from config if available
|
|
59
|
+
const dbPath = getDbPath()
|
|
60
|
+
const embeddingDim = config.embedding?.dimension
|
|
61
|
+
const db = openDatabase(dbPath, embeddingDim ? { embeddingDim } : undefined)
|
|
62
62
|
|
|
63
|
-
export function loadSearchIndex(): SearchIndex | null {
|
|
64
|
-
const indexPath = getIndexPath()
|
|
65
|
-
if (!fs.existsSync(indexPath)) return null
|
|
66
63
|
try {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
64
|
+
// Check if we should do incremental
|
|
65
|
+
const prevStashDir = getMeta(db, "stashDir")
|
|
66
|
+
const prevBuiltAt = getMeta(db, "builtAt")
|
|
67
|
+
const isIncremental = !options?.full && prevStashDir === stashDir && !!prevBuiltAt
|
|
68
|
+
const builtAtMs = isIncremental ? new Date(prevBuiltAt!).getTime() : 0
|
|
69
|
+
|
|
70
|
+
if (options?.full || !isIncremental) {
|
|
71
|
+
// Wipe all entries for full rebuild or stashDir change
|
|
72
|
+
db.exec("DELETE FROM entries")
|
|
73
|
+
db.exec("DELETE FROM entries_fts")
|
|
74
|
+
if (isVecAvailable()) {
|
|
75
|
+
try { db.exec("DELETE FROM entries_vec") } catch { /* ignore */ }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
74
78
|
|
|
75
|
-
|
|
79
|
+
const tWalkStart = Date.now()
|
|
76
80
|
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
// Walk stash dirs and index entries
|
|
82
|
+
const { scannedDirs, skippedDirs, generatedCount, dirsNeedingLlm } = indexEntries(db, allStashDirs, stashDir, isIncremental, builtAtMs)
|
|
79
83
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const config = loadConfig(stashDir)
|
|
84
|
+
// Enhance entries with LLM if configured
|
|
85
|
+
await enhanceDirsWithLlm(db, config, dirsNeedingLlm)
|
|
83
86
|
|
|
84
|
-
|
|
85
|
-
for (const d of config.additionalStashDirs) {
|
|
86
|
-
try {
|
|
87
|
-
if (fs.statSync(d).isDirectory() && !allStashDirs.includes(path.resolve(d))) {
|
|
88
|
-
allStashDirs.push(path.resolve(d))
|
|
89
|
-
}
|
|
90
|
-
} catch { /* skip nonexistent dirs */ }
|
|
91
|
-
}
|
|
87
|
+
const tWalkEnd = Date.now()
|
|
92
88
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
89
|
+
// Rebuild FTS after all inserts
|
|
90
|
+
rebuildFts(db)
|
|
91
|
+
const tFtsEnd = Date.now()
|
|
92
|
+
|
|
93
|
+
// Generate embeddings if semantic search is enabled
|
|
94
|
+
const hasEmbeddings = await generateEmbeddingsForDb(db, config)
|
|
95
|
+
|
|
96
|
+
const tEmbedEnd = Date.now()
|
|
97
|
+
|
|
98
|
+
// Update metadata
|
|
99
|
+
setMeta(db, "version", String(DB_VERSION))
|
|
100
|
+
setMeta(db, "builtAt", new Date().toISOString())
|
|
101
|
+
setMeta(db, "stashDir", stashDir)
|
|
102
|
+
setMeta(db, "stashDirs", JSON.stringify(allStashDirs))
|
|
103
|
+
setMeta(db, "hasEmbeddings", hasEmbeddings ? "1" : "0")
|
|
104
|
+
|
|
105
|
+
const totalEntries = getEntryCount(db)
|
|
98
106
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
const tEnd = Date.now()
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
stashDir,
|
|
111
|
+
totalEntries,
|
|
112
|
+
generatedMetadata: generatedCount,
|
|
113
|
+
indexPath: dbPath,
|
|
114
|
+
mode: isIncremental ? "incremental" : "full",
|
|
115
|
+
directoriesScanned: scannedDirs,
|
|
116
|
+
directoriesSkipped: skippedDirs,
|
|
117
|
+
timing: {
|
|
118
|
+
totalMs: tEnd - t0,
|
|
119
|
+
walkMs: tWalkEnd - tWalkStart,
|
|
120
|
+
embedMs: tEmbedEnd - tFtsEnd,
|
|
121
|
+
ftsMs: tFtsEnd - tWalkEnd,
|
|
122
|
+
},
|
|
111
123
|
}
|
|
124
|
+
} finally {
|
|
125
|
+
closeDatabase(db)
|
|
112
126
|
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ── Extracted helpers for agentikitIndex ─────────────────────────────────────
|
|
113
130
|
|
|
131
|
+
function indexEntries(
|
|
132
|
+
db: import("bun:sqlite").Database,
|
|
133
|
+
allStashDirs: string[],
|
|
134
|
+
stashDir: string,
|
|
135
|
+
isIncremental: boolean,
|
|
136
|
+
builtAtMs: number,
|
|
137
|
+
): { scannedDirs: number; skippedDirs: number; generatedCount: number; dirsNeedingLlm: Array<{ dirPath: string; files: string[]; assetType: AgentikitAssetType; currentStashDir: string }> } {
|
|
138
|
+
let scannedDirs = 0
|
|
139
|
+
let skippedDirs = 0
|
|
140
|
+
let generatedCount = 0
|
|
114
141
|
const seenPaths = new Set<string>()
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
for (const currentStashDir of allStashDirs) {
|
|
118
|
-
for (const assetType of ASSET_TYPES as AgentikitAssetType[]) {
|
|
119
|
-
const typeRoot = path.join(currentStashDir, TYPE_DIRS[assetType])
|
|
120
|
-
try {
|
|
121
|
-
if (!fs.statSync(typeRoot).isDirectory()) continue
|
|
122
|
-
} catch { continue }
|
|
123
|
-
|
|
124
|
-
// Group files by their immediate parent directory
|
|
125
|
-
const dirGroups = walkStash(typeRoot, assetType)
|
|
126
|
-
|
|
127
|
-
for (const { dirPath, files } of dirGroups) {
|
|
128
|
-
// Deduplicate by dirPath across stash dirs
|
|
129
|
-
if (seenPaths.has(path.resolve(dirPath))) continue
|
|
130
|
-
seenPaths.add(path.resolve(dirPath))
|
|
131
|
-
|
|
132
|
-
// Incremental: skip directories that haven't changed
|
|
133
|
-
const prevEntries = previousEntriesByDir.get(dirPath)
|
|
134
|
-
if (isIncremental && prevEntries && !isDirStale(dirPath, files, prevEntries, builtAtMs)) {
|
|
135
|
-
allEntries.push(...prevEntries)
|
|
136
|
-
skippedDirs++
|
|
137
|
-
continue
|
|
138
|
-
}
|
|
142
|
+
const dirsNeedingLlm: Array<{ dirPath: string; files: string[]; assetType: AgentikitAssetType; currentStashDir: string }> = []
|
|
139
143
|
|
|
140
|
-
|
|
144
|
+
const insertTransaction = db.transaction(() => {
|
|
145
|
+
for (const currentStashDir of allStashDirs) {
|
|
146
|
+
for (const assetType of ASSET_TYPES as AgentikitAssetType[]) {
|
|
147
|
+
const typeRoot = path.join(currentStashDir, TYPE_DIRS[assetType])
|
|
148
|
+
try {
|
|
149
|
+
if (!fs.statSync(typeRoot).isDirectory()) continue
|
|
150
|
+
} catch { continue }
|
|
151
|
+
|
|
152
|
+
const dirGroups = walkStash(typeRoot, assetType)
|
|
153
|
+
|
|
154
|
+
for (const { dirPath, files } of dirGroups) {
|
|
155
|
+
if (seenPaths.has(path.resolve(dirPath))) continue
|
|
156
|
+
seenPaths.add(path.resolve(dirPath))
|
|
157
|
+
|
|
158
|
+
// Incremental: skip directories that haven't changed
|
|
159
|
+
if (isIncremental) {
|
|
160
|
+
const prevEntries = getEntriesByDir(db, dirPath)
|
|
161
|
+
if (prevEntries.length > 0 && !isDirStale(dirPath, files, prevEntries, builtAtMs)) {
|
|
162
|
+
skippedDirs++
|
|
163
|
+
continue
|
|
164
|
+
}
|
|
165
|
+
}
|
|
141
166
|
|
|
142
|
-
|
|
143
|
-
let stash = loadStashFile(dirPath)
|
|
167
|
+
scannedDirs++
|
|
144
168
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (migration.changed) {
|
|
148
|
-
stash = migration.stash
|
|
149
|
-
writeStashFile(dirPath, stash)
|
|
150
|
-
}
|
|
151
|
-
}
|
|
169
|
+
// Delete old entries for this dir (will be re-inserted)
|
|
170
|
+
deleteEntriesByDir(db, dirPath)
|
|
152
171
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
generatedCount += stash.entries.length
|
|
172
|
+
// Try loading existing .stash.json (user metadata overrides)
|
|
173
|
+
let stash = loadStashFile(dirPath)
|
|
174
|
+
|
|
175
|
+
if (stash) {
|
|
176
|
+
const migration = migrateGeneratedSkillMetadata(stash, files, typeRoot)
|
|
177
|
+
if (migration.changed) {
|
|
178
|
+
stash = migration.stash
|
|
179
|
+
writeStashFile(dirPath, stash)
|
|
180
|
+
}
|
|
163
181
|
}
|
|
164
|
-
}
|
|
165
182
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
183
|
+
if (!stash) {
|
|
184
|
+
// Generate metadata heuristically
|
|
185
|
+
stash = generateMetadata(dirPath, assetType, files, typeRoot)
|
|
186
|
+
if (stash.entries.length > 0) {
|
|
187
|
+
writeStashFile(dirPath, stash)
|
|
188
|
+
generatedCount += stash.entries.length
|
|
189
|
+
}
|
|
172
190
|
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
191
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
try {
|
|
195
|
-
const { embed } = await import("./embedder.js")
|
|
196
|
-
for (const ie of allEntries) {
|
|
197
|
-
if (!ie.embedding) {
|
|
198
|
-
const text = buildSearchText(ie.entry)
|
|
199
|
-
ie.embedding = await embed(text, config.embedding)
|
|
192
|
+
if (stash) {
|
|
193
|
+
for (const entry of stash.entries) {
|
|
194
|
+
const entryPath = entry.entry
|
|
195
|
+
? path.join(dirPath, entry.entry)
|
|
196
|
+
: files[0] || dirPath
|
|
197
|
+
const entryKey = `${currentStashDir}:${entry.type}:${entry.name}`
|
|
198
|
+
const searchText = buildSearchText(entry)
|
|
199
|
+
|
|
200
|
+
upsertEntry(db, entryKey, dirPath, entryPath, currentStashDir, entry, searchText)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Collect dirs needing LLM enhancement during the first walk
|
|
204
|
+
if (stash.entries.some((e) => e.generated)) {
|
|
205
|
+
dirsNeedingLlm.push({ dirPath, files, assetType, currentStashDir })
|
|
206
|
+
}
|
|
207
|
+
}
|
|
200
208
|
}
|
|
201
209
|
}
|
|
202
|
-
hasEmbeddings = true
|
|
203
|
-
} catch {
|
|
204
|
-
// Embedding provider not available, continue without embeddings
|
|
205
210
|
}
|
|
206
|
-
}
|
|
211
|
+
})
|
|
207
212
|
|
|
208
|
-
|
|
213
|
+
insertTransaction()
|
|
209
214
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const indexDir = path.dirname(indexPath)
|
|
213
|
-
if (!fs.existsSync(indexDir)) {
|
|
214
|
-
fs.mkdirSync(indexDir, { recursive: true })
|
|
215
|
-
}
|
|
215
|
+
return { scannedDirs, skippedDirs, generatedCount, dirsNeedingLlm }
|
|
216
|
+
}
|
|
216
217
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
218
|
+
async function enhanceDirsWithLlm(
|
|
219
|
+
db: import("bun:sqlite").Database,
|
|
220
|
+
config: import("./config").AgentikitConfig,
|
|
221
|
+
dirsNeedingLlm: Array<{ dirPath: string; files: string[]; assetType: AgentikitAssetType; currentStashDir: string }>,
|
|
222
|
+
): Promise<void> {
|
|
223
|
+
if (!config.llm || dirsNeedingLlm.length === 0) return
|
|
224
|
+
|
|
225
|
+
for (const { dirPath, files, currentStashDir } of dirsNeedingLlm) {
|
|
226
|
+
let stash = loadStashFile(dirPath)
|
|
227
|
+
if (!stash) continue
|
|
228
|
+
stash = await enhanceStashWithLlm(config.llm, stash, dirPath, files)
|
|
229
|
+
writeStashFile(dirPath, stash)
|
|
230
|
+
|
|
231
|
+
// Re-upsert enhanced entries
|
|
232
|
+
for (const entry of stash.entries) {
|
|
233
|
+
const entryPath = entry.entry ? path.join(dirPath, entry.entry) : files[0] || dirPath
|
|
234
|
+
const entryKey = `${currentStashDir}:${entry.type}:${entry.name}`
|
|
235
|
+
const searchText = buildSearchText(entry)
|
|
236
|
+
upsertEntry(db, entryKey, dirPath, entryPath, currentStashDir, entry, searchText)
|
|
237
|
+
}
|
|
225
238
|
}
|
|
226
|
-
|
|
239
|
+
}
|
|
227
240
|
|
|
228
|
-
|
|
241
|
+
async function generateEmbeddingsForDb(
|
|
242
|
+
db: import("bun:sqlite").Database,
|
|
243
|
+
config: import("./config").AgentikitConfig,
|
|
244
|
+
): Promise<boolean> {
|
|
245
|
+
if (!config.semanticSearch || !isVecAvailable()) return false
|
|
229
246
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
embedMs: tEmbedEnd - tTfidfEnd,
|
|
242
|
-
tfidfMs: tTfidfEnd - tWalkEnd,
|
|
243
|
-
},
|
|
247
|
+
try {
|
|
248
|
+
const { embed } = await import("./embedder.js")
|
|
249
|
+
const allEntries = getAllEntriesForEmbedding(db)
|
|
250
|
+
for (const { id, searchText } of allEntries) {
|
|
251
|
+
const embedding = await embed(searchText, config.embedding)
|
|
252
|
+
upsertEmbedding(db, id, embedding)
|
|
253
|
+
}
|
|
254
|
+
return true
|
|
255
|
+
} catch (error) {
|
|
256
|
+
console.warn("Embedding generation failed, continuing without:", error instanceof Error ? error.message : String(error))
|
|
257
|
+
return false
|
|
244
258
|
}
|
|
245
259
|
}
|
|
246
260
|
|
|
247
261
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
248
262
|
|
|
263
|
+
function getAllEntriesForEmbedding(db: import("bun:sqlite").Database): Array<{ id: number; searchText: string }> {
|
|
264
|
+
return db
|
|
265
|
+
.prepare(`
|
|
266
|
+
SELECT e.id, e.search_text AS searchText FROM entries e
|
|
267
|
+
WHERE NOT EXISTS (SELECT 1 FROM entries_vec v WHERE v.id = e.id)
|
|
268
|
+
`)
|
|
269
|
+
.all() as Array<{ id: number; searchText: string }>
|
|
270
|
+
}
|
|
271
|
+
|
|
249
272
|
function isDirStale(
|
|
250
273
|
dirPath: string,
|
|
251
274
|
currentFiles: string[],
|
|
252
|
-
previousEntries:
|
|
275
|
+
previousEntries: DbIndexedEntry[],
|
|
253
276
|
builtAtMs: number,
|
|
254
277
|
): boolean {
|
|
255
278
|
// Check if file set changed (additions or deletions)
|
|
@@ -327,7 +350,6 @@ async function enhanceStashWithLlm(
|
|
|
327
350
|
const enhanced: StashEntry[] = []
|
|
328
351
|
for (const entry of stash.entries) {
|
|
329
352
|
try {
|
|
330
|
-
// Find the file matching this entry for content context
|
|
331
353
|
const entryFile = entry.entry
|
|
332
354
|
? files.find((f) => path.basename(f) === entry.entry) ?? files[0]
|
|
333
355
|
: files[0]
|
|
@@ -345,7 +367,6 @@ async function enhanceStashWithLlm(
|
|
|
345
367
|
if (improvements.tags?.length) updated.tags = improvements.tags
|
|
346
368
|
enhanced.push(updated)
|
|
347
369
|
} catch {
|
|
348
|
-
// LLM enhancement failed for this entry, keep original
|
|
349
370
|
enhanced.push(entry)
|
|
350
371
|
}
|
|
351
372
|
}
|
package/src/init.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agentikit initialization logic.
|
|
3
3
|
*
|
|
4
|
-
* Creates the stash directory structure, sets the
|
|
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
|
|
|
@@ -25,7 +25,7 @@ export interface InitResponse {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export function agentikitInit(): InitResponse {
|
|
28
|
+
export async function agentikitInit(): Promise<InitResponse> {
|
|
29
29
|
let stashDir: string
|
|
30
30
|
if (IS_WINDOWS) {
|
|
31
31
|
const userProfile = process.env.USERPROFILE?.trim()
|
|
@@ -59,7 +59,7 @@ export function agentikitInit(): InitResponse {
|
|
|
59
59
|
let profileUpdated: string | undefined
|
|
60
60
|
|
|
61
61
|
if (IS_WINDOWS) {
|
|
62
|
-
const result = spawnSync("setx", ["
|
|
62
|
+
const result = spawnSync("setx", ["AKM_STASH_DIR", stashDir], {
|
|
63
63
|
encoding: "utf8",
|
|
64
64
|
timeout: 10_000,
|
|
65
65
|
})
|
|
@@ -76,22 +76,30 @@ export function agentikitInit(): InitResponse {
|
|
|
76
76
|
profile = path.join(homeDir, ".profile")
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
const exportLine = `export
|
|
79
|
+
const exportLine = `export AKM_STASH_DIR="${stashDir}"`
|
|
80
80
|
const existing = fs.existsSync(profile) ? fs.readFileSync(profile, "utf8") : ""
|
|
81
|
-
if (!existing.includes("
|
|
82
|
-
|
|
81
|
+
if (!existing.includes("AKM_STASH_DIR")) {
|
|
82
|
+
const updated = existing + `\n# Agentikit working stash directory\n${exportLine}\n`
|
|
83
|
+
const tmpPath = profile + `.tmp.${process.pid}`
|
|
84
|
+
try {
|
|
85
|
+
fs.writeFileSync(tmpPath, updated, "utf8")
|
|
86
|
+
fs.renameSync(tmpPath, profile)
|
|
87
|
+
} catch (err) {
|
|
88
|
+
try { fs.unlinkSync(tmpPath) } catch { /* ignore */ }
|
|
89
|
+
throw err
|
|
90
|
+
}
|
|
83
91
|
envSet = true
|
|
84
92
|
profileUpdated = profile
|
|
85
93
|
}
|
|
86
94
|
}
|
|
87
95
|
|
|
88
96
|
// Create default config.json if it doesn't exist
|
|
89
|
-
const configPath = getConfigPath(
|
|
97
|
+
const configPath = getConfigPath()
|
|
90
98
|
if (!fs.existsSync(configPath)) {
|
|
91
|
-
saveConfig(DEFAULT_CONFIG
|
|
99
|
+
saveConfig(DEFAULT_CONFIG)
|
|
92
100
|
}
|
|
93
101
|
|
|
94
|
-
process.env.
|
|
102
|
+
process.env.AKM_STASH_DIR = stashDir
|
|
95
103
|
|
|
96
104
|
// Ensure ripgrep is available (install to stash/bin if needed)
|
|
97
105
|
let ripgrep: InitResponse["ripgrep"]
|
package/src/llm.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { LlmConnectionConfig } from "./config"
|
|
2
|
+
import { fetchWithTimeout } from "./common"
|
|
2
3
|
import type { StashEntry } from "./metadata"
|
|
3
4
|
|
|
4
5
|
// ── OpenAI-compatible chat completions ──────────────────────────────────────
|
|
@@ -21,14 +22,14 @@ async function chatCompletion(
|
|
|
21
22
|
headers["Authorization"] = `Bearer ${config.apiKey}`
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
const response = await
|
|
25
|
+
const response = await fetchWithTimeout(config.endpoint, {
|
|
25
26
|
method: "POST",
|
|
26
27
|
headers,
|
|
27
28
|
body: JSON.stringify({
|
|
28
29
|
model: config.model,
|
|
29
30
|
messages,
|
|
30
|
-
temperature: 0.3,
|
|
31
|
-
max_tokens: 512,
|
|
31
|
+
temperature: config.temperature ?? 0.3,
|
|
32
|
+
max_tokens: config.maxTokens ?? 512,
|
|
32
33
|
}),
|
|
33
34
|
})
|
|
34
35
|
|
package/src/metadata.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { type AgentikitAssetType, isAssetType } from "./common"
|
|
|
4
4
|
import { SCRIPT_EXTENSIONS, isRelevantAssetFile, deriveCanonicalAssetName } from "./asset-spec"
|
|
5
5
|
import { parseFrontmatter, toStringOrUndefined } from "./frontmatter"
|
|
6
6
|
import { parseMarkdownToc, type TocHeading } from "./markdown"
|
|
7
|
+
import { tryGetHandler } from "./asset-type-handler"
|
|
7
8
|
|
|
8
9
|
// ── Schema ──────────────────────────────────────────────────────────────────
|
|
9
10
|
|
|
@@ -183,25 +184,10 @@ export function generateMetadata(
|
|
|
183
184
|
}
|
|
184
185
|
}
|
|
185
186
|
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const toc = parseMarkdownToc(mdContent)
|
|
191
|
-
if (toc.headings.length > 0) entry.toc = toc.headings
|
|
192
|
-
} catch {
|
|
193
|
-
// Non-fatal: skip TOC if file can't be read
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Priority 3: Code comments (for script files)
|
|
198
|
-
if (SCRIPT_EXTENSIONS.has(ext) && ext !== ".md") {
|
|
199
|
-
const commentDesc = extractDescriptionFromComments(file)
|
|
200
|
-
if (commentDesc && !entry.description) {
|
|
201
|
-
entry.description = commentDesc
|
|
202
|
-
entry.source = "comments"
|
|
203
|
-
entry.confidence = 0.7
|
|
204
|
-
}
|
|
187
|
+
// Type-specific metadata extraction (e.g. TOC for knowledge, comments for tools/scripts)
|
|
188
|
+
const handler = tryGetHandler(assetType)
|
|
189
|
+
if (handler?.extractTypeMetadata) {
|
|
190
|
+
handler.extractTypeMetadata(entry, file, ext)
|
|
205
191
|
}
|
|
206
192
|
|
|
207
193
|
// Priority 4: Filename heuristics (fallback)
|
|
@@ -249,52 +235,6 @@ function buildAliases(name: string, tags: string[]): string[] {
|
|
|
249
235
|
return Array.from(aliases)
|
|
250
236
|
}
|
|
251
237
|
|
|
252
|
-
// ── Intent Generation ────────────────────────────────────────────────────────
|
|
253
|
-
|
|
254
|
-
export function generateIntents(description: string, tags: string[], name: string): string[] {
|
|
255
|
-
const intents = new Set<string>()
|
|
256
|
-
|
|
257
|
-
// Split name on separators to extract tokens and potential verb
|
|
258
|
-
const nameTokens = name
|
|
259
|
-
.replace(/[-_]+/g, " ")
|
|
260
|
-
.replace(/([a-z])([A-Z])/g, "$1 $2")
|
|
261
|
-
.toLowerCase()
|
|
262
|
-
.trim()
|
|
263
|
-
.split(/\s+/)
|
|
264
|
-
.filter((t) => t.length > 1)
|
|
265
|
-
|
|
266
|
-
// Intent from name as phrase (e.g. "summarize diff")
|
|
267
|
-
const namePhrase = nameTokens.join(" ")
|
|
268
|
-
if (namePhrase.length > 2) intents.add(namePhrase)
|
|
269
|
-
|
|
270
|
-
// Intent from description (lowercased)
|
|
271
|
-
const desc = description.toLowerCase().trim()
|
|
272
|
-
if (desc.length > 2) intents.add(desc)
|
|
273
|
-
|
|
274
|
-
// Combine first name token (potential verb) with tags
|
|
275
|
-
// e.g. name "summarize-diff", tags ["git"] → "summarize git diff"
|
|
276
|
-
if (nameTokens.length >= 1 && tags.length > 0) {
|
|
277
|
-
const verb = nameTokens[0]
|
|
278
|
-
const rest = nameTokens.slice(1).join(" ")
|
|
279
|
-
for (const tag of tags) {
|
|
280
|
-
const tagLower = tag.toLowerCase()
|
|
281
|
-
// verb + tag + rest (e.g. "summarize git diff")
|
|
282
|
-
const parts = [verb, tagLower, rest].filter((p) => p.length > 0)
|
|
283
|
-
const phrase = parts.join(" ")
|
|
284
|
-
if (phrase !== namePhrase && phrase.length > 2) intents.add(phrase)
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// Join tag pairs (e.g. ["git", "diff"] → "git diff")
|
|
289
|
-
if (tags.length >= 2) {
|
|
290
|
-
const tagPhrase = tags.map((t) => t.toLowerCase()).join(" ")
|
|
291
|
-
if (tagPhrase.length > 2) intents.add(tagPhrase)
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Cap at 8 intents
|
|
295
|
-
return Array.from(intents).slice(0, 8)
|
|
296
|
-
}
|
|
297
|
-
|
|
298
238
|
export function extractDescriptionFromComments(filePath: string): string | null {
|
|
299
239
|
let content: string
|
|
300
240
|
try {
|