@getpaseo/cli 0.1.91-beta.2 → 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",
@@ -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.2",
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.2",
31
- "@getpaseo/protocol": "0.1.91-beta.2",
32
- "@getpaseo/server": "0.1.91-beta.2",
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",