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.
Files changed (39) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/README.md +14 -30
  3. package/bin/loom-bun.js +95 -67
  4. package/bin/loom-tui.js +21 -1
  5. package/bin/loom.js +0 -10
  6. package/package.json +14 -9
  7. package/scripts/postinstall.js +57 -5
  8. package/src/connectors/convex.js +356 -0
  9. package/src/connectors/deploy.js +390 -0
  10. package/src/connectors/index.js +23 -0
  11. package/src/connectors/supabase.js +279 -0
  12. package/src/core/agents.js +32 -2
  13. package/src/core/cli.js +56 -7
  14. package/src/core/permissions.js +12 -1
  15. package/src/core/plugin-cmd.js +1 -0
  16. package/src/core/session.js +87 -6
  17. package/src/mcp/mcp-client.js +42 -2
  18. package/src/skills/skills-manager.js +25 -2
  19. package/src/tools/browser.js +204 -0
  20. package/src/tools/index.js +116 -5
  21. package/src/tui/App.tsx +19 -4
  22. package/src/tui/components/BreadcrumbBar.tsx +10 -9
  23. package/src/tui/components/ChatArea.tsx +3 -3
  24. package/src/tui/components/InputBar.tsx +22 -11
  25. package/src/tui/components/MdText.tsx +2 -1
  26. package/src/tui/components/Modals.tsx +31 -17
  27. package/src/tui/components/PermissionPopup.tsx +15 -12
  28. package/src/tui/components/Sidebar.tsx +6 -2
  29. package/src/tui/components/SplashScreen.tsx +17 -24
  30. package/src/tui/components/SubagentPanel.tsx +1 -1
  31. package/src/tui/components/ToastOverlay.tsx +23 -12
  32. package/src/tui/store.ts +4 -2
  33. package/src/tui/theme.ts +45 -30
  34. package/src/tui/tool-display.ts +13 -2
  35. package/docs/web.md +0 -205
  36. package/src/web/attach.js +0 -242
  37. package/src/web/graph-view.html +0 -262
  38. package/src/web/index.html +0 -824
  39. 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
- # Loom Code
1
+ ```
2
+ ██╗ ██████╗ ██████╗ ███╗ ███╗ ██████╗ ██████╗ ██████╗ ███████╗
3
+ ██║ ██╔═══██╗██╔═══██╗████╗ ████║ ██╔════╝██╔═══██╗██╔══██╗██╔════╝
4
+ ██║ ██║ ██║██║ ██║██╔████╔██║ ██║ ██║ ██║██║ ██║█████╗
5
+ ██║ ██║ ██║██║ ██║██║╚██╔╝██║ ██║ ██║ ██║██║ ██║██╔══╝
6
+ ███████╗╚██████╔╝╚██████╔╝██║ ╚═╝ ██║ ╚██████╗██████╔╝██████╔╝███████╗
7
+ ╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝╚═════╝ ╚═════╝ ╚══════╝
8
+ ```
2
9
 
3
10
  [![npm version](https://img.shields.io/npm/v/loom-agent?style=flat&color=blue)](https://www.npmjs.com/package/loom-agent)
4
11
  [![License: MIT](https://img.shields.io/npm/l/loom-agent?style=flat&color=green)](LICENSE)
5
12
  [![Platform: Win/Mac/Linux](https://img.shields.io/badge/platform-Win--Mac--Linux-orange)](#)
13
+ [![Website: loomcluster.in](https://img.shields.io/badge/website-loomcluster.in-success)](https://loomcluster.in/)
14
+
15
+ ![Loom Code splash — connected to NVIDIA](docs/screenshot.png)
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
- - 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)
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-code
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
- const result = spawnSync(
21
- process.platform === "win32" ? "bun.exe" : "bun",
22
- [
23
- "--preload", path.join(pkgRoot, "src", "tui-preload.js"),
24
- __filename,
25
- ...process.argv.slice(2),
26
- ],
27
- {
28
- stdio: "inherit",
29
- cwd: process.env.LOOM_START_CWD || process.cwd(),
30
- env: { ...process.env, LOOM_START_CWD: process.env.LOOM_START_CWD || process.cwd() },
31
- windowsHide: false,
32
- }
33
- );
34
- if (result.error) {
35
- console.error("[loom] Bun is required for the full CLI. Install it from https://bun.sh/");
36
- process.exit(1);
37
- }
38
- process.exit(result.status == null ? 1 : result.status);
39
- }
40
- process.title = "loom-code";
41
- (async () => {
42
- try {
43
- // The npm bin entry bypasses src/index.js, so load dotenv here for both
44
- // the package environment and the project from which `loom` was run.
45
- const dotenv = require("dotenv");
46
- dotenv.config({ path: path.join(__dirname, "..", ".env") });
47
- const startCwd = process.env.LOOM_START_CWD || process.cwd();
48
- const projectEnv = path.join(startCwd, ".env");
49
- if (fs.existsSync(projectEnv)) dotenv.config({ path: projectEnv, override: false });
50
- // The npm global shim can start Bun outside the package directory, so do
51
- // not depend on bunfig.toml discovery for Windows console setup.
52
- await import("../src/tui-preload.js");
53
- const args = process.argv.slice(2);
54
- const coreMode = args.includes("--basic") || args.includes("-p") || args.includes("--print")
55
- || args.includes("--help") || args.includes("-h") || args.includes("--version") || args.includes("-v")
56
- || ["acp", "web", "attach", "graph"].includes(args[0]);
57
- if (!coreMode) {
58
- await import("@opentui/solid/preload");
59
- await import("../src/tui-open.tsx");
60
- return;
61
- }
62
- const { main } = require("../src/core/cli.js");
63
- await main();
64
- } catch (err) {
65
- console.error(err && err.message ? err.message : String(err));
66
- process.exit(1);
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
- process.platform === "win32" ? "bun.exe" : "bun",
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.33",
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/web/web-server.test.js src/tui/keybinds.test.ts",
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
- "bonjour-service": "^1.4.4",
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": "ISC",
123
+ "license": "MIT",
119
124
  "type": "commonjs"
120
125
  }
@@ -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 "%~dp0bun.exe" (SET "_prog=%~dp0bun.exe") ELSE (SET "_prog=bun")',
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 (no-cwd-change TUI launch)",
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
- "& bun --preload (Join-Path $pkg 'src/tui-preload.js') (Join-Path $pkg 'bin/" + script + "') @args",
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 (no-cwd-change TUI launch)",
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
- 'exec bun --preload "$PKG/src/tui-preload.js" "$PKG/bin/' + script + '" "$@"',
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("/");