@agent-team-foundation/first-tree-hub 0.6.0 → 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-BnlTKa0H.mjs → bootstrap-DW7aIpmE.mjs} +50 -133
- package/dist/cli/index.mjs +319 -117
- package/dist/{core-B9bH7EjM.mjs → core-RXUUKkCO.mjs} +5008 -3951
- package/dist/drizzle/0018_agent_visibility.sql +13 -0
- 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/0018_snapshot.json +1938 -0
- package/dist/drizzle/meta/_journal.json +28 -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-C_FKYVro.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,113 +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
478
|
loadCredentials: () => loadCredentials,
|
|
491
|
-
|
|
492
|
-
resolveAgentToken: () => resolveAgentToken,
|
|
479
|
+
resolveAccessToken: () => resolveAccessToken,
|
|
493
480
|
resolveServerUrl: () => resolveServerUrl,
|
|
494
481
|
saveAgentConfig: () => saveAgentConfig,
|
|
495
482
|
saveCredentials: () => saveCredentials
|
|
496
483
|
});
|
|
497
484
|
const CREDENTIALS_PATH = join(DEFAULT_CONFIG_DIR, "credentials.json");
|
|
498
485
|
/**
|
|
499
|
-
* Get the current GitHub username from `gh auth status`.
|
|
500
|
-
*/
|
|
501
|
-
function getGitHubUsername() {
|
|
502
|
-
try {
|
|
503
|
-
const output = execSync("gh api /user --jq .login", { encoding: "utf-8" }).trim();
|
|
504
|
-
if (!output) throw new Error("Empty response");
|
|
505
|
-
return output;
|
|
506
|
-
} catch {
|
|
507
|
-
throw new Error("Failed to get GitHub username. Ensure `gh` CLI is installed and authenticated:\n gh auth login");
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
/**
|
|
511
|
-
* Get the GitHub auth token from `gh auth token`.
|
|
512
|
-
*/
|
|
513
|
-
function getGitHubToken() {
|
|
514
|
-
try {
|
|
515
|
-
const output = execSync("gh auth token", { encoding: "utf-8" }).trim();
|
|
516
|
-
if (!output) throw new Error("Empty response");
|
|
517
|
-
return output;
|
|
518
|
-
} catch {
|
|
519
|
-
throw new Error("Failed to get GitHub token. Ensure `gh` CLI is installed and authenticated:\n gh auth login");
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
/**
|
|
523
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.
|
|
524
490
|
*/
|
|
525
491
|
function resolveServerUrl(flagValue) {
|
|
526
492
|
if (flagValue) return flagValue;
|
|
527
|
-
if (process.env.
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
* Bootstrap a token for an agent using GitHub identity.
|
|
536
|
-
*/
|
|
537
|
-
async function bootstrapToken(serverUrl, agentName, options = {}) {
|
|
538
|
-
const githubToken = getGitHubToken();
|
|
539
|
-
const body = { name: "bootstrap" };
|
|
540
|
-
if (options.type) body.type = options.type;
|
|
541
|
-
if (options.displayName) body.displayName = options.displayName;
|
|
542
|
-
if (options.delegateMention) body.delegateMention = options.delegateMention;
|
|
543
|
-
if (options.profile) body.profile = options.profile;
|
|
544
|
-
if (options.metadata) body.metadata = options.metadata;
|
|
545
|
-
const res = await fetch(`${serverUrl}/api/v1/bootstrap/${encodeURIComponent(agentName)}/token`, {
|
|
546
|
-
method: "POST",
|
|
547
|
-
headers: {
|
|
548
|
-
"X-GitHub-Token": githubToken,
|
|
549
|
-
"Content-Type": "application/json"
|
|
550
|
-
},
|
|
551
|
-
body: JSON.stringify(body)
|
|
552
|
-
});
|
|
553
|
-
if (!res.ok) {
|
|
554
|
-
const msg = (await res.json().catch(() => ({}))).error ?? `HTTP ${res.status}`;
|
|
555
|
-
throw new Error(`Bootstrap failed for "${agentName}": ${msg}`);
|
|
493
|
+
if (process.env.FIRST_TREE_HUB_SERVER_URL) return process.env.FIRST_TREE_HUB_SERVER_URL;
|
|
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;
|
|
556
501
|
}
|
|
557
|
-
|
|
558
|
-
const isHuman = options.type === "human";
|
|
559
|
-
if ((options.saveTo === "agent" || !options.saveTo) && !isHuman) {
|
|
560
|
-
const configDir = join(DEFAULT_CONFIG_DIR, "agents", agentName);
|
|
561
|
-
const configPath = `${configDir}/agent.yaml`;
|
|
562
|
-
mkdirSync(configDir, {
|
|
563
|
-
recursive: true,
|
|
564
|
-
mode: 448
|
|
565
|
-
});
|
|
566
|
-
writeFileSync(configPath, `token: "${data.token}"\nruntime: claude-code\n`, { mode: 384 });
|
|
567
|
-
chmodSync(configDir, 448);
|
|
568
|
-
} else if (options.saveTo && options.saveTo !== "agent") {
|
|
569
|
-
mkdirSync(dirname(options.saveTo), { recursive: true });
|
|
570
|
-
writeFileSync(options.saveTo, data.token, { mode: 384 });
|
|
571
|
-
}
|
|
572
|
-
return data;
|
|
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>");
|
|
573
503
|
}
|
|
574
504
|
/**
|
|
575
|
-
* Resolve
|
|
576
|
-
*
|
|
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.
|
|
577
511
|
*/
|
|
578
|
-
function
|
|
579
|
-
const
|
|
580
|
-
if (!
|
|
581
|
-
return
|
|
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;
|
|
582
516
|
}
|
|
583
517
|
/**
|
|
584
|
-
* Ensure the persisted access token is fresh. Call before any
|
|
585
|
-
* 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.
|
|
586
521
|
*/
|
|
587
|
-
async function
|
|
588
|
-
const envToken = process.env.FIRST_TREE_HUB_ADMIN_TOKEN;
|
|
589
|
-
if (envToken) return envToken;
|
|
522
|
+
async function ensureFreshAccessToken() {
|
|
590
523
|
const creds = loadCredentials();
|
|
591
|
-
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.");
|
|
592
525
|
if (!isTokenExpired(creds.accessToken)) return creds.accessToken;
|
|
593
526
|
const res = await fetch(`${creds.serverUrl}/api/v1/auth/refresh`, {
|
|
594
527
|
method: "POST",
|
|
@@ -596,7 +529,7 @@ async function ensureFreshAdminToken() {
|
|
|
596
529
|
body: JSON.stringify({ refreshToken: creds.refreshToken }),
|
|
597
530
|
signal: AbortSignal.timeout(1e4)
|
|
598
531
|
});
|
|
599
|
-
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>`.");
|
|
600
533
|
const data = await res.json();
|
|
601
534
|
saveCredentials({
|
|
602
535
|
...creds,
|
|
@@ -604,7 +537,8 @@ async function ensureFreshAdminToken() {
|
|
|
604
537
|
});
|
|
605
538
|
return data.accessToken;
|
|
606
539
|
}
|
|
607
|
-
/**
|
|
540
|
+
/** Back-compat alias retained so existing call sites keep compiling. */
|
|
541
|
+
const ensureFreshAdminToken = ensureFreshAccessToken;
|
|
608
542
|
function isTokenExpired(token) {
|
|
609
543
|
try {
|
|
610
544
|
const parts = token.split(".");
|
|
@@ -625,7 +559,7 @@ function saveCredentials(creds) {
|
|
|
625
559
|
writeFileSync(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), { mode: 384 });
|
|
626
560
|
}
|
|
627
561
|
/**
|
|
628
|
-
* Load persisted credentials saved by `connect` command.
|
|
562
|
+
* Load persisted credentials saved by the `connect` command.
|
|
629
563
|
*/
|
|
630
564
|
function loadCredentials() {
|
|
631
565
|
try {
|
|
@@ -637,33 +571,16 @@ function loadCredentials() {
|
|
|
637
571
|
}
|
|
638
572
|
}
|
|
639
573
|
/**
|
|
640
|
-
*
|
|
574
|
+
* Write agent config (agentId + runtime) to disk.
|
|
641
575
|
*/
|
|
642
|
-
|
|
643
|
-
const githubToken = getGitHubToken();
|
|
644
|
-
const res = await fetch(`${serverUrl}/api/v1/bootstrap/${encodeURIComponent(agentName)}/status`, { headers: { "X-GitHub-Token": githubToken } });
|
|
645
|
-
if (!res.ok) {
|
|
646
|
-
const body = await res.json().catch(() => ({}));
|
|
647
|
-
throw new Error(body.error ?? `HTTP ${res.status}`);
|
|
648
|
-
}
|
|
649
|
-
return await res.json();
|
|
650
|
-
}
|
|
651
|
-
/**
|
|
652
|
-
* Write agent config (token + runtime) to disk.
|
|
653
|
-
* Used by `agent create`, `agent add`, bootstrap, and server-pushed provisioning.
|
|
654
|
-
*/
|
|
655
|
-
function saveAgentConfig(agentName, token, runtime) {
|
|
576
|
+
function saveAgentConfig(agentName, agentId, runtime) {
|
|
656
577
|
const agentDir = join(DEFAULT_CONFIG_DIR, "agents", agentName);
|
|
657
578
|
mkdirSync(agentDir, {
|
|
658
579
|
recursive: true,
|
|
659
580
|
mode: 448
|
|
660
581
|
});
|
|
661
|
-
writeFileSync(join(agentDir, "agent.yaml"), `
|
|
582
|
+
writeFileSync(join(agentDir, "agent.yaml"), `agentId: "${agentId}"\nruntime: ${runtime}\n`, { mode: 384 });
|
|
662
583
|
return agentDir;
|
|
663
584
|
}
|
|
664
|
-
/** Mask a token for display: show first 6 + last 2 chars. */
|
|
665
|
-
function maskToken(token) {
|
|
666
|
-
return token.length > 8 ? `${token.slice(0, 6)}***${token.slice(-2)}` : "***";
|
|
667
|
-
}
|
|
668
585
|
//#endregion
|
|
669
|
-
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 };
|