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