@kontextmind/kxm 0.7.82 → 0.7.84

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.
@@ -11,7 +11,7 @@
11
11
  "name": "kxm",
12
12
  "source": "./plugins/kxm",
13
13
  "description": "Durable workflows, peer agents, and kxm tui",
14
- "version": "0.7.82",
14
+ "version": "0.7.84",
15
15
  "category": "development",
16
16
  "tags": ["kxm", "multi-agent", "workflows", "mcp"]
17
17
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kontextmind/kxm",
3
- "version": "0.7.82",
3
+ "version": "0.7.84",
4
4
  "description": "KXM local-first multi-agent orchestration and operator dashboard",
5
5
  "type": "module",
6
6
  "author": "KontextMind",
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "kxm",
4
4
  "displayName": "KXM",
5
- "version": "0.7.82",
5
+ "version": "0.7.84",
6
6
  "description": "Headless multi-agent orchestration, durable workflows, and a live operator dashboard for Pi and Claude Code",
7
7
  "author": {
8
8
  "name": "KontextMind",
@@ -18176,6 +18176,13 @@ function readInstalledKxmVersion(root) {
18176
18176
  }
18177
18177
  return pkg.version;
18178
18178
  }
18179
+ function installedKxmVersion(root) {
18180
+ try {
18181
+ return readInstalledKxmVersion(root);
18182
+ } catch {
18183
+ return void 0;
18184
+ }
18185
+ }
18179
18186
  function parseSemver(value) {
18180
18187
  const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(value.trim());
18181
18188
  if (!match) return void 0;
@@ -18335,7 +18342,7 @@ import { dirname, join as join2 } from "node:path";
18335
18342
  import { fileURLToPath } from "node:url";
18336
18343
  var ROOT_MARKERS = ["scripts/kxm-hub.mjs", "scripts/kxm.mjs"];
18337
18344
  var MAX_WALK_DEPTH = 10;
18338
- function findKxmRepoRoot(fromUrl = import.meta.url) {
18345
+ function tryFindKxmRepoRoot(fromUrl = import.meta.url) {
18339
18346
  let dir = dirname(fileURLToPath(fromUrl));
18340
18347
  for (let depth = 0; depth < MAX_WALK_DEPTH; depth += 1) {
18341
18348
  if (ROOT_MARKERS.some((marker) => existsSync2(join2(dir, marker)))) return dir;
@@ -18343,6 +18350,11 @@ function findKxmRepoRoot(fromUrl = import.meta.url) {
18343
18350
  if (parent === dir) break;
18344
18351
  dir = parent;
18345
18352
  }
18353
+ return void 0;
18354
+ }
18355
+ function findKxmRepoRoot(fromUrl = import.meta.url) {
18356
+ const found = tryFindKxmRepoRoot(fromUrl);
18357
+ if (found !== void 0) return found;
18346
18358
  throw new Error(
18347
18359
  `kxm: cannot locate the KXM repo root from ${fileURLToPath(fromUrl)} (walked ${MAX_WALK_DEPTH} levels looking for ${ROOT_MARKERS[0]})`
18348
18360
  );
@@ -44588,14 +44600,30 @@ async function hubGet(url, fetchImpl) {
44588
44600
  }
44589
44601
  function installProbeFrom(runtime) {
44590
44602
  const partial = runtime.io.installProbe ?? {};
44603
+ const moduleDir = partial.moduleDir ?? dirname20(fileURLToPath3(import.meta.url));
44591
44604
  return {
44592
- moduleDir: partial.moduleDir ?? dirname20(fileURLToPath3(import.meta.url)),
44593
- repoRoot: partial.repoRoot ?? resolve24("."),
44605
+ moduleDir,
44606
+ // Classify the install that is **running**, never the directory the caller
44607
+ // happens to be standing in. Deriving this from cwd made `kxm update --kxm`
44608
+ // answer "kxm is running from source at <cwd>; update it with git pull there"
44609
+ // on an npm-global install whenever the operator ran it inside a kxm
44610
+ // checkout — and a git pull there deploys nothing, because the services exec
44611
+ // the installed copy. An unrecognisable layout falls back to the module
44612
+ // directory, which classifies as `unknown` ("update it the way it was
44613
+ // installed") rather than inventing a source install.
44614
+ repoRoot: partial.repoRoot ?? tryFindKxmRepoRoot(import.meta.url) ?? moduleDir,
44594
44615
  homeDir: partial.homeDir ?? homedir7(),
44595
44616
  platform: partial.platform ?? process.platform,
44596
44617
  env: partial.env ?? runtime.env
44597
44618
  };
44598
44619
  }
44620
+ function moduleInstallRoot() {
44621
+ try {
44622
+ return findKxmRepoRoot(import.meta.url);
44623
+ } catch {
44624
+ return dirname20(fileURLToPath3(import.meta.url));
44625
+ }
44626
+ }
44599
44627
  function warnIgnoredProjectUpdateYaml(runtime) {
44600
44628
  const projectFile = join41(runtime.dirs.workspace, "update.yaml");
44601
44629
  if (!existsSync31(projectFile)) return;
@@ -44603,11 +44631,20 @@ function warnIgnoredProjectUpdateYaml(runtime) {
44603
44631
  runtime.io.stderr(`kxm: ignoring .kxm/update.yaml in ${runtime.dirs.workdir}; update settings are read only from ${userFile}
44604
44632
  `);
44605
44633
  }
44606
- async function refreshKxmUpdateNotice(runtime, config) {
44634
+ async function refreshKxmUpdateNotice(runtime, config, current) {
44607
44635
  const resolved = config ?? loadKxmUpdateConfig(runtime.env);
44608
- const current = readInstalledKxmVersion(resolve24("."));
44636
+ const installed = current ?? installedKxmVersion(findKxmRepoRoot(import.meta.url)) ?? installedKxmVersion(moduleInstallRoot());
44637
+ if (installed === void 0) {
44638
+ return {
44639
+ current: "unknown",
44640
+ available: false,
44641
+ auto: false,
44642
+ source: resolved.source,
44643
+ message: `kxm update check skipped: the installed version is unreadable at ${moduleInstallRoot()}`
44644
+ };
44645
+ }
44609
44646
  const fetched = await fetchLatestKxmVersion(resolved.source, runtime.env, runtime.fetchImpl);
44610
- const notice = noticeFromVersions(current, fetched.latest, resolved, fetched.error, fetched.asset);
44647
+ const notice = noticeFromVersions(installed, fetched.latest, resolved, fetched.error, fetched.asset);
44611
44648
  writeUpdateCache(runtime.dirs.state, notice);
44612
44649
  return notice;
44613
44650
  }
@@ -47128,16 +47165,16 @@ async function cmdUpdate(runtime, harness, options) {
47128
47165
  warnIgnoredProjectUpdateYaml(runtime);
47129
47166
  const probe = installProbeFrom(runtime);
47130
47167
  const classified = classifyInstallRoot(probe);
47131
- const current = readInstalledKxmVersion(findKxmRepoRoot(import.meta.url));
47168
+ const current = installedKxmVersion(classified.root) ?? installedKxmVersion(findKxmRepoRoot(import.meta.url));
47132
47169
  let notice;
47133
47170
  let kindReport = classified;
47134
47171
  if (classified.kind === "source") {
47135
47172
  if (options.check) {
47136
- const message = `kxm ${current} (running from source at ${classified.root})`;
47173
+ const message = `kxm ${current ?? "unknown"} (running from source at ${classified.root})`;
47137
47174
  print(runtime.io, runtime.json, {
47138
47175
  ok: true,
47139
47176
  command: "update check",
47140
- current,
47177
+ current: current ?? "unknown",
47141
47178
  available: false,
47142
47179
  auto: false,
47143
47180
  source: "github",
@@ -47159,15 +47196,15 @@ async function cmdUpdate(runtime, harness, options) {
47159
47196
  return 2;
47160
47197
  }
47161
47198
  notice = {
47162
- current,
47199
+ current: current ?? "unknown",
47163
47200
  available: false,
47164
47201
  auto: false,
47165
47202
  source: "github",
47166
- message: `kxm ${current} (running from source)`
47203
+ message: `kxm ${current ?? "unknown"} (running from source)`
47167
47204
  };
47168
47205
  } else {
47169
47206
  try {
47170
- notice = await refreshKxmUpdateNotice(runtime);
47207
+ notice = await refreshKxmUpdateNotice(runtime, void 0, current);
47171
47208
  } catch (error) {
47172
47209
  if (error instanceof KxmUpdateConfigError) {
47173
47210
  print(runtime.io, runtime.json, { ok: false, command: "update", error: error.code, ...installKindPayload(classified) }, error.message);
@@ -16238,7 +16238,7 @@ import { dirname as dirname5, join as join5 } from "node:path";
16238
16238
  import { fileURLToPath } from "node:url";
16239
16239
  var ROOT_MARKERS = ["scripts/kxm-hub.mjs", "scripts/kxm.mjs"];
16240
16240
  var MAX_WALK_DEPTH = 10;
16241
- function findKxmRepoRoot(fromUrl = import.meta.url) {
16241
+ function tryFindKxmRepoRoot(fromUrl = import.meta.url) {
16242
16242
  let dir = dirname5(fileURLToPath(fromUrl));
16243
16243
  for (let depth = 0; depth < MAX_WALK_DEPTH; depth += 1) {
16244
16244
  if (ROOT_MARKERS.some((marker) => existsSync5(join5(dir, marker)))) return dir;
@@ -16246,6 +16246,11 @@ function findKxmRepoRoot(fromUrl = import.meta.url) {
16246
16246
  if (parent === dir) break;
16247
16247
  dir = parent;
16248
16248
  }
16249
+ return void 0;
16250
+ }
16251
+ function findKxmRepoRoot(fromUrl = import.meta.url) {
16252
+ const found = tryFindKxmRepoRoot(fromUrl);
16253
+ if (found !== void 0) return found;
16249
16254
  throw new Error(
16250
16255
  `kxm: cannot locate the KXM repo root from ${fileURLToPath(fromUrl)} (walked ${MAX_WALK_DEPTH} levels looking for ${ROOT_MARKERS[0]})`
16251
16256
  );
@@ -17182,7 +17182,7 @@ async function deliverInboxNotification(messageId, delivered, notify) {
17182
17182
  }
17183
17183
 
17184
17184
  // plugins/kxm/src/mcp-server.ts
17185
- var VERSION = "0.7.82";
17185
+ var VERSION = "0.7.84";
17186
17186
  var inbox = /* @__PURE__ */ new Map();
17187
17187
  var notifiedInbox = /* @__PURE__ */ new Set();
17188
17188
  var meshClient;
@@ -14791,7 +14791,7 @@ import { dirname, join } from "node:path";
14791
14791
  import { fileURLToPath } from "node:url";
14792
14792
  var ROOT_MARKERS = ["scripts/kxm-hub.mjs", "scripts/kxm.mjs"];
14793
14793
  var MAX_WALK_DEPTH = 10;
14794
- function findKxmRepoRoot(fromUrl = import.meta.url) {
14794
+ function tryFindKxmRepoRoot(fromUrl = import.meta.url) {
14795
14795
  let dir = dirname(fileURLToPath(fromUrl));
14796
14796
  for (let depth = 0; depth < MAX_WALK_DEPTH; depth += 1) {
14797
14797
  if (ROOT_MARKERS.some((marker) => existsSync(join(dir, marker)))) return dir;
@@ -14799,6 +14799,11 @@ function findKxmRepoRoot(fromUrl = import.meta.url) {
14799
14799
  if (parent === dir) break;
14800
14800
  dir = parent;
14801
14801
  }
14802
+ return void 0;
14803
+ }
14804
+ function findKxmRepoRoot(fromUrl = import.meta.url) {
14805
+ const found = tryFindKxmRepoRoot(fromUrl);
14806
+ if (found !== void 0) return found;
14802
14807
  throw new Error(
14803
14808
  `kxm: cannot locate the KXM repo root from ${fileURLToPath(fromUrl)} (walked ${MAX_WALK_DEPTH} levels looking for ${ROOT_MARKERS[0]})`
14804
14809
  );
@@ -15119,7 +15119,7 @@ import { dirname, join } from "node:path";
15119
15119
  import { fileURLToPath } from "node:url";
15120
15120
  var ROOT_MARKERS = ["scripts/kxm-hub.mjs", "scripts/kxm.mjs"];
15121
15121
  var MAX_WALK_DEPTH = 10;
15122
- function findKxmRepoRoot(fromUrl = import.meta.url) {
15122
+ function tryFindKxmRepoRoot(fromUrl = import.meta.url) {
15123
15123
  let dir = dirname(fileURLToPath(fromUrl));
15124
15124
  for (let depth = 0; depth < MAX_WALK_DEPTH; depth += 1) {
15125
15125
  if (ROOT_MARKERS.some((marker) => existsSync(join(dir, marker)))) return dir;
@@ -15127,6 +15127,11 @@ function findKxmRepoRoot(fromUrl = import.meta.url) {
15127
15127
  if (parent === dir) break;
15128
15128
  dir = parent;
15129
15129
  }
15130
+ return void 0;
15131
+ }
15132
+ function findKxmRepoRoot(fromUrl = import.meta.url) {
15133
+ const found = tryFindKxmRepoRoot(fromUrl);
15134
+ if (found !== void 0) return found;
15130
15135
  throw new Error(
15131
15136
  `kxm: cannot locate the KXM repo root from ${fileURLToPath(fromUrl)} (walked ${MAX_WALK_DEPTH} levels looking for ${ROOT_MARKERS[0]})`
15132
15137
  );
@@ -14943,7 +14943,7 @@ import { dirname, join } from "node:path";
14943
14943
  import { fileURLToPath } from "node:url";
14944
14944
  var ROOT_MARKERS = ["scripts/kxm-hub.mjs", "scripts/kxm.mjs"];
14945
14945
  var MAX_WALK_DEPTH = 10;
14946
- function findKxmRepoRoot(fromUrl = import.meta.url) {
14946
+ function tryFindKxmRepoRoot(fromUrl = import.meta.url) {
14947
14947
  let dir = dirname(fileURLToPath(fromUrl));
14948
14948
  for (let depth = 0; depth < MAX_WALK_DEPTH; depth += 1) {
14949
14949
  if (ROOT_MARKERS.some((marker) => existsSync(join(dir, marker)))) return dir;
@@ -14951,6 +14951,11 @@ function findKxmRepoRoot(fromUrl = import.meta.url) {
14951
14951
  if (parent === dir) break;
14952
14952
  dir = parent;
14953
14953
  }
14954
+ return void 0;
14955
+ }
14956
+ function findKxmRepoRoot(fromUrl = import.meta.url) {
14957
+ const found = tryFindKxmRepoRoot(fromUrl);
14958
+ if (found !== void 0) return found;
14954
14959
  throw new Error(
14955
14960
  `kxm: cannot locate the KXM repo root from ${fileURLToPath(fromUrl)} (walked ${MAX_WALK_DEPTH} levels looking for ${ROOT_MARKERS[0]})`
14956
14961
  );
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-plugin",
3
- "version": "0.7.82",
3
+ "version": "0.7.84",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "engines": {
@@ -73,9 +73,18 @@ export async function hubGet(url: string, fetchImpl: typeof fetch): Promise<{ ok
73
73
 
74
74
  export function installProbeFrom(runtime: Runtime): InstallProbe {
75
75
  const partial = runtime.io.installProbe ?? {};
76
+ const moduleDir = partial.moduleDir ?? dirname(fileURLToPath(import.meta.url));
76
77
  return {
77
- moduleDir: partial.moduleDir ?? dirname(fileURLToPath(import.meta.url)),
78
- repoRoot: partial.repoRoot ?? resolve("."),
78
+ moduleDir,
79
+ // Classify the install that is **running**, never the directory the caller
80
+ // happens to be standing in. Deriving this from cwd made `kxm update --kxm`
81
+ // answer "kxm is running from source at <cwd>; update it with git pull there"
82
+ // on an npm-global install whenever the operator ran it inside a kxm
83
+ // checkout — and a git pull there deploys nothing, because the services exec
84
+ // the installed copy. An unrecognisable layout falls back to the module
85
+ // directory, which classifies as `unknown` ("update it the way it was
86
+ // installed") rather than inventing a source install.
87
+ repoRoot: partial.repoRoot ?? tryFindKxmRepoRoot(import.meta.url) ?? moduleDir,
79
88
  homeDir: partial.homeDir ?? homedir(),
80
89
  platform: partial.platform ?? process.platform,
81
90
  env: partial.env ?? runtime.env,
@@ -84,6 +93,17 @@ export function installProbeFrom(runtime: Runtime): InstallProbe {
84
93
 
85
94
  import { fileURLToPath } from "node:url";
86
95
  import { dirname } from "node:path";
96
+ import { findKxmRepoRoot, tryFindKxmRepoRoot } from "../repo-root.ts";
97
+ import { installedKxmVersion } from "../kxm-update.ts";
98
+
99
+ /** The install root derived from the loaded module; `unknown` never crashes a caller. */
100
+ function moduleInstallRoot(): string {
101
+ try {
102
+ return findKxmRepoRoot(import.meta.url);
103
+ } catch {
104
+ return dirname(fileURLToPath(import.meta.url));
105
+ }
106
+ }
87
107
  import { homedir } from "node:os";
88
108
 
89
109
  export function warnIgnoredProjectUpdateYaml(runtime: Runtime): void {
@@ -93,11 +113,33 @@ export function warnIgnoredProjectUpdateYaml(runtime: Runtime): void {
93
113
  runtime.io.stderr(`kxm: ignoring .kxm/update.yaml in ${runtime.dirs.workdir}; update settings are read only from ${userFile}\n`);
94
114
  }
95
115
 
96
- export async function refreshKxmUpdateNotice(runtime: Runtime, config?: KxmUpdateConfig): Promise<KxmUpdateNotice> {
116
+ export async function refreshKxmUpdateNotice(
117
+ runtime: Runtime,
118
+ config?: KxmUpdateConfig,
119
+ current?: string,
120
+ ): Promise<KxmUpdateNotice> {
97
121
  const resolved = config ?? loadKxmUpdateConfig(runtime.env);
98
- const current = readInstalledKxmVersion(resolve("."));
122
+ // The version being compared is the one **running**, taken from the install
123
+ // root. Reading it from the caller's directory made `cd ~ && kxm update --kxm`
124
+ // die with a raw ENOENT on `$HOME/package.json` — and silently compared a
125
+ // stranger's version wherever the operator happened to stand and a
126
+ // package.json did exist. Callers that already resolved it pass it in.
127
+ const installed = current
128
+ ?? installedKxmVersion(findKxmRepoRoot(import.meta.url))
129
+ ?? installedKxmVersion(moduleInstallRoot());
130
+ if (installed === undefined) {
131
+ // No version to compare against: say nothing rather than crash a hub start
132
+ // over an update notice.
133
+ return {
134
+ current: "unknown",
135
+ available: false,
136
+ auto: false,
137
+ source: resolved.source,
138
+ message: `kxm update check skipped: the installed version is unreadable at ${moduleInstallRoot()}`,
139
+ };
140
+ }
99
141
  const fetched = await fetchLatestKxmVersion(resolved.source, runtime.env, runtime.fetchImpl);
100
- const notice = noticeFromVersions(current, fetched.latest, resolved, fetched.error, fetched.asset);
142
+ const notice = noticeFromVersions(installed, fetched.latest, resolved, fetched.error, fetched.asset);
101
143
  writeUpdateCache(runtime.dirs.state, notice);
102
144
  return notice;
103
145
  }
@@ -63,6 +63,7 @@ import {
63
63
  type InstallProbe,
64
64
  } from "../kxm-install-kind.ts";
65
65
  import {
66
+ installedKxmVersion,
66
67
  fetchLatestKxmVersion,
67
68
  kxmReleaseAssetName,
68
69
  noticeFromVersions,
@@ -296,16 +297,22 @@ export async function cmdUpdate(runtime: Runtime, harness: string | undefined, o
296
297
  warnIgnoredProjectUpdateYaml(runtime);
297
298
  const probe = installProbeFrom(runtime);
298
299
  const classified = classifyInstallRoot(probe);
299
- const current = readInstalledKxmVersion(findKxmRepoRoot(import.meta.url));
300
+ // The version of the install being updated, taken from the root that was just
301
+ // classified — not from whichever directory the operator stood in. A
302
+ // `readInstalledKxmVersion(resolve("."))` in the notice path turned
303
+ // `cd ~ && kxm update --kxm` into an uncaught ENOENT on `$HOME/package.json`,
304
+ // and would happily compare a stranger project's version wherever one did
305
+ // exist. Unreadable is now a stated refusal.
306
+ const current = installedKxmVersion(classified.root) ?? installedKxmVersion(findKxmRepoRoot(import.meta.url));
300
307
  let notice: KxmUpdateNotice;
301
308
  let kindReport = classified;
302
309
  if (classified.kind === "source") {
303
310
  if (options.check) {
304
- const message = `kxm ${current} (running from source at ${classified.root})`;
311
+ const message = `kxm ${current ?? "unknown"} (running from source at ${classified.root})`;
305
312
  print(runtime.io, runtime.json, {
306
313
  ok: true,
307
314
  command: "update check",
308
- current,
315
+ current: current ?? "unknown",
309
316
  available: false,
310
317
  auto: false,
311
318
  source: "github",
@@ -327,15 +334,15 @@ export async function cmdUpdate(runtime: Runtime, harness: string | undefined, o
327
334
  return 2;
328
335
  }
329
336
  notice = {
330
- current,
337
+ current: current ?? "unknown",
331
338
  available: false,
332
339
  auto: false,
333
340
  source: "github",
334
- message: `kxm ${current} (running from source)`,
341
+ message: `kxm ${current ?? "unknown"} (running from source)`,
335
342
  };
336
343
  } else {
337
344
  try {
338
- notice = await refreshKxmUpdateNotice(runtime);
345
+ notice = await refreshKxmUpdateNotice(runtime, undefined, current);
339
346
  } catch (error) {
340
347
  if (error instanceof KxmUpdateConfigError) {
341
348
  print(runtime.io, runtime.json, { ok: false, command: "update", error: error.code, ...installKindPayload(classified) }, error.message);
@@ -51,6 +51,21 @@ export function readInstalledKxmVersion(root: string): string {
51
51
  return pkg.version;
52
52
  }
53
53
 
54
+ /**
55
+ * `readInstalledKxmVersion` without the raw filesystem failure. The installed
56
+ * version is read to decide whether a candidate is an upgrade, a downgrade or
57
+ * the same thing — so a directory that has no readable `package.json` must come
58
+ * back as "unknown" and be refused in words, not surface as an uncaught ENOENT
59
+ * stack from `kxm update`.
60
+ */
61
+ export function installedKxmVersion(root: string): string | undefined {
62
+ try {
63
+ return readInstalledKxmVersion(root);
64
+ } catch {
65
+ return undefined;
66
+ }
67
+ }
68
+
54
69
  export function parseSemver(value: string): [number, number, number] | undefined {
55
70
  const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(value.trim());
56
71
  if (!match) return undefined;
@@ -8,7 +8,7 @@ import { AGENT_COMMANDS_MAP, enforceToolPolicy, getMcpTools, reconcileInbox } fr
8
8
  import { deliverInboxNotification } from "./inbox.ts";
9
9
  import type { HubEvent, MessageRecord } from "./protocol.ts";
10
10
 
11
- const VERSION = "0.7.82";
11
+ const VERSION = "0.7.84";
12
12
  const inbox = new Map<string, MessageRecord>();
13
13
  const notifiedInbox = new Set<string>();
14
14
  let meshClient: HubClient | undefined;
@@ -14,7 +14,12 @@ const MAX_WALK_DEPTH = 10;
14
14
  * layout; marker discovery is correct in both, and in npm-installed
15
15
  * layouts, which also ship `scripts/`.
16
16
  */
17
- export function findKxmRepoRoot(fromUrl: string = import.meta.url): string {
17
+ /**
18
+ * The repo/package root this module was loaded from, or `undefined` when the
19
+ * layout is not recognisable. For callers that must degrade to "cannot tell how
20
+ * it was installed" instead of throwing — install classification, mostly.
21
+ */
22
+ export function tryFindKxmRepoRoot(fromUrl: string = import.meta.url): string | undefined {
18
23
  let dir = dirname(fileURLToPath(fromUrl));
19
24
  for (let depth = 0; depth < MAX_WALK_DEPTH; depth += 1) {
20
25
  if (ROOT_MARKERS.some((marker) => existsSync(join(dir, marker)))) return dir;
@@ -22,6 +27,12 @@ export function findKxmRepoRoot(fromUrl: string = import.meta.url): string {
22
27
  if (parent === dir) break;
23
28
  dir = parent;
24
29
  }
30
+ return undefined;
31
+ }
32
+
33
+ export function findKxmRepoRoot(fromUrl: string = import.meta.url): string {
34
+ const found = tryFindKxmRepoRoot(fromUrl);
35
+ if (found !== undefined) return found;
25
36
  throw new Error(
26
37
  `kxm: cannot locate the KXM repo root from ${fileURLToPath(fromUrl)} ` +
27
38
  `(walked ${MAX_WALK_DEPTH} levels looking for ${ROOT_MARKERS[0]})`,