@toon-protocol/rig 4.0.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -411,7 +411,6 @@ Faucet routes (`POST`, body `{"address": "<wallet>"}`):
411
411
  |---|---|
412
412
  | `/api/base-sepolia/request` | 1000 USDC (ungated on-chain mint) + best-effort Base Sepolia gas |
413
413
  | `/api/solana/usdc-request` | 1000 USDC (treasury transfer, no SOL leg) |
414
- | `/api/solana/request` | 2 SOL + 1000 USDC (airdrop leg subject to the public devnet's per-IP quota) |
415
414
 
416
415
  `rig fund` hits the two USDC-only routes for the identity's derived wallets —
417
416
  it funds USDC and assumes gas is already present.
@@ -489,8 +488,10 @@ non-fast-forward updates; `--repo-id <id>` overrides the configured repo id.
489
488
 
490
489
  Repo addressing (`30617:<owner>:<repoId>`) comes from the `toon.repoid` / `toon.owner`
491
490
  git config keys `rig init` writes — an unconfigured repo is a clear "run `rig init`"
492
- error, and pushing never mutates git config. Objects over 95KB are a hard error in
493
- v1 (large-object support: toon-client#235).
491
+ error, and pushing never mutates git config. Objects over the storage upstream's
492
+ free per-item ceiling (107,520 bytes) are **not** an error — they upload as paid
493
+ writes, priced by the store route's per-KiB schedule, and the confirm table shows
494
+ the cost before anything is spent.
494
495
 
495
496
  ## Relays are origins
496
497
 
@@ -551,7 +552,8 @@ it — that stays behind the `Publisher` seam:
551
552
 
552
553
  - `objects.ts` — git object construction with SHA-1 envelope hashing: `createGitBlob`,
553
554
  `createGitTree`, `createGitCommit`, `createGitTag`, `hashGitObject`, and the
554
- `MAX_OBJECT_SIZE` (95KB) upload guard.
555
+ `FREE_TIER_MAX_ITEM_BYTES` (107,520) free-tier threshold (`MAX_OBJECT_SIZE` is
556
+ a deprecated alias; it is no longer a cap).
555
557
  - `nip34-events.ts` — NIP-34 event builders returning `UnsignedEvent`:
556
558
  `buildRepoAnnouncement` (30617), `buildRepoRefs` (30618), `buildIssue` (1621),
557
559
  `buildComment` (1622), `buildPatch` (1617), `buildStatus` (1630–1633).
@@ -0,0 +1,16 @@
1
+ // src/publisher.ts
2
+ var UPLOAD_ENVELOPE_OVERHEAD_BYTES = 704;
3
+ function estimateSealedUploadBytes(bodyBytes) {
4
+ return Math.ceil(bodyBytes / 3) * 4 + UPLOAD_ENVELOPE_OVERHEAD_BYTES;
5
+ }
6
+ function uploadChargeFor(rates, bodyBytes) {
7
+ const perKib = rates.uploadPerKib ?? 0n;
8
+ if (perKib === 0n) return rates.uploadFee;
9
+ const sealed = estimateSealedUploadBytes(bodyBytes);
10
+ return rates.uploadFee + perKib * BigInt(Math.floor(sealed / 1024) + 1);
11
+ }
12
+
13
+ export {
14
+ uploadChargeFor
15
+ };
16
+ //# sourceMappingURL=chunk-CXREFXB6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/publisher.ts"],"sourcesContent":["/**\n * Publisher — the paid-write seam between push planning (this package) and\n * the two transport implementations that follow it in epic #222:\n *\n * 1. daemon (#227): client-mcp ControlClient → toon-clientd loopback\n * /git/* routes, backed by `ClientRunner.publish`/`uploadBlob` (the\n * production paid-publish path — apex channel, signBalanceProof, flat\n * per-event fee).\n * 2. standalone (#228): an embedded ToonClient constructed from a\n * mnemonic, uploading git objects as kind:5094 store writes with\n * Git-SHA/Git-Type/Repo tags (the proven seed-pipeline shape) and\n * publishing NIP-34 events through the relay.\n *\n * The interface is deliberately minimal so both fit:\n *\n * - `uploadGitObject` takes the raw object body (content only — no git\n * envelope header; the store re-derives the SHA envelope from the\n * Git-Type/Git-SHA tags) and returns the Arweave txId plus the fee paid.\n * - `publishEvent` takes an UNSIGNED event template — signing stays with\n * the implementation (the daemon-held key never leaves the daemon; the\n * standalone impl signs with its own keypair). Relay URLs are plural\n * from day one (forward-compat for parked #84/#92; size 1 today).\n * - `getFeeRates` exposes what `planPush` needs for the pre-push estimate:\n * a per-byte upload rate (seed pipeline bids bytes × rate) and a flat\n * per-event publish fee (the daemon charges `apex.feePerEvent` regardless\n * of event size).\n *\n * All fees are bigint in the smallest asset unit (matches `@toon-protocol/client`\n * channel math); HTTP transports serialize them as strings and convert at\n * the boundary.\n */\n\nimport type { UnsignedEvent } from './nip34-events.js';\nimport type { GitObjectType } from './objects.js';\n\n/** A git object queued for upload to Arweave via the paid store path. */\nexport interface GitObjectUpload {\n /** Full 40-hex SHA-1 (over the git envelope — see `hashGitObject`). */\n sha: string;\n type: GitObjectType;\n /** Raw object body (content only, no `<type> <size>\\0` header). */\n body: Buffer;\n /** Repository identifier — becomes the `Repo` tag on the store write. */\n repoId: string;\n /**\n * Path the blob was reached by in the tree (#368), if known. Its extension\n * derives the `Content-Type` sent in the store write's `output` tag so a\n * gateway serves the blob as its real media type instead of\n * `application/octet-stream`. Absent (or a non-blob object) → octet-stream.\n */\n path?: string;\n}\n\n/**\n * A NON-git blob queued for upload to Arweave with an explicit `Content-Type`\n * (#368): the ar.io path manifest that turns a pushed repo into a permaweb\n * site. Unlike {@link GitObjectUpload} it carries no `Git-SHA`/`Git-Type`\n * tags, so the store stores the raw bytes verbatim (no git-envelope\n * re-derivation) — exactly what a manifest needs.\n */\nexport interface BlobUpload {\n /** Raw bytes to store. */\n body: Buffer;\n /** MIME type sent in the store write's `output` tag. */\n contentType: string;\n /** Optional repository identifier for provenance (`Repo` tag). */\n repoId?: string;\n}\n\n/** Receipt for one uploaded git object. */\nexport interface UploadReceipt {\n /** Arweave transaction ID the object is retrievable under. */\n txId: string;\n /** Fee paid for this upload, in the smallest asset unit. */\n feePaid: bigint;\n}\n\n/** Receipt for one published Nostr event. */\nexport interface PublishReceipt {\n /** Event ID as accepted by the relay(s). */\n eventId: string;\n /** Fee paid for this publish, in the smallest asset unit. */\n feePaid: bigint;\n}\n\n/**\n * One NIP-90 job for a store node's DVM, carried on the PAID path (#101).\n *\n * The store sits behind the connector's payment termination, so there is no\n * public HTTP endpoint to POST these to — a reachable one would be an\n * anonymous free gateway to a paid handler, which is the failure ADR 0020\n * exists to close. The job therefore travels exactly as a kind:5094 store\n * write does: inside a paid ILP packet, with `/store` as the envelope target\n * BENEATH the route's handler path rather than as a URL path.\n */\nexport interface StoreJobRequest {\n /** NIP-90 job kind — 5095 brokered ArNS buy, 5096 gas station. */\n kind: number;\n /** Job arguments, sent as `[\"param\", key, value]` tags. */\n params: [string, string][];\n}\n\n/**\n * A store DVM's answer, opened from the sealed FULFILL.\n *\n * ⚠️ A refusal is a RESULT, not an error. Under ADR 0020 an HTTP status is\n * envelope content and never a packet outcome, so `accept: false` arrives on a\n * FULFILL and the payer was charged for it either way. Implementations must\n * return it rather than throw: the zero-ARIO rehearsal (submit a buy with no\n * `processId` and watch the handler refuse by name before it quotes or touches\n * the registry) is the cheapest proof the whole paid path works, and it only\n * works if the refusal comes back as data.\n */\nexport interface StoreJobResponse {\n /** HTTP status the app answered with, inside the envelope. */\n status: number;\n /** The DVM's own accept flag. `false` is a refusal, not a transport fault. */\n accept: boolean;\n /** Machine-readable refusal reason, when the DVM sent one. */\n code?: string;\n /** Human-readable detail, when the DVM sent one. */\n message?: string;\n /** The job's output, shape depending on `kind`. */\n result?: Record<string, unknown>;\n /** Fee paid to carry this job, in the smallest asset unit. */\n feePaid: bigint;\n}\n\n/**\n * Fee rates used by `planPush` for the pre-push estimate.\n *\n * Both figures are FLAT per packet. ADR 0020 (toon-client#452) removed\n * byte-proportional pricing from the protocol: one handler, one price, and an\n * app that wants to charge differently exposes more handlers. A 100-byte and\n * a 100 KB upload to the same store route now cost the same, and the\n * connector charges accordingly regardless of what a client computes — so\n * there is no `uploadFeePerByte` to multiply and no floor to apply, because\n * the route's price is the whole fee rather than a lower bound on one.\n */\nexport interface FeeRates {\n /**\n * Flat cost per git-object/blob upload (smallest asset unit): the store\n * destination's route price, as reported by the terminating connector's\n * `GET /ilp/routes/price`. The connector gates every paid packet at that\n * price, so an estimate built from it is exactly what a push pays.\n */\n uploadFee: bigint;\n /**\n * The store route's per-kibibyte rate, when it meters by size (ADR 0065: a\n * price is a schedule `{base, per_kib}`; a flat route has no slope). Absent\n * or zero means every upload costs exactly `uploadFee`.\n */\n uploadPerKib?: bigint;\n /**\n * Flat cost per published event (smallest asset unit). Implementations\n * already fold any per-packet route-price floor into this flat value, so\n * estimates using it match the claims actually signed.\n */\n eventFee: bigint;\n}\n\n/**\n * Paid transport for `executePush` — implemented by the daemon route\n * handlers (#227) and the standalone embedded client (#228).\n *\n * Implementations must be safe to call sequentially (uploads are issued one\n * at a time in dependency-safe order) and should throw on any failure —\n * `executePush` does not retry; a crashed push is resumed by re-planning\n * (content-addressed uploads make the retry idempotent).\n */\nexport interface Publisher {\n /** Current fee rates for estimation (may be queried before every plan). */\n getFeeRates(): Promise<FeeRates>;\n /** Upload one git object body to Arweave; paid. */\n uploadGitObject(upload: GitObjectUpload): Promise<UploadReceipt>;\n /**\n * Upload one raw blob (no git envelope) with an explicit `Content-Type`;\n * paid. Used by `rig site` (#368) for the ar.io path manifest. Optional so\n * transports that only move git objects (and pre-#368 test fakes) need not\n * implement it — `rig site` requires it and errors clearly when absent.\n */\n uploadBlob?(upload: BlobUpload): Promise<UploadReceipt>;\n /**\n * Submit one NIP-90 job to the store node's DVM over the paid path and\n * return its answer; paid. Used by `rig name buy/set --via` (#101) for the\n * brokered kind:5095 buy and the kind:5096 gas station.\n *\n * Optional for the same reason as {@link uploadBlob}: transports that only\n * move git objects (and pre-#101 test fakes) need not implement it, and\n * `rig name --via` errors clearly when it is absent.\n */\n submitStoreJob?(request: StoreJobRequest): Promise<StoreJobResponse>;\n /**\n * Sign (implementation-held key) and pay-to-publish one event to the\n * given relay(s).\n */\n publishEvent(\n event: UnsignedEvent,\n relayUrls: string[]\n ): Promise<PublishReceipt>;\n}\n\n// ---------------------------------------------------------------------------\n// Per-upload charge on a metered route\n// ---------------------------------------------------------------------------\n\n/**\n * Bytes the connector meters that are not the caller's object: the kind:5094\n * event around it (id, pubkey, sig, kind, created_at, tag names), the `{event}`\n * body wrapper, the request envelope and the gift wrap. Measured against the\n * live edge and rounded up; a KiB boundary crossed by this margin costs one\n * `uploadPerKib` more than the store will actually charge, never less.\n */\nexport const UPLOAD_ENVELOPE_OVERHEAD_BYTES = 704;\n\n/**\n * The bytes a connector meters for an upload of `bodyBytes`: the object is\n * base64 in an `i` tag (4/3 expansion), plus {@link UPLOAD_ENVELOPE_OVERHEAD_BYTES}.\n */\nexport function estimateSealedUploadBytes(bodyBytes: number): number {\n return Math.ceil(bodyBytes / 3) * 4 + UPLOAD_ENVELOPE_OVERHEAD_BYTES;\n}\n\n/**\n * What one upload of `bodyBytes` costs under `rates`: the flat `uploadFee`,\n * plus — on a metered route — `uploadPerKib` per started kibibyte of the\n * sealed payload, counted from one (the connector's own rule:\n * `price + pricePerKib × (⌊bytes / 1024⌋ + 1)`).\n */\nexport function uploadChargeFor(rates: FeeRates, bodyBytes: number): bigint {\n const perKib = rates.uploadPerKib ?? 0n;\n if (perKib === 0n) return rates.uploadFee;\n const sealed = estimateSealedUploadBytes(bodyBytes);\n return rates.uploadFee + perKib * BigInt(Math.floor(sealed / 1024) + 1);\n}\n"],"mappings":";AAqNO,IAAM,iCAAiC;AAMvC,SAAS,0BAA0B,WAA2B;AACnE,SAAO,KAAK,KAAK,YAAY,CAAC,IAAI,IAAI;AACxC;AAQO,SAAS,gBAAgB,OAAiB,WAA2B;AAC1E,QAAM,SAAS,MAAM,gBAAgB;AACrC,MAAI,WAAW,GAAI,QAAO,MAAM;AAChC,QAAM,SAAS,0BAA0B,SAAS;AAClD,SAAO,MAAM,YAAY,SAAS,OAAO,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC;AACxE;","names":[]}
@@ -3,9 +3,8 @@ import {
3
3
  contentTypeForPath
4
4
  } from "./chunk-YZRR2UO6.js";
5
5
  import {
6
- MAX_OBJECT_SIZE,
7
6
  uploadChargeFor
8
- } from "./chunk-GVXBUL3C.js";
7
+ } from "./chunk-CXREFXB6.js";
9
8
 
10
9
  // src/standalone/connector-publisher.ts
11
10
  import { finalizeEvent } from "nostr-tools/pure";
@@ -164,11 +163,6 @@ var ConnectorPublisher = class {
164
163
  return { txId, feePaid: result.claim?.amount ?? estimate };
165
164
  }
166
165
  async uploadGitObject(upload) {
167
- if (upload.body.length > MAX_OBJECT_SIZE) {
168
- throw new ConnectorPublishError(
169
- `git object ${upload.sha} exceeds the ${String(MAX_OBJECT_SIZE)}-byte limit: ${String(upload.body.length)} bytes`
170
- );
171
- }
172
166
  const contentType = upload.type === "blob" ? contentTypeForPath(upload.path) : DEFAULT_CONTENT_TYPE;
173
167
  return this.storeWrite(
174
168
  [
@@ -183,11 +177,6 @@ var ConnectorPublisher = class {
183
177
  );
184
178
  }
185
179
  async uploadBlob(upload) {
186
- if (upload.body.length > MAX_OBJECT_SIZE) {
187
- throw new ConnectorPublishError(
188
- `blob exceeds the ${String(MAX_OBJECT_SIZE)}-byte limit: ${String(upload.body.length)} bytes`
189
- );
190
- }
191
180
  return this.storeWrite(
192
181
  [
193
182
  ["i", upload.body.toString("base64"), "blob"],
@@ -287,4 +276,4 @@ export {
287
276
  extractArweaveTxId,
288
277
  ConnectorPublisher
289
278
  };
290
- //# sourceMappingURL=chunk-CDSFVZDV.js.map
279
+ //# sourceMappingURL=chunk-FT2ZEKPX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/standalone/connector-publisher.ts"],"sourcesContent":["/**\n * The paid write path on `@toon-protocol/client` 2.x — a {@link Publisher}\n * that pays a TOON connector per request.\n *\n * Everything a paid command needs comes from one connector URL: the client\n * reads the node's own `GET /ilp` (addresses, prices, settlement chains,\n * sealing key — connector ADR 0050), opens or adopts a payment channel, and\n * pays each request with a signed claim. There is no relay to discover peers\n * on (kind:10032 was removed by ADR 0046) and no topology to negotiate: a\n * destination is a route the node prices, and `send()` pays what the route\n * costs — including the per-kibibyte part of an ADR 0065 schedule, which the\n * client works out from the sealed payload so this publisher never has to.\n *\n * Three request shapes, all ordinary paid POSTs:\n *\n * 1. `publishEvent` — a NIP-34 event signed with the OWNER's Nostr key,\n * sent as `{ event }` to the publish route; the relay answers\n * `{ eventId, storedAt, payment? }`.\n * 2. `uploadGitObject` / `uploadBlob` — a kind:5094 store job carrying the\n * bytes base64 in an `i` tag; the store answers the Arweave tx id.\n * 3. `submitStoreJob` — a kind:5095 / 5096 job with `param` tags; the\n * DVM's answer comes back as data, refusal included (ADR 0020: an answer\n * is an answer, and the payer was charged for it).\n *\n * The store route may terminate on a different node than the one addressed\n * (the edge forwards it). Then the payload must be sealed to THAT node's\n * identity — `sealTo` — and, when the store node holds its own channel, paid\n * through its own client. Both are options here; the common single-node case\n * needs neither.\n */\n\nimport { finalizeEvent } from 'nostr-tools/pure';\nimport { contentTypeForPath, DEFAULT_CONTENT_TYPE } from '../mime.js';\nimport type { UnsignedEvent } from '../nip34-events.js';\nimport {\n uploadChargeFor,\n type BlobUpload,\n type FeeRates,\n type GitObjectUpload,\n type PublishReceipt,\n type Publisher,\n type StoreJobRequest,\n type StoreJobResponse,\n type UploadReceipt,\n} from '../publisher.js';\n\n// ---------------------------------------------------------------------------\n// The slice of ToonClient this publisher drives (structural, so tests mock it)\n// ---------------------------------------------------------------------------\n\n/** A paid request, as `ToonClient.send` takes it. */\nexport interface PaidRequest {\n method?: string;\n target?: string;\n headers?: Record<string, string> | [string, string][];\n body?: string | Uint8Array | object;\n}\n\nexport interface PaidSendOptions {\n /** The terminating connector's `GET /ilp` URL or raw key, on a forwarded route. */\n sealTo?: string | Uint8Array;\n timeoutMs?: number;\n}\n\nexport interface PaidFulfilled {\n fulfilled: true;\n status: number;\n body: Uint8Array;\n /** What this request paid; absent on a route priced at zero. */\n claim?: { amount: bigint };\n}\n\nexport interface PaidRefused {\n fulfilled: false;\n code: string;\n message: string;\n refusedBy?: string;\n}\n\nexport type PaidSendResult = PaidFulfilled | PaidRefused;\n\n/** The route terms a connector publishes: a base price and an optional slope. */\nexport interface RouteTerms {\n price: bigint;\n pricePerKib?: bigint;\n}\n\n/** The slice of `ToonClient` (2.x) this publisher needs. */\nexport interface PaidClientLike {\n send(\n destination: string,\n request: PaidRequest,\n options?: PaidSendOptions\n ): Promise<PaidSendResult>;\n /** The node's terms for a destination, or `null` when it prices no such route. */\n routePrice(destination: string): Promise<RouteTerms | null>;\n}\n\n/** One leg of the write path: who pays, where to, and whom to seal to. */\nexport interface PaidLeg {\n client: PaidClientLike;\n destination: string;\n sealTo?: string | Uint8Array;\n}\n\nexport interface ConnectorPublisherOptions {\n /** Pays the publish route and signs nothing — the Nostr key below signs. */\n publish: PaidLeg;\n /** Pays the store route (often the same client as `publish`). */\n store: PaidLeg;\n /** The owner's Nostr secret key: signs every event this publisher sends. */\n nostrSecretKey: Uint8Array;\n /**\n * A configured per-event fee. Informational floor only: the connector's\n * published price for the publish route wins when it is higher, and the\n * client pays that price whatever this says.\n */\n eventFee?: bigint;\n /** Per-packet timeout for uploads (the store's Arweave round trip). */\n uploadTimeoutMs?: number;\n warn?(line: string): void;\n}\n\nexport class ConnectorPublishError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ConnectorPublishError';\n }\n}\n\nconst ARWEAVE_TX_ID_REGEX = /^[A-Za-z0-9_-]{43}$/;\n\nfunction decodeUtf8(bytes: Uint8Array): string {\n return new TextDecoder().decode(bytes);\n}\n\nfunction nowSeconds(): number {\n return Math.floor(Date.now() / 1000);\n}\n\nfunction describeRefusal(result: PaidRefused): string {\n const by = result.refusedBy ? ` by the ${result.refusedBy}` : '';\n return `${result.code}${by}: ${result.message}`;\n}\n\n/**\n * Read the Arweave tx id out of a store's kind:5094 answer.\n *\n * The store answers one of two ways — `{ txId }` as JSON, or the bare id\n * base64 in `data` — and refuses with `{ accept: false, error }`. A non-2xx\n * status is a refusal too; both are thrown, because an upload without an id\n * is an upload that did not happen (even though the packet was paid for).\n */\nexport function extractArweaveTxId(status: number, body: Uint8Array): string {\n const text = decodeUtf8(body);\n if (status < 200 || status >= 300) {\n throw new ConnectorPublishError(\n `the store answered HTTP ${String(status)}${text ? ` - ${text}` : ''}`\n );\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n if (ARWEAVE_TX_ID_REGEX.test(text.trim())) return text.trim();\n throw new ConnectorPublishError(\n `the store's answer was not valid JSON: ${JSON.stringify(text.slice(0, 200))}`\n );\n }\n const answer = (parsed ?? {}) as {\n accept?: unknown;\n error?: unknown;\n txId?: unknown;\n data?: unknown;\n result?: unknown;\n };\n if (answer.accept === false) {\n const reason = typeof answer.error === 'string' ? `: ${answer.error}` : '';\n throw new ConnectorPublishError(\n `upload refused by the store (accept:false)${reason}`\n );\n }\n if (\n typeof answer.txId === 'string' &&\n ARWEAVE_TX_ID_REGEX.test(answer.txId)\n ) {\n return answer.txId;\n }\n const nested = answer.result as { txId?: unknown } | undefined;\n if (\n nested &&\n typeof nested.txId === 'string' &&\n ARWEAVE_TX_ID_REGEX.test(nested.txId)\n ) {\n return nested.txId;\n }\n if (typeof answer.data === 'string' && answer.data.length > 0) {\n const decoded = Buffer.from(answer.data, 'base64').toString('utf8');\n if (ARWEAVE_TX_ID_REGEX.test(decoded)) return decoded;\n }\n throw new ConnectorPublishError(\n `the store's answer carried no Arweave tx id: ${JSON.stringify(text.slice(0, 200))}`\n );\n}\n\n// ---------------------------------------------------------------------------\n// The publisher\n// ---------------------------------------------------------------------------\n\nexport class ConnectorPublisher implements Publisher {\n private readonly publish: PaidLeg;\n private readonly store: PaidLeg;\n private readonly nostrSecretKey: Uint8Array;\n private readonly configuredEventFee: bigint;\n private readonly uploadTimeoutMs: number | undefined;\n private readonly warn: (line: string) => void;\n private rates: Promise<FeeRates> | undefined;\n\n constructor(options: ConnectorPublisherOptions) {\n this.publish = options.publish;\n this.store = options.store;\n this.nostrSecretKey = options.nostrSecretKey;\n this.configuredEventFee = options.eventFee ?? 0n;\n this.uploadTimeoutMs = options.uploadTimeoutMs;\n this.warn = options.warn ?? (() => undefined);\n }\n\n /** Where events go, and where objects go. */\n get destinations(): { publish: string; store: string } {\n return { publish: this.publish.destination, store: this.store.destination };\n }\n\n /**\n * The two routes' published terms, read once per publisher. A route the\n * node does not price is a configuration error and says which knob to fix.\n */\n getFeeRates(): Promise<FeeRates> {\n this.rates ??= this.readFeeRates();\n return this.rates;\n }\n\n private async readFeeRates(): Promise<FeeRates> {\n const [publishTerms, storeTerms] = await Promise.all([\n this.publish.client.routePrice(this.publish.destination),\n this.store.client.routePrice(this.store.destination),\n ]);\n if (!publishTerms) {\n throw new ConnectorPublishError(\n `the connector prices no route for the publish destination ${this.publish.destination} ` +\n '(set publishDestination / TOON_CLIENT_PUBLISH_DESTINATION to one of its GET /ilp routes)'\n );\n }\n if (!storeTerms) {\n throw new ConnectorPublishError(\n `the connector prices no route for the store destination ${this.store.destination} ` +\n '(set storeDestination / TOON_CLIENT_STORE_DESTINATION to one of its GET /ilp routes)'\n );\n }\n if (\n publishTerms.pricePerKib !== undefined &&\n publishTerms.pricePerKib > 0n\n ) {\n this.warn(\n `rig: the publish route ${this.publish.destination} meters by size ` +\n `(${String(publishTerms.pricePerKib)}/KiB); the per-event estimate shows its base price only`\n );\n }\n const eventFee =\n this.configuredEventFee > publishTerms.price\n ? this.configuredEventFee\n : publishTerms.price;\n return {\n uploadFee: storeTerms.price,\n ...(storeTerms.pricePerKib !== undefined && storeTerms.pricePerKib > 0n\n ? { uploadPerKib: storeTerms.pricePerKib }\n : {}),\n eventFee,\n };\n }\n\n private sign(template: UnsignedEvent) {\n return finalizeEvent(\n {\n kind: template.kind,\n content: template.content,\n tags: template.tags,\n created_at: template.created_at,\n },\n this.nostrSecretKey\n );\n }\n\n /** One paid kind:5094 write; returns the tx id and what the claim spent. */\n private async storeWrite(\n tags: string[][],\n bodyBytes: number,\n what: string\n ): Promise<UploadReceipt> {\n const rates = await this.getFeeRates();\n const estimate = uploadChargeFor(rates, bodyBytes);\n const event = this.sign({\n kind: 5094,\n content: '',\n created_at: nowSeconds(),\n tags: [...tags, ['bid', estimate.toString(), 'usdc']],\n });\n const result = await this.store.client.send(\n this.store.destination,\n { body: { event } },\n {\n ...(this.store.sealTo !== undefined\n ? { sealTo: this.store.sealTo }\n : {}),\n ...(this.uploadTimeoutMs !== undefined\n ? { timeoutMs: this.uploadTimeoutMs }\n : {}),\n }\n );\n if (!result.fulfilled) {\n throw new ConnectorPublishError(\n `${what} rejected — ${describeRefusal(result)}`\n );\n }\n let txId: string;\n try {\n txId = extractArweaveTxId(result.status, result.body);\n } catch (err) {\n throw new ConnectorPublishError(\n `${what}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n return { txId, feePaid: result.claim?.amount ?? estimate };\n }\n\n async uploadGitObject(upload: GitObjectUpload): Promise<UploadReceipt> {\n // No size gate (#102): the store route prices per KiB, so a body above\n // FREE_TIER_MAX_ITEM_BYTES is a paid upload rather than a refusal.\n const contentType =\n upload.type === 'blob'\n ? contentTypeForPath(upload.path)\n : DEFAULT_CONTENT_TYPE;\n return this.storeWrite(\n [\n ['i', upload.body.toString('base64'), 'blob'],\n ['output', contentType],\n ['Git-SHA', upload.sha],\n ['Git-Type', upload.type],\n ['Repo', upload.repoId],\n ],\n upload.body.length,\n `git-object upload (${upload.sha})`\n );\n }\n\n async uploadBlob(upload: BlobUpload): Promise<UploadReceipt> {\n // No size gate (#102), matching uploadGitObject: the store route prices\n // per KiB, so a blob above FREE_TIER_MAX_ITEM_BYTES costs more rather than\n // being refused.\n return this.storeWrite(\n [\n ['i', upload.body.toString('base64'), 'blob'],\n ['output', upload.contentType],\n ...(upload.repoId ? [['Repo', upload.repoId]] : []),\n ],\n upload.body.length,\n 'blob upload'\n );\n }\n\n /**\n * One NIP-90 job to the store node's DVM, over the paid path. A refusal is\n * returned, not thrown: `accept: false` and a non-2xx status are the\n * answer's content, and the payer was charged for the answer (ADR 0020).\n */\n async submitStoreJob(request: StoreJobRequest): Promise<StoreJobResponse> {\n const rates = await this.getFeeRates();\n const fee = rates.uploadFee;\n const event = this.sign({\n kind: request.kind,\n content: '',\n created_at: nowSeconds(),\n tags: [\n ...request.params.map(([key, value]) => ['param', key, value]),\n ['bid', fee.toString(), 'usdc'],\n ],\n });\n const result = await this.store.client.send(\n this.store.destination,\n { body: { event } },\n this.store.sealTo !== undefined ? { sealTo: this.store.sealTo } : {}\n );\n if (!result.fulfilled) {\n throw new ConnectorPublishError(\n `kind:${String(request.kind)} job rejected — ${describeRefusal(result)}`\n );\n }\n const text = decodeUtf8(result.body);\n let body: {\n accept?: unknown;\n code?: unknown;\n message?: unknown;\n result?: unknown;\n };\n try {\n body = JSON.parse(text) as typeof body;\n } catch {\n throw new ConnectorPublishError(\n `kind:${String(request.kind)} job answer was not valid JSON ` +\n `(HTTP ${String(result.status)}): ${JSON.stringify(text.slice(0, 200))}`\n );\n }\n return {\n status: result.status,\n accept: body.accept === true,\n ...(typeof body.code === 'string' ? { code: body.code } : {}),\n ...(typeof body.message === 'string' ? { message: body.message } : {}),\n ...(body.result !== null && typeof body.result === 'object'\n ? { result: body.result as Record<string, unknown> }\n : {}),\n feePaid: result.claim?.amount ?? fee,\n };\n }\n\n /**\n * Sign the event with the owner's key and pay to publish it. `relayUrls`\n * is the seam's plural surface: this publisher routes to one configured\n * destination, so more than one relay is refused rather than half-done.\n */\n async publishEvent(\n event: UnsignedEvent,\n relayUrls: string[]\n ): Promise<PublishReceipt> {\n if (relayUrls.length > 1) {\n throw new ConnectorPublishError(\n `multi-relay publish is not supported (got ${String(relayUrls.length)} relays) — ` +\n 'the connector publisher routes to a single publish destination'\n );\n }\n const rates = await this.getFeeRates();\n const signed = this.sign(event);\n const result = await this.publish.client.send(\n this.publish.destination,\n { body: { event: signed } },\n this.publish.sealTo !== undefined ? { sealTo: this.publish.sealTo } : {}\n );\n if (!result.fulfilled) {\n throw new ConnectorPublishError(\n `publish rejected (kind ${String(event.kind)}) — ${describeRefusal(result)}`\n );\n }\n if (result.status < 200 || result.status >= 300) {\n throw new ConnectorPublishError(\n `publish (kind ${String(event.kind)}): the relay answered HTTP ${String(result.status)} - ${decodeUtf8(result.body)}`\n );\n }\n let eventId = signed.id;\n try {\n const answer = JSON.parse(decodeUtf8(result.body)) as {\n eventId?: unknown;\n };\n if (typeof answer.eventId === 'string' && answer.eventId)\n eventId = answer.eventId;\n } catch {\n // A 2xx without a JSON body still stored the event we signed.\n }\n return { eventId, feePaid: result.claim?.amount ?? rates.eventFee };\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,4 +1,5 @@
1
1
  // src/nip34-events.ts
2
+ import { getAddress, isAddress } from "viem";
2
3
  import {
3
4
  ISSUE_KIND,
4
5
  PATCH_KIND,
@@ -29,7 +30,36 @@ function authorizedStatusAuthors(ownerPubkey, repoAnnouncementTags) {
29
30
  ...parseMaintainers(repoAnnouncementTags)
30
31
  ]);
31
32
  }
32
- function buildRepoAnnouncement(repoId, name, description, maintainers = []) {
33
+ var PAYOUT_TAG = "payout";
34
+ var SUPPORTED_PAYOUT_CHAINS = /* @__PURE__ */ new Set([
35
+ "evm"
36
+ ]);
37
+ function isSupportedPayoutChain(chain) {
38
+ return SUPPORTED_PAYOUT_CHAINS.has(chain);
39
+ }
40
+ function isValidEvmPayoutAddress(address) {
41
+ return isAddress(address);
42
+ }
43
+ function parsePayout(tags) {
44
+ let result = null;
45
+ let ignored = 0;
46
+ for (const tag of tags) {
47
+ if (tag[0] !== PAYOUT_TAG) continue;
48
+ const [, chain, address] = tag;
49
+ if (result === null && chain !== void 0 && isSupportedPayoutChain(chain) && address !== void 0 && isValidEvmPayoutAddress(address)) {
50
+ result = { chain, address: getAddress(address) };
51
+ } else {
52
+ ignored++;
53
+ }
54
+ }
55
+ if (ignored > 0) {
56
+ console.warn(
57
+ `rig: ignoring ${ignored} extra/invalid "${PAYOUT_TAG}" tag(s) on kind:30617` + (result ? ` \u2014 using ${result.chain} ${result.address}` : "")
58
+ );
59
+ }
60
+ return result;
61
+ }
62
+ function buildRepoAnnouncement(repoId, name, description, maintainers = [], payout) {
33
63
  const tags = [
34
64
  ["d", repoId],
35
65
  ["name", name],
@@ -47,6 +77,9 @@ function buildRepoAnnouncement(repoId, name, description, maintainers = []) {
47
77
  if (declared.length > 0) {
48
78
  tags.push([MAINTAINERS_TAG, ...declared]);
49
79
  }
80
+ if (payout) {
81
+ tags.push([PAYOUT_TAG, payout.chain, payout.address]);
82
+ }
50
83
  return {
51
84
  kind: REPOSITORY_ANNOUNCEMENT_KIND,
52
85
  content: "",
@@ -443,6 +476,7 @@ async function fetchRemoteState(options) {
443
476
  }
444
477
  }
445
478
  const maintainers = announceEvent ? parseMaintainers(announceEvent.tags) : [];
479
+ const payout = announceEvent ? parsePayout(announceEvent.tags) : null;
446
480
  const resolveMissing = async (shas) => {
447
481
  const resolved = /* @__PURE__ */ new Map();
448
482
  const missing = [];
@@ -475,6 +509,7 @@ async function fetchRemoteState(options) {
475
509
  description,
476
510
  relays,
477
511
  maintainers,
512
+ payout,
478
513
  resolveMissing
479
514
  };
480
515
  }
@@ -485,6 +520,9 @@ export {
485
520
  MAINTAINERS_TAG,
486
521
  parseMaintainers,
487
522
  authorizedStatusAuthors,
523
+ PAYOUT_TAG,
524
+ isValidEvmPayoutAddress,
525
+ parsePayout,
488
526
  buildRepoAnnouncement,
489
527
  buildRepoRefs,
490
528
  buildIssue,
@@ -497,4 +535,4 @@ export {
497
535
  queryRelay,
498
536
  fetchRemoteState
499
537
  };
500
- //# sourceMappingURL=chunk-2KJJ7LSO.js.map
538
+ //# sourceMappingURL=chunk-NCEMQUPA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/nip34-events.ts","../src/remote-state.ts","../../../node_modules/.pnpm/@toon-protocol+arweave@0.2.0/node_modules/@toon-protocol/arweave/src/gateways.ts","../../../node_modules/.pnpm/@toon-protocol+arweave@0.2.0/node_modules/@toon-protocol/arweave/src/git-sha.ts"],"sourcesContent":["/**\n * Pure NIP-34 event builders for the Git-to-TOON write path.\n *\n * Promoted from `packages/rig-web/tests/e2e/seed/lib/event-builders.ts` (#223).\n * All builders return UnsignedEvent — the caller signs with their keypair\n * via finalizeEvent() and publishes through a Publisher (#226). Tag\n * structures follow the NIP-34 spec and `@toon-protocol/core/nip34`.\n */\n\nimport { getAddress, isAddress } from 'viem';\nimport {\n ISSUE_KIND,\n PATCH_KIND,\n REPOSITORY_ANNOUNCEMENT_KIND,\n} from '@toon-protocol/core/nip34';\nimport type {\n STATUS_APPLIED_KIND,\n STATUS_CLOSED_KIND,\n STATUS_DRAFT_KIND,\n STATUS_OPEN_KIND,\n} from '@toon-protocol/core/nip34';\n\n// Kinds not (yet) exported by @toon-protocol/core/nip34:\n/** Repository State (refs) — replaceable, pairs with kind:30617 via `d` tag. */\nexport const REPOSITORY_STATE_KIND = 30618;\n/** Comment on an issue or patch (NIP-22 style threading within NIP-34). */\nexport const COMMENT_KIND = 1622;\n\n// ---------------------------------------------------------------------------\n// UnsignedEvent type (subset of nostr-tools — no id, sig, or pubkey)\n// ---------------------------------------------------------------------------\n\nexport interface UnsignedEvent {\n kind: number;\n content: string;\n tags: string[][];\n created_at: number;\n}\n\n// ---------------------------------------------------------------------------\n// kind:30617 — Repository Announcement (+ maintainer authority, #287)\n// ---------------------------------------------------------------------------\n\n/**\n * NIP-34 tag naming the repo's declared maintainers: one multi-valued tag\n * `[\"maintainers\", \"<hex-pubkey>\", \"<hex-pubkey>\", …]` on the kind:30617\n * announcement (mirrors the spec's multi-valued `relays` tag). The repo\n * OWNER — the announcement event's own pubkey — is ALWAYS an implicit\n * maintainer and need not be listed. Consumers derive an issue/PR's status\n * ONLY from kind:1630-1633 events signed by owner ∪ maintainers (#287): the\n * relay is permissionless, so this is the CONSUMER-side authority filter.\n */\nexport const MAINTAINERS_TAG = 'maintainers';\n\nconst HEX64 = /^[0-9a-f]{64}$/;\n\n/**\n * Collect the declared maintainer pubkeys (lowercased hex) from a kind:30617\n * event's tags. Tolerant of repeated `maintainers` tags and non-hex noise —\n * only 64-char hex values survive. Does NOT include the owner (implicit).\n */\nexport function parseMaintainers(tags: string[][]): string[] {\n const out: string[] = [];\n const seen = new Set<string>();\n for (const tag of tags) {\n if (tag[0] !== MAINTAINERS_TAG) continue;\n for (const value of tag.slice(1)) {\n const hex = value.toLowerCase();\n if (HEX64.test(hex) && !seen.has(hex)) {\n seen.add(hex);\n out.push(hex);\n }\n }\n }\n return out;\n}\n\n/**\n * The set of pubkeys whose kind:1630-1633 status events are authoritative for\n * a repo: the owner (always) ∪ the declared maintainers (from the 30617's\n * `maintainers` tag). All values are lowercased hex.\n */\nexport function authorizedStatusAuthors(\n ownerPubkey: string,\n repoAnnouncementTags: string[][]\n): Set<string> {\n return new Set([\n ownerPubkey.toLowerCase(),\n ...parseMaintainers(repoAnnouncementTags),\n ]);\n}\n\n// ---------------------------------------------------------------------------\n// Payout pointer (rig#92, part of the payout epic toon-protocol/toon-meta#391)\n// ---------------------------------------------------------------------------\n\n/**\n * NIP-34 tag naming the repo's declared payout pointer: a single\n * `[\"payout\", \"<chain>\", \"<address>\"]` tag on the kind:30617 announcement.\n * No pointer → no split, the serving node keeps 100% of repo-scoped write\n * fees (toon-meta#391 decision 2). The tag carries the chain label so the\n * shape survives future chains, but v1 accepts exactly one: `evm` — the\n * payout accrual ledger is EVM-only today\n * (`crates/connector-client-edge/src/btp.rs:270-272`).\n */\nexport const PAYOUT_TAG = 'payout';\n\n/** Chains the payout pointer can target. v1: `evm` only (toon-meta#391). */\nexport type PayoutChain = 'evm';\n\nconst SUPPORTED_PAYOUT_CHAINS: ReadonlySet<string> = new Set<PayoutChain>([\n 'evm',\n]);\n\n/** True when `chain` is a chain the payout pointer supports today. */\nfunction isSupportedPayoutChain(chain: string): chain is PayoutChain {\n return SUPPORTED_PAYOUT_CHAINS.has(chain);\n}\n\n/** A repo's declared payout pointer (parsed from / built into the `payout` tag). */\nexport interface PayoutPointer {\n chain: PayoutChain;\n /** EIP-55 checksummed address (never a raw lowercase/mixed-case echo). */\n address: string;\n}\n\n/**\n * True when `address` is a structurally valid, correctly-checksummed EVM\n * address: `0x` + 40 hex chars, and — when mixed-case — a valid EIP-55\n * checksum (an all-lowercase address is accepted as \"unchecksummed\").\n */\nexport function isValidEvmPayoutAddress(address: string): boolean {\n return isAddress(address);\n}\n\n/**\n * Collect the repo's declared payout pointer from a kind:30617 event's tags.\n * Only `[\"payout\", \"evm\", <address>]` with a shape- and checksum-valid\n * address is accepted; the address is normalized to its EIP-55 checksummed\n * form. Tolerant of relay noise: an unsupported chain or malformed address\n * is skipped (not thrown). The relay is permissionless and 30617 is\n * replaceable, so a legacy/hand-crafted event could carry more than one\n * `payout` tag — the first valid one wins and the rest are ignored (a\n * `console.warn` notes the drop so a stray extra tag isn't silently\n * mysterious).\n */\nexport function parsePayout(tags: string[][]): PayoutPointer | null {\n let result: PayoutPointer | null = null;\n let ignored = 0;\n for (const tag of tags) {\n if (tag[0] !== PAYOUT_TAG) continue;\n const [, chain, address] = tag;\n if (\n result === null &&\n chain !== undefined &&\n isSupportedPayoutChain(chain) &&\n address !== undefined &&\n isValidEvmPayoutAddress(address)\n ) {\n result = { chain, address: getAddress(address) };\n } else {\n ignored++;\n }\n }\n if (ignored > 0) {\n console.warn(\n `rig: ignoring ${ignored} extra/invalid \"${PAYOUT_TAG}\" tag(s) on kind:30617` +\n (result ? ` — using ${result.chain} ${result.address}` : '')\n );\n }\n return result;\n}\n\n/**\n * Build a kind:30617 repository announcement event.\n *\n * @param repoId - Repository identifier (d tag)\n * @param name - Human-readable repository name\n * @param description - Repository description\n * @param maintainers - Optional declared maintainer pubkeys (hex). Emitted as\n * a single `[\"maintainers\", …]` tag when non-empty; duplicate and non-64-hex\n * values are dropped. The owner (the signer) is an implicit maintainer and\n * need not be listed — if passed it is emitted, which is harmless since the\n * owner is authorized regardless. See {@link MAINTAINERS_TAG}.\n * @param payout - Optional declared payout pointer (rig#92). Emitted as a\n * single `[\"payout\", \"evm\", <address>]` tag when given; omit (or pass\n * `null`) to leave the repo with no payout pointer. See {@link PAYOUT_TAG}.\n */\nexport function buildRepoAnnouncement(\n repoId: string,\n name: string,\n description: string,\n maintainers: string[] = [],\n payout?: PayoutPointer | null\n): UnsignedEvent {\n const tags: string[][] = [\n ['d', repoId],\n ['name', name],\n ['description', description],\n ];\n const declared: string[] = [];\n const seen = new Set<string>();\n for (const value of maintainers) {\n const hex = value.toLowerCase();\n if (HEX64.test(hex) && !seen.has(hex)) {\n seen.add(hex);\n declared.push(hex);\n }\n }\n if (declared.length > 0) {\n tags.push([MAINTAINERS_TAG, ...declared]);\n }\n if (payout) {\n tags.push([PAYOUT_TAG, payout.chain, payout.address]);\n }\n return {\n kind: REPOSITORY_ANNOUNCEMENT_KIND,\n content: '',\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:30618 — Repository Refs/State\n// ---------------------------------------------------------------------------\n\n/**\n * Build a kind:30618 repository refs/state event.\n *\n * @param repoId - Repository identifier (d tag, matches kind:30617)\n * @param refs - Map of ref paths to commit SHAs (e.g., { 'refs/heads/main': 'abc123' })\n * @param arweaveMap - Map of git SHAs to Arweave transaction IDs\n */\nexport function buildRepoRefs(\n repoId: string,\n refs: Record<string, string>,\n arweaveMap: Record<string, string> = {}\n): UnsignedEvent {\n const tags: string[][] = [['d', repoId]];\n\n // Add ref tags\n for (const [refPath, commitSha] of Object.entries(refs)) {\n tags.push(['r', refPath, commitSha]);\n }\n\n // Default HEAD to first ref (typically refs/heads/main)\n const firstRef = Object.keys(refs)[0];\n if (firstRef) {\n tags.push(['HEAD', `ref: ${firstRef}`]);\n }\n\n // Add arweave SHA-to-txId mapping tags\n for (const [sha, txId] of Object.entries(arweaveMap)) {\n tags.push(['arweave', sha, txId]);\n }\n\n return {\n kind: REPOSITORY_STATE_KIND,\n content: '',\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:1621 — Issue\n// ---------------------------------------------------------------------------\n\n/**\n * Build a kind:1621 issue event.\n *\n * @param repoOwnerPubkey - Pubkey of the repository owner\n * @param repoId - Repository identifier\n * @param title - Issue title (subject tag)\n * @param body - Issue body (Markdown content)\n * @param labels - Optional labels (t tags)\n */\nexport function buildIssue(\n repoOwnerPubkey: string,\n repoId: string,\n title: string,\n body: string,\n labels: string[] = []\n): UnsignedEvent {\n const tags: string[][] = [\n ['a', `${REPOSITORY_ANNOUNCEMENT_KIND}:${repoOwnerPubkey}:${repoId}`],\n ['p', repoOwnerPubkey],\n ['subject', title],\n ...labels.map((label) => ['t', label]),\n ];\n\n return {\n kind: ISSUE_KIND,\n content: body,\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:1622 — Comment (on issue or PR)\n// ---------------------------------------------------------------------------\n\n/**\n * Build a kind:1622 comment event.\n *\n * @param repoOwnerPubkey - Pubkey of the repository owner\n * @param repoId - Repository identifier\n * @param issueOrPrEventId - Event ID of the issue or PR being commented on\n * @param authorPubkey - Pubkey of the issue/PR author (NIP-34 `p` tag for threading), NOT the comment author\n * @param body - Comment body (Markdown content)\n * @param marker - Event reference marker: 'root' or 'reply' (default: 'reply')\n */\nexport function buildComment(\n repoOwnerPubkey: string,\n repoId: string,\n issueOrPrEventId: string,\n authorPubkey: string,\n body: string,\n marker: 'root' | 'reply' = 'reply'\n): UnsignedEvent {\n return {\n kind: COMMENT_KIND,\n content: body,\n tags: [\n ['a', `${REPOSITORY_ANNOUNCEMENT_KIND}:${repoOwnerPubkey}:${repoId}`],\n ['e', issueOrPrEventId, '', marker],\n ['p', authorPubkey],\n ],\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:1617 — Patch / PR\n// ---------------------------------------------------------------------------\n\n/**\n * Build a kind:1617 patch event.\n *\n * The PR body/description travels in a dedicated `description` tag, NEVER in\n * `content` (#280): `content` is real `git format-patch` output that readers\n * pipe straight into `git am`, and git's patch-format detection hard-fails on\n * any leading prose (verified: \"Patch format detection failed.\"). The tag\n * route keeps `git am` consumption intact while `rig pr show` and the\n * rig-web/views `parsePR` renderers surface the description.\n *\n * @param repoOwnerPubkey - Pubkey of the repository owner\n * @param repoId - Repository identifier\n * @param title - Patch/PR title (subject tag)\n * @param commits - Array of { sha, parentSha } for commit and parent-commit tags\n * @param branchTag - Branch name for the t tag\n * @param content - Real `git format-patch` text (NIP-34 patch body); defaults\n * to '' for callers that only reference commits by tag\n * @param description - PR body/cover text (`description` tag) — kept out of\n * `content` so `git am` still applies it\n */\nexport function buildPatch(\n repoOwnerPubkey: string,\n repoId: string,\n title: string,\n commits: { sha: string; parentSha: string }[],\n branchTag?: string,\n content = '',\n description?: string\n): UnsignedEvent {\n const tags: string[][] = [\n ['a', `${REPOSITORY_ANNOUNCEMENT_KIND}:${repoOwnerPubkey}:${repoId}`],\n ['p', repoOwnerPubkey],\n ['subject', title],\n ];\n\n if (description !== undefined && description !== '') {\n tags.push(['description', description]);\n }\n\n for (const commit of commits) {\n tags.push(['commit', commit.sha]);\n tags.push(['parent-commit', commit.parentSha]);\n }\n\n if (branchTag) {\n tags.push(['t', branchTag]);\n }\n\n return {\n kind: PATCH_KIND,\n content,\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:1630-1633 — Status\n// ---------------------------------------------------------------------------\n\n/** Status kinds: 1630 open, 1631 applied/merged, 1632 closed, 1633 draft. */\nexport type StatusKind =\n | typeof STATUS_OPEN_KIND\n | typeof STATUS_APPLIED_KIND\n | typeof STATUS_CLOSED_KIND\n | typeof STATUS_DRAFT_KIND;\n\n/**\n * Build a status event (kind 1630-1633).\n *\n * @param targetEventId - Event ID of the patch, PR, or issue being updated\n * @param statusKind - One of 1630 (open), 1631 (applied), 1632 (closed), 1633 (draft)\n * @param targetPubkey - Optional pubkey of the target event author (p tag per NIP-34 StatusEvent)\n */\nexport function buildStatus(\n targetEventId: string,\n statusKind: StatusKind,\n targetPubkey?: string\n): UnsignedEvent {\n const tags: string[][] = [['e', targetEventId]];\n if (targetPubkey) {\n tags.push(['p', targetPubkey]);\n }\n return {\n kind: statusKind,\n content: '',\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n","/**\n * Remote repository state reader — the \"what does the remote have?\" half of\n * `rig push` (epic #222, ticket #225).\n *\n * Queries relay(s) over NIP-01 WebSocket for the repository's NIP-34 state:\n * - kind:30618 (repository state): `r` tags → ref map, `HEAD` symref,\n * `arweave` tags → git SHA → Arweave txId hints.\n * - kind:30617 (repository announcement): presence = the repo exists on\n * TOON (first-push detection) + name/description/relays metadata.\n *\n * Both kinds are NIP-33 parameterized-replaceable: per relay only the latest\n * event per (kind, author, d) survives, but we still pick the newest across\n * relays (highest created_at, ties broken by lowest id per NIP-01).\n *\n * SHAs missing from the `arweave` tag map can be resolved via the shared\n * Arweave GraphQL Git-SHA resolver (@toon-protocol/arweave — the same module\n * the rig SPA uses) through {@link RemoteState.resolveMissing}.\n *\n * Relay payload encodings (mirrors rig's `web/relay-client.ts` decode logic):\n * EVENT payloads arrive as an inline JSON object (standard NIP-01), as a\n * double-JSON-encoded string (devnet relay quirk: a JSON string containing\n * the event JSON), or as a TOON-encoded string. All three are tolerated.\n */\n\nimport { decode as decodeToon } from '@toon-format/toon';\nimport {\n resolveGitSha,\n seedShaCache,\n shaCacheKey,\n} from '@toon-protocol/arweave';\nimport { REPOSITORY_ANNOUNCEMENT_KIND } from '@toon-protocol/core/nip34';\n\nimport {\n REPOSITORY_STATE_KIND,\n parseMaintainers,\n parsePayout,\n type PayoutPointer,\n} from './nip34-events.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** A signed Nostr event as seen on a relay (read side). */\nexport interface NostrEvent {\n id: string;\n pubkey: string;\n created_at: number;\n kind: number;\n tags: string[][];\n content: string;\n sig: string;\n}\n\n/** NIP-01 subscription filter (only the fields rig's readers send). */\nexport interface NostrFilter {\n ids?: string[];\n kinds?: number[];\n authors?: string[];\n '#d'?: string[];\n /** Repo address tag filter, e.g. `30617:<owner>:<repoId>` (#278 tracker). */\n '#a'?: string[];\n /** Event-reference tag filter (#278 tracker: statuses + comments). */\n '#e'?: string[];\n limit?: number;\n}\n\n/**\n * Minimal structural WebSocket type — satisfied by the WHATWG WebSocket\n * global (Node >= 22 / undici, browsers) and by the `ws` package.\n */\nexport interface WebSocketLike {\n readyState: number;\n send(data: string): void;\n close(): void;\n addEventListener(type: string, listener: (event: never) => void): void;\n}\n\n/** Factory for WebSocket connections (injectable for tests / `ws` fallback). */\nexport type WebSocketFactory = (url: string) => WebSocketLike;\n\nexport interface FetchRemoteStateOptions {\n /**\n * Relay WebSocket URLs to query. Plural from day one (forward-compat with\n * multi-relay routing); size 1 is typical today. Results are merged and the\n * newest replaceable event across all relays wins.\n */\n relayUrls: string[];\n /** Repository owner's pubkey (hex) — the author of 30617/30618. */\n ownerPubkey: string;\n /** Repository identifier (NIP-34 `d` tag). */\n repoId: string;\n /** Per-relay timeout in milliseconds (default 10000). On timeout the relay contributes whatever it sent so far. */\n timeoutMs?: number;\n /**\n * Git-SHA → Arweave txId resolver used by {@link RemoteState.resolveMissing}.\n * Defaults to the shared GraphQL resolver from @toon-protocol/arweave;\n * injectable for tests.\n */\n resolveSha?: (sha: string, repo: string) => Promise<string | null>;\n /** WebSocket constructor override (defaults to the global WebSocket). */\n webSocketFactory?: WebSocketFactory;\n}\n\n/** Remote repository state assembled from relay events. */\nexport interface RemoteState {\n /** True when a kind:30617 announcement exists (false ⇒ first push). */\n announced: boolean;\n /** Ref map from the latest kind:30618: refname → commit SHA. */\n refs: Map<string, string>;\n /** HEAD symref target (e.g. `refs/heads/main`), or null if unset. */\n headSymref: string | null;\n /** Git SHA → Arweave txId hints from the latest kind:30618 `arweave` tags. */\n shaToTxId: Map<string, string>;\n /** The latest kind:30618 event, or null if the repo has no state yet. */\n refsEvent: NostrEvent | null;\n /** The latest kind:30617 event, or null if the repo is unannounced. */\n announceEvent: NostrEvent | null;\n /** Repository name from the announcement `name` tag. */\n name: string | null;\n /** Announcement `description` tag (falls back to event content). */\n description: string | null;\n /** Relay URLs advertised in the announcement `relays` tag(s). */\n relays: string[];\n /**\n * Declared maintainer pubkeys (hex) from the announcement `maintainers`\n * tag (#287). Does NOT include the owner (an implicit maintainer). Empty\n * when unannounced or owner-only.\n */\n maintainers: string[];\n /**\n * Declared payout pointer (rig#92) from the announcement `payout` tag, or\n * `null` when unannounced or no pointer is declared — no pointer means the\n * serving node keeps 100% of repo-scoped write fees (toon-meta#391).\n */\n payout: PayoutPointer | null;\n /**\n * Resolve SHAs to Arweave txIds: served from the `arweave` tag map when\n * present, otherwise via the GraphQL Git-SHA resolver. SHAs that resolve\n * nowhere are omitted from the returned map.\n */\n resolveMissing(shas: string[]): Promise<Map<string, string>>;\n}\n\n// ---------------------------------------------------------------------------\n// Relay query (NIP-01 REQ → EVENT* → EOSE)\n// ---------------------------------------------------------------------------\n\n/** Validate that a relay URL uses a WebSocket scheme (mirrors rig's url-utils). */\nfunction isValidRelayUrl(url: string): boolean {\n return /^wss?:\\/\\//i.test(url);\n}\n\n/** WHATWG WebSocket OPEN ready state. */\nconst WS_OPEN = 1;\n\nfunction defaultWebSocketFactory(url: string): WebSocketLike {\n const ctor = (\n globalThis as { WebSocket?: new (url: string) => WebSocketLike }\n ).WebSocket;\n if (!ctor) {\n throw new Error(\n 'No global WebSocket constructor (Node >= 22 required) — pass webSocketFactory'\n );\n }\n return new ctor(url);\n}\n\n/**\n * Decode a relay EVENT payload, tolerating every encoding seen in the wild:\n * inline object (standard NIP-01), double-JSON-encoded string (devnet relay\n * serves the event as a JSON string containing the event JSON), or a\n * TOON-encoded string (rig's `decodeToonMessage` path).\n */\nfunction decodeEventPayload(payload: unknown): NostrEvent | null {\n if (payload !== null && typeof payload === 'object') {\n return payload as NostrEvent;\n }\n if (typeof payload !== 'string') {\n return null;\n }\n try {\n const parsed: unknown = JSON.parse(payload);\n if (parsed !== null && typeof parsed === 'object') {\n return parsed as NostrEvent;\n }\n } catch {\n // Not JSON — fall through to TOON\n }\n try {\n return decodeToon(payload) as unknown as NostrEvent;\n } catch {\n return null;\n }\n}\n\n/**\n * Query one relay: send a REQ, collect EVENTs until EOSE, then CLOSE.\n * Mirrors rig's `queryRelay` (partial results on timeout / early close).\n * Exported for reuse by the network-bootstrap discovery (kind:10032).\n */\nexport function queryRelay(\n relayUrl: string,\n filter: NostrFilter,\n timeoutMs: number,\n webSocketFactory: WebSocketFactory\n): Promise<NostrEvent[]> {\n return new Promise((resolve, reject) => {\n const events: NostrEvent[] = [];\n const subId = `git-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n let ws: WebSocketLike;\n // eslint-disable-next-line prefer-const -- assigned after `settle` is defined\n let timeoutHandle: ReturnType<typeof setTimeout>;\n let settled = false;\n\n const settle = (outcome: 'resolve' | 'reject', error?: Error) => {\n if (settled) return;\n settled = true;\n clearTimeout(timeoutHandle);\n try {\n if (ws.readyState === WS_OPEN) {\n ws.send(JSON.stringify(['CLOSE', subId]));\n }\n ws.close();\n } catch {\n // Ignore close errors\n }\n if (outcome === 'reject') {\n reject(error);\n } else {\n resolve(events);\n }\n };\n\n if (!isValidRelayUrl(relayUrl)) {\n reject(\n new Error(\n `Invalid relay URL protocol (must be ws:// or wss://): ${relayUrl}`\n )\n );\n return;\n }\n\n try {\n ws = webSocketFactory(relayUrl);\n } catch (err) {\n reject(new Error(`Failed to connect to relay ${relayUrl}: ${String(err)}`));\n return;\n }\n\n timeoutHandle = setTimeout(() => {\n // Resolve with whatever we collected so far (partial results)\n settle('resolve');\n }, timeoutMs);\n\n ws.addEventListener('open', () => {\n ws.send(JSON.stringify(['REQ', subId, filter]));\n });\n\n ws.addEventListener('message', (msgEvent: { data?: unknown }) => {\n try {\n const msg = JSON.parse(String(msgEvent.data)) as unknown[];\n if (!Array.isArray(msg) || msg.length < 2) return;\n\n const msgType = msg[0];\n if (msgType === 'EVENT' && msg[1] === subId && msg[2] !== undefined) {\n const event = decodeEventPayload(msg[2]);\n if (event) events.push(event);\n } else if (msgType === 'EOSE' && msg[1] === subId) {\n settle('resolve');\n }\n } catch {\n // Ignore parse errors for individual messages\n }\n });\n\n ws.addEventListener('error', (event: { message?: unknown }) => {\n const detail =\n typeof event === 'object' && event !== null && 'message' in event\n ? String(event.message)\n : 'unknown';\n settle(\n 'reject',\n new Error(`WebSocket error connecting to ${relayUrl}: ${detail}`)\n );\n });\n\n ws.addEventListener('close', () => {\n // If we haven't settled yet, resolve with what we have\n settle('resolve');\n });\n });\n}\n\n// ---------------------------------------------------------------------------\n// NIP-33 replaceable selection + tag parsing\n// ---------------------------------------------------------------------------\n\n/**\n * Pick the winning replaceable event: highest created_at, ties broken by\n * lowest id (NIP-01 replaceable-event convention).\n */\nfunction latestReplaceable(events: NostrEvent[]): NostrEvent | null {\n let winner: NostrEvent | null = null;\n for (const event of events) {\n if (\n winner === null ||\n event.created_at > winner.created_at ||\n (event.created_at === winner.created_at && event.id < winner.id)\n ) {\n winner = event;\n }\n }\n return winner;\n}\n\n/** Get the first value for a tag name. */\nfunction getTagValue(tags: string[][], name: string): string | undefined {\n const tag = tags.find((t) => t[0] === name);\n return tag?.[1];\n}\n\n/** Maximum number of refs parsed from a single kind:30618 event (mirrors views). */\nconst MAX_REFS_PER_EVENT = 1000;\n\n/** Symref prefix used in `HEAD` tags: `[\"HEAD\", \"ref: refs/heads/main\"]`. */\nconst SYMREF_PREFIX = 'ref: ';\n\ninterface ParsedRefs {\n refs: Map<string, string>;\n headSymref: string | null;\n shaToTxId: Map<string, string>;\n}\n\n/** Parse a kind:30618 event's `r` / `HEAD` / `arweave` tags. */\nfunction parseRefsEvent(event: NostrEvent): ParsedRefs {\n const refs = new Map<string, string>();\n const shaToTxId = new Map<string, string>();\n let headSymref: string | null = null;\n\n for (const tag of event.tags) {\n const [tagName, v1, v2] = tag;\n if (tagName === 'r' && v1 && v2) {\n if (v1 === 'HEAD' && v2.startsWith(SYMREF_PREFIX)) {\n // Alternate symref spelling: [\"r\", \"HEAD\", \"ref: refs/heads/main\"]\n headSymref = v2.slice(SYMREF_PREFIX.length);\n continue;\n }\n if (refs.size >= MAX_REFS_PER_EVENT) continue;\n refs.set(v1, v2);\n } else if (tagName === 'HEAD' && v1?.startsWith(SYMREF_PREFIX)) {\n // NIP-34 symref tag: [\"HEAD\", \"ref: refs/heads/main\"]\n headSymref = v1.slice(SYMREF_PREFIX.length);\n } else if (tagName === 'arweave' && v1 && v2) {\n shaToTxId.set(v1, v2);\n }\n }\n\n return { refs, headSymref, shaToTxId };\n}\n\n// ---------------------------------------------------------------------------\n// fetchRemoteState\n// ---------------------------------------------------------------------------\n\n/**\n * Fetch the remote repository state from the relay(s).\n *\n * Sends one REQ per relay for kind:30617 + kind:30618 with\n * `authors=[ownerPubkey]`, `#d=[repoId]`, collects until EOSE (or timeout),\n * and reduces to the latest replaceable event per kind.\n *\n * Resolves as long as at least one relay answers; throws only when every\n * relay fails. Events from other authors / repos are ignored (defense\n * against misbehaving relays that over-return).\n */\nexport async function fetchRemoteState(\n options: FetchRemoteStateOptions\n): Promise<RemoteState> {\n const {\n relayUrls,\n ownerPubkey,\n repoId,\n timeoutMs = 10000,\n resolveSha = resolveGitSha,\n webSocketFactory = defaultWebSocketFactory,\n } = options;\n\n if (relayUrls.length === 0) {\n throw new Error('fetchRemoteState: relayUrls must not be empty');\n }\n if (!ownerPubkey) {\n throw new Error('fetchRemoteState: ownerPubkey is required');\n }\n if (!repoId) {\n throw new Error('fetchRemoteState: repoId is required');\n }\n\n const filter: NostrFilter = {\n kinds: [REPOSITORY_ANNOUNCEMENT_KIND, REPOSITORY_STATE_KIND],\n authors: [ownerPubkey],\n '#d': [repoId],\n };\n\n const results = await Promise.allSettled(\n relayUrls.map((url) => queryRelay(url, filter, timeoutMs, webSocketFactory))\n );\n\n const failures: string[] = [];\n const byId = new Map<string, NostrEvent>();\n for (const result of results) {\n if (result.status === 'rejected') {\n failures.push(String((result.reason as Error)?.message ?? result.reason));\n continue;\n }\n for (const event of result.value) {\n // Only trust events from the repo owner for this repo — relays are\n // untrusted and may over-return.\n if (event.pubkey !== ownerPubkey) continue;\n if (getTagValue(event.tags, 'd') !== repoId) continue;\n if (typeof event.id === 'string' && !byId.has(event.id)) {\n byId.set(event.id, event);\n }\n }\n }\n\n if (failures.length === relayUrls.length) {\n throw new Error(\n `fetchRemoteState: all ${relayUrls.length} relay(s) failed: ${failures.join('; ')}`\n );\n }\n\n const events = [...byId.values()];\n const refsEvent = latestReplaceable(\n events.filter((e) => e.kind === REPOSITORY_STATE_KIND)\n );\n const announceEvent = latestReplaceable(\n events.filter((e) => e.kind === REPOSITORY_ANNOUNCEMENT_KIND)\n );\n\n const { refs, headSymref, shaToTxId } = refsEvent\n ? parseRefsEvent(refsEvent)\n : {\n refs: new Map<string, string>(),\n headSymref: null,\n shaToTxId: new Map<string, string>(),\n };\n\n // Seed the shared resolver cache so later resolveGitSha calls (here or in\n // any other consumer of @toon-protocol/arweave) skip GraphQL for known SHAs.\n if (shaToTxId.size > 0) {\n seedShaCache(\n [...shaToTxId].map(\n ([sha, txId]) => [shaCacheKey(sha, repoId), txId] as [string, string]\n )\n );\n }\n\n // Announcement metadata (mirrors views' parseRepoAnnouncement defaults).\n const name = announceEvent\n ? (getTagValue(announceEvent.tags, 'name') ?? null)\n : null;\n const description = announceEvent\n ? (getTagValue(announceEvent.tags, 'description') ?? announceEvent.content)\n : null;\n // NIP-34 announcement relays tag is multi-valued: [\"relays\", url1, url2, …]\n const relays: string[] = [];\n if (announceEvent) {\n for (const tag of announceEvent.tags) {\n if (tag[0] === 'relays') {\n relays.push(...tag.slice(1).filter((url) => url.length > 0));\n }\n }\n }\n // Declared maintainers (#287): the `maintainers` tag on the 30617. Owner is\n // an implicit maintainer and is NOT listed here.\n const maintainers = announceEvent\n ? parseMaintainers(announceEvent.tags)\n : [];\n // Declared payout pointer (rig#92): the `payout` tag on the 30617.\n const payout = announceEvent ? parsePayout(announceEvent.tags) : null;\n\n const resolveMissing = async (\n shas: string[]\n ): Promise<Map<string, string>> => {\n const resolved = new Map<string, string>();\n const missing: string[] = [];\n for (const sha of new Set(shas)) {\n const known = shaToTxId.get(sha);\n if (known !== undefined) {\n resolved.set(sha, known);\n } else {\n missing.push(sha);\n }\n }\n const lookups = await Promise.all(\n missing.map(\n async (sha) => [sha, await resolveSha(sha, repoId)] as const\n )\n );\n for (const [sha, txId] of lookups) {\n if (txId) resolved.set(sha, txId);\n }\n return resolved;\n };\n\n return {\n announced: announceEvent !== null,\n refs,\n headSymref,\n shaToTxId,\n refsEvent,\n announceEvent,\n name,\n description,\n relays,\n maintainers,\n payout,\n resolveMissing,\n };\n}\n",null,null],"mappings":"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","names":["REPOSITORY_ANNOUNCEMENT_KIND","REPOSITORY_ANNOUNCEMENT_KIND"]}
@@ -4,13 +4,59 @@ import {
4
4
  buildRepoAnnouncement,
5
5
  buildRepoRefs,
6
6
  isValidArweaveTxId
7
- } from "./chunk-2KJJ7LSO.js";
7
+ } from "./chunk-NCEMQUPA.js";
8
8
  import {
9
- EMPTY_BLOB_SHA,
10
- MAX_OBJECT_SIZE,
11
- hashGitObject,
12
9
  uploadChargeFor
13
- } from "./chunk-GVXBUL3C.js";
10
+ } from "./chunk-CXREFXB6.js";
11
+
12
+ // src/objects.ts
13
+ import { createHash } from "crypto";
14
+ var FREE_TIER_MAX_ITEM_BYTES = 107520;
15
+ var MAX_OBJECT_SIZE = FREE_TIER_MAX_ITEM_BYTES;
16
+ var EMPTY_BLOB_SHA = "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391";
17
+ function hashGitObject(type, body) {
18
+ const header = Buffer.from(`${type} ${body.length}\0`);
19
+ const fullObject = Buffer.concat([header, body]);
20
+ const sha = createHash("sha1").update(fullObject).digest("hex");
21
+ return { sha, buffer: fullObject, body };
22
+ }
23
+ function createGitBlob(content) {
24
+ return hashGitObject("blob", Buffer.from(content, "utf-8"));
25
+ }
26
+ function createGitTree(entries) {
27
+ const sorted = [...entries].sort(
28
+ (a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0
29
+ );
30
+ const entryBuffers = [];
31
+ for (const entry of sorted) {
32
+ const modeAndName = Buffer.from(`${entry.mode} ${entry.name}\0`);
33
+ const rawSha = Buffer.from(entry.sha, "hex");
34
+ entryBuffers.push(Buffer.concat([modeAndName, rawSha]));
35
+ }
36
+ return hashGitObject("tree", Buffer.concat(entryBuffers));
37
+ }
38
+ function createGitCommit(opts) {
39
+ const lines = [
40
+ `tree ${opts.treeSha}`,
41
+ ...opts.parentSha ? [`parent ${opts.parentSha}`] : [],
42
+ `author ${opts.authorName} <${opts.authorPubkey}@nostr> ${opts.timestamp} +0000`,
43
+ `committer ${opts.authorName} <${opts.authorPubkey}@nostr> ${opts.timestamp} +0000`,
44
+ "",
45
+ opts.message
46
+ ];
47
+ return hashGitObject("commit", Buffer.from(lines.join("\n"), "utf-8"));
48
+ }
49
+ function createGitTag(opts) {
50
+ const lines = [
51
+ `object ${opts.objectSha}`,
52
+ `type ${opts.objectType}`,
53
+ `tag ${opts.tagName}`,
54
+ `tagger ${opts.taggerName} <${opts.taggerPubkey}@nostr> ${opts.timestamp} +0000`,
55
+ "",
56
+ opts.message
57
+ ];
58
+ return hashGitObject("tag", Buffer.from(lines.join("\n"), "utf-8"));
59
+ }
14
60
 
15
61
  // src/repo-reader.ts
16
62
  import { execFile, spawn } from "child_process";
@@ -1060,14 +1106,6 @@ async function planPush(options) {
1060
1106
  `objects vanished from the local repository during planning: ${missing.join(", ")}`
1061
1107
  );
1062
1108
  }
1063
- const oversize = [];
1064
- for (const stat of stats) {
1065
- if (stat.size > MAX_OBJECT_SIZE) {
1066
- const path = pathBySha.get(stat.sha);
1067
- oversize.push({ ...stat, ...path ? { path } : {} });
1068
- }
1069
- }
1070
- if (oversize.length > 0) throw new OversizeObjectsError(oversize);
1071
1109
  const tipShas = new Set(updates.map((u) => u.localSha));
1072
1110
  const planned = [];
1073
1111
  const skippedEmptyObjects = [];
@@ -1217,6 +1255,12 @@ async function executePush(options) {
1217
1255
  }
1218
1256
 
1219
1257
  export {
1258
+ MAX_OBJECT_SIZE,
1259
+ hashGitObject,
1260
+ createGitBlob,
1261
+ createGitTree,
1262
+ createGitCommit,
1263
+ createGitTag,
1220
1264
  GitError,
1221
1265
  GitRepoReader,
1222
1266
  DEFAULT_CONCURRENCY,
@@ -1247,4 +1291,4 @@ export {
1247
1291
  planPush,
1248
1292
  executePush
1249
1293
  };
1250
- //# sourceMappingURL=chunk-ZZBA5VVL.js.map
1294
+ //# sourceMappingURL=chunk-TJPIE5X4.js.map