@camerontaylor/paseo-cli 0.9.1-fork.1 → 0.10.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.
- package/dist/commands/agent/run.d.ts +11 -2
- package/dist/commands/agent/run.js +23 -7
- package/dist/commands/daemon/pair.js +6 -1
- package/dist/commands/daemon/set-password.js +7 -1
- package/dist/commands/daemon/status.js +1 -0
- package/dist/commands/permit/allow.d.ts +3 -0
- package/dist/commands/permit/allow.js +12 -8
- package/dist/commands/permit/deny.js +2 -8
- package/dist/commands/permit/ls.d.ts +3 -0
- package/dist/commands/permit/ls.js +17 -16
- package/dist/output/pairing.d.ts +2 -1
- package/dist/output/pairing.js +14 -2
- package/dist/utils/client.d.ts +8 -0
- package/dist/utils/client.js +30 -9
- package/package.json +4 -4
|
@@ -72,7 +72,16 @@ export interface RunWorkspaceLookupClient {
|
|
|
72
72
|
}
|
|
73
73
|
export declare function resolveExistingRunWorkspace(client: RunWorkspaceLookupClient, workspaceId: string): Promise<RunWorkspace>;
|
|
74
74
|
export declare function runRunCommand(prompt: string, options: AgentRunOptions, _command: Command): Promise<SingleResult<AgentRunResult>>;
|
|
75
|
-
export
|
|
75
|
+
export interface RunCallerLookupClient {
|
|
76
|
+
fetchAgent(options: {
|
|
77
|
+
agentId: string;
|
|
78
|
+
}): Promise<{
|
|
79
|
+
agent: {
|
|
80
|
+
id: string;
|
|
81
|
+
};
|
|
82
|
+
} | null>;
|
|
83
|
+
}
|
|
84
|
+
export declare function resolveRunCallerAgentId(client: RunCallerLookupClient, env?: {
|
|
76
85
|
PASEO_AGENT_ID?: string;
|
|
77
|
-
}): string | undefined
|
|
86
|
+
}): Promise<string | undefined>;
|
|
78
87
|
//# sourceMappingURL=run.d.ts.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Option } from "commander";
|
|
2
2
|
import { getStructuredAgentResponse, StructuredAgentResponseError, } from "@camerontaylor/paseo-server/agent-response";
|
|
3
|
+
import { DaemonConnectionError } from "@camerontaylor/paseo-client/internal/daemon-client";
|
|
3
4
|
import { connectToDaemon } from "../../utils/client.js";
|
|
4
5
|
import { readFileSync } from "node:fs";
|
|
5
6
|
import { resolve } from "node:path";
|
|
@@ -374,18 +375,18 @@ export async function resolveExistingRunWorkspace(client, workspaceId) {
|
|
|
374
375
|
}
|
|
375
376
|
// Workspace policy for `paseo run`. Precedence:
|
|
376
377
|
// 1. --workspace <id> -> run in that existing workspace
|
|
377
|
-
// 2.
|
|
378
|
+
// 2. caller agent -> daemon resolves the caller's workspace
|
|
378
379
|
// 3. $PASEO_WORKSPACE_ID -> exported by workspace terminals
|
|
379
380
|
// 4. --new-workspace <kind> -> mint a new workspace explicitly
|
|
380
381
|
// 5. bare run -> mint a new local-backed workspace for cwd
|
|
381
|
-
async function resolveRunWorkspace(client, options, cwd) {
|
|
382
|
+
async function resolveRunWorkspace(client, options, cwd, callerAgentId) {
|
|
382
383
|
const newWorkspace = resolveNewWorkspaceKind(options);
|
|
383
384
|
const explicit = newWorkspace ? undefined : options.workspace?.trim();
|
|
384
385
|
if (explicit) {
|
|
385
386
|
console.error(`Using workspace ${explicit}`);
|
|
386
387
|
return resolveExistingRunWorkspace(client, explicit);
|
|
387
388
|
}
|
|
388
|
-
if (!newWorkspace &&
|
|
389
|
+
if (!newWorkspace && callerAgentId) {
|
|
389
390
|
return { cwd };
|
|
390
391
|
}
|
|
391
392
|
const ambientWorkspaceId = newWorkspace ? undefined : process.env.PASEO_WORKSPACE_ID?.trim();
|
|
@@ -434,9 +435,9 @@ export async function runRunCommand(prompt, options, _command) {
|
|
|
434
435
|
const labels = parseRunLabels(options.label);
|
|
435
436
|
const env = parseRunEnv(options.env);
|
|
436
437
|
const requestEnv = Object.keys(env).length > 0 ? env : undefined;
|
|
437
|
-
const
|
|
438
|
+
const callerAgentId = await resolveRunCallerAgentId(client);
|
|
439
|
+
const workspace = await resolveRunWorkspace(client, options, cwd, callerAgentId);
|
|
438
440
|
const workspaceId = workspace.id;
|
|
439
|
-
const callerAgentId = resolveRunCallerAgentId();
|
|
440
441
|
const runCwd = workspace.cwd;
|
|
441
442
|
if (outputSchema) {
|
|
442
443
|
let structuredAgent = null;
|
|
@@ -543,7 +544,22 @@ export async function runRunCommand(prompt, options, _command) {
|
|
|
543
544
|
throw error;
|
|
544
545
|
}
|
|
545
546
|
}
|
|
546
|
-
|
|
547
|
-
|
|
547
|
+
// PASEO_AGENT_ID names an agent on the daemon that launched this shell. A run
|
|
548
|
+
// sent to another daemon (--host or --home) has no caller there, so it runs as
|
|
549
|
+
// a top-level agent instead of failing on an unknown caller.
|
|
550
|
+
export async function resolveRunCallerAgentId(client, env = process.env) {
|
|
551
|
+
const agentId = env.PASEO_AGENT_ID?.trim();
|
|
552
|
+
if (!agentId) {
|
|
553
|
+
return undefined;
|
|
554
|
+
}
|
|
555
|
+
const caller = await client.fetchAgent({ agentId }).catch((error) => {
|
|
556
|
+
// A daemon without this agent answers with an error. A lost or timed-out
|
|
557
|
+
// connection is not an answer, so it must not drop the caller.
|
|
558
|
+
if (error instanceof DaemonConnectionError) {
|
|
559
|
+
throw error;
|
|
560
|
+
}
|
|
561
|
+
return null;
|
|
562
|
+
});
|
|
563
|
+
return caller?.agent.id === agentId ? agentId : undefined;
|
|
548
564
|
}
|
|
549
565
|
//# sourceMappingURL=run.js.map
|
|
@@ -7,6 +7,8 @@ import { readPersistedConfig, editPersistedConfig, resolveConfigFromPersisted, }
|
|
|
7
7
|
import { connectToDaemon } from "../../utils/client.js";
|
|
8
8
|
import { addJsonAndDaemonHostOptions, withGlobalOptions } from "../../utils/command-options.js";
|
|
9
9
|
import { formatPairingInstructions } from "../../output/pairing.js";
|
|
10
|
+
import { parseConnectionOfferFromUrl } from "@camerontaylor/paseo-protocol/connection-offer";
|
|
11
|
+
import { serializeRelayConnectionUri } from "@camerontaylor/paseo-protocol/daemon-endpoints";
|
|
10
12
|
const PAIRING_DAEMON_RPC_TIMEOUT_MS = 1500;
|
|
11
13
|
const RELAY_DOCS_URL = "https://paseo.sh/docs/security";
|
|
12
14
|
function createProcessOutput() {
|
|
@@ -126,13 +128,16 @@ function outputPairingResult(pairing, options, output) {
|
|
|
126
128
|
output.setExitCode(1);
|
|
127
129
|
return;
|
|
128
130
|
}
|
|
131
|
+
const offer = parseConnectionOfferFromUrl(pairing.url);
|
|
132
|
+
const connectionUri = offer ? serializeRelayConnectionUri({ offer }) : null;
|
|
129
133
|
if (options.json) {
|
|
130
|
-
output.writeStdout(`${JSON.stringify({ relayEnabled: pairing.relayEnabled, url: pairing.url, qr: pairing.qr }, null, 2)}\n`);
|
|
134
|
+
output.writeStdout(`${JSON.stringify({ relayEnabled: pairing.relayEnabled, url: pairing.url, qr: pairing.qr, connectionUri }, null, 2)}\n`);
|
|
131
135
|
return;
|
|
132
136
|
}
|
|
133
137
|
output.writeStdout(formatPairingInstructions({
|
|
134
138
|
url: pairing.url,
|
|
135
139
|
qr: pairing.qr,
|
|
140
|
+
connectionUri,
|
|
136
141
|
columns: output.columns,
|
|
137
142
|
}));
|
|
138
143
|
}
|
|
@@ -27,6 +27,12 @@ const setPasswordResultSchema = {
|
|
|
27
27
|
function createCommandError(code, message, details) {
|
|
28
28
|
return { code, message, ...(details ? { details } : {}) };
|
|
29
29
|
}
|
|
30
|
+
function terminalPasswordPrompt() {
|
|
31
|
+
if (!process.stdin.isTTY) {
|
|
32
|
+
throw createCommandError("PASSWORD_TTY_REQUIRED", "paseo daemon set-password needs a terminal to read the password", "Run it in an interactive terminal, or set PASEO_PASSWORD in the daemon's environment instead.");
|
|
33
|
+
}
|
|
34
|
+
return (message) => passwordPrompt({ message });
|
|
35
|
+
}
|
|
30
36
|
async function promptForPassword(promptPassword) {
|
|
31
37
|
const first = await promptPassword("New daemon password");
|
|
32
38
|
if (isCancel(first)) {
|
|
@@ -69,7 +75,7 @@ export async function setDaemonPasswordInConfig(newPassword, options = {}) {
|
|
|
69
75
|
export async function runSetPasswordCommand(options, _command) {
|
|
70
76
|
const promptPassword = typeof options.promptPassword === "function"
|
|
71
77
|
? options.promptPassword
|
|
72
|
-
: (
|
|
78
|
+
: terminalPasswordPrompt();
|
|
73
79
|
const newPassword = await promptForPassword(promptPassword);
|
|
74
80
|
const result = await setDaemonPasswordInConfig(newPassword, {
|
|
75
81
|
home: options.daemonTarget.kind === "instance" ? options.daemonTarget.home : undefined,
|
|
@@ -102,6 +102,7 @@ function localStatus(home, instance) {
|
|
|
102
102
|
pid: instance?.pid ?? null,
|
|
103
103
|
startedAt: instance?.startedAt ?? null,
|
|
104
104
|
listen: instance?.listen ?? null,
|
|
105
|
+
serverId: instance?.serverId ?? undefined,
|
|
105
106
|
hostname: instance?.hostname ?? null,
|
|
106
107
|
configuredListen: config.listen,
|
|
107
108
|
localDaemon,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
|
+
import type { AgentPermissionRequest } from "@camerontaylor/paseo-protocol/agent-types";
|
|
2
3
|
import type { CommandOptions, ListResult, OutputSchema } from "../../output/index.js";
|
|
3
4
|
/** Permission response item for display */
|
|
4
5
|
export interface PermissionResponseItem {
|
|
@@ -10,6 +11,8 @@ export interface PermissionResponseItem {
|
|
|
10
11
|
}
|
|
11
12
|
/** Schema for permit allow/deny output */
|
|
12
13
|
export declare const permitResponseSchema: OutputSchema<PermissionResponseItem>;
|
|
14
|
+
/** Transform a permission the agent answered to a response item */
|
|
15
|
+
export declare function toPermissionResponseItem(agentId: string, permission: AgentPermissionRequest, result: "allowed" | "denied"): PermissionResponseItem;
|
|
13
16
|
export type PermitAllowResult = ListResult<PermissionResponseItem>;
|
|
14
17
|
export interface PermitAllowOptions extends CommandOptions {
|
|
15
18
|
all?: boolean;
|
|
@@ -3,7 +3,7 @@ import { connectToDaemon } from "../../utils/client.js";
|
|
|
3
3
|
export const permitResponseSchema = {
|
|
4
4
|
idField: "requestId",
|
|
5
5
|
columns: [
|
|
6
|
-
{ header: "REQUEST ID", field:
|
|
6
|
+
{ header: "REQUEST ID", field: (item) => item.requestId.slice(0, 8), width: 12 },
|
|
7
7
|
{ header: "AGENT", field: "agentShortId", width: 10 },
|
|
8
8
|
{ header: "TOOL", field: "name", width: 20 },
|
|
9
9
|
{
|
|
@@ -20,6 +20,16 @@ export const permitResponseSchema = {
|
|
|
20
20
|
},
|
|
21
21
|
],
|
|
22
22
|
};
|
|
23
|
+
/** Transform a permission the agent answered to a response item */
|
|
24
|
+
export function toPermissionResponseItem(agentId, permission, result) {
|
|
25
|
+
return {
|
|
26
|
+
requestId: permission.id,
|
|
27
|
+
agentId,
|
|
28
|
+
agentShortId: agentId.slice(0, 7),
|
|
29
|
+
name: permission.name,
|
|
30
|
+
result,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
23
33
|
export async function runAllowCommand(agentIdOrPrefix, reqId, options, _command) {
|
|
24
34
|
// No validation needed - if no reqId provided, allow all by default
|
|
25
35
|
// Parse input JSON if provided
|
|
@@ -88,13 +98,7 @@ export async function runAllowCommand(agentIdOrPrefix, reqId, options, _command)
|
|
|
88
98
|
behavior: "allow",
|
|
89
99
|
...(updatedInput ? { updatedInput } : {}),
|
|
90
100
|
});
|
|
91
|
-
return
|
|
92
|
-
requestId: permission.id.slice(0, 8),
|
|
93
|
-
agentId: resolvedAgentId,
|
|
94
|
-
agentShortId: resolvedAgentId.slice(0, 7),
|
|
95
|
-
name: permission.name,
|
|
96
|
-
result: "allowed",
|
|
97
|
-
};
|
|
101
|
+
return toPermissionResponseItem(resolvedAgentId, permission, "allowed");
|
|
98
102
|
}));
|
|
99
103
|
await client.close();
|
|
100
104
|
return {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { connectToDaemon } from "../../utils/client.js";
|
|
2
|
-
import { permitResponseSchema } from "./allow.js";
|
|
2
|
+
import { permitResponseSchema, toPermissionResponseItem, } from "./allow.js";
|
|
3
3
|
export async function runDenyCommand(agentIdOrPrefix, reqId, options, _command) {
|
|
4
4
|
// Validate arguments
|
|
5
5
|
if (!options.all && !reqId) {
|
|
@@ -60,13 +60,7 @@ export async function runDenyCommand(agentIdOrPrefix, reqId, options, _command)
|
|
|
60
60
|
...(options.message ? { message: options.message } : {}),
|
|
61
61
|
...(options.interrupt ? { interrupt: true } : {}),
|
|
62
62
|
});
|
|
63
|
-
return
|
|
64
|
-
requestId: permission.id.slice(0, 8),
|
|
65
|
-
agentId: resolvedAgentId,
|
|
66
|
-
agentShortId: resolvedAgentId.slice(0, 7),
|
|
67
|
-
name: permission.name,
|
|
68
|
-
result: "denied",
|
|
69
|
-
};
|
|
63
|
+
return toPermissionResponseItem(resolvedAgentId, permission, "denied");
|
|
70
64
|
}));
|
|
71
65
|
await client.close();
|
|
72
66
|
return {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
|
+
import type { AgentSnapshotPayload } from "@camerontaylor/paseo-protocol/messages";
|
|
2
3
|
import type { CommandOptions, ListResult, OutputSchema } from "../../output/index.js";
|
|
3
4
|
/** Permission list item for display */
|
|
4
5
|
export interface PermissionListItem {
|
|
@@ -11,6 +12,8 @@ export interface PermissionListItem {
|
|
|
11
12
|
/** Schema for permit ls output */
|
|
12
13
|
export declare const permitLsSchema: OutputSchema<PermissionListItem>;
|
|
13
14
|
export type PermitLsResult = ListResult<PermissionListItem>;
|
|
15
|
+
/** Collect the pending permissions of every agent */
|
|
16
|
+
export declare function listPendingPermissions(agents: AgentSnapshotPayload[]): PermitLsResult;
|
|
14
17
|
export interface PermitLsOptions extends CommandOptions {
|
|
15
18
|
host?: string;
|
|
16
19
|
}
|
|
@@ -4,7 +4,7 @@ export const permitLsSchema = {
|
|
|
4
4
|
idField: "id",
|
|
5
5
|
columns: [
|
|
6
6
|
{ header: "AGENT", field: "agentShortId", width: 12 },
|
|
7
|
-
{ header: "REQ_ID", field:
|
|
7
|
+
{ header: "REQ_ID", field: (item) => item.id.slice(0, 8), width: 12 },
|
|
8
8
|
{ header: "TOOL", field: "name", width: 20 },
|
|
9
9
|
{ header: "DESCRIPTION", field: "description", width: 50 },
|
|
10
10
|
],
|
|
@@ -12,33 +12,34 @@ export const permitLsSchema = {
|
|
|
12
12
|
/** Transform agent snapshot + permission to list item */
|
|
13
13
|
function toListItem(agent, permission) {
|
|
14
14
|
return {
|
|
15
|
-
id: permission.id
|
|
15
|
+
id: permission.id,
|
|
16
16
|
agentId: agent.id,
|
|
17
17
|
agentShortId: agent.id.slice(0, 7),
|
|
18
18
|
name: permission.name,
|
|
19
19
|
description: permission.description ?? "-",
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
|
+
/** Collect the pending permissions of every agent */
|
|
23
|
+
export function listPendingPermissions(agents) {
|
|
24
|
+
const items = [];
|
|
25
|
+
for (const agent of agents) {
|
|
26
|
+
for (const permission of agent.pendingPermissions ?? []) {
|
|
27
|
+
items.push(toListItem(agent, permission));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
type: "list",
|
|
32
|
+
data: items,
|
|
33
|
+
schema: permitLsSchema,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
22
36
|
export async function runLsCommand(options, _command) {
|
|
23
37
|
const client = await connectToDaemon({ target: options.daemonTarget });
|
|
24
38
|
try {
|
|
25
39
|
const agentsPayload = await client.fetchAgents({ filter: { includeArchived: true } });
|
|
26
40
|
const agents = agentsPayload.entries.map((entry) => entry.agent);
|
|
27
41
|
await client.close();
|
|
28
|
-
|
|
29
|
-
const items = [];
|
|
30
|
-
for (const agent of agents) {
|
|
31
|
-
if (agent.pendingPermissions && agent.pendingPermissions.length > 0) {
|
|
32
|
-
for (const permission of agent.pendingPermissions) {
|
|
33
|
-
items.push(toListItem(agent, permission));
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return {
|
|
38
|
-
type: "list",
|
|
39
|
-
data: items,
|
|
40
|
-
schema: permitLsSchema,
|
|
41
|
-
};
|
|
42
|
+
return listPendingPermissions(agents);
|
|
42
43
|
}
|
|
43
44
|
catch (err) {
|
|
44
45
|
await client.close().catch(() => { });
|
package/dist/output/pairing.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
interface PairingInstructions {
|
|
2
2
|
url: string;
|
|
3
3
|
qr: string | null;
|
|
4
|
+
connectionUri?: string | null;
|
|
4
5
|
columns?: number;
|
|
5
6
|
}
|
|
6
|
-
export declare function formatPairingInstructions({ url, qr, columns }: PairingInstructions): string;
|
|
7
|
+
export declare function formatPairingInstructions({ url, qr, columns, connectionUri, }: PairingInstructions): string;
|
|
7
8
|
export {};
|
|
8
9
|
//# sourceMappingURL=pairing.d.ts.map
|
package/dist/output/pairing.js
CHANGED
|
@@ -18,7 +18,19 @@ function formatQr(qr, columns) {
|
|
|
18
18
|
}
|
|
19
19
|
return qr;
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
function redactConnectionUri(uri) {
|
|
22
|
+
try {
|
|
23
|
+
const parsed = new URL(uri);
|
|
24
|
+
if (parsed.searchParams.has("password")) {
|
|
25
|
+
parsed.searchParams.set("password", "[redacted]");
|
|
26
|
+
}
|
|
27
|
+
return parsed.toString();
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return "Invalid connection URI";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function formatPairingInstructions({ url, qr, columns, connectionUri, }) {
|
|
34
|
+
return `\nScan to pair:\n${formatQr(qr, columns)}\n\nPairing link:\n${url}${connectionUri ? `\n\nConnection URI:\n${redactConnectionUri(connectionUri)}` : ""}\n\nTreat this pairing link like a password. Anyone with it can access this daemon.\n`;
|
|
23
35
|
}
|
|
24
36
|
//# sourceMappingURL=pairing.js.map
|
package/dist/utils/client.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export interface ConnectOptions {
|
|
|
7
7
|
timeout?: number;
|
|
8
8
|
instance?: DaemonInstance;
|
|
9
9
|
}
|
|
10
|
+
export declare function resolveClientPaseoHome(target: DaemonTarget, env?: Readonly<Record<string, string | undefined>>): string;
|
|
10
11
|
type TransportTarget = {
|
|
11
12
|
type: "tcp";
|
|
12
13
|
url: string;
|
|
@@ -26,6 +27,13 @@ export declare function buildDaemonConnectionCommandError(options: ConnectOption
|
|
|
26
27
|
export declare function normalizeDaemonHost(raw: string): string | null;
|
|
27
28
|
export declare function resolveDaemonTarget(host: string): TransportTarget;
|
|
28
29
|
export declare function resolveDaemonPassword(host: string): string | undefined;
|
|
30
|
+
export declare function resolveDaemonCredential(host: string, home: string): {
|
|
31
|
+
kind: "password";
|
|
32
|
+
password: string;
|
|
33
|
+
} | {
|
|
34
|
+
kind: "localCredential";
|
|
35
|
+
token: string;
|
|
36
|
+
} | null;
|
|
29
37
|
export declare function connectToDaemon(options: ConnectOptions): Promise<DaemonClient>;
|
|
30
38
|
/**
|
|
31
39
|
* Try to connect to the daemon, returns null if connection fails
|
package/dist/utils/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { waitForDaemonReady, resolvePaseoHome, } from "@camerontaylor/paseo-server/daemon-control";
|
|
1
|
+
import { waitForDaemonReady, resolvePaseoHome, readLocalCredentialForTarget, } from "@camerontaylor/paseo-server/daemon-control";
|
|
2
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";
|
|
@@ -8,6 +8,9 @@ import { WebSocket } from "ws";
|
|
|
8
8
|
import { getOrCreateCliClientId } from "./client-id.js";
|
|
9
9
|
import { resolveCliVersion } from "../version.js";
|
|
10
10
|
import { createSshTunnel } from "../ssh/ssh-tunnel.js";
|
|
11
|
+
export function resolveClientPaseoHome(target, env = process.env) {
|
|
12
|
+
return target.kind === "instance" ? target.home : resolvePaseoHome(env);
|
|
13
|
+
}
|
|
11
14
|
const DEFAULT_TIMEOUT = 15000;
|
|
12
15
|
export function getDaemonHost(options) {
|
|
13
16
|
return describeDaemonTarget(options.target);
|
|
@@ -31,11 +34,18 @@ export function buildDaemonConnectionCommandError(options) {
|
|
|
31
34
|
return {
|
|
32
35
|
code,
|
|
33
36
|
message: `Cannot connect to daemon at ${describeDaemonTarget(options.target)}: ${message}`,
|
|
34
|
-
details: options.target
|
|
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.",
|
|
37
|
+
details: describeConnectionRemedy(code, options.target),
|
|
37
38
|
};
|
|
38
39
|
}
|
|
40
|
+
function describeConnectionRemedy(code, target) {
|
|
41
|
+
if (code === "AUTH_REQUIRED")
|
|
42
|
+
return "The daemon requires a password. Set PASEO_PASSWORD and retry.";
|
|
43
|
+
if (code === "AUTH_FAILED")
|
|
44
|
+
return "The daemon rejected the password. Check PASEO_PASSWORD and retry.";
|
|
45
|
+
if (target.kind === "instance")
|
|
46
|
+
return `Start with: paseo daemon start --home ${JSON.stringify(target.home)}`;
|
|
47
|
+
return "Check the selected endpoint and credentials. SSH transport does not install or start the daemon.";
|
|
48
|
+
}
|
|
39
49
|
export function normalizeDaemonHost(raw) {
|
|
40
50
|
const trimmed = raw.trim();
|
|
41
51
|
if (!trimmed) {
|
|
@@ -126,6 +136,13 @@ export function resolveDaemonPassword(host) {
|
|
|
126
136
|
const fromEnv = process.env.PASEO_PASSWORD;
|
|
127
137
|
return fromEnv && fromEnv.length > 0 ? fromEnv : undefined;
|
|
128
138
|
}
|
|
139
|
+
export function resolveDaemonCredential(host, home) {
|
|
140
|
+
const password = resolveDaemonPassword(host);
|
|
141
|
+
if (password)
|
|
142
|
+
return { kind: "password", password };
|
|
143
|
+
const token = readLocalCredentialForTarget(home, host);
|
|
144
|
+
return token ? { kind: "localCredential", token } : null;
|
|
145
|
+
}
|
|
129
146
|
/**
|
|
130
147
|
* Create a WebSocket factory that works in Node.js
|
|
131
148
|
*/
|
|
@@ -141,14 +158,17 @@ function createNodeWebSocketFactory() {
|
|
|
141
158
|
* Create and connect a daemon client
|
|
142
159
|
* Returns the connected client or throws if connection fails
|
|
143
160
|
*/
|
|
144
|
-
async function tryConnectHost(host,
|
|
161
|
+
async function tryConnectHost(host, credential, home, clientId, timeout, nodeWebSocketFactory) {
|
|
145
162
|
const target = resolveDaemonTarget(host);
|
|
146
163
|
const client = new DaemonClient({
|
|
147
164
|
url: target.url,
|
|
148
165
|
clientId,
|
|
149
166
|
clientType: "cli",
|
|
150
167
|
appVersion: resolveCliVersion(),
|
|
151
|
-
password,
|
|
168
|
+
...(credential?.kind === "password" ? { password: credential.password } : {}),
|
|
169
|
+
...(credential?.kind === "localCredential"
|
|
170
|
+
? { localCredential: () => readLocalCredentialForTarget(home, host) ?? undefined }
|
|
171
|
+
: {}),
|
|
152
172
|
connectTimeoutMs: timeout,
|
|
153
173
|
webSocketFactory: (url, config) => nodeWebSocketFactory(url, {
|
|
154
174
|
headers: config?.headers,
|
|
@@ -214,13 +234,14 @@ async function connectSelectedDaemon(options) {
|
|
|
214
234
|
timeoutMs: timeout,
|
|
215
235
|
instance: options.instance,
|
|
216
236
|
})).listen;
|
|
217
|
-
const
|
|
237
|
+
const home = resolveClientPaseoHome(options.target);
|
|
238
|
+
const clientId = await getOrCreateCliClientId(home);
|
|
218
239
|
const nodeWebSocketFactory = createNodeWebSocketFactory();
|
|
219
240
|
if (explicitHost?.trim().startsWith("ssh://")) {
|
|
220
241
|
const target = parseSshTransportUri(explicitHost.trim());
|
|
221
242
|
const tunnel = await createSshTunnel(target);
|
|
222
243
|
const password = resolveDaemonPassword(explicitHost);
|
|
223
|
-
const result = await tryConnectHost(tunnel.endpoint, password, clientId, Math.max(1, deadline - Date.now()), nodeWebSocketFactory);
|
|
244
|
+
const result = await tryConnectHost(tunnel.endpoint, password ? { kind: "password", password } : null, home, clientId, Math.max(1, deadline - Date.now()), nodeWebSocketFactory);
|
|
224
245
|
if ("client" in result) {
|
|
225
246
|
const close = result.client.close.bind(result.client);
|
|
226
247
|
result.client.close = async () => {
|
|
@@ -243,7 +264,7 @@ async function connectSelectedDaemon(options) {
|
|
|
243
264
|
if (offer) {
|
|
244
265
|
return connectViaRelayOffer(offer, clientId, Math.max(1, deadline - Date.now()), nodeWebSocketFactory);
|
|
245
266
|
}
|
|
246
|
-
const result = await tryConnectHost(explicitHost,
|
|
267
|
+
const result = await tryConnectHost(explicitHost, resolveDaemonCredential(explicitHost, home), home, clientId, Math.max(1, deadline - Date.now()), nodeWebSocketFactory);
|
|
247
268
|
if ("client" in result)
|
|
248
269
|
return result.client;
|
|
249
270
|
throw result.error;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camerontaylor/paseo-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.2-fork.1",
|
|
4
4
|
"description": "Paseo CLI - control your AI coding agents from the command line",
|
|
5
5
|
"bin": {
|
|
6
6
|
"paseo": "bin/paseo"
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@clack/prompts": "^1.0.0",
|
|
31
|
-
"@camerontaylor/paseo-client": "0.
|
|
32
|
-
"@camerontaylor/paseo-protocol": "0.
|
|
33
|
-
"@camerontaylor/paseo-server": "0.
|
|
31
|
+
"@camerontaylor/paseo-client": "0.10.2-fork.1",
|
|
32
|
+
"@camerontaylor/paseo-protocol": "0.10.2-fork.1",
|
|
33
|
+
"@camerontaylor/paseo-server": "0.10.2-fork.1",
|
|
34
34
|
"chalk": "^5.3.0",
|
|
35
35
|
"commander": "^12.0.0",
|
|
36
36
|
"mime-types": "^2.1.35",
|