viveworker 0.8.4 → 0.8.6
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 +11 -1
- package/package.json +18 -10
- package/scripts/mcp-server.mjs +111 -5
- package/scripts/share-cli.mjs +326 -45
- package/scripts/viveworker-bridge.mjs +458 -39
- package/scripts/viveworker.mjs +9 -0
- package/templates/CLAUDE.viveworker.md +3 -1
- package/web/app.css +195 -0
- package/web/app.js +1017 -237
- package/web/build-id.js +1 -1
- package/web/i18n.js +114 -18
- package/web/sw.js +4 -0
package/README.md
CHANGED
|
@@ -163,6 +163,8 @@ Use these commands most often:
|
|
|
163
163
|
start `viveworker` again using the saved config
|
|
164
164
|
- `npx viveworker stop`
|
|
165
165
|
stop the local background service
|
|
166
|
+
- `npx viveworker restart`
|
|
167
|
+
restart the local bridge using the saved config
|
|
166
168
|
- `npx viveworker status`
|
|
167
169
|
show the current app URL, launchd/background status, and health
|
|
168
170
|
- `npx viveworker doctor`
|
|
@@ -266,6 +268,7 @@ What it supports:
|
|
|
266
268
|
- PNG / JPG / GIF / WebP
|
|
267
269
|
- CSV rendered as an HTML table by default
|
|
268
270
|
- optional password protection
|
|
271
|
+
- short-lived token URLs for password-protected shares
|
|
269
272
|
- optional expiry
|
|
270
273
|
|
|
271
274
|
HTML uploads are optimized by default when possible.
|
|
@@ -288,10 +291,17 @@ Typical commands:
|
|
|
288
291
|
- `npx viveworker share update <slug> --file updated-report.html`
|
|
289
292
|
- `npx viveworker share update <slug> --password "hunter2"`
|
|
290
293
|
- `npx viveworker share update <slug> --expires-days 7`
|
|
291
|
-
- `npx viveworker share link <slug
|
|
294
|
+
- `npx viveworker share link <slug> --password "hunter2" --ttl-hours 24`
|
|
292
295
|
- `VIVEWORKER_BUYER_PRIVATE_KEY=0x... npx viveworker share pay https://share.viveworker.com/v/<slug> --output ./deliverable.pdf`
|
|
293
296
|
- `npx viveworker share pay https://share.viveworker.com/v/<slug> --wallet hazbase --output ./deliverable.pdf`
|
|
294
297
|
|
|
298
|
+
`share link` is for agent handoff. It verifies the current password locally with
|
|
299
|
+
the File Share worker and returns a short-lived `https://share.viveworker.com/v/<slug>?t=<token>`
|
|
300
|
+
URL, so the recipient can open the share without seeing the password. The token
|
|
301
|
+
defaults to 24 hours, can be capped with `--ttl-hours` up to 720 hours, never
|
|
302
|
+
outlives the share expiry, and is invalidated when you rotate the share password
|
|
303
|
+
with `share update --password`.
|
|
304
|
+
|
|
295
305
|
`share pay` is human-in-the-loop by default. EOA mode reads the x402 payment
|
|
296
306
|
requirements and asks the paired device to approve before signing. `--wallet
|
|
297
307
|
hazbase` instead sends the payment request to the paired device, asks for
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "viveworker",
|
|
3
|
-
"version": "0.8.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.8.6",
|
|
4
|
+
"description": "Mobile control plane for AI agents. Approve, answer, and hand off work for Codex, Claude, MCP-ready tools, and A2A from one phone.",
|
|
5
5
|
"author": "Yuta Hoshino <hoshino.lireneo@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"keywords": [
|
|
@@ -9,6 +9,15 @@
|
|
|
9
9
|
"codex-desktop",
|
|
10
10
|
"claude",
|
|
11
11
|
"claude-desktop",
|
|
12
|
+
"mcp",
|
|
13
|
+
"model-context-protocol",
|
|
14
|
+
"mcp-server",
|
|
15
|
+
"ai-agents",
|
|
16
|
+
"agent-control-plane",
|
|
17
|
+
"agent-handoff",
|
|
18
|
+
"a2a",
|
|
19
|
+
"agent-to-agent",
|
|
20
|
+
"a2a-relay",
|
|
12
21
|
"macos",
|
|
13
22
|
"mobile",
|
|
14
23
|
"ios",
|
|
@@ -22,17 +31,15 @@
|
|
|
22
31
|
"moltbook",
|
|
23
32
|
"file-share",
|
|
24
33
|
"thread-sharing",
|
|
25
|
-
"ai-agents",
|
|
26
|
-
"a2a",
|
|
27
|
-
"agent-to-agent",
|
|
28
|
-
"a2a-relay",
|
|
29
|
-
"agent-control-plane",
|
|
30
34
|
"lan",
|
|
31
|
-
"
|
|
32
|
-
"
|
|
35
|
+
"remote-pairing",
|
|
36
|
+
"companion-app"
|
|
33
37
|
],
|
|
34
38
|
"homepage": "https://viveworker.com/",
|
|
35
39
|
"repository": "viveworker-dev/viveworker",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/viveworker-dev/viveworker/issues"
|
|
42
|
+
},
|
|
36
43
|
"type": "module",
|
|
37
44
|
"bin": {
|
|
38
45
|
"viveworker": "./scripts/viveworker.mjs"
|
|
@@ -67,7 +74,8 @@
|
|
|
67
74
|
"ntfy/docker-compose.yml.example"
|
|
68
75
|
],
|
|
69
76
|
"dependencies": {
|
|
70
|
-
"@hazbase/auth": "^0.5.
|
|
77
|
+
"@hazbase/auth": "^0.5.3",
|
|
78
|
+
"@hazbase/simplicity": "^0.3.0",
|
|
71
79
|
"@noble/ciphers": "^1.0.0",
|
|
72
80
|
"@noble/curves": "^1.2.0",
|
|
73
81
|
"@noble/hashes": "^1.3.2",
|
package/scripts/mcp-server.mjs
CHANGED
|
@@ -146,6 +146,8 @@ async function callTool(params) {
|
|
|
146
146
|
return toolOk(await toolRequestApproval(args));
|
|
147
147
|
case "viveworker_share_file":
|
|
148
148
|
return toolOk(await toolShareFile(args));
|
|
149
|
+
case "viveworker_share_link":
|
|
150
|
+
return toolOk(await toolShareLink(args));
|
|
149
151
|
case "viveworker_thread_share":
|
|
150
152
|
return toolOk(await toolThreadShare(args));
|
|
151
153
|
case "viveworker_send_a2a_task":
|
|
@@ -199,6 +201,13 @@ async function toolShareFile(args) {
|
|
|
199
201
|
const workspaceRoot = optionalString(args.workspaceRoot) || process.env.VIVEWORKER_MCP_WORKSPACE_ROOT || process.cwd();
|
|
200
202
|
const file = await validateSharePath(requestedPath, workspaceRoot);
|
|
201
203
|
const stat = await fs.stat(file.realPath);
|
|
204
|
+
const password = optionalString(args.password);
|
|
205
|
+
const expiresDays = optionalString(args.expiresDays ?? args["expires-days"]);
|
|
206
|
+
const tokenize = optionalBoolean(args.tokenize) || optionalBoolean(args.tokenizedUrl) || optionalBoolean(args.passwordlessUrl);
|
|
207
|
+
const tokenTtlHours = normalizeShareTokenTtlHours(args.tokenTtlHours ?? args["token-ttl-hours"] ?? args.ttlHours ?? args["ttl-hours"]);
|
|
208
|
+
if (tokenize && !password) {
|
|
209
|
+
throw rpcInvalidParams("tokenize requires password because tokenized URLs are minted from password-protected shares");
|
|
210
|
+
}
|
|
202
211
|
const approval = await bridgeEvent({
|
|
203
212
|
eventType: "approval_request",
|
|
204
213
|
title: "Share file with viveworker File Share",
|
|
@@ -207,6 +216,8 @@ async function toolShareFile(args) {
|
|
|
207
216
|
"",
|
|
208
217
|
`File: ${file.displayPath}`,
|
|
209
218
|
`Size: ${stat.size} bytes`,
|
|
219
|
+
password ? "Password gate: enabled" : "Password gate: disabled",
|
|
220
|
+
tokenize ? `Token URL: create short-lived passwordless ?t= URL${tokenTtlHours ? ` (${tokenTtlHours}h)` : " (24h)"}` : "",
|
|
210
221
|
"",
|
|
211
222
|
"Approve only if this file is safe to send outside this Mac.",
|
|
212
223
|
].join("\n"),
|
|
@@ -219,12 +230,52 @@ async function toolShareFile(args) {
|
|
|
219
230
|
}
|
|
220
231
|
|
|
221
232
|
const uploadArgs = ["share", "upload", file.realPath, "--json"];
|
|
222
|
-
const password = optionalString(args.password);
|
|
223
|
-
const expiresDays = optionalString(args.expiresDays ?? args["expires-days"]);
|
|
224
233
|
if (password) uploadArgs.push("--password", password);
|
|
225
234
|
if (expiresDays) uploadArgs.push("--expires-days", expiresDays);
|
|
226
235
|
const upload = await runViveworkerCliJson(uploadArgs, 90_000);
|
|
227
|
-
|
|
236
|
+
if (!tokenize) {
|
|
237
|
+
return { approved: true, share: upload };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const slug = optionalString(upload.slug) || slugFromShareUrl(upload.url);
|
|
241
|
+
if (!slug) {
|
|
242
|
+
throw new Error("share upload did not return a slug for tokenized URL creation");
|
|
243
|
+
}
|
|
244
|
+
const link = await createShareTokenLink(slug, password, tokenTtlHours);
|
|
245
|
+
return { approved: true, share: upload, tokenizedLink: link };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function toolShareLink(args) {
|
|
249
|
+
const slug = requiredString(args.slug, "slug");
|
|
250
|
+
if (!/^[A-Za-z0-9]+$/.test(slug)) {
|
|
251
|
+
throw rpcInvalidParams("slug must contain only letters and numbers");
|
|
252
|
+
}
|
|
253
|
+
const password = requiredString(args.password, "password");
|
|
254
|
+
if (password.length > 256) {
|
|
255
|
+
throw rpcInvalidParams("password is too long (max 256 characters)");
|
|
256
|
+
}
|
|
257
|
+
const ttlHours = normalizeShareTokenTtlHours(args.ttlHours ?? args["ttl-hours"] ?? args.tokenTtlHours ?? args["token-ttl-hours"]);
|
|
258
|
+
const approval = await bridgeEvent({
|
|
259
|
+
eventType: "approval_request",
|
|
260
|
+
title: "Create File Share token link",
|
|
261
|
+
message: [
|
|
262
|
+
"An MCP client wants to mint a short-lived passwordless File Share URL.",
|
|
263
|
+
"",
|
|
264
|
+
`Share slug: ${slug}`,
|
|
265
|
+
`Token TTL: ${ttlHours || 24}h`,
|
|
266
|
+
"",
|
|
267
|
+
"Anyone with the returned ?t= URL can open this share until the token or share expires.",
|
|
268
|
+
"Approve only if you are comfortable handing off this password-protected share.",
|
|
269
|
+
].join("\n"),
|
|
270
|
+
approvalKind: "file_share_link",
|
|
271
|
+
timeoutMs: clampTimeout(args.timeoutMs),
|
|
272
|
+
}, clampTimeout(args.timeoutMs));
|
|
273
|
+
if (!approval.approved) {
|
|
274
|
+
return { approved: false, decision: approval.decision || "rejected" };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const link = await createShareTokenLink(slug, password, ttlHours);
|
|
278
|
+
return { approved: true, link };
|
|
228
279
|
}
|
|
229
280
|
|
|
230
281
|
async function toolThreadShare(args) {
|
|
@@ -577,6 +628,43 @@ function normalizeStringArray(value) {
|
|
|
577
628
|
return value.map((item) => String(item || "").trim()).filter(Boolean);
|
|
578
629
|
}
|
|
579
630
|
|
|
631
|
+
function optionalBoolean(value) {
|
|
632
|
+
if (value === true) return true;
|
|
633
|
+
if (value === false || value == null) return false;
|
|
634
|
+
const text = String(value).trim().toLowerCase();
|
|
635
|
+
return text === "1" || text === "true" || text === "yes" || text === "on";
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function normalizeShareTokenTtlHours(value) {
|
|
639
|
+
if (value == null || value === "") return undefined;
|
|
640
|
+
const n = Number(value);
|
|
641
|
+
if (!Number.isFinite(n) || n <= 0 || n > 720) {
|
|
642
|
+
throw rpcInvalidParams("token TTL must be a number between 1 and 720 hours");
|
|
643
|
+
}
|
|
644
|
+
return n;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function slugFromShareUrl(value) {
|
|
648
|
+
const text = optionalString(value);
|
|
649
|
+
if (!text) return "";
|
|
650
|
+
try {
|
|
651
|
+
const url = new URL(text);
|
|
652
|
+
const match = url.pathname.match(/^\/v\/([A-Za-z0-9]+)/u);
|
|
653
|
+
return match?.[1] || "";
|
|
654
|
+
} catch {
|
|
655
|
+
const match = text.match(/\/v\/([A-Za-z0-9]+)/u);
|
|
656
|
+
return match?.[1] || "";
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
async function createShareTokenLink(slug, password, ttlHours) {
|
|
661
|
+
const linkArgs = ["share", "link", slug, "--password", password, "--json"];
|
|
662
|
+
if (ttlHours !== undefined) {
|
|
663
|
+
linkArgs.push("--ttl-hours", String(ttlHours));
|
|
664
|
+
}
|
|
665
|
+
return runViveworkerCliJson(linkArgs, 30_000);
|
|
666
|
+
}
|
|
667
|
+
|
|
580
668
|
function requiredString(value, name) {
|
|
581
669
|
const text = optionalString(value);
|
|
582
670
|
if (!text) throw rpcInvalidParams(`${name} is required`);
|
|
@@ -789,7 +877,7 @@ const TOOLS = [
|
|
|
789
877
|
{
|
|
790
878
|
name: "viveworker_share_file",
|
|
791
879
|
title: "Share file",
|
|
792
|
-
description: "After phone approval, upload a workspace file to viveworker File Share and return the limited URL.",
|
|
880
|
+
description: "After phone approval, upload a workspace file to viveworker File Share and return the limited URL. If password is set, pass tokenize=true to also mint a short-lived passwordless ?t= URL.",
|
|
793
881
|
inputSchema: {
|
|
794
882
|
type: "object",
|
|
795
883
|
additionalProperties: false,
|
|
@@ -798,11 +886,29 @@ const TOOLS = [
|
|
|
798
886
|
workspaceRoot: { type: "string" },
|
|
799
887
|
password: { type: "string" },
|
|
800
888
|
expiresDays: { type: "string" },
|
|
889
|
+
tokenize: { type: "boolean" },
|
|
890
|
+
tokenTtlHours: { type: "number" },
|
|
801
891
|
timeoutMs: { type: "number" },
|
|
802
892
|
},
|
|
803
893
|
required: ["path"],
|
|
804
894
|
},
|
|
805
895
|
},
|
|
896
|
+
{
|
|
897
|
+
name: "viveworker_share_link",
|
|
898
|
+
title: "Create File Share token URL",
|
|
899
|
+
description: "After phone approval, mint a short-lived passwordless ?t= URL for an existing password-protected File Share slug.",
|
|
900
|
+
inputSchema: {
|
|
901
|
+
type: "object",
|
|
902
|
+
additionalProperties: false,
|
|
903
|
+
properties: {
|
|
904
|
+
slug: { type: "string" },
|
|
905
|
+
password: { type: "string" },
|
|
906
|
+
ttlHours: { type: "number" },
|
|
907
|
+
timeoutMs: { type: "number" },
|
|
908
|
+
},
|
|
909
|
+
required: ["slug", "password"],
|
|
910
|
+
},
|
|
911
|
+
},
|
|
806
912
|
{
|
|
807
913
|
name: "viveworker_thread_share",
|
|
808
914
|
title: "Thread share",
|
|
@@ -871,7 +977,7 @@ const PROMPTS = [
|
|
|
871
977
|
title: "Share deliverable",
|
|
872
978
|
description: "Package a local deliverable through viveworker File Share with phone approval.",
|
|
873
979
|
},
|
|
874
|
-
text: "When the user asks to share a report, prototype, screenshot, CSV, or standalone HTML deliverable, use viveworker_share_file. Only share files inside the workspace and never share secrets or credentials.",
|
|
980
|
+
text: "When the user asks to share a report, prototype, screenshot, CSV, or standalone HTML deliverable, use viveworker_share_file. Only share files inside the workspace and never share secrets or credentials. If the user wants a password-protected share but the recipient should not know the password, set password plus tokenize=true, or use viveworker_share_link for an existing password-protected slug to mint a short-lived ?t= URL.",
|
|
875
981
|
},
|
|
876
982
|
{
|
|
877
983
|
definition: {
|