@otto-code/cli 0.5.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 (200) hide show
  1. package/bin/otto +3 -0
  2. package/dist/classify.d.ts +19 -0
  3. package/dist/classify.js +49 -0
  4. package/dist/cli.d.ts +3 -0
  5. package/dist/cli.js +113 -0
  6. package/dist/commands/agent/archive.d.ts +18 -0
  7. package/dist/commands/agent/archive.js +102 -0
  8. package/dist/commands/agent/attach.d.ts +11 -0
  9. package/dist/commands/agent/attach.js +159 -0
  10. package/dist/commands/agent/delete.d.ts +15 -0
  11. package/dist/commands/agent/delete.js +107 -0
  12. package/dist/commands/agent/import.d.ts +14 -0
  13. package/dist/commands/agent/import.js +122 -0
  14. package/dist/commands/agent/index.d.ts +3 -0
  15. package/dist/commands/agent/index.js +48 -0
  16. package/dist/commands/agent/inspect.d.ts +15 -0
  17. package/dist/commands/agent/inspect.js +209 -0
  18. package/dist/commands/agent/logs.d.ts +19 -0
  19. package/dist/commands/agent/logs.js +180 -0
  20. package/dist/commands/agent/ls.d.ts +44 -0
  21. package/dist/commands/agent/ls.js +204 -0
  22. package/dist/commands/agent/mode.d.ts +18 -0
  23. package/dist/commands/agent/mode.js +100 -0
  24. package/dist/commands/agent/reload.d.ts +15 -0
  25. package/dist/commands/agent/reload.js +75 -0
  26. package/dist/commands/agent/run.d.ts +44 -0
  27. package/dist/commands/agent/run.js +464 -0
  28. package/dist/commands/agent/send.d.ts +19 -0
  29. package/dist/commands/agent/send.js +187 -0
  30. package/dist/commands/agent/stop.d.ts +17 -0
  31. package/dist/commands/agent/stop.js +115 -0
  32. package/dist/commands/agent/update.d.ts +18 -0
  33. package/dist/commands/agent/update.js +139 -0
  34. package/dist/commands/agent/wait.d.ts +17 -0
  35. package/dist/commands/agent/wait.js +158 -0
  36. package/dist/commands/chat/create.d.ts +9 -0
  37. package/dist/commands/chat/create.js +23 -0
  38. package/dist/commands/chat/delete.d.ts +6 -0
  39. package/dist/commands/chat/delete.js +20 -0
  40. package/dist/commands/chat/index.d.ts +3 -0
  41. package/dist/commands/chat/index.js +47 -0
  42. package/dist/commands/chat/inspect.d.ts +6 -0
  43. package/dist/commands/chat/inspect.js +20 -0
  44. package/dist/commands/chat/ls.d.ts +6 -0
  45. package/dist/commands/chat/ls.js +20 -0
  46. package/dist/commands/chat/post.d.ts +9 -0
  47. package/dist/commands/chat/post.js +28 -0
  48. package/dist/commands/chat/read.d.ts +11 -0
  49. package/dist/commands/chat/read.js +40 -0
  50. package/dist/commands/chat/schema.d.ts +36 -0
  51. package/dist/commands/chat/schema.js +81 -0
  52. package/dist/commands/chat/shared.d.ts +19 -0
  53. package/dist/commands/chat/shared.js +118 -0
  54. package/dist/commands/chat/wait.d.ts +9 -0
  55. package/dist/commands/chat/wait.js +45 -0
  56. package/dist/commands/daemon/index.d.ts +3 -0
  57. package/dist/commands/daemon/index.js +57 -0
  58. package/dist/commands/daemon/local-daemon.d.ts +78 -0
  59. package/dist/commands/daemon/local-daemon.js +544 -0
  60. package/dist/commands/daemon/pair.d.ts +9 -0
  61. package/dist/commands/daemon/pair.js +76 -0
  62. package/dist/commands/daemon/restart.d.ts +12 -0
  63. package/dist/commands/daemon/restart.js +99 -0
  64. package/dist/commands/daemon/runtime-toolchain.d.ts +6 -0
  65. package/dist/commands/daemon/runtime-toolchain.js +87 -0
  66. package/dist/commands/daemon/set-password.d.ts +17 -0
  67. package/dist/commands/daemon/set-password.js +82 -0
  68. package/dist/commands/daemon/start.d.ts +6 -0
  69. package/dist/commands/daemon/start.js +56 -0
  70. package/dist/commands/daemon/status.d.ts +10 -0
  71. package/dist/commands/daemon/status.js +338 -0
  72. package/dist/commands/daemon/stop.d.ts +15 -0
  73. package/dist/commands/daemon/stop.js +60 -0
  74. package/dist/commands/hooks.d.ts +19 -0
  75. package/dist/commands/hooks.js +89 -0
  76. package/dist/commands/loop/index.d.ts +3 -0
  77. package/dist/commands/loop/index.js +18 -0
  78. package/dist/commands/loop/inspect.d.ts +13 -0
  79. package/dist/commands/loop/inspect.js +100 -0
  80. package/dist/commands/loop/logs.d.ts +8 -0
  81. package/dist/commands/loop/logs.js +69 -0
  82. package/dist/commands/loop/ls.d.ts +18 -0
  83. package/dist/commands/loop/ls.js +62 -0
  84. package/dist/commands/loop/run.d.ts +30 -0
  85. package/dist/commands/loop/run.js +162 -0
  86. package/dist/commands/loop/stop.d.ts +15 -0
  87. package/dist/commands/loop/stop.js +56 -0
  88. package/dist/commands/loop/types.d.ts +138 -0
  89. package/dist/commands/loop/types.js +2 -0
  90. package/dist/commands/onboard.d.ts +10 -0
  91. package/dist/commands/onboard.js +416 -0
  92. package/dist/commands/open.d.ts +2 -0
  93. package/dist/commands/open.js +81 -0
  94. package/dist/commands/permit/allow.d.ts +20 -0
  95. package/dist/commands/permit/allow.js +133 -0
  96. package/dist/commands/permit/deny.d.ts +12 -0
  97. package/dist/commands/permit/deny.js +105 -0
  98. package/dist/commands/permit/index.d.ts +3 -0
  99. package/dist/commands/permit/index.js +27 -0
  100. package/dist/commands/permit/ls.d.ts +18 -0
  101. package/dist/commands/permit/ls.js +66 -0
  102. package/dist/commands/provider/index.d.ts +3 -0
  103. package/dist/commands/provider/index.js +16 -0
  104. package/dist/commands/provider/ls.d.ts +19 -0
  105. package/dist/commands/provider/ls.js +73 -0
  106. package/dist/commands/provider/models.d.ts +20 -0
  107. package/dist/commands/provider/models.js +53 -0
  108. package/dist/commands/schedule/create.d.ts +18 -0
  109. package/dist/commands/schedule/create.js +40 -0
  110. package/dist/commands/schedule/delete.d.ts +10 -0
  111. package/dist/commands/schedule/delete.js +32 -0
  112. package/dist/commands/schedule/index.d.ts +3 -0
  113. package/dist/commands/schedule/index.js +66 -0
  114. package/dist/commands/schedule/inspect.d.ts +6 -0
  115. package/dist/commands/schedule/inspect.js +24 -0
  116. package/dist/commands/schedule/logs.d.ts +6 -0
  117. package/dist/commands/schedule/logs.js +23 -0
  118. package/dist/commands/schedule/ls.d.ts +5 -0
  119. package/dist/commands/schedule/ls.js +23 -0
  120. package/dist/commands/schedule/pause.d.ts +5 -0
  121. package/dist/commands/schedule/pause.js +23 -0
  122. package/dist/commands/schedule/resume.d.ts +5 -0
  123. package/dist/commands/schedule/resume.js +23 -0
  124. package/dist/commands/schedule/run-once.d.ts +5 -0
  125. package/dist/commands/schedule/run-once.js +23 -0
  126. package/dist/commands/schedule/schema.d.ts +21 -0
  127. package/dist/commands/schedule/schema.js +67 -0
  128. package/dist/commands/schedule/shared.d.ts +56 -0
  129. package/dist/commands/schedule/shared.js +346 -0
  130. package/dist/commands/schedule/types.d.ts +168 -0
  131. package/dist/commands/schedule/types.js +2 -0
  132. package/dist/commands/schedule/update.d.ts +21 -0
  133. package/dist/commands/schedule/update.js +39 -0
  134. package/dist/commands/speech/index.d.ts +3 -0
  135. package/dist/commands/speech/index.js +5 -0
  136. package/dist/commands/terminal/capture.d.ts +10 -0
  137. package/dist/commands/terminal/capture.js +68 -0
  138. package/dist/commands/terminal/create.d.ts +10 -0
  139. package/dist/commands/terminal/create.js +38 -0
  140. package/dist/commands/terminal/index.d.ts +3 -0
  141. package/dist/commands/terminal/index.js +43 -0
  142. package/dist/commands/terminal/kill.d.ts +6 -0
  143. package/dist/commands/terminal/kill.js +36 -0
  144. package/dist/commands/terminal/ls.d.ts +10 -0
  145. package/dist/commands/terminal/ls.js +21 -0
  146. package/dist/commands/terminal/schema.d.ts +18 -0
  147. package/dist/commands/terminal/schema.js +23 -0
  148. package/dist/commands/terminal/send-keys.d.ts +7 -0
  149. package/dist/commands/terminal/send-keys.js +76 -0
  150. package/dist/commands/terminal/shared.d.ts +12 -0
  151. package/dist/commands/terminal/shared.js +61 -0
  152. package/dist/commands/worktree/archive.d.ts +20 -0
  153. package/dist/commands/worktree/archive.js +104 -0
  154. package/dist/commands/worktree/create-input.d.ts +15 -0
  155. package/dist/commands/worktree/create-input.js +56 -0
  156. package/dist/commands/worktree/create.d.ts +11 -0
  157. package/dist/commands/worktree/create.js +58 -0
  158. package/dist/commands/worktree/index.d.ts +3 -0
  159. package/dist/commands/worktree/index.js +25 -0
  160. package/dist/commands/worktree/ls.d.ts +19 -0
  161. package/dist/commands/worktree/ls.js +97 -0
  162. package/dist/index.d.ts +2 -0
  163. package/dist/index.js +6 -0
  164. package/dist/output/index.d.ts +45 -0
  165. package/dist/output/index.js +47 -0
  166. package/dist/output/json.d.ts +11 -0
  167. package/dist/output/json.js +37 -0
  168. package/dist/output/quiet.d.ts +9 -0
  169. package/dist/output/quiet.js +22 -0
  170. package/dist/output/render.d.ts +15 -0
  171. package/dist/output/render.js +83 -0
  172. package/dist/output/table.d.ts +13 -0
  173. package/dist/output/table.js +135 -0
  174. package/dist/output/types.d.ts +73 -0
  175. package/dist/output/types.js +8 -0
  176. package/dist/output/with-output.d.ts +41 -0
  177. package/dist/output/with-output.js +87 -0
  178. package/dist/output/yaml.d.ts +11 -0
  179. package/dist/output/yaml.js +38 -0
  180. package/dist/run.d.ts +14 -0
  181. package/dist/run.js +33 -0
  182. package/dist/utils/client-id.d.ts +2 -0
  183. package/dist/utils/client-id.js +37 -0
  184. package/dist/utils/client.d.ts +44 -0
  185. package/dist/utils/client.js +330 -0
  186. package/dist/utils/command-options.d.ts +6 -0
  187. package/dist/utils/command-options.js +17 -0
  188. package/dist/utils/duration.d.ts +7 -0
  189. package/dist/utils/duration.js +38 -0
  190. package/dist/utils/errors.d.ts +5 -0
  191. package/dist/utils/errors.js +10 -0
  192. package/dist/utils/paths.d.ts +12 -0
  193. package/dist/utils/paths.js +22 -0
  194. package/dist/utils/provider-model.d.ts +11 -0
  195. package/dist/utils/provider-model.js +49 -0
  196. package/dist/utils/timeline.d.ts +11 -0
  197. package/dist/utils/timeline.js +11 -0
  198. package/dist/version.d.ts +2 -0
  199. package/dist/version.js +10 -0
  200. package/package.json +48 -0
@@ -0,0 +1,61 @@
1
+ import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
2
+ export async function connectTerminalClient(host) {
3
+ const daemonHost = getDaemonHost({ host });
4
+ try {
5
+ const client = await connectToDaemon({ host });
6
+ return { client, daemonHost };
7
+ }
8
+ catch (err) {
9
+ const message = err instanceof Error ? err.message : String(err);
10
+ const error = {
11
+ code: "DAEMON_NOT_RUNNING",
12
+ message: `Cannot connect to daemon at ${daemonHost}: ${message}`,
13
+ details: "Start the daemon with: otto daemon start",
14
+ };
15
+ throw error;
16
+ }
17
+ }
18
+ export function toTerminalCommandError(code, action, err) {
19
+ if (err && typeof err === "object" && "code" in err && "message" in err) {
20
+ return err;
21
+ }
22
+ const message = err instanceof Error ? err.message : String(err);
23
+ const rpcCode = typeof err === "object" && err !== null && "code" in err && typeof err.code === "string"
24
+ ? err.code
25
+ : undefined;
26
+ return {
27
+ code: rpcCode ?? code,
28
+ message: `Failed to ${action}: ${message}`,
29
+ };
30
+ }
31
+ export async function resolveTerminalId(client, idOrName) {
32
+ const payload = await client.listTerminals();
33
+ return resolveTerminalIdentifier(idOrName, payload.terminals);
34
+ }
35
+ function resolveTerminalIdentifier(idOrName, terminals) {
36
+ if (!idOrName || terminals.length === 0) {
37
+ return null;
38
+ }
39
+ const query = idOrName.toLowerCase();
40
+ const exactMatch = terminals.find((terminal) => terminal.id === idOrName);
41
+ if (exactMatch) {
42
+ return exactMatch.id;
43
+ }
44
+ const prefixMatches = terminals.filter((terminal) => terminal.id.toLowerCase().startsWith(query));
45
+ if (prefixMatches.length === 1 && prefixMatches[0]) {
46
+ return prefixMatches[0].id;
47
+ }
48
+ if (prefixMatches.length > 1) {
49
+ return null;
50
+ }
51
+ const nameMatches = terminals.filter((terminal) => terminal.name?.toLowerCase() === query);
52
+ if (nameMatches.length === 1 && nameMatches[0]) {
53
+ return nameMatches[0].id;
54
+ }
55
+ const partialNameMatches = terminals.filter((terminal) => terminal.name?.toLowerCase().includes(query));
56
+ if (partialNameMatches.length === 1 && partialNameMatches[0]) {
57
+ return partialNameMatches[0].id;
58
+ }
59
+ return null;
60
+ }
61
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1,20 @@
1
+ import type { Command } from "commander";
2
+ import { connectToDaemon } from "../../utils/client.js";
3
+ import type { CommandOptions, SingleResult, OutputSchema } from "../../output/index.js";
4
+ /** Result type for worktree archive command */
5
+ export interface WorktreeArchiveResult {
6
+ name: string;
7
+ status: "archived";
8
+ removedAgents: string[];
9
+ }
10
+ /** Schema for archive command output */
11
+ export declare const archiveSchema: OutputSchema<WorktreeArchiveResult>;
12
+ export interface WorktreeArchiveOptions extends CommandOptions {
13
+ host?: string;
14
+ }
15
+ export type WorktreeArchiveCommandResult = SingleResult<WorktreeArchiveResult>;
16
+ export declare function runArchiveCommand(nameArg: string, options: WorktreeArchiveOptions, _command: Command): Promise<WorktreeArchiveCommandResult>;
17
+ export declare function runArchiveCommandWithDeps(nameArg: string, options: WorktreeArchiveOptions, deps: {
18
+ connectToDaemon: typeof connectToDaemon;
19
+ }): Promise<WorktreeArchiveCommandResult>;
20
+ //# sourceMappingURL=archive.d.ts.map
@@ -0,0 +1,104 @@
1
+ import path from "path";
2
+ import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
3
+ /** Schema for archive command output */
4
+ export const archiveSchema = {
5
+ idField: "name",
6
+ columns: [
7
+ { header: "NAME", field: "name" },
8
+ { header: "STATUS", field: "status" },
9
+ {
10
+ header: "REMOVED AGENTS",
11
+ field: (item) => (item.removedAgents.length > 0 ? item.removedAgents.join(", ") : "-"),
12
+ },
13
+ ],
14
+ };
15
+ export async function runArchiveCommand(nameArg, options, _command) {
16
+ return runArchiveCommandWithDeps(nameArg, options, { connectToDaemon });
17
+ }
18
+ export async function runArchiveCommandWithDeps(nameArg, options, deps) {
19
+ const host = getDaemonHost({ host: options.host });
20
+ // Validate arguments
21
+ if (!nameArg || nameArg.trim().length === 0) {
22
+ const error = {
23
+ code: "MISSING_WORKTREE_NAME",
24
+ message: "Worktree name is required",
25
+ details: "Usage: otto worktree archive <name>",
26
+ };
27
+ throw error;
28
+ }
29
+ let client;
30
+ try {
31
+ client = await deps.connectToDaemon({ host: options.host });
32
+ }
33
+ catch (err) {
34
+ const message = err instanceof Error ? err.message : String(err);
35
+ const error = {
36
+ code: "DAEMON_NOT_RUNNING",
37
+ message: `Cannot connect to daemon at ${host}: ${message}`,
38
+ details: "Start the daemon with: otto daemon start",
39
+ };
40
+ throw error;
41
+ }
42
+ try {
43
+ // Get the list of worktrees first to resolve the name
44
+ const listResponse = await client.getOttoWorktreeList({});
45
+ if (listResponse.error) {
46
+ const error = {
47
+ code: "WORKTREE_LIST_FAILED",
48
+ message: `Failed to list worktrees: ${listResponse.error.message}`,
49
+ };
50
+ throw error;
51
+ }
52
+ // Find the worktree by name or branch
53
+ const worktree = listResponse.worktrees.find((wt) => {
54
+ const name = path.basename(wt.worktreePath);
55
+ return name === nameArg || wt.branchName === nameArg;
56
+ });
57
+ if (!worktree) {
58
+ const error = {
59
+ code: "WORKTREE_NOT_FOUND",
60
+ message: `Worktree not found: ${nameArg}`,
61
+ details: 'Use "otto worktree ls" to list available worktrees',
62
+ };
63
+ throw error;
64
+ }
65
+ // Archive the worktree. scope:"worktree" archives every active workspace on
66
+ // the directory and then removes the directory (Otto-owned gated).
67
+ const response = await client.archiveOttoWorktree({
68
+ worktreePath: worktree.worktreePath,
69
+ scope: "worktree",
70
+ });
71
+ await client.close();
72
+ if (response.error) {
73
+ const error = {
74
+ code: "WORKTREE_ARCHIVE_FAILED",
75
+ message: `Failed to archive worktree: ${response.error.message}`,
76
+ };
77
+ throw error;
78
+ }
79
+ const worktreeName = path.basename(worktree.worktreePath) || nameArg;
80
+ return {
81
+ type: "single",
82
+ data: {
83
+ name: worktreeName,
84
+ status: "archived",
85
+ removedAgents: response.removedAgents ?? [],
86
+ },
87
+ schema: archiveSchema,
88
+ };
89
+ }
90
+ catch (err) {
91
+ await client.close().catch(() => { });
92
+ // Re-throw CommandError as-is
93
+ if (err && typeof err === "object" && "code" in err) {
94
+ throw err;
95
+ }
96
+ const message = err instanceof Error ? err.message : String(err);
97
+ const error = {
98
+ code: "WORKTREE_ARCHIVE_FAILED",
99
+ message: `Failed to archive worktree: ${message}`,
100
+ };
101
+ throw error;
102
+ }
103
+ }
104
+ //# sourceMappingURL=archive.js.map
@@ -0,0 +1,15 @@
1
+ import type { DaemonClient } from "@otto-code/client/internal/daemon-client";
2
+ import type { CommandOptions } from "../../output/index.js";
3
+ export interface WorktreeCreateOptions extends CommandOptions {
4
+ host?: string;
5
+ cwd?: string;
6
+ mode?: string;
7
+ newBranch?: string;
8
+ base?: string;
9
+ branch?: string;
10
+ prNumber?: string;
11
+ }
12
+ type CreateOttoWorktreeRequest = Parameters<DaemonClient["createOttoWorktree"]>[0];
13
+ export declare function buildCreateWorktreeRequest(options: WorktreeCreateOptions, cwd: string): CreateOttoWorktreeRequest;
14
+ export {};
15
+ //# sourceMappingURL=create-input.d.ts.map
@@ -0,0 +1,56 @@
1
+ const VALID_MODES = ["branch-off", "checkout-branch", "checkout-pr"];
2
+ export function buildCreateWorktreeRequest(options, cwd) {
3
+ const mode = options.mode;
4
+ if (!mode) {
5
+ throw cmdError("MISSING_MODE", "--mode is required", `Expected one of: ${VALID_MODES.join(", ")}`);
6
+ }
7
+ switch (mode) {
8
+ case "branch-off":
9
+ return buildBranchOffRequest(options, cwd);
10
+ case "checkout-branch":
11
+ return buildCheckoutBranchRequest(options, cwd);
12
+ case "checkout-pr":
13
+ return buildCheckoutPrRequest(options, cwd);
14
+ default:
15
+ throw cmdError("INVALID_MODE", `Invalid --mode: ${mode}`, `Expected one of: ${VALID_MODES.join(", ")}`);
16
+ }
17
+ }
18
+ function buildBranchOffRequest(options, cwd) {
19
+ if (!options.newBranch) {
20
+ throw cmdError("MISSING_NEW_BRANCH", "--new-branch is required for --mode branch-off");
21
+ }
22
+ return {
23
+ cwd,
24
+ worktreeSlug: options.newBranch,
25
+ action: "branch-off",
26
+ ...(options.base ? { refName: options.base } : {}),
27
+ };
28
+ }
29
+ function buildCheckoutBranchRequest(options, cwd) {
30
+ if (!options.branch) {
31
+ throw cmdError("MISSING_BRANCH", "--branch is required for --mode checkout-branch");
32
+ }
33
+ return {
34
+ cwd,
35
+ action: "checkout",
36
+ refName: options.branch,
37
+ };
38
+ }
39
+ function buildCheckoutPrRequest(options, cwd) {
40
+ if (options.prNumber === undefined || options.prNumber === "") {
41
+ throw cmdError("MISSING_PR_NUMBER", "--pr-number is required for --mode checkout-pr");
42
+ }
43
+ const prNumber = Number(options.prNumber);
44
+ if (!Number.isInteger(prNumber) || prNumber <= 0) {
45
+ throw cmdError("INVALID_PR_NUMBER", `Invalid --pr-number: ${options.prNumber}`, "Expected a positive integer");
46
+ }
47
+ return {
48
+ cwd,
49
+ action: "checkout",
50
+ githubPrNumber: prNumber,
51
+ };
52
+ }
53
+ function cmdError(code, message, details) {
54
+ return details ? { code, message, details } : { code, message };
55
+ }
56
+ //# sourceMappingURL=create-input.js.map
@@ -0,0 +1,11 @@
1
+ import type { Command } from "commander";
2
+ import type { OutputSchema, SingleResult } from "../../output/index.js";
3
+ import { type WorktreeCreateOptions } from "./create-input.js";
4
+ export interface WorktreeCreateResult {
5
+ name: string;
6
+ branchName: string;
7
+ worktreePath: string;
8
+ }
9
+ export declare const createSchema: OutputSchema<WorktreeCreateResult>;
10
+ export declare function runCreateCommand(options: WorktreeCreateOptions, _command: Command): Promise<SingleResult<WorktreeCreateResult>>;
11
+ //# sourceMappingURL=create.d.ts.map
@@ -0,0 +1,58 @@
1
+ import path from "node:path";
2
+ import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
3
+ import { buildCreateWorktreeRequest } from "./create-input.js";
4
+ export const createSchema = {
5
+ idField: "worktreePath",
6
+ columns: [
7
+ { header: "NAME", field: "name", width: 24 },
8
+ { header: "BRANCH", field: "branchName", width: 28 },
9
+ { header: "PATH", field: "worktreePath", width: 50 },
10
+ ],
11
+ };
12
+ function cmdError(code, message, details) {
13
+ return details ? { code, message, details } : { code, message };
14
+ }
15
+ export async function runCreateCommand(options, _command) {
16
+ const cwd = options.cwd ?? process.cwd();
17
+ const request = buildCreateWorktreeRequest(options, cwd);
18
+ const host = getDaemonHost({ host: options.host });
19
+ let client;
20
+ try {
21
+ client = await connectToDaemon({ host: options.host });
22
+ }
23
+ catch (err) {
24
+ const message = err instanceof Error ? err.message : String(err);
25
+ throw cmdError("DAEMON_NOT_RUNNING", `Cannot connect to daemon at ${host}: ${message}`, "Start the daemon with: otto daemon start");
26
+ }
27
+ try {
28
+ const response = await client.createOttoWorktree(request);
29
+ const workspace = response.workspace;
30
+ if (!workspace || response.error) {
31
+ throw cmdError("WORKTREE_CREATE_FAILED", `Failed to create worktree: ${response.error ?? "no workspace returned"}`);
32
+ }
33
+ if (!workspace.workspaceDirectory) {
34
+ throw cmdError("WORKTREE_CREATE_FAILED", "Failed to create worktree: workspace directory missing from daemon response");
35
+ }
36
+ const worktreePath = workspace.workspaceDirectory;
37
+ return {
38
+ type: "single",
39
+ data: {
40
+ name: path.basename(worktreePath),
41
+ branchName: workspace.name,
42
+ worktreePath,
43
+ },
44
+ schema: createSchema,
45
+ };
46
+ }
47
+ catch (err) {
48
+ if (err && typeof err === "object" && "code" in err) {
49
+ throw err;
50
+ }
51
+ const message = err instanceof Error ? err.message : String(err);
52
+ throw cmdError("WORKTREE_CREATE_FAILED", `Failed to create worktree: ${message}`);
53
+ }
54
+ finally {
55
+ await client.close().catch(() => { });
56
+ }
57
+ }
58
+ //# sourceMappingURL=create.js.map
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare function createWorktreeCommand(): Command;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,25 @@
1
+ import { Command } from "commander";
2
+ import { runLsCommand } from "./ls.js";
3
+ import { runArchiveCommand } from "./archive.js";
4
+ import { runCreateCommand } from "./create.js";
5
+ import { withOutput } from "../../output/index.js";
6
+ import { addJsonAndDaemonHostOptions } from "../../utils/command-options.js";
7
+ export function createWorktreeCommand() {
8
+ const worktree = new Command("worktree").description("Manage Otto-managed git worktrees");
9
+ addJsonAndDaemonHostOptions(worktree.command("ls").description("List Otto-managed git worktrees")).action(withOutput(runLsCommand));
10
+ addJsonAndDaemonHostOptions(worktree
11
+ .command("create")
12
+ .description("Create a Otto-managed git worktree")
13
+ .option("--mode <mode>", "Creation mode: branch-off, checkout-branch, or checkout-pr")
14
+ .option("--new-branch <name>", "New branch name (--mode branch-off)")
15
+ .option("--base <ref>", "Base ref for new branch (--mode branch-off, defaults to repo default)")
16
+ .option("--branch <name>", "Existing branch to check out (--mode checkout-branch)")
17
+ .option("--pr-number <n>", "Pull request number (--mode checkout-pr)")
18
+ .option("--cwd <path>", "Repository directory (default: current)")).action(withOutput(runCreateCommand));
19
+ addJsonAndDaemonHostOptions(worktree
20
+ .command("archive")
21
+ .description("Archive a worktree (removes worktree and associated branch)")
22
+ .argument("<name>", "Worktree name or branch name")).action(withOutput(runArchiveCommand));
23
+ return worktree;
24
+ }
25
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,19 @@
1
+ import type { Command } from "commander";
2
+ import type { CommandOptions, ListResult, OutputSchema } from "../../output/index.js";
3
+ /** Worktree list item for display */
4
+ export interface WorktreeListItem {
5
+ name: string;
6
+ branch: string;
7
+ cwd: string;
8
+ agent: string;
9
+ }
10
+ export declare function resolveOttoHomePath(): string;
11
+ export declare function resolveOttoWorktreesDir(): string;
12
+ /** Schema for worktree ls output */
13
+ export declare const worktreeLsSchema: OutputSchema<WorktreeListItem>;
14
+ export type WorktreeLsResult = ListResult<WorktreeListItem>;
15
+ export interface WorktreeLsOptions extends CommandOptions {
16
+ host?: string;
17
+ }
18
+ export declare function runLsCommand(options: WorktreeLsOptions, _command: Command): Promise<WorktreeLsResult>;
19
+ //# sourceMappingURL=ls.d.ts.map
@@ -0,0 +1,97 @@
1
+ import { homedir } from "node:os";
2
+ import { basename, join, sep } from "node:path";
3
+ import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
4
+ /** Shorten home directory in path */
5
+ function shortenPath(path) {
6
+ const home = process.env.HOME;
7
+ if (home && path.startsWith(home)) {
8
+ return "~" + path.slice(home.length);
9
+ }
10
+ return path;
11
+ }
12
+ /** Extract worktree name from path */
13
+ function extractWorktreeName(path) {
14
+ return basename(path);
15
+ }
16
+ export function resolveOttoHomePath() {
17
+ return process.env.OTTO_HOME ?? join(homedir(), ".otto");
18
+ }
19
+ export function resolveOttoWorktreesDir() {
20
+ return join(resolveOttoHomePath(), "worktrees");
21
+ }
22
+ function isAgentInManagedWorktree(agentCwd) {
23
+ const worktreesDir = resolveOttoWorktreesDir();
24
+ return agentCwd === worktreesDir || agentCwd.startsWith(worktreesDir + sep);
25
+ }
26
+ /** Schema for worktree ls output */
27
+ export const worktreeLsSchema = {
28
+ idField: "name",
29
+ columns: [
30
+ { header: "NAME", field: "name", width: 20 },
31
+ { header: "BRANCH", field: "branch", width: 25 },
32
+ { header: "CWD", field: "cwd", width: 45 },
33
+ { header: "AGENT", field: "agent", width: 10 },
34
+ ],
35
+ };
36
+ export async function runLsCommand(options, _command) {
37
+ const host = getDaemonHost({ host: options.host });
38
+ let client;
39
+ try {
40
+ client = await connectToDaemon({ host: options.host });
41
+ }
42
+ catch (err) {
43
+ const message = err instanceof Error ? err.message : String(err);
44
+ const error = {
45
+ code: "DAEMON_NOT_RUNNING",
46
+ message: `Cannot connect to daemon at ${host}: ${message}`,
47
+ details: "Start the daemon with: otto daemon start",
48
+ };
49
+ throw error;
50
+ }
51
+ try {
52
+ const agentsPayload = await client.fetchAgents({ filter: { includeArchived: true } });
53
+ const agents = agentsPayload.entries.map((entry) => entry.agent);
54
+ // Get worktree list from daemon
55
+ const response = await client.getOttoWorktreeList({});
56
+ await client.close();
57
+ if (response.error) {
58
+ const error = {
59
+ code: "WORKTREE_LIST_FAILED",
60
+ message: `Failed to list worktrees: ${response.error.message}`,
61
+ };
62
+ throw error;
63
+ }
64
+ // Build a map of worktree paths to agent IDs
65
+ const worktreeAgentMap = new Map();
66
+ for (const agent of agents) {
67
+ if (isAgentInManagedWorktree(agent.cwd)) {
68
+ worktreeAgentMap.set(agent.cwd, agent.id.slice(0, 7));
69
+ }
70
+ }
71
+ const items = response.worktrees.map((wt) => ({
72
+ name: extractWorktreeName(wt.worktreePath),
73
+ branch: wt.branchName ?? "-",
74
+ cwd: shortenPath(wt.worktreePath),
75
+ agent: worktreeAgentMap.get(wt.worktreePath) ?? "-",
76
+ }));
77
+ return {
78
+ type: "list",
79
+ data: items,
80
+ schema: worktreeLsSchema,
81
+ };
82
+ }
83
+ catch (err) {
84
+ await client.close().catch(() => { });
85
+ // Re-throw CommandError as-is
86
+ if (err && typeof err === "object" && "code" in err) {
87
+ throw err;
88
+ }
89
+ const message = err instanceof Error ? err.message : String(err);
90
+ const error = {
91
+ code: "WORKTREE_LIST_FAILED",
92
+ message: `Failed to list worktrees: ${message}`,
93
+ };
94
+ throw error;
95
+ }
96
+ }
97
+ //# sourceMappingURL=ls.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ import { runCli } from "./run.js";
2
+ const exitCode = await runCli(process.argv.slice(2), {
3
+ nodeArgv: [process.argv[0] ?? "node", process.argv[1] ?? "otto"],
4
+ });
5
+ process.exitCode = exitCode;
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Output abstraction layer for the Otto CLI.
3
+ *
4
+ * This module provides structured output rendering with support for multiple formats:
5
+ * - table: Human-readable aligned tables (default)
6
+ * - json: Machine-readable JSON
7
+ * - yaml: Machine-readable YAML
8
+ * - quiet: Minimal output (IDs only)
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { withOutput, render, type ListResult, type OutputSchema } from './output/index.js'
13
+ *
14
+ * // Define your data type
15
+ * interface Agent { id: string; title: string; status: string }
16
+ *
17
+ * // Define how to render it
18
+ * const schema: OutputSchema<Agent> = {
19
+ * idField: 'id',
20
+ * columns: [
21
+ * { header: 'ID', field: 'id' },
22
+ * { header: 'TITLE', field: 'title' },
23
+ * { header: 'STATUS', field: 'status', color: (v) => v === 'running' ? 'green' : undefined },
24
+ * ],
25
+ * }
26
+ *
27
+ * // Return structured data from commands
28
+ * const result: ListResult<Agent> = {
29
+ * type: 'list',
30
+ * data: agents,
31
+ * schema,
32
+ * }
33
+ *
34
+ * // Render with options
35
+ * const output = render(result, { format: 'json' })
36
+ * ```
37
+ */
38
+ export type { OutputFormat, OutputOptions, ColumnDef, OutputSchema, CommandResult, SingleResult, ListResult, AnyCommandResult, CommandError, } from "./types.js";
39
+ export { renderTable, renderTableHeader, renderTableRow } from "./table.js";
40
+ export { renderJson, renderJsonLine } from "./json.js";
41
+ export { renderYaml, renderYamlDoc } from "./yaml.js";
42
+ export { renderQuiet } from "./quiet.js";
43
+ export { render, renderError, toCommandError, defaultOutputOptions } from "./render.js";
44
+ export { withOutput, createOutputOptions, type CommandOptions } from "./with-output.js";
45
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Output abstraction layer for the Otto CLI.
3
+ *
4
+ * This module provides structured output rendering with support for multiple formats:
5
+ * - table: Human-readable aligned tables (default)
6
+ * - json: Machine-readable JSON
7
+ * - yaml: Machine-readable YAML
8
+ * - quiet: Minimal output (IDs only)
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { withOutput, render, type ListResult, type OutputSchema } from './output/index.js'
13
+ *
14
+ * // Define your data type
15
+ * interface Agent { id: string; title: string; status: string }
16
+ *
17
+ * // Define how to render it
18
+ * const schema: OutputSchema<Agent> = {
19
+ * idField: 'id',
20
+ * columns: [
21
+ * { header: 'ID', field: 'id' },
22
+ * { header: 'TITLE', field: 'title' },
23
+ * { header: 'STATUS', field: 'status', color: (v) => v === 'running' ? 'green' : undefined },
24
+ * ],
25
+ * }
26
+ *
27
+ * // Return structured data from commands
28
+ * const result: ListResult<Agent> = {
29
+ * type: 'list',
30
+ * data: agents,
31
+ * schema,
32
+ * }
33
+ *
34
+ * // Render with options
35
+ * const output = render(result, { format: 'json' })
36
+ * ```
37
+ */
38
+ // Renderers
39
+ export { renderTable, renderTableHeader, renderTableRow } from "./table.js";
40
+ export { renderJson, renderJsonLine } from "./json.js";
41
+ export { renderYaml, renderYamlDoc } from "./yaml.js";
42
+ export { renderQuiet } from "./quiet.js";
43
+ // Main render function
44
+ export { render, renderError, toCommandError, defaultOutputOptions } from "./render.js";
45
+ // Command wrapper
46
+ export { withOutput, createOutputOptions } from "./with-output.js";
47
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * JSON renderer for CLI output.
3
+ *
4
+ * Renders structured data as formatted JSON for machine consumption.
5
+ */
6
+ import type { AnyCommandResult, OutputOptions } from "./types.js";
7
+ /** Render command result as JSON */
8
+ export declare function renderJson<T>(result: AnyCommandResult<T>, _options: OutputOptions): string;
9
+ /** Render a single item as JSON line (for NDJSON streaming) */
10
+ export declare function renderJsonLine<T>(item: T, serialize?: (data: T) => unknown): string;
11
+ //# sourceMappingURL=json.d.ts.map
@@ -0,0 +1,37 @@
1
+ /**
2
+ * JSON renderer for CLI output.
3
+ *
4
+ * Renders structured data as formatted JSON for machine consumption.
5
+ */
6
+ /** Render command result as JSON */
7
+ export function renderJson(result, _options) {
8
+ const { schema } = result;
9
+ // Apply custom serializer if provided
10
+ if (schema.serialize) {
11
+ if (result.type === "list") {
12
+ // If all items serialize to the same object, return just one
13
+ // This handles the case where a list of key-value rows should serialize
14
+ // to a single structured object
15
+ const serialized = result.data.map((item) => schema.serialize(item));
16
+ if (serialized.length > 0) {
17
+ const first = JSON.stringify(serialized[0]);
18
+ const allSame = serialized.every((s) => JSON.stringify(s) === first);
19
+ if (allSame) {
20
+ return JSON.stringify(serialized[0], null, 2);
21
+ }
22
+ }
23
+ return JSON.stringify(serialized, null, 2);
24
+ }
25
+ else {
26
+ const serialized = schema.serialize(result.data);
27
+ return JSON.stringify(serialized, null, 2);
28
+ }
29
+ }
30
+ return JSON.stringify(result.data, null, 2);
31
+ }
32
+ /** Render a single item as JSON line (for NDJSON streaming) */
33
+ export function renderJsonLine(item, serialize) {
34
+ const output = serialize ? serialize(item) : item;
35
+ return JSON.stringify(output);
36
+ }
37
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Quiet renderer for CLI output.
3
+ *
4
+ * Outputs only ID fields, one per line. Useful for scripting and pipelines.
5
+ */
6
+ import type { AnyCommandResult, OutputOptions } from "./types.js";
7
+ /** Render command result in quiet mode (IDs only) */
8
+ export declare function renderQuiet<T>(result: AnyCommandResult<T>, _options: OutputOptions): string;
9
+ //# sourceMappingURL=quiet.d.ts.map
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Quiet renderer for CLI output.
3
+ *
4
+ * Outputs only ID fields, one per line. Useful for scripting and pipelines.
5
+ */
6
+ /** Extract ID from item using schema definition */
7
+ function getId(item, idField) {
8
+ if (typeof idField === "function") {
9
+ return idField(item);
10
+ }
11
+ return String(item[idField]);
12
+ }
13
+ /** Render command result in quiet mode (IDs only) */
14
+ export function renderQuiet(result, _options) {
15
+ if (result.type === "single") {
16
+ return getId(result.data, result.schema.idField);
17
+ }
18
+ else {
19
+ return result.data.map((item) => getId(item, result.schema.idField)).join("\n");
20
+ }
21
+ }
22
+ //# sourceMappingURL=quiet.js.map