@agent-team-foundation/first-tree-hub 0.10.7 → 0.10.9

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,13 +1,13 @@
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 { $ as success, A as checkServerHealth, C as checkAgentConfigs, D as checkDocker, E as checkDatabase, F as getClientServiceStatus, H as stopPostgres, I as installClientService, J as removeLocalAgent, K as findStaleAliases, L as isServiceSupported, M as checkWebSocket, N as printResults, O as checkNodeVersion, P as reconcileAgentConfigs, Q as fail, S as runMigrations, T as checkClientConfig, U as ClientRuntime, W as handleClientOrgMismatch, Y as createOwner, Z as resolveReplyToFromEnv, _ as onboardCreate, a as declineUpdate, at as ClientUserMismatchError, b as createApiNameResolver, c as COMMAND_VERSION, ct as SessionRegistry, d as isInteractive, dt as applyClientLoggerConfig, f as promptAddAgent, ft as configureClientLoggerForService, g as onboardCheck, h as loadOnboardState, i as createExecuteUpdate, it as ClientOrgMismatchError, j as checkServerReachable, k as checkServerConfig, l as reconcileLocalRuntimeProviders, lt as cleanWorkspaces, m as formatCheckReport, nt as setJsonMode, o as promptUpdate, ot as FirstTreeHubSDK, p as promptMissingFields, q as formatStaleReason, r as registerSaaSConnectCommand, s as startServer, st as SdkError, tt as print, u as uploadClientCapabilities, ut as probeCapabilities, v as saveOnboardState, w as checkBackgroundService, x as migrateLocalAgentDirs, y as runHomeMigration } from "../saas-connect-CKQ15VLz.mjs";
4
4
  import "../logger-core-BTmvdflj-DjW8FM4T.mjs";
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";
5
+ import { C as serverConfigSchema, S as resolveConfigReadonly, _ 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-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
- import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync } from "node:fs";
10
+ import { existsSync, mkdirSync, readFileSync, readdirSync } from "node:fs";
11
11
  import { Command } from "commander";
12
12
  import { confirm, input, password, select } from "@inquirer/prompts";
13
13
  //#region src/commands/agent-config.ts
@@ -281,6 +281,21 @@ async function resolveAgent(serverUrl, adminToken, agentName) {
281
281
  if (!found) fail("NOT_FOUND", `Agent "${agentName}" not found`, 1);
282
282
  return found;
283
283
  }
284
+ /**
285
+ * Read the persisted `client.id` from `client.yaml`. Required by `agent
286
+ * prune` to filter the user-scoped `listMyAgents` response down to "what
287
+ * actually binds on THIS machine". `fail()` instead of throwing so the
288
+ * "no client.yaml — run client connect first" path renders as a clean
289
+ * CLI error rather than a stack trace.
290
+ */
291
+ function readClientId() {
292
+ const id = resolveConfigReadonly({
293
+ schema: clientConfigSchema,
294
+ role: "client"
295
+ }).client?.id;
296
+ if (typeof id !== "string" || id.length === 0) fail("MISSING_CLIENT_ID", "No client.id found in client.yaml. Run `first-tree-hub connect <token>` first.", 2);
297
+ return id;
298
+ }
284
299
  function registerAgentCommands(program) {
285
300
  const agent = program.command("agent").description("Agent management — config, bindings, messaging");
286
301
  registerAgentConfigCommands(agent);
@@ -307,36 +322,103 @@ function registerAgentCommands(program) {
307
322
  }
308
323
  });
309
324
  agent.command("remove <name>").description("Remove an agent from this client and delete its local runtime data (config dir, workspace, session state)").action((name) => {
310
- const agentDir = join(DEFAULT_CONFIG_DIR, "agents", name);
311
- if (!existsSync(agentDir)) {
325
+ if (!existsSync(join(DEFAULT_CONFIG_DIR, "agents", name))) {
312
326
  print.line(` Agent "${name}" not found.\n`);
313
327
  process.exit(1);
314
328
  }
315
- rmSync(agentDir, {
316
- recursive: true,
317
- force: true
318
- });
319
- rmSync(join(DEFAULT_DATA_DIR, "workspaces", name), {
320
- recursive: true,
321
- force: true
322
- });
323
- rmSync(join(DEFAULT_DATA_DIR, "sessions", `${name}.json`), { force: true });
329
+ removeLocalAgent(name);
324
330
  print.line(` Agent "${name}" removed.\n`);
325
331
  });
326
- agent.command("list").description("List locally-configured agents").action(() => {
327
- const agentsDir = join(DEFAULT_CONFIG_DIR, "agents");
332
+ agent.command("prune").description("Remove local agent aliases that won't bind on this client (unowned, pinned elsewhere, or unreadable)").option("--yes", "Skip the interactive confirmation prompt").option("--dry-run", "Only list what would be removed; don't touch the filesystem").option("--server <url>", "Hub server URL").action(async (options) => {
328
333
  try {
329
- const agents = loadAgents({
330
- schema: agentConfigSchema,
331
- agentsDir
334
+ const serverUrl = resolveServerUrl(options.server);
335
+ const clientId = readClientId();
336
+ const sdk = new FirstTreeHubSDK({
337
+ serverUrl,
338
+ getAccessToken: () => ensureFreshAccessToken()
332
339
  });
333
- if (agents.size === 0) {
340
+ const stale = await findStaleAliases({
341
+ clientId,
342
+ listPinnedAgents: () => sdk.listMyAgents()
343
+ });
344
+ if (stale.length === 0) {
345
+ print.line("\n ✓ No stale agent aliases. Local config matches the server.\n\n");
346
+ return;
347
+ }
348
+ print.line(`\n ${stale.length} stale ${stale.length === 1 ? "alias" : "aliases"}:\n\n`);
349
+ for (const s of stale) {
350
+ const id = s.agentId ?? "—";
351
+ print.line(` - ${s.name.padEnd(30)} ${id.padEnd(38)} ${formatStaleReason(s.reason)}\n`);
352
+ }
353
+ print.line("\n");
354
+ if (options.dryRun) {
355
+ print.line(" Dry run — no files removed. Re-run without --dry-run to delete.\n\n");
356
+ return;
357
+ }
358
+ if (!options.yes) {
359
+ if (!await confirm({
360
+ message: `Remove the ${stale.length} stale ${stale.length === 1 ? "alias" : "aliases"} above (config + workspace + session state)?`,
361
+ default: false
362
+ }).catch(() => false)) {
363
+ print.line(" Cancelled.\n\n");
364
+ return;
365
+ }
366
+ }
367
+ let removed = 0;
368
+ let failed = 0;
369
+ for (const s of stale) try {
370
+ removeLocalAgent(s.name);
371
+ print.line(` ✓ removed ${s.name}\n`);
372
+ removed++;
373
+ } catch (err) {
374
+ const msg = err instanceof Error ? err.message : String(err);
375
+ print.line(` ✗ ${s.name} (${msg.slice(0, 80)})\n`);
376
+ failed++;
377
+ }
378
+ print.line(`\n ${removed} pruned${failed > 0 ? `, ${failed} failed (re-run to retry)` : ""}.\n\n`);
379
+ if (failed > 0) process.exitCode = 1;
380
+ } catch (error) {
381
+ fail("PRUNE_ERROR", error instanceof Error ? error.message : String(error));
382
+ }
383
+ });
384
+ 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) => {
385
+ if (!(options.remote === true || typeof options.org === "string")) {
386
+ const agentsDir = join(DEFAULT_CONFIG_DIR, "agents");
387
+ try {
388
+ const agents = loadAgents({
389
+ schema: agentConfigSchema,
390
+ agentsDir
391
+ });
392
+ if (agents.size === 0) {
393
+ print.line(" No agents configured.\n");
394
+ return;
395
+ }
396
+ for (const [name, config] of agents) print.line(` ${name.padEnd(20)} runtime: ${config.runtime.padEnd(14)} uuid: ${config.agentId}\n`);
397
+ } catch {
334
398
  print.line(" No agents configured.\n");
399
+ }
400
+ return;
401
+ }
402
+ try {
403
+ const serverUrl = resolveServerUrl(options.server);
404
+ const token = await ensureFreshAccessToken();
405
+ const res = await fetch(`${serverUrl}/api/v1/me/managed-agents`, {
406
+ headers: { Authorization: `Bearer ${token}` },
407
+ signal: AbortSignal.timeout(1e4)
408
+ });
409
+ if (!res.ok) fail("LIST_ERROR", `Server returned ${res.status}`, 1);
410
+ const agents = await res.json();
411
+ const filtered = options.org ? agents.filter((a) => a.organizationId === options.org) : agents;
412
+ if (filtered.length === 0) {
413
+ print.line(" No agents found.\n");
335
414
  return;
336
415
  }
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");
416
+ const header = ` ${"NAME".padEnd(24)} ${"TYPE".padEnd(20)} ${"RUNTIME".padEnd(14)} ${"ORG".padEnd(40)} CLIENT`;
417
+ print.line(`${header}\n`);
418
+ print.line(` ${"─".repeat(header.length - 2)}\n`);
419
+ 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`);
420
+ } catch (error) {
421
+ fail("LIST_ERROR", error instanceof Error ? error.message : String(error));
340
422
  }
341
423
  });
342
424
  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 +1054,14 @@ function registerConnectCommand(parent) {
972
1054
  print.line("\n Cancelled.\n");
973
1055
  return;
974
1056
  }
1057
+ if (error instanceof ClientUserMismatchError) {
1058
+ print.line("\n");
1059
+ print.line(" ⚠️ This client.yaml is owned by a different user.\n");
1060
+ print.line(" Run `first-tree-hub client claim --confirm` to transfer ownership\n");
1061
+ print.line(" to your account. The previous owner's agents will be unpinned\n");
1062
+ print.line(" from this machine.\n\n");
1063
+ process.exit(1);
1064
+ }
975
1065
  if (error instanceof ClientOrgMismatchError) await handleClientOrgMismatch(error, {
976
1066
  managed: false,
977
1067
  configDir: DEFAULT_CONFIG_DIR,
@@ -1069,6 +1159,14 @@ function registerClientCommands(program) {
1069
1159
  process.on("SIGTERM", () => void shutdown());
1070
1160
  await new Promise(() => {});
1071
1161
  } catch (error) {
1162
+ if (error instanceof ClientUserMismatchError) {
1163
+ print.line("\n");
1164
+ print.line(" ⚠️ This client.yaml is owned by a different user.\n");
1165
+ print.line(" Run `first-tree-hub client claim --confirm` to transfer ownership\n");
1166
+ print.line(" to your account. The previous owner's agents will be unpinned\n");
1167
+ print.line(" from this machine.\n\n");
1168
+ process.exit(1);
1169
+ }
1072
1170
  if (error instanceof ClientOrgMismatchError) await handleClientOrgMismatch(error, {
1073
1171
  managed: options.interactive === false,
1074
1172
  configDir: DEFAULT_CONFIG_DIR,
@@ -1084,11 +1182,32 @@ function registerClientCommands(program) {
1084
1182
  });
1085
1183
  client.command("doctor").description("Check client environment readiness").action(async () => {
1086
1184
  print.line("\n First Tree Hub Client Doctor\n\n");
1185
+ let agentCheck;
1186
+ try {
1187
+ const serverUrl = resolveServerUrl();
1188
+ const cfg = await initConfig({
1189
+ schema: clientConfigSchema,
1190
+ role: "client"
1191
+ });
1192
+ const sdk = new FirstTreeHubSDK({
1193
+ serverUrl,
1194
+ getAccessToken: () => ensureFreshAccessToken()
1195
+ });
1196
+ agentCheck = await reconcileAgentConfigs({
1197
+ clientId: cfg.client.id,
1198
+ listPinnedAgents: () => sdk.listMyAgents()
1199
+ });
1200
+ } catch {
1201
+ agentCheck = checkAgentConfigs();
1202
+ } finally {
1203
+ resetConfig();
1204
+ resetConfigMeta();
1205
+ }
1087
1206
  printResults([
1088
1207
  checkNodeVersion(),
1089
1208
  checkClientConfig(),
1090
1209
  await checkServerReachable(),
1091
- checkAgentConfigs(),
1210
+ agentCheck,
1092
1211
  await checkWebSocket(),
1093
1212
  checkBackgroundService()
1094
1213
  ]);
@@ -1142,6 +1261,93 @@ function registerClientCommands(program) {
1142
1261
  fail("CLIENT_LIST_ERROR", error instanceof Error ? error.message : String(error));
1143
1262
  }
1144
1263
  });
1264
+ client.command("claim").description("Transfer ownership of this machine to your account (unpins the previous owner's agents from this machine)").option("--confirm", "Skip confirmation prompts (claim + auto-prune stale aliases)").option("--server <url>", "Hub server URL").action(async (options) => {
1265
+ try {
1266
+ const config = await initConfig({
1267
+ schema: clientConfigSchema,
1268
+ role: "client"
1269
+ });
1270
+ const serverUrl = resolveServerUrl(options.server) ?? config.server.url;
1271
+ const clientId = config.client.id;
1272
+ print.line("\n");
1273
+ print.line(" Transferring ownership of this machine to your account.\n");
1274
+ print.line(" This will unpin the previous owner's agents from this client.\n\n");
1275
+ print.status("client.id", clientId);
1276
+ print.status("server", serverUrl);
1277
+ print.line("\n");
1278
+ if (!options.confirm) {
1279
+ if (!await confirm({
1280
+ message: "Proceed with ownership transfer?",
1281
+ default: false
1282
+ }).catch(() => false)) {
1283
+ print.line(" Cancelled.\n\n");
1284
+ return;
1285
+ }
1286
+ }
1287
+ const token = await ensureFreshAccessToken();
1288
+ const response = await fetch(`${serverUrl}/api/v1/me/clients/${clientId}/claim`, {
1289
+ method: "POST",
1290
+ headers: {
1291
+ Authorization: `Bearer ${token}`,
1292
+ "Content-Type": "application/json"
1293
+ },
1294
+ body: "{}",
1295
+ signal: AbortSignal.timeout(1e4)
1296
+ });
1297
+ if (!response.ok) {
1298
+ const body = await response.text();
1299
+ fail("CLAIM_ERROR", `Server returned ${response.status}: ${body}`, 1);
1300
+ }
1301
+ const result = await response.json();
1302
+ print.line(` ✓ Ownership transferred. ${result.unpinnedAgentCount} agent(s) unpinned.\n`);
1303
+ try {
1304
+ const sdk = new FirstTreeHubSDK({
1305
+ serverUrl,
1306
+ getAccessToken: () => ensureFreshAccessToken()
1307
+ });
1308
+ const stale = await findStaleAliases({
1309
+ clientId,
1310
+ listPinnedAgents: () => sdk.listMyAgents()
1311
+ });
1312
+ if (stale.length === 0) print.line(" No stale local aliases — local config already matches the server.\n");
1313
+ else {
1314
+ print.line(`\n ${stale.length} local ${stale.length === 1 ? "alias" : "aliases"} won't bind on this client:\n\n`);
1315
+ for (const s of stale) {
1316
+ const id = s.agentId ?? "—";
1317
+ print.line(` - ${s.name.padEnd(30)} ${id.padEnd(38)} ${formatStaleReason(s.reason)}\n`);
1318
+ }
1319
+ print.line("\n");
1320
+ if (options.confirm === true ? true : await confirm({
1321
+ message: `Remove the ${stale.length} stale ${stale.length === 1 ? "alias" : "aliases"} above (config + workspace + session state)?`,
1322
+ default: true
1323
+ }).catch(() => false)) {
1324
+ let removed = 0;
1325
+ let failed = 0;
1326
+ for (const s of stale) try {
1327
+ removeLocalAgent(s.name);
1328
+ print.line(` ✓ removed ${s.name}\n`);
1329
+ removed++;
1330
+ } catch (err) {
1331
+ const msg = err instanceof Error ? err.message : String(err);
1332
+ print.line(` ✗ ${s.name} (${msg.slice(0, 80)})\n`);
1333
+ failed++;
1334
+ }
1335
+ print.line(`\n ${removed} pruned${failed > 0 ? `, ${failed} failed (re-run \`agent prune\` to retry)` : ""}.\n`);
1336
+ } else print.line(" Skipped. Run `first-tree-hub agent prune` later to clean up.\n");
1337
+ }
1338
+ } catch (err) {
1339
+ const msg = err instanceof Error ? err.message : String(err);
1340
+ print.line(` (Could not check for stale aliases: ${msg.slice(0, 100)})\n`);
1341
+ print.line(" Run `first-tree-hub agent prune` after reconnecting.\n");
1342
+ }
1343
+ print.line("\n Run `first-tree-hub client start` to reconnect.\n\n");
1344
+ } catch (error) {
1345
+ fail("CLAIM_ERROR", error instanceof Error ? error.message : String(error));
1346
+ } finally {
1347
+ resetConfig();
1348
+ resetConfigMeta();
1349
+ }
1350
+ });
1145
1351
  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
1352
  try {
1147
1353
  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 { A as checkServerHealth, B as ensurePostgres, C as checkAgentConfigs, D as checkDocker, E as checkDatabase, F as getClientServiceStatus, G as rotateClientIdWithBackup, H as stopPostgres, I as installClientService, L as isServiceSupported, M as checkWebSocket, N as printResults, O as checkNodeVersion, R as resolveCliInvocation, S as runMigrations, T as checkClientConfig, U as ClientRuntime, V as isDockerAvailable, W as handleClientOrgMismatch, X as hasUser, Y as createOwner, _ as onboardCreate, d as isInteractive, et as blank, f as promptAddAgent, g as onboardCheck, j as checkServerReachable, k as checkServerConfig, m as formatCheckReport, n as deriveHubUrlFromToken, ot as FirstTreeHubSDK, p as promptMissingFields, rt as status, s as startServer, st as SdkError, t as HubUrlDerivationError, y as runHomeMigration, z as uninstallClientService } from "./saas-connect-CKQ15VLz.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 };