agentikit 0.0.7 → 0.0.9

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 (98) hide show
  1. package/README.md +215 -76
  2. package/dist/index.d.ts +17 -3
  3. package/dist/index.js +10 -2
  4. package/dist/src/asset-spec.d.ts +14 -0
  5. package/dist/src/asset-spec.js +46 -0
  6. package/dist/src/cli.js +268 -57
  7. package/dist/src/common.d.ts +8 -0
  8. package/dist/src/common.js +46 -0
  9. package/dist/src/config.d.ts +37 -0
  10. package/dist/src/config.js +124 -0
  11. package/dist/src/embedder.d.ts +10 -0
  12. package/dist/src/embedder.js +87 -0
  13. package/dist/src/frontmatter.d.ts +30 -0
  14. package/dist/src/frontmatter.js +86 -0
  15. package/dist/src/indexer.d.ts +20 -2
  16. package/dist/src/indexer.js +212 -80
  17. package/dist/src/init.d.ts +19 -0
  18. package/dist/src/init.js +87 -0
  19. package/dist/src/llm.d.ts +15 -0
  20. package/dist/src/llm.js +91 -0
  21. package/dist/src/markdown.d.ts +18 -0
  22. package/dist/src/markdown.js +77 -0
  23. package/dist/src/metadata.d.ts +11 -2
  24. package/dist/src/metadata.js +161 -29
  25. package/dist/src/registry-install.d.ts +11 -0
  26. package/dist/src/registry-install.js +208 -0
  27. package/dist/src/registry-resolve.d.ts +3 -0
  28. package/dist/src/registry-resolve.js +231 -0
  29. package/dist/src/registry-search.d.ts +5 -0
  30. package/dist/src/registry-search.js +129 -0
  31. package/dist/src/registry-types.d.ts +55 -0
  32. package/dist/src/registry-types.js +1 -0
  33. package/dist/src/ripgrep-install.d.ts +12 -0
  34. package/dist/src/ripgrep-install.js +169 -0
  35. package/dist/src/ripgrep-resolve.d.ts +13 -0
  36. package/dist/src/ripgrep-resolve.js +68 -0
  37. package/dist/src/ripgrep.d.ts +3 -36
  38. package/dist/src/ripgrep.js +2 -262
  39. package/dist/src/similarity.d.ts +1 -2
  40. package/dist/src/similarity.js +11 -0
  41. package/dist/src/stash-add.d.ts +4 -0
  42. package/dist/src/stash-add.js +59 -0
  43. package/dist/src/stash-ref.d.ts +7 -0
  44. package/dist/src/stash-ref.js +33 -0
  45. package/dist/src/stash-registry.d.ts +18 -0
  46. package/dist/src/stash-registry.js +221 -0
  47. package/dist/src/stash-resolve.d.ts +2 -0
  48. package/dist/src/stash-resolve.js +45 -0
  49. package/dist/src/stash-search.d.ts +8 -0
  50. package/dist/src/stash-search.js +484 -0
  51. package/dist/src/stash-show.d.ts +5 -0
  52. package/dist/src/stash-show.js +114 -0
  53. package/dist/src/stash-types.d.ts +217 -0
  54. package/dist/src/stash-types.js +1 -0
  55. package/dist/src/stash.d.ts +10 -63
  56. package/dist/src/stash.js +6 -633
  57. package/dist/src/tool-runner.d.ts +35 -0
  58. package/dist/src/tool-runner.js +100 -0
  59. package/dist/src/walker.d.ts +19 -0
  60. package/dist/src/walker.js +47 -0
  61. package/package.json +8 -14
  62. package/src/asset-spec.ts +69 -0
  63. package/src/cli.ts +282 -46
  64. package/src/common.ts +58 -0
  65. package/src/config.ts +183 -0
  66. package/src/embedder.ts +117 -0
  67. package/src/frontmatter.ts +95 -0
  68. package/src/indexer.ts +244 -84
  69. package/src/init.ts +106 -0
  70. package/src/llm.ts +124 -0
  71. package/src/markdown.ts +106 -0
  72. package/src/metadata.ts +171 -27
  73. package/src/registry-install.ts +245 -0
  74. package/src/registry-resolve.ts +272 -0
  75. package/src/registry-search.ts +145 -0
  76. package/src/registry-types.ts +64 -0
  77. package/src/ripgrep-install.ts +200 -0
  78. package/src/ripgrep-resolve.ts +72 -0
  79. package/src/ripgrep.ts +3 -315
  80. package/src/similarity.ts +13 -1
  81. package/src/stash-add.ts +66 -0
  82. package/src/stash-ref.ts +41 -0
  83. package/src/stash-registry.ts +259 -0
  84. package/src/stash-resolve.ts +47 -0
  85. package/src/stash-search.ts +595 -0
  86. package/src/stash-show.ts +112 -0
  87. package/src/stash-types.ts +221 -0
  88. package/src/stash.ts +31 -760
  89. package/src/tool-runner.ts +129 -0
  90. package/src/walker.ts +53 -0
  91. package/.claude-plugin/plugin.json +0 -21
  92. package/commands/open.md +0 -11
  93. package/commands/run.md +0 -11
  94. package/commands/search.md +0 -11
  95. package/dist/src/plugin.d.ts +0 -2
  96. package/dist/src/plugin.js +0 -55
  97. package/skills/stash/SKILL.md +0 -73
  98. package/src/plugin.ts +0 -56
package/src/stash.ts CHANGED
@@ -1,760 +1,31 @@
1
- import { spawnSync } from "node:child_process"
2
- import fs from "node:fs"
3
- import path from "node:path"
4
- import { loadSearchIndex, buildSearchText } from "./indexer"
5
- import { TfIdfAdapter, type ScoredEntry } from "./similarity"
6
- import { rgFilterCandidates, ensureRg } from "./ripgrep"
7
-
8
- export type AgentikitAssetType = "tool" | "skill" | "command" | "agent"
9
- export type AgentikitSearchType = AgentikitAssetType | "any"
10
-
11
- export interface SearchHit {
12
- type: AgentikitAssetType
13
- name: string
14
- path: string
15
- openRef: string
16
- summary?: string
17
- description?: string
18
- tags?: string[]
19
- score?: number
20
- runCmd?: string
21
- kind?: "bash" | "bun" | "powershell" | "cmd"
22
- }
23
-
24
- export interface SearchResponse {
25
- stashDir: string
26
- hits: SearchHit[]
27
- tip?: string
28
- }
29
-
30
- export interface OpenResponse {
31
- type: AgentikitAssetType
32
- name: string
33
- path: string
34
- content?: string
35
- template?: string
36
- prompt?: string
37
- description?: string
38
- toolPolicy?: unknown
39
- modelHint?: unknown
40
- runCmd?: string
41
- kind?: "bash" | "bun" | "powershell" | "cmd"
42
- }
43
-
44
- export interface RunResponse {
45
- type: "tool"
46
- name: string
47
- path: string
48
- output: string
49
- exitCode: number
50
- }
51
-
52
- type IndexedAsset = {
53
- type: AgentikitAssetType
54
- name: string
55
- path: string
56
- }
57
-
58
- interface ToolExecution {
59
- command: string
60
- args: string[]
61
- cwd?: string
62
- }
63
-
64
- interface ToolInfo {
65
- runCmd: string
66
- kind: "bash" | "bun" | "powershell" | "cmd"
67
- install?: ToolExecution
68
- execute: ToolExecution
69
- }
70
-
71
- const IS_WINDOWS = process.platform === "win32"
72
- const TOOL_EXTENSIONS = new Set([".sh", ".ts", ".js", ".ps1", ".cmd", ".bat"])
73
- const DEFAULT_LIMIT = 20
74
-
75
- export function resolveStashDir(): string {
76
- const raw = process.env.AGENTIKIT_STASH_DIR?.trim()
77
- if (!raw) {
78
- throw new Error("AGENTIKIT_STASH_DIR is not set. Set it to your Agentikit stash path.")
79
- }
80
- const stashDir = path.resolve(raw)
81
- let stat: fs.Stats
82
- try {
83
- stat = fs.statSync(stashDir)
84
- } catch {
85
- throw new Error(`Unable to read AGENTIKIT_STASH_DIR at "${stashDir}".`)
86
- }
87
- if (!stat.isDirectory()) {
88
- throw new Error(`AGENTIKIT_STASH_DIR must point to a directory: "${stashDir}".`)
89
- }
90
- return stashDir
91
- }
92
-
93
- export function agentikitSearch(input: {
94
- query: string
95
- type?: AgentikitSearchType
96
- limit?: number
97
- }): SearchResponse {
98
- const query = input.query.trim().toLowerCase()
99
- const searchType = input.type ?? "any"
100
- const limit = normalizeLimit(input.limit)
101
- const stashDir = resolveStashDir()
102
-
103
- // Try semantic search via persisted index
104
- const semanticHits = trySemanticSearch(query, searchType, limit, stashDir)
105
- if (semanticHits) {
106
- return {
107
- stashDir,
108
- hits: semanticHits,
109
- tip: semanticHits.length === 0 ? "No matching stash assets were found. Try running 'agentikit index' to rebuild." : undefined,
110
- }
111
- }
112
-
113
- // Fallback: substring matching (no index built yet)
114
- const assets = indexAssets(stashDir, searchType)
115
- const hits = assets
116
- .filter((asset) => asset.name.toLowerCase().includes(query))
117
- .sort(compareAssets)
118
- .slice(0, limit)
119
- .map((asset): SearchHit => assetToSearchHit(asset, stashDir))
120
-
121
- return {
122
- stashDir,
123
- hits,
124
- tip: hits.length === 0 ? "No matching stash assets were found." : undefined,
125
- }
126
- }
127
-
128
- function trySemanticSearch(
129
- query: string,
130
- searchType: AgentikitSearchType,
131
- limit: number,
132
- stashDir: string,
133
- ): SearchHit[] | null {
134
- const index = loadSearchIndex()
135
- if (!index || !index.entries || index.entries.length === 0) return null
136
- if (index.stashDir !== stashDir) return null
137
-
138
- // Stage 1: ripgrep candidate filtering
139
- // Use rg to pre-filter .stash.json files that contain query tokens,
140
- // then only run TF-IDF ranking on those candidates.
141
- let candidateEntries = index.entries
142
- if (query) {
143
- const rgResult = rgFilterCandidates(query, stashDir, stashDir)
144
- if (rgResult && rgResult.usedRg) {
145
- const matchedDirs = new Set(rgResult.matchedFiles.map((f) => path.dirname(f)))
146
- candidateEntries = index.entries.filter((ie) => matchedDirs.has(ie.dirPath))
147
- // If rg found nothing but we have a query, still fall through to TF-IDF
148
- // on all entries — rg is a fast pre-filter, not the final authority
149
- if (candidateEntries.length === 0) {
150
- candidateEntries = index.entries
151
- }
152
- }
153
- }
154
-
155
- // Stage 2: TF-IDF semantic ranking
156
- const scoredEntries: ScoredEntry[] = candidateEntries.map((ie) => ({
157
- id: `${ie.entry.type}:${ie.entry.name}`,
158
- text: buildSearchText(ie.entry),
159
- entry: ie.entry,
160
- path: ie.path,
161
- }))
162
-
163
- let adapter: TfIdfAdapter
164
- if (index.tfidf && !query) {
165
- // Use cached TF-IDF state for empty queries (listing all)
166
- const allScored: ScoredEntry[] = index.entries.map((ie) => ({
167
- id: `${ie.entry.type}:${ie.entry.name}`,
168
- text: buildSearchText(ie.entry),
169
- entry: ie.entry,
170
- path: ie.path,
171
- }))
172
- adapter = TfIdfAdapter.deserialize(index.tfidf as any, allScored)
173
- } else {
174
- // Rebuild adapter from candidate subset
175
- adapter = new TfIdfAdapter()
176
- adapter.buildIndex(scoredEntries)
177
- }
178
-
179
- const typeFilter = searchType === "any" ? undefined : searchType
180
- const results = adapter.search(query, limit, typeFilter)
181
-
182
- return results.map((r): SearchHit => {
183
- // Derive the openRef name from the filesystem path, not the stash entry name,
184
- // because agentikitOpen resolves assets by their relative path under the type root.
185
- const openRefName = deriveOpenRefName(r.entry.type, r.path, stashDir)
186
-
187
- const hit: SearchHit = {
188
- type: r.entry.type,
189
- name: r.entry.name,
190
- path: r.path,
191
- openRef: makeOpenRef(r.entry.type, openRefName),
192
- description: r.entry.description,
193
- tags: r.entry.tags,
194
- score: r.score,
195
- }
196
-
197
- if (r.entry.type === "tool") {
198
- try {
199
- const toolInfo = buildToolInfo(stashDir, r.path)
200
- hit.runCmd = toolInfo.runCmd
201
- hit.kind = toolInfo.kind
202
- } catch {
203
- // Tool file may have been removed since indexing
204
- }
205
- }
206
-
207
- return hit
208
- })
209
- }
210
-
211
- /**
212
- * Derive the correct openRef name for a semantic search result.
213
- * Tools use their relative file path (e.g., "deploy/deploy-k8s.sh"),
214
- * skills use directory name, commands/agents use relative .md path.
215
- */
216
- function deriveOpenRefName(
217
- type: AgentikitAssetType,
218
- filePath: string,
219
- stashDir: string,
220
- ): string {
221
- const indexer = ASSET_INDEXERS[type]
222
- const root = path.join(stashDir, indexer.dir)
223
- if (type === "skill") {
224
- // Skills resolve by directory name relative to skills/
225
- const rel = toPosix(path.dirname(path.relative(root, filePath)))
226
- return rel === "." ? path.basename(path.dirname(filePath)) : rel
227
- }
228
- return toPosix(path.relative(root, filePath))
229
- }
230
-
231
- export function agentikitOpen(input: { ref: string }): OpenResponse {
232
- const parsed = parseOpenRef(input.ref)
233
- const stashDir = resolveStashDir()
234
- const assetPath = resolveAssetPath(stashDir, parsed.type, parsed.name)
235
- const content = fs.readFileSync(assetPath, "utf8")
236
-
237
- switch (parsed.type) {
238
- case "skill":
239
- return {
240
- type: "skill",
241
- name: parsed.name,
242
- path: assetPath,
243
- content,
244
- }
245
- case "command": {
246
- const parsedMd = parseFrontmatter(content)
247
- return {
248
- type: "command",
249
- name: parsed.name,
250
- path: assetPath,
251
- description: toStringOrUndefined(parsedMd.data.description),
252
- template: parsedMd.content,
253
- }
254
- }
255
- case "agent": {
256
- const parsedMd = parseFrontmatter(content)
257
- return {
258
- type: "agent",
259
- name: parsed.name,
260
- path: assetPath,
261
- description: toStringOrUndefined(parsedMd.data.description),
262
- prompt: parsedMd.content,
263
- toolPolicy: parsedMd.data.tools,
264
- modelHint: parsedMd.data.model,
265
- }
266
- }
267
- case "tool": {
268
- const toolInfo = buildToolInfo(stashDir, assetPath)
269
- return {
270
- type: "tool",
271
- name: parsed.name,
272
- path: assetPath,
273
- runCmd: toolInfo.runCmd,
274
- kind: toolInfo.kind,
275
- }
276
- }
277
- }
278
- }
279
-
280
- export function agentikitRun(input: { ref: string }): RunResponse {
281
- const parsed = parseOpenRef(input.ref)
282
- if (parsed.type !== "tool") {
283
- throw new Error(`agentikitRun only supports tool refs. Got: "${parsed.type}".`)
284
- }
285
- const stashDir = resolveStashDir()
286
- const assetPath = resolveAssetPath(stashDir, "tool", parsed.name)
287
- const toolInfo = buildToolInfo(stashDir, assetPath)
288
-
289
- if (toolInfo.install) {
290
- const installResult = runToolExecution(toolInfo.install)
291
- if (installResult.exitCode !== 0) {
292
- return {
293
- type: "tool",
294
- name: parsed.name,
295
- path: assetPath,
296
- output: installResult.output,
297
- exitCode: installResult.exitCode,
298
- }
299
- }
300
- }
301
-
302
- const runResult = runToolExecution(toolInfo.execute)
303
-
304
- return {
305
- type: "tool",
306
- name: parsed.name,
307
- path: assetPath,
308
- output: runResult.output,
309
- exitCode: runResult.exitCode,
310
- }
311
- }
312
-
313
- function assetToSearchHit(asset: IndexedAsset, stashDir: string): SearchHit {
314
- if (asset.type !== "tool") {
315
- return {
316
- type: asset.type,
317
- name: asset.name,
318
- path: asset.path,
319
- openRef: makeOpenRef(asset.type, asset.name),
320
- }
321
- }
322
- const toolInfo = buildToolInfo(stashDir, asset.path)
323
- return {
324
- type: "tool",
325
- name: asset.name,
326
- path: asset.path,
327
- openRef: makeOpenRef("tool", asset.name),
328
- runCmd: toolInfo.runCmd,
329
- kind: toolInfo.kind,
330
- }
331
- }
332
-
333
- function normalizeLimit(limit?: number): number {
334
- if (typeof limit !== "number" || Number.isNaN(limit) || limit <= 0) {
335
- return DEFAULT_LIMIT
336
- }
337
- return Math.min(Math.floor(limit), 200)
338
- }
339
-
340
- const ASSET_INDEXERS: Record<AgentikitAssetType, { dir: string; collect: (root: string, file: string) => IndexedAsset | undefined }> = {
341
- tool: {
342
- dir: "tools",
343
- collect(root, file) {
344
- if (!TOOL_EXTENSIONS.has(path.extname(file).toLowerCase())) return undefined
345
- return { type: "tool", name: toPosix(path.relative(root, file)), path: file }
346
- },
347
- },
348
- skill: {
349
- dir: "skills",
350
- collect(root, file) {
351
- if (path.basename(file) !== "SKILL.md") return undefined
352
- const relDir = toPosix(path.dirname(path.relative(root, file)))
353
- if (!relDir || relDir === ".") return undefined
354
- return { type: "skill", name: relDir, path: file }
355
- },
356
- },
357
- command: {
358
- dir: "commands",
359
- collect(root, file) {
360
- if (path.extname(file).toLowerCase() !== ".md") return undefined
361
- return { type: "command", name: toPosix(path.relative(root, file)), path: file }
362
- },
363
- },
364
- agent: {
365
- dir: "agents",
366
- collect(root, file) {
367
- if (path.extname(file).toLowerCase() !== ".md") return undefined
368
- return { type: "agent", name: toPosix(path.relative(root, file)), path: file }
369
- },
370
- },
371
- }
372
-
373
- function indexAssets(stashDir: string, type: AgentikitSearchType): IndexedAsset[] {
374
- const assets: IndexedAsset[] = []
375
- const types = type === "any" ? (Object.keys(ASSET_INDEXERS) as AgentikitAssetType[]) : [type]
376
- for (const assetType of types) {
377
- const indexer = ASSET_INDEXERS[assetType]
378
- const root = path.join(stashDir, indexer.dir)
379
- walkFiles(root, (file) => {
380
- const asset = indexer.collect(root, file)
381
- if (asset) assets.push(asset)
382
- })
383
- }
384
- return assets
385
- }
386
-
387
- function walkFiles(root: string, onFile: (file: string) => void): void {
388
- if (!fs.existsSync(root)) return
389
- const stack = [root]
390
- while (stack.length > 0) {
391
- const current = stack.pop()
392
- if (!current) continue
393
- const entries = fs.readdirSync(current, { withFileTypes: true })
394
- for (const entry of entries) {
395
- const fullPath = path.join(current, entry.name)
396
- if (entry.isDirectory()) {
397
- stack.push(fullPath)
398
- } else if (entry.isFile()) {
399
- onFile(fullPath)
400
- }
401
- }
402
- }
403
- }
404
-
405
- function compareAssets(a: IndexedAsset, b: IndexedAsset): number {
406
- if (a.type !== b.type) return a.type.localeCompare(b.type)
407
- return a.name.localeCompare(b.name)
408
- }
409
-
410
- function parseOpenRef(ref: string): { type: AgentikitAssetType; name: string } {
411
- const separator = ref.indexOf(":")
412
- if (separator <= 0) {
413
- throw new Error("Invalid open ref. Expected format '<type>:<name>'.")
414
- }
415
- const rawType = ref.slice(0, separator)
416
- const rawName = ref.slice(separator + 1)
417
- if (!isAssetType(rawType)) {
418
- throw new Error(`Invalid open ref type: "${rawType}".`)
419
- }
420
- let name: string
421
- try {
422
- name = decodeURIComponent(rawName)
423
- } catch {
424
- throw new Error("Invalid open ref encoding.")
425
- }
426
- const normalized = path.posix.normalize(name.replace(/\\/g, "/"))
427
- if (
428
- !name
429
- || name.includes("\0")
430
- || /^[A-Za-z]:/.test(name)
431
- || path.posix.isAbsolute(normalized)
432
- || normalized === ".."
433
- || normalized.startsWith("../")
434
- ) {
435
- throw new Error("Invalid open ref name.")
436
- }
437
- return { type: rawType, name: normalized }
438
- }
439
-
440
- function makeOpenRef(type: AgentikitAssetType, name: string): string {
441
- return `${type}:${encodeURIComponent(name)}`
442
- }
443
-
444
- function resolveAssetPath(stashDir: string, type: AgentikitAssetType, name: string): string {
445
- const root = path.join(stashDir, type === "tool" ? "tools" : `${type}s`)
446
- const target = type === "skill" ? path.join(root, name, "SKILL.md") : path.join(root, name)
447
- const resolvedRoot = resolveAndValidateTypeRoot(root, type, name)
448
- const resolvedTarget = path.resolve(target)
449
- if (!isWithin(resolvedTarget, resolvedRoot)) {
450
- throw new Error("Ref resolves outside the stash root.")
451
- }
452
- if (!fs.existsSync(resolvedTarget) || !fs.statSync(resolvedTarget).isFile()) {
453
- throw new Error(`Stash asset not found for ref: ${type}:${name}`)
454
- }
455
- const realTarget = fs.realpathSync(resolvedTarget)
456
- if (!isWithin(realTarget, resolvedRoot)) {
457
- throw new Error("Ref resolves outside the stash root.")
458
- }
459
- if (type === "tool" && !TOOL_EXTENSIONS.has(path.extname(resolvedTarget).toLowerCase())) {
460
- throw new Error("Tool ref must resolve to a .sh, .ts, .js, .ps1, .cmd, or .bat file.")
461
- }
462
- return realTarget
463
- }
464
-
465
- function resolveAndValidateTypeRoot(root: string, type: AgentikitAssetType, name: string): string {
466
- const rootStat = readTypeRootStat(root, type, name)
467
- if (!rootStat.isDirectory()) {
468
- throw new Error(`Stash type root is not a directory for ref: ${type}:${name}`)
469
- }
470
- return fs.realpathSync(root)
471
- }
472
-
473
- function readTypeRootStat(root: string, type: AgentikitAssetType, name: string): fs.Stats {
474
- try {
475
- return fs.statSync(root)
476
- } catch (error: unknown) {
477
- if (hasErrnoCode(error, "ENOENT")) {
478
- throw new Error(`Stash type root not found for ref: ${type}:${name}`)
479
- }
480
- throw error
481
- }
482
- }
483
-
484
- function buildToolInfo(stashDir: string, filePath: string): ToolInfo {
485
- const ext = path.extname(filePath).toLowerCase()
486
-
487
- if (ext === ".sh") {
488
- return {
489
- runCmd: `bash ${shellQuote(filePath)}`,
490
- kind: "bash",
491
- execute: { command: "bash", args: [filePath] },
492
- }
493
- }
494
-
495
- if (ext === ".ps1") {
496
- return {
497
- runCmd: `powershell -ExecutionPolicy Bypass -File ${shellQuote(filePath)}`,
498
- kind: "powershell",
499
- execute: { command: "powershell", args: ["-ExecutionPolicy", "Bypass", "-File", filePath] },
500
- }
501
- }
502
-
503
- if (ext === ".cmd" || ext === ".bat") {
504
- return {
505
- runCmd: `cmd /c ${shellQuote(filePath)}`,
506
- kind: "cmd",
507
- execute: { command: "cmd", args: ["/c", filePath] },
508
- }
509
- }
510
-
511
- if (ext !== ".ts" && ext !== ".js") {
512
- throw new Error(`Unsupported tool extension: ${ext}`)
513
- }
514
-
515
- const toolsRoot = path.resolve(path.join(stashDir, "tools"))
516
- const pkgDir = findNearestPackageDir(path.dirname(filePath), toolsRoot)
517
- if (!pkgDir) {
518
- return {
519
- runCmd: `bun ${shellQuote(filePath)}`,
520
- kind: "bun",
521
- execute: { command: "bun", args: [filePath] },
522
- }
523
- }
524
- const installFlag = process.env.AGENTIKIT_BUN_INSTALL
525
- const shouldInstall = installFlag === "1" || installFlag === "true" || installFlag === "yes"
526
-
527
- const quotedPkgDir = shellQuote(pkgDir)
528
- const quotedFilePath = shellQuote(filePath)
529
- const cdCmd = IS_WINDOWS ? `cd /d ${quotedPkgDir}` : `cd ${quotedPkgDir}`
530
- const chain = IS_WINDOWS ? " & " : " && "
531
- return {
532
- runCmd: shouldInstall
533
- ? `${cdCmd}${chain}bun install${chain}bun ${quotedFilePath}`
534
- : `${cdCmd}${chain}bun ${quotedFilePath}`,
535
- kind: "bun",
536
- install: shouldInstall ? { command: "bun", args: ["install"], cwd: pkgDir } : undefined,
537
- execute: { command: "bun", args: [filePath], cwd: pkgDir },
538
- }
539
- }
540
-
541
- function findNearestPackageDir(startDir: string, toolsRoot: string): string | undefined {
542
- let current = path.resolve(startDir)
543
- const root = path.resolve(toolsRoot)
544
- while (isWithin(current, root)) {
545
- if (fs.existsSync(path.join(current, "package.json"))) {
546
- return current
547
- }
548
- if (current === root) return undefined
549
- current = path.dirname(current)
550
- }
551
- return undefined
552
- }
553
-
554
- function isWithin(candidate: string, root: string): boolean {
555
- const normalizedRoot = normalizeFsPathForComparison(path.resolve(root))
556
- const normalizedCandidate = normalizeFsPathForComparison(path.resolve(candidate))
557
- const rel = path.relative(normalizedRoot, normalizedCandidate)
558
- return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel))
559
- }
560
-
561
- function normalizeFsPathForComparison(value: string): string {
562
- return process.platform === "win32" ? value.toLowerCase() : value
563
- }
564
-
565
- function toPosix(input: string): string {
566
- return input.split(path.sep).join("/")
567
- }
568
-
569
- function parseFrontmatter(raw: string): { data: Record<string, unknown>; content: string } {
570
- const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/)
571
- if (!match) {
572
- return { data: {}, content: raw }
573
- }
574
-
575
- const data: Record<string, unknown> = {}
576
- let currentKey: string | null = null
577
- let nested: Record<string, unknown> | null = null
578
-
579
- for (const line of match[1].split(/\r?\n/)) {
580
- const indented = line.match(/^ (\w[\w-]*):\s*(.+)$/)
581
- if (indented && currentKey && nested) {
582
- nested[indented[1]] = parseYamlScalar(indented[2].trim())
583
- continue
584
- }
585
-
586
- const top = line.match(/^(\w[\w-]*):\s*(.*)$/)
587
- if (!top) {
588
- continue
589
- }
590
-
591
- currentKey = top[1]
592
- const value = top[2].trim()
593
- if (value === "") {
594
- nested = {}
595
- data[currentKey] = nested
596
- } else {
597
- nested = null
598
- data[currentKey] = parseYamlScalar(value)
599
- }
600
- }
601
- return { data, content: match[2] }
602
- }
603
-
604
- function parseYamlScalar(value: string): unknown {
605
- if (value === "") return ""
606
- if (value === "true") return true
607
- if (value === "false") return false
608
- const asNumber = Number(value)
609
- if (!Number.isNaN(asNumber)) return asNumber
610
- if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
611
- return value.slice(1, -1)
612
- }
613
- return value
614
- }
615
-
616
- function isAssetType(type: string): type is AgentikitAssetType {
617
- return type === "tool" || type === "skill" || type === "command" || type === "agent"
618
- }
619
-
620
- function toStringOrUndefined(value: unknown): string | undefined {
621
- return typeof value === "string" && value.trim() ? value : undefined
622
- }
623
-
624
- function shellQuote(input: string): string {
625
- if (/[\r\n\t\0]/.test(input)) {
626
- throw new Error("Unsupported control characters in stash path.")
627
- }
628
- if (IS_WINDOWS) {
629
- return `"${input.replace(/"/g, '""')}"`
630
- }
631
- const escaped = input
632
- .replace(/\\/g, "\\\\")
633
- .replace(/"/g, '\\"')
634
- .replace(/\$/g, "\\$")
635
- .replace(/`/g, "\\`")
636
- return `"${escaped}"`
637
- }
638
-
639
- function runToolExecution(execution: ToolExecution): { output: string; exitCode: number } {
640
- const result = spawnSync(execution.command, execution.args, {
641
- cwd: execution.cwd,
642
- encoding: "utf8",
643
- timeout: 60_000,
644
- })
645
-
646
- const stdout = typeof result.stdout === "string" ? result.stdout : ""
647
- const stderr = typeof result.stderr === "string" ? result.stderr : ""
648
- const combinedOutput = combineProcessOutput(stdout, stderr)
649
- if (typeof result.status === "number") {
650
- return { output: combinedOutput, exitCode: result.status }
651
- }
652
- if (result.error) {
653
- return {
654
- output: `${combinedOutput}${result.error.message ? `\n${result.error.message}` : ""}`.trim(),
655
- exitCode: 1,
656
- }
657
- }
658
- return {
659
- output: combinedOutput || `Unexpected process termination while running "${execution.command}": no status code or error information available.`,
660
- exitCode: 1,
661
- }
662
- }
663
-
664
- function combineProcessOutput(stdout: string, stderr: string): string {
665
- if (stdout && stderr) {
666
- return `stdout:\n${stdout.trim()}\n\nstderr:\n${stderr.trim()}`
667
- }
668
- return `${stdout}${stderr}`.trim()
669
- }
670
-
671
- export interface InitResponse {
672
- stashDir: string
673
- created: boolean
674
- envSet: boolean
675
- profileUpdated?: string
676
- ripgrep?: {
677
- rgPath: string
678
- installed: boolean
679
- version: string
680
- }
681
- }
682
-
683
- export function agentikitInit(): InitResponse {
684
- let stashDir: string
685
- const home = process.env.HOME || ""
686
- if (IS_WINDOWS) {
687
- const docs = process.env.USERPROFILE
688
- ? path.join(process.env.USERPROFILE, "Documents")
689
- : ""
690
- if (!docs) {
691
- throw new Error("Unable to determine Documents folder. Ensure USERPROFILE is set.")
692
- }
693
- stashDir = path.join(docs, "agentikit")
694
- } else {
695
- if (!home) {
696
- throw new Error("Unable to determine home directory. Set HOME.")
697
- }
698
- stashDir = path.join(home, "agentikit")
699
- }
700
-
701
- let created = false
702
- if (!fs.existsSync(stashDir)) {
703
- fs.mkdirSync(stashDir, { recursive: true })
704
- created = true
705
- }
706
-
707
- for (const sub of ["tools", "skills", "commands", "agents"]) {
708
- const subDir = path.join(stashDir, sub)
709
- if (!fs.existsSync(subDir)) {
710
- fs.mkdirSync(subDir, { recursive: true })
711
- }
712
- }
713
-
714
- let envSet = false
715
- let profileUpdated: string | undefined
716
-
717
- if (IS_WINDOWS) {
718
- const result = spawnSync("setx", ["AGENTIKIT_STASH_DIR", stashDir], {
719
- encoding: "utf8",
720
- timeout: 10_000,
721
- })
722
- envSet = result.status === 0
723
- } else {
724
- const shell = process.env.SHELL || ""
725
- let profile: string
726
- if (shell.endsWith("/zsh")) {
727
- profile = path.join(home, ".zshrc")
728
- } else if (shell.endsWith("/bash")) {
729
- profile = path.join(home, ".bashrc")
730
- } else {
731
- profile = path.join(home, ".profile")
732
- }
733
-
734
- const exportLine = `export AGENTIKIT_STASH_DIR="${stashDir}"`
735
- const existing = fs.existsSync(profile) ? fs.readFileSync(profile, "utf8") : ""
736
- if (!existing.includes("AGENTIKIT_STASH_DIR")) {
737
- fs.appendFileSync(profile, `\n# Agentikit stash directory\n${exportLine}\n`)
738
- envSet = true
739
- profileUpdated = profile
740
- }
741
- }
742
-
743
- process.env.AGENTIKIT_STASH_DIR = stashDir
744
-
745
- // Ensure ripgrep is available (install to stash/bin if needed)
746
- let ripgrep: InitResponse["ripgrep"]
747
- try {
748
- const rgResult = ensureRg(stashDir)
749
- ripgrep = rgResult
750
- } catch {
751
- // Non-fatal: ripgrep is optional, search works without it
752
- }
753
-
754
- return { stashDir, created, envSet, profileUpdated, ripgrep }
755
- }
756
-
757
- function hasErrnoCode(error: unknown, code: string): boolean {
758
- if (typeof error !== "object" || error === null || !("code" in error)) return false
759
- return (error as Record<string, unknown>).code === code
760
- }
1
+ export type { AgentikitAssetType } from "./common"
2
+ export { resolveStashDir } from "./common"
3
+ export { agentikitInit } from "./init"
4
+ export type { InitResponse } from "./init"
5
+ export type { ToolKind } from "./tool-runner"
6
+
7
+ export { agentikitSearch } from "./stash-search"
8
+ export { agentikitShow } from "./stash-show"
9
+ export { agentikitAdd } from "./stash-add"
10
+ export { agentikitList, agentikitRemove, agentikitReinstall, agentikitUpdate } from "./stash-registry"
11
+
12
+ export type {
13
+ AddResponse,
14
+ AgentikitSearchType,
15
+ LocalSearchHit,
16
+ RegistrySearchResultHit,
17
+ SearchSource,
18
+ SearchUsageMode,
19
+ SearchHit,
20
+ SearchResponse,
21
+ ShowResponse,
22
+ KnowledgeView,
23
+ ListResponse,
24
+ RemoveResponse,
25
+ ReinstallResponse,
26
+ UpdateResponse,
27
+ RegistryListEntry,
28
+ RegistryInstallStatus,
29
+ ReinstallResultItem,
30
+ UpdateResultItem,
31
+ } from "./stash-types"