@otto-code/cli 0.8.12 → 0.8.13

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 (103) hide show
  1. package/dist/cli.js +1 -5
  2. package/dist/commands/daemon/index.js +1 -0
  3. package/dist/commands/daemon/local-daemon.js +3 -0
  4. package/dist/commands/daemon/pair.d.ts +27 -1
  5. package/dist/commands/daemon/pair.js +133 -46
  6. package/dist/commands/daemon/start.js +1 -0
  7. package/dist/commands/daemon/status.d.ts +11 -0
  8. package/dist/commands/daemon/status.js +47 -22
  9. package/dist/commands/hub/authority.d.ts +17 -0
  10. package/dist/commands/hub/authority.js +18 -0
  11. package/dist/commands/hub/connect.d.ts +21 -0
  12. package/dist/commands/hub/connect.js +34 -0
  13. package/dist/commands/hub/credentials.d.ts +21 -0
  14. package/dist/commands/hub/credentials.js +143 -0
  15. package/dist/commands/hub/daemon-client.d.ts +27 -0
  16. package/dist/commands/hub/daemon-client.js +14 -0
  17. package/dist/commands/hub/deploy-bundle.d.ts +16 -0
  18. package/dist/commands/hub/deploy-bundle.js +248 -0
  19. package/dist/commands/hub/deploy.d.ts +40 -0
  20. package/dist/commands/hub/deploy.js +90 -0
  21. package/dist/commands/hub/disconnect.d.ts +16 -0
  22. package/dist/commands/hub/disconnect.js +24 -0
  23. package/dist/commands/hub/error.d.ts +6 -0
  24. package/dist/commands/hub/error.js +9 -0
  25. package/dist/commands/hub/help.d.ts +3 -0
  26. package/dist/commands/hub/help.js +5 -0
  27. package/dist/commands/hub/hub-client/index.d.ts +19 -0
  28. package/dist/commands/hub/hub-client/index.js +99 -0
  29. package/dist/commands/hub/hub-client/internal/contracts.d.ts +86 -0
  30. package/dist/commands/hub/hub-client/internal/contracts.js +62 -0
  31. package/dist/commands/hub/hub-client/internal/problem.d.ts +3 -0
  32. package/dist/commands/hub/hub-client/internal/problem.js +85 -0
  33. package/dist/commands/hub/hub-client/internal/transport.d.ts +15 -0
  34. package/dist/commands/hub/hub-client/internal/transport.js +30 -0
  35. package/dist/commands/hub/index.d.ts +15 -25
  36. package/dist/commands/hub/index.js +67 -73
  37. package/dist/commands/hub/init-plan.d.ts +50 -0
  38. package/dist/commands/hub/init-plan.js +136 -0
  39. package/dist/commands/hub/init.d.ts +20 -0
  40. package/dist/commands/hub/init.js +415 -0
  41. package/dist/commands/hub/{device-authorization.d.ts → login-flow.d.ts} +12 -15
  42. package/dist/commands/hub/login-flow.js +78 -0
  43. package/dist/commands/hub/login.d.ts +21 -0
  44. package/dist/commands/hub/login.js +34 -0
  45. package/dist/commands/hub/logout.d.ts +32 -0
  46. package/dist/commands/hub/logout.js +71 -0
  47. package/dist/commands/hub/origin.d.ts +2 -0
  48. package/dist/commands/hub/origin.js +27 -0
  49. package/dist/commands/hub/projects.d.ts +24 -0
  50. package/dist/commands/hub/projects.js +49 -0
  51. package/dist/commands/hub/reporter.d.ts +10 -0
  52. package/dist/commands/hub/reporter.js +11 -0
  53. package/dist/commands/hub/status-output.d.ts +13 -0
  54. package/dist/commands/hub/status-output.js +30 -0
  55. package/dist/commands/onboard.js +20 -40
  56. package/dist/commands/provider/diagnostic.d.ts +12 -0
  57. package/dist/commands/provider/diagnostic.js +31 -0
  58. package/dist/commands/provider/index.js +5 -0
  59. package/dist/commands/schedule/types.d.ts +1 -4
  60. package/dist/commands/workspace/index.js +10 -0
  61. package/dist/commands/workspace/rename.d.ts +22 -0
  62. package/dist/commands/workspace/rename.js +61 -0
  63. package/dist/output/types.d.ts +1 -1
  64. package/dist/run.js +8 -1
  65. package/dist/utils/client.js +4 -1
  66. package/package.json +5 -5
  67. package/dist/commands/chat/create.d.ts +0 -9
  68. package/dist/commands/chat/create.js +0 -23
  69. package/dist/commands/chat/delete.d.ts +0 -6
  70. package/dist/commands/chat/delete.js +0 -20
  71. package/dist/commands/chat/index.d.ts +0 -3
  72. package/dist/commands/chat/index.js +0 -47
  73. package/dist/commands/chat/inspect.d.ts +0 -6
  74. package/dist/commands/chat/inspect.js +0 -20
  75. package/dist/commands/chat/ls.d.ts +0 -6
  76. package/dist/commands/chat/ls.js +0 -20
  77. package/dist/commands/chat/post.d.ts +0 -9
  78. package/dist/commands/chat/post.js +0 -28
  79. package/dist/commands/chat/read.d.ts +0 -11
  80. package/dist/commands/chat/read.js +0 -40
  81. package/dist/commands/chat/schema.d.ts +0 -36
  82. package/dist/commands/chat/schema.js +0 -81
  83. package/dist/commands/chat/shared.d.ts +0 -19
  84. package/dist/commands/chat/shared.js +0 -118
  85. package/dist/commands/chat/wait.d.ts +0 -9
  86. package/dist/commands/chat/wait.js +0 -45
  87. package/dist/commands/hub/cloud-device-authorization.d.ts +0 -45
  88. package/dist/commands/hub/cloud-device-authorization.js +0 -92
  89. package/dist/commands/hub/device-authorization.js +0 -87
  90. package/dist/commands/loop/index.d.ts +0 -3
  91. package/dist/commands/loop/index.js +0 -18
  92. package/dist/commands/loop/inspect.d.ts +0 -13
  93. package/dist/commands/loop/inspect.js +0 -100
  94. package/dist/commands/loop/logs.d.ts +0 -8
  95. package/dist/commands/loop/logs.js +0 -69
  96. package/dist/commands/loop/ls.d.ts +0 -18
  97. package/dist/commands/loop/ls.js +0 -62
  98. package/dist/commands/loop/run.d.ts +0 -30
  99. package/dist/commands/loop/run.js +0 -162
  100. package/dist/commands/loop/stop.d.ts +0 -15
  101. package/dist/commands/loop/stop.js +0 -56
  102. package/dist/commands/loop/types.d.ts +0 -138
  103. package/dist/commands/loop/types.js +0 -2
@@ -0,0 +1,21 @@
1
+ import type { Command } from "commander";
2
+ import { type SingleResult } from "../../output/index.js";
3
+ import type { HubCredentialStore } from "./credentials.js";
4
+ import type { CliLoginFlow } from "./login-flow.js";
5
+ import { type HubReporter } from "./reporter.js";
6
+ interface HubLoginResult {
7
+ origin: string;
8
+ status: "logged_in";
9
+ }
10
+ interface HubLoginDependencies {
11
+ env: Readonly<Record<string, string | undefined>>;
12
+ credentials: HubCredentialStore;
13
+ flow: Pick<CliLoginFlow, "authorize">;
14
+ reporter: HubReporter;
15
+ }
16
+ export declare function runHubLogin(originInput: string | undefined, options: {
17
+ json?: boolean;
18
+ }, dependencies: HubLoginDependencies): Promise<SingleResult<HubLoginResult>>;
19
+ export declare function addHubLoginCommand(parent: Command, dependencies: HubLoginDependencies): void;
20
+ export {};
21
+ //# sourceMappingURL=login.d.ts.map
@@ -0,0 +1,34 @@
1
+ import { withOutput } from "../../output/index.js";
2
+ import { addJsonOption } from "../../utils/command-options.js";
3
+ import { resolveHubOrigin } from "./authority.js";
4
+ import { reportHubProgress } from "./reporter.js";
5
+ import { addHubResolutionHelp } from "./help.js";
6
+ const schema = {
7
+ idField: "origin",
8
+ columns: [
9
+ { header: "HUB", field: "origin" },
10
+ { header: "STATUS", field: "status" },
11
+ ],
12
+ };
13
+ export async function runHubLogin(originInput, options, dependencies) {
14
+ const origin = resolveHubOrigin({
15
+ options: { origin: originInput },
16
+ env: dependencies.env,
17
+ credentials: dependencies.credentials,
18
+ });
19
+ reportHubProgress(dependencies.reporter, options, `Logging in to ${origin}`);
20
+ const credential = await dependencies.flow.authorize(origin);
21
+ dependencies.credentials.save({ origin, credential });
22
+ return { type: "single", data: { origin, status: "logged_in" }, schema };
23
+ }
24
+ export function addHubLoginCommand(parent, dependencies) {
25
+ addJsonOption(addHubResolutionHelp(parent
26
+ .command("login")
27
+ .description("Log in to a Paseo Hub for CLI access")
28
+ .argument("[origin]", "Paseo Hub origin"))).action(withOutput(async (...args) => {
29
+ const origin = args[0];
30
+ const options = args.at(-2);
31
+ return runHubLogin(origin, options, dependencies);
32
+ }));
33
+ }
34
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1,32 @@
1
+ import type { Command } from "commander";
2
+ import { type SingleResult } from "../../output/index.js";
3
+ import type { HubCredentialStore } from "./credentials.js";
4
+ import type { HubDaemonConnection } from "./daemon-client.js";
5
+ import { type HubReporter } from "./reporter.js";
6
+ interface HubLogoutResult {
7
+ origin: string | null;
8
+ status: "logged_out" | "not_logged_in";
9
+ daemonDisconnected: boolean;
10
+ warning?: string;
11
+ }
12
+ interface HubLogoutOptions {
13
+ disconnectDaemon?: boolean;
14
+ force?: boolean;
15
+ host?: string;
16
+ json?: boolean;
17
+ }
18
+ interface HubLogoutDependencies {
19
+ credentials: HubCredentialStore;
20
+ daemon: HubDaemonConnection;
21
+ isInteractive(): boolean;
22
+ confirmDisconnect(origin: string): Promise<boolean>;
23
+ reporter: HubReporter;
24
+ }
25
+ export declare function runHubLogout(options: HubLogoutOptions, dependencies: HubLogoutDependencies): Promise<SingleResult<HubLogoutResult>>;
26
+ export declare function addHubLogoutCommand(parent: Command, dependencies: HubLogoutDependencies): void;
27
+ export declare const productionLogoutPrompt: {
28
+ isInteractive: () => boolean;
29
+ confirmDisconnect(origin: string): Promise<boolean>;
30
+ };
31
+ export {};
32
+ //# sourceMappingURL=logout.d.ts.map
@@ -0,0 +1,71 @@
1
+ import { confirm, isCancel } from "@clack/prompts";
2
+ import { withOutput } from "../../output/index.js";
3
+ import { addJsonAndDaemonHostOptions } from "../../utils/command-options.js";
4
+ import { withHubDaemon } from "./daemon-client.js";
5
+ import { reportHubProgress } from "./reporter.js";
6
+ const schema = {
7
+ idField: "status",
8
+ columns: [
9
+ { header: "HUB", field: "origin" },
10
+ { header: "STATUS", field: "status" },
11
+ { header: "DAEMON DISCONNECTED", field: "daemonDisconnected" },
12
+ { header: "WARNING", field: "warning" },
13
+ ],
14
+ };
15
+ export async function runHubLogout(options, dependencies) {
16
+ const active = dependencies.credentials.active();
17
+ if (active === null) {
18
+ return logoutResult({ origin: null, status: "not_logged_in", daemonDisconnected: false });
19
+ }
20
+ reportHubProgress(dependencies.reporter, options, `Logging out of ${active.origin}`);
21
+ const mayPrompt = options.json !== true && dependencies.isInteractive();
22
+ let daemonStatus = null;
23
+ const shouldInspectDaemon = options.disconnectDaemon === true || mayPrompt;
24
+ if (shouldInspectDaemon) {
25
+ daemonStatus = await withHubDaemon(dependencies.daemon, options.host, async (daemon) => daemon.getHubStatus().then((response) => response.status));
26
+ }
27
+ const sameHub = daemonStatus?.hubOrigin === active.origin;
28
+ let shouldDisconnect = options.disconnectDaemon === true && sameHub;
29
+ if (options.disconnectDaemon !== true && mayPrompt && sameHub) {
30
+ shouldDisconnect = await dependencies.confirmDisconnect(active.origin);
31
+ }
32
+ if (!shouldDisconnect) {
33
+ dependencies.credentials.logoutActive();
34
+ return logoutResult({ origin: active.origin, status: "logged_out", daemonDisconnected: false });
35
+ }
36
+ reportHubProgress(dependencies.reporter, options, `Disconnecting this daemon from ${active.origin}`);
37
+ const disconnect = await withHubDaemon(dependencies.daemon, options.host, async (daemon) => {
38
+ return daemon.disconnectHub(options.force ?? false);
39
+ });
40
+ dependencies.credentials.logoutActive();
41
+ return logoutResult({
42
+ origin: active.origin,
43
+ status: "logged_out",
44
+ daemonDisconnected: disconnect.status.state === "not_connected",
45
+ ...(disconnect.warning ? { warning: disconnect.warning } : {}),
46
+ });
47
+ }
48
+ export function addHubLogoutCommand(parent, dependencies) {
49
+ addJsonAndDaemonHostOptions(parent
50
+ .command("logout")
51
+ .description("Remove the active stored Hub CLI login")
52
+ .option("--disconnect-daemon", "Also disconnect a daemon related to the same Hub")
53
+ .option("--force", "Remove daemon authority without notifying the Hub")).action(withOutput(async (...args) => {
54
+ const options = args.at(-2);
55
+ return runHubLogout(options, dependencies);
56
+ }));
57
+ }
58
+ export const productionLogoutPrompt = {
59
+ isInteractive: () => Boolean(process.stdin.isTTY && process.stdout.isTTY),
60
+ async confirmDisconnect(origin) {
61
+ const answer = await confirm({
62
+ message: `Disconnect this daemon from ${origin}?`,
63
+ initialValue: false,
64
+ });
65
+ return !isCancel(answer) && answer;
66
+ },
67
+ };
68
+ function logoutResult(data) {
69
+ return { type: "single", data, schema };
70
+ }
71
+ //# sourceMappingURL=logout.js.map
@@ -0,0 +1,2 @@
1
+ export declare function normalizeHubOrigin(value: string): string;
2
+ //# sourceMappingURL=origin.d.ts.map
@@ -0,0 +1,27 @@
1
+ import { HubCommandError } from "./error.js";
2
+ export function normalizeHubOrigin(value) {
3
+ let url;
4
+ try {
5
+ url = new URL(value);
6
+ }
7
+ catch {
8
+ throw invalidHubOrigin();
9
+ }
10
+ if (!["http:", "https:"].includes(url.protocol) ||
11
+ (url.protocol === "http:" && !isLoopbackHostname(url.hostname)) ||
12
+ url.username ||
13
+ url.password ||
14
+ url.pathname !== "/" ||
15
+ url.search ||
16
+ url.hash) {
17
+ throw invalidHubOrigin();
18
+ }
19
+ return url.origin;
20
+ }
21
+ function isLoopbackHostname(hostname) {
22
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
23
+ }
24
+ function invalidHubOrigin() {
25
+ return new HubCommandError("HUB_INVALID_ORIGIN", "Hub URL must be an HTTPS origin without credentials, path, query, or hash. HTTP is allowed only for localhost, 127.0.0.1, or [::1].");
26
+ }
27
+ //# sourceMappingURL=origin.js.map
@@ -0,0 +1,24 @@
1
+ import type { Command } from "commander";
2
+ import { type SingleResult } from "../../output/index.js";
3
+ import type { HubHttpClient, HubProject } from "./hub-client/index.js";
4
+ import type { HubCredentialStore } from "./credentials.js";
5
+ import { type HubReporter } from "./reporter.js";
6
+ interface HubProjectsResult {
7
+ origin: string;
8
+ projects: HubProject[];
9
+ }
10
+ export interface HubProjectsOptions {
11
+ hub?: string;
12
+ apiKey?: string;
13
+ json?: boolean;
14
+ }
15
+ interface HubProjectsDependencies {
16
+ env: Readonly<Record<string, string | undefined>>;
17
+ credentials: HubCredentialStore;
18
+ hub: Pick<HubHttpClient, "listProjects">;
19
+ reporter: HubReporter;
20
+ }
21
+ export declare function runHubProjects(options: HubProjectsOptions, dependencies: HubProjectsDependencies): Promise<SingleResult<HubProjectsResult>>;
22
+ export declare function addHubProjectsCommand(parent: Command, dependencies: HubProjectsDependencies): void;
23
+ export {};
24
+ //# sourceMappingURL=projects.d.ts.map
@@ -0,0 +1,49 @@
1
+ import { render, withOutput } from "../../output/index.js";
2
+ import { addJsonOption } from "../../utils/command-options.js";
3
+ import { resolveHubCredential, resolveHubOrigin } from "./authority.js";
4
+ import { reportHubProgress } from "./reporter.js";
5
+ import { addHubResolutionHelp } from "./help.js";
6
+ const projectSchema = {
7
+ idField: "id",
8
+ columns: [
9
+ { header: "SLUG", field: "slug" },
10
+ { header: "NAME", field: "name" },
11
+ { header: "ID", field: "id" },
12
+ { header: "HUB", field: "origin" },
13
+ ],
14
+ };
15
+ const schema = {
16
+ idField: "origin",
17
+ columns: [
18
+ { header: "HUB", field: "origin" },
19
+ { header: "PROJECTS", field: (result) => result.projects.length },
20
+ ],
21
+ renderHuman(result, options) {
22
+ const results = result.type === "single" ? [result.data] : result.data;
23
+ const data = results.flatMap((entry) => entry.projects.map((project) => ({ ...project, origin: entry.origin })));
24
+ return render({ type: "list", data, schema: projectSchema }, options);
25
+ },
26
+ };
27
+ export async function runHubProjects(options, dependencies) {
28
+ const resolution = {
29
+ options: { origin: options.hub, apiKey: options.apiKey },
30
+ env: dependencies.env,
31
+ credentials: dependencies.credentials,
32
+ };
33
+ const origin = resolveHubOrigin(resolution);
34
+ reportHubProgress(dependencies.reporter, options, `Listing projects from ${origin}`);
35
+ const credential = resolveHubCredential({ ...resolution, origin });
36
+ const projects = await dependencies.hub.listProjects(origin, credential);
37
+ return { type: "single", data: { origin, projects }, schema };
38
+ }
39
+ export function addHubProjectsCommand(parent, dependencies) {
40
+ addJsonOption(addHubResolutionHelp(parent
41
+ .command("projects")
42
+ .description("List projects for the authenticated Hub organization")
43
+ .option("--hub <origin>", "Paseo Hub origin")
44
+ .option("--api-key <secret>", "Organization API key"))).action(withOutput(async (...args) => {
45
+ const options = args.at(-2);
46
+ return runHubProjects(options, dependencies);
47
+ }));
48
+ }
49
+ //# sourceMappingURL=projects.js.map
@@ -0,0 +1,10 @@
1
+ export interface HubReporter {
2
+ progress(message: string): void;
3
+ }
4
+ export declare const processHubReporter: HubReporter;
5
+ interface HubReportOptions {
6
+ json?: boolean;
7
+ }
8
+ export declare function reportHubProgress(reporter: HubReporter, options: HubReportOptions, message: string): void;
9
+ export {};
10
+ //# sourceMappingURL=reporter.d.ts.map
@@ -0,0 +1,11 @@
1
+ export const processHubReporter = {
2
+ progress(message) {
3
+ process.stderr.write(`${message}\n`);
4
+ },
5
+ };
6
+ export function reportHubProgress(reporter, options, message) {
7
+ if (options.json === true)
8
+ return;
9
+ reporter.progress(message);
10
+ }
11
+ //# sourceMappingURL=reporter.js.map
@@ -0,0 +1,13 @@
1
+ import type { ListResult } from "../../output/index.js";
2
+ import type { HubStatus } from "./daemon-client.js";
3
+ export interface HubRow {
4
+ state: string;
5
+ daemonId: string | null;
6
+ hub: string | null;
7
+ scopes: string;
8
+ connectedAt: string | null;
9
+ error: string | null;
10
+ warning?: string;
11
+ }
12
+ export declare function hubStatusResult(status: HubStatus, warning?: string, reportedHubOrigin?: string | null): ListResult<HubRow>;
13
+ //# sourceMappingURL=status-output.d.ts.map
@@ -0,0 +1,30 @@
1
+ const schema = {
2
+ idField: "state",
3
+ columns: [
4
+ { header: "STATE", field: "state" },
5
+ { header: "HUB", field: "hub" },
6
+ { header: "DAEMON", field: "daemonId" },
7
+ { header: "SCOPES", field: "scopes" },
8
+ { header: "CONNECTED", field: "connectedAt" },
9
+ { header: "ERROR", field: "error" },
10
+ { header: "WARNING", field: "warning" },
11
+ ],
12
+ };
13
+ export function hubStatusResult(status, warning, reportedHubOrigin = status.hubOrigin) {
14
+ return {
15
+ type: "list",
16
+ data: [
17
+ {
18
+ state: status.state,
19
+ daemonId: status.daemonId,
20
+ hub: reportedHubOrigin,
21
+ scopes: status.scopes.join(", "),
22
+ connectedAt: status.connectedAt,
23
+ error: status.lastError,
24
+ warning,
25
+ },
26
+ ],
27
+ schema,
28
+ };
29
+ }
30
+ //# sourceMappingURL=status-output.js.map
@@ -2,10 +2,11 @@ import { cancel, confirm, intro, isCancel, log, note, outro, spinner } from "@cl
2
2
  import { Command, Option } from "commander";
3
3
  import { writeFileSync } from "node:fs";
4
4
  import path from "node:path";
5
- import { generateLocalPairingOffer, loadConfig, loadPersistedConfig, } from "@otto-code/server";
5
+ import { loadPersistedConfig } from "@otto-code/server";
6
6
  import { resolveLocalOttoHome, resolveLocalDaemonState, resolveTcpHostFromListen, startLocalDaemonDetached, tailDaemonLog, } from "./daemon/local-daemon.js";
7
7
  import { tryConnectToDaemon } from "../utils/client.js";
8
8
  import { formatPairingInstructions } from "../output/pairing.js";
9
+ import { confirmRelayPairing, printDirectConnectionGuidance, resolveLocalPairingOffer, } from "./daemon/pair.js";
9
10
  const DEFAULT_READY_TIMEOUT_MS = 10 * 60 * 1000;
10
11
  const READY_PROBE_TIMEOUT_MS = 1200;
11
12
  class OnboardCancelledError extends Error {
@@ -29,32 +30,6 @@ function parseTimeoutMs(raw) {
29
30
  }
30
31
  return Math.ceil(seconds * 1000);
31
32
  }
32
- function toCliOverrides(options) {
33
- const cliOverrides = {};
34
- if (options.listen) {
35
- cliOverrides.listen = options.listen;
36
- }
37
- else if (options.port) {
38
- cliOverrides.listen = `127.0.0.1:${options.port}`;
39
- }
40
- if (options.relay === false) {
41
- cliOverrides.relayEnabled = false;
42
- }
43
- if (options.hostnames) {
44
- const raw = options.hostnames.trim();
45
- cliOverrides.hostnames =
46
- raw.toLowerCase() === "true"
47
- ? true
48
- : raw
49
- .split(",")
50
- .map((host) => host.trim())
51
- .filter(Boolean);
52
- }
53
- if (options.mcp === false) {
54
- cliOverrides.mcpEnabled = false;
55
- }
56
- return cliOverrides;
57
- }
58
33
  function savePersistedConfig(ottoHome, config) {
59
34
  const configPath = path.join(ottoHome, "config.json");
60
35
  writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
@@ -243,6 +218,7 @@ export function onboardCommand() {
243
218
  .option("--listen <listen>", "Listen target (host:port, port, or unix socket path)")
244
219
  .option("--port <port>", "Port to listen on (default: 6868)")
245
220
  .option("--home <path>", "Otto home directory (default: ~/.otto)")
221
+ .option("--relay", "Enable relay connection without prompting")
246
222
  .option("--no-relay", "Disable relay connection")
247
223
  .option("--no-mcp", "Disable the Agent MCP HTTP endpoint")
248
224
  .option("--hostnames <hosts>", 'Daemon hostnames (comma-separated, e.g. "myhost,.example.com" or "true" for any)')
@@ -371,7 +347,6 @@ export async function runOnboard(options) {
371
347
  renderNote(ottoHome, "Otto home");
372
348
  }
373
349
  const voiceEnabled = await resolveAndPersistVoice(ottoHome, options);
374
- const config = loadConfig(ottoHome, { cli: toCliOverrides(options) });
375
350
  log.message(voiceEnabled
376
351
  ? "Voice features enabled. Local speech models will be downloaded automatically if missing."
377
352
  : "Voice features disabled. Local speech models will not be downloaded.");
@@ -381,24 +356,29 @@ export async function runOnboard(options) {
381
356
  timeoutMs,
382
357
  richUi,
383
358
  });
384
- if (config.relayEnabled === false) {
385
- log.warn("Relay is disabled; pairing offer is unavailable for this daemon.");
359
+ if (options.relay === false) {
360
+ log.message("Relay pairing skipped because --no-relay was provided.");
386
361
  printNextSteps(null, ottoHome, richUi);
387
- if (richUi) {
362
+ if (richUi)
388
363
  outro("Otto daemon is running.");
389
- }
390
364
  return;
391
365
  }
392
- const pairing = await generateLocalPairingOffer({
366
+ let pairing = await resolveLocalPairingOffer({
393
367
  ottoHome,
394
- relayEnabled: config.relayEnabled,
395
- relayEndpoint: config.relayEndpoint,
396
- relayPublicEndpoint: config.relayPublicEndpoint,
397
- relayUseTls: config.relayUseTls,
398
- relayPublicUseTls: config.relayPublicUseTls,
399
- appBaseUrl: config.appBaseUrl,
400
- includeQr: true,
368
+ enableRelay: options.relay === true,
401
369
  });
370
+ if (!pairing.relayEnabled) {
371
+ const shouldEnable = richUi ? await confirmRelayPairing() : false;
372
+ if (!shouldEnable) {
373
+ printDirectConnectionGuidance();
374
+ printNextSteps(null, ottoHome, richUi);
375
+ if (richUi)
376
+ outro("Otto daemon is running.");
377
+ return;
378
+ }
379
+ pairing = await resolveLocalPairingOffer({ ottoHome, enableRelay: true });
380
+ log.success("Relay enabled");
381
+ }
402
382
  if (!pairing.url) {
403
383
  log.warn("Relay pairing URL is unavailable for this daemon configuration.");
404
384
  printNextSteps(null, ottoHome, richUi);
@@ -0,0 +1,12 @@
1
+ import type { Command } from "commander";
2
+ import type { CommandOptions, OutputSchema, SingleResult } from "../../output/index.js";
3
+ export interface ProviderDiagnosticResult {
4
+ provider: string;
5
+ diagnostic: string;
6
+ }
7
+ export declare const providerDiagnosticSchema: OutputSchema<ProviderDiagnosticResult>;
8
+ export interface ProviderDiagnosticOptions extends CommandOptions {
9
+ host?: string;
10
+ }
11
+ export declare function runDiagnosticCommand(provider: string, options: ProviderDiagnosticOptions, _command: Command): Promise<SingleResult<ProviderDiagnosticResult>>;
12
+ //# sourceMappingURL=diagnostic.d.ts.map
@@ -0,0 +1,31 @@
1
+ import { connectToDaemon } from "../../utils/client.js";
2
+ export const providerDiagnosticSchema = {
3
+ idField: "provider",
4
+ columns: [
5
+ { header: "PROVIDER", field: "provider" },
6
+ { header: "DIAGNOSTIC", field: "diagnostic" },
7
+ ],
8
+ renderHuman(result) {
9
+ if (result.type === "single")
10
+ return result.data.diagnostic;
11
+ return result.data.map((entry) => entry.diagnostic).join("\n\n");
12
+ },
13
+ };
14
+ export async function runDiagnosticCommand(provider, options, _command) {
15
+ const client = await connectToDaemon({ host: options.host });
16
+ try {
17
+ const result = await client.getProviderDiagnostic(provider.trim().toLowerCase());
18
+ return {
19
+ type: "single",
20
+ data: {
21
+ provider: result.provider,
22
+ diagnostic: result.diagnostic,
23
+ },
24
+ schema: providerDiagnosticSchema,
25
+ };
26
+ }
27
+ finally {
28
+ await client.close();
29
+ }
30
+ }
31
+ //# sourceMappingURL=diagnostic.js.map
@@ -1,6 +1,7 @@
1
1
  import { Command } from "commander";
2
2
  import { runLsCommand } from "./ls.js";
3
3
  import { runModelsCommand } from "./models.js";
4
+ import { runDiagnosticCommand } from "./diagnostic.js";
4
5
  import { withOutput } from "../../output/index.js";
5
6
  import { addJsonAndDaemonHostOptions } from "../../utils/command-options.js";
6
7
  export function createProviderCommand() {
@@ -11,6 +12,10 @@ export function createProviderCommand() {
11
12
  .description("List models for a provider")
12
13
  .argument("<provider>", "Provider name (claude, codex, opencode)")
13
14
  .option("--thinking", "Include thinking option IDs for each model")).action(withOutput(runModelsCommand));
15
+ addJsonAndDaemonHostOptions(provider
16
+ .command("diagnostic")
17
+ .description("Show provider installation, environment, and availability diagnostics")
18
+ .argument("<provider>", "Provider name")).action(withOutput(runDiagnosticCommand));
14
19
  return provider;
15
20
  }
16
21
  //# sourceMappingURL=index.js.map
@@ -22,10 +22,7 @@ export type ScheduleTarget = {
22
22
  model?: string;
23
23
  thinkingOptionId?: string;
24
24
  title?: string | null;
25
- approvalPolicy?: string;
26
- sandboxMode?: string;
27
- networkAccess?: boolean;
28
- webSearch?: boolean;
25
+ providerOptions?: Record<string, unknown>;
29
26
  };
30
27
  };
31
28
  export interface ScheduleRunRecord {
@@ -4,6 +4,7 @@ import { addJsonAndDaemonHostOptions } from "../../utils/command-options.js";
4
4
  import { runArchiveCommand } from "./archive.js";
5
5
  import { runCreateCommand } from "./create.js";
6
6
  import { runLsCommand } from "./ls.js";
7
+ import { runRenameCommand } from "./rename.js";
7
8
  export function createWorkspaceCommand() {
8
9
  const workspace = new Command("workspace").description("Manage workspaces");
9
10
  addJsonAndDaemonHostOptions(workspace
@@ -21,6 +22,15 @@ export function createWorkspaceCommand() {
21
22
  .option("--pr-number <n>", "Pull request or change request number (--mode checkout-pr)")
22
23
  .option("--forge <forge>", "Forge for --mode checkout-pr (default: source checkout)")).action(withOutput(runCreateCommand));
23
24
  addJsonAndDaemonHostOptions(workspace.command("ls").description("List active workspaces")).action(withOutput(runLsCommand));
25
+ addJsonAndDaemonHostOptions(workspace
26
+ .command("rename")
27
+ .description("Set a workspace's user-visible title")
28
+ .argument("<workspace-id>", "Workspace id")
29
+ .argument("[title]", "New workspace title")
30
+ .option("--reset", "Clear the title and revert to the branch or directory name")
31
+ // Commander 12 accepts excess positionals by default, which would rename to
32
+ // the first word of an unquoted multi-word title and silently drop the rest.
33
+ .allowExcessArguments(false)).action(withOutput(runRenameCommand));
24
34
  addJsonAndDaemonHostOptions(workspace
25
35
  .command("archive")
26
36
  .description("Archive a workspace and everything it owns")
@@ -0,0 +1,22 @@
1
+ import type { Command } from "commander";
2
+ import type { SingleResult } from "../../output/index.js";
3
+ interface WorkspaceRenameResult {
4
+ workspaceId: string;
5
+ /** Raw title override; null once it has been reset to the derived name. */
6
+ title: string | null;
7
+ }
8
+ export interface WorkspaceRenameOptions {
9
+ host?: string;
10
+ reset?: boolean;
11
+ }
12
+ /**
13
+ * Resolve the title to send to the daemon. Null clears the override and
14
+ * reverts the workspace to its branch- or directory-derived name.
15
+ */
16
+ export declare function resolveWorkspaceTitle(input: {
17
+ title?: string;
18
+ reset?: boolean;
19
+ }): string | null;
20
+ export declare function runRenameCommand(workspaceId: string, titleArg: string | undefined, options: WorkspaceRenameOptions, _command: Command): Promise<SingleResult<WorkspaceRenameResult>>;
21
+ export {};
22
+ //# sourceMappingURL=rename.d.ts.map
@@ -0,0 +1,61 @@
1
+ import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
2
+ const workspaceRenameSchema = {
3
+ idField: "workspaceId",
4
+ columns: [
5
+ { header: "WORKSPACE ID", field: "workspaceId", width: 20 },
6
+ { header: "TITLE", field: "title", width: 30 },
7
+ ],
8
+ };
9
+ /**
10
+ * Resolve the title to send to the daemon. Null clears the override and
11
+ * reverts the workspace to its branch- or directory-derived name.
12
+ */
13
+ export function resolveWorkspaceTitle(input) {
14
+ const title = input.title?.trim() ?? "";
15
+ if (input.reset) {
16
+ if (input.title !== undefined) {
17
+ throw {
18
+ code: "INVALID_OPTIONS",
19
+ message: "--reset cannot be combined with a title",
20
+ details: "Pass a title to rename the workspace, or --reset to revert to the derived name",
21
+ };
22
+ }
23
+ return null;
24
+ }
25
+ if (title.length === 0) {
26
+ throw {
27
+ code: "MISSING_TITLE",
28
+ message: "Title cannot be empty",
29
+ details: "Usage: otto workspace rename <workspace-id> <title> | --reset",
30
+ };
31
+ }
32
+ return title;
33
+ }
34
+ export async function runRenameCommand(workspaceId, titleArg, options, _command) {
35
+ const title = resolveWorkspaceTitle({ title: titleArg, reset: options.reset });
36
+ const host = getDaemonHost({ host: options.host });
37
+ const client = await connectToDaemon({ host: options.host }).catch((error) => {
38
+ const message = error instanceof Error ? error.message : String(error);
39
+ throw {
40
+ code: "DAEMON_NOT_RUNNING",
41
+ message: `Cannot connect to daemon at ${host}: ${message}`,
42
+ details: "Start the daemon with: otto daemon start",
43
+ };
44
+ });
45
+ try {
46
+ const applied = await client.setWorkspaceTitle(workspaceId, title);
47
+ return {
48
+ type: "single",
49
+ data: { workspaceId, title: applied.title },
50
+ schema: workspaceRenameSchema,
51
+ };
52
+ }
53
+ catch (error) {
54
+ const message = error instanceof Error ? error.message : String(error);
55
+ throw { code: "WORKSPACE_RENAME_FAILED", message };
56
+ }
57
+ finally {
58
+ await client.close().catch(() => undefined);
59
+ }
60
+ }
61
+ //# sourceMappingURL=rename.js.map
@@ -58,7 +58,7 @@ export interface ListResult<T> {
58
58
  schema: OutputSchema<T>;
59
59
  }
60
60
  /** Union type for all command results */
61
- export type AnyCommandResult<T> = SingleResult<T> | ListResult<T>;
61
+ export type AnyCommandResult<T> = T extends unknown ? SingleResult<T> | ListResult<T> : never;
62
62
  /** Base interface for command results (deprecated, use SingleResult or ListResult) */
63
63
  export type CommandResult<T> = SingleResult<T> | ListResult<T>;
64
64
  /** Structured error for command failures */
package/dist/run.js CHANGED
@@ -13,7 +13,14 @@ export function createCliParseArgv(input) {
13
13
  return invocation;
14
14
  }
15
15
  const nodeArgv = input.nodeArgv ?? ["otto", "otto"];
16
- const cliArgv = invocation.argv.length === 0 ? ["onboard"] : invocation.argv;
16
+ const isOnboardRootFlag = invocation.argv[0] === "--relay" || invocation.argv[0] === "--no-relay";
17
+ let cliArgv = invocation.argv;
18
+ if (invocation.argv.length === 0) {
19
+ cliArgv = ["onboard"];
20
+ }
21
+ else if (isOnboardRootFlag) {
22
+ cliArgv = ["onboard", ...invocation.argv];
23
+ }
17
24
  return [...nodeArgv, ...cliArgv];
18
25
  }
19
26
  export async function runCli(argv, options = {}) {