@forestrie/mcp-register 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +126 -2
  3. package/bin/mcp-register.mjs +2 -0
  4. package/dist/core/base64.d.ts +7 -0
  5. package/dist/core/base64.d.ts.map +1 -0
  6. package/dist/core/base64.js +20 -0
  7. package/dist/core/base64.js.map +1 -0
  8. package/dist/core/delegation.d.ts +56 -0
  9. package/dist/core/delegation.d.ts.map +1 -0
  10. package/dist/core/delegation.js +130 -0
  11. package/dist/core/delegation.js.map +1 -0
  12. package/dist/core/grant.d.ts +41 -0
  13. package/dist/core/grant.d.ts.map +1 -0
  14. package/dist/core/grant.js +78 -0
  15. package/dist/core/grant.js.map +1 -0
  16. package/dist/core/hex.d.ts +4 -0
  17. package/dist/core/hex.d.ts.map +1 -0
  18. package/dist/core/hex.js +18 -0
  19. package/dist/core/hex.js.map +1 -0
  20. package/dist/core/index.d.ts +28 -0
  21. package/dist/core/index.d.ts.map +1 -0
  22. package/dist/core/index.js +21 -0
  23. package/dist/core/index.js.map +1 -0
  24. package/dist/core/issue.d.ts +38 -0
  25. package/dist/core/issue.d.ts.map +1 -0
  26. package/dist/core/issue.js +135 -0
  27. package/dist/core/issue.js.map +1 -0
  28. package/dist/core/limits.d.ts +9 -0
  29. package/dist/core/limits.d.ts.map +1 -0
  30. package/dist/core/limits.js +9 -0
  31. package/dist/core/limits.js.map +1 -0
  32. package/dist/core/scrapi.d.ts +66 -0
  33. package/dist/core/scrapi.d.ts.map +1 -0
  34. package/dist/core/scrapi.js +174 -0
  35. package/dist/core/scrapi.js.map +1 -0
  36. package/dist/core/signer.d.ts +24 -0
  37. package/dist/core/signer.d.ts.map +1 -0
  38. package/dist/core/signer.js +2 -0
  39. package/dist/core/signer.js.map +1 -0
  40. package/dist/core/statement.d.ts +26 -0
  41. package/dist/core/statement.d.ts.map +1 -0
  42. package/dist/core/statement.js +70 -0
  43. package/dist/core/statement.js.map +1 -0
  44. package/dist/core/version.d.ts +23 -0
  45. package/dist/core/version.d.ts.map +1 -0
  46. package/dist/core/version.js +23 -0
  47. package/dist/core/version.js.map +1 -0
  48. package/dist/core/x402.d.ts +99 -0
  49. package/dist/core/x402.d.ts.map +1 -0
  50. package/dist/core/x402.js +202 -0
  51. package/dist/core/x402.js.map +1 -0
  52. package/dist/net/delegate.d.ts +56 -0
  53. package/dist/net/delegate.d.ts.map +1 -0
  54. package/dist/net/delegate.js +182 -0
  55. package/dist/net/delegate.js.map +1 -0
  56. package/dist/net/http.d.ts +17 -0
  57. package/dist/net/http.d.ts.map +1 -0
  58. package/dist/net/http.js +52 -0
  59. package/dist/net/http.js.map +1 -0
  60. package/dist/net/index.d.ts +14 -0
  61. package/dist/net/index.d.ts.map +1 -0
  62. package/dist/net/index.js +13 -0
  63. package/dist/net/index.js.map +1 -0
  64. package/dist/net/write.d.ts +55 -0
  65. package/dist/net/write.d.ts.map +1 -0
  66. package/dist/net/write.js +46 -0
  67. package/dist/net/write.js.map +1 -0
  68. package/dist/node/cli.d.ts +8 -0
  69. package/dist/node/cli.d.ts.map +1 -0
  70. package/dist/node/cli.js +143 -0
  71. package/dist/node/cli.js.map +1 -0
  72. package/dist/node/grant-store.d.ts +49 -0
  73. package/dist/node/grant-store.d.ts.map +1 -0
  74. package/dist/node/grant-store.js +95 -0
  75. package/dist/node/grant-store.js.map +1 -0
  76. package/dist/node/grant-tools.d.ts +29 -0
  77. package/dist/node/grant-tools.d.ts.map +1 -0
  78. package/dist/node/grant-tools.js +498 -0
  79. package/dist/node/grant-tools.js.map +1 -0
  80. package/dist/node/ledger.d.ts +26 -0
  81. package/dist/node/ledger.d.ts.map +1 -0
  82. package/dist/node/ledger.js +78 -0
  83. package/dist/node/ledger.js.map +1 -0
  84. package/dist/node/payer.d.ts +48 -0
  85. package/dist/node/payer.d.ts.map +1 -0
  86. package/dist/node/payer.js +170 -0
  87. package/dist/node/payer.js.map +1 -0
  88. package/dist/node/resolve-input.d.ts +19 -0
  89. package/dist/node/resolve-input.d.ts.map +1 -0
  90. package/dist/node/resolve-input.js +76 -0
  91. package/dist/node/resolve-input.js.map +1 -0
  92. package/dist/node/server.d.ts +47 -0
  93. package/dist/node/server.d.ts.map +1 -0
  94. package/dist/node/server.js +212 -0
  95. package/dist/node/server.js.map +1 -0
  96. package/dist/node/signer-file.d.ts +34 -0
  97. package/dist/node/signer-file.d.ts.map +1 -0
  98. package/dist/node/signer-file.js +183 -0
  99. package/dist/node/signer-file.js.map +1 -0
  100. package/dist/node/store.d.ts +40 -0
  101. package/dist/node/store.d.ts.map +1 -0
  102. package/dist/node/store.js +117 -0
  103. package/dist/node/store.js.map +1 -0
  104. package/dist/node/text.d.ts +29 -0
  105. package/dist/node/text.d.ts.map +1 -0
  106. package/dist/node/text.js +124 -0
  107. package/dist/node/text.js.map +1 -0
  108. package/dist/node/tools.d.ts +118 -0
  109. package/dist/node/tools.d.ts.map +1 -0
  110. package/dist/node/tools.js +548 -0
  111. package/dist/node/tools.js.map +1 -0
  112. package/package.json +108 -3
  113. package/src/core/base64.ts +18 -0
  114. package/src/core/delegation.ts +239 -0
  115. package/src/core/grant.ts +127 -0
  116. package/src/core/hex.ts +18 -0
  117. package/src/core/index.ts +84 -0
  118. package/src/core/issue.ts +209 -0
  119. package/src/core/limits.ts +8 -0
  120. package/src/core/scrapi.ts +236 -0
  121. package/src/core/signer.ts +23 -0
  122. package/src/core/statement.ts +133 -0
  123. package/src/core/version.ts +25 -0
  124. package/src/core/x402.ts +314 -0
  125. package/src/net/delegate.ts +274 -0
  126. package/src/net/http.ts +71 -0
  127. package/src/net/index.ts +26 -0
  128. package/src/net/write.ts +124 -0
  129. package/src/node/cli.ts +170 -0
  130. package/src/node/grant-store.ts +154 -0
  131. package/src/node/grant-tools.ts +707 -0
  132. package/src/node/ledger.ts +98 -0
  133. package/src/node/payer.ts +241 -0
  134. package/src/node/resolve-input.ts +96 -0
  135. package/src/node/server.ts +332 -0
  136. package/src/node/signer-file.ts +251 -0
  137. package/src/node/store.ts +175 -0
  138. package/src/node/text.ts +153 -0
  139. package/src/node/tools.ts +810 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signer-file.d.ts","sourceRoot":"","sources":["../../src/node/signer-file.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIrD,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,IAAI,EAAG,oBAAoB,CAAU;CAC/C;AAED,gGAAgG;AAChG,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,MAAM,CAI5E;AAkED;;;;;GAKG;AACH,MAAM,MAAM,OAAO,GAAG,KAAK,GAAG,WAAW,CAAC;AAkB1C,MAAM,WAAW,aAAc,SAAQ,WAAW;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GACjD,OAAO,CAAC,aAAa,CAAC,CA8ExB;AAQD,oFAAoF;AACpF,wBAAgB,YAAY,CAAC,EAAE,EAAE,UAAU,GAAG,MAAM,CAInD"}
@@ -0,0 +1,183 @@
1
+ /**
2
+ * The default signer backend: an ES256 (P-256) private key in a local JWK
3
+ * file, created on first use.
4
+ *
5
+ * Proportionate custody for what the key can do: it owns one data log
6
+ * whose grant someone else issued and bounded, and it holds no funds. The
7
+ * file is written once, `0600` in a `0700` directory, atomically (written
8
+ * aside, then hard-linked into place), so two servers starting together
9
+ * cannot both write one and neither can read a half-written one. It is
10
+ * never overwritten: a key that signed a grant's `grantData` must not
11
+ * silently change under it.
12
+ *
13
+ * The private key is imported non-extractable for signing; nothing in this
14
+ * package reads the `d` field again after import, and no tool result or
15
+ * log line carries it.
16
+ */
17
+ import { webcrypto } from "node:crypto";
18
+ import { linkSync, lstatSync, mkdirSync, readFileSync, unlinkSync, writeFileSync, } from "node:fs";
19
+ import { homedir } from "node:os";
20
+ import { dirname, join } from "node:path";
21
+ const subtle = webcrypto.subtle;
22
+ export class KeyFileError extends Error {
23
+ code = "signer_unavailable";
24
+ }
25
+ /** `$MCP_REGISTER_HOME`, else `$XDG_CONFIG_HOME/forestrie/mcp-register`, else `~/.config/…`. */
26
+ export function registerHome(env) {
27
+ if (env["MCP_REGISTER_HOME"])
28
+ return env["MCP_REGISTER_HOME"];
29
+ const config = env["XDG_CONFIG_HOME"] ?? join(homedir(), ".config");
30
+ return join(config, "forestrie", "mcp-register");
31
+ }
32
+ function b64urlToBytes(s) {
33
+ return new Uint8Array(Buffer.from(s, "base64url"));
34
+ }
35
+ function isP256PrivateJwk(v) {
36
+ const j = v;
37
+ return (typeof v === "object" &&
38
+ v !== null &&
39
+ j.kty === "EC" &&
40
+ j.crv === "P-256" &&
41
+ typeof j.x === "string" &&
42
+ typeof j.y === "string" &&
43
+ typeof j.d === "string");
44
+ }
45
+ /**
46
+ * Write a new key without ever exposing a half-written file: the JWK goes
47
+ * to a private temporary file first, then is hard-linked into place, which
48
+ * fails (EEXIST) if another process got there first, in which case its key
49
+ * is used. The temporary file is always removed.
50
+ */
51
+ async function createKeyFile(path, note) {
52
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
53
+ const pair = (await subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, ["sign", "verify"]));
54
+ const jwk = await subtle.exportKey("jwk", pair.privateKey);
55
+ const record = {
56
+ kty: jwk.kty,
57
+ crv: jwk.crv,
58
+ x: jwk.x,
59
+ y: jwk.y,
60
+ d: jwk.d,
61
+ use: "sig",
62
+ alg: "ES256",
63
+ note,
64
+ };
65
+ const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
66
+ try {
67
+ writeFileSync(tmp, JSON.stringify(record, null, 2) + "\n", {
68
+ flag: "wx",
69
+ mode: 0o600,
70
+ });
71
+ try {
72
+ linkSync(tmp, path);
73
+ }
74
+ catch (err) {
75
+ // Lost a race to another process creating the same file: use theirs.
76
+ if (err.code !== "EEXIST")
77
+ throw err;
78
+ }
79
+ }
80
+ finally {
81
+ try {
82
+ unlinkSync(tmp);
83
+ }
84
+ catch {
85
+ // already gone
86
+ }
87
+ }
88
+ }
89
+ const KEY_ROLES = {
90
+ log: {
91
+ label: "log key",
92
+ note: "Forestrie log key for @forestrie/mcp-register. Grants name its public x‖y; keep this file private.",
93
+ createdBy: "get_log_key or register_statement",
94
+ },
95
+ authority: {
96
+ label: "authority key",
97
+ note: "Forestrie authority key for @forestrie/mcp-register: owns the requester's authority log and signs the child grants issue_grant makes. Keep this file private.",
98
+ createdBy: 'get_log_key with which: "authority", or issue_grant,',
99
+ },
100
+ };
101
+ /**
102
+ * Load the key at `path`. Lazy by design: the server opens the signer at the
103
+ * first tool call that needs it, so starting the server never writes a
104
+ * file. With `create` (the default) a missing key is created; without it a
105
+ * missing key is a `KeyFileError`, so a read-only tool never makes one.
106
+ *
107
+ * Refused, like ssh refuses a loose private key: a key path that is a
108
+ * symlink, or a key file readable or writable by group or others.
109
+ */
110
+ export async function openKeyFileSigner(path, options = {}) {
111
+ const role = options.role ?? "log";
112
+ const label = KEY_ROLES[role].label;
113
+ let created = false;
114
+ let stat = lstatOrUndefined(path);
115
+ if (stat === undefined) {
116
+ if (options.create === false)
117
+ throw new KeyFileError(`no ${label} at ${path} yet: ${KEY_ROLES[role].createdBy} creates it`);
118
+ try {
119
+ await createKeyFile(path, KEY_ROLES[role].note);
120
+ }
121
+ catch (err) {
122
+ throw new KeyFileError(`cannot create the ${label} file ${path}: ${err.message}`);
123
+ }
124
+ created = true;
125
+ stat = lstatOrUndefined(path);
126
+ }
127
+ if (stat === undefined || stat.isSymbolicLink() || !stat.isFile())
128
+ throw new KeyFileError(`the ${label} path ${path} is not a regular file (a symlink or directory is refused)`);
129
+ if ((stat.mode & 0o077) !== 0)
130
+ throw new KeyFileError(`the ${label} file ${path} is accessible to other users (mode ${(stat.mode & 0o777).toString(8)}); chmod 600 it`);
131
+ let text;
132
+ try {
133
+ text = readFileSync(path, "utf8");
134
+ }
135
+ catch (err) {
136
+ throw new KeyFileError(`cannot read the ${label} file ${path}: ${err.message}`);
137
+ }
138
+ let jwk;
139
+ try {
140
+ jwk = JSON.parse(text);
141
+ }
142
+ catch {
143
+ throw new KeyFileError(`the ${label} file ${path} is not JSON`);
144
+ }
145
+ if (!isP256PrivateJwk(jwk))
146
+ throw new KeyFileError(`the ${label} file ${path} is not a P-256 private JWK (kty EC, crv P-256, x, y, d)`);
147
+ let privateKey;
148
+ try {
149
+ privateKey = await subtle.importKey("jwk", { kty: "EC", crv: "P-256", x: jwk.x, y: jwk.y, d: jwk.d, ext: false }, { name: "ECDSA", namedCurve: "P-256" }, false, ["sign"]);
150
+ }
151
+ catch {
152
+ throw new KeyFileError(`the ${label} file ${path} holds an invalid P-256 key`);
153
+ }
154
+ const xy = new Uint8Array(64);
155
+ xy.set(b64urlToBytes(jwk.x), 0);
156
+ xy.set(b64urlToBytes(jwk.y), 32);
157
+ return {
158
+ path,
159
+ created,
160
+ description: `local ${label} file ${path}`,
161
+ publicKeyXY: async () => xy.slice(),
162
+ sign: async (message) => new Uint8Array(await subtle.sign({ name: "ECDSA", hash: "SHA-256" }, privateKey, message)),
163
+ };
164
+ }
165
+ /** SubjectPublicKeyInfo DER prefix for an uncompressed P-256 point. */
166
+ const P256_SPKI_PREFIX = Buffer.from("3059301306072a8648ce3d020106082a8648ce3d030107034200", "hex");
167
+ /** The public key as an SPKI PEM, the form forestrie-cli's `--signer-pem` reads. */
168
+ export function publicKeyPem(xy) {
169
+ const der = Buffer.concat([P256_SPKI_PREFIX, Buffer.from([0x04]), xy]);
170
+ const b64 = der.toString("base64").replace(/(.{64})/g, "$1\n");
171
+ return `-----BEGIN PUBLIC KEY-----\n${b64}\n-----END PUBLIC KEY-----\n`;
172
+ }
173
+ function lstatOrUndefined(path) {
174
+ try {
175
+ return lstatSync(path);
176
+ }
177
+ catch (err) {
178
+ if (err.code === "ENOENT")
179
+ return undefined;
180
+ throw new KeyFileError(`cannot stat the key path ${path}: ${err.message}`);
181
+ }
182
+ }
183
+ //# sourceMappingURL=signer-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signer-file.js","sourceRoot":"","sources":["../../src/node/signer-file.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ export interface RegistrationRecord {
2
+ contentHash: string;
3
+ /** "signed": saved, not (yet) known to be accepted. "accepted": canopy answered 303. */
4
+ state: "signed" | "accepted";
5
+ /** From canopy's 303; absent until accepted. */
6
+ statusUrl?: string;
7
+ baseUrl: string;
8
+ rootLogId: string;
9
+ logId: string;
10
+ contentType: string;
11
+ issuer: string;
12
+ subject: string;
13
+ signedAt: string;
14
+ acceptedAt?: string;
15
+ /** When check_registration first saw it pending; bounds the wait. */
16
+ firstPendingAt?: string;
17
+ entryId?: string;
18
+ massifHeight?: number;
19
+ receiptUrl?: string;
20
+ sealedAt?: string;
21
+ }
22
+ export declare class StoreError extends Error {
23
+ readonly code: "unknown_registration" | "corrupt_record" | "store_failed";
24
+ constructor(code: "unknown_registration" | "corrupt_record" | "store_failed", message: string);
25
+ }
26
+ export declare class RegistrationStore {
27
+ readonly root: string;
28
+ constructor(root: string);
29
+ dir(contentHash: string): string;
30
+ statementPath(contentHash: string): string;
31
+ receiptPath(contentHash: string): string;
32
+ saveStatement(record: RegistrationRecord, statement: Uint8Array): void;
33
+ saveRecord(record: RegistrationRecord): void;
34
+ saveReceipt(contentHash: string, receipt: Uint8Array): void;
35
+ load(contentHash: string): RegistrationRecord;
36
+ readStatement(contentHash: string): Uint8Array;
37
+ /** The saved receipt, or `undefined` when there is none yet. */
38
+ readReceipt(contentHash: string): Uint8Array | undefined;
39
+ }
40
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/node/store.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,wFAAwF;IACxF,KAAK,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC7B,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qEAAqE;IACrE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,UAAW,SAAQ,KAAK;IAEjC,QAAQ,CAAC,IAAI,EAAE,sBAAsB,GAAG,gBAAgB,GAAG,cAAc;gBAAhE,IAAI,EAAE,sBAAsB,GAAG,gBAAgB,GAAG,cAAc,EACzE,OAAO,EAAE,MAAM;CAIlB;AAgCD,qBAAa,iBAAiB;IAChB,QAAQ,CAAC,IAAI,EAAE,MAAM;gBAAZ,IAAI,EAAE,MAAM;IAEjC,GAAG,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAShC,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAI1C,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAIxC,aAAa,CAAC,MAAM,EAAE,kBAAkB,EAAE,SAAS,EAAE,UAAU,GAAG,IAAI;IAWtE,UAAU,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI;IAS5C,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI;IAM3D,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,kBAAkB;IA8B7C,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,UAAU;IAO9C,gEAAgE;IAChE,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;CAWzD"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Where registrations are kept between tool calls:
3
+ * `<home>/registrations/<contentHash>/{record.json, statement.cose, receipt.cose}`.
4
+ *
5
+ * The statement and receipt live on disk so tools can hand back paths
6
+ * rather than megabytes of base64, and so `@forestrie/mcp-verify` (which
7
+ * takes `{path}` inputs) can be pointed straight at them. The record is
8
+ * the caller's evidence of what was registered where; nothing here is
9
+ * secret.
10
+ *
11
+ * The signed statement is saved BEFORE it is sent (`state: "signed"`), so a
12
+ * failure after canopy has accepted it never loses the handle: the status
13
+ * URL is derived from the content hash, and check_registration can still
14
+ * find it. Every filesystem failure is a `StoreError` with a code.
15
+ */
16
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
17
+ import { join } from "node:path";
18
+ export class StoreError extends Error {
19
+ code;
20
+ constructor(code, message) {
21
+ super(message);
22
+ this.code = code;
23
+ }
24
+ }
25
+ const HASH = /^[0-9a-f]{64}$/;
26
+ function io(what, run) {
27
+ try {
28
+ return run();
29
+ }
30
+ catch (err) {
31
+ throw new StoreError("store_failed", `${what}: ${err instanceof Error ? err.message : String(err)}`);
32
+ }
33
+ }
34
+ function isRecord(v) {
35
+ const r = v;
36
+ return (typeof r === "object" &&
37
+ r !== null &&
38
+ typeof r.contentHash === "string" &&
39
+ HASH.test(r.contentHash) &&
40
+ (r.state === "signed" || r.state === "accepted") &&
41
+ typeof r.baseUrl === "string" &&
42
+ typeof r.rootLogId === "string" &&
43
+ typeof r.logId === "string" &&
44
+ (r.statusUrl === undefined || typeof r.statusUrl === "string") &&
45
+ (r.receiptUrl === undefined || typeof r.receiptUrl === "string") &&
46
+ (r.entryId === undefined || /^[0-9a-f]{32}$/.test(r.entryId)));
47
+ }
48
+ export class RegistrationStore {
49
+ root;
50
+ constructor(root) {
51
+ this.root = root;
52
+ }
53
+ dir(contentHash) {
54
+ if (!HASH.test(contentHash))
55
+ throw new StoreError("unknown_registration", `contentHash must be 64 lowercase hex characters (the sha256 register_statement returned)`);
56
+ return join(this.root, "registrations", contentHash);
57
+ }
58
+ statementPath(contentHash) {
59
+ return join(this.dir(contentHash), "statement.cose");
60
+ }
61
+ receiptPath(contentHash) {
62
+ return join(this.dir(contentHash), "receipt.cose");
63
+ }
64
+ saveStatement(record, statement) {
65
+ io(`cannot save the signed statement under ${this.root}`, () => {
66
+ mkdirSync(this.dir(record.contentHash), {
67
+ recursive: true,
68
+ mode: 0o700,
69
+ });
70
+ writeFileSync(this.statementPath(record.contentHash), statement);
71
+ });
72
+ this.saveRecord(record);
73
+ }
74
+ saveRecord(record) {
75
+ io(`cannot save the registration record under ${this.root}`, () => writeFileSync(join(this.dir(record.contentHash), "record.json"), JSON.stringify(record, null, 2) + "\n"));
76
+ }
77
+ saveReceipt(contentHash, receipt) {
78
+ io(`cannot save the receipt under ${this.root}`, () => writeFileSync(this.receiptPath(contentHash), receipt));
79
+ }
80
+ load(contentHash) {
81
+ const path = join(this.dir(contentHash), "record.json");
82
+ let text;
83
+ try {
84
+ text = readFileSync(path, "utf8");
85
+ }
86
+ catch (err) {
87
+ if (err.code === "ENOENT")
88
+ throw new StoreError("unknown_registration", `no registration ${contentHash} in ${this.root}: register_statement records each registration there`);
89
+ throw new StoreError("store_failed", `cannot read ${path}: ${err.message}`);
90
+ }
91
+ let record;
92
+ try {
93
+ record = JSON.parse(text);
94
+ }
95
+ catch {
96
+ record = undefined;
97
+ }
98
+ if (!isRecord(record) || record.contentHash !== contentHash)
99
+ throw new StoreError("corrupt_record", `${path} is not a registration record for ${contentHash}`);
100
+ return record;
101
+ }
102
+ readStatement(contentHash) {
103
+ return io(`cannot read the signed statement for ${contentHash}`, () => new Uint8Array(readFileSync(this.statementPath(contentHash))));
104
+ }
105
+ /** The saved receipt, or `undefined` when there is none yet. */
106
+ readReceipt(contentHash) {
107
+ try {
108
+ return new Uint8Array(readFileSync(this.receiptPath(contentHash)));
109
+ }
110
+ catch (err) {
111
+ if (err.code === "ENOENT")
112
+ return undefined;
113
+ throw new StoreError("store_failed", `cannot read the receipt for ${contentHash}: ${err.message}`);
114
+ }
115
+ }
116
+ }
117
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/node/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Outward-facing text for the MCP adapter: the server `instructions`, the
3
+ * per-tool titles and descriptions, and the prompts. Change the wording on
4
+ * purpose, never in passing.
5
+ */
6
+ export declare const INSTRUCTIONS = "@forestrie/mcp-register is the write path of the Forestrie MCP trio. It signs statements with a log key it holds (ES256, behind a signer backend; by default a local key file it creates), registers them on a Forestrie transparency log under a Forestrie-Grant, delegates sealing for your data log, and checks the receipt with @forestrie/mcp-verify's verifier through @forestrie/mcp-resolve.\n\nIt can also act as a requester that is its own grant issuer: issue_grant signs a child grant with a separate authority key, and register_grant registers it with canopy, paying canopy's x402 402 from a configured payer within hard caps, or handing you the payment requirements to pay from your own wallet (relay mode). Forestrie runs no grant issuer.\n\nYou never handle signature bytes or keys: you pass the payload, the server signs; the payer key never crosses this boundary, and the spending caps are configuration, not arguments.\n\nThe loop with a grant you hold: get_log_key \u2192 delegate_sealing once per log \u2192 register_statement \u2192 check_registration until sealed \u2192 verify_registration (the \"register\" prompt). Buying the grant first: issue_grant \u2192 register_grant until complete (the \"issue-and-pay\" prompt). The \"audit\" prompt explains what a verified receipt does and does not prove.\n\nConfiguration, each overridable per call: FORESTRIE_BASE_URL (the canopy SCRAPI base URL), FORESTRIE_ROOT_LOG_ID (the forest's root log id, R), FORESTRIE_GRANT (the grant, as base64 or a file path), FORESTRIE_AUTHORITY_GRANT (your authority log's grant, for issue_grant), FORESTRIE_COORDINATOR_URL and FORESTRIE_KNOWN_SEALER_KEY (for delegate_sealing; the operator publishes both). Payment, never overridable per call: MCP_REGISTER_PAYER_KEY_FILE and the MCP_REGISTER_PAY_* caps. MCP_REGISTER_HOME holds the key files, the grants, the registrations and the spend ledger.";
7
+ /** Tool names, in registration order — the one list `server.ts` registers
8
+ * from and `cli.ts`'s `--help` prints, so the two cannot drift.
9
+ * `test/node/help.test.ts` asserts `--help` names exactly what `tools/list`
10
+ * returns. */
11
+ export declare const TOOL_NAMES: readonly ["get_log_key", "register_statement", "check_registration", "verify_registration", "delegate_sealing", "issue_grant", "register_grant"];
12
+ export type ToolName = (typeof TOOL_NAMES)[number];
13
+ export declare const TOOL_TITLES: Record<ToolName, string>;
14
+ export declare const TOOL_DESCRIPTIONS: Record<ToolName, string>;
15
+ /** Prompt names, in registration order. */
16
+ export declare const PROMPT_NAMES: readonly ["register", "audit", "issue-and-pay"];
17
+ export type PromptName = (typeof PROMPT_NAMES)[number];
18
+ export declare const PROMPT_TITLES: Record<PromptName, string>;
19
+ export declare const PROMPT_DESCRIPTIONS: Record<PromptName, string>;
20
+ export declare function registerPromptText(args: {
21
+ what?: string | undefined;
22
+ }): string;
23
+ export declare function auditPromptText(args: {
24
+ contentHash?: string | undefined;
25
+ }): string;
26
+ export declare function issueAndPayPromptText(args: {
27
+ purpose?: string | undefined;
28
+ }): string;
29
+ //# sourceMappingURL=text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../src/node/text.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,eAAO,MAAM,YAAY,m2DAQkiB,CAAC;AAE5jB;;;eAGe;AACf,eAAO,MAAM,UAAU,kJAQb,CAAC;AAEX,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnD,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAQhD,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAetD,CAAC;AAEF,2CAA2C;AAC3C,eAAO,MAAM,YAAY,iDAAkD,CAAC;AAE5E,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAIpD,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAO1D,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACvC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3B,GAAG,MAAM,CAmBT;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC,GAAG,MAAM,CAgBT;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC9B,GAAG,MAAM,CA4BT"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Outward-facing text for the MCP adapter: the server `instructions`, the
3
+ * per-tool titles and descriptions, and the prompts. Change the wording on
4
+ * purpose, never in passing.
5
+ */
6
+ export const INSTRUCTIONS = `@forestrie/mcp-register is the write path of the Forestrie MCP trio. It signs statements with a log key it holds (ES256, behind a signer backend; by default a local key file it creates), registers them on a Forestrie transparency log under a Forestrie-Grant, delegates sealing for your data log, and checks the receipt with @forestrie/mcp-verify's verifier through @forestrie/mcp-resolve.
7
+
8
+ It can also act as a requester that is its own grant issuer: issue_grant signs a child grant with a separate authority key, and register_grant registers it with canopy, paying canopy's x402 402 from a configured payer within hard caps, or handing you the payment requirements to pay from your own wallet (relay mode). Forestrie runs no grant issuer.
9
+
10
+ You never handle signature bytes or keys: you pass the payload, the server signs; the payer key never crosses this boundary, and the spending caps are configuration, not arguments.
11
+
12
+ The loop with a grant you hold: get_log_key → delegate_sealing once per log → register_statement → check_registration until sealed → verify_registration (the "register" prompt). Buying the grant first: issue_grant → register_grant until complete (the "issue-and-pay" prompt). The "audit" prompt explains what a verified receipt does and does not prove.
13
+
14
+ Configuration, each overridable per call: FORESTRIE_BASE_URL (the canopy SCRAPI base URL), FORESTRIE_ROOT_LOG_ID (the forest's root log id, R), FORESTRIE_GRANT (the grant, as base64 or a file path), FORESTRIE_AUTHORITY_GRANT (your authority log's grant, for issue_grant), FORESTRIE_COORDINATOR_URL and FORESTRIE_KNOWN_SEALER_KEY (for delegate_sealing; the operator publishes both). Payment, never overridable per call: MCP_REGISTER_PAYER_KEY_FILE and the MCP_REGISTER_PAY_* caps. MCP_REGISTER_HOME holds the key files, the grants, the registrations and the spend ledger.`;
15
+ /** Tool names, in registration order — the one list `server.ts` registers
16
+ * from and `cli.ts`'s `--help` prints, so the two cannot drift.
17
+ * `test/node/help.test.ts` asserts `--help` names exactly what `tools/list`
18
+ * returns. */
19
+ export const TOOL_NAMES = [
20
+ "get_log_key",
21
+ "register_statement",
22
+ "check_registration",
23
+ "verify_registration",
24
+ "delegate_sealing",
25
+ "issue_grant",
26
+ "register_grant",
27
+ ];
28
+ export const TOOL_TITLES = {
29
+ get_log_key: "Get the log key",
30
+ register_statement: "Sign and register a statement",
31
+ check_registration: "Check a registration",
32
+ verify_registration: "Verify a registration's receipt",
33
+ delegate_sealing: "Delegate sealing for your log",
34
+ issue_grant: "Issue a child grant as the requester",
35
+ register_grant: "Register a child grant, paying if canopy asks",
36
+ };
37
+ export const TOOL_DESCRIPTIONS = {
38
+ get_log_key: 'Return the public half of one of this server\'s keys (ES256, 64 bytes x‖y, as hex, base64 and an SPKI PEM) and where it lives, creating it on first use. which: "log" (the default) is the key that owns your data log: a grant issuer names it in the grant\'s grantData, and a grant naming any other key cannot carry statements this server signs. which: "authority" is the requester\'s key that owns your authority log and signs the grants issue_grant makes. Never returns private key material.',
39
+ register_statement: "Sign a payload with the log key and register it under your Forestrie-Grant. Takes the payload bytes (inline base64 or a file path, up to canopy's 4 MiB statement limit) and its content type. The signed statement is saved before it is sent, so the returned content hash always names it, even if the request fails. A payload path may not point at the log key or inside this server's home. Registration is accepted, not yet sequenced: call check_registration next.",
40
+ check_registration: "One status check for a registration, by its content hash: pending (not yet sequenced; retry after the given seconds), awaiting_seal (sequenced, entry id known, receipt not yet sealed — sealing takes minutes, and never happens until delegate_sealing has run for the log), or sealed (the receipt is saved and its path returned). Any other answer from canopy is reported as a problem with its reason. canopy answers a statement it never received exactly like a pending one, so after ten minutes of pending this says so. Does not sleep; pace the retries yourself.",
41
+ verify_registration: "Verify a sealed registration's receipt over the statement you registered, with @forestrie/mcp-verify's verifier, and return its four-question answers unaltered. By default the trust root is this server's own log key, the root of your data log; verifying never creates a key. Pass a known accumulator (from @forestrie/mcp-resolve's fetch_accumulator) to use a root that can also answer split-view.",
42
+ delegate_sealing: "Authorise the operator's sealer to publish checkpoints for your data log, signed with the log key. Without it entries sequence but are never sealed. If the coordinator has not learned the log's public root, this calls canopy's prepare with the same grant first. Run once per log; the delegation lasts until the returned expiresAt. With as: \"authority\" it does the same for your authority log, signed with the authority key, so the child grants you register there get sealed.",
43
+ issue_grant: "As the requester, sign a child grant with this server's authority key: a data-log creation grant under your authority log (named by your authority grant, FORESTRIE_AUTHORITY_GRANT), naming the performer's log key (by default this server's own) and sized with maxHeight, the batch ceiling canopy prices it by. Saves the grant under a fresh child log id; nothing is sent. Call register_grant next.",
44
+ register_grant: "Register a child grant with canopy (POST /register/{R}/grants, your authority grant as the parent) and follow it to its receipt; call it again with the same logId to resume until it reports complete. If canopy answers 402 and a payer is configured, it pays within the server's hard caps (per call, per UTC day, one network and asset, testnet only by default) and reports any cap that refuses. With no payer it returns the payment requirements: pay them from your own x402 wallet and call again with payment set to the X-PAYMENT value. Every payment for a grant carries a nonce derived from that grant, so retries and overlapping calls can never pay for it twice. When complete it returns the completed grant for delegate_sealing and register_statement.",
45
+ };
46
+ /** Prompt names, in registration order. */
47
+ export const PROMPT_NAMES = ["register", "audit", "issue-and-pay"];
48
+ export const PROMPT_TITLES = {
49
+ register: "Register a statement and keep the receipt",
50
+ audit: "Audit a receipt: trust roots and the four questions",
51
+ "issue-and-pay": "Issue a child grant and pay to register it",
52
+ };
53
+ export const PROMPT_DESCRIPTIONS = {
54
+ register: "Walk through checking the key and grant, delegating sealing, registering, waiting for the seal, verifying and keeping the receipt.",
55
+ audit: "Explain which of the four trust questions a receipt answers under each trust root, and how to choose a root.",
56
+ "issue-and-pay": "As the requester, size and sign a child grant, register it with canopy, pay its 402 within the caps or relay the payment, and hand the completed grant to the performer.",
57
+ };
58
+ export function registerPromptText(args) {
59
+ const subject = args.what?.trim()
60
+ ? `The statement to register: ${args.what.trim()}.`
61
+ : "If it is not already clear what is to be registered, ask.";
62
+ return `Register a statement on a Forestrie transparency log with the @forestrie/mcp-register tools, and keep a receipt anyone can verify later. ${subject}
63
+
64
+ You never handle signature bytes. Pass the payload; the server signs with its log key.
65
+
66
+ 1. Key and grant. Call get_log_key. The grant you register under must name exactly that key (its grantData is the 64-byte x‖y get_log_key returns) and must be for a data log. If you do not hold such a grant and you own an authority log yourself, buy one with the issue-and-pay prompt. Otherwise stop: give the public key to the grant issuer for your log (the owner of the authority log above it) and ask for one. Forestrie issues no grants itself.
67
+
68
+ 2. Sealing, once per log. Call delegate_sealing. It authorises the operator's sealer for your data log; if the coordinator never learned the log's public root it runs canopy's prepare with the same grant first. Without it entries are sequenced but never sealed, and check_registration stays at awaiting_seal. If it reports a problem, report its stage and message; do not retry in a loop.
69
+
70
+ 3. Register. Call register_statement with the payload and an accurate content type (application/json, text/plain, application/octet-stream, …). The signed statement may be up to 4 MiB. For anything larger, register a small manifest instead: a JSON document naming the artefact, its size and its sha-256 digest. The receipt then commits to the digest, and the artefact can live anywhere.
71
+
72
+ 4. Wait. Call check_registration with the content hash. pending: wait the suggested seconds and check again. awaiting_seal: the entry is sequenced; sealing typically takes minutes, so check about once a minute. sealed: the receipt is saved and its path returned.
73
+
74
+ 5. Verify. Call verify_registration and report every one of the four answers exactly as returned, with the trust root it used. For what each root can and cannot answer, use the audit prompt.
75
+
76
+ 6. Keep. Tell the user the content hash, the entry id, and the paths of the signed statement and the receipt. With a trust root, those let anyone re-verify offline with @forestrie/mcp-verify, without this server or Forestrie.`;
77
+ }
78
+ export function auditPromptText(args) {
79
+ const which = args.contentHash?.trim()
80
+ ? `The registration to audit: content hash ${args.contentHash.trim()}.`
81
+ : "Audit the registration the user names, or ask which.";
82
+ return `Audit a Forestrie receipt: report which of the four trust questions it answers, and under which trust root. ${which}
83
+
84
+ The verifier (@forestrie/mcp-verify) answers four questions — sealing, split-view, append-authority and attribution — each ok, failed, or not_answered_by_this_root. A trust root is what you take on faith, and each root answers some questions and not others. The roots are not ranked: "not answered by this root" is a real answer, not a failure, and it must reach the user.
85
+
86
+ Choosing a root:
87
+ - Your own log key (verify_registration's default). You hold the root of your data log, so this checks the receipt against the key that delegated its sealing. Like any signature root, it cannot see a split view: it cannot tell you that others are shown the same log.
88
+ - A known accumulator: a snapshot of the log's peaks read from the chain the forest is anchored to, which the operator does not control. Get one with @forestrie/mcp-resolve's fetch_accumulator (you need your own RPC access) and pass it to verify_registration, or let @forestrie/mcp-resolve's verify_fetched_receipt read and verify in one call. This kind of root can answer split-view.
89
+ - A retained checkpoint chain also answers split-view; the forest genesis document names the operator's bootstrap key. See https://github.com/forestrie/mcp-verify/blob/main/docs/trust-roots.md for when each root applies.
90
+
91
+ Reporting: list each question with its status exactly as the verifier returned it, name the root, and say where the root came from (this server's key file, a chain read at a given block, someone else). Never turn "not answered" into "ok", and never edit the verifier's answers. If verification failed, quote the failing stage and its reason.
92
+
93
+ Anyone holding the signed statement, the receipt and a root can re-run the check offline with @forestrie/mcp-verify. The receipt is evidence on its own; it does not depend on trusting this server.`;
94
+ }
95
+ export function issueAndPayPromptText(args) {
96
+ const purpose = args.purpose?.trim()
97
+ ? `What the grant is for: ${args.purpose.trim()}.`
98
+ : "If it is not clear what the grant is for, and so how large it must be, ask.";
99
+ return `Buy a grant on a Forestrie transparency log as the requester, with the @forestrie/mcp-register tools: sign a child grant under your own authority log, register it with canopy, pay if canopy asks, and hand the completed grant to whoever will write under it. ${purpose}
100
+
101
+ Forestrie runs no grant issuer. You are the issuer: your authority key owns an authority log that the owner of the root above you created for you once. If that log carries the child-payment-required flag, every child grant registered under it is paid for over x402, and the payment goes straight to the transparency operator.
102
+
103
+ You never handle keys or signature bytes. The server signs grants with its authority key and pays with its own payer; you never see either key, and you cannot raise its spending caps.
104
+
105
+ 1. Authority. Call get_log_key with which: "authority". You need an authority grant naming that key (FORESTRIE_AUTHORITY_GRANT, or pass parentGrant). If you have none, stop: give the publicKeyPem to the owner of the root above you and ask them to create your authority log for it. Its sealing must be delegated once, with delegate_sealing as: "authority" and that grant, or your child grants are sequenced but never sealed.
106
+
107
+ 2. Size. maxHeight is the grant's batch ceiling, and canopy prices a paid child grant as its unit price × maxHeight. Size it to the work it is for, no larger: a bigger grant costs more, and the server's per-call cap refuses one it cannot afford.
108
+
109
+ 3. Issue. Call issue_grant with maxHeight. By default it names this server's own log key, so the same agent writes (a self-purchase); pass grantee (base64 of a 64-byte x‖y) to issue to another agent's log key. Keep the logId it returns.
110
+
111
+ 4. Register and pay. Call register_grant with that logId.
112
+ - accepted: canopy took the grant (and the payment, if it asked for one). Go to 5.
113
+ - payment_required, no payer configured (relay mode): show the user the amount, asset, network and payee. Pay only with a wallet the user has authorised for this; give it the paymentRequiredHeader, then call register_grant again with the same logId and payment set to the X-PAYMENT value it returns. Never invent or edit a payment value.
114
+ - a cap_… problem: the server's own limit refused the payment. Report which cap and the amounts. Do not try to get around it: do not split the grant to slip under a cap, switch canopy, or retry in a loop. A smaller grant (step 2) or the user's decision are the ways forward.
115
+ - payment_rejected: canopy refused the payment; report its reason. Nothing was registered.
116
+ - a network error: call register_grant again with the same logId. It signs with the grant's own nonce again, so at most one payment can ever settle.
117
+
118
+ 5. Wait. Call register_grant with the logId until it reports complete: pending and awaiting_seal mean wait (seconds, then about a minute) and call again. Sealing takes minutes.
119
+
120
+ 6. Hand over. complete returns grantBase64, the completed grant. If it names this server's log key, continue with the register prompt: delegate_sealing with that grant, then register_statement. Otherwise give it to the performer whose key it names.
121
+
122
+ Report what was paid, to whom, on which network, and the logId: the spend is recorded in this server's ledger, and the operator's receipt for the grant is the public record of the purchase.`;
123
+ }
124
+ //# sourceMappingURL=text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/node/text.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,CAAC,MAAM,YAAY,GAAG;;;;;;;;2jBAQ+hB,CAAC;AAE5jB;;;eAGe;AACf,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,aAAa;IACb,oBAAoB;IACpB,oBAAoB;IACpB,qBAAqB;IACrB,kBAAkB;IAClB,aAAa;IACb,gBAAgB;CACR,CAAC;AAIX,MAAM,CAAC,MAAM,WAAW,GAA6B;IACnD,WAAW,EAAE,iBAAiB;IAC9B,kBAAkB,EAAE,+BAA+B;IACnD,kBAAkB,EAAE,sBAAsB;IAC1C,mBAAmB,EAAE,iCAAiC;IACtD,gBAAgB,EAAE,+BAA+B;IACjD,WAAW,EAAE,sCAAsC;IACnD,cAAc,EAAE,+CAA+C;CAChE,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAA6B;IACzD,WAAW,EACT,4eAA4e;IAC9e,kBAAkB,EAChB,+cAA+c;IACjd,kBAAkB,EAChB,ijBAAijB;IACnjB,mBAAmB,EACjB,8YAA8Y;IAChZ,gBAAgB,EACd,8dAA8d;IAChe,WAAW,EACT,6YAA6Y;IAC/Y,cAAc,EACZ,kvBAAkvB;CACrvB,CAAC;AAEF,2CAA2C;AAC3C,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,eAAe,CAAU,CAAC;AAI5E,MAAM,CAAC,MAAM,aAAa,GAA+B;IACvD,QAAQ,EAAE,2CAA2C;IACrD,KAAK,EAAE,qDAAqD;IAC5D,eAAe,EAAE,4CAA4C;CAC9D,CAAC;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAA+B;IAC7D,QAAQ,EACN,oIAAoI;IACtI,KAAK,EACH,8GAA8G;IAChH,eAAe,EACb,0KAA0K;CAC7K,CAAC;AAEF,MAAM,UAAU,kBAAkB,CAAC,IAElC;IACC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE;QAC/B,CAAC,CAAC,8BAA8B,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG;QACnD,CAAC,CAAC,2DAA2D,CAAC;IAChE,OAAO,4IAA4I,OAAO;;;;;;;;;;;;;;kOAcsE,CAAC;AACnO,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAE/B;IACC,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,IAAI,EAAE;QACpC,CAAC,CAAC,2CAA2C,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,GAAG;QACvE,CAAC,CAAC,sDAAsD,CAAC;IAC3D,OAAO,+GAA+G,KAAK;;;;;;;;;;;qMAWwE,CAAC;AACtM,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,IAErC;IACC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE;QAClC,CAAC,CAAC,0BAA0B,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG;QAClD,CAAC,CAAC,6EAA6E,CAAC;IAClF,OAAO,oQAAoQ,OAAO;;;;;;;;;;;;;;;;;;;;;;;8LAuBtF,CAAC;AAC/L,CAAC"}