viveworker 0.1.9 → 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.9",
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,
@@ -12477,6 +12497,22 @@ function extractRolloutMessageText(content) {
12477
12497
  );
12478
12498
  }
12479
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
+
12480
12516
  function rolloutContentHasImages(content) {
12481
12517
  if (!Array.isArray(content)) {
12482
12518
  return false;
@@ -12592,6 +12628,22 @@ async function extractRolloutThreadMetadata(filePath) {
12592
12628
  break;
12593
12629
  }
12594
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
+ }
12595
12647
  }
12596
12648
  }
12597
12649
  } catch {
@@ -12768,11 +12820,12 @@ function refreshResolvedThreadLabels({ config, runtime, state }) {
12768
12820
  if (!conversationId) {
12769
12821
  return item;
12770
12822
  }
12771
- const threadLabel = getNativeThreadLabel({
12823
+ const nativeThreadLabel = getNativeThreadLabel({
12772
12824
  runtime,
12773
12825
  conversationId,
12774
12826
  cwd: "",
12775
12827
  });
12828
+ const threadLabel = preferTitleOnlyJsonThreadLabel(nativeThreadLabel, conversationId, item.messageText, item.summary);
12776
12829
  const title = formatTitle(kindTitle(config.defaultLocale, item.kind), threadLabel);
12777
12830
  if (threadLabel === item.threadLabel && title === item.title) {
12778
12831
  return item;
@@ -12802,11 +12855,12 @@ function refreshResolvedThreadLabels({ config, runtime, state }) {
12802
12855
  if (!threadId) {
12803
12856
  return entry;
12804
12857
  }
12805
- const threadLabel = getNativeThreadLabel({
12858
+ const nativeThreadLabel = getNativeThreadLabel({
12806
12859
  runtime,
12807
12860
  conversationId: threadId,
12808
12861
  cwd: "",
12809
12862
  });
12863
+ const threadLabel = preferTitleOnlyJsonThreadLabel(nativeThreadLabel, threadId, entry.messageText, entry.summary);
12810
12864
  const title = threadLabel || kindTitle(config.defaultLocale, entry.kind);
12811
12865
  if (threadLabel === entry.threadLabel && title === entry.title) {
12812
12866
  return entry;
@@ -12836,11 +12890,12 @@ function refreshResolvedThreadLabels({ config, runtime, state }) {
12836
12890
  if (!threadId) {
12837
12891
  return entry;
12838
12892
  }
12839
- const threadLabel = getNativeThreadLabel({
12893
+ const nativeThreadLabel = getNativeThreadLabel({
12840
12894
  runtime,
12841
12895
  conversationId: threadId,
12842
12896
  cwd: "",
12843
12897
  });
12898
+ const threadLabel = preferTitleOnlyJsonThreadLabel(nativeThreadLabel, threadId, entry.messageText, entry.summary);
12844
12899
  const title = threadLabel || kindTitle(config.defaultLocale, entry.kind);
12845
12900
  if (threadLabel === entry.threadLabel && title === entry.title) {
12846
12901
  return entry;
@@ -12902,6 +12957,10 @@ function summarizeNotificationText(value) {
12902
12957
  }
12903
12958
 
12904
12959
  function normalizeLongText(value) {
12960
+ const titleOnlyJsonTitle = extractTitleOnlyJsonTitle(value);
12961
+ if (titleOnlyJsonTitle) {
12962
+ return titleOnlyJsonTitle;
12963
+ }
12905
12964
  return String(stripEnvironmentContextBlocks(stripMarkdownLinks(value)) || "")
12906
12965
  .replace(/\r\n/gu, "\n")
12907
12966
  .replace(/[ \t]+\n/gu, "\n")
@@ -13013,6 +13072,53 @@ function cleanText(value) {
13013
13072
  return String(value || "").replace(/\s+/gu, " ").trim();
13014
13073
  }
13015
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
+
13016
13122
  function stripMarkdownLinks(value) {
13017
13123
  return String(value || "").replace(/\[([^\]]+)\]\(([^)]+)\)/gu, "$1");
13018
13124
  }
@@ -13227,6 +13333,8 @@ async function main() {
13227
13333
  }
13228
13334
 
13229
13335
  if (
13336
+ normalizedHistoryStateChanged ||
13337
+ normalizedTimelineStateChanged ||
13230
13338
  migratedPairedDevicesStateChanged ||
13231
13339
  restoredPendingPlanStateChanged ||
13232
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/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.",
@@ -387,7 +387,7 @@ const translations = {
387
387
  "server.message.fileApprovalNeeded": "File changes need approval.",
388
388
  "server.message.commandPrefix": ({ command }) => `Command: ${command}`,
389
389
  "server.message.pathPrefix": ({ path }) => `Path: ${path}`,
390
- "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.",
391
391
  "server.message.approvalNeededInCodex": "Approval needed in Codex.",
392
392
  "server.message.extraApprovals": ({ count }) => `Extra approvals: ${count}`,
393
393
  "server.message.taskFinished": "Task finished.",
@@ -405,7 +405,7 @@ const translations = {
405
405
  "server.confirm.approve": "This approval will be confirmed and Codex will continue.",
406
406
  "server.confirm.reject": "This action will be rejected and Codex will be told not to continue.",
407
407
  "server.pushTest.title": "viveworker test notification",
408
- "server.pushTest.body": "Notifications are working on this iPhone.",
408
+ "server.pushTest.body": "Notifications are working on this device.",
409
409
  "server.page.notFoundTitle": "Not found",
410
410
  "server.page.notFoundBody": "This approval link does not exist.",
411
411
  "server.page.notFoundHint": "You can close this page safely.",
@@ -448,16 +448,16 @@ const translations = {
448
448
  "cli.setup.webPushEnabled": "Web Push: enabled (HTTPS)",
449
449
  "cli.setup.webPushDisabled": "Web Push: disabled",
450
450
  "cli.setup.completePending": "viveworker setup finished, but health has not responded yet.",
451
- "cli.setup.caFlow.title": "Before opening viveworker on iPhone, install and trust the local root certificate:",
452
- "cli.setup.caFlow.step1": "1. Open the CA download URL or scan the QR code below on your iPhone.",
453
- "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.",
454
454
  "cli.setup.caFlow.step3": "3. After that, press Enter here to show the viveworker app URL and pairing QR.",
455
455
  "cli.setup.instructions.https":
456
- "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.",
457
457
  "cli.setup.instructions.afterCa":
458
- "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.",
459
459
  "cli.setup.instructions.insecureHttpLan":
460
- "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.",
461
461
  "cli.setup.instructions.localOnlyHttp":
462
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.",
463
463
  "logout.confirm.title": "Choose how to log out",
@@ -475,6 +475,12 @@ const translations = {
475
475
  "cli.setup.qrPairing": "Pairing QR:",
476
476
  "cli.setup.qrCaDownload": "rootCA.pem download QR (IP):",
477
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",
478
484
  "cli.start.progress.prepare": "Loading saved viveworker config...",
479
485
  "cli.start.progress.refreshPairing": "Refreshing expired pairing credentials...",
480
486
  "cli.start.progress.launchd": "Starting the launchd service...",
@@ -583,11 +589,11 @@ const translations = {
583
589
  "tab.diff.title": "差分",
584
590
  "tab.diff.label": "差分",
585
591
  "tab.diff.eyebrow": "現在の変更",
586
- "tab.diff.description": "Codex が残している現在の未ステージ変更を iPhone で確認します。",
592
+ "tab.diff.description": "Codex が残している現在の未ステージ変更を端末で確認します。",
587
593
  "tab.code.title": "コード",
588
594
  "tab.code.label": "コード",
589
595
  "tab.code.eyebrow": "現在の変更",
590
- "tab.code.description": "Codex が残している現在の未ステージ変更を iPhone で確認します。",
596
+ "tab.code.description": "Codex が残している現在の未ステージ変更を端末で確認します。",
591
597
  "tab.completed.title": "完了",
592
598
  "tab.completed.label": "完了",
593
599
  "tab.completed.eyebrow": "履歴",
@@ -597,34 +603,34 @@ const translations = {
597
603
  "tab.settings.eyebrow": "端末",
598
604
  "tab.settings.description": "ペアリング、インストール、通知の設定です。",
599
605
  "pair.copy":
600
- "この iPhone をローカルの Codex desktop session とペアリングして、承認、プラン、選択、完了をすぐ確認できるようにします。",
606
+ "この端末をローカルの Codex desktop session とペアリングして、承認、プラン、選択、完了をすぐ確認できるようにします。",
601
607
  "pair.codeLabel": "ペアリングコード",
602
608
  "pair.codePlaceholder": "ペアリングコードを入力",
603
- "pair.connect": "この iPhone を接続",
609
+ "pair.connect": "この端末を接続",
604
610
  "pair.installFromHomeScreen":
605
611
  "この pairing link はホーム画面アプリ用に温存されています。Safari から viveworker をホーム画面に追加し、新しいアイコンを開いてそこで pairing を完了してください。",
606
612
  "pair.helperTitle": "ホーム画面に追加",
607
613
  "pair.helperCopy": "モバイル表示と Web Push を最適に使うには viveworker をインストールしてください。",
608
614
  "banner.install.title": "viveworker をホーム画面に追加",
609
615
  "banner.install.copy.safari":
610
- "Safari からインストールすると、iPhone 向け表示とバックグラウンド通知が使いやすくなります。",
616
+ "Safari からインストールすると、モバイル向け表示とバックグラウンド通知が使いやすくなります。",
611
617
  "banner.install.copy.other": "Safari でこのページを開いて、viveworker をホーム画面に追加してください。",
612
618
  "banner.push.title": "プッシュ通知を有効にすると便利です",
613
619
  "banner.push.copy.https": "プッシュ通知を使うには HTTPS の viveworker を開いてください。",
614
- "banner.push.copy.standalone": "iPhone でプッシュ通知を受けるには、viveworker をホーム画面に追加してください。",
620
+ "banner.push.copy.standalone": "この端末でプッシュ通知を受けるには、viveworker をホーム画面に追加してください。",
615
621
  "banner.push.copy.denied":
616
- "このアプリの通知は現在ブロックされています。あとで iPhone の設定から再度有効にできます。",
622
+ "このアプリの通知は現在ブロックされています。あとで端末の設定から再度有効にできます。",
617
623
  "banner.push.copy.default":
618
624
  "バックグラウンドでも承認、プラン、質問、完了に気づけるよう、プッシュ通知を有効にしてください。",
619
625
  "install.guide.title": "ホーム画面に追加",
620
626
  "install.guide.intro.safari":
621
- "iPhone ではサイト側から直接追加できませんが、数タップでホーム画面に追加できます。",
622
- "install.guide.intro.other": "iPhone では Safari からホーム画面に追加するのが最適です。",
627
+ "このブラウザではサイト側から直接追加できませんが、数タップでホーム画面に追加できます。",
628
+ "install.guide.intro.other": "ここでホーム画面に追加できない場合は、Safari など対応ブラウザでこのページを開いてください。",
623
629
  "install.guide.step.openSafari": "この URL を Safari で開きます。",
624
630
  "install.guide.step.tapShare": "共有ボタンをタップします。",
625
631
  "install.guide.step.chooseAdd": "「ホーム画面に追加」を選びます。",
626
632
  "install.guide.step.tapAdd": "「追加」を押して、新しいアイコンを開きます。",
627
- "shell.subtitle.settings": "この iPhone のペアリング、通知、インストール設定です。",
633
+ "shell.subtitle.settings": "この端末のペアリング、通知、インストール設定です。",
628
634
  "shell.subtitle.detail": "いまの流れを途切れさせずに、項目を確認して操作できます。",
629
635
  "empty.pending": "いま対応が必要な項目はありません。新しい承認、プラン、選択がここに表示されます。",
630
636
  "empty.timeline": "まだタイムライン項目はありません。新しい会話の更新がここに表示されます。",
@@ -662,7 +668,7 @@ const translations = {
662
668
  "reply.imageAddMore": "さらに画像を追加",
663
669
  "reply.imageReplace": "画像を差し替え",
664
670
  "reply.imageRemove": "削除",
665
- "reply.imageHint": "iPhone の画像を最大 {count} 枚まで添付して、メッセージと一緒に送れます。",
671
+ "reply.imageHint": "この端末の画像を最大 {count} 枚まで添付して、メッセージと一緒に送れます。",
666
672
  "reply.imageAttached": "添付した画像",
667
673
  "reply.send": "Codex に送信",
668
674
  "reply.sendConfirm": "それでも送信",
@@ -740,34 +746,34 @@ const translations = {
740
746
  "diff.threadSummary": ({ count }) => `変更ファイル ${count}件`,
741
747
  "diff.latestChange": "最終変更",
742
748
  "diff.latestChangeFallback": "このスレッドに現在未ステージの変更があります。",
743
- "settings.intro": "この iPhone のペアリング、言語、インストール状況、Web Push の状態を確認できます。",
749
+ "settings.intro": "この端末のペアリング、言語、インストール状況、Web Push の状態を確認できます。",
744
750
  "settings.section.overview": "クイック確認",
745
751
  "settings.section.notifications": "通知",
746
752
  "settings.section.install": "インストール",
747
- "settings.section.device": "この iPhone",
753
+ "settings.section.device": "この端末",
748
754
  "settings.group.general": "一般",
749
- "settings.group.device": "この iPhone",
755
+ "settings.group.device": "この端末",
750
756
  "settings.group.values": "設定値",
751
757
  "settings.group.advanced": "詳細設定",
752
758
  "settings.group.actions": "操作",
753
759
  "settings.overview.title": "ひと目で確認",
754
- "settings.overview.copy": "この iPhone で準備できていること、まだ必要な設定、次にやることをまとめて確認できます。",
760
+ "settings.overview.copy": "この端末で準備できていること、まだ必要な設定、次にやることをまとめて確認できます。",
755
761
  "settings.summary.notifications": "通知",
756
762
  "settings.summary.install": "ホーム画面",
757
763
  "settings.summary.pairing": "ペアリング",
758
764
  "settings.nextStep.eyebrow": "次の一手",
759
765
  "settings.nextStep.enableNotifications.title": "通知を有効にする",
760
- "settings.nextStep.enableNotifications.copy": "この iPhone で Web Push を有効にすると、承認や質問をバックグラウンドでも受け取れます。",
766
+ "settings.nextStep.enableNotifications.copy": "この端末で Web Push を有効にすると、承認や質問をバックグラウンドでも受け取れます。",
761
767
  "settings.nextStep.serverDisabled.title": "サーバー側の設定がまだ必要です",
762
- "settings.nextStep.serverDisabled.copy": "この viveworker サーバーでは Web Push がまだ有効ではないため、この iPhone からは購読できません。",
768
+ "settings.nextStep.serverDisabled.copy": "この viveworker サーバーでは Web Push がまだ有効ではないため、この端末からは購読できません。",
763
769
  "settings.nextStep.openHttps.title": "HTTPS 版を開く",
764
- "settings.nextStep.openHttps.copy": "iPhone で通知を有効にするには、このページを HTTPS で開く必要があります。",
770
+ "settings.nextStep.openHttps.copy": "この端末で通知を有効にするには、このページを HTTPS で開く必要があります。",
765
771
  "settings.nextStep.install.title": "ホーム画面に追加する",
766
- "settings.nextStep.install.copy": "Safari からアプリを追加し、ホーム画面版を開き直してから通知を有効にしてください。",
772
+ "settings.nextStep.install.copy": "アプリをホーム画面に追加し、インストール版を開き直してから通知を有効にしてください。",
767
773
  "settings.nextStep.permissionBlocked.title": "通知がブロックされています",
768
- "settings.nextStep.permissionBlocked.copy": "iPhone の設定で通知を再許可してから、ここへ戻ってもう一度確認してください。",
774
+ "settings.nextStep.permissionBlocked.copy": "端末の設定で通知を再許可してから、ここへ戻ってもう一度確認してください。",
769
775
  "settings.nextStep.test.title": "準備は完了です",
770
- "settings.nextStep.test.copy": "テスト通知を送って、この iPhone が viveworker の push を受け取れるか確認できます。",
776
+ "settings.nextStep.test.copy": "テスト通知を送って、この端末が viveworker の push を受け取れるか確認できます。",
771
777
  "settings.status.notAvailable": "未対応",
772
778
  "settings.status.unsupported": "非対応",
773
779
  "settings.status.needsHttps": "HTTPS が必要",
@@ -785,7 +791,7 @@ const translations = {
785
791
  "settings.notifications.serverDisabled": "サーバー側で Web Push がまだ有効化されていません。",
786
792
  "settings.notifications.openHttps": "通知を有効にする前に、HTTPS の viveworker を開いてください。",
787
793
  "settings.notifications.openHomeScreen":
788
- "iPhone では通知を有効にする前に、ホーム画面に追加したアプリを開いてください。",
794
+ "この端末で通知を有効にする前に、ホーム画面に追加したアプリを開いてください。",
789
795
  "settings.action.enableNotifications": "通知を有効化",
790
796
  "settings.action.disableNotifications": "通知を無効化",
791
797
  "settings.action.sendTest": "テスト通知を送信",
@@ -794,7 +800,7 @@ const translations = {
794
800
  "settings.action.removeThisDevice": "この端末を削除",
795
801
  "settings.install.title": "ホーム画面に追加",
796
802
  "settings.install.copy":
797
- "iPhone で最も使いやすい体験にするには、Safari から viveworker をホーム画面に追加してください。",
803
+ "いちばん使いやすい体験にするには、viveworker をホーム画面に追加して、インストール版を開いてください。",
798
804
  "settings.device.title": "Devices",
799
805
  "settings.device.copy": "この LAN 上で viveworker を開ける信頼済み端末を管理します。",
800
806
  "settings.device.count": ({ count }) => `${count} 台の端末`,
@@ -804,7 +810,7 @@ const translations = {
804
810
  "settings.device.emptyCurrent": "この端末の情報はまだ表示できません。",
805
811
  "settings.device.emptyOther": "他に有効な信頼済み端末はありません。",
806
812
  "settings.device.addAnother.title": "端末を追加する",
807
- "settings.device.addAnother.heading": "新しい iPhone / ブラウザを追加",
813
+ "settings.device.addAnother.heading": "新しい端末 / ブラウザを追加",
808
814
  "settings.device.addAnother.copy":
809
815
  "別の端末を追加したいときだけ、Mac で次のコマンドを実行します。通常の app URL は変わりません。",
810
816
  "settings.device.addAnother.commandLabel": "Mac で実行",
@@ -817,12 +823,12 @@ const translations = {
817
823
  "settings.device.mode.standalone": "ホーム画面アプリ",
818
824
  "settings.device.mode.browser": "ブラウザ",
819
825
  "settings.language.title": "言語",
820
- "settings.language.copy": "この iPhone で viveworker を表示する言語を選びます。",
826
+ "settings.language.copy": "この端末で viveworker を表示する言語を選びます。",
821
827
  "settings.pairing.title": "ペアリング",
822
- "settings.pairing.copy": "この iPhone はローカルの Codex desktop session とペアリングされています。",
828
+ "settings.pairing.copy": "この端末はローカルの Codex desktop session とペアリングされています。",
823
829
  "settings.pairing.connected": "ペアリング済み",
824
830
  "settings.technical.title": "技術情報",
825
- "settings.technical.copy": "この iPhone の接続状態や通知状態を確認できます。",
831
+ "settings.technical.copy": "この端末の接続状態や通知状態を確認できます。",
826
832
  "settings.technical.copyShort": "接続や通知の診断情報です。",
827
833
  "settings.row.deviceId": "Device ID",
828
834
  "settings.row.pairedAt": "初回ペアリング",
@@ -849,11 +855,11 @@ const translations = {
849
855
  "notice.loggedOutKeepTrusted": "ログアウトしました。この端末の信頼は維持されるので、次回は再 pairing なしで戻れます。",
850
856
  "notice.loggedOutDeviceRemoved": "ログアウトし、この端末の信頼も削除しました。次回は再 pairing が必要です。",
851
857
  "error.networkHint":
852
- "最新の HTTPS URL を Safari で開き直して、必要ならホーム画面アプリを追加し直してください。",
858
+ "最新の HTTPS URL をブラウザで開き直して、必要ならホーム画面アプリを追加し直してください。",
853
859
  "error.webPushDisabled": "サーバー側で Web Push が有効ではありません。",
854
860
  "error.notificationsRequireHttps": "通知には HTTPS が必要です。",
855
861
  "error.pushUnsupported": "このブラウザは Web Push に対応していません。",
856
- "error.openHomeScreen": "iPhone では通知を有効にする前に、ホーム画面の viveworker を開いてください。",
862
+ "error.openHomeScreen": "この端末で通知を有効にする前に、ホーム画面の viveworker を開いてください。",
857
863
  "error.notificationPermission": ({ status }) => `通知権限は ${status} です。`,
858
864
  "error.pushServerNotReady": "サーバー側で Web Push の準備ができていません。",
859
865
  "error.serviceWorkerUnavailable": "Service Worker は利用できません。",
@@ -902,7 +908,7 @@ const translations = {
902
908
  "server.message.fileApprovalNeeded": "ファイル変更の承認が必要です。",
903
909
  "server.message.commandPrefix": ({ command }) => `Command: ${command}`,
904
910
  "server.message.pathPrefix": ({ path }) => `Path: ${path}`,
905
- "server.message.approveOnMac": "Mac で承認してください: この iPhone 通知は codex.app の鏡像です。",
911
+ "server.message.approveOnMac": "Mac で承認してください: この端末の通知は codex.app の鏡像です。",
906
912
  "server.message.approvalNeededInCodex": "Codex で承認が必要です。",
907
913
  "server.message.extraApprovals": ({ count }) => `追加の承認: ${count}`,
908
914
  "server.message.taskFinished": "タスクが完了しました。",
@@ -920,7 +926,7 @@ const translations = {
920
926
  "server.confirm.approve": "この承認を確定して Codex を続行します。",
921
927
  "server.confirm.reject": "この操作を拒否して Codex に続行しないよう伝えます。",
922
928
  "server.pushTest.title": "viveworker テスト通知",
923
- "server.pushTest.body": "この iPhone で通知が動作しています。",
929
+ "server.pushTest.body": "この端末で通知が動作しています。",
924
930
  "server.page.notFoundTitle": "見つかりません",
925
931
  "server.page.notFoundBody": "この承認リンクは存在しません。",
926
932
  "server.page.notFoundHint": "このページは閉じて大丈夫です。",
@@ -963,18 +969,18 @@ const translations = {
963
969
  "cli.setup.webPushEnabled": "Web Push: enabled (HTTPS)",
964
970
  "cli.setup.webPushDisabled": "Web Push: disabled",
965
971
  "cli.setup.completePending": "viveworker の setup は完了しましたが、まだ health 応答は確認できていません。",
966
- "cli.setup.caFlow.title": "iPhone viveworker を開く前に、まずローカルの証明書をインストールしてください:",
967
- "cli.setup.caFlow.step1": "1. 下の CA download URL を開くか QR を読み取り、iPhone rootCA.pem を取得します。",
968
- "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 をインストールし、必要なら端末側でその証明書を信頼します。",
969
975
  "cli.setup.caFlow.step3": "3. そのあと、このターミナルで Enter を押すと viveworker の接続先 URL と Pairing QR を表示します。",
970
976
  "cli.setup.instructions.https":
971
- "Safari HTTPS URL を開き、iPhone でローカル CA を信頼してから、ホーム画面に追加して通知を有効にしてください。",
977
+ "ブラウザで HTTPS URL を開き、必要ならローカル CA を信頼してから、ホーム画面に追加して通知を有効にしてください。",
972
978
  "cli.setup.instructions.afterCa":
973
- "次に iPhone の Safari で Pairing URL を開き、必要ならペアリングしてから、viveworker をホーム画面に追加してください。",
979
+ "次に端末のブラウザで Pairing URL を開き、必要ならペアリングしてから、viveworker をホーム画面に追加してください。",
974
980
  "cli.setup.instructions.insecureHttpLan":
975
- "HTTP URL は、LAN 上の平文アクセスを明示的に許可した場合だけ Safari で開いてください。共有 Wi-Fi では非推奨です。",
981
+ "HTTP URL は、LAN 上の平文アクセスを明示的に許可した場合だけブラウザで開いてください。共有 Wi-Fi では非推奨です。",
976
982
  "cli.setup.instructions.localOnlyHttp":
977
- "この setup はローカル開発用の loopback-only HTTP です。iPhone から使うなら既定の HTTPS setup か --allow-insecure-http-lan を使ってください。",
983
+ "この setup はローカル開発用の loopback-only HTTP です。別の端末から使うなら既定の HTTPS setup か --allow-insecure-http-lan を使ってください。",
978
984
  "logout.confirm.title": "ログアウト方法を選んでください",
979
985
  "logout.confirm.copy": "session だけ終えることも、この端末の信頼ごと削除して次回 pairing 必須にすることもできます。",
980
986
  "logout.confirm.keepTrustedTitle": "ログアウトのみ",
@@ -990,6 +996,12 @@ const translations = {
990
996
  "cli.setup.qrPairing": "Pairing QR:",
991
997
  "cli.setup.qrCaDownload": "rootCA.pem download QR (IP):",
992
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",
993
1005
  "cli.start.progress.prepare": "保存済みの viveworker 設定を読み込んでいます...",
994
1006
  "cli.start.progress.refreshPairing": "期限切れの pairing 情報を更新しています...",
995
1007
  "cli.start.progress.launchd": "launchd サービスを起動しています...",