@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,17 @@
1
+ import { createRequire as __antbotCreateRequire } from 'node:module';
2
+ const require = __antbotCreateRequire(import.meta.url);
3
+ import {
4
+ MultipleSkillsError,
5
+ SKILL_TEMPLATE,
6
+ SkillStore,
7
+ parseFrontmatter,
8
+ renderSkillFile
9
+ } from "./chunk-KSQOVWP5.js";
10
+ export {
11
+ MultipleSkillsError,
12
+ SKILL_TEMPLATE,
13
+ SkillStore,
14
+ parseFrontmatter,
15
+ renderSkillFile
16
+ };
17
+ //# sourceMappingURL=skills-66WRX64H.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -0,0 +1,172 @@
1
+ import { createRequire as __antbotCreateRequire } from 'node:module';
2
+ const require = __antbotCreateRequire(import.meta.url);
3
+ import {
4
+ parseFrontmatter
5
+ } from "./chunk-KSQOVWP5.js";
6
+
7
+ // packages/server/src/skills/spec.ts
8
+ import fs from "node:fs";
9
+ import path from "node:path";
10
+ var SPEC_LIMITS = {
11
+ NAME_MAX: 64,
12
+ DESCRIPTION_MAX: 1024,
13
+ COMPATIBILITY_MAX: 500,
14
+ /** "Keep your main SKILL.md under 500 lines." */
15
+ BODY_MAX_LINES: 500
16
+ };
17
+ var KNOWN_FIELDS = /* @__PURE__ */ new Set([
18
+ "name",
19
+ "description",
20
+ "license",
21
+ "compatibility",
22
+ "metadata",
23
+ "allowed-tools"
24
+ ]);
25
+ function readFrontmatterFields(raw) {
26
+ const m = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
27
+ if (!m) return { found: false, fields: /* @__PURE__ */ new Map() };
28
+ const fields = /* @__PURE__ */ new Map();
29
+ for (const line of (m[1] ?? "").split(/\r?\n/)) {
30
+ if (/^\s/.test(line) || line.trim() === "") continue;
31
+ const idx = line.indexOf(":");
32
+ if (idx === -1) continue;
33
+ fields.set(line.slice(0, idx).trim(), line.slice(idx + 1).trim());
34
+ }
35
+ return { found: true, fields };
36
+ }
37
+ function extractFileReferences(body) {
38
+ const withoutFences = body.replace(/^```[\s\S]*?^```/gm, "");
39
+ const out = /* @__PURE__ */ new Set();
40
+ for (const m of withoutFences.matchAll(/!?\[[^\]]*\]\(([^)\s]+)\)/g)) {
41
+ const target = (m[1] ?? "").split("#")[0].trim();
42
+ if (!target || /^[a-z][a-z0-9+.-]*:/i.test(target) || target.startsWith("/") || target.startsWith("#")) continue;
43
+ out.add(target);
44
+ }
45
+ for (const m of withoutFences.matchAll(/(?:^|[\s`("'])((?:scripts|references|assets)\/[\w./-]+)/g)) {
46
+ const target = (m[1] ?? "").replace(/[.,;:)`'"]+$/, "");
47
+ if (target.endsWith("/")) continue;
48
+ out.add(target);
49
+ }
50
+ return [...out];
51
+ }
52
+ function validateSkill(input) {
53
+ const v = [];
54
+ const err = (code, message, field) => {
55
+ v.push({ code, level: "error", message, field });
56
+ };
57
+ const warn = (code, message, field) => {
58
+ v.push({ code, level: "warning", message, field });
59
+ };
60
+ const { found, fields } = readFrontmatterFields(input.raw);
61
+ if (!found) {
62
+ err("frontmatter-missing", "SKILL.md must open with a YAML frontmatter block delimited by ---.");
63
+ return v;
64
+ }
65
+ const { name, description, bodyMd } = parseFrontmatter(input.raw);
66
+ if (!name) {
67
+ err("name-missing", "Frontmatter must set a non-empty `name`.", "name");
68
+ } else {
69
+ if (name.length > SPEC_LIMITS.NAME_MAX) {
70
+ err("name-too-long", `\`name\` is ${name.length} characters; the limit is ${SPEC_LIMITS.NAME_MAX}.`, "name");
71
+ }
72
+ if (!/^[a-z0-9-]+$/.test(name)) {
73
+ err("name-charset", `\`name\` may only contain lowercase letters, digits and hyphens \u2014 got "${name}".`, "name");
74
+ }
75
+ if (name.startsWith("-") || name.endsWith("-")) {
76
+ err("name-hyphen-edge", "`name` must not start or end with a hyphen.", "name");
77
+ }
78
+ if (name.includes("--")) {
79
+ err("name-consecutive-hyphens", "`name` must not contain consecutive hyphens.", "name");
80
+ }
81
+ if (name !== input.dirName) {
82
+ err("name-dir-mismatch", `\`name\` ("${name}") must match the skill's directory name ("${input.dirName}").`, "name");
83
+ }
84
+ }
85
+ if (!description.trim()) {
86
+ err("description-missing", "Frontmatter must set a non-empty `description` saying what the skill does and when to use it.", "description");
87
+ } else if (description.length > SPEC_LIMITS.DESCRIPTION_MAX) {
88
+ err("description-too-long", `\`description\` is ${description.length} characters; the limit is ${SPEC_LIMITS.DESCRIPTION_MAX}.`, "description");
89
+ }
90
+ const compatibility = fields.get("compatibility");
91
+ if (compatibility !== void 0 && compatibility.length > SPEC_LIMITS.COMPATIBILITY_MAX) {
92
+ err("compatibility-too-long", `\`compatibility\` is ${compatibility.length} characters; the limit is ${SPEC_LIMITS.COMPATIBILITY_MAX}.`, "compatibility");
93
+ }
94
+ for (const key of fields.keys()) {
95
+ if (!KNOWN_FIELDS.has(key)) {
96
+ warn("unknown-field", `\`${key}\` is not a spec field \u2014 put client-specific values under \`metadata\`.`, key);
97
+ }
98
+ }
99
+ const bodyLines = bodyMd.split("\n").length;
100
+ if (bodyLines > SPEC_LIMITS.BODY_MAX_LINES) {
101
+ warn("body-too-long", `SKILL.md body is ${bodyLines} lines; keep it under ${SPEC_LIMITS.BODY_MAX_LINES} and move detail into references/.`);
102
+ }
103
+ const present = new Set(input.files.map((f) => f.split(path.sep).join("/")));
104
+ for (const ref of extractFileReferences(bodyMd)) {
105
+ const normalized = ref.replace(/^\.\//, "");
106
+ if (!present.has(normalized)) {
107
+ err("reference-missing", `SKILL.md points at "${ref}", which the skill does not ship.`, ref);
108
+ } else if (normalized.split("/").length > 2) {
109
+ warn("reference-too-deep", `"${ref}" is more than one level deep; keep references directly under a top-level directory.`, ref);
110
+ }
111
+ }
112
+ return v;
113
+ }
114
+ function listSkillFiles(dir, prefix = "") {
115
+ const out = [];
116
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
117
+ if (entry.name === ".git" || entry.name === "node_modules") continue;
118
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
119
+ if (entry.isDirectory()) out.push(...listSkillFiles(path.join(dir, entry.name), rel));
120
+ else if (entry.isFile()) out.push(rel);
121
+ }
122
+ return out;
123
+ }
124
+ function validateSkillDir(dir) {
125
+ const file = path.join(dir, "SKILL.md");
126
+ if (!fs.existsSync(file)) {
127
+ return [{ code: "skill-md-missing", level: "error", message: `${dir} has no SKILL.md.` }];
128
+ }
129
+ return validateSkill({
130
+ dirName: path.basename(dir),
131
+ raw: fs.readFileSync(file, "utf8"),
132
+ files: listSkillFiles(dir)
133
+ });
134
+ }
135
+ function validateSkillsIn(root) {
136
+ if (!fs.existsSync(root)) return [];
137
+ const out = [];
138
+ for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
139
+ if (!entry.isDirectory()) continue;
140
+ const dir = path.join(root, entry.name);
141
+ if (!fs.existsSync(path.join(dir, "SKILL.md"))) continue;
142
+ out.push({ slug: entry.name, violations: validateSkillDir(dir) });
143
+ }
144
+ return out;
145
+ }
146
+ function formatSpecViolations(violations) {
147
+ if (violations.length === 0) return "";
148
+ const errors = violations.filter((v) => v.level === "error").length;
149
+ const lines = [
150
+ "",
151
+ errors > 0 ? `\u26A0 This skill does not conform to the Agent Skills spec (${errors} error(s)):` : "\u26A0 Spec warnings for this skill:"
152
+ ];
153
+ for (const v of violations) {
154
+ lines.push(` ${v.level === "error" ? "error" : "warn "} ${v.code} ${v.message}`);
155
+ }
156
+ if (violations.some((v) => v.code === "name-dir-mismatch")) {
157
+ lines.push("", " Until `name` matches the directory, bots cannot actually enable this skill.");
158
+ }
159
+ return `${lines.join("\n")}
160
+ `;
161
+ }
162
+ export {
163
+ SPEC_LIMITS,
164
+ extractFileReferences,
165
+ formatSpecViolations,
166
+ listSkillFiles,
167
+ readFrontmatterFields,
168
+ validateSkill,
169
+ validateSkillDir,
170
+ validateSkillsIn
171
+ };
172
+ //# sourceMappingURL=skills-spec.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../packages/server/src/skills/spec.ts"],
4
+ "sourcesContent": ["import fs from 'node:fs';\nimport path from 'node:path';\nimport { parseFrontmatter } from './skills.js';\n\n/**\n * Validation against the Agent Skills specification (`skills/SPEC.md`).\n *\n * Two things make this worth having as code rather than a review checklist. The frontmatter\n * `name` is not cosmetic \u2014 `manager.ts` passes it to the SDK as `enabledSkills`, so a name that\n * drifts from the spec (or from its directory) makes the skill silently unavailable to bots\n * rather than failing loudly. And a skill that points at `references/foo.md` files it does not\n * ship still loads fine; it just quietly instructs the model to read things that are not there.\n * Both failure modes are invisible at runtime, so they are caught here instead.\n */\n\nexport type ViolationLevel = 'error' | 'warning';\n\nexport interface SpecViolation {\n code: string;\n level: ViolationLevel;\n /** Frontmatter field or file path the violation is about, when there is one. */\n field?: string;\n message: string;\n}\n\nexport interface SkillSpecInput {\n /** The skill's directory name \u2014 the spec requires `name` to match it. */\n dirName: string;\n /** Full text of SKILL.md. */\n raw: string;\n /** Every file in the skill directory, relative to it, in posix form. */\n files: string[];\n}\n\nexport const SPEC_LIMITS = {\n NAME_MAX: 64,\n DESCRIPTION_MAX: 1024,\n COMPATIBILITY_MAX: 500,\n /** \"Keep your main SKILL.md under 500 lines.\" */\n BODY_MAX_LINES: 500,\n} as const;\n\nconst KNOWN_FIELDS = new Set([\n 'name', 'description', 'license', 'compatibility', 'metadata', 'allowed-tools',\n]);\n\n/** Top-level frontmatter keys and their raw text, plus whether a `---` block was found at all. */\nexport function readFrontmatterFields(raw: string): { found: boolean; fields: Map<string, string> } {\n const m = raw.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---/);\n if (!m) return { found: false, fields: new Map() };\n const fields = new Map<string, string>();\n for (const line of (m[1] ?? '').split(/\\r?\\n/)) {\n if (/^\\s/.test(line) || line.trim() === '') continue; // nested/continuation lines\n const idx = line.indexOf(':');\n if (idx === -1) continue;\n fields.set(line.slice(0, idx).trim(), line.slice(idx + 1).trim());\n }\n return { found: true, fields };\n}\n\n/**\n * Relative file paths the body points at. Fenced code blocks are skipped: they are full of\n * example paths that are not meant to resolve, and flagging those trains people to ignore\n * the linter.\n */\nexport function extractFileReferences(body: string): string[] {\n const withoutFences = body.replace(/^```[\\s\\S]*?^```/gm, '');\n const out = new Set<string>();\n\n // Markdown links and images: [text](path) \u2014 skip URLs, anchors and absolute paths.\n for (const m of withoutFences.matchAll(/!?\\[[^\\]]*\\]\\(([^)\\s]+)\\)/g)) {\n const target = (m[1] ?? '').split('#')[0]!.trim();\n if (!target || /^[a-z][a-z0-9+.-]*:/i.test(target) || target.startsWith('/') || target.startsWith('#')) continue;\n out.add(target);\n }\n // Bare mentions of the conventional directories, in prose or inline code:\n // `scripts/extract.py`, references/REFERENCE.md\n for (const m of withoutFences.matchAll(/(?:^|[\\s`(\"'])((?:scripts|references|assets)\\/[\\w./-]+)/g)) {\n const target = (m[1] ?? '').replace(/[.,;:)`'\"]+$/, '');\n if (target.endsWith('/')) continue;\n out.add(target);\n }\n return [...out];\n}\n\n/** Validate one skill against the spec. Pure \u2014 the caller supplies the file listing. */\nexport function validateSkill(input: SkillSpecInput): SpecViolation[] {\n const v: SpecViolation[] = [];\n const err = (code: string, message: string, field?: string): void => {\n v.push({ code, level: 'error', message, field });\n };\n const warn = (code: string, message: string, field?: string): void => {\n v.push({ code, level: 'warning', message, field });\n };\n\n const { found, fields } = readFrontmatterFields(input.raw);\n if (!found) {\n err('frontmatter-missing', 'SKILL.md must open with a YAML frontmatter block delimited by ---.');\n return v;\n }\n\n const { name, description, bodyMd } = parseFrontmatter(input.raw);\n\n // name\n if (!name) {\n err('name-missing', 'Frontmatter must set a non-empty `name`.', 'name');\n } else {\n if (name.length > SPEC_LIMITS.NAME_MAX) {\n err('name-too-long', `\\`name\\` is ${name.length} characters; the limit is ${SPEC_LIMITS.NAME_MAX}.`, 'name');\n }\n if (!/^[a-z0-9-]+$/.test(name)) {\n err('name-charset', `\\`name\\` may only contain lowercase letters, digits and hyphens \u2014 got \"${name}\".`, 'name');\n }\n if (name.startsWith('-') || name.endsWith('-')) {\n err('name-hyphen-edge', '`name` must not start or end with a hyphen.', 'name');\n }\n if (name.includes('--')) {\n err('name-consecutive-hyphens', '`name` must not contain consecutive hyphens.', 'name');\n }\n if (name !== input.dirName) {\n // Not cosmetic: the SDK is handed frontmatter names, so a mismatch makes the skill\n // impossible to enable by its directory slug.\n err('name-dir-mismatch', `\\`name\\` (\"${name}\") must match the skill's directory name (\"${input.dirName}\").`, 'name');\n }\n }\n\n // description\n if (!description.trim()) {\n err('description-missing', 'Frontmatter must set a non-empty `description` saying what the skill does and when to use it.', 'description');\n } else if (description.length > SPEC_LIMITS.DESCRIPTION_MAX) {\n err('description-too-long', `\\`description\\` is ${description.length} characters; the limit is ${SPEC_LIMITS.DESCRIPTION_MAX}.`, 'description');\n }\n\n // compatibility\n const compatibility = fields.get('compatibility');\n if (compatibility !== undefined && compatibility.length > SPEC_LIMITS.COMPATIBILITY_MAX) {\n err('compatibility-too-long', `\\`compatibility\\` is ${compatibility.length} characters; the limit is ${SPEC_LIMITS.COMPATIBILITY_MAX}.`, 'compatibility');\n }\n\n for (const key of fields.keys()) {\n if (!KNOWN_FIELDS.has(key)) {\n // The spec defines a closed set; anything else belongs under `metadata`. A warning, not\n // an error, because an unknown key is inert rather than harmful.\n warn('unknown-field', `\\`${key}\\` is not a spec field \u2014 put client-specific values under \\`metadata\\`.`, key);\n }\n }\n\n // SKILL.md is loaded whole once a skill activates, so length is a context cost, not a style issue.\n const bodyLines = bodyMd.split('\\n').length;\n if (bodyLines > SPEC_LIMITS.BODY_MAX_LINES) {\n warn('body-too-long', `SKILL.md body is ${bodyLines} lines; keep it under ${SPEC_LIMITS.BODY_MAX_LINES} and move detail into references/.`);\n }\n\n // File references\n const present = new Set(input.files.map((f) => f.split(path.sep).join('/')));\n for (const ref of extractFileReferences(bodyMd)) {\n const normalized = ref.replace(/^\\.\\//, '');\n if (!present.has(normalized)) {\n err('reference-missing', `SKILL.md points at \"${ref}\", which the skill does not ship.`, ref);\n } else if (normalized.split('/').length > 2) {\n warn('reference-too-deep', `\"${ref}\" is more than one level deep; keep references directly under a top-level directory.`, ref);\n }\n }\n\n return v;\n}\n\n/** List every file in a skill directory, relative and posix-style. */\nexport function listSkillFiles(dir: string, prefix = ''): string[] {\n const out: string[] = [];\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n if (entry.name === '.git' || entry.name === 'node_modules') continue;\n const rel = prefix ? `${prefix}/${entry.name}` : entry.name;\n if (entry.isDirectory()) out.push(...listSkillFiles(path.join(dir, entry.name), rel));\n else if (entry.isFile()) out.push(rel);\n }\n return out;\n}\n\n/** Validate a skill on disk. Returns a `skill-md-missing` error if there is no SKILL.md. */\nexport function validateSkillDir(dir: string): SpecViolation[] {\n const file = path.join(dir, 'SKILL.md');\n if (!fs.existsSync(file)) {\n return [{ code: 'skill-md-missing', level: 'error', message: `${dir} has no SKILL.md.` }];\n }\n return validateSkill({\n dirName: path.basename(dir),\n raw: fs.readFileSync(file, 'utf8'),\n files: listSkillFiles(dir),\n });\n}\n\n/** Validate every skill directory under `root`, skipping anything without a SKILL.md. */\nexport function validateSkillsIn(root: string): { slug: string; violations: SpecViolation[] }[] {\n if (!fs.existsSync(root)) return [];\n const out: { slug: string; violations: SpecViolation[] }[] = [];\n for (const entry of fs.readdirSync(root, { withFileTypes: true })) {\n if (!entry.isDirectory()) continue;\n const dir = path.join(root, entry.name);\n if (!fs.existsSync(path.join(dir, 'SKILL.md'))) continue;\n out.push({ slug: entry.name, violations: validateSkillDir(dir) });\n }\n return out;\n}\n\n/**\n * Render violations for a human reading install output. Returns '' when there are none, so\n * callers can append it unconditionally.\n */\nexport function formatSpecViolations(violations: SpecViolation[]): string {\n if (violations.length === 0) return '';\n const errors = violations.filter((v) => v.level === 'error').length;\n const lines = [\n '',\n errors > 0\n ? `\u26A0 This skill does not conform to the Agent Skills spec (${errors} error(s)):`\n : '\u26A0 Spec warnings for this skill:',\n ];\n for (const v of violations) {\n lines.push(` ${v.level === 'error' ? 'error' : 'warn '} ${v.code} ${v.message}`);\n }\n // A name that does not match its directory is the one violation that silently breaks the\n // skill at runtime, so it gets its own sentence rather than sitting in a list.\n if (violations.some((v) => v.code === 'name-dir-mismatch')) {\n lines.push('', ' Until `name` matches the directory, bots cannot actually enable this skill.');\n }\n return `${lines.join('\\n')}\\n`;\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,148 @@
1
+ import { createRequire as __antbotCreateRequire } from 'node:module';
2
+ const require = __antbotCreateRequire(import.meta.url);
3
+
4
+ // packages/server/src/computer/tools.ts
5
+ import path from "node:path";
6
+ import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
7
+ import { z } from "zod";
8
+ var DOCTRINE = "Prefer a real API/connector for this task if one is available \u2014 it is more reliable than clicking through a site. Never type passwords, one-time codes, or other secrets into any field here \u2014 ask the human to take over the computer instead. If a login wall, CAPTCHA, or 2FA prompt appears, stop immediately and report it in your reply; never attempt to solve or bypass a verification check.";
9
+ function ok(text) {
10
+ return { content: [{ type: "text", text }] };
11
+ }
12
+ function fail(err) {
13
+ const msg = err instanceof Error ? err.message : String(err);
14
+ return { content: [{ type: "text", text: `Browser action failed: ${msg}` }] };
15
+ }
16
+ function createBrowserToolServer(service, botId, workspace) {
17
+ return createSdkMcpServer({
18
+ name: "browser",
19
+ version: "1.0.0",
20
+ tools: [
21
+ tool(
22
+ "browser_navigate",
23
+ `Navigate this bot's browser screen to a URL. ${DOCTRINE}`,
24
+ { url: z.string().describe('URL or bare domain to open, e.g. "https://news.ycombinator.com"') },
25
+ async (args) => {
26
+ try {
27
+ return ok(await service.navigate(botId, args.url));
28
+ } catch (err) {
29
+ return fail(err);
30
+ }
31
+ }
32
+ ),
33
+ tool(
34
+ "browser_click",
35
+ `Click an element on this bot's current browser page, identified by a CSS selector. ${DOCTRINE}`,
36
+ { selector: z.string().describe("CSS selector of the element to click") },
37
+ async (args) => {
38
+ try {
39
+ return ok(await service.click(botId, args.selector));
40
+ } catch (err) {
41
+ return fail(err);
42
+ }
43
+ }
44
+ ),
45
+ tool(
46
+ "browser_type",
47
+ `Type text into a field on this bot's current browser page. ${DOCTRINE}`,
48
+ {
49
+ selector: z.string().describe("CSS selector of the input/textarea to type into"),
50
+ text: z.string().describe("text to type (never a password or one-time code)"),
51
+ submit: z.boolean().optional().describe("press Enter after typing, e.g. to submit a search box")
52
+ },
53
+ async (args) => {
54
+ try {
55
+ return ok(await service.type(botId, args.selector, args.text, { submit: args.submit }));
56
+ } catch (err) {
57
+ return fail(err);
58
+ }
59
+ }
60
+ ),
61
+ tool(
62
+ "browser_read",
63
+ `Read visible text from this bot's current browser page (whole page or one selector), or list its links. ${DOCTRINE}`,
64
+ {
65
+ selector: z.string().optional().describe("CSS selector to read text from; omit to read the whole page"),
66
+ links: z.boolean().optional().describe("true to list the page's links instead of reading text")
67
+ },
68
+ async (args) => {
69
+ try {
70
+ const text = args.links ? await service.readLinks(botId) : await service.readText(botId, args.selector);
71
+ return ok(text);
72
+ } catch (err) {
73
+ return fail(err);
74
+ }
75
+ }
76
+ ),
77
+ tool(
78
+ "browser_screenshot",
79
+ `Take a PNG screenshot of this bot's current browser screen and save it to the shared workspace. ${DOCTRINE}`,
80
+ { name: z.string().optional().describe("optional file name (without extension) for the screenshot") },
81
+ async (args) => {
82
+ try {
83
+ const safe = (args.name ?? `shot-${Date.now()}`).replace(/[^a-zA-Z0-9._-]/g, "-");
84
+ const file = path.join(workspace, "bots", botId, "screenshots", `${safe}.png`);
85
+ return ok(await service.screenshot(botId, file));
86
+ } catch (err) {
87
+ return fail(err);
88
+ }
89
+ }
90
+ ),
91
+ tool(
92
+ "browser_press",
93
+ `Press a single keyboard key on this bot's current browser page (e.g. "Enter", "Escape", "ArrowDown"). ${DOCTRINE}`,
94
+ { key: z.string().describe("key name, per Playwright keyboard.press conventions") },
95
+ async (args) => {
96
+ try {
97
+ return ok(await service.pressKey(botId, args.key));
98
+ } catch (err) {
99
+ return fail(err);
100
+ }
101
+ }
102
+ ),
103
+ tool(
104
+ "browser_wait_for",
105
+ `Wait for a CSS selector to appear on this bot's current browser page before continuing. ${DOCTRINE}`,
106
+ {
107
+ selector: z.string().describe("CSS selector to wait for"),
108
+ timeoutMs: z.number().optional().describe("milliseconds to wait, default 10000")
109
+ },
110
+ async (args) => {
111
+ try {
112
+ return ok(await service.waitFor(botId, args.selector, args.timeoutMs ?? 1e4));
113
+ } catch (err) {
114
+ return fail(err);
115
+ }
116
+ }
117
+ ),
118
+ tool(
119
+ "browser_scroll",
120
+ `Scroll this bot's current browser page vertically. ${DOCTRINE}`,
121
+ { dy: z.number().describe("pixels to scroll; positive scrolls down, negative scrolls up") },
122
+ async (args) => {
123
+ try {
124
+ return ok(await service.scroll(botId, args.dy));
125
+ } catch (err) {
126
+ return fail(err);
127
+ }
128
+ }
129
+ ),
130
+ tool(
131
+ "browser_back",
132
+ `Go back to the previous page on this bot's browser screen. ${DOCTRINE}`,
133
+ {},
134
+ async () => {
135
+ try {
136
+ return ok(await service.goBack(botId));
137
+ } catch (err) {
138
+ return fail(err);
139
+ }
140
+ }
141
+ )
142
+ ]
143
+ });
144
+ }
145
+ export {
146
+ createBrowserToolServer
147
+ };
148
+ //# sourceMappingURL=tools-P5537ASX.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../packages/server/src/computer/tools.ts"],
4
+ "sourcesContent": ["import path from 'node:path';\nimport { createSdkMcpServer, tool } from '@anthropic-ai/claude-agent-sdk';\nimport { z } from 'zod';\nimport type { BrowserService } from './browser.js';\n\n/**\n * Doctrine appended to every tool description (grok-bot-system-outline.md \u00A75, \u00A79, \u00A715):\n * connectors/APIs beat computer-use when one exists; secrets never go through the browser tools;\n * verification walls are a stop-and-notify signal, never something to solve.\n */\nconst DOCTRINE =\n 'Prefer a real API/connector for this task if one is available \u2014 it is more reliable than clicking through a site. ' +\n 'Never type passwords, one-time codes, or other secrets into any field here \u2014 ask the human to take over the ' +\n \"computer instead. If a login wall, CAPTCHA, or 2FA prompt appears, stop immediately and report it in your reply; \" +\n 'never attempt to solve or bypass a verification check.';\n\nfunction ok(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\nfunction fail(err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n return { content: [{ type: 'text' as const, text: `Browser action failed: ${msg}` }] };\n}\n\n/**\n * Builds the MCP tool server exposed to a single bot's turn for driving its browser \"screen\".\n * `botId` scopes every action to that bot's dedicated page; `workspace` anchors screenshot paths.\n */\nexport function createBrowserToolServer(service: BrowserService, botId: string, workspace: string) {\n return createSdkMcpServer({\n name: 'browser',\n version: '1.0.0',\n tools: [\n tool(\n 'browser_navigate',\n `Navigate this bot's browser screen to a URL. ${DOCTRINE}`,\n { url: z.string().describe('URL or bare domain to open, e.g. \"https://news.ycombinator.com\"') },\n async (args: { url: string }) => {\n try {\n return ok(await service.navigate(botId, args.url));\n } catch (err) {\n return fail(err);\n }\n },\n ),\n\n tool(\n 'browser_click',\n `Click an element on this bot's current browser page, identified by a CSS selector. ${DOCTRINE}`,\n { selector: z.string().describe('CSS selector of the element to click') },\n async (args: { selector: string }) => {\n try {\n return ok(await service.click(botId, args.selector));\n } catch (err) {\n return fail(err);\n }\n },\n ),\n\n tool(\n 'browser_type',\n `Type text into a field on this bot's current browser page. ${DOCTRINE}`,\n {\n selector: z.string().describe('CSS selector of the input/textarea to type into'),\n text: z.string().describe('text to type (never a password or one-time code)'),\n submit: z.boolean().optional().describe('press Enter after typing, e.g. to submit a search box'),\n },\n async (args: { selector: string; text: string; submit?: boolean }) => {\n try {\n return ok(await service.type(botId, args.selector, args.text, { submit: args.submit }));\n } catch (err) {\n return fail(err);\n }\n },\n ),\n\n tool(\n 'browser_read',\n `Read visible text from this bot's current browser page (whole page or one selector), or list its links. ${DOCTRINE}`,\n {\n selector: z.string().optional().describe('CSS selector to read text from; omit to read the whole page'),\n links: z.boolean().optional().describe('true to list the page\\'s links instead of reading text'),\n },\n async (args: { selector?: string; links?: boolean }) => {\n try {\n const text = args.links ? await service.readLinks(botId) : await service.readText(botId, args.selector);\n return ok(text);\n } catch (err) {\n return fail(err);\n }\n },\n ),\n\n tool(\n 'browser_screenshot',\n `Take a PNG screenshot of this bot's current browser screen and save it to the shared workspace. ${DOCTRINE}`,\n { name: z.string().optional().describe('optional file name (without extension) for the screenshot') },\n async (args: { name?: string }) => {\n try {\n const safe = (args.name ?? `shot-${Date.now()}`).replace(/[^a-zA-Z0-9._-]/g, '-');\n const file = path.join(workspace, 'bots', botId, 'screenshots', `${safe}.png`);\n return ok(await service.screenshot(botId, file));\n } catch (err) {\n return fail(err);\n }\n },\n ),\n\n tool(\n 'browser_press',\n `Press a single keyboard key on this bot's current browser page (e.g. \"Enter\", \"Escape\", \"ArrowDown\"). ${DOCTRINE}`,\n { key: z.string().describe('key name, per Playwright keyboard.press conventions') },\n async (args: { key: string }) => {\n try {\n return ok(await service.pressKey(botId, args.key));\n } catch (err) {\n return fail(err);\n }\n },\n ),\n\n tool(\n 'browser_wait_for',\n `Wait for a CSS selector to appear on this bot's current browser page before continuing. ${DOCTRINE}`,\n {\n selector: z.string().describe('CSS selector to wait for'),\n timeoutMs: z.number().optional().describe('milliseconds to wait, default 10000'),\n },\n async (args: { selector: string; timeoutMs?: number }) => {\n try {\n return ok(await service.waitFor(botId, args.selector, args.timeoutMs ?? 10000));\n } catch (err) {\n return fail(err);\n }\n },\n ),\n\n tool(\n 'browser_scroll',\n `Scroll this bot's current browser page vertically. ${DOCTRINE}`,\n { dy: z.number().describe('pixels to scroll; positive scrolls down, negative scrolls up') },\n async (args: { dy: number }) => {\n try {\n return ok(await service.scroll(botId, args.dy));\n } catch (err) {\n return fail(err);\n }\n },\n ),\n\n tool(\n 'browser_back',\n `Go back to the previous page on this bot's browser screen. ${DOCTRINE}`,\n {},\n async () => {\n try {\n return ok(await service.goBack(botId));\n } catch (err) {\n return fail(err);\n }\n },\n ),\n ],\n });\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@michael-joseph-miller/ant-bot",
3
+ "version": "0.1.0",
4
+ "description": "Local-first daemon and web UI hosting persistent, named Claude agents.",
5
+ "license": "MIT",
6
+ "author": "Michael Miller",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/mjmiller41/ant-bot.git"
10
+ },
11
+ "homepage": "https://github.com/mjmiller41/ant-bot#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/mjmiller41/ant-bot/issues"
14
+ },
15
+ "type": "module",
16
+ "bin": {
17
+ "antbot": "./dist/index.js"
18
+ },
19
+ "engines": {
20
+ "node": ">=24"
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "web",
25
+ "skills",
26
+ "README.md",
27
+ "CHANGELOG.md",
28
+ "LICENSE"
29
+ ],
30
+ "dependencies": {
31
+ "@anthropic-ai/claude-agent-sdk": "^0.3.238",
32
+ "better-sqlite3": "^13.0.3",
33
+ "playwright": "^1.62.1",
34
+ "fastify": "^5.12.1",
35
+ "@fastify/cors": "^11.1.0",
36
+ "@fastify/multipart": "^9.2.1",
37
+ "@fastify/static": "^8.2.0",
38
+ "@fastify/websocket": "^11.3.0",
39
+ "node-cron": "^4.6.0",
40
+ "smol-toml": "^1.4.2",
41
+ "zod": "^4.4.3"
42
+ },
43
+ "keywords": [
44
+ "claude",
45
+ "agent",
46
+ "ai",
47
+ "daemon",
48
+ "local-first",
49
+ "anthropic"
50
+ ]
51
+ }
@@ -0,0 +1,69 @@
1
+ # Bundled skills
2
+
3
+ Every directory here containing a `SKILL.md` ships with ant-bot and is installed into your skills
4
+ directory (`~/.ant-bot/skills/skills/`) on every start, ready to assign to a Bot. Five skills ship
5
+ today — `bug-repro`, `deep-research`, `inbox-digest`, `skill-author` and `weekly-report` — and they
6
+ double as the worked examples in `docs/SKILLS.md`.
7
+
8
+ `SPEC.md` here is the Agent Skills specification. Every skill in this directory conforms to it, and
9
+ a test enforces that (`packages/server/src/skills/spec.test.ts`), so a non-conforming skill cannot
10
+ land. Check your own with:
11
+
12
+ ```bash
13
+ antbot skill lint ./skills/my-skill # one skill
14
+ antbot skill lint ./skills # this directory
15
+ antbot skill lint # everything installed on this machine
16
+ ```
17
+
18
+ The `skill-author` skill is the workflow for writing a new skill or repairing one — including the
19
+ behavioral inventory that keeps a rewrite from quietly dropping instructions.
20
+
21
+ Add one here when a skill belongs with the code it operates on: it gets reviewed, versioned and
22
+ branched alongside everything else in the repo, and it reaches every install on the next upgrade.
23
+
24
+ ```
25
+ skills/
26
+ └── my-skill/
27
+ └── SKILL.md
28
+ ```
29
+
30
+ `SKILL.md` is the standard Claude skill format: frontmatter with `name` and `description`, followed
31
+ by the body. See `docs/SKILLS.md` for the six-section shape ant-bot's own skills follow, and read
32
+ `bug-repro`, `inbox-digest` and `weekly-report` before writing your first one — they are the
33
+ calibration for how specific each section should be. `name` must be lowercase letters, digits and
34
+ hyphens, and must match the directory name exactly; ant-bot hands frontmatter names to the SDK as
35
+ its enabled-skills filter, so a mismatch makes the skill silently unavailable rather than failing. (`deep-research` is a published-style skill
36
+ that deliberately does not follow that shape; `docs/SKILLS.md` says why.)
37
+
38
+ ```markdown
39
+ ---
40
+ name: my-skill
41
+ description: Use this when … (write it as a trigger, not a title — the model matches on it)
42
+ ---
43
+
44
+ ## When to use it
45
+ ...
46
+ ```
47
+
48
+ Supporting files — reference docs, templates, scripts — can sit alongside `SKILL.md` and are copied
49
+ with it. Anything executable is flagged when the skill is installed.
50
+
51
+ ## Your copy versus ours
52
+
53
+ A bundled skill has two owners, so the sync is not a plain copy. ant-bot records a hash of what it
54
+ wrote in `~/.ant-bot/skills/skills/.managed.json` and compares against it on the next boot:
55
+
56
+ | Your copy | What happens on the next start |
57
+ |---|---|
58
+ | Never installed | It gets installed |
59
+ | Untouched since ant-bot wrote it | It is refreshed when the shipped version changes |
60
+ | Edited by you | Left alone — your edits survive upgrades |
61
+ | Deleted by you | Stays deleted |
62
+ | Same slug, installed from elsewhere | Left alone; ant-bot never claims a skill it did not write |
63
+
64
+ Two consequences worth knowing. Editing your installed copy of a bundled skill *forks* it — it will
65
+ never pick up upstream changes again; edit the file in this directory instead if you want both. And
66
+ a contributor's loop still works exactly as before: change a file here, restart the daemon, and the
67
+ change is live, because the installed copy is untouched and therefore refreshed.
68
+
69
+ Skills installed from other sources (`antbot skill add …`) are never affected by any of this.