@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>",
|
|
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
|
-
/**
|
|
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 -
|
|
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", "
|
|
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.
|
|
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 -
|
|
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
|
|
422
|
-
if (
|
|
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
|
|
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
|
|
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
|
|
31
|
-
"@getpaseo/protocol": "0.1.91
|
|
32
|
-
"@getpaseo/server": "0.1.91
|
|
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",
|