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
@@ -75,6 +75,17 @@ if (rawArgs[0] === "stats") {
75
75
  }
76
76
  }
77
77
 
78
+ if (rawArgs[0] === "mcp") {
79
+ const { runMcpCli } = await import("./mcp-server.mjs");
80
+ try {
81
+ await runMcpCli(rawArgs.slice(1));
82
+ process.exit(0);
83
+ } catch (error) {
84
+ console.error(error.message || String(error));
85
+ process.exit(1);
86
+ }
87
+ }
88
+
78
89
  const cli = parseArgs(rawArgs);
79
90
 
80
91
  try {
@@ -388,7 +399,7 @@ async function runPair(cliOptions) {
388
399
  async function runEnable(cliOptions) {
389
400
  const target = String(cliOptions.enableTarget || "").trim().toLowerCase();
390
401
  if (!target) {
391
- throw new Error("Usage: viveworker enable <claude|a2a|moltbook|scout> [...]");
402
+ throw new Error("Usage: viveworker enable <claude|a2a|moltbook|scout|mcp> [...]");
392
403
  }
393
404
 
394
405
  switch (target) {
@@ -406,11 +417,248 @@ async function runEnable(cliOptions) {
406
417
  case "scout":
407
418
  await runEnableScout(cliOptions);
408
419
  return;
420
+ case "mcp":
421
+ await runEnableMcp(cliOptions);
422
+ return;
409
423
  default:
410
424
  throw new Error(`Unknown feature: ${target}`);
411
425
  }
412
426
  }
413
427
 
428
+ async function runEnableMcp(cliOptions) {
429
+ const target = normalizeMcpInstallTarget(cliOptions.mcpTarget || "all");
430
+ const allTargets = target === "all" ? ["claude", "cursor", "codex"] : [target];
431
+ const entries = [];
432
+ const config = mcpServerConfigSnippet();
433
+
434
+ for (const item of allTargets) {
435
+ const entry = await prepareMcpConfigChange(item, config, { explicit: target !== "all" });
436
+ if (entry) entries.push(entry);
437
+ }
438
+
439
+ if (entries.length === 0) {
440
+ console.log("");
441
+ console.log("No supported MCP client config was found.");
442
+ console.log("Run `npx viveworker mcp config` to print a manual config snippet.");
443
+ return;
444
+ }
445
+
446
+ console.log("");
447
+ console.log("viveworker MCP setup");
448
+ console.log("");
449
+ console.log("The following MCP client config changes will be applied:");
450
+ for (const entry of entries) {
451
+ console.log(`- ${entry.label}: ${entry.status} ${entry.path}`);
452
+ }
453
+ console.log("");
454
+ console.log("MCP server entry:");
455
+ console.log(JSON.stringify({ mcpServers: { viveworker: config } }, null, 2));
456
+
457
+ if (cliOptions.mcpDryRun) {
458
+ console.log("");
459
+ console.log("Dry run only. No files were changed.");
460
+ return;
461
+ }
462
+
463
+ const changedEntries = entries.filter((entry) => entry.changed);
464
+ if (changedEntries.length === 0) {
465
+ console.log("");
466
+ console.log("MCP config is already up to date.");
467
+ return;
468
+ }
469
+
470
+ if (!cliOptions.yes) {
471
+ const confirmed = await confirmCli("Write these MCP config changes? [y/N] ");
472
+ if (!confirmed) {
473
+ console.log("Cancelled.");
474
+ return;
475
+ }
476
+ }
477
+
478
+ for (const entry of changedEntries) {
479
+ await fs.mkdir(path.dirname(entry.path), { recursive: true });
480
+ await fs.writeFile(entry.path, entry.nextText, entry.mode || "utf8");
481
+ }
482
+
483
+ console.log("");
484
+ console.log("MCP config updated.");
485
+ console.log("Restart the target app so it reloads the MCP server list.");
486
+ }
487
+
488
+ function normalizeMcpInstallTarget(value) {
489
+ const target = String(value || "all").trim().toLowerCase();
490
+ if (["claude", "cursor", "codex", "all"].includes(target)) {
491
+ return target;
492
+ }
493
+ throw new Error("Use --target claude, --target cursor, --target codex, or --target all.");
494
+ }
495
+
496
+ function mcpServerConfigSnippet() {
497
+ return {
498
+ command: "npx",
499
+ args: ["viveworker", "mcp"],
500
+ };
501
+ }
502
+
503
+ async function prepareMcpConfigChange(target, serverConfig, { explicit = false } = {}) {
504
+ const descriptor = mcpConfigDescriptor(target);
505
+ if (!descriptor) {
506
+ return null;
507
+ }
508
+ if (!explicit && !(await fileExists(descriptor.parentDir))) {
509
+ return null;
510
+ }
511
+ const currentText = await readOptionalText(descriptor.path);
512
+ const result = descriptor.kind === "toml"
513
+ ? upsertCodexMcpConfig(currentText, serverConfig)
514
+ : upsertJsonMcpConfig(currentText, serverConfig);
515
+ return {
516
+ target,
517
+ label: descriptor.label,
518
+ path: descriptor.path,
519
+ status: result.changed
520
+ ? (currentText.trim() ? "update" : "create")
521
+ : "unchanged",
522
+ changed: result.changed,
523
+ nextText: result.text,
524
+ };
525
+ }
526
+
527
+ function mcpConfigDescriptor(target) {
528
+ if (target === "claude") {
529
+ const override = process.env.VIVEWORKER_MCP_CLAUDE_CONFIG_FILE || "";
530
+ const defaultPath = path.join(os.homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
531
+ const configPath = resolvePath(override || defaultPath);
532
+ return {
533
+ kind: "json",
534
+ label: "Claude Desktop",
535
+ path: configPath,
536
+ parentDir: path.dirname(configPath),
537
+ };
538
+ }
539
+ if (target === "cursor") {
540
+ const override = process.env.VIVEWORKER_MCP_CURSOR_CONFIG_FILE || "";
541
+ const defaultPath = path.join(os.homedir(), ".cursor", "mcp.json");
542
+ const configPath = resolvePath(override || defaultPath);
543
+ return {
544
+ kind: "json",
545
+ label: "Cursor",
546
+ path: configPath,
547
+ parentDir: path.dirname(configPath),
548
+ };
549
+ }
550
+ if (target === "codex") {
551
+ const override = process.env.VIVEWORKER_MCP_CODEX_CONFIG_FILE || "";
552
+ const defaultPath = path.join(os.homedir(), ".codex", "config.toml");
553
+ const configPath = resolvePath(override || defaultPath);
554
+ return {
555
+ kind: "toml",
556
+ label: "Codex",
557
+ path: configPath,
558
+ parentDir: path.dirname(configPath),
559
+ };
560
+ }
561
+ return null;
562
+ }
563
+
564
+ function upsertJsonMcpConfig(currentText, serverConfig) {
565
+ let data = {};
566
+ if (currentText.trim()) {
567
+ try {
568
+ data = JSON.parse(currentText);
569
+ } catch (error) {
570
+ throw new Error(`Unable to parse existing MCP JSON config: ${error.message}`);
571
+ }
572
+ }
573
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
574
+ data = {};
575
+ }
576
+ const previous = JSON.stringify(data);
577
+ const mcpServers = data.mcpServers && typeof data.mcpServers === "object" && !Array.isArray(data.mcpServers)
578
+ ? data.mcpServers
579
+ : {};
580
+ data.mcpServers = {
581
+ ...mcpServers,
582
+ viveworker: serverConfig,
583
+ };
584
+ const text = `${JSON.stringify(data, null, 2)}\n`;
585
+ return {
586
+ changed: JSON.stringify(data) !== previous,
587
+ text,
588
+ };
589
+ }
590
+
591
+ function upsertCodexMcpConfig(currentText, serverConfig) {
592
+ const block = [
593
+ "[mcp_servers.viveworker]",
594
+ `command = ${tomlString(serverConfig.command)}`,
595
+ `args = [${serverConfig.args.map((arg) => tomlString(arg)).join(", ")}]`,
596
+ "",
597
+ ].join("\n");
598
+ const withoutExisting = removeTomlTable(currentText, "mcp_servers.viveworker").replace(/\s+$/u, "");
599
+ const text = `${withoutExisting ? `${withoutExisting}\n\n` : ""}${block}`;
600
+ return {
601
+ changed: normalizeConfigText(text) !== normalizeConfigText(currentText),
602
+ text,
603
+ };
604
+ }
605
+
606
+ function removeTomlTable(text, tableName) {
607
+ const lines = String(text || "").split(/\r?\n/u);
608
+ const out = [];
609
+ let skipping = false;
610
+ const header = `[${tableName}]`;
611
+ for (const line of lines) {
612
+ const trimmed = line.trim();
613
+ if (trimmed === header) {
614
+ skipping = true;
615
+ continue;
616
+ }
617
+ if (skipping && /^\[[^\]]+\]\s*$/u.test(trimmed)) {
618
+ skipping = false;
619
+ }
620
+ if (!skipping) {
621
+ out.push(line);
622
+ }
623
+ }
624
+ return out.join("\n");
625
+ }
626
+
627
+ function tomlString(value) {
628
+ return JSON.stringify(String(value || ""));
629
+ }
630
+
631
+ function normalizeConfigText(value) {
632
+ return String(value || "").replace(/\r\n/gu, "\n").trim();
633
+ }
634
+
635
+ async function readOptionalText(filePath) {
636
+ try {
637
+ return await fs.readFile(filePath, "utf8");
638
+ } catch (error) {
639
+ if (error?.code === "ENOENT") {
640
+ return "";
641
+ }
642
+ throw error;
643
+ }
644
+ }
645
+
646
+ async function confirmCli(prompt) {
647
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
648
+ throw new Error("Refusing to write MCP config without an interactive terminal. Re-run with --yes or --dry-run.");
649
+ }
650
+ const rl = createReadlineInterface({
651
+ input: process.stdin,
652
+ output: process.stdout,
653
+ });
654
+ try {
655
+ const answer = await rl.question(prompt);
656
+ return /^y(es)?$/iu.test(String(answer || "").trim());
657
+ } finally {
658
+ rl.close();
659
+ }
660
+ }
661
+
414
662
  async function runEnableClaude(cliOptions) {
415
663
  const setup = await loadExistingSetup(cliOptions);
416
664
  const settingsFile = resolvePath(
@@ -863,13 +1111,16 @@ async function runStart(cliOptions) {
863
1111
  }
864
1112
 
865
1113
  progress.update("cli.start.progress.bridge");
866
- await startDetachedBridge({
867
- envFile,
868
- logFile: resolvePath(cliOptions.logFile || defaultLogFile),
869
- pidFile,
870
- });
1114
+ const alreadyHealthy = await waitForHealth(healthUrl, { attempts: 1, intervalMs: 0 });
1115
+ if (!alreadyHealthy) {
1116
+ await startDetachedBridge({
1117
+ envFile,
1118
+ logFile: resolvePath(cliOptions.logFile || defaultLogFile),
1119
+ pidFile,
1120
+ });
1121
+ }
871
1122
  progress.update("cli.start.progress.health");
872
- const healthy = await waitForHealth(healthUrl);
1123
+ const healthy = alreadyHealthy || await waitForHealth(healthUrl);
873
1124
  const pairingReady = healthy && rotatedPairing.rotated
874
1125
  ? await waitForExpectedPairing(config.NATIVE_APPROVAL_SERVER_PUBLIC_BASE_URL || "", rotatedPairing.pairingToken)
875
1126
  : true;
@@ -1394,6 +1645,9 @@ function parseArgs(argv) {
1394
1645
  autoScoutHarness: "auto",
1395
1646
  autoScoutSubmolts: "",
1396
1647
  autoScoutMaxDaily: 0,
1648
+ mcpTarget: "",
1649
+ mcpDryRun: false,
1650
+ yes: false,
1397
1651
  };
1398
1652
 
1399
1653
  if (argv[0] && !argv[0].startsWith("-")) {
@@ -1549,6 +1803,13 @@ function parseArgs(argv) {
1549
1803
  } else if (arg === "--auto-scout-max-daily") {
1550
1804
  parsed.autoScoutMaxDaily = Number(next) || 0;
1551
1805
  index += 1;
1806
+ } else if (arg === "--target") {
1807
+ parsed.mcpTarget = next;
1808
+ index += 1;
1809
+ } else if (arg === "--dry-run") {
1810
+ parsed.mcpDryRun = true;
1811
+ } else if (arg === "--yes" || arg === "-y") {
1812
+ parsed.yes = true;
1552
1813
  } else if (arg === "--help" || arg === "-h") {
1553
1814
  parsed.command = "help";
1554
1815
  } else {
@@ -1576,6 +1837,8 @@ ${t(locale, "cli.help.commands")}
1576
1837
  ${t(locale, "cli.help.status")}
1577
1838
  ${t(locale, "cli.help.doctor")}
1578
1839
  ${t(locale, "cli.help.update")}
1840
+ mcp Start the stdio MCP server
1841
+ mcp config Print MCP client config snippets
1579
1842
 
1580
1843
  ${t(locale, "cli.help.commonOptions")}
1581
1844
  --port <n>
@@ -1604,6 +1867,7 @@ ${t(locale, "cli.help.featureOptions")}
1604
1867
  ${t(locale, "cli.help.enableA2a")}
1605
1868
  ${t(locale, "cli.help.enableMoltbook")}
1606
1869
  ${t(locale, "cli.help.enableScout")}
1870
+ enable mcp --target <claude|cursor|codex|all> [--dry-run|--yes]
1607
1871
  ${t(locale, "cli.help.doctorFix")}
1608
1872
  `);
1609
1873
  }
@@ -1691,6 +1955,11 @@ function buildLaunchAgentPlist({ label, nodePath, bridgeScript, envFile, logFile
1691
1955
  }
1692
1956
 
1693
1957
  async function startDetachedBridge({ envFile, logFile, pidFile }) {
1958
+ const existingPid = await maybeReadPid(pidFile);
1959
+ if (existingPid && isProcessRunning(existingPid)) {
1960
+ return { pid: existingPid, alreadyRunning: true };
1961
+ }
1962
+
1694
1963
  await fs.mkdir(path.dirname(logFile), { recursive: true });
1695
1964
  const logHandle = await fs.open(logFile, "a");
1696
1965
  const child = spawn(process.execPath, [bridgeScript, "--env-file", envFile], {
@@ -1700,6 +1969,7 @@ async function startDetachedBridge({ envFile, logFile, pidFile }) {
1700
1969
  child.unref();
1701
1970
  await logHandle.close();
1702
1971
  await fs.writeFile(pidFile, `${child.pid}\n`, "utf8");
1972
+ return { pid: child.pid, alreadyRunning: false };
1703
1973
  }
1704
1974
 
1705
1975
  async function maybeReadPid(pidFile) {
@@ -1712,6 +1982,18 @@ async function maybeReadPid(pidFile) {
1712
1982
  }
1713
1983
  }
1714
1984
 
1985
+ function isProcessRunning(pid) {
1986
+ if (!Number.isFinite(pid) || pid <= 0) {
1987
+ return false;
1988
+ }
1989
+ try {
1990
+ process.kill(pid, 0);
1991
+ return true;
1992
+ } catch {
1993
+ return false;
1994
+ }
1995
+ }
1996
+
1715
1997
  async function maybeReadEnvFile(filePath) {
1716
1998
  const output = {};
1717
1999
  try {
@@ -0,0 +1,104 @@
1
+ ---
2
+ name: viveworker-control-plane
3
+ description: Route AI agent operations through the user's viveworker mobile control plane. Use when Codex has access to the viveworker MCP tools and needs to notify the phone, ask the user a question, request human approval, share a workspace deliverable through File Share, hand off context to another Codex/Claude/inbox thread, or delegate a bounded task to a registered A2A target. Especially use for externally visible, irreversible, sensitive, or cross-agent actions.
4
+ ---
5
+
6
+ # viveworker Control Plane
7
+
8
+ Use viveworker as the mobile control surface around agent work. Keep normal local work moving, but route decisions, confirmations, handoffs, deliverables, and delegation through the paired phone when that improves safety or flow.
9
+
10
+ ## First Check
11
+
12
+ - If tool availability is uncertain, call `viveworker_status` first.
13
+ - If viveworker MCP is unavailable, do not pretend the action happened. Tell the user to run `npx viveworker enable mcp --target codex` or add the config from `npx viveworker mcp config`.
14
+ - Do not use viveworker for every routine local read/edit. Use it when a human decision, mobile notification, external handoff, or durable record is useful.
15
+
16
+ ## First-Run Onboarding
17
+
18
+ Use this flow when the user asks to set up viveworker, pair a phone, install the control plane, or "make viveworker work here".
19
+
20
+ 1. Check whether MCP tools are available.
21
+ - If available, call `viveworker_status` first and use the result to decide what is missing.
22
+ - If unavailable, explain that the MCP entry is not loaded yet and guide the user through `npx viveworker enable mcp --target codex`, then ask them to restart Codex.
23
+ 2. If the bridge is not running or no local setup exists, ask for confirmation before running or recommending `npx viveworker setup`.
24
+ - Do not auto-run setup silently. It can install local certificates, write config, register launchd services, and change agent hooks.
25
+ - If the user explicitly asks you to execute setup, use the normal shell tool with clear confirmation, not MCP.
26
+ 3. If setup exists but no phone is trusted, guide the user to run `npx viveworker pair` and open the pairing URL on the phone.
27
+ 4. Ask the user to allow notifications from the Home Screen app when prompted.
28
+ 5. Re-check with `viveworker_status`.
29
+ 6. Send a final smoke notification with `viveworker_notify`.
30
+ 7. Summarize what is ready: bridge, pairing, notifications, Remote connection, File Share, A2A, and Moltbook where applicable.
31
+
32
+ Keep onboarding calm and step-by-step. Do not make the user debug config files unless the status output points there.
33
+
34
+ ## Tool Selection
35
+
36
+ - Use `viveworker_notify` for informational milestones that should appear on the phone or timeline, such as "build finished", "review ready", or "agent is blocked".
37
+ - Use `viveworker_ask` when the next step depends on a human preference, missing requirement, or choice that should not be guessed.
38
+ - Use `viveworker_request_approval` before actions that are externally visible, hard to undo, risky, sensitive, delegated, paid, or likely to surprise the user.
39
+ - Use `viveworker_share_file` when the user wants a workspace deliverable shared as a limited File Share URL. Good fits include `.html`, `.htm`, `.pdf`, `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, and `.csv`.
40
+ - Use `viveworker_thread_share` when context should move to another Codex session, Claude session, or viveworker inbox while preserving a human-visible handoff.
41
+ - Use `viveworker_send_a2a_task` only for registered A2A target aliases and bounded tasks with clear acceptance criteria.
42
+
43
+ ## Approval And Question Style
44
+
45
+ - Keep phone prompts short and concrete.
46
+ - Include the action, expected result, and specific risk.
47
+ - Include relevant file refs, but never include secrets, tokens, private keys, or unnecessary file contents.
48
+ - Treat timeout, rejection, or transport failure as "not approved". Do not continue as if approval was granted.
49
+ - After the tool returns, summarize the decision or returned artifact to the user.
50
+
51
+ ## File Share Rules
52
+
53
+ - Share only files inside the workspace root.
54
+ - Do not share `.env`, credential files, private keys, `.ssh`, `.aws`, `.gnupg`, or secret-looking paths.
55
+ - Prefer sharing final deliverables, not internal source files, unless the user explicitly asks.
56
+ - For unsupported file types, explain the accepted types and suggest exporting to HTML, PDF, image, or CSV first.
57
+ - Do not use File Share to bypass a user's explicit request not to upload or publish content.
58
+
59
+ ## Thread Share Rules
60
+
61
+ - Use `targetConversationId` when the user names a specific thread.
62
+ - Use `targetTool` when the user says "send this to Codex", "share with Claude", or "put this in the inbox" without a specific thread ID.
63
+ - Send a compact handoff: current goal, decisions made, blockers, and concrete next action.
64
+ - Attach `contextFiles` only when they help the receiver act.
65
+
66
+ ## A2A Delegation Rules
67
+
68
+ - Use only aliases from `~/.viveworker/a2a-targets.json`; never inline API keys in tool arguments.
69
+ - Keep delegated tasks self-contained and limited in scope.
70
+ - Include success criteria, constraints, and any deadline or budget.
71
+ - Ask for approval before sending, even if the task looks harmless.
72
+ - Do not send secrets, private repo data, or local paths unless the user clearly approves that exposure.
73
+
74
+ ## Examples
75
+
76
+ Ask before a risky action:
77
+
78
+ ```json
79
+ {
80
+ "title": "Approve release publish",
81
+ "message": "Publish viveworker 0.8.0 to npm. Risk: this makes the package public and hard to fully undo.",
82
+ "approvalKind": "release"
83
+ }
84
+ ```
85
+
86
+ Share a deliverable:
87
+
88
+ ```json
89
+ {
90
+ "path": "/workspace/report.html",
91
+ "workspaceRoot": "/workspace",
92
+ "expiresDays": "3"
93
+ }
94
+ ```
95
+
96
+ Delegate through A2A:
97
+
98
+ ```json
99
+ {
100
+ "target": "reviewer",
101
+ "instruction": "Review the remote connection security notes and return only concrete risks with file references.",
102
+ "metadata": { "scope": "security-review" }
103
+ }
104
+ ```
@@ -0,0 +1,12 @@
1
+ interface:
2
+ display_name: "viveworker Control Plane"
3
+ short_description: "Route AI agent decisions through viveworker"
4
+ default_prompt: "Use $viveworker-control-plane to route approvals, questions, file shares, thread handoffs, and A2A delegation through viveworker."
5
+ dependencies:
6
+ tools:
7
+ - type: "mcp"
8
+ value: "viveworker"
9
+ description: "Local viveworker stdio MCP server"
10
+ transport: "stdio"
11
+ policy:
12
+ allow_implicit_invocation: true
@@ -0,0 +1,67 @@
1
+ # viveworker MCP guide for Claude
2
+
3
+ Use viveworker as the mobile control plane for this Claude session when the user wants phone-based questions, approvals, file sharing, thread sharing, or A2A delegation.
4
+
5
+ ## Setup
6
+
7
+ For Claude Desktop, the user should install the MCP entry once:
8
+
9
+ ```bash
10
+ npx viveworker enable mcp --target claude
11
+ ```
12
+
13
+ Then restart Claude Desktop.
14
+
15
+ For Claude Code, the MCP config is separate from Claude Desktop. Install it once with:
16
+
17
+ ```bash
18
+ claude mcp add --scope user viveworker -- npx viveworker mcp
19
+ ```
20
+
21
+ Then restart the Claude Code session. If the tools are not available, ask the user to run the setup command for the Claude surface they are using.
22
+
23
+ ## Tools
24
+
25
+ - `viveworker_status` checks bridge, pairing, Remote connection, A2A, File Share, and Moltbook state.
26
+ - `viveworker_notify` sends an informational phone notification and records a timeline entry.
27
+ - `viveworker_ask` asks the paired phone a question and waits for the answer.
28
+ - `viveworker_request_approval` asks the phone to approve or reject a proposed action.
29
+ - `viveworker_share_file` uploads a workspace file to File Share after phone approval.
30
+ - `viveworker_thread_share` shares context into another Codex / Claude / inbox thread.
31
+ - `viveworker_send_a2a_task` sends a task to a registered A2A target after phone approval.
32
+
33
+ ## When to use viveworker
34
+
35
+ - If the user says "ask me on my phone", "スマホに聞いて", or a short decision blocks progress, use `viveworker_ask`.
36
+ - If the user asks you to proceed with a risky, external, irreversible, payment-related, or user-visible action, use `viveworker_request_approval`.
37
+ - If the user asks for a report, prototype, screenshot, PDF, CSV, or standalone HTML to become a shareable link, use `viveworker_share_file`.
38
+ - If the user says "share this with Codex/Claude", "Aの内容をBに共有して", or wants context handed to another session, use `viveworker_thread_share`.
39
+ - If the user wants another registered agent to do work, use `viveworker_send_a2a_task`.
40
+
41
+ ## Prompting rules
42
+
43
+ - Keep phone prompts short and concrete.
44
+ - Include the action, why it matters, expected outcome, and clear choices.
45
+ - Do not send secrets, private keys, `.env` content, credentials, or unnecessary file contents.
46
+ - Treat timeout, rejection, or missing response as not approved.
47
+ - Do not spam the phone for routine local reads or low-risk edits unless the user asked for mobile confirmation.
48
+
49
+ ## Thread sharing
50
+
51
+ Thread Share is useful for handoffs such as:
52
+
53
+ - "Tell Codex what we just decided."
54
+ - "Share this Claude result with the Codex thread."
55
+ - "Aの内容をBに共有して。"
56
+
57
+ Send a compact summary with the decision, relevant context, and the exact next action expected from the recipient.
58
+
59
+ Claude may not automatically notice newly shared messages from another session. If the user says something arrived or asks you to pull shared context, inspect the viveworker timeline/inbox surface available in the current environment, or ask the user to open the relevant shared item.
60
+
61
+ ## Safety defaults
62
+
63
+ - MCP is a control-plane surface, not a shell executor.
64
+ - Do not run shell strings through MCP.
65
+ - File Share and A2A task sending require phone approval.
66
+ - Use registered A2A aliases only; do not ask the user to paste API keys into prompts.
67
+ - Prefer `viveworker_status` before troubleshooting a missing notification, stale pairing, or remote connectivity issue.