@agent-team-foundation/first-tree-hub 0.10.6 → 0.10.8

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,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import "../observability-DPyf745N-BSc8QNcR.mjs";
3
- import { A as checkServerHealth, C as checkAgentConfigs, D as checkDocker, E as checkDatabase, F as installClientService, G as createOwner, H as ClientRuntime, I as isServiceSupported, J as fail, M as checkWebSocket, N as printResults, O as checkNodeVersion, P as getClientServiceStatus, Q as setJsonMode, S as runMigrations, T as checkClientConfig, U as handleClientOrgMismatch, V as stopPostgres, Y as success, Z as print, _ as onboardCreate, a as declineUpdate, at as probeCapabilities, b as createApiNameResolver, c as COMMAND_VERSION, d as isInteractive, et as ClientOrgMismatchError, f as promptAddAgent, g as onboardCheck, h as loadOnboardState, i as createExecuteUpdate, it as cleanWorkspaces, j as checkServerReachable, k as checkServerConfig, l as reconcileLocalRuntimeProviders, m as formatCheckReport, nt as SdkError, o as promptUpdate, ot as applyClientLoggerConfig, p as promptMissingFields, q as resolveReplyToFromEnv, r as registerSaaSConnectCommand, rt as SessionRegistry, s as startServer, st as configureClientLoggerForService, tt as FirstTreeHubSDK, u as uploadClientCapabilities, v as saveOnboardState, w as checkBackgroundService, x as migrateLocalAgentDirs, y as runHomeMigration } from "../saas-connect-vLyx73kJ.mjs";
3
+ import { A as checkServerHealth, C as checkAgentConfigs, D as checkDocker, E as checkDatabase, F as installClientService, G as createOwner, H as ClientRuntime, I as isServiceSupported, J as fail, M as checkWebSocket, N as printResults, O as checkNodeVersion, P as getClientServiceStatus, Q as setJsonMode, S as runMigrations, T as checkClientConfig, U as handleClientOrgMismatch, V as stopPostgres, Y as success, Z as print, _ as onboardCreate, a as declineUpdate, at as cleanWorkspaces, b as createApiNameResolver, c as COMMAND_VERSION, ct as configureClientLoggerForService, d as isInteractive, et as ClientOrgMismatchError, f as promptAddAgent, g as onboardCheck, h as loadOnboardState, i as createExecuteUpdate, it as SessionRegistry, j as checkServerReachable, k as checkServerConfig, l as reconcileLocalRuntimeProviders, m as formatCheckReport, nt as FirstTreeHubSDK, o as promptUpdate, ot as probeCapabilities, p as promptMissingFields, q as resolveReplyToFromEnv, r as registerSaaSConnectCommand, rt as SdkError, s as startServer, st as applyClientLoggerConfig, tt as ClientUserMismatchError, u as uploadClientCapabilities, v as saveOnboardState, w as checkBackgroundService, x as migrateLocalAgentDirs, y as runHomeMigration } from "../saas-connect-D-fy3xu-.mjs";
4
4
  import "../logger-core-BTmvdflj-DjW8FM4T.mjs";
5
5
  import { C as serverConfigSchema, _ as loadAgents, b as resetConfig, c as saveCredentials, d as DEFAULT_HOME_DIR, f as agentConfigSchema, g as initConfig, h as getConfigValue, i as loadCredentials, l as DEFAULT_CONFIG_DIR, n as ensureFreshAccessToken, o as resolveServerUrl, p as clientConfigSchema, r as ensureFreshAdminToken, s as saveAgentConfig, u as DEFAULT_DATA_DIR, w as setConfigValue, x as resetConfigMeta, y as readConfigFile } from "../bootstrap-jx5nN1qZ.mjs";
6
- import "../dist-CbX9mUVH.mjs";
7
- import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-DvjRZMdZ.mjs";
8
- import "../invitation-BljIolbO-DLeHfURd.mjs";
6
+ import "../dist-DSr_I5Ia.mjs";
7
+ import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-eynC54km.mjs";
8
+ import "../invitation-B1pjAyOz-BaCA9PII.mjs";
9
9
  import { join } from "node:path";
10
10
  import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync } from "node:fs";
11
11
  import { Command } from "commander";
@@ -323,20 +323,44 @@ function registerAgentCommands(program) {
323
323
  rmSync(join(DEFAULT_DATA_DIR, "sessions", `${name}.json`), { force: true });
324
324
  print.line(` Agent "${name}" removed.\n`);
325
325
  });
326
- agent.command("list").description("List locally-configured agents").action(() => {
327
- const agentsDir = join(DEFAULT_CONFIG_DIR, "agents");
326
+ agent.command("list").description("List agents — locally-configured by default, or every agent you manage with --remote").option("--remote", "List every agent you manage on the Hub server (cross-org)").option("--org <id>", "When listing remote, restrict to a single organization id").option("--server <url>", "Hub server URL").action(async (options) => {
327
+ if (!(options.remote === true || typeof options.org === "string")) {
328
+ const agentsDir = join(DEFAULT_CONFIG_DIR, "agents");
329
+ try {
330
+ const agents = loadAgents({
331
+ schema: agentConfigSchema,
332
+ agentsDir
333
+ });
334
+ if (agents.size === 0) {
335
+ print.line(" No agents configured.\n");
336
+ return;
337
+ }
338
+ for (const [name, config] of agents) print.line(` ${name.padEnd(20)} runtime: ${config.runtime.padEnd(14)} uuid: ${config.agentId}\n`);
339
+ } catch {
340
+ print.line(" No agents configured.\n");
341
+ }
342
+ return;
343
+ }
328
344
  try {
329
- const agents = loadAgents({
330
- schema: agentConfigSchema,
331
- agentsDir
345
+ const serverUrl = resolveServerUrl(options.server);
346
+ const token = await ensureFreshAccessToken();
347
+ const res = await fetch(`${serverUrl}/api/v1/me/managed-agents`, {
348
+ headers: { Authorization: `Bearer ${token}` },
349
+ signal: AbortSignal.timeout(1e4)
332
350
  });
333
- if (agents.size === 0) {
334
- print.line(" No agents configured.\n");
351
+ if (!res.ok) fail("LIST_ERROR", `Server returned ${res.status}`, 1);
352
+ const agents = await res.json();
353
+ const filtered = options.org ? agents.filter((a) => a.organizationId === options.org) : agents;
354
+ if (filtered.length === 0) {
355
+ print.line(" No agents found.\n");
335
356
  return;
336
357
  }
337
- for (const [name, config] of agents) print.line(` ${name.padEnd(20)} runtime: ${config.runtime.padEnd(14)} uuid: ${config.agentId}\n`);
338
- } catch {
339
- print.line(" No agents configured.\n");
358
+ const header = ` ${"NAME".padEnd(24)} ${"TYPE".padEnd(20)} ${"RUNTIME".padEnd(14)} ${"ORG".padEnd(40)} CLIENT`;
359
+ print.line(`${header}\n`);
360
+ print.line(` ${"─".repeat(header.length - 2)}\n`);
361
+ for (const a of filtered) print.line(` ${(a.name ?? a.uuid).padEnd(24)} ${a.type.padEnd(20)} ${a.runtimeProvider.padEnd(14)} ${a.organizationId.padEnd(40)} ${a.clientId ?? "—"}\n`);
362
+ } catch (error) {
363
+ fail("LIST_ERROR", error instanceof Error ? error.message : String(error));
340
364
  }
341
365
  });
342
366
  agent.command("create <name>").description("Create an agent on Hub and bind it locally").requiredOption("--type <type>", "Agent type (human, personal_assistant, autonomous_agent)").requiredOption("--client-id <id>", "Client (machine) that will run this agent — must be owned by you. Run `first-tree-hub client connect` on that machine first.").option("--runtime <runtime>", "Runtime handler (default: claude-code)", "claude-code").option("--display-name <name>", "Display name").option("--server <url>", "Hub server URL").action(async (name, options) => {
@@ -972,6 +996,14 @@ function registerConnectCommand(parent) {
972
996
  print.line("\n Cancelled.\n");
973
997
  return;
974
998
  }
999
+ if (error instanceof ClientUserMismatchError) {
1000
+ print.line("\n");
1001
+ print.line(" ⚠️ This client.yaml is owned by a different user.\n");
1002
+ print.line(" Run `first-tree-hub client claim --confirm` to transfer ownership\n");
1003
+ print.line(" to your account. The previous owner's agents will be unpinned\n");
1004
+ print.line(" from this machine.\n\n");
1005
+ process.exit(1);
1006
+ }
975
1007
  if (error instanceof ClientOrgMismatchError) await handleClientOrgMismatch(error, {
976
1008
  managed: false,
977
1009
  configDir: DEFAULT_CONFIG_DIR,
@@ -1069,6 +1101,14 @@ function registerClientCommands(program) {
1069
1101
  process.on("SIGTERM", () => void shutdown());
1070
1102
  await new Promise(() => {});
1071
1103
  } catch (error) {
1104
+ if (error instanceof ClientUserMismatchError) {
1105
+ print.line("\n");
1106
+ print.line(" ⚠️ This client.yaml is owned by a different user.\n");
1107
+ print.line(" Run `first-tree-hub client claim --confirm` to transfer ownership\n");
1108
+ print.line(" to your account. The previous owner's agents will be unpinned\n");
1109
+ print.line(" from this machine.\n\n");
1110
+ process.exit(1);
1111
+ }
1072
1112
  if (error instanceof ClientOrgMismatchError) await handleClientOrgMismatch(error, {
1073
1113
  managed: options.interactive === false,
1074
1114
  configDir: DEFAULT_CONFIG_DIR,
@@ -1142,6 +1182,53 @@ function registerClientCommands(program) {
1142
1182
  fail("CLIENT_LIST_ERROR", error instanceof Error ? error.message : String(error));
1143
1183
  }
1144
1184
  });
1185
+ client.command("claim").description("Transfer ownership of this machine to your account (unpins the previous owner's agents from this machine)").option("--confirm", "Skip the interactive confirmation prompt").option("--server <url>", "Hub server URL").action(async (options) => {
1186
+ try {
1187
+ const config = await initConfig({
1188
+ schema: clientConfigSchema,
1189
+ role: "client"
1190
+ });
1191
+ const serverUrl = resolveServerUrl(options.server) ?? config.server.url;
1192
+ const clientId = config.client.id;
1193
+ print.line("\n");
1194
+ print.line(" Transferring ownership of this machine to your account.\n");
1195
+ print.line(" This will unpin the previous owner's agents from this client.\n\n");
1196
+ print.status("client.id", clientId);
1197
+ print.status("server", serverUrl);
1198
+ print.line("\n");
1199
+ if (!options.confirm) {
1200
+ if (!await confirm({
1201
+ message: "Proceed with ownership transfer?",
1202
+ default: false
1203
+ }).catch(() => false)) {
1204
+ print.line(" Cancelled.\n\n");
1205
+ return;
1206
+ }
1207
+ }
1208
+ const token = await ensureFreshAccessToken();
1209
+ const response = await fetch(`${serverUrl}/api/v1/me/clients/${clientId}/claim`, {
1210
+ method: "POST",
1211
+ headers: {
1212
+ Authorization: `Bearer ${token}`,
1213
+ "Content-Type": "application/json"
1214
+ },
1215
+ body: "{}",
1216
+ signal: AbortSignal.timeout(1e4)
1217
+ });
1218
+ if (!response.ok) {
1219
+ const body = await response.text();
1220
+ fail("CLAIM_ERROR", `Server returned ${response.status}: ${body}`, 1);
1221
+ }
1222
+ const result = await response.json();
1223
+ print.line(` ✓ Ownership transferred. ${result.unpinnedAgentCount} agent(s) unpinned.\n`);
1224
+ print.line(" Run `first-tree-hub client start` to reconnect.\n\n");
1225
+ } catch (error) {
1226
+ fail("CLAIM_ERROR", error instanceof Error ? error.message : String(error));
1227
+ } finally {
1228
+ resetConfig();
1229
+ resetConfigMeta();
1230
+ }
1231
+ });
1145
1232
  client.command("hub-disconnect <clientId>").description("Force-disconnect a client from the Hub server").option("--server <url>", "Hub server URL").action(async (clientId, options) => {
1146
1233
  try {
1147
1234
  const serverUrl = resolveServerUrl(options.server);
@@ -613,6 +613,11 @@ const clientRegisterSchema = z.object({
613
613
  sdkVersion: z.string().max(50).optional(),
614
614
  wireCapabilities: clientWireCapabilitiesSchema.optional()
615
615
  });
616
+ z.object({
617
+ clientId: z.string(),
618
+ previousUserId: z.string().nullable(),
619
+ unpinnedAgentCount: z.number().int().nonnegative()
620
+ });
616
621
  const capabilityStateSchema = z.enum([
617
622
  "ok",
618
623
  "missing",
@@ -874,6 +879,13 @@ const createOrgFromMeSchema = z.object({
874
879
  });
875
880
  /** Body for `POST /auth/switch-org`. */
876
881
  const switchOrgSchema = z.object({ organizationId: z.string().min(1) });
882
+ z.object({
883
+ id: z.string(),
884
+ organizationId: z.string(),
885
+ organizationName: z.string(),
886
+ role: z.enum(["admin", "member"]),
887
+ agentId: z.string()
888
+ });
877
889
  const memberRoleSchema = z.enum(["admin", "member"]);
878
890
  const memberSchema = z.object({
879
891
  id: z.string(),
@@ -1,5 +1,5 @@
1
1
  import { d as __exportAll } from "./esm-CYu4tXXn.mjs";
2
- import { r as AGENT_SELECTOR_HEADER } from "./dist-CbX9mUVH.mjs";
2
+ import { r as AGENT_SELECTOR_HEADER } from "./dist-DSr_I5Ia.mjs";
3
3
  //#region src/core/feishu.ts
4
4
  var feishu_exports = /* @__PURE__ */ __exportAll({
5
5
  bindFeishuBot: () => bindFeishuBot,
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import "./observability-DPyf745N-BSc8QNcR.mjs";
2
- import { $ as status, A as checkServerHealth, B as isDockerAvailable, C as checkAgentConfigs, D as checkDocker, E as checkDatabase, F as installClientService, G as createOwner, H as ClientRuntime, I as isServiceSupported, K as hasUser, L as resolveCliInvocation, M as checkWebSocket, N as printResults, O as checkNodeVersion, P as getClientServiceStatus, R as uninstallClientService, S as runMigrations, T as checkClientConfig, U as handleClientOrgMismatch, V as stopPostgres, W as rotateClientIdWithBackup, X as blank, _ as onboardCreate, d as isInteractive, f as promptAddAgent, g as onboardCheck, j as checkServerReachable, k as checkServerConfig, m as formatCheckReport, n as deriveHubUrlFromToken, nt as SdkError, p as promptMissingFields, s as startServer, t as HubUrlDerivationError, tt as FirstTreeHubSDK, y as runHomeMigration, z as ensurePostgres } from "./saas-connect-vLyx73kJ.mjs";
2
+ import { $ as status, A as checkServerHealth, B as isDockerAvailable, C as checkAgentConfigs, D as checkDocker, E as checkDatabase, F as installClientService, G as createOwner, H as ClientRuntime, I as isServiceSupported, K as hasUser, L as resolveCliInvocation, M as checkWebSocket, N as printResults, O as checkNodeVersion, P as getClientServiceStatus, R as uninstallClientService, S as runMigrations, T as checkClientConfig, U as handleClientOrgMismatch, V as stopPostgres, W as rotateClientIdWithBackup, X as blank, _ as onboardCreate, d as isInteractive, f as promptAddAgent, g as onboardCheck, j as checkServerReachable, k as checkServerConfig, m as formatCheckReport, n as deriveHubUrlFromToken, nt as FirstTreeHubSDK, p as promptMissingFields, rt as SdkError, s as startServer, t as HubUrlDerivationError, y as runHomeMigration, z as ensurePostgres } from "./saas-connect-D-fy3xu-.mjs";
3
3
  import "./logger-core-BTmvdflj-DjW8FM4T.mjs";
4
4
  import { a as resolveAccessToken, n as ensureFreshAccessToken, o as resolveServerUrl, r as ensureFreshAdminToken } from "./bootstrap-jx5nN1qZ.mjs";
5
- import "./dist-CbX9mUVH.mjs";
6
- import { n as bindFeishuUser, t as bindFeishuBot } from "./feishu-DvjRZMdZ.mjs";
7
- import "./invitation-BljIolbO-DLeHfURd.mjs";
5
+ import "./dist-DSr_I5Ia.mjs";
6
+ import { n as bindFeishuUser, t as bindFeishuBot } from "./feishu-eynC54km.mjs";
7
+ import "./invitation-B1pjAyOz-BaCA9PII.mjs";
8
8
  export { ClientRuntime, FirstTreeHubSDK, HubUrlDerivationError, SdkError, bindFeishuBot, bindFeishuUser, blank, checkAgentConfigs, checkClientConfig, checkDatabase, checkDocker, checkNodeVersion, checkServerConfig, checkServerHealth, checkServerReachable, checkWebSocket, createOwner, deriveHubUrlFromToken, ensureFreshAccessToken, ensureFreshAdminToken, ensurePostgres, formatCheckReport, getClientServiceStatus, handleClientOrgMismatch, hasUser, installClientService, isDockerAvailable, isInteractive, isServiceSupported, onboardCheck, onboardCreate, printResults, promptAddAgent, promptMissingFields, resolveAccessToken, resolveCliInvocation, resolveServerUrl, rotateClientIdWithBackup, runHomeMigration, runMigrations, startServer, status, stopPostgres, uninstallClientService };
@@ -1,7 +1,7 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import { and, desc, eq, gt, isNull, or } from "drizzle-orm";
3
3
  import { index, integer, jsonb, pgTable, text, timestamp } from "drizzle-orm/pg-core";
4
- //#region ../server/dist/invitation-BljIolbO.mjs
4
+ //#region ../server/dist/invitation-B1pjAyOz.mjs
5
5
  /** Organization entity. Agents and chats belong to exactly one organization. */
6
6
  const organizations = pgTable("organizations", {
7
7
  id: text("id").primaryKey(),
@@ -63,10 +63,11 @@ var BadRequestError = class extends AppError {
63
63
  };
64
64
  /**
65
65
  * Thrown when an operation targets a client whose organization does not match
66
- * the caller's authenticated organization. A client is bound to exactly one
67
- * org for its lifetime; re-registering or operating under a different org's
68
- * credentials is refused. CLI consumers recognize the `code` field and
69
- * respond by abandoning the local clientId to register a fresh one.
66
+ * the caller's authenticated organization. Retained for wire compatibility:
67
+ * the read paths that produced this error were retired in
68
+ * decouple-client-from-identity §4.1, so the server itself no longer raises
69
+ * it. SDK consumers may still pattern-match the `code` field on legacy
70
+ * payloads.
70
71
  */
71
72
  var ClientOrgMismatchError = class extends AppError {
72
73
  code = "CLIENT_ORG_MISMATCH";
@@ -75,6 +76,19 @@ var ClientOrgMismatchError = class extends AppError {
75
76
  this.name = "ClientOrgMismatchError";
76
77
  }
77
78
  };
79
+ /**
80
+ * Thrown when a client.yaml is presented with a JWT whose user_id does not
81
+ * match the row's owner. The CLI responds by guiding the operator through
82
+ * `first-tree-hub client claim --confirm` to take over ownership, which
83
+ * unpins the previous owner's agents from this machine.
84
+ */
85
+ var ClientUserMismatchError = class extends AppError {
86
+ code = "CLIENT_USER_MISMATCH";
87
+ constructor(message = "Client belongs to a different user") {
88
+ super(403, message);
89
+ this.name = "ClientUserMismatchError";
90
+ }
91
+ };
78
92
  /** Generate a UUID v7 (time-ordered). No external dependency. */
79
93
  function uuidv7() {
80
94
  const now = BigInt(Date.now());
@@ -256,4 +270,4 @@ function buildInviteUrl(publicUrl, token) {
256
270
  return `${publicUrl.replace(/\/+$/, "")}/invite/${token}`;
257
271
  }
258
272
  //#endregion
259
- export { rotateInvitation as _, ForbiddenError as a, buildInviteUrl as c, getActiveInvitation as d, invitationRedemptions as f, recordRedemption as g, previewInvitation as h, ConflictError as i, ensureActiveInvitation as l, organizations as m, BadRequestError as n, NotFoundError as o, invitations as p, ClientOrgMismatchError as r, UnauthorizedError as s, AppError as t, findActiveByToken as u, users as v, uuidv7 as y };
273
+ export { recordRedemption as _, ConflictError as a, uuidv7 as b, UnauthorizedError as c, findActiveByToken as d, getActiveInvitation as f, previewInvitation as g, organizations as h, ClientUserMismatchError as i, buildInviteUrl as l, invitations as m, BadRequestError as n, ForbiddenError as o, invitationRedemptions as p, ClientOrgMismatchError as r, NotFoundError as s, AppError as t, ensureActiveInvitation as u, rotateInvitation as v, users as y };
@@ -0,0 +1,3 @@
1
+ import "./dist-DSr_I5Ia.mjs";
2
+ import { g as previewInvitation, v as rotateInvitation } from "./invitation-B1pjAyOz-BaCA9PII.mjs";
3
+ export { previewInvitation, rotateInvitation };