@agent-team-foundation/first-tree-hub 0.6.1 → 0.6.2
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-DW7aIpmE.mjs} +49 -156
- package/dist/cli/index.mjs +318 -120
- package/dist/{core-Dt3yNBTm.mjs → core-RXUUKkCO.mjs} +4573 -3803
- package/dist/drizzle/0019_agent_configs.sql +30 -0
- package/dist/drizzle/0020_unified_user_token.sql +148 -0
- package/dist/drizzle/0021_drop_agents_profile.sql +10 -0
- package/dist/drizzle/meta/_journal.json +21 -0
- package/dist/feishu-BZ8pnMrQ.mjs +832 -0
- package/dist/index.mjs +4 -4
- package/dist/web/assets/index-BMOr9-X2.js +308 -0
- package/dist/web/assets/index-CTl4pHIL.css +1 -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",
|
|
@@ -68,10 +63,6 @@ const clientConfigSchema = defineConfig({
|
|
|
68
63
|
"error"
|
|
69
64
|
]).default("info"), { env: "FIRST_TREE_HUB_LOG_LEVEL" })
|
|
70
65
|
});
|
|
71
|
-
/** Typed accessor for client configuration singleton. */
|
|
72
|
-
function getClientConfig() {
|
|
73
|
-
return getConfig();
|
|
74
|
-
}
|
|
75
66
|
const DEFAULT_HOME_DIR = process.env.FIRST_TREE_HUB_HOME ?? join(homedir(), ".first-tree-hub");
|
|
76
67
|
const DEFAULT_CONFIG_DIR = join(DEFAULT_HOME_DIR, "config");
|
|
77
68
|
const DEFAULT_DATA_DIR = join(DEFAULT_HOME_DIR, "data");
|
|
@@ -482,137 +473,55 @@ const serverConfigSchema = defineConfig({
|
|
|
482
473
|
//#endregion
|
|
483
474
|
//#region src/core/bootstrap.ts
|
|
484
475
|
var bootstrap_exports = /* @__PURE__ */ __exportAll({
|
|
485
|
-
|
|
486
|
-
checkBootstrapStatus: () => checkBootstrapStatus,
|
|
476
|
+
ensureFreshAccessToken: () => ensureFreshAccessToken,
|
|
487
477
|
ensureFreshAdminToken: () => ensureFreshAdminToken,
|
|
488
|
-
getGitHubToken: () => getGitHubToken,
|
|
489
|
-
getGitHubUsername: () => getGitHubUsername,
|
|
490
|
-
loadAgentTokenByName: () => loadAgentTokenByName,
|
|
491
478
|
loadCredentials: () => loadCredentials,
|
|
492
|
-
|
|
493
|
-
resolveAgentToken: () => resolveAgentToken,
|
|
479
|
+
resolveAccessToken: () => resolveAccessToken,
|
|
494
480
|
resolveServerUrl: () => resolveServerUrl,
|
|
495
481
|
saveAgentConfig: () => saveAgentConfig,
|
|
496
482
|
saveCredentials: () => saveCredentials
|
|
497
483
|
});
|
|
498
484
|
const CREDENTIALS_PATH = join(DEFAULT_CONFIG_DIR, "credentials.json");
|
|
499
485
|
/**
|
|
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
486
|
* Resolve Hub server URL from flag, env, or config.
|
|
487
|
+
*
|
|
488
|
+
* Uses resolveConfigReadonly (not the singleton getClientConfig) so CLI entry
|
|
489
|
+
* points don't have to remember to call initConfig() first.
|
|
525
490
|
*/
|
|
526
491
|
function resolveServerUrl(flagValue) {
|
|
527
492
|
if (flagValue) return flagValue;
|
|
528
493
|
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;
|
|
494
|
+
const server = resolveConfigReadonly({
|
|
495
|
+
schema: clientConfigSchema,
|
|
496
|
+
role: "client"
|
|
497
|
+
}).server;
|
|
498
|
+
if (server !== null && typeof server === "object") {
|
|
499
|
+
const url = Reflect.get(server, "url");
|
|
500
|
+
if (typeof url === "string" && url.length > 0) return url;
|
|
588
501
|
}
|
|
502
|
+
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
503
|
}
|
|
590
504
|
/**
|
|
591
|
-
* Resolve
|
|
592
|
-
*
|
|
593
|
-
*
|
|
594
|
-
*
|
|
505
|
+
* Resolve the current member access JWT from persisted credentials.
|
|
506
|
+
*
|
|
507
|
+
* Unified-user-token milestone: the CLI has a single credential store and a
|
|
508
|
+
* single onboarding path (`first-tree-hub connect`). The legacy
|
|
509
|
+
* `FIRST_TREE_HUB_TOKEN` env var is no longer read — callers get a clear
|
|
510
|
+
* error pointing at `connect` instead.
|
|
595
511
|
*/
|
|
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.");
|
|
512
|
+
function resolveAccessToken() {
|
|
513
|
+
const creds = loadCredentials();
|
|
514
|
+
if (!creds) throw new Error("No credentials found. Run `first-tree-hub connect <server-url>` to sign in.");
|
|
515
|
+
return creds.accessToken;
|
|
606
516
|
}
|
|
607
517
|
/**
|
|
608
|
-
* Ensure the persisted access token is fresh. Call before any
|
|
609
|
-
* when using persisted credentials. Returns the (possibly refreshed) access
|
|
518
|
+
* Ensure the persisted access token is fresh. Call before any API request
|
|
519
|
+
* when using persisted credentials. Returns the (possibly refreshed) access
|
|
520
|
+
* token. Service-user API keys are out of scope for this milestone.
|
|
610
521
|
*/
|
|
611
|
-
async function
|
|
612
|
-
const envToken = process.env.FIRST_TREE_HUB_ADMIN_TOKEN;
|
|
613
|
-
if (envToken) return envToken;
|
|
522
|
+
async function ensureFreshAccessToken() {
|
|
614
523
|
const creds = loadCredentials();
|
|
615
|
-
if (!creds) throw new Error("No credentials found
|
|
524
|
+
if (!creds) throw new Error("No credentials found. Run `first-tree-hub connect <server-url>` to sign in.");
|
|
616
525
|
if (!isTokenExpired(creds.accessToken)) return creds.accessToken;
|
|
617
526
|
const res = await fetch(`${creds.serverUrl}/api/v1/auth/refresh`, {
|
|
618
527
|
method: "POST",
|
|
@@ -620,7 +529,7 @@ async function ensureFreshAdminToken() {
|
|
|
620
529
|
body: JSON.stringify({ refreshToken: creds.refreshToken }),
|
|
621
530
|
signal: AbortSignal.timeout(1e4)
|
|
622
531
|
});
|
|
623
|
-
if (!res.ok) throw new Error("Access token expired and refresh failed
|
|
532
|
+
if (!res.ok) throw new Error("Access token expired and refresh failed. Run `first-tree-hub connect <server-url>`.");
|
|
624
533
|
const data = await res.json();
|
|
625
534
|
saveCredentials({
|
|
626
535
|
...creds,
|
|
@@ -628,7 +537,8 @@ async function ensureFreshAdminToken() {
|
|
|
628
537
|
});
|
|
629
538
|
return data.accessToken;
|
|
630
539
|
}
|
|
631
|
-
/**
|
|
540
|
+
/** Back-compat alias retained so existing call sites keep compiling. */
|
|
541
|
+
const ensureFreshAdminToken = ensureFreshAccessToken;
|
|
632
542
|
function isTokenExpired(token) {
|
|
633
543
|
try {
|
|
634
544
|
const parts = token.split(".");
|
|
@@ -649,7 +559,7 @@ function saveCredentials(creds) {
|
|
|
649
559
|
writeFileSync(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), { mode: 384 });
|
|
650
560
|
}
|
|
651
561
|
/**
|
|
652
|
-
* Load persisted credentials saved by `connect` command.
|
|
562
|
+
* Load persisted credentials saved by the `connect` command.
|
|
653
563
|
*/
|
|
654
564
|
function loadCredentials() {
|
|
655
565
|
try {
|
|
@@ -661,33 +571,16 @@ function loadCredentials() {
|
|
|
661
571
|
}
|
|
662
572
|
}
|
|
663
573
|
/**
|
|
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.
|
|
574
|
+
* Write agent config (agentId + runtime) to disk.
|
|
678
575
|
*/
|
|
679
|
-
function saveAgentConfig(agentName,
|
|
576
|
+
function saveAgentConfig(agentName, agentId, runtime) {
|
|
680
577
|
const agentDir = join(DEFAULT_CONFIG_DIR, "agents", agentName);
|
|
681
578
|
mkdirSync(agentDir, {
|
|
682
579
|
recursive: true,
|
|
683
580
|
mode: 448
|
|
684
581
|
});
|
|
685
|
-
writeFileSync(join(agentDir, "agent.yaml"), `
|
|
582
|
+
writeFileSync(join(agentDir, "agent.yaml"), `agentId: "${agentId}"\nruntime: ${runtime}\n`, { mode: 384 });
|
|
686
583
|
return agentDir;
|
|
687
584
|
}
|
|
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
585
|
//#endregion
|
|
693
|
-
export {
|
|
586
|
+
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 };
|