@agent-team-foundation/first-tree-hub 0.2.0 → 0.3.1

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.
@@ -1,9 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import { A as loadRuntimeConfig, B as readConfigFile, C as stopPostgres, D as FirstTreeHubSDK, E as AgentSlot, F as agentConfigSchema, H as resetConfigMeta, I as clientConfigSchema, L as getConfigValue, M as createAdminUser, O as SdkError, P as DEFAULT_CONFIG_DIR, R as initConfig, T as AgentRuntime, U as serverConfigSchema, V as resetConfig, W as setConfigValue, _ as checkWebSocket, a as runMigrations, c as checkClientConfig, d as checkDocker, f as checkGitHubToken, g as checkServerReachable, h as checkServerHealth, i as promptMissingFields, j as registerBuiltinHandlers, k as getHandlerFactory, l as checkContextTreeRepo, m as checkServerConfig, o as checkAgentConfigs, p as checkNodeVersion, r as promptAddAgent, s as checkAgentTokens, t as startServer, u as checkDatabase, v as printResults, w as ClientRuntime, z as loadAgents } from "../core-CD3xEbyB.mjs";
2
+ 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-B9JsJR3Z.mjs";
3
+ import { A as ClientRuntime, B as registerBuiltinHandlers, C as checkWebSocket, F as SdkError, I as SessionRegistry, L as cleanWorkspaces, M as AgentSlot, N as DEFAULT_WORKSPACE_TTL_MS, P as FirstTreeHubSDK, R as getHandlerFactory, S as checkServerReachable, V as createAdminUser, _ 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 AgentRuntime, 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, z as loadRuntimeConfig } from "../core-D-c9r7D6.mjs";
4
+ import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-Y4m2zFc3.mjs";
3
5
  import { createRequire } from "node:module";
4
6
  import { Command } from "commander";
5
- import { existsSync, mkdirSync, rmSync } from "node:fs";
7
+ import { existsSync, mkdirSync, readdirSync, rmSync } from "node:fs";
6
8
  import { join } from "node:path";
9
+ import { confirm, input, select } from "@inquirer/prompts";
7
10
  //#region src/commands/admin.ts
8
11
  function registerAdminCommands(program) {
9
12
  program.command("admin").description("Admin user management").command("create").description("Create an admin user").option("-u, --username <name>", "Admin username", "admin").option("-p, --password <pass>", "Admin password (auto-generated if omitted)").action(async (options) => {
@@ -84,6 +87,37 @@ function registerAgentCommands(program) {
84
87
  });
85
88
  }
86
89
  //#endregion
90
+ //#region src/commands/bind.ts
91
+ function registerBindCommands(program) {
92
+ program.command("bind-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("--server <url>", "Hub server URL").action(async (options) => {
93
+ try {
94
+ if (options.platform !== "feishu") fail("UNSUPPORTED_PLATFORM", `Platform "${options.platform}" is not supported. Use "feishu".`);
95
+ await bindFeishuBot(resolveServerUrl(options.server), resolveAgentToken(), options.appId, options.appSecret);
96
+ process.stderr.write("Feishu bot bound successfully.\n");
97
+ success({
98
+ platform: "feishu",
99
+ bound: true
100
+ });
101
+ } catch (error) {
102
+ fail("BIND_BOT_ERROR", error instanceof Error ? error.message : String(error));
103
+ }
104
+ });
105
+ program.command("bind-user <humanAgentId>").description("Bind a Feishu user to a human agent (via delegate_mention)").requiredOption("--platform <platform>", "Platform: feishu").requiredOption("--feishu-id <id>", "Feishu user ID (ou_xxx)").option("--server <url>", "Hub server URL").action(async (humanAgentId, options) => {
106
+ try {
107
+ if (options.platform !== "feishu") fail("UNSUPPORTED_PLATFORM", `Platform "${options.platform}" is not supported. Use "feishu".`);
108
+ await bindFeishuUser(resolveServerUrl(options.server), resolveAgentToken(), humanAgentId, options.feishuId);
109
+ process.stderr.write(`Feishu user ${options.feishuId} bound to ${humanAgentId}.\n`);
110
+ success({
111
+ platform: "feishu",
112
+ humanAgentId,
113
+ feishuUserId: options.feishuId
114
+ });
115
+ } catch (error) {
116
+ fail("BIND_USER_ERROR", error instanceof Error ? error.message : String(error));
117
+ }
118
+ });
119
+ }
120
+ //#endregion
87
121
  //#region src/cli/util.ts
88
122
  function resolveConfig() {
89
123
  const token = process.env.FIRST_TREE_HUB_TOKEN;
@@ -237,7 +271,7 @@ function registerClientCommands(program) {
237
271
  process.exit(1);
238
272
  }
239
273
  });
240
- client.command("remove <name>").description("Remove an agent instance").action((name) => {
274
+ client.command("remove <name>").description("Remove an agent instance and its runtime data").action((name) => {
241
275
  const agentDir = join(DEFAULT_CONFIG_DIR, "agents", name);
242
276
  if (!existsSync(agentDir)) {
243
277
  process.stderr.write(` Agent "${name}" not found.\n`);
@@ -247,6 +281,11 @@ function registerClientCommands(program) {
247
281
  recursive: true,
248
282
  force: true
249
283
  });
284
+ rmSync(join(DEFAULT_DATA_DIR, "workspaces", name), {
285
+ recursive: true,
286
+ force: true
287
+ });
288
+ rmSync(join(DEFAULT_DATA_DIR, "sessions", `${name}.json`), { force: true });
250
289
  process.stderr.write(` Agent "${name}" removed.\n`);
251
290
  });
252
291
  client.command("list").description("List configured agents").action(() => {
@@ -268,6 +307,28 @@ function registerClientCommands(program) {
268
307
  process.stderr.write(" No agents configured.\n");
269
308
  }
270
309
  });
310
+ client.command("workspace").description("Manage agent workspaces").command("clean [agent-name]").description("Remove stale workspace directories (older than TTL with no active session)").option("--ttl <days>", "TTL in days", String(DEFAULT_WORKSPACE_TTL_MS / (1440 * 60 * 1e3))).action((agentName, options) => {
311
+ const defaultDays = DEFAULT_WORKSPACE_TTL_MS / (1440 * 60 * 1e3);
312
+ const ttlMs = Number.parseInt(options?.ttl ?? String(defaultDays), 10) * 24 * 60 * 60 * 1e3;
313
+ const workspacesDir = join(DEFAULT_DATA_DIR, "workspaces");
314
+ if (!existsSync(workspacesDir)) {
315
+ process.stderr.write(" No workspaces found.\n");
316
+ return;
317
+ }
318
+ const agentNames = agentName ? [agentName] : readdirSync(workspacesDir);
319
+ let totalRemoved = 0;
320
+ for (const name of agentNames) {
321
+ const agentWorkspaceRoot = join(workspacesDir, name);
322
+ if (!existsSync(agentWorkspaceRoot)) continue;
323
+ const persisted = new SessionRegistry(join(DEFAULT_DATA_DIR, "sessions", `${name}.json`)).load();
324
+ const activeChatIds = /* @__PURE__ */ new Set();
325
+ for (const [chatId, data] of persisted) if (data.status !== "evicted") activeChatIds.add(chatId);
326
+ const removed = cleanWorkspaces(agentWorkspaceRoot, activeChatIds, ttlMs);
327
+ totalRemoved += removed.length;
328
+ for (const chatId of removed) process.stderr.write(` Removed: ${name}/${chatId}\n`);
329
+ }
330
+ process.stderr.write(` ${totalRemoved} workspace(s) cleaned.\n`);
331
+ });
271
332
  }
272
333
  //#endregion
273
334
  //#region src/commands/config.ts
@@ -395,6 +456,142 @@ function registerHistoryCommand(program) {
395
456
  });
396
457
  }
397
458
  //#endregion
459
+ //#region src/commands/onboard.ts
460
+ async function promptMissing(args) {
461
+ let ghUsername = null;
462
+ try {
463
+ const { getGitHubUsername } = await import("../bootstrap-B9JsJR3Z.mjs").then((n) => n.n);
464
+ ghUsername = getGitHubUsername();
465
+ } catch {}
466
+ if (!args.id) {
467
+ args.id = await input({
468
+ message: "Member ID (directory name):",
469
+ default: ghUsername ?? void 0
470
+ });
471
+ saveOnboardState(args);
472
+ }
473
+ if (!args.type) {
474
+ args.type = await select({
475
+ message: "Agent type:",
476
+ choices: [
477
+ {
478
+ name: "human",
479
+ value: "human"
480
+ },
481
+ {
482
+ name: "personal_assistant",
483
+ value: "personal_assistant"
484
+ },
485
+ {
486
+ name: "autonomous_agent",
487
+ value: "autonomous_agent"
488
+ }
489
+ ]
490
+ });
491
+ saveOnboardState(args);
492
+ }
493
+ if (!args.role) {
494
+ args.role = await input({ message: "Role:" });
495
+ saveOnboardState(args);
496
+ }
497
+ if (!args.domains) {
498
+ args.domains = await input({ message: "Domains (comma-separated):" });
499
+ saveOnboardState(args);
500
+ }
501
+ if (!args.displayName) {
502
+ const name = await input({ message: `Display name (Enter to use "${args.id}"):` });
503
+ if (name) {
504
+ args.displayName = name;
505
+ saveOnboardState(args);
506
+ }
507
+ }
508
+ if (!args.assistant) {
509
+ if (await confirm({
510
+ message: "Create a personal assistant?",
511
+ default: false
512
+ })) {
513
+ args.assistant = await input({
514
+ message: "Assistant ID:",
515
+ default: `${args.id}-assistant`
516
+ });
517
+ saveOnboardState(args);
518
+ }
519
+ }
520
+ if (!args.server) try {
521
+ const { resolveServerUrl } = await import("../bootstrap-B9JsJR3Z.mjs").then((n) => n.n);
522
+ resolveServerUrl();
523
+ } catch {
524
+ args.server = await input({ message: "Hub server URL:" });
525
+ saveOnboardState(args);
526
+ }
527
+ if (!args.feishuBotAppId) {
528
+ if (await confirm({
529
+ message: "Bind Feishu bot?",
530
+ default: false
531
+ })) {
532
+ args.feishuBotAppId = await input({ message: "Feishu App ID:" });
533
+ args.feishuBotAppSecret = await input({ message: "Feishu App Secret:" });
534
+ saveOnboardState(args);
535
+ }
536
+ }
537
+ }
538
+ function registerOnboardCommand(program) {
539
+ 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) => {
540
+ try {
541
+ const args = {
542
+ ...loadOnboardState() ?? {},
543
+ ...options.id && { id: options.id },
544
+ ...options.type && { type: options.type },
545
+ ...options.displayName && { displayName: options.displayName },
546
+ ...options.role && { role: options.role },
547
+ ...options.domains && { domains: options.domains },
548
+ ...options.assistant && { assistant: options.assistant },
549
+ ...options.delegateMention && { delegateMention: options.delegateMention },
550
+ ...options.server && { server: options.server },
551
+ ...options.feishuBotAppId && { feishuBotAppId: options.feishuBotAppId },
552
+ ...options.feishuBotAppSecret && { feishuBotAppSecret: options.feishuBotAppSecret },
553
+ check: options.check,
554
+ continue: options.continue
555
+ };
556
+ if (!args.feishuBotAppId && process.env.FEISHU_APP_ID) args.feishuBotAppId = process.env.FEISHU_APP_ID;
557
+ if (!args.feishuBotAppSecret && process.env.FEISHU_APP_SECRET) args.feishuBotAppSecret = process.env.FEISHU_APP_SECRET;
558
+ if (args.continue) {
559
+ await onboardContinue(args);
560
+ return;
561
+ }
562
+ if (args.check) {
563
+ const items = await onboardCheck(args);
564
+ const report = formatCheckReport(items);
565
+ process.stderr.write(`\nOnboard Check: ${args.id ?? "(no id)"}\n\n${report}\n\n`);
566
+ if (items.some((i) => i.status === "missing_required" || i.status === "error")) process.exit(1);
567
+ return;
568
+ }
569
+ if (isInteractive()) await promptMissing(args);
570
+ const items = await onboardCheck(args);
571
+ if (items.some((i) => i.status === "missing_required" || i.status === "error")) {
572
+ const report = formatCheckReport(items);
573
+ process.stderr.write(`\nOnboard Check: ${args.id ?? "(no id)"}\n\n${report}\n\n`);
574
+ fail("MISSING_PARAMS", "Required parameters are missing. See checklist above.");
575
+ }
576
+ const result = await onboardCreate(args);
577
+ process.stderr.write(`\nPR created: ${result.prUrl}\n`);
578
+ process.stderr.write("Review and merge the PR, then run:\n");
579
+ process.stderr.write(" first-tree-hub onboard --continue\n\n");
580
+ success({
581
+ phase: "create",
582
+ prUrl: result.prUrl
583
+ });
584
+ } catch (error) {
585
+ const msg = error instanceof Error ? error.message : String(error);
586
+ if (isInteractive()) {
587
+ process.stderr.write(`\n❌ ${msg}\n\n`);
588
+ process.exit(1);
589
+ }
590
+ fail("ONBOARD_ERROR", msg);
591
+ }
592
+ });
593
+ }
594
+ //#endregion
398
595
  //#region src/commands/send.ts
399
596
  const MAX_STDIN_BYTES = 10 * 1024 * 1024;
400
597
  /** Read all of stdin as a string. Returns null if stdin is a TTY. */
@@ -555,6 +752,23 @@ function formatUptime(seconds) {
555
752
  return `${mins}m`;
556
753
  }
557
754
  //#endregion
755
+ //#region src/commands/token.ts
756
+ function registerTokenCommands(program) {
757
+ program.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) => {
758
+ try {
759
+ const result = await bootstrapToken(resolveServerUrl(options.server), agentId, { saveTo: options.saveTo });
760
+ if (options.saveTo === "agent") process.stderr.write(`Token saved to ~/.first-tree-hub/agents/${agentId}/agent.yaml\n`);
761
+ else process.stderr.write(`Token saved to ${options.saveTo}\n`);
762
+ success({
763
+ agentId: result.agentId,
764
+ tokenSaved: true
765
+ });
766
+ } catch (error) {
767
+ fail("BOOTSTRAP_ERROR", error instanceof Error ? error.message : String(error));
768
+ }
769
+ });
770
+ }
771
+ //#endregion
558
772
  //#region src/cli/connect.ts
559
773
  function registerConnectCommand(program) {
560
774
  program.command("connect").description("Connect a single agent to server and process messages").option("-t, --type <type>", "Handler type", "claude-code").option("--concurrency <n>", "Max parallel message processing", "5").option("--server <url>", "Override FIRST_TREE_HUB_SERVER").action(async (options) => {
@@ -628,6 +842,9 @@ registerStatusCommand(program);
628
842
  registerConnectCommand(program);
629
843
  registerStartCommand(program);
630
844
  registerAgentCommands(program);
845
+ registerOnboardCommand(program);
846
+ registerTokenCommands(program);
847
+ registerBindCommands(program);
631
848
  registerSendCommand(program);
632
849
  registerChatsCommand(program);
633
850
  registerHistoryCommand(program);