@levr-one/cli 0.11.6 → 0.11.7

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.
@@ -773,6 +773,41 @@ function buildServerEntry(harness, mcpUrl, scope = defaultScope(harness), server
773
773
  args
774
774
  } };
775
775
  }
776
+ /** The workspace a scoped MCP URL names (`…/w/<url_key>`), if any. */
777
+ function workspaceKeyOf(url) {
778
+ const m = /\/w\/([^/]+)$/.exec(url.replace(/\/+$/, ""));
779
+ if (!m?.[1]) return void 0;
780
+ try {
781
+ return decodeURIComponent(m[1]);
782
+ } catch {
783
+ return m[1];
784
+ }
785
+ }
786
+ /**
787
+ * Allowed client entry names: 1-64 of `A-Za-z0-9_-`, and never starting with
788
+ * `-`. The name is passed as an argv element to the client's own CLI
789
+ * (`claude mcp add … <name> <url>`), so a leading `-` would be read there as a
790
+ * flag — the ENG-4159 argument-injection family.
791
+ */
792
+ const ENTRY_NAME = /^[A-Za-z0-9_-]{1,64}$/;
793
+ function isValidEntryName(name) {
794
+ return ENTRY_NAME.test(name) && !name.startsWith("-");
795
+ }
796
+ /** Claude Code tool names are `mcp__<entry>__<tool>`; keep that well short. */
797
+ const BESIDE_NAME_MAX = 30;
798
+ /**
799
+ * The name for a second entry beside `serverName`: `<serverName>-<url_key>`,
800
+ * so it says which workspace it is. Undefined — and "Add beside it" is not
801
+ * offered — when the target URL names no workspace, or when that name would
802
+ * be too long or not a valid entry name. It is never shortened: a cut name
803
+ * could drop the workspace, or give two workspaces the same entry.
804
+ */
805
+ function besideName(serverName, url) {
806
+ const key = workspaceKeyOf(url);
807
+ if (!key) return void 0;
808
+ const name = `${serverName}-${key}`;
809
+ return name.length <= BESIDE_NAME_MAX && isValidEntryName(name) ? name : void 0;
810
+ }
776
811
 
777
812
  //#endregion
778
813
  //#region ../mcp-harnesses/dist/node/paths.js
@@ -1611,47 +1646,44 @@ function needsShell(binPath) {
1611
1646
  */
1612
1647
  const SHELL_METACHARACTERS = /[&|<>^"%!`]/;
1613
1648
  /**
1614
- * Run a harness CLI invocation, or explain why it was not run.
1649
+ * Everything decided before a command is spawned, shared by both runners: an
1650
+ * outcome when it must not run, else how to run it.
1615
1651
  *
1616
1652
  * Resolution uses {@link whichPathSync} — a pure `PATH` scan that spawns
1617
1653
  * nothing, so the "is this installed" check can never itself be the thing
1618
1654
  * that hangs. It returns the resolved path rather than a boolean because the
1619
1655
  * extension decides how the command must be launched.
1620
1656
  */
1621
- function runHarnessCommandSync(argv, opts = {}) {
1657
+ function prepare(argv, opts) {
1622
1658
  const env = opts.env ?? defaultEnv();
1623
1659
  const dryRun = opts.dryRun ?? false;
1624
1660
  const [bin, ...args] = argv;
1625
- if (!bin) return {
1661
+ if (!bin) return { skip: {
1626
1662
  executed: false,
1627
1663
  ok: false,
1628
1664
  reason: "empty-command"
1629
- };
1630
- if (dryRun) return {
1665
+ } };
1666
+ if (dryRun) return { skip: {
1631
1667
  executed: false,
1632
1668
  ok: true,
1633
1669
  reason: "dry-run"
1634
- };
1670
+ } };
1635
1671
  const binPath = whichPathSync(bin, env);
1636
- if (!binPath) return {
1672
+ if (!binPath) return { skip: {
1637
1673
  executed: false,
1638
1674
  ok: true,
1639
1675
  reason: "binary-not-found"
1640
- };
1676
+ } };
1641
1677
  const useShell = needsShell(binPath);
1642
- if (useShell && args.some((a) => SHELL_METACHARACTERS.test(a))) return {
1678
+ if (useShell && (binPath.includes("\"") || args.some((a) => SHELL_METACHARACTERS.test(a)))) return { skip: {
1643
1679
  executed: false,
1644
1680
  ok: false,
1645
1681
  reason: "unsafe-argument"
1646
- };
1647
- try {
1648
- execFileSync(binPath, args, {
1649
- encoding: "utf8",
1650
- stdio: [
1651
- "ignore",
1652
- "pipe",
1653
- "pipe"
1654
- ],
1682
+ } };
1683
+ return { spawn: {
1684
+ binPath: useShell ? `"${binPath}"` : binPath,
1685
+ args,
1686
+ options: {
1655
1687
  timeout: COMMAND_TIMEOUT_MS,
1656
1688
  env: {
1657
1689
  ...childVars(env),
@@ -1660,20 +1692,42 @@ function runHarnessCommandSync(argv, opts = {}) {
1660
1692
  },
1661
1693
  cwd: env.cwd,
1662
1694
  ...useShell ? { shell: true } : {}
1695
+ }
1696
+ } };
1697
+ }
1698
+ /** A command that ran and failed (or could not start), as an outcome. */
1699
+ function failureOutcome(err) {
1700
+ const e = err;
1701
+ const exitCode = typeof e.status === "number" ? e.status : typeof e.code === "number" ? e.code : void 0;
1702
+ const raw = typeof e.stderr === "string" ? e.stderr : e.stderr?.toString("utf8") ?? "";
1703
+ return {
1704
+ executed: true,
1705
+ ok: false,
1706
+ ...exitCode === void 0 ? {} : { exitCode },
1707
+ stderr: (raw.trim() || e.message || "command failed").slice(0, MAX_STDERR)
1708
+ };
1709
+ }
1710
+ /** Run a harness CLI invocation, or explain why it was not run. */
1711
+ function runHarnessCommandSync(argv, opts = {}) {
1712
+ const prepared = prepare(argv, opts);
1713
+ if ("skip" in prepared) return prepared.skip;
1714
+ const { binPath, args, options } = prepared.spawn;
1715
+ try {
1716
+ execFileSync(binPath, args, {
1717
+ ...options,
1718
+ encoding: "utf8",
1719
+ stdio: [
1720
+ "ignore",
1721
+ "pipe",
1722
+ "pipe"
1723
+ ]
1663
1724
  });
1664
1725
  return {
1665
1726
  executed: true,
1666
1727
  ok: true
1667
1728
  };
1668
1729
  } catch (err) {
1669
- const e = err;
1670
- const raw = typeof e.stderr === "string" ? e.stderr : e.stderr?.toString("utf8") ?? "";
1671
- return {
1672
- executed: true,
1673
- ok: false,
1674
- ...typeof e.status === "number" ? { exitCode: e.status } : {},
1675
- stderr: (raw.trim() || e.message || "command failed").slice(0, MAX_STDERR)
1676
- };
1730
+ return failureOutcome(err);
1677
1731
  }
1678
1732
  }
1679
1733
 
@@ -1745,8 +1799,16 @@ function canonical(value) {
1745
1799
  if (value !== null && typeof value === "object") return `{${Object.entries(value).filter(([, v]) => v !== void 0).sort(([x], [y]) => x < y ? -1 : x > y ? 1 : 0).map(([k, v]) => `${JSON.stringify(k)}:${canonical(v)}`).join(",")}}`;
1746
1800
  return JSON.stringify(value) ?? "undefined";
1747
1801
  }
1802
+ function drive(steps, run) {
1803
+ let step = steps.next();
1804
+ while (!step.done) step = steps.next(run(step.value.argv, step.value.opts));
1805
+ return step.value;
1806
+ }
1748
1807
  /** Merge (or preview) our MCP into a harness config. */
1749
1808
  function installHarnessSync(harness, mcpUrl, opts = {}) {
1809
+ return drive(installSteps(harness, mcpUrl, opts), opts.runCommand ?? runHarnessCommandSync);
1810
+ }
1811
+ function* installSteps(harness, mcpUrl, opts) {
1750
1812
  const env = opts.env ?? defaultEnv();
1751
1813
  const dryRun = opts.dryRun ?? false;
1752
1814
  const scope = opts.scope ?? defaultScope(harness);
@@ -1785,13 +1847,12 @@ function installHarnessSync(harness, mcpUrl, opts = {}) {
1785
1847
  alreadyConfigured: false,
1786
1848
  reason: "unrecognized-entry"
1787
1849
  };
1788
- if (opts.replaceExisting) return replaceViaCommand(harness, argv, existing$1.url, {
1850
+ if (opts.replaceExisting) return yield* replaceSteps(harness, argv, existing$1.url, {
1789
1851
  env,
1790
1852
  dryRun,
1791
1853
  scope,
1792
1854
  serverName,
1793
- execute: opts.execute ?? true,
1794
- run: opts.runCommand ?? runHarnessCommandSync
1855
+ execute: opts.execute ?? true
1795
1856
  });
1796
1857
  return {
1797
1858
  ...base,
@@ -1802,11 +1863,13 @@ function installHarnessSync(harness, mcpUrl, opts = {}) {
1802
1863
  ...existing$1.url ? { currentUrl: existing$1.url } : {}
1803
1864
  };
1804
1865
  }
1805
- const run = opts.runCommand ?? runHarnessCommandSync;
1806
- const outcome = opts.execute ?? true ? run(argv, {
1807
- env,
1808
- dryRun
1809
- }) : {
1866
+ const outcome = opts.execute ?? true ? yield {
1867
+ argv,
1868
+ opts: {
1869
+ env,
1870
+ dryRun
1871
+ }
1872
+ } : {
1810
1873
  executed: false,
1811
1874
  ok: true,
1812
1875
  reason: "not-requested"
@@ -1815,7 +1878,7 @@ function installHarnessSync(harness, mcpUrl, opts = {}) {
1815
1878
  ...base,
1816
1879
  ok: outcome.ok,
1817
1880
  executed: outcome.executed,
1818
- ...outcome.ok ? {} : { commandError: outcome.stderr },
1881
+ ...outcome.ok ? {} : { commandError: notRunError(outcome) },
1819
1882
  alreadyConfigured: false
1820
1883
  };
1821
1884
  }
@@ -1907,7 +1970,14 @@ function installHarnessSync(harness, mcpUrl, opts = {}) {
1907
1970
  * second command must not leave the user with no entry at all without saying
1908
1971
  * so.
1909
1972
  */
1910
- function replaceViaCommand(harness, addArgv, previousUrl, ctx) {
1973
+ /**
1974
+ * What went wrong with a failed command: its stderr, else why it was never
1975
+ * run — so a refusal never reads as "unknown reason" (ENG-6737).
1976
+ */
1977
+ function notRunError(outcome) {
1978
+ return outcome.stderr ?? (outcome.reason ? `not run: ${outcome.reason}` : void 0);
1979
+ }
1980
+ function* replaceSteps(harness, addArgv, previousUrl, ctx) {
1911
1981
  const removeArgv = removeCommandArgv(ctx.scope, ctx.serverName);
1912
1982
  const base = {
1913
1983
  wrote: false,
@@ -1923,11 +1993,15 @@ function replaceViaCommand(harness, addArgv, previousUrl, ctx) {
1923
1993
  ok: true,
1924
1994
  executed: false
1925
1995
  };
1926
- const removed = ctx.run(removeArgv, { env: ctx.env });
1996
+ const removed = yield {
1997
+ argv: removeArgv,
1998
+ opts: { env: ctx.env }
1999
+ };
1927
2000
  if (!removed.executed) return {
1928
2001
  ...base,
1929
2002
  ok: removed.ok,
1930
- executed: false
2003
+ executed: false,
2004
+ ...removed.ok ? {} : { commandError: notRunError(removed) }
1931
2005
  };
1932
2006
  if (!removed.ok) return {
1933
2007
  ...base,
@@ -1935,21 +2009,26 @@ function replaceViaCommand(harness, addArgv, previousUrl, ctx) {
1935
2009
  executed: true,
1936
2010
  commandError: `could not remove the existing entry: ${removed.stderr ?? "unknown error"}`
1937
2011
  };
1938
- const added = ctx.run(addArgv, { env: ctx.env });
2012
+ const added = yield {
2013
+ argv: addArgv,
2014
+ opts: { env: ctx.env }
2015
+ };
1939
2016
  if (added.ok) return {
1940
2017
  ...base,
1941
2018
  ok: true,
1942
2019
  executed: true
1943
2020
  };
1944
- const restoreArgv = renderHarnessCommand(harness, previousUrl, ctx.scope, ctx.serverName);
1945
- const restore = ctx.run(restoreArgv, { env: ctx.env });
2021
+ const restore = yield {
2022
+ argv: renderHarnessCommand(harness, previousUrl, ctx.scope, ctx.serverName),
2023
+ opts: { env: ctx.env }
2024
+ };
1946
2025
  return {
1947
2026
  ...base,
1948
2027
  ok: false,
1949
2028
  executed: true,
1950
- commandError: added.stderr ?? "unknown error",
2029
+ commandError: notRunError(added) ?? "unknown error",
1951
2030
  restored: restore.ok && restore.executed,
1952
- ...restore.ok && restore.executed ? {} : { restoreError: restore.stderr ?? "the restore command did not run" }
2031
+ ...restore.ok && restore.executed ? {} : { restoreError: notRunError(restore) ?? "the restore command did not run" }
1953
2032
  };
1954
2033
  }
1955
2034
  /**
@@ -2111,16 +2190,6 @@ function servedBySessionApi(mcpUrl) {
2111
2190
  }
2112
2191
  /** `…/v1/mcp/w/<url_key>`: the URL already names one workspace (ENG-6300). */
2113
2192
  const SCOPED_SUFFIX = /\/w\/[^/]+$/;
2114
- /** The workspace a scoped MCP URL names (`…/w/<url_key>`), if any. */
2115
- function workspaceKeyOf(url) {
2116
- const m = /\/w\/([^/]+)$/.exec(stripSlash(url));
2117
- if (!m?.[1]) return void 0;
2118
- try {
2119
- return decodeURIComponent(m[1]);
2120
- } catch {
2121
- return m[1];
2122
- }
2123
- }
2124
2193
  /** Does this MCP URL already name a workspace? */
2125
2194
  function isScopedMcpUrl(url) {
2126
2195
  return SCOPED_SUFFIX.test(stripSlash(url));
@@ -2188,16 +2257,6 @@ function entryState(s, url) {
2188
2257
  return s.currentUrl === void 0 ? "unrecognized" : "other-url";
2189
2258
  }
2190
2259
  /**
2191
- * Allowed client entry names: 1-64 of `A-Za-z0-9_-`, and never starting with
2192
- * `-`. The name is passed as an argv element to the client's own CLI
2193
- * (`claude mcp add … <name> <url>`), so a leading `-` would be read there as a
2194
- * flag — the ENG-4159 argument-injection family.
2195
- */
2196
- const ENTRY_NAME = /^[A-Za-z0-9_-]{1,64}$/;
2197
- function isValidEntryName(name) {
2198
- return ENTRY_NAME.test(name) && !name.startsWith("-");
2199
- }
2200
- /**
2201
2260
  * The scope an install would land in: the requested one, or, for a harness
2202
2261
  * that cannot do it, its own default, which is where the fallback goes.
2203
2262
  */
@@ -2261,21 +2320,6 @@ function clientChoices(detected, scope, url, serverName = DEFAULT_SERVER_NAME) {
2261
2320
  };
2262
2321
  });
2263
2322
  }
2264
- /** Claude Code tool names are `mcp__<entry>__<tool>`; keep that well short. */
2265
- const BESIDE_NAME_MAX = 30;
2266
- /**
2267
- * The name for a second entry beside `serverName`: `<serverName>-<url_key>`,
2268
- * so it says which workspace it is. Undefined — and "Add beside it" is not
2269
- * offered — when the target URL names no workspace, or when that name would
2270
- * be too long or not a valid entry name. It is never shortened: a cut name
2271
- * could drop the workspace, or give two workspaces the same entry.
2272
- */
2273
- function besideName(serverName, url) {
2274
- const key = workspaceKeyOf(url);
2275
- if (!key) return void 0;
2276
- const name = `${serverName}-${key}`;
2277
- return name.length <= BESIDE_NAME_MAX && isValidEntryName(name) ? name : void 0;
2278
- }
2279
2323
  /**
2280
2324
  * The selected clients whose entry is not this URL's: each needs a decision —
2281
2325
  * switch it, add beside it, or leave it — before anything is installed
package/dist/cli.js CHANGED
@@ -209,7 +209,7 @@ Examples:
209
209
  aliases: { y: "yes" }
210
210
  },
211
211
  loader: async () => {
212
- const { mcpAddHandler } = await import("./addHandler-DJqJGqz3.js");
212
+ const { mcpAddHandler } = await import("./addHandler-BqLLDeKp.js");
213
213
  return mcpAddHandler;
214
214
  }
215
215
  });
@@ -876,7 +876,7 @@ Examples:
876
876
 
877
877
  //#endregion
878
878
  //#region package.json
879
- var version = "0.11.6";
879
+ var version = "0.11.7";
880
880
 
881
881
  //#endregion
882
882
  //#region src/app.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@levr-one/cli",
3
- "version": "0.11.6",
3
+ "version": "0.11.7",
4
4
  "description": "The command-line interface for Levr",
5
5
  "type": "module",
6
6
  "license": "MIT",