@particle-academy/fancy-term-host 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 +136 -0
- package/dist/chunk-2DQJKTG5.js +127 -0
- package/dist/chunk-2DQJKTG5.js.map +1 -0
- package/dist/index.cjs +1182 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +777 -0
- package/dist/index.d.ts +777 -0
- package/dist/index.js +1015 -0
- package/dist/index.js.map +1 -0
- package/dist/pty-host.cjs +309 -0
- package/dist/pty-host.cjs.map +1 -0
- package/dist/pty-host.d.cts +2 -0
- package/dist/pty-host.d.ts +2 -0
- package/dist/pty-host.js +236 -0
- package/dist/pty-host.js.map +1 -0
- package/docs/persistence.md +92 -0
- package/docs/ports.md +229 -0
- package/package.json +79 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,777 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Ports — the injected interfaces that keep the terminal CORE runtime-agnostic.
|
|
5
|
+
*
|
|
6
|
+
* The terminal core (manager / sessions / shells / host-lifecycle / host-client)
|
|
7
|
+
* must never import `electron` or `../db`. Instead it receives these small ports
|
|
8
|
+
* and EMITS events for the things that used to be direct DB writes. Genie's
|
|
9
|
+
* adapter (genie-adapter.ts + ipc.ts) is the ONE place that builds the Electron /
|
|
10
|
+
* SQLite implementations and subscribes to the events to persist + broadcast.
|
|
11
|
+
*
|
|
12
|
+
* This is Phase 1 of the @particle-academy/fancy-term-host extraction (see
|
|
13
|
+
* .ai/_discovery/fancy-term-host-extraction.md §3): inverting the coupling so the
|
|
14
|
+
* core can be lifted into a package near-mechanically. The shapes here match the
|
|
15
|
+
* brief's §3 interfaces verbatim.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Read-only settings access. Replaces the core's old `getAllSettings()` reach
|
|
19
|
+
* into `../db`. The adapter implements `{ get: k => getAllSettings()[k] }`.
|
|
20
|
+
*/
|
|
21
|
+
interface SettingsProvider {
|
|
22
|
+
get(key: string): string | undefined;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* At-rest cipher for snapshot bytes (T1). The adapter wraps Electron
|
|
26
|
+
* `safeStorage`; a plain-node consumer could pass a passthrough or libsodium
|
|
27
|
+
* implementation. `isAvailable()` mirrors `safeStorage.isEncryptionAvailable()`
|
|
28
|
+
* so the core can still take the plaintext-magic fallback when encryption is
|
|
29
|
+
* unavailable, exactly as before.
|
|
30
|
+
*/
|
|
31
|
+
interface Encryptor {
|
|
32
|
+
isAvailable(): boolean;
|
|
33
|
+
encrypt(b: Buffer): Buffer;
|
|
34
|
+
decrypt(b: Buffer): Buffer;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Everything the snapshot store (T1) needs that used to come from `electron`:
|
|
38
|
+
* the base directory (was `app.getPath('userData')`) and the cipher (was
|
|
39
|
+
* `safeStorage`). The store appends `/sessions` under `baseDir` itself, matching
|
|
40
|
+
* the historical on-disk path.
|
|
41
|
+
*/
|
|
42
|
+
interface SnapshotStoreConfig {
|
|
43
|
+
baseDir: string;
|
|
44
|
+
encryptor: Encryptor;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The detached-host spawn surface (T3). The connect-or-spawn-or-fallback LOGIC
|
|
48
|
+
* is core; only these three OS/Electron-specific operations are injected:
|
|
49
|
+
* - resolveHostScript(): dev vs asar.unpacked path resolution.
|
|
50
|
+
* - spawnDetached(): the ABI-correct exec (Electron: execPath +
|
|
51
|
+
* ELECTRON_RUN_AS_NODE), detached + unref.
|
|
52
|
+
* - userDataDir(): the directory pidfile/socket live under (was
|
|
53
|
+
* app.getPath('userData')).
|
|
54
|
+
*/
|
|
55
|
+
interface HostSpawner {
|
|
56
|
+
resolveHostScript(): string | null;
|
|
57
|
+
spawnDetached(scriptPath: string, env: Record<string, string>): void;
|
|
58
|
+
userDataDir(): string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Shared terminal-subsystem types.
|
|
63
|
+
*
|
|
64
|
+
* Lifted out of manager.ts so both the in-process manager and the Tier 3
|
|
65
|
+
* HostClient (which proxies the same shapes over a socket) can reference them
|
|
66
|
+
* without a circular import through the PtyBackend interface.
|
|
67
|
+
*/
|
|
68
|
+
interface CreateTerminalOpts {
|
|
69
|
+
/** Stable id chosen by the renderer (ulid). The manager uses it as the key. */
|
|
70
|
+
id: string;
|
|
71
|
+
/** Working directory for the spawned shell. */
|
|
72
|
+
cwd: string;
|
|
73
|
+
/** Shell executable. Defaults to the user's preferred login shell per platform. */
|
|
74
|
+
shell?: string;
|
|
75
|
+
/** Extra args for the shell. */
|
|
76
|
+
args?: string[];
|
|
77
|
+
/** Initial cols × rows. Renderer should send a `resize` immediately after mount. */
|
|
78
|
+
cols?: number;
|
|
79
|
+
rows?: number;
|
|
80
|
+
/** Extra env overrides. Merged on top of process.env. */
|
|
81
|
+
env?: Record<string, string>;
|
|
82
|
+
}
|
|
83
|
+
interface TerminalInfo {
|
|
84
|
+
id: string;
|
|
85
|
+
pid: number;
|
|
86
|
+
shell: string;
|
|
87
|
+
}
|
|
88
|
+
interface AttachResult extends TerminalInfo {
|
|
89
|
+
/** True when an existing pty was returned (caller is "joining"). */
|
|
90
|
+
existing: boolean;
|
|
91
|
+
/** Bounded scrollback so a late-joining window can replay history. */
|
|
92
|
+
scrollback: string;
|
|
93
|
+
/**
|
|
94
|
+
* A previous-session snapshot to replay, present ONLY on a COLD spawn that
|
|
95
|
+
* found a snapshot on disk. On a warm reattach this is omitted — the live
|
|
96
|
+
* scrollback already covers the history. The renderer frames it as
|
|
97
|
+
* "— previous session —" then resets before the fresh shell.
|
|
98
|
+
*/
|
|
99
|
+
snapshot?: {
|
|
100
|
+
serialized: string;
|
|
101
|
+
savedAt: number;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* PtyBackend — the abstraction the IPC layer (main/terminal/ipc.ts), Tier 1
|
|
107
|
+
* (snapshots) and Tier 2 (retained set) talk to, instead of talking to a
|
|
108
|
+
* concrete pty pool directly.
|
|
109
|
+
*
|
|
110
|
+
* Two implementations:
|
|
111
|
+
*
|
|
112
|
+
* • InProcessBackend (the TerminalManager singleton) — node-pty instances live
|
|
113
|
+
* IN the Electron main process. This is today's behaviour and the T1/T2
|
|
114
|
+
* floor: snapshots survive a full quit, retained ptys survive a window
|
|
115
|
+
* detach (but die on a real quit, degrading to T1 snapshots).
|
|
116
|
+
*
|
|
117
|
+
* • HostClient (Tier 3) — node-pty instances live in a DETACHED headless
|
|
118
|
+
* pty-host process. The backend proxies every call over a local socket; the
|
|
119
|
+
* ptys (and the host's own scrollback ring buffer) SURVIVE A FULL QUIT of
|
|
120
|
+
* the Electron app, so reopening reattaches to the still-running shells.
|
|
121
|
+
*
|
|
122
|
+
* ipc.ts is oblivious to which one it holds: same method names, same shapes,
|
|
123
|
+
* same data/exit event subscription. The active backend is chosen once at
|
|
124
|
+
* startup (selectBackend, see manager.ts) and swapped behind this interface.
|
|
125
|
+
*
|
|
126
|
+
* The data/exit subscription mirrors EventEmitter semantics so the InProcess
|
|
127
|
+
* backend can BE an EventEmitter and satisfy this for free, while HostClient
|
|
128
|
+
* fans host-pushed messages out to the same callback shape.
|
|
129
|
+
*/
|
|
130
|
+
interface PtyBackend {
|
|
131
|
+
/** Spawn (or rejoin an existing) pty for opts.id. */
|
|
132
|
+
create(opts: CreateTerminalOpts): AttachResult;
|
|
133
|
+
write(id: string, data: string): boolean;
|
|
134
|
+
resize(id: string, cols: number, rows: number): boolean;
|
|
135
|
+
/** Explicit kill (user delete). Clears retained flag + scrollback. */
|
|
136
|
+
kill(id: string): boolean;
|
|
137
|
+
/** Tear down every pty. Called on a real quit for the in-process backend;
|
|
138
|
+
* a NO-OP for the host client (the whole point of T3 is they survive). */
|
|
139
|
+
killAll(): void;
|
|
140
|
+
list(): TerminalInfo[];
|
|
141
|
+
isLive(id: string): boolean;
|
|
142
|
+
setRetained(id: string, retained: boolean): void;
|
|
143
|
+
isRetained(id: string): boolean;
|
|
144
|
+
retainedCount(): number;
|
|
145
|
+
retainedIds(): string[];
|
|
146
|
+
getScrollback(id: string): string | undefined;
|
|
147
|
+
/** Subscribe to pushed pty output. */
|
|
148
|
+
on(event: 'data', listener: (id: string, data: string) => void): this;
|
|
149
|
+
/** Subscribe to pty exit. */
|
|
150
|
+
on(event: 'exit', listener: (id: string, payload: {
|
|
151
|
+
exitCode: number;
|
|
152
|
+
signal?: number;
|
|
153
|
+
}) => void): this;
|
|
154
|
+
/**
|
|
155
|
+
* Subscribe to live-cwd changes learned from OSC-7 (debounced). Replaces the
|
|
156
|
+
* core's old direct `updateTerminalSpec({ live_cwd })` write — the adapter
|
|
157
|
+
* subscribes here and persists. The in-process backend emits this; the host
|
|
158
|
+
* client simply never fires it (the detached host tracks cwd on its own side
|
|
159
|
+
* and the client doesn't observe OSC-7), so a no-op subscription is harmless.
|
|
160
|
+
*/
|
|
161
|
+
on(event: 'cwd', listener: (id: string, cwd: string) => void): this;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* The typed core event surface (brief §3). The in-process backend EMITS these;
|
|
165
|
+
* Genie's adapter SUBSCRIBES and persists/broadcasts:
|
|
166
|
+
* - 'data' (id, data) — pushed pty output (unchanged)
|
|
167
|
+
* - 'exit' (id, { exitCode, signal }) — pty exit (unchanged)
|
|
168
|
+
* - 'cwd' (id, cwd) — was updateTerminalSpec({live_cwd})
|
|
169
|
+
* - 'snapshot' (id, bytes, at) — was the snapshot-pointer write in ipc
|
|
170
|
+
* - 'host-status' (status) — was the BrowserWindow fallback toast
|
|
171
|
+
*
|
|
172
|
+
* `snapshot` is emitted by the snapshot persistence path and `host-status` by
|
|
173
|
+
* the host lifecycle; both are surfaced here as the contract a packaged core
|
|
174
|
+
* exposes. In Genie's Phase-1 adapter the snapshot write + toast broadcast still
|
|
175
|
+
* originate in the adapter (ipc.ts / the injected host-status sink), so these two
|
|
176
|
+
* are documented rather than carried on PtyBackend itself.
|
|
177
|
+
*/
|
|
178
|
+
interface HostStatus {
|
|
179
|
+
message: string;
|
|
180
|
+
level: 'info' | 'warn';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
interface SnapshotRead {
|
|
184
|
+
serialized: string;
|
|
185
|
+
/** Epoch ms the file was last written (from its mtime). */
|
|
186
|
+
savedAt: number;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* The snapshot persistence surface (T1). Returned by createSnapshotStore so the
|
|
190
|
+
* core can read/write/delete snapshots without knowing where the bytes live or
|
|
191
|
+
* how they're encrypted.
|
|
192
|
+
*/
|
|
193
|
+
interface SnapshotStore {
|
|
194
|
+
/**
|
|
195
|
+
* Persist a snapshot for `id`. Returns the on-disk byte size (so the caller
|
|
196
|
+
* can record `snapshot_bytes`), or null when nothing was written (empty
|
|
197
|
+
* input or an I/O error — never throws).
|
|
198
|
+
*/
|
|
199
|
+
writeSnapshot(id: string, serialized: string): number | null;
|
|
200
|
+
/**
|
|
201
|
+
* Read a snapshot for `id`, or null when absent / unreadable / corrupt.
|
|
202
|
+
* Never throws.
|
|
203
|
+
*/
|
|
204
|
+
readSnapshot(id: string): SnapshotRead | null;
|
|
205
|
+
/** Best-effort delete. Never throws; a missing file is success. */
|
|
206
|
+
deleteSnapshot(id: string): void;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Build a SnapshotStore bound to the given base directory + Encryptor. All the
|
|
210
|
+
* gzip / trim / `.plain`-fallback / read-tolerates-corrupt logic is UNCHANGED
|
|
211
|
+
* from the original module — only `app.getPath` → `config.baseDir` and
|
|
212
|
+
* `safeStorage.*` → `config.encryptor.*`.
|
|
213
|
+
*/
|
|
214
|
+
declare function createSnapshotStore(config: SnapshotStoreConfig): SnapshotStore;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Dependencies the in-process backend needs that used to be direct `../db` /
|
|
218
|
+
* `electron` reaches: a SettingsProvider (cwd-hook gating) and a SnapshotStore
|
|
219
|
+
* (cold-spawn restore). Injected by the composition root via
|
|
220
|
+
* configureInProcessBackend; defaults are inert so a test/pre-config load is
|
|
221
|
+
* harmless (no settings → cwd hook degrades to {}, no-op snapshot store → no
|
|
222
|
+
* restore), preserving the historical "db not ready → best-effort" behaviour.
|
|
223
|
+
*/
|
|
224
|
+
interface BackendDeps {
|
|
225
|
+
settings: SettingsProvider;
|
|
226
|
+
snapshots: SnapshotStore;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* InProcessBackend — node-pty instances owned directly by the Electron main
|
|
230
|
+
* process. This is the historical TerminalManager body verbatim; it now also
|
|
231
|
+
* formally implements the PtyBackend interface so the IPC layer can hold it
|
|
232
|
+
* behind that abstraction interchangeably with the Tier 3 HostClient.
|
|
233
|
+
*
|
|
234
|
+
* EventEmitter gives us the `on('data'|'exit', …)` half of PtyBackend for free.
|
|
235
|
+
*/
|
|
236
|
+
declare class InProcessBackend extends EventEmitter implements PtyBackend {
|
|
237
|
+
private deps;
|
|
238
|
+
constructor(deps?: BackendDeps);
|
|
239
|
+
/** Swap injected deps (only used if configure lands after lazy construction). */
|
|
240
|
+
setDeps(deps: BackendDeps): void;
|
|
241
|
+
private readonly ptys;
|
|
242
|
+
private readonly scrollback;
|
|
243
|
+
private readonly shells;
|
|
244
|
+
/** Last cwd reported by each pty via OSC-7 (in-memory, authoritative). */
|
|
245
|
+
private readonly liveCwd;
|
|
246
|
+
/** Pending debounced cwd-persist timers, keyed by terminal id. */
|
|
247
|
+
private readonly cwdTimers;
|
|
248
|
+
/**
|
|
249
|
+
* Tier 2: ids that must keep their pty alive even with zero attached
|
|
250
|
+
* windows (a disabled-but-retained terminal — e.g. a dev server the user
|
|
251
|
+
* suspended). The IPC layer consults this in detachOwner: a retained id is
|
|
252
|
+
* left running on the last detach instead of killed, so re-enable reattaches
|
|
253
|
+
* to the LIVE session (scrollback replays) rather than spawning fresh.
|
|
254
|
+
* Insertion order is preserved so the cap can evict the oldest if needed.
|
|
255
|
+
*/
|
|
256
|
+
private readonly retained;
|
|
257
|
+
/**
|
|
258
|
+
* Spawn a new pty for the given id, OR return the existing one if a
|
|
259
|
+
* window has already attached. Idempotent: a Stage window can attach
|
|
260
|
+
* to a spec that TheFloor is already running and get the same live
|
|
261
|
+
* shell + a buffered scrollback to catch up.
|
|
262
|
+
*/
|
|
263
|
+
create(opts: CreateTerminalOpts): AttachResult;
|
|
264
|
+
private scheduleCwdPersist;
|
|
265
|
+
private cleanupCwd;
|
|
266
|
+
/** Last cwd reported by this pty via OSC-7, or undefined when unknown. */
|
|
267
|
+
getLiveCwd(id: string): string | undefined;
|
|
268
|
+
write(id: string, data: string): boolean;
|
|
269
|
+
resize(id: string, cols: number, rows: number): boolean;
|
|
270
|
+
kill(id: string): boolean;
|
|
271
|
+
killAll(): void;
|
|
272
|
+
list(): TerminalInfo[];
|
|
273
|
+
isLive(id: string): boolean;
|
|
274
|
+
/**
|
|
275
|
+
* Mark/unmark a terminal as retained. A retained terminal's pty is kept
|
|
276
|
+
* alive by the IPC layer even when its last window detaches. Returns the
|
|
277
|
+
* resulting retained-id set size. Retaining a terminal that isn't live is
|
|
278
|
+
* harmless (the flag simply has no pty to protect yet).
|
|
279
|
+
*/
|
|
280
|
+
setRetained(id: string, retained: boolean): void;
|
|
281
|
+
isRetained(id: string): boolean;
|
|
282
|
+
/** Number of currently-retained terminals (for the resource cap). */
|
|
283
|
+
retainedCount(): number;
|
|
284
|
+
/** Snapshot of retained ids in insertion order (oldest first). */
|
|
285
|
+
retainedIds(): string[];
|
|
286
|
+
/**
|
|
287
|
+
* Buffered scrollback for a live pty (raw ANSI text), or undefined when the
|
|
288
|
+
* id has no pty. Tier 2 uses this to serialize a windowless retained pty at
|
|
289
|
+
* quit so its post-disable output still lands in a snapshot (T2→T1 degrade).
|
|
290
|
+
*/
|
|
291
|
+
getScrollback(id: string): string | undefined;
|
|
292
|
+
}
|
|
293
|
+
/** Back-compat alias. The class was renamed InProcessBackend in Tier 3; existing
|
|
294
|
+
* imports of `TerminalManager` as a TYPE keep working. */
|
|
295
|
+
type TerminalManager = InProcessBackend;
|
|
296
|
+
/**
|
|
297
|
+
* Wire the in-process backend's settings + snapshot store. Must be called by the
|
|
298
|
+
* adapter at app-ready, before any terminal is created. Idempotent if the
|
|
299
|
+
* singleton hasn't been built yet; if it already exists this updates the deps it
|
|
300
|
+
* uses (the adapter calls this exactly once, before first use).
|
|
301
|
+
*/
|
|
302
|
+
declare function configureInProcessBackend(deps: BackendDeps): void;
|
|
303
|
+
declare function inProcessBackend(): InProcessBackend;
|
|
304
|
+
declare function terminalManager(): PtyBackend;
|
|
305
|
+
/**
|
|
306
|
+
* Subscribers that want to follow whichever backend is active (the IPC layer's
|
|
307
|
+
* data/exit fan-out). Re-bound on every backend swap so events always come from
|
|
308
|
+
* the LIVE backend, not a stale one left behind after a fallback.
|
|
309
|
+
*/
|
|
310
|
+
interface BackendEventHandlers {
|
|
311
|
+
onData: (id: string, data: string) => void;
|
|
312
|
+
onExit: (id: string, payload: {
|
|
313
|
+
exitCode: number;
|
|
314
|
+
signal?: number;
|
|
315
|
+
}) => void;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Register the data/exit fan-out once. Binds to the current active backend and
|
|
319
|
+
* is automatically re-bound to any backend swapped in via setActiveBackend, so
|
|
320
|
+
* the IPC layer never has to know the backend changed underneath it.
|
|
321
|
+
*/
|
|
322
|
+
declare function subscribeBackendEvents(handlers: BackendEventHandlers): void;
|
|
323
|
+
/**
|
|
324
|
+
* Swap the active backend (Tier 3 connect/spawn success → HostClient). Idempotent
|
|
325
|
+
* and safe to call before any pty exists. Passing null reverts to the in-process
|
|
326
|
+
* backend — used by the graceful-fallback path when the host dies mid-session.
|
|
327
|
+
* Re-binds the IPC event fan-out to the new backend.
|
|
328
|
+
*/
|
|
329
|
+
declare function setActiveBackend(backend: PtyBackend | null): void;
|
|
330
|
+
declare function defaultShell(): string;
|
|
331
|
+
|
|
332
|
+
declare class HostClient extends EventEmitter implements PtyBackend {
|
|
333
|
+
private readonly socketPath;
|
|
334
|
+
private readonly snapshots;
|
|
335
|
+
private socket;
|
|
336
|
+
private readonly decoder;
|
|
337
|
+
private seq;
|
|
338
|
+
private readonly pending;
|
|
339
|
+
private readonly mirror;
|
|
340
|
+
private readonly retained;
|
|
341
|
+
/** Host pid, learned from hello-ok — surfaced for diagnostics. */
|
|
342
|
+
hostPid: number;
|
|
343
|
+
private connected;
|
|
344
|
+
private constructor();
|
|
345
|
+
/**
|
|
346
|
+
* Connect to a running host at `socketPath`, perform the version handshake,
|
|
347
|
+
* and seed the local mirror from the host's live ptys (list + per-pty
|
|
348
|
+
* scrollback). Resolves to a ready client, or rejects on connect failure /
|
|
349
|
+
* version mismatch / timeout — the caller then falls back to in-process.
|
|
350
|
+
*
|
|
351
|
+
* `snapshots` is the injected snapshot store used by cold-create to surface
|
|
352
|
+
* any on-disk previous-session snapshot (was a direct `./sessions` import).
|
|
353
|
+
*/
|
|
354
|
+
static connect(socketPath: string, snapshots: SnapshotStore, timeoutMs?: number): Promise<HostClient>;
|
|
355
|
+
private wireSocket;
|
|
356
|
+
private handleSocketError;
|
|
357
|
+
private handleHostMessage;
|
|
358
|
+
private nextSeq;
|
|
359
|
+
/** Send a request and await the correlated reply. */
|
|
360
|
+
private request;
|
|
361
|
+
/** Fire-and-forget send for messages with no reply (write/resize/kill/…). */
|
|
362
|
+
private send;
|
|
363
|
+
/** Seed the local mirror from the host's live ptys after a (re)connect. */
|
|
364
|
+
private seedFromHost;
|
|
365
|
+
/** Ids the host currently has live — used by the lifecycle layer to drive
|
|
366
|
+
* the reattach (renderer remounts these specs, replaying host scrollback). */
|
|
367
|
+
liveIds(): string[];
|
|
368
|
+
isConnected(): boolean;
|
|
369
|
+
/** Disconnect WITHOUT killing host ptys (before-quit leave-running). */
|
|
370
|
+
disconnect(): void;
|
|
371
|
+
create(opts: CreateTerminalOpts): AttachResult;
|
|
372
|
+
write(id: string, data: string): boolean;
|
|
373
|
+
resize(id: string, cols: number, rows: number): boolean;
|
|
374
|
+
kill(id: string): boolean;
|
|
375
|
+
/**
|
|
376
|
+
* NO-OP for the host backend. The whole point of Tier 3 is that ptys survive
|
|
377
|
+
* a full quit, so the before-quit teardown must NOT kill them. The lifecycle
|
|
378
|
+
* layer disconnects the client and leaves the host running instead.
|
|
379
|
+
*/
|
|
380
|
+
killAll(): void;
|
|
381
|
+
list(): TerminalInfo[];
|
|
382
|
+
isLive(id: string): boolean;
|
|
383
|
+
setRetained(id: string, retained: boolean): void;
|
|
384
|
+
isRetained(id: string): boolean;
|
|
385
|
+
retainedCount(): number;
|
|
386
|
+
retainedIds(): string[];
|
|
387
|
+
getScrollback(id: string): string | undefined;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Tier 3 lifecycle: decide the backend at app-ready, manage the detached
|
|
392
|
+
* pty-host, and handle graceful fallback.
|
|
393
|
+
*
|
|
394
|
+
* The flow (initTerminalBackend):
|
|
395
|
+
* 1. If the `detached_terminals` setting is OFF (the default — see below) →
|
|
396
|
+
* use the in-process backend. Done. This is today's T1/T2 behaviour.
|
|
397
|
+
* 2. ON → try to CONNECT to an existing host (pidfile alive + version match +
|
|
398
|
+
* socket reachable). Success → HostClient, reattach existing ptys.
|
|
399
|
+
* 3. No usable host → SPAWN one detached, await its pidfile, then connect.
|
|
400
|
+
* 4. Any failure (spawn, timeout, version mismatch, socket error) → fall back
|
|
401
|
+
* to the in-process backend and surface a NON-FATAL toast. The app stays
|
|
402
|
+
* fully functional.
|
|
403
|
+
*
|
|
404
|
+
* SETTING DEFAULT — `detached_terminals` defaults OFF.
|
|
405
|
+
* Rationale: T3 is the heaviest tier and its #1 risk is the dev-vs-packaged
|
|
406
|
+
* host-script path. Shipping it default-ON would put every user on an
|
|
407
|
+
* unproven detached process the first launch after upgrade. Default-OFF means
|
|
408
|
+
* the proven in-process T1/T2 path remains the out-of-box experience; users
|
|
409
|
+
* opt in via Settings → Terminal → "Keep terminals running after quit".
|
|
410
|
+
*
|
|
411
|
+
* RUNTIME-AGNOSTIC: this module imports neither `electron` nor `../db`. The
|
|
412
|
+
* connect-or-spawn-or-fallback LOGIC is core; the Electron specifics are
|
|
413
|
+
* injected:
|
|
414
|
+
* - HostSpawner — resolveHostScript / spawnDetached / userDataDir
|
|
415
|
+
* (was app.getPath + child_process.spawn with execPath +
|
|
416
|
+
* ELECTRON_RUN_AS_NODE).
|
|
417
|
+
* - SettingsProvider — the `detached_terminals` read (was getAllSettings).
|
|
418
|
+
* - SnapshotStore — passed to HostClient for cold-create snapshot probe.
|
|
419
|
+
* - onHostStatus — the fallback toast sink, emits `host-status` instead of
|
|
420
|
+
* a direct BrowserWindow broadcast.
|
|
421
|
+
* Genie's adapter (genie-adapter.ts) supplies all four via configureHostLifecycle.
|
|
422
|
+
*/
|
|
423
|
+
interface HostLifecycleDeps {
|
|
424
|
+
spawner: HostSpawner;
|
|
425
|
+
settings: SettingsProvider;
|
|
426
|
+
snapshots: SnapshotStore;
|
|
427
|
+
onHostStatus: (status: HostStatus) => void;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Wire the host lifecycle's injected ports. Called once by the adapter at
|
|
431
|
+
* app-ready, before initTerminalBackend. NEVER configured = in-process only
|
|
432
|
+
* (detachedEnabled below returns false defensively).
|
|
433
|
+
*/
|
|
434
|
+
declare function configureHostLifecycle(d: HostLifecycleDeps): void;
|
|
435
|
+
/** True when the active backend is the detached host (diagnostics + before-quit). */
|
|
436
|
+
declare function isHostBacked(): boolean;
|
|
437
|
+
declare function getHostClient(): HostClient | null;
|
|
438
|
+
/**
|
|
439
|
+
* Initialise the terminal backend at app-ready. Returns the list of host pty ids
|
|
440
|
+
* that should be reattached by the renderer (empty for the in-process path or a
|
|
441
|
+
* cold host). NEVER throws — every failure degrades to in-process.
|
|
442
|
+
*/
|
|
443
|
+
declare function initTerminalBackend(): Promise<{
|
|
444
|
+
host: boolean;
|
|
445
|
+
reattachIds: string[];
|
|
446
|
+
}>;
|
|
447
|
+
/**
|
|
448
|
+
* before-quit, host-backed: DO NOT kill the host ptys. Snapshot (T1) already ran
|
|
449
|
+
* via the normal before-quit path; here we just disconnect the client and leave
|
|
450
|
+
* the host running so the next launch reattaches.
|
|
451
|
+
*/
|
|
452
|
+
declare function disconnectHostLeaveRunning(): void;
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Absolute path to the bundled detached pty-host script (Tier 3), so a
|
|
456
|
+
* `HostSpawner` can launch it as a detached child without knowing this
|
|
457
|
+
* package's dist layout:
|
|
458
|
+
*
|
|
459
|
+
* ```ts
|
|
460
|
+
* import { spawn } from 'node:child_process';
|
|
461
|
+
* import { ptyHostScriptPath } from '@particle-academy/fancy-term-host';
|
|
462
|
+
*
|
|
463
|
+
* const child = spawn(process.execPath, [ptyHostScriptPath(), userDataDir], {
|
|
464
|
+
* detached: true, stdio: 'ignore',
|
|
465
|
+
* });
|
|
466
|
+
* child.unref();
|
|
467
|
+
* ```
|
|
468
|
+
*
|
|
469
|
+
* The host script is emitted alongside this module in `dist/` as
|
|
470
|
+
* `pty-host.js`. Works in both the ESM and CJS builds (esbuild fills in
|
|
471
|
+
* `import.meta.url` for the CJS output; `__dirname` is used when present).
|
|
472
|
+
*/
|
|
473
|
+
declare function ptyHostScriptPath(): string;
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Path + pidfile resolution for the detached pty-host (Tier 3).
|
|
477
|
+
*
|
|
478
|
+
* Kept ELECTRON-FREE on the resolution side that the host itself uses (the host
|
|
479
|
+
* is a plain node process — no `app`), so the userData path is passed IN. The
|
|
480
|
+
* in-app side (host-client lifecycle) imports `app` separately and feeds it here.
|
|
481
|
+
*/
|
|
482
|
+
interface Pidfile {
|
|
483
|
+
pid: number;
|
|
484
|
+
socketPath: string;
|
|
485
|
+
protocolVersion: number;
|
|
486
|
+
startedAt: number;
|
|
487
|
+
}
|
|
488
|
+
/** Short, stable per-user hash so two OS users don't collide on the Windows
|
|
489
|
+
* pipe name (the pipe namespace is machine-global). */
|
|
490
|
+
declare function userHash(): string;
|
|
491
|
+
/**
|
|
492
|
+
* The local IPC transport address.
|
|
493
|
+
* • Windows: a named pipe `\\.\pipe\genie-ptyhost-<userhash>`. The default
|
|
494
|
+
* Windows pipe ACL is per-logon-session, so another user on the same machine
|
|
495
|
+
* can't open it — that's our ACL. (Documented; we don't tighten further.)
|
|
496
|
+
* • POSIX: a unix domain socket under userData (preferred — survives /tmp
|
|
497
|
+
* cleaners and is per-user by directory perms) named `ptyhost.sock`.
|
|
498
|
+
*/
|
|
499
|
+
declare function socketPathFor(userDataDir: string): string;
|
|
500
|
+
declare function pidfilePath(userDataDir: string): string;
|
|
501
|
+
declare function writePidfile(userDataDir: string, pf: Pidfile): void;
|
|
502
|
+
declare function readPidfile(userDataDir: string): Pidfile | null;
|
|
503
|
+
declare function deletePidfile(userDataDir: string): void;
|
|
504
|
+
/** True when a process with `pid` is alive (signal 0 probes without killing). */
|
|
505
|
+
declare function isPidAlive(pid: number): boolean;
|
|
506
|
+
/**
|
|
507
|
+
* Decide whether an existing pidfile points at a usable host.
|
|
508
|
+
* Usable = pid alive AND protocol versions match. A stale/dead/mismatched
|
|
509
|
+
* pidfile means we must spawn a fresh host.
|
|
510
|
+
*/
|
|
511
|
+
declare function pidfileUsable(pf: Pidfile | null): boolean;
|
|
512
|
+
/**
|
|
513
|
+
* Resolve the compiled pty-host script on disk, trying multiple candidate paths
|
|
514
|
+
* so it works in BOTH `npm run dev` (script at app/pty-host.js next to
|
|
515
|
+
* background.js) AND a packaged asar build. node-pty's native binding can't load
|
|
516
|
+
* from inside an asar, so the host (which requires node-pty) must run UNPACKED —
|
|
517
|
+
* `app.asar.unpacked/...`. We try the unpacked path first, then the in-asar path,
|
|
518
|
+
* then a dev-relative path. Returns the first that exists, or null.
|
|
519
|
+
*
|
|
520
|
+
* `dirname` is main/background's __dirname (the directory the compiled main
|
|
521
|
+
* bundle lives in). The host script is emitted alongside it as `pty-host.js`.
|
|
522
|
+
*/
|
|
523
|
+
declare function resolveHostScript(dirname: string): string | null;
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Pty-host wire protocol (Tier 3).
|
|
527
|
+
*
|
|
528
|
+
* The detached pty-host (main/terminal/pty-host.ts) and the in-app HostClient
|
|
529
|
+
* (main/terminal/host-client.ts) talk over a local IPC transport — a named pipe
|
|
530
|
+
* on Windows, a unix domain socket on POSIX — using a tiny length-prefixed JSON
|
|
531
|
+
* framing so there's no heavy dependency. This module is PURE (no electron, no
|
|
532
|
+
* node-pty, no net): just the message shapes + the encode/decode for the framing,
|
|
533
|
+
* so it can be imported by both ends AND unit-tested in isolation.
|
|
534
|
+
*
|
|
535
|
+
* Framing: each message is `[4-byte big-endian uint32 length][utf8 JSON body]`.
|
|
536
|
+
* The length prefix is the byte length of the JSON body. A FrameDecoder buffers
|
|
537
|
+
* partial reads and yields whole messages as they complete — TCP/pipe streams
|
|
538
|
+
* don't preserve message boundaries, so we can't assume one `data` event == one
|
|
539
|
+
* message.
|
|
540
|
+
*/
|
|
541
|
+
/**
|
|
542
|
+
* Protocol version. Bumped whenever the message shapes change in a way that
|
|
543
|
+
* makes an old host incompatible with a new client (or vice-versa). The client
|
|
544
|
+
* refuses to attach to a host whose pidfile reports a different version and
|
|
545
|
+
* spawns a fresh host instead — see host-client.ts connect-or-spawn.
|
|
546
|
+
*/
|
|
547
|
+
declare const PROTOCOL_VERSION = 1;
|
|
548
|
+
/** Requests the client sends to the host. `seq` correlates a reply. */
|
|
549
|
+
type ClientMessage = {
|
|
550
|
+
kind: 'hello';
|
|
551
|
+
seq: number;
|
|
552
|
+
protocolVersion: number;
|
|
553
|
+
} | {
|
|
554
|
+
kind: 'create';
|
|
555
|
+
seq: number;
|
|
556
|
+
opts: {
|
|
557
|
+
id: string;
|
|
558
|
+
cwd: string;
|
|
559
|
+
shell?: string;
|
|
560
|
+
args?: string[];
|
|
561
|
+
cols?: number;
|
|
562
|
+
rows?: number;
|
|
563
|
+
env?: Record<string, string>;
|
|
564
|
+
};
|
|
565
|
+
} | {
|
|
566
|
+
kind: 'write';
|
|
567
|
+
id: string;
|
|
568
|
+
data: string;
|
|
569
|
+
} | {
|
|
570
|
+
kind: 'resize';
|
|
571
|
+
id: string;
|
|
572
|
+
cols: number;
|
|
573
|
+
rows: number;
|
|
574
|
+
} | {
|
|
575
|
+
kind: 'kill';
|
|
576
|
+
id: string;
|
|
577
|
+
} | {
|
|
578
|
+
kind: 'list';
|
|
579
|
+
seq: number;
|
|
580
|
+
} | {
|
|
581
|
+
kind: 'set-retained';
|
|
582
|
+
id: string;
|
|
583
|
+
retained: boolean;
|
|
584
|
+
} | {
|
|
585
|
+
kind: 'get-scrollback';
|
|
586
|
+
seq: number;
|
|
587
|
+
id: string;
|
|
588
|
+
} | {
|
|
589
|
+
kind: 'ping';
|
|
590
|
+
seq: number;
|
|
591
|
+
};
|
|
592
|
+
/** Pushes + replies the host sends to the client. */
|
|
593
|
+
type HostMessage = {
|
|
594
|
+
kind: 'hello-ok';
|
|
595
|
+
seq: number;
|
|
596
|
+
protocolVersion: number;
|
|
597
|
+
pid: number;
|
|
598
|
+
} | {
|
|
599
|
+
kind: 'created';
|
|
600
|
+
seq: number;
|
|
601
|
+
result: {
|
|
602
|
+
id: string;
|
|
603
|
+
pid: number;
|
|
604
|
+
shell: string;
|
|
605
|
+
existing: boolean;
|
|
606
|
+
scrollback: string;
|
|
607
|
+
};
|
|
608
|
+
} | {
|
|
609
|
+
kind: 'list-result';
|
|
610
|
+
seq: number;
|
|
611
|
+
terminals: Array<{
|
|
612
|
+
id: string;
|
|
613
|
+
pid: number;
|
|
614
|
+
shell: string;
|
|
615
|
+
}>;
|
|
616
|
+
} | {
|
|
617
|
+
kind: 'scrollback-result';
|
|
618
|
+
seq: number;
|
|
619
|
+
scrollback: string | null;
|
|
620
|
+
} | {
|
|
621
|
+
kind: 'pong';
|
|
622
|
+
seq: number;
|
|
623
|
+
} | {
|
|
624
|
+
kind: 'data';
|
|
625
|
+
id: string;
|
|
626
|
+
data: string;
|
|
627
|
+
} | {
|
|
628
|
+
kind: 'exit';
|
|
629
|
+
id: string;
|
|
630
|
+
exitCode: number;
|
|
631
|
+
signal?: number;
|
|
632
|
+
};
|
|
633
|
+
type Frame = ClientMessage | HostMessage;
|
|
634
|
+
/** Encode a message as a length-prefixed JSON frame ready for the socket. */
|
|
635
|
+
declare function encodeFrame(msg: Frame): Buffer;
|
|
636
|
+
/**
|
|
637
|
+
* Streaming frame decoder. Feed it raw socket chunks via `push`; it returns the
|
|
638
|
+
* complete messages that became available (zero or more), buffering any partial
|
|
639
|
+
* tail until the rest arrives. One decoder per socket.
|
|
640
|
+
*
|
|
641
|
+
* Resilient by design: a malformed JSON body is skipped (the frame is consumed
|
|
642
|
+
* but yields nothing) rather than throwing — a corrupt frame must not wedge the
|
|
643
|
+
* whole stream. An absurd length prefix (> MAX_FRAME) is treated as a desync and
|
|
644
|
+
* the buffer is reset; the caller can decide whether to drop the connection.
|
|
645
|
+
*/
|
|
646
|
+
declare class FrameDecoder {
|
|
647
|
+
private buffer;
|
|
648
|
+
/** Hard cap on a single frame (16 MB). Guards against a runaway/garbage
|
|
649
|
+
* length prefix allocating unbounded memory. node-pty data chunks are tiny;
|
|
650
|
+
* a serialized scrollback is bounded well under this. */
|
|
651
|
+
static readonly MAX_FRAME: number;
|
|
652
|
+
/** True when the last push hit an oversized/desynced frame. The caller
|
|
653
|
+
* should drop the connection — the stream can't be trusted to realign. */
|
|
654
|
+
desynced: boolean;
|
|
655
|
+
push(chunk: Buffer): Frame[];
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Shell detection + default-shell resolution for the terminal subsystem.
|
|
660
|
+
*
|
|
661
|
+
* Mirrors main/editors.ts: probe well-known install paths, return what's
|
|
662
|
+
* actually present. Ids line up with fancy-term's BUILTIN_SHELLS so the
|
|
663
|
+
* renderer can map detections straight onto ShellProfile entries
|
|
664
|
+
* (cmd · powershell · pwsh · git-bash · bash · zsh · wsl).
|
|
665
|
+
*
|
|
666
|
+
* Default policy (Windows): Git Bash when detected — it's the shell the
|
|
667
|
+
* Tynn toolchain assumes — then pwsh, then Windows PowerShell, then cmd.
|
|
668
|
+
* On macOS/Linux the user's $SHELL wins, falling back to bash.
|
|
669
|
+
*/
|
|
670
|
+
interface ShellInfo {
|
|
671
|
+
/** Stable id, matches fancy-term BUILTIN_SHELLS where possible. */
|
|
672
|
+
id: string;
|
|
673
|
+
/** Display label, e.g. "Git Bash". */
|
|
674
|
+
label: string;
|
|
675
|
+
/** Absolute executable path (or bare command when resolved via PATH). */
|
|
676
|
+
command: string;
|
|
677
|
+
/** Default args for an interactive session. */
|
|
678
|
+
args: string[];
|
|
679
|
+
}
|
|
680
|
+
declare function detectShells(): ShellInfo[];
|
|
681
|
+
/** Default policy: Git Bash > pwsh > powershell > cmd (win); $SHELL > bash (unix). */
|
|
682
|
+
declare function defaultShellId(detected: ShellInfo[]): string | null;
|
|
683
|
+
/**
|
|
684
|
+
* Split a manual "executable line" into command + args. Honors double
|
|
685
|
+
* quotes around the executable path ("C:\Program Files\Git\bin\bash.exe"
|
|
686
|
+
* --login -i). Single-token lines pass through untouched.
|
|
687
|
+
*/
|
|
688
|
+
declare function parseCommandLine(line: string): {
|
|
689
|
+
command: string;
|
|
690
|
+
args: string[];
|
|
691
|
+
};
|
|
692
|
+
/** Coarse shell family, derived from the executable name, used to decide which
|
|
693
|
+
* OSC-7 prompt hook (if any) we can inject. */
|
|
694
|
+
type ShellKind = 'powershell' | 'bash' | 'zsh' | 'fish' | 'cmd' | 'other';
|
|
695
|
+
declare function shellKind(command: string): ShellKind;
|
|
696
|
+
/** The spawn additions (env + extra args) a shell needs to emit OSC-7 cwd. */
|
|
697
|
+
interface CwdHook {
|
|
698
|
+
/** Extra env entries to merge into the pty's environment. */
|
|
699
|
+
env: Record<string, string>;
|
|
700
|
+
/** Extra args to APPEND to the shell's launch args (PowerShell needs these). */
|
|
701
|
+
args: string[];
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Build the spawn additions (env + args) that make a shell emit OSC-7 cwd
|
|
705
|
+
* reports on every prompt, so resumed terminals know where they were
|
|
706
|
+
* (Tier 1.5). Gated by the `track_cwd` setting (default ON). Returns an empty
|
|
707
|
+
* hook when tracking is off or the shell genuinely can't be hooked — the
|
|
708
|
+
* manager then degrades to the static cwd.
|
|
709
|
+
*
|
|
710
|
+
* Coverage (all overlay, never clobber the user's own prompt/rc):
|
|
711
|
+
* - **bash** — prepend an OSC-7 `printf` to `PROMPT_COMMAND` (env only). The
|
|
712
|
+
* portable, reliable case (Git Bash on Windows, bash on POSIX).
|
|
713
|
+
* - **zsh** — point `ZDOTDIR` at a generated dir whose `.zshrc` sources the
|
|
714
|
+
* user's real `.zshrc`, restores `ZDOTDIR`, then registers a `precmd`
|
|
715
|
+
* emitter; `.zshenv` sources the user's real `.zshenv` first.
|
|
716
|
+
* - **fish** — prepend a generated dir to `XDG_DATA_DIRS` carrying a
|
|
717
|
+
* `fish/vendor_conf.d/osc7.fish` that hooks `--on-event fish_prompt`
|
|
718
|
+
* (vendor conf overlays the user's config rather than replacing it).
|
|
719
|
+
* - **PowerShell (pwsh/powershell)** — write a profile shim that wraps any
|
|
720
|
+
* existing `prompt` and emits OSC-7, dot-sourced via appended
|
|
721
|
+
* `-NoExit -Command ". '<shim>'"` args (PS has no env-var prompt hook).
|
|
722
|
+
* - **cmd.exe (best-effort)** — set `PROMPT` to emit OSC-7 via the `$E`
|
|
723
|
+
* escape before the normal `$P$G`. Renders only where the console honors
|
|
724
|
+
* VT sequences in the prompt; otherwise degrades to static cwd.
|
|
725
|
+
*
|
|
726
|
+
* The emitted payload is always `file:///<path>` (empty authority, forward
|
|
727
|
+
* slashes / Windows drive) so it round-trips through {@link scanOsc7Cwd}.
|
|
728
|
+
*/
|
|
729
|
+
declare function cwdHookSpawn(command: string, settings: SettingsProvider): CwdHook;
|
|
730
|
+
/**
|
|
731
|
+
* Back-compat env-only view of {@link cwdHookSpawn} — returns just the env
|
|
732
|
+
* additions. Shells that also need launch args (PowerShell) are only fully
|
|
733
|
+
* hooked via `cwdHookSpawn`; callers using this alone get the env half.
|
|
734
|
+
*/
|
|
735
|
+
declare function cwdHookEnv(command: string, settings: SettingsProvider): Record<string, string>;
|
|
736
|
+
/**
|
|
737
|
+
* Resolve the user's configured default shell to a concrete spawn target.
|
|
738
|
+
* Reads the `terminal_shell` setting (a detected id, or 'custom' paired
|
|
739
|
+
* with `terminal_custom_cmd`). Anything unresolvable falls back to the
|
|
740
|
+
* detection-based default so the terminal always opens SOMETHING.
|
|
741
|
+
*/
|
|
742
|
+
declare function resolveDefaultShell(settings: SettingsProvider): {
|
|
743
|
+
command: string;
|
|
744
|
+
args: string[];
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* OSC-7 cwd reporting (Tier 1.5).
|
|
749
|
+
*
|
|
750
|
+
* A shell that emits OSC-7 tells the terminal its current working directory on
|
|
751
|
+
* every prompt:
|
|
752
|
+
*
|
|
753
|
+
* ESC ] 7 ; file://HOST/PATH (BEL | ESC \)
|
|
754
|
+
*
|
|
755
|
+
* We scan raw pty output for these and parse out an absolute filesystem path so
|
|
756
|
+
* a fresh shell spawned on resume can start where the old one left off. The
|
|
757
|
+
* sequence is terminated by either BEL (\x07) or ST (ESC \, i.e. \x1b\x5c).
|
|
758
|
+
*
|
|
759
|
+
* Path forms handled:
|
|
760
|
+
* file:///home/user/proj → /home/user/proj
|
|
761
|
+
* file://hostname/home/user/proj → /home/user/proj (host ignored)
|
|
762
|
+
* file:///C:/Users/me/proj → C:\Users\me\proj (Windows drive)
|
|
763
|
+
* percent-encoded segments (%20) → decoded
|
|
764
|
+
*/
|
|
765
|
+
/**
|
|
766
|
+
* Parse a `file://...` URL from an OSC-7 payload into a local filesystem path.
|
|
767
|
+
* Returns null when the payload isn't a usable file URL.
|
|
768
|
+
*/
|
|
769
|
+
declare function parseFileUrl(payload: string): string | null;
|
|
770
|
+
/**
|
|
771
|
+
* Scan a chunk of pty output and return the LAST cwd reported via OSC-7, or
|
|
772
|
+
* null when the chunk contains no (parseable) OSC-7 sequence. We take the last
|
|
773
|
+
* one because a single chunk can carry several prompts; the most recent wins.
|
|
774
|
+
*/
|
|
775
|
+
declare function scanOsc7Cwd(chunk: string): string | null;
|
|
776
|
+
|
|
777
|
+
export { type AttachResult, type BackendDeps, type ClientMessage, type CreateTerminalOpts, type CwdHook, type Encryptor, type Frame, FrameDecoder, HostClient, type HostMessage, type HostSpawner, type HostStatus, PROTOCOL_VERSION, type Pidfile, type PtyBackend, type SettingsProvider, type ShellInfo, type ShellKind, type SnapshotRead, type SnapshotStore, type SnapshotStoreConfig, type TerminalInfo, type TerminalManager, configureHostLifecycle, configureInProcessBackend, createSnapshotStore, cwdHookEnv, cwdHookSpawn, defaultShell, defaultShellId, deletePidfile, detectShells, disconnectHostLeaveRunning, encodeFrame, getHostClient, inProcessBackend, initTerminalBackend, isHostBacked, isPidAlive, parseCommandLine, parseFileUrl, pidfilePath, pidfileUsable, ptyHostScriptPath, readPidfile, resolveDefaultShell, resolveHostScript, scanOsc7Cwd, setActiveBackend, shellKind, socketPathFor, subscribeBackendEvents, terminalManager, userHash, writePidfile };
|