agent-afk 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.
package/LICENSE ADDED
@@ -0,0 +1 @@
1
+ UNLICENSED
package/README.md ADDED
@@ -0,0 +1,675 @@
1
+ # Agent AFK CLI
2
+
3
+ > A TypeScript CLI, daemon, and Telegram bot for running Claude via `@anthropic-ai/claude-agent-sdk` — ships seven orchestration skills as subagents and mirrors the `agent-framework-private` plugin surface.
4
+
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.7-blue)](https://www.typescriptlang.org/)
6
+
7
+ ## Features
8
+
9
+ - 🚀 **Real Claude Agent SDK integration** — native subagents, hooks, elicitations, cost guardrails
10
+ - 🧩 **Seven orchestration skills as CLI subagents** — `/mint`, `/diagnose`, `/shadow-verify`, `/forge`, `/parallelize`, `/forge-gate-check`, `/forge-l2-eval`
11
+ - 🔌 **Plugin skill-router** — skills under `~/.afk/plugins/*/skills/` auto-exposed as slash commands
12
+ - 🏠 **AFK-scoped config** — `~/.afk/` independent of `~/.claude/`, with `afk plugin install/update/list/remove`
13
+ - 💬 **Three surfaces** — interactive REPL, daemon, Telegram bot sharing one session manager
14
+ - 📊 **Routing telemetry** — every subagent dispatch appended to `~/.claude/agent-framework/routing-decisions.jsonl`
15
+ - 🤖 **Multiple Claude models** — Opus, Sonnet, Haiku
16
+ - 🔓 **Bypass permissions mode** — no prompts, fully automated tool execution
17
+ - 🛡️ **Type-safe** — TypeScript strict mode
18
+
19
+ ## Table of Contents
20
+
21
+ - [Installation](#installation)
22
+ - [Quick Start](#quick-start)
23
+ - [Usage](#usage)
24
+ - [Orchestration Skills](#orchestration-skills)
25
+ - [Scripts](#scripts)
26
+ - [Configuration](#configuration)
27
+ - [Plugins & Slash Commands](#plugins--slash-commands)
28
+ - [Bypass Permissions Mode](#bypass-permissions-mode)
29
+ - [Development](#development)
30
+ - [Troubleshooting](#troubleshooting)
31
+ - [API Reference](#api-reference)
32
+ - [License](#license)
33
+
34
+ ## Installation
35
+
36
+ ### Prerequisites
37
+
38
+ - **Node.js ≥ 18.0.0**
39
+ - **pnpm** — this project's lockfile is pnpm-specific. Running `npm install` will desync it.
40
+ - Fastest path: `corepack enable` (bundled with Node ≥ 16.9), then use `pnpm` directly.
41
+ - Or install globally: `npm install -g pnpm@latest`.
42
+ - A valid Anthropic API key ([console.anthropic.com/settings/keys](https://console.anthropic.com/settings/keys))
43
+
44
+ ### Install from source
45
+
46
+ ```bash
47
+ git clone https://github.com/griffinwork40/agent-afk.git
48
+ cd agent-afk
49
+ corepack enable # optional, pins pnpm to the repo's version
50
+ pnpm install
51
+ pnpm build
52
+ ```
53
+
54
+ ## Quick Start
55
+
56
+ 1. **Configure your API key:**
57
+
58
+ ```bash
59
+ cp .env.example .env
60
+ # Edit .env and set ANTHROPIC_API_KEY
61
+ ```
62
+
63
+ 2. **Build:**
64
+
65
+ ```bash
66
+ pnpm build
67
+ ```
68
+
69
+ 3. **Run your first command:**
70
+
71
+ ```bash
72
+ pnpm start -- chat "What is 2+2?"
73
+ ```
74
+
75
+ 4. **Check status:**
76
+
77
+ ```bash
78
+ pnpm start:status
79
+ ```
80
+
81
+ > Runtime state (sessions, plugins, SDK settings) lives under `~/.afk/`, not `~/.claude/`. See [Configuration → Config Home](#config-home-afk) below.
82
+
83
+ ## Usage
84
+
85
+ ### Quick Aliases
86
+
87
+ - `afk c` — alias for `afk chat`
88
+ - `afk i` — alias for `afk interactive`
89
+ - `afk s` — alias for `afk status`
90
+
91
+ ### CLI Commands
92
+
93
+ The `afk` CLI exposes seven top-level commands registered in `src/cli/index.ts`:
94
+
95
+ | Command | Purpose |
96
+ |---|---|
97
+ | `chat` | Single-turn message |
98
+ | `interactive` | REPL with full Agent SDK features |
99
+ | `status` | Connection, API-key, model, bypass-mode status |
100
+ | `config` | Dump resolved configuration |
101
+ | `daemon` | Long-running headless agent (see `src/agent/daemon/`) |
102
+ | `login` | OAuth flow for `console.anthropic.com` |
103
+ | `plugin` | Manage `~/.afk/plugins/` (install / update / list / remove / enable / disable) |
104
+
105
+ #### Chat (single message)
106
+
107
+ ```bash
108
+ pnpm start -- chat "What is the capital of France?"
109
+ pnpm start -- chat "Hello" --model opus
110
+ pnpm start -- chat "Test" --format json
111
+ pnpm start -- chat "Tell me a story" --stream
112
+ ```
113
+
114
+ #### Interactive mode
115
+
116
+ ```bash
117
+ pnpm start:interactive
118
+ pnpm start -- interactive --model haiku
119
+ ```
120
+
121
+ In interactive mode: type messages, Claude responds with full tool access, Ctrl+C exits.
122
+
123
+ #### Status
124
+
125
+ ```bash
126
+ pnpm start:status
127
+ ```
128
+
129
+ Shows SDK connection, API-key validation, default model, bypass-mode state.
130
+
131
+ Supports `--format json` for machine-readable output.
132
+
133
+ #### View configuration
134
+
135
+ ```bash
136
+ pnpm start -- config
137
+ ```
138
+
139
+ Supports `--format json` for machine-readable output.
140
+
141
+ #### System Health Check
142
+
143
+ ```bash
144
+ npm start -- doctor
145
+ ```
146
+
147
+ Run a self-check on environment, API keys, and configuration. Use `--format json` for machine-readable output.
148
+
149
+ #### Shell Completion
150
+
151
+ ```bash
152
+ npm start -- completion <zsh|bash|fish>
153
+ ```
154
+
155
+ Print a shell completion script. To enable completions, append to your shell rc file:
156
+
157
+ ```bash
158
+ afk completion zsh >> ~/.zshrc
159
+ afk completion bash >> ~/.bashrc
160
+ afk completion fish >> ~/.config/fish/conf.d/afk.fish
161
+ ```
162
+
163
+ #### Plugin Management
164
+
165
+ ```bash
166
+ npm start -- plugin list
167
+ ```
168
+
169
+ Supports `--format json` for machine-readable output.
170
+
171
+ ### Telegram Bot
172
+
173
+ Run Claude as a Telegram bot with full Agent SDK capabilities:
174
+
175
+ ```bash
176
+ # 1. Get a bot token from @BotFather on Telegram
177
+ # - Open Telegram, search @BotFather
178
+ # - Send /newbot and follow instructions, copy the token
179
+ # 2. Add to .env: TELEGRAM_BOT_TOKEN=1234567890:ABCdefGHI...
180
+ # 3. Build and run
181
+ pnpm build
182
+ pnpm telegram
183
+ ```
184
+
185
+ **Bot commands:**
186
+ - `/start` — welcome
187
+ - `/reset` — clear conversation
188
+ - `/model opus|sonnet|haiku` — switch model
189
+
190
+ Send any other message to chat. The bot has full tool access via bypass mode.
191
+
192
+ Background-service helpers:
193
+
194
+ ```bash
195
+ pnpm telegram:start
196
+ pnpm telegram:stop
197
+ pnpm telegram:status
198
+ pnpm telegram:restart
199
+ ```
200
+
201
+ ## Orchestration Skills
202
+
203
+ agent-afk ships seven built-in subagent orchestrators plus `devils-advocate` (in development). These are **skill-router-dispatched**: typing `/mint add dark mode` in the REPL parses the slash form, resolves it to a skill handler under `src/skills/<name>/`, and dispatches a fresh subagent via `SubagentManager.forkSubagent()`. Every dispatch is logged to `~/.claude/agent-framework/routing-decisions.jsonl`.
204
+
205
+ | Skill | Purpose |
206
+ |---|---|
207
+ | `/mint` | End-to-end feature/refactor pipeline: spec → research → plan → parallelize → build → verify → heal → ship |
208
+ | `/diagnose` | Parallel hypothesis generation + validation for bugs and failing tests |
209
+ | `/shadow-verify` | Adversarial re-derivation of sub-agent claims before you act on them |
210
+ | `/forge` | Generate new skills autonomously, gated by L1 capability evals |
211
+ | `/parallelize` | Transform a linear plan into dependency-aware parallel waves |
212
+ | `/forge-gate-check` | Report whether `/forge` is thawed; rerun the L1 eval harness |
213
+ | `/forge-l2-eval` | Run L2 capability evals (live sub-agent verdict probes) |
214
+
215
+ The same seven skills ship in two surfaces:
216
+
217
+ - **CLI surface** (this repo) — TypeScript handlers under `src/skills/<name>/` invoked via `skill-router`.
218
+ - **Plugin surface** — prompt-based `SKILL.md` files under `agent-framework-private/skills/<name>/`, invoked inside a Claude Code session.
219
+
220
+ Vendored subagents (`qualify`, `research-agent`, `contract`) live under `src/skills/_agents/` and are kept byte-equal with the upstream copies — drift is caught by `tests/skills/_agents/vendored.test.ts`.
221
+
222
+ See the workspace-root [`SYSTEM.md`](../SYSTEM.md) for the topology, skill dependency graph, and multi-prompt loading convention.
223
+
224
+ ## Scripts
225
+
226
+ ```bash
227
+ # Build / dev
228
+ pnpm build # tsc && node scripts/copy-prompts.js (markdown prompts → dist/)
229
+ pnpm dev # tsx watch src/cli/index.ts
230
+ pnpm start # node dist/cli/index.js
231
+ pnpm start:chat # shortcut for `chat`
232
+ pnpm start:interactive # shortcut for `interactive`
233
+ pnpm start:status # shortcut for `status`
234
+ pnpm clean # rm -rf dist
235
+
236
+ # Testing
237
+ pnpm test # vitest run (all)
238
+ pnpm test:integration # integration tests only
239
+ pnpm test:e2e # end-to-end tests only
240
+ pnpm test:coverage # with coverage report
241
+ pnpm test:watch # watch mode
242
+ pnpm lint # tsc --noEmit (type-check only)
243
+
244
+ # Telegram daemon
245
+ pnpm telegram # run in foreground
246
+ pnpm telegram:start # background service (launchd/systemd-style wrapper)
247
+ pnpm telegram:stop
248
+ pnpm telegram:status
249
+ pnpm telegram:restart
250
+
251
+ # SDK dependency auditing
252
+ pnpm audit:sdk # regenerate docs/sdk-dependency.md snapshot
253
+ pnpm audit:sdk:check # CI gate — fail if new SDK symbols appear without a lock update
254
+ pnpm audit:sdk:update-lock # update .sdk-dependency.lock.json allowlist
255
+ ```
256
+
257
+ agent-afk is the only subrepo in its workspace that imports `@anthropic-ai/claude-agent-sdk` / `@anthropic-ai/sdk` directly. The `audit:sdk*` scripts track that surface mechanically and fail CI on unauthorized drift. See [`docs/sdk-dependency.md`](docs/sdk-dependency.md) and [`.sdk-dependency.lock.json`](.sdk-dependency.lock.json). When adding a new SDK import, run `pnpm audit:sdk:update-lock` and edit the lock entry's `reason` with a one-line justification before committing.
258
+
259
+ ## Configuration
260
+
261
+ ### Config Home (`~/.afk/`)
262
+
263
+ agent-afk keeps its configuration and user-scope runtime state under `~/.afk/`, fully independent of Claude Code's `~/.claude/`. AFK resolves plugins, agents, commands, skills, and `settings.json` directly from that home:
264
+
265
+ ```
266
+ ~/.afk/
267
+ config/ afk.env, afk.config.json
268
+ state/ sessions/, todos/, daemon/
269
+ logs/
270
+ cache/
271
+ plugins/ user-installed Claude plugins (starts empty)
272
+ agents/ user-defined subagents (SDK)
273
+ commands/ user-defined slash commands (SDK)
274
+ skills/ user-defined skills (SDK)
275
+ settings.json SDK settings (model, permissions, MCP, etc.)
276
+ agent-framework/ forge telemetry + ceiling ledger + briefs
277
+ ```
278
+
279
+ You can delete `~/.claude/` entirely and agent-afk still runs.
280
+
281
+ ### Environment Variables
282
+
283
+ Create a `.env` file in the project root:
284
+
285
+ ```env
286
+ # Required
287
+ ANTHROPIC_API_KEY=sk-ant-api03-...
288
+
289
+ # Optional
290
+ CLAUDE_MODEL=sonnet # opus | sonnet | haiku (default sonnet)
291
+ TELEGRAM_BOT_TOKEN=1234567890:ABC...
292
+
293
+ # Cost guardrails (see SDK-native features below)
294
+ AFK_MAX_BUDGET_USD=5.00
295
+ AFK_TASK_BUDGET=100000
296
+
297
+ # Prompt cache (anthropic-direct provider) — see Prompt Caching below
298
+ AFK_DISABLE_PROMPT_CACHE= # 1 | true | yes | on disables; unset = enabled
299
+ AFK_PROMPT_CACHE_TTL=1h # 5m | 1h (default 1h)
300
+
301
+ # Optional: Color output control
302
+ # Set NO_COLOR=1 to disable colors (per https://no-color.org)
303
+ # Unset or leave empty for auto-detection (disables in CI or piped output)
304
+ NO_COLOR=
305
+ ```
306
+
307
+ ### Prompt Caching (anthropic-direct provider)
308
+
309
+ The `anthropic-direct` provider stamps two `cache_control` breakpoints per request — one at the end of `system` (which implicitly caches `tools + system` together) and one at the end of the last `messages[]` entry. The end-of-messages marker floats forward each turn; cache lookup walks back over prefix-hash matches up to a 20-block window, so the moving marker still hits prior cache writes within a tool-use loop and across consecutive turns.
310
+
311
+ Defaults are tuned for `agent-afk`'s long-lived surfaces (daemon, Telegram bot) which often idle past the 5-minute window:
312
+
313
+ | Variable | Values | Default | Effect |
314
+ |---|---|---|---|
315
+ | `AFK_DISABLE_PROMPT_CACHE` | `1` / `true` / `yes` / `on` (case-insensitive) | unset (cache enabled) | Disables both breakpoints; useful for A/B comparisons and debugging cache-attribution issues |
316
+ | `AFK_PROMPT_CACHE_TTL` | `5m` / `1h` | `1h` | TTL for both breakpoints. Anything other than `5m` or `1h` falls back to the default |
317
+
318
+ Markers never leak back into stored history — `cache-policy.ts` clones-and-stamps so the canonical `messages` array stays marker-free across iterations (accumulating markers would break prefix-hash matching). Implementation: [`src/agent/providers/anthropic-direct/cache-policy.ts`](src/agent/providers/anthropic-direct/cache-policy.ts).
319
+
320
+ ### Supported Models
321
+
322
+ - **opus** — most capable, for complex tasks
323
+ - **sonnet** — balanced performance and speed (default)
324
+ - **haiku** — fastest, best for simple tasks
325
+
326
+ ## Plugins & Slash Commands
327
+
328
+ ### Installing plugins
329
+
330
+ The `/plugin` slash command (and marketplace-based install) is a Claude Code CLI feature, not an Agent SDK feature — see [anthropics/claude-code#15071](https://github.com/anthropics/claude-code/issues/15071). Inside an agent-afk session those commands don't exist, and marketplace installs run from Claude Code still land in `~/.claude/plugins/`. AFK ships its own `afk plugin` subcommand that keeps everything under `~/.afk/plugins/`:
331
+
332
+ ```bash
333
+ # Install from a GitHub shorthand (expands to https://github.com/<owner>/<repo>.git)
334
+ afk plugin install anthropics/claude-plugins-official
335
+
336
+ # Install from an explicit git URL (https or ssh)
337
+ afk plugin install https://github.com/example/my-plugin.git
338
+
339
+ # Install from a local checkout (symlinks into ~/.afk/plugins/)
340
+ afk plugin install ~/Projects/personal_projects/agent-workspace/agent-framework-private
341
+
342
+ # Pin to a specific tag, branch, or SHA
343
+ afk plugin install owner/repo --ref v1.2.3
344
+
345
+ # List, update, and disable without deleting
346
+ afk plugin list
347
+ afk plugin update # all plugins
348
+ afk plugin update claude-plugins-official
349
+ afk plugin disable claude-plugins-official
350
+ afk plugin enable claude-plugins-official
351
+ afk plugin remove claude-plugins-official
352
+ ```
353
+
354
+ By default `install` picks the highest semver git tag, falling back to the default branch when no tag parses as semver. State lives in `~/.afk/plugins/.index.json` — the scanner reads it at session startup and skips any entry with `enabled: false`.
355
+
356
+ Advanced: AFK still auto-discovers any plugin dropped into `~/.afk/plugins/<name>/` by hand (git clone, symlink, or copy). The directory just has to contain `.claude-plugin/plugin.json`; the scanner walks up to 5 levels deep so Claude Code's `cache/<marketplace>/<plugin>/<version>/` layout also works.
357
+
358
+ Plugin state (telemetry, ledger, briefs) writes to `~/.afk/agent-framework/` in the AFK runtime.
359
+
360
+ ### Plugin skills as slash commands
361
+
362
+ Every skill loaded from `~/.afk/plugins/<plugin>/skills/*/SKILL.md` is exposed automatically in the interactive REPL as `/<skill-name>`. There is no per-skill handler code — agent-afk asks the SDK for its skill catalog at session start (`session.supportedCommands()`) and registers a passthrough handler per entry. Typing `/mint add dark mode` pipes the raw line into the SDK turn loop; the subprocess parses the slash form natively and dispatches to the plugin's skill, exactly the way Claude Code does it.
363
+
364
+ - `/help` — list all available slash commands (built-in + plugin-loaded)
365
+ - `/skills` — discover skills loaded from plugins
366
+ - `/reload-plugins` — reload after editing SKILL.md files on disk
367
+
368
+ Implementation lives in `src/cli/slash/plugin-skills.ts`; see the module header for the flow.
369
+
370
+ ### SDK-native features surfaced in the REPL
371
+
372
+ agent-afk wires several Claude Agent SDK capabilities that Claude Code exposes natively, so they feel the same here:
373
+
374
+ - **`/agents`** — lists Task-tool subagents loaded by the SDK (plugin + user + project scope). Agents are not user-invokable slashes; they're dispatch targets the model picks via the Task tool. The list shows name, description, and model override when present. Refresh with `/reload-plugins` after editing `~/.afk/agents/` or plugin agent definitions.
375
+ - **`/tokens`** — renders the authoritative SDK breakdown of context usage: total vs model max, auto-compact threshold, top categories, system tools, MCP tools, agents, skills, slash commands, and the last-turn API usage. Falls back to local-stats aggregation when the SDK call can't be served (e.g., before the subprocess is warm).
376
+ - **Status-line context %** — sampled every 3 turns from `session.getContextUsage()`, cached between samples, degrades gracefully on transient failures. See `src/cli/context-sampler.ts`.
377
+ - **Progress banners** — when the SDK emits `task_progress` events (long subagent runs, multi-tool flows), they render inline as `◦ description (stats)` plus an indented summary when present. Telegram forwards the same lines with the existing edit-throttle, and prompt suggestions trail as `💡` lines below the response. Enabled by default via `agentProgressSummaries: true`.
378
+ - **Cost guardrails** — pass `--max-budget-usd <n>` (or set `AFK_MAX_BUDGET_USD`) to abort the session cleanly on cost breach. `--task-budget <tokens>` (or `AFK_TASK_BUDGET`) is an advisory per-task token hint surfaced to the model so it can pace itself. Both work across `afk interactive`, `afk chat`, and the Telegram bot.
379
+ - **MCP elicitations** — when an MCP server requests OAuth consent (e.g. Supabase re-auth), the REPL prints the server name, message, and URL, then asks `Continue? [y/N]`. Empty answer cancels; `n` declines; `y` accepts. Form-mode elicitations are auto-declined in v1 (tracked in `todo.md`). Handler is installed via `elicitationRouter.install(...)`; bridges can install their own.
380
+
381
+ ## Bypass Permissions Mode
382
+
383
+ ### What is it?
384
+
385
+ By default, the Claude Agent SDK asks for permission before executing tools like:
386
+ - Running bash commands
387
+ - Reading/writing files
388
+ - Making web requests
389
+ - Etc.
390
+
391
+ **Agent AFK CLI enables `bypassPermissions: true`** which:
392
+ - ✅ Skips all permission prompts
393
+ - ✅ Allows fully automated workflows
394
+ - ✅ Perfect for CLI/scripting use cases
395
+ - ⚠️ **Requires trust** — Claude has full tool access
396
+
397
+ ### Security Considerations
398
+
399
+ When bypass mode is enabled:
400
+ - Claude can execute bash commands
401
+ - Claude can read and write files in accessible directories
402
+ - Claude can make web requests
403
+ - Claude can use grep, glob, and other system tools
404
+
405
+ **Use in trusted environments only.** This mode is designed for:
406
+ - Personal CLI tools
407
+ - Automation scripts
408
+ - Development environments
409
+ - Trusted agent workflows
410
+
411
+ ### Default Allowed Tools
412
+
413
+ From `src/agent/session/query-options.ts`:
414
+
415
+ ```typescript
416
+ [
417
+ 'Bash',
418
+ 'Read',
419
+ 'Write',
420
+ 'Edit',
421
+ 'Glob',
422
+ 'Grep',
423
+ 'WebSearch',
424
+ 'WebFetch',
425
+ 'LS',
426
+ 'Task',
427
+ 'BashOutput',
428
+ ]
429
+ ```
430
+
431
+ Override per-session by passing `config.tools.allowedTools` to `AgentSession`, or disable individual tools via `config.tools.disallowedTools`.
432
+
433
+ ## Development
434
+
435
+ ### Project Structure
436
+
437
+ ```
438
+ agent-afk/
439
+ ├── src/
440
+ │ ├── cli/
441
+ │ │ ├── index.ts # CLI entry (commander)
442
+ │ │ └── commands/ # chat, interactive, status, config, daemon, login, plugin, skill-router
443
+ │ ├── agent/
444
+ │ │ ├── session.ts # AgentSession barrel
445
+ │ │ ├── session/ # agent-session, query-options, …
446
+ │ │ ├── subagent.ts # SubagentManager barrel
447
+ │ │ ├── subagent/ # forkSubagent implementation
448
+ │ │ ├── subagent-hooks.ts
449
+ │ │ ├── routing-telemetry.ts # appends routing-decisions.jsonl
450
+ │ │ ├── daemon/ # long-running headless agent
451
+ │ │ ├── plugins/ # afk plugin install / update / remove / index-store
452
+ │ │ ├── providers/ # provider abstraction
453
+ │ │ ├── elicitation-router.ts
454
+ │ │ ├── hook-registry.ts, hooks.ts, default-hook-registry.ts
455
+ │ │ ├── permissions.ts, abort-graph.ts, dag.ts, message-queue.ts
456
+ │ │ ├── output-extractor.ts, plugins-scanner.ts, timeout.ts
457
+ │ │ ├── shadow-verify-nudge.ts
458
+ │ │ └── types.ts, types/
459
+ │ ├── skills/
460
+ │ │ ├── mint/ # /mint
461
+ │ │ ├── diagnose/ # /diagnose
462
+ │ │ ├── shadow-verify/ # /shadow-verify
463
+ │ │ ├── forge/ # /forge
464
+ │ │ ├── parallelize/ # /parallelize
465
+ │ │ ├── forge-gate-check/ # /forge-gate-check
466
+ │ │ ├── forge-l2-eval/ # /forge-l2-eval
467
+ │ │ ├── devils-advocate/ # in development
468
+ │ │ ├── _agents/ # vendored subagents (qualify, research-agent, contract)
469
+ │ │ ├── _lib/ # prompt-loader, shared helpers
470
+ │ │ ├── example-template/ # scaffold for new skills
471
+ │ │ └── index.ts
472
+ │ ├── telegram/ # telegram bridge
473
+ │ ├── telegram.ts # telegram bot entry
474
+ │ ├── utils/
475
+ │ ├── paths.ts
476
+ │ └── index.ts
477
+ ├── tests/
478
+ │ ├── integration/
479
+ │ └── e2e/
480
+ ├── scripts/
481
+ │ ├── copy-prompts.js # bundles src/**/*.md into dist/ after tsc
482
+ │ ├── audit-sdk-dependency.ts
483
+ │ └── telegram-manager.sh
484
+ ├── docs/
485
+ │ └── sdk-dependency.md # committed SDK symbol snapshot
486
+ ├── .sdk-dependency.lock.json # SDK symbol allowlist (CI-gated)
487
+ ├── pnpm-lock.yaml
488
+ ├── package.json
489
+ ├── tsconfig.json
490
+ ├── vitest.config.ts
491
+ └── README.md
492
+ ```
493
+
494
+ ### Build Process
495
+
496
+ ```bash
497
+ pnpm clean && pnpm build # full clean rebuild
498
+ pnpm dev # auto-rebuild (tsx watch)
499
+ pnpm lint # type-check without emitting
500
+ ```
501
+
502
+ `pnpm build` runs `tsc` and then `scripts/copy-prompts.js`, which copies every `src/**/*.md` file into `dist/` at matching relative paths. Skills read their prompts via `readFileSync` at import time, so those markdown files must live next to the compiled `.js` output.
503
+
504
+ ### SDK Dependency Tracking
505
+
506
+ agent-afk is the only subrepo in its workspace that imports from `@anthropic-ai/claude-agent-sdk` or `@anthropic-ai/sdk`. That surface is tracked mechanically:
507
+
508
+ - [`docs/sdk-dependency.md`](docs/sdk-dependency.md) — committed snapshot of every tracked symbol, its files, and runtime-vs-type classification.
509
+ - [`.sdk-dependency.lock.json`](.sdk-dependency.lock.json) — allowlist with per-symbol `reason` fields. CI fails when a new symbol or kind-change appears without a lock update.
510
+ - `~/.afk/agent-framework/sdk-dependency-telemetry.jsonl` — append-only log of symbol-count deltas and SHA over time.
511
+
512
+ When adding a new SDK import: run `pnpm audit:sdk:update-lock`, then edit the generated lock entry's `reason` with a one-line justification before committing.
513
+
514
+ ### Testing
515
+
516
+ ```bash
517
+ pnpm test # all
518
+ pnpm test:coverage # with coverage
519
+ pnpm test:watch # watch mode
520
+ pnpm test:integration # integration only
521
+ pnpm test:e2e # e2e only
522
+ ```
523
+
524
+ ## Troubleshooting
525
+
526
+ ### API Key Issues
527
+
528
+ **Error: `invalid x-api-key`**
529
+
530
+ Your API key is invalid or expired. Get a new one at [console.anthropic.com/settings/keys](https://console.anthropic.com/settings/keys).
531
+
532
+ ```bash
533
+ # Test your API key
534
+ node -e "
535
+ import('@anthropic-ai/sdk').then(async (Anthropic) => {
536
+ const dotenv = await import('dotenv');
537
+ dotenv.config();
538
+ const client = new Anthropic.default({ apiKey: process.env.ANTHROPIC_API_KEY });
539
+ const message = await client.messages.create({
540
+ max_tokens: 10,
541
+ messages: [{ role: 'user', content: 'Hi' }],
542
+ model: 'claude-3-5-sonnet-20241022',
543
+ });
544
+ console.log('✓ API Key works!');
545
+ }).catch(e => console.error('✗ API Key error:', e.message));
546
+ "
547
+ ```
548
+
549
+ **Error: `ANTHROPIC_API_KEY not found`**
550
+
551
+ Check that:
552
+ 1. `.env` file exists in project root
553
+ 2. Contains `ANTHROPIC_API_KEY=sk-ant-...`
554
+ 3. No quotes around the key value
555
+ 4. No trailing spaces
556
+
557
+ ### Build Issues
558
+
559
+ **TypeScript errors:**
560
+
561
+ ```bash
562
+ pnpm clean
563
+ rm -rf node_modules
564
+ pnpm install
565
+ pnpm build
566
+ ```
567
+
568
+ **Module not found:**
569
+
570
+ ```bash
571
+ pnpm build
572
+ ```
573
+
574
+ ### Runtime Issues
575
+
576
+ **`Cannot send message: session is closed`** — the session was closed or timed out. Create a new one.
577
+
578
+ **Timeout errors** — increase max turns:
579
+
580
+ ```bash
581
+ pnpm start -- chat "complex task" --max-turns 50
582
+ ```
583
+
584
+ **`Maximum turns exceeded`** — the conversation hit the turn limit (a safety feature). Raise `--max-turns` if needed.
585
+
586
+ ## API Reference
587
+
588
+ ### AgentSession
589
+
590
+ ```typescript
591
+ import { AgentSession } from './agent/session.js';
592
+
593
+ const session = new AgentSession({
594
+ model: 'sonnet',
595
+ apiKey: process.env.ANTHROPIC_API_KEY!,
596
+ maxTurns: 100,
597
+ systemPrompt: 'You are a helpful assistant.',
598
+ tools: {
599
+ allowedTools: ['Bash', 'Read', 'Write'],
600
+ },
601
+ });
602
+
603
+ // Single message
604
+ const response = await session.sendMessage('Hello!');
605
+ console.log(response.content);
606
+
607
+ // Streaming (async iterator)
608
+ for await (const event of session.sendMessageStream('Tell me a story')) {
609
+ process.stdout.write(event.text ?? '');
610
+ }
611
+
612
+ // Runtime control
613
+ await session.interrupt(); // cancel in-flight turn
614
+ await session.setModel('opus'); // hot-swap model
615
+ await session.setPermissionMode('acceptEdits'); // switch permission mode
616
+
617
+ // State accessors
618
+ session.state; // 'idle' | 'processing' | 'streaming' | 'closed'
619
+ session.sessionId; // string | undefined
620
+ session.abortSignal; // AbortSignal
621
+ const history = session.getHistory();
622
+
623
+ // Cleanup
624
+ await session.close();
625
+ ```
626
+
627
+ ### Types
628
+
629
+ ```typescript
630
+ interface AgentConfig {
631
+ model: 'opus' | 'sonnet' | 'haiku';
632
+ apiKey: string;
633
+ maxTurns?: number;
634
+ systemPrompt?: string;
635
+ tools?: {
636
+ allowedTools?: string[];
637
+ disallowedTools?: string[];
638
+ };
639
+ }
640
+
641
+ interface Message {
642
+ role: 'user' | 'assistant';
643
+ content: string;
644
+ timestamp?: Date;
645
+ }
646
+
647
+ type SessionState = 'idle' | 'processing' | 'streaming' | 'closed';
648
+ ```
649
+
650
+ ## Contributing
651
+
652
+ Contributions welcome. Standard flow:
653
+
654
+ 1. Fork the repository
655
+ 2. Create a feature branch
656
+ 3. Make your changes
657
+ 4. Add tests
658
+ 5. Run `pnpm test && pnpm lint`
659
+ 6. Open a pull request
660
+
661
+ New orchestration skills, CI gate changes, and ceiling-ledger conventions are documented in the workspace-root [`SYSTEM.md`](../SYSTEM.md).
662
+
663
+ ## License
664
+
665
+ MIT © Griffin Long
666
+
667
+ ## Acknowledgments
668
+
669
+ - Built with [@anthropic-ai/claude-agent-sdk](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk)
670
+ - CLI framework: [Commander.js](https://github.com/tj/commander.js)
671
+ - Testing: [Vitest](https://vitest.dev/)
672
+
673
+ ---
674
+
675
+ **Note:** This tool uses bypass permissions mode. Only use in trusted environments where you're comfortable giving Claude full tool access.