nexus-agents 2.62.0 → 2.63.0

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.
@@ -68,7 +68,7 @@ import {
68
68
  clampTaskTtl,
69
69
  getAvailabilityCache,
70
70
  resolveFallback
71
- } from "./chunk-V7AFOKWC.js";
71
+ } from "./chunk-FMFQJLMR.js";
72
72
  import {
73
73
  DEFAULTS
74
74
  } from "./chunk-H43PABG4.js";
@@ -53661,4 +53661,4 @@ export {
53661
53661
  detectBackend,
53662
53662
  createTaskTracker
53663
53663
  };
53664
- //# sourceMappingURL=chunk-X5WDX7L3.js.map
53664
+ //# sourceMappingURL=chunk-4G7MSCIK.js.map
@@ -8,7 +8,7 @@ import {
8
8
  checkSqlite,
9
9
  defaultConfig,
10
10
  initDataDirectories
11
- } from "./chunk-V7AFOKWC.js";
11
+ } from "./chunk-FMFQJLMR.js";
12
12
  import {
13
13
  BUILT_IN_EXPERTS
14
14
  } from "./chunk-GJVHRJO2.js";
@@ -1933,4 +1933,4 @@ export {
1933
1933
  setupCommand,
1934
1934
  setupCommandAsync
1935
1935
  };
1936
- //# sourceMappingURL=chunk-OJBW4II4.js.map
1936
+ //# sourceMappingURL=chunk-7C32M23X.js.map
@@ -38,7 +38,7 @@ import {
38
38
  } from "./chunk-CLYZ7FWP.js";
39
39
 
40
40
  // src/version.ts
41
- var VERSION = true ? "2.62.0" : "dev";
41
+ var VERSION = true ? "2.63.0" : "dev";
42
42
 
43
43
  // src/config/schemas-core.ts
44
44
  import { z } from "zod";
@@ -2025,7 +2025,7 @@ async function runDoctorFix(result) {
2025
2025
  writeLine2("\u2500".repeat(40));
2026
2026
  let fixCount = 0;
2027
2027
  if (!result.dataDirectory.rootExists || result.dataDirectory.subdirectories.some((d) => !d.exists || !d.writable)) {
2028
- const { runSetup } = await import("./setup-command-FC67SJSP.js");
2028
+ const { runSetup } = await import("./setup-command-QKAVRVLV.js");
2029
2029
  const setupResult = runSetup({
2030
2030
  skipMcp: true,
2031
2031
  skipRules: true,
@@ -2135,4 +2135,4 @@ export {
2135
2135
  startStdioServer,
2136
2136
  closeServer
2137
2137
  };
2138
- //# sourceMappingURL=chunk-V7AFOKWC.js.map
2138
+ //# sourceMappingURL=chunk-FMFQJLMR.js.map
package/dist/cli.d.ts CHANGED
@@ -109,6 +109,8 @@ interface ParsedCliArgs {
109
109
  portable?: boolean;
110
110
  gitignore?: boolean;
111
111
  mcpConfig?: boolean;
112
+ install?: boolean;
113
+ uninstall?: boolean;
112
114
  };
113
115
  positionals: string[];
114
116
  }
package/dist/cli.js CHANGED
@@ -16,7 +16,7 @@ import "./chunk-6QU4DJYW.js";
16
16
  import {
17
17
  setupCommandAsync,
18
18
  verifyCommand
19
- } from "./chunk-OJBW4II4.js";
19
+ } from "./chunk-7C32M23X.js";
20
20
  import "./chunk-QGZBCD2A.js";
21
21
  import {
22
22
  AuthHandler,
@@ -142,7 +142,7 @@ import {
142
142
  validateCommand,
143
143
  validateWorkflow,
144
144
  wrapInMarkdownFence
145
- } from "./chunk-X5WDX7L3.js";
145
+ } from "./chunk-4G7MSCIK.js";
146
146
  import "./chunk-ED6VQWNG.js";
147
147
  import {
148
148
  resolveToken
@@ -201,7 +201,7 @@ import {
201
201
  loadConfig,
202
202
  runDoctor,
203
203
  validateNexusEnv
204
- } from "./chunk-V7AFOKWC.js";
204
+ } from "./chunk-FMFQJLMR.js";
205
205
  import {
206
206
  DEFAULTS
207
207
  } from "./chunk-H43PABG4.js";
@@ -17668,6 +17668,15 @@ var PARSE_ARGS_CONFIG = {
17668
17668
  "mcp-config": {
17669
17669
  type: "boolean",
17670
17670
  default: false
17671
+ },
17672
+ // init --portable --install / --uninstall flags (#2311)
17673
+ install: {
17674
+ type: "boolean",
17675
+ default: false
17676
+ },
17677
+ uninstall: {
17678
+ type: "boolean",
17679
+ default: false
17671
17680
  }
17672
17681
  },
17673
17682
  allowPositionals: true,
@@ -17726,27 +17735,27 @@ function isValidCommand(value) {
17726
17735
  }
17727
17736
 
17728
17737
  // src/cli-commands-handlers.ts
17729
- import { existsSync as existsSync20 } from "fs";
17738
+ import { existsSync as existsSync22 } from "fs";
17730
17739
 
17731
17740
  // src/cli/init-portable.ts
17732
17741
  import {
17733
- existsSync as existsSync18,
17734
- mkdirSync as mkdirSync3,
17742
+ existsSync as existsSync20,
17743
+ mkdirSync as mkdirSync5,
17735
17744
  readdirSync as readdirSync2,
17736
17745
  statSync as statSync3,
17737
17746
  appendFileSync as appendFileSync2,
17738
- readFileSync as readFileSync11
17747
+ readFileSync as readFileSync12
17739
17748
  } from "fs";
17740
- import { resolve as resolve11, join as join14, isAbsolute as isAbsolute2 } from "path";
17749
+ import { resolve as resolve11, join as join16, isAbsolute as isAbsolute2 } from "path";
17741
17750
 
17742
17751
  // src/cli/mcp-config-emitter.ts
17743
17752
  import { existsSync as existsSync17, readFileSync as readFileSync10, writeFileSync as writeFileSync5, appendFileSync } from "fs";
17744
17753
  import { join as join13 } from "path";
17745
17754
  var MCP_CONFIG_FILENAME = ".mcp.json";
17746
17755
  var NEXUS_SERVER_KEY = "nexus-agents";
17747
- function buildNexusServerEntry(dataDir) {
17756
+ function buildNexusServerEntry(dataDir, commandPath) {
17748
17757
  return {
17749
- command: "nexus-agents",
17758
+ command: commandPath ?? "nexus-agents",
17750
17759
  args: ["--mode=server"],
17751
17760
  env: { NEXUS_DATA_DIR: dataDir }
17752
17761
  };
@@ -17821,7 +17830,7 @@ function emitMcpConfig(options) {
17821
17830
  const mcpConfigPath = join13(options.workspaceDir, MCP_CONFIG_FILENAME);
17822
17831
  const dryRun = options.dryRun === true;
17823
17832
  const force = options.force === true;
17824
- const desired = buildNexusServerEntry(options.dataDir);
17833
+ const desired = buildNexusServerEntry(options.dataDir, options.commandPath);
17825
17834
  const loaded = loadExistingConfig(mcpConfigPath);
17826
17835
  if (!loaded.ok) return makeFailure(mcpConfigPath, loaded.error);
17827
17836
  const decision = decideEmission(loaded.value, desired, force);
@@ -17854,6 +17863,165 @@ function makeFailure(mcpConfigPath, error) {
17854
17863
  };
17855
17864
  }
17856
17865
 
17866
+ // src/cli/portable-installer.ts
17867
+ import { execFile } from "child_process";
17868
+ import { existsSync as existsSync19, mkdirSync as mkdirSync4, rmSync, writeFileSync as writeFileSync7 } from "fs";
17869
+ import { join as join15 } from "path";
17870
+ import { promisify } from "util";
17871
+
17872
+ // src/cli/bin-shim.ts
17873
+ import { existsSync as existsSync18, readFileSync as readFileSync11, writeFileSync as writeFileSync6, chmodSync as chmodSync2, mkdirSync as mkdirSync3 } from "fs";
17874
+ import { join as join14 } from "path";
17875
+ var SHIM_BASENAME = "nexus-agents";
17876
+ var SHIM_MODE = 493;
17877
+ function buildShimContents(cliEntryPath) {
17878
+ const lines = [
17879
+ "#!/usr/bin/env node",
17880
+ "// Generated by `nexus-agents init --portable --install` (#3a).",
17881
+ "// Do not edit by hand \u2014 re-run `init --portable --install` to refresh.",
17882
+ `import('${cliEntryPath}').catch((e) => { console.error(e); process.exit(1); });`,
17883
+ ""
17884
+ ];
17885
+ return lines.join("\n");
17886
+ }
17887
+ function writeBinShim(options) {
17888
+ const shimPath = join14(options.binDir, SHIM_BASENAME);
17889
+ const desired = buildShimContents(options.cliEntryPath);
17890
+ const dryRun = options.dryRun === true;
17891
+ try {
17892
+ if (existsSync18(shimPath)) {
17893
+ const current = readFileSync11(shimPath, "utf-8");
17894
+ if (current === desired) {
17895
+ return { success: true, shimPath, written: false, alreadyMatched: true, error: null };
17896
+ }
17897
+ }
17898
+ if (!dryRun) {
17899
+ if (!existsSync18(options.binDir)) mkdirSync3(options.binDir, { recursive: true });
17900
+ writeFileSync6(shimPath, desired, "utf-8");
17901
+ chmodSync2(shimPath, SHIM_MODE);
17902
+ }
17903
+ return { success: true, shimPath, written: true, alreadyMatched: false, error: null };
17904
+ } catch (error) {
17905
+ const msg = error instanceof Error ? error.message : String(error);
17906
+ return { success: false, shimPath, written: false, alreadyMatched: false, error: msg };
17907
+ }
17908
+ }
17909
+
17910
+ // src/cli/portable-installer.ts
17911
+ var execFileAsync = promisify(execFile);
17912
+ var CLI_SUBDIR = "cli";
17913
+ var BIN_SUBDIR = "bin";
17914
+ var CLI_ENTRY_RELATIVE = "node_modules/nexus-agents/dist/cli.js";
17915
+ var NPM_INSTALL_TIMEOUT_MS = 5 * 60 * 1e3;
17916
+ function resolveInstallVersion(override) {
17917
+ const v = override ?? VERSION;
17918
+ if (v === "dev" || v === "" || v.includes(" ")) {
17919
+ return {
17920
+ ok: false,
17921
+ error: `cannot resolve install version: got '${v}'. Portable install requires a published nexus-agents version (build the CLI from a release).`
17922
+ };
17923
+ }
17924
+ return { ok: true, value: v };
17925
+ }
17926
+ function writeInstallManifest(cliDir, version) {
17927
+ const manifest = {
17928
+ name: "nexus-agents-portable-shim",
17929
+ private: true,
17930
+ version: "0.0.0",
17931
+ description: "Local install root for portable nexus-agents (generated; do not commit)",
17932
+ dependencies: { "nexus-agents": version }
17933
+ };
17934
+ writeFileSync7(join15(cliDir, "package.json"), JSON.stringify(manifest, null, 2) + "\n", "utf-8");
17935
+ }
17936
+ function isAlreadyInstalled(cliDir) {
17937
+ return existsSync19(join15(cliDir, "node_modules", "nexus-agents", "package.json"));
17938
+ }
17939
+ async function spawnNpmInstall(cliDir) {
17940
+ await execFileAsync("npm", ["install", "--no-audit", "--no-fund", "--silent"], {
17941
+ cwd: cliDir,
17942
+ timeout: NPM_INSTALL_TIMEOUT_MS
17943
+ });
17944
+ }
17945
+ function cleanupOnFailure(cliDir) {
17946
+ try {
17947
+ rmSync(cliDir, { recursive: true, force: true });
17948
+ } catch {
17949
+ }
17950
+ }
17951
+ async function runNpmStep(ctx) {
17952
+ try {
17953
+ if (!existsSync19(ctx.cliDir)) mkdirSync4(ctx.cliDir, { recursive: true });
17954
+ writeInstallManifest(ctx.cliDir, ctx.version);
17955
+ await spawnNpmInstall(ctx.cliDir);
17956
+ return void 0;
17957
+ } catch (error) {
17958
+ cleanupOnFailure(ctx.cliDir);
17959
+ const msg = error instanceof Error ? error.message : String(error);
17960
+ return { success: false, ...ctx, skipped: false, error: `npm install failed: ${msg}` };
17961
+ }
17962
+ }
17963
+ async function installPortable(options) {
17964
+ const cliDir = join15(options.dataDir, CLI_SUBDIR);
17965
+ const binDir = join15(options.dataDir, BIN_SUBDIR);
17966
+ const versionResolution = resolveInstallVersion(options.version);
17967
+ if (!versionResolution.ok) {
17968
+ return {
17969
+ success: false,
17970
+ version: "",
17971
+ cliDir,
17972
+ binDir,
17973
+ skipped: false,
17974
+ error: versionResolution.error
17975
+ };
17976
+ }
17977
+ const ctx = { cliDir, binDir, version: versionResolution.value };
17978
+ if (isAlreadyInstalled(cliDir) && options.force !== true) {
17979
+ return { success: true, ...ctx, skipped: true, error: null };
17980
+ }
17981
+ if (options.dryRun === true) {
17982
+ return { success: true, ...ctx, skipped: false, error: null };
17983
+ }
17984
+ const npmFailure = await runNpmStep(ctx);
17985
+ if (npmFailure !== void 0) return npmFailure;
17986
+ const shim = writeBinShim({ binDir, cliEntryPath: join15(cliDir, CLI_ENTRY_RELATIVE) });
17987
+ if (!shim.success) {
17988
+ cleanupOnFailure(cliDir);
17989
+ return {
17990
+ success: false,
17991
+ ...ctx,
17992
+ shim,
17993
+ skipped: false,
17994
+ error: `bin shim emission failed: ${shim.error ?? "unknown"}`
17995
+ };
17996
+ }
17997
+ return { success: true, ...ctx, shim, skipped: false, error: null };
17998
+ }
17999
+ function uninstallPortable(options) {
18000
+ const cliDir = join15(options.dataDir, CLI_SUBDIR);
18001
+ const binDir = join15(options.dataDir, BIN_SUBDIR);
18002
+ const removed = [];
18003
+ const notPresent = [];
18004
+ const dryRun = options.dryRun === true;
18005
+ try {
18006
+ for (const dir of [cliDir, binDir]) {
18007
+ if (!existsSync19(dir)) {
18008
+ notPresent.push(dir);
18009
+ continue;
18010
+ }
18011
+ if (!dryRun) rmSync(dir, { recursive: true, force: true });
18012
+ removed.push(dir);
18013
+ }
18014
+ return { success: true, removed, notPresent, error: null };
18015
+ } catch (error) {
18016
+ const msg = error instanceof Error ? error.message : String(error);
18017
+ return { success: false, removed, notPresent, error: msg };
18018
+ }
18019
+ }
18020
+ function findBinShim(dataDir) {
18021
+ const shimPath = join15(dataDir, BIN_SUBDIR, "nexus-agents");
18022
+ return existsSync19(shimPath) ? shimPath : void 0;
18023
+ }
18024
+
17857
18025
  // src/cli/init-portable.ts
17858
18026
  var DEFAULT_PORTABLE_DIRNAME = ".nexus-agents";
17859
18027
  var RESTRICTED_SUBDIRS = /* @__PURE__ */ new Set(["auth"]);
@@ -17864,29 +18032,29 @@ function resolveTargetPath(rawPath) {
17864
18032
  return isAbsolute2(rawPath) ? rawPath : resolve11(process.cwd(), rawPath);
17865
18033
  }
17866
18034
  function isNonEmpty(dir) {
17867
- if (!existsSync18(dir)) return false;
18035
+ if (!existsSync20(dir)) return false;
17868
18036
  const stat2 = statSync3(dir);
17869
18037
  if (!stat2.isDirectory()) return true;
17870
18038
  return readdirSync2(dir).length > 0;
17871
18039
  }
17872
18040
  function ensureDir(path23, dryRun, created, alreadyExisted, mode) {
17873
- if (existsSync18(path23)) {
18041
+ if (existsSync20(path23)) {
17874
18042
  alreadyExisted.push(path23);
17875
18043
  return;
17876
18044
  }
17877
18045
  if (!dryRun) {
17878
- mkdirSync3(path23, { recursive: true, ...mode !== void 0 ? { mode } : {} });
18046
+ mkdirSync5(path23, { recursive: true, ...mode !== void 0 ? { mode } : {} });
17879
18047
  }
17880
18048
  created.push(path23);
17881
18049
  }
17882
18050
  function maybeUpdateGitignore(workspaceDir, portableDirName, dryRun) {
17883
- const gitDir = join14(workspaceDir, ".git");
17884
- if (!existsSync18(gitDir)) return false;
17885
- const gitignorePath = join14(workspaceDir, ".gitignore");
18051
+ const gitDir = join16(workspaceDir, ".git");
18052
+ if (!existsSync20(gitDir)) return false;
18053
+ const gitignorePath = join16(workspaceDir, ".gitignore");
17886
18054
  const entry = `${portableDirName}/`;
17887
18055
  let existing = "";
17888
- if (existsSync18(gitignorePath)) {
17889
- existing = readFileSync11(gitignorePath, "utf-8");
18056
+ if (existsSync20(gitignorePath)) {
18057
+ existing = readFileSync12(gitignorePath, "utf-8");
17890
18058
  if (existing.split("\n").some((l) => l.trim() === entry || l.trim() === portableDirName)) {
17891
18059
  return false;
17892
18060
  }
@@ -17899,18 +18067,18 @@ function maybeUpdateGitignore(workspaceDir, portableDirName, dryRun) {
17899
18067
  return true;
17900
18068
  }
17901
18069
  function inspectTarget(target) {
17902
- const exists = existsSync18(target);
18070
+ const exists = existsSync20(target);
17903
18071
  if (!exists) return { exists: false, nonEmpty: false, isExistingNexusDir: false };
17904
18072
  const nonEmpty = isNonEmpty(target);
17905
18073
  const stat2 = statSync3(target);
17906
- const isExistingNexusDir = stat2.isDirectory() && existsSync18(join14(target, "audit"));
18074
+ const isExistingNexusDir = stat2.isDirectory() && existsSync20(join16(target, "audit"));
17907
18075
  return { exists, nonEmpty, isExistingNexusDir };
17908
18076
  }
17909
18077
  function createDataLayout(target, dryRun, created, alreadyExisted) {
17910
18078
  ensureDir(target, dryRun, created, alreadyExisted);
17911
18079
  for (const subdir of DATA_SUBDIRECTORIES) {
17912
18080
  const mode = RESTRICTED_SUBDIRS.has(subdir) ? 448 : void 0;
17913
- ensureDir(join14(target, subdir), dryRun, created, alreadyExisted, mode);
18081
+ ensureDir(join16(target, subdir), dryRun, created, alreadyExisted, mode);
17914
18082
  }
17915
18083
  }
17916
18084
  function makeResult(opts) {
@@ -17922,6 +18090,8 @@ function makeResult(opts) {
17922
18090
  skipped: opts.skipped ?? false,
17923
18091
  gitignoreUpdated: opts.gitignoreUpdated ?? false,
17924
18092
  ...opts.mcpConfig !== void 0 ? { mcpConfig: opts.mcpConfig } : {},
18093
+ ...opts.install !== void 0 ? { install: opts.install } : {},
18094
+ ...opts.uninstall !== void 0 ? { uninstall: opts.uninstall } : {},
17925
18095
  error: opts.error ?? null
17926
18096
  };
17927
18097
  }
@@ -17934,23 +18104,60 @@ function applyGitignoreOption(target, options, dryRun) {
17934
18104
  function applyMcpConfigOption(target, options, dryRun) {
17935
18105
  if (options.mcpConfig !== true) return void 0;
17936
18106
  const workspaceDir = resolve11(target, "..");
18107
+ const shimPath = findBinShim(target);
17937
18108
  return emitMcpConfig({
17938
18109
  workspaceDir,
17939
18110
  dataDir: target,
18111
+ ...shimPath !== void 0 && { commandPath: shimPath },
17940
18112
  force: options.force === true,
17941
18113
  dryRun
17942
18114
  });
17943
18115
  }
17944
- function buildSuccessResult(base, flags, mcpConfig) {
17945
- if (mcpConfig === void 0) {
17946
- return makeResult({ ...base, ...flags, success: true });
18116
+ async function applyInstallOption(target, options, dryRun) {
18117
+ if (options.install !== true) return void 0;
18118
+ return installPortable({ dataDir: target, force: options.force === true, dryRun });
18119
+ }
18120
+ function buildSuccessResult(base, flags, extras) {
18121
+ const installFailed = extras.install !== void 0 && !extras.install.success;
18122
+ const mcpFailed = extras.mcpConfig !== void 0 && !extras.mcpConfig.success;
18123
+ if (installFailed) {
18124
+ return makeResult({
18125
+ ...base,
18126
+ ...flags,
18127
+ success: false,
18128
+ ...extras,
18129
+ error: extras.install?.error ?? "install failed"
18130
+ });
17947
18131
  }
17948
- if (mcpConfig.success) {
17949
- return makeResult({ ...base, ...flags, success: true, mcpConfig });
18132
+ if (mcpFailed) {
18133
+ return makeResult({
18134
+ ...base,
18135
+ ...flags,
18136
+ success: false,
18137
+ ...extras,
18138
+ error: extras.mcpConfig?.error ?? "mcp-config emission failed"
18139
+ });
17950
18140
  }
17951
- return makeResult({ ...base, ...flags, success: false, mcpConfig, error: mcpConfig.error });
18141
+ return makeResult({ ...base, ...flags, success: true, ...extras });
18142
+ }
18143
+ async function collectExtras(target, options, dryRun) {
18144
+ const extras = {};
18145
+ const install = await applyInstallOption(target, options, dryRun);
18146
+ if (install !== void 0) extras.install = install;
18147
+ const mcpConfig = applyMcpConfigOption(target, options, dryRun);
18148
+ if (mcpConfig !== void 0) extras.mcpConfig = mcpConfig;
18149
+ return extras;
18150
+ }
18151
+ function handleUninstall(target, base, dryRun) {
18152
+ const uninstall = uninstallPortable({ dataDir: target, dryRun });
18153
+ return makeResult({
18154
+ ...base,
18155
+ success: uninstall.success,
18156
+ uninstall,
18157
+ error: uninstall.error
18158
+ });
17952
18159
  }
17953
- function initPortable(options = {}) {
18160
+ async function initPortable(options = {}) {
17954
18161
  const created = [];
17955
18162
  const alreadyExisted = [];
17956
18163
  const dryRun = options.dryRun === true;
@@ -17958,14 +18165,12 @@ function initPortable(options = {}) {
17958
18165
  const target = resolveTargetPath(options.path);
17959
18166
  const base = { absolutePath: target, created, alreadyExisted };
17960
18167
  try {
18168
+ if (options.uninstall === true) return handleUninstall(target, base, dryRun);
17961
18169
  const state = inspectTarget(target);
17962
18170
  if (state.isExistingNexusDir && !force) {
17963
18171
  createDataLayout(target, dryRun, created, alreadyExisted);
17964
- return buildSuccessResult(
17965
- base,
17966
- { skipped: true },
17967
- applyMcpConfigOption(target, options, dryRun)
17968
- );
18172
+ const extras2 = await collectExtras(target, options, dryRun);
18173
+ return buildSuccessResult(base, { skipped: true }, extras2);
17969
18174
  }
17970
18175
  if (state.nonEmpty && !state.isExistingNexusDir && !force) {
17971
18176
  const error = `target ${target} already exists and is not empty; pass --force to use anyway`;
@@ -17973,11 +18178,8 @@ function initPortable(options = {}) {
17973
18178
  }
17974
18179
  createDataLayout(target, dryRun, created, alreadyExisted);
17975
18180
  const gitignoreUpdated = applyGitignoreOption(target, options, dryRun);
17976
- return buildSuccessResult(
17977
- base,
17978
- { gitignoreUpdated },
17979
- applyMcpConfigOption(target, options, dryRun)
17980
- );
18181
+ const extras = await collectExtras(target, options, dryRun);
18182
+ return buildSuccessResult(base, { gitignoreUpdated }, extras);
17981
18183
  } catch (error) {
17982
18184
  const msg = error instanceof Error ? error.message : String(error);
17983
18185
  return makeResult({ ...base, success: false, error: msg });
@@ -18004,11 +18206,34 @@ function renderMcpConfigCaveat(mcpConfig) {
18004
18206
  "run `nexus-agents init --portable --mcp-config` themselves."
18005
18207
  ];
18006
18208
  }
18209
+ function renderInstallLines(install) {
18210
+ if (install.skipped) return [`\u2713 Portable install already present (${install.version})`];
18211
+ return [
18212
+ `\u2713 Installed nexus-agents@${install.version} \u2192 ${install.cliDir}`,
18213
+ `\u2713 Wrote bin shim \u2192 ${install.shim?.shimPath ?? install.binDir + "/nexus-agents"}`
18214
+ ];
18215
+ }
18216
+ function renderUninstallLines(uninstall) {
18217
+ const lines = [];
18218
+ if (uninstall.removed.length === 0 && uninstall.notPresent.length > 0) {
18219
+ lines.push("Nothing to uninstall \u2014 cli/ and bin/ were not present.");
18220
+ }
18221
+ for (const r of uninstall.removed) lines.push(`\u2713 Removed: ${r}`);
18222
+ if (uninstall.removed.length > 0) {
18223
+ lines.push("");
18224
+ lines.push("Note: data subdirs (memory, audit, voting, sessions, \u2026) preserved.");
18225
+ lines.push("To purge data too, remove the parent dir manually.");
18226
+ }
18227
+ return lines;
18228
+ }
18007
18229
  function formatInitPortableMessage(result, dryRun) {
18008
18230
  if (!result.success) {
18009
18231
  return `init --portable failed: ${result.error ?? "unknown error"}
18010
18232
  `;
18011
18233
  }
18234
+ if (result.uninstall !== void 0) {
18235
+ return renderUninstallLines(result.uninstall).join("\n") + "\n";
18236
+ }
18012
18237
  if (dryRun) {
18013
18238
  const lines2 = [
18014
18239
  `(dry-run) would create ${String(result.created.length)} entries under:`,
@@ -18021,6 +18246,7 @@ function formatInitPortableMessage(result, dryRun) {
18021
18246
  result.skipped ? `\u2713 Already initialized: ${result.absolutePath}` : `\u2713 Created: ${result.absolutePath}`
18022
18247
  );
18023
18248
  if (result.gitignoreUpdated) lines.push(`\u2713 Added entry to .gitignore`);
18249
+ if (result.install !== void 0) lines.push(...renderInstallLines(result.install));
18024
18250
  if (result.mcpConfig !== void 0) lines.push(...renderMcpConfigLines(result.mcpConfig));
18025
18251
  lines.push("");
18026
18252
  lines.push("Activate by exporting:");
@@ -20843,8 +21069,8 @@ function printFirstRunHint() {
20843
21069
  const isTTY = process.stderr.isTTY;
20844
21070
  if (!isTTY) return;
20845
21071
  const dataDir = getNexusDataDir();
20846
- const hasConfig = existsSync20("./nexus-agents.yaml") || existsSync20("./nexus-agents.yml");
20847
- if (existsSync20(dataDir) || hasConfig) return;
21072
+ const hasConfig = existsSync22("./nexus-agents.yaml") || existsSync22("./nexus-agents.yml");
21073
+ if (existsSync22(dataDir) || hasConfig) return;
20848
21074
  process.stderr.write(
20849
21075
  "\n\x1B[36mnexus-agents\x1B[0m: First time? Run \x1B[1mnexus-agents setup\x1B[0m to configure.\n\n"
20850
21076
  );
@@ -21010,20 +21236,29 @@ async function handleDoctorCommand(args) {
21010
21236
  }
21011
21237
  process.exit(exitCode === 0 ? EXIT_CODES.SUCCESS : EXIT_CODES.SERVER_START_FAILED);
21012
21238
  }
21013
- function handleInitCommand(args) {
21239
+ function validateInitFlags(args) {
21014
21240
  if (args.options.portable !== true) {
21015
21241
  process.stderr.write(
21016
- "Usage: nexus-agents init --portable [path] [--force] [--dry-run] [--gitignore]\nBootstraps a workspace-local nexus-agents data directory.\n"
21242
+ "Usage: nexus-agents init --portable [path] [--force] [--dry-run]\n [--gitignore] [--mcp-config]\n [--install | --uninstall]\nBootstraps a workspace-local nexus-agents data directory.\n"
21017
21243
  );
21018
21244
  process.exit(EXIT_CODES.INVALID_ARGS);
21019
21245
  }
21246
+ if (args.options.install === true && args.options.uninstall === true) {
21247
+ process.stderr.write("Error: --install and --uninstall are mutually exclusive.\n");
21248
+ process.exit(EXIT_CODES.INVALID_ARGS);
21249
+ }
21250
+ }
21251
+ async function handleInitCommand(args) {
21252
+ validateInitFlags(args);
21020
21253
  const targetPath = args.positionals[1];
21021
- const result = initPortable({
21254
+ const result = await initPortable({
21022
21255
  ...targetPath !== void 0 && targetPath !== "" ? { path: targetPath } : {},
21023
21256
  force: args.options.force,
21024
21257
  dryRun: args.options.dryRun,
21025
21258
  gitignore: args.options.gitignore ?? false,
21026
- mcpConfig: args.options.mcpConfig ?? false
21259
+ mcpConfig: args.options.mcpConfig ?? false,
21260
+ install: args.options.install ?? false,
21261
+ uninstall: args.options.uninstall ?? false
21027
21262
  });
21028
21263
  process.stdout.write(formatInitPortableMessage(result, args.options.dryRun));
21029
21264
  process.exit(result.success ? EXIT_CODES.SUCCESS : EXIT_CODES.SERVER_START_FAILED);
@@ -22501,7 +22736,7 @@ function handleStatusCommand2(args) {
22501
22736
 
22502
22737
  // src/cli/scenario-command.ts
22503
22738
  import { readdir as readdir4 } from "fs/promises";
22504
- import { join as join16, resolve as resolve14 } from "path";
22739
+ import { join as join18, resolve as resolve14 } from "path";
22505
22740
 
22506
22741
  // src/testing/e2e/scenario-runner.ts
22507
22742
  import { readFile as readFile7 } from "fs/promises";
@@ -22871,7 +23106,7 @@ async function handleRun(args) {
22871
23106
  process.exit(EXIT_CODES.SERVER_START_FAILED);
22872
23107
  }
22873
23108
  const runner = createScenarioRunner();
22874
- const fixturePath = join16(FIXTURES_DIR, `${name}${SCENARIO_SUFFIX}`);
23109
+ const fixturePath = join18(FIXTURES_DIR, `${name}${SCENARIO_SUFFIX}`);
22875
23110
  try {
22876
23111
  const fixture = await runner.loadFixture(fixturePath);
22877
23112
  const result = await runner.run(fixture);
@@ -23134,8 +23369,6 @@ var SYNC_COMMAND_HANDLERS = {
23134
23369
  status: handleStatusCommand2,
23135
23370
  // Issue #1023: Warm-Up Command
23136
23371
  "warm-up": handleWarmUpCommand,
23137
- // #2305: Init Portable Command
23138
- init: handleInitCommand,
23139
23372
  "e2e-eval": handleE2EEvalCommand,
23140
23373
  "routing-ab": handleRoutingABCommand,
23141
23374
  "memory-eval": handleMemoryEvalCommand,
@@ -23175,6 +23408,8 @@ var ASYNC_COMMAND_HANDLERS = {
23175
23408
  hooks: handleHooksCommand,
23176
23409
  setup: handleSetupCommandAsync,
23177
23410
  // Uses async for interactive wizard support (Issue #425)
23411
+ // #2305 / #2308 / #2311: Init Portable Command (async because --install spawns npm)
23412
+ init: handleInitCommand,
23178
23413
  demo: handleDemoCommand,
23179
23414
  // Made async for live CLI execution
23180
23415
  // Issue #526: Newly wired async commands
@@ -23590,7 +23825,9 @@ function buildInitOptions(values) {
23590
23825
  return {
23591
23826
  portable: values.portable,
23592
23827
  gitignore: values.gitignore,
23593
- mcpConfig: values["mcp-config"]
23828
+ mcpConfig: values["mcp-config"],
23829
+ install: values.install,
23830
+ uninstall: values.uninstall
23594
23831
  };
23595
23832
  }
23596
23833
  function parseCliArgs(args = process.argv.slice(2)) {