@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.
- package/dist/cli/index.mjs +104 -3
- package/dist/{core-CNR-lUlr.mjs → core-CxoH-s16.mjs} +134 -96
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
package/dist/cli/index.mjs
CHANGED
|
@@ -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-
|
|
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
|
-
...
|
|
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
|
-
|
|
11907
|
-
|
|
11908
|
-
|
|
11909
|
-
|
|
11910
|
-
|
|
11911
|
-
|
|
11912
|
-
|
|
11913
|
-
|
|
11914
|
-
|
|
11915
|
-
|
|
11916
|
-
|
|
11917
|
-
|
|
11918
|
-
|
|
11919
|
-
|
|
11920
|
-
|
|
11921
|
-
|
|
11922
|
-
|
|
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: "
|
|
11927
|
-
detail:
|
|
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
|
-
|
|
11939
|
-
|
|
11940
|
-
|
|
11941
|
-
|
|
11942
|
-
|
|
11943
|
-
|
|
11944
|
-
|
|
11945
|
-
"
|
|
11946
|
-
|
|
11947
|
-
|
|
11948
|
-
|
|
11949
|
-
|
|
11950
|
-
|
|
11951
|
-
|
|
11952
|
-
|
|
11953
|
-
|
|
11954
|
-
|
|
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
|
|
11969
|
-
|
|
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
|
-
|
|
12013
|
-
|
|
12014
|
-
|
|
12015
|
-
|
|
12016
|
-
|
|
12017
|
-
|
|
12018
|
-
|
|
12019
|
-
|
|
12020
|
-
|
|
12021
|
-
|
|
12022
|
-
|
|
12023
|
-
|
|
12024
|
-
|
|
12025
|
-
|
|
12026
|
-
|
|
12027
|
-
|
|
12028
|
-
|
|
12029
|
-
}
|
|
12030
|
-
|
|
12031
|
-
|
|
12032
|
-
|
|
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
|
-
|
|
12043
|
-
|
|
12044
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12068
|
-
|
|
12069
|
-
|
|
12070
|
-
|
|
12071
|
-
|
|
12072
|
-
|
|
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
|
-
|
|
12080
|
-
|
|
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-
|
|
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 };
|