@openape/apes 0.6.1 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -171,6 +171,10 @@ declare function createShapesGrant(resolved: ResolvedCommand, params: {
171
171
  }>;
172
172
  declare function waitForGrantStatus(idp: string, grantId: string): Promise<'approved' | 'denied' | 'revoked'>;
173
173
  declare function fetchGrantToken(idp: string, grantId: string): Promise<string>;
174
+ /**
175
+ * One-shot verify + consume + execute. Preserves the legacy behavior of
176
+ * the `apes run --shell` path so existing callers keep working unchanged.
177
+ */
174
178
  declare function verifyAndExecute(token: string, resolved: ResolvedCommand): Promise<void>;
175
179
  declare function findExistingGrant(resolved: ResolvedCommand, idp: string): Promise<string | null>;
176
180
 
@@ -183,7 +187,12 @@ declare function buildStructuredCliGrantRequest(resolved: ResolvedCommand | Reso
183
187
 
184
188
  declare function fetchRegistry(forceRefresh?: boolean): Promise<RegistryIndex>;
185
189
  declare function searchAdapters(index: RegistryIndex, query: string): RegistryEntry[];
186
- declare function findAdapter(index: RegistryIndex, id: string): RegistryEntry | undefined;
190
+ /**
191
+ * Look up a registry entry by its id or its executable field. This lets callers
192
+ * pass either the registry id ("o365") or the binary name ("o365-cli"); most
193
+ * adapters have id === executable, but the two can diverge.
194
+ */
195
+ declare function findAdapter(index: RegistryIndex, idOrExecutable: string): RegistryEntry | undefined;
187
196
 
188
197
  interface InstallResult {
189
198
  id: string;
package/dist/index.js CHANGED
@@ -10,7 +10,6 @@ import {
10
10
  appendAuditLog,
11
11
  buildExactCommandGrantRequest,
12
12
  buildStructuredCliGrantRequest,
13
- clearAuth,
14
13
  createShapesGrant,
15
14
  discoverEndpoints,
16
15
  extractOption,
@@ -21,28 +20,31 @@ import {
21
20
  findAdapter,
22
21
  findConflictingAdapters,
23
22
  findExistingGrant,
24
- getAuthToken,
25
- getIdpUrl,
26
23
  getInstalledDigest,
27
- getRequesterIdentity,
28
24
  installAdapter,
29
25
  isInstalled,
30
26
  loadAdapter,
31
- loadAuth,
32
- loadConfig,
33
27
  loadOrInstallAdapter,
34
28
  parseShellCommand,
35
29
  removeAdapter,
36
30
  resolveAdapterPath,
37
31
  resolveCapabilityRequest,
38
32
  resolveCommand,
39
- saveAuth,
40
- saveConfig,
41
33
  searchAdapters,
42
34
  tryLoadAdapter,
43
35
  verifyAndExecute,
44
36
  waitForGrantStatus
45
- } from "./chunk-G3Q2TMAI.js";
37
+ } from "./chunk-B32ZQP5K.js";
38
+ import {
39
+ clearAuth,
40
+ getAuthToken,
41
+ getIdpUrl,
42
+ getRequesterIdentity,
43
+ loadAuth,
44
+ loadConfig,
45
+ saveAuth,
46
+ saveConfig
47
+ } from "./chunk-TBYYREL6.js";
46
48
  export {
47
49
  ApiError,
48
50
  CliError,
@@ -0,0 +1,637 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ apiFetch,
4
+ appendAuditLog,
5
+ createShapesGrant,
6
+ fetchGrantToken,
7
+ findExistingGrant,
8
+ getGrantsEndpoint,
9
+ loadOrInstallAdapter,
10
+ parseShellCommand,
11
+ resolveCommand,
12
+ verifyAndConsume,
13
+ waitForGrantStatus
14
+ } from "./chunk-B32ZQP5K.js";
15
+ import {
16
+ loadAuth
17
+ } from "./chunk-TBYYREL6.js";
18
+
19
+ // src/shell/orchestrator.ts
20
+ import { hostname } from "os";
21
+ import consola3 from "consola";
22
+
23
+ // src/shell/grant-dispatch.ts
24
+ import { basename } from "path";
25
+ import consola from "consola";
26
+ async function requestGrantForShellLine(line, options) {
27
+ const auth = loadAuth();
28
+ if (!auth) {
29
+ return { kind: "denied", reason: "Not logged in. Run `apes login` first." };
30
+ }
31
+ const idp = auth.idp;
32
+ if (!idp) {
33
+ return { kind: "denied", reason: "No IdP URL configured. Run `apes login` first." };
34
+ }
35
+ const parsed = parseShellCommand(line);
36
+ if (parsed && !parsed.isCompound) {
37
+ try {
38
+ const loaded = await loadOrInstallAdapter(parsed.executable);
39
+ if (loaded) {
40
+ const normalizedExecutable = basename(parsed.executable);
41
+ const resolved = await resolveCommand(loaded, [normalizedExecutable, ...parsed.argv]);
42
+ try {
43
+ const existingGrantId = await findExistingGrant(resolved, idp);
44
+ if (existingGrantId) {
45
+ consola.info(`Reusing grant ${existingGrantId} for: ${resolved.detail.display}`);
46
+ const token2 = await fetchGrantToken(idp, existingGrantId);
47
+ await verifyAndConsume(token2, resolved);
48
+ return { kind: "approved", grantId: existingGrantId, mode: "adapter" };
49
+ }
50
+ } catch (err) {
51
+ consola.debug(`ape-shell: findExistingGrant failed, will request new grant:`, err);
52
+ }
53
+ consola.info(`Requesting grant for: ${resolved.detail.display}`);
54
+ const grant = await createShapesGrant(resolved, {
55
+ idp,
56
+ approval: options.approval ?? "once",
57
+ reason: `ape-shell: ${resolved.detail.display}`
58
+ });
59
+ consola.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
60
+ const status = await waitForGrantStatus(idp, grant.id);
61
+ if (status !== "approved") {
62
+ return { kind: "denied", reason: `Grant ${status}` };
63
+ }
64
+ const token = await fetchGrantToken(idp, grant.id);
65
+ await verifyAndConsume(token, resolved);
66
+ return { kind: "approved", grantId: grant.id, mode: "adapter" };
67
+ }
68
+ } catch (err) {
69
+ consola.debug(`ape-shell: adapter resolve failed, falling back to session grant:`, err);
70
+ }
71
+ }
72
+ const grantsUrl = await getGrantsEndpoint(idp);
73
+ try {
74
+ const grants = await apiFetch(
75
+ `${grantsUrl}?requester=${encodeURIComponent(auth.email)}&status=approved&limit=20`
76
+ );
77
+ const sessionGrant = grants.data.find(
78
+ (g) => g.request.audience === "ape-shell" && g.request.target_host === options.targetHost && g.request.grant_type !== "once"
79
+ );
80
+ if (sessionGrant) {
81
+ return { kind: "approved", grantId: sessionGrant.id, mode: "session" };
82
+ }
83
+ } catch (err) {
84
+ consola.debug(`ape-shell: session grant lookup failed:`, err);
85
+ }
86
+ consola.info(`Requesting ape-shell session grant on ${options.targetHost}`);
87
+ try {
88
+ const grant = await apiFetch(grantsUrl, {
89
+ method: "POST",
90
+ body: {
91
+ requester: auth.email,
92
+ target_host: options.targetHost,
93
+ audience: "ape-shell",
94
+ grant_type: options.approval ?? "once",
95
+ command: ["bash", "-c", line],
96
+ reason: `Shell session: ${line.slice(0, 100)}`
97
+ }
98
+ });
99
+ consola.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
100
+ const maxWait = 3e5;
101
+ const interval = 3e3;
102
+ const start = Date.now();
103
+ while (Date.now() - start < maxWait) {
104
+ const status = await apiFetch(`${grantsUrl}/${grant.id}`);
105
+ if (status.status === "approved")
106
+ return { kind: "approved", grantId: grant.id, mode: "session" };
107
+ if (status.status === "denied" || status.status === "revoked")
108
+ return { kind: "denied", reason: `Grant ${status.status}` };
109
+ await new Promise((r) => setTimeout(r, interval));
110
+ }
111
+ return { kind: "denied", reason: "Grant approval timed out after 5 minutes" };
112
+ } catch (err) {
113
+ const msg = err instanceof Error ? err.message : String(err);
114
+ return { kind: "denied", reason: `Grant request failed: ${msg}` };
115
+ }
116
+ }
117
+
118
+ // src/shell/pty-bridge.ts
119
+ import { randomBytes } from "crypto";
120
+ import * as pty from "node-pty";
121
+ var PtyBridge = class {
122
+ marker;
123
+ /** Compiled once. Captures the exit code in group 1. */
124
+ markerRegex;
125
+ term;
126
+ events;
127
+ /** Bytes received since the last completed line. Searched for the marker. */
128
+ pending = "";
129
+ /**
130
+ * Accumulated output for the current in-flight line. Streams to `onOutput`
131
+ * live as chunks arrive, and is handed to `onLineDone` in full when the
132
+ * marker is matched. Resets at that point.
133
+ */
134
+ currentLineBuffer = "";
135
+ /** True until bash prints its first marker-prompt. */
136
+ readyForFirstLine = false;
137
+ awaitingInitialPrompt = null;
138
+ constructor(events, options = {}) {
139
+ this.events = events;
140
+ this.marker = randomBytes(16).toString("hex");
141
+ this.markerRegex = new RegExp(
142
+ `__APES_${this.marker}__:(-?\\d+):__END__\\r?\\n?`
143
+ );
144
+ const cols = options.cols ?? process.stdout.columns ?? 80;
145
+ const rows = options.rows ?? process.stdout.rows ?? 24;
146
+ this.term = pty.spawn("bash", ["--login", "-i"], {
147
+ name: "xterm-256color",
148
+ cols,
149
+ rows,
150
+ cwd: options.cwd ?? process.cwd(),
151
+ env: {
152
+ ...process.env,
153
+ // Force our marker PS1 on every prompt and keep pty echo off —
154
+ // both survive .bashrc overrides because PROMPT_COMMAND runs
155
+ // before each prompt.
156
+ PROMPT_COMMAND: `stty -echo 2>/dev/null; PS1='__APES_${this.marker}__:$?:__END__'`,
157
+ // Also set it initially so the very first prompt carries the marker.
158
+ PS1: `__APES_${this.marker}__:$?:__END__`,
159
+ PS2: "> ",
160
+ // Silence bash-specific onboarding that would pollute our output.
161
+ BASH_SILENCE_DEPRECATION_WARNING: "1"
162
+ }
163
+ });
164
+ this.term.onData((chunk) => this.handleData(chunk));
165
+ this.term.onExit(({ exitCode, signal }) => {
166
+ this.events.onExit(exitCode, signal);
167
+ });
168
+ }
169
+ /**
170
+ * Resolves once bash has printed its very first marker-bearing prompt,
171
+ * which means it has finished sourcing ~/.bashrc and is ready to accept
172
+ * input. Callers should await this before sending the first line.
173
+ */
174
+ waitForReady() {
175
+ if (this.readyForFirstLine)
176
+ return Promise.resolve();
177
+ return new Promise((resolve) => {
178
+ this.awaitingInitialPrompt = resolve;
179
+ });
180
+ }
181
+ /**
182
+ * Write a shell command line to bash's stdin. The caller must ensure the
183
+ * line has already been approved by the grant flow. The bridge does NOT
184
+ * validate or filter the line.
185
+ */
186
+ writeLine(line) {
187
+ const clean = line.replace(/\r?\n+$/, "");
188
+ this.term.write(`${clean}
189
+ `);
190
+ }
191
+ /**
192
+ * Raw passthrough write — used for forwarding user keystrokes during
193
+ * interactive output mode (e.g. while vim is running).
194
+ */
195
+ writeRaw(data) {
196
+ this.term.write(data);
197
+ }
198
+ /** Resize the underlying pty. Called on SIGWINCH. */
199
+ resize(cols, rows) {
200
+ this.term.resize(cols, rows);
201
+ }
202
+ /** Kill the bash child. Called on Ctrl-D / shell exit. */
203
+ kill(signal) {
204
+ this.term.kill(signal);
205
+ }
206
+ /** Process id of the bash child, for logging / debugging. */
207
+ get pid() {
208
+ return this.term.pid;
209
+ }
210
+ /** Exposed for tests that want to look at the raw marker. */
211
+ getMarkerForTest() {
212
+ return this.marker;
213
+ }
214
+ // ----- internals -----
215
+ handleData(chunk) {
216
+ this.pending += chunk;
217
+ for (; ; ) {
218
+ const match = this.pending.match(this.markerRegex);
219
+ if (!match || match.index === void 0)
220
+ break;
221
+ const before = this.pending.slice(0, match.index);
222
+ const exitCode = Number(match[1]);
223
+ if (before.length > 0) {
224
+ this.currentLineBuffer += before;
225
+ if (this.readyForFirstLine)
226
+ this.events.onOutput(before);
227
+ }
228
+ this.pending = this.pending.slice(match.index + match[0].length);
229
+ if (!this.readyForFirstLine) {
230
+ this.readyForFirstLine = true;
231
+ this.currentLineBuffer = "";
232
+ const resolve = this.awaitingInitialPrompt;
233
+ this.awaitingInitialPrompt = null;
234
+ if (resolve)
235
+ resolve();
236
+ continue;
237
+ }
238
+ const frame = { output: this.currentLineBuffer, exitCode };
239
+ this.currentLineBuffer = "";
240
+ this.events.onLineDone(frame);
241
+ }
242
+ if (!this.readyForFirstLine)
243
+ return;
244
+ const lastNewline = this.pending.lastIndexOf("\n");
245
+ if (lastNewline >= 0) {
246
+ const ready = this.pending.slice(0, lastNewline + 1);
247
+ this.pending = this.pending.slice(lastNewline + 1);
248
+ if (ready.length > 0) {
249
+ this.currentLineBuffer += ready;
250
+ this.events.onOutput(ready);
251
+ }
252
+ }
253
+ }
254
+ };
255
+
256
+ // src/shell/repl.ts
257
+ import { createInterface } from "readline";
258
+ import { homedir } from "os";
259
+ import { join } from "path";
260
+ import consola2 from "consola";
261
+
262
+ // src/shell/multi-line.ts
263
+ import { spawnSync } from "child_process";
264
+ var CONTINUE_PATTERNS = [
265
+ /syntax error: unexpected end of file/i,
266
+ /unexpected end of file/i,
267
+ /here-document.+delimited by end-of-file/i,
268
+ /unexpected EOF while looking for matching/i
269
+ ];
270
+ function hasUnterminatedHeredoc(buffer) {
271
+ const pattern = /<<(-?)\s*(['"]?)([A-Z_]\w*)\2/gi;
272
+ for (const match of buffer.matchAll(pattern)) {
273
+ const stripTabs = match[1] === "-";
274
+ const delimiter = match[3];
275
+ const afterMatch = buffer.slice((match.index ?? 0) + match[0].length);
276
+ const lines = afterMatch.split("\n").slice(1);
277
+ const terminated = lines.some((line) => {
278
+ const compare = stripTabs ? line.replace(/^\t+/, "") : line;
279
+ return compare === delimiter;
280
+ });
281
+ if (!terminated)
282
+ return true;
283
+ }
284
+ return false;
285
+ }
286
+ function checkMultiLineStatus(buffer) {
287
+ if (buffer.trim().length === 0)
288
+ return { kind: "complete" };
289
+ if (hasUnterminatedHeredoc(buffer))
290
+ return { kind: "continue" };
291
+ const result = spawnSync("bash", ["-n", "-c", buffer], {
292
+ stdio: ["ignore", "ignore", "pipe"],
293
+ encoding: "utf-8"
294
+ });
295
+ if (result.error) {
296
+ return { kind: "error", message: `Failed to spawn bash for syntax check: ${result.error.message}` };
297
+ }
298
+ if (result.status === 0)
299
+ return { kind: "complete" };
300
+ const stderr = result.stderr || "";
301
+ for (const pattern of CONTINUE_PATTERNS) {
302
+ if (pattern.test(stderr))
303
+ return { kind: "continue" };
304
+ }
305
+ return { kind: "error", message: stderr.trim() || "Syntax error" };
306
+ }
307
+
308
+ // src/shell/repl.ts
309
+ var HISTORY_FILE = join(homedir(), ".config", "apes", "shell-history");
310
+ var PS1 = "apes$ ";
311
+ var PS2 = "> ";
312
+ var ShellRepl = class {
313
+ events;
314
+ input;
315
+ output;
316
+ quiet;
317
+ rl = null;
318
+ /** Accumulated input across multi-line continuations. */
319
+ buffer = "";
320
+ /** Whether the REPL has called `stop`. */
321
+ stopped = false;
322
+ constructor(events, options = {}) {
323
+ this.events = events;
324
+ this.input = options.input ?? process.stdin;
325
+ this.output = options.output ?? process.stdout;
326
+ this.quiet = options.quiet ?? false;
327
+ }
328
+ /**
329
+ * Start the REPL. Resolves when the user ends the session (Ctrl-D) or
330
+ * `stop()` is called from outside. Errors bubble out of `onLine` and
331
+ * cause the line to be rejected, but do NOT tear down the REPL.
332
+ */
333
+ async run() {
334
+ if (this.stopped)
335
+ return;
336
+ this.rl = createInterface({
337
+ input: this.input,
338
+ output: this.output,
339
+ prompt: PS1,
340
+ historySize: 1e3,
341
+ // Enable tab completion fallback (file names + history) via default.
342
+ terminal: true
343
+ });
344
+ if (!this.quiet)
345
+ this.writeBanner();
346
+ this.safePrompt(PS1);
347
+ return new Promise((resolve) => {
348
+ this.rl.on("line", async (line) => {
349
+ try {
350
+ await this.handleLine(line);
351
+ } catch (err) {
352
+ const msg = err instanceof Error ? err.message : String(err);
353
+ consola2.error(`Shell error: ${msg}`);
354
+ this.resetBuffer();
355
+ this.safePrompt(PS1);
356
+ }
357
+ });
358
+ this.rl.on("SIGINT", () => {
359
+ if (this.buffer.length > 0)
360
+ this.output.write("\n");
361
+ this.resetBuffer();
362
+ this.safePrompt(PS1);
363
+ });
364
+ this.rl.on("close", async () => {
365
+ this.stopped = true;
366
+ await this.events.onExit();
367
+ resolve();
368
+ });
369
+ });
370
+ }
371
+ /**
372
+ * Request the REPL to stop cleanly. Equivalent to the user pressing
373
+ * Ctrl-D. Typically called during shutdown or after a fatal error.
374
+ */
375
+ stop() {
376
+ if (this.rl)
377
+ this.rl.close();
378
+ }
379
+ // ----- internals -----
380
+ writeBanner() {
381
+ this.output.write("apes interactive shell\n");
382
+ this.output.write("Ctrl-D to exit.\n");
383
+ this.output.write("\n");
384
+ }
385
+ async handleLine(rawLine) {
386
+ this.buffer = this.buffer.length === 0 ? rawLine : `${this.buffer}
387
+ ${rawLine}`;
388
+ const status = checkMultiLineStatus(this.buffer);
389
+ if (status.kind === "continue") {
390
+ this.safePrompt(PS2);
391
+ return;
392
+ }
393
+ if (status.kind === "error") {
394
+ this.output.write(`${status.message}
395
+ `);
396
+ this.resetBuffer();
397
+ this.safePrompt(PS1);
398
+ return;
399
+ }
400
+ const completeLine = this.buffer;
401
+ this.resetBuffer();
402
+ if (completeLine.trim().length === 0) {
403
+ this.safePrompt(PS1);
404
+ return;
405
+ }
406
+ await this.events.onLine(completeLine);
407
+ this.safePrompt(PS1);
408
+ }
409
+ /**
410
+ * Draw a prompt, but only if the readline interface is still alive.
411
+ * The onLine callback may have triggered `stop()` (e.g., bash exited),
412
+ * in which case setPrompt/prompt would throw ERR_USE_AFTER_CLOSE.
413
+ */
414
+ safePrompt(prompt) {
415
+ if (this.stopped || !this.rl)
416
+ return;
417
+ try {
418
+ this.rl.setPrompt(prompt);
419
+ this.rl.prompt();
420
+ } catch (err) {
421
+ const code = err?.code;
422
+ if (code !== "ERR_USE_AFTER_CLOSE")
423
+ throw err;
424
+ }
425
+ }
426
+ resetBuffer() {
427
+ this.buffer = "";
428
+ }
429
+ };
430
+
431
+ // src/shell/session.ts
432
+ import { randomBytes as randomBytes2 } from "crypto";
433
+ var ShellSession = class {
434
+ id;
435
+ startedAt;
436
+ lineSeq = 0;
437
+ constructor(options) {
438
+ this.id = randomBytes2(8).toString("hex");
439
+ this.startedAt = Date.now();
440
+ appendAuditLog({
441
+ action: "shell-session-start",
442
+ session_id: this.id,
443
+ host: options.host,
444
+ requester: options.requester
445
+ });
446
+ }
447
+ /**
448
+ * Record a granted line that was approved for execution. Called after the
449
+ * grant flow returns approval but before (or right after) bash runs the
450
+ * command. Stores the grant id so the line can be traced back to the
451
+ * specific grant that authorized it.
452
+ */
453
+ logLineGranted(params) {
454
+ const seq = ++this.lineSeq;
455
+ appendAuditLog({
456
+ action: "shell-session-line",
457
+ session_id: this.id,
458
+ seq,
459
+ line: params.line,
460
+ grant_id: params.grantId,
461
+ grant_mode: params.grantMode,
462
+ status: "executing"
463
+ });
464
+ return seq;
465
+ }
466
+ /** Record the final exit code of a previously-granted line. */
467
+ logLineDone(params) {
468
+ appendAuditLog({
469
+ action: "shell-session-line-done",
470
+ session_id: this.id,
471
+ seq: params.seq,
472
+ exit_code: params.exitCode
473
+ });
474
+ }
475
+ /** Record that a line was denied by the grant flow and never reached bash. */
476
+ logLineDenied(params) {
477
+ const seq = ++this.lineSeq;
478
+ appendAuditLog({
479
+ action: "shell-session-line",
480
+ session_id: this.id,
481
+ seq,
482
+ line: params.line,
483
+ status: "denied",
484
+ reason: params.reason
485
+ });
486
+ }
487
+ /** Record session termination. Fires on clean Ctrl-D or bash death. */
488
+ close() {
489
+ appendAuditLog({
490
+ action: "shell-session-end",
491
+ session_id: this.id,
492
+ duration_ms: Date.now() - this.startedAt,
493
+ lines: this.lineSeq
494
+ });
495
+ }
496
+ };
497
+
498
+ // src/shell/orchestrator.ts
499
+ async function runInteractiveShell() {
500
+ let pendingResolve = null;
501
+ let lastExitCode = 0;
502
+ let shuttingDown = false;
503
+ let repl = null;
504
+ const bridge = new PtyBridge(
505
+ {
506
+ onOutput: (chunk) => {
507
+ process.stdout.write(chunk);
508
+ },
509
+ onLineDone: (frame) => {
510
+ if (pendingResolve) {
511
+ const r = pendingResolve;
512
+ pendingResolve = null;
513
+ lastExitCode = frame.exitCode;
514
+ r();
515
+ }
516
+ },
517
+ onExit: (exitCode) => {
518
+ if (!shuttingDown) {
519
+ process.stdout.write(`
520
+ [bash exited with code ${exitCode}]
521
+ `);
522
+ repl?.stop();
523
+ }
524
+ }
525
+ }
526
+ );
527
+ await bridge.waitForReady();
528
+ const targetHost = hostname();
529
+ const auth = loadAuth();
530
+ const session = new ShellSession({
531
+ host: targetHost,
532
+ requester: auth?.email ?? "unknown"
533
+ });
534
+ repl = new ShellRepl(
535
+ {
536
+ onLine: async (line) => {
537
+ const grant = await requestGrantForShellLine(line, {
538
+ targetHost,
539
+ approval: "once"
540
+ });
541
+ if (grant.kind === "denied") {
542
+ session.logLineDenied({ line, reason: grant.reason });
543
+ consola3.error(grant.reason);
544
+ return;
545
+ }
546
+ const seq = session.logLineGranted({
547
+ line,
548
+ grantId: grant.grantId,
549
+ grantMode: grant.mode
550
+ });
551
+ const wasRaw = process.stdin.isTTY && process.stdin.isRaw;
552
+ if (process.stdin.isTTY && !wasRaw)
553
+ process.stdin.setRawMode(true);
554
+ const forward = (chunk) => {
555
+ bridge.writeRaw(chunk.toString());
556
+ };
557
+ const rawInputAvailable = process.stdin.isTTY === true;
558
+ if (rawInputAvailable)
559
+ process.stdin.on("data", forward);
560
+ try {
561
+ await new Promise((resolve) => {
562
+ pendingResolve = resolve;
563
+ bridge.writeLine(line);
564
+ });
565
+ } finally {
566
+ if (rawInputAvailable) {
567
+ process.stdin.off("data", forward);
568
+ if (!wasRaw && process.stdin.isTTY)
569
+ process.stdin.setRawMode(false);
570
+ }
571
+ }
572
+ session.logLineDone({ seq, exitCode: lastExitCode });
573
+ if (lastExitCode !== 0) {
574
+ consola3.debug(`(exit ${lastExitCode})`);
575
+ }
576
+ },
577
+ onExit: () => {
578
+ shuttingDown = true;
579
+ session.close();
580
+ try {
581
+ bridge.kill();
582
+ } catch {
583
+ }
584
+ }
585
+ }
586
+ );
587
+ const onResize = () => {
588
+ const cols = process.stdout.columns ?? 80;
589
+ const rows = process.stdout.rows ?? 24;
590
+ try {
591
+ bridge.resize(cols, rows);
592
+ } catch {
593
+ }
594
+ };
595
+ process.stdout.on("resize", onResize);
596
+ const restoreTty = () => {
597
+ if (process.stdin.isTTY && process.stdin.isRaw) {
598
+ try {
599
+ process.stdin.setRawMode(false);
600
+ } catch {
601
+ }
602
+ }
603
+ };
604
+ process.on("exit", restoreTty);
605
+ const gracefulShutdown = () => {
606
+ if (shuttingDown)
607
+ return;
608
+ shuttingDown = true;
609
+ restoreTty();
610
+ try {
611
+ session.close();
612
+ } catch {
613
+ }
614
+ try {
615
+ bridge.kill();
616
+ } catch {
617
+ }
618
+ try {
619
+ repl?.stop();
620
+ } catch {
621
+ }
622
+ };
623
+ process.on("SIGTERM", gracefulShutdown);
624
+ process.on("SIGHUP", gracefulShutdown);
625
+ try {
626
+ await repl.run();
627
+ } finally {
628
+ process.stdout.off("resize", onResize);
629
+ process.off("exit", restoreTty);
630
+ process.off("SIGTERM", gracefulShutdown);
631
+ process.off("SIGHUP", gracefulShutdown);
632
+ }
633
+ }
634
+ export {
635
+ runInteractiveShell
636
+ };
637
+ //# sourceMappingURL=orchestrator-JAMWD6DD.js.map