agentikit 0.0.13 → 0.0.15

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 (156) hide show
  1. package/LICENSE +385 -0
  2. package/README.md +187 -110
  3. package/dist/{src/asset-spec.js → asset-spec.js} +11 -2
  4. package/dist/{src/asset-type-handler.js → asset-type-handler.js} +4 -3
  5. package/dist/cli.js +709 -0
  6. package/dist/common.js +192 -0
  7. package/dist/{src/config-cli.js → config-cli.js} +36 -30
  8. package/dist/{src/config.js → config.js} +95 -25
  9. package/dist/{src/db.js → db.js} +123 -51
  10. package/dist/{src/embedder.js → embedder.js} +57 -2
  11. package/dist/errors.js +28 -0
  12. package/dist/file-context.js +188 -0
  13. package/dist/{src/frontmatter.js → frontmatter.js} +1 -1
  14. package/dist/{src/github.js → github.js} +1 -3
  15. package/dist/handlers/agent-handler.js +19 -0
  16. package/dist/handlers/command-handler.js +20 -0
  17. package/dist/handlers/handler-bridge.js +51 -0
  18. package/dist/handlers/index.js +19 -0
  19. package/dist/handlers/knowledge-handler.js +32 -0
  20. package/dist/handlers/script-handler.js +42 -0
  21. package/dist/{src/handlers → handlers}/skill-handler.js +5 -6
  22. package/dist/{src/handlers → handlers}/tool-handler.js +8 -24
  23. package/dist/{src/indexer.js → indexer.js} +50 -26
  24. package/dist/init.js +43 -0
  25. package/dist/{src/llm.js → llm.js} +6 -11
  26. package/dist/lockfile.js +60 -0
  27. package/dist/matchers.js +163 -0
  28. package/dist/{src/metadata.js → metadata.js} +36 -16
  29. package/dist/{src/origin-resolve.js → origin-resolve.js} +10 -9
  30. package/dist/paths.js +83 -0
  31. package/dist/{src/registry-install.js → registry-install.js} +151 -19
  32. package/dist/{src/registry-resolve.js → registry-resolve.js} +190 -26
  33. package/dist/{src/registry-search.js → registry-search.js} +13 -21
  34. package/dist/renderers.js +286 -0
  35. package/dist/{src/ripgrep-install.js → ripgrep-install.js} +8 -27
  36. package/dist/{src/ripgrep-resolve.js → ripgrep-resolve.js} +21 -11
  37. package/dist/ripgrep.js +2 -0
  38. package/dist/self-update.js +226 -0
  39. package/dist/{src/stash-add.js → stash-add.js} +14 -4
  40. package/dist/stash-clone.js +115 -0
  41. package/dist/{src/stash-ref.js → stash-ref.js} +10 -9
  42. package/dist/{src/stash-registry.js → stash-registry.js} +21 -46
  43. package/dist/{src/stash-resolve.js → stash-resolve.js} +10 -9
  44. package/dist/{src/stash-search.js → stash-search.js} +89 -74
  45. package/dist/stash-show.js +74 -0
  46. package/dist/stash-source.js +127 -0
  47. package/dist/submit.js +557 -0
  48. package/dist/{src/tool-runner.js → tool-runner.js} +1 -5
  49. package/dist/{src/walker.js → walker.js} +38 -0
  50. package/dist/warn.js +20 -0
  51. package/package.json +13 -18
  52. package/dist/index.d.ts +0 -28
  53. package/dist/index.js +0 -15
  54. package/dist/src/asset-spec.d.ts +0 -16
  55. package/dist/src/asset-type-handler.d.ts +0 -27
  56. package/dist/src/cli.d.ts +0 -2
  57. package/dist/src/cli.js +0 -399
  58. package/dist/src/common.d.ts +0 -13
  59. package/dist/src/common.js +0 -60
  60. package/dist/src/config-cli.d.ts +0 -9
  61. package/dist/src/config.d.ts +0 -50
  62. package/dist/src/db.d.ts +0 -46
  63. package/dist/src/embedder.d.ts +0 -10
  64. package/dist/src/frontmatter.d.ts +0 -30
  65. package/dist/src/github.d.ts +0 -4
  66. package/dist/src/handlers/agent-handler.d.ts +0 -2
  67. package/dist/src/handlers/agent-handler.js +0 -26
  68. package/dist/src/handlers/command-handler.d.ts +0 -2
  69. package/dist/src/handlers/command-handler.js +0 -23
  70. package/dist/src/handlers/index.d.ts +0 -6
  71. package/dist/src/handlers/index.js +0 -23
  72. package/dist/src/handlers/knowledge-handler.d.ts +0 -2
  73. package/dist/src/handlers/knowledge-handler.js +0 -56
  74. package/dist/src/handlers/markdown-helpers.d.ts +0 -7
  75. package/dist/src/handlers/script-handler.d.ts +0 -2
  76. package/dist/src/handlers/script-handler.js +0 -78
  77. package/dist/src/handlers/skill-handler.d.ts +0 -2
  78. package/dist/src/handlers/tool-handler.d.ts +0 -2
  79. package/dist/src/indexer.d.ts +0 -22
  80. package/dist/src/init.d.ts +0 -19
  81. package/dist/src/init.js +0 -99
  82. package/dist/src/llm.d.ts +0 -15
  83. package/dist/src/markdown.d.ts +0 -18
  84. package/dist/src/metadata.d.ts +0 -41
  85. package/dist/src/origin-resolve.d.ts +0 -19
  86. package/dist/src/registry-install.d.ts +0 -11
  87. package/dist/src/registry-resolve.d.ts +0 -3
  88. package/dist/src/registry-search.d.ts +0 -27
  89. package/dist/src/registry-types.d.ts +0 -62
  90. package/dist/src/ripgrep-install.d.ts +0 -12
  91. package/dist/src/ripgrep-resolve.d.ts +0 -13
  92. package/dist/src/ripgrep.d.ts +0 -3
  93. package/dist/src/ripgrep.js +0 -2
  94. package/dist/src/stash-add.d.ts +0 -4
  95. package/dist/src/stash-clone.d.ts +0 -22
  96. package/dist/src/stash-clone.js +0 -83
  97. package/dist/src/stash-ref.d.ts +0 -31
  98. package/dist/src/stash-registry.d.ts +0 -18
  99. package/dist/src/stash-resolve.d.ts +0 -2
  100. package/dist/src/stash-search.d.ts +0 -8
  101. package/dist/src/stash-show.d.ts +0 -5
  102. package/dist/src/stash-show.js +0 -46
  103. package/dist/src/stash-source.d.ts +0 -24
  104. package/dist/src/stash-source.js +0 -81
  105. package/dist/src/stash-types.d.ts +0 -227
  106. package/dist/src/stash.d.ts +0 -16
  107. package/dist/src/stash.js +0 -9
  108. package/dist/src/tool-runner.d.ts +0 -35
  109. package/dist/src/walker.d.ts +0 -19
  110. package/src/asset-spec.ts +0 -85
  111. package/src/asset-type-handler.ts +0 -77
  112. package/src/cli.ts +0 -427
  113. package/src/common.ts +0 -76
  114. package/src/config-cli.ts +0 -499
  115. package/src/config.ts +0 -305
  116. package/src/db.ts +0 -411
  117. package/src/embedder.ts +0 -128
  118. package/src/frontmatter.ts +0 -95
  119. package/src/github.ts +0 -21
  120. package/src/handlers/agent-handler.ts +0 -32
  121. package/src/handlers/command-handler.ts +0 -29
  122. package/src/handlers/index.ts +0 -25
  123. package/src/handlers/knowledge-handler.ts +0 -62
  124. package/src/handlers/markdown-helpers.ts +0 -19
  125. package/src/handlers/script-handler.ts +0 -92
  126. package/src/handlers/skill-handler.ts +0 -37
  127. package/src/handlers/tool-handler.ts +0 -71
  128. package/src/indexer.ts +0 -392
  129. package/src/init.ts +0 -114
  130. package/src/llm.ts +0 -125
  131. package/src/markdown.ts +0 -106
  132. package/src/metadata.ts +0 -333
  133. package/src/origin-resolve.ts +0 -67
  134. package/src/registry-install.ts +0 -361
  135. package/src/registry-resolve.ts +0 -341
  136. package/src/registry-search.ts +0 -335
  137. package/src/registry-types.ts +0 -72
  138. package/src/ripgrep-install.ts +0 -200
  139. package/src/ripgrep-resolve.ts +0 -72
  140. package/src/ripgrep.ts +0 -3
  141. package/src/stash-add.ts +0 -63
  142. package/src/stash-clone.ts +0 -127
  143. package/src/stash-ref.ts +0 -99
  144. package/src/stash-registry.ts +0 -259
  145. package/src/stash-resolve.ts +0 -50
  146. package/src/stash-search.ts +0 -613
  147. package/src/stash-show.ts +0 -55
  148. package/src/stash-source.ts +0 -103
  149. package/src/stash-types.ts +0 -231
  150. package/src/stash.ts +0 -39
  151. package/src/tool-runner.ts +0 -142
  152. package/src/walker.ts +0 -53
  153. /package/dist/{src/handlers → handlers}/markdown-helpers.js +0 -0
  154. /package/dist/{src/markdown.js → markdown.js} +0 -0
  155. /package/dist/{src/registry-types.js → registry-types.js} +0 -0
  156. /package/dist/{src/stash-types.js → stash-types.js} +0 -0
@@ -0,0 +1,286 @@
1
+ /**
2
+ * Built-in asset renderers.
3
+ *
4
+ * Each renderer mirrors the show/search/metadata behavior of its corresponding
5
+ * legacy AssetTypeHandler, re-expressed against the AssetRenderer interface
6
+ * from ./file-context. Renderers are registered at module-load time so that
7
+ * importing this module is sufficient to make them available.
8
+ */
9
+ import path from "node:path";
10
+ import { SCRIPT_EXTENSIONS } from "./asset-spec";
11
+ import { hasErrnoCode } from "./common";
12
+ import { registerRenderer } from "./file-context";
13
+ import { parseFrontmatter, toStringOrUndefined } from "./frontmatter";
14
+ import { extractFrontmatterOnly, extractLineRange, extractSection, formatToc, parseMarkdownToc } from "./markdown";
15
+ import { extractDescriptionFromComments } from "./metadata";
16
+ import { buildToolInfo } from "./tool-runner";
17
+ // ── Helpers ──────────────────────────────────────────────────────────────────
18
+ /**
19
+ * Derive a display name from the RenderContext.
20
+ *
21
+ * Prefers `matchResult.meta.name` when present; otherwise falls back to the
22
+ * POSIX-style relative path stripped of its extension.
23
+ */
24
+ function deriveName(ctx) {
25
+ const metaName = ctx.matchResult.meta?.name;
26
+ if (typeof metaName === "string" && metaName)
27
+ return metaName;
28
+ // Strip the extension from the relPath for a reasonable fallback.
29
+ const ext = path.extname(ctx.relPath);
30
+ return ext ? ctx.relPath.slice(0, -ext.length) : ctx.relPath;
31
+ }
32
+ /**
33
+ * Find the stashDir that contains `filePath`, falling back to the first
34
+ * entry in the array when no prefix match is found.
35
+ */
36
+ function findContainingStashDir(stashDirs, filePath) {
37
+ return stashDirs.find((d) => path.resolve(filePath).startsWith(path.resolve(d) + path.sep)) ?? stashDirs[0];
38
+ }
39
+ // ── 1. tool-script ───────────────────────────────────────────────────────────
40
+ const toolScriptRenderer = {
41
+ name: "tool-script",
42
+ buildShowResponse(ctx) {
43
+ const name = deriveName(ctx);
44
+ const stashDirs = ctx.stashDirs;
45
+ const assetStashDir = findContainingStashDir(stashDirs, ctx.absPath);
46
+ if (!assetStashDir) {
47
+ return { type: "tool", name, path: ctx.absPath, content: ctx.content() };
48
+ }
49
+ const toolInfo = buildToolInfo(assetStashDir, ctx.absPath);
50
+ return {
51
+ type: "tool",
52
+ name,
53
+ path: ctx.absPath,
54
+ runCmd: toolInfo.runCmd,
55
+ kind: toolInfo.kind,
56
+ };
57
+ },
58
+ enrichSearchHit(hit, stashDir) {
59
+ try {
60
+ const toolInfo = buildToolInfo(stashDir, hit.path);
61
+ hit.runCmd = toolInfo.runCmd;
62
+ hit.kind = toolInfo.kind;
63
+ }
64
+ catch (error) {
65
+ if (!hasErrnoCode(error, "ENOENT"))
66
+ throw error;
67
+ }
68
+ },
69
+ extractMetadata(entry, ctx) {
70
+ if (SCRIPT_EXTENSIONS.has(ctx.ext) && ctx.ext !== ".md") {
71
+ const commentDesc = extractDescriptionFromComments(ctx.absPath);
72
+ if (commentDesc && !entry.description) {
73
+ entry.description = commentDesc;
74
+ entry.source = "comments";
75
+ entry.confidence = 0.7;
76
+ }
77
+ }
78
+ },
79
+ usageGuide: [
80
+ "Use the hit's runCmd for execution so runtime and working directory stay correct.",
81
+ "Use `akm show <openRef>` to inspect the tool before running it.",
82
+ ],
83
+ };
84
+ // ── 2. skill-md ──────────────────────────────────────────────────────────────
85
+ const skillMdRenderer = {
86
+ name: "skill-md",
87
+ buildShowResponse(ctx) {
88
+ const name = deriveName(ctx);
89
+ return {
90
+ type: "skill",
91
+ name,
92
+ path: ctx.absPath,
93
+ content: ctx.content(),
94
+ };
95
+ },
96
+ usageGuide: [
97
+ "Read and apply the skill instructions as written, then adapt examples to your current repo state and task.",
98
+ "Use `akm show <openRef>` to read the full SKILL.md for required steps and constraints.",
99
+ ],
100
+ };
101
+ // ── 3. command-md ────────────────────────────────────────────────────────────
102
+ const commandMdRenderer = {
103
+ name: "command-md",
104
+ buildShowResponse(ctx) {
105
+ const name = deriveName(ctx);
106
+ const parsedMd = parseFrontmatter(ctx.content());
107
+ return {
108
+ type: "command",
109
+ name,
110
+ path: ctx.absPath,
111
+ description: toStringOrUndefined(parsedMd.data.description),
112
+ template: parsedMd.content,
113
+ modelHint: parsedMd.data.model,
114
+ agent: toStringOrUndefined(parsedMd.data.agent),
115
+ };
116
+ },
117
+ usageGuide: [
118
+ "Read the .md file, fill $ARGUMENTS placeholders, and run it in the current repo context.",
119
+ "Use `akm show <openRef>` to retrieve the command template body.",
120
+ "When `agent` is specified, dispatch the command to that agent.",
121
+ ],
122
+ };
123
+ // ── 4. agent-md ──────────────────────────────────────────────────────────────
124
+ const agentMdRenderer = {
125
+ name: "agent-md",
126
+ buildShowResponse(ctx) {
127
+ const name = deriveName(ctx);
128
+ const parsedMd = parseFrontmatter(ctx.content());
129
+ return {
130
+ type: "agent",
131
+ name,
132
+ path: ctx.absPath,
133
+ description: toStringOrUndefined(parsedMd.data.description),
134
+ prompt: "Dispatching prompt must include the agent's full prompt content verbatim; summaries are non-compliant. \n\n" +
135
+ parsedMd.content,
136
+ toolPolicy: parsedMd.data.tools,
137
+ modelHint: parsedMd.data.model,
138
+ };
139
+ },
140
+ usageGuide: [
141
+ "Read the .md file and dispatch an agent using the content of the file. Use modelHint/toolPolicy when present to run the agent with compatible settings.",
142
+ "Use with `akm show <openRef>` to get the full prompt payload.",
143
+ ],
144
+ };
145
+ // ── 5. knowledge-md ──────────────────────────────────────────────────────────
146
+ const knowledgeMdRenderer = {
147
+ name: "knowledge-md",
148
+ buildShowResponse(ctx) {
149
+ const name = deriveName(ctx);
150
+ const v = ctx.matchResult.meta?.view ?? { mode: "full" };
151
+ const content = ctx.content();
152
+ switch (v.mode) {
153
+ case "toc": {
154
+ const toc = parseMarkdownToc(content);
155
+ return { type: "knowledge", name, path: ctx.absPath, content: formatToc(toc) };
156
+ }
157
+ case "frontmatter": {
158
+ const fm = extractFrontmatterOnly(content);
159
+ return { type: "knowledge", name, path: ctx.absPath, content: fm ?? "(no frontmatter)" };
160
+ }
161
+ case "section": {
162
+ const section = extractSection(content, v.heading);
163
+ if (!section) {
164
+ return {
165
+ type: "knowledge",
166
+ name,
167
+ path: ctx.absPath,
168
+ content: `Section "${v.heading}" not found in ${name}. Try --view toc to discover available headings.`,
169
+ };
170
+ }
171
+ return { type: "knowledge", name, path: ctx.absPath, content: section.content };
172
+ }
173
+ case "lines": {
174
+ return {
175
+ type: "knowledge",
176
+ name,
177
+ path: ctx.absPath,
178
+ content: extractLineRange(content, v.start, v.end),
179
+ };
180
+ }
181
+ default: {
182
+ return { type: "knowledge", name, path: ctx.absPath, content };
183
+ }
184
+ }
185
+ },
186
+ extractMetadata(entry, ctx) {
187
+ try {
188
+ const toc = parseMarkdownToc(ctx.content());
189
+ if (toc.headings.length > 0)
190
+ entry.toc = toc.headings;
191
+ }
192
+ catch {
193
+ // Non-fatal: skip TOC if file can't be read
194
+ }
195
+ },
196
+ usageGuide: [
197
+ "Use `akm show <openRef>` to read the document; start with `--view toc` for large files.",
198
+ "Use `--view section` or `--view lines` to load only the part you need.",
199
+ ],
200
+ };
201
+ // ── 6. script-source ─────────────────────────────────────────────────────────
202
+ /** Extensions that buildToolInfo can handle (tool-runner supported) */
203
+ const RUNNABLE_EXTENSIONS = SCRIPT_EXTENSIONS;
204
+ const scriptSourceRenderer = {
205
+ name: "script-source",
206
+ buildShowResponse(ctx) {
207
+ const name = deriveName(ctx);
208
+ const ext = path.extname(ctx.absPath).toLowerCase();
209
+ // For extensions supported by tool-runner, show runCmd
210
+ if (RUNNABLE_EXTENSIONS.has(ext)) {
211
+ const stashDirs = ctx.stashDirs;
212
+ const assetStashDir = findContainingStashDir(stashDirs, ctx.absPath);
213
+ if (assetStashDir) {
214
+ try {
215
+ const toolInfo = buildToolInfo(assetStashDir, ctx.absPath);
216
+ return {
217
+ type: "script",
218
+ name,
219
+ path: ctx.absPath,
220
+ runCmd: toolInfo.runCmd,
221
+ kind: toolInfo.kind,
222
+ };
223
+ }
224
+ catch {
225
+ // Fall through to content display
226
+ }
227
+ }
228
+ }
229
+ // For other extensions or when buildToolInfo fails, show file content
230
+ return {
231
+ type: "script",
232
+ name,
233
+ path: ctx.absPath,
234
+ content: ctx.content(),
235
+ };
236
+ },
237
+ enrichSearchHit(hit, stashDir) {
238
+ const ext = path.extname(hit.path).toLowerCase();
239
+ if (!RUNNABLE_EXTENSIONS.has(ext))
240
+ return;
241
+ try {
242
+ const toolInfo = buildToolInfo(stashDir, hit.path);
243
+ hit.runCmd = toolInfo.runCmd;
244
+ hit.kind = toolInfo.kind;
245
+ }
246
+ catch (error) {
247
+ if (!hasErrnoCode(error, "ENOENT"))
248
+ throw error;
249
+ }
250
+ },
251
+ extractMetadata(entry, ctx) {
252
+ if (ctx.ext !== ".md") {
253
+ const commentDesc = extractDescriptionFromComments(ctx.absPath);
254
+ if (commentDesc && !entry.description) {
255
+ entry.description = commentDesc;
256
+ entry.source = "comments";
257
+ entry.confidence = 0.7;
258
+ }
259
+ }
260
+ },
261
+ usageGuide: [
262
+ "Use the hit's runCmd for execution when available, or run the script directly with the appropriate interpreter.",
263
+ "Use `akm show <openRef>` to inspect the script before running it.",
264
+ ],
265
+ };
266
+ // ── Registration ─────────────────────────────────────────────────────────────
267
+ /** All built-in renderers. */
268
+ const builtinRenderers = [
269
+ toolScriptRenderer,
270
+ skillMdRenderer,
271
+ commandMdRenderer,
272
+ agentMdRenderer,
273
+ knowledgeMdRenderer,
274
+ scriptSourceRenderer,
275
+ ];
276
+ /**
277
+ * Register all built-in renderers with the file-context registry.
278
+ * Called once from the CLI entry point (or ensureBuiltinsRegistered).
279
+ */
280
+ export function registerBuiltinRenderers() {
281
+ for (const renderer of builtinRenderers) {
282
+ registerRenderer(renderer);
283
+ }
284
+ }
285
+ // ── Named exports for testing ────────────────────────────────────────────────
286
+ export { toolScriptRenderer, skillMdRenderer, commandMdRenderer, agentMdRenderer, knowledgeMdRenderer, scriptSourceRenderer, };
@@ -28,14 +28,15 @@ function getRgPlatformTarget() {
28
28
  }
29
29
  const RG_VERSION = "14.1.1";
30
30
  /**
31
- * Ensure ripgrep is available. If not found on PATH or in stash/bin,
32
- * download and install it to stash/bin.
31
+ * Ensure ripgrep is available. If not found on PATH or in the given binDir,
32
+ * download and install it to binDir.
33
33
  *
34
+ * @param binDir - Directory to install ripgrep into (e.g. cache/bin from paths.ts)
34
35
  * Returns the path to the ripgrep binary and whether it was newly installed.
35
36
  */
36
- export function ensureRg(stashDir) {
37
+ export function ensureRg(binDir) {
37
38
  // Already available?
38
- const existing = resolveRg(stashDir);
39
+ const existing = resolveRg(binDir);
39
40
  if (existing) {
40
41
  return { rgPath: existing, installed: false, version: getRgVersion(existing) };
41
42
  }
@@ -45,7 +46,6 @@ export function ensureRg(stashDir) {
45
46
  throw new Error(`Unsupported platform for ripgrep auto-install: ${process.platform}/${process.arch}. ` +
46
47
  `Install ripgrep manually: https://github.com/BurntSushi/ripgrep#installation`);
47
48
  }
48
- const binDir = path.join(stashDir, "bin");
49
49
  if (!fs.existsSync(binDir)) {
50
50
  fs.mkdirSync(binDir, { recursive: true });
51
51
  }
@@ -78,14 +78,7 @@ function downloadAndExtractTarGz(url, archiveName, destBinary) {
78
78
  throw new Error(`Failed to download ripgrep from ${url}: ${err}`);
79
79
  }
80
80
  // Extract the specific binary from the archive into destDir
81
- const tarResult = spawnSync("tar", [
82
- "xzf",
83
- tmpTarGz,
84
- "--strip-components=1",
85
- "-C",
86
- destDir,
87
- `${archiveName}/rg`,
88
- ], {
81
+ const tarResult = spawnSync("tar", ["xzf", tmpTarGz, "--strip-components=1", "-C", destDir, `${archiveName}/rg`], {
89
82
  encoding: "utf8",
90
83
  timeout: 60_000,
91
84
  });
@@ -124,13 +117,7 @@ function downloadAndExtractZip(url, archiveName, destBinary) {
124
117
  }
125
118
  // Extract the zip archive using separate spawnSync calls with argument arrays
126
119
  // to avoid shell injection via path interpolation in PowerShell -Command strings
127
- const expandResult = spawnSync("powershell", [
128
- "-Command",
129
- "Expand-Archive",
130
- "-Path", tmpZip,
131
- "-DestinationPath", destDir,
132
- "-Force",
133
- ], {
120
+ const expandResult = spawnSync("powershell", ["-Command", "Expand-Archive", "-Path", tmpZip, "-DestinationPath", destDir, "-Force"], {
134
121
  encoding: "utf8",
135
122
  timeout: 60_000,
136
123
  });
@@ -138,13 +125,7 @@ function downloadAndExtractZip(url, archiveName, destBinary) {
138
125
  throw new Error(expandResult.stderr?.trim() || "extraction failed");
139
126
  }
140
127
  const srcRgExe = path.join(destDir, archiveName, "rg.exe");
141
- const moveResult = spawnSync("powershell", [
142
- "-Command",
143
- "Move-Item",
144
- "-Force",
145
- "-Path", srcRgExe,
146
- "-Destination", destBinary,
147
- ], {
128
+ const moveResult = spawnSync("powershell", ["-Command", "Move-Item", "-Force", "-Path", srcRgExe, "-Destination", destBinary], {
148
129
  encoding: "utf8",
149
130
  timeout: 60_000,
150
131
  });
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { IS_WINDOWS } from "./common";
4
+ import { getBinDir } from "./paths";
4
5
  export const RG_BINARY = IS_WINDOWS ? "rg.exe" : "rg";
5
6
  function canExecute(filePath) {
6
7
  if (!fs.existsSync(filePath))
@@ -47,22 +48,31 @@ function resolveFromPath() {
47
48
  /**
48
49
  * Resolve the path to a usable ripgrep binary.
49
50
  * Checks in order:
50
- * 1. stashDir/bin/rg
51
- * 2. system PATH (rg)
51
+ * 1. Provided binDir (or default cache bin dir) for rg
52
+ * 2. System PATH (rg)
52
53
  * Returns null if ripgrep is not available.
53
54
  */
54
- export function resolveRg(stashDir) {
55
- // Check stash bin directory first
56
- if (stashDir) {
57
- const stashRg = path.join(stashDir, "bin", RG_BINARY);
58
- if (canExecute(stashRg))
59
- return stashRg;
55
+ export function resolveRg(binDir) {
56
+ if (binDir) {
57
+ const directRg = path.join(binDir, RG_BINARY);
58
+ if (canExecute(directRg))
59
+ return directRg;
60
+ }
61
+ // Check default cache bin dir
62
+ try {
63
+ const defaultBinDir = getBinDir();
64
+ const cachedRg = path.join(defaultBinDir, RG_BINARY);
65
+ if (canExecute(cachedRg))
66
+ return cachedRg;
67
+ }
68
+ catch {
69
+ // getBinDir may fail if HOME is not set — fall through
60
70
  }
61
71
  return resolveFromPath();
62
72
  }
63
73
  /**
64
- * Check if ripgrep is available (either in stash/bin or system PATH).
74
+ * Check if ripgrep is available (in cache/bin or system PATH).
65
75
  */
66
- export function isRgAvailable(stashDir) {
67
- return resolveRg(stashDir) !== null;
76
+ export function isRgAvailable(binDir) {
77
+ return resolveRg(binDir) !== null;
68
78
  }
@@ -0,0 +1,2 @@
1
+ export { ensureRg } from "./ripgrep-install";
2
+ export { isRgAvailable, resolveRg } from "./ripgrep-resolve";
@@ -0,0 +1,226 @@
1
+ import { createHash } from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { fetchWithRetry, IS_WINDOWS } from "./common";
5
+ import { githubHeaders } from "./github";
6
+ const REPO = "itlackey/agentikit";
7
+ export function detectInstallMethod() {
8
+ // Bun-compiled binaries: Bun.main equals process.execPath
9
+ if (typeof Bun !== "undefined" && Bun.main === process.execPath) {
10
+ return "binary";
11
+ }
12
+ // npm/bun global install: import.meta.dir contains node_modules
13
+ if (import.meta.dir?.includes("node_modules")) {
14
+ return "npm";
15
+ }
16
+ return "unknown";
17
+ }
18
+ export function getAkmBinaryName() {
19
+ const platform = process.platform;
20
+ const arch = process.arch;
21
+ if (platform === "linux" && arch === "x64")
22
+ return "akm-linux-x64";
23
+ if (platform === "linux" && arch === "arm64")
24
+ return "akm-linux-arm64";
25
+ if (platform === "darwin" && arch === "x64")
26
+ return "akm-darwin-x64";
27
+ if (platform === "darwin" && arch === "arm64")
28
+ return "akm-darwin-arm64";
29
+ if (platform === "win32" && arch === "x64")
30
+ return "akm-windows-x64.exe";
31
+ throw new Error(`Unsupported platform for binary upgrade: ${platform}/${arch}`);
32
+ }
33
+ export async function checkForUpdate(currentVersion) {
34
+ const installMethod = detectInstallMethod();
35
+ const url = `https://api.github.com/repos/${REPO}/releases/latest`;
36
+ const response = await fetchWithRetry(url, { headers: githubHeaders() });
37
+ if (!response.ok) {
38
+ throw new Error(`Failed to check for updates: ${response.status} ${response.statusText}`);
39
+ }
40
+ const release = (await response.json());
41
+ const latestTag = release.tag_name ?? "";
42
+ const latestVersion = latestTag.replace(/^v/, "");
43
+ return {
44
+ currentVersion,
45
+ latestVersion,
46
+ updateAvailable: latestVersion !== "" && Bun.semver.order(currentVersion, latestVersion) < 0,
47
+ installMethod,
48
+ };
49
+ }
50
+ export async function performUpgrade(check, opts) {
51
+ const { currentVersion, latestVersion, installMethod } = check;
52
+ const force = opts?.force === true;
53
+ if (installMethod === "npm") {
54
+ return {
55
+ currentVersion,
56
+ newVersion: latestVersion,
57
+ upgraded: false,
58
+ installMethod,
59
+ message: `akm installed via npm. Run: bun install -g agentikit@latest`,
60
+ };
61
+ }
62
+ if (installMethod === "unknown") {
63
+ return {
64
+ currentVersion,
65
+ newVersion: latestVersion,
66
+ upgraded: false,
67
+ installMethod,
68
+ message: `Unable to detect install method. Upgrade manually from https://github.com/${REPO}/releases`,
69
+ };
70
+ }
71
+ // Binary install
72
+ if (!check.updateAvailable && !force) {
73
+ return {
74
+ currentVersion,
75
+ newVersion: latestVersion,
76
+ upgraded: false,
77
+ installMethod,
78
+ message: `akm v${currentVersion} is already the latest version`,
79
+ };
80
+ }
81
+ if (!latestVersion) {
82
+ throw new Error("Unable to determine latest version from GitHub releases. Check https://github.com/itlackey/agentikit/releases");
83
+ }
84
+ const tag = `v${latestVersion}`;
85
+ const binaryName = getAkmBinaryName();
86
+ const binaryUrl = `https://github.com/${REPO}/releases/download/${tag}/${binaryName}`;
87
+ const checksumsUrl = `https://github.com/${REPO}/releases/download/${tag}/checksums.txt`;
88
+ // Download binary
89
+ const binaryResponse = await fetchWithRetry(binaryUrl);
90
+ if (!binaryResponse.ok) {
91
+ throw new Error(`Failed to download binary: ${binaryResponse.status} ${binaryResponse.statusText}`);
92
+ }
93
+ const binaryData = new Uint8Array(await binaryResponse.arrayBuffer());
94
+ // Download and verify checksum
95
+ let checksumVerified = false;
96
+ try {
97
+ const checksumsResponse = await fetchWithRetry(checksumsUrl);
98
+ if (checksumsResponse.ok) {
99
+ const checksumsText = await checksumsResponse.text();
100
+ const expectedHash = parseChecksumForFile(checksumsText, binaryName);
101
+ if (expectedHash) {
102
+ const actualHash = createHash("sha256").update(binaryData).digest("hex");
103
+ if (actualHash !== expectedHash) {
104
+ throw new Error(`Checksum mismatch for ${binaryName}.\n` + `Expected: ${expectedHash}\n` + `Got: ${actualHash}`);
105
+ }
106
+ checksumVerified = true;
107
+ }
108
+ }
109
+ }
110
+ catch (err) {
111
+ if (err instanceof Error && err.message.includes("Checksum mismatch")) {
112
+ throw err;
113
+ }
114
+ // Non-fatal: checksum file missing or unparseable
115
+ }
116
+ const execPath = process.execPath;
117
+ const execDir = path.dirname(execPath);
118
+ const execName = path.basename(execPath);
119
+ if (IS_WINDOWS) {
120
+ // Windows: rename running exe, write new one, clean up old on success
121
+ const oldPath = execPath + ".old";
122
+ try {
123
+ fs.renameSync(execPath, oldPath);
124
+ }
125
+ catch (err) {
126
+ const code = err.code;
127
+ if (code === "EPERM" || code === "EACCES") {
128
+ throw new Error(`Permission denied. Cannot rename ${execPath}.\n` +
129
+ `Try running as Administrator, or re-download from https://github.com/${REPO}/releases`);
130
+ }
131
+ const detail = err instanceof Error ? err.message : String(err);
132
+ throw new Error(`Failed to rename ${execPath}: ${detail}`);
133
+ }
134
+ try {
135
+ fs.writeFileSync(execPath, binaryData);
136
+ }
137
+ catch (err) {
138
+ // Restore from old
139
+ fs.renameSync(oldPath, execPath);
140
+ throw err;
141
+ }
142
+ // Best-effort cleanup of .old
143
+ try {
144
+ fs.unlinkSync(oldPath);
145
+ }
146
+ catch {
147
+ // Windows may lock the old exe — it will be cleaned up on next startup or manually
148
+ }
149
+ }
150
+ else {
151
+ // Unix: write to temp file, chmod +x, atomic rename
152
+ const tmpPath = path.join(execDir, `.${execName}.tmp.${process.pid}`);
153
+ const bakPath = execPath + ".bak";
154
+ try {
155
+ fs.writeFileSync(tmpPath, binaryData);
156
+ fs.chmodSync(tmpPath, 0o755);
157
+ }
158
+ catch (err) {
159
+ // Clean up temp file on failure
160
+ try {
161
+ fs.unlinkSync(tmpPath);
162
+ }
163
+ catch {
164
+ /* ignore */
165
+ }
166
+ const code = err.code;
167
+ if (code === "EACCES" || code === "EPERM") {
168
+ throw new Error(`Permission denied writing to ${execDir}.\n` +
169
+ `Run: sudo akm upgrade\n` +
170
+ `Or re-run the install script: curl -fsSL https://raw.githubusercontent.com/${REPO}/main/install.sh | bash`);
171
+ }
172
+ throw err;
173
+ }
174
+ // Backup current, then atomic rename
175
+ try {
176
+ fs.copyFileSync(execPath, bakPath);
177
+ fs.renameSync(tmpPath, execPath);
178
+ }
179
+ catch (err) {
180
+ // Restore from backup if rename failed
181
+ try {
182
+ fs.unlinkSync(tmpPath);
183
+ }
184
+ catch {
185
+ /* ignore */
186
+ }
187
+ try {
188
+ if (fs.existsSync(bakPath) && !fs.existsSync(execPath)) {
189
+ fs.renameSync(bakPath, execPath);
190
+ }
191
+ }
192
+ catch {
193
+ /* ignore */
194
+ }
195
+ throw err;
196
+ }
197
+ // Cleanup backup
198
+ try {
199
+ fs.unlinkSync(bakPath);
200
+ }
201
+ catch {
202
+ /* ignore */
203
+ }
204
+ }
205
+ return {
206
+ currentVersion,
207
+ newVersion: latestVersion,
208
+ upgraded: true,
209
+ installMethod,
210
+ binaryPath: execPath,
211
+ checksumVerified,
212
+ };
213
+ }
214
+ function parseChecksumForFile(checksumsText, filename) {
215
+ for (const line of checksumsText.split("\n")) {
216
+ const trimmed = line.trim();
217
+ if (!trimmed)
218
+ continue;
219
+ // Format: <hash> <filename>
220
+ const match = trimmed.match(/^([0-9a-f]{64})\s+(.+)$/);
221
+ if (match && match[2] === filename) {
222
+ return match[1];
223
+ }
224
+ }
225
+ return undefined;
226
+ }
@@ -1,12 +1,14 @@
1
- import { agentikitIndex } from "./indexer";
2
1
  import fs from "node:fs";
3
2
  import { resolveStashDir } from "./common";
4
3
  import { loadConfig } from "./config";
5
- import { upsertInstalledRegistryEntry, installRegistryRef } from "./registry-install";
4
+ import { UsageError } from "./errors";
5
+ import { agentikitIndex } from "./indexer";
6
+ import { upsertLockEntry } from "./lockfile";
7
+ import { installRegistryRef, upsertInstalledRegistryEntry } from "./registry-install";
6
8
  export async function agentikitAdd(input) {
7
9
  const ref = input.ref.trim();
8
10
  if (!ref)
9
- throw new Error("Install ref or local git directory is required.");
11
+ throw new UsageError("Install ref or local directory is required.");
10
12
  const stashDir = resolveStashDir();
11
13
  const installed = await installRegistryRef(ref);
12
14
  const replaced = loadConfig().registry?.installed.find((entry) => entry.id === installed.id);
@@ -21,6 +23,14 @@ export async function agentikitAdd(input) {
21
23
  cacheDir: installed.cacheDir,
22
24
  installedAt: installed.installedAt,
23
25
  });
26
+ upsertLockEntry({
27
+ id: installed.id,
28
+ source: installed.source,
29
+ ref: installed.ref,
30
+ resolvedVersion: installed.resolvedVersion,
31
+ resolvedRevision: installed.resolvedRevision,
32
+ integrity: installed.integrity ?? (installed.source === "local" ? "local" : undefined),
33
+ });
24
34
  if (replaced && replaced.cacheDir !== installed.cacheDir) {
25
35
  try {
26
36
  fs.rmSync(replaced.cacheDir, { recursive: true, force: true });
@@ -46,7 +56,7 @@ export async function agentikitAdd(input) {
46
56
  installedAt: installed.installedAt,
47
57
  },
48
58
  config: {
49
- mountedStashDirs: config.mountedStashDirs,
59
+ searchPaths: config.searchPaths,
50
60
  installedRegistryCount: config.registry?.installed.length ?? 0,
51
61
  },
52
62
  index: {