@kdonev/termscape 0.1.9 → 0.2.1

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 (103) hide show
  1. package/README.md +111 -45
  2. package/dist/cli-args.d.ts +10 -0
  3. package/dist/cli-args.d.ts.map +1 -1
  4. package/dist/cli-args.js +2 -0
  5. package/dist/cli-args.js.map +1 -1
  6. package/dist/cli.js +15 -258
  7. package/dist/cli.js.map +1 -1
  8. package/dist/hub.d.ts +28 -1
  9. package/dist/hub.d.ts.map +1 -1
  10. package/dist/hub.js +65 -2
  11. package/dist/hub.js.map +1 -1
  12. package/dist/hub.tgz +0 -0
  13. package/dist/main.d.ts +7 -0
  14. package/dist/main.d.ts.map +1 -0
  15. package/dist/main.js +343 -0
  16. package/dist/main.js.map +1 -0
  17. package/dist/protocol/domain.d.ts +35 -0
  18. package/dist/protocol/domain.d.ts.map +1 -1
  19. package/dist/protocol/domain.js +31 -2
  20. package/dist/protocol/domain.js.map +1 -1
  21. package/dist/protocol/index.d.ts +1 -0
  22. package/dist/protocol/index.d.ts.map +1 -1
  23. package/dist/protocol/index.js +1 -0
  24. package/dist/protocol/index.js.map +1 -1
  25. package/dist/protocol/mouse.d.ts +14 -0
  26. package/dist/protocol/mouse.d.ts.map +1 -1
  27. package/dist/protocol/mouse.js +16 -0
  28. package/dist/protocol/mouse.js.map +1 -1
  29. package/dist/protocol/peer.d.ts +23 -0
  30. package/dist/protocol/peer.d.ts.map +1 -1
  31. package/dist/protocol/peer.js +45 -1
  32. package/dist/protocol/peer.js.map +1 -1
  33. package/dist/protocol/version.d.ts +12 -0
  34. package/dist/protocol/version.d.ts.map +1 -0
  35. package/dist/protocol/version.js +37 -0
  36. package/dist/protocol/version.js.map +1 -0
  37. package/dist/protocol/ws.d.ts +103 -1
  38. package/dist/protocol/ws.d.ts.map +1 -1
  39. package/dist/protocol/ws.js +45 -1
  40. package/dist/protocol/ws.js.map +1 -1
  41. package/dist/remote/enroll.d.ts.map +1 -1
  42. package/dist/remote/enroll.js +22 -1
  43. package/dist/remote/enroll.js.map +1 -1
  44. package/dist/remote/join.d.ts.map +1 -1
  45. package/dist/remote/join.js +10 -1
  46. package/dist/remote/join.js.map +1 -1
  47. package/dist/remote/peer-serve.d.ts.map +1 -1
  48. package/dist/remote/peer-serve.js +23 -0
  49. package/dist/remote/peer-serve.js.map +1 -1
  50. package/dist/remote/peer.d.ts +7 -0
  51. package/dist/remote/peer.d.ts.map +1 -1
  52. package/dist/remote/peer.js +25 -1
  53. package/dist/remote/peer.js.map +1 -1
  54. package/dist/remote/registry.d.ts +24 -2
  55. package/dist/remote/registry.d.ts.map +1 -1
  56. package/dist/remote/registry.js +91 -5
  57. package/dist/remote/registry.js.map +1 -1
  58. package/dist/server.d.ts.map +1 -1
  59. package/dist/server.js +76 -8
  60. package/dist/server.js.map +1 -1
  61. package/dist/session/manager.d.ts.map +1 -1
  62. package/dist/session/manager.js +7 -1
  63. package/dist/session/manager.js.map +1 -1
  64. package/dist/session/paste-image.d.ts +19 -0
  65. package/dist/session/paste-image.d.ts.map +1 -0
  66. package/dist/session/paste-image.js +65 -0
  67. package/dist/session/paste-image.js.map +1 -0
  68. package/dist/supervisor.d.ts +44 -0
  69. package/dist/supervisor.d.ts.map +1 -0
  70. package/dist/supervisor.js +125 -0
  71. package/dist/supervisor.js.map +1 -0
  72. package/dist/update/host.d.ts +19 -0
  73. package/dist/update/host.d.ts.map +1 -0
  74. package/dist/update/host.js +56 -0
  75. package/dist/update/host.js.map +1 -0
  76. package/dist/update/install.d.ts +38 -0
  77. package/dist/update/install.d.ts.map +1 -0
  78. package/dist/update/install.js +81 -0
  79. package/dist/update/install.js.map +1 -0
  80. package/dist/update/post-install.d.ts +2 -0
  81. package/dist/update/post-install.d.ts.map +1 -0
  82. package/dist/update/post-install.js +39 -0
  83. package/dist/update/post-install.js.map +1 -0
  84. package/dist/update/restart.d.ts +31 -0
  85. package/dist/update/restart.d.ts.map +1 -0
  86. package/dist/update/restart.js +65 -0
  87. package/dist/update/restart.js.map +1 -0
  88. package/dist/update/resume.d.ts +5 -0
  89. package/dist/update/resume.d.ts.map +1 -0
  90. package/dist/update/resume.js +47 -0
  91. package/dist/update/resume.js.map +1 -0
  92. package/dist/update/updater.d.ts +57 -0
  93. package/dist/update/updater.d.ts.map +1 -0
  94. package/dist/update/updater.js +190 -0
  95. package/dist/update/updater.js.map +1 -0
  96. package/dist/window/app-window.js +43 -1
  97. package/dist/window/app-window.js.map +1 -1
  98. package/package.json +1 -1
  99. package/web/assets/index-85NfOib0.js +111 -0
  100. package/web/assets/index-Czi_vAR-.css +1 -0
  101. package/web/index.html +2 -2
  102. package/web/assets/index-gfbbNQK4.css +0 -1
  103. package/web/assets/index-i7fXLKF0.js +0 -111
package/README.md CHANGED
@@ -1,55 +1,89 @@
1
1
  # Termscape
2
2
 
3
- Run a crew of AI coding agents on one infinite canvas, and let them talk to
3
+ Run a crew of AI coding agents side by side on one canvas, and let them talk to
4
4
  each other.
5
5
 
6
- Each terminal window on the canvas runs an agent CLI wired to an MCP server the
7
- hub exposes. Agents can look each other up, send each other messages, spawn
8
- helpers into their workspace, and check on each other's terminals. A message
9
- from one agent is delivered by typing it into the other's terminal, immediately.
6
+ ![Agents on a Termscape canvas messaging each other](https://raw.githubusercontent.com/kdonev/termscape/main/docs/intro.gif)
10
7
 
11
- Local-first: the hub runs on your machine and the UI opens in a window of its
12
- own — the same web page a browser would show. It binds
13
- every interface, so the canvas opens on your phone or a second screen as well —
14
- with its token — and another machine can attach itself from the join page.
15
- `--listen loopback` keeps the whole thing to this machine. Other machines run
16
- the same hub as a daemon and appear on the same canvas.
8
+ ## What it's for
17
9
 
18
- ## Getting started
10
+ Running several coding agents at once usually means a pile of terminal tabs
11
+ that know nothing about each other, with you copying text between them.
12
+ Termscape puts them all in one place and lets them work together:
13
+
14
+ - **See every agent at once.** Each agent is a live terminal on a zoomable
15
+ canvas, grouped by project.
16
+ - **Let agents talk to each other.** An agent can message another, ask it for
17
+ a review, or start a helper to take part of the work.
18
+ - **Pick up where you left off.** Close Termscape and start it again: the
19
+ canvas comes back, and your agents can continue their conversations.
20
+ - **Use more than one machine.** Agents on another computer appear on the same
21
+ canvas and are messaged the same way.
22
+ - **Check in from anywhere.** Open the canvas on your phone or a second screen,
23
+ or share a single terminal with a colleague.
24
+
25
+ Works with Claude Code, Codex, Gemini CLI, Kilo Code and opencode.
26
+
27
+ ## Quick start
28
+
29
+ You need Node 24 or newer and at least one agent CLI, such as
30
+ [Claude Code](https://claude.com/claude-code). Then run:
19
31
 
20
32
  ```bash
21
33
  npx @kdonev/termscape
22
34
  ```
23
35
 
24
- That starts the hub and opens the canvas in a window of its own. The window is
25
- the app: closing it stops the hub, saving state first, just as Ctrl+C in the
26
- terminal would. `--browser` opens the canvas in a browser tab instead, which
27
- the hub outlives, and `--no-open` opens nothing; the URL is printed either
28
- way. Nothing is installed system-wide: state lives in `~/.termscape`, and
29
- deleting that directory is the uninstall.
30
-
31
- The window is the operating system's own webview — WebView2 on Windows,
32
- WebKit on macOS, WebKitGTK on Linux so there is no browser bundled with it.
33
- Linux needs WebKitGTK 4.1 and libxdo (`sudo apt install libwebkit2gtk-4.1-0
34
- libxdo3` on Debian and Ubuntu). Where no webview can be loaded, the hub says
35
- why and opens the browser instead.
36
-
37
- The hub is reachable from your network, so the same URL — token and all — opens
38
- the canvas on a phone or a second screen, and the **+ machine** dialog already
39
- has a join link in it for the second machine; see
40
- [Adding another machine](#adding-another-machine). To keep the hub to this
41
- machine entirely no join page, nothing off 127.0.0.1:
36
+ The canvas opens in a window of its own; close the window to stop Termscape.
37
+ Nothing is installed system-wide. To remove it, delete `~/.termscape`.
38
+
39
+ On Linux, install WebKitGTK first (`sudo apt install libwebkit2gtk-4.1-0
40
+ libxdo3` on Debian and Ubuntu), or the canvas opens in your browser instead.
41
+
42
+ ## Quick tour
43
+
44
+ 1. **Add a workspace.** Click **machines** in the top bar, then
45
+ **+ workspace**, and pick a project folder.
46
+ 2. **Start an agent.** Click **+ agent** on the workspace and choose one, such
47
+ as `claude`. Its terminal appears on the canvas, and you use it as you
48
+ would in any terminal.
49
+ 3. **Start a second one** in the same workspace.
50
+ 4. **Let them work together.** Just ask, in plain words: *"ask the other agent
51
+ to review your last change"*. The message shows up in the other agent's
52
+ terminal with the sender's name, and the answer comes back the same way.
53
+ Agents can also start helpers of their own.
54
+ 5. **Find your way around.** Scroll to pan, **Ctrl/⌘ + scroll** to zoom,
55
+ **Ctrl/⌘ + 1** to see everything, **Ctrl/⌘ + 2** to zoom in on one
56
+ terminal. Clicking an agent in the panel takes you to it.
57
+ 6. **See what was said.** **messages** in the top bar lists every message the
58
+ agents have sent each other.
59
+ 7. **Come back later.** Stop Termscape and start it again. The canvas returns
60
+ as you left it, and **resume** on a window continues that agent's
61
+ conversation. (Agents that can't continue one show **restart** instead.)
62
+
63
+ That's the core of it. The sections below cover the details.
64
+
65
+ ## Starting options
66
+
67
+ The window is the app: closing it stops Termscape, saving state first, just as
68
+ Ctrl+C in the terminal would. `--browser` opens the canvas in a browser tab
69
+ instead, which Termscape outlives, and `--no-open` opens nothing; the URL is
70
+ printed either way. State lives in `~/.termscape`.
71
+
72
+ The window is the operating system's own webview (WebView2 on Windows, WebKit
73
+ on macOS, WebKitGTK on Linux), so no browser is bundled. Where no webview can
74
+ be loaded, Termscape says why and opens the browser instead.
75
+
76
+ The canvas is reachable from your network with its token, so the same URL
77
+ opens it on a phone or a second screen, and the **+ machine** dialog has a join
78
+ link for adding another computer (see
79
+ [Adding another machine](#adding-another-machine)). To keep everything on this
80
+ machine only:
42
81
 
43
82
  ```bash
44
83
  npx @kdonev/termscape --listen loopback
45
84
  ```
46
85
 
47
- Slide out the **machines** panel: every machine, the workspaces on it, and
48
- the agents in each. Point a workspace at a folder there, and start an agent
49
- in it. Adding, editing and removing all open a dialog over the canvas, so the
50
- node you acted on stays where it was and a refusal — a folder that is not
51
- there, a rename the addresses will not allow — arrives in the dialog next to
52
- the field, with what you typed still in it.
86
+ ## Using the canvas
53
87
 
54
88
  - **Scroll** to pan, **Ctrl/⌘ + scroll** to zoom, **Ctrl/⌘ + 1** to fit,
55
89
  **Ctrl/⌘ + 2** to zoom to one terminal
@@ -57,12 +91,16 @@ the field, with what you typed still in it.
57
91
  navigates instead of zooming: in over a terminal maximizes it, out steps
58
92
  back to the workspace around it and then to everything. Zooming at any
59
93
  ordinary pace is left alone
60
- - Every window shows its real terminal at every zoom; the text minifies
61
- rather than being replaced by a card, so a zoomed-out canvas still shows
62
- the shape of what each agent is doing
63
- - Clicking an agent in the panel brings the canvas to it
94
+ - Every window shows its real terminal at every zoom; the text shrinks rather
95
+ than being replaced by a card, so a zoomed-out canvas still shows the shape
96
+ of what each agent is doing
97
+ - The **machines** panel lists every machine, the workspaces on it, and the
98
+ agents in each. Clicking an agent brings the canvas to it
64
99
  - Clicking the canvas closes the panel, as does **Escape** — which closes the
65
100
  panel first and clears the selection only once it is shut
101
+ - Adding, editing and removing open a dialog over the canvas; if something is
102
+ refused, such as a folder that does not exist, the reason appears next to
103
+ the field with what you typed still in it
66
104
  - **share** on a window's title bar hands a reviewer a link to that one
67
105
  terminal, live, with nothing else on the canvas visible to them — see
68
106
  [Security](#security) for what the link actually grants
@@ -73,7 +111,8 @@ the field, with what you typed still in it.
73
111
  needs 22)
74
112
  - An agent CLI on your `PATH` — [Claude Code](https://claude.com/claude-code)
75
113
  is the profile that ships configured
76
- - macOS, Windows, or Linux
114
+ - macOS, Windows, or Linux; on Linux, WebKitGTK 4.1 and libxdo for the app
115
+ window
77
116
 
78
117
  Everything native is prebuilt on all three platforms, so no compiler is
79
118
  needed.
@@ -91,10 +130,11 @@ Hub (Node)
91
130
  Remote hub (same binary, --headless)
92
131
  ```
93
132
 
133
+ Each terminal window runs an agent CLI wired to an MCP server the hub exposes.
94
134
  Agents never talk to each other directly. They call `send_message` on their own
95
- hub; the hub does the delivery, locally or by forwarding to a peer. That is why
96
- an agent addresses a peer on another machine exactly as it addresses one in the
97
- next window.
135
+ hub; the hub does the delivery, locally or by forwarding to a peer, by typing
136
+ the message into the other agent's terminal. That is why an agent addresses a
137
+ peer on another machine exactly as it addresses one in the next window.
98
138
 
99
139
  ## Adding another machine
100
140
 
@@ -411,6 +451,31 @@ Two consequences worth knowing:
411
451
  - Resume relaunches in the **same working directory**, because that is how
412
452
  Claude Code finds a conversation. Move the folder and resume starts fresh.
413
453
 
454
+ ## Updating
455
+
456
+ The hub asks npm for a newer release shortly after it starts and every six
457
+ hours after that. When there is one, an **update** button appears next to the
458
+ version in the top bar. Nothing restarts until you press it. Then the hub
459
+ downloads the release, restarts on the same address in the same terminal,
460
+ and the canvas reconnects on its own. The agents that were running stop with
461
+ the old hub and are resumed by the new one, continuing their conversations.
462
+ (Stopping the hub yourself is different: those agents come back stopped, as
463
+ described under [State and restart](#state-and-restart).)
464
+
465
+ This works for `npx @kdonev/termscape` and for a global `npm install -g`. A
466
+ checkout of this repository updates the way checkouts do, so for one of those
467
+ the button only links to the release. `--no-update-check` (or
468
+ `TERMSCAPE_NO_UPDATE_CHECK=1`) turns the check off.
469
+
470
+ Other machines take the canvas's own build, not npm's. When one runs a
471
+ different version from the canvas, its row in the machines panel gets an
472
+ **update** button, so you pick when each machine restarts. A deployed machine
473
+ is redeployed over SSH. A joined one runs the join installer again by itself
474
+ (its output is in `~/.termscape/update.log` there) and rejoins as the same
475
+ machine. Either way, the agents that were running there are resumed. A joined machine one protocol version behind the canvas stays on the
476
+ canvas as *outdated* until you update it. Machines joined with 0.1.9 or
477
+ earlier can't update themselves yet, so re-run the join command on those once.
478
+
414
479
  ## Remote machines
415
480
 
416
481
  Add a machine in the panel. The hub is copied over SSH, installed, and started as a
@@ -449,7 +514,8 @@ could be talked into sending an attacker's text to a peer.
449
514
  - `--dangerously-skip-permissions` is never a default.
450
515
  - A share link is a real grant of a keyboard on an agent that can run
451
516
  commands, not a read-only view — the dialog says so before you copy one.
452
- It opens exactly one terminal and nothing else: no canvas, no other agent's
517
+ That includes pasting images: each one is written, capped at 5 MB, to a file
518
+ under that session's own directory in `~/.termscape/run`. It opens exactly one terminal and nothing else: no canvas, no other agent's
453
519
  name, no panel. It is scoped to that one session everywhere the canvas
454
520
  token is checked, stored in `state.db` so it survives a hub restart, and
455
521
  reachable only on the interfaces the hub bound — a hub started with
@@ -1,3 +1,4 @@
1
+ import type { parseArgs } from 'node:util';
1
2
  /**
2
3
  * The hub's command line, in a module of its own.
3
4
  *
@@ -53,6 +54,10 @@ export declare const CLI_OPTIONS: {
53
54
  readonly type: "boolean";
54
55
  readonly default: false;
55
56
  };
57
+ readonly 'no-update-check': {
58
+ readonly type: "boolean";
59
+ readonly default: false;
60
+ };
56
61
  readonly version: {
57
62
  readonly type: "boolean";
58
63
  readonly default: false;
@@ -62,4 +67,9 @@ export declare const CLI_OPTIONS: {
62
67
  readonly default: false;
63
68
  };
64
69
  };
70
+ /** What `parseArgs` makes of CLI_OPTIONS. */
71
+ export type CliValues = ReturnType<typeof parseArgs<{
72
+ options: typeof CLI_OPTIONS;
73
+ strict: true;
74
+ }>>['values'];
65
75
  //# sourceMappingURL=cli-args.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cli-args.d.ts","sourceRoot":"","sources":["../src/cli-args.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAeoD,CAAC"}
1
+ {"version":3,"file":"cli-args.d.ts","sourceRoot":"","sources":["../src/cli-args.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiBoD,CAAC;AAE7E,6CAA6C;AAC7C,MAAM,MAAM,SAAS,GAAG,UAAU,CAChC,OAAO,SAAS,CAAC;IAAE,OAAO,EAAE,OAAO,WAAW,CAAC;IAAC,MAAM,EAAE,IAAI,CAAA;CAAE,CAAC,CAChE,CAAC,QAAQ,CAAC,CAAC"}
package/dist/cli-args.js CHANGED
@@ -31,6 +31,8 @@ export const CLI_OPTIONS = {
31
31
  'no-open': { type: 'boolean', default: false },
32
32
  // The canvas opens in a window of its own unless this asks for a tab.
33
33
  browser: { type: 'boolean', default: false },
34
+ // Checking npm for a newer release is on unless this says otherwise.
35
+ 'no-update-check': { type: 'boolean', default: false },
34
36
  version: { type: 'boolean', default: false },
35
37
  help: { type: 'boolean', default: false },
36
38
  };
@@ -1 +1 @@
1
- {"version":3,"file":"cli-args.js","sourceRoot":"","sources":["../src/cli-args.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IACxB,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;IAC7C,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IACzB,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IAC1B,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IACxB,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IAChC,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IACzB,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;IACzC,oEAAoE;IACpE,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;IAC9C,sEAAsE;IACtE,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;IAC5C,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;IAC5C,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;CACiC,CAAC"}
1
+ {"version":3,"file":"cli-args.js","sourceRoot":"","sources":["../src/cli-args.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IACxB,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;IAC7C,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IACzB,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IAC1B,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IACxB,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IAChC,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IACzB,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;IACzC,oEAAoE;IACpE,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;IAC9C,sEAAsE;IACtE,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;IAC5C,qEAAqE;IACrE,iBAAiB,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;IACtD,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;IAC5C,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;CACiC,CAAC"}
package/dist/cli.js CHANGED
@@ -1,269 +1,26 @@
1
1
  #!/usr/bin/env node
2
- import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
3
2
  import { parseArgs } from 'node:util';
4
- import { Hub, HUB_VERSION } from './hub.js';
5
- import { serve, MEMORABLE_PORTS } from './server.js';
6
- import { mintClientToken } from './agents/tokens.js';
7
- import { paths } from './paths.js';
8
- import { listenPlan } from './remote/lan.js';
9
- import { joinCanvas } from './remote/join.js';
10
- import { openInBrowser } from './browser.js';
11
- import { openAppWindow } from './window/open-window.js';
12
- import { ProfileRegistry } from './agents/profiles.js';
13
- import { which } from './agents/resolve.js';
14
- import { debugTopics } from './debug.js';
3
+ import { fileURLToPath } from 'node:url';
15
4
  import { CLI_OPTIONS } from './cli-args.js';
16
- /** How long shutdown waits for open connections to close. See `shutdown`. */
17
- const SERVER_CLOSE_DEADLINE_MS = 2000;
18
- /**
19
- * Warn when no agent CLI is installed.
20
- *
21
- * Without this the first thing a new user does - start an agent - fails inside
22
- * a terminal window on the canvas, where the error is easy to miss and reads
23
- * like the hub is broken. Checking every profile that wires up MCP, rather
24
- * than "claude" specifically, keeps a custom ~/.termscape/agents.toml counted.
5
+ import { shouldSupervise, supervise } from './supervisor.js';
6
+ /*
7
+ * Deliberately almost empty. Deciding whether this process is the hub or the
8
+ * supervisor that runs it has to happen before anything loads a native
9
+ * module: the supervisor outlives every hub it starts, and on Windows a
10
+ * global update cannot replace a .node file any live process has loaded. So
11
+ * the hub is imported only on the branch that runs it.
25
12
  */
26
- function preflightAgents() {
27
- const agents = ProfileRegistry.load()
28
- .list()
29
- .filter((profile) => profile.mcp);
30
- if (agents.length === 0)
31
- return;
32
- if (agents.some((profile) => which(profile.command)))
33
- return;
34
- const names = agents.map((profile) => profile.command).join(', ');
35
- console.error(`[warn] no agent CLI found on PATH (looked for: ${names})`);
36
- console.error(' install one - https://claude.com/claude-code - or set');
37
- console.error(' an absolute "command" in ~/.termscape/agents.toml.');
38
- console.error(' Terminals with the "shell" profile still work.');
39
- console.error('');
40
- }
41
- async function main() {
13
+ async function run() {
42
14
  const { values } = parseArgs({ options: CLI_OPTIONS, strict: true });
43
- if (values.version) {
44
- console.log(HUB_VERSION);
15
+ const cliPath = fileURLToPath(import.meta.url);
16
+ if (shouldSupervise(values, cliPath)) {
17
+ await supervise(cliPath);
45
18
  return;
46
19
  }
47
- if (values.help) {
48
- console.log(`termscape ${HUB_VERSION}
49
-
50
- --port <n> port to bind; 0 lets the OS pick. Default: the first
51
- free memorable port (7777, 4242, ...)
52
- --listen <addr> bind address. Default: every interface, so the canvas is
53
- reachable from your network with its token and another
54
- machine can attach from the join page - the one page
55
- served *without* the token. "loopback" keeps the hub to
56
- this machine, and turns that page off with it
57
- --headless serve no web UI; used when running as a remote hub
58
- --token <t> client token to use instead of generating one
59
- --open open the canvas even when not on a terminal
60
- --no-open do not open the canvas; just print the url
61
- --browser open the canvas in the browser instead of its own
62
- window. Closing that window stops the hub; closing a
63
- browser tab does not
64
-
65
- Joining another machine's canvas:
66
-
67
- --join <url> dial that hub and put this machine on its canvas
68
- --join-token <t> single-use token from its join page; not needed once
69
- this machine has enrolled once
70
- --label <name> how to name this machine there (default: hostname)
71
- `);
72
- return;
73
- }
74
- preflightAgents();
75
- mkdirSync(paths.home(), { recursive: true });
76
- const clientToken = values.token ?? mintClientToken();
77
- writeFileSync(paths.tokenFile(), clientToken, { mode: 0o600 });
78
- // The join installer reads this to stop a previous hub before replacing its
79
- // files; on Windows a loaded .node cannot be deleted while it runs.
80
- writeFileSync(paths.pidFile(), String(process.pid));
81
- const plan = listenPlan(values.listen, { headless: values.headless });
82
- const hub = new Hub();
83
- const { app, origin, lanOrigin, lanAltOrigin, enrollOrigin, enrollAltOrigin, port, peerServer, } = await serve({
84
- hub,
85
- host: plan.host,
86
- enroll: plan.enroll,
87
- // The join page has to be typed by hand on another machine, so prefer a
88
- // port worth remembering unless one was asked for explicitly.
89
- port: values.port === undefined ? MEMORABLE_PORTS : Number(values.port),
90
- clientToken,
91
- headless: values.headless,
92
- });
93
- const url = `${origin}/?token=${clientToken}`;
94
- // A remote hub is parsed by the deployer, so keep this line machine-readable.
95
- console.log(`termscape hub ${HUB_VERSION} listening on ${origin}`);
96
- console.log(`TERMSCAPE_PORT=${port}`);
97
- // Said out loud because it is easy to leave on, and because a remote
98
- // session only traces if the hub on the *other* machine was started with
99
- // it too - a half-traced path is what makes an input look lost.
100
- const traced = debugTopics();
101
- if (traced.length > 0) {
102
- console.log(` tracing: ${traced.join(', ')} (TERMSCAPE_DEBUG)`);
103
- }
104
- /*
105
- * Binding wide and answering the join page is the default now, which makes
106
- * it news rather than a confirmation - so it is said first, before the URLs,
107
- * and it says what it costs before it says what it buys. The old banner put
108
- * this last, where it read as an acknowledgement of something the operator
109
- * had just typed.
110
- *
111
- * The way out is printed on the enrolling path too, not only the other one.
112
- * It is the branch where somebody might want it.
113
- */
114
- if (!values.headless && lanOrigin) {
115
- const where = lanOrigin.replace(/^http:\/\//, '');
116
- console.log(`\n on your network at ${where}`);
117
- if (enrollOrigin) {
118
- console.log(` anyone who can reach it can load the join page and`);
119
- console.log(` attach a machine to this canvas. Everything else`);
120
- console.log(` needs the token below.`);
121
- console.log(` --listen loopback keeps the hub to this machine.`);
122
- }
123
- else {
124
- console.log(` the canvas needs the token below, so this is worth`);
125
- console.log(` opening on a phone or a second screen.`);
126
- console.log(` --listen loopback keeps the hub to this machine.`);
127
- }
128
- }
129
- if (!values.headless)
130
- console.log(`\n open: ${url}`);
131
- // The canvas is worth opening from a phone or a second screen, and that
132
- // needs the token too - so offer the whole URL, not just the host.
133
- if (!values.headless && lanOrigin) {
134
- console.log(` or ${lanOrigin}/?token=${clientToken}`);
135
- // If the name does not resolve on the other machine, the IP always will.
136
- if (lanAltOrigin)
137
- console.log(` or ${lanAltOrigin}/?token=${clientToken}`);
138
- }
139
- if (enrollOrigin) {
140
- console.log(`\n enroll: ${enrollOrigin}/join`);
141
- if (enrollAltOrigin)
142
- console.log(` or ${enrollAltOrigin}/join`);
143
- }
144
- else if (!values.headless) {
145
- // Only two ways to get here now that the default enrolls: the operator
146
- // asked for loopback, or this machine has no address anyone could reach it
147
- // on. Saying which is the difference between a setting they chose and a
148
- // network they need to go and look at.
149
- const why = values.listen === undefined
150
- ? 'this machine has no address another machine could reach it on'
151
- : 'this hub is bound to loopback';
152
- console.log(`\n no join page: ${why}`);
153
- }
154
- if (!values.headless)
155
- console.log('');
156
- // The canvas hub asks us to stop when the user drops this host, so the
157
- // machine does not keep a hub running that belongs to nobody.
158
- // A join that was refused cannot be retried into working, and this hub was
159
- // started only to join. Stopping frees its install for a re-run.
160
- hub.on('joinFailed', (message) => {
161
- console.error(`[join] cannot join: ${message}`);
162
- void shutdown('join failed');
163
- });
164
- hub.on('peerShutdown', () => {
165
- console.log('\n[peer] the canvas dropped this host; stopping');
166
- void shutdown('peer');
167
- });
168
- let link = null;
169
- if (values.join) {
170
- link = joinCanvas({
171
- hub,
172
- peerServer,
173
- hubUrl: values.join,
174
- joinToken: values['join-token'],
175
- label: values.label,
176
- });
177
- }
178
- let shuttingDown = false;
179
- const shutdown = async (signal) => {
180
- if (shuttingDown)
181
- return;
182
- shuttingDown = true;
183
- console.log(`\n[${signal}] saving state...`);
184
- // Snapshots are forced here: this is the write that makes a clean restart
185
- // come back with the right screens.
186
- try {
187
- link?.stop();
188
- hub.shutdown();
189
- }
190
- catch (err) {
191
- // Stopping is the one thing this must not fail at. A throw here used to
192
- // skip the exit below and leave the hub up with nothing to stop it.
193
- console.error('[shutdown]', err instanceof Error ? err.message : err);
194
- }
195
- try {
196
- rmSync(paths.pidFile(), { force: true });
197
- }
198
- catch {
199
- // Best effort; a stale pid file is handled by whoever reads it.
200
- }
201
- /*
202
- * Closing the server is given a deadline rather than waited out. It waits
203
- * for every websocket to finish its close handshake, and a client that
204
- * never answers - a tab on a laptop that went to sleep, a machine on the
205
- * other end of a dead link, anything that opened a socket and went quiet -
206
- * held it open indefinitely. The canvas window had gone, the terminal was
207
- * still held, and only Ctrl+C gave it back. State is already saved by now;
208
- * all that is left to lose is a goodbye nobody is listening for.
209
- */
210
- await Promise.race([
211
- app.close().catch(() => {
212
- // Server already down.
213
- }),
214
- new Promise((resolve) => setTimeout(resolve, SERVER_CLOSE_DEADLINE_MS).unref()),
215
- ]);
216
- // node-pty on ConPTY keeps handles that can hold the loop open past close.
217
- process.exit(0);
218
- };
219
- for (const sig of ['SIGINT', 'SIGTERM']) {
220
- process.on(sig, () => void shutdown(sig));
221
- }
222
- process.on('uncaughtException', (err) => {
223
- console.error('[fatal]', err);
224
- void shutdown('uncaughtException');
225
- });
226
- // Node escalates an unhandled rejection to uncaughtException, which above
227
- // stops the hub. A peer refusing a request is a normal thing that must not
228
- // cost the user every agent running on this machine, so it is logged and
229
- // survived instead. Call sites still catch their own; this is the net.
230
- process.on('unhandledRejection', (reason) => {
231
- console.error('[unhandled]', reason instanceof Error ? reason.message : reason);
232
- });
233
- /*
234
- * Opening the canvas is the default, not a flag.
235
- *
236
- * The URL carries a token, so it is long and cannot be retyped - leaving
237
- * the user to copy it out of scrollback is most of the friction in a
238
- * one-command install. A headless hub has no UI to open, and a hub started
239
- * by a script or a supervisor has nobody watching, which is what the TTY
240
- * check stands in for. --open overrides that check; --no-open beats both.
241
- *
242
- * What opens is a window of the canvas's own, which is the app: closing it
243
- * stops the hub, as Ctrl+C would. --browser asks for a tab instead, and a
244
- * machine with no webview to show gets one anyway rather than nothing.
245
- * Opened after `shutdown` exists, since closing the window calls it.
246
- */
247
- const wantsCanvas = !values.headless &&
248
- !values['no-open'] &&
249
- (values.open || process.stdout.isTTY === true);
250
- if (wantsCanvas && values.browser) {
251
- openInBrowser(url);
252
- }
253
- else if (wantsCanvas) {
254
- openAppWindow(url, {
255
- onClosed: () => void shutdown('window closed'),
256
- onUnavailable: () => {
257
- console.error('[app] no native window here; opening the browser instead');
258
- openInBrowser(url);
259
- },
260
- onCrashed: (why) => {
261
- console.error(`[app] the window exited (${why}); the hub is still running at ${url}`);
262
- },
263
- });
264
- }
20
+ const { main } = await import('./main.js');
21
+ await main(values, cliPath);
265
22
  }
266
- main().catch((err) => {
23
+ run().catch((err) => {
267
24
  console.error(err);
268
25
  process.exit(1);
269
26
  });
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE7D;;;;;;GAMG;AACH,KAAK,UAAU,GAAG;IAChB,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IACrE,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/C,IAAI,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;QACrC,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC;QACzB,OAAO;IACT,CAAC;IACD,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC;IAC3C,MAAM,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAC9B,CAAC;AAED,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IAClB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
package/dist/hub.d.ts CHANGED
@@ -12,12 +12,13 @@ import { SessionManager } from './session/manager.js';
12
12
  import type { AgentApi } from './mcp/server.js';
13
13
  import { PeerRegistry } from './remote/registry.js';
14
14
  import { type Uplink } from './remote/peer-serve.js';
15
+ import type { Updater } from './update/updater.js';
15
16
  /** One template as list_templates shows it: described, with its env named but not given. */
16
17
  type TemplateView = Omit<AgentTemplateInfo, 'env' | 'error'> & {
17
18
  envNames: string[];
18
19
  error: string | null;
19
20
  };
20
- export declare const HUB_VERSION = "0.1.9";
21
+ export declare const HUB_VERSION = "0.2.1";
21
22
  /** How many agents one workspace may hold, bounding runaway recursive spawns. */
22
23
  export declare const DEFAULT_SPAWN_CAP = 12;
23
24
  export interface HubOptions {
@@ -48,6 +49,12 @@ export declare class Hub extends EventEmitter implements AgentApi {
48
49
  readonly sessions: SessionManager;
49
50
  readonly router: MessageRouter;
50
51
  readonly peers: PeerRegistry;
52
+ /**
53
+ * Newer releases and installing them. Set by the command line once it knows
54
+ * how this hub was installed; null in tests and on a joined machine, which
55
+ * takes its canvas's build instead.
56
+ */
57
+ updater: Updater | null;
51
58
  /**
52
59
  * The link back to the canvas this hub was attached to, if it was. Null on a
53
60
  * hub that owns a canvas — which is what makes the difference between the
@@ -304,6 +311,15 @@ export declare class Hub extends EventEmitter implements AgentApi {
304
311
  * fails on a permission error rather than replacing the files.
305
312
  */
306
313
  removeHost(hostId: string): Promise<void>;
314
+ /**
315
+ * Bring a host to this hub's version.
316
+ *
317
+ * An ssh host is redeployed: its hub is stopped first, because a deploy
318
+ * reuses a hub it finds running, and then `connectHost` finds the version
319
+ * wrong and installs this one. An enrolled host is told to fetch this hub's
320
+ * build and restart itself; it comes back on its own, at the new version.
321
+ */
322
+ upgradeHost(hostId: string): Promise<void>;
307
323
  /**
308
324
  * Deploy the hub to a host if needed, tunnel to it, and join it to the
309
325
  * directory. The peer token is minted per connection and never written to
@@ -406,6 +422,17 @@ export declare class Hub extends EventEmitter implements AgentApi {
406
422
  * did at start.
407
423
  */
408
424
  clearSession(sessionId: string): Promise<Session>;
425
+ /**
426
+ * Write down which agents on this machine are running, ahead of a restart
427
+ * for an update, so the hub that comes up next can resume them.
428
+ */
429
+ rememberRunning(): void;
430
+ /**
431
+ * Resume what the hub before this one was running when it went down for an
432
+ * update. One agent that will not come back is reported and skipped, not a
433
+ * reason to leave the others stopped.
434
+ */
435
+ resumeRemembered(): Promise<string[]>;
409
436
  resumeWorkspace(workspaceId: string): Promise<Session[]>;
410
437
  moveWindow(sessionId: string, rect: WindowRect): void;
411
438
  setViewport(v: Viewport): void;