@fnndsc/calypso 0.6.0 → 0.7.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/dist/calypso.js +53 -3
- package/dist/calypso.js.map +1 -1
- package/dist/daemon/attachFile.d.ts +43 -0
- package/dist/daemon/attachFile.js +80 -0
- package/dist/daemon/attachFile.js.map +1 -0
- package/dist/daemon/engine.d.ts +16 -0
- package/dist/daemon/face.d.ts +127 -0
- package/dist/daemon/face.js +419 -0
- package/dist/daemon/face.js.map +1 -0
- package/dist/daemon/launch.d.ts +21 -6
- package/dist/daemon/launch.js +82 -18
- package/dist/daemon/launch.js.map +1 -1
- package/dist/daemon/server.d.ts +83 -4
- package/dist/daemon/server.js +157 -5
- package/dist/daemon/server.js.map +1 -1
- package/dist/daemon/static.d.ts +13 -0
- package/dist/daemon/static.js +36 -0
- package/dist/daemon/static.js.map +1 -1
- package/dist/index.d.ts +15 -8
- package/dist/index.js +14 -7
- package/dist/index.js.map +1 -1
- package/package.json +6 -9
- package/dist/protocol/envelope.d.ts +0 -136
- package/dist/protocol/envelope.js +0 -46
- package/dist/protocol/envelope.js.map +0 -1
- package/dist/protocol/index.d.ts +0 -16
- package/dist/protocol/index.js +0 -17
- package/dist/protocol/index.js.map +0 -1
- package/dist/protocol/messages.d.ts +0 -1568
- package/dist/protocol/messages.js +0 -281
- package/dist/protocol/messages.js.map +0 -1
- package/dist/protocol/validate.d.ts +0 -57
- package/dist/protocol/validate.js +0 -88
- package/dist/protocol/validate.js.map +0 -1
- package/dist/protocol/version.d.ts +0 -25
- package/dist/protocol/version.js +0 -28
- package/dist/protocol/version.js.map +0 -1
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The daemon console face: the terminal's resting state once booted.
|
|
3
|
+
*
|
|
4
|
+
* A daemon's terminal has no REPL competing for the screen, so after boot it
|
|
5
|
+
* becomes an instrument panel: the mise brain pulsing on the alternate screen
|
|
6
|
+
* buffer, the daemon's identity and addresses beside it, and a short strip of
|
|
7
|
+
* the latest log lines beneath. The alternate buffer never scrolls, so the
|
|
8
|
+
* animation anchors at absolute coordinates — none of the cursor arithmetic
|
|
9
|
+
* that made the boot-phase pulse fight its own log output.
|
|
10
|
+
*
|
|
11
|
+
* The pulse is honest: idle is a slow ambient shimmer, an executing command
|
|
12
|
+
* quickens it, and a surface attaching or detaching flares it briefly.
|
|
13
|
+
*
|
|
14
|
+
* Esc or `q` drops to the normal buffer — the full boot log restored exactly,
|
|
15
|
+
* with the lines captured while the face was up flushed beneath it — and any
|
|
16
|
+
* key returns. Ctrl-C stops the daemon from either mode.
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
import chalk from 'chalk';
|
|
21
|
+
import { logo_frameRender, logoColumns_count, logoRows_count } from '@fnndsc/brasa';
|
|
22
|
+
/** Strips ANSI escapes for width math and log-strip storage. */
|
|
23
|
+
const ESCAPE_PATTERN = /\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-Z\\-_])/g;
|
|
24
|
+
/** Log lines kept for the drop-to-text flush. */
|
|
25
|
+
const RING_CAPACITY = 400;
|
|
26
|
+
/** Log lines shown in the steady face's strip. */
|
|
27
|
+
const STRIP_LINES = 5;
|
|
28
|
+
/** Log lines shown while booting — the messages are the point then. */
|
|
29
|
+
const BOOT_STRIP_LINES = 10;
|
|
30
|
+
/** Frame period. */
|
|
31
|
+
const TICK_MS = 100;
|
|
32
|
+
/** Ambient pulse advances one frame per this many ticks. */
|
|
33
|
+
const AMBIENT_STRIDE = 3;
|
|
34
|
+
/** How long an attach/detach flare quickens the pulse. */
|
|
35
|
+
const FLARE_MS = 1500;
|
|
36
|
+
/**
|
|
37
|
+
* A bounded line buffer for output arriving while the face owns the screen.
|
|
38
|
+
*
|
|
39
|
+
* Everything a stray `console.log` (a retry warning, a background sweep
|
|
40
|
+
* report) writes is caged here: the newest lines feed the face's strip, and
|
|
41
|
+
* the whole run since the last flush is replayed into the normal buffer when
|
|
42
|
+
* the operator drops to text — so nothing is ever lost, only re-homed.
|
|
43
|
+
*/
|
|
44
|
+
export class FaceLogRing {
|
|
45
|
+
lines = [];
|
|
46
|
+
pending = '';
|
|
47
|
+
unflushed = 0;
|
|
48
|
+
/**
|
|
49
|
+
* Absorbs one raw write, splitting it into stripped, stored lines.
|
|
50
|
+
*
|
|
51
|
+
* A carriage return without a newline is a redraw (a spinner frame): it
|
|
52
|
+
* discards what preceded it on the line rather than appending, so a
|
|
53
|
+
* thousand frames stay one line, not one enormous one.
|
|
54
|
+
*
|
|
55
|
+
* @param chunk - The text a hijacked stdout/stderr write carried.
|
|
56
|
+
*/
|
|
57
|
+
push(chunk) {
|
|
58
|
+
const text = this.pending + chunk.replace(ESCAPE_PATTERN, '');
|
|
59
|
+
const parts = text.split('\n');
|
|
60
|
+
this.pending = (parts.pop() ?? '').split('\r').pop() ?? '';
|
|
61
|
+
for (const part of parts) {
|
|
62
|
+
const line = part.split('\r').pop() ?? '';
|
|
63
|
+
this.lines.push(line);
|
|
64
|
+
this.unflushed += 1;
|
|
65
|
+
}
|
|
66
|
+
if (this.lines.length > RING_CAPACITY) {
|
|
67
|
+
const excess = this.lines.length - RING_CAPACITY;
|
|
68
|
+
this.lines.splice(0, excess);
|
|
69
|
+
this.unflushed = Math.min(this.unflushed, this.lines.length);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The newest lines, for the face's log strip.
|
|
74
|
+
*
|
|
75
|
+
* @param count - How many lines the strip shows.
|
|
76
|
+
* @returns Up to `count` lines, oldest first.
|
|
77
|
+
*/
|
|
78
|
+
tail(count) {
|
|
79
|
+
return this.lines.slice(-count);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Returns and forgets the lines not yet replayed into the normal buffer.
|
|
83
|
+
*
|
|
84
|
+
* @returns The unflushed lines, oldest first.
|
|
85
|
+
*/
|
|
86
|
+
drain() {
|
|
87
|
+
const drained = this.unflushed === 0 ? [] : this.lines.slice(-this.unflushed);
|
|
88
|
+
this.unflushed = 0;
|
|
89
|
+
return drained;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Formats an uptime as `2d 03:14:07` / `03:14:07`.
|
|
94
|
+
*
|
|
95
|
+
* @param seconds - Whole seconds since the daemon came up.
|
|
96
|
+
* @returns The formatted uptime.
|
|
97
|
+
*/
|
|
98
|
+
export function uptime_format(seconds) {
|
|
99
|
+
const days = Math.floor(seconds / 86400);
|
|
100
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
101
|
+
const clock = `${pad(Math.floor((seconds % 86400) / 3600))}:${pad(Math.floor((seconds % 3600) / 60))}:${pad(Math.floor(seconds % 60))}`;
|
|
102
|
+
return days > 0 ? `${days}d ${clock}` : clock;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Truncates a plain (escape-free) line to the terminal width.
|
|
106
|
+
*
|
|
107
|
+
* @param line - The plain line.
|
|
108
|
+
* @param columns - The terminal width.
|
|
109
|
+
* @returns The line, cut so it can never wrap.
|
|
110
|
+
*/
|
|
111
|
+
function line_clip(line, columns) {
|
|
112
|
+
return line.length > columns ? line.slice(0, Math.max(0, columns - 1)) + '…' : line;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Composes one face frame as screen lines, top to bottom. Pure: no terminal
|
|
116
|
+
* control, no timers — the runtime places these under the alternate buffer
|
|
117
|
+
* and the tests read them directly.
|
|
118
|
+
*
|
|
119
|
+
* @param frame - The readings and geometry for this frame.
|
|
120
|
+
* @returns The lines to paint, at most `frame.rows - 1` of them.
|
|
121
|
+
*/
|
|
122
|
+
export function face_frameCompose(frame) {
|
|
123
|
+
return frame.phase === 'boot' ? bootFrame_compose(frame) : readyFrame_compose(frame);
|
|
124
|
+
}
|
|
125
|
+
/** Centers one plain line, colored after clipping. */
|
|
126
|
+
function centered_line(text, columns, paint) {
|
|
127
|
+
const clipped = line_clip(text, columns);
|
|
128
|
+
const pad = ' '.repeat(Math.max(0, Math.floor((columns - clipped.length) / 2)));
|
|
129
|
+
return pad + paint(clipped);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The boot flavor: the brain over a headline and a tall strip of the
|
|
133
|
+
* streaming boot log. (Kept for hosts that want an animated boot on the
|
|
134
|
+
* alternate screen; the chell daemon boot uses the classic in-scroll boot.)
|
|
135
|
+
*/
|
|
136
|
+
function bootFrame_compose(frame) {
|
|
137
|
+
const lines = [];
|
|
138
|
+
const stripHeight = frame.logTail.length > 0 ? frame.logTail.length + 1 : 0;
|
|
139
|
+
const brainFits = logoRows_count() + stripHeight + 5 <= frame.rows - 1 && logoColumns_count() <= frame.columns;
|
|
140
|
+
if (brainFits) {
|
|
141
|
+
const indent = ' '.repeat(Math.max(0, Math.floor((frame.columns - logoColumns_count()) / 2)));
|
|
142
|
+
for (const brainLine of logo_frameRender(frame.frameIndex)) {
|
|
143
|
+
lines.push(indent + brainLine);
|
|
144
|
+
}
|
|
145
|
+
lines.push('');
|
|
146
|
+
}
|
|
147
|
+
lines.push(centered_line('SYSTEMS INITIALIZING', frame.columns, chalk.cyan));
|
|
148
|
+
lines.push('');
|
|
149
|
+
if (frame.logTail.length > 0) {
|
|
150
|
+
lines.push(chalk.gray(' ' + '─'.repeat(Math.min(40, Math.max(0, frame.columns - 4)))));
|
|
151
|
+
for (const logLine of frame.logTail) {
|
|
152
|
+
lines.push(chalk.gray(line_clip(` ${logLine}`, frame.columns)));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return lines.slice(0, Math.max(0, frame.rows - 1));
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* The steady face, minimal by law: the animating brain, one status line,
|
|
159
|
+
* and the toggle hint — vertically centered. Everything informational (the
|
|
160
|
+
* addresses, the boot report) lives on the Esc side, verbatim.
|
|
161
|
+
*/
|
|
162
|
+
function readyFrame_compose(frame) {
|
|
163
|
+
const busy = frame.telemetry?.busy ?? false;
|
|
164
|
+
const status = busy ? 'DAEMON RUNNING · ENGINE EXECUTING' : 'DAEMON RUNNING · SESSION ESTABLISHED';
|
|
165
|
+
const hint = 'HIT ESC TO TOGGLE THE BOOT LOG · CTRL-C STOPS THE DAEMON';
|
|
166
|
+
const content = [];
|
|
167
|
+
const brainFits = logoRows_count() + 5 <= frame.rows - 1 && logoColumns_count() <= frame.columns;
|
|
168
|
+
if (brainFits) {
|
|
169
|
+
const indent = ' '.repeat(Math.max(0, Math.floor((frame.columns - logoColumns_count()) / 2)));
|
|
170
|
+
for (const brainLine of logo_frameRender(frame.frameIndex)) {
|
|
171
|
+
content.push(indent + brainLine);
|
|
172
|
+
}
|
|
173
|
+
content.push('');
|
|
174
|
+
}
|
|
175
|
+
content.push(centered_line(status, frame.columns, chalk.cyan));
|
|
176
|
+
content.push('');
|
|
177
|
+
content.push(centered_line(hint, frame.columns, chalk.gray));
|
|
178
|
+
const topPad = Math.max(0, Math.floor((frame.rows - 1 - content.length) / 2));
|
|
179
|
+
const lines = [...Array(topPad).fill(''), ...content];
|
|
180
|
+
return lines.slice(0, Math.max(0, frame.rows - 1));
|
|
181
|
+
}
|
|
182
|
+
let state = null;
|
|
183
|
+
/** Whether the console face currently owns the terminal. */
|
|
184
|
+
export function face_isActive() {
|
|
185
|
+
return state !== null;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Routes stray writes into the ring while the face owns the screen.
|
|
189
|
+
*/
|
|
190
|
+
function writes_hijack() {
|
|
191
|
+
if (state === null)
|
|
192
|
+
return;
|
|
193
|
+
const active = state;
|
|
194
|
+
const capture = ((chunk) => {
|
|
195
|
+
const text = typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf8');
|
|
196
|
+
active.ring.push(text);
|
|
197
|
+
return true;
|
|
198
|
+
});
|
|
199
|
+
process.stdout.write = capture;
|
|
200
|
+
process.stderr.write = capture;
|
|
201
|
+
}
|
|
202
|
+
/** Restores the terminal's own writes. */
|
|
203
|
+
function writes_restore() {
|
|
204
|
+
if (state === null)
|
|
205
|
+
return;
|
|
206
|
+
process.stdout.write = state.stdoutWrite;
|
|
207
|
+
process.stderr.write = state.stderrWrite;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Paints one frame: home the cursor, draw every composed line with a
|
|
211
|
+
* clear-to-end, then clear everything below. No full-screen erase per frame,
|
|
212
|
+
* so the repaint never flickers.
|
|
213
|
+
*/
|
|
214
|
+
function frame_paint() {
|
|
215
|
+
if (state === null)
|
|
216
|
+
return;
|
|
217
|
+
const telemetry = state.options.telemetry_get?.() ?? null;
|
|
218
|
+
if (telemetry !== null && telemetry.sessions !== state.lastSessions) {
|
|
219
|
+
state.flareUntil = Date.now() + FLARE_MS;
|
|
220
|
+
state.lastSessions = telemetry.sessions;
|
|
221
|
+
}
|
|
222
|
+
const quickened = (telemetry?.busy ?? false) || Date.now() < state.flareUntil;
|
|
223
|
+
state.tick += 1;
|
|
224
|
+
if (state.phase === 'boot') {
|
|
225
|
+
// Booting is frenetic: the pulse races while the machine comes up.
|
|
226
|
+
state.frameIndex += 2;
|
|
227
|
+
}
|
|
228
|
+
else if (quickened || state.tick % AMBIENT_STRIDE === 0) {
|
|
229
|
+
state.frameIndex += 1;
|
|
230
|
+
}
|
|
231
|
+
const lines = face_frameCompose({
|
|
232
|
+
rows: process.stdout.rows || 24,
|
|
233
|
+
columns: process.stdout.columns || 80,
|
|
234
|
+
frameIndex: state.frameIndex,
|
|
235
|
+
phase: state.phase,
|
|
236
|
+
info: state.options.info,
|
|
237
|
+
telemetry,
|
|
238
|
+
logTail: state.ring.tail(state.phase === 'boot' ? BOOT_STRIP_LINES : STRIP_LINES),
|
|
239
|
+
uptimeSeconds: Math.floor((Date.now() - state.startedAt) / 1000),
|
|
240
|
+
});
|
|
241
|
+
state.realOut('\x1b[H' + lines.map((line) => line + '\x1b[K').join('\r\n') + '\x1b[J');
|
|
242
|
+
}
|
|
243
|
+
/** Enters (or re-enters) the alternate-buffer face. */
|
|
244
|
+
function face_enter() {
|
|
245
|
+
if (state === null || state.mode === 'face')
|
|
246
|
+
return;
|
|
247
|
+
state.mode = 'face';
|
|
248
|
+
writes_hijack();
|
|
249
|
+
state.realOut('\x1b[?1049h\x1b[?25l');
|
|
250
|
+
frame_paint();
|
|
251
|
+
state.interval = setInterval(frame_paint, TICK_MS);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Drops to the normal buffer: the boot log restored exactly, the lines the
|
|
255
|
+
* face caged flushed beneath it, and a hint on the way back.
|
|
256
|
+
*/
|
|
257
|
+
function text_drop() {
|
|
258
|
+
if (state === null || state.mode === 'text')
|
|
259
|
+
return;
|
|
260
|
+
state.mode = 'text';
|
|
261
|
+
if (state.interval !== null) {
|
|
262
|
+
clearInterval(state.interval);
|
|
263
|
+
state.interval = null;
|
|
264
|
+
}
|
|
265
|
+
writes_restore();
|
|
266
|
+
state.realOut('\x1b[?1049l\x1b[?25h');
|
|
267
|
+
for (const line of state.ring.drain()) {
|
|
268
|
+
state.realOut(line + '\n');
|
|
269
|
+
}
|
|
270
|
+
state.realOut(chalk.gray('[face] any key returns to the console face; Ctrl-C stops the daemon\n'));
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Takes the daemon terminal over as the console face.
|
|
274
|
+
*
|
|
275
|
+
* A no-op off a TTY (systemd, nohup, a pipe): the daemon then logs
|
|
276
|
+
* sequentially exactly as before.
|
|
277
|
+
*
|
|
278
|
+
* @param options - Identity rows and live-telemetry hook.
|
|
279
|
+
* @returns True when the face took the screen.
|
|
280
|
+
*/
|
|
281
|
+
export function face_start(options, phase = 'ready') {
|
|
282
|
+
if (state !== null)
|
|
283
|
+
return true;
|
|
284
|
+
if (!process.stdout.isTTY || !process.stdin.isTTY || typeof process.stdin.setRawMode !== 'function') {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
const realOutWrite = process.stdout.write.bind(process.stdout);
|
|
288
|
+
const realErrWrite = process.stderr.write.bind(process.stderr);
|
|
289
|
+
const exitRestore = () => {
|
|
290
|
+
// The process is dying with the face up: put the operator's terminal
|
|
291
|
+
// back — normal buffer, visible cursor — whatever else is going on.
|
|
292
|
+
if (state === null)
|
|
293
|
+
return;
|
|
294
|
+
writes_restore();
|
|
295
|
+
if (state.mode === 'face')
|
|
296
|
+
realOutWrite('\x1b[?1049l\x1b[?25h');
|
|
297
|
+
};
|
|
298
|
+
const keyHandler = (data) => {
|
|
299
|
+
if (state === null)
|
|
300
|
+
return;
|
|
301
|
+
if (data.includes(0x03)) {
|
|
302
|
+
// Raw mode swallows the terminal's own Ctrl-C; re-raise it so the
|
|
303
|
+
// daemon's ordinary shutdown path (berth cleanup included) runs.
|
|
304
|
+
face_stop();
|
|
305
|
+
process.kill(process.pid, 'SIGINT');
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (state.mode === 'face') {
|
|
309
|
+
const key = data.toString('utf8');
|
|
310
|
+
if (key === 'q' || key === 'Q' || key === '\x1b')
|
|
311
|
+
text_drop();
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
face_enter();
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
state = {
|
|
318
|
+
options,
|
|
319
|
+
ring: new FaceLogRing(),
|
|
320
|
+
mode: 'text',
|
|
321
|
+
phase,
|
|
322
|
+
suspended: false,
|
|
323
|
+
interval: null,
|
|
324
|
+
frameIndex: 0,
|
|
325
|
+
tick: 0,
|
|
326
|
+
startedAt: Date.now(),
|
|
327
|
+
flareUntil: 0,
|
|
328
|
+
lastSessions: options.telemetry_get?.().sessions ?? 0,
|
|
329
|
+
realOut: (chunk) => { realOutWrite(chunk); },
|
|
330
|
+
realErr: (chunk) => { realErrWrite(chunk); },
|
|
331
|
+
stdoutWrite: process.stdout.write,
|
|
332
|
+
stderrWrite: process.stderr.write,
|
|
333
|
+
keyHandler,
|
|
334
|
+
exitRestore,
|
|
335
|
+
};
|
|
336
|
+
process.stdin.setRawMode(true);
|
|
337
|
+
process.stdin.resume();
|
|
338
|
+
process.stdin.on('data', keyHandler);
|
|
339
|
+
process.once('exit', exitRestore);
|
|
340
|
+
face_enter();
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Takes the terminal over for the boot phase: the brain in its frenetic
|
|
345
|
+
* boot pulse over a tall strip of the streaming boot log — no identity
|
|
346
|
+
* panel yet, because there is nothing to identify. A no-op off a TTY.
|
|
347
|
+
*
|
|
348
|
+
* @returns True when the boot face took the screen.
|
|
349
|
+
*/
|
|
350
|
+
export function face_boot() {
|
|
351
|
+
return face_start({ info: [] }, 'boot');
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Settles the face into its steady state: the calm ambient pulse, the
|
|
355
|
+
* identity panel, live telemetry, and the toggle hint. Called on a face
|
|
356
|
+
* already up (the boot phase) it repaints in place; called with no face
|
|
357
|
+
* (a host that skipped the boot phase) it starts one. Off a TTY, nothing.
|
|
358
|
+
*
|
|
359
|
+
* @param options - Identity rows and live-telemetry hook.
|
|
360
|
+
* @returns True when a face is showing the steady state.
|
|
361
|
+
*/
|
|
362
|
+
export function face_ready(options) {
|
|
363
|
+
if (state === null) {
|
|
364
|
+
return face_start(options);
|
|
365
|
+
}
|
|
366
|
+
state.options = options;
|
|
367
|
+
state.phase = 'ready';
|
|
368
|
+
state.lastSessions = options.telemetry_get?.().sessions ?? state.lastSessions;
|
|
369
|
+
if (state.mode === 'face')
|
|
370
|
+
frame_paint();
|
|
371
|
+
return true;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Steps aside for an interactive prompt: the terminal returns to the
|
|
375
|
+
* normal buffer with cooked stdin, and the face's key handler stops
|
|
376
|
+
* listening so the prompt's readline owns every keystroke.
|
|
377
|
+
*/
|
|
378
|
+
export function face_suspend() {
|
|
379
|
+
if (state === null || state.suspended)
|
|
380
|
+
return;
|
|
381
|
+
state.suspended = true;
|
|
382
|
+
if (state.mode !== 'text')
|
|
383
|
+
text_drop();
|
|
384
|
+
process.stdin.off('data', state.keyHandler);
|
|
385
|
+
if (typeof process.stdin.setRawMode === 'function')
|
|
386
|
+
process.stdin.setRawMode(false);
|
|
387
|
+
}
|
|
388
|
+
/** Returns from a prompt: raw keys again, and back onto the face. */
|
|
389
|
+
export function face_resume() {
|
|
390
|
+
if (state === null || !state.suspended)
|
|
391
|
+
return;
|
|
392
|
+
state.suspended = false;
|
|
393
|
+
if (typeof process.stdin.setRawMode === 'function')
|
|
394
|
+
process.stdin.setRawMode(true);
|
|
395
|
+
process.stdin.on('data', state.keyHandler);
|
|
396
|
+
face_enter();
|
|
397
|
+
}
|
|
398
|
+
/** Releases the terminal entirely: buffers, writes, raw mode, listeners. */
|
|
399
|
+
export function face_stop() {
|
|
400
|
+
if (state === null)
|
|
401
|
+
return;
|
|
402
|
+
if (state.interval !== null)
|
|
403
|
+
clearInterval(state.interval);
|
|
404
|
+
writes_restore();
|
|
405
|
+
if (state.mode === 'face')
|
|
406
|
+
state.realOut('\x1b[?1049l\x1b[?25h');
|
|
407
|
+
// Lines still caged in the ring (a dying boot's abort reason, say) land
|
|
408
|
+
// in the normal buffer rather than vanishing with the face.
|
|
409
|
+
for (const line of state.ring.drain()) {
|
|
410
|
+
state.realOut(line + '\n');
|
|
411
|
+
}
|
|
412
|
+
process.stdin.off('data', state.keyHandler);
|
|
413
|
+
if (typeof process.stdin.setRawMode === 'function')
|
|
414
|
+
process.stdin.setRawMode(false);
|
|
415
|
+
process.stdin.pause();
|
|
416
|
+
process.off('exit', state.exitRestore);
|
|
417
|
+
state = null;
|
|
418
|
+
}
|
|
419
|
+
//# sourceMappingURL=face.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"face.js","sourceRoot":"","sources":["../../src/daemon/face.ts"],"names":[],"mappings":"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"}
|
package/dist/daemon/launch.d.ts
CHANGED
|
@@ -3,6 +3,21 @@ import type { BrasaEngine } from '@fnndsc/brasa';
|
|
|
3
3
|
import { type OutputSink } from '@fnndsc/brasa';
|
|
4
4
|
import type { ProgressEvent } from '@fnndsc/brasa';
|
|
5
5
|
import { type Surface } from '@fnndsc/brasa';
|
|
6
|
+
/** What a launched daemon hands back to its host (the console face's feed). */
|
|
7
|
+
export interface DaemonLaunchInfo {
|
|
8
|
+
/** The CUBE identity this daemon hosts. */
|
|
9
|
+
identity: string;
|
|
10
|
+
/** The WebSocket wire address. */
|
|
11
|
+
url: string;
|
|
12
|
+
/** The attach token gating every session. */
|
|
13
|
+
token: string;
|
|
14
|
+
/** The berth file advertising this daemon. */
|
|
15
|
+
berthPath: string;
|
|
16
|
+
/** The ARGUS web-surface address, when a bundle is served. */
|
|
17
|
+
argusUrl: string | null;
|
|
18
|
+
/** The running daemon, for live telemetry. */
|
|
19
|
+
daemon: CalypsoDaemon;
|
|
20
|
+
}
|
|
6
21
|
/** The daemon sink forwards live command output to the executing surface. */
|
|
7
22
|
export declare class DaemonSink implements OutputSink {
|
|
8
23
|
private readonly daemon;
|
|
@@ -21,9 +36,9 @@ export declare class DaemonSink implements OutputSink {
|
|
|
21
36
|
* Builds the surface a daemon-hosted engine talks to.
|
|
22
37
|
*
|
|
23
38
|
* Interactivity is a surface capability: prompts, pipeline segments, shell
|
|
24
|
-
* escapes,
|
|
25
|
-
* the daemon's brokers, without knowing the transport. Nothing
|
|
26
|
-
* on the daemon host.
|
|
39
|
+
* escapes, local editing and file delivery reach the surface running the
|
|
40
|
+
* command through the daemon's brokers, without knowing the transport. Nothing
|
|
41
|
+
* ever spawns on the daemon host, and nothing is ever written to its disk.
|
|
27
42
|
*
|
|
28
43
|
* @param daemon - The daemon whose brokers deliver the capability requests.
|
|
29
44
|
* @returns The surface to install with `surface_set` on the daemon host.
|
|
@@ -35,7 +50,7 @@ export declare function daemonSurface_create(daemon: CalypsoDaemon): Surface;
|
|
|
35
50
|
* @param engine - The engine to host (already created and connected by boot).
|
|
36
51
|
* @param beforeListen - Optional host preparation run after the duplicate guard
|
|
37
52
|
* and before the socket binds or the berth is published.
|
|
38
|
-
* @returns
|
|
39
|
-
* then stays alive on the WebSocket server.
|
|
53
|
+
* @returns The launched daemon's addresses and handle, once it is listening;
|
|
54
|
+
* the process then stays alive on the WebSocket server.
|
|
40
55
|
*/
|
|
41
|
-
export declare function daemon_launch(engine: BrasaEngine, beforeListen?: () => Promise<void>): Promise<
|
|
56
|
+
export declare function daemon_launch(engine: BrasaEngine, beforeListen?: () => Promise<void>): Promise<DaemonLaunchInfo>;
|
package/dist/daemon/launch.js
CHANGED
|
@@ -15,14 +15,15 @@ import { hostname } from 'node:os';
|
|
|
15
15
|
import * as path from 'node:path';
|
|
16
16
|
import chalk from 'chalk';
|
|
17
17
|
import { CalypsoDaemon } from './server.js';
|
|
18
|
-
import { webRoot_resolve } from './static.js';
|
|
18
|
+
import { bundledWebRoot_find, webRoot_resolve } from './static.js';
|
|
19
19
|
import { token_generate } from './token.js';
|
|
20
20
|
import { sink_set } from '@fnndsc/brasa';
|
|
21
21
|
import { surface_set } from '@fnndsc/brasa';
|
|
22
|
-
import { sessionPromptContext_build } from '@fnndsc/brasa';
|
|
22
|
+
import { procIndex_snapshot, sessionPromptContext_build } from '@fnndsc/brasa';
|
|
23
23
|
import { welcomeLine_build, versionReport_build, versions_get, buildHash_get, fortune_random } from '@fnndsc/brasa';
|
|
24
24
|
import { chrisContext } from '@fnndsc/cumin';
|
|
25
25
|
import { identity_forSession, berth_write, berth_read, berth_path, berthUrl_isAlive, DISCONNECTED_IDENTITY } from './berth.js';
|
|
26
|
+
import { attachFile_write, attachFile_remove } from './attachFile.js';
|
|
26
27
|
/** The daemon sink forwards live command output to the executing surface. */
|
|
27
28
|
export class DaemonSink {
|
|
28
29
|
daemon;
|
|
@@ -46,26 +47,40 @@ export class DaemonSink {
|
|
|
46
47
|
* Builds the surface a daemon-hosted engine talks to.
|
|
47
48
|
*
|
|
48
49
|
* Interactivity is a surface capability: prompts, pipeline segments, shell
|
|
49
|
-
* escapes,
|
|
50
|
-
* the daemon's brokers, without knowing the transport. Nothing
|
|
51
|
-
* on the daemon host.
|
|
50
|
+
* escapes, local editing and file delivery reach the surface running the
|
|
51
|
+
* command through the daemon's brokers, without knowing the transport. Nothing
|
|
52
|
+
* ever spawns on the daemon host, and nothing is ever written to its disk.
|
|
52
53
|
*
|
|
53
54
|
* @param daemon - The daemon whose brokers deliver the capability requests.
|
|
54
55
|
* @returns The surface to install with `surface_set` on the daemon host.
|
|
55
56
|
*/
|
|
56
57
|
export function daemonSurface_create(daemon) {
|
|
57
58
|
return {
|
|
58
|
-
capabilities
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
// A daemon has no capabilities of its own to offer. Those that vary by
|
|
60
|
+
// surface are read from whichever one is executing, as it declared them on
|
|
61
|
+
// attach — a browser and a remote shell differ, and answering for both
|
|
62
|
+
// would either archive a directory a shell wanted whole or hand a browser
|
|
63
|
+
// a tree it cannot hold.
|
|
64
|
+
get capabilities() {
|
|
65
|
+
const attached = daemon.capabilities_current();
|
|
66
|
+
return {
|
|
67
|
+
hiddenInput: true,
|
|
68
|
+
localEdit: true,
|
|
69
|
+
tty: true,
|
|
70
|
+
pipeSegments: true,
|
|
71
|
+
shellCommands: true,
|
|
72
|
+
fileDelivery: attached?.fileDelivery ?? false,
|
|
73
|
+
// The daemon's disk belongs to nobody attending this session, so a
|
|
74
|
+
// builtin that would write must deliver through the surface instead.
|
|
75
|
+
engineFilesystem: false,
|
|
76
|
+
localFilesystem: attached?.localFilesystem ?? false,
|
|
77
|
+
};
|
|
64
78
|
},
|
|
65
79
|
prompt: (request) => daemon.prompt_current(request.message, request.hidden ?? false),
|
|
66
80
|
pipeSegment: (command, input) => daemon.pipe_current(command, input),
|
|
67
81
|
shellCommand: (command) => daemon.shell_current(command),
|
|
68
82
|
localEdit: (request) => daemon.edit_current(request.content, request.extension),
|
|
83
|
+
fileDeliver: (request) => daemon.deliver_current(request),
|
|
69
84
|
};
|
|
70
85
|
}
|
|
71
86
|
/**
|
|
@@ -74,8 +89,8 @@ export function daemonSurface_create(daemon) {
|
|
|
74
89
|
* @param engine - The engine to host (already created and connected by boot).
|
|
75
90
|
* @param beforeListen - Optional host preparation run after the duplicate guard
|
|
76
91
|
* and before the socket binds or the berth is published.
|
|
77
|
-
* @returns
|
|
78
|
-
* then stays alive on the WebSocket server.
|
|
92
|
+
* @returns The launched daemon's addresses and handle, once it is listening;
|
|
93
|
+
* the process then stays alive on the WebSocket server.
|
|
79
94
|
*/
|
|
80
95
|
export async function daemon_launch(engine, beforeListen) {
|
|
81
96
|
// Force color into the engine's rendered text: no TTY here to auto-detect.
|
|
@@ -105,12 +120,14 @@ export async function daemon_launch(engine, beforeListen) {
|
|
|
105
120
|
}
|
|
106
121
|
const token = token_generate();
|
|
107
122
|
// The web surface (argus) is served from the same port as the wire when a
|
|
108
|
-
// built bundle is found: an explicit CALYPSO_WEB_ROOT wins,
|
|
109
|
-
//
|
|
110
|
-
// `chell --daemon` serves the surface
|
|
123
|
+
// built bundle is found: an explicit CALYPSO_WEB_ROOT wins, then the working
|
|
124
|
+
// directory, then the checkout enclosing this module — so a dev-tree
|
|
125
|
+
// `chell --daemon` serves the surface from any directory, with no
|
|
126
|
+
// configuration at all.
|
|
111
127
|
const webRoot = webRoot_resolve([
|
|
112
128
|
process.env['CALYPSO_WEB_ROOT'],
|
|
113
129
|
path.join(process.cwd(), 'apps', 'argus', 'dist'),
|
|
130
|
+
bundledWebRoot_find() ?? undefined,
|
|
114
131
|
]);
|
|
115
132
|
// Loopback is the posture; CALYPSO_BIND is a deliberate, per-launch
|
|
116
133
|
// opt-out for demos on a trusted network. The attach token still gates
|
|
@@ -125,7 +142,10 @@ export async function daemon_launch(engine, beforeListen) {
|
|
|
125
142
|
...(webRoot !== null ? { webRoot } : {}),
|
|
126
143
|
// Only the daemon holds the session context, so it renders the themed
|
|
127
144
|
// prompt and pushes it to surfaces.
|
|
128
|
-
|
|
145
|
+
telemetryProvider: procIndex_snapshot,
|
|
146
|
+
promptProvider: (last) => sessionPromptContext_build(last !== undefined
|
|
147
|
+
? { lastCommandDurationMs: last.durationMs, lastExitCode: last.exitCode }
|
|
148
|
+
: {}),
|
|
129
149
|
// Report this process's own versions and build hash so attaching surfaces
|
|
130
150
|
// greet with the daemon's truth rather than their local install's.
|
|
131
151
|
stack: { ...versions_get(), build: buildHash_get() },
|
|
@@ -133,10 +153,35 @@ export async function daemon_launch(engine, beforeListen) {
|
|
|
133
153
|
sink_set(new DaemonSink(daemon));
|
|
134
154
|
surface_set(daemonSurface_create(daemon));
|
|
135
155
|
const port = await daemon.start();
|
|
136
|
-
|
|
156
|
+
// The berth records where this daemon can actually be reached. Loopback is
|
|
157
|
+
// right for the ordinary case and wrong the moment CALYPSO_BIND opens the
|
|
158
|
+
// wire to the network: a berth copied to another machine, or an address
|
|
159
|
+
// pasted into `chell --remote --attach`, has to name a host that resolves
|
|
160
|
+
// from somewhere else.
|
|
161
|
+
const wireHost = bindHost === '0.0.0.0' ? hostname() : bindHost;
|
|
162
|
+
const url = `ws://${wireHost}:${port}`;
|
|
137
163
|
const berth = { identity, url, token };
|
|
138
164
|
berth_write(berth);
|
|
139
165
|
const attachHint = identity === DISCONNECTED_IDENTITY ? '' : ` ${identity}`;
|
|
166
|
+
// The boot animation keeps repainting this terminal, and scrollback dies with
|
|
167
|
+
// the next clear. The same addresses go to a file so they can be read back
|
|
168
|
+
// without fighting either.
|
|
169
|
+
const noteLines = [
|
|
170
|
+
`identity: ${identity}`,
|
|
171
|
+
`wire: ${url}`,
|
|
172
|
+
`token: ${token}`,
|
|
173
|
+
...(webRoot !== null
|
|
174
|
+
? [`ARGUS: http://${bindHost === '0.0.0.0' ? hostname() : bindHost}:${port}/?token=${token}`]
|
|
175
|
+
: []),
|
|
176
|
+
`attach: chell --remote --attach ${url} --token ${token}`,
|
|
177
|
+
];
|
|
178
|
+
const notePath = attachFile_write(noteLines);
|
|
179
|
+
if (notePath !== null) {
|
|
180
|
+
const cleanup = () => attachFile_remove();
|
|
181
|
+
process.once('exit', cleanup);
|
|
182
|
+
process.once('SIGINT', () => { cleanup(); process.exit(0); });
|
|
183
|
+
process.once('SIGTERM', () => { cleanup(); process.exit(0); });
|
|
184
|
+
}
|
|
140
185
|
console.log(chalk.bold.cyan(welcomeLine_build('calypso')));
|
|
141
186
|
for (const line of versionReport_build().split('\n')) {
|
|
142
187
|
console.log(chalk.gray(` ${line}`));
|
|
@@ -147,6 +192,12 @@ export async function daemon_launch(engine, beforeListen) {
|
|
|
147
192
|
console.log(chalk.gray(` token: ${token}`));
|
|
148
193
|
console.log(chalk.gray(` berth: ${berth_path(identity)}`));
|
|
149
194
|
console.log(chalk.gray(` attach a surface with: chell --remote${attachHint}`));
|
|
195
|
+
if (bindHost !== '127.0.0.1') {
|
|
196
|
+
// Berth discovery is a file in this host's runtime directory, so it cannot
|
|
197
|
+
// reach another machine. An explicit address is how a surface elsewhere
|
|
198
|
+
// attaches, and it is worth printing ready to paste.
|
|
199
|
+
console.log(chalk.gray(` from another machine: chell --remote --attach ${url} --token ${token}`));
|
|
200
|
+
}
|
|
150
201
|
if (webRoot !== null) {
|
|
151
202
|
// A wildcard bind has no routable form for a URL; name the machine so
|
|
152
203
|
// the printed address works from another host.
|
|
@@ -154,5 +205,18 @@ export async function daemon_launch(engine, beforeListen) {
|
|
|
154
205
|
console.log(chalk.green(`[+] ARGUS web surface at http://${displayHost}:${port}/?token=${token}`));
|
|
155
206
|
console.log(chalk.gray(` serving: ${webRoot}`));
|
|
156
207
|
}
|
|
208
|
+
if (notePath !== null) {
|
|
209
|
+
console.log(chalk.gray(` these lines also in: ${notePath}`));
|
|
210
|
+
console.log(chalk.gray(' or run: calypso --berths'));
|
|
211
|
+
}
|
|
212
|
+
const displayHost = bindHost === '0.0.0.0' ? hostname() : bindHost;
|
|
213
|
+
return {
|
|
214
|
+
identity,
|
|
215
|
+
url,
|
|
216
|
+
token,
|
|
217
|
+
berthPath: berth_path(identity),
|
|
218
|
+
argusUrl: webRoot !== null ? `http://${displayHost}:${port}/?token=${token}` : null,
|
|
219
|
+
daemon,
|
|
220
|
+
};
|
|
157
221
|
}
|
|
158
222
|
//# sourceMappingURL=launch.js.map
|