@agent-team-foundation/first-tree-hub 0.9.9 → 0.9.11

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,15 +1,14 @@
1
1
  import { m as __toESM } from "./esm-CYu4tXXn.mjs";
2
- import { _ as withSpan, a as endWsConnectionSpan, b as require_pino, c as messageAttrs, d as rootLogger$1, g as startWsConnectionSpan, i as currentTraceId, n as applyLoggerConfig, o as getFastifyOtelPlugin, p as setWsConnectionAttrs, r as createLogger$1, t as adapterAttrs, u as observabilityPlugin, v as withWsMessageSpan, y as FIRST_TREE_HUB_ATTR } from "./observability-DV_fQKqV-CuLWzBxQ.mjs";
3
- import { s as formatPrettyEntry$1, t as LOG_LEVELS$1, u as parseLogLevel$1 } from "./logger-core-BTmvdflj-DhdipBkV.mjs";
4
- import { C as serverConfigSchema, S as resolveConfigReadonly, _ as loadAgents, d as DEFAULT_HOME_DIR$1, f as agentConfigSchema, g as initConfig, i as loadCredentials, l as DEFAULT_CONFIG_DIR, m as collectMissingPrompts, n as ensureFreshAccessToken, o as resolveServerUrl, p as clientConfigSchema, s as saveAgentConfig, u as DEFAULT_DATA_DIR$1, v as migrateLegacyHome, w as setConfigValue } from "./bootstrap-hh_PkTu6.mjs";
5
- import { $ as sessionStateMessageSchema, A as createMemberSchema, B as loginSchema, C as agentTypeSchema$1, D as createAdapterMappingSchema, E as createAdapterConfigSchema, F as extractMentions, G as runtimeStateMessageSchema, H as notificationQuerySchema, I as imageInlineContentSchema, J as sendToAgentSchema, K as selfServiceFeishuBotSchema, L as inboxPollQuerySchema, M as createTaskSchema, N as delegateFeishuUserSchema, O as createAgentSchema, P as dryRunAgentRuntimeConfigSchema, Q as sessionReconcileRequestSchema, R as isRedactedEnvValue, S as agentRuntimeConfigPayloadSchema$1, T as connectTokenExchangeSchema, U as paginationQuerySchema, V as messageSourceSchema$1, W as refreshTokenSchema, X as sessionEventMessageSchema, Y as sessionCompletionMessageSchema, Z as sessionEventSchema$1, _ as addParticipantSchema, a as AGENT_SELECTOR_HEADER$1, at as updateMemberSchema, b as agentBindRequestSchema, c as AGENT_TYPES, ct as updateTaskStatusSchema, d as SYSTEM_CONFIG_DEFAULTS, et as taskListQuerySchema, f as TASK_CREATOR_TYPES, g as WS_AUTH_FRAME_TIMEOUT_MS, h as TASK_TERMINAL_STATUSES, i as AGENT_BIND_REJECT_REASONS, it as updateChatSchema, j as createOrganizationSchema, k as createChatSchema, l as AGENT_VISIBILITY, lt as wsAuthFrameSchema, m as TASK_STATUSES, nt as updateAgentRuntimeConfigSchema, o as AGENT_SOURCES, ot as updateOrganizationSchema, p as TASK_HEALTH_SIGNALS, q as sendMessageSchema, rt as updateAgentSchema, s as AGENT_STATUSES, st as updateSystemConfigSchema, tt as updateAdapterConfigSchema, u as DEFAULT_AGENT_RUNTIME_CONFIG_PAYLOAD, v as adminCreateTaskSchema, w as clientRegisterSchema, x as agentPinnedMessageSchema$1, y as adminUpdateTaskSchema, z as linkTaskChatSchema } from "./feishu-B1Kiq7S6.mjs";
2
+ import { _ as withSpan, a as endWsConnectionSpan, b as require_pino, c as messageAttrs, d as rootLogger$1, g as startWsConnectionSpan, i as currentTraceId, n as applyLoggerConfig, o as getFastifyOtelPlugin, p as setWsConnectionAttrs, r as createLogger$1, t as adapterAttrs, u as observabilityPlugin, v as withWsMessageSpan, y as FIRST_TREE_HUB_ATTR } from "./observability-DV_fQKqV-oxfXX6Z2.mjs";
3
+ import { C as serverConfigSchema, S as resolveConfigReadonly, _ as loadAgents, d as DEFAULT_HOME_DIR$1, f as agentConfigSchema, g as initConfig, i as loadCredentials, l as DEFAULT_CONFIG_DIR, m as collectMissingPrompts, n as ensureFreshAccessToken, o as resolveServerUrl, p as clientConfigSchema, s as saveAgentConfig, u as DEFAULT_DATA_DIR$1, v as migrateLegacyHome, w as setConfigValue } from "./bootstrap-CtVqQA8a.mjs";
4
+ import { $ as sessionEventSchema$1, A as createChatSchema, B as isReservedAgentName$1, C as agentRuntimeConfigPayloadSchema$1, D as createAdapterConfigSchema, E as connectTokenExchangeSchema, F as dryRunAgentRuntimeConfigSchema, G as paginationQuerySchema, H as loginSchema, I as extractMentions, J as selfServiceFeishuBotSchema, K as refreshTokenSchema, L as imageInlineContentSchema, M as createOrganizationSchema, N as createTaskSchema, O as createAdapterMappingSchema, P as delegateFeishuUserSchema, Q as sessionEventMessageSchema, R as inboxPollQuerySchema, S as agentPinnedMessageSchema$1, T as clientRegisterSchema, U as messageSourceSchema$1, V as linkTaskChatSchema, W as notificationQuerySchema, X as sendToAgentSchema, Y as sendMessageSchema, Z as sessionCompletionMessageSchema, _ as WS_AUTH_FRAME_TIMEOUT_MS, a as AGENT_NAME_REGEX$1, at as updateAgentSchema, b as adminUpdateTaskSchema, c as AGENT_STATUSES, ct as updateOrganizationSchema, d as DEFAULT_AGENT_RUNTIME_CONFIG_PAYLOAD, dt as wsAuthFrameSchema, et as sessionReconcileRequestSchema, f as SYSTEM_CONFIG_DEFAULTS, g as TASK_TERMINAL_STATUSES, h as TASK_STATUSES, i as AGENT_BIND_REJECT_REASONS, it as updateAgentRuntimeConfigSchema, j as createMemberSchema, k as createAgentSchema, l as AGENT_TYPES, lt as updateSystemConfigSchema, m as TASK_HEALTH_SIGNALS, nt as taskListQuerySchema, o as AGENT_SELECTOR_HEADER$1, ot as updateChatSchema, p as TASK_CREATOR_TYPES, q as runtimeStateMessageSchema, rt as updateAdapterConfigSchema, s as AGENT_SOURCES, st as updateMemberSchema, tt as sessionStateMessageSchema, u as AGENT_VISIBILITY, ut as updateTaskStatusSchema, v as addParticipantSchema, w as agentTypeSchema$1, x as agentBindRequestSchema, y as adminCreateTaskSchema, z as isRedactedEnvValue } from "./feishu-B2sjp6Z6.mjs";
6
5
  import { createRequire } from "node:module";
7
6
  import { ZodError, z } from "zod";
8
7
  import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
9
8
  import { Writable } from "node:stream";
10
9
  import { homedir, hostname, platform, tmpdir, userInfo } from "node:os";
11
10
  import { EventEmitter } from "node:events";
12
- import { closeSync, copyFileSync, createReadStream, existsSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, unwatchFile, watch, watchFile, writeFileSync, writeSync } from "node:fs";
11
+ import { closeSync, copyFileSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, watch, writeFileSync, writeSync } from "node:fs";
13
12
  import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
14
13
  import WebSocket from "ws";
15
14
  import { mkdir, writeFile } from "node:fs/promises";
@@ -32,7 +31,6 @@ import Fastify from "fastify";
32
31
  import { bigserial, boolean, index, integer, jsonb, pgTable, primaryKey, serial, text, timestamp, unique, uniqueIndex } from "drizzle-orm/pg-core";
33
32
  import { SignJWT, jwtVerify } from "jose";
34
33
  import { Client, EventDispatcher, LoggerLevel, WSClient } from "@larksuiteoapi/node-sdk";
35
- import { createInterface } from "node:readline";
36
34
  //#region ../client/dist/observability-B4kO005X.mjs
37
35
  var import_pino = /* @__PURE__ */ __toESM(require_pino(), 1);
38
36
  /**
@@ -430,8 +428,31 @@ const agentTypeSchema = z.enum([
430
428
  const agentVisibilitySchema = z.enum(["private", "organization"]);
431
429
  const agentSourceSchema = z.enum(["admin-api", "portal"]);
432
430
  z.enum(["active", "suspended"]);
431
+ /**
432
+ * Agent-name rules (see docs/agent-naming-design.md §3.1):
433
+ * - Lowercase ASCII slug, hyphens + underscores allowed.
434
+ * - Must start with alphanumeric: `-` / `_` as first char collide with
435
+ * CLI flag parsing and markdown list syntax.
436
+ * - 1–64 chars — aligned with `MENTION_REGEX` so any valid name can be
437
+ * @-mentioned in chat. Older rows created under the previous 1–100
438
+ * regex are grandfathered; the tight rule only gates new creates.
439
+ */
440
+ const AGENT_NAME_REGEX = /^[a-z0-9][a-z0-9_-]{0,63}$/;
441
+ const RESERVED_AGENT_NAMES_SET = new Set([
442
+ "admin",
443
+ "agent",
444
+ "first-tree",
445
+ "hub",
446
+ "me",
447
+ "null",
448
+ "system",
449
+ "undefined"
450
+ ]);
451
+ function isReservedAgentName(name) {
452
+ return RESERVED_AGENT_NAMES_SET.has(name);
453
+ }
433
454
  z.object({
434
- name: z.string().min(1).max(100).regex(/^[a-z0-9_-]+$/, "Only lowercase alphanumeric, hyphens, and underscores").optional(),
455
+ name: z.string().min(1).max(64).regex(AGENT_NAME_REGEX, "Must start with a letter or digit and contain only lowercase letters, digits, hyphens (-), and underscores (_). Max 64 chars.").refine((n) => !isReservedAgentName(n), { message: "That agent name is reserved — pick a different one." }).optional(),
435
456
  type: agentTypeSchema,
436
457
  displayName: z.string().max(200).optional(),
437
458
  delegateMention: z.string().max(100).optional(),
@@ -541,7 +562,7 @@ const gitRepoSchema = z.object({
541
562
  */
542
563
  const agentRuntimeConfigPayloadShape = z.object({
543
564
  prompt: promptConfigSchema.default({ append: "" }),
544
- model: z.string().default(""),
565
+ model: z.string().default("opus"),
545
566
  mcpServers: z.array(mcpServerSchema).default([]),
546
567
  env: z.array(envEntrySchema).default([]),
547
568
  gitRepos: z.array(gitRepoSchema).default([])
@@ -3161,7 +3182,7 @@ const createClaudeCodeHandler = (config) => {
3161
3182
  abortController,
3162
3183
  permissionMode,
3163
3184
  allowDangerouslySkipPermissions: true,
3164
- settingSources: ["project"],
3185
+ settingSources: ["user", "project"],
3165
3186
  env: buildEnv(sessionCtx),
3166
3187
  ...claudeCodeExecutable ? { pathToClaudeCodeExecutable: claudeCodeExecutable } : {},
3167
3188
  ...payload?.model ? { model: payload.model } : {},
@@ -4848,7 +4869,7 @@ async function createOwner(databaseUrl, username, orgName, displayName, password
4848
4869
  `);
4849
4870
  await tx.execute(sql`
4850
4871
  INSERT INTO agent_configs (agent_id, version, payload, updated_by)
4851
- VALUES (${agentId}, 1, ${sql`'{"prompt":{"append":""},"model":"","mcpServers":[],"env":[],"gitRepos":[]}'::jsonb`}, 'system')
4872
+ VALUES (${agentId}, 1, ${sql`'{"prompt":{"append":""},"model":"opus","mcpServers":[],"env":[],"gitRepos":[]}'::jsonb`}, 'system')
4852
4873
  ON CONFLICT (agent_id) DO NOTHING
4853
4874
  `);
4854
4875
  });
@@ -5373,386 +5394,84 @@ function getContainerPassword() {
5373
5394
  throw new Error("Cannot determine PostgreSQL password from container");
5374
5395
  }
5375
5396
  //#endregion
5376
- //#region src/core/doctor.ts
5377
- function getServerConfig() {
5378
- return resolveConfigReadonly({
5379
- schema: serverConfigSchema,
5380
- role: "server"
5381
- });
5382
- }
5383
- function getClientConfig() {
5384
- return resolveConfigReadonly({
5385
- schema: clientConfigSchema,
5386
- role: "client"
5397
+ //#region src/core/service-install.ts
5398
+ /**
5399
+ * Run a subprocess capturing stderr so failures surface a meaningful error
5400
+ * instead of Node's opaque "Command failed". Used for launchctl/systemctl —
5401
+ * anywhere the stderr message is diagnostically crucial.
5402
+ */
5403
+ function runCapture(program, args, timeoutMs) {
5404
+ const res = spawnSync(program, args, {
5405
+ encoding: "utf-8",
5406
+ timeout: timeoutMs,
5407
+ stdio: [
5408
+ "ignore",
5409
+ "pipe",
5410
+ "pipe"
5411
+ ]
5387
5412
  });
5388
- }
5389
- function get(obj, dotPath) {
5390
- const parts = dotPath.split(".");
5391
- let current = obj;
5392
- for (const part of parts) {
5393
- if (current === null || current === void 0 || typeof current !== "object") return void 0;
5394
- current = current[part];
5395
- }
5396
- return current;
5397
- }
5398
- function checkNodeVersion() {
5399
- const version = process.versions.node;
5400
- const [major] = version.split(".").map(Number);
5401
- const ok = major !== void 0 && major >= 22;
5402
- return {
5403
- label: "Node.js",
5404
- ok,
5405
- detail: ok ? `v${version}` : `v${version} (requires >= 22.16)`
5406
- };
5407
- }
5408
- function checkDocker() {
5409
- try {
5410
- return {
5411
- label: "Docker",
5412
- ok: true,
5413
- detail: execFileSync("docker", ["--version"], {
5414
- encoding: "utf-8",
5415
- timeout: 5e3
5416
- }).trim().replace("Docker version ", "v").split(",")[0] ?? ""
5417
- };
5418
- } catch {
5419
- return {
5420
- label: "Docker",
5421
- ok: false,
5422
- detail: "not found (optional — needed for auto PG provisioning)"
5423
- };
5424
- }
5425
- }
5426
- function checkServerConfig() {
5427
- const hasFile = existsSync(join(DEFAULT_CONFIG_DIR, "server.yaml"));
5428
- const hasEnv = !!process.env.FIRST_TREE_HUB_DATABASE_URL;
5429
- if (hasFile && hasEnv) return {
5430
- label: "Config",
5431
- ok: true,
5432
- detail: "config file + env vars"
5433
- };
5434
- if (hasFile) return {
5435
- label: "Config",
5436
- ok: true,
5437
- detail: join(DEFAULT_CONFIG_DIR, "server.yaml")
5438
- };
5439
- if (hasEnv) return {
5440
- label: "Config",
5441
- ok: true,
5442
- detail: "via environment variables"
5443
- };
5413
+ if (res.status === 0) return { ok: true };
5444
5414
  return {
5445
- label: "Config",
5446
5415
  ok: false,
5447
- detail: "no config file or env vars found"
5416
+ stderr: (res.stderr ?? "").trim(),
5417
+ code: res.status
5448
5418
  };
5449
5419
  }
5450
- async function checkDatabase() {
5451
- const dbUrl = get(getServerConfig(), "database.url");
5452
- if (typeof dbUrl !== "string" || !dbUrl) return {
5453
- label: "Database",
5454
- ok: false,
5455
- detail: "not configured (FIRST_TREE_HUB_DATABASE_URL or config file)"
5456
- };
5420
+ function sleepSync(ms) {
5421
+ const shared = new Int32Array(new SharedArrayBuffer(4));
5422
+ Atomics.wait(shared, 0, 0, ms);
5423
+ }
5424
+ const LAUNCHD_LABEL = "dev.first-tree-hub.client";
5425
+ const SYSTEMD_UNIT = "first-tree-hub-client.service";
5426
+ const LOG_DIR = join(DEFAULT_HOME_DIR$1, "logs");
5427
+ function whichBin(name) {
5457
5428
  try {
5458
- const { default: pg } = await import("postgres");
5459
- const sql = pg(dbUrl, {
5460
- max: 1,
5461
- connect_timeout: 5,
5462
- idle_timeout: 1
5463
- });
5464
- await sql.unsafe("SELECT 1");
5465
- await sql.end();
5466
- return {
5467
- label: "Database",
5468
- ok: true,
5469
- detail: "connected"
5470
- };
5471
- } catch (err) {
5472
- return {
5473
- label: "Database",
5474
- ok: false,
5475
- detail: `unreachable — ${(err instanceof Error ? err.message : String(err)).slice(0, 80)}`
5476
- };
5429
+ return execFileSync(process.platform === "win32" ? "where" : "which", [name], {
5430
+ encoding: "utf-8",
5431
+ timeout: 3e3
5432
+ }).split(/\r?\n/).map((s) => s.trim()).filter(Boolean)[0] ?? null;
5433
+ } catch {
5434
+ return null;
5477
5435
  }
5478
5436
  }
5479
- async function checkServerHealth() {
5480
- const config = getServerConfig();
5481
- const host = get(config, "server.host") ?? "127.0.0.1";
5482
- const port = get(config, "server.port") ?? 8e3;
5483
- const url = `http://${host}:${port}/healthz`;
5484
- try {
5485
- const res = await fetch(url, { signal: AbortSignal.timeout(3e3) });
5486
- if (res.ok) return {
5487
- label: "Server Health",
5488
- ok: true,
5489
- detail: `running at ${host}:${port}`
5490
- };
5437
+ /**
5438
+ * Resolve how the service should launch the CLI.
5439
+ *
5440
+ * Prefers the installed `first-tree-hub` bin on PATH (usually a shim under
5441
+ * /usr/local/bin or ~/.npm-global/bin). Falls back to invoking the current
5442
+ * Node interpreter against the running script (handles `pnpm dev`, tsx, and
5443
+ * dev-only global installs).
5444
+ */
5445
+ function resolveCliInvocation() {
5446
+ const bin = whichBin("first-tree-hub");
5447
+ if (bin && isAbsolute(bin)) try {
5491
5448
  return {
5492
- label: "Server Health",
5493
- ok: false,
5494
- detail: `unhealthy (HTTP ${res.status}) at ${host}:${port}`
5449
+ kind: "bin",
5450
+ program: realpathSync(bin)
5495
5451
  };
5496
5452
  } catch {
5497
5453
  return {
5498
- label: "Server Health",
5499
- ok: false,
5500
- detail: `not running at ${host}:${port}`
5454
+ kind: "bin",
5455
+ program: bin
5501
5456
  };
5502
5457
  }
5503
- }
5504
- function checkClientConfig() {
5505
- const hasFile = existsSync(join(DEFAULT_CONFIG_DIR, "client.yaml"));
5506
- const hasEnv = !!process.env.FIRST_TREE_HUB_SERVER_URL;
5507
- if (hasFile && hasEnv) return {
5508
- label: "Config",
5509
- ok: true,
5510
- detail: "config file + env vars"
5511
- };
5512
- if (hasFile) return {
5513
- label: "Config",
5514
- ok: true,
5515
- detail: join(DEFAULT_CONFIG_DIR, "client.yaml")
5516
- };
5517
- if (hasEnv) return {
5518
- label: "Config",
5519
- ok: true,
5520
- detail: "via environment variables"
5521
- };
5458
+ const script = process.argv[1];
5459
+ if (!script) throw new Error("Cannot resolve CLI entry point (process.argv[1] is empty).");
5460
+ const scriptAbs = isAbsolute(script) ? script : join(process.cwd(), script);
5522
5461
  return {
5523
- label: "Config",
5524
- ok: false,
5525
- detail: "no config file or env vars found"
5462
+ kind: "node",
5463
+ program: process.execPath,
5464
+ args: [scriptAbs]
5526
5465
  };
5527
5466
  }
5528
- async function checkServerReachable() {
5529
- const serverUrl = get(getClientConfig(), "server.url");
5530
- if (typeof serverUrl !== "string" || !serverUrl) return {
5531
- label: "Server URL",
5532
- ok: false,
5533
- detail: "not configured (FIRST_TREE_HUB_SERVER_URL or config file)"
5534
- };
5535
- try {
5536
- const res = await fetch(`${serverUrl}/healthz`, { signal: AbortSignal.timeout(5e3) });
5537
- if (res.ok) return {
5538
- label: "Server URL",
5539
- ok: true,
5540
- detail: serverUrl
5541
- };
5542
- return {
5543
- label: "Server URL",
5544
- ok: false,
5545
- detail: `unhealthy (HTTP ${res.status}) at ${serverUrl}`
5546
- };
5547
- } catch {
5548
- return {
5549
- label: "Server URL",
5550
- ok: false,
5551
- detail: `unreachable at ${serverUrl}`
5552
- };
5553
- }
5467
+ function ensureLogDir() {
5468
+ mkdirSync(LOG_DIR, {
5469
+ recursive: true,
5470
+ mode: 448
5471
+ });
5554
5472
  }
5555
- function checkAgentConfigs() {
5556
- const agentsDir = join(DEFAULT_CONFIG_DIR, "agents");
5557
- if (!existsSync(agentsDir)) return {
5558
- label: "Agents",
5559
- ok: false,
5560
- detail: "no agents configured"
5561
- };
5562
- try {
5563
- const agents = loadAgents({
5564
- schema: agentConfigSchema,
5565
- agentsDir
5566
- });
5567
- if (agents.size === 0) return {
5568
- label: "Agents",
5569
- ok: false,
5570
- detail: "no agents configured"
5571
- };
5572
- const names = [...agents.keys()].join(", ");
5573
- return {
5574
- label: "Agents",
5575
- ok: true,
5576
- detail: `${agents.size} configured (${names})`
5577
- };
5578
- } catch {
5579
- return {
5580
- label: "Agents",
5581
- ok: false,
5582
- detail: "error reading agent configs"
5583
- };
5584
- }
5585
- }
5586
- async function checkWebSocket() {
5587
- const serverUrl = get(getClientConfig(), "server.url");
5588
- if (typeof serverUrl !== "string" || !serverUrl) return {
5589
- label: "WebSocket",
5590
- ok: false,
5591
- detail: "cannot check (no server URL)"
5592
- };
5593
- const wsUrl = serverUrl.replace(/^http/, "ws");
5594
- try {
5595
- if ((await fetch(`${serverUrl}/healthz`, { signal: AbortSignal.timeout(3e3) })).ok) return {
5596
- label: "WebSocket",
5597
- ok: true,
5598
- detail: `${wsUrl} (server reachable)`
5599
- };
5600
- return {
5601
- label: "WebSocket",
5602
- ok: false,
5603
- detail: "server not healthy"
5604
- };
5605
- } catch {
5606
- return {
5607
- label: "WebSocket",
5608
- ok: false,
5609
- detail: `server unreachable at ${serverUrl}`
5610
- };
5611
- }
5612
- }
5613
- function printResults(results) {
5614
- for (const r of results) {
5615
- const icon = r.ok ? "✓" : "✗";
5616
- print.line(` ${icon} ${r.label.padEnd(22)} ${r.detail}\n`);
5617
- }
5618
- blank();
5619
- const failures = results.filter((r) => !r.ok);
5620
- if (failures.length === 0) print.line(" All checks passed.\n");
5621
- else print.line(` ${failures.length} issue(s) found.\n`);
5622
- blank();
5623
- }
5624
- //#endregion
5625
- //#region src/core/migrate.ts
5626
- /**
5627
- * Resolve the drizzle migrations directory.
5628
- * 1. npm install: embedded at dist/drizzle/ (relative to the built CLI)
5629
- * 2. Monorepo dev: resolved from @first-tree-hub/server package
5630
- */
5631
- function resolveMigrationsFolder() {
5632
- const embeddedPath = join(dirname(fileURLToPath(import.meta.url)), "..", "drizzle");
5633
- if (existsSync(embeddedPath)) return embeddedPath;
5634
- return join(dirname(fileURLToPath(import.meta.resolve("@first-tree-hub/server/package.json"))), "drizzle");
5635
- }
5636
- /**
5637
- * Validate that migration journal timestamps are strictly increasing.
5638
- * Drizzle silently skips migrations whose `when` is <= the last applied
5639
- * timestamp, which causes missing columns/tables with no error.
5640
- */
5641
- function validateJournalOrder(migrationsFolder) {
5642
- const journalPath = join(migrationsFolder, "meta", "_journal.json");
5643
- if (!existsSync(journalPath)) return;
5644
- const journal = JSON.parse(readFileSync(journalPath, "utf-8"));
5645
- let prevWhen = 0;
5646
- let prevTag = "";
5647
- for (const entry of journal.entries) {
5648
- if (entry.when <= prevWhen) throw new Error(`Migration journal timestamps are not monotonically increasing:\n "${prevTag}" (when: ${prevWhen}) >= "${entry.tag}" (when: ${entry.when})\n Drizzle will silently skip "${entry.tag}". Fix the 'when' values in:\n ${journalPath}`);
5649
- prevWhen = entry.when;
5650
- prevTag = entry.tag;
5651
- }
5652
- }
5653
- /**
5654
- * Run Drizzle database migrations.
5655
- */
5656
- async function runMigrations(databaseUrl) {
5657
- const migrationsFolder = resolveMigrationsFolder();
5658
- validateJournalOrder(migrationsFolder);
5659
- const client = postgres(databaseUrl, { max: 1 });
5660
- const db = drizzle(client);
5661
- try {
5662
- await migrate(db, { migrationsFolder });
5663
- } finally {
5664
- await client.end();
5665
- }
5666
- const countClient = postgres(databaseUrl, { max: 1 });
5667
- try {
5668
- return (await countClient`
5669
- SELECT count(*)::int AS count
5670
- FROM information_schema.tables
5671
- WHERE table_schema = 'public' AND table_type = 'BASE TABLE'
5672
- `)[0].count;
5673
- } finally {
5674
- await countClient.end();
5675
- }
5676
- }
5677
- //#endregion
5678
- //#region src/core/service-install.ts
5679
- /**
5680
- * Run a subprocess capturing stderr so failures surface a meaningful error
5681
- * instead of Node's opaque "Command failed". Used for launchctl/systemctl —
5682
- * anywhere the stderr message is diagnostically crucial.
5683
- */
5684
- function runCapture(program, args, timeoutMs) {
5685
- const res = spawnSync(program, args, {
5686
- encoding: "utf-8",
5687
- timeout: timeoutMs,
5688
- stdio: [
5689
- "ignore",
5690
- "pipe",
5691
- "pipe"
5692
- ]
5693
- });
5694
- if (res.status === 0) return { ok: true };
5695
- return {
5696
- ok: false,
5697
- stderr: (res.stderr ?? "").trim(),
5698
- code: res.status
5699
- };
5700
- }
5701
- function sleepSync(ms) {
5702
- const shared = new Int32Array(new SharedArrayBuffer(4));
5703
- Atomics.wait(shared, 0, 0, ms);
5704
- }
5705
- const LAUNCHD_LABEL = "dev.first-tree-hub.client";
5706
- const SYSTEMD_UNIT = "first-tree-hub-client.service";
5707
- const LOG_DIR$1 = join(DEFAULT_HOME_DIR$1, "logs");
5708
- function whichBin(name) {
5709
- try {
5710
- return execFileSync(process.platform === "win32" ? "where" : "which", [name], {
5711
- encoding: "utf-8",
5712
- timeout: 3e3
5713
- }).split(/\r?\n/).map((s) => s.trim()).filter(Boolean)[0] ?? null;
5714
- } catch {
5715
- return null;
5716
- }
5717
- }
5718
- /**
5719
- * Resolve how the service should launch the CLI.
5720
- *
5721
- * Prefers the installed `first-tree-hub` bin on PATH (usually a shim under
5722
- * /usr/local/bin or ~/.npm-global/bin). Falls back to invoking the current
5723
- * Node interpreter against the running script (handles `pnpm dev`, tsx, and
5724
- * dev-only global installs).
5725
- */
5726
- function resolveCliInvocation() {
5727
- const bin = whichBin("first-tree-hub");
5728
- if (bin && isAbsolute(bin)) try {
5729
- return {
5730
- kind: "bin",
5731
- program: realpathSync(bin)
5732
- };
5733
- } catch {
5734
- return {
5735
- kind: "bin",
5736
- program: bin
5737
- };
5738
- }
5739
- const script = process.argv[1];
5740
- if (!script) throw new Error("Cannot resolve CLI entry point (process.argv[1] is empty).");
5741
- const scriptAbs = isAbsolute(script) ? script : join(process.cwd(), script);
5742
- return {
5743
- kind: "node",
5744
- program: process.execPath,
5745
- args: [scriptAbs]
5746
- };
5747
- }
5748
- function ensureLogDir() {
5749
- mkdirSync(LOG_DIR$1, {
5750
- recursive: true,
5751
- mode: 448
5752
- });
5753
- }
5754
- function launchdPlistPath() {
5755
- return join(homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
5473
+ function launchdPlistPath() {
5474
+ return join(homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
5756
5475
  }
5757
5476
  function renderPlist(invocation) {
5758
5477
  const argsXml = (invocation.kind === "bin" ? [
@@ -5767,8 +5486,8 @@ function renderPlist(invocation) {
5767
5486
  "start",
5768
5487
  "--no-interactive"
5769
5488
  ]).map((a) => ` <string>${escapeXml(a)}</string>`).join("\n");
5770
- const stdoutFallback = join(LOG_DIR$1, "client.stdout.log");
5771
- const stderrFallback = join(LOG_DIR$1, "client.stderr.log");
5489
+ const stdoutFallback = join(LOG_DIR, "client.stdout.log");
5490
+ const stderrFallback = join(LOG_DIR, "client.stderr.log");
5772
5491
  return `<?xml version="1.0" encoding="UTF-8"?>
5773
5492
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTD/PropertyList-1.0.dtd">
5774
5493
  <plist version="1.0">
@@ -5888,7 +5607,7 @@ function installLaunchd() {
5888
5607
  lastBootstrapErr = res;
5889
5608
  if (attempt < 2) sleepSync(1e3);
5890
5609
  }
5891
- if (lastBootstrapErr) throw new Error(`launchctl bootstrap failed: ${lastBootstrapErr.stderr || `exit ${lastBootstrapErr.code ?? "unknown"}`}\n Command: launchctl bootstrap ${target} ${plistPath}\n Recovery: \`launchctl bootout ${target}/${LAUNCHD_LABEL}\` then \`first-tree-hub service install\`.`);
5610
+ if (lastBootstrapErr) throw new Error(`launchctl bootstrap failed: ${lastBootstrapErr.stderr || `exit ${lastBootstrapErr.code ?? "unknown"}`}\n Command: launchctl bootstrap ${target} ${plistPath}\n Recovery: \`launchctl bootout ${target}/${LAUNCHD_LABEL}\` then \`first-tree-hub client connect <server-url>\`.`);
5892
5611
  const enableRes = runCapture("launchctl", ["enable", `${target}/${LAUNCHD_LABEL}`], 5e3);
5893
5612
  if (!enableRes.ok) print.line(` warning: launchctl enable: ${enableRes.stderr || `exit ${enableRes.code ?? "unknown"}`}\n`);
5894
5613
  const { state, detail } = launchdState();
@@ -5896,7 +5615,7 @@ function installLaunchd() {
5896
5615
  platform: "launchd",
5897
5616
  label: LAUNCHD_LABEL,
5898
5617
  unitPath: plistPath,
5899
- logDir: LOG_DIR$1,
5618
+ logDir: LOG_DIR,
5900
5619
  state,
5901
5620
  detail
5902
5621
  };
@@ -5910,7 +5629,7 @@ function uninstallLaunchd() {
5910
5629
  platform: "launchd",
5911
5630
  label: LAUNCHD_LABEL,
5912
5631
  unitPath: plistPath,
5913
- logDir: LOG_DIR$1,
5632
+ logDir: LOG_DIR,
5914
5633
  state: "not-installed"
5915
5634
  };
5916
5635
  }
@@ -5928,8 +5647,8 @@ Type=simple
5928
5647
  ExecStart=${invocation.kind === "bin" ? `${shellQuote(invocation.program)} client start --no-interactive` : `${shellQuote(invocation.program)} ${invocation.args.map(shellQuote).join(" ")} client start --no-interactive`}
5929
5648
  Restart=always
5930
5649
  RestartSec=10
5931
- StandardOutput=append:${join(LOG_DIR$1, "client.stdout.log")}
5932
- StandardError=append:${join(LOG_DIR$1, "client.stderr.log")}
5650
+ StandardOutput=append:${join(LOG_DIR, "client.stdout.log")}
5651
+ StandardError=append:${join(LOG_DIR, "client.stderr.log")}
5933
5652
  Environment=PATH=/usr/local/bin:/usr/bin:/bin
5934
5653
  Environment=FIRST_TREE_HUB_SERVICE_MODE=1
5935
5654
 
@@ -5941,128 +5660,453 @@ function shellQuote(value) {
5941
5660
  if (/^[A-Za-z0-9_\-./:=]+$/.test(value)) return value;
5942
5661
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
5943
5662
  }
5944
- function systemdState() {
5945
- if (!existsSync(systemdUnitPath())) return { state: "not-installed" };
5946
- const res = spawnSync("systemctl", [
5947
- "--user",
5948
- "is-active",
5949
- SYSTEMD_UNIT
5950
- ], {
5951
- encoding: "utf-8",
5952
- timeout: 5e3,
5953
- stdio: [
5954
- "ignore",
5955
- "pipe",
5956
- "pipe"
5957
- ]
5958
- });
5959
- const out = (res.stdout ?? "").trim();
5960
- if (res.status === 0 && out === "active") return {
5961
- state: "active",
5962
- detail: "running"
5663
+ function systemdState() {
5664
+ if (!existsSync(systemdUnitPath())) return { state: "not-installed" };
5665
+ const res = spawnSync("systemctl", [
5666
+ "--user",
5667
+ "is-active",
5668
+ SYSTEMD_UNIT
5669
+ ], {
5670
+ encoding: "utf-8",
5671
+ timeout: 5e3,
5672
+ stdio: [
5673
+ "ignore",
5674
+ "pipe",
5675
+ "pipe"
5676
+ ]
5677
+ });
5678
+ const out = (res.stdout ?? "").trim();
5679
+ if (res.status === 0 && out === "active") return {
5680
+ state: "active",
5681
+ detail: "running"
5682
+ };
5683
+ return {
5684
+ state: "inactive",
5685
+ detail: out || "unit present but not active"
5686
+ };
5687
+ }
5688
+ function installSystemd() {
5689
+ const invocation = resolveCliInvocation();
5690
+ ensureLogDir();
5691
+ const unitPath = systemdUnitPath();
5692
+ mkdirSync(dirname(unitPath), { recursive: true });
5693
+ writeFileSync(unitPath, renderSystemdUnit(invocation), { mode: 420 });
5694
+ const reloadRes = runCapture("systemctl", ["--user", "daemon-reload"], 5e3);
5695
+ if (!reloadRes.ok) throw new Error(`systemctl --user daemon-reload failed: ${reloadRes.stderr || `exit ${reloadRes.code ?? "unknown"}`}`);
5696
+ const enableRes = runCapture("systemctl", [
5697
+ "--user",
5698
+ "enable",
5699
+ "--now",
5700
+ SYSTEMD_UNIT
5701
+ ], 1e4);
5702
+ if (!enableRes.ok) throw new Error(`systemctl --user enable --now ${SYSTEMD_UNIT} failed: ${enableRes.stderr || `exit ${enableRes.code ?? "unknown"}`}\n Recovery: \`systemctl --user stop ${SYSTEMD_UNIT}\` then \`first-tree-hub client connect <server-url>\`.`);
5703
+ const { state, detail } = systemdState();
5704
+ return {
5705
+ platform: "systemd",
5706
+ label: SYSTEMD_UNIT,
5707
+ unitPath,
5708
+ logDir: LOG_DIR,
5709
+ state,
5710
+ detail
5711
+ };
5712
+ }
5713
+ function uninstallSystemd() {
5714
+ const unitPath = systemdUnitPath();
5715
+ const disableRes = runCapture("systemctl", [
5716
+ "--user",
5717
+ "disable",
5718
+ "--now",
5719
+ SYSTEMD_UNIT
5720
+ ], 1e4);
5721
+ if (!disableRes.ok && !/not found|no such|not loaded/i.test(disableRes.stderr)) print.line(` warning: systemctl disable during uninstall: ${disableRes.stderr || `exit ${disableRes.code ?? "unknown"}`}\n`);
5722
+ if (existsSync(unitPath)) rmSync(unitPath);
5723
+ const reloadRes = runCapture("systemctl", ["--user", "daemon-reload"], 5e3);
5724
+ if (!reloadRes.ok) print.line(` warning: systemctl daemon-reload during uninstall: ${reloadRes.stderr || `exit ${reloadRes.code ?? "unknown"}`}\n`);
5725
+ return {
5726
+ platform: "systemd",
5727
+ label: SYSTEMD_UNIT,
5728
+ unitPath,
5729
+ logDir: LOG_DIR,
5730
+ state: "not-installed"
5731
+ };
5732
+ }
5733
+ /** Is background-service install supported on the current platform? */
5734
+ function isServiceSupported() {
5735
+ return process.platform === "darwin" || process.platform === "linux";
5736
+ }
5737
+ /**
5738
+ * Install the background service for the current platform.
5739
+ *
5740
+ * @throws {Error} if the platform is not supported or the service manager fails.
5741
+ */
5742
+ function installClientService() {
5743
+ if (process.platform === "darwin") return installLaunchd();
5744
+ if (process.platform === "linux") return installSystemd();
5745
+ throw new Error(`Background service install is not supported on ${process.platform}. Run \`first-tree-hub client start\` manually to keep the computer online.`);
5746
+ }
5747
+ /** Report the current service state without modifying anything. */
5748
+ function getClientServiceStatus() {
5749
+ if (process.platform === "darwin") {
5750
+ const { state, detail } = launchdState();
5751
+ return {
5752
+ platform: "launchd",
5753
+ label: LAUNCHD_LABEL,
5754
+ unitPath: launchdPlistPath(),
5755
+ logDir: LOG_DIR,
5756
+ state,
5757
+ detail
5758
+ };
5759
+ }
5760
+ if (process.platform === "linux") {
5761
+ const { state, detail } = systemdState();
5762
+ return {
5763
+ platform: "systemd",
5764
+ label: SYSTEMD_UNIT,
5765
+ unitPath: systemdUnitPath(),
5766
+ logDir: LOG_DIR,
5767
+ state,
5768
+ detail
5769
+ };
5770
+ }
5771
+ return {
5772
+ platform: "unsupported",
5773
+ label: "",
5774
+ unitPath: "",
5775
+ logDir: LOG_DIR,
5776
+ state: "not-installed",
5777
+ detail: `platform ${process.platform} not supported`
5778
+ };
5779
+ }
5780
+ /** Uninstall the background service. No-op if not installed. */
5781
+ function uninstallClientService() {
5782
+ if (process.platform === "darwin") return uninstallLaunchd();
5783
+ if (process.platform === "linux") return uninstallSystemd();
5784
+ return getClientServiceStatus();
5785
+ }
5786
+ //#endregion
5787
+ //#region src/core/doctor.ts
5788
+ function getServerConfig() {
5789
+ return resolveConfigReadonly({
5790
+ schema: serverConfigSchema,
5791
+ role: "server"
5792
+ });
5793
+ }
5794
+ function getClientConfig() {
5795
+ return resolveConfigReadonly({
5796
+ schema: clientConfigSchema,
5797
+ role: "client"
5798
+ });
5799
+ }
5800
+ function get(obj, dotPath) {
5801
+ const parts = dotPath.split(".");
5802
+ let current = obj;
5803
+ for (const part of parts) {
5804
+ if (current === null || current === void 0 || typeof current !== "object") return void 0;
5805
+ current = current[part];
5806
+ }
5807
+ return current;
5808
+ }
5809
+ function checkNodeVersion() {
5810
+ const version = process.versions.node;
5811
+ const [major] = version.split(".").map(Number);
5812
+ const ok = major !== void 0 && major >= 22;
5813
+ return {
5814
+ label: "Node.js",
5815
+ ok,
5816
+ detail: ok ? `v${version}` : `v${version} (requires >= 22.16)`
5817
+ };
5818
+ }
5819
+ function checkDocker() {
5820
+ try {
5821
+ return {
5822
+ label: "Docker",
5823
+ ok: true,
5824
+ detail: execFileSync("docker", ["--version"], {
5825
+ encoding: "utf-8",
5826
+ timeout: 5e3
5827
+ }).trim().replace("Docker version ", "v").split(",")[0] ?? ""
5828
+ };
5829
+ } catch {
5830
+ return {
5831
+ label: "Docker",
5832
+ ok: false,
5833
+ detail: "not found (optional — needed for auto PG provisioning)"
5834
+ };
5835
+ }
5836
+ }
5837
+ function checkServerConfig() {
5838
+ const hasFile = existsSync(join(DEFAULT_CONFIG_DIR, "server.yaml"));
5839
+ const hasEnv = !!process.env.FIRST_TREE_HUB_DATABASE_URL;
5840
+ if (hasFile && hasEnv) return {
5841
+ label: "Config",
5842
+ ok: true,
5843
+ detail: "config file + env vars"
5844
+ };
5845
+ if (hasFile) return {
5846
+ label: "Config",
5847
+ ok: true,
5848
+ detail: join(DEFAULT_CONFIG_DIR, "server.yaml")
5849
+ };
5850
+ if (hasEnv) return {
5851
+ label: "Config",
5852
+ ok: true,
5853
+ detail: "via environment variables"
5854
+ };
5855
+ return {
5856
+ label: "Config",
5857
+ ok: false,
5858
+ detail: "no config file or env vars found"
5859
+ };
5860
+ }
5861
+ async function checkDatabase() {
5862
+ const dbUrl = get(getServerConfig(), "database.url");
5863
+ if (typeof dbUrl !== "string" || !dbUrl) return {
5864
+ label: "Database",
5865
+ ok: false,
5866
+ detail: "not configured (FIRST_TREE_HUB_DATABASE_URL or config file)"
5867
+ };
5868
+ try {
5869
+ const { default: pg } = await import("postgres");
5870
+ const sql = pg(dbUrl, {
5871
+ max: 1,
5872
+ connect_timeout: 5,
5873
+ idle_timeout: 1
5874
+ });
5875
+ await sql.unsafe("SELECT 1");
5876
+ await sql.end();
5877
+ return {
5878
+ label: "Database",
5879
+ ok: true,
5880
+ detail: "connected"
5881
+ };
5882
+ } catch (err) {
5883
+ return {
5884
+ label: "Database",
5885
+ ok: false,
5886
+ detail: `unreachable — ${(err instanceof Error ? err.message : String(err)).slice(0, 80)}`
5887
+ };
5888
+ }
5889
+ }
5890
+ async function checkServerHealth() {
5891
+ const config = getServerConfig();
5892
+ const host = get(config, "server.host") ?? "127.0.0.1";
5893
+ const port = get(config, "server.port") ?? 8e3;
5894
+ const url = `http://${host}:${port}/healthz`;
5895
+ try {
5896
+ const res = await fetch(url, { signal: AbortSignal.timeout(3e3) });
5897
+ if (res.ok) return {
5898
+ label: "Server Health",
5899
+ ok: true,
5900
+ detail: `running at ${host}:${port}`
5901
+ };
5902
+ return {
5903
+ label: "Server Health",
5904
+ ok: false,
5905
+ detail: `unhealthy (HTTP ${res.status}) at ${host}:${port}`
5906
+ };
5907
+ } catch {
5908
+ return {
5909
+ label: "Server Health",
5910
+ ok: false,
5911
+ detail: `not running at ${host}:${port}`
5912
+ };
5913
+ }
5914
+ }
5915
+ function checkClientConfig() {
5916
+ const hasFile = existsSync(join(DEFAULT_CONFIG_DIR, "client.yaml"));
5917
+ const hasEnv = !!process.env.FIRST_TREE_HUB_SERVER_URL;
5918
+ if (hasFile && hasEnv) return {
5919
+ label: "Config",
5920
+ ok: true,
5921
+ detail: "config file + env vars"
5922
+ };
5923
+ if (hasFile) return {
5924
+ label: "Config",
5925
+ ok: true,
5926
+ detail: join(DEFAULT_CONFIG_DIR, "client.yaml")
5927
+ };
5928
+ if (hasEnv) return {
5929
+ label: "Config",
5930
+ ok: true,
5931
+ detail: "via environment variables"
5932
+ };
5933
+ return {
5934
+ label: "Config",
5935
+ ok: false,
5936
+ detail: "no config file or env vars found"
5937
+ };
5938
+ }
5939
+ async function checkServerReachable() {
5940
+ const serverUrl = get(getClientConfig(), "server.url");
5941
+ if (typeof serverUrl !== "string" || !serverUrl) return {
5942
+ label: "Server URL",
5943
+ ok: false,
5944
+ detail: "not configured (FIRST_TREE_HUB_SERVER_URL or config file)"
5945
+ };
5946
+ try {
5947
+ const res = await fetch(`${serverUrl}/healthz`, { signal: AbortSignal.timeout(5e3) });
5948
+ if (res.ok) return {
5949
+ label: "Server URL",
5950
+ ok: true,
5951
+ detail: serverUrl
5952
+ };
5953
+ return {
5954
+ label: "Server URL",
5955
+ ok: false,
5956
+ detail: `unhealthy (HTTP ${res.status}) at ${serverUrl}`
5957
+ };
5958
+ } catch {
5959
+ return {
5960
+ label: "Server URL",
5961
+ ok: false,
5962
+ detail: `unreachable at ${serverUrl}`
5963
+ };
5964
+ }
5965
+ }
5966
+ function checkAgentConfigs() {
5967
+ const agentsDir = join(DEFAULT_CONFIG_DIR, "agents");
5968
+ if (!existsSync(agentsDir)) return {
5969
+ label: "Agents",
5970
+ ok: false,
5971
+ detail: "no agents configured"
5972
+ };
5973
+ try {
5974
+ const agents = loadAgents({
5975
+ schema: agentConfigSchema,
5976
+ agentsDir
5977
+ });
5978
+ if (agents.size === 0) return {
5979
+ label: "Agents",
5980
+ ok: false,
5981
+ detail: "no agents configured"
5982
+ };
5983
+ const names = [...agents.keys()].join(", ");
5984
+ return {
5985
+ label: "Agents",
5986
+ ok: true,
5987
+ detail: `${agents.size} configured (${names})`
5988
+ };
5989
+ } catch {
5990
+ return {
5991
+ label: "Agents",
5992
+ ok: false,
5993
+ detail: "error reading agent configs"
5994
+ };
5995
+ }
5996
+ }
5997
+ function checkBackgroundService() {
5998
+ const info = getClientServiceStatus();
5999
+ if (info.platform === "unsupported") return {
6000
+ label: "Background service",
6001
+ ok: true,
6002
+ detail: `not supported on ${process.platform} — runs inline`
5963
6003
  };
5964
- return {
5965
- state: "inactive",
5966
- detail: out || "unit present but not active"
6004
+ if (info.state === "active") return {
6005
+ label: "Background service",
6006
+ ok: true,
6007
+ detail: `running (${info.platform}${info.detail ? `, ${info.detail}` : ""}); logs at ${info.logDir}`
6008
+ };
6009
+ if (info.state === "inactive") return {
6010
+ label: "Background service",
6011
+ ok: false,
6012
+ detail: `installed but not running${info.detail ? ` — ${info.detail}` : ""}; unit at ${info.unitPath}`
5967
6013
  };
5968
- }
5969
- function installSystemd() {
5970
- const invocation = resolveCliInvocation();
5971
- ensureLogDir();
5972
- const unitPath = systemdUnitPath();
5973
- mkdirSync(dirname(unitPath), { recursive: true });
5974
- writeFileSync(unitPath, renderSystemdUnit(invocation), { mode: 420 });
5975
- const reloadRes = runCapture("systemctl", ["--user", "daemon-reload"], 5e3);
5976
- if (!reloadRes.ok) throw new Error(`systemctl --user daemon-reload failed: ${reloadRes.stderr || `exit ${reloadRes.code ?? "unknown"}`}`);
5977
- const enableRes = runCapture("systemctl", [
5978
- "--user",
5979
- "enable",
5980
- "--now",
5981
- SYSTEMD_UNIT
5982
- ], 1e4);
5983
- if (!enableRes.ok) throw new Error(`systemctl --user enable --now ${SYSTEMD_UNIT} failed: ${enableRes.stderr || `exit ${enableRes.code ?? "unknown"}`}\n Recovery: \`systemctl --user stop ${SYSTEMD_UNIT}\` then \`first-tree-hub service install\`.`);
5984
- const { state, detail } = systemdState();
5985
6014
  return {
5986
- platform: "systemd",
5987
- label: SYSTEMD_UNIT,
5988
- unitPath,
5989
- logDir: LOG_DIR$1,
5990
- state,
5991
- detail
6015
+ label: "Background service",
6016
+ ok: false,
6017
+ detail: "not installed — re-run `first-tree-hub client connect <url>` to install"
5992
6018
  };
5993
6019
  }
5994
- function uninstallSystemd() {
5995
- const unitPath = systemdUnitPath();
5996
- const disableRes = runCapture("systemctl", [
5997
- "--user",
5998
- "disable",
5999
- "--now",
6000
- SYSTEMD_UNIT
6001
- ], 1e4);
6002
- if (!disableRes.ok && !/not found|no such|not loaded/i.test(disableRes.stderr)) print.line(` warning: systemctl disable during uninstall: ${disableRes.stderr || `exit ${disableRes.code ?? "unknown"}`}\n`);
6003
- if (existsSync(unitPath)) rmSync(unitPath);
6004
- const reloadRes = runCapture("systemctl", ["--user", "daemon-reload"], 5e3);
6005
- if (!reloadRes.ok) print.line(` warning: systemctl daemon-reload during uninstall: ${reloadRes.stderr || `exit ${reloadRes.code ?? "unknown"}`}\n`);
6006
- return {
6007
- platform: "systemd",
6008
- label: SYSTEMD_UNIT,
6009
- unitPath,
6010
- logDir: LOG_DIR$1,
6011
- state: "not-installed"
6020
+ async function checkWebSocket() {
6021
+ const serverUrl = get(getClientConfig(), "server.url");
6022
+ if (typeof serverUrl !== "string" || !serverUrl) return {
6023
+ label: "WebSocket",
6024
+ ok: false,
6025
+ detail: "cannot check (no server URL)"
6012
6026
  };
6027
+ const wsUrl = serverUrl.replace(/^http/, "ws");
6028
+ try {
6029
+ if ((await fetch(`${serverUrl}/healthz`, { signal: AbortSignal.timeout(3e3) })).ok) return {
6030
+ label: "WebSocket",
6031
+ ok: true,
6032
+ detail: `${wsUrl} (server reachable)`
6033
+ };
6034
+ return {
6035
+ label: "WebSocket",
6036
+ ok: false,
6037
+ detail: "server not healthy"
6038
+ };
6039
+ } catch {
6040
+ return {
6041
+ label: "WebSocket",
6042
+ ok: false,
6043
+ detail: `server unreachable at ${serverUrl}`
6044
+ };
6045
+ }
6013
6046
  }
6014
- /** Is background-service install supported on the current platform? */
6015
- function isServiceSupported() {
6016
- return process.platform === "darwin" || process.platform === "linux";
6047
+ function printResults(results) {
6048
+ for (const r of results) {
6049
+ const icon = r.ok ? "" : "";
6050
+ print.line(` ${icon} ${r.label.padEnd(22)} ${r.detail}\n`);
6051
+ }
6052
+ blank();
6053
+ const failures = results.filter((r) => !r.ok);
6054
+ if (failures.length === 0) print.line(" All checks passed.\n");
6055
+ else print.line(` ${failures.length} issue(s) found.\n`);
6056
+ blank();
6057
+ }
6058
+ //#endregion
6059
+ //#region src/core/migrate.ts
6060
+ /**
6061
+ * Resolve the drizzle migrations directory.
6062
+ * 1. npm install: embedded at dist/drizzle/ (relative to the built CLI)
6063
+ * 2. Monorepo dev: resolved from @first-tree-hub/server package
6064
+ */
6065
+ function resolveMigrationsFolder() {
6066
+ const embeddedPath = join(dirname(fileURLToPath(import.meta.url)), "..", "drizzle");
6067
+ if (existsSync(embeddedPath)) return embeddedPath;
6068
+ return join(dirname(fileURLToPath(import.meta.resolve("@first-tree-hub/server/package.json"))), "drizzle");
6017
6069
  }
6018
6070
  /**
6019
- * Install the background service for the current platform.
6020
- *
6021
- * @throws {Error} if the platform is not supported or the service manager fails.
6071
+ * Validate that migration journal timestamps are strictly increasing.
6072
+ * Drizzle silently skips migrations whose `when` is <= the last applied
6073
+ * timestamp, which causes missing columns/tables with no error.
6022
6074
  */
6023
- function installClientService() {
6024
- if (process.platform === "darwin") return installLaunchd();
6025
- if (process.platform === "linux") return installSystemd();
6026
- throw new Error(`Background service install is not supported on ${process.platform}. Run \`first-tree-hub client start\` manually to keep the computer online.`);
6075
+ function validateJournalOrder(migrationsFolder) {
6076
+ const journalPath = join(migrationsFolder, "meta", "_journal.json");
6077
+ if (!existsSync(journalPath)) return;
6078
+ const journal = JSON.parse(readFileSync(journalPath, "utf-8"));
6079
+ let prevWhen = 0;
6080
+ let prevTag = "";
6081
+ for (const entry of journal.entries) {
6082
+ if (entry.when <= prevWhen) throw new Error(`Migration journal timestamps are not monotonically increasing:\n "${prevTag}" (when: ${prevWhen}) >= "${entry.tag}" (when: ${entry.when})\n Drizzle will silently skip "${entry.tag}". Fix the 'when' values in:\n ${journalPath}`);
6083
+ prevWhen = entry.when;
6084
+ prevTag = entry.tag;
6085
+ }
6027
6086
  }
6028
- /** Report the current service state without modifying anything. */
6029
- function getClientServiceStatus() {
6030
- if (process.platform === "darwin") {
6031
- const { state, detail } = launchdState();
6032
- return {
6033
- platform: "launchd",
6034
- label: LAUNCHD_LABEL,
6035
- unitPath: launchdPlistPath(),
6036
- logDir: LOG_DIR$1,
6037
- state,
6038
- detail
6039
- };
6087
+ /**
6088
+ * Run Drizzle database migrations.
6089
+ */
6090
+ async function runMigrations(databaseUrl) {
6091
+ const migrationsFolder = resolveMigrationsFolder();
6092
+ validateJournalOrder(migrationsFolder);
6093
+ const client = postgres(databaseUrl, { max: 1 });
6094
+ const db = drizzle(client);
6095
+ try {
6096
+ await migrate(db, { migrationsFolder });
6097
+ } finally {
6098
+ await client.end();
6040
6099
  }
6041
- if (process.platform === "linux") {
6042
- const { state, detail } = systemdState();
6043
- return {
6044
- platform: "systemd",
6045
- label: SYSTEMD_UNIT,
6046
- unitPath: systemdUnitPath(),
6047
- logDir: LOG_DIR$1,
6048
- state,
6049
- detail
6050
- };
6100
+ const countClient = postgres(databaseUrl, { max: 1 });
6101
+ try {
6102
+ return (await countClient`
6103
+ SELECT count(*)::int AS count
6104
+ FROM information_schema.tables
6105
+ WHERE table_schema = 'public' AND table_type = 'BASE TABLE'
6106
+ `)[0].count;
6107
+ } finally {
6108
+ await countClient.end();
6051
6109
  }
6052
- return {
6053
- platform: "unsupported",
6054
- label: "",
6055
- unitPath: "",
6056
- logDir: LOG_DIR$1,
6057
- state: "not-installed",
6058
- detail: `platform ${process.platform} not supported`
6059
- };
6060
- }
6061
- /** Uninstall the background service. No-op if not installed. */
6062
- function uninstallClientService() {
6063
- if (process.platform === "darwin") return uninstallLaunchd();
6064
- if (process.platform === "linux") return uninstallSystemd();
6065
- return getClientServiceStatus();
6066
6110
  }
6067
6111
  //#endregion
6068
6112
  //#region src/core/migrate-home.ts
@@ -6098,7 +6142,7 @@ function runHomeMigration() {
6098
6142
  }
6099
6143
  print.line(`[first-tree-hub] Copied client home to new layout: ${result.from} → ${result.to}\n (Legacy directory preserved as a backup — delete it manually once you've verified the new location works.)\n`);
6100
6144
  if (process.argv.includes("--no-interactive")) {
6101
- print.line("[first-tree-hub] Note: running as background service — skipped auto re-register to avoid self-termination.\n Run `first-tree-hub client service install` from a terminal to refresh log paths.\n");
6145
+ print.line("[first-tree-hub] Note: running as background service — skipped auto re-register to avoid self-termination.\n Service paths will refresh on the next `first-tree-hub client connect <url>`.\n");
6102
6146
  return;
6103
6147
  }
6104
6148
  const status = getClientServiceStatus();
@@ -6108,7 +6152,7 @@ function runHomeMigration() {
6108
6152
  print.line(`[first-tree-hub] Re-registered background service with new home paths.\n`);
6109
6153
  } catch (err) {
6110
6154
  const msg = err instanceof Error ? err.message : String(err);
6111
- print.line(`[first-tree-hub] WARNING: home migration succeeded but re-registering the background service failed: ${msg}\n Run \`first-tree-hub client service install\` to refresh log paths.\n`);
6155
+ print.line(`[first-tree-hub] WARNING: home migration succeeded but re-registering the background service failed: ${msg}\n Re-run \`first-tree-hub client connect <url>\` to refresh service paths.\n`);
6112
6156
  }
6113
6157
  }
6114
6158
  //#endregion
@@ -6279,7 +6323,7 @@ async function onboardCreate(args) {
6279
6323
  }
6280
6324
  const runtimeAgent = args.type === "human" ? args.assistant : args.id;
6281
6325
  if (args.feishuBotAppId && args.feishuBotAppSecret) {
6282
- const { bindFeishuBot } = await import("./feishu-B1Kiq7S6.mjs").then((n) => n.r);
6326
+ const { bindFeishuBot } = await import("./feishu-B2sjp6Z6.mjs").then((n) => n.r);
6283
6327
  const targetAgentUuid = args.type === "human" ? assistantUuid : primary.uuid;
6284
6328
  if (!targetAgentUuid) print.line(`Warning: Cannot bind Feishu bot — no runtime agent available for "${args.id}".\n`);
6285
6329
  else {
@@ -7177,7 +7221,7 @@ function createFeedbackHandler(config) {
7177
7221
  return { handle };
7178
7222
  }
7179
7223
  //#endregion
7180
- //#region ../server/dist/app-RWQiJW6_.mjs
7224
+ //#region ../server/dist/app-UkoRS4YL.mjs
7181
7225
  var __defProp = Object.defineProperty;
7182
7226
  var __exportAll = (all, no_symbols) => {
7183
7227
  let target = {};
@@ -8207,6 +8251,7 @@ async function createAgent(db, data) {
8207
8251
  const uuid = uuidv7();
8208
8252
  const name = data.name ?? null;
8209
8253
  if (name?.startsWith(RESERVED_AGENT_NAME_PREFIX)) throw new BadRequestError(`Agent name "${name}" is reserved — names starting with "${RESERVED_AGENT_NAME_PREFIX}" are Hub-internal`);
8254
+ if (name && isReservedAgentName$1(name)) throw new BadRequestError(`Agent name "${name}" is reserved — pick a different one.`);
8210
8255
  const inboxId = `inbox_${uuid}`;
8211
8256
  let orgId;
8212
8257
  let managerId;
@@ -8262,6 +8307,21 @@ async function createAgent(db, data) {
8262
8307
  throw err;
8263
8308
  }
8264
8309
  }
8310
+ async function checkAgentNameAvailability(db, orgId, name) {
8311
+ if (!AGENT_NAME_REGEX$1.test(name)) return {
8312
+ available: false,
8313
+ reason: "invalid"
8314
+ };
8315
+ if (isReservedAgentName$1(name) || name.startsWith(RESERVED_AGENT_NAME_PREFIX)) return {
8316
+ available: false,
8317
+ reason: "reserved"
8318
+ };
8319
+ const [existing] = await db.select({ uuid: agents.uuid }).from(agents).where(and(eq(agents.organizationId, orgId), eq(agents.name, name), ne(agents.status, AGENT_STATUSES.DELETED))).limit(1);
8320
+ return existing ? {
8321
+ available: false,
8322
+ reason: "taken"
8323
+ } : { available: true };
8324
+ }
8265
8325
  async function getAgent(db, uuid) {
8266
8326
  const [agent] = await db.select().from(agents).where(and(eq(agents.uuid, uuid), ne(agents.status, AGENT_STATUSES.DELETED))).limit(1);
8267
8327
  if (!agent) throw new NotFoundError(`Agent "${uuid}" not found`);
@@ -9516,6 +9576,18 @@ async function adminAgentRoutes(app) {
9516
9576
  nextCursor: result.nextCursor
9517
9577
  };
9518
9578
  });
9579
+ /**
9580
+ * Pre-create availability probe for the web creation form. The caller types
9581
+ * an agent name; we answer whether the POST would succeed (`available: true`)
9582
+ * or why it would fail (`invalid` / `reserved` / `taken`) without actually
9583
+ * inserting a row. The regular POST still validates authoritatively — this is
9584
+ * a pure UX convenience. Scoped to the caller's org, so two orgs can each
9585
+ * have a `coder` without one blocking the other.
9586
+ */
9587
+ app.get("/names/:name/availability", async (request) => {
9588
+ const scope = memberScope(request);
9589
+ return await checkAgentNameAvailability(app.db, scope.organizationId, request.params.name);
9590
+ });
9519
9591
  app.post("/", async (request, reply) => {
9520
9592
  const scope = memberScope(request);
9521
9593
  const body = createAgentSchema.parse(request.body);
@@ -15031,7 +15103,7 @@ async function startServer(options) {
15031
15103
  instanceId: `srv_${randomUUID().slice(0, 8)}`,
15032
15104
  commandVersion: COMMAND_VERSION
15033
15105
  };
15034
- const { initTelemetry, shutdownTelemetry } = await import("./observability-hDEdrmMS.mjs");
15106
+ const { initTelemetry, shutdownTelemetry } = await import("./observability-DDkJwSKv.mjs");
15035
15107
  await initTelemetry(serverConfig.observability.tracing, config.instanceId);
15036
15108
  const app = await buildApp(config);
15037
15109
  const shutdown = async () => {
@@ -15081,175 +15153,6 @@ function resolveWebDist() {
15081
15153
  } catch {}
15082
15154
  }
15083
15155
  //#endregion
15084
- //#region src/core/service-logs.ts
15085
- const LOG_DIR = join(DEFAULT_HOME_DIR$1, "logs");
15086
- const PRIMARY_LOG = join(LOG_DIR, "client.log");
15087
- const FALLBACK_STDOUT = join(LOG_DIR, "client.stdout.log");
15088
- const FALLBACK_STDERR = join(LOG_DIR, "client.stderr.log");
15089
- /**
15090
- * Duration string → milliseconds. Accepts `10s`, `5m`, `2h`, `1d`; rejects
15091
- * everything else. Keeps the parser tiny rather than pulling in a library —
15092
- * the `--since` flag is the only consumer.
15093
- */
15094
- function parseDuration(input) {
15095
- const match = /^(\d+)\s*(s|m|h|d)$/.exec(input.trim());
15096
- if (!match) throw new Error(`invalid duration "${input}" (expected e.g. 30s, 5m, 2h, 1d)`);
15097
- return Number(match[1]) * ({
15098
- s: 1e3,
15099
- m: 6e4,
15100
- h: 36e5,
15101
- d: 864e5
15102
- }[match[2]] ?? 0);
15103
- }
15104
- const LEVEL_RANK = {
15105
- trace: 10,
15106
- debug: 20,
15107
- info: 30,
15108
- warn: 40,
15109
- error: 50,
15110
- fatal: 60
15111
- };
15112
- /** Rotated log files, newest-first. Missing files are silently skipped. */
15113
- function listLogFilesNewestFirst() {
15114
- const files = [];
15115
- if (existsSync(PRIMARY_LOG)) files.push(PRIMARY_LOG);
15116
- for (let i = 1;; i++) {
15117
- const p = `${PRIMARY_LOG}.${i}`;
15118
- if (!existsSync(p)) break;
15119
- files.push(p);
15120
- }
15121
- return files;
15122
- }
15123
- /** Supervisor fallback files (raw stdout/stderr, not NDJSON). Missing files skipped. */
15124
- function listFallbackFiles() {
15125
- const files = [];
15126
- if (existsSync(FALLBACK_STDERR)) files.push(FALLBACK_STDERR);
15127
- if (existsSync(FALLBACK_STDOUT)) files.push(FALLBACK_STDOUT);
15128
- return files;
15129
- }
15130
- function matchesFilters(obj, minLevel, cutoffMs) {
15131
- if (minLevel !== void 0) {
15132
- const lvl = typeof obj.level === "number" ? obj.level : NaN;
15133
- if (!Number.isFinite(lvl) || lvl < minLevel) return false;
15134
- }
15135
- if (cutoffMs !== void 0) {
15136
- const t = parseLogTime(obj.time);
15137
- if (t === null || t < cutoffMs) return false;
15138
- }
15139
- return true;
15140
- }
15141
- /** Logger writes `time` as a local-ish string (`YYYY-MM-DD HH:mm:ss`). */
15142
- function parseLogTime(value) {
15143
- if (typeof value === "number") return value;
15144
- if (typeof value !== "string") return null;
15145
- const iso = value.replace(" ", "T");
15146
- const ms = Date.parse(iso);
15147
- return Number.isFinite(ms) ? ms : null;
15148
- }
15149
- function renderLine(line, json) {
15150
- if (!line.trim()) return null;
15151
- if (json) return `${line}\n`;
15152
- try {
15153
- return formatPrettyEntry$1(line);
15154
- } catch {
15155
- return `${line}\n`;
15156
- }
15157
- }
15158
- function processLogLine(line, minLevel, cutoffMs, json) {
15159
- let obj;
15160
- try {
15161
- obj = JSON.parse(line);
15162
- } catch {
15163
- return json ? null : `${line}\n`;
15164
- }
15165
- if (!matchesFilters(obj, minLevel, cutoffMs)) return null;
15166
- return renderLine(line, json);
15167
- }
15168
- async function readFileLines(path, minLevel, cutoffMs, json) {
15169
- const rl = createInterface({ input: createReadStream(path, { encoding: "utf8" }) });
15170
- for await (const line of rl) {
15171
- const rendered = processLogLine(line, minLevel, cutoffMs, json);
15172
- if (rendered) process.stdout.write(rendered);
15173
- }
15174
- }
15175
- /**
15176
- * Read a supervisor fallback file (launchd / systemd stdout/stderr capture).
15177
- * These are plain text, not NDJSON: level and time filters don't apply, so we
15178
- * honour `--since` by dropping the whole file when its mtime predates the
15179
- * cutoff and otherwise pass every line through. In pretty mode each line is
15180
- * tagged with the source so operators can tell it apart from pino output; in
15181
- * `--json` mode we emit a synthetic record so NDJSON consumers keep one
15182
- * object per line.
15183
- */
15184
- async function readFallbackFile(path, cutoffMs, json) {
15185
- try {
15186
- const mtime = statSync(path).mtimeMs;
15187
- if (cutoffMs !== void 0 && mtime < cutoffMs) return;
15188
- } catch {
15189
- return;
15190
- }
15191
- const source = path.endsWith(".stderr.log") ? "supervisor:stderr" : "supervisor:stdout";
15192
- const rl = createInterface({ input: createReadStream(path, { encoding: "utf8" }) });
15193
- for await (const line of rl) {
15194
- if (!line.trim()) continue;
15195
- if (json) process.stdout.write(`${JSON.stringify({
15196
- source,
15197
- line
15198
- })}\n`);
15199
- else process.stdout.write(`[${source}] ${line}\n`);
15200
- }
15201
- }
15202
- /**
15203
- * Print existing log history, applying filters. `--tail` then switches to
15204
- * follow mode and keeps printing new lines as the active file grows; rotation
15205
- * is not handled during the tail (a follow-up rotation will simply stop
15206
- * emitting new lines — operator can re-run the command).
15207
- */
15208
- async function showServiceLogs(options) {
15209
- if (!existsSync(LOG_DIR)) {
15210
- print.status("logs", `directory not found: ${LOG_DIR}`);
15211
- return;
15212
- }
15213
- const minLevel = options.level ? LEVEL_RANK[options.level] : void 0;
15214
- const cutoffMs = options.sinceMs !== void 0 ? Date.now() - options.sinceMs : void 0;
15215
- for (const f of listFallbackFiles()) await readFallbackFile(f, cutoffMs, options.json);
15216
- const files = listLogFilesNewestFirst().reverse();
15217
- for (const f of files) await readFileLines(f, minLevel, cutoffMs, options.json);
15218
- if (!options.tail) return;
15219
- if (!existsSync(PRIMARY_LOG)) print.status("tail", "waiting for client.log to appear...");
15220
- await new Promise((resolve) => {
15221
- let position = existsSync(PRIMARY_LOG) ? statSync(PRIMARY_LOG).size : 0;
15222
- const onChange = () => {
15223
- if (!existsSync(PRIMARY_LOG)) return;
15224
- const current = statSync(PRIMARY_LOG).size;
15225
- if (current < position) position = 0;
15226
- if (current <= position) return;
15227
- const stream = createReadStream(PRIMARY_LOG, {
15228
- start: position,
15229
- end: current - 1,
15230
- encoding: "utf8"
15231
- });
15232
- position = current;
15233
- createInterface({ input: stream }).on("line", (line) => {
15234
- const rendered = processLogLine(line, minLevel, cutoffMs, options.json);
15235
- if (rendered) process.stdout.write(rendered);
15236
- });
15237
- };
15238
- watchFile(PRIMARY_LOG, { interval: 500 }, onChange);
15239
- process.once("SIGINT", () => {
15240
- unwatchFile(PRIMARY_LOG, onChange);
15241
- resolve();
15242
- });
15243
- });
15244
- }
15245
- /** Validated flag parsers the CLI layer can reuse without re-doing the work. */
15246
- function validateLevel(value) {
15247
- if (value === void 0) return void 0;
15248
- const parsed = parseLogLevel$1(value);
15249
- if (parsed.fellBack) throw new Error(`invalid --level "${value}" (expected one of ${LOG_LEVELS$1.join(", ")})`);
15250
- return parsed.level;
15251
- }
15252
- //#endregion
15253
15156
  //#region src/core/update.ts
15254
15157
  const PACKAGE_NAME = "@agent-team-foundation/first-tree-hub";
15255
15158
  /**
@@ -15426,4 +15329,4 @@ function createExecuteUpdate({ managed }) {
15426
15329
  };
15427
15330
  }
15428
15331
  //#endregion
15429
- export { configureClientLoggerForService as $, checkServerHealth as A, createOwner as B, runMigrations as C, checkDocker as D, checkDatabase as E, isDockerAvailable as F, setJsonMode as G, resolveReplyToFromEnv as H, stopPostgres as I, FirstTreeHubSDK as J, status as K, ClientRuntime as L, checkWebSocket as M, printResults as N, checkNodeVersion as O, ensurePostgres as P, applyClientLoggerConfig as Q, handleClientOrgMismatch as R, uninstallClientService as S, checkClientConfig as T, blank as U, hasUser as V, print as W, SessionRegistry as X, SdkError as Y, cleanWorkspaces as Z, runHomeMigration as _, showServiceLogs as a, isServiceSupported as b, COMMAND_VERSION as c, promptMissingFields as d, formatCheckReport as f, saveOnboardState as g, onboardCreate as h, parseDuration as i, checkServerReachable as j, checkServerConfig as k, isInteractive as l, onboardCheck as m, declineUpdate as n, validateLevel as o, loadOnboardState as p, ClientOrgMismatchError$1 as q, promptUpdate as r, startServer as s, createExecuteUpdate as t, promptAddAgent as u, getClientServiceStatus as v, checkAgentConfigs as w, resolveCliInvocation as x, installClientService as y, rotateClientIdWithBackup as z };
15332
+ export { resolveCliInvocation as A, resolveReplyToFromEnv as B, checkServerHealth as C, getClientServiceStatus as D, printResults as E, ClientRuntime as F, ClientOrgMismatchError$1 as G, print as H, handleClientOrgMismatch as I, SessionRegistry as J, FirstTreeHubSDK as K, rotateClientIdWithBackup as L, ensurePostgres as M, isDockerAvailable as N, installClientService as O, stopPostgres as P, createOwner as R, checkServerConfig as S, checkWebSocket as T, setJsonMode as U, blank as V, status as W, applyClientLoggerConfig as X, cleanWorkspaces as Y, configureClientLoggerForService as Z, checkBackgroundService as _, COMMAND_VERSION as a, checkDocker as b, promptMissingFields as c, onboardCheck as d, onboardCreate as f, checkAgentConfigs as g, runMigrations as h, startServer as i, uninstallClientService as j, isServiceSupported as k, formatCheckReport as l, runHomeMigration as m, declineUpdate as n, isInteractive as o, saveOnboardState as p, SdkError as q, promptUpdate as r, promptAddAgent as s, createExecuteUpdate as t, loadOnboardState as u, checkClientConfig as v, checkServerReachable as w, checkNodeVersion as x, checkDatabase as y, hasUser as z };