@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,3 +1,4 @@
1
+ import type { DaemonTarget } from "../../utils/daemon-target.js";
1
2
  import type { ProviderSnapshotEntry } from "@camerontaylor/paseo-protocol/agent-types";
2
3
  export interface HubStatus {
3
4
  state: string;
@@ -33,8 +34,8 @@ export interface HubDaemonClient {
33
34
  close(): Promise<void>;
34
35
  }
35
36
  export interface HubDaemonConnection {
36
- connect(host: string | undefined): Promise<HubDaemonClient>;
37
+ connect(target: DaemonTarget): Promise<HubDaemonClient>;
37
38
  }
38
39
  export declare const productionHubDaemonConnection: HubDaemonConnection;
39
- export declare function withHubDaemon<T>(connection: HubDaemonConnection, host: string | undefined, action: (client: HubDaemonClient) => Promise<T>): Promise<T>;
40
+ export declare function withHubDaemon<T>(connection: HubDaemonConnection, target: DaemonTarget, action: (client: HubDaemonClient) => Promise<T>): Promise<T>;
40
41
  //# sourceMappingURL=daemon-client.d.ts.map
@@ -1,9 +1,9 @@
1
1
  import { connectToDaemon } from "../../utils/client.js";
2
2
  export const productionHubDaemonConnection = {
3
- connect: (host) => connectToDaemon({ host }),
3
+ connect: (target) => connectToDaemon({ target }),
4
4
  };
5
- export async function withHubDaemon(connection, host, action) {
6
- const client = await connection.connect(host);
5
+ export async function withHubDaemon(connection, target, action) {
6
+ const client = await connection.connect(target);
7
7
  try {
8
8
  return await action(client);
9
9
  }
@@ -4,6 +4,7 @@ import { type HubReporter } from "./reporter.js";
4
4
  interface HubDisconnectOptions {
5
5
  force?: boolean;
6
6
  host?: string;
7
+ daemonTarget: import("../../utils/daemon-target.js").DaemonTarget;
7
8
  json?: boolean;
8
9
  }
9
10
  interface HubDisconnectDependencies {
@@ -4,7 +4,7 @@ import { withHubDaemon } from "./daemon-client.js";
4
4
  import { reportHubProgress } from "./reporter.js";
5
5
  import { hubStatusResult } from "./status-output.js";
6
6
  export function runHubDisconnect(options, dependencies) {
7
- return withHubDaemon(dependencies.daemon, options.host, async (client) => {
7
+ return withHubDaemon(dependencies.daemon, options.daemonTarget, async (client) => {
8
8
  const current = (await client.getHubStatus()).status;
9
9
  if (current.hubOrigin !== null) {
10
10
  reportHubProgress(dependencies.reporter, options, `Disconnecting this daemon from ${current.hubOrigin}`);
@@ -40,7 +40,7 @@ export function createHubCommand(overrides = {}) {
40
40
  flow: environment.login,
41
41
  reporter: environment.reporter,
42
42
  isInteractive: environment.isInteractive,
43
- continueGuidedSetup: (origin) => continueHubGuidedSetup(origin, environment),
43
+ continueGuidedSetup: (origin, daemonTarget) => continueHubGuidedSetup(origin, { ...environment, daemonTarget }),
44
44
  });
45
45
  addHubInitCommand(hub, environment);
46
46
  addHubConnectCommand(hub, {
@@ -52,7 +52,7 @@ export function createHubCommand(overrides = {}) {
52
52
  });
53
53
  addJsonAndDaemonHostOptions(hub.command("status")).action(withOutput(async (...args) => {
54
54
  const options = args.at(-2);
55
- return withHubDaemon(environment.daemon, options.host, async (client) => hubStatusResult((await client.getHubStatus()).status));
55
+ return withHubDaemon(environment.daemon, options.daemonTarget, async (client) => hubStatusResult((await client.getHubStatus()).status));
56
56
  }));
57
57
  addHubDisconnectCommand(hub, {
58
58
  daemon: environment.daemon,
@@ -1,3 +1,4 @@
1
+ import type { DaemonTarget } from "../../utils/daemon-target.js";
1
2
  import { select, text } from "@clack/prompts";
2
3
  import type { Command } from "commander";
3
4
  import type { HubCredentialStore } from "./credentials.js";
@@ -6,6 +7,7 @@ import type { HubHttpClient } from "./hub-client/index.js";
6
7
  import type { CliLoginFlow } from "./login-flow.js";
7
8
  import type { HubReporter } from "./reporter.js";
8
9
  export interface HubGuidedSetupEnvironment {
10
+ daemonTarget: DaemonTarget;
9
11
  env: Readonly<Record<string, string | undefined>>;
10
12
  credentials: HubCredentialStore;
11
13
  hub: HubHttpClient;
@@ -26,7 +28,7 @@ interface HubGuidedSetupState {
26
28
  daemonId?: string;
27
29
  deploy?: boolean;
28
30
  }
29
- export declare function addHubInitCommand(parent: Command, environment: HubGuidedSetupEnvironment): void;
31
+ export declare function addHubInitCommand(parent: Command, environment: Omit<HubGuidedSetupEnvironment, "daemonTarget">): void;
30
32
  export declare function runHubInit(environment: HubGuidedSetupEnvironment): Promise<void>;
31
33
  export declare function runHubGuidedSetup(environment: HubGuidedSetupEnvironment, state?: HubGuidedSetupState): Promise<void>;
32
34
  export declare function continueHubGuidedSetup(origin: string, environment: HubGuidedSetupEnvironment): Promise<void>;
@@ -1,3 +1,4 @@
1
+ import { withGlobalOptions } from "../../utils/command-options.js";
1
2
  import { cancel, confirm, intro, isCancel, log, note, outro, select, spinner, text, } from "@clack/prompts";
2
3
  import { execFile } from "node:child_process";
3
4
  import { randomUUID } from "node:crypto";
@@ -28,9 +29,9 @@ export function addHubInitCommand(parent, environment) {
28
29
  parent
29
30
  .command("init")
30
31
  .description("Create and optionally deploy a safe starter Hub trigger")
31
- .action(async () => {
32
+ .action(withGlobalOptions(async (options) => {
32
33
  try {
33
- await runHubInit(environment);
34
+ await runHubInit({ ...environment, daemonTarget: options.daemonTarget });
34
35
  }
35
36
  catch (error) {
36
37
  if (error instanceof HubInitCancelledError) {
@@ -40,7 +41,7 @@ export function addHubInitCommand(parent, environment) {
40
41
  cancel(initErrorMessage(error));
41
42
  process.exitCode = 1;
42
43
  }
43
- });
44
+ }));
44
45
  }
45
46
  export async function runHubInit(environment) {
46
47
  await runHubGuidedSetup(environment);
@@ -102,13 +103,13 @@ export async function runHubGuidedSetup(environment, state = {}) {
102
103
  outro(deploy ? "Hub is ready" : "Hub trigger is ready");
103
104
  }
104
105
  export async function continueHubGuidedSetup(origin, environment) {
105
- const currentStatus = await withHubDaemon(environment.daemon, undefined, async (daemon) => daemon.getHubStatus().then((response) => response.status));
106
+ const currentStatus = await withHubDaemon(environment.daemon, environment.daemonTarget, async (daemon) => daemon.getHubStatus().then((response) => response.status));
106
107
  const current = resolveHubInitConnection(currentStatus, origin);
107
108
  if (current.kind === "connected") {
108
109
  reportMessage(environment, `This daemon is already connected to ${origin}. Permissions: ${currentStatus.permissions.join(", ") || "None"}.`);
109
110
  }
110
111
  else if (current.kind === "pending") {
111
- await waitForDaemonReady(origin, environment.daemon);
112
+ await waitForDaemonReady(origin, environment);
112
113
  }
113
114
  else if (current.kind === "conflict") {
114
115
  reportMessage(environment, `This daemon is connected to ${current.origin}. Disconnect it before connecting to ${origin}.`);
@@ -163,7 +164,7 @@ async function ensureLogin(activeOrigin, environment) {
163
164
  return normalizedOrigin;
164
165
  }
165
166
  async function ensureDaemonConnection(origin, environment, confirmed = false, permissions = ["hub.execute"]) {
166
- const status = await withHubDaemon(environment.daemon, undefined, async (daemon) => daemon.getHubStatus().then((response) => response.status));
167
+ const status = await withHubDaemon(environment.daemon, environment.daemonTarget, async (daemon) => daemon.getHubStatus().then((response) => response.status));
167
168
  const connection = resolveHubInitConnection(status, origin);
168
169
  if (connection.kind === "connected") {
169
170
  if (permissions.includes("hub.execute") && !status.permissions.includes("hub.execute")) {
@@ -172,7 +173,7 @@ async function ensureDaemonConnection(origin, environment, confirmed = false, pe
172
173
  return connection.daemonId;
173
174
  }
174
175
  if (connection.kind === "pending") {
175
- return waitForDaemonReady(origin, environment.daemon);
176
+ return waitForDaemonReady(origin, environment);
176
177
  }
177
178
  if (connection.kind === "conflict") {
178
179
  throw new HubCommandError("HUB_DAEMON_ALREADY_CONNECTED", `This daemon is connected to ${connection.origin}. Disconnect it before running Hub init for ${origin}.`);
@@ -184,17 +185,17 @@ async function ensureDaemonConnection(origin, environment, confirmed = false, pe
184
185
  return connectDaemon(origin, environment, permissions);
185
186
  }
186
187
  async function connectDaemon(origin, environment, permissions = ["hub.execute"]) {
187
- await runHubConnect(origin, { permissions }, {
188
+ await runHubConnect(origin, { permissions, daemonTarget: environment.daemonTarget }, {
188
189
  env: environment.env,
189
190
  credentials: environment.credentials,
190
191
  hub: environment.hub,
191
192
  daemon: environment.daemon,
192
193
  reporter: environment.reporter,
193
194
  });
194
- return waitForDaemonReady(origin, environment.daemon);
195
+ return waitForDaemonReady(origin, environment);
195
196
  }
196
- async function waitForDaemonReady(origin, connection) {
197
- return withSpinner("Waiting for the daemon to connect", async (reporter) => withHubDaemon(connection, undefined, async (daemon) => {
197
+ async function waitForDaemonReady(origin, environment) {
198
+ return withSpinner("Waiting for the daemon to connect", async (reporter) => withHubDaemon(environment.daemon, environment.daemonTarget, async (daemon) => {
198
199
  const deadline = Date.now() + DAEMON_READY_TIMEOUT_MS;
199
200
  while (true) {
200
201
  const status = (await daemon.getHubStatus()).status;
@@ -260,7 +261,7 @@ async function chooseStarterAgentRuntime(environment, cwd) {
260
261
  return { ...runtime, mode: runtime.mode };
261
262
  }
262
263
  async function waitForStarterAgentProviders(environment, cwd) {
263
- return withSpinner("Discovering agent runtimes", async (reporter) => withHubDaemon(environment.daemon, undefined, async (daemon) => {
264
+ return withSpinner("Discovering agent runtimes", async (reporter) => withHubDaemon(environment.daemon, environment.daemonTarget, async (daemon) => {
264
265
  const deadline = Date.now() + PROVIDER_READY_TIMEOUT_MS;
265
266
  while (true) {
266
267
  const snapshot = await daemon.getProvidersSnapshot({ cwd });
@@ -1,3 +1,5 @@
1
+ import type { CommandOptions } from "../../output/index.js";
2
+ import type { DaemonTarget } from "../../utils/daemon-target.js";
1
3
  import type { Command } from "commander";
2
4
  import { type SingleResult } from "../../output/index.js";
3
5
  import type { HubCredentialStore } from "./credentials.js";
@@ -13,11 +15,9 @@ interface HubLoginDependencies {
13
15
  flow: Pick<CliLoginFlow, "authorize">;
14
16
  reporter: HubReporter;
15
17
  isInteractive?(): boolean;
16
- continueGuidedSetup?(origin: string): Promise<void>;
18
+ continueGuidedSetup?(origin: string, daemonTarget: DaemonTarget): Promise<void>;
17
19
  }
18
- export declare function runHubLogin(originInput: string | undefined, options: {
19
- json?: boolean;
20
- }, dependencies: HubLoginDependencies): Promise<SingleResult<HubLoginResult>>;
20
+ export declare function runHubLogin(originInput: string | undefined, options: Pick<CommandOptions, "json" | "daemonTarget">, dependencies: HubLoginDependencies): Promise<SingleResult<HubLoginResult>>;
21
21
  export declare function addHubLoginCommand(parent: Command, dependencies: HubLoginDependencies): void;
22
22
  export {};
23
23
  //# sourceMappingURL=login.d.ts.map
@@ -23,7 +23,7 @@ export async function runHubLogin(originInput, options, dependencies) {
23
23
  if (!options.json &&
24
24
  dependencies.isInteractive?.() &&
25
25
  dependencies.continueGuidedSetup !== undefined) {
26
- await dependencies.continueGuidedSetup(origin);
26
+ await dependencies.continueGuidedSetup(origin, options.daemonTarget);
27
27
  }
28
28
  return { type: "single", data: { origin, status: "logged_in" }, schema };
29
29
  }
@@ -13,6 +13,7 @@ interface HubLogoutOptions {
13
13
  disconnectDaemon?: boolean;
14
14
  force?: boolean;
15
15
  host?: string;
16
+ daemonTarget: import("../../utils/daemon-target.js").DaemonTarget;
16
17
  json?: boolean;
17
18
  }
18
19
  interface HubLogoutDependencies {
@@ -22,7 +22,7 @@ export async function runHubLogout(options, dependencies) {
22
22
  let daemonStatus = null;
23
23
  const shouldInspectDaemon = options.disconnectDaemon === true || mayPrompt;
24
24
  if (shouldInspectDaemon) {
25
- daemonStatus = await withHubDaemon(dependencies.daemon, options.host, async (daemon) => daemon.getHubStatus().then((response) => response.status));
25
+ daemonStatus = await withHubDaemon(dependencies.daemon, options.daemonTarget, async (daemon) => daemon.getHubStatus().then((response) => response.status));
26
26
  }
27
27
  const sameHub = daemonStatus?.hubOrigin === active.origin;
28
28
  let shouldDisconnect = options.disconnectDaemon === true && sameHub;
@@ -34,7 +34,7 @@ export async function runHubLogout(options, dependencies) {
34
34
  return logoutResult({ origin: active.origin, status: "logged_out", daemonDisconnected: false });
35
35
  }
36
36
  reportHubProgress(dependencies.reporter, options, `Disconnecting this daemon from ${active.origin}`);
37
- const disconnect = await withHubDaemon(dependencies.daemon, options.host, async (daemon) => {
37
+ const disconnect = await withHubDaemon(dependencies.daemon, options.daemonTarget, async (daemon) => {
38
38
  return daemon.disconnectHub(options.force ?? false);
39
39
  });
40
40
  dependencies.credentials.logoutActive();
@@ -4,6 +4,7 @@ import type { HubDaemonConnection } from "./daemon-client.js";
4
4
  import { type HubReporter } from "./reporter.js";
5
5
  interface HubPermissionsOptions {
6
6
  host?: string;
7
+ daemonTarget: import("../../utils/daemon-target.js").DaemonTarget;
7
8
  json?: boolean;
8
9
  }
9
10
  interface HubPermissionsDependencies {
@@ -11,7 +11,7 @@ const schema = {
11
11
  ],
12
12
  };
13
13
  export function runHubPermissionsList(options, dependencies) {
14
- return withHubDaemon(dependencies.daemon, options.host, async (client) => {
14
+ return withHubDaemon(dependencies.daemon, options.daemonTarget, async (client) => {
15
15
  const status = (await client.getHubStatus()).status;
16
16
  requireConnectedHub(status);
17
17
  return {
@@ -25,7 +25,7 @@ export function runHubPermissionsList(options, dependencies) {
25
25
  });
26
26
  }
27
27
  export function runHubPermissionChange(operation, permission, options, dependencies) {
28
- return withHubDaemon(dependencies.daemon, options.host, async (client) => {
28
+ return withHubDaemon(dependencies.daemon, options.daemonTarget, async (client) => {
29
29
  const current = (await client.getHubStatus()).status;
30
30
  requireConnectedHub(current);
31
31
  const response = await client.updateHubPermissions(operation === "grant" ? { grant: [permission] } : { revoke: [permission] });
@@ -1,6 +1,11 @@
1
1
  import { Command } from "commander";
2
- import { type DaemonStartOptions } from "./daemon/local-daemon.js";
3
- interface OnboardOptions extends DaemonStartOptions {
2
+ import type { CommandOptions } from "../output/index.js";
3
+ interface OnboardOptions extends CommandOptions {
4
+ port?: string;
5
+ listen?: string;
6
+ relay?: boolean;
7
+ mcp?: boolean;
8
+ hostnames?: string;
4
9
  timeout?: string;
5
10
  voice?: "ask" | "enable" | "disable";
6
11
  }
@@ -1,39 +1,19 @@
1
- import { cancel, confirm, intro, isCancel, log, note, outro, spinner } from "@clack/prompts";
1
+ import { addLocalDaemonOptions } from "../utils/command-options.js";
2
+ import { cancel, confirm, intro, isCancel, log, note, outro } from "@clack/prompts";
2
3
  import { Command, Option } from "commander";
3
- import { writeFileSync } from "node:fs";
4
4
  import path from "node:path";
5
- import { loadPersistedConfig } from "@camerontaylor/paseo-server";
6
- import { resolveLocalPaseoHome, resolveLocalDaemonState, resolveTcpHostFromListen, startLocalDaemonDetached, tailDaemonLog, } from "./daemon/local-daemon.js";
7
- import { tryConnectToDaemon } from "../utils/client.js";
5
+ import { readPersistedConfig as loadPersistedConfig, savePersistedConfig, readDaemonInstance, waitForDaemonReady, } from "@camerontaylor/paseo-server";
6
+ import { withGlobalOptions } from "../utils/command-options.js";
7
+ import { launchLocalDaemon, parseTimeoutMs } from "./daemon/local-daemon.js";
8
+ import { connectToDaemon } from "../utils/client.js";
8
9
  import { formatPairingInstructions } from "../output/pairing.js";
9
10
  import { confirmRelayPairing, printDirectConnectionGuidance, resolveLocalPairingOffer, } from "./daemon/pair.js";
10
- const DEFAULT_READY_TIMEOUT_MS = 10 * 60 * 1000;
11
- const READY_PROBE_TIMEOUT_MS = 1200;
12
11
  class OnboardCancelledError extends Error {
13
12
  }
14
13
  const plainNoteFormat = (line) => line;
15
14
  function renderNote(message, title) {
16
15
  note(message, title, { format: plainNoteFormat });
17
16
  }
18
- function sleep(ms) {
19
- return new Promise((resolve) => {
20
- setTimeout(resolve, ms);
21
- });
22
- }
23
- function parseTimeoutMs(raw) {
24
- if (!raw || raw.trim().length === 0) {
25
- return DEFAULT_READY_TIMEOUT_MS;
26
- }
27
- const seconds = Number(raw);
28
- if (!Number.isFinite(seconds) || seconds <= 0) {
29
- throw new Error(`Invalid timeout value: ${raw}`);
30
- }
31
- return Math.ceil(seconds * 1000);
32
- }
33
- function savePersistedConfig(paseoHome, config) {
34
- const configPath = path.join(paseoHome, "config.json");
35
- writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
36
- }
37
17
  function applyVoiceSelection(config, enabled) {
38
18
  return {
39
19
  ...config,
@@ -83,101 +63,6 @@ async function resolveVoiceSelection(mode) {
83
63
  }
84
64
  return answer;
85
65
  }
86
- function parseDownloadProgress(logTail) {
87
- const lines = logTail.split("\n").filter(Boolean);
88
- for (let index = lines.length - 1; index >= 0; index -= 1) {
89
- const line = lines[index];
90
- if (!line || !line.includes("Downloading model artifact")) {
91
- continue;
92
- }
93
- const pctMatch = line.match(/"pct"\s*:\s*(\d{1,3})|\bpct[=:]\s*(\d{1,3})/);
94
- const modelMatch = line.match(/"modelId"\s*:\s*"([^"]+)"|\bmodelId[=:]\s*"?([^\s",}]+)/);
95
- return {
96
- modelId: modelMatch?.[1] ?? modelMatch?.[2] ?? null,
97
- pct: pctMatch ? Number(pctMatch[1] ?? pctMatch[2]) : null,
98
- };
99
- }
100
- return null;
101
- }
102
- function renderProgressLine(progress) {
103
- const modelSuffix = progress.modelId ? ` (${progress.modelId})` : "";
104
- if (progress.pct === null) {
105
- return `Downloading speech model${modelSuffix}...`;
106
- }
107
- return `Downloading speech model${modelSuffix}: ${progress.pct}%`;
108
- }
109
- async function probeDaemonReady(home, timeoutMs) {
110
- const state = resolveLocalDaemonState({ home });
111
- const host = resolveTcpHostFromListen(state.listen);
112
- const deadline = Date.now() + timeoutMs;
113
- const remainingTimeoutMs = () => Math.max(1, deadline - Date.now());
114
- if (state.running && host) {
115
- const client = await tryConnectToDaemon({
116
- host,
117
- timeout: Math.min(remainingTimeoutMs(), READY_PROBE_TIMEOUT_MS),
118
- });
119
- if (client) {
120
- try {
121
- await client.fetchAgents({
122
- timeout: Math.min(remainingTimeoutMs(), READY_PROBE_TIMEOUT_MS),
123
- });
124
- return { kind: "ready", listen: state.listen, host };
125
- }
126
- catch {
127
- // Daemon process is alive but not API-ready yet.
128
- }
129
- finally {
130
- await client.close().catch(() => { });
131
- }
132
- }
133
- }
134
- else if (state.running && !host) {
135
- return { kind: "ready", listen: state.listen, host: null };
136
- }
137
- return { kind: "pending" };
138
- }
139
- function announceProgress(home, state, onStatus) {
140
- const progress = parseDownloadProgress(tailDaemonLog(home, 120) ?? "");
141
- const progressLine = progress ? renderProgressLine(progress) : null;
142
- const statusMessage = progressLine ?? "Waiting for daemon to become ready...";
143
- if (statusMessage !== state.lastStatus) {
144
- onStatus?.(statusMessage);
145
- return { lastStatus: statusMessage, lastPrintedAt: Date.now() };
146
- }
147
- if (!onStatus && Date.now() - state.lastPrintedAt >= 3000) {
148
- console.log(statusMessage);
149
- return { lastStatus: state.lastStatus, lastPrintedAt: Date.now() };
150
- }
151
- return state;
152
- }
153
- async function waitForDaemonReady(args) {
154
- const deadline = Date.now() + args.timeoutMs;
155
- const createTimeoutError = () => {
156
- const recentLogs = tailDaemonLog(args.home, 60);
157
- return new Error([
158
- `Timed out after ${Math.ceil(args.timeoutMs / 1000)}s waiting for daemon readiness.`,
159
- recentLogs ? `Recent daemon logs:\n${recentLogs}` : null,
160
- ]
161
- .filter(Boolean)
162
- .join("\n\n"));
163
- };
164
- async function poll(state) {
165
- if (Date.now() >= deadline) {
166
- throw createTimeoutError();
167
- }
168
- const probe = await probeDaemonReady(args.home, Math.max(1, deadline - Date.now()));
169
- if (probe.kind === "ready") {
170
- return { listen: probe.listen, host: probe.host };
171
- }
172
- const nextState = announceProgress(args.home, state, args.onStatus);
173
- if (Date.now() >= deadline) {
174
- throw createTimeoutError();
175
- }
176
- await sleep(200);
177
- return poll(nextState);
178
- }
179
- return poll({ lastStatus: "", lastPrintedAt: 0 });
180
- }
181
66
  function printNextSteps(pairingUrl, paseoHome, richUi) {
182
67
  const daemonLogPath = path.join(paseoHome, "daemon.log");
183
68
  const nextStepsLines = [
@@ -187,13 +72,13 @@ function printNextSteps(pairingUrl, paseoHome, richUi) {
187
72
  "2. Web app: https://app.paseo.sh",
188
73
  "3. Desktop app: https://github.com/getpaseo/paseo/releases/latest",
189
74
  "4. Docs: https://paseo.sh/docs",
190
- '5. Example: paseo run --output-schema schema.json "extract fields"',
75
+ `5. Example: paseo run --home ${JSON.stringify(paseoHome)} --output-schema schema.json "extract fields"`,
191
76
  ];
192
77
  const quickReferenceLines = [
193
78
  "1. paseo --help",
194
- "2. paseo ls",
195
- '3. paseo run "your prompt"',
196
- "4. paseo status",
79
+ `2. paseo ls --home ${JSON.stringify(paseoHome)}`,
80
+ `3. paseo run --home ${JSON.stringify(paseoHome)} "your prompt"`,
81
+ `4. paseo status --home ${JSON.stringify(paseoHome)}`,
197
82
  `5. Daemon logs: ${daemonLogPath}`,
198
83
  ];
199
84
  if (!richUi) {
@@ -213,11 +98,10 @@ function printNextSteps(pairingUrl, paseoHome, richUi) {
213
98
  renderNote(quickReferenceLines.join("\n"), "CLI quick reference");
214
99
  }
215
100
  export function onboardCommand() {
216
- return new Command("onboard")
101
+ return addLocalDaemonOptions(new Command("onboard"))
217
102
  .description("Run first-time setup, start daemon, and print pairing instructions")
218
103
  .option("--listen <listen>", "Listen target (host:port, port, or unix socket path)")
219
104
  .option("--port <port>", "Port to listen on (default: 6767)")
220
- .option("--home <path>", "Paseo home directory (default: ~/.paseo)")
221
105
  .option("--relay", "Enable relay connection without prompting")
222
106
  .option("--no-relay", "Disable relay connection")
223
107
  .option("--no-mcp", "Disable the Agent MCP HTTP endpoint")
@@ -225,12 +109,13 @@ export function onboardCommand() {
225
109
  .addOption(new Option("--allowed-hosts <hosts>").hideHelp())
226
110
  .option("--timeout <seconds>", "Max time to wait for daemon readiness (default: 600)")
227
111
  .option("--voice <mode>", "Voice setup mode: ask, enable, disable", "ask")
228
- .action(async (options) => {
112
+ .action(withGlobalOptions(async (options, command) => {
229
113
  await runOnboard({
230
114
  ...options,
115
+ mcp: command.getOptionValueSource("mcp") === "cli" ? options.mcp : undefined,
231
116
  hostnames: options.hostnames ?? options.allowedHosts,
232
117
  });
233
- });
118
+ }));
234
119
  }
235
120
  async function resolveAndPersistVoice(paseoHome, options) {
236
121
  let persisted = loadPersistedConfig(paseoHome);
@@ -257,72 +142,30 @@ async function resolveAndPersistVoice(paseoHome, options) {
257
142
  savePersistedConfig(paseoHome, persisted);
258
143
  return voiceEnabled;
259
144
  }
260
- async function ensureDaemonStarted(options, richUi) {
261
- const stateBeforeStart = resolveLocalDaemonState({ home: options.home });
262
- if (stateBeforeStart.running) {
263
- log.message(`Daemon already running (PID ${stateBeforeStart.pidInfo?.pid ?? "unknown"}).`);
264
- return;
265
- }
266
- const startSpinner = richUi ? spinner() : null;
267
- try {
268
- if (startSpinner) {
269
- startSpinner.start("Starting daemon...");
270
- }
271
- else {
272
- log.message("Starting daemon...");
273
- }
274
- const startup = await startLocalDaemonDetached(options);
275
- if (startSpinner) {
276
- startSpinner.stop(`Daemon started (PID ${startup.pid ?? "unknown"})`);
277
- }
278
- else {
279
- log.message(`Daemon started (PID ${startup.pid ?? "unknown"})`);
280
- }
281
- log.message(`Logs: ${startup.logPath}`);
282
- }
283
- catch (error) {
284
- const message = error instanceof Error ? error.message : String(error);
285
- if (startSpinner) {
286
- startSpinner.error(message);
287
- }
288
- else {
289
- log.error(message);
290
- }
291
- process.exit(1);
292
- }
293
- }
294
- async function waitForDaemonReadyWithUi(args) {
295
- const readySpinner = args.richUi ? spinner() : null;
296
- try {
297
- if (readySpinner) {
298
- readySpinner.start("Waiting for daemon to become ready...");
299
- }
300
- else {
301
- log.message("Waiting for daemon to become ready...");
302
- }
303
- const readyState = await waitForDaemonReady({
304
- home: args.home,
305
- timeoutMs: args.timeoutMs,
306
- onStatus: readySpinner ? (message) => readySpinner.message(message) : undefined,
307
- });
308
- if (readySpinner) {
309
- readySpinner.stop(`Daemon ready on ${readyState.listen}`);
310
- }
311
- else {
312
- log.message(`Daemon ready on ${readyState.listen}`);
313
- }
314
- return readyState;
315
- }
316
- catch (error) {
317
- const message = error instanceof Error ? error.message : String(error);
318
- if (readySpinner) {
319
- readySpinner.error(message);
320
- }
321
- else {
322
- log.error(message);
323
- }
324
- return process.exit(1);
145
+ function persistSetupChoices(paseoHome, options) {
146
+ const persisted = loadPersistedConfig(paseoHome, { defaultsIfMissing: true });
147
+ if (options.listen || options.port) {
148
+ persisted.daemon = {
149
+ ...persisted.daemon,
150
+ listen: options.listen ?? `127.0.0.1:${options.port}`,
151
+ };
325
152
  }
153
+ if (options.relay !== undefined)
154
+ persisted.daemon = {
155
+ ...persisted.daemon,
156
+ relay: { ...persisted.daemon?.relay, enabled: options.relay },
157
+ };
158
+ if (options.mcp !== undefined)
159
+ persisted.daemon = {
160
+ ...persisted.daemon,
161
+ mcp: { ...persisted.daemon?.mcp, enabled: options.mcp },
162
+ };
163
+ if (options.hostnames)
164
+ persisted.daemon = {
165
+ ...persisted.daemon,
166
+ hostnames: options.hostnames === "true" ? true : options.hostnames.split(","),
167
+ };
168
+ savePersistedConfig(paseoHome, persisted);
326
169
  }
327
170
  export async function runOnboard(options) {
328
171
  const richUi = process.stdin.isTTY && process.stdout.isTTY;
@@ -333,16 +176,12 @@ export async function runOnboard(options) {
333
176
  cancel("Cannot use --listen and --port together");
334
177
  process.exit(1);
335
178
  }
336
- let timeoutMs = DEFAULT_READY_TIMEOUT_MS;
337
- try {
338
- timeoutMs = parseTimeoutMs(options.timeout);
339
- }
340
- catch (error) {
341
- const message = error instanceof Error ? error.message : String(error);
342
- cancel(message);
343
- process.exit(1);
344
- }
345
- const paseoHome = resolveLocalPaseoHome(options.home);
179
+ const timeoutMs = parseTimeoutMs(options.timeout);
180
+ if (options.daemonTarget.kind !== "instance")
181
+ throw new Error("Onboarding requires a local home");
182
+ const paseoHome = options.daemonTarget.home;
183
+ const alreadyRunning = await readDaemonInstance(paseoHome);
184
+ persistSetupChoices(paseoHome, options);
346
185
  if (richUi) {
347
186
  renderNote(paseoHome, "Paseo home");
348
187
  }
@@ -350,12 +189,21 @@ export async function runOnboard(options) {
350
189
  log.message(voiceEnabled
351
190
  ? "Voice features enabled. Local speech models will be downloaded automatically if missing."
352
191
  : "Voice features disabled. Local speech models will not be downloaded.");
353
- await ensureDaemonStarted(options, richUi);
354
- await waitForDaemonReadyWithUi({
355
- home: options.home ?? paseoHome,
356
- timeoutMs,
357
- richUi,
358
- });
192
+ if (alreadyRunning) {
193
+ log.message(`Daemon already running (PID ${alreadyRunning.pid}); retaining its supervisor.`);
194
+ const client = await connectToDaemon({ target: options.daemonTarget, timeout: timeoutMs });
195
+ try {
196
+ log.message(JSON.stringify(await client.reloadDaemonConfig()));
197
+ }
198
+ finally {
199
+ await client.close();
200
+ }
201
+ }
202
+ else {
203
+ await launchLocalDaemon({ home: paseoHome, timeoutMs });
204
+ }
205
+ const ready = await waitForDaemonReady(paseoHome, { timeoutMs });
206
+ log.message(`Daemon ready on ${ready.listen}`);
359
207
  if (options.relay === false) {
360
208
  log.message("Relay pairing skipped because --no-relay was provided.");
361
209
  printNextSteps(null, paseoHome, richUi);
@@ -78,6 +78,8 @@ export async function openDesktopWithProject(projectPath) {
78
78
  launchDesktop([projectPath]);
79
79
  }
80
80
  catch (error) {
81
+ if (error && typeof error === "object" && "code" in error)
82
+ throw error;
81
83
  const message = error instanceof Error ? error.message : String(error);
82
84
  process.stderr.write(`${message}\n`);
83
85
  process.exitCode = 1;