@otto-code/cli 0.8.10 → 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.
- package/dist/cli.js +1 -5
- package/dist/commands/daemon/index.js +1 -0
- package/dist/commands/daemon/local-daemon.js +3 -0
- package/dist/commands/daemon/pair.d.ts +27 -1
- package/dist/commands/daemon/pair.js +133 -46
- package/dist/commands/daemon/start.js +1 -0
- package/dist/commands/daemon/status.d.ts +11 -0
- package/dist/commands/daemon/status.js +47 -22
- package/dist/commands/hub/authority.d.ts +17 -0
- package/dist/commands/hub/authority.js +18 -0
- package/dist/commands/hub/connect.d.ts +21 -0
- package/dist/commands/hub/connect.js +34 -0
- package/dist/commands/hub/credentials.d.ts +21 -0
- package/dist/commands/hub/credentials.js +143 -0
- package/dist/commands/hub/daemon-client.d.ts +27 -0
- package/dist/commands/hub/daemon-client.js +14 -0
- package/dist/commands/hub/deploy-bundle.d.ts +16 -0
- package/dist/commands/hub/deploy-bundle.js +248 -0
- package/dist/commands/hub/deploy.d.ts +40 -0
- package/dist/commands/hub/deploy.js +90 -0
- package/dist/commands/hub/disconnect.d.ts +16 -0
- package/dist/commands/hub/disconnect.js +24 -0
- package/dist/commands/hub/error.d.ts +6 -0
- package/dist/commands/hub/error.js +9 -0
- package/dist/commands/hub/help.d.ts +3 -0
- package/dist/commands/hub/help.js +5 -0
- package/dist/commands/hub/hub-client/index.d.ts +19 -0
- package/dist/commands/hub/hub-client/index.js +99 -0
- package/dist/commands/hub/hub-client/internal/contracts.d.ts +86 -0
- package/dist/commands/hub/hub-client/internal/contracts.js +62 -0
- package/dist/commands/hub/hub-client/internal/problem.d.ts +3 -0
- package/dist/commands/hub/hub-client/internal/problem.js +85 -0
- package/dist/commands/hub/hub-client/internal/transport.d.ts +15 -0
- package/dist/commands/hub/hub-client/internal/transport.js +30 -0
- package/dist/commands/hub/index.d.ts +15 -25
- package/dist/commands/hub/index.js +67 -73
- package/dist/commands/hub/init-plan.d.ts +50 -0
- package/dist/commands/hub/init-plan.js +136 -0
- package/dist/commands/hub/init.d.ts +20 -0
- package/dist/commands/hub/init.js +415 -0
- package/dist/commands/hub/{device-authorization.d.ts → login-flow.d.ts} +12 -15
- package/dist/commands/hub/login-flow.js +78 -0
- package/dist/commands/hub/login.d.ts +21 -0
- package/dist/commands/hub/login.js +34 -0
- package/dist/commands/hub/logout.d.ts +32 -0
- package/dist/commands/hub/logout.js +71 -0
- package/dist/commands/hub/origin.d.ts +2 -0
- package/dist/commands/hub/origin.js +27 -0
- package/dist/commands/hub/projects.d.ts +24 -0
- package/dist/commands/hub/projects.js +49 -0
- package/dist/commands/hub/reporter.d.ts +10 -0
- package/dist/commands/hub/reporter.js +11 -0
- package/dist/commands/hub/status-output.d.ts +13 -0
- package/dist/commands/hub/status-output.js +30 -0
- package/dist/commands/onboard.js +20 -40
- package/dist/commands/provider/diagnostic.d.ts +12 -0
- package/dist/commands/provider/diagnostic.js +31 -0
- package/dist/commands/provider/index.js +5 -0
- package/dist/commands/schedule/types.d.ts +1 -4
- package/dist/commands/workspace/index.js +10 -0
- package/dist/commands/workspace/rename.d.ts +22 -0
- package/dist/commands/workspace/rename.js +61 -0
- package/dist/output/types.d.ts +1 -1
- package/dist/run.js +8 -1
- package/dist/utils/client.js +4 -1
- package/package.json +5 -5
- package/dist/commands/chat/create.d.ts +0 -9
- package/dist/commands/chat/create.js +0 -23
- package/dist/commands/chat/delete.d.ts +0 -6
- package/dist/commands/chat/delete.js +0 -20
- package/dist/commands/chat/index.d.ts +0 -3
- package/dist/commands/chat/index.js +0 -47
- package/dist/commands/chat/inspect.d.ts +0 -6
- package/dist/commands/chat/inspect.js +0 -20
- package/dist/commands/chat/ls.d.ts +0 -6
- package/dist/commands/chat/ls.js +0 -20
- package/dist/commands/chat/post.d.ts +0 -9
- package/dist/commands/chat/post.js +0 -28
- package/dist/commands/chat/read.d.ts +0 -11
- package/dist/commands/chat/read.js +0 -40
- package/dist/commands/chat/schema.d.ts +0 -36
- package/dist/commands/chat/schema.js +0 -81
- package/dist/commands/chat/shared.d.ts +0 -19
- package/dist/commands/chat/shared.js +0 -118
- package/dist/commands/chat/wait.d.ts +0 -9
- package/dist/commands/chat/wait.js +0 -45
- package/dist/commands/hub/cloud-device-authorization.d.ts +0 -45
- package/dist/commands/hub/cloud-device-authorization.js +0 -92
- package/dist/commands/hub/device-authorization.js +0 -87
- package/dist/commands/loop/index.d.ts +0 -3
- package/dist/commands/loop/index.js +0 -18
- package/dist/commands/loop/inspect.d.ts +0 -13
- package/dist/commands/loop/inspect.js +0 -100
- package/dist/commands/loop/logs.d.ts +0 -8
- package/dist/commands/loop/logs.js +0 -69
- package/dist/commands/loop/ls.d.ts +0 -18
- package/dist/commands/loop/ls.js +0 -62
- package/dist/commands/loop/run.d.ts +0 -30
- package/dist/commands/loop/run.js +0 -162
- package/dist/commands/loop/stop.d.ts +0 -15
- package/dist/commands/loop/stop.js +0 -56
- package/dist/commands/loop/types.d.ts +0 -138
- package/dist/commands/loop/types.js +0 -2
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { Command, Option } from "commander";
|
|
2
2
|
import { createAgentCommand } from "./commands/agent/index.js";
|
|
3
3
|
import { createDaemonCommand } from "./commands/daemon/index.js";
|
|
4
|
-
import { createChatCommand } from "./commands/chat/index.js";
|
|
5
|
-
import { createLoopCommand } from "./commands/loop/index.js";
|
|
6
4
|
import { createPermitCommand } from "./commands/permit/index.js";
|
|
7
5
|
import { createProviderCommand } from "./commands/provider/index.js";
|
|
8
6
|
import { createScheduleCommand } from "./commands/schedule/index.js";
|
|
@@ -91,6 +89,7 @@ export function createCli() {
|
|
|
91
89
|
.option("--force", "Send SIGKILL if graceful stop times out")
|
|
92
90
|
.option("--listen <listen>", "Listen target for restarted daemon (host:port, port, or unix socket)")
|
|
93
91
|
.option("--port <port>", "Port for restarted daemon listen target")
|
|
92
|
+
.option("--relay", "Enable relay on restarted daemon")
|
|
94
93
|
.option("--no-relay", "Disable relay on restarted daemon")
|
|
95
94
|
.option("--no-mcp", "Disable Agent MCP on restarted daemon")
|
|
96
95
|
.option("--hostnames <hosts>", 'Daemon hostnames (comma-separated, e.g. "myhost,.example.com" or "true" for any)')
|
|
@@ -108,13 +107,10 @@ export function createCli() {
|
|
|
108
107
|
program.addCommand(createDaemonCommand());
|
|
109
108
|
program.addCommand(createHubCommand());
|
|
110
109
|
// Chat commands
|
|
111
|
-
program.addCommand(createChatCommand());
|
|
112
110
|
// Terminal commands
|
|
113
111
|
program.addCommand(createTerminalCommand());
|
|
114
112
|
// Workspace script commands
|
|
115
113
|
program.addCommand(createScriptCommand());
|
|
116
|
-
// Loop commands
|
|
117
|
-
program.addCommand(createLoopCommand());
|
|
118
114
|
// Schedule commands
|
|
119
115
|
program.addCommand(createScheduleCommand());
|
|
120
116
|
program.addCommand(createHeartbeatCommand());
|
|
@@ -33,6 +33,7 @@ export function createDaemonCommand() {
|
|
|
33
33
|
.option("--force", "Send SIGKILL if graceful stop times out")
|
|
34
34
|
.option("--listen <listen>", "Listen target for restarted daemon (host:port, port, or unix socket)")
|
|
35
35
|
.option("--port <port>", "Port for restarted daemon listen target")
|
|
36
|
+
.option("--relay", "Enable relay on restarted daemon")
|
|
36
37
|
.option("--no-relay", "Disable relay on restarted daemon")
|
|
37
38
|
.option("--no-mcp", "Disable Agent MCP on restarted daemon")
|
|
38
39
|
.option("--no-inject-mcp", "Disable auto-injecting the Otto MCP into created agents")
|
|
@@ -2,8 +2,34 @@ import { Command } from "commander";
|
|
|
2
2
|
interface PairOptions {
|
|
3
3
|
home?: string;
|
|
4
4
|
json?: boolean;
|
|
5
|
+
relay?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface PairCommandDependencies {
|
|
8
|
+
resolveOffer: typeof resolveLocalPairingOffer;
|
|
9
|
+
confirmRelay: typeof confirmRelayPairing;
|
|
10
|
+
printDirectGuidance: typeof printDirectConnectionGuidance;
|
|
11
|
+
isInteractive: () => boolean;
|
|
12
|
+
output: PairCommandOutput;
|
|
13
|
+
}
|
|
14
|
+
export interface PairCommandOutput {
|
|
15
|
+
columns: number | undefined;
|
|
16
|
+
writeStdout(message: string): void;
|
|
17
|
+
writeStderr(message: string): void;
|
|
18
|
+
setExitCode(code: number): void;
|
|
19
|
+
success(message: string): void;
|
|
20
|
+
}
|
|
21
|
+
export interface PairingOffer {
|
|
22
|
+
relayEnabled: boolean;
|
|
23
|
+
url: string | null;
|
|
24
|
+
qr: string | null;
|
|
5
25
|
}
|
|
6
26
|
export declare function pairCommand(): Command;
|
|
7
|
-
export declare function
|
|
27
|
+
export declare function resolveLocalPairingOffer(options: {
|
|
28
|
+
ottoHome: string;
|
|
29
|
+
enableRelay?: boolean;
|
|
30
|
+
}): Promise<PairingOffer>;
|
|
31
|
+
export declare function confirmRelayPairing(): Promise<boolean>;
|
|
32
|
+
export declare function printDirectConnectionGuidance(): void;
|
|
33
|
+
export declare function runPairCommand(options: PairOptions, dependencyOverrides?: Partial<PairCommandDependencies>): Promise<void>;
|
|
8
34
|
export {};
|
|
9
35
|
//# sourceMappingURL=pair.d.ts.map
|
|
@@ -1,52 +1,53 @@
|
|
|
1
|
+
import { confirm, isCancel, log } from "@clack/prompts";
|
|
1
2
|
import { Command } from "commander";
|
|
2
3
|
import chalk from "chalk";
|
|
3
|
-
import { generateLocalPairingOffer, loadConfig, resolveOttoHome } from "@otto-code/server";
|
|
4
|
+
import { generateLocalPairingOffer, getOrCreateServerId, loadConfig, resolveOttoHome, } from "@otto-code/server";
|
|
4
5
|
import { tryConnectToDaemon } from "../../utils/client.js";
|
|
5
|
-
import { resolveLocalDaemonState
|
|
6
|
+
import { resolveLocalDaemonState } from "./local-daemon.js";
|
|
6
7
|
import { addJsonOption } from "../../utils/command-options.js";
|
|
7
8
|
import { formatPairingInstructions } from "../../output/pairing.js";
|
|
8
9
|
const PAIRING_DAEMON_RPC_TIMEOUT_MS = 1500;
|
|
10
|
+
const RELAY_DOCS_URL = "https://otto-code.me/docs/security";
|
|
11
|
+
function createProcessOutput() {
|
|
12
|
+
return {
|
|
13
|
+
columns: process.stdout.columns,
|
|
14
|
+
writeStdout(message) {
|
|
15
|
+
process.stdout.write(message);
|
|
16
|
+
},
|
|
17
|
+
writeStderr(message) {
|
|
18
|
+
process.stderr.write(message);
|
|
19
|
+
},
|
|
20
|
+
setExitCode(code) {
|
|
21
|
+
process.exitCode = code;
|
|
22
|
+
},
|
|
23
|
+
success(message) {
|
|
24
|
+
log.success(message);
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
9
28
|
export function pairCommand() {
|
|
10
29
|
return addJsonOption(new Command("pair").description("Print the daemon pairing QR code and link"))
|
|
11
30
|
.option("--home <path>", "Otto home directory (default: ~/.otto)")
|
|
31
|
+
.option("--relay", "Enable relay without prompting")
|
|
12
32
|
.action(async (_options, command) => {
|
|
13
33
|
await runPairCommand(command.optsWithGlobals());
|
|
14
34
|
});
|
|
15
35
|
}
|
|
16
|
-
export async function
|
|
17
|
-
|
|
18
|
-
|
|
36
|
+
export async function resolveLocalPairingOffer(options) {
|
|
37
|
+
const state = resolveLocalDaemonState({ home: options.ottoHome });
|
|
38
|
+
const serverId = getOrCreateServerId(state.home);
|
|
39
|
+
const daemonOffer = await resolveDaemonPairingOffer(state.listen, serverId, options.enableRelay);
|
|
40
|
+
if (daemonOffer)
|
|
41
|
+
return daemonOffer;
|
|
42
|
+
if (state.running) {
|
|
43
|
+
throw new Error("The running daemon did not provide a pairing offer. Check daemon connectivity or update the daemon.");
|
|
19
44
|
}
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
// Try to get the pairing offer from the running daemon first.
|
|
24
|
-
if (host) {
|
|
25
|
-
const client = await tryConnectToDaemon({ host, timeout: 1500 });
|
|
26
|
-
if (client) {
|
|
27
|
-
const supportsDaemonStatusRpc = client.getLastServerInfoMessage()?.features?.daemonStatusRpc === true;
|
|
28
|
-
if (supportsDaemonStatusRpc) {
|
|
29
|
-
try {
|
|
30
|
-
const offer = await client.getDaemonPairingOffer({
|
|
31
|
-
timeout: PAIRING_DAEMON_RPC_TIMEOUT_MS,
|
|
32
|
-
});
|
|
33
|
-
await client.close().catch(() => { });
|
|
34
|
-
outputPairingResult({ relayEnabled: offer.relayEnabled, url: offer.url, qr: offer.qr ?? null }, options);
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
// COMPAT(daemon-rpc-rollout): fall back to CLI-side pairing generation while
|
|
39
|
-
// old daemons lack daemonStatusRpc. Remove once the daemon floor is past
|
|
40
|
-
// v0.1.76; pairing should come from daemon.get_pairing_offer.
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
await client.close().catch(() => { });
|
|
44
|
-
}
|
|
45
|
+
const config = loadConfig(options.ottoHome);
|
|
46
|
+
if (options.enableRelay && !config.relayEnabled) {
|
|
47
|
+
throw new Error("Start the daemon before enabling relay for pairing.");
|
|
45
48
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const pairing = await generateLocalPairingOffer({
|
|
49
|
-
ottoHome,
|
|
49
|
+
return generateLocalPairingOffer({
|
|
50
|
+
ottoHome: options.ottoHome,
|
|
50
51
|
relayEnabled: config.relayEnabled,
|
|
51
52
|
relayEndpoint: config.relayEndpoint,
|
|
52
53
|
relayPublicEndpoint: config.relayPublicEndpoint,
|
|
@@ -55,26 +56,112 @@ export async function runPairCommand(options) {
|
|
|
55
56
|
appBaseUrl: config.appBaseUrl,
|
|
56
57
|
includeQr: true,
|
|
57
58
|
});
|
|
58
|
-
outputPairingResult(pairing, options);
|
|
59
59
|
}
|
|
60
|
-
function
|
|
60
|
+
async function resolveDaemonPairingOffer(listen, expectedServerId, enableRelay) {
|
|
61
|
+
const client = await tryConnectToDaemon({
|
|
62
|
+
host: listen,
|
|
63
|
+
timeout: PAIRING_DAEMON_RPC_TIMEOUT_MS,
|
|
64
|
+
});
|
|
65
|
+
if (!client)
|
|
66
|
+
return null;
|
|
67
|
+
try {
|
|
68
|
+
const serverInfo = client.getLastServerInfoMessage();
|
|
69
|
+
if (serverInfo?.serverId.trim() !== expectedServerId) {
|
|
70
|
+
throw new Error("The reachable daemon belongs to a different Otto home. Check --home or the daemon listen configuration.");
|
|
71
|
+
}
|
|
72
|
+
if (serverInfo?.features?.daemonStatusRpc !== true) {
|
|
73
|
+
throw new Error("Update the Otto daemon before pairing from this command.");
|
|
74
|
+
}
|
|
75
|
+
let offer = await client.getDaemonPairingOffer({
|
|
76
|
+
timeout: PAIRING_DAEMON_RPC_TIMEOUT_MS,
|
|
77
|
+
});
|
|
78
|
+
if (!offer.relayEnabled && enableRelay) {
|
|
79
|
+
if (serverInfo.features.relayConfig !== true) {
|
|
80
|
+
throw new Error("Update the Otto daemon before enabling relay from this command.");
|
|
81
|
+
}
|
|
82
|
+
await client.patchDaemonConfig({ relay: { enabled: true } });
|
|
83
|
+
offer = await client.getDaemonPairingOffer({
|
|
84
|
+
timeout: PAIRING_DAEMON_RPC_TIMEOUT_MS,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
relayEnabled: offer.relayEnabled,
|
|
89
|
+
url: offer.url || null,
|
|
90
|
+
qr: offer.qr ?? null,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
await client.close().catch(() => undefined);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
export async function confirmRelayPairing() {
|
|
98
|
+
log.message("Your connection is end-to-end encrypted. Otto cannot read your code or messages.");
|
|
99
|
+
log.message(`Learn how it works: ${RELAY_DOCS_URL}`);
|
|
100
|
+
const answer = await confirm({
|
|
101
|
+
message: "Enable relay to pair a device?",
|
|
102
|
+
initialValue: false,
|
|
103
|
+
});
|
|
104
|
+
return !isCancel(answer) && answer;
|
|
105
|
+
}
|
|
106
|
+
export function printDirectConnectionGuidance() {
|
|
107
|
+
console.log("Daemon is running with relay off.");
|
|
108
|
+
console.log("To connect another device directly, use the daemon's TCP address over your LAN, Tailscale, or another VPN.");
|
|
109
|
+
console.log(`Learn more: ${RELAY_DOCS_URL}#direct-connections`);
|
|
110
|
+
}
|
|
111
|
+
export async function runPairCommand(options, dependencyOverrides = {}) {
|
|
112
|
+
if (options.home)
|
|
113
|
+
process.env.OTTO_HOME = options.home;
|
|
114
|
+
const dependencies = {
|
|
115
|
+
resolveOffer: resolveLocalPairingOffer,
|
|
116
|
+
confirmRelay: confirmRelayPairing,
|
|
117
|
+
printDirectGuidance: printDirectConnectionGuidance,
|
|
118
|
+
isInteractive: () => Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
119
|
+
output: createProcessOutput(),
|
|
120
|
+
...dependencyOverrides,
|
|
121
|
+
};
|
|
122
|
+
const ottoHome = resolveOttoHome();
|
|
123
|
+
let pairing = await dependencies.resolveOffer({
|
|
124
|
+
ottoHome,
|
|
125
|
+
enableRelay: options.relay === true,
|
|
126
|
+
});
|
|
127
|
+
const canPrompt = dependencies.isInteractive() && options.json !== true;
|
|
128
|
+
if (!pairing.relayEnabled && canPrompt) {
|
|
129
|
+
const shouldEnable = await dependencies.confirmRelay();
|
|
130
|
+
if (!shouldEnable) {
|
|
131
|
+
dependencies.printDirectGuidance();
|
|
132
|
+
dependencies.output.writeStderr(`${chalk.yellow("No pairing QR was created.")}\n`);
|
|
133
|
+
dependencies.output.setExitCode(1);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
pairing = await dependencies.resolveOffer({ ottoHome, enableRelay: true });
|
|
137
|
+
dependencies.output.success("Relay enabled");
|
|
138
|
+
}
|
|
139
|
+
outputPairingResult(pairing, options, dependencies.output);
|
|
140
|
+
}
|
|
141
|
+
function outputPairingResult(pairing, options, output) {
|
|
61
142
|
if (!pairing.relayEnabled || !pairing.url) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
143
|
+
if (options.json) {
|
|
144
|
+
output.writeStderr(`${JSON.stringify({
|
|
145
|
+
code: "RELAY_DISABLED",
|
|
146
|
+
message: "Relay pairing is disabled for this daemon.",
|
|
147
|
+
action: "Run otto daemon pair --relay --json to enable it explicitly.",
|
|
148
|
+
})}\n`);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
output.writeStderr(`${chalk.red("Relay pairing is disabled for this daemon.")}\n`);
|
|
152
|
+
output.writeStderr(`${chalk.yellow("Run otto daemon pair --relay to enable it.")}\n`);
|
|
153
|
+
}
|
|
154
|
+
output.setExitCode(1);
|
|
155
|
+
return;
|
|
65
156
|
}
|
|
66
157
|
if (options.json) {
|
|
67
|
-
|
|
68
|
-
relayEnabled: pairing.relayEnabled,
|
|
69
|
-
url: pairing.url,
|
|
70
|
-
qr: pairing.qr,
|
|
71
|
-
}, null, 2)}\n`);
|
|
158
|
+
output.writeStdout(`${JSON.stringify({ relayEnabled: pairing.relayEnabled, url: pairing.url, qr: pairing.qr }, null, 2)}\n`);
|
|
72
159
|
return;
|
|
73
160
|
}
|
|
74
|
-
|
|
161
|
+
output.writeStdout(formatPairingInstructions({
|
|
75
162
|
url: pairing.url,
|
|
76
163
|
qr: pairing.qr,
|
|
77
|
-
columns:
|
|
164
|
+
columns: output.columns,
|
|
78
165
|
}));
|
|
79
166
|
}
|
|
80
167
|
//# sourceMappingURL=pair.js.map
|
|
@@ -9,6 +9,7 @@ export function startCommand() {
|
|
|
9
9
|
.option("--port <port>", "Port to listen on (default: 6868)")
|
|
10
10
|
.option("--home <path>", "Otto home directory (default: ~/.otto)")
|
|
11
11
|
.option("--foreground", "Run in foreground (don't daemonize)")
|
|
12
|
+
.option("--relay", "Enable relay connection")
|
|
12
13
|
.option("--no-relay", "Disable relay connection")
|
|
13
14
|
.option("--relay-use-tls", "Use wss:// for the relay connection and pairing offers")
|
|
14
15
|
.option("--no-mcp", "Disable the Agent MCP HTTP endpoint")
|
|
@@ -4,6 +4,17 @@ interface StatusRow {
|
|
|
4
4
|
key: string;
|
|
5
5
|
value: string;
|
|
6
6
|
}
|
|
7
|
+
interface RelayStatusConfig {
|
|
8
|
+
enabled: boolean;
|
|
9
|
+
endpoint: string;
|
|
10
|
+
publicEndpoint: string;
|
|
11
|
+
useTls: boolean;
|
|
12
|
+
publicUseTls: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function selectRelayStatus(input: {
|
|
15
|
+
persisted: RelayStatusConfig;
|
|
16
|
+
live?: RelayStatusConfig;
|
|
17
|
+
}): string;
|
|
7
18
|
export type StatusResult = ListResult<StatusRow>;
|
|
8
19
|
export declare function runStatusCommand(options: CommandOptions, _command: Command): Promise<StatusResult>;
|
|
9
20
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { getOrCreateServerId, findExecutable, execCommand } from "@otto-code/server";
|
|
3
3
|
import { connectToDaemon } from "../../utils/client.js";
|
|
4
|
-
import { resolveLocalDaemonState
|
|
4
|
+
import { resolveLocalDaemonState } from "./local-daemon.js";
|
|
5
5
|
import { resolveNodePathFromPid } from "./runtime-toolchain.js";
|
|
6
6
|
const DAEMON_STATUS_PROBE_TIMEOUT_MS = 1500;
|
|
7
7
|
const require = createRequire(import.meta.url);
|
|
@@ -203,12 +203,19 @@ async function probeDaemonOverWebsocket(args) {
|
|
|
203
203
|
version: p.available ? null : (p.error ?? null),
|
|
204
204
|
source: "daemon",
|
|
205
205
|
}));
|
|
206
|
+
const relayStatus = statusPayload.relay == null
|
|
207
|
+
? undefined
|
|
208
|
+
: selectRelayStatus({
|
|
209
|
+
persisted: relayConfigFromLocalState(state),
|
|
210
|
+
live: statusPayload.relay,
|
|
211
|
+
});
|
|
206
212
|
if (!state.running) {
|
|
207
213
|
return {
|
|
208
214
|
connectedDaemon: "reachable",
|
|
209
215
|
daemonVersion: statusPayload.version ?? daemonVersion,
|
|
210
216
|
daemonNodeOverride: statusPayload.nodePath,
|
|
211
217
|
daemonProviders,
|
|
218
|
+
relayStatus,
|
|
212
219
|
note: state.pidInfo
|
|
213
220
|
? `Connected daemon is reachable at ${host} even though local daemon PID ${state.pidInfo.pid} is stale`
|
|
214
221
|
: `Connected daemon is reachable at ${host} but no local daemon PID file was found`,
|
|
@@ -219,6 +226,7 @@ async function probeDaemonOverWebsocket(args) {
|
|
|
219
226
|
daemonVersion: statusPayload.version ?? daemonVersion,
|
|
220
227
|
daemonNodeOverride: statusPayload.nodePath,
|
|
221
228
|
daemonProviders,
|
|
229
|
+
relayStatus,
|
|
222
230
|
};
|
|
223
231
|
}
|
|
224
232
|
catch {
|
|
@@ -242,6 +250,7 @@ function applyProbeToStatus(input) {
|
|
|
242
250
|
daemonNode: probe.daemonNodeOverride ?? input.daemonNode,
|
|
243
251
|
daemonVersion: probe.daemonVersion !== undefined ? probe.daemonVersion : input.daemonVersion,
|
|
244
252
|
daemonProviders: probe.daemonProviders ?? input.daemonProviders,
|
|
253
|
+
relayStatus: probe.relayStatus ?? input.relayStatus,
|
|
245
254
|
note: probe.note ? appendNote(input.note, probe.note) : input.note,
|
|
246
255
|
};
|
|
247
256
|
}
|
|
@@ -264,16 +273,26 @@ async function resolveDaemonNodeLabel(state) {
|
|
|
264
273
|
const fromPid = await resolveNodePathFromPid(state.pidInfo.pid);
|
|
265
274
|
return fromPid.nodePath ?? `unknown (${fromPid.error ?? "could not resolve from PID"})`;
|
|
266
275
|
}
|
|
267
|
-
function
|
|
268
|
-
|
|
276
|
+
function relayConfigFromLocalState(state) {
|
|
277
|
+
return {
|
|
278
|
+
enabled: state.relayEnabled,
|
|
279
|
+
endpoint: state.relayEndpoint,
|
|
280
|
+
publicEndpoint: state.relayEndpoint,
|
|
281
|
+
useTls: state.relayUseTls,
|
|
282
|
+
publicUseTls: state.relayPublicUseTls,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
export function selectRelayStatus(input) {
|
|
286
|
+
const relay = input.live ?? input.persisted;
|
|
287
|
+
if (!relay.enabled)
|
|
269
288
|
return "disabled";
|
|
270
|
-
const scheme =
|
|
271
|
-
return `${scheme}://${
|
|
289
|
+
const scheme = relay.publicUseTls ? "wss" : "ws";
|
|
290
|
+
return `${scheme}://${relay.publicEndpoint}`;
|
|
272
291
|
}
|
|
273
292
|
export async function runStatusCommand(options, _command) {
|
|
274
293
|
const home = typeof options.home === "string" ? options.home : undefined;
|
|
275
294
|
const state = resolveLocalDaemonState({ home });
|
|
276
|
-
const
|
|
295
|
+
const daemonTarget = state.listen.trim();
|
|
277
296
|
const owner = resolveOwnerLabel(state.pidInfo?.uid, state.pidInfo?.hostname);
|
|
278
297
|
let daemonNode = await resolveDaemonNodeLabel(state);
|
|
279
298
|
const cliNode = process.execPath;
|
|
@@ -281,26 +300,32 @@ export async function runStatusCommand(options, _command) {
|
|
|
281
300
|
let connectedDaemon = "not_probed";
|
|
282
301
|
let daemonVersion = null;
|
|
283
302
|
let daemonProviders;
|
|
303
|
+
let relayStatus = selectRelayStatus({ persisted: relayConfigFromLocalState(state) });
|
|
284
304
|
let note;
|
|
285
305
|
if (!state.running && state.stalePidFile && state.pidInfo) {
|
|
286
306
|
localDaemon = "stale_pid";
|
|
287
307
|
note = `Stale PID file found for PID ${state.pidInfo.pid}`;
|
|
288
308
|
}
|
|
289
|
-
if (
|
|
290
|
-
const probe = await probeDaemonOverWebsocket({ host, state });
|
|
291
|
-
({
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
309
|
+
if (daemonTarget) {
|
|
310
|
+
const probe = await probeDaemonOverWebsocket({ host: daemonTarget, state });
|
|
311
|
+
({
|
|
312
|
+
connectedDaemon,
|
|
313
|
+
localDaemon,
|
|
314
|
+
daemonNode,
|
|
315
|
+
daemonVersion,
|
|
316
|
+
daemonProviders,
|
|
317
|
+
relayStatus,
|
|
318
|
+
note,
|
|
319
|
+
} = applyProbeToStatus({
|
|
320
|
+
probe,
|
|
321
|
+
connectedDaemon,
|
|
322
|
+
localDaemon,
|
|
323
|
+
daemonNode,
|
|
324
|
+
daemonVersion,
|
|
325
|
+
daemonProviders,
|
|
326
|
+
relayStatus,
|
|
327
|
+
note,
|
|
328
|
+
}));
|
|
304
329
|
}
|
|
305
330
|
const cliVersion = resolveCliVersion();
|
|
306
331
|
const serverIdResult = resolveServerIdSafely(state.home);
|
|
@@ -315,7 +340,7 @@ export async function runStatusCommand(options, _command) {
|
|
|
315
340
|
connectedDaemon,
|
|
316
341
|
home: state.home,
|
|
317
342
|
listen: state.listen,
|
|
318
|
-
relay:
|
|
343
|
+
relay: relayStatus,
|
|
319
344
|
hostname: state.pidInfo?.hostname ?? null,
|
|
320
345
|
pid: state.pidInfo?.pid ?? null,
|
|
321
346
|
startedAt: state.pidInfo?.startedAt ?? null,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { HubCredentialStore } from "./credentials.js";
|
|
2
|
+
export interface HubAuthorityOptions {
|
|
3
|
+
origin?: string;
|
|
4
|
+
apiKey?: string;
|
|
5
|
+
}
|
|
6
|
+
interface ResolveHubInput {
|
|
7
|
+
options: HubAuthorityOptions;
|
|
8
|
+
env: Readonly<Record<string, string | undefined>>;
|
|
9
|
+
credentials: HubCredentialStore;
|
|
10
|
+
}
|
|
11
|
+
export declare const DEFAULT_HUB_ORIGIN = "https://hub.otto-code.me";
|
|
12
|
+
export declare function resolveHubOrigin(input: ResolveHubInput): string;
|
|
13
|
+
export declare function resolveHubCredential(input: ResolveHubInput & {
|
|
14
|
+
origin: string;
|
|
15
|
+
}): string;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=authority.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { HubCommandError } from "./error.js";
|
|
2
|
+
import { normalizeHubOrigin } from "./origin.js";
|
|
3
|
+
export const DEFAULT_HUB_ORIGIN = "https://hub.otto-code.me";
|
|
4
|
+
export function resolveHubOrigin(input) {
|
|
5
|
+
const configuredOrigin = input.options.origin ?? input.env.OTTO_HUB_URL;
|
|
6
|
+
const selectedOrigin = configuredOrigin ?? input.credentials.active()?.origin ?? DEFAULT_HUB_ORIGIN;
|
|
7
|
+
return normalizeHubOrigin(selectedOrigin);
|
|
8
|
+
}
|
|
9
|
+
export function resolveHubCredential(input) {
|
|
10
|
+
const explicitCredential = input.options.apiKey ?? input.env.OTTO_HUB_API_KEY;
|
|
11
|
+
if (explicitCredential !== undefined)
|
|
12
|
+
return explicitCredential;
|
|
13
|
+
const stored = input.credentials.get(input.origin);
|
|
14
|
+
if (stored !== null)
|
|
15
|
+
return stored.credential;
|
|
16
|
+
throw new HubCommandError("HUB_API_KEY_REQUIRED", `No stored Hub login matches ${input.origin}. Run \`otto hub login ${input.origin}\`, pass --api-key <secret>, or set OTTO_HUB_API_KEY.`);
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=authority.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import type { HubHttpClient } from "./hub-client/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 HubConnectOptions {
|
|
7
|
+
apiKey?: string;
|
|
8
|
+
host?: string;
|
|
9
|
+
json?: boolean;
|
|
10
|
+
}
|
|
11
|
+
interface HubConnectDependencies {
|
|
12
|
+
env: Readonly<Record<string, string | undefined>>;
|
|
13
|
+
credentials: HubCredentialStore;
|
|
14
|
+
hub: Pick<HubHttpClient, "issueEnrollmentToken">;
|
|
15
|
+
daemon: HubDaemonConnection;
|
|
16
|
+
reporter: HubReporter;
|
|
17
|
+
}
|
|
18
|
+
export declare function runHubConnect(originInput: string | undefined, options: HubConnectOptions, dependencies: HubConnectDependencies): Promise<import("../../output/types.js").ListResult<import("./status-output.js").HubRow>>;
|
|
19
|
+
export declare function addHubConnectCommand(parent: Command, dependencies: HubConnectDependencies): void;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=connect.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { withOutput } from "../../output/index.js";
|
|
2
|
+
import { addJsonAndDaemonHostOptions } from "../../utils/command-options.js";
|
|
3
|
+
import { resolveHubCredential, resolveHubOrigin } from "./authority.js";
|
|
4
|
+
import { withHubDaemon } from "./daemon-client.js";
|
|
5
|
+
import { hubStatusResult } from "./status-output.js";
|
|
6
|
+
import { reportHubProgress } from "./reporter.js";
|
|
7
|
+
import { addHubResolutionHelp } from "./help.js";
|
|
8
|
+
export async function runHubConnect(originInput, options, dependencies) {
|
|
9
|
+
const resolution = {
|
|
10
|
+
options: { origin: originInput, apiKey: options.apiKey },
|
|
11
|
+
env: dependencies.env,
|
|
12
|
+
credentials: dependencies.credentials,
|
|
13
|
+
};
|
|
14
|
+
const origin = resolveHubOrigin(resolution);
|
|
15
|
+
reportHubProgress(dependencies.reporter, options, `Connecting this daemon to ${origin}`);
|
|
16
|
+
const credential = resolveHubCredential({ ...resolution, origin });
|
|
17
|
+
const token = await dependencies.hub.issueEnrollmentToken(origin, credential);
|
|
18
|
+
return withHubDaemon(dependencies.daemon, options.host, async (daemon) => {
|
|
19
|
+
const response = await daemon.connectHub(origin, token);
|
|
20
|
+
return hubStatusResult(response.status);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
export function addHubConnectCommand(parent, dependencies) {
|
|
24
|
+
addJsonAndDaemonHostOptions(addHubResolutionHelp(parent
|
|
25
|
+
.command("connect")
|
|
26
|
+
.description("Enroll this daemon with a Paseo Hub")
|
|
27
|
+
.argument("[origin]", "Paseo Hub origin")
|
|
28
|
+
.option("--api-key <secret>", "Organization API key"))).action(withOutput(async (...args) => {
|
|
29
|
+
const origin = args[0];
|
|
30
|
+
const options = args.at(-2);
|
|
31
|
+
return runHubConnect(origin, options, dependencies);
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=connect.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface StoredHubCredential {
|
|
2
|
+
origin: string;
|
|
3
|
+
credential: string;
|
|
4
|
+
}
|
|
5
|
+
export interface HubCredentialStore {
|
|
6
|
+
active(): StoredHubCredential | null;
|
|
7
|
+
get(origin: string): StoredHubCredential | null;
|
|
8
|
+
save(credential: StoredHubCredential): void;
|
|
9
|
+
logoutActive(): StoredHubCredential | null;
|
|
10
|
+
}
|
|
11
|
+
export declare class PrivateHubCredentialStore implements HubCredentialStore {
|
|
12
|
+
private readonly filePath;
|
|
13
|
+
constructor(env?: Readonly<Record<string, string | undefined>>);
|
|
14
|
+
active(): StoredHubCredential | null;
|
|
15
|
+
get(origin: string): StoredHubCredential | null;
|
|
16
|
+
save(credential: StoredHubCredential): void;
|
|
17
|
+
logoutActive(): StoredHubCredential | null;
|
|
18
|
+
private read;
|
|
19
|
+
private write;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=credentials.d.ts.map
|