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
package/dist/submit.js ADDED
@@ -0,0 +1,557 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { createInterface } from "node:readline/promises";
6
+ import { fetchWithRetry, isAssetType } from "./common";
7
+ import { asRecord, asString, GITHUB_API_BASE, githubHeaders } from "./github";
8
+ import { parseRegistryRef } from "./registry-resolve";
9
+ const REGISTRY_OWNER = "itlackey";
10
+ const REGISTRY_REPO = "agentikit-registry";
11
+ const MANUAL_ENTRIES_FILE = "manual-entries.json";
12
+ const GH_MIN_MAJOR = 2;
13
+ export async function agentikitSubmit(options = {}) {
14
+ const cwd = path.resolve(options.cwd ?? process.cwd());
15
+ const progress = options.progress ?? (() => { });
16
+ const runtime = resolveRuntimeBinaries(options);
17
+ progress("Checking GitHub CLI availability");
18
+ ensureGhAvailable(runtime);
19
+ progress("Checking GitHub CLI authentication");
20
+ ensureGhAuthenticated(runtime);
21
+ progress("Resolving submit target");
22
+ const resolved = await resolveSubmitTarget(options.ref, cwd);
23
+ progress("Building registry entry");
24
+ const entry = await buildSubmitEntry({
25
+ parsed: resolved.parsed,
26
+ packageJson: resolved.packageJson,
27
+ cwd,
28
+ interactive: options.interactive ?? (process.stdin.isTTY && process.stdout.isTTY),
29
+ ...options,
30
+ });
31
+ progress("Validating public accessibility");
32
+ const refAccessible = await isRefAccessible(resolved.parsed);
33
+ if (!refAccessible) {
34
+ throw new Error(`Registry ref "${entry.ref}" is not publicly accessible.`);
35
+ }
36
+ progress("Checking for duplicate manual entries");
37
+ const remoteEntries = await fetchRegistryManualEntries();
38
+ const duplicateFound = remoteEntries.some((item) => asString(asRecord(item).id) === entry.id);
39
+ if (duplicateFound) {
40
+ throw new Error(`Registry entry "${entry.id}" already exists in agentikit-registry.`);
41
+ }
42
+ progress("Looking up GitHub username");
43
+ const username = getGhUsername(runtime);
44
+ const branchName = buildSubmitBranchName(entry.id);
45
+ const pullRequestBody = buildPullRequestBody(entry);
46
+ const commands = buildPlannedCommands({
47
+ branchName,
48
+ entry,
49
+ username,
50
+ cleanupFork: options.cleanupFork === true,
51
+ pullRequestBody,
52
+ });
53
+ if (options.dryRun) {
54
+ return {
55
+ entry,
56
+ dryRun: true,
57
+ validation: {
58
+ refAccessible,
59
+ duplicateFound,
60
+ },
61
+ commands,
62
+ };
63
+ }
64
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "agentikit-submit-"));
65
+ const cloneDir = path.join(tempRoot, REGISTRY_REPO);
66
+ let forkCreated = false;
67
+ try {
68
+ progress("Forking and cloning agentikit-registry");
69
+ runCommand(runtime, "gh", ["repo", "fork", `${REGISTRY_OWNER}/${REGISTRY_REPO}`, "--clone", "--remote"], {
70
+ cwd: tempRoot,
71
+ });
72
+ forkCreated = true;
73
+ progress(`Creating branch ${branchName}`);
74
+ runCommand(runtime, "git", ["checkout", "-b", branchName], { cwd: cloneDir });
75
+ progress(`Updating ${MANUAL_ENTRIES_FILE}`);
76
+ appendManualEntry(cloneDir, entry);
77
+ progress("Committing manual entry");
78
+ runCommand(runtime, "git", ["add", MANUAL_ENTRIES_FILE], { cwd: cloneDir });
79
+ runCommand(runtime, "git", ["commit", "-m", `feat: add ${entry.name} to registry`], { cwd: cloneDir });
80
+ progress("Pushing branch to fork");
81
+ runCommand(runtime, "git", ["push", "origin", branchName], { cwd: cloneDir });
82
+ progress("Opening pull request");
83
+ const pr = createPullRequest({ cloneDir, username, branchName, entry, runtime, pullRequestBody });
84
+ const forkUrl = `https://github.com/${username}/${REGISTRY_REPO}`;
85
+ const cleanupCommand = `gh repo delete ${username}/${REGISTRY_REPO} --yes`;
86
+ if (options.cleanupFork) {
87
+ progress(`Fork cleanup deferred — the PR source branch lives on the fork. Run \`${cleanupCommand}\` after the PR is merged.`);
88
+ }
89
+ return {
90
+ entry,
91
+ pr,
92
+ fork: {
93
+ url: forkUrl,
94
+ cleanupCommand,
95
+ },
96
+ dryRun: false,
97
+ validation: {
98
+ refAccessible,
99
+ duplicateFound,
100
+ },
101
+ commands,
102
+ };
103
+ }
104
+ catch (error) {
105
+ if (forkCreated) {
106
+ progress(`A fork was created at https://github.com/${username}/${REGISTRY_REPO} but the submit failed. You can delete it with: gh repo delete ${username}/${REGISTRY_REPO} --yes`);
107
+ }
108
+ throw error;
109
+ }
110
+ finally {
111
+ fs.rmSync(tempRoot, { recursive: true, force: true });
112
+ }
113
+ }
114
+ export async function buildSubmitEntry(options) {
115
+ const packageJson = options.packageJson;
116
+ const interactive = options.interactive === true;
117
+ const defaultName = options.name?.trim() || packageJson?.name || inferNameFromParsedRef(options.parsed);
118
+ const defaultDescription = options.description?.trim() || packageJson?.description;
119
+ const defaultTags = normalizeTags(options.tags ?? packageJson?.keywords ?? []);
120
+ const defaultAssetTypes = normalizeAssetTypes(options.assetTypes ?? packageJson?.agentikitAssetTypes ?? []);
121
+ const defaultAuthor = options.author?.trim() || packageJson?.author;
122
+ const defaultLicense = options.license?.trim() || packageJson?.license;
123
+ const defaultHomepage = options.homepage?.trim() || packageJson?.homepage || inferHomepage(options.parsed);
124
+ const rl = interactive ? createInterface({ input: process.stdin, output: process.stdout }) : undefined;
125
+ try {
126
+ const promptedName = await promptWithDefault("Name", defaultName, rl);
127
+ const promptedDescription = await promptWithDefault("Description", defaultDescription, rl);
128
+ const promptedTags = await promptWithDefault("Tags (comma-separated)", defaultTags.join(", "), rl);
129
+ const promptedAssetTypes = await promptWithDefault("Asset types (comma-separated)", defaultAssetTypes.join(", "), rl);
130
+ const promptedAuthor = await promptWithDefault("Author", defaultAuthor, rl);
131
+ const promptedLicense = await promptWithDefault("License", defaultLicense, rl);
132
+ const promptedHomepage = await promptWithDefault("Homepage", defaultHomepage, rl);
133
+ const name = promptedName?.trim() || defaultName;
134
+ if (!name) {
135
+ throw new Error("Unable to determine a name for the registry entry.");
136
+ }
137
+ const description = promptedDescription?.trim() || defaultDescription;
138
+ const tags = normalizeTags(promptedTags || defaultTags);
139
+ const assetTypes = normalizeAssetTypes(promptedAssetTypes || defaultAssetTypes);
140
+ const author = promptedAuthor?.trim() || defaultAuthor?.trim();
141
+ const license = promptedLicense?.trim() || defaultLicense?.trim();
142
+ const homepage = promptedHomepage?.trim() || defaultHomepage?.trim();
143
+ const entry = {
144
+ id: options.parsed.id,
145
+ name,
146
+ ref: canonicalSubmitRef(options.parsed),
147
+ source: options.parsed.source,
148
+ };
149
+ if (description)
150
+ entry.description = description;
151
+ if (homepage)
152
+ entry.homepage = homepage;
153
+ if (tags.length > 0)
154
+ entry.tags = tags;
155
+ if (assetTypes.length > 0)
156
+ entry.assetTypes = assetTypes;
157
+ if (author)
158
+ entry.author = author;
159
+ if (license)
160
+ entry.license = license;
161
+ return entry;
162
+ }
163
+ finally {
164
+ rl?.close();
165
+ }
166
+ }
167
+ export function buildSubmitBranchName(entryId, now = new Date()) {
168
+ const yyyy = now.getUTCFullYear();
169
+ const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
170
+ const dd = String(now.getUTCDate()).padStart(2, "0");
171
+ const hh = String(now.getUTCHours()).padStart(2, "0");
172
+ const min = String(now.getUTCMinutes()).padStart(2, "0");
173
+ return `submit/${slugifySubmitValue(entryId)}-${yyyy}${mm}${dd}-${hh}${min}`;
174
+ }
175
+ export function slugifySubmitValue(value) {
176
+ return value
177
+ .toLowerCase()
178
+ .replace(/[^a-z0-9]+/g, "-")
179
+ .replace(/^-+|-+$/g, "")
180
+ .replace(/-+/g, "-");
181
+ }
182
+ async function resolveSubmitTarget(rawRef, cwd) {
183
+ if (!rawRef) {
184
+ return await inferSubmitTargetFromDir(cwd);
185
+ }
186
+ const parsed = parseSubmitRef(rawRef, cwd);
187
+ if (parsed.source === "npm" || parsed.source === "github") {
188
+ return { parsed };
189
+ }
190
+ if (parsed.source === "local") {
191
+ return await inferSubmitTargetFromDir(parsed.sourcePath);
192
+ }
193
+ if (parsed.source === "git") {
194
+ throw new Error("`akm submit` does not support generic git URLs. Use a public npm package name or GitHub owner/repo ref instead.");
195
+ }
196
+ throw new Error("`akm submit` requires a public npm package or GitHub repository ref.");
197
+ }
198
+ async function inferSubmitTargetFromDir(dir) {
199
+ const packageJson = readPackageJson(dir);
200
+ if (!packageJson) {
201
+ throw new Error("Unable to infer a public npm or GitHub ref from the current directory. Add a package.json or pass a ref explicitly.");
202
+ }
203
+ const candidates = [];
204
+ if (packageJson.name) {
205
+ candidates.push(parseSubmitRef(packageJson.name));
206
+ }
207
+ const githubRef = extractGithubRepoRef(packageJson.repositoryUrl) ?? extractGithubRepoRef(packageJson.homepage);
208
+ if (githubRef) {
209
+ candidates.push(parseSubmitRef(githubRef));
210
+ }
211
+ for (const candidate of candidates) {
212
+ if (await isRefAccessible(candidate)) {
213
+ return { parsed: candidate, packageJson };
214
+ }
215
+ }
216
+ throw new Error("Unable to infer a publicly accessible npm package or GitHub repository from package.json.");
217
+ }
218
+ function readPackageJson(dir) {
219
+ const packagePath = path.join(dir, "package.json");
220
+ if (!fs.existsSync(packagePath))
221
+ return undefined;
222
+ let raw;
223
+ try {
224
+ raw = JSON.parse(fs.readFileSync(packagePath, "utf8"));
225
+ }
226
+ catch {
227
+ throw new Error(`Failed to parse package.json in ${dir}.`);
228
+ }
229
+ const pkg = asRecord(raw);
230
+ const repository = normalizeRepositoryUrl(pkg.repository);
231
+ const author = normalizeAuthor(pkg.author);
232
+ const keywords = Array.isArray(pkg.keywords)
233
+ ? pkg.keywords.filter((value) => typeof value === "string")
234
+ : undefined;
235
+ const agentikit = asRecord(pkg.agentikit);
236
+ const assetTypes = Array.isArray(agentikit.assetTypes)
237
+ ? agentikit.assetTypes.filter((value) => typeof value === "string" && isAssetType(value))
238
+ : undefined;
239
+ return {
240
+ name: asString(pkg.name),
241
+ description: asString(pkg.description),
242
+ keywords,
243
+ homepage: asString(pkg.homepage),
244
+ author,
245
+ license: asString(pkg.license),
246
+ agentikitAssetTypes: assetTypes,
247
+ repositoryUrl: repository,
248
+ };
249
+ }
250
+ function normalizeRepositoryUrl(value) {
251
+ if (typeof value === "string")
252
+ return value;
253
+ if (typeof value === "object" && value !== null) {
254
+ return asString(value.url);
255
+ }
256
+ return undefined;
257
+ }
258
+ function normalizeAuthor(value) {
259
+ if (typeof value === "string")
260
+ return value;
261
+ if (typeof value === "object" && value !== null) {
262
+ return asString(value.name);
263
+ }
264
+ return undefined;
265
+ }
266
+ function inferNameFromParsedRef(parsed) {
267
+ if (parsed.source === "npm")
268
+ return parsed.packageName;
269
+ return parsed.repo;
270
+ }
271
+ function canonicalSubmitRef(parsed) {
272
+ if (parsed.source === "npm")
273
+ return parsed.packageName;
274
+ return parsed.requestedRef
275
+ ? `${parsed.owner}/${parsed.repo}#${parsed.requestedRef}`
276
+ : `${parsed.owner}/${parsed.repo}`;
277
+ }
278
+ function inferHomepage(parsed) {
279
+ if (parsed.source === "npm") {
280
+ return `https://www.npmjs.com/package/${parsed.packageName}`;
281
+ }
282
+ return `https://github.com/${parsed.owner}/${parsed.repo}`;
283
+ }
284
+ /** Encode a (possibly scoped) npm package name for use in registry.npmjs.org URLs. */
285
+ function encodeNpmPackageName(name) {
286
+ // Scoped packages need only the slash encoded: @scope%2Fname
287
+ // encodeURIComponent would also encode the @ which the registry rejects.
288
+ return name.replace(/\//g, "%2F");
289
+ }
290
+ function extractGithubRepoRef(value) {
291
+ if (!value)
292
+ return undefined;
293
+ const cleaned = value.replace(/^git\+/, "");
294
+ const httpsMatch = cleaned.match(/^https:\/\/github\.com\/([^/]+)\/([^/#]+?)(?:\.git)?(?:#.*)?$/i);
295
+ if (httpsMatch)
296
+ return `${httpsMatch[1]}/${httpsMatch[2]}`;
297
+ const sshMatch = cleaned.match(/^git@github\.com:([^/]+)\/([^/#]+?)(?:\.git)?$/i);
298
+ if (sshMatch)
299
+ return `${sshMatch[1]}/${sshMatch[2]}`;
300
+ const shortMatch = cleaned.match(/^github:([^/]+)\/([^/#]+?)(?:#.*)?$/i);
301
+ if (shortMatch)
302
+ return `${shortMatch[1]}/${shortMatch[2]}`;
303
+ return undefined;
304
+ }
305
+ function parseSubmitRef(rawRef, cwd = process.cwd()) {
306
+ const trimmed = rawRef.trim();
307
+ if (looksLikeScopedPackage(trimmed)) {
308
+ return parseRegistryRef(`npm:${trimmed}`);
309
+ }
310
+ // Existing local directories like "kits/my-kit" should win over owner/repo
311
+ // shorthand so users can submit relative kit paths without needing "./".
312
+ if (isExistingLocalDirectory(trimmed, cwd)) {
313
+ return parseRegistryRef(path.resolve(cwd, trimmed));
314
+ }
315
+ if (looksLikeGithubRepo(trimmed)) {
316
+ return parseRegistryRef(`github:${trimmed}`);
317
+ }
318
+ return parseRegistryRef(trimmed);
319
+ }
320
+ function looksLikeScopedPackage(value) {
321
+ return /^@[^/]+\/[^/@]+(?:@[^/]+)?$/.test(value);
322
+ }
323
+ function looksLikeGithubRepo(value) {
324
+ return /^[^./][^/]*\/[^/]+(?:#.+)?$/.test(value);
325
+ }
326
+ function isExistingLocalDirectory(ref, cwd) {
327
+ try {
328
+ return fs.statSync(path.resolve(cwd, ref)).isDirectory();
329
+ }
330
+ catch {
331
+ return false;
332
+ }
333
+ }
334
+ async function promptWithDefault(label, value, rl) {
335
+ if (!rl)
336
+ return value;
337
+ const suffix = value ? ` [${value}]` : "";
338
+ const answer = await rl.question(`${label}${suffix}: `);
339
+ return answer.trim() || value;
340
+ }
341
+ function normalizeTags(value) {
342
+ const values = Array.isArray(value) ? value : typeof value === "string" ? value.split(",") : [];
343
+ const deduped = new Set();
344
+ for (const item of values) {
345
+ const normalized = item.trim().toLowerCase();
346
+ if (!normalized || normalized === "agentikit" || normalized === "akm")
347
+ continue;
348
+ deduped.add(normalized);
349
+ }
350
+ return Array.from(deduped);
351
+ }
352
+ function normalizeAssetTypes(value) {
353
+ const values = Array.isArray(value) ? value : typeof value === "string" ? value.split(",") : [];
354
+ const deduped = new Set();
355
+ for (const item of values) {
356
+ const normalized = item.trim().toLowerCase();
357
+ if (!normalized)
358
+ continue;
359
+ if (!isAssetType(normalized)) {
360
+ throw new Error(`Invalid asset type: ${item}`);
361
+ }
362
+ deduped.add(normalized);
363
+ }
364
+ return Array.from(deduped);
365
+ }
366
+ async function isRefAccessible(parsed) {
367
+ if (parsed.source === "npm") {
368
+ const url = `https://registry.npmjs.org/${encodeNpmPackageName(parsed.packageName)}`;
369
+ const response = await fetchWithRetry(url, undefined, { timeout: 10_000 });
370
+ return response.ok;
371
+ }
372
+ const url = `${GITHUB_API_BASE}/repos/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}`;
373
+ const response = await fetchWithRetry(url, { headers: githubHeaders() }, { timeout: 10_000 });
374
+ if (!response.ok)
375
+ return false;
376
+ const repo = asRecord(await response.json());
377
+ const isPrivate = repo.private === true;
378
+ const visibility = asString(repo.visibility)?.toLowerCase();
379
+ return !isPrivate && (!visibility || visibility === "public");
380
+ }
381
+ async function fetchRegistryManualEntries() {
382
+ const repoResponse = await fetchWithRetry(`${GITHUB_API_BASE}/repos/${REGISTRY_OWNER}/${REGISTRY_REPO}`, { headers: githubHeaders() }, { timeout: 10_000 });
383
+ if (!repoResponse.ok) {
384
+ throw new Error(`Failed to load ${REGISTRY_OWNER}/${REGISTRY_REPO} metadata (${repoResponse.status}).`);
385
+ }
386
+ const repoJson = asRecord(await repoResponse.json());
387
+ const defaultBranch = asString(repoJson.default_branch) ?? "main";
388
+ const rawUrl = `https://raw.githubusercontent.com/${REGISTRY_OWNER}/${REGISTRY_REPO}/${defaultBranch}/${MANUAL_ENTRIES_FILE}`;
389
+ const entriesResponse = await fetchWithRetry(rawUrl, undefined, { timeout: 10_000 });
390
+ if (!entriesResponse.ok) {
391
+ throw new Error(`Failed to load ${MANUAL_ENTRIES_FILE} from ${REGISTRY_OWNER}/${REGISTRY_REPO} (${entriesResponse.status}).`);
392
+ }
393
+ const parsed = (await entriesResponse.json());
394
+ if (!Array.isArray(parsed)) {
395
+ throw new Error(`${MANUAL_ENTRIES_FILE} is not a JSON array.`);
396
+ }
397
+ return parsed;
398
+ }
399
+ function appendManualEntry(cloneDir, entry) {
400
+ const filePath = path.join(cloneDir, MANUAL_ENTRIES_FILE);
401
+ const existing = readManualEntriesFile(filePath);
402
+ existing.push(entry);
403
+ fs.writeFileSync(filePath, `${JSON.stringify(existing, null, 2)}\n`, "utf8");
404
+ }
405
+ function readManualEntriesFile(filePath) {
406
+ if (!fs.existsSync(filePath)) {
407
+ throw new Error(`${MANUAL_ENTRIES_FILE} not found in the fork. The agentikit-registry layout may have changed.`);
408
+ }
409
+ let raw;
410
+ try {
411
+ raw = JSON.parse(fs.readFileSync(filePath, "utf8"));
412
+ }
413
+ catch {
414
+ throw new Error(`${MANUAL_ENTRIES_FILE} in the fork contains invalid JSON.`);
415
+ }
416
+ if (!Array.isArray(raw)) {
417
+ throw new Error(`${MANUAL_ENTRIES_FILE} in the fork is not a JSON array.`);
418
+ }
419
+ return raw;
420
+ }
421
+ function createPullRequest(options) {
422
+ const output = runCommand(options.runtime, "gh", buildPullRequestArgs(options.entry, options.username, options.branchName, options.pullRequestBody), { cwd: options.cloneDir });
423
+ const url = output.stdout
424
+ .trim()
425
+ .split(/\r?\n/)
426
+ .find((line) => /^https:\/\/github\.com\//.test(line));
427
+ if (!url) {
428
+ throw new Error("gh pr create did not return a pull request URL.");
429
+ }
430
+ const match = url.match(/\/pull\/(\d+)(?:\/?$)/);
431
+ return {
432
+ url,
433
+ number: match ? parseInt(match[1], 10) : undefined,
434
+ };
435
+ }
436
+ function buildPullRequestBody(entry) {
437
+ return [
438
+ `## New registry entry: ${entry.name}`,
439
+ "",
440
+ `**Ref:** \`${entry.ref}\` (${entry.source})`,
441
+ `**Install:** \`akm add ${entry.ref}\``,
442
+ "",
443
+ "### Entry JSON",
444
+ "```json",
445
+ JSON.stringify(entry, null, 2),
446
+ "```",
447
+ "",
448
+ "### Verification",
449
+ "- [ ] Ref is publicly accessible",
450
+ "- [ ] Package/repo contains agentikit-compatible assets",
451
+ "",
452
+ "Submitted via `akm submit`",
453
+ ].join("\n");
454
+ }
455
+ function buildPlannedCommands(options) {
456
+ const commands = [
457
+ formatCommand(["gh", "repo", "fork", `${REGISTRY_OWNER}/${REGISTRY_REPO}`, "--clone", "--remote"]),
458
+ formatCommand(["git", "checkout", "-b", options.branchName]),
459
+ formatCommand(["git", "add", MANUAL_ENTRIES_FILE]),
460
+ formatCommand(["git", "commit", "-m", `feat: add ${options.entry.name} to registry`]),
461
+ formatCommand(["git", "push", "origin", options.branchName]),
462
+ formatCommand([
463
+ "gh",
464
+ ...buildPullRequestArgs(options.entry, options.username, options.branchName, options.pullRequestBody),
465
+ ]),
466
+ ];
467
+ if (options.cleanupFork) {
468
+ commands.push(`# After PR is merged: ${formatCommand(["gh", "repo", "delete", `${options.username}/${REGISTRY_REPO}`, "--yes"])}`);
469
+ }
470
+ return commands;
471
+ }
472
+ function buildPullRequestArgs(entry, username, branchName, body) {
473
+ return [
474
+ "pr",
475
+ "create",
476
+ "--repo",
477
+ `${REGISTRY_OWNER}/${REGISTRY_REPO}`,
478
+ "--title",
479
+ `Add ${entry.name} to registry`,
480
+ "--body",
481
+ body,
482
+ "--head",
483
+ `${username}:${branchName}`,
484
+ ];
485
+ }
486
+ function formatCommand(args) {
487
+ const command = args.map(quoteShellArg).join(" ");
488
+ return process.platform === "win32" ? `& ${command}` : command;
489
+ }
490
+ function quoteShellArg(value) {
491
+ if (/^[a-zA-Z0-9_./:@#=-]+$/.test(value))
492
+ return value;
493
+ if (process.platform === "win32") {
494
+ // These planned commands are rendered for PowerShell on Windows, where a
495
+ // single-quoted string escapes embedded single quotes by doubling them.
496
+ return `'${value.replace(/'/g, "''")}'`;
497
+ }
498
+ // POSIX shells keep single-quoted strings literal; embed a literal quote by
499
+ // closing the string, escaping the quote, and reopening it.
500
+ return `'${value.replace(/'/g, `'\\''`)}'`;
501
+ }
502
+ function ensureGhAvailable(runtime) {
503
+ const result = spawnSync(runtime.ghBin, ["--version"], { encoding: "utf8", timeout: 10_000 });
504
+ if (result.error || result.status !== 0) {
505
+ throw new Error("gh CLI is required to use `akm submit`.");
506
+ }
507
+ const versionText = `${result.stdout}\n${result.stderr}`;
508
+ const match = versionText.match(/gh version (\d+)\.(\d+)\.(\d+)/i);
509
+ if (!match)
510
+ return;
511
+ const major = parseInt(match[1], 10);
512
+ if (major < GH_MIN_MAJOR) {
513
+ throw new Error("gh CLI is required to use `akm submit`.");
514
+ }
515
+ }
516
+ function ensureGhAuthenticated(runtime) {
517
+ const result = spawnSync(runtime.ghBin, ["auth", "status", "--hostname", "github.com"], {
518
+ encoding: "utf8",
519
+ timeout: 10_000,
520
+ });
521
+ if (result.status !== 0) {
522
+ throw new Error("gh CLI is not authenticated for github.com.");
523
+ }
524
+ }
525
+ function getGhUsername(runtime) {
526
+ const result = runCommand(runtime, "gh", ["api", "user", "--jq", ".login"]);
527
+ const username = result.stdout.trim();
528
+ if (!username) {
529
+ throw new Error("Unable to determine GitHub username from gh CLI.");
530
+ }
531
+ return username;
532
+ }
533
+ function resolveRuntimeBinaries(options) {
534
+ return {
535
+ ghBin: options.ghBin?.trim() || process.env.AKM_SUBMIT_GH_BIN?.trim() || "gh",
536
+ gitBin: options.gitBin?.trim() || process.env.AKM_SUBMIT_GIT_BIN?.trim() || "git",
537
+ };
538
+ }
539
+ function runCommand(runtime, command, args, options) {
540
+ const result = spawnSync(resolveCommandBin(runtime, command), args, {
541
+ ...options,
542
+ encoding: "utf8",
543
+ timeout: options?.timeout ?? 120_000,
544
+ });
545
+ if (result.error || result.status !== 0) {
546
+ const detail = result.stderr?.trim() || result.stdout?.trim() || result.error?.message || "unknown error";
547
+ throw new Error(`Command failed: ${command} ${args.join(" ")}\n${detail}`);
548
+ }
549
+ return result;
550
+ }
551
+ function resolveCommandBin(runtime, command) {
552
+ if (command === "gh")
553
+ return runtime.ghBin;
554
+ if (command === "git")
555
+ return runtime.gitBin;
556
+ return command;
557
+ }
@@ -88,11 +88,7 @@ export function shellQuote(input) {
88
88
  .replace(/"/g, '""');
89
89
  return `"${escaped}"`;
90
90
  }
91
- const escaped = input
92
- .replace(/\\/g, "\\\\")
93
- .replace(/"/g, '\\"')
94
- .replace(/\$/g, "\\$")
95
- .replace(/`/g, "\\`");
91
+ const escaped = input.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\$/g, "\\$").replace(/`/g, "\\`");
96
92
  return `"${escaped}"`;
97
93
  }
98
94
  /**
@@ -8,6 +8,7 @@
8
8
  import fs from "node:fs";
9
9
  import path from "node:path";
10
10
  import { isRelevantAssetFile } from "./asset-spec";
11
+ import { buildFileContext } from "./file-context";
11
12
  /**
12
13
  * Walk a type root directory and return files grouped by their parent directory.
13
14
  *
@@ -45,3 +46,40 @@ export function walkStash(typeRoot, assetType) {
45
46
  }
46
47
  return Array.from(groups, ([dirPath, files]) => ({ dirPath, files }));
47
48
  }
49
+ /**
50
+ * Walk an entire stash root directory and return FileContext objects for every
51
+ * regular file found.
52
+ *
53
+ * Unlike walkStash(), this does NOT filter by asset type or require files to
54
+ * live under type-specific directories. Matchers decide what each file is.
55
+ *
56
+ * Skips: .git, node_modules, bin, .cache, any directory starting with ".",
57
+ * and .stash.json files.
58
+ */
59
+ export function walkStashFlat(stashRoot) {
60
+ if (!fs.existsSync(stashRoot))
61
+ return [];
62
+ const results = [];
63
+ const SKIP_DIRS = new Set([".git", "node_modules", "bin", ".cache"]);
64
+ const stack = [stashRoot];
65
+ while (stack.length > 0) {
66
+ const current = stack.pop();
67
+ if (!current)
68
+ continue;
69
+ const entries = fs.readdirSync(current, { withFileTypes: true });
70
+ for (const entry of entries) {
71
+ if (entry.name === ".stash.json")
72
+ continue;
73
+ const fullPath = path.join(current, entry.name);
74
+ if (entry.isDirectory()) {
75
+ if (SKIP_DIRS.has(entry.name) || entry.name.startsWith("."))
76
+ continue;
77
+ stack.push(fullPath);
78
+ }
79
+ else if (entry.isFile()) {
80
+ results.push(buildFileContext(stashRoot, fullPath));
81
+ }
82
+ }
83
+ }
84
+ return results;
85
+ }
package/dist/warn.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Module-level quiet flag for suppressing stderr warnings.
3
+ * Controlled by the CLI --quiet / -q flag.
4
+ */
5
+ let quiet = false;
6
+ export function setQuiet(value) {
7
+ quiet = value;
8
+ }
9
+ export function isQuiet() {
10
+ return quiet;
11
+ }
12
+ /**
13
+ * Emit a warning to stderr unless --quiet is active.
14
+ * Drop-in replacement for console.warn() across the codebase.
15
+ */
16
+ export function warn(...args) {
17
+ if (!quiet) {
18
+ console.warn(...args);
19
+ }
20
+ }