@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
@@ -3,5 +3,5 @@ import { type TerminalCommandOptions } from "./shared.js";
3
3
  export interface TerminalSendKeysOptions extends TerminalCommandOptions {
4
4
  literal?: boolean;
5
5
  }
6
- export declare function runSendKeysCommand(terminalId: string, keys: string[], _options: TerminalSendKeysOptions, command: Command): Promise<void>;
6
+ export declare function runSendKeysCommand(terminalId: string, keys: string[], options: TerminalSendKeysOptions, _command: Command): Promise<void>;
7
7
  //# sourceMappingURL=send-keys.d.ts.map
@@ -1,7 +1,6 @@
1
1
  import { renderError, toCommandError } from "../../output/render.js";
2
2
  import { connectTerminalClient, resolveTerminalId, toTerminalCommandError, } from "./shared.js";
3
- export async function runSendKeysCommand(terminalId, keys, _options, command) {
4
- const options = command.optsWithGlobals();
3
+ export async function runSendKeysCommand(terminalId, keys, options, _command) {
5
4
  try {
6
5
  const payload = await executeSendKeysCommand(terminalId, keys, options);
7
6
  if (options.json) {
@@ -18,7 +17,7 @@ export async function runSendKeysCommand(terminalId, keys, _options, command) {
18
17
  }
19
18
  }
20
19
  async function executeSendKeysCommand(terminalId, keys, options) {
21
- const { client, close } = await connectTerminalClient(options.host);
20
+ const { client, close } = await connectTerminalClient(options.daemonTarget);
22
21
  try {
23
22
  const resolvedId = await resolveTerminalId(client, terminalId);
24
23
  if (!resolvedId) {
@@ -1,8 +1,9 @@
1
+ import type { DaemonTarget } from "../../utils/daemon-target.js";
1
2
  import type { CommandError, CommandOptions } from "../../output/index.js";
2
3
  export interface TerminalCommandOptions extends CommandOptions {
3
4
  host?: string;
4
5
  }
5
- export declare function connectTerminalClient(host?: string): Promise<{
6
+ export declare function connectTerminalClient(target: DaemonTarget): Promise<{
6
7
  client: import("@camerontaylor/paseo-client").PaseoApi;
7
8
  daemonHost: string;
8
9
  close: () => Promise<void>;
@@ -1,12 +1,14 @@
1
1
  import { createPaseoApi } from "@camerontaylor/paseo-client";
2
2
  import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
3
- export async function connectTerminalClient(host) {
4
- const daemonHost = getDaemonHost({ host });
3
+ export async function connectTerminalClient(target) {
4
+ const daemonHost = getDaemonHost({ target });
5
5
  try {
6
- const client = await connectToDaemon({ host });
6
+ const client = await connectToDaemon({ target });
7
7
  return { client: createPaseoApi(client), daemonHost, close: () => client.close() };
8
8
  }
9
9
  catch (err) {
10
+ if (err && typeof err === "object" && "code" in err)
11
+ throw err;
10
12
  const message = err instanceof Error ? err.message : String(err);
11
13
  const error = {
12
14
  code: "DAEMON_NOT_RUNNING",
@@ -7,6 +7,7 @@ interface WorkspaceArchiveResult {
7
7
  }
8
8
  export declare function runArchiveCommand(workspaceId: string, options: {
9
9
  host?: string;
10
+ daemonTarget: import("../../utils/daemon-target.js").DaemonTarget;
10
11
  }, _command: Command): Promise<SingleResult<WorkspaceArchiveResult>>;
11
12
  export {};
12
13
  //# sourceMappingURL=archive.d.ts.map
@@ -8,8 +8,8 @@ const workspaceArchiveSchema = {
8
8
  ],
9
9
  };
10
10
  export async function runArchiveCommand(workspaceId, options, _command) {
11
- const host = getDaemonHost({ host: options.host });
12
- const client = await connectToDaemon({ host: options.host }).catch((error) => {
11
+ const host = getDaemonHost({ target: options.daemonTarget });
12
+ const client = await connectToDaemon({ target: options.daemonTarget }).catch((error) => {
13
13
  const message = error instanceof Error ? error.message : String(error);
14
14
  throw {
15
15
  code: "DAEMON_NOT_RUNNING",
@@ -31,6 +31,8 @@ export async function runArchiveCommand(workspaceId, options, _command) {
31
31
  };
32
32
  }
33
33
  catch (error) {
34
+ if (error && typeof error === "object" && "code" in error)
35
+ throw error;
34
36
  const message = error instanceof Error ? error.message : String(error);
35
37
  throw { code: "WORKSPACE_ARCHIVE_FAILED", message };
36
38
  }
@@ -85,8 +85,8 @@ export function buildWorkspaceSource(options) {
85
85
  throw new Error(`Unsupported workspace isolation: ${String(options.isolation)}`);
86
86
  }
87
87
  export async function runCreateCommand(options, _command) {
88
- const host = getDaemonHost({ host: options.host });
89
- const client = await connectToDaemon({ host: options.host }).catch((error) => {
88
+ const host = getDaemonHost({ target: options.daemonTarget });
89
+ const client = await connectToDaemon({ target: options.daemonTarget }).catch((error) => {
90
90
  const message = error instanceof Error ? error.message : String(error);
91
91
  throw {
92
92
  code: "DAEMON_NOT_RUNNING",
@@ -104,6 +104,8 @@ export async function runCreateCommand(options, _command) {
104
104
  return { type: "single", data: toWorkspaceRow(payload.workspace), schema: workspaceSchema };
105
105
  }
106
106
  catch (error) {
107
+ if (error && typeof error === "object" && "code" in error)
108
+ throw error;
107
109
  const message = error instanceof Error ? error.message : String(error);
108
110
  throw { code: "WORKSPACE_CREATE_FAILED", message };
109
111
  }
@@ -3,5 +3,6 @@ import type { ListResult } from "../../output/index.js";
3
3
  import { type WorkspaceRow } from "./shared.js";
4
4
  export declare function runLsCommand(options: {
5
5
  host?: string;
6
+ daemonTarget: import("../../utils/daemon-target.js").DaemonTarget;
6
7
  }, _command: Command): Promise<ListResult<WorkspaceRow>>;
7
8
  //# sourceMappingURL=ls.d.ts.map
@@ -1,8 +1,8 @@
1
1
  import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
2
2
  import { toWorkspaceRow, workspaceSchema } from "./shared.js";
3
3
  export async function runLsCommand(options, _command) {
4
- const host = getDaemonHost({ host: options.host });
5
- const client = await connectToDaemon({ host: options.host }).catch((error) => {
4
+ const host = getDaemonHost({ target: options.daemonTarget });
5
+ const client = await connectToDaemon({ target: options.daemonTarget }).catch((error) => {
6
6
  const message = error instanceof Error ? error.message : String(error);
7
7
  throw {
8
8
  code: "DAEMON_NOT_RUNNING",
@@ -7,6 +7,7 @@ interface WorkspaceRenameResult {
7
7
  }
8
8
  export interface WorkspaceRenameOptions {
9
9
  host?: string;
10
+ daemonTarget: import("../../utils/daemon-target.js").DaemonTarget;
10
11
  reset?: boolean;
11
12
  }
12
13
  /**
@@ -33,8 +33,8 @@ export function resolveWorkspaceTitle(input) {
33
33
  }
34
34
  export async function runRenameCommand(workspaceId, titleArg, options, _command) {
35
35
  const title = resolveWorkspaceTitle({ title: titleArg, reset: options.reset });
36
- const host = getDaemonHost({ host: options.host });
37
- const client = await connectToDaemon({ host: options.host }).catch((error) => {
36
+ const host = getDaemonHost({ target: options.daemonTarget });
37
+ const client = await connectToDaemon({ target: options.daemonTarget }).catch((error) => {
38
38
  const message = error instanceof Error ? error.message : String(error);
39
39
  throw {
40
40
  code: "DAEMON_NOT_RUNNING",
@@ -51,6 +51,8 @@ export async function runRenameCommand(workspaceId, titleArg, options, _command)
51
51
  };
52
52
  }
53
53
  catch (error) {
54
+ if (error && typeof error === "object" && "code" in error)
55
+ throw error;
54
56
  const message = error instanceof Error ? error.message : String(error);
55
57
  throw { code: "WORKSPACE_RENAME_FAILED", message };
56
58
  }
@@ -6,6 +6,7 @@ interface WorkspaceSetupResult {
6
6
  }
7
7
  export declare function runSetupCommand(workspaceId: string, options: {
8
8
  host?: string;
9
+ daemonTarget: import("../../utils/daemon-target.js").DaemonTarget;
9
10
  }, _command: Command): Promise<SingleResult<WorkspaceSetupResult>>;
10
11
  export {};
11
12
  //# sourceMappingURL=setup.d.ts.map
@@ -7,8 +7,8 @@ const workspaceSetupSchema = {
7
7
  ],
8
8
  };
9
9
  export async function runSetupCommand(workspaceId, options, _command) {
10
- const host = getDaemonHost({ host: options.host });
11
- const client = await connectToDaemon({ host: options.host }).catch((error) => {
10
+ const host = getDaemonHost({ target: options.daemonTarget });
11
+ const client = await connectToDaemon({ target: options.daemonTarget }).catch((error) => {
12
12
  throw {
13
13
  code: "DAEMON_NOT_RUNNING",
14
14
  message: `Cannot connect to daemon at ${host}: ${error instanceof Error ? error.message : String(error)}`,
@@ -16,7 +16,7 @@ export async function runArchiveCommand(nameArg, options, _command) {
16
16
  return runArchiveCommandWithDeps(nameArg, options, { connectToDaemon });
17
17
  }
18
18
  export async function runArchiveCommandWithDeps(nameArg, options, deps) {
19
- const host = getDaemonHost({ host: options.host });
19
+ const host = getDaemonHost({ target: options.daemonTarget });
20
20
  // Validate arguments
21
21
  if (!nameArg || nameArg.trim().length === 0) {
22
22
  const error = {
@@ -28,9 +28,11 @@ export async function runArchiveCommandWithDeps(nameArg, options, deps) {
28
28
  }
29
29
  let client;
30
30
  try {
31
- client = await deps.connectToDaemon({ host: options.host });
31
+ client = await deps.connectToDaemon({ target: options.daemonTarget });
32
32
  }
33
33
  catch (err) {
34
+ if (err && typeof err === "object" && "code" in err)
35
+ throw err;
34
36
  const message = err instanceof Error ? err.message : String(err);
35
37
  const error = {
36
38
  code: "DAEMON_NOT_RUNNING",
@@ -1,5 +1,5 @@
1
1
  import path from "node:path";
2
- import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
2
+ import { connectToDaemon } from "../../utils/client.js";
3
3
  import { buildCreateWorktreeRequest } from "./create-input.js";
4
4
  export const createSchema = {
5
5
  idField: "worktreePath",
@@ -15,15 +15,7 @@ function cmdError(code, message, details) {
15
15
  export async function runCreateCommand(options, _command) {
16
16
  const cwd = options.cwd ?? process.cwd();
17
17
  const request = buildCreateWorktreeRequest(options, cwd);
18
- const host = getDaemonHost({ host: options.host });
19
- let client;
20
- try {
21
- client = await connectToDaemon({ host: options.host });
22
- }
23
- catch (err) {
24
- const message = err instanceof Error ? err.message : String(err);
25
- throw cmdError("DAEMON_NOT_RUNNING", `Cannot connect to daemon at ${host}: ${message}`, "Start the daemon with: paseo daemon start");
26
- }
18
+ const client = await connectToDaemon({ target: options.daemonTarget });
27
19
  try {
28
20
  const response = await client.createPaseoWorktree(request);
29
21
  const workspace = response.workspace;
@@ -7,8 +7,6 @@ export interface WorktreeListItem {
7
7
  cwd: string;
8
8
  agent: string;
9
9
  }
10
- export declare function resolvePaseoHomePath(): string;
11
- export declare function resolvePaseoWorktreesDir(): string;
12
10
  /** Schema for worktree ls output */
13
11
  export declare const worktreeLsSchema: OutputSchema<WorktreeListItem>;
14
12
  export type WorktreeLsResult = ListResult<WorktreeListItem>;
@@ -1,6 +1,5 @@
1
- import { homedir } from "node:os";
2
- import { basename, join, sep } from "node:path";
3
- import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
1
+ import { basename } from "node:path";
2
+ import { connectToDaemon } from "../../utils/client.js";
4
3
  /** Shorten home directory in path */
5
4
  function shortenPath(path) {
6
5
  const home = process.env.HOME;
@@ -13,16 +12,6 @@ function shortenPath(path) {
13
12
  function extractWorktreeName(path) {
14
13
  return basename(path);
15
14
  }
16
- export function resolvePaseoHomePath() {
17
- return process.env.PASEO_HOME ?? join(homedir(), ".paseo");
18
- }
19
- export function resolvePaseoWorktreesDir() {
20
- return join(resolvePaseoHomePath(), "worktrees");
21
- }
22
- function isAgentInManagedWorktree(agentCwd) {
23
- const worktreesDir = resolvePaseoWorktreesDir();
24
- return agentCwd === worktreesDir || agentCwd.startsWith(worktreesDir + sep);
25
- }
26
15
  /** Schema for worktree ls output */
27
16
  export const worktreeLsSchema = {
28
17
  idField: "name",
@@ -34,20 +23,7 @@ export const worktreeLsSchema = {
34
23
  ],
35
24
  };
36
25
  export async function runLsCommand(options, _command) {
37
- const host = getDaemonHost({ host: options.host });
38
- let client;
39
- try {
40
- client = await connectToDaemon({ host: options.host });
41
- }
42
- catch (err) {
43
- const message = err instanceof Error ? err.message : String(err);
44
- const error = {
45
- code: "DAEMON_NOT_RUNNING",
46
- message: `Cannot connect to daemon at ${host}: ${message}`,
47
- details: "Start the daemon with: paseo daemon start",
48
- };
49
- throw error;
50
- }
26
+ const client = await connectToDaemon({ target: options.daemonTarget });
51
27
  try {
52
28
  const agentsPayload = await client.fetchAgents({ filter: { includeArchived: true } });
53
29
  const agents = agentsPayload.entries.map((entry) => entry.agent);
@@ -64,9 +40,7 @@ export async function runLsCommand(options, _command) {
64
40
  // Build a map of worktree paths to agent IDs
65
41
  const worktreeAgentMap = new Map();
66
42
  for (const agent of agents) {
67
- if (isAgentInManagedWorktree(agent.cwd)) {
68
- worktreeAgentMap.set(agent.cwd, agent.id.slice(0, 7));
69
- }
43
+ worktreeAgentMap.set(agent.cwd, agent.id.slice(0, 7));
70
44
  }
71
45
  const items = response.worktrees.map((wt) => ({
72
46
  name: extractWorktreeName(wt.worktreePath),
@@ -7,6 +7,8 @@ import type { Command } from "commander";
7
7
  import type { AnyCommandResult, OutputOptions } from "./types.js";
8
8
  /** Options that include output settings from global options */
9
9
  export interface CommandOptions extends Partial<OutputOptions> {
10
+ daemonTarget: import("../utils/daemon-target.js").DaemonTarget;
11
+ home?: string;
10
12
  /** Daemon host target from --host option */
11
13
  host?: string;
12
14
  /** JSON output flag from --json option */
package/dist/run.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { renderError, toCommandError, defaultOutputOptions } from "./output/render.js";
1
2
  import { createCli } from "./cli.js";
2
3
  import { classifyInvocation } from "./classify.js";
3
4
  import { openDesktopWithProject } from "./commands/open.js";
@@ -34,7 +35,16 @@ export async function runCli(argv, options = {}) {
34
35
  return typeof process.exitCode === "number" ? process.exitCode : 0;
35
36
  }
36
37
  const program = createCli();
37
- await program.parseAsync(parseArgv, { from: "node" });
38
+ try {
39
+ await program.parseAsync(parseArgv, { from: "node" });
40
+ }
41
+ catch (error) {
42
+ process.stderr.write(renderError(toCommandError(error), {
43
+ ...defaultOutputOptions,
44
+ format: argv.includes("--json") ? "json" : "table",
45
+ }) + "\n");
46
+ return 1;
47
+ }
38
48
  return typeof process.exitCode === "number" ? process.exitCode : 0;
39
49
  }
40
50
  //# sourceMappingURL=run.js.map
@@ -1,2 +1,2 @@
1
- export declare function getOrCreateCliClientId(): Promise<string>;
1
+ export declare function getOrCreateCliClientId(home: string): Promise<string>;
2
2
  //# sourceMappingURL=client-id.d.ts.map
@@ -1,9 +1,7 @@
1
- import { mkdir, readFile, writeFile } from "node:fs/promises";
1
+ import { ensurePrivateDirectory } from "@camerontaylor/paseo-server";
2
+ import { readFile, writeFile } from "node:fs/promises";
2
3
  import { randomUUID } from "node:crypto";
3
- import { dirname, join } from "node:path";
4
- import { homedir } from "node:os";
5
- const CLIENT_SESSION_KEY_FILE = join(process.env.PASEO_HOME ?? join(homedir(), ".paseo"), "cli-client-id");
6
- let cachedClientId = null;
4
+ import { join } from "node:path";
7
5
  function normalizeClientId(value) {
8
6
  const trimmed = value.trim();
9
7
  return trimmed.length > 0 ? trimmed : null;
@@ -11,14 +9,11 @@ function normalizeClientId(value) {
11
9
  function generateClientId() {
12
10
  return `cid_${randomUUID().replace(/-/g, "")}`;
13
11
  }
14
- export async function getOrCreateCliClientId() {
15
- if (cachedClientId) {
16
- return cachedClientId;
17
- }
12
+ export async function getOrCreateCliClientId(home) {
13
+ const clientSessionKeyFile = join(home, "cli-client-id");
18
14
  try {
19
- const existing = normalizeClientId(await readFile(CLIENT_SESSION_KEY_FILE, "utf8"));
15
+ const existing = normalizeClientId(await readFile(clientSessionKeyFile, "utf8"));
20
16
  if (existing) {
21
- cachedClientId = existing;
22
17
  return existing;
23
18
  }
24
19
  }
@@ -29,9 +24,8 @@ export async function getOrCreateCliClientId() {
29
24
  }
30
25
  }
31
26
  const nextValue = generateClientId();
32
- await mkdir(dirname(CLIENT_SESSION_KEY_FILE), { recursive: true });
33
- await writeFile(CLIENT_SESSION_KEY_FILE, nextValue, { mode: 0o600 });
34
- cachedClientId = nextValue;
27
+ ensurePrivateDirectory(home);
28
+ await writeFile(clientSessionKeyFile, nextValue, { mode: 0o600 });
35
29
  return nextValue;
36
30
  }
37
31
  //# sourceMappingURL=client-id.js.map
@@ -1,14 +1,13 @@
1
+ import { type DaemonInstance } from "@camerontaylor/paseo-server";
2
+ import { type DaemonTarget } from "./daemon-target.js";
3
+ export type { DaemonTarget } from "./daemon-target.js";
1
4
  import { DaemonClient } from "@camerontaylor/paseo-client/internal/daemon-client";
2
5
  export interface ConnectOptions {
3
- host?: string;
6
+ target: DaemonTarget;
4
7
  timeout?: number;
8
+ instance?: DaemonInstance;
5
9
  }
6
- export interface DaemonConnectionCommandError {
7
- code: "DAEMON_NOT_RUNNING";
8
- message: string;
9
- details: string;
10
- }
11
- type DaemonTarget = {
10
+ type TransportTarget = {
12
11
  type: "tcp";
13
12
  url: string;
14
13
  } | {
@@ -16,25 +15,22 @@ type DaemonTarget = {
16
15
  url: string;
17
16
  socketPath: string;
18
17
  };
19
- /**
20
- * Get the daemon host from environment or options
21
- */
22
- export declare function getDaemonHost(options?: ConnectOptions): string;
23
- export declare function buildDaemonConnectionCommandError(options: {
24
- host?: string;
18
+ export declare function getDaemonHost(options: ConnectOptions): string;
19
+ export declare function buildDaemonConnectionCommandError(options: ConnectOptions & {
25
20
  error: unknown;
26
- }): DaemonConnectionCommandError;
21
+ }): {
22
+ code: string;
23
+ message: string;
24
+ details: string;
25
+ };
27
26
  export declare function normalizeDaemonHost(raw: string): string | null;
28
- export declare function resolveDefaultDaemonHost(env?: NodeJS.ProcessEnv): string;
29
- export declare function resolveDefaultDaemonHosts(env?: NodeJS.ProcessEnv): string[];
30
- export declare function getExplicitDaemonHost(host: string | undefined, env?: NodeJS.ProcessEnv): string | undefined;
31
- export declare function resolveDaemonTarget(host: string): DaemonTarget;
27
+ export declare function resolveDaemonTarget(host: string): TransportTarget;
32
28
  export declare function resolveDaemonPassword(host: string): string | undefined;
33
- export declare function connectToDaemon(options?: ConnectOptions): Promise<DaemonClient>;
29
+ export declare function connectToDaemon(options: ConnectOptions): Promise<DaemonClient>;
34
30
  /**
35
31
  * Try to connect to the daemon, returns null if connection fails
36
32
  */
37
- export declare function tryConnectToDaemon(options?: ConnectOptions): Promise<DaemonClient | null>;
33
+ export declare function tryConnectToDaemon(options: ConnectOptions): Promise<DaemonClient | null>;
38
34
  /** Minimal agent type for ID resolution */
39
35
  interface AgentLike {
40
36
  id: string;
@@ -50,5 +46,4 @@ interface AgentLike {
50
46
  * Returns the full agent ID if found, null otherwise.
51
47
  */
52
48
  export declare function resolveAgentId(idOrName: string, agents: AgentLike[]): string | null;
53
- export {};
54
49
  //# sourceMappingURL=client.d.ts.map
@@ -1,32 +1,39 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { loadConfig, resolvePaseoHome } from "@camerontaylor/paseo-server";
1
+ import { waitForDaemonReady, resolvePaseoHome } from "@camerontaylor/paseo-server";
2
+ import { describeDaemonTarget } from "./daemon-target.js";
3
3
  import { buildDaemonWebSocketUrl, buildRelayWebSocketUrl, normalizeHostPort, parseConnectionUri, shouldUseTlsForDefaultHostedRelay, } from "@camerontaylor/paseo-protocol/daemon-endpoints";
4
4
  import { parseConnectionOfferFromUrl, } from "@camerontaylor/paseo-protocol/connection-offer";
5
5
  import { parseSshTransportUri } from "@camerontaylor/paseo-protocol/ssh-transport";
6
6
  import { DaemonClient } from "@camerontaylor/paseo-client/internal/daemon-client";
7
- import path from "node:path";
8
7
  import { WebSocket } from "ws";
9
8
  import { getOrCreateCliClientId } from "./client-id.js";
10
9
  import { resolveCliVersion } from "../version.js";
11
10
  import { createSshTunnel } from "../ssh/ssh-tunnel.js";
12
- const DEFAULT_HOST = "localhost:6767";
13
11
  const DEFAULT_TIMEOUT = 15000;
14
- const PID_FILENAME = "paseo.pid";
15
- /**
16
- * Get the daemon host from environment or options
17
- */
18
12
  export function getDaemonHost(options) {
19
- return resolveDaemonHostCandidates(options)[0] ?? DEFAULT_HOST;
13
+ return describeDaemonTarget(options.target);
20
14
  }
21
15
  export function buildDaemonConnectionCommandError(options) {
22
- const host = getDaemonHost({ host: options.host });
23
- const message = options.error instanceof Error ? options.error.message : String(options.error);
16
+ const error = options.error;
17
+ let message = error instanceof Error ? error.message : String(error);
18
+ if (error && typeof error === "object" && "message" in error)
19
+ message = String(error.message);
20
+ if (options.target.kind === "endpoint")
21
+ message = message.replaceAll(options.target.host, describeDaemonTarget(options.target));
22
+ if (message.startsWith("Cannot connect to daemon at "))
23
+ return error;
24
+ let code = "DAEMON_UNREACHABLE";
25
+ if (typeof error === "object" && error !== null && "code" in error)
26
+ code = String(error.code);
27
+ else if (message === "Password required")
28
+ code = "AUTH_REQUIRED";
29
+ else if (message === "Incorrect password")
30
+ code = "AUTH_FAILED";
24
31
  return {
25
- code: "DAEMON_NOT_RUNNING",
26
- message: `Cannot connect to daemon at ${host}: ${message}`,
27
- details: host.trim().startsWith("ssh://")
28
- ? "Start the Paseo daemon on the SSH host; SSH transport does not install or start it."
29
- : "Start the daemon with: paseo daemon start",
32
+ code,
33
+ message: `Cannot connect to daemon at ${describeDaemonTarget(options.target)}: ${message}`,
34
+ details: options.target.kind === "instance"
35
+ ? `Start with: paseo daemon start --home ${JSON.stringify(options.target.home)}`
36
+ : "Check the selected endpoint and credentials. SSH transport does not install or start the daemon.",
30
37
  };
31
38
  }
32
39
  export function normalizeDaemonHost(raw) {
@@ -70,77 +77,6 @@ export function normalizeDaemonHost(raw) {
70
77
  }
71
78
  return trimmed.includes(":") ? trimmed : null;
72
79
  }
73
- export function resolveDefaultDaemonHost(env = process.env) {
74
- return resolveDefaultDaemonHosts(env)[0] ?? DEFAULT_HOST;
75
- }
76
- function isIpcDaemonHost(host) {
77
- return host !== null && (host.startsWith("unix://") || host.startsWith("pipe://"));
78
- }
79
- function isTcpDaemonHost(host) {
80
- return host !== null && !isIpcDaemonHost(host);
81
- }
82
- function readPidSocketTarget(paseoHome) {
83
- const pidPath = path.join(paseoHome, PID_FILENAME);
84
- if (!existsSync(pidPath)) {
85
- return null;
86
- }
87
- try {
88
- const parsed = JSON.parse(readFileSync(pidPath, "utf-8"));
89
- if (typeof parsed.listen === "string")
90
- return parsed.listen;
91
- if (typeof parsed.sockPath === "string")
92
- return parsed.sockPath;
93
- return null;
94
- }
95
- catch {
96
- return null;
97
- }
98
- }
99
- function resolveConfiguredIpcDaemonHost(env, paseoHome) {
100
- const directEnvHost = normalizeDaemonHost(env.PASEO_LISTEN ?? "");
101
- if (isIpcDaemonHost(directEnvHost)) {
102
- return directEnvHost;
103
- }
104
- const pidHost = normalizeDaemonHost(readPidSocketTarget(paseoHome) ?? "");
105
- if (isIpcDaemonHost(pidHost)) {
106
- return pidHost;
107
- }
108
- const config = loadConfig(paseoHome, { env });
109
- const configuredHost = normalizeDaemonHost(config.listen);
110
- return isIpcDaemonHost(configuredHost) ? configuredHost : null;
111
- }
112
- function resolveConfiguredTcpDaemonHost(env, paseoHome) {
113
- const configuredHost = normalizeDaemonHost(loadConfig(paseoHome, { env }).listen);
114
- if (!isTcpDaemonHost(configuredHost)) {
115
- return null;
116
- }
117
- return configuredHost === "127.0.0.1:6767" ? null : configuredHost;
118
- }
119
- export function resolveDefaultDaemonHosts(env = process.env) {
120
- const paseoHome = resolvePaseoHome(env);
121
- const candidates = [];
122
- const configuredIpcHost = resolveConfiguredIpcDaemonHost(env, paseoHome);
123
- if (configuredIpcHost) {
124
- candidates.push(configuredIpcHost);
125
- }
126
- const configuredTcpHost = resolveConfiguredTcpDaemonHost(env, paseoHome);
127
- if (configuredTcpHost) {
128
- candidates.push(configuredTcpHost);
129
- }
130
- candidates.push(DEFAULT_HOST);
131
- return Array.from(new Set(candidates));
132
- }
133
- function resolveDaemonHostCandidates(options) {
134
- const explicitHost = getExplicitDaemonHost(options?.host);
135
- if (explicitHost) {
136
- return [explicitHost];
137
- }
138
- return resolveDefaultDaemonHosts();
139
- }
140
- export function getExplicitDaemonHost(host, env = process.env) {
141
- const explicitHost = host ?? env.PASEO_HOST;
142
- return explicitHost?.trim() ? explicitHost : undefined;
143
- }
144
80
  function stripIpcPrefix(trimmed) {
145
81
  if (trimmed.startsWith("unix://"))
146
82
  return trimmed.slice("unix://".length).trim();
@@ -269,18 +205,34 @@ function parseHostOfferOrNull(host) {
269
205
  throw new Error(`Invalid pairing offer URL: ${message}`, { cause: error });
270
206
  }
271
207
  }
272
- export async function connectToDaemon(options) {
273
- const timeout = options?.timeout ?? DEFAULT_TIMEOUT;
274
- const clientId = await getOrCreateCliClientId();
208
+ async function connectSelectedDaemon(options) {
209
+ const timeout = options.timeout ?? DEFAULT_TIMEOUT;
210
+ const deadline = Date.now() + timeout;
211
+ const explicitHost = options.target.kind === "endpoint"
212
+ ? options.target.host
213
+ : (await waitForDaemonReady(options.target.home, {
214
+ timeoutMs: timeout,
215
+ instance: options.instance,
216
+ })).listen;
217
+ const clientId = await getOrCreateCliClientId(resolvePaseoHome({}));
275
218
  const nodeWebSocketFactory = createNodeWebSocketFactory();
276
- const explicitHost = getExplicitDaemonHost(options?.host);
277
219
  if (explicitHost?.trim().startsWith("ssh://")) {
278
220
  const target = parseSshTransportUri(explicitHost.trim());
279
221
  const tunnel = await createSshTunnel(target);
280
222
  const password = resolveDaemonPassword(explicitHost);
281
- const result = await tryConnectHost(tunnel.endpoint, password, clientId, timeout, nodeWebSocketFactory);
282
- if ("client" in result)
223
+ const result = await tryConnectHost(tunnel.endpoint, password, clientId, Math.max(1, deadline - Date.now()), nodeWebSocketFactory);
224
+ if ("client" in result) {
225
+ const close = result.client.close.bind(result.client);
226
+ result.client.close = async () => {
227
+ try {
228
+ await close();
229
+ }
230
+ finally {
231
+ tunnel.close();
232
+ }
233
+ };
283
234
  return result.client;
235
+ }
284
236
  const failure = tunnel.failureDetail();
285
237
  tunnel.close();
286
238
  if (failure)
@@ -289,24 +241,20 @@ export async function connectToDaemon(options) {
289
241
  }
290
242
  const offer = parseHostOfferOrNull(explicitHost);
291
243
  if (offer) {
292
- return connectViaRelayOffer(offer, clientId, timeout, nodeWebSocketFactory);
244
+ return connectViaRelayOffer(offer, clientId, Math.max(1, deadline - Date.now()), nodeWebSocketFactory);
293
245
  }
294
- const hosts = resolveDaemonHostCandidates(options);
295
- async function tryNext(index, lastError) {
296
- if (index >= hosts.length) {
297
- if (lastError instanceof Error)
298
- throw lastError;
299
- throw new Error(`Unable to connect to Paseo daemon via ${hosts.join(", ")}`);
300
- }
301
- const host = hosts[index];
302
- const password = resolveDaemonPassword(host);
303
- const result = await tryConnectHost(host, password, clientId, timeout, nodeWebSocketFactory);
304
- if ("client" in result) {
305
- return result.client;
306
- }
307
- return tryNext(index + 1, result.error);
246
+ const result = await tryConnectHost(explicitHost, resolveDaemonPassword(explicitHost), clientId, Math.max(1, deadline - Date.now()), nodeWebSocketFactory);
247
+ if ("client" in result)
248
+ return result.client;
249
+ throw result.error;
250
+ }
251
+ export async function connectToDaemon(options) {
252
+ try {
253
+ return await connectSelectedDaemon(options);
254
+ }
255
+ catch (error) {
256
+ throw buildDaemonConnectionCommandError({ ...options, error });
308
257
  }
309
- return tryNext(0, null);
310
258
  }
311
259
  /**
312
260
  * Try to connect to the daemon, returns null if connection fails