@fnndsc/calypso 0.6.1 → 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.
@@ -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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"}
@@ -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, and local editing reach the surface running the command through
25
- * the daemon's brokers, without knowing the transport. Nothing ever spawns
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 A promise that resolves once the daemon is listening; the process
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<void>;
56
+ export declare function daemon_launch(engine: BrasaEngine, beforeListen?: () => Promise<void>): Promise<DaemonLaunchInfo>;
@@ -19,10 +19,11 @@ 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, and local editing reach the surface running the command through
50
- * the daemon's brokers, without knowing the transport. Nothing ever spawns
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
- hiddenInput: true,
60
- localEdit: true,
61
- tty: true,
62
- pipeSegments: true,
63
- shellCommands: true,
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 A promise that resolves once the daemon is listening; the process
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.
@@ -127,7 +142,10 @@ export async function daemon_launch(engine, beforeListen) {
127
142
  ...(webRoot !== null ? { webRoot } : {}),
128
143
  // Only the daemon holds the session context, so it renders the themed
129
144
  // prompt and pushes it to surfaces.
130
- promptProvider: () => sessionPromptContext_build(),
145
+ telemetryProvider: procIndex_snapshot,
146
+ promptProvider: (last) => sessionPromptContext_build(last !== undefined
147
+ ? { lastCommandDurationMs: last.durationMs, lastExitCode: last.exitCode }
148
+ : {}),
131
149
  // Report this process's own versions and build hash so attaching surfaces
132
150
  // greet with the daemon's truth rather than their local install's.
133
151
  stack: { ...versions_get(), build: buildHash_get() },
@@ -135,10 +153,35 @@ export async function daemon_launch(engine, beforeListen) {
135
153
  sink_set(new DaemonSink(daemon));
136
154
  surface_set(daemonSurface_create(daemon));
137
155
  const port = await daemon.start();
138
- const url = `ws://127.0.0.1:${port}`;
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}`;
139
163
  const berth = { identity, url, token };
140
164
  berth_write(berth);
141
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
+ }
142
185
  console.log(chalk.bold.cyan(welcomeLine_build('calypso')));
143
186
  for (const line of versionReport_build().split('\n')) {
144
187
  console.log(chalk.gray(` ${line}`));
@@ -149,6 +192,12 @@ export async function daemon_launch(engine, beforeListen) {
149
192
  console.log(chalk.gray(` token: ${token}`));
150
193
  console.log(chalk.gray(` berth: ${berth_path(identity)}`));
151
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
+ }
152
201
  if (webRoot !== null) {
153
202
  // A wildcard bind has no routable form for a URL; name the machine so
154
203
  // the printed address works from another host.
@@ -156,5 +205,18 @@ export async function daemon_launch(engine, beforeListen) {
156
205
  console.log(chalk.green(`[+] ARGUS web surface at http://${displayHost}:${port}/?token=${token}`));
157
206
  console.log(chalk.gray(` serving: ${webRoot}`));
158
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
+ };
159
221
  }
160
222
  //# sourceMappingURL=launch.js.map