@agent-team-foundation/first-tree-hub 0.7.0 → 0.7.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.
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  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-CRDR6NwE.mjs";
3
- import { A as SdkError, D as createOwner, E as ClientRuntime, M as cleanWorkspaces, T as stopPostgres, _ as checkServerHealth, a as formatCheckReport, b as printResults, c as onboardCreate, d as checkAgentConfigs, f as checkClientConfig, g as checkServerConfig, h as checkNodeVersion, i as promptMissingFields, j as SessionRegistry, k as FirstTreeHubSDK, l as saveOnboardState, m as checkDocker, n as isInteractive, o as loadOnboardState, p as checkDatabase, r as promptAddAgent, s as onboardCheck, t as startServer, u as runMigrations, v as checkServerReachable, y as checkWebSocket } from "../core-4nvleGlC.mjs";
3
+ import { A as stopPostgres, C as checkServerReachable, F as SdkError, I as SessionRegistry, L as cleanWorkspaces, M as createOwner, P as FirstTreeHubSDK, 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-C05B8FzH.mjs";
4
4
  import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-CJ08ntOD.mjs";
5
5
  import { createRequire } from "node:module";
6
6
  import { Command } from "commander";
@@ -798,7 +798,7 @@ async function authenticateInteractive(url) {
798
798
  return await loginRes.json();
799
799
  }
800
800
  function registerConnectCommand(parent) {
801
- parent.command("connect <server-url>").description("Connect to a Hub server — configure, authenticate, and start client").option("--token <token>", "Connect token (from Hub web console) — skips interactive login").action(async (serverUrl, options) => {
801
+ 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) => {
802
802
  try {
803
803
  const url = serverUrl.replace(/\/+$/, "");
804
804
  setConfigValue(join(DEFAULT_CONFIG_DIR, "client.yaml"), "server.url", url);
@@ -814,12 +814,23 @@ function registerConnectCommand(parent) {
814
814
  schema: clientConfigSchema,
815
815
  role: "client"
816
816
  });
817
+ process.stderr.write(` \u2713 Connected as this computer (id: ${config.client.id})\n`);
818
+ if (options.service !== false && isServiceSupported()) {
819
+ const info = installClientService();
820
+ process.stderr.write(` \u2713 Installed as a background service (${info.platform}) — you can close this terminal\n\n`);
821
+ process.stderr.write(` Unit: ${info.unitPath}\n`);
822
+ process.stderr.write(` Logs: ${info.logDir}\n`);
823
+ if (info.state === "active" && info.detail) process.stderr.write(` State: running (${info.detail})\n`);
824
+ process.stderr.write("\n");
825
+ return;
826
+ }
827
+ if (options.service === false) process.stderr.write(" (--no-service) running inline — Ctrl+C to stop\n");
828
+ else process.stderr.write(` Background service not supported on ${process.platform}; running inline.\n`);
817
829
  const agentsDir = join(DEFAULT_CONFIG_DIR, "agents");
818
830
  const agents = loadAgents({
819
831
  schema: agentConfigSchema,
820
832
  agentsDir
821
833
  });
822
- process.stderr.write(`\n Starting client (id: ${config.client.id})...\n`);
823
834
  const runtime = new ClientRuntime(config.server.url, config.client.id);
824
835
  for (const [name, agentConfig] of agents) runtime.addAgent(name, agentConfig);
825
836
  await runtime.start();
@@ -923,6 +934,48 @@ function registerClientCommands(program) {
923
934
  process.stderr.write(" No agents directory found.\n");
924
935
  }
925
936
  });
937
+ const service = client.command("service").description("Install/uninstall the background service that keeps this computer online");
938
+ service.command("install").description("Install as a background service — auto-starts on login/boot").action(() => {
939
+ if (!isServiceSupported()) {
940
+ process.stderr.write(` Background service is not supported on ${process.platform}.\n Run \`first-tree-hub client start\` manually to keep the computer online.
941
+ `);
942
+ process.exit(1);
943
+ }
944
+ try {
945
+ const info = installClientService();
946
+ process.stderr.write(`\n \u2713 Installed as a background service (${info.platform}).\n`);
947
+ process.stderr.write(` Unit: ${info.unitPath}\n`);
948
+ process.stderr.write(` Logs: ${info.logDir}\n`);
949
+ if (info.state === "active") process.stderr.write(` State: running${info.detail ? ` (${info.detail})` : ""}\n`);
950
+ else process.stderr.write(` State: ${info.state}${info.detail ? ` (${info.detail})` : ""}\n`);
951
+ process.stderr.write("\n You can close this terminal — the computer stays online.\n");
952
+ } catch (error) {
953
+ fail("SERVICE_INSTALL_ERROR", error instanceof Error ? error.message : String(error));
954
+ }
955
+ });
956
+ service.command("status").description("Show background service state").action(() => {
957
+ const info = getClientServiceStatus();
958
+ if (info.platform === "unsupported") {
959
+ process.stderr.write(` Not supported on ${process.platform}.\n`);
960
+ return;
961
+ }
962
+ process.stderr.write(`\n ${info.platform}: ${info.label}\n`);
963
+ process.stderr.write(` Unit: ${info.unitPath}\n`);
964
+ process.stderr.write(` Logs: ${info.logDir}\n`);
965
+ process.stderr.write(` State: ${info.state}${info.detail ? ` (${info.detail})` : ""}\n\n`);
966
+ });
967
+ service.command("uninstall").description("Stop and remove the background service").action(() => {
968
+ if (!isServiceSupported()) {
969
+ process.stderr.write(` Not supported on ${process.platform}.\n`);
970
+ return;
971
+ }
972
+ try {
973
+ const info = uninstallClientService();
974
+ process.stderr.write(`\n \u2713 Uninstalled background service (${info.platform}).\n\n`);
975
+ } catch (error) {
976
+ fail("SERVICE_UNINSTALL_ERROR", error instanceof Error ? error.message : String(error));
977
+ }
978
+ });
926
979
  client.command("hub-list").description("List clients on the Hub server").option("--server <url>", "Hub server URL").action(async (options) => {
927
980
  try {
928
981
  const serverUrl = resolveServerUrl(options.server);
@@ -1,11 +1,11 @@
1
1
  import { C as setConfigValue, S as serverConfigSchema, _ as loadAgents, d as DEFAULT_HOME_DIR$1, f as agentConfigSchema, g as initConfig, i as loadCredentials, l as DEFAULT_CONFIG_DIR, m as collectMissingPrompts, n as ensureFreshAccessToken, o as resolveServerUrl, p as clientConfigSchema, s as saveAgentConfig, u as DEFAULT_DATA_DIR$1, x as resolveConfigReadonly } from "./bootstrap-CRDR6NwE.mjs";
2
2
  import { $ as updateAgentSchema, A as createOrganizationSchema, B as paginationQuerySchema, C as clientRegisterSchema, D as createAgentSchema, E as createAdapterMappingSchema, F as isRedactedEnvValue, G as sendToAgentSchema, H as runtimeStateMessageSchema, I as linkTaskChatSchema, J as sessionEventSchema$1, K as sessionCompletionMessageSchema, L as loginSchema, M as delegateFeishuUserSchema, N as dryRunAgentRuntimeConfigSchema, O as createChatSchema, P as inboxPollQuerySchema, Q as updateAgentRuntimeConfigSchema, R as messageSourceSchema$1, S as agentTypeSchema$1, T as createAdapterConfigSchema, U as selfServiceFeishuBotSchema, V as refreshTokenSchema, W as sendMessageSchema, X as taskListQuerySchema, Y as sessionStateMessageSchema, Z as updateAdapterConfigSchema, _ as addParticipantSchema, a as AGENT_SELECTOR_HEADER$1, b as agentBindRequestSchema, c as AGENT_TYPES, d as SYSTEM_CONFIG_DEFAULTS, et as updateMemberSchema, f as TASK_CREATOR_TYPES, g as WS_AUTH_FRAME_TIMEOUT_MS, h as TASK_TERMINAL_STATUSES, i as AGENT_BIND_REJECT_REASONS, it as wsAuthFrameSchema, j as createTaskSchema, k as createMemberSchema, l as AGENT_VISIBILITY, m as TASK_STATUSES, nt as updateSystemConfigSchema, o as AGENT_SOURCES, p as TASK_HEALTH_SIGNALS, q as sessionEventMessageSchema, rt as updateTaskStatusSchema, s as AGENT_STATUSES, tt as updateOrganizationSchema, u as DEFAULT_AGENT_RUNTIME_CONFIG_PAYLOAD, v as adminCreateTaskSchema, w as connectTokenExchangeSchema, x as agentRuntimeConfigPayloadSchema$1, y as adminUpdateTaskSchema, z as notificationQuerySchema } from "./feishu-CJ08ntOD.mjs";
3
- import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, watch, writeFileSync } from "node:fs";
4
- import { dirname, join, resolve } from "node:path";
3
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, watch, writeFileSync } from "node:fs";
4
+ import { dirname, isAbsolute, join, resolve } from "node:path";
5
5
  import { ZodError, z } from "zod";
6
6
  import "yaml";
7
7
  import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
8
- import { homedir, hostname, platform } from "node:os";
8
+ import { homedir, hostname, platform, userInfo } from "node:os";
9
9
  import { EventEmitter } from "node:events";
10
10
  import WebSocket from "ws";
11
11
  import { query } from "@anthropic-ai/claude-agent-sdk";
@@ -1106,7 +1106,6 @@ var ClientConnection = class extends EventEmitter {
1106
1106
  return;
1107
1107
  }
1108
1108
  if (type === "auth:rejected" || type === "auth:expired") {
1109
- this.registered = false;
1110
1109
  if (type === "auth:expired") this.emit("auth:expired");
1111
1110
  this.ws?.close(4401, type);
1112
1111
  return;
@@ -4114,7 +4113,7 @@ function setNestedByDot(obj, dotPath, value) {
4114
4113
  if (lastKey !== void 0) current[lastKey] = value;
4115
4114
  }
4116
4115
  //#endregion
4117
- //#region ../server/dist/app-nJ9jSQtv.mjs
4116
+ //#region ../server/dist/app-CWKBBGod.mjs
4118
4117
  var __defProp = Object.defineProperty;
4119
4118
  var __exportAll = (all, no_symbols) => {
4120
4119
  let target = {};
@@ -4649,8 +4648,17 @@ function parseId$1(raw) {
4649
4648
  return id;
4650
4649
  }
4651
4650
  async function adminAdapterMappingRoutes(app) {
4652
- app.get("/", async () => {
4653
- return (await app.db.select().from(adapterAgentMappings).orderBy(desc(adapterAgentMappings.createdAt))).map((r) => ({
4651
+ app.get("/", async (request) => {
4652
+ const scope = memberScope(request);
4653
+ return (await app.db.select({
4654
+ id: adapterAgentMappings.id,
4655
+ platform: adapterAgentMappings.platform,
4656
+ externalUserId: adapterAgentMappings.externalUserId,
4657
+ agentId: adapterAgentMappings.agentId,
4658
+ boundVia: adapterAgentMappings.boundVia,
4659
+ displayName: adapterAgentMappings.displayName,
4660
+ createdAt: adapterAgentMappings.createdAt
4661
+ }).from(adapterAgentMappings).innerJoin(agents, eq(agents.uuid, adapterAgentMappings.agentId)).where(eq(agents.organizationId, scope.organizationId)).orderBy(desc(adapterAgentMappings.createdAt))).map((r) => ({
4654
4662
  id: r.id,
4655
4663
  platform: r.platform,
4656
4664
  externalUserId: r.externalUserId,
@@ -5077,8 +5085,23 @@ async function createAgent(db, data) {
5077
5085
  const name = data.name ?? null;
5078
5086
  if (name?.startsWith(RESERVED_AGENT_NAME_PREFIX)) throw new BadRequestError(`Agent name "${name}" is reserved — names starting with "${RESERVED_AGENT_NAME_PREFIX}" are Hub-internal`);
5079
5087
  const inboxId = `inbox_${uuid}`;
5080
- const orgId = data.organizationId ?? await resolveDefaultOrgId(db);
5081
- const managerId = data.managerId ?? await resolveFallbackManagerId(db, orgId);
5088
+ let orgId;
5089
+ let managerId;
5090
+ if (data.managerId && data.organizationId) {
5091
+ orgId = data.organizationId;
5092
+ managerId = data.managerId;
5093
+ } else if (data.managerId) {
5094
+ const [manager] = await db.select({
5095
+ id: members.id,
5096
+ organizationId: members.organizationId
5097
+ }).from(members).where(eq(members.id, data.managerId)).limit(1);
5098
+ if (!manager) throw new BadRequestError(`Manager "${data.managerId}" not found`);
5099
+ orgId = manager.organizationId;
5100
+ managerId = manager.id;
5101
+ } else {
5102
+ orgId = data.organizationId ?? await resolveDefaultOrgId(db);
5103
+ managerId = await resolveFallbackManagerId(db, orgId);
5104
+ }
5082
5105
  const clientId = await resolveAgentClient(db, {
5083
5106
  clientId: data.clientId,
5084
5107
  managerId,
@@ -11044,4 +11067,353 @@ function resolveWebDist() {
11044
11067
  } catch {}
11045
11068
  }
11046
11069
  //#endregion
11047
- export { SdkError as A, ensurePostgres as C, createOwner as D, ClientRuntime as E, cleanWorkspaces as M, hasUser as O, status as S, stopPostgres as T, checkServerHealth as _, formatCheckReport as a, printResults as b, onboardCreate as c, checkAgentConfigs as d, checkClientConfig as f, checkServerConfig as g, checkNodeVersion as h, promptMissingFields as i, SessionRegistry as j, FirstTreeHubSDK as k, saveOnboardState as l, checkDocker as m, isInteractive as n, loadOnboardState as o, checkDatabase as p, promptAddAgent as r, onboardCheck as s, startServer as t, runMigrations as u, checkServerReachable as v, isDockerAvailable as w, blank as x, checkWebSocket as y };
11070
+ //#region src/core/service-install.ts
11071
+ const LAUNCHD_LABEL = "dev.first-tree-hub.client";
11072
+ const SYSTEMD_UNIT = "first-tree-hub-client.service";
11073
+ const LOG_DIR = join(DEFAULT_HOME_DIR$1, "logs");
11074
+ function whichBin(name) {
11075
+ try {
11076
+ return execFileSync(process.platform === "win32" ? "where" : "which", [name], {
11077
+ encoding: "utf-8",
11078
+ timeout: 3e3
11079
+ }).split(/\r?\n/).map((s) => s.trim()).filter(Boolean)[0] ?? null;
11080
+ } catch {
11081
+ return null;
11082
+ }
11083
+ }
11084
+ /**
11085
+ * Resolve how the service should launch the CLI.
11086
+ *
11087
+ * Prefers the installed `first-tree-hub` bin on PATH (usually a shim under
11088
+ * /usr/local/bin or ~/.npm-global/bin). Falls back to invoking the current
11089
+ * Node interpreter against the running script (handles `pnpm dev`, tsx, and
11090
+ * dev-only global installs).
11091
+ */
11092
+ function resolveCliInvocation() {
11093
+ const bin = whichBin("first-tree-hub");
11094
+ if (bin && isAbsolute(bin)) try {
11095
+ return {
11096
+ kind: "bin",
11097
+ program: realpathSync(bin)
11098
+ };
11099
+ } catch {
11100
+ return {
11101
+ kind: "bin",
11102
+ program: bin
11103
+ };
11104
+ }
11105
+ const script = process.argv[1];
11106
+ if (!script) throw new Error("Cannot resolve CLI entry point (process.argv[1] is empty).");
11107
+ const scriptAbs = isAbsolute(script) ? script : join(process.cwd(), script);
11108
+ return {
11109
+ kind: "node",
11110
+ program: process.execPath,
11111
+ args: [scriptAbs]
11112
+ };
11113
+ }
11114
+ function ensureLogDir() {
11115
+ mkdirSync(LOG_DIR, {
11116
+ recursive: true,
11117
+ mode: 448
11118
+ });
11119
+ }
11120
+ function launchdPlistPath() {
11121
+ return join(homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
11122
+ }
11123
+ function renderPlist(invocation) {
11124
+ const argsXml = (invocation.kind === "bin" ? [
11125
+ invocation.program,
11126
+ "client",
11127
+ "start",
11128
+ "--no-interactive"
11129
+ ] : [
11130
+ invocation.program,
11131
+ ...invocation.args,
11132
+ "client",
11133
+ "start",
11134
+ "--no-interactive"
11135
+ ]).map((a) => ` <string>${escapeXml(a)}</string>`).join("\n");
11136
+ const outLog = join(LOG_DIR, "client.out.log");
11137
+ const errLog = join(LOG_DIR, "client.err.log");
11138
+ return `<?xml version="1.0" encoding="UTF-8"?>
11139
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTD/PropertyList-1.0.dtd">
11140
+ <plist version="1.0">
11141
+ <dict>
11142
+ <key>Label</key>
11143
+ <string>${LAUNCHD_LABEL}</string>
11144
+ <key>ProgramArguments</key>
11145
+ <array>
11146
+ ${argsXml}
11147
+ </array>
11148
+ <key>EnvironmentVariables</key>
11149
+ <dict>
11150
+ <key>PATH</key>
11151
+ <string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
11152
+ </dict>
11153
+ <key>RunAtLoad</key>
11154
+ <true/>
11155
+ <key>KeepAlive</key>
11156
+ <dict>
11157
+ <key>SuccessfulExit</key>
11158
+ <false/>
11159
+ </dict>
11160
+ <key>ThrottleInterval</key>
11161
+ <integer>10</integer>
11162
+ <key>StandardOutPath</key>
11163
+ <string>${escapeXml(outLog)}</string>
11164
+ <key>StandardErrorPath</key>
11165
+ <string>${escapeXml(errLog)}</string>
11166
+ </dict>
11167
+ </plist>
11168
+ `;
11169
+ }
11170
+ function escapeXml(value) {
11171
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
11172
+ }
11173
+ function launchctlDomainTarget() {
11174
+ return `gui/${userInfo().uid}`;
11175
+ }
11176
+ function launchdState() {
11177
+ if (!existsSync(launchdPlistPath())) return { state: "not-installed" };
11178
+ try {
11179
+ const out = execFileSync("launchctl", ["print", `${launchctlDomainTarget()}/${LAUNCHD_LABEL}`], {
11180
+ encoding: "utf-8",
11181
+ timeout: 5e3
11182
+ });
11183
+ const stateLine = out.split(/\r?\n/).find((l) => l.trim().startsWith("state ="));
11184
+ const pidLine = out.split(/\r?\n/).find((l) => l.trim().startsWith("pid ="));
11185
+ if (stateLine?.includes("running")) {
11186
+ const pid = pidLine?.split("=")[1]?.trim();
11187
+ return {
11188
+ state: "active",
11189
+ detail: pid ? `pid ${pid}` : "running"
11190
+ };
11191
+ }
11192
+ return {
11193
+ state: "inactive",
11194
+ detail: stateLine?.trim() ?? "loaded"
11195
+ };
11196
+ } catch {
11197
+ return {
11198
+ state: "inactive",
11199
+ detail: "plist present but not loaded"
11200
+ };
11201
+ }
11202
+ }
11203
+ function installLaunchd() {
11204
+ const invocation = resolveCliInvocation();
11205
+ ensureLogDir();
11206
+ const plistPath = launchdPlistPath();
11207
+ mkdirSync(dirname(plistPath), { recursive: true });
11208
+ writeFileSync(plistPath, renderPlist(invocation), { mode: 420 });
11209
+ const target = launchctlDomainTarget();
11210
+ try {
11211
+ execFileSync("launchctl", ["bootout", `${target}/${LAUNCHD_LABEL}`], {
11212
+ stdio: "ignore",
11213
+ timeout: 5e3
11214
+ });
11215
+ } catch {}
11216
+ execFileSync("launchctl", [
11217
+ "bootstrap",
11218
+ target,
11219
+ plistPath
11220
+ ], {
11221
+ stdio: "ignore",
11222
+ timeout: 5e3
11223
+ });
11224
+ execFileSync("launchctl", ["enable", `${target}/${LAUNCHD_LABEL}`], {
11225
+ stdio: "ignore",
11226
+ timeout: 5e3
11227
+ });
11228
+ const { state, detail } = launchdState();
11229
+ return {
11230
+ platform: "launchd",
11231
+ label: LAUNCHD_LABEL,
11232
+ unitPath: plistPath,
11233
+ logDir: LOG_DIR,
11234
+ state,
11235
+ detail
11236
+ };
11237
+ }
11238
+ function uninstallLaunchd() {
11239
+ const plistPath = launchdPlistPath();
11240
+ const target = launchctlDomainTarget();
11241
+ try {
11242
+ execFileSync("launchctl", ["bootout", `${target}/${LAUNCHD_LABEL}`], {
11243
+ stdio: "ignore",
11244
+ timeout: 5e3
11245
+ });
11246
+ } catch {}
11247
+ if (existsSync(plistPath)) rmSync(plistPath);
11248
+ return {
11249
+ platform: "launchd",
11250
+ label: LAUNCHD_LABEL,
11251
+ unitPath: plistPath,
11252
+ logDir: LOG_DIR,
11253
+ state: "not-installed"
11254
+ };
11255
+ }
11256
+ function systemdUnitPath() {
11257
+ return join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "systemd", "user", SYSTEMD_UNIT);
11258
+ }
11259
+ function renderSystemdUnit(invocation) {
11260
+ return `[Unit]
11261
+ Description=First Tree Hub Client
11262
+ After=network-online.target
11263
+ Wants=network-online.target
11264
+
11265
+ [Service]
11266
+ Type=simple
11267
+ ExecStart=${invocation.kind === "bin" ? `${shellQuote(invocation.program)} client start --no-interactive` : `${shellQuote(invocation.program)} ${invocation.args.map(shellQuote).join(" ")} client start --no-interactive`}
11268
+ Restart=always
11269
+ RestartSec=10
11270
+ StandardOutput=append:${join(LOG_DIR, "client.out.log")}
11271
+ StandardError=append:${join(LOG_DIR, "client.err.log")}
11272
+ Environment=PATH=/usr/local/bin:/usr/bin:/bin
11273
+
11274
+ [Install]
11275
+ WantedBy=default.target
11276
+ `;
11277
+ }
11278
+ function shellQuote(value) {
11279
+ if (/^[A-Za-z0-9_\-./:=]+$/.test(value)) return value;
11280
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
11281
+ }
11282
+ function systemdState() {
11283
+ if (!existsSync(systemdUnitPath())) return { state: "not-installed" };
11284
+ try {
11285
+ const out = execFileSync("systemctl", [
11286
+ "--user",
11287
+ "is-active",
11288
+ SYSTEMD_UNIT
11289
+ ], {
11290
+ encoding: "utf-8",
11291
+ timeout: 5e3
11292
+ }).trim();
11293
+ if (out === "active") return {
11294
+ state: "active",
11295
+ detail: "running"
11296
+ };
11297
+ return {
11298
+ state: "inactive",
11299
+ detail: out
11300
+ };
11301
+ } catch (err) {
11302
+ return {
11303
+ state: "inactive",
11304
+ detail: (typeof err.stdout === "string" ? (err.stdout ?? "").trim() : "") || "unit present but not active"
11305
+ };
11306
+ }
11307
+ }
11308
+ function installSystemd() {
11309
+ const invocation = resolveCliInvocation();
11310
+ ensureLogDir();
11311
+ const unitPath = systemdUnitPath();
11312
+ mkdirSync(dirname(unitPath), { recursive: true });
11313
+ writeFileSync(unitPath, renderSystemdUnit(invocation), { mode: 420 });
11314
+ execFileSync("systemctl", ["--user", "daemon-reload"], {
11315
+ stdio: "ignore",
11316
+ timeout: 5e3
11317
+ });
11318
+ execFileSync("systemctl", [
11319
+ "--user",
11320
+ "enable",
11321
+ "--now",
11322
+ SYSTEMD_UNIT
11323
+ ], {
11324
+ stdio: "ignore",
11325
+ timeout: 1e4
11326
+ });
11327
+ const { state, detail } = systemdState();
11328
+ return {
11329
+ platform: "systemd",
11330
+ label: SYSTEMD_UNIT,
11331
+ unitPath,
11332
+ logDir: LOG_DIR,
11333
+ state,
11334
+ detail
11335
+ };
11336
+ }
11337
+ function uninstallSystemd() {
11338
+ const unitPath = systemdUnitPath();
11339
+ try {
11340
+ execFileSync("systemctl", [
11341
+ "--user",
11342
+ "disable",
11343
+ "--now",
11344
+ SYSTEMD_UNIT
11345
+ ], {
11346
+ stdio: "ignore",
11347
+ timeout: 1e4
11348
+ });
11349
+ } catch {}
11350
+ if (existsSync(unitPath)) rmSync(unitPath);
11351
+ try {
11352
+ execFileSync("systemctl", ["--user", "daemon-reload"], {
11353
+ stdio: "ignore",
11354
+ timeout: 5e3
11355
+ });
11356
+ } catch {}
11357
+ return {
11358
+ platform: "systemd",
11359
+ label: SYSTEMD_UNIT,
11360
+ unitPath,
11361
+ logDir: LOG_DIR,
11362
+ state: "not-installed"
11363
+ };
11364
+ }
11365
+ /** Is background-service install supported on the current platform? */
11366
+ function isServiceSupported() {
11367
+ return process.platform === "darwin" || process.platform === "linux";
11368
+ }
11369
+ /**
11370
+ * Install the background service for the current platform.
11371
+ *
11372
+ * @throws {Error} if the platform is not supported or the service manager fails.
11373
+ */
11374
+ function installClientService() {
11375
+ if (process.platform === "darwin") return installLaunchd();
11376
+ if (process.platform === "linux") return installSystemd();
11377
+ throw new Error(`Background service install is not supported on ${process.platform}. Run \`first-tree-hub client start\` manually to keep the computer online.`);
11378
+ }
11379
+ /** Report the current service state without modifying anything. */
11380
+ function getClientServiceStatus() {
11381
+ if (process.platform === "darwin") {
11382
+ const { state, detail } = launchdState();
11383
+ return {
11384
+ platform: "launchd",
11385
+ label: LAUNCHD_LABEL,
11386
+ unitPath: launchdPlistPath(),
11387
+ logDir: LOG_DIR,
11388
+ state,
11389
+ detail
11390
+ };
11391
+ }
11392
+ if (process.platform === "linux") {
11393
+ const { state, detail } = systemdState();
11394
+ return {
11395
+ platform: "systemd",
11396
+ label: SYSTEMD_UNIT,
11397
+ unitPath: systemdUnitPath(),
11398
+ logDir: LOG_DIR,
11399
+ state,
11400
+ detail
11401
+ };
11402
+ }
11403
+ return {
11404
+ platform: "unsupported",
11405
+ label: "",
11406
+ unitPath: "",
11407
+ logDir: LOG_DIR,
11408
+ state: "not-installed",
11409
+ detail: `platform ${process.platform} not supported`
11410
+ };
11411
+ }
11412
+ /** Uninstall the background service. No-op if not installed. */
11413
+ function uninstallClientService() {
11414
+ if (process.platform === "darwin") return uninstallLaunchd();
11415
+ if (process.platform === "linux") return uninstallSystemd();
11416
+ return getClientServiceStatus();
11417
+ }
11418
+ //#endregion
11419
+ export { stopPostgres as A, checkServerReachable as C, status as D, blank as E, SdkError as F, SessionRegistry as I, cleanWorkspaces as L, createOwner as M, hasUser as N, ensurePostgres as O, FirstTreeHubSDK as P, checkServerHealth as S, printResults as T, checkClientConfig as _, uninstallClientService as a, checkNodeVersion as b, promptAddAgent as c, loadOnboardState as d, onboardCheck as f, checkAgentConfigs as g, runMigrations as h, resolveCliInvocation as i, ClientRuntime as j, isDockerAvailable as k, promptMissingFields as l, saveOnboardState as m, installClientService as n, startServer as o, onboardCreate as p, isServiceSupported as r, isInteractive as s, getClientServiceStatus as t, formatCheckReport as u, checkDatabase as v, checkWebSocket as w, checkServerConfig as x, checkDocker as y };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  import { a as resolveAccessToken, n as ensureFreshAccessToken, o as resolveServerUrl, r as ensureFreshAdminToken } from "./bootstrap-CRDR6NwE.mjs";
2
- import { A as SdkError, C as ensurePostgres, D as createOwner, E as ClientRuntime, O as hasUser, S as status, T as stopPostgres, _ as checkServerHealth, a as formatCheckReport, b as printResults, c as onboardCreate, d as checkAgentConfigs, f as checkClientConfig, g as checkServerConfig, h as checkNodeVersion, i as promptMissingFields, k as FirstTreeHubSDK, m as checkDocker, n as isInteractive, p as checkDatabase, r as promptAddAgent, s as onboardCheck, t as startServer, u as runMigrations, v as checkServerReachable, w as isDockerAvailable, x as blank, y as checkWebSocket } from "./core-4nvleGlC.mjs";
2
+ 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-C05B8FzH.mjs";
3
3
  import { n as bindFeishuUser, t as bindFeishuBot } from "./feishu-CJ08ntOD.mjs";
4
- export { ClientRuntime, FirstTreeHubSDK, SdkError, bindFeishuBot, bindFeishuUser, blank, checkAgentConfigs, checkClientConfig, checkDatabase, checkDocker, checkNodeVersion, checkServerConfig, checkServerHealth, checkServerReachable, checkWebSocket, createOwner, ensureFreshAccessToken, ensureFreshAdminToken, ensurePostgres, formatCheckReport, hasUser, isDockerAvailable, isInteractive, onboardCheck, onboardCreate, printResults, promptAddAgent, promptMissingFields, resolveAccessToken, resolveServerUrl, runMigrations, startServer, status, stopPostgres };
4
+ 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.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "description": "First Tree Hub — unified CLI for server, client, and agent management",
6
6
  "exports": {