@agent-team-foundation/first-tree-hub 0.6.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{bootstrap-DW7aIpmE.mjs → bootstrap-CRDR6NwE.mjs} +11 -6
- package/dist/cli/index.mjs +128 -109
- package/dist/{core-RXUUKkCO.mjs → core-4nvleGlC.mjs} +625 -180
- package/dist/drizzle/0020_unified_user_token.sql +50 -44
- package/dist/drizzle/0022_session_events.sql +32 -0
- package/dist/drizzle/meta/_journal.json +7 -0
- package/dist/{feishu-BZ8pnMrQ.mjs → feishu-CJ08ntOD.mjs} +55 -7
- package/dist/index.mjs +3 -3
- package/dist/web/assets/index-30C-bada.js +333 -0
- package/dist/web/assets/index-COlVuDVR.css +1 -0
- package/dist/web/index.html +9 -2
- package/package.json +1 -1
- package/dist/web/assets/index-BMOr9-X2.js +0 -308
- package/dist/web/assets/index-CTl4pHIL.css +0 -1
|
@@ -56,6 +56,10 @@ const clientConfigSchema = defineConfig({
|
|
|
56
56
|
default: "http://localhost:8000"
|
|
57
57
|
}
|
|
58
58
|
}) },
|
|
59
|
+
client: { id: field(z.string().regex(/^client_[a-f0-9]{8}$/), {
|
|
60
|
+
auto: "client-id",
|
|
61
|
+
env: "FIRST_TREE_HUB_CLIENT_ID"
|
|
62
|
+
}) },
|
|
59
63
|
logLevel: field(z.enum([
|
|
60
64
|
"debug",
|
|
61
65
|
"info",
|
|
@@ -112,6 +116,7 @@ function coerceEnvValue(value, schema) {
|
|
|
112
116
|
return value;
|
|
113
117
|
}
|
|
114
118
|
function builtinAutoGenerate(strategy) {
|
|
119
|
+
if (strategy === "client-id") return `client_${randomBytes(4).toString("hex")}`;
|
|
115
120
|
const match = /^random:(\w+):(\d+)$/.exec(strategy);
|
|
116
121
|
if (!match) throw new Error(`Unknown auto-generation strategy: ${strategy}`);
|
|
117
122
|
const encoding = match[1];
|
|
@@ -505,13 +510,13 @@ function resolveServerUrl(flagValue) {
|
|
|
505
510
|
* Resolve the current member access JWT from persisted credentials.
|
|
506
511
|
*
|
|
507
512
|
* Unified-user-token milestone: the CLI has a single credential store and a
|
|
508
|
-
* single onboarding path (`first-tree-hub connect`). The legacy
|
|
513
|
+
* single onboarding path (`first-tree-hub client connect`). The legacy
|
|
509
514
|
* `FIRST_TREE_HUB_TOKEN` env var is no longer read — callers get a clear
|
|
510
|
-
* error pointing at `connect` instead.
|
|
515
|
+
* error pointing at `client connect` instead.
|
|
511
516
|
*/
|
|
512
517
|
function resolveAccessToken() {
|
|
513
518
|
const creds = loadCredentials();
|
|
514
|
-
if (!creds) throw new Error("No credentials found. Run `first-tree-hub connect <server-url>` to sign in.");
|
|
519
|
+
if (!creds) throw new Error("No credentials found. Run `first-tree-hub client connect <server-url>` to sign in.");
|
|
515
520
|
return creds.accessToken;
|
|
516
521
|
}
|
|
517
522
|
/**
|
|
@@ -521,7 +526,7 @@ function resolveAccessToken() {
|
|
|
521
526
|
*/
|
|
522
527
|
async function ensureFreshAccessToken() {
|
|
523
528
|
const creds = loadCredentials();
|
|
524
|
-
if (!creds) throw new Error("No credentials found. Run `first-tree-hub connect <server-url>` to sign in.");
|
|
529
|
+
if (!creds) throw new Error("No credentials found. Run `first-tree-hub client connect <server-url>` to sign in.");
|
|
525
530
|
if (!isTokenExpired(creds.accessToken)) return creds.accessToken;
|
|
526
531
|
const res = await fetch(`${creds.serverUrl}/api/v1/auth/refresh`, {
|
|
527
532
|
method: "POST",
|
|
@@ -529,7 +534,7 @@ async function ensureFreshAccessToken() {
|
|
|
529
534
|
body: JSON.stringify({ refreshToken: creds.refreshToken }),
|
|
530
535
|
signal: AbortSignal.timeout(1e4)
|
|
531
536
|
});
|
|
532
|
-
if (!res.ok) throw new Error("Access token expired and refresh failed. Run `first-tree-hub connect <server-url>`.");
|
|
537
|
+
if (!res.ok) throw new Error("Access token expired and refresh failed. Run `first-tree-hub client connect <server-url>`.");
|
|
533
538
|
const data = await res.json();
|
|
534
539
|
saveCredentials({
|
|
535
540
|
...creds,
|
|
@@ -545,7 +550,7 @@ function isTokenExpired(token) {
|
|
|
545
550
|
if (parts.length !== 3 || !parts[1]) return true;
|
|
546
551
|
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
547
552
|
if (!payload.exp) return false;
|
|
548
|
-
return payload.exp * 1e3 < Date.now()
|
|
553
|
+
return payload.exp * 1e3 < Date.now() + 3e4;
|
|
549
554
|
} catch {
|
|
550
555
|
return true;
|
|
551
556
|
}
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
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-
|
|
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-
|
|
4
|
-
import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-
|
|
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";
|
|
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";
|
|
7
7
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
|
@@ -362,7 +362,7 @@ function registerAgentCommands(program) {
|
|
|
362
362
|
process.stderr.write(" No agents configured.\n");
|
|
363
363
|
}
|
|
364
364
|
});
|
|
365
|
-
agent.command("create <name>").description("Create an agent on Hub and bind it locally").requiredOption("--type <type>", "Agent type (human, personal_assistant, autonomous_agent)").requiredOption("--client-id <id>", "Client (machine) that will run this agent — must be owned by you. Run `first-tree-hub connect` on that machine first.").option("--runtime <runtime>", "Runtime handler (default: claude-code)", "claude-code").option("--display-name <name>", "Display name").option("--server <url>", "Hub server URL").action(async (name, options) => {
|
|
365
|
+
agent.command("create <name>").description("Create an agent on Hub and bind it locally").requiredOption("--type <type>", "Agent type (human, personal_assistant, autonomous_agent)").requiredOption("--client-id <id>", "Client (machine) that will run this agent — must be owned by you. Run `first-tree-hub client connect` on that machine first.").option("--runtime <runtime>", "Runtime handler (default: claude-code)", "claude-code").option("--display-name <name>", "Display name").option("--server <url>", "Hub server URL").action(async (name, options) => {
|
|
366
366
|
try {
|
|
367
367
|
const serverUrl = resolveServerUrl(options.server);
|
|
368
368
|
const headers = {
|
|
@@ -439,7 +439,31 @@ function registerAgentCommands(program) {
|
|
|
439
439
|
}
|
|
440
440
|
process.stderr.write(` ${totalRemoved} workspace(s) cleaned.\n`);
|
|
441
441
|
});
|
|
442
|
-
const bind = agent.command("bind").description("Bind external IM
|
|
442
|
+
const bind = agent.command("bind").description("Bind an agent to a client machine or external IM account");
|
|
443
|
+
bind.command("client <agentName>").description("Bind an unbound agent to a client machine (first-time bind only — ID is immutable once set)").requiredOption("--client-id <id>", "Client (machine) ID — must be owned by you. Run `first-tree-hub client connect` on that machine first.").option("--server <url>", "Hub server URL").action(async (agentName, options) => {
|
|
444
|
+
try {
|
|
445
|
+
const serverUrl = resolveServerUrl(options.server);
|
|
446
|
+
const accessToken = await ensureFreshAccessToken();
|
|
447
|
+
const target = await resolveAgent(serverUrl, accessToken, agentName);
|
|
448
|
+
const patchRes = await fetch(`${serverUrl}/api/v1/admin/agents/${target.uuid}`, {
|
|
449
|
+
method: "PATCH",
|
|
450
|
+
headers: {
|
|
451
|
+
Authorization: `Bearer ${accessToken}`,
|
|
452
|
+
"Content-Type": "application/json"
|
|
453
|
+
},
|
|
454
|
+
body: JSON.stringify({ clientId: options.clientId }),
|
|
455
|
+
signal: AbortSignal.timeout(1e4)
|
|
456
|
+
});
|
|
457
|
+
if (!patchRes.ok) fail("BIND_CLIENT_ERROR", (await patchRes.json().catch(() => ({}))).error ?? `Bind failed (HTTP ${patchRes.status})`, 1);
|
|
458
|
+
process.stderr.write(` \u2713 Bound "${target.name ?? target.uuid}" to client ${options.clientId}.\n`);
|
|
459
|
+
success({
|
|
460
|
+
agentId: target.uuid,
|
|
461
|
+
clientId: options.clientId
|
|
462
|
+
});
|
|
463
|
+
} catch (error) {
|
|
464
|
+
fail("BIND_CLIENT_ERROR", error instanceof Error ? error.message : String(error));
|
|
465
|
+
}
|
|
466
|
+
});
|
|
443
467
|
bind.command("bot").description("Bind a Feishu bot to this agent (self-service)").requiredOption("--platform <platform>", "Platform: feishu").requiredOption("--app-id <id>", "Feishu bot App ID").requiredOption("--app-secret <secret>", "Feishu bot App Secret").option("--agent <name>", "Local agent alias (default: first configured)").option("--server <url>", "Hub server URL").action(async (options) => {
|
|
444
468
|
try {
|
|
445
469
|
if (options.platform !== "feishu") fail("UNSUPPORTED_PLATFORM", `Platform "${options.platform}" is not supported. Use "feishu".`);
|
|
@@ -740,9 +764,94 @@ function registerAgentCommands(program) {
|
|
|
740
764
|
});
|
|
741
765
|
}
|
|
742
766
|
//#endregion
|
|
767
|
+
//#region src/commands/connect.ts
|
|
768
|
+
/**
|
|
769
|
+
* Authenticate via connect token — exchange for full JWT credentials.
|
|
770
|
+
*/
|
|
771
|
+
async function authenticateWithToken(url, token) {
|
|
772
|
+
const res = await fetch(`${url}/api/v1/auth/connect-token`, {
|
|
773
|
+
method: "POST",
|
|
774
|
+
headers: { "Content-Type": "application/json" },
|
|
775
|
+
body: JSON.stringify({ token }),
|
|
776
|
+
signal: AbortSignal.timeout(1e4)
|
|
777
|
+
});
|
|
778
|
+
if (!res.ok) fail("AUTH_ERROR", (await res.json().catch(() => ({}))).error ?? `Token exchange failed (HTTP ${res.status})`, 1);
|
|
779
|
+
return await res.json();
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Authenticate via interactive username/password login.
|
|
783
|
+
*/
|
|
784
|
+
async function authenticateInteractive(url) {
|
|
785
|
+
process.stderr.write("\n Log in to Hub:\n");
|
|
786
|
+
const username = await input({ message: " Username:" });
|
|
787
|
+
const pw = await password({ message: " Password:" });
|
|
788
|
+
const loginRes = await fetch(`${url}/api/v1/auth/login`, {
|
|
789
|
+
method: "POST",
|
|
790
|
+
headers: { "Content-Type": "application/json" },
|
|
791
|
+
body: JSON.stringify({
|
|
792
|
+
username,
|
|
793
|
+
password: pw
|
|
794
|
+
}),
|
|
795
|
+
signal: AbortSignal.timeout(1e4)
|
|
796
|
+
});
|
|
797
|
+
if (!loginRes.ok) fail("AUTH_ERROR", (await loginRes.json().catch(() => ({}))).error ?? `Login failed (HTTP ${loginRes.status})`, 1);
|
|
798
|
+
return await loginRes.json();
|
|
799
|
+
}
|
|
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) => {
|
|
802
|
+
try {
|
|
803
|
+
const url = serverUrl.replace(/\/+$/, "");
|
|
804
|
+
setConfigValue(join(DEFAULT_CONFIG_DIR, "client.yaml"), "server.url", url);
|
|
805
|
+
process.stderr.write(`\n \u2713 Server configured: ${url}\n`);
|
|
806
|
+
saveCredentials({
|
|
807
|
+
...options.token ? await authenticateWithToken(url, options.token) : await authenticateInteractive(url),
|
|
808
|
+
serverUrl: url
|
|
809
|
+
});
|
|
810
|
+
process.stderr.write(" ✓ Authenticated\n");
|
|
811
|
+
resetConfig();
|
|
812
|
+
resetConfigMeta();
|
|
813
|
+
const config = await initConfig({
|
|
814
|
+
schema: clientConfigSchema,
|
|
815
|
+
role: "client"
|
|
816
|
+
});
|
|
817
|
+
const agentsDir = join(DEFAULT_CONFIG_DIR, "agents");
|
|
818
|
+
const agents = loadAgents({
|
|
819
|
+
schema: agentConfigSchema,
|
|
820
|
+
agentsDir
|
|
821
|
+
});
|
|
822
|
+
process.stderr.write(`\n Starting client (id: ${config.client.id})...\n`);
|
|
823
|
+
const runtime = new ClientRuntime(config.server.url, config.client.id);
|
|
824
|
+
for (const [name, agentConfig] of agents) runtime.addAgent(name, agentConfig);
|
|
825
|
+
await runtime.start();
|
|
826
|
+
runtime.watchAgentsDir(agentsDir);
|
|
827
|
+
const shutdown = async () => {
|
|
828
|
+
process.stderr.write("\n Shutting down...\n");
|
|
829
|
+
runtime.unwatchAgentsDir();
|
|
830
|
+
await runtime.stop();
|
|
831
|
+
process.exit(0);
|
|
832
|
+
};
|
|
833
|
+
process.on("SIGINT", () => void shutdown());
|
|
834
|
+
process.on("SIGTERM", () => void shutdown());
|
|
835
|
+
await new Promise(() => {});
|
|
836
|
+
} catch (error) {
|
|
837
|
+
if (error.name === "ExitPromptError") {
|
|
838
|
+
process.stderr.write("\n Cancelled.\n");
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
842
|
+
process.stderr.write(` Error: ${msg}\n`);
|
|
843
|
+
process.exit(1);
|
|
844
|
+
} finally {
|
|
845
|
+
resetConfig();
|
|
846
|
+
resetConfigMeta();
|
|
847
|
+
}
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
//#endregion
|
|
743
851
|
//#region src/commands/client.ts
|
|
744
852
|
function registerClientCommands(program) {
|
|
745
853
|
const client = program.command("client").description("Client runtime — connect agents to the server");
|
|
854
|
+
registerConnectCommand(client);
|
|
746
855
|
client.command("start").description("Start client — connect all configured agents to the server").option("--no-interactive", "Skip interactive prompts (for Docker/CI)").action(async (options) => {
|
|
747
856
|
try {
|
|
748
857
|
await promptMissingFields({
|
|
@@ -759,8 +868,8 @@ function registerClientCommands(program) {
|
|
|
759
868
|
schema: agentConfigSchema,
|
|
760
869
|
agentsDir
|
|
761
870
|
});
|
|
762
|
-
process.stderr.write(`\n Connecting to ${config.server.url}...\n`);
|
|
763
|
-
const runtime = new ClientRuntime(config.server.url);
|
|
871
|
+
process.stderr.write(`\n Connecting to ${config.server.url} (client id: ${config.client.id})...\n`);
|
|
872
|
+
const runtime = new ClientRuntime(config.server.url, config.client.id);
|
|
764
873
|
for (const [name, agentConfig] of agents) runtime.addAgent(name, agentConfig);
|
|
765
874
|
await runtime.start();
|
|
766
875
|
runtime.watchAgentsDir(agentsDir);
|
|
@@ -814,21 +923,21 @@ function registerClientCommands(program) {
|
|
|
814
923
|
process.stderr.write(" No agents directory found.\n");
|
|
815
924
|
}
|
|
816
925
|
});
|
|
817
|
-
client.command("hub-list").description("List
|
|
926
|
+
client.command("hub-list").description("List clients on the Hub server").option("--server <url>", "Hub server URL").action(async (options) => {
|
|
818
927
|
try {
|
|
819
928
|
const serverUrl = resolveServerUrl(options.server);
|
|
820
929
|
const token = await ensureFreshAccessToken();
|
|
821
|
-
const response = await fetch(`${serverUrl}/api/v1/
|
|
930
|
+
const response = await fetch(`${serverUrl}/api/v1/clients`, {
|
|
822
931
|
headers: { Authorization: `Bearer ${token}` },
|
|
823
932
|
signal: AbortSignal.timeout(1e4)
|
|
824
933
|
});
|
|
825
934
|
if (!response.ok) fail("FETCH_ERROR", `Server returned ${response.status}`, 1);
|
|
826
935
|
const clients = await response.json();
|
|
827
936
|
if (clients.length === 0) {
|
|
828
|
-
process.stderr.write(" No
|
|
937
|
+
process.stderr.write(" No clients.\n");
|
|
829
938
|
return;
|
|
830
939
|
}
|
|
831
|
-
process.stderr.write(`\n
|
|
940
|
+
process.stderr.write(`\n Clients: ${clients.length}\n\n`);
|
|
832
941
|
const header = ` ${"CLIENT".padEnd(20)} ${"HOST".padEnd(25)} ${"AGENTS".padEnd(8)} CONNECTED`;
|
|
833
942
|
process.stderr.write(`${header}\n`);
|
|
834
943
|
process.stderr.write(` ${"─".repeat(header.length - 2)}\n`);
|
|
@@ -845,7 +954,7 @@ function registerClientCommands(program) {
|
|
|
845
954
|
try {
|
|
846
955
|
const serverUrl = resolveServerUrl(options.server);
|
|
847
956
|
const token = await ensureFreshAccessToken();
|
|
848
|
-
const response = await fetch(`${serverUrl}/api/v1/
|
|
957
|
+
const response = await fetch(`${serverUrl}/api/v1/clients/${clientId}/disconnect`, {
|
|
849
958
|
method: "POST",
|
|
850
959
|
headers: { Authorization: `Bearer ${token}` },
|
|
851
960
|
signal: AbortSignal.timeout(1e4)
|
|
@@ -961,101 +1070,17 @@ function isSecretField(schema, dotPath) {
|
|
|
961
1070
|
return false;
|
|
962
1071
|
}
|
|
963
1072
|
//#endregion
|
|
964
|
-
//#region src/commands/connect.ts
|
|
965
|
-
/**
|
|
966
|
-
* Authenticate via connect token — exchange for full JWT credentials.
|
|
967
|
-
*/
|
|
968
|
-
async function authenticateWithToken(url, token) {
|
|
969
|
-
const res = await fetch(`${url}/api/v1/auth/connect-token`, {
|
|
970
|
-
method: "POST",
|
|
971
|
-
headers: { "Content-Type": "application/json" },
|
|
972
|
-
body: JSON.stringify({ token }),
|
|
973
|
-
signal: AbortSignal.timeout(1e4)
|
|
974
|
-
});
|
|
975
|
-
if (!res.ok) fail("AUTH_ERROR", (await res.json().catch(() => ({}))).error ?? `Token exchange failed (HTTP ${res.status})`, 1);
|
|
976
|
-
return await res.json();
|
|
977
|
-
}
|
|
978
|
-
/**
|
|
979
|
-
* Authenticate via interactive username/password login.
|
|
980
|
-
*/
|
|
981
|
-
async function authenticateInteractive(url) {
|
|
982
|
-
process.stderr.write("\n Log in to Hub:\n");
|
|
983
|
-
const username = await input({ message: " Username:" });
|
|
984
|
-
const pw = await password({ message: " Password:" });
|
|
985
|
-
const loginRes = await fetch(`${url}/api/v1/auth/login`, {
|
|
986
|
-
method: "POST",
|
|
987
|
-
headers: { "Content-Type": "application/json" },
|
|
988
|
-
body: JSON.stringify({
|
|
989
|
-
username,
|
|
990
|
-
password: pw
|
|
991
|
-
}),
|
|
992
|
-
signal: AbortSignal.timeout(1e4)
|
|
993
|
-
});
|
|
994
|
-
if (!loginRes.ok) fail("AUTH_ERROR", (await loginRes.json().catch(() => ({}))).error ?? `Login failed (HTTP ${loginRes.status})`, 1);
|
|
995
|
-
return await loginRes.json();
|
|
996
|
-
}
|
|
997
|
-
function registerConnectCommand(program) {
|
|
998
|
-
program.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) => {
|
|
999
|
-
try {
|
|
1000
|
-
const url = serverUrl.replace(/\/+$/, "");
|
|
1001
|
-
setConfigValue(join(DEFAULT_CONFIG_DIR, "client.yaml"), "server.url", url);
|
|
1002
|
-
process.stderr.write(`\n \u2713 Server configured: ${url}\n`);
|
|
1003
|
-
saveCredentials({
|
|
1004
|
-
...options.token ? await authenticateWithToken(url, options.token) : await authenticateInteractive(url),
|
|
1005
|
-
serverUrl: url
|
|
1006
|
-
});
|
|
1007
|
-
process.stderr.write(" ✓ Authenticated\n");
|
|
1008
|
-
resetConfig();
|
|
1009
|
-
resetConfigMeta();
|
|
1010
|
-
const config = await initConfig({
|
|
1011
|
-
schema: clientConfigSchema,
|
|
1012
|
-
role: "client"
|
|
1013
|
-
});
|
|
1014
|
-
const agentsDir = join(DEFAULT_CONFIG_DIR, "agents");
|
|
1015
|
-
const agents = loadAgents({
|
|
1016
|
-
schema: agentConfigSchema,
|
|
1017
|
-
agentsDir
|
|
1018
|
-
});
|
|
1019
|
-
process.stderr.write(`\n Starting client...\n`);
|
|
1020
|
-
const runtime = new ClientRuntime(config.server.url);
|
|
1021
|
-
for (const [name, agentConfig] of agents) runtime.addAgent(name, agentConfig);
|
|
1022
|
-
await runtime.start();
|
|
1023
|
-
runtime.watchAgentsDir(agentsDir);
|
|
1024
|
-
const shutdown = async () => {
|
|
1025
|
-
process.stderr.write("\n Shutting down...\n");
|
|
1026
|
-
runtime.unwatchAgentsDir();
|
|
1027
|
-
await runtime.stop();
|
|
1028
|
-
process.exit(0);
|
|
1029
|
-
};
|
|
1030
|
-
process.on("SIGINT", () => void shutdown());
|
|
1031
|
-
process.on("SIGTERM", () => void shutdown());
|
|
1032
|
-
await new Promise(() => {});
|
|
1033
|
-
} catch (error) {
|
|
1034
|
-
if (error.name === "ExitPromptError") {
|
|
1035
|
-
process.stderr.write("\n Cancelled.\n");
|
|
1036
|
-
return;
|
|
1037
|
-
}
|
|
1038
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
1039
|
-
process.stderr.write(` Error: ${msg}\n`);
|
|
1040
|
-
process.exit(1);
|
|
1041
|
-
} finally {
|
|
1042
|
-
resetConfig();
|
|
1043
|
-
resetConfigMeta();
|
|
1044
|
-
}
|
|
1045
|
-
});
|
|
1046
|
-
}
|
|
1047
|
-
//#endregion
|
|
1048
1073
|
//#region src/commands/onboard.ts
|
|
1049
1074
|
async function promptMissing(args) {
|
|
1050
1075
|
if (!args.server) try {
|
|
1051
|
-
const { resolveServerUrl } = await import("../bootstrap-
|
|
1076
|
+
const { resolveServerUrl } = await import("../bootstrap-CRDR6NwE.mjs").then((n) => n.t);
|
|
1052
1077
|
resolveServerUrl();
|
|
1053
1078
|
} catch {
|
|
1054
1079
|
args.server = await input({ message: "Hub server URL:" });
|
|
1055
1080
|
saveOnboardState(args);
|
|
1056
1081
|
}
|
|
1057
|
-
const { loadCredentials } = await import("../bootstrap-
|
|
1058
|
-
if (!loadCredentials()) throw new Error("No saved credentials. Run `first-tree-hub connect <server-url>` before onboarding.");
|
|
1082
|
+
const { loadCredentials } = await import("../bootstrap-CRDR6NwE.mjs").then((n) => n.t);
|
|
1083
|
+
if (!loadCredentials()) throw new Error("No saved credentials. Run `first-tree-hub client connect <server-url>` before onboarding.");
|
|
1059
1084
|
if (!args.id) {
|
|
1060
1085
|
args.id = await input({ message: "Agent ID:" });
|
|
1061
1086
|
saveOnboardState(args);
|
|
@@ -1080,11 +1105,9 @@ async function promptMissing(args) {
|
|
|
1080
1105
|
});
|
|
1081
1106
|
saveOnboardState(args);
|
|
1082
1107
|
}
|
|
1083
|
-
if (args.type !== "human" &&
|
|
1084
|
-
args.clientId = await input({
|
|
1085
|
-
|
|
1086
|
-
validate: (v) => v.length > 0 ? true : "clientId is required for non-human agents"
|
|
1087
|
-
});
|
|
1108
|
+
if (args.type !== "human" && args.clientId === void 0) {
|
|
1109
|
+
args.clientId = await input({ message: "Client ID (Enter to leave unbound — first WS connect will claim it):" });
|
|
1110
|
+
if (!args.clientId) args.clientId = void 0;
|
|
1088
1111
|
saveOnboardState(args);
|
|
1089
1112
|
}
|
|
1090
1113
|
if (!args.role) {
|
|
@@ -1117,10 +1140,7 @@ async function promptMissing(args) {
|
|
|
1117
1140
|
message: "Assistant ID:",
|
|
1118
1141
|
default: `${args.id}-assistant`
|
|
1119
1142
|
});
|
|
1120
|
-
if (
|
|
1121
|
-
message: "Client ID for the assistant (must be owned by you):",
|
|
1122
|
-
validate: (v) => v.length > 0 ? true : "clientId is required"
|
|
1123
|
-
});
|
|
1143
|
+
if (args.clientId === void 0) args.clientId = await input({ message: "Client ID for the assistant (Enter to leave unbound):" }) || void 0;
|
|
1124
1144
|
saveOnboardState(args);
|
|
1125
1145
|
}
|
|
1126
1146
|
}
|
|
@@ -1327,7 +1347,6 @@ registerClientCommands(program);
|
|
|
1327
1347
|
registerAgentCommands(program);
|
|
1328
1348
|
registerConfigCommands(program);
|
|
1329
1349
|
registerStatusCommand(program);
|
|
1330
|
-
registerConnectCommand(program);
|
|
1331
1350
|
registerOnboardCommand(program);
|
|
1332
1351
|
program.parse();
|
|
1333
1352
|
//#endregion
|