viveworker 0.8.2 → 0.8.4
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 +7 -4
- package/package.json +1 -1
- package/scripts/com.viveworker.moltbook-scout.plist.sample +2 -2
- package/scripts/moltbook-api.mjs +178 -2
- package/scripts/moltbook-cli.mjs +134 -8
- package/scripts/moltbook-scout-auto.sh +100 -10
- package/scripts/moltbook-watcher.mjs +10 -0
- package/scripts/share-cli.mjs +127 -54
- package/scripts/viveworker-bridge.mjs +758 -46
- package/web/app.css +33 -0
- package/web/app.js +86 -19
- package/web/build-id.js +1 -1
- package/web/i18n.js +26 -2
- package/web/remote-pairing/api-router.js +168 -15
- package/web/remote-pairing/transport.js +27 -3
package/scripts/share-cli.mjs
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* Commands:
|
|
10
10
|
* viveworker share upload <file> [--password <pw>] [--price <usd> --pay-to <0x…>] [--expires-days <n>] [--no-optimize] [--json]
|
|
11
11
|
* viveworker share list [--json]
|
|
12
|
-
* viveworker share update <slug> [--password <pw>] [--no-password] [--price <usd>] [--no-price] [--pay-to <0x…>] [--expires-days <n>] [--json]
|
|
12
|
+
* viveworker share update <slug> [--file <file>] [--password <pw>] [--no-password] [--price <usd>] [--no-price] [--pay-to <0x…>] [--expires-days <n>] [--no-optimize] [--json]
|
|
13
|
+
* viveworker share replace <slug> <file> [--expires-days <n>] [--no-optimize] [--json]
|
|
13
14
|
* viveworker share link <slug> --password <pw> [--ttl-hours <n>] [--json]
|
|
14
15
|
* viveworker share pay <url> [--output <file>] [--dry-run] [--wallet eoa|hazbase] [--no-approval] [--json]
|
|
15
16
|
* viveworker share delete <slug>
|
|
@@ -80,6 +81,8 @@ export async function runShareCli(args) {
|
|
|
80
81
|
return handleList(args.slice(1));
|
|
81
82
|
case "update":
|
|
82
83
|
return handleUpdate(args.slice(1));
|
|
84
|
+
case "replace":
|
|
85
|
+
return handleReplace(args.slice(1));
|
|
83
86
|
case "link":
|
|
84
87
|
return handleLink(args.slice(1));
|
|
85
88
|
case "pay":
|
|
@@ -99,7 +102,8 @@ function printHelp() {
|
|
|
99
102
|
console.log("Commands:");
|
|
100
103
|
console.log(" viveworker share upload <file> [--password <pw>] [--price <usd> --pay-to <0x…>] [--expires-days <n>] [--no-optimize] [--json]");
|
|
101
104
|
console.log(" viveworker share list [--metrics] [--json]");
|
|
102
|
-
console.log(" viveworker share update <slug> [--password <pw>] [--no-password] [--price <usd>] [--no-price] [--pay-to <0x…>] [--expires-days <n>] [--json]");
|
|
105
|
+
console.log(" viveworker share update <slug> [--file <file>] [--password <pw>] [--no-password] [--price <usd>] [--no-price] [--pay-to <0x…>] [--expires-days <n>] [--no-optimize] [--json]");
|
|
106
|
+
console.log(" viveworker share replace <slug> <file> [--expires-days <n>] [--no-optimize] [--json]");
|
|
103
107
|
console.log(" viveworker share link <slug> --password <pw> [--ttl-hours <n>] [--json]");
|
|
104
108
|
console.log(" viveworker share pay <url> [--output <file>] [--dry-run] [--no-approval] [--json]");
|
|
105
109
|
console.log(" viveworker share delete <slug>");
|
|
@@ -133,28 +137,6 @@ async function handleUpload(args) {
|
|
|
133
137
|
throw new Error("Usage: viveworker share upload <file> [--password <pw>] [--expires-days <n>]");
|
|
134
138
|
}
|
|
135
139
|
|
|
136
|
-
const absolute = path.resolve(filePath);
|
|
137
|
-
let stat;
|
|
138
|
-
try {
|
|
139
|
-
stat = await fs.stat(absolute);
|
|
140
|
-
} catch {
|
|
141
|
-
throw new Error(`File not found: ${filePath}`);
|
|
142
|
-
}
|
|
143
|
-
if (!stat.isFile()) {
|
|
144
|
-
throw new Error(`Not a regular file: ${filePath}`);
|
|
145
|
-
}
|
|
146
|
-
if (stat.size === 0) {
|
|
147
|
-
throw new Error(`File is empty: ${filePath}`);
|
|
148
|
-
}
|
|
149
|
-
const ext = path.extname(absolute).toLowerCase();
|
|
150
|
-
if (!ALLOWED_EXTENSIONS.includes(ext)) {
|
|
151
|
-
throw new Error(
|
|
152
|
-
`Accepted file types: ${ALLOWED_EXTENSIONS.join(" / ")}. ` +
|
|
153
|
-
`Got: ${ext || "(no extension)"}`
|
|
154
|
-
);
|
|
155
|
-
}
|
|
156
|
-
const { mime } = SHARE_TYPES[ext];
|
|
157
|
-
|
|
158
140
|
const password = flags["password"] || "";
|
|
159
141
|
const expiresDays = flags["expires-days"] || flags["expiresDays"] || "";
|
|
160
142
|
const price = flags["price"] || "";
|
|
@@ -190,21 +172,10 @@ async function handleUpload(args) {
|
|
|
190
172
|
|
|
191
173
|
const { apiKey, userId, shareUrl } = await resolveCredentials();
|
|
192
174
|
|
|
193
|
-
const
|
|
194
|
-
const optimization =
|
|
195
|
-
flags["no-optimize"] || flags["noOptimize"]
|
|
196
|
-
? { bytes: originalBytes, info: null }
|
|
197
|
-
: maybeOptimizeUpload({ absolute, ext, bytes: originalBytes });
|
|
198
|
-
const bytes = optimization.bytes;
|
|
199
|
-
if (bytes.length > MAX_FILE_SIZE) {
|
|
200
|
-
const detail = optimization.info
|
|
201
|
-
? ` after optimization to ${formatSize(bytes.length)}`
|
|
202
|
-
: "";
|
|
203
|
-
throw new Error(`File too large (${originalBytes.length} bytes, max ${MAX_FILE_SIZE})${detail}`);
|
|
204
|
-
}
|
|
175
|
+
const preparedFile = await prepareShareFile(filePath, flags);
|
|
205
176
|
const form = new FormData();
|
|
206
|
-
const blob = new Blob([bytes], { type: mime });
|
|
207
|
-
const file = new File([blob], path.basename(absolute), { type: mime });
|
|
177
|
+
const blob = new Blob([preparedFile.bytes], { type: preparedFile.mime });
|
|
178
|
+
const file = new File([blob], path.basename(preparedFile.absolute), { type: preparedFile.mime });
|
|
208
179
|
form.set("file", file);
|
|
209
180
|
if (password) form.set("password", password);
|
|
210
181
|
if (expiresDays) form.set("expiresDays", String(expiresDays));
|
|
@@ -233,14 +204,14 @@ async function handleUpload(args) {
|
|
|
233
204
|
}
|
|
234
205
|
|
|
235
206
|
console.log("");
|
|
236
|
-
console.log(`✅ Uploaded ${body.originalName || path.basename(absolute)} (${formatSize(body.size)})`);
|
|
207
|
+
console.log(`✅ Uploaded ${body.originalName || path.basename(preparedFile.absolute)} (${formatSize(body.size)})`);
|
|
237
208
|
console.log("");
|
|
238
|
-
if (optimization.info) {
|
|
209
|
+
if (preparedFile.optimization.info) {
|
|
239
210
|
console.log(
|
|
240
|
-
` Optimized HTML: ${formatSize(optimization.info.originalBytes)} → ${formatSize(optimization.info.optimizedBytes)}`
|
|
211
|
+
` Optimized HTML: ${formatSize(preparedFile.optimization.info.originalBytes)} → ${formatSize(preparedFile.optimization.info.optimizedBytes)}`
|
|
241
212
|
);
|
|
242
213
|
console.log(
|
|
243
|
-
` Removed embedded fonts: ${optimization.info.removedFonts}`
|
|
214
|
+
` Removed embedded fonts: ${preparedFile.optimization.info.removedFonts}`
|
|
244
215
|
);
|
|
245
216
|
console.log("");
|
|
246
217
|
}
|
|
@@ -262,6 +233,44 @@ async function handleUpload(args) {
|
|
|
262
233
|
console.log("");
|
|
263
234
|
}
|
|
264
235
|
|
|
236
|
+
async function prepareShareFile(filePath, flags = {}) {
|
|
237
|
+
const absolute = path.resolve(String(filePath || ""));
|
|
238
|
+
let stat;
|
|
239
|
+
try {
|
|
240
|
+
stat = await fs.stat(absolute);
|
|
241
|
+
} catch {
|
|
242
|
+
throw new Error(`File not found: ${filePath}`);
|
|
243
|
+
}
|
|
244
|
+
if (!stat.isFile()) {
|
|
245
|
+
throw new Error(`Not a regular file: ${filePath}`);
|
|
246
|
+
}
|
|
247
|
+
if (stat.size === 0) {
|
|
248
|
+
throw new Error(`File is empty: ${filePath}`);
|
|
249
|
+
}
|
|
250
|
+
const ext = path.extname(absolute).toLowerCase();
|
|
251
|
+
if (!ALLOWED_EXTENSIONS.includes(ext)) {
|
|
252
|
+
throw new Error(
|
|
253
|
+
`Accepted file types: ${ALLOWED_EXTENSIONS.join(" / ")}. ` +
|
|
254
|
+
`Got: ${ext || "(no extension)"}`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
const { mime } = SHARE_TYPES[ext];
|
|
258
|
+
const originalBytes = await fs.readFile(absolute);
|
|
259
|
+
const optimization =
|
|
260
|
+
flags["no-optimize"] || flags["noOptimize"]
|
|
261
|
+
? { bytes: originalBytes, info: null }
|
|
262
|
+
: maybeOptimizeUpload({ absolute, ext, bytes: originalBytes });
|
|
263
|
+
const bytes = optimization.bytes;
|
|
264
|
+
if (bytes.length > MAX_FILE_SIZE) {
|
|
265
|
+
const detail = optimization.info
|
|
266
|
+
? ` after optimization to ${formatSize(bytes.length)}`
|
|
267
|
+
: "";
|
|
268
|
+
throw new Error(`File too large (${originalBytes.length} bytes, max ${MAX_FILE_SIZE})${detail}`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return { absolute, ext, mime, bytes, optimization };
|
|
272
|
+
}
|
|
273
|
+
|
|
265
274
|
function maybeOptimizeUpload({ absolute, ext, bytes }) {
|
|
266
275
|
if (ext !== ".html" && ext !== ".htm") {
|
|
267
276
|
return { bytes, info: null };
|
|
@@ -506,15 +515,34 @@ function printMetricsSection(metrics, err) {
|
|
|
506
515
|
|
|
507
516
|
async function handleUpdate(args) {
|
|
508
517
|
const flags = parseFlags(args);
|
|
518
|
+
return handleUpdateWithFlags(flags, "update");
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
async function handleReplace(args) {
|
|
522
|
+
const flags = parseFlags(args);
|
|
523
|
+
const slug = flags._[0];
|
|
524
|
+
const filePath = flags._[1] || flags.file || flags.f;
|
|
525
|
+
if (!slug || !filePath || flags._.length > 2) {
|
|
526
|
+
throw new Error("Usage: viveworker share replace <slug> <file> [--expires-days <n>] [--no-optimize] [--json]");
|
|
527
|
+
}
|
|
528
|
+
flags._ = [slug];
|
|
529
|
+
flags.file = filePath;
|
|
530
|
+
return handleUpdateWithFlags(flags, "replace");
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
async function handleUpdateWithFlags(flags, mode = "update") {
|
|
509
534
|
const slug = flags._[0];
|
|
510
535
|
if (!slug) {
|
|
511
536
|
throw new Error(
|
|
512
|
-
"Usage: viveworker share update <slug> [--password <pw>] [--no-password] [--price <usd>] [--no-price] [--pay-to <0x…>] [--expires-days <n>]"
|
|
537
|
+
"Usage: viveworker share update <slug> [--file <file>] [--password <pw>] [--no-password] [--price <usd>] [--no-price] [--pay-to <0x…>] [--expires-days <n>] [--no-optimize]"
|
|
513
538
|
);
|
|
514
539
|
}
|
|
515
540
|
if (!/^[A-Za-z0-9]+$/.test(slug)) {
|
|
516
541
|
throw new Error(`Invalid slug: ${slug}`);
|
|
517
542
|
}
|
|
543
|
+
if (flags._.length > 1) {
|
|
544
|
+
throw new Error("Unexpected positional argument. Use `share replace <slug> <file>` or `share update <slug> --file <file>`.");
|
|
545
|
+
}
|
|
518
546
|
|
|
519
547
|
const hasPassword = Object.prototype.hasOwnProperty.call(flags, "password");
|
|
520
548
|
const hasNoPassword = Object.prototype.hasOwnProperty.call(flags, "no-password");
|
|
@@ -525,6 +553,9 @@ async function handleUpdate(args) {
|
|
|
525
553
|
Object.prototype.hasOwnProperty.call(flags, "noPrice");
|
|
526
554
|
const hasPayTo = Object.prototype.hasOwnProperty.call(flags, "pay-to") ||
|
|
527
555
|
Object.prototype.hasOwnProperty.call(flags, "payTo");
|
|
556
|
+
const hasFile = Object.prototype.hasOwnProperty.call(flags, "file") ||
|
|
557
|
+
Object.prototype.hasOwnProperty.call(flags, "f");
|
|
558
|
+
const filePath = flags.file || flags.f || "";
|
|
528
559
|
|
|
529
560
|
if (hasPassword && hasNoPassword) {
|
|
530
561
|
throw new Error("Pass either --password OR --no-password, not both");
|
|
@@ -535,9 +566,12 @@ async function handleUpdate(args) {
|
|
|
535
566
|
if (hasPrice && hasPassword) {
|
|
536
567
|
throw new Error("--price and --password cannot be combined on a single share");
|
|
537
568
|
}
|
|
538
|
-
if (
|
|
569
|
+
if (hasFile && (typeof filePath !== "string" || filePath.length === 0)) {
|
|
570
|
+
throw new Error("--file requires a path");
|
|
571
|
+
}
|
|
572
|
+
if (!hasPassword && !hasNoPassword && !hasExpires && !hasPrice && !hasNoPrice && !hasPayTo && !hasFile) {
|
|
539
573
|
throw new Error(
|
|
540
|
-
"Nothing to update — specify at least one of --password <pw>, --no-password, --price <usd>, --no-price, --pay-to <0x…>, --expires-days <n>"
|
|
574
|
+
"Nothing to update — specify at least one of --file <file>, --password <pw>, --no-password, --price <usd>, --no-price, --pay-to <0x…>, --expires-days <n>"
|
|
541
575
|
);
|
|
542
576
|
}
|
|
543
577
|
|
|
@@ -603,19 +637,44 @@ async function handleUpdate(args) {
|
|
|
603
637
|
|
|
604
638
|
const { apiKey, userId, shareUrl } = await resolveCredentials();
|
|
605
639
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
640
|
+
let preparedFile = null;
|
|
641
|
+
let requestBody;
|
|
642
|
+
let requestHeaders;
|
|
643
|
+
let timeoutMs;
|
|
644
|
+
if (hasFile) {
|
|
645
|
+
preparedFile = await prepareShareFile(filePath, flags);
|
|
646
|
+
const form = new FormData();
|
|
647
|
+
const blob = new Blob([preparedFile.bytes], { type: preparedFile.mime });
|
|
648
|
+
const file = new File([blob], path.basename(preparedFile.absolute), { type: preparedFile.mime });
|
|
649
|
+
form.set("file", file);
|
|
650
|
+
for (const [key, value] of Object.entries(body)) {
|
|
651
|
+
form.set(key, value === null ? "" : String(value));
|
|
652
|
+
}
|
|
653
|
+
requestBody = form;
|
|
654
|
+
requestHeaders = {
|
|
655
|
+
"x-a2a-user": userId,
|
|
656
|
+
"x-a2a-key": apiKey,
|
|
657
|
+
};
|
|
658
|
+
timeoutMs = 60_000;
|
|
659
|
+
} else {
|
|
660
|
+
requestBody = JSON.stringify(body);
|
|
661
|
+
requestHeaders = {
|
|
609
662
|
"content-type": "application/json",
|
|
610
663
|
"x-a2a-user": userId,
|
|
611
664
|
"x-a2a-key": apiKey,
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
}
|
|
665
|
+
};
|
|
666
|
+
timeoutMs = 30_000;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const res = await fetchWithTimeout(`${shareUrl}/api/share/${encodeURIComponent(slug)}`, {
|
|
670
|
+
method: "PATCH",
|
|
671
|
+
headers: requestHeaders,
|
|
672
|
+
body: requestBody,
|
|
673
|
+
}, timeoutMs);
|
|
615
674
|
|
|
616
675
|
const respBody = await readJson(res);
|
|
617
676
|
if (!res.ok || respBody.error) {
|
|
618
|
-
throw new Error(formatApiError("Update", res.status, respBody));
|
|
677
|
+
throw new Error(formatApiError(mode === "replace" ? "Replace" : "Update", res.status, respBody));
|
|
619
678
|
}
|
|
620
679
|
|
|
621
680
|
if (flags.json) {
|
|
@@ -624,17 +683,25 @@ async function handleUpdate(args) {
|
|
|
624
683
|
}
|
|
625
684
|
|
|
626
685
|
console.log("");
|
|
627
|
-
console.log(`✅ Updated ${slug}`);
|
|
686
|
+
console.log(hasFile ? `✅ Replaced file for ${slug}` : `✅ Updated ${slug}`);
|
|
628
687
|
console.log("");
|
|
629
688
|
console.log(` ${respBody.url}`);
|
|
630
689
|
console.log("");
|
|
690
|
+
if (hasFile) {
|
|
691
|
+
console.log(` File: ${respBody.originalName || path.basename(preparedFile.absolute)} (${formatSize(respBody.size || preparedFile.bytes.length)})`);
|
|
692
|
+
if (preparedFile.optimization.info) {
|
|
693
|
+
console.log(
|
|
694
|
+
` Optimized HTML: ${formatSize(preparedFile.optimization.info.originalBytes)} → ${formatSize(preparedFile.optimization.info.optimizedBytes)}`
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
631
698
|
if (respBody.hasPassword) {
|
|
632
699
|
console.log(` 🔒 Password-protected${hasPassword ? " (existing unlock cookies invalidated)" : ""}`);
|
|
633
700
|
} else if (hasNoPassword) {
|
|
634
701
|
console.log(` 🔓 Password removed`);
|
|
635
702
|
}
|
|
636
703
|
if (respBody.price && respBody.payTo) {
|
|
637
|
-
const rotated = hasPrice ? " (paid sessions invalidated)" : "";
|
|
704
|
+
const rotated = hasPrice || respBody.paymentSessionsInvalidated ? " (paid sessions invalidated)" : "";
|
|
638
705
|
console.log(
|
|
639
706
|
` 💰 Paid — ${formatUsdc(respBody.price)} USDC on ${respBody.network || "?"} → ${respBody.payTo}${rotated}`
|
|
640
707
|
);
|
|
@@ -1181,6 +1248,12 @@ function formatApiError(op, status, body) {
|
|
|
1181
1248
|
return `${op} failed (${status}): file count exceeded — ${body.current}/${body.max}. Delete something first.`;
|
|
1182
1249
|
case "file-too-large":
|
|
1183
1250
|
return `${op} failed (${status}): file too large (max ${formatSize(body.maxBytes)})`;
|
|
1251
|
+
case "empty-file":
|
|
1252
|
+
return `${op} failed (${status}): file is empty`;
|
|
1253
|
+
case "invalid-form-data":
|
|
1254
|
+
return `${op} failed (${status}): invalid multipart form data`;
|
|
1255
|
+
case "invalid-file-field":
|
|
1256
|
+
return `${op} failed (${status}): invalid file field`;
|
|
1184
1257
|
case "unsupported-extension":
|
|
1185
1258
|
return `${op} failed (${status}): unsupported file type. Accepted: ${(body.allowed || []).join(" / ") || "n/a"}`;
|
|
1186
1259
|
case "unsupported-content-type":
|