@kontextmind/kxm 0.7.21 → 0.7.23

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.
@@ -299,7 +299,7 @@ var HubClient = class {
299
299
  while (!this.stopped && this.agent) {
300
300
  this.eventsAbort = new AbortController();
301
301
  try {
302
- const response = await fetch(
302
+ const response = await (this.options.fetchImpl ?? fetch)(
303
303
  `${this.options.serverUrl.replace(/\/$/, "")}/v1/events?agentId=${encodeURIComponent(this.agent.id)}`,
304
304
  {
305
305
  headers: this.headers(),
@@ -376,7 +376,7 @@ var HubClient = class {
376
376
  const signal = init.signal ? AbortSignal.any([init.signal, timeoutSignal]) : timeoutSignal;
377
377
  let response;
378
378
  try {
379
- response = await fetch(`${this.options.serverUrl.replace(/\/$/, "")}${path}`, {
379
+ response = await (this.options.fetchImpl ?? fetch)(`${this.options.serverUrl.replace(/\/$/, "")}${path}`, {
380
380
  ...init,
381
381
  signal,
382
382
  headers: { ...this.headers(includeIdentity), ...init.headers ?? {} }
@@ -14834,7 +14834,7 @@ var HubClient = class {
14834
14834
  while (!this.stopped && this.agent) {
14835
14835
  this.eventsAbort = new AbortController();
14836
14836
  try {
14837
- const response = await fetch(
14837
+ const response = await (this.options.fetchImpl ?? fetch)(
14838
14838
  `${this.options.serverUrl.replace(/\/$/, "")}/v1/events?agentId=${encodeURIComponent(this.agent.id)}`,
14839
14839
  {
14840
14840
  headers: this.headers(),
@@ -14911,7 +14911,7 @@ var HubClient = class {
14911
14911
  const signal = init.signal ? AbortSignal.any([init.signal, timeoutSignal]) : timeoutSignal;
14912
14912
  let response;
14913
14913
  try {
14914
- response = await fetch(`${this.options.serverUrl.replace(/\/$/, "")}${path}`, {
14914
+ response = await (this.options.fetchImpl ?? fetch)(`${this.options.serverUrl.replace(/\/$/, "")}${path}`, {
14915
14915
  ...init,
14916
14916
  signal,
14917
14917
  headers: { ...this.headers(includeIdentity), ...init.headers ?? {} }
@@ -2984,7 +2984,7 @@ var require_compile = __commonJS({
2984
2984
  const schOrFunc = root.refs[ref];
2985
2985
  if (schOrFunc)
2986
2986
  return schOrFunc;
2987
- let _sch = resolve2.call(this, root, ref);
2987
+ let _sch = resolve3.call(this, root, ref);
2988
2988
  if (_sch === void 0) {
2989
2989
  const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
2990
2990
  const { schemaId } = this.opts;
@@ -3011,7 +3011,7 @@ var require_compile = __commonJS({
3011
3011
  function sameSchemaEnv(s1, s2) {
3012
3012
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
3013
3013
  }
3014
- function resolve2(root, ref) {
3014
+ function resolve3(root, ref) {
3015
3015
  let sch;
3016
3016
  while (typeof (sch = this.refs[ref]) == "string")
3017
3017
  ref = sch;
@@ -3836,7 +3836,7 @@ var require_fast_uri = __commonJS({
3836
3836
  }
3837
3837
  return uri;
3838
3838
  }
3839
- function resolve2(baseURI, relativeURI, options) {
3839
+ function resolve3(baseURI, relativeURI, options) {
3840
3840
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3841
3841
  const {
3842
3842
  parsed: baseParsed,
@@ -4198,7 +4198,7 @@ var require_fast_uri = __commonJS({
4198
4198
  var fastUri = {
4199
4199
  SCHEMES,
4200
4200
  normalize,
4201
- resolve: resolve2,
4201
+ resolve: resolve3,
4202
4202
  resolveComponent,
4203
4203
  equal,
4204
4204
  serialize,
@@ -14505,7 +14505,7 @@ var Protocol = class {
14505
14505
  return;
14506
14506
  }
14507
14507
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
14508
- await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
14508
+ await new Promise((resolve3) => setTimeout(resolve3, pollInterval));
14509
14509
  options?.signal?.throwIfAborted();
14510
14510
  }
14511
14511
  } catch (error2) {
@@ -14522,7 +14522,7 @@ var Protocol = class {
14522
14522
  */
14523
14523
  request(request, resultSchema, options) {
14524
14524
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
14525
- return new Promise((resolve2, reject) => {
14525
+ return new Promise((resolve3, reject) => {
14526
14526
  const earlyReject = (error2) => {
14527
14527
  reject(error2);
14528
14528
  };
@@ -14600,7 +14600,7 @@ var Protocol = class {
14600
14600
  if (!parseResult.success) {
14601
14601
  reject(parseResult.error);
14602
14602
  } else {
14603
- resolve2(parseResult.data);
14603
+ resolve3(parseResult.data);
14604
14604
  }
14605
14605
  } catch (error2) {
14606
14606
  reject(error2);
@@ -14861,12 +14861,12 @@ var Protocol = class {
14861
14861
  }
14862
14862
  } catch {
14863
14863
  }
14864
- return new Promise((resolve2, reject) => {
14864
+ return new Promise((resolve3, reject) => {
14865
14865
  if (signal.aborted) {
14866
14866
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
14867
14867
  return;
14868
14868
  }
14869
- const timeoutId = setTimeout(resolve2, interval);
14869
+ const timeoutId = setTimeout(resolve3, interval);
14870
14870
  signal.addEventListener("abort", () => {
14871
14871
  clearTimeout(timeoutId);
14872
14872
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -15742,12 +15742,12 @@ var StdioServerTransport = class {
15742
15742
  this.onclose?.();
15743
15743
  }
15744
15744
  send(message) {
15745
- return new Promise((resolve2) => {
15745
+ return new Promise((resolve3) => {
15746
15746
  const json = serializeMessage(message);
15747
15747
  if (this._stdout.write(json)) {
15748
- resolve2();
15748
+ resolve3();
15749
15749
  } else {
15750
- this._stdout.once("drain", resolve2);
15750
+ this._stdout.once("drain", resolve3);
15751
15751
  }
15752
15752
  });
15753
15753
  }
@@ -16022,7 +16022,7 @@ var HubClient = class {
16022
16022
  if (signal?.aborted) throw new MeshWaitError("aborted", messageId);
16023
16023
  const message = await this.getMessage(messageId);
16024
16024
  if (["replied", "cancelled", "expired", "error"].includes(message.status)) return message;
16025
- await new Promise((resolve2, reject) => {
16025
+ await new Promise((resolve3, reject) => {
16026
16026
  const onAbort = () => {
16027
16027
  signal?.removeEventListener("abort", onAbort);
16028
16028
  clearTimeout(timer);
@@ -16030,7 +16030,7 @@ var HubClient = class {
16030
16030
  };
16031
16031
  const timer = setTimeout(() => {
16032
16032
  signal?.removeEventListener("abort", onAbort);
16033
- resolve2();
16033
+ resolve3();
16034
16034
  }, Math.min(500, Math.max(1, deadline - Date.now())));
16035
16035
  signal?.addEventListener("abort", onAbort, { once: true });
16036
16036
  if (signal?.aborted) onAbort();
@@ -16054,7 +16054,7 @@ var HubClient = class {
16054
16054
  while (!this.stopped && this.agent) {
16055
16055
  this.eventsAbort = new AbortController();
16056
16056
  try {
16057
- const response = await fetch(
16057
+ const response = await (this.options.fetchImpl ?? fetch)(
16058
16058
  `${this.options.serverUrl.replace(/\/$/, "")}/v1/events?agentId=${encodeURIComponent(this.agent.id)}`,
16059
16059
  {
16060
16060
  headers: this.headers(),
@@ -16085,7 +16085,7 @@ var HubClient = class {
16085
16085
  } catch (error2) {
16086
16086
  if (this.stopped || error2 instanceof Error && error2.name === "AbortError") return;
16087
16087
  }
16088
- if (!this.stopped) await new Promise((resolve2) => setTimeout(resolve2, this.options.reconnectMs));
16088
+ if (!this.stopped) await new Promise((resolve3) => setTimeout(resolve3, this.options.reconnectMs));
16089
16089
  }
16090
16090
  }
16091
16091
  headers(includeIdentity = true) {
@@ -16131,7 +16131,7 @@ var HubClient = class {
16131
16131
  const signal = init.signal ? AbortSignal.any([init.signal, timeoutSignal]) : timeoutSignal;
16132
16132
  let response;
16133
16133
  try {
16134
- response = await fetch(`${this.options.serverUrl.replace(/\/$/, "")}${path}`, {
16134
+ response = await (this.options.fetchImpl ?? fetch)(`${this.options.serverUrl.replace(/\/$/, "")}${path}`, {
16135
16135
  ...init,
16136
16136
  signal,
16137
16137
  headers: { ...this.headers(includeIdentity), ...init.headers ?? {} }
@@ -16166,14 +16166,88 @@ var HubClient = class {
16166
16166
  }
16167
16167
  };
16168
16168
 
16169
+ // plugins/kxm/src/hub-env.ts
16170
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
16171
+ import { homedir } from "node:os";
16172
+ import { dirname, isAbsolute, join, resolve } from "node:path";
16173
+ var HUB_ENV_SCHEMA = "kxm.hub-env.v1";
16174
+ var HubEnvError = class extends Error {
16175
+ constructor(message) {
16176
+ super(message);
16177
+ this.name = "HubEnvError";
16178
+ }
16179
+ };
16180
+ function resolveUserStateRoot(env) {
16181
+ const explicit = env.KXM_STATE_HOME?.trim();
16182
+ if (explicit) {
16183
+ if (!isAbsolute(explicit)) throw new HubEnvError("local_state_root_not_absolute");
16184
+ return resolve(explicit);
16185
+ }
16186
+ if (process.platform === "win32") {
16187
+ const localAppData = env.LOCALAPPDATA?.trim();
16188
+ const base2 = localAppData && isAbsolute(localAppData) ? localAppData : join(homedir(), "AppData", "Local");
16189
+ return resolve(base2, "KXM");
16190
+ }
16191
+ if (process.platform === "darwin") return resolve(homedir(), "Library", "Application Support", "KXM");
16192
+ const xdgState = env.XDG_STATE_HOME?.trim();
16193
+ const base = xdgState && isAbsolute(xdgState) ? xdgState : join(homedir(), ".local", "state");
16194
+ return resolve(base, "kxm");
16195
+ }
16196
+ function hubEnvFile(env = process.env) {
16197
+ return join(resolveUserStateRoot(env), "hub-env.json");
16198
+ }
16199
+ function readHubEnvRecord(env = process.env) {
16200
+ const file = hubEnvFile(env);
16201
+ if (!existsSync(file)) return void 0;
16202
+ let parsed;
16203
+ try {
16204
+ parsed = JSON.parse(readFileSync(file, "utf8"));
16205
+ } catch (error2) {
16206
+ throw new HubEnvError(`hub env file is malformed at ${file}: ${error2 instanceof Error ? error2.message : String(error2)}`);
16207
+ }
16208
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
16209
+ throw new HubEnvError(`hub env file is malformed at ${file}: expected a JSON object`);
16210
+ }
16211
+ const record2 = parsed;
16212
+ if (record2.schema !== HUB_ENV_SCHEMA) {
16213
+ throw new HubEnvError(`hub env file at ${file} has unsupported schema ${String(record2.schema)}`);
16214
+ }
16215
+ if (record2.authToken !== void 0 && (typeof record2.authToken !== "string" || record2.authToken.trim().length === 0)) {
16216
+ throw new HubEnvError(`hub env file at ${file} has an invalid authToken`);
16217
+ }
16218
+ if (record2.projectTokens !== void 0) {
16219
+ const tokens = record2.projectTokens;
16220
+ if (!tokens || typeof tokens !== "object" || Array.isArray(tokens)) {
16221
+ throw new HubEnvError(`hub env file at ${file} has invalid projectTokens`);
16222
+ }
16223
+ for (const [project, token] of Object.entries(tokens)) {
16224
+ if (!project.trim() || typeof token !== "string" || !token.trim()) {
16225
+ throw new HubEnvError(`hub env file at ${file} has an empty project name or token`);
16226
+ }
16227
+ }
16228
+ }
16229
+ return {
16230
+ schema: HUB_ENV_SCHEMA,
16231
+ createdAt: typeof record2.createdAt === "string" ? record2.createdAt : "",
16232
+ ...record2.authToken !== void 0 ? { authToken: record2.authToken } : {},
16233
+ ...record2.projectTokens !== void 0 ? { projectTokens: record2.projectTokens } : {}
16234
+ };
16235
+ }
16236
+ function resolveClientHubAuthToken(env, project) {
16237
+ const envToken = env.KXM_AUTH_TOKEN?.trim();
16238
+ if (envToken) return envToken;
16239
+ const record2 = readHubEnvRecord(env);
16240
+ return record2?.projectTokens?.[project]?.trim() || record2?.authToken?.trim() || void 0;
16241
+ }
16242
+
16169
16243
  // plugins/kxm/src/project-name.ts
16170
- import { readFileSync } from "node:fs";
16171
- import { basename, join } from "node:path";
16244
+ import { readFileSync as readFileSync2 } from "node:fs";
16245
+ import { basename, join as join2 } from "node:path";
16172
16246
  function defaultProjectName(cwd, env = process.env) {
16173
16247
  const fromEnv = env.KXM_PROJECT?.trim();
16174
16248
  if (fromEnv) return fromEnv;
16175
16249
  try {
16176
- const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
16250
+ const pkg = JSON.parse(readFileSync2(join2(cwd, "package.json"), "utf8"));
16177
16251
  if (typeof pkg.name === "string" && pkg.name.trim().length > 0) return pkg.name.trim();
16178
16252
  } catch {
16179
16253
  }
@@ -16181,9 +16255,9 @@ function defaultProjectName(cwd, env = process.env) {
16181
16255
  }
16182
16256
 
16183
16257
  // plugins/kxm/src/commands.ts
16184
- import { chmodSync, existsSync, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "node:fs";
16185
- import { homedir } from "node:os";
16186
- import { dirname, join as join2, resolve } from "node:path";
16258
+ import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
16259
+ import { homedir as homedir2 } from "node:os";
16260
+ import { dirname as dirname2, join as join3, resolve as resolve2 } from "node:path";
16187
16261
  function requiredString(value, name) {
16188
16262
  if (typeof value !== "string" || value.trim() === "") throw new Error(`${name} is required`);
16189
16263
  return value.trim();
@@ -16918,11 +16992,11 @@ function parseSessionToken(token) {
16918
16992
  return void 0;
16919
16993
  }
16920
16994
  function resolveUserConfigDirectory(overrideDir) {
16921
- if (overrideDir) return resolve(overrideDir);
16922
- return resolve(process.env.KXM_USER_CONFIG_DIR?.trim() || join2(homedir(), ".config", "kxm"));
16995
+ if (overrideDir) return resolve2(overrideDir);
16996
+ return resolve2(process.env.KXM_USER_CONFIG_DIR?.trim() || join3(homedir2(), ".config", "kxm"));
16923
16997
  }
16924
16998
  function sessionTokenPath(userConfigDir) {
16925
- return join2(resolveUserConfigDirectory(userConfigDir), "session.token");
16999
+ return join3(resolveUserConfigDirectory(userConfigDir), "session.token");
16926
17000
  }
16927
17001
  function matchToolPattern(pattern, toolName) {
16928
17002
  if (pattern === "*" || pattern === toolName) return true;
@@ -17015,10 +17089,10 @@ function enforceToolPolicy(commandName, env = process.env, options) {
17015
17089
  return { allowed: true };
17016
17090
  }
17017
17091
  const tokenFile = sessionTokenPath(env.KXM_USER_CONFIG_DIR);
17018
- if (existsSync(tokenFile)) {
17092
+ if (existsSync2(tokenFile)) {
17019
17093
  let tokenRaw;
17020
17094
  try {
17021
- tokenRaw = readFileSync2(tokenFile, "utf8").trim();
17095
+ tokenRaw = readFileSync3(tokenFile, "utf8").trim();
17022
17096
  } catch {
17023
17097
  return { allowed: false, error: "session_token_invalid", detail: "Session token file on disk could not be read" };
17024
17098
  }
@@ -17047,7 +17121,7 @@ async function deliverInboxNotification(messageId, delivered, notify) {
17047
17121
  }
17048
17122
 
17049
17123
  // plugins/kxm/src/mcp-server.ts
17050
- var VERSION = "0.7.21";
17124
+ var VERSION = "0.7.23";
17051
17125
  var inbox = /* @__PURE__ */ new Map();
17052
17126
  var notifiedInbox = /* @__PURE__ */ new Set();
17053
17127
  var meshClient;
@@ -17071,9 +17145,6 @@ var mcp = new Server(
17071
17145
  function textResult(value) {
17072
17146
  return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
17073
17147
  }
17074
- function optionalString2(value) {
17075
- return typeof value === "string" && value.trim() ? value.trim() : void 0;
17076
- }
17077
17148
  function asRecord2(value) {
17078
17149
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
17079
17150
  }
@@ -17115,12 +17186,13 @@ async function ensureClient() {
17115
17186
  if (starting) return starting;
17116
17187
  starting = (async () => {
17117
17188
  const projectDir = process.env.KXM_PROJECT_DIR || process.env.CLAUDE_PROJECT_DIR || process.cwd();
17118
- const authToken = optionalString2(process.env.KXM_AUTH_TOKEN);
17189
+ const project = defaultProjectName(projectDir, process.env);
17190
+ const authToken = resolveClientHubAuthToken(process.env, project);
17119
17191
  const candidate = new HubClient({
17120
17192
  serverUrl: process.env.KXM_SERVER_URL?.trim() || "http://127.0.0.1:7331",
17121
17193
  name: process.env.KXM_AGENT_NAME?.trim() || `claude-${process.pid}`,
17122
17194
  purpose: process.env.KXM_AGENT_PURPOSE?.trim() || "Claude Code implementation and review agent",
17123
- project: defaultProjectName(projectDir, process.env),
17195
+ project,
17124
17196
  model: "claude-code",
17125
17197
  ...authToken ? { authToken } : {}
17126
17198
  });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kxm-claude-plugin",
3
- "version": "0.7.21",
3
+ "version": "0.7.23",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "engines": {
@@ -3,6 +3,7 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync
3
3
  import { join, resolve } from "node:path";
4
4
  import { redactSecrets } from "../redact.ts";
5
5
  import { defaultProjectName } from "../project-name.ts";
6
+ import { resolveClientHubAuthToken } from "../hub-env.ts";
6
7
  import { agentWorker, type Worker } from "../envelope.ts";
7
8
  import { MESH_TUI_PANELS, runMeshTui, type MeshTuiPanel } from "../tui.ts";
8
9
  import { formatSessionBriefText, loadSessionBriefAsync, type SessionHubStatus } from "../session-work.ts";
@@ -132,7 +133,7 @@ export async function cmdDash(runtime: Runtime, options: { screen?: string | und
132
133
  return 2;
133
134
  }
134
135
  const project = defaultProjectName(runtime.dirs.workdir, runtime.env) || "project";
135
- const authToken = runtime.env.KXM_AUTH_TOKEN?.trim();
136
+ const authToken = resolveClientHubAuthToken(runtime.env, project);
136
137
  return await runMeshTui({
137
138
  serverUrl: runtime.serverUrl,
138
139
  dataPath,
@@ -18,6 +18,7 @@ import { Command, CommanderError } from "commander";
18
18
  import { readInstalledKxmVersion } from "./kxm-update.ts";
19
19
  import { findKxmRepoRoot } from "./repo-root.ts";
20
20
  import { HubClient } from "./client.ts";
21
+ import { resolveClientHubAuthToken } from "./hub-env.ts";
21
22
  import { defaultProjectName } from "./project-name.ts";
22
23
  import {
23
24
  AGENT_COMMANDS_MAP,
@@ -183,12 +184,13 @@ async function ensureCliClient(runtime: Runtime): Promise<HubClient> {
183
184
  const project = defaultProjectName(runtime.cwd, runtime.env);
184
185
  const name = runtime.env.KXM_AGENT_NAME?.trim() || `cli-${process.pid}`;
185
186
  const purpose = runtime.env.KXM_AGENT_PURPOSE?.trim() || "CLI agent client";
186
- const authToken = runtime.env.KXM_AUTH_TOKEN?.trim();
187
+ const authToken = resolveClientHubAuthToken(runtime.env, project);
187
188
  const client = new HubClient({
188
189
  serverUrl,
189
190
  name,
190
191
  project,
191
192
  purpose,
193
+ fetchImpl: runtime.fetchImpl,
192
194
  ...(authToken ? { authToken } : {}),
193
195
  });
194
196
  await client.start(() => {});
@@ -55,6 +55,8 @@ export interface HubClientOptions {
55
55
  heartbeatMs?: number;
56
56
  reconnectMs?: number;
57
57
  requestTimeoutMs?: number;
58
+ /** Injectable fetch for CLI/tests; defaults to global fetch. */
59
+ fetchImpl?: typeof fetch;
58
60
  }
59
61
 
60
62
  export interface SendOptions {
@@ -493,7 +495,7 @@ export class HubClient {
493
495
  while (!this.stopped && this.agent) {
494
496
  this.eventsAbort = new AbortController();
495
497
  try {
496
- const response = await fetch(
498
+ const response = await (this.options.fetchImpl ?? fetch)(
497
499
  `${this.options.serverUrl.replace(/\/$/, "")}/v1/events?agentId=${encodeURIComponent(this.agent.id)}`,
498
500
  {
499
501
  headers: this.headers(),
@@ -578,7 +580,7 @@ export class HubClient {
578
580
  const signal = init.signal ? AbortSignal.any([init.signal, timeoutSignal]) : timeoutSignal;
579
581
  let response: Response;
580
582
  try {
581
- response = await fetch(`${this.options.serverUrl.replace(/\/$/, "")}${path}`, {
583
+ response = await (this.options.fetchImpl ?? fetch)(`${this.options.serverUrl.replace(/\/$/, "")}${path}`, {
582
584
  ...init,
583
585
  signal,
584
586
  headers: { ...this.headers(includeIdentity), ...(init.headers ?? {}) },
@@ -191,3 +191,18 @@ export function resolveHubCredentials(options: ResolveHubCredentialsOptions = {}
191
191
  written: needsPersist === true,
192
192
  };
193
193
  }
194
+
195
+ /** Read-only token for one-shot hub clients (CLI, MCP server, dashboards).
196
+ *
197
+ * Precedence: explicit KXM_AUTH_TOKEN, then the persisted project token for
198
+ * the resolved project, then the persisted admin token. Never generates or
199
+ * writes; a malformed persisted record fails closed with HubEnvError. This
200
+ * matches the credential precedence `kxm hub start` announces, so a hub
201
+ * started fresh (generated token persisted) accepts authenticated client
202
+ * commands without the operator exporting the token. */
203
+ export function resolveClientHubAuthToken(env: NodeJS.ProcessEnv, project: string): string | undefined {
204
+ const envToken = env.KXM_AUTH_TOKEN?.trim();
205
+ if (envToken) return envToken;
206
+ const record = readHubEnvRecord(env);
207
+ return record?.projectTokens?.[project]?.trim() || record?.authToken?.trim() || undefined;
208
+ }
@@ -2,12 +2,13 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
3
  import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
4
4
  import { HubClient } from "./client.ts";
5
+ import { resolveClientHubAuthToken } from "./hub-env.ts";
5
6
  import { defaultProjectName } from "./project-name.ts";
6
7
  import { AGENT_COMMANDS_MAP, enforceToolPolicy, getMcpTools, reconcileInbox } from "./commands.ts";
7
8
  import { deliverInboxNotification } from "./inbox.ts";
8
9
  import type { HubEvent, MessageRecord } from "./protocol.ts";
9
10
 
10
- const VERSION = "0.7.21";
11
+ const VERSION = "0.7.23";
11
12
  const inbox = new Map<string, MessageRecord>();
12
13
  const notifiedInbox = new Set<string>();
13
14
  let meshClient: HubClient | undefined;
@@ -34,10 +35,6 @@ function textResult(value: unknown) {
34
35
  return { content: [{ type: "text" as const, text: JSON.stringify(value, null, 2) }] };
35
36
  }
36
37
 
37
- function optionalString(value: unknown): string | undefined {
38
- return typeof value === "string" && value.trim() ? value.trim() : undefined;
39
- }
40
-
41
38
  function asRecord(value: unknown): Record<string, unknown> {
42
39
  return value && typeof value === "object" && !Array.isArray(value)
43
40
  ? (value as Record<string, unknown>)
@@ -83,12 +80,13 @@ async function ensureClient(): Promise<HubClient> {
83
80
  if (starting) return starting;
84
81
  starting = (async () => {
85
82
  const projectDir = process.env.KXM_PROJECT_DIR || process.env.CLAUDE_PROJECT_DIR || process.cwd();
86
- const authToken = optionalString(process.env.KXM_AUTH_TOKEN);
83
+ const project = defaultProjectName(projectDir, process.env);
84
+ const authToken = resolveClientHubAuthToken(process.env, project);
87
85
  const candidate = new HubClient({
88
86
  serverUrl: process.env.KXM_SERVER_URL?.trim() || "http://127.0.0.1:7331",
89
87
  name: process.env.KXM_AGENT_NAME?.trim() || `claude-${process.pid}`,
90
88
  purpose: process.env.KXM_AGENT_PURPOSE?.trim() || "Claude Code implementation and review agent",
91
- project: defaultProjectName(projectDir, process.env),
89
+ project,
92
90
  model: "claude-code",
93
91
  ...(authToken ? { authToken } : {}),
94
92
  });