viveworker 0.1.8 → 0.1.10

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 CHANGED
@@ -5,12 +5,12 @@
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` brings Codex Desktop to your iPhone.
8
+ `viveworker` brings Codex Desktop to your phone.
9
9
 
10
10
  When Codex needs an approval, asks whether to implement a plan, wants you to choose from options, or finishes a task 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
 
12
12
  Think of it as a local companion for Codex on your Mac:
13
- your Mac keeps building, and your iPhone keeps you in the loop.
13
+ your Mac keeps building, and your device keeps you in the loop.
14
14
 
15
15
  ## Why It Feels Good
16
16
 
@@ -29,13 +29,13 @@ keep Codex moving, keep context close, and keep your momentum.
29
29
 
30
30
  `viveworker` works best with:
31
31
 
32
- - Mac + iPhone
32
+ - Mac + mobile device
33
33
  - the same Wi-Fi or LAN
34
34
  - a trusted local network
35
35
  - the Home Screen web app with Web Push enabled
36
36
 
37
37
  It gets even more fun with a Mac mini.
38
- Leave Codex 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 iPhone handles approvals, plan checks, questions, and follow-up replies from anywhere in your home or office.
38
+ Leave Codex 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.
39
39
 
40
40
  `viveworker` is designed for local use only.
41
41
  It is not intended for Internet exposure.
@@ -48,7 +48,7 @@ You can use it as:
48
48
 
49
49
  - an always-on Codex station that stays running in the background
50
50
  - a way to keep approvals and plan checks moving even when you are away from your desk
51
- - a lightweight monitor for long-running coding or research tasks, where your iPhone only surfaces what needs your attention
51
+ - a lightweight monitor for long-running coding or research tasks, where your device only surfaces what needs your attention
52
52
  - a small local AI appliance for your home or office
53
53
  - a quick way to review a completion and send “do this next” back into the latest thread from your phone
54
54
 
@@ -79,10 +79,10 @@ npx viveworker setup --port 8820
79
79
 
80
80
  1. Run `npx viveworker setup --install-mkcert` on your Mac
81
81
  2. If macOS asks, allow the local CA install
82
- 3. On your iPhone, open the printed `rootCA.pem` URL
83
- 4. Install the certificate profile and trust it in iPhone certificate trust settings
84
- 5. Open the printed pairing URL in Safari
85
- 6. Pair your iPhone with the code if needed
82
+ 3. On your device, open the printed `rootCA.pem` URL
83
+ 4. If your device requires local CA trust, install the certificate profile and trust it
84
+ 5. Open the printed pairing URL on your device
85
+ 6. Pair your device with the code if needed
86
86
  7. Add `viveworker` to your Home Screen
87
87
  8. Open the Home Screen app
88
88
  9. In `Settings`, tap `Enable Notifications`
@@ -128,7 +128,7 @@ Useful options:
128
128
 
129
129
  `--pair` reissues only the short-lived pairing code and pairing URL.
130
130
  It does not change the main app URL, port, session secret, TLS, or Web Push settings.
131
- Use it only when you want to add another trusted iPhone or browser.
131
+ Use it only when you want to add another trusted device or browser.
132
132
 
133
133
  ## Questions and Limits
134
134
 
@@ -156,7 +156,7 @@ If you later want a second wake-up notification path, you can add `ntfy` alongsi
156
156
 
157
157
  - If the `.local` URL does not open, use the printed IP-based URL
158
158
  - If pairing has expired, run `npx viveworker setup --pair`
159
- - If notifications do not appear, make sure you opened the Home Screen app, not just a Safari tab
159
+ - If notifications do not appear, make sure you opened the Home Screen app, not just a browser tab
160
160
  - If Web Push is enabled, make sure you are opening the HTTPS URL
161
161
  - If you are stuck, run:
162
162
 
@@ -171,13 +171,13 @@ npx viveworker doctor
171
171
  - Web Push still depends on the browser/platform push service
172
172
  - `--install-mkcert` can automate the Mac-side `mkcert` install and `mkcert -install`
173
173
  - macOS may still show an administrator prompt while installing the local CA
174
- - iPhone trust is still manual: you need to trust the local CA profile on the device
174
+ - On some devices, local CA trust is still manual before HTTPS works reliably
175
175
  - Web Push supports approvals, plans, multiple-choice questions, and completions
176
176
 
177
177
  ## Roadmap
178
178
 
179
179
  Planned next steps include:
180
180
 
181
- - Android support
182
181
  - Windows support
182
+ - ✅ ~~Android support~~ (Apr 1, 2026)
183
183
  - ✅ ~~image attachment support from mobile~~ (Mar 26, 2026)
@@ -8,7 +8,7 @@ base-url: "http://YOUR-MAC-IP-OR-HOSTNAME:8080"
8
8
  # The container listens on port 80 internally; Docker maps 8080 -> 80.
9
9
  listen-http: ":80"
10
10
 
11
- # Persistent cache is required so iPhone can fetch message contents after a poll request.
11
+ # Persistent cache is required so a paired device can fetch message contents after a poll request.
12
12
  cache-file: "/var/lib/ntfy/cache.db"
13
13
 
14
14
  # User, ACL, and token database. Created automatically if it does not exist.
@@ -19,7 +19,7 @@ auth-default-access: "deny-all"
19
19
  enable-login: true
20
20
  enable-signup: false
21
21
 
22
- # Needed for near-instant iPhone delivery on a self-hosted server.
22
+ # Needed for near-instant mobile delivery on a self-hosted server.
23
23
  # Only a poll request is forwarded upstream, not your real message body.
24
24
  upstream-base-url: "https://ntfy.sh"
25
25
 
package/package.json CHANGED
@@ -1,15 +1,16 @@
1
1
  {
2
2
  "name": "viveworker",
3
- "version": "0.1.8",
4
- "description": "Local iPhone companion for Codex Desktop approvals, plan checks, questions, and notifications on your LAN.",
3
+ "version": "0.1.10",
4
+ "description": "Local device companion for Codex Desktop approvals, plan checks, questions, and notifications on your LAN.",
5
5
  "author": "Yuta Hoshino <hoshino.lireneo@gmail.com>",
6
6
  "license": "MIT",
7
7
  "keywords": [
8
8
  "codex",
9
9
  "codex-desktop",
10
10
  "macos",
11
- "iphone",
11
+ "mobile",
12
12
  "ios",
13
+ "android",
13
14
  "pwa",
14
15
  "web-push",
15
16
  "notifications",
@@ -85,8 +85,16 @@ const state = await loadState(config.stateFile);
85
85
  const migratedPairedDevicesStateChanged = migratePairedDevicesState({ config, state });
86
86
  const restoredPendingPlanStateChanged = restorePendingPlanRequests({ config, runtime, state });
87
87
  const restoredPendingUserInputStateChanged = restorePendingUserInputRequests({ config, runtime, state });
88
- runtime.recentHistoryItems = normalizeHistoryItems(state.recentHistoryItems ?? [], config.maxHistoryItems);
89
- runtime.recentTimelineEntries = normalizeTimelineEntries(state.recentTimelineEntries ?? [], config.maxTimelineEntries);
88
+ const initialHistoryItems = normalizeHistoryItems(state.recentHistoryItems ?? [], config.maxHistoryItems);
89
+ const initialTimelineEntries = normalizeTimelineEntries(state.recentTimelineEntries ?? [], config.maxTimelineEntries);
90
+ const normalizedHistoryStateChanged =
91
+ JSON.stringify(initialHistoryItems) !== JSON.stringify(Array.isArray(state.recentHistoryItems) ? state.recentHistoryItems : []);
92
+ const normalizedTimelineStateChanged =
93
+ JSON.stringify(initialTimelineEntries) !== JSON.stringify(Array.isArray(state.recentTimelineEntries) ? state.recentTimelineEntries : []);
94
+ runtime.recentHistoryItems = initialHistoryItems;
95
+ runtime.recentTimelineEntries = initialTimelineEntries;
96
+ state.recentHistoryItems = initialHistoryItems;
97
+ state.recentTimelineEntries = initialTimelineEntries;
90
98
  const migratedRecentCodeEventsStateChanged = migrateRecentCodeEventsState({ config, runtime, state });
91
99
  const restoredTimelineImagePathsStateChanged = await backfillPersistedTimelineImagePaths({ config, runtime, state });
92
100
  runtime.historyFileState.offset = Number(state.historyFileOffset) || 0;
@@ -1791,7 +1799,11 @@ function normalizeHistoryItem(raw) {
1791
1799
 
1792
1800
  const stableId = cleanText(raw.stableId ?? raw.id ?? "");
1793
1801
  const kind = cleanText(raw.kind ?? "");
1794
- const title = cleanText(raw.title ?? "");
1802
+ const threadId = cleanText(raw.threadId ?? extractConversationIdFromStableId(stableId) ?? "");
1803
+ const rawThreadLabel = cleanText(raw.threadLabel ?? "");
1804
+ const threadLabel = preferTitleOnlyJsonThreadLabel(rawThreadLabel, threadId, raw.messageText, raw.summary, raw.detailText, raw.message);
1805
+ const title =
1806
+ cleanText(raw.title ?? "") || (threadLabel ? formatTitle(kindTitle(DEFAULT_LOCALE, kind), threadLabel) : kindTitle(DEFAULT_LOCALE, kind));
1795
1807
  const messageText = normalizeTimelineMessageText(raw.messageText ?? "");
1796
1808
  const summary = normalizeNotificationText(raw.summary ?? "") || formatNotificationBody(messageText, 100) || "";
1797
1809
  const createdAtMs = Number(raw.createdAtMs) || Date.now();
@@ -1805,9 +1817,9 @@ function normalizeHistoryItem(raw) {
1805
1817
  stableId,
1806
1818
  token: cleanText(raw.token ?? "") || historyToken(stableId),
1807
1819
  kind,
1808
- threadId: cleanText(raw.threadId ?? extractConversationIdFromStableId(stableId) ?? ""),
1820
+ threadId,
1809
1821
  title,
1810
- threadLabel: cleanText(raw.threadLabel ?? ""),
1822
+ threadLabel,
1811
1823
  summary,
1812
1824
  messageText,
1813
1825
  imagePaths: normalizeTimelineImagePaths(raw.imagePaths ?? raw.localImagePaths ?? []),
@@ -1922,6 +1934,7 @@ function normalizeTimelineEntry(raw) {
1922
1934
  return null;
1923
1935
  }
1924
1936
 
1937
+ const threadId = cleanText(raw.threadId ?? extractConversationIdFromStableId(stableId) ?? "");
1925
1938
  const messageText = normalizeTimelineMessageText(raw.messageText ?? "");
1926
1939
  const fileEventType = normalizeTimelineFileEventType(raw.fileEventType ?? "");
1927
1940
  const diffText = normalizeTimelineDiffText(raw.diffText ?? "");
@@ -1932,7 +1945,14 @@ function normalizeTimelineEntry(raw) {
1932
1945
  formatNotificationBody(messageText, 180) ||
1933
1946
  (kind === "file_event" ? "" : cleanText(raw.title ?? "")) ||
1934
1947
  "";
1935
- const threadLabel = cleanText(raw.threadLabel ?? "");
1948
+ const threadLabel = preferTitleOnlyJsonThreadLabel(
1949
+ cleanText(raw.threadLabel ?? ""),
1950
+ threadId,
1951
+ raw.messageText,
1952
+ raw.summary,
1953
+ raw.detailText,
1954
+ raw.message
1955
+ );
1936
1956
  const title =
1937
1957
  cleanText(raw.title ?? "") ||
1938
1958
  (kind === "file_event" ? fileEventTitle(DEFAULT_LOCALE, fileEventType) : "") ||
@@ -1944,7 +1964,7 @@ function normalizeTimelineEntry(raw) {
1944
1964
  stableId,
1945
1965
  token: cleanText(raw.token ?? "") || historyToken(stableId),
1946
1966
  kind,
1947
- threadId: cleanText(raw.threadId ?? extractConversationIdFromStableId(stableId) ?? ""),
1967
+ threadId,
1948
1968
  threadLabel,
1949
1969
  title,
1950
1970
  summary,
@@ -6820,16 +6840,16 @@ function formatNativeApprovalMessage(kind, params, locale = config?.defaultLocal
6820
6840
  function formatCommandApprovalMessage(params, locale = config?.defaultLocale || DEFAULT_LOCALE) {
6821
6841
  const parts = [];
6822
6842
  const reason = truncate(cleanText(params.reason ?? params.justification ?? ""), 220);
6823
- const command = truncate(cleanText(params.command ?? params.cmd ?? ""), 220);
6843
+ const command = truncate(cleanText(params.command ?? params.cmd ?? ""), 1200);
6824
6844
  if (reason) {
6825
6845
  parts.push(reason);
6826
6846
  } else {
6827
6847
  parts.push(t(locale, "server.message.commandApprovalNeeded"));
6828
6848
  }
6829
6849
  if (command) {
6830
- parts.push(t(locale, "server.message.commandPrefix", { command }));
6850
+ parts.push(`${t(locale, "server.message.commandLabel")}\n\`\`\`sh\n${command}\n\`\`\``);
6831
6851
  }
6832
- return truncate(parts.join("\n") || t(locale, "server.message.commandApprovalNeeded"), 1024);
6852
+ return parts.join("\n\n") || t(locale, "server.message.commandApprovalNeeded");
6833
6853
  }
6834
6854
 
6835
6855
  function formatFileApprovalMessage(params, locale = config?.defaultLocale || DEFAULT_LOCALE) {
@@ -8830,6 +8850,7 @@ function buildPendingApprovalDetail(runtime, approval, locale) {
8830
8850
  const previousContext = buildPreviousApprovalContext(runtime, approval);
8831
8851
  return {
8832
8852
  kind: "approval",
8853
+ approvalKind: cleanText(approval.kind || ""),
8833
8854
  token: approval.token,
8834
8855
  title: formatLocalizedTitle(locale, "server.title.approval", approval.threadLabel),
8835
8856
  threadLabel: approval.threadLabel || "",
@@ -12476,6 +12497,22 @@ function extractRolloutMessageText(content) {
12476
12497
  );
12477
12498
  }
12478
12499
 
12500
+ function extractTitleOnlyJsonTitleFromRolloutContent(content) {
12501
+ if (!Array.isArray(content)) {
12502
+ return "";
12503
+ }
12504
+ for (const entry of content) {
12505
+ if (!isPlainObject(entry) || (entry.type !== "input_text" && entry.type !== "output_text")) {
12506
+ continue;
12507
+ }
12508
+ const title = extractTitleOnlyJsonTitle(entry.text ?? "");
12509
+ if (title) {
12510
+ return title;
12511
+ }
12512
+ }
12513
+ return "";
12514
+ }
12515
+
12479
12516
  function rolloutContentHasImages(content) {
12480
12517
  if (!Array.isArray(content)) {
12481
12518
  return false;
@@ -12591,6 +12628,22 @@ async function extractRolloutThreadMetadata(filePath) {
12591
12628
  break;
12592
12629
  }
12593
12630
  }
12631
+ } else if (!metadata.titleCandidate && entry.payload?.type === "message" && entry.payload?.role === "assistant") {
12632
+ const titleCandidate = truncate(cleanText(extractTitleOnlyJsonTitleFromRolloutContent(entry.payload.content)), 90);
12633
+ if (titleCandidate) {
12634
+ metadata.titleCandidate = titleCandidate;
12635
+ if (metadata.threadId) {
12636
+ break;
12637
+ }
12638
+ }
12639
+ } else if (!metadata.titleCandidate && entry.type === "event_msg" && entry.payload?.type === "task_complete") {
12640
+ const titleCandidate = truncate(cleanText(extractTitleOnlyJsonTitle(entry.payload.last_agent_message ?? "")), 90);
12641
+ if (titleCandidate) {
12642
+ metadata.titleCandidate = titleCandidate;
12643
+ if (metadata.threadId) {
12644
+ break;
12645
+ }
12646
+ }
12594
12647
  }
12595
12648
  }
12596
12649
  } catch {
@@ -12767,11 +12820,12 @@ function refreshResolvedThreadLabels({ config, runtime, state }) {
12767
12820
  if (!conversationId) {
12768
12821
  return item;
12769
12822
  }
12770
- const threadLabel = getNativeThreadLabel({
12823
+ const nativeThreadLabel = getNativeThreadLabel({
12771
12824
  runtime,
12772
12825
  conversationId,
12773
12826
  cwd: "",
12774
12827
  });
12828
+ const threadLabel = preferTitleOnlyJsonThreadLabel(nativeThreadLabel, conversationId, item.messageText, item.summary);
12775
12829
  const title = formatTitle(kindTitle(config.defaultLocale, item.kind), threadLabel);
12776
12830
  if (threadLabel === item.threadLabel && title === item.title) {
12777
12831
  return item;
@@ -12801,11 +12855,12 @@ function refreshResolvedThreadLabels({ config, runtime, state }) {
12801
12855
  if (!threadId) {
12802
12856
  return entry;
12803
12857
  }
12804
- const threadLabel = getNativeThreadLabel({
12858
+ const nativeThreadLabel = getNativeThreadLabel({
12805
12859
  runtime,
12806
12860
  conversationId: threadId,
12807
12861
  cwd: "",
12808
12862
  });
12863
+ const threadLabel = preferTitleOnlyJsonThreadLabel(nativeThreadLabel, threadId, entry.messageText, entry.summary);
12809
12864
  const title = threadLabel || kindTitle(config.defaultLocale, entry.kind);
12810
12865
  if (threadLabel === entry.threadLabel && title === entry.title) {
12811
12866
  return entry;
@@ -12835,11 +12890,12 @@ function refreshResolvedThreadLabels({ config, runtime, state }) {
12835
12890
  if (!threadId) {
12836
12891
  return entry;
12837
12892
  }
12838
- const threadLabel = getNativeThreadLabel({
12893
+ const nativeThreadLabel = getNativeThreadLabel({
12839
12894
  runtime,
12840
12895
  conversationId: threadId,
12841
12896
  cwd: "",
12842
12897
  });
12898
+ const threadLabel = preferTitleOnlyJsonThreadLabel(nativeThreadLabel, threadId, entry.messageText, entry.summary);
12843
12899
  const title = threadLabel || kindTitle(config.defaultLocale, entry.kind);
12844
12900
  if (threadLabel === entry.threadLabel && title === entry.title) {
12845
12901
  return entry;
@@ -12901,6 +12957,10 @@ function summarizeNotificationText(value) {
12901
12957
  }
12902
12958
 
12903
12959
  function normalizeLongText(value) {
12960
+ const titleOnlyJsonTitle = extractTitleOnlyJsonTitle(value);
12961
+ if (titleOnlyJsonTitle) {
12962
+ return titleOnlyJsonTitle;
12963
+ }
12904
12964
  return String(stripEnvironmentContextBlocks(stripMarkdownLinks(value)) || "")
12905
12965
  .replace(/\r\n/gu, "\n")
12906
12966
  .replace(/[ \t]+\n/gu, "\n")
@@ -13012,6 +13072,53 @@ function cleanText(value) {
13012
13072
  return String(value || "").replace(/\s+/gu, " ").trim();
13013
13073
  }
13014
13074
 
13075
+ function extractTitleOnlyJsonTitle(value) {
13076
+ const rawText = String(value ?? "").trim();
13077
+ if (!rawText) {
13078
+ return "";
13079
+ }
13080
+ const parsed = safeJsonParse(rawText);
13081
+ if (!isPlainObject(parsed) || typeof parsed.title !== "string") {
13082
+ return "";
13083
+ }
13084
+ const meaningfulExtraKeys = Object.entries(parsed).filter(([key, entryValue]) => {
13085
+ if (key === "title") {
13086
+ return false;
13087
+ }
13088
+ if (entryValue == null) {
13089
+ return false;
13090
+ }
13091
+ if (typeof entryValue === "string" && cleanText(entryValue) === "") {
13092
+ return false;
13093
+ }
13094
+ if (Array.isArray(entryValue) && entryValue.length === 0) {
13095
+ return false;
13096
+ }
13097
+ if (isPlainObject(entryValue) && Object.keys(entryValue).length === 0) {
13098
+ return false;
13099
+ }
13100
+ return true;
13101
+ });
13102
+ if (meaningfulExtraKeys.length > 0) {
13103
+ return "";
13104
+ }
13105
+ return cleanText(parsed.title || "");
13106
+ }
13107
+
13108
+ function preferTitleOnlyJsonThreadLabel(rawThreadLabel, conversationId, ...candidates) {
13109
+ const preferredThreadLabel = sanitizeResolvedThreadLabel(rawThreadLabel, conversationId);
13110
+ if (preferredThreadLabel) {
13111
+ return preferredThreadLabel;
13112
+ }
13113
+ for (const candidate of candidates) {
13114
+ const titleOnlyJsonTitle = truncate(cleanText(extractTitleOnlyJsonTitle(candidate)), 90);
13115
+ if (titleOnlyJsonTitle) {
13116
+ return titleOnlyJsonTitle;
13117
+ }
13118
+ }
13119
+ return cleanText(rawThreadLabel || "");
13120
+ }
13121
+
13015
13122
  function stripMarkdownLinks(value) {
13016
13123
  return String(value || "").replace(/\[([^\]]+)\]\(([^)]+)\)/gu, "$1");
13017
13124
  }
@@ -13226,6 +13333,8 @@ async function main() {
13226
13333
  }
13227
13334
 
13228
13335
  if (
13336
+ normalizedHistoryStateChanged ||
13337
+ normalizedTimelineStateChanged ||
13229
13338
  migratedPairedDevicesStateChanged ||
13230
13339
  restoredPendingPlanStateChanged ||
13231
13340
  restoredTimelineImagePathsStateChanged ||
@@ -1336,10 +1336,24 @@ async function installMkcertForMac(progress, locale) {
1336
1336
  }
1337
1337
 
1338
1338
  progress?.update("cli.setup.progress.installMkcert");
1339
- await execCommand([brewPath, "install", "mkcert"], {
1340
- streamOutput: true,
1341
- beforeStreamOutput: () => progress?.clear(),
1342
- });
1339
+ try {
1340
+ await execCommand([brewPath, "install", "mkcert"], {
1341
+ streamOutput: true,
1342
+ beforeStreamOutput: () => progress?.clear(),
1343
+ });
1344
+ } catch (error) {
1345
+ const rawMessage = String(error?.message || error || "");
1346
+ const permissionIssue =
1347
+ /not writable/iu.test(rawMessage) &&
1348
+ (/homebrew/iu.test(rawMessage) || /\/opt\/homebrew/iu.test(rawMessage) || /\/usr\/local/iu.test(rawMessage));
1349
+ throw new Error(
1350
+ [
1351
+ t(locale, permissionIssue ? "cli.setup.error.mkcertInstallPermission" : "cli.setup.error.mkcertInstallFailed"),
1352
+ t(locale, "cli.setup.error.mkcertInstallNext"),
1353
+ t(locale, "cli.setup.error.mkcertInstallExample"),
1354
+ ].join("\n")
1355
+ );
1356
+ }
1343
1357
  const mkcertPath = await findExecutable("mkcert");
1344
1358
  if (!mkcertPath) {
1345
1359
  throw new Error("mkcert installation finished, but the mkcert executable is still not available.");
@@ -74,15 +74,15 @@ NOTIFY_PLANS=1
74
74
  NATIVE_APPROVALS=1
75
75
  ENABLE_NTFY=0
76
76
 
77
- # Native codex.app approval pages for iPhone.
77
+ # Native codex.app approval pages for mobile devices.
78
78
  # Use HTTPS for LAN access whenever possible. Plain HTTP over LAN requires
79
79
  # ALLOW_INSECURE_LAN_HTTP=1 and is not recommended.
80
- # Use a public LAN URL the iPhone can reach. This can be different from NTFY_PUBLISH_BASE_URL.
80
+ # Use a public LAN URL the device can reach. This can be different from NTFY_PUBLISH_BASE_URL.
81
81
  NATIVE_APPROVAL_SERVER_PUBLIC_BASE_URL=
82
82
  NATIVE_APPROVAL_SERVER_HOST=127.0.0.1
83
83
  NATIVE_APPROVAL_SERVER_PORT=8810
84
84
 
85
- # App-server runner settings for interactive iPhone approvals.
85
+ # App-server runner settings for interactive mobile approvals.
86
86
  # If empty, the runner derives the host from NTFY_BASE_URL.
87
87
  APPROVAL_SERVER_PUBLIC_BASE_URL=
88
88
  APPROVAL_SERVER_HOST=0.0.0.0
package/web/app.css CHANGED
@@ -1927,6 +1927,7 @@ code {
1927
1927
  padding: 0 0.2rem;
1928
1928
  color: var(--muted);
1929
1929
  line-height: 1.55;
1930
+ overflow-wrap: anywhere;
1930
1931
  }
1931
1932
 
1932
1933
  .detail-page-copy--approval {
package/web/app.js CHANGED
@@ -3133,6 +3133,9 @@ function renderDetailPlainIntro(detail, options = {}) {
3133
3133
  if (!["approval", "diff_thread", "file_event"].includes(detail?.kind || "")) {
3134
3134
  return "";
3135
3135
  }
3136
+ if (detail?.kind === "approval" && normalizeClientText(detail?.approvalKind || "") !== "file") {
3137
+ return "";
3138
+ }
3136
3139
  if (!detail?.messageHtml) {
3137
3140
  return "";
3138
3141
  }
package/web/i18n.js CHANGED
@@ -70,7 +70,7 @@ const translations = {
70
70
  "tab.code.title": "Code",
71
71
  "tab.code.label": "Code",
72
72
  "tab.code.eyebrow": "Current changes",
73
- "tab.code.description": "Review the current unstaged file changes from Codex in a mobile-friendly diff view.",
73
+ "tab.code.description": "Review the current unstaged file changes from Codex in a mobile-friendly diff view on your device.",
74
74
  "tab.completed.title": "Completed",
75
75
  "tab.completed.label": "Completed",
76
76
  "tab.completed.eyebrow": "History",
@@ -80,34 +80,34 @@ const translations = {
80
80
  "tab.settings.eyebrow": "Device",
81
81
  "tab.settings.description": "Pairing, installation, and notification controls.",
82
82
  "pair.copy":
83
- "Pair this iPhone with your local Codex desktop session and keep approvals, plans, choices, and completions within reach.",
83
+ "Pair this device with your local Codex desktop session and keep approvals, plans, choices, and completions within reach.",
84
84
  "pair.codeLabel": "Pairing code",
85
85
  "pair.codePlaceholder": "Enter the pairing code",
86
- "pair.connect": "Connect this iPhone",
86
+ "pair.connect": "Connect this device",
87
87
  "pair.installFromHomeScreen":
88
88
  "This pairing link is being kept for the Home Screen app. Add viveworker to your Home Screen from Safari, then open the new icon to finish pairing there.",
89
89
  "pair.helperTitle": "Add to Home Screen",
90
90
  "pair.helperCopy": "Install viveworker for the best mobile layout and Web Push support.",
91
91
  "banner.install.title": "Add viveworker to your Home Screen",
92
92
  "banner.install.copy.safari":
93
- "Install it from Safari for a cleaner iPhone layout and background notifications.",
93
+ "Install it from Safari for a cleaner mobile layout and background notifications.",
94
94
  "banner.install.copy.other": "Open this page in Safari to install viveworker to your Home Screen.",
95
95
  "banner.push.title": "Push notifications can help",
96
96
  "banner.push.copy.https": "Open viveworker over HTTPS to turn on push notifications.",
97
- "banner.push.copy.standalone": "Add viveworker to your Home Screen to receive push notifications on iPhone.",
97
+ "banner.push.copy.standalone": "Add viveworker to your Home Screen to receive push notifications on this device.",
98
98
  "banner.push.copy.denied":
99
- "Notifications are currently blocked for this app. You can re-enable them later from iPhone settings.",
99
+ "Notifications are currently blocked for this app. You can re-enable them later from your device settings.",
100
100
  "banner.push.copy.default":
101
101
  "Enable push notifications to catch approvals, plans, questions, and completions even when the app is in the background.",
102
102
  "install.guide.title": "Add to Home Screen",
103
103
  "install.guide.intro.safari":
104
- "iPhone does not let websites trigger this directly, but you can add viveworker in a few taps.",
105
- "install.guide.intro.other": "On iPhone, Home Screen installation works best from Safari.",
104
+ "This browser does not let websites trigger this directly, but you can add viveworker in a few taps.",
105
+ "install.guide.intro.other": "If Home Screen installation is unavailable here, try opening this page in Safari or another supported mobile browser.",
106
106
  "install.guide.step.openSafari": "Open this same URL in Safari.",
107
107
  "install.guide.step.tapShare": "Tap the Share button.",
108
108
  "install.guide.step.chooseAdd": "Choose Add to Home Screen.",
109
109
  "install.guide.step.tapAdd": "Tap Add, then open the new icon.",
110
- "shell.subtitle.settings": "Pairing, notifications, and installation for this iPhone.",
110
+ "shell.subtitle.settings": "Pairing, notifications, and installation for this device.",
111
111
  "shell.subtitle.detail": "Review the selected item and act without leaving your current flow.",
112
112
  "empty.pending":
113
113
  "Nothing needs attention right now. New approvals, plans, and choices will appear here first.",
@@ -147,7 +147,7 @@ const translations = {
147
147
  "reply.imageAddMore": "Add more images",
148
148
  "reply.imageReplace": "Replace image",
149
149
  "reply.imageRemove": "Remove",
150
- "reply.imageHint": "Attach up to {count} images from your iPhone and send them together with your message.",
150
+ "reply.imageHint": "Attach up to {count} images from your device and send them together with your message.",
151
151
  "reply.imageAttached": "Attached image",
152
152
  "reply.send": "Send to Codex",
153
153
  "reply.sendConfirm": "Send anyway",
@@ -225,34 +225,34 @@ const translations = {
225
225
  "diff.threadSummary": ({ count }) => `Changed ${count} ${count === 1 ? "file" : "files"}`,
226
226
  "diff.latestChange": "Latest change",
227
227
  "diff.latestChangeFallback": "Current unstaged changes are available in this thread.",
228
- "settings.intro": "Check pairing, language, install status, and Web Push health for this iPhone.",
228
+ "settings.intro": "Check pairing, language, install status, and Web Push health for this device.",
229
229
  "settings.section.overview": "Quick setup",
230
230
  "settings.section.notifications": "Notifications",
231
231
  "settings.section.install": "Install",
232
- "settings.section.device": "This iPhone",
232
+ "settings.section.device": "This device",
233
233
  "settings.group.general": "General",
234
- "settings.group.device": "This iPhone",
234
+ "settings.group.device": "This device",
235
235
  "settings.group.values": "Current settings",
236
236
  "settings.group.advanced": "Advanced",
237
237
  "settings.group.actions": "Actions",
238
238
  "settings.overview.title": "At a glance",
239
- "settings.overview.copy": "See what is ready, what still needs setup, and what to do next on this iPhone.",
239
+ "settings.overview.copy": "See what is ready, what still needs setup, and what to do next on this device.",
240
240
  "settings.summary.notifications": "Notifications",
241
241
  "settings.summary.install": "Home Screen",
242
242
  "settings.summary.pairing": "Pairing",
243
243
  "settings.nextStep.eyebrow": "Next step",
244
244
  "settings.nextStep.enableNotifications.title": "Turn on notifications",
245
- "settings.nextStep.enableNotifications.copy": "Enable Web Push on this iPhone so approvals, plans, and questions can reach you in the background.",
245
+ "settings.nextStep.enableNotifications.copy": "Enable Web Push on this device so approvals, plans, and questions can reach you in the background.",
246
246
  "settings.nextStep.serverDisabled.title": "Server setup is still missing",
247
- "settings.nextStep.serverDisabled.copy": "Web Push is not enabled on this viveworker server yet, so this iPhone cannot subscribe yet.",
247
+ "settings.nextStep.serverDisabled.copy": "Web Push is not enabled on this viveworker server yet, so this device cannot subscribe yet.",
248
248
  "settings.nextStep.openHttps.title": "Open the HTTPS version",
249
- "settings.nextStep.openHttps.copy": "This page must be opened over HTTPS before iPhone can enable notifications.",
249
+ "settings.nextStep.openHttps.copy": "This page must be opened over HTTPS before this device can enable notifications.",
250
250
  "settings.nextStep.install.title": "Add viveworker to Home Screen",
251
- "settings.nextStep.install.copy": "Install the app from Safari, then reopen it from the Home Screen before enabling notifications.",
251
+ "settings.nextStep.install.copy": "Add the app to your Home Screen, then reopen the installed app before enabling notifications.",
252
252
  "settings.nextStep.permissionBlocked.title": "Notifications are blocked",
253
- "settings.nextStep.permissionBlocked.copy": "Re-enable notifications in iPhone settings, then return here to test again.",
253
+ "settings.nextStep.permissionBlocked.copy": "Re-enable notifications in your device settings, then return here to test again.",
254
254
  "settings.nextStep.test.title": "Everything is ready",
255
- "settings.nextStep.test.copy": "Send a test notification to confirm this iPhone can receive pushes from viveworker.",
255
+ "settings.nextStep.test.copy": "Send a test notification to confirm this device can receive pushes from viveworker.",
256
256
  "settings.status.notAvailable": "Not available",
257
257
  "settings.status.unsupported": "Not supported",
258
258
  "settings.status.needsHttps": "Needs HTTPS",
@@ -270,7 +270,7 @@ const translations = {
270
270
  "settings.notifications.serverDisabled": "Web Push is not enabled on the server yet.",
271
271
  "settings.notifications.openHttps": "Open viveworker over HTTPS before enabling notifications.",
272
272
  "settings.notifications.openHomeScreen":
273
- "Open the Home Screen app before enabling notifications on iPhone.",
273
+ "Open the installed Home Screen app before enabling notifications on this device.",
274
274
  "settings.action.enableNotifications": "Enable Notifications",
275
275
  "settings.action.disableNotifications": "Disable Notifications",
276
276
  "settings.action.sendTest": "Send Test Notification",
@@ -279,9 +279,9 @@ const translations = {
279
279
  "settings.action.removeThisDevice": "Remove this device",
280
280
  "settings.install.title": "Install to Home Screen",
281
281
  "settings.install.copy":
282
- "For the best iPhone experience, install viveworker from Safari and open the Home Screen app.",
282
+ "For the best mobile experience, add viveworker to your Home Screen and open the installed app.",
283
283
  "settings.device.title": "Devices",
284
- "settings.device.copy": "Manage trusted iPhones and browsers that can open this LAN viveworker app.",
284
+ "settings.device.copy": "Manage trusted devices and browsers that can open this LAN viveworker app.",
285
285
  "settings.device.count": ({ count }) => `${count} ${count === 1 ? "device" : "devices"}`,
286
286
  "settings.device.section.current": "This device",
287
287
  "settings.device.section.other": "Other trusted devices",
@@ -289,7 +289,7 @@ const translations = {
289
289
  "settings.device.emptyCurrent": "This device is not listed right now.",
290
290
  "settings.device.emptyOther": "No other trusted devices are active.",
291
291
  "settings.device.addAnother.title": "Add another device",
292
- "settings.device.addAnother.heading": "Add a new iPhone or browser",
292
+ "settings.device.addAnother.heading": "Add a new device or browser",
293
293
  "settings.device.addAnother.copy":
294
294
  "Run this on your Mac only when you want to add another trusted device. Your main app URL stays the same.",
295
295
  "settings.device.addAnother.commandLabel": "Run on your Mac",
@@ -302,12 +302,12 @@ const translations = {
302
302
  "settings.device.mode.standalone": "Home Screen app",
303
303
  "settings.device.mode.browser": "Browser tab",
304
304
  "settings.language.title": "Language",
305
- "settings.language.copy": "Choose how this iPhone should display viveworker.",
305
+ "settings.language.copy": "Choose how this device should display viveworker.",
306
306
  "settings.pairing.title": "Pairing",
307
- "settings.pairing.copy": "This iPhone is paired with your local Codex desktop session.",
307
+ "settings.pairing.copy": "This device is paired with your local Codex desktop session.",
308
308
  "settings.pairing.connected": "Paired",
309
309
  "settings.technical.title": "Technical details",
310
- "settings.technical.copy": "Connection and notification diagnostics for this iPhone.",
310
+ "settings.technical.copy": "Connection and notification diagnostics for this device.",
311
311
  "settings.technical.copyShort": "Connection and notification diagnostics.",
312
312
  "settings.row.deviceId": "Device ID",
313
313
  "settings.row.pairedAt": "Paired",
@@ -334,11 +334,11 @@ const translations = {
334
334
  "notice.loggedOutKeepTrusted": "Logged out. This device stays trusted, so you can come back without pairing again.",
335
335
  "notice.loggedOutDeviceRemoved": "Logged out and removed this device. Pair again before using viveworker here.",
336
336
  "error.networkHint":
337
- "Try reopening the latest HTTPS URL in Safari, then re-add the Home Screen app if needed.",
337
+ "Try reopening the latest HTTPS URL in your browser, then re-add the Home Screen app if needed.",
338
338
  "error.webPushDisabled": "Web Push is not enabled on the server.",
339
339
  "error.notificationsRequireHttps": "Notifications require HTTPS.",
340
340
  "error.pushUnsupported": "This browser does not support Web Push.",
341
- "error.openHomeScreen": "On iPhone, open viveworker from the Home Screen before enabling notifications.",
341
+ "error.openHomeScreen": "Open viveworker from the Home Screen before enabling notifications on this device.",
342
342
  "error.notificationPermission": ({ status }) => `Notification permission is ${status}.`,
343
343
  "error.pushServerNotReady": "The server is not ready for Web Push.",
344
344
  "error.serviceWorkerUnavailable": "Service Worker is not available.",
@@ -383,10 +383,11 @@ const translations = {
383
383
  "server.action.reject": "Reject",
384
384
  "server.action.implement": "Implement",
385
385
  "server.message.commandApprovalNeeded": "Command approval needed.",
386
+ "server.message.commandLabel": "Command",
386
387
  "server.message.fileApprovalNeeded": "File changes need approval.",
387
388
  "server.message.commandPrefix": ({ command }) => `Command: ${command}`,
388
389
  "server.message.pathPrefix": ({ path }) => `Path: ${path}`,
389
- "server.message.approveOnMac": "Approve on Mac only: this iPhone alert mirrors codex.app.",
390
+ "server.message.approveOnMac": "Approve on Mac only: this device alert mirrors codex.app.",
390
391
  "server.message.approvalNeededInCodex": "Approval needed in Codex.",
391
392
  "server.message.extraApprovals": ({ count }) => `Extra approvals: ${count}`,
392
393
  "server.message.taskFinished": "Task finished.",
@@ -404,7 +405,7 @@ const translations = {
404
405
  "server.confirm.approve": "This approval will be confirmed and Codex will continue.",
405
406
  "server.confirm.reject": "This action will be rejected and Codex will be told not to continue.",
406
407
  "server.pushTest.title": "viveworker test notification",
407
- "server.pushTest.body": "Notifications are working on this iPhone.",
408
+ "server.pushTest.body": "Notifications are working on this device.",
408
409
  "server.page.notFoundTitle": "Not found",
409
410
  "server.page.notFoundBody": "This approval link does not exist.",
410
411
  "server.page.notFoundHint": "You can close this page safely.",
@@ -447,16 +448,16 @@ const translations = {
447
448
  "cli.setup.webPushEnabled": "Web Push: enabled (HTTPS)",
448
449
  "cli.setup.webPushDisabled": "Web Push: disabled",
449
450
  "cli.setup.completePending": "viveworker setup finished, but health has not responded yet.",
450
- "cli.setup.caFlow.title": "Before opening viveworker on iPhone, install and trust the local root certificate:",
451
- "cli.setup.caFlow.step1": "1. Open the CA download URL or scan the QR code below on your iPhone.",
452
- "cli.setup.caFlow.step2": "2. Install the rootCA.pem profile, then enable trust for it in iPhone certificate trust settings.",
451
+ "cli.setup.caFlow.title": "Before opening viveworker on your device, install and trust the local root certificate if needed:",
452
+ "cli.setup.caFlow.step1": "1. Open the CA download URL or scan the QR code below on your device.",
453
+ "cli.setup.caFlow.step2": "2. Install the rootCA.pem profile, then enable trust for it if your device requires local CA trust.",
453
454
  "cli.setup.caFlow.step3": "3. After that, press Enter here to show the viveworker app URL and pairing QR.",
454
455
  "cli.setup.instructions.https":
455
- "Open the HTTPS URL in Safari, trust your local CA on iPhone, then add the app to the Home Screen to enable notifications.",
456
+ "Open the HTTPS URL in your mobile browser, trust your local CA if needed, then add the app to the Home Screen to enable notifications.",
456
457
  "cli.setup.instructions.afterCa":
457
- "Now open the pairing URL in Safari on your iPhone, pair it if needed, then add viveworker to the Home Screen.",
458
+ "Now open the pairing URL on your device, pair it if needed, then add viveworker to the Home Screen.",
458
459
  "cli.setup.instructions.insecureHttpLan":
459
- "Open the HTTP URL in Safari only if you intentionally allowed insecure LAN access. This mode is not recommended on shared Wi-Fi.",
460
+ "Open the HTTP URL in a mobile browser only if you intentionally allowed insecure LAN access. This mode is not recommended on shared Wi-Fi.",
460
461
  "cli.setup.instructions.localOnlyHttp":
461
462
  "This setup is loopback-only HTTP for local development. Use the default HTTPS setup or --allow-insecure-http-lan if you explicitly need LAN access.",
462
463
  "logout.confirm.title": "Choose how to log out",
@@ -474,6 +475,12 @@ const translations = {
474
475
  "cli.setup.qrPairing": "Pairing QR:",
475
476
  "cli.setup.qrCaDownload": "rootCA.pem download QR (IP):",
476
477
  "cli.setup.prompt.continueToApp": "Press Enter to continue to the viveworker app URL.",
478
+ "cli.setup.error.mkcertInstallFailed": "viveworker could not install mkcert with Homebrew.",
479
+ "cli.setup.error.mkcertInstallPermission":
480
+ "Homebrew is installed, but its prefix is not writable by your current user, so viveworker could not auto-install mkcert.",
481
+ "cli.setup.error.mkcertInstallNext":
482
+ "Fix your Homebrew permissions, then rerun `npx viveworker setup --install-mkcert`, or install mkcert manually first.",
483
+ "cli.setup.error.mkcertInstallExample": "Example: brew install mkcert && mkcert -install",
477
484
  "cli.start.progress.prepare": "Loading saved viveworker config...",
478
485
  "cli.start.progress.refreshPairing": "Refreshing expired pairing credentials...",
479
486
  "cli.start.progress.launchd": "Starting the launchd service...",
@@ -582,11 +589,11 @@ const translations = {
582
589
  "tab.diff.title": "差分",
583
590
  "tab.diff.label": "差分",
584
591
  "tab.diff.eyebrow": "現在の変更",
585
- "tab.diff.description": "Codex が残している現在の未ステージ変更を iPhone で確認します。",
592
+ "tab.diff.description": "Codex が残している現在の未ステージ変更を端末で確認します。",
586
593
  "tab.code.title": "コード",
587
594
  "tab.code.label": "コード",
588
595
  "tab.code.eyebrow": "現在の変更",
589
- "tab.code.description": "Codex が残している現在の未ステージ変更を iPhone で確認します。",
596
+ "tab.code.description": "Codex が残している現在の未ステージ変更を端末で確認します。",
590
597
  "tab.completed.title": "完了",
591
598
  "tab.completed.label": "完了",
592
599
  "tab.completed.eyebrow": "履歴",
@@ -596,34 +603,34 @@ const translations = {
596
603
  "tab.settings.eyebrow": "端末",
597
604
  "tab.settings.description": "ペアリング、インストール、通知の設定です。",
598
605
  "pair.copy":
599
- "この iPhone をローカルの Codex desktop session とペアリングして、承認、プラン、選択、完了をすぐ確認できるようにします。",
606
+ "この端末をローカルの Codex desktop session とペアリングして、承認、プラン、選択、完了をすぐ確認できるようにします。",
600
607
  "pair.codeLabel": "ペアリングコード",
601
608
  "pair.codePlaceholder": "ペアリングコードを入力",
602
- "pair.connect": "この iPhone を接続",
609
+ "pair.connect": "この端末を接続",
603
610
  "pair.installFromHomeScreen":
604
611
  "この pairing link はホーム画面アプリ用に温存されています。Safari から viveworker をホーム画面に追加し、新しいアイコンを開いてそこで pairing を完了してください。",
605
612
  "pair.helperTitle": "ホーム画面に追加",
606
613
  "pair.helperCopy": "モバイル表示と Web Push を最適に使うには viveworker をインストールしてください。",
607
614
  "banner.install.title": "viveworker をホーム画面に追加",
608
615
  "banner.install.copy.safari":
609
- "Safari からインストールすると、iPhone 向け表示とバックグラウンド通知が使いやすくなります。",
616
+ "Safari からインストールすると、モバイル向け表示とバックグラウンド通知が使いやすくなります。",
610
617
  "banner.install.copy.other": "Safari でこのページを開いて、viveworker をホーム画面に追加してください。",
611
618
  "banner.push.title": "プッシュ通知を有効にすると便利です",
612
619
  "banner.push.copy.https": "プッシュ通知を使うには HTTPS の viveworker を開いてください。",
613
- "banner.push.copy.standalone": "iPhone でプッシュ通知を受けるには、viveworker をホーム画面に追加してください。",
620
+ "banner.push.copy.standalone": "この端末でプッシュ通知を受けるには、viveworker をホーム画面に追加してください。",
614
621
  "banner.push.copy.denied":
615
- "このアプリの通知は現在ブロックされています。あとで iPhone の設定から再度有効にできます。",
622
+ "このアプリの通知は現在ブロックされています。あとで端末の設定から再度有効にできます。",
616
623
  "banner.push.copy.default":
617
624
  "バックグラウンドでも承認、プラン、質問、完了に気づけるよう、プッシュ通知を有効にしてください。",
618
625
  "install.guide.title": "ホーム画面に追加",
619
626
  "install.guide.intro.safari":
620
- "iPhone ではサイト側から直接追加できませんが、数タップでホーム画面に追加できます。",
621
- "install.guide.intro.other": "iPhone では Safari からホーム画面に追加するのが最適です。",
627
+ "このブラウザではサイト側から直接追加できませんが、数タップでホーム画面に追加できます。",
628
+ "install.guide.intro.other": "ここでホーム画面に追加できない場合は、Safari など対応ブラウザでこのページを開いてください。",
622
629
  "install.guide.step.openSafari": "この URL を Safari で開きます。",
623
630
  "install.guide.step.tapShare": "共有ボタンをタップします。",
624
631
  "install.guide.step.chooseAdd": "「ホーム画面に追加」を選びます。",
625
632
  "install.guide.step.tapAdd": "「追加」を押して、新しいアイコンを開きます。",
626
- "shell.subtitle.settings": "この iPhone のペアリング、通知、インストール設定です。",
633
+ "shell.subtitle.settings": "この端末のペアリング、通知、インストール設定です。",
627
634
  "shell.subtitle.detail": "いまの流れを途切れさせずに、項目を確認して操作できます。",
628
635
  "empty.pending": "いま対応が必要な項目はありません。新しい承認、プラン、選択がここに表示されます。",
629
636
  "empty.timeline": "まだタイムライン項目はありません。新しい会話の更新がここに表示されます。",
@@ -661,7 +668,7 @@ const translations = {
661
668
  "reply.imageAddMore": "さらに画像を追加",
662
669
  "reply.imageReplace": "画像を差し替え",
663
670
  "reply.imageRemove": "削除",
664
- "reply.imageHint": "iPhone の画像を最大 {count} 枚まで添付して、メッセージと一緒に送れます。",
671
+ "reply.imageHint": "この端末の画像を最大 {count} 枚まで添付して、メッセージと一緒に送れます。",
665
672
  "reply.imageAttached": "添付した画像",
666
673
  "reply.send": "Codex に送信",
667
674
  "reply.sendConfirm": "それでも送信",
@@ -739,34 +746,34 @@ const translations = {
739
746
  "diff.threadSummary": ({ count }) => `変更ファイル ${count}件`,
740
747
  "diff.latestChange": "最終変更",
741
748
  "diff.latestChangeFallback": "このスレッドに現在未ステージの変更があります。",
742
- "settings.intro": "この iPhone のペアリング、言語、インストール状況、Web Push の状態を確認できます。",
749
+ "settings.intro": "この端末のペアリング、言語、インストール状況、Web Push の状態を確認できます。",
743
750
  "settings.section.overview": "クイック確認",
744
751
  "settings.section.notifications": "通知",
745
752
  "settings.section.install": "インストール",
746
- "settings.section.device": "この iPhone",
753
+ "settings.section.device": "この端末",
747
754
  "settings.group.general": "一般",
748
- "settings.group.device": "この iPhone",
755
+ "settings.group.device": "この端末",
749
756
  "settings.group.values": "設定値",
750
757
  "settings.group.advanced": "詳細設定",
751
758
  "settings.group.actions": "操作",
752
759
  "settings.overview.title": "ひと目で確認",
753
- "settings.overview.copy": "この iPhone で準備できていること、まだ必要な設定、次にやることをまとめて確認できます。",
760
+ "settings.overview.copy": "この端末で準備できていること、まだ必要な設定、次にやることをまとめて確認できます。",
754
761
  "settings.summary.notifications": "通知",
755
762
  "settings.summary.install": "ホーム画面",
756
763
  "settings.summary.pairing": "ペアリング",
757
764
  "settings.nextStep.eyebrow": "次の一手",
758
765
  "settings.nextStep.enableNotifications.title": "通知を有効にする",
759
- "settings.nextStep.enableNotifications.copy": "この iPhone で Web Push を有効にすると、承認や質問をバックグラウンドでも受け取れます。",
766
+ "settings.nextStep.enableNotifications.copy": "この端末で Web Push を有効にすると、承認や質問をバックグラウンドでも受け取れます。",
760
767
  "settings.nextStep.serverDisabled.title": "サーバー側の設定がまだ必要です",
761
- "settings.nextStep.serverDisabled.copy": "この viveworker サーバーでは Web Push がまだ有効ではないため、この iPhone からは購読できません。",
768
+ "settings.nextStep.serverDisabled.copy": "この viveworker サーバーでは Web Push がまだ有効ではないため、この端末からは購読できません。",
762
769
  "settings.nextStep.openHttps.title": "HTTPS 版を開く",
763
- "settings.nextStep.openHttps.copy": "iPhone で通知を有効にするには、このページを HTTPS で開く必要があります。",
770
+ "settings.nextStep.openHttps.copy": "この端末で通知を有効にするには、このページを HTTPS で開く必要があります。",
764
771
  "settings.nextStep.install.title": "ホーム画面に追加する",
765
- "settings.nextStep.install.copy": "Safari からアプリを追加し、ホーム画面版を開き直してから通知を有効にしてください。",
772
+ "settings.nextStep.install.copy": "アプリをホーム画面に追加し、インストール版を開き直してから通知を有効にしてください。",
766
773
  "settings.nextStep.permissionBlocked.title": "通知がブロックされています",
767
- "settings.nextStep.permissionBlocked.copy": "iPhone の設定で通知を再許可してから、ここへ戻ってもう一度確認してください。",
774
+ "settings.nextStep.permissionBlocked.copy": "端末の設定で通知を再許可してから、ここへ戻ってもう一度確認してください。",
768
775
  "settings.nextStep.test.title": "準備は完了です",
769
- "settings.nextStep.test.copy": "テスト通知を送って、この iPhone が viveworker の push を受け取れるか確認できます。",
776
+ "settings.nextStep.test.copy": "テスト通知を送って、この端末が viveworker の push を受け取れるか確認できます。",
770
777
  "settings.status.notAvailable": "未対応",
771
778
  "settings.status.unsupported": "非対応",
772
779
  "settings.status.needsHttps": "HTTPS が必要",
@@ -784,7 +791,7 @@ const translations = {
784
791
  "settings.notifications.serverDisabled": "サーバー側で Web Push がまだ有効化されていません。",
785
792
  "settings.notifications.openHttps": "通知を有効にする前に、HTTPS の viveworker を開いてください。",
786
793
  "settings.notifications.openHomeScreen":
787
- "iPhone では通知を有効にする前に、ホーム画面に追加したアプリを開いてください。",
794
+ "この端末で通知を有効にする前に、ホーム画面に追加したアプリを開いてください。",
788
795
  "settings.action.enableNotifications": "通知を有効化",
789
796
  "settings.action.disableNotifications": "通知を無効化",
790
797
  "settings.action.sendTest": "テスト通知を送信",
@@ -793,7 +800,7 @@ const translations = {
793
800
  "settings.action.removeThisDevice": "この端末を削除",
794
801
  "settings.install.title": "ホーム画面に追加",
795
802
  "settings.install.copy":
796
- "iPhone で最も使いやすい体験にするには、Safari から viveworker をホーム画面に追加してください。",
803
+ "いちばん使いやすい体験にするには、viveworker をホーム画面に追加して、インストール版を開いてください。",
797
804
  "settings.device.title": "Devices",
798
805
  "settings.device.copy": "この LAN 上で viveworker を開ける信頼済み端末を管理します。",
799
806
  "settings.device.count": ({ count }) => `${count} 台の端末`,
@@ -803,7 +810,7 @@ const translations = {
803
810
  "settings.device.emptyCurrent": "この端末の情報はまだ表示できません。",
804
811
  "settings.device.emptyOther": "他に有効な信頼済み端末はありません。",
805
812
  "settings.device.addAnother.title": "端末を追加する",
806
- "settings.device.addAnother.heading": "新しい iPhone / ブラウザを追加",
813
+ "settings.device.addAnother.heading": "新しい端末 / ブラウザを追加",
807
814
  "settings.device.addAnother.copy":
808
815
  "別の端末を追加したいときだけ、Mac で次のコマンドを実行します。通常の app URL は変わりません。",
809
816
  "settings.device.addAnother.commandLabel": "Mac で実行",
@@ -816,12 +823,12 @@ const translations = {
816
823
  "settings.device.mode.standalone": "ホーム画面アプリ",
817
824
  "settings.device.mode.browser": "ブラウザ",
818
825
  "settings.language.title": "言語",
819
- "settings.language.copy": "この iPhone で viveworker を表示する言語を選びます。",
826
+ "settings.language.copy": "この端末で viveworker を表示する言語を選びます。",
820
827
  "settings.pairing.title": "ペアリング",
821
- "settings.pairing.copy": "この iPhone はローカルの Codex desktop session とペアリングされています。",
828
+ "settings.pairing.copy": "この端末はローカルの Codex desktop session とペアリングされています。",
822
829
  "settings.pairing.connected": "ペアリング済み",
823
830
  "settings.technical.title": "技術情報",
824
- "settings.technical.copy": "この iPhone の接続状態や通知状態を確認できます。",
831
+ "settings.technical.copy": "この端末の接続状態や通知状態を確認できます。",
825
832
  "settings.technical.copyShort": "接続や通知の診断情報です。",
826
833
  "settings.row.deviceId": "Device ID",
827
834
  "settings.row.pairedAt": "初回ペアリング",
@@ -848,11 +855,11 @@ const translations = {
848
855
  "notice.loggedOutKeepTrusted": "ログアウトしました。この端末の信頼は維持されるので、次回は再 pairing なしで戻れます。",
849
856
  "notice.loggedOutDeviceRemoved": "ログアウトし、この端末の信頼も削除しました。次回は再 pairing が必要です。",
850
857
  "error.networkHint":
851
- "最新の HTTPS URL を Safari で開き直して、必要ならホーム画面アプリを追加し直してください。",
858
+ "最新の HTTPS URL をブラウザで開き直して、必要ならホーム画面アプリを追加し直してください。",
852
859
  "error.webPushDisabled": "サーバー側で Web Push が有効ではありません。",
853
860
  "error.notificationsRequireHttps": "通知には HTTPS が必要です。",
854
861
  "error.pushUnsupported": "このブラウザは Web Push に対応していません。",
855
- "error.openHomeScreen": "iPhone では通知を有効にする前に、ホーム画面の viveworker を開いてください。",
862
+ "error.openHomeScreen": "この端末で通知を有効にする前に、ホーム画面の viveworker を開いてください。",
856
863
  "error.notificationPermission": ({ status }) => `通知権限は ${status} です。`,
857
864
  "error.pushServerNotReady": "サーバー側で Web Push の準備ができていません。",
858
865
  "error.serviceWorkerUnavailable": "Service Worker は利用できません。",
@@ -897,10 +904,11 @@ const translations = {
897
904
  "server.action.reject": "拒否",
898
905
  "server.action.implement": "実装する",
899
906
  "server.message.commandApprovalNeeded": "コマンド実行の承認が必要です。",
907
+ "server.message.commandLabel": "コマンド",
900
908
  "server.message.fileApprovalNeeded": "ファイル変更の承認が必要です。",
901
909
  "server.message.commandPrefix": ({ command }) => `Command: ${command}`,
902
910
  "server.message.pathPrefix": ({ path }) => `Path: ${path}`,
903
- "server.message.approveOnMac": "Mac で承認してください: この iPhone 通知は codex.app の鏡像です。",
911
+ "server.message.approveOnMac": "Mac で承認してください: この端末の通知は codex.app の鏡像です。",
904
912
  "server.message.approvalNeededInCodex": "Codex で承認が必要です。",
905
913
  "server.message.extraApprovals": ({ count }) => `追加の承認: ${count}`,
906
914
  "server.message.taskFinished": "タスクが完了しました。",
@@ -918,7 +926,7 @@ const translations = {
918
926
  "server.confirm.approve": "この承認を確定して Codex を続行します。",
919
927
  "server.confirm.reject": "この操作を拒否して Codex に続行しないよう伝えます。",
920
928
  "server.pushTest.title": "viveworker テスト通知",
921
- "server.pushTest.body": "この iPhone で通知が動作しています。",
929
+ "server.pushTest.body": "この端末で通知が動作しています。",
922
930
  "server.page.notFoundTitle": "見つかりません",
923
931
  "server.page.notFoundBody": "この承認リンクは存在しません。",
924
932
  "server.page.notFoundHint": "このページは閉じて大丈夫です。",
@@ -961,18 +969,18 @@ const translations = {
961
969
  "cli.setup.webPushEnabled": "Web Push: enabled (HTTPS)",
962
970
  "cli.setup.webPushDisabled": "Web Push: disabled",
963
971
  "cli.setup.completePending": "viveworker の setup は完了しましたが、まだ health 応答は確認できていません。",
964
- "cli.setup.caFlow.title": "iPhone viveworker を開く前に、まずローカルの証明書をインストールしてください:",
965
- "cli.setup.caFlow.step1": "1. 下の CA download URL を開くか QR を読み取り、iPhone rootCA.pem を取得します。",
966
- "cli.setup.caFlow.step2": "2. rootCA.pem をインストールし、iPhone の証明書信頼設定でその証明書を信頼します。",
972
+ "cli.setup.caFlow.title": "端末で viveworker を開く前に、必要ならローカルの証明書をインストールしてください:",
973
+ "cli.setup.caFlow.step1": "1. 下の CA download URL を開くか QR を読み取り、端末で rootCA.pem を取得します。",
974
+ "cli.setup.caFlow.step2": "2. rootCA.pem をインストールし、必要なら端末側でその証明書を信頼します。",
967
975
  "cli.setup.caFlow.step3": "3. そのあと、このターミナルで Enter を押すと viveworker の接続先 URL と Pairing QR を表示します。",
968
976
  "cli.setup.instructions.https":
969
- "Safari HTTPS URL を開き、iPhone でローカル CA を信頼してから、ホーム画面に追加して通知を有効にしてください。",
977
+ "ブラウザで HTTPS URL を開き、必要ならローカル CA を信頼してから、ホーム画面に追加して通知を有効にしてください。",
970
978
  "cli.setup.instructions.afterCa":
971
- "次に iPhone の Safari で Pairing URL を開き、必要ならペアリングしてから、viveworker をホーム画面に追加してください。",
979
+ "次に端末のブラウザで Pairing URL を開き、必要ならペアリングしてから、viveworker をホーム画面に追加してください。",
972
980
  "cli.setup.instructions.insecureHttpLan":
973
- "HTTP URL は、LAN 上の平文アクセスを明示的に許可した場合だけ Safari で開いてください。共有 Wi-Fi では非推奨です。",
981
+ "HTTP URL は、LAN 上の平文アクセスを明示的に許可した場合だけブラウザで開いてください。共有 Wi-Fi では非推奨です。",
974
982
  "cli.setup.instructions.localOnlyHttp":
975
- "この setup はローカル開発用の loopback-only HTTP です。iPhone から使うなら既定の HTTPS setup か --allow-insecure-http-lan を使ってください。",
983
+ "この setup はローカル開発用の loopback-only HTTP です。別の端末から使うなら既定の HTTPS setup か --allow-insecure-http-lan を使ってください。",
976
984
  "logout.confirm.title": "ログアウト方法を選んでください",
977
985
  "logout.confirm.copy": "session だけ終えることも、この端末の信頼ごと削除して次回 pairing 必須にすることもできます。",
978
986
  "logout.confirm.keepTrustedTitle": "ログアウトのみ",
@@ -988,6 +996,12 @@ const translations = {
988
996
  "cli.setup.qrPairing": "Pairing QR:",
989
997
  "cli.setup.qrCaDownload": "rootCA.pem download QR (IP):",
990
998
  "cli.setup.prompt.continueToApp": "viveworker の接続先 URL を表示するには Enter を押してください。",
999
+ "cli.setup.error.mkcertInstallFailed": "Homebrew で mkcert をインストールできませんでした。",
1000
+ "cli.setup.error.mkcertInstallPermission":
1001
+ "Homebrew は見つかりましたが、その prefix に現在のユーザーの書き込み権限がないため、viveworker が mkcert を自動インストールできませんでした。",
1002
+ "cli.setup.error.mkcertInstallNext":
1003
+ "`npx viveworker setup --install-mkcert` をやり直す前に Homebrew の権限を直すか、先に mkcert を手動でインストールしてください。",
1004
+ "cli.setup.error.mkcertInstallExample": "例: brew install mkcert && mkcert -install",
991
1005
  "cli.start.progress.prepare": "保存済みの viveworker 設定を読み込んでいます...",
992
1006
  "cli.start.progress.refreshPairing": "期限切れの pairing 情報を更新しています...",
993
1007
  "cli.start.progress.launchd": "launchd サービスを起動しています...",