@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.
- package/README.md +52 -0
- package/dist/chunk-4WFGAICZ.js +707 -0
- package/dist/chunk-4WFGAICZ.js.map +1 -0
- package/dist/chunk-KXXHAUXL.js +109 -0
- package/dist/chunk-KXXHAUXL.js.map +1 -0
- package/dist/chunk-LJA7PPZI.js +144 -0
- package/dist/chunk-LJA7PPZI.js.map +1 -0
- package/dist/chunk-M7O4SEVW.js +56 -0
- package/dist/chunk-M7O4SEVW.js.map +1 -0
- package/dist/chunk-R3JVS6SX.js +345 -0
- package/dist/chunk-R3JVS6SX.js.map +1 -0
- package/dist/chunk-SBMFWVCP.js +265 -0
- package/dist/chunk-SBMFWVCP.js.map +1 -0
- package/dist/cli/rig.d.ts +1 -0
- package/dist/cli/rig.js +1430 -0
- package/dist/cli/rig.js.map +1 -0
- package/dist/index.d.ts +742 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/publisher-VEIEQHl6.d.ts +254 -0
- package/dist/standalone/index.d.ts +272 -0
- package/dist/standalone/index.js +30 -0
- package/dist/standalone/index.js.map +1 -0
- package/dist/standalone-mode-UFMHGUOM.js +132 -0
- package/dist/standalone-mode-UFMHGUOM.js.map +1 -0
- package/package.json +71 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GitError,
|
|
3
|
+
GitRepoReader,
|
|
4
|
+
NonFastForwardError,
|
|
5
|
+
OversizeObjectsError,
|
|
6
|
+
executePush,
|
|
7
|
+
planPush,
|
|
8
|
+
serializeEventReceipt,
|
|
9
|
+
serializeFeeEstimate,
|
|
10
|
+
serializePushPlan,
|
|
11
|
+
serializePushResult
|
|
12
|
+
} from "./chunk-4WFGAICZ.js";
|
|
13
|
+
import {
|
|
14
|
+
fetchRemoteState
|
|
15
|
+
} from "./chunk-R3JVS6SX.js";
|
|
16
|
+
import {
|
|
17
|
+
COMMENT_KIND,
|
|
18
|
+
REPOSITORY_STATE_KIND,
|
|
19
|
+
buildComment,
|
|
20
|
+
buildIssue,
|
|
21
|
+
buildPatch,
|
|
22
|
+
buildRepoAnnouncement,
|
|
23
|
+
buildRepoRefs,
|
|
24
|
+
buildStatus
|
|
25
|
+
} from "./chunk-KXXHAUXL.js";
|
|
26
|
+
import {
|
|
27
|
+
MAX_OBJECT_SIZE,
|
|
28
|
+
createGitBlob,
|
|
29
|
+
createGitCommit,
|
|
30
|
+
createGitTag,
|
|
31
|
+
createGitTree,
|
|
32
|
+
hashGitObject
|
|
33
|
+
} from "./chunk-M7O4SEVW.js";
|
|
34
|
+
export {
|
|
35
|
+
COMMENT_KIND,
|
|
36
|
+
GitError,
|
|
37
|
+
GitRepoReader,
|
|
38
|
+
MAX_OBJECT_SIZE,
|
|
39
|
+
NonFastForwardError,
|
|
40
|
+
OversizeObjectsError,
|
|
41
|
+
REPOSITORY_STATE_KIND,
|
|
42
|
+
buildComment,
|
|
43
|
+
buildIssue,
|
|
44
|
+
buildPatch,
|
|
45
|
+
buildRepoAnnouncement,
|
|
46
|
+
buildRepoRefs,
|
|
47
|
+
buildStatus,
|
|
48
|
+
createGitBlob,
|
|
49
|
+
createGitCommit,
|
|
50
|
+
createGitTag,
|
|
51
|
+
createGitTree,
|
|
52
|
+
executePush,
|
|
53
|
+
fetchRemoteState,
|
|
54
|
+
hashGitObject,
|
|
55
|
+
planPush,
|
|
56
|
+
serializeEventReceipt,
|
|
57
|
+
serializeFeeEstimate,
|
|
58
|
+
serializePushPlan,
|
|
59
|
+
serializePushResult
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { STATUS_OPEN_KIND, STATUS_APPLIED_KIND, STATUS_CLOSED_KIND, STATUS_DRAFT_KIND } from '@toon-protocol/core/nip34';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pure git object construction and SHA-1 envelope hashing.
|
|
5
|
+
*
|
|
6
|
+
* Promoted from `packages/rig/tests/e2e/seed/lib/git-builder.ts` (#223) —
|
|
7
|
+
* the proven seed pipeline builders, now the core of the Git-to-TOON write
|
|
8
|
+
* path. Everything here is pure: no network, no signing, no payments.
|
|
9
|
+
* Upload/publish lives with the Publisher (#226).
|
|
10
|
+
*
|
|
11
|
+
* Git object format: `<type> <size>\0<content>`. The SHA-1 is computed over
|
|
12
|
+
* the full envelope (header + NUL + content); the `body` (content only) is
|
|
13
|
+
* what gets uploaded to Arweave.
|
|
14
|
+
*/
|
|
15
|
+
/** All git object types TOON can carry. */
|
|
16
|
+
type GitObjectType = 'blob' | 'tree' | 'commit' | 'tag';
|
|
17
|
+
interface GitObject {
|
|
18
|
+
/** SHA-1 hex digest computed over full git envelope */
|
|
19
|
+
sha: string;
|
|
20
|
+
/** Full git object (header + null + content) */
|
|
21
|
+
buffer: Buffer;
|
|
22
|
+
/** Body only (content after the null byte) — this is what gets uploaded */
|
|
23
|
+
body: Buffer;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Maximum uploadable git object body size: 95KB safety margin under the
|
|
27
|
+
* 100KB free tier (R10-005). Larger objects are a hard error in v1.
|
|
28
|
+
*/
|
|
29
|
+
declare const MAX_OBJECT_SIZE: number;
|
|
30
|
+
/**
|
|
31
|
+
* Wrap a raw object body in the git envelope (`<type> <size>\0`) and compute
|
|
32
|
+
* its SHA-1. This is exactly what `git hash-object -t <type>` does.
|
|
33
|
+
*/
|
|
34
|
+
declare function hashGitObject(type: GitObjectType, body: Buffer): GitObject;
|
|
35
|
+
/**
|
|
36
|
+
* Construct a git blob object and compute its SHA-1.
|
|
37
|
+
*
|
|
38
|
+
* Format: blob <size>\0<content>
|
|
39
|
+
* SHA is over the full envelope; body is content only (for upload).
|
|
40
|
+
*/
|
|
41
|
+
declare function createGitBlob(content: string): GitObject;
|
|
42
|
+
/**
|
|
43
|
+
* Construct a git tree object from sorted entries.
|
|
44
|
+
*
|
|
45
|
+
* Format: tree <size>\0<entries>
|
|
46
|
+
* Each entry: <mode> <name>\0<20-byte-raw-sha1>
|
|
47
|
+
* Entries MUST be sorted by name (byte-wise).
|
|
48
|
+
*/
|
|
49
|
+
declare function createGitTree(entries: {
|
|
50
|
+
mode: string;
|
|
51
|
+
name: string;
|
|
52
|
+
sha: string;
|
|
53
|
+
}[]): GitObject;
|
|
54
|
+
/**
|
|
55
|
+
* Construct a git commit object.
|
|
56
|
+
*
|
|
57
|
+
* Format: commit <size>\0tree <tree-sha>\n[parent ...]\nauthor ...\ncommitter ...\n\n<message>
|
|
58
|
+
* Tree/parent SHAs are hex-encoded (40 chars) in commits, unlike tree entries.
|
|
59
|
+
*/
|
|
60
|
+
declare function createGitCommit(opts: {
|
|
61
|
+
treeSha: string;
|
|
62
|
+
parentSha?: string;
|
|
63
|
+
authorName: string;
|
|
64
|
+
authorPubkey: string;
|
|
65
|
+
message: string;
|
|
66
|
+
timestamp: number;
|
|
67
|
+
}): GitObject;
|
|
68
|
+
/**
|
|
69
|
+
* Construct an annotated git tag object.
|
|
70
|
+
*
|
|
71
|
+
* Format: tag <size>\0object <sha>\ntype <type>\ntag <name>\ntagger ...\n\n<message>
|
|
72
|
+
* The tagged object is usually a commit, but git allows tagging any object
|
|
73
|
+
* type (including another tag).
|
|
74
|
+
*/
|
|
75
|
+
declare function createGitTag(opts: {
|
|
76
|
+
/** SHA-1 of the object being tagged (hex, 40 chars) */
|
|
77
|
+
objectSha: string;
|
|
78
|
+
/** Type of the tagged object (usually 'commit') */
|
|
79
|
+
objectType: GitObjectType;
|
|
80
|
+
/** Tag name, e.g. 'v1.0.0' */
|
|
81
|
+
tagName: string;
|
|
82
|
+
taggerName: string;
|
|
83
|
+
taggerPubkey: string;
|
|
84
|
+
message: string;
|
|
85
|
+
timestamp: number;
|
|
86
|
+
}): GitObject;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Pure NIP-34 event builders for the Git-to-TOON write path.
|
|
90
|
+
*
|
|
91
|
+
* Promoted from `packages/rig/tests/e2e/seed/lib/event-builders.ts` (#223).
|
|
92
|
+
* All builders return UnsignedEvent — the caller signs with their keypair
|
|
93
|
+
* via finalizeEvent() and publishes through a Publisher (#226). Tag
|
|
94
|
+
* structures follow the NIP-34 spec and `@toon-protocol/core/nip34`.
|
|
95
|
+
*/
|
|
96
|
+
|
|
97
|
+
/** Repository State (refs) — replaceable, pairs with kind:30617 via `d` tag. */
|
|
98
|
+
declare const REPOSITORY_STATE_KIND = 30618;
|
|
99
|
+
/** Comment on an issue or patch (NIP-22 style threading within NIP-34). */
|
|
100
|
+
declare const COMMENT_KIND = 1622;
|
|
101
|
+
interface UnsignedEvent {
|
|
102
|
+
kind: number;
|
|
103
|
+
content: string;
|
|
104
|
+
tags: string[][];
|
|
105
|
+
created_at: number;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Build a kind:30617 repository announcement event.
|
|
109
|
+
*
|
|
110
|
+
* @param repoId - Repository identifier (d tag)
|
|
111
|
+
* @param name - Human-readable repository name
|
|
112
|
+
* @param description - Repository description
|
|
113
|
+
*/
|
|
114
|
+
declare function buildRepoAnnouncement(repoId: string, name: string, description: string): UnsignedEvent;
|
|
115
|
+
/**
|
|
116
|
+
* Build a kind:30618 repository refs/state event.
|
|
117
|
+
*
|
|
118
|
+
* @param repoId - Repository identifier (d tag, matches kind:30617)
|
|
119
|
+
* @param refs - Map of ref paths to commit SHAs (e.g., { 'refs/heads/main': 'abc123' })
|
|
120
|
+
* @param arweaveMap - Map of git SHAs to Arweave transaction IDs
|
|
121
|
+
*/
|
|
122
|
+
declare function buildRepoRefs(repoId: string, refs: Record<string, string>, arweaveMap?: Record<string, string>): UnsignedEvent;
|
|
123
|
+
/**
|
|
124
|
+
* Build a kind:1621 issue event.
|
|
125
|
+
*
|
|
126
|
+
* @param repoOwnerPubkey - Pubkey of the repository owner
|
|
127
|
+
* @param repoId - Repository identifier
|
|
128
|
+
* @param title - Issue title (subject tag)
|
|
129
|
+
* @param body - Issue body (Markdown content)
|
|
130
|
+
* @param labels - Optional labels (t tags)
|
|
131
|
+
*/
|
|
132
|
+
declare function buildIssue(repoOwnerPubkey: string, repoId: string, title: string, body: string, labels?: string[]): UnsignedEvent;
|
|
133
|
+
/**
|
|
134
|
+
* Build a kind:1622 comment event.
|
|
135
|
+
*
|
|
136
|
+
* @param repoOwnerPubkey - Pubkey of the repository owner
|
|
137
|
+
* @param repoId - Repository identifier
|
|
138
|
+
* @param issueOrPrEventId - Event ID of the issue or PR being commented on
|
|
139
|
+
* @param authorPubkey - Pubkey of the issue/PR author (NIP-34 `p` tag for threading), NOT the comment author
|
|
140
|
+
* @param body - Comment body (Markdown content)
|
|
141
|
+
* @param marker - Event reference marker: 'root' or 'reply' (default: 'reply')
|
|
142
|
+
*/
|
|
143
|
+
declare function buildComment(repoOwnerPubkey: string, repoId: string, issueOrPrEventId: string, authorPubkey: string, body: string, marker?: 'root' | 'reply'): UnsignedEvent;
|
|
144
|
+
/**
|
|
145
|
+
* Build a kind:1617 patch event.
|
|
146
|
+
*
|
|
147
|
+
* @param repoOwnerPubkey - Pubkey of the repository owner
|
|
148
|
+
* @param repoId - Repository identifier
|
|
149
|
+
* @param title - Patch/PR title (subject tag)
|
|
150
|
+
* @param commits - Array of { sha, parentSha } for commit and parent-commit tags
|
|
151
|
+
* @param branchTag - Branch name for the t tag
|
|
152
|
+
* @param content - Real `git format-patch` text (NIP-34 patch body); defaults
|
|
153
|
+
* to '' for callers that only reference commits by tag
|
|
154
|
+
*/
|
|
155
|
+
declare function buildPatch(repoOwnerPubkey: string, repoId: string, title: string, commits: {
|
|
156
|
+
sha: string;
|
|
157
|
+
parentSha: string;
|
|
158
|
+
}[], branchTag?: string, content?: string): UnsignedEvent;
|
|
159
|
+
/** Status kinds: 1630 open, 1631 applied/merged, 1632 closed, 1633 draft. */
|
|
160
|
+
type StatusKind = typeof STATUS_OPEN_KIND | typeof STATUS_APPLIED_KIND | typeof STATUS_CLOSED_KIND | typeof STATUS_DRAFT_KIND;
|
|
161
|
+
/**
|
|
162
|
+
* Build a status event (kind 1630-1633).
|
|
163
|
+
*
|
|
164
|
+
* @param targetEventId - Event ID of the patch, PR, or issue being updated
|
|
165
|
+
* @param statusKind - One of 1630 (open), 1631 (applied), 1632 (closed), 1633 (draft)
|
|
166
|
+
* @param targetPubkey - Optional pubkey of the target event author (p tag per NIP-34 StatusEvent)
|
|
167
|
+
*/
|
|
168
|
+
declare function buildStatus(targetEventId: string, statusKind: StatusKind, targetPubkey?: string): UnsignedEvent;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Publisher — the paid-write seam between push planning (this package) and
|
|
172
|
+
* the two transport implementations that follow it in epic #222:
|
|
173
|
+
*
|
|
174
|
+
* 1. daemon (#227): client-mcp ControlClient → toon-clientd loopback
|
|
175
|
+
* /git/* routes, backed by `ClientRunner.publish`/`uploadBlob` (the
|
|
176
|
+
* production paid-publish path — apex channel, signBalanceProof, flat
|
|
177
|
+
* per-event fee).
|
|
178
|
+
* 2. standalone (#228): an embedded ToonClient constructed from a
|
|
179
|
+
* mnemonic, uploading git objects as kind:5094 store writes with
|
|
180
|
+
* Git-SHA/Git-Type/Repo tags (the proven seed-pipeline shape) and
|
|
181
|
+
* publishing NIP-34 events through the relay.
|
|
182
|
+
*
|
|
183
|
+
* The interface is deliberately minimal so both fit:
|
|
184
|
+
*
|
|
185
|
+
* - `uploadGitObject` takes the raw object body (content only — no git
|
|
186
|
+
* envelope header; the store re-derives the SHA envelope from the
|
|
187
|
+
* Git-Type/Git-SHA tags) and returns the Arweave txId plus the fee paid.
|
|
188
|
+
* - `publishEvent` takes an UNSIGNED event template — signing stays with
|
|
189
|
+
* the implementation (the daemon-held key never leaves the daemon; the
|
|
190
|
+
* standalone impl signs with its own keypair). Relay URLs are plural
|
|
191
|
+
* from day one (forward-compat for parked #84/#92; size 1 today).
|
|
192
|
+
* - `getFeeRates` exposes what `planPush` needs for the pre-push estimate:
|
|
193
|
+
* a per-byte upload rate (seed pipeline bids bytes × rate) and a flat
|
|
194
|
+
* per-event publish fee (the daemon charges `apex.feePerEvent` regardless
|
|
195
|
+
* of event size).
|
|
196
|
+
*
|
|
197
|
+
* All fees are bigint in the smallest asset unit (matches `@toon-protocol/client`
|
|
198
|
+
* channel math); HTTP transports serialize them as strings and convert at
|
|
199
|
+
* the boundary.
|
|
200
|
+
*/
|
|
201
|
+
|
|
202
|
+
/** A git object queued for upload to Arweave via the paid store path. */
|
|
203
|
+
interface GitObjectUpload {
|
|
204
|
+
/** Full 40-hex SHA-1 (over the git envelope — see `hashGitObject`). */
|
|
205
|
+
sha: string;
|
|
206
|
+
type: GitObjectType;
|
|
207
|
+
/** Raw object body (content only, no `<type> <size>\0` header). */
|
|
208
|
+
body: Buffer;
|
|
209
|
+
/** Repository identifier — becomes the `Repo` tag on the store write. */
|
|
210
|
+
repoId: string;
|
|
211
|
+
}
|
|
212
|
+
/** Receipt for one uploaded git object. */
|
|
213
|
+
interface UploadReceipt {
|
|
214
|
+
/** Arweave transaction ID the object is retrievable under. */
|
|
215
|
+
txId: string;
|
|
216
|
+
/** Fee paid for this upload, in the smallest asset unit. */
|
|
217
|
+
feePaid: bigint;
|
|
218
|
+
}
|
|
219
|
+
/** Receipt for one published Nostr event. */
|
|
220
|
+
interface PublishReceipt {
|
|
221
|
+
/** Event ID as accepted by the relay(s). */
|
|
222
|
+
eventId: string;
|
|
223
|
+
/** Fee paid for this publish, in the smallest asset unit. */
|
|
224
|
+
feePaid: bigint;
|
|
225
|
+
}
|
|
226
|
+
/** Fee rates used by `planPush` for the pre-push estimate. */
|
|
227
|
+
interface FeeRates {
|
|
228
|
+
/** Upload cost per body byte (smallest asset unit). */
|
|
229
|
+
uploadFeePerByte: bigint;
|
|
230
|
+
/** Flat cost per published event (smallest asset unit). */
|
|
231
|
+
eventFee: bigint;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Paid transport for `executePush` — implemented by the daemon route
|
|
235
|
+
* handlers (#227) and the standalone embedded client (#228).
|
|
236
|
+
*
|
|
237
|
+
* Implementations must be safe to call sequentially (uploads are issued one
|
|
238
|
+
* at a time in dependency-safe order) and should throw on any failure —
|
|
239
|
+
* `executePush` does not retry; a crashed push is resumed by re-planning
|
|
240
|
+
* (content-addressed uploads make the retry idempotent).
|
|
241
|
+
*/
|
|
242
|
+
interface Publisher {
|
|
243
|
+
/** Current fee rates for estimation (may be queried before every plan). */
|
|
244
|
+
getFeeRates(): Promise<FeeRates>;
|
|
245
|
+
/** Upload one git object body to Arweave; paid. */
|
|
246
|
+
uploadGitObject(upload: GitObjectUpload): Promise<UploadReceipt>;
|
|
247
|
+
/**
|
|
248
|
+
* Sign (implementation-held key) and pay-to-publish one event to the
|
|
249
|
+
* given relay(s).
|
|
250
|
+
*/
|
|
251
|
+
publishEvent(event: UnsignedEvent, relayUrls: string[]): Promise<PublishReceipt>;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export { COMMENT_KIND as C, type FeeRates as F, type GitObjectUpload as G, MAX_OBJECT_SIZE as M, type Publisher as P, REPOSITORY_STATE_KIND as R, type StatusKind as S, type UnsignedEvent as U, type UploadReceipt as a, type PublishReceipt as b, type GitObjectType as c, type GitObject as d, buildComment as e, buildIssue as f, buildPatch as g, buildRepoAnnouncement as h, buildRepoRefs as i, buildStatus as j, createGitBlob as k, createGitCommit as l, createGitTag as m, createGitTree as n, hashGitObject as o };
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { ToonClientConfig, SignedBalanceProof, PublishEventResult } from '@toon-protocol/client';
|
|
2
|
+
import { U as UnsignedEvent, P as Publisher, F as FeeRates, G as GitObjectUpload, a as UploadReceipt, b as PublishReceipt } from '../publisher-VEIEQHl6.js';
|
|
3
|
+
import '@toon-protocol/core/nip34';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* StandalonePublisher — impl 2 of the {@link Publisher} seam (#228): an
|
|
7
|
+
* EMBEDDED ToonClient constructed from the caller's config (mnemonic +
|
|
8
|
+
* account index, the exact `packages/client/src/config.ts` shape) instead of
|
|
9
|
+
* routing through a running toon-clientd. Made for CI jobs, servers, and
|
|
10
|
+
* one-shot CLI runs where no daemon exists.
|
|
11
|
+
*
|
|
12
|
+
* Paid-write mechanics mirror the production daemon path
|
|
13
|
+
* (`client-mcp/src/daemon/client-runner.ts`) and the proven seed pipeline
|
|
14
|
+
* (`rig/tests/e2e/seed/lib/{publish,git-builder}.ts`):
|
|
15
|
+
*
|
|
16
|
+
* - one signed balance-proof CLAIM per write (`signBalanceProof(channelId,
|
|
17
|
+
* fee)` — the ChannelManager accumulates the cumulative watermark),
|
|
18
|
+
* - publishes route to the relay write destination with a flat per-event
|
|
19
|
+
* fee (the daemon's `feePerEvent` convention),
|
|
20
|
+
* - git objects upload as kind:5094 store writes tagged
|
|
21
|
+
* Git-SHA/Git-Type/Repo, priced at bytes × per-byte rate (the seed
|
|
22
|
+
* pipeline's bid), routed to the store destination via `proxyPath:
|
|
23
|
+
* '/store'`, with the Arweave txId decoded from the FULFILL data.
|
|
24
|
+
*
|
|
25
|
+
* Because the embedded client signs claims on the SAME channels a daemon on
|
|
26
|
+
* the same identity would, every paid operation is preceded by the nonce
|
|
27
|
+
* guard (./nonce-guard.ts): refuse if a toon-clientd holds this identity,
|
|
28
|
+
* and hold an exclusive per-pubkey lockfile against other standalone
|
|
29
|
+
* processes for the lifetime of this publisher.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/** A fully-signed Nostr event (structural subset of nostr-tools' NostrEvent). */
|
|
33
|
+
interface SignedNostrEvent extends UnsignedEvent {
|
|
34
|
+
id: string;
|
|
35
|
+
pubkey: string;
|
|
36
|
+
sig: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The slice of `ToonClient` the publisher drives. Kept structural so tests
|
|
40
|
+
* inject a mock and alternative client builds stay compatible.
|
|
41
|
+
*/
|
|
42
|
+
interface ToonClientLike {
|
|
43
|
+
start(): Promise<unknown>;
|
|
44
|
+
stop(): Promise<void>;
|
|
45
|
+
isStarted?(): boolean;
|
|
46
|
+
getPublicKey(): string;
|
|
47
|
+
signEvent(template: UnsignedEvent): SignedNostrEvent;
|
|
48
|
+
openChannel(destination?: string): Promise<string>;
|
|
49
|
+
signBalanceProof(channelId: string, amount: bigint): Promise<SignedBalanceProof>;
|
|
50
|
+
publishEvent(event: SignedNostrEvent, options?: {
|
|
51
|
+
destination?: string;
|
|
52
|
+
claim?: SignedBalanceProof;
|
|
53
|
+
ilpAmount?: bigint;
|
|
54
|
+
proxyPath?: string;
|
|
55
|
+
}): Promise<PublishEventResult>;
|
|
56
|
+
}
|
|
57
|
+
interface StandalonePublisherOptions {
|
|
58
|
+
/**
|
|
59
|
+
* ToonClient config (mnemonic + mnemonicAccountIndex + proxy/BTP uplink +
|
|
60
|
+
* settlement fields — see `packages/client/src/config.ts`). Exactly one of
|
|
61
|
+
* `clientConfig` | `client` is required.
|
|
62
|
+
*/
|
|
63
|
+
clientConfig?: ToonClientConfig;
|
|
64
|
+
/** Pre-built client (tests / advanced callers). */
|
|
65
|
+
client?: ToonClientLike;
|
|
66
|
+
/**
|
|
67
|
+
* ILP route for event publishes (relay `/write`). Default: derived from the
|
|
68
|
+
* config's `destinationAddress` anchor (`<base>.relay.store` → `<base>.relay`,
|
|
69
|
+
* matching the daemon's route derivation), else the anchor itself.
|
|
70
|
+
*/
|
|
71
|
+
publishDestination?: string;
|
|
72
|
+
/**
|
|
73
|
+
* ILP route for git-object uploads (store `/store` → Arweave). Default:
|
|
74
|
+
* derived like `publishDestination` (`<base>.relay.store` → `<base>.store`).
|
|
75
|
+
*/
|
|
76
|
+
storeDestination?: string;
|
|
77
|
+
/**
|
|
78
|
+
* ILP destination the payment channel anchors to. Default: the client
|
|
79
|
+
* config's `destinationAddress`.
|
|
80
|
+
*/
|
|
81
|
+
channelDestination?: string;
|
|
82
|
+
/** Flat fee per published event (daemon `feePerEvent` convention). Default 1n. */
|
|
83
|
+
eventFee?: bigint;
|
|
84
|
+
/** Upload fee per object body byte (seed pipeline's bid rate). Default 10n. */
|
|
85
|
+
uploadFeePerByte?: bigint;
|
|
86
|
+
/** Daemon control API port probed by the nonce guard. */
|
|
87
|
+
daemonPort?: number;
|
|
88
|
+
/** Directory for the per-identity advisory lockfile. */
|
|
89
|
+
lockDir?: string;
|
|
90
|
+
/** Fetch impl for the daemon probe (tests). */
|
|
91
|
+
fetchImpl?: typeof fetch;
|
|
92
|
+
}
|
|
93
|
+
/** A relay/store rejected a paid write (fee NOT spent iff the claim failed too). */
|
|
94
|
+
declare class StandalonePublishError extends Error {
|
|
95
|
+
constructor(message: string);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Derive publish/store routes from the channel anchor. Behind the devnet
|
|
99
|
+
* proxy the anchor is `<base>.relay.store` (e.g. `g.proxy.relay.store`):
|
|
100
|
+
* publishes terminate at `<base>.relay`, uploads at `<base>.store`. Anchors
|
|
101
|
+
* not matching the convention pass through unchanged.
|
|
102
|
+
*/
|
|
103
|
+
declare function deriveRouteDestinations(anchor: string): {
|
|
104
|
+
publish: string;
|
|
105
|
+
store: string;
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Decode the Arweave txId from a store-write FULFILL. The deployed payment
|
|
109
|
+
* proxy returns the store's verbatim HTTP/1.1 response
|
|
110
|
+
* (`{"accept":true,"txId":…}` body); legacy non-proxy providers return bare
|
|
111
|
+
* `base64(utf8(txId))`.
|
|
112
|
+
*
|
|
113
|
+
* @throws {StandalonePublishError} when no valid txId can be extracted.
|
|
114
|
+
*/
|
|
115
|
+
declare function extractArweaveTxId(base64Data: string): string;
|
|
116
|
+
declare class StandalonePublisher implements Publisher {
|
|
117
|
+
private readonly client;
|
|
118
|
+
private readonly ownsClient;
|
|
119
|
+
private readonly publishDestination;
|
|
120
|
+
private readonly storeDestination;
|
|
121
|
+
private readonly channelDestination;
|
|
122
|
+
private readonly eventFee;
|
|
123
|
+
private readonly uploadFeePerByte;
|
|
124
|
+
private readonly daemonPort;
|
|
125
|
+
private readonly lockDir;
|
|
126
|
+
private readonly fetchImpl;
|
|
127
|
+
private lock;
|
|
128
|
+
private channelId;
|
|
129
|
+
private readyPromise;
|
|
130
|
+
constructor(options: StandalonePublisherOptions);
|
|
131
|
+
/** Hex Nostr pubkey of the embedded identity (available before start). */
|
|
132
|
+
getPublicKey(): string;
|
|
133
|
+
/**
|
|
134
|
+
* Run the nonce guard, start the embedded client, and open (or resume) the
|
|
135
|
+
* payment channel. Called lazily by the first paid operation; safe to call
|
|
136
|
+
* eagerly to fail fast. Idempotent.
|
|
137
|
+
*/
|
|
138
|
+
start(): Promise<void>;
|
|
139
|
+
private doStart;
|
|
140
|
+
/** Release the identity lock and stop the embedded client (if we own it). */
|
|
141
|
+
stop(): Promise<void>;
|
|
142
|
+
/**
|
|
143
|
+
* Fee rates for `planPush` estimation: the flat per-event fee and the
|
|
144
|
+
* per-byte upload rate this publisher pays (daemon `feePerEvent` and seed
|
|
145
|
+
* bid-rate conventions; override via options).
|
|
146
|
+
*/
|
|
147
|
+
getFeeRates(): Promise<FeeRates>;
|
|
148
|
+
/**
|
|
149
|
+
* Upload one git object as a kind:5094 store write (Git-SHA/Git-Type/Repo
|
|
150
|
+
* tagged — the proven seed-pipeline shape), signing one balance-proof claim
|
|
151
|
+
* for `body.length × uploadFeePerByte`.
|
|
152
|
+
*/
|
|
153
|
+
uploadGitObject(upload: GitObjectUpload): Promise<UploadReceipt>;
|
|
154
|
+
/**
|
|
155
|
+
* Sign the event with the embedded identity and pay-to-publish it through
|
|
156
|
+
* the relay write route, one claim for the flat per-event fee.
|
|
157
|
+
*
|
|
158
|
+
* `relayUrls` is the interface's plural forward-compat surface (parked
|
|
159
|
+
* #84): the standalone impl routes over ILP to its single configured
|
|
160
|
+
* publish destination, so more than one relay is refused rather than
|
|
161
|
+
* silently half-published.
|
|
162
|
+
*/
|
|
163
|
+
publishEvent(event: UnsignedEvent, relayUrls: string[]): Promise<PublishReceipt>;
|
|
164
|
+
private requireChannel;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Nonce-ownership guard for the STANDALONE embedded Publisher (#228).
|
|
169
|
+
*
|
|
170
|
+
* Why this exists: a payment channel's balance proof is a CUMULATIVE
|
|
171
|
+
* watermark — the ChannelManager auto-increments the nonce and cumulative
|
|
172
|
+
* amount on every `signBalanceProof`. Two writers signing claims on the same
|
|
173
|
+
* channel from separate processes (a running `toon-clientd` daemon plus a
|
|
174
|
+
* standalone embedded client, or two standalone processes) each keep their
|
|
175
|
+
* own cumulative counter, so their claims race: the connector sees
|
|
176
|
+
* non-monotonic watermarks and a re-signed claim can double-charge (the
|
|
177
|
+
* hazard documented in packages/rig/tests/e2e/seed/lib/publish.ts).
|
|
178
|
+
*
|
|
179
|
+
* Two independent defenses, both keyed by the Nostr pubkey (one identity =
|
|
180
|
+
* one channel set):
|
|
181
|
+
*
|
|
182
|
+
* 1. Daemon detection — probe the toon-clientd loopback control API
|
|
183
|
+
* (`GET /status`) and REFUSE when it reports the SAME identity. A daemon
|
|
184
|
+
* on a different identity holds different channels and is harmless.
|
|
185
|
+
* 2. Advisory lockfile — an exclusive per-pubkey lockfile under the shared
|
|
186
|
+
* `~/.toon-client` state dir so two STANDALONE processes can't race each
|
|
187
|
+
* other (the daemon check only covers the daemon). Stale locks (dead pid)
|
|
188
|
+
* are reclaimed.
|
|
189
|
+
*
|
|
190
|
+
* The daemon port and state-dir conventions are DUPLICATED from
|
|
191
|
+
* `packages/client-mcp/src/daemon/config.ts` (default port 8787 /
|
|
192
|
+
* `TOON_CLIENT_HTTP_PORT`; `~/.toon-client` / `TOON_CLIENT_HOME`).
|
|
193
|
+
* `@toon-protocol/git` must not depend on `@toon-protocol/client-mcp`
|
|
194
|
+
* (the daemon package depends on this one for the #227 Publisher — the
|
|
195
|
+
* import would be circular), so the constants live here with this note.
|
|
196
|
+
* Keep them in sync.
|
|
197
|
+
*/
|
|
198
|
+
/** Default toon-clientd loopback control API port (client-mcp `httpPort`). */
|
|
199
|
+
declare const DEFAULT_DAEMON_PORT = 8787;
|
|
200
|
+
/** Daemon control API port: `TOON_CLIENT_HTTP_PORT` env, else 8787. */
|
|
201
|
+
declare function defaultDaemonPort(): number;
|
|
202
|
+
/**
|
|
203
|
+
* Shared client state dir: `TOON_CLIENT_HOME` env, else `~/.toon-client` —
|
|
204
|
+
* the same dir the daemon keeps its config/channel stores in, so daemon and
|
|
205
|
+
* standalone processes agree on where the advisory locks live.
|
|
206
|
+
*/
|
|
207
|
+
declare function defaultLockDir(): string;
|
|
208
|
+
/** A running toon-clientd holds the same identity (channel watermark owner). */
|
|
209
|
+
declare class DaemonIdentityConflictError extends Error {
|
|
210
|
+
/** The shared Nostr pubkey (hex). */
|
|
211
|
+
readonly pubkey: string;
|
|
212
|
+
/** The daemon control API URL that answered. */
|
|
213
|
+
readonly daemonUrl: string;
|
|
214
|
+
constructor(
|
|
215
|
+
/** The shared Nostr pubkey (hex). */
|
|
216
|
+
pubkey: string,
|
|
217
|
+
/** The daemon control API URL that answered. */
|
|
218
|
+
daemonUrl: string);
|
|
219
|
+
}
|
|
220
|
+
/** Another standalone process already holds the per-identity lock. */
|
|
221
|
+
declare class StandaloneLockError extends Error {
|
|
222
|
+
readonly pubkey: string;
|
|
223
|
+
readonly lockPath: string;
|
|
224
|
+
readonly holderPid: number;
|
|
225
|
+
constructor(pubkey: string, lockPath: string, holderPid: number);
|
|
226
|
+
}
|
|
227
|
+
interface CheckDaemonOptions {
|
|
228
|
+
/** Control API port (default: `TOON_CLIENT_HTTP_PORT` env, else 8787). */
|
|
229
|
+
port?: number;
|
|
230
|
+
/** Probe timeout, ms (default 1500 — loopback, so fast). */
|
|
231
|
+
timeoutMs?: number;
|
|
232
|
+
/** Inject a fetch implementation (tests). Defaults to global `fetch`. */
|
|
233
|
+
fetchImpl?: typeof fetch;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Probe the toon-clientd loopback control API and throw
|
|
237
|
+
* {@link DaemonIdentityConflictError} when a daemon responds on `/status`
|
|
238
|
+
* with `identity.nostrPubkey === pubkey`.
|
|
239
|
+
*
|
|
240
|
+
* Anything short of a positive identity match lets the caller proceed: no
|
|
241
|
+
* listener, a timeout, a non-JSON response (some other local service on the
|
|
242
|
+
* port), or a daemon on a DIFFERENT identity (its channels are keyed to its
|
|
243
|
+
* own pubkey — no shared watermark).
|
|
244
|
+
*/
|
|
245
|
+
declare function checkDaemonIdentity(pubkey: string, options?: CheckDaemonOptions): Promise<void>;
|
|
246
|
+
interface AcquireLockOptions {
|
|
247
|
+
/** Directory the lockfile lives in (default: {@link defaultLockDir}). */
|
|
248
|
+
dir?: string;
|
|
249
|
+
/** Override the recorded pid (tests). Defaults to `process.pid`. */
|
|
250
|
+
pid?: number;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Exclusive advisory lock for one identity's payment-channel watermark.
|
|
254
|
+
*
|
|
255
|
+
* Acquired with an atomic `wx` create of `standalone-<pubkey>.lock` (JSON:
|
|
256
|
+
* pid + pubkey + timestamp) under the shared state dir. A pre-existing lock
|
|
257
|
+
* whose pid is dead (or whose contents are unreadable) is STALE and gets
|
|
258
|
+
* reclaimed; a live holder throws {@link StandaloneLockError}. Released
|
|
259
|
+
* explicitly via {@link release} and best-effort on process exit.
|
|
260
|
+
*/
|
|
261
|
+
declare class NonceLock {
|
|
262
|
+
readonly pubkey: string;
|
|
263
|
+
readonly lockPath: string;
|
|
264
|
+
private released;
|
|
265
|
+
private readonly exitHandler;
|
|
266
|
+
private constructor();
|
|
267
|
+
static acquire(pubkey: string, options?: AcquireLockOptions): Promise<NonceLock>;
|
|
268
|
+
/** Remove the lockfile and detach the exit hook. Idempotent. */
|
|
269
|
+
release(): void;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export { type AcquireLockOptions, type CheckDaemonOptions, DEFAULT_DAEMON_PORT, DaemonIdentityConflictError, NonceLock, type SignedNostrEvent, StandaloneLockError, StandalonePublishError, StandalonePublisher, type StandalonePublisherOptions, type ToonClientLike, checkDaemonIdentity, defaultDaemonPort, defaultLockDir, deriveRouteDestinations, extractArweaveTxId };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {
|
|
2
|
+
StandalonePublishError,
|
|
3
|
+
StandalonePublisher,
|
|
4
|
+
deriveRouteDestinations,
|
|
5
|
+
extractArweaveTxId
|
|
6
|
+
} from "../chunk-SBMFWVCP.js";
|
|
7
|
+
import {
|
|
8
|
+
DEFAULT_DAEMON_PORT,
|
|
9
|
+
DaemonIdentityConflictError,
|
|
10
|
+
NonceLock,
|
|
11
|
+
StandaloneLockError,
|
|
12
|
+
checkDaemonIdentity,
|
|
13
|
+
defaultDaemonPort,
|
|
14
|
+
defaultLockDir
|
|
15
|
+
} from "../chunk-LJA7PPZI.js";
|
|
16
|
+
import "../chunk-M7O4SEVW.js";
|
|
17
|
+
export {
|
|
18
|
+
DEFAULT_DAEMON_PORT,
|
|
19
|
+
DaemonIdentityConflictError,
|
|
20
|
+
NonceLock,
|
|
21
|
+
StandaloneLockError,
|
|
22
|
+
StandalonePublishError,
|
|
23
|
+
StandalonePublisher,
|
|
24
|
+
checkDaemonIdentity,
|
|
25
|
+
defaultDaemonPort,
|
|
26
|
+
defaultLockDir,
|
|
27
|
+
deriveRouteDestinations,
|
|
28
|
+
extractArweaveTxId
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|