@chainingintention/pi-web-cn 1.202610.2 → 1.202610.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/README.md +1 -0
  2. package/dist/cli.js +129 -122
  3. package/dist/cli.js.map +1 -1
  4. package/dist/client/assets/PiWebApp-CN3g7SqA.js +4362 -0
  5. package/dist/client/assets/external-DUGCSDhZ.js +2 -0
  6. package/dist/client/assets/index-JduZlbIF.js +2 -0
  7. package/dist/client/index.html +4 -4
  8. package/dist/client/manifest.webmanifest +2 -1
  9. package/dist/localization.js +24 -0
  10. package/dist/localization.js.map +1 -0
  11. package/dist/nativeServices/serviceDoctor.js +45 -44
  12. package/dist/nativeServices/serviceDoctor.js.map +1 -1
  13. package/dist/pi-packages/captains-log/dist/browser/index.js +46 -45
  14. package/dist/pi-packages/captains-log/dist/browser/markdown.js +2 -1
  15. package/dist/pi-packages/captains-log/dist/browser/panel.js +56 -55
  16. package/dist/pi-packages/captains-log/i18n.js +7 -0
  17. package/dist/pi-packages/relays/i18n.js +7 -0
  18. package/dist/pi-packages/relays/markdownDocument.js +2 -1
  19. package/dist/pi-packages/relays/pi-web-plugin.js +8 -6
  20. package/dist/pi-packages/relays/relaysPanelElement.js +24 -23
  21. package/dist/pi-web-plugins/files/browser/pi-web-plugin.js +124 -124
  22. package/dist/pi-web-plugins/git/browser/git-contract.js +13 -12
  23. package/dist/pi-web-plugins/git/browser/git-panel.js +25 -24
  24. package/dist/pi-web-plugins/git/browser/i18n.js +7 -0
  25. package/dist/pi-web-plugins/git/browser/pi-web-plugin.js +7 -3
  26. package/dist/pi-web-plugins/git/git-backend.js +10 -9
  27. package/dist/pi-web-plugins/git/i18n.js +7 -0
  28. package/dist/pi-web-plugins/git/server-plugin.js +21 -19
  29. package/dist/pi-web-plugins/info/i18n.js +7 -0
  30. package/dist/pi-web-plugins/info/infoInternals.js +57 -62
  31. package/dist/pi-web-plugins/info/pi-web-plugin.js +34 -36
  32. package/dist/pi-web-plugins/mermaid/browser/pi-web-plugin.js +3 -3
  33. package/dist/pi-web-plugins/terminal/browser/pi-web-plugin.js +55 -52
  34. package/dist/pi-web-plugins/terminal/i18n.js +7 -0
  35. package/dist/pi-web-plugins/terminal/server-plugin.js +23 -21
  36. package/dist/pi-web-plugins/terminal/terminalService.js +22 -21
  37. package/dist/pi-web-plugins/updates/i18n.js +7 -0
  38. package/dist/pi-web-plugins/updates/pi-web-plugin.js +58 -54
  39. package/dist/pi-web-plugins/updates/updatesLogic.js +21 -20
  40. package/dist/pi-web-plugins/workspace-tasks/config.js +12 -11
  41. package/dist/pi-web-plugins/workspace-tasks/i18n.js +7 -0
  42. package/dist/pi-web-plugins/workspace-tasks/pi-web-plugin.js +8 -6
  43. package/dist/pi-web-plugins/workspace-tasks/tasksPanelElement.js +20 -19
  44. package/dist/pi-web-plugins/workspace-tasks/workspaceTasksClient.js +12 -11
  45. package/dist/pi-web-plugins/zh-cn/auth.json +9 -0
  46. package/dist/pi-web-plugins/zh-cn/bundled.json +176 -0
  47. package/dist/pi-web-plugins/zh-cn/cli.json +171 -0
  48. package/dist/pi-web-plugins/zh-cn/client.json +219 -0
  49. package/dist/pi-web-plugins/zh-cn/components.json +1002 -0
  50. package/dist/pi-web-plugins/zh-cn/config-errors.json +172 -0
  51. package/dist/pi-web-plugins/zh-cn/files.json +41 -0
  52. package/dist/pi-web-plugins/zh-cn/machines.json +28 -0
  53. package/dist/pi-web-plugins/zh-cn/main.json +459 -0
  54. package/dist/pi-web-plugins/zh-cn/management-errors.json +23 -0
  55. package/dist/pi-web-plugins/zh-cn/manifest.webmanifest +25 -0
  56. package/dist/pi-web-plugins/zh-cn/metadata.json +3 -0
  57. package/dist/pi-web-plugins/zh-cn/package.json +10 -0
  58. package/dist/pi-web-plugins/zh-cn/pi-web-plugin.js +16 -0
  59. package/dist/pi-web-plugins/zh-cn/projects.json +6 -0
  60. package/dist/pi-web-plugins/zh-cn/provider-auth.json +17 -0
  61. package/dist/pi-web-plugins/zh-cn/routing.json +68 -0
  62. package/dist/pi-web-plugins/zh-cn/server-main.json +99 -0
  63. package/dist/pi-web-plugins/zh-cn/server.json +174 -0
  64. package/dist/piWebVersionReport.js +17 -16
  65. package/dist/piWebVersionReport.js.map +1 -1
  66. package/dist/plugin-api.d.ts +4 -1
  67. package/dist/pluginRecoveryCli.js +25 -24
  68. package/dist/pluginRecoveryCli.js.map +1 -1
  69. package/dist/server/app.js +43 -28
  70. package/dist/server/app.js.map +1 -1
  71. package/dist/server/audit/normalToolAuditCli.js +45 -43
  72. package/dist/server/audit/normalToolAuditCli.js.map +1 -1
  73. package/dist/server/configRoutes.js +39 -30
  74. package/dist/server/configRoutes.js.map +1 -1
  75. package/dist/server/diagnostics/nodePtyNativeModule.js +7 -6
  76. package/dist/server/diagnostics/nodePtyNativeModule.js.map +1 -1
  77. package/dist/server/diagnostics/nodePtySpawnHelper.js +21 -20
  78. package/dist/server/diagnostics/nodePtySpawnHelper.js.map +1 -1
  79. package/dist/server/knownAutoInstallPiPackages.js +2 -2
  80. package/dist/server/knownAutoInstallPiPackages.js.map +1 -1
  81. package/dist/server/knownPiPackages.js +2 -2
  82. package/dist/server/knownPiPackages.js.map +1 -1
  83. package/dist/server/machines/machineClient.js +8 -6
  84. package/dist/server/machines/machineClient.js.map +1 -1
  85. package/dist/server/machines/machinePluginProxyRoutes.js +11 -10
  86. package/dist/server/machines/machinePluginProxyRoutes.js.map +1 -1
  87. package/dist/server/machines/machineProxyRoutes.js +27 -29
  88. package/dist/server/machines/machineProxyRoutes.js.map +1 -1
  89. package/dist/server/machines/machineRoutes.js +10 -12
  90. package/dist/server/machines/machineRoutes.js.map +1 -1
  91. package/dist/server/machines/machineService.js +17 -18
  92. package/dist/server/machines/machineService.js.map +1 -1
  93. package/dist/server/managementEmbed.js +26 -25
  94. package/dist/server/managementEmbed.js.map +1 -1
  95. package/dist/server/normalAuth.js +29 -26
  96. package/dist/server/normalAuth.js.map +1 -1
  97. package/dist/server/passwordVerifier.js +2 -1
  98. package/dist/server/passwordVerifier.js.map +1 -1
  99. package/dist/server/piPackageService.js +7 -5
  100. package/dist/server/piPackageService.js.map +1 -1
  101. package/dist/server/piWebPluginService.js +27 -11
  102. package/dist/server/piWebPluginService.js.map +1 -1
  103. package/dist/server/piWebStatus.js +16 -14
  104. package/dist/server/piWebStatus.js.map +1 -1
  105. package/dist/server/plugins/serverPluginRuntime.js +4 -2
  106. package/dist/server/plugins/serverPluginRuntime.js.map +1 -1
  107. package/dist/server/projects/projectService.js +4 -3
  108. package/dist/server/projects/projectService.js.map +1 -1
  109. package/dist/server/sessiond/projectMutationRoutes.js +5 -4
  110. package/dist/server/sessiond/projectMutationRoutes.js.map +1 -1
  111. package/dist/server/sessiond/sessionProxyRoutes.js +11 -8
  112. package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -1
  113. package/dist/server/sessiond/workspaceCatalogRoutes.js +5 -3
  114. package/dist/server/sessiond/workspaceCatalogRoutes.js.map +1 -1
  115. package/dist/server/sessiond.js +8 -3
  116. package/dist/server/sessiond.js.map +1 -1
  117. package/dist/server/sessions/authRoutes.js +20 -19
  118. package/dist/server/sessions/authRoutes.js.map +1 -1
  119. package/dist/server/sessions/authService.js +13 -10
  120. package/dist/server/sessions/authService.js.map +1 -1
  121. package/dist/server/sessions/builtinCommands.js +28 -21
  122. package/dist/server/sessions/builtinCommands.js.map +1 -1
  123. package/dist/server/sessions/oauthLoginFlowService.js +17 -15
  124. package/dist/server/sessions/oauthLoginFlowService.js.map +1 -1
  125. package/dist/server/sessions/piSessionService.js +229 -208
  126. package/dist/server/sessions/piSessionService.js.map +1 -1
  127. package/dist/server/sessions/projectAuthService.js +5 -3
  128. package/dist/server/sessions/projectAuthService.js.map +1 -1
  129. package/dist/server/sessions/sessionArchiveMapping.js +8 -7
  130. package/dist/server/sessions/sessionArchiveMapping.js.map +1 -1
  131. package/dist/server/sessions/sessionCleanup.js +4 -3
  132. package/dist/server/sessions/sessionCleanup.js.map +1 -1
  133. package/dist/server/sessions/sessionCommandService.js +70 -67
  134. package/dist/server/sessions/sessionCommandService.js.map +1 -1
  135. package/dist/server/sessions/sessionNotificationStore.js +12 -11
  136. package/dist/server/sessions/sessionNotificationStore.js.map +1 -1
  137. package/dist/server/sessions/sessionRoutes.js +74 -72
  138. package/dist/server/sessions/sessionRoutes.js.map +1 -1
  139. package/dist/server/sessions/sessionTreeProjection.js +50 -49
  140. package/dist/server/sessions/sessionTreeProjection.js.map +1 -1
  141. package/dist/server/sessions/sessionUnreadStore.js +25 -2
  142. package/dist/server/sessions/sessionUnreadStore.js.map +1 -1
  143. package/dist/server/terminals/managementCommandRunService.js +13 -12
  144. package/dist/server/terminals/managementCommandRunService.js.map +1 -1
  145. package/dist/server/workbench/accessStateStore.js +9 -7
  146. package/dist/server/workbench/accessStateStore.js.map +1 -1
  147. package/dist/server/workbench/capabilitySearch.js +3 -2
  148. package/dist/server/workbench/capabilitySearch.js.map +1 -1
  149. package/dist/server/workbench/knowledgeSearch.js +8 -7
  150. package/dist/server/workbench/knowledgeSearch.js.map +1 -1
  151. package/dist/server/workbench/mcpClient.js +6 -5
  152. package/dist/server/workbench/mcpClient.js.map +1 -1
  153. package/dist/server/workbench/skillSync.js +4 -2
  154. package/dist/server/workbench/skillSync.js.map +1 -1
  155. package/dist/server/workbench/workbenchClient.js +8 -7
  156. package/dist/server/workbench/workbenchClient.js.map +1 -1
  157. package/dist/server/workbench/workbenchTools.js +8 -6
  158. package/dist/server/workbench/workbenchTools.js.map +1 -1
  159. package/dist/server/workingDirectory.js +3 -2
  160. package/dist/server/workingDirectory.js.map +1 -1
  161. package/dist/server/workspaceExplorerRoutes.js +16 -13
  162. package/dist/server/workspaceExplorerRoutes.js.map +1 -1
  163. package/dist/server/workspaces/fileContentService.js +11 -10
  164. package/dist/server/workspaces/fileContentService.js.map +1 -1
  165. package/dist/server/workspaces/fileOperationService.js +7 -6
  166. package/dist/server/workspaces/fileOperationService.js.map +1 -1
  167. package/dist/server/workspaces/filePreviewService.js +5 -4
  168. package/dist/server/workspaces/filePreviewService.js.map +1 -1
  169. package/dist/server/workspaces/fileSuggestions.js +2 -1
  170. package/dist/server/workspaces/fileSuggestions.js.map +1 -1
  171. package/dist/server/workspaces/pathAccessPolicy.js +24 -18
  172. package/dist/server/workspaces/pathAccessPolicy.js.map +1 -1
  173. package/dist/server/workspaces/pathSafety.js +6 -5
  174. package/dist/server/workspaces/pathSafety.js.map +1 -1
  175. package/dist/server/workspaces/projectPiWebConfig.js +3 -2
  176. package/dist/server/workspaces/projectPiWebConfig.js.map +1 -1
  177. package/dist/server/workspaces/workspaceRemovalService.js +4 -3
  178. package/dist/server/workspaces/workspaceRemovalService.js.map +1 -1
  179. package/dist/server/workspaces/workspaceRouteErrors.js +4 -2
  180. package/dist/server/workspaces/workspaceRouteErrors.js.map +1 -1
  181. package/dist/server/workspaces/workspaceUserError.js +18 -0
  182. package/dist/server/workspaces/workspaceUserError.js.map +1 -0
  183. package/dist/server-plugin-api.d.ts +3 -1
  184. package/dist/server-plugin-api.js.map +1 -1
  185. package/dist/serverPluginRecovery.js +3 -2
  186. package/dist/serverPluginRecovery.js.map +1 -1
  187. package/dist/shared/apiTypes.js.map +1 -1
  188. package/dist/shared/i18n.js +41 -0
  189. package/dist/shared/i18n.js.map +1 -0
  190. package/dist/shared/piWebConfigParsing.js +52 -49
  191. package/dist/shared/piWebConfigParsing.js.map +1 -1
  192. package/dist/shared/pluginApiTypes.d.ts +7 -0
  193. package/dist/shared/requiredTerminalPlugin.js +1 -1
  194. package/dist/shared/requiredTerminalPlugin.js.map +1 -1
  195. package/dist/shared/sessionDefaults.js +7 -6
  196. package/dist/shared/sessionDefaults.js.map +1 -1
  197. package/dist/shared/thinkingLevels.js +17 -16
  198. package/dist/shared/thinkingLevels.js.map +1 -1
  199. package/docs/config.md +10 -1
  200. package/docs/plugins.md +8 -0
  201. package/package.json +1 -1
  202. package/dist/client/assets/index-CFhaGb5m.js +0 -4357
package/README.md CHANGED
@@ -130,6 +130,7 @@ npm run verify
130
130
 
131
131
  ## 文档
132
132
 
133
+ - [代码库维护文档:架构、模块契约、改动地图与验证](https://github.com/nayuto-wakusei/pi-web-ChineseTranslation/blob/main/docs/maintenance.md)
133
134
  - [上游网站](https://pi-web.dev/)
134
135
  - [安装](https://pi-web.dev/install)
135
136
  - [远端优先](https://pi-web.dev/remote-first)
package/dist/cli.js CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env node
2
+ import { loadNodeTranslator } from "./localization.js";
3
+ import { createTranslator } from "./shared/i18n.js";
2
4
  import { spawnSync } from "node:child_process";
3
5
  import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
4
6
  import { mkdir, rm, writeFile } from "node:fs/promises";
@@ -48,22 +50,22 @@ export function serviceBackendForPlatform(platform) {
48
50
  function currentServiceBackend() {
49
51
  return serviceBackendForPlatform(process.platform);
50
52
  }
51
- function requireServiceBackend(command) {
53
+ function requireServiceBackend(command, t = createTranslator("en", {})) {
52
54
  const backend = currentServiceBackend();
53
55
  if (backend !== undefined)
54
56
  return backend;
55
- throw new Error(`\`${command}\` requires a supported per-user service manager (systemd user services or LaunchAgents) and is not supported on ${platformLabel()}.\n\n${manualRunAdvice()}`);
57
+ throw new Error(t("service.unsupported", "`{command}` requires a supported per-user service manager (systemd user services or LaunchAgents) and is not supported on {platform}.\n\n{advice}", { command, platform: platformLabel(), advice: manualRunAdvice(t) }));
56
58
  }
57
- function manualRunAdvice() {
59
+ function manualRunAdvice(t = createTranslator("en", {})) {
58
60
  return [
59
- "Run PI WEB manually from a checkout:",
61
+ t("manual.checkout", "Run PI WEB manually from a checkout:"),
60
62
  " npm run start:sessiond",
61
63
  " PI_WEB_PORT=8504 npm start",
62
64
  "",
63
- "For development in one terminal:",
65
+ t("manual.singleTerminal", "For development in one terminal:"),
64
66
  " npm run dev",
65
67
  "",
66
- "For split development, keep sessiond separate and run web/API plus Vite UI separately:",
68
+ t("manual.split", "For split development, keep sessiond separate and run web/API plus Vite UI separately:"),
67
69
  " npm run dev:sessiond",
68
70
  " npm run dev:web",
69
71
  " npm run dev:client",
@@ -113,7 +115,7 @@ function isLingerEnabled() {
113
115
  return false;
114
116
  return undefined;
115
117
  }
116
- function parseInstallOptions(args) {
118
+ function parseInstallOptions(args, t = createTranslator("en", {})) {
117
119
  const options = { host: "127.0.0.1", port: "8504", mode: "production" };
118
120
  for (let i = 0; i < args.length; i += 1) {
119
121
  const arg = args[i];
@@ -122,7 +124,7 @@ function parseInstallOptions(args) {
122
124
  if (arg === "--host") {
123
125
  const value = args[i + 1];
124
126
  if (value === undefined)
125
- throw new Error("--host requires a value");
127
+ throw new Error(t("install.hostRequired", "--host requires a value"));
126
128
  options.host = value;
127
129
  i += 1;
128
130
  }
@@ -132,7 +134,7 @@ function parseInstallOptions(args) {
132
134
  else if (arg === "--port") {
133
135
  const value = args[i + 1];
134
136
  if (value === undefined)
135
- throw new Error("--port requires a value");
137
+ throw new Error(t("install.portRequired", "--port requires a value"));
136
138
  options.port = value;
137
139
  i += 1;
138
140
  }
@@ -142,7 +144,7 @@ function parseInstallOptions(args) {
142
144
  else if (arg === "--config") {
143
145
  const value = args[i + 1];
144
146
  if (value === undefined)
145
- throw new Error("--config requires a value");
147
+ throw new Error(t("install.configRequired", "--config requires a value"));
146
148
  options.config = value;
147
149
  i += 1;
148
150
  }
@@ -156,7 +158,7 @@ function parseInstallOptions(args) {
156
158
  // Accepted for backwards-compatible readability; PI WEB chooses the native user service backend automatically.
157
159
  }
158
160
  else {
159
- throw new Error(`Unknown install option: ${arg}`);
161
+ throw new Error(t("install.unknownOption", "Unknown install option: {arg}", { arg: arg }));
160
162
  }
161
163
  }
162
164
  return options;
@@ -233,14 +235,14 @@ function stopOrder(refs) {
233
235
  function devRootPath() {
234
236
  return resolve(process.cwd());
235
237
  }
236
- function validateDevCheckout(root) {
238
+ function validateDevCheckout(root, t) {
237
239
  const packageJsonPath = join(root, "package.json");
238
240
  if (!existsSync(packageJsonPath)) {
239
- throw new Error(`Development mode must be installed from a PI WEB checkout. Missing package.json: ${packageJsonPath}`);
241
+ throw new Error(t("install.checkoutMissing", "Development mode must be installed from a PI WEB checkout. Missing package.json: {path}", { path: packageJsonPath }));
240
242
  }
241
243
  const parsed = JSON.parse(readFileSync(packageJsonPath, "utf8"));
242
244
  if (!isRecord(parsed) || parsed["name"] !== PI_WEB_PACKAGE_NAME) {
243
- throw new Error(`Development mode must be installed from a PI WEB checkout. ${packageJsonPath} is not ${PI_WEB_PACKAGE_NAME}.`);
245
+ throw new Error(t("install.checkoutWrong", "Development mode must be installed from a PI WEB checkout. {path} is not {packageName}.", { path: packageJsonPath, packageName: PI_WEB_PACKAGE_NAME }));
244
246
  }
245
247
  }
246
248
  function launchdLogPath(ref) {
@@ -363,12 +365,12 @@ async function uninstallNativeServices(backend) {
363
365
  else
364
366
  await uninstallLaunchdServices();
365
367
  }
366
- function serviceDisplayName(ref) {
368
+ function serviceDisplayName(ref, t = createTranslator("en", {})) {
367
369
  if (ref.id === "sessiond")
368
- return "session daemon";
370
+ return t("service.sessiond", "session daemon");
369
371
  if (ref.id === "uiDev")
370
- return "UI/API dev server";
371
- return "web server";
372
+ return t("service.uiDev", "UI/API dev server");
373
+ return t("service.web", "web server");
372
374
  }
373
375
  function statusServiceRefs(backend) {
374
376
  const ids = installedServiceIds(backend);
@@ -382,20 +384,20 @@ function statusServiceRefs(backend) {
382
384
  return productionServiceRefs();
383
385
  return serviceRefList(["sessiond"]);
384
386
  }
385
- function serviceInstallMode(backend) {
387
+ function serviceInstallMode(backend, t) {
386
388
  const ids = installedServiceIds(backend);
387
389
  if (ids.size === 0)
388
- return "not installed";
390
+ return t("service.notInstalled", "not installed");
389
391
  const hasSessiond = ids.has("sessiond");
390
392
  const hasWeb = ids.has("web");
391
393
  const hasUiDev = ids.has("uiDev");
392
394
  if (hasWeb && hasUiDev)
393
- return "mixed";
395
+ return t("service.mixed", "mixed");
394
396
  if (hasUiDev)
395
- return hasSessiond ? "development" : "development (incomplete)";
397
+ return hasSessiond ? t("service.development", "development") : t("service.developmentIncomplete", "development (incomplete)");
396
398
  if (hasWeb)
397
- return hasSessiond ? "production" : "production (incomplete)";
398
- return "partial";
399
+ return hasSessiond ? t("service.production", "production") : t("service.productionIncomplete", "production (incomplete)");
400
+ return t("service.partial", "partial");
399
401
  }
400
402
  function makeServiceRuntimeStatus(ref, health, detail, target, filePath, pid) {
401
403
  return {
@@ -462,25 +464,25 @@ function launchdRuntimeStatus(backend, ref) {
462
464
  function runtimeStatus(backend, ref) {
463
465
  return backend.kind === "systemd" ? systemdRuntimeStatus(backend, ref) : launchdRuntimeStatus(backend, ref);
464
466
  }
465
- function printServiceStatus(status) {
467
+ function printServiceStatus(status, t = createTranslator("en", {})) {
466
468
  const icon = status.health === "running" ? "✓" : "✗";
467
469
  const pid = status.pid === undefined ? "" : `, pid ${status.pid}`;
468
- console.log(`${icon} ${serviceDisplayName(status.ref)}: ${status.detail} (${status.target}${pid})`);
470
+ console.log(`${icon} ${serviceDisplayName(status.ref, t)}: ${status.detail} (${status.target}${pid})`);
469
471
  if (status.health === "not-installed")
470
- console.log(` missing service file: ${status.filePath}`);
472
+ console.log(t("status.missingFile", " missing service file: {filePath}", { filePath: status.filePath }));
471
473
  }
472
- function printServiceStatusReport(backend) {
474
+ function printServiceStatusReport(backend, t = createTranslator("en", {})) {
473
475
  const refs = statusServiceRefs(backend);
474
- console.log(`PI WEB services: ${serviceInstallMode(backend)} (${backend.label})`);
476
+ console.log(t("status.services", "PI WEB services: {mode} ({backend})", { mode: serviceInstallMode(backend, t), backend: backend.label }));
475
477
  if (refs.length === 0) {
476
- console.log("✗ no PI WEB service files found");
477
- console.log(" Run `pi-web install` or `pi-web install --dev`.");
478
+ console.log(t("status.noServices", "✗ no PI WEB service files found"));
479
+ console.log(t("status.installAdvice", " Run `pi-web install` or `pi-web install --dev`."));
478
480
  return false;
479
481
  }
480
482
  const statuses = refs.map((ref) => runtimeStatus(backend, ref));
481
483
  for (const status of statuses)
482
- printServiceStatus(status);
483
- console.log("\nUse `pi-web logs` for service logs.");
484
+ printServiceStatus(status, t);
485
+ console.log(t("status.logsAdvice", "\nUse `pi-web logs` for service logs."));
484
486
  return statuses.every((status) => status.health === "running");
485
487
  }
486
488
  function configuredServiceCommand(name) {
@@ -527,17 +529,17 @@ function nativeServiceInstallCandidate(options, backend, configPath, devRoot) {
527
529
  },
528
530
  };
529
531
  }
530
- function printNativeServiceInstallFailure(failure) {
532
+ function printNativeServiceInstallFailure(failure, t) {
531
533
  if (failure.kind === "plan-resolution") {
532
534
  for (const item of failure.failures) {
533
535
  if (item.kind === "probe-infrastructure") {
534
- console.log(`✗ Service-manager probe infrastructure failure (${item.reason}): ${item.message}`);
536
+ console.log(t("install.probeFailure", "✗ Service-manager probe infrastructure failure ({reason}): {message}", { reason: item.reason, message: item.message }));
535
537
  }
536
538
  else if (item.kind === "entrypoint-inspection-failure") {
537
- console.log(`✗ Could not inspect bundled ${item.serviceId} entrypoint ${item.entrypointPath}: ${item.message}`);
539
+ console.log(t("install.entrypointFailure", "✗ Could not inspect bundled {service} entrypoint {path}: {message}", { service: item.serviceId, path: item.entrypointPath, message: item.message }));
538
540
  }
539
541
  else {
540
- console.log(`✗ ${item.namedCommand} is unavailable to the service manager, and bundled entrypoint ${item.bundledEntrypointPath} is missing.`);
542
+ console.log(t("install.commandMissing", "✗ {command} is unavailable to the service manager, and bundled entrypoint {path} is missing.", { command: item.namedCommand, path: item.bundledEntrypointPath }));
541
543
  if (item.namedCommandFailure !== null)
542
544
  console.log(` ${item.namedCommandFailure}`);
543
545
  }
@@ -546,7 +548,7 @@ function printNativeServiceInstallFailure(failure) {
546
548
  }
547
549
  for (const item of failure.failures) {
548
550
  if (item.kind === "probe-infrastructure") {
549
- console.log(`✗ Service-manager probe infrastructure failure (${item.reason}): ${item.message}`);
551
+ console.log(t("install.probeFailure", "✗ Service-manager probe infrastructure failure ({reason}): {message}", { reason: item.reason, message: item.message }));
550
552
  }
551
553
  else {
552
554
  console.log(`✗ ${item.prerequisite.description}`);
@@ -555,19 +557,19 @@ function printNativeServiceInstallFailure(failure) {
555
557
  }
556
558
  }
557
559
  }
558
- async function install(args) {
559
- const backend = requireServiceBackend("pi-web install");
560
- const options = parseInstallOptions(args);
560
+ async function install(args, t = createTranslator("en", {})) {
561
+ const backend = requireServiceBackend("pi-web install", t);
562
+ const options = parseInstallOptions(args, t);
561
563
  const devRoot = options.mode === "dev" ? devRootPath() : undefined;
562
564
  if (devRoot !== undefined)
563
- validateDevCheckout(devRoot);
565
+ validateDevCheckout(devRoot, t);
564
566
  const configPath = installConfigPath(options);
565
567
  const candidate = nativeServiceInstallCandidate(options, backend, configPath, devRoot);
566
- console.log(`Running PI WEB ${options.mode} install preflight checks...`);
567
- console.log(`Service backend: ${backend.label}`);
568
- console.log(`Service shell: ${describeServiceShell()}`);
569
- if (!printNodePtyNativeModuleCheck()) {
570
- throw new Error("Install preflight checks failed without changing config or services. Fix the failure above, then run `pi-web doctor` for more detail.");
568
+ console.log(t("install.preflight", "Running PI WEB {mode} install preflight checks...", { mode: options.mode }));
569
+ console.log(t("service.backend", "Service backend: {label}", { label: backend.label }));
570
+ console.log(t("service.shell", "Service shell: {value}", { value: describeServiceShell() }));
571
+ if (!printNodePtyNativeModuleCheck(t)) {
572
+ throw new Error(t("install.preflightFailed", "Install preflight checks failed without changing config or services. Fix the failure above, then run `pi-web doctor` for more detail."));
571
573
  }
572
574
  const result = await installNativeServiceCandidate(candidate, {
573
575
  probe: createNativeServiceAuthoritativeProbe(),
@@ -576,47 +578,47 @@ async function install(args) {
576
578
  replaceServices: installNativeServices,
577
579
  });
578
580
  if (!result.ok) {
579
- printNativeServiceInstallFailure(result.failure);
581
+ printNativeServiceInstallFailure(result.failure, t);
580
582
  if (nativeServiceInstallFailureNeedsPathAdvice(result.failure))
581
- printPathSetupAdvice();
582
- throw new Error("Install preflight checks failed without changing config or services. Fix the failure above, then run `pi-web doctor` for more detail.");
583
+ printPathSetupAdvice(undefined, t);
584
+ throw new Error(t("install.preflightFailed", "Install preflight checks failed without changing config or services. Fix the failure above, then run `pi-web doctor` for more detail."));
583
585
  }
584
586
  for (const service of result.plan.services.filter((item) => item.strategy.kind === "configured-override")) {
585
- console.log(`! ${service.description} uses a configured command override; preflight did not execute that arbitrary command.`);
587
+ console.log(t("install.overrideSkipped", "! {service} uses a configured command override; preflight did not execute that arbitrary command.", { service: service.description }));
586
588
  }
587
- console.log(`\nPI WEB ${options.mode} services are installed and starting.`);
588
- console.log(`Config: ${configPath}`);
589
+ console.log(t("install.started", "\nPI WEB {mode} services are installed and starting.", { mode: options.mode }));
590
+ console.log(t("install.config", "Config: {configPath}", { configPath: configPath }));
589
591
  if (options.mode === "dev") {
590
- console.log("Open: http://127.0.0.1:8505");
592
+ console.log(t("install.open", "Open: {url}", { url: "http://127.0.0.1:8505" }));
591
593
  }
592
594
  else {
593
- console.log(`Open: http://${options.host === "0.0.0.0" ? "127.0.0.1" : options.host}:${options.port}`);
595
+ console.log(t("install.open", "Open: {url}", { url: `http://${options.host === "0.0.0.0" ? "127.0.0.1" : options.host}:${options.port}` }));
594
596
  }
595
597
  if (backend.kind === "systemd") {
596
598
  const linger = isLingerEnabled();
597
599
  if (linger === false) {
598
- console.log("\nRecommended for server use: keep user services running after logout/reboot:");
600
+ console.log(t("install.lingerAdvice", "\nRecommended for server use: keep user services running after logout/reboot:"));
599
601
  console.log(` sudo loginctl enable-linger ${userInfo().username}`);
600
602
  }
601
603
  else if (linger === undefined) {
602
- console.log("\nRecommended for server use: enable systemd user lingering so services survive logout/reboot:");
604
+ console.log(t("install.enableLinger", "\nRecommended for server use: enable systemd user lingering so services survive logout/reboot:"));
603
605
  console.log(` sudo loginctl enable-linger ${userInfo().username}`);
604
606
  }
605
607
  }
606
- console.log("\nUseful commands:");
608
+ console.log(t("install.commands", "\nUseful commands:"));
607
609
  console.log(" pi-web status");
608
610
  console.log(" pi-web logs");
609
611
  console.log(" pi-web restart");
610
612
  }
611
- async function uninstall() {
612
- const backend = requireServiceBackend("pi-web uninstall");
613
+ async function uninstall(t = createTranslator("en", {})) {
614
+ const backend = requireServiceBackend("pi-web uninstall", t);
613
615
  await uninstallNativeServices(backend);
614
- console.log(`PI WEB ${backend.label} removed. Production and development service files were removed; config and data were left in place.`);
616
+ console.log(t("uninstall.done", "PI WEB {label} removed. Production and development service files were removed; config and data were left in place.", { label: backend.label }));
615
617
  }
616
- async function serviceAction(action) {
618
+ async function serviceAction(action, t = createTranslator("en", {})) {
617
619
  const backend = requireServiceBackend(`pi-web ${action}`);
618
620
  if (action === "status") {
619
- if (!printServiceStatusReport(backend))
621
+ if (!printServiceStatusReport(backend, t))
620
622
  process.exitCode = 1;
621
623
  return;
622
624
  }
@@ -693,8 +695,8 @@ function activeDockerMode(env) {
693
695
  function truthyEnvValue(value) {
694
696
  return value !== undefined && value !== "" && value !== "0" && value.toLowerCase() !== "false";
695
697
  }
696
- function logs() {
697
- const backend = requireServiceBackend("pi-web logs");
698
+ function logs(t = createTranslator("en", {})) {
699
+ const backend = requireServiceBackend("pi-web logs", t);
698
700
  const refs = installedServiceRefs(backend);
699
701
  if (backend.kind === "systemd") {
700
702
  run("journalctl", ["--user", ...refs.flatMap((ref) => ["-u", ref.systemdName]), "-f"]);
@@ -763,7 +765,7 @@ function optionalDoctorChecks() {
763
765
  const shell = serviceShellLabel();
764
766
  return [[`Caller login ${shell} can find optional ripgrep (rg)`, serviceShellCommand(commandCheck("rg"))]];
765
767
  }
766
- function printOptionalDoctorChecks() {
768
+ function printOptionalDoctorChecks(t = createTranslator("en", {})) {
767
769
  let missingOptionalTool = false;
768
770
  for (const [label, command] of optionalDoctorChecks()) {
769
771
  const [bin, ...args] = command;
@@ -776,8 +778,8 @@ function printOptionalDoctorChecks() {
776
778
  printCheckOutput(result.stdout || result.stderr);
777
779
  }
778
780
  if (missingOptionalTool) {
779
- console.log(" Install ripgrep, or make rg visible to the service shell, for faster all-file @ suggestions.");
780
- console.log(" PI WEB falls back to a bounded filesystem scan when rg is unavailable.");
781
+ console.log(t("doctor.ripgrep", " Install ripgrep, or make rg visible to the service shell, for faster all-file @ suggestions."));
782
+ console.log(t("doctor.scanFallback", " PI WEB falls back to a bounded filesystem scan when rg is unavailable."));
781
783
  }
782
784
  }
783
785
  function installedServiceDefinitions(backend, ids, purpose, warn = (message) => { console.warn(`! ${message}`); }) {
@@ -877,7 +879,7 @@ export async function runReadinessCliCommand(command, dependencies) {
877
879
  managedInspectionFailed,
878
880
  });
879
881
  }
880
- function readinessCliCommandDependencies() {
882
+ function readinessCliCommandDependencies(t = createTranslator("en", {})) {
881
883
  return {
882
884
  environment: process.env,
883
885
  currentBackend: currentServiceBackend,
@@ -887,23 +889,23 @@ function readinessCliCommandDependencies() {
887
889
  // warnings there, including when an explicit config bypasses selection here.
888
890
  inspectDefinitions: (backend, ids, purpose) => installedServiceDefinitions(backend, ids, purpose, purpose === "doctor" ? () => { } : undefined),
889
891
  runLifecycle: performLifecycleServiceAction,
890
- runDoctor: doctor,
892
+ runDoctor: (context) => doctor(context, t),
891
893
  printVersion: async () => { await printPiWebVersionReport(); },
892
894
  };
893
895
  }
894
- function nativeServiceDoctorTarget(backend, ids) {
896
+ function nativeServiceDoctorTarget(backend, ids, t = createTranslator("en", {})) {
895
897
  const mode = inferInstalledNativeServiceMode(ids);
896
898
  if (mode === "ambiguous") {
897
899
  return {
898
900
  kind: "inspection-failure",
899
- message: `installed service IDs do not identify one mode (${[...ids].join(", ") || "none"}).`,
901
+ message: t("doctor.ambiguousMode", "installed service IDs do not identify one mode ({ids}).", { ids: [...ids].join(", ") || "none" }),
900
902
  };
901
903
  }
902
904
  if (mode === "none") {
903
905
  return {
904
906
  kind: "prospective-production",
905
907
  input: productionNativeServicePlanInput(backend, detectServiceShell(), {}),
906
- reason: "no installed service strategy is available",
908
+ reason: t("doctor.noStrategy", "no installed service strategy is available"),
907
909
  };
908
910
  }
909
911
  const expectedIds = mode === "production"
@@ -913,7 +915,7 @@ function nativeServiceDoctorTarget(backend, ids) {
913
915
  if (missingId !== undefined) {
914
916
  return {
915
917
  kind: "inspection-failure",
916
- message: `installed ${mode} service set is incomplete; ${missingId} is missing.`,
918
+ message: t("doctor.incompleteSet", "installed {mode} service set is incomplete; {service} is missing.", { mode, service: missingId }),
917
919
  };
918
920
  }
919
921
  const definitions = installedServiceDefinitions(backend, expectedIds, "doctor");
@@ -930,22 +932,22 @@ function nativeServiceDoctorTarget(backend, ids) {
930
932
  ? {
931
933
  kind: "prospective-production",
932
934
  input: productionNativeServicePlanInput(backend, inspection.value.shell, inspection.value.environment),
933
- reason: "installed executable strategy is not recorded",
935
+ reason: t("doctor.unrecordedStrategy", "installed executable strategy is not recorded"),
934
936
  }
935
937
  : { kind: "inspection-failure", message: inspection.message };
936
938
  }
937
- async function printNativeServiceDoctorChecks(backend, installedIds) {
938
- const result = await runNativeServiceDoctor(nativeServiceDoctorTarget(backend, installedIds), {
939
+ async function printNativeServiceDoctorChecks(backend, installedIds, t = createTranslator("en", {})) {
940
+ const result = await runNativeServiceDoctor(nativeServiceDoctorTarget(backend, installedIds, t), {
939
941
  probe: createNativeServiceAuthoritativeProbe(),
940
942
  fileExists: regularFileExists,
941
943
  });
942
- const report = formatNativeServiceDoctorResult(result);
944
+ const report = formatNativeServiceDoctorResult(result, t);
943
945
  for (const line of report.lines)
944
946
  console.log(line);
945
- printCallerContextComparisons(report);
947
+ printCallerContextComparisons(report, t);
946
948
  return report;
947
949
  }
948
- function printCallerContextComparisons(report) {
950
+ function printCallerContextComparisons(report, t) {
949
951
  if (report.plan === null || report.failedPrerequisites.length === 0)
950
952
  return;
951
953
  const seen = new Set();
@@ -956,7 +958,11 @@ function printCallerContextComparisons(report) {
956
958
  const service = report.plan.services.find((candidate) => candidate.prerequisites.some((item) => item.id === prerequisite.id));
957
959
  const command = nativeServicePrerequisiteShellCheck(report.plan.shell.name, prerequisite);
958
960
  const result = captureServiceShell(report.plan.shell, command, service?.workingDirectory ?? null);
959
- console.log(` Caller-invoked ${report.plan.shell.name} -lc ${result.status === 0 ? "satisfies" : "also does not satisfy"} ${prerequisite.description}; the service-manager result is authoritative.`);
961
+ console.log(t("doctor.callerComparison", " Caller-invoked {shell} -lc {outcome} {requirement}; the service-manager result is authoritative.", {
962
+ shell: report.plan.shell.name,
963
+ outcome: result.status === 0 ? t("doctor.satisfies", "satisfies") : t("doctor.unsatisfied", "also does not satisfy"),
964
+ requirement: prerequisite.description,
965
+ }));
960
966
  }
961
967
  }
962
968
  function captureServiceShell(shell, command, workingDirectory) {
@@ -968,20 +974,20 @@ function captureServiceShell(shell, command, workingDirectory) {
968
974
  function shellQuoteFor(shell, value) {
969
975
  return shell === "fish" ? fishSingleQuote(value) : shellSingleQuote(value);
970
976
  }
971
- function printPathSetupAdvice(shell = detectServiceShell()) {
972
- console.log("\nPATH setup advice:");
977
+ function printPathSetupAdvice(shell = detectServiceShell(), t = createTranslator("en", {})) {
978
+ console.log(t("doctor.pathAdvice", "\nPATH setup advice:"));
973
979
  if (shell.name === "bash") {
974
- console.log(" Detected bash. Put PATH setup for node/version managers/tools in ~/.bash_profile or ~/.profile.");
975
- console.log(" If ~/.bash_profile exists, bash will not read ~/.profile unless you source it from ~/.bash_profile.");
976
- console.log(" Do not rely only on ~/.bashrc or prompt hooks for tools needed by services or agents.");
980
+ console.log(t("doctor.bashPath", " Detected bash. Put PATH setup for node/version managers/tools in ~/.bash_profile or ~/.profile."));
981
+ console.log(t("doctor.bashProfile", " If ~/.bash_profile exists, bash will not read ~/.profile unless you source it from ~/.bash_profile."));
982
+ console.log(t("doctor.bashHooks", " Do not rely only on ~/.bashrc or prompt hooks for tools needed by services or agents."));
977
983
  }
978
984
  else if (shell.name === "zsh") {
979
- console.log(" Detected zsh. Put PATH setup for node/version managers/tools in ~/.zprofile, not only ~/.zshrc.");
980
- console.log(" Avoid relying on prompt hooks; PI WEB services run non-interactive login shells.");
985
+ console.log(t("doctor.zshPath", " Detected zsh. Put PATH setup for node/version managers/tools in ~/.zprofile, not only ~/.zshrc."));
986
+ console.log(t("doctor.promptHooks", " Avoid relying on prompt hooks; PI WEB services run non-interactive login shells."));
981
987
  }
982
988
  else {
983
- console.log(" Detected fish. Prefer universal PATH setup such as `fish_add_path -U ...` for tools needed by services or agents.");
984
- console.log(" Avoid relying on prompt hooks; PI WEB services run non-interactive login shells.");
989
+ console.log(t("doctor.fishPath", " Detected fish. Prefer universal PATH setup such as `fish_add_path -U ...` for tools needed by services or agents."));
990
+ console.log(t("doctor.promptHooks", " Avoid relying on prompt hooks; PI WEB services run non-interactive login shells."));
985
991
  }
986
992
  }
987
993
  /**
@@ -1006,13 +1012,13 @@ export function expectedRunningComponents(installedServices, env) {
1006
1012
  export function doctorExitCode(generalReadinessOk, nativeServicePlanOk, nodePtyRuntimeOk, runningComponentsOk) {
1007
1013
  return generalReadinessOk && nativeServicePlanOk && nodePtyRuntimeOk && runningComponentsOk ? 0 : 1;
1008
1014
  }
1009
- async function doctor(context) {
1015
+ async function doctor(context, t = createTranslator("en", {})) {
1010
1016
  const { backend, installedIds, environment, versionReportOptions, managedInspectionFailed } = context;
1011
1017
  console.log(`Platform: ${platformLabel()}`);
1012
1018
  console.log(`Service backend: ${backend?.label ?? "manual run only"}`);
1013
1019
  console.log(`Service shell: ${describeServiceShell()}`);
1014
1020
  if (backend === undefined) {
1015
- console.log(`- Native user service plan checks skipped on ${platformLabel()}; no native-service drift is reported.`);
1021
+ console.log(t("doctor.nativeSkipped", "- Native user service plan checks skipped on {value}; no native-service drift is reported.", { value: platformLabel() }));
1016
1022
  }
1017
1023
  console.log("");
1018
1024
  const runningVersionInfo = await printPiWebVersionReport(versionReportOptions);
@@ -1022,12 +1028,12 @@ async function doctor(context) {
1022
1028
  console.log("\n✗ Installed PI WEB services are unavailable or stale (see \"Running services\" above).");
1023
1029
  console.log(" Run `pi-web restart`, then check `pi-web status`.");
1024
1030
  }
1025
- console.log("\nGeneral login-shell readiness (separate from native-service requirements):");
1031
+ console.log(t("doctor.loginShell", "\nGeneral login-shell readiness (separate from native-service requirements):"));
1026
1032
  const generalReadinessOk = runChecks(generalDoctorChecks());
1027
- printOptionalDoctorChecks();
1028
- console.log("\nNative terminal runtime readiness:");
1029
- const nodePtyNativeModuleOk = printNodePtyNativeModuleCheck();
1030
- const nodePtySpawnHelperOk = nodePtyNativeModuleOk ? printNodePtyDarwinSpawnHelperCheck() : true;
1033
+ printOptionalDoctorChecks(t);
1034
+ console.log(t("doctor.terminal", "\nNative terminal runtime readiness:"));
1035
+ const nodePtyNativeModuleOk = printNodePtyNativeModuleCheck(t);
1036
+ const nodePtySpawnHelperOk = nodePtyNativeModuleOk ? printNodePtyDarwinSpawnHelperCheck(t) : true;
1031
1037
  let nativeServiceReport = null;
1032
1038
  if (backend !== undefined) {
1033
1039
  console.log("\nNative service plan checks (service-manager context):");
@@ -1036,52 +1042,52 @@ async function doctor(context) {
1036
1042
  console.log(" Repair the reported service-manager definition before relying on doctor results.");
1037
1043
  }
1038
1044
  else {
1039
- nativeServiceReport = await printNativeServiceDoctorChecks(backend, installedIds);
1045
+ nativeServiceReport = await printNativeServiceDoctorChecks(backend, installedIds, t);
1040
1046
  }
1041
1047
  }
1042
1048
  if (backend?.kind === "systemd") {
1043
1049
  const linger = isLingerEnabled();
1044
1050
  if (linger === true) {
1045
- console.log("✓ systemd user lingering enabled");
1051
+ console.log(t("doctor.lingerEnabled", "✓ systemd user lingering enabled"));
1046
1052
  }
1047
1053
  else if (linger === false) {
1048
- console.log("✗ systemd user lingering disabled");
1049
- console.log(` Recommended on servers: sudo loginctl enable-linger ${userInfo().username}`);
1054
+ console.log(t("doctor.lingerDisabled", "✗ systemd user lingering disabled"));
1055
+ console.log(t("doctor.lingerCommand", " Recommended on servers: sudo loginctl enable-linger {username}", { username: userInfo().username }));
1050
1056
  }
1051
1057
  else {
1052
- console.log("? systemd user lingering unknown");
1053
- console.log(` Recommended on servers: sudo loginctl enable-linger ${userInfo().username}`);
1058
+ console.log(t("doctor.lingerUnknown", "? systemd user lingering unknown"));
1059
+ console.log(t("doctor.lingerCommand", " Recommended on servers: sudo loginctl enable-linger {username}", { username: userInfo().username }));
1054
1060
  }
1055
1061
  }
1056
1062
  else if (backend?.kind === "launchd") {
1057
- console.log("- user services start at login with LaunchAgents");
1063
+ console.log(t("doctor.launchAgents", "- user services start at login with LaunchAgents"));
1058
1064
  }
1059
1065
  else {
1060
- console.log(`- systemd user lingering skipped on ${platformLabel()}`);
1066
+ console.log(t("doctor.lingerSkipped", "- systemd user lingering skipped on {value}", { value: platformLabel() }));
1061
1067
  }
1062
1068
  const nativeServicePlanOk = !managedInspectionFailed && (nativeServiceReport?.ok ?? true);
1063
1069
  const pathFailure = !generalReadinessOk || nativeServiceReport?.pathAdviceRecommended === true;
1064
1070
  if (pathFailure) {
1065
- console.log("\nIf a command works in your terminal but fails in the service-manager check, compare the caller and manager contexts above.");
1071
+ console.log(t("doctor.compareContexts", "\nIf a command works in your terminal but fails in the service-manager check, compare the caller and manager contexts above."));
1066
1072
  const adviceShell = nativeServiceReport?.pathAdviceRecommended === true && nativeServiceReport.adviceShell !== null
1067
1073
  ? nativeServiceReport.adviceShell
1068
1074
  : detectServiceShell();
1069
- printPathSetupAdvice(adviceShell);
1075
+ printPathSetupAdvice(adviceShell, t);
1070
1076
  }
1071
1077
  if (generalReadinessOk && backend === undefined) {
1072
- console.log(`\n${manualRunAdvice()}`);
1078
+ console.log(`\n${manualRunAdvice(t)}`);
1073
1079
  }
1074
1080
  if (doctorExitCode(generalReadinessOk, nativeServicePlanOk, nodePtyNativeModuleOk && nodePtySpawnHelperOk, runningComponentsOk) !== 0)
1075
1081
  process.exitCode = 1;
1076
1082
  }
1077
- function printNodePtyNativeModuleCheck() {
1078
- const result = formatNodePtyNativeModuleCheck(checkNodePtyNativeModule());
1083
+ function printNodePtyNativeModuleCheck(t) {
1084
+ const result = formatNodePtyNativeModuleCheck(checkNodePtyNativeModule(), t);
1079
1085
  for (const line of result.lines)
1080
1086
  console.log(line);
1081
1087
  return result.ok;
1082
1088
  }
1083
- function printNodePtyDarwinSpawnHelperCheck() {
1084
- const result = formatNodePtyDarwinSpawnHelperCheck(checkNodePtyDarwinSpawnHelper());
1089
+ function printNodePtyDarwinSpawnHelperCheck(t) {
1090
+ const result = formatNodePtyDarwinSpawnHelperCheck(checkNodePtyDarwinSpawnHelper(), t);
1085
1091
  for (const line of result.lines)
1086
1092
  console.log(line);
1087
1093
  return result.ok;
@@ -1089,8 +1095,8 @@ function printNodePtyDarwinSpawnHelperCheck() {
1089
1095
  function isRecord(value) {
1090
1096
  return typeof value === "object" && value !== null && !Array.isArray(value);
1091
1097
  }
1092
- function help() {
1093
- console.log(`PI WEB
1098
+ export function cliHelp(t) {
1099
+ return t("help", `PI WEB
1094
1100
 
1095
1101
  Usage:
1096
1102
  pi-web install [--dev] [--host 127.0.0.1] [--port 8504] [--config ~/.config/pi-web/config.json]
@@ -1114,30 +1120,31 @@ Development service install from a checkout:
1114
1120
  `);
1115
1121
  }
1116
1122
  async function main() {
1123
+ const t = loadNodeTranslator("cli");
1117
1124
  const [command = "help", ...args] = process.argv.slice(2);
1118
1125
  if (command === "install")
1119
- await install(args);
1126
+ await install(args, t);
1120
1127
  else if (command === "uninstall")
1121
- await uninstall();
1128
+ await uninstall(t);
1122
1129
  else if (command === "start" || command === "restart" || command === "doctor" || command === "version") {
1123
- await runReadinessCliCommand(command, readinessCliCommandDependencies());
1130
+ await runReadinessCliCommand(command, readinessCliCommandDependencies(t));
1124
1131
  }
1125
1132
  else if (command === "stop" || command === "status")
1126
- await serviceAction(command);
1133
+ await serviceAction(command, t);
1127
1134
  else if (command === "logs")
1128
- logs();
1135
+ logs(t);
1129
1136
  else if (command === "audit") {
1130
1137
  const { runNormalToolAuditCommand } = await import("./server/audit/normalToolAuditCli.js");
1131
1138
  runNormalToolAuditCommand(args);
1132
1139
  }
1133
1140
  else if (command === "plugins")
1134
- runPluginRecoveryCli(args, { restartPlan: (context) => sessionDaemonRestartPlan(context) });
1141
+ runPluginRecoveryCli(args, { restartPlan: (context) => sessionDaemonRestartPlan(context) }, t);
1135
1142
  else if (command === "--version" || command === "-v")
1136
1143
  console.log(packageVersion());
1137
1144
  else if (command === "help" || command === "--help" || command === "-h")
1138
- help();
1145
+ console.log(cliHelp(t));
1139
1146
  else
1140
- throw new Error(`Unknown command: ${command}`);
1147
+ throw new Error(t("cli.unknownCommand", "Unknown command: {command}", { command: command }));
1141
1148
  }
1142
1149
  export function isCliEntrypoint(entrypoint = process.argv[1], modulePath = fileURLToPath(import.meta.url)) {
1143
1150
  if (entrypoint === undefined)