@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.
- package/dist/cli.js +25 -37
- package/dist/commands/agent/archive.js +2 -15
- package/dist/commands/agent/attach.d.ts +1 -0
- package/dist/commands/agent/attach.js +8 -12
- package/dist/commands/agent/delete.js +4 -15
- package/dist/commands/agent/detach.js +2 -13
- package/dist/commands/agent/import.js +2 -16
- package/dist/commands/agent/inspect.js +2 -15
- package/dist/commands/agent/logs.js +12 -12
- package/dist/commands/agent/ls.js +2 -23
- package/dist/commands/agent/mode.js +2 -2
- package/dist/commands/agent/open.js +2 -8
- package/dist/commands/agent/reload.js +2 -15
- package/dist/commands/agent/run.js +11 -16
- package/dist/commands/agent/send.js +6 -15
- package/dist/commands/agent/stop.js +2 -15
- package/dist/commands/agent/update.js +2 -15
- package/dist/commands/agent/wait.js +4 -15
- package/dist/commands/clone.js +2 -8
- package/dist/commands/daemon/config.d.ts +3 -0
- package/dist/commands/daemon/config.js +102 -0
- package/dist/commands/daemon/index.js +20 -52
- package/dist/commands/daemon/local-daemon.d.ts +10 -75
- package/dist/commands/daemon/local-daemon.js +61 -503
- package/dist/commands/daemon/pair.d.ts +3 -8
- package/dist/commands/daemon/pair.js +31 -61
- package/dist/commands/daemon/reload.d.ts +3 -1
- package/dist/commands/daemon/reload.js +10 -2
- package/dist/commands/daemon/restart.d.ts +19 -11
- package/dist/commands/daemon/restart.js +102 -88
- package/dist/commands/daemon/set-password.js +6 -7
- package/dist/commands/daemon/start.d.ts +17 -3
- package/dist/commands/daemon/start.js +47 -52
- package/dist/commands/daemon/status.d.ts +12 -20
- package/dist/commands/daemon/status.js +90 -342
- package/dist/commands/daemon/stop.d.ts +19 -14
- package/dist/commands/daemon/stop.js +53 -55
- package/dist/commands/heartbeat/index.js +3 -3
- package/dist/commands/hub/connect.d.ts +1 -0
- package/dist/commands/hub/connect.js +1 -1
- package/dist/commands/hub/daemon-client.d.ts +3 -2
- package/dist/commands/hub/daemon-client.js +3 -3
- package/dist/commands/hub/disconnect.d.ts +1 -0
- package/dist/commands/hub/disconnect.js +1 -1
- package/dist/commands/hub/index.js +2 -2
- package/dist/commands/hub/init.d.ts +3 -1
- package/dist/commands/hub/init.js +13 -12
- package/dist/commands/hub/login.d.ts +4 -4
- package/dist/commands/hub/login.js +1 -1
- package/dist/commands/hub/logout.d.ts +1 -0
- package/dist/commands/hub/logout.js +2 -2
- package/dist/commands/hub/permissions.d.ts +1 -0
- package/dist/commands/hub/permissions.js +2 -2
- package/dist/commands/onboard.d.ts +7 -2
- package/dist/commands/onboard.js +58 -210
- package/dist/commands/open.js +2 -0
- package/dist/commands/permit/allow.js +2 -15
- package/dist/commands/permit/deny.js +2 -15
- package/dist/commands/permit/ls.js +2 -15
- package/dist/commands/plugin/index.js +7 -7
- package/dist/commands/plugin/shared.d.ts +4 -3
- package/dist/commands/plugin/shared.js +8 -8
- package/dist/commands/project/create.js +5 -3
- package/dist/commands/project/delete.js +4 -2
- package/dist/commands/project/ls.js +2 -2
- package/dist/commands/project/rename.js +4 -2
- package/dist/commands/provider/add.js +2 -2
- package/dist/commands/provider/diagnostic.js +1 -1
- package/dist/commands/provider/ls.js +2 -30
- package/dist/commands/provider/models.js +1 -1
- package/dist/commands/provider/rm.js +2 -2
- package/dist/commands/schedule/create.js +2 -2
- package/dist/commands/schedule/delete.js +1 -1
- package/dist/commands/schedule/inspect.js +1 -1
- package/dist/commands/schedule/logs.js +1 -1
- package/dist/commands/schedule/ls.js +1 -1
- package/dist/commands/schedule/pause.js +1 -1
- package/dist/commands/schedule/resume.js +1 -1
- package/dist/commands/schedule/run-once.js +1 -1
- package/dist/commands/schedule/shared.d.ts +3 -1
- package/dist/commands/schedule/shared.js +7 -5
- package/dist/commands/schedule/update.js +1 -1
- package/dist/commands/script/ls.js +1 -1
- package/dist/commands/script/shared.d.ts +2 -1
- package/dist/commands/script/shared.js +3 -3
- package/dist/commands/script/start.js +1 -1
- package/dist/commands/script/stop.js +1 -1
- package/dist/commands/terminal/capture.d.ts +1 -1
- package/dist/commands/terminal/capture.js +2 -3
- package/dist/commands/terminal/create.js +1 -1
- package/dist/commands/terminal/kill.js +1 -1
- package/dist/commands/terminal/ls.js +1 -1
- package/dist/commands/terminal/send-keys.d.ts +1 -1
- package/dist/commands/terminal/send-keys.js +2 -3
- package/dist/commands/terminal/shared.d.ts +2 -1
- package/dist/commands/terminal/shared.js +5 -3
- package/dist/commands/workspace/archive.d.ts +1 -0
- package/dist/commands/workspace/archive.js +4 -2
- package/dist/commands/workspace/create.js +4 -2
- package/dist/commands/workspace/ls.d.ts +1 -0
- package/dist/commands/workspace/ls.js +2 -2
- package/dist/commands/workspace/rename.d.ts +1 -0
- package/dist/commands/workspace/rename.js +4 -2
- package/dist/commands/workspace/setup.d.ts +1 -0
- package/dist/commands/workspace/setup.js +2 -2
- package/dist/commands/worktree/archive.js +4 -2
- package/dist/commands/worktree/create.js +2 -10
- package/dist/commands/worktree/ls.d.ts +0 -2
- package/dist/commands/worktree/ls.js +4 -30
- package/dist/output/with-output.d.ts +2 -0
- package/dist/run.js +11 -1
- package/dist/utils/client-id.d.ts +1 -1
- package/dist/utils/client-id.js +8 -14
- package/dist/utils/client.d.ts +16 -21
- package/dist/utils/client.js +57 -109
- package/dist/utils/command-options.d.ts +1 -0
- package/dist/utils/command-options.js +24 -2
- package/dist/utils/daemon-target.d.ts +13 -0
- package/dist/utils/daemon-target.js +51 -0
- 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,
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
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
|
|
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(
|
|
33
|
-
await runPairCommand(command.optsWithGlobals());
|
|
34
|
-
});
|
|
30
|
+
.action(withGlobalOptions((options, _command) => runPairCommand(options)));
|
|
35
31
|
}
|
|
36
32
|
export async function resolveLocalPairingOffer(options) {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (
|
|
41
|
-
|
|
42
|
-
|
|
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(
|
|
61
|
-
const client = await
|
|
62
|
-
|
|
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
|
-
|
|
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
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
|
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}`), "",
|
|
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({
|
|
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
|
|
2
|
-
import type
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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 {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
52
|
-
const
|
|
53
|
-
const
|
|
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
|
-
|
|
37
|
+
workerPid = (await client.getDaemonStatus({ timeout: remaining() })).pid;
|
|
38
|
+
await checkSupervisor();
|
|
56
39
|
try {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
73
|
-
|
|
83
|
+
finally {
|
|
84
|
+
await replacement.close();
|
|
74
85
|
}
|
|
75
86
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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,
|
|
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 =
|
|
46
|
+
const paseoHome = resolvePaseoHome({ PASEO_HOME: options.home });
|
|
48
47
|
const configPath = path.join(paseoHome, CONFIG_FILENAME);
|
|
49
|
-
const persisted =
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
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 {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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
|
|
8
|
-
.option("--
|
|
9
|
-
.
|
|
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.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
2
|
-
import type
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|