@promptbook/cli 0.112.0-96 → 0.112.0-98

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.
Files changed (79) hide show
  1. package/apps/agents-server/playwright.config.ts +2 -1
  2. package/apps/agents-server/src/app/admin/cli-access/CliAccessClient.tsx +99 -0
  3. package/apps/agents-server/src/app/admin/cli-access/page.tsx +14 -0
  4. package/apps/agents-server/src/app/admin/code-runners/CodeRunnersClient.tsx +124 -34
  5. package/apps/agents-server/src/app/admin/servers/CreateServerDialog.tsx +46 -505
  6. package/apps/agents-server/src/app/admin/servers/ServersClient.tsx +23 -11
  7. package/apps/agents-server/src/app/admin/servers/ServersRegistryApi.ts +5 -0
  8. package/apps/agents-server/src/app/admin/servers/ServersRegistryDnsTypes.ts +87 -0
  9. package/apps/agents-server/src/app/admin/servers/ServersRegistryTable.tsx +258 -128
  10. package/apps/agents-server/src/app/admin/servers/useCreateServerWizard.ts +46 -334
  11. package/apps/agents-server/src/app/admin/servers/useServersRegistryState.ts +26 -2
  12. package/apps/agents-server/src/app/admin/update/UpdateClient.tsx +435 -0
  13. package/apps/agents-server/src/app/admin/update/page.tsx +14 -0
  14. package/apps/agents-server/src/app/agents/[agentName]/chat/CanonicalAgentChatSurface.tsx +24 -0
  15. package/apps/agents-server/src/app/api/admin/cli-access/route.ts +137 -0
  16. package/apps/agents-server/src/app/api/admin/code-runners/authentication/route.ts +140 -0
  17. package/apps/agents-server/src/app/api/admin/code-runners/route.ts +4 -35
  18. package/apps/agents-server/src/app/api/admin/servers/[serverId]/route.ts +7 -2
  19. package/apps/agents-server/src/app/api/admin/servers/route.ts +95 -4
  20. package/apps/agents-server/src/app/api/admin/update/route.ts +52 -0
  21. package/apps/agents-server/src/app/api/auth/login/route.ts +8 -0
  22. package/apps/agents-server/src/app/api/auth/logout/route.ts +10 -2
  23. package/apps/agents-server/src/app/api/chat/export/pdf/route.ts +63 -0
  24. package/apps/agents-server/src/app/page.tsx +10 -0
  25. package/apps/agents-server/src/components/AdminTerminal/AdminTerminalCard.tsx +279 -0
  26. package/apps/agents-server/src/components/AdminTerminal/useAdminTerminalSession.ts +336 -0
  27. package/apps/agents-server/src/components/Header/buildHeaderSystemMenuItems.ts +10 -0
  28. package/apps/agents-server/src/languages/ServerTranslationKeys.ts +2 -0
  29. package/apps/agents-server/src/languages/translations/czech.yaml +2 -0
  30. package/apps/agents-server/src/languages/translations/english.yaml +2 -0
  31. package/apps/agents-server/src/middleware.ts +32 -0
  32. package/apps/agents-server/src/tools/BrowserConnectionProvider.ts +1 -1
  33. package/apps/agents-server/src/utils/chatExport/downloadChatPdfFromServer.ts +59 -0
  34. package/apps/agents-server/src/utils/chatExport/renderHtmlToPdfOnServer.ts +37 -0
  35. package/apps/agents-server/src/utils/codeRunnerAuthentication.ts +234 -0
  36. package/apps/agents-server/src/utils/codeRunnerConfiguration.ts +67 -0
  37. package/apps/agents-server/src/utils/createInteractiveTerminalEventStream.ts +84 -0
  38. package/apps/agents-server/src/utils/interactiveTerminalSession.ts +442 -0
  39. package/apps/agents-server/src/utils/serverCliAccess.ts +221 -0
  40. package/apps/agents-server/src/utils/serverManagement/standaloneVpsServerMetadata.ts +145 -0
  41. package/apps/agents-server/src/utils/serverRegistry.ts +3 -2
  42. package/apps/agents-server/src/utils/session.ts +37 -9
  43. package/apps/agents-server/src/utils/shibboleth/createShibbolethAuthenticationLogPayload.ts +173 -0
  44. package/apps/agents-server/src/utils/shibboleth/writeShibbolethAuthenticationLog.ts +27 -0
  45. package/apps/agents-server/src/utils/standaloneVpsDnsDiagnostics.ts +258 -0
  46. package/apps/agents-server/src/utils/standaloneVpsRawIpBootstrap.ts +87 -0
  47. package/apps/agents-server/src/utils/vpsConfiguration.ts +87 -13
  48. package/apps/agents-server/src/utils/vpsSelfUpdate.ts +664 -0
  49. package/esm/apps/agents-server/src/utils/serverRegistry.d.ts +1 -1
  50. package/esm/index.es.js +7 -5
  51. package/esm/index.es.js.map +1 -1
  52. package/esm/src/book-components/Chat/Chat/ChatActionsBar.d.ts +2 -0
  53. package/esm/src/book-components/Chat/Chat/ChatProps.d.ts +6 -0
  54. package/esm/src/book-components/Chat/save/_common/ChatSaveFormatHandler.d.ts +35 -0
  55. package/esm/src/book-components/Chat/save/_common/createChatExportFilename.d.ts +11 -0
  56. package/esm/src/version.d.ts +1 -1
  57. package/package.json +1 -1
  58. package/src/book-components/Chat/Chat/Chat.tsx +2 -0
  59. package/src/book-components/Chat/Chat/ChatActionsBar.tsx +17 -9
  60. package/src/book-components/Chat/Chat/ChatProps.tsx +7 -0
  61. package/src/book-components/Chat/save/_common/ChatSaveFormatHandler.ts +40 -0
  62. package/src/book-components/Chat/save/_common/createChatExportFilename.ts +20 -0
  63. package/src/book-components/Chat/utils/renderMarkdown.ts +1 -3
  64. package/src/other/templates/getTemplatesPipelineCollection.ts +718 -790
  65. package/src/scrapers/document/DocumentScraper.ts +1 -1
  66. package/src/scrapers/document-legacy/LegacyDocumentScraper.ts +1 -1
  67. package/src/version.ts +2 -2
  68. package/src/versions.txt +2 -0
  69. package/umd/apps/agents-server/src/utils/serverRegistry.d.ts +1 -1
  70. package/umd/index.umd.js +7 -5
  71. package/umd/index.umd.js.map +1 -1
  72. package/umd/src/book-components/Chat/Chat/ChatActionsBar.d.ts +2 -0
  73. package/umd/src/book-components/Chat/Chat/ChatProps.d.ts +6 -0
  74. package/umd/src/book-components/Chat/save/_common/ChatSaveFormatHandler.d.ts +35 -0
  75. package/umd/src/book-components/Chat/save/_common/createChatExportFilename.d.ts +11 -0
  76. package/umd/src/version.d.ts +1 -1
  77. package/src/conversion/validation/_importPipeline.ts +0 -88
  78. /package/esm/src/conversion/validation/{_importPipeline.d.ts → _importPipeline.test.d.ts} +0 -0
  79. /package/umd/src/conversion/validation/{_importPipeline.d.ts → _importPipeline.test.d.ts} +0 -0
@@ -0,0 +1,442 @@
1
+ import { spawn, type ChildProcessWithoutNullStreams } from 'child_process';
2
+ import { randomUUID } from 'crypto';
3
+ import { EventEmitter } from 'events';
4
+ import { spaceTrim } from 'spacetrim';
5
+
6
+ const MAX_SESSION_OUTPUT_LENGTH = 200_000;
7
+ const SESSION_RETENTION_MILLISECONDS = 30 * 60 * 1000;
8
+
9
+ /**
10
+ * Serializable snapshot of one interactive terminal session.
11
+ */
12
+ export type InteractiveTerminalSessionSnapshot = {
13
+ /**
14
+ * Session identifier used by the browser UI.
15
+ */
16
+ readonly id: string;
17
+
18
+ /**
19
+ * Stable logical key reused to reconnect to the latest session of the same purpose.
20
+ */
21
+ readonly sessionKey: string;
22
+
23
+ /**
24
+ * Human-readable title shown by the admin UI.
25
+ */
26
+ readonly title: string;
27
+
28
+ /**
29
+ * Browser-safe session metadata.
30
+ */
31
+ readonly metadata: Readonly<Record<string, string>>;
32
+
33
+ /**
34
+ * Whether the interactive process is still active.
35
+ */
36
+ readonly isRunning: boolean;
37
+
38
+ /**
39
+ * Buffered session output shown in the admin terminal.
40
+ */
41
+ readonly output: string;
42
+
43
+ /**
44
+ * Session start timestamp in ISO format.
45
+ */
46
+ readonly startedAt: string;
47
+
48
+ /**
49
+ * Session finish timestamp in ISO format, when available.
50
+ */
51
+ readonly finishedAt: string | null;
52
+
53
+ /**
54
+ * Process exit code once the session has finished.
55
+ */
56
+ readonly exitCode: number | null;
57
+
58
+ /**
59
+ * Process signal once the session has finished.
60
+ */
61
+ readonly signal: NodeJS.Signals | null;
62
+ };
63
+
64
+ /**
65
+ * Output event emitted while the interactive terminal is running.
66
+ */
67
+ export type InteractiveTerminalOutputEvent = {
68
+ readonly type: 'output';
69
+ readonly chunk: string;
70
+ };
71
+
72
+ /**
73
+ * Exit event emitted when the interactive terminal finishes.
74
+ */
75
+ export type InteractiveTerminalExitEvent = {
76
+ readonly type: 'exit';
77
+ readonly snapshot: InteractiveTerminalSessionSnapshot;
78
+ };
79
+
80
+ /**
81
+ * Browser stream callbacks used by one subscribed UI client.
82
+ */
83
+ export type InteractiveTerminalSessionSubscriber = {
84
+ /**
85
+ * Called whenever new terminal output arrives.
86
+ */
87
+ readonly onOutput: (event: InteractiveTerminalOutputEvent) => void;
88
+
89
+ /**
90
+ * Called once the session exits.
91
+ */
92
+ readonly onExit: (event: InteractiveTerminalExitEvent) => void;
93
+ };
94
+
95
+ /**
96
+ * Options used when starting one managed terminal session.
97
+ */
98
+ export type StartInteractiveTerminalSessionOptions = {
99
+ /**
100
+ * Stable key used to reuse the currently running session of the same purpose.
101
+ */
102
+ readonly sessionKey: string;
103
+
104
+ /**
105
+ * Human-readable title shown by the admin UI.
106
+ */
107
+ readonly title: string;
108
+
109
+ /**
110
+ * Executable path or command name to spawn.
111
+ */
112
+ readonly command: string;
113
+
114
+ /**
115
+ * Raw process arguments.
116
+ */
117
+ readonly arguments?: ReadonlyArray<string>;
118
+
119
+ /**
120
+ * Working directory for the interactive process.
121
+ */
122
+ readonly cwd?: string;
123
+
124
+ /**
125
+ * Child-process environment.
126
+ */
127
+ readonly env?: NodeJS.ProcessEnv;
128
+
129
+ /**
130
+ * Browser-safe session metadata.
131
+ */
132
+ readonly metadata?: Readonly<Record<string, string>>;
133
+
134
+ /**
135
+ * Error shown when the current runtime cannot support the terminal.
136
+ */
137
+ readonly unavailableErrorMessage?: string;
138
+ };
139
+
140
+ /**
141
+ * Internal mutable session state stored in-process.
142
+ */
143
+ type InteractiveTerminalSession = {
144
+ readonly id: string;
145
+ readonly sessionKey: string;
146
+ readonly title: string;
147
+ readonly metadata: Readonly<Record<string, string>>;
148
+ readonly process: ChildProcessWithoutNullStreams;
149
+ readonly events: EventEmitter<{
150
+ output: [InteractiveTerminalOutputEvent];
151
+ exit: [InteractiveTerminalExitEvent];
152
+ }>;
153
+ readonly startedAt: Date;
154
+ cleanupTimeout: NodeJS.Timeout | null;
155
+ output: string;
156
+ isRunning: boolean;
157
+ finishedAt: Date | null;
158
+ exitCode: number | null;
159
+ signal: NodeJS.Signals | null;
160
+ };
161
+
162
+ /**
163
+ * Shared in-memory state reused across requests in the standalone server process.
164
+ */
165
+ type InteractiveTerminalState = {
166
+ readonly sessionsById: Map<string, InteractiveTerminalSession>;
167
+ readonly latestSessionIdByKey: Map<string, string>;
168
+ };
169
+
170
+ /**
171
+ * Starts one managed terminal session or reconnects to the running session with the same key.
172
+ *
173
+ * @param options - Terminal process settings.
174
+ * @returns Existing running session for the same key or a new one.
175
+ */
176
+ export function startInteractiveTerminalSession(
177
+ options: StartInteractiveTerminalSessionOptions,
178
+ ): InteractiveTerminalSessionSnapshot {
179
+ const existingSession = getLatestInteractiveTerminalSession(options.sessionKey);
180
+ if (existingSession?.isRunning) {
181
+ return existingSession;
182
+ }
183
+
184
+ if (process.platform !== 'linux') {
185
+ throw new Error(options.unavailableErrorMessage || 'Interactive terminal access is available only on Linux.');
186
+ }
187
+
188
+ const sessionId = randomUUID();
189
+ const childProcess = spawn(options.command, [...(options.arguments || [])], {
190
+ cwd: options.cwd,
191
+ env: options.env,
192
+ stdio: ['pipe', 'pipe', 'pipe'],
193
+ });
194
+
195
+ const session: InteractiveTerminalSession = {
196
+ id: sessionId,
197
+ sessionKey: options.sessionKey,
198
+ title: options.title,
199
+ metadata: options.metadata || {},
200
+ process: childProcess,
201
+ events: new EventEmitter(),
202
+ startedAt: new Date(),
203
+ cleanupTimeout: null,
204
+ output: '',
205
+ isRunning: true,
206
+ finishedAt: null,
207
+ exitCode: null,
208
+ signal: null,
209
+ };
210
+
211
+ const state = getInteractiveTerminalState();
212
+ state.sessionsById.set(sessionId, session);
213
+ state.latestSessionIdByKey.set(options.sessionKey, sessionId);
214
+
215
+ childProcess.stdout.setEncoding('utf-8');
216
+ childProcess.stderr.setEncoding('utf-8');
217
+ childProcess.stdout.on('data', (chunk: string) => appendInteractiveTerminalOutput(session, chunk));
218
+ childProcess.stderr.on('data', (chunk: string) => appendInteractiveTerminalOutput(session, chunk));
219
+ childProcess.on('close', (exitCode, signal) => finalizeInteractiveTerminalSession(session, exitCode, signal));
220
+ childProcess.on('error', (error) => {
221
+ appendInteractiveTerminalOutput(
222
+ session,
223
+ spaceTrim(`
224
+ Failed to start the interactive terminal process.
225
+
226
+ ${error.message}
227
+ `) + '\n',
228
+ );
229
+ });
230
+
231
+ return createInteractiveTerminalSessionSnapshot(session);
232
+ }
233
+
234
+ /**
235
+ * Returns the latest known session snapshot for one logical terminal key.
236
+ *
237
+ * @param sessionKey - Stable logical session key.
238
+ * @returns Serializable session snapshot or `null`.
239
+ */
240
+ export function getLatestInteractiveTerminalSession(sessionKey: string): InteractiveTerminalSessionSnapshot | null {
241
+ const sessionId = getInteractiveTerminalState().latestSessionIdByKey.get(sessionKey);
242
+ if (!sessionId) {
243
+ return null;
244
+ }
245
+
246
+ return getInteractiveTerminalSession(sessionId);
247
+ }
248
+
249
+ /**
250
+ * Looks up one managed terminal session by id.
251
+ *
252
+ * @param sessionId - Session identifier.
253
+ * @returns Serializable session snapshot or `null`.
254
+ */
255
+ export function getInteractiveTerminalSession(sessionId: string): InteractiveTerminalSessionSnapshot | null {
256
+ const session = getInteractiveTerminalState().sessionsById.get(sessionId);
257
+ return session ? createInteractiveTerminalSessionSnapshot(session) : null;
258
+ }
259
+
260
+ /**
261
+ * Subscribes one browser connection to live terminal events.
262
+ *
263
+ * @param sessionId - Session identifier.
264
+ * @param subscriber - Stream callbacks.
265
+ * @returns Cleanup callback.
266
+ */
267
+ export function subscribeToInteractiveTerminalSession(
268
+ sessionId: string,
269
+ subscriber: InteractiveTerminalSessionSubscriber,
270
+ ): (() => void) | null {
271
+ const session = getInteractiveTerminalState().sessionsById.get(sessionId);
272
+ if (!session) {
273
+ return null;
274
+ }
275
+
276
+ session.events.on('output', subscriber.onOutput);
277
+ session.events.on('exit', subscriber.onExit);
278
+
279
+ return () => {
280
+ session.events.off('output', subscriber.onOutput);
281
+ session.events.off('exit', subscriber.onExit);
282
+ };
283
+ }
284
+
285
+ /**
286
+ * Sends raw terminal input to a running managed session.
287
+ *
288
+ * @param sessionId - Session identifier.
289
+ * @param input - Raw text or control characters to write to stdin.
290
+ * @returns Updated session snapshot.
291
+ */
292
+ export function writeInteractiveTerminalSessionInput(
293
+ sessionId: string,
294
+ input: string,
295
+ ): InteractiveTerminalSessionSnapshot {
296
+ const session = getRequiredInteractiveTerminalSession(sessionId);
297
+
298
+ if (!session.isRunning) {
299
+ throw new Error('The terminal session has already finished.');
300
+ }
301
+
302
+ session.process.stdin.write(input);
303
+ return createInteractiveTerminalSessionSnapshot(session);
304
+ }
305
+
306
+ /**
307
+ * Stops one running managed terminal session.
308
+ *
309
+ * @param sessionId - Session identifier.
310
+ * @returns Updated session snapshot.
311
+ */
312
+ export function stopInteractiveTerminalSession(sessionId: string): InteractiveTerminalSessionSnapshot {
313
+ const session = getRequiredInteractiveTerminalSession(sessionId);
314
+
315
+ if (session.isRunning) {
316
+ session.process.kill('SIGTERM');
317
+ }
318
+
319
+ return createInteractiveTerminalSessionSnapshot(session);
320
+ }
321
+
322
+ /**
323
+ * Returns the mutable singleton state for managed terminal sessions.
324
+ *
325
+ * @returns Shared in-memory session state.
326
+ */
327
+ function getInteractiveTerminalState(): InteractiveTerminalState {
328
+ const globalState = globalThis as typeof globalThis & {
329
+ __promptbookInteractiveTerminalState?: InteractiveTerminalState;
330
+ };
331
+
332
+ if (!globalState.__promptbookInteractiveTerminalState) {
333
+ globalState.__promptbookInteractiveTerminalState = {
334
+ sessionsById: new Map(),
335
+ latestSessionIdByKey: new Map(),
336
+ };
337
+ }
338
+
339
+ return globalState.__promptbookInteractiveTerminalState;
340
+ }
341
+
342
+ /**
343
+ * Appends streamed output while keeping the session buffer size bounded.
344
+ *
345
+ * @param session - Active terminal session.
346
+ * @param rawChunk - Terminal chunk from stdout or stderr.
347
+ */
348
+ function appendInteractiveTerminalOutput(session: InteractiveTerminalSession, rawChunk: string | Buffer): void {
349
+ const chunk = normalizeInteractiveTerminalOutput(rawChunk.toString());
350
+ if (!chunk) {
351
+ return;
352
+ }
353
+
354
+ session.output = (session.output + chunk).slice(-MAX_SESSION_OUTPUT_LENGTH);
355
+ session.events.emit('output', {
356
+ type: 'output',
357
+ chunk,
358
+ });
359
+ }
360
+
361
+ /**
362
+ * Normalizes streamed terminal output for browser rendering.
363
+ *
364
+ * @param output - Raw process output chunk.
365
+ * @returns UI-friendly terminal text.
366
+ */
367
+ function normalizeInteractiveTerminalOutput(output: string): string {
368
+ return output.replace(/\r\n/gu, '\n').replace(/\r/gu, '\n');
369
+ }
370
+
371
+ /**
372
+ * Marks one terminal session as finished and schedules retention cleanup.
373
+ *
374
+ * @param session - Finished terminal session.
375
+ * @param exitCode - Process exit code.
376
+ * @param signal - Process signal.
377
+ */
378
+ function finalizeInteractiveTerminalSession(
379
+ session: InteractiveTerminalSession,
380
+ exitCode: number | null,
381
+ signal: NodeJS.Signals | null,
382
+ ): void {
383
+ session.isRunning = false;
384
+ session.finishedAt = new Date();
385
+ session.exitCode = exitCode;
386
+ session.signal = signal;
387
+
388
+ session.events.emit('exit', {
389
+ type: 'exit',
390
+ snapshot: createInteractiveTerminalSessionSnapshot(session),
391
+ });
392
+
393
+ if (session.cleanupTimeout) {
394
+ clearTimeout(session.cleanupTimeout);
395
+ }
396
+
397
+ session.cleanupTimeout = setTimeout(() => {
398
+ const state = getInteractiveTerminalState();
399
+ state.sessionsById.delete(session.id);
400
+ if (state.latestSessionIdByKey.get(session.sessionKey) === session.id) {
401
+ state.latestSessionIdByKey.delete(session.sessionKey);
402
+ }
403
+ }, SESSION_RETENTION_MILLISECONDS);
404
+ }
405
+
406
+ /**
407
+ * Reads one required mutable session and throws when missing.
408
+ *
409
+ * @param sessionId - Session identifier.
410
+ * @returns Mutable session state.
411
+ */
412
+ function getRequiredInteractiveTerminalSession(sessionId: string): InteractiveTerminalSession {
413
+ const session = getInteractiveTerminalState().sessionsById.get(sessionId);
414
+ if (!session) {
415
+ throw new Error('Terminal session was not found.');
416
+ }
417
+
418
+ return session;
419
+ }
420
+
421
+ /**
422
+ * Converts mutable terminal state into a browser-safe snapshot.
423
+ *
424
+ * @param session - Mutable in-memory session.
425
+ * @returns Serializable session snapshot.
426
+ */
427
+ function createInteractiveTerminalSessionSnapshot(
428
+ session: InteractiveTerminalSession,
429
+ ): InteractiveTerminalSessionSnapshot {
430
+ return {
431
+ id: session.id,
432
+ sessionKey: session.sessionKey,
433
+ title: session.title,
434
+ metadata: session.metadata,
435
+ isRunning: session.isRunning,
436
+ output: session.output,
437
+ startedAt: session.startedAt.toISOString(),
438
+ finishedAt: session.finishedAt ? session.finishedAt.toISOString() : null,
439
+ exitCode: session.exitCode,
440
+ signal: session.signal,
441
+ };
442
+ }
@@ -0,0 +1,221 @@
1
+ import { dirname } from 'path';
2
+ import {
3
+ createVpsInstallerCommandEnvironment,
4
+ resolveVpsEnvironmentFilePath,
5
+ resolveVpsInstallerScriptPath,
6
+ } from './vpsConfiguration';
7
+ import {
8
+ getInteractiveTerminalSession,
9
+ getLatestInteractiveTerminalSession,
10
+ startInteractiveTerminalSession,
11
+ stopInteractiveTerminalSession,
12
+ subscribeToInteractiveTerminalSession,
13
+ type InteractiveTerminalSessionSnapshot,
14
+ type InteractiveTerminalSessionSubscriber,
15
+ writeInteractiveTerminalSessionInput,
16
+ } from './interactiveTerminalSession';
17
+
18
+ const SERVER_CLI_ACCESS_SESSION_KEY = 'server-cli-access';
19
+
20
+ /**
21
+ * Serializable snapshot of one raw server CLI access session.
22
+ */
23
+ export type ServerCliAccessSessionSnapshot = {
24
+ /**
25
+ * Session identifier used by the browser UI.
26
+ */
27
+ readonly id: string;
28
+
29
+ /**
30
+ * Human-readable title shown in the super-admin UI.
31
+ */
32
+ readonly title: string;
33
+
34
+ /**
35
+ * Shell binary started for the session.
36
+ */
37
+ readonly shell: string;
38
+
39
+ /**
40
+ * Working directory used when the shell starts.
41
+ */
42
+ readonly workingDirectory: string;
43
+
44
+ /**
45
+ * Whether the interactive shell process is still active.
46
+ */
47
+ readonly isRunning: boolean;
48
+
49
+ /**
50
+ * Buffered session output shown in the admin terminal.
51
+ */
52
+ readonly output: string;
53
+
54
+ /**
55
+ * Session start timestamp in ISO format.
56
+ */
57
+ readonly startedAt: string;
58
+
59
+ /**
60
+ * Session finish timestamp in ISO format, when available.
61
+ */
62
+ readonly finishedAt: string | null;
63
+
64
+ /**
65
+ * Process exit code once the session has finished.
66
+ */
67
+ readonly exitCode: number | null;
68
+
69
+ /**
70
+ * Process signal once the session has finished.
71
+ */
72
+ readonly signal: NodeJS.Signals | null;
73
+ };
74
+
75
+ /**
76
+ * Browser stream callbacks used by one subscribed CLI-access UI client.
77
+ */
78
+ export type ServerCliAccessSessionSubscriber = {
79
+ /**
80
+ * Called whenever new terminal output arrives.
81
+ */
82
+ readonly onOutput: (event: Parameters<InteractiveTerminalSessionSubscriber['onOutput']>[0]) => void;
83
+
84
+ /**
85
+ * Called once the shell exits.
86
+ */
87
+ readonly onExit: (event: { readonly type: 'exit'; readonly snapshot: ServerCliAccessSessionSnapshot }) => void;
88
+ };
89
+
90
+ /**
91
+ * Starts or reconnects to the raw shell session exposed to the super-admin UI.
92
+ *
93
+ * @returns Existing running session or a new shell session.
94
+ */
95
+ export async function startServerCliAccessSession(): Promise<ServerCliAccessSessionSnapshot> {
96
+ const scriptPath = await resolveVpsInstallerScriptPath();
97
+ if (!scriptPath) {
98
+ throw new Error('The VPS installer script could not be found on this server.');
99
+ }
100
+
101
+ return toRequiredServerCliAccessSessionSnapshot(
102
+ startInteractiveTerminalSession({
103
+ sessionKey: SERVER_CLI_ACCESS_SESSION_KEY,
104
+ title: 'Server CLI Access',
105
+ command: 'bash',
106
+ arguments: [scriptPath, 'cli-shell'],
107
+ env: createVpsInstallerCommandEnvironment({
108
+ isNonInteractiveModeEnabled: false,
109
+ isProcessRestartEnabled: false,
110
+ }),
111
+ metadata: {
112
+ shell: 'bash',
113
+ workingDirectory: dirname(resolveVpsEnvironmentFilePath()),
114
+ },
115
+ unavailableErrorMessage: 'Raw CLI access is available only on the Linux VPS runtime.',
116
+ }),
117
+ );
118
+ }
119
+
120
+ /**
121
+ * Returns the latest raw CLI session snapshot when one exists.
122
+ *
123
+ * @returns Latest CLI access session or `null`.
124
+ */
125
+ export function getLatestServerCliAccessSession(): ServerCliAccessSessionSnapshot | null {
126
+ return toServerCliAccessSessionSnapshot(getLatestInteractiveTerminalSession(SERVER_CLI_ACCESS_SESSION_KEY));
127
+ }
128
+
129
+ /**
130
+ * Looks up one CLI access session by id.
131
+ *
132
+ * @param sessionId - Session identifier.
133
+ * @returns Session snapshot or `null`.
134
+ */
135
+ export function getServerCliAccessSession(sessionId: string): ServerCliAccessSessionSnapshot | null {
136
+ return toServerCliAccessSessionSnapshot(getInteractiveTerminalSession(sessionId));
137
+ }
138
+
139
+ /**
140
+ * Subscribes one browser connection to live raw-shell events.
141
+ *
142
+ * @param sessionId - Session identifier.
143
+ * @param subscriber - Stream callbacks.
144
+ * @returns Cleanup callback.
145
+ */
146
+ export function subscribeToServerCliAccessSession(
147
+ sessionId: string,
148
+ subscriber: ServerCliAccessSessionSubscriber,
149
+ ): (() => void) | null {
150
+ return subscribeToInteractiveTerminalSession(sessionId, {
151
+ onOutput: subscriber.onOutput,
152
+ onExit: ({ snapshot }) =>
153
+ subscriber.onExit({ type: 'exit', snapshot: toRequiredServerCliAccessSessionSnapshot(snapshot) }),
154
+ });
155
+ }
156
+
157
+ /**
158
+ * Sends raw terminal input to the running CLI access shell.
159
+ *
160
+ * @param sessionId - Session identifier.
161
+ * @param input - Raw text or control characters to write to stdin.
162
+ * @returns Updated session snapshot.
163
+ */
164
+ export function writeServerCliAccessSessionInput(sessionId: string, input: string): ServerCliAccessSessionSnapshot {
165
+ return toRequiredServerCliAccessSessionSnapshot(writeInteractiveTerminalSessionInput(sessionId, input));
166
+ }
167
+
168
+ /**
169
+ * Stops the active raw-shell session.
170
+ *
171
+ * @param sessionId - Session identifier.
172
+ * @returns Updated session snapshot.
173
+ */
174
+ export function stopServerCliAccessSession(sessionId: string): ServerCliAccessSessionSnapshot {
175
+ return toRequiredServerCliAccessSessionSnapshot(stopInteractiveTerminalSession(sessionId));
176
+ }
177
+
178
+ /**
179
+ * Converts one generic terminal snapshot into the CLI-access browser shape.
180
+ *
181
+ * @param session - Generic terminal snapshot.
182
+ * @returns CLI access snapshot or `null`.
183
+ */
184
+ function toServerCliAccessSessionSnapshot(
185
+ session: InteractiveTerminalSessionSnapshot | null,
186
+ ): ServerCliAccessSessionSnapshot | null {
187
+ if (!session) {
188
+ return null;
189
+ }
190
+
191
+ return {
192
+ id: session.id,
193
+ title: session.title,
194
+ shell: session.metadata.shell || 'bash',
195
+ workingDirectory: session.metadata.workingDirectory || '',
196
+ isRunning: session.isRunning,
197
+ output: session.output,
198
+ startedAt: session.startedAt,
199
+ finishedAt: session.finishedAt,
200
+ exitCode: session.exitCode,
201
+ signal: session.signal,
202
+ };
203
+ }
204
+
205
+ /**
206
+ * Converts one generic terminal snapshot into a required CLI-access snapshot.
207
+ *
208
+ * @param session - Generic terminal snapshot.
209
+ * @returns CLI access snapshot.
210
+ */
211
+ function toRequiredServerCliAccessSessionSnapshot(
212
+ session: InteractiveTerminalSessionSnapshot | null,
213
+ ): ServerCliAccessSessionSnapshot {
214
+ const mappedSession = toServerCliAccessSessionSnapshot(session);
215
+
216
+ if (!mappedSession) {
217
+ throw new Error('CLI access session was not found.');
218
+ }
219
+
220
+ return mappedSession;
221
+ }