@otto-code/cli 0.7.5 → 0.7.6

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 (70) hide show
  1. package/dist/cli.js +22 -2
  2. package/dist/commands/agent/delete.js +1 -1
  3. package/dist/commands/agent/detach.d.ts +9 -0
  4. package/dist/commands/agent/detach.js +38 -0
  5. package/dist/commands/agent/index.js +9 -1
  6. package/dist/commands/agent/open.d.ts +11 -0
  7. package/dist/commands/agent/open.js +61 -0
  8. package/dist/commands/agent/run.d.ts +35 -0
  9. package/dist/commands/agent/run.js +142 -49
  10. package/dist/commands/agent/update.d.ts +33 -0
  11. package/dist/commands/agent/update.js +72 -25
  12. package/dist/commands/clone.d.ts +17 -0
  13. package/dist/commands/clone.js +65 -0
  14. package/dist/commands/daemon/local-daemon.d.ts +15 -1
  15. package/dist/commands/daemon/local-daemon.js +12 -5
  16. package/dist/commands/daemon/pair.js +6 -2
  17. package/dist/commands/heartbeat/index.d.ts +3 -0
  18. package/dist/commands/heartbeat/index.js +139 -0
  19. package/dist/commands/hub/cloud-device-authorization.d.ts +45 -0
  20. package/dist/commands/hub/cloud-device-authorization.js +92 -0
  21. package/dist/commands/hub/device-authorization.d.ts +37 -0
  22. package/dist/commands/hub/device-authorization.js +87 -0
  23. package/dist/commands/hub/index.d.ts +30 -0
  24. package/dist/commands/hub/index.js +85 -0
  25. package/dist/commands/hub-disabled.d.ts +22 -0
  26. package/dist/commands/hub-disabled.js +34 -0
  27. package/dist/commands/onboard.js +6 -2
  28. package/dist/commands/open.d.ts +2 -0
  29. package/dist/commands/open.js +22 -17
  30. package/dist/commands/schedule/create.d.ts +1 -0
  31. package/dist/commands/schedule/create.js +1 -0
  32. package/dist/commands/schedule/index.js +6 -6
  33. package/dist/commands/schedule/inspect.js +3 -0
  34. package/dist/commands/schedule/logs.js +2 -1
  35. package/dist/commands/schedule/ls.js +3 -1
  36. package/dist/commands/schedule/pause.js +2 -1
  37. package/dist/commands/schedule/resume.js +2 -1
  38. package/dist/commands/schedule/run-once.js +2 -1
  39. package/dist/commands/schedule/shared.d.ts +3 -0
  40. package/dist/commands/schedule/shared.js +35 -23
  41. package/dist/commands/schedule/update.js +2 -1
  42. package/dist/commands/script/index.d.ts +3 -0
  43. package/dist/commands/script/index.js +19 -0
  44. package/dist/commands/script/ls.d.ts +6 -0
  45. package/dist/commands/script/ls.js +20 -0
  46. package/dist/commands/script/schema.d.ts +5 -0
  47. package/dist/commands/script/schema.js +13 -0
  48. package/dist/commands/script/shared.d.ts +11 -0
  49. package/dist/commands/script/shared.js +59 -0
  50. package/dist/commands/script/start.d.ts +6 -0
  51. package/dist/commands/script/start.js +23 -0
  52. package/dist/commands/script/stop.d.ts +6 -0
  53. package/dist/commands/script/stop.js +23 -0
  54. package/dist/commands/workspace/archive.d.ts +12 -0
  55. package/dist/commands/workspace/archive.js +41 -0
  56. package/dist/commands/workspace/create.d.ts +49 -0
  57. package/dist/commands/workspace/create.js +114 -0
  58. package/dist/commands/workspace/index.d.ts +3 -0
  59. package/dist/commands/workspace/index.js +30 -0
  60. package/dist/commands/workspace/ls.d.ts +7 -0
  61. package/dist/commands/workspace/ls.js +28 -0
  62. package/dist/commands/workspace/shared.d.ts +12 -0
  63. package/dist/commands/workspace/shared.js +20 -0
  64. package/dist/output/pairing.d.ts +8 -0
  65. package/dist/output/pairing.js +24 -0
  66. package/dist/utils/client.d.ts +9 -0
  67. package/dist/utils/client.js +9 -0
  68. package/dist/utils/duration.d.ts +1 -1
  69. package/dist/utils/duration.js +8 -7
  70. package/package.json +9 -7
@@ -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
@@ -0,0 +1,12 @@
1
+ import type { WorkspaceDescriptorPayload } from "@otto-code/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
@@ -0,0 +1,8 @@
1
+ interface PairingInstructions {
2
+ url: string;
3
+ qr: string | null;
4
+ columns?: number;
5
+ }
6
+ export declare function formatPairingInstructions({ url, qr, columns }: PairingInstructions): string;
7
+ export {};
8
+ //# sourceMappingURL=pairing.d.ts.map
@@ -0,0 +1,24 @@
1
+ const ANSI_PATTERN = new RegExp(`${String.fromCharCode(0x1b)}\\[[0-9;]*m`, "g");
2
+ function visibleWidth(value) {
3
+ return Math.max(...value
4
+ .replace(ANSI_PATTERN, "")
5
+ .split("\n")
6
+ .map((line) => line.length));
7
+ }
8
+ function formatQr(qr, columns) {
9
+ if (!qr) {
10
+ return "QR code is unavailable. Use the pairing link below.";
11
+ }
12
+ if (columns === undefined) {
13
+ return "QR code not shown because terminal width could not be detected.";
14
+ }
15
+ const width = visibleWidth(qr);
16
+ if (columns <= width) {
17
+ return `QR code not shown. Resize the terminal to at least ${width + 1} columns, then run this command again.`;
18
+ }
19
+ return qr;
20
+ }
21
+ export function formatPairingInstructions({ url, qr, columns }) {
22
+ return `\nScan to pair:\n${formatQr(qr, columns)}\n\nPairing link:\n${url}\n`;
23
+ }
24
+ //# sourceMappingURL=pairing.js.map
@@ -3,6 +3,11 @@ export interface ConnectOptions {
3
3
  host?: string;
4
4
  timeout?: number;
5
5
  }
6
+ export interface DaemonConnectionCommandError {
7
+ code: "DAEMON_NOT_RUNNING";
8
+ message: string;
9
+ details: string;
10
+ }
6
11
  type DaemonTarget = {
7
12
  type: "tcp";
8
13
  url: string;
@@ -15,6 +20,10 @@ type DaemonTarget = {
15
20
  * Get the daemon host from environment or options
16
21
  */
17
22
  export declare function getDaemonHost(options?: ConnectOptions): string;
23
+ export declare function buildDaemonConnectionCommandError(options: {
24
+ host?: string;
25
+ error: unknown;
26
+ }): DaemonConnectionCommandError;
18
27
  export declare function normalizeDaemonHost(raw: string): string | null;
19
28
  export declare function resolveDefaultDaemonHost(env?: NodeJS.ProcessEnv): string;
20
29
  export declare function resolveDefaultDaemonHosts(env?: NodeJS.ProcessEnv): string[];
@@ -16,6 +16,15 @@ const PID_FILENAME = "otto.pid";
16
16
  export function getDaemonHost(options) {
17
17
  return resolveDaemonHostCandidates(options)[0] ?? DEFAULT_HOST;
18
18
  }
19
+ export function buildDaemonConnectionCommandError(options) {
20
+ const host = getDaemonHost({ host: options.host });
21
+ const message = options.error instanceof Error ? options.error.message : String(options.error);
22
+ return {
23
+ code: "DAEMON_NOT_RUNNING",
24
+ message: `Cannot connect to daemon at ${host}: ${message}`,
25
+ details: "Start the daemon with: otto daemon start",
26
+ };
27
+ }
19
28
  export function normalizeDaemonHost(raw) {
20
29
  const trimmed = raw.trim();
21
30
  if (!trimmed) {
@@ -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;