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
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "viveworker",
3
+ "interface": {
4
+ "displayName": "viveworker"
5
+ },
6
+ "plugins": [
7
+ {
8
+ "name": "viveworker-control-plane",
9
+ "source": {
10
+ "source": "local",
11
+ "path": "./plugins/viveworker-control-plane"
12
+ },
13
+ "policy": {
14
+ "installation": "AVAILABLE",
15
+ "authentication": "ON_INSTALL"
16
+ },
17
+ "category": "Productivity"
18
+ }
19
+ ]
20
+ }
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![npm version](https://badge.fury.io/js/viveworker.svg)](https://badge.fury.io/js/viveworker)
6
6
  [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
7
 
8
- `viveworker` is an open mobile control surface for Codex Desktop, Claude Desktop, Claude Code, A2A tasks, File Share, and Moltbook.
8
+ `viveworker` is an open mobile control surface for Codex Desktop, Claude Desktop, Claude Code, Remote connection, A2A tasks, File Share, and Moltbook.
9
9
 
10
10
  When your AI desktop session needs an approval, asks whether to implement a plan, wants you to choose from options, finishes a task, needs to hand off a file, or receives a task from another agent while you are away from your desk, `viveworker` keeps all of that within reach on your phone. Instead of breaking your rhythm, it helps you keep vivecoding going from anywhere in your home or office.
11
11
 
@@ -31,6 +31,7 @@ keep your AI session moving, keep context close, and keep your momentum.
31
31
 
32
32
  - **AI coding sessions**: approvals, plan checks, questions, completions, and mobile code review for Codex and Claude
33
33
  - **Thread Sharing**: pass context, plan-review requests, or full handoffs between Codex and Claude sessions
34
+ - **Remote connection**: reach your Mac from a paired device outside your LAN through an end-to-end encrypted relay
34
35
  - **File Share**: host static files on a private URL, with optional password protection and expiry
35
36
  - **Moltbook ops**: draft posts, scout replies, and handle incoming responses from the same phone UI
36
37
  - **A2A relay**: receive tasks from other agents, approve them on your phone, and execute locally on your Mac
@@ -50,8 +51,9 @@ one phone, one local control surface, multiple agent workflows.
50
51
  It gets even more fun with a Mac mini.
51
52
  Leave Codex or Claude running on a small always-on machine, and `viveworker` starts to feel like a local coding appliance: your Mac mini keeps building in the background while your device handles approvals, plan checks, questions, and follow-up replies from anywhere in your home or office.
52
53
 
53
- `viveworker` is designed for local use the bridge runs on your LAN and is not exposed to the Internet.
54
- External communication is handled through the A2A relay (`a2a.viveworker.com`), which the bridge polls outbound.
54
+ `viveworker` starts from a local-first model: the bridge runs on your Mac and is not exposed directly to the Internet.
55
+ When Remote connection is enabled, paired devices can also reach that bridge off-LAN through an end-to-end encrypted relay.
56
+ External agent communication is handled separately through the A2A relay (`a2a.viveworker.com`), which the bridge polls outbound.
55
57
 
56
58
  ## Mac mini Ideas
57
59
 
@@ -117,6 +119,22 @@ After setup:
117
119
  - use the pairing URL only for first-time setup or when you intentionally add another device
118
120
  - keep using the Home Screen app if you want notifications to work reliably
119
121
 
122
+ ## Remote Connection
123
+
124
+ Remote connection lets a paired device reach your Mac even when it is not on the same Wi-Fi.
125
+
126
+ Enable it from `Settings > Remote connection` in the Home Screen app. The bridge still stays local on your Mac; it does not open an inbound public port. Instead, the Mac and the paired device meet through the relay, and the actual traffic is end-to-end encrypted between the paired device and your bridge.
127
+
128
+ Security details:
129
+
130
+ - only devices already paired on your LAN can use Remote connection
131
+ - the relay token can be rotated manually from the paired device when you are back on the same LAN
132
+ - existing devices refresh their relay token automatically during LAN enrollment after the rotation window
133
+ - connection events and token rotations appear in `Settings > Remote connection > Connection history`
134
+ - if a device is lost, revoke it from `Settings > Remote connection` or `Settings > Devices`
135
+
136
+ If Remote connection is turned off, off-LAN devices cannot reach the Mac again until you return to the same Wi-Fi and turn it back on.
137
+
120
138
  ## Common Commands
121
139
 
122
140
  Use these commands most often:
@@ -133,6 +151,14 @@ Use these commands most often:
133
151
  install the Moltbook watcher and auto-scout after base setup
134
152
  - `npx viveworker enable scout`
135
153
  tune, reinstall, or uninstall the Moltbook auto-scout job
154
+ - `npx viveworker stats`
155
+ show server-side adoption signals for npm, A2A, File Share, Remote connection, and local Moltbook state
156
+ - `npx viveworker mcp`
157
+ start the stdio MCP server for MCP-compatible AI tools
158
+ - `npx viveworker mcp config`
159
+ print a Claude Desktop / Cursor / Codex MCP config snippet
160
+ - `npx viveworker enable mcp --target claude|cursor|codex|all`
161
+ install the MCP config entry after showing the exact change
136
162
  - `npx viveworker start`
137
163
  start `viveworker` again using the saved config
138
164
  - `npx viveworker stop`
@@ -155,6 +181,78 @@ Useful options:
155
181
  It does not change the main app URL, port, session secret, TLS, or Web Push settings.
156
182
  Use it only when you want to add another trusted device or browser.
157
183
 
184
+ ## MCP Integration
185
+
186
+ `viveworker` can run as a local stdio MCP server, so MCP-compatible tools can use the same mobile control plane.
187
+
188
+ Install it into a supported client:
189
+
190
+ ```bash
191
+ npx viveworker enable mcp --target claude
192
+ npx viveworker enable mcp --target cursor
193
+ npx viveworker enable mcp --target codex
194
+ ```
195
+
196
+ Use `--target all` to update every detected client, `--dry-run` to preview without writing, and `--yes` for non-interactive installs.
197
+ `--target claude` configures Claude Desktop. Claude Code keeps a separate MCP config; add it with:
198
+
199
+ ```bash
200
+ claude mcp add --scope user viveworker -- npx viveworker mcp
201
+ ```
202
+
203
+ If you prefer manual setup, add this to an MCP client:
204
+
205
+ ```json
206
+ {
207
+ "mcpServers": {
208
+ "viveworker": {
209
+ "command": "npx",
210
+ "args": ["viveworker", "mcp"]
211
+ }
212
+ }
213
+ }
214
+ ```
215
+
216
+ Available tools:
217
+
218
+ - `viveworker_status` checks bridge, pairing, Remote connection, A2A, File Share, and Moltbook status
219
+ - `viveworker_notify` sends an informational phone notification and timeline entry
220
+ - `viveworker_ask` asks a question on the paired phone and waits for the answer
221
+ - `viveworker_request_approval` asks the phone to approve or reject a proposed action
222
+ - `viveworker_share_file` uploads a workspace file to File Share after phone approval
223
+ - `viveworker_thread_share` shares context into another Codex / Claude / inbox thread
224
+ - `viveworker_send_a2a_task` sends a task to a registered A2A target after phone approval
225
+
226
+ Available prompts include setup guidance, control-plane usage, risky-action approval, File Share deliverables, and A2A delegation.
227
+
228
+ Security defaults:
229
+
230
+ - MCP is stdio-only in this release; there is no HTTP MCP server
231
+ - file sharing is limited to the current workspace and refuses `.env`, credential directories, private keys, and secret-looking paths
232
+ - File Share and A2A task sending require phone approval
233
+ - MCP tool calls are recorded locally with `provider: "mcp"`
234
+ - prompts, message bodies, file contents, file paths, command text, tokens, public keys, and IP addresses are not sent to central analytics
235
+
236
+ Bundled agent guidance:
237
+
238
+ - `skills/viveworker-control-plane/SKILL.md` gives Codex-style agents a compact policy for when to use each viveworker MCP tool
239
+ - `templates/CLAUDE.viveworker.md` can be copied into a repo `CLAUDE.md` so Claude knows when to ask, approve, share, hand off, or delegate through viveworker
240
+ - `.agents/plugins/marketplace.json` and `plugins/viveworker-control-plane/` package the MCP config and Codex skill as a local Codex plugin for plugin-based installs
241
+ - use these guides when you want agents to guide first-run setup, ask on mobile, request approval, share deliverables, hand off context, or delegate through A2A instead of guessing
242
+
243
+ For local Codex plugin testing from a checkout, register this repo as a marketplace in `~/.codex/config.toml`:
244
+
245
+ ```toml
246
+ [marketplaces.viveworker]
247
+ source_type = "local"
248
+ source = "/path/to/viveworker"
249
+
250
+ [plugins."viveworker-control-plane@viveworker"]
251
+ enabled = true
252
+ ```
253
+
254
+ Restart Codex after changing the config. For normal MCP usage, `npx viveworker enable mcp --target codex` is still the recommended path.
255
+
158
256
  ## File Share
159
257
 
160
258
  `viveworker` includes **File Share**, a private file-hosting surface for agent outputs. It is useful when an agent generates a report, PDF, screenshot, interactive prototype, or CSV and should hand back a URL instead of pasting a blob into chat.
@@ -392,6 +490,7 @@ Today the project already exposes practical integration points through:
392
490
 
393
491
  - Codex + Claude Desktop support
394
492
  - Claude hooks
493
+ - Remote connection for paired devices
395
494
  - A2A relay + Agent Card
396
495
  - File Share URLs
397
496
  - Thread Sharing across sessions
@@ -404,6 +503,7 @@ make `viveworker` the default local/mobile surface that other AI tools can plug
404
503
  If you want to build on `viveworker`, these are the main surfaces to think in:
405
504
 
406
505
  - **Approvals and structured decisions**: approvals, plan checks, multiple-choice questions, and completions all land in the same mobile flow
506
+ - **Remote connection**: keep that mobile flow reachable from paired devices even when they are outside the LAN
407
507
  - **Thread Sharing**: move notes, plan reviews, and handoffs between Codex and Claude sessions with phone approval in the loop
408
508
  - **File Share**: hand back static artefacts as private URLs instead of chat attachments
409
509
  - **A2A relay**: receive or send external agent tasks through a public relay while execution stays local
@@ -416,6 +516,7 @@ These parts already feel like core product surface, not side experiments:
416
516
  - Codex mobile approvals, questions, completions, and code review
417
517
  - Claude Desktop integration through hooks
418
518
  - trusted-LAN pairing, HTTPS, PWA install, and Web Push
519
+ - Remote connection for paired devices, with E2E relay traffic, token rotation, and connection history
419
520
  - A2A task intake + approval + local execution
420
521
  - File Share for static artefacts
421
522
  - Thread Sharing between Codex and Claude sessions
@@ -474,8 +575,13 @@ If you can map your tool onto one of those paths, you probably do not need a bra
474
575
 
475
576
  ## Security Model
476
577
 
477
- - use `viveworker` only on a trusted LAN
578
+ - pair devices only on a trusted LAN
478
579
  - do not expose the bridge directly to the Internet
580
+ - Remote connection uses a relay only for rendezvous/transport; paired-device traffic is end-to-end encrypted to your bridge
581
+ - relay tokens can be rotated manually and are refreshed automatically during LAN enrollment after the rotation window
582
+ - remote connection activity is recorded locally in `~/.viveworker/remote-pairing-audit.jsonl`
583
+ - public relay analytics expose only coarse, delayed adoption counters; detailed relay health and abuse counters require an operator admin token
584
+ - relay analytics never store prompts, replies, file contents, file paths, command text, relay tokens, public keys, or IP addresses
479
585
  - if you lose a paired device, revoke it from `Settings > Devices`
480
586
  - use `pair` only when you want to add another trusted device
481
587
  - A2A relay authentication: external agents must provide a valid API key (`X-A2A-Key` header), and registration requires GitHub OAuth
@@ -490,6 +596,7 @@ If you later want a second wake-up notification path, you can add `ntfy` alongsi
490
596
  ## Troubleshooting
491
597
 
492
598
  - If the `.local` URL does not open, use the printed IP-based URL
599
+ - If Remote connection does not work off-LAN, open `Settings > Remote connection` once on the same Wi-Fi as the Mac and confirm the device is registered
493
600
  - If pairing has expired, run `npx viveworker pair`
494
601
  - If notifications do not appear, make sure you opened the Home Screen app, not just a browser tab
495
602
  - If Web Push is enabled, make sure you are opening the HTTPS URL
@@ -512,3 +619,7 @@ Planned next steps include:
512
619
  - ✅ ~~Android support~~ (Apr 1, 2026)
513
620
  - ✅ ~~Moltbook integration~~ (Apr 10, 2026)
514
621
  - ✅ ~~A2A protocol support~~ (Apr 12, 2026)
622
+ - ✅ ~~File Share~~ (Apr 18, 2026)
623
+ - ✅ ~~Auto Pilot~~ (Apr 21, 2026)
624
+ - ✅ ~~x402 / pay-per-unlock flow (testnet)~~ (Apr 25, 2026)
625
+ - ✅ ~~Remote connection~~ (Apr 28, 2026)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "viveworker",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "Open mobile control surface for Codex, Claude, Thread Sharing, File Share, Moltbook, and A2A tasks on your trusted LAN.",
5
5
  "author": "Yuta Hoshino <hoshino.lireneo@gmail.com>",
6
6
  "license": "MIT",
@@ -52,7 +52,13 @@
52
52
  "scripts/a2a-executor.mjs",
53
53
  "scripts/a2a-relay-client.mjs",
54
54
  "scripts/share-cli.mjs",
55
+ "scripts/stats-cli.mjs",
56
+ "scripts/mcp-server.mjs",
55
57
  "scripts/lib",
58
+ ".agents",
59
+ "skills",
60
+ "templates",
61
+ "plugins",
56
62
  "web",
57
63
  "README.md",
58
64
  "viveworker.env.example",
@@ -61,12 +67,16 @@
61
67
  "ntfy/docker-compose.yml.example"
62
68
  ],
63
69
  "dependencies": {
64
- "@hazbase/auth": "^0.5.0",
70
+ "@hazbase/auth": "^0.5.1",
71
+ "@noble/ciphers": "^1.0.0",
72
+ "@noble/curves": "^1.2.0",
73
+ "@noble/hashes": "^1.3.2",
65
74
  "ethers": "^6.16.0",
66
75
  "qrcode-terminal": "^0.12.0",
67
76
  "web-push": "^3.6.7"
68
77
  },
69
78
  "devDependencies": {
70
- "@cf-wasm/resvg": "^0.3.3"
79
+ "@cf-wasm/resvg": "^0.3.3",
80
+ "fake-indexeddb": "^6.2.5"
71
81
  }
72
82
  }
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "viveworker-control-plane",
3
+ "version": "0.8.0",
4
+ "description": "Run AI agent work from your phone.",
5
+ "author": {
6
+ "name": "viveworker team",
7
+ "email": "hello@viveworker.com",
8
+ "url": "https://viveworker.com/"
9
+ },
10
+ "homepage": "https://viveworker.com/",
11
+ "repository": "https://github.com/viveworker-dev/viveworker",
12
+ "license": "MIT",
13
+ "keywords": [
14
+ "viveworker",
15
+ "mcp",
16
+ "codex",
17
+ "mobile",
18
+ "approvals",
19
+ "thread-sharing",
20
+ "file-share",
21
+ "a2a"
22
+ ],
23
+ "skills": "./skills/",
24
+ "mcpServers": "./.mcp.json",
25
+ "interface": {
26
+ "displayName": "viveworker",
27
+ "shortDescription": "Control Codex, Claude, and agent tasks from your phone.",
28
+ "longDescription": "Pair your phone with viveworker to control Codex, Claude, File Share, Thread Share, A2A tasks, and social posting workflows like Moltbook. Approve actions, answer questions, receive updates, share deliverables, delegate work, and keep agent work moving without being tied to your computer.",
29
+ "developerName": "viveworker team",
30
+ "category": "Productivity",
31
+ "capabilities": [
32
+ "Interactive",
33
+ "MCP",
34
+ "Mobile approvals",
35
+ "File sharing",
36
+ "Thread sharing",
37
+ "A2A delegation"
38
+ ],
39
+ "websiteURL": "https://viveworker.com/",
40
+ "defaultPrompt": [
41
+ "Set up viveworker and pair my phone.",
42
+ "Ask me on my phone before risky actions.",
43
+ "Share this result with another agent."
44
+ ],
45
+ "brandColor": "#64D99A",
46
+ "composerIcon": "./assets/viveworker-logo-v2.png",
47
+ "logo": "./assets/viveworker-logo-v2.png"
48
+ }
49
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "mcpServers": {
3
+ "viveworker": {
4
+ "command": "npx",
5
+ "args": [
6
+ "viveworker",
7
+ "mcp"
8
+ ]
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,39 @@
1
+ <svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <defs>
3
+ <radialGradient id="bg" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(238 164) rotate(43.8309) scale(925.664)">
4
+ <stop stop-color="#173448"/>
5
+ <stop offset="0.38" stop-color="#0F1F2B"/>
6
+ <stop offset="1" stop-color="#091117"/>
7
+ </radialGradient>
8
+ <radialGradient id="glow" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(760 248) rotate(130.1) scale(256.971)">
9
+ <stop stop-color="#5ED6FF" stop-opacity="0.34"/>
10
+ <stop offset="1" stop-color="#5ED6FF" stop-opacity="0"/>
11
+ </radialGradient>
12
+ <linearGradient id="vFill" x1="264" y1="286" x2="778" y2="776" gradientUnits="userSpaceOnUse">
13
+ <stop stop-color="#FFFFFF"/>
14
+ <stop offset="0.5" stop-color="#F5FBFF"/>
15
+ <stop offset="1" stop-color="#E7F4F8"/>
16
+ </linearGradient>
17
+ <radialGradient id="vShade" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(512 674) rotate(90) scale(196 292)">
18
+ <stop stop-color="#CFEAF5" stop-opacity="0.46"/>
19
+ <stop offset="1" stop-color="#CFEAF5" stop-opacity="0"/>
20
+ </radialGradient>
21
+ <filter id="soft" x="557" y="90" width="320" height="320" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
22
+ <feGaussianBlur stdDeviation="24"/>
23
+ </filter>
24
+ <filter id="vShadow" x="190" y="240" width="650" height="630" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
25
+ <feDropShadow dx="0" dy="24" stdDeviation="30" flood-color="#5ED6FF" flood-opacity="0.14"/>
26
+ </filter>
27
+ </defs>
28
+ <rect width="1024" height="1024" rx="224" fill="url(#bg)"/>
29
+ <circle cx="717" cy="250" r="112" fill="url(#glow)"/>
30
+ <g filter="url(#soft)">
31
+ <circle cx="717" cy="250" r="58" fill="#7DD3FC" fill-opacity="0.36"/>
32
+ </g>
33
+ <g filter="url(#vShadow)">
34
+ <path d="M270 292C235 304 220 342 237 376L421 729C458 800 566 800 603 729L787 376C804 342 789 304 754 292C719 280 684 301 670 335L534 657C525 679 499 679 490 657L354 335C340 301 305 280 270 292Z" fill="url(#vFill)"/>
35
+ <path d="M270 292C235 304 220 342 237 376L421 729C458 800 566 800 603 729L787 376C804 342 789 304 754 292C719 280 684 301 670 335L534 657C525 679 499 679 490 657L354 335C340 301 305 280 270 292Z" fill="url(#vShade)"/>
36
+ </g>
37
+ <path d="M697 180C748 180 789 221 789 272" stroke="#7DD3FC" stroke-width="40" stroke-linecap="round"/>
38
+ <path d="M667 120C752 120 821 189 821 274" stroke="#7DD3FC" stroke-width="28" stroke-linecap="round" opacity="0.92"/>
39
+ </svg>
@@ -0,0 +1,83 @@
1
+ ---
2
+ name: viveworker-control-plane
3
+ description: Use viveworker MCP as the mobile control plane for Codex when work needs phone-based questions, approvals, File Share uploads, Thread Share handoffs, or A2A delegation. Use when the user asks to ask on mobile, approve before acting, share a deliverable, send context to another agent/session, or delegate work to an A2A target.
4
+ ---
5
+
6
+ # viveworker Control Plane
7
+
8
+ Use viveworker when the user wants the agent workflow to leave the chat and enter the mobile control plane: ask, approve, notify, share, hand off, or delegate.
9
+
10
+ ## First check
11
+
12
+ If anything looks stale or disconnected, call `viveworker_status` before troubleshooting. It summarizes bridge, pairing, Remote connection, A2A, File Share, and Moltbook state.
13
+
14
+ If viveworker MCP tools are unavailable, tell the user to run:
15
+
16
+ ```bash
17
+ npx viveworker enable mcp --target codex
18
+ ```
19
+
20
+ Then they should restart Codex.
21
+
22
+ ## First-run onboarding
23
+
24
+ Use this flow when the user says "set up viveworker", "pair my phone", "viveworker を初期設定して", or "viveworker を使えるようにして".
25
+
26
+ 1. Try `viveworker_status` if MCP tools are available.
27
+ 2. If the bridge is missing or setup is incomplete, ask for confirmation before running or recommending `npx viveworker setup`.
28
+ 3. Do not silently run setup. It can install local certificates, write config, register launchd services, and update agent hooks.
29
+ 4. If setup exists but no phone is trusted, guide the user to run `npx viveworker pair` and open the pairing URL on the phone.
30
+ 5. Ask the user to allow notifications from the Home Screen app.
31
+ 6. Re-check with `viveworker_status`.
32
+ 7. Send a smoke notification with `viveworker_notify`.
33
+ 8. Summarize what is ready and what remains optional, such as Remote connection, File Share, A2A, or Moltbook.
34
+
35
+ ## Tool policy
36
+
37
+ - Use `viveworker_ask` for a short user decision that blocks progress.
38
+ - Use `viveworker_request_approval` before risky, external, irreversible, payment-related, or user-visible actions.
39
+ - Use `viveworker_notify` for informational updates that should appear on the phone and timeline.
40
+ - Use `viveworker_share_file` when a local deliverable should become a limited File Share URL.
41
+ - Use `viveworker_thread_share` when context should move to another Codex / Claude / inbox thread.
42
+ - Use `viveworker_send_a2a_task` when the user asks to delegate to a registered A2A target.
43
+
44
+ ## Safety rules
45
+
46
+ - Keep phone prompts concise: action, reason, expected result, and choices.
47
+ - Treat timeout, rejection, or missing response as not approved.
48
+ - Do not send secrets, private keys, `.env` content, credentials, relay tokens, public keys, or unnecessary file contents through MCP.
49
+ - Do not use MCP as a shell executor. It creates control-plane events; it should not run arbitrary commands.
50
+ - Do not upload files through File Share without phone approval.
51
+ - Use registered A2A target aliases only. Do not inline API keys.
52
+
53
+ ## Natural language triggers
54
+
55
+ Use `viveworker_ask` for:
56
+
57
+ - "ask me on my phone"
58
+ - "スマホに聞いて"
59
+ - "確認してから進めて"
60
+
61
+ Use `viveworker_request_approval` for:
62
+
63
+ - "approve this on my phone"
64
+ - "承認してから実行して"
65
+ - "外部に送る前に確認して"
66
+
67
+ Use `viveworker_share_file` for:
68
+
69
+ - "share this report"
70
+ - "共有リンクにして"
71
+ - "File Share にアップして"
72
+
73
+ Use `viveworker_thread_share` for:
74
+
75
+ - "share this with Claude"
76
+ - "tell Codex what we decided"
77
+ - "Aの内容をBに共有して"
78
+
79
+ Use `viveworker_send_a2a_task` for:
80
+
81
+ - "delegate this to another agent"
82
+ - "A2Aで依頼して"
83
+ - "registered target に送って"
@@ -170,15 +170,125 @@ function renderBlockquote(lines) {
170
170
 
171
171
  function renderCodeBlock(lines) {
172
172
  const [fence, ...rest] = lines;
173
+ // parseBlocks pushes the closing fence onto the block before breaking out
174
+ // of the loop, so `rest` ends with that closing line whenever the fence is
175
+ // properly closed. Strip it so the rendered <pre><code>...</code></pre>
176
+ // doesn't show the literal "```" as the last line of the code body.
177
+ const body = [...rest];
178
+ if (body.length > 0 && /^```\s*$/u.test(body[body.length - 1])) {
179
+ body.pop();
180
+ }
173
181
  const language = fence.replace(/^```/u, "").trim();
174
182
  const className = language ? ` class="language-${escapeHtml(language)}"` : "";
175
- return `<pre><code${className}>${escapeHtml(rest.join("\n"))}</code></pre>`;
183
+ return `<pre><code${className}>${escapeHtml(body.join("\n"))}</code></pre>`;
176
184
  }
177
185
 
178
186
  function renderParagraph(lines) {
179
187
  return `<p>${lines.map((line) => renderInline(line.trim())).join("<br>")}</p>`;
180
188
  }
181
189
 
190
+ // ---------------------------------------------------------------------------
191
+ // Pipe-style markdown tables
192
+ // ---------------------------------------------------------------------------
193
+ //
194
+ // | header 1 | header 2 |
195
+ // |----------|---------:|
196
+ // | cell | 42 |
197
+ //
198
+ // Detection requires both a header row (single line starting AND ending with
199
+ // `|`) and a separator row immediately below it whose cells are
200
+ // `:?-{2,}:?` (with optional alignment markers). Anything else falls through
201
+ // to the paragraph fallback so a stray `| not a table |` line still
202
+ // renders as plain text.
203
+ //
204
+ // Cells go through `renderInline` so they pick up the existing escape /
205
+ // inline-formatting pipeline; we never inject raw HTML.
206
+
207
+ function isTableRowLine(line) {
208
+ if (typeof line !== "string") return false;
209
+ const trimmed = line.trim();
210
+ return trimmed.length >= 2 && trimmed.startsWith("|") && trimmed.endsWith("|");
211
+ }
212
+
213
+ function splitTableCells(line) {
214
+ // Strip the leading + trailing pipes; honour `\|` as an escaped literal.
215
+ const trimmed = line.trim().replace(/^\|/u, "").replace(/\|$/u, "");
216
+ const cells = [];
217
+ let current = "";
218
+ let i = 0;
219
+ while (i < trimmed.length) {
220
+ if (trimmed[i] === "\\" && trimmed[i + 1] === "|") {
221
+ current += "|";
222
+ i += 2;
223
+ continue;
224
+ }
225
+ if (trimmed[i] === "|") {
226
+ cells.push(current);
227
+ current = "";
228
+ i += 1;
229
+ continue;
230
+ }
231
+ current += trimmed[i];
232
+ i += 1;
233
+ }
234
+ cells.push(current);
235
+ return cells.map((cell) => cell.trim());
236
+ }
237
+
238
+ function isTableSeparatorLine(line) {
239
+ if (!isTableRowLine(line)) return false;
240
+ const cells = splitTableCells(line);
241
+ if (cells.length === 0) return false;
242
+ return cells.every((cell) => /^:?-{2,}:?$/u.test(cell));
243
+ }
244
+
245
+ function parseTableAlignments(separatorLine) {
246
+ return splitTableCells(separatorLine).map((cell) => {
247
+ const left = cell.startsWith(":");
248
+ const right = cell.endsWith(":");
249
+ if (left && right) return "center";
250
+ if (right) return "right";
251
+ if (left) return "left";
252
+ return "";
253
+ });
254
+ }
255
+
256
+ function renderTableCell(cell, align, tag) {
257
+ const styleAttr = align ? ` style="text-align:${align}"` : "";
258
+ return `<${tag}${styleAttr}>${renderInline(cell)}</${tag}>`;
259
+ }
260
+
261
+ function renderTable(headerLine, separatorLine, dataLines) {
262
+ const alignments = parseTableAlignments(separatorLine);
263
+ const headerCells = splitTableCells(headerLine);
264
+ // Pad alignments out to header width so any data row that's wider still
265
+ // gets a sensible alignment default.
266
+ while (alignments.length < headerCells.length) alignments.push("");
267
+
268
+ const headerHtml = headerCells
269
+ .map((cell, i) => renderTableCell(cell, alignments[i] || "", "th"))
270
+ .join("");
271
+ const bodyHtml = dataLines
272
+ .map((row) => {
273
+ const cells = splitTableCells(row);
274
+ const cellsHtml = cells
275
+ .map((cell, i) => renderTableCell(cell, alignments[i] || "", "td"))
276
+ .join("");
277
+ return `<tr>${cellsHtml}</tr>`;
278
+ })
279
+ .join("");
280
+
281
+ return `<table><thead><tr>${headerHtml}</tr></thead><tbody>${bodyHtml}</tbody></table>`;
282
+ }
283
+
284
+ function looksLikeTableStart(lines, index) {
285
+ return (
286
+ isTableRowLine(lines[index]) &&
287
+ index + 1 < lines.length &&
288
+ isTableSeparatorLine(lines[index + 1])
289
+ );
290
+ }
291
+
182
292
  function parseBlocks(markdown) {
183
293
  const lines = markdown.replace(/\r\n/gu, "\n").trim().split("\n");
184
294
  const blocks = [];
@@ -241,6 +351,19 @@ function parseBlocks(markdown) {
241
351
  continue;
242
352
  }
243
353
 
354
+ if (looksLikeTableStart(lines, index)) {
355
+ const headerLine = lines[index];
356
+ const separatorLine = lines[index + 1];
357
+ const dataLines = [];
358
+ index += 2;
359
+ while (index < lines.length && isTableRowLine(lines[index])) {
360
+ dataLines.push(lines[index]);
361
+ index += 1;
362
+ }
363
+ blocks.push(renderTable(headerLine, separatorLine, dataLines));
364
+ continue;
365
+ }
366
+
244
367
  const block = [];
245
368
  while (index < lines.length) {
246
369
  const current = lines[index];
@@ -250,6 +373,10 @@ function parseBlocks(markdown) {
250
373
  if (isUnorderedList(current) || isOrderedList(current) || /^(?:---|\*\*\*|___)\s*$/u.test(current)) {
251
374
  break;
252
375
  }
376
+ if (looksLikeTableStart(lines, index)) {
377
+ // Don't swallow a table-start row into the previous paragraph.
378
+ break;
379
+ }
253
380
  block.push(current);
254
381
  index += 1;
255
382
  }