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.
- package/LICENSE +21 -0
- package/README.md +295 -0
- package/package.json +46 -0
- package/src/__tests__/branded-types.test.ts +47 -0
- package/src/__tests__/context.test.ts +163 -0
- package/src/__tests__/cost-tracker.test.ts +274 -0
- package/src/__tests__/cron.test.ts +197 -0
- package/src/__tests__/dream.test.ts +204 -0
- package/src/__tests__/error-handler.test.ts +192 -0
- package/src/__tests__/features.test.ts +69 -0
- package/src/__tests__/file-history.test.ts +177 -0
- package/src/__tests__/hooks.test.ts +145 -0
- package/src/__tests__/keybindings.test.ts +159 -0
- package/src/__tests__/model-patches.test.ts +82 -0
- package/src/__tests__/permissions-rules.test.ts +121 -0
- package/src/__tests__/permissions.test.ts +108 -0
- package/src/__tests__/project-config.test.ts +63 -0
- package/src/__tests__/retry.test.ts +321 -0
- package/src/__tests__/router.test.ts +158 -0
- package/src/__tests__/session-compact.test.ts +191 -0
- package/src/__tests__/session.test.ts +145 -0
- package/src/__tests__/skill-registry.test.ts +130 -0
- package/src/__tests__/speculation.test.ts +196 -0
- package/src/__tests__/tasks-v2.test.ts +267 -0
- package/src/__tests__/telemetry.test.ts +149 -0
- package/src/__tests__/tool-executor.test.ts +141 -0
- package/src/__tests__/tool-registry.test.ts +166 -0
- package/src/__tests__/undercover.test.ts +93 -0
- package/src/__tests__/workflow.test.ts +195 -0
- package/src/agent/async-context.ts +64 -0
- package/src/agent/context.ts +245 -0
- package/src/agent/cron.ts +189 -0
- package/src/agent/dream.ts +165 -0
- package/src/agent/error-handler.ts +108 -0
- package/src/agent/ipc.ts +256 -0
- package/src/agent/kairos.ts +207 -0
- package/src/agent/loop.ts +314 -0
- package/src/agent/model-patches.ts +68 -0
- package/src/agent/speculation.ts +219 -0
- package/src/agent/sub-agent.ts +125 -0
- package/src/agent/system-prompt.ts +231 -0
- package/src/agent/team.ts +220 -0
- package/src/agent/tool-executor.ts +162 -0
- package/src/agent/workflow.ts +189 -0
- package/src/agent/worktree-manager.ts +86 -0
- package/src/autopilot/queue.ts +186 -0
- package/src/autopilot/scanner.ts +245 -0
- package/src/autopilot/types.ts +58 -0
- package/src/bridge/bridge-client.ts +57 -0
- package/src/bridge/bridge-server.ts +81 -0
- package/src/cli.ts +1120 -0
- package/src/config/features.ts +51 -0
- package/src/config/git.ts +137 -0
- package/src/config/hooks.ts +201 -0
- package/src/config/permissions.ts +251 -0
- package/src/config/project-config.ts +63 -0
- package/src/config/remote-settings.ts +163 -0
- package/src/config/settings-sync.ts +170 -0
- package/src/config/settings.ts +113 -0
- package/src/config/undercover.ts +76 -0
- package/src/config/upgrade-notice.ts +65 -0
- package/src/mcp/client.ts +197 -0
- package/src/mcp/manager.ts +125 -0
- package/src/mcp/oauth.ts +252 -0
- package/src/mcp/types.ts +61 -0
- package/src/persistence/memory.ts +129 -0
- package/src/persistence/session.ts +289 -0
- package/src/planning/plan-mode.ts +128 -0
- package/src/planning/plan-tools.ts +138 -0
- package/src/providers/anthropic.ts +177 -0
- package/src/providers/cost-tracker.ts +184 -0
- package/src/providers/retry.ts +264 -0
- package/src/providers/router.ts +159 -0
- package/src/providers/types.ts +79 -0
- package/src/providers/xai.ts +217 -0
- package/src/repl.tsx +1384 -0
- package/src/setup.ts +119 -0
- package/src/skills/loader.ts +78 -0
- package/src/skills/registry.ts +78 -0
- package/src/skills/types.ts +11 -0
- package/src/state/file-history.ts +264 -0
- package/src/telemetry/event-log.ts +116 -0
- package/src/tools/agent.ts +133 -0
- package/src/tools/ask-user.ts +229 -0
- package/src/tools/bash.ts +146 -0
- package/src/tools/config.ts +147 -0
- package/src/tools/diff.ts +137 -0
- package/src/tools/file-edit.ts +123 -0
- package/src/tools/file-read.ts +82 -0
- package/src/tools/file-write.ts +82 -0
- package/src/tools/glob.ts +76 -0
- package/src/tools/grep.ts +187 -0
- package/src/tools/ls.ts +77 -0
- package/src/tools/lsp.ts +375 -0
- package/src/tools/mcp-resources.ts +83 -0
- package/src/tools/mcp-tool.ts +47 -0
- package/src/tools/memory.ts +148 -0
- package/src/tools/notebook-edit.ts +133 -0
- package/src/tools/peers.ts +113 -0
- package/src/tools/powershell.ts +83 -0
- package/src/tools/registry.ts +114 -0
- package/src/tools/send-message.ts +75 -0
- package/src/tools/sleep.ts +50 -0
- package/src/tools/snip.ts +143 -0
- package/src/tools/tasks.ts +349 -0
- package/src/tools/team.ts +309 -0
- package/src/tools/todo-write.ts +93 -0
- package/src/tools/tool-search.ts +83 -0
- package/src/tools/types.ts +52 -0
- package/src/tools/web-browser.ts +263 -0
- package/src/tools/web-fetch.ts +118 -0
- package/src/tools/web-search.ts +107 -0
- package/src/tools/workflow.ts +188 -0
- package/src/tools/worktree.ts +143 -0
- package/src/types/branded.ts +22 -0
- package/src/ui/App.tsx +184 -0
- package/src/ui/BuddyPanel.tsx +52 -0
- package/src/ui/PermissionPrompt.tsx +29 -0
- package/src/ui/banner.ts +217 -0
- package/src/ui/buddy-ai.ts +108 -0
- package/src/ui/buddy.ts +466 -0
- package/src/ui/context-bar.ts +60 -0
- package/src/ui/effort.ts +65 -0
- package/src/ui/keybindings.ts +143 -0
- package/src/ui/markdown.ts +271 -0
- package/src/ui/message-renderer.ts +73 -0
- package/src/ui/mode.ts +80 -0
- package/src/ui/notifications.ts +57 -0
- package/src/ui/speech-bubble.ts +95 -0
- package/src/ui/spinner.ts +116 -0
- package/src/ui/theme.ts +98 -0
- package/src/version.ts +5 -0
- 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
|
+
[]()
|
|
6
|
+
[]()
|
|
7
|
+
[]()
|
|
8
|
+
[]()
|
|
9
|
+
[]()
|
|
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
|
+
});
|