@nanorail/sdk 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.
- package/LICENSE +21 -0
- package/README.md +66 -0
- package/dist/index.d.ts +488 -0
- package/dist/index.mjs +1668 -0
- package/dist/nanorail.js +1668 -0
- package/package.json +67 -0
package/dist/nanorail.js
ADDED
|
@@ -0,0 +1,1668 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
(() => {
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
5
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
6
|
+
|
|
7
|
+
// ../shared/dist/money.js
|
|
8
|
+
var MICROS_PER_USD = 1e6;
|
|
9
|
+
var MICRO_DECIMALS = 6;
|
|
10
|
+
function toMicros(decimal) {
|
|
11
|
+
const s = typeof decimal === "number" ? decimal.toString() : decimal.trim();
|
|
12
|
+
if (!/^-?\d+(\.\d+)?$/.test(s)) {
|
|
13
|
+
throw new Error(`Invalid money string: "${decimal}"`);
|
|
14
|
+
}
|
|
15
|
+
const negative = s.startsWith("-");
|
|
16
|
+
const unsigned = negative ? s.slice(1) : s;
|
|
17
|
+
const [whole, frac = ""] = unsigned.split(".");
|
|
18
|
+
const fracPadded = (frac + "0".repeat(MICRO_DECIMALS)).slice(0, MICRO_DECIMALS);
|
|
19
|
+
const micros = Number(whole) * MICROS_PER_USD + Number(fracPadded || "0");
|
|
20
|
+
return negative ? -micros : micros;
|
|
21
|
+
}
|
|
22
|
+
function fromMicros(micros) {
|
|
23
|
+
const negative = micros < 0;
|
|
24
|
+
const abs = Math.abs(Math.round(micros));
|
|
25
|
+
const whole = Math.floor(abs / MICROS_PER_USD);
|
|
26
|
+
const frac = abs % MICROS_PER_USD;
|
|
27
|
+
let out;
|
|
28
|
+
if (frac === 0) {
|
|
29
|
+
out = `${whole}`;
|
|
30
|
+
} else {
|
|
31
|
+
const fracStr = frac.toString().padStart(MICRO_DECIMALS, "0").replace(/0+$/, "");
|
|
32
|
+
out = `${whole}.${fracStr}`;
|
|
33
|
+
}
|
|
34
|
+
return negative ? `-${out}` : out;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ../shared/dist/policy.js
|
|
38
|
+
function evaluatePurchase(policy, ctx) {
|
|
39
|
+
const { publisherId, chunkType, amountMicros, articleSpentMicros, sessionSpentMicros } = ctx;
|
|
40
|
+
if (policy.blockedContentTypes.includes(chunkType)) {
|
|
41
|
+
return block(`${chunkType} content is not allowed`);
|
|
42
|
+
}
|
|
43
|
+
if (!policy.allowedContentTypes.includes(chunkType)) {
|
|
44
|
+
return block(`content type "${chunkType}" is not in the allow-list`);
|
|
45
|
+
}
|
|
46
|
+
if (policy.allowedPublishers.length > 0 && !policy.allowedPublishers.includes(publisherId)) {
|
|
47
|
+
return block(`publisher "${publisherId}" is not allowed`);
|
|
48
|
+
}
|
|
49
|
+
const perChunk = toMicros(policy.maxSpendPerChunk);
|
|
50
|
+
if (amountMicros > perChunk) {
|
|
51
|
+
return block(`amount ${fromMicros(amountMicros)} exceeds per-chunk limit ${policy.maxSpendPerChunk}`);
|
|
52
|
+
}
|
|
53
|
+
const perArticle = toMicros(policy.maxSpendPerArticle);
|
|
54
|
+
if (articleSpentMicros + amountMicros > perArticle) {
|
|
55
|
+
return block(`would exceed per-article limit ${policy.maxSpendPerArticle}`);
|
|
56
|
+
}
|
|
57
|
+
const sessionBudget = toMicros(policy.sessionBudget);
|
|
58
|
+
if (sessionSpentMicros + amountMicros > sessionBudget) {
|
|
59
|
+
return block(`would exceed session budget ${policy.sessionBudget}`);
|
|
60
|
+
}
|
|
61
|
+
const manualThreshold = toMicros(policy.requireManualApprovalAbove);
|
|
62
|
+
const requiresManualApproval = !policy.autoPayEnabled || amountMicros > manualThreshold;
|
|
63
|
+
return { allowed: true, requiresManualApproval };
|
|
64
|
+
}
|
|
65
|
+
function block(reason) {
|
|
66
|
+
return { allowed: false, requiresManualApproval: false, reason };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ../../node_modules/@noble/ed25519/index.js
|
|
70
|
+
var ed25519_CURVE = {
|
|
71
|
+
p: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffedn,
|
|
72
|
+
n: 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3edn,
|
|
73
|
+
h: 8n,
|
|
74
|
+
a: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffecn,
|
|
75
|
+
d: 0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3n,
|
|
76
|
+
Gx: 0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51an,
|
|
77
|
+
Gy: 0x6666666666666666666666666666666666666666666666666666666666666658n
|
|
78
|
+
};
|
|
79
|
+
var { p: P, n: N, Gx, Gy, a: _a, d: _d } = ed25519_CURVE;
|
|
80
|
+
var h = 8n;
|
|
81
|
+
var L = 32;
|
|
82
|
+
var L2 = 64;
|
|
83
|
+
var err = (m = "") => {
|
|
84
|
+
throw new Error(m);
|
|
85
|
+
};
|
|
86
|
+
var isBig = (n) => typeof n === "bigint";
|
|
87
|
+
var isStr = (s) => typeof s === "string";
|
|
88
|
+
var isBytes = (a) => a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
|
|
89
|
+
var abytes = (a, l) => !isBytes(a) || typeof l === "number" && l > 0 && a.length !== l ? err("Uint8Array expected") : a;
|
|
90
|
+
var u8n = (len) => new Uint8Array(len);
|
|
91
|
+
var u8fr = (buf) => Uint8Array.from(buf);
|
|
92
|
+
var padh = (n, pad) => n.toString(16).padStart(pad, "0");
|
|
93
|
+
var bytesToHex = (b) => Array.from(abytes(b)).map((e) => padh(e, 2)).join("");
|
|
94
|
+
var C = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 };
|
|
95
|
+
var _ch = (ch) => {
|
|
96
|
+
if (ch >= C._0 && ch <= C._9)
|
|
97
|
+
return ch - C._0;
|
|
98
|
+
if (ch >= C.A && ch <= C.F)
|
|
99
|
+
return ch - (C.A - 10);
|
|
100
|
+
if (ch >= C.a && ch <= C.f)
|
|
101
|
+
return ch - (C.a - 10);
|
|
102
|
+
return;
|
|
103
|
+
};
|
|
104
|
+
var hexToBytes = (hex) => {
|
|
105
|
+
const e = "hex invalid";
|
|
106
|
+
if (!isStr(hex))
|
|
107
|
+
return err(e);
|
|
108
|
+
const hl = hex.length;
|
|
109
|
+
const al = hl / 2;
|
|
110
|
+
if (hl % 2)
|
|
111
|
+
return err(e);
|
|
112
|
+
const array = u8n(al);
|
|
113
|
+
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
|
|
114
|
+
const n1 = _ch(hex.charCodeAt(hi));
|
|
115
|
+
const n2 = _ch(hex.charCodeAt(hi + 1));
|
|
116
|
+
if (n1 === void 0 || n2 === void 0)
|
|
117
|
+
return err(e);
|
|
118
|
+
array[ai] = n1 * 16 + n2;
|
|
119
|
+
}
|
|
120
|
+
return array;
|
|
121
|
+
};
|
|
122
|
+
var toU8 = (a, len) => abytes(isStr(a) ? hexToBytes(a) : u8fr(abytes(a)), len);
|
|
123
|
+
var cr = () => globalThis?.crypto;
|
|
124
|
+
var subtle = () => cr()?.subtle ?? err("crypto.subtle must be defined");
|
|
125
|
+
var concatBytes = (...arrs) => {
|
|
126
|
+
const r = u8n(arrs.reduce((sum, a) => sum + abytes(a).length, 0));
|
|
127
|
+
let pad = 0;
|
|
128
|
+
arrs.forEach((a) => {
|
|
129
|
+
r.set(a, pad);
|
|
130
|
+
pad += a.length;
|
|
131
|
+
});
|
|
132
|
+
return r;
|
|
133
|
+
};
|
|
134
|
+
var randomBytes = (len = L) => {
|
|
135
|
+
const c = cr();
|
|
136
|
+
return c.getRandomValues(u8n(len));
|
|
137
|
+
};
|
|
138
|
+
var big = BigInt;
|
|
139
|
+
var arange = (n, min, max, msg = "bad number: out of range") => isBig(n) && min <= n && n < max ? n : err(msg);
|
|
140
|
+
var M = (a, b = P) => {
|
|
141
|
+
const r = a % b;
|
|
142
|
+
return r >= 0n ? r : b + r;
|
|
143
|
+
};
|
|
144
|
+
var modN = (a) => M(a, N);
|
|
145
|
+
var invert = (num, md) => {
|
|
146
|
+
if (num === 0n || md <= 0n)
|
|
147
|
+
err("no inverse n=" + num + " mod=" + md);
|
|
148
|
+
let a = M(num, md), b = md, x = 0n, y = 1n, u = 1n, v = 0n;
|
|
149
|
+
while (a !== 0n) {
|
|
150
|
+
const q = b / a, r = b % a;
|
|
151
|
+
const m = x - u * q, n = y - v * q;
|
|
152
|
+
b = a, a = r, x = u, y = v, u = m, v = n;
|
|
153
|
+
}
|
|
154
|
+
return b === 1n ? M(x, md) : err("no inverse");
|
|
155
|
+
};
|
|
156
|
+
var callHash = (name) => {
|
|
157
|
+
const fn = etc[name];
|
|
158
|
+
if (typeof fn !== "function")
|
|
159
|
+
err("hashes." + name + " not set");
|
|
160
|
+
return fn;
|
|
161
|
+
};
|
|
162
|
+
var apoint = (p) => p instanceof Point ? p : err("Point expected");
|
|
163
|
+
var B256 = 2n ** 256n;
|
|
164
|
+
var _Point = class _Point {
|
|
165
|
+
constructor(ex, ey, ez, et) {
|
|
166
|
+
__publicField(this, "ex");
|
|
167
|
+
__publicField(this, "ey");
|
|
168
|
+
__publicField(this, "ez");
|
|
169
|
+
__publicField(this, "et");
|
|
170
|
+
const max = B256;
|
|
171
|
+
this.ex = arange(ex, 0n, max);
|
|
172
|
+
this.ey = arange(ey, 0n, max);
|
|
173
|
+
this.ez = arange(ez, 1n, max);
|
|
174
|
+
this.et = arange(et, 0n, max);
|
|
175
|
+
Object.freeze(this);
|
|
176
|
+
}
|
|
177
|
+
static fromAffine(p) {
|
|
178
|
+
return new _Point(p.x, p.y, 1n, M(p.x * p.y));
|
|
179
|
+
}
|
|
180
|
+
/** RFC8032 5.1.3: Uint8Array to Point. */
|
|
181
|
+
static fromBytes(hex, zip215 = false) {
|
|
182
|
+
const d = _d;
|
|
183
|
+
const normed = u8fr(abytes(hex, L));
|
|
184
|
+
const lastByte = hex[31];
|
|
185
|
+
normed[31] = lastByte & ~128;
|
|
186
|
+
const y = bytesToNumLE(normed);
|
|
187
|
+
const max = zip215 ? B256 : P;
|
|
188
|
+
arange(y, 0n, max);
|
|
189
|
+
const y2 = M(y * y);
|
|
190
|
+
const u = M(y2 - 1n);
|
|
191
|
+
const v = M(d * y2 + 1n);
|
|
192
|
+
let { isValid, value: x } = uvRatio(u, v);
|
|
193
|
+
if (!isValid)
|
|
194
|
+
err("bad point: y not sqrt");
|
|
195
|
+
const isXOdd = (x & 1n) === 1n;
|
|
196
|
+
const isLastByteOdd = (lastByte & 128) !== 0;
|
|
197
|
+
if (!zip215 && x === 0n && isLastByteOdd)
|
|
198
|
+
err("bad point: x==0, isLastByteOdd");
|
|
199
|
+
if (isLastByteOdd !== isXOdd)
|
|
200
|
+
x = M(-x);
|
|
201
|
+
return new _Point(x, y, 1n, M(x * y));
|
|
202
|
+
}
|
|
203
|
+
/** Checks if the point is valid and on-curve. */
|
|
204
|
+
assertValidity() {
|
|
205
|
+
const a = _a;
|
|
206
|
+
const d = _d;
|
|
207
|
+
const p = this;
|
|
208
|
+
if (p.is0())
|
|
209
|
+
throw new Error("bad point: ZERO");
|
|
210
|
+
const { ex: X, ey: Y, ez: Z, et: T } = p;
|
|
211
|
+
const X2 = M(X * X);
|
|
212
|
+
const Y2 = M(Y * Y);
|
|
213
|
+
const Z2 = M(Z * Z);
|
|
214
|
+
const Z4 = M(Z2 * Z2);
|
|
215
|
+
const aX2 = M(X2 * a);
|
|
216
|
+
const left = M(Z2 * M(aX2 + Y2));
|
|
217
|
+
const right = M(Z4 + M(d * M(X2 * Y2)));
|
|
218
|
+
if (left !== right)
|
|
219
|
+
throw new Error("bad point: equation left != right (1)");
|
|
220
|
+
const XY = M(X * Y);
|
|
221
|
+
const ZT = M(Z * T);
|
|
222
|
+
if (XY !== ZT)
|
|
223
|
+
throw new Error("bad point: equation left != right (2)");
|
|
224
|
+
return this;
|
|
225
|
+
}
|
|
226
|
+
/** Equality check: compare points P&Q. */
|
|
227
|
+
equals(other) {
|
|
228
|
+
const { ex: X1, ey: Y1, ez: Z1 } = this;
|
|
229
|
+
const { ex: X2, ey: Y2, ez: Z2 } = apoint(other);
|
|
230
|
+
const X1Z2 = M(X1 * Z2);
|
|
231
|
+
const X2Z1 = M(X2 * Z1);
|
|
232
|
+
const Y1Z2 = M(Y1 * Z2);
|
|
233
|
+
const Y2Z1 = M(Y2 * Z1);
|
|
234
|
+
return X1Z2 === X2Z1 && Y1Z2 === Y2Z1;
|
|
235
|
+
}
|
|
236
|
+
is0() {
|
|
237
|
+
return this.equals(I);
|
|
238
|
+
}
|
|
239
|
+
/** Flip point over y coordinate. */
|
|
240
|
+
negate() {
|
|
241
|
+
return new _Point(M(-this.ex), this.ey, this.ez, M(-this.et));
|
|
242
|
+
}
|
|
243
|
+
/** Point doubling. Complete formula. Cost: `4M + 4S + 1*a + 6add + 1*2`. */
|
|
244
|
+
double() {
|
|
245
|
+
const { ex: X1, ey: Y1, ez: Z1 } = this;
|
|
246
|
+
const a = _a;
|
|
247
|
+
const A = M(X1 * X1);
|
|
248
|
+
const B = M(Y1 * Y1);
|
|
249
|
+
const C2 = M(2n * M(Z1 * Z1));
|
|
250
|
+
const D = M(a * A);
|
|
251
|
+
const x1y1 = X1 + Y1;
|
|
252
|
+
const E = M(M(x1y1 * x1y1) - A - B);
|
|
253
|
+
const G2 = D + B;
|
|
254
|
+
const F = G2 - C2;
|
|
255
|
+
const H = D - B;
|
|
256
|
+
const X3 = M(E * F);
|
|
257
|
+
const Y3 = M(G2 * H);
|
|
258
|
+
const T3 = M(E * H);
|
|
259
|
+
const Z3 = M(F * G2);
|
|
260
|
+
return new _Point(X3, Y3, Z3, T3);
|
|
261
|
+
}
|
|
262
|
+
/** Point addition. Complete formula. Cost: `8M + 1*k + 8add + 1*2`. */
|
|
263
|
+
add(other) {
|
|
264
|
+
const { ex: X1, ey: Y1, ez: Z1, et: T1 } = this;
|
|
265
|
+
const { ex: X2, ey: Y2, ez: Z2, et: T2 } = apoint(other);
|
|
266
|
+
const a = _a;
|
|
267
|
+
const d = _d;
|
|
268
|
+
const A = M(X1 * X2);
|
|
269
|
+
const B = M(Y1 * Y2);
|
|
270
|
+
const C2 = M(T1 * d * T2);
|
|
271
|
+
const D = M(Z1 * Z2);
|
|
272
|
+
const E = M((X1 + Y1) * (X2 + Y2) - A - B);
|
|
273
|
+
const F = M(D - C2);
|
|
274
|
+
const G2 = M(D + C2);
|
|
275
|
+
const H = M(B - a * A);
|
|
276
|
+
const X3 = M(E * F);
|
|
277
|
+
const Y3 = M(G2 * H);
|
|
278
|
+
const T3 = M(E * H);
|
|
279
|
+
const Z3 = M(F * G2);
|
|
280
|
+
return new _Point(X3, Y3, Z3, T3);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Point-by-scalar multiplication. Scalar must be in range 1 <= n < CURVE.n.
|
|
284
|
+
* Uses {@link wNAF} for base point.
|
|
285
|
+
* Uses fake point to mitigate side-channel leakage.
|
|
286
|
+
* @param n scalar by which point is multiplied
|
|
287
|
+
* @param safe safe mode guards against timing attacks; unsafe mode is faster
|
|
288
|
+
*/
|
|
289
|
+
multiply(n, safe = true) {
|
|
290
|
+
if (!safe && (n === 0n || this.is0()))
|
|
291
|
+
return I;
|
|
292
|
+
arange(n, 1n, N);
|
|
293
|
+
if (n === 1n)
|
|
294
|
+
return this;
|
|
295
|
+
if (this.equals(G))
|
|
296
|
+
return wNAF(n).p;
|
|
297
|
+
let p = I;
|
|
298
|
+
let f = G;
|
|
299
|
+
for (let d = this; n > 0n; d = d.double(), n >>= 1n) {
|
|
300
|
+
if (n & 1n)
|
|
301
|
+
p = p.add(d);
|
|
302
|
+
else if (safe)
|
|
303
|
+
f = f.add(d);
|
|
304
|
+
}
|
|
305
|
+
return p;
|
|
306
|
+
}
|
|
307
|
+
/** Convert point to 2d xy affine point. (X, Y, Z) ∋ (x=X/Z, y=Y/Z) */
|
|
308
|
+
toAffine() {
|
|
309
|
+
const { ex: x, ey: y, ez: z } = this;
|
|
310
|
+
if (this.equals(I))
|
|
311
|
+
return { x: 0n, y: 1n };
|
|
312
|
+
const iz = invert(z, P);
|
|
313
|
+
if (M(z * iz) !== 1n)
|
|
314
|
+
err("invalid inverse");
|
|
315
|
+
return { x: M(x * iz), y: M(y * iz) };
|
|
316
|
+
}
|
|
317
|
+
toBytes() {
|
|
318
|
+
const { x, y } = this.assertValidity().toAffine();
|
|
319
|
+
const b = numTo32bLE(y);
|
|
320
|
+
b[31] |= x & 1n ? 128 : 0;
|
|
321
|
+
return b;
|
|
322
|
+
}
|
|
323
|
+
toHex() {
|
|
324
|
+
return bytesToHex(this.toBytes());
|
|
325
|
+
}
|
|
326
|
+
// encode to hex string
|
|
327
|
+
clearCofactor() {
|
|
328
|
+
return this.multiply(big(h), false);
|
|
329
|
+
}
|
|
330
|
+
isSmallOrder() {
|
|
331
|
+
return this.clearCofactor().is0();
|
|
332
|
+
}
|
|
333
|
+
isTorsionFree() {
|
|
334
|
+
let p = this.multiply(N / 2n, false).double();
|
|
335
|
+
if (N % 2n)
|
|
336
|
+
p = p.add(this);
|
|
337
|
+
return p.is0();
|
|
338
|
+
}
|
|
339
|
+
static fromHex(hex, zip215) {
|
|
340
|
+
return _Point.fromBytes(toU8(hex), zip215);
|
|
341
|
+
}
|
|
342
|
+
get x() {
|
|
343
|
+
return this.toAffine().x;
|
|
344
|
+
}
|
|
345
|
+
get y() {
|
|
346
|
+
return this.toAffine().y;
|
|
347
|
+
}
|
|
348
|
+
toRawBytes() {
|
|
349
|
+
return this.toBytes();
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
__publicField(_Point, "BASE");
|
|
353
|
+
__publicField(_Point, "ZERO");
|
|
354
|
+
var Point = _Point;
|
|
355
|
+
var G = new Point(Gx, Gy, 1n, M(Gx * Gy));
|
|
356
|
+
var I = new Point(0n, 1n, 1n, 0n);
|
|
357
|
+
Point.BASE = G;
|
|
358
|
+
Point.ZERO = I;
|
|
359
|
+
var numTo32bLE = (num) => hexToBytes(padh(arange(num, 0n, B256), L2)).reverse();
|
|
360
|
+
var bytesToNumLE = (b) => big("0x" + bytesToHex(u8fr(abytes(b)).reverse()));
|
|
361
|
+
var pow2 = (x, power) => {
|
|
362
|
+
let r = x;
|
|
363
|
+
while (power-- > 0n) {
|
|
364
|
+
r *= r;
|
|
365
|
+
r %= P;
|
|
366
|
+
}
|
|
367
|
+
return r;
|
|
368
|
+
};
|
|
369
|
+
var pow_2_252_3 = (x) => {
|
|
370
|
+
const x2 = x * x % P;
|
|
371
|
+
const b2 = x2 * x % P;
|
|
372
|
+
const b4 = pow2(b2, 2n) * b2 % P;
|
|
373
|
+
const b5 = pow2(b4, 1n) * x % P;
|
|
374
|
+
const b10 = pow2(b5, 5n) * b5 % P;
|
|
375
|
+
const b20 = pow2(b10, 10n) * b10 % P;
|
|
376
|
+
const b40 = pow2(b20, 20n) * b20 % P;
|
|
377
|
+
const b80 = pow2(b40, 40n) * b40 % P;
|
|
378
|
+
const b160 = pow2(b80, 80n) * b80 % P;
|
|
379
|
+
const b240 = pow2(b160, 80n) * b80 % P;
|
|
380
|
+
const b250 = pow2(b240, 10n) * b10 % P;
|
|
381
|
+
const pow_p_5_8 = pow2(b250, 2n) * x % P;
|
|
382
|
+
return { pow_p_5_8, b2 };
|
|
383
|
+
};
|
|
384
|
+
var RM1 = 0x2b8324804fc1df0b2b4d00993dfbd7a72f431806ad2fe478c4ee1b274a0ea0b0n;
|
|
385
|
+
var uvRatio = (u, v) => {
|
|
386
|
+
const v3 = M(v * v * v);
|
|
387
|
+
const v7 = M(v3 * v3 * v);
|
|
388
|
+
const pow = pow_2_252_3(u * v7).pow_p_5_8;
|
|
389
|
+
let x = M(u * v3 * pow);
|
|
390
|
+
const vx2 = M(v * x * x);
|
|
391
|
+
const root1 = x;
|
|
392
|
+
const root2 = M(x * RM1);
|
|
393
|
+
const useRoot1 = vx2 === u;
|
|
394
|
+
const useRoot2 = vx2 === M(-u);
|
|
395
|
+
const noRoot = vx2 === M(-u * RM1);
|
|
396
|
+
if (useRoot1)
|
|
397
|
+
x = root1;
|
|
398
|
+
if (useRoot2 || noRoot)
|
|
399
|
+
x = root2;
|
|
400
|
+
if ((M(x) & 1n) === 1n)
|
|
401
|
+
x = M(-x);
|
|
402
|
+
return { isValid: useRoot1 || useRoot2, value: x };
|
|
403
|
+
};
|
|
404
|
+
var modL_LE = (hash) => modN(bytesToNumLE(hash));
|
|
405
|
+
var sha512a = (...m) => etc.sha512Async(...m);
|
|
406
|
+
var sha512s = (...m) => callHash("sha512Sync")(...m);
|
|
407
|
+
var hash2extK = (hashed) => {
|
|
408
|
+
const head = hashed.slice(0, L);
|
|
409
|
+
head[0] &= 248;
|
|
410
|
+
head[31] &= 127;
|
|
411
|
+
head[31] |= 64;
|
|
412
|
+
const prefix = hashed.slice(L, L2);
|
|
413
|
+
const scalar = modL_LE(head);
|
|
414
|
+
const point = G.multiply(scalar);
|
|
415
|
+
const pointBytes = point.toBytes();
|
|
416
|
+
return { head, prefix, scalar, point, pointBytes };
|
|
417
|
+
};
|
|
418
|
+
var getExtendedPublicKeyAsync = (priv) => sha512a(toU8(priv, L)).then(hash2extK);
|
|
419
|
+
var getExtendedPublicKey = (priv) => hash2extK(sha512s(toU8(priv, L)));
|
|
420
|
+
var getPublicKeyAsync = (priv) => getExtendedPublicKeyAsync(priv).then((p) => p.pointBytes);
|
|
421
|
+
var hashFinishA = (res) => sha512a(res.hashable).then(res.finish);
|
|
422
|
+
var _sign = (e, rBytes, msg) => {
|
|
423
|
+
const { pointBytes: P2, scalar: s } = e;
|
|
424
|
+
const r = modL_LE(rBytes);
|
|
425
|
+
const R = G.multiply(r).toBytes();
|
|
426
|
+
const hashable = concatBytes(R, P2, msg);
|
|
427
|
+
const finish = (hashed) => {
|
|
428
|
+
const S = modN(r + modL_LE(hashed) * s);
|
|
429
|
+
return abytes(concatBytes(R, numTo32bLE(S)), L2);
|
|
430
|
+
};
|
|
431
|
+
return { hashable, finish };
|
|
432
|
+
};
|
|
433
|
+
var signAsync = async (msg, privKey) => {
|
|
434
|
+
const m = toU8(msg);
|
|
435
|
+
const e = await getExtendedPublicKeyAsync(privKey);
|
|
436
|
+
const rBytes = await sha512a(e.prefix, m);
|
|
437
|
+
return hashFinishA(_sign(e, rBytes, m));
|
|
438
|
+
};
|
|
439
|
+
var etc = {
|
|
440
|
+
sha512Async: async (...messages) => {
|
|
441
|
+
const s = subtle();
|
|
442
|
+
const m = concatBytes(...messages);
|
|
443
|
+
return u8n(await s.digest("SHA-512", m.buffer));
|
|
444
|
+
},
|
|
445
|
+
sha512Sync: void 0,
|
|
446
|
+
bytesToHex,
|
|
447
|
+
hexToBytes,
|
|
448
|
+
concatBytes,
|
|
449
|
+
mod: M,
|
|
450
|
+
invert,
|
|
451
|
+
randomBytes
|
|
452
|
+
};
|
|
453
|
+
var utils = {
|
|
454
|
+
getExtendedPublicKeyAsync,
|
|
455
|
+
getExtendedPublicKey,
|
|
456
|
+
randomPrivateKey: () => randomBytes(L),
|
|
457
|
+
precompute: (w = 8, p = G) => {
|
|
458
|
+
p.multiply(3n);
|
|
459
|
+
w;
|
|
460
|
+
return p;
|
|
461
|
+
}
|
|
462
|
+
// no-op
|
|
463
|
+
};
|
|
464
|
+
var W = 8;
|
|
465
|
+
var scalarBits = 256;
|
|
466
|
+
var pwindows = Math.ceil(scalarBits / W) + 1;
|
|
467
|
+
var pwindowSize = 2 ** (W - 1);
|
|
468
|
+
var precompute = () => {
|
|
469
|
+
const points = [];
|
|
470
|
+
let p = G;
|
|
471
|
+
let b = p;
|
|
472
|
+
for (let w = 0; w < pwindows; w++) {
|
|
473
|
+
b = p;
|
|
474
|
+
points.push(b);
|
|
475
|
+
for (let i = 1; i < pwindowSize; i++) {
|
|
476
|
+
b = b.add(p);
|
|
477
|
+
points.push(b);
|
|
478
|
+
}
|
|
479
|
+
p = b.double();
|
|
480
|
+
}
|
|
481
|
+
return points;
|
|
482
|
+
};
|
|
483
|
+
var Gpows = void 0;
|
|
484
|
+
var ctneg = (cnd, p) => {
|
|
485
|
+
const n = p.negate();
|
|
486
|
+
return cnd ? n : p;
|
|
487
|
+
};
|
|
488
|
+
var wNAF = (n) => {
|
|
489
|
+
const comp = Gpows || (Gpows = precompute());
|
|
490
|
+
let p = I;
|
|
491
|
+
let f = G;
|
|
492
|
+
const pow_2_w = 2 ** W;
|
|
493
|
+
const maxNum = pow_2_w;
|
|
494
|
+
const mask = big(pow_2_w - 1);
|
|
495
|
+
const shiftBy = big(W);
|
|
496
|
+
for (let w = 0; w < pwindows; w++) {
|
|
497
|
+
let wbits = Number(n & mask);
|
|
498
|
+
n >>= shiftBy;
|
|
499
|
+
if (wbits > pwindowSize) {
|
|
500
|
+
wbits -= maxNum;
|
|
501
|
+
n += 1n;
|
|
502
|
+
}
|
|
503
|
+
const off = w * pwindowSize;
|
|
504
|
+
const offF = off;
|
|
505
|
+
const offP = off + Math.abs(wbits) - 1;
|
|
506
|
+
const isEven = w % 2 !== 0;
|
|
507
|
+
const isNeg = wbits < 0;
|
|
508
|
+
if (wbits === 0) {
|
|
509
|
+
f = f.add(ctneg(isEven, comp[offF]));
|
|
510
|
+
} else {
|
|
511
|
+
p = p.add(ctneg(isNeg, comp[offP]));
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return { p, f };
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
// ../shared/dist/voucher.js
|
|
518
|
+
var { bytesToHex: bytesToHex2, hexToBytes: hexToBytes2 } = etc;
|
|
519
|
+
function canonicalVoucherBytes(p) {
|
|
520
|
+
const canonical = [
|
|
521
|
+
"nanorail-voucher/v1",
|
|
522
|
+
p.sessionId,
|
|
523
|
+
p.challengeId,
|
|
524
|
+
p.articleId,
|
|
525
|
+
p.publisherId,
|
|
526
|
+
p.chunkId,
|
|
527
|
+
p.chunkType,
|
|
528
|
+
p.amount,
|
|
529
|
+
p.currency,
|
|
530
|
+
p.recipient,
|
|
531
|
+
p.nonce,
|
|
532
|
+
String(p.expiresAt)
|
|
533
|
+
].join("\n");
|
|
534
|
+
return new TextEncoder().encode(canonical);
|
|
535
|
+
}
|
|
536
|
+
function generatePrivateKeyHex() {
|
|
537
|
+
return bytesToHex2(utils.randomPrivateKey());
|
|
538
|
+
}
|
|
539
|
+
async function getPublicKeyHex(privateKeyHex) {
|
|
540
|
+
const pub = await getPublicKeyAsync(hexToBytes2(privateKeyHex));
|
|
541
|
+
return bytesToHex2(pub);
|
|
542
|
+
}
|
|
543
|
+
async function signVoucher(payload, privateKeyHex) {
|
|
544
|
+
const msg = canonicalVoucherBytes(payload);
|
|
545
|
+
const sig = await signAsync(msg, hexToBytes2(privateKeyHex));
|
|
546
|
+
const publicKey = await getPublicKeyHex(privateKeyHex);
|
|
547
|
+
return { payload, signature: bytesToHex2(sig), publicKey };
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// ../shared/dist/content.js
|
|
551
|
+
var PUBLISHER_ID = "agentic-chronicle";
|
|
552
|
+
var DEFAULT_POLICY = {
|
|
553
|
+
autoPayEnabled: true,
|
|
554
|
+
maxSpendPerArticle: "0.05",
|
|
555
|
+
maxSpendPerChunk: "0.025",
|
|
556
|
+
allowedPublishers: [PUBLISHER_ID],
|
|
557
|
+
allowedContentTypes: ["text", "chart", "citation_pack"],
|
|
558
|
+
blockedContentTypes: ["video"],
|
|
559
|
+
requireManualApprovalAbove: "0.025",
|
|
560
|
+
sessionBudget: "1.00",
|
|
561
|
+
sessionExpiryMinutes: 10
|
|
562
|
+
};
|
|
563
|
+
var DEFAULT_SESSION_BUDGET = DEFAULT_POLICY.sessionBudget;
|
|
564
|
+
|
|
565
|
+
// src/events.ts
|
|
566
|
+
function eventName(label) {
|
|
567
|
+
return `nanorail:${label.replace(/\s+/g, "-")}`;
|
|
568
|
+
}
|
|
569
|
+
var NanoRailEvents = class {
|
|
570
|
+
constructor(target) {
|
|
571
|
+
this.target = target;
|
|
572
|
+
}
|
|
573
|
+
emit(detail) {
|
|
574
|
+
const full = { ...detail, at: Date.now() };
|
|
575
|
+
this.target.dispatchEvent(new CustomEvent("nanorail:lifecycle", { detail: full }));
|
|
576
|
+
this.target.dispatchEvent(new CustomEvent(eventName(detail.label), { detail: full }));
|
|
577
|
+
}
|
|
578
|
+
on(label, handler) {
|
|
579
|
+
const name = label === "lifecycle" ? "nanorail:lifecycle" : eventName(label);
|
|
580
|
+
const wrapped = (e) => handler(e.detail);
|
|
581
|
+
this.target.addEventListener(name, wrapped);
|
|
582
|
+
return () => this.target.removeEventListener(name, wrapped);
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
// src/client.ts
|
|
587
|
+
var NanoRailClient = class {
|
|
588
|
+
constructor(config = {}) {
|
|
589
|
+
__publicField(this, "events");
|
|
590
|
+
__publicField(this, "gatewayUrl");
|
|
591
|
+
__publicField(this, "fetchImpl");
|
|
592
|
+
__publicField(this, "policy");
|
|
593
|
+
__publicField(this, "defaultActor");
|
|
594
|
+
__publicField(this, "privateKeyHex");
|
|
595
|
+
__publicField(this, "publicKeyHex");
|
|
596
|
+
__publicField(this, "session");
|
|
597
|
+
__publicField(this, "spentMicros", 0);
|
|
598
|
+
__publicField(this, "receipts", []);
|
|
599
|
+
__publicField(this, "blocked", []);
|
|
600
|
+
/** Chunk ids the user has manually approved (for amounts above the threshold). */
|
|
601
|
+
__publicField(this, "approvals", /* @__PURE__ */ new Set());
|
|
602
|
+
__publicField(this, "sessionPromise");
|
|
603
|
+
__publicField(this, "settlementWatch");
|
|
604
|
+
__publicField(this, "lastBatchCount", 0);
|
|
605
|
+
this.gatewayUrl = (config.gatewayUrl ?? "http://localhost:4020").replace(/\/$/, "");
|
|
606
|
+
this.fetchImpl = config.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
607
|
+
this.policy = config.policy ?? DEFAULT_POLICY;
|
|
608
|
+
this.defaultActor = config.actor ?? "human";
|
|
609
|
+
const target = config.eventTarget ?? (typeof document !== "undefined" ? document : new EventTarget());
|
|
610
|
+
this.events = new NanoRailEvents(target);
|
|
611
|
+
}
|
|
612
|
+
getPolicy() {
|
|
613
|
+
return this.policy;
|
|
614
|
+
}
|
|
615
|
+
/** Fetch the agent-readable manifest for an article. */
|
|
616
|
+
async getManifest(articleId) {
|
|
617
|
+
const res = await this.fetchImpl(`${this.gatewayUrl}/api/articles/${articleId}/manifest`);
|
|
618
|
+
const { manifest } = await res.json();
|
|
619
|
+
return manifest;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Ask the publisher's discovery endpoint "what paid content is about X?".
|
|
623
|
+
* Returns ranked, publicly-safe matches (no gated content). This is a
|
|
624
|
+
* recommendation layer — the publisher is as trustworthy as the matches.
|
|
625
|
+
*/
|
|
626
|
+
async discover(articleId, query, actor = this.defaultActor) {
|
|
627
|
+
const res = await this.fetchImpl(`${this.gatewayUrl}/api/articles/${articleId}/discover`, {
|
|
628
|
+
method: "POST",
|
|
629
|
+
headers: { "content-type": "application/json" },
|
|
630
|
+
body: JSON.stringify({ query, actor })
|
|
631
|
+
});
|
|
632
|
+
const body = await res.json().catch(() => ({ matches: [] }));
|
|
633
|
+
return body.matches ?? [];
|
|
634
|
+
}
|
|
635
|
+
/** Register an SDK-generated manifest so agents can discover this article. */
|
|
636
|
+
async registerManifest(manifest) {
|
|
637
|
+
const res = await this.fetchImpl(`${this.gatewayUrl}/api/articles/${manifest.articleId}/manifest`, {
|
|
638
|
+
method: "POST",
|
|
639
|
+
headers: { "content-type": "application/json" },
|
|
640
|
+
body: JSON.stringify({ manifest })
|
|
641
|
+
});
|
|
642
|
+
return await res.json().catch(() => ({ ok: false }));
|
|
643
|
+
}
|
|
644
|
+
/** Open a funded session once; concurrent callers share the same promise. */
|
|
645
|
+
async ensureSession() {
|
|
646
|
+
if (this.session) return this.session;
|
|
647
|
+
if (this.sessionPromise) return this.sessionPromise;
|
|
648
|
+
this.sessionPromise = this.openSession();
|
|
649
|
+
return this.sessionPromise;
|
|
650
|
+
}
|
|
651
|
+
async openSession() {
|
|
652
|
+
this.privateKeyHex = generatePrivateKeyHex();
|
|
653
|
+
this.publicKeyHex = await getPublicKeyHex(this.privateKeyHex);
|
|
654
|
+
const res = await this.fetchImpl(`${this.gatewayUrl}/api/session/open`, {
|
|
655
|
+
method: "POST",
|
|
656
|
+
headers: { "content-type": "application/json" },
|
|
657
|
+
body: JSON.stringify({ payerPublicKey: this.publicKeyHex, policy: this.policy })
|
|
658
|
+
});
|
|
659
|
+
const { session } = await res.json();
|
|
660
|
+
this.session = session;
|
|
661
|
+
this.events.emit({
|
|
662
|
+
label: "session opened",
|
|
663
|
+
detail: `funded session ${session.sessionId} \xB7 budget ${this.policy.sessionBudget} USD`
|
|
664
|
+
});
|
|
665
|
+
return session;
|
|
666
|
+
}
|
|
667
|
+
/** Pure client-side policy check using locally-tracked spend. */
|
|
668
|
+
evaluate(chunk) {
|
|
669
|
+
return evaluatePurchase(this.policy, {
|
|
670
|
+
publisherId: chunk.publisherId,
|
|
671
|
+
chunkType: chunk.type,
|
|
672
|
+
amountMicros: toMicros(chunk.price),
|
|
673
|
+
articleSpentMicros: this.spentMicros,
|
|
674
|
+
sessionSpentMicros: this.spentMicros
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Full unlock flow for one chunk:
|
|
679
|
+
* 1. evaluate client policy — if blocked, record + emit `policy blocked`, never sign.
|
|
680
|
+
* 2. if manual approval needed and not granted, return needsApproval.
|
|
681
|
+
* 3. request challenge, sign voucher, POST unlock.
|
|
682
|
+
* 4. on success record receipt + reveal content.
|
|
683
|
+
*/
|
|
684
|
+
async unlock(chunk, opts = {}) {
|
|
685
|
+
const actor = opts.actor ?? this.defaultActor;
|
|
686
|
+
const decision = this.evaluate(chunk);
|
|
687
|
+
if (!decision.allowed) {
|
|
688
|
+
const reason = decision.reason ?? "blocked by policy";
|
|
689
|
+
await this.recordClientBlock(chunk, reason, actor);
|
|
690
|
+
return { ok: false, blocked: true, reason };
|
|
691
|
+
}
|
|
692
|
+
if (decision.requiresManualApproval && !opts.approved && !this.approvals.has(chunk.chunkId)) {
|
|
693
|
+
return { ok: false, needsApproval: true, reason: `manual approval required above ${this.policy.requireManualApprovalAbove} USD` };
|
|
694
|
+
}
|
|
695
|
+
if (opts.approved) this.approvals.add(chunk.chunkId);
|
|
696
|
+
const session = await this.ensureSession();
|
|
697
|
+
const challenge = await this.requestChallenge(chunk, session.sessionId, actor);
|
|
698
|
+
this.events.emit({
|
|
699
|
+
label: "chunk challenge issued",
|
|
700
|
+
chunkId: chunk.chunkId,
|
|
701
|
+
chunkType: chunk.type,
|
|
702
|
+
amount: chunk.price,
|
|
703
|
+
detail: `challenge ${challenge.challengeId}`,
|
|
704
|
+
actor
|
|
705
|
+
});
|
|
706
|
+
const payload = {
|
|
707
|
+
sessionId: challenge.sessionId,
|
|
708
|
+
challengeId: challenge.challengeId,
|
|
709
|
+
articleId: challenge.articleId,
|
|
710
|
+
publisherId: challenge.publisherId,
|
|
711
|
+
chunkId: challenge.chunkId,
|
|
712
|
+
chunkType: challenge.chunkType,
|
|
713
|
+
amount: challenge.amount,
|
|
714
|
+
currency: challenge.currency,
|
|
715
|
+
recipient: challenge.recipient,
|
|
716
|
+
nonce: challenge.nonce,
|
|
717
|
+
expiresAt: challenge.expiresAt
|
|
718
|
+
};
|
|
719
|
+
const voucher = await signVoucher(payload, this.privateKeyHex);
|
|
720
|
+
this.events.emit({
|
|
721
|
+
label: "voucher signed",
|
|
722
|
+
chunkId: chunk.chunkId,
|
|
723
|
+
chunkType: chunk.type,
|
|
724
|
+
amount: chunk.price,
|
|
725
|
+
detail: `voucher signed for ${chunk.title}`,
|
|
726
|
+
actor
|
|
727
|
+
});
|
|
728
|
+
const res = await this.fetchImpl(`${this.gatewayUrl}/api/chunks/${chunk.chunkId}/unlock`, {
|
|
729
|
+
method: "POST",
|
|
730
|
+
headers: { "content-type": "application/json" },
|
|
731
|
+
body: JSON.stringify({ voucher, actor, viaDiscovery: opts.viaDiscovery === true })
|
|
732
|
+
});
|
|
733
|
+
if (!res.ok) {
|
|
734
|
+
const body = await res.json().catch(() => ({}));
|
|
735
|
+
const reason = body.error ?? `unlock failed (${res.status})`;
|
|
736
|
+
this.blocked.push({ chunkId: chunk.chunkId, reason, at: Date.now() });
|
|
737
|
+
this.events.emit({ label: "policy blocked", chunkId: chunk.chunkId, chunkType: chunk.type, reason, detail: reason, actor });
|
|
738
|
+
return { ok: false, blocked: true, reason };
|
|
739
|
+
}
|
|
740
|
+
const out = await res.json();
|
|
741
|
+
this.receipts.push(out.receipt);
|
|
742
|
+
this.spentMicros = toMicros(out.session.spent);
|
|
743
|
+
if (this.session) this.session.spentMicros = this.spentMicros;
|
|
744
|
+
this.events.emit({
|
|
745
|
+
label: "receipt returned",
|
|
746
|
+
chunkId: chunk.chunkId,
|
|
747
|
+
chunkType: chunk.type,
|
|
748
|
+
amount: chunk.price,
|
|
749
|
+
receiptId: out.receipt.receiptId,
|
|
750
|
+
detail: `receipt ${out.receipt.receiptId}`,
|
|
751
|
+
actor
|
|
752
|
+
});
|
|
753
|
+
return { ok: true, content: out.content, title: out.title, receipt: out.receipt };
|
|
754
|
+
}
|
|
755
|
+
async requestChallenge(chunk, sessionId, actor = this.defaultActor) {
|
|
756
|
+
const res = await this.fetchImpl(`${this.gatewayUrl}/api/chunks/${chunk.chunkId}/challenge`, {
|
|
757
|
+
method: "POST",
|
|
758
|
+
headers: { "content-type": "application/json" },
|
|
759
|
+
body: JSON.stringify({ sessionId, actor })
|
|
760
|
+
});
|
|
761
|
+
const { challenge } = await res.json();
|
|
762
|
+
return challenge;
|
|
763
|
+
}
|
|
764
|
+
/** Record a client-side policy block (no voucher signed) and emit the event. */
|
|
765
|
+
async recordClientBlock(chunk, reason, actor = this.defaultActor) {
|
|
766
|
+
this.blocked.push({ chunkId: chunk.chunkId, reason, at: Date.now() });
|
|
767
|
+
this.events.emit({
|
|
768
|
+
label: "policy blocked",
|
|
769
|
+
chunkId: chunk.chunkId,
|
|
770
|
+
chunkType: chunk.type,
|
|
771
|
+
amount: chunk.price,
|
|
772
|
+
reason,
|
|
773
|
+
detail: `blocked before signing: ${reason}`,
|
|
774
|
+
actor
|
|
775
|
+
});
|
|
776
|
+
try {
|
|
777
|
+
await this.fetchImpl(`${this.gatewayUrl}/api/policy/blocked`, {
|
|
778
|
+
method: "POST",
|
|
779
|
+
headers: { "content-type": "application/json" },
|
|
780
|
+
body: JSON.stringify({
|
|
781
|
+
sessionId: this.session?.sessionId,
|
|
782
|
+
articleId: chunk.articleId,
|
|
783
|
+
publisherId: chunk.publisherId,
|
|
784
|
+
chunkId: chunk.chunkId,
|
|
785
|
+
chunkType: chunk.type,
|
|
786
|
+
amount: chunk.price,
|
|
787
|
+
reason,
|
|
788
|
+
actor
|
|
789
|
+
})
|
|
790
|
+
});
|
|
791
|
+
} catch {
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
/** Trigger the server-side forced-rejection demo path for a chunk. */
|
|
795
|
+
async forceServerRejection(chunk, actor = this.defaultActor) {
|
|
796
|
+
const session = await this.ensureSession();
|
|
797
|
+
const res = await this.fetchImpl(`${this.gatewayUrl}/api/policy/attempt-rejected`, {
|
|
798
|
+
method: "POST",
|
|
799
|
+
headers: { "content-type": "application/json" },
|
|
800
|
+
body: JSON.stringify({ sessionId: session.sessionId, chunkId: chunk.chunkId, actor })
|
|
801
|
+
});
|
|
802
|
+
const body = await res.json().catch(() => ({}));
|
|
803
|
+
return { rejected: !!body.rejected, reason: body.reason };
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Unlock the premium snippet via the REAL Tempo MPP path (HTTP 402 → payment →
|
|
807
|
+
* receipt). This is settled by the gateway's PaymentProvider (real Tempo testnet
|
|
808
|
+
* when configured, offline fallback otherwise) and is intentionally separate from
|
|
809
|
+
* the mock Ed25519 voucher flow. Emits a `Real Tempo MPP unlock` event on success.
|
|
810
|
+
*/
|
|
811
|
+
async unlockTempoSnippet() {
|
|
812
|
+
const res = await this.fetchImpl(`${this.gatewayUrl}/api/premium/tempo-snippet`, {
|
|
813
|
+
method: "POST",
|
|
814
|
+
headers: { "content-type": "application/json" },
|
|
815
|
+
body: JSON.stringify({})
|
|
816
|
+
});
|
|
817
|
+
const body = await res.json().catch(() => ({}));
|
|
818
|
+
if (!res.ok) return { ok: false, reason: body.error ?? `tempo unlock failed (${res.status})` };
|
|
819
|
+
this.events.emit({
|
|
820
|
+
label: "Real Tempo MPP unlock",
|
|
821
|
+
amount: void 0,
|
|
822
|
+
detail: body.error ? `real-network failed \u2192 offline \xB7 ${body.error}` : `tempo snippet \xB7 ${body.mode} \xB7 ${body.settlementRef ?? ""}`.trim(),
|
|
823
|
+
actor: "agent"
|
|
824
|
+
});
|
|
825
|
+
return {
|
|
826
|
+
ok: true,
|
|
827
|
+
snippet: body.snippet,
|
|
828
|
+
receipt: body.receipt,
|
|
829
|
+
settlementRef: body.settlementRef,
|
|
830
|
+
mode: body.mode,
|
|
831
|
+
error: body.error,
|
|
832
|
+
recipient: body.recipient,
|
|
833
|
+
explorerUrl: body.explorerUrl
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
/** Poll settlements so the widget timeline shows `batch settled` events. */
|
|
837
|
+
startSettlementWatch(intervalMs = 2e3) {
|
|
838
|
+
if (this.settlementWatch) return;
|
|
839
|
+
this.settlementWatch = setInterval(async () => {
|
|
840
|
+
try {
|
|
841
|
+
const res = await this.fetchImpl(`${this.gatewayUrl}/api/settlements`);
|
|
842
|
+
const { batches } = await res.json();
|
|
843
|
+
if (batches.length > this.lastBatchCount) {
|
|
844
|
+
const fresh = batches.slice(0, batches.length - this.lastBatchCount);
|
|
845
|
+
for (const b of fresh) {
|
|
846
|
+
this.events.emit({ label: "batch settled", batchId: b.batchId, detail: `settlement batch ${b.batchId}` });
|
|
847
|
+
}
|
|
848
|
+
this.lastBatchCount = batches.length;
|
|
849
|
+
}
|
|
850
|
+
} catch {
|
|
851
|
+
}
|
|
852
|
+
}, intervalMs);
|
|
853
|
+
}
|
|
854
|
+
stopSettlementWatch() {
|
|
855
|
+
if (this.settlementWatch) clearInterval(this.settlementWatch);
|
|
856
|
+
this.settlementWatch = void 0;
|
|
857
|
+
}
|
|
858
|
+
/** Snapshot of state for the widget. */
|
|
859
|
+
snapshot() {
|
|
860
|
+
return {
|
|
861
|
+
sessionId: this.session?.sessionId,
|
|
862
|
+
publicKey: this.publicKeyHex,
|
|
863
|
+
policy: this.policy,
|
|
864
|
+
budget: this.policy.sessionBudget,
|
|
865
|
+
spent: this.spentMicros,
|
|
866
|
+
receipts: this.receipts,
|
|
867
|
+
blocked: this.blocked,
|
|
868
|
+
approvalsPending: this.policy.requireManualApprovalAbove
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
};
|
|
872
|
+
|
|
873
|
+
// src/widget.ts
|
|
874
|
+
var nrMark = (fill, size = 15) => `<svg width="${size}" height="${size}" viewBox="0 0 64 64" style="vertical-align:-3px;flex:none"><g fill="${fill}"><circle cx="14" cy="12" r="4"/><circle cx="14" cy="22" r="4"/><circle cx="14" cy="32" r="4"/><circle cx="14" cy="42" r="4"/><circle cx="14" cy="52" r="4"/><circle cx="54" cy="12" r="4"/><circle cx="54" cy="22" r="4"/><circle cx="54" cy="42" r="4"/><circle cx="54" cy="52" r="4"/><circle cx="24" cy="22" r="4"/><circle cx="34" cy="32" r="4"/><circle cx="44" cy="42" r="4"/></g></svg>`;
|
|
875
|
+
var LABEL_DOT = {
|
|
876
|
+
"discovery query": "#e879f9",
|
|
877
|
+
"discovery match": "#d946ef",
|
|
878
|
+
"session opened": "#8b5cf6",
|
|
879
|
+
"chunk challenge issued": "#0ea5e9",
|
|
880
|
+
"voucher signed": "#f59e0b",
|
|
881
|
+
"receipt returned": "#22c55e",
|
|
882
|
+
"policy blocked": "#ef4444",
|
|
883
|
+
"batch settled": "#FFC247",
|
|
884
|
+
"Real Tempo MPP unlock": "#E2A12F"
|
|
885
|
+
};
|
|
886
|
+
var TYPE_PLURAL = {
|
|
887
|
+
text: "Articles",
|
|
888
|
+
chart: "Charts",
|
|
889
|
+
citation_pack: "Citations",
|
|
890
|
+
video: "Video",
|
|
891
|
+
audio: "Audio",
|
|
892
|
+
image: "Images",
|
|
893
|
+
download: "Downloads"
|
|
894
|
+
};
|
|
895
|
+
var typePlural = (t) => TYPE_PLURAL[t] ?? t.replace(/_/g, " ").replace(/^\w/, (c) => c.toUpperCase());
|
|
896
|
+
var TYPE_NOUN = {
|
|
897
|
+
text: "an article",
|
|
898
|
+
chart: "a chart",
|
|
899
|
+
citation_pack: "a citation pack",
|
|
900
|
+
video: "a video",
|
|
901
|
+
audio: "audio",
|
|
902
|
+
image: "an image"
|
|
903
|
+
};
|
|
904
|
+
var typeNoun = (t) => t && TYPE_NOUN[t] || "content";
|
|
905
|
+
var esc = (s) => s.replace(/[&<>]/g, (c) => ({ "&": "&", "<": "<", ">": ">" })[c]);
|
|
906
|
+
function blockReason(e) {
|
|
907
|
+
const raw = e.reason ?? e.detail ?? "not allowed by your policy";
|
|
908
|
+
return raw.replace(/^(no voucher signed|server rejected|blocked by policy):\s*/i, "");
|
|
909
|
+
}
|
|
910
|
+
var NanoRailWidget = class {
|
|
911
|
+
constructor(client) {
|
|
912
|
+
this.client = client;
|
|
913
|
+
__publicField(this, "root");
|
|
914
|
+
/** Human-readable recent purchases / blocks. */
|
|
915
|
+
__publicField(this, "activity", []);
|
|
916
|
+
/** Raw protocol events for the Technical details drawer. */
|
|
917
|
+
__publicField(this, "tech", []);
|
|
918
|
+
__publicField(this, "collapsed", false);
|
|
919
|
+
__publicField(this, "showTech", false);
|
|
920
|
+
injectStyles();
|
|
921
|
+
this.root = document.createElement("div");
|
|
922
|
+
this.root.id = "nanorail-widget";
|
|
923
|
+
document.body.appendChild(this.root);
|
|
924
|
+
this.client.events.on("lifecycle", (e) => {
|
|
925
|
+
this.tech.unshift(e);
|
|
926
|
+
this.tech = this.tech.slice(0, 16);
|
|
927
|
+
if (e.label === "receipt returned" || e.label === "policy blocked") {
|
|
928
|
+
this.activity.unshift(e);
|
|
929
|
+
this.activity = this.activity.slice(0, 5);
|
|
930
|
+
}
|
|
931
|
+
this.render();
|
|
932
|
+
});
|
|
933
|
+
this.render();
|
|
934
|
+
}
|
|
935
|
+
toggle() {
|
|
936
|
+
this.collapsed = !this.collapsed;
|
|
937
|
+
this.render();
|
|
938
|
+
}
|
|
939
|
+
render() {
|
|
940
|
+
const s = this.client.snapshot();
|
|
941
|
+
const p = s.policy;
|
|
942
|
+
const budgetMicros = toMicros(s.budget);
|
|
943
|
+
const remaining = fromMicros(Math.max(0, budgetMicros - s.spent));
|
|
944
|
+
const pct2 = Math.min(100, Math.round(s.spent / Math.max(1, budgetMicros) * 100));
|
|
945
|
+
if (this.collapsed) {
|
|
946
|
+
this.root.className = "nr-widget nr-collapsed";
|
|
947
|
+
this.root.innerHTML = `<button class="nr-w-fab" title="Agent Wallet">${nrMark("#0A0A0A", 14)} Agent Wallet \xB7 $${remaining}</button>`;
|
|
948
|
+
this.root.querySelector(".nr-w-fab").onclick = () => this.toggle();
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
const allowed = p.allowedContentTypes.map((t) => `<span class="nr-perm ok">\u2713 ${esc(typePlural(t))}</span>`).join("");
|
|
952
|
+
const blocked = p.blockedContentTypes.length ? p.blockedContentTypes.map((t) => `<span class="nr-perm no">\u2717 ${esc(typePlural(t))}</span>`).join("") : '<span class="nr-perm muted">none</span>';
|
|
953
|
+
const publishers = p.allowedPublishers.length ? "Verified only" : "Any";
|
|
954
|
+
const activityRows = this.activity.length ? this.activity.map((e) => {
|
|
955
|
+
if (e.label === "receipt returned") {
|
|
956
|
+
const who = e.actor === "agent" ? "Agent" : "You";
|
|
957
|
+
return `<div class="nr-act ok"><span class="nr-act-dot"></span><span class="nr-act-txt">${who} purchased ${esc(typeNoun(e.chunkType))}</span><span class="nr-act-amt">$${esc(e.amount ?? "0")}</span></div>`;
|
|
958
|
+
}
|
|
959
|
+
return `<div class="nr-act no"><span class="nr-act-dot"></span><span class="nr-act-txt">Blocked \u2014 ${esc(blockReason(e))}</span></div>`;
|
|
960
|
+
}).join("") : '<div class="nr-w-empty">No purchases yet \u2014 scroll the page or run Agent Mode.</div>';
|
|
961
|
+
const techRows = this.tech.length ? this.tech.map(
|
|
962
|
+
(e) => `<div class="nr-tl-row"><i style="background:${LABEL_DOT[e.label]}"></i><span class="nr-tl-label">${e.label}</span>${e.amount ? `<span class="nr-tl-amt">$${e.amount}</span>` : ""}</div>`
|
|
963
|
+
).join("") : '<div class="nr-w-empty">No protocol events yet.</div>';
|
|
964
|
+
this.root.className = "nr-widget";
|
|
965
|
+
this.root.innerHTML = `
|
|
966
|
+
<div class="nr-w-head">
|
|
967
|
+
<span class="nr-w-brand">${nrMark("#FFC247", 16)} Agent Wallet</span>
|
|
968
|
+
<button class="nr-w-min" title="Minimize">\u2013</button>
|
|
969
|
+
</div>
|
|
970
|
+
|
|
971
|
+
<div class="nr-w-budget">
|
|
972
|
+
<div class="nr-w-budget-top"><span class="nr-w-budget-label">Remaining today</span><span class="nr-w-budget-val">$${remaining}</span></div>
|
|
973
|
+
<div class="nr-w-bar"><i style="width:${pct2}%"></i></div>
|
|
974
|
+
<div class="nr-w-budget-foot"><span>Daily budget $${esc(s.budget)}</span><span>Spent $${fromMicros(s.spent)}</span></div>
|
|
975
|
+
</div>
|
|
976
|
+
|
|
977
|
+
<div class="nr-w-sec">
|
|
978
|
+
<div class="nr-w-sec-h">Permissions <em>\u2014 what your agent may buy</em></div>
|
|
979
|
+
<div class="nr-w-perms">
|
|
980
|
+
<div class="nr-perm-col"><div class="nr-perm-lab">Allowed</div>${allowed}</div>
|
|
981
|
+
<div class="nr-perm-col"><div class="nr-perm-lab">Blocked</div>${blocked}</div>
|
|
982
|
+
</div>
|
|
983
|
+
</div>
|
|
984
|
+
|
|
985
|
+
<div class="nr-w-sec">
|
|
986
|
+
<div class="nr-w-sec-h">Purchase rules</div>
|
|
987
|
+
<div class="nr-w-row"><span>Auto-pay</span><strong class="${p.autoPayEnabled ? "nr-on" : "nr-off"}">${p.autoPayEnabled ? "ON" : "OFF"}</strong></div>
|
|
988
|
+
<div class="nr-w-row nr-muted"><span>Maximum purchase</span><span class="nr-mono">$${esc(p.maxSpendPerChunk)}</span></div>
|
|
989
|
+
<div class="nr-w-row nr-muted"><span>Manual approval above</span><span class="nr-mono">$${esc(p.requireManualApprovalAbove)}</span></div>
|
|
990
|
+
<div class="nr-w-row nr-muted"><span>Publishers</span><span>${publishers}</span></div>
|
|
991
|
+
</div>
|
|
992
|
+
|
|
993
|
+
<div class="nr-w-stats">
|
|
994
|
+
<div class="nr-stat"><b>${s.receipts.length}</b><span>receipts</span></div>
|
|
995
|
+
<div class="nr-stat"><b>${s.blocked.length}</b><span>blocked</span></div>
|
|
996
|
+
<div class="nr-stat"><b>$${fromMicros(s.spent)}</b><span>spent</span></div>
|
|
997
|
+
</div>
|
|
998
|
+
|
|
999
|
+
<div class="nr-w-sec">
|
|
1000
|
+
<div class="nr-w-sec-h">Recent purchases</div>
|
|
1001
|
+
${activityRows}
|
|
1002
|
+
</div>
|
|
1003
|
+
|
|
1004
|
+
<div class="nr-w-tech">
|
|
1005
|
+
<button class="nr-w-tech-toggle">${this.showTech ? "\u25BE" : "\u25B8"} Technical details <em>(MPP protocol)</em></button>
|
|
1006
|
+
${this.showTech ? `<div class="nr-w-tech-body">
|
|
1007
|
+
<div class="nr-w-row nr-muted"><span>Session</span><span class="nr-mono">${s.sessionId ? esc(s.sessionId.slice(0, 14)) + "\u2026" : "none"}</span></div>
|
|
1008
|
+
${techRows}
|
|
1009
|
+
</div>` : ""}
|
|
1010
|
+
</div>`;
|
|
1011
|
+
this.root.querySelector(".nr-w-min").onclick = () => this.toggle();
|
|
1012
|
+
this.root.querySelector(".nr-w-tech-toggle").onclick = () => {
|
|
1013
|
+
this.showTech = !this.showTech;
|
|
1014
|
+
this.render();
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
};
|
|
1018
|
+
var stylesInjected = false;
|
|
1019
|
+
function injectStyles() {
|
|
1020
|
+
if (stylesInjected || typeof document === "undefined") return;
|
|
1021
|
+
stylesInjected = true;
|
|
1022
|
+
const css = `
|
|
1023
|
+
#nanorail-widget{position:fixed;right:20px;bottom:20px;width:312px;max-height:calc(100vh - 40px);overflow:auto;z-index:99999;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;font-size:12px;color:#EDEDED;background:#0E0E0E;border:1px solid #1E1E1E;border-radius:14px;box-shadow:0 12px 40px rgba(0,0,0,.55)}
|
|
1024
|
+
#nanorail-widget.nr-collapsed{width:auto;overflow:visible;background:none;border:none;box-shadow:none}
|
|
1025
|
+
.nr-w-fab{display:flex;align-items:center;gap:7px;background:#FFC247;color:#0A0A0A;border:none;border-radius:999px;padding:10px 16px;font-weight:800;cursor:pointer;box-shadow:0 8px 24px rgba(255,194,71,.45)}
|
|
1026
|
+
.nr-w-head{display:flex;align-items:center;gap:8px;padding:11px 13px;background:#161616;border-bottom:1px solid #1E1E1E;position:sticky;top:0}
|
|
1027
|
+
.nr-w-brand{display:flex;align-items:center;gap:7px;font-weight:800;font-size:14px;color:#F2E2BE}
|
|
1028
|
+
.nr-w-min{margin-left:auto;background:none;border:none;color:#8A8A8A;cursor:pointer;font-size:18px;line-height:1}
|
|
1029
|
+
/* Section 1 \u2014 budget, most prominent */
|
|
1030
|
+
.nr-w-budget{padding:14px 14px 12px;border-bottom:1px solid #1A1A1A;background:linear-gradient(180deg,#121212,#0E0E0E)}
|
|
1031
|
+
.nr-w-budget-top{display:flex;justify-content:space-between;align-items:baseline}
|
|
1032
|
+
.nr-w-budget-label{color:#8A8A8A;font-size:11px;text-transform:uppercase;letter-spacing:.06em}
|
|
1033
|
+
.nr-w-budget-val{font-family:ui-monospace,monospace;font-size:28px;font-weight:800;color:#FFC247}
|
|
1034
|
+
.nr-w-bar{height:7px;background:#1E1E1E;border-radius:4px;overflow:hidden;margin:9px 0 7px}
|
|
1035
|
+
.nr-w-bar i{display:block;height:100%;background:linear-gradient(90deg,#FFC247,#E2A12F)}
|
|
1036
|
+
.nr-w-budget-foot{display:flex;justify-content:space-between;color:#8A8A8A;font-size:11px}
|
|
1037
|
+
/* generic section */
|
|
1038
|
+
.nr-w-sec{padding:11px 14px;border-bottom:1px solid #1A1A1A}
|
|
1039
|
+
.nr-w-sec-h{font-weight:700;color:#D4D4D4;margin-bottom:9px;font-size:12px}
|
|
1040
|
+
.nr-w-sec-h em{color:#7A7A7A;font-style:normal;font-weight:400}
|
|
1041
|
+
.nr-w-row{display:flex;justify-content:space-between;align-items:center;margin:4px 0}
|
|
1042
|
+
.nr-muted{color:#8A8A8A}.nr-mono{font-family:ui-monospace,monospace}
|
|
1043
|
+
.nr-on{color:#57E389}.nr-off{color:#ef4444}
|
|
1044
|
+
/* permissions */
|
|
1045
|
+
.nr-w-perms{display:flex;gap:10px}
|
|
1046
|
+
.nr-perm-col{flex:1;display:flex;flex-direction:column;gap:5px}
|
|
1047
|
+
.nr-perm-lab{font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:#7A7A7A;margin-bottom:1px}
|
|
1048
|
+
.nr-perm{font-size:12px}
|
|
1049
|
+
.nr-perm.ok{color:#86efac}.nr-perm.no{color:#fca5a5}.nr-perm.muted{color:#6E6E6E}
|
|
1050
|
+
/* activity summary */
|
|
1051
|
+
.nr-w-stats{display:flex;gap:8px;padding:11px 14px;border-bottom:1px solid #1A1A1A}
|
|
1052
|
+
.nr-stat{flex:1;text-align:center;background:#161616;border-radius:8px;padding:7px 4px}
|
|
1053
|
+
.nr-stat b{display:block;font-size:16px;font-family:ui-monospace,monospace;color:#EDEDED}
|
|
1054
|
+
.nr-stat span{color:#8A8A8A;font-size:10px}
|
|
1055
|
+
/* recent purchases */
|
|
1056
|
+
.nr-act{display:flex;align-items:center;gap:7px;padding:4px 0;font-size:12px}
|
|
1057
|
+
.nr-act-dot{width:7px;height:7px;border-radius:50%;flex:none}
|
|
1058
|
+
.nr-act.ok .nr-act-dot{background:#57E389}.nr-act.no .nr-act-dot{background:#ef4444}
|
|
1059
|
+
.nr-act.no .nr-act-txt{color:#fca5a5}
|
|
1060
|
+
.nr-act-txt{flex:1}
|
|
1061
|
+
.nr-act-amt{font-family:ui-monospace,monospace;color:#FFC247;font-weight:700}
|
|
1062
|
+
.nr-w-empty{color:#7A7A7A;font-size:11px}
|
|
1063
|
+
/* technical drawer */
|
|
1064
|
+
.nr-w-tech{padding:8px 14px 12px}
|
|
1065
|
+
.nr-w-tech-toggle{width:100%;text-align:left;background:none;border:none;color:#8A8A8A;cursor:pointer;font-size:11px;padding:4px 0}
|
|
1066
|
+
.nr-w-tech-toggle em{font-style:normal;opacity:.7}
|
|
1067
|
+
.nr-w-tech-body{margin-top:6px;border-top:1px dashed #1E1E1E;padding-top:8px}
|
|
1068
|
+
.nr-tl-row{display:flex;align-items:center;gap:6px;padding:2px 0;font-size:11px;color:#B9B9B9}
|
|
1069
|
+
.nr-tl-row i{width:8px;height:8px;border-radius:50%;flex:none}
|
|
1070
|
+
.nr-tl-label{flex:1}.nr-tl-amt{color:#FFC247;font-family:ui-monospace,monospace}
|
|
1071
|
+
`;
|
|
1072
|
+
const style = document.createElement("style");
|
|
1073
|
+
style.textContent = css;
|
|
1074
|
+
document.head.appendChild(style);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
// src/render.ts
|
|
1078
|
+
function escapeHtml(s) {
|
|
1079
|
+
return s.replace(/[&<>"']/g, (ch) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[ch]);
|
|
1080
|
+
}
|
|
1081
|
+
function renderChart(json) {
|
|
1082
|
+
const data = JSON.parse(json);
|
|
1083
|
+
const max = Math.max(...data.series.flatMap((s) => s.points), 1);
|
|
1084
|
+
const colors = ["#4f8cff", "#22c55e", "#f59e0b"];
|
|
1085
|
+
const cols = data.xLabels.map((x, i) => {
|
|
1086
|
+
const bars = data.series.map((s, si) => {
|
|
1087
|
+
const h2 = Math.round(s.points[i] / max * 100);
|
|
1088
|
+
return `<div class="nr-bar" style="height:${h2}%;background:${colors[si % colors.length]}" title="${escapeHtml(s.label)}: ${s.points[i]}${data.unit ?? ""}"></div>`;
|
|
1089
|
+
}).join("");
|
|
1090
|
+
return `<div class="nr-col"><div class="nr-bars">${bars}</div><span class="nr-xlabel">${escapeHtml(x)}</span></div>`;
|
|
1091
|
+
}).join("");
|
|
1092
|
+
const legend = data.series.map((s, si) => `<span class="nr-legend"><i style="background:${colors[si % colors.length]}"></i>${escapeHtml(s.label)}</span>`).join("");
|
|
1093
|
+
return `<figure class="nr-chart"><figcaption>${escapeHtml(data.title)}</figcaption><div class="nr-chart-body">${cols}</div><div class="nr-legends">${legend}</div></figure>`;
|
|
1094
|
+
}
|
|
1095
|
+
function renderCitations(json) {
|
|
1096
|
+
const items = JSON.parse(json);
|
|
1097
|
+
const lis = items.map((c) => `<li>${escapeHtml(c.text)}</li>`).join("");
|
|
1098
|
+
return `<div class="nr-citations"><h4>Sources & further reading</h4><ol>${lis}</ol></div>`;
|
|
1099
|
+
}
|
|
1100
|
+
function renderVideo(json) {
|
|
1101
|
+
const v = JSON.parse(json);
|
|
1102
|
+
return `<div class="nr-video"><div class="nr-video-frame">\u25B6</div><div class="nr-video-meta"><strong>${escapeHtml(v.title)}</strong><span>${escapeHtml(v.duration)} \xB7 ${escapeHtml(v.src)}</span></div></div>`;
|
|
1103
|
+
}
|
|
1104
|
+
function renderContent(content) {
|
|
1105
|
+
if (content.startsWith("CHART::")) return renderChart(content.slice("CHART::".length));
|
|
1106
|
+
if (content.startsWith("CITATIONS::")) return renderCitations(content.slice("CITATIONS::".length));
|
|
1107
|
+
if (content.startsWith("VIDEO::")) return renderVideo(content.slice("VIDEO::".length));
|
|
1108
|
+
return `<p>${escapeHtml(content)}</p>`;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
// src/scan.ts
|
|
1112
|
+
var PUBLISHER_DEFAULT = "agentic-chronicle";
|
|
1113
|
+
var ARTICLE_DEFAULT = "the-agentic-chronicle";
|
|
1114
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1115
|
+
function readChunk(el) {
|
|
1116
|
+
return {
|
|
1117
|
+
chunkId: el.dataset.nanorailChunk ?? el.id ?? "unknown",
|
|
1118
|
+
type: el.dataset.nanorailType ?? "text",
|
|
1119
|
+
price: el.dataset.nanorailPrice ?? "0",
|
|
1120
|
+
publisherId: el.dataset.nanorailPublisher ?? PUBLISHER_DEFAULT,
|
|
1121
|
+
articleId: el.dataset.nanorailArticle ?? ARTICLE_DEFAULT,
|
|
1122
|
+
recipient: el.dataset.nanorailRecipient ?? "",
|
|
1123
|
+
title: el.dataset.nanorailTitle ?? el.dataset.nanorailChunk ?? "Premium chunk",
|
|
1124
|
+
teaser: el.dataset.nanorailTeaser,
|
|
1125
|
+
tags: parseTags(el.dataset.nanorailTags)
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
function parseTags(raw) {
|
|
1129
|
+
if (!raw) return [];
|
|
1130
|
+
return raw.split(/[,|]/).map((t) => t.trim()).filter(Boolean);
|
|
1131
|
+
}
|
|
1132
|
+
function shouldAutoUnlock(decision) {
|
|
1133
|
+
return decision.allowed && !decision.requiresManualApproval;
|
|
1134
|
+
}
|
|
1135
|
+
var autoHandlers = /* @__PURE__ */ new WeakMap();
|
|
1136
|
+
var autoObserver;
|
|
1137
|
+
function ensureAutoObserver() {
|
|
1138
|
+
if (typeof IntersectionObserver === "undefined") return void 0;
|
|
1139
|
+
if (!autoObserver) {
|
|
1140
|
+
autoObserver = new IntersectionObserver(
|
|
1141
|
+
(entries, obs) => {
|
|
1142
|
+
for (const entry of entries) {
|
|
1143
|
+
if (!entry.isIntersecting) continue;
|
|
1144
|
+
const handler = autoHandlers.get(entry.target);
|
|
1145
|
+
if (!handler) continue;
|
|
1146
|
+
obs.unobserve(entry.target);
|
|
1147
|
+
autoHandlers.delete(entry.target);
|
|
1148
|
+
handler();
|
|
1149
|
+
}
|
|
1150
|
+
},
|
|
1151
|
+
{ threshold: 0.6 }
|
|
1152
|
+
);
|
|
1153
|
+
}
|
|
1154
|
+
return autoObserver;
|
|
1155
|
+
}
|
|
1156
|
+
function scanAndLock(client, root = document, opts = {}) {
|
|
1157
|
+
const els = Array.from(root.querySelectorAll("[data-nanorail-price]"));
|
|
1158
|
+
for (const el of els) {
|
|
1159
|
+
if (el.dataset.nanorailLocked === "true") continue;
|
|
1160
|
+
lockChunk(client, el, opts.autoUnlock === true);
|
|
1161
|
+
}
|
|
1162
|
+
return els;
|
|
1163
|
+
}
|
|
1164
|
+
function lockChunk(client, el, autoUnlock) {
|
|
1165
|
+
const chunk = readChunk(el);
|
|
1166
|
+
el.dataset.nanorailLocked = "true";
|
|
1167
|
+
el.classList.add("nr-chunk", "nr-locked");
|
|
1168
|
+
const panel = document.createElement("div");
|
|
1169
|
+
panel.className = "nr-lock-panel";
|
|
1170
|
+
panel.innerHTML = `
|
|
1171
|
+
<div class="nr-lock-head">
|
|
1172
|
+
<span class="nr-type nr-type-${chunk.type}">${chunk.type.replace("_", " ")}</span>
|
|
1173
|
+
<span class="nr-price">$${chunk.price}</span>
|
|
1174
|
+
</div>
|
|
1175
|
+
<div class="nr-lock-title">${chunk.title}</div>
|
|
1176
|
+
<div class="nr-lock-actions"></div>
|
|
1177
|
+
<div class="nr-lock-msg" role="status"></div>`;
|
|
1178
|
+
const actions = panel.querySelector(".nr-lock-actions");
|
|
1179
|
+
const msg = panel.querySelector(".nr-lock-msg");
|
|
1180
|
+
const button = document.createElement("button");
|
|
1181
|
+
button.className = "nr-unlock-btn";
|
|
1182
|
+
button.textContent = `Unlock \xB7 $${chunk.price}`;
|
|
1183
|
+
actions.appendChild(button);
|
|
1184
|
+
const reveal = (html, auto) => {
|
|
1185
|
+
el.classList.remove("nr-locked", "nr-auto");
|
|
1186
|
+
el.classList.add("nr-unlocked");
|
|
1187
|
+
const badge = auto ? `metered as you read \xB7 $${chunk.price}` : `unlocked via NanoRail \xB7 $${chunk.price}`;
|
|
1188
|
+
el.innerHTML = `<div class="nr-content">${html}</div><div class="nr-content-badge">${badge}</div>`;
|
|
1189
|
+
};
|
|
1190
|
+
const runUnlock = async (approved = false, auto = false) => {
|
|
1191
|
+
button.disabled = true;
|
|
1192
|
+
if (auto) {
|
|
1193
|
+
el.classList.add("nr-auto");
|
|
1194
|
+
button.textContent = "Metering\u2026";
|
|
1195
|
+
msg.className = "nr-lock-msg nr-metering";
|
|
1196
|
+
msg.innerHTML = `<span class="nr-step">\u2713 Policy check passed</span>`;
|
|
1197
|
+
const advancing = sleep(420).then(() => {
|
|
1198
|
+
msg.innerHTML = `<span class="nr-step">\u2713 MPP payment authorized</span>`;
|
|
1199
|
+
});
|
|
1200
|
+
const out2 = await client.unlock(chunk, { approved });
|
|
1201
|
+
await advancing;
|
|
1202
|
+
if (out2.ok && out2.content) {
|
|
1203
|
+
msg.innerHTML = `<span class="nr-step nr-step-paid">\u2713 Auto-paid $${chunk.price} \xB7 receipt ${out2.receipt?.receiptId?.slice(0, 10) ?? "issued"}</span>`;
|
|
1204
|
+
await sleep(850);
|
|
1205
|
+
reveal(renderContent(out2.content), true);
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
el.classList.remove("nr-auto");
|
|
1209
|
+
button.disabled = false;
|
|
1210
|
+
if (out2.needsApproval) {
|
|
1211
|
+
msg.textContent = out2.reason ?? "Manual approval required";
|
|
1212
|
+
msg.className = "nr-lock-msg nr-warn";
|
|
1213
|
+
button.textContent = `Approve & unlock \xB7 $${chunk.price}`;
|
|
1214
|
+
button.onclick = () => runUnlock(true);
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
msg.textContent = `Blocked by policy: ${out2.reason}`;
|
|
1218
|
+
msg.className = "nr-lock-msg nr-blocked";
|
|
1219
|
+
button.textContent = "Blocked by policy";
|
|
1220
|
+
button.disabled = true;
|
|
1221
|
+
el.classList.add("nr-chunk-blocked");
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
button.textContent = "Unlocking\u2026";
|
|
1225
|
+
const out = await client.unlock(chunk, { approved });
|
|
1226
|
+
if (out.ok && out.content) {
|
|
1227
|
+
reveal(renderContent(out.content), auto);
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
el.classList.remove("nr-auto");
|
|
1231
|
+
button.disabled = false;
|
|
1232
|
+
if (out.needsApproval) {
|
|
1233
|
+
msg.textContent = out.reason ?? "Manual approval required";
|
|
1234
|
+
msg.className = "nr-lock-msg nr-warn";
|
|
1235
|
+
button.textContent = `Approve & unlock \xB7 $${chunk.price}`;
|
|
1236
|
+
button.onclick = () => runUnlock(true);
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
msg.textContent = `Blocked by policy: ${out.reason}`;
|
|
1240
|
+
msg.className = "nr-lock-msg nr-blocked";
|
|
1241
|
+
button.textContent = "Blocked by policy";
|
|
1242
|
+
button.disabled = true;
|
|
1243
|
+
el.classList.add("nr-chunk-blocked");
|
|
1244
|
+
};
|
|
1245
|
+
button.onclick = () => runUnlock(false);
|
|
1246
|
+
el.replaceChildren(panel);
|
|
1247
|
+
if (autoUnlock) {
|
|
1248
|
+
const observer = ensureAutoObserver();
|
|
1249
|
+
if (observer) {
|
|
1250
|
+
autoHandlers.set(el, () => {
|
|
1251
|
+
const decision = client.evaluate(chunk);
|
|
1252
|
+
if (shouldAutoUnlock(decision)) {
|
|
1253
|
+
void runUnlock(false, true);
|
|
1254
|
+
} else if (decision.requiresManualApproval) {
|
|
1255
|
+
msg.textContent = "Premium \u2014 approve to unlock";
|
|
1256
|
+
msg.className = "nr-lock-msg nr-warn";
|
|
1257
|
+
} else {
|
|
1258
|
+
msg.textContent = `Held back by your policy: ${decision.reason ?? "not permitted"}`;
|
|
1259
|
+
msg.className = "nr-lock-msg nr-blocked";
|
|
1260
|
+
}
|
|
1261
|
+
});
|
|
1262
|
+
observer.observe(el);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
// src/manifest.ts
|
|
1268
|
+
function generateManifest(opts = {}) {
|
|
1269
|
+
const root = opts.root ?? document;
|
|
1270
|
+
const els = Array.from(root.querySelectorAll("[data-nanorail-price]"));
|
|
1271
|
+
const chunks = els.map((el) => {
|
|
1272
|
+
const c = readChunk(el);
|
|
1273
|
+
return {
|
|
1274
|
+
chunkId: c.chunkId,
|
|
1275
|
+
type: c.type,
|
|
1276
|
+
title: c.title,
|
|
1277
|
+
price: c.price,
|
|
1278
|
+
currency: "USD",
|
|
1279
|
+
recipient: c.recipient,
|
|
1280
|
+
teaser: c.teaser ?? c.title,
|
|
1281
|
+
tags: c.tags ?? [],
|
|
1282
|
+
endpoints: {
|
|
1283
|
+
challenge: `/api/chunks/${c.chunkId}/challenge`,
|
|
1284
|
+
unlock: `/api/chunks/${c.chunkId}/unlock`
|
|
1285
|
+
}
|
|
1286
|
+
};
|
|
1287
|
+
});
|
|
1288
|
+
const articleId = opts.articleId ?? els[0]?.dataset.nanorailArticle ?? "unknown-article";
|
|
1289
|
+
const publisherId = opts.publisherId ?? els[0]?.dataset.nanorailPublisher ?? "unknown-publisher";
|
|
1290
|
+
const title = opts.title ?? (root instanceof Document ? root.querySelector("h1")?.textContent?.trim() : void 0) ?? (typeof document !== "undefined" ? document.title : void 0) ?? articleId;
|
|
1291
|
+
return {
|
|
1292
|
+
articleId,
|
|
1293
|
+
publisherId,
|
|
1294
|
+
title,
|
|
1295
|
+
currency: "USD",
|
|
1296
|
+
source: "sdk-scan",
|
|
1297
|
+
generatedAt: Date.now(),
|
|
1298
|
+
endpoints: {
|
|
1299
|
+
challenge: "/api/chunks/:chunkId/challenge",
|
|
1300
|
+
unlock: "/api/chunks/:chunkId/unlock"
|
|
1301
|
+
},
|
|
1302
|
+
chunks
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
// src/agent.ts
|
|
1307
|
+
var DEFAULT_TASK = "Summarize MPP payments and receipts";
|
|
1308
|
+
var DEFAULT_QUERY = "MPP payments and receipts";
|
|
1309
|
+
var sleep2 = (ms) => ms > 0 ? new Promise((r) => setTimeout(r, ms)) : Promise.resolve();
|
|
1310
|
+
function descriptor(manifest, m) {
|
|
1311
|
+
return {
|
|
1312
|
+
chunkId: m.chunkId,
|
|
1313
|
+
type: m.type,
|
|
1314
|
+
price: m.price,
|
|
1315
|
+
publisherId: manifest.publisherId,
|
|
1316
|
+
articleId: manifest.articleId,
|
|
1317
|
+
recipient: m.recipient,
|
|
1318
|
+
title: m.title,
|
|
1319
|
+
teaser: m.teaser,
|
|
1320
|
+
tags: m.tags
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
async function runAgentSummary(client, articleId = "the-agentic-chronicle", opts = {}) {
|
|
1324
|
+
const emit = opts.onProgress ?? (() => {
|
|
1325
|
+
});
|
|
1326
|
+
const delay = opts.stepDelayMs ?? 0;
|
|
1327
|
+
const task = opts.task ?? DEFAULT_TASK;
|
|
1328
|
+
const query = opts.query ?? DEFAULT_QUERY;
|
|
1329
|
+
emit({ phase: "task", task, query });
|
|
1330
|
+
const manifest = await client.getManifest(articleId);
|
|
1331
|
+
const matches = await client.discover(articleId, query, "agent");
|
|
1332
|
+
emit({ phase: "discovery", query, matches });
|
|
1333
|
+
await sleep2(delay);
|
|
1334
|
+
const confidenceById = new Map(matches.map((m) => [m.chunkId, m.confidence]));
|
|
1335
|
+
const plan = manifest.chunks.map((m) => {
|
|
1336
|
+
const chunk = descriptor(manifest, m);
|
|
1337
|
+
const decision = client.evaluate(chunk);
|
|
1338
|
+
const confidence = confidenceById.get(m.chunkId);
|
|
1339
|
+
if (!decision.allowed) {
|
|
1340
|
+
return { chunkId: m.chunkId, type: m.type, title: m.title, price: m.price, action: "block", reason: decision.reason ?? "blocked by policy", confidence };
|
|
1341
|
+
}
|
|
1342
|
+
if (confidence !== void 0) {
|
|
1343
|
+
return { chunkId: m.chunkId, type: m.type, title: m.title, price: m.price, action: "buy", reason: `discovery match (confidence ${confidence})`, confidence };
|
|
1344
|
+
}
|
|
1345
|
+
return { chunkId: m.chunkId, type: m.type, title: m.title, price: m.price, action: "skip", reason: "not surfaced by discovery" };
|
|
1346
|
+
});
|
|
1347
|
+
emit({ phase: "plan", plan });
|
|
1348
|
+
await sleep2(delay);
|
|
1349
|
+
const steps = [];
|
|
1350
|
+
const receiptIds = [];
|
|
1351
|
+
const purchased = [];
|
|
1352
|
+
const boughtText = [];
|
|
1353
|
+
let totalMicros = 0;
|
|
1354
|
+
for (const item of plan) {
|
|
1355
|
+
const m = manifest.chunks.find((c) => c.chunkId === item.chunkId);
|
|
1356
|
+
const chunk = descriptor(manifest, m);
|
|
1357
|
+
emit({ phase: "chunk-start", item });
|
|
1358
|
+
await sleep2(delay);
|
|
1359
|
+
if (item.action === "skip") {
|
|
1360
|
+
steps.push({ chunkId: item.chunkId, type: item.type, decision: "skipped", reason: item.reason });
|
|
1361
|
+
emit({ phase: "skipped", chunkId: item.chunkId, reason: item.reason });
|
|
1362
|
+
continue;
|
|
1363
|
+
}
|
|
1364
|
+
if (item.action === "block") {
|
|
1365
|
+
await client.recordClientBlock(chunk, item.reason, "agent");
|
|
1366
|
+
steps.push({ chunkId: item.chunkId, type: item.type, decision: "blocked", reason: item.reason });
|
|
1367
|
+
emit({ phase: "blocked", chunkId: item.chunkId, reason: item.reason });
|
|
1368
|
+
continue;
|
|
1369
|
+
}
|
|
1370
|
+
const out = await client.unlock(chunk, { actor: "agent", viaDiscovery: true });
|
|
1371
|
+
if (out.ok && out.receipt) {
|
|
1372
|
+
receiptIds.push(out.receipt.receiptId);
|
|
1373
|
+
purchased.push({ chunkId: item.chunkId, title: item.title, type: item.type, price: item.price, receiptId: out.receipt.receiptId });
|
|
1374
|
+
totalMicros += toMicros(item.price);
|
|
1375
|
+
steps.push({ chunkId: item.chunkId, type: item.type, decision: "bought", receiptId: out.receipt.receiptId, content: out.content });
|
|
1376
|
+
if (item.type === "text" && out.content) boughtText.push(out.content);
|
|
1377
|
+
emit({ phase: "bought", chunkId: item.chunkId, receiptId: out.receipt.receiptId, content: out.content });
|
|
1378
|
+
} else {
|
|
1379
|
+
const reason = out.reason ?? "unlock failed";
|
|
1380
|
+
steps.push({ chunkId: item.chunkId, type: item.type, decision: "blocked", reason });
|
|
1381
|
+
emit({ phase: "blocked", chunkId: item.chunkId, reason });
|
|
1382
|
+
}
|
|
1383
|
+
await sleep2(delay);
|
|
1384
|
+
}
|
|
1385
|
+
const summary = `Task: ${task}
|
|
1386
|
+
Discovered ${matches.length} relevant chunk(s); bought ${receiptIds.length}; skipped the rest; video blocked by policy.
|
|
1387
|
+
|
|
1388
|
+
` + boughtText.map((t) => `\u2022 ${t}`).join("\n\n") + `
|
|
1389
|
+
|
|
1390
|
+
Receipts: ${receiptIds.join(", ") || "none"}`;
|
|
1391
|
+
const result = {
|
|
1392
|
+
task,
|
|
1393
|
+
query,
|
|
1394
|
+
matches,
|
|
1395
|
+
plan,
|
|
1396
|
+
steps,
|
|
1397
|
+
receiptIds,
|
|
1398
|
+
purchased,
|
|
1399
|
+
totalCost: fromMicros(totalMicros),
|
|
1400
|
+
summary
|
|
1401
|
+
};
|
|
1402
|
+
emit({ phase: "done", result });
|
|
1403
|
+
return result;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
// src/agent-console.ts
|
|
1407
|
+
var DEFAULT_AGENT_TASKS = [
|
|
1408
|
+
{ label: "Find evidence supporting MPP receipts", query: "MPP receipts and payment verification" },
|
|
1409
|
+
{ label: "Explain Tempo session payments", query: "Tempo session payments and vouchers" },
|
|
1410
|
+
{ label: "Summarize machine vs human readership", query: "machine vs human read trends data chart" }
|
|
1411
|
+
];
|
|
1412
|
+
var esc2 = (s) => s.replace(/[&<>]/g, (c) => ({ "&": "&", "<": "<", ">": ">" })[c]);
|
|
1413
|
+
var pct = (c) => `${Math.round(c * 100)}%`;
|
|
1414
|
+
function mountAgentConsole(instance, opts = {}) {
|
|
1415
|
+
injectStyles2();
|
|
1416
|
+
const tasks = opts.tasks ?? DEFAULT_AGENT_TASKS;
|
|
1417
|
+
const stepDelayMs = opts.stepDelayMs ?? 750;
|
|
1418
|
+
const fab = document.createElement("button");
|
|
1419
|
+
fab.className = "nr-ac-fab";
|
|
1420
|
+
fab.innerHTML = `${markSvg("#0A0A0A")} Agent Mode`;
|
|
1421
|
+
document.body.appendChild(fab);
|
|
1422
|
+
const overlay = document.createElement("div");
|
|
1423
|
+
overlay.className = "nr-ac-overlay";
|
|
1424
|
+
overlay.innerHTML = `
|
|
1425
|
+
<div class="nr-ac-modal" role="dialog" aria-label="NanoRail Agent Mode">
|
|
1426
|
+
<div class="nr-ac-head">
|
|
1427
|
+
<span class="nr-ac-title">${markSvg()} Agent Mode <em>\u2014 buy only what the task needs</em></span>
|
|
1428
|
+
<button class="nr-ac-close" title="Close">\xD7</button>
|
|
1429
|
+
</div>
|
|
1430
|
+
<div class="nr-ac-body">
|
|
1431
|
+
<div class="nr-ac-setup">
|
|
1432
|
+
<div class="nr-ac-label">1 \xB7 Give the agent a task</div>
|
|
1433
|
+
<div class="nr-ac-tasks"></div>
|
|
1434
|
+
<div class="nr-ac-label">Your buyer policy (the agent must obey it)</div>
|
|
1435
|
+
<div class="nr-ac-policy"></div>
|
|
1436
|
+
<button class="nr-ac-run" disabled>Run Agent</button>
|
|
1437
|
+
</div>
|
|
1438
|
+
<div class="nr-ac-run-views" hidden>
|
|
1439
|
+
<div class="nr-ac-block nr-ac-discovery"></div>
|
|
1440
|
+
<div class="nr-ac-block nr-ac-timeline-wrap"><div class="nr-ac-label">Agent activity \u2014 watch it make economic decisions</div><div class="nr-ac-timeline"></div></div>
|
|
1441
|
+
<div class="nr-ac-block nr-ac-output"></div>
|
|
1442
|
+
</div>
|
|
1443
|
+
</div>
|
|
1444
|
+
</div>`;
|
|
1445
|
+
document.body.appendChild(overlay);
|
|
1446
|
+
const q = (sel) => overlay.querySelector(sel);
|
|
1447
|
+
const tasksEl = q(".nr-ac-tasks");
|
|
1448
|
+
const policyEl = q(".nr-ac-policy");
|
|
1449
|
+
const runBtn = q(".nr-ac-run");
|
|
1450
|
+
const runViews = q(".nr-ac-run-views");
|
|
1451
|
+
const discoveryEl = q(".nr-ac-discovery");
|
|
1452
|
+
const timelineEl = q(".nr-ac-timeline");
|
|
1453
|
+
const outputEl = q(".nr-ac-output");
|
|
1454
|
+
let selected = -1;
|
|
1455
|
+
tasksEl.innerHTML = tasks.map((t, i) => `<button class="nr-ac-task" data-i="${i}">${esc2(t.label)}</button>`).join("");
|
|
1456
|
+
tasksEl.querySelectorAll(".nr-ac-task").forEach((b) => {
|
|
1457
|
+
b.onclick = () => {
|
|
1458
|
+
selected = Number(b.dataset.i);
|
|
1459
|
+
tasksEl.querySelectorAll(".nr-ac-task").forEach((x) => x.classList.remove("on"));
|
|
1460
|
+
b.classList.add("on");
|
|
1461
|
+
runBtn.disabled = false;
|
|
1462
|
+
};
|
|
1463
|
+
});
|
|
1464
|
+
const p = instance.client.getPolicy();
|
|
1465
|
+
policyEl.innerHTML = `<div class="nr-ac-pol-row"><span>Budget</span><strong>$${esc2(p.sessionBudget)}</strong></div><div class="nr-ac-pol-row"><span>Allowed</span><span class="nr-ac-allow">${p.allowedContentTypes.map((t) => esc2(t)).join(", ")}</span></div><div class="nr-ac-pol-row"><span>Blocked</span><span class="nr-ac-block-types">${p.blockedContentTypes.map((t) => esc2(t)).join(", ") || "\u2014"}</span></div>`;
|
|
1466
|
+
const open = () => overlay.classList.add("open");
|
|
1467
|
+
const close = () => overlay.classList.remove("open");
|
|
1468
|
+
fab.onclick = open;
|
|
1469
|
+
q(".nr-ac-close").onclick = close;
|
|
1470
|
+
overlay.onclick = (e) => {
|
|
1471
|
+
if (e.target === overlay) close();
|
|
1472
|
+
};
|
|
1473
|
+
const titleOf = /* @__PURE__ */ new Map();
|
|
1474
|
+
const priceOf = /* @__PURE__ */ new Map();
|
|
1475
|
+
const row = (cls, html) => {
|
|
1476
|
+
const d = document.createElement("div");
|
|
1477
|
+
d.className = `nr-ac-tl ${cls}`;
|
|
1478
|
+
d.innerHTML = html;
|
|
1479
|
+
timelineEl.appendChild(d);
|
|
1480
|
+
timelineEl.scrollTop = timelineEl.scrollHeight;
|
|
1481
|
+
};
|
|
1482
|
+
runBtn.onclick = async () => {
|
|
1483
|
+
if (selected < 0) return;
|
|
1484
|
+
const task = tasks[selected];
|
|
1485
|
+
runBtn.disabled = true;
|
|
1486
|
+
runBtn.textContent = "Agent working\u2026";
|
|
1487
|
+
runViews.hidden = false;
|
|
1488
|
+
discoveryEl.innerHTML = "";
|
|
1489
|
+
timelineEl.innerHTML = "";
|
|
1490
|
+
outputEl.innerHTML = "";
|
|
1491
|
+
titleOf.clear();
|
|
1492
|
+
priceOf.clear();
|
|
1493
|
+
const onProgress = (ev) => {
|
|
1494
|
+
if (ev.phase === "task") {
|
|
1495
|
+
row("q", `<span class="nr-ac-dot"></span> Querying the Publisher Discovery Agent: <em>\u201C${esc2(ev.query)}\u201D</em>`);
|
|
1496
|
+
} else if (ev.phase === "discovery") {
|
|
1497
|
+
for (const m of ev.matches) {
|
|
1498
|
+
titleOf.set(m.chunkId, m.title);
|
|
1499
|
+
priceOf.set(m.chunkId, m.price);
|
|
1500
|
+
}
|
|
1501
|
+
discoveryEl.innerHTML = `<div class="nr-ac-label">Publisher Discovery Agent \u2014 found ${ev.matches.length} relevant paid chunk${ev.matches.length === 1 ? "" : "s"} (no premium content revealed)</div>` + (ev.matches.length ? ev.matches.map(
|
|
1502
|
+
(m, i) => `<div class="nr-ac-disc"><span class="nr-ac-disc-n">${i + 1}</span><div class="nr-ac-disc-main"><b>${esc2(m.title)}</b><span class="nr-ac-disc-teaser">${esc2(m.teaser)}</span><span class="nr-ac-disc-tags">${m.tags.map((t) => `#${esc2(t)}`).join(" ")}</span></div><div class="nr-ac-disc-meta"><span class="nr-ac-type t-${m.type}">${esc2(m.type)}</span><span class="nr-ac-price">$${esc2(m.price)}</span><span class="nr-ac-conf">${pct(m.confidence)} match</span></div></div>`
|
|
1503
|
+
).join("") : '<div class="nr-ac-empty">No relevant paid content \u2014 the agent will not spend.</div>');
|
|
1504
|
+
row("found", `<span class="nr-ac-dot"></span> Found <strong>${ev.matches.length}</strong> purchasable chunk${ev.matches.length === 1 ? "" : "s"}`);
|
|
1505
|
+
} else if (ev.phase === "plan") {
|
|
1506
|
+
for (const it of ev.plan) {
|
|
1507
|
+
titleOf.set(it.chunkId, it.title);
|
|
1508
|
+
priceOf.set(it.chunkId, it.price);
|
|
1509
|
+
}
|
|
1510
|
+
row("eval", `<span class="nr-ac-dot"></span> Evaluating relevance, price & policy\u2026`);
|
|
1511
|
+
} else if (ev.phase === "chunk-start") {
|
|
1512
|
+
if (ev.item.action === "buy") row("buy", `<span class="nr-ac-dot"></span> Purchasing <b>${esc2(ev.item.title)}</b> <span class="nr-ac-cost">$${esc2(ev.item.price)}</span>\u2026`);
|
|
1513
|
+
} else if (ev.phase === "bought") {
|
|
1514
|
+
row("receipt", `<span class="nr-ac-dot ok"></span> Receipt issued \xB7 <code>${esc2(ev.receiptId)}</code>`);
|
|
1515
|
+
} else if (ev.phase === "blocked") {
|
|
1516
|
+
row("blocked", `<span class="nr-ac-dot no"></span> Skipped <b>${esc2(titleOf.get(ev.chunkId) ?? ev.chunkId)}</b> \u2014 ${esc2(ev.reason)}`);
|
|
1517
|
+
} else if (ev.phase === "skipped") {
|
|
1518
|
+
row("skip", `<span class="nr-ac-dot"></span> Skipped <b>${esc2(titleOf.get(ev.chunkId) ?? ev.chunkId)}</b> \u2014 not relevant to the task`);
|
|
1519
|
+
} else if (ev.phase === "done") {
|
|
1520
|
+
row("gen", `<span class="nr-ac-dot"></span> Generating answer\u2026`);
|
|
1521
|
+
row("done", `<span class="nr-ac-dot ok"></span> Done`);
|
|
1522
|
+
renderOutput(outputEl, ev.result.purchased, ev.result.receiptIds, ev.result.totalCost);
|
|
1523
|
+
}
|
|
1524
|
+
};
|
|
1525
|
+
try {
|
|
1526
|
+
await instance.runAgent({ task: task.label, query: task.query, onProgress, stepDelayMs });
|
|
1527
|
+
} catch {
|
|
1528
|
+
row("blocked", `<span class="nr-ac-dot no"></span> Agent run failed \u2014 is the gateway running?`);
|
|
1529
|
+
}
|
|
1530
|
+
runBtn.textContent = "Run again";
|
|
1531
|
+
runBtn.disabled = false;
|
|
1532
|
+
};
|
|
1533
|
+
return { open, close };
|
|
1534
|
+
}
|
|
1535
|
+
function renderOutput(el, purchased, receiptIds, totalCost) {
|
|
1536
|
+
const sources = purchased.length ? purchased.map((s) => `<li>${esc2(s.title)} <span class="nr-ac-cost">$${esc2(s.price)}</span></li>`).join("") : '<li class="nr-ac-empty">None \u2014 nothing met the task + policy bar.</li>';
|
|
1537
|
+
el.innerHTML = `<div class="nr-ac-out-h">Generated answer</div><div class="nr-ac-out-grid"><div><div class="nr-ac-out-label">Sources purchased</div><ul class="nr-ac-sources">${sources}</ul></div><div><div class="nr-ac-out-label">Receipts</div><div class="nr-ac-receipts">${receiptIds.map((r) => `<code>${esc2(r)}</code>`).join(" ") || "\u2014"}</div><div class="nr-ac-out-label" style="margin-top:12px">Total cost</div><div class="nr-ac-total">$${esc2(totalCost)}</div></div></div><div class="nr-ac-thesis">The agent discovered what was available, decided what was worth buying, and paid \u2014 on Tempo, with receipts.</div>`;
|
|
1538
|
+
}
|
|
1539
|
+
function markSvg(fill = "#FFC247") {
|
|
1540
|
+
return `<svg width="15" height="15" viewBox="0 0 64 64" style="vertical-align:-2px;flex:none"><g fill="${fill}"><circle cx="14" cy="12" r="4"/><circle cx="14" cy="22" r="4"/><circle cx="14" cy="32" r="4"/><circle cx="14" cy="42" r="4"/><circle cx="14" cy="52" r="4"/><circle cx="54" cy="12" r="4"/><circle cx="54" cy="22" r="4"/><circle cx="54" cy="42" r="4"/><circle cx="54" cy="52" r="4"/><circle cx="24" cy="22" r="4"/><circle cx="34" cy="32" r="4"/><circle cx="44" cy="42" r="4"/></g></svg>`;
|
|
1541
|
+
}
|
|
1542
|
+
function injectStyles2() {
|
|
1543
|
+
if (document.getElementById("nr-ac-styles")) return;
|
|
1544
|
+
const css = `
|
|
1545
|
+
.nr-ac-fab{position:fixed;left:50%;transform:translateX(-50%);bottom:22px;z-index:99998;display:flex;align-items:center;gap:7px;background:#FFC247;color:#0A0A0A;border:none;border-radius:999px;padding:11px 20px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;font-size:14px;font-weight:800;cursor:pointer;box-shadow:0 10px 30px rgba(255,194,71,.35)}
|
|
1546
|
+
.nr-ac-fab:hover{background:#E2A12F}
|
|
1547
|
+
.nr-ac-overlay{position:fixed;inset:0;z-index:100000;background:rgba(7,7,7,.66);backdrop-filter:blur(4px);display:none;align-items:flex-start;justify-content:center;padding:5vh 16px;overflow:auto}
|
|
1548
|
+
.nr-ac-overlay.open{display:flex}
|
|
1549
|
+
.nr-ac-modal{width:100%;max-width:660px;background:#0B0B0B;border:1px solid #1E1E1E;border-radius:16px;color:#EDEDED;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;box-shadow:0 24px 70px rgba(0,0,0,.6);overflow:hidden}
|
|
1550
|
+
.nr-ac-head{display:flex;align-items:center;justify-content:space-between;padding:16px 18px;border-bottom:1px solid #1E1E1E;background:#0E0E0E}
|
|
1551
|
+
.nr-ac-title{display:flex;align-items:center;gap:8px;font-weight:700;font-size:15px}
|
|
1552
|
+
.nr-ac-title em{color:#8A8A8A;font-style:normal;font-weight:400;font-size:13px}
|
|
1553
|
+
.nr-ac-close{background:none;border:none;color:#8A8A8A;font-size:22px;line-height:1;cursor:pointer}
|
|
1554
|
+
.nr-ac-close:hover{color:#EDEDED}
|
|
1555
|
+
.nr-ac-body{padding:18px;max-height:78vh;overflow:auto}
|
|
1556
|
+
.nr-ac-label{font-family:ui-monospace,monospace;font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:#8A8A8A;margin:0 0 8px}
|
|
1557
|
+
.nr-ac-tasks{display:flex;flex-direction:column;gap:8px;margin-bottom:18px}
|
|
1558
|
+
.nr-ac-task{text-align:left;background:#0E0E0E;border:1px solid #1E1E1E;border-radius:10px;padding:11px 14px;color:#EDEDED;font-size:14px;cursor:pointer}
|
|
1559
|
+
.nr-ac-task:hover{border-color:#3A3A3A}
|
|
1560
|
+
.nr-ac-task.on{border-color:#FFC247;background:rgba(255,194,71,.08);box-shadow:0 0 0 1px rgba(255,194,71,.25)}
|
|
1561
|
+
.nr-ac-policy{background:#0E0E0E;border:1px solid #1E1E1E;border-radius:10px;padding:10px 14px;margin-bottom:18px}
|
|
1562
|
+
.nr-ac-pol-row{display:flex;justify-content:space-between;align-items:center;padding:4px 0;font-size:13px;color:#B9B9B9}
|
|
1563
|
+
.nr-ac-pol-row strong{color:#FFC247;font-family:ui-monospace,monospace}
|
|
1564
|
+
.nr-ac-allow{color:#86efac}.nr-ac-block-types{color:#fca5a5}
|
|
1565
|
+
.nr-ac-run{width:100%;background:#FFC247;color:#0A0A0A;border:none;border-radius:10px;padding:12px;font-weight:800;font-size:15px;cursor:pointer}
|
|
1566
|
+
.nr-ac-run:disabled{opacity:.45;cursor:default}
|
|
1567
|
+
.nr-ac-run:not(:disabled):hover{background:#E2A12F}
|
|
1568
|
+
.nr-ac-block{margin-top:20px}
|
|
1569
|
+
.nr-ac-disc{display:flex;gap:10px;align-items:flex-start;background:#0E0E0E;border:1px solid #1E1E1E;border-radius:10px;padding:11px 13px;margin-bottom:8px}
|
|
1570
|
+
.nr-ac-disc-n{width:20px;height:20px;flex:none;border-radius:6px;background:#161616;color:#FFC247;font-family:ui-monospace,monospace;font-size:11px;display:flex;align-items:center;justify-content:center;font-weight:700}
|
|
1571
|
+
.nr-ac-disc-main{flex:1;display:flex;flex-direction:column;gap:2px;min-width:0}
|
|
1572
|
+
.nr-ac-disc-main b{font-size:14px}
|
|
1573
|
+
.nr-ac-disc-teaser{color:#9A9A9A;font-size:12px}
|
|
1574
|
+
.nr-ac-disc-tags{color:#6E6E6E;font-size:11px;font-family:ui-monospace,monospace}
|
|
1575
|
+
.nr-ac-disc-meta{display:flex;flex-direction:column;align-items:flex-end;gap:3px;flex:none}
|
|
1576
|
+
.nr-ac-price{font-family:ui-monospace,monospace;font-weight:700;color:#EDEDED;font-size:13px}
|
|
1577
|
+
.nr-ac-conf{font-size:11px;color:#FFC247;font-family:ui-monospace,monospace}
|
|
1578
|
+
.nr-ac-type{font-size:10px;text-transform:uppercase;letter-spacing:.5px;padding:1px 6px;border-radius:999px;background:#161616;color:#C4C4C4}
|
|
1579
|
+
.nr-ac-type.t-video{background:#3b1d1d;color:#fca5a5}.nr-ac-type.t-chart{background:#14321f;color:#86efac}.nr-ac-type.t-citation_pack{background:#332b12;color:#fde68a}
|
|
1580
|
+
.nr-ac-timeline{background:#0E0E0E;border:1px solid #1E1E1E;border-radius:10px;padding:10px 14px;max-height:240px;overflow:auto;font-size:13px}
|
|
1581
|
+
.nr-ac-tl{display:flex;align-items:center;gap:8px;padding:5px 0;animation:nr-ac-in .25s ease;color:#D4D4D4}
|
|
1582
|
+
@keyframes nr-ac-in{from{opacity:0;transform:translateX(-5px)}to{opacity:1;transform:none}}
|
|
1583
|
+
.nr-ac-tl code{font-family:ui-monospace,monospace;font-size:11px;color:#FFC247;background:#161616;padding:1px 5px;border-radius:4px}
|
|
1584
|
+
.nr-ac-tl em{color:#9A9A9A;font-style:normal}
|
|
1585
|
+
.nr-ac-cost{font-family:ui-monospace,monospace;color:#FFC247;font-weight:700}
|
|
1586
|
+
.nr-ac-dot{width:8px;height:8px;border-radius:50%;flex:none;background:#5C5C5C}
|
|
1587
|
+
.nr-ac-dot.ok{background:#57E389}.nr-ac-dot.no{background:#ef4444}
|
|
1588
|
+
.nr-ac-tl.buy{color:#EDEDED}.nr-ac-tl.blocked{color:#fca5a5}
|
|
1589
|
+
.nr-ac-output{background:linear-gradient(180deg,#0E0E0E,#0B0B0B);border:1px solid #FFC247;border-radius:12px;padding:16px 18px}
|
|
1590
|
+
.nr-ac-out-h{font-weight:800;color:#FFC247;margin-bottom:12px}
|
|
1591
|
+
.nr-ac-out-grid{display:grid;grid-template-columns:1fr 1fr;gap:18px}
|
|
1592
|
+
.nr-ac-out-label{font-family:ui-monospace,monospace;font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:#8A8A8A;margin-bottom:6px}
|
|
1593
|
+
.nr-ac-sources{margin:0;padding-left:16px;font-size:13px}.nr-ac-sources li{margin:3px 0}
|
|
1594
|
+
.nr-ac-receipts code{font-family:ui-monospace,monospace;font-size:11px;color:#FFC247;background:#161616;padding:1px 5px;border-radius:4px;margin-right:4px}
|
|
1595
|
+
.nr-ac-total{font-family:ui-monospace,monospace;font-size:22px;font-weight:800;color:#FFC247}
|
|
1596
|
+
.nr-ac-thesis{margin-top:14px;padding-top:12px;border-top:1px solid #1E1E1E;color:#9A9A9A;font-size:13px}
|
|
1597
|
+
.nr-ac-empty{color:#8A8A8A}
|
|
1598
|
+
@media (max-width:560px){.nr-ac-out-grid{grid-template-columns:1fr}}
|
|
1599
|
+
`;
|
|
1600
|
+
const style = document.createElement("style");
|
|
1601
|
+
style.id = "nr-ac-styles";
|
|
1602
|
+
style.textContent = css;
|
|
1603
|
+
document.head.appendChild(style);
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
// src/mount.ts
|
|
1607
|
+
function initNanoRail(config = {}) {
|
|
1608
|
+
const gatewayUrl = config.gatewayUrl ?? "http://localhost:4020";
|
|
1609
|
+
const articleId = config.articleId ?? "the-agentic-chronicle";
|
|
1610
|
+
const client = new NanoRailClient({ gatewayUrl, actor: config.actor ?? "human" });
|
|
1611
|
+
const widget = config.mountWidget === false ? void 0 : new NanoRailWidget(client);
|
|
1612
|
+
client.ensureSession().then(() => widget?.render());
|
|
1613
|
+
client.startSettlementWatch();
|
|
1614
|
+
const buildAndRegister = async () => {
|
|
1615
|
+
const m = generateManifest({ articleId });
|
|
1616
|
+
if (m.chunks.length > 0) await client.registerManifest(m).catch(() => {
|
|
1617
|
+
});
|
|
1618
|
+
return m;
|
|
1619
|
+
};
|
|
1620
|
+
const instance = {
|
|
1621
|
+
client,
|
|
1622
|
+
widget,
|
|
1623
|
+
manifest: generateManifest({ articleId }),
|
|
1624
|
+
rescan: () => scanAndLock(client, document, { autoUnlock: config.autoUnlockOnScroll === true }),
|
|
1625
|
+
regenerateManifest: async () => {
|
|
1626
|
+
instance.manifest = await buildAndRegister();
|
|
1627
|
+
return instance.manifest;
|
|
1628
|
+
},
|
|
1629
|
+
runAgent: (opts) => runAgentSummary(client, articleId, opts),
|
|
1630
|
+
forceVideoRejection: async () => {
|
|
1631
|
+
const el = document.querySelector('[data-nanorail-type="video"]');
|
|
1632
|
+
const chunk = el ? readChunk(el) : void 0;
|
|
1633
|
+
if (!chunk) return { rejected: false, reason: "no video chunk on page" };
|
|
1634
|
+
return client.forceServerRejection(chunk, "human");
|
|
1635
|
+
},
|
|
1636
|
+
unlockTempoSnippet: () => client.unlockTempoSnippet()
|
|
1637
|
+
};
|
|
1638
|
+
buildAndRegister().then((m) => {
|
|
1639
|
+
instance.manifest = m;
|
|
1640
|
+
});
|
|
1641
|
+
if (config.autoScan !== false) {
|
|
1642
|
+
scanAndLock(client, document, { autoUnlock: config.autoUnlockOnScroll === true });
|
|
1643
|
+
}
|
|
1644
|
+
if (config.agentConsole) {
|
|
1645
|
+
const console_ = mountAgentConsole(instance, { tasks: config.agentTasks });
|
|
1646
|
+
instance.openAgentConsole = console_.open;
|
|
1647
|
+
}
|
|
1648
|
+
return instance;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
// src/browser.ts
|
|
1652
|
+
function init() {
|
|
1653
|
+
const nr = initNanoRail(window.NANORAIL_CONFIG ?? {});
|
|
1654
|
+
window.NanoRail = nr;
|
|
1655
|
+
document.dispatchEvent(new CustomEvent("nanorail:ready"));
|
|
1656
|
+
}
|
|
1657
|
+
if (document.readyState === "loading") {
|
|
1658
|
+
document.addEventListener("DOMContentLoaded", init);
|
|
1659
|
+
} else {
|
|
1660
|
+
init();
|
|
1661
|
+
}
|
|
1662
|
+
})();
|
|
1663
|
+
/*! Bundled license information:
|
|
1664
|
+
|
|
1665
|
+
@noble/ed25519/index.js:
|
|
1666
|
+
(*! noble-ed25519 - MIT License (c) 2019 Paul Miller (paulmillr.com) *)
|
|
1667
|
+
*/
|
|
1668
|
+
//# sourceMappingURL=nanorail.js.map
|