@michael-joseph-miller/ant-bot 0.1.0 → 0.1.1
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.
- package/CHANGELOG.md +23 -5
- package/README.md +69 -24
- package/dist/{browser-OHRD7YI3.js → browser-3UAU6ARY.js} +3 -3
- package/dist/{browser-OHRD7YI3.js.map → browser-3UAU6ARY.js.map} +1 -1
- package/dist/{bundled-46DUK5PG.js → bundled-26SJR7EB.js} +5 -5
- package/dist/{bundled-46DUK5PG.js.map → bundled-26SJR7EB.js.map} +1 -1
- package/dist/{chunk-AHAON6J7.js → chunk-CNCHYZVD.js} +2 -2
- package/dist/{chunk-AHAON6J7.js.map → chunk-CNCHYZVD.js.map} +1 -1
- package/dist/{chunk-Z2YT2PZN.js → chunk-GYWJUW4E.js} +6 -6
- package/dist/{chunk-Z2YT2PZN.js.map → chunk-GYWJUW4E.js.map} +3 -3
- package/dist/{chunk-7BOHBPB2.js → chunk-JCZBDGBA.js} +3 -3
- package/dist/{chunk-7BOHBPB2.js.map → chunk-JCZBDGBA.js.map} +1 -1
- package/dist/{chunk-KSQOVWP5.js → chunk-MSSHR6SW.js} +6 -6
- package/dist/chunk-MSSHR6SW.js.map +7 -0
- package/dist/{chunk-DYIJTUMY.js → chunk-XMIPBKUY.js} +8 -7
- package/dist/{chunk-DYIJTUMY.js.map → chunk-XMIPBKUY.js.map} +3 -3
- package/dist/index.js +28 -28
- package/dist/index.js.map +2 -2
- package/dist/{install-IY2M3OUQ.js → install-KTEW2CW3.js} +3 -3
- package/dist/{plugin-POMHVGD4.js → plugin-WYUCG6F7.js} +2 -2
- package/dist/{plugin-POMHVGD4.js.map → plugin-WYUCG6F7.js.map} +1 -1
- package/dist/{scheduler-Q7OHNGP6.js → scheduler-VZVHQWGD.js} +3 -3
- package/dist/scheduler-VZVHQWGD.js.map +7 -0
- package/dist/server.js +41 -41
- package/dist/server.js.map +3 -3
- package/dist/{skills-66WRX64H.js → skills-JUHWFBD6.js} +2 -2
- package/dist/skills-spec.js +2 -2
- package/dist/skills-spec.js.map +1 -1
- package/dist/{tools-P5537ASX.js → tools-YNE7ZRPR.js} +2 -2
- package/dist/{tools-P5537ASX.js.map → tools-YNE7ZRPR.js.map} +1 -1
- package/package.json +1 -1
- package/skills/README.md +1 -1
- package/web/dist/assets/index-CrbtUidR.js +130 -0
- package/web/dist/index.html +1 -1
- package/dist/chunk-KSQOVWP5.js.map +0 -7
- package/dist/scheduler-Q7OHNGP6.js.map +0 -7
- package/web/dist/assets/index-BLQ8rPiN.js +0 -130
- /package/dist/{install-IY2M3OUQ.js.map → install-KTEW2CW3.js.map} +0 -0
- /package/dist/{skills-66WRX64H.js.map → skills-JUHWFBD6.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,27 @@ All notable changes to ant-bot are recorded here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow
|
|
5
5
|
[semantic versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
-
## [
|
|
7
|
+
## [0.1.1] — 2026-08-23
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- **Repo layout flattened.** The four workspace packages moved out of `packages/` to the repo
|
|
12
|
+
root as `contract/`, `daemon/`, `ui/` and `cli/`, and their npm names follow
|
|
13
|
+
(`@antbot/shared` → `@antbot/contract`, `@antbot/server` → `@antbot/daemon`,
|
|
14
|
+
`@antbot/web` → `@antbot/ui`). Each now has a README saying what it is and why it exists.
|
|
15
|
+
No behaviour change; the published package is unaffected.
|
|
16
|
+
- **README leads with where your data lives** — a map of `~/.ant-bot`, what each directory holds,
|
|
17
|
+
and why `workspace/` is the security boundary rather than just a folder.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **`fable` and `haiku` model tiers for Bots.** `haiku` was already valid in the schema but no
|
|
22
|
+
picker offered it; `fable` is new. Both pickers now render the shared `MODEL_TIERS` list, so the
|
|
23
|
+
UI cannot offer a tier the API rejects, or hide one it accepts.
|
|
24
|
+
|
|
25
|
+
## [0.1.0] — 2026-08-23
|
|
26
|
+
|
|
27
|
+
First published release: `npm i -g @michael-joseph-miller/ant-bot`.
|
|
8
28
|
|
|
9
29
|
### Added
|
|
10
30
|
|
|
@@ -34,7 +54,5 @@ All notable changes to ant-bot are recorded here. The format follows
|
|
|
34
54
|
- Published as **`@michael-joseph-miller/ant-bot`**, not `ant-bot`. npm's name-similarity guard
|
|
35
55
|
refuses the unscoped name as too close to `antbot`, a security holding package from 2022 that
|
|
36
56
|
cannot be claimed. Scoped names are exempt from that check. The binary is still `antbot`.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
Initial version: the ant-bot daemon, web UI and CLI.
|
|
57
|
+
- The release procedure, the provenance requirements and the packaging traps now live in
|
|
58
|
+
`CLAUDE.md` under "Releasing"; `docs/PACKAGING-PLAN.md` has served its purpose and is deleted.
|
package/README.md
CHANGED
|
@@ -107,11 +107,41 @@ ln -s "$PWD/antbot" ~/.local/bin/antbot # or /usr/local/bin, or anywhere on yo
|
|
|
107
107
|
The launcher resolves symlinks, so it still finds the repo and rebuilds when sources change.
|
|
108
108
|
From the repo root you can also skip the `./` with `pnpm antbot status`.
|
|
109
109
|
|
|
110
|
-
###
|
|
110
|
+
### Where your data lives
|
|
111
111
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
Everything ant-bot creates lives in one directory you own, in plain files you can read. Nothing
|
|
113
|
+
is hidden in a service, and nothing leaves the machine. Go and look:
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
~/.ant-bot/
|
|
117
|
+
├── antbot.db SQLite: bots, threads, messages, approvals, rules, routines, usage
|
|
118
|
+
├── config.toml port and host (re-read each boot) + first-run setting defaults
|
|
119
|
+
├── workspace/ ← the Bots' computer. This is the security boundary.
|
|
120
|
+
│ ├── bots/<slug>/ each Bot's own folder: its files, and memory/*.md
|
|
121
|
+
│ └── projects/ shared project files — durable work lives here
|
|
122
|
+
├── skills/ installed skills, as a local Claude plugin
|
|
123
|
+
│ └── skills/<slug>/SKILL.md
|
|
124
|
+
├── attachments/ files you upload into threads
|
|
125
|
+
├── browser-profile/ the shared Chromium profile: cookies, logins, sessions
|
|
126
|
+
├── backups/ `antbot backup` archives, and pre-migration DB snapshots
|
|
127
|
+
└── logs/ daemon logs
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**`workspace/` is the important one.** It is the Bots' computer, and everything outside it is
|
|
131
|
+
*your* machine. A tool call that reaches outside the workspace is denied, or forced to a human
|
|
132
|
+
prompt, depending on your `localExecution` setting — and that check runs *before* any "allow" rule
|
|
133
|
+
you have written, so a broad rule cannot be used to reach `~/.ssh`. Work inside the workspace runs
|
|
134
|
+
without a prompt; work outside it does not. See [`docs/SECURITY.md`](docs/SECURITY.md).
|
|
135
|
+
|
|
136
|
+
**`workspace/bots/<slug>/memory/*.md`** is worth knowing about: those markdown files are read back
|
|
137
|
+
into that Bot's system prompt every turn. Editing one edits what the Bot remembers. Deleting one
|
|
138
|
+
makes it forget.
|
|
139
|
+
|
|
140
|
+
A Bot's folder outlives the Bot — deleting a Bot removes its threads and routines but leaves its
|
|
141
|
+
files, since the workspace is shared and other Bots may depend on them.
|
|
142
|
+
|
|
143
|
+
Set **`ANTBOT_HOME`** to point the daemon and CLI at a different data directory, e.g. for a second
|
|
144
|
+
profile or a test instance:
|
|
115
145
|
|
|
116
146
|
```bash
|
|
117
147
|
ANTBOT_HOME=/path/to/alt-home ./antbot start --port 4791
|
|
@@ -168,7 +198,7 @@ ANTBOT_HOME=/path/to/alt-home ./antbot start --port 4791
|
|
|
168
198
|
│ └──────┬──────────────────────────────────────────┘ │
|
|
169
199
|
│ │ │
|
|
170
200
|
│ ┌──────────────────────▼───────────────┐ ┌──────────────────────────────────────────────┐ │
|
|
171
|
-
│ │ Computer service (
|
|
201
|
+
│ │ Computer service (daemon/ │ │ Storage: SQLite (better-sqlite3, WAL) — │ │
|
|
172
202
|
│ │ src/computer): Playwright persistent │ │ bots, threads, messages, approvals, rules, │ │
|
|
173
203
|
│ │ profile, per-bot screen lock, │ │ skills, routines, mailbox, usage, settings + │ │
|
|
174
204
|
│ │ screencast, takeover │ │ ~/.ant-bot/workspace + attachments on disk │ │
|
|
@@ -184,7 +214,7 @@ Permission Gateway before the tool is allowed to run — this is true for the bu
|
|
|
184
214
|
|
|
185
215
|
## CLI commands
|
|
186
216
|
|
|
187
|
-
All commands are implemented in `
|
|
217
|
+
All commands are implemented in `cli`. Run them as `./antbot <command>` from the repo
|
|
188
218
|
root, or as plain `antbot <command>` once it is [on your `PATH`](#putting-antbot-on-your-path).
|
|
189
219
|
|
|
190
220
|
| Command | Description |
|
|
@@ -204,24 +234,40 @@ Every command takes `--help`, e.g. `antbot start --help`.
|
|
|
204
234
|
|
|
205
235
|
## Project layout
|
|
206
236
|
|
|
237
|
+
Four workspace packages at the repo root, each with its own README explaining what it is and why
|
|
238
|
+
it exists. The dependency direction only ever points one way:
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
contract ← daemon ← cli
|
|
242
|
+
↑
|
|
243
|
+
└──── ui
|
|
244
|
+
```
|
|
245
|
+
|
|
207
246
|
| Path | What it is |
|
|
208
247
|
|---|---|
|
|
209
|
-
| `
|
|
210
|
-
| `
|
|
211
|
-
| `
|
|
212
|
-
| `
|
|
213
|
-
| `skills/` | The skills that ship with ant-bot
|
|
214
|
-
| `
|
|
215
|
-
| `
|
|
248
|
+
| [`contract/`](contract/) | **The shared vocabulary.** Zod schemas and types for every entity, request, response and websocket event, plus `LIMITS`. Both the daemon and the UI import from here; it imports from nothing. One declaration per shape, so the two sides cannot drift. |
|
|
249
|
+
| [`daemon/`](daemon/) | **The thing that runs.** Fastify API + websocket, Bot turn queue, the Agent SDK wrapper, the Permission Gateway, the scheduler, the skills store, the browser service, and SQLite. Start reading at `src/app.ts`. |
|
|
250
|
+
| [`ui/`](ui/) | **The web interface.** React 19 + Vite + Tailwind v4. Built to `ui/dist/` and served by the daemon itself — there is no separate frontend server. |
|
|
251
|
+
| [`cli/`](cli/) | **The `antbot` command.** start/stop/status/doctor/open/skill/backup/restore/update. Kept separate from the daemon so `antbot doctor` still works when the daemon does not. |
|
|
252
|
+
| [`skills/`](skills/) | The skills that ship with ant-bot, plus [`SPEC.md`](skills/SPEC.md), the Agent Skills spec they conform to. Synced into `~/.ant-bot/skills` on every start, and never overwritten if you have edited your copy. |
|
|
253
|
+
| [`docs/`](docs/) | [`USER-GUIDE.md`](docs/USER-GUIDE.md) (the manual), [`SECURITY.md`](docs/SECURITY.md) (the trust model), [`API-CONTRACT.md`](docs/API-CONTRACT.md) (frozen HTTP/WS contract), [`SKILLS.md`](docs/SKILLS.md). Plus two historical design records that are deliberately not kept in sync with the code. |
|
|
254
|
+
| [`scripts/`](scripts/) | `build-package.mjs`, which assembles the publishable npm package into `dist-npm/`. |
|
|
255
|
+
| [`CLAUDE.md`](CLAUDE.md) | Guidance for agents — and the densest description of the invariants and traps in this codebase. Worth reading even if you are human. |
|
|
256
|
+
|
|
257
|
+
### Where to start reading
|
|
258
|
+
|
|
259
|
+
1. **Using it?** [`docs/USER-GUIDE.md`](docs/USER-GUIDE.md).
|
|
260
|
+
2. **Evaluating whether to trust it?** [`docs/SECURITY.md`](docs/SECURITY.md), then "Where your data lives" above.
|
|
261
|
+
3. **Changing it?** [`CLAUDE.md`](CLAUDE.md), then `daemon/src/app.ts` — the composition root, which builds every subsystem in order.
|
|
216
262
|
|
|
217
263
|
## Testing
|
|
218
264
|
|
|
219
265
|
Each package uses Vitest. Run all of them from the repo root with `pnpm test`, or per package:
|
|
220
266
|
|
|
221
267
|
```bash
|
|
222
|
-
pnpm --filter @antbot/
|
|
223
|
-
pnpm --filter @antbot/
|
|
224
|
-
pnpm --filter @antbot/
|
|
268
|
+
pnpm --filter @antbot/contract test
|
|
269
|
+
pnpm --filter @antbot/daemon test
|
|
270
|
+
pnpm --filter @antbot/ui test
|
|
225
271
|
pnpm --filter @antbot/cli test
|
|
226
272
|
```
|
|
227
273
|
|
|
@@ -229,20 +275,19 @@ Current totals, as run against this checkout:
|
|
|
229
275
|
|
|
230
276
|
| Package | Test files | Tests |
|
|
231
277
|
|---|---|---|
|
|
232
|
-
| `@antbot/
|
|
233
|
-
| `@antbot/
|
|
234
|
-
| `@antbot/
|
|
235
|
-
| `@antbot/cli` | 7 |
|
|
236
|
-
| **Total** | **34** | **
|
|
278
|
+
| `@antbot/contract` | 1 | 22 |
|
|
279
|
+
| `@antbot/daemon` | 19 | 450 |
|
|
280
|
+
| `@antbot/ui` | 7 | 52 |
|
|
281
|
+
| `@antbot/cli` | 7 | 116 |
|
|
282
|
+
| **Total** | **34** | **640** |
|
|
237
283
|
|
|
238
284
|
## Status / not built
|
|
239
285
|
|
|
240
286
|
Honestly, as of this checkout:
|
|
241
287
|
|
|
242
288
|
- **Container computer mode** — `computer.mode` is a valid `"host" | "container"` setting in the
|
|
243
|
-
schema and shows up in Settings, but only `host` mode has an implementation
|
|
244
|
-
|
|
245
|
-
`container` does not change daemon behavior.
|
|
289
|
+
schema and shows up in Settings, but only `host` mode has an implementation. There is no
|
|
290
|
+
container image, and selecting `container` does not change daemon behavior.
|
|
246
291
|
- **Teach-by-demonstration** — not implemented. There is no trace recording, no draft-skill-from-
|
|
247
292
|
recording flow, and no "Teach a task" UI affordance anywhere in the codebase.
|
|
248
293
|
- **Event-trigger webhooks** — not implemented. Routines only fire on a cron schedule
|
|
@@ -2,9 +2,9 @@ import { createRequire as __antbotCreateRequire } from 'node:module';
|
|
|
2
2
|
const require = __antbotCreateRequire(import.meta.url);
|
|
3
3
|
import {
|
|
4
4
|
logger
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-CNCHYZVD.js";
|
|
6
6
|
|
|
7
|
-
//
|
|
7
|
+
// daemon/src/computer/browser.ts
|
|
8
8
|
import fs from "node:fs";
|
|
9
9
|
import os from "node:os";
|
|
10
10
|
import path from "node:path";
|
|
@@ -521,4 +521,4 @@ export {
|
|
|
521
521
|
detectBlockFromSignals,
|
|
522
522
|
normalizeUrl
|
|
523
523
|
};
|
|
524
|
-
//# sourceMappingURL=browser-
|
|
524
|
+
//# sourceMappingURL=browser-3UAU6ARY.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../
|
|
3
|
+
"sources": ["../../daemon/src/computer/browser.ts"],
|
|
4
4
|
"sourcesContent": ["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { chromium } from 'playwright';\nimport type { BrowserContext, CDPSession, Page } from 'playwright';\nimport type { EventBus } from '../util/bus.js';\nimport { logger } from '../util/log.js';\n\nconst log = logger('browser');\n\n/* ------------------------------------------------------------------------------------------- *\n * Errors\n * ------------------------------------------------------------------------------------------- */\n\n/** Thrown when the browser cannot be launched (e.g. Chromium isn't installed). */\nexport class BrowserUnavailableError extends Error {\n constructor(public readonly reason: string) {\n super(`Browser is unavailable: ${reason}. Try: npx playwright install chromium`);\n this.name = 'BrowserUnavailableError';\n }\n}\n\n/** Thrown by ScreenLock when a bot's screen already has a computer-use task in flight. */\nexport class ScreenBusyError extends Error {\n constructor(public readonly botId: string) {\n super(`That bot's screen is already busy with another computer-use task. Wait for it to finish before starting another.`);\n this.name = 'ScreenBusyError';\n }\n}\n\n/** Thrown by normalizeUrl for disallowed / malformed URLs. */\nexport class InvalidUrlError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidUrlError';\n }\n}\n\n/** Thrown when an action targets a bot screen the human currently has taken over. */\nexport class ScreenTakenOverError extends Error {\n constructor() {\n super('The human has taken control of this screen; wait until they return it.');\n this.name = 'ScreenTakenOverError';\n }\n}\n\n/* ------------------------------------------------------------------------------------------- *\n * Pure helpers (no browser required) \u2014 exported for unit testing.\n * ------------------------------------------------------------------------------------------- */\n\n/**\n * Adds `https://` to bare domains/paths, rejects `javascript:` and `file:` URLs outright, and\n * otherwise passes the input through unchanged. Deliberately permissive about other schemes\n * (e.g. `data:`, `about:`) \u2014 computer-use tasks legitimately need to load those in tests and\n * sandboxes; the block list is specifically the two schemes that are dangerous or meaningless\n * for a screen-driving agent.\n */\nexport function normalizeUrl(input: string): string {\n const trimmed = (input ?? '').trim();\n if (!trimmed) throw new InvalidUrlError('URL is empty.');\n const schemeMatch = trimmed.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):/);\n if (!schemeMatch) return `https://${trimmed}`;\n const scheme = schemeMatch[1]!.toLowerCase();\n if (scheme === 'javascript') throw new InvalidUrlError('javascript: URLs are not allowed.');\n if (scheme === 'file') throw new InvalidUrlError('file: URLs are not allowed.');\n return trimmed;\n}\n\nexport interface BlockSignals {\n hasPasswordInput: boolean;\n iframeSrcs: string[];\n bodyText: string;\n}\n\nexport interface BlockDetection {\n kind: 'login' | 'captcha' | '2fa';\n message: string;\n}\n\n/**\n * Heuristic login-wall / CAPTCHA / 2FA detector over already-extracted page signals. Kept pure\n * (no Page dependency) so it can run without a browser in unit tests; `BrowserService.detectBlock`\n * gathers the signals from a live Page and delegates here.\n */\nexport function detectBlockFromSignals(signals: BlockSignals): BlockDetection | null {\n const iframeBlob = (signals.iframeSrcs ?? []).join(' ').toLowerCase();\n const body = signals.bodyText ?? '';\n\n if (/recaptcha/i.test(iframeBlob) || /hcaptcha/i.test(iframeBlob)) {\n return { kind: 'captcha', message: 'A CAPTCHA challenge (reCAPTCHA/hCaptcha) is present on this page.' };\n }\n if (/challenges\\.cloudflare\\.com|cf-challenge|turnstile/i.test(iframeBlob) || /checking your browser|cloudflare/i.test(body)) {\n return { kind: 'captcha', message: 'A Cloudflare browser challenge is present on this page.' };\n }\n if (/verify (your \\w+|it'?s you)|two-factor|one-time code/i.test(body)) {\n return { kind: '2fa', message: \"This page is asking for two-factor / one-time-code verification.\" };\n }\n if (signals.hasPasswordInput) {\n return { kind: 'login', message: 'This page has a password field \u2014 it looks like a login wall.' };\n }\n return null;\n}\n\n/**\n * Per-key mutex: `run` executes `fn` immediately if the key is free, otherwise rejects with\n * `ScreenBusyError` rather than queueing \u2014 mirrors \"one computer-use task per Bot screen at a\n * time\" (outline \u00A713): a second concurrent call is a caller bug, not something to serialize.\n */\nexport class ScreenLock {\n private busy = new Set<string>();\n\n isBusy(key: string): boolean {\n return this.busy.has(key);\n }\n\n async run<T>(key: string, fn: () => Promise<T>): Promise<T> {\n if (this.busy.has(key)) throw new ScreenBusyError(key);\n this.busy.add(key);\n try {\n return await fn();\n } finally {\n this.busy.delete(key);\n }\n }\n}\n\n/**\n * Frame-rate limiter for the screencast. `shouldEmit()` returns true at most once per\n * `1000/fps` ms according to the injected clock (defaults to `Date.now`), so tests can drive it\n * deterministically without real timers.\n */\nexport class FrameThrottle {\n private lastEmit = -Infinity;\n\n constructor(\n private readonly fps: number,\n private readonly clock: () => number = () => Date.now(),\n ) {}\n\n get intervalMs(): number {\n return 1000 / this.fps;\n }\n\n shouldEmit(): boolean {\n const now = this.clock();\n if (now - this.lastEmit >= this.intervalMs) {\n this.lastEmit = now;\n return true;\n }\n return false;\n }\n}\n\nfunction errMsg(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/* ------------------------------------------------------------------------------------------- *\n * BrowserService\n * ------------------------------------------------------------------------------------------- */\n\nexport interface BrowserServiceDeps {\n profileDir: string;\n bus: EventBus;\n headless?: boolean;\n}\n\nexport interface ComputerPageStatus {\n botId: string;\n url: string;\n title: string;\n}\n\nexport interface BrowserStatus {\n available: boolean;\n reason?: string;\n mode: 'host';\n headless: boolean;\n pages: ComputerPageStatus[];\n}\n\nexport interface TakeOverResult {\n ok: boolean;\n mode: 'screencast-only' | 'window';\n message: string;\n}\n\ntype FrameListener = (jpegBase64: string, w: number, h: number) => void;\n\n/**\n * One Playwright persistent browser context shared by every Bot (\"the computer\"), with a\n * dedicated Page (\"screen\") per botId. Because all pages share one persistent context, cookies\n * and logins are shared across bots by design (outline \u00A75: \"one computer, shared by all your\n * Bots\") \u2014 this is intentional, not a bug.\n */\nexport class BrowserService {\n private context: BrowserContext | null = null;\n private launchPromise: Promise<BrowserContext> | null = null;\n private unavailableReason: string | undefined;\n\n private readonly headlessMode: boolean;\n private readonly pages = new Map<string, Page>();\n private readonly titleCache = new Map<string, string>();\n private readonly takenOver = new Set<string>();\n private readonly lock = new ScreenLock();\n\n private readonly screencastSessions = new Map<string, CDPSession>();\n private readonly screencastListeners = new Map<string, Set<FrameListener>>();\n private readonly screencastThrottles = new Map<string, FrameThrottle>();\n private readonly viewerCounts = new Map<string, number>();\n\n constructor(private readonly deps: BrowserServiceDeps) {\n this.headlessMode = deps.headless ?? true;\n }\n\n get isHeadless(): boolean {\n return this.headlessMode;\n }\n\n /** Lazily launches the shared persistent context. Concurrent callers share one launch. */\n private async ensureContext(): Promise<BrowserContext> {\n if (this.context) return this.context;\n if (this.launchPromise) return this.launchPromise;\n\n this.launchPromise = (async () => {\n try {\n fs.mkdirSync(this.deps.profileDir, { recursive: true });\n const ctx = await chromium.launchPersistentContext(this.deps.profileDir, {\n headless: this.headlessMode,\n viewport: { width: 1280, height: 800 },\n });\n this.context = ctx;\n ctx.on('close', () => {\n this.context = null;\n this.launchPromise = null;\n this.pages.clear();\n });\n return ctx;\n } catch (err) {\n this.unavailableReason = errMsg(err);\n this.launchPromise = null;\n throw new BrowserUnavailableError(this.unavailableReason);\n }\n })();\n return this.launchPromise;\n }\n\n /** Returns this bot's dedicated page, creating it lazily. Pages persist across turns. */\n async getPage(botId: string): Promise<Page> {\n const existing = this.pages.get(botId);\n if (existing && !existing.isClosed()) return existing;\n const ctx = await this.ensureContext();\n const page = await ctx.newPage();\n this.pages.set(botId, page);\n page.on('close', () => {\n if (this.pages.get(botId) === page) this.pages.delete(botId);\n });\n return page;\n }\n\n async closePage(botId: string): Promise<void> {\n const page = this.pages.get(botId);\n if (page) {\n await page.close().catch(() => {});\n this.pages.delete(botId);\n }\n this.titleCache.delete(botId);\n this.takenOver.delete(botId);\n }\n\n /** Serializes computer-use tasks per bot screen; a concurrent second call is rejected. */\n async withScreen<T>(botId: string, fn: (page: Page) => Promise<T>): Promise<T> {\n return this.lock.run(botId, async () => {\n if (this.takenOver.has(botId)) throw new ScreenTakenOverError();\n const page = await this.getPage(botId);\n return fn(page);\n });\n }\n\n private async orient(botId: string, page: Page): Promise<{ url: string; title: string }> {\n const url = page.url();\n let title = this.titleCache.get(botId) ?? '';\n try {\n title = await page.title();\n this.titleCache.set(botId, title);\n } catch {\n // page may be mid-navigation or closed; fall back to the cached title.\n }\n return { url, title };\n }\n\n private orientLine(o: { url: string; title: string }): string {\n return `URL: ${o.url}\\nTitle: ${o.title}`;\n }\n\n /** Gathers page signals and runs them through the pure detector. Never throws. */\n async detectBlock(page: Page): Promise<BlockDetection | null> {\n try {\n const hasPasswordInput = (await page.$$('input[type=\"password\"]').catch(() => [])).length > 0;\n const iframeSrcs = await page\n .$$eval('iframe', (els) => els.map((e) => e.getAttribute('src') ?? ''))\n .catch(() => [] as string[]);\n const bodyText = await page\n .evaluate(() => document.body?.innerText ?? '')\n .catch(() => '');\n return detectBlockFromSignals({ hasPasswordInput, iframeSrcs, bodyText: bodyText.slice(0, 4000) });\n } catch {\n return null;\n }\n }\n\n /** If a block is detected, publishes a warn notification and returns the STOP text to prepend. */\n private async checkBlock(botId: string, page: Page): Promise<string | null> {\n const block = await this.detectBlock(page);\n if (!block) return null;\n this.deps.bus.publish({\n type: 'notify',\n botId,\n threadId: null,\n level: 'warn',\n title: 'Browser needs a human',\n body: block.message,\n });\n return (\n `${block.message} STOP: do not attempt to solve or bypass this yourself \u2014 ` +\n `ask the human to take over the computer and wait for them to return control.`\n );\n }\n\n /* ---------------------------------------------------------------------------------------- *\n * Actions \u2014 each returns a short result string including the resulting URL + title, and is\n * safe to call even if the page has navigated away, closed, or errored mid-action.\n * ---------------------------------------------------------------------------------------- */\n\n async navigate(botId: string, url: string): Promise<string> {\n const target = normalizeUrl(url);\n return this.withScreen(botId, async (page) => {\n try {\n await page.goto(target, { waitUntil: 'domcontentloaded', timeout: 30000 });\n } catch (err) {\n return `Navigation to ${target} failed: ${errMsg(err)}\\n${this.orientLine(await this.orient(botId, page))}`;\n }\n const block = await this.checkBlock(botId, page);\n const o = await this.orient(botId, page);\n return block ? `${block}\\n${this.orientLine(o)}` : `Navigated.\\n${this.orientLine(o)}`;\n });\n }\n\n async click(botId: string, selector: string): Promise<string> {\n return this.withScreen(botId, async (page) => {\n try {\n await page.click(selector, { timeout: 10000 });\n } catch (err) {\n return `Click on \"${selector}\" failed: ${errMsg(err)}\\n${this.orientLine(await this.orient(botId, page))}`;\n }\n const block = await this.checkBlock(botId, page);\n const o = await this.orient(botId, page);\n return block ? `${block}\\n${this.orientLine(o)}` : `Clicked \"${selector}\".\\n${this.orientLine(o)}`;\n });\n }\n\n async type(botId: string, selector: string, text: string, opts?: { submit?: boolean }): Promise<string> {\n return this.withScreen(botId, async (page) => {\n try {\n const isPassword = await page\n .$eval(selector, (el) => (el as HTMLInputElement).type === 'password')\n .catch(() => false);\n if (isPassword) {\n return (\n `Refused: \"${selector}\" looks like a password field. Never type credentials into the browser \u2014 ` +\n `ask the human to take over the computer instead.\\n${this.orientLine(await this.orient(botId, page))}`\n );\n }\n await page.fill(selector, text, { timeout: 10000 });\n if (opts?.submit) await page.press(selector, 'Enter');\n } catch (err) {\n return `Type into \"${selector}\" failed: ${errMsg(err)}\\n${this.orientLine(await this.orient(botId, page))}`;\n }\n const block = await this.checkBlock(botId, page);\n const o = await this.orient(botId, page);\n return block ? `${block}\\n${this.orientLine(o)}` : `Typed into \"${selector}\".\\n${this.orientLine(o)}`;\n });\n }\n\n async pressKey(botId: string, key: string): Promise<string> {\n return this.withScreen(botId, async (page) => {\n try {\n await page.keyboard.press(key);\n } catch (err) {\n return `Key press \"${key}\" failed: ${errMsg(err)}\\n${this.orientLine(await this.orient(botId, page))}`;\n }\n return `Pressed \"${key}\".\\n${this.orientLine(await this.orient(botId, page))}`;\n });\n }\n\n /** Returns visible text (page body, or a selector's text), capped at ~8000 characters. */\n async readText(botId: string, selector?: string): Promise<string> {\n return this.withScreen(botId, async (page) => {\n let text: string;\n try {\n text = selector\n ? ((await page.$eval(selector, (el) => (el as HTMLElement).innerText ?? '')) ?? '')\n : await page.evaluate(() => document.body?.innerText ?? '');\n } catch (err) {\n return `Read${selector ? ` of \"${selector}\"` : ''} failed: ${errMsg(err)}\\n${this.orientLine(await this.orient(botId, page))}`;\n }\n const capped = text.length > 8000 ? `${text.slice(0, 8000)}\\n\u2026(truncated)` : text;\n return `${this.orientLine(await this.orient(botId, page))}\\n---\\n${capped}`;\n });\n }\n\n async readLinks(botId: string): Promise<string> {\n return this.withScreen(botId, async (page) => {\n let links: Array<{ text: string; href: string }>;\n try {\n links = await page.$$eval('a[href]', (els) =>\n els\n .slice(0, 200)\n .map((e) => ({ text: (e as HTMLElement).innerText.trim().slice(0, 120), href: (e as HTMLAnchorElement).href })),\n );\n } catch (err) {\n return `Reading links failed: ${errMsg(err)}\\n${this.orientLine(await this.orient(botId, page))}`;\n }\n const body = links.map((l) => `- ${l.text || '(no text)'} -> ${l.href}`).join('\\n') || '(no links found)';\n return `${this.orientLine(await this.orient(botId, page))}\\n---\\n${body}`;\n });\n }\n\n /** Saves a PNG screenshot (to `filePath`, or a temp file if omitted) and returns its path. */\n async screenshot(botId: string, filePath?: string): Promise<string> {\n return this.withScreen(botId, async (page) => {\n const target = filePath ?? path.join(os.tmpdir(), `antbot-screenshot-${botId}-${Date.now()}.png`);\n try {\n fs.mkdirSync(path.dirname(target), { recursive: true });\n await page.screenshot({ path: target });\n } catch (err) {\n return `Screenshot failed: ${errMsg(err)}\\n${this.orientLine(await this.orient(botId, page))}`;\n }\n return `Screenshot saved to ${target}\\n${this.orientLine(await this.orient(botId, page))}`;\n });\n }\n\n async currentUrl(botId: string): Promise<string> {\n return this.withScreen(botId, async (page) => this.orientLine(await this.orient(botId, page)));\n }\n\n async waitFor(botId: string, selector: string, timeoutMs: number): Promise<string> {\n return this.withScreen(botId, async (page) => {\n try {\n await page.waitForSelector(selector, { timeout: timeoutMs });\n } catch (err) {\n return `Waiting for \"${selector}\" timed out: ${errMsg(err)}\\n${this.orientLine(await this.orient(botId, page))}`;\n }\n return `Found \"${selector}\".\\n${this.orientLine(await this.orient(botId, page))}`;\n });\n }\n\n async scroll(botId: string, dy: number): Promise<string> {\n return this.withScreen(botId, async (page) => {\n try {\n await page.mouse.wheel(0, dy);\n } catch (err) {\n return `Scroll failed: ${errMsg(err)}\\n${this.orientLine(await this.orient(botId, page))}`;\n }\n return `Scrolled by ${dy}px.\\n${this.orientLine(await this.orient(botId, page))}`;\n });\n }\n\n async goBack(botId: string): Promise<string> {\n return this.withScreen(botId, async (page) => {\n try {\n await page.goBack({ waitUntil: 'domcontentloaded', timeout: 15000 });\n } catch (err) {\n return `Go back failed: ${errMsg(err)}\\n${this.orientLine(await this.orient(botId, page))}`;\n }\n const block = await this.checkBlock(botId, page);\n const o = await this.orient(botId, page);\n return block ? `${block}\\n${this.orientLine(o)}` : `Went back.\\n${this.orientLine(o)}`;\n });\n }\n\n /* ---------------------------------------------------------------------------------------- *\n * Screencast \u2014 CDP-driven JPEG frames, throttled to ~4fps, reference-counted per bot so it\n * auto-stops once the last viewer detaches.\n * ---------------------------------------------------------------------------------------- */\n\n async startScreencast(botId: string, onFrame: FrameListener): Promise<() => void> {\n const page = await this.getPage(botId);\n\n let listeners = this.screencastListeners.get(botId);\n if (!listeners) {\n listeners = new Set();\n this.screencastListeners.set(botId, listeners);\n }\n listeners.add(onFrame);\n this.viewerCounts.set(botId, (this.viewerCounts.get(botId) ?? 0) + 1);\n\n if (!this.screencastSessions.has(botId)) {\n const ctx = await this.ensureContext();\n const session = await ctx.newCDPSession(page);\n const throttle = new FrameThrottle(4);\n this.screencastSessions.set(botId, session);\n this.screencastThrottles.set(botId, throttle);\n\n session.on('Page.screencastFrame', (evt) => {\n void session.send('Page.screencastFrameAck', { sessionId: evt.sessionId }).catch(() => {});\n if (!throttle.shouldEmit()) return;\n const w = evt.metadata?.deviceWidth ?? 0;\n const h = evt.metadata?.deviceHeight ?? 0;\n for (const l of listeners!) {\n try {\n l(evt.data, w, h);\n } catch (err) {\n log.warn('screencast frame listener threw', err);\n }\n }\n });\n\n await session.send('Page.startScreencast', { format: 'jpeg', quality: 60, maxWidth: 1280, everyNthFrame: 2 });\n }\n\n let stopped = false;\n return () => {\n if (stopped) return;\n stopped = true;\n listeners!.delete(onFrame);\n const remaining = Math.max(0, (this.viewerCounts.get(botId) ?? 1) - 1);\n this.viewerCounts.set(botId, remaining);\n if (remaining > 0) return;\n\n const session = this.screencastSessions.get(botId);\n this.screencastSessions.delete(botId);\n this.screencastThrottles.delete(botId);\n this.screencastListeners.delete(botId);\n if (session) {\n void session\n .send('Page.stopScreencast')\n .catch(() => {})\n .then(() => session.detach().catch(() => {}));\n }\n };\n }\n\n /* ---------------------------------------------------------------------------------------- *\n * Takeover \u2014 pauses agent-driven actions on a bot's screen for the human.\n * ---------------------------------------------------------------------------------------- */\n\n isTakenOver(botId: string): boolean {\n return this.takenOver.has(botId);\n }\n\n /**\n * Marks the screen as human-controlled. When headless, there is no window to bring forward\n * (and launching a second headed context on the same profile dir isn't possible \u2014 Chromium\n * locks the profile), so the human acts through the screencast view instead; interactive input\n * forwarding through that view is out of scope here. When headed, brings the real window to\n * the front for the human to drive directly.\n */\n async takeOver(botId: string): Promise<TakeOverResult> {\n this.takenOver.add(botId);\n if (this.headlessMode) {\n return {\n ok: true,\n mode: 'screencast-only',\n message:\n 'This computer is running headless, so there is no window to bring to the front. ' +\n 'Act through the screencast view for the blocked step (password, 2FA, CAPTCHA), then return control. ' +\n 'Never paste secrets into chat.',\n };\n }\n try {\n const page = await this.getPage(botId);\n await page.bringToFront();\n } catch (err) {\n log.warn('failed to bring page to front for takeover', err);\n }\n return {\n ok: true,\n mode: 'window',\n message: 'The browser window for this bot has been brought to the front. Complete the blocked step, then return control.',\n };\n }\n\n returnControl(botId: string): void {\n this.takenOver.delete(botId);\n }\n\n /* ---------------------------------------------------------------------------------------- */\n\n /** Never launches the browser. Reports optimistically until a launch attempt has failed. */\n status(): BrowserStatus {\n if (this.unavailableReason) {\n return { available: false, reason: this.unavailableReason, mode: 'host', headless: this.headlessMode, pages: [] };\n }\n const pages: ComputerPageStatus[] = [];\n if (this.context) {\n for (const [botId, page] of this.pages) {\n if (page.isClosed()) continue;\n pages.push({ botId, url: page.url(), title: this.titleCache.get(botId) ?? '' });\n }\n }\n return { available: true, mode: 'host', headless: this.headlessMode, pages };\n }\n\n async shutdown(): Promise<void> {\n for (const session of this.screencastSessions.values()) {\n await session\n .send('Page.stopScreencast')\n .catch(() => {})\n .then(() => session.detach().catch(() => {}));\n }\n this.screencastSessions.clear();\n this.screencastListeners.clear();\n this.screencastThrottles.clear();\n this.viewerCounts.clear();\n\n for (const page of this.pages.values()) {\n await page.close().catch(() => {});\n }\n this.pages.clear();\n this.titleCache.clear();\n this.takenOver.clear();\n\n if (this.context) {\n await this.context.close().catch(() => {});\n this.context = null;\n }\n this.launchPromise = null;\n }\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": []
|
|
@@ -3,13 +3,13 @@ const require = __antbotCreateRequire(import.meta.url);
|
|
|
3
3
|
import {
|
|
4
4
|
findBundledSkillsDir,
|
|
5
5
|
nodeLocateDeps
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-GYWJUW4E.js";
|
|
7
7
|
import {
|
|
8
8
|
copyTree
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
9
|
+
} from "./chunk-JCZBDGBA.js";
|
|
10
|
+
import "./chunk-MSSHR6SW.js";
|
|
11
11
|
|
|
12
|
-
//
|
|
12
|
+
// daemon/src/skills/bundled.ts
|
|
13
13
|
import fs from "node:fs";
|
|
14
14
|
import path from "node:path";
|
|
15
15
|
import crypto from "node:crypto";
|
|
@@ -130,4 +130,4 @@ export {
|
|
|
130
130
|
syncBundledSkills,
|
|
131
131
|
writeLedger
|
|
132
132
|
};
|
|
133
|
-
//# sourceMappingURL=bundled-
|
|
133
|
+
//# sourceMappingURL=bundled-26SJR7EB.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../
|
|
3
|
+
"sources": ["../../daemon/src/skills/bundled.ts"],
|
|
4
4
|
"sourcesContent": ["import fs from 'node:fs';\nimport path from 'node:path';\nimport crypto from 'node:crypto';\nimport { fileURLToPath } from 'node:url';\nimport { copyTree } from './install.js';\nimport { findBundledSkillsDir, nodeLocateDeps } from '../util/locate.js';\n\n/**\n * Skills that ship with ant-bot itself, in the repo's (and the published package's)\n * `skills/` directory, installed into the user's skills directory on boot.\n *\n * The hard part is not copying files, it is deciding what to copy *again*. A bundled skill\n * has two owners: ant-bot ships updates to it, and the user is free to edit or delete it.\n * Refreshing unconditionally throws away the user's edits and resurrects what they deleted;\n * seeding once means a shipped fix never reaches anyone who already has the old copy. So the\n * hash of what we wrote is recorded in a ledger, and on the next boot the on-disk copy is\n * compared against it: untouched copies track upstream, touched ones are left alone. This is\n * the same bargain a package manager makes with an edited config file.\n */\n\nconst LEDGER_FILE = '.managed.json';\nconst LEDGER_VERSION = 1;\n\nexport interface LedgerEntry {\n /** Hash of the skill directory as ant-bot last wrote it. */\n hash: string;\n syncedAt: string;\n}\n\nexport interface Ledger {\n version: number;\n skills: Record<string, LedgerEntry>;\n}\n\nexport type SkillSyncAction =\n /** Not on disk and never seeded \u2014 a first install. */\n | 'install'\n /** Ours, untouched by the user, and the shipped copy changed. */\n | 'update'\n /** Not in the ledger but byte-identical to what ships \u2014 adopt it so it tracks upstream. */\n | 'adopt'\n /** Ours, untouched, already current. */\n | 'unchanged'\n /** Ours, but the user edited it. */\n | 'skip-modified'\n /** Same slug, different content, never seeded by us \u2014 someone else's skill. */\n | 'skip-foreign'\n /** Seeded once and since deleted by the user. */\n | 'skip-deleted';\n\nexport interface SkillSyncState {\n slug: string;\n /** Hash of the skill as it ships with this build. */\n shippedHash: string;\n /** Hash of the copy in the user's skills directory, or null if it is not there. */\n installedHash: string | null;\n /** Hash recorded the last time ant-bot wrote this skill, or undefined if it never did. */\n ledgerHash: string | undefined;\n /** Hashes this skill shipped with in earlier ant-bot versions. See KNOWN_PRIOR_HASHES. */\n priorHashes?: string[];\n}\n\n/**\n * Hashes of skills as earlier ant-bot versions shipped them, before the ledger existed.\n *\n * Without this an upgrade that changes a shipped skill can never reach anyone who already has\n * it: their copy is not in the ledger and no longer matches what ships, so it reads as someone\n * else's skill and is left alone forever. Matching a known prior hash proves the copy is an\n * untouched older version of ours, which is safe to replace. This is the same record dpkg keeps\n * for conffiles, and it is append-only \u2014 add the outgoing hash here whenever a shipped skill\n * changes, never remove one.\n */\nexport const KNOWN_PRIOR_HASHES: Record<string, string[]> = {\n // 0.1.0, seeded from skills-examples/ \u2014 before the skills were renamed to spec-conformant\n // frontmatter names and deep-research got its references/ directory.\n 'bug-repro': ['e8faa77eb98c3ba4d7618731d946c233354d158b3ecae7b1bed5908dce3e9cac'],\n 'deep-research': ['33f7482fa2d136dad53c556b5a25527136838f8bd72d35a9657f45e6c21f391b'],\n 'inbox-digest': ['45e137d9d6ce26ad6e230ef1b0711ad7d6b4be06d744f42accea2700f0039361'],\n 'weekly-report': ['fb05e984b96e8771b236602b47a82f1ec600bd307b44878cd92694cdb658a852'],\n};\n\nexport interface SkillSyncDecision {\n slug: string;\n action: SkillSyncAction;\n}\n\n/**\n * Decide what to do with each bundled skill. Pure, so every branch below is testable without\n * touching a filesystem \u2014 and there are more branches here than the copying deserves.\n */\nexport function planSkillSync(states: SkillSyncState[]): SkillSyncDecision[] {\n return states.map(({ slug, shippedHash, installedHash, ledgerHash, priorHashes }): SkillSyncDecision => {\n if (installedHash === null) {\n // A ledger entry with nothing on disk means the user deleted it. Putting it back on\n // every boot would make deletion impossible.\n return { slug, action: ledgerHash === undefined ? 'install' : 'skip-deleted' };\n }\n if (ledgerHash === undefined) {\n // Never seeded by us. Byte-identical to what ships means a pre-ledger seed from an older\n // ant-bot: adopt it as-is. Matching a hash we are known to have shipped before means an\n // untouched *older* copy of ours, safe to bring up to date. Anything else belongs to\n // whoever put it there (`antbot skill add`, or dropped in by hand) and we keep our hands off.\n if (installedHash === shippedHash) return { slug, action: 'adopt' };\n if (priorHashes?.includes(installedHash)) return { slug, action: 'update' };\n return { slug, action: 'skip-foreign' };\n }\n if (installedHash !== ledgerHash) return { slug, action: 'skip-modified' };\n return { slug, action: installedHash === shippedHash ? 'unchanged' : 'update' };\n });\n}\n\n/** Hash a skill directory: every file's path and contents, in a stable order. */\nexport function hashSkillDir(dir: string): string {\n const h = crypto.createHash('sha256');\n for (const rel of listFilesRecursive(dir).sort()) {\n h.update(rel.split(path.sep).join('/'));\n h.update('\\0');\n h.update(fs.readFileSync(path.join(dir, rel)));\n h.update('\\0');\n }\n return h.digest('hex');\n}\n\nfunction listFilesRecursive(dir: string, prefix = ''): string[] {\n const out: string[] = [];\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n // Mirrors copyTree: what is not copied must not be hashed, or a skill would look\n // modified forever.\n if (entry.name === '.git' || entry.name === 'node_modules') continue;\n if (entry.isSymbolicLink()) continue;\n const rel = path.join(prefix, entry.name);\n if (entry.isDirectory()) out.push(...listFilesRecursive(path.join(dir, entry.name), rel));\n else if (entry.isFile()) out.push(rel);\n }\n return out;\n}\n\nexport function readLedger(targetDir: string): Ledger {\n try {\n const raw = fs.readFileSync(path.join(targetDir, LEDGER_FILE), 'utf8');\n const parsed = JSON.parse(raw) as Partial<Ledger>;\n if (parsed.version !== LEDGER_VERSION || typeof parsed.skills !== 'object' || !parsed.skills) {\n return { version: LEDGER_VERSION, skills: {} };\n }\n return { version: LEDGER_VERSION, skills: parsed.skills };\n } catch {\n // No ledger, or an unreadable one. Treating it as empty is safe: every on-disk skill then\n // looks foreign or adoptable, and nothing gets overwritten.\n return { version: LEDGER_VERSION, skills: {} };\n }\n}\n\nexport function writeLedger(targetDir: string, ledger: Ledger): void {\n fs.mkdirSync(targetDir, { recursive: true });\n fs.writeFileSync(path.join(targetDir, LEDGER_FILE), `${JSON.stringify(ledger, null, 2)}\\n`, 'utf8');\n}\n\n/** Location of the skills that ship with ant-bot; see `util/locate.ts` for the candidate order. */\nexport function defaultBundledSkillsDir(): string {\n return findBundledSkillsDir(\n nodeLocateDeps(path.dirname(fileURLToPath(import.meta.url)), () => null),\n );\n}\n\n/**\n * Install or refresh ant-bot's own skills in `targetDir`. Safe to call on every boot; see the\n * module comment for why it is not a plain copy. Returns what it did, for the boot log.\n */\nexport function syncBundledSkills(\n targetDir: string,\n bundledDir: string = defaultBundledSkillsDir(),\n): SkillSyncDecision[] {\n if (!fs.existsSync(bundledDir)) return [];\n\n const ledger = readLedger(targetDir);\n const states: SkillSyncState[] = [];\n const shippedDirs = new Map<string, string>();\n\n for (const entry of fs.readdirSync(bundledDir, { withFileTypes: true })) {\n if (!entry.isDirectory()) continue;\n const src = path.join(bundledDir, entry.name);\n if (!fs.existsSync(path.join(src, 'SKILL.md'))) continue;\n const dest = path.join(targetDir, entry.name);\n shippedDirs.set(entry.name, src);\n states.push({\n slug: entry.name,\n shippedHash: hashSkillDir(src),\n installedHash: fs.existsSync(path.join(dest, 'SKILL.md')) ? hashSkillDir(dest) : null,\n ledgerHash: ledger.skills[entry.name]?.hash,\n priorHashes: KNOWN_PRIOR_HASHES[entry.name],\n });\n }\n if (states.length === 0) return [];\n\n const decisions = planSkillSync(states);\n const byslug = new Map(states.map((s) => [s.slug, s]));\n const syncedAt = new Date().toISOString();\n let ledgerChanged = false;\n\n fs.mkdirSync(targetDir, { recursive: true });\n for (const decision of decisions) {\n const state = byslug.get(decision.slug)!;\n if (decision.action === 'install' || decision.action === 'update') {\n const dest = path.join(targetDir, decision.slug);\n fs.rmSync(dest, { recursive: true, force: true });\n copyTree(shippedDirs.get(decision.slug)!, dest);\n } else if (decision.action !== 'adopt') {\n continue;\n }\n ledger.skills[decision.slug] = { hash: state.shippedHash, syncedAt };\n ledgerChanged = true;\n }\n if (ledgerChanged) writeLedger(targetDir, ledger);\n\n return decisions;\n}\n"],
|
|
5
5
|
"mappings": ";;;;;;;;;;;;AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,OAAO,YAAY;AACnB,SAAS,qBAAqB;AAiB9B,IAAM,cAAc;AACpB,IAAM,iBAAiB;AAmDhB,IAAM,qBAA+C;AAAA;AAAA;AAAA,EAG1D,aAAa,CAAC,kEAAkE;AAAA,EAChF,iBAAiB,CAAC,kEAAkE;AAAA,EACpF,gBAAgB,CAAC,kEAAkE;AAAA,EACnF,iBAAiB,CAAC,kEAAkE;AACtF;AAWO,SAAS,cAAc,QAA+C;AAC3E,SAAO,OAAO,IAAI,CAAC,EAAE,MAAM,aAAa,eAAe,YAAY,YAAY,MAAyB;AACtG,QAAI,kBAAkB,MAAM;AAG1B,aAAO,EAAE,MAAM,QAAQ,eAAe,SAAY,YAAY,eAAe;AAAA,IAC/E;AACA,QAAI,eAAe,QAAW;AAK5B,UAAI,kBAAkB,YAAa,QAAO,EAAE,MAAM,QAAQ,QAAQ;AAClE,UAAI,aAAa,SAAS,aAAa,EAAG,QAAO,EAAE,MAAM,QAAQ,SAAS;AAC1E,aAAO,EAAE,MAAM,QAAQ,eAAe;AAAA,IACxC;AACA,QAAI,kBAAkB,WAAY,QAAO,EAAE,MAAM,QAAQ,gBAAgB;AACzE,WAAO,EAAE,MAAM,QAAQ,kBAAkB,cAAc,cAAc,SAAS;AAAA,EAChF,CAAC;AACH;AAGO,SAAS,aAAa,KAAqB;AAChD,QAAM,IAAI,OAAO,WAAW,QAAQ;AACpC,aAAW,OAAO,mBAAmB,GAAG,EAAE,KAAK,GAAG;AAChD,MAAE,OAAO,IAAI,MAAM,KAAK,GAAG,EAAE,KAAK,GAAG,CAAC;AACtC,MAAE,OAAO,IAAI;AACb,MAAE,OAAO,GAAG,aAAa,KAAK,KAAK,KAAK,GAAG,CAAC,CAAC;AAC7C,MAAE,OAAO,IAAI;AAAA,EACf;AACA,SAAO,EAAE,OAAO,KAAK;AACvB;AAEA,SAAS,mBAAmB,KAAa,SAAS,IAAc;AAC9D,QAAM,MAAgB,CAAC;AACvB,aAAW,SAAS,GAAG,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC,GAAG;AAGhE,QAAI,MAAM,SAAS,UAAU,MAAM,SAAS,eAAgB;AAC5D,QAAI,MAAM,eAAe,EAAG;AAC5B,UAAM,MAAM,KAAK,KAAK,QAAQ,MAAM,IAAI;AACxC,QAAI,MAAM,YAAY,EAAG,KAAI,KAAK,GAAG,mBAAmB,KAAK,KAAK,KAAK,MAAM,IAAI,GAAG,GAAG,CAAC;AAAA,aAC/E,MAAM,OAAO,EAAG,KAAI,KAAK,GAAG;AAAA,EACvC;AACA,SAAO;AACT;AAEO,SAAS,WAAW,WAA2B;AACpD,MAAI;AACF,UAAM,MAAM,GAAG,aAAa,KAAK,KAAK,WAAW,WAAW,GAAG,MAAM;AACrE,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,OAAO,YAAY,kBAAkB,OAAO,OAAO,WAAW,YAAY,CAAC,OAAO,QAAQ;AAC5F,aAAO,EAAE,SAAS,gBAAgB,QAAQ,CAAC,EAAE;AAAA,IAC/C;AACA,WAAO,EAAE,SAAS,gBAAgB,QAAQ,OAAO,OAAO;AAAA,EAC1D,QAAQ;AAGN,WAAO,EAAE,SAAS,gBAAgB,QAAQ,CAAC,EAAE;AAAA,EAC/C;AACF;AAEO,SAAS,YAAY,WAAmB,QAAsB;AACnE,KAAG,UAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAC3C,KAAG,cAAc,KAAK,KAAK,WAAW,WAAW,GAAG,GAAG,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAAA,GAAM,MAAM;AACpG;AAGO,SAAS,0BAAkC;AAChD,SAAO;AAAA,IACL,eAAe,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC,GAAG,MAAM,IAAI;AAAA,EACzE;AACF;AAMO,SAAS,kBACd,WACA,aAAqB,wBAAwB,GACxB;AACrB,MAAI,CAAC,GAAG,WAAW,UAAU,EAAG,QAAO,CAAC;AAExC,QAAM,SAAS,WAAW,SAAS;AACnC,QAAM,SAA2B,CAAC;AAClC,QAAM,cAAc,oBAAI,IAAoB;AAE5C,aAAW,SAAS,GAAG,YAAY,YAAY,EAAE,eAAe,KAAK,CAAC,GAAG;AACvE,QAAI,CAAC,MAAM,YAAY,EAAG;AAC1B,UAAM,MAAM,KAAK,KAAK,YAAY,MAAM,IAAI;AAC5C,QAAI,CAAC,GAAG,WAAW,KAAK,KAAK,KAAK,UAAU,CAAC,EAAG;AAChD,UAAM,OAAO,KAAK,KAAK,WAAW,MAAM,IAAI;AAC5C,gBAAY,IAAI,MAAM,MAAM,GAAG;AAC/B,WAAO,KAAK;AAAA,MACV,MAAM,MAAM;AAAA,MACZ,aAAa,aAAa,GAAG;AAAA,MAC7B,eAAe,GAAG,WAAW,KAAK,KAAK,MAAM,UAAU,CAAC,IAAI,aAAa,IAAI,IAAI;AAAA,MACjF,YAAY,OAAO,OAAO,MAAM,IAAI,GAAG;AAAA,MACvC,aAAa,mBAAmB,MAAM,IAAI;AAAA,IAC5C,CAAC;AAAA,EACH;AACA,MAAI,OAAO,WAAW,EAAG,QAAO,CAAC;AAEjC,QAAM,YAAY,cAAc,MAAM;AACtC,QAAM,SAAS,IAAI,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AACrD,QAAM,YAAW,oBAAI,KAAK,GAAE,YAAY;AACxC,MAAI,gBAAgB;AAEpB,KAAG,UAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAC3C,aAAW,YAAY,WAAW;AAChC,UAAM,QAAQ,OAAO,IAAI,SAAS,IAAI;AACtC,QAAI,SAAS,WAAW,aAAa,SAAS,WAAW,UAAU;AACjE,YAAM,OAAO,KAAK,KAAK,WAAW,SAAS,IAAI;AAC/C,SAAG,OAAO,MAAM,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAChD,eAAS,YAAY,IAAI,SAAS,IAAI,GAAI,IAAI;AAAA,IAChD,WAAW,SAAS,WAAW,SAAS;AACtC;AAAA,IACF;AACA,WAAO,OAAO,SAAS,IAAI,IAAI,EAAE,MAAM,MAAM,aAAa,SAAS;AACnE,oBAAgB;AAAA,EAClB;AACA,MAAI,cAAe,aAAY,WAAW,MAAM;AAEhD,SAAO;AACT;",
|
|
6
6
|
"names": []
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire as __antbotCreateRequire } from 'node:module';
|
|
2
2
|
const require = __antbotCreateRequire(import.meta.url);
|
|
3
3
|
|
|
4
|
-
//
|
|
4
|
+
// daemon/src/util/log.ts
|
|
5
5
|
var order = { debug: 10, info: 20, warn: 30, error: 40 };
|
|
6
6
|
var min = order[process.env.ANTBOT_LOG_LEVEL ?? "info"] ?? 20;
|
|
7
7
|
function emit(level, scope, msg, extra) {
|
|
@@ -21,4 +21,4 @@ var logger = (scope) => ({
|
|
|
21
21
|
export {
|
|
22
22
|
logger
|
|
23
23
|
};
|
|
24
|
-
//# sourceMappingURL=chunk-
|
|
24
|
+
//# sourceMappingURL=chunk-CNCHYZVD.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../
|
|
3
|
+
"sources": ["../../daemon/src/util/log.ts"],
|
|
4
4
|
"sourcesContent": ["type Level = 'debug' | 'info' | 'warn' | 'error';\nconst order: Record<Level, number> = { debug: 10, info: 20, warn: 30, error: 40 };\nconst min = order[(process.env.ANTBOT_LOG_LEVEL as Level) ?? 'info'] ?? 20;\n\nfunction emit(level: Level, scope: string, msg: string, extra?: unknown) {\n if (order[level] < min) return;\n const line = `${new Date().toISOString()} ${level.toUpperCase().padEnd(5)} [${scope}] ${msg}`;\n if (level === 'error') console.error(line, extra ?? '');\n else if (level === 'warn') console.warn(line, extra ?? '');\n else console.log(line, extra ?? '');\n}\n\nexport const logger = (scope: string) => ({\n debug: (m: string, e?: unknown) => emit('debug', scope, m, e),\n info: (m: string, e?: unknown) => emit('info', scope, m, e),\n warn: (m: string, e?: unknown) => emit('warn', scope, m, e),\n error: (m: string, e?: unknown) => emit('error', scope, m, e),\n});\n"],
|
|
5
5
|
"mappings": ";;;;AACA,IAAM,QAA+B,EAAE,OAAO,IAAI,MAAM,IAAI,MAAM,IAAI,OAAO,GAAG;AAChF,IAAM,MAAM,MAAO,QAAQ,IAAI,oBAA8B,MAAM,KAAK;AAExE,SAAS,KAAK,OAAc,OAAe,KAAa,OAAiB;AACvE,MAAI,MAAM,KAAK,IAAI,IAAK;AACxB,QAAM,OAAO,IAAG,oBAAI,KAAK,GAAE,YAAY,CAAC,IAAI,MAAM,YAAY,EAAE,OAAO,CAAC,CAAC,KAAK,KAAK,KAAK,GAAG;AAC3F,MAAI,UAAU,QAAS,SAAQ,MAAM,MAAM,SAAS,EAAE;AAAA,WAC7C,UAAU,OAAQ,SAAQ,KAAK,MAAM,SAAS,EAAE;AAAA,MACpD,SAAQ,IAAI,MAAM,SAAS,EAAE;AACpC;AAEO,IAAM,SAAS,CAAC,WAAmB;AAAA,EACxC,OAAO,CAAC,GAAW,MAAgB,KAAK,SAAS,OAAO,GAAG,CAAC;AAAA,EAC5D,MAAM,CAAC,GAAW,MAAgB,KAAK,QAAQ,OAAO,GAAG,CAAC;AAAA,EAC1D,MAAM,CAAC,GAAW,MAAgB,KAAK,QAAQ,OAAO,GAAG,CAAC;AAAA,EAC1D,OAAO,CAAC,GAAW,MAAgB,KAAK,SAAS,OAAO,GAAG,CAAC;AAC9D;",
|
|
6
6
|
"names": []
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire as __antbotCreateRequire } from 'node:module';
|
|
2
2
|
const require = __antbotCreateRequire(import.meta.url);
|
|
3
3
|
|
|
4
|
-
//
|
|
4
|
+
// daemon/src/util/locate.ts
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
function nodeLocateDeps(here, resolve) {
|
|
@@ -27,14 +27,14 @@ function readPackageVersion(here, exists, read) {
|
|
|
27
27
|
}
|
|
28
28
|
function webDistCandidates(deps) {
|
|
29
29
|
const out = [];
|
|
30
|
-
const resolved = deps.resolve("@antbot/
|
|
30
|
+
const resolved = deps.resolve("@antbot/ui/package.json");
|
|
31
31
|
if (resolved) out.push(path.join(path.dirname(resolved), "dist"));
|
|
32
32
|
const pkg = findPackageRoot(deps.here, deps.exists);
|
|
33
33
|
if (pkg) {
|
|
34
34
|
out.push(path.join(pkg, "web", "dist"));
|
|
35
|
-
out.push(path.resolve(pkg, "..", "
|
|
35
|
+
out.push(path.resolve(pkg, "..", "ui", "dist"));
|
|
36
36
|
}
|
|
37
|
-
out.push(path.resolve(deps.cwd, "
|
|
37
|
+
out.push(path.resolve(deps.cwd, "ui", "dist"));
|
|
38
38
|
return out;
|
|
39
39
|
}
|
|
40
40
|
function findWebDist(deps) {
|
|
@@ -45,7 +45,7 @@ function bundledSkillsCandidates(deps) {
|
|
|
45
45
|
const pkg = findPackageRoot(deps.here, deps.exists);
|
|
46
46
|
if (pkg) {
|
|
47
47
|
out.push(path.join(pkg, "skills"));
|
|
48
|
-
out.push(path.resolve(pkg, "..", "
|
|
48
|
+
out.push(path.resolve(pkg, "..", "skills"));
|
|
49
49
|
}
|
|
50
50
|
out.push(path.resolve(deps.cwd, "skills"));
|
|
51
51
|
return out;
|
|
@@ -61,4 +61,4 @@ export {
|
|
|
61
61
|
findWebDist,
|
|
62
62
|
findBundledSkillsDir
|
|
63
63
|
};
|
|
64
|
-
//# sourceMappingURL=chunk-
|
|
64
|
+
//# sourceMappingURL=chunk-GYWJUW4E.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../
|
|
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 `
|
|
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;
|
|
3
|
+
"sources": ["../../daemon/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 the `daemon/` directory in a\n * checkout and the installed package root otherwise, so `pkg/web/dist` covers the shipped layout\n * and `pkg/../ui/dist` covers the sibling workspace package \u2014 neither depends on compile depth.\n *\n * The sibling name is `ui`, not `web`: those are workspace *directories*, and they are the one\n * thing here that a repo reorganisation moves. A rename must update this list and its tests.\n */\nexport function webDistCandidates(deps: LocateDeps): string[] {\n const out: string[] = [];\n const resolved = deps.resolve('@antbot/ui/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, '..', 'ui', 'dist'));\n }\n out.push(path.resolve(deps.cwd, 'ui', '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 the `daemon/`\n * directory, so the repo root is one level up; in the installed package the directory sits beside\n * the code. (This was two levels up while the workspace lived under `packages/`.)\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;AAUO,SAAS,kBAAkB,MAA4B;AAC5D,QAAM,MAAgB,CAAC;AACvB,QAAM,WAAW,KAAK,QAAQ,yBAAyB;AACvD,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,MAAM,MAAM,CAAC;AAAA,EAChD;AACA,MAAI,KAAK,KAAK,QAAQ,KAAK,KAAK,MAAM,MAAM,CAAC;AAC7C,SAAO;AACT;AAGO,SAAS,YAAY,MAAiC;AAC3D,SAAO,kBAAkB,IAAI,EAAE,KAAK,CAAC,MAAM,KAAK,OAAO,KAAK,KAAK,GAAG,YAAY,CAAC,CAAC,KAAK;AACzF;AAOO,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,QAAQ,CAAC;AAAA,EAC5C;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
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,9 +2,9 @@ import { createRequire as __antbotCreateRequire } from 'node:module';
|
|
|
2
2
|
const require = __antbotCreateRequire(import.meta.url);
|
|
3
3
|
import {
|
|
4
4
|
parseFrontmatter
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-MSSHR6SW.js";
|
|
6
6
|
|
|
7
|
-
//
|
|
7
|
+
// daemon/src/skills/install.ts
|
|
8
8
|
import fs from "node:fs";
|
|
9
9
|
import os from "node:os";
|
|
10
10
|
import path from "node:path";
|
|
@@ -284,4 +284,4 @@ export {
|
|
|
284
284
|
stage,
|
|
285
285
|
renderManifest
|
|
286
286
|
};
|
|
287
|
-
//# sourceMappingURL=chunk-
|
|
287
|
+
//# sourceMappingURL=chunk-JCZBDGBA.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../
|
|
3
|
+
"sources": ["../../daemon/src/skills/install.ts"],
|
|
4
4
|
"sourcesContent": ["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { spawn } from 'node:child_process';\n\n/**\n * Installing a skill means adding instructions that later steer a bot, and possibly\n * scripts that run with the same access as any bot command. Every source is therefore\n * resolved explicitly rather than guessed at, everything lands under the skills\n * directory, and the caller is handed a manifest of exactly what arrived.\n */\n\nexport type SkillSource =\n | { kind: 'git'; url: string; ref?: string; subdir?: string }\n | { kind: 'path'; path: string }\n | { kind: 'url'; url: string };\n\nconst SCRIPT_EXTENSIONS = new Set([\n '.sh', '.bash', '.zsh', '.fish', '.py', '.rb', '.pl', '.js', '.mjs', '.cjs',\n '.ts', '.ps1', '.bat', '.cmd', '.exe', '.bin',\n]);\n\n/** A file worth warning about: it can be executed, not merely read. */\nexport function isExecutableFile(relPath: string, mode: number): boolean {\n if (SCRIPT_EXTENSIONS.has(path.extname(relPath).toLowerCase())) return true;\n return (mode & 0o111) !== 0;\n}\n\nexport interface FileStat {\n relPath: string;\n bytes: number;\n mode: number;\n}\n\nexport interface SkillManifest {\n files: FileStat[];\n executables: string[];\n totalBytes: number;\n}\n\nexport function classifyFiles(files: FileStat[]): SkillManifest {\n return {\n files,\n executables: files.filter((f) => isExecutableFile(f.relPath, f.mode)).map((f) => f.relPath),\n totalBytes: files.reduce((n, f) => n + f.bytes, 0),\n };\n}\n\nconst GITHUB_TREE = /^(?:https?:\\/\\/)?(?:www\\.)?github\\.com\\/([^/]+)\\/([^/#]+?)(?:\\.git)?\\/(?:tree|blob)\\/([^/]+)(?:\\/(.*))?$/;\nconst GITHUB_REPO = /^(?:https?:\\/\\/)?(?:www\\.)?github\\.com\\/([^/]+)\\/([^/#]+?)(?:\\.git)?$/;\nconst OWNER_REPO = /^([A-Za-z0-9][\\w.-]*)\\/([\\w.-]+)$/;\n\n/**\n * Turn a user-supplied spec into an explicit source.\n *\n * Accepts: `owner/repo`, `github.com/owner/repo`, a full https/ssh git URL, a GitHub\n * `/tree/<ref>/<subdir>` link, a local path, or a direct link to a raw `SKILL.md`.\n * Anything else throws rather than being guessed at.\n */\nexport function parseSkillSource(rawSpec: string): SkillSource {\n const spec = rawSpec.trim();\n if (!spec) throw new Error('A skill source is required.');\n\n // Local paths first \u2014 an explicit path prefix is unambiguous.\n if (spec.startsWith('./') || spec.startsWith('../') || spec.startsWith('/') || spec.startsWith('~')) {\n const expanded = spec.startsWith('~') ? path.join(os.homedir(), spec.slice(1)) : spec;\n return { kind: 'path', path: expanded };\n }\n\n if (spec.startsWith('git@') || spec.startsWith('ssh://') || spec.endsWith('.git')) {\n const [url, ref] = splitRef(spec);\n return { kind: 'git', url, ref, subdir: undefined };\n }\n\n const tree = GITHUB_TREE.exec(spec);\n if (tree) {\n const [, owner, repo, ref, subdir] = tree;\n // A `/blob/<ref>/<dir>/SKILL.md` link is what GitHub's UI hands you, and it is the most\n // natural thing to paste. Point it at the containing directory: a subdir naming a file\n // finds no skill directory and would fail with a confusing \"No SKILL.md found\".\n const dir = subdir ? subdir.replace(/(^|\\/)SKILL\\.md$/i, '') : '';\n return { kind: 'git', url: `https://github.com/${owner}/${repo}`, ref, subdir: dir || undefined };\n }\n\n const [base, ref] = splitRef(spec);\n\n const repoMatch = GITHUB_REPO.exec(base);\n if (repoMatch) {\n const [, owner, repo] = repoMatch;\n return { kind: 'git', url: `https://github.com/${owner}/${repo}`, ref, subdir: undefined };\n }\n\n if (/^https?:\\/\\//.test(base)) {\n if (/\\/SKILL\\.md$/i.test(base)) return { kind: 'url', url: base };\n throw new Error(\n `Unsupported URL: ${base}\\nSupported: a git repository, or a direct link ending in /SKILL.md.`,\n );\n }\n\n const ownerRepo = OWNER_REPO.exec(base);\n if (ownerRepo) {\n const [, owner, repo] = ownerRepo;\n return { kind: 'git', url: `https://github.com/${owner}/${repo}`, ref, subdir: undefined };\n }\n\n throw new Error(\n `Could not understand skill source \"${rawSpec}\".\\n` +\n 'Try: owner/repo, github.com/owner/repo, a git URL, a local path, or a URL ending in /SKILL.md.',\n );\n}\n\n/**\n * One line describing what installing `source` would pull in, for an approval card.\n * The distinction that matters is scope: one named skill, or every skill in a repository.\n * Never throws \u2014 an unparseable source is described verbatim so the card still renders.\n */\nexport function describeSkillSource(rawSpec: string): string {\n let parsed: SkillSource;\n try {\n parsed = parseSkillSource(rawSpec);\n } catch {\n return `Install skill from ${rawSpec}`;\n }\n const where = (u: string): string => u.replace(/^https?:\\/\\//, '');\n switch (parsed.kind) {\n case 'url':\n return `Install one skill from ${where(parsed.url)}`;\n case 'path':\n return `Install skill(s) from the local path ${parsed.path}`;\n case 'git': {\n const ref = parsed.ref ? `#${parsed.ref}` : '';\n return parsed.subdir\n ? `Install \"${parsed.subdir}\" from ${where(parsed.url)}${ref}`\n : `Install EVERY skill in ${where(parsed.url)}${ref} (whole repository)`;\n }\n }\n}\n\nfunction splitRef(spec: string): [string, string | undefined] {\n const hash = spec.lastIndexOf('#');\n if (hash === -1) return [spec, undefined];\n return [spec.slice(0, hash), spec.slice(hash + 1) || undefined];\n}\n\n/* ------------------------------- filesystem helpers ------------------------------- */\n\n/** Every file under `root`, relative-pathed, skipping VCS metadata. */\nexport function walkFiles(root: string, rel = ''): FileStat[] {\n const out: FileStat[] = [];\n const dir = path.join(root, rel);\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n if (entry.name === '.git' || entry.name === 'node_modules') continue;\n const childRel = rel ? path.join(rel, entry.name) : entry.name;\n const full = path.join(root, childRel);\n if (entry.isSymbolicLink()) continue; // never follow links out of the tree\n if (entry.isDirectory()) out.push(...walkFiles(root, childRel));\n else if (entry.isFile()) {\n const st = fs.statSync(full);\n out.push({ relPath: childRel, bytes: st.size, mode: st.mode });\n }\n }\n return out;\n}\n\n/** Directories containing a SKILL.md, anywhere under `root`. A repo may hold many. */\nexport function findSkillDirs(root: string): string[] {\n const found: string[] = [];\n const visit = (dir: string, depth: number): void => {\n if (depth > 5) return;\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n if (entries.some((e) => e.isFile() && e.name.toLowerCase() === 'skill.md')) {\n found.push(dir);\n return; // a skill directory owns its whole subtree\n }\n for (const e of entries) {\n if (!e.isDirectory() || e.name === '.git' || e.name === 'node_modules') continue;\n visit(path.join(dir, e.name), depth + 1);\n }\n };\n visit(root, 0);\n return found.sort();\n}\n\nexport function copyTree(src: string, dest: string): void {\n fs.mkdirSync(dest, { recursive: true });\n for (const entry of fs.readdirSync(src, { withFileTypes: true })) {\n if (entry.name === '.git' || entry.name === 'node_modules') continue;\n if (entry.isSymbolicLink()) continue;\n const s = path.join(src, entry.name);\n const d = path.join(dest, entry.name);\n if (entry.isDirectory()) copyTree(s, d);\n else if (entry.isFile()) fs.copyFileSync(s, d);\n }\n}\n\nexport function runGit(args: string[], cwd?: string, timeoutMs = 120_000): Promise<void> {\n return new Promise((resolve, reject) => {\n const child = spawn('git', args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] });\n let stderr = '';\n const timer = setTimeout(() => {\n child.kill('SIGKILL');\n reject(new Error(`git ${args[0]} timed out after ${timeoutMs / 1000}s`));\n }, timeoutMs);\n child.stderr?.on('data', (d) => (stderr += String(d)));\n child.on('error', (err) => {\n clearTimeout(timer);\n reject(new Error(`git is required to install from a repository: ${err.message}`));\n });\n child.on('close', (code) => {\n clearTimeout(timer);\n if (code === 0) resolve();\n else reject(new Error(`git ${args[0]} failed: ${stderr.trim().split('\\n').slice(-3).join(' ')}`));\n });\n });\n}\n\n/* ---------------------------------- staging ---------------------------------- */\n\nimport { parseFrontmatter } from './skills.js';\n\nexport interface StagedSkill {\n /** Name from the SKILL.md frontmatter \u2014 what the SDK's Skill tool matches on. */\n name: string;\n description: string;\n /** Directory holding SKILL.md and any supporting files, ready to copy in. */\n dir: string;\n manifest: SkillManifest;\n}\n\n/** Hard cap so a mis-typed source cannot fill the disk. */\nexport const MAX_SKILL_BYTES = 10 * 1024 * 1024;\n\nfunction readStaged(dir: string): StagedSkill {\n const file = path.join(dir, 'SKILL.md');\n const raw = fs.readFileSync(file, 'utf8');\n const { name, description } = parseFrontmatter(raw);\n if (!name.trim()) {\n throw new Error(\n `${file} has no \\`name\\` in its frontmatter. A skill needs:\\n---\\nname: my-skill\\ndescription: what it does\\n---`,\n );\n }\n const manifest = classifyFiles(walkFiles(dir));\n if (manifest.totalBytes > MAX_SKILL_BYTES) {\n throw new Error(\n `Skill \"${name}\" is ${(manifest.totalBytes / 1048576).toFixed(1)} MB; the limit is ${MAX_SKILL_BYTES / 1048576} MB.`,\n );\n }\n return { name: name.trim(), description: description.trim(), dir, manifest };\n}\n\n/** Collect every skill under a local path. Accepts a skill dir, a SKILL.md, or a pack of skills. */\nexport async function stageFromPath(source: { kind: 'path'; path: string }): Promise<StagedSkill[]> {\n const target = path.resolve(source.path);\n if (!fs.existsSync(target)) throw new Error(`Path not found: ${target}`);\n\n const stat = fs.statSync(target);\n if (stat.isFile()) {\n if (path.basename(target).toLowerCase() !== 'skill.md')\n throw new Error(`Expected a directory or a SKILL.md file, got ${target}`);\n return [readStaged(path.dirname(target))];\n }\n\n const dirs = findSkillDirs(target);\n if (!dirs.length) throw new Error(`No SKILL.md found under ${target}`);\n return dirs.map(readStaged);\n}\n\n/** Clone a repo shallowly into a temp dir and stage the skills inside it. */\nexport async function stageFromGit(\n source: { kind: 'git'; url: string; ref?: string; subdir?: string },\n tmpRoot = os.tmpdir(),\n): Promise<{ staged: StagedSkill[]; cleanup: () => void }> {\n const dir = fs.mkdtempSync(path.join(tmpRoot, 'antbot-skill-git-'));\n const cleanup = (): void => fs.rmSync(dir, { recursive: true, force: true });\n try {\n const args = ['clone', '--depth', '1', '--quiet'];\n if (source.ref) args.push('--branch', source.ref);\n args.push(source.url, dir);\n await runGit(args);\n\n const root = source.subdir ? path.join(dir, source.subdir) : dir;\n if (!fs.existsSync(root)) throw new Error(`\"${source.subdir}\" does not exist in ${source.url}`);\n\n const dirs = findSkillDirs(root);\n if (!dirs.length) throw new Error(`No SKILL.md found in ${source.url}${source.subdir ? `/${source.subdir}` : ''}`);\n return { staged: dirs.map(readStaged), cleanup };\n } catch (err) {\n cleanup();\n throw err;\n }\n}\n\n/** Fetch a single raw SKILL.md into a temp dir. */\nexport async function stageFromUrl(\n source: { kind: 'url'; url: string },\n tmpRoot = os.tmpdir(),\n fetchImpl: typeof fetch = fetch,\n): Promise<{ staged: StagedSkill[]; cleanup: () => void }> {\n const dir = fs.mkdtempSync(path.join(tmpRoot, 'antbot-skill-url-'));\n const cleanup = (): void => fs.rmSync(dir, { recursive: true, force: true });\n try {\n const res = await fetchImpl(source.url, { redirect: 'follow' });\n if (!res.ok) throw new Error(`Fetching ${source.url} failed: HTTP ${res.status}`);\n const body = await res.text();\n if (body.length > MAX_SKILL_BYTES) throw new Error(`${source.url} is larger than the ${MAX_SKILL_BYTES / 1048576} MB limit.`);\n fs.writeFileSync(path.join(dir, 'SKILL.md'), body);\n return { staged: [readStaged(dir)], cleanup };\n } catch (err) {\n cleanup();\n throw err;\n }\n}\n\n/** Resolve any source to staged skills plus a cleanup for whatever temp state it created. */\nexport async function stage(source: SkillSource): Promise<{ staged: StagedSkill[]; cleanup: () => void }> {\n if (source.kind === 'path') return { staged: await stageFromPath(source), cleanup: () => {} };\n if (source.kind === 'git') return stageFromGit(source);\n return stageFromUrl(source);\n}\n\n/** Human-readable summary of what an install put on disk. */\nexport function renderManifest(skill: StagedSkill, installedAt: string): string {\n const lines = [`\u2713 Installed '${skill.name}'`, ''];\n const width = Math.max(...skill.manifest.files.map((f) => f.relPath.length), 10);\n for (const f of [...skill.manifest.files].sort((a, b) => a.relPath.localeCompare(b.relPath))) {\n const size = f.bytes < 1024 ? `${f.bytes} B` : `${(f.bytes / 1024).toFixed(1)} KB`;\n const flag = isExecutableFile(f.relPath, f.mode) ? ' \u26A0 executable' : '';\n lines.push(` ${f.relPath.padEnd(width)} ${size.padStart(8)}${flag}`);\n }\n if (skill.manifest.executables.length) {\n const n = skill.manifest.executables.length;\n lines.push(\n '',\n `\u26A0 This skill ships ${n} ${n === 1 ? 'script' : 'scripts'}. Scripts run with the same access`,\n ' as any bot command. Review before assigning:',\n ` ${installedAt}`,\n );\n }\n return lines.join('\\n');\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": ["ref"]
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createRequire as __antbotCreateRequire } from 'node:module';
|
|
2
2
|
const require = __antbotCreateRequire(import.meta.url);
|
|
3
3
|
|
|
4
|
-
//
|
|
4
|
+
// daemon/src/skills/skills.ts
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
|
|
8
|
-
//
|
|
8
|
+
// daemon/src/util/ids.ts
|
|
9
9
|
import { randomUUID } from "node:crypto";
|
|
10
10
|
var newId = () => randomUUID();
|
|
11
11
|
var now = () => Date.now();
|
|
@@ -17,7 +17,7 @@ function slugify(name, existing = /* @__PURE__ */ new Set()) {
|
|
|
17
17
|
return slug;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
//
|
|
20
|
+
// daemon/src/skills/skills.ts
|
|
21
21
|
var SKILL_TEMPLATE = `## When to use it
|
|
22
22
|
|
|
23
23
|
Describe the trigger: what request or situation should make a bot reach for this skill instead
|
|
@@ -165,8 +165,8 @@ var SkillStore = class {
|
|
|
165
165
|
* `allowMultiple` is set, so a caller cannot install a hundred skills by accident.
|
|
166
166
|
*/
|
|
167
167
|
async installFromSource(source, opts = {}) {
|
|
168
|
-
const { stage, copyTree, renderManifest } = await import("./install-
|
|
169
|
-
const parsed = (await import("./install-
|
|
168
|
+
const { stage, copyTree, renderManifest } = await import("./install-KTEW2CW3.js");
|
|
169
|
+
const parsed = (await import("./install-KTEW2CW3.js")).parseSkillSource(source);
|
|
170
170
|
const { staged, cleanup } = await stage(parsed);
|
|
171
171
|
try {
|
|
172
172
|
if (staged.length > 1 && !opts.allowMultiple) {
|
|
@@ -307,4 +307,4 @@ export {
|
|
|
307
307
|
MultipleSkillsError,
|
|
308
308
|
SkillStore
|
|
309
309
|
};
|
|
310
|
-
//# sourceMappingURL=chunk-
|
|
310
|
+
//# sourceMappingURL=chunk-MSSHR6SW.js.map
|