viveworker 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/README.md +115 -4
  3. package/package.json +13 -3
  4. package/plugins/viveworker-control-plane/.codex-plugin/plugin.json +49 -0
  5. package/plugins/viveworker-control-plane/.mcp.json +11 -0
  6. package/plugins/viveworker-control-plane/assets/viveworker-logo-v2.png +0 -0
  7. package/plugins/viveworker-control-plane/assets/viveworker-logo-v2.svg +39 -0
  8. package/plugins/viveworker-control-plane/skills/viveworker-control-plane/SKILL.md +83 -0
  9. package/scripts/lib/markdown-render.mjs +128 -1
  10. package/scripts/lib/remote-pairing/README.md +164 -0
  11. package/scripts/lib/remote-pairing/_browser-bundle-entry.mjs +86 -0
  12. package/scripts/lib/remote-pairing/audit.mjs +122 -0
  13. package/scripts/lib/remote-pairing/bridge-relay-client.mjs +737 -0
  14. package/scripts/lib/remote-pairing/control.mjs +156 -0
  15. package/scripts/lib/remote-pairing/envelope.mjs +224 -0
  16. package/scripts/lib/remote-pairing/http-dispatch.mjs +530 -0
  17. package/scripts/lib/remote-pairing/keys-core.mjs +110 -0
  18. package/scripts/lib/remote-pairing/keys.mjs +181 -0
  19. package/scripts/lib/remote-pairing/noise.mjs +436 -0
  20. package/scripts/lib/remote-pairing/orchestrator.mjs +356 -0
  21. package/scripts/lib/remote-pairing/pairings.mjs +446 -0
  22. package/scripts/lib/remote-pairing/rpc.mjs +381 -0
  23. package/scripts/mcp-server.mjs +891 -0
  24. package/scripts/moltbook-scout-auto.sh +16 -8
  25. package/scripts/share-cli.mjs +14 -130
  26. package/scripts/stats-cli.mjs +683 -0
  27. package/scripts/viveworker-bridge.mjs +1676 -127
  28. package/scripts/viveworker.mjs +289 -7
  29. package/skills/viveworker-control-plane/SKILL.md +104 -0
  30. package/skills/viveworker-control-plane/agents/openai.yaml +12 -0
  31. package/templates/CLAUDE.viveworker.md +67 -0
  32. package/web/app.css +683 -9
  33. package/web/app.js +1780 -187
  34. package/web/build-id.js +1 -0
  35. package/web/i18n.js +187 -9
  36. package/web/icons/apple-touch-icon.png +0 -0
  37. package/web/icons/viveworker-icon-192.png +0 -0
  38. package/web/icons/viveworker-icon-512.png +0 -0
  39. package/web/icons/viveworker-v-pulse.svg +16 -1
  40. package/web/index.html +32 -2
  41. package/web/remote-pairing/api-router.js +873 -0
  42. package/web/remote-pairing/keys.js +237 -0
  43. package/web/remote-pairing/pairing-state.js +313 -0
  44. package/web/remote-pairing/rpc-client.js +765 -0
  45. package/web/remote-pairing/transport.js +804 -0
  46. package/web/remote-pairing/wake.js +149 -0
  47. package/web/remote-pairing-test.html +400 -0
  48. package/web/remote-pairing.bundle.js +3 -0
  49. package/web/remote-pairing.bundle.js.LEGAL.txt +15 -0
  50. package/web/remote-pairing.bundle.js.map +7 -0
  51. package/web/sw.js +190 -20
  52. package/web/icons/viveworker-beacon-v.svg +0 -19
  53. package/web/icons/viveworker-icon-1024.png +0 -0
  54. package/web/icons/viveworker-v-check.svg +0 -19
@@ -11,6 +11,7 @@
11
11
  # SCOUT_HARNESS — "claude" or "codex" (default: auto-detect)
12
12
  # SCOUT_FLAGS — extra flags for scout (e.g. "--submolts builds,general --max-daily 5")
13
13
  # SCOUT_TIMEOUT — propose timeout in seconds (default: 900)
14
+ # SCOUT_DRAFT_TIMEOUT — LLM draft timeout in seconds (default: 120)
14
15
  # SCOUT_WINDOW — batch window in seconds (default: 1800 = 30 min)
15
16
  # COMPOSE_MAX — max original posts per day (default: 1)
16
17
  set -euo pipefail
@@ -26,6 +27,8 @@ NODE="$(command -v node)"
26
27
  VIVEWORKER="$SCRIPT_DIR/viveworker.mjs"
27
28
  PERSONA_FILE="$HOME/.viveworker/moltbook-persona.md"
28
29
  WINDOW_SEC="${SCOUT_WINDOW:-1800}"
30
+ PROPOSE_TIMEOUT_SEC="${SCOUT_TIMEOUT:-900}"
31
+ DRAFT_TIMEOUT_SEC="${SCOUT_DRAFT_TIMEOUT:-120}"
29
32
 
30
33
  # Seconds until a given hour (local time). Used for compose slot timeouts.
31
34
  seconds_until_hour() {
@@ -85,7 +88,12 @@ candidate_should_skip() {
85
88
 
86
89
  # Temp file for JSON interchange
87
90
  SCOUT_TMP=$(mktemp /tmp/viveworker-scout-XXXXXX.json)
88
- trap 'rm -f "$SCOUT_TMP"' EXIT
91
+ LOCK_DIR="${TMPDIR:-/tmp}/viveworker-moltbook-scout.lock"
92
+ if ! mkdir "$LOCK_DIR" 2>/dev/null; then
93
+ echo "[scout-auto] another scout run is already active — skipping this invocation"
94
+ exit 0
95
+ fi
96
+ trap 'rm -f "$SCOUT_TMP"; rmdir "$LOCK_DIR" 2>/dev/null || true' EXIT
89
97
 
90
98
  read_field() {
91
99
  "$NODE" -e "
@@ -243,15 +251,15 @@ if [ "$PICK_STATUS" = "picked" ]; then
243
251
  echo "[scout-auto] batch complete: picked '$BEST_TITLE' by @$BEST_AUTHOR (score=$BEST_SCORE, considered=$CONSIDERED)"
244
252
 
245
253
  # Fetch post content from Moltbook API
246
- POST_DATA=$("$NODE" --input-type=module -e "
254
+ POST_DATA=$(MOLTBOOK_API_MODULE="$SCRIPT_DIR/moltbook-api.mjs" "$NODE" --input-type=module -e "
247
255
  import { readFileSync } from 'fs';
248
256
  const env = readFileSync(process.env.HOME + '/.viveworker/moltbook.env', 'utf8');
249
257
  for(const line of env.split('\n')){
250
258
  const m = line.match(/^(\w+)=(.*)\$/);
251
259
  if(m) process.env[m[1]] = m[2];
252
260
  }
253
- const { createMoltbookClient } = await import('./scripts/moltbook-api.mjs');
254
- const { client: mb } = await createMoltbookClient();
261
+ const { createMoltbookClient } = await import('file://' + process.env.MOLTBOOK_API_MODULE);
262
+ const mb = createMoltbookClient(process.env.MOLTBOOK_API_KEY);
255
263
  try {
256
264
  const post = await mb('/posts/${BEST_POST_ID}');
257
265
  const p = post?.post || post;
@@ -297,13 +305,13 @@ PROMPT_EOF
297
305
  echo "[scout-auto] drafting via $HARNESS for '$BEST_TITLE'"
298
306
  DRAFT_TEXT=""
299
307
  if [ "$HARNESS" = "claude" ]; then
300
- DRAFT_TEXT=$("$HARNESS_BIN" -p "$DRAFT_PROMPT" --output-format text) || true
308
+ DRAFT_TEXT=$(portable_timeout "$DRAFT_TIMEOUT_SEC" "$HARNESS_BIN" -p "$DRAFT_PROMPT" --output-format text 2>/dev/null) || true
301
309
  elif [ "$HARNESS" = "codex" ]; then
302
- DRAFT_TEXT=$("$HARNESS_BIN" exec "$DRAFT_PROMPT") || true
310
+ DRAFT_TEXT=$(portable_timeout "$DRAFT_TIMEOUT_SEC" "$HARNESS_BIN" exec "$DRAFT_PROMPT" 2>/dev/null) || true
303
311
  fi
304
312
 
305
313
  if [ -z "$DRAFT_TEXT" ]; then
306
- echo "[scout-auto] error: harness returned empty draft"
314
+ echo "[scout-auto] error: harness returned empty draft or timed out after ${DRAFT_TIMEOUT_SEC}s"
307
315
  exit 1
308
316
  fi
309
317
 
@@ -335,7 +343,7 @@ PROMPT_EOF
335
343
  --post-body "$POST_CONTENT" \
336
344
  --intent "$INTENT" \
337
345
  --text "$REPLY_BODY" \
338
- --timeout "$WINDOW_SEC"
346
+ --timeout "$PROPOSE_TIMEOUT_SEC"
339
347
 
340
348
  exit 0
341
349
  fi
@@ -34,27 +34,21 @@ import https from "node:https";
34
34
  import os from "node:os";
35
35
  import path from "node:path";
36
36
  import { Blob, File } from "node:buffer";
37
+ import {
38
+ buildX402PaymentHeader,
39
+ decodeXPaymentResponseHeader,
40
+ getX402PaymentRequestId,
41
+ parseX402ResponseBody,
42
+ selectX402PaymentRequirement,
43
+ SUPPORTED_X402_BUYER_NETWORKS,
44
+ } from "@hazbase/auth";
37
45
 
38
46
  const A2A_ENV_FILE = path.join(os.homedir(), ".viveworker", "a2a.env");
39
47
  const CONFIG_ENV_FILE = path.join(os.homedir(), ".viveworker", "config.env");
40
48
  const DEFAULT_SHARE_URL = "https://share.viveworker.com";
41
49
  const MAX_FILE_SIZE = 5 * 1024 * 1024; // mirror worker
42
- const X402_VERSION = 1;
43
50
  const PAYMENT_APPROVAL_TIMEOUT_MS = 10 * 60 * 1000;
44
- const SUPPORTED_BUYER_NETWORKS = {
45
- base: {
46
- chainId: 8453,
47
- label: "Base",
48
- usdcName: "USD Coin",
49
- usdcVersion: "2",
50
- },
51
- "base-sepolia": {
52
- chainId: 84532,
53
- label: "Base Sepolia",
54
- usdcName: "USDC",
55
- usdcVersion: "2",
56
- },
57
- };
51
+ const SUPPORTED_BUYER_NETWORKS = SUPPORTED_X402_BUYER_NETWORKS;
58
52
 
59
53
  // Mirror share-worker/worker.js SHARE_TYPES. Keep in sync by inspection —
60
54
  // scripts/ and share-worker/ don't share a module. Adding a new type here
@@ -772,8 +766,8 @@ async function handlePay(args) {
772
766
  throw new Error(`Expected HTTP 402 payment requirements, got ${initial.status}`);
773
767
  }
774
768
 
775
- const x402 = parseX402Body(initialText);
776
- const requirement = selectPaymentRequirement(x402);
769
+ const x402 = parseX402ResponseBody(initialText);
770
+ const requirement = selectX402PaymentRequirement(x402);
777
771
  const paymentSummary = summarizeRequirement(requirement);
778
772
  if (flags["dry-run"] || flags.dryRun) {
779
773
  const dryRun = {
@@ -802,7 +796,7 @@ async function handlePay(args) {
802
796
  },
803
797
  }, 60_000);
804
798
  const paidBytes = Buffer.from(await paid.arrayBuffer());
805
- const responsePreview = decodePaymentResponseHeader(paid.headers.get("x-payment-response"));
799
+ const responsePreview = decodeXPaymentResponseHeader(paid.headers.get("x-payment-response"));
806
800
 
807
801
  if (!paid.ok) {
808
802
  let body = {};
@@ -885,46 +879,6 @@ async function handleDelete(args) {
885
879
  console.log(`✅ Deleted ${slug}`);
886
880
  }
887
881
 
888
- function parseX402Body(text) {
889
- try {
890
- const parsed = JSON.parse(text);
891
- if (parsed && typeof parsed === "object") return parsed;
892
- } catch {}
893
-
894
- const match = String(text || "").match(
895
- /<script[^>]+type=["']application\/x-x402\+json["'][^>]*>([\s\S]*?)<\/script>/iu
896
- );
897
- if (match) {
898
- try {
899
- return JSON.parse(match[1]);
900
- } catch {}
901
- }
902
- throw new Error("HTTP 402 response did not contain a valid x402 JSON body");
903
- }
904
-
905
- function selectPaymentRequirement(x402) {
906
- const accepts = Array.isArray(x402?.accepts) ? x402.accepts : [];
907
- const requirement = accepts.find((item) =>
908
- item &&
909
- item.scheme === "exact" &&
910
- Object.prototype.hasOwnProperty.call(SUPPORTED_BUYER_NETWORKS, String(item.network || ""))
911
- );
912
- if (!requirement) {
913
- const networks = accepts.map((item) => item?.network).filter(Boolean).join(", ") || "none";
914
- throw new Error(`No supported x402 exact payment option found (offered: ${networks})`);
915
- }
916
- if (!ETH_ADDR_REGEX.test(String(requirement.payTo || ""))) {
917
- throw new Error("x402 payment requirement has an invalid payTo address");
918
- }
919
- if (!ETH_ADDR_REGEX.test(String(requirement.asset || ""))) {
920
- throw new Error("x402 payment requirement has an invalid asset address");
921
- }
922
- if (!/^\d+$/u.test(String(requirement.maxAmountRequired || ""))) {
923
- throw new Error("x402 payment requirement has an invalid amount");
924
- }
925
- return requirement;
926
- }
927
-
928
882
  function summarizeRequirement(requirement) {
929
883
  const network = SUPPORTED_BUYER_NETWORKS[String(requirement.network)];
930
884
  return {
@@ -960,7 +914,7 @@ function resolveBuyerWalletMode(flags) {
960
914
  async function requestEoaPayment({ url, requirement, paymentSummary, flags }) {
961
915
  await requirePaymentApproval({ url, paymentSummary, flags });
962
916
  const privateKey = resolveBuyerPrivateKey();
963
- const payment = await buildXPaymentHeader(requirement, privateKey);
917
+ const payment = await buildX402PaymentHeader({ requirement, privateKey });
964
918
  return {
965
919
  header: payment.header,
966
920
  payer: payment.payer,
@@ -970,9 +924,7 @@ async function requestEoaPayment({ url, requirement, paymentSummary, flags }) {
970
924
  }
971
925
 
972
926
  async function requestHazbaseWalletPayment({ url, x402, requirement, paymentSummary, flags }) {
973
- const paymentRequestId = cleanPaymentRequestId(
974
- x402?.paymentRequestId || x402?.hazbase?.paymentRequestId || requirement?.extra?.paymentRequestId || ""
975
- );
927
+ const paymentRequestId = getX402PaymentRequestId(x402, requirement);
976
928
  if (!paymentRequestId) {
977
929
  throw new Error("This 402 response does not expose a hazBase paymentRequestId; --wallet hazbase requires a hazBase-backed share.");
978
930
  }
@@ -1009,11 +961,6 @@ async function requestHazbaseWalletPayment({ url, x402, requirement, paymentSumm
1009
961
  };
1010
962
  }
1011
963
 
1012
- function cleanPaymentRequestId(value) {
1013
- const text = String(value || "").trim();
1014
- return /^[a-zA-Z0-9:_-]{8,160}$/u.test(text) ? text : "";
1015
- }
1016
-
1017
964
  async function requirePaymentApproval({ url, paymentSummary, flags }) {
1018
965
  const config = await resolveApprovalBridgeConfig();
1019
966
  if (shouldSkipPaymentApproval(flags, config.envText)) {
@@ -1203,69 +1150,6 @@ function resolveBuyerPrivateKey() {
1203
1150
  return raw.startsWith("0x") ? raw : `0x${raw}`;
1204
1151
  }
1205
1152
 
1206
- async function buildXPaymentHeader(requirement, privateKey) {
1207
- const ethers = await import("ethers");
1208
- const network = SUPPORTED_BUYER_NETWORKS[String(requirement.network)];
1209
- if (!network) throw new Error(`Unsupported buyer network: ${requirement.network}`);
1210
-
1211
- const wallet = new ethers.Wallet(privateKey);
1212
- const now = Math.floor(Date.now() / 1000);
1213
- const maxTimeout = Number(requirement.maxTimeoutSeconds || 60);
1214
- const validAfter = String(Math.max(0, now - 30));
1215
- const validBefore = String(now + Math.max(30, Math.min(300, Number.isFinite(maxTimeout) ? maxTimeout : 60)));
1216
- const authorization = {
1217
- from: wallet.address,
1218
- to: ethers.getAddress(String(requirement.payTo)),
1219
- value: String(requirement.maxAmountRequired),
1220
- validAfter,
1221
- validBefore,
1222
- nonce: `0x${crypto.randomBytes(32).toString("hex")}`,
1223
- };
1224
- const domain = {
1225
- name: String(requirement.extra?.name || network.usdcName),
1226
- version: String(requirement.extra?.version || network.usdcVersion),
1227
- chainId: network.chainId,
1228
- verifyingContract: ethers.getAddress(String(requirement.asset)),
1229
- };
1230
- const types = {
1231
- TransferWithAuthorization: [
1232
- { name: "from", type: "address" },
1233
- { name: "to", type: "address" },
1234
- { name: "value", type: "uint256" },
1235
- { name: "validAfter", type: "uint256" },
1236
- { name: "validBefore", type: "uint256" },
1237
- { name: "nonce", type: "bytes32" },
1238
- ],
1239
- };
1240
- const signature = await wallet.signTypedData(domain, types, authorization);
1241
- const payload = {
1242
- x402Version: X402_VERSION,
1243
- scheme: String(requirement.scheme || "exact"),
1244
- network: String(requirement.network),
1245
- payload: {
1246
- signature,
1247
- authorization,
1248
- },
1249
- };
1250
- return {
1251
- header: Buffer.from(JSON.stringify(payload)).toString("base64"),
1252
- payer: wallet.address,
1253
- payload,
1254
- };
1255
- }
1256
-
1257
- function decodePaymentResponseHeader(value) {
1258
- const raw = String(value || "").trim();
1259
- if (!raw) return null;
1260
- try {
1261
- const normalized = raw.replace(/-/g, "+").replace(/_/g, "/");
1262
- const padding = normalized.length % 4 === 0 ? "" : "=".repeat(4 - (normalized.length % 4));
1263
- return JSON.parse(Buffer.from(normalized + padding, "base64").toString("utf8"));
1264
- } catch {
1265
- return { raw };
1266
- }
1267
- }
1268
-
1269
1153
  async function writeOrPreviewPaidBody(flags, res, bytes) {
1270
1154
  const contentType = res.headers.get("content-type") || "";
1271
1155
  if (flags.output) {