@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
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { basename } from 'node:path';
|
|
4
|
+
import { platform } from 'node:process';
|
|
5
|
+
import { buildAgentEnv } from '../agents/env.js';
|
|
6
|
+
/*
|
|
7
|
+
* node-pty, @xterm/headless and @xterm/addon-serialize are CommonJS, and their
|
|
8
|
+
* typings use `declare module` with named exports. That combination is a trap:
|
|
9
|
+
* `import { Terminal } from '@xterm/headless'` typechecks cleanly and then
|
|
10
|
+
* throws at runtime, because Node's CJS named-export detection cannot see
|
|
11
|
+
* through their bundled output. createRequire gets us the real exports while
|
|
12
|
+
* `typeof import(...)` keeps full type information.
|
|
13
|
+
*/
|
|
14
|
+
const require = createRequire(import.meta.url);
|
|
15
|
+
const pty = require('node-pty');
|
|
16
|
+
const { Terminal } = require('@xterm/headless');
|
|
17
|
+
const { SerializeAddon } = require('@xterm/addon-serialize');
|
|
18
|
+
/** How much history the hub keeps in memory for live reattach. Never persisted. */
|
|
19
|
+
const SCROLLBACK_LINES = 2000;
|
|
20
|
+
/** Scrollback included when replaying to a browser that just attached. */
|
|
21
|
+
const ATTACH_SCROLLBACK = 1000;
|
|
22
|
+
/**
|
|
23
|
+
* How long a `heuristic` profile must be quiet before its last line is worth
|
|
24
|
+
* testing against the prompt pattern.
|
|
25
|
+
*/
|
|
26
|
+
const HEURISTIC_QUIET_MS = 400;
|
|
27
|
+
/**
|
|
28
|
+
* How long a `hooks` profile whose hooks have never arrived must be quiet
|
|
29
|
+
* before it is called idle. Longer, because this path has no prompt pattern to
|
|
30
|
+
* check and only silence to go on - and an agent mid-turn is animating a
|
|
31
|
+
* spinner, which is not silence.
|
|
32
|
+
*/
|
|
33
|
+
const HOOK_FALLBACK_QUIET_MS = 2000;
|
|
34
|
+
/**
|
|
35
|
+
* One PTY plus a headless xterm that mirrors it.
|
|
36
|
+
*
|
|
37
|
+
* The headless terminal earns its keep twice: it produces the serialized
|
|
38
|
+
* screen we persist to SQLite, and it is the in-memory scrollback that backs
|
|
39
|
+
* LOD detach/reattach. Feeding one parser instead of keeping a separate raw
|
|
40
|
+
* ring buffer means the replayed screen is always consistent with what the
|
|
41
|
+
* program actually drew, escape sequences and all.
|
|
42
|
+
*/
|
|
43
|
+
export class PtySession extends EventEmitter {
|
|
44
|
+
id;
|
|
45
|
+
cols;
|
|
46
|
+
rows;
|
|
47
|
+
readyHint;
|
|
48
|
+
statusMode;
|
|
49
|
+
proc = null;
|
|
50
|
+
term;
|
|
51
|
+
serializer;
|
|
52
|
+
lastOutputAt = 0;
|
|
53
|
+
idleTimer = null;
|
|
54
|
+
_status = 'unknown';
|
|
55
|
+
/**
|
|
56
|
+
* Whether this session has ever reported a turn boundary itself. Until it
|
|
57
|
+
* has, a `hooks` profile is indistinguishable from one whose hooks cannot
|
|
58
|
+
* run at all, and the dot has to come from somewhere.
|
|
59
|
+
*/
|
|
60
|
+
hooksSeen = false;
|
|
61
|
+
/** Last title this program set, to keep repeats off the wire. */
|
|
62
|
+
lastTitle = null;
|
|
63
|
+
/** argv[0], to recognise the one title the program did not choose. */
|
|
64
|
+
launchedFile = '';
|
|
65
|
+
disposed = false;
|
|
66
|
+
constructor(id, cols, rows, readyHint, statusMode) {
|
|
67
|
+
super();
|
|
68
|
+
this.id = id;
|
|
69
|
+
this.cols = cols;
|
|
70
|
+
this.rows = rows;
|
|
71
|
+
this.readyHint = readyHint;
|
|
72
|
+
this.statusMode = statusMode;
|
|
73
|
+
this.term = new Terminal({
|
|
74
|
+
cols,
|
|
75
|
+
rows,
|
|
76
|
+
scrollback: SCROLLBACK_LINES,
|
|
77
|
+
allowProposedApi: true,
|
|
78
|
+
});
|
|
79
|
+
this.serializer = new SerializeAddon();
|
|
80
|
+
this.term.loadAddon(this.serializer);
|
|
81
|
+
// OSC 0 and OSC 2, read off the mirror terminal rather than with a regex
|
|
82
|
+
// over the stream. The parser already handles a sequence split across two
|
|
83
|
+
// reads and either terminator, and nothing has to be stripped: the
|
|
84
|
+
// browser's own terminal is entitled to the same bytes.
|
|
85
|
+
this.term.onTitleChange((title) => {
|
|
86
|
+
const next = title.trim();
|
|
87
|
+
if (next === this.lastTitle || this.isLaunchNoise(next))
|
|
88
|
+
return;
|
|
89
|
+
this.lastTitle = next;
|
|
90
|
+
this.emit('title', next);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* ConPTY announces the child's image path as the terminal title the moment
|
|
95
|
+
* it starts, before the program has said anything at all. That is Windows
|
|
96
|
+
* talking, not the agent, and `C:...node.exe` in a window header is worse
|
|
97
|
+
* than the canvas address it would replace.
|
|
98
|
+
*/
|
|
99
|
+
isLaunchNoise(title) {
|
|
100
|
+
if (!this.launchedFile)
|
|
101
|
+
return false;
|
|
102
|
+
const same = (a, b) => platform === 'win32' ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
103
|
+
return (same(title, this.launchedFile) || same(title, basename(this.launchedFile)));
|
|
104
|
+
}
|
|
105
|
+
get status() {
|
|
106
|
+
return this._status;
|
|
107
|
+
}
|
|
108
|
+
get running() {
|
|
109
|
+
return this.proc !== null;
|
|
110
|
+
}
|
|
111
|
+
get pid() {
|
|
112
|
+
return this.proc?.pid ?? null;
|
|
113
|
+
}
|
|
114
|
+
start(opts) {
|
|
115
|
+
if (this.proc)
|
|
116
|
+
throw new Error(`session ${this.id} already running`);
|
|
117
|
+
const [file, ...args] = opts.argv;
|
|
118
|
+
if (!file)
|
|
119
|
+
throw new Error('empty argv');
|
|
120
|
+
this.launchedFile = file;
|
|
121
|
+
this.cols = opts.cols;
|
|
122
|
+
this.rows = opts.rows;
|
|
123
|
+
this.term.resize(opts.cols, opts.rows);
|
|
124
|
+
this.proc = pty.spawn(file, args, {
|
|
125
|
+
name: 'xterm-256color',
|
|
126
|
+
cols: opts.cols,
|
|
127
|
+
rows: opts.rows,
|
|
128
|
+
cwd: opts.cwd,
|
|
129
|
+
// Strips the parent agent CLI's own session state; see agents/env.ts for
|
|
130
|
+
// why inheriting it wholesale breaks resume and leaks an IPC channel.
|
|
131
|
+
env: buildAgentEnv(process.env, opts.env),
|
|
132
|
+
// ConPTY is the only way to get a real PTY on Windows; node-pty falls
|
|
133
|
+
// back to winpty on older builds, which we do not support.
|
|
134
|
+
useConpty: platform === 'win32' ? true : undefined,
|
|
135
|
+
});
|
|
136
|
+
this.proc.onData((data) => this.ingest(data));
|
|
137
|
+
this.proc.onExit(({ exitCode, signal }) => {
|
|
138
|
+
this.proc = null;
|
|
139
|
+
this.clearIdleTimer();
|
|
140
|
+
this._status = 'unknown';
|
|
141
|
+
this.emit('exit', { exitCode, signal });
|
|
142
|
+
});
|
|
143
|
+
this.markBusy();
|
|
144
|
+
}
|
|
145
|
+
/** Feed output to subscribers and to the mirror terminal. */
|
|
146
|
+
ingest(data) {
|
|
147
|
+
if (this.disposed)
|
|
148
|
+
return;
|
|
149
|
+
this.lastOutputAt = Date.now();
|
|
150
|
+
this.term.write(data);
|
|
151
|
+
this.emit('data', data);
|
|
152
|
+
// A hooked session that has reported a boundary is left to its hooks.
|
|
153
|
+
// One that never has is watched like any other, or it would sit on
|
|
154
|
+
// whatever status it started with for the life of the process.
|
|
155
|
+
if (this.statusMode === 'heuristic' || !this.hooksSeen)
|
|
156
|
+
this.scheduleIdleCheck();
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Idle detection from output alone. Drives the status chip only — never
|
|
160
|
+
* gates delivery, because delivery is immediate by design.
|
|
161
|
+
*
|
|
162
|
+
* Two callers, with different standards of proof. A `heuristic` profile has
|
|
163
|
+
* a prompt pattern, so a short quiet spell plus a matching last line is
|
|
164
|
+
* enough. A `hooks` profile only reaches here while its hooks have never
|
|
165
|
+
* arrived, and has nothing to match against, so it waits out a longer
|
|
166
|
+
* silence and takes that as the answer.
|
|
167
|
+
*/
|
|
168
|
+
scheduleIdleCheck() {
|
|
169
|
+
this.markBusy();
|
|
170
|
+
this.clearIdleTimer();
|
|
171
|
+
const fallback = this.statusMode === 'hooks';
|
|
172
|
+
const quiet = fallback ? HOOK_FALLBACK_QUIET_MS : HEURISTIC_QUIET_MS;
|
|
173
|
+
this.idleTimer = setTimeout(() => {
|
|
174
|
+
if (Date.now() - this.lastOutputAt < quiet)
|
|
175
|
+
return;
|
|
176
|
+
if (fallback) {
|
|
177
|
+
// A hook that arrived while this was pending outranks it.
|
|
178
|
+
if (!this.hooksSeen)
|
|
179
|
+
this.setStatus('idle');
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const tail = this.lastNonEmptyLine();
|
|
183
|
+
const looksIdle = this.readyHint ? this.readyHint.test(tail) : true;
|
|
184
|
+
this.setStatus(looksIdle ? 'idle' : 'busy');
|
|
185
|
+
}, quiet + 100);
|
|
186
|
+
}
|
|
187
|
+
clearIdleTimer() {
|
|
188
|
+
if (this.idleTimer) {
|
|
189
|
+
clearTimeout(this.idleTimer);
|
|
190
|
+
this.idleTimer = null;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
markBusy() {
|
|
194
|
+
this.setStatus('busy');
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* A turn boundary the agent reported itself. Exact, where reading the output
|
|
198
|
+
* is a guess — so the first one to arrive retires the fallback for good and
|
|
199
|
+
* this session's status is whatever its hooks say from then on.
|
|
200
|
+
*/
|
|
201
|
+
noteHook(s) {
|
|
202
|
+
this.hooksSeen = true;
|
|
203
|
+
this.clearIdleTimer();
|
|
204
|
+
this.setStatus(s);
|
|
205
|
+
}
|
|
206
|
+
setStatus(s) {
|
|
207
|
+
if (this._status === s)
|
|
208
|
+
return;
|
|
209
|
+
this._status = s;
|
|
210
|
+
this.emit('status', s);
|
|
211
|
+
}
|
|
212
|
+
write(data) {
|
|
213
|
+
if (!this.proc)
|
|
214
|
+
throw new Error(`session ${this.id} is not running`);
|
|
215
|
+
this.proc.write(data);
|
|
216
|
+
}
|
|
217
|
+
resize(cols, rows) {
|
|
218
|
+
if (cols < 1 || rows < 1)
|
|
219
|
+
return;
|
|
220
|
+
this.cols = cols;
|
|
221
|
+
this.rows = rows;
|
|
222
|
+
this.term.resize(cols, rows);
|
|
223
|
+
// A dead session still tracks its geometry so the restored window is right.
|
|
224
|
+
this.proc?.resize(cols, rows);
|
|
225
|
+
}
|
|
226
|
+
/** Full-ish history, for a browser that just attached to this window. */
|
|
227
|
+
serializeForAttach() {
|
|
228
|
+
return this.serializer.serialize({ scrollback: ATTACH_SCROLLBACK });
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Screen only. This is what goes in SQLite: a restart preserves the last
|
|
232
|
+
* screenful, deliberately not the scrollback above it.
|
|
233
|
+
*/
|
|
234
|
+
serializeForPersist() {
|
|
235
|
+
return this.serializer.serialize({ scrollback: 0 });
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* The last line the program actually drew, wherever it is on the screen.
|
|
239
|
+
*
|
|
240
|
+
* Not the bottom row: a CLI sitting at its prompt after printing one banner
|
|
241
|
+
* has its prompt at the top and twenty-three blank rows under it, and
|
|
242
|
+
* testing the bottom row against a prompt pattern matched nothing, ever.
|
|
243
|
+
* That is what kept a freshly started window amber for the life of the
|
|
244
|
+
* process.
|
|
245
|
+
*/
|
|
246
|
+
lastNonEmptyLine() {
|
|
247
|
+
const drawn = this.tailLines(this.rows);
|
|
248
|
+
return drawn.slice(drawn.lastIndexOf('\n') + 1).trim();
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Trailing rendered lines, for the read_screen MCP tool.
|
|
252
|
+
*
|
|
253
|
+
* Rows the terminal wrapped are rejoined. A wrap is a property of how
|
|
254
|
+
* wide the window happens to be, not of the text, so breaking a line
|
|
255
|
+
* there hands the reader a word split down the middle at a column that
|
|
256
|
+
* depends on someone else's terminal size - and the reader here is an
|
|
257
|
+
* agent trying to make sense of what another agent is doing.
|
|
258
|
+
*/
|
|
259
|
+
tailLines(n) {
|
|
260
|
+
const buf = this.term.buffer.active;
|
|
261
|
+
const end = buf.baseY + this.term.rows;
|
|
262
|
+
const start = Math.max(0, end - n);
|
|
263
|
+
const out = [];
|
|
264
|
+
for (let i = start; i < end; i++) {
|
|
265
|
+
const line = buf.getLine(i);
|
|
266
|
+
const text = line?.translateToString(true) ?? '';
|
|
267
|
+
if (line?.isWrapped && out.length > 0)
|
|
268
|
+
out[out.length - 1] += text;
|
|
269
|
+
else
|
|
270
|
+
out.push(text);
|
|
271
|
+
}
|
|
272
|
+
while (out.length > 0 && out[out.length - 1].trim() === '')
|
|
273
|
+
out.pop();
|
|
274
|
+
return out.join('\n');
|
|
275
|
+
}
|
|
276
|
+
/** Restore a persisted screen into the mirror so reattach shows it. */
|
|
277
|
+
restore(serialized) {
|
|
278
|
+
if (serialized)
|
|
279
|
+
this.term.write(serialized);
|
|
280
|
+
}
|
|
281
|
+
kill() {
|
|
282
|
+
this.clearIdleTimer();
|
|
283
|
+
if (!this.proc)
|
|
284
|
+
return;
|
|
285
|
+
try {
|
|
286
|
+
this.proc.kill();
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
// Already gone; onExit has fired or will not fire. Nothing to do.
|
|
290
|
+
}
|
|
291
|
+
this.proc = null;
|
|
292
|
+
}
|
|
293
|
+
dispose() {
|
|
294
|
+
this.disposed = true;
|
|
295
|
+
this.kill();
|
|
296
|
+
this.term.dispose();
|
|
297
|
+
this.removeAllListeners();
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
//# sourceMappingURL=pty.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pty.js","sourceRoot":"","sources":["../../src/session/pty.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kdonev/termscape",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Run a crew of AI coding agents on one infinite canvas, and let them talk to each other.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "kdonev",
|
|
7
|
+
"homepage": "https://github.com/kdonev/termscape#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/kdonev/termscape.git",
|
|
11
|
+
"directory": "packages/hub"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/kdonev/termscape/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"ai",
|
|
18
|
+
"agents",
|
|
19
|
+
"terminal",
|
|
20
|
+
"canvas",
|
|
21
|
+
"mcp",
|
|
22
|
+
"claude-code",
|
|
23
|
+
"pty",
|
|
24
|
+
"tui"
|
|
25
|
+
],
|
|
26
|
+
"type": "module",
|
|
27
|
+
"bin": {
|
|
28
|
+
"termscape": "./dist/cli.js"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=22"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@fastify/static": "^10.1.3",
|
|
35
|
+
"@fastify/websocket": "^11.0.1",
|
|
36
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
37
|
+
"@xterm/addon-serialize": "^0.13.0",
|
|
38
|
+
"@xterm/headless": "^5.5.0",
|
|
39
|
+
"better-sqlite3": "^13.0.3",
|
|
40
|
+
"fastify": "^5.2.0",
|
|
41
|
+
"node-pty": "1.2.0-beta.15",
|
|
42
|
+
"smol-toml": "^1.3.1",
|
|
43
|
+
"ssh2": "^1.16.0",
|
|
44
|
+
"zod": "^4.5.4"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"start": "node dist/cli.js"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.xterm{cursor:text;-webkit-user-select:none;user-select:none;position:relative}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{z-index:5;position:absolute;top:0}.xterm .xterm-helper-textarea{opacity:0;z-index:-5;white-space:nowrap;resize:none;border:0;width:0;height:0;margin:0;padding:0;position:absolute;top:0;left:-9999em;overflow:hidden}.xterm .composition-view{color:#fff;white-space:nowrap;z-index:1;background:#000;display:none;position:absolute}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{cursor:default;background-color:#000;position:absolute;inset:0;overflow-y:scroll}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;top:0;left:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{visibility:hidden;line-height:normal;display:inline-block;position:absolute;top:0;left:-9999em}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{z-index:10;color:#0000;pointer-events:none;position:absolute;inset:0}.xterm .xterm-accessibility-tree:not(.debug) ::selection{color:#0000}.xterm .xterm-accessibility-tree{-webkit-user-select:text;user-select:text;white-space:pre}.xterm .live-region{width:1px;height:1px;position:absolute;left:-9999px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{-webkit-text-decoration:underline double;text-decoration:underline double}.xterm-underline-3{-webkit-text-decoration:underline wavy;text-decoration:underline wavy}.xterm-underline-4{-webkit-text-decoration:underline dotted;text-decoration:underline dotted}.xterm-underline-5{-webkit-text-decoration:underline dashed;text-decoration:underline dashed}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:underline overline}.xterm-overline.xterm-underline-2{-webkit-text-decoration:overline double underline;text-decoration:overline double underline}.xterm-overline.xterm-underline-3{-webkit-text-decoration:overline wavy underline;text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{-webkit-text-decoration:overline dotted underline;text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{-webkit-text-decoration:overline dashed underline;text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;pointer-events:none;position:absolute;top:0;right:0}.xterm-decoration-top{z-index:2;position:relative}:root{--bg:#0a0c10;--grid:#151a23;--panel:#12161e;--panel-2:#171c26;--border:#262d3b;--text:#d5dae2;--muted:#7c8596;--accent:#7c9cf5;--danger:#e06c75;--ok:#88c07a;--warn:#d8b271;--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}*{box-sizing:border-box}html,body,#root{height:100%;margin:0}body{background:var(--bg);color:var(--text);font:13px/1.45 ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,sans-serif;overflow:hidden}.app{flex-direction:column;height:100%;display:flex}.toolbar{background:var(--panel);border-bottom:1px solid var(--border);z-index:20;flex:none;align-items:center;gap:10px;padding:8px 12px;display:flex;position:relative}.brand{letter-spacing:.2px;align-items:baseline;gap:8px;font-weight:650;display:flex}.conn{font-size:11px;font-weight:400}.conn.on{color:var(--muted)}.conn.off{color:var(--warn)}.group{align-items:center;gap:6px;display:flex}.spacer{flex:1}.input{background:var(--panel-2);border:1px solid var(--border);color:var(--text);font:inherit;border-radius:6px;min-width:120px;padding:5px 8px}.input.wide{min-width:260px}.input:focus{border-color:var(--accent);outline:none}.btn{background:var(--panel-2);border:1px solid var(--border);color:var(--text);font:inherit;cursor:pointer;white-space:nowrap;border-radius:6px;padding:5px 9px}.btn:hover:not(:disabled){border-color:var(--accent)}.btn:disabled{opacity:.45;cursor:default}.btn.primary{background:#1e2a45;border-color:#33406a}.btn.danger:hover{border-color:var(--danger);color:var(--danger)}.btn.wide{min-width:56px}.canvas{cursor:default;background-color:var(--bg);background-image:linear-gradient(var(--grid) 1px, transparent 1px), linear-gradient(90deg, var(--grid) 1px, transparent 1px);touch-action:none;background-size:40px 40px;flex:1;position:relative;overflow:hidden}.canvas.panning{cursor:grabbing}.world{transform-origin:0 0;position:absolute;inset:0}.world.interacting{will-change:transform}.edges{pointer-events:none;position:absolute;overflow:visible}.ws-group{opacity:.4;pointer-events:none;border:1px dashed;border-radius:14px;position:absolute;top:0;left:0}.ws-label{white-space:nowrap;align-items:baseline;gap:8px;font-size:12px;font-weight:600;display:flex;position:absolute;top:-22px;left:12px}.ws-path{color:var(--muted);font-size:11px;font-weight:400}.window{background:var(--panel);border:1px solid var(--border);border-radius:10px;flex-direction:column;display:flex;position:absolute;top:0;left:0;overflow:hidden;box-shadow:0 8px 28px #00000073}.window.selected{box-shadow:0 0 0 1px var(--accent), 0 12px 36px #0000008c}.window.stopped{opacity:.82}.window-bar{background:var(--panel-2);border-bottom:1px solid var(--border);cursor:grab;-webkit-user-select:none;user-select:none;flex:none;align-items:center;gap:8px;padding:6px 8px;display:flex}.window-bar:active{cursor:grabbing}.window-bar .dot{border-radius:50%;flex:none;width:8px;height:8px}.window-bar .addr{font-size:12px;font-weight:600}.window-bar .meta{color:var(--muted);font-size:11px}.window-bar .btn{padding:2px 7px;font-size:11px}.window-body{background:#0e1117;flex:1;min-height:0;position:relative}.term-host{position:absolute;inset:4px 6px;overflow:hidden}.term-scale{transform-origin:0 0;position:absolute;top:0;left:0}.term-scale .xterm{width:100%;height:100%}.term-scale .xterm-viewport{scrollbar-width:none}.term-scale .xterm-viewport::-webkit-scrollbar{width:0;height:0;display:none}.resize-handle{cursor:nwse-resize;background:linear-gradient(135deg, transparent 50%, var(--border) 50%);width:16px;height:16px;position:absolute;bottom:0;right:0}.lod{text-align:center;height:100%;color:var(--muted);flex-direction:column;justify-content:center;align-items:center;gap:6px;padding:16px;display:flex}.lod-title{color:var(--text);font-size:20px;font-weight:650}.lod-sub{font-size:13px}.lod-status{color:var(--warn);font-size:13px}.lod-hint{opacity:.6;margin-top:6px;font-size:11px}.msg-log{background:var(--panel);border:1px solid var(--border);border-radius:10px;width:420px;max-height:60vh;padding:8px;position:absolute;top:100%;right:12px;overflow-y:auto;box-shadow:0 12px 32px #00000080}.msg-empty{color:var(--muted);text-align:center;padding:10px}.msg{border-left:2px solid var(--ok);background:var(--panel-2);border-radius:7px;margin-bottom:6px;padding:7px 9px}.msg.failed{border-left-color:var(--danger)}.msg-head{align-items:baseline;gap:6px;font-size:11px;display:flex}.msg-head .from,.msg-head .to{font-weight:600}.msg-head .arrow,.msg-head .when{color:var(--muted)}.msg-head .when{margin-left:auto}.msg-body{white-space:pre-wrap;word-break:break-word;max-height:90px;margin-top:4px;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:11px;overflow:hidden}.msg-error{color:var(--danger);margin-top:4px;font-size:11px}.empty-state{text-align:center;max-width:460px;color:var(--muted);pointer-events:none;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.empty-state h1{color:var(--text);margin:0 0 8px;font-size:20px}.empty-state p{margin:0 0 10px}.empty-state .hint{opacity:.7;font-size:11px}.empty-state .btn{pointer-events:auto}.errors{z-index:30;flex-direction:column;gap:6px;max-width:460px;display:flex;position:absolute;bottom:14px;left:14px}.error{border:1px solid var(--danger);color:#f0b6bb;background:#2a1a1e;border-radius:7px;align-items:flex-start;gap:8px;padding:7px 10px;font-size:12px;display:flex}.error-body{word-break:break-word;flex:1;min-width:0}.error-close{color:inherit;opacity:.7;font:inherit;cursor:pointer;background:0 0;border:0;flex:none;padding:0 2px;line-height:1}.error-close:hover{opacity:1}.panel{background:var(--panel);border-left:1px solid var(--border);z-index:40;flex-direction:column;width:420px;max-width:92vw;transition:transform .18s;display:flex;position:absolute;top:0;bottom:0;right:0;transform:translate(100%);box-shadow:-18px 0 40px #00000073}.panel.open{transform:translate(0)}.panel[aria-hidden=true]{visibility:hidden}.panel.open[aria-hidden=false]{visibility:visible}.panel-head{border-bottom:1px solid var(--border);flex:none;align-items:center;gap:8px;padding:9px 12px;display:flex}.panel-title{font-weight:650}.panel-body{flex:1;padding:8px;overflow-y:auto}.node{border-radius:6px;align-items:center;gap:7px;min-width:0;padding:5px 7px;display:flex}.node .dot{border-radius:50%;flex:none;width:8px;height:8px}.node-label{white-space:nowrap;text-overflow:ellipsis;flex:0 auto;font-weight:600;overflow:hidden}.node-sub{color:var(--muted);white-space:nowrap;text-overflow:ellipsis;flex:0 auto;min-width:0;font-size:11px;overflow:hidden}.node.machine{background:var(--panel-2);margin-top:6px}.node.session{cursor:pointer}.node.session:hover{background:var(--panel-2)}.node.session.on{box-shadow:inset 0 0 0 1px var(--accent);background:#1b2540}.node .btn{padding:2px 7px;font-size:11px}.node-children{border-left:1px solid var(--border);margin-left:11px;padding-left:9px}.node-empty{color:var(--muted);padding:4px 8px;font-size:11px}.node-error{color:var(--danger);word-break:break-word;padding:2px 8px 4px 26px;font-size:11px}.node-log{color:var(--muted);word-break:break-word;padding:2px 8px 4px 26px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:10.5px;line-height:1.35}.node-form{flex-wrap:wrap;gap:5px;padding:5px 7px 8px;display:flex}.node-form .input{flex:120px;min-width:90px}.node-form .input.wide{flex:3 200px;min-width:140px}.twisty{color:var(--muted);cursor:pointer;font:inherit;background:0 0;border:0;flex:none;width:14px;padding:0}.twisty:hover{color:var(--text)}.add-machine{margin:10px 0 0 7px}.add-machine-panel{border:1px solid var(--border);border-radius:8px;margin-top:10px;padding:8px}.host-tabs{border-bottom:1px solid var(--border);align-items:center;gap:4px;margin-bottom:4px;padding-bottom:8px;display:flex}.host-tabs .tab{font:inherit;color:var(--muted);cursor:pointer;background:0 0;border:1px solid #0000;border-radius:6px;flex:1;padding:5px 8px;font-size:11px}.host-tabs .tab:hover{color:var(--text)}.host-tabs .tab.on{color:var(--text);background:var(--panel-2);border-color:var(--border)}.join-url{background:var(--panel-2);border:1px solid var(--border);border-radius:7px;flex:1 0 100%;align-items:center;gap:6px;padding:7px 8px;display:flex}.join-url code{white-space:nowrap;min-width:0;color:var(--accent);flex:1;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px;overflow-x:auto}.host-note code{color:var(--text);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.host-form{flex-wrap:wrap;gap:6px;padding-top:8px;display:flex}.host-form .input{flex:1;min-width:90px}.host-form .input.tiny{flex:none;min-width:56px;max-width:64px}.host-form .input.wide{flex:1 0 100%}.host-note{color:var(--muted);flex:1 0 100%;margin:2px 0 0;font-size:11px;line-height:1.4}
|