@camerontaylor/paseo-cli 0.8.0-fork.1 → 0.9.0-beta.2.fork.1

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 (125) hide show
  1. package/dist/cli.js +44 -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 +13 -13
  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 +12 -17
  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 +103 -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 +33 -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/runtime-toolchain.js +1 -1
  32. package/dist/commands/daemon/set-password.js +8 -7
  33. package/dist/commands/daemon/start.d.ts +17 -3
  34. package/dist/commands/daemon/start.js +47 -52
  35. package/dist/commands/daemon/status.d.ts +12 -20
  36. package/dist/commands/daemon/status.js +91 -342
  37. package/dist/commands/daemon/stop.d.ts +19 -14
  38. package/dist/commands/daemon/stop.js +53 -55
  39. package/dist/commands/heartbeat/index.js +3 -3
  40. package/dist/commands/hub/connect.d.ts +1 -0
  41. package/dist/commands/hub/connect.js +1 -1
  42. package/dist/commands/hub/daemon-client.d.ts +3 -2
  43. package/dist/commands/hub/daemon-client.js +3 -3
  44. package/dist/commands/hub/disconnect.d.ts +1 -0
  45. package/dist/commands/hub/disconnect.js +1 -1
  46. package/dist/commands/hub/index.js +2 -2
  47. package/dist/commands/hub/init.d.ts +3 -1
  48. package/dist/commands/hub/init.js +13 -12
  49. package/dist/commands/hub/login.d.ts +4 -4
  50. package/dist/commands/hub/login.js +1 -1
  51. package/dist/commands/hub/logout.d.ts +1 -0
  52. package/dist/commands/hub/logout.js +2 -2
  53. package/dist/commands/hub/permissions.d.ts +1 -0
  54. package/dist/commands/hub/permissions.js +2 -2
  55. package/dist/commands/onboard.d.ts +7 -2
  56. package/dist/commands/onboard.js +59 -210
  57. package/dist/commands/open.js +3 -1
  58. package/dist/commands/permit/allow.js +2 -15
  59. package/dist/commands/permit/deny.js +2 -15
  60. package/dist/commands/permit/ls.js +2 -15
  61. package/dist/commands/plugin/index.d.ts +6 -0
  62. package/dist/commands/plugin/index.js +62 -46
  63. package/dist/commands/plugin/scaffold.js +10 -0
  64. package/dist/commands/plugin/shared.d.ts +5 -3
  65. package/dist/commands/plugin/shared.js +13 -9
  66. package/dist/commands/plugin/update.d.ts +37 -0
  67. package/dist/commands/plugin/update.js +58 -0
  68. package/dist/commands/project/create.js +5 -3
  69. package/dist/commands/project/delete.js +4 -2
  70. package/dist/commands/project/ls.js +2 -2
  71. package/dist/commands/project/rename.js +4 -2
  72. package/dist/commands/provider/add.js +2 -2
  73. package/dist/commands/provider/diagnostic.js +1 -1
  74. package/dist/commands/provider/ls.js +2 -30
  75. package/dist/commands/provider/models.js +1 -1
  76. package/dist/commands/provider/rm.js +2 -2
  77. package/dist/commands/schedule/create.js +2 -2
  78. package/dist/commands/schedule/delete.js +1 -1
  79. package/dist/commands/schedule/inspect.js +1 -1
  80. package/dist/commands/schedule/logs.js +1 -1
  81. package/dist/commands/schedule/ls.js +1 -1
  82. package/dist/commands/schedule/pause.js +1 -1
  83. package/dist/commands/schedule/resume.js +1 -1
  84. package/dist/commands/schedule/run-once.js +1 -1
  85. package/dist/commands/schedule/shared.d.ts +3 -1
  86. package/dist/commands/schedule/shared.js +7 -5
  87. package/dist/commands/schedule/update.js +1 -1
  88. package/dist/commands/script/ls.js +1 -1
  89. package/dist/commands/script/shared.d.ts +2 -1
  90. package/dist/commands/script/shared.js +3 -3
  91. package/dist/commands/script/start.js +1 -1
  92. package/dist/commands/script/stop.js +1 -1
  93. package/dist/commands/terminal/capture.d.ts +1 -1
  94. package/dist/commands/terminal/capture.js +2 -3
  95. package/dist/commands/terminal/create.js +1 -1
  96. package/dist/commands/terminal/kill.js +1 -1
  97. package/dist/commands/terminal/ls.js +1 -1
  98. package/dist/commands/terminal/send-keys.d.ts +1 -1
  99. package/dist/commands/terminal/send-keys.js +2 -3
  100. package/dist/commands/terminal/shared.d.ts +2 -1
  101. package/dist/commands/terminal/shared.js +5 -3
  102. package/dist/commands/workspace/archive.d.ts +1 -0
  103. package/dist/commands/workspace/archive.js +4 -2
  104. package/dist/commands/workspace/create.js +4 -2
  105. package/dist/commands/workspace/ls.d.ts +1 -0
  106. package/dist/commands/workspace/ls.js +2 -2
  107. package/dist/commands/workspace/rename.d.ts +1 -0
  108. package/dist/commands/workspace/rename.js +4 -2
  109. package/dist/commands/workspace/setup.d.ts +1 -0
  110. package/dist/commands/workspace/setup.js +2 -2
  111. package/dist/commands/worktree/archive.js +4 -2
  112. package/dist/commands/worktree/create.js +2 -10
  113. package/dist/commands/worktree/ls.d.ts +0 -2
  114. package/dist/commands/worktree/ls.js +4 -30
  115. package/dist/output/with-output.d.ts +2 -0
  116. package/dist/run.js +11 -1
  117. package/dist/utils/client-id.d.ts +1 -1
  118. package/dist/utils/client-id.js +8 -14
  119. package/dist/utils/client.d.ts +16 -21
  120. package/dist/utils/client.js +57 -109
  121. package/dist/utils/command-options.d.ts +1 -0
  122. package/dist/utils/command-options.js +26 -2
  123. package/dist/utils/daemon-target.d.ts +13 -0
  124. package/dist/utils/daemon-target.js +51 -0
  125. 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,20 @@
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, } from "@camerontaylor/paseo-server/configuration";
6
+ import { readDaemonInstance, waitForDaemonReady } from "@camerontaylor/paseo-server/daemon-control";
7
+ import { withGlobalOptions } from "../utils/command-options.js";
8
+ import { launchLocalDaemon, parseTimeoutMs } from "./daemon/local-daemon.js";
9
+ import { connectToDaemon } from "../utils/client.js";
8
10
  import { formatPairingInstructions } from "../output/pairing.js";
9
11
  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
12
  class OnboardCancelledError extends Error {
13
13
  }
14
14
  const plainNoteFormat = (line) => line;
15
15
  function renderNote(message, title) {
16
16
  note(message, title, { format: plainNoteFormat });
17
17
  }
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
18
  function applyVoiceSelection(config, enabled) {
38
19
  return {
39
20
  ...config,
@@ -83,101 +64,6 @@ async function resolveVoiceSelection(mode) {
83
64
  }
84
65
  return answer;
85
66
  }
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
67
  function printNextSteps(pairingUrl, paseoHome, richUi) {
182
68
  const daemonLogPath = path.join(paseoHome, "daemon.log");
183
69
  const nextStepsLines = [
@@ -187,13 +73,13 @@ function printNextSteps(pairingUrl, paseoHome, richUi) {
187
73
  "2. Web app: https://app.paseo.sh",
188
74
  "3. Desktop app: https://github.com/getpaseo/paseo/releases/latest",
189
75
  "4. Docs: https://paseo.sh/docs",
190
- '5. Example: paseo run --output-schema schema.json "extract fields"',
76
+ `5. Example: paseo run --home ${JSON.stringify(paseoHome)} --output-schema schema.json "extract fields"`,
191
77
  ];
192
78
  const quickReferenceLines = [
193
79
  "1. paseo --help",
194
- "2. paseo ls",
195
- '3. paseo run "your prompt"',
196
- "4. paseo status",
80
+ `2. paseo ls --home ${JSON.stringify(paseoHome)}`,
81
+ `3. paseo run --home ${JSON.stringify(paseoHome)} "your prompt"`,
82
+ `4. paseo status --home ${JSON.stringify(paseoHome)}`,
197
83
  `5. Daemon logs: ${daemonLogPath}`,
198
84
  ];
199
85
  if (!richUi) {
@@ -213,11 +99,10 @@ function printNextSteps(pairingUrl, paseoHome, richUi) {
213
99
  renderNote(quickReferenceLines.join("\n"), "CLI quick reference");
214
100
  }
215
101
  export function onboardCommand() {
216
- return new Command("onboard")
102
+ return addLocalDaemonOptions(new Command("onboard"))
217
103
  .description("Run first-time setup, start daemon, and print pairing instructions")
218
104
  .option("--listen <listen>", "Listen target (host:port, port, or unix socket path)")
219
105
  .option("--port <port>", "Port to listen on (default: 6767)")
220
- .option("--home <path>", "Paseo home directory (default: ~/.paseo)")
221
106
  .option("--relay", "Enable relay connection without prompting")
222
107
  .option("--no-relay", "Disable relay connection")
223
108
  .option("--no-mcp", "Disable the Agent MCP HTTP endpoint")
@@ -225,12 +110,13 @@ export function onboardCommand() {
225
110
  .addOption(new Option("--allowed-hosts <hosts>").hideHelp())
226
111
  .option("--timeout <seconds>", "Max time to wait for daemon readiness (default: 600)")
227
112
  .option("--voice <mode>", "Voice setup mode: ask, enable, disable", "ask")
228
- .action(async (options) => {
113
+ .action(withGlobalOptions(async (options, command) => {
229
114
  await runOnboard({
230
115
  ...options,
116
+ mcp: command.getOptionValueSource("mcp") === "cli" ? options.mcp : undefined,
231
117
  hostnames: options.hostnames ?? options.allowedHosts,
232
118
  });
233
- });
119
+ }));
234
120
  }
235
121
  async function resolveAndPersistVoice(paseoHome, options) {
236
122
  let persisted = loadPersistedConfig(paseoHome);
@@ -257,72 +143,30 @@ async function resolveAndPersistVoice(paseoHome, options) {
257
143
  savePersistedConfig(paseoHome, persisted);
258
144
  return voiceEnabled;
259
145
  }
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);
146
+ function persistSetupChoices(paseoHome, options) {
147
+ const persisted = loadPersistedConfig(paseoHome, { defaultsIfMissing: true });
148
+ if (options.listen || options.port) {
149
+ persisted.daemon = {
150
+ ...persisted.daemon,
151
+ listen: options.listen ?? `127.0.0.1:${options.port}`,
152
+ };
325
153
  }
154
+ if (options.relay !== undefined)
155
+ persisted.daemon = {
156
+ ...persisted.daemon,
157
+ relay: { ...persisted.daemon?.relay, enabled: options.relay },
158
+ };
159
+ if (options.mcp !== undefined)
160
+ persisted.daemon = {
161
+ ...persisted.daemon,
162
+ mcp: { ...persisted.daemon?.mcp, enabled: options.mcp },
163
+ };
164
+ if (options.hostnames)
165
+ persisted.daemon = {
166
+ ...persisted.daemon,
167
+ hostnames: options.hostnames === "true" ? true : options.hostnames.split(","),
168
+ };
169
+ savePersistedConfig(paseoHome, persisted);
326
170
  }
327
171
  export async function runOnboard(options) {
328
172
  const richUi = process.stdin.isTTY && process.stdout.isTTY;
@@ -333,16 +177,12 @@ export async function runOnboard(options) {
333
177
  cancel("Cannot use --listen and --port together");
334
178
  process.exit(1);
335
179
  }
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);
180
+ const timeoutMs = parseTimeoutMs(options.timeout);
181
+ if (options.daemonTarget.kind !== "instance")
182
+ throw new Error("Onboarding requires a local home");
183
+ const paseoHome = options.daemonTarget.home;
184
+ const alreadyRunning = await readDaemonInstance(paseoHome);
185
+ persistSetupChoices(paseoHome, options);
346
186
  if (richUi) {
347
187
  renderNote(paseoHome, "Paseo home");
348
188
  }
@@ -350,12 +190,21 @@ export async function runOnboard(options) {
350
190
  log.message(voiceEnabled
351
191
  ? "Voice features enabled. Local speech models will be downloaded automatically if missing."
352
192
  : "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
- });
193
+ if (alreadyRunning) {
194
+ log.message(`Daemon already running (PID ${alreadyRunning.pid}); retaining its supervisor.`);
195
+ const client = await connectToDaemon({ target: options.daemonTarget, timeout: timeoutMs });
196
+ try {
197
+ log.message(JSON.stringify(await client.reloadDaemonConfig()));
198
+ }
199
+ finally {
200
+ await client.close();
201
+ }
202
+ }
203
+ else {
204
+ await launchLocalDaemon({ home: paseoHome, timeoutMs });
205
+ }
206
+ const ready = await waitForDaemonReady(paseoHome, { timeoutMs });
207
+ log.message(`Daemon ready on ${ready.listen}`);
359
208
  if (options.relay === false) {
360
209
  log.message("Relay pairing skipped because --no-relay was provided.");
361
210
  printNextSteps(null, paseoHome, richUi);
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import path from "node:path";
4
- import { spawnProcess } from "@camerontaylor/paseo-server";
4
+ import { spawnProcess } from "@camerontaylor/paseo-server/process";
5
5
  import { buildAgentDeepLink } from "@camerontaylor/paseo-protocol/agent-deep-link";
6
6
  function findDesktopApp() {
7
7
  if (process.platform === "darwin") {
@@ -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;