@getpaseo/cli 0.1.91-beta.1 → 0.1.91

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.
@@ -1,14 +1,11 @@
1
1
  import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
2
2
  import { collectMultiple } from "../../utils/command-options.js";
3
3
  import { agentRunSchema } from "./run.js";
4
- const IMPORT_PROVIDER_LIST = ["claude", "codex", "opencode", "pi", "acp"];
5
- const IMPORT_PROVIDERS = new Set(IMPORT_PROVIDER_LIST);
6
- const IMPORT_PROVIDER_HELP = IMPORT_PROVIDER_LIST.join(", ");
7
4
  export function addImportOptions(cmd) {
8
5
  return cmd
9
6
  .description("Import an existing provider session as a Paseo agent")
10
7
  .argument("<id>", "Provider session/thread ID to import")
11
- .requiredOption("--provider <provider>", `Agent provider: ${IMPORT_PROVIDER_HELP}`)
8
+ .requiredOption("--provider <provider>", "Agent provider id")
12
9
  .option("--cwd <path>", "Working directory for providers that require it")
13
10
  .option("--label <key=value>", "Add label(s) to the agent (can be used multiple times)", collectMultiple, []);
14
11
  }
@@ -30,13 +27,6 @@ function parseImportProvider(provider) {
30
27
  details: "Usage: paseo import --provider <provider> <id>",
31
28
  };
32
29
  }
33
- if (!IMPORT_PROVIDERS.has(normalizedProvider)) {
34
- throw {
35
- code: "INVALID_PROVIDER",
36
- message: `Unsupported provider: ${normalizedProvider}`,
37
- details: `Supported providers: ${IMPORT_PROVIDER_HELP}`,
38
- };
39
- }
40
30
  return normalizedProvider;
41
31
  }
42
32
  function parseImportLabels(labelFlags) {
@@ -20,7 +20,7 @@ export type AgentLsResult = ListResult<AgentListItem>;
20
20
  export interface AgentLsOptions extends CommandOptions {
21
21
  /** -a: Include archived agents */
22
22
  all?: boolean;
23
- /** Legacy flag retained for CLI compatibility */
23
+ /** -g: List agents across all directories */
24
24
  global?: boolean;
25
25
  /** Filter by specific status */
26
26
  status?: string;
@@ -31,11 +31,13 @@ export interface AgentLsOptions extends CommandOptions {
31
31
  /** Filter by thinking option ID */
32
32
  thinking?: string;
33
33
  }
34
- export declare function buildAgentLsFetchOptions(options: Pick<AgentLsOptions, "all" | "label" | "thinking">): FetchAgentsOptions;
34
+ export declare function buildAgentLsFetchOptions(options: Pick<AgentLsOptions, "all" | "global" | "label" | "thinking">): FetchAgentsOptions;
35
35
  /**
36
36
  * Agent ls command semantics:
37
37
  * - `paseo agent ls` → active non-archived agents
38
- * - `paseo agent ls -a` → include archived agents
38
+ * - `paseo agent ls -g` → global non-archived agents
39
+ * - `paseo agent ls -a` → active agents, including archived
40
+ * - `paseo agent ls -ag` → global agents, including archived
39
41
  */
40
42
  export declare function runLsCommand(options: AgentLsOptions, _command: Command): Promise<AgentLsResult>;
41
43
  export {};
@@ -5,7 +5,7 @@ export function addLsOptions(cmd) {
5
5
  return cmd
6
6
  .description("List agents. By default excludes archived agents.")
7
7
  .option("-a, --all", "Include archived agents")
8
- .option("-g, --global", "Legacy no-op (kept for compatibility)")
8
+ .option("-g, --global", "List agents across all directories")
9
9
  .option("--label <key=value>", "Filter by label (can be used multiple times)", collectMultiple, [])
10
10
  .option("--thinking <id>", "Filter by thinking option ID");
11
11
  }
@@ -104,7 +104,7 @@ export function buildAgentLsFetchOptions(options) {
104
104
  daemonFilter.thinkingOptionId = normalizedThinkingOptionId;
105
105
  }
106
106
  const fetchOptions = {};
107
- if (!options.all) {
107
+ if (!options.global) {
108
108
  fetchOptions.scope = "active";
109
109
  }
110
110
  if (Object.keys(daemonFilter).length > 0) {
@@ -115,7 +115,9 @@ export function buildAgentLsFetchOptions(options) {
115
115
  /**
116
116
  * Agent ls command semantics:
117
117
  * - `paseo agent ls` → active non-archived agents
118
- * - `paseo agent ls -a` → include archived agents
118
+ * - `paseo agent ls -g` → global non-archived agents
119
+ * - `paseo agent ls -a` → active agents, including archived
120
+ * - `paseo agent ls -ag` → global agents, including archived
119
121
  */
120
122
  export async function runLsCommand(options, _command) {
121
123
  const host = getDaemonHost({ host: options.host });
@@ -1,5 +1,5 @@
1
1
  import { spawnSync } from "node:child_process";
2
- import { existsSync, readFileSync } from "node:fs";
2
+ import { existsSync, readFileSync, unlinkSync } from "node:fs";
3
3
  import { createRequire } from "node:module";
4
4
  import path from "node:path";
5
5
  import { loadConfig, resolvePaseoHome, spawnProcess } from "@getpaseo/server";
@@ -239,6 +239,84 @@ async function waitForPidExit(pid, timeoutMs) {
239
239
  }
240
240
  return poll();
241
241
  }
242
+ async function waitForDaemonUnreachable(state, timeoutMs) {
243
+ const host = resolveTcpHostFromListen(state.listen);
244
+ if (!host) {
245
+ return true;
246
+ }
247
+ const reachableHost = host;
248
+ const deadline = Date.now() + timeoutMs;
249
+ async function poll() {
250
+ const client = await tryConnectToDaemon({ host: reachableHost, timeout: 500 });
251
+ if (!client) {
252
+ return true;
253
+ }
254
+ await client.close().catch(() => undefined);
255
+ if (Date.now() >= deadline) {
256
+ const finalClient = await tryConnectToDaemon({
257
+ host: reachableHost,
258
+ timeout: PID_POLL_INTERVAL_MS,
259
+ });
260
+ if (!finalClient) {
261
+ return true;
262
+ }
263
+ await finalClient.close().catch(() => undefined);
264
+ return false;
265
+ }
266
+ await sleep(PID_POLL_INTERVAL_MS);
267
+ return poll();
268
+ }
269
+ return poll();
270
+ }
271
+ function removeStalePidFile(state) {
272
+ if (!state.stalePidFile) {
273
+ return;
274
+ }
275
+ try {
276
+ unlinkSync(state.pidPath);
277
+ }
278
+ catch {
279
+ // Best-effort cleanup only. The successful lifecycle stop is authoritative.
280
+ }
281
+ }
282
+ function createNotRunningStopResult(state, pid, message) {
283
+ return {
284
+ action: "not_running",
285
+ home: state.home,
286
+ pid,
287
+ forced: false,
288
+ message,
289
+ };
290
+ }
291
+ function createStopTimeoutError(state, pid, timeoutMs) {
292
+ if (!state.running) {
293
+ const host = resolveTcpHostFromListen(state.listen);
294
+ return new Error(`Timed out waiting for daemon${host ? ` at ${host}` : ""} to stop after ${Math.ceil(timeoutMs / 1000)}s`);
295
+ }
296
+ return new Error(`Timed out waiting for daemon PID ${pid} to stop after ${Math.ceil(timeoutMs / 1000)}s`);
297
+ }
298
+ async function signalDaemonOwnerForStop(state, pid) {
299
+ if (pid === null) {
300
+ return createNotRunningStopResult(state, null, "Daemon is not running");
301
+ }
302
+ const signaled = await signalProcessTreeOrOwnerSafely(pid, "SIGTERM");
303
+ if (signaled) {
304
+ return null;
305
+ }
306
+ return createNotRunningStopResult(state, pid, "Daemon process was already stopped");
307
+ }
308
+ async function waitForStopAfterRequest(args) {
309
+ const { state, pid, timeoutMs, killTimeoutMs, force } = args;
310
+ let stopped = state.running && pid !== null
311
+ ? await waitForPidExit(pid, timeoutMs)
312
+ : await waitForDaemonUnreachable(state, timeoutMs);
313
+ if (!stopped && force && state.running && pid !== null) {
314
+ await signalProcessTreeOrOwnerSafely(pid, "SIGKILL");
315
+ stopped = await waitForPidExit(pid, killTimeoutMs);
316
+ return { stopped, forced: true };
317
+ }
318
+ return { stopped, forced: false };
319
+ }
242
320
  function getErrorMessage(error) {
243
321
  return error instanceof Error ? error.message : String(error);
244
322
  }
@@ -402,41 +480,31 @@ export async function stopLocalDaemon(options = {}) {
402
480
  const timeoutMs = options.timeoutMs ?? DEFAULT_STOP_TIMEOUT_MS;
403
481
  const killTimeoutMs = options.killTimeoutMs ?? DEFAULT_KILL_TIMEOUT_MS;
404
482
  const state = resolveLocalDaemonState({ home: options.home });
405
- if (!state.pidInfo || !state.running) {
406
- const staleSuffix = state.stalePidFile && state.pidInfo ? ` (stale PID file for ${state.pidInfo.pid})` : "";
407
- return {
408
- action: "not_running",
409
- home: state.home,
410
- pid: state.pidInfo?.pid ?? null,
411
- forced: false,
412
- message: `Daemon is not running${staleSuffix}`,
413
- };
414
- }
415
- const pid = state.pidInfo.pid;
416
483
  const shutdownAttempt = await requestLifecycleShutdown(state, timeoutMs);
417
484
  const lifecycleRequested = shutdownAttempt.requested;
485
+ if (!state.pidInfo || (!state.running && !lifecycleRequested)) {
486
+ const staleSuffix = state.stalePidFile && state.pidInfo ? ` (stale PID file for ${state.pidInfo.pid})` : "";
487
+ return createNotRunningStopResult(state, state.pidInfo?.pid ?? null, `Daemon is not running${staleSuffix}`);
488
+ }
489
+ const pid = state.pidInfo?.pid ?? null;
418
490
  const fallbackMessage = shutdownAttempt.requested ? null : shutdownAttempt.reason;
419
- let forced = false;
420
491
  if (!lifecycleRequested) {
421
- const signaled = await signalProcessTreeOrOwnerSafely(pid, "SIGTERM");
422
- if (!signaled) {
423
- return {
424
- action: "not_running",
425
- home: state.home,
426
- pid,
427
- forced: false,
428
- message: "Daemon process was already stopped",
429
- };
430
- }
431
- }
432
- let stopped = await waitForPidExit(pid, timeoutMs);
433
- if (!stopped && options.force) {
434
- forced = true;
435
- await signalProcessTreeOrOwnerSafely(pid, "SIGKILL");
436
- stopped = await waitForPidExit(pid, killTimeoutMs);
492
+ const notRunningResult = await signalDaemonOwnerForStop(state, pid);
493
+ if (notRunningResult)
494
+ return notRunningResult;
437
495
  }
496
+ const { stopped, forced } = await waitForStopAfterRequest({
497
+ state,
498
+ pid,
499
+ timeoutMs,
500
+ killTimeoutMs,
501
+ force: options.force,
502
+ });
438
503
  if (!stopped) {
439
- throw new Error(`Timed out waiting for daemon PID ${pid} to stop after ${Math.ceil(timeoutMs / 1000)}s`);
504
+ throw createStopTimeoutError(state, pid, timeoutMs);
505
+ }
506
+ if (lifecycleRequested) {
507
+ removeStalePidFile(state);
440
508
  }
441
509
  return {
442
510
  action: "stopped",
@@ -1,6 +1,6 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { getOrCreateServerId, findExecutable, execCommand } from "@getpaseo/server";
3
- import { tryConnectToDaemon } from "../../utils/client.js";
3
+ import { connectToDaemon } from "../../utils/client.js";
4
4
  import { resolveLocalDaemonState, resolveTcpHostFromListen } from "./local-daemon.js";
5
5
  import { resolveNodePathFromPid } from "./runtime-toolchain.js";
6
6
  const require = createRequire(import.meta.url);
@@ -55,7 +55,7 @@ function createStatusSchema(status) {
55
55
  if (item.key === "Connected Daemon") {
56
56
  if (item.value === "reachable")
57
57
  return "green";
58
- if (item.value === "not_probed")
58
+ if (item.value === "not_probed" || item.value === "auth_required")
59
59
  return "yellow";
60
60
  return "red";
61
61
  }
@@ -100,7 +100,7 @@ function toStatusRows(status) {
100
100
  else {
101
101
  rows.push({
102
102
  key: "Agents",
103
- value: "Unavailable (daemon API not reachable)",
103
+ value: `Unavailable (${status.agentsUnavailableReason ?? "daemon API not reachable"})`,
104
104
  });
105
105
  }
106
106
  if (status.note) {
@@ -164,10 +164,41 @@ function resolveOwnerLabel(uid, hostname) {
164
164
  const hostPart = hostname ?? "unknown-host";
165
165
  return `${uidPart}@${hostPart}`;
166
166
  }
167
+ function classifyDaemonAuthProbeFailure(error) {
168
+ if (!(error instanceof Error))
169
+ return null;
170
+ if (error.message === "Password required")
171
+ return "auth_required";
172
+ if (error.message === "Incorrect password")
173
+ return "auth_failed";
174
+ return null;
175
+ }
176
+ function describeDaemonAuthProbeFailure(host, failure) {
177
+ if (failure === "auth_required") {
178
+ return `Daemon is reachable at ${host} but requires a password. Set PASEO_PASSWORD and retry.`;
179
+ }
180
+ return `Daemon is reachable at ${host} but the supplied password was rejected. Check PASEO_PASSWORD and retry.`;
181
+ }
182
+ function describeAgentsUnavailableReason(failure) {
183
+ if (failure === "auth_required")
184
+ return "password required";
185
+ return "incorrect password";
186
+ }
167
187
  async function probeDaemonOverWebsocket(args) {
168
188
  const { host, state } = args;
169
- const client = await tryConnectToDaemon({ host, timeout: 1500 });
170
- if (!client) {
189
+ let client;
190
+ try {
191
+ client = await connectToDaemon({ host, timeout: 1500 });
192
+ }
193
+ catch (error) {
194
+ const authFailure = classifyDaemonAuthProbeFailure(error);
195
+ if (authFailure) {
196
+ return {
197
+ connectedDaemon: authFailure,
198
+ agentsUnavailableReason: describeAgentsUnavailableReason(authFailure),
199
+ note: describeDaemonAuthProbeFailure(host, authFailure),
200
+ };
201
+ }
171
202
  if (state.running) {
172
203
  return {
173
204
  connectedDaemon: "unreachable",
@@ -247,6 +278,7 @@ function applyProbeToStatus(input) {
247
278
  runningAgents: probe.runningAgents !== undefined ? probe.runningAgents : input.runningAgents,
248
279
  idleAgents: probe.idleAgents !== undefined ? probe.idleAgents : input.idleAgents,
249
280
  daemonProviders: probe.daemonProviders ?? input.daemonProviders,
281
+ agentsUnavailableReason: probe.agentsUnavailableReason ?? input.agentsUnavailableReason,
250
282
  note: probe.note ? appendNote(input.note, probe.note) : input.note,
251
283
  };
252
284
  }
@@ -288,6 +320,7 @@ export async function runStatusCommand(options, _command) {
288
320
  let idleAgents = null;
289
321
  let daemonVersion = null;
290
322
  let daemonProviders;
323
+ let agentsUnavailableReason;
291
324
  let note;
292
325
  if (!state.running && state.stalePidFile && state.pidInfo) {
293
326
  localDaemon = "stale_pid";
@@ -303,6 +336,7 @@ export async function runStatusCommand(options, _command) {
303
336
  runningAgents,
304
337
  idleAgents,
305
338
  daemonProviders,
339
+ agentsUnavailableReason,
306
340
  note,
307
341
  } = applyProbeToStatus({
308
342
  probe,
@@ -313,6 +347,7 @@ export async function runStatusCommand(options, _command) {
313
347
  runningAgents,
314
348
  idleAgents,
315
349
  daemonProviders,
350
+ agentsUnavailableReason,
316
351
  note,
317
352
  }));
318
353
  }
@@ -346,6 +381,7 @@ export async function runStatusCommand(options, _command) {
346
381
  daemonVersion,
347
382
  desktopManaged: state.pidInfo?.desktopManaged === true,
348
383
  providers,
384
+ agentsUnavailableReason,
349
385
  note,
350
386
  };
351
387
  return {
@@ -5,7 +5,7 @@ const PROVIDERS = AGENT_PROVIDER_DEFINITIONS.map((def) => ({
5
5
  provider: def.id,
6
6
  label: def.label,
7
7
  status: "available",
8
- enabled: "Enabled",
8
+ enabled: def.enabledByDefault === false ? "Disabled" : "Enabled",
9
9
  defaultMode: def.defaultModeId ?? "-",
10
10
  modes: def.modes.length > 0 ? def.modes.map((m) => m.label).join(", ") : "-",
11
11
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getpaseo/cli",
3
- "version": "0.1.91-beta.1",
3
+ "version": "0.1.91",
4
4
  "description": "Paseo CLI - control your AI coding agents from the command line",
5
5
  "bin": {
6
6
  "paseo": "bin/paseo"
@@ -27,9 +27,9 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@clack/prompts": "^1.0.0",
30
- "@getpaseo/client": "0.1.91-beta.1",
31
- "@getpaseo/protocol": "0.1.91-beta.1",
32
- "@getpaseo/server": "0.1.91-beta.1",
30
+ "@getpaseo/client": "0.1.91",
31
+ "@getpaseo/protocol": "0.1.91",
32
+ "@getpaseo/server": "0.1.91",
33
33
  "chalk": "^5.3.0",
34
34
  "commander": "^12.0.0",
35
35
  "mime-types": "^2.1.35",