@kdonev/termscape 0.1.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/LICENSE +21 -0
- package/README.md +288 -0
- package/dist/agents/env.d.ts +36 -0
- package/dist/agents/env.d.ts.map +1 -0
- package/dist/agents/env.js +61 -0
- package/dist/agents/env.js.map +1 -0
- package/dist/agents/profiles.d.ts +45 -0
- package/dist/agents/profiles.d.ts.map +1 -0
- package/dist/agents/profiles.js +123 -0
- package/dist/agents/profiles.js.map +1 -0
- package/dist/agents/resolve.d.ts +16 -0
- package/dist/agents/resolve.d.ts.map +1 -0
- package/dist/agents/resolve.js +71 -0
- package/dist/agents/resolve.js.map +1 -0
- package/dist/agents/router.d.ts +29 -0
- package/dist/agents/router.d.ts.map +1 -0
- package/dist/agents/router.js +83 -0
- package/dist/agents/router.js.map +1 -0
- package/dist/agents/tokens.d.ts +21 -0
- package/dist/agents/tokens.d.ts.map +1 -0
- package/dist/agents/tokens.js +39 -0
- package/dist/agents/tokens.js.map +1 -0
- package/dist/agents/wiring.d.ts +30 -0
- package/dist/agents/wiring.d.ts.map +1 -0
- package/dist/agents/wiring.js +110 -0
- package/dist/agents/wiring.js.map +1 -0
- package/dist/browser.d.ts +13 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +34 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +200 -0
- package/dist/cli.js.map +1 -0
- package/dist/db/index.d.ts +14 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +24 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/migrations.d.ts +15 -0
- package/dist/db/migrations.d.ts.map +1 -0
- package/dist/db/migrations.js +176 -0
- package/dist/db/migrations.js.map +1 -0
- package/dist/db/store.d.ts +71 -0
- package/dist/db/store.d.ts.map +1 -0
- package/dist/db/store.js +341 -0
- package/dist/db/store.js.map +1 -0
- package/dist/hub.d.ts +188 -0
- package/dist/hub.d.ts.map +1 -0
- package/dist/hub.js +636 -0
- package/dist/hub.js.map +1 -0
- package/dist/hub.tgz +0 -0
- package/dist/mcp/server.d.ts +31 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +66 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/paths.d.ts +27 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +31 -0
- package/dist/paths.js.map +1 -0
- package/dist/protocol/address.d.ts +15 -0
- package/dist/protocol/address.d.ts.map +1 -0
- package/dist/protocol/address.js +39 -0
- package/dist/protocol/address.js.map +1 -0
- package/dist/protocol/domain.d.ts +151 -0
- package/dist/protocol/domain.d.ts.map +1 -0
- package/dist/protocol/domain.js +104 -0
- package/dist/protocol/domain.js.map +1 -0
- package/dist/protocol/index.d.ts +7 -0
- package/dist/protocol/index.d.ts.map +1 -0
- package/dist/protocol/index.js +7 -0
- package/dist/protocol/index.js.map +1 -0
- package/dist/protocol/inject.d.ts +29 -0
- package/dist/protocol/inject.d.ts.map +1 -0
- package/dist/protocol/inject.js +40 -0
- package/dist/protocol/inject.js.map +1 -0
- package/dist/protocol/mcp-tools.d.ts +45 -0
- package/dist/protocol/mcp-tools.d.ts.map +1 -0
- package/dist/protocol/mcp-tools.js +46 -0
- package/dist/protocol/mcp-tools.js.map +1 -0
- package/dist/protocol/peer.d.ts +304 -0
- package/dist/protocol/peer.d.ts.map +1 -0
- package/dist/protocol/peer.js +183 -0
- package/dist/protocol/peer.js.map +1 -0
- package/dist/protocol/ws.d.ts +416 -0
- package/dist/protocol/ws.d.ts.map +1 -0
- package/dist/protocol/ws.js +119 -0
- package/dist/protocol/ws.js.map +1 -0
- package/dist/remote/deployer.d.ts +78 -0
- package/dist/remote/deployer.d.ts.map +1 -0
- package/dist/remote/deployer.js +216 -0
- package/dist/remote/deployer.js.map +1 -0
- package/dist/remote/enroll.d.ts +14 -0
- package/dist/remote/enroll.d.ts.map +1 -0
- package/dist/remote/enroll.js +244 -0
- package/dist/remote/enroll.js.map +1 -0
- package/dist/remote/join-script.d.ts +29 -0
- package/dist/remote/join-script.d.ts.map +1 -0
- package/dist/remote/join-script.js +709 -0
- package/dist/remote/join-script.js.map +1 -0
- package/dist/remote/join.d.ts +19 -0
- package/dist/remote/join.d.ts.map +1 -0
- package/dist/remote/join.js +195 -0
- package/dist/remote/join.js.map +1 -0
- package/dist/remote/lan.d.ts +45 -0
- package/dist/remote/lan.d.ts.map +1 -0
- package/dist/remote/lan.js +105 -0
- package/dist/remote/lan.js.map +1 -0
- package/dist/remote/peer-serve.d.ts +27 -0
- package/dist/remote/peer-serve.d.ts.map +1 -0
- package/dist/remote/peer-serve.js +274 -0
- package/dist/remote/peer-serve.js.map +1 -0
- package/dist/remote/peer.d.ts +62 -0
- package/dist/remote/peer.d.ts.map +1 -0
- package/dist/remote/peer.js +250 -0
- package/dist/remote/peer.js.map +1 -0
- package/dist/remote/registry.d.ts +104 -0
- package/dist/remote/registry.d.ts.map +1 -0
- package/dist/remote/registry.js +359 -0
- package/dist/remote/registry.js.map +1 -0
- package/dist/remote/tarball.d.ts +9 -0
- package/dist/remote/tarball.d.ts.map +1 -0
- package/dist/remote/tarball.js +22 -0
- package/dist/remote/tarball.js.map +1 -0
- package/dist/server.d.ts +54 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +438 -0
- package/dist/server.js.map +1 -0
- package/dist/session/manager.d.ts +104 -0
- package/dist/session/manager.d.ts.map +1 -0
- package/dist/session/manager.js +421 -0
- package/dist/session/manager.js.map +1 -0
- package/dist/session/pty.d.ts +114 -0
- package/dist/session/pty.d.ts.map +1 -0
- package/dist/session/pty.js +300 -0
- package/dist/session/pty.js.map +1 -0
- package/package.json +49 -0
- package/web/assets/index-B2JtCtHa.css +1 -0
- package/web/assets/index-BOqZOoep.js +107 -0
- package/web/index.html +13 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
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 { resolveBindHost } from './remote/lan.js';
|
|
9
|
+
import { joinCanvas } from './remote/join.js';
|
|
10
|
+
import { openInBrowser } from './browser.js';
|
|
11
|
+
import { ProfileRegistry } from './agents/profiles.js';
|
|
12
|
+
import { which } from './agents/resolve.js';
|
|
13
|
+
/**
|
|
14
|
+
* Warn when no agent CLI is installed.
|
|
15
|
+
*
|
|
16
|
+
* Without this the first thing a new user does - start an agent - fails inside
|
|
17
|
+
* a terminal window on the canvas, where the error is easy to miss and reads
|
|
18
|
+
* like the hub is broken. Checking every profile that wires up MCP, rather
|
|
19
|
+
* than "claude" specifically, keeps a custom ~/.termscape/agents.toml counted.
|
|
20
|
+
*/
|
|
21
|
+
function preflightAgents() {
|
|
22
|
+
const agents = ProfileRegistry.load()
|
|
23
|
+
.list()
|
|
24
|
+
.filter((profile) => profile.mcp);
|
|
25
|
+
if (agents.length === 0)
|
|
26
|
+
return;
|
|
27
|
+
if (agents.some((profile) => which(profile.command)))
|
|
28
|
+
return;
|
|
29
|
+
const names = agents.map((profile) => profile.command).join(', ');
|
|
30
|
+
console.error(`[warn] no agent CLI found on PATH (looked for: ${names})`);
|
|
31
|
+
console.error(' install one - https://claude.com/claude-code - or set');
|
|
32
|
+
console.error(' an absolute "command" in ~/.termscape/agents.toml.');
|
|
33
|
+
console.error(' Terminals with the "shell" profile still work.');
|
|
34
|
+
console.error('');
|
|
35
|
+
}
|
|
36
|
+
async function main() {
|
|
37
|
+
const { values } = parseArgs({
|
|
38
|
+
options: {
|
|
39
|
+
port: { type: 'string' },
|
|
40
|
+
headless: { type: 'boolean', default: false },
|
|
41
|
+
token: { type: 'string' },
|
|
42
|
+
listen: { type: 'string' },
|
|
43
|
+
join: { type: 'string' },
|
|
44
|
+
'join-token': { type: 'string' },
|
|
45
|
+
label: { type: 'string' },
|
|
46
|
+
open: { type: 'boolean', default: false },
|
|
47
|
+
// parseArgs has no --no-x negation, so the opt-out is its own flag.
|
|
48
|
+
'no-open': { type: 'boolean', default: false },
|
|
49
|
+
version: { type: 'boolean', default: false },
|
|
50
|
+
help: { type: 'boolean', default: false },
|
|
51
|
+
},
|
|
52
|
+
strict: true,
|
|
53
|
+
});
|
|
54
|
+
if (values.version) {
|
|
55
|
+
console.log(HUB_VERSION);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (values.help) {
|
|
59
|
+
console.log(`termscape ${HUB_VERSION}
|
|
60
|
+
|
|
61
|
+
--port <n> port to bind; 0 lets the OS pick. Default: the first
|
|
62
|
+
free memorable port (7777, 4242, ...)
|
|
63
|
+
--listen <addr> bind address; "lan" picks this machine's own address so
|
|
64
|
+
other machines can open the join page. Default: loopback
|
|
65
|
+
--headless serve no web UI; used when running as a remote hub
|
|
66
|
+
--token <t> client token to use instead of generating one
|
|
67
|
+
--open open the UI in the browser even when not on a terminal
|
|
68
|
+
--no-open do not open the browser; just print the url
|
|
69
|
+
|
|
70
|
+
Joining another machine's canvas:
|
|
71
|
+
|
|
72
|
+
--join <url> dial that hub and put this machine on its canvas
|
|
73
|
+
--join-token <t> single-use token from its join page; not needed once
|
|
74
|
+
this machine has enrolled once
|
|
75
|
+
--label <name> how to name this machine there (default: hostname)
|
|
76
|
+
`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
preflightAgents();
|
|
80
|
+
mkdirSync(paths.home(), { recursive: true });
|
|
81
|
+
const clientToken = values.token ?? mintClientToken();
|
|
82
|
+
writeFileSync(paths.tokenFile(), clientToken, { mode: 0o600 });
|
|
83
|
+
// The join installer reads this to stop a previous hub before replacing its
|
|
84
|
+
// files; on Windows a loaded .node cannot be deleted while it runs.
|
|
85
|
+
writeFileSync(paths.pidFile(), String(process.pid));
|
|
86
|
+
const bindHost = values.listen ? resolveBindHost(values.listen) : undefined;
|
|
87
|
+
const hub = new Hub();
|
|
88
|
+
const { app, origin, enrollOrigin, enrollAltOrigin, port, peerServer } = await serve({
|
|
89
|
+
hub,
|
|
90
|
+
host: bindHost,
|
|
91
|
+
// The join page has to be typed by hand on another machine, so prefer a
|
|
92
|
+
// port worth remembering unless one was asked for explicitly.
|
|
93
|
+
port: values.port === undefined ? MEMORABLE_PORTS : Number(values.port),
|
|
94
|
+
clientToken,
|
|
95
|
+
headless: values.headless,
|
|
96
|
+
});
|
|
97
|
+
const url = `${origin}/?token=${clientToken}`;
|
|
98
|
+
// A remote hub is parsed by the deployer, so keep this line machine-readable.
|
|
99
|
+
console.log(`termscape hub ${HUB_VERSION} listening on ${origin}`);
|
|
100
|
+
console.log(`TERMSCAPE_PORT=${port}`);
|
|
101
|
+
if (!values.headless)
|
|
102
|
+
console.log(`\n open: ${url}`);
|
|
103
|
+
// The canvas is worth opening from a phone or a second screen, and that
|
|
104
|
+
// needs the token too - so offer the whole URL, not just the host.
|
|
105
|
+
if (!values.headless && enrollOrigin) {
|
|
106
|
+
console.log(` or ${enrollOrigin}/?token=${clientToken}`);
|
|
107
|
+
}
|
|
108
|
+
if (enrollOrigin) {
|
|
109
|
+
// Say plainly what binding wider actually did. Every other route still
|
|
110
|
+
// requires the token, but the hub is on the network either way now.
|
|
111
|
+
console.log(` enroll: ${enrollOrigin}/join`);
|
|
112
|
+
// If the name does not resolve on the other machine, the IP always will.
|
|
113
|
+
if (enrollAltOrigin)
|
|
114
|
+
console.log(` or ${enrollAltOrigin}/join`);
|
|
115
|
+
console.log(` reachable from your network — anyone who can load`);
|
|
116
|
+
console.log(` that page can attach a machine to this canvas`);
|
|
117
|
+
}
|
|
118
|
+
if (!values.headless)
|
|
119
|
+
console.log('');
|
|
120
|
+
/*
|
|
121
|
+
* Opening the canvas is the default, not a flag.
|
|
122
|
+
*
|
|
123
|
+
* The URL carries a token, so it is long and cannot be retyped - leaving
|
|
124
|
+
* the user to copy it out of scrollback is most of the friction in a
|
|
125
|
+
* one-command install. A headless hub has no UI to open, and a hub started
|
|
126
|
+
* by a script or a supervisor has nobody watching, which is what the TTY
|
|
127
|
+
* check stands in for. --open overrides that check; --no-open beats both.
|
|
128
|
+
*/
|
|
129
|
+
const wantsBrowser = !values.headless &&
|
|
130
|
+
!values['no-open'] &&
|
|
131
|
+
(values.open || process.stdout.isTTY === true);
|
|
132
|
+
if (wantsBrowser)
|
|
133
|
+
openInBrowser(url);
|
|
134
|
+
// The canvas hub asks us to stop when the user drops this host, so the
|
|
135
|
+
// machine does not keep a hub running that belongs to nobody.
|
|
136
|
+
// A join that was refused cannot be retried into working, and this hub was
|
|
137
|
+
// started only to join. Stopping frees its install for a re-run.
|
|
138
|
+
hub.on('joinFailed', (message) => {
|
|
139
|
+
console.error(`[join] cannot join: ${message}`);
|
|
140
|
+
void shutdown('join failed');
|
|
141
|
+
});
|
|
142
|
+
hub.on('peerShutdown', () => {
|
|
143
|
+
console.log('\n[peer] the canvas dropped this host; stopping');
|
|
144
|
+
void shutdown('peer');
|
|
145
|
+
});
|
|
146
|
+
let link = null;
|
|
147
|
+
if (values.join) {
|
|
148
|
+
link = joinCanvas({
|
|
149
|
+
hub,
|
|
150
|
+
peerServer,
|
|
151
|
+
hubUrl: values.join,
|
|
152
|
+
joinToken: values['join-token'],
|
|
153
|
+
label: values.label,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
let shuttingDown = false;
|
|
157
|
+
const shutdown = async (signal) => {
|
|
158
|
+
if (shuttingDown)
|
|
159
|
+
return;
|
|
160
|
+
shuttingDown = true;
|
|
161
|
+
console.log(`\n[${signal}] saving state...`);
|
|
162
|
+
// Snapshots are forced here: this is the write that makes a clean restart
|
|
163
|
+
// come back with the right screens.
|
|
164
|
+
link?.stop();
|
|
165
|
+
hub.shutdown();
|
|
166
|
+
try {
|
|
167
|
+
rmSync(paths.pidFile(), { force: true });
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
// Best effort; a stale pid file is handled by whoever reads it.
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
await app.close();
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
// Server already down.
|
|
177
|
+
}
|
|
178
|
+
// node-pty on ConPTY keeps handles that can hold the loop open past close.
|
|
179
|
+
process.exit(0);
|
|
180
|
+
};
|
|
181
|
+
for (const sig of ['SIGINT', 'SIGTERM']) {
|
|
182
|
+
process.on(sig, () => void shutdown(sig));
|
|
183
|
+
}
|
|
184
|
+
process.on('uncaughtException', (err) => {
|
|
185
|
+
console.error('[fatal]', err);
|
|
186
|
+
void shutdown('uncaughtException');
|
|
187
|
+
});
|
|
188
|
+
// Node escalates an unhandled rejection to uncaughtException, which above
|
|
189
|
+
// stops the hub. A peer refusing a request is a normal thing that must not
|
|
190
|
+
// cost the user every agent running on this machine, so it is logged and
|
|
191
|
+
// survived instead. Call sites still catch their own; this is the net.
|
|
192
|
+
process.on('unhandledRejection', (reason) => {
|
|
193
|
+
console.error('[unhandled]', reason instanceof Error ? reason.message : reason);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
main().catch((err) => {
|
|
197
|
+
console.error(err);
|
|
198
|
+
process.exit(1);
|
|
199
|
+
});
|
|
200
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import { LATEST_VERSION, runMigrations } from './migrations.js';
|
|
3
|
+
export type Db = Database.Database;
|
|
4
|
+
/**
|
|
5
|
+
* Open (creating if needed) the hub database.
|
|
6
|
+
*
|
|
7
|
+
* WAL + synchronous=NORMAL is the right trade here: we accept losing the last
|
|
8
|
+
* few milliseconds of writes on power loss, in exchange for snapshot writes
|
|
9
|
+
* that never stall the PTY read loop. A hard kill still leaves a recoverable
|
|
10
|
+
* database, which the restore path depends on.
|
|
11
|
+
*/
|
|
12
|
+
export declare function openDb(path: string): Db;
|
|
13
|
+
export { LATEST_VERSION, runMigrations };
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/db/index.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAGtC,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhE,MAAM,MAAM,EAAE,GAAG,QAAQ,CAAC,QAAQ,CAAC;AAEnC;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,EAAE,CAQvC;AAED,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,CAAC"}
|
package/dist/db/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import { mkdirSync } from 'node:fs';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
import { LATEST_VERSION, runMigrations } from './migrations.js';
|
|
5
|
+
/**
|
|
6
|
+
* Open (creating if needed) the hub database.
|
|
7
|
+
*
|
|
8
|
+
* WAL + synchronous=NORMAL is the right trade here: we accept losing the last
|
|
9
|
+
* few milliseconds of writes on power loss, in exchange for snapshot writes
|
|
10
|
+
* that never stall the PTY read loop. A hard kill still leaves a recoverable
|
|
11
|
+
* database, which the restore path depends on.
|
|
12
|
+
*/
|
|
13
|
+
export function openDb(path) {
|
|
14
|
+
if (path !== ':memory:')
|
|
15
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
16
|
+
const db = new Database(path);
|
|
17
|
+
db.pragma('journal_mode = WAL');
|
|
18
|
+
db.pragma('synchronous = NORMAL');
|
|
19
|
+
db.pragma('foreign_keys = ON');
|
|
20
|
+
runMigrations(db);
|
|
21
|
+
return db;
|
|
22
|
+
}
|
|
23
|
+
export { LATEST_VERSION, runMigrations };
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/db/index.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAIhE;;;;;;;GAOG;AACH,MAAM,UAAU,MAAM,CAAC,IAAY;IACjC,IAAI,IAAI,KAAK,UAAU;QAAE,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvE,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC9B,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAChC,EAAE,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC;IAClC,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAC/B,aAAa,CAAC,EAAE,CAAC,CAAC;IAClB,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Database } from 'better-sqlite3';
|
|
2
|
+
/**
|
|
3
|
+
* Numbered, append-only migrations. Each runs once, in order, inside a
|
|
4
|
+
* transaction; `schema_meta` records the high-water mark. Never edit a
|
|
5
|
+
* migration that has shipped — add a new one.
|
|
6
|
+
*/
|
|
7
|
+
export interface Migration {
|
|
8
|
+
version: number;
|
|
9
|
+
name: string;
|
|
10
|
+
up: string;
|
|
11
|
+
}
|
|
12
|
+
export declare const MIGRATIONS: Migration[];
|
|
13
|
+
export declare function runMigrations(db: Database): number;
|
|
14
|
+
export declare const LATEST_VERSION: number;
|
|
15
|
+
//# sourceMappingURL=migrations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../src/db/migrations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,eAAO,MAAM,UAAU,EAAE,SAAS,EA2JjC,CAAC;AAEF,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,GAAG,MAAM,CA+BlD;AAED,eAAO,MAAM,cAAc,QAG1B,CAAC"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
export const MIGRATIONS = [
|
|
2
|
+
{
|
|
3
|
+
version: 1,
|
|
4
|
+
name: 'initial',
|
|
5
|
+
up: `
|
|
6
|
+
CREATE TABLE host (
|
|
7
|
+
id TEXT PRIMARY KEY,
|
|
8
|
+
label TEXT NOT NULL,
|
|
9
|
+
ssh_host TEXT NOT NULL,
|
|
10
|
+
ssh_user TEXT NOT NULL,
|
|
11
|
+
ssh_port INTEGER NOT NULL DEFAULT 22,
|
|
12
|
+
key_ref TEXT,
|
|
13
|
+
hub_version TEXT,
|
|
14
|
+
state TEXT NOT NULL DEFAULT 'disconnected',
|
|
15
|
+
last_seen_at INTEGER,
|
|
16
|
+
error TEXT
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
CREATE TABLE workspace (
|
|
20
|
+
id TEXT PRIMARY KEY,
|
|
21
|
+
name TEXT NOT NULL UNIQUE,
|
|
22
|
+
kind TEXT NOT NULL CHECK (kind IN ('local','remote')),
|
|
23
|
+
root_path TEXT NOT NULL,
|
|
24
|
+
host_id TEXT REFERENCES host(id) ON DELETE CASCADE,
|
|
25
|
+
color TEXT NOT NULL,
|
|
26
|
+
created_at INTEGER NOT NULL,
|
|
27
|
+
archived_at INTEGER
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
CREATE TABLE session (
|
|
31
|
+
id TEXT PRIMARY KEY,
|
|
32
|
+
workspace_id TEXT NOT NULL REFERENCES workspace(id) ON DELETE CASCADE,
|
|
33
|
+
name TEXT NOT NULL,
|
|
34
|
+
profile TEXT NOT NULL,
|
|
35
|
+
cwd TEXT NOT NULL,
|
|
36
|
+
agent_session_uuid TEXT,
|
|
37
|
+
argv_json TEXT NOT NULL,
|
|
38
|
+
env_json TEXT NOT NULL,
|
|
39
|
+
spawned_by TEXT REFERENCES session(id) ON DELETE SET NULL,
|
|
40
|
+
state TEXT NOT NULL,
|
|
41
|
+
status_text TEXT,
|
|
42
|
+
title TEXT,
|
|
43
|
+
pid INTEGER,
|
|
44
|
+
exit_code INTEGER,
|
|
45
|
+
cols INTEGER NOT NULL DEFAULT 80,
|
|
46
|
+
rows INTEGER NOT NULL DEFAULT 24,
|
|
47
|
+
created_at INTEGER NOT NULL,
|
|
48
|
+
exited_at INTEGER,
|
|
49
|
+
last_active_at INTEGER NOT NULL,
|
|
50
|
+
UNIQUE (workspace_id, name)
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
CREATE TABLE session_snapshot (
|
|
54
|
+
session_id TEXT PRIMARY KEY REFERENCES session(id) ON DELETE CASCADE,
|
|
55
|
+
serialized TEXT NOT NULL,
|
|
56
|
+
cols INTEGER NOT NULL,
|
|
57
|
+
rows INTEGER NOT NULL,
|
|
58
|
+
captured_at INTEGER NOT NULL
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
CREATE TABLE window (
|
|
62
|
+
session_id TEXT PRIMARY KEY REFERENCES session(id) ON DELETE CASCADE,
|
|
63
|
+
x REAL NOT NULL,
|
|
64
|
+
y REAL NOT NULL,
|
|
65
|
+
w REAL NOT NULL,
|
|
66
|
+
h REAL NOT NULL,
|
|
67
|
+
z INTEGER NOT NULL DEFAULT 0,
|
|
68
|
+
collapsed INTEGER NOT NULL DEFAULT 0
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
CREATE TABLE viewport (
|
|
72
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
73
|
+
pan_x REAL NOT NULL,
|
|
74
|
+
pan_y REAL NOT NULL,
|
|
75
|
+
zoom REAL NOT NULL
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
CREATE TABLE message (
|
|
79
|
+
id TEXT PRIMARY KEY,
|
|
80
|
+
from_addr TEXT NOT NULL,
|
|
81
|
+
to_addr TEXT NOT NULL,
|
|
82
|
+
body TEXT NOT NULL,
|
|
83
|
+
sent_at INTEGER NOT NULL,
|
|
84
|
+
delivered_at INTEGER,
|
|
85
|
+
delivery_state TEXT NOT NULL,
|
|
86
|
+
error TEXT
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
CREATE INDEX idx_session_workspace ON session(workspace_id);
|
|
90
|
+
CREATE INDEX idx_message_sent_at ON message(sent_at);
|
|
91
|
+
CREATE INDEX idx_workspace_host ON workspace(host_id);
|
|
92
|
+
`,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
version: 2,
|
|
96
|
+
name: 'remote_windows',
|
|
97
|
+
up: `
|
|
98
|
+
-- Canvas layout for sessions that live on a peer hub.
|
|
99
|
+
--
|
|
100
|
+
-- Remote session state is deliberately NOT cached locally: the peer
|
|
101
|
+
-- owns its sessions and their snapshots. But the layout is the user's
|
|
102
|
+
-- view of their own canvas, so it belongs here, keyed by address rather
|
|
103
|
+
-- than by a session id this database does not have.
|
|
104
|
+
CREATE TABLE remote_window (
|
|
105
|
+
address TEXT PRIMARY KEY,
|
|
106
|
+
host_id TEXT NOT NULL REFERENCES host(id) ON DELETE CASCADE,
|
|
107
|
+
x REAL NOT NULL,
|
|
108
|
+
y REAL NOT NULL,
|
|
109
|
+
w REAL NOT NULL,
|
|
110
|
+
h REAL NOT NULL,
|
|
111
|
+
z INTEGER NOT NULL DEFAULT 0,
|
|
112
|
+
collapsed INTEGER NOT NULL DEFAULT 0
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
CREATE INDEX idx_remote_window_host ON remote_window(host_id);
|
|
116
|
+
`,
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
version: 3,
|
|
120
|
+
name: 'enrolled_hosts',
|
|
121
|
+
up: `
|
|
122
|
+
-- A host that ran the join installer dialled us; we hold no SSH
|
|
123
|
+
-- credentials for it and cannot reach it on our own.
|
|
124
|
+
--
|
|
125
|
+
-- ssh_host and ssh_user are NOT NULL from migration 1 and enrolled rows
|
|
126
|
+
-- have neither. They are written as '' and mapped back to null in the
|
|
127
|
+
-- store. Rebuilding the table to relax the constraint would mean
|
|
128
|
+
-- dropping it while workspace.host_id and remote_window.host_id hold
|
|
129
|
+
-- cascading references to it, which is far more risk than a sentinel.
|
|
130
|
+
ALTER TABLE host ADD COLUMN kind TEXT NOT NULL DEFAULT 'ssh';
|
|
131
|
+
-- The durable credential an enrolled host presents on every reconnect.
|
|
132
|
+
-- Server-side only, like key_ref: it is never sent to the browser.
|
|
133
|
+
ALTER TABLE host ADD COLUMN host_token TEXT;
|
|
134
|
+
ALTER TABLE host ADD COLUMN platform TEXT;
|
|
135
|
+
`,
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
version: 4,
|
|
139
|
+
name: 'pending_removals',
|
|
140
|
+
up: `
|
|
141
|
+
-- A window the user closed while its host was unreachable.
|
|
142
|
+
--
|
|
143
|
+
-- The intent outlives the disconnection. Without this the removal is
|
|
144
|
+
-- lost with the process and the peer reports the session again on its
|
|
145
|
+
-- next connection, which is what made closed terminals come back after
|
|
146
|
+
-- a restart. Replayed the moment that host is reachable again.
|
|
147
|
+
CREATE TABLE pending_removal (
|
|
148
|
+
address TEXT PRIMARY KEY,
|
|
149
|
+
host_id TEXT NOT NULL REFERENCES host(id) ON DELETE CASCADE,
|
|
150
|
+
requested_at INTEGER NOT NULL
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
CREATE INDEX idx_pending_removal_host ON pending_removal(host_id);
|
|
154
|
+
`,
|
|
155
|
+
},
|
|
156
|
+
];
|
|
157
|
+
export function runMigrations(db) {
|
|
158
|
+
db.exec(`
|
|
159
|
+
CREATE TABLE IF NOT EXISTS schema_meta (
|
|
160
|
+
version INTEGER PRIMARY KEY,
|
|
161
|
+
applied_at INTEGER NOT NULL
|
|
162
|
+
);
|
|
163
|
+
`);
|
|
164
|
+
const current = db.prepare('SELECT MAX(version) AS v FROM schema_meta').get().v ?? 0;
|
|
165
|
+
const pending = MIGRATIONS.filter((m) => m.version > current).sort((a, b) => a.version - b.version);
|
|
166
|
+
for (const m of pending) {
|
|
167
|
+
const apply = db.transaction(() => {
|
|
168
|
+
db.exec(m.up);
|
|
169
|
+
db.prepare('INSERT INTO schema_meta (version, applied_at) VALUES (?, ?)').run(m.version, Date.now());
|
|
170
|
+
});
|
|
171
|
+
apply();
|
|
172
|
+
}
|
|
173
|
+
return LATEST_VERSION;
|
|
174
|
+
}
|
|
175
|
+
export const LATEST_VERSION = MIGRATIONS.reduce((max, m) => Math.max(max, m.version), 0);
|
|
176
|
+
//# sourceMappingURL=migrations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../src/db/migrations.ts"],"names":[],"mappings":"AAaA,MAAM,CAAC,MAAM,UAAU,GAAgB;IACrC;QACE,OAAO,EAAE,CAAC;QACV,IAAI,EAAE,SAAS;QACf,EAAE,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAuFH;KACF;IACD;QACE,OAAO,EAAE,CAAC;QACV,IAAI,EAAE,gBAAgB;QACtB,EAAE,EAAE;;;;;;;;;;;;;;;;;;;KAmBH;KACF;IACD;QACE,OAAO,EAAE,CAAC;QACV,IAAI,EAAE,gBAAgB;QACtB,EAAE,EAAE;;;;;;;;;;;;;;KAcH;KACF;IACD;QACE,OAAO,EAAE,CAAC;QACV,IAAI,EAAE,kBAAkB;QACxB,EAAE,EAAE;;;;;;;;;;;;;;KAcH;KACF;CACF,CAAC;AAEF,MAAM,UAAU,aAAa,CAAC,EAAY;IACxC,EAAE,CAAC,IAAI,CAAC;;;;;GAKP,CAAC,CAAC;IAEH,MAAM,OAAO,GAET,EAAE,CAAC,OAAO,CAAC,2CAA2C,CAAC,CAAC,GAAG,EAG5D,CAAC,CAAC,IAAI,CAAC,CAAC;IAEX,MAAM,OAAO,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,GAAG,OAAO,CAAC,CAAC,IAAI,CAChE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAChC,CAAC;IAEF,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,KAAK,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;YAChC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACd,EAAE,CAAC,OAAO,CAAC,6DAA6D,CAAC,CAAC,GAAG,CAC3E,CAAC,CAAC,OAAO,EACT,IAAI,CAAC,GAAG,EAAE,CACX,CAAC;QACJ,CAAC,CAAC,CAAC;QACH,KAAK,EAAE,CAAC;IACV,CAAC;IAED,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,UAAU,CAAC,MAAM,CAC7C,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,OAAO,CAAC,EACpC,CAAC,CACF,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { type Host, type Message, type Session, type Viewport, type WindowRect, type Workspace } from '../protocol/index.js';
|
|
2
|
+
import type { Db } from './index.js';
|
|
3
|
+
export declare const DEFAULT_WINDOW: WindowRect;
|
|
4
|
+
/** Persisted fields that live only in the DB, not on the wire Session. */
|
|
5
|
+
export interface SessionLaunchSpec {
|
|
6
|
+
argv: string[];
|
|
7
|
+
env: Record<string, string>;
|
|
8
|
+
}
|
|
9
|
+
export declare class Store {
|
|
10
|
+
private readonly db;
|
|
11
|
+
constructor(db: Db);
|
|
12
|
+
listHosts(): Host[];
|
|
13
|
+
getHost(id: string): Host | null;
|
|
14
|
+
upsertHost(h: Host & {
|
|
15
|
+
keyRef?: string | null;
|
|
16
|
+
hostToken?: string | null;
|
|
17
|
+
}): void;
|
|
18
|
+
removeHost(id: string): void;
|
|
19
|
+
hostKeyRef(id: string): string | null;
|
|
20
|
+
/**
|
|
21
|
+
* Match an enrolled host by the durable token it presents on reconnect.
|
|
22
|
+
* Server-side only — like key_ref, this never travels to the browser.
|
|
23
|
+
*/
|
|
24
|
+
hostByToken(token: string): Host | null;
|
|
25
|
+
listWorkspaces(): Workspace[];
|
|
26
|
+
getWorkspace(id: string): Workspace | null;
|
|
27
|
+
getWorkspaceByName(name: string): Workspace | null;
|
|
28
|
+
insertWorkspace(w: Workspace): void;
|
|
29
|
+
removeWorkspace(id: string): void;
|
|
30
|
+
private rowToSession;
|
|
31
|
+
private readonly selectSession;
|
|
32
|
+
listSessions(isResumable: (profile: string) => boolean): Session[];
|
|
33
|
+
getSession(id: string, isResumable: (profile: string) => boolean): Session | null;
|
|
34
|
+
namesInWorkspace(workspaceId: string): string[];
|
|
35
|
+
insertSession(s: Session, spec: SessionLaunchSpec): void;
|
|
36
|
+
/** Fields that change over a session's life. Layout is written separately. */
|
|
37
|
+
updateSession(id: string, patch: Partial<Pick<Session, 'state' | 'pid' | 'exitCode' | 'statusText' | 'title' | 'cols' | 'rows' | 'exitedAt' | 'lastActiveAt' | 'agentSessionUuid'>>): void;
|
|
38
|
+
getLaunchSpec(id: string): SessionLaunchSpec | null;
|
|
39
|
+
setLaunchSpec(id: string, spec: SessionLaunchSpec): void;
|
|
40
|
+
removeSession(id: string): void;
|
|
41
|
+
saveWindow(sessionId: string, rect: WindowRect): void;
|
|
42
|
+
getViewport(): Viewport;
|
|
43
|
+
saveViewport(v: Viewport): void;
|
|
44
|
+
/**
|
|
45
|
+
* Layout for peer-hosted sessions, keyed by address. Remote session state
|
|
46
|
+
* itself is never cached here; the peer owns it.
|
|
47
|
+
*/
|
|
48
|
+
getRemoteWindows(): Map<string, WindowRect>;
|
|
49
|
+
saveRemoteWindow(address: string, hostId: string, rect: WindowRect): void;
|
|
50
|
+
removeRemoteWindow(address: string): void;
|
|
51
|
+
/**
|
|
52
|
+
* Addresses the user closed while their host was unreachable, kept until
|
|
53
|
+
* that host can be told. Keyed by address for the same reason remote_window
|
|
54
|
+
* is: the owning hub's session id means nothing in this database.
|
|
55
|
+
*/
|
|
56
|
+
pendingRemovals(hostId?: string): {
|
|
57
|
+
address: string;
|
|
58
|
+
hostId: string;
|
|
59
|
+
}[];
|
|
60
|
+
addPendingRemoval(address: string, hostId: string): void;
|
|
61
|
+
clearPendingRemoval(address: string): void;
|
|
62
|
+
saveSnapshot(sessionId: string, serialized: string, cols: number, rows: number): void;
|
|
63
|
+
getSnapshot(sessionId: string): {
|
|
64
|
+
serialized: string;
|
|
65
|
+
cols: number;
|
|
66
|
+
rows: number;
|
|
67
|
+
} | null;
|
|
68
|
+
listMessages(limit?: number): Message[];
|
|
69
|
+
insertMessage(m: Message): void;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/db/store.ts"],"names":[],"mappings":"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"}
|