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/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("Hosted server needs browser auth but --no-browser was passed."));
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 await res.json();
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 Command17 } from "commander";
1853
- import pc18 from "picocolors";
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 Command18 } from "commander";
1807
+ import { Command as Command17 } from "commander";
1961
1808
  import { WebSocket as WebSocket2 } from "ws";
1962
- import pc19 from "picocolors";
1963
- var daemonCommand = new Command18("daemon").description("Manage and run the local botapp daemon");
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(pc19.red("No paired daemon found. Run `bot pair` first."));
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(pc19.yellow("No background daemon PID file found."));
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(pc19.red(`Invalid PID in ${pidFile}`));
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(pc19.green(`Stopped daemon (pid ${pid})`));
1834
+ console.log(pc18.green(`Stopped daemon (pid ${pid})`));
1988
1835
  } catch (e) {
1989
- console.error(pc19.red(`Failed to stop daemon: ${e.message}`));
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 Command18("list").description("List registered daemon agents").action(async () => {
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(`${pc19.bold(agent.name)} ${pc19.dim(`(${agent.id})`)}`);
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 Command18("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(
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(pc19.green(`Registered daemon agent: ${pc19.bold(data.agent.name)}`));
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 Command18("remove").alias("rm").description("Remove a daemon agent by id or name").argument("<agent>", "Daemon agent id or name").action(async (agent) => {
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(pc19.green(`Removed daemon agent: ${pc19.bold(data.agent.name)}`));
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(pc19.blue(`Connecting daemon ${profile.daemonName} to ${profile.server}...`));
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(pc19.yellow(`Retrying daemon connection in ${Math.round(waitMs / 1e3)}s...`));
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(pc19.green("Daemon connected"));
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(pc19.yellow(`Daemon disconnected (${code}: ${reason.toString() || "no reason"})`));
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(pc19.red(`Daemon WebSocket error: ${err.message}`));
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(pc19.blue(`Running ${job.agent.name} job ${job.id}`));
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(pc19.green(`Completed job ${job.id}`));
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(pc19.red(`Job ${job.id} failed: ${e.message}`));
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(pc19.red(`Server error: ${frame.message}`));
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.2"
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(pc19.red("No paired daemon found. Run `bot pair` first."));
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.2";
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);