ashlrcode 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +295 -0
  3. package/package.json +46 -0
  4. package/src/__tests__/branded-types.test.ts +47 -0
  5. package/src/__tests__/context.test.ts +163 -0
  6. package/src/__tests__/cost-tracker.test.ts +274 -0
  7. package/src/__tests__/cron.test.ts +197 -0
  8. package/src/__tests__/dream.test.ts +204 -0
  9. package/src/__tests__/error-handler.test.ts +192 -0
  10. package/src/__tests__/features.test.ts +69 -0
  11. package/src/__tests__/file-history.test.ts +177 -0
  12. package/src/__tests__/hooks.test.ts +145 -0
  13. package/src/__tests__/keybindings.test.ts +159 -0
  14. package/src/__tests__/model-patches.test.ts +82 -0
  15. package/src/__tests__/permissions-rules.test.ts +121 -0
  16. package/src/__tests__/permissions.test.ts +108 -0
  17. package/src/__tests__/project-config.test.ts +63 -0
  18. package/src/__tests__/retry.test.ts +321 -0
  19. package/src/__tests__/router.test.ts +158 -0
  20. package/src/__tests__/session-compact.test.ts +191 -0
  21. package/src/__tests__/session.test.ts +145 -0
  22. package/src/__tests__/skill-registry.test.ts +130 -0
  23. package/src/__tests__/speculation.test.ts +196 -0
  24. package/src/__tests__/tasks-v2.test.ts +267 -0
  25. package/src/__tests__/telemetry.test.ts +149 -0
  26. package/src/__tests__/tool-executor.test.ts +141 -0
  27. package/src/__tests__/tool-registry.test.ts +166 -0
  28. package/src/__tests__/undercover.test.ts +93 -0
  29. package/src/__tests__/workflow.test.ts +195 -0
  30. package/src/agent/async-context.ts +64 -0
  31. package/src/agent/context.ts +245 -0
  32. package/src/agent/cron.ts +189 -0
  33. package/src/agent/dream.ts +165 -0
  34. package/src/agent/error-handler.ts +108 -0
  35. package/src/agent/ipc.ts +256 -0
  36. package/src/agent/kairos.ts +207 -0
  37. package/src/agent/loop.ts +314 -0
  38. package/src/agent/model-patches.ts +68 -0
  39. package/src/agent/speculation.ts +219 -0
  40. package/src/agent/sub-agent.ts +125 -0
  41. package/src/agent/system-prompt.ts +231 -0
  42. package/src/agent/team.ts +220 -0
  43. package/src/agent/tool-executor.ts +162 -0
  44. package/src/agent/workflow.ts +189 -0
  45. package/src/agent/worktree-manager.ts +86 -0
  46. package/src/autopilot/queue.ts +186 -0
  47. package/src/autopilot/scanner.ts +245 -0
  48. package/src/autopilot/types.ts +58 -0
  49. package/src/bridge/bridge-client.ts +57 -0
  50. package/src/bridge/bridge-server.ts +81 -0
  51. package/src/cli.ts +1120 -0
  52. package/src/config/features.ts +51 -0
  53. package/src/config/git.ts +137 -0
  54. package/src/config/hooks.ts +201 -0
  55. package/src/config/permissions.ts +251 -0
  56. package/src/config/project-config.ts +63 -0
  57. package/src/config/remote-settings.ts +163 -0
  58. package/src/config/settings-sync.ts +170 -0
  59. package/src/config/settings.ts +113 -0
  60. package/src/config/undercover.ts +76 -0
  61. package/src/config/upgrade-notice.ts +65 -0
  62. package/src/mcp/client.ts +197 -0
  63. package/src/mcp/manager.ts +125 -0
  64. package/src/mcp/oauth.ts +252 -0
  65. package/src/mcp/types.ts +61 -0
  66. package/src/persistence/memory.ts +129 -0
  67. package/src/persistence/session.ts +289 -0
  68. package/src/planning/plan-mode.ts +128 -0
  69. package/src/planning/plan-tools.ts +138 -0
  70. package/src/providers/anthropic.ts +177 -0
  71. package/src/providers/cost-tracker.ts +184 -0
  72. package/src/providers/retry.ts +264 -0
  73. package/src/providers/router.ts +159 -0
  74. package/src/providers/types.ts +79 -0
  75. package/src/providers/xai.ts +217 -0
  76. package/src/repl.tsx +1384 -0
  77. package/src/setup.ts +119 -0
  78. package/src/skills/loader.ts +78 -0
  79. package/src/skills/registry.ts +78 -0
  80. package/src/skills/types.ts +11 -0
  81. package/src/state/file-history.ts +264 -0
  82. package/src/telemetry/event-log.ts +116 -0
  83. package/src/tools/agent.ts +133 -0
  84. package/src/tools/ask-user.ts +229 -0
  85. package/src/tools/bash.ts +146 -0
  86. package/src/tools/config.ts +147 -0
  87. package/src/tools/diff.ts +137 -0
  88. package/src/tools/file-edit.ts +123 -0
  89. package/src/tools/file-read.ts +82 -0
  90. package/src/tools/file-write.ts +82 -0
  91. package/src/tools/glob.ts +76 -0
  92. package/src/tools/grep.ts +187 -0
  93. package/src/tools/ls.ts +77 -0
  94. package/src/tools/lsp.ts +375 -0
  95. package/src/tools/mcp-resources.ts +83 -0
  96. package/src/tools/mcp-tool.ts +47 -0
  97. package/src/tools/memory.ts +148 -0
  98. package/src/tools/notebook-edit.ts +133 -0
  99. package/src/tools/peers.ts +113 -0
  100. package/src/tools/powershell.ts +83 -0
  101. package/src/tools/registry.ts +114 -0
  102. package/src/tools/send-message.ts +75 -0
  103. package/src/tools/sleep.ts +50 -0
  104. package/src/tools/snip.ts +143 -0
  105. package/src/tools/tasks.ts +349 -0
  106. package/src/tools/team.ts +309 -0
  107. package/src/tools/todo-write.ts +93 -0
  108. package/src/tools/tool-search.ts +83 -0
  109. package/src/tools/types.ts +52 -0
  110. package/src/tools/web-browser.ts +263 -0
  111. package/src/tools/web-fetch.ts +118 -0
  112. package/src/tools/web-search.ts +107 -0
  113. package/src/tools/workflow.ts +188 -0
  114. package/src/tools/worktree.ts +143 -0
  115. package/src/types/branded.ts +22 -0
  116. package/src/ui/App.tsx +184 -0
  117. package/src/ui/BuddyPanel.tsx +52 -0
  118. package/src/ui/PermissionPrompt.tsx +29 -0
  119. package/src/ui/banner.ts +217 -0
  120. package/src/ui/buddy-ai.ts +108 -0
  121. package/src/ui/buddy.ts +466 -0
  122. package/src/ui/context-bar.ts +60 -0
  123. package/src/ui/effort.ts +65 -0
  124. package/src/ui/keybindings.ts +143 -0
  125. package/src/ui/markdown.ts +271 -0
  126. package/src/ui/message-renderer.ts +73 -0
  127. package/src/ui/mode.ts +80 -0
  128. package/src/ui/notifications.ts +57 -0
  129. package/src/ui/speech-bubble.ts +95 -0
  130. package/src/ui/spinner.ts +116 -0
  131. package/src/ui/theme.ts +98 -0
  132. package/src/version.ts +5 -0
  133. package/src/voice/voice-mode.ts +169 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AshlrAI, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,295 @@
1
+ # AshlrCode (ac)
2
+
3
+ **Multi-provider AI coding agent for the terminal.**
4
+
5
+ [![Version](https://img.shields.io/badge/version-1.0.0-blue)]()
6
+ [![Tests](https://img.shields.io/badge/tests-335%20passing-green)]()
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue)]()
8
+ [![Runtime](https://img.shields.io/badge/runtime-Bun-black)]()
9
+ [![License](https://img.shields.io/badge/license-MIT-green)]()
10
+
11
+ **42 tools | 34 commands | 6 providers | 335 tests | 130 source files**
12
+
13
+ ---
14
+
15
+ ## What is AshlrCode?
16
+
17
+ AshlrCode is an open-source AI coding agent CLI built as an alternative to Claude Code. It runs multi-provider LLM conversations with tool use in your terminal — powered by xAI Grok by default, with failover to Anthropic, OpenAI, DeepSeek, Groq, and Ollama. It ships with 42 built-in tools, an autonomous KAIROS mode, sub-agent orchestration, and a persistent buddy companion.
18
+
19
+ ---
20
+
21
+ ## Quick Start
22
+
23
+ ```bash
24
+ git clone https://github.com/ashlrai/ashlrcode.git
25
+ cd ashlrcode
26
+ bun install
27
+ bun link # makes 'ac' available globally
28
+
29
+ export XAI_API_KEY="your-key"
30
+
31
+ ac # interactive REPL
32
+ ac "fix the login bug" # single-shot mode
33
+ ac --continue # resume last session
34
+ ac --resume <id> # resume specific session
35
+ ```
36
+
37
+ ---
38
+
39
+ ## Features
40
+
41
+ ### Core
42
+
43
+ - **Agent loop** — AsyncGenerator-based streaming with parallel tool execution
44
+ - **Multi-provider failover** — automatic retry and provider switching on rate limits
45
+ - **3-tier context compression** — autoCompact, snipCompact, contextCollapse
46
+ - **Speculation** — speculative tool execution for faster responses
47
+ - **Model patches** — per-model prompt adjustments for optimal behavior
48
+
49
+ ### Tools (42)
50
+
51
+ | Category | Tools | Description |
52
+ |----------|-------|-------------|
53
+ | **File I/O** | Read, Write, Edit, NotebookEdit, LS | Read, write, and edit files with undo snapshots |
54
+ | **Search** | Glob, Grep, ToolSearch | Pattern matching, regex search, tool discovery |
55
+ | **Execution** | Bash, PowerShell | Shell execution with live streaming and timeouts |
56
+ | **Web** | WebFetch, WebSearch, WebBrowser | HTTP requests, search engines, browser automation |
57
+ | **Interaction** | AskUser, SendMessage | Structured user prompts, inter-agent messaging |
58
+ | **Agents** | Agent, ListPeers | Parallel sub-agents, peer discovery |
59
+ | **Tasks** | TaskCreate, TaskUpdate, TaskList, TaskGet, TodoWrite | Task boards with dependencies and ownership |
60
+ | **Planning** | EnterPlan, PlanWrite, ExitPlan (via mode) | Read-only exploration then structured execution |
61
+ | **Memory** | MemorySave, MemoryList, MemoryDelete | Persistent per-project context across sessions |
62
+ | **Config** | Config | View and modify settings at runtime |
63
+ | **Git** | EnterWorktree, ExitWorktree, Diff | Isolated worktree branches, diff inspection |
64
+ | **Teams** | TeamCreate, TeamDelete, TeamList, TeamDispatch | Named teammate roles with task dispatch |
65
+ | **Infrastructure** | LSP, Workflow, Snip, Sleep | Language server, reusable workflows, context trimming, polling |
66
+ | **MCP** | ListMcpResources, mcp__*__* | External tool servers via Model Context Protocol |
67
+
68
+ ### Commands (34)
69
+
70
+ | Command | Description |
71
+ |---------|-------------|
72
+ | `/help` | List all commands |
73
+ | `/cost` | Token usage and cost breakdown |
74
+ | `/status` | Provider, context usage, session info |
75
+ | `/model [name]` | Show or switch model (aliases: `grok-fast`, `sonnet`, `opus`, `local`) |
76
+ | `/effort [level]` | Cycle or set effort level (low / normal / high) |
77
+ | `/compact` | Run all 3 context compression tiers |
78
+ | `/clear` | Clear conversation history |
79
+ | `/history` | File change history with timestamps |
80
+ | `/undo` | Revert last file change |
81
+ | `/restore` | Show available file snapshots |
82
+ | `/diff` | Git diff --stat |
83
+ | `/git` | Recent git log |
84
+ | `/plan` | Cycle mode (normal / plan / auto) |
85
+ | `/tools` | List all registered tools |
86
+ | `/skills` | List available slash-command skills |
87
+ | `/sessions` | List saved sessions |
88
+ | `/memory` | Show project memories |
89
+ | `/buddy` | Buddy stats, species, rarity, level |
90
+ | `/btw <question>` | Side question in sub-agent (no main context pollution) |
91
+ | `/autopilot` | Autonomous scan / queue / approve / run / auto |
92
+ | `/kairos <goal>` | Start KAIROS autonomous mode |
93
+ | `/trigger` | Scheduled triggers (add / list / toggle / delete) |
94
+ | `/voice` | Voice input via Whisper (record / transcribe) |
95
+ | `/sync` | Export / import settings across machines |
96
+ | `/bridge` | Bridge server status (HTTP API for external tools) |
97
+ | `/keybindings` | Show and customize keyboard shortcuts |
98
+ | `/features` | Feature flag status |
99
+ | `/patches` | Active model patches for current model |
100
+ | `/undercover` | Toggle undercover mode (stealth prompts) |
101
+ | `/remote` | Remote settings status |
102
+ | `/telemetry` | Recent telemetry events |
103
+ | `/quit` | Exit (also `/exit`, `/q`) |
104
+
105
+ Plus **custom skills** loaded from `~/.ashlrcode/skills/*.md` — invoked as `/skill-name`.
106
+
107
+ ### Agent System
108
+
109
+ - **Sub-agents** — spawn parallel agents for research, exploration, and independent tasks
110
+ - **Worktree isolation** — agents work in git worktrees to avoid conflicts
111
+ - **KAIROS autonomous mode** — heartbeat-driven loop with focus-aware autonomy levels
112
+ - **Team dispatch** — named teammates with roles, dispatched to tasks
113
+ - **IPC** — inter-process communication between agent instances
114
+ - **Peer discovery** — agents find and message sibling instances
115
+
116
+ ### UX
117
+
118
+ - **Ink-based UI** — React terminal rendering with input box, context bar, and autocomplete
119
+ - **Buddy system** — persistent ASCII pet companion with species, moods, hats, rarity, and stats
120
+ - **Keybindings** — customizable shortcuts, chord bindings, Shift+Tab mode switching
121
+ - **Effort levels** — low / normal / high controls response depth
122
+ - **Smart paste** — large clipboard pastes auto-collapsed in context
123
+ - **Image support** — drag-and-drop images with base64 collapse
124
+ - **Voice mode** — record and transcribe via Whisper
125
+ - **Notifications** — system notifications on task completion
126
+
127
+ ### Persistence
128
+
129
+ - **Sessions** — JSONL at `~/.ashlrcode/sessions/`, resume with `--continue` or `--resume`
130
+ - **Dreams** — background memory consolidation when idle, loaded on next session
131
+ - **File undo** — every Write/Edit snapshots the original, revert with `/undo`
132
+ - **Settings sync** — export/import settings across machines with `/sync`
133
+ - **Memory** — persistent per-project context loaded automatically
134
+
135
+ ### Security
136
+
137
+ - **Permission system** — read-only tools auto-allowed; write tools prompt `[y]es / [a]lways / [n]o / [d]eny-always`
138
+ - **Permission rules** — regex-based allow/deny rules in settings
139
+ - **Hook system** — pre/post tool hooks can block, modify, or extend tool calls
140
+ - **Undercover mode** — stealth prompt adjustments
141
+ - **Input validation** — tool input schemas validated before execution
142
+
143
+ ### Infrastructure
144
+
145
+ - **Feature flags** — runtime toggles for experimental features
146
+ - **Telemetry** — event logging for debugging and analytics
147
+ - **Cost tracking** — per-provider token and cost accounting
148
+ - **Retry with backoff** — rate limits (3x, 1s base), network errors (2x, 2s base)
149
+ - **Speculation** — predictive tool execution
150
+ - **LSP integration** — Language Server Protocol for diagnostics and completions
151
+ - **MCP OAuth** — OAuth flow for MCP server authentication
152
+ - **Cron triggers** — scheduled recurring agent tasks
153
+ - **IPC** — inter-process messaging between instances
154
+ - **Bridge server** — HTTP API for external tool integration
155
+ - **Remote settings** — fetch config overrides from a URL
156
+ - **Model patches** — per-model prompt tuning
157
+
158
+ ---
159
+
160
+ ## Configuration
161
+
162
+ | Path | Purpose |
163
+ |------|---------|
164
+ | `~/.ashlrcode/settings.json` | Providers, hooks, MCP servers, feature flags |
165
+ | `~/.ashlrcode/keybindings.json` | Custom keyboard shortcuts |
166
+ | `~/.ashlrcode/permissions.json` | Persisted tool permission rules |
167
+ | `~/.ashlrcode/sessions/` | Saved conversation sessions (JSONL) |
168
+ | `~/.ashlrcode/dreams/` | Background memory consolidation files |
169
+ | `~/.ashlrcode/memory/` | Per-project persistent memories |
170
+ | `~/.ashlrcode/tasks/` | Persisted task boards |
171
+ | `~/.ashlrcode/skills/` | Custom skill definitions (`.md` files) |
172
+ | `./ASHLR.md` or `./CLAUDE.md` | Project-level instructions |
173
+
174
+ ### Environment Variables
175
+
176
+ | Variable | Purpose |
177
+ |----------|---------|
178
+ | `XAI_API_KEY` | xAI Grok API key (primary) |
179
+ | `ANTHROPIC_API_KEY` | Anthropic Claude API key |
180
+ | `OPENAI_API_KEY` | OpenAI API key (also used for Whisper voice) |
181
+ | `DEEPSEEK_API_KEY` | DeepSeek API key |
182
+ | `GROQ_API_KEY` | Groq API key |
183
+ | `AC_BRIDGE_PORT` | Enable bridge server on this port |
184
+ | `AC_REMOTE_SETTINGS_URL` | URL for remote settings fetch |
185
+ | `AC_FEATURE_VOICE_MODE` | Enable voice input (`true`) |
186
+
187
+ ### Hook System
188
+
189
+ Pre/post tool execution hooks for automation and safety:
190
+
191
+ ```json
192
+ {
193
+ "hooks": {
194
+ "preToolUse": [
195
+ { "toolName": "Bash", "inputPattern": "rm -rf", "action": "deny", "message": "Blocked" },
196
+ { "toolName": "Bash", "inputPattern": "git push", "action": "ask" }
197
+ ],
198
+ "postToolUse": [
199
+ { "toolName": "Edit", "command": "bun run lint --fix $TOOL_INPUT" }
200
+ ]
201
+ }
202
+ }
203
+ ```
204
+
205
+ ---
206
+
207
+ ## Providers
208
+
209
+ | Provider | Model | Cost (in/out per 1M tokens) | Context |
210
+ |----------|-------|-----------------------------|---------|
211
+ | **xAI** (default) | grok-4-1-fast-reasoning | $0.20 / $0.50 | 2M |
212
+ | **Anthropic** | claude-sonnet-4-6 | $3.00 / $15.00 | 200K |
213
+ | **OpenAI** | gpt-4o | $2.50 / $10.00 | 128K |
214
+ | **DeepSeek** | deepseek-chat | $0.14 / $0.28 | 128K |
215
+ | **Groq** | llama-3.3-70b | $0.59 / $0.79 | 128K |
216
+ | **Ollama** (local) | any local model | Free | Model-dependent |
217
+
218
+ Auto-failover on rate limits. Model aliases: `grok-fast`, `grok-4`, `grok-3`, `sonnet`, `opus`, `llama`, `local`.
219
+
220
+ ---
221
+
222
+ ## KAIROS Mode
223
+
224
+ KAIROS is an autonomous agent mode with a heartbeat-driven loop. It detects terminal focus to adjust autonomy:
225
+
226
+ - **Focused** — collaborative: asks before significant changes
227
+ - **Unfocused** — full-auto: acts independently while you're away
228
+ - **Unknown** — balanced default
229
+
230
+ ```bash
231
+ ac
232
+ > /kairos "refactor the auth module and add tests"
233
+ > /kairos stop
234
+ ```
235
+
236
+ ---
237
+
238
+ ## Buddy System
239
+
240
+ Every user gets a deterministic ASCII pet companion based on a hash of their home directory. Eight species with rarity tiers, mood-based animations, equippable hats, and stats that grow with usage.
241
+
242
+ ```
243
+ ┌──────────────────────────────┐ c\ /c
244
+ │ What if we tried a different │ ( . . )
245
+ │ approach to the auth flow? │ ( _nn_ )
246
+ └──────────────────────┐ │ (______)
247
+ └───────┘ || ||
248
+ ```
249
+
250
+ Species: penguin, cat, ghost, owl, robot, dragon, axolotl (epic), capybara (legendary). Stats: debugging, patience, chaos, wisdom, snark.
251
+
252
+ ---
253
+
254
+ ## Development
255
+
256
+ ```bash
257
+ git clone https://github.com/ashlrai/ashlrcode.git
258
+ cd ashlrcode
259
+ bun install
260
+
261
+ bun run dev # watch mode
262
+ bun run start # run CLI
263
+ bun test # 335 tests, 666 assertions, ~10s
264
+ bunx tsc --noEmit # type check
265
+ bun run build # bundle to dist/
266
+ ```
267
+
268
+ ### Architecture
269
+
270
+ ```
271
+ src/ # 130 source files
272
+ ├── cli.ts # Entry point + fallback REPL
273
+ ├── repl.tsx # Ink-based terminal UI
274
+ ├── setup.ts # Initialization and wiring
275
+ ├── agent/ # Core agent loop, sub-agents, KAIROS, teams, dreams, IPC
276
+ ├── providers/ # xAI, Anthropic, router, retry, cost tracking
277
+ ├── tools/ # 42 tools (32 files)
278
+ ├── skills/ # Skill loader + registry
279
+ ├── mcp/ # MCP client, manager, OAuth
280
+ ├── planning/ # Plan mode + plan tools
281
+ ├── persistence/ # Sessions + memory
282
+ ├── config/ # Settings, hooks, permissions, features, sync, undercover
283
+ ├── state/ # File history (undo)
284
+ ├── ui/ # Ink components, buddy, speech bubbles, theme, effort
285
+ ├── autopilot/ # Scanner + work queue
286
+ ├── bridge/ # HTTP bridge server + client
287
+ ├── telemetry/ # Event logging
288
+ └── voice/ # Voice input via Whisper
289
+ ```
290
+
291
+ ---
292
+
293
+ ## License
294
+
295
+ MIT
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "ashlrcode",
3
+ "version": "1.0.0",
4
+ "description": "Multi-provider AI coding agent CLI — 42 tools, 34 commands, autonomous mode",
5
+ "module": "src/cli.ts",
6
+ "type": "module",
7
+ "private": false,
8
+ "license": "MIT",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/ashlrai/ashlrcode"
12
+ },
13
+ "keywords": ["ai", "coding", "agent", "cli", "xai", "grok", "claude", "multi-provider", "autonomous"],
14
+ "files": ["src/", "README.md", "LICENSE"],
15
+ "bin": {
16
+ "ashlrcode": "./src/cli.ts",
17
+ "ac": "./src/cli.ts"
18
+ },
19
+ "scripts": {
20
+ "dev": "bun run src/cli.ts",
21
+ "test": "bun test",
22
+ "build": "bun build src/cli.ts --compile --outfile dist/ac",
23
+ "build:all": "bun build src/cli.ts --compile --outfile dist/ac-$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m)",
24
+ "typecheck": "tsc --noEmit"
25
+ },
26
+ "devDependencies": {
27
+ "@types/bun": "latest",
28
+ "@types/react": "^19.2.14"
29
+ },
30
+ "peerDependencies": {
31
+ "typescript": "^5"
32
+ },
33
+ "dependencies": {
34
+ "@anthropic-ai/sdk": "^0.82.0",
35
+ "chalk": "^5.6.2",
36
+ "fast-glob": "^3.3.3",
37
+ "ink": "^6.8.0",
38
+ "ink-text-input": "^6.0.0",
39
+ "openai": "^6.33.0",
40
+ "react": "^19.2.4",
41
+ "react-devtools-core": "^7.0.1"
42
+ },
43
+ "optionalDependencies": {
44
+ "puppeteer": "^24.0.0"
45
+ }
46
+ }
@@ -0,0 +1,47 @@
1
+ import { test, expect, describe } from "bun:test";
2
+ import {
3
+ asSystemPrompt,
4
+ asSessionId,
5
+ asAgentId,
6
+ asToolName,
7
+ } from "../types/branded.ts";
8
+
9
+ describe("Branded Types", () => {
10
+ test("asSystemPrompt returns the string", () => {
11
+ const result = asSystemPrompt("You are a helpful assistant.");
12
+ expect(result).toBe("You are a helpful assistant.");
13
+ expect(typeof result).toBe("string");
14
+ });
15
+
16
+ test("asSessionId returns the string", () => {
17
+ const result = asSessionId("sess-abc-123");
18
+ expect(result).toBe("sess-abc-123");
19
+ expect(typeof result).toBe("string");
20
+ });
21
+
22
+ test("asAgentId returns the string", () => {
23
+ const result = asAgentId("agent-007");
24
+ expect(result).toBe("agent-007");
25
+ expect(typeof result).toBe("string");
26
+ });
27
+
28
+ test("asToolName returns the string", () => {
29
+ const result = asToolName("Bash");
30
+ expect(result).toBe("Bash");
31
+ expect(typeof result).toBe("string");
32
+ });
33
+
34
+ test("all branded type functions exist and are callable", () => {
35
+ expect(typeof asSystemPrompt).toBe("function");
36
+ expect(typeof asSessionId).toBe("function");
37
+ expect(typeof asAgentId).toBe("function");
38
+ expect(typeof asToolName).toBe("function");
39
+ });
40
+
41
+ test("branded values work with string operations", () => {
42
+ const prompt = asSystemPrompt("hello world");
43
+ expect(prompt.toUpperCase()).toBe("HELLO WORLD");
44
+ expect(prompt.length).toBe(11);
45
+ expect(prompt.includes("world")).toBe(true);
46
+ });
47
+ });
@@ -0,0 +1,163 @@
1
+ import { test, expect, describe } from "bun:test";
2
+ import { estimateTokens, needsCompaction, snipCompact } from "../agent/context.ts";
3
+ import type { Message } from "../providers/types.ts";
4
+
5
+ describe("estimateTokens", () => {
6
+ test("estimates tokens for string content", () => {
7
+ const messages: Message[] = [
8
+ { role: "user", content: "Hello world" }, // 11 chars => ceil(11/4) = 3
9
+ ];
10
+ expect(estimateTokens(messages)).toBe(3);
11
+ });
12
+
13
+ test("returns 0 for empty messages", () => {
14
+ expect(estimateTokens([])).toBe(0);
15
+ });
16
+
17
+ test("estimates tokens for text blocks", () => {
18
+ const messages: Message[] = [
19
+ {
20
+ role: "assistant",
21
+ content: [{ type: "text", text: "abcdefgh" }], // 8 chars => 2 tokens
22
+ },
23
+ ];
24
+ expect(estimateTokens(messages)).toBe(2);
25
+ });
26
+
27
+ test("estimates tokens for tool_use blocks", () => {
28
+ const messages: Message[] = [
29
+ {
30
+ role: "assistant",
31
+ content: [
32
+ { type: "tool_use", id: "1", name: "Bash", input: { command: "ls" } },
33
+ ],
34
+ },
35
+ ];
36
+ // name "Bash" (4) + JSON.stringify({command: "ls"}) (16) = 20 => ceil(20/4) = 5
37
+ const tokens = estimateTokens(messages);
38
+ expect(tokens).toBeGreaterThan(0);
39
+ });
40
+
41
+ test("estimates tokens for tool_result blocks", () => {
42
+ const messages: Message[] = [
43
+ {
44
+ role: "tool",
45
+ content: [
46
+ { type: "tool_result", tool_use_id: "1", content: "file1.ts\nfile2.ts" },
47
+ ],
48
+ },
49
+ ];
50
+ const tokens = estimateTokens(messages);
51
+ expect(tokens).toBeGreaterThan(0);
52
+ });
53
+
54
+ test("sums across multiple messages", () => {
55
+ const messages: Message[] = [
56
+ { role: "user", content: "aaaa" }, // 4 chars => 1 token
57
+ { role: "assistant", content: "bbbbbbbb" }, // 8 chars => 2 tokens
58
+ ];
59
+ expect(estimateTokens(messages)).toBe(3);
60
+ });
61
+ });
62
+
63
+ describe("needsCompaction", () => {
64
+ test("returns false when well under limit", () => {
65
+ const messages: Message[] = [{ role: "user", content: "hi" }];
66
+ expect(needsCompaction(messages, 100)).toBe(false);
67
+ });
68
+
69
+ test("returns true when over limit", () => {
70
+ // Create a message that's clearly over the default limit
71
+ // Default: maxContextTokens=100000, reserveTokens=8192
72
+ // So threshold is 91808 tokens => 91808 * 4 = 367232 chars
73
+ const bigContent = "x".repeat(400_000);
74
+ const messages: Message[] = [{ role: "user", content: bigContent }];
75
+ expect(needsCompaction(messages, 0)).toBe(true);
76
+ });
77
+
78
+ test("respects custom config", () => {
79
+ // "hello world test" = 16 chars / 4 = 4 tokens. Limit 3, so should trigger.
80
+ const messages: Message[] = [{ role: "user", content: "hello world test" }];
81
+ expect(
82
+ needsCompaction(messages, 0, { maxContextTokens: 3, reserveTokens: 0 })
83
+ ).toBe(true);
84
+ });
85
+
86
+ test("accounts for system prompt tokens", () => {
87
+ const messages: Message[] = [{ role: "user", content: "hi" }];
88
+ // "hi" = 1 token. systemPromptTokens = 95000. Total = 95001.
89
+ // maxContext 100000 - reserve 8192 = 91808. 95001 > 91808 = true
90
+ expect(
91
+ needsCompaction(messages, 95_000, {
92
+ maxContextTokens: 100_000,
93
+ reserveTokens: 8192,
94
+ })
95
+ ).toBe(true);
96
+ });
97
+ });
98
+
99
+ describe("snipCompact", () => {
100
+ test("does not modify short tool results", () => {
101
+ const messages: Message[] = [
102
+ {
103
+ role: "tool",
104
+ content: [
105
+ { type: "tool_result", tool_use_id: "1", content: "short result" },
106
+ ],
107
+ },
108
+ ];
109
+ const result = snipCompact(messages);
110
+ expect((result[0]!.content as any)[0].content).toBe("short result");
111
+ });
112
+
113
+ test("truncates tool results longer than 2000 chars", () => {
114
+ const longContent = "a".repeat(3000);
115
+ const messages: Message[] = [
116
+ {
117
+ role: "tool",
118
+ content: [
119
+ { type: "tool_result", tool_use_id: "1", content: longContent },
120
+ ],
121
+ },
122
+ ];
123
+ const result = snipCompact(messages);
124
+ const content = (result[0]!.content as any)[0].content as string;
125
+ expect(content.length).toBeLessThan(longContent.length);
126
+ expect(content).toContain("[... truncated ...]");
127
+ });
128
+
129
+ test("preserves string content messages unchanged", () => {
130
+ const messages: Message[] = [{ role: "user", content: "hello" }];
131
+ const result = snipCompact(messages);
132
+ expect(result[0]!.content).toBe("hello");
133
+ });
134
+
135
+ test("preserves text blocks unchanged", () => {
136
+ const messages: Message[] = [
137
+ {
138
+ role: "assistant",
139
+ content: [{ type: "text", text: "some analysis" }],
140
+ },
141
+ ];
142
+ const result = snipCompact(messages);
143
+ expect((result[0]!.content as any)[0].text).toBe("some analysis");
144
+ });
145
+
146
+ test("truncated result keeps first 800 and last 800 chars", () => {
147
+ const longContent = "a".repeat(800) + "MIDDLE" + "b".repeat(800) + "c".repeat(1400);
148
+ const messages: Message[] = [
149
+ {
150
+ role: "tool",
151
+ content: [
152
+ { type: "tool_result", tool_use_id: "1", content: longContent },
153
+ ],
154
+ },
155
+ ];
156
+ const result = snipCompact(messages);
157
+ const content = (result[0]!.content as any)[0].content as string;
158
+ // Should start with 800 chars from the beginning
159
+ expect(content.startsWith("a".repeat(800))).toBe(true);
160
+ // Should end with last 800 chars from the original
161
+ expect(content.endsWith(longContent.slice(-800))).toBe(true);
162
+ });
163
+ });