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
@@ -0,0 +1,72 @@
1
+ import fs from "node:fs"
2
+ import path from "node:path"
3
+ import { IS_WINDOWS } from "./common"
4
+
5
+ export const RG_BINARY = IS_WINDOWS ? "rg.exe" : "rg"
6
+
7
+ function canExecute(filePath: string): boolean {
8
+ if (!fs.existsSync(filePath)) return false
9
+ if (IS_WINDOWS) return true
10
+ try {
11
+ fs.accessSync(filePath, fs.constants.X_OK)
12
+ return true
13
+ } catch {
14
+ return false
15
+ }
16
+ }
17
+
18
+ function resolveFromPath(): string | null {
19
+ const rawPath = process.env.PATH
20
+ if (!rawPath) return null
21
+
22
+ const pathEntries = rawPath.split(path.delimiter).filter(Boolean)
23
+
24
+ if (IS_WINDOWS) {
25
+ const pathext = (process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM")
26
+ .split(";")
27
+ .filter(Boolean)
28
+ .map((ext) => ext.toLowerCase())
29
+
30
+ for (const entry of pathEntries) {
31
+ const directCandidate = path.join(entry, "rg")
32
+ if (canExecute(directCandidate)) return directCandidate
33
+
34
+ for (const ext of pathext) {
35
+ const candidate = path.join(entry, `rg${ext}`)
36
+ if (canExecute(candidate)) return candidate
37
+ }
38
+ }
39
+ return null
40
+ }
41
+
42
+ for (const entry of pathEntries) {
43
+ const candidate = path.join(entry, "rg")
44
+ if (canExecute(candidate)) return candidate
45
+ }
46
+
47
+ return null
48
+ }
49
+
50
+ /**
51
+ * Resolve the path to a usable ripgrep binary.
52
+ * Checks in order:
53
+ * 1. stashDir/bin/rg
54
+ * 2. system PATH (rg)
55
+ * Returns null if ripgrep is not available.
56
+ */
57
+ export function resolveRg(stashDir?: string): string | null {
58
+ // Check stash bin directory first
59
+ if (stashDir) {
60
+ const stashRg = path.join(stashDir, "bin", RG_BINARY)
61
+ if (canExecute(stashRg)) return stashRg
62
+ }
63
+
64
+ return resolveFromPath()
65
+ }
66
+
67
+ /**
68
+ * Check if ripgrep is available (either in stash/bin or system PATH).
69
+ */
70
+ export function isRgAvailable(stashDir?: string): boolean {
71
+ return resolveRg(stashDir) !== null
72
+ }
package/src/ripgrep.ts CHANGED
@@ -1,315 +1,3 @@
1
- import { spawnSync } from "node:child_process"
2
- import fs from "node:fs"
3
- import path from "node:path"
4
-
5
- // ── ripgrep Resolution ──────────────────────────────────────────────────────
6
-
7
- const IS_WINDOWS = process.platform === "win32"
8
- const RG_BINARY = IS_WINDOWS ? "rg.exe" : "rg"
9
-
10
- function canExecute(filePath: string): boolean {
11
- if (!fs.existsSync(filePath)) return false
12
- if (IS_WINDOWS) return true
13
- try {
14
- fs.accessSync(filePath, fs.constants.X_OK)
15
- return true
16
- } catch {
17
- return false
18
- }
19
- }
20
-
21
- function resolveFromPath(): string | null {
22
- const rawPath = process.env.PATH
23
- if (!rawPath) return null
24
-
25
- const pathEntries = rawPath.split(path.delimiter).filter(Boolean)
26
-
27
- if (IS_WINDOWS) {
28
- const pathext = (process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM")
29
- .split(";")
30
- .filter(Boolean)
31
- .map((ext) => ext.toLowerCase())
32
-
33
- for (const entry of pathEntries) {
34
- const directCandidate = path.join(entry, "rg")
35
- if (canExecute(directCandidate)) return directCandidate
36
-
37
- for (const ext of pathext) {
38
- const candidate = path.join(entry, `rg${ext}`)
39
- if (canExecute(candidate)) return candidate
40
- }
41
- }
42
- return null
43
- }
44
-
45
- for (const entry of pathEntries) {
46
- const candidate = path.join(entry, "rg")
47
- if (canExecute(candidate)) return candidate
48
- }
49
-
50
- return null
51
- }
52
-
53
- /**
54
- * Resolve the path to a usable ripgrep binary.
55
- * Checks in order:
56
- * 1. stashDir/bin/rg
57
- * 2. system PATH (rg)
58
- * Returns null if ripgrep is not available.
59
- */
60
- export function resolveRg(stashDir?: string): string | null {
61
- // Check stash bin directory first
62
- if (stashDir) {
63
- const stashRg = path.join(stashDir, "bin", RG_BINARY)
64
- if (canExecute(stashRg)) return stashRg
65
- }
66
-
67
- return resolveFromPath()
68
- }
69
-
70
- /**
71
- * Check if ripgrep is available (either in stash/bin or system PATH).
72
- */
73
- export function isRgAvailable(stashDir?: string): boolean {
74
- return resolveRg(stashDir) !== null
75
- }
76
-
77
- // ── ripgrep Candidate Filtering ─────────────────────────────────────────────
78
-
79
- export interface RgCandidateResult {
80
- matchedFiles: string[]
81
- usedRg: boolean
82
- }
83
-
84
- /**
85
- * Use ripgrep to find .stash.json files that match query tokens.
86
- * Returns paths to matching .stash.json files.
87
- *
88
- * If ripgrep is not available or the query is empty, returns null
89
- * to signal that the caller should skip pre-filtering.
90
- */
91
- export function rgFilterCandidates(
92
- query: string,
93
- searchDir: string,
94
- stashDir?: string,
95
- ): RgCandidateResult | null {
96
- if (!query.trim()) return null
97
-
98
- const rgPath = resolveRg(stashDir)
99
- if (!rgPath) return null
100
-
101
- // Tokenize the query into an OR pattern for ripgrep
102
- const tokens = query
103
- .toLowerCase()
104
- .replace(/[^a-z0-9\s]/g, " ")
105
- .split(/\s+/)
106
- .filter((t) => t.length > 1)
107
-
108
- if (tokens.length === 0) return null
109
-
110
- const pattern = tokens.join("|")
111
-
112
- const result = spawnSync(rgPath, [
113
- "-i", // case insensitive
114
- "-l", // files-with-matches only
115
- "--hidden", // include hidden files such as .stash.json
116
- "--no-ignore", // include ignored files to ensure metadata is searchable
117
- "--glob", ".stash.json", // only search .stash.json files
118
- pattern,
119
- searchDir,
120
- ], {
121
- encoding: "utf8",
122
- timeout: 10_000,
123
- })
124
-
125
- if (result.status !== 0 && result.status !== 1) {
126
- // rg exit code 1 = no matches (normal), anything else = error
127
- return null
128
- }
129
-
130
- const files = (result.stdout || "")
131
- .trim()
132
- .split(/\r?\n/)
133
- .filter((f) => f.length > 0)
134
-
135
- return { matchedFiles: files, usedRg: true }
136
- }
137
-
138
- // ── ripgrep Installation ────────────────────────────────────────────────────
139
-
140
- /**
141
- * Platform and architecture detection for ripgrep binary downloads.
142
- */
143
- function getRgPlatformTarget(): { platform: string; arch: string; ext: string } | null {
144
- const platform = process.platform
145
- const arch = process.arch
146
-
147
- if (platform === "linux" && arch === "x64") {
148
- return { platform: "x86_64-unknown-linux-musl", arch: "x64", ext: ".tar.gz" }
149
- }
150
- if (platform === "linux" && arch === "arm64") {
151
- return { platform: "aarch64-unknown-linux-gnu", arch: "arm64", ext: ".tar.gz" }
152
- }
153
- if (platform === "darwin" && arch === "x64") {
154
- return { platform: "x86_64-apple-darwin", arch: "x64", ext: ".tar.gz" }
155
- }
156
- if (platform === "darwin" && arch === "arm64") {
157
- return { platform: "aarch64-apple-darwin", arch: "arm64", ext: ".tar.gz" }
158
- }
159
- if (platform === "win32" && arch === "x64") {
160
- return { platform: "x86_64-pc-windows-msvc", arch: "x64", ext: ".zip" }
161
- }
162
-
163
- return null
164
- }
165
-
166
- const RG_VERSION = "14.1.1"
167
-
168
- export interface EnsureRgResult {
169
- rgPath: string
170
- installed: boolean
171
- version: string
172
- }
173
-
174
- /**
175
- * Ensure ripgrep is available. If not found on PATH or in stash/bin,
176
- * download and install it to stash/bin.
177
- *
178
- * Returns the path to the ripgrep binary and whether it was newly installed.
179
- */
180
- export function ensureRg(stashDir: string): EnsureRgResult {
181
- // Already available?
182
- const existing = resolveRg(stashDir)
183
- if (existing) {
184
- return { rgPath: existing, installed: false, version: getRgVersion(existing) }
185
- }
186
-
187
- // Determine platform
188
- const target = getRgPlatformTarget()
189
- if (!target) {
190
- throw new Error(
191
- `Unsupported platform for ripgrep auto-install: ${process.platform}/${process.arch}. ` +
192
- `Install ripgrep manually: https://github.com/BurntSushi/ripgrep#installation`
193
- )
194
- }
195
-
196
- const binDir = path.join(stashDir, "bin")
197
- if (!fs.existsSync(binDir)) {
198
- fs.mkdirSync(binDir, { recursive: true })
199
- }
200
-
201
- const archiveName = `ripgrep-${RG_VERSION}-${target.platform}`
202
- const url = `https://github.com/BurntSushi/ripgrep/releases/download/${RG_VERSION}/${archiveName}${target.ext}`
203
- const destBinary = path.join(binDir, RG_BINARY)
204
-
205
- if (target.ext === ".tar.gz") {
206
- downloadAndExtractTarGz(url, archiveName, destBinary)
207
- } else {
208
- downloadAndExtractZip(url, archiveName, destBinary)
209
- }
210
-
211
- // Make executable
212
- if (!IS_WINDOWS) {
213
- fs.chmodSync(destBinary, 0o755)
214
- }
215
-
216
- return { rgPath: destBinary, installed: true, version: RG_VERSION }
217
- }
218
-
219
- function downloadAndExtractTarGz(url: string, archiveName: string, destBinary: string): void {
220
- const destDir = path.dirname(destBinary)
221
- const tmpTarGz = path.join(destDir, "rg-download.tar.gz")
222
-
223
- try {
224
- // Download archive to a temporary file without using a shell
225
- const curlResult = spawnSync(
226
- "curl",
227
- ["-fsSL", "-o", tmpTarGz, url],
228
- {
229
- encoding: "utf8",
230
- timeout: 60_000,
231
- }
232
- )
233
-
234
- if (curlResult.status !== 0) {
235
- const err = curlResult.stderr?.trim() || curlResult.error?.message || "unknown error"
236
- throw new Error(`Failed to download ripgrep from ${url}: ${err}`)
237
- }
238
-
239
- // Extract the specific binary from the archive into destDir
240
- const tarResult = spawnSync(
241
- "tar",
242
- [
243
- "xzf",
244
- tmpTarGz,
245
- "--strip-components=1",
246
- "-C",
247
- destDir,
248
- `${archiveName}/rg`,
249
- ],
250
- {
251
- encoding: "utf8",
252
- timeout: 60_000,
253
- }
254
- )
255
-
256
- if (tarResult.status !== 0) {
257
- const err = tarResult.stderr?.trim() || tarResult.error?.message || "unknown error"
258
- throw new Error(`Failed to extract ripgrep from ${url}: ${err}`)
259
- }
260
-
261
- if (!fs.existsSync(destBinary)) {
262
- throw new Error(`ripgrep binary not found at ${destBinary} after extraction`)
263
- }
264
- } finally {
265
- // Best-effort cleanup of temporary archive
266
- try {
267
- if (fs.existsSync(tmpTarGz)) {
268
- fs.unlinkSync(tmpTarGz)
269
- }
270
- } catch {
271
- // ignore cleanup errors
272
- }
273
- }
274
- }
275
-
276
- function downloadAndExtractZip(url: string, archiveName: string, destBinary: string): void {
277
- const destDir = path.dirname(destBinary)
278
- const tmpZip = path.join(destDir, "rg-download.zip")
279
- const expandedDir = path.join(destDir, archiveName)
280
- try {
281
- // Download
282
- const dlResult = spawnSync("curl", ["-fsSL", "-o", tmpZip, url], {
283
- encoding: "utf8",
284
- timeout: 60_000,
285
- })
286
- if (dlResult.status !== 0) {
287
- throw new Error(dlResult.stderr?.trim() || "download failed")
288
- }
289
-
290
- // Extract just the rg.exe
291
- const extractResult = spawnSync("powershell", [
292
- "-Command",
293
- `Expand-Archive -Path "${tmpZip}" -DestinationPath "${destDir}" -Force; ` +
294
- `Move-Item -Force "${path.join(destDir, archiveName, "rg.exe")}" "${destBinary}"`,
295
- ], {
296
- encoding: "utf8",
297
- timeout: 60_000,
298
- })
299
- if (extractResult.status !== 0) {
300
- throw new Error(extractResult.stderr?.trim() || "extraction failed")
301
- }
302
- } finally {
303
- if (fs.existsSync(tmpZip)) fs.unlinkSync(tmpZip)
304
- if (fs.existsSync(expandedDir)) fs.rmSync(expandedDir, { recursive: true, force: true })
305
- }
306
- }
307
-
308
- function getRgVersion(rgPath: string): string {
309
- const result = spawnSync(rgPath, ["--version"], { encoding: "utf8", timeout: 5_000 })
310
- if (result.status === 0 && result.stdout) {
311
- const match = result.stdout.match(/ripgrep\s+([\d.]+)/)
312
- return match ? match[1] : "unknown"
313
- }
314
- return "unknown"
315
- }
1
+ export { resolveRg, isRgAvailable } from "./ripgrep-resolve"
2
+ export { ensureRg } from "./ripgrep-install"
3
+ export type { EnsureRgResult } from "./ripgrep-install"
package/src/similarity.ts CHANGED
@@ -28,7 +28,7 @@ interface TfIdfDocument {
28
28
  magnitude: number
29
29
  }
30
30
 
31
- interface SerializedTfIdf {
31
+ export interface SerializedTfIdf {
32
32
  idf: Record<string, number>
33
33
  docs: Array<{
34
34
  id: string
@@ -147,6 +147,18 @@ export class TfIdfAdapter implements SearchAdapter {
147
147
  }
148
148
  }
149
149
 
150
+ // Boost: intent phrase contains query token
151
+ const intents = doc.entry.entry.intents || []
152
+ for (const intent of intents) {
153
+ const intentLower = intent.toLowerCase()
154
+ for (const token of queryTokens) {
155
+ if (intentLower.includes(token)) {
156
+ score += 0.12
157
+ break // one boost per intent phrase
158
+ }
159
+ }
160
+ }
161
+
150
162
  // Boost: name contains query token
151
163
  const nameLower = doc.entry.entry.name.toLowerCase().replace(/[-_]/g, " ")
152
164
  for (const token of queryTokens) {
@@ -0,0 +1,66 @@
1
+ import { agentikitIndex } from "./indexer"
2
+ import fs from "node:fs"
3
+ import { resolveStashDir } from "./common"
4
+ import { loadConfig } from "./config"
5
+ import { upsertInstalledRegistryEntry, installRegistryRef } from "./registry-install"
6
+ import type { AddResponse } from "./stash-types"
7
+
8
+ export async function agentikitAdd(input: { ref: string }): Promise<AddResponse> {
9
+ const ref = input.ref.trim()
10
+ if (!ref) throw new Error("Registry ref is required.")
11
+
12
+ const stashDir = resolveStashDir()
13
+ const installed = await installRegistryRef(ref)
14
+ const replaced = loadConfig(stashDir).registry?.installed.find((entry) => entry.id === installed.id)
15
+ const config = upsertInstalledRegistryEntry(
16
+ {
17
+ id: installed.id,
18
+ source: installed.source,
19
+ ref: installed.ref,
20
+ artifactUrl: installed.artifactUrl,
21
+ resolvedVersion: installed.resolvedVersion,
22
+ resolvedRevision: installed.resolvedRevision,
23
+ stashRoot: installed.stashRoot,
24
+ cacheDir: installed.cacheDir,
25
+ installedAt: installed.installedAt,
26
+ },
27
+ stashDir,
28
+ )
29
+
30
+ if (replaced && replaced.cacheDir !== installed.cacheDir) {
31
+ try {
32
+ fs.rmSync(replaced.cacheDir, { recursive: true, force: true })
33
+ } catch {
34
+ // Best-effort cleanup only.
35
+ }
36
+ }
37
+
38
+ const index = await agentikitIndex({ stashDir })
39
+
40
+ return {
41
+ stashDir,
42
+ ref,
43
+ installed: {
44
+ id: installed.id,
45
+ source: installed.source,
46
+ ref: installed.ref,
47
+ artifactUrl: installed.artifactUrl,
48
+ resolvedVersion: installed.resolvedVersion,
49
+ resolvedRevision: installed.resolvedRevision,
50
+ stashRoot: installed.stashRoot,
51
+ cacheDir: installed.cacheDir,
52
+ extractedDir: installed.extractedDir,
53
+ installedAt: installed.installedAt,
54
+ },
55
+ config: {
56
+ additionalStashDirs: config.additionalStashDirs,
57
+ installedRegistryCount: config.registry?.installed.length ?? 0,
58
+ },
59
+ index: {
60
+ mode: index.mode,
61
+ totalEntries: index.totalEntries,
62
+ directoriesScanned: index.directoriesScanned,
63
+ directoriesSkipped: index.directoriesSkipped,
64
+ },
65
+ }
66
+ }
@@ -0,0 +1,41 @@
1
+ import path from "node:path"
2
+ import { type AgentikitAssetType, isAssetType } from "./common"
3
+
4
+ export interface OpenRef {
5
+ type: AgentikitAssetType
6
+ name: string
7
+ }
8
+
9
+ export function parseOpenRef(ref: string): OpenRef {
10
+ const separator = ref.indexOf(":")
11
+ if (separator <= 0) {
12
+ throw new Error("Invalid open ref. Expected format '<type>:<name>'.")
13
+ }
14
+ const rawType = ref.slice(0, separator)
15
+ const rawName = ref.slice(separator + 1)
16
+ if (!isAssetType(rawType)) {
17
+ throw new Error(`Invalid open ref type: "${rawType}".`)
18
+ }
19
+ let name: string
20
+ try {
21
+ name = decodeURIComponent(rawName)
22
+ } catch {
23
+ throw new Error("Invalid open ref encoding.")
24
+ }
25
+ const normalized = path.posix.normalize(name.replace(/\\/g, "/"))
26
+ if (
27
+ !name
28
+ || name.includes("\0")
29
+ || /^[A-Za-z]:/.test(name)
30
+ || path.posix.isAbsolute(normalized)
31
+ || normalized === ".."
32
+ || normalized.startsWith("../")
33
+ ) {
34
+ throw new Error("Invalid open ref name.")
35
+ }
36
+ return { type: rawType, name: normalized }
37
+ }
38
+
39
+ export function makeOpenRef(type: AgentikitAssetType, name: string): string {
40
+ return `${type}:${encodeURIComponent(name)}`
41
+ }