nexrall-code 0.5.49 → 0.5.51

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.
Files changed (2) hide show
  1. package/dist/index.js +1106 -202
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1773,13 +1773,13 @@ Expecting one of '${allowedValues.join("', '")}'`);
1773
1773
  * @param {string} source - expected values are default/config/env/cli/implied
1774
1774
  * @return {Command} `this` command for chaining
1775
1775
  */
1776
- setOptionValueWithSource(key, value, source) {
1776
+ setOptionValueWithSource(key, value, source2) {
1777
1777
  if (this._storeOptionsAsProperties) {
1778
1778
  this[key] = value;
1779
1779
  } else {
1780
1780
  this._optionValues[key] = value;
1781
1781
  }
1782
- this._optionValueSources[key] = source;
1782
+ this._optionValueSources[key] = source2;
1783
1783
  return this;
1784
1784
  }
1785
1785
  /**
@@ -1800,13 +1800,13 @@ Expecting one of '${allowedValues.join("', '")}'`);
1800
1800
  * @return {string}
1801
1801
  */
1802
1802
  getOptionValueSourceWithGlobals(key) {
1803
- let source;
1803
+ let source2;
1804
1804
  this._getCommandAndAncestors().forEach((cmd) => {
1805
1805
  if (cmd.getOptionValueSource(key) !== void 0) {
1806
- source = cmd.getOptionValueSource(key);
1806
+ source2 = cmd.getOptionValueSource(key);
1807
1807
  }
1808
1808
  });
1809
- return source;
1809
+ return source2;
1810
1810
  }
1811
1811
  /**
1812
1812
  * Get user arguments from implied or explicit arguments.
@@ -2615,8 +2615,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
2615
2615
  const getErrorMessage = (option2) => {
2616
2616
  const bestOption = findBestOptionFromValue(option2);
2617
2617
  const optionKey = bestOption.attributeName();
2618
- const source = this.getOptionValueSource(optionKey);
2619
- if (source === "env") {
2618
+ const source2 = this.getOptionValueSource(optionKey);
2619
+ if (source2 === "env") {
2620
2620
  return `environment variable '${bestOption.envVar}'`;
2621
2621
  }
2622
2622
  return `option '${bestOption.flags}'`;
@@ -5998,10 +5998,10 @@ var require_ponyfill_es2018 = __commonJS({
5998
5998
  return ctor;
5999
5999
  }
6000
6000
  const DOMException2 = getFromGlobal() || createPolyfill();
6001
- function ReadableStreamPipeTo(source, dest, preventClose, preventAbort, preventCancel, signal) {
6002
- const reader = AcquireReadableStreamDefaultReader(source);
6001
+ function ReadableStreamPipeTo(source2, dest, preventClose, preventAbort, preventCancel, signal) {
6002
+ const reader = AcquireReadableStreamDefaultReader(source2);
6003
6003
  const writer = AcquireWritableStreamDefaultWriter(dest);
6004
- source._disturbed = true;
6004
+ source2._disturbed = true;
6005
6005
  let shuttingDown = false;
6006
6006
  let currentWrite = promiseResolvedWith(void 0);
6007
6007
  return newPromise((resolve3, reject) => {
@@ -6020,8 +6020,8 @@ var require_ponyfill_es2018 = __commonJS({
6020
6020
  }
6021
6021
  if (!preventCancel) {
6022
6022
  actions.push(() => {
6023
- if (source._state === "readable") {
6024
- return ReadableStreamCancel(source, error);
6023
+ if (source2._state === "readable") {
6024
+ return ReadableStreamCancel(source2, error);
6025
6025
  }
6026
6026
  return promiseResolvedWith(void 0);
6027
6027
  });
@@ -6063,7 +6063,7 @@ var require_ponyfill_es2018 = __commonJS({
6063
6063
  });
6064
6064
  });
6065
6065
  }
6066
- isOrBecomesErrored(source, reader._closedPromise, (storedError) => {
6066
+ isOrBecomesErrored(source2, reader._closedPromise, (storedError) => {
6067
6067
  if (!preventAbort) {
6068
6068
  shutdownWithAction(() => WritableStreamAbort(dest, storedError), true, storedError);
6069
6069
  } else {
@@ -6073,13 +6073,13 @@ var require_ponyfill_es2018 = __commonJS({
6073
6073
  });
6074
6074
  isOrBecomesErrored(dest, writer._closedPromise, (storedError) => {
6075
6075
  if (!preventCancel) {
6076
- shutdownWithAction(() => ReadableStreamCancel(source, storedError), true, storedError);
6076
+ shutdownWithAction(() => ReadableStreamCancel(source2, storedError), true, storedError);
6077
6077
  } else {
6078
6078
  shutdown(true, storedError);
6079
6079
  }
6080
6080
  return null;
6081
6081
  });
6082
- isOrBecomesClosed(source, reader._closedPromise, () => {
6082
+ isOrBecomesClosed(source2, reader._closedPromise, () => {
6083
6083
  if (!preventClose) {
6084
6084
  shutdownWithAction(() => WritableStreamDefaultWriterCloseWithErrorPropagation(writer));
6085
6085
  } else {
@@ -6090,7 +6090,7 @@ var require_ponyfill_es2018 = __commonJS({
6090
6090
  if (WritableStreamCloseQueuedOrInFlight(dest) || dest._state === "closed") {
6091
6091
  const destClosed = new TypeError("the destination writable stream closed before all data could be piped to it");
6092
6092
  if (!preventCancel) {
6093
- shutdownWithAction(() => ReadableStreamCancel(source, destClosed), true, destClosed);
6093
+ shutdownWithAction(() => ReadableStreamCancel(source2, destClosed), true, destClosed);
6094
6094
  } else {
6095
6095
  shutdown(true, destClosed);
6096
6096
  }
@@ -6726,11 +6726,11 @@ var require_ponyfill_es2018 = __commonJS({
6726
6726
  function isReadableStreamLike(stream) {
6727
6727
  return typeIsObject(stream) && typeof stream.getReader !== "undefined";
6728
6728
  }
6729
- function ReadableStreamFrom(source) {
6730
- if (isReadableStreamLike(source)) {
6731
- return ReadableStreamFromDefaultReader(source.getReader());
6729
+ function ReadableStreamFrom(source2) {
6730
+ if (isReadableStreamLike(source2)) {
6731
+ return ReadableStreamFromDefaultReader(source2.getReader());
6732
6732
  }
6733
- return ReadableStreamFromIterable(source);
6733
+ return ReadableStreamFromIterable(source2);
6734
6734
  }
6735
6735
  function ReadableStreamFromIterable(asyncIterable) {
6736
6736
  let stream;
@@ -6817,9 +6817,9 @@ var require_ponyfill_es2018 = __commonJS({
6817
6817
  stream = CreateReadableStream(startAlgorithm, pullAlgorithm, cancelAlgorithm, 0);
6818
6818
  return stream;
6819
6819
  }
6820
- function convertUnderlyingDefaultOrByteSource(source, context) {
6821
- assertDictionary(source, context);
6822
- const original = source;
6820
+ function convertUnderlyingDefaultOrByteSource(source2, context) {
6821
+ assertDictionary(source2, context);
6822
+ const original = source2;
6823
6823
  const autoAllocateChunkSize = original === null || original === void 0 ? void 0 : original.autoAllocateChunkSize;
6824
6824
  const cancel = original === null || original === void 0 ? void 0 : original.cancel;
6825
6825
  const pull = original === null || original === void 0 ? void 0 : original.pull;
@@ -11201,6 +11201,8 @@ var require_plugins = __commonJS({
11201
11201
  };
11202
11202
  }();
11203
11203
  Object.defineProperty(exports, "__esModule", { value: true });
11204
+ exports.FILE_CANDIDATES = void 0;
11205
+ exports.resolvePluginFile = resolvePluginFile;
11204
11206
  exports.loadPlugins = loadPlugins3;
11205
11207
  exports.pluginAssetDirs = pluginAssetDirs;
11206
11208
  exports.pluginHooks = pluginHooks;
@@ -11208,9 +11210,28 @@ var require_plugins = __commonJS({
11208
11210
  var fs9 = __importStar(__require("fs"));
11209
11211
  var path6 = __importStar(__require("path"));
11210
11212
  var os6 = __importStar(__require("os"));
11213
+ exports.FILE_CANDIDATES = {
11214
+ manifest: ["plugin.json", path6.join(".claude-plugin", "plugin.json")],
11215
+ hooks: ["hooks.json", path6.join("hooks", "hooks.json")],
11216
+ mcp: ["mcp.json", ".mcp.json"]
11217
+ };
11218
+ function resolvePluginFile(dir, kind) {
11219
+ for (const rel of exports.FILE_CANDIDATES[kind]) {
11220
+ const full = path6.join(dir, rel);
11221
+ try {
11222
+ if (fs9.statSync(full).isFile())
11223
+ return full;
11224
+ } catch {
11225
+ }
11226
+ }
11227
+ return null;
11228
+ }
11211
11229
  function readMeta(dir) {
11230
+ const file = resolvePluginFile(dir, "manifest");
11231
+ if (!file)
11232
+ return {};
11212
11233
  try {
11213
- const raw = fs9.readFileSync(path6.join(dir, "plugin.json"), "utf-8");
11234
+ const raw = fs9.readFileSync(file, "utf-8");
11214
11235
  const j = JSON.parse(raw);
11215
11236
  return {
11216
11237
  name: typeof j.name === "string" ? j.name : void 0,
@@ -11263,8 +11284,11 @@ var require_plugins = __commonJS({
11263
11284
  function pluginHooks(workDir) {
11264
11285
  const merged = {};
11265
11286
  for (const p of loadPlugins3(workDir)) {
11287
+ const file = resolvePluginFile(p.dir, "hooks");
11288
+ if (!file)
11289
+ continue;
11266
11290
  try {
11267
- const raw = fs9.readFileSync(path6.join(p.dir, "hooks.json"), "utf-8");
11291
+ const raw = fs9.readFileSync(file, "utf-8");
11268
11292
  const j = JSON.parse(raw);
11269
11293
  const hooks = j.hooks ?? j;
11270
11294
  for (const [phase, entries] of Object.entries(hooks)) {
@@ -11280,8 +11304,11 @@ var require_plugins = __commonJS({
11280
11304
  function pluginMcpServers(workDir) {
11281
11305
  const merged = {};
11282
11306
  for (const p of loadPlugins3(workDir)) {
11307
+ const file = resolvePluginFile(p.dir, "mcp");
11308
+ if (!file)
11309
+ continue;
11283
11310
  try {
11284
- const raw = fs9.readFileSync(path6.join(p.dir, "mcp.json"), "utf-8");
11311
+ const raw = fs9.readFileSync(file, "utf-8");
11285
11312
  const j = JSON.parse(raw);
11286
11313
  const servers = j.mcpServers ?? j;
11287
11314
  for (const [name, cfg] of Object.entries(servers)) {
@@ -11400,7 +11427,7 @@ var require_loader = __commonJS({
11400
11427
  }
11401
11428
  return { meta, body: (m2[2] ?? "").trim() };
11402
11429
  }
11403
- function loadDir(dir, source, into) {
11430
+ function loadDir(dir, source2, into) {
11404
11431
  let files;
11405
11432
  try {
11406
11433
  files = fs9.readdirSync(dir).filter((f3) => f3.endsWith(".md"));
@@ -11414,7 +11441,7 @@ var require_loader = __commonJS({
11414
11441
  const name = (meta.name || path6.basename(file, ".md")).trim().toLowerCase();
11415
11442
  if (!name)
11416
11443
  continue;
11417
- if (source !== "project" && into.has(name))
11444
+ if (source2 !== "project" && into.has(name))
11418
11445
  continue;
11419
11446
  const model = ["turbo", "pro", "ultra"].find((x2) => x2 === (meta.model ?? "").toLowerCase());
11420
11447
  into.set(name, {
@@ -11423,7 +11450,7 @@ var require_loader = __commonJS({
11423
11450
  model,
11424
11451
  mode: meta.mode || void 0,
11425
11452
  body,
11426
- source
11453
+ source: source2
11427
11454
  });
11428
11455
  } catch {
11429
11456
  }
@@ -11569,7 +11596,7 @@ var require_skills = __commonJS({
11569
11596
  return false;
11570
11597
  return fallback;
11571
11598
  }
11572
- function toSkill(meta, body, name, source, dir) {
11599
+ function toSkill(meta, body, name, source2, dir) {
11573
11600
  const model = ["turbo", "pro", "ultra"].find((x2) => x2 === (meta.model ?? "").toLowerCase());
11574
11601
  return {
11575
11602
  name,
@@ -11577,13 +11604,13 @@ var require_skills = __commonJS({
11577
11604
  model,
11578
11605
  mode: meta.mode || void 0,
11579
11606
  body,
11580
- source,
11607
+ source: source2,
11581
11608
  dir,
11582
11609
  disableModelInvocation: parseBool(meta["disable-model-invocation"], false),
11583
11610
  userInvocable: parseBool(meta["user-invocable"], true)
11584
11611
  };
11585
11612
  }
11586
- function loadFlatCommandDir(dir, source, into) {
11613
+ function loadFlatCommandDir(dir, source2, into) {
11587
11614
  let files;
11588
11615
  try {
11589
11616
  files = fs9.readdirSync(dir).filter((f3) => f3.endsWith(".md"));
@@ -11597,14 +11624,14 @@ var require_skills = __commonJS({
11597
11624
  const name = (meta.name || path6.basename(file, ".md")).trim().toLowerCase();
11598
11625
  if (!name)
11599
11626
  continue;
11600
- if (source !== "project" && into.has(name))
11627
+ if (source2 !== "project" && into.has(name))
11601
11628
  continue;
11602
- into.set(name, toSkill(meta, body, name, source));
11629
+ into.set(name, toSkill(meta, body, name, source2));
11603
11630
  } catch {
11604
11631
  }
11605
11632
  }
11606
11633
  }
11607
- function loadSkillDir(root, source, into) {
11634
+ function loadSkillDir(root, source2, into) {
11608
11635
  let entries;
11609
11636
  try {
11610
11637
  entries = fs9.readdirSync(root, { withFileTypes: true });
@@ -11628,9 +11655,9 @@ var require_skills = __commonJS({
11628
11655
  const name = (meta.name || entry.name).trim().toLowerCase();
11629
11656
  if (!name)
11630
11657
  continue;
11631
- if (source !== "project" && into.has(name))
11658
+ if (source2 !== "project" && into.has(name))
11632
11659
  continue;
11633
- into.set(name, toSkill(meta, body, name, source, skillDir));
11660
+ into.set(name, toSkill(meta, body, name, source2, skillDir));
11634
11661
  } catch {
11635
11662
  }
11636
11663
  }
@@ -12164,12 +12191,12 @@ var require_symbols = __commonJS({
12164
12191
  return new Set(EXT_TO_LANG.keys());
12165
12192
  }
12166
12193
  var MAX_FILE_BYTES = 1024 * 1024;
12167
- function extractSymbols(filePath, source) {
12194
+ function extractSymbols(filePath, source2) {
12168
12195
  const lang = EXT_TO_LANG.get(path6.extname(filePath).toLowerCase());
12169
12196
  if (!lang)
12170
12197
  return [];
12171
12198
  const hits = [];
12172
- const lines = source.split("\n");
12199
+ const lines = source2.split("\n");
12173
12200
  let inBlockComment = false;
12174
12201
  for (let i2 = 0; i2 < lines.length; i2++) {
12175
12202
  const trimmed = lines[i2].trim();
@@ -12206,21 +12233,21 @@ var require_symbols = __commonJS({
12206
12233
  if (!rel)
12207
12234
  return { error: "path is required" };
12208
12235
  const abs = resolveIn(workDir, rel);
12209
- let source;
12236
+ let source2;
12210
12237
  try {
12211
12238
  const st = fs9.statSync(abs);
12212
12239
  if (!st.isFile())
12213
12240
  return { error: `Not a file: ${rel}` };
12214
12241
  if (st.size > MAX_FILE_BYTES)
12215
12242
  return { error: `File too large for symbol scan (${Math.round(st.size / 1024)} KB > 1024 KB)` };
12216
- source = fs9.readFileSync(abs, "utf-8");
12243
+ source2 = fs9.readFileSync(abs, "utf-8");
12217
12244
  } catch (err) {
12218
12245
  return { error: `Cannot read ${rel}: ${err.message}` };
12219
12246
  }
12220
12247
  if (!EXT_TO_LANG.has(path6.extname(abs).toLowerCase())) {
12221
12248
  return { output: `No symbol support for ${path6.extname(abs) || "this file type"} (regex-based scanner). Use search_files instead.` };
12222
12249
  }
12223
- const hits = extractSymbols(abs, source);
12250
+ const hits = extractSymbols(abs, source2);
12224
12251
  if (!hits.length)
12225
12252
  return { output: `No symbols found in ${rel}.` };
12226
12253
  const lines = hits.map((h2) => `${h2.kind.padEnd(10)} ${h2.name} (line ${h2.line})`);
@@ -12262,10 +12289,10 @@ ${lines.join("\n")}` };
12262
12289
  const st = fs9.statSync(full);
12263
12290
  if (st.size > MAX_FILE_BYTES)
12264
12291
  continue;
12265
- const source = fs9.readFileSync(full, "utf-8");
12266
- if (!source.toLowerCase().includes(q))
12292
+ const source2 = fs9.readFileSync(full, "utf-8");
12293
+ if (!source2.toLowerCase().includes(q))
12267
12294
  continue;
12268
- for (const h2 of extractSymbols(full, source)) {
12295
+ for (const h2 of extractSymbols(full, source2)) {
12269
12296
  if (h2.name.toLowerCase().includes(q)) {
12270
12297
  results.push({ ...h2, file: path6.relative(root, full) });
12271
12298
  if (results.length >= MAX_RESULTS)
@@ -12452,18 +12479,18 @@ var require_tsLangService = __commonJS({
12452
12479
  return false;
12453
12480
  }
12454
12481
  }
12455
- function posToOffset(source, line, character) {
12456
- const lines = source.split("\n");
12482
+ function posToOffset(source2, line, character) {
12483
+ const lines = source2.split("\n");
12457
12484
  let offset = 0;
12458
12485
  for (let i2 = 0; i2 < line - 1 && i2 < lines.length; i2++)
12459
12486
  offset += lines[i2].length + 1;
12460
12487
  return offset + Math.max(0, character - 1);
12461
12488
  }
12462
- function offsetToPos(source, offset) {
12489
+ function offsetToPos(source2, offset) {
12463
12490
  let line = 1;
12464
12491
  let last = 0;
12465
- for (let i2 = 0; i2 < offset && i2 < source.length; i2++) {
12466
- if (source[i2] === "\n") {
12492
+ for (let i2 = 0; i2 < offset && i2 < source2.length; i2++) {
12493
+ if (source2[i2] === "\n") {
12467
12494
  line++;
12468
12495
  last = i2 + 1;
12469
12496
  }
@@ -12480,8 +12507,8 @@ var require_tsLangService = __commonJS({
12480
12507
  return null;
12481
12508
  if (!ensureFile(svc, deps.filePath))
12482
12509
  return { error: `Cannot read ${deps.filePath}` };
12483
- const source = svc.snapshots.get(deps.filePath) ?? "";
12484
- const offset = posToOffset(source, deps.line, deps.character);
12510
+ const source2 = svc.snapshots.get(deps.filePath) ?? "";
12511
+ const offset = posToOffset(source2, deps.line, deps.character);
12485
12512
  let defs;
12486
12513
  try {
12487
12514
  defs = svc.service.getDefinitionAtPosition(deps.filePath, offset);
@@ -12504,8 +12531,8 @@ ${out.join("\n")}` };
12504
12531
  return null;
12505
12532
  if (!ensureFile(svc, deps.filePath))
12506
12533
  return { error: `Cannot read ${deps.filePath}` };
12507
- const source = svc.snapshots.get(deps.filePath) ?? "";
12508
- const offset = posToOffset(source, deps.line, deps.character);
12534
+ const source2 = svc.snapshots.get(deps.filePath) ?? "";
12535
+ const offset = posToOffset(source2, deps.line, deps.character);
12509
12536
  let refs;
12510
12537
  try {
12511
12538
  refs = svc.service.getReferencesAtPosition(deps.filePath, offset);
@@ -12532,8 +12559,8 @@ ${lines.join("\n")}` };
12532
12559
  return null;
12533
12560
  if (!ensureFile(svc, deps.filePath))
12534
12561
  return { error: `Cannot read ${deps.filePath}` };
12535
- const source = svc.snapshots.get(deps.filePath) ?? "";
12536
- const offset = posToOffset(source, deps.line, deps.character);
12562
+ const source2 = svc.snapshots.get(deps.filePath) ?? "";
12563
+ const offset = posToOffset(source2, deps.line, deps.character);
12537
12564
  let info;
12538
12565
  try {
12539
12566
  info = svc.service.getQuickInfoAtPosition(deps.filePath, offset);
@@ -13528,14 +13555,14 @@ ${globalCapMatches(output)}` : "";
13528
13555
  }
13529
13556
  }
13530
13557
  async function moveFile(input, workDir) {
13531
- const source = typeof input.source === "string" ? input.source : "";
13558
+ const source2 = typeof input.source === "string" ? input.source : "";
13532
13559
  const dest = typeof input.dest === "string" ? input.dest : "";
13533
- if (!source)
13560
+ if (!source2)
13534
13561
  return { error: "Missing required parameter: source" };
13535
13562
  if (!dest)
13536
13563
  return { error: "Missing required parameter: dest" };
13537
13564
  try {
13538
- const resolvedSource = resolvePath(source, workDir);
13565
+ const resolvedSource = resolvePath(source2, workDir);
13539
13566
  const resolvedDest = resolvePath(dest, workDir);
13540
13567
  fs9.mkdirSync(path6.dirname(resolvedDest), { recursive: true });
13541
13568
  try {
@@ -13680,14 +13707,14 @@ ${diff3}${xfile}${sec}` };
13680
13707
  }
13681
13708
  }
13682
13709
  async function copyFile(input, workDir) {
13683
- const source = typeof input.source === "string" ? input.source : "";
13710
+ const source2 = typeof input.source === "string" ? input.source : "";
13684
13711
  const destination = typeof input.destination === "string" ? input.destination : "";
13685
- if (!source)
13712
+ if (!source2)
13686
13713
  return { error: "Missing required parameter: source" };
13687
13714
  if (!destination)
13688
13715
  return { error: "Missing required parameter: destination" };
13689
13716
  try {
13690
- const resolvedSrc = resolvePath(source, workDir);
13717
+ const resolvedSrc = resolvePath(source2, workDir);
13691
13718
  const resolvedDest = resolvePath(destination, workDir);
13692
13719
  if (fs9.existsSync(resolvedDest) && fs9.statSync(resolvedDest).isDirectory()) {
13693
13720
  return { error: `Destination is a directory: ${resolvedDest}. Provide a full file path including the filename.` };
@@ -14133,7 +14160,7 @@ ${lines.join("\n")}`;
14133
14160
  const editType = typeof input.edit_type === "string" ? input.edit_type : "";
14134
14161
  const rawIdx = input.cell_index;
14135
14162
  const cellIndex = typeof rawIdx === "number" ? Math.floor(rawIdx) : typeof rawIdx === "string" && rawIdx.trim() !== "" ? parseInt(rawIdx, 10) : -1;
14136
- const source = typeof input.source === "string" ? input.source : null;
14163
+ const source2 = typeof input.source === "string" ? input.source : null;
14137
14164
  const cellType = typeof input.cell_type === "string" ? input.cell_type : "code";
14138
14165
  if (!filePath)
14139
14166
  return { error: "Missing required parameter: path" };
@@ -14152,10 +14179,10 @@ ${lines.join("\n")}`;
14152
14179
  if (editType === "replace") {
14153
14180
  if (cellIndex < 0 || cellIndex >= n)
14154
14181
  return { error: `cell_index ${cellIndex} out of range (0\u2013${n - 1})` };
14155
- if (source === null)
14182
+ if (source2 === null)
14156
14183
  return { error: "Missing required parameter: source (required for replace)" };
14157
14184
  const cell = nb.cells[cellIndex];
14158
- cell.source = source;
14185
+ cell.source = source2;
14159
14186
  if (cell.cell_type === "code") {
14160
14187
  cell.outputs = [];
14161
14188
  cell.execution_count = null;
@@ -14170,12 +14197,12 @@ ${lines.join("\n")}`;
14170
14197
  save();
14171
14198
  return { output: `Deleted cell ${cellIndex} [${removed.cell_type}] from ${resolved}. Notebook now has ${nb.cells.length} cells.` };
14172
14199
  }
14173
- if (source === null)
14200
+ if (source2 === null)
14174
14201
  return { error: "Missing required parameter: source (required for insert)" };
14175
14202
  const insertAt = cellIndex >= 0 ? Math.min(cellIndex + 1, n) : n;
14176
14203
  const newCell = {
14177
14204
  cell_type: cellType,
14178
- source,
14205
+ source: source2,
14179
14206
  metadata: {},
14180
14207
  ...cellType === "code" ? { execution_count: null, outputs: [] } : {}
14181
14208
  };
@@ -14542,7 +14569,10 @@ var require_agentTypes = __commonJS({
14542
14569
  };
14543
14570
  }();
14544
14571
  Object.defineProperty(exports, "__esModule", { value: true });
14545
- exports.loadAgentTypes = loadAgentTypes2;
14572
+ exports.loadAgentTypes = loadAgentTypes;
14573
+ exports.loadAgentTypesWithWarnings = loadAgentTypesWithWarnings2;
14574
+ exports.knownToolNames = knownToolNames;
14575
+ exports.builtinAgents = builtinAgents;
14546
14576
  exports.summariseAgents = summariseAgents;
14547
14577
  exports.findAgentType = findAgentType;
14548
14578
  var fs9 = __importStar(__require("fs"));
@@ -14557,9 +14587,17 @@ var require_agentTypes = __commonJS({
14557
14587
  "list_directory",
14558
14588
  "bash",
14559
14589
  "bash_output",
14590
+ // kill_shell belongs next to bash_output: an agent that can start a background
14591
+ // process and poll it but never stop it leaks that process past its own
14592
+ // lifetime. Stopping a shell you started is not a write to the repo.
14593
+ "kill_shell",
14560
14594
  "notebook_read",
14561
14595
  "todo_write",
14562
14596
  "todo_read",
14597
+ // Skills are reusable prompt playbooks, and loop.ts advertises the skills
14598
+ // catalogue to sub-agents at EVERY depth — so withholding the tool that loads
14599
+ // one meant showing every sub-agent a menu it could not order from.
14600
+ "use_skill",
14563
14601
  // VS Code language server (ignored on the CLI)
14564
14602
  "get_symbols",
14565
14603
  "get_workspace_symbols",
@@ -14568,7 +14606,7 @@ var require_agentTypes = __commonJS({
14568
14606
  "get_hover",
14569
14607
  "get_diagnostics"
14570
14608
  ];
14571
- var RESEARCH_TOOLS = [...READ_ONLY_TOOLS, "web_search", "fetch_url"];
14609
+ var RESEARCH_TOOLS = [...READ_ONLY_TOOLS, "fetch_url"];
14572
14610
  var WRITE_TOOLS = [
14573
14611
  ...READ_ONLY_TOOLS,
14574
14612
  "write_file",
@@ -14576,7 +14614,11 @@ var require_agentTypes = __commonJS({
14576
14614
  "multi_edit",
14577
14615
  "create_directory",
14578
14616
  "move_file",
14579
- "copy_file"
14617
+ "copy_file",
14618
+ // notebook_edit is a write tool like any other, and allowsTestOnlyWrite already
14619
+ // knows its shape (`source` is cell CONTENT, not a path). Omitting it just meant
14620
+ // test-writer silently could not touch notebooks.
14621
+ "notebook_edit"
14580
14622
  ];
14581
14623
  var BUILTIN_AGENTS = [
14582
14624
  {
@@ -14756,17 +14798,61 @@ var require_agentTypes = __commonJS({
14756
14798
  ].join("\n")
14757
14799
  }
14758
14800
  ];
14801
+ var KNOWN_TOOL_NAMES = /* @__PURE__ */ new Set([
14802
+ "read_file",
14803
+ "write_file",
14804
+ "edit_file",
14805
+ "multi_edit",
14806
+ "list_directory",
14807
+ "create_directory",
14808
+ "move_file",
14809
+ "copy_file",
14810
+ "delete_file",
14811
+ "search_files",
14812
+ "glob",
14813
+ "bash",
14814
+ "bash_output",
14815
+ "kill_shell",
14816
+ "notebook_read",
14817
+ "notebook_edit",
14818
+ "todo_write",
14819
+ "todo_read",
14820
+ "memory_write",
14821
+ "memory_read",
14822
+ "use_skill",
14823
+ "fetch_url",
14824
+ "web_search",
14825
+ "generate_image",
14826
+ "stock_photo",
14827
+ "open_in_browser",
14828
+ "task",
14829
+ "get_symbols",
14830
+ "get_workspace_symbols",
14831
+ "find_references",
14832
+ "go_to_definition",
14833
+ "get_hover",
14834
+ "get_diagnostics"
14835
+ ]);
14836
+ var KNOWN_META_KEYS = /* @__PURE__ */ new Set([
14837
+ "name",
14838
+ "description",
14839
+ "tools",
14840
+ "model",
14841
+ "test_files_only",
14842
+ "testfilesonly"
14843
+ ]);
14844
+ var VALID_MODELS = ["turbo", "pro", "ultra"];
14759
14845
  function parseFrontmatter(raw) {
14760
14846
  const m2 = /^\s*---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/.exec(raw);
14761
14847
  if (!m2)
14762
- return { meta: {}, body: raw.trim() };
14848
+ return { meta: {}, body: raw.trim(), ok: false };
14763
14849
  const meta = {};
14764
14850
  for (const line of m2[1].split(/\r?\n/)) {
14765
14851
  const kv = /^([A-Za-z0-9_-]+)\s*:\s*(.*)$/.exec(line.trim());
14766
14852
  if (kv)
14767
14853
  meta[kv[1].toLowerCase()] = kv[2].trim().replace(/^["']|["']$/g, "");
14768
14854
  }
14769
- return { meta, body: (m2[2] ?? "").trim() };
14855
+ return { meta, body: (m2[2] ?? "").trim(), ok: true };
14770
14856
  }
14771
14857
  function parseModel(v) {
14772
14858
  const s2 = (v ?? "").toLowerCase();
@@ -14781,51 +14867,113 @@ var require_agentTypes = __commonJS({
14781
14867
  const tools = v.replace(/^\[|\]$/g, "").split(/[,\s]+/).map((t2) => t2.trim()).filter(Boolean);
14782
14868
  return tools.length ? tools : void 0;
14783
14869
  }
14784
- function loadDir(dir, source, into) {
14870
+ function loadDir(dir, source2, into, warnings) {
14785
14871
  let entries;
14786
14872
  try {
14787
- entries = fs9.readdirSync(dir).filter((f3) => f3.endsWith(".md"));
14873
+ entries = fs9.readdirSync(dir, { withFileTypes: true });
14788
14874
  } catch {
14789
14875
  return;
14790
14876
  }
14791
- for (const file of entries) {
14877
+ for (const entry of entries) {
14878
+ const full = path6.join(dir, entry.name);
14879
+ if (entry.isDirectory()) {
14880
+ loadDir(full, source2, into, warnings);
14881
+ continue;
14882
+ }
14883
+ if (!entry.name.endsWith(".md"))
14884
+ continue;
14885
+ let raw;
14792
14886
  try {
14793
- const raw = fs9.readFileSync(path6.join(dir, file), "utf-8");
14794
- const { meta, body } = parseFrontmatter(raw);
14795
- const name = (meta.name || path6.basename(file, ".md")).trim();
14796
- if (!name)
14797
- continue;
14798
- if (source !== "project" && into.has(name))
14799
- continue;
14800
- into.set(name, {
14801
- name,
14802
- description: meta.description || `Custom ${name} agent`,
14803
- tools: parseToolList(meta.tools),
14804
- model: parseModel(meta.model),
14805
- prompt: body,
14806
- source,
14807
- // Exposed to user/plugin definitions too — `test_files_only: true` (or
14808
- // `testFilesOnly`) lets anyone build a test-writing agent that genuinely
14809
- // cannot touch production source, rather than only the builtin getting
14810
- // that guarantee.
14811
- ...parseBool(meta.test_files_only ?? meta.testfilesonly) ? { testFilesOnly: true } : {}
14887
+ raw = fs9.readFileSync(full, "utf-8");
14888
+ } catch (err) {
14889
+ warnings.push({ file: full, agent: path6.basename(entry.name, ".md"), message: `could not be read (${err.message}) \u2014 this agent was skipped` });
14890
+ continue;
14891
+ }
14892
+ const { meta, body, ok } = parseFrontmatter(raw);
14893
+ const name = (meta.name || path6.basename(entry.name, ".md")).trim();
14894
+ if (!name)
14895
+ continue;
14896
+ if (source2 !== "project" && into.has(name))
14897
+ continue;
14898
+ const tools = parseToolList(meta.tools);
14899
+ let effectiveTools = tools;
14900
+ if (!ok) {
14901
+ effectiveTools = READ_ONLY_TOOLS;
14902
+ warnings.push({
14903
+ file: full,
14904
+ agent: name,
14905
+ message: "has no valid YAML frontmatter (a `---` block must be the first thing in the file), so no tool allowlist could be read. Treating it as READ-ONLY. Add frontmatter with a `tools:` line to grant more."
14812
14906
  });
14813
- } catch {
14907
+ } else {
14908
+ if (!meta.description) {
14909
+ warnings.push({
14910
+ file: full,
14911
+ agent: name,
14912
+ message: "has no `description:` \u2014 that text is the ONLY thing the model uses to decide when to delegate to this agent, so it will rarely be picked."
14913
+ });
14914
+ }
14915
+ if (meta.model !== void 0 && parseModel(meta.model) === void 0) {
14916
+ warnings.push({
14917
+ file: full,
14918
+ agent: name,
14919
+ message: `has model: "${meta.model}", which is not valid \u2014 use one of ${VALID_MODELS.join(", ")}, or omit the line to inherit the current session's model.`
14920
+ });
14921
+ }
14922
+ const unknown = (tools ?? []).filter((t2) => !KNOWN_TOOL_NAMES.has(t2) && !t2.includes("__"));
14923
+ if (unknown.length) {
14924
+ warnings.push({
14925
+ file: full,
14926
+ agent: name,
14927
+ message: `lists unknown tool name(s): ${unknown.join(", ")}. An allowlist only grants, so these silently do nothing and the agent cannot use them. Tool names are lower_snake_case (read_file, search_files, glob, bash).`
14928
+ });
14929
+ }
14930
+ const strayKeys = Object.keys(meta).filter((k) => !KNOWN_META_KEYS.has(k));
14931
+ if (strayKeys.length) {
14932
+ warnings.push({
14933
+ file: full,
14934
+ agent: name,
14935
+ message: `has unrecognised frontmatter key(s): ${strayKeys.join(", ")} \u2014 these are ignored.`
14936
+ });
14937
+ }
14938
+ if (!body) {
14939
+ warnings.push({
14940
+ file: full,
14941
+ agent: name,
14942
+ message: "has an empty body \u2014 the text below the frontmatter IS the agent's system prompt, so it currently has no instructions."
14943
+ });
14944
+ }
14814
14945
  }
14946
+ into.set(name, {
14947
+ name,
14948
+ description: meta.description || `Custom ${name} agent`,
14949
+ tools: effectiveTools,
14950
+ model: parseModel(meta.model),
14951
+ prompt: body,
14952
+ source: source2,
14953
+ // Exposed to user/plugin definitions too — `test_files_only: true` (or
14954
+ // `testFilesOnly`) lets anyone build a test-writing agent that genuinely
14955
+ // cannot touch production source, rather than only the builtin getting
14956
+ // that guarantee.
14957
+ ...parseBool(meta.test_files_only ?? meta.testfilesonly) ? { testFilesOnly: true } : {}
14958
+ });
14815
14959
  }
14816
14960
  }
14817
- function loadAgentTypes2(workDir) {
14961
+ function loadAgentTypes(workDir) {
14962
+ return loadAgentTypesWithWarnings2(workDir).types;
14963
+ }
14964
+ function loadAgentTypesWithWarnings2(workDir) {
14818
14965
  const out = /* @__PURE__ */ new Map();
14819
- loadDir(path6.join(workDir, ".nexrall", "agents"), "project", out);
14820
- loadDir(path6.join(os6.homedir(), ".nexrall", "agents"), "global", out);
14966
+ const warnings = [];
14967
+ loadDir(path6.join(workDir, ".nexrall", "agents"), "project", out, warnings);
14968
+ loadDir(path6.join(os6.homedir(), ".nexrall", "agents"), "global", out, warnings);
14821
14969
  for (const dir of (0, index_1.pluginAssetDirs)(workDir, "agents"))
14822
- loadDir(dir, "plugin", out);
14970
+ loadDir(dir, "plugin", out, warnings);
14823
14971
  for (const agent of BUILTIN_AGENTS) {
14824
14972
  if (!out.has(agent.name))
14825
14973
  out.set(agent.name, agent);
14826
14974
  }
14827
14975
  const builtinOrder = new Map(BUILTIN_AGENTS.map((a, i2) => [a.name, i2]));
14828
- return [...out.values()].sort((a, b) => {
14976
+ const types3 = [...out.values()].sort((a, b) => {
14829
14977
  const ai = builtinOrder.get(a.name);
14830
14978
  const bi = builtinOrder.get(b.name);
14831
14979
  if (ai !== void 0 && bi !== void 0)
@@ -14836,6 +14984,13 @@ var require_agentTypes = __commonJS({
14836
14984
  return 1;
14837
14985
  return a.name.localeCompare(b.name);
14838
14986
  });
14987
+ return { types: types3, warnings };
14988
+ }
14989
+ function knownToolNames() {
14990
+ return [...KNOWN_TOOL_NAMES].sort();
14991
+ }
14992
+ function builtinAgents() {
14993
+ return BUILTIN_AGENTS;
14839
14994
  }
14840
14995
  function summariseAgents(types3) {
14841
14996
  if (!types3.length)
@@ -15037,6 +15192,9 @@ var require_rules = __commonJS({
15037
15192
  case "stock_photo":
15038
15193
  vals.push(str(input.path));
15039
15194
  break;
15195
+ case "task":
15196
+ vals.push(str(input.subagent_type));
15197
+ break;
15040
15198
  default:
15041
15199
  pushPath(str(input.path));
15042
15200
  break;
@@ -15077,6 +15235,350 @@ var require_rules = __commonJS({
15077
15235
  }
15078
15236
  });
15079
15237
 
15238
+ // ../core/dist/agent/planMode.js
15239
+ var require_planMode = __commonJS({
15240
+ "../core/dist/agent/planMode.js"(exports) {
15241
+ "use strict";
15242
+ Object.defineProperty(exports, "__esModule", { value: true });
15243
+ exports.PLAN_MODE_INSTRUCTIONS = void 0;
15244
+ exports.isReadOnlyCommand = isReadOnlyCommand;
15245
+ exports.checkPlanMode = checkPlanMode;
15246
+ var READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
15247
+ "read_file",
15248
+ "search_files",
15249
+ "glob",
15250
+ "list_directory",
15251
+ "notebook_read",
15252
+ // Planning bookkeeping. todo_write persists only to the session's own todo
15253
+ // list, not the repo, so it stays available — a plan mode that cannot draft
15254
+ // a checklist is missing the point of plan mode.
15255
+ "todo_write",
15256
+ "todo_read",
15257
+ // Reading memory is fine; memory_write is NOT here on purpose. Memory is
15258
+ // durable state that survives the session, so writing it is a real mutation
15259
+ // even though no file in the repo changes.
15260
+ "memory_read",
15261
+ "use_skill",
15262
+ "fetch_url",
15263
+ "web_search",
15264
+ // Delegating research is ALLOWED, and this is safe rather than a hole:
15265
+ // runSubTask passes planMode down, so the sub-agent is under the same lock and
15266
+ // its own write tools are refused by this very function one level deeper.
15267
+ //
15268
+ // Worth allowing rather than blanket-refusing, because plan mode exists to
15269
+ // support deep research and the explorer agent is the cheapest way to do bulk
15270
+ // searching without flooding the main context. Blocking `task` would have made
15271
+ // the mode's headline use case worse, for no security gain.
15272
+ "task",
15273
+ // VS Code language server — all pure queries.
15274
+ "get_symbols",
15275
+ "get_workspace_symbols",
15276
+ "find_references",
15277
+ "go_to_definition",
15278
+ "get_hover",
15279
+ "get_diagnostics",
15280
+ // Reading output from an ALREADY-running background shell. Starting one is
15281
+ // gated with the rest of bash below; draining a buffer is not a new effect.
15282
+ "bash_output"
15283
+ ]);
15284
+ var READ_ONLY_BASH = /* @__PURE__ */ new Set([
15285
+ "ls",
15286
+ "cat",
15287
+ "head",
15288
+ "tail",
15289
+ "wc",
15290
+ "file",
15291
+ "stat",
15292
+ "du",
15293
+ "df",
15294
+ "pwd",
15295
+ "which",
15296
+ "type",
15297
+ "echo",
15298
+ "printf",
15299
+ "basename",
15300
+ "dirname",
15301
+ "realpath",
15302
+ "readlink",
15303
+ "grep",
15304
+ "egrep",
15305
+ "fgrep",
15306
+ "rg",
15307
+ "ag",
15308
+ "ack",
15309
+ "find",
15310
+ "fd",
15311
+ "locate",
15312
+ "diff",
15313
+ "comm",
15314
+ "cmp",
15315
+ "sort",
15316
+ "uniq",
15317
+ "cut",
15318
+ "tr",
15319
+ "column",
15320
+ "jq",
15321
+ "yq",
15322
+ "date",
15323
+ "whoami",
15324
+ "hostname",
15325
+ "uname",
15326
+ "env",
15327
+ "printenv",
15328
+ "id",
15329
+ "groups",
15330
+ "ps",
15331
+ "top",
15332
+ "uptime",
15333
+ "free",
15334
+ "node",
15335
+ "python",
15336
+ "python3",
15337
+ "tree",
15338
+ "less",
15339
+ "more",
15340
+ "nl",
15341
+ "tac",
15342
+ "strings",
15343
+ "md5sum",
15344
+ "sha256sum",
15345
+ "true",
15346
+ "false",
15347
+ "test",
15348
+ "sleep",
15349
+ "seq",
15350
+ "expr"
15351
+ ]);
15352
+ var READ_ONLY_SUBCOMMANDS = {
15353
+ git: /* @__PURE__ */ new Set([
15354
+ "log",
15355
+ "show",
15356
+ "diff",
15357
+ "status",
15358
+ "blame",
15359
+ "branch",
15360
+ "tag",
15361
+ "remote",
15362
+ "describe",
15363
+ "rev-parse",
15364
+ "rev-list",
15365
+ "ls-files",
15366
+ "ls-tree",
15367
+ "ls-remote",
15368
+ "cat-file",
15369
+ "shortlog",
15370
+ "reflog",
15371
+ "whatchanged",
15372
+ "grep",
15373
+ "config",
15374
+ "check-ignore",
15375
+ "merge-base",
15376
+ "name-rev",
15377
+ "count-objects",
15378
+ "verify-commit"
15379
+ ]),
15380
+ // Package managers: only their query verbs.
15381
+ npm: /* @__PURE__ */ new Set(["ls", "list", "view", "info", "outdated", "why", "config", "ping", "search"]),
15382
+ pnpm: /* @__PURE__ */ new Set(["ls", "list", "view", "info", "outdated", "why", "config"]),
15383
+ yarn: /* @__PURE__ */ new Set(["list", "info", "why", "config"]),
15384
+ cargo: /* @__PURE__ */ new Set(["tree", "metadata", "search"]),
15385
+ go: /* @__PURE__ */ new Set(["list", "version", "env", "vet", "doc"]),
15386
+ docker: /* @__PURE__ */ new Set(["ps", "images", "logs", "inspect", "version", "info", "top", "port", "diff", "stats"]),
15387
+ kubectl: /* @__PURE__ */ new Set(["get", "describe", "logs", "explain", "top", "version", "api-resources", "api-versions"]),
15388
+ // gh: read verbs only. `gh run rerun`, `gh pr merge` etc. are excluded, and
15389
+ // the nested-verb check in isReadOnlyCommand handles `gh pr view` vs `gh pr merge`.
15390
+ gh: /* @__PURE__ */ new Set(["browse"])
15391
+ };
15392
+ var READ_ONLY_NESTED = {
15393
+ gh: /* @__PURE__ */ new Set(["view", "list", "status", "diff", "checks"])
15394
+ };
15395
+ var SHELL_CONTROL = /[\n\r;&|`]|\$\(|>>|>|<\(/;
15396
+ function firstWords(command) {
15397
+ return command.trim().split(/\s+/).filter(Boolean);
15398
+ }
15399
+ function isReadOnlyCommand(command) {
15400
+ const cmd = command.trim();
15401
+ if (!cmd)
15402
+ return false;
15403
+ if (SHELL_CONTROL.test(cmd))
15404
+ return false;
15405
+ const words = firstWords(cmd);
15406
+ if (!words.length)
15407
+ return false;
15408
+ let i2 = 0;
15409
+ while (i2 < words.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(words[i2]))
15410
+ i2++;
15411
+ if (i2 >= words.length)
15412
+ return false;
15413
+ let verb = words[i2];
15414
+ const slash = verb.lastIndexOf("/");
15415
+ if (slash >= 0)
15416
+ verb = verb.slice(slash + 1);
15417
+ if (verb === "sudo" || verb === "doas" || verb === "su")
15418
+ return false;
15419
+ const rest = words.slice(i2 + 1).filter((w) => !w.startsWith("-"));
15420
+ const subs = READ_ONLY_SUBCOMMANDS[verb];
15421
+ if (subs) {
15422
+ const sub = rest[0];
15423
+ if (!sub) {
15424
+ return true;
15425
+ }
15426
+ if (subs.has(sub)) {
15427
+ if (verb === "git" && sub === "config") {
15428
+ const args = words.slice(i2 + 2).filter((w) => !w.startsWith("-"));
15429
+ return args.length <= 1;
15430
+ }
15431
+ return true;
15432
+ }
15433
+ const nested = READ_ONLY_NESTED[verb];
15434
+ if (nested && rest[1] && nested.has(rest[1]))
15435
+ return true;
15436
+ return false;
15437
+ }
15438
+ if (!READ_ONLY_TOOLS.has(verb) && !READ_ONLY_BASH.has(verb))
15439
+ return false;
15440
+ if (verb === "node" || verb === "python" || verb === "python3") {
15441
+ return words.slice(i2 + 1).every((w) => w === "--version" || w === "-V" || w === "-v");
15442
+ }
15443
+ if (verb === "find" && words.some((w) => w === "-exec" || w === "-execdir" || w === "-delete")) {
15444
+ return false;
15445
+ }
15446
+ return true;
15447
+ }
15448
+ function checkPlanMode(tool, input) {
15449
+ if (tool === "bash") {
15450
+ const command = typeof input.command === "string" ? input.command : "";
15451
+ if (input.run_in_background === true) {
15452
+ return {
15453
+ reason: "bash-not-read-only",
15454
+ message: "Plan mode is ON: background processes cannot be started. Finish researching and propose your plan; the user will leave plan mode if they want it carried out."
15455
+ };
15456
+ }
15457
+ if (isReadOnlyCommand(command))
15458
+ return null;
15459
+ return {
15460
+ reason: "bash-not-read-only",
15461
+ message: `Plan mode is ON, so \`bash\` is limited to provably read-only commands and this one was not recognised as such (chained commands, redirection and command substitution are always refused). Do NOT ask the user to approve it and do NOT try a variation to get around this \u2014 it is a session-wide lock, not a per-call prompt. Use read-only inspection instead, and put the command in your plan as a step to run once plan mode is off.`
15462
+ };
15463
+ }
15464
+ if (READ_ONLY_TOOLS.has(tool))
15465
+ return null;
15466
+ return {
15467
+ reason: "mutating-tool",
15468
+ message: `Plan mode is ON, so \`${tool}\` is unavailable \u2014 this session may not modify anything yet. Do NOT ask for approval: no answer the user gives at this prompt can unlock it, because it is a session-wide lock they control directly. Research with read-only tools and finish by proposing a concrete plan (files to change, in order, and how to verify). The user will exit plan mode to let you carry it out.`
15469
+ };
15470
+ }
15471
+ exports.PLAN_MODE_INSTRUCTIONS = [
15472
+ "# PLAN MODE IS ACTIVE",
15473
+ "",
15474
+ "This session is READ-ONLY. You cannot edit files, write files, run mutating commands, or start",
15475
+ "background processes. This is enforced outside your control \u2014 it is not a permission prompt, and",
15476
+ "the user cannot approve an exception from inside this turn.",
15477
+ "",
15478
+ "Therefore:",
15479
+ '- NEVER ask "shall I go ahead and make this change?" \u2014 you cannot, whatever the answer.',
15480
+ '- NEVER attempt a mutating tool "just to see" \u2014 it will be refused and wastes the turn.',
15481
+ "- NEVER try to reach a blocked command another way (a different flag, a script, an interpreter).",
15482
+ "",
15483
+ "Your job is to research thoroughly, then deliver a plan:",
15484
+ "1. What you found \u2014 the specific files, functions and line numbers that matter.",
15485
+ "2. What you propose to change \u2014 file by file, in dependency order.",
15486
+ "3. Risks and unknowns \u2014 what could break, what you could not verify, what you assumed.",
15487
+ "4. How it will be verified \u2014 the exact build/test/lint command that proves it works.",
15488
+ "",
15489
+ 'Be concrete. "Update the auth logic" is not a plan; "add a `refreshToken` field to AuthConfig in',
15490
+ 'auth/index.ts:42, then thread it through client.ts:118" is. The user will review the plan and',
15491
+ "leave plan mode to have it carried out."
15492
+ ].join("\n");
15493
+ }
15494
+ });
15495
+
15496
+ // ../core/dist/agent/agentRegistry.js
15497
+ var require_agentRegistry = __commonJS({
15498
+ "../core/dist/agent/agentRegistry.js"(exports) {
15499
+ "use strict";
15500
+ Object.defineProperty(exports, "__esModule", { value: true });
15501
+ exports._limits = void 0;
15502
+ exports.rememberAgent = rememberAgent;
15503
+ exports.updateAgent = updateAgent;
15504
+ exports.getAgent = getAgent;
15505
+ exports.listAgents = listAgents;
15506
+ exports._resetAgentRegistry = _resetAgentRegistry;
15507
+ var MAX_AGENTS = 20;
15508
+ var MAX_TOTAL_BYTES = 8 * 1024 * 1024;
15509
+ var _agents = /* @__PURE__ */ new Map();
15510
+ var _counter = 0;
15511
+ var _seq = 0;
15512
+ function totalBytes() {
15513
+ let n = 0;
15514
+ for (const a of _agents.values())
15515
+ n += a.bytes;
15516
+ return n;
15517
+ }
15518
+ function evictUntilWithinLimits() {
15519
+ while (_agents.size > MAX_AGENTS) {
15520
+ const oldest = _agents.keys().next().value;
15521
+ if (oldest === void 0)
15522
+ break;
15523
+ _agents.delete(oldest);
15524
+ }
15525
+ while (totalBytes() > MAX_TOTAL_BYTES && _agents.size > 1) {
15526
+ const oldest = _agents.keys().next().value;
15527
+ if (oldest === void 0)
15528
+ break;
15529
+ _agents.delete(oldest);
15530
+ }
15531
+ }
15532
+ function sizeOf(messages) {
15533
+ try {
15534
+ return JSON.stringify(messages).length;
15535
+ } catch {
15536
+ return MAX_TOTAL_BYTES;
15537
+ }
15538
+ }
15539
+ function rememberAgent(agentName, description, messages) {
15540
+ const id = `agent_${++_counter}`;
15541
+ const now = Date.now();
15542
+ _agents.set(id, {
15543
+ id,
15544
+ agentName,
15545
+ description,
15546
+ messages,
15547
+ createdAt: now,
15548
+ updatedAt: now,
15549
+ seq: ++_seq,
15550
+ bytes: sizeOf(messages)
15551
+ });
15552
+ evictUntilWithinLimits();
15553
+ return id;
15554
+ }
15555
+ function updateAgent(id, messages) {
15556
+ const existing = _agents.get(id);
15557
+ if (!existing)
15558
+ return;
15559
+ existing.messages = messages;
15560
+ existing.updatedAt = Date.now();
15561
+ existing.seq = ++_seq;
15562
+ existing.bytes = sizeOf(messages);
15563
+ _agents.delete(id);
15564
+ _agents.set(id, existing);
15565
+ evictUntilWithinLimits();
15566
+ }
15567
+ function getAgent(id) {
15568
+ return _agents.get(id);
15569
+ }
15570
+ function listAgents() {
15571
+ return [..._agents.values()].sort((a, b) => b.seq - a.seq);
15572
+ }
15573
+ function _resetAgentRegistry() {
15574
+ _agents.clear();
15575
+ _counter = 0;
15576
+ _seq = 0;
15577
+ }
15578
+ exports._limits = { MAX_AGENTS, MAX_TOTAL_BYTES };
15579
+ }
15580
+ });
15581
+
15080
15582
  // ../core/dist/agent/flaky.js
15081
15583
  var require_flaky = __commonJS({
15082
15584
  "../core/dist/agent/flaky.js"(exports) {
@@ -15255,7 +15757,7 @@ var require_loop = __commonJS({
15255
15757
  };
15256
15758
  }();
15257
15759
  Object.defineProperty(exports, "__esModule", { value: true });
15258
- exports.VERIFY_CMD_RE = exports.WRITE_TOOL_NAMES = void 0;
15760
+ exports.VERIFY_CMD_RE = exports.WRITE_TOOL_NAMES = exports.ToolNotAllowedError = void 0;
15259
15761
  exports.resolveMaxIterations = resolveMaxIterations;
15260
15762
  exports.createLimiter = createLimiter;
15261
15763
  exports.extractSubTaskText = extractSubTaskText;
@@ -15281,6 +15783,8 @@ var require_loop = __commonJS({
15281
15783
  var agentTypes_1 = require_agentTypes();
15282
15784
  var skills_1 = require_skills();
15283
15785
  var rules_1 = require_rules();
15786
+ var planMode_1 = require_planMode();
15787
+ var agentRegistry_1 = require_agentRegistry();
15284
15788
  var sandbox_1 = require_sandbox();
15285
15789
  var index_1 = require_plugins();
15286
15790
  var testIntegrity_1 = require_testIntegrity();
@@ -15532,6 +16036,13 @@ var require_loop = __commonJS({
15532
16036
  var _subTaskCounter = 0;
15533
16037
  var SUBTASK_TIMEOUT_MS = Number(process.env.NEXRALL_SUBTASK_TIMEOUT_MS) > 0 ? Number(process.env.NEXRALL_SUBTASK_TIMEOUT_MS) : 10 * 60 * 1e3;
15534
16038
  var SUBTASK_MAX = 48e3;
16039
+ var ToolNotAllowedError = class extends Error {
16040
+ constructor(message) {
16041
+ super(message);
16042
+ this.name = "ToolNotAllowedError";
16043
+ }
16044
+ };
16045
+ exports.ToolNotAllowedError = ToolNotAllowedError;
15535
16046
  function sliceSafeEnd(s2, max) {
15536
16047
  if (s2.length <= max)
15537
16048
  return s2;
@@ -15594,11 +16105,34 @@ ${tail}`;
15594
16105
  if (depth >= MAX_TASK_DEPTH) {
15595
16106
  return { error: "Sub-agents cannot spawn further sub-agents. Do this work directly, or report back so the main agent can delegate it." };
15596
16107
  }
15597
- const requestedType = typeof input.subagent_type === "string" ? input.subagent_type : "";
15598
- const agent = (0, agentTypes_1.findAgentType)(agentTypes, requestedType);
16108
+ const resumeId = typeof input.resume_agent_id === "string" ? input.resume_agent_id.trim() : "";
16109
+ const resumed = resumeId ? (0, agentRegistry_1.getAgent)(resumeId) : void 0;
16110
+ if (resumeId && !resumed) {
16111
+ return {
16112
+ error: `No resumable sub-agent with id "${resumeId}". Ids live only for the current session and the oldest are dropped when too many accumulate, so this one has expired or never existed. Start a fresh sub-task with a self-contained prompt instead.`
16113
+ };
16114
+ }
16115
+ const requestedType = resumed ? resumed.agentName ?? "" : typeof input.subagent_type === "string" ? input.subagent_type : "";
16116
+ if (requestedType) {
16117
+ const decision = (0, rules_1.evaluatePermission)((0, rules_1.loadSettings)(options.workDir).permissions, "task", { subagent_type: requestedType }, options.workDir);
16118
+ if (decision === "deny") {
16119
+ return {
16120
+ error: `The sub-agent "${requestedType}" is disabled by a permission rule in this project (permissions.deny in settings.json). This is a deliberate policy choice, not a missing file \u2014 do not create it and do not retry. Do the work yourself, or use a different sub-agent.`
16121
+ };
16122
+ }
16123
+ }
16124
+ let agent = (0, agentTypes_1.findAgentType)(agentTypes, requestedType);
16125
+ let knownTypes = agentTypes;
15599
16126
  if (requestedType && !agent) {
15600
- const known = agentTypes.map((a) => a.name).join(", ") || "(none defined)";
15601
- return { error: `Unknown subagent_type "${requestedType}". Available types: ${known}.` };
16127
+ knownTypes = (0, agentTypes_1.loadAgentTypes)(options.workDir);
16128
+ agent = (0, agentTypes_1.findAgentType)(knownTypes, requestedType);
16129
+ }
16130
+ if (requestedType && !agent) {
16131
+ const known = knownTypes.map((a) => a.name).join(", ") || "(none defined)";
16132
+ return {
16133
+ error: `Unknown subagent_type "${requestedType}". Available types: ${known}.
16134
+ If you just created .nexrall/agents/` + requestedType + ".md, make sure the write finished in an EARLIER tool call than this one \u2014 a file written in the same batch may not be on disk yet."
16135
+ };
15602
16136
  }
15603
16137
  const subNexrallMd = agent ? `# Sub-agent role: ${agent.name}
15604
16138
  ${agent.prompt}` + (options.nexrallMd ? `
@@ -15608,15 +16142,15 @@ ${agent.prompt}` + (options.nexrallMd ? `
15608
16142
  ${options.nexrallMd}` : "") : options.nexrallMd;
15609
16143
  const allowed = agent?.tools ? new Set(agent.tools) : null;
15610
16144
  const gatedPermission = async (req) => {
15611
- if (allowed && !allowed.has(req.tool))
15612
- return false;
15613
- if (agent?.testFilesOnly && !allowsTestOnlyWrite(req.tool, req.input))
15614
- return false;
16145
+ if (allowed && !allowed.has(req.tool)) {
16146
+ throw new ToolNotAllowedError(`The "${agent.name}" sub-agent is not allowed to use \`${req.tool}\` \u2014 it is not in that agent's tool allowlist. This is a restriction of the agent definition, NOT a user decision: do not ask for approval, use one of the tools you do have, or report back that the task needs a different agent.`);
16147
+ }
16148
+ if (agent?.testFilesOnly && !allowsTestOnlyWrite(req.tool, req.input)) {
16149
+ throw new ToolNotAllowedError(`The "${agent.name}" sub-agent may only write to TEST files, so \`${req.tool}\` was refused for this path. Do not try to work around it: if production code must change, say so in your report instead.`);
16150
+ }
15615
16151
  return options.requestPermission(req);
15616
16152
  };
15617
- const subMessages = [
15618
- { role: "user", content: [{ type: "text", text: prompt2 }] }
15619
- ];
16153
+ const subMessages = resumed ? [...resumed.messages, { role: "user", content: [{ type: "text", text: prompt2 }] }] : [{ role: "user", content: [{ type: "text", text: prompt2 }] }];
15620
16154
  const subAbort = { aborted: false };
15621
16155
  const timer = setTimeout(() => {
15622
16156
  subAbort.aborted = true;
@@ -15634,6 +16168,9 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
15634
16168
  editorContext: null,
15635
16169
  // fresh isolated context for sub-agent
15636
16170
  model: agent?.model ?? options.model,
16171
+ // Plan mode is inherited, never relaxed. If the main agent could spawn a
16172
+ // sub-agent that writes, the lock would be one `task` call from useless.
16173
+ planMode: options.planMode,
15637
16174
  nexrallMd: subNexrallMd,
15638
16175
  abortSignal: subAbort,
15639
16176
  requestPermission: gatedPermission,
@@ -15682,7 +16219,13 @@ ${partial}` : "",
15682
16219
  return { error: sections.join("\n\n") };
15683
16220
  }
15684
16221
  const text = capSubTaskText(extractSubTaskText(result, true));
15685
- return { output: text || "(sub-task completed with no text output)" };
16222
+ const agentId = resumed ? ((0, agentRegistry_1.updateAgent)(resumed.id, result), resumed.id) : (0, agentRegistry_1.rememberAgent)(agent?.name ?? null, typeof input.description === "string" && input.description.trim() || prompt2.slice(0, 80), result);
16223
+ const body = text || "(sub-task completed with no text output)";
16224
+ return {
16225
+ output: `${body}
16226
+
16227
+ [resumable: this sub-agent is "${agentId}". To ask IT a follow-up \u2014 keeping everything it already read and concluded \u2014 call task again with resume_agent_id="` + agentId + '" instead of writing a new prompt from scratch.]'
16228
+ };
15686
16229
  } catch (err) {
15687
16230
  const salvaged = (0, types_1.salvageHistory)(err);
15688
16231
  if (salvaged) {
@@ -16064,10 +16607,15 @@ Continue the work from here.` }] });
16064
16607
  const hooks = loadHooks(options.workDir);
16065
16608
  const depth = options._depth ?? 0;
16066
16609
  const agentScope = options._agentScope ?? "root";
16067
- const agentTypes = (0, agentTypes_1.loadAgentTypes)(options.workDir);
16610
+ const settings = (0, rules_1.loadSettings)(options.workDir);
16611
+ const agentTypes = (0, agentTypes_1.loadAgentTypes)(options.workDir).filter((t2) => (0, rules_1.evaluatePermission)(settings.permissions, "task", { subagent_type: t2.name }, options.workDir) !== "deny");
16068
16612
  const agentsCatalogue = depth === 0 ? (0, agentTypes_1.summariseAgents)(agentTypes) : "";
16069
16613
  const skillsCatalogue = (0, skills_1.summariseSkills)((0, skills_1.loadSkills)(options.workDir));
16070
- const settings = (0, rules_1.loadSettings)(options.workDir);
16614
+ const planAwareNexrallMd = options.planMode ? planMode_1.PLAN_MODE_INSTRUCTIONS + (options.nexrallMd ? `
16615
+
16616
+ ---
16617
+
16618
+ ${options.nexrallMd}` : "") : options.nexrallMd;
16071
16619
  const sandboxCfg = (0, sandbox_1.parseSandboxConfig)(settings.raw.sandbox) ?? void 0;
16072
16620
  const maxIterations = resolveMaxIterations(options.maxIterations, settings.raw);
16073
16621
  const autoContinue = resolveAutoContinue(options.autoContinue, settings.raw);
@@ -16175,7 +16723,7 @@ Continue the work from here.` }] });
16175
16723
  effort: options.effort,
16176
16724
  env: options.env,
16177
16725
  editorContext: options.editorContext,
16178
- nexrallMd: options.nexrallMd,
16726
+ nexrallMd: planAwareNexrallMd,
16179
16727
  clientType: options.clientType,
16180
16728
  abortSignal: options.abortSignal,
16181
16729
  extraTools: options.mcpManager?.getAnthropicTools(),
@@ -16295,15 +16843,26 @@ Continue the work from here.` }] });
16295
16843
  options.onToolResult(name, result);
16296
16844
  return { block: { ...block, id }, result };
16297
16845
  }
16846
+ if (options.planMode) {
16847
+ const refusal = (0, planMode_1.checkPlanMode)(name, input);
16848
+ if (refusal) {
16849
+ result = { error: refusal.message };
16850
+ options.onToolResult(name, result);
16851
+ return { block: { ...block, id }, result };
16852
+ }
16853
+ }
16298
16854
  const description = humanDescription(name, input);
16299
16855
  let permitted;
16856
+ let deniedReason = null;
16300
16857
  try {
16301
16858
  permitted = await options.requestPermission({ tool: name, input, description });
16302
- } catch {
16859
+ } catch (err) {
16303
16860
  permitted = false;
16861
+ if (err instanceof ToolNotAllowedError)
16862
+ deniedReason = err.message;
16304
16863
  }
16305
16864
  if (!permitted) {
16306
- result = { error: "Permission denied by user" };
16865
+ result = { error: deniedReason ?? "Permission denied by user" };
16307
16866
  } else if (name === "task") {
16308
16867
  result = depth === 0 ? await _subTaskLimit(() => runSubTask(input, options, agentTypes)) : await runSubTask(input, options, agentTypes);
16309
16868
  } else {
@@ -17744,6 +18303,175 @@ var require_destructive = __commonJS({
17744
18303
  }
17745
18304
  });
17746
18305
 
18306
+ // ../core/dist/plugins/sources.js
18307
+ var require_sources = __commonJS({
18308
+ "../core/dist/plugins/sources.js"(exports) {
18309
+ "use strict";
18310
+ var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m2, k, k2) {
18311
+ if (k2 === void 0)
18312
+ k2 = k;
18313
+ var desc = Object.getOwnPropertyDescriptor(m2, k);
18314
+ if (!desc || ("get" in desc ? !m2.__esModule : desc.writable || desc.configurable)) {
18315
+ desc = { enumerable: true, get: function() {
18316
+ return m2[k];
18317
+ } };
18318
+ }
18319
+ Object.defineProperty(o, k2, desc);
18320
+ } : function(o, m2, k, k2) {
18321
+ if (k2 === void 0)
18322
+ k2 = k;
18323
+ o[k2] = m2[k];
18324
+ });
18325
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? function(o, v) {
18326
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18327
+ } : function(o, v) {
18328
+ o["default"] = v;
18329
+ });
18330
+ var __importStar = exports && exports.__importStar || /* @__PURE__ */ function() {
18331
+ var ownKeys = function(o) {
18332
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
18333
+ var ar = [];
18334
+ for (var k in o2)
18335
+ if (Object.prototype.hasOwnProperty.call(o2, k))
18336
+ ar[ar.length] = k;
18337
+ return ar;
18338
+ };
18339
+ return ownKeys(o);
18340
+ };
18341
+ return function(mod) {
18342
+ if (mod && mod.__esModule)
18343
+ return mod;
18344
+ var result = {};
18345
+ if (mod != null) {
18346
+ for (var k = ownKeys(mod), i2 = 0; i2 < k.length; i2++)
18347
+ if (k[i2] !== "default")
18348
+ __createBinding(result, mod, k[i2]);
18349
+ }
18350
+ __setModuleDefault(result, mod);
18351
+ return result;
18352
+ };
18353
+ }();
18354
+ Object.defineProperty(exports, "__esModule", { value: true });
18355
+ exports.loadDeclaredSources = loadDeclaredSources2;
18356
+ exports.allowedSourcePatterns = allowedSourcePatterns2;
18357
+ exports.isSourceAllowed = isSourceAllowed2;
18358
+ exports.resolveSourceAlias = resolveSourceAlias2;
18359
+ exports.writeDeclaredSource = writeDeclaredSource2;
18360
+ exports.removeDeclaredSource = removeDeclaredSource2;
18361
+ exports.sourcesFileFor = sourcesFileFor2;
18362
+ var fs9 = __importStar(__require("fs"));
18363
+ var path6 = __importStar(__require("path"));
18364
+ var os6 = __importStar(__require("os"));
18365
+ function managedSettingsPath() {
18366
+ if (process.platform === "darwin")
18367
+ return "/Library/Application Support/Nexrall/managed-settings.json";
18368
+ if (process.platform === "win32")
18369
+ return path6.join(process.env.PROGRAMDATA ?? "C:\\ProgramData", "Nexrall", "managed-settings.json");
18370
+ return "/etc/nexrall/managed-settings.json";
18371
+ }
18372
+ function readJson(file) {
18373
+ try {
18374
+ return JSON.parse(fs9.readFileSync(file, "utf-8"));
18375
+ } catch {
18376
+ return null;
18377
+ }
18378
+ }
18379
+ var ALIAS_RE = /^[a-z0-9][\w.-]{0,63}$/i;
18380
+ function readSpec(value) {
18381
+ if (typeof value === "string")
18382
+ return value.trim() || null;
18383
+ if (value && typeof value === "object") {
18384
+ const v = value;
18385
+ const inner = v.source ?? v.repo ?? v.url;
18386
+ if (typeof inner === "string")
18387
+ return inner.trim() || null;
18388
+ if (inner && typeof inner === "object") {
18389
+ const deep = inner.repo ?? inner.url;
18390
+ if (typeof deep === "string")
18391
+ return deep.trim() || null;
18392
+ }
18393
+ }
18394
+ return null;
18395
+ }
18396
+ function collect(file, tier, into) {
18397
+ const obj = readJson(file);
18398
+ const raw = obj?.pluginSources;
18399
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
18400
+ return;
18401
+ for (const [name, value] of Object.entries(raw)) {
18402
+ if (!ALIAS_RE.test(name))
18403
+ continue;
18404
+ const spec = readSpec(value);
18405
+ if (!spec)
18406
+ continue;
18407
+ if (into.has(name))
18408
+ continue;
18409
+ into.set(name, { name, spec, tier, file });
18410
+ }
18411
+ }
18412
+ function loadDeclaredSources2(workDir, opts = {}) {
18413
+ const out = /* @__PURE__ */ new Map();
18414
+ collect(managedSettingsPath(), "managed", out);
18415
+ collect(path6.join(os6.homedir(), ".nexrall", "settings.json"), "user", out);
18416
+ if (opts.includeProject) {
18417
+ collect(path6.join(workDir, ".nexrall", "settings.json"), "project", out);
18418
+ collect(path6.join(workDir, ".nexrall", "settings.local.json"), "project", out);
18419
+ }
18420
+ return [...out.values()];
18421
+ }
18422
+ function allowedSourcePatterns2() {
18423
+ const obj = readJson(managedSettingsPath());
18424
+ const v = obj?.allowedPluginSources;
18425
+ if (!Array.isArray(v))
18426
+ return null;
18427
+ const pats = v.filter((x2) => typeof x2 === "string" && x2.trim().length > 0);
18428
+ return pats.length ? pats : null;
18429
+ }
18430
+ function isSourceAllowed2(spec, patterns = allowedSourcePatterns2()) {
18431
+ if (!patterns)
18432
+ return true;
18433
+ const cleaned = spec.trim().replace(/^https?:\/\/(?:www\.)?github\.com\//i, "").replace(/\.git$/, "");
18434
+ const base = cleaned.split("#")[0].split("/").slice(0, 2).join("/").toLowerCase();
18435
+ return patterns.some((p) => {
18436
+ const pat = p.trim().toLowerCase().replace(/^https?:\/\/(?:www\.)?github\.com\//i, "");
18437
+ if (pat === "*")
18438
+ return true;
18439
+ if (pat.endsWith("/*"))
18440
+ return base.startsWith(pat.slice(0, -1));
18441
+ return base === pat.split("#")[0].split("/").slice(0, 2).join("/");
18442
+ });
18443
+ }
18444
+ function resolveSourceAlias2(alias, workDir, opts = {}) {
18445
+ const want = alias.trim().toLowerCase();
18446
+ return loadDeclaredSources2(workDir, opts).find((s2) => s2.name.toLowerCase() === want) ?? null;
18447
+ }
18448
+ function writeDeclaredSource2(file, name, spec) {
18449
+ if (!ALIAS_RE.test(name)) {
18450
+ throw new Error(`Invalid source name "${name}". Use letters, numbers, dot, dash or underscore.`);
18451
+ }
18452
+ const obj = readJson(file) ?? {};
18453
+ const existing = obj.pluginSources && typeof obj.pluginSources === "object" && !Array.isArray(obj.pluginSources) ? obj.pluginSources : {};
18454
+ obj.pluginSources = { ...existing, [name]: spec };
18455
+ fs9.mkdirSync(path6.dirname(file), { recursive: true });
18456
+ fs9.writeFileSync(file, JSON.stringify(obj, null, 2) + "\n", "utf-8");
18457
+ }
18458
+ function removeDeclaredSource2(file, name) {
18459
+ const obj = readJson(file);
18460
+ if (!obj?.pluginSources || typeof obj.pluginSources !== "object")
18461
+ return false;
18462
+ const sources = obj.pluginSources;
18463
+ if (!(name in sources))
18464
+ return false;
18465
+ delete sources[name];
18466
+ fs9.writeFileSync(file, JSON.stringify(obj, null, 2) + "\n", "utf-8");
18467
+ return true;
18468
+ }
18469
+ function sourcesFileFor2(scope, workDir) {
18470
+ return scope === "user" ? path6.join(os6.homedir(), ".nexrall", "settings.json") : path6.join(workDir, ".nexrall", "settings.json");
18471
+ }
18472
+ }
18473
+ });
18474
+
17747
18475
  // ../core/dist/plugins/installer.js
17748
18476
  var require_installer = __commonJS({
17749
18477
  "../core/dist/plugins/installer.js"(exports) {
@@ -17796,8 +18524,8 @@ var require_installer = __commonJS({
17796
18524
  exports.searchRegistry = searchRegistry2;
17797
18525
  exports.getRegistryPlugin = getRegistryPlugin2;
17798
18526
  exports.reportInstall = reportInstall2;
17799
- exports.parsePluginSource = parsePluginSource;
17800
- exports.inspectPluginDir = inspectPluginDir;
18527
+ exports.parsePluginSource = parsePluginSource2;
18528
+ exports.inspectPluginDir = inspectPluginDir2;
17801
18529
  exports.installPlugin = installPlugin2;
17802
18530
  exports.readReceipt = readReceipt2;
17803
18531
  exports.removePlugin = removePlugin2;
@@ -17807,6 +18535,8 @@ var require_installer = __commonJS({
17807
18535
  var os6 = __importStar(__require("os"));
17808
18536
  var child_process_1 = __require("child_process");
17809
18537
  var client_1 = require_client();
18538
+ var index_1 = require_plugins();
18539
+ var sources_1 = require_sources();
17810
18540
  var RECEIPT_FILE = "_install.json";
17811
18541
  async function searchRegistry2(query, limit = 50) {
17812
18542
  const u = new URL(`${client_1.API_BASE}/api/code/plugins`);
@@ -17836,7 +18566,7 @@ var require_installer = __commonJS({
17836
18566
  fetch(`${client_1.API_BASE}/api/code/plugins/${encodeURIComponent(name)}/installed`, { method: "POST" }).catch(() => {
17837
18567
  });
17838
18568
  }
17839
- function parsePluginSource(spec) {
18569
+ function parsePluginSource2(spec) {
17840
18570
  const raw = spec.trim();
17841
18571
  if (!raw)
17842
18572
  throw new Error("Empty plugin source.");
@@ -17883,29 +18613,45 @@ var require_installer = __commonJS({
17883
18613
  return [];
17884
18614
  }
17885
18615
  }
17886
- function inspectPluginDir(dir) {
18616
+ function inspectPluginDir2(dir) {
17887
18617
  let meta = {};
17888
- try {
17889
- const j = JSON.parse(fs9.readFileSync(path6.join(dir, "plugin.json"), "utf-8"));
17890
- meta = {
17891
- name: typeof j.name === "string" ? j.name : void 0,
17892
- version: typeof j.version === "string" ? j.version : void 0,
17893
- description: typeof j.description === "string" ? j.description : void 0
17894
- };
17895
- } catch {
18618
+ const manifest = (0, index_1.resolvePluginFile)(dir, "manifest");
18619
+ if (manifest) {
18620
+ try {
18621
+ const j = JSON.parse(fs9.readFileSync(manifest, "utf-8"));
18622
+ meta = {
18623
+ name: typeof j.name === "string" ? j.name : void 0,
18624
+ version: typeof j.version === "string" ? j.version : void 0,
18625
+ description: typeof j.description === "string" ? j.description : void 0
18626
+ };
18627
+ } catch {
18628
+ }
17896
18629
  }
17897
18630
  return {
17898
18631
  commands: listMd(path6.join(dir, "commands")),
17899
18632
  agents: listMd(path6.join(dir, "agents")),
17900
- hasHooks: fs9.existsSync(path6.join(dir, "hooks.json")),
17901
- hasMcp: fs9.existsSync(path6.join(dir, "mcp.json")),
18633
+ hasHooks: (0, index_1.resolvePluginFile)(dir, "hooks") !== null,
18634
+ hasMcp: (0, index_1.resolvePluginFile)(dir, "mcp") !== null,
17902
18635
  meta
17903
18636
  };
17904
18637
  }
17905
18638
  function looksLikePlugin(dir) {
17906
- const i2 = inspectPluginDir(dir);
18639
+ const i2 = inspectPluginDir2(dir);
17907
18640
  return i2.commands.length > 0 || i2.agents.length > 0 || i2.hasHooks || i2.hasMcp || i2.meta.name !== void 0;
17908
18641
  }
18642
+ async function resolveCommitSha(repo, ref) {
18643
+ try {
18644
+ const res = await fetch(`https://api.github.com/repos/${repo}/commits/${encodeURIComponent(ref || "HEAD")}`, {
18645
+ headers: { Accept: "application/vnd.github.sha", "User-Agent": "nexrall-code" }
18646
+ });
18647
+ if (!res.ok)
18648
+ return null;
18649
+ const sha = (await res.text()).trim();
18650
+ return /^[0-9a-f]{40}$/i.test(sha) ? sha : null;
18651
+ } catch {
18652
+ return null;
18653
+ }
18654
+ }
17909
18655
  async function downloadTarball(repo, ref, destFile) {
17910
18656
  const r2 = ref || "HEAD";
17911
18657
  const url = `https://codeload.github.com/${repo}/tar.gz/${encodeURIComponent(r2)}`;
@@ -17956,28 +18702,33 @@ var require_installer = __commonJS({
17956
18702
  return scope === "project" ? path6.join(workDir || process.cwd(), ".nexrall", "plugins") : path6.join(os6.homedir(), ".nexrall", "plugins");
17957
18703
  }
17958
18704
  async function installPlugin2(spec, opts = {}) {
17959
- const source = parsePluginSource(spec);
18705
+ const source2 = parsePluginSource2(spec);
17960
18706
  const scope = opts.scope ?? "global";
18707
+ const patterns = (0, sources_1.allowedSourcePatterns)();
18708
+ if (patterns && source2.kind === "github" && !(0, sources_1.isSourceAllowed)(source2.raw, patterns)) {
18709
+ throw new Error(`Blocked by your organisation's policy: plugins may only be installed from ${patterns.join(", ")}.`);
18710
+ }
17961
18711
  const tmpBase = fs9.mkdtempSync(path6.join(os6.tmpdir(), "nexrall-plugin-"));
17962
18712
  try {
17963
18713
  let repoRoot;
17964
- if (source.kind === "local") {
17965
- if (!fs9.existsSync(source.repo) || !fs9.statSync(source.repo).isDirectory()) {
17966
- throw new Error(`Local plugin path not found: ${source.repo}`);
18714
+ if (source2.kind === "local") {
18715
+ if (!fs9.existsSync(source2.repo) || !fs9.statSync(source2.repo).isDirectory()) {
18716
+ throw new Error(`Local plugin path not found: ${source2.repo}`);
17967
18717
  }
17968
- repoRoot = source.repo;
18718
+ repoRoot = source2.repo;
17969
18719
  } else {
17970
18720
  const tarFile = path6.join(tmpBase, "plugin.tar.gz");
17971
- await downloadTarball(source.repo, source.ref, tarFile);
18721
+ await downloadTarball(source2.repo, source2.ref, tarFile);
17972
18722
  repoRoot = extractTarball(tarFile, path6.join(tmpBase, "x"));
17973
18723
  }
17974
- let pluginRoot = source.subdir ? path6.join(repoRoot, source.subdir) : repoRoot;
18724
+ const sha = source2.kind === "github" ? await resolveCommitSha(source2.repo, source2.ref) : null;
18725
+ let pluginRoot = source2.subdir ? path6.join(repoRoot, source2.subdir) : repoRoot;
17975
18726
  pluginRoot = path6.resolve(pluginRoot);
17976
18727
  if (!path6.resolve(pluginRoot).startsWith(path6.resolve(repoRoot))) {
17977
18728
  throw new Error("Plugin subdirectory escapes the repository \u2014 refusing.");
17978
18729
  }
17979
18730
  if (!fs9.existsSync(pluginRoot) || !fs9.statSync(pluginRoot).isDirectory()) {
17980
- throw new Error(`Subdirectory not found in repo: ${source.subdir}`);
18731
+ throw new Error(`Subdirectory not found in repo: ${source2.subdir}`);
17981
18732
  }
17982
18733
  if (!looksLikePlugin(pluginRoot)) {
17983
18734
  const candidates = ["plugin", ".nexrall-plugin"].map((c) => path6.join(pluginRoot, c));
@@ -17988,12 +18739,16 @@ var require_installer = __commonJS({
17988
18739
  throw new Error("No plugin found: expected plugin.json, commands/, agents/, hooks.json or mcp.json at the source root.");
17989
18740
  }
17990
18741
  }
17991
- const inspection = inspectPluginDir(pluginRoot);
17992
- const name = opts.name || inspection.meta.name || (source.subdir ? path6.basename(source.subdir) : void 0) || (source.kind === "github" ? source.repo.split("/")[1] : path6.basename(source.repo));
18742
+ const inspection = inspectPluginDir2(pluginRoot);
18743
+ const name = opts.name || inspection.meta.name || (source2.subdir ? path6.basename(source2.subdir) : void 0) || (source2.kind === "github" ? source2.repo.split("/")[1] : path6.basename(source2.repo));
17993
18744
  if (!/^[\w.-]+$/.test(name))
17994
18745
  throw new Error(`Invalid plugin name "${name}".`);
17995
18746
  if (opts.confirm) {
17996
- const ok = await opts.confirm(inspection, name);
18747
+ const ok = await opts.confirm(inspection, name, {
18748
+ sha: sha ?? void 0,
18749
+ previousSha: opts._previousSha,
18750
+ isUpdate: Boolean(opts._previousSha) || Boolean(opts.force)
18751
+ });
17997
18752
  if (!ok)
17998
18753
  throw new Error("Installation cancelled.");
17999
18754
  }
@@ -18006,14 +18761,15 @@ var require_installer = __commonJS({
18006
18761
  }
18007
18762
  copyPluginDir(pluginRoot, dest);
18008
18763
  const receipt = {
18009
- source: source.raw,
18010
- kind: source.kind,
18011
- ref: source.ref,
18012
- subdir: source.subdir,
18013
- installedAt: (/* @__PURE__ */ new Date()).toISOString()
18764
+ source: source2.raw,
18765
+ kind: source2.kind,
18766
+ ref: source2.ref,
18767
+ subdir: source2.subdir,
18768
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
18769
+ ...sha ? { sha } : {}
18014
18770
  };
18015
18771
  fs9.writeFileSync(path6.join(dest, RECEIPT_FILE), JSON.stringify(receipt, null, 2));
18016
- return { name, dir: dest, scope, inspection };
18772
+ return { name, dir: dest, scope, inspection, sha: sha ?? void 0, previousSha: opts._previousSha };
18017
18773
  } finally {
18018
18774
  fs9.rmSync(tmpBase, { recursive: true, force: true });
18019
18775
  }
@@ -18052,7 +18808,14 @@ var require_installer = __commonJS({
18052
18808
  if (receipt.kind === "local") {
18053
18809
  throw new Error(`Plugin "${name}" was installed from a local path \u2014 re-run 'nex plugin install ${receipt.source} --force'.`);
18054
18810
  }
18055
- return installPlugin2(receipt.source, { scope: s2, workDir: opts.workDir, name, force: true, confirm: opts.confirm });
18811
+ return installPlugin2(receipt.source, {
18812
+ scope: s2,
18813
+ workDir: opts.workDir,
18814
+ name,
18815
+ force: true,
18816
+ confirm: opts.confirm,
18817
+ _previousSha: receipt.sha
18818
+ });
18056
18819
  }
18057
18820
  throw new Error(`Plugin "${name}" is not installed.`);
18058
18821
  }
@@ -18103,10 +18866,13 @@ var require_dist2 = __commonJS({
18103
18866
  __exportStar(require_manager2(), exports);
18104
18867
  __exportStar(require_loader(), exports);
18105
18868
  __exportStar(require_agentTypes(), exports);
18869
+ __exportStar(require_planMode(), exports);
18870
+ __exportStar(require_agentRegistry(), exports);
18106
18871
  __exportStar(require_rules(), exports);
18107
18872
  __exportStar(require_destructive(), exports);
18108
18873
  __exportStar(require_plugins(), exports);
18109
18874
  __exportStar(require_installer(), exports);
18875
+ __exportStar(require_sources(), exports);
18110
18876
  }
18111
18877
  });
18112
18878
 
@@ -20922,23 +21688,23 @@ var require_react_reconciler_production = __commonJS({
20922
21688
  return "\nError generating stack: " + x2.message + "\n" + x2.stack;
20923
21689
  }
20924
21690
  }
20925
- function createCapturedValueAtFiber(value, source) {
21691
+ function createCapturedValueAtFiber(value, source2) {
20926
21692
  if ("object" === typeof value && null !== value) {
20927
21693
  var existing = CapturedStacks.get(value);
20928
21694
  if (void 0 !== existing)
20929
21695
  return existing;
20930
- source = {
21696
+ source2 = {
20931
21697
  value,
20932
- source,
20933
- stack: getStackByFiberInDevAndProd(source)
21698
+ source: source2,
21699
+ stack: getStackByFiberInDevAndProd(source2)
20934
21700
  };
20935
- CapturedStacks.set(value, source);
20936
- return source;
21701
+ CapturedStacks.set(value, source2);
21702
+ return source2;
20937
21703
  }
20938
21704
  return {
20939
21705
  value,
20940
- source,
20941
- stack: getStackByFiberInDevAndProd(source)
21706
+ source: source2,
21707
+ stack: getStackByFiberInDevAndProd(source2)
20942
21708
  };
20943
21709
  }
20944
21710
  function pushTreeFork(workInProgress2, totalChildren) {
@@ -30110,23 +30876,23 @@ var require_react_reconciler_development = __commonJS({
30110
30876
  function describeFunctionComponentFrameWithoutLineNumber(fn) {
30111
30877
  return (fn = fn ? fn.displayName || fn.name : "") ? describeBuiltInComponentFrame(fn) : "";
30112
30878
  }
30113
- function createCapturedValueAtFiber(value, source) {
30879
+ function createCapturedValueAtFiber(value, source2) {
30114
30880
  if ("object" === typeof value && null !== value) {
30115
30881
  var existing = CapturedStacks.get(value);
30116
30882
  if (void 0 !== existing)
30117
30883
  return existing;
30118
- source = {
30884
+ source2 = {
30119
30885
  value,
30120
- source,
30121
- stack: getStackByFiberInDevAndProd(source)
30886
+ source: source2,
30887
+ stack: getStackByFiberInDevAndProd(source2)
30122
30888
  };
30123
- CapturedStacks.set(value, source);
30124
- return source;
30889
+ CapturedStacks.set(value, source2);
30890
+ return source2;
30125
30891
  }
30126
30892
  return {
30127
30893
  value,
30128
- source,
30129
- stack: getStackByFiberInDevAndProd(source)
30894
+ source: source2,
30895
+ stack: getStackByFiberInDevAndProd(source2)
30130
30896
  };
30131
30897
  }
30132
30898
  function pushTreeFork(workInProgress2, totalChildren) {
@@ -42684,9 +43450,9 @@ var require_buffer_util = __commonJS({
42684
43450
  }
42685
43451
  return target;
42686
43452
  }
42687
- function _mask(source, mask, output, offset, length) {
43453
+ function _mask(source2, mask, output, offset, length) {
42688
43454
  for (let i2 = 0; i2 < length; i2++) {
42689
- output[offset + i2] = source[i2] ^ mask[i2 & 3];
43455
+ output[offset + i2] = source2[i2] ^ mask[i2 & 3];
42690
43456
  }
42691
43457
  }
42692
43458
  function _unmask(buffer, mask) {
@@ -42725,11 +43491,11 @@ var require_buffer_util = __commonJS({
42725
43491
  if (!process.env.WS_NO_BUFFER_UTIL) {
42726
43492
  try {
42727
43493
  const bufferUtil = __require("bufferutil");
42728
- module.exports.mask = function(source, mask, output, offset, length) {
43494
+ module.exports.mask = function(source2, mask, output, offset, length) {
42729
43495
  if (length < 48)
42730
- _mask(source, mask, output, offset, length);
43496
+ _mask(source2, mask, output, offset, length);
42731
43497
  else
42732
- bufferUtil.mask(source, mask, output, offset, length);
43498
+ bufferUtil.mask(source2, mask, output, offset, length);
42733
43499
  };
42734
43500
  module.exports.unmask = function(buffer, mask) {
42735
43501
  if (buffer.length < 32)
@@ -49446,16 +50212,16 @@ var require_dist3 = __commonJS({
49446
50212
  }
49447
50213
  function _objectSpread(target) {
49448
50214
  for (var i2 = 1; i2 < arguments.length; i2++) {
49449
- var source = arguments[i2] != null ? arguments[i2] : {};
50215
+ var source2 = arguments[i2] != null ? arguments[i2] : {};
49450
50216
  if (i2 % 2) {
49451
- ownKeys(Object(source), true).forEach(function(key) {
49452
- _defineProperty(target, key, source[key]);
50217
+ ownKeys(Object(source2), true).forEach(function(key) {
50218
+ _defineProperty(target, key, source2[key]);
49453
50219
  });
49454
50220
  } else if (Object.getOwnPropertyDescriptors) {
49455
- Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
50221
+ Object.defineProperties(target, Object.getOwnPropertyDescriptors(source2));
49456
50222
  } else {
49457
- ownKeys(Object(source)).forEach(function(key) {
49458
- Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
50223
+ ownKeys(Object(source2)).forEach(function(key) {
50224
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source2, key));
49459
50225
  });
49460
50226
  }
49461
50227
  }
@@ -54175,6 +54941,16 @@ function trustGrantedByEnv() {
54175
54941
  const v = process.env[TRUST_ENV_VAR];
54176
54942
  return v === "1" || v === "true";
54177
54943
  }
54944
+ function declaresPluginSources(dir) {
54945
+ try {
54946
+ const raw = fs5.readFileSync(path3.join(dir, ".nexrall", "settings.json"), "utf-8");
54947
+ const obj = JSON.parse(raw);
54948
+ const s2 = obj.pluginSources;
54949
+ return Boolean(s2 && typeof s2 === "object" && !Array.isArray(s2) && Object.keys(s2).length > 0);
54950
+ } catch {
54951
+ return false;
54952
+ }
54953
+ }
54178
54954
  function detectTrustSignals(dir) {
54179
54955
  const signals = [];
54180
54956
  const exists = (...p) => {
@@ -54194,7 +54970,9 @@ function detectTrustSignals(dir) {
54194
54970
  signals.push(".nexrall/permissions.json \u2014 pre-approved tool permissions");
54195
54971
  }
54196
54972
  if (exists(".nexrall", "settings.json")) {
54197
- signals.push(".nexrall/settings.json \u2014 project settings and permission rules");
54973
+ signals.push(
54974
+ declaresPluginSources(dir) ? ".nexrall/settings.json \u2014 project settings, and declares plugin sources (aliases for remote plugin repos)" : ".nexrall/settings.json \u2014 project settings and permission rules"
54975
+ );
54198
54976
  }
54199
54977
  if (exists("nexrall.md")) {
54200
54978
  signals.push("nexrall.md \u2014 project instructions added to the agent\u2019s prompt");
@@ -60703,15 +61481,15 @@ var generateLineNumbers = (line, around) => {
60703
61481
  }
60704
61482
  return lineNumbers;
60705
61483
  };
60706
- var codeExcerpt = (source, line, options = {}) => {
61484
+ var codeExcerpt = (source2, line, options = {}) => {
60707
61485
  var _a;
60708
- if (typeof source !== "string") {
61486
+ if (typeof source2 !== "string") {
60709
61487
  throw new TypeError("Source code is missing.");
60710
61488
  }
60711
61489
  if (!line || line < 1) {
60712
61490
  throw new TypeError("Line number must start from `1`.");
60713
61491
  }
60714
- const lines = dist_default3(source).split(/\r?\n/);
61492
+ const lines = dist_default3(source2).split(/\r?\n/);
60715
61493
  if (line > lines.length) {
60716
61494
  return;
60717
61495
  }
@@ -63166,7 +63944,7 @@ var InkReadlineAdapter = class extends EventEmitter3 {
63166
63944
  };
63167
63945
 
63168
63946
  // src/commands/chat.ts
63169
- var CLI_VERSION = "0.5.49";
63947
+ var CLI_VERSION = "0.5.51";
63170
63948
  var MODEL_LABELS = {
63171
63949
  turbo: "Nexrall Turbo",
63172
63950
  pro: "Nexrall Pro",
@@ -63384,15 +64162,23 @@ function formatContextUsage(messages, modelAlias) {
63384
64162
  return lines.join("\n");
63385
64163
  }
63386
64164
  function formatAgentsList(workDir) {
63387
- const types3 = (0, import_code_core3.loadAgentTypes)(workDir);
64165
+ const { types: types3, warnings } = (0, import_code_core3.loadAgentTypesWithWarnings)(workDir);
63388
64166
  if (!types3.length)
63389
64167
  return source_default.dim(" No agent types found (this should not happen \u2014 builtins always register).");
63390
64168
  const lines = types3.map((t2) => {
63391
- const tools = t2.tools ? source_default.dim(` \u2014 tools: ${t2.tools.join(", ")}`) : source_default.dim(" \u2014 all tools");
64169
+ const tools = t2.tools ? source_default.dim(` \u2014 tools: ${t2.tools.join(", ")}`) : source_default.dim(" \u2014 ALL tools (no allowlist)");
63392
64170
  const model = t2.model ? source_default.dim(` \u2014 model: ${t2.model}`) : "";
63393
64171
  const scope = source_default.dim(` (${t2.source})`);
63394
64172
  return " " + source_default.cyan(t2.name.padEnd(16)) + source_default.white(t2.description) + scope + tools + model;
63395
64173
  });
64174
+ if (warnings.length) {
64175
+ lines.push("");
64176
+ lines.push(" " + source_default.yellow.bold(`\u26A0 ${warnings.length} problem${warnings.length > 1 ? "s" : ""} in your agent definitions:`));
64177
+ for (const w of warnings) {
64178
+ lines.push(" " + source_default.yellow(`\u2022 ${w.agent}`) + source_default.dim(` \u2014 ${w.message}`));
64179
+ lines.push(" " + source_default.dim(w.file));
64180
+ }
64181
+ }
63396
64182
  return lines.join("\n");
63397
64183
  }
63398
64184
  async function runTurn(messages, modelAlias, workDir, abortSignal, env3, nexrallMd, mode, effort, checkpointManager, onProgress) {
@@ -63429,6 +64215,12 @@ async function runTurn(messages, modelAlias, workDir, abortSignal, env3, nexrall
63429
64215
  env: env3,
63430
64216
  nexrallMd,
63431
64217
  mode,
64218
+ // `/mode plan` previously only hinted to the backend — the footer said "plan
64219
+ // mode on" while every write tool stayed live, so the one mode whose entire
64220
+ // promise is "I will not touch anything" was the one not enforced. This
64221
+ // makes it real: the loop refuses mutating tools before the permission
64222
+ // prompt, and sub-agents inherit the lock.
64223
+ planMode: mode === "plan",
63432
64224
  effort,
63433
64225
  // The backend streams a cumulative output-token count (routes/code.js's
63434
64226
  // sendProgress, throttled to ≤5/s) covering thinking, visible text AND
@@ -63582,6 +64374,7 @@ async function runTurnHeadless(messages, modelAlias, workDir, _abortSignal, env3
63582
64374
  env: env3,
63583
64375
  nexrallMd,
63584
64376
  mode,
64377
+ planMode: mode === "plan",
63585
64378
  effort,
63586
64379
  abortSignal: _abortSignal,
63587
64380
  onText: (text) => {
@@ -64509,11 +65302,23 @@ ${caption}` : `[Attached: ${rel}]`;
64509
65302
  // src/commands/plugin.ts
64510
65303
  var import_prompts = __toESM(require_prompts3(), 1);
64511
65304
  var import_code_core4 = __toESM(require_dist2(), 1);
64512
- function describeInspection(i2, name) {
65305
+ function describeInspection(i2, name, ctx) {
64513
65306
  console.log();
64514
65307
  console.log(source_default.bold(` Plugin: ${name}`) + (i2.meta.version ? source_default.dim(` v${i2.meta.version}`) : ""));
64515
65308
  if (i2.meta.description)
64516
65309
  console.log(source_default.dim(` ${i2.meta.description}`));
65310
+ if (ctx?.sha) {
65311
+ const short = (s2) => s2.slice(0, 7);
65312
+ if (ctx.previousSha && ctx.previousSha !== ctx.sha) {
65313
+ console.log(source_default.dim(" commit: ") + source_default.yellow(`${short(ctx.previousSha)} \u2192 ${short(ctx.sha)}`));
65314
+ } else if (ctx.previousSha === ctx.sha) {
65315
+ console.log(source_default.dim(` commit: ${short(ctx.sha)} (already up to date)`));
65316
+ } else {
65317
+ console.log(source_default.dim(` commit: ${short(ctx.sha)}`));
65318
+ }
65319
+ } else if (ctx?.isUpdate) {
65320
+ console.log(source_default.dim(" commit: unknown (could not reach the GitHub API)"));
65321
+ }
64517
65322
  if (i2.commands.length)
64518
65323
  console.log(` ${source_default.cyan("commands:")} ${i2.commands.map((c) => "/" + c).join(", ")}`);
64519
65324
  if (i2.agents.length)
@@ -64523,10 +65328,16 @@ function describeInspection(i2, name) {
64523
65328
  if (i2.hasMcp)
64524
65329
  console.log(` ${source_default.yellow("mcp:")} yes`);
64525
65330
  }
64526
- async function confirmInstall(i2, name, autoYes) {
64527
- describeInspection(i2, name);
65331
+ async function confirmInstall(i2, name, autoYes, ctx) {
65332
+ describeInspection(i2, name, ctx);
64528
65333
  const dangerous = i2.hasHooks || i2.hasMcp;
64529
65334
  if (dangerous) {
65335
+ if (ctx?.isUpdate) {
65336
+ console.log();
65337
+ console.log(source_default.red.bold(" \u26A0 THIS UPDATE SHIPS CODE EXECUTION"));
65338
+ console.log(source_default.red(" A version of this plugin you already trust is being replaced by one"));
65339
+ console.log(source_default.red(" that runs commands. Review the diff before continuing."));
65340
+ }
64530
65341
  console.log();
64531
65342
  console.log(source_default.red.bold(" \u26A0 SECURITY WARNING"));
64532
65343
  if (i2.hasHooks) {
@@ -64562,35 +65373,43 @@ async function confirmInstall(i2, name, autoYes) {
64562
65373
  async function pluginInstallCommand(spec, opts) {
64563
65374
  const scope = opts.project ? "project" : "global";
64564
65375
  try {
64565
- let source = spec;
65376
+ let source2 = spec;
64566
65377
  let registryName;
64567
65378
  const isShortName = /^[\w.-]+$/.test(spec.trim()) && !spec.includes("/");
64568
65379
  if (isShortName) {
65380
+ const alias = (0, import_code_core4.resolveSourceAlias)(spec.trim(), process.cwd(), { includeProject: true });
65381
+ if (alias) {
65382
+ source2 = alias.spec;
65383
+ console.log(source_default.dim(` Source: ${alias.name} (${alias.tier}) \u2190 ${alias.spec}`));
65384
+ }
65385
+ }
65386
+ if (isShortName && source2 === spec) {
64569
65387
  const entry = await (0, import_code_core4.getRegistryPlugin)(spec.trim()).catch(() => null);
64570
65388
  if (entry) {
64571
- source = entry.source;
65389
+ source2 = entry.source;
64572
65390
  registryName = entry.name;
64573
65391
  const badge = entry.official ? source_default.magenta(" [official]") : entry.verified ? source_default.green(" [verified]") : "";
64574
65392
  console.log(source_default.dim(` Registry: ${entry.display_name}${badge} \u2190 ${entry.source}`));
64575
65393
  } else {
64576
- console.error(source_default.red(` \u2717 "${spec}" not found in the registry.`));
64577
- console.error(source_default.dim(" Search: nex plugin search <keyword>"));
64578
- console.error(source_default.dim(" Or install from GitHub directly: nex plugin install owner/repo"));
65394
+ console.error(source_default.red(` \u2717 "${spec}" is not a declared source or a registry plugin.`));
65395
+ console.error(source_default.dim(" Search: nex plugin search <keyword>"));
65396
+ console.error(source_default.dim(" From GitHub: nex plugin install owner/repo"));
65397
+ console.error(source_default.dim(" Declare it: nex plugin source add " + spec.trim() + " owner/repo"));
64579
65398
  process.exitCode = 1;
64580
65399
  return;
64581
65400
  }
64582
65401
  }
64583
- const res = await (0, import_code_core4.installPlugin)(source, {
65402
+ const res = await (0, import_code_core4.installPlugin)(source2, {
64584
65403
  scope,
64585
65404
  workDir: process.cwd(),
64586
65405
  name: opts.name ?? registryName,
64587
65406
  force: opts.force,
64588
- confirm: (i2, name) => confirmInstall(i2, name, opts.yes === true)
65407
+ confirm: (i2, name, ctx) => confirmInstall(i2, name, opts.yes === true, ctx)
64589
65408
  });
64590
65409
  if (registryName)
64591
65410
  (0, import_code_core4.reportInstall)(registryName);
64592
65411
  console.log();
64593
- console.log(source_default.green(` \u2713 Installed "${res.name}" (${res.scope}) \u2192 ${res.dir}`));
65412
+ console.log(source_default.green(` \u2713 Installed "${res.name}" (${res.scope}) \u2192 ${res.dir}`) + (res.sha ? source_default.dim(` @ ${res.sha.slice(0, 7)}`) : ""));
64594
65413
  if (res.inspection.commands.length) {
64595
65414
  console.log(source_default.dim(` Try: ${res.inspection.commands.map((c) => "/" + c).join(", ")}`));
64596
65415
  }
@@ -64624,9 +65443,10 @@ async function pluginUpdateCommand(name, opts) {
64624
65443
  try {
64625
65444
  const res = await (0, import_code_core4.updatePlugin)(t2, {
64626
65445
  workDir,
64627
- confirm: (i2, n) => confirmInstall(i2, n, opts.yes === true)
65446
+ confirm: (i2, n, ctx) => confirmInstall(i2, n, opts.yes === true, ctx)
64628
65447
  });
64629
- console.log(source_default.green(` \u2713 Updated "${res.name}"`));
65448
+ const moved = res.sha && res.previousSha && res.sha !== res.previousSha ? source_default.dim(` ${res.previousSha.slice(0, 7)} \u2192 ${res.sha.slice(0, 7)}`) : res.sha && res.previousSha === res.sha ? source_default.dim(" (unchanged)") : "";
65449
+ console.log(source_default.green(` \u2713 Updated "${res.name}"`) + moved);
64630
65450
  } catch (err) {
64631
65451
  console.error(source_default.red(` \u2717 ${t2}: ${err.message}`));
64632
65452
  process.exitCode = 1;
@@ -64708,24 +65528,98 @@ function pluginListCommand() {
64708
65528
  for (const p of plugins) {
64709
65529
  const receipt = (0, import_code_core4.readReceipt)(p.dir);
64710
65530
  const src = receipt ? source_default.dim(` \u2190 ${receipt.source}`) : source_default.dim(" (manual)");
65531
+ const at = receipt?.sha ? source_default.dim(` @ ${receipt.sha.slice(0, 7)}`) : "";
65532
+ const insp = (0, import_code_core4.inspectPluginDir)(p.dir);
65533
+ const runs = insp.hasHooks || insp.hasMcp ? source_default.yellow(` \u26A0 ${[insp.hasHooks && "hooks", insp.hasMcp && "mcp"].filter(Boolean).join("+")}`) : "";
64711
65534
  console.log(
64712
- " " + source_default.cyan(p.name.padEnd(24)) + source_default.dim(`${(p.version ?? "").padEnd(10)}${p.scope.padEnd(9)}`) + src
65535
+ " " + source_default.cyan(p.name.padEnd(24)) + source_default.dim(`${(p.version ?? "").padEnd(10)}${p.scope.padEnd(9)}`) + src + at + runs
64713
65536
  );
64714
65537
  }
64715
65538
  console.log();
64716
65539
  }
65540
+ function pluginSourceListCommand() {
65541
+ const workDir = process.cwd();
65542
+ const sources = (0, import_code_core4.loadDeclaredSources)(workDir, { includeProject: true });
65543
+ const patterns = (0, import_code_core4.allowedSourcePatterns)();
65544
+ if (patterns) {
65545
+ console.log();
65546
+ console.log(source_default.bold(" Organisation policy: ") + source_default.dim(`installs restricted to ${patterns.join(", ")}`));
65547
+ }
65548
+ if (!sources.length) {
65549
+ console.log(source_default.dim("\n No plugin sources declared."));
65550
+ console.log(source_default.dim(" Add one: nex plugin source add <alias> owner/repo"));
65551
+ console.log();
65552
+ return;
65553
+ }
65554
+ console.log();
65555
+ console.log(source_default.bold(" Declared plugin sources:"));
65556
+ for (const s2 of sources) {
65557
+ const tier = s2.tier === "project" ? source_default.yellow(s2.tier.padEnd(9)) : source_default.dim(s2.tier.padEnd(9));
65558
+ const blocked = !(0, import_code_core4.isSourceAllowed)(s2.spec, patterns) ? source_default.red(" \u2717 blocked by policy") : "";
65559
+ console.log(" " + source_default.cyan(s2.name.padEnd(20)) + tier + source_default.white(s2.spec) + blocked);
65560
+ }
65561
+ console.log();
65562
+ console.log(source_default.dim(" Install from one: nex plugin install <alias>"));
65563
+ console.log(source_default.dim(" Declaring a source does not install anything."));
65564
+ console.log();
65565
+ }
65566
+ function pluginSourceAddCommand(name, spec, opts) {
65567
+ try {
65568
+ (0, import_code_core4.parsePluginSource)(spec);
65569
+ const patterns = (0, import_code_core4.allowedSourcePatterns)();
65570
+ if (patterns && !(0, import_code_core4.isSourceAllowed)(spec, patterns)) {
65571
+ console.error(source_default.red(` \u2717 "${spec}" is not permitted by your organisation's policy.`));
65572
+ console.error(source_default.dim(` Allowed: ${patterns.join(", ")}`));
65573
+ process.exitCode = 1;
65574
+ return;
65575
+ }
65576
+ const scope = opts.project ? "project" : "user";
65577
+ const file = (0, import_code_core4.sourcesFileFor)(scope, process.cwd());
65578
+ (0, import_code_core4.writeDeclaredSource)(file, name, spec);
65579
+ console.log();
65580
+ console.log(source_default.green(` \u2713 Declared "${name}" \u2192 ${spec}`) + source_default.dim(` (${scope})`));
65581
+ console.log(source_default.dim(` ${file}`));
65582
+ console.log(source_default.dim(` Nothing installed yet \u2014 run: nex plugin install ${name}`));
65583
+ console.log();
65584
+ } catch (err) {
65585
+ console.error(source_default.red(` \u2717 ${err.message}`));
65586
+ process.exitCode = 1;
65587
+ }
65588
+ }
65589
+ function pluginSourceRemoveCommand(name, opts) {
65590
+ const workDir = process.cwd();
65591
+ const scope = opts.project ? "project" : "user";
65592
+ const file = (0, import_code_core4.sourcesFileFor)(scope, workDir);
65593
+ if ((0, import_code_core4.removeDeclaredSource)(file, name)) {
65594
+ console.log(source_default.green(` \u2713 Removed source "${name}" (${scope})`));
65595
+ console.log(source_default.dim(" Plugins already installed from it are untouched \u2014 remove them with nex plugin remove <name>"));
65596
+ return;
65597
+ }
65598
+ const found = (0, import_code_core4.resolveSourceAlias)(name, workDir, { includeProject: true });
65599
+ if (found) {
65600
+ console.error(source_default.red(` \u2717 "${name}" is declared in the ${found.tier} tier, not ${scope}.`));
65601
+ console.error(source_default.dim(` ${found.file}`));
65602
+ if (found.tier === "project")
65603
+ console.error(source_default.dim(" Retry with --project"));
65604
+ if (found.tier === "managed")
65605
+ console.error(source_default.dim(" Managed sources are set by your organisation and cannot be removed here."));
65606
+ } else {
65607
+ console.error(source_default.red(` \u2717 No source named "${name}".`));
65608
+ }
65609
+ process.exitCode = 1;
65610
+ }
64717
65611
 
64718
65612
  // src/index.ts
64719
65613
  var program2 = new Command();
64720
- program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version("0.5.49").enablePositionalOptions();
65614
+ program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version("0.5.51").enablePositionalOptions();
64721
65615
  program2.command("auth").description("Login to your Nexrall account").action(authCommand);
64722
65616
  program2.command("logout").description("Log out of your Nexrall account").action(logoutCommand);
64723
65617
  program2.command("update").description("Update nex to the latest version").option("-c, --check", "Check for updates without installing").option("-y, --yes", "Skip confirmation prompt").action(async (opts) => {
64724
65618
  await updateCommand({ check: opts.check, yes: opts.yes });
64725
65619
  });
64726
65620
  var plugin = program2.command("plugin").description("Manage plugins (install from GitHub, list, update, remove)");
64727
- plugin.command("install <source>").description("Install a plugin: registry name, owner/repo(#ref)(/subdir), GitHub URL, or local path").option("--project", "Install into ./.nexrall/plugins (default: ~/.nexrall/plugins)").option("--name <name>", "Override the installed plugin name").option("-f, --force", "Overwrite if already installed").option("-y, --yes", "Skip confirmation (still prompts for plugins with hooks/MCP)").action(async (source, opts) => {
64728
- await pluginInstallCommand(source, opts);
65621
+ plugin.command("install <source>").description("Install a plugin: registry name, owner/repo(#ref)(/subdir), GitHub URL, or local path").option("--project", "Install into ./.nexrall/plugins (default: ~/.nexrall/plugins)").option("--name <name>", "Override the installed plugin name").option("-f, --force", "Overwrite if already installed").option("-y, --yes", "Skip confirmation (still prompts for plugins with hooks/MCP)").action(async (source2, opts) => {
65622
+ await pluginInstallCommand(source2, opts);
64729
65623
  });
64730
65624
  plugin.command("remove <name>").alias("uninstall").description("Remove an installed plugin").option("--project", "Only look in ./.nexrall/plugins").action((name, opts) => {
64731
65625
  pluginRemoveCommand(name, opts);
@@ -64742,6 +65636,16 @@ plugin.command("search [query]").description("Search the Nexrall plugin registry
64742
65636
  plugin.command("info <name>").description("Show registry details for a plugin").action(async (name) => {
64743
65637
  await pluginInfoCommand(name);
64744
65638
  });
65639
+ var source = plugin.command("source").description("Declare where plugins may be installed from (aliases for plugin repos)");
65640
+ source.command("list").alias("ls").description("List declared plugin sources").action(() => {
65641
+ pluginSourceListCommand();
65642
+ });
65643
+ source.command("add <name> <spec>").description("Declare a source: nex plugin source add acme acme/plugins").option("--project", "Write to ./.nexrall/settings.json so the team shares it (default: ~/.nexrall/settings.json)").action((name, spec, opts) => {
65644
+ pluginSourceAddCommand(name, spec, opts);
65645
+ });
65646
+ source.command("remove <name>").alias("rm").description("Remove a declared source (installed plugins are untouched)").option("--project", "Remove from ./.nexrall/settings.json").action((name, opts) => {
65647
+ pluginSourceRemoveCommand(name, opts);
65648
+ });
64745
65649
  program2.command("sessions").description("List or delete saved chat sessions").option("-d, --delete <id>", "Delete a session by id").option("--clear-all", "Delete ALL saved sessions").action((opts) => {
64746
65650
  if (opts.clearAll) {
64747
65651
  const sessions2 = listSessions();