loom-agent 1.2.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 (79) hide show
  1. package/.env.example +25 -0
  2. package/CHANGELOG.md +402 -0
  3. package/LICENSE +21 -0
  4. package/LOOM.md +235 -0
  5. package/README.md +433 -0
  6. package/bin/loom-tui.js +43 -0
  7. package/bin/loom.js +44 -0
  8. package/docs/acp.md +151 -0
  9. package/docs/web.md +205 -0
  10. package/package.json +97 -0
  11. package/scripts/acp-smoke.js +146 -0
  12. package/src/acp/acp-server.js +287 -0
  13. package/src/config/provider-cmd.js +37 -0
  14. package/src/config/settings.js +164 -0
  15. package/src/core/agents.js +361 -0
  16. package/src/core/background-tasks.js +103 -0
  17. package/src/core/cli.js +579 -0
  18. package/src/core/custom-commands.js +70 -0
  19. package/src/core/errors.js +29 -0
  20. package/src/core/events.js +24 -0
  21. package/src/core/file-diffs.js +282 -0
  22. package/src/core/format.js +206 -0
  23. package/src/core/graph.js +257 -0
  24. package/src/core/hooks.js +82 -0
  25. package/src/core/lsp.js +385 -0
  26. package/src/core/memory.js +87 -0
  27. package/src/core/model-router.js +87 -0
  28. package/src/core/permissions.js +327 -0
  29. package/src/core/platform.js +33 -0
  30. package/src/core/plugin-cmd.js +380 -0
  31. package/src/core/restore.js +207 -0
  32. package/src/core/session-store.js +167 -0
  33. package/src/core/session.js +910 -0
  34. package/src/core/subagent-log.js +134 -0
  35. package/src/core/tokens.js +31 -0
  36. package/src/core/update.js +6 -0
  37. package/src/core/usage.js +166 -0
  38. package/src/index.js +41 -0
  39. package/src/mcp/mcp-client.js +201 -0
  40. package/src/mcp/mcp-manager.js +193 -0
  41. package/src/providers/anthropic.js +243 -0
  42. package/src/providers/google.js +29 -0
  43. package/src/providers/index.js +175 -0
  44. package/src/providers/local.js +27 -0
  45. package/src/providers/nvidia.js +85 -0
  46. package/src/providers/openai-compat.js +269 -0
  47. package/src/providers/openai.js +35 -0
  48. package/src/providers/openrouter.js +43 -0
  49. package/src/providers/registry.js +196 -0
  50. package/src/providers/tokenrouter.js +19 -0
  51. package/src/skills/skill-matcher.js +133 -0
  52. package/src/skills/skills-manager.js +213 -0
  53. package/src/tools/index.js +543 -0
  54. package/src/tui/App.tsx +1578 -0
  55. package/src/tui/components/BreadcrumbBar.tsx +34 -0
  56. package/src/tui/components/ChatArea.tsx +518 -0
  57. package/src/tui/components/InputBar.tsx +354 -0
  58. package/src/tui/components/MdText.tsx +105 -0
  59. package/src/tui/components/Modals.tsx +851 -0
  60. package/src/tui/components/PermissionPopup.tsx +264 -0
  61. package/src/tui/components/Sidebar.tsx +182 -0
  62. package/src/tui/components/SplashScreen.tsx +51 -0
  63. package/src/tui/components/SubagentPanel.tsx +217 -0
  64. package/src/tui/components/ToastOverlay.tsx +34 -0
  65. package/src/tui/keybinds.ts +318 -0
  66. package/src/tui/mcp-presets.ts +189 -0
  67. package/src/tui/md-render.ts +228 -0
  68. package/src/tui/store.ts +714 -0
  69. package/src/tui/suite-home.ts +20 -0
  70. package/src/tui/theme.ts +313 -0
  71. package/src/tui/themes.generated.ts +968 -0
  72. package/src/tui/tool-display.ts +176 -0
  73. package/src/tui/toolname.ts +60 -0
  74. package/src/tui/tui-config.ts +28 -0
  75. package/src/tui-open.tsx +51 -0
  76. package/src/web/attach.js +242 -0
  77. package/src/web/graph-view.html +262 -0
  78. package/src/web/index.html +824 -0
  79. package/src/web/web-server.js +470 -0
package/LOOM.md ADDED
@@ -0,0 +1,235 @@
1
+ # LOOM.md
2
+
3
+ ## Project Overview
4
+ Loom Code is an AI-powered coding agent for the terminal — multi-provider support (Anthropic, OpenAI, NVIDIA, Google, OpenRouter, Local/Ollama) and a redesigned OpenTUI (SolidJS) terminal interface.
5
+
6
+ ## Build / Run Commands
7
+ - `npm install` — install node dependencies (basic REPL)
8
+ - `bun install` — install all deps including OpenTUI native modules
9
+ - `node src/index.js` — run basic line-mode REPL
10
+ - `bun run src/tui-open.tsx` — launch the new OpenTUI TUI for this project
11
+ - `bun src/tui-open.tsx -p "query"` — one-shot/headless mode
12
+ - `npm run tui` — alias for `bun run src/tui-open.tsx`
13
+ - `npm start` — alias for `node src/index.js`
14
+ - `bun build src/tui-open.tsx --outdir=dist --target=bun` — bundle the TUI
15
+
16
+ ## Test Commands
17
+ - Manual TUI render test: `bun run src/tui-open.tsx` (visual check)
18
+ - Interactive render test: `bun run src/tui/test-interactive.tsx` — verifies splash, slash popup, filtering, Enter execute, modals (asserts included)
19
+ - Headless smoke (no TTY): pipe into `bun src/tui-open.tsx -p "hi"` — requires a valid provider API key
20
+ - No unit test framework configured yet; add `bun test` tests under `src/**/*.test.tsx` using `testRender` from `@opentui/solid`
21
+
22
+ ## Code Style
23
+ - CommonJS (`.js`) for legacy core (session, providers, tools, mcp, config, cli)
24
+ - ES modules + TypeScript (`.ts`, `.tsx`) for the OpenTUI layer — `src/tui/**`, `src/tui-open.tsx`
25
+ - SolidJS signals for state; `@opentui/solid` hooks (`useKeyboard`, `useRenderer`, `useTerminalDimensions`)
26
+ - OpenTUI JSX intrinsics use kebab/underscore names per reconciliation rules:
27
+ - `<text>`, `<box>`, `<scrollbox>` — layout/display
28
+ - `<input>`, `<textarea>`, `<select>`, `<tab_select>` — inputs (underscore for `tab_select`)
29
+ - Text styling uses **nested** modifier tags: `<strong>`, `<em>`, `<u>`, `<span fg="...">` — NOT props
30
+ - Colors via palette helpers in `src/tui/theme.ts` (hex strings)
31
+
32
+ ## Architecture — TUI (new, OpenTUI/SolidJS)
33
+ ```
34
+ src/tui-open.tsx Entry point (bun) — render(<App/>)
35
+ src/tui/
36
+ App.tsx Root component — layout, keyboard routing, session submit loop, slash commands (command errors surface as toasts, not chat messages)
37
+ store.ts SolidJS signals: messages, input, thinking, modal, suggestions, provider state, todos, pets
38
+ theme.ts Color palette (loom dark/light), LOOM banner ASCII, VERSION
39
+ components/
40
+ SplashScreen.tsx Full-screen banner + input prompt (shown when no messages)
41
+ ChatArea.tsx Message list (user/assistant/tool/system bubbles, thinking indicator) — assistant bubbles render on a slightly darker patch; while the model edits files the view splits: chat left, diff panel right
42
+ Sidebar.tsx Right-hand panel: provider, model, tabs (Info/Todos/Files), companion pet
43
+ InputBar.tsx Bottom input with slash autocomplete (mouse click/wheel) + status line
44
+ Modals.tsx Provider picker, model picker, key input, base-URL editor, confirm dialogs (mouse click/wheel)
45
+ companion/
46
+ openpets.ts Optional OpenPets desktop app bridge (IPC via @open-pets/client)
47
+ (Companion.tsx lives in components/ — animated pet with blink frames, click reactions, speech bubbles)
48
+ ```
49
+
50
+ The old `src/tui.js` (ink) has been removed. `src/core/cli.js` now spawns `bun src/tui-open.tsx` when TTY + bun available, else falls back to the basic readline REPL.
51
+
52
+ ## Architecture — Core (unchanged)
53
+ ```
54
+ src/providers/ anthropic.js, openai.js, nvidia.js, google.js, openrouter.js, local.js
55
+ All "OpenAI-compat" providers delegate to providers/openai-compat.js
56
+ Base URLs resolved via config/settings.getBaseUrl(provider)
57
+ Env overrides: ANTHROPIC_BASE_URL, OPENAI_BASE_URL, NVIDIA_BASE_URL, GOOGLE_BASE_URL, OPENROUTER_BASE_URL
58
+
59
+ src/core/session.js Session class — agentic tool loop (up to 50 iterations)
60
+ src/core/model-router.js Budget router — free/cheap/best/auto level → per-turn model pick
61
+ src/core/usage.js Usage & billing ledger + monthly spend governor (budgetStatus / setMonthlyBudget)
62
+ src/core/events.js Sync event bus (turn lifecycle, model:switch, tool calls)
63
+ src/core/session-store.js Save/load conversations in ~/.loom/sessions
64
+ src/core/permissions.js bash/edit/write permission gating
65
+ src/core/plugin-cmd.js /skills, /mcp, /diff, /debug, /editor, /export, /sessions, /fork handlers
66
+ src/config/settings.js ~/.loom/config.json persistence, API key + base URL helpers
67
+ src/tools/index.js read/write/edit/bash/grep/glob/webfetch/todowrite + MCP passthrough
68
+ src/mcp/ MCP client + manager (stdio servers)
69
+ src/skills/ Skill install/remove/list from ~/.loom/skills
70
+ ```
71
+
72
+ ## Companion Pet System
73
+ - **5 built-in pets**: Cat, Robot, Fenrir (wolf), Luma (firefly), OpenPets (desktop sync)
74
+ - **Animation**: 700ms blink interval, idle/thinking/working/happy/sleep poses, random phrases
75
+ - **Interaction**: Click pet → celebrating mood + hearts counter + speech bubble
76
+ - **Event-driven moods**: `notifyPet({ mood: "working"|"success"|"error"|"celebrating" ... })` called from chat lifecycle
77
+ - **OpenPets desktop sync** (optional): `@open-pets/client` IPC bridge at `src/tui/companion/openpets.ts`
78
+ - Reactions: idle, thinking, working, editing, running, testing, waiting, waving, success, error, celebrating
79
+ - Auto-discovers desktop app via `%APPDATA%\OpenPets\runtime\ipc.json` (Win) or `~/.config/OpenPets/runtime/ipc.json` (Linux)
80
+ - Graceful no-op when desktop app not running
81
+ - `/companion` opens picker modal; selection persisted to `~/.loom/tui.json`
82
+ - Settings `[e]` toggles OpenPets sync on/off
83
+
84
+ ## Slash Commands (39 total)
85
+ | Command | Args | Description |
86
+ |---------|------|-------------|
87
+ | `/help` | — | Show this help dialog |
88
+ | `/build` | — | Build mode — full agent tools (all tools, executes changes) |
89
+ | `/plan` | — | Plan mode — read-only analysis; model gets only read/glob/grep/webfetch/todowrite, mutations blocked by an execute guard; ends with "Plan complete — Tab to Build, send 'go'" |
90
+ | `/chat` | — | Chat mode — conversation only, zero tools |
91
+ | `/connect` | `[provider]` | Add/connect a provider (opens picker if no arg) |
92
+ | `/key` | — | Edit API key for current provider |
93
+ | `/baseurl` | `[provider] [url]` | Set provider base URL |
94
+ | `/model` | `[model-id]` | Pick active model (or open picker) |
95
+ | `/models` | — | List available models (grouped by provider) |
96
+ | `/providers` | — | List supported providers |
97
+ | `/status` | — | Show connection status (provider, model, key) |
98
+ | `/usage` | — | Show token usage and billing breakdown (session + lifetime + monthly budget) |
99
+ | `/budget` | `[level \| $]` | Budget routing — `free`/`cheap`/`best`/`auto`, or a dollar cap (`/budget 50`). Over-cap blocks paid turns (`/budget free` is the escape hatch) |
100
+ | `/new` | — | Start a new session |
101
+ | `/clear` | — | Clear the chat |
102
+ | `/compact` | — | Compact conversation (keep last 10) |
103
+ | `/restore` | — | Restore a snapshot point |
104
+ | `/undo` | — | Undo last exchange |
105
+ | `/redo` | — | Redo last undone exchange |
106
+ | `/reset` | — | Reset the session |
107
+ | `/settings` | — | Toggle details/thinking/sidebar/OpenPets |
108
+ | `/sessions` | — | Browse saved sessions |
109
+ | `/share` | — | Export the current session to JSON |
110
+ | `/export` | — | Export to markdown |
111
+ | `/thinking` | — | Toggle thinking visibility |
112
+ | `/details` | — | Toggle tool detail visibility |
113
+ | `/theme` | — | Theme picker (live switch + persist) |
114
+ | `/permissions` | — | Saved permission rules (view / reset) |
115
+ | `/editor` | — | Open external editor (LOOM.md) |
116
+ | `/diff` | — | Show git diff |
117
+ | `/init` | — | Create LOOM.md |
118
+ | `/memory` | — | Show memory file locations |
119
+ | `/doctor` | — | Run diagnostics |
120
+ | `/skills` | `install <dir\|git> \| remove <name>` | Manage skills (`/skills install <path-or-url> [name]`, `/skills remove <name>`, `/skills help`) |
121
+ | `/mcp` | `add <name> <cmd> \| remove \| toggle` | Manage MCP servers (`/mcp add <name> <command> [args...]`, `/mcp remove <name>`, `/mcp toggle <name>`, `/mcp help`) |
122
+ | `/debug` | — | Show debug info |
123
+ | `/fork` | — | Fork conversation |
124
+ | `/companion` | — | Change your companion pet |
125
+ | `/exit` | — | Quit Loom Code |
126
+
127
+ ### Leader Key (Ctrl+X)
128
+ | Key | Slash Command |
129
+ |-----|---------------|
130
+ | `c` | `/compact` |
131
+ | `e` | `/editor` |
132
+ | `q` | `/exit` |
133
+ | `x` | `/export` |
134
+ | `h` | `/help` |
135
+ | `m` | `/models` |
136
+ | `n` | `/new` |
137
+ | `r` | `/redo` |
138
+ | `l` | `/sessions` |
139
+ | `u` | `/undo` |
140
+ | `s` | `/settings` |
141
+ | `t` | `/thinking` |
142
+ | `d` | `/details` |
143
+ | `b` | `/build` |
144
+ | `p` | `/plan` |
145
+
146
+ ### Autocomplete
147
+ - **Slash (`/`)** — live-filtered commands with descriptions, 10-row scrollable window over the full list, UP/DOWN/TAB/ENTER/ESC
148
+ - **At (`@`)** — fuzzy file search (project files, top 10)
149
+ - **Bang (`!`)** — shell presets (`!ls -la`, `!git status`, `!git diff`, `!pwd`)
150
+ - **Mouse** — click a suggestion row to select + execute it; scroll wheel scrolls through the full list and moves the selection (also in modals: provider/model/companion pickers + palette — model picker shows "showing N-M of X" range)
151
+
152
+ ### Keyboard Shortcuts
153
+ | Keys | Action |
154
+ |------|--------|
155
+ | `Enter` | Submit prompt / pick autocomplete |
156
+ | `Esc` | Interrupt thinking / close modal / clear input |
157
+ | `Ctrl+C` | Exit TUI (clean terminal restore) |
158
+ | `Ctrl+B` | Toggle sidebar |
159
+ | `Ctrl+P` | Command palette (type to filter) |
160
+ | `Ctrl+X` | Leader prefix (3s timeout) |
161
+ | `Ctrl+I` | Cycle sidebar tab (Info/Todos/Files) |
162
+ | `Tab` | Cycle autocomplete / cycle input mode (Build/Plan/Chat) |
163
+ | `Up/Down` | Navigate autocomplete / suggestion list |
164
+ | `Mouse wheel` | Scroll chat history (scrollbox) / move suggestion + modal selection |
165
+ | `Mouse click` | Run a suggestion or modal option (autocomplete, pickers, palette) |
166
+
167
+ ## Configuration Format
168
+ `~/.loom/config.json`:
169
+ ```json
170
+ {
171
+ "provider": "nvidia",
172
+ "model": { "nvidia": "deepseek-ai/deepseek-v4-flash", "anthropic": "claude-sonnet-4-20250514" },
173
+ "apiKeys": { "nvidia": "..." },
174
+ "baseUrls": { "nvidia": "https://integrate.api.nvidia.com/v1" },
175
+ "maxTokens": 8192,
176
+ "temperature": 0.7
177
+ }
178
+ ```
179
+
180
+ ## TUI State Persistence
181
+ `~/.loom/tui.json`:
182
+ ```json
183
+ {
184
+ "sidebarVisible": true,
185
+ "showToolDetails": false,
186
+ "showThinking": true,
187
+ "companion": "cat",
188
+ "openPetsSync": false,
189
+ "petEnabled": true
190
+ }
191
+ ```
192
+
193
+ ## Native Modules Note
194
+ `@opentui/core` ships per-platform native packages (`@opentui/core-win32-x64`, `-darwin-arm64`, etc.).
195
+ Install the one matching the host OS — npm/bun install will pull them automatically when flagged in
196
+ `optionalDependencies`. `bun build` may warn about missing *other* platforms; that is expected.
197
+
198
+ ## Notes for the Coding Agent
199
+ - Always use `bun run src/tui-open.tsx` to drive the UI; never edit the old `tui.js` (gone)
200
+ - When adding a new slash command:
201
+ 1. Register in `src/tui/store.ts` (`SLASH_LIST`)
202
+ 2. Add a `case "cmdname":` block in `src/tui/App.tsx` (`processSlash`)
203
+ 3. If it opens a picker, add a modal handler in `src/tui/components/Modals.tsx`
204
+ - To add a provider: drop a file in `src/providers/`, register in `providers/index.js` (`PROVIDERS`, `PROVIDER_ORDER`, `PROVIDER_LABELS`), add its base URL default to `config/settings.js`
205
+ - Run deterministic checks with `bun -e "..."` or `bun build` before pushing UI changes
206
+ - **Critical**: All signal reads MUST be inside JSX expressions (`{signal()}`) or `<Show when={signal()}>` — top-level `const s = signal()` captures once and never updates
207
+
208
+ ## SolidJS Reactivity Pattern (MUST FOLLOW)
209
+ ```tsx
210
+ // WRONG — captures once, never updates
211
+ const s = suggestions();
212
+
213
+ // CORRECT — read inside JSX
214
+ <Show when={suggestions().length > 0 && autoKind() === "slash"}>
215
+ {suggestions().slice(0, 4).map(...)}
216
+ </Show>
217
+ ```
218
+
219
+ This pattern applies to ALL components: InputBar, Companion, Sidebar, BreadcrumbBar, ChatArea, Modals.
220
+
221
+ ### Passing props to children
222
+ Pass **accessor functions**, not resolved values, for reactive props. Component bodies
223
+ run once (untracked), so `const msgs = props.messages` in a child body captures a stale value.
224
+
225
+ ```tsx
226
+ // WRONG — child body reads props.messages once, never updates
227
+ <ChatArea messages={messages()} />
228
+
229
+ // CORRECT — child calls the accessor inside JSX
230
+ <ChatArea messages={messages} />
231
+ // in ChatArea: const visible = () => (props.messages?.() || []).slice(-40); ... {visible().map(...)}
232
+ ```
233
+
234
+ Boolean/static props may stay as values (`show={sidebarVisible()}` is fine when read inside
235
+ `<Show when={...}>` or another JSX expression).
package/README.md ADDED
@@ -0,0 +1,433 @@
1
+ # Loom Code
2
+
3
+ [![npm version](https://img.shields.io/npm/v/loom-code?style=flat&color=blue)](https://npmjs.com/loom-code)
4
+ [![License: MIT](https://img.shields.io/npm/l/loom-code?style=flat&color=green)](LICENSE)
5
+ [![Platform: Win/Mac/Linux](https://img.shields.io/badge/terminal-Win%20%7C%20Mac%20%7C%20Linux-orange)](#)
6
+
7
+ An AI-powered coding agent for the terminal with multi-provider support and a full terminal UI.
8
+
9
+ ## Features
10
+
11
+ - Full OpenTUI interface with slash commands, autocomplete, and ESC-to-interrupt
12
+ - Build / Plan / Chat modes
13
+ - OpenCode-style agents — primary agents (`build`/`plan`/`chat`) plus delegating subagents (`explore`, `scout`, `general`) invoked automatically via the `task` tool or manually via `@agent` mentions
14
+ - LOOM.md project memory file
15
+ - Multi-provider: Anthropic, OpenAI, NVIDIA, Google Gemini, OpenRouter, Local (Ollama)
16
+ - Built-in tools: read, write, edit, bash, grep, glob, webfetch, todowrite
17
+ - Agentic tool loop (multiple tool calls per turn)
18
+ - MCP (Model Context Protocol) server integration
19
+ - Drag-to-copy selections, paste support
20
+ - Session memory, /undo, /redo, /compact, /reset, /fork
21
+ - One-shot print mode: `loom -p "query"`
22
+ - Editor integration via the Agent Client Protocol (ACP): `loom acp` (JSON-RPC over stdio) so Zed, JetBrains, and Neovim plugins can drive Loom as their coding agent
23
+ - Browser interface via `loom web` — a zero-dependency Node HTTP server (chat in the browser, password auth, CORS) plus `loom attach` to share the same server from the terminal; optional mDNS advertising as `loom.local` via [bonjour-service](https://www.npmjs.com/package/bonjour-service)
24
+ - Usage & billing tracking per session + lifetime
25
+
26
+ ## Prerequisites
27
+
28
+ - **[bun](https://bun.sh) >= 1.0** (required for the full TUI). Without bun, only the line-mode REPL runs.
29
+ - **Node.js >= 18** (for non-TUI mode and package scripts)
30
+ - **An API key** from at least one provider:
31
+
32
+ | Provider | Sign-up | Key type |
33
+ |----------|---------|----------|
34
+ | Anthropic | [console.anthropic.com](https://console.anthropic.com) | Claude API key |
35
+ | OpenAI | [platform.openai.com](https://platform.openai.com) | API key |
36
+ | NVIDIA | [build.nvidia.com](https://build.nvidia.com) | NIM API key (free tier available) |
37
+ | Google | [aistudio.google.com](https://aistudio.google.com) | Gemini API key (free tier available) |
38
+ | OpenRouter | [openrouter.ai](https://openrouter.ai) | OpenRouter API key |
39
+ | Local | Install [Ollama](https://ollama.com) | No key needed (runs locally) |
40
+
41
+ Install from npm (global — installs the `loom` command):
42
+ ```bash
43
+ npm install -g loom-code
44
+ ```
45
+
46
+ Or run from source:
47
+ ```bash
48
+ npm install -g .
49
+ # or
50
+ npm link
51
+ ```
52
+
53
+ ## Setup
54
+
55
+ 1. **Install bun** (for the full TUI):
56
+ ```bash
57
+ # macOS / Linux
58
+ curl -fsSL https://bun.sh/install | bash
59
+
60
+ # Windows
61
+ powershell -c "irm bun.sh/install.ps1 | iex"
62
+ ```
63
+
64
+ 2. **Configure API keys** — pick one method:
65
+ ```bash
66
+ # a) Environment variables (.env)
67
+ cp .env.example .env
68
+ # Edit .env with your key:
69
+ # ANTHROPIC_API_KEY=sk-ant-...
70
+ # NVIDIA_API_KEY=nvapi-...
71
+ # OPENAI_API_KEY=sk-...
72
+
73
+ # b) Interactive setup in the TUI
74
+ loom
75
+ > /connect nvidia
76
+ ```
77
+
78
+ 3. **Initialize project memory** (optional):
79
+ ```bash
80
+ loom
81
+ > /init
82
+ ```
83
+ Creates `LOOM.md` with a project-specific template.
84
+
85
+ ## Usage
86
+
87
+ ```bash
88
+ loom # interactive TUI session
89
+ loom "explain this project" # start with initial prompt
90
+ loom -p "list files in src/" # one-shot, print result, exit
91
+ cat logs.txt | loom -p "explain" # analyze piped content
92
+ loom --version
93
+ loom --help
94
+ loom --basic # skip TUI, use line-mode REPL
95
+ loom acp # ACP subprocess mode for editor integrations
96
+ loom web # browser interface (HTTP server, opens browser)
97
+ loom attach http://localhost:4096 # attach the terminal to a running loom web
98
+ ```
99
+
100
+ ## Web
101
+
102
+ Loom runs in your browser with `loom web` — a zero-dependency HTTP server that
103
+ serves a single-page UI and drives the same core session loop as the TUI. Same
104
+ providers/models, tools, MCP servers, and saved sessions; chat streams live in
105
+ the browser, and a shared terminal client can attach to the same server.
106
+
107
+ ```bash
108
+ loom web # 127.0.0.1, random port, opens the browser
109
+ loom web --port 4096 # fixed port
110
+ loom web --hostname 0.0.0.0 # reachable on the LAN
111
+ loom web --mdns # advertise as loom.local (implies 0.0.0.0)
112
+ loom web --mdns-domain proj.local # custom mDNS domain
113
+ loom web --cors https://example.com
114
+ LOOM_SERVER_PASSWORD=secret loom web # password-protect (user: LOOM_SERVER_USERNAME, default "loom")
115
+
116
+ # Attach the terminal (shares the server's sessions/state):
117
+ loom attach http://localhost:4096
118
+ ```
119
+
120
+ Config-file equivalent (`~/.loom/config.json`): `{ "server": { "port": 4096,
121
+ "hostname": "0.0.0.0", "mdns": true, "cors": ["https://example.com"] } }` — CLI
122
+ flags take precedence. Full flags, the JSON API, and `loom attach` options are
123
+ documented in **[docs/web.md](docs/web.md)**.
124
+
125
+ ## Editor integration (ACP)
126
+
127
+ Loom implements the [Agent Client Protocol (ACP)](https://agentclientprotocol.com)
128
+ as `loom acp` — a JSON-RPC-over-stdio subprocess server that any ACP-compatible
129
+ editor can launch, the same mechanism opencode uses with Zed. Zed, JetBrains,
130
+ and Neovim (Avante.nvim / CodeCompanion.nvim) all support ACP agents; you only
131
+ configure the agent command `loom acp`, then chat from the editor while Loom
132
+ runs real tools in your repo.
133
+
134
+ Zed (add to `~/.config/zed/settings.json`, then Command Palette → `agent: new thread`):
135
+
136
+ ```json
137
+ {
138
+ "agent_servers": {
139
+ "Loom Code": { "type": "custom", "command": "loom", "args": ["acp"], "env": {} }
140
+ }
141
+ }
142
+ ```
143
+
144
+ Full transport spec, JetBrains/Neovim configs, the protocol walkthrough, and a
145
+ self-test client (`node scripts/acp-smoke.js`) live in **[docs/acp.md](docs/acp.md)**.
146
+
147
+ ## Slash Commands (35 total)
148
+
149
+ | Command | Args | Description |
150
+ |---------|------|-------------|
151
+ | `/help` | — | Show all commands |
152
+ | `/build` | — | Build mode — all tools |
153
+ | `/plan` | — | Plan mode — read-only analysis |
154
+ | `/chat` | — | Chat mode — no tools |
155
+ | `/agents` | — | List primary agents and subagents |
156
+ | `/connect` | `[provider]` | Add/connect a provider |
157
+ | `/key` | — | Edit API key for current provider |
158
+ | `/baseurl` | `[provider] [url]` | Set provider base URL |
159
+ | `/model` | `[model-id]` | Pick active model |
160
+ | `/models` | — | List available models |
161
+ | `/providers` | — | List supported providers |
162
+ | `/status` | — | Show connection status |
163
+ | `/usage` | — | Show token usage and billing |
164
+ | `/new` | — | Start a new session |
165
+ | `/clear` | — | Clear the chat |
166
+ | `/compact` | — | Compact conversation |
167
+ | `/undo` | — | Undo last exchange |
168
+ | `/redo` | — | Redo last undone exchange |
169
+ | `/reset` | — | Reset the session |
170
+ | `/settings` | — | Toggle details/sidebar/etc. |
171
+ | `/sessions` | — | Browse saved sessions |
172
+ | `/share` | — | Export current session to JSON |
173
+ | `/export` | — | Export to markdown |
174
+ | `/thinking` | — | Toggle thinking visibility |
175
+ | `/details` | — | Toggle tool detail visibility |
176
+ | `/init` | — | Create LOOM.md |
177
+ | `/memory` | — | Show memory files |
178
+ | `/doctor` | — | Run diagnostics |
179
+ | `/skills` | `install\|remove` | Manage skills |
180
+ | `/mcp` | `add\|remove\|toggle` | Manage MCP servers |
181
+ | `/debug` | — | Show debug info |
182
+ | `/fork` | — | Fork conversation |
183
+ | `/exit` | — | Quit Loom Code |
184
+
185
+ ## Keybindings
186
+
187
+ Every key is configurable from `~/.loom/tui.json` (`keybinds`, `leader`,
188
+ `leader_timeout`) — see [docs/keybinds.md](docs/keybinds.md) for the full
189
+ action list, syntax, and opencode-compatible aliases. The defaults:
190
+
191
+ | Key | Action |
192
+ |-----|--------|
193
+ | **ESC** | Interrupt current operation (aborts API requests) / close dialogs / clear the draft |
194
+ | **Ctrl+C** | Exit (copies a text selection first) |
195
+ | **Ctrl+B** | Toggle sidebar (file browser) |
196
+ | **Ctrl+P** | Open command palette |
197
+ | **Ctrl+X** | Leader key — the next key runs a leader binding |
198
+ | **Ctrl+X** then **b / p** | Build mode / Plan mode |
199
+ | **Ctrl+X** then **n / l / x / c** | New session / sessions list / export / compact |
200
+ | **Ctrl+X** then **m / a / h / e** | Model picker / agents / help / editor |
201
+ | **Ctrl+X** then **q** | Quit |
202
+ | **Tab** | Next suggestion, or cycle mode (Build → Plan → Chat) |
203
+ | **Ctrl+A** | Select the whole draft (readline-style) |
204
+ | **Shift+Enter** | Insert a newline in the draft |
205
+
206
+ ## Agents
207
+
208
+ Loom Code agent architecture: the user talks to a
209
+ **primary agent** (picked by the active mode), and that primary can delegate
210
+ focused work to **subagents** — either **automatically**, by calling the `task`
211
+ tool when a subtask warrants it, or **manually**, when you prefix a message with
212
+ `@agent`.
213
+
214
+ ### Primaries (matched to your mode)
215
+
216
+ | Agent | Mode | Tools | Role |
217
+ |-------|------|-------|------|
218
+ | `build` | Build | all tools (`*`) | Full development work — editing, shell, anything. |
219
+ | `plan` | Plan | read-only + `task` | Analyze and produce an ordered plan. Never edits files or runs shell commands; delegates heavy investigation to subagents. |
220
+ | `chat` | Chat | none | Conversation only. |
221
+
222
+ ### Subagents (delegated to via the `task` tool or `@agent` mentions)
223
+
224
+ | Agent | Tools | Role |
225
+ |-------|-------|------|
226
+ | `explore` | read-only, minus `task` | Fast read-only codebase exploration: search symbols, read files, list files. Never modifies anything and never delegates (no recursion). |
227
+ | `scout` | `read`, `glob`, `grep`, `webfetch` | External research: fetch docs, check APIs and dependencies. Read-only. |
228
+ | `general` | all tools, minus `task` | General-purpose autonomous subagent for self-contained implementation tasks, bug fixes, and multi-step work. |
229
+
230
+ Every subagent is **read-only or sandboxed** and **cannot delegate further**
231
+ (subagents never get the `task` tool), so delegation always terminates.
232
+
233
+ ### Two ways to invoke a subagent
234
+
235
+ 1. **Automatic — the main agent calls `task` itself.** When a turn would
236
+ benefit from a focused subagent (e.g. a fast read-only sweep before editing),
237
+ the primary calls the `task` tool with an agent id and a prompt. Progress
238
+ streams into a dedicated panel in the chat:
239
+
240
+ ```
241
+ ┌ @explore finished · done ─────────────┐
242
+ │ │ grep · read │
243
+ │ child findings… │
244
+ └────────────────────────────────────────┘
245
+ ```
246
+
247
+ 2. **Manual — `@agent` mentions.** Prefix your message with `@agent` to force
248
+ the whole turn onto that subagent:
249
+
250
+ ```
251
+ @explore find the bug
252
+ @scout what's the latest Stripe API for refunds?
253
+ ```
254
+
255
+ The `@agent` prefix is stripped from the user bubble shown in chat, so only
256
+ your query renders. Type `@` to open an autocomplete of available subagents.
257
+
258
+ ### Listing, configuring, and extending agents
259
+
260
+ - **`/agents`** — prints the active registry (id, mode, tool set, model).
261
+ - **Custom subagents** — add to `~/.loom/config.json`:
262
+
263
+ ```json
264
+ {
265
+ "agents": {
266
+ "reviewer": {
267
+ "mode": "subagent",
268
+ "description": "Reads diffs and flags risky changes before commit.",
269
+ "tools": ["read", "glob", "grep", "diff"],
270
+ "prompt": "You are a cautious code reviewer. Read the diff and list risk points.",
271
+ "model": "anthropic/claude-sonnet-4-20250514"
272
+ },
273
+ "explore": { "disable": true }
274
+ }
275
+ }
276
+ ```
277
+
278
+ Custom `mode: "subagent"` entries need a `description`; built-ins can be
279
+ disabled with `"disable": true`. Tool patterns use last-match-wins semantics:
280
+ `["*"]` (all), `["read","glob"]` (only those), `["*","!task"]` (all except
281
+ delegation), `["mcp__*"]` (wildcards).
282
+
283
+ ## Troubleshooting
284
+
285
+ ### "bun not found — full TUI requires bun"
286
+ Install bun from [bun.sh](https://bun.sh). If bun is already installed but not found, add `~/.bun/bin` to your `PATH`.
287
+
288
+ ### "API key is invalid or expired"
289
+ Run `/connect <provider>` in the TUI and paste a new key. For environment variables, check your `.env` file.
290
+
291
+ ### "403 Forbidden: the API key is not authorized"
292
+ Your key doesn't have access to the selected model. Some providers (especially NVIDIA NIM and OpenRouter) require accepting model terms on their website first.
293
+
294
+ ### "402 quota exceeded"
295
+ Your API billing tier or rate limit has been reached. Upgrade your plan or switch to a different provider.
296
+
297
+ ### TUI is slow or flickering
298
+ Try launching with `--basic` to use the line-mode REPL instead, which is lighter.
299
+
300
+ ## Project Structure
301
+
302
+ ```
303
+ LoomCode/
304
+ ├── bin/
305
+ │ ├── loom.js # CLI entry point
306
+ │ ├── loomcode.js # Alternative binary name
307
+ │ └── loom-tui.js # TUI launcher
308
+ ├── src/
309
+ │ ├── index.js # Bootstraps CLI
310
+ │ ├── core/
311
+ │ │ ├── cli.js # Interactive REPL + slash commands
312
+ │ │ ├── session.js # Conversation + agent tool loop
313
+ │ │ ├── agents.js # agent registry + subagent runner
314
+ │ │ ├── permissions.js # Command permission checks
315
+ │ │ ├── platform.js # OS/platform detection
316
+ │ │ ├── session-store.js # Persisted sessions
317
+ │ │ ├── restore.js # Snapshot/restore project file tree
318
+ │ │ ├── usage.js # Token/cost tracking
319
+ │ │ └── plugin-cmd.js # Subcommand backend
320
+ │ ├── providers/
321
+ │ │ ├── index.js # ProviderRouter dispatch
322
+ │ │ ├── openai-compat.js # OpenAI-compatible provider base
323
+ │ │ ├── anthropic.js # Anthropic Claude connector
324
+ │ │ ├── openai.js # OpenAI GPT connector
325
+ │ │ ├── nvidia.js # NVIDIA NIM connector
326
+ │ │ ├── google.js # Google Gemini connector
327
+ │ │ ├── openrouter.js # OpenRouter connector
328
+ │ │ ├── local.js # Local (Ollama) connector
329
+ │ │ └── custom.js # Custom provider host
330
+ │ ├── tools/
331
+ │ │ └── index.js # read/write/edit/bash/grep/glob/webfetch/todowrite/task
332
+ │ ├── config/
333
+ │ │ ├── settings.js # ~/.loom/config.json persistence
334
+ │ │ └── provider-cmd.js # /connect command logic
335
+ │ └── tui/
336
+ │ ├── App.tsx # OpenTUI root component
337
+ │ ├── store.ts # SolidJS reactive store
338
+ │ ├── theme.ts # Theme / palette
339
+ │ ├── components/
340
+ │ │ ├── InputBar.tsx # Chat input + autocomplete
341
+ │ │ ├── ChatArea.tsx # Message list
342
+ │ │ ├── BreadcrumbBar.tsx # Mode + provider bar
343
+ │ │ ├── Modals.tsx # Settings pickers
344
+ │ │ └── Sidebar.tsx # File sidebar
345
+ ├── package.json
346
+ ├── LOOM.md # Developer reference
347
+ └── .gitignore
348
+ ```
349
+
350
+ ## Configuration
351
+
352
+ Config is stored at `~/.loom/config.json` (permissions: 0600). Includes:
353
+ - `provider` — default LLM provider
354
+ - `model` — per-provider model IDs (editable in source or via `/model`)
355
+ - `apiKeys` — API keys from `/connect` or manual edit
356
+ - `baseUrls` — custom provider endpoints
357
+ - `maxTokens`, `temperature` — model settings
358
+ - `permission` — OpenCode-style permission tree (see below)
359
+ - `permissionRules` — rules saved from the permission popup ("Always allow"/"Never")
360
+
361
+ API keys can also be set via environment variables: `.env` or `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `NVIDIA_API_KEY`, `GOOGLE_API_KEY`, `OPENROUTER_API_KEY` etc.
362
+
363
+ ## Permissions
364
+
365
+ Every tool call resolves through an OpenCode-style permission tree, so you can
366
+ allow or block specific tools, commands, and file patterns without retyping a
367
+ prompt each time. The last matching rule wins; `*` matches any run of
368
+ characters and `?` a single one, and `~`/`$HOME` are expanded in paths.
369
+
370
+ ```jsonc
371
+ {
372
+ "permission": {
373
+ "bash": {
374
+ "*": "allow", // allow shell commands...
375
+ "git push --force": "deny" // ...except destructive ones
376
+ },
377
+ "edit": { "*": "ask", "src/**": "allow" },
378
+ "read": {
379
+ "*": "allow",
380
+ "*.env": "deny", // secrets stay off-limits by default
381
+ "*.env.example": "allow"
382
+ },
383
+ "external_directory": "ask" // reads/writes outside the project dir
384
+ }
385
+ }
386
+ ```
387
+
388
+ Available permission keys: `read`, `edit` (covers edit/write), `glob`, `grep`,
389
+ `bash`, `task`, `skill`, `lsp`, `question`, `webfetch`, `websearch`,
390
+ `external_directory` (paths outside the working directory), and `doom_loop`
391
+ (three identical tool calls in a row). Most tools default to `allow`;
392
+ `edit`/`bash`/`task`/`skill`/`external_directory`/`doom_loop` default to `ask`,
393
+ and `read` denies `*.env`/`*.env.*` files (except `*.env.example`).
394
+
395
+ When the model asks for permission, the TUI popup offers Allow, Always allow,
396
+ Deny, or a typed answer. "Always allow"/"Never" persist a rule to
397
+ `permissionRules`. Run `loom --auto` (or `/permissions auto` in the TUI, or
398
+ Ctrl+P → the palette) to auto-approve `ask` results — explicit `deny` rules
399
+ still block. A muted `auto` indicator appears in the status row while enabled.
400
+
401
+ Per-agent overrides live in the agent's own config
402
+ (`agent.<id>.permission`, same shape, applied on top of the global tree):
403
+
404
+ ```jsonc
405
+ {
406
+ "agent": {
407
+ "explore": { "mode": "subagent", "permission": { "edit": "deny" } }
408
+ }
409
+ }
410
+ ```
411
+
412
+ ## Adding a New Provider
413
+
414
+ Register in `src/providers/<name>.js` exporting `{ chat, stream, models }`:
415
+
416
+ ```js
417
+ async function chat(messages, options) {
418
+ // call API, return { content: '...', toolCalls: [{...}], usage: {{...}} }
419
+ }
420
+ async function stream(messages, options, onDelta) {
421
+ // stream response, return { content, toolCalls, usage }
422
+ }
423
+ const models = [
424
+ { id: 'my-model', name: 'My Model', provider: 'myname', context: 128000, priceIn: 0.50, priceOut: 2.00 }
425
+ ];
426
+ module.exports = { chat, stream, models };
427
+ ```
428
+
429
+ Then add it in `src/providers/index.js` under `PROVIDERS`.
430
+
431
+ ## License
432
+
433
+ MIT