@agent-team-foundation/first-tree-hub 0.3.4 → 0.4.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.
@@ -72,7 +72,7 @@ const clientConfigSchema = defineConfig({
72
72
  function getClientConfig() {
73
73
  return getConfig();
74
74
  }
75
- const DEFAULT_HOME_DIR = join(homedir(), ".first-tree-hub");
75
+ const DEFAULT_HOME_DIR = process.env.FIRST_TREE_HUB_HOME ?? join(homedir(), ".first-tree-hub");
76
76
  const DEFAULT_CONFIG_DIR = join(DEFAULT_HOME_DIR, "config");
77
77
  const DEFAULT_DATA_DIR = join(DEFAULT_HOME_DIR, "data");
78
78
  function isFieldDef(value) {
@@ -450,33 +450,37 @@ const serverConfigSchema = defineConfig({
450
450
  secret: true
451
451
  })
452
452
  },
453
- contextTree: {
453
+ contextTree: optional({
454
454
  repo: field(z.string(), {
455
455
  env: "FIRST_TREE_HUB_CONTEXT_TREE_REPO",
456
456
  prompt: { message: "Context Tree repo URL (e.g. https://github.com/org/first-tree):" }
457
457
  }),
458
- branch: field(z.string().default("main")),
459
- syncInterval: field(z.number().default(60))
460
- },
458
+ branch: field(z.string().default("main"))
459
+ }),
461
460
  github: {
462
- token: field(z.string(), {
461
+ token: field(z.string().optional(), {
463
462
  env: "FIRST_TREE_HUB_GITHUB_TOKEN",
464
- secret: true,
465
- prompt: {
466
- message: "GitHub token (create at https://github.com/settings/tokens → repo scope):",
467
- type: "password"
468
- }
463
+ secret: true
469
464
  }),
470
465
  webhookSecret: field(z.string().optional(), {
471
466
  env: "FIRST_TREE_HUB_GITHUB_WEBHOOK_SECRET",
472
467
  secret: true
473
- })
468
+ }),
469
+ allowedOrg: field(z.string().optional(), { env: "FIRST_TREE_HUB_GITHUB_ALLOWED_ORG" })
474
470
  },
475
471
  cors: optional({ origin: field(z.string(), { env: "FIRST_TREE_HUB_CORS_ORIGIN" }) }),
476
472
  rateLimit: optional({
477
473
  max: field(z.number().default(100), { env: "FIRST_TREE_HUB_RATE_LIMIT_MAX" }),
478
474
  loginMax: field(z.number().default(5), { env: "FIRST_TREE_HUB_RATE_LIMIT_LOGIN_MAX" }),
479
475
  webhookMax: field(z.number().default(60), { env: "FIRST_TREE_HUB_RATE_LIMIT_WEBHOOK_MAX" })
476
+ }),
477
+ kael: optional({
478
+ endpoint: field(z.string(), { env: "KAEL_ENDPOINT" }),
479
+ apiKey: field(z.string(), {
480
+ env: "KAEL_API_KEY",
481
+ secret: true
482
+ }),
483
+ hubPublicUrl: field(z.string(), { env: "FIRST_TREE_HUB_PUBLIC_URL" })
480
484
  })
481
485
  });
482
486
  //#endregion
@@ -530,13 +534,19 @@ function resolveServerUrl(flagValue) {
530
534
  */
531
535
  async function bootstrapToken(serverUrl, agentId, options = {}) {
532
536
  const githubToken = getGitHubToken();
537
+ const body = { name: "bootstrap" };
538
+ if (options.type) body.type = options.type;
539
+ if (options.displayName) body.displayName = options.displayName;
540
+ if (options.delegateMention) body.delegateMention = options.delegateMention;
541
+ if (options.profile) body.profile = options.profile;
542
+ if (options.metadata) body.metadata = options.metadata;
533
543
  const res = await fetch(`${serverUrl}/api/v1/bootstrap/${encodeURIComponent(agentId)}/token`, {
534
544
  method: "POST",
535
545
  headers: {
536
546
  "X-GitHub-Token": githubToken,
537
547
  "Content-Type": "application/json"
538
548
  },
539
- body: JSON.stringify({ name: "bootstrap" })
549
+ body: JSON.stringify(body)
540
550
  });
541
551
  if (!res.ok) {
542
552
  const msg = (await res.json().catch(() => ({}))).error ?? `HTTP ${res.status}`;
@@ -580,4 +590,4 @@ async function checkBootstrapStatus(serverUrl, agentId) {
580
590
  return await res.json();
581
591
  }
582
592
  //#endregion
583
- export { resetConfig as _, getGitHubUsername as a, serverConfigSchema as b, DEFAULT_CONFIG_DIR as c, clientConfigSchema as d, collectMissingPrompts as f, readConfigFile as g, loadAgents as h, getGitHubToken as i, DEFAULT_DATA_DIR as l, initConfig as m, bootstrap_exports as n, resolveAgentToken as o, getConfigValue as p, checkBootstrapStatus as r, resolveServerUrl as s, bootstrapToken as t, agentConfigSchema as u, resetConfigMeta as v, setConfigValue as x, resolveConfigReadonly as y };
593
+ export { setConfigValue as S, readConfigFile as _, getGitHubUsername as a, resolveConfigReadonly as b, DEFAULT_CONFIG_DIR as c, agentConfigSchema as d, clientConfigSchema as f, loadAgents as g, initConfig as h, getGitHubToken as i, DEFAULT_DATA_DIR as l, getConfigValue as m, bootstrap_exports as n, resolveAgentToken as o, collectMissingPrompts as p, checkBootstrapStatus as r, resolveServerUrl as s, bootstrapToken as t, DEFAULT_HOME_DIR as u, resetConfig as v, serverConfigSchema as x, resetConfigMeta as y };
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { A as ClientRuntime, C as checkWebSocket, F as SessionRegistry, I as cleanWorkspaces, N as FirstTreeHubSDK, P as SdkError, S as checkServerReachable, _ as checkDocker, a as formatCheckReport, b as checkServerConfig, c as onboardContinue, d as runMigrations, f as checkAgentConfigs, g as checkDatabase, h as checkContextTreeRepo, i as promptMissingFields, j as createAdminUser, k as stopPostgres, l as onboardCreate, m as checkClientConfig, n as isInteractive, o as loadOnboardState, p as checkAgentTokens, r as promptAddAgent, s as onboardCheck, t as startServer, u as saveOnboardState, v as checkGitHubToken, w as printResults, x as checkServerHealth, y as checkNodeVersion } from "../core-CZjUVAU-.mjs";
3
- import { _ as resetConfig, b as serverConfigSchema, c as DEFAULT_CONFIG_DIR, d as clientConfigSchema, g as readConfigFile, h as loadAgents, l as DEFAULT_DATA_DIR, m as initConfig, o as resolveAgentToken, p as getConfigValue, s as resolveServerUrl, t as bootstrapToken, u as agentConfigSchema, v as resetConfigMeta, x as setConfigValue } from "../bootstrap-CPdLNPme.mjs";
2
+ import { A as createAdminUser, C as printResults, F as cleanWorkspaces, M as FirstTreeHubSDK, N as SdkError, O as stopPostgres, P as SessionRegistry, S as checkWebSocket, _ as checkGitHubToken, a as formatCheckReport, b as checkServerHealth, c as onboardCreate, d as checkAgentConfigs, f as checkAgentTokens, g as checkDocker, h as checkDatabase, i as promptMissingFields, k as ClientRuntime, l as saveOnboardState, m as checkContextTreeRepo, n as isInteractive, o as loadOnboardState, p as checkClientConfig, r as promptAddAgent, s as onboardCheck, t as startServer, u as runMigrations, v as checkNodeVersion, x as checkServerReachable, y as checkServerConfig } from "../core-CMeOAZmx.mjs";
3
+ import { S as setConfigValue, _ as readConfigFile, c as DEFAULT_CONFIG_DIR, d as agentConfigSchema, f as clientConfigSchema, g as loadAgents, h as initConfig, l as DEFAULT_DATA_DIR, m as getConfigValue, o as resolveAgentToken, s as resolveServerUrl, t as bootstrapToken, u as DEFAULT_HOME_DIR, v as resetConfig, x as serverConfigSchema, y as resetConfigMeta } from "../bootstrap-uyPaaI05.mjs";
4
4
  import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-Y4m2zFc3.mjs";
5
5
  import { createRequire } from "node:module";
6
6
  import { Command } from "commander";
@@ -168,7 +168,7 @@ function registerAgentCommands(program) {
168
168
  agent.command("token").description("Agent token management").command("bootstrap <agentId>").description("Bootstrap a token using GitHub identity (requires gh CLI)").option("--save-to <target>", "Save token to: \"agent\" (default) or a file path", "agent").option("--server <url>", "Hub server URL").action(async (agentId, options) => {
169
169
  try {
170
170
  const result = await bootstrapToken(resolveServerUrl(options.server), agentId, { saveTo: options.saveTo });
171
- if (options.saveTo === "agent") process.stderr.write(`Token saved to ~/.first-tree-hub/config/agents/${agentId}/agent.yaml\n`);
171
+ if (options.saveTo === "agent") process.stderr.write(`Token saved to ${DEFAULT_HOME_DIR}/config/agents/${agentId}/agent.yaml\n`);
172
172
  else process.stderr.write(`Token saved to ${options.saveTo}\n`);
173
173
  success({
174
174
  agentId: result.agentId,
@@ -455,14 +455,31 @@ function isSecretField(schema, dotPath) {
455
455
  //#endregion
456
456
  //#region src/commands/onboard.ts
457
457
  async function promptMissing(args) {
458
+ if (!args.server) try {
459
+ const { resolveServerUrl } = await import("../bootstrap-uyPaaI05.mjs").then((n) => n.n);
460
+ resolveServerUrl();
461
+ } catch {
462
+ args.server = await input({ message: "Hub server URL:" });
463
+ saveOnboardState(args);
464
+ }
465
+ const { resolveServerUrl } = await import("../bootstrap-uyPaaI05.mjs").then((n) => n.n);
466
+ const serverUrl = resolveServerUrl(args.server).replace(/\/+$/, "");
467
+ try {
468
+ const res = await fetch(`${serverUrl}/api/v1/bootstrap/config`);
469
+ if (res.ok) {
470
+ if (!(await res.json()).allowedOrg) throw new Error("Server does not have FIRST_TREE_HUB_GITHUB_ALLOWED_ORG configured.\n Ask the server admin to set this before onboarding.");
471
+ }
472
+ } catch (err) {
473
+ if (err instanceof Error && err.message.includes("FIRST_TREE_HUB_GITHUB_ALLOWED_ORG")) throw err;
474
+ }
458
475
  let ghUsername = null;
459
476
  try {
460
- const { getGitHubUsername } = await import("../bootstrap-CPdLNPme.mjs").then((n) => n.n);
477
+ const { getGitHubUsername } = await import("../bootstrap-uyPaaI05.mjs").then((n) => n.n);
461
478
  ghUsername = getGitHubUsername();
462
479
  } catch {}
463
480
  if (!args.id) {
464
481
  args.id = await input({
465
- message: "Member ID (directory name):",
482
+ message: "Agent ID:",
466
483
  default: ghUsername ?? void 0
467
484
  });
468
485
  saveOnboardState(args);
@@ -488,12 +505,18 @@ async function promptMissing(args) {
488
505
  saveOnboardState(args);
489
506
  }
490
507
  if (!args.role) {
491
- args.role = await input({ message: "Role:" });
492
- saveOnboardState(args);
508
+ const role = await input({ message: "Role (optional, Enter to skip):" });
509
+ if (role) {
510
+ args.role = role;
511
+ saveOnboardState(args);
512
+ }
493
513
  }
494
514
  if (!args.domains) {
495
- args.domains = await input({ message: "Domains (comma-separated):" });
496
- saveOnboardState(args);
515
+ const domains = await input({ message: "Domains (comma-separated, optional, Enter to skip):" });
516
+ if (domains) {
517
+ args.domains = domains;
518
+ saveOnboardState(args);
519
+ }
497
520
  }
498
521
  if (!args.displayName) {
499
522
  const name = await input({ message: `Display name (Enter to use "${args.id}"):` });
@@ -502,7 +525,7 @@ async function promptMissing(args) {
502
525
  saveOnboardState(args);
503
526
  }
504
527
  }
505
- if (!args.assistant) {
528
+ if (!args.assistant && args.type === "human") {
506
529
  if (await confirm({
507
530
  message: "Create a personal assistant?",
508
531
  default: false
@@ -514,14 +537,7 @@ async function promptMissing(args) {
514
537
  saveOnboardState(args);
515
538
  }
516
539
  }
517
- if (!args.server) try {
518
- const { resolveServerUrl } = await import("../bootstrap-CPdLNPme.mjs").then((n) => n.n);
519
- resolveServerUrl();
520
- } catch {
521
- args.server = await input({ message: "Hub server URL:" });
522
- saveOnboardState(args);
523
- }
524
- if (!args.feishuBotAppId) {
540
+ if (!args.feishuBotAppId && (args.type !== "human" || args.assistant)) {
525
541
  if (await confirm({
526
542
  message: "Bind Feishu bot?",
527
543
  default: false
@@ -533,7 +549,7 @@ async function promptMissing(args) {
533
549
  }
534
550
  }
535
551
  function registerOnboardCommand(program) {
536
- program.command("onboard").description("Onboard a new member to First Tree Hub (end-to-end)").option("--id <id>", "Member ID (directory name)").option("--type <type>", "Agent type: human | personal_assistant | autonomous_agent").option("--display-name <name>", "Display name (defaults to id)").option("--role <role>", "Role description").option("--domains <domains>", "Comma-separated domains").option("--assistant <id>", "Also create a personal_assistant with this ID").option("--delegate-mention <id>", "Set delegate_mention field").option("--server <url>", "Hub server URL").option("--feishu-bot-app-id <id>", "Feishu bot App ID").option("--feishu-bot-app-secret <secret>", "Feishu bot App Secret").option("--check", "Dry-run: show readiness checklist without executing").option("--continue", "Resume after PR merge (Phase 2)").action(async (options) => {
552
+ program.command("onboard").description("Onboard a new agent to First Tree Hub").option("--id <id>", "Agent ID").option("--type <type>", "Agent type: human | personal_assistant | autonomous_agent").option("--display-name <name>", "Display name (defaults to id)").option("--role <role>", "Role description").option("--domains <domains>", "Comma-separated domains").option("--profile <text>", "Agent profile (markdown)").option("--assistant <id>", "Also create a personal_assistant with this ID").option("--delegate-mention <id>", "Set delegate_mention field").option("--server <url>", "Hub server URL").option("--feishu-bot-app-id <id>", "Feishu bot App ID").option("--feishu-bot-app-secret <secret>", "Feishu bot App Secret").option("--check", "Dry-run: show readiness checklist without executing").action(async (options) => {
537
553
  try {
538
554
  const args = {
539
555
  ...loadOnboardState() ?? {},
@@ -542,20 +558,16 @@ function registerOnboardCommand(program) {
542
558
  ...options.displayName && { displayName: options.displayName },
543
559
  ...options.role && { role: options.role },
544
560
  ...options.domains && { domains: options.domains },
561
+ ...options.profile && { profile: options.profile },
545
562
  ...options.assistant && { assistant: options.assistant },
546
563
  ...options.delegateMention && { delegateMention: options.delegateMention },
547
564
  ...options.server && { server: options.server },
548
565
  ...options.feishuBotAppId && { feishuBotAppId: options.feishuBotAppId },
549
566
  ...options.feishuBotAppSecret && { feishuBotAppSecret: options.feishuBotAppSecret },
550
- check: options.check,
551
- continue: options.continue
567
+ check: options.check
552
568
  };
553
569
  if (!args.feishuBotAppId && process.env.FEISHU_APP_ID) args.feishuBotAppId = process.env.FEISHU_APP_ID;
554
570
  if (!args.feishuBotAppSecret && process.env.FEISHU_APP_SECRET) args.feishuBotAppSecret = process.env.FEISHU_APP_SECRET;
555
- if (args.continue) {
556
- await onboardContinue(args);
557
- return;
558
- }
559
571
  if (args.check) {
560
572
  const items = await onboardCheck(args);
561
573
  const report = formatCheckReport(items);
@@ -570,18 +582,11 @@ function registerOnboardCommand(program) {
570
582
  process.stderr.write(`\nOnboard Check: ${args.id ?? "(no id)"}\n\n${report}\n\n`);
571
583
  fail("MISSING_PARAMS", "Required parameters are missing. See checklist above.");
572
584
  }
573
- const result = await onboardCreate(args);
574
- process.stderr.write(`\nPR created: ${result.prUrl}\n`);
575
- process.stderr.write("Review and merge the PR, then run:\n");
576
- process.stderr.write(" first-tree-hub onboard --continue\n\n");
577
- success({
578
- phase: "create",
579
- prUrl: result.prUrl
580
- });
585
+ await onboardCreate(args);
581
586
  } catch (error) {
582
587
  const msg = error instanceof Error ? error.message : String(error);
583
588
  if (isInteractive()) {
584
- process.stderr.write(`\n ${msg}\n\n`);
589
+ process.stderr.write(`\n\u274C ${msg}\n\n`);
585
590
  process.exit(1);
586
591
  }
587
592
  fail("ONBOARD_ERROR", msg);