@getpaseo/cli 0.6.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.
@@ -78,6 +78,20 @@ function toListItem(agent) {
78
78
  created: relativeTime(agent.createdAt),
79
79
  };
80
80
  }
81
+ function daemonConnectionFailure(host, cause) {
82
+ const reason = cause instanceof Error ? cause.message : String(cause);
83
+ const isSsh = host.trim().startsWith("ssh://");
84
+ return {
85
+ code: "DAEMON_NOT_RUNNING",
86
+ message: `Cannot reach the daemon at ${host}: ${reason}`,
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"),
93
+ };
94
+ }
81
95
  function parseLabelFilters(labels) {
82
96
  const labelFilters = {};
83
97
  for (const labelStr of labels ?? []) {
@@ -126,13 +140,7 @@ export async function runLsCommand(options, _command) {
126
140
  client = await connectToDaemon({ host: options.host });
127
141
  }
128
142
  catch (err) {
129
- const message = err instanceof Error ? err.message : String(err);
130
- const error = {
131
- code: "DAEMON_NOT_RUNNING",
132
- message: `Cannot connect to daemon at ${host}: ${message}`,
133
- details: "Start the daemon with: paseo daemon start\nFor a remote daemon, pass --host <host:port> or set PASEO_HOST.",
134
- };
135
- throw error;
143
+ throw daemonConnectionFailure(host, err);
136
144
  }
137
145
  try {
138
146
  const normalizedThinkingOptionId = options.thinking?.trim();
@@ -5,6 +5,9 @@ import { type PluginScaffold } from "./scaffold.js";
5
5
  interface PluginOptions extends CommandOptions {
6
6
  host?: string;
7
7
  id?: string;
8
+ ref?: string;
9
+ path?: string;
10
+ all?: boolean;
8
11
  }
9
12
  export declare function runPluginInitCommand(directory: string, options: PluginOptions, _command: Command): Promise<SingleResult<PluginScaffold>>;
10
13
  export declare function runPluginListCommand(options: PluginOptions, _command: Command): Promise<ListResult<PluginListItem>>;
@@ -1,8 +1,9 @@
1
1
  import { Command } from "commander";
2
+ import path from "node:path";
2
3
  import { withOutput } from "../../output/index.js";
3
4
  import { addJsonAndDaemonHostOptions, addJsonOption } from "../../utils/command-options.js";
4
5
  import { scaffoldPluginDirectory } from "./scaffold.js";
5
- import { withPluginLogsClient, withPluginManagementClient } from "./shared.js";
6
+ import { withPluginLogsClient, withPluginManagementClient, withPluginSourceClient, } from "./shared.js";
6
7
  const pluginSchema = {
7
8
  idField: "id",
8
9
  columns: [
@@ -28,6 +29,30 @@ const pluginLogsSchema = {
28
29
  { header: "MESSAGE", field: "message", width: 80 },
29
30
  ],
30
31
  };
32
+ function shortCommit(commit) {
33
+ return commit?.slice(0, 12) ?? "-";
34
+ }
35
+ const pluginStatusSchema = {
36
+ idField: "id",
37
+ columns: [
38
+ { header: "PLUGIN", field: "id", width: 20 },
39
+ { header: "SOURCE", field: "source", width: 10 },
40
+ { header: "CURRENT", field: (plugin) => shortCommit(plugin.currentCommit), width: 14 },
41
+ { header: "LATEST", field: (plugin) => shortCommit(plugin.latestCommit), width: 14 },
42
+ { header: "COMMITS", field: (plugin) => String(plugin.commitsBehind ?? 0), width: 8 },
43
+ { header: "REF", field: (plugin) => plugin.ref ?? "-", width: 24 },
44
+ ],
45
+ };
46
+ const pluginUpdateSchema = {
47
+ idField: "id",
48
+ columns: [
49
+ { header: "PLUGIN", field: "id", width: 20 },
50
+ { header: "PREVIOUS", field: (plugin) => shortCommit(plugin.previousCommit), width: 14 },
51
+ { header: "CURRENT", field: (plugin) => shortCommit(plugin.currentCommit), width: 14 },
52
+ { header: "COMMITS", field: (plugin) => String(plugin.commits), width: 8 },
53
+ { header: "UPDATED", field: (plugin) => (plugin.updated ? "yes" : "no"), width: 8 },
54
+ ],
55
+ };
31
56
  export async function runPluginInitCommand(directory, options, _command) {
32
57
  return {
33
58
  type: "single",
@@ -43,10 +68,36 @@ export async function runPluginLogsCommand(pluginId, options, _command) {
43
68
  const data = await withPluginLogsClient(options.host, (client) => client.getPluginLogs(pluginId));
44
69
  return { type: "list", data, schema: pluginLogsSchema };
45
70
  }
46
- async function install(directory, options, _command) {
47
- const data = await withPluginManagementClient(options.host, (client) => client.installDirectoryPlugin(directory, options.id));
71
+ async function install(source, options, _command) {
72
+ const isExplicitPath = path.isAbsolute(source) ||
73
+ source === "." ||
74
+ source === ".." ||
75
+ source.startsWith("./") ||
76
+ source.startsWith("../") ||
77
+ source.startsWith(".\\") ||
78
+ source.startsWith("..\\");
79
+ const canUseLegacyDirectoryInstall = isExplicitPath && !options.ref && !options.path;
80
+ const data = canUseLegacyDirectoryInstall
81
+ ? await withPluginManagementClient(options.host, (client) => client.installDirectoryPlugin(source, options.id))
82
+ : await withPluginSourceClient(options.host, (client) => client.installPluginSource({
83
+ source,
84
+ ...(options.id ? { id: options.id } : {}),
85
+ ...(options.ref ? { ref: options.ref } : {}),
86
+ ...(options.path ? { pluginPath: options.path } : {}),
87
+ }));
48
88
  return { type: "single", data, schema: pluginSchema };
49
89
  }
90
+ async function status(pluginId, options, _command) {
91
+ const data = await withPluginSourceClient(options.host, (client) => client.getPluginSourceStatus(pluginId));
92
+ return { type: "list", data, schema: pluginStatusSchema };
93
+ }
94
+ async function update(pluginId, options, _command) {
95
+ if ((pluginId === undefined) === (options.all !== true)) {
96
+ throw new Error("Choose one plugin ID or pass --all");
97
+ }
98
+ const data = await withPluginSourceClient(options.host, (client) => client.updatePluginSources(pluginId));
99
+ return { type: "list", data, schema: pluginUpdateSchema };
100
+ }
50
101
  async function act(action, pluginId, options) {
51
102
  const data = await withPluginManagementClient(options.host, (client) => client[`${action}Plugin`](pluginId));
52
103
  return { type: "single", data, schema: pluginSchema };
@@ -62,7 +113,7 @@ async function remove(pluginId, options, _command) {
62
113
  return { type: "single", data, schema: pluginSchema };
63
114
  }
64
115
  export function createPluginCommand() {
65
- const plugin = new Command("plugin").description("Manage trusted local plugins");
116
+ const plugin = new Command("plugin").description("Manage trusted plugins");
66
117
  addJsonOption(plugin
67
118
  .command("init")
68
119
  .description("Create a typecheckable local plugin")
@@ -72,11 +123,20 @@ export function createPluginCommand() {
72
123
  addJsonAndDaemonHostOptions(plugin.command("logs").description("Show recent plugin output").argument("<id>")).action(withOutput(runPluginLogsCommand));
73
124
  addJsonAndDaemonHostOptions(plugin
74
125
  .command("install")
75
- .description("Install a local plugin directory")
76
- .argument("<directory>", "Host filesystem directory")
77
- .option("--id <id>", "Runtime plugin ID (defaults to paseo-plugin.json id)")).action(withOutput(install));
126
+ .alias("add")
127
+ .description("Install a plugin from a directory or Git repository")
128
+ .argument("<source>", "Host directory, owner/repo shorthand, or Git URL")
129
+ .option("--id <id>", "Runtime plugin ID (defaults to paseo-plugin.json id)")
130
+ .option("--ref <ref>", "Git branch, tag, or commit")
131
+ .option("--path <path>", "Plugin directory within the repository")).action(withOutput(install));
132
+ addJsonAndDaemonHostOptions(plugin.command("status").description("Check plugin source updates").argument("[id]")).action(withOutput(status));
133
+ addJsonAndDaemonHostOptions(plugin
134
+ .command("update")
135
+ .description("Update a Git-managed plugin")
136
+ .argument("[id]")
137
+ .option("--all", "Update every Git-managed plugin")).action(withOutput(update));
78
138
  for (const action of ["reload", "enable", "disable"]) {
79
- addJsonAndDaemonHostOptions(plugin.command(action).description(`${action} a local plugin`).argument("<id>")).action(withOutput((id, options, _command) => act(action, id, options)));
139
+ addJsonAndDaemonHostOptions(plugin.command(action).description(`${action} a plugin`).argument("<id>")).action(withOutput((id, options, _command) => act(action, id, options)));
80
140
  }
81
141
  addJsonAndDaemonHostOptions(plugin.command("remove").description("Remove plugin configuration").argument("<id>")).action(withOutput(remove));
82
142
  return plugin;
@@ -55,9 +55,50 @@ 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";
101
+ import type { AgentTimelineItem } from "@getpaseo/protocol/agent-types";
61
102
  import type { ZodType, input as ZodInput, output as ZodOutput } from "zod";
62
103
  import type {
63
104
  PluginAttachmentSourceContribution,
@@ -80,10 +121,15 @@ declare module "@getpaseo/plugin" {
80
121
  export interface PluginTheme {
81
122
  readonly colors: {
82
123
  readonly surface0: string;
124
+ readonly surface1: string;
125
+ readonly surface2: string;
126
+ readonly border: string;
83
127
  readonly foreground: string;
84
128
  readonly foregroundMuted: string;
85
129
  readonly accent: string;
86
130
  readonly accentForeground: string;
131
+ readonly statusSuccess: string;
132
+ readonly statusWarning: string;
87
133
  readonly statusDanger: string;
88
134
  };
89
135
  }
@@ -96,6 +142,12 @@ declare module "@getpaseo/plugin" {
96
142
 
97
143
  export interface PluginSurfaceProps extends PluginHostProps {}
98
144
 
145
+ export interface PluginIconProps {
146
+ name: string;
147
+ size?: number;
148
+ color?: string;
149
+ }
150
+
99
151
  export interface PluginWorkspaceSnapshot {
100
152
  readonly id: string;
101
153
  readonly projectId: string;
@@ -142,8 +194,26 @@ declare module "@getpaseo/plugin" {
142
194
  agentId: string;
143
195
  }
144
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
+
145
211
  export type PluginPanelLocation = "workspace" | "explorer";
146
212
  export interface PluginOpenPanelOptions { location?: PluginPanelLocation; }
213
+ export interface PluginClientOpenPanelOptions extends PluginOpenPanelOptions {
214
+ workspaceId: string;
215
+ agentId?: string;
216
+ }
147
217
 
148
218
  export type PluginWorkspacePanelContribution =
149
219
  | { id: string; title: string; icon: string; locations?: readonly PluginPanelLocation[]; context: "workspace"; Component: ComponentType<PluginWorkspacePanelProps> }
@@ -179,6 +249,29 @@ declare module "@getpaseo/plugin" {
179
249
  Component: ComponentType<PluginSurfaceProps>;
180
250
  }
181
251
 
252
+ export type PluginTimelineData = null | boolean | number | string | PluginTimelineData[] | { [key: string]: PluginTimelineData };
253
+ export interface PluginTimelineItem { type: "plugin"; kind: string; version: number; data: PluginTimelineData; }
254
+ export interface PluginTimelineTransformResult { items: PluginTimelineItem[]; }
255
+ export type PluginTimelineTransformerContribution<ItemType extends AgentTimelineItem["type"] = AgentTimelineItem["type"]> =
256
+ ItemType extends AgentTimelineItem["type"]
257
+ ? {
258
+ id: string;
259
+ query: { itemType: ItemType };
260
+ transform(input: { item: Extract<AgentTimelineItem, { type: ItemType }> }): PluginTimelineTransformResult | undefined;
261
+ }
262
+ : never;
263
+ export interface PluginTimelineItemProps<Data = unknown> extends PluginHostProps {
264
+ agentId: string;
265
+ item: { type: "plugin"; kind: string; version: number; data: Data };
266
+ timestamp: Date;
267
+ }
268
+ export interface PluginTimelineRendererContribution<Schema extends ZodType = ZodType> {
269
+ kind: string;
270
+ version: number;
271
+ schema: Schema;
272
+ Component: ComponentType<PluginTimelineItemProps<ZodOutput<Schema>>>;
273
+ }
274
+
182
275
  export interface PluginCommandCapabilities {
183
276
  paseo: PaseoApi;
184
277
  rpc<InputSchema extends ZodType, OutputSchema extends ZodType>(
@@ -205,6 +298,12 @@ declare module "@getpaseo/plugin" {
205
298
  openPanel(id: string, options?: PluginOpenPanelOptions): void;
206
299
  }
207
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
+
208
307
  export type PluginCommandCenterItemContribution =
209
308
  | { id: string; title: string; icon: string; keywords?: readonly string[]; context: "global"; onSelect(context: PluginGlobalCommandContext): void | Promise<void> }
210
309
  | { id: string; title: string; icon: string; keywords?: readonly string[]; context: "workspace"; onSelect(context: PluginWorkspaceCommandContext): void | Promise<void> }
@@ -222,13 +321,18 @@ declare module "@getpaseo/plugin" {
222
321
  addSidebarItem(contribution: PluginSidebarContribution): void;
223
322
  addWorkspacePanel(contribution: PluginWorkspacePanelContribution): void;
224
323
  addCommandCenterItem(contribution: PluginCommandCenterItemContribution): void;
324
+ addClientSide(contribution: PluginClientContribution): void;
225
325
  addAttachmentSource(contribution: PluginAttachmentSourceContribution): void;
226
326
  addTheme(contribution: PluginThemeContribution): void;
327
+ addTimelineTransformer<ItemType extends AgentTimelineItem["type"]>(contribution: PluginTimelineTransformerContribution<ItemType>): void;
328
+ addTimelineRenderer<Schema extends ZodType>(contribution: PluginTimelineRendererContribution<Schema>): void;
227
329
  }
228
330
 
229
331
  export type PluginCleanup = () => void | Promise<void>;
230
332
  export type PluginContribution = (plugin: PluginContext) => PluginCleanup;
231
333
 
334
+ export const Icon: ComponentType<PluginIconProps>;
335
+
232
336
  export function useRpc<InputSchema extends ZodType, OutputSchema extends ZodType>(
233
337
  contract: PluginRpcContract<InputSchema, OutputSchema>,
234
338
  ): (input: ZodInput<InputSchema>) => Promise<ZodOutput<OutputSchema>>;
@@ -307,6 +411,7 @@ export async function scaffoldPluginDirectory(targetDirectory, requestedId) {
307
411
  scripts: { typecheck: "tsc --noEmit" },
308
412
  devDependencies: {
309
413
  "@getpaseo/client": "^0.4.0",
414
+ "@getpaseo/protocol": "^0.6.1",
310
415
  "@tanstack/react-query": "^5.90.11",
311
416
  "@types/react": "~19.2.0",
312
417
  react: "19.1.0",
@@ -1,4 +1,5 @@
1
1
  import type { DaemonClient } from "@getpaseo/client/internal/daemon-client";
2
2
  export declare function withPluginManagementClient<T>(host: string | undefined, run: (client: DaemonClient) => Promise<T>): Promise<T>;
3
3
  export declare function withPluginLogsClient<T>(host: string | undefined, run: (client: DaemonClient) => Promise<T>): Promise<T>;
4
+ export declare function withPluginSourceClient<T>(host: string | undefined, run: (client: DaemonClient) => Promise<T>): Promise<T>;
4
5
  //# sourceMappingURL=shared.d.ts.map
@@ -23,4 +23,8 @@ export async function withPluginLogsClient(host, run) {
23
23
  // COMPAT(pluginLogs): added in v0.4.0, remove gate after 2027-08-16.
24
24
  return withPluginClient(host, "pluginLogs", "Update the host to view plugin logs.", run);
25
25
  }
26
+ export async function withPluginSourceClient(host, run) {
27
+ // COMPAT(pluginGitManagement): added in v0.7.0, remove gate after 2027-08-26.
28
+ return withPluginClient(host, "pluginGitManagement", "Update the host to install and update Git plugins.", run);
29
+ }
26
30
  //# sourceMappingURL=shared.js.map
@@ -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
@@ -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: "Start the daemon with: paseo daemon start",
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 or tcp://host:port?ssl=true&password=secret (default: local socket/pipe, then localhost:6767)";
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.6.1",
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.6.1",
32
- "@getpaseo/protocol": "0.6.1",
33
- "@getpaseo/server": "0.6.1",
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",