@agent-team-foundation/first-tree-hub 0.6.1 → 0.6.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/{bootstrap-Dq_k_6ZD.mjs → bootstrap-DNL1cEwv.mjs} +54 -156
- package/dist/cli/index.mjs +400 -207
- package/dist/{core-Dt3yNBTm.mjs → core-B10jgThe.mjs} +4610 -3804
- package/dist/drizzle/0019_agent_configs.sql +30 -0
- package/dist/drizzle/0020_unified_user_token.sql +154 -0
- package/dist/drizzle/0021_drop_agents_profile.sql +10 -0
- package/dist/drizzle/meta/_journal.json +21 -0
- package/dist/feishu-BoMJHlOv.mjs +832 -0
- package/dist/index.mjs +4 -4
- package/dist/web/assets/index-CTl4pHIL.css +1 -0
- package/dist/web/assets/index-CnLpaSBg.js +308 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/feishu-Y4m2zFc3.mjs +0 -51
- package/dist/web/assets/index--kyp_ZHv.css +0 -1
- package/dist/web/assets/index-D7-5shxZ.js +0 -310
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-twds-ZHy.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { parse, stringify } from "yaml";
|
|
6
6
|
import { randomBytes } from "node:crypto";
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
|
-
import { execSync } from "node:child_process";
|
|
9
8
|
//#region ../shared/dist/config/index.mjs
|
|
10
9
|
/** Declare a config field with a Zod schema and optional metadata. */
|
|
11
10
|
function field(schema, options) {
|
|
@@ -27,8 +26,17 @@ function optional(shape) {
|
|
|
27
26
|
function defineConfig(shape) {
|
|
28
27
|
return shape;
|
|
29
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Agent config layout on disk: `~/.first-tree-hub/config/agents/<name>/agent.yaml`.
|
|
31
|
+
*
|
|
32
|
+
* After the unified-user-token milestone the local config no longer stores an
|
|
33
|
+
* agent bearer; authentication comes from the user's member JWT in
|
|
34
|
+
* `credentials.json`. The config just pins the agent UUID and its runtime so
|
|
35
|
+
* the runtime knows which agent to act as (via `X-Agent-Id`) and which
|
|
36
|
+
* handler to instantiate.
|
|
37
|
+
*/
|
|
30
38
|
const agentConfigSchema = defineConfig({
|
|
31
|
-
|
|
39
|
+
agentId: field(z.string().min(1)),
|
|
32
40
|
runtime: field(z.string().default("claude-code")),
|
|
33
41
|
concurrency: field(z.number().int().positive().default(5)),
|
|
34
42
|
session: {
|
|
@@ -36,23 +44,10 @@ const agentConfigSchema = defineConfig({
|
|
|
36
44
|
max_sessions: field(z.number().int().positive().default(10))
|
|
37
45
|
}
|
|
38
46
|
});
|
|
39
|
-
let _config;
|
|
40
47
|
/** Store the resolved config as a singleton. Called by initConfig(). */
|
|
41
|
-
function setConfig(config) {
|
|
42
|
-
_config = config;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Get the resolved config singleton.
|
|
46
|
-
* Must be called after initConfig().
|
|
47
|
-
*/
|
|
48
|
-
function getConfig() {
|
|
49
|
-
if (_config === void 0) throw new Error("Config not initialized. Call initConfig() first.");
|
|
50
|
-
return _config;
|
|
51
|
-
}
|
|
48
|
+
function setConfig(config) {}
|
|
52
49
|
/** Reset the config singleton. For testing only. */
|
|
53
|
-
function resetConfig() {
|
|
54
|
-
_config = void 0;
|
|
55
|
-
}
|
|
50
|
+
function resetConfig() {}
|
|
56
51
|
const clientConfigSchema = defineConfig({
|
|
57
52
|
server: { url: field(z.string(), {
|
|
58
53
|
env: "FIRST_TREE_HUB_SERVER_URL",
|
|
@@ -61,6 +56,10 @@ const clientConfigSchema = defineConfig({
|
|
|
61
56
|
default: "http://localhost:8000"
|
|
62
57
|
}
|
|
63
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
|
+
}) },
|
|
64
63
|
logLevel: field(z.enum([
|
|
65
64
|
"debug",
|
|
66
65
|
"info",
|
|
@@ -68,10 +67,6 @@ const clientConfigSchema = defineConfig({
|
|
|
68
67
|
"error"
|
|
69
68
|
]).default("info"), { env: "FIRST_TREE_HUB_LOG_LEVEL" })
|
|
70
69
|
});
|
|
71
|
-
/** Typed accessor for client configuration singleton. */
|
|
72
|
-
function getClientConfig() {
|
|
73
|
-
return getConfig();
|
|
74
|
-
}
|
|
75
70
|
const DEFAULT_HOME_DIR = process.env.FIRST_TREE_HUB_HOME ?? join(homedir(), ".first-tree-hub");
|
|
76
71
|
const DEFAULT_CONFIG_DIR = join(DEFAULT_HOME_DIR, "config");
|
|
77
72
|
const DEFAULT_DATA_DIR = join(DEFAULT_HOME_DIR, "data");
|
|
@@ -121,6 +116,7 @@ function coerceEnvValue(value, schema) {
|
|
|
121
116
|
return value;
|
|
122
117
|
}
|
|
123
118
|
function builtinAutoGenerate(strategy) {
|
|
119
|
+
if (strategy === "client-id") return `client_${randomBytes(4).toString("hex")}`;
|
|
124
120
|
const match = /^random:(\w+):(\d+)$/.exec(strategy);
|
|
125
121
|
if (!match) throw new Error(`Unknown auto-generation strategy: ${strategy}`);
|
|
126
122
|
const encoding = match[1];
|
|
@@ -482,137 +478,55 @@ const serverConfigSchema = defineConfig({
|
|
|
482
478
|
//#endregion
|
|
483
479
|
//#region src/core/bootstrap.ts
|
|
484
480
|
var bootstrap_exports = /* @__PURE__ */ __exportAll({
|
|
485
|
-
|
|
486
|
-
checkBootstrapStatus: () => checkBootstrapStatus,
|
|
481
|
+
ensureFreshAccessToken: () => ensureFreshAccessToken,
|
|
487
482
|
ensureFreshAdminToken: () => ensureFreshAdminToken,
|
|
488
|
-
getGitHubToken: () => getGitHubToken,
|
|
489
|
-
getGitHubUsername: () => getGitHubUsername,
|
|
490
|
-
loadAgentTokenByName: () => loadAgentTokenByName,
|
|
491
483
|
loadCredentials: () => loadCredentials,
|
|
492
|
-
|
|
493
|
-
resolveAgentToken: () => resolveAgentToken,
|
|
484
|
+
resolveAccessToken: () => resolveAccessToken,
|
|
494
485
|
resolveServerUrl: () => resolveServerUrl,
|
|
495
486
|
saveAgentConfig: () => saveAgentConfig,
|
|
496
487
|
saveCredentials: () => saveCredentials
|
|
497
488
|
});
|
|
498
489
|
const CREDENTIALS_PATH = join(DEFAULT_CONFIG_DIR, "credentials.json");
|
|
499
490
|
/**
|
|
500
|
-
* Get the current GitHub username from `gh auth status`.
|
|
501
|
-
*/
|
|
502
|
-
function getGitHubUsername() {
|
|
503
|
-
try {
|
|
504
|
-
const output = execSync("gh api /user --jq .login", { encoding: "utf-8" }).trim();
|
|
505
|
-
if (!output) throw new Error("Empty response");
|
|
506
|
-
return output;
|
|
507
|
-
} catch {
|
|
508
|
-
throw new Error("Failed to get GitHub username. Ensure `gh` CLI is installed and authenticated:\n gh auth login");
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
/**
|
|
512
|
-
* Get the GitHub auth token from `gh auth token`.
|
|
513
|
-
*/
|
|
514
|
-
function getGitHubToken() {
|
|
515
|
-
try {
|
|
516
|
-
const output = execSync("gh auth token", { encoding: "utf-8" }).trim();
|
|
517
|
-
if (!output) throw new Error("Empty response");
|
|
518
|
-
return output;
|
|
519
|
-
} catch {
|
|
520
|
-
throw new Error("Failed to get GitHub token. Ensure `gh` CLI is installed and authenticated:\n gh auth login");
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
/**
|
|
524
491
|
* Resolve Hub server URL from flag, env, or config.
|
|
492
|
+
*
|
|
493
|
+
* Uses resolveConfigReadonly (not the singleton getClientConfig) so CLI entry
|
|
494
|
+
* points don't have to remember to call initConfig() first.
|
|
525
495
|
*/
|
|
526
496
|
function resolveServerUrl(flagValue) {
|
|
527
497
|
if (flagValue) return flagValue;
|
|
528
498
|
if (process.env.FIRST_TREE_HUB_SERVER_URL) return process.env.FIRST_TREE_HUB_SERVER_URL;
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
* Bootstrap a token for an agent using GitHub identity.
|
|
537
|
-
*/
|
|
538
|
-
async function bootstrapToken(serverUrl, agentName, options = {}) {
|
|
539
|
-
const githubToken = getGitHubToken();
|
|
540
|
-
const body = { name: "bootstrap" };
|
|
541
|
-
if (options.type) body.type = options.type;
|
|
542
|
-
if (options.displayName) body.displayName = options.displayName;
|
|
543
|
-
if (options.delegateMention) body.delegateMention = options.delegateMention;
|
|
544
|
-
if (options.profile) body.profile = options.profile;
|
|
545
|
-
if (options.metadata) body.metadata = options.metadata;
|
|
546
|
-
const res = await fetch(`${serverUrl}/api/v1/bootstrap/${encodeURIComponent(agentName)}/token`, {
|
|
547
|
-
method: "POST",
|
|
548
|
-
headers: {
|
|
549
|
-
"X-GitHub-Token": githubToken,
|
|
550
|
-
"Content-Type": "application/json"
|
|
551
|
-
},
|
|
552
|
-
body: JSON.stringify(body)
|
|
553
|
-
});
|
|
554
|
-
if (!res.ok) {
|
|
555
|
-
const msg = (await res.json().catch(() => ({}))).error ?? `HTTP ${res.status}`;
|
|
556
|
-
throw new Error(`Bootstrap failed for "${agentName}": ${msg}`);
|
|
557
|
-
}
|
|
558
|
-
const data = await res.json();
|
|
559
|
-
const isHuman = options.type === "human";
|
|
560
|
-
if ((options.saveTo === "agent" || !options.saveTo) && !isHuman) {
|
|
561
|
-
const configDir = join(DEFAULT_CONFIG_DIR, "agents", agentName);
|
|
562
|
-
const configPath = `${configDir}/agent.yaml`;
|
|
563
|
-
mkdirSync(configDir, {
|
|
564
|
-
recursive: true,
|
|
565
|
-
mode: 448
|
|
566
|
-
});
|
|
567
|
-
writeFileSync(configPath, `token: "${data.token}"\nruntime: claude-code\n`, { mode: 384 });
|
|
568
|
-
chmodSync(configDir, 448);
|
|
569
|
-
} else if (options.saveTo && options.saveTo !== "agent") {
|
|
570
|
-
mkdirSync(dirname(options.saveTo), { recursive: true });
|
|
571
|
-
writeFileSync(options.saveTo, data.token, { mode: 384 });
|
|
572
|
-
}
|
|
573
|
-
return data;
|
|
574
|
-
}
|
|
575
|
-
/**
|
|
576
|
-
* Load an agent's token from `~/.first-tree-hub/agents/<agentName>/agent.yaml`.
|
|
577
|
-
* Returns null if the file is missing or has no token.
|
|
578
|
-
*/
|
|
579
|
-
function loadAgentTokenByName(agentName) {
|
|
580
|
-
const configPath = join(DEFAULT_CONFIG_DIR, "agents", agentName, "agent.yaml");
|
|
581
|
-
if (!existsSync(configPath)) return null;
|
|
582
|
-
try {
|
|
583
|
-
const raw = parse(readFileSync(configPath, "utf-8"));
|
|
584
|
-
if (typeof raw.token === "string" && raw.token.length > 0) return raw.token;
|
|
585
|
-
return null;
|
|
586
|
-
} catch {
|
|
587
|
-
return null;
|
|
499
|
+
const server = resolveConfigReadonly({
|
|
500
|
+
schema: clientConfigSchema,
|
|
501
|
+
role: "client"
|
|
502
|
+
}).server;
|
|
503
|
+
if (server !== null && typeof server === "object") {
|
|
504
|
+
const url = Reflect.get(server, "url");
|
|
505
|
+
if (typeof url === "string" && url.length > 0) return url;
|
|
588
506
|
}
|
|
507
|
+
throw new Error("Server URL not configured.\n Provide via: --server <url>, FIRST_TREE_HUB_SERVER_URL env var, or\n first-tree-hub config set -c server.url <url>");
|
|
589
508
|
}
|
|
590
509
|
/**
|
|
591
|
-
* Resolve
|
|
592
|
-
*
|
|
593
|
-
*
|
|
594
|
-
*
|
|
510
|
+
* Resolve the current member access JWT from persisted credentials.
|
|
511
|
+
*
|
|
512
|
+
* Unified-user-token milestone: the CLI has a single credential store and a
|
|
513
|
+
* single onboarding path (`first-tree-hub client connect`). The legacy
|
|
514
|
+
* `FIRST_TREE_HUB_TOKEN` env var is no longer read — callers get a clear
|
|
515
|
+
* error pointing at `client connect` instead.
|
|
595
516
|
*/
|
|
596
|
-
function
|
|
597
|
-
const
|
|
598
|
-
if (
|
|
599
|
-
|
|
600
|
-
if (agentName) {
|
|
601
|
-
const loaded = loadAgentTokenByName(agentName);
|
|
602
|
-
if (loaded) return loaded;
|
|
603
|
-
throw new Error(`Agent "${agentName}" has no token in ${join(DEFAULT_CONFIG_DIR, "agents", agentName)}/agent.yaml.\n Verify the agent exists locally or set FIRST_TREE_HUB_AGENT_TOKEN explicitly.`);
|
|
604
|
-
}
|
|
605
|
-
throw new Error("No agent token configured.\n Set FIRST_TREE_HUB_AGENT_TOKEN directly, or\n set FIRST_TREE_HUB_AGENT=<agentName> to use a stored agent config.");
|
|
517
|
+
function resolveAccessToken() {
|
|
518
|
+
const creds = loadCredentials();
|
|
519
|
+
if (!creds) throw new Error("No credentials found. Run `first-tree-hub client connect <server-url>` to sign in.");
|
|
520
|
+
return creds.accessToken;
|
|
606
521
|
}
|
|
607
522
|
/**
|
|
608
|
-
* Ensure the persisted access token is fresh. Call before any
|
|
609
|
-
* when using persisted credentials. Returns the (possibly refreshed) access
|
|
523
|
+
* Ensure the persisted access token is fresh. Call before any API request
|
|
524
|
+
* when using persisted credentials. Returns the (possibly refreshed) access
|
|
525
|
+
* token. Service-user API keys are out of scope for this milestone.
|
|
610
526
|
*/
|
|
611
|
-
async function
|
|
612
|
-
const envToken = process.env.FIRST_TREE_HUB_ADMIN_TOKEN;
|
|
613
|
-
if (envToken) return envToken;
|
|
527
|
+
async function ensureFreshAccessToken() {
|
|
614
528
|
const creds = loadCredentials();
|
|
615
|
-
if (!creds) throw new Error("No credentials found
|
|
529
|
+
if (!creds) throw new Error("No credentials found. Run `first-tree-hub client connect <server-url>` to sign in.");
|
|
616
530
|
if (!isTokenExpired(creds.accessToken)) return creds.accessToken;
|
|
617
531
|
const res = await fetch(`${creds.serverUrl}/api/v1/auth/refresh`, {
|
|
618
532
|
method: "POST",
|
|
@@ -620,7 +534,7 @@ async function ensureFreshAdminToken() {
|
|
|
620
534
|
body: JSON.stringify({ refreshToken: creds.refreshToken }),
|
|
621
535
|
signal: AbortSignal.timeout(1e4)
|
|
622
536
|
});
|
|
623
|
-
if (!res.ok) throw new Error("Access token expired and refresh failed
|
|
537
|
+
if (!res.ok) throw new Error("Access token expired and refresh failed. Run `first-tree-hub client connect <server-url>`.");
|
|
624
538
|
const data = await res.json();
|
|
625
539
|
saveCredentials({
|
|
626
540
|
...creds,
|
|
@@ -628,7 +542,8 @@ async function ensureFreshAdminToken() {
|
|
|
628
542
|
});
|
|
629
543
|
return data.accessToken;
|
|
630
544
|
}
|
|
631
|
-
/**
|
|
545
|
+
/** Back-compat alias retained so existing call sites keep compiling. */
|
|
546
|
+
const ensureFreshAdminToken = ensureFreshAccessToken;
|
|
632
547
|
function isTokenExpired(token) {
|
|
633
548
|
try {
|
|
634
549
|
const parts = token.split(".");
|
|
@@ -649,7 +564,7 @@ function saveCredentials(creds) {
|
|
|
649
564
|
writeFileSync(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), { mode: 384 });
|
|
650
565
|
}
|
|
651
566
|
/**
|
|
652
|
-
* Load persisted credentials saved by `connect` command.
|
|
567
|
+
* Load persisted credentials saved by the `connect` command.
|
|
653
568
|
*/
|
|
654
569
|
function loadCredentials() {
|
|
655
570
|
try {
|
|
@@ -661,33 +576,16 @@ function loadCredentials() {
|
|
|
661
576
|
}
|
|
662
577
|
}
|
|
663
578
|
/**
|
|
664
|
-
*
|
|
665
|
-
*/
|
|
666
|
-
async function checkBootstrapStatus(serverUrl, agentName) {
|
|
667
|
-
const githubToken = getGitHubToken();
|
|
668
|
-
const res = await fetch(`${serverUrl}/api/v1/bootstrap/${encodeURIComponent(agentName)}/status`, { headers: { "X-GitHub-Token": githubToken } });
|
|
669
|
-
if (!res.ok) {
|
|
670
|
-
const body = await res.json().catch(() => ({}));
|
|
671
|
-
throw new Error(body.error ?? `HTTP ${res.status}`);
|
|
672
|
-
}
|
|
673
|
-
return await res.json();
|
|
674
|
-
}
|
|
675
|
-
/**
|
|
676
|
-
* Write agent config (token + runtime) to disk.
|
|
677
|
-
* Used by `agent create`, `agent add`, bootstrap, and server-pushed provisioning.
|
|
579
|
+
* Write agent config (agentId + runtime) to disk.
|
|
678
580
|
*/
|
|
679
|
-
function saveAgentConfig(agentName,
|
|
581
|
+
function saveAgentConfig(agentName, agentId, runtime) {
|
|
680
582
|
const agentDir = join(DEFAULT_CONFIG_DIR, "agents", agentName);
|
|
681
583
|
mkdirSync(agentDir, {
|
|
682
584
|
recursive: true,
|
|
683
585
|
mode: 448
|
|
684
586
|
});
|
|
685
|
-
writeFileSync(join(agentDir, "agent.yaml"), `
|
|
587
|
+
writeFileSync(join(agentDir, "agent.yaml"), `agentId: "${agentId}"\nruntime: ${runtime}\n`, { mode: 384 });
|
|
686
588
|
return agentDir;
|
|
687
589
|
}
|
|
688
|
-
/** Mask a token for display: show first 6 + last 2 chars. */
|
|
689
|
-
function maskToken(token) {
|
|
690
|
-
return token.length > 8 ? `${token.slice(0, 6)}***${token.slice(-2)}` : "***";
|
|
691
|
-
}
|
|
692
590
|
//#endregion
|
|
693
|
-
export {
|
|
591
|
+
export { setConfigValue as C, serverConfigSchema as S, loadAgents as _, resolveAccessToken as a, resetConfigMeta as b, saveCredentials as c, DEFAULT_HOME_DIR as d, agentConfigSchema as f, initConfig as g, getConfigValue as h, loadCredentials as i, DEFAULT_CONFIG_DIR as l, collectMissingPrompts as m, ensureFreshAccessToken as n, resolveServerUrl as o, clientConfigSchema as p, ensureFreshAdminToken as r, saveAgentConfig as s, bootstrap_exports as t, DEFAULT_DATA_DIR as u, readConfigFile as v, resolveConfigReadonly as x, resetConfig as y };
|