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.
- package/.agents/plugins/marketplace.json +20 -0
- package/README.md +115 -4
- package/package.json +13 -3
- package/plugins/viveworker-control-plane/.codex-plugin/plugin.json +49 -0
- package/plugins/viveworker-control-plane/.mcp.json +11 -0
- package/plugins/viveworker-control-plane/assets/viveworker-logo-v2.png +0 -0
- package/plugins/viveworker-control-plane/assets/viveworker-logo-v2.svg +39 -0
- package/plugins/viveworker-control-plane/skills/viveworker-control-plane/SKILL.md +83 -0
- package/scripts/lib/markdown-render.mjs +128 -1
- package/scripts/lib/remote-pairing/README.md +164 -0
- package/scripts/lib/remote-pairing/_browser-bundle-entry.mjs +86 -0
- package/scripts/lib/remote-pairing/audit.mjs +122 -0
- package/scripts/lib/remote-pairing/bridge-relay-client.mjs +737 -0
- package/scripts/lib/remote-pairing/control.mjs +156 -0
- package/scripts/lib/remote-pairing/envelope.mjs +224 -0
- package/scripts/lib/remote-pairing/http-dispatch.mjs +530 -0
- package/scripts/lib/remote-pairing/keys-core.mjs +110 -0
- package/scripts/lib/remote-pairing/keys.mjs +181 -0
- package/scripts/lib/remote-pairing/noise.mjs +436 -0
- package/scripts/lib/remote-pairing/orchestrator.mjs +356 -0
- package/scripts/lib/remote-pairing/pairings.mjs +446 -0
- package/scripts/lib/remote-pairing/rpc.mjs +381 -0
- package/scripts/mcp-server.mjs +891 -0
- package/scripts/moltbook-scout-auto.sh +16 -8
- package/scripts/share-cli.mjs +14 -130
- package/scripts/stats-cli.mjs +683 -0
- package/scripts/viveworker-bridge.mjs +1676 -127
- package/scripts/viveworker.mjs +289 -7
- package/skills/viveworker-control-plane/SKILL.md +104 -0
- package/skills/viveworker-control-plane/agents/openai.yaml +12 -0
- package/templates/CLAUDE.viveworker.md +67 -0
- package/web/app.css +683 -9
- package/web/app.js +1780 -187
- package/web/build-id.js +1 -0
- package/web/i18n.js +187 -9
- package/web/icons/apple-touch-icon.png +0 -0
- package/web/icons/viveworker-icon-192.png +0 -0
- package/web/icons/viveworker-icon-512.png +0 -0
- package/web/icons/viveworker-v-pulse.svg +16 -1
- package/web/index.html +32 -2
- package/web/remote-pairing/api-router.js +873 -0
- package/web/remote-pairing/keys.js +237 -0
- package/web/remote-pairing/pairing-state.js +313 -0
- package/web/remote-pairing/rpc-client.js +765 -0
- package/web/remote-pairing/transport.js +804 -0
- package/web/remote-pairing/wake.js +149 -0
- package/web/remote-pairing-test.html +400 -0
- package/web/remote-pairing.bundle.js +3 -0
- package/web/remote-pairing.bundle.js.LEGAL.txt +15 -0
- package/web/remote-pairing.bundle.js.map +7 -0
- package/web/sw.js +190 -20
- package/web/icons/viveworker-beacon-v.svg +0 -19
- package/web/icons/viveworker-icon-1024.png +0 -0
- 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
|
-
|
|
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('
|
|
254
|
-
const
|
|
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 "$
|
|
346
|
+
--timeout "$PROPOSE_TIMEOUT_SEC"
|
|
339
347
|
|
|
340
348
|
exit 0
|
|
341
349
|
fi
|
package/scripts/share-cli.mjs
CHANGED
|
@@ -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 =
|
|
776
|
-
const requirement =
|
|
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 =
|
|
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
|
|
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 =
|
|
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) {
|