@toon-protocol/git 1.0.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.
@@ -0,0 +1,742 @@
1
+ import { c as GitObjectType, P as Publisher, F as FeeRates, b as PublishReceipt } from './publisher-VEIEQHl6.js';
2
+ export { C as COMMENT_KIND, d as GitObject, G as GitObjectUpload, M as MAX_OBJECT_SIZE, R as REPOSITORY_STATE_KIND, S as StatusKind, U as UnsignedEvent, a as UploadReceipt, e as buildComment, f as buildIssue, g as buildPatch, h as buildRepoAnnouncement, i as buildRepoRefs, j as buildStatus, k as createGitBlob, l as createGitCommit, m as createGitTag, n as createGitTree, o as hashGitObject } from './publisher-VEIEQHl6.js';
3
+ import '@toon-protocol/core/nip34';
4
+
5
+ /**
6
+ * GitRepoReader — read a local repository via `execFile` git plumbing.
7
+ *
8
+ * Real git gives perfect fidelity (packfiles, delta chains, exotic history)
9
+ * with zero new deps — no isomorphic-git. Everything here is read-only and
10
+ * injection-safe:
11
+ *
12
+ * - child processes are spawned with `execFile`/`spawn` and argument
13
+ * ARRAYS — never a shell, never string interpolation;
14
+ * - every caller-supplied revision/range is validated against strict
15
+ * regexes that (among other things) reject a leading `-`, so a value
16
+ * like `--upload-pack=…` can never be parsed as an option;
17
+ * - `--` terminators are appended where git supports them so nothing
18
+ * user-supplied can be re-interpreted as a pathspec.
19
+ *
20
+ * Push planning/publishing live in follow-up tickets of epic
21
+ * toon-client#222 — this module only reads.
22
+ */
23
+
24
+ /** A single ref from `git for-each-ref` (branches + tags). */
25
+ interface GitRef {
26
+ /** Full refname, e.g. `refs/heads/main` or `refs/tags/v1.0.0`. */
27
+ refname: string;
28
+ /**
29
+ * SHA the ref points at. For annotated tags this is the TAG object's SHA
30
+ * (the peeled commit is in {@link peeledSha}); for branches and
31
+ * lightweight tags it is the commit SHA.
32
+ */
33
+ sha: string;
34
+ /** Type of the referenced object: `commit`, or `tag` for annotated tags. */
35
+ type: GitObjectType;
36
+ /** For annotated tags: the peeled (target) object SHA. */
37
+ peeledSha?: string;
38
+ }
39
+ /** Result of {@link GitRepoReader.listRefs}. */
40
+ interface RepoRefs {
41
+ /**
42
+ * Full refname HEAD points at (e.g. `refs/heads/main`), or `undefined`
43
+ * when HEAD is detached.
44
+ */
45
+ head?: string;
46
+ refs: GitRef[];
47
+ }
48
+ /** One object streamed out of `git cat-file --batch`. */
49
+ interface ReadGitObject {
50
+ /** Full 40-hex SHA-1. */
51
+ sha: string;
52
+ type: GitObjectType;
53
+ /** Raw object body (content only, no envelope header). May be binary. */
54
+ body: Buffer;
55
+ }
56
+ /** Result of {@link GitRepoReader.readObjects}. */
57
+ interface ReadObjectsResult {
58
+ /** Objects found, in input order (minus missing ones). */
59
+ objects: ReadGitObject[];
60
+ /** Requested SHAs not present in the repository. */
61
+ missing: string[];
62
+ }
63
+ /** One object from `rev-list --objects`: SHA plus the path it was reached by. */
64
+ interface ObjectWithPath {
65
+ /** Full 40-hex SHA-1. */
66
+ sha: string;
67
+ /**
68
+ * Path the object was first reached by (blobs and non-root trees);
69
+ * `undefined` for commits, root trees, and tag objects.
70
+ */
71
+ path?: string;
72
+ }
73
+ /** One object's metadata from `cat-file --batch-check`. */
74
+ interface ObjectStat {
75
+ sha: string;
76
+ type: GitObjectType;
77
+ /** Object body size in bytes (content only, no envelope header). */
78
+ size: number;
79
+ }
80
+ /** Result of {@link GitRepoReader.statObjects}. */
81
+ interface StatObjectsResult {
82
+ /** Stats found, in input order (minus missing ones). */
83
+ objects: ObjectStat[];
84
+ /** Requested SHAs not present in the repository. */
85
+ missing: string[];
86
+ }
87
+ /** Error from a git child process, carrying exit code and stderr. */
88
+ declare class GitError extends Error {
89
+ /** Process exit code (undefined when the process failed to spawn). */
90
+ readonly exitCode: number | undefined;
91
+ /** Captured stderr, trimmed. */
92
+ readonly stderr: string;
93
+ constructor(message: string,
94
+ /** Process exit code (undefined when the process failed to spawn). */
95
+ exitCode: number | undefined,
96
+ /** Captured stderr, trimmed. */
97
+ stderr: string);
98
+ }
99
+ /**
100
+ * Read-only view of a local git repository via git plumbing commands.
101
+ *
102
+ * All methods throw {@link GitError} when the underlying git process fails
103
+ * unexpectedly, and plain `Error` when a caller-supplied argument fails
104
+ * validation (before any process is spawned).
105
+ */
106
+ declare class GitRepoReader {
107
+ /** Absolute or relative path to the repository worktree (or .git dir). */
108
+ readonly repoPath: string;
109
+ constructor(
110
+ /** Absolute or relative path to the repository worktree (or .git dir). */
111
+ repoPath: string);
112
+ /** Run git with argument-array safety; resolves stdout as UTF-8. */
113
+ private git;
114
+ /**
115
+ * List all branches and tags plus the symbolic HEAD.
116
+ *
117
+ * Annotated tags report the tag object's SHA/type with the peeled target
118
+ * in `peeledSha`. A detached HEAD is tolerated (`head` is `undefined`).
119
+ */
120
+ listRefs(): Promise<RepoRefs>;
121
+ /**
122
+ * SHAs of every object reachable from `want` but not from `have`
123
+ * (`git rev-list --objects <want…> --not <have…>`), i.e. the push delta.
124
+ *
125
+ * Haves that don't exist locally (e.g. remote tips we never fetched) are
126
+ * filtered out first via one `cat-file --batch-check` pass — rev-list
127
+ * would otherwise die on them.
128
+ */
129
+ objectsBetween(want: string[], have: string[]): Promise<string[]>;
130
+ /**
131
+ * Like {@link objectsBetween} but keeps the path each object was reached
132
+ * by (`rev-list --objects` emits `<sha> <path>` for blobs and non-root
133
+ * trees) — used by push planning to report actionable oversize errors.
134
+ */
135
+ objectsBetweenWithPaths(want: string[], have: string[]): Promise<ObjectWithPath[]>;
136
+ /** Run git feeding `input` on stdin; resolves collected stdout bytes. */
137
+ private runWithStdin;
138
+ /** Of the given revisions, keep only those resolvable locally. */
139
+ private filterExisting;
140
+ /** One `cat-file --batch-check` pass; returns names reported missing. */
141
+ private batchCheck;
142
+ /**
143
+ * Object metadata (type + body size) for a batch of SHAs via one
144
+ * `cat-file --batch-check` pass — no bodies are read. Missing objects are
145
+ * reported, not thrown.
146
+ */
147
+ statObjects(shas: string[]): Promise<StatObjectsResult>;
148
+ /**
149
+ * Read raw object bodies via a single streaming `git cat-file --batch`
150
+ * child process. Bodies may be binary and are parsed size-driven across
151
+ * chunk boundaries. Missing objects are reported, not thrown.
152
+ */
153
+ readObjects(shas: string[]): Promise<ReadObjectsResult>;
154
+ /**
155
+ * `git merge-base --is-ancestor <a> <b>` — true when `a` is an ancestor
156
+ * of `b` (fast-forward check / force detection). Exit codes other than
157
+ * 0/1 (e.g. unknown revisions) throw.
158
+ */
159
+ isAncestor(a: string, b: string): Promise<boolean>;
160
+ /**
161
+ * `git format-patch --stdout <range>` — the full mbox-formatted patch
162
+ * series text (empty string when the range selects no commits).
163
+ */
164
+ formatPatch(range: string): Promise<string>;
165
+ /**
166
+ * Parent SHAs for a batch of commit SHAs via one
167
+ * `git rev-list --no-walk=unsorted --parents` pass. Root commits map to an
168
+ * empty array. Used to derive the kind:1617 `commit`/`parent-commit` tag
169
+ * pairs for exactly the commits a format-patch series carries.
170
+ */
171
+ commitParents(shas: string[]): Promise<Map<string, string[]>>;
172
+ /**
173
+ * Resolve a ref/revision to a full SHA via `git rev-parse --verify`.
174
+ * Throws {@link GitError} when the name doesn't resolve.
175
+ */
176
+ resolveRef(name: string): Promise<string>;
177
+ }
178
+
179
+ /**
180
+ * Remote repository state reader — the "what does the remote have?" half of
181
+ * `rig push` (epic #222, ticket #225).
182
+ *
183
+ * Queries relay(s) over NIP-01 WebSocket for the repository's NIP-34 state:
184
+ * - kind:30618 (repository state): `r` tags → ref map, `HEAD` symref,
185
+ * `arweave` tags → git SHA → Arweave txId hints.
186
+ * - kind:30617 (repository announcement): presence = the repo exists on
187
+ * TOON (first-push detection) + name/description/relays metadata.
188
+ *
189
+ * Both kinds are NIP-33 parameterized-replaceable: per relay only the latest
190
+ * event per (kind, author, d) survives, but we still pick the newest across
191
+ * relays (highest created_at, ties broken by lowest id per NIP-01).
192
+ *
193
+ * SHAs missing from the `arweave` tag map can be resolved via the shared
194
+ * Arweave GraphQL Git-SHA resolver (@toon-protocol/arweave — the same module
195
+ * the rig SPA uses) through {@link RemoteState.resolveMissing}.
196
+ *
197
+ * Relay payload encodings (mirrors rig's `web/relay-client.ts` decode logic):
198
+ * EVENT payloads arrive as an inline JSON object (standard NIP-01), as a
199
+ * double-JSON-encoded string (devnet relay quirk: a JSON string containing
200
+ * the event JSON), or as a TOON-encoded string. All three are tolerated.
201
+ */
202
+ /** A signed Nostr event as seen on a relay (read side). */
203
+ interface NostrEvent {
204
+ id: string;
205
+ pubkey: string;
206
+ created_at: number;
207
+ kind: number;
208
+ tags: string[][];
209
+ content: string;
210
+ sig: string;
211
+ }
212
+ /**
213
+ * Minimal structural WebSocket type — satisfied by the WHATWG WebSocket
214
+ * global (Node >= 22 / undici, browsers) and by the `ws` package.
215
+ */
216
+ interface WebSocketLike {
217
+ readyState: number;
218
+ send(data: string): void;
219
+ close(): void;
220
+ addEventListener(type: string, listener: (event: never) => void): void;
221
+ }
222
+ /** Factory for WebSocket connections (injectable for tests / `ws` fallback). */
223
+ type WebSocketFactory = (url: string) => WebSocketLike;
224
+ interface FetchRemoteStateOptions {
225
+ /**
226
+ * Relay WebSocket URLs to query. Plural from day one (forward-compat with
227
+ * multi-relay routing); size 1 is typical today. Results are merged and the
228
+ * newest replaceable event across all relays wins.
229
+ */
230
+ relayUrls: string[];
231
+ /** Repository owner's pubkey (hex) — the author of 30617/30618. */
232
+ ownerPubkey: string;
233
+ /** Repository identifier (NIP-34 `d` tag). */
234
+ repoId: string;
235
+ /** Per-relay timeout in milliseconds (default 10000). On timeout the relay contributes whatever it sent so far. */
236
+ timeoutMs?: number;
237
+ /**
238
+ * Git-SHA → Arweave txId resolver used by {@link RemoteState.resolveMissing}.
239
+ * Defaults to the shared GraphQL resolver from @toon-protocol/arweave;
240
+ * injectable for tests.
241
+ */
242
+ resolveSha?: (sha: string, repo: string) => Promise<string | null>;
243
+ /** WebSocket constructor override (defaults to the global WebSocket). */
244
+ webSocketFactory?: WebSocketFactory;
245
+ }
246
+ /** Remote repository state assembled from relay events. */
247
+ interface RemoteState {
248
+ /** True when a kind:30617 announcement exists (false ⇒ first push). */
249
+ announced: boolean;
250
+ /** Ref map from the latest kind:30618: refname → commit SHA. */
251
+ refs: Map<string, string>;
252
+ /** HEAD symref target (e.g. `refs/heads/main`), or null if unset. */
253
+ headSymref: string | null;
254
+ /** Git SHA → Arweave txId hints from the latest kind:30618 `arweave` tags. */
255
+ shaToTxId: Map<string, string>;
256
+ /** The latest kind:30618 event, or null if the repo has no state yet. */
257
+ refsEvent: NostrEvent | null;
258
+ /** The latest kind:30617 event, or null if the repo is unannounced. */
259
+ announceEvent: NostrEvent | null;
260
+ /** Repository name from the announcement `name` tag. */
261
+ name: string | null;
262
+ /** Announcement `description` tag (falls back to event content). */
263
+ description: string | null;
264
+ /** Relay URLs advertised in the announcement `relays` tag(s). */
265
+ relays: string[];
266
+ /**
267
+ * Resolve SHAs to Arweave txIds: served from the `arweave` tag map when
268
+ * present, otherwise via the GraphQL Git-SHA resolver. SHAs that resolve
269
+ * nowhere are omitted from the returned map.
270
+ */
271
+ resolveMissing(shas: string[]): Promise<Map<string, string>>;
272
+ }
273
+ /**
274
+ * Fetch the remote repository state from the relay(s).
275
+ *
276
+ * Sends one REQ per relay for kind:30617 + kind:30618 with
277
+ * `authors=[ownerPubkey]`, `#d=[repoId]`, collects until EOSE (or timeout),
278
+ * and reduces to the latest replaceable event per kind.
279
+ *
280
+ * Resolves as long as at least one relay answers; throws only when every
281
+ * relay fails. Events from other authors / repos are ignored (defense
282
+ * against misbehaving relays that over-return).
283
+ */
284
+ declare function fetchRemoteState(options: FetchRemoteStateOptions): Promise<RemoteState>;
285
+
286
+ /**
287
+ * Push planner/executor — the core of `rig push` (epic #222, ticket #226).
288
+ *
289
+ * `planPush` is network-free (relay/Arweave-wise — it only runs local git
290
+ * plumbing through GitRepoReader plus one injectable async resolver step):
291
+ * it classifies every ref update, computes the object delta against what the
292
+ * remote already stores, hard-errors on oversize objects, and prices the
293
+ * push. The returned {@link PushPlan} carries everything a confirm UI needs.
294
+ *
295
+ * `executePush` spends money: it uploads the planned objects through a
296
+ * {@link Publisher} (ref-tip objects last, so a crashed push never leads to
297
+ * a state where a discoverable tip's history is missing), then publishes ONE
298
+ * cumulative kind:30618 whose `arweave` tags are the MERGE of the remote's
299
+ * existing sha→txId map and the new uploads — kind:30618 is NIP-33
300
+ * replaceable, so dropping prior tags would orphan earlier hints — and whose
301
+ * `r` tags are the full new ref state. On a first push it publishes the
302
+ * kind:30617 announcement before the refs event.
303
+ *
304
+ * Resume safety: uploads are content-addressed (Git-SHA-tagged), so re-running
305
+ * `executePush` after a crash is safe — it consults the merged
306
+ * remote + planned sha→txId map before paying for any upload, and a re-plan
307
+ * with fresh remote state (whose `resolveMissing` finds the already-uploaded
308
+ * objects via GraphQL) skips them entirely.
309
+ */
310
+
311
+ /** A ref update rejected because it is not a fast-forward. */
312
+ interface RejectedRefUpdate {
313
+ refname: string;
314
+ localSha: string;
315
+ remoteSha: string;
316
+ }
317
+ /** Thrown by {@link planPush} when a non-fast-forward update lacks `force`. */
318
+ declare class NonFastForwardError extends Error {
319
+ /** The refs that would need `--force` to update. */
320
+ readonly refs: RejectedRefUpdate[];
321
+ constructor(
322
+ /** The refs that would need `--force` to update. */
323
+ refs: RejectedRefUpdate[]);
324
+ }
325
+ /** One object exceeding {@link MAX_OBJECT_SIZE}. */
326
+ interface OversizeObject {
327
+ sha: string;
328
+ type: GitObjectType;
329
+ /** Body size in bytes. */
330
+ size: number;
331
+ /** Path the object was reached by (blobs / non-root trees), if known. */
332
+ path?: string;
333
+ }
334
+ /**
335
+ * Thrown by {@link planPush} when any object in the delta exceeds the 95KB
336
+ * upload limit (hard error in v1 — the paid blob path is a follow-up spike).
337
+ */
338
+ declare class OversizeObjectsError extends Error {
339
+ /** The offending objects with paths and sizes. */
340
+ readonly objects: OversizeObject[];
341
+ constructor(
342
+ /** The offending objects with paths and sizes. */
343
+ objects: OversizeObject[]);
344
+ }
345
+ /** How a ref moves relative to the remote. */
346
+ type RefUpdateKind =
347
+ /** Ref does not exist on the remote yet. */
348
+ 'new'
349
+ /** Remote tip is an ancestor of the local tip. */
350
+ | 'fast-forward'
351
+ /** Non-fast-forward, allowed because `force` was set. */
352
+ | 'forced'
353
+ /** Local and remote tips already match — nothing to push. */
354
+ | 'up-to-date';
355
+ /** One planned ref update (deletions are out of scope in v1). */
356
+ interface RefUpdate {
357
+ /** Full refname, e.g. `refs/heads/main`. */
358
+ refname: string;
359
+ /** Local tip SHA (tag object SHA for annotated tags). */
360
+ localSha: string;
361
+ /** Remote tip SHA, or null when the ref is new. */
362
+ remoteSha: string | null;
363
+ kind: RefUpdateKind;
364
+ }
365
+ /** One object scheduled for upload. */
366
+ interface PlannedObject {
367
+ sha: string;
368
+ type: GitObjectType;
369
+ /** Body size in bytes (what the upload fee is charged on). */
370
+ size: number;
371
+ /** Path the object was reached by, if any (blobs / non-root trees). */
372
+ path?: string;
373
+ /** True when this SHA is the tip of a planned ref update (uploaded last). */
374
+ isRefTip: boolean;
375
+ }
376
+ /** Pre-push fee estimate — render this in the confirm table. */
377
+ interface PushFeeEstimate {
378
+ /** Number of objects to upload. */
379
+ objectCount: number;
380
+ /** Total bytes across all planned object bodies. */
381
+ totalObjectBytes: number;
382
+ /** Σ size × uploadFeePerByte (smallest asset unit). */
383
+ uploadFee: bigint;
384
+ /** Number of events to publish (refs event + announcement on first push). */
385
+ eventCount: number;
386
+ /** eventCount × eventFee (smallest asset unit). */
387
+ eventFees: bigint;
388
+ /** uploadFee + eventFees. */
389
+ totalFee: bigint;
390
+ }
391
+ /** Everything `executePush` (and a confirm UI) needs. */
392
+ interface PushPlan {
393
+ repoId: string;
394
+ /** Every considered ref with its classification (incl. up-to-date). */
395
+ refUpdates: RefUpdate[];
396
+ /**
397
+ * Full new ref state to publish as `r` tags: the remote's refs overlaid
398
+ * with the planned updates (refs not being pushed are preserved — v1
399
+ * never deletes). Ordered with the HEAD target first, which is what
400
+ * `buildRepoRefs` derives the HEAD symref tag from.
401
+ */
402
+ newRefs: Record<string, string>;
403
+ /** HEAD symref target for the new state (first key of {@link newRefs}). */
404
+ headSymref: string | null;
405
+ /**
406
+ * Objects to upload, dependency-safe order: ref-tip objects last so a
407
+ * crashed push never uploads a tip whose history is missing.
408
+ */
409
+ objects: PlannedObject[];
410
+ /**
411
+ * sha→txId hints known WITHOUT uploading: the remote's `arweave` tags
412
+ * plus anything `resolveMissing` found. Merged into the published
413
+ * kind:30618 so prior hints are never dropped.
414
+ */
415
+ knownShaToTxId: Map<string, string>;
416
+ /** True when no kind:30617 exists yet — executePush announces first. */
417
+ announceNeeded: boolean;
418
+ /** Announcement metadata used when {@link announceNeeded}. */
419
+ announcement: {
420
+ name: string;
421
+ description: string;
422
+ };
423
+ estimate: PushFeeEstimate;
424
+ }
425
+ interface PlanPushOptions {
426
+ repoReader: GitRepoReader;
427
+ remoteState: RemoteState;
428
+ /** Fee rates from `Publisher.getFeeRates()`. */
429
+ feeRates: FeeRates;
430
+ /** Repository identifier (NIP-34 `d` tag). */
431
+ repoId: string;
432
+ /**
433
+ * Full refnames to push (e.g. `['refs/heads/main']`). Defaults to every
434
+ * local branch and tag. Refs that don't exist locally are an error
435
+ * (deletions are out of scope in v1).
436
+ */
437
+ refs?: string[];
438
+ /** Allow non-fast-forward updates (default false → hard error). */
439
+ force?: boolean;
440
+ /** Repo name/description for the first-push announcement. */
441
+ announcement?: {
442
+ name?: string;
443
+ description?: string;
444
+ };
445
+ /**
446
+ * Async resolver for SHAs the remote's `arweave` tags don't cover —
447
+ * consulted before deciding to re-upload. Defaults to
448
+ * `remoteState.resolveMissing` (GraphQL fallback); injectable so the
449
+ * planner core stays testable without network.
450
+ */
451
+ resolveMissing?: (shas: string[]) => Promise<Map<string, string>>;
452
+ }
453
+ /**
454
+ * Classify ref updates, compute the object delta, enforce the size limit,
455
+ * and price the push. Throws {@link NonFastForwardError} /
456
+ * {@link OversizeObjectsError} (both carry structured data for UIs).
457
+ */
458
+ declare function planPush(options: PlanPushOptions): Promise<PushPlan>;
459
+ /** Result of one object-upload step. */
460
+ interface UploadStepResult {
461
+ sha: string;
462
+ txId: string;
463
+ /** 0n when skipped (already uploaded — content-addressed resume). */
464
+ feePaid: bigint;
465
+ /** True when the object was already on Arweave and nothing was paid. */
466
+ skipped: boolean;
467
+ }
468
+ /** Result of the whole push. */
469
+ interface PushResult {
470
+ /** Per-object results, in plan order. */
471
+ uploads: UploadStepResult[];
472
+ /** kind:30617 receipt, or null when the repo was already announced. */
473
+ announceReceipt: PublishReceipt | null;
474
+ /** kind:30618 (cumulative refs + arweave map) receipt. */
475
+ refsReceipt: PublishReceipt;
476
+ /**
477
+ * The full sha→txId map published in the refs event: remote hints +
478
+ * resolver finds + this push's uploads.
479
+ */
480
+ arweaveMap: Map<string, string>;
481
+ /** Total fees actually paid (uploads + events), smallest asset unit. */
482
+ totalFeePaid: bigint;
483
+ }
484
+ interface ExecutePushOptions {
485
+ plan: PushPlan;
486
+ publisher: Publisher;
487
+ /**
488
+ * Remote state — pass a FRESH fetch when resuming after a crash so its
489
+ * `shaToTxId` (and `announced`) reflect what the previous attempt already
490
+ * paid for.
491
+ */
492
+ remoteState: RemoteState;
493
+ repoReader: GitRepoReader;
494
+ /** Relay URLs to publish events to (plural from day one; size 1 today). */
495
+ relayUrls: string[];
496
+ }
497
+ /**
498
+ * Execute a {@link PushPlan}: upload objects (ref tips last), then publish
499
+ * the kind:30617 announcement (first push only) and ONE cumulative
500
+ * kind:30618 whose `arweave` tags merge every known sha→txId hint with the
501
+ * new uploads and whose `r` tags carry the full new ref state.
502
+ *
503
+ * Safe to re-run after a crash: SHAs already present in the merged
504
+ * remote + plan map are skipped without paying.
505
+ */
506
+ declare function executePush(options: ExecutePushOptions): Promise<PushResult>;
507
+
508
+ /**
509
+ * Wire shapes of the toon-clientd `/git/*` control routes (epic #222).
510
+ *
511
+ * These are the JSON request/response types the daemon serves (bigints as
512
+ * decimal strings, Maps as plain records) — defined HERE, in the dependency
513
+ * root, so both sides of the route can share them: the `rig` CLI (#229)
514
+ * consumes them as a plain-fetch client, and `@toon-protocol/client-mcp`
515
+ * (which depends on this package for the planner — the reverse import would
516
+ * be circular) can adopt them for its `control-api.ts` declarations. TYPES
517
+ * ONLY plus two pure serializers; no transport code lives here.
518
+ *
519
+ * Keep in byte-for-byte sync with
520
+ * `packages/client-mcp/src/control-api.ts` (`Git*` shapes) and
521
+ * `packages/client-mcp/src/daemon/routes.ts` (error envelopes: 409
522
+ * `non_fast_forward` carries `refs`, 413 `oversize_objects` carries
523
+ * `objects`, 503 `bootstrapping` / 402 `insufficient_gas` are retryable).
524
+ */
525
+
526
+ /** One planned ref update (JSON-safe as-is). */
527
+ type GitRefUpdate = RefUpdate;
528
+ /** One object scheduled for upload (JSON-safe as-is). */
529
+ type GitPlannedObject = PlannedObject;
530
+ /**
531
+ * `POST /git/estimate` — plan a push (local git plumbing + remote-state read)
532
+ * and price it WITHOUT paying anything. The same body (plus `confirm`) drives
533
+ * `POST /git/push`.
534
+ */
535
+ interface GitEstimateRequest {
536
+ /** Path to the local git repository (worktree or .git dir). Must exist. */
537
+ repoPath: string;
538
+ /** Repository identifier (NIP-34 `d` tag). The daemon identity is the owner. */
539
+ repoId: string;
540
+ /**
541
+ * Full refnames to push (e.g. `["refs/heads/main"]`). Default: every local
542
+ * branch and tag.
543
+ */
544
+ refspecs?: string[];
545
+ /** Allow non-fast-forward updates (default false → 409 `non_fast_forward`). */
546
+ force?: boolean;
547
+ /**
548
+ * Relay URLs to read remote state from and publish to. Plural from day one
549
+ * (forward-compat); defaults to the daemon's config-seeded relay.
550
+ */
551
+ relayUrls?: string[];
552
+ /** Repo name/description for the first-push kind:30617 announcement. */
553
+ announcement?: {
554
+ name?: string;
555
+ description?: string;
556
+ };
557
+ }
558
+ /** Pre-push fee table (all fees in base/micro units, decimal strings). */
559
+ interface GitFeeEstimate {
560
+ objectCount: number;
561
+ totalObjectBytes: number;
562
+ /** Σ size × uploadFeePerByte. */
563
+ uploadFee: string;
564
+ /** Events to publish (refs event + announcement on first push). */
565
+ eventCount: number;
566
+ /** eventCount × per-event fee. */
567
+ eventFees: string;
568
+ /** uploadFee + eventFees. */
569
+ totalFee: string;
570
+ }
571
+ /** Serialized `PushPlan` — everything a confirm UI needs. */
572
+ interface GitEstimateResponse {
573
+ repoId: string;
574
+ refUpdates: GitRefUpdate[];
575
+ /** Full new ref state to publish (HEAD target first). */
576
+ newRefs: Record<string, string>;
577
+ headSymref: string | null;
578
+ objects: GitPlannedObject[];
579
+ /** sha→txId hints known WITHOUT uploading (remote tags + resolver finds). */
580
+ knownShaToTxId: Record<string, string>;
581
+ /** True when no kind:30617 exists yet — the push announces first. */
582
+ announceNeeded: boolean;
583
+ announcement: {
584
+ name: string;
585
+ description: string;
586
+ };
587
+ estimate: GitFeeEstimate;
588
+ }
589
+ /**
590
+ * `POST /git/push` — plan + execute: upload the delta to Arweave and publish
591
+ * the cumulative kind:30618 (+ kind:30617 on first push). PERMANENT + PAID.
592
+ */
593
+ interface GitPushRequest extends GitEstimateRequest {
594
+ /** Must be literally `true` — a push spends channel funds irreversibly. */
595
+ confirm: boolean;
596
+ }
597
+ /** One object-upload step result. */
598
+ interface GitUploadStep {
599
+ sha: string;
600
+ txId: string;
601
+ /** '0' when skipped (already on Arweave — content-addressed resume). */
602
+ feePaid: string;
603
+ skipped: boolean;
604
+ }
605
+ /** Receipt for one published event. */
606
+ interface GitPublishReceipt {
607
+ eventId: string;
608
+ feePaid: string;
609
+ }
610
+ /** Serialized `PushResult` — per-step receipts + total fees actually paid. */
611
+ interface GitPushResponse {
612
+ repoId: string;
613
+ refUpdates: GitRefUpdate[];
614
+ /** Per-object results, in plan order. */
615
+ uploads: GitUploadStep[];
616
+ /** kind:30617 receipt, or null when the repo was already announced. */
617
+ announceReceipt: GitPublishReceipt | null;
618
+ /** kind:30618 (cumulative refs + arweave map) receipt. */
619
+ refsReceipt: GitPublishReceipt;
620
+ /** Full sha→txId map published in the refs event. */
621
+ arweaveMap: Record<string, string>;
622
+ /** Total fees actually paid (uploads + events), base units, decimal. */
623
+ totalFeePaid: string;
624
+ /** The pre-push estimate the push ran under (compare against totalFeePaid). */
625
+ estimate: GitFeeEstimate;
626
+ }
627
+ /** NIP-34 repository address: the owner+id pair behind `a` tags. */
628
+ interface GitRepoAddr {
629
+ /** Repository owner's Nostr pubkey (64-char hex) — author of kind:30617/30618. */
630
+ ownerPubkey: string;
631
+ /** Repository identifier (NIP-34 `d` tag). */
632
+ repoId: string;
633
+ }
634
+ /** `POST /git/issue` — publish a kind:1621 issue against a repo. PAID. */
635
+ interface GitIssueRequest {
636
+ repoAddr: GitRepoAddr;
637
+ /** Issue title (`subject` tag). */
638
+ title: string;
639
+ /** Issue body (Markdown content). */
640
+ body: string;
641
+ /** Labels (`t` tags). */
642
+ labels?: string[];
643
+ }
644
+ /** `POST /git/comment` — publish a kind:1622 comment on an issue/patch. PAID. */
645
+ interface GitCommentRequest {
646
+ repoAddr: GitRepoAddr;
647
+ /** Event id of the issue or patch being commented on. */
648
+ rootEventId: string;
649
+ /** Comment body (Markdown content). */
650
+ body: string;
651
+ /**
652
+ * Pubkey of the TARGET event's author (NIP-34 `p` threading tag — not the
653
+ * comment author). Defaults to the repo owner.
654
+ */
655
+ parentAuthorPubkey?: string;
656
+ /** `e`-tag marker (default 'root': commenting directly on the issue/patch). */
657
+ marker?: 'root' | 'reply';
658
+ }
659
+ /**
660
+ * `POST /git/patch` — publish a kind:1617 patch. Supply EXACTLY ONE of
661
+ * `patchText` (literal `git format-patch` output) or `repoPath`+`range`
662
+ * (the daemon runs `git format-patch --stdout <range>` locally). PAID.
663
+ */
664
+ interface GitPatchRequest {
665
+ repoAddr: GitRepoAddr;
666
+ /** Patch/PR title (`subject` tag). */
667
+ title: string;
668
+ /** Literal patch text. Mutually exclusive with `repoPath`+`range`. */
669
+ patchText?: string;
670
+ /** Local repository to run format-patch in. Requires `range`. */
671
+ repoPath?: string;
672
+ /** Revision range for format-patch (`<rev>`, `<rev>..<rev>`, `<rev>...<rev>`). */
673
+ range?: string;
674
+ /** Commit/parent pairs for `commit`/`parent-commit` tags. */
675
+ commits?: {
676
+ sha: string;
677
+ parentSha: string;
678
+ }[];
679
+ /** Branch name for the `t` tag. */
680
+ branch?: string;
681
+ }
682
+ type GitStatusValue = 'open' | 'applied' | 'closed' | 'draft';
683
+ /** `POST /git/status` — publish a kind:1630-1633 status event. PAID. */
684
+ interface GitStatusRequest {
685
+ repoAddr: GitRepoAddr;
686
+ /** Event id of the issue/patch whose status is being set. */
687
+ targetEventId: string;
688
+ /** open → 1630, applied → 1631, closed → 1632, draft → 1633. */
689
+ status: GitStatusValue;
690
+ /** Pubkey of the target event's author (`p` tag), when known. */
691
+ targetPubkey?: string;
692
+ }
693
+ /**
694
+ * Response of the single-event git publishes (issue/comment/patch/status):
695
+ * a publish receipt plus the NIP-34 kind that was published. Daemon
696
+ * responses extend the full `POST /publish` receipt, so the channel fields
697
+ * (`channelId`/`nonce`/…) are present there; they are optional here because
698
+ * the CLI's standalone path publishes through the embedded client and has
699
+ * no channel wire shape to report.
700
+ */
701
+ interface GitEventResponse {
702
+ /** Event ID as accepted by the relay. */
703
+ eventId: string;
704
+ /** Fee actually paid for this publish, base units, decimal string. */
705
+ feePaid: string;
706
+ /** The NIP-34 kind that was published. */
707
+ kind: number;
708
+ /** Channel the claim was signed against (daemon responses). */
709
+ channelId?: string;
710
+ /** Channel nonce after this publish (daemon responses). */
711
+ nonce?: number;
712
+ /** FULFILL response data (base64), when the backend returned any. */
713
+ data?: string;
714
+ /** Spendable channel balance after this write, when known. */
715
+ channelBalanceAfter?: string;
716
+ }
717
+ /**
718
+ * Uniform error envelope of non-2xx control-route responses. Structured
719
+ * errors put extra fields at the top level: `non_fast_forward` (409) adds
720
+ * `refs`, `oversize_objects` (413) adds `objects`.
721
+ */
722
+ interface GitErrorEnvelope {
723
+ error: string;
724
+ detail?: string;
725
+ /** True when the caller should retry (e.g. daemon still bootstrapping). */
726
+ retryable?: boolean;
727
+ [extra: string]: unknown;
728
+ }
729
+ /** Serialize a plan's fee estimate onto the wire (bigints → strings). */
730
+ declare function serializeFeeEstimate(plan: PushPlan): GitFeeEstimate;
731
+ /** Serialize a PushPlan onto the wire (bigints → strings, Maps → records). */
732
+ declare function serializePushPlan(plan: PushPlan): GitEstimateResponse;
733
+ /**
734
+ * Serialize a standalone {@link PublishReceipt} into the wire shape the
735
+ * daemon's single-event `/git/*` routes answer with, so `--json` consumers
736
+ * see one `GitEventResponse` shape regardless of publisher mode.
737
+ */
738
+ declare function serializeEventReceipt(kind: number, receipt: PublishReceipt): GitEventResponse;
739
+ /** Serialize a PushResult onto the wire (bigints → strings, Maps → records). */
740
+ declare function serializePushResult(plan: PushPlan, result: PushResult): GitPushResponse;
741
+
742
+ export { type ExecutePushOptions, FeeRates, type FetchRemoteStateOptions, type GitCommentRequest, GitError, type GitErrorEnvelope, type GitEstimateRequest, type GitEstimateResponse, type GitEventResponse, type GitFeeEstimate, type GitIssueRequest, GitObjectType, type GitPatchRequest, type GitPlannedObject, type GitPublishReceipt, type GitPushRequest, type GitPushResponse, type GitRef, type GitRefUpdate, type GitRepoAddr, GitRepoReader, type GitStatusRequest, type GitStatusValue, type GitUploadStep, NonFastForwardError, type NostrEvent, type ObjectStat, type ObjectWithPath, type OversizeObject, OversizeObjectsError, type PlanPushOptions, type PlannedObject, PublishReceipt, Publisher, type PushFeeEstimate, type PushPlan, type PushResult, type ReadGitObject, type ReadObjectsResult, type RefUpdate, type RefUpdateKind, type RejectedRefUpdate, type RemoteState, type RepoRefs, type StatObjectsResult, type UploadStepResult, type WebSocketFactory, type WebSocketLike, executePush, fetchRemoteState, planPush, serializeEventReceipt, serializeFeeEstimate, serializePushPlan, serializePushResult };