orizu 0.5.27 → 0.5.29

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 (93) hide show
  1. package/README.md +52 -0
  2. package/dist/cli-project-slug.d.ts +17 -0
  3. package/dist/cli-project-slug.d.ts.map +1 -0
  4. package/dist/cli-project-slug.js +24 -0
  5. package/dist/credentials.d.ts +4 -0
  6. package/dist/credentials.d.ts.map +1 -1
  7. package/dist/credentials.js +15 -4
  8. package/dist/dataset-transfer-download.d.ts +2 -0
  9. package/dist/dataset-transfer-download.d.ts.map +1 -0
  10. package/dist/dataset-transfer-download.js +66 -0
  11. package/dist/dataset-transfer-policy.d.ts +2 -0
  12. package/dist/dataset-transfer-policy.d.ts.map +1 -0
  13. package/dist/dataset-transfer-policy.js +8 -0
  14. package/dist/dataset-transfer-upload.d.ts +42 -0
  15. package/dist/dataset-transfer-upload.d.ts.map +1 -0
  16. package/dist/dataset-transfer-upload.js +110 -0
  17. package/dist/feedback-cli.d.ts +14 -0
  18. package/dist/feedback-cli.d.ts.map +1 -0
  19. package/dist/feedback-cli.js +601 -0
  20. package/dist/feedback-contract.d.ts +77 -0
  21. package/dist/feedback-contract.d.ts.map +1 -0
  22. package/dist/feedback-contract.js +414 -0
  23. package/dist/feedback-scrub.d.ts +16 -0
  24. package/dist/feedback-scrub.d.ts.map +1 -0
  25. package/dist/feedback-scrub.js +324 -0
  26. package/dist/headless-login.d.ts +2 -0
  27. package/dist/headless-login.d.ts.map +1 -1
  28. package/dist/headless-login.js +14 -11
  29. package/dist/help.d.ts.map +1 -1
  30. package/dist/help.js +35 -1
  31. package/dist/hosted-boot.d.ts +5 -0
  32. package/dist/hosted-boot.d.ts.map +1 -1
  33. package/dist/hosted-boot.js +12 -3
  34. package/dist/hosted-harness-opencode.d.ts +8 -9
  35. package/dist/hosted-harness-opencode.d.ts.map +1 -1
  36. package/dist/hosted-harness-opencode.js +12 -9
  37. package/dist/hosted-loop.d.ts.map +1 -1
  38. package/dist/hosted-loop.js +15 -9
  39. package/dist/http.d.ts +6 -4
  40. package/dist/http.d.ts.map +1 -1
  41. package/dist/http.js +51 -28
  42. package/dist/index.d.ts +1 -1
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +82 -111
  45. package/dist/instruction-set-sync/index.d.ts +1 -0
  46. package/dist/instruction-set-sync/index.d.ts.map +1 -1
  47. package/dist/instruction-set-sync/index.js +15 -6
  48. package/dist/last-error-caps.d.ts +6 -0
  49. package/dist/last-error-caps.d.ts.map +1 -0
  50. package/dist/last-error-caps.js +5 -0
  51. package/dist/last-error-record.d.ts +34 -0
  52. package/dist/last-error-record.d.ts.map +1 -0
  53. package/dist/last-error-record.js +289 -0
  54. package/dist/runner-env.d.ts +3 -0
  55. package/dist/runner-env.d.ts.map +1 -0
  56. package/dist/runner-env.js +29 -0
  57. package/dist/self-update.d.ts +42 -0
  58. package/dist/self-update.d.ts.map +1 -0
  59. package/dist/self-update.js +506 -0
  60. package/package.json +2 -1
  61. package/src/cli-project-slug.ts +38 -0
  62. package/src/credentials.ts +21 -4
  63. package/src/dataset-transfer-download.ts +61 -0
  64. package/src/dataset-transfer-policy.ts +8 -0
  65. package/src/dataset-transfer-upload.ts +145 -0
  66. package/src/feedback-cli.ts +763 -0
  67. package/src/feedback-contract.ts +473 -0
  68. package/src/feedback-scrub.ts +466 -0
  69. package/src/headless-login.ts +25 -11
  70. package/src/help.ts +35 -1
  71. package/src/hosted-boot.ts +21 -3
  72. package/src/hosted-harness-opencode.ts +12 -9
  73. package/src/hosted-loop.ts +15 -8
  74. package/src/http.ts +56 -27
  75. package/src/index.ts +82 -152
  76. package/src/instruction-set-sync/index.ts +25 -6
  77. package/src/last-error-caps.js +4 -0
  78. package/src/last-error-caps.ts +5 -0
  79. package/src/last-error-record.ts +382 -0
  80. package/src/runner-env.ts +31 -0
  81. package/src/secret-redaction.js +4 -0
  82. package/src/self-update.ts +548 -0
  83. package/vendor/skills/orizu/SKILL.md +4 -0
  84. package/vendor/skills/orizu/flows/expand.md +2 -0
  85. package/vendor/skills/orizu/flows/first-win.md +2 -0
  86. package/vendor/skills/orizu/flows/onboard.md +2 -0
  87. package/vendor/skills/orizu/flows/promote.md +2 -0
  88. package/vendor/skills/orizu/flows/recurse.md +2 -0
  89. package/vendor/skills/orizu/flows/triage.md +2 -0
  90. package/vendor/skills/orizu/references/authority-map.md +1 -0
  91. package/vendor/skills/orizu/references/cli-reference.md +62 -0
  92. package/vendor/skills/orizu/references/feedback.md +101 -0
  93. package/vendor/skills/orizu/references/prompt-control-plane.md +4 -3
package/README.md CHANGED
@@ -156,6 +156,35 @@ This prints the skill location plus source metadata (`name`, `root`, `skillMd`,
156
156
  to reading `SKILL.md` in two commands and verify the guidance matches the CLI
157
157
  that supplied it.
158
158
 
159
+ ### Update the CLI
160
+
161
+ An npm-global install can update itself to npm's `latest` release:
162
+
163
+ ```bash
164
+ orizu update [--dry-run] [--json]
165
+ ```
166
+
167
+ The command detects the npm prefix, checks and compares versions, installs only
168
+ when `latest` is newer, and verifies the newly installed binary in a fresh
169
+ process. A successful `orizu update` automatically runs `orizu skills update`
170
+ with the installed CLI, including when Orizu is already the latest. `--dry-run`
171
+ stops after the version comparison without installing or refreshing skills. Source checkouts,
172
+ `npx` caches, and pnpm, Bun, or Yarn global layouts are refused without being
173
+ modified.
174
+ For now, Windows installs and custom npm prefixes without their own `bin/node`
175
+ (for example `npm config set prefix ~/.npm-global`) are refused with
176
+ `update_unsupported_install`; use the manual npm command in that error.
177
+
178
+ On other human-readable commands, an npm-global install may print one stderr
179
+ line when its cached release check finds a newer version. Cache refresh happens
180
+ in a detached process at most once every 24 hours, so commands do not wait for
181
+ the registry. Notices and refreshes are suppressed for `--json`, CI,
182
+ `NO_UPDATE_NOTIFIER`, and `update` itself. Disable them explicitly with:
183
+
184
+ ```bash
185
+ ORIZU_NO_UPDATE_CHECK=1 orizu --help
186
+ ```
187
+
159
188
  Agents are first-class users of this CLI: **every command supports `--json`**
160
189
  (as a prefix, `orizu --json teams list`, or a trailing flag) and emits a single
161
190
  machine-readable JSON document instead of formatted text. Discover the full
@@ -210,6 +239,10 @@ orizu logout
210
239
  <td>Locate the bundled skill (read-only)</td>
211
240
  <td><code>orizu skills path --json</code></td>
212
241
  </tr>
242
+ <tr>
243
+ <td>Update an npm-global CLI and its installed skills</td>
244
+ <td><code>orizu update</code></td>
245
+ </tr>
213
246
  <tr>
214
247
  <td>Inspect CLI capabilities as JSON</td>
215
248
  <td><code>orizu capabilities --json</code></td>
@@ -427,6 +460,25 @@ Task and dataset export support:
427
460
  - `json`
428
461
  - `jsonl`
429
462
 
463
+ Dataset `upload` and `push` create an immutable version after the rows are
464
+ acknowledged. With `--json`, upload prints one result document. A failed snapshot
465
+ reports the uploaded dataset identity so recovery can target the snapshot step.
466
+ If a write's response is lost or times out, inspect the dataset before retrying:
467
+ the last chunk may already have committed.
468
+
469
+ Dataset downloads retry transient read failures up to three attempts. Each
470
+ transfer request, including its response body, has a two-minute deadline;
471
+ `ORIZU_DATASET_TRANSFER_TIMEOUT_MS` accepts 1–300000 milliseconds. Downloads
472
+ receive the complete response before writing to the destination. Existing files
473
+ keep their inode, ownership, permissions and ACLs; writes follow existing
474
+ symlinks and update hardlinked readers. New files request mode `0600`, subject
475
+ to the filesystem's access rules. A failed network transfer leaves an existing output
476
+ untouched. The final write retains ordinary filesystem behavior: it is not
477
+ atomic, and a disk failure during that write can leave partial output.
478
+ SIGINT/SIGTERM cancels a download. Upload writes
479
+ have no additional transient retries; the existing session refresh behavior is
480
+ unchanged.
481
+
430
482
  ## Interactive And Automated Usage
431
483
 
432
484
  Many commands can prompt for missing team, project, app, dataset, or task selections in an interactive terminal. In scripts and CI, pass explicit flags instead:
@@ -0,0 +1,17 @@
1
+ export interface ParsedProjectSlug {
2
+ teamSlug: string;
3
+ projectSlug: string;
4
+ }
5
+ export interface SplitProjectArg {
6
+ /** null when the caller passed a bare project slug. */
7
+ teamSlug: string | null;
8
+ projectSlug: string;
9
+ }
10
+ export declare function normalizeSlugInput(slug: string): string;
11
+ /**
12
+ * Accept a `--project` value in either documented form: `teamSlug/projectSlug`
13
+ * or a bare `projectSlug` (resolved within the caller's team).
14
+ */
15
+ export declare function splitCliProjectArg(value: string): SplitProjectArg | null;
16
+ export declare function parseCliProjectSlug(projectSlug: string): ParsedProjectSlug | null;
17
+ //# sourceMappingURL=cli-project-slug.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-project-slug.d.ts","sourceRoot":"","sources":["../src/cli-project-slug.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,uDAAuD;IACvD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAQxE;AAED,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CAQjF"}
@@ -0,0 +1,24 @@
1
+ export function normalizeSlugInput(slug) {
2
+ return slug.trim().toLowerCase();
3
+ }
4
+ /**
5
+ * Accept a `--project` value in either documented form: `teamSlug/projectSlug`
6
+ * or a bare `projectSlug` (resolved within the caller's team).
7
+ */
8
+ export function splitCliProjectArg(value) {
9
+ if (!value.includes('/')) {
10
+ const projectSlug = normalizeSlugInput(value);
11
+ return projectSlug ? { teamSlug: null, projectSlug } : null;
12
+ }
13
+ const parsed = parseCliProjectSlug(value);
14
+ return parsed ? { teamSlug: parsed.teamSlug, projectSlug: parsed.projectSlug } : null;
15
+ }
16
+ export function parseCliProjectSlug(projectSlug) {
17
+ const segments = projectSlug.split('/');
18
+ if (segments.length !== 2)
19
+ return null;
20
+ const [teamSlug, project] = segments.map(normalizeSlugInput);
21
+ if (!teamSlug || !project)
22
+ return null;
23
+ return { teamSlug, projectSlug: project };
24
+ }
@@ -1,4 +1,5 @@
1
1
  import { ServerCredentials, StoredCredentialsV2, StoredCredentialsV3 } from './types.js';
2
+ export declare function getConfigDir(): string;
2
3
  /**
3
4
  * Runs a synchronous credentials transaction under the one process-wide and
4
5
  * cross-process lock. Exported so real-process fixtures can hold the production
@@ -18,6 +19,9 @@ export declare function updateServerCredentials(baseUrl: string, credentials: Se
18
19
  export declare function credentialsEqual(left: ServerCredentials, right: ServerCredentials): boolean;
19
20
  export declare function updateServerCredentialsIfCurrent(baseUrl: string, expected: ServerCredentials, replacement: ServerCredentials): boolean;
20
21
  export declare function getActiveBaseUrl(): string | null;
22
+ export declare function rememberProcessSecret(secret: string): string;
23
+ export declare function rememberResolvedBearer(bearer: string): string;
24
+ export declare function getSecretsSeenThisProcess(): readonly string[];
21
25
  /**
22
26
  * In-sandbox bearer resolution (ALI-1044). Resolves a bearer supplied out-of-band
23
27
  * to the hosted agent, WITHOUT consulting credentials.json:
@@ -1 +1 @@
1
- {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAiBA,OAAO,EACL,iBAAiB,EAEjB,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,YAAY,CAAA;AAqInB;;;;GAIG;AACH,wBAAgB,8BAA8B,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,CAgCvE;AAED;;;;GAIG;AACH,wBAAsB,mCAAmC,CAAC,CAAC,EACzD,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAC1B,OAAO,CAAC,CAAC,CAAC,CAiDZ;AAwGD,wBAAgB,qBAAqB,IAAI,mBAAmB,GAAG,mBAAmB,GAAG,IAAI,CA8BxF;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CAW9E;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,iBAAiB,QAepF;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,iBAAiB,QAatF;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,iBAAiB,EAAE,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAO3F;AAED,wBAAgB,gCAAgC,CAC9C,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,iBAAiB,EAC3B,WAAW,EAAE,iBAAiB,GAC7B,OAAO,CAUT;AAED,wBAAgB,gBAAgB,IAAI,MAAM,GAAG,IAAI,CAGhD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,GAAG,IAAI,CA0BrD;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAYlE;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAS1D;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,QAMtD;AAED,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,iBAAiB,GAC1B,OAAO,CAWT;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAc/D;AAED,wBAAgB,oBAAoB,SAKnC"}
1
+ {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAiBA,OAAO,EACL,iBAAiB,EAEjB,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,YAAY,CAAA;AAMnB,wBAAgB,YAAY,IAAI,MAAM,CAKrC;AA0HD;;;;GAIG;AACH,wBAAgB,8BAA8B,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,CAgCvE;AAED;;;;GAIG;AACH,wBAAsB,mCAAmC,CAAC,CAAC,EACzD,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAC1B,OAAO,CAAC,CAAC,CAAC,CAiDZ;AAwGD,wBAAgB,qBAAqB,IAAI,mBAAmB,GAAG,mBAAmB,GAAG,IAAI,CA8BxF;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CAW9E;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,iBAAiB,QAepF;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,iBAAiB,QAatF;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,iBAAiB,EAAE,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAO3F;AAED,wBAAgB,gCAAgC,CAC9C,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,iBAAiB,EAC3B,WAAW,EAAE,iBAAiB,GAC7B,OAAO,CAUT;AAED,wBAAgB,gBAAgB,IAAI,MAAM,GAAG,IAAI,CAGhD;AAID,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAG5D;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,wBAAgB,yBAAyB,IAAI,SAAS,MAAM,EAAE,CAE7D;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,GAAG,IAAI,CA0BrD;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAclE;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAS1D;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,QAMtD;AAED,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,iBAAiB,GAC1B,OAAO,CAWT;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAc/D;AAED,wBAAgB,oBAAoB,SAKnC"}
@@ -6,7 +6,7 @@ import { AsyncLocalStorage } from 'async_hooks';
6
6
  function isNodeError(error) {
7
7
  return error instanceof Error;
8
8
  }
9
- function getConfigDir() {
9
+ export function getConfigDir() {
10
10
  if (process.env.ORIZU_CONFIG_DIR) {
11
11
  return process.env.ORIZU_CONFIG_DIR;
12
12
  }
@@ -364,6 +364,17 @@ export function getActiveBaseUrl() {
364
364
  const config = loadCredentialsConfig();
365
365
  return config?.activeBaseUrl || null;
366
366
  }
367
+ const secretsSeenThisProcess = new Set();
368
+ export function rememberProcessSecret(secret) {
369
+ secretsSeenThisProcess.add(secret);
370
+ return secret;
371
+ }
372
+ export function rememberResolvedBearer(bearer) {
373
+ return rememberProcessSecret(bearer);
374
+ }
375
+ export function getSecretsSeenThisProcess() {
376
+ return [...secretsSeenThisProcess];
377
+ }
367
378
  /**
368
379
  * In-sandbox bearer resolution (ALI-1044). Resolves a bearer supplied out-of-band
369
380
  * to the hosted agent, WITHOUT consulting credentials.json:
@@ -385,7 +396,7 @@ export function getActiveBaseUrl() {
385
396
  export function resolveEnvBearerToken() {
386
397
  const explicit = process.env.ORIZU_TOKEN;
387
398
  if (explicit && explicit.trim()) {
388
- return explicit.trim();
399
+ return rememberResolvedBearer(explicit.trim());
389
400
  }
390
401
  const tokenFile = process.env.ORIZU_TOKEN_FILE;
391
402
  if (tokenFile && tokenFile.length > 0) {
@@ -404,7 +415,7 @@ export function resolveEnvBearerToken() {
404
415
  if (!token) {
405
416
  throw new Error(`ORIZU_TOKEN_FILE (${tokenFile}) is empty.`);
406
417
  }
407
- return token;
418
+ return rememberResolvedBearer(token);
408
419
  }
409
420
  return null;
410
421
  }
@@ -426,7 +437,7 @@ export function resolveAuthTokenForBaseUrl(baseUrl) {
426
437
  if (!credentials) {
427
438
  throw new Error(`Not logged in for ${baseUrl}. Run \`orizu login --server ${baseUrl}\` (or \`--local\`) first.`);
428
439
  }
429
- return 'accessToken' in credentials ? credentials.accessToken : credentials.apiKey;
440
+ return rememberResolvedBearer('accessToken' in credentials ? credentials.accessToken : credentials.apiKey);
430
441
  }
431
442
  /**
432
443
  * Non-throwing "is any auth available?" check (login --no-prompt-if-logged-in,
@@ -0,0 +1,2 @@
1
+ export declare function transferDatasetDownload(datasetId: string, format: string, filename: string): Promise<void>;
2
+ //# sourceMappingURL=dataset-transfer-download.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataset-transfer-download.d.ts","sourceRoot":"","sources":["../src/dataset-transfer-download.ts"],"names":[],"mappings":"AAmBA,wBAAsB,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyChH"}
@@ -0,0 +1,66 @@
1
+ import { Effect, Either, Schedule } from 'effect';
2
+ import { writeFileSync } from 'node:fs';
3
+ import { datasetTransferTimeoutMs } from './dataset-transfer-policy.js';
4
+ import { authedFetch } from './http.js';
5
+ class DownloadHttpError extends Error {
6
+ status;
7
+ _tag = 'DownloadHttpError';
8
+ constructor(status, detail) {
9
+ super(`Download failed (HTTP ${status}): ${detail}`);
10
+ this.status = status;
11
+ }
12
+ }
13
+ class DownloadTransportError extends Error {
14
+ _tag = 'DownloadTransportError';
15
+ constructor(cause) {
16
+ super('Dataset download transport failed', { cause });
17
+ }
18
+ }
19
+ export async function transferDatasetDownload(datasetId, format, filename) {
20
+ const timeoutMs = datasetTransferTimeoutMs();
21
+ const download = Effect.tryPromise({
22
+ try: async (signal) => {
23
+ const response = await authedFetch(`/api/cli/datasets/${encodeURIComponent(datasetId)}/download?format=${encodeURIComponent(format)}`, { signal });
24
+ if (!response.ok)
25
+ throw new DownloadHttpError(response.status, await response.text());
26
+ return new Uint8Array(await response.arrayBuffer());
27
+ },
28
+ catch: cause => {
29
+ if (cause instanceof TypeError)
30
+ return new DownloadTransportError(cause);
31
+ return cause instanceof Error ? cause : new Error('Dataset download request failed', { cause });
32
+ },
33
+ }).pipe(Effect.timeoutFail({
34
+ duration: timeoutMs,
35
+ onTimeout: () => new Error('DATASET_TRANSFER_TIMEOUT: Dataset download exceeded its deadline'),
36
+ }), Effect.retry({
37
+ times: 2,
38
+ schedule: Schedule.spaced(100),
39
+ while: error => error instanceof DownloadTransportError || (error instanceof DownloadHttpError && [429, 502, 503, 504].includes(error.status)),
40
+ }));
41
+ const fileError = (cause) => new Error(`DATASET_DOWNLOAD_FILE: Could not save dataset to ${filename}: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
42
+ const write = (bytes) => Effect.try({
43
+ // Wait for the complete response, then retain the CLI's existing in-place
44
+ // write semantics, including inode metadata, hardlinks and symlink targets.
45
+ try: () => writeFileSync(filename, bytes, { mode: 0o600 }),
46
+ catch: fileError,
47
+ });
48
+ const controller = new AbortController();
49
+ const handleInterrupt = () => controller.abort();
50
+ process.once('SIGINT', handleInterrupt);
51
+ process.once('SIGTERM', handleInterrupt);
52
+ try {
53
+ const result = await Effect.runPromise(Effect.either(Effect.flatMap(download, write)), { signal: controller.signal });
54
+ if (Either.isLeft(result))
55
+ throw result.left;
56
+ }
57
+ catch (error) {
58
+ if (controller.signal.aborted)
59
+ throw new Error('DATASET_TRANSFER_INTERRUPTED: Dataset download cancelled');
60
+ throw error;
61
+ }
62
+ finally {
63
+ process.removeListener('SIGINT', handleInterrupt);
64
+ process.removeListener('SIGTERM', handleInterrupt);
65
+ }
66
+ }
@@ -0,0 +1,2 @@
1
+ export declare function datasetTransferTimeoutMs(): number;
2
+ //# sourceMappingURL=dataset-transfer-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataset-transfer-policy.d.ts","sourceRoot":"","sources":["../src/dataset-transfer-policy.ts"],"names":[],"mappings":"AAAA,wBAAgB,wBAAwB,IAAI,MAAM,CAOjD"}
@@ -0,0 +1,8 @@
1
+ export function datasetTransferTimeoutMs() {
2
+ const configured = process.env.ORIZU_DATASET_TRANSFER_TIMEOUT_MS;
3
+ const timeoutMs = configured === undefined ? 120_000 : Number(configured);
4
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 300_000) {
5
+ throw new Error('DATASET_TRANSFER_CONFIG: ORIZU_DATASET_TRANSFER_TIMEOUT_MS must be an integer from 1 to 300000');
6
+ }
7
+ return timeoutMs;
8
+ }
@@ -0,0 +1,42 @@
1
+ export interface DatasetUploadResponse {
2
+ dataset: {
3
+ id: string;
4
+ name: string;
5
+ rowCount: number;
6
+ sourceType: string;
7
+ url?: string;
8
+ };
9
+ readmeVersion?: {
10
+ id: string;
11
+ version_num: number;
12
+ created_at: string;
13
+ } | null;
14
+ }
15
+ interface DatasetSnapshot {
16
+ datasetVersion: {
17
+ id: string;
18
+ };
19
+ }
20
+ type UploadRows = Array<Record<string, unknown>>;
21
+ type UploadSource = {
22
+ kind: 'jsonl';
23
+ file: string;
24
+ } | {
25
+ kind: 'rows';
26
+ rows: UploadRows;
27
+ sourceType: 'csv' | 'json' | 'jsonl';
28
+ };
29
+ export interface DatasetUploadOptions {
30
+ project: string;
31
+ name: string;
32
+ source: UploadSource;
33
+ readmeMarkdown: string | null;
34
+ json: boolean;
35
+ onChunk: (index: number, rowCount: number) => void;
36
+ }
37
+ export declare function transferDatasetUpload(options: DatasetUploadOptions): Promise<{
38
+ data: DatasetUploadResponse;
39
+ version: DatasetSnapshot;
40
+ }>;
41
+ export {};
42
+ //# sourceMappingURL=dataset-transfer-upload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataset-transfer-upload.d.ts","sourceRoot":"","sources":["../src/dataset-transfer-upload.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACzF,aAAa,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CAC/E;AAED,UAAU,eAAe;IAAG,cAAc,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE;AAE5D,KAAK,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;AAChD,KAAK,YAAY,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,CAAC;IAAC,UAAU,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,CAAA;CAAE,CAAA;AAE9H,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,YAAY,CAAA;IACpB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,IAAI,EAAE,OAAO,CAAA;IACb,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;CACnD;AAoHD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,qBAAqB,CAAC;IAAC,OAAO,EAAE,eAAe,CAAA;CAAE,CAAC,CAEvI"}
@@ -0,0 +1,110 @@
1
+ import { Context, Effect, Either, Layer } from 'effect';
2
+ import { ensureDatasetUploadSnapshot } from './dataset-upload-snapshot.js';
3
+ import { datasetTransferTimeoutMs } from './dataset-transfer-policy.js';
4
+ import { extractErrorMessage } from './error-response.js';
5
+ import { authedFetch } from './http.js';
6
+ import { parseJsonResponse, sanitizeTerminalText } from './json-response.js';
7
+ import { streamJsonlRowChunks } from './jsonl-stream.js';
8
+ class DatasetWriteFailure extends Error {
9
+ outcome;
10
+ _tag = 'DatasetWriteFailure';
11
+ constructor(message, outcome, options) {
12
+ super(message, options);
13
+ this.outcome = outcome;
14
+ }
15
+ }
16
+ class DatasetInputFailure extends Error {
17
+ _tag = 'DatasetInputFailure';
18
+ constructor(cause) { super(cause instanceof Error ? cause.message : String(cause), { cause }); }
19
+ }
20
+ const DatasetUploadApi = Context.GenericTag('orizu/DatasetUploadApi');
21
+ // Only the existing authentication client's rejected-token refresh may replay a
22
+ // request. No transient/transport retries belong around these non-idempotent writes.
23
+ function post(path, body, context, prefix, timeoutMs) {
24
+ return Effect.suspend(() => {
25
+ let requestStarted = false;
26
+ return Effect.tryPromise({
27
+ try: async (signal) => {
28
+ const response = await authedFetch(path, {
29
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
30
+ body: JSON.stringify(body), signal,
31
+ }, () => { requestStarted = true; });
32
+ if (!response.ok) {
33
+ throw new DatasetWriteFailure(`${prefix}: ${await extractErrorMessage(response)}`, response.status >= 500 ? 'unknown' : 'rejected');
34
+ }
35
+ return await parseJsonResponse(response, context);
36
+ },
37
+ catch: cause => cause instanceof DatasetWriteFailure ? cause : new DatasetWriteFailure(cause instanceof Error ? cause.message : String(cause), requestStarted ? 'unknown' : 'rejected', { cause }),
38
+ }).pipe(Effect.timeoutFail({
39
+ duration: timeoutMs,
40
+ onTimeout: () => new DatasetWriteFailure(`DATASET_TRANSFER_TIMEOUT: ${context} exceeded its deadline`, requestStarted ? 'unknown' : 'rejected'),
41
+ }));
42
+ });
43
+ }
44
+ function liveUploadApi(options) {
45
+ const timeoutMs = datasetTransferTimeoutMs();
46
+ return Layer.succeed(DatasetUploadApi, {
47
+ create: (rows, sourceType) => post('/api/cli/datasets/upload', {
48
+ projectSlug: options.project, name: options.name, rows, sourceType,
49
+ ...(options.readmeMarkdown !== null ? { readmeMarkdown: options.readmeMarkdown } : {}),
50
+ }, 'Dataset upload', 'Upload failed', timeoutMs),
51
+ append: (id, rows) => post(`/api/cli/datasets/${encodeURIComponent(id)}/rows`, { rows }, 'Dataset append', 'Append failed', timeoutMs),
52
+ snapshot: id => post(`/api/cli/datasets/${encodeURIComponent(id)}/versions`, {}, 'Dataset version create', 'Failed to create dataset version', timeoutMs),
53
+ });
54
+ }
55
+ async function expectedPromise(program) {
56
+ const result = await Effect.runPromise(Effect.either(program));
57
+ if (Either.isLeft(result))
58
+ throw result.left;
59
+ return result.right;
60
+ }
61
+ function uploadProgram(options) {
62
+ return Effect.gen(function* () {
63
+ const api = yield* DatasetUploadApi;
64
+ const chunks = yield* Effect.acquireRelease(Effect.sync(() => options.source.kind === 'jsonl'
65
+ ? streamJsonlRowChunks(options.source.file)
66
+ : (async function* () { if (options.source.kind === 'rows')
67
+ yield options.source.rows; })()), iterator => Effect.promise(async () => { await iterator.return(undefined); }));
68
+ let data;
69
+ let confirmedRows = 0;
70
+ let chunkIndex = 0;
71
+ while (true) {
72
+ const next = yield* Effect.tryPromise({ try: () => chunks.next(), catch: cause => new DatasetInputFailure(cause) }).pipe(Effect.mapError(error => new Error(data
73
+ ? `Upload stopped while reading the next JSONL chunk: ${error.message}\nDataset ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}) was created and ${confirmedRows} rows were uploaded. Fix the file, remove the first ${confirmedRows} rows, and run orizu datasets append --dataset ${data.dataset.id} --file <remaining-file>.`
74
+ : error.message)));
75
+ if (next.done)
76
+ break;
77
+ chunkIndex++;
78
+ if (options.source.kind === 'jsonl')
79
+ yield* Effect.sync(() => options.onChunk(chunkIndex, next.value.length));
80
+ const write = data
81
+ ? api.append(data.dataset.id, next.value).pipe(Effect.map(result => ({
82
+ data: { ...data, dataset: { ...data.dataset, rowCount: result.dataset.rowCount } },
83
+ added: result.appendedCount,
84
+ })))
85
+ : api.create(next.value, options.source.kind === 'jsonl' ? 'jsonl' : options.source.sourceType).pipe(Effect.map(result => ({ data: result, added: result.dataset.rowCount })));
86
+ const result = yield* write.pipe(Effect.mapError(error => {
87
+ if (error.outcome === 'unknown') {
88
+ return new Error(`DATASET_UPLOAD_UNCERTAIN: Chunk ${chunkIndex} may have committed, but its outcome could not be confirmed: ${error.message}\n` +
89
+ (data ? `Dataset ${sanitizeTerminalText(data.dataset.id)} has ${confirmedRows} confirmed uploaded rows. ` : 'Dataset creation could not be confirmed. ') +
90
+ 'Inspect the dataset and reconcile row IDs before retrying; do not replay this chunk blindly.');
91
+ }
92
+ return new Error(data
93
+ ? `Chunk ${chunkIndex} failed: ${error.message}\nDataset ${sanitizeTerminalText(data.dataset.name)} (${sanitizeTerminalText(data.dataset.id)}) was created and ${confirmedRows} rows were uploaded. To retry, remove the first ${confirmedRows} rows from your file and run orizu datasets append --dataset ${data.dataset.id} --file <remaining-file>.`
94
+ : error.message);
95
+ }));
96
+ data = result.data;
97
+ confirmedRows += result.added;
98
+ }
99
+ if (!data)
100
+ return yield* Effect.fail(new Error('Dataset file contains no rows'));
101
+ const version = yield* Effect.tryPromise({
102
+ try: () => ensureDatasetUploadSnapshot(data.dataset, options.json, id => expectedPromise(api.snapshot(id))),
103
+ catch: cause => cause instanceof Error ? cause : new Error(String(cause)),
104
+ });
105
+ return { data, version };
106
+ });
107
+ }
108
+ export function transferDatasetUpload(options) {
109
+ return expectedPromise(uploadProgram(options).pipe(Effect.scoped, Effect.provide(liveUploadApi(options))));
110
+ }
@@ -0,0 +1,14 @@
1
+ export interface FeedbackCliIo {
2
+ json: boolean;
3
+ print: (message: string) => void;
4
+ printErr: (message: string) => void;
5
+ fetcher?: (path: string, init?: RequestInit) => Promise<Response>;
6
+ configDir?: string;
7
+ cwd?: string;
8
+ now?: () => number;
9
+ homeDir?: string;
10
+ hasAuth?: (baseUrl: string) => boolean;
11
+ }
12
+ export declare function resolveFeedbackTimeoutMs(baseUrl: string): number;
13
+ export declare function feedbackCommand(args: string[], io: FeedbackCliIo): Promise<number>;
14
+ //# sourceMappingURL=feedback-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feedback-cli.d.ts","sourceRoot":"","sources":["../src/feedback-cli.ts"],"names":[],"mappings":"AAyDA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IACjE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAA;CACvC;AAwaD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAWhE;AAiCD,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAmOxF"}