orizu 0.5.23 → 0.5.24

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 (118) hide show
  1. package/README.md +18 -10
  2. package/dist/child-output-tee.d.ts +48 -0
  3. package/dist/child-output-tee.d.ts.map +1 -0
  4. package/dist/child-output-tee.js +512 -0
  5. package/dist/cli-auth-browser-page.d.ts +2 -0
  6. package/dist/cli-auth-browser-page.d.ts.map +1 -0
  7. package/dist/cli-auth-browser-page.js +107 -0
  8. package/dist/github-setup.js +8 -8
  9. package/dist/headless-login.d.ts +17 -0
  10. package/dist/headless-login.d.ts.map +1 -0
  11. package/dist/headless-login.js +105 -0
  12. package/dist/help.d.ts.map +1 -1
  13. package/dist/help.js +30 -22
  14. package/dist/hosted-boot.js +3 -3
  15. package/dist/hosted-bootstrap.js +2 -2
  16. package/dist/hosted-commands.d.ts +2 -0
  17. package/dist/hosted-commands.d.ts.map +1 -1
  18. package/dist/hosted-commands.js +11 -0
  19. package/dist/hosted-environment.d.ts +3 -0
  20. package/dist/hosted-environment.d.ts.map +1 -0
  21. package/dist/hosted-environment.js +2 -0
  22. package/dist/hosted-gepa-cli.d.ts +3 -0
  23. package/dist/hosted-gepa-cli.d.ts.map +1 -1
  24. package/dist/hosted-gepa-cli.js +92 -5
  25. package/dist/hosted-loop.d.ts +1 -1
  26. package/dist/hosted-loop.d.ts.map +1 -1
  27. package/dist/hosted-loop.js +2 -1
  28. package/dist/hosted-optimization-cli.d.ts +7 -0
  29. package/dist/hosted-optimization-cli.d.ts.map +1 -0
  30. package/dist/hosted-optimization-cli.js +218 -0
  31. package/dist/hosted-optimization-refusals.d.ts +88 -0
  32. package/dist/hosted-optimization-refusals.d.ts.map +1 -0
  33. package/dist/hosted-optimization-refusals.js +86 -0
  34. package/dist/hosted-optimization.d.ts +6 -0
  35. package/dist/hosted-optimization.d.ts.map +1 -0
  36. package/dist/hosted-optimization.js +436 -0
  37. package/dist/hosted-provider-settings.d.ts +3 -0
  38. package/dist/hosted-provider-settings.d.ts.map +1 -1
  39. package/dist/hosted-provider-settings.js +9 -0
  40. package/dist/hosted-session-cli.d.ts.map +1 -1
  41. package/dist/hosted-session-cli.js +6 -1
  42. package/dist/hosted-skill-staging.d.ts +9 -9
  43. package/dist/hosted-skill-staging.d.ts.map +1 -1
  44. package/dist/hosted-skill-staging.js +13 -10
  45. package/dist/index.d.ts +4 -4
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +338 -368
  48. package/dist/report-comments-cli.d.ts +1 -0
  49. package/dist/report-comments-cli.d.ts.map +1 -1
  50. package/dist/report-comments-cli.js +70 -4
  51. package/dist/runner-dir-verify.d.ts +5 -0
  52. package/dist/runner-dir-verify.d.ts.map +1 -1
  53. package/dist/runner-dir-verify.js +41 -9
  54. package/dist/runner-version-materialization.d.ts +6 -0
  55. package/dist/runner-version-materialization.d.ts.map +1 -0
  56. package/dist/runner-version-materialization.js +64 -0
  57. package/dist/setup-onboarding.d.ts +16 -0
  58. package/dist/setup-onboarding.d.ts.map +1 -0
  59. package/dist/setup-onboarding.js +59 -0
  60. package/dist/skill-installer.d.ts +3 -3
  61. package/dist/skill-installer.d.ts.map +1 -1
  62. package/dist/skill-installer.js +39 -3
  63. package/dist/skilled-proposer-launch.d.ts +5 -1
  64. package/dist/skilled-proposer-launch.d.ts.map +1 -1
  65. package/dist/skilled-proposer-launch.js +27 -5
  66. package/dist/skilled-proposer-wire.d.ts +2 -0
  67. package/dist/skilled-proposer-wire.d.ts.map +1 -0
  68. package/dist/skilled-proposer-wire.js +25 -0
  69. package/dist/workspace.d.ts +14 -0
  70. package/dist/workspace.d.ts.map +1 -1
  71. package/dist/workspace.js +63 -4
  72. package/package.json +1 -1
  73. package/scripts/vendor-orizu-skill.mjs +3 -3
  74. package/src/child-output-tee.ts +587 -0
  75. package/src/cli-auth-browser-page.ts +108 -0
  76. package/src/github-setup.ts +8 -8
  77. package/src/headless-login.ts +178 -0
  78. package/src/help.ts +30 -22
  79. package/src/hosted-boot.ts +3 -3
  80. package/src/hosted-bootstrap.ts +2 -2
  81. package/src/hosted-commands.ts +16 -0
  82. package/src/hosted-environment.ts +2 -0
  83. package/src/hosted-gepa-cli.ts +112 -4
  84. package/src/hosted-loop.ts +2 -1
  85. package/src/hosted-optimization-cli.ts +215 -0
  86. package/src/hosted-optimization-refusals.ts +101 -0
  87. package/src/hosted-optimization.ts +483 -0
  88. package/src/hosted-provider-settings.ts +12 -0
  89. package/src/hosted-session-cli.ts +6 -1
  90. package/src/hosted-skill-staging.ts +17 -14
  91. package/src/index.ts +289 -290
  92. package/src/report-comments-cli.ts +83 -5
  93. package/src/runner-dir-verify.ts +74 -9
  94. package/src/runner-version-materialization.ts +56 -0
  95. package/src/setup-onboarding.ts +64 -0
  96. package/src/skill-installer.ts +39 -3
  97. package/src/skilled-proposer-launch.ts +36 -5
  98. package/src/skilled-proposer-wire.ts +22 -0
  99. package/src/workspace.ts +65 -4
  100. package/vendor/orizu-gepa/src/orizu_gepa_connector/runtime.py +56 -21
  101. package/vendor/orizu-gepa-python/src/orizu_gepa/client.py +76 -13
  102. package/vendor/orizu-gepa-python/src/orizu_gepa/optimizer.py +16 -2
  103. package/vendor/skills/{orizu-cli → orizu}/SKILL.md +7 -3
  104. package/vendor/skills/orizu/agents/openai.yaml +4 -0
  105. package/vendor/skills/{orizu-cli → orizu}/references/assess-and-plan.md +1 -1
  106. package/vendor/skills/{orizu-cli → orizu}/references/building-apps.md +1 -1
  107. package/vendor/skills/{orizu-cli → orizu}/references/cli-reference.md +4 -4
  108. package/vendor/skills/{orizu-cli → orizu}/references/migrate-existing-gepa-setup.md +1 -1
  109. package/vendor/skills/{orizu-cli → orizu}/references/optimization-with-gepa.md +17 -3
  110. package/vendor/skills/orizu-cli/agents/openai.yaml +0 -4
  111. /package/vendor/skills/{orizu-cli → orizu}/references/building-judges.md +0 -0
  112. /package/vendor/skills/{orizu-cli → orizu}/references/dataset-design.md +0 -0
  113. /package/vendor/skills/{orizu-cli → orizu}/references/eval-strategy.md +0 -0
  114. /package/vendor/skills/{orizu-cli → orizu}/references/instructions-after-prompts.md +0 -0
  115. /package/vendor/skills/{orizu-cli → orizu}/references/optimization-reports.md +0 -0
  116. /package/vendor/skills/{orizu-cli → orizu}/references/primer.md +0 -0
  117. /package/vendor/skills/{orizu-cli → orizu}/references/prompt-control-plane.md +0 -0
  118. /package/vendor/skills/{orizu-cli → orizu}/scripts/test-app.mjs +0 -0
package/README.md CHANGED
@@ -86,12 +86,15 @@ Install the CLI globally with your package manager:
86
86
  | pnpm | `pnpm add -g orizu` |
87
87
  | Bun | `bun add -g orizu` |
88
88
 
89
- Then run the guided setup — it signs you in, initializes the local workspace
90
- contract, installs global coding-agent skill symlinks, and shows the next
91
- command for repo-specific Orizu adoption:
89
+ Then run the guided setup — it signs you in, asks you to choose or create a
90
+ team and project, initializes the local workspace contract, installs the shared
91
+ `~/.agents/skills/orizu` skill plus selected native targets, and optionally
92
+ launches Claude, Codex, or Pi:
92
93
 
93
94
  ```bash
94
95
  orizu setup
96
+ # Scripts use explicit choices after `orizu login --headless`:
97
+ orizu setup --team acme --project support-agent --agent codex --non-interactive
95
98
  ```
96
99
 
97
100
  To repair or customize skill installs later, run the companion skill installer
@@ -137,9 +140,8 @@ Agents are first-class users of this CLI: **every command supports `--json`**
137
140
  (as a prefix, `orizu --json teams list`, or a trailing flag) and emits a single
138
141
  machine-readable JSON document instead of formatted text. Discover the full
139
142
  command surface, including global options, with `orizu capabilities --json`.
140
- Hosted agents may read and sync instruction sets and prepare complete manifests. A local coding agent using the user's token runs instruction create, push, shape,
141
- archive, and restore itself after plan ratification. It gathers only read-only pointer-resolution and binding evidence; a human curator executes every production/default pointer command on every surface.
142
- In a hosted session, a human curator using the local CLI must run both categories.
143
+ Hosted agents may read and sync instruction sets, create profile seeds, run shape add/remove with team-scoped curator authority, and prepare complete manifests. A local coding agent using the user's token runs instruction create, push, shape add/remove, archive, and restore itself after plan ratification. It gathers only read-only pointer-resolution and binding evidence; a human curator executes every production/default pointer command on every surface.
144
+ In a hosted session, a human curator using the local CLI must run create/push, archive/restore, and every pointer command; shape add/remove stays agent-runnable because it creates unpromoted heads only.
143
145
 
144
146
  ## Authentication
145
147
 
@@ -152,6 +154,12 @@ orizu login
152
154
  This will open a browser tab for you to login with. You must either have an account on the [Orizu platform](https://orizu.ai) or it will help you create one.
153
155
  Approving the login creates a personal access token for the CLI and stores it in your local Orizu credentials file. You can revoke CLI tokens from the Personal Tokens page in Orizu.
154
156
 
157
+ In an SSH or display-less Linux session, the CLI automatically prints an approval URL and polls for completion instead of opening a local browser. Force this mode when needed with:
158
+
159
+ ```bash
160
+ orizu login --headless
161
+ ```
162
+
155
163
  **Check the signed-in user**
156
164
 
157
165
  ```bash
@@ -239,12 +247,12 @@ orizu logout
239
247
  <td colspan="2"><strong>Report comments</strong></td>
240
248
  </tr>
241
249
  <tr>
242
- <td>List report comments</td>
243
- <td><code>orizu comments list --prompt &lt;promptIdOrName&gt; --project ops-eval/support-qa</code><br><code>orizu comments list --run &lt;runId&gt;</code><br><code>orizu comments list --task &lt;taskId&gt;</code></td>
250
+ <td>List report or README comments</td>
251
+ <td><code>orizu comments list --prompt &lt;promptIdOrName&gt; --project ops-eval/support-qa</code><br><code>orizu comments list --run &lt;runId&gt;</code><br><code>orizu comments list --task &lt;taskId&gt;</code><br><code>orizu comments list --dataset &lt;datasetIdOrName&gt; --project ops-eval/support-qa</code></td>
244
252
  </tr>
245
253
  <tr>
246
254
  <td>Add or update comments</td>
247
- <td><code>orizu comments add --run &lt;runId&gt; --body @comment.md --anchor "Summary" --lines 4:6</code><br><code>orizu comments reply &lt;commentId&gt; --body "Fixed"</code><br><code>orizu comments resolve &lt;commentId&gt;</code></td>
255
+ <td><code>orizu comments add --run &lt;runId&gt; --body @comment.md --anchor "Summary" --lines 4:6</code><br><code>orizu comments add --dataset &lt;datasetIdOrName&gt; --project ops-eval/support-qa --body "Clarify this README" --anchor "Summary" --lines 4:6</code><br><code>orizu comments reply &lt;commentId&gt; --body "Fixed"</code><br><code>orizu comments resolve &lt;commentId&gt;</code></td>
248
256
  </tr>
249
257
  <tr>
250
258
  <td colspan="2"><strong>Diff comments</strong></td>
@@ -412,4 +420,4 @@ Use `--json` on supported task and app commands when automation needs structured
412
420
  ## More Documentation
413
421
 
414
422
  - [docs/cli.md](docs/cli.md): complete CLI guide and command reference
415
- - [skills/orizu-cli/references/cli-reference.md](skills/orizu-cli/references/cli-reference.md): compact command matrix and end-to-end flows
423
+ - [skills/orizu/references/cli-reference.md](skills/orizu/references/cli-reference.md): compact command matrix and end-to-end flows
@@ -0,0 +1,48 @@
1
+ import { Readable, Writable } from 'node:stream';
2
+ declare class FileDescriptorSink extends Writable {
3
+ private readonly descriptor;
4
+ private readonly activeWriteCancellations;
5
+ private isLiveOutputAvailable;
6
+ private relay;
7
+ constructor(descriptor: number);
8
+ _write(chunk: Buffer, _encoding: BufferEncoding, callback: (error?: Error | null) => void): void;
9
+ prepare(): Promise<void>;
10
+ cancelPendingWrites(): void;
11
+ private relayProcess;
12
+ }
13
+ export declare const childOutputSinks: {
14
+ stdout: FileDescriptorSink;
15
+ stderr: FileDescriptorSink;
16
+ };
17
+ export interface PipedChild {
18
+ stdout: Readable;
19
+ stderr: Readable;
20
+ exited: Promise<number | null>;
21
+ signalCode: NodeJS.Signals | null;
22
+ kill(signal?: number | NodeJS.Signals): void;
23
+ }
24
+ export declare function spawnPipedChild(command: string, args: string[], environment: NodeJS.ProcessEnv): Promise<PipedChild>;
25
+ export declare function writeChildStderr(text: string): Promise<void>;
26
+ export interface ChildOutputForwarding {
27
+ completed: Promise<void>;
28
+ failure: Promise<Error>;
29
+ failureReason(): Error | undefined;
30
+ expectSourceClose(): void;
31
+ stopLiveOutput(): void;
32
+ }
33
+ export interface ChildOutputCapture {
34
+ stdout(chunk: string): void;
35
+ stderr(chunk: string): void;
36
+ }
37
+ export declare function completePipedChild(child: PipedChild, forwarding: readonly ChildOutputForwarding[]): Promise<number | null>;
38
+ export declare function runPipedChild(command: string, args: string[], environment: NodeJS.ProcessEnv, onSpawn: () => Promise<void>, capture: ChildOutputCapture, observeChild?: (child: PipedChild) => void): Promise<{
39
+ status: number | null;
40
+ error?: Error;
41
+ }>;
42
+ /**
43
+ * Forward a child stream live while retaining a bounded caller-owned copy.
44
+ * Transient sink slowness pushes back; a stall degrades safely to capture-only.
45
+ */
46
+ export declare function teeChildOutput(source: Readable, destination: Writable, capture: (chunk: string) => void): ChildOutputForwarding;
47
+ export {};
48
+ //# sourceMappingURL=child-output-tee.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"child-output-tee.d.ts","sourceRoot":"","sources":["../src/child-output-tee.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAsHhD,cAAM,kBAAmB,SAAQ,QAAQ;IAK3B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAJvC,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAwB;IACjE,OAAO,CAAC,qBAAqB,CAAO;IACpC,OAAO,CAAC,KAAK,CAA6B;gBAEb,UAAU,EAAE,MAAM;IAItC,MAAM,CACb,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,cAAc,EACzB,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GACvC,IAAI;IAgCD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAK9B,mBAAmB,IAAI,IAAI;IAS3B,OAAO,CAAC,YAAY;CAiCrB;AAED,eAAO,MAAM,gBAAgB;;;CAG5B,CAAA;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,QAAQ,CAAA;IAChB,MAAM,EAAE,QAAQ,CAAA;IAChB,MAAM,EAAE,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IAC9B,UAAU,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAA;IACjC,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,OAAO,GAAG,IAAI,CAAA;CAC7C;AAqDD,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,WAAW,EAAE,MAAM,CAAC,UAAU,GAC7B,OAAO,CAAC,UAAU,CAAC,CAiDrB;AAoCD,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAWlE;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;IACxB,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,CAAA;IACvB,aAAa,IAAI,KAAK,GAAG,SAAS,CAAA;IAClC,iBAAiB,IAAI,IAAI,CAAA;IACzB,cAAc,IAAI,IAAI,CAAA;CACvB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B;AAuCD,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,UAAU,EACjB,UAAU,EAAE,SAAS,qBAAqB,EAAE,GAC3C,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA6CxB;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,WAAW,EAAE,MAAM,CAAC,UAAU,EAC9B,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EAC5B,OAAO,EAAE,kBAAkB,EAC3B,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,GACzC,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC,CAqBnD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,QAAQ,EAChB,WAAW,EAAE,QAAQ,EACrB,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAC/B,qBAAqB,CA2DvB"}
@@ -0,0 +1,512 @@
1
+ import { spawn, spawnSync } from 'node:child_process';
2
+ import { closeSync, constants, mkdtempSync, openSync, read as readFileDescriptor, rmSync, } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { Readable, Writable } from 'node:stream';
6
+ const CHILD_OUTPUT_PREFETCH_MAX_BYTES = 64 * 1024;
7
+ const CHILD_OUTPUT_READ_BUFFER_BYTES = 16 * 1024;
8
+ const EMPTY_FIFO_INITIAL_RETRY_MS = 4;
9
+ const EMPTY_FIFO_MAX_RETRY_MS = 64;
10
+ const FORWARDING_FAILURE_TERMINATION_TIMEOUT_MS = 250, LIVE_OUTPUT_STALL_TIMEOUT_MS = 1_500;
11
+ const LIVE_OUTPUT_RELAY_SCRIPT = [
12
+ "const { writeSync } = require('node:fs')",
13
+ 'process.stdout._handle?.setBlocking(true)',
14
+ 'let buffered = Buffer.alloc(0)',
15
+ 'process.stdin.on(\'data\', chunk => {',
16
+ ' buffered = Buffer.concat([buffered, chunk])',
17
+ ' while (buffered.length >= 4) {',
18
+ ' const length = buffered.readUInt32BE(0)',
19
+ ' if (buffered.length < length + 4) return',
20
+ ' const payload = buffered.subarray(4, length + 4)',
21
+ ' buffered = buffered.subarray(length + 4)',
22
+ ' for (let offset = 0; offset < payload.length; )',
23
+ ' offset += writeSync(1, payload, offset, payload.length - offset)',
24
+ " writeSync(3, '1')",
25
+ ' }',
26
+ '})',
27
+ "writeSync(3, 'R')",
28
+ ].join('\n');
29
+ class FileDescriptorSource extends Readable {
30
+ descriptor;
31
+ readBuffer = Buffer.allocUnsafe(CHILD_OUTPUT_READ_BUFFER_BYTES);
32
+ emptyRetryMilliseconds = EMPTY_FIFO_INITIAL_RETRY_MS;
33
+ isReadPending = false;
34
+ retryTimer;
35
+ pendingDestroy;
36
+ constructor(descriptor) {
37
+ super({ highWaterMark: CHILD_OUTPUT_PREFETCH_MAX_BYTES });
38
+ this.descriptor = descriptor;
39
+ }
40
+ _read() {
41
+ if (this.isReadPending || this.retryTimer || this.descriptor === undefined)
42
+ return;
43
+ this.isReadPending = true;
44
+ readFileDescriptor(this.descriptor, this.readBuffer, 0, this.readBuffer.byteLength, null, (error, bytesRead) => {
45
+ this.isReadPending = false;
46
+ if (this.pendingDestroy) {
47
+ this.finishDestroy();
48
+ return;
49
+ }
50
+ if (error) {
51
+ if (error.code === 'EINTR') {
52
+ this._read();
53
+ return;
54
+ }
55
+ if (['EAGAIN', 'EWOULDBLOCK'].includes(error.code ?? '')) {
56
+ const delay = this.emptyRetryMilliseconds;
57
+ this.emptyRetryMilliseconds = Math.min(EMPTY_FIFO_MAX_RETRY_MS, this.emptyRetryMilliseconds * 2);
58
+ this.retryTimer = setTimeout(() => {
59
+ this.retryTimer = undefined;
60
+ this._read();
61
+ }, delay);
62
+ return;
63
+ }
64
+ this.destroy(error);
65
+ return;
66
+ }
67
+ if (bytesRead === 0) {
68
+ this.push(null);
69
+ return;
70
+ }
71
+ this.emptyRetryMilliseconds = EMPTY_FIFO_INITIAL_RETRY_MS;
72
+ this.push(Buffer.from(this.readBuffer.subarray(0, bytesRead)));
73
+ });
74
+ }
75
+ _destroy(error, callback) {
76
+ if (this.retryTimer)
77
+ clearTimeout(this.retryTimer);
78
+ this.retryTimer = undefined;
79
+ this.pendingDestroy = { error, callback };
80
+ if (!this.isReadPending)
81
+ this.finishDestroy();
82
+ }
83
+ finishDestroy() {
84
+ const pendingDestroy = this.pendingDestroy;
85
+ if (!pendingDestroy)
86
+ return;
87
+ this.pendingDestroy = undefined;
88
+ const descriptor = this.descriptor;
89
+ this.descriptor = undefined;
90
+ try {
91
+ if (descriptor !== undefined)
92
+ closeSync(descriptor);
93
+ pendingDestroy.callback(pendingDestroy.error);
94
+ }
95
+ catch (error) {
96
+ pendingDestroy.callback(error instanceof Error ? error : new Error(String(error)));
97
+ }
98
+ }
99
+ }
100
+ class FileDescriptorSink extends Writable {
101
+ descriptor;
102
+ activeWriteCancellations = new Set();
103
+ isLiveOutputAvailable = true;
104
+ relay;
105
+ constructor(descriptor) {
106
+ super({ highWaterMark: 16 * 1024 });
107
+ this.descriptor = descriptor;
108
+ }
109
+ _write(chunk, _encoding, callback) {
110
+ if (!this.isLiveOutputAvailable) {
111
+ callback();
112
+ return;
113
+ }
114
+ let relay;
115
+ let hasSettled = false;
116
+ const settle = (error) => {
117
+ if (hasSettled)
118
+ return;
119
+ hasSettled = true;
120
+ if (error)
121
+ this.isLiveOutputAvailable = false;
122
+ this.activeWriteCancellations.delete(cancel);
123
+ relay?.acknowledgements.off('data', acknowledge);
124
+ relay?.child.off('exit', relayExited);
125
+ callback(error);
126
+ };
127
+ const acknowledge = () => settle();
128
+ const relayExited = () => settle(new Error('live output relay exited before write acknowledgement'));
129
+ const cancel = () => settle();
130
+ this.activeWriteCancellations.add(cancel);
131
+ try {
132
+ relay = this.relayProcess();
133
+ }
134
+ catch (error) {
135
+ settle(errorFromUnknown(error));
136
+ return;
137
+ }
138
+ void relay.ready.then(() => {
139
+ if (hasSettled)
140
+ return;
141
+ relay.acknowledgements.once('data', acknowledge);
142
+ relay.child.once('exit', relayExited);
143
+ const frame = Buffer.allocUnsafe(chunk.byteLength + 4);
144
+ frame.writeUInt32BE(chunk.byteLength);
145
+ chunk.copy(frame, 4);
146
+ relay.input.write(frame, error => { if (error)
147
+ settle(error); });
148
+ }).catch(error => settle(errorFromUnknown(error)));
149
+ }
150
+ async prepare() {
151
+ if (!this.isLiveOutputAvailable)
152
+ return;
153
+ await this.relayProcess().ready;
154
+ }
155
+ cancelPendingWrites() {
156
+ this.isLiveOutputAvailable = false;
157
+ const relay = this.relay;
158
+ this.relay = undefined;
159
+ relay?.input.destroy();
160
+ relay?.child.kill('SIGKILL');
161
+ for (const cancel of [...this.activeWriteCancellations])
162
+ cancel();
163
+ }
164
+ relayProcess() {
165
+ if (this.relay)
166
+ return this.relay;
167
+ const child = spawn('node', ['-e', LIVE_OUTPUT_RELAY_SCRIPT], {
168
+ stdio: ['pipe', this.descriptor, 'ignore', 'pipe'],
169
+ });
170
+ const input = child.stdin;
171
+ const acknowledgements = child.stdio[3];
172
+ if (!input || !acknowledgements)
173
+ throw new Error('live output relay has no control pipes');
174
+ child.on('error', () => { });
175
+ input.on('error', () => { });
176
+ acknowledgements.on('error', () => { });
177
+ const ready = new Promise((resolve, reject) => {
178
+ acknowledgements.once('data', chunk => {
179
+ if (chunk.includes('R'.charCodeAt(0)))
180
+ resolve();
181
+ else
182
+ reject(new Error('live output relay sent an invalid readiness acknowledgement'));
183
+ });
184
+ child.once('exit', () => reject(new Error('live output relay exited before readiness')));
185
+ child.once('error', reject);
186
+ });
187
+ void ready.catch(() => { });
188
+ child.unref();
189
+ void ready.then(() => {
190
+ input.unref?.();
191
+ acknowledgements.unref?.();
192
+ }, () => { });
193
+ const relay = { child, input, acknowledgements, ready };
194
+ child.once('exit', () => {
195
+ if (this.relay?.child !== child)
196
+ return;
197
+ this.relay = undefined;
198
+ });
199
+ this.relay = relay;
200
+ return relay;
201
+ }
202
+ }
203
+ export const childOutputSinks = {
204
+ stdout: new FileDescriptorSink(1),
205
+ stderr: new FileDescriptorSink(2),
206
+ };
207
+ const POST_LEADER_CAPTURE_DRAIN_TIMEOUT_MS = 250;
208
+ function closeDescriptor(descriptor) {
209
+ if (descriptor === undefined)
210
+ return;
211
+ try {
212
+ closeSync(descriptor);
213
+ }
214
+ catch {
215
+ // Best-effort cleanup after another operation has already failed.
216
+ }
217
+ }
218
+ function createOutputFifo(name) {
219
+ const root = mkdtempSync(join(tmpdir(), 'orizu-child-output-'));
220
+ const fifoPath = join(root, name);
221
+ let anchorDescriptor;
222
+ let readDescriptor;
223
+ let writeDescriptor;
224
+ try {
225
+ const creation = spawnSync('mkfifo', [fifoPath], { stdio: 'ignore' });
226
+ if (creation.error)
227
+ throw creation.error;
228
+ if (creation.status !== 0) {
229
+ throw new Error(`mkfifo exited with status ${creation.status ?? 'unknown'}`);
230
+ }
231
+ // The temporary read/write anchor prevents either open from waiting for
232
+ // its peer. The child inherits the blocking write end; the parent polls a
233
+ // nonblocking read end so descendant-held descriptors remain cancellable.
234
+ anchorDescriptor = openSync(fifoPath, constants.O_RDWR | constants.O_NONBLOCK);
235
+ readDescriptor = openSync(fifoPath, constants.O_RDONLY | constants.O_NONBLOCK);
236
+ writeDescriptor = openSync(fifoPath, constants.O_WRONLY);
237
+ closeDescriptor(anchorDescriptor);
238
+ anchorDescriptor = undefined;
239
+ const source = new FileDescriptorSource(readDescriptor);
240
+ readDescriptor = undefined;
241
+ rmSync(root, { force: true, recursive: true });
242
+ return { source, writeDescriptor };
243
+ }
244
+ catch (error) {
245
+ closeDescriptor(writeDescriptor);
246
+ closeDescriptor(readDescriptor);
247
+ closeDescriptor(anchorDescriptor);
248
+ rmSync(root, { force: true, recursive: true });
249
+ throw error;
250
+ }
251
+ }
252
+ export async function spawnPipedChild(command, args, environment) {
253
+ const stdout = createOutputFifo('stdout');
254
+ let stderr;
255
+ try {
256
+ stderr = createOutputFifo('stderr');
257
+ }
258
+ catch (error) {
259
+ stdout.source.destroy();
260
+ closeDescriptor(stdout.writeDescriptor);
261
+ throw error;
262
+ }
263
+ let child;
264
+ try {
265
+ // Begin the bounded parent-side drain before an immediate child crash can
266
+ // strand bytes in the child's userland stdout/stderr buffers.
267
+ stdout.source.read(0);
268
+ stderr.source.read(0);
269
+ child = spawn(command, args, {
270
+ env: environment,
271
+ stdio: ['inherit', stdout.writeDescriptor, stderr.writeDescriptor],
272
+ });
273
+ }
274
+ catch (error) {
275
+ stdout.source.destroy();
276
+ stderr.source.destroy();
277
+ throw error;
278
+ }
279
+ finally {
280
+ closeDescriptor(stdout.writeDescriptor);
281
+ closeDescriptor(stderr.writeDescriptor);
282
+ }
283
+ // `close` also waits for descendant-held stdio descriptors. Observe the
284
+ // leader's exit so completePipedChild can enforce its bounded EOF grace.
285
+ const exited = new Promise(resolve => child.once('exit', resolve));
286
+ try {
287
+ await new Promise((resolve, reject) => {
288
+ child.once('spawn', resolve);
289
+ child.once('error', reject);
290
+ });
291
+ }
292
+ catch (error) {
293
+ stdout.source.destroy();
294
+ stderr.source.destroy();
295
+ throw error;
296
+ }
297
+ return {
298
+ stdout: stdout.source,
299
+ stderr: stderr.source,
300
+ exited,
301
+ get signalCode() { return child.signalCode; },
302
+ kill: signal => { child.kill(signal); },
303
+ };
304
+ }
305
+ const liveSinkStates = new WeakMap();
306
+ function liveSinkState(destination) {
307
+ const existing = liveSinkStates.get(destination);
308
+ if (existing)
309
+ return existing;
310
+ const state = { hasFailed: false };
311
+ destination.on('error', () => { state.hasFailed = true; });
312
+ liveSinkStates.set(destination, state);
313
+ return state;
314
+ }
315
+ async function writeLiveOutput(destination, state, text) {
316
+ if (state.hasFailed)
317
+ return;
318
+ await new Promise(resolve => {
319
+ try {
320
+ destination.write(text, error => {
321
+ if (error)
322
+ state.hasFailed = true;
323
+ resolve();
324
+ });
325
+ }
326
+ catch {
327
+ state.hasFailed = true;
328
+ resolve();
329
+ }
330
+ });
331
+ }
332
+ export async function writeChildStderr(text) {
333
+ const sink = childOutputSinks.stderr;
334
+ let timer;
335
+ const didForward = await Promise.race([
336
+ writeLiveOutput(sink, liveSinkState(sink), `${text}\n`).then(() => true),
337
+ new Promise(resolve => {
338
+ timer = setTimeout(() => resolve(false), LIVE_OUTPUT_STALL_TIMEOUT_MS);
339
+ }),
340
+ ]);
341
+ if (timer)
342
+ clearTimeout(timer);
343
+ if (!didForward)
344
+ sink.cancelPendingWrites();
345
+ }
346
+ function errorFromUnknown(cause) {
347
+ return cause instanceof Error ? cause : new Error(String(cause));
348
+ }
349
+ async function waitForChildExit(child, timeoutMilliseconds) {
350
+ let timer;
351
+ const didExit = await Promise.race([
352
+ child.exited.then(() => true),
353
+ new Promise(resolve => {
354
+ timer = setTimeout(() => resolve(false), timeoutMilliseconds);
355
+ }),
356
+ ]);
357
+ if (timer)
358
+ clearTimeout(timer);
359
+ return didExit;
360
+ }
361
+ async function terminateAfterForwardingFailure(child, forwarding, captureDrain, error) {
362
+ child.kill('SIGTERM');
363
+ for (const stream of forwarding)
364
+ stream.stopLiveOutput();
365
+ child.stdout.destroy();
366
+ child.stderr.destroy();
367
+ await captureDrain;
368
+ if (!await waitForChildExit(child, FORWARDING_FAILURE_TERMINATION_TIMEOUT_MS)) {
369
+ child.kill('SIGKILL');
370
+ await waitForChildExit(child, FORWARDING_FAILURE_TERMINATION_TIMEOUT_MS);
371
+ }
372
+ throw error;
373
+ }
374
+ export async function completePipedChild(child, forwarding) {
375
+ const captureDrain = Promise.allSettled(forwarding.map(stream => stream.completed));
376
+ const firstFailure = Promise.race(forwarding.map(stream => stream.failure));
377
+ const firstOutcome = await Promise.race([
378
+ child.exited.then(status => ({ kind: 'exit', status })),
379
+ firstFailure.then(error => ({ kind: 'failure', error })),
380
+ ]);
381
+ if (firstOutcome.kind === 'failure') {
382
+ return terminateAfterForwardingFailure(child, forwarding, captureDrain, firstOutcome.error);
383
+ }
384
+ const status = firstOutcome.status;
385
+ const waitForCaptureDrain = async () => {
386
+ let timer;
387
+ const didDrain = await Promise.race([
388
+ captureDrain.then(() => true),
389
+ new Promise(resolve => {
390
+ timer = setTimeout(() => resolve(false), POST_LEADER_CAPTURE_DRAIN_TIMEOUT_MS);
391
+ }),
392
+ ]);
393
+ if (timer)
394
+ clearTimeout(timer);
395
+ return didDrain;
396
+ };
397
+ const throwIfCaptureFailed = async () => {
398
+ await captureDrain;
399
+ const failure = forwarding.map(stream => stream.failureReason()).find(Boolean);
400
+ if (failure)
401
+ throw failure;
402
+ };
403
+ // Preserve ordinary live forwarding when EOF is already on its way. If a
404
+ // blocked sink or descendant-held descriptor prevents that bounded drain,
405
+ // switch to capture-only before enforcing the same bounded EOF grace.
406
+ if (await waitForCaptureDrain()) {
407
+ await throwIfCaptureFailed();
408
+ return status;
409
+ }
410
+ for (const stream of forwarding)
411
+ stream.stopLiveOutput();
412
+ if (await waitForCaptureDrain()) {
413
+ await throwIfCaptureFailed();
414
+ return status;
415
+ }
416
+ for (const stream of forwarding)
417
+ stream.expectSourceClose();
418
+ child.stdout.destroy();
419
+ child.stderr.destroy();
420
+ await throwIfCaptureFailed();
421
+ return status;
422
+ }
423
+ export async function runPipedChild(command, args, environment, onSpawn, capture, observeChild) {
424
+ await Promise.all([childOutputSinks.stdout.prepare(), childOutputSinks.stderr.prepare()]);
425
+ const child = await spawnPipedChild(command, args, environment);
426
+ observeChild?.(child);
427
+ const forwarding = [
428
+ teeChildOutput(child.stdout, childOutputSinks.stdout, capture.stdout),
429
+ teeChildOutput(child.stderr, childOutputSinks.stderr, capture.stderr),
430
+ ];
431
+ const completion = completePipedChild(child, forwarding);
432
+ const firstForwardingFailure = Promise.race(forwarding.map(stream => stream.failure)).then(error => { throw error; });
433
+ try {
434
+ await Promise.race([onSpawn(), firstForwardingFailure]);
435
+ const exitCode = await completion;
436
+ return { status: child.signalCode ? null : exitCode };
437
+ }
438
+ catch (cause) {
439
+ child.kill('SIGTERM');
440
+ await completion.catch(() => undefined);
441
+ throw cause;
442
+ }
443
+ }
444
+ /**
445
+ * Forward a child stream live while retaining a bounded caller-owned copy.
446
+ * Transient sink slowness pushes back; a stall degrades safely to capture-only.
447
+ */
448
+ export function teeChildOutput(source, destination, capture) {
449
+ let isLiveOutputEnabled = true;
450
+ let isSourceCloseExpected = false;
451
+ let unexpectedFailure;
452
+ let remainingLiveOutputWaitMilliseconds = LIVE_OUTPUT_STALL_TIMEOUT_MS;
453
+ let liveOutputWaitFinishedAt = performance.now();
454
+ let resolveLiveOutputStopped = () => { };
455
+ const liveOutputStopped = new Promise(resolve => { resolveLiveOutputStopped = resolve; });
456
+ const stopLiveOutput = () => {
457
+ if (!isLiveOutputEnabled)
458
+ return;
459
+ isLiveOutputEnabled = false;
460
+ if (destination instanceof FileDescriptorSink)
461
+ destination.cancelPendingWrites();
462
+ resolveLiveOutputStopped();
463
+ };
464
+ const sinkState = liveSinkState(destination);
465
+ const forward = async (text) => {
466
+ capture(text);
467
+ if (!isLiveOutputEnabled)
468
+ return;
469
+ const waitStartedAt = performance.now();
470
+ remainingLiveOutputWaitMilliseconds = Math.min(LIVE_OUTPUT_STALL_TIMEOUT_MS, remainingLiveOutputWaitMilliseconds + waitStartedAt - liveOutputWaitFinishedAt);
471
+ let stallTimer;
472
+ const wasForwarded = await Promise.race([
473
+ writeLiveOutput(destination, sinkState, text).then(() => true),
474
+ liveOutputStopped.then(() => false),
475
+ new Promise(resolve => {
476
+ stallTimer = setTimeout(() => resolve(false), remainingLiveOutputWaitMilliseconds);
477
+ }),
478
+ ]);
479
+ if (stallTimer)
480
+ clearTimeout(stallTimer);
481
+ liveOutputWaitFinishedAt = performance.now();
482
+ remainingLiveOutputWaitMilliseconds -= liveOutputWaitFinishedAt - waitStartedAt;
483
+ if (!wasForwarded || remainingLiveOutputWaitMilliseconds <= 0)
484
+ stopLiveOutput();
485
+ };
486
+ const completed = (async () => {
487
+ const decoder = new TextDecoder();
488
+ for await (const chunk of source) {
489
+ const text = decoder.decode(chunk, { stream: true });
490
+ if (text)
491
+ await forward(text);
492
+ }
493
+ const remainder = decoder.decode();
494
+ if (remainder)
495
+ await forward(remainder);
496
+ })();
497
+ let resolveFailure = () => { };
498
+ const failure = new Promise(resolve => { resolveFailure = resolve; });
499
+ void completed.catch(cause => {
500
+ if (isSourceCloseExpected)
501
+ return;
502
+ unexpectedFailure = errorFromUnknown(cause);
503
+ resolveFailure(unexpectedFailure);
504
+ });
505
+ return {
506
+ completed,
507
+ failure,
508
+ failureReason: () => unexpectedFailure,
509
+ expectSourceClose: () => { isSourceCloseExpected = true; },
510
+ stopLiveOutput,
511
+ };
512
+ }
@@ -0,0 +1,2 @@
1
+ export declare function renderCliAuthBrowserPage(status: 'success' | 'error'): string;
2
+ //# sourceMappingURL=cli-auth-browser-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-auth-browser-page.d.ts","sourceRoot":"","sources":["../src/cli-auth-browser-page.ts"],"names":[],"mappings":"AAAA,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,MAAM,CA2G5E"}