@getpaseo/cli 0.7.0-beta.1 → 0.7.0-beta.2
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.
|
@@ -80,13 +80,16 @@ function toListItem(agent) {
|
|
|
80
80
|
}
|
|
81
81
|
function daemonConnectionFailure(host, cause) {
|
|
82
82
|
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
83
|
+
const isSsh = host.trim().startsWith("ssh://");
|
|
83
84
|
return {
|
|
84
85
|
code: "DAEMON_NOT_RUNNING",
|
|
85
86
|
message: `Cannot reach the daemon at ${host}: ${reason}`,
|
|
86
|
-
details:
|
|
87
|
-
"Start
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
details: isSsh
|
|
88
|
+
? "Start the Paseo daemon on the SSH host; SSH transport does not install or start it."
|
|
89
|
+
: [
|
|
90
|
+
"Start a local daemon with: paseo daemon start",
|
|
91
|
+
"To use another daemon, pass --host <host:port> or set PASEO_HOST.",
|
|
92
|
+
].join("\n"),
|
|
90
93
|
};
|
|
91
94
|
}
|
|
92
95
|
function parseLabelFilters(labels) {
|
|
@@ -55,6 +55,46 @@ const SDK_DECLARATIONS = `declare module "@getpaseo/plugin/server" {
|
|
|
55
55
|
export const PluginAttachmentSearchPayloadSchema: import("zod").ZodType<PluginAttachmentSearchPayload>;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
declare module "@getpaseo/plugin/react-native" {
|
|
59
|
+
import type { ComponentType, FunctionComponent, ReactNode } from "react";
|
|
60
|
+
|
|
61
|
+
export interface PluginIconProps {
|
|
62
|
+
name: string;
|
|
63
|
+
size?: number;
|
|
64
|
+
color?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface ModalProps {
|
|
68
|
+
title: string;
|
|
69
|
+
icon?: ReactNode;
|
|
70
|
+
open: boolean;
|
|
71
|
+
onOpenChange(open: boolean): void;
|
|
72
|
+
children: ReactNode;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface ModalContentProps {
|
|
76
|
+
children: ReactNode;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface ModalComponent extends FunctionComponent<ModalProps> {
|
|
80
|
+
Content: ComponentType<ModalContentProps>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export type ToastVariant = "default" | "info" | "success" | "warning" | "error";
|
|
84
|
+
export interface ToastOptions {
|
|
85
|
+
variant?: ToastVariant;
|
|
86
|
+
durationMs?: number;
|
|
87
|
+
}
|
|
88
|
+
export interface ToastApi {
|
|
89
|
+
show(message: string, options?: ToastOptions): void;
|
|
90
|
+
error(message: string): void;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const Icon: ComponentType<PluginIconProps>;
|
|
94
|
+
export const Modal: ModalComponent;
|
|
95
|
+
export function useToast(): ToastApi;
|
|
96
|
+
}
|
|
97
|
+
|
|
58
98
|
declare module "@getpaseo/plugin" {
|
|
59
99
|
import type { ComponentType } from "react";
|
|
60
100
|
import type { PaseoApi } from "@getpaseo/client";
|
|
@@ -154,8 +194,26 @@ declare module "@getpaseo/plugin" {
|
|
|
154
194
|
agentId: string;
|
|
155
195
|
}
|
|
156
196
|
|
|
197
|
+
export interface PluginComposerPillProps extends PluginHostProps {
|
|
198
|
+
workspaceId: string;
|
|
199
|
+
agentId: string;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export interface PluginComposerPillContribution {
|
|
203
|
+
id: string;
|
|
204
|
+
title: string;
|
|
205
|
+
workspaceId: string;
|
|
206
|
+
agentId: string;
|
|
207
|
+
Component: ComponentType<PluginComposerPillProps>;
|
|
208
|
+
onPress(): void | Promise<void>;
|
|
209
|
+
}
|
|
210
|
+
|
|
157
211
|
export type PluginPanelLocation = "workspace" | "explorer";
|
|
158
212
|
export interface PluginOpenPanelOptions { location?: PluginPanelLocation; }
|
|
213
|
+
export interface PluginClientOpenPanelOptions extends PluginOpenPanelOptions {
|
|
214
|
+
workspaceId: string;
|
|
215
|
+
agentId?: string;
|
|
216
|
+
}
|
|
159
217
|
|
|
160
218
|
export type PluginWorkspacePanelContribution =
|
|
161
219
|
| { id: string; title: string; icon: string; locations?: readonly PluginPanelLocation[]; context: "workspace"; Component: ComponentType<PluginWorkspacePanelProps> }
|
|
@@ -240,6 +298,12 @@ declare module "@getpaseo/plugin" {
|
|
|
240
298
|
openPanel(id: string, options?: PluginOpenPanelOptions): void;
|
|
241
299
|
}
|
|
242
300
|
|
|
301
|
+
export interface PluginClientContext extends PluginCommandCapabilities {
|
|
302
|
+
addComposerPill(contribution: PluginComposerPillContribution): PluginCleanup;
|
|
303
|
+
openPanel(id: string, options: PluginClientOpenPanelOptions): void;
|
|
304
|
+
}
|
|
305
|
+
export type PluginClientContribution = (client: PluginClientContext) => PluginCleanup;
|
|
306
|
+
|
|
243
307
|
export type PluginCommandCenterItemContribution =
|
|
244
308
|
| { id: string; title: string; icon: string; keywords?: readonly string[]; context: "global"; onSelect(context: PluginGlobalCommandContext): void | Promise<void> }
|
|
245
309
|
| { id: string; title: string; icon: string; keywords?: readonly string[]; context: "workspace"; onSelect(context: PluginWorkspaceCommandContext): void | Promise<void> }
|
|
@@ -257,6 +321,7 @@ declare module "@getpaseo/plugin" {
|
|
|
257
321
|
addSidebarItem(contribution: PluginSidebarContribution): void;
|
|
258
322
|
addWorkspacePanel(contribution: PluginWorkspacePanelContribution): void;
|
|
259
323
|
addCommandCenterItem(contribution: PluginCommandCenterItemContribution): void;
|
|
324
|
+
addClientSide(contribution: PluginClientContribution): void;
|
|
260
325
|
addAttachmentSource(contribution: PluginAttachmentSourceContribution): void;
|
|
261
326
|
addTheme(contribution: PluginThemeContribution): void;
|
|
262
327
|
addTimelineTransformer<ItemType extends AgentTimelineItem["type"]>(contribution: PluginTimelineTransformerContribution<ItemType>): void;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type SshTransportTarget } from "@getpaseo/protocol/ssh-transport";
|
|
2
|
+
export interface SshTunnel {
|
|
3
|
+
endpoint: string;
|
|
4
|
+
close(): void;
|
|
5
|
+
failureDetail(): string | null;
|
|
6
|
+
}
|
|
7
|
+
export declare function resolveSshFailureDetail(failure: string | null, stderr: string): string | null;
|
|
8
|
+
export declare function createSshTunnel(target: SshTransportTarget): Promise<SshTunnel>;
|
|
9
|
+
//# sourceMappingURL=ssh-tunnel.d.ts.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createServer } from "node:net";
|
|
3
|
+
import { buildSshTunnelArgs } from "@getpaseo/protocol/ssh-transport";
|
|
4
|
+
const SSH_STDERR_LIMIT = 8192;
|
|
5
|
+
function formatSshFailure(stderr, code, signal) {
|
|
6
|
+
const detail = stderr.trim();
|
|
7
|
+
if (detail)
|
|
8
|
+
return detail;
|
|
9
|
+
if (signal)
|
|
10
|
+
return `ssh exited with signal ${signal}`;
|
|
11
|
+
return `ssh exited with code ${code ?? "unknown"}`;
|
|
12
|
+
}
|
|
13
|
+
export function resolveSshFailureDetail(failure, stderr) {
|
|
14
|
+
return failure ?? (stderr.trim() || null);
|
|
15
|
+
}
|
|
16
|
+
export function createSshTunnel(target) {
|
|
17
|
+
let server = null;
|
|
18
|
+
let socket = null;
|
|
19
|
+
let child = null;
|
|
20
|
+
let stderr = "";
|
|
21
|
+
let failure = null;
|
|
22
|
+
function close() {
|
|
23
|
+
server?.close();
|
|
24
|
+
server = null;
|
|
25
|
+
socket?.destroy();
|
|
26
|
+
socket = null;
|
|
27
|
+
if (child && !child.killed)
|
|
28
|
+
child.kill();
|
|
29
|
+
child = null;
|
|
30
|
+
}
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
server = createServer((acceptedSocket) => {
|
|
33
|
+
socket = acceptedSocket;
|
|
34
|
+
server?.close();
|
|
35
|
+
server = null;
|
|
36
|
+
child = spawn("ssh", buildSshTunnelArgs(target), {
|
|
37
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
38
|
+
windowsHide: true,
|
|
39
|
+
});
|
|
40
|
+
child.stderr.on("data", (chunk) => {
|
|
41
|
+
stderr = `${stderr}${chunk.toString()}`.slice(-SSH_STDERR_LIMIT);
|
|
42
|
+
});
|
|
43
|
+
child.on("error", (error) => {
|
|
44
|
+
failure = error.message;
|
|
45
|
+
acceptedSocket.destroy(error);
|
|
46
|
+
});
|
|
47
|
+
child.on("exit", (code, signal) => {
|
|
48
|
+
if (code !== 0 || signal)
|
|
49
|
+
failure = formatSshFailure(stderr, code, signal);
|
|
50
|
+
acceptedSocket.destroy(failure ? new Error(failure) : undefined);
|
|
51
|
+
});
|
|
52
|
+
acceptedSocket.on("error", () => undefined);
|
|
53
|
+
acceptedSocket.on("close", () => {
|
|
54
|
+
if (child && !child.killed)
|
|
55
|
+
child.kill();
|
|
56
|
+
});
|
|
57
|
+
acceptedSocket.pipe(child.stdin);
|
|
58
|
+
child.stdout.pipe(acceptedSocket);
|
|
59
|
+
});
|
|
60
|
+
server.once("error", (error) => {
|
|
61
|
+
close();
|
|
62
|
+
reject(error);
|
|
63
|
+
});
|
|
64
|
+
server.listen(0, "127.0.0.1", () => {
|
|
65
|
+
const address = server?.address();
|
|
66
|
+
if (!address || typeof address === "string") {
|
|
67
|
+
close();
|
|
68
|
+
reject(new Error("Failed to allocate the SSH tunnel port"));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
resolve({
|
|
72
|
+
endpoint: `127.0.0.1:${address.port}`,
|
|
73
|
+
close,
|
|
74
|
+
failureDetail: () => resolveSshFailureDetail(failure, stderr),
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=ssh-tunnel.js.map
|
package/dist/utils/client.js
CHANGED
|
@@ -2,11 +2,13 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import { loadConfig, resolvePaseoHome } from "@getpaseo/server";
|
|
3
3
|
import { buildDaemonWebSocketUrl, buildRelayWebSocketUrl, normalizeHostPort, parseConnectionUri, shouldUseTlsForDefaultHostedRelay, } from "@getpaseo/protocol/daemon-endpoints";
|
|
4
4
|
import { parseConnectionOfferFromUrl, } from "@getpaseo/protocol/connection-offer";
|
|
5
|
+
import { parseSshTransportUri } from "@getpaseo/protocol/ssh-transport";
|
|
5
6
|
import { DaemonClient } from "@getpaseo/client/internal/daemon-client";
|
|
6
7
|
import path from "node:path";
|
|
7
8
|
import { WebSocket } from "ws";
|
|
8
9
|
import { getOrCreateCliClientId } from "./client-id.js";
|
|
9
10
|
import { resolveCliVersion } from "../version.js";
|
|
11
|
+
import { createSshTunnel } from "../ssh/ssh-tunnel.js";
|
|
10
12
|
const DEFAULT_HOST = "localhost:6767";
|
|
11
13
|
const DEFAULT_TIMEOUT = 15000;
|
|
12
14
|
const PID_FILENAME = "paseo.pid";
|
|
@@ -22,7 +24,9 @@ export function buildDaemonConnectionCommandError(options) {
|
|
|
22
24
|
return {
|
|
23
25
|
code: "DAEMON_NOT_RUNNING",
|
|
24
26
|
message: `Cannot connect to daemon at ${host}: ${message}`,
|
|
25
|
-
details: "
|
|
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",
|
|
26
30
|
};
|
|
27
31
|
}
|
|
28
32
|
export function normalizeDaemonHost(raw) {
|
|
@@ -266,6 +270,19 @@ export async function connectToDaemon(options) {
|
|
|
266
270
|
const clientId = await getOrCreateCliClientId();
|
|
267
271
|
const nodeWebSocketFactory = createNodeWebSocketFactory();
|
|
268
272
|
const explicitHost = options?.host ?? process.env.PASEO_HOST;
|
|
273
|
+
if (explicitHost?.trim().startsWith("ssh://")) {
|
|
274
|
+
const target = parseSshTransportUri(explicitHost.trim());
|
|
275
|
+
const tunnel = await createSshTunnel(target);
|
|
276
|
+
const password = resolveDaemonPassword(explicitHost);
|
|
277
|
+
const result = await tryConnectHost(tunnel.endpoint, password, clientId, timeout, nodeWebSocketFactory);
|
|
278
|
+
if ("client" in result)
|
|
279
|
+
return result.client;
|
|
280
|
+
const failure = tunnel.failureDetail();
|
|
281
|
+
tunnel.close();
|
|
282
|
+
if (failure)
|
|
283
|
+
throw new Error(`SSH connection failed: ${failure}`, { cause: result.error });
|
|
284
|
+
throw result.error;
|
|
285
|
+
}
|
|
269
286
|
const offer = parseHostOfferOrNull(explicitHost);
|
|
270
287
|
if (offer) {
|
|
271
288
|
return connectViaRelayOffer(offer, clientId, timeout, nodeWebSocketFactory);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const JSON_OPTION_DESCRIPTION = "Output in JSON format";
|
|
2
|
-
const DAEMON_HOST_OPTION_DESCRIPTION = "Daemon host target: host:port
|
|
2
|
+
const DAEMON_HOST_OPTION_DESCRIPTION = "Daemon host target: host:port, tcp://host:port, or ssh://user@host (default: local socket/pipe, then localhost:6767)";
|
|
3
3
|
export function collectMultiple(value, previous) {
|
|
4
4
|
return previous.concat([value]);
|
|
5
5
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getpaseo/cli",
|
|
3
|
-
"version": "0.7.0-beta.
|
|
3
|
+
"version": "0.7.0-beta.2",
|
|
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
|
-
"@getpaseo/client": "0.7.0-beta.
|
|
32
|
-
"@getpaseo/protocol": "0.7.0-beta.
|
|
33
|
-
"@getpaseo/server": "0.7.0-beta.
|
|
31
|
+
"@getpaseo/client": "0.7.0-beta.2",
|
|
32
|
+
"@getpaseo/protocol": "0.7.0-beta.2",
|
|
33
|
+
"@getpaseo/server": "0.7.0-beta.2",
|
|
34
34
|
"chalk": "^5.3.0",
|
|
35
35
|
"commander": "^12.0.0",
|
|
36
36
|
"mime-types": "^2.1.35",
|