@michael-joseph-miller/ant-bot 0.1.0

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 (60) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/LICENSE +21 -0
  3. package/README.md +267 -0
  4. package/dist/browser-OHRD7YI3.js +524 -0
  5. package/dist/browser-OHRD7YI3.js.map +7 -0
  6. package/dist/bundled-46DUK5PG.js +133 -0
  7. package/dist/bundled-46DUK5PG.js.map +7 -0
  8. package/dist/chunk-7BOHBPB2.js +287 -0
  9. package/dist/chunk-7BOHBPB2.js.map +7 -0
  10. package/dist/chunk-AHAON6J7.js +24 -0
  11. package/dist/chunk-AHAON6J7.js.map +7 -0
  12. package/dist/chunk-DYIJTUMY.js +321 -0
  13. package/dist/chunk-DYIJTUMY.js.map +7 -0
  14. package/dist/chunk-KSQOVWP5.js +310 -0
  15. package/dist/chunk-KSQOVWP5.js.map +7 -0
  16. package/dist/chunk-Z2YT2PZN.js +64 -0
  17. package/dist/chunk-Z2YT2PZN.js.map +7 -0
  18. package/dist/index.js +1562 -0
  19. package/dist/index.js.map +7 -0
  20. package/dist/install-IY2M3OUQ.js +36 -0
  21. package/dist/install-IY2M3OUQ.js.map +7 -0
  22. package/dist/plugin-POMHVGD4.js +53 -0
  23. package/dist/plugin-POMHVGD4.js.map +7 -0
  24. package/dist/scheduler-Q7OHNGP6.js +274 -0
  25. package/dist/scheduler-Q7OHNGP6.js.map +7 -0
  26. package/dist/server.js +3016 -0
  27. package/dist/server.js.map +7 -0
  28. package/dist/skills-66WRX64H.js +17 -0
  29. package/dist/skills-66WRX64H.js.map +7 -0
  30. package/dist/skills-spec.js +172 -0
  31. package/dist/skills-spec.js.map +7 -0
  32. package/dist/tools-P5537ASX.js +148 -0
  33. package/dist/tools-P5537ASX.js.map +7 -0
  34. package/package.json +51 -0
  35. package/skills/README.md +69 -0
  36. package/skills/SPEC.md +274 -0
  37. package/skills/bug-repro/SKILL.md +53 -0
  38. package/skills/deep-research/SKILL.md +451 -0
  39. package/skills/deep-research/references/V6_1_improvements.md +112 -0
  40. package/skills/deep-research/references/completeness_review_checklist.md +25 -0
  41. package/skills/deep-research/references/counter_review_team_guide.md +181 -0
  42. package/skills/deep-research/references/enterprise_analysis_frameworks.md +135 -0
  43. package/skills/deep-research/references/enterprise_mode.md +99 -0
  44. package/skills/deep-research/references/enterprise_quality_checklist.md +160 -0
  45. package/skills/deep-research/references/enterprise_research_methodology.md +164 -0
  46. package/skills/deep-research/references/formatting_rules.md +31 -0
  47. package/skills/deep-research/references/quality_gates.md +77 -0
  48. package/skills/deep-research/references/report_template_v6.md +82 -0
  49. package/skills/deep-research/references/research_notes_format.md +147 -0
  50. package/skills/deep-research/references/research_plan_checklist.md +26 -0
  51. package/skills/deep-research/references/research_report_template.md +49 -0
  52. package/skills/deep-research/references/source_accessibility_policy.md +179 -0
  53. package/skills/deep-research/references/source_quality_rubric.md +28 -0
  54. package/skills/deep-research/references/subagent_prompt.md +116 -0
  55. package/skills/inbox-digest/SKILL.md +51 -0
  56. package/skills/skill-author/SKILL.md +92 -0
  57. package/skills/weekly-report/SKILL.md +49 -0
  58. package/web/dist/assets/index-BLQ8rPiN.js +130 -0
  59. package/web/dist/assets/index-IEIkG_jd.css +2 -0
  60. package/web/dist/index.html +13 -0
@@ -0,0 +1,310 @@
1
+ import { createRequire as __antbotCreateRequire } from 'node:module';
2
+ const require = __antbotCreateRequire(import.meta.url);
3
+
4
+ // packages/server/src/skills/skills.ts
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+
8
+ // packages/server/src/util/ids.ts
9
+ import { randomUUID } from "node:crypto";
10
+ var newId = () => randomUUID();
11
+ var now = () => Date.now();
12
+ function slugify(name, existing = /* @__PURE__ */ new Set()) {
13
+ const base = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "bot";
14
+ let slug = base;
15
+ let n = 2;
16
+ while (existing.has(slug)) slug = `${base}-${n++}`;
17
+ return slug;
18
+ }
19
+
20
+ // packages/server/src/skills/skills.ts
21
+ var SKILL_TEMPLATE = `## When to use it
22
+
23
+ Describe the trigger: what request or situation should make a bot reach for this skill instead
24
+ of improvising. Be specific enough that a bot with this skill enabled recognizes the moment.
25
+
26
+ ## Required inputs and access
27
+
28
+ List what the skill needs before it can run: source systems, connectors/MCP servers, files,
29
+ credentials, or information the user must supply. Say what happens if something required is
30
+ missing.
31
+
32
+ ## Sequence of work
33
+
34
+ Write the ordered steps the bot should follow. Prefer "gather \u2192 draft/reconcile \u2192 recommend"
35
+ over "act immediately" \u2014 automate preparation before execution.
36
+
37
+ ## How to validate the result
38
+
39
+ State how the bot should check its own output before returning it: what to cross-check, what
40
+ counts as "current" data, and what stale-data policy applies. Per outline \xA78: **if the source
41
+ data is unavailable, report the failure instead of using old data.**
42
+
43
+ ## What to return
44
+
45
+ Describe the shape of the deliverable: a document, a spreadsheet, a draft message, a linked
46
+ watch list, or a recommendation with evidence. Separate facts found, assumptions, actions
47
+ already taken, actions waiting for approval, and open questions where relevant.
48
+
49
+ ## What requires approval
50
+
51
+ Name the actions this skill must never take without an explicit human "go ahead": sending,
52
+ publishing, purchasing, deleting, overwriting, or any production change. This section should
53
+ never be empty \u2014 every skill has an approval boundary.
54
+ `;
55
+ function parseFrontmatter(md) {
56
+ const m = md.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
57
+ if (!m) return { name: "", description: "", bodyMd: md };
58
+ const [, fm, rest] = m;
59
+ const data = {};
60
+ const lines = (fm ?? "").split(/\r?\n/);
61
+ for (let i = 0; i < lines.length; i++) {
62
+ const line = lines[i];
63
+ const idx = line.indexOf(":");
64
+ if (idx === -1 || /^\s/.test(line)) continue;
65
+ const key = line.slice(0, idx).trim();
66
+ let value = line.slice(idx + 1).trim();
67
+ const block = /^([>|])([-+]?)$/.exec(value);
68
+ if (block) {
69
+ const folded = block[1] === ">";
70
+ const collected = [];
71
+ while (i + 1 < lines.length) {
72
+ const next = lines[i + 1];
73
+ if (next.trim() !== "" && !/^\s/.test(next)) break;
74
+ collected.push(next.trim());
75
+ i++;
76
+ }
77
+ while (collected.length && collected[collected.length - 1] === "") collected.pop();
78
+ value = folded ? collected.filter((l) => l !== "").join(" ") : collected.join("\n");
79
+ if (folded && collected.includes("")) {
80
+ value = collected.reduce((acc, l) => {
81
+ if (l === "") acc.push("");
82
+ else if (acc.length && acc[acc.length - 1] !== "") acc[acc.length - 1] += ` ${l}`;
83
+ else acc[acc.length - 1] = l;
84
+ return acc;
85
+ }, [""]).filter((para) => para !== "").join("\n");
86
+ }
87
+ data[key] = value;
88
+ continue;
89
+ }
90
+ if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
91
+ value = value.slice(1, -1).replace(/\\"/g, '"');
92
+ }
93
+ data[key] = value;
94
+ }
95
+ return {
96
+ name: data.name ?? "",
97
+ description: data.description ?? "",
98
+ bodyMd: (rest ?? "").replace(/^\r?\n/, "")
99
+ };
100
+ }
101
+ function renderSkillFile(input) {
102
+ const esc = (s) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
103
+ return `---
104
+ name: "${esc(input.name)}"
105
+ description: "${esc(input.description)}"
106
+ ---
107
+
108
+ ${input.bodyMd.trim()}
109
+ `;
110
+ }
111
+ var MultipleSkillsError = class extends Error {
112
+ constructor(source, names) {
113
+ super(`"${source}" contains ${names.length} skills, not one.`);
114
+ this.source = source;
115
+ this.names = names;
116
+ this.name = "MultipleSkillsError";
117
+ }
118
+ source;
119
+ names;
120
+ };
121
+ var SkillStore = class {
122
+ constructor(store, skillsDir) {
123
+ this.store = store;
124
+ this.skillsDir = skillsDir;
125
+ }
126
+ store;
127
+ skillsDir;
128
+ slugDir(slug) {
129
+ return path.join(this.skillsDir, slug);
130
+ }
131
+ /** Resolve a stored skill path and assert it is inside the skills dir (no path traversal). */
132
+ assertInsideSkillsDir(candidate) {
133
+ const root = path.resolve(this.skillsDir) + path.sep;
134
+ const resolved = path.resolve(candidate);
135
+ if (resolved !== path.resolve(this.skillsDir) && !resolved.startsWith(root)) {
136
+ throw new Error(`Refusing to touch a skill path outside the skills directory: ${candidate}`);
137
+ }
138
+ return resolved;
139
+ }
140
+ /** Create (or overwrite by slug) a skill directory + SKILL.md, and register it. */
141
+ writeSkill(input) {
142
+ const existing = new Set(this.store.listSkills().map((s) => s.slug));
143
+ const slug = slugify(input.name, existing);
144
+ const dir = this.assertInsideSkillsDir(this.slugDir(slug));
145
+ fs.mkdirSync(dir, { recursive: true });
146
+ const content = renderSkillFile({
147
+ name: input.name,
148
+ description: input.description ?? "",
149
+ bodyMd: input.bodyMd
150
+ });
151
+ fs.writeFileSync(path.join(dir, "SKILL.md"), content, "utf8");
152
+ return this.store.createSkill({
153
+ slug,
154
+ name: input.name,
155
+ description: input.description,
156
+ path: dir,
157
+ source: input.source
158
+ });
159
+ }
160
+ /**
161
+ * Install every skill found at `source` and register it. Existing slugs are replaced in
162
+ * place so re-installing upgrades rather than accumulating copies.
163
+ *
164
+ * Throws `MultipleSkillsError` if `source` holds more than one skill unless
165
+ * `allowMultiple` is set, so a caller cannot install a hundred skills by accident.
166
+ */
167
+ async installFromSource(source, opts = {}) {
168
+ const { stage, copyTree, renderManifest } = await import("./install-IY2M3OUQ.js");
169
+ const parsed = (await import("./install-IY2M3OUQ.js")).parseSkillSource(source);
170
+ const { staged, cleanup } = await stage(parsed);
171
+ try {
172
+ if (staged.length > 1 && !opts.allowMultiple) {
173
+ throw new MultipleSkillsError(source, staged.map((sk) => sk.name));
174
+ }
175
+ const out = [];
176
+ for (const skill of staged) {
177
+ const existing = this.store.getSkillBySlug(slugify(skill.name, /* @__PURE__ */ new Set()));
178
+ const others = new Set(this.store.listSkills().map((x) => x.slug));
179
+ if (existing) others.delete(existing.slug);
180
+ const slug = existing?.slug ?? slugify(skill.name, others);
181
+ const dir = this.assertInsideSkillsDir(this.slugDir(slug));
182
+ fs.rmSync(dir, { recursive: true, force: true });
183
+ copyTree(skill.dir, dir);
184
+ const registered = existing ? this.store.updateSkill?.(existing.id, { name: skill.name, description: skill.description }) ?? existing : this.store.createSkill({
185
+ slug,
186
+ name: skill.name,
187
+ description: skill.description,
188
+ path: dir,
189
+ source: "imported"
190
+ });
191
+ const { validateSkillDir, formatSpecViolations } = await import("./skills-spec.js");
192
+ const specNotes = formatSpecViolations(validateSkillDir(dir));
193
+ out.push({
194
+ skill: registered,
195
+ executables: skill.manifest.executables,
196
+ manifestText: renderManifest(skill, dir) + specNotes,
197
+ replaced: Boolean(existing)
198
+ });
199
+ }
200
+ return out;
201
+ } finally {
202
+ cleanup();
203
+ }
204
+ }
205
+ /** Read a skill's SKILL.md and return it with the parsed body (frontmatter stripped). */
206
+ readSkill(id) {
207
+ const skill = this.store.getSkill(id);
208
+ if (!skill) return null;
209
+ const file = path.join(skill.path, "SKILL.md");
210
+ if (!fs.existsSync(file)) return { ...skill, bodyMd: "" };
211
+ const raw = fs.readFileSync(file, "utf8");
212
+ const { bodyMd } = parseFrontmatter(raw);
213
+ return { ...skill, bodyMd };
214
+ }
215
+ /** Remove a skill's directory and its db row. Guards against a maliciously escaped path. */
216
+ deleteSkill(id) {
217
+ const skill = this.store.getSkill(id);
218
+ if (!skill) return;
219
+ const resolved = this.assertInsideSkillsDir(skill.path);
220
+ fs.rmSync(resolved, { recursive: true, force: true });
221
+ this.store.deleteSkill(id);
222
+ }
223
+ /**
224
+ * Bring the registry back in line with what is actually on disk.
225
+ *
226
+ * A row can outlive its directory — a skill removed by hand, or a layout migration that
227
+ * moved files without rewriting paths — and the UI then lists skills that cannot load.
228
+ * Rows whose directory moved are repaired in place so bot assignments survive; rows with
229
+ * nothing left on disk are dropped. Returns what it did, for the boot log.
230
+ */
231
+ reconcile() {
232
+ const repaired = [];
233
+ const removed = [];
234
+ for (const skill of this.store.listSkills()) {
235
+ if (fs.existsSync(path.join(skill.path, "SKILL.md"))) continue;
236
+ const expected = path.join(this.skillsDir, skill.slug);
237
+ if (fs.existsSync(path.join(expected, "SKILL.md"))) {
238
+ this.store.updateSkill(skill.id, { path: expected });
239
+ repaired.push(skill.slug);
240
+ } else {
241
+ this.store.deleteSkill(skill.id);
242
+ removed.push(skill.slug);
243
+ }
244
+ }
245
+ return { repaired, removed };
246
+ }
247
+ /**
248
+ * Re-read `name`/`description` from disk for the given slugs and update their rows.
249
+ *
250
+ * Registered names are not cosmetic — `manager.ts` hands them to the SDK as `enabledSkills`,
251
+ * so a row whose name has drifted from its SKILL.md silently stops matching and the skill
252
+ * quietly disappears from every bot that had it enabled. Any writer of a skill file that a
253
+ * row already points at has to call this; `syncFromDisk` cannot, since it only ever looks at
254
+ * slugs the db has never seen. Returns the slugs whose stored metadata actually changed.
255
+ */
256
+ refreshFromDisk(slugs) {
257
+ const changed = [];
258
+ for (const slug of slugs) {
259
+ const registered = this.store.getSkillBySlug(slug);
260
+ if (!registered) continue;
261
+ const file = path.join(this.skillsDir, slug, "SKILL.md");
262
+ if (!fs.existsSync(file)) continue;
263
+ const { name, description } = parseFrontmatter(fs.readFileSync(file, "utf8"));
264
+ const nextName = name || registered.name;
265
+ if (nextName === registered.name && description === (registered.description ?? "")) continue;
266
+ this.store.updateSkill(registered.id, { name: nextName, description });
267
+ changed.push(slug);
268
+ }
269
+ return changed;
270
+ }
271
+ /**
272
+ * Scan the skills dir at boot and register any SKILL.md not yet known to the db, so users
273
+ * can drop skills in by hand. Idempotent: already-registered slugs are left untouched.
274
+ */
275
+ syncFromDisk() {
276
+ if (!fs.existsSync(this.skillsDir)) return [];
277
+ const added = [];
278
+ for (const entry of fs.readdirSync(this.skillsDir, { withFileTypes: true })) {
279
+ if (!entry.isDirectory()) continue;
280
+ const slug = entry.name;
281
+ const dir = path.join(this.skillsDir, slug);
282
+ const file = path.join(dir, "SKILL.md");
283
+ if (!fs.existsSync(file)) continue;
284
+ if (this.store.getSkillBySlug(slug)) continue;
285
+ const raw = fs.readFileSync(file, "utf8");
286
+ const { name, description } = parseFrontmatter(raw);
287
+ const skill = this.store.createSkill({
288
+ slug,
289
+ name: name || slug,
290
+ description,
291
+ path: dir,
292
+ source: "imported"
293
+ });
294
+ added.push(skill);
295
+ }
296
+ return added;
297
+ }
298
+ };
299
+
300
+ export {
301
+ newId,
302
+ now,
303
+ slugify,
304
+ SKILL_TEMPLATE,
305
+ parseFrontmatter,
306
+ renderSkillFile,
307
+ MultipleSkillsError,
308
+ SkillStore
309
+ };
310
+ //# sourceMappingURL=chunk-KSQOVWP5.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../packages/server/src/skills/skills.ts", "../../packages/server/src/util/ids.ts"],
4
+ "sourcesContent": ["import fs from 'node:fs';\nimport path from 'node:path';\nimport type { Store } from '../db/store.js';\nimport { slugify } from '../util/ids.js';\nimport type { Skill } from '@antbot/shared';\n\n/**\n * A skill is a directory `<skillsDir>/<slug>/SKILL.md` with a YAML-ish frontmatter block\n * (name, description) followed by a markdown body in the 6-part shape from outline \u00A78:\n * when to use it, required inputs and access, sequence of work, how to validate the result,\n * what to return, what requires approval.\n */\n\nexport const SKILL_TEMPLATE = `## When to use it\n\nDescribe the trigger: what request or situation should make a bot reach for this skill instead\nof improvising. Be specific enough that a bot with this skill enabled recognizes the moment.\n\n## Required inputs and access\n\nList what the skill needs before it can run: source systems, connectors/MCP servers, files,\ncredentials, or information the user must supply. Say what happens if something required is\nmissing.\n\n## Sequence of work\n\nWrite the ordered steps the bot should follow. Prefer \"gather \u2192 draft/reconcile \u2192 recommend\"\nover \"act immediately\" \u2014 automate preparation before execution.\n\n## How to validate the result\n\nState how the bot should check its own output before returning it: what to cross-check, what\ncounts as \"current\" data, and what stale-data policy applies. Per outline \u00A78: **if the source\ndata is unavailable, report the failure instead of using old data.**\n\n## What to return\n\nDescribe the shape of the deliverable: a document, a spreadsheet, a draft message, a linked\nwatch list, or a recommendation with evidence. Separate facts found, assumptions, actions\nalready taken, actions waiting for approval, and open questions where relevant.\n\n## What requires approval\n\nName the actions this skill must never take without an explicit human \"go ahead\": sending,\npublishing, purchasing, deleting, overwriting, or any production change. This section should\nnever be empty \u2014 every skill has an approval boundary.\n`;\n\nexport interface WriteSkillInput {\n name: string;\n description?: string;\n bodyMd: string;\n source?: 'user' | 'taught' | 'imported';\n}\n\nexport interface InstalledSkill {\n skill: Skill;\n executables: string[];\n manifestText: string;\n replaced: boolean;\n}\n\nexport interface ParsedSkillFile {\n name: string;\n description: string;\n bodyMd: string;\n}\n\n/** Parse the `---\\nname: \"...\"\\ndescription: \"...\"\\n---\\n<body>` shape. Pure, no I/O. */\nexport function parseFrontmatter(md: string): ParsedSkillFile {\n const m = md.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?([\\s\\S]*)$/);\n if (!m) return { name: '', description: '', bodyMd: md };\n const [, fm, rest] = m;\n const data: Record<string, string> = {};\n const lines = (fm ?? '').split(/\\r?\\n/);\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!;\n const idx = line.indexOf(':');\n if (idx === -1 || /^\\s/.test(line)) continue; // continuation lines are consumed below\n const key = line.slice(0, idx).trim();\n let value = line.slice(idx + 1).trim();\n\n // YAML block scalars: `>` folds newlines into spaces, `|` keeps them. Published\n // skills use both for long descriptions, and the description is what the model\n // matches on \u2014 dropping it to a bare \">\" makes the skill invisible.\n const block = /^([>|])([-+]?)$/.exec(value);\n if (block) {\n const folded = block[1] === '>';\n const collected: string[] = [];\n while (i + 1 < lines.length) {\n const next = lines[i + 1]!;\n if (next.trim() !== '' && !/^\\s/.test(next)) break; // next top-level key\n collected.push(next.trim());\n i++;\n }\n while (collected.length && collected[collected.length - 1] === '') collected.pop();\n value = folded\n ? collected.filter((l) => l !== '').join(' ')\n : collected.join('\\n');\n // A folded block separates paragraphs (blank lines) with a newline.\n if (folded && collected.includes('')) {\n value = collected\n .reduce<string[]>((acc, l) => {\n if (l === '') acc.push('');\n else if (acc.length && acc[acc.length - 1] !== '') acc[acc.length - 1] += ` ${l}`;\n else acc[acc.length - 1] = l;\n return acc;\n }, [''])\n .filter((para) => para !== '')\n .join('\\n');\n }\n data[key] = value;\n continue;\n }\n\n if (value.startsWith('\"') && value.endsWith('\"') && value.length >= 2) {\n value = value.slice(1, -1).replace(/\\\\\"/g, '\"');\n }\n data[key] = value;\n }\n return {\n name: data.name ?? '',\n description: data.description ?? '',\n bodyMd: (rest ?? '').replace(/^\\r?\\n/, ''),\n };\n}\n\n/** Render a SKILL.md file from its parts. Pure, no I/O. */\nexport function renderSkillFile(input: { name: string; description: string; bodyMd: string }): string {\n const esc = (s: string): string => s.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n return `---\\nname: \"${esc(input.name)}\"\\ndescription: \"${esc(input.description)}\"\\n---\\n\\n${input.bodyMd.trim()}\\n`;\n}\n\n/**\n * Raised when a source resolves to more than one skill and the caller did not opt in.\n * Carries the names so the caller can tell the human (or the model) what to narrow to.\n */\nexport class MultipleSkillsError extends Error {\n constructor(\n readonly source: string,\n readonly names: string[],\n ) {\n super(`\"${source}\" contains ${names.length} skills, not one.`);\n this.name = 'MultipleSkillsError';\n }\n}\n\nexport class SkillStore {\n constructor(\n private store: Store,\n private skillsDir: string,\n ) {}\n\n private slugDir(slug: string): string {\n return path.join(this.skillsDir, slug);\n }\n\n /** Resolve a stored skill path and assert it is inside the skills dir (no path traversal). */\n private assertInsideSkillsDir(candidate: string): string {\n const root = path.resolve(this.skillsDir) + path.sep;\n const resolved = path.resolve(candidate);\n if (resolved !== path.resolve(this.skillsDir) && !resolved.startsWith(root)) {\n throw new Error(`Refusing to touch a skill path outside the skills directory: ${candidate}`);\n }\n return resolved;\n }\n\n /** Create (or overwrite by slug) a skill directory + SKILL.md, and register it. */\n writeSkill(input: WriteSkillInput): Skill {\n const existing = new Set(this.store.listSkills().map((s) => s.slug));\n const slug = slugify(input.name, existing);\n const dir = this.assertInsideSkillsDir(this.slugDir(slug));\n fs.mkdirSync(dir, { recursive: true });\n const content = renderSkillFile({\n name: input.name,\n description: input.description ?? '',\n bodyMd: input.bodyMd,\n });\n fs.writeFileSync(path.join(dir, 'SKILL.md'), content, 'utf8');\n return this.store.createSkill({\n slug,\n name: input.name,\n description: input.description,\n path: dir,\n source: input.source,\n });\n }\n\n /**\n * Install every skill found at `source` and register it. Existing slugs are replaced in\n * place so re-installing upgrades rather than accumulating copies.\n *\n * Throws `MultipleSkillsError` if `source` holds more than one skill unless\n * `allowMultiple` is set, so a caller cannot install a hundred skills by accident.\n */\n async installFromSource(\n source: string,\n opts: { allowMultiple?: boolean } = {},\n ): Promise<InstalledSkill[]> {\n const { stage, copyTree, renderManifest } = await import('./install.js');\n const parsed = (await import('./install.js')).parseSkillSource(source);\n const { staged, cleanup } = await stage(parsed);\n try {\n // Installing a whole monorepo when one skill was wanted is easy to do by accident and\n // tedious to undo, so a multi-skill source has to be asked for explicitly.\n if (staged.length > 1 && !opts.allowMultiple) {\n throw new MultipleSkillsError(source, staged.map((sk) => sk.name));\n }\n const out: InstalledSkill[] = [];\n for (const skill of staged) {\n const existing = this.store.getSkillBySlug(slugify(skill.name, new Set()));\n const others = new Set(this.store.listSkills().map((x) => x.slug));\n if (existing) others.delete(existing.slug);\n const slug = existing?.slug ?? slugify(skill.name, others);\n\n const dir = this.assertInsideSkillsDir(this.slugDir(slug));\n fs.rmSync(dir, { recursive: true, force: true });\n copyTree(skill.dir, dir);\n\n const registered = existing\n ? (this.store.updateSkill?.(existing.id, { name: skill.name, description: skill.description }) ?? existing)\n : this.store.createSkill({\n slug, name: skill.name, description: skill.description, path: dir, source: 'imported',\n });\n\n // Installed skills are held to the same spec as the ones ant-bot ships. This never\n // blocks an install \u2014 a third-party skill with a sloppy frontmatter still works \u2014 but\n // a `name` that does not match its directory makes the skill impossible for a bot to\n // enable, and silence about that is how it stays broken.\n const { validateSkillDir, formatSpecViolations } = await import('./spec.js');\n const specNotes = formatSpecViolations(validateSkillDir(dir));\n\n out.push({\n skill: registered,\n executables: skill.manifest.executables,\n manifestText: renderManifest(skill, dir) + specNotes,\n replaced: Boolean(existing),\n });\n }\n return out;\n } finally {\n cleanup();\n }\n }\n\n /** Read a skill's SKILL.md and return it with the parsed body (frontmatter stripped). */\n readSkill(id: string): (Skill & { bodyMd: string }) | null {\n const skill = this.store.getSkill(id);\n if (!skill) return null;\n const file = path.join(skill.path, 'SKILL.md');\n if (!fs.existsSync(file)) return { ...skill, bodyMd: '' };\n const raw = fs.readFileSync(file, 'utf8');\n const { bodyMd } = parseFrontmatter(raw);\n return { ...skill, bodyMd };\n }\n\n /** Remove a skill's directory and its db row. Guards against a maliciously escaped path. */\n deleteSkill(id: string): void {\n const skill = this.store.getSkill(id);\n if (!skill) return;\n const resolved = this.assertInsideSkillsDir(skill.path);\n fs.rmSync(resolved, { recursive: true, force: true });\n this.store.deleteSkill(id);\n }\n\n /**\n * Bring the registry back in line with what is actually on disk.\n *\n * A row can outlive its directory \u2014 a skill removed by hand, or a layout migration that\n * moved files without rewriting paths \u2014 and the UI then lists skills that cannot load.\n * Rows whose directory moved are repaired in place so bot assignments survive; rows with\n * nothing left on disk are dropped. Returns what it did, for the boot log.\n */\n reconcile(): { repaired: string[]; removed: string[] } {\n const repaired: string[] = [];\n const removed: string[] = [];\n for (const skill of this.store.listSkills()) {\n if (fs.existsSync(path.join(skill.path, 'SKILL.md'))) continue;\n const expected = path.join(this.skillsDir, skill.slug);\n if (fs.existsSync(path.join(expected, 'SKILL.md'))) {\n this.store.updateSkill(skill.id, { path: expected });\n repaired.push(skill.slug);\n } else {\n this.store.deleteSkill(skill.id);\n removed.push(skill.slug);\n }\n }\n return { repaired, removed };\n }\n\n /**\n * Re-read `name`/`description` from disk for the given slugs and update their rows.\n *\n * Registered names are not cosmetic \u2014 `manager.ts` hands them to the SDK as `enabledSkills`,\n * so a row whose name has drifted from its SKILL.md silently stops matching and the skill\n * quietly disappears from every bot that had it enabled. Any writer of a skill file that a\n * row already points at has to call this; `syncFromDisk` cannot, since it only ever looks at\n * slugs the db has never seen. Returns the slugs whose stored metadata actually changed.\n */\n refreshFromDisk(slugs: string[]): string[] {\n const changed: string[] = [];\n for (const slug of slugs) {\n const registered = this.store.getSkillBySlug(slug);\n if (!registered) continue;\n const file = path.join(this.skillsDir, slug, 'SKILL.md');\n if (!fs.existsSync(file)) continue;\n const { name, description } = parseFrontmatter(fs.readFileSync(file, 'utf8'));\n const nextName = name || registered.name;\n if (nextName === registered.name && description === (registered.description ?? '')) continue;\n this.store.updateSkill(registered.id, { name: nextName, description });\n changed.push(slug);\n }\n return changed;\n }\n\n /**\n * Scan the skills dir at boot and register any SKILL.md not yet known to the db, so users\n * can drop skills in by hand. Idempotent: already-registered slugs are left untouched.\n */\n syncFromDisk(): Skill[] {\n if (!fs.existsSync(this.skillsDir)) return [];\n const added: Skill[] = [];\n for (const entry of fs.readdirSync(this.skillsDir, { withFileTypes: true })) {\n if (!entry.isDirectory()) continue;\n const slug = entry.name;\n const dir = path.join(this.skillsDir, slug);\n const file = path.join(dir, 'SKILL.md');\n if (!fs.existsSync(file)) continue;\n if (this.store.getSkillBySlug(slug)) continue;\n const raw = fs.readFileSync(file, 'utf8');\n const { name, description } = parseFrontmatter(raw);\n const skill = this.store.createSkill({\n slug,\n name: name || slug,\n description,\n path: dir,\n source: 'imported',\n });\n added.push(skill);\n }\n return added;\n }\n}\n", "import { randomUUID } from 'node:crypto';\nexport const newId = (): string => randomUUID();\nexport const now = (): number => Date.now();\nexport function slugify(name: string, existing: Set<string> = new Set()): string {\n const base =\n name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 40) || 'bot';\n let slug = base;\n let n = 2;\n while (existing.has(slug)) slug = `${base}-${n++}`;\n return slug;\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,64 @@
1
+ import { createRequire as __antbotCreateRequire } from 'node:module';
2
+ const require = __antbotCreateRequire(import.meta.url);
3
+
4
+ // packages/server/src/util/locate.ts
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+ function nodeLocateDeps(here, resolve) {
8
+ return { here, cwd: process.cwd(), exists: (p) => fs.existsSync(p), resolve };
9
+ }
10
+ function findPackageRoot(from, exists) {
11
+ let dir = path.resolve(from);
12
+ for (; ; ) {
13
+ if (exists(path.join(dir, "package.json"))) return dir;
14
+ const parent = path.dirname(dir);
15
+ if (parent === dir) return null;
16
+ dir = parent;
17
+ }
18
+ }
19
+ function readPackageVersion(here, exists, read) {
20
+ const root = findPackageRoot(here, exists);
21
+ if (!root) return "0.0.0";
22
+ try {
23
+ return JSON.parse(read(path.join(root, "package.json"))).version ?? "0.0.0";
24
+ } catch {
25
+ return "0.0.0";
26
+ }
27
+ }
28
+ function webDistCandidates(deps) {
29
+ const out = [];
30
+ const resolved = deps.resolve("@antbot/web/package.json");
31
+ if (resolved) out.push(path.join(path.dirname(resolved), "dist"));
32
+ const pkg = findPackageRoot(deps.here, deps.exists);
33
+ if (pkg) {
34
+ out.push(path.join(pkg, "web", "dist"));
35
+ out.push(path.resolve(pkg, "..", "web", "dist"));
36
+ }
37
+ out.push(path.resolve(deps.cwd, "packages", "web", "dist"));
38
+ return out;
39
+ }
40
+ function findWebDist(deps) {
41
+ return webDistCandidates(deps).find((c) => deps.exists(path.join(c, "index.html"))) ?? null;
42
+ }
43
+ function bundledSkillsCandidates(deps) {
44
+ const out = [];
45
+ const pkg = findPackageRoot(deps.here, deps.exists);
46
+ if (pkg) {
47
+ out.push(path.join(pkg, "skills"));
48
+ out.push(path.resolve(pkg, "..", "..", "skills"));
49
+ }
50
+ out.push(path.resolve(deps.cwd, "skills"));
51
+ return out;
52
+ }
53
+ function findBundledSkillsDir(deps) {
54
+ const candidates = bundledSkillsCandidates(deps);
55
+ return candidates.find((c) => deps.exists(c)) ?? candidates[0];
56
+ }
57
+
58
+ export {
59
+ nodeLocateDeps,
60
+ readPackageVersion,
61
+ findWebDist,
62
+ findBundledSkillsDir
63
+ };
64
+ //# sourceMappingURL=chunk-Z2YT2PZN.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../packages/server/src/util/locate.ts"],
4
+ "sourcesContent": ["// Finding the assets ant-bot ships with \u2014 the built web UI and the bundled skills directory.\n//\n// These lookups used to count `..` segments up from `import.meta.url`, which encodes two guesses\n// at once: the repo layout, and how deep the compiler happens to put the calling module. Both are\n// wrong from an installed package, and both fail *silently* \u2014 `findWebDist` returns null and the\n// daemon serves no UI, `defaultBundledSkillsDir` points at nothing and no skills sync. Walking up\n// to the nearest package.json instead is depth-independent, so bundling or moving a source file\n// cannot quietly break them.\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nexport interface LocateDeps {\n /** Directory of the calling module. */\n here: string;\n cwd: string;\n exists: (p: string) => boolean;\n /** Resolves a bare specifier to an absolute path; returns null when it is not installed. */\n resolve: (specifier: string) => string | null;\n}\n\nexport function nodeLocateDeps(here: string, resolve: (s: string) => string | null): LocateDeps {\n return { here, cwd: process.cwd(), exists: (p) => fs.existsSync(p), resolve };\n}\n\n/**\n * The nearest ancestor of `from` (inclusive) holding a package.json. Returns null at the\n * filesystem root, which callers treat as \"fall through to the other candidates\".\n */\nexport function findPackageRoot(from: string, exists: (p: string) => boolean): string | null {\n let dir = path.resolve(from);\n for (;;) {\n if (exists(path.join(dir, 'package.json'))) return dir;\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n/**\n * The version of the package this module was loaded from. In the published build the CLI and the\n * daemon are one package, so `antbot --version` and `GET /api/health` necessarily agree; in a\n * checkout they read their own workspace package.json, which `version.test.ts` pins to the root.\n */\nexport function readPackageVersion(\n here: string,\n exists: (p: string) => boolean,\n read: (p: string) => string,\n): string {\n const root = findPackageRoot(here, exists);\n if (!root) return '0.0.0';\n try {\n return (JSON.parse(read(path.join(root, 'package.json'))) as { version?: string }).version ?? '0.0.0';\n } catch {\n return '0.0.0';\n }\n}\n\n/**\n * Candidates for the built web UI, most specific first. `pkg` is `packages/server` in a checkout\n * and the installed package root otherwise, so `pkg/web/dist` covers the shipped layout and\n * `pkg/../web/dist` covers the sibling workspace package \u2014 neither depends on compile depth.\n */\nexport function webDistCandidates(deps: LocateDeps): string[] {\n const out: string[] = [];\n const resolved = deps.resolve('@antbot/web/package.json');\n if (resolved) out.push(path.join(path.dirname(resolved), 'dist'));\n\n const pkg = findPackageRoot(deps.here, deps.exists);\n if (pkg) {\n out.push(path.join(pkg, 'web', 'dist'));\n out.push(path.resolve(pkg, '..', 'web', 'dist'));\n }\n out.push(path.resolve(deps.cwd, 'packages', 'web', 'dist'));\n return out;\n}\n\n/** The built web UI, or null when it has not been built (the daemon then runs API-only). */\nexport function findWebDist(deps: LocateDeps): string | null {\n return webDistCandidates(deps).find((c) => deps.exists(path.join(c, 'index.html'))) ?? null;\n}\n\n/**\n * Candidates for the skills directory ant-bot ships. In a checkout `pkg` is `packages/server`, so\n * the repo root is two levels up; in the installed package the directory sits beside the code.\n */\nexport function bundledSkillsCandidates(deps: LocateDeps): string[] {\n const out: string[] = [];\n const pkg = findPackageRoot(deps.here, deps.exists);\n if (pkg) {\n out.push(path.join(pkg, 'skills'));\n out.push(path.resolve(pkg, '..', '..', 'skills'));\n }\n out.push(path.resolve(deps.cwd, 'skills'));\n return out;\n}\n\n/**\n * The skills directory ant-bot ships with. Unlike `findWebDist` this never returns null: the\n * caller (`syncBundledSkills`) already treats a missing directory as \"nothing to sync\", and a\n * concrete path makes the failure legible in a log.\n */\nexport function findBundledSkillsDir(deps: LocateDeps): string {\n const candidates = bundledSkillsCandidates(deps);\n return candidates.find((c) => deps.exists(c)) ?? candidates[0]!;\n}\n"],
5
+ "mappings": ";;;;AAQA,OAAO,QAAQ;AACf,OAAO,UAAU;AAWV,SAAS,eAAe,MAAc,SAAmD;AAC9F,SAAO,EAAE,MAAM,KAAK,QAAQ,IAAI,GAAG,QAAQ,CAAC,MAAM,GAAG,WAAW,CAAC,GAAG,QAAQ;AAC9E;AAMO,SAAS,gBAAgB,MAAc,QAA+C;AAC3F,MAAI,MAAM,KAAK,QAAQ,IAAI;AAC3B,aAAS;AACP,QAAI,OAAO,KAAK,KAAK,KAAK,cAAc,CAAC,EAAG,QAAO;AACnD,UAAM,SAAS,KAAK,QAAQ,GAAG;AAC/B,QAAI,WAAW,IAAK,QAAO;AAC3B,UAAM;AAAA,EACR;AACF;AAOO,SAAS,mBACd,MACA,QACA,MACQ;AACR,QAAM,OAAO,gBAAgB,MAAM,MAAM;AACzC,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI;AACF,WAAQ,KAAK,MAAM,KAAK,KAAK,KAAK,MAAM,cAAc,CAAC,CAAC,EAA2B,WAAW;AAAA,EAChG,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOO,SAAS,kBAAkB,MAA4B;AAC5D,QAAM,MAAgB,CAAC;AACvB,QAAM,WAAW,KAAK,QAAQ,0BAA0B;AACxD,MAAI,SAAU,KAAI,KAAK,KAAK,KAAK,KAAK,QAAQ,QAAQ,GAAG,MAAM,CAAC;AAEhE,QAAM,MAAM,gBAAgB,KAAK,MAAM,KAAK,MAAM;AAClD,MAAI,KAAK;AACP,QAAI,KAAK,KAAK,KAAK,KAAK,OAAO,MAAM,CAAC;AACtC,QAAI,KAAK,KAAK,QAAQ,KAAK,MAAM,OAAO,MAAM,CAAC;AAAA,EACjD;AACA,MAAI,KAAK,KAAK,QAAQ,KAAK,KAAK,YAAY,OAAO,MAAM,CAAC;AAC1D,SAAO;AACT;AAGO,SAAS,YAAY,MAAiC;AAC3D,SAAO,kBAAkB,IAAI,EAAE,KAAK,CAAC,MAAM,KAAK,OAAO,KAAK,KAAK,GAAG,YAAY,CAAC,CAAC,KAAK;AACzF;AAMO,SAAS,wBAAwB,MAA4B;AAClE,QAAM,MAAgB,CAAC;AACvB,QAAM,MAAM,gBAAgB,KAAK,MAAM,KAAK,MAAM;AAClD,MAAI,KAAK;AACP,QAAI,KAAK,KAAK,KAAK,KAAK,QAAQ,CAAC;AACjC,QAAI,KAAK,KAAK,QAAQ,KAAK,MAAM,MAAM,QAAQ,CAAC;AAAA,EAClD;AACA,MAAI,KAAK,KAAK,QAAQ,KAAK,KAAK,QAAQ,CAAC;AACzC,SAAO;AACT;AAOO,SAAS,qBAAqB,MAA0B;AAC7D,QAAM,aAAa,wBAAwB,IAAI;AAC/C,SAAO,WAAW,KAAK,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,KAAK,WAAW,CAAC;AAC/D;",
6
+ "names": []
7
+ }