@agent-team-foundation/first-tree-hub 0.6.1 → 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.
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { C as resetConfig, D as setConfigValue, E as serverConfigSchema, S as readConfigFile, _ as clientConfigSchema, b as initConfig, c as maskToken, d as saveAgentConfig, f as saveCredentials, g as agentConfigSchema, h as DEFAULT_HOME_DIR, i as ensureFreshAdminToken, l as resolveAgentToken, m as DEFAULT_DATA_DIR, p as DEFAULT_CONFIG_DIR, t as bootstrapToken, u as resolveServerUrl, w as resetConfigMeta, x as loadAgents, y as getConfigValue } from "../bootstrap-Dq_k_6ZD.mjs";
3
- import { A as FirstTreeHubSDK, D as ClientRuntime, E as stopPostgres, M as SessionRegistry, N as cleanWorkspaces, O as createOwner, _ as checkServerConfig, a as formatCheckReport, b as checkWebSocket, c as onboardCreate, d as checkAgentConfigs, f as checkAgentTokens, g as checkNodeVersion, h as checkDocker, i as promptMissingFields, j as SdkError, l as saveOnboardState, m as checkDatabase, n as isInteractive, o as loadOnboardState, p as checkClientConfig, r as promptAddAgent, s as onboardCheck, t as startServer, u as runMigrations, v as checkServerHealth, x as printResults, y as checkServerReachable } from "../core-Dt3yNBTm.mjs";
4
- import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-Y4m2zFc3.mjs";
2
+ import { C as setConfigValue, S as serverConfigSchema, _ as loadAgents, b as resetConfigMeta, c as saveCredentials, f as agentConfigSchema, g as initConfig, h as getConfigValue, 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, v as readConfigFile, y as resetConfig } from "../bootstrap-DW7aIpmE.mjs";
3
+ import { A as SdkError, D as createOwner, E as ClientRuntime, M as cleanWorkspaces, T as stopPostgres, _ as checkServerHealth, a as formatCheckReport, b as printResults, c as onboardCreate, d as checkAgentConfigs, f as checkClientConfig, g as checkServerConfig, h as checkNodeVersion, i as promptMissingFields, j as SessionRegistry, k as FirstTreeHubSDK, l as saveOnboardState, m as checkDocker, n as isInteractive, o as loadOnboardState, p as checkDatabase, r as promptAddAgent, s as onboardCheck, t as startServer, u as runMigrations, v as checkServerReachable, y as checkWebSocket } from "../core-RXUUKkCO.mjs";
4
+ import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-BZ8pnMrQ.mjs";
5
5
  import { createRequire } from "node:module";
6
6
  import { Command } from "commander";
7
- import { existsSync, mkdirSync, readdirSync, rmSync } from "node:fs";
7
+ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync } from "node:fs";
8
8
  import { join } from "node:path";
9
9
  import { confirm, input, password, select } from "@inquirer/prompts";
10
10
  //#region src/cli/output.ts
@@ -27,28 +27,234 @@ function fail(code, message, exitCode = 1) {
27
27
  process.exit(exitCode);
28
28
  }
29
29
  //#endregion
30
+ //#region src/commands/agent-config.ts
31
+ async function resolveAgentRecord(serverUrl, adminToken, agentName) {
32
+ const res = await fetch(`${serverUrl}/api/v1/admin/agents?limit=100`, {
33
+ headers: { Authorization: `Bearer ${adminToken}` },
34
+ signal: AbortSignal.timeout(1e4)
35
+ });
36
+ if (!res.ok) fail("FETCH_ERROR", `Failed to list agents: ${res.status}`, 1);
37
+ const found = (await res.json()).items.find((a) => a.name === agentName || a.uuid === agentName);
38
+ if (!found) fail("NOT_FOUND", `Agent "${agentName}" not found`, 1);
39
+ return found;
40
+ }
41
+ async function adminFetch(url, init) {
42
+ const { adminToken, headers, ...rest } = init;
43
+ const res = await fetch(url, {
44
+ ...rest,
45
+ headers: {
46
+ Authorization: `Bearer ${adminToken}`,
47
+ ...rest.body ? { "Content-Type": "application/json" } : {},
48
+ ...headers
49
+ },
50
+ signal: AbortSignal.timeout(15e3)
51
+ });
52
+ if (!res.ok) {
53
+ const text = await res.text();
54
+ fail(`HTTP_${res.status}`, text || res.statusText, res.status === 401 ? 3 : 1);
55
+ }
56
+ return await res.json();
57
+ }
58
+ async function getCurrent(serverUrl, adminToken, agentId) {
59
+ return adminFetch(`${serverUrl}/api/v1/admin/agents/${agentId}/config`, {
60
+ method: "GET",
61
+ adminToken
62
+ });
63
+ }
64
+ async function patchConfig(serverUrl, adminToken, agentId, expectedVersion, patch) {
65
+ return adminFetch(`${serverUrl}/api/v1/admin/agents/${agentId}/config`, {
66
+ method: "PATCH",
67
+ adminToken,
68
+ body: JSON.stringify({
69
+ expectedVersion,
70
+ payload: patch
71
+ })
72
+ });
73
+ }
74
+ function printConfig(cfg) {
75
+ process.stdout.write(`Agent: ${cfg.agentId}\n`);
76
+ process.stdout.write(`Version: ${cfg.version} (updated ${cfg.updatedAt} by ${cfg.updatedBy})\n`);
77
+ process.stdout.write(`\nModel: ${cfg.payload.model || "(unset)"}\n`);
78
+ process.stdout.write(`Prompt append: ${cfg.payload.prompt.append ? "(set)" : "(empty)"}\n`);
79
+ if (cfg.payload.prompt.append) process.stdout.write(` > ${cfg.payload.prompt.append.replace(/\n/g, "\n > ")}\n`);
80
+ process.stdout.write(`\nMCP servers (${cfg.payload.mcpServers.length}):\n`);
81
+ for (const s of cfg.payload.mcpServers) process.stdout.write(` - ${s.name} [${s.transport}]\n`);
82
+ process.stdout.write(`\nEnv (${cfg.payload.env.length}):\n`);
83
+ for (const e of cfg.payload.env) process.stdout.write(` - ${e.key}=${e.value} ${e.sensitive ? "(sensitive)" : ""}\n`);
84
+ process.stdout.write(`\nGit repos (${cfg.payload.gitRepos.length}):\n`);
85
+ for (const r of cfg.payload.gitRepos) {
86
+ const ref = r.ref ? `@${r.ref}` : "";
87
+ const path = r.localPath ? ` → ${r.localPath}` : "";
88
+ process.stdout.write(` - ${r.url}${ref}${path}\n`);
89
+ }
90
+ }
91
+ function registerAgentConfigCommands(parent) {
92
+ const config = parent.command("config").description("Manage agent runtime configuration (Step 8)");
93
+ config.command("get <agent>").description("Print the current runtime config for an agent").action(async (agentName) => {
94
+ const serverUrl = resolveServerUrl(process.env.FIRST_TREE_HUB_SERVER_URL);
95
+ const adminToken = await ensureFreshAdminToken();
96
+ const { uuid } = await resolveAgentRecord(serverUrl, adminToken, agentName);
97
+ printConfig(await getCurrent(serverUrl, adminToken, uuid));
98
+ });
99
+ config.command("set-model <agent> <model>").description("Replace the model field (e.g. claude-opus-4-6)").action(async (agentName, model) => {
100
+ const serverUrl = resolveServerUrl(process.env.FIRST_TREE_HUB_SERVER_URL);
101
+ const adminToken = await ensureFreshAdminToken();
102
+ const { uuid } = await resolveAgentRecord(serverUrl, adminToken, agentName);
103
+ const updated = await patchConfig(serverUrl, adminToken, uuid, (await getCurrent(serverUrl, adminToken, uuid)).version, { model });
104
+ success({
105
+ agentId: updated.agentId,
106
+ version: updated.version,
107
+ model: updated.payload.model
108
+ });
109
+ });
110
+ config.command("append-prompt <agent>").description("Replace the systemPrompt append text — reads from -f file or stdin").option("-f, --file <path>", "Read prompt text from this file").action(async (agentName, opts) => {
111
+ const serverUrl = resolveServerUrl(process.env.FIRST_TREE_HUB_SERVER_URL);
112
+ const adminToken = await ensureFreshAdminToken();
113
+ const { uuid } = await resolveAgentRecord(serverUrl, adminToken, agentName);
114
+ let text;
115
+ if (opts.file) text = readFileSync(opts.file, "utf-8");
116
+ else if (!process.stdin.isTTY) text = await new Promise((resolve, reject) => {
117
+ const chunks = [];
118
+ process.stdin.on("data", (c) => chunks.push(c));
119
+ process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
120
+ process.stdin.on("error", reject);
121
+ });
122
+ else fail("MISSING_INPUT", "Provide -f <file> or pipe prompt text via stdin", 2);
123
+ const updated = await patchConfig(serverUrl, adminToken, uuid, (await getCurrent(serverUrl, adminToken, uuid)).version, { prompt: { append: text } });
124
+ success({
125
+ agentId: updated.agentId,
126
+ version: updated.version,
127
+ append_length: text.length
128
+ });
129
+ });
130
+ config.command("add-mcp <agent>").description("Add or replace an MCP server (replace-by-name semantics)").requiredOption("--name <name>", "MCP server name").requiredOption("--transport <transport>", "stdio | http | sse").option("--command <command>", "stdio command").option("--args <args...>", "stdio command args").option("--url <url>", "http/sse URL").action(async (agentName, opts) => {
131
+ const serverUrl = resolveServerUrl(process.env.FIRST_TREE_HUB_SERVER_URL);
132
+ const adminToken = await ensureFreshAdminToken();
133
+ const { uuid } = await resolveAgentRecord(serverUrl, adminToken, agentName);
134
+ const current = await getCurrent(serverUrl, adminToken, uuid);
135
+ let server;
136
+ if (opts.transport === "stdio") {
137
+ if (!opts.command) fail("MISSING_COMMAND", "stdio transport requires --command", 2);
138
+ server = {
139
+ name: opts.name,
140
+ transport: "stdio",
141
+ command: opts.command,
142
+ args: opts.args
143
+ };
144
+ } else if (opts.transport === "http" || opts.transport === "sse") {
145
+ if (!opts.url) fail("MISSING_URL", `${opts.transport} transport requires --url`, 2);
146
+ server = {
147
+ name: opts.name,
148
+ transport: opts.transport,
149
+ url: opts.url
150
+ };
151
+ } else fail("BAD_TRANSPORT", `transport must be stdio|http|sse, got ${opts.transport}`, 2);
152
+ const remaining = current.payload.mcpServers.filter((s) => s.name !== opts.name);
153
+ const updated = await patchConfig(serverUrl, adminToken, uuid, current.version, { mcpServers: [...remaining, server] });
154
+ success({
155
+ agentId: updated.agentId,
156
+ version: updated.version,
157
+ mcpServer: opts.name
158
+ });
159
+ });
160
+ config.command("set-env <agent> <kv>").description("Set an env variable: KEY=VALUE. Use --sensitive for secrets.").option("--sensitive", "Mark this value as sensitive (encrypted at rest, masked in echo)").action(async (agentName, kv, opts) => {
161
+ const serverUrl = resolveServerUrl(process.env.FIRST_TREE_HUB_SERVER_URL);
162
+ const adminToken = await ensureFreshAdminToken();
163
+ const { uuid } = await resolveAgentRecord(serverUrl, adminToken, agentName);
164
+ const eqIdx = kv.indexOf("=");
165
+ if (eqIdx <= 0) fail("BAD_KV", "Expected KEY=VALUE", 2);
166
+ const key = kv.slice(0, eqIdx);
167
+ const value = kv.slice(eqIdx + 1);
168
+ const current = await getCurrent(serverUrl, adminToken, uuid);
169
+ const remaining = current.payload.env.filter((e) => e.key !== key);
170
+ const updated = await patchConfig(serverUrl, adminToken, uuid, current.version, { env: [...remaining, {
171
+ key,
172
+ value,
173
+ sensitive: opts.sensitive ?? false
174
+ }] });
175
+ success({
176
+ agentId: updated.agentId,
177
+ version: updated.version,
178
+ env: key
179
+ });
180
+ });
181
+ config.command("add-repo <agent> <url>").description("Add a Git repo to the agent's worktree set").option("--ref <ref>", "branch / tag / commit (defaults to repo HEAD)").option("--path <path>", "local path relative to session cwd").action(async (agentName, url, opts) => {
182
+ const serverUrl = resolveServerUrl(process.env.FIRST_TREE_HUB_SERVER_URL);
183
+ const adminToken = await ensureFreshAdminToken();
184
+ const { uuid } = await resolveAgentRecord(serverUrl, adminToken, agentName);
185
+ const current = await getCurrent(serverUrl, adminToken, uuid);
186
+ const remaining = current.payload.gitRepos.filter((r) => r.url !== url);
187
+ const updated = await patchConfig(serverUrl, adminToken, uuid, current.version, { gitRepos: [...remaining, {
188
+ url,
189
+ ref: opts.ref,
190
+ localPath: opts.path
191
+ }] });
192
+ success({
193
+ agentId: updated.agentId,
194
+ version: updated.version,
195
+ repo: url
196
+ });
197
+ });
198
+ config.command("dry-run <agent>").description("Validate a JSON patch and print the diff without persisting").requiredOption("-f, --file <path>", "JSON file with the partial payload to apply").action(async (agentName, opts) => {
199
+ const serverUrl = resolveServerUrl(process.env.FIRST_TREE_HUB_SERVER_URL);
200
+ const adminToken = await ensureFreshAdminToken();
201
+ const { uuid } = await resolveAgentRecord(serverUrl, adminToken, agentName);
202
+ const patch = JSON.parse(readFileSync(opts.file, "utf-8"));
203
+ const result = await adminFetch(`${serverUrl}/api/v1/admin/agents/${uuid}/config/dry-run`, {
204
+ method: "POST",
205
+ adminToken,
206
+ body: JSON.stringify({ payload: patch })
207
+ });
208
+ process.stdout.write(`Diff (${result.diff.length} change${result.diff.length === 1 ? "" : "s"}):\n`);
209
+ for (const d of result.diff) process.stdout.write(` ${d.op} ${d.path}\n`);
210
+ });
211
+ }
212
+ //#endregion
30
213
  //#region src/commands/agent.ts
31
214
  const DEFAULT_WORKSPACE_TTL_MS = 10080 * 60 * 1e3;
32
- function resolveAgentConfig() {
33
- let token;
34
- try {
35
- token = resolveAgentToken();
36
- } catch (error) {
37
- fail("MISSING_TOKEN", error instanceof Error ? error.message : String(error), 2);
38
- }
215
+ /**
216
+ * Resolve the agent this CLI invocation should act on. We read the local
217
+ * `agents/<name>/agent.yaml` file to find the agentId, then pair it with the
218
+ * user's current member JWT (refreshed on demand) at call time.
219
+ *
220
+ * Only one agent is expected per command invocation — if the user has many
221
+ * agents configured they must pick one with `--agent <name>` (next step of
222
+ * CLI polish) or rely on a single entry.
223
+ */
224
+ function resolveLocalAgent(agentName) {
225
+ const agentsDir = join(DEFAULT_CONFIG_DIR, "agents");
226
+ const agents = loadAgents({
227
+ schema: agentConfigSchema,
228
+ agentsDir
229
+ });
230
+ if (agents.size === 0) fail("MISSING_AGENT", "No agent configured. Run `first-tree-hub agent add` first.", 2);
231
+ let resolvedName;
232
+ if (agentName) resolvedName = agentName;
233
+ else if (agents.size === 1) {
234
+ const [only] = [...agents.keys()];
235
+ if (!only) fail("MISSING_AGENT", "No agent configured. Run `first-tree-hub agent add` first.", 2);
236
+ resolvedName = only;
237
+ } else fail("AMBIGUOUS_AGENT", `Multiple agents configured — specify --agent <name>. Available: ${[...agents.keys()].join(", ")}`, 2);
238
+ const cfg = agents.get(resolvedName);
239
+ if (!cfg) fail("UNKNOWN_AGENT", `Agent "${resolvedName}" not found in ${agentsDir}`, 2);
39
240
  let serverUrl;
40
241
  try {
41
242
  serverUrl = resolveServerUrl(process.env.FIRST_TREE_HUB_SERVER_URL);
42
- } catch {
43
- serverUrl = "http://localhost:8000";
243
+ } catch (error) {
244
+ fail("MISSING_SERVER_URL", error instanceof Error ? error.message : String(error), 2);
44
245
  }
45
246
  return {
46
247
  serverUrl,
47
- token
248
+ agentId: cfg.agentId
48
249
  };
49
250
  }
50
- function createSdk() {
51
- return new FirstTreeHubSDK(resolveAgentConfig());
251
+ function createSdk(agentName) {
252
+ const { serverUrl, agentId } = resolveLocalAgent(agentName);
253
+ return new FirstTreeHubSDK({
254
+ serverUrl,
255
+ getAccessToken: () => ensureFreshAccessToken(),
256
+ agentId
257
+ });
52
258
  }
53
259
  function handleSdkError(error) {
54
260
  if (error instanceof SdkError) {
@@ -82,10 +288,6 @@ function readStdin() {
82
288
  process.stdin.on("error", reject);
83
289
  });
84
290
  }
85
- /**
86
- * Resolve an agent name (or UUID) to its record via the admin agents API.
87
- * Accepts either a name or a UUID; throws via fail() if not found.
88
- */
89
291
  async function resolveAgent(serverUrl, adminToken, agentName) {
90
292
  const res = await fetch(`${serverUrl}/api/v1/admin/agents?limit=100`, {
91
293
  headers: { Authorization: `Bearer ${adminToken}` },
@@ -97,22 +299,24 @@ async function resolveAgent(serverUrl, adminToken, agentName) {
97
299
  return found;
98
300
  }
99
301
  function registerAgentCommands(program) {
100
- const agent = program.command("agent").description("Agent management — config, tokens, bindings, messaging");
101
- agent.command("add [name]").description("Add an agent instance").option("-t, --token <token>", "Agent token").action(async (name, options) => {
302
+ const agent = program.command("agent").description("Agent management — config, bindings, messaging");
303
+ registerAgentConfigCommands(agent);
304
+ agent.command("add [name]").description("Register a local alias for an existing Hub agent (stores agentId)").option("--agent-id <id>", "Agent UUID on the Hub").action(async (name, options) => {
102
305
  try {
103
306
  let agentName = name;
104
- let agentToken = options?.token;
105
- if (!agentName || !agentToken) {
307
+ let agentId = options?.agentId;
308
+ if (!agentName || !agentId) {
106
309
  const result = await promptAddAgent();
107
310
  agentName = agentName ?? result.name;
108
- agentToken = agentToken ?? result.token;
311
+ agentId = agentId ?? result.agentId;
109
312
  }
313
+ if (!agentName || !agentId) fail("MISSING_AGENT_ARGS", "Both agent name and agent-id are required.", 2);
110
314
  const agentDir = join(DEFAULT_CONFIG_DIR, "agents", agentName);
111
315
  mkdirSync(agentDir, {
112
316
  recursive: true,
113
317
  mode: 448
114
318
  });
115
- setConfigValue(join(agentDir, "agent.yaml"), "token", agentToken);
319
+ setConfigValue(join(agentDir, "agent.yaml"), "agentId", agentId);
116
320
  process.stderr.write(` Agent "${agentName}" added.\n`);
117
321
  process.stderr.write(` Config: ${join(agentDir, "agent.yaml")}\n`);
118
322
  } catch (error) {
@@ -125,7 +329,7 @@ function registerAgentCommands(program) {
125
329
  process.exit(1);
126
330
  }
127
331
  });
128
- agent.command("remove <name>").description("Remove an agent instance and its runtime data").action((name) => {
332
+ agent.command("remove <name>").description("Remove a local agent alias and its runtime data").action((name) => {
129
333
  const agentDir = join(DEFAULT_CONFIG_DIR, "agents", name);
130
334
  if (!existsSync(agentDir)) {
131
335
  process.stderr.write(` Agent "${name}" not found.\n`);
@@ -142,7 +346,7 @@ function registerAgentCommands(program) {
142
346
  rmSync(join(DEFAULT_DATA_DIR, "sessions", `${name}.json`), { force: true });
143
347
  process.stderr.write(` Agent "${name}" removed.\n`);
144
348
  });
145
- agent.command("list").description("List configured agents").action(() => {
349
+ agent.command("list").description("List locally-configured agents").action(() => {
146
350
  const agentsDir = join(DEFAULT_CONFIG_DIR, "agents");
147
351
  try {
148
352
  const agents = loadAgents({
@@ -153,21 +357,22 @@ function registerAgentCommands(program) {
153
357
  process.stderr.write(" No agents configured.\n");
154
358
  return;
155
359
  }
156
- for (const [name, config] of agents) process.stderr.write(` ${name.padEnd(20)} runtime: ${config.runtime.padEnd(14)} token: ${maskToken(config.token)}\n`);
360
+ for (const [name, config] of agents) process.stderr.write(` ${name.padEnd(20)} runtime: ${config.runtime.padEnd(14)} agentId: ${config.agentId}\n`);
157
361
  } catch {
158
362
  process.stderr.write(" No agents configured.\n");
159
363
  }
160
364
  });
161
- agent.command("create <name>").description("Create an agent on Hub and bind it locally (CLI-first)").requiredOption("--type <type>", "Agent type (human, personal_assistant, autonomous_agent)").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) => {
365
+ 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 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) => {
162
366
  try {
163
367
  const serverUrl = resolveServerUrl(options.server);
164
368
  const headers = {
165
- Authorization: `Bearer ${await ensureFreshAdminToken()}`,
369
+ Authorization: `Bearer ${await ensureFreshAccessToken()}`,
166
370
  "Content-Type": "application/json"
167
371
  };
168
372
  const createBody = {
169
373
  name,
170
- type: options.type
374
+ type: options.type,
375
+ clientId: options.clientId
171
376
  };
172
377
  if (options.displayName) createBody.displayName = options.displayName;
173
378
  const createRes = await fetch(`${serverUrl}/api/v1/admin/agents`, {
@@ -177,20 +382,39 @@ function registerAgentCommands(program) {
177
382
  signal: AbortSignal.timeout(1e4)
178
383
  });
179
384
  if (!createRes.ok) fail("CREATE_ERROR", (await createRes.json().catch(() => ({}))).error ?? `Failed to create agent (HTTP ${createRes.status})`, 1);
180
- const agent = await createRes.json();
181
- process.stderr.write(` \u2713 Agent created: ${agent.name ?? agent.uuid}\n`);
182
- const tokenRes = await fetch(`${serverUrl}/api/v1/admin/agents/${agent.uuid}/tokens`, {
183
- method: "POST",
184
- headers,
185
- body: JSON.stringify({ name: "default" }),
385
+ const created = await createRes.json();
386
+ process.stderr.write(` \u2713 Agent created: ${created.name ?? created.uuid}\n`);
387
+ const agentDir = saveAgentConfig(name, created.uuid, options.runtime);
388
+ process.stderr.write(` \u2713 Config saved: ${agentDir}/agent.yaml\n`);
389
+ process.stderr.write(" ✓ Agent ready — start the client on that machine to bind\n");
390
+ } catch (error) {
391
+ fail("CREATE_ERROR", error instanceof Error ? error.message : String(error));
392
+ }
393
+ });
394
+ agent.command("claim <agentName>").description("Become the manager of an agent (admin-only, or self-claim an unmanaged agent)").option("--server <url>", "Hub server URL").action(async (agentName, options) => {
395
+ try {
396
+ const serverUrl = resolveServerUrl(options.server);
397
+ const accessToken = await ensureFreshAccessToken();
398
+ const meRes = await fetch(`${serverUrl}/api/v1/me`, {
399
+ headers: { Authorization: `Bearer ${accessToken}` },
186
400
  signal: AbortSignal.timeout(1e4)
187
401
  });
188
- if (!tokenRes.ok) fail("TOKEN_ERROR", `Failed to generate token (HTTP ${tokenRes.status})`, 1);
189
- const agentDir = saveAgentConfig(name, (await tokenRes.json()).token, options.runtime);
190
- process.stderr.write(` \u2713 Token saved: ${agentDir}/agent.yaml\n`);
191
- process.stderr.write(` \u2713 Agent ready running client will auto-bind\n`);
402
+ if (!meRes.ok) fail("ME_ERROR", `Failed to fetch current member (HTTP ${meRes.status})`, 1);
403
+ const me = await meRes.json();
404
+ const target = await resolveAgent(serverUrl, accessToken, agentName);
405
+ const patchRes = await fetch(`${serverUrl}/api/v1/admin/agents/${target.uuid}`, {
406
+ method: "PATCH",
407
+ headers: {
408
+ Authorization: `Bearer ${accessToken}`,
409
+ "Content-Type": "application/json"
410
+ },
411
+ body: JSON.stringify({ managerId: me.memberId }),
412
+ signal: AbortSignal.timeout(1e4)
413
+ });
414
+ if (!patchRes.ok) fail("CLAIM_ERROR", (await patchRes.json().catch(() => ({}))).error ?? `Claim failed (HTTP ${patchRes.status})`, 1);
415
+ process.stderr.write(` Claimed "${target.name ?? target.uuid}" — now managed by you.\n`);
192
416
  } catch (error) {
193
- fail("CREATE_ERROR", error instanceof Error ? error.message : String(error));
417
+ fail("CLAIM_ERROR", error instanceof Error ? error.message : String(error));
194
418
  }
195
419
  });
196
420
  agent.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) => {
@@ -215,24 +439,13 @@ function registerAgentCommands(program) {
215
439
  }
216
440
  process.stderr.write(` ${totalRemoved} workspace(s) cleaned.\n`);
217
441
  });
218
- agent.command("token").description("Agent token management").command("bootstrap <agentName>").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 (agentName, options) => {
219
- try {
220
- const result = await bootstrapToken(resolveServerUrl(options.server), agentName, { saveTo: options.saveTo });
221
- if (options.saveTo === "agent") process.stderr.write(`Token saved to ${DEFAULT_HOME_DIR}/config/agents/${agentName}/agent.yaml\n`);
222
- else process.stderr.write(`Token saved to ${options.saveTo}\n`);
223
- success({
224
- agentId: result.agentId,
225
- tokenSaved: true
226
- });
227
- } catch (error) {
228
- fail("BOOTSTRAP_ERROR", error instanceof Error ? error.message : String(error));
229
- }
230
- });
231
442
  const bind = agent.command("bind").description("Bind external IM accounts to agents");
232
- bind.command("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) => {
443
+ bind.command("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("--agent <name>", "Local agent alias (default: first configured)").option("--server <url>", "Hub server URL").action(async (options) => {
233
444
  try {
234
445
  if (options.platform !== "feishu") fail("UNSUPPORTED_PLATFORM", `Platform "${options.platform}" is not supported. Use "feishu".`);
235
- await bindFeishuBot(resolveServerUrl(options.server), resolveAgentToken(), options.appId, options.appSecret);
446
+ const serverUrl = resolveServerUrl(options.server);
447
+ const { agentId } = resolveLocalAgent(options.agent);
448
+ await bindFeishuBot(serverUrl, await ensureFreshAccessToken(), agentId, options.appId, options.appSecret);
236
449
  process.stderr.write("Feishu bot bound successfully.\n");
237
450
  success({
238
451
  platform: "feishu",
@@ -242,10 +455,12 @@ function registerAgentCommands(program) {
242
455
  fail("BIND_BOT_ERROR", error instanceof Error ? error.message : String(error));
243
456
  }
244
457
  });
245
- bind.command("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) => {
458
+ bind.command("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("--agent <name>", "Local agent alias (default: first configured)").option("--server <url>", "Hub server URL").action(async (humanAgentId, options) => {
246
459
  try {
247
460
  if (options.platform !== "feishu") fail("UNSUPPORTED_PLATFORM", `Platform "${options.platform}" is not supported. Use "feishu".`);
248
- await bindFeishuUser(resolveServerUrl(options.server), resolveAgentToken(), humanAgentId, options.feishuId);
461
+ const serverUrl = resolveServerUrl(options.server);
462
+ const { agentId } = resolveLocalAgent(options.agent);
463
+ await bindFeishuUser(serverUrl, await ensureFreshAccessToken(), agentId, humanAgentId, options.feishuId);
249
464
  process.stderr.write(`Feishu user ${options.feishuId} bound to ${humanAgentId}.\n`);
250
465
  success({
251
466
  platform: "feishu",
@@ -256,7 +471,7 @@ function registerAgentCommands(program) {
256
471
  fail("BIND_USER_ERROR", error instanceof Error ? error.message : String(error));
257
472
  }
258
473
  });
259
- agent.command("send <target> [message]").description("Send a message to an agent or chat").option("-f, --format <format>", "Message format (text|markdown|card)", "text").option("--chat", "Treat target as chat ID instead of agent ID").option("-m, --metadata <json>", "JSON metadata to attach").option("--reply-to <messageId>", "Message ID to reply to").option("--reply-to-inbox <inboxId>", "Cross-chat reply target inbox").option("--reply-to-chat <chatId>", "Cross-chat reply target chat").action(async (target, message, options) => {
474
+ agent.command("send <target> [message]").description("Send a message to an agent or chat").option("-f, --format <format>", "Message format (text|markdown|card)", "text").option("--chat", "Treat target as chat ID instead of agent ID").option("-m, --metadata <json>", "JSON metadata to attach").option("--reply-to <messageId>", "Message ID to reply to").option("--reply-to-inbox <inboxId>", "Cross-chat reply target inbox").option("--reply-to-chat <chatId>", "Cross-chat reply target chat").option("--agent <name>", "Local agent alias (default: first configured)").action(async (target, message, options) => {
260
475
  try {
261
476
  const content = message ?? await readStdin();
262
477
  if (!content) fail("NO_MESSAGE", "No message provided. Pass as argument or pipe via stdin.", 2);
@@ -266,7 +481,7 @@ function registerAgentCommands(program) {
266
481
  } catch {
267
482
  fail("INVALID_METADATA", "Metadata must be valid JSON.", 2);
268
483
  }
269
- const sdk = createSdk();
484
+ const sdk = createSdk(options.agent);
270
485
  if (options.chat) success(await sdk.sendMessage(target, {
271
486
  format: options.format,
272
487
  content,
@@ -286,10 +501,10 @@ function registerAgentCommands(program) {
286
501
  handleSdkError(error);
287
502
  }
288
503
  });
289
- agent.command("chats").description("List chats this agent participates in").option("-l, --limit <number>", "Maximum chats to return (1-100)", "20").option("--cursor <cursor>", "Pagination cursor from previous response").action(async (options) => {
504
+ agent.command("chats").description("List chats this agent participates in").option("-l, --limit <number>", "Maximum chats to return (1-100)", "20").option("--cursor <cursor>", "Pagination cursor from previous response").option("--agent <name>", "Local agent alias (default: first configured)").action(async (options) => {
290
505
  try {
291
506
  const limit = parseLimit(options.limit, 100);
292
- success(await createSdk().listChats({
507
+ success(await createSdk(options.agent).listChats({
293
508
  limit,
294
509
  cursor: options.cursor
295
510
  }));
@@ -297,10 +512,10 @@ function registerAgentCommands(program) {
297
512
  handleSdkError(error);
298
513
  }
299
514
  });
300
- agent.command("history <chatId>").description("View message history in a chat").option("-l, --limit <number>", "Maximum messages to return (1-100)", "20").option("--cursor <cursor>", "Pagination cursor from previous response").action(async (chatId, options) => {
515
+ agent.command("history <chatId>").description("View message history in a chat").option("-l, --limit <number>", "Maximum messages to return (1-100)", "20").option("--cursor <cursor>", "Pagination cursor from previous response").option("--agent <name>", "Local agent alias (default: first configured)").action(async (chatId, options) => {
301
516
  try {
302
517
  const limit = parseLimit(options.limit, 100);
303
- success(await createSdk().listMessages(chatId, {
518
+ success(await createSdk(options.agent).listMessages(chatId, {
304
519
  limit,
305
520
  cursor: options.cursor
306
521
  }));
@@ -312,22 +527,22 @@ function registerAgentCommands(program) {
312
527
  try {
313
528
  const serverUrl = resolveServerUrl(options?.server);
314
529
  const response = await fetch(`${serverUrl}/api/v1/admin/agents/activity`, {
315
- headers: { Authorization: `Bearer ${await ensureFreshAdminToken()}` },
530
+ headers: { Authorization: `Bearer ${await ensureFreshAccessToken()}` },
316
531
  signal: AbortSignal.timeout(1e4)
317
532
  });
318
533
  if (!response.ok) fail("FETCH_ERROR", `Server returned ${response.status}`, 1);
319
534
  const data = await response.json();
320
535
  if (name) {
321
- const agent = data.agents.find((a) => a.agentId === name);
322
- if (!agent) {
536
+ const ag = data.agents.find((a) => a.agentId === name);
537
+ if (!ag) {
323
538
  process.stderr.write(`\n Agent "${name}" is not running.\n\n`);
324
539
  return;
325
540
  }
326
- process.stderr.write(`\n Agent: ${agent.agentId}\n`);
327
- process.stderr.write(` Runtime: ${agent.runtimeType ?? "—"}\n`);
328
- process.stderr.write(` State: ${agent.runtimeState ?? "—"}\n`);
329
- if (agent.activeSessions !== null) process.stderr.write(` Sessions: ${agent.activeSessions} active / ${agent.totalSessions ?? 0} total\n`);
330
- if (agent.clientId) process.stderr.write(` Client: ${agent.clientId}\n`);
541
+ process.stderr.write(`\n Agent: ${ag.agentId}\n`);
542
+ process.stderr.write(` Runtime: ${ag.runtimeType ?? "—"}\n`);
543
+ process.stderr.write(` State: ${ag.runtimeState ?? "—"}\n`);
544
+ if (ag.activeSessions !== null) process.stderr.write(` Sessions: ${ag.activeSessions} active / ${ag.totalSessions ?? 0} total\n`);
545
+ if (ag.clientId) process.stderr.write(` Client: ${ag.clientId}\n`);
331
546
  process.stderr.write("\n");
332
547
  return;
333
548
  }
@@ -346,17 +561,6 @@ function registerAgentCommands(program) {
346
561
  process.stderr.write("\n");
347
562
  }
348
563
  } catch (error) {
349
- if (error instanceof Error && error.message.includes("ADMIN_TOKEN")) {
350
- try {
351
- const me = await createSdk().register();
352
- process.stderr.write(`\n Agent: ${me.agentId} (${me.displayName ?? "no name"})\n`);
353
- process.stderr.write(` Type: ${me.type}\n`);
354
- process.stderr.write(` Status: ${me.status}\n\n`);
355
- } catch (sdkError) {
356
- handleSdkError(sdkError);
357
- }
358
- return;
359
- }
360
564
  fail("STATUS_ERROR", error instanceof Error ? error.message : String(error));
361
565
  }
362
566
  });
@@ -365,7 +569,7 @@ function registerAgentCommands(program) {
365
569
  const serverUrl = resolveServerUrl(options.server);
366
570
  const response = await fetch(`${serverUrl}/api/v1/admin/agents/activity/${name}/reset-activity`, {
367
571
  method: "POST",
368
- headers: { Authorization: `Bearer ${await ensureFreshAdminToken()}` },
572
+ headers: { Authorization: `Bearer ${await ensureFreshAccessToken()}` },
369
573
  signal: AbortSignal.timeout(1e4)
370
574
  });
371
575
  if (!response.ok) fail("RESET_ERROR", `Server returned ${response.status}`, 1);
@@ -377,7 +581,7 @@ function registerAgentCommands(program) {
377
581
  agent.command("sessions <agent-name>").description("List sessions for an agent").option("--server <url>", "Hub server URL").option("--state <state>", "Filter by session state (active/suspended/evicted)").action(async (agentName, options) => {
378
582
  try {
379
583
  const serverUrl = resolveServerUrl(options.server);
380
- const adminToken = await ensureFreshAdminToken();
584
+ const adminToken = await ensureFreshAccessToken();
381
585
  const agentId = (await resolveAgent(serverUrl, adminToken, agentName)).uuid;
382
586
  const qs = options.state ? `?state=${options.state}` : "";
383
587
  const response = await fetch(`${serverUrl}/api/v1/admin/sessions/agents/${agentId}${qs}`, {
@@ -411,7 +615,7 @@ function registerAgentCommands(program) {
411
615
  ]) sessionCmd.command(`${cmd} <agent-name> <chat-id>`).description(desc).option("--server <url>", "Hub server URL").action(async (agentName, chatId, options) => {
412
616
  try {
413
617
  const serverUrl = resolveServerUrl(options.server);
414
- const adminToken = await ensureFreshAdminToken();
618
+ const adminToken = await ensureFreshAccessToken();
415
619
  const agentId = (await resolveAgent(serverUrl, adminToken, agentName)).uuid;
416
620
  const response = await fetch(`${serverUrl}/api/v1/admin/sessions/agents/${agentId}/${chatId}/${cmd}`, {
417
621
  method: "POST",
@@ -427,10 +631,10 @@ function registerAgentCommands(program) {
427
631
  fail("SESSION_CMD_ERROR", error instanceof Error ? error.message : String(error));
428
632
  }
429
633
  });
430
- agent.command("chat <agent-name>").description("Open an interactive chat with an agent (as admin human agent)").option("--server <url>", "Hub server URL").action(async (agentName, options) => {
634
+ agent.command("chat <agent-name>").description("Open an interactive chat with an agent (as the current member's human agent)").option("--server <url>", "Hub server URL").action(async (agentName, options) => {
431
635
  try {
432
636
  const serverUrl = resolveServerUrl(options.server);
433
- const adminToken = await ensureFreshAdminToken();
637
+ const adminToken = await ensureFreshAccessToken();
434
638
  const headers = {
435
639
  Authorization: `Bearer ${adminToken}`,
436
640
  "Content-Type": "application/json"
@@ -516,16 +720,16 @@ function registerAgentCommands(program) {
516
720
  fail("CHAT_ERROR", error instanceof Error ? error.message : String(error));
517
721
  }
518
722
  });
519
- agent.command("register").description("Register this agent and return identity info").action(async () => {
723
+ agent.command("register").description("Register this agent and return identity info").option("--agent <name>", "Local agent alias (default: first configured)").action(async (options) => {
520
724
  try {
521
- success(await createSdk().register());
725
+ success(await createSdk(options.agent).register());
522
726
  } catch (error) {
523
727
  handleSdkError(error);
524
728
  }
525
729
  });
526
- agent.command("pull").description("Pull pending messages from inbox").option("-l, --limit <number>", "Maximum entries to return", "10").option("-a, --ack", "Automatically ACK entries after pulling").action(async (options) => {
730
+ agent.command("pull").description("Pull pending messages from inbox").option("-l, --limit <number>", "Maximum entries to return", "10").option("-a, --ack", "Automatically ACK entries after pulling").option("--agent <name>", "Local agent alias (default: first configured)").action(async (options) => {
527
731
  try {
528
- const sdk = createSdk();
732
+ const sdk = createSdk(options.agent);
529
733
  const limit = parseLimit(options.limit, 50);
530
734
  const result = await sdk.pull(limit);
531
735
  if (options.ack && result.entries.length > 0) await Promise.all(result.entries.map((entry) => sdk.ack(entry.id)));
@@ -585,7 +789,6 @@ function registerClientCommands(program) {
585
789
  checkClientConfig(),
586
790
  await checkServerReachable(),
587
791
  checkAgentConfigs(),
588
- await checkAgentTokens(),
589
792
  await checkWebSocket()
590
793
  ]);
591
794
  });
@@ -605,7 +808,7 @@ function registerClientCommands(program) {
605
808
  return;
606
809
  }
607
810
  process.stderr.write("\n Configured agents:\n\n");
608
- for (const [name, config] of agents) process.stderr.write(` ${name.padEnd(20)} runtime: ${config.runtime.padEnd(14)} token: ${maskToken(config.token)}\n`);
811
+ for (const [name, config] of agents) process.stderr.write(` ${name.padEnd(20)} runtime: ${config.runtime.padEnd(14)} agentId: ${config.agentId}\n`);
609
812
  process.stderr.write("\n");
610
813
  } catch {
611
814
  process.stderr.write(" No agents directory found.\n");
@@ -614,7 +817,7 @@ function registerClientCommands(program) {
614
817
  client.command("hub-list").description("List connected clients on the Hub server").option("--server <url>", "Hub server URL").action(async (options) => {
615
818
  try {
616
819
  const serverUrl = resolveServerUrl(options.server);
617
- const token = await ensureFreshAdminToken();
820
+ const token = await ensureFreshAccessToken();
618
821
  const response = await fetch(`${serverUrl}/api/v1/admin/clients`, {
619
822
  headers: { Authorization: `Bearer ${token}` },
620
823
  signal: AbortSignal.timeout(1e4)
@@ -641,7 +844,7 @@ function registerClientCommands(program) {
641
844
  client.command("hub-disconnect <clientId>").description("Force-disconnect a client from the Hub server").option("--server <url>", "Hub server URL").action(async (clientId, options) => {
642
845
  try {
643
846
  const serverUrl = resolveServerUrl(options.server);
644
- const token = await ensureFreshAdminToken();
847
+ const token = await ensureFreshAccessToken();
645
848
  const response = await fetch(`${serverUrl}/api/v1/admin/clients/${clientId}/disconnect`, {
646
849
  method: "POST",
647
850
  headers: { Authorization: `Bearer ${token}` },
@@ -845,32 +1048,16 @@ function registerConnectCommand(program) {
845
1048
  //#region src/commands/onboard.ts
846
1049
  async function promptMissing(args) {
847
1050
  if (!args.server) try {
848
- const { resolveServerUrl } = await import("../bootstrap-Dq_k_6ZD.mjs").then((n) => n.n);
1051
+ const { resolveServerUrl } = await import("../bootstrap-DW7aIpmE.mjs").then((n) => n.t);
849
1052
  resolveServerUrl();
850
1053
  } catch {
851
1054
  args.server = await input({ message: "Hub server URL:" });
852
1055
  saveOnboardState(args);
853
1056
  }
854
- const { resolveServerUrl } = await import("../bootstrap-Dq_k_6ZD.mjs").then((n) => n.n);
855
- const serverUrl = resolveServerUrl(args.server).replace(/\/+$/, "");
856
- try {
857
- const res = await fetch(`${serverUrl}/api/v1/bootstrap/config`);
858
- if (res.ok) {
859
- 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.");
860
- }
861
- } catch (err) {
862
- if (err instanceof Error && err.message.includes("FIRST_TREE_HUB_GITHUB_ALLOWED_ORG")) throw err;
863
- }
864
- let ghUsername = null;
865
- try {
866
- const { getGitHubUsername } = await import("../bootstrap-Dq_k_6ZD.mjs").then((n) => n.n);
867
- ghUsername = getGitHubUsername();
868
- } catch {}
1057
+ const { loadCredentials } = await import("../bootstrap-DW7aIpmE.mjs").then((n) => n.t);
1058
+ if (!loadCredentials()) throw new Error("No saved credentials. Run `first-tree-hub connect <server-url>` before onboarding.");
869
1059
  if (!args.id) {
870
- args.id = await input({
871
- message: "Agent ID:",
872
- default: ghUsername ?? void 0
873
- });
1060
+ args.id = await input({ message: "Agent ID:" });
874
1061
  saveOnboardState(args);
875
1062
  }
876
1063
  if (!args.type) {
@@ -893,6 +1080,13 @@ async function promptMissing(args) {
893
1080
  });
894
1081
  saveOnboardState(args);
895
1082
  }
1083
+ if (args.type !== "human" && !args.clientId) {
1084
+ args.clientId = await input({
1085
+ message: "Client ID (machine that will run this agent — must be owned by you):",
1086
+ validate: (v) => v.length > 0 ? true : "clientId is required for non-human agents"
1087
+ });
1088
+ saveOnboardState(args);
1089
+ }
896
1090
  if (!args.role) {
897
1091
  const role = await input({ message: "Role (optional, Enter to skip):" });
898
1092
  if (role) {
@@ -923,6 +1117,10 @@ async function promptMissing(args) {
923
1117
  message: "Assistant ID:",
924
1118
  default: `${args.id}-assistant`
925
1119
  });
1120
+ if (!args.clientId) args.clientId = await input({
1121
+ message: "Client ID for the assistant (must be owned by you):",
1122
+ validate: (v) => v.length > 0 ? true : "clientId is required"
1123
+ });
926
1124
  saveOnboardState(args);
927
1125
  }
928
1126
  }
@@ -938,16 +1136,16 @@ async function promptMissing(args) {
938
1136
  }
939
1137
  }
940
1138
  function registerOnboardCommand(program) {
941
- 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) => {
1139
+ 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("--client-id <id>", "Client (machine) to pin a non-human agent to").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").action(async (options) => {
942
1140
  try {
943
1141
  const args = {
944
1142
  ...loadOnboardState() ?? {},
945
1143
  ...options.id && { id: options.id },
946
1144
  ...options.type && { type: options.type },
1145
+ ...options.clientId && { clientId: options.clientId },
947
1146
  ...options.displayName && { displayName: options.displayName },
948
1147
  ...options.role && { role: options.role },
949
1148
  ...options.domains && { domains: options.domains },
950
- ...options.profile && { profile: options.profile },
951
1149
  ...options.assistant && { assistant: options.assistant },
952
1150
  ...options.delegateMention && { delegateMention: options.delegateMention },
953
1151
  ...options.server && { server: options.server },