agentikit 0.0.13 → 0.0.14

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 (145) hide show
  1. package/LICENSE +385 -0
  2. package/README.md +180 -110
  3. package/dist/cli.js +671 -0
  4. package/dist/common.js +192 -0
  5. package/dist/{src/config-cli.js → config-cli.js} +14 -6
  6. package/dist/{src/config.js → config.js} +92 -24
  7. package/dist/{src/db.js → db.js} +109 -35
  8. package/dist/{src/embedder.js → embedder.js} +57 -2
  9. package/dist/file-context.js +158 -0
  10. package/dist/{src/handlers → handlers}/command-handler.js +2 -0
  11. package/dist/{src/handlers → handlers}/index.js +0 -6
  12. package/dist/{src/indexer.js → indexer.js} +34 -10
  13. package/dist/init.js +43 -0
  14. package/dist/lockfile.js +55 -0
  15. package/dist/matchers.js +157 -0
  16. package/dist/{src/metadata.js → metadata.js} +12 -1
  17. package/dist/{src/origin-resolve.js → origin-resolve.js} +10 -9
  18. package/dist/paths.js +82 -0
  19. package/dist/{src/registry-install.js → registry-install.js} +145 -17
  20. package/dist/{src/registry-resolve.js → registry-resolve.js} +178 -18
  21. package/dist/{src/registry-search.js → registry-search.js} +8 -16
  22. package/dist/renderers.js +276 -0
  23. package/dist/{src/ripgrep-install.js → ripgrep-install.js} +5 -5
  24. package/dist/{src/ripgrep-resolve.js → ripgrep-resolve.js} +21 -11
  25. package/dist/self-update.js +220 -0
  26. package/dist/{src/stash-add.js → stash-add.js} +11 -2
  27. package/dist/stash-clone.js +115 -0
  28. package/dist/{src/stash-registry.js → stash-registry.js} +15 -41
  29. package/dist/{src/stash-search.js → stash-search.js} +67 -55
  30. package/dist/{src/stash-show.js → stash-show.js} +30 -3
  31. package/dist/{src/stash-source.js → stash-source.js} +56 -9
  32. package/dist/submit.js +552 -0
  33. package/dist/{src/walker.js → walker.js} +38 -0
  34. package/package.json +7 -16
  35. package/dist/index.d.ts +0 -28
  36. package/dist/index.js +0 -15
  37. package/dist/src/asset-spec.d.ts +0 -16
  38. package/dist/src/asset-type-handler.d.ts +0 -27
  39. package/dist/src/cli.d.ts +0 -2
  40. package/dist/src/cli.js +0 -399
  41. package/dist/src/common.d.ts +0 -13
  42. package/dist/src/common.js +0 -60
  43. package/dist/src/config-cli.d.ts +0 -9
  44. package/dist/src/config.d.ts +0 -50
  45. package/dist/src/db.d.ts +0 -46
  46. package/dist/src/embedder.d.ts +0 -10
  47. package/dist/src/frontmatter.d.ts +0 -30
  48. package/dist/src/github.d.ts +0 -4
  49. package/dist/src/handlers/agent-handler.d.ts +0 -2
  50. package/dist/src/handlers/command-handler.d.ts +0 -2
  51. package/dist/src/handlers/index.d.ts +0 -6
  52. package/dist/src/handlers/knowledge-handler.d.ts +0 -2
  53. package/dist/src/handlers/markdown-helpers.d.ts +0 -7
  54. package/dist/src/handlers/script-handler.d.ts +0 -2
  55. package/dist/src/handlers/skill-handler.d.ts +0 -2
  56. package/dist/src/handlers/tool-handler.d.ts +0 -2
  57. package/dist/src/indexer.d.ts +0 -22
  58. package/dist/src/init.d.ts +0 -19
  59. package/dist/src/init.js +0 -99
  60. package/dist/src/llm.d.ts +0 -15
  61. package/dist/src/markdown.d.ts +0 -18
  62. package/dist/src/metadata.d.ts +0 -41
  63. package/dist/src/origin-resolve.d.ts +0 -19
  64. package/dist/src/registry-install.d.ts +0 -11
  65. package/dist/src/registry-resolve.d.ts +0 -3
  66. package/dist/src/registry-search.d.ts +0 -27
  67. package/dist/src/registry-types.d.ts +0 -62
  68. package/dist/src/ripgrep-install.d.ts +0 -12
  69. package/dist/src/ripgrep-resolve.d.ts +0 -13
  70. package/dist/src/ripgrep.d.ts +0 -3
  71. package/dist/src/stash-add.d.ts +0 -4
  72. package/dist/src/stash-clone.d.ts +0 -22
  73. package/dist/src/stash-clone.js +0 -83
  74. package/dist/src/stash-ref.d.ts +0 -31
  75. package/dist/src/stash-registry.d.ts +0 -18
  76. package/dist/src/stash-resolve.d.ts +0 -2
  77. package/dist/src/stash-search.d.ts +0 -8
  78. package/dist/src/stash-show.d.ts +0 -5
  79. package/dist/src/stash-source.d.ts +0 -24
  80. package/dist/src/stash-types.d.ts +0 -227
  81. package/dist/src/stash.d.ts +0 -16
  82. package/dist/src/stash.js +0 -9
  83. package/dist/src/tool-runner.d.ts +0 -35
  84. package/dist/src/walker.d.ts +0 -19
  85. package/src/asset-spec.ts +0 -85
  86. package/src/asset-type-handler.ts +0 -77
  87. package/src/cli.ts +0 -427
  88. package/src/common.ts +0 -76
  89. package/src/config-cli.ts +0 -499
  90. package/src/config.ts +0 -305
  91. package/src/db.ts +0 -411
  92. package/src/embedder.ts +0 -128
  93. package/src/frontmatter.ts +0 -95
  94. package/src/github.ts +0 -21
  95. package/src/handlers/agent-handler.ts +0 -32
  96. package/src/handlers/command-handler.ts +0 -29
  97. package/src/handlers/index.ts +0 -25
  98. package/src/handlers/knowledge-handler.ts +0 -62
  99. package/src/handlers/markdown-helpers.ts +0 -19
  100. package/src/handlers/script-handler.ts +0 -92
  101. package/src/handlers/skill-handler.ts +0 -37
  102. package/src/handlers/tool-handler.ts +0 -71
  103. package/src/indexer.ts +0 -392
  104. package/src/init.ts +0 -114
  105. package/src/llm.ts +0 -125
  106. package/src/markdown.ts +0 -106
  107. package/src/metadata.ts +0 -333
  108. package/src/origin-resolve.ts +0 -67
  109. package/src/registry-install.ts +0 -361
  110. package/src/registry-resolve.ts +0 -341
  111. package/src/registry-search.ts +0 -335
  112. package/src/registry-types.ts +0 -72
  113. package/src/ripgrep-install.ts +0 -200
  114. package/src/ripgrep-resolve.ts +0 -72
  115. package/src/ripgrep.ts +0 -3
  116. package/src/stash-add.ts +0 -63
  117. package/src/stash-clone.ts +0 -127
  118. package/src/stash-ref.ts +0 -99
  119. package/src/stash-registry.ts +0 -259
  120. package/src/stash-resolve.ts +0 -50
  121. package/src/stash-search.ts +0 -613
  122. package/src/stash-show.ts +0 -55
  123. package/src/stash-source.ts +0 -103
  124. package/src/stash-types.ts +0 -231
  125. package/src/stash.ts +0 -39
  126. package/src/tool-runner.ts +0 -142
  127. package/src/walker.ts +0 -53
  128. /package/dist/{src/asset-spec.js → asset-spec.js} +0 -0
  129. /package/dist/{src/asset-type-handler.js → asset-type-handler.js} +0 -0
  130. /package/dist/{src/frontmatter.js → frontmatter.js} +0 -0
  131. /package/dist/{src/github.js → github.js} +0 -0
  132. /package/dist/{src/handlers → handlers}/agent-handler.js +0 -0
  133. /package/dist/{src/handlers → handlers}/knowledge-handler.js +0 -0
  134. /package/dist/{src/handlers → handlers}/markdown-helpers.js +0 -0
  135. /package/dist/{src/handlers → handlers}/script-handler.js +0 -0
  136. /package/dist/{src/handlers → handlers}/skill-handler.js +0 -0
  137. /package/dist/{src/handlers → handlers}/tool-handler.js +0 -0
  138. /package/dist/{src/llm.js → llm.js} +0 -0
  139. /package/dist/{src/markdown.js → markdown.js} +0 -0
  140. /package/dist/{src/registry-types.js → registry-types.js} +0 -0
  141. /package/dist/{src/ripgrep.js → ripgrep.js} +0 -0
  142. /package/dist/{src/stash-ref.js → stash-ref.js} +0 -0
  143. /package/dist/{src/stash-resolve.js → stash-resolve.js} +0 -0
  144. /package/dist/{src/stash-types.js → stash-types.js} +0 -0
  145. /package/dist/{src/tool-runner.js → tool-runner.js} +0 -0
@@ -0,0 +1,276 @@
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 fs from "node:fs";
10
+ import path from "node:path";
11
+ import { registerRenderer } from "./file-context";
12
+ import { parseFrontmatter, toStringOrUndefined } from "./frontmatter";
13
+ import { SCRIPT_EXTENSIONS } from "./asset-spec";
14
+ import { buildToolInfo } from "./tool-runner";
15
+ import { hasErrnoCode } from "./common";
16
+ import { extractDescriptionFromComments } from "./metadata";
17
+ import { parseMarkdownToc, extractSection, extractLineRange, extractFrontmatterOnly, formatToc, } from "./markdown";
18
+ // ── Helpers ──────────────────────────────────────────────────────────────────
19
+ /**
20
+ * Derive a display name from the RenderContext.
21
+ *
22
+ * Prefers `matchResult.meta.name` when present; otherwise falls back to the
23
+ * POSIX-style relative path stripped of its extension.
24
+ */
25
+ function deriveName(ctx) {
26
+ const metaName = ctx.matchResult.meta?.name;
27
+ if (typeof metaName === "string" && metaName)
28
+ return metaName;
29
+ // Strip the extension from the relPath for a reasonable fallback.
30
+ const ext = path.extname(ctx.relPath);
31
+ return ext ? ctx.relPath.slice(0, -ext.length) : ctx.relPath;
32
+ }
33
+ /**
34
+ * Find the stashDir that contains `filePath`, falling back to the first
35
+ * entry in the array when no prefix match is found.
36
+ */
37
+ function findContainingStashDir(stashDirs, filePath) {
38
+ return (stashDirs.find((d) => path.resolve(filePath).startsWith(path.resolve(d) + path.sep)) ?? stashDirs[0]);
39
+ }
40
+ // ── 1. tool-script ───────────────────────────────────────────────────────────
41
+ const toolScriptRenderer = {
42
+ name: "tool-script",
43
+ buildShowResponse(ctx) {
44
+ const name = deriveName(ctx);
45
+ const stashDirs = ctx.stashDirs;
46
+ const assetStashDir = findContainingStashDir(stashDirs, ctx.absPath);
47
+ if (!assetStashDir) {
48
+ return { type: "tool", name, path: ctx.absPath, content: ctx.content() };
49
+ }
50
+ const toolInfo = buildToolInfo(assetStashDir, ctx.absPath);
51
+ return {
52
+ type: "tool",
53
+ name,
54
+ path: ctx.absPath,
55
+ runCmd: toolInfo.runCmd,
56
+ kind: toolInfo.kind,
57
+ };
58
+ },
59
+ enrichSearchHit(hit, stashDir) {
60
+ try {
61
+ const toolInfo = buildToolInfo(stashDir, hit.path);
62
+ hit.runCmd = toolInfo.runCmd;
63
+ hit.kind = toolInfo.kind;
64
+ }
65
+ catch (error) {
66
+ if (!hasErrnoCode(error, "ENOENT"))
67
+ throw error;
68
+ }
69
+ },
70
+ extractMetadata(entry, ctx) {
71
+ if (SCRIPT_EXTENSIONS.has(ctx.ext) && ctx.ext !== ".md") {
72
+ const commentDesc = extractDescriptionFromComments(ctx.absPath);
73
+ if (commentDesc && !entry.description) {
74
+ entry.description = commentDesc;
75
+ entry.source = "comments";
76
+ entry.confidence = 0.7;
77
+ }
78
+ }
79
+ },
80
+ usageGuide: [
81
+ "Use the hit's runCmd for execution so runtime and working directory stay correct.",
82
+ "Use `akm show <openRef>` to inspect the tool before running it.",
83
+ ],
84
+ };
85
+ // ── 2. skill-md ──────────────────────────────────────────────────────────────
86
+ const skillMdRenderer = {
87
+ name: "skill-md",
88
+ buildShowResponse(ctx) {
89
+ const name = deriveName(ctx);
90
+ return {
91
+ type: "skill",
92
+ name,
93
+ path: ctx.absPath,
94
+ content: ctx.content(),
95
+ };
96
+ },
97
+ usageGuide: [
98
+ "Read and apply the skill instructions as written, then adapt examples to your current repo state and task.",
99
+ "Use `akm show <openRef>` to read the full SKILL.md for required steps and constraints.",
100
+ ],
101
+ };
102
+ // ── 3. command-md ────────────────────────────────────────────────────────────
103
+ const commandMdRenderer = {
104
+ name: "command-md",
105
+ buildShowResponse(ctx) {
106
+ const name = deriveName(ctx);
107
+ const parsedMd = parseFrontmatter(ctx.content());
108
+ return {
109
+ type: "command",
110
+ name,
111
+ path: ctx.absPath,
112
+ description: toStringOrUndefined(parsedMd.data.description),
113
+ template: parsedMd.content,
114
+ modelHint: parsedMd.data.model,
115
+ agent: toStringOrUndefined(parsedMd.data.agent),
116
+ };
117
+ },
118
+ usageGuide: [
119
+ "Read the .md file, fill $ARGUMENTS placeholders, and run it in the current repo context.",
120
+ "Use `akm show <openRef>` to retrieve the command template body.",
121
+ "When `agent` is specified, dispatch the command to that agent.",
122
+ ],
123
+ };
124
+ // ── 4. agent-md ──────────────────────────────────────────────────────────────
125
+ const agentMdRenderer = {
126
+ name: "agent-md",
127
+ buildShowResponse(ctx) {
128
+ const name = deriveName(ctx);
129
+ const parsedMd = parseFrontmatter(ctx.content());
130
+ return {
131
+ type: "agent",
132
+ name,
133
+ path: ctx.absPath,
134
+ description: toStringOrUndefined(parsedMd.data.description),
135
+ prompt: "Dispatching prompt must include the agent's full prompt content verbatim; summaries are non-compliant. \n\n" +
136
+ parsedMd.content,
137
+ toolPolicy: parsedMd.data.tools,
138
+ modelHint: parsedMd.data.model,
139
+ };
140
+ },
141
+ usageGuide: [
142
+ "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.",
143
+ "Use with `akm show <openRef>` to get the full prompt payload.",
144
+ ],
145
+ };
146
+ // ── 5. knowledge-md ──────────────────────────────────────────────────────────
147
+ const knowledgeMdRenderer = {
148
+ name: "knowledge-md",
149
+ buildShowResponse(ctx) {
150
+ const name = deriveName(ctx);
151
+ const v = ctx.matchResult.meta?.view ?? { mode: "full" };
152
+ const content = ctx.content();
153
+ switch (v.mode) {
154
+ case "toc": {
155
+ const toc = parseMarkdownToc(content);
156
+ return { type: "knowledge", name, path: ctx.absPath, content: formatToc(toc) };
157
+ }
158
+ case "frontmatter": {
159
+ const fm = extractFrontmatterOnly(content);
160
+ return { type: "knowledge", name, path: ctx.absPath, content: fm ?? "(no frontmatter)" };
161
+ }
162
+ case "section": {
163
+ const section = extractSection(content, v.heading);
164
+ if (!section) {
165
+ return {
166
+ type: "knowledge",
167
+ name,
168
+ path: ctx.absPath,
169
+ content: `Section "${v.heading}" not found in ${name}. Try --view toc to discover available headings.`,
170
+ };
171
+ }
172
+ return { type: "knowledge", name, path: ctx.absPath, content: section.content };
173
+ }
174
+ case "lines": {
175
+ return {
176
+ type: "knowledge",
177
+ name,
178
+ path: ctx.absPath,
179
+ content: extractLineRange(content, v.start, v.end),
180
+ };
181
+ }
182
+ default: {
183
+ return { type: "knowledge", name, path: ctx.absPath, content };
184
+ }
185
+ }
186
+ },
187
+ extractMetadata(entry, ctx) {
188
+ try {
189
+ const mdContent = fs.readFileSync(ctx.absPath, "utf8");
190
+ const toc = parseMarkdownToc(mdContent);
191
+ if (toc.headings.length > 0)
192
+ entry.toc = toc.headings;
193
+ }
194
+ catch {
195
+ // Non-fatal: skip TOC if file can't be read
196
+ }
197
+ },
198
+ usageGuide: [
199
+ "Use `akm show <openRef>` to read the document; start with `--view toc` for large files.",
200
+ "Use `--view section` or `--view lines` to load only the part you need.",
201
+ ],
202
+ };
203
+ // ── 6. script-source ─────────────────────────────────────────────────────────
204
+ /** Extensions that buildToolInfo can handle (tool-runner supported) */
205
+ const RUNNABLE_EXTENSIONS = SCRIPT_EXTENSIONS;
206
+ const scriptSourceRenderer = {
207
+ name: "script-source",
208
+ buildShowResponse(ctx) {
209
+ const name = deriveName(ctx);
210
+ const ext = path.extname(ctx.absPath).toLowerCase();
211
+ // For extensions supported by tool-runner, show runCmd
212
+ if (RUNNABLE_EXTENSIONS.has(ext)) {
213
+ const stashDirs = ctx.stashDirs;
214
+ const assetStashDir = findContainingStashDir(stashDirs, ctx.absPath);
215
+ if (assetStashDir) {
216
+ try {
217
+ const toolInfo = buildToolInfo(assetStashDir, ctx.absPath);
218
+ return {
219
+ type: "script",
220
+ name,
221
+ path: ctx.absPath,
222
+ runCmd: toolInfo.runCmd,
223
+ kind: toolInfo.kind,
224
+ };
225
+ }
226
+ catch {
227
+ // Fall through to content display
228
+ }
229
+ }
230
+ }
231
+ // For other extensions or when buildToolInfo fails, show file content
232
+ return {
233
+ type: "script",
234
+ name,
235
+ path: ctx.absPath,
236
+ content: ctx.content(),
237
+ };
238
+ },
239
+ enrichSearchHit(hit, stashDir) {
240
+ const ext = path.extname(hit.path).toLowerCase();
241
+ if (!RUNNABLE_EXTENSIONS.has(ext))
242
+ return;
243
+ try {
244
+ const toolInfo = buildToolInfo(stashDir, hit.path);
245
+ hit.runCmd = toolInfo.runCmd;
246
+ hit.kind = toolInfo.kind;
247
+ }
248
+ catch (error) {
249
+ if (!hasErrnoCode(error, "ENOENT"))
250
+ throw error;
251
+ }
252
+ },
253
+ extractMetadata(entry, ctx) {
254
+ if (ctx.ext !== ".md") {
255
+ const commentDesc = extractDescriptionFromComments(ctx.absPath);
256
+ if (commentDesc && !entry.description) {
257
+ entry.description = commentDesc;
258
+ entry.source = "comments";
259
+ entry.confidence = 0.7;
260
+ }
261
+ }
262
+ },
263
+ usageGuide: [
264
+ "Use the hit's runCmd for execution when available, or run the script directly with the appropriate interpreter.",
265
+ "Use `akm show <openRef>` to inspect the script before running it.",
266
+ ],
267
+ };
268
+ // ── Registration ─────────────────────────────────────────────────────────────
269
+ registerRenderer(toolScriptRenderer);
270
+ registerRenderer(skillMdRenderer);
271
+ registerRenderer(commandMdRenderer);
272
+ registerRenderer(agentMdRenderer);
273
+ registerRenderer(knowledgeMdRenderer);
274
+ registerRenderer(scriptSourceRenderer);
275
+ // ── Named exports for testing ────────────────────────────────────────────────
276
+ 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
  }
@@ -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,220 @@
1
+ import { createHash } from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { IS_WINDOWS, fetchWithRetry } 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 !== "" && latestVersion !== currentVersion,
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` +
105
+ `Expected: ${expectedHash}\n` +
106
+ `Got: ${actualHash}`);
107
+ }
108
+ checksumVerified = true;
109
+ }
110
+ }
111
+ }
112
+ catch (err) {
113
+ if (err instanceof Error && err.message.includes("Checksum mismatch")) {
114
+ throw err;
115
+ }
116
+ // Non-fatal: checksum file missing or unparseable
117
+ }
118
+ const execPath = process.execPath;
119
+ const execDir = path.dirname(execPath);
120
+ const execName = path.basename(execPath);
121
+ if (IS_WINDOWS) {
122
+ // Windows: rename running exe, write new one, clean up old on success
123
+ const oldPath = execPath + ".old";
124
+ try {
125
+ fs.renameSync(execPath, oldPath);
126
+ }
127
+ catch (err) {
128
+ const code = err.code;
129
+ if (code === "EPERM" || code === "EACCES") {
130
+ throw new Error(`Permission denied. Cannot rename ${execPath}.\n` +
131
+ `Try running as Administrator, or re-download from https://github.com/${REPO}/releases`);
132
+ }
133
+ const detail = err instanceof Error ? err.message : String(err);
134
+ throw new Error(`Failed to rename ${execPath}: ${detail}`);
135
+ }
136
+ try {
137
+ fs.writeFileSync(execPath, binaryData);
138
+ }
139
+ catch (err) {
140
+ // Restore from old
141
+ fs.renameSync(oldPath, execPath);
142
+ throw err;
143
+ }
144
+ // Best-effort cleanup of .old
145
+ try {
146
+ fs.unlinkSync(oldPath);
147
+ }
148
+ catch {
149
+ // Windows may lock the old exe — it will be cleaned up on next startup or manually
150
+ }
151
+ }
152
+ else {
153
+ // Unix: write to temp file, chmod +x, atomic rename
154
+ const tmpPath = path.join(execDir, `.${execName}.tmp.${process.pid}`);
155
+ const bakPath = execPath + ".bak";
156
+ try {
157
+ fs.writeFileSync(tmpPath, binaryData);
158
+ fs.chmodSync(tmpPath, 0o755);
159
+ }
160
+ catch (err) {
161
+ // Clean up temp file on failure
162
+ try {
163
+ fs.unlinkSync(tmpPath);
164
+ }
165
+ catch { /* ignore */ }
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 { /* ignore */ }
185
+ try {
186
+ if (fs.existsSync(bakPath) && !fs.existsSync(execPath)) {
187
+ fs.renameSync(bakPath, execPath);
188
+ }
189
+ }
190
+ catch { /* ignore */ }
191
+ throw err;
192
+ }
193
+ // Cleanup backup
194
+ try {
195
+ fs.unlinkSync(bakPath);
196
+ }
197
+ catch { /* ignore */ }
198
+ }
199
+ return {
200
+ currentVersion,
201
+ newVersion: latestVersion,
202
+ upgraded: true,
203
+ installMethod,
204
+ binaryPath: execPath,
205
+ checksumVerified,
206
+ };
207
+ }
208
+ function parseChecksumForFile(checksumsText, filename) {
209
+ for (const line of checksumsText.split("\n")) {
210
+ const trimmed = line.trim();
211
+ if (!trimmed)
212
+ continue;
213
+ // Format: <hash> <filename>
214
+ const match = trimmed.match(/^([0-9a-f]{64})\s+(.+)$/);
215
+ if (match && match[2] === filename) {
216
+ return match[1];
217
+ }
218
+ }
219
+ return undefined;
220
+ }
@@ -2,11 +2,12 @@ import { agentikitIndex } from "./indexer";
2
2
  import fs from "node:fs";
3
3
  import { resolveStashDir } from "./common";
4
4
  import { loadConfig } from "./config";
5
+ import { upsertLockEntry } from "./lockfile";
5
6
  import { upsertInstalledRegistryEntry, installRegistryRef } from "./registry-install";
6
7
  export async function agentikitAdd(input) {
7
8
  const ref = input.ref.trim();
8
9
  if (!ref)
9
- throw new Error("Install ref or local git directory is required.");
10
+ throw new Error("Install ref or local directory is required.");
10
11
  const stashDir = resolveStashDir();
11
12
  const installed = await installRegistryRef(ref);
12
13
  const replaced = loadConfig().registry?.installed.find((entry) => entry.id === installed.id);
@@ -21,6 +22,14 @@ export async function agentikitAdd(input) {
21
22
  cacheDir: installed.cacheDir,
22
23
  installedAt: installed.installedAt,
23
24
  });
25
+ upsertLockEntry({
26
+ id: installed.id,
27
+ source: installed.source,
28
+ ref: installed.ref,
29
+ resolvedVersion: installed.resolvedVersion,
30
+ resolvedRevision: installed.resolvedRevision,
31
+ integrity: installed.integrity ?? (installed.source === "local" ? "local" : undefined),
32
+ });
24
33
  if (replaced && replaced.cacheDir !== installed.cacheDir) {
25
34
  try {
26
35
  fs.rmSync(replaced.cacheDir, { recursive: true, force: true });
@@ -46,7 +55,7 @@ export async function agentikitAdd(input) {
46
55
  installedAt: installed.installedAt,
47
56
  },
48
57
  config: {
49
- mountedStashDirs: config.mountedStashDirs,
58
+ searchPaths: config.searchPaths,
50
59
  installedRegistryCount: config.registry?.installed.length ?? 0,
51
60
  },
52
61
  index: {