@camerontaylor/paseo-cli 0.8.0-fork.1 → 0.8.0-fork.3

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 (120) hide show
  1. package/dist/cli.js +25 -37
  2. package/dist/commands/agent/archive.js +2 -15
  3. package/dist/commands/agent/attach.d.ts +1 -0
  4. package/dist/commands/agent/attach.js +8 -12
  5. package/dist/commands/agent/delete.js +4 -15
  6. package/dist/commands/agent/detach.js +2 -13
  7. package/dist/commands/agent/import.js +2 -16
  8. package/dist/commands/agent/inspect.js +2 -15
  9. package/dist/commands/agent/logs.js +12 -12
  10. package/dist/commands/agent/ls.js +2 -23
  11. package/dist/commands/agent/mode.js +2 -2
  12. package/dist/commands/agent/open.js +2 -8
  13. package/dist/commands/agent/reload.js +2 -15
  14. package/dist/commands/agent/run.js +11 -16
  15. package/dist/commands/agent/send.js +6 -15
  16. package/dist/commands/agent/stop.js +2 -15
  17. package/dist/commands/agent/update.js +2 -15
  18. package/dist/commands/agent/wait.js +4 -15
  19. package/dist/commands/clone.js +2 -8
  20. package/dist/commands/daemon/config.d.ts +3 -0
  21. package/dist/commands/daemon/config.js +102 -0
  22. package/dist/commands/daemon/index.js +20 -52
  23. package/dist/commands/daemon/local-daemon.d.ts +10 -75
  24. package/dist/commands/daemon/local-daemon.js +61 -503
  25. package/dist/commands/daemon/pair.d.ts +3 -8
  26. package/dist/commands/daemon/pair.js +31 -61
  27. package/dist/commands/daemon/reload.d.ts +3 -1
  28. package/dist/commands/daemon/reload.js +10 -2
  29. package/dist/commands/daemon/restart.d.ts +19 -11
  30. package/dist/commands/daemon/restart.js +102 -88
  31. package/dist/commands/daemon/set-password.js +6 -7
  32. package/dist/commands/daemon/start.d.ts +17 -3
  33. package/dist/commands/daemon/start.js +47 -52
  34. package/dist/commands/daemon/status.d.ts +12 -20
  35. package/dist/commands/daemon/status.js +90 -342
  36. package/dist/commands/daemon/stop.d.ts +19 -14
  37. package/dist/commands/daemon/stop.js +53 -55
  38. package/dist/commands/heartbeat/index.js +3 -3
  39. package/dist/commands/hub/connect.d.ts +1 -0
  40. package/dist/commands/hub/connect.js +1 -1
  41. package/dist/commands/hub/daemon-client.d.ts +3 -2
  42. package/dist/commands/hub/daemon-client.js +3 -3
  43. package/dist/commands/hub/disconnect.d.ts +1 -0
  44. package/dist/commands/hub/disconnect.js +1 -1
  45. package/dist/commands/hub/index.js +2 -2
  46. package/dist/commands/hub/init.d.ts +3 -1
  47. package/dist/commands/hub/init.js +13 -12
  48. package/dist/commands/hub/login.d.ts +4 -4
  49. package/dist/commands/hub/login.js +1 -1
  50. package/dist/commands/hub/logout.d.ts +1 -0
  51. package/dist/commands/hub/logout.js +2 -2
  52. package/dist/commands/hub/permissions.d.ts +1 -0
  53. package/dist/commands/hub/permissions.js +2 -2
  54. package/dist/commands/onboard.d.ts +7 -2
  55. package/dist/commands/onboard.js +58 -210
  56. package/dist/commands/open.js +2 -0
  57. package/dist/commands/permit/allow.js +2 -15
  58. package/dist/commands/permit/deny.js +2 -15
  59. package/dist/commands/permit/ls.js +2 -15
  60. package/dist/commands/plugin/index.js +7 -7
  61. package/dist/commands/plugin/shared.d.ts +4 -3
  62. package/dist/commands/plugin/shared.js +8 -8
  63. package/dist/commands/project/create.js +5 -3
  64. package/dist/commands/project/delete.js +4 -2
  65. package/dist/commands/project/ls.js +2 -2
  66. package/dist/commands/project/rename.js +4 -2
  67. package/dist/commands/provider/add.js +2 -2
  68. package/dist/commands/provider/diagnostic.js +1 -1
  69. package/dist/commands/provider/ls.js +2 -30
  70. package/dist/commands/provider/models.js +1 -1
  71. package/dist/commands/provider/rm.js +2 -2
  72. package/dist/commands/schedule/create.js +2 -2
  73. package/dist/commands/schedule/delete.js +1 -1
  74. package/dist/commands/schedule/inspect.js +1 -1
  75. package/dist/commands/schedule/logs.js +1 -1
  76. package/dist/commands/schedule/ls.js +1 -1
  77. package/dist/commands/schedule/pause.js +1 -1
  78. package/dist/commands/schedule/resume.js +1 -1
  79. package/dist/commands/schedule/run-once.js +1 -1
  80. package/dist/commands/schedule/shared.d.ts +3 -1
  81. package/dist/commands/schedule/shared.js +7 -5
  82. package/dist/commands/schedule/update.js +1 -1
  83. package/dist/commands/script/ls.js +1 -1
  84. package/dist/commands/script/shared.d.ts +2 -1
  85. package/dist/commands/script/shared.js +3 -3
  86. package/dist/commands/script/start.js +1 -1
  87. package/dist/commands/script/stop.js +1 -1
  88. package/dist/commands/terminal/capture.d.ts +1 -1
  89. package/dist/commands/terminal/capture.js +2 -3
  90. package/dist/commands/terminal/create.js +1 -1
  91. package/dist/commands/terminal/kill.js +1 -1
  92. package/dist/commands/terminal/ls.js +1 -1
  93. package/dist/commands/terminal/send-keys.d.ts +1 -1
  94. package/dist/commands/terminal/send-keys.js +2 -3
  95. package/dist/commands/terminal/shared.d.ts +2 -1
  96. package/dist/commands/terminal/shared.js +5 -3
  97. package/dist/commands/workspace/archive.d.ts +1 -0
  98. package/dist/commands/workspace/archive.js +4 -2
  99. package/dist/commands/workspace/create.js +4 -2
  100. package/dist/commands/workspace/ls.d.ts +1 -0
  101. package/dist/commands/workspace/ls.js +2 -2
  102. package/dist/commands/workspace/rename.d.ts +1 -0
  103. package/dist/commands/workspace/rename.js +4 -2
  104. package/dist/commands/workspace/setup.d.ts +1 -0
  105. package/dist/commands/workspace/setup.js +2 -2
  106. package/dist/commands/worktree/archive.js +4 -2
  107. package/dist/commands/worktree/create.js +2 -10
  108. package/dist/commands/worktree/ls.d.ts +0 -2
  109. package/dist/commands/worktree/ls.js +4 -30
  110. package/dist/output/with-output.d.ts +2 -0
  111. package/dist/run.js +11 -1
  112. package/dist/utils/client-id.d.ts +1 -1
  113. package/dist/utils/client-id.js +8 -14
  114. package/dist/utils/client.d.ts +16 -21
  115. package/dist/utils/client.js +57 -109
  116. package/dist/utils/command-options.d.ts +1 -0
  117. package/dist/utils/command-options.js +24 -2
  118. package/dist/utils/daemon-target.d.ts +13 -0
  119. package/dist/utils/daemon-target.js +51 -0
  120. package/package.json +8 -4
@@ -1,363 +1,111 @@
1
- import { createRequire } from "node:module";
2
- import { getOrCreateServerId, findExecutable, execCommand } from "@camerontaylor/paseo-server";
3
- import { connectToDaemon } from "../../utils/client.js";
4
- import { resolveLocalDaemonState } from "./local-daemon.js";
5
- import { resolveNodePathFromPid } from "./runtime-toolchain.js";
6
- const DAEMON_STATUS_PROBE_TIMEOUT_MS = 1500;
7
- const require = createRequire(import.meta.url);
8
- function normalizeError(error) {
9
- if (error instanceof Error) {
10
- return error.message;
11
- }
12
- return String(error);
13
- }
14
- function shortenMessage(message, max = 120) {
15
- const normalized = message.replace(/\s+/g, " ").trim();
16
- if (normalized.length <= max) {
17
- return normalized;
18
- }
19
- return `${normalized.slice(0, max - 3)}...`;
20
- }
21
- function appendNote(current, next) {
22
- if (!next)
23
- return current;
24
- if (!current)
25
- return next;
26
- return `${current}; ${next}`;
27
- }
28
- function resolveCliVersion() {
29
- try {
30
- const packageJson = require("../../../package.json");
31
- if (typeof packageJson.version === "string" && packageJson.version.trim().length > 0) {
32
- return packageJson.version.trim();
33
- }
34
- }
35
- catch {
36
- // Fall through.
37
- }
38
- return "unknown";
1
+ import { Command } from "commander";
2
+ import { DaemonConnectionError } from "@camerontaylor/paseo-client/internal/daemon-client";
3
+ import { readDaemonInstance, readPersistedConfig, resolveConfigFromPersisted, daemonLogPath, isSameDaemonInstance, DaemonInstanceError, } from "@camerontaylor/paseo-server";
4
+ import { connectToDaemon, buildDaemonConnectionCommandError } from "../../utils/client.js";
5
+ import { withOutput, toCommandError } from "../../output/index.js";
6
+ import { addJsonAndDaemonHostOptions } from "../../utils/command-options.js";
7
+ import { describeDaemonTarget } from "../../utils/daemon-target.js";
8
+ export function daemonStatusCommand() {
9
+ return addJsonAndDaemonHostOptions(new Command("status").description("Observe the selected daemon and its published endpoint")).action(withOutput(runStatusCommand));
39
10
  }
40
- function createStatusSchema(status) {
11
+ export async function runStatusCommand(options, _command) {
12
+ const target = options.daemonTarget;
13
+ const instance = target.kind === "instance" ? await readDaemonInstance(target.home) : null;
14
+ const local = target.kind === "instance"
15
+ ? localStatus(target.home, instance)
16
+ : { host: describeDaemonTarget(target) };
17
+ const observed = target.kind === "endpoint" || instance?.listen
18
+ ? await probeDaemonStatus(target, instance, local)
19
+ : { connectedDaemon: "not_probed" };
20
+ const data = { ...local, ...observed };
41
21
  return {
42
- idField: "key",
43
- columns: [
44
- { header: "KEY", field: "key" },
45
- {
46
- header: "VALUE",
47
- field: "value",
48
- color: (_, item) => {
49
- if (item.key === "Local Daemon") {
50
- if (item.value === "running")
51
- return "green";
52
- if (item.value === "unresponsive")
53
- return "yellow";
54
- return "red";
55
- }
56
- if (item.key === "Connected Daemon") {
57
- if (item.value === "reachable")
58
- return "green";
59
- if (item.value === "not_probed" || item.value === "auth_required")
60
- return "yellow";
61
- return "red";
62
- }
63
- if (item.key.startsWith(" ")) {
64
- if (item.value === "not found" || item.value === "not found (daemon)")
65
- return "red";
66
- if (item.value.endsWith("(--version failed)"))
67
- return "yellow";
68
- return "green";
69
- }
70
- return undefined;
71
- },
72
- },
73
- ],
74
- serialize: () => status,
22
+ type: "single",
23
+ data,
24
+ schema: {
25
+ idField: () => "daemon",
26
+ columns: [],
27
+ renderHuman: () => Object.entries(data)
28
+ .filter(([, value]) => value !== undefined)
29
+ .map(([key, value]) => `${key}: ${typeof value === "object" ? JSON.stringify(value) : value}`)
30
+ .join("\n"),
31
+ },
75
32
  };
76
33
  }
77
- function toStatusRows(status) {
78
- const rows = [
79
- { key: "Server ID", value: status.serverId ?? "-" },
80
- { key: "Local Daemon", value: status.localDaemon },
81
- { key: "Connected Daemon", value: status.connectedDaemon },
82
- { key: "Home", value: status.home },
83
- { key: "Listen", value: status.listen },
84
- { key: "Relay", value: status.relay },
85
- { key: "Hostname", value: status.hostname ?? "-" },
86
- { key: "PID", value: status.pid === null ? "-" : String(status.pid) },
87
- { key: "Started", value: status.startedAt ?? "-" },
88
- { key: "Owner", value: status.owner ?? "-" },
89
- { key: "Logs", value: status.logPath },
90
- { key: "Daemon Node", value: status.daemonNode },
91
- { key: "CLI Node", value: status.cliNode },
92
- { key: "CLI", value: status.cliVersion },
93
- { key: "Daemon Version", value: status.daemonVersion ?? "-" },
94
- ];
95
- if (status.note) {
96
- rows.push({ key: "Note", value: status.note });
97
- }
98
- rows.push({ key: "", value: "" });
99
- rows.push({ key: "Providers", value: "" });
100
- for (const provider of status.providers) {
101
- if (provider.source === "daemon") {
102
- if (!provider.path) {
103
- rows.push({ key: ` ${provider.label}`, value: "not found (daemon)" });
104
- }
105
- else {
106
- rows.push({ key: ` ${provider.label}`, value: `${provider.path} (daemon)` });
107
- }
108
- }
109
- else if (!provider.path) {
110
- rows.push({ key: ` ${provider.label}`, value: "not found" });
111
- }
112
- else if (!provider.version) {
113
- rows.push({ key: ` ${provider.label}`, value: `${provider.path} (--version failed)` });
114
- }
115
- else {
116
- rows.push({ key: ` ${provider.label}`, value: `${provider.path} (${provider.version})` });
117
- }
118
- }
119
- return rows;
120
- }
121
- const PROVIDER_BINARIES = [
122
- { label: "Claude", binary: "claude" },
123
- { label: "Codex", binary: "codex" },
124
- { label: "OpenCode", binary: "opencode" },
125
- ];
126
- async function checkProviderBinary(binary) {
127
- const binaryPath = await findExecutable(binary);
128
- if (!binaryPath) {
129
- return { path: null, version: null };
130
- }
131
- try {
132
- const { stdout } = await execCommand(binaryPath, ["--version"], {
133
- timeout: 5000,
134
- });
135
- return { path: binaryPath, version: stdout.trim() || null };
136
- }
137
- catch {
138
- return { path: binaryPath, version: null };
139
- }
140
- }
141
- async function checkProviderBinaries() {
142
- const results = await Promise.all(PROVIDER_BINARIES.map(async ({ label, binary }) => {
143
- const result = await checkProviderBinary(binary);
144
- return Object.assign({ label }, result);
145
- }));
146
- return results;
147
- }
148
- function resolveOwnerLabel(uid, hostname) {
149
- if (uid === undefined && !hostname) {
150
- return null;
151
- }
152
- const uidPart = uid === undefined ? "?" : String(uid);
153
- const hostPart = hostname ?? "unknown-host";
154
- return `${uidPart}@${hostPart}`;
155
- }
156
- function classifyDaemonAuthProbeFailure(error) {
157
- if (!(error instanceof Error))
158
- return null;
159
- if (error.message === "Password required")
160
- return "auth_required";
161
- if (error.message === "Incorrect password")
162
- return "auth_failed";
163
- return null;
164
- }
165
- function describeDaemonAuthProbeFailure(host, failure) {
166
- if (failure === "auth_required") {
167
- return `Daemon is reachable at ${host} but requires a password. Set PASEO_PASSWORD and retry.`;
168
- }
169
- return `Daemon is reachable at ${host} but the supplied password was rejected. Check PASEO_PASSWORD and retry.`;
170
- }
171
- async function probeDaemonOverWebsocket(args) {
172
- const { host, state } = args;
34
+ async function probeDaemonStatus(target, instance, local) {
35
+ let connectedDaemon = "unreachable";
36
+ let note;
37
+ let live = {};
173
38
  let client;
174
39
  try {
175
- client = await connectToDaemon({ host, timeout: 1500 });
40
+ client = await connectToDaemon({ target, instance: instance ?? undefined, timeout: 1500 });
176
41
  }
177
42
  catch (error) {
178
- const authFailure = classifyDaemonAuthProbeFailure(error);
179
- if (authFailure) {
180
- return {
181
- connectedDaemon: authFailure,
182
- note: describeDaemonAuthProbeFailure(host, authFailure),
183
- };
184
- }
185
- if (state.running) {
186
- return {
187
- connectedDaemon: "unreachable",
188
- localDaemonOverride: "unresponsive",
189
- note: `Local daemon PID is running but websocket at ${host} is not reachable`,
190
- };
191
- }
192
- return { connectedDaemon: "unreachable" };
193
- }
194
- const daemonVersion = client.getLastServerInfoMessage()?.version ?? null;
43
+ const failure = buildDaemonConnectionCommandError({ target, error });
44
+ if (target.kind === "endpoint")
45
+ throw failure;
46
+ connectedDaemon = "unreachable";
47
+ if (failure.code === "AUTH_REQUIRED")
48
+ connectedDaemon = "auth_required";
49
+ if (failure.code === "AUTH_FAILED")
50
+ connectedDaemon = "auth_failed";
51
+ note = failure.message;
52
+ }
53
+ if (!client)
54
+ return { connectedDaemon, note };
195
55
  try {
196
- const statusPayload = await client.getDaemonStatus({
197
- timeout: DAEMON_STATUS_PROBE_TIMEOUT_MS,
56
+ let requestError;
57
+ const status = await client.getDaemonStatus({ timeout: 1500 }).catch((error) => {
58
+ requestError = error;
59
+ return null;
198
60
  });
199
- const labelMap = new Map(PROVIDER_BINARIES.map((p) => [p.binary, p.label]));
200
- const daemonProviders = statusPayload.providers.map((p) => ({
201
- label: labelMap.get(p.provider) ?? p.provider,
202
- path: p.available ? "available" : null,
203
- version: p.available ? null : (p.error ?? null),
204
- source: "daemon",
205
- }));
206
- const relayStatus = statusPayload.relay == null
207
- ? undefined
208
- : selectRelayStatus({
209
- persisted: relayConfigFromLocalState(state),
210
- live: statusPayload.relay,
211
- });
212
- if (!state.running) {
213
- return {
214
- connectedDaemon: "reachable",
215
- daemonVersion: statusPayload.version ?? daemonVersion,
216
- daemonNodeOverride: statusPayload.nodePath,
217
- daemonProviders,
218
- relayStatus,
219
- note: state.pidInfo
220
- ? `Connected daemon is reachable at ${host} even though local daemon PID ${state.pidInfo.pid} is stale`
221
- : `Connected daemon is reachable at ${host} but no local daemon PID file was found`,
61
+ // Recheck even when the RPC times out before combining local and live observations.
62
+ const current = target.kind === "instance" ? await readDaemonInstance(target.home) : null;
63
+ if (instance && (!current || !isSameDaemonInstance(instance, current)))
64
+ throw new DaemonInstanceError("DAEMON_REPLACED", "Supervisor exited or was replaced during status observation.");
65
+ const info = client.getLastServerInfoMessage();
66
+ live = { serverId: info?.serverId, daemonVersion: info?.version };
67
+ connectedDaemon = client.isConnected ? "reachable" : "unreachable";
68
+ if (!status) {
69
+ const failure = toCommandError(requestError);
70
+ note = `Status details unavailable (${failure.code}): ${failure.message}`;
71
+ if (target.kind === "endpoint" ||
72
+ !client.isConnected ||
73
+ !(requestError instanceof DaemonConnectionError) ||
74
+ requestError.code !== "DAEMON_REQUEST_TIMEOUT") {
75
+ throw { ...failure, message: note, details: { ...local, ...live, connectedDaemon } };
76
+ }
77
+ }
78
+ else {
79
+ live = {
80
+ ...live,
81
+ daemonVersion: status.version,
82
+ workerPid: status.pid,
83
+ daemonNode: status.nodePath,
84
+ providers: status.providers,
85
+ relay: status.relay,
222
86
  };
223
87
  }
224
- return {
225
- connectedDaemon: "reachable",
226
- daemonVersion: statusPayload.version ?? daemonVersion,
227
- daemonNodeOverride: statusPayload.nodePath,
228
- daemonProviders,
229
- relayStatus,
230
- };
231
- }
232
- catch {
233
- return {
234
- connectedDaemon: "reachable",
235
- daemonVersion,
236
- note: state.running
237
- ? `Local daemon PID is running but daemon detail request to ${host} failed`
238
- : `Connected daemon websocket is reachable at ${host} but daemon status request failed`,
239
- };
240
88
  }
241
89
  finally {
242
- await client.close().catch(() => { });
243
- }
244
- }
245
- function applyProbeToStatus(input) {
246
- const { probe } = input;
247
- return {
248
- connectedDaemon: probe.connectedDaemon,
249
- localDaemon: probe.localDaemonOverride ?? input.localDaemon,
250
- daemonNode: probe.daemonNodeOverride ?? input.daemonNode,
251
- daemonVersion: probe.daemonVersion !== undefined ? probe.daemonVersion : input.daemonVersion,
252
- daemonProviders: probe.daemonProviders ?? input.daemonProviders,
253
- relayStatus: probe.relayStatus ?? input.relayStatus,
254
- note: probe.note ? appendNote(input.note, probe.note) : input.note,
255
- };
256
- }
257
- function resolveServerIdSafely(home) {
258
- try {
259
- return { serverId: getOrCreateServerId(home), error: null };
260
- }
261
- catch (error) {
262
- return {
263
- serverId: null,
264
- error: `serverId unavailable: ${shortenMessage(normalizeError(error))}`,
265
- };
90
+ await client.close();
266
91
  }
92
+ return { ...live, connectedDaemon, note };
267
93
  }
268
- async function resolveDaemonNodeLabel(state) {
269
- if (!state.running)
270
- return "-";
271
- if (!state.pidInfo?.pid)
272
- return "unknown (no PID available)";
273
- const fromPid = await resolveNodePathFromPid(state.pidInfo.pid);
274
- return fromPid.nodePath ?? `unknown (${fromPid.error ?? "could not resolve from PID"})`;
275
- }
276
- function relayConfigFromLocalState(state) {
94
+ function localStatus(home, instance) {
95
+ const config = resolveConfigFromPersisted(home, readPersistedConfig(home, { defaultsIfMissing: true }), { env: {} });
96
+ let localDaemon = "stopped";
97
+ if (instance)
98
+ localDaemon = instance.listen ? "running" : "not_ready";
277
99
  return {
278
- enabled: state.relayEnabled,
279
- endpoint: state.relayEndpoint,
280
- publicEndpoint: state.relayEndpoint,
281
- useTls: state.relayUseTls,
282
- publicUseTls: state.relayPublicUseTls,
283
- };
284
- }
285
- export function selectRelayStatus(input) {
286
- const relay = input.live ?? input.persisted;
287
- if (!relay.enabled)
288
- return "disabled";
289
- const scheme = relay.publicUseTls ? "wss" : "ws";
290
- return `${scheme}://${relay.publicEndpoint}`;
291
- }
292
- export async function runStatusCommand(options, _command) {
293
- const home = typeof options.home === "string" ? options.home : undefined;
294
- const state = resolveLocalDaemonState({ home });
295
- const daemonTarget = state.listen.trim();
296
- const owner = resolveOwnerLabel(state.pidInfo?.uid, state.pidInfo?.hostname);
297
- let daemonNode = await resolveDaemonNodeLabel(state);
298
- const cliNode = process.execPath;
299
- let localDaemon = state.running ? "running" : "stopped";
300
- let connectedDaemon = "not_probed";
301
- let daemonVersion = null;
302
- let daemonProviders;
303
- let relayStatus = selectRelayStatus({ persisted: relayConfigFromLocalState(state) });
304
- let note;
305
- if (!state.running && state.stalePidFile && state.pidInfo) {
306
- localDaemon = "stale_pid";
307
- note = `Stale PID file found for PID ${state.pidInfo.pid}`;
308
- }
309
- if (daemonTarget) {
310
- const probe = await probeDaemonOverWebsocket({ host: daemonTarget, state });
311
- ({
312
- connectedDaemon,
313
- localDaemon,
314
- daemonNode,
315
- daemonVersion,
316
- daemonProviders,
317
- relayStatus,
318
- note,
319
- } = applyProbeToStatus({
320
- probe,
321
- connectedDaemon,
322
- localDaemon,
323
- daemonNode,
324
- daemonVersion,
325
- daemonProviders,
326
- relayStatus,
327
- note,
328
- }));
329
- }
330
- const cliVersion = resolveCliVersion();
331
- const serverIdResult = resolveServerIdSafely(state.home);
332
- const serverId = serverIdResult.serverId;
333
- if (serverIdResult.error) {
334
- note = appendNote(note, serverIdResult.error);
335
- }
336
- const providers = daemonProviders ?? (await checkProviderBinaries());
337
- const daemonStatus = {
338
- serverId,
100
+ home,
101
+ pid: instance?.pid ?? null,
102
+ startedAt: instance?.startedAt ?? null,
103
+ listen: instance?.listen ?? null,
104
+ hostname: instance?.hostname ?? null,
105
+ configuredListen: config.listen,
339
106
  localDaemon,
340
- connectedDaemon,
341
- home: state.home,
342
- listen: state.listen,
343
- relay: relayStatus,
344
- hostname: state.pidInfo?.hostname ?? null,
345
- pid: state.pidInfo?.pid ?? null,
346
- startedAt: state.pidInfo?.startedAt ?? null,
347
- owner,
348
- logPath: state.logPath,
349
- daemonNode,
350
- cliNode,
351
- cliVersion,
352
- daemonVersion,
353
- desktopManaged: state.pidInfo?.desktopManaged === true,
354
- providers,
355
- note,
356
- };
357
- return {
358
- type: "list",
359
- data: toStatusRows(daemonStatus),
360
- schema: createStatusSchema(daemonStatus),
107
+ desktopManaged: instance?.desktopManaged === true,
108
+ logPath: daemonLogPath(home),
361
109
  };
362
110
  }
363
111
  //# sourceMappingURL=status.js.map
@@ -1,15 +1,20 @@
1
- import type { Command } from "commander";
2
- import type { CommandOptions, SingleResult } from "../../output/index.js";
3
- interface StopResult {
4
- action: "stopped" | "not_running";
5
- home: string;
6
- pid: string;
7
- forced: boolean;
8
- usedLifecycleRpc: boolean;
9
- reason: "not_running" | "lifecycle_shutdown_rpc" | "owner_pid_signal" | "owner_pid_sigkill";
10
- message: string;
11
- }
12
- export type StopCommandResult = SingleResult<StopResult>;
13
- export declare function runStopCommand(options: CommandOptions, _command: Command): Promise<StopCommandResult>;
14
- export {};
1
+ import { Command } from "commander";
2
+ import { type CommandOptions } from "../../output/index.js";
3
+ export declare function daemonStopCommand(): Command;
4
+ export declare function runStopCommand(options: CommandOptions, _command: Command): Promise<{
5
+ type: "single";
6
+ data: {
7
+ action: string;
8
+ home?: string;
9
+ host?: string;
10
+ pid?: number | null;
11
+ forced?: boolean;
12
+ usedLifecycleRpc?: boolean;
13
+ };
14
+ schema: {
15
+ idField: "action";
16
+ columns: never[];
17
+ renderHuman: () => string;
18
+ };
19
+ }>;
15
20
  //# sourceMappingURL=stop.d.ts.map
@@ -1,60 +1,58 @@
1
- import { stopLocalDaemon, DEFAULT_STOP_TIMEOUT_MS, DEFAULT_KILL_TIMEOUT_MS, } from "./local-daemon.js";
2
- const stopResultSchema = {
3
- idField: "action",
4
- columns: [
5
- {
6
- header: "STATUS",
7
- field: "action",
8
- color: (value) => (value === "stopped" ? "green" : "yellow"),
9
- },
10
- { header: "HOME", field: "home" },
11
- { header: "PID", field: "pid" },
12
- { header: "MESSAGE", field: "message" },
13
- ],
14
- };
15
- function parseSecondsOption(raw, fallbackMs, label) {
16
- if (typeof raw !== "string" || raw.trim().length === 0) {
17
- return fallbackMs;
18
- }
19
- const seconds = Number(raw);
20
- if (!Number.isFinite(seconds) || seconds <= 0) {
21
- const error = {
22
- code: "INVALID_TIMEOUT",
23
- message: `Invalid ${label} value: ${raw}`,
24
- details: `${label} must be a positive number of seconds`,
25
- };
26
- throw error;
27
- }
28
- return Math.ceil(seconds * 1000);
1
+ import { Command } from "commander";
2
+ import { stopDaemonInstance } from "@camerontaylor/paseo-server";
3
+ import { connectToDaemon } from "../../utils/client.js";
4
+ import { withOutput } from "../../output/index.js";
5
+ import { addJsonAndDaemonHostOptions } from "../../utils/command-options.js";
6
+ import { describeDaemonTarget } from "../../utils/daemon-target.js";
7
+ import { parseTimeoutMs } from "./local-daemon.js";
8
+ export function daemonStopCommand() {
9
+ return addJsonAndDaemonHostOptions(new Command("stop").description("Stop the selected daemon"))
10
+ .option("--timeout <seconds>", "Graceful exit deadline (default: 15)")
11
+ .option("--force", "Permit forced local process cleanup")
12
+ .option("--kill-timeout <seconds>", "Forced exit deadline (default: 3)")
13
+ .action(withOutput(runStopCommand));
29
14
  }
30
15
  export async function runStopCommand(options, _command) {
31
- const home = typeof options.home === "string" ? options.home : undefined;
32
- const force = options.force === true;
33
- const timeoutMs = parseSecondsOption(options.timeout, DEFAULT_STOP_TIMEOUT_MS, "timeout");
34
- const killTimeoutMs = parseSecondsOption(options.killTimeout, DEFAULT_KILL_TIMEOUT_MS, "kill-timeout");
35
- try {
36
- const result = await stopLocalDaemon({ home, force, timeoutMs, killTimeoutMs });
37
- return {
38
- type: "single",
39
- data: {
40
- action: result.action,
41
- home: result.home,
42
- pid: result.pid === null ? "-" : String(result.pid),
43
- forced: result.forced,
44
- usedLifecycleRpc: result.usedLifecycleRpc,
45
- reason: result.reason,
46
- message: result.message,
47
- },
48
- schema: stopResultSchema,
16
+ const target = options.daemonTarget;
17
+ const timeoutMs = parseTimeoutMs(options.timeout, 15000);
18
+ const deadline = Date.now() + timeoutMs;
19
+ const remaining = () => Math.max(1, deadline - Date.now());
20
+ const requestShutdown = async (instance) => {
21
+ const client = await connectToDaemon({ target, timeout: remaining(), instance });
22
+ try {
23
+ await client.shutdownServer({ timeout: remaining() });
24
+ }
25
+ finally {
26
+ await client.close();
27
+ }
28
+ };
29
+ if (target.kind === "endpoint" && options.force)
30
+ throw {
31
+ code: "INVALID_OPTIONS",
32
+ message: "--force requires a local --home; an endpoint gives no remote process authority.",
49
33
  };
50
- }
51
- catch (err) {
52
- const message = err instanceof Error ? err.message : String(err);
53
- const error = {
54
- code: "STOP_FAILED",
55
- message: `Failed to stop local daemon: ${message}`,
56
- };
57
- throw error;
58
- }
34
+ const result = target.kind === "instance"
35
+ ? {
36
+ ...(await stopDaemonInstance(target.home, {
37
+ force: options.force === true,
38
+ timeoutMs,
39
+ killTimeoutMs: parseTimeoutMs(options.killTimeout, 3000),
40
+ requestShutdown,
41
+ })),
42
+ home: target.home,
43
+ }
44
+ : (await requestShutdown(),
45
+ { action: "shutdown_requested", host: describeDaemonTarget(target) });
46
+ return {
47
+ type: "single",
48
+ data: result,
49
+ schema: {
50
+ idField: "action",
51
+ columns: [],
52
+ renderHuman: () => target.kind === "endpoint"
53
+ ? "Shutdown requested; remote process exit was not verified."
54
+ : `${result.action.replaceAll("_", " ")}: ${target.home}`,
55
+ },
56
+ };
59
57
  }
60
58
  //# sourceMappingURL=stop.js.map
@@ -33,7 +33,7 @@ async function runCreateHeartbeat(prompt, options, _command) {
33
33
  if (!cron) {
34
34
  throw new Error("--cron is required");
35
35
  }
36
- const { client } = await connectScheduleClient(options.host);
36
+ const { client } = await connectScheduleClient(options.daemonTarget);
37
37
  try {
38
38
  const maxRuns = options.maxRuns ? Number.parseInt(options.maxRuns, 10) : undefined;
39
39
  if (maxRuns !== undefined && (!Number.isSafeInteger(maxRuns) || maxRuns <= 0)) {
@@ -71,7 +71,7 @@ async function runUpdateHeartbeat(id, options, _command) {
71
71
  if (!cron) {
72
72
  throw new Error("--cron is required");
73
73
  }
74
- const { client } = await connectScheduleClient(options.host);
74
+ const { client } = await connectScheduleClient(options.daemonTarget);
75
75
  try {
76
76
  await requireOwnedHeartbeat(client, id, agentId);
77
77
  const payload = await client.scheduleUpdate({
@@ -96,7 +96,7 @@ async function runUpdateHeartbeat(id, options, _command) {
96
96
  }
97
97
  async function runDeleteHeartbeat(id, options, _command) {
98
98
  const agentId = requireCallerAgentId();
99
- const { client } = await connectScheduleClient(options.host);
99
+ const { client } = await connectScheduleClient(options.daemonTarget);
100
100
  try {
101
101
  await requireOwnedHeartbeat(client, id, agentId);
102
102
  const payload = await client.scheduleDelete({ id });
@@ -6,6 +6,7 @@ import { type HubReporter } from "./reporter.js";
6
6
  interface HubConnectOptions {
7
7
  apiKey?: string;
8
8
  host?: string;
9
+ daemonTarget: import("../../utils/daemon-target.js").DaemonTarget;
9
10
  json?: boolean;
10
11
  permission?: readonly string[];
11
12
  permissions?: readonly string[];
@@ -16,7 +16,7 @@ export async function runHubConnect(originInput, options, dependencies) {
16
16
  const credential = resolveHubCredential({ ...resolution, origin });
17
17
  const token = await dependencies.hub.issueEnrollmentToken(origin, credential);
18
18
  const permissions = options.permissions ?? options.permission ?? [];
19
- return withHubDaemon(dependencies.daemon, options.host, async (daemon) => {
19
+ return withHubDaemon(dependencies.daemon, options.daemonTarget, async (daemon) => {
20
20
  const response = await daemon.connectHub(origin, token, permissions);
21
21
  if (response.status.hubOrigin !== null &&
22
22
  !samePermissions(response.status.permissions, permissions)) {