viveworker 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/package.json +1 -1
- package/scripts/moltbook-api.mjs +319 -72
- package/scripts/share-cli.mjs +115 -6
- package/scripts/viveworker-bridge.mjs +61 -14
- package/scripts/viveworker.mjs +11 -0
package/README.md
CHANGED
|
@@ -168,12 +168,21 @@ What it supports:
|
|
|
168
168
|
- optional password protection
|
|
169
169
|
- optional expiry
|
|
170
170
|
|
|
171
|
+
HTML uploads are optimized by default when possible.
|
|
172
|
+
This is especially useful for bundled standalone HTML exports that carry large embedded font payloads.
|
|
173
|
+
`viveworker share upload` will try to shrink those files locally before upload, which often makes otherwise-too-large deck or prototype exports shareable without extra prep.
|
|
174
|
+
|
|
175
|
+
If you want the original HTML bytes untouched, use `--no-optimize`.
|
|
176
|
+
When optimization strips embedded fonts, layout and typography may change slightly, but the goal is to preserve a usable standalone share URL.
|
|
177
|
+
|
|
171
178
|
It reuses the same A2A credentials as the rest of `viveworker`, so there is no separate auth or setup step.
|
|
172
179
|
|
|
173
180
|
Typical commands:
|
|
174
181
|
|
|
175
182
|
- `npx viveworker share upload report.html`
|
|
183
|
+
- `npx viveworker share upload deck_standalone.html`
|
|
176
184
|
- `npx viveworker share upload report.pdf --password "hunter2" --expires-days 7`
|
|
185
|
+
- `npx viveworker share upload deck_standalone.html --no-optimize`
|
|
177
186
|
- `npx viveworker share list`
|
|
178
187
|
- `npx viveworker share update <slug> --password "hunter2"`
|
|
179
188
|
- `npx viveworker share update <slug> --expires-days 7`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "viveworker",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Open mobile control surface for Codex, Claude, Thread Sharing, File Share, Moltbook, and A2A tasks on your trusted LAN.",
|
|
5
5
|
"author": "Yuta Hoshino <hoshino.lireneo@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
package/scripts/moltbook-api.mjs
CHANGED
|
@@ -312,43 +312,114 @@ export async function listPendingDrafts(dir = DEFAULT_DRAFTS_DIR) {
|
|
|
312
312
|
//
|
|
313
313
|
// Shared by the CLI (for manual `reply` flow) and the bridge (for fire-and-
|
|
314
314
|
// forget draft posting on approval).
|
|
315
|
+
//
|
|
316
|
+
// Moltbook's verification puzzles are obfuscated word problems — e.g.
|
|
317
|
+
// `lOoB-stErR ClAw FoRcE iS tHiRtY fIvE NoOtOnS aNd iT s OtHeR ClAw Is tWeNtY
|
|
318
|
+
// tHrEe NooToNs, tOtAl/ FoRcE?` → 35 + 23 = 58.00. The solver strips
|
|
319
|
+
// non-letter noise, identifies number words (including compounds like
|
|
320
|
+
// "one hundred forty two" = 142), identifies operator words around each
|
|
321
|
+
// number, and evaluates left-to-right. See `scripts/test-puzzle-solver.mjs`
|
|
322
|
+
// for the regression corpus; add new cases there whenever we observe a
|
|
323
|
+
// failure in the wild via the `~/.viveworker/moltbook-verify-history.jsonl`
|
|
324
|
+
// log.
|
|
325
|
+
|
|
326
|
+
// Operator encoding: 0=add, 1=sub, 2=mul, 3=div.
|
|
327
|
+
const OP_ADD = 0;
|
|
328
|
+
const OP_SUB = 1;
|
|
329
|
+
const OP_MUL = 2;
|
|
330
|
+
const OP_DIV = 3;
|
|
331
|
+
|
|
332
|
+
const NUMBER_WORDS = Object.freeze({
|
|
333
|
+
zero: 0, one: 1, two: 2, three: 3, four: 4, five: 5, six: 6, seven: 7, eight: 8, nine: 9,
|
|
334
|
+
ten: 10, eleven: 11, twelve: 12, thirteen: 13, fourteen: 14, fifteen: 15, sixteen: 16,
|
|
335
|
+
seventeen: 17, eighteen: 18, nineteen: 19,
|
|
336
|
+
twenty: 20, thirty: 30, forty: 40, fifty: 50,
|
|
337
|
+
sixty: 60, seventy: 70, eighty: 80, ninety: 90,
|
|
338
|
+
hundred: 100, thousand: 1000,
|
|
339
|
+
// Cultural shortcuts that occasionally appear in the generator.
|
|
340
|
+
dozen: 12, score: 20,
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// Operator words. Keep contextual/framing words (force, velocity, total, etc.)
|
|
344
|
+
// mapped to OP_ADD so their presence doesn't crash the parser — the binary-
|
|
345
|
+
// sequence builder drops ops that have no number to operate against.
|
|
346
|
+
const OP_WORDS = Object.freeze({
|
|
347
|
+
// Addition / neutral-context.
|
|
348
|
+
add: OP_ADD, added: OP_ADD, adds: OP_ADD, plus: OP_ADD, and: OP_ADD,
|
|
349
|
+
sum: OP_ADD, summed: OP_ADD, combined: OP_ADD, combine: OP_ADD, together: OP_ADD,
|
|
350
|
+
total: OP_ADD, totaled: OP_ADD, totals: OP_ADD, both: OP_ADD,
|
|
351
|
+
gain: OP_ADD, gains: OP_ADD, gained: OP_ADD,
|
|
352
|
+
increase: OP_ADD, increased: OP_ADD, increases: OP_ADD,
|
|
353
|
+
boost: OP_ADD, boosted: OP_ADD, boosts: OP_ADD,
|
|
354
|
+
faster: OP_ADD, accelerates: OP_ADD, accelerated: OP_ADD, more: OP_ADD,
|
|
355
|
+
force: OP_ADD, velocity: OP_ADD, speed: OP_ADD, exerts: OP_ADD, new: OP_ADD,
|
|
356
|
+
// Subtraction.
|
|
357
|
+
minus: OP_SUB, less: OP_SUB, difference: OP_SUB, subtract: OP_SUB,
|
|
358
|
+
subtracts: OP_SUB, subtracted: OP_SUB,
|
|
359
|
+
decrease: OP_SUB, decreased: OP_SUB, decreases: OP_SUB,
|
|
360
|
+
loses: OP_SUB, lost: OP_SUB, lose: OP_SUB,
|
|
361
|
+
reduce: OP_SUB, reduced: OP_SUB, reduces: OP_SUB, reducing: OP_SUB, reduction: OP_SUB,
|
|
362
|
+
drop: OP_SUB, drops: OP_SUB, dropped: OP_SUB,
|
|
363
|
+
slower: OP_SUB, slows: OP_SUB, slowed: OP_SUB,
|
|
364
|
+
remove: OP_SUB, removes: OP_SUB, removed: OP_SUB,
|
|
365
|
+
// Multiplication.
|
|
366
|
+
multiply: OP_MUL, multiplied: OP_MUL, multiplies: OP_MUL,
|
|
367
|
+
times: OP_MUL, product: OP_MUL,
|
|
368
|
+
twice: OP_MUL, double: OP_MUL, doubled: OP_MUL,
|
|
369
|
+
triple: OP_MUL, tripled: OP_MUL,
|
|
370
|
+
squared: OP_MUL, square: OP_MUL,
|
|
371
|
+
// Division.
|
|
372
|
+
divide: OP_DIV, divided: OP_DIV, divides: OP_DIV, division: OP_DIV,
|
|
373
|
+
ratio: OP_DIV,
|
|
374
|
+
// NOTE: "per" intentionally NOT mapped here — "per second" / "per hour"
|
|
375
|
+
// are units, not division operators. When a puzzle really means division
|
|
376
|
+
// it uses "divided by" or "ratio of" or "split".
|
|
377
|
+
half: OP_DIV, halved: OP_DIV, halves: OP_DIV,
|
|
378
|
+
split: OP_DIV,
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// Collapse runs of the same letter: the generator often doubles consonants
|
|
382
|
+
// (e.g. "NoOtOnS" → "nootons"; "lOoB-stErR" → "loobsterr"). Collapsing means
|
|
383
|
+
// known-word detection still fires on the de-doubled form.
|
|
384
|
+
const collapseRuns = (w) => w.replace(/([a-z])\1+/g, "$1");
|
|
385
|
+
|
|
386
|
+
function lookupWord(word) {
|
|
387
|
+
if (NUMBER_WORDS[word] != null) return { type: "num", value: NUMBER_WORDS[word] };
|
|
388
|
+
if (OP_WORDS[word] != null) return { type: "op", value: OP_WORDS[word] };
|
|
389
|
+
const collapsed = collapseRuns(word);
|
|
390
|
+
if (collapsed !== word) {
|
|
391
|
+
if (NUMBER_WORDS[collapsed] != null) return { type: "num", value: NUMBER_WORDS[collapsed] };
|
|
392
|
+
if (OP_WORDS[collapsed] != null) return { type: "op", value: OP_WORDS[collapsed] };
|
|
393
|
+
}
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
315
396
|
|
|
316
|
-
// Naive verification-puzzle solver.
|
|
317
|
-
//
|
|
318
|
-
// `null` if it can't confidently solve — caller falls back to LLM or manual.
|
|
397
|
+
// Naive verification-puzzle solver. Returns answer as "XX.XX" or null if we
|
|
398
|
+
// can't confidently recover two numbers. Caller falls back to LLM.
|
|
319
399
|
export function solveVerificationPuzzle(challengeText) {
|
|
320
400
|
if (!challengeText) return null;
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
seventeen: 17, eighteen: 18, nineteen: 19, twenty: 20, thirty: 30, forty: 40, fifty: 50,
|
|
328
|
-
sixty: 60, seventy: 70, eighty: 80, ninety: 90, hundred: 100,
|
|
329
|
-
};
|
|
330
|
-
const collapseRuns = (w) => w.replace(/([a-z])\1+/g, "$1");
|
|
331
|
-
const rawTokens = cleaned
|
|
401
|
+
|
|
402
|
+
// Strip everything that isn't a letter or digit. Symbols (/, *, ^, ~, [, ])
|
|
403
|
+
// are deliberately NOT operators — the generator sprinkles them as pure
|
|
404
|
+
// noise. All real operators are spelled out in words.
|
|
405
|
+
const rawTokens = String(challengeText)
|
|
406
|
+
.toLowerCase()
|
|
332
407
|
.replace(/[^a-z0-9\s]/g, " ")
|
|
333
408
|
.split(/\s+/)
|
|
334
409
|
.filter(Boolean);
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
"divide", "divided", "ratio",
|
|
340
|
-
"how", "much", "what", "exerts", "new",
|
|
341
|
-
]);
|
|
342
|
-
const isKnown = (w) => numberWords[w] != null || numberWords[collapseRuns(w)] != null || operationWords.has(w) || operationWords.has(collapseRuns(w));
|
|
410
|
+
|
|
411
|
+
// The generator sometimes splits a known word into pieces separated by
|
|
412
|
+
// stripped punctuation (e.g. "tHiR-tY" → "thir" "ty"). Try to re-merge
|
|
413
|
+
// adjacent tokens if their concatenation is a known number or operator.
|
|
343
414
|
const merged = [];
|
|
344
415
|
let ti = 0;
|
|
345
416
|
while (ti < rawTokens.length) {
|
|
346
|
-
let best = rawTokens[ti];
|
|
347
417
|
let bestLen = 1;
|
|
418
|
+
let best = rawTokens[ti];
|
|
348
419
|
let candidate = rawTokens[ti];
|
|
349
420
|
for (let span = 2; span <= Math.min(4, rawTokens.length - ti); span++) {
|
|
350
421
|
candidate += rawTokens[ti + span - 1];
|
|
351
|
-
if (
|
|
422
|
+
if (lookupWord(candidate)) {
|
|
352
423
|
best = candidate;
|
|
353
424
|
bestLen = span;
|
|
354
425
|
}
|
|
@@ -356,57 +427,199 @@ export function solveVerificationPuzzle(challengeText) {
|
|
|
356
427
|
merged.push(best);
|
|
357
428
|
ti += bestLen;
|
|
358
429
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
while (i < words.length) {
|
|
369
|
-
const w = words[i];
|
|
430
|
+
|
|
431
|
+
// Convert the token stream into a sequence of {type:"num"|"op", value}.
|
|
432
|
+
// Compound numbers ("one hundred forty two" = 142) are collected greedily.
|
|
433
|
+
const tokens = [];
|
|
434
|
+
let j = 0;
|
|
435
|
+
while (j < merged.length) {
|
|
436
|
+
const w = merged[j];
|
|
437
|
+
|
|
438
|
+
// Bare digit.
|
|
370
439
|
if (/^\d+$/.test(w)) {
|
|
371
|
-
|
|
372
|
-
|
|
440
|
+
tokens.push({ type: "num", value: Number(w) });
|
|
441
|
+
j += 1;
|
|
373
442
|
continue;
|
|
374
443
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
444
|
+
|
|
445
|
+
const lookup = lookupWord(w);
|
|
446
|
+
if (!lookup) { j += 1; continue; }
|
|
447
|
+
|
|
448
|
+
if (lookup.type === "num") {
|
|
449
|
+
// Greedy compound-number consumption. Examples:
|
|
450
|
+
// "thirty five" = 35 (chunk += 30 then chunk += 5)
|
|
451
|
+
// "one hundred forty two" = 142 (chunk=1 → *100 → +40 → +2)
|
|
452
|
+
// "three thousand four hundred fifty" = 3450
|
|
453
|
+
let sum = 0;
|
|
454
|
+
let chunk = 0;
|
|
455
|
+
let consumed = false;
|
|
456
|
+
while (j < merged.length) {
|
|
457
|
+
const cur = merged[j];
|
|
458
|
+
if (/^\d+$/.test(cur)) {
|
|
459
|
+
if (consumed) break; // mixing word-number + digit-number, stop
|
|
460
|
+
sum = Number(cur);
|
|
461
|
+
consumed = true;
|
|
462
|
+
j += 1;
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
465
|
+
const inner = lookupWord(cur);
|
|
466
|
+
if (!inner || inner.type !== "num") break;
|
|
467
|
+
const n = inner.value;
|
|
468
|
+
if (n === 1000) {
|
|
469
|
+
sum += (chunk || 1) * 1000;
|
|
470
|
+
chunk = 0;
|
|
471
|
+
} else if (n === 100) {
|
|
472
|
+
chunk = (chunk || 1) * 100;
|
|
473
|
+
} else {
|
|
474
|
+
// n < 100: tens + ones accumulate into the current chunk.
|
|
475
|
+
chunk += n;
|
|
476
|
+
}
|
|
477
|
+
consumed = true;
|
|
478
|
+
j += 1;
|
|
384
479
|
}
|
|
385
|
-
|
|
480
|
+
if (consumed) tokens.push({ type: "num", value: sum + chunk });
|
|
386
481
|
continue;
|
|
387
482
|
}
|
|
388
|
-
|
|
483
|
+
|
|
484
|
+
// Operator.
|
|
485
|
+
tokens.push({ type: "op", value: lookup.value });
|
|
486
|
+
j += 1;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Capture the first non-default op that appears BEFORE the first number.
|
|
490
|
+
// Phrases like "product of X and Y", "difference between X and Y", and
|
|
491
|
+
// "ratio of X to Y" put the real operation at the front and leave only a
|
|
492
|
+
// weak connector ("and", "to", "of") between the numbers. Without this,
|
|
493
|
+
// the solver would drop the leading op and default to addition.
|
|
494
|
+
let leadingStrongOp = null;
|
|
495
|
+
for (const t of tokens) {
|
|
496
|
+
if (t.type === "num") break;
|
|
497
|
+
if (t.type === "op" && t.value !== OP_ADD) { leadingStrongOp = t.value; break; }
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Normalise into a strict alternation [num, op, num, op, num, ...].
|
|
501
|
+
// - Drop leading ops (framing words like "total force is ...").
|
|
502
|
+
// - If two ops appear back-to-back, prefer the STRONGER (non-ADD) one
|
|
503
|
+
// over the default ADD. This is because contextual framing verbs
|
|
504
|
+
// (force / velocity / speed / exerts) are mapped to OP_ADD so their
|
|
505
|
+
// presence doesn't crash the parser, but they should NOT clobber a
|
|
506
|
+
// real operator (OP_SUB / OP_MUL / OP_DIV) that appears right next
|
|
507
|
+
// to them. Example: "reducing speed by four" → tokens end up as
|
|
508
|
+
// [OP_SUB(reducing), OP_ADD(speed)] — SUB must survive.
|
|
509
|
+
// When both ops are the same strength, the latter wins (closest to
|
|
510
|
+
// the following number).
|
|
511
|
+
// - If two nums appear with no op between them, insert a default add.
|
|
512
|
+
// - Drop trailing ops.
|
|
513
|
+
const seq = [];
|
|
514
|
+
for (const t of tokens) {
|
|
515
|
+
if (seq.length === 0) {
|
|
516
|
+
if (t.type === "num") seq.push(t);
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
const last = seq[seq.length - 1];
|
|
520
|
+
if (t.type === "num") {
|
|
521
|
+
if (last.type === "num") {
|
|
522
|
+
seq.push({ type: "op", value: OP_ADD });
|
|
523
|
+
}
|
|
524
|
+
seq.push(t);
|
|
525
|
+
} else {
|
|
526
|
+
if (last.type === "op") {
|
|
527
|
+
// Prefer strong over default (ADD). Otherwise latter wins.
|
|
528
|
+
if (last.value === OP_ADD && t.value !== OP_ADD) {
|
|
529
|
+
seq[seq.length - 1] = t;
|
|
530
|
+
} else if (last.value !== OP_ADD && t.value === OP_ADD) {
|
|
531
|
+
// Keep last — strong op survives over a trailing framing verb.
|
|
532
|
+
} else {
|
|
533
|
+
seq[seq.length - 1] = t;
|
|
534
|
+
}
|
|
535
|
+
} else {
|
|
536
|
+
seq.push(t);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
389
539
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
540
|
+
while (seq.length > 0 && seq[seq.length - 1].type === "op") seq.pop();
|
|
541
|
+
|
|
542
|
+
if (seq.length < 3) return null;
|
|
543
|
+
if (seq[0].type !== "num") return null;
|
|
544
|
+
|
|
545
|
+
// Promote the leading op if no strong op appeared between the numbers. We
|
|
546
|
+
// only do this when every internal op is the default OP_ADD — otherwise
|
|
547
|
+
// the internal ops were the real signal (e.g. "a+b-c" should stay mixed,
|
|
548
|
+
// even if the sentence started with "total").
|
|
549
|
+
if (leadingStrongOp != null) {
|
|
550
|
+
const hasStrongInternal = seq.some((t) => t.type === "op" && t.value !== OP_ADD);
|
|
551
|
+
if (!hasStrongInternal) {
|
|
552
|
+
for (const t of seq) {
|
|
553
|
+
if (t.type === "op") t.value = leadingStrongOp;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Evaluate strictly left-to-right (no precedence — Moltbook puzzles are
|
|
559
|
+
// binary in practice, so this only matters for the rare 3-number case).
|
|
560
|
+
let result = seq[0].value;
|
|
561
|
+
for (let k = 1; k < seq.length; k += 2) {
|
|
562
|
+
const op = seq[k];
|
|
563
|
+
const rhs = seq[k + 1];
|
|
564
|
+
if (!op || !rhs || op.type !== "op" || rhs.type !== "num") break;
|
|
565
|
+
switch (op.value) {
|
|
566
|
+
case OP_ADD: result += rhs.value; break;
|
|
567
|
+
case OP_SUB: result -= rhs.value; break;
|
|
568
|
+
case OP_MUL: result *= rhs.value; break;
|
|
569
|
+
case OP_DIV: result = rhs.value !== 0 ? result / rhs.value : result; break;
|
|
570
|
+
default: break;
|
|
571
|
+
}
|
|
404
572
|
}
|
|
573
|
+
|
|
574
|
+
if (!Number.isFinite(result)) return null;
|
|
575
|
+
// Refuse answers the puzzle generator shouldn't produce. A negative answer
|
|
576
|
+
// usually means we mis-identified the operator direction; returning null
|
|
577
|
+
// lets the LLM take a clean shot rather than submitting a definitely-wrong
|
|
578
|
+
// value.
|
|
579
|
+
if (result < 0) return null;
|
|
580
|
+
|
|
405
581
|
return result.toFixed(2);
|
|
406
582
|
}
|
|
407
583
|
|
|
408
584
|
// LLM-based verification puzzle solver. Shells out to claude or codex CLI.
|
|
409
585
|
// Returns the answer as "XX.XX" string, or null if unavailable.
|
|
586
|
+
//
|
|
587
|
+
// The naive solver misses ~13% of Moltbook's puzzles (mostly 3-number or
|
|
588
|
+
// unusual-vocabulary cases). The LLM fallback has been the same ~13% blind
|
|
589
|
+
// spot because parsing was too strict — it only accepted "XX.XX" on its own
|
|
590
|
+
// or a pure-integer line, and many CLI wrappers prepend banners or reason
|
|
591
|
+
// step-by-step before emitting the final number. `extractPuzzleAnswerFromText`
|
|
592
|
+
// is now shared with `solvePuzzleWithLLM` and is much more forgiving:
|
|
593
|
+
// prefer explicit decimals, fall back to the last standalone integer, then
|
|
594
|
+
// the last integer anywhere in the response. Exported for tests.
|
|
595
|
+
export function extractPuzzleAnswerFromText(text) {
|
|
596
|
+
if (!text) return null;
|
|
597
|
+
const trimmed = String(text).trim();
|
|
598
|
+
if (!trimmed) return null;
|
|
599
|
+
|
|
600
|
+
// 1. Prefer an exact "NN.NN" (two decimals) token. This is what the prompt
|
|
601
|
+
// explicitly asks for and what every happy-path response contains.
|
|
602
|
+
const twoDecimal = trimmed.match(/(?:^|[^\d])(\d+\.\d{2})(?:[^\d]|$)/);
|
|
603
|
+
if (twoDecimal) return twoDecimal[1];
|
|
604
|
+
|
|
605
|
+
// 2. Accept a one-decimal form and pad — e.g. "58.5" → "58.50".
|
|
606
|
+
const oneDecimal = trimmed.match(/(?:^|[^\d])(\d+\.\d)(?:[^\d]|$)/);
|
|
607
|
+
if (oneDecimal) return `${oneDecimal[1]}0`;
|
|
608
|
+
|
|
609
|
+
// 3. If the response contains any integer at all, use the LAST one. The LLM
|
|
610
|
+
// often reasons step-by-step ("thirty-five = 35, twenty-three = 23,
|
|
611
|
+
// sum is 58"), and the final integer is the answer. This is less
|
|
612
|
+
// precise than step (1) but beats returning null on otherwise-correct
|
|
613
|
+
// reasoning.
|
|
614
|
+
const ints = [...trimmed.matchAll(/\b(\d+)\b/g)].map((m) => m[1]);
|
|
615
|
+
if (ints.length > 0) {
|
|
616
|
+
const pick = Number(ints[ints.length - 1]);
|
|
617
|
+
if (Number.isFinite(pick) && pick >= 0) return `${pick}.00`;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
return null;
|
|
621
|
+
}
|
|
622
|
+
|
|
410
623
|
export async function solvePuzzleWithLLM(challengeText) {
|
|
411
624
|
if (!challengeText) return null;
|
|
412
625
|
const prompt =
|
|
@@ -414,13 +627,16 @@ export async function solvePuzzleWithLLM(challengeText) {
|
|
|
414
627
|
`The text has random capitalization, doubled letters, and stray punctuation — ignore all of that. ` +
|
|
415
628
|
`CRITICAL: ALL symbols (/, *, ^, ~, [, ], etc.) are NOISE, NOT arithmetic operators. ` +
|
|
416
629
|
`The operation is ALWAYS expressed in natural language words only. ` +
|
|
417
|
-
`Extract the numbers (written as words like "thirty five" = 35
|
|
418
|
-
`
|
|
419
|
-
`
|
|
420
|
-
`
|
|
421
|
-
`
|
|
630
|
+
`Extract the numbers (written as words like "thirty five" = 35, or "one hundred forty" = 140), ` +
|
|
631
|
+
`determine the operation from WORDS ONLY ` +
|
|
632
|
+
`(addition: "total", "combined", "and", "plus", "gains", "sum", "together", "increased", "new velocity"; ` +
|
|
633
|
+
`subtraction: "difference", "minus", "less", "loses", "decreased", "reduced"; ` +
|
|
634
|
+
`multiplication: "times", "product", "multiplied", "doubled", "tripled", "squared"; ` +
|
|
635
|
+
`division: "divided by", "ratio", "per", "halved"). ` +
|
|
636
|
+
`If two or more operation words appear, apply them left-to-right in order. ` +
|
|
422
637
|
`If no operation word is found, default to addition. ` +
|
|
423
|
-
`Compute the result and output ONLY the number with exactly 2 decimal places (e.g. "58.00").
|
|
638
|
+
`Compute the result and output ONLY the number with exactly 2 decimal places (e.g. "58.00"). ` +
|
|
639
|
+
`No reasoning, no other text — JUST the number.\n\n` +
|
|
424
640
|
`Puzzle: ${challengeText}`;
|
|
425
641
|
for (const cmd of ["claude", "codex"]) {
|
|
426
642
|
let bin;
|
|
@@ -443,13 +659,44 @@ export async function solvePuzzleWithLLM(challengeText) {
|
|
|
443
659
|
p.on("exit", (code) => (code === 0 ? resolve(out.trim()) : reject(new Error(`exit ${code}`))));
|
|
444
660
|
p.on("error", reject);
|
|
445
661
|
});
|
|
446
|
-
const
|
|
447
|
-
if (
|
|
448
|
-
const intMatch = result.match(/^(\d+)$/m);
|
|
449
|
-
if (intMatch) return `${intMatch[1]}.00`;
|
|
662
|
+
const extracted = extractPuzzleAnswerFromText(result);
|
|
663
|
+
if (extracted) return extracted;
|
|
450
664
|
} catch {
|
|
451
665
|
// try next
|
|
452
666
|
}
|
|
453
667
|
}
|
|
454
668
|
return null;
|
|
455
669
|
}
|
|
670
|
+
|
|
671
|
+
// ---------- Verification history (for offline analysis / retraining) ----------
|
|
672
|
+
//
|
|
673
|
+
// Every verify attempt — whether it succeeded, was rejected by the server,
|
|
674
|
+
// or couldn't be solved at all — gets a single JSONL line appended to
|
|
675
|
+
// `~/.viveworker/moltbook-verify-history.jsonl`. The file is the only way to
|
|
676
|
+
// reconstruct a failing `challenge_text` after the fact, because the puzzle
|
|
677
|
+
// is ephemeral on the Moltbook side (returned once in the POST /posts
|
|
678
|
+
// response and never exposed again).
|
|
679
|
+
//
|
|
680
|
+
// Having the corpus lets us:
|
|
681
|
+
// 1. Diagnose concrete failures with `scripts/test-puzzle-solver.mjs`.
|
|
682
|
+
// 2. Add new regression cases to the test harness when a new template
|
|
683
|
+
// starts appearing.
|
|
684
|
+
// 3. Measure solver vs LLM hit rate over time.
|
|
685
|
+
export const DEFAULT_VERIFY_HISTORY_FILE = path.join(
|
|
686
|
+
os.homedir(), ".viveworker", "moltbook-verify-history.jsonl"
|
|
687
|
+
);
|
|
688
|
+
|
|
689
|
+
export async function recordPuzzleAttempt(entry, file = DEFAULT_VERIFY_HISTORY_FILE) {
|
|
690
|
+
try {
|
|
691
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
692
|
+
const record = {
|
|
693
|
+
ts: new Date().toISOString(),
|
|
694
|
+
...entry,
|
|
695
|
+
};
|
|
696
|
+
await fs.appendFile(file, JSON.stringify(record) + "\n", "utf8");
|
|
697
|
+
} catch (err) {
|
|
698
|
+
// Best-effort logging — never fail a post just because the history file
|
|
699
|
+
// is unwritable.
|
|
700
|
+
try { console.error(`[moltbook-verify-history] Failed to record: ${err.message}`); } catch { /* ignore */ }
|
|
701
|
+
}
|
|
702
|
+
}
|
package/scripts/share-cli.mjs
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* .html .htm .pdf .png .jpg .jpeg .gif .webp .csv
|
|
8
8
|
*
|
|
9
9
|
* Commands:
|
|
10
|
-
* viveworker share upload <file> [--password <pw>] [--expires-days <n>] [--json]
|
|
10
|
+
* viveworker share upload <file> [--password <pw>] [--expires-days <n>] [--no-optimize] [--json]
|
|
11
11
|
* viveworker share list [--json]
|
|
12
12
|
* viveworker share update <slug> [--password <pw>] [--no-password] [--expires-days <n>] [--json]
|
|
13
13
|
* viveworker share link <slug> --password <pw> [--ttl-hours <n>] [--json]
|
|
@@ -71,7 +71,7 @@ export async function runShareCli(args) {
|
|
|
71
71
|
|
|
72
72
|
function printHelp() {
|
|
73
73
|
console.log("Commands:");
|
|
74
|
-
console.log(" viveworker share upload <file> [--password <pw>] [--expires-days <n>] [--json]");
|
|
74
|
+
console.log(" viveworker share upload <file> [--password <pw>] [--expires-days <n>] [--no-optimize] [--json]");
|
|
75
75
|
console.log(" viveworker share list [--json]");
|
|
76
76
|
console.log(" viveworker share update <slug> [--password <pw>] [--no-password] [--expires-days <n>] [--json]");
|
|
77
77
|
console.log(" viveworker share link <slug> --password <pw> [--ttl-hours <n>] [--json]");
|
|
@@ -79,6 +79,7 @@ function printHelp() {
|
|
|
79
79
|
console.log("");
|
|
80
80
|
console.log(`Accepted file types: ${ALLOWED_EXTENSIONS.join(" / ")}`);
|
|
81
81
|
console.log("CSV files are rendered as an HTML table on view; append ?raw=1 for bytes.");
|
|
82
|
+
console.log("HTML uploads are optimized by default when possible (use --no-optimize to disable).");
|
|
82
83
|
console.log("");
|
|
83
84
|
console.log("Credentials are read from ~/.viveworker/a2a.env (same as `viveworker a2a`).");
|
|
84
85
|
}
|
|
@@ -107,9 +108,6 @@ async function handleUpload(args) {
|
|
|
107
108
|
if (stat.size === 0) {
|
|
108
109
|
throw new Error(`File is empty: ${filePath}`);
|
|
109
110
|
}
|
|
110
|
-
if (stat.size > MAX_FILE_SIZE) {
|
|
111
|
-
throw new Error(`File too large (${stat.size} bytes, max ${MAX_FILE_SIZE})`);
|
|
112
|
-
}
|
|
113
111
|
const ext = path.extname(absolute).toLowerCase();
|
|
114
112
|
if (!ALLOWED_EXTENSIONS.includes(ext)) {
|
|
115
113
|
throw new Error(
|
|
@@ -134,7 +132,18 @@ async function handleUpload(args) {
|
|
|
134
132
|
|
|
135
133
|
const { apiKey, userId, shareUrl } = await resolveCredentials();
|
|
136
134
|
|
|
137
|
-
const
|
|
135
|
+
const originalBytes = await fs.readFile(absolute);
|
|
136
|
+
const optimization =
|
|
137
|
+
flags["no-optimize"] || flags["noOptimize"]
|
|
138
|
+
? { bytes: originalBytes, info: null }
|
|
139
|
+
: maybeOptimizeUpload({ absolute, ext, bytes: originalBytes });
|
|
140
|
+
const bytes = optimization.bytes;
|
|
141
|
+
if (bytes.length > MAX_FILE_SIZE) {
|
|
142
|
+
const detail = optimization.info
|
|
143
|
+
? ` after optimization to ${formatSize(bytes.length)}`
|
|
144
|
+
: "";
|
|
145
|
+
throw new Error(`File too large (${originalBytes.length} bytes, max ${MAX_FILE_SIZE})${detail}`);
|
|
146
|
+
}
|
|
138
147
|
const form = new FormData();
|
|
139
148
|
const blob = new Blob([bytes], { type: mime });
|
|
140
149
|
const file = new File([blob], path.basename(absolute), { type: mime });
|
|
@@ -164,6 +173,15 @@ async function handleUpload(args) {
|
|
|
164
173
|
console.log("");
|
|
165
174
|
console.log(`✅ Uploaded ${body.originalName || path.basename(absolute)} (${formatSize(body.size)})`);
|
|
166
175
|
console.log("");
|
|
176
|
+
if (optimization.info) {
|
|
177
|
+
console.log(
|
|
178
|
+
` Optimized HTML: ${formatSize(optimization.info.originalBytes)} → ${formatSize(optimization.info.optimizedBytes)}`
|
|
179
|
+
);
|
|
180
|
+
console.log(
|
|
181
|
+
` Removed embedded fonts: ${optimization.info.removedFonts}`
|
|
182
|
+
);
|
|
183
|
+
console.log("");
|
|
184
|
+
}
|
|
167
185
|
console.log(` ${body.url}`);
|
|
168
186
|
console.log("");
|
|
169
187
|
if (body.hasPassword) console.log(` 🔒 Password-protected`);
|
|
@@ -179,6 +197,97 @@ async function handleUpload(args) {
|
|
|
179
197
|
console.log("");
|
|
180
198
|
}
|
|
181
199
|
|
|
200
|
+
function maybeOptimizeUpload({ absolute, ext, bytes }) {
|
|
201
|
+
if (ext !== ".html" && ext !== ".htm") {
|
|
202
|
+
return { bytes, info: null };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const source = bytes.toString("utf8");
|
|
206
|
+
const optimized = optimizeBundledStandaloneHtml(source);
|
|
207
|
+
if (!optimized) {
|
|
208
|
+
return { bytes, info: null };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const optimizedBytes = Buffer.from(optimized.html, "utf8");
|
|
212
|
+
if (optimizedBytes.length >= bytes.length) {
|
|
213
|
+
return { bytes, info: null };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
bytes: optimizedBytes,
|
|
218
|
+
info: {
|
|
219
|
+
kind: "bundled-standalone-html",
|
|
220
|
+
removedFonts: optimized.removedFonts,
|
|
221
|
+
originalBytes: bytes.length,
|
|
222
|
+
optimizedBytes: optimizedBytes.length,
|
|
223
|
+
file: absolute,
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function optimizeBundledStandaloneHtml(source) {
|
|
229
|
+
const manifestTagPattern = /(<script\b[^>]*type="__bundler\/manifest"[^>]*>)([\s\S]*?)(<\/script>)/i;
|
|
230
|
+
const templateTagPattern = /(<script\b[^>]*type="__bundler\/template"[^>]*>)([\s\S]*?)(<\/script>)/i;
|
|
231
|
+
const manifestMatch = source.match(manifestTagPattern);
|
|
232
|
+
const templateMatch = source.match(templateTagPattern);
|
|
233
|
+
if (!manifestMatch || !templateMatch) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
let manifest;
|
|
238
|
+
let template;
|
|
239
|
+
try {
|
|
240
|
+
manifest = JSON.parse(manifestMatch[2]);
|
|
241
|
+
template = JSON.parse(templateMatch[2]);
|
|
242
|
+
} catch {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const fontUuids = Object.entries(manifest)
|
|
247
|
+
.filter(([, entry]) => entry?.mime === "font/woff2")
|
|
248
|
+
.map(([uuid]) => uuid);
|
|
249
|
+
if (fontUuids.length === 0) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
for (const uuid of fontUuids) {
|
|
254
|
+
delete manifest[uuid];
|
|
255
|
+
const escapedUuid = escapeRegExp(uuid);
|
|
256
|
+
template = template.replace(
|
|
257
|
+
new RegExp(`@font-face\\s*\\{[^{}]*${escapedUuid}[^{}]*\\}`, "g"),
|
|
258
|
+
"",
|
|
259
|
+
);
|
|
260
|
+
template = template.split(uuid).join("");
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
let html = source.replace(
|
|
264
|
+
manifestTagPattern,
|
|
265
|
+
(_, openTag, _json, closeTag) => `${openTag}${stringifyForHtmlScriptTag(manifest)}${closeTag}`,
|
|
266
|
+
);
|
|
267
|
+
html = html.replace(
|
|
268
|
+
templateTagPattern,
|
|
269
|
+
(_, openTag, _json, closeTag) => `${openTag}${stringifyForHtmlScriptTag(template)}${closeTag}`,
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
html,
|
|
274
|
+
removedFonts: fontUuids.length,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function escapeRegExp(value) {
|
|
279
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function stringifyForHtmlScriptTag(value) {
|
|
283
|
+
return JSON.stringify(value)
|
|
284
|
+
.replace(/</g, "\\u003C")
|
|
285
|
+
.replace(/>/g, "\\u003E")
|
|
286
|
+
.replace(/&/g, "\\u0026")
|
|
287
|
+
.replace(/\u2028/g, "\\u2028")
|
|
288
|
+
.replace(/\u2029/g, "\\u2029");
|
|
289
|
+
}
|
|
290
|
+
|
|
182
291
|
// ---------------------------------------------------------------------------
|
|
183
292
|
// list
|
|
184
293
|
// ---------------------------------------------------------------------------
|
|
@@ -18,7 +18,7 @@ import { generatePairingCredentials, shouldRotatePairing, upsertEnvText } from "
|
|
|
18
18
|
import { renderMarkdownHtml } from "./lib/markdown-render.mjs";
|
|
19
19
|
import { buildAgentCard, handleA2ARequest, resolveA2ATaskDecision, completeA2ATask, failA2ATask } from "./a2a-handler.mjs";
|
|
20
20
|
import { registerWithRelay, startRelayPolling, stopRelayPolling, postRelayResult, getRelayStatus, updatePublicTasksFlag } from "./a2a-relay-client.mjs";
|
|
21
|
-
import { createMoltbookClient, readScoutState, writeScoutState, rollScoutDayIfNeeded, markPostSeen, recordComposeAttempt, writeDraft, readDraft, deleteDraft, listPendingDrafts, solveVerificationPuzzle, solvePuzzleWithLLM, listInboxItems } from "./moltbook-api.mjs";
|
|
21
|
+
import { createMoltbookClient, readScoutState, writeScoutState, rollScoutDayIfNeeded, markPostSeen, recordComposeAttempt, writeDraft, readDraft, deleteDraft, listPendingDrafts, solveVerificationPuzzle, solvePuzzleWithLLM, recordPuzzleAttempt, listInboxItems } from "./moltbook-api.mjs";
|
|
22
22
|
|
|
23
23
|
const __filename = fileURLToPath(import.meta.url);
|
|
24
24
|
const __dirname = path.dirname(__filename);
|
|
@@ -15038,6 +15038,12 @@ async function executeMoltbookDraftPost(draft, config, runtime, state) {
|
|
|
15038
15038
|
const finalTitle = draft.decision.title || draft.postTitle;
|
|
15039
15039
|
|
|
15040
15040
|
let verification;
|
|
15041
|
+
// Captured so the post-verify history record knows which post/comment the
|
|
15042
|
+
// challenge_text was attached to. `draft.postId` only exists for replies;
|
|
15043
|
+
// for `original_post` drafts the id only shows up in the POST /posts
|
|
15044
|
+
// response.
|
|
15045
|
+
let createdPostId = null;
|
|
15046
|
+
let createdCommentId = null;
|
|
15041
15047
|
|
|
15042
15048
|
if (draft.draftType === "original_post") {
|
|
15043
15049
|
const result = await mb("/posts", {
|
|
@@ -15051,6 +15057,7 @@ async function executeMoltbookDraftPost(draft, config, runtime, state) {
|
|
|
15051
15057
|
});
|
|
15052
15058
|
const post = result?.post || null;
|
|
15053
15059
|
verification = post?.verification || null;
|
|
15060
|
+
createdPostId = post?.id || null;
|
|
15054
15061
|
console.log(`[moltbook-draft-post] Posted original post (id=${post?.id})`);
|
|
15055
15062
|
|
|
15056
15063
|
const scoutState = rollScoutDayIfNeeded(await readScoutState());
|
|
@@ -15066,6 +15073,8 @@ async function executeMoltbookDraftPost(draft, config, runtime, state) {
|
|
|
15066
15073
|
});
|
|
15067
15074
|
const comment = result?.comment || null;
|
|
15068
15075
|
verification = comment?.verification || null;
|
|
15076
|
+
createdPostId = draft.postId || null;
|
|
15077
|
+
createdCommentId = comment?.id || null;
|
|
15069
15078
|
console.log(`[moltbook-draft-post] Posted reply (commentId=${comment?.id}) to post ${draft.postId}`);
|
|
15070
15079
|
|
|
15071
15080
|
const scoutState = rollScoutDayIfNeeded(await readScoutState());
|
|
@@ -15080,41 +15089,79 @@ async function executeMoltbookDraftPost(draft, config, runtime, state) {
|
|
|
15080
15089
|
}
|
|
15081
15090
|
|
|
15082
15091
|
// Solve verification puzzle if present.
|
|
15092
|
+
//
|
|
15093
|
+
// Every attempt — success, server rejection, or complete failure — gets
|
|
15094
|
+
// recorded via `recordPuzzleAttempt` so we can build a corpus of
|
|
15095
|
+
// challenge_text at `~/.viveworker/moltbook-verify-history.jsonl`. This is
|
|
15096
|
+
// the only way to reconstruct failing puzzles; Moltbook only returns
|
|
15097
|
+
// `challenge_text` in the initial POST response.
|
|
15083
15098
|
if (verification) {
|
|
15084
|
-
|
|
15085
|
-
|
|
15086
|
-
|
|
15087
|
-
|
|
15099
|
+
const solverAnswer = solveVerificationPuzzle(verification.challenge_text);
|
|
15100
|
+
let submittedAnswer = solverAnswer;
|
|
15101
|
+
let llmAnswer = null;
|
|
15102
|
+
let outcome = "unknown";
|
|
15103
|
+
let verifyError = null;
|
|
15104
|
+
|
|
15105
|
+
if (submittedAnswer == null) {
|
|
15106
|
+
llmAnswer = await solvePuzzleWithLLM(verification.challenge_text);
|
|
15107
|
+
submittedAnswer = llmAnswer;
|
|
15088
15108
|
}
|
|
15089
|
-
|
|
15109
|
+
|
|
15110
|
+
if (submittedAnswer != null) {
|
|
15090
15111
|
try {
|
|
15091
15112
|
await mb("/verify", {
|
|
15092
15113
|
method: "POST",
|
|
15093
|
-
body: JSON.stringify({ verification_code: verification.verification_code, answer }),
|
|
15114
|
+
body: JSON.stringify({ verification_code: verification.verification_code, answer: submittedAnswer }),
|
|
15094
15115
|
});
|
|
15095
|
-
console.log(`[moltbook-draft-verify] Verified with answer ${
|
|
15096
|
-
|
|
15116
|
+
console.log(`[moltbook-draft-verify] Verified with answer ${submittedAnswer}`);
|
|
15117
|
+
outcome = solverAnswer != null && llmAnswer == null ? "solver-verified" : "llm-verified";
|
|
15118
|
+
} catch (err) {
|
|
15119
|
+
verifyError = err.message;
|
|
15097
15120
|
// Wrong answer from solver — retry with LLM.
|
|
15098
|
-
if (/incorrect/i.test(
|
|
15099
|
-
|
|
15100
|
-
if (llmAnswer && llmAnswer !==
|
|
15121
|
+
if (/incorrect/i.test(err.message) && solverAnswer != null && llmAnswer == null) {
|
|
15122
|
+
llmAnswer = await solvePuzzleWithLLM(verification.challenge_text);
|
|
15123
|
+
if (llmAnswer && llmAnswer !== solverAnswer) {
|
|
15101
15124
|
try {
|
|
15102
15125
|
await mb("/verify", {
|
|
15103
15126
|
method: "POST",
|
|
15104
15127
|
body: JSON.stringify({ verification_code: verification.verification_code, answer: llmAnswer }),
|
|
15105
15128
|
});
|
|
15106
15129
|
console.log(`[moltbook-draft-verify] Verified with LLM retry answer ${llmAnswer}`);
|
|
15130
|
+
submittedAnswer = llmAnswer;
|
|
15131
|
+
verifyError = null;
|
|
15132
|
+
outcome = "solver-incorrect-llm-verified";
|
|
15107
15133
|
} catch (retryError) {
|
|
15108
15134
|
console.error(`[moltbook-draft-verify] LLM retry failed: ${retryError.message}`);
|
|
15135
|
+
verifyError = retryError.message;
|
|
15136
|
+
outcome = "both-incorrect";
|
|
15109
15137
|
}
|
|
15138
|
+
} else {
|
|
15139
|
+
outcome = "solver-incorrect-llm-agreed-or-null";
|
|
15110
15140
|
}
|
|
15111
15141
|
} else {
|
|
15112
|
-
console.error(`[moltbook-draft-verify] Failed: ${
|
|
15142
|
+
console.error(`[moltbook-draft-verify] Failed: ${err.message}`);
|
|
15143
|
+
outcome = solverAnswer != null ? "solver-rejected" : "llm-rejected";
|
|
15113
15144
|
}
|
|
15114
15145
|
}
|
|
15115
15146
|
} else {
|
|
15116
15147
|
console.error(`[moltbook-draft-verify] Could not solve puzzle for draft ${draft.token}`);
|
|
15117
|
-
|
|
15148
|
+
outcome = "both-failed-to-solve";
|
|
15149
|
+
}
|
|
15150
|
+
|
|
15151
|
+
// Best-effort corpus write. Never throws.
|
|
15152
|
+
await recordPuzzleAttempt({
|
|
15153
|
+
draftToken: draft.token,
|
|
15154
|
+
draftType: draft.draftType,
|
|
15155
|
+
postId: createdPostId,
|
|
15156
|
+
commentId: createdCommentId,
|
|
15157
|
+
challenge_text: verification.challenge_text,
|
|
15158
|
+
verification_code: verification.verification_code,
|
|
15159
|
+
solverAnswer,
|
|
15160
|
+
llmAnswer,
|
|
15161
|
+
submittedAnswer,
|
|
15162
|
+
outcome,
|
|
15163
|
+
verifyError,
|
|
15164
|
+
});
|
|
15118
15165
|
}
|
|
15119
15166
|
|
|
15120
15167
|
// Push notification for successful post.
|
package/scripts/viveworker.mjs
CHANGED
|
@@ -64,6 +64,17 @@ if (rawArgs[0] === "share") {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
if (rawArgs[0] === "stats") {
|
|
68
|
+
const { runStatsCli } = await import("./stats-cli.mjs");
|
|
69
|
+
try {
|
|
70
|
+
await runStatsCli(rawArgs.slice(1));
|
|
71
|
+
process.exit(0);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error(error.message || String(error));
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
67
78
|
const cli = parseArgs(rawArgs);
|
|
68
79
|
|
|
69
80
|
try {
|