loom-agent 1.2.33 → 1.2.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +118 -0
- package/README.md +14 -30
- package/bin/loom-bun.js +95 -67
- package/bin/loom-tui.js +21 -1
- package/bin/loom.js +0 -10
- package/package.json +14 -9
- package/scripts/postinstall.js +57 -5
- package/src/connectors/convex.js +356 -0
- package/src/connectors/deploy.js +390 -0
- package/src/connectors/index.js +23 -0
- package/src/connectors/supabase.js +279 -0
- package/src/core/agents.js +32 -2
- package/src/core/cli.js +56 -7
- package/src/core/permissions.js +12 -1
- package/src/core/plugin-cmd.js +1 -0
- package/src/core/session.js +87 -6
- package/src/mcp/mcp-client.js +42 -2
- package/src/skills/skills-manager.js +25 -2
- package/src/tools/browser.js +204 -0
- package/src/tools/index.js +116 -5
- package/src/tui/App.tsx +19 -4
- package/src/tui/components/BreadcrumbBar.tsx +10 -9
- package/src/tui/components/ChatArea.tsx +3 -3
- package/src/tui/components/InputBar.tsx +22 -11
- package/src/tui/components/MdText.tsx +2 -1
- package/src/tui/components/Modals.tsx +31 -17
- package/src/tui/components/PermissionPopup.tsx +15 -12
- package/src/tui/components/Sidebar.tsx +6 -2
- package/src/tui/components/SplashScreen.tsx +17 -24
- package/src/tui/components/SubagentPanel.tsx +1 -1
- package/src/tui/components/ToastOverlay.tsx +23 -12
- package/src/tui/store.ts +4 -2
- package/src/tui/theme.ts +45 -30
- package/src/tui/tool-display.ts +13 -2
- package/docs/web.md +0 -205
- package/src/web/attach.js +0 -242
- package/src/web/graph-view.html +0 -262
- package/src/web/index.html +0 -824
- package/src/web/web-server.js +0 -470
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,124 @@ All notable changes to **Loom Code** are documented here.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [Unreleased] — completion harness, specialist agents, headless browser, TUI refresh
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **Drive-to-completion harness (`src/core/session.js`).** When the model ends
|
|
11
|
+
a turn with todos still pending/in_progress (the "stops between tasks"
|
|
12
|
+
failure), the loop injects a continuation listing the unfinished items — up
|
|
13
|
+
to 5 nudges per turn, build mode only, only when the turn actually wrote
|
|
14
|
+
todos. Cancelled/completed items via todowrite stop the nudging cleanly.
|
|
15
|
+
Child subagent sessions inherit the same guard.
|
|
16
|
+
- **Transient provider-error retry.** Network blips / 5xx / stream resets now
|
|
17
|
+
retry the model call twice (side-effect free) instead of killing the turn
|
|
18
|
+
mid-task.
|
|
19
|
+
- **Specialist subagents (`src/core/agents.js`)**: `research` (multi-source
|
|
20
|
+
investigation, sourced report), `coder` (isolated implementation),
|
|
21
|
+
`reviewer` (severity-ranked code review), `tester` (reproduce → test →
|
|
22
|
+
green), `web` (headless-browser work). All non-delegating (`no task tool`).
|
|
23
|
+
- **Main orchestrator prompt.** The `build` primary agent now carries an
|
|
24
|
+
orchestrator role prompt (todo-first, delegate-to-specialists, verify,
|
|
25
|
+
finish). Primary-agent prompts are actually wired into the system prompt now
|
|
26
|
+
(`Session._primaryBlock()`) — previously `build`/`plan`/`chat` prompts were
|
|
27
|
+
registry-only and never reached the model.
|
|
28
|
+
- **`browser` tool (`src/tools/browser.js`).** Headless Chromium control via
|
|
29
|
+
Playwright: goto/click/fill/press/text/eval/links/screenshot/close. Engine:
|
|
30
|
+
bundled `playwright` if installed, else `playwright-core` + system
|
|
31
|
+
Chrome/Edge. The page singleton persists across calls. Permissions: passive
|
|
32
|
+
reads allow, interactive click/fill/press ask.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
- **TUI design overhaul.** The splash leads with the full LOOM CODE wordmark
|
|
36
|
+
in solid terracotta over a separator rule and a pill-style status
|
|
37
|
+
row; the default "Loom Dark" (and Light) palettes got deeper surface
|
|
38
|
+
stepping so panels read without borders. Every selection surface — command
|
|
39
|
+
palette, slash/file popups, select modals, permission popup — now uses a
|
|
40
|
+
tinted active row with a `▸` marker; the command palette finally renders
|
|
41
|
+
each command's description; modals gained a title divider; toasts carry a
|
|
42
|
+
kind-colored status dot (● ok / ✖ error / ◆ info) and clip long text so
|
|
43
|
+
they never cover the chat. System notices sit on a tinted surface, the
|
|
44
|
+
assistant footer is brand-colored, markdown headings get an h1/h2/h3 color
|
|
45
|
+
hierarchy, the input placeholder color-codes /commands @file !shell, and
|
|
46
|
+
hard-coded reds now follow the theme's error color. Still grid-line-free in
|
|
47
|
+
the main view — color and spacing do the work, no box frames.
|
|
48
|
+
- **Sidebar version now tracks `package.json`** (it was stuck at a hardcoded
|
|
49
|
+
v1.2.0 while the package moved on); splash and sidebar share one source.
|
|
50
|
+
- **`task` tool description now lists every built-in subagent with its specialty.**
|
|
51
|
+
- **Stop messages show the real state, not canned text.** Continuation nudges
|
|
52
|
+
now quote the model's ACTUAL last message plus the open todo list (instead
|
|
53
|
+
of one identical line every time); the empty-reply nudge reports the real
|
|
54
|
+
tool-call count; a build turn that ends with open todos returns them
|
|
55
|
+
(`resp.openTodos`) and the TUI appends "Stopped with N todo(s) still open: …"
|
|
56
|
+
with the actual items; the interrupt line reports kept chars + the
|
|
57
|
+
in-progress todo; the plan-complete line includes the planned step count;
|
|
58
|
+
"(no response)"/"(interrupted)" placeholders describe what happened.
|
|
59
|
+
|
|
60
|
+
## [1.2.36] — fix `loom web` / `loom acp` / `loom attach`
|
|
61
|
+
|
|
62
|
+
### Fixed
|
|
63
|
+
- **`loom web` launched the TUI instead of the browser server.** Since 1.2.34
|
|
64
|
+
the npm shims prefer `bin/loom-bun.js`, whose non-TUI branch routed every
|
|
65
|
+
subcommand into `src/core/cli.js`'s interactive `main()` — which has no
|
|
66
|
+
handler for `web`/`acp`/`attach`, so control fell through to the full-screen
|
|
67
|
+
app (reproduced: splash painted, no HTTP listener). `bin/loom-bun.js` now
|
|
68
|
+
routes those three subcommands directly to their own entry points, exactly
|
|
69
|
+
like `bin/loom.js` always did. Verified end-to-end on a global install:
|
|
70
|
+
`/api/health` → 200, UI served, banner prints.
|
|
71
|
+
|
|
72
|
+
## [1.2.35] — security hardening
|
|
73
|
+
|
|
74
|
+
### Fixed
|
|
75
|
+
- **webfetch SSRF guard (`src/tools/index.js`).** The fetch tool is driven by
|
|
76
|
+
the model, and prompt-injected page content could steer it at cloud
|
|
77
|
+
metadata (`169.254.169.254`), localhost services, or internal hosts.
|
|
78
|
+
webfetch now allows only `http:`/`https:` (no `file:`/`ftp:`/`data:`),
|
|
79
|
+
refuses loopback, RFC1918, CGNAT, link-local/metadata, IPv6 ULA/link-local
|
|
80
|
+
and IPv4-mapped targets, **resolves DNS before connecting** so
|
|
81
|
+
hostname→internal-IP tricks fail, follows redirects manually and re-checks
|
|
82
|
+
every hop, and fails closed on unresolvable hosts.
|
|
83
|
+
- **MCP servers no longer inherit the full shell environment**
|
|
84
|
+
(`src/mcp/mcp-client.js`). Every spawned MCP server used to receive all of
|
|
85
|
+
`process.env` — provider API keys, cloud tokens, cookies — handing them to
|
|
86
|
+
whatever npm package a config starts. Servers now get a minimal OS/runtime
|
|
87
|
+
baseline (PATH, ComSpec, TEMP/HOME, proxy vars) plus only what their own
|
|
88
|
+
config explicitly declares (`cfg.env`). Secrets are opt-in per server.
|
|
89
|
+
- **Skills install name traversal blocked (`src/skills/skills-manager.js`).**
|
|
90
|
+
A remote-provided install name like `../../escapee` was joined raw onto
|
|
91
|
+
`~/.loom/skills`. Names are now a strict allowlist
|
|
92
|
+
(`^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$`); traversal attempts error instead of
|
|
93
|
+
silently renaming or escaping the skills directory.
|
|
94
|
+
|
|
95
|
+
### Added
|
|
96
|
+
- **`loom web` session hardening (`src/web/web-server.js`).** Login tokens
|
|
97
|
+
previously lived until server restart; they now expire after **12 h**
|
|
98
|
+
(tunable via `LOOM_SERVER_TOKEN_TTL_MS`, cookie `Max-Age` kept in sync), a
|
|
99
|
+
lazy sweep drops expired tokens, and new **`POST /api/auth/logout`**
|
|
100
|
+
revokes the presented token immediately.
|
|
101
|
+
- **Security headers on every response:** `X-Content-Type-Options: nosniff`,
|
|
102
|
+
`X-Frame-Options: DENY`, `Referrer-Policy: no-referrer`; the browser UI
|
|
103
|
+
additionally ships a strict `Content-Security-Policy`
|
|
104
|
+
(`default-src 'none'; connect-src 'self'` — the page is self-contained).
|
|
105
|
+
- **LAN-exposure warning:** `loom web --hostname 0.0.0.0` (or `::`) without
|
|
106
|
+
`LOOM_SERVER_PASSWORD` now prints an explicit warning that anyone on the
|
|
107
|
+
network can run an agent and reach its tools/files.
|
|
108
|
+
- **New regression suite `src/tools/security.test.js`** plus five web-server
|
|
109
|
+
hardening tests (headers, CSP, token TTL expiry, Max-Age sync, logout).
|
|
110
|
+
|
|
111
|
+
## [1.2.34] — bundle bun with the npm install
|
|
112
|
+
|
|
113
|
+
### Added
|
|
114
|
+
- **`npm i -g loom-agent` now brings its own bun.** The `@oven/bun-<platform>`
|
|
115
|
+
binaries (pinned to bun 1.3.14, the version the project builds against) ship
|
|
116
|
+
as `optionalDependencies`; npm downloads only the one matching the user's
|
|
117
|
+
OS/arch, and no install scripts are involved (npm allow-scripts policies
|
|
118
|
+
cannot break it). The postinstall rewrites the `loom` shims to prefer the
|
|
119
|
+
bundled binary, then `bun` from PATH.
|
|
120
|
+
- **No-bun users are never stranded:** if neither the bundled binary nor a
|
|
121
|
+
PATH bun exists, the shim falls back to `node bin/loom.js` (the line-mode
|
|
122
|
+
REPL) with a hint — instead of dying with "'bun' is not recognized".
|
|
123
|
+
`findBun()` and the bin respawn paths check the bundled location first too.
|
|
124
|
+
|
|
7
125
|
## [1.2.33] — fix frozen splash on global installs + stable model picker
|
|
8
126
|
|
|
9
127
|
### Fixed
|
package/README.md
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
```
|
|
2
|
+
██╗ ██████╗ ██████╗ ███╗ ███╗ ██████╗ ██████╗ ██████╗ ███████╗
|
|
3
|
+
██║ ██╔═══██╗██╔═══██╗████╗ ████║ ██╔════╝██╔═══██╗██╔══██╗██╔════╝
|
|
4
|
+
██║ ██║ ██║██║ ██║██╔████╔██║ ██║ ██║ ██║██║ ██║█████╗
|
|
5
|
+
██║ ██║ ██║██║ ██║██║╚██╔╝██║ ██║ ██║ ██║██║ ██║██╔══╝
|
|
6
|
+
███████╗╚██████╔╝╚██████╔╝██║ ╚═╝ ██║ ╚██████╗██████╔╝██████╔╝███████╗
|
|
7
|
+
╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝╚═════╝ ╚═════╝ ╚══════╝
|
|
8
|
+
```
|
|
2
9
|
|
|
3
10
|
[](https://www.npmjs.com/package/loom-agent)
|
|
4
11
|
[](LICENSE)
|
|
5
12
|
[](#)
|
|
13
|
+
[](https://loomcluster.in/)
|
|
14
|
+
|
|
15
|
+

|
|
6
16
|
|
|
7
17
|
An AI-powered coding agent for the terminal with multi-provider support and a full terminal UI.
|
|
8
18
|
|
|
@@ -20,7 +30,8 @@ An AI-powered coding agent for the terminal with multi-provider support and a fu
|
|
|
20
30
|
- Session memory, /undo, /redo, /compact, /reset, /fork
|
|
21
31
|
- One-shot print mode: `loom -p "query"`
|
|
22
32
|
- 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
|
-
-
|
|
33
|
+
- OpenCode-style agent harness: main orchestrator agent delegates to specialist subagents (`research`, `coder`, `reviewer`, `tester`, `web`, `explore`, `scout`) via the `task` tool, and a drive-to-completion loop keeps working while todos are still open
|
|
34
|
+
- Headless browser tool (`browser`) — navigate, click, fill, read the DOM, and screenshot real pages from the `web` agent
|
|
24
35
|
- Usage & billing tracking per session + lifetime
|
|
25
36
|
|
|
26
37
|
## Prerequisites
|
|
@@ -40,7 +51,7 @@ An AI-powered coding agent for the terminal with multi-provider support and a fu
|
|
|
40
51
|
|
|
41
52
|
Install from npm (global — installs the `loom` command):
|
|
42
53
|
```bash
|
|
43
|
-
npm install -g loom-
|
|
54
|
+
npm install -g loom-agent
|
|
44
55
|
```
|
|
45
56
|
|
|
46
57
|
Or run from source:
|
|
@@ -93,35 +104,8 @@ loom --version
|
|
|
93
104
|
loom --help
|
|
94
105
|
loom --basic # skip TUI, use line-mode REPL
|
|
95
106
|
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
107
|
```
|
|
99
108
|
|
|
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
109
|
## Editor integration (ACP)
|
|
126
110
|
|
|
127
111
|
Loom implements the [Agent Client Protocol (ACP)](https://agentclientprotocol.com)
|
package/bin/loom-bun.js
CHANGED
|
@@ -1,68 +1,96 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
// npm invokes bin targets through Node on Windows, so re-launch under Bun
|
|
3
|
-
// when this file was not started by Bun itself.
|
|
4
|
-
const path = require("path");
|
|
5
|
-
const fs = require("fs");
|
|
6
|
-
const { spawnSync } = require("child_process");
|
|
7
|
-
|
|
8
|
-
// The Solid JSX transform must register in Bun's preload phase, so the
|
|
9
|
-
// preloads are passed as ABSOLUTE paths. That lets Bun start directly in the
|
|
10
|
-
// user's project directory — the old "spawn at the package root, then chdir
|
|
11
|
-
// to the project inside tui-open.tsx" dance froze the TUI after the first
|
|
12
|
-
// frame (splash paints once, then no repaints and no keyboard input; proven
|
|
13
|
-
// by A/B-launching the identical entry with and without the mid-flight
|
|
14
|
-
// process.chdir). No chdir ever happens now: LOOM_START_CWD always equals
|
|
15
|
-
// the starting directory, so the restore in tui-open.tsx is a no-op kept
|
|
16
|
-
// only as a safety net.
|
|
17
|
-
const pkgRoot = path.join(__dirname, "..");
|
|
18
|
-
const underBun = typeof Bun !== "undefined" && !!process.versions.bun;
|
|
19
|
-
if (!underBun) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// npm invokes bin targets through Node on Windows, so re-launch under Bun
|
|
3
|
+
// when this file was not started by Bun itself.
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const { spawnSync } = require("child_process");
|
|
7
|
+
|
|
8
|
+
// The Solid JSX transform must register in Bun's preload phase, so the
|
|
9
|
+
// preloads are passed as ABSOLUTE paths. That lets Bun start directly in the
|
|
10
|
+
// user's project directory — the old "spawn at the package root, then chdir
|
|
11
|
+
// to the project inside tui-open.tsx" dance froze the TUI after the first
|
|
12
|
+
// frame (splash paints once, then no repaints and no keyboard input; proven
|
|
13
|
+
// by A/B-launching the identical entry with and without the mid-flight
|
|
14
|
+
// process.chdir). No chdir ever happens now: LOOM_START_CWD always equals
|
|
15
|
+
// the starting directory, so the restore in tui-open.tsx is a no-op kept
|
|
16
|
+
// only as a safety net.
|
|
17
|
+
const pkgRoot = path.join(__dirname, "..");
|
|
18
|
+
const underBun = typeof Bun !== "undefined" && !!process.versions.bun;
|
|
19
|
+
if (!underBun) {
|
|
20
|
+
// Prefer the bundled @oven bun binary (shipped as an optional dep), then
|
|
21
|
+
// whatever bun is on PATH. Without either, still respawn so the inner
|
|
22
|
+
// failure message can guide the user.
|
|
23
|
+
const OVEN = {
|
|
24
|
+
"win32-x64": "bun-windows-x64",
|
|
25
|
+
"win32-arm64": "bun-windows-aarch64",
|
|
26
|
+
"darwin-x64": "bun-darwin-x64",
|
|
27
|
+
"darwin-arm64": "bun-darwin-aarch64",
|
|
28
|
+
"linux-x64": "bun-linux-x64",
|
|
29
|
+
"linux-arm64": "bun-linux-aarch64",
|
|
30
|
+
};
|
|
31
|
+
let bunCmd = process.platform === "win32" ? "bun.exe" : "bun";
|
|
32
|
+
const short = OVEN[process.platform + "-" + process.arch];
|
|
33
|
+
const exe = process.platform === "win32" ? "bun.exe" : "bun";
|
|
34
|
+
const ovenCandidates = short ? [
|
|
35
|
+
path.join(pkgRoot, "node_modules", "@oven", short, "bin", exe),
|
|
36
|
+
path.join(pkgRoot, "..", "@oven", short, "bin", exe),
|
|
37
|
+
] : [];
|
|
38
|
+
for (const c of ovenCandidates) {
|
|
39
|
+
if (fs.existsSync(c)) { bunCmd = c; break; }
|
|
40
|
+
}
|
|
41
|
+
const result = spawnSync(
|
|
42
|
+
bunCmd,
|
|
43
|
+
[
|
|
44
|
+
"--preload", path.join(pkgRoot, "src", "tui-preload.js"),
|
|
45
|
+
__filename,
|
|
46
|
+
...process.argv.slice(2),
|
|
47
|
+
],
|
|
48
|
+
{
|
|
49
|
+
stdio: "inherit",
|
|
50
|
+
cwd: process.env.LOOM_START_CWD || process.cwd(),
|
|
51
|
+
env: { ...process.env, LOOM_START_CWD: process.env.LOOM_START_CWD || process.cwd() },
|
|
52
|
+
windowsHide: false,
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
if (result.error) {
|
|
56
|
+
console.error("[loom] Bun is required for the full CLI. Install it from https://bun.sh/");
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
process.exit(result.status == null ? 1 : result.status);
|
|
60
|
+
}
|
|
61
|
+
process.title = "loom-code";
|
|
62
|
+
(async () => {
|
|
63
|
+
try {
|
|
64
|
+
// The npm bin entry bypasses src/index.js, so load dotenv here for both
|
|
65
|
+
// the package environment and the project from which `loom` was run.
|
|
66
|
+
const dotenv = require("dotenv");
|
|
67
|
+
dotenv.config({ path: path.join(__dirname, "..", ".env") });
|
|
68
|
+
const startCwd = process.env.LOOM_START_CWD || process.cwd();
|
|
69
|
+
const projectEnv = path.join(startCwd, ".env");
|
|
70
|
+
if (fs.existsSync(projectEnv)) dotenv.config({ path: projectEnv, override: false });
|
|
71
|
+
// The npm global shim can start Bun outside the package directory, so do
|
|
72
|
+
// not depend on bunfig.toml discovery for Windows console setup.
|
|
73
|
+
await import("../src/tui-preload.js");
|
|
74
|
+
const args = process.argv.slice(2);
|
|
75
|
+
const coreMode = args.includes("--basic") || args.includes("-p") || args.includes("--print")
|
|
76
|
+
|| args.includes("--help") || args.includes("-h") || args.includes("--version") || args.includes("-v")
|
|
77
|
+
|| ["acp", "graph"].includes(args[0]);
|
|
78
|
+
if (!coreMode) {
|
|
79
|
+
await import("@opentui/solid/preload");
|
|
80
|
+
await import("../src/tui-open.tsx");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
// Subcommands that must never reach the interactive CLI/TUI: cli.main()
|
|
84
|
+
// has no handler for them and would fall through to launching the full
|
|
85
|
+
// screen app instead of running the requested command.
|
|
86
|
+
if (args[0] === "acp") {
|
|
87
|
+
require("../src/acp/acp-server.js").main();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const { main } = require("../src/core/cli.js");
|
|
91
|
+
await main();
|
|
92
|
+
} catch (err) {
|
|
93
|
+
console.error(err && err.message ? err.message : String(err));
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
68
96
|
})();
|
package/bin/loom-tui.js
CHANGED
|
@@ -10,8 +10,28 @@ const { spawnSync } = require("child_process");
|
|
|
10
10
|
const pkgRoot = path.join(__dirname, "..");
|
|
11
11
|
const underBun = typeof Bun !== "undefined" && !!process.versions.bun;
|
|
12
12
|
if (!underBun) {
|
|
13
|
+
// Prefer the bundled @oven bun binary (shipped as an optional dep), then
|
|
14
|
+
// whatever bun is on PATH — same scheme as bin/loom-bun.js.
|
|
15
|
+
const OVEN = {
|
|
16
|
+
"win32-x64": "bun-windows-x64",
|
|
17
|
+
"win32-arm64": "bun-windows-aarch64",
|
|
18
|
+
"darwin-x64": "bun-darwin-x64",
|
|
19
|
+
"darwin-arm64": "bun-darwin-aarch64",
|
|
20
|
+
"linux-x64": "bun-linux-x64",
|
|
21
|
+
"linux-arm64": "bun-linux-aarch64",
|
|
22
|
+
};
|
|
23
|
+
let bunCmd = process.platform === "win32" ? "bun.exe" : "bun";
|
|
24
|
+
const short = OVEN[process.platform + "-" + process.arch];
|
|
25
|
+
const exe = process.platform === "win32" ? "bun.exe" : "bun";
|
|
26
|
+
const ovenCandidates = short ? [
|
|
27
|
+
path.join(pkgRoot, "node_modules", "@oven", short, "bin", exe),
|
|
28
|
+
path.join(pkgRoot, "..", "@oven", short, "bin", exe),
|
|
29
|
+
] : [];
|
|
30
|
+
for (const c of ovenCandidates) {
|
|
31
|
+
if (fs.existsSync(c)) { bunCmd = c; break; }
|
|
32
|
+
}
|
|
13
33
|
const result = spawnSync(
|
|
14
|
-
|
|
34
|
+
bunCmd,
|
|
15
35
|
[
|
|
16
36
|
"--preload", path.join(pkgRoot, "src", "tui-preload.js"),
|
|
17
37
|
__filename,
|
package/bin/loom.js
CHANGED
|
@@ -24,16 +24,6 @@ process.on('unhandledRejection', (reason) => {
|
|
|
24
24
|
require('../src/acp/acp-server').main();
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
|
-
if (sub === 'web') {
|
|
28
|
-
// Browser interface: HTTP server on 127.0.0.1, opens the browser.
|
|
29
|
-
require('../src/web/web-server').main();
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
if (sub === 'attach') {
|
|
33
|
-
// Terminal client for a running `loom web` server.
|
|
34
|
-
require('../src/web/attach').main();
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
27
|
try {
|
|
38
28
|
await updateCheck();
|
|
39
29
|
await main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loom-agent",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.37",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/toshalkumbhar8979-design/loomcode.git"
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"dev": "node --watch src/index.js",
|
|
25
25
|
"setup": "node src/setup.js",
|
|
26
26
|
"test": "bun run src/tui/test-interactive.tsx && bun run test:unit && bun run lint:core",
|
|
27
|
-
"test:unit": "bun test src/core/agents.test.js src/core/hooks.test.js src/core/custom-commands.test.js src/core/background-tasks.test.js src/core/memory.test.js src/core/subagent-log.test.js src/core/session.test.js src/tools/index.test.js src/providers/providers.test.js src/providers/caching.test.js src/providers/caching-thinking.test.js src/providers/registry.test.js src/mcp/mcp-client.test.js src/mcp/mcp-manager.test.js src/core/session-store.test.js src/skills/skills-manager.test.js src/core/usage.test.js src/core/permissions.test.js src/core/format.test.js src/acp/acp-server.test.js src/
|
|
27
|
+
"test:unit": "bun test src/core/agents.test.js src/core/hooks.test.js src/core/custom-commands.test.js src/core/background-tasks.test.js src/core/memory.test.js src/core/subagent-log.test.js src/core/session.test.js src/tools/index.test.js src/tools/security.test.js src/providers/providers.test.js src/providers/caching.test.js src/providers/caching-thinking.test.js src/providers/registry.test.js src/mcp/mcp-client.test.js src/mcp/mcp-manager.test.js src/core/session-store.test.js src/skills/skills-manager.test.js src/core/usage.test.js src/core/permissions.test.js src/core/format.test.js src/acp/acp-server.test.js src/tui/keybinds.test.ts",
|
|
28
28
|
"lint:core": "node node_modules/typescript/bin/tsc -p tsconfig.core.json",
|
|
29
29
|
"smoke:acp": "node scripts/acp-smoke.js",
|
|
30
30
|
"tui": "bun bin/loom-tui.js",
|
|
@@ -38,11 +38,12 @@
|
|
|
38
38
|
"@opentui/core": "0.5.3",
|
|
39
39
|
"@opentui/solid": "0.5.3",
|
|
40
40
|
"@shikijs/themes": "4.4.3",
|
|
41
|
-
"
|
|
41
|
+
"@supabase/supabase-js": "^2.45.0",
|
|
42
42
|
"boxen": "^8.0.0",
|
|
43
43
|
"chalk": "^5.3.0",
|
|
44
44
|
"cli-spinners": "^3.2.0",
|
|
45
45
|
"commander": "^12.1.0",
|
|
46
|
+
"convex": "^1.14.0",
|
|
46
47
|
"dayjs": "^1.11.0",
|
|
47
48
|
"diff": "^7.0.0",
|
|
48
49
|
"dotenv": "^16.4.0",
|
|
@@ -50,6 +51,7 @@
|
|
|
50
51
|
"inquirer": "^12.1.0",
|
|
51
52
|
"openai": "^5.23.2",
|
|
52
53
|
"ora": "^8.0.0",
|
|
54
|
+
"playwright-core": "^1.64.1",
|
|
53
55
|
"shell-quote": "^1.8.1",
|
|
54
56
|
"solid-js": "1.9.12",
|
|
55
57
|
"strip-ansi": "^7.1.0",
|
|
@@ -62,7 +64,13 @@
|
|
|
62
64
|
"@opentui/core-darwin-x64": "0.5.3",
|
|
63
65
|
"@opentui/core-linux-arm64": "0.5.3",
|
|
64
66
|
"@opentui/core-linux-x64": "0.5.3",
|
|
65
|
-
"@opentui/core-win32-x64": "0.5.3"
|
|
67
|
+
"@opentui/core-win32-x64": "0.5.3",
|
|
68
|
+
"@oven/bun-darwin-aarch64": "1.3.14",
|
|
69
|
+
"@oven/bun-darwin-x64": "1.3.14",
|
|
70
|
+
"@oven/bun-linux-aarch64": "1.3.14",
|
|
71
|
+
"@oven/bun-linux-x64": "1.3.14",
|
|
72
|
+
"@oven/bun-windows-aarch64": "1.3.14",
|
|
73
|
+
"@oven/bun-windows-x64": "1.3.14"
|
|
66
74
|
},
|
|
67
75
|
"overrides": {
|
|
68
76
|
"glob": "^13.0.6"
|
|
@@ -91,10 +99,7 @@
|
|
|
91
99
|
"LOOM.md",
|
|
92
100
|
"docs/acp.md",
|
|
93
101
|
"scripts/acp-smoke.js",
|
|
94
|
-
"scripts/postinstall.js"
|
|
95
|
-
"docs/web.md",
|
|
96
|
-
"src/web/index.html",
|
|
97
|
-
"src/web/graph-view.html"
|
|
102
|
+
"scripts/postinstall.js"
|
|
98
103
|
],
|
|
99
104
|
"keywords": [
|
|
100
105
|
"ai",
|
|
@@ -115,6 +120,6 @@
|
|
|
115
120
|
"doc": "docs"
|
|
116
121
|
},
|
|
117
122
|
"author": "",
|
|
118
|
-
"license": "
|
|
123
|
+
"license": "MIT",
|
|
119
124
|
"type": "commonjs"
|
|
120
125
|
}
|
package/scripts/postinstall.js
CHANGED
|
@@ -9,6 +9,40 @@ const path = require("path");
|
|
|
9
9
|
|
|
10
10
|
const pkgRoot = path.resolve(__dirname, "..");
|
|
11
11
|
|
|
12
|
+
// Bun, bundled: loom-agent lists the @oven/bun-<platform> binaries as
|
|
13
|
+
// optionalDependencies, so `npm i -g loom-agent` brings the REAL bun binary
|
|
14
|
+
// (pinned, matching the repo's bun) along even when the user has no bun
|
|
15
|
+
// installed. npm skips non-matching platforms via each package's os/cpu
|
|
16
|
+
// fields, and this resolution never needs install scripts (npm
|
|
17
|
+
// allow-scripts policies can't break it). Resolved here once at install
|
|
18
|
+
// time and baked into the shims as the preferred runtime; the shims still
|
|
19
|
+
// fall back to whatever `bun` is on PATH, and — if NO bun exists at all —
|
|
20
|
+
// to `node bin/loom.js` (line-mode REPL) instead of dying with
|
|
21
|
+
// "'bun' is not recognized".
|
|
22
|
+
function ovenBunPath() {
|
|
23
|
+
const PKGS = {
|
|
24
|
+
"win32-x64": "bun-windows-x64",
|
|
25
|
+
"win32-arm64": "bun-windows-aarch64",
|
|
26
|
+
"darwin-x64": "bun-darwin-x64",
|
|
27
|
+
"darwin-arm64": "bun-darwin-aarch64",
|
|
28
|
+
"linux-x64": "bun-linux-x64",
|
|
29
|
+
"linux-arm64": "bun-linux-aarch64",
|
|
30
|
+
};
|
|
31
|
+
const short = PKGS[process.platform + "-" + process.arch];
|
|
32
|
+
if (!short) return "";
|
|
33
|
+
const exe = process.platform === "win32" ? "bun.exe" : "bun";
|
|
34
|
+
const candidates = [
|
|
35
|
+
path.join(pkgRoot, "node_modules", "@oven", short, "bin", exe), // nested (npm default)
|
|
36
|
+
path.join(pkgRoot, "..", "@oven", short, "bin", exe), // hoisted
|
|
37
|
+
];
|
|
38
|
+
for (var i = 0; i < candidates.length; i++) {
|
|
39
|
+
try { if (fs.existsSync(candidates[i])) return candidates[i]; } catch {}
|
|
40
|
+
}
|
|
41
|
+
return "";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
var ovenPath = "";
|
|
45
|
+
|
|
12
46
|
function shimDirs() {
|
|
13
47
|
const dirs = new Set();
|
|
14
48
|
if (path.basename(path.resolve(pkgRoot, "..")) !== "node_modules") return [];
|
|
@@ -29,8 +63,11 @@ function cmdShim(pkg, script) {
|
|
|
29
63
|
"@ECHO off",
|
|
30
64
|
"SETLOCAL",
|
|
31
65
|
'SET "LOOM_START_CWD=%CD%"',
|
|
32
|
-
'IF EXIST "
|
|
66
|
+
'IF EXIST "' + ovenPath + '" (SET "_prog=' + ovenPath + '") ELSE (SET "_prog=bun")',
|
|
33
67
|
'"%_prog%" --preload "%~dp0' + pkgEscaped + '\\src\\tui-preload.js" "%~dp0' + pkgEscaped + '\\bin\\' + script + '" %*',
|
|
68
|
+
"IF %ERRORLEVEL% NEQ 9009 EXIT /b %ERRORLEVEL%",
|
|
69
|
+
"REM bun not found (errorlevel 9009): fall back to the Node REPL",
|
|
70
|
+
"node \"%~dp0" + pkgEscaped + "\\bin\\loom.js\" %*",
|
|
34
71
|
"EXIT /b %ERRORLEVEL%",
|
|
35
72
|
""
|
|
36
73
|
].join("\r\n");
|
|
@@ -39,10 +76,13 @@ function cmdShim(pkg, script) {
|
|
|
39
76
|
function psShim(pkg, script) {
|
|
40
77
|
return [
|
|
41
78
|
"#!/usr/bin/env pwsh",
|
|
42
|
-
"# rewritten by loom-agent postinstall (
|
|
79
|
+
"# rewritten by loom-agent postinstall (bundled bun + node fallback)",
|
|
43
80
|
"$env:LOOM_START_CWD = (Get-Location).Path",
|
|
44
81
|
"$pkg = Join-Path $PSScriptRoot '" + pkg + "'",
|
|
45
|
-
"
|
|
82
|
+
"$bun = '" + ovenPath + "'",
|
|
83
|
+
"if (-not ($bun -and (Test-Path $bun))) { $bun = 'bun' }",
|
|
84
|
+
"& $bun --preload (Join-Path $pkg 'src/tui-preload.js') (Join-Path $pkg 'bin/" + script + "') @args",
|
|
85
|
+
"if ($LASTEXITCODE -eq 9009) { node (Join-Path $pkg 'bin/loom.js') @args; exit $LASTEXITCODE }",
|
|
46
86
|
"exit $LASTEXITCODE",
|
|
47
87
|
""
|
|
48
88
|
].join("\n");
|
|
@@ -51,16 +91,28 @@ function psShim(pkg, script) {
|
|
|
51
91
|
function shShim(pkg, script) {
|
|
52
92
|
return [
|
|
53
93
|
"#!/bin/sh",
|
|
54
|
-
"# rewritten by loom-agent postinstall (
|
|
94
|
+
"# rewritten by loom-agent postinstall (bundled bun + node fallback)",
|
|
55
95
|
'LOOM_START_CWD="$(pwd)"',
|
|
56
96
|
"export LOOM_START_CWD",
|
|
57
97
|
'PKG="$(dirname "$0")/' + pkg + '"',
|
|
58
|
-
'
|
|
98
|
+
'BUN="' + ovenPath + '"',
|
|
99
|
+
'[ -x "$BUN" ] || BUN="$(command -v bun || true)"',
|
|
100
|
+
'if [ -n "$BUN" ]; then',
|
|
101
|
+
' exec "$BUN" --preload "$PKG/src/tui-preload.js" "$PKG/bin/' + script + '" "$@"',
|
|
102
|
+
"fi",
|
|
103
|
+
'echo "[loom] full TUI needs bun - install with: npm i -g bun (https://bun.sh)" >&2',
|
|
104
|
+
'exec node "$PKG/bin/loom.js" "$@"',
|
|
59
105
|
""
|
|
60
106
|
].join("\n");
|
|
61
107
|
}
|
|
62
108
|
|
|
63
109
|
try {
|
|
110
|
+
ovenPath = ovenBunPath();
|
|
111
|
+
if (ovenPath) {
|
|
112
|
+
console.log("[loom-agent] bundled bun found: " + ovenPath);
|
|
113
|
+
} else {
|
|
114
|
+
console.log("[loom-agent] bundled bun not present for " + process.platform + "-" + process.arch + " (optional dep skipped?) - shims will use bun from PATH, or fall back to the Node REPL");
|
|
115
|
+
}
|
|
64
116
|
for (var i = 0; i < shimDirs().length; i++) {
|
|
65
117
|
var dir = shimDirs()[i];
|
|
66
118
|
var relPkg = path.relative(dir, pkgRoot).split(path.sep).join("/");
|