@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,10 +1,9 @@
1
1
  import { confirm, isCancel, log } from "@clack/prompts";
2
2
  import { Command } from "commander";
3
3
  import chalk from "chalk";
4
- import { generateLocalPairingOffer, getOrCreateServerId, loadConfig, resolvePaseoHome, } from "@camerontaylor/paseo-server";
5
- import { tryConnectToDaemon } from "../../utils/client.js";
6
- import { resolveLocalDaemonState } from "./local-daemon.js";
7
- import { addJsonOption } from "../../utils/command-options.js";
4
+ import { generateLocalPairingOffer, readDaemonInstance, readPersistedConfig, editPersistedConfig, resolveConfigFromPersisted, } from "@camerontaylor/paseo-server";
5
+ import { connectToDaemon } from "../../utils/client.js";
6
+ import { addJsonAndDaemonHostOptions, withGlobalOptions } from "../../utils/command-options.js";
8
7
  import { formatPairingInstructions } from "../../output/pairing.js";
9
8
  const PAIRING_DAEMON_RPC_TIMEOUT_MS = 1500;
10
9
  const RELAY_DOCS_URL = "https://paseo.sh/docs/security";
@@ -26,26 +25,17 @@ function createProcessOutput() {
26
25
  };
27
26
  }
28
27
  export function pairCommand() {
29
- return addJsonOption(new Command("pair").description("Print the daemon pairing QR code and link"))
30
- .option("--home <path>", "Paseo home directory (default: ~/.paseo)")
28
+ return addJsonAndDaemonHostOptions(new Command("pair").description("Print the daemon pairing QR code and link"))
31
29
  .option("--relay", "Enable relay without prompting")
32
- .action(async (_options, command) => {
33
- await runPairCommand(command.optsWithGlobals());
34
- });
30
+ .action(withGlobalOptions((options, _command) => runPairCommand(options)));
35
31
  }
36
32
  export async function resolveLocalPairingOffer(options) {
37
- const state = resolveLocalDaemonState({ home: options.paseoHome });
38
- const serverId = getOrCreateServerId(state.home);
39
- const daemonOffer = await resolveDaemonPairingOffer(state.listen, serverId, options.enableRelay);
40
- if (daemonOffer)
41
- return daemonOffer;
42
- if (state.running) {
43
- throw new Error("The running daemon did not provide a pairing offer. Check daemon connectivity or update the daemon.");
44
- }
45
- const config = loadConfig(options.paseoHome);
46
- if (options.enableRelay && !config.relayEnabled) {
47
- throw new Error("Start the daemon before enabling relay for pairing.");
48
- }
33
+ const instance = await readDaemonInstance(options.paseoHome);
34
+ if (instance)
35
+ return resolveDaemonPairingOffer({ kind: "instance", home: options.paseoHome }, options.enableRelay);
36
+ if (options.enableRelay)
37
+ editPersistedConfig(options.paseoHome, "daemon.relay.enabled", { value: true });
38
+ const config = resolveConfigFromPersisted(options.paseoHome, readPersistedConfig(options.paseoHome, { defaultsIfMissing: true }), { env: {} });
49
39
  return generateLocalPairingOffer({
50
40
  paseoHome: options.paseoHome,
51
41
  relayEnabled: config.relayEnabled,
@@ -57,18 +47,13 @@ export async function resolveLocalPairingOffer(options) {
57
47
  includeQr: true,
58
48
  });
59
49
  }
60
- async function resolveDaemonPairingOffer(listen, expectedServerId, enableRelay) {
61
- const client = await tryConnectToDaemon({
62
- host: listen,
50
+ async function resolveDaemonPairingOffer(target, enableRelay) {
51
+ const client = await connectToDaemon({
52
+ target,
63
53
  timeout: PAIRING_DAEMON_RPC_TIMEOUT_MS,
64
54
  });
65
- if (!client)
66
- return null;
67
55
  try {
68
56
  const serverInfo = client.getLastServerInfoMessage();
69
- if (serverInfo?.serverId.trim() !== expectedServerId) {
70
- throw new Error("The reachable daemon belongs to a different Paseo home. Check --home or the daemon listen configuration.");
71
- }
72
57
  if (serverInfo?.features?.daemonStatusRpc !== true) {
73
58
  throw new Error("Update the Paseo daemon before pairing from this command.");
74
59
  }
@@ -80,9 +65,12 @@ async function resolveDaemonPairingOffer(listen, expectedServerId, enableRelay)
80
65
  throw new Error("Update the Paseo daemon before enabling relay from this command.");
81
66
  }
82
67
  await client.patchDaemonConfig({ relay: { enabled: true } });
83
- offer = await client.getDaemonPairingOffer({
84
- timeout: PAIRING_DAEMON_RPC_TIMEOUT_MS,
85
- });
68
+ try {
69
+ offer = await client.getDaemonPairingOffer({ timeout: PAIRING_DAEMON_RPC_TIMEOUT_MS });
70
+ }
71
+ catch (error) {
72
+ throw new Error(`Relay configuration was saved, but fetching the pairing offer failed: ${String(error)}`, { cause: error });
73
+ }
86
74
  }
87
75
  return {
88
76
  relayEnabled: offer.relayEnabled,
@@ -108,35 +96,17 @@ export function printDirectConnectionGuidance() {
108
96
  console.log("To connect another device directly, use the daemon's TCP address over your LAN, Tailscale, or another VPN.");
109
97
  console.log(`Learn more: ${RELAY_DOCS_URL}#direct-connections`);
110
98
  }
111
- export async function runPairCommand(options, dependencyOverrides = {}) {
112
- if (options.home)
113
- process.env.PASEO_HOME = options.home;
114
- const dependencies = {
115
- resolveOffer: resolveLocalPairingOffer,
116
- confirmRelay: confirmRelayPairing,
117
- printDirectGuidance: printDirectConnectionGuidance,
118
- isInteractive: () => Boolean(process.stdin.isTTY && process.stdout.isTTY),
119
- output: createProcessOutput(),
120
- ...dependencyOverrides,
121
- };
122
- const paseoHome = resolvePaseoHome();
123
- let pairing = await dependencies.resolveOffer({
124
- paseoHome,
125
- enableRelay: options.relay === true,
126
- });
127
- const canPrompt = dependencies.isInteractive() && options.json !== true;
128
- if (!pairing.relayEnabled && canPrompt) {
129
- const shouldEnable = await dependencies.confirmRelay();
130
- if (!shouldEnable) {
131
- dependencies.printDirectGuidance();
132
- dependencies.output.writeStderr(`${chalk.yellow("No pairing QR was created.")}\n`);
133
- dependencies.output.setExitCode(1);
134
- return;
135
- }
136
- pairing = await dependencies.resolveOffer({ paseoHome, enableRelay: true });
137
- dependencies.output.success("Relay enabled");
138
- }
139
- outputPairingResult(pairing, options, dependencies.output);
99
+ export async function runPairCommand(options) {
100
+ const output = createProcessOutput();
101
+ const target = options.daemonTarget;
102
+ const resolveOffer = (enableRelay) => target.kind === "instance"
103
+ ? resolveLocalPairingOffer({ paseoHome: target.home, enableRelay })
104
+ : resolveDaemonPairingOffer(target, enableRelay);
105
+ const offline = target.kind === "instance" && !(await readDaemonInstance(target.home));
106
+ const pairing = await resolveOffer(options.relay === true);
107
+ if (offline)
108
+ output.writeStderr(`Offline pairing offer. Start with: paseo daemon start --home ${JSON.stringify(target.kind === "instance" ? target.home : "")}\n`);
109
+ outputPairingResult(pairing, options, output);
140
110
  }
141
111
  function outputPairingResult(pairing, options, output) {
142
112
  if (!pairing.relayEnabled || !pairing.url) {
@@ -1,10 +1,12 @@
1
- import type { Command } from "commander";
1
+ import { Command } from "commander";
2
2
  import type { CommandOptions, OutputSchema, SingleResult } from "../../output/index.js";
3
3
  export interface DaemonReloadResult {
4
+ restartCommand: string;
4
5
  appliedPaths: string[];
5
6
  restartRequiredPaths: string[];
6
7
  overrideControlledPaths: string[];
7
8
  }
8
9
  export declare const daemonReloadSchema: OutputSchema<DaemonReloadResult>;
9
10
  export declare function runDaemonReloadCommand(options: CommandOptions, _command: Command): Promise<SingleResult<DaemonReloadResult>>;
11
+ export declare function daemonReloadCommand(): Command;
10
12
  //# sourceMappingURL=reload.d.ts.map
@@ -1,3 +1,7 @@
1
+ import { addJsonAndDaemonHostOptions } from "../../utils/command-options.js";
2
+ import { withOutput } from "../../output/index.js";
3
+ import { Command } from "commander";
4
+ import { describeDaemonTarget } from "../../utils/daemon-target.js";
1
5
  import { connectToDaemon } from "../../utils/client.js";
2
6
  export const daemonReloadSchema = {
3
7
  idField: () => "daemon-config",
@@ -7,7 +11,7 @@ export const daemonReloadSchema = {
7
11
  return "";
8
12
  const lines = ["Configuration reloaded."];
9
13
  if (result.data.restartRequiredPaths.length > 0) {
10
- lines.push("", "Warning: These changes require a daemon restart:", ...result.data.restartRequiredPaths.map((path) => ` ${path}`), "", "Run: paseo daemon restart");
14
+ lines.push("", "Warning: These changes require a daemon restart:", ...result.data.restartRequiredPaths.map((path) => ` ${path}`), "", `Run: ${result.data.restartCommand}`);
11
15
  }
12
16
  if (result.data.overrideControlledPaths.length > 0) {
13
17
  lines.push("", "Warning: These settings are controlled by daemon launch overrides:", ...result.data.overrideControlledPaths.map((path) => ` ${path}`));
@@ -16,12 +20,13 @@ export const daemonReloadSchema = {
16
20
  },
17
21
  };
18
22
  export async function runDaemonReloadCommand(options, _command) {
19
- const client = await connectToDaemon({ host: options.host });
23
+ const client = await connectToDaemon({ target: options.daemonTarget });
20
24
  try {
21
25
  const payload = await client.reloadDaemonConfig();
22
26
  return {
23
27
  type: "single",
24
28
  data: {
29
+ restartCommand: `paseo daemon restart ${options.daemonTarget.kind === "instance" ? `--home ${JSON.stringify(options.daemonTarget.home)}` : `--host ${JSON.stringify(describeDaemonTarget(options.daemonTarget))}`}`,
25
30
  appliedPaths: payload.appliedPaths,
26
31
  restartRequiredPaths: payload.restartRequiredPaths,
27
32
  overrideControlledPaths: payload.overrideControlledPaths,
@@ -33,4 +38,7 @@ export async function runDaemonReloadCommand(options, _command) {
33
38
  await client.close();
34
39
  }
35
40
  }
41
+ export function daemonReloadCommand() {
42
+ return addJsonAndDaemonHostOptions(new Command("reload").description("Reload config.json without restarting")).action(withOutput(runDaemonReloadCommand));
43
+ }
36
44
  //# sourceMappingURL=reload.js.map
@@ -1,12 +1,20 @@
1
- import type { Command } from "commander";
2
- import type { CommandOptions, SingleResult } from "../../output/index.js";
3
- interface RestartResult {
4
- action: "restarted";
5
- home: string;
6
- pid: string;
7
- message: string;
8
- }
9
- export type RestartCommandResult = SingleResult<RestartResult>;
10
- export declare function runRestartCommand(options: CommandOptions, _command: Command): Promise<RestartCommandResult>;
11
- export {};
1
+ import { Command } from "commander";
2
+ import { type CommandOptions } from "../../output/index.js";
3
+ export declare function daemonRestartCommand(): Command;
4
+ export declare function runRestartCommand(options: CommandOptions, _command: Command): Promise<{
5
+ type: "single";
6
+ data: {
7
+ action: string;
8
+ target: string;
9
+ supervisorPid: number | null;
10
+ previousWorkerPid: number;
11
+ workerPid: number;
12
+ acknowledged: boolean;
13
+ };
14
+ schema: {
15
+ idField: "action";
16
+ columns: never[];
17
+ renderHuman: () => string;
18
+ };
19
+ }>;
12
20
  //# sourceMappingURL=restart.d.ts.map
@@ -1,99 +1,113 @@
1
- import { startLocalDaemonDetached, stopLocalDaemon, DEFAULT_STOP_TIMEOUT_MS, } from "./local-daemon.js";
2
- const restartResultSchema = {
3
- idField: "action",
4
- columns: [
5
- {
6
- header: "STATUS",
7
- field: "action",
8
- color: () => "green",
9
- },
10
- { header: "HOME", field: "home" },
11
- { header: "PID", field: "pid" },
12
- { header: "MESSAGE", field: "message" },
13
- ],
14
- };
15
- function parseTimeoutMs(raw) {
16
- if (typeof raw !== "string" || raw.trim().length === 0) {
17
- return DEFAULT_STOP_TIMEOUT_MS;
18
- }
19
- const seconds = Number(raw);
20
- if (!Number.isFinite(seconds) || seconds <= 0) {
21
- const error = {
22
- code: "INVALID_TIMEOUT",
23
- message: `Invalid timeout value: ${raw}`,
24
- details: "Timeout must be a positive number of seconds",
25
- };
26
- throw error;
27
- }
28
- return Math.ceil(seconds * 1000);
29
- }
30
- function toStartOptions(options) {
31
- const startOptions = {
32
- home: typeof options.home === "string" ? options.home : undefined,
33
- listen: typeof options.listen === "string" ? options.listen : undefined,
34
- port: typeof options.port === "string" ? options.port : undefined,
35
- relay: typeof options.relay === "boolean" ? options.relay : undefined,
36
- mcp: typeof options.mcp === "boolean" ? options.mcp : undefined,
37
- injectMcp: typeof options.injectMcp === "boolean" ? options.injectMcp : undefined,
38
- webUi: typeof options.webUi === "boolean" ? options.webUi : undefined,
39
- hostnames: typeof options.hostnames === "string" ? options.hostnames : undefined,
40
- };
41
- if (startOptions.listen && startOptions.port) {
42
- const error = {
43
- code: "INVALID_OPTIONS",
44
- message: "Cannot use --listen and --port together",
45
- };
46
- throw error;
47
- }
48
- return startOptions;
1
+ import { Command } from "commander";
2
+ import { readDaemonInstance, isSameDaemonInstance, DaemonInstanceError } from "@camerontaylor/paseo-server";
3
+ import { setTimeout as delay } from "node:timers/promises";
4
+ import { connectToDaemon } from "../../utils/client.js";
5
+ import { withOutput } from "../../output/index.js";
6
+ import { addJsonAndDaemonHostOptions } from "../../utils/command-options.js";
7
+ import { describeDaemonTarget } from "../../utils/daemon-target.js";
8
+ import { parseTimeoutMs, rejectRemovedLaunchFlags } from "./local-daemon.js";
9
+ export function daemonRestartCommand() {
10
+ return rejectRemovedLaunchFlags(addJsonAndDaemonHostOptions(new Command("restart").description("Restart the selected daemon worker, retaining its supervisor launch")))
11
+ .option("--timeout <seconds>", "Replacement readiness deadline (default: 600)")
12
+ .action(withOutput(runRestartCommand));
49
13
  }
50
14
  export async function runRestartCommand(options, _command) {
51
- const timeoutMs = parseTimeoutMs(options.timeout);
52
- const force = options.force === true;
53
- const startOptions = toStartOptions(options);
15
+ const target = options.daemonTarget;
16
+ const deadline = Date.now() + parseTimeoutMs(options.timeout);
17
+ const remaining = () => Math.max(1, deadline - Date.now());
18
+ const instance = target.kind === "instance" ? await readDaemonInstance(target.home) : null;
19
+ async function checkSupervisor() {
20
+ if (target.kind !== "instance")
21
+ return;
22
+ const current = await readDaemonInstance(target.home);
23
+ if (!current || !instance || !isSameDaemonInstance(instance, current))
24
+ throw new DaemonInstanceError("DAEMON_REPLACED", `Supervisor exited or was replaced for ${target.home}.`);
25
+ }
26
+ if (target.kind === "instance" && !instance)
27
+ throw new DaemonInstanceError("DAEMON_NOT_RUNNING", `Daemon is not running for ${target.home}.`);
28
+ const client = await connectToDaemon({
29
+ target,
30
+ instance: instance ?? undefined,
31
+ timeout: remaining(),
32
+ });
33
+ let workerPid;
34
+ const serverId = client.getLastServerInfoMessage()?.serverId;
35
+ let acknowledged = false;
54
36
  try {
55
- let stopResult;
37
+ workerPid = (await client.getDaemonStatus({ timeout: remaining() })).pid;
38
+ await checkSupervisor();
56
39
  try {
57
- stopResult = await stopLocalDaemon({
58
- home: startOptions.home,
59
- timeoutMs,
60
- force,
61
- });
40
+ await client.restartServer("cli_restart", undefined, { timeout: remaining() });
41
+ acknowledged = true;
42
+ }
43
+ catch (error) {
44
+ if (!isReconnectFailure(error))
45
+ throw error;
62
46
  }
63
- catch (err) {
64
- const isTimeout = err instanceof Error && err.message.includes("Timed out waiting for daemon PID");
65
- if (!force && isTimeout) {
66
- stopResult = await stopLocalDaemon({
67
- home: startOptions.home,
68
- timeoutMs,
69
- force: true,
70
- });
47
+ }
48
+ finally {
49
+ await client.close();
50
+ }
51
+ let lastError = "No replacement worker observed";
52
+ while (Date.now() < deadline) {
53
+ try {
54
+ const replacement = await connectToDaemon({
55
+ target,
56
+ instance: instance ?? undefined,
57
+ timeout: Math.min(1000, remaining()),
58
+ });
59
+ try {
60
+ if (replacement.getLastServerInfoMessage()?.serverId !== serverId)
61
+ throw new Error("Connected peer identity changed");
62
+ const status = await replacement.getDaemonStatus({ timeout: Math.min(1000, remaining()) });
63
+ if (status.pid !== workerPid) {
64
+ await checkSupervisor();
65
+ return {
66
+ type: "single",
67
+ data: {
68
+ action: "restarted",
69
+ target: describeDaemonTarget(target),
70
+ supervisorPid: instance?.pid ?? null,
71
+ previousWorkerPid: workerPid,
72
+ workerPid: status.pid,
73
+ acknowledged,
74
+ },
75
+ schema: {
76
+ idField: "action",
77
+ columns: [],
78
+ renderHuman: () => `Restarted worker ${workerPid} → ${status.pid} at ${describeDaemonTarget(target)}. Supervisor launch retained.`,
79
+ },
80
+ };
81
+ }
71
82
  }
72
- else {
73
- throw err;
83
+ finally {
84
+ await replacement.close();
74
85
  }
75
86
  }
76
- const startup = await startLocalDaemonDetached(startOptions);
77
- const before = stopResult.pid === null ? "not running" : `PID ${stopResult.pid}`;
78
- const after = startup.pid === null ? "unknown PID" : `PID ${startup.pid}`;
79
- return {
80
- type: "single",
81
- data: {
82
- action: "restarted",
83
- home: stopResult.home,
84
- pid: startup.pid === null ? "-" : String(startup.pid),
85
- message: `Local daemon restarted (${before} -> ${after})`,
86
- },
87
- schema: restartResultSchema,
88
- };
89
- }
90
- catch (err) {
91
- const message = err instanceof Error ? err.message : String(err);
92
- const error = {
93
- code: "RESTART_FAILED",
94
- message: `Failed to restart local daemon: ${message}`,
95
- };
96
- throw error;
87
+ catch (error) {
88
+ lastError = error;
89
+ const code = error?.code;
90
+ if (code === "DAEMON_REPLACED" || code === "DAEMON_NOT_RUNNING")
91
+ break;
92
+ if (!isReconnectFailure(error))
93
+ throw error;
94
+ }
95
+ await delay(Math.min(100, remaining()));
97
96
  }
97
+ throw {
98
+ code: "RESTART_NOT_CONFIRMED",
99
+ message: `Replacement was not confirmed for ${describeDaemonTarget(target)}. Restart acknowledged: ${acknowledged}. Last observation: ${String(lastError)}`,
100
+ };
101
+ }
102
+ function isReconnectFailure(error) {
103
+ return Boolean(error &&
104
+ typeof error === "object" &&
105
+ "code" in error &&
106
+ [
107
+ "DAEMON_CONNECTION_LOST",
108
+ "DAEMON_REQUEST_TIMEOUT",
109
+ "DAEMON_UNREACHABLE",
110
+ "DAEMON_NOT_READY",
111
+ ].includes(String(error.code)));
98
112
  }
99
113
  //# sourceMappingURL=restart.js.map
@@ -1,7 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { isCancel, password as passwordPrompt } from "@clack/prompts";
3
- import { hashDaemonPassword, loadPersistedConfig, savePersistedConfig, } from "@camerontaylor/paseo-server";
4
- import { resolveLocalPaseoHome } from "./local-daemon.js";
3
+ import { hashDaemonPassword, readPersistedConfig, resolvePaseoHome, savePersistedConfig, } from "@camerontaylor/paseo-server";
5
4
  const CONFIG_FILENAME = "config.json";
6
5
  const setPasswordResultSchema = {
7
6
  idField: "action",
@@ -44,9 +43,9 @@ async function promptForPassword(promptPassword) {
44
43
  return first;
45
44
  }
46
45
  export async function setDaemonPasswordInConfig(newPassword, options = {}) {
47
- const paseoHome = resolveLocalPaseoHome(options.home);
46
+ const paseoHome = resolvePaseoHome({ PASEO_HOME: options.home });
48
47
  const configPath = path.join(paseoHome, CONFIG_FILENAME);
49
- const persisted = loadPersistedConfig(paseoHome);
48
+ const persisted = readPersistedConfig(paseoHome);
50
49
  const nextConfig = {
51
50
  ...persisted,
52
51
  daemon: {
@@ -61,8 +60,8 @@ export async function setDaemonPasswordInConfig(newPassword, options = {}) {
61
60
  return {
62
61
  action: "password_set",
63
62
  configPath,
64
- restartCommand: "paseo daemon restart",
65
- message: `Password written to ${configPath}\nRestart the daemon for the change to take effect.\nRun: paseo daemon restart`,
63
+ restartCommand: `paseo daemon restart --home ${JSON.stringify(paseoHome)}`,
64
+ message: `Password written to ${configPath}\nRestart the daemon for the change to take effect.\nRun: paseo daemon restart --home ${JSON.stringify(paseoHome)}`,
66
65
  };
67
66
  }
68
67
  export async function runSetPasswordCommand(options, _command) {
@@ -71,7 +70,7 @@ export async function runSetPasswordCommand(options, _command) {
71
70
  : (message) => passwordPrompt({ message });
72
71
  const newPassword = await promptForPassword(promptPassword);
73
72
  const result = await setDaemonPasswordInConfig(newPassword, {
74
- home: typeof options.home === "string" ? options.home : undefined,
73
+ home: options.daemonTarget.kind === "instance" ? options.daemonTarget.home : undefined,
75
74
  });
76
75
  return {
77
76
  type: "single",
@@ -1,6 +1,20 @@
1
1
  import { Command } from "commander";
2
- import { type DaemonStartOptions as StartOptions } from "./local-daemon.js";
3
- export type { DaemonStartOptions as StartOptions } from "./local-daemon.js";
2
+ import { type CommandOptions } from "../../output/index.js";
4
3
  export declare function startCommand(): Command;
5
- export declare function runStart(options: StartOptions): Promise<void>;
4
+ export declare function runStart(options: CommandOptions, _command: Command): Promise<{
5
+ type: "single";
6
+ data: {
7
+ action: string;
8
+ home: string;
9
+ pid: number;
10
+ listen: string | null;
11
+ logPath: string;
12
+ };
13
+ schema: {
14
+ idField: "pid";
15
+ columns: never[];
16
+ renderHuman: () => string;
17
+ };
18
+ }>;
19
+ export declare function daemonRunCommand(): Command;
6
20
  //# sourceMappingURL=start.d.ts.map
@@ -1,57 +1,52 @@
1
- import { Command, Option } from "commander";
2
- import chalk from "chalk";
3
- import { startLocalDaemonForeground, startLocalDaemonDetached, } from "./local-daemon.js";
4
- import { getErrorMessage } from "../../utils/errors.js";
1
+ import { addLocalDaemonOptions } from "../../utils/command-options.js";
2
+ import { Command } from "commander";
3
+ import { daemonLogPath } from "@camerontaylor/paseo-server";
4
+ import { launchLocalDaemon, parseTimeoutMs, rejectRemovedLaunchFlags } from "./local-daemon.js";
5
+ import { withOutput } from "../../output/index.js";
5
6
  export function startCommand() {
6
- return new Command("start")
7
- .description("Start the local Paseo daemon")
8
- .option("--listen <listen>", "Listen target (host:port, port, or unix socket path)")
9
- .option("--port <port>", "Port to listen on (default: 6767)")
10
- .option("--home <path>", "Paseo home directory (default: ~/.paseo)")
11
- .option("--foreground", "Run in foreground (don't daemonize)")
12
- .option("--relay", "Enable relay connection")
13
- .option("--no-relay", "Disable relay connection")
14
- .option("--relay-use-tls", "Use wss:// for the relay connection and pairing offers")
15
- .option("--no-mcp", "Disable the Agent MCP HTTP endpoint")
16
- .option("--no-inject-mcp", "Disable auto-injecting the Paseo MCP into created agents")
17
- .option("--web-ui", "Enable the bundled daemon web UI")
18
- .option("--no-web-ui", "Disable the bundled daemon web UI")
19
- .option("--hostnames <hosts>", 'Daemon hostnames (comma-separated, e.g. "myhost,.example.com" or "true" for any)')
20
- .addOption(new Option("--allowed-hosts <hosts>").hideHelp())
21
- .action(async (options) => {
22
- await runStart({
23
- ...options,
24
- hostnames: options.hostnames ?? options.allowedHosts,
25
- });
26
- });
7
+ return rejectRemovedLaunchFlags(addLocalDaemonOptions(new Command("start")))
8
+ .description("Start the local daemon from persistent configuration (local operation)")
9
+ .option("--timeout <seconds>", "Readiness deadline (default: 600)")
10
+ .action(withOutput(runStart));
27
11
  }
28
- export async function runStart(options) {
29
- if (options.listen && options.port) {
30
- console.error(chalk.red("Cannot use --listen and --port together"));
31
- process.exit(1);
32
- }
33
- if (!options.foreground) {
34
- try {
35
- const startup = await startLocalDaemonDetached(options);
36
- console.log(chalk.green(`Daemon starting in background (PID ${startup.pid ?? "unknown"}).`));
37
- console.log(chalk.dim(`Logs: ${startup.logPath}`));
38
- }
39
- catch (err) {
40
- exitWithError(getErrorMessage(err));
41
- }
42
- return;
43
- }
44
- try {
45
- const status = startLocalDaemonForeground(options);
46
- process.exit(status);
47
- }
48
- catch (err) {
49
- const message = getErrorMessage(err);
50
- exitWithError(`Failed to start daemon: ${message}`);
51
- }
12
+ export async function runStart(options, _command) {
13
+ if (options.daemonTarget.kind !== "instance")
14
+ throw new Error("Start requires a local home");
15
+ const home = options.daemonTarget.home;
16
+ const result = await launchLocalDaemon({ home, timeoutMs: parseTimeoutMs(options.timeout) });
17
+ const data = {
18
+ action: result.spawned ? "started" : "already_running",
19
+ home,
20
+ pid: result.instance.pid,
21
+ listen: result.instance.listen,
22
+ logPath: daemonLogPath(home),
23
+ };
24
+ return {
25
+ type: "single",
26
+ data,
27
+ schema: {
28
+ idField: "pid",
29
+ columns: [],
30
+ renderHuman: () => `${result.spawned ? "Started" : "Already running"}: PID ${data.pid}${data.listen ? `, listening on ${data.listen}` : ", not ready"}\nLogs: ${data.logPath}`,
31
+ },
32
+ };
52
33
  }
53
- function exitWithError(message) {
54
- console.error(chalk.red(message));
55
- process.exit(1);
34
+ export function daemonRunCommand() {
35
+ return rejectRemovedLaunchFlags(addLocalDaemonOptions(new Command("run")))
36
+ .description("Run a local daemon in the foreground with deployment environment overrides")
37
+ .action(withOutput(async (options, _command) => {
38
+ if (options.daemonTarget.kind !== "instance")
39
+ throw new Error("Run requires a local home");
40
+ const result = await launchLocalDaemon({
41
+ home: options.daemonTarget.home,
42
+ foreground: true,
43
+ });
44
+ process.exitCode = result.exitCode ?? 0;
45
+ return {
46
+ type: "single",
47
+ data: { pid: result.instance.pid, action: result.spawned ? "exited" : "already_running" },
48
+ schema: { idField: "pid", columns: [] },
49
+ };
50
+ }));
56
51
  }
57
52
  //# sourceMappingURL=start.js.map
@@ -1,21 +1,13 @@
1
- import type { Command } from "commander";
2
- import type { CommandOptions, ListResult } from "../../output/index.js";
3
- interface StatusRow {
4
- key: string;
5
- value: string;
6
- }
7
- interface RelayStatusConfig {
8
- enabled: boolean;
9
- endpoint: string;
10
- publicEndpoint: string;
11
- useTls: boolean;
12
- publicUseTls: boolean;
13
- }
14
- export declare function selectRelayStatus(input: {
15
- persisted: RelayStatusConfig;
16
- live?: RelayStatusConfig;
17
- }): string;
18
- export type StatusResult = ListResult<StatusRow>;
19
- export declare function runStatusCommand(options: CommandOptions, _command: Command): Promise<StatusResult>;
20
- export {};
1
+ import { Command } from "commander";
2
+ import { type CommandOptions } from "../../output/index.js";
3
+ export declare function daemonStatusCommand(): Command;
4
+ export declare function runStatusCommand(options: CommandOptions, _command: Command): Promise<{
5
+ type: "single";
6
+ data: Record<string, unknown>;
7
+ schema: {
8
+ idField: () => string;
9
+ columns: never[];
10
+ renderHuman: () => string;
11
+ };
12
+ }>;
21
13
  //# sourceMappingURL=status.d.ts.map