@getpaseo/cli 0.1.110 → 0.2.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.
Files changed (33) hide show
  1. package/dist/cli.js +10 -2
  2. package/dist/commands/agent/detach.d.ts +9 -0
  3. package/dist/commands/agent/detach.js +38 -0
  4. package/dist/commands/agent/index.js +5 -0
  5. package/dist/commands/agent/run.d.ts +28 -0
  6. package/dist/commands/agent/run.js +73 -31
  7. package/dist/commands/heartbeat/index.d.ts +3 -0
  8. package/dist/commands/heartbeat/index.js +139 -0
  9. package/dist/commands/hub/index.d.ts +3 -0
  10. package/dist/commands/hub/index.js +65 -0
  11. package/dist/commands/schedule/index.js +5 -6
  12. package/dist/commands/schedule/inspect.js +3 -0
  13. package/dist/commands/schedule/logs.js +2 -1
  14. package/dist/commands/schedule/ls.js +3 -1
  15. package/dist/commands/schedule/pause.js +2 -1
  16. package/dist/commands/schedule/resume.js +2 -1
  17. package/dist/commands/schedule/run-once.js +2 -1
  18. package/dist/commands/schedule/shared.d.ts +2 -0
  19. package/dist/commands/schedule/shared.js +25 -21
  20. package/dist/commands/schedule/update.js +2 -1
  21. package/dist/commands/workspace/archive.d.ts +12 -0
  22. package/dist/commands/workspace/archive.js +41 -0
  23. package/dist/commands/workspace/create.d.ts +49 -0
  24. package/dist/commands/workspace/create.js +114 -0
  25. package/dist/commands/workspace/index.d.ts +3 -0
  26. package/dist/commands/workspace/index.js +30 -0
  27. package/dist/commands/workspace/ls.d.ts +7 -0
  28. package/dist/commands/workspace/ls.js +28 -0
  29. package/dist/commands/workspace/shared.d.ts +12 -0
  30. package/dist/commands/workspace/shared.js +20 -0
  31. package/dist/utils/duration.d.ts +1 -1
  32. package/dist/utils/duration.js +8 -7
  33. package/package.json +4 -4
package/dist/cli.js CHANGED
@@ -9,6 +9,9 @@ import { createScheduleCommand } from "./commands/schedule/index.js";
9
9
  import { createSpeechCommand } from "./commands/speech/index.js";
10
10
  import { createTerminalCommand } from "./commands/terminal/index.js";
11
11
  import { createWorktreeCommand } from "./commands/worktree/index.js";
12
+ import { createWorkspaceCommand } from "./commands/workspace/index.js";
13
+ import { createHeartbeatCommand } from "./commands/heartbeat/index.js";
14
+ import { createHubCommand } from "./commands/hub/index.js";
12
15
  import { createHooksCommand } from "./commands/hooks.js";
13
16
  import { startCommand as daemonStartCommand } from "./commands/daemon/start.js";
14
17
  import { runStatusCommand as runDaemonStatusCommand } from "./commands/daemon/status.js";
@@ -100,6 +103,7 @@ export function createCli() {
100
103
  program.addCommand(createAgentCommand());
101
104
  // Daemon commands
102
105
  program.addCommand(createDaemonCommand());
106
+ program.addCommand(createHubCommand());
103
107
  // Chat commands
104
108
  program.addCommand(createChatCommand());
105
109
  // Terminal commands
@@ -108,14 +112,18 @@ export function createCli() {
108
112
  program.addCommand(createLoopCommand());
109
113
  // Schedule commands
110
114
  program.addCommand(createScheduleCommand());
115
+ program.addCommand(createHeartbeatCommand());
111
116
  // Permission commands
112
117
  program.addCommand(createPermitCommand());
113
118
  // Provider commands
114
119
  program.addCommand(createProviderCommand());
115
120
  // Speech model commands
116
121
  program.addCommand(createSpeechCommand());
117
- // Worktree commands
118
- program.addCommand(createWorktreeCommand());
122
+ // Workspace commands
123
+ program.addCommand(createWorkspaceCommand());
124
+ // COMPAT(worktreeCli): legacy command alias added before workspace was the product unit.
125
+ // Added in v0.2.0; remove after 2027-01-17.
126
+ program.addCommand(createWorktreeCommand(), { hidden: true });
119
127
  return program;
120
128
  }
121
129
  //# sourceMappingURL=cli.js.map
@@ -0,0 +1,9 @@
1
+ import type { Command } from "commander";
2
+ import type { CommandOptions, SingleResult } from "../../output/index.js";
3
+ interface AgentDetachResult {
4
+ agentId: string;
5
+ status: "detached";
6
+ }
7
+ export declare function runDetachCommand(agentIdArg: string, options: CommandOptions, _command: Command): Promise<SingleResult<AgentDetachResult>>;
8
+ export {};
9
+ //# sourceMappingURL=detach.d.ts.map
@@ -0,0 +1,38 @@
1
+ import { connectToDaemon, getDaemonHost, resolveAgentId } from "../../utils/client.js";
2
+ const detachSchema = {
3
+ idField: "agentId",
4
+ columns: [
5
+ { header: "AGENT ID", field: "agentId" },
6
+ { header: "STATUS", field: "status" },
7
+ ],
8
+ };
9
+ export async function runDetachCommand(agentIdArg, options, _command) {
10
+ const host = getDaemonHost({ host: options.host });
11
+ let client;
12
+ try {
13
+ client = await connectToDaemon({ host: options.host });
14
+ }
15
+ catch (error) {
16
+ const message = error instanceof Error ? error.message : String(error);
17
+ throw {
18
+ code: "DAEMON_NOT_RUNNING",
19
+ message: `Cannot connect to daemon at ${host}: ${message}`,
20
+ };
21
+ }
22
+ try {
23
+ const payload = await client.fetchAgents({ filter: { includeArchived: true } });
24
+ const agentId = resolveAgentId(agentIdArg, payload.entries.map((entry) => entry.agent));
25
+ if (!agentId) {
26
+ throw {
27
+ code: "AGENT_NOT_FOUND",
28
+ message: `Agent not found: ${agentIdArg}`,
29
+ };
30
+ }
31
+ await client.detachAgent(agentId);
32
+ return { type: "single", data: { agentId, status: "detached" }, schema: detachSchema };
33
+ }
34
+ finally {
35
+ await client.close().catch(() => undefined);
36
+ }
37
+ }
38
+ //# sourceMappingURL=detach.js.map
@@ -13,6 +13,7 @@ import { addAttachOptions, runAttachCommand } from "./attach.js";
13
13
  import { addReloadOptions, runReloadCommand } from "./reload.js";
14
14
  import { addImportOptions, runImportCommand } from "./import.js";
15
15
  import { runUpdateCommand } from "./update.js";
16
+ import { runDetachCommand } from "./detach.js";
16
17
  import { withOutput } from "../../output/index.js";
17
18
  import { addDaemonHostOption, addJsonAndDaemonHostOptions, collectMultiple, } from "../../utils/command-options.js";
18
19
  export function createAgentCommand() {
@@ -37,6 +38,10 @@ export function createAgentCommand() {
37
38
  .option("--list", "List available modes for this agent")).action(withOutput(runModeCommand));
38
39
  addJsonAndDaemonHostOptions(addArchiveOptions(agent.command("archive"))).action(withOutput(runArchiveCommand));
39
40
  addJsonAndDaemonHostOptions(addReloadOptions(agent.command("reload"))).action(withOutput(runReloadCommand));
41
+ addJsonAndDaemonHostOptions(agent
42
+ .command("detach")
43
+ .description("Make a subagent independent without stopping or moving it")
44
+ .argument("<id>", "Agent ID, prefix, or name")).action(withOutput(runDetachCommand));
40
45
  addJsonAndDaemonHostOptions(agent
41
46
  .command("update")
42
47
  .description("Update an agent's metadata")
@@ -14,6 +14,7 @@ export interface AgentRunResult {
14
14
  /** Schema for agent run output */
15
15
  export declare const agentRunSchema: OutputSchema<AgentRunResult>;
16
16
  export interface AgentRunOptions extends CommandOptions {
17
+ background?: boolean;
17
18
  detach?: boolean;
18
19
  title?: string;
19
20
  name?: string;
@@ -21,6 +22,7 @@ export interface AgentRunOptions extends CommandOptions {
21
22
  model?: string;
22
23
  thinking?: string;
23
24
  mode?: string;
25
+ isolation?: string;
24
26
  worktree?: string;
25
27
  base?: string;
26
28
  workspace?: string;
@@ -40,5 +42,31 @@ export declare function resolveStructuredResponseMessage(options: {
40
42
  agentId: string;
41
43
  lastMessage: string | null;
42
44
  }): Promise<string | null>;
45
+ interface RunWorkspace {
46
+ id?: string;
47
+ cwd: string;
48
+ }
49
+ export interface RunWorkspaceLookupClient {
50
+ fetchWorkspaces(options: {
51
+ filter: {
52
+ query: string;
53
+ };
54
+ page: {
55
+ limit: number;
56
+ };
57
+ }): Promise<{
58
+ entries: Array<{
59
+ id: string;
60
+ workspaceDirectory: string;
61
+ }>;
62
+ pageInfo: {
63
+ nextCursor: string | null;
64
+ };
65
+ }>;
66
+ }
67
+ export declare function resolveExistingRunWorkspace(client: RunWorkspaceLookupClient, workspaceId: string): Promise<RunWorkspace>;
43
68
  export declare function runRunCommand(prompt: string, options: AgentRunOptions, _command: Command): Promise<SingleResult<AgentRunResult>>;
69
+ export declare function resolveRunCallerAgentId(env?: {
70
+ PASEO_AGENT_ID?: string;
71
+ }): string | undefined;
44
72
  //# sourceMappingURL=run.d.ts.map
@@ -9,25 +9,29 @@ import { collectMultiple } from "../../utils/command-options.js";
9
9
  import { resolveProviderAndModel } from "../../utils/provider-model.js";
10
10
  export { resolveProviderAndModel } from "../../utils/provider-model.js";
11
11
  export function addRunOptions(cmd) {
12
- return cmd
12
+ return (cmd
13
13
  .description("Create and start an agent with a task")
14
14
  .argument("<prompt>", "The task/prompt for the agent")
15
- .option("-d, --detach", "Run in background (detached)")
15
+ .option("-d, --background", "Run in background")
16
+ // COMPAT(detachRunFlag): --detach used to mean background execution, not
17
+ // ownership transfer. Added in v0.2.0; remove after 2027-01-17.
18
+ .addOption(new Option("--detach", "Legacy alias for --background").hideHelp())
16
19
  .option("--title <title>", "Assign a title to the agent")
17
20
  .addOption(new Option("--name <name>", "Hidden alias for --title").hideHelp())
18
21
  .option("--provider <provider>", "Agent provider, or provider/model (e.g. codex or codex/gpt-5.4)")
19
22
  .option("--model <model>", "Model to use (e.g., claude-sonnet-4-20250514, claude-3-5-haiku-20241022)")
20
23
  .option("--thinking <id>", "Thinking option ID to use for this run")
21
24
  .option("--mode <mode>", "Provider-specific mode (e.g., plan, default, bypass)")
22
- .option("--worktree <name>", "Create agent in a new git worktree")
23
- .option("--base <branch>", "Base branch for worktree (default: current branch)")
25
+ .option("--isolation <local|worktree>", "Create a new workspace with this isolation")
26
+ .addOption(new Option("--worktree <name>", "Legacy workspace isolation alias").hideHelp())
27
+ .option("--base <branch>", "Base branch for an isolated workspace")
24
28
  .option("--workspace <id>", "Run in an existing workspace (default: a new workspace is created per run; falls back to $PASEO_WORKSPACE_ID)")
25
29
  .option("--image <path>", "Attach image(s) to the initial prompt (can be used multiple times)", collectMultiple, [])
26
30
  .option("--cwd <path>", "Working directory (default: current)")
27
31
  .option("--env <key=value>", "Set environment variable(s) for the agent process (can be used multiple times)", collectMultiple, [])
28
32
  .option("--label <key=value>", "Add label(s) to the agent (can be used multiple times)", collectMultiple, [])
29
33
  .option("--wait-timeout <duration>", "Maximum time to wait for agent to finish (e.g., 30s, 5m, 1h). Default: no limit")
30
- .option("--output-schema <schema>", "Output JSON matching the provided schema file path or inline JSON schema");
34
+ .option("--output-schema <schema>", "Output JSON matching the provided schema file path or inline JSON schema"));
31
35
  }
32
36
  /** Schema for agent run output */
33
37
  export const agentRunSchema = {
@@ -172,35 +176,48 @@ function validateRunOptions(prompt, options, outputSchema) {
172
176
  details: "Usage: paseo agent run [options] <prompt>",
173
177
  };
174
178
  }
175
- if (options.base && !options.worktree) {
179
+ const createsIsolatedWorkspace = options.isolation === "worktree" || Boolean(options.worktree);
180
+ if (options.isolation && options.isolation !== "local" && options.isolation !== "worktree") {
176
181
  throw {
177
182
  code: "INVALID_OPTIONS",
178
- message: "--base can only be used with --worktree",
179
- details: "Usage: paseo agent run --worktree <name> --base <branch> <prompt>",
183
+ message: `Unsupported workspace isolation: ${options.isolation}`,
184
+ details: "Use --isolation local or --isolation worktree",
180
185
  };
181
186
  }
182
- if (options.worktree && options.workspace) {
187
+ if (options.base && !createsIsolatedWorkspace) {
183
188
  throw {
184
189
  code: "INVALID_OPTIONS",
185
- message: "--worktree and --workspace cannot be combined",
186
- details: "--workspace runs in an existing workspace; --worktree mints a new one",
190
+ message: "--base can only be used with --isolation worktree",
191
+ details: "Usage: paseo agent run --isolation worktree --base <branch> <prompt>",
187
192
  };
188
193
  }
189
- if (options.worktree && !options.workspace && process.env.PASEO_WORKSPACE_ID) {
194
+ if (options.isolation && options.workspace) {
190
195
  throw {
191
196
  code: "INVALID_OPTIONS",
192
- message: "--worktree cannot be combined with an ambient PASEO_WORKSPACE_ID",
193
- details: "PASEO_WORKSPACE_ID selects an existing workspace; --worktree mints a new one. Unset PASEO_WORKSPACE_ID to use --worktree.",
197
+ message: "--isolation and --workspace cannot be combined",
198
+ details: "Select an existing workspace or create a new one with an isolation choice",
194
199
  };
195
200
  }
196
- if (outputSchema && options.detach) {
201
+ // COMPAT(worktreeRunFlag): --worktree implies a new worktree-isolated workspace.
202
+ // Added in v0.2.0; remove after 2027-01-17.
203
+ if (options.worktree && options.workspace) {
197
204
  throw {
198
205
  code: "INVALID_OPTIONS",
199
- message: "--output-schema cannot be used with --detach",
206
+ message: "--worktree and --workspace cannot be combined",
207
+ details: "Use --isolation worktree instead of the legacy --worktree flag",
208
+ };
209
+ }
210
+ if (outputSchema && runsInBackground(options)) {
211
+ throw {
212
+ code: "INVALID_OPTIONS",
213
+ message: "--output-schema cannot be used with --background",
200
214
  details: "Structured output requires waiting for the agent to finish",
201
215
  };
202
216
  }
203
217
  }
218
+ function runsInBackground(options) {
219
+ return Boolean(options.background || options.detach);
220
+ }
204
221
  function parseWaitTimeoutOption(waitTimeout) {
205
222
  if (!waitTimeout)
206
223
  return 0;
@@ -289,27 +306,46 @@ async function connectToDaemonOrThrow(hostOption, host) {
289
306
  };
290
307
  }
291
308
  }
309
+ export async function resolveExistingRunWorkspace(client, workspaceId) {
310
+ const result = await client.fetchWorkspaces({
311
+ filter: { query: workspaceId },
312
+ page: { limit: 200 },
313
+ });
314
+ const workspace = result.entries.find((entry) => entry.id === workspaceId);
315
+ if (workspace) {
316
+ return { id: workspace.id, cwd: workspace.workspaceDirectory };
317
+ }
318
+ throw {
319
+ code: "WORKSPACE_NOT_FOUND",
320
+ message: `Workspace not found: ${workspaceId}`,
321
+ };
322
+ }
292
323
  // Workspace policy for `paseo run`. Precedence:
293
324
  // 1. --workspace <id> -> run in that existing workspace
294
- // 2. $PASEO_WORKSPACE_ID -> exported by workspace terminals
295
- // 3. --worktree <name> -> mint a new worktree-backed workspace
296
- // 4. bare run -> mint a new local-backed workspace for cwd
297
- // --worktree is rejected alongside both --workspace and an ambient
298
- // $PASEO_WORKSPACE_ID (validateRunOptions), so worktree resolution here never
299
- // races an existing-workspace selection.
325
+ // 2. $PASEO_AGENT_ID -> daemon resolves the caller's workspace
326
+ // 3. $PASEO_WORKSPACE_ID -> exported by workspace terminals
327
+ // 4. --isolation <kind> -> mint a new workspace with explicit isolation
328
+ // 5. bare run -> mint a new local-backed workspace for cwd
300
329
  async function resolveRunWorkspace(client, options, cwd) {
301
- // An explicit --worktree mints its own workspace; --workspace and an ambient
302
- // PASEO_WORKSPACE_ID are both rejected alongside --worktree upstream.
303
- const explicit = options.worktree
304
- ? undefined
305
- : options.workspace?.trim() || process.env.PASEO_WORKSPACE_ID?.trim();
330
+ const requestedIsolation = options.isolation ?? (options.worktree ? "worktree" : undefined);
331
+ const explicit = requestedIsolation ? undefined : options.workspace?.trim();
306
332
  if (explicit) {
307
333
  console.error(`Using workspace ${explicit}`);
308
- return { id: explicit, cwd };
334
+ return resolveExistingRunWorkspace(client, explicit);
335
+ }
336
+ if (!requestedIsolation && resolveRunCallerAgentId()) {
337
+ return { cwd };
338
+ }
339
+ const ambientWorkspaceId = requestedIsolation
340
+ ? undefined
341
+ : process.env.PASEO_WORKSPACE_ID?.trim();
342
+ if (ambientWorkspaceId) {
343
+ console.error(`Using workspace ${ambientWorkspaceId}`);
344
+ return resolveExistingRunWorkspace(client, ambientWorkspaceId);
309
345
  }
310
346
  // TODO: thread the run `prompt` as firstAgentContext so workspace-level
311
347
  // title/branch generation picks up the task description (U8/U6 deferred).
312
- const result = options.worktree
348
+ const result = requestedIsolation === "worktree"
313
349
  ? await client.createWorkspace({
314
350
  source: {
315
351
  kind: "worktree",
@@ -357,6 +393,7 @@ export async function runRunCommand(prompt, options, _command) {
357
393
  const requestEnv = Object.keys(env).length > 0 ? env : undefined;
358
394
  const workspace = await resolveRunWorkspace(client, options, cwd);
359
395
  const workspaceId = workspace.id;
396
+ const callerAgentId = resolveRunCallerAgentId();
360
397
  const runCwd = workspace.cwd;
361
398
  if (outputSchema) {
362
399
  let structuredAgent = null;
@@ -366,6 +403,7 @@ export async function runRunCommand(prompt, options, _command) {
366
403
  provider: resolvedProviderModel.provider,
367
404
  cwd: runCwd,
368
405
  workspaceId,
406
+ callerAgentId,
369
407
  title: resolvedTitle,
370
408
  modeId: options.mode,
371
409
  model: resolvedProviderModel.model,
@@ -420,6 +458,7 @@ export async function runRunCommand(prompt, options, _command) {
420
458
  provider: resolvedProviderModel.provider,
421
459
  cwd: runCwd,
422
460
  workspaceId,
461
+ callerAgentId,
423
462
  title: resolvedTitle,
424
463
  modeId: options.mode,
425
464
  model: resolvedProviderModel.model,
@@ -429,8 +468,8 @@ export async function runRunCommand(prompt, options, _command) {
429
468
  env: requestEnv,
430
469
  labels: Object.keys(labels).length > 0 ? labels : undefined,
431
470
  });
432
- // Default run behavior is foreground: wait for completion unless --detach is set.
433
- if (!options.detach) {
471
+ // Default run behavior is foreground: wait for completion unless background execution is set.
472
+ if (!runsInBackground(options)) {
434
473
  const state = await client.waitForFinish(agent.id, waitTimeoutMs);
435
474
  await client.close();
436
475
  const finalAgent = state.final ?? agent;
@@ -461,4 +500,7 @@ export async function runRunCommand(prompt, options, _command) {
461
500
  throw error;
462
501
  }
463
502
  }
503
+ export function resolveRunCallerAgentId(env = process.env) {
504
+ return env.PASEO_AGENT_ID?.trim() || undefined;
505
+ }
464
506
  //# sourceMappingURL=run.js.map
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare function createHeartbeatCommand(): Command;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,139 @@
1
+ import { Command } from "commander";
2
+ import { withOutput } from "../../output/index.js";
3
+ import { addJsonAndDaemonHostOptions } from "../../utils/command-options.js";
4
+ import { parseDuration } from "../../utils/duration.js";
5
+ import { connectScheduleClient, toScheduleCommandError, toScheduleRow, } from "../schedule/shared.js";
6
+ import { scheduleSchema } from "../schedule/schema.js";
7
+ const heartbeatDeleteSchema = {
8
+ idField: "id",
9
+ columns: [
10
+ { header: "ID", field: "id" },
11
+ { header: "STATUS", field: "status" },
12
+ ],
13
+ };
14
+ function requireCallerAgentId() {
15
+ const agentId = process.env.PASEO_AGENT_ID?.trim();
16
+ if (!agentId) {
17
+ throw new Error("Heartbeat commands must run inside a Paseo agent");
18
+ }
19
+ return agentId;
20
+ }
21
+ async function requireOwnedHeartbeat(client, id, agentId) {
22
+ const payload = await client.scheduleInspect({ id });
23
+ if (payload.error || !payload.schedule) {
24
+ throw new Error(payload.error ?? `Heartbeat not found: ${id}`);
25
+ }
26
+ if (payload.schedule.target.type !== "agent" || payload.schedule.target.agentId !== agentId) {
27
+ throw new Error(`Heartbeat ${id} does not belong to agent ${agentId}`);
28
+ }
29
+ }
30
+ async function runCreateHeartbeat(prompt, options, _command) {
31
+ const agentId = requireCallerAgentId();
32
+ const cron = options.cron?.trim();
33
+ if (!cron) {
34
+ throw new Error("--cron is required");
35
+ }
36
+ const { client } = await connectScheduleClient(options.host);
37
+ try {
38
+ const maxRuns = options.maxRuns ? Number.parseInt(options.maxRuns, 10) : undefined;
39
+ if (maxRuns !== undefined && (!Number.isSafeInteger(maxRuns) || maxRuns <= 0)) {
40
+ throw new Error("--max-runs must be a positive integer");
41
+ }
42
+ const payload = await client.scheduleCreate({
43
+ prompt: prompt.trim(),
44
+ cadence: {
45
+ type: "cron",
46
+ expression: cron,
47
+ ...(options.timezone?.trim() ? { timezone: options.timezone.trim() } : {}),
48
+ },
49
+ target: { type: "agent", agentId },
50
+ ...(options.name?.trim() ? { name: options.name.trim() } : {}),
51
+ ...(maxRuns ? { maxRuns } : {}),
52
+ ...(options.expiresIn
53
+ ? { expiresAt: new Date(Date.now() + parseDuration(options.expiresIn)).toISOString() }
54
+ : {}),
55
+ });
56
+ if (payload.error || !payload.schedule) {
57
+ throw new Error(payload.error ?? "Heartbeat creation failed");
58
+ }
59
+ return { type: "single", data: toScheduleRow(payload.schedule), schema: scheduleSchema };
60
+ }
61
+ catch (error) {
62
+ throw toScheduleCommandError("HEARTBEAT_CREATE_FAILED", "create heartbeat", error);
63
+ }
64
+ finally {
65
+ await client.close().catch(() => undefined);
66
+ }
67
+ }
68
+ async function runUpdateHeartbeat(id, options, _command) {
69
+ const agentId = requireCallerAgentId();
70
+ const cron = options.cron?.trim();
71
+ if (!cron) {
72
+ throw new Error("--cron is required");
73
+ }
74
+ const { client } = await connectScheduleClient(options.host);
75
+ try {
76
+ await requireOwnedHeartbeat(client, id, agentId);
77
+ const payload = await client.scheduleUpdate({
78
+ id,
79
+ cadence: {
80
+ type: "cron",
81
+ expression: cron,
82
+ ...(options.timezone?.trim() ? { timezone: options.timezone.trim() } : {}),
83
+ },
84
+ });
85
+ if (payload.error || !payload.schedule) {
86
+ throw new Error(payload.error ?? `Heartbeat update failed: ${id}`);
87
+ }
88
+ return { type: "single", data: toScheduleRow(payload.schedule), schema: scheduleSchema };
89
+ }
90
+ catch (error) {
91
+ throw toScheduleCommandError("HEARTBEAT_UPDATE_FAILED", "update heartbeat", error);
92
+ }
93
+ finally {
94
+ await client.close().catch(() => undefined);
95
+ }
96
+ }
97
+ async function runDeleteHeartbeat(id, options, _command) {
98
+ const agentId = requireCallerAgentId();
99
+ const { client } = await connectScheduleClient(options.host);
100
+ try {
101
+ await requireOwnedHeartbeat(client, id, agentId);
102
+ const payload = await client.scheduleDelete({ id });
103
+ if (payload.error) {
104
+ throw new Error(payload.error);
105
+ }
106
+ return {
107
+ type: "single",
108
+ data: { id: payload.scheduleId, status: "deleted" },
109
+ schema: heartbeatDeleteSchema,
110
+ };
111
+ }
112
+ catch (error) {
113
+ throw toScheduleCommandError("HEARTBEAT_DELETE_FAILED", "delete heartbeat", error);
114
+ }
115
+ finally {
116
+ await client.close().catch(() => undefined);
117
+ }
118
+ }
119
+ export function createHeartbeatCommand() {
120
+ const heartbeat = new Command("heartbeat").description("Manage this agent's heartbeats");
121
+ addJsonAndDaemonHostOptions(heartbeat
122
+ .command("create")
123
+ .description("Create a recurring prompt for this agent")
124
+ .argument("<prompt>", "Prompt to send")
125
+ .requiredOption("--cron <expr>", "Five-field cron cadence")
126
+ .option("--timezone <iana>", "IANA time zone")
127
+ .option("--name <name>", "Heartbeat name")
128
+ .option("--max-runs <n>", "Maximum number of runs")
129
+ .option("--expires-in <duration>", "Time to live")).action(withOutput(runCreateHeartbeat));
130
+ addJsonAndDaemonHostOptions(heartbeat
131
+ .command("update")
132
+ .description("Change a heartbeat cron cadence")
133
+ .argument("<id>", "Heartbeat ID")
134
+ .requiredOption("--cron <expr>", "Five-field cron cadence")
135
+ .option("--timezone <iana>", "IANA time zone")).action(withOutput(runUpdateHeartbeat));
136
+ addJsonAndDaemonHostOptions(heartbeat.command("delete").description("Delete a heartbeat").argument("<id>", "Heartbeat ID")).action(withOutput(runDeleteHeartbeat));
137
+ return heartbeat;
138
+ }
139
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare function createHubCommand(): Command;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,65 @@
1
+ import { Command } from "commander";
2
+ import { withOutput } from "../../output/index.js";
3
+ import { addJsonAndDaemonHostOptions } from "../../utils/command-options.js";
4
+ import { connectToDaemon } from "../../utils/client.js";
5
+ const schema = {
6
+ idField: "state",
7
+ columns: [
8
+ { header: "STATE", field: "state" },
9
+ { header: "HUB", field: "hub" },
10
+ { header: "DAEMON", field: "daemonId" },
11
+ { header: "SCOPES", field: "scopes" },
12
+ { header: "CONNECTED", field: "connectedAt" },
13
+ { header: "ERROR", field: "error" },
14
+ { header: "WARNING", field: "warning" },
15
+ ],
16
+ };
17
+ function result(status, warning) {
18
+ return {
19
+ type: "list",
20
+ data: [
21
+ {
22
+ state: status.state,
23
+ daemonId: status.daemonId,
24
+ hub: status.hubOrigin,
25
+ scopes: status.scopes.join(", "),
26
+ connectedAt: status.connectedAt,
27
+ error: status.lastError,
28
+ warning,
29
+ },
30
+ ],
31
+ schema,
32
+ };
33
+ }
34
+ async function withClient(host, action) {
35
+ const client = await connectToDaemon({ host });
36
+ try {
37
+ return await action(client);
38
+ }
39
+ finally {
40
+ await client.close().catch(() => undefined);
41
+ }
42
+ }
43
+ export function createHubCommand() {
44
+ const hub = new Command("hub").description("Manage this daemon's Paseo Hub relationship");
45
+ addJsonAndDaemonHostOptions(hub.command("connect").argument("<url>").requiredOption("--token <token>")).action(withOutput(async (...args) => {
46
+ const url = args[0];
47
+ const options = args.at(-2);
48
+ return withClient(options.host, async (client) => result((await client.connectHub(url, options.token)).status));
49
+ }));
50
+ addJsonAndDaemonHostOptions(hub.command("status")).action(withOutput(async (...args) => {
51
+ const options = args.at(-2);
52
+ return withClient(options.host, async (client) => result((await client.getHubStatus()).status));
53
+ }));
54
+ addJsonAndDaemonHostOptions(hub
55
+ .command("disconnect")
56
+ .option("--force", "Remove local authority even if the Hub is offline")).action(withOutput(async (...args) => {
57
+ const options = args.at(-2);
58
+ return withClient(options.host, async (client) => {
59
+ const response = await client.disconnectHub(options.force ?? false);
60
+ return result(response.status, response.warning);
61
+ });
62
+ }));
63
+ return hub;
64
+ }
65
+ //# sourceMappingURL=index.js.map
@@ -1,4 +1,4 @@
1
- import { Command } from "commander";
1
+ import { Command, Option } from "commander";
2
2
  import { withOutput } from "../../output/index.js";
3
3
  import { addJsonAndDaemonHostOptions } from "../../utils/command-options.js";
4
4
  import { runCreateCommand } from "./create.js";
@@ -16,16 +16,15 @@ export function createScheduleCommand() {
16
16
  .command("create")
17
17
  .description("Create a schedule")
18
18
  .argument("<prompt>", "Prompt to run on the schedule")
19
- .option("--every <duration>", "Fixed interval cadence (for example: 5m, 1h)")
19
+ .option("--every <duration>", "Cron-compatible cadence preset (for example: 5m, 1h)")
20
20
  .option("--cron <expr>", "Cron cadence expression")
21
21
  .option("--timezone <iana>", "IANA time zone for cron cadence (default: UTC)")
22
22
  .option("--name <name>", "Optional schedule name")
23
- .option("--target <self|new-agent|agent-id>", "Run target")
23
+ .addOption(new Option("--target <target>", "Legacy schedule target").hideHelp())
24
24
  .option("--provider <provider>", "Agent provider, or provider/model (e.g. codex or codex/gpt-5.4)")
25
25
  .option("--mode <mode>", "Provider-specific mode (e.g. claude bypassPermissions, opencode build)")
26
26
  .option("--cwd <path>", "Working directory (default: current; required with --host)")
27
- .option("--run-now", "Fire one immediate run on creation (only with --cron)")
28
- .option("--no-run-now", "Wait the full interval before the first run (only with --every)")
27
+ .option("--run-now", "Fire one immediate run on creation")
29
28
  .option("--max-runs <n>", "Maximum number of runs")
30
29
  .option("--expires-in <duration>", "Time to live for the schedule")).action(withOutput(runCreateCommand));
31
30
  addJsonAndDaemonHostOptions(schedule.command("ls").description("List schedules")).action(withOutput(runLsCommand));
@@ -48,7 +47,7 @@ export function createScheduleCommand() {
48
47
  .command("update")
49
48
  .description("Update an existing schedule in place")
50
49
  .argument("<id>", "Schedule ID")
51
- .option("--every <duration>", "Switch to fixed interval cadence (for example: 5m, 1h)")
50
+ .option("--every <duration>", "Cron-compatible cadence preset (for example: 5m, 1h)")
52
51
  .option("--cron <expr>", "Switch to cron cadence expression")
53
52
  .option("--timezone <iana>", "IANA time zone for cron cadence (requires --cron)")
54
53
  .option("--name <name>", "Rename the schedule (empty string clears the name)")
@@ -7,6 +7,9 @@ export async function runInspectCommand(id, options, _command) {
7
7
  if (payload.error || !payload.schedule) {
8
8
  throw new Error(payload.error ?? `Schedule not found: ${id}`);
9
9
  }
10
+ if (payload.schedule.target.type !== "new-agent") {
11
+ throw new Error(`Schedule not found: ${id}`);
12
+ }
10
13
  const rows = createScheduleInspectRows(payload.schedule);
11
14
  return {
12
15
  type: "list",
@@ -1,8 +1,9 @@
1
1
  import { scheduleLogSchema, toScheduleLogRow } from "./schema.js";
2
- import { connectScheduleClient, toScheduleCommandError, } from "./shared.js";
2
+ import { connectScheduleClient, requireNewAgentSchedule, toScheduleCommandError, } from "./shared.js";
3
3
  export async function runLogsCommand(id, options, _command) {
4
4
  const { client } = await connectScheduleClient(options.host);
5
5
  try {
6
+ await requireNewAgentSchedule(client, id);
6
7
  const payload = await client.scheduleLogs({ id });
7
8
  if (payload.error) {
8
9
  throw new Error(payload.error);
@@ -9,7 +9,9 @@ export async function runLsCommand(options, _command) {
9
9
  }
10
10
  return {
11
11
  type: "list",
12
- data: payload.schedules.map(toScheduleRow),
12
+ data: payload.schedules
13
+ .filter((schedule) => schedule.target.type === "new-agent")
14
+ .map(toScheduleRow),
13
15
  schema: scheduleSchema,
14
16
  };
15
17
  }
@@ -1,8 +1,9 @@
1
1
  import { scheduleSchema } from "./schema.js";
2
- import { connectScheduleClient, toScheduleCommandError, toScheduleRow, } from "./shared.js";
2
+ import { connectScheduleClient, requireNewAgentSchedule, toScheduleCommandError, toScheduleRow, } from "./shared.js";
3
3
  export async function runPauseCommand(id, options, _command) {
4
4
  const { client } = await connectScheduleClient(options.host);
5
5
  try {
6
+ await requireNewAgentSchedule(client, id);
6
7
  const payload = await client.schedulePause({ id });
7
8
  if (payload.error || !payload.schedule) {
8
9
  throw new Error(payload.error ?? `Failed to pause schedule: ${id}`);
@@ -1,8 +1,9 @@
1
1
  import { scheduleSchema } from "./schema.js";
2
- import { connectScheduleClient, toScheduleCommandError, toScheduleRow, } from "./shared.js";
2
+ import { connectScheduleClient, requireNewAgentSchedule, toScheduleCommandError, toScheduleRow, } from "./shared.js";
3
3
  export async function runResumeCommand(id, options, _command) {
4
4
  const { client } = await connectScheduleClient(options.host);
5
5
  try {
6
+ await requireNewAgentSchedule(client, id);
6
7
  const payload = await client.scheduleResume({ id });
7
8
  if (payload.error || !payload.schedule) {
8
9
  throw new Error(payload.error ?? `Failed to resume schedule: ${id}`);
@@ -1,8 +1,9 @@
1
1
  import { scheduleSchema } from "./schema.js";
2
- import { connectScheduleClient, toScheduleCommandError, toScheduleRow, } from "./shared.js";
2
+ import { connectScheduleClient, requireNewAgentSchedule, toScheduleCommandError, toScheduleRow, } from "./shared.js";
3
3
  export async function runRunOnceCommand(id, options, _command) {
4
4
  const { client } = await connectScheduleClient(options.host);
5
5
  try {
6
+ await requireNewAgentSchedule(client, id);
6
7
  const payload = await client.scheduleRunOnce({ id });
7
8
  if (payload.error || !payload.schedule) {
8
9
  throw new Error(payload.error ?? `Failed to run schedule once: ${id}`);
@@ -8,6 +8,7 @@ export declare function connectScheduleClient(host: string | undefined): Promise
8
8
  host: string;
9
9
  }>;
10
10
  export declare function toScheduleCommandError(code: string, action: string, error: unknown): CommandError;
11
+ export declare function requireNewAgentSchedule(client: ScheduleDaemonClient, id: string): Promise<void>;
11
12
  export declare function formatCadence(cadence: ScheduleCadence): string;
12
13
  export declare function formatTarget(target: ScheduleTarget | ScheduleListItem["target"]): string;
13
14
  export declare function formatDurationMs(durationMs: number): string;
@@ -43,6 +44,7 @@ export interface ScheduleUpdateOptionsInput {
43
44
  clearExpires?: boolean;
44
45
  }
45
46
  export declare function parseScheduleUpdateInput(options: ScheduleUpdateOptionsInput): UpdateScheduleInput;
47
+ export declare function compileEveryPresetToCron(value: string): string;
46
48
  export interface ScheduleRow {
47
49
  id: string;
48
50
  name: string | null;
@@ -1,6 +1,7 @@
1
1
  import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
2
2
  import { parseDuration } from "../../utils/duration.js";
3
3
  import { resolveProviderAndModel } from "../../utils/provider-model.js";
4
+ import { everyMsToFiveFieldCron } from "@getpaseo/protocol/schedule/cadence";
4
5
  export async function connectScheduleClient(host) {
5
6
  const resolvedHost = getDaemonHost({ host });
6
7
  try {
@@ -28,6 +29,12 @@ export function toScheduleCommandError(code, action, error) {
28
29
  message: `Failed to ${action}: ${message}`,
29
30
  };
30
31
  }
32
+ export async function requireNewAgentSchedule(client, id) {
33
+ const payload = await client.scheduleInspect({ id });
34
+ if (payload.error || !payload.schedule || payload.schedule.target.type !== "new-agent") {
35
+ throw new Error(payload.error ?? `Schedule not found: ${id}`);
36
+ }
37
+ }
31
38
  export function formatCadence(cadence) {
32
39
  if (cadence.type === "cron") {
33
40
  const timezoneSuffix = cadence.timezone ? ` (${cadence.timezone})` : "";
@@ -66,11 +73,7 @@ export function formatDurationMs(durationMs) {
66
73
  }
67
74
  function resolveScheduleTarget(args) {
68
75
  const { targetValue, hasExplicitNewAgentOption, createNewAgentTarget } = args;
69
- const currentAgentId = process.env.PASEO_AGENT_ID?.trim();
70
76
  if (!targetValue) {
71
- if (currentAgentId && !hasExplicitNewAgentOption) {
72
- return { type: "self", agentId: currentAgentId };
73
- }
74
77
  return createNewAgentTarget();
75
78
  }
76
79
  if (targetValue === "new-agent") {
@@ -84,6 +87,9 @@ function resolveScheduleTarget(args) {
84
87
  };
85
88
  }
86
89
  if (targetValue === "self") {
90
+ // COMPAT(scheduleSelfTarget): heartbeat creation moved to `paseo heartbeat create`.
91
+ // Added in v0.2.0; remove after 2027-01-17.
92
+ const currentAgentId = process.env.PASEO_AGENT_ID?.trim();
87
93
  if (!currentAgentId) {
88
94
  throw {
89
95
  code: "INVALID_TARGET",
@@ -153,22 +159,8 @@ export function parseScheduleCreateInput(options) {
153
159
  ...(expiresAt ? { expiresAt } : {}),
154
160
  };
155
161
  }
156
- function resolveRunOnCreate(runNow, cadenceType) {
157
- if (runNow === true && cadenceType === "every") {
158
- throw {
159
- code: "REDUNDANT_RUN_NOW",
160
- message: "--run-now is redundant with --every (interval schedules already fire on creation)",
161
- details: "Drop --run-now, or use --no-run-now to wait the full interval before the first run",
162
- };
163
- }
164
- if (runNow === false && cadenceType === "cron") {
165
- throw {
166
- code: "REDUNDANT_NO_RUN_NOW",
167
- message: "--no-run-now is redundant with --cron (cron schedules never fire on creation)",
168
- details: "Drop --no-run-now, or use --run-now to fire one immediate run on creation",
169
- };
170
- }
171
- return runNow ?? cadenceType === "every";
162
+ function resolveRunOnCreate(runNow, _cadenceType) {
163
+ return runNow ?? false;
172
164
  }
173
165
  export function parseScheduleUpdateInput(options) {
174
166
  const id = options.id.trim();
@@ -220,7 +212,7 @@ function parseCadenceFromFlags(every, cron, timezone) {
220
212
  };
221
213
  }
222
214
  if (every !== undefined) {
223
- return { type: "every", everyMs: parseDuration(every) };
215
+ return { type: "cron", expression: compileEveryPresetToCron(every) };
224
216
  }
225
217
  if (cron !== undefined) {
226
218
  return {
@@ -231,6 +223,18 @@ function parseCadenceFromFlags(every, cron, timezone) {
231
223
  }
232
224
  return undefined;
233
225
  }
226
+ export function compileEveryPresetToCron(value) {
227
+ const durationMs = parseDuration(value);
228
+ const cron = everyMsToFiveFieldCron(durationMs);
229
+ if (cron) {
230
+ return cron;
231
+ }
232
+ throw {
233
+ code: "UNREPRESENTABLE_CADENCE",
234
+ message: `${value} cannot be represented faithfully by five-field cron`,
235
+ details: "Use --cron for calendar schedules",
236
+ };
237
+ }
234
238
  function parseTimeZoneFlag(timeZone) {
235
239
  if (timeZone === undefined) {
236
240
  return undefined;
@@ -1,5 +1,5 @@
1
1
  import { createScheduleInspectRows, createScheduleInspectSchema, } from "./schema.js";
2
- import { connectScheduleClient, parseScheduleUpdateInput, toScheduleCommandError, } from "./shared.js";
2
+ import { connectScheduleClient, parseScheduleUpdateInput, requireNewAgentSchedule, toScheduleCommandError, } from "./shared.js";
3
3
  export async function runUpdateCommand(id, options, _command) {
4
4
  const input = parseScheduleUpdateInput({
5
5
  id,
@@ -19,6 +19,7 @@ export async function runUpdateCommand(id, options, _command) {
19
19
  });
20
20
  const { client } = await connectScheduleClient(options.host);
21
21
  try {
22
+ await requireNewAgentSchedule(client, id);
22
23
  const payload = await client.scheduleUpdate(input);
23
24
  if (payload.error || !payload.schedule) {
24
25
  throw new Error(payload.error ?? `Failed to update schedule: ${id}`);
@@ -0,0 +1,12 @@
1
+ import type { Command } from "commander";
2
+ import type { SingleResult } from "../../output/index.js";
3
+ interface WorkspaceArchiveResult {
4
+ workspaceId: string;
5
+ status: "archived";
6
+ archivedAt: string;
7
+ }
8
+ export declare function runArchiveCommand(workspaceId: string, options: {
9
+ host?: string;
10
+ }, _command: Command): Promise<SingleResult<WorkspaceArchiveResult>>;
11
+ export {};
12
+ //# sourceMappingURL=archive.d.ts.map
@@ -0,0 +1,41 @@
1
+ import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
2
+ const workspaceArchiveSchema = {
3
+ idField: "workspaceId",
4
+ columns: [
5
+ { header: "WORKSPACE ID", field: "workspaceId", width: 20 },
6
+ { header: "STATUS", field: "status", width: 10 },
7
+ { header: "ARCHIVED AT", field: "archivedAt", width: 26 },
8
+ ],
9
+ };
10
+ export async function runArchiveCommand(workspaceId, options, _command) {
11
+ const host = getDaemonHost({ host: options.host });
12
+ const client = await connectToDaemon({ host: options.host }).catch((error) => {
13
+ const message = error instanceof Error ? error.message : String(error);
14
+ throw {
15
+ code: "DAEMON_NOT_RUNNING",
16
+ message: `Cannot connect to daemon at ${host}: ${message}`,
17
+ };
18
+ });
19
+ try {
20
+ const payload = await client.archiveWorkspace(workspaceId);
21
+ if (payload.error) {
22
+ throw new Error(payload.error);
23
+ }
24
+ if (!payload.archivedAt) {
25
+ throw new Error("Workspace archive did not return an archive timestamp");
26
+ }
27
+ return {
28
+ type: "single",
29
+ data: { workspaceId, status: "archived", archivedAt: payload.archivedAt },
30
+ schema: workspaceArchiveSchema,
31
+ };
32
+ }
33
+ catch (error) {
34
+ const message = error instanceof Error ? error.message : String(error);
35
+ throw { code: "WORKSPACE_ARCHIVE_FAILED", message };
36
+ }
37
+ finally {
38
+ await client.close().catch(() => undefined);
39
+ }
40
+ }
41
+ //# sourceMappingURL=archive.js.map
@@ -0,0 +1,49 @@
1
+ import type { Command } from "commander";
2
+ import type { CommandOptions, SingleResult } from "../../output/index.js";
3
+ import { type WorkspaceRow } from "./shared.js";
4
+ export interface WorkspaceCreateOptions extends CommandOptions {
5
+ isolation?: string;
6
+ path?: string;
7
+ project?: string;
8
+ title?: string;
9
+ mode?: string;
10
+ worktreeSlug?: string;
11
+ newBranch?: string;
12
+ base?: string;
13
+ branch?: string;
14
+ prNumber?: string;
15
+ forge?: string;
16
+ }
17
+ export declare function buildWorkspaceSource(options: WorkspaceCreateOptions): {
18
+ projectId?: string | undefined;
19
+ kind: "directory";
20
+ path: string;
21
+ } | {
22
+ baseBranch?: string | undefined;
23
+ branchName?: string | undefined;
24
+ action: "branch-off";
25
+ kind: "worktree";
26
+ cwd?: string;
27
+ projectId?: string;
28
+ worktreeSlug?: string;
29
+ } | {
30
+ action: "checkout";
31
+ refName: string;
32
+ kind: "worktree";
33
+ cwd?: string;
34
+ projectId?: string;
35
+ worktreeSlug?: string;
36
+ } | {
37
+ action: "checkout";
38
+ checkoutSource: {
39
+ number: number;
40
+ forge?: string | undefined;
41
+ kind: "change_request";
42
+ };
43
+ kind: "worktree";
44
+ cwd?: string;
45
+ projectId?: string;
46
+ worktreeSlug?: string;
47
+ };
48
+ export declare function runCreateCommand(options: WorkspaceCreateOptions, _command: Command): Promise<SingleResult<WorkspaceRow>>;
49
+ //# sourceMappingURL=create.d.ts.map
@@ -0,0 +1,114 @@
1
+ import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
2
+ import { toWorkspaceRow, workspaceSchema } from "./shared.js";
3
+ function assertOptionsAbsent(values, message) {
4
+ if (values.some((value) => value !== undefined)) {
5
+ throw new Error(message);
6
+ }
7
+ }
8
+ function buildLocalWorkspaceSource(options, path) {
9
+ assertOptionsAbsent([
10
+ options.mode,
11
+ options.worktreeSlug,
12
+ options.newBranch,
13
+ options.base,
14
+ options.branch,
15
+ options.prNumber,
16
+ options.forge,
17
+ ], "Worktree options require --isolation worktree");
18
+ return {
19
+ kind: "directory",
20
+ path,
21
+ ...(options.project ? { projectId: options.project } : {}),
22
+ };
23
+ }
24
+ function buildBranchOffSource(options, source) {
25
+ assertOptionsAbsent([options.branch, options.prNumber, options.forge], "--branch, --pr-number, and --forge require a checkout mode");
26
+ return {
27
+ ...source,
28
+ action: "branch-off",
29
+ ...(options.newBranch ? { branchName: options.newBranch } : {}),
30
+ ...(options.base ? { baseBranch: options.base } : {}),
31
+ };
32
+ }
33
+ function buildBranchCheckoutSource(options, source) {
34
+ if (!options.branch) {
35
+ throw new Error("--branch is required for --mode checkout-branch");
36
+ }
37
+ assertOptionsAbsent([options.newBranch, options.base, options.prNumber, options.forge], "--new-branch, --base, --pr-number, and --forge are not valid for --mode checkout-branch");
38
+ return { ...source, action: "checkout", refName: options.branch };
39
+ }
40
+ function buildPullRequestCheckoutSource(options, source) {
41
+ if (options.prNumber === undefined || options.prNumber === "") {
42
+ throw new Error("--pr-number is required for --mode checkout-pr");
43
+ }
44
+ const prNumber = Number(options.prNumber);
45
+ if (!Number.isInteger(prNumber) || prNumber <= 0) {
46
+ throw new Error("--pr-number must be a positive integer");
47
+ }
48
+ assertOptionsAbsent([options.newBranch, options.base, options.branch], "--new-branch, --base, and --branch are not valid for --mode checkout-pr");
49
+ return {
50
+ ...source,
51
+ action: "checkout",
52
+ checkoutSource: {
53
+ kind: "change_request",
54
+ ...(options.forge ? { forge: options.forge } : {}),
55
+ number: prNumber,
56
+ },
57
+ };
58
+ }
59
+ function buildWorktreeWorkspaceSource(options, path) {
60
+ const source = {
61
+ kind: "worktree",
62
+ ...(path ? { cwd: path } : {}),
63
+ ...(options.project ? { projectId: options.project } : {}),
64
+ ...(options.worktreeSlug ? { worktreeSlug: options.worktreeSlug } : {}),
65
+ };
66
+ switch (options.mode ?? "branch-off") {
67
+ case "branch-off":
68
+ return buildBranchOffSource(options, source);
69
+ case "checkout-branch":
70
+ return buildBranchCheckoutSource(options, source);
71
+ case "checkout-pr":
72
+ return buildPullRequestCheckoutSource(options, source);
73
+ default:
74
+ throw new Error(`Unsupported worktree mode: ${String(options.mode)}`);
75
+ }
76
+ }
77
+ export function buildWorkspaceSource(options) {
78
+ if (options.isolation === "local") {
79
+ return buildLocalWorkspaceSource(options, options.path ?? process.cwd());
80
+ }
81
+ if (options.isolation === "worktree") {
82
+ const sourcePath = options.path ?? (options.project ? undefined : process.cwd());
83
+ return buildWorktreeWorkspaceSource(options, sourcePath);
84
+ }
85
+ throw new Error(`Unsupported workspace isolation: ${String(options.isolation)}`);
86
+ }
87
+ export async function runCreateCommand(options, _command) {
88
+ const host = getDaemonHost({ host: options.host });
89
+ const client = await connectToDaemon({ host: options.host }).catch((error) => {
90
+ const message = error instanceof Error ? error.message : String(error);
91
+ throw {
92
+ code: "DAEMON_NOT_RUNNING",
93
+ message: `Cannot connect to daemon at ${host}: ${message}`,
94
+ };
95
+ });
96
+ try {
97
+ const payload = await client.createWorkspace({
98
+ source: buildWorkspaceSource(options),
99
+ ...(options.title ? { title: options.title } : {}),
100
+ });
101
+ if (!payload.workspace) {
102
+ throw new Error(payload.error ?? "Workspace creation failed");
103
+ }
104
+ return { type: "single", data: toWorkspaceRow(payload.workspace), schema: workspaceSchema };
105
+ }
106
+ catch (error) {
107
+ const message = error instanceof Error ? error.message : String(error);
108
+ throw { code: "WORKSPACE_CREATE_FAILED", message };
109
+ }
110
+ finally {
111
+ await client.close().catch(() => undefined);
112
+ }
113
+ }
114
+ //# sourceMappingURL=create.js.map
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare function createWorkspaceCommand(): Command;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,30 @@
1
+ import { Command } from "commander";
2
+ import { withOutput } from "../../output/index.js";
3
+ import { addJsonAndDaemonHostOptions } from "../../utils/command-options.js";
4
+ import { runArchiveCommand } from "./archive.js";
5
+ import { runCreateCommand } from "./create.js";
6
+ import { runLsCommand } from "./ls.js";
7
+ export function createWorkspaceCommand() {
8
+ const workspace = new Command("workspace").description("Manage workspaces");
9
+ addJsonAndDaemonHostOptions(workspace
10
+ .command("create")
11
+ .description("Create a workspace")
12
+ .requiredOption("--isolation <local|worktree>", "Workspace isolation")
13
+ .option("--path <path>", "Local directory or source checkout (default: current)")
14
+ .option("--project <id>", "Existing project id")
15
+ .option("--title <title>", "Workspace title")
16
+ .option("--mode <mode>", "Worktree mode: branch-off, checkout-branch, or checkout-pr (default: branch-off)")
17
+ .option("--worktree-slug <slug>", "Managed worktree path slug")
18
+ .option("--new-branch <name>", "New branch name (--mode branch-off)")
19
+ .option("--base <ref>", "Base ref (--mode branch-off)")
20
+ .option("--branch <name>", "Existing branch (--mode checkout-branch)")
21
+ .option("--pr-number <n>", "Pull request or change request number (--mode checkout-pr)")
22
+ .option("--forge <forge>", "Forge for --mode checkout-pr (default: source checkout)")).action(withOutput(runCreateCommand));
23
+ addJsonAndDaemonHostOptions(workspace.command("ls").description("List active workspaces")).action(withOutput(runLsCommand));
24
+ addJsonAndDaemonHostOptions(workspace
25
+ .command("archive")
26
+ .description("Archive a workspace and everything it owns")
27
+ .argument("<workspace-id>", "Workspace id")).action(withOutput(runArchiveCommand));
28
+ return workspace;
29
+ }
30
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ import type { Command } from "commander";
2
+ import type { ListResult } from "../../output/index.js";
3
+ import { type WorkspaceRow } from "./shared.js";
4
+ export declare function runLsCommand(options: {
5
+ host?: string;
6
+ }, _command: Command): Promise<ListResult<WorkspaceRow>>;
7
+ //# sourceMappingURL=ls.d.ts.map
@@ -0,0 +1,28 @@
1
+ import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
2
+ import { toWorkspaceRow, workspaceSchema } from "./shared.js";
3
+ export async function runLsCommand(options, _command) {
4
+ const host = getDaemonHost({ host: options.host });
5
+ const client = await connectToDaemon({ host: options.host }).catch((error) => {
6
+ const message = error instanceof Error ? error.message : String(error);
7
+ throw {
8
+ code: "DAEMON_NOT_RUNNING",
9
+ message: `Cannot connect to daemon at ${host}: ${message}`,
10
+ };
11
+ });
12
+ try {
13
+ const workspaces = [];
14
+ let cursor;
15
+ do {
16
+ const payload = await client.fetchWorkspaces({
17
+ page: { limit: 200, ...(cursor ? { cursor } : {}) },
18
+ });
19
+ workspaces.push(...payload.entries.map(toWorkspaceRow));
20
+ cursor = payload.pageInfo.nextCursor ?? undefined;
21
+ } while (cursor);
22
+ return { type: "list", data: workspaces, schema: workspaceSchema };
23
+ }
24
+ finally {
25
+ await client.close().catch(() => undefined);
26
+ }
27
+ }
28
+ //# sourceMappingURL=ls.js.map
@@ -0,0 +1,12 @@
1
+ import type { WorkspaceDescriptorPayload } from "@getpaseo/protocol/messages";
2
+ import type { OutputSchema } from "../../output/index.js";
3
+ export interface WorkspaceRow {
4
+ workspaceId: string;
5
+ project: string;
6
+ name: string;
7
+ isolation: "local" | "worktree";
8
+ cwd: string;
9
+ }
10
+ export declare const workspaceSchema: OutputSchema<WorkspaceRow>;
11
+ export declare function toWorkspaceRow(workspace: WorkspaceDescriptorPayload): WorkspaceRow;
12
+ //# sourceMappingURL=shared.d.ts.map
@@ -0,0 +1,20 @@
1
+ export const workspaceSchema = {
2
+ idField: "workspaceId",
3
+ columns: [
4
+ { header: "WORKSPACE ID", field: "workspaceId", width: 20 },
5
+ { header: "PROJECT", field: "project", width: 20 },
6
+ { header: "NAME", field: "name", width: 22 },
7
+ { header: "ISOLATION", field: "isolation", width: 10 },
8
+ { header: "CWD", field: "cwd", width: 42 },
9
+ ],
10
+ };
11
+ export function toWorkspaceRow(workspace) {
12
+ return {
13
+ workspaceId: workspace.id,
14
+ project: workspace.projectDisplayName,
15
+ name: workspace.name,
16
+ isolation: workspace.workspaceKind === "worktree" ? "worktree" : "local",
17
+ cwd: workspace.workspaceDirectory,
18
+ };
19
+ }
20
+ //# sourceMappingURL=shared.js.map
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Parse duration string to milliseconds.
3
- * Supports formats like: 5m, 30s, 1h, 2h30m, 90, etc.
3
+ * Supports formats like: 5m, 30s, 1h, 2h30m, 1d, 90, etc.
4
4
  * If no unit is specified, assumes seconds.
5
5
  */
6
6
  export declare function parseDuration(input: string): number;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Parse duration string to milliseconds.
3
- * Supports formats like: 5m, 30s, 1h, 2h30m, 90, etc.
3
+ * Supports formats like: 5m, 30s, 1h, 2h30m, 1d, 90, etc.
4
4
  * If no unit is specified, assumes seconds.
5
5
  */
6
6
  export function parseDuration(input) {
@@ -9,13 +9,14 @@ export function parseDuration(input) {
9
9
  if (/^\d+$/.test(trimmed)) {
10
10
  return parseInt(trimmed, 10) * 1000;
11
11
  }
12
+ if (!/^(?:\d+[smhd])+$/.test(trimmed)) {
13
+ throw new Error(`Invalid duration format: ${input}. Use formats like: 5m, 30s, 1h, 2h30m, 1d`);
14
+ }
12
15
  // Parse duration with units
13
16
  let totalMs = 0;
14
- const regex = /(\d+)([smh])/g;
17
+ const regex = /(\d+)([smhd])/g;
15
18
  let match;
16
- let hasMatch = false;
17
19
  while ((match = regex.exec(trimmed)) !== null) {
18
- hasMatch = true;
19
20
  const value = parseInt(match[1], 10);
20
21
  const unit = match[2];
21
22
  switch (unit) {
@@ -28,11 +29,11 @@ export function parseDuration(input) {
28
29
  case "h":
29
30
  totalMs += value * 60 * 60 * 1000;
30
31
  break;
32
+ case "d":
33
+ totalMs += value * 24 * 60 * 60 * 1000;
34
+ break;
31
35
  }
32
36
  }
33
- if (!hasMatch) {
34
- throw new Error(`Invalid duration format: ${input}. Use formats like: 5m, 30s, 1h, 2h30m`);
35
- }
36
37
  return totalMs;
37
38
  }
38
39
  //# sourceMappingURL=duration.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getpaseo/cli",
3
- "version": "0.1.110",
3
+ "version": "0.2.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"
@@ -27,9 +27,9 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@clack/prompts": "^1.0.0",
30
- "@getpaseo/client": "0.1.110",
31
- "@getpaseo/protocol": "0.1.110",
32
- "@getpaseo/server": "0.1.110",
30
+ "@getpaseo/client": "0.2.0-beta.2",
31
+ "@getpaseo/protocol": "0.2.0-beta.2",
32
+ "@getpaseo/server": "0.2.0-beta.2",
33
33
  "chalk": "^5.3.0",
34
34
  "commander": "^12.0.0",
35
35
  "mime-types": "^2.1.35",