@otto-code/cli 0.7.5 → 0.8.0

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 (76) hide show
  1. package/dist/cli.js +22 -2
  2. package/dist/commands/agent/delete.d.ts +1 -1
  3. package/dist/commands/agent/delete.js +2 -2
  4. package/dist/commands/agent/detach.d.ts +9 -0
  5. package/dist/commands/agent/detach.js +38 -0
  6. package/dist/commands/agent/index.js +9 -1
  7. package/dist/commands/agent/open.d.ts +11 -0
  8. package/dist/commands/agent/open.js +61 -0
  9. package/dist/commands/agent/run.d.ts +35 -0
  10. package/dist/commands/agent/run.js +142 -49
  11. package/dist/commands/agent/send.js +1 -1
  12. package/dist/commands/agent/update.d.ts +33 -0
  13. package/dist/commands/agent/update.js +72 -25
  14. package/dist/commands/clone.d.ts +17 -0
  15. package/dist/commands/clone.js +65 -0
  16. package/dist/commands/daemon/local-daemon.d.ts +15 -1
  17. package/dist/commands/daemon/local-daemon.js +14 -7
  18. package/dist/commands/daemon/pair.js +6 -2
  19. package/dist/commands/heartbeat/index.d.ts +3 -0
  20. package/dist/commands/heartbeat/index.js +139 -0
  21. package/dist/commands/hub/cloud-device-authorization.d.ts +45 -0
  22. package/dist/commands/hub/cloud-device-authorization.js +92 -0
  23. package/dist/commands/hub/device-authorization.d.ts +37 -0
  24. package/dist/commands/hub/device-authorization.js +87 -0
  25. package/dist/commands/hub/index.d.ts +30 -0
  26. package/dist/commands/hub/index.js +85 -0
  27. package/dist/commands/hub-disabled.d.ts +22 -0
  28. package/dist/commands/hub-disabled.js +34 -0
  29. package/dist/commands/onboard.js +6 -2
  30. package/dist/commands/open.d.ts +2 -0
  31. package/dist/commands/open.js +22 -17
  32. package/dist/commands/provider/ls.js +1 -1
  33. package/dist/commands/schedule/create.d.ts +1 -0
  34. package/dist/commands/schedule/create.js +1 -0
  35. package/dist/commands/schedule/index.js +6 -6
  36. package/dist/commands/schedule/inspect.js +3 -0
  37. package/dist/commands/schedule/logs.js +2 -1
  38. package/dist/commands/schedule/ls.js +3 -1
  39. package/dist/commands/schedule/pause.js +2 -1
  40. package/dist/commands/schedule/resume.js +2 -1
  41. package/dist/commands/schedule/run-once.js +2 -1
  42. package/dist/commands/schedule/shared.d.ts +3 -0
  43. package/dist/commands/schedule/shared.js +35 -23
  44. package/dist/commands/schedule/update.js +2 -1
  45. package/dist/commands/script/index.d.ts +3 -0
  46. package/dist/commands/script/index.js +19 -0
  47. package/dist/commands/script/ls.d.ts +6 -0
  48. package/dist/commands/script/ls.js +20 -0
  49. package/dist/commands/script/schema.d.ts +5 -0
  50. package/dist/commands/script/schema.js +13 -0
  51. package/dist/commands/script/shared.d.ts +11 -0
  52. package/dist/commands/script/shared.js +59 -0
  53. package/dist/commands/script/start.d.ts +6 -0
  54. package/dist/commands/script/start.js +23 -0
  55. package/dist/commands/script/stop.d.ts +6 -0
  56. package/dist/commands/script/stop.js +23 -0
  57. package/dist/commands/workspace/archive.d.ts +12 -0
  58. package/dist/commands/workspace/archive.js +41 -0
  59. package/dist/commands/workspace/create.d.ts +49 -0
  60. package/dist/commands/workspace/create.js +114 -0
  61. package/dist/commands/workspace/index.d.ts +3 -0
  62. package/dist/commands/workspace/index.js +30 -0
  63. package/dist/commands/workspace/ls.d.ts +7 -0
  64. package/dist/commands/workspace/ls.js +28 -0
  65. package/dist/commands/workspace/shared.d.ts +12 -0
  66. package/dist/commands/workspace/shared.js +20 -0
  67. package/dist/output/pairing.d.ts +8 -0
  68. package/dist/output/pairing.js +24 -0
  69. package/dist/utils/client.d.ts +9 -0
  70. package/dist/utils/client.js +9 -0
  71. package/dist/utils/duration.d.ts +1 -1
  72. package/dist/utils/duration.js +8 -7
  73. package/dist/utils/paths.js +1 -1
  74. package/dist/utils/provider-model.d.ts +1 -1
  75. package/dist/utils/provider-model.js +1 -1
  76. package/package.json +9 -7
@@ -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 "@otto-code/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.OTTO_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") {
@@ -79,11 +82,14 @@ function resolveScheduleTarget(args) {
79
82
  if (hasExplicitNewAgentOption) {
80
83
  throw {
81
84
  code: "INVALID_TARGET",
82
- message: "--provider/--mode can only be used with a new-agent target",
85
+ message: "--provider/--mode/--thinking can only be used with a new-agent target",
83
86
  details: "Use --target new-agent or omit --target to create a new agent schedule",
84
87
  };
85
88
  }
86
89
  if (targetValue === "self") {
90
+ // COMPAT(scheduleSelfTarget): heartbeat creation moved to `otto heartbeat create`.
91
+ // Added in v0.2.0; remove after 2027-01-17.
92
+ const currentAgentId = process.env.OTTO_AGENT_ID?.trim();
87
93
  if (!currentAgentId) {
88
94
  throw {
89
95
  code: "INVALID_TARGET",
@@ -119,7 +125,14 @@ export function parseScheduleCreateInput(options) {
119
125
  const runOnCreate = resolveRunOnCreate(options.runNow, cadence.type);
120
126
  const targetValue = options.target?.trim();
121
127
  const modeId = options.mode?.trim();
122
- const hasExplicitNewAgentOption = options.provider !== undefined || options.mode !== undefined;
128
+ const thinkingOptionId = options.thinking?.trim();
129
+ if (options.thinking !== undefined && !thinkingOptionId) {
130
+ throw {
131
+ code: "INVALID_THINKING_OPTION",
132
+ message: "--thinking cannot be empty",
133
+ };
134
+ }
135
+ const hasExplicitNewAgentOption = options.provider !== undefined || options.mode !== undefined || options.thinking !== undefined;
123
136
  const createNewAgentTarget = () => {
124
137
  const resolvedProviderModel = resolveProviderAndModel({
125
138
  provider: options.provider,
@@ -131,6 +144,7 @@ export function parseScheduleCreateInput(options) {
131
144
  cwd: cwdInput ?? process.cwd(),
132
145
  ...(resolvedProviderModel.model ? { model: resolvedProviderModel.model } : {}),
133
146
  ...(modeId ? { modeId } : {}),
147
+ ...(thinkingOptionId ? { thinkingOptionId } : {}),
134
148
  },
135
149
  };
136
150
  };
@@ -153,22 +167,8 @@ export function parseScheduleCreateInput(options) {
153
167
  ...(expiresAt ? { expiresAt } : {}),
154
168
  };
155
169
  }
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";
170
+ function resolveRunOnCreate(runNow, _cadenceType) {
171
+ return runNow ?? false;
172
172
  }
173
173
  export function parseScheduleUpdateInput(options) {
174
174
  const id = options.id.trim();
@@ -220,7 +220,7 @@ function parseCadenceFromFlags(every, cron, timezone) {
220
220
  };
221
221
  }
222
222
  if (every !== undefined) {
223
- return { type: "every", everyMs: parseDuration(every) };
223
+ return { type: "cron", expression: compileEveryPresetToCron(every) };
224
224
  }
225
225
  if (cron !== undefined) {
226
226
  return {
@@ -231,6 +231,18 @@ function parseCadenceFromFlags(every, cron, timezone) {
231
231
  }
232
232
  return undefined;
233
233
  }
234
+ export function compileEveryPresetToCron(value) {
235
+ const durationMs = parseDuration(value);
236
+ const cron = everyMsToFiveFieldCron(durationMs);
237
+ if (cron) {
238
+ return cron;
239
+ }
240
+ throw {
241
+ code: "UNREPRESENTABLE_CADENCE",
242
+ message: `${value} cannot be represented faithfully by five-field cron`,
243
+ details: "Use --cron for calendar schedules",
244
+ };
245
+ }
234
246
  function parseTimeZoneFlag(timeZone) {
235
247
  if (timeZone === undefined) {
236
248
  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,3 @@
1
+ import { Command } from "commander";
2
+ export declare function createScriptCommand(): Command;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,19 @@
1
+ import { Command } from "commander";
2
+ import { withOutput } from "../../output/index.js";
3
+ import { addJsonAndDaemonHostOptions } from "../../utils/command-options.js";
4
+ import { runLsCommand } from "./ls.js";
5
+ import { runStartCommand } from "./start.js";
6
+ import { runStopCommand } from "./stop.js";
7
+ function addWorkspaceSelectionOptions(command) {
8
+ return command
9
+ .option("--cwd <path>", "Workspace directory (default: current directory)")
10
+ .option("--workspace <workspace-id>", "Workspace ID (required when a directory has multiple workspaces)");
11
+ }
12
+ export function createScriptCommand() {
13
+ const script = new Command("script").description("Manage configured workspace scripts");
14
+ addJsonAndDaemonHostOptions(addWorkspaceSelectionOptions(script.command("ls").description("List configured workspace scripts"))).action(withOutput(runLsCommand));
15
+ addJsonAndDaemonHostOptions(addWorkspaceSelectionOptions(script.command("start").description("Start a configured workspace script").argument("<name>"))).action(withOutput(runStartCommand));
16
+ addJsonAndDaemonHostOptions(addWorkspaceSelectionOptions(script.command("stop").description("Stop a running workspace script").argument("<name>"))).action(withOutput(runStopCommand));
17
+ return script;
18
+ }
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,6 @@
1
+ import type { Command } from "commander";
2
+ import type { ListResult } from "../../output/index.js";
3
+ import { type WorkspaceScriptCommandOptions } from "./shared.js";
4
+ import { type WorkspaceScriptRow } from "./schema.js";
5
+ export declare function runLsCommand(options: WorkspaceScriptCommandOptions, _command: Command): Promise<ListResult<WorkspaceScriptRow>>;
6
+ //# sourceMappingURL=ls.d.ts.map
@@ -0,0 +1,20 @@
1
+ import { connectWorkspaceScriptClient, resolveWorkspaceScriptWorkspaceId, toWorkspaceScriptCommandError, } from "./shared.js";
2
+ import { workspaceScriptSchema } from "./schema.js";
3
+ export async function runLsCommand(options, _command) {
4
+ const client = await connectWorkspaceScriptClient(options.host);
5
+ try {
6
+ const workspaceId = await resolveWorkspaceScriptWorkspaceId(client, options);
7
+ const payload = await client.listWorkspaceScripts(workspaceId);
8
+ if (payload.error) {
9
+ throw new Error(payload.error);
10
+ }
11
+ return { type: "list", data: payload.scripts ?? [], schema: workspaceScriptSchema };
12
+ }
13
+ catch (error) {
14
+ throw toWorkspaceScriptCommandError("WORKSPACE_SCRIPT_LIST_FAILED", "list workspace scripts", error);
15
+ }
16
+ finally {
17
+ await client.close().catch(() => { });
18
+ }
19
+ }
20
+ //# sourceMappingURL=ls.js.map
@@ -0,0 +1,5 @@
1
+ import type { WorkspaceScriptPayload } from "@otto-code/protocol/messages";
2
+ import type { OutputSchema } from "../../output/index.js";
3
+ export type WorkspaceScriptRow = WorkspaceScriptPayload;
4
+ export declare const workspaceScriptSchema: OutputSchema<WorkspaceScriptRow>;
5
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1,13 @@
1
+ export const workspaceScriptSchema = {
2
+ idField: "scriptName",
3
+ columns: [
4
+ { header: "NAME", field: "scriptName", width: 20 },
5
+ { header: "TYPE", field: "type", width: 9 },
6
+ { header: "LIFECYCLE", field: "lifecycle", width: 10 },
7
+ { header: "HEALTH", field: (script) => script.health ?? "-", width: 10 },
8
+ { header: "PORT", field: (script) => script.port ?? "-", width: 7 },
9
+ { header: "PROXY URL", field: (script) => script.proxyUrl ?? "-", width: 42 },
10
+ { header: "TERMINAL", field: (script) => script.terminalId ?? "-", width: 12 },
11
+ ],
12
+ };
13
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1,11 @@
1
+ import type { DaemonClient } from "@otto-code/client/internal/daemon-client";
2
+ import type { CommandError, CommandOptions } from "../../output/index.js";
3
+ export interface WorkspaceScriptCommandOptions extends CommandOptions {
4
+ host?: string;
5
+ cwd?: string;
6
+ workspace?: string;
7
+ }
8
+ export declare function connectWorkspaceScriptClient(host?: string): Promise<DaemonClient>;
9
+ export declare function resolveWorkspaceScriptWorkspaceId(client: DaemonClient, options: WorkspaceScriptCommandOptions): Promise<string>;
10
+ export declare function toWorkspaceScriptCommandError(code: string, action: string, error: unknown): CommandError;
11
+ //# sourceMappingURL=shared.d.ts.map
@@ -0,0 +1,59 @@
1
+ import { resolve } from "node:path";
2
+ import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
3
+ export async function connectWorkspaceScriptClient(host) {
4
+ const daemonHost = getDaemonHost({ host });
5
+ try {
6
+ const client = await connectToDaemon({ host });
7
+ // COMPAT(workspaceScriptManagement): added in v0.1.105, remove gate after 2027-01-10.
8
+ if (!client.getLastServerInfoMessage()?.features?.workspaceScriptManagement) {
9
+ await client.close().catch(() => { });
10
+ throw {
11
+ code: "DAEMON_UPDATE_REQUIRED",
12
+ message: "Update the host to use workspace script management.",
13
+ };
14
+ }
15
+ return client;
16
+ }
17
+ catch (error) {
18
+ if (error && typeof error === "object" && "code" in error && "message" in error) {
19
+ throw error;
20
+ }
21
+ const message = error instanceof Error ? error.message : String(error);
22
+ throw {
23
+ code: "DAEMON_NOT_RUNNING",
24
+ message: `Cannot connect to daemon at ${daemonHost}: ${message}`,
25
+ details: "Start the daemon with: otto daemon start",
26
+ };
27
+ }
28
+ }
29
+ export async function resolveWorkspaceScriptWorkspaceId(client, options) {
30
+ if (options.workspace) {
31
+ return options.workspace;
32
+ }
33
+ const cwd = resolve(options.cwd ?? process.cwd());
34
+ const payload = await client.fetchWorkspaces({ page: { limit: 200 } });
35
+ const matches = payload.entries.filter((workspace) => resolve(workspace.workspaceDirectory) === cwd);
36
+ if (matches.length === 1) {
37
+ return matches[0].id;
38
+ }
39
+ if (matches.length > 1) {
40
+ throw {
41
+ code: "WORKSPACE_AMBIGUOUS",
42
+ message: `Multiple workspaces use ${cwd}`,
43
+ details: "Pass --workspace <workspace-id> to select one.",
44
+ };
45
+ }
46
+ throw {
47
+ code: "WORKSPACE_NOT_FOUND",
48
+ message: `No Otto workspace found for ${cwd}`,
49
+ details: "Open the directory in Otto first, or pass --workspace <workspace-id>.",
50
+ };
51
+ }
52
+ export function toWorkspaceScriptCommandError(code, action, error) {
53
+ if (error && typeof error === "object" && "code" in error && "message" in error) {
54
+ return error;
55
+ }
56
+ const message = error instanceof Error ? error.message : String(error);
57
+ return { code, message: `Failed to ${action}: ${message}` };
58
+ }
59
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1,6 @@
1
+ import type { Command } from "commander";
2
+ import type { SingleResult } from "../../output/index.js";
3
+ import { type WorkspaceScriptCommandOptions } from "./shared.js";
4
+ import { type WorkspaceScriptRow } from "./schema.js";
5
+ export declare function runStartCommand(scriptName: string, options: WorkspaceScriptCommandOptions, _command: Command): Promise<SingleResult<WorkspaceScriptRow>>;
6
+ //# sourceMappingURL=start.d.ts.map
@@ -0,0 +1,23 @@
1
+ import { connectWorkspaceScriptClient, resolveWorkspaceScriptWorkspaceId, toWorkspaceScriptCommandError, } from "./shared.js";
2
+ import { workspaceScriptSchema } from "./schema.js";
3
+ export async function runStartCommand(scriptName, options, _command) {
4
+ const client = await connectWorkspaceScriptClient(options.host);
5
+ try {
6
+ const workspaceId = await resolveWorkspaceScriptWorkspaceId(client, options);
7
+ const payload = await client.startWorkspaceScriptWithStatus(workspaceId, scriptName);
8
+ if (payload.error || !payload.script) {
9
+ throw {
10
+ code: "WORKSPACE_SCRIPT_START_FAILED",
11
+ message: payload.error ?? `Script '${scriptName}' did not return status metadata`,
12
+ };
13
+ }
14
+ return { type: "single", data: payload.script, schema: workspaceScriptSchema };
15
+ }
16
+ catch (error) {
17
+ throw toWorkspaceScriptCommandError("WORKSPACE_SCRIPT_START_FAILED", "start workspace script", error);
18
+ }
19
+ finally {
20
+ await client.close().catch(() => { });
21
+ }
22
+ }
23
+ //# sourceMappingURL=start.js.map
@@ -0,0 +1,6 @@
1
+ import type { Command } from "commander";
2
+ import type { SingleResult } from "../../output/index.js";
3
+ import { type WorkspaceScriptCommandOptions } from "./shared.js";
4
+ import { type WorkspaceScriptRow } from "./schema.js";
5
+ export declare function runStopCommand(scriptName: string, options: WorkspaceScriptCommandOptions, _command: Command): Promise<SingleResult<WorkspaceScriptRow>>;
6
+ //# sourceMappingURL=stop.d.ts.map
@@ -0,0 +1,23 @@
1
+ import { connectWorkspaceScriptClient, resolveWorkspaceScriptWorkspaceId, toWorkspaceScriptCommandError, } from "./shared.js";
2
+ import { workspaceScriptSchema } from "./schema.js";
3
+ export async function runStopCommand(scriptName, options, _command) {
4
+ const client = await connectWorkspaceScriptClient(options.host);
5
+ try {
6
+ const workspaceId = await resolveWorkspaceScriptWorkspaceId(client, options);
7
+ const payload = await client.stopWorkspaceScript(workspaceId, scriptName);
8
+ if (payload.error || !payload.script) {
9
+ throw {
10
+ code: "WORKSPACE_SCRIPT_STOP_FAILED",
11
+ message: payload.error ?? `Script '${scriptName}' did not return status metadata`,
12
+ };
13
+ }
14
+ return { type: "single", data: payload.script, schema: workspaceScriptSchema };
15
+ }
16
+ catch (error) {
17
+ throw toWorkspaceScriptCommandError("WORKSPACE_SCRIPT_STOP_FAILED", "stop workspace script", error);
18
+ }
19
+ finally {
20
+ await client.close().catch(() => { });
21
+ }
22
+ }
23
+ //# sourceMappingURL=stop.js.map
@@ -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