@agent-team-foundation/first-tree-hub 0.8.2 → 0.8.3

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.
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import "../logger-core-2yeIU1fc-B-__AsQO.mjs";
3
- import { C as setConfigValue, S as serverConfigSchema, _ as loadAgents, b as resetConfigMeta, c as saveCredentials, f as agentConfigSchema, g as initConfig, h as getConfigValue, l as DEFAULT_CONFIG_DIR, n as ensureFreshAccessToken, o as resolveServerUrl, p as clientConfigSchema, r as ensureFreshAdminToken, s as saveAgentConfig, u as DEFAULT_DATA_DIR, v as readConfigFile, y as resetConfig } from "../bootstrap-99vUYmLs.mjs";
3
+ import { C as setConfigValue, S as serverConfigSchema, _ as loadAgents, b as resetConfigMeta, c as saveCredentials, f as agentConfigSchema, g as initConfig, h as getConfigValue, i as loadCredentials, l as DEFAULT_CONFIG_DIR, n as ensureFreshAccessToken, o as resolveServerUrl, p as clientConfigSchema, r as ensureFreshAdminToken, s as saveAgentConfig, u as DEFAULT_DATA_DIR, v as readConfigFile, y as resetConfig } from "../bootstrap-99vUYmLs.mjs";
4
4
  import "../observability-CJzDFY_G-CmvgUuzc.mjs";
5
- import { A as stopPostgres, C as checkServerReachable, F as SdkError, I as SessionRegistry, L as cleanWorkspaces, M as createOwner, P as FirstTreeHubSDK, R as applyClientLoggerConfig, S as checkServerHealth, T as printResults, _ as checkClientConfig, a as uninstallClientService, b as checkNodeVersion, c as promptAddAgent, d as loadOnboardState, f as onboardCheck, g as checkAgentConfigs, h as runMigrations, j as ClientRuntime, l as promptMissingFields, m as saveOnboardState, n as installClientService, o as startServer, p as onboardCreate, r as isServiceSupported, s as isInteractive, t as getClientServiceStatus, u as formatCheckReport, v as checkDatabase, w as checkWebSocket, x as checkServerConfig, y as checkDocker } from "../core-CNR-lUlr.mjs";
5
+ import { A as stopPostgres, C as checkServerReachable, F as SdkError, I as SessionRegistry, L as cleanWorkspaces, M as createOwner, P as FirstTreeHubSDK, R as applyClientLoggerConfig, S as checkServerHealth, T as printResults, _ as checkClientConfig, a as uninstallClientService, b as checkNodeVersion, c as promptAddAgent, d as loadOnboardState, f as onboardCheck, g as checkAgentConfigs, h as runMigrations, j as ClientRuntime, l as promptMissingFields, m as saveOnboardState, n as installClientService, o as startServer, p as onboardCreate, r as isServiceSupported, s as isInteractive, t as getClientServiceStatus, u as formatCheckReport, v as checkDatabase, w as checkWebSocket, x as checkServerConfig, y as checkDocker } from "../core-CxoH-s16.mjs";
6
6
  import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-BOISS0DK.mjs";
7
7
  import { createRequire } from "node:module";
8
8
  import { Command } from "commander";
@@ -767,6 +767,90 @@ function registerAgentCommands(program) {
767
767
  }
768
768
  //#endregion
769
769
  //#region src/commands/connect.ts
770
+ /** Decode a JWT payload without signature verification. For UI purposes only. */
771
+ function decodeJwtPayload(token) {
772
+ try {
773
+ const parts = token.split(".");
774
+ if (parts.length !== 3 || !parts[1]) return null;
775
+ const raw = Buffer.from(parts[1], "base64url").toString();
776
+ const obj = JSON.parse(raw);
777
+ if (typeof obj !== "object" || obj === null) return null;
778
+ return obj;
779
+ } catch {
780
+ return null;
781
+ }
782
+ }
783
+ /**
784
+ * Detect if the current home already holds a setup for a *different* account,
785
+ * and give the operator a chance to back out before we overwrite credentials.
786
+ *
787
+ * Why this gate exists: running `client connect` implicitly overwrites
788
+ * `~/.first-tree-hub/config/credentials.json`. Without this prompt, someone
789
+ * onboarding a second account on their own machine silently logs themselves
790
+ * out of the first account — they'd only notice when their "main" agents
791
+ * appeared offline later. We treat single-account-per-machine as the product
792
+ * default; the `FIRST_TREE_HUB_HOME` env var remains the advanced escape
793
+ * hatch for power users who want parallel setups.
794
+ *
795
+ * The caller passes the *new* memberId directly so this gate can run BEFORE
796
+ * auth. That matters for the `--token` path: connect tokens are single-use;
797
+ * if we auth first and the user picks Cancel, the token is burned even
798
+ * though nothing changed on disk. Decoding the connect token locally lets
799
+ * us return early without spending it.
800
+ *
801
+ * Behavior:
802
+ * - No existing credentials → proceed silently (first-time install).
803
+ * - Existing credentials, same memberId → proceed silently (reconnect /
804
+ * token refresh — common + safe).
805
+ * - Existing credentials, memberId indeterminate → prompt with
806
+ * "unknown account" label so the user can decide.
807
+ * - Existing credentials, different memberId → prompt [Replace / Cancel].
808
+ * Cancel prints the isolation guide and returns "cancel".
809
+ */
810
+ async function promptReplaceOrCancel(newMemberId, newServerUrl) {
811
+ const existing = loadCredentials();
812
+ if (!existing) return "proceed";
813
+ const existingPayload = decodeJwtPayload(existing.accessToken);
814
+ const existingMemberId = typeof existingPayload?.memberId === "string" ? existingPayload.memberId : null;
815
+ if (existingMemberId && existingMemberId === newMemberId) return "proceed";
816
+ const existingMember = existingMemberId ? `member ${existingMemberId.slice(0, 8)}` : "unknown account";
817
+ const existingOrg = typeof existingPayload?.organizationId === "string" ? existingPayload.organizationId : null;
818
+ const serviceStatus = getClientServiceStatus();
819
+ const serviceLine = serviceStatus.state === "active" ? `running (${serviceStatus.detail ?? "live"})` : serviceStatus.state === "inactive" ? `installed but not running${serviceStatus.detail ? ` — ${serviceStatus.detail}` : ""}` : "not installed";
820
+ process.stderr.write("\n");
821
+ process.stderr.write(" ⚠️ This computer is already connected to the Hub under another account.\n\n");
822
+ process.stderr.write(` Existing account: ${existingMember}\n`);
823
+ if (existingOrg) process.stderr.write(` Organization: ${existingOrg.slice(0, 8)}\n`);
824
+ process.stderr.write(` Server: ${existing.serverUrl}\n`);
825
+ process.stderr.write(` Background service: ${serviceLine}\n\n`);
826
+ process.stderr.write(" Replacing only affects THIS computer. Your agents, messages, and\n");
827
+ process.stderr.write(" settings on the Hub itself are untouched.\n\n");
828
+ if (await select({
829
+ message: "How would you like to continue?",
830
+ choices: [{
831
+ name: "Replace — log out the other account and set up this one",
832
+ value: "replace"
833
+ }, {
834
+ name: "Cancel — keep the existing setup on this computer",
835
+ value: "cancel"
836
+ }]
837
+ }) === "cancel") {
838
+ printIsolationGuide(newServerUrl);
839
+ return "cancel";
840
+ }
841
+ return "proceed";
842
+ }
843
+ function printIsolationGuide(newServerUrl) {
844
+ process.stderr.write("\n Cancelled. The existing account on this computer is untouched.\n\n");
845
+ process.stderr.write(" To run this new account alongside it (advanced — no background service):\n\n");
846
+ process.stderr.write(" export FIRST_TREE_HUB_HOME=\"$HOME/.first-tree-hub-<label>\"\n");
847
+ process.stderr.write(` first-tree-hub client connect ${newServerUrl} --token <token>\n`);
848
+ process.stderr.write(" first-tree-hub client start\n\n");
849
+ process.stderr.write(" Notes:\n");
850
+ process.stderr.write(" - Run the commands in a FRESH terminal (the isolated home must be set first).\n");
851
+ process.stderr.write(" - In isolated mode the client stays online only while that terminal runs.\n");
852
+ process.stderr.write(" - The main account's background service is not affected.\n\n");
853
+ }
770
854
  /**
771
855
  * Authenticate via connect token — exchange for full JWT credentials.
772
856
  */
@@ -803,10 +887,27 @@ function registerConnectCommand(parent) {
803
887
  parent.command("connect <server-url>").description("Connect to a Hub server — configure, authenticate, and install the background service").option("--token <token>", "Connect token (from Hub web console) — skips interactive login").option("--no-service", "Skip background service install (runs inline until Ctrl+C)").action(async (serverUrl, options) => {
804
888
  try {
805
889
  const url = serverUrl.replace(/\/+$/, "");
890
+ let preAuthDecided = false;
891
+ if (options.token) {
892
+ const connectPayload = decodeJwtPayload(options.token);
893
+ const newMemberId = typeof connectPayload?.memberId === "string" ? connectPayload.memberId : null;
894
+ if (newMemberId !== null) {
895
+ if (await promptReplaceOrCancel(newMemberId, url) === "cancel") return;
896
+ preAuthDecided = true;
897
+ }
898
+ }
899
+ const tokens = options.token ? await authenticateWithToken(url, options.token) : await authenticateInteractive(url);
900
+ if (!preAuthDecided) {
901
+ const newPayload = decodeJwtPayload(tokens.accessToken);
902
+ const newMemberId = typeof newPayload?.memberId === "string" ? newPayload.memberId : null;
903
+ if (newMemberId !== null) {
904
+ if (await promptReplaceOrCancel(newMemberId, url) === "cancel") return;
905
+ }
906
+ }
806
907
  setConfigValue(join(DEFAULT_CONFIG_DIR, "client.yaml"), "server.url", url);
807
908
  process.stderr.write(`\n \u2713 Server configured: ${url}\n`);
808
909
  saveCredentials({
809
- ...options.token ? await authenticateWithToken(url, options.token) : await authenticateInteractive(url),
910
+ ...tokens,
810
911
  serverUrl: url
811
912
  });
812
913
  process.stderr.write(" ✓ Authenticated\n");
@@ -12,7 +12,7 @@ import { homedir, hostname, platform, userInfo } from "node:os";
12
12
  import { EventEmitter } from "node:events";
13
13
  import WebSocket from "ws";
14
14
  import { query } from "@anthropic-ai/claude-agent-sdk";
15
- import { execFileSync, execSync, spawn } from "node:child_process";
15
+ import { execFileSync, execSync, spawn, spawnSync } from "node:child_process";
16
16
  import bcrypt from "bcrypt";
17
17
  import { and, asc, count, desc, eq, gt, inArray, isNotNull, isNull, lt, ne, or, sql } from "drizzle-orm";
18
18
  import { drizzle } from "drizzle-orm/postgres-js";
@@ -11796,6 +11796,32 @@ function resolveWebDist() {
11796
11796
  }
11797
11797
  //#endregion
11798
11798
  //#region src/core/service-install.ts
11799
+ /**
11800
+ * Run a subprocess capturing stderr so failures surface a meaningful error
11801
+ * instead of Node's opaque "Command failed". Used for launchctl/systemctl —
11802
+ * anywhere the stderr message is diagnostically crucial.
11803
+ */
11804
+ function runCapture(program, args, timeoutMs) {
11805
+ const res = spawnSync(program, args, {
11806
+ encoding: "utf-8",
11807
+ timeout: timeoutMs,
11808
+ stdio: [
11809
+ "ignore",
11810
+ "pipe",
11811
+ "pipe"
11812
+ ]
11813
+ });
11814
+ if (res.status === 0) return { ok: true };
11815
+ return {
11816
+ ok: false,
11817
+ stderr: (res.stderr ?? "").trim(),
11818
+ code: res.status
11819
+ };
11820
+ }
11821
+ function sleepSync(ms) {
11822
+ const shared = new Int32Array(new SharedArrayBuffer(4));
11823
+ Atomics.wait(shared, 0, 0, ms);
11824
+ }
11799
11825
  const LAUNCHD_LABEL = "dev.first-tree-hub.client";
11800
11826
  const SYSTEMD_UNIT = "first-tree-hub-client.service";
11801
11827
  const LOG_DIR = join(DEFAULT_HOME_DIR$1, "logs");
@@ -11903,30 +11929,56 @@ function launchctlDomainTarget() {
11903
11929
  }
11904
11930
  function launchdState() {
11905
11931
  if (!existsSync(launchdPlistPath())) return { state: "not-installed" };
11906
- try {
11907
- const out = execFileSync("launchctl", ["print", `${launchctlDomainTarget()}/${LAUNCHD_LABEL}`], {
11908
- encoding: "utf-8",
11909
- timeout: 5e3
11910
- });
11911
- const stateLine = out.split(/\r?\n/).find((l) => l.trim().startsWith("state ="));
11912
- const pidLine = out.split(/\r?\n/).find((l) => l.trim().startsWith("pid ="));
11913
- if (stateLine?.includes("running")) {
11914
- const pid = pidLine?.split("=")[1]?.trim();
11915
- return {
11916
- state: "active",
11917
- detail: pid ? `pid ${pid}` : "running"
11918
- };
11919
- }
11920
- return {
11921
- state: "inactive",
11922
- detail: stateLine?.trim() ?? "loaded"
11923
- };
11924
- } catch {
11932
+ const res = spawnSync("launchctl", ["print", `${launchctlDomainTarget()}/${LAUNCHD_LABEL}`], {
11933
+ encoding: "utf-8",
11934
+ timeout: 5e3,
11935
+ stdio: [
11936
+ "ignore",
11937
+ "pipe",
11938
+ "pipe"
11939
+ ]
11940
+ });
11941
+ if (res.status !== 0) return {
11942
+ state: "inactive",
11943
+ detail: "plist present but not loaded"
11944
+ };
11945
+ const out = res.stdout ?? "";
11946
+ const stateLine = out.split(/\r?\n/).find((l) => l.trim().startsWith("state ="));
11947
+ const pidLine = out.split(/\r?\n/).find((l) => l.trim().startsWith("pid ="));
11948
+ if (stateLine?.includes("running")) {
11949
+ const pid = pidLine?.split("=")[1]?.trim();
11925
11950
  return {
11926
- state: "inactive",
11927
- detail: "plist present but not loaded"
11951
+ state: "active",
11952
+ detail: pid ? `pid ${pid}` : "running"
11928
11953
  };
11929
11954
  }
11955
+ return {
11956
+ state: "inactive",
11957
+ detail: stateLine?.trim() ?? "loaded"
11958
+ };
11959
+ }
11960
+ /**
11961
+ * Poll `launchctl print` until the label disappears, confirming launchd has
11962
+ * finished the async eviction kicked off by `bootout`. Required because
11963
+ * `bootout` returns before the actual unload completes when the service has
11964
+ * active WebSocket connections — a follow-up `bootstrap` against a still-
11965
+ * registered label fails with `Bootstrap failed: 5: Input/output error`.
11966
+ */
11967
+ function waitForLabelEvicted(target, label, timeoutMs) {
11968
+ const deadline = Date.now() + timeoutMs;
11969
+ while (Date.now() < deadline) {
11970
+ if (spawnSync("launchctl", ["print", `${target}/${label}`], {
11971
+ encoding: "utf-8",
11972
+ timeout: 2e3,
11973
+ stdio: [
11974
+ "ignore",
11975
+ "ignore",
11976
+ "pipe"
11977
+ ]
11978
+ }).status !== 0) return true;
11979
+ sleepSync(200);
11980
+ }
11981
+ return false;
11930
11982
  }
11931
11983
  function installLaunchd() {
11932
11984
  const invocation = resolveCliInvocation();
@@ -11935,24 +11987,28 @@ function installLaunchd() {
11935
11987
  mkdirSync(dirname(plistPath), { recursive: true });
11936
11988
  writeFileSync(plistPath, renderPlist(invocation), { mode: 420 });
11937
11989
  const target = launchctlDomainTarget();
11938
- try {
11939
- execFileSync("launchctl", ["bootout", `${target}/${LAUNCHD_LABEL}`], {
11940
- stdio: "ignore",
11941
- timeout: 5e3
11942
- });
11943
- } catch {}
11944
- execFileSync("launchctl", [
11945
- "bootstrap",
11946
- target,
11947
- plistPath
11948
- ], {
11949
- stdio: "ignore",
11950
- timeout: 5e3
11951
- });
11952
- execFileSync("launchctl", ["enable", `${target}/${LAUNCHD_LABEL}`], {
11953
- stdio: "ignore",
11954
- timeout: 5e3
11955
- });
11990
+ const bootoutRes = runCapture("launchctl", ["bootout", `${target}/${LAUNCHD_LABEL}`], 15e3);
11991
+ if (!bootoutRes.ok) {
11992
+ if (!/not find|no such|not loaded/i.test(bootoutRes.stderr)) process.stderr.write(` warning: launchctl bootout: ${bootoutRes.stderr || `exit ${bootoutRes.code ?? "unknown"}`}\n`);
11993
+ }
11994
+ waitForLabelEvicted(target, LAUNCHD_LABEL, 1e4);
11995
+ let lastBootstrapErr = null;
11996
+ for (let attempt = 1; attempt <= 2; attempt++) {
11997
+ const res = runCapture("launchctl", [
11998
+ "bootstrap",
11999
+ target,
12000
+ plistPath
12001
+ ], 1e4);
12002
+ if (res.ok) {
12003
+ lastBootstrapErr = null;
12004
+ break;
12005
+ }
12006
+ lastBootstrapErr = res;
12007
+ if (attempt < 2) sleepSync(1e3);
12008
+ }
12009
+ if (lastBootstrapErr) throw new Error(`launchctl bootstrap failed: ${lastBootstrapErr.stderr || `exit ${lastBootstrapErr.code ?? "unknown"}`}\n Command: launchctl bootstrap ${target} ${plistPath}\n Recovery: \`launchctl bootout ${target}/${LAUNCHD_LABEL}\` then \`first-tree-hub service install\`.`);
12010
+ const enableRes = runCapture("launchctl", ["enable", `${target}/${LAUNCHD_LABEL}`], 5e3);
12011
+ if (!enableRes.ok) process.stderr.write(` warning: launchctl enable: ${enableRes.stderr || `exit ${enableRes.code ?? "unknown"}`}\n`);
11956
12012
  const { state, detail } = launchdState();
11957
12013
  return {
11958
12014
  platform: "launchd",
@@ -11965,13 +12021,8 @@ function installLaunchd() {
11965
12021
  }
11966
12022
  function uninstallLaunchd() {
11967
12023
  const plistPath = launchdPlistPath();
11968
- const target = launchctlDomainTarget();
11969
- try {
11970
- execFileSync("launchctl", ["bootout", `${target}/${LAUNCHD_LABEL}`], {
11971
- stdio: "ignore",
11972
- timeout: 5e3
11973
- });
11974
- } catch {}
12024
+ const res = runCapture("launchctl", ["bootout", `${launchctlDomainTarget()}/${LAUNCHD_LABEL}`], 15e3);
12025
+ if (!res.ok && !/not find|no such|not loaded/i.test(res.stderr)) process.stderr.write(` warning: bootout during uninstall: ${res.stderr || `exit ${res.code ?? "unknown"}`}\n`);
11975
12026
  if (existsSync(plistPath)) rmSync(plistPath);
11976
12027
  return {
11977
12028
  platform: "launchd",
@@ -12009,29 +12060,28 @@ function shellQuote(value) {
12009
12060
  }
12010
12061
  function systemdState() {
12011
12062
  if (!existsSync(systemdUnitPath())) return { state: "not-installed" };
12012
- try {
12013
- const out = execFileSync("systemctl", [
12014
- "--user",
12015
- "is-active",
12016
- SYSTEMD_UNIT
12017
- ], {
12018
- encoding: "utf-8",
12019
- timeout: 5e3
12020
- }).trim();
12021
- if (out === "active") return {
12022
- state: "active",
12023
- detail: "running"
12024
- };
12025
- return {
12026
- state: "inactive",
12027
- detail: out
12028
- };
12029
- } catch (err) {
12030
- return {
12031
- state: "inactive",
12032
- detail: (typeof err.stdout === "string" ? (err.stdout ?? "").trim() : "") || "unit present but not active"
12033
- };
12034
- }
12063
+ const res = spawnSync("systemctl", [
12064
+ "--user",
12065
+ "is-active",
12066
+ SYSTEMD_UNIT
12067
+ ], {
12068
+ encoding: "utf-8",
12069
+ timeout: 5e3,
12070
+ stdio: [
12071
+ "ignore",
12072
+ "pipe",
12073
+ "pipe"
12074
+ ]
12075
+ });
12076
+ const out = (res.stdout ?? "").trim();
12077
+ if (res.status === 0 && out === "active") return {
12078
+ state: "active",
12079
+ detail: "running"
12080
+ };
12081
+ return {
12082
+ state: "inactive",
12083
+ detail: out || "unit present but not active"
12084
+ };
12035
12085
  }
12036
12086
  function installSystemd() {
12037
12087
  const invocation = resolveCliInvocation();
@@ -12039,19 +12089,15 @@ function installSystemd() {
12039
12089
  const unitPath = systemdUnitPath();
12040
12090
  mkdirSync(dirname(unitPath), { recursive: true });
12041
12091
  writeFileSync(unitPath, renderSystemdUnit(invocation), { mode: 420 });
12042
- execFileSync("systemctl", ["--user", "daemon-reload"], {
12043
- stdio: "ignore",
12044
- timeout: 5e3
12045
- });
12046
- execFileSync("systemctl", [
12092
+ const reloadRes = runCapture("systemctl", ["--user", "daemon-reload"], 5e3);
12093
+ if (!reloadRes.ok) throw new Error(`systemctl --user daemon-reload failed: ${reloadRes.stderr || `exit ${reloadRes.code ?? "unknown"}`}`);
12094
+ const enableRes = runCapture("systemctl", [
12047
12095
  "--user",
12048
12096
  "enable",
12049
12097
  "--now",
12050
12098
  SYSTEMD_UNIT
12051
- ], {
12052
- stdio: "ignore",
12053
- timeout: 1e4
12054
- });
12099
+ ], 1e4);
12100
+ if (!enableRes.ok) throw new Error(`systemctl --user enable --now ${SYSTEMD_UNIT} failed: ${enableRes.stderr || `exit ${enableRes.code ?? "unknown"}`}\n Recovery: \`systemctl --user stop ${SYSTEMD_UNIT}\` then \`first-tree-hub service install\`.`);
12055
12101
  const { state, detail } = systemdState();
12056
12102
  return {
12057
12103
  platform: "systemd",
@@ -12064,24 +12110,16 @@ function installSystemd() {
12064
12110
  }
12065
12111
  function uninstallSystemd() {
12066
12112
  const unitPath = systemdUnitPath();
12067
- try {
12068
- execFileSync("systemctl", [
12069
- "--user",
12070
- "disable",
12071
- "--now",
12072
- SYSTEMD_UNIT
12073
- ], {
12074
- stdio: "ignore",
12075
- timeout: 1e4
12076
- });
12077
- } catch {}
12113
+ const disableRes = runCapture("systemctl", [
12114
+ "--user",
12115
+ "disable",
12116
+ "--now",
12117
+ SYSTEMD_UNIT
12118
+ ], 1e4);
12119
+ if (!disableRes.ok && !/not found|no such|not loaded/i.test(disableRes.stderr)) process.stderr.write(` warning: systemctl disable during uninstall: ${disableRes.stderr || `exit ${disableRes.code ?? "unknown"}`}\n`);
12078
12120
  if (existsSync(unitPath)) rmSync(unitPath);
12079
- try {
12080
- execFileSync("systemctl", ["--user", "daemon-reload"], {
12081
- stdio: "ignore",
12082
- timeout: 5e3
12083
- });
12084
- } catch {}
12121
+ const reloadRes = runCapture("systemctl", ["--user", "daemon-reload"], 5e3);
12122
+ if (!reloadRes.ok) process.stderr.write(` warning: systemctl daemon-reload during uninstall: ${reloadRes.stderr || `exit ${reloadRes.code ?? "unknown"}`}\n`);
12085
12123
  return {
12086
12124
  platform: "systemd",
12087
12125
  label: SYSTEMD_UNIT,
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import "./logger-core-2yeIU1fc-B-__AsQO.mjs";
2
2
  import { a as resolveAccessToken, n as ensureFreshAccessToken, o as resolveServerUrl, r as ensureFreshAdminToken } from "./bootstrap-99vUYmLs.mjs";
3
3
  import "./observability-CJzDFY_G-CmvgUuzc.mjs";
4
- import { A as stopPostgres, C as checkServerReachable, D as status, E as blank, F as SdkError, M as createOwner, N as hasUser, O as ensurePostgres, P as FirstTreeHubSDK, S as checkServerHealth, T as printResults, _ as checkClientConfig, a as uninstallClientService, b as checkNodeVersion, c as promptAddAgent, f as onboardCheck, g as checkAgentConfigs, h as runMigrations, i as resolveCliInvocation, j as ClientRuntime, k as isDockerAvailable, l as promptMissingFields, n as installClientService, o as startServer, p as onboardCreate, r as isServiceSupported, s as isInteractive, t as getClientServiceStatus, u as formatCheckReport, v as checkDatabase, w as checkWebSocket, x as checkServerConfig, y as checkDocker } from "./core-CNR-lUlr.mjs";
4
+ import { A as stopPostgres, C as checkServerReachable, D as status, E as blank, F as SdkError, M as createOwner, N as hasUser, O as ensurePostgres, P as FirstTreeHubSDK, S as checkServerHealth, T as printResults, _ as checkClientConfig, a as uninstallClientService, b as checkNodeVersion, c as promptAddAgent, f as onboardCheck, g as checkAgentConfigs, h as runMigrations, i as resolveCliInvocation, j as ClientRuntime, k as isDockerAvailable, l as promptMissingFields, n as installClientService, o as startServer, p as onboardCreate, r as isServiceSupported, s as isInteractive, t as getClientServiceStatus, u as formatCheckReport, v as checkDatabase, w as checkWebSocket, x as checkServerConfig, y as checkDocker } from "./core-CxoH-s16.mjs";
5
5
  import { n as bindFeishuUser, t as bindFeishuBot } from "./feishu-BOISS0DK.mjs";
6
6
  export { ClientRuntime, FirstTreeHubSDK, SdkError, bindFeishuBot, bindFeishuUser, blank, checkAgentConfigs, checkClientConfig, checkDatabase, checkDocker, checkNodeVersion, checkServerConfig, checkServerHealth, checkServerReachable, checkWebSocket, createOwner, ensureFreshAccessToken, ensureFreshAdminToken, ensurePostgres, formatCheckReport, getClientServiceStatus, hasUser, installClientService, isDockerAvailable, isInteractive, isServiceSupported, onboardCheck, onboardCreate, printResults, promptAddAgent, promptMissingFields, resolveAccessToken, resolveCliInvocation, resolveServerUrl, runMigrations, startServer, status, stopPostgres, uninstallClientService };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-team-foundation/first-tree-hub",
3
- "version": "0.8.2",
3
+ "version": "0.8.3",
4
4
  "type": "module",
5
5
  "description": "First Tree Hub — unified CLI for server, client, and agent management",
6
6
  "exports": {