botapp-cli 0.1.2 → 0.2.1
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/bin/bot.js +112 -183
- package/dist/bin/bot.js.map +1 -1
- package/dist/index.js +112 -183
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -719,34 +719,8 @@ Configured for ${serverUrl}`));
|
|
|
719
719
|
return;
|
|
720
720
|
}
|
|
721
721
|
const profileName = pickProfileName(serverUrl);
|
|
722
|
-
if (health.mode === "local" && health.token) {
|
|
723
|
-
const config2 = loadProfile();
|
|
724
|
-
config2.profiles[profileName] = {
|
|
725
|
-
server: serverUrl,
|
|
726
|
-
app_url: appUrl ?? serverUrl,
|
|
727
|
-
token: health.token,
|
|
728
|
-
user_id: "local",
|
|
729
|
-
agent_id: "local-agent"
|
|
730
|
-
};
|
|
731
|
-
config2.active_profile = profileName;
|
|
732
|
-
saveProfile(config2);
|
|
733
|
-
console.log(pc4.green("Logged in (local mode)"));
|
|
734
|
-
console.log(` Profile: ${pc4.bold(profileName)}`);
|
|
735
|
-
console.log(` Server: ${pc4.cyan(serverUrl)}`);
|
|
736
|
-
const paired = await tryLocalDaemonPair(serverUrl, hostname());
|
|
737
|
-
if (paired) {
|
|
738
|
-
console.log(
|
|
739
|
-
` Daemon: ${pc4.bold(paired.name)} ${pc4.dim(`(${paired.id})`)}`
|
|
740
|
-
);
|
|
741
|
-
await autoRegisterAgents(opts);
|
|
742
|
-
await autoStartDaemon(opts, serverUrl, paired.id);
|
|
743
|
-
} else {
|
|
744
|
-
console.log(pc4.yellow(` Daemon: pairing failed \u2014 run \`bot pair\` later`));
|
|
745
|
-
}
|
|
746
|
-
return;
|
|
747
|
-
}
|
|
748
722
|
if (opts.browser === false) {
|
|
749
|
-
console.error(pc4.red("
|
|
723
|
+
console.error(pc4.red("Login needs browser auth but --no-browser was passed."));
|
|
750
724
|
process.exitCode = 1;
|
|
751
725
|
return;
|
|
752
726
|
}
|
|
@@ -910,35 +884,6 @@ async function autoRegisterAgents(opts) {
|
|
|
910
884
|
console.log(pc4.yellow(` Agents: registration failed (${e.message})`));
|
|
911
885
|
}
|
|
912
886
|
}
|
|
913
|
-
async function tryLocalDaemonPair(serverUrl, name) {
|
|
914
|
-
try {
|
|
915
|
-
const tokRes = await fetch(`${serverUrl}/api/daemon/pairing-tokens`, {
|
|
916
|
-
method: "POST",
|
|
917
|
-
headers: { "Content-Type": "application/json" },
|
|
918
|
-
body: JSON.stringify({ name })
|
|
919
|
-
});
|
|
920
|
-
if (!tokRes.ok) return null;
|
|
921
|
-
const tokData = await tokRes.json();
|
|
922
|
-
if (!tokData.token) return null;
|
|
923
|
-
const pairRes = await fetch(`${serverUrl}/api/daemon/pair`, {
|
|
924
|
-
method: "POST",
|
|
925
|
-
headers: { "Content-Type": "application/json" },
|
|
926
|
-
body: JSON.stringify({ token: tokData.token, name, machine: name })
|
|
927
|
-
});
|
|
928
|
-
if (!pairRes.ok) return null;
|
|
929
|
-
const data = await pairRes.json();
|
|
930
|
-
if (!data.daemon?.id || !data.token) return null;
|
|
931
|
-
saveDaemonProfile({
|
|
932
|
-
server: serverUrl,
|
|
933
|
-
daemonId: data.daemon.id,
|
|
934
|
-
daemonName: data.daemon.name,
|
|
935
|
-
token: data.token
|
|
936
|
-
});
|
|
937
|
-
return { id: data.daemon.id, name: data.daemon.name };
|
|
938
|
-
} catch {
|
|
939
|
-
return null;
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
887
|
function pickProfileName(serverUrl) {
|
|
943
888
|
try {
|
|
944
889
|
const host = new URL(serverUrl).hostname;
|
|
@@ -953,7 +898,7 @@ async function fetchHealth(serverUrl) {
|
|
|
953
898
|
try {
|
|
954
899
|
const res = await fetch(`${serverUrl}/health`, { signal: AbortSignal.timeout(5e3) });
|
|
955
900
|
if (!res.ok) return null;
|
|
956
|
-
return
|
|
901
|
+
return { ok: true };
|
|
957
902
|
} catch {
|
|
958
903
|
return null;
|
|
959
904
|
}
|
|
@@ -1849,106 +1794,8 @@ var skillCommand = skillSingle;
|
|
|
1849
1794
|
|
|
1850
1795
|
// src/commands/pairing.ts
|
|
1851
1796
|
import { hostname as hostname2 } from "os";
|
|
1852
|
-
import { Command as
|
|
1853
|
-
import
|
|
1854
|
-
var pairingCommand = new Command17("pairing").alias("pair").description("Pair this machine as a botapp daemon").option("--token <token>", "Pairing token (skip browser flow)").option("--name <name>", "Daemon name", hostname2()).option("--app-url <url>", "Dashboard URL (defaults to server URL)").option("--no-browser", "Error instead of opening a browser").action(async (opts, cmd) => {
|
|
1855
|
-
const globalOpts = cmd.parent?.opts() ?? {};
|
|
1856
|
-
const serverUrl = resolveServerUrl(globalOpts.server);
|
|
1857
|
-
const name = opts.name;
|
|
1858
|
-
try {
|
|
1859
|
-
const grant = await obtainPairingToken({
|
|
1860
|
-
serverUrl,
|
|
1861
|
-
appUrl: opts.appUrl,
|
|
1862
|
-
name,
|
|
1863
|
-
explicitToken: opts.token,
|
|
1864
|
-
allowBrowser: opts.browser !== false
|
|
1865
|
-
});
|
|
1866
|
-
if (!grant) {
|
|
1867
|
-
console.error(
|
|
1868
|
-
pc18.red(
|
|
1869
|
-
"No pairing token and browser flow disabled. Pass --token, or drop --no-browser."
|
|
1870
|
-
)
|
|
1871
|
-
);
|
|
1872
|
-
process.exitCode = 1;
|
|
1873
|
-
return;
|
|
1874
|
-
}
|
|
1875
|
-
if (grant.userEmail) {
|
|
1876
|
-
console.log(pc18.green(`Authenticated as ${pc18.bold(grant.userEmail)}`));
|
|
1877
|
-
}
|
|
1878
|
-
const res = await fetch(`${serverUrl}/api/daemon/pair`, {
|
|
1879
|
-
method: "POST",
|
|
1880
|
-
headers: { "Content-Type": "application/json" },
|
|
1881
|
-
body: JSON.stringify({
|
|
1882
|
-
token: grant.pairingToken,
|
|
1883
|
-
name,
|
|
1884
|
-
machine: hostname2()
|
|
1885
|
-
})
|
|
1886
|
-
});
|
|
1887
|
-
const data = await res.json().catch(() => ({}));
|
|
1888
|
-
if (!res.ok) {
|
|
1889
|
-
console.error(pc18.red(`Error: ${data.error ?? res.statusText}`));
|
|
1890
|
-
process.exitCode = 1;
|
|
1891
|
-
return;
|
|
1892
|
-
}
|
|
1893
|
-
saveDaemonProfile({
|
|
1894
|
-
server: serverUrl,
|
|
1895
|
-
daemonId: data.daemon.id,
|
|
1896
|
-
daemonName: data.daemon.name,
|
|
1897
|
-
token: data.token
|
|
1898
|
-
});
|
|
1899
|
-
console.log(pc18.green(`Paired daemon: ${pc18.bold(data.daemon.name)}`));
|
|
1900
|
-
console.log(` ID: ${data.daemon.id}`);
|
|
1901
|
-
console.log(` Server: ${serverUrl}`);
|
|
1902
|
-
console.log(pc18.dim("\nNext: botapp daemon agent add codex --command codex"));
|
|
1903
|
-
console.log(pc18.dim("Then: botapp daemon run"));
|
|
1904
|
-
} catch (e) {
|
|
1905
|
-
console.error(pc18.red(`Error: ${e.message}`));
|
|
1906
|
-
process.exitCode = 1;
|
|
1907
|
-
}
|
|
1908
|
-
});
|
|
1909
|
-
async function obtainPairingToken(opts) {
|
|
1910
|
-
if (opts.explicitToken) return { pairingToken: opts.explicitToken };
|
|
1911
|
-
const localToken = await tryLocalFastPath(opts.serverUrl, opts.name);
|
|
1912
|
-
if (localToken) return { pairingToken: localToken };
|
|
1913
|
-
if (!opts.allowBrowser) return null;
|
|
1914
|
-
const appUrl = opts.appUrl ?? opts.serverUrl;
|
|
1915
|
-
const result = await runBrowserAuth({
|
|
1916
|
-
serverUrl: opts.serverUrl,
|
|
1917
|
-
appUrl,
|
|
1918
|
-
scope: "pair",
|
|
1919
|
-
name: opts.name
|
|
1920
|
-
});
|
|
1921
|
-
if (!result.pairingToken) {
|
|
1922
|
-
throw new Error("server returned no pairing token");
|
|
1923
|
-
}
|
|
1924
|
-
return { pairingToken: result.pairingToken, userEmail: result.userEmail };
|
|
1925
|
-
}
|
|
1926
|
-
async function tryLocalFastPath(serverUrl, name) {
|
|
1927
|
-
let mode = null;
|
|
1928
|
-
try {
|
|
1929
|
-
const res = await fetch(`${serverUrl}/health`, {
|
|
1930
|
-
signal: AbortSignal.timeout(3e3)
|
|
1931
|
-
});
|
|
1932
|
-
if (!res.ok) return null;
|
|
1933
|
-
const data = await res.json().catch(() => ({}));
|
|
1934
|
-
mode = data.mode ?? null;
|
|
1935
|
-
} catch {
|
|
1936
|
-
return null;
|
|
1937
|
-
}
|
|
1938
|
-
if (mode !== "local") return null;
|
|
1939
|
-
try {
|
|
1940
|
-
const res = await fetch(`${serverUrl}/api/daemon/pairing-tokens`, {
|
|
1941
|
-
method: "POST",
|
|
1942
|
-
headers: { "Content-Type": "application/json" },
|
|
1943
|
-
body: JSON.stringify({ name })
|
|
1944
|
-
});
|
|
1945
|
-
if (!res.ok) return null;
|
|
1946
|
-
const data = await res.json().catch(() => ({}));
|
|
1947
|
-
return data.token ?? null;
|
|
1948
|
-
} catch {
|
|
1949
|
-
return null;
|
|
1950
|
-
}
|
|
1951
|
-
}
|
|
1797
|
+
import { Command as Command18 } from "commander";
|
|
1798
|
+
import pc19 from "picocolors";
|
|
1952
1799
|
|
|
1953
1800
|
// src/commands/daemon.ts
|
|
1954
1801
|
import { spawn as spawn5 } from "child_process";
|
|
@@ -1957,14 +1804,14 @@ import { existsSync as existsSync10, readdirSync, readFileSync as readFileSync8,
|
|
|
1957
1804
|
import { homedir as homedir7 } from "os";
|
|
1958
1805
|
import { join as join7, resolve as resolve6 } from "path";
|
|
1959
1806
|
import { createInterface as createInterface3 } from "readline";
|
|
1960
|
-
import { Command as
|
|
1807
|
+
import { Command as Command17 } from "commander";
|
|
1961
1808
|
import { WebSocket as WebSocket2 } from "ws";
|
|
1962
|
-
import
|
|
1963
|
-
var daemonCommand = new
|
|
1809
|
+
import pc18 from "picocolors";
|
|
1810
|
+
var daemonCommand = new Command17("daemon").description("Manage and run the local botapp daemon");
|
|
1964
1811
|
daemonCommand.command("run").description("Run the local daemon and wait for server jobs").action(async () => {
|
|
1965
1812
|
const profile = loadDaemonProfile();
|
|
1966
1813
|
if (!profile) {
|
|
1967
|
-
console.error(
|
|
1814
|
+
console.error(pc18.red("No paired daemon found. Run `bot pair` first."));
|
|
1968
1815
|
process.exitCode = 1;
|
|
1969
1816
|
return;
|
|
1970
1817
|
}
|
|
@@ -1973,37 +1820,37 @@ daemonCommand.command("run").description("Run the local daemon and wait for serv
|
|
|
1973
1820
|
daemonCommand.command("stop").description("Stop the background daemon started by `bot launch`").action(async () => {
|
|
1974
1821
|
const pidFile = join7(homedir7(), ".botapp", "daemon.pid");
|
|
1975
1822
|
if (!existsSync10(pidFile)) {
|
|
1976
|
-
console.log(
|
|
1823
|
+
console.log(pc18.yellow("No background daemon PID file found."));
|
|
1977
1824
|
return;
|
|
1978
1825
|
}
|
|
1979
1826
|
try {
|
|
1980
1827
|
const pid = parseInt(readFileSync8(pidFile, "utf-8").trim(), 10);
|
|
1981
1828
|
if (!Number.isInteger(pid) || pid <= 0) {
|
|
1982
|
-
console.error(
|
|
1829
|
+
console.error(pc18.red(`Invalid PID in ${pidFile}`));
|
|
1983
1830
|
process.exitCode = 1;
|
|
1984
1831
|
return;
|
|
1985
1832
|
}
|
|
1986
1833
|
process.kill(pid, "SIGTERM");
|
|
1987
|
-
console.log(
|
|
1834
|
+
console.log(pc18.green(`Stopped daemon (pid ${pid})`));
|
|
1988
1835
|
} catch (e) {
|
|
1989
|
-
console.error(
|
|
1836
|
+
console.error(pc18.red(`Failed to stop daemon: ${e.message}`));
|
|
1990
1837
|
process.exitCode = 1;
|
|
1991
1838
|
}
|
|
1992
1839
|
});
|
|
1993
1840
|
daemonCommand.command("agent").description("Manage agents hosted by this daemon").addCommand(
|
|
1994
|
-
new
|
|
1841
|
+
new Command17("list").description("List registered daemon agents").action(async () => {
|
|
1995
1842
|
const profile = requireProfile();
|
|
1996
1843
|
if (!profile) return;
|
|
1997
1844
|
const data = await daemonRequest(profile.server, profile.token, "/api/daemon/self/agents");
|
|
1998
1845
|
for (const agent of data.agents ?? []) {
|
|
1999
|
-
console.log(`${
|
|
1846
|
+
console.log(`${pc18.bold(agent.name)} ${pc18.dim(`(${agent.id})`)}`);
|
|
2000
1847
|
console.log(` Kind: ${agent.kind}`);
|
|
2001
1848
|
console.log(` Command: ${agent.command} ${(agent.args ?? []).join(" ")}`);
|
|
2002
1849
|
if (agent.cwd) console.log(` CWD: ${agent.cwd}`);
|
|
2003
1850
|
}
|
|
2004
1851
|
})
|
|
2005
1852
|
).addCommand(createDaemonAgentConfigCommand()).addCommand(
|
|
2006
|
-
new
|
|
1853
|
+
new Command17("add").description("Register a local agent command").argument("<name>", "Name, e.g. codex, claude-code, openclaw, hermes, or hermes-agent").requiredOption("--command <command>", "Executable command").option(
|
|
2007
1854
|
"--kind <kind>",
|
|
2008
1855
|
"Agent adapter kind: acp, codex, claude-code, openclaw, hermes, hermes-agent, or shell",
|
|
2009
1856
|
"acp"
|
|
@@ -2027,12 +1874,12 @@ daemonCommand.command("agent").description("Manage agents hosted by this daemon"
|
|
|
2027
1874
|
}
|
|
2028
1875
|
}
|
|
2029
1876
|
);
|
|
2030
|
-
console.log(
|
|
1877
|
+
console.log(pc18.green(`Registered daemon agent: ${pc18.bold(data.agent.name)}`));
|
|
2031
1878
|
console.log(` ID: ${data.agent.id}`);
|
|
2032
1879
|
console.log(` Command: ${data.agent.command} ${(data.agent.args ?? []).join(" ")}`);
|
|
2033
1880
|
})
|
|
2034
1881
|
).addCommand(
|
|
2035
|
-
new
|
|
1882
|
+
new Command17("remove").alias("rm").description("Remove a daemon agent by id or name").argument("<agent>", "Daemon agent id or name").action(async (agent) => {
|
|
2036
1883
|
const profile = requireProfile();
|
|
2037
1884
|
if (!profile) return;
|
|
2038
1885
|
const data = await daemonRequest(
|
|
@@ -2041,7 +1888,7 @@ daemonCommand.command("agent").description("Manage agents hosted by this daemon"
|
|
|
2041
1888
|
`/api/daemon/self/agents/${encodeURIComponent(agent)}`,
|
|
2042
1889
|
{ method: "DELETE" }
|
|
2043
1890
|
);
|
|
2044
|
-
console.log(
|
|
1891
|
+
console.log(pc18.green(`Removed daemon agent: ${pc18.bold(data.agent.name)}`));
|
|
2045
1892
|
console.log(` ID: ${data.agent.id}`);
|
|
2046
1893
|
})
|
|
2047
1894
|
);
|
|
@@ -2068,7 +1915,7 @@ async function runDaemonConnectionLoop(profile) {
|
|
|
2068
1915
|
process.once("SIGINT", stop);
|
|
2069
1916
|
process.once("SIGTERM", stop);
|
|
2070
1917
|
while (!stopped) {
|
|
2071
|
-
console.log(
|
|
1918
|
+
console.log(pc18.blue(`Connecting daemon ${profile.daemonName} to ${profile.server}...`));
|
|
2072
1919
|
const outcome = await runDaemonSocket(wsUrl, (ws) => {
|
|
2073
1920
|
activeWs = ws;
|
|
2074
1921
|
});
|
|
@@ -2076,7 +1923,7 @@ async function runDaemonConnectionLoop(profile) {
|
|
|
2076
1923
|
if (stopped) break;
|
|
2077
1924
|
const waitMs = outcome.opened ? baseRetryMs : retryMs;
|
|
2078
1925
|
retryMs = outcome.opened ? baseRetryMs : Math.min(retryMs * 2, maxRetryMs);
|
|
2079
|
-
console.log(
|
|
1926
|
+
console.log(pc18.yellow(`Retrying daemon connection in ${Math.round(waitMs / 1e3)}s...`));
|
|
2080
1927
|
await new Promise((resolveWait) => {
|
|
2081
1928
|
resolveRetry = resolveWait;
|
|
2082
1929
|
retryTimer = setTimeout(() => {
|
|
@@ -2102,7 +1949,7 @@ function runDaemonSocket(wsUrl, setActiveWs) {
|
|
|
2102
1949
|
}
|
|
2103
1950
|
ws.on("open", () => {
|
|
2104
1951
|
opened = true;
|
|
2105
|
-
console.log(
|
|
1952
|
+
console.log(pc18.green("Daemon connected"));
|
|
2106
1953
|
ws.send(JSON.stringify({ type: "daemon_ready" }));
|
|
2107
1954
|
ping = setInterval(() => {
|
|
2108
1955
|
if (ws.readyState === WebSocket2.OPEN) ws.send(JSON.stringify({ type: "ping" }));
|
|
@@ -2112,11 +1959,11 @@ function runDaemonSocket(wsUrl, setActiveWs) {
|
|
|
2112
1959
|
void handleFrame(ws, raw.toString());
|
|
2113
1960
|
});
|
|
2114
1961
|
ws.on("close", (code, reason) => {
|
|
2115
|
-
console.log(
|
|
1962
|
+
console.log(pc18.yellow(`Daemon disconnected (${code}: ${reason.toString() || "no reason"})`));
|
|
2116
1963
|
finish();
|
|
2117
1964
|
});
|
|
2118
1965
|
ws.on("error", (err) => {
|
|
2119
|
-
console.error(
|
|
1966
|
+
console.error(pc18.red(`Daemon WebSocket error: ${err.message}`));
|
|
2120
1967
|
if (!opened) {
|
|
2121
1968
|
if (ws.readyState !== WebSocket2.CLOSED) ws.terminate();
|
|
2122
1969
|
finish();
|
|
@@ -2128,7 +1975,7 @@ async function handleFrame(ws, raw) {
|
|
|
2128
1975
|
const frame = JSON.parse(raw);
|
|
2129
1976
|
if (frame.type === "daemon_job") {
|
|
2130
1977
|
const job = frame.job;
|
|
2131
|
-
console.log(
|
|
1978
|
+
console.log(pc18.blue(`Running ${job.agent.name} job ${job.id}`));
|
|
2132
1979
|
ws.send(JSON.stringify({ type: "daemon_job_started", jobId: job.id }));
|
|
2133
1980
|
try {
|
|
2134
1981
|
const result = await runAgentJob(job, (update) => {
|
|
@@ -2146,7 +1993,7 @@ async function handleFrame(ws, raw) {
|
|
|
2146
1993
|
status: "succeeded",
|
|
2147
1994
|
result
|
|
2148
1995
|
}));
|
|
2149
|
-
console.log(
|
|
1996
|
+
console.log(pc18.green(`Completed job ${job.id}`));
|
|
2150
1997
|
} catch (e) {
|
|
2151
1998
|
ws.send(JSON.stringify({
|
|
2152
1999
|
type: "daemon_job_result",
|
|
@@ -2154,12 +2001,12 @@ async function handleFrame(ws, raw) {
|
|
|
2154
2001
|
status: "failed",
|
|
2155
2002
|
error: e.message
|
|
2156
2003
|
}));
|
|
2157
|
-
console.error(
|
|
2004
|
+
console.error(pc18.red(`Job ${job.id} failed: ${e.message}`));
|
|
2158
2005
|
}
|
|
2159
2006
|
return;
|
|
2160
2007
|
}
|
|
2161
2008
|
if (frame.type === "error") {
|
|
2162
|
-
console.error(
|
|
2009
|
+
console.error(pc18.red(`Server error: ${frame.message}`));
|
|
2163
2010
|
}
|
|
2164
2011
|
}
|
|
2165
2012
|
async function runAgentJob(job, update) {
|
|
@@ -3034,7 +2881,7 @@ Invalid ACP stdout: ${line}`;
|
|
|
3034
2881
|
clientInfo: {
|
|
3035
2882
|
name: "botapp-daemon",
|
|
3036
2883
|
title: "botapp daemon",
|
|
3037
|
-
version: "0.1
|
|
2884
|
+
version: "0.2.1"
|
|
3038
2885
|
}
|
|
3039
2886
|
});
|
|
3040
2887
|
const session = await request2("session/new", {
|
|
@@ -3055,7 +2902,7 @@ Invalid ACP stdout: ${line}`;
|
|
|
3055
2902
|
function requireProfile() {
|
|
3056
2903
|
const profile = loadDaemonProfile();
|
|
3057
2904
|
if (!profile) {
|
|
3058
|
-
console.error(
|
|
2905
|
+
console.error(pc18.red("No paired daemon found. Run `bot pair` first."));
|
|
3059
2906
|
process.exitCode = 1;
|
|
3060
2907
|
return null;
|
|
3061
2908
|
}
|
|
@@ -3158,8 +3005,90 @@ async function waitForChild(child, timeoutMs, label) {
|
|
|
3158
3005
|
});
|
|
3159
3006
|
}
|
|
3160
3007
|
|
|
3008
|
+
// src/commands/pairing.ts
|
|
3009
|
+
var pairingCommand = new Command18("pairing").alias("pair").description("Pair this machine as a botapp daemon").option("--token <token>", "Pairing token (skip browser flow)").option("--name <name>", "Daemon name", hostname2()).option("--app-url <url>", "Dashboard URL (defaults to server URL)").option("--no-browser", "Error instead of opening a browser").option("--run", "After pairing, register local agents and run the daemon (single-process onboarding)").action(async (opts, cmd) => {
|
|
3010
|
+
const globalOpts = cmd.parent?.opts() ?? {};
|
|
3011
|
+
const serverUrl = resolveServerUrl(globalOpts.server);
|
|
3012
|
+
const name = opts.name;
|
|
3013
|
+
try {
|
|
3014
|
+
const grant = await obtainPairingToken({
|
|
3015
|
+
serverUrl,
|
|
3016
|
+
appUrl: opts.appUrl,
|
|
3017
|
+
name,
|
|
3018
|
+
explicitToken: opts.token,
|
|
3019
|
+
allowBrowser: opts.browser !== false
|
|
3020
|
+
});
|
|
3021
|
+
if (!grant) {
|
|
3022
|
+
console.error(
|
|
3023
|
+
pc19.red(
|
|
3024
|
+
"No pairing token and browser flow disabled. Pass --token, or drop --no-browser."
|
|
3025
|
+
)
|
|
3026
|
+
);
|
|
3027
|
+
process.exitCode = 1;
|
|
3028
|
+
return;
|
|
3029
|
+
}
|
|
3030
|
+
if (grant.userEmail) {
|
|
3031
|
+
console.log(pc19.green(`Authenticated as ${pc19.bold(grant.userEmail)}`));
|
|
3032
|
+
}
|
|
3033
|
+
const res = await fetch(`${serverUrl}/api/daemon/pair`, {
|
|
3034
|
+
method: "POST",
|
|
3035
|
+
headers: { "Content-Type": "application/json" },
|
|
3036
|
+
body: JSON.stringify({
|
|
3037
|
+
token: grant.pairingToken,
|
|
3038
|
+
name,
|
|
3039
|
+
machine: hostname2()
|
|
3040
|
+
})
|
|
3041
|
+
});
|
|
3042
|
+
const data = await res.json().catch(() => ({}));
|
|
3043
|
+
if (!res.ok) {
|
|
3044
|
+
console.error(pc19.red(`Error: ${data.error ?? res.statusText}`));
|
|
3045
|
+
process.exitCode = 1;
|
|
3046
|
+
return;
|
|
3047
|
+
}
|
|
3048
|
+
saveDaemonProfile({
|
|
3049
|
+
server: serverUrl,
|
|
3050
|
+
daemonId: data.daemon.id,
|
|
3051
|
+
daemonName: data.daemon.name,
|
|
3052
|
+
token: data.token
|
|
3053
|
+
});
|
|
3054
|
+
console.log(pc19.green(`Paired daemon: ${pc19.bold(data.daemon.name)}`));
|
|
3055
|
+
console.log(` ID: ${data.daemon.id}`);
|
|
3056
|
+
console.log(` Server: ${serverUrl}`);
|
|
3057
|
+
if (opts.run) {
|
|
3058
|
+
const profile = loadDaemonProfile();
|
|
3059
|
+
if (!profile) {
|
|
3060
|
+
console.error(pc19.red("Pairing succeeded but daemon profile is missing \u2014 cannot run."));
|
|
3061
|
+
process.exitCode = 1;
|
|
3062
|
+
return;
|
|
3063
|
+
}
|
|
3064
|
+
await runDaemonConnectionLoop(profile);
|
|
3065
|
+
return;
|
|
3066
|
+
}
|
|
3067
|
+
console.log(pc19.dim("\nNext: botapp daemon agent add codex --command codex"));
|
|
3068
|
+
console.log(pc19.dim("Then: botapp daemon run"));
|
|
3069
|
+
} catch (e) {
|
|
3070
|
+
console.error(pc19.red(`Error: ${e.message}`));
|
|
3071
|
+
process.exitCode = 1;
|
|
3072
|
+
}
|
|
3073
|
+
});
|
|
3074
|
+
async function obtainPairingToken(opts) {
|
|
3075
|
+
if (opts.explicitToken) return { pairingToken: opts.explicitToken };
|
|
3076
|
+
if (!opts.allowBrowser) return null;
|
|
3077
|
+
const appUrl = opts.appUrl ?? opts.serverUrl;
|
|
3078
|
+
const result = await runBrowserAuth({
|
|
3079
|
+
serverUrl: opts.serverUrl,
|
|
3080
|
+
appUrl,
|
|
3081
|
+
scope: "pair",
|
|
3082
|
+
name: opts.name
|
|
3083
|
+
});
|
|
3084
|
+
if (!result.pairingToken) {
|
|
3085
|
+
throw new Error("server returned no pairing token");
|
|
3086
|
+
}
|
|
3087
|
+
return { pairingToken: result.pairingToken, userEmail: result.userEmail };
|
|
3088
|
+
}
|
|
3089
|
+
|
|
3161
3090
|
// src/index.ts
|
|
3162
|
-
var version = "0.1
|
|
3091
|
+
var version = "0.2.1";
|
|
3163
3092
|
var program = new Command19().name("bot").description("botapp CLI \u2014 operate apps from the command line").version(version).enablePositionalOptions(true).option("--json", "Output as JSON").option("-s, --server <url>", "Server URL override").option("-t, --token <token>", "Auth token override").option("-v, --verbose", "Verbose output");
|
|
3164
3093
|
program.addCommand(launchCommand);
|
|
3165
3094
|
program.addCommand(runCommand);
|