@songsid/agend 2.3.0-beta.22 → 2.3.0-beta.24

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 (298) hide show
  1. package/README.md +2 -2
  2. package/README.zh-TW.md +2 -2
  3. package/dist/agent-cli.js +4 -2
  4. package/dist/agent-cli.js.map +1 -1
  5. package/dist/agent-endpoint.d.ts +1 -0
  6. package/dist/agent-endpoint.js +4 -0
  7. package/dist/agent-endpoint.js.map +1 -1
  8. package/dist/agent-whats-new.d.ts +5 -0
  9. package/dist/agent-whats-new.js +16 -1
  10. package/dist/agent-whats-new.js.map +1 -1
  11. package/dist/auth-api.d.ts +7 -0
  12. package/dist/auth-api.js +54 -21
  13. package/dist/auth-api.js.map +1 -1
  14. package/dist/backend/codex-package.d.ts +30 -0
  15. package/dist/backend/codex-package.js +133 -0
  16. package/dist/backend/codex-package.js.map +1 -0
  17. package/dist/backend/codex.d.ts +2 -0
  18. package/dist/backend/codex.js +9 -2
  19. package/dist/backend/codex.js.map +1 -1
  20. package/dist/backend/grok.js +3 -1
  21. package/dist/backend/grok.js.map +1 -1
  22. package/dist/backend/kiro.d.ts +9 -1
  23. package/dist/backend/kiro.js +102 -1
  24. package/dist/backend/kiro.js.map +1 -1
  25. package/dist/backend/opencode-permission.js +1 -1
  26. package/dist/backend/opencode-permission.js.map +1 -1
  27. package/dist/backend/opencode.js +2 -1
  28. package/dist/backend/opencode.js.map +1 -1
  29. package/dist/binary-probe.js +1 -1
  30. package/dist/bug-report-cli.js +11 -10
  31. package/dist/bug-report-cli.js.map +1 -1
  32. package/dist/bug-report.js +11 -10
  33. package/dist/bug-report.js.map +1 -1
  34. package/dist/bundled-skills.d.ts +7 -3
  35. package/dist/bundled-skills.js +16 -7
  36. package/dist/bundled-skills.js.map +1 -1
  37. package/dist/channel/adapters/discord.js +3 -3
  38. package/dist/channel/adapters/discord.js.map +1 -1
  39. package/dist/channel/mcp-server.js +12 -4
  40. package/dist/channel/mcp-server.js.map +1 -1
  41. package/dist/channel/mcp-tools.d.ts +3 -1
  42. package/dist/channel/mcp-tools.js +36 -0
  43. package/dist/channel/mcp-tools.js.map +1 -1
  44. package/dist/child-process.d.ts +21 -0
  45. package/dist/child-process.js +54 -0
  46. package/dist/child-process.js.map +1 -0
  47. package/dist/cli.js +188 -204
  48. package/dist/cli.js.map +1 -1
  49. package/dist/command-table.d.ts +1 -1
  50. package/dist/command-table.js +1 -1
  51. package/dist/command-table.js.map +1 -1
  52. package/dist/config-validator.js +20 -1
  53. package/dist/config-validator.js.map +1 -1
  54. package/dist/config.js +3 -1
  55. package/dist/config.js.map +1 -1
  56. package/dist/daemon.d.ts +8 -0
  57. package/dist/daemon.js +31 -8
  58. package/dist/daemon.js.map +1 -1
  59. package/dist/darwin-memory.js +1 -1
  60. package/dist/doctor.d.ts +5 -0
  61. package/dist/doctor.js +42 -0
  62. package/dist/doctor.js.map +1 -1
  63. package/dist/export-import.js +4 -3
  64. package/dist/export-import.js.map +1 -1
  65. package/dist/fleet-level-config.d.ts +1 -1
  66. package/dist/fleet-level-config.js +2 -0
  67. package/dist/fleet-level-config.js.map +1 -1
  68. package/dist/fleet-manager.d.ts +45 -0
  69. package/dist/fleet-manager.js +324 -36
  70. package/dist/fleet-manager.js.map +1 -1
  71. package/dist/full-restart.d.ts +5 -5
  72. package/dist/full-restart.js +5 -5
  73. package/dist/full-restart.js.map +1 -1
  74. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +48 -10
  75. package/dist/general-knowledge/skills/fleet-config/SKILL.md +1 -1
  76. package/dist/general-knowledge/skills/fleet-restart/SKILL.md +7 -8
  77. package/dist/general-knowledge/skills/multi-channel/SKILL.md +1 -1
  78. package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +319 -0
  79. package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +529 -0
  80. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +17 -4
  81. package/dist/general-knowledge/steering/core-rules.md +1 -1
  82. package/dist/instance-config-impact.js +10 -3
  83. package/dist/instance-config-impact.js.map +1 -1
  84. package/dist/instance-lifecycle.js +4 -4
  85. package/dist/locale.js +296 -4
  86. package/dist/locale.js.map +1 -1
  87. package/dist/login-flows.js +1 -1
  88. package/dist/login-shell.js +3 -1
  89. package/dist/login-shell.js.map +1 -1
  90. package/dist/ls-rows.js +1 -1
  91. package/dist/needs-you-hub.d.ts +98 -1
  92. package/dist/needs-you-hub.js +313 -44
  93. package/dist/needs-you-hub.js.map +1 -1
  94. package/dist/needs-you-live.d.ts +5 -2
  95. package/dist/needs-you-live.js +6 -3
  96. package/dist/needs-you-live.js.map +1 -1
  97. package/dist/needs-you.d.ts +58 -3
  98. package/dist/needs-you.js +36 -3
  99. package/dist/needs-you.js.map +1 -1
  100. package/dist/outbound-schemas.d.ts +3 -3
  101. package/dist/public-link-progress.d.ts +12 -1
  102. package/dist/public-link-progress.js +5 -10
  103. package/dist/public-link-progress.js.map +1 -1
  104. package/dist/public-tunnel-config.d.ts +5 -0
  105. package/dist/public-tunnel-config.js +45 -1
  106. package/dist/public-tunnel-config.js.map +1 -1
  107. package/dist/public-web-link.js +6 -5
  108. package/dist/public-web-link.js.map +1 -1
  109. package/dist/quickstart.js +15 -9
  110. package/dist/quickstart.js.map +1 -1
  111. package/dist/resource-report.js +1 -1
  112. package/dist/scheduler/task-board.d.ts +41 -1
  113. package/dist/scheduler/task-board.js +152 -5
  114. package/dist/scheduler/task-board.js.map +1 -1
  115. package/dist/sd-notify.js +2 -1
  116. package/dist/sd-notify.js.map +1 -1
  117. package/dist/service-installer.d.ts +69 -0
  118. package/dist/service-installer.js +182 -0
  119. package/dist/service-installer.js.map +1 -1
  120. package/dist/service-owner.d.ts +44 -0
  121. package/dist/service-owner.js +48 -0
  122. package/dist/service-owner.js.map +1 -0
  123. package/dist/service-plan.d.ts +8 -0
  124. package/dist/service-plan.js +13 -0
  125. package/dist/service-plan.js.map +1 -1
  126. package/dist/settings-api.d.ts +4 -0
  127. package/dist/settings-api.js +21 -3
  128. package/dist/settings-api.js.map +1 -1
  129. package/dist/settings-change.js +88 -4
  130. package/dist/settings-change.js.map +1 -1
  131. package/dist/settings-confirmation.d.ts +2 -0
  132. package/dist/settings-confirmation.js +14 -8
  133. package/dist/settings-confirmation.js.map +1 -1
  134. package/dist/settings-control.js +2 -1
  135. package/dist/settings-control.js.map +1 -1
  136. package/dist/settings-effect.js +2 -0
  137. package/dist/settings-effect.js.map +1 -1
  138. package/dist/settings-http-confirmation.d.ts +2 -0
  139. package/dist/settings-http-confirmation.js +3 -2
  140. package/dist/settings-http-confirmation.js.map +1 -1
  141. package/dist/settings-request-capability.d.ts +4 -1
  142. package/dist/settings-request-capability.js +11 -3
  143. package/dist/settings-request-capability.js.map +1 -1
  144. package/dist/setup-auth.d.ts +1 -0
  145. package/dist/setup-auth.js +2 -1
  146. package/dist/setup-auth.js.map +1 -1
  147. package/dist/setup-host.d.ts +1 -0
  148. package/dist/setup-host.js +7 -3
  149. package/dist/setup-host.js.map +1 -1
  150. package/dist/setup-wizard.js +1 -1
  151. package/dist/setup-wizard.js.map +1 -1
  152. package/dist/task-board-summary.d.ts +101 -0
  153. package/dist/task-board-summary.js +177 -0
  154. package/dist/task-board-summary.js.map +1 -0
  155. package/dist/task-board-waker.d.ts +8 -1
  156. package/dist/task-board-waker.js +15 -6
  157. package/dist/task-board-waker.js.map +1 -1
  158. package/dist/tmux-control.js +1 -1
  159. package/dist/tmux-control.js.map +1 -1
  160. package/dist/tmux-exec-helper.d.ts +13 -3
  161. package/dist/tmux-exec-helper.js +34 -29
  162. package/dist/tmux-exec-helper.js.map +1 -1
  163. package/dist/tmux-manager.js +1 -1
  164. package/dist/tmux-read.js +1 -1
  165. package/dist/tmux-read.js.map +1 -1
  166. package/dist/topic-commands.d.ts +3 -0
  167. package/dist/topic-commands.js +29 -18
  168. package/dist/topic-commands.js.map +1 -1
  169. package/dist/transcript-monitor.js +5 -0
  170. package/dist/transcript-monitor.js.map +1 -1
  171. package/dist/transcript-sources.d.ts +2 -0
  172. package/dist/transcript-sources.js +4 -0
  173. package/dist/transcript-sources.js.map +1 -1
  174. package/dist/tunnel/cloudflared-install.js +3 -1
  175. package/dist/tunnel/cloudflared-install.js.map +1 -1
  176. package/dist/tunnel/cloudflared.d.ts +1 -1
  177. package/dist/tunnel/cloudflared.js +51 -10
  178. package/dist/tunnel/cloudflared.js.map +1 -1
  179. package/dist/tunnel/named-diagnostics.d.ts +13 -0
  180. package/dist/tunnel/named-diagnostics.js +52 -0
  181. package/dist/tunnel/named-diagnostics.js.map +1 -0
  182. package/dist/tunnel/types.d.ts +2 -1
  183. package/dist/tunnel/types.js.map +1 -1
  184. package/dist/types.d.ts +9 -0
  185. package/dist/ui/app-effects.js +10 -4
  186. package/dist/ui/chat-strings.js +3 -0
  187. package/dist/ui/chat-thread.js +14 -7
  188. package/dist/ui/effect-trial-offer.js +2 -31
  189. package/dist/ui/effects-waker.js +2 -70
  190. package/dist/ui/fleet-task-card.js +5 -5
  191. package/dist/ui/fleet-task-common.js +43 -9
  192. package/dist/ui/fleet-task-work.js +107 -0
  193. package/dist/ui/fleet-tasks.js +46 -12
  194. package/dist/ui/image-lightbox.js +39 -1
  195. package/dist/ui/image-zoom.js +242 -0
  196. package/dist/ui/panel-chat.js +24 -13
  197. package/dist/ui/panel-details.js +2 -0
  198. package/dist/ui/panel-fleet.js +6 -2
  199. package/dist/ui/panel-needs.js +21 -6
  200. package/dist/ui/panel-settings.js +35 -13
  201. package/dist/ui/preview-panel.js +2 -1
  202. package/dist/ui/search-dialog.js +2 -1
  203. package/dist/ui/settings-appearance.js +44 -10
  204. package/dist/ui/settings-dialogs.js +33 -17
  205. package/dist/ui/settings-discord.js +6 -2
  206. package/dist/ui/settings-guests.js +2 -1
  207. package/dist/ui/settings-model.js +2 -11
  208. package/dist/ui/settings-mods.js +8 -3
  209. package/dist/ui/settings-strings.js +32 -0
  210. package/dist/ui/settings-token.js +3 -1
  211. package/dist/ui/settings-web-authority.js +6 -3
  212. package/dist/ui/settings-wizard.js +13 -7
  213. package/dist/ui/shared/app-i18n.js +2 -2
  214. package/dist/ui/shared/app-needs.js +2 -2
  215. package/dist/ui/shared/app-store.js +2 -2
  216. package/dist/ui/shared/app.css +93 -32
  217. package/dist/ui/shared/app.js +5 -1
  218. package/dist/ui/shared/effect-app-runtime.js +7 -5
  219. package/dist/ui/shared/effect-chat-runtime.js +2 -7
  220. package/dist/ui/shared/effect-cursor.js +3 -2
  221. package/dist/ui/shared/effect-display-metadata.js +39 -0
  222. package/dist/ui/shared/effect-registry.js +4 -2
  223. package/dist/ui/shared/effect-runtime-loader.js +3 -1
  224. package/dist/ui/shared/effect-stock-trust.js +5 -5
  225. package/dist/ui/shared/feature-flags.js +127 -0
  226. package/dist/ui/shared/identity-color.js +6 -3
  227. package/dist/ui/shared/instance-nav.js +3 -2
  228. package/dist/ui/shared/panel-view.js +14 -29
  229. package/dist/ui/shared/text-undo.js +210 -0
  230. package/dist/ui/shared/ui-dialog.js +4 -3
  231. package/dist/ui/shared/ui-icons.js +2 -0
  232. package/dist/ui/shared/usage-dialog.js +29 -8
  233. package/dist/ui/shared/view-strings.js +2 -4
  234. package/dist/ui/web-terminal/terminal.html +10 -10
  235. package/dist/ui/web-terminal/terminal.js +58 -12
  236. package/dist/update-dispatch.js +1 -1
  237. package/dist/update-launch.d.ts +1 -1
  238. package/dist/update-launch.js +1 -2
  239. package/dist/update-launch.js.map +1 -1
  240. package/dist/update-preflight.js +1 -1
  241. package/dist/usage/providers.js +1 -1
  242. package/dist/usage/providers.js.map +1 -1
  243. package/dist/view-api.js +1 -1
  244. package/dist/web-api.d.ts +6 -0
  245. package/dist/web-api.js +163 -21
  246. package/dist/web-api.js.map +1 -1
  247. package/dist/web-app-icon-worker.js +2 -1
  248. package/dist/web-app-icon-worker.js.map +1 -1
  249. package/dist/web-app-icon.d.ts +10 -8
  250. package/dist/web-app-icon.js +14 -13
  251. package/dist/web-app-icon.js.map +1 -1
  252. package/dist/web-appearance-tools.d.ts +1 -0
  253. package/dist/web-appearance-tools.js +7 -3
  254. package/dist/web-appearance-tools.js.map +1 -1
  255. package/dist/web-appearance.d.ts +4 -10
  256. package/dist/web-appearance.js +4 -3
  257. package/dist/web-appearance.js.map +1 -1
  258. package/dist/web-auth.d.ts +8 -4
  259. package/dist/web-auth.js +28 -17
  260. package/dist/web-auth.js.map +1 -1
  261. package/dist/web-chat-history.d.ts +14 -0
  262. package/dist/web-chat-history.js +26 -1
  263. package/dist/web-chat-history.js.map +1 -1
  264. package/dist/web-cookie-names.d.ts +5 -0
  265. package/dist/web-cookie-names.js +12 -0
  266. package/dist/web-cookie-names.js.map +1 -0
  267. package/dist/web-experimental-http.d.ts +2 -0
  268. package/dist/web-experimental-http.js +12 -0
  269. package/dist/web-experimental-http.js.map +1 -0
  270. package/dist/web-experimental.d.ts +11 -0
  271. package/dist/web-experimental.js +49 -0
  272. package/dist/web-experimental.js.map +1 -0
  273. package/dist/web-guest-access.d.ts +1 -0
  274. package/dist/web-guest-access.js +4 -2
  275. package/dist/web-guest-access.js.map +1 -1
  276. package/dist/web-mod-store.d.ts +2 -0
  277. package/dist/web-mod-store.js +10 -0
  278. package/dist/web-mod-store.js.map +1 -1
  279. package/dist/web-png.d.ts +4 -0
  280. package/dist/web-png.js +10 -2
  281. package/dist/web-png.js.map +1 -1
  282. package/dist/web-preview.d.ts +2 -0
  283. package/dist/web-preview.js +2 -0
  284. package/dist/web-preview.js.map +1 -1
  285. package/dist/web-returning-device.d.ts +4 -2
  286. package/dist/web-returning-device.js +12 -5
  287. package/dist/web-returning-device.js.map +1 -1
  288. package/dist/web-settings-patch.d.ts +2 -1
  289. package/dist/web-settings-patch.js +5 -1
  290. package/dist/web-settings-patch.js.map +1 -1
  291. package/dist/web-settings-tools.d.ts +1 -0
  292. package/dist/web-settings-tools.js +14 -10
  293. package/dist/web-settings-tools.js.map +1 -1
  294. package/dist/web-terminal.js +1 -1
  295. package/dist/web-theme-store.d.ts +2 -2
  296. package/dist/web-theme-store.js +6 -3
  297. package/dist/web-theme-store.js.map +1 -1
  298. package/package.json +11 -11
package/dist/cli.js CHANGED
@@ -13,7 +13,7 @@ applyNetworkReliabilityDefaults();
13
13
  // never restarts into a binary that would crash at the first native DB open.
14
14
  import { checkNodeVersion } from "./node-version-guard.js";
15
15
  checkNodeVersion();
16
- import { Command } from "commander";
16
+ import { Command, Option } from "commander";
17
17
  import { registerBugReportCommand } from "./bug-report-cli.js";
18
18
  import { join, dirname } from "node:path";
19
19
  import { SchedulerDb } from "./scheduler/db.js";
@@ -21,7 +21,8 @@ import { Cron } from "croner";
21
21
  import { existsSync, readFileSync, writeFileSync, appendFileSync, unlinkSync, mkdirSync, chmodSync, readdirSync, rmSync, statSync, realpathSync, openSync, writeSync, closeSync, } from "node:fs";
22
22
  import { homedir, totalmem, freemem, userInfo } from "node:os";
23
23
  import { fileURLToPath } from "node:url";
24
- import { spawn, spawnSync, execSync, execFileSync } from "node:child_process";
24
+ import { spawnSync, execSync, execFileSync } from "node:child_process";
25
+ import { spawn } from "./child-process.js";
25
26
  import { getAgendHome, getTmuxSocketName, isDefaultAgendHome } from "./paths.js";
26
27
  import { hasPausedMarker } from "./pause-marker.js";
27
28
  import { COMPLETION_SHELLS, completionScript } from "./completion.js";
@@ -31,15 +32,17 @@ import { describeSignalSource, forceAllowed, gateFleetControl, recordInstanceCon
31
32
  import { FLEET_STOP_TIMEOUT_MS, stopDetachedOwner, systemdStopTimeoutMs, detachedProcessStart, detachedProcessExited } from "./fleet-stop-budget.js";
32
33
  import { acquireFleetLock, isFleetStartCommandLine, readProcessCommandLine, releaseProcessFleetLock, setProcessFleetLock } from "./fleet-lock.js";
33
34
  import { limitFleetCoreDumps } from "./coredump-filter.js";
34
- import { SYSTEMD_RESTART_TIMEOUT_MS } from "./service-installer.js";
35
+ import { SYSTEMD_RESTART_TIMEOUT_MS, getServicePath, getSystemServicePath } from "./service-installer.js";
35
36
  import { selectSystemdRestartTarget, SYSTEMD_RESTART_INDETERMINATE_EXIT_CODE, } from "./service-restart-selection.js";
36
37
  import { loadRawFleetConfig } from "./config.js";
37
- import { setLocale, t } from "./locale.js";
38
+ import { detectLocale, setLocale, t } from "./locale.js";
39
+ import { settingsSourceLabel } from "./settings-confirmation.js";
38
40
  import { DeliveryOutbox, deliveryStatusSelector } from "./delivery-outbox.js";
39
41
  import { DeliveryStatusArgs } from "./outbound-schemas.js";
40
42
  import { requestCpuProfile } from "./profile-control.js";
41
43
  import { requestSettingsConfirmation } from "./settings-control.js";
42
44
  import { canonicalCliEntry, selfCommand } from "./cli-entry.js";
45
+ import { DIRECT_FLEET_START_ENV, redirectsToService, startedByServiceManager } from "./service-owner.js";
43
46
  import { handOff, shouldHandOff } from "./launchd-handoff.js";
44
47
  /** Prefix tmux args with -L when socket isolation is active. */
45
48
  function tmuxArgs(args) {
@@ -105,14 +108,15 @@ async function handOffLaunchdActivation(action) {
105
108
  return false;
106
109
  }
107
110
  const FLEET_CONFIG_PATH = join(DATA_DIR, "fleet.yaml");
108
- // CLI commands run outside FleetManager. Honor an explicit fleet locale while
109
- // keeping the historical English CLI default when config is missing/unset.
111
+ // CLI commands run outside FleetManager, in the fleet's language as the fleet decides it (#2390 follow-up): an explicit
112
+ // defaults.locale, else the host's zone (UTC+8 → zh-TW), else English. A line another program reads (the updater, the
113
+ // Windows installer and shortcuts, the rehearsal scripts) is never a locale string: tests/cli-machine-lines-2390 pins them.
114
+ let configForLocale;
110
115
  try {
111
- const configuredLocale = loadRawFleetConfig(FLEET_CONFIG_PATH).defaults?.locale;
112
- if (configuredLocale === "en" || configuredLocale === "zh-TW")
113
- setLocale(configuredLocale);
116
+ configForLocale = loadRawFleetConfig(FLEET_CONFIG_PATH);
114
117
  }
115
- catch { /* malformed or missing config: keep the English CLI default */ }
118
+ catch { /* malformed or missing config: the zone decides */ }
119
+ setLocale(detectLocale(configForLocale));
116
120
  const program = new Command();
117
121
  function claimFleetSingleton() {
118
122
  try {
@@ -165,15 +169,15 @@ settingsCommand.command("confirm <id>").option("--yes", "Explicit noninteractive
165
169
  .action(async (id, options) => {
166
170
  try {
167
171
  const inspected = await requestSettingsConfirmation(DATA_DIR, { action: "inspect", id });
168
- console.log(`Source: ${inspected.pending_change.source}\nRequester: ${inspected.pending_change.requested_by}\n${inspected.pending_change.summary.join("\n")}`);
172
+ console.log([t("settings_confirm.source", settingsSourceLabel(inspected.pending_change.source)), t("settings_confirm.requester", inspected.pending_change.requested_by), ...inspected.pending_change.summary].join("\n"));
169
173
  let confirmed = options.yes === true;
170
174
  if (!confirmed) {
171
175
  if (!process.stdin.isTTY)
172
- throw new Error("Interactive confirmation required; --yes explicitly confirms the printed diff.");
176
+ throw new Error(t("settings_confirm.cli_interactive"));
173
177
  const { createInterface } = await import("node:readline/promises");
174
178
  const input = createInterface({ input: process.stdin, output: process.stdout });
175
179
  try {
176
- confirmed = (await input.question("Apply this exact change? [y/N] ")).trim().toLowerCase() === "y";
180
+ confirmed = (await input.question(t("settings_confirm.cli_question"))).trim().toLowerCase() === "y";
177
181
  }
178
182
  finally {
179
183
  input.close();
@@ -189,10 +193,10 @@ settingsCommand.command("confirm <id>").option("--yes", "Explicit noninteractive
189
193
  process.exitCode = 1;
190
194
  }
191
195
  });
192
- settingsCommand.command("reject <id>").action(async (id) => {
196
+ settingsCommand.command("reject <id>").description("Reject a pending Settings change on this host").action(async (id) => {
193
197
  try {
194
198
  const inspected = await requestSettingsConfirmation(DATA_DIR, { action: "inspect", id });
195
- console.log(`Source: ${inspected.pending_change.source}\nRequester: ${inspected.pending_change.requested_by}\n${inspected.pending_change.summary.join("\n")}`);
199
+ console.log([t("settings_confirm.source", settingsSourceLabel(inspected.pending_change.source)), t("settings_confirm.requester", inspected.pending_change.requested_by), ...inspected.pending_change.summary].join("\n"));
196
200
  const result = await requestSettingsConfirmation(DATA_DIR, { action: "reject", ticket: inspected.ticket });
197
201
  console.log(result.pending_change.outcome?.message ?? result.pending_change.state);
198
202
  }
@@ -218,12 +222,24 @@ fleet
218
222
  .command("start")
219
223
  .description("Start fleet or specific instance")
220
224
  .argument("[instance]", "Specific instance to start")
221
- .action(async (instance) => {
225
+ .option("--direct", "Start here even though an AgEnD service is installed (operators only: the service cannot run on this host)")
226
+ .action(async (instance, opts) => {
222
227
  // Root running fleet = sandbox environment (e.g. Docker, WSL root service)
223
228
  // Set IS_SANDBOX so child processes (claude-code) don't refuse to run
224
229
  if (process.getuid?.() === 0 && !process.env.IS_SANDBOX) {
225
230
  process.env.IS_SANDBOX = "1";
226
231
  }
232
+ const marked = process.env[DIRECT_FLEET_START_ENV] === "1";
233
+ delete process.env[DIRECT_FLEET_START_ENV];
234
+ // #2356: the way out when the installed service cannot run here (never activated, no GUI session for launchd) — an
235
+ // operator's own decision, as `restart --force` is; the fleet's singleton lock still refuses a second fleet.
236
+ if (opts?.direct && !forceAllowed()) {
237
+ console.error(t("cli.direct_refused"));
238
+ process.exit(1);
239
+ }
240
+ const direct = marked || !!opts?.direct;
241
+ if (!instance && redirectToService("start", [], direct))
242
+ return;
227
243
  if (instance) {
228
244
  // If fleet daemon is already running, delegate via HTTP API
229
245
  const { loadFleetConfig } = await import("./config.js");
@@ -318,6 +334,8 @@ fleet
318
334
  .argument("[instance]", "Specific instance to stop")
319
335
  .option("-y, --yes", "Confirm when run from a fleet agent session (this affects every instance)")
320
336
  .action(async (instance, opts) => {
337
+ if (!instance && redirectToService("stop", opts?.yes ? ["--yes"] : [], false))
338
+ return;
321
339
  if (instance)
322
340
  recordInstanceControl(DATA_DIR, "instance-stop", instance);
323
341
  else if (!gateFleetControl(DATA_DIR, "fleet-stop", { yes: opts?.yes }))
@@ -351,7 +369,7 @@ fleet
351
369
  }
352
370
  }
353
371
  catch {
354
- console.error(`Cannot connect to fleet (port ${port}). Is the fleet running?`);
372
+ console.error(t("cli.fleet_unreachable", port));
355
373
  process.exit(1);
356
374
  }
357
375
  return;
@@ -392,159 +410,37 @@ fleet
392
410
  });
393
411
  fleet
394
412
  .command("restart")
395
- .description("Graceful restart: wait for instances to idle, then restart")
396
- .argument("[instance]", "Specific instance to restart (immediate, no idle wait)")
397
- .option("--reload", "Full process restart to load new code")
413
+ .description("Deprecated: runs `agend restart` (removed in 2.4)")
414
+ .argument("[instance]", "Restart only this instance (now `agend restart <instance>`)")
415
+ .option("--reload", "Ignored: every full restart is `agend restart`")
398
416
  .option("-y, --yes", "Confirm when run from a fleet agent session (this affects every instance)")
399
- .action(async (instance, opts) => {
417
+ .action((instance, opts) => {
400
418
  if (instance && opts?.reload) {
401
419
  console.error("--reload restarts the entire fleet process. Cannot combine with instance name.");
402
420
  process.exit(1);
403
421
  }
404
- if (instance)
405
- recordInstanceControl(DATA_DIR, "instance-restart", instance);
406
- else if (!gateFleetControl(DATA_DIR, opts?.reload ? "fleet-restart-reload" : "fleet-restart", { yes: opts?.yes }))
407
- process.exit(1);
408
- if (instance) {
409
- // Single instance restart via fleet's HTTP API
410
- const { loadFleetConfig } = await import("./config.js");
411
- const fleet = loadFleetConfig(FLEET_CONFIG_PATH);
412
- const port = fleet.health_port ?? 19280;
413
- let token = "";
414
- try {
415
- token = readFileSync(join(DATA_DIR, "web.token"), "utf-8").trim();
416
- }
417
- catch { /* fleet may not be running */ }
418
- try {
419
- const resp = await fetch(`http://127.0.0.1:${port}/restart/${encodeURIComponent(instance)}`, {
420
- method: "POST",
421
- headers: token ? { "X-Agend-Token": token } : {},
422
- });
423
- const body = await resp.json();
424
- if (resp.ok) {
425
- console.log(`Instance "${instance}" restarted (immediate)`);
426
- }
427
- else {
428
- console.error(`Restart failed: ${body.error ?? resp.statusText}`);
429
- process.exit(1);
430
- }
431
- }
432
- catch (err) {
433
- console.error(`Cannot connect to fleet (port ${port}). Is the fleet running?`);
434
- process.exit(1);
435
- }
436
- return;
437
- }
438
- const pidPath = join(DATA_DIR, "fleet.pid");
439
- if (!existsSync(pidPath)) {
440
- console.error("Fleet is not running (no PID file found)");
441
- process.exit(1);
442
- }
443
- const pid = parseInt(readFileSync(pidPath, "utf-8").trim(), 10);
444
- if (!Number.isInteger(pid) || pid <= 0) {
445
- console.error(`Fleet PID file at ${pidPath} is corrupted`);
446
- process.exit(1);
447
- }
448
- if (opts?.reload) {
449
- // Check if managed by launchd — if so, just signal and let launchd restart
450
- let managedByLaunchd = false;
451
- try {
452
- const ppid = parseInt(execFileSync("ps", ["-o", "ppid=", "-p", String(pid)]).toString().trim(), 10);
453
- managedByLaunchd = ppid === 1;
454
- }
455
- catch { /* ignore */ }
456
- try {
457
- process.kill(pid, "SIGUSR1");
458
- }
459
- catch {
460
- console.error("Failed to send reload signal (process may have exited)");
461
- process.exit(1);
462
- }
463
- if (managedByLaunchd) {
464
- console.log("Fleet is managed by launchd — sent reload signal.");
465
- console.log("launchd will automatically restart with new code.");
466
- // Wait briefly for old process to exit, then confirm new one started
467
- const deadline = Date.now() + 6 * 60 * 1000;
468
- while (Date.now() < deadline) {
469
- try {
470
- process.kill(pid, 0);
471
- await new Promise(r => setTimeout(r, 500));
472
- }
473
- catch {
474
- break;
475
- }
476
- }
477
- // Wait for launchd to start new process
478
- await new Promise(r => setTimeout(r, 2000));
479
- if (existsSync(pidPath)) {
480
- const newPid = parseInt(readFileSync(pidPath, "utf-8").trim(), 10);
481
- if (newPid !== pid) {
482
- console.log(`New fleet process started (PID ${newPid})`);
483
- }
484
- }
485
- return;
486
- }
487
- console.log("Full restart signal sent — waiting for fleet to stop...");
488
- // Wait for old process to exit (up to 6 minutes: 5 min idle wait + buffer)
489
- const deadline = Date.now() + 6 * 60 * 1000;
490
- while (Date.now() < deadline) {
491
- try {
492
- process.kill(pid, 0); // check if alive
493
- await new Promise(r => setTimeout(r, 500));
494
- }
495
- catch {
496
- break; // process exited
497
- }
498
- }
499
- // Verify it actually exited
500
- try {
501
- process.kill(pid, 0);
502
- console.error("Old fleet process still running after timeout — aborting");
503
- process.exit(1);
504
- }
505
- catch {
506
- // Good, it exited
507
- }
508
- console.log("Old fleet stopped. Starting with new code...");
509
- // Start new fleet in this process (new Node.js process = new code loaded)
510
- if (!claimFleetSingleton())
511
- return;
512
- const { FleetManager } = await import("./fleet-manager.js");
513
- const fm = new FleetManager(DATA_DIR);
514
- const handlers = installFleetProcessHandlers({
515
- dataDir: DATA_DIR, manager: fm, runtime: process, console,
516
- describeSignal: () => describeSignalSource(DATA_DIR, "SIGTERM"),
517
- });
518
- if (handlers.stopping)
519
- return;
520
- await fm.startAll(FLEET_CONFIG_PATH);
521
- console.log("Fleet restarted with new code");
522
- }
523
- else {
524
- // Instance-only restart (existing behavior)
525
- try {
526
- process.kill(pid, "SIGUSR2");
527
- console.log("Graceful restart signal sent — fleet will restart when all instances are idle");
528
- }
529
- catch {
530
- console.error("Failed to send restart signal (process may have exited)");
531
- process.exit(1);
532
- }
533
- }
422
+ // #2356: no restart of its own any more. Its SIGUSR1 reload made the fleet exit 0 and took over in this process, so
423
+ // under a Restart=on-failure unit the fleet stayed down (and this process, in an agent pane, died with the unit's
424
+ // cgroup). It runs `agend restart` itself — this Node, this CLI — with the same gate, guard and exit code.
425
+ const args = instance ? ["restart", instance] : ["restart", ...(opts?.yes ? ["--yes"] : [])];
426
+ console.error(t("cli.fleet_restart_deprecated", args.join(" ")));
427
+ const self = selfCommand(args);
428
+ const result = spawnSync(self.command, self.args, { stdio: "inherit" });
429
+ process.exit(result.status ?? 1);
534
430
  });
535
431
  fleet
536
- .command("status")
537
- .description("Show fleet status, workspace disk usage, unregistered instance directories, and host memory/swap")
432
+ .command("status", { hidden: true })
433
+ .description("Alias of `agend ls --resources`")
538
434
  .option("--json", "Output as JSON")
539
435
  .action(async (opts) => {
540
- // Delegate to the `ls` command implementation
541
436
  await lsAction({ ...opts, resources: true });
542
437
  });
543
438
  fleet
544
- .command("logs")
545
- .description("Alias for `agend logs`")
439
+ .command("logs", { hidden: true })
440
+ .description("Removed; use `agend logs`")
546
441
  .action(() => {
547
- console.log("Use `agend logs` instead. Run `agend logs --help` for options.");
442
+ console.error(t("cli.fleet_logs_removed"));
443
+ process.exitCode = 1;
548
444
  });
549
445
  fleet
550
446
  .command("history")
@@ -876,9 +772,6 @@ backend
876
772
  console.error(`Unknown backend: ${backendName}. Available: ${BACKENDS.map(b => b.id).join(", ")}`);
877
773
  process.exit(1);
878
774
  }
879
- if (info.deprecated) {
880
- console.warn(`⚠️ ${info.label} is deprecated (stops 2026-06-18). Consider switching to backend: antigravity`);
881
- }
882
775
  let issues = 0;
883
776
  const ok = (msg) => console.log(` \x1b[32m✓\x1b[0m ${msg}`);
884
777
  const fail = (msg) => { issues++; console.log(` \x1b[31m✗\x1b[0m ${msg}`); };
@@ -1142,7 +1035,7 @@ async function doctorMcp() {
1142
1035
  }
1143
1036
  if (!ipcOk) {
1144
1037
  issues.push("IPC ✗ (socket not responding)");
1145
- suggestions.push("agend fleet restart");
1038
+ suggestions.push("agend restart");
1146
1039
  }
1147
1040
  // 2. MCP config check
1148
1041
  let mcpOk = false;
@@ -1490,7 +1383,7 @@ program
1490
1383
  // #2264 review: the old system copy is retired only for a confirmed 2.1-era layout — the copy this updater runs
1491
1384
  // from is a 2.1.x release (2.1's updater left it there). Never from "nvm" or "root" alone: a 2.2+ copy stays.
1492
1385
  if (verified.retireSystemCopy && !/^2\.1\./.test(pkgVersion)) {
1493
- console.log(` Note: the system install (v${pkgVersion}) is kept; only a 2.1-era copy is retired after a move to nvm.`);
1386
+ console.log(t("cli.system_install_kept", pkgVersion));
1494
1387
  delete verified.retireSystemCopy;
1495
1388
  }
1496
1389
  if (verified.retireSystemCopy) {
@@ -1763,7 +1656,7 @@ program
1763
1656
  };
1764
1657
  const viaInstaller = !needsSudo && targetVersion !== null && shipsInstaller();
1765
1658
  if (!needsSudo && !viaInstaller)
1766
- console.log(` ℹ ${pkg} has no installer of its own (a release from before #2264); this updater installs it.`);
1659
+ console.log(t("cli.no_own_installer", pkg));
1767
1660
  if (viaInstaller) {
1768
1661
  console.log(`\n Updating AgEnD v${pkgVersion} → v${targetVersion} (its own installer)...\n`);
1769
1662
  markUpdateInProgress(DATA_DIR);
@@ -1894,9 +1787,12 @@ program
1894
1787
  program
1895
1788
  .command("install")
1896
1789
  .description("Install the AgEnD system service and start it by default")
1897
- .option("--activate", "Deprecated; service activation is now the default")
1790
+ .addOption(new Option("--activate").hideHelp())
1898
1791
  .option("--no-activate", "Write the service file without activating it")
1899
1792
  .action(async (opts) => {
1793
+ if (process.argv.includes("--activate")) {
1794
+ console.error(t("cli.install_activate_removed"));
1795
+ }
1900
1796
  const { installService, activateService, detectPlatform, renderLaunchdPlist, launchdPlistPath, getServicePath, getSystemServicePath, serviceHomeFor } = await import("./service-installer.js");
1901
1797
  // #1631 B: an older updater (2.2.0-beta.1 and before) refreshes a hand-started fleet's service too, through this
1902
1798
  // command — which would CREATE a unit or plist, and every later update would then take a manager it may not reach
@@ -1977,6 +1873,13 @@ program
1977
1873
  process.exitCode = 1;
1978
1874
  }
1979
1875
  }
1876
+ // #2331: a host whose fleet runs under the system unit gets no second, user unit from the refresh — it would only
1877
+ // sit beside it unused, and be named as a leftover on every update after.
1878
+ if (systemUnit) {
1879
+ console.log(t("install.path", systemUnit));
1880
+ console.log(t("install.updated"));
1881
+ return;
1882
+ }
1980
1883
  const svcPath = installService(serviceVars);
1981
1884
  console.log(t("install.path", svcPath));
1982
1885
  if (opts.activate !== false) {
@@ -2075,7 +1978,17 @@ program
2075
1978
  .action(async (opts) => {
2076
1979
  if (!gateFleetControl(DATA_DIR, "stop", { yes: opts.yes }))
2077
1980
  process.exit(1);
2078
- const { getServicePath, stopService } = await import("./service-installer.js");
1981
+ const { getServicePath, getSystemServicePath, stopService, stopSystemService } = await import("./service-installer.js");
1982
+ // #2356: the system unit first, as `agend start` and `agend restart` choose it; a SIGTERM would only stop the process.
1983
+ if (getSystemServicePath()) {
1984
+ if (stopSystemService()) {
1985
+ console.log(t("cli.system_service_stopped"));
1986
+ return;
1987
+ }
1988
+ console.error(t("cli.system_service_stop_failed"));
1989
+ process.exitCode = 1;
1990
+ return;
1991
+ }
2079
1992
  if (!getServicePath()) {
2080
1993
  // No service — try killing by PID
2081
1994
  const pidPath = join(DATA_DIR, "fleet.pid");
@@ -2124,10 +2037,10 @@ program
2124
2037
  const systemServicePath = getSystemServicePath();
2125
2038
  if (!userServicePath && !systemServicePath) {
2126
2039
  console.log("No service installed. Starting fleet directly...");
2127
- const { spawn } = await import("node:child_process");
2040
+ const { spawn } = await import("./child-process.js");
2128
2041
  // This Node on this CLI, not `sh -c "agend fleet start"` through PATH (#1450 C5).
2129
2042
  const start = selfCommand(["fleet", "start"]);
2130
- const child = spawn(start.command, start.args, { detached: true, stdio: "ignore" });
2043
+ const child = spawn(start.command, start.args, { detached: true, stdio: "ignore", env: { ...process.env, [DIRECT_FLEET_START_ENV]: "1" } });
2131
2044
  child.unref();
2132
2045
  console.log("Fleet starting in background.");
2133
2046
  return;
@@ -2140,16 +2053,79 @@ program
2140
2053
  // A service file means the service manager owns this fleet. Falling back
2141
2054
  // to a detached process here creates a second fleet when the bus recovers.
2142
2055
  console.error("Failed to start the installed AgEnD service; refusing to start a detached duplicate.");
2143
- console.error("Check your service manager, then retry: systemctl --user status com.agend.fleet");
2056
+ console.error(t("cli.service_retry", process.platform === "darwin" ? "launchctl print gui/$(id -u)/com.agend.fleet" : systemServicePath ? "systemctl status agend" : "systemctl --user status com.agend.fleet"));
2057
+ // #2356: a service file that cannot run here (never activated, no session for it) would otherwise leave no start.
2058
+ console.error(t("cli.service_direct_hint"));
2144
2059
  process.exitCode = 1;
2145
2060
  }
2146
2061
  });
2062
+ /**
2063
+ * #2356 (user decision): on a host with an AgEnD service installed, a hand-run `fleet start` / `fleet stop` would run or
2064
+ * kill a fleet the service manager does not own — a duplicate beside the unit, or a stop systemd undoes. Run `agend
2065
+ * start` / `agend stop` instead (this Node, this CLI; their own gate and exit code). The manager's own `fleet start`
2066
+ * (the unit's ExecStart, the launchd job) and AgEnD's marked detached start go ahead directly; so does every host
2067
+ * without a service (Docker, manual setups). Exits when it redirected; false to go on.
2068
+ */
2069
+ function redirectToService(command, extra, directMarker) {
2070
+ let ownCgroup = null;
2071
+ try {
2072
+ ownCgroup = readFileSync("/proc/self/cgroup", "utf-8");
2073
+ }
2074
+ catch { /* not Linux */ }
2075
+ const byServiceManager = startedByServiceManager({ platform: process.platform, env: process.env, ownCgroup, ppid: process.ppid, launchdLabel: "com.agend.fleet" });
2076
+ const installed = getSystemServicePath() ?? getServicePath();
2077
+ if (!redirectsToService({ command, directMarker, byServiceManager, installedServicePath: installed }))
2078
+ return false;
2079
+ console.error(t("cli.service_redirect", String(installed), [command, ...extra].join(" ")));
2080
+ const self = selfCommand([command, ...extra]);
2081
+ const result = spawnSync(self.command, self.args, { stdio: "inherit" });
2082
+ process.exit(result.status ?? 1);
2083
+ }
2084
+ /** One instance, at once, through the running fleet's HTTP API; the fleet process keeps running. */
2085
+ async function restartOneInstance(instance) {
2086
+ recordInstanceControl(DATA_DIR, "instance-restart", instance);
2087
+ const { loadFleetConfig } = await import("./config.js");
2088
+ const fleet = loadFleetConfig(FLEET_CONFIG_PATH);
2089
+ const port = fleet.health_port ?? 19280;
2090
+ let token = "";
2091
+ try {
2092
+ token = readFileSync(join(DATA_DIR, "web.token"), "utf-8").trim();
2093
+ }
2094
+ catch { /* fleet may not be running */ }
2095
+ try {
2096
+ const resp = await fetch(`http://127.0.0.1:${port}/restart/${encodeURIComponent(instance)}`, {
2097
+ method: "POST",
2098
+ headers: token ? { "X-Agend-Token": token } : {},
2099
+ });
2100
+ const body = await resp.json();
2101
+ if (resp.ok) {
2102
+ console.log(t("cli.instance_restarted", instance));
2103
+ }
2104
+ else {
2105
+ console.error(t("cli.restart_failed", String(body.error ?? resp.statusText)));
2106
+ process.exit(1);
2107
+ }
2108
+ }
2109
+ catch {
2110
+ console.error(t("cli.fleet_unreachable", port));
2111
+ process.exit(1);
2112
+ }
2113
+ }
2147
2114
  program
2148
2115
  .command("restart")
2149
- .description("Restart the AgEnD service (auto-detects systemd/launchd/detached)")
2116
+ .description("Restart the AgEnD service (auto-detects systemd/launchd/detached) — the one full restart; or one instance")
2117
+ .argument("[instance]", "Restart only this instance, immediately; the fleet keeps running")
2150
2118
  .option("-y, --yes", "Confirm when run from a fleet agent session (this affects every instance)")
2151
2119
  .option("--force", "Restart even when the loaded service does not start this install on its selected Node (operators only)")
2152
- .action(async (opts) => {
2120
+ .action(async (instance, opts) => {
2121
+ if (instance) {
2122
+ if (opts.force) {
2123
+ console.error(t("cli.force_whole_service"));
2124
+ process.exit(1);
2125
+ }
2126
+ await restartOneInstance(instance);
2127
+ return;
2128
+ }
2153
2129
  // --force overrides the C6 guard: an operator's own decision, never a fleet agent's or a fleet-internal spawn's.
2154
2130
  if (opts.force && !forceAllowed()) {
2155
2131
  console.error(" ✗ Not restarting: --force is for an operator's own shell, not a fleet agent session or the fleet's own commands. Nothing was stopped.");
@@ -2199,8 +2175,9 @@ program
2199
2175
  // service's own HOME, which may differ from what this command resolves (sudo,
2200
2176
  // AGEND_HOME) — an absent pid file does NOT mean it's stopped. Ask the service
2201
2177
  // manager first. This is the canonical restart the `update` flow spawns.
2202
- const { detectPlatform, getServicePath, getSystemServicePath, getSystemdServiceState, systemdRestartOutcome, ensureSystemdUnitHardening, effectiveUnitDropIns, unitCoredumpFilterState, } = await import("./service-installer.js");
2203
- const { spawn, spawnSync } = await import("node:child_process");
2178
+ const { detectPlatform, getServicePath, getSystemServicePath, getSystemdServiceState, systemdRestartOutcome, ensureSystemdUnitHardening, effectiveUnitDropIns, unitCoredumpFilterState, loadedUnitCheck, } = await import("./service-installer.js");
2179
+ const { spawnSync } = await import("node:child_process");
2180
+ const { spawn } = await import("./child-process.js");
2204
2181
  const plat = detectPlatform();
2205
2182
  const pidPath = join(DATA_DIR, "fleet.pid");
2206
2183
  const run = (cmd) => {
@@ -2329,7 +2306,11 @@ program
2329
2306
  .filter(([key, state]) => key !== "KillMode" && (state === "added" || state === "upgraded"))
2330
2307
  .map(([key]) => key);
2331
2308
  if (updated.length > 0)
2332
- console.log(` ✓ ${unitPath}: updated ${updated.join(", ")} (#908/#1071/#1113: graceful stop, small crash dumps, bounded start, start limit)`);
2309
+ console.log(t("cli.unit_updated", String(unitPath), updated.join(", ")));
2310
+ // #2331: what systemd LOADED — a drop-in that refuses the fleet's READY is overridden by AgEnD's own, leftovers
2311
+ // are named. Not a gate: the fleet still restarts, and the line says what will happen and what to remove.
2312
+ for (const line of loadedUnitCheck({ user: systemdTarget.user, unit: systemdTarget.unit, unitPath }))
2313
+ console.log(line);
2333
2314
  // Re-judged after the reload, against what systemd now applies: an
2334
2315
  // operator's own mask (anywhere — main file, unit or type drop-in, or a
2335
2316
  // trailing empty assignment that restores the inherited one) is warned
@@ -2400,13 +2381,10 @@ program
2400
2381
  if (printed.status === 0) {
2401
2382
  const plistPath = getServicePath() ?? join(homedir(), "Library", "LaunchAgents", `${label}.plist`);
2402
2383
  // Path 2 (#1450 C6): a proven new plist waits on disk with its plan — exactly one bootout + bootstrap.
2403
- const { readPlan, admitPlan, planPath, planCarriedOut } = await import("./service-plan.js");
2404
- const recorded = opts.force ? null : readPlan(DATA_DIR, deps);
2405
- // #1871: an interrupted update's plan whose job launchd already runs (after `agend start`) is done: restart that
2406
- // job as an unchanged one (path 1) instead of refusing it for its new pid.
2407
- const plan = recorded && planCarriedOut(recorded, target, deps) ? null : recorded;
2408
- if (recorded && !plan)
2409
- console.log(" ℹ launchd already runs the job an interrupted update planned; restarting it as it is.");
2384
+ const { pendingPlan, admitPlan, planPath } = await import("./service-plan.js");
2385
+ // #1871: a plan whose job launchd already runs (an interrupted update, then `agend start`) is done: restart that
2386
+ // job as an unchanged one (path 1) instead of refusing it for its new pid. #2375: quietly (no note on every restart).
2387
+ const plan = opts.force ? null : pendingPlan(DATA_DIR, target, deps);
2410
2388
  if (plan && expectation.ok) {
2411
2389
  const pkgDir = dirname(dirname(expectation.expected.entry));
2412
2390
  const admitted = admitPlan(plan, { target, expected: expectation.expected, pkgDir }, deps);
@@ -2566,7 +2544,7 @@ program
2566
2544
  unlinkSync(pidPath);
2567
2545
  }
2568
2546
  catch { /* old owner already removed it */ }
2569
- const child = spawn(start.command, start.args, { detached: true, stdio: "ignore" });
2547
+ const child = spawn(start.command, start.args, { detached: true, stdio: "ignore", env: { ...process.env, [DIRECT_FLEET_START_ENV]: "1" } });
2570
2548
  child.unref();
2571
2549
  console.log("Fleet restarting in background.");
2572
2550
  return;
@@ -2583,7 +2561,7 @@ program
2583
2561
  });
2584
2562
  program
2585
2563
  .command("setup")
2586
- .description("Open the guided setup page in a browser (before a fleet exists)")
2564
+ .description("Browser first-run page, before a fleet exists (not the CLI wizard; see `agend quickstart`)")
2587
2565
  .option("--reset", "Allow setup again after it has already been completed")
2588
2566
  .option("--port <port>", "Port for the setup page (default: the health port)")
2589
2567
  .option("--tunnel", "Expose the setup page through a Cloudflare quick tunnel so a phone can open it")
@@ -2678,14 +2656,14 @@ program
2678
2656
  });
2679
2657
  program
2680
2658
  .command("quickstart")
2681
- .description("Guided setup for a Backend, chat channel, projects, and fleet service")
2659
+ .description("CLI guided setup: backend, chat channel, projects, and fleet service (recommended)")
2682
2660
  .action(async () => {
2683
2661
  const { runQuickstart } = await import("./quickstart.js");
2684
2662
  await runQuickstart();
2685
2663
  });
2686
2664
  program
2687
2665
  .command("init")
2688
- .description("Interactive setup wizard (advanced)")
2666
+ .description("Interactive advanced wizard (setup-wizard); for first-run use `agend quickstart` or `agend setup`")
2689
2667
  .action(async () => {
2690
2668
  const { runSetupWizard } = await import("./setup-wizard.js");
2691
2669
  await runSetupWizard();
@@ -2734,7 +2712,7 @@ program
2734
2712
  minutes = Math.max(1, Math.round(((body.expiresAt ?? Date.now()) - Date.now()) / 60_000));
2735
2713
  }
2736
2714
  catch {
2737
- console.error(`Cannot connect to fleet (port ${port}). Is the fleet running?`);
2715
+ console.error(t("cli.fleet_unreachable", port));
2738
2716
  process.exit(1);
2739
2717
  }
2740
2718
  const signin = `http://localhost:${port}/signin`;
@@ -3006,11 +2984,11 @@ schedule
3006
2984
  }
3007
2985
  });
3008
2986
  schedule
3009
- .command("trigger")
3010
- .description("Show how to manually trigger a schedule")
3011
- .argument("<id>", "Schedule ID")
3012
- .action((id) => {
3013
- console.log("Manual trigger requires fleet manager running. Use the Telegram interface instead.");
2987
+ .command("trigger", { hidden: true })
2988
+ .argument("[id]", "Schedule ID")
2989
+ .action(() => {
2990
+ console.error(t("cli.schedule_trigger_removed"));
2991
+ process.exitCode = 1;
3014
2992
  });
3015
2993
  // === Chat Export ===
3016
2994
  program
@@ -3511,16 +3489,21 @@ async function lsAction(opts) {
3511
3489
  console.log("Tip: agend completion install — then agend attach <TAB> completes instance names.");
3512
3490
  }
3513
3491
  }
3514
- program
3515
- .command("ls")
3516
- .description("List all instances with status, backend, team, and last activity")
3517
- .option("--json", "Output as JSON")
3518
- .option("--names-only", "Print instance names one per line (for shell completion)")
3519
- .option("--resources", "Also report workspace disk usage, unregistered instance directories, and host memory/swap (text only)")
3520
- .option("--running", "Show only running instances")
3521
- .option("--team <name>", "Filter by team name")
3522
- .option("--backend <name>", "Filter by backend (exact match)")
3523
- .option("--display", "Show display names as last column")
3492
+ function addLsOptions(cmd) {
3493
+ return cmd
3494
+ .option("--json", "Output as JSON")
3495
+ .option("--names-only", "Print instance names one per line (for shell completion)")
3496
+ .option("--resources", "Also report workspace disk usage, unregistered instance directories, and host memory/swap (text only); `agend fleet status` is a hidden alias of this")
3497
+ .option("--running", "Show only running instances")
3498
+ .option("--team <name>", "Filter by team name")
3499
+ .option("--backend <name>", "Filter by backend (exact match)")
3500
+ .option("--display", "Show display names as last column");
3501
+ }
3502
+ addLsOptions(program.command("ls").description("List all instances with status, backend, team, and last activity"))
3503
+ .action(async (opts) => {
3504
+ await lsAction(opts);
3505
+ });
3506
+ addLsOptions(program.command("status", { hidden: true }).description("Alias of `agend ls`"))
3524
3507
  .action(async (opts) => {
3525
3508
  await lsAction(opts);
3526
3509
  });
@@ -3547,9 +3530,10 @@ Then reload the shell. Tab-completes instance names for \`agend attach\` and
3547
3530
  // Subcommand names come from commander itself, so the script can't drift
3548
3531
  // out of sync with the CLI the way a hardcoded list would.
3549
3532
  const fleetCmd = program.commands.find(c => c.name() === "fleet");
3533
+ const visible = (cmds) => cmds.filter(c => !c._hidden).map(c => c.name());
3550
3534
  const spec = {
3551
- topLevel: program.commands.map(c => c.name()),
3552
- fleetSub: fleetCmd?.commands.map(c => c.name()) ?? [],
3535
+ topLevel: visible(program.commands),
3536
+ fleetSub: visible(fleetCmd?.commands ?? []),
3553
3537
  instanceCommands: ["attach"],
3554
3538
  fleetInstanceCommands: ["start", "stop", "restart"],
3555
3539
  };
@@ -3606,7 +3590,7 @@ Then reload the shell. Tab-completes instance names for \`agend attach\` and
3606
3590
  console.log(completionScript(target, spec));
3607
3591
  });
3608
3592
  program
3609
- .command("health").description("Fleet health check — shows problems and diagnostics")
3593
+ .command("health").description("Live fleet health of running instances (HTTP, tmux, IPC); for host/setup diagnostics use `agend doctor`")
3610
3594
  .option("--json", "Output as JSON")
3611
3595
  .option("-q, --quiet", "One-line summary only")
3612
3596
  .action(async (opts) => {
@@ -3844,7 +3828,7 @@ program
3844
3828
  });
3845
3829
  program
3846
3830
  .command("logs")
3847
- .description("Show fleet log (alias for `agend fleet logs`)")
3831
+ .description("Show fleet log (daemon.log, or fleet.log if the daemon log is not there yet)")
3848
3832
  .option("-n, --lines <count>", "Number of lines to show", "50")
3849
3833
  .option("-f, --follow", "Follow log output (like tail -f)")
3850
3834
  .option("--instance <name>", "Filter by instance name")