primcli 1.20.0 → 1.21.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.
Files changed (2) hide show
  1. package/dist/oclif/index.js +522 -12
  2. package/package.json +1 -1
@@ -1,12 +1,13 @@
1
1
  import { A as saveActiveChatState, C as resolveCliAuth, D as deleteChatState, E as chatStatePath, M as createClient, N as createConfig, O as loadActiveChatState, S as normalizeApiBaseUrl, T as saveSignupCredentials, _ as deleteCliCredentials, a as normalizeCliEnvironmentName, b as hasStoredCliLogin, c as resolveConfigEnvironment, d as validateCliHeaderName, f as validateCliHeaderValue, g as credentialsPath, h as credentialsLockPath, i as loadCliConfig, j as PrimitiveApiClient, k as loadChatConversationByLocalId, l as saveCliConfig, m as cliAccessTokenExpiresAt, n as deleteCliConfig, o as redactCliEnvironment, p as acquireCliCredentialsLock, r as emptyCliConfig, s as removeCliEnvironment, u as upsertCliEnvironment, v as deleteCliCredentialsLock, w as saveCliCredentials, x as loadCliCredentials, y as detectPrimitiveKeyEnvMisname } from "../cli-config-BKso8oJu.js";
2
2
  import { Args, Command, Errors, Flags, ux } from "@oclif/core";
3
- import { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
3
+ import { chmodSync, closeSync, createWriteStream, existsSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
4
4
  import { randomUUID } from "node:crypto";
5
5
  import path, { basename, dirname, join, relative, resolve, sep } from "node:path";
6
6
  import process$1 from "node:process";
7
7
  import { createInterface } from "node:readline/promises";
8
8
  import { hostname } from "node:os";
9
9
  import { spawn } from "node:child_process";
10
+ import { open, rm, stat } from "node:fs/promises";
10
11
  import { gunzipSync } from "node:zlib";
11
12
  import { concat, getAddress, hexToBytes, keccak256, stringToBytes } from "viem";
12
13
  import { privateKeyToAccount } from "viem/accounts";
@@ -9241,6 +9242,40 @@ const openapiDocument = {
9241
9242
  },
9242
9243
  "required": ["filename", "content_base64"]
9243
9244
  },
9245
+ "SendMailPayloadRef": {
9246
+ "type": "object",
9247
+ "additionalProperties": false,
9248
+ "description": "A reference to an already-uploaded Primitive Payloads object, delivered as an attachment without inlining the bytes — the way to send an attachment larger than the inline cap. Upload the object via /v1/payloads (with a client-held CEK the server never sees), then reference it here.",
9249
+ "properties": {
9250
+ "root": {
9251
+ "type": "string",
9252
+ "pattern": "^[0-9a-f]{64}$",
9253
+ "description": "The 64-char lowercase-hex Merkle root of a finalized payloads object."
9254
+ },
9255
+ "filename": {
9256
+ "type": "string",
9257
+ "minLength": 1,
9258
+ "maxLength": 255,
9259
+ "description": "Attachment filename presented to the recipient."
9260
+ },
9261
+ "content_type": {
9262
+ "type": "string",
9263
+ "minLength": 1,
9264
+ "maxLength": 255,
9265
+ "description": "Optional MIME content type."
9266
+ },
9267
+ "cek": {
9268
+ "type": "string",
9269
+ "pattern": "^[A-Za-z0-9_-]{1,128}$",
9270
+ "description": "Base64url-encoded (unpadded) content-encryption key the recipient uses to decrypt. Travels with the email; the object store only ever holds ciphertext."
9271
+ }
9272
+ },
9273
+ "required": [
9274
+ "root",
9275
+ "filename",
9276
+ "cek"
9277
+ ]
9278
+ },
9244
9279
  "SendMailInput": {
9245
9280
  "type": "object",
9246
9281
  "additionalProperties": false,
@@ -9295,6 +9330,12 @@ const openapiDocument = {
9295
9330
  "description": "Inline attachments. Send requests with attachments to https://api.primitive.dev/v1/send-mail. Combined raw decoded attachment bytes must be at most 31457280.",
9296
9331
  "items": { "$ref": "#/components/schemas/SendMailAttachment" }
9297
9332
  },
9333
+ "payload_attachments": {
9334
+ "type": "array",
9335
+ "maxItems": 1,
9336
+ "description": "Deliver an already-uploaded Primitive Payloads object as an attachment by reference, without inlining the bytes — the way to send attachments larger than the inline cap. Upload the object via /v1/payloads (client-held CEK), then reference it here. v1 supports at most one.",
9337
+ "items": { "$ref": "#/components/schemas/SendMailPayloadRef" }
9338
+ },
9298
9339
  "wait": {
9299
9340
  "type": "boolean",
9300
9341
  "description": "When true, wait for the first downstream SMTP delivery outcome before returning."
@@ -21832,6 +21873,45 @@ const operationManifest = [
21832
21873
  "required": ["filename", "content_base64"]
21833
21874
  }
21834
21875
  },
21876
+ "payload_attachments": {
21877
+ "type": "array",
21878
+ "maxItems": 1,
21879
+ "description": "Deliver an already-uploaded Primitive Payloads object as an attachment by reference, without inlining the bytes — the way to send attachments larger than the inline cap. Upload the object via /v1/payloads (client-held CEK), then reference it here. v1 supports at most one.",
21880
+ "items": {
21881
+ "type": "object",
21882
+ "additionalProperties": false,
21883
+ "description": "A reference to an already-uploaded Primitive Payloads object, delivered as an attachment without inlining the bytes — the way to send an attachment larger than the inline cap. Upload the object via /v1/payloads (with a client-held CEK the server never sees), then reference it here.",
21884
+ "properties": {
21885
+ "root": {
21886
+ "type": "string",
21887
+ "pattern": "^[0-9a-f]{64}$",
21888
+ "description": "The 64-char lowercase-hex Merkle root of a finalized payloads object."
21889
+ },
21890
+ "filename": {
21891
+ "type": "string",
21892
+ "minLength": 1,
21893
+ "maxLength": 255,
21894
+ "description": "Attachment filename presented to the recipient."
21895
+ },
21896
+ "content_type": {
21897
+ "type": "string",
21898
+ "minLength": 1,
21899
+ "maxLength": 255,
21900
+ "description": "Optional MIME content type."
21901
+ },
21902
+ "cek": {
21903
+ "type": "string",
21904
+ "pattern": "^[A-Za-z0-9_-]{1,128}$",
21905
+ "description": "Base64url-encoded (unpadded) content-encryption key the recipient uses to decrypt. Travels with the email; the object store only ever holds ciphertext."
21906
+ }
21907
+ },
21908
+ "required": [
21909
+ "root",
21910
+ "filename",
21911
+ "cek"
21912
+ ]
21913
+ }
21914
+ },
21835
21915
  "wait": {
21836
21916
  "type": "boolean",
21837
21917
  "description": "When true, wait for the first downstream SMTP delivery outcome before returning."
@@ -24841,7 +24921,7 @@ async function fetchEmailSearchPage(params) {
24841
24921
  rows
24842
24922
  };
24843
24923
  }
24844
- function sleep$1(ms) {
24924
+ function sleep$2(ms) {
24845
24925
  return new Promise((resolve) => setTimeout(resolve, ms));
24846
24926
  }
24847
24927
  function cliError$6(message) {
@@ -25874,7 +25954,7 @@ async function waitForReply(params) {
25874
25954
  if (lastAccepted !== void 0) continue;
25875
25955
  if (phase.deadline !== null && Date.now() >= phase.deadline) break;
25876
25956
  if (totalDeadline !== null && Date.now() >= totalDeadline) return null;
25877
- await sleep$1(params.interval * 1e3);
25957
+ await sleep$2(params.interval * 1e3);
25878
25958
  }
25879
25959
  }
25880
25960
  return null;
@@ -26656,7 +26736,7 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
26656
26736
  }
26657
26737
  if (cursorAdvanced) continue;
26658
26738
  if (deadline !== null && Date.now() >= deadline) break;
26659
- await sleep$1(flags.interval * 1e3);
26739
+ await sleep$2(flags.interval * 1e3);
26660
26740
  }
26661
26741
  process.stderr.write(`Timed out waiting for ${flags.number} matching email${flags.number === 1 ? "" : "s"}; received ${matched}.\n`);
26662
26742
  process.exitCode = 1;
@@ -26775,7 +26855,7 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
26775
26855
  }
26776
26856
  if (cursorAdvanced) continue;
26777
26857
  if (deadline !== null && Date.now() >= deadline) break;
26778
- await sleep$1(flags.interval * 1e3);
26858
+ await sleep$2(flags.interval * 1e3);
26779
26859
  }
26780
26860
  }
26781
26861
  };
@@ -27936,8 +28016,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
27936
28016
  name: "Primitive Team",
27937
28017
  url: "https://primitive.dev"
27938
28018
  };
27939
- const SDK_VERSION_RANGE = "^1.20.0";
27940
- const CLI_VERSION_RANGE = "^1.20.0";
28019
+ const SDK_VERSION_RANGE = "^1.21.0";
28020
+ const CLI_VERSION_RANGE = "^1.21.0";
27941
28021
  const ESBUILD_VERSION_RANGE = "^0.27.0";
27942
28022
  function renderHandler() {
27943
28023
  return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
@@ -28672,7 +28752,7 @@ var FunctionsLogsCommand = class FunctionsLogsCommand extends Command {
28672
28752
  return;
28673
28753
  }
28674
28754
  completedInitialFollowPoll = true;
28675
- await sleep$1(flags["poll-interval"] * 1e3);
28755
+ await sleep$2(flags["poll-interval"] * 1e3);
28676
28756
  }
28677
28757
  });
28678
28758
  }
@@ -29583,7 +29663,7 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
29583
29663
  trace = fetched;
29584
29664
  break;
29585
29665
  }
29586
- await sleep$1(pollIntervalMs);
29666
+ await sleep$2(pollIntervalMs);
29587
29667
  }
29588
29668
  if (!trace) {
29589
29669
  const finalTrace = ((await getFunctionTestRunTrace({
@@ -29962,7 +30042,7 @@ const MAX_CLI_LOGIN_POLL_INTERVAL_SECONDS = 60;
29962
30042
  function cliError$3(message) {
29963
30043
  return new Errors.CLIError(message, { exit: 1 });
29964
30044
  }
29965
- function sleep(ms) {
30045
+ function sleep$1(ms) {
29966
30046
  return new Promise((resolve) => setTimeout(resolve, ms));
29967
30047
  }
29968
30048
  function openBrowser(url) {
@@ -30127,7 +30207,7 @@ var LoginCommand$1 = class extends Command {
30127
30207
  let interval = Math.min(Math.max(1, start.interval), MAX_CLI_LOGIN_POLL_INTERVAL_SECONDS);
30128
30208
  let nextPollDelay = 1;
30129
30209
  while (Date.now() < deadline) {
30130
- await sleep(nextPollDelay * 1e3);
30210
+ await sleep$1(nextPollDelay * 1e3);
30131
30211
  nextPollDelay = interval;
30132
30212
  const polled = await pollCliLogin({
30133
30213
  body: { device_code: start.device_code },
@@ -31757,6 +31837,434 @@ var OrgSecretsSetCommand = class OrgSecretsSetCommand extends Command {
31757
31837
  }
31758
31838
  };
31759
31839
  //#endregion
31840
+ //#region ../sdk-node/dist/payloads/index.js
31841
+ /**
31842
+ * Primitive Payloads — streaming client for large, content-addressed,
31843
+ * end-to-end-encrypted objects (attachments up to ~1 TB).
31844
+ *
31845
+ * Objects are split into fixed 64 MiB chunks; each chunk is encrypted with a
31846
+ * per-chunk AES-256-GCM key (HKDF-derived from the object CEK), content-addressed
31847
+ * by its ciphertext SHA-256, and committed under a Merkle root. This module
31848
+ * streams: it processes one chunk at a time and never holds the whole object in
31849
+ * memory, so a multi-GB file uploads/downloads in bounded memory.
31850
+ *
31851
+ * The chunk/manifest construction is byte-compatible with the server's object
31852
+ * model (packages/payloads-core in the API monorepo).
31853
+ */
31854
+ const CHUNK_SIZE = 64 * 1024 * 1024;
31855
+ const MANIFEST_VERSION = 1;
31856
+ const CHUNK_KDF_INFO = "payloads-chunk";
31857
+ const OBJECT_ID_BYTES = 16;
31858
+ const CEK_BYTES = 32;
31859
+ const subtle = globalThis.crypto.subtle;
31860
+ const textEncoder = new TextEncoder();
31861
+ function randomBytes(length) {
31862
+ const out = new Uint8Array(length);
31863
+ globalThis.crypto.getRandomValues(out);
31864
+ return out;
31865
+ }
31866
+ function toHex(bytes) {
31867
+ let out = "";
31868
+ for (const b of bytes) out += b.toString(16).padStart(2, "0");
31869
+ return out;
31870
+ }
31871
+ function fromHex(hex) {
31872
+ if (hex.length % 2 !== 0) throw new Error("invalid hex: odd length");
31873
+ if (hex.length > 0 && !/^[0-9a-fA-F]+$/.test(hex)) throw new Error("invalid hex: non-hex character");
31874
+ const out = new Uint8Array(hex.length / 2);
31875
+ for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
31876
+ return out;
31877
+ }
31878
+ async function contentHash(bytes) {
31879
+ return new Uint8Array(await subtle.digest("SHA-256", bytes));
31880
+ }
31881
+ async function contentHashHex(bytes) {
31882
+ return toHex(await contentHash(bytes));
31883
+ }
31884
+ /** Binary Merkle root over ordered hex leaf hashes (odd node promoted). */
31885
+ async function merkleRoot(leafHashesHex) {
31886
+ if (leafHashesHex.length === 0) return contentHashHex(new Uint8Array(0));
31887
+ let level = leafHashesHex.map(fromHex);
31888
+ while (level.length > 1) {
31889
+ const next = [];
31890
+ for (let i = 0; i < level.length; i += 2) {
31891
+ const left = level[i];
31892
+ const right = i + 1 < level.length ? level[i + 1] : level[i];
31893
+ const combined = new Uint8Array(left.length + right.length);
31894
+ combined.set(left, 0);
31895
+ combined.set(right, left.length);
31896
+ next.push(await contentHash(combined));
31897
+ }
31898
+ level = next;
31899
+ }
31900
+ return toHex(level[0]);
31901
+ }
31902
+ async function deriveChunkKey(cek, index) {
31903
+ const baseKey = await subtle.importKey("raw", cek, "HKDF", false, ["deriveKey"]);
31904
+ const info = textEncoder.encode(`${CHUNK_KDF_INFO}:${index}`);
31905
+ return subtle.deriveKey({
31906
+ name: "HKDF",
31907
+ hash: "SHA-256",
31908
+ salt: new Uint8Array(32),
31909
+ info
31910
+ }, baseKey, {
31911
+ name: "AES-GCM",
31912
+ length: 256
31913
+ }, false, ["encrypt", "decrypt"]);
31914
+ }
31915
+ function chunkNonce(index) {
31916
+ const nonce = new Uint8Array(12);
31917
+ new DataView(nonce.buffer).setUint32(8, index >>> 0, false);
31918
+ return nonce;
31919
+ }
31920
+ function chunkAad(objectId, index) {
31921
+ const aad = new Uint8Array(objectId.length + 4);
31922
+ aad.set(objectId, 0);
31923
+ new DataView(aad.buffer).setUint32(objectId.length, index >>> 0, false);
31924
+ return aad;
31925
+ }
31926
+ async function encryptChunk(cek, objectId, index, plaintext) {
31927
+ const key = await deriveChunkKey(cek, index);
31928
+ const ct = await subtle.encrypt({
31929
+ name: "AES-GCM",
31930
+ iv: chunkNonce(index),
31931
+ additionalData: chunkAad(objectId, index)
31932
+ }, key, plaintext);
31933
+ return new Uint8Array(ct);
31934
+ }
31935
+ async function decryptChunk(cek, objectId, index, ciphertext) {
31936
+ const key = await deriveChunkKey(cek, index);
31937
+ const pt = await subtle.decrypt({
31938
+ name: "AES-GCM",
31939
+ iv: chunkNonce(index),
31940
+ additionalData: chunkAad(objectId, index)
31941
+ }, key, ciphertext);
31942
+ return new Uint8Array(pt);
31943
+ }
31944
+ function authHeaders(apiKey) {
31945
+ return { authorization: `Bearer ${apiKey}` };
31946
+ }
31947
+ function apiRoot(baseUrl) {
31948
+ let end = baseUrl.length;
31949
+ while (end > 0 && baseUrl.charCodeAt(end - 1) === 47) end--;
31950
+ return `${baseUrl.slice(0, end)}/v1/payloads`;
31951
+ }
31952
+ const DEFAULT_MAX_RETRIES = 6;
31953
+ const RETRYABLE_STATUS = new Set([
31954
+ 429,
31955
+ 500,
31956
+ 502,
31957
+ 503,
31958
+ 504
31959
+ ]);
31960
+ function sleep(ms) {
31961
+ return new Promise((resolve) => setTimeout(resolve, ms));
31962
+ }
31963
+ /**
31964
+ * fetch with exponential backoff on transient failures. Request bodies here are
31965
+ * in-memory buffers/strings (never streams), so a retry can safely resend them.
31966
+ */
31967
+ async function retryingFetch(url, init, label, maxRetries = DEFAULT_MAX_RETRIES) {
31968
+ for (let attempt = 0;; attempt++) {
31969
+ let res;
31970
+ let networkError;
31971
+ try {
31972
+ res = await fetch(url, init);
31973
+ } catch (err) {
31974
+ networkError = err;
31975
+ }
31976
+ if (res && !RETRYABLE_STATUS.has(res.status)) return res;
31977
+ if (attempt >= maxRetries) {
31978
+ if (res) return res;
31979
+ throw new Error(`${label}: network error after ${attempt} retries: ${networkError?.message ?? networkError}`);
31980
+ }
31981
+ if (res) await res.text().catch(() => {});
31982
+ await sleep(Math.min(1e3 * 2 ** attempt, 15e3) + Math.floor(Math.random() * 250));
31983
+ }
31984
+ }
31985
+ async function initiate(opts, manifest) {
31986
+ const res = await retryingFetch(apiRoot(opts.baseUrl), {
31987
+ method: "POST",
31988
+ headers: {
31989
+ ...authHeaders(opts.apiKey),
31990
+ "content-type": "application/json"
31991
+ },
31992
+ body: JSON.stringify({ manifest })
31993
+ }, "initiate");
31994
+ if (!res.ok) throw new Error(`initiate failed: HTTP ${res.status} ${await res.text()}`);
31995
+ await res.text();
31996
+ }
31997
+ async function putChunk(opts, root, d, bytes) {
31998
+ const res = await retryingFetch(`${apiRoot(opts.baseUrl)}/${root}/chunks/${d.ciphertextHash}`, {
31999
+ method: "PUT",
32000
+ headers: {
32001
+ ...authHeaders(opts.apiKey),
32002
+ "content-type": "application/octet-stream"
32003
+ },
32004
+ body: bytes
32005
+ }, `chunk ${d.index} upload`);
32006
+ if (!res.ok) throw new Error(`chunk ${d.index} upload failed: HTTP ${res.status} ${await res.text()}`);
32007
+ await res.text();
32008
+ }
32009
+ async function finalize(opts, root) {
32010
+ const res = await retryingFetch(`${apiRoot(opts.baseUrl)}/${root}/finalize`, {
32011
+ method: "POST",
32012
+ headers: authHeaders(opts.apiKey)
32013
+ }, "finalize");
32014
+ if (!res.ok) throw new Error(`finalize failed: HTTP ${res.status} ${await res.text()}`);
32015
+ await res.text();
32016
+ }
32017
+ async function fetchManifest(opts, root) {
32018
+ const res = await retryingFetch(`${apiRoot(opts.baseUrl)}/${root}/manifest`, { headers: authHeaders(opts.apiKey) }, "get manifest");
32019
+ if (!res.ok) throw new Error(`get manifest failed: HTTP ${res.status} ${await res.text()}`);
32020
+ return (await res.json()).data.manifest;
32021
+ }
32022
+ async function getChunkBytes(opts, root, d) {
32023
+ const res = await retryingFetch(`${apiRoot(opts.baseUrl)}/${root}/chunks/${d.ciphertextHash}`, { headers: authHeaders(opts.apiKey) }, `get chunk ${d.index}`);
32024
+ if (!res.ok) throw new Error(`get chunk ${d.index} failed: HTTP ${res.status}`);
32025
+ return new Uint8Array(await res.arrayBuffer());
32026
+ }
32027
+ async function readWindow(fh, position, length) {
32028
+ const buf = Buffer.allocUnsafe(length);
32029
+ let read = 0;
32030
+ while (read < length) {
32031
+ const { bytesRead } = await fh.read(buf, read, length - read, position + read);
32032
+ if (bytesRead === 0) break;
32033
+ read += bytesRead;
32034
+ }
32035
+ return read === length ? buf : buf.subarray(0, read);
32036
+ }
32037
+ async function mapLimit(items, limit, fn) {
32038
+ let next = 0;
32039
+ let firstError;
32040
+ const worker = async () => {
32041
+ while (next < items.length && firstError === void 0) {
32042
+ const i = next++;
32043
+ try {
32044
+ await fn(items[i], i);
32045
+ } catch (err) {
32046
+ if (firstError === void 0) firstError = err;
32047
+ return;
32048
+ }
32049
+ }
32050
+ };
32051
+ await Promise.all(Array.from({ length: Math.min(limit, items.length || 1) }, () => worker()));
32052
+ if (firstError !== void 0) throw firstError;
32053
+ }
32054
+ /**
32055
+ * Stream a file up as a Primitive Payload. Two passes over the file (encrypt to
32056
+ * build the manifest, then encrypt again to upload) keep memory bounded to a few
32057
+ * chunks — the whole object is never resident. Returns the content address
32058
+ * (merkleRoot) and the hex CEK needed to download it.
32059
+ */
32060
+ async function pushFile(filePath, opts) {
32061
+ const chunkSize = opts.chunkSize ?? CHUNK_SIZE;
32062
+ const concurrency = opts.concurrency ?? 3;
32063
+ const { size } = await stat(filePath);
32064
+ const cek = randomBytes(CEK_BYTES);
32065
+ const objectId = randomBytes(OBJECT_ID_BYTES);
32066
+ const chunkCount = size === 0 ? 0 : Math.ceil(size / chunkSize);
32067
+ const fh = await open(filePath, "r");
32068
+ try {
32069
+ const descriptors = [];
32070
+ for (let index = 0; index < chunkCount; index++) {
32071
+ const position = index * chunkSize;
32072
+ const length = Math.min(chunkSize, size - position);
32073
+ const plaintext = await readWindow(fh, position, length);
32074
+ const ciphertext = await encryptChunk(cek, objectId, index, plaintext);
32075
+ descriptors.push({
32076
+ index,
32077
+ ciphertextHash: await contentHashHex(ciphertext),
32078
+ plaintextSize: length,
32079
+ ciphertextSize: ciphertext.length
32080
+ });
32081
+ opts.onProgress?.("encrypt", index + 1, chunkCount);
32082
+ }
32083
+ const root = await merkleRoot(descriptors.map((d) => d.ciphertextHash));
32084
+ await initiate(opts, {
32085
+ version: MANIFEST_VERSION,
32086
+ objectId: toHex(objectId),
32087
+ chunkSize,
32088
+ totalPlaintextSize: size,
32089
+ chunkCount,
32090
+ chunks: descriptors,
32091
+ merkleRoot: root
32092
+ });
32093
+ let uploaded = 0;
32094
+ await mapLimit(descriptors, concurrency, async (d) => {
32095
+ const plaintext = await readWindow(fh, d.index * chunkSize, d.plaintextSize);
32096
+ await putChunk(opts, root, d, await encryptChunk(cek, objectId, d.index, plaintext));
32097
+ opts.onProgress?.("upload", ++uploaded, chunkCount);
32098
+ });
32099
+ await finalize(opts, root);
32100
+ return {
32101
+ merkleRoot: root,
32102
+ cek: toHex(cek),
32103
+ chunkCount,
32104
+ totalBytes: size
32105
+ };
32106
+ } finally {
32107
+ await fh.close();
32108
+ }
32109
+ }
32110
+ /**
32111
+ * Stream a Primitive Payload down to a file, verifying and decrypting one chunk
32112
+ * at a time (bounded memory). Every chunk is content-address-checked before
32113
+ * decryption, so a corrupt or substituted chunk throws.
32114
+ */
32115
+ async function pullFile(root, outPath, opts) {
32116
+ const manifest = await fetchManifest(opts, root);
32117
+ const computedRoot = await merkleRoot(manifest.chunks.map((c) => c.ciphertextHash));
32118
+ if (computedRoot !== root) throw new Error(`manifest does not match the requested content address (got ${computedRoot})`);
32119
+ const cek = fromHex(opts.cek);
32120
+ const objectId = fromHex(manifest.objectId);
32121
+ const out = createWriteStream(outPath);
32122
+ let onWriteError = () => {};
32123
+ const writeErrored = new Promise((_, reject) => {
32124
+ onWriteError = reject;
32125
+ });
32126
+ writeErrored.catch(() => {});
32127
+ out.on("error", (err) => onWriteError(err));
32128
+ try {
32129
+ let done = 0;
32130
+ for (const d of manifest.chunks) {
32131
+ const ciphertext = await getChunkBytes(opts, root, d);
32132
+ if (await contentHashHex(ciphertext) !== d.ciphertextHash) throw new Error(`chunk ${d.index} failed integrity check`);
32133
+ const plaintext = await decryptChunk(cek, objectId, d.index, ciphertext);
32134
+ if (!out.write(plaintext)) await Promise.race([new Promise((resolve) => out.once("drain", resolve)), writeErrored]);
32135
+ opts.onProgress?.("download", ++done, manifest.chunkCount);
32136
+ }
32137
+ await Promise.race([new Promise((resolve) => out.end(resolve)), writeErrored]);
32138
+ } catch (err) {
32139
+ out.destroy();
32140
+ await rm(outPath, { force: true });
32141
+ throw err;
32142
+ }
32143
+ return manifest;
32144
+ }
32145
+ //#endregion
32146
+ //#region src/oclif/commands/payloads.ts
32147
+ const DEFAULT_API_BASE_URL = "https://api.primitive.dev";
32148
+ const authFlags = {
32149
+ "api-key": Flags.string({
32150
+ description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved login credentials)",
32151
+ env: "PRIMITIVE_API_KEY"
32152
+ }),
32153
+ "api-base-url": Flags.string({
32154
+ description: "Override the API base URL. Internal testing only.",
32155
+ env: "PRIMITIVE_API_BASE_URL",
32156
+ hidden: true
32157
+ })
32158
+ };
32159
+ function resolveClient(configDir, flags) {
32160
+ const auth = resolveCliAuth({
32161
+ configDir,
32162
+ apiKey: flags["api-key"],
32163
+ apiBaseUrl: flags["api-base-url"]
32164
+ });
32165
+ if (!auth.apiKey) throw new Error("Not authenticated: set PRIMITIVE_API_KEY, pass --api-key, or run `primitive login`.");
32166
+ return {
32167
+ baseUrl: auth.apiBaseUrl ?? DEFAULT_API_BASE_URL,
32168
+ apiKey: auth.apiKey
32169
+ };
32170
+ }
32171
+ function progressToStderr() {
32172
+ let lastPct = -1;
32173
+ return (phase, done, total) => {
32174
+ const pct = total === 0 ? 100 : Math.floor(done / total * 100);
32175
+ if (pct !== lastPct) {
32176
+ lastPct = pct;
32177
+ process.stderr.write(`\r${phase}: ${done}/${total} (${pct}%) `);
32178
+ if (done === total) process.stderr.write("\n");
32179
+ }
32180
+ };
32181
+ }
32182
+ var PayloadsPushCommand = class PayloadsPushCommand extends Command {
32183
+ static description = `Upload a file as a Primitive Payload — a large, content-addressed, end-to-end-encrypted object.
32184
+
32185
+ The file is chunked and encrypted client-side and streamed up in bounded
32186
+ memory (multi-GB files never load fully into RAM). Prints the object's content
32187
+ address (merkle_root) and the hex CEK required to download it — keep the CEK
32188
+ secret; without it the object cannot be decrypted.`;
32189
+ static summary = "Stream-upload a file as an encrypted payload";
32190
+ static examples = ["<%= config.bin %> payloads push ./big-video.mp4"];
32191
+ static args = { file: Args.string({
32192
+ required: true,
32193
+ description: "Path to the file to upload"
32194
+ }) };
32195
+ static flags = {
32196
+ ...authFlags,
32197
+ concurrency: Flags.integer({
32198
+ description: "Parallel chunk uploads",
32199
+ default: 3
32200
+ }),
32201
+ quiet: Flags.boolean({
32202
+ description: "Suppress progress output",
32203
+ default: false
32204
+ })
32205
+ };
32206
+ async run() {
32207
+ const { args, flags } = await this.parse(PayloadsPushCommand);
32208
+ const { baseUrl, apiKey } = resolveClient(this.config.configDir, flags);
32209
+ const result = await pushFile(args.file, {
32210
+ baseUrl,
32211
+ apiKey,
32212
+ concurrency: flags.concurrency,
32213
+ onProgress: flags.quiet ? void 0 : progressToStderr()
32214
+ });
32215
+ this.log(JSON.stringify({
32216
+ merkle_root: result.merkleRoot,
32217
+ cek: result.cek,
32218
+ chunk_count: result.chunkCount,
32219
+ total_bytes: result.totalBytes
32220
+ }, null, 2));
32221
+ }
32222
+ };
32223
+ var PayloadsPullCommand = class PayloadsPullCommand extends Command {
32224
+ static description = `Download and decrypt a Primitive Payload to a file.
32225
+
32226
+ Streams one chunk at a time, verifying each against its content address before
32227
+ decryption, so a corrupt or substituted chunk fails loudly. Requires the hex
32228
+ CEK printed by \`payloads push\`.`;
32229
+ static summary = "Stream-download and decrypt a payload to a file";
32230
+ static examples = ["<%= config.bin %> payloads pull <merkle_root> --cek <hex> --out ./restored.mp4"];
32231
+ static args = { root: Args.string({
32232
+ required: true,
32233
+ description: "Object content address (merkle_root)"
32234
+ }) };
32235
+ static flags = {
32236
+ ...authFlags,
32237
+ out: Flags.string({
32238
+ required: true,
32239
+ description: "Output file path"
32240
+ }),
32241
+ cek: Flags.string({
32242
+ required: true,
32243
+ description: "Hex content-encryption key from `payloads push`"
32244
+ }),
32245
+ quiet: Flags.boolean({
32246
+ description: "Suppress progress output",
32247
+ default: false
32248
+ })
32249
+ };
32250
+ async run() {
32251
+ const { args, flags } = await this.parse(PayloadsPullCommand);
32252
+ const { baseUrl, apiKey } = resolveClient(this.config.configDir, flags);
32253
+ const manifest = await pullFile(args.root, flags.out, {
32254
+ baseUrl,
32255
+ apiKey,
32256
+ cek: flags.cek,
32257
+ onProgress: flags.quiet ? void 0 : progressToStderr()
32258
+ });
32259
+ this.log(JSON.stringify({
32260
+ merkle_root: args.root,
32261
+ out: flags.out,
32262
+ chunk_count: manifest.chunkCount,
32263
+ total_bytes: manifest.totalPlaintextSize
32264
+ }, null, 2));
32265
+ }
32266
+ };
32267
+ //#endregion
31760
32268
  //#region ../sdk-node/dist/x402/index.js
31761
32269
  /**
31762
32270
  * x402 client-side signing.
@@ -33186,7 +33694,7 @@ async function pollForSettlementInteraction(params) {
33186
33694
  }
33187
33695
  if (receipt) return receipt;
33188
33696
  if (deadline !== null && Date.now() >= deadline) break;
33189
- await sleep$1(params.intervalSeconds * 1e3);
33697
+ await sleep$2(params.intervalSeconds * 1e3);
33190
33698
  }
33191
33699
  return null;
33192
33700
  }
@@ -36148,6 +36656,8 @@ const COMMANDS = {
36148
36656
  "org:secrets:list": OrgSecretsListCommand,
36149
36657
  "org:secrets:set": OrgSecretsSetCommand,
36150
36658
  "org:secrets:remove": OrgSecretsRemoveCommand,
36659
+ "payloads:push": PayloadsPushCommand,
36660
+ "payloads:pull": PayloadsPullCommand,
36151
36661
  "functions:test": FunctionsTestFunctionCommand,
36152
36662
  "functions:test-function": FunctionsTestFunctionCommand,
36153
36663
  "functions:route-set": FunctionsRouteSetCommand,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primcli",
3
- "version": "1.20.0",
3
+ "version": "1.21.0",
4
4
  "description": "Official Primitive CLI: deploy Primitive Functions, send and inspect mail, manage endpoints, all from the terminal. Wraps the @primitivedotdev/sdk runtime client with one-shot commands.",
5
5
  "type": "module",
6
6
  "sideEffects": false,