@kdonev/termscape 0.1.9 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -0
- package/dist/cli-args.d.ts +10 -0
- package/dist/cli-args.d.ts.map +1 -1
- package/dist/cli-args.js +2 -0
- package/dist/cli-args.js.map +1 -1
- package/dist/cli.js +15 -258
- package/dist/cli.js.map +1 -1
- package/dist/hub.d.ts +28 -1
- package/dist/hub.d.ts.map +1 -1
- package/dist/hub.js +65 -2
- package/dist/hub.js.map +1 -1
- package/dist/hub.tgz +0 -0
- package/dist/main.d.ts +7 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +343 -0
- package/dist/main.js.map +1 -0
- package/dist/protocol/domain.d.ts +35 -0
- package/dist/protocol/domain.d.ts.map +1 -1
- package/dist/protocol/domain.js +31 -2
- package/dist/protocol/domain.js.map +1 -1
- package/dist/protocol/index.d.ts +1 -0
- package/dist/protocol/index.d.ts.map +1 -1
- package/dist/protocol/index.js +1 -0
- package/dist/protocol/index.js.map +1 -1
- package/dist/protocol/peer.d.ts +12 -0
- package/dist/protocol/peer.d.ts.map +1 -1
- package/dist/protocol/peer.js +30 -1
- package/dist/protocol/peer.js.map +1 -1
- package/dist/protocol/version.d.ts +12 -0
- package/dist/protocol/version.d.ts.map +1 -0
- package/dist/protocol/version.js +37 -0
- package/dist/protocol/version.js.map +1 -0
- package/dist/protocol/ws.d.ts +67 -1
- package/dist/protocol/ws.d.ts.map +1 -1
- package/dist/protocol/ws.js +11 -1
- package/dist/protocol/ws.js.map +1 -1
- package/dist/remote/enroll.d.ts.map +1 -1
- package/dist/remote/enroll.js +22 -1
- package/dist/remote/enroll.js.map +1 -1
- package/dist/remote/join.d.ts.map +1 -1
- package/dist/remote/join.js +10 -1
- package/dist/remote/join.js.map +1 -1
- package/dist/remote/peer-serve.d.ts.map +1 -1
- package/dist/remote/peer-serve.js +14 -0
- package/dist/remote/peer-serve.js.map +1 -1
- package/dist/remote/peer.d.ts +7 -0
- package/dist/remote/peer.d.ts.map +1 -1
- package/dist/remote/peer.js +25 -1
- package/dist/remote/peer.js.map +1 -1
- package/dist/remote/registry.d.ts +24 -2
- package/dist/remote/registry.d.ts.map +1 -1
- package/dist/remote/registry.js +76 -5
- package/dist/remote/registry.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +11 -1
- package/dist/server.js.map +1 -1
- package/dist/supervisor.d.ts +44 -0
- package/dist/supervisor.d.ts.map +1 -0
- package/dist/supervisor.js +125 -0
- package/dist/supervisor.js.map +1 -0
- package/dist/update/host.d.ts +19 -0
- package/dist/update/host.d.ts.map +1 -0
- package/dist/update/host.js +56 -0
- package/dist/update/host.js.map +1 -0
- package/dist/update/install.d.ts +38 -0
- package/dist/update/install.d.ts.map +1 -0
- package/dist/update/install.js +81 -0
- package/dist/update/install.js.map +1 -0
- package/dist/update/post-install.d.ts +2 -0
- package/dist/update/post-install.d.ts.map +1 -0
- package/dist/update/post-install.js +39 -0
- package/dist/update/post-install.js.map +1 -0
- package/dist/update/restart.d.ts +31 -0
- package/dist/update/restart.d.ts.map +1 -0
- package/dist/update/restart.js +65 -0
- package/dist/update/restart.js.map +1 -0
- package/dist/update/resume.d.ts +5 -0
- package/dist/update/resume.d.ts.map +1 -0
- package/dist/update/resume.js +47 -0
- package/dist/update/resume.js.map +1 -0
- package/dist/update/updater.d.ts +57 -0
- package/dist/update/updater.d.ts.map +1 -0
- package/dist/update/updater.js +190 -0
- package/dist/update/updater.js.map +1 -0
- package/package.json +1 -1
- package/web/assets/{index-i7fXLKF0.js → index-BoqmaIyh.js} +9 -9
- package/web/assets/index-Czi_vAR-.css +1 -0
- package/web/index.html +2 -2
- package/web/assets/index-gfbbNQK4.css +0 -1
package/README.md
CHANGED
|
@@ -411,6 +411,31 @@ Two consequences worth knowing:
|
|
|
411
411
|
- Resume relaunches in the **same working directory**, because that is how
|
|
412
412
|
Claude Code finds a conversation. Move the folder and resume starts fresh.
|
|
413
413
|
|
|
414
|
+
## Updating
|
|
415
|
+
|
|
416
|
+
The hub asks npm for a newer release shortly after it starts and every six
|
|
417
|
+
hours after that. When there is one, an **update** button appears next to the
|
|
418
|
+
version in the top bar. Nothing restarts until you press it. Then the hub
|
|
419
|
+
downloads the release, restarts on the same address in the same terminal,
|
|
420
|
+
and the canvas reconnects on its own. The agents that were running stop with
|
|
421
|
+
the old hub and are resumed by the new one, continuing their conversations.
|
|
422
|
+
(Stopping the hub yourself is different: those agents come back stopped, as
|
|
423
|
+
described under [State and restart](#state-and-restart).)
|
|
424
|
+
|
|
425
|
+
This works for `npx @kdonev/termscape` and for a global `npm install -g`. A
|
|
426
|
+
checkout of this repository updates the way checkouts do, so for one of those
|
|
427
|
+
the button only links to the release. `--no-update-check` (or
|
|
428
|
+
`TERMSCAPE_NO_UPDATE_CHECK=1`) turns the check off.
|
|
429
|
+
|
|
430
|
+
Other machines take the canvas's own build, not npm's. When one runs a
|
|
431
|
+
different version from the canvas, its row in the machines panel gets an
|
|
432
|
+
**update** button, so you pick when each machine restarts. A deployed machine
|
|
433
|
+
is redeployed over SSH. A joined one runs the join installer again by itself
|
|
434
|
+
(its output is in `~/.termscape/update.log` there) and rejoins as the same
|
|
435
|
+
machine. Either way, the agents that were running there are resumed. A joined machine one protocol version behind the canvas stays on the
|
|
436
|
+
canvas as *outdated* until you update it. Machines joined with 0.1.9 or
|
|
437
|
+
earlier can't update themselves yet, so re-run the join command on those once.
|
|
438
|
+
|
|
414
439
|
## Remote machines
|
|
415
440
|
|
|
416
441
|
Add a machine in the panel. The hub is copied over SSH, installed, and started as a
|
package/dist/cli-args.d.ts
CHANGED
|
@@ -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
|
package/dist/cli-args.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli-args.d.ts","sourceRoot":"","sources":["../src/cli-args.ts"],"names":[],"mappings":"
|
|
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
|
};
|
package/dist/cli-args.js.map
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
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
|
|
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
|
-
|
|
44
|
-
|
|
15
|
+
const cliPath = fileURLToPath(import.meta.url);
|
|
16
|
+
if (shouldSupervise(values, cliPath)) {
|
|
17
|
+
await supervise(cliPath);
|
|
45
18
|
return;
|
|
46
19
|
}
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,
|
|
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.
|
|
21
|
+
export declare const HUB_VERSION = "0.2.0";
|
|
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;
|
package/dist/hub.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hub.d.ts","sourceRoot":"","sources":["../src/hub.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAOL,KAAK,iBAAiB,EACtB,KAAK,QAAQ,EAEb,KAAK,OAAO,EACZ,KAAK,IAAI,EACT,KAAK,IAAI,EAGT,KAAK,OAAO,EACZ,KAAK,gBAAgB,EACrB,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,SAAS,EACf,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAU,KAAK,EAAE,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EAA8B,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EACL,gBAAgB,EAGjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGtD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAA0B,KAAK,MAAM,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"hub.d.ts","sourceRoot":"","sources":["../src/hub.ts"],"names":[],"mappings":"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"}
|
package/dist/hub.js
CHANGED
|
@@ -21,7 +21,8 @@ import { hubTarballPath } from './remote/tarball.js';
|
|
|
21
21
|
import { debug } from './debug.js';
|
|
22
22
|
import { paths } from './paths.js';
|
|
23
23
|
import { checkFolder, folderName } from './folders.js';
|
|
24
|
-
|
|
24
|
+
import { saveResumeList, takeResumeList } from './update/resume.js';
|
|
25
|
+
export const HUB_VERSION = '0.2.0';
|
|
25
26
|
/**
|
|
26
27
|
* How long changes are pooled before every attached machine is told the
|
|
27
28
|
* canvas has moved on. A status change is a session change, and agents change
|
|
@@ -90,6 +91,12 @@ export class Hub extends EventEmitter {
|
|
|
90
91
|
sessions;
|
|
91
92
|
router;
|
|
92
93
|
peers;
|
|
94
|
+
/**
|
|
95
|
+
* Newer releases and installing them. Set by the command line once it knows
|
|
96
|
+
* how this hub was installed; null in tests and on a joined machine, which
|
|
97
|
+
* takes its canvas's build instead.
|
|
98
|
+
*/
|
|
99
|
+
updater = null;
|
|
93
100
|
/**
|
|
94
101
|
* The link back to the canvas this hub was attached to, if it was. Null on a
|
|
95
102
|
* hub that owns a canvas — which is what makes the difference between the
|
|
@@ -157,7 +164,7 @@ export class Hub extends EventEmitter {
|
|
|
157
164
|
this.sessions.visiblePeers = (s) => this.everyAgent()
|
|
158
165
|
.filter((o) => o.address !== s.address && canSee(s, o))
|
|
159
166
|
.map((o) => o.address);
|
|
160
|
-
this.peers.on('host', (h) => this.emit('host', h));
|
|
167
|
+
this.peers.on('host', (h) => this.emit('host', this.peers.decorate(h)));
|
|
161
168
|
this.peers.on('peerAgents', (hostId, found) => this.emit('hostAgents', hostId, found));
|
|
162
169
|
this.peers.on('peerSession', (s) => this.emit('session', s));
|
|
163
170
|
this.peers.on('peerSessionRemoved', (addr) => this.emit('removed', addr, addr));
|
|
@@ -745,6 +752,30 @@ export class Hub extends EventEmitter {
|
|
|
745
752
|
this.store.removeHost(hostId);
|
|
746
753
|
this.emit('hostRemoved', hostId);
|
|
747
754
|
}
|
|
755
|
+
/**
|
|
756
|
+
* Bring a host to this hub's version.
|
|
757
|
+
*
|
|
758
|
+
* An ssh host is redeployed: its hub is stopped first, because a deploy
|
|
759
|
+
* reuses a hub it finds running, and then `connectHost` finds the version
|
|
760
|
+
* wrong and installs this one. An enrolled host is told to fetch this hub's
|
|
761
|
+
* build and restart itself; it comes back on its own, at the new version.
|
|
762
|
+
*/
|
|
763
|
+
async upgradeHost(hostId) {
|
|
764
|
+
const host = this.store.listHosts().find((h) => h.id === hostId);
|
|
765
|
+
if (!host)
|
|
766
|
+
throw new Error(`unknown host ${hostId}`);
|
|
767
|
+
if (host.kind === 'ssh') {
|
|
768
|
+
// Asked to remember its running agents on the way down: the hub the
|
|
769
|
+
// redeploy starts resumes them.
|
|
770
|
+
await this.peers.requestShutdown(hostId, { resume: true }).catch(() => false);
|
|
771
|
+
this.peers.remove(hostId);
|
|
772
|
+
await this.tunnels.get(hostId)?.dispose().catch(() => { });
|
|
773
|
+
this.tunnels.delete(hostId);
|
|
774
|
+
await this.connectHost(hostId);
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
await this.peers.requestUpdate(hostId);
|
|
778
|
+
}
|
|
748
779
|
/**
|
|
749
780
|
* Deploy the hub to a host if needed, tunnel to it, and join it to the
|
|
750
781
|
* directory. The peer token is minted per connection and never written to
|
|
@@ -965,6 +996,38 @@ export class Hub extends EventEmitter {
|
|
|
965
996
|
this.noteOpening(session.id, opening || null);
|
|
966
997
|
return session;
|
|
967
998
|
}
|
|
999
|
+
/**
|
|
1000
|
+
* Write down which agents on this machine are running, ahead of a restart
|
|
1001
|
+
* for an update, so the hub that comes up next can resume them.
|
|
1002
|
+
*/
|
|
1003
|
+
rememberRunning() {
|
|
1004
|
+
const running = this.sessions
|
|
1005
|
+
.list()
|
|
1006
|
+
.filter((s) => s.state === 'running' || s.state === 'starting')
|
|
1007
|
+
.map((s) => s.id);
|
|
1008
|
+
saveResumeList(running);
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* Resume what the hub before this one was running when it went down for an
|
|
1012
|
+
* update. One agent that will not come back is reported and skipped, not a
|
|
1013
|
+
* reason to leave the others stopped.
|
|
1014
|
+
*/
|
|
1015
|
+
async resumeRemembered() {
|
|
1016
|
+
const resumed = [];
|
|
1017
|
+
for (const id of takeResumeList()) {
|
|
1018
|
+
const s = this.sessions.get(id);
|
|
1019
|
+
if (!s || s.state === 'running' || s.state === 'starting')
|
|
1020
|
+
continue;
|
|
1021
|
+
try {
|
|
1022
|
+
await this.resumeSession(id);
|
|
1023
|
+
resumed.push(s.address);
|
|
1024
|
+
}
|
|
1025
|
+
catch (err) {
|
|
1026
|
+
this.emit('error', new Error(`resume ${s.address}: ${err.message}`));
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
return resumed;
|
|
1030
|
+
}
|
|
968
1031
|
async resumeWorkspace(workspaceId) {
|
|
969
1032
|
const out = [];
|
|
970
1033
|
for (const s of this.sessions.list()) {
|