@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
@@ -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;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Parse duration string to milliseconds.
3
- * Supports formats like: 5m, 30s, 1h, 2h30m, 90, etc.
3
+ * Supports formats like: 5m, 30s, 1h, 2h30m, 1d, 90, etc.
4
4
  * If no unit is specified, assumes seconds.
5
5
  */
6
6
  export function parseDuration(input) {
@@ -9,13 +9,14 @@ export function parseDuration(input) {
9
9
  if (/^\d+$/.test(trimmed)) {
10
10
  return parseInt(trimmed, 10) * 1000;
11
11
  }
12
+ if (!/^(?:\d+[smhd])+$/.test(trimmed)) {
13
+ throw new Error(`Invalid duration format: ${input}. Use formats like: 5m, 30s, 1h, 2h30m, 1d`);
14
+ }
12
15
  // Parse duration with units
13
16
  let totalMs = 0;
14
- const regex = /(\d+)([smh])/g;
17
+ const regex = /(\d+)([smhd])/g;
15
18
  let match;
16
- let hasMatch = false;
17
19
  while ((match = regex.exec(trimmed)) !== null) {
18
- hasMatch = true;
19
20
  const value = parseInt(match[1], 10);
20
21
  const unit = match[2];
21
22
  switch (unit) {
@@ -28,11 +29,11 @@ export function parseDuration(input) {
28
29
  case "h":
29
30
  totalMs += value * 60 * 60 * 1000;
30
31
  break;
32
+ case "d":
33
+ totalMs += value * 24 * 60 * 60 * 1000;
34
+ break;
31
35
  }
32
36
  }
33
- if (!hasMatch) {
34
- throw new Error(`Invalid duration format: ${input}. Use formats like: 5m, 30s, 1h, 2h30m`);
35
- }
36
37
  return totalMs;
37
38
  }
38
39
  //# sourceMappingURL=duration.js.map
@@ -12,7 +12,7 @@ export function isSameOrDescendantPath(basePath, candidatePath) {
12
12
  // Normalize both paths: replace all backslashes with forward slashes, strip trailing separator
13
13
  let normalizedBase = basePath.replace(/\\/g, "/").replace(/\/$/, "");
14
14
  let normalizedCandidate = candidatePath.replace(/\\/g, "/").replace(/\/$/, "");
15
- // Windows paths are case-insensitive detect by drive letter prefix (e.g. "C:/")
15
+ // Windows paths are case-insensitive - detect by drive letter prefix (e.g. "C:/")
16
16
  if (/^[a-zA-Z]:\//.test(normalizedBase) || /^[a-zA-Z]:\//.test(normalizedCandidate)) {
17
17
  normalizedBase = normalizedBase.toLowerCase();
18
18
  normalizedCandidate = normalizedCandidate.toLowerCase();
@@ -13,7 +13,7 @@ export interface ResolvedProviderModel {
13
13
  *
14
14
  * Callers run this **before** connecting. Full resolution happens after a connection because it
15
15
  * consults the host (personalities, defaults), so leaving this check there meant a plainly
16
- * malformed command line reported "Cannot connect to daemon" instead of the real problem and
16
+ * malformed command line reported "Cannot connect to daemon" instead of the real problem - and
17
17
  * only on machines without a daemon running, which is why it read as a CI-only failure.
18
18
  *
19
19
  * Silent when either input is absent or they agree; `resolveProviderAndModel` still repeats the
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Callers run this **before** connecting. Full resolution happens after a connection because it
6
6
  * consults the host (personalities, defaults), so leaving this check there meant a plainly
7
- * malformed command line reported "Cannot connect to daemon" instead of the real problem and
7
+ * malformed command line reported "Cannot connect to daemon" instead of the real problem - and
8
8
  * only on machines without a daemon running, which is why it read as a CI-only failure.
9
9
  *
10
10
  * Silent when either input is absent or they agree; `resolveProviderAndModel` still repeats the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@otto-code/cli",
3
- "version": "0.7.5",
3
+ "version": "0.8.0",
4
4
  "description": "Otto CLI - control your AI coding agents from the command line",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "bin": {
@@ -21,23 +21,25 @@
21
21
  "build:clean": "npm run clean && npm run build",
22
22
  "prepack": "npm run build:clean",
23
23
  "typecheck": "tsgo --noEmit",
24
- "test": "npm run test:local",
24
+ "test": "npm run test:unit && npm run test:local",
25
+ "test:unit": "vitest run src",
25
26
  "test:local": "tsx tests/run-all.ts",
26
27
  "test:e2e": "npm run test:local",
27
28
  "test:e2e:lifecycle": "npx tsx tests/e2e/agent-lifecycle.test.ts"
28
29
  },
29
30
  "dependencies": {
30
31
  "@clack/prompts": "^1.0.0",
31
- "@otto-code/brain": "0.7.5",
32
- "@otto-code/client": "0.7.5",
33
- "@otto-code/protocol": "0.7.5",
34
- "@otto-code/server": "0.7.5",
32
+ "@otto-code/brain": "0.8.0",
33
+ "@otto-code/client": "0.8.0",
34
+ "@otto-code/protocol": "0.8.0",
35
+ "@otto-code/server": "0.8.0",
35
36
  "chalk": "^5.3.0",
36
37
  "commander": "^12.0.0",
37
38
  "mime-types": "^2.1.35",
38
39
  "tree-kill": "^1.2.2",
39
40
  "ws": "^8.21.0",
40
- "yaml": "^2.8.4"
41
+ "yaml": "^2.8.4",
42
+ "zod": "^4.4.3"
41
43
  },
42
44
  "devDependencies": {
43
45
  "@types/mime-types": "^3.0.1",