@kontextmind/kxm 0.7.20 → 0.7.22

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,6 +1,6 @@
1
1
  {
2
2
  "name": "kxm-claude-plugin",
3
- "version": "0.7.20",
3
+ "version": "0.7.22",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "engines": {
@@ -2,9 +2,8 @@ import { spawnSync } from "node:child_process";
2
2
  import { createHash } from "node:crypto";
3
3
  import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { homedir, tmpdir } from "node:os";
5
- import { dirname, join, resolve } from "node:path";
5
+ import { join, resolve } from "node:path";
6
6
  import { createInterface } from "node:readline";
7
- import { fileURLToPath } from "node:url";
8
7
  import { verifyArtifactExists } from "../artifacts-exist.ts";
9
8
  import { parseWorkflowDefinitions } from "../workflow.ts";
10
9
  import { redactSecrets } from "../redact.ts";
@@ -99,8 +98,8 @@ import {
99
98
  warnIgnoredProjectUpdateYaml,
100
99
  refreshKxmUpdateNotice,
101
100
  } from "./hub.ts";
101
+ import { findKxmRepoRoot } from "../repo-root.ts";
102
102
 
103
- const repoRoot = resolve(fileURLToPath(new URL("../../../../", import.meta.url)));
104
103
 
105
104
  export function cliSpawn(runtime: Runtime, command: string, args: readonly string[], extra?: { timeout?: number }): CliSpawnResult {
106
105
  if (runtime.io.spawnSync) return runtime.io.spawnSync(command, args);
@@ -297,7 +296,7 @@ export async function cmdUpdate(runtime: Runtime, harness: string | undefined, o
297
296
  warnIgnoredProjectUpdateYaml(runtime);
298
297
  const probe = installProbeFrom(runtime);
299
298
  const classified = classifyInstallRoot(probe);
300
- const current = readInstalledKxmVersion(repoRoot);
299
+ const current = readInstalledKxmVersion(findKxmRepoRoot(import.meta.url));
301
300
  let notice: KxmUpdateNotice;
302
301
  let kindReport = classified;
303
302
  if (classified.kind === "source") {
@@ -1,6 +1,6 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { join, resolve } from "node:path";
3
- import { fileURLToPath } from "node:url";
3
+ import { findKxmRepoRoot } from "../repo-root.ts";
4
4
  import type { Command } from "commander";
5
5
  import { redactSecrets } from "../redact.ts";
6
6
  import { appendTelemetry, inferImprovementTarget, makeTelemetryEvent, telemetryPath } from "../telemetry.ts";
@@ -61,11 +61,9 @@ export interface Runtime extends CliContext, Required<Pick<GlobalOpts, "json" |
61
61
  fetchImpl: typeof fetch;
62
62
  }
63
63
 
64
- const repoRoot = resolve(fileURLToPath(new URL("../../../../", import.meta.url)));
65
-
66
64
  export function spawnScript(scriptName: string, extraEnv: NodeJS.ProcessEnv = {}): Promise<number> {
67
65
  return new Promise((resolveExit) => {
68
- const child = spawn(process.execPath, [join(repoRoot, "scripts", scriptName)], {
66
+ const child = spawn(process.execPath, [join(findKxmRepoRoot(import.meta.url), "scripts", scriptName)], {
69
67
  stdio: "inherit",
70
68
  env: { ...process.env, ...extraEnv },
71
69
  });
@@ -1,9 +1,8 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
3
3
  import { homedir, tmpdir } from "node:os";
4
- import { dirname, join, resolve } from "node:path";
4
+ import { join, resolve } from "node:path";
5
5
  import { createInterface } from "node:readline";
6
- import { fileURLToPath } from "node:url";
7
6
  import { createBackup, restoreBackup } from "../database.ts";
8
7
  import { refreshModelInventory } from "../model-inventory.ts";
9
8
  import { listInventoryModels, listRoleBindings, loadProducerPolicy, setModelState, updateProducer } from "../producers.ts";
@@ -48,8 +47,6 @@ export const vnextDriveCliSeams: {
48
47
  runtimeRequest?: typeof vnextRuntimeRequest;
49
48
  } = {};
50
49
 
51
- const repoRoot = resolve(fileURLToPath(new URL("../../../../", import.meta.url)));
52
-
53
50
  export function initPlanPayload(plan: VnextInitializationPlan): Record<string, unknown> {
54
51
  return {
55
52
  mode: plan.mode,
@@ -16,7 +16,9 @@ import { dirname, join, resolve } from "node:path";
16
16
  import { fileURLToPath } from "node:url";
17
17
  import { Command, CommanderError } from "commander";
18
18
  import { readInstalledKxmVersion } from "./kxm-update.ts";
19
+ import { findKxmRepoRoot } from "./repo-root.ts";
19
20
  import { HubClient } from "./client.ts";
21
+ import { readHubEnvRecord } from "./hub-env.ts";
20
22
  import { defaultProjectName } from "./project-name.ts";
21
23
  import {
22
24
  AGENT_COMMANDS_MAP,
@@ -155,7 +157,16 @@ export type { CliIo, CliSpawnResult };
155
157
  export { hubContextPost };
156
158
 
157
159
  const CLI_NAME = "kxm";
158
- const repoRoot = resolve(fileURLToPath(new URL("../../../", import.meta.url)));
160
+
161
+ /** Authenticate CLI hub clients: explicit KXM_AUTH_TOKEN first, then the
162
+ * persisted hub env record (project token for this project, then the admin
163
+ * token), matching the credential precedence `kxm hub start` prints. */
164
+ function resolveCliAuthToken(runtime: Runtime, project: string): string | undefined {
165
+ const envToken = runtime.env.KXM_AUTH_TOKEN?.trim();
166
+ if (envToken) return envToken;
167
+ const record = readHubEnvRecord(runtime.env);
168
+ return record?.projectTokens?.[project]?.trim() || record?.authToken?.trim() || undefined;
169
+ }
159
170
 
160
171
  const USAGE_ERROR_CODES = new Set([
161
172
  "commander.help",
@@ -183,12 +194,13 @@ async function ensureCliClient(runtime: Runtime): Promise<HubClient> {
183
194
  const project = defaultProjectName(runtime.cwd, runtime.env);
184
195
  const name = runtime.env.KXM_AGENT_NAME?.trim() || `cli-${process.pid}`;
185
196
  const purpose = runtime.env.KXM_AGENT_PURPOSE?.trim() || "CLI agent client";
186
- const authToken = runtime.env.KXM_AUTH_TOKEN?.trim();
197
+ const authToken = resolveCliAuthToken(runtime, project);
187
198
  const client = new HubClient({
188
199
  serverUrl,
189
200
  name,
190
201
  project,
191
202
  purpose,
203
+ fetchImpl: runtime.fetchImpl,
192
204
  ...(authToken ? { authToken } : {}),
193
205
  });
194
206
  await client.start(() => {});
@@ -321,7 +333,7 @@ function createProgram(ctx: CliContext, result: { code: number }): Command {
321
333
  const program = new Command(CLI_NAME);
322
334
  program
323
335
  .description("KontextMind local-first orchestration CLI")
324
- .version(readInstalledKxmVersion(repoRoot), "-V, --version", "Print the installed kxm version")
336
+ .version(readInstalledKxmVersion(findKxmRepoRoot(import.meta.url)), "-V, --version", "Print the installed kxm version")
325
337
  .exitOverride()
326
338
  .configureOutput({
327
339
  writeOut: (text) => ctx.io.stdout(text),
@@ -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 ?? {}) },
@@ -1,9 +1,9 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { closeSync, mkdirSync, openSync, readFileSync } from "node:fs";
3
- import { dirname, join, resolve } from "node:path";
4
- import { fileURLToPath } from "node:url";
3
+ import { join, resolve } from "node:path";
5
4
  import { probeHubHealth, readHubBinding } from "./hub-binding.ts";
6
5
  import { resolveHubCredentials } from "./hub-env.ts";
6
+ import { findKxmRepoRoot } from "./repo-root.ts";
7
7
  import { redactSecrets } from "./redact.ts";
8
8
 
9
9
  /** Background hub auto-start for harness extensions (Pi TUI, one-shot CLIs).
@@ -99,11 +99,11 @@ export function readLiveHubClaim(stateDir: string, processExists: (pid: number)
99
99
  };
100
100
  }
101
101
 
102
- /** The hub wrapper script next to the checked-out KXM repo. The extension and
102
+ /** The hub wrapper script at the KXM repo/package root. The extension and
103
103
  * the CLI both resolve the same path so auto-start launches the identical
104
104
  * supervision wrapper as `kxm hub start`. */
105
105
  export function defaultHubWrapperScriptPath(moduleUrl: string = import.meta.url): string {
106
- return resolve(dirname(fileURLToPath(moduleUrl)), "../../../scripts/kxm-hub.mjs");
106
+ return join(findKxmRepoRoot(moduleUrl), "scripts", "kxm-hub.mjs");
107
107
  }
108
108
 
109
109
  function defaultHubSpawner(command: string, args: readonly string[], options: HubSpawnOptions): HubSpawned {
@@ -7,7 +7,7 @@ import { AGENT_COMMANDS_MAP, enforceToolPolicy, getMcpTools, reconcileInbox } fr
7
7
  import { deliverInboxNotification } from "./inbox.ts";
8
8
  import type { HubEvent, MessageRecord } from "./protocol.ts";
9
9
 
10
- const VERSION = "0.7.20";
10
+ const VERSION = "0.7.22";
11
11
  const inbox = new Map<string, MessageRecord>();
12
12
  const notifiedInbox = new Set<string>();
13
13
  let meshClient: HubClient | undefined;
@@ -0,0 +1,29 @@
1
+ import { existsSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ /** Files that only exist at the KXM repo/package root. */
6
+ const ROOT_MARKERS = ["scripts/kxm-hub.mjs", "scripts/kxm.mjs"] as const;
7
+ const MAX_WALK_DEPTH = 10;
8
+
9
+ /**
10
+ * Locate the KXM repo/package root by walking up from `fromUrl` until a
11
+ * directory contains a root marker. Fixed-depth `../..` resolution breaks
12
+ * because the same module runs from both the source tree
13
+ * (`plugins/kxm/src/...`) and the flat bundled `plugins/kxm/dist/...`
14
+ * layout; marker discovery is correct in both, and in npm-installed
15
+ * layouts, which also ship `scripts/`.
16
+ */
17
+ export function findKxmRepoRoot(fromUrl: string = import.meta.url): string {
18
+ let dir = dirname(fileURLToPath(fromUrl));
19
+ for (let depth = 0; depth < MAX_WALK_DEPTH; depth += 1) {
20
+ if (ROOT_MARKERS.some((marker) => existsSync(join(dir, marker)))) return dir;
21
+ const parent = dirname(dir);
22
+ if (parent === dir) break;
23
+ dir = parent;
24
+ }
25
+ throw new Error(
26
+ `kxm: cannot locate the KXM repo root from ${fileURLToPath(fromUrl)} ` +
27
+ `(walked ${MAX_WALK_DEPTH} levels looking for ${ROOT_MARKERS[0]})`,
28
+ );
29
+ }
@@ -2,7 +2,6 @@ import { spawnSync } from "node:child_process";
2
2
  import { createHash } from "node:crypto";
3
3
  import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync } from "node:fs";
4
4
  import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
5
- import { fileURLToPath } from "node:url";
6
5
  import { Ajv2020, type ErrorObject, type ValidateFunction } from "ajv/dist/2020.js";
7
6
  import {
8
7
  RestrictedYamlError,
@@ -11,6 +10,7 @@ import {
11
10
  type VnextYamlLimits,
12
11
  } from "./restricted-yaml.mjs";
13
12
  import { resolveVnextTemplateBaseline } from "./vnext-template.ts";
13
+ import { findKxmRepoRoot } from "./repo-root.ts";
14
14
  import { BUILTIN_HARNESS_IDS, DEFAULT_HARNESS, validateHarnessModelPair } from "./vnext-harness.ts";
15
15
 
16
16
  export type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue };
@@ -86,8 +86,10 @@ export interface VnextInitializationPlan {
86
86
  configRevision?: string;
87
87
  }
88
88
 
89
- const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../../..");
90
- const DEFAULT_SCHEMA_DIR = join(PACKAGE_ROOT, "schemas", "vnext");
89
+ function defaultVnextSchemaDir(): string {
90
+ return join(findKxmRepoRoot(import.meta.url), "schemas", "vnext");
91
+ }
92
+ const DEFAULT_SCHEMA_DIR = defaultVnextSchemaDir();
91
93
  const RESOURCE_SCHEMA: Readonly<Record<VnextResourceKind, { identity: string; file: string }>> = Object.freeze({
92
94
  project: { identity: "kxm.project.v1", file: "project.schema.json" },
93
95
  repository: { identity: "kxm.repository.v1", file: "repository.schema.json" },
@@ -2,10 +2,9 @@ import { spawn } from "node:child_process";
2
2
  import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
3
3
  import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
5
- import { dirname, isAbsolute, join, resolve } from "node:path";
6
- import { fileURLToPath } from "node:url";
5
+ import { dirname, isAbsolute, join } from "node:path";
6
+ import { findKxmRepoRoot } from "./repo-root.ts";
7
7
  import { loadVnextProject, VnextConfigError, type VnextConfigOptions } from "./vnext-config.ts";
8
- const repoRoot = resolve(fileURLToPath(new URL("../../../", import.meta.url)));
9
8
  import {
10
9
  VnextRuntimeRegistry,
11
10
  projectRuntimeKey,
@@ -201,7 +200,7 @@ export async function ensureVnextSupervisor(
201
200
 
202
201
  // Auto-start: claim the singleton, spawn detached, wait for readiness.
203
202
  clearSupervisorError(paths);
204
- const scriptPath = join(repoRoot, "scripts", "kxm-runtime-supervisor.mjs");
203
+ const scriptPath = join(findKxmRepoRoot(import.meta.url), "scripts", "kxm-runtime-supervisor.mjs");
205
204
  const spawnImpl = options.spawnImpl ?? ((script: string, env: NodeJS.ProcessEnv): number => {
206
205
  const child = spawn(process.execPath, [script], {
207
206
  detached: true,