nexrall-code 0.5.49 → 0.5.50

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 +705 -194
  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)
@@ -15255,7 +15410,7 @@ var require_loop = __commonJS({
15255
15410
  };
15256
15411
  }();
15257
15412
  Object.defineProperty(exports, "__esModule", { value: true });
15258
- exports.VERIFY_CMD_RE = exports.WRITE_TOOL_NAMES = void 0;
15413
+ exports.VERIFY_CMD_RE = exports.WRITE_TOOL_NAMES = exports.ToolNotAllowedError = void 0;
15259
15414
  exports.resolveMaxIterations = resolveMaxIterations;
15260
15415
  exports.createLimiter = createLimiter;
15261
15416
  exports.extractSubTaskText = extractSubTaskText;
@@ -15532,6 +15687,13 @@ var require_loop = __commonJS({
15532
15687
  var _subTaskCounter = 0;
15533
15688
  var SUBTASK_TIMEOUT_MS = Number(process.env.NEXRALL_SUBTASK_TIMEOUT_MS) > 0 ? Number(process.env.NEXRALL_SUBTASK_TIMEOUT_MS) : 10 * 60 * 1e3;
15534
15689
  var SUBTASK_MAX = 48e3;
15690
+ var ToolNotAllowedError = class extends Error {
15691
+ constructor(message) {
15692
+ super(message);
15693
+ this.name = "ToolNotAllowedError";
15694
+ }
15695
+ };
15696
+ exports.ToolNotAllowedError = ToolNotAllowedError;
15535
15697
  function sliceSafeEnd(s2, max) {
15536
15698
  if (s2.length <= max)
15537
15699
  return s2;
@@ -15595,10 +15757,18 @@ ${tail}`;
15595
15757
  return { error: "Sub-agents cannot spawn further sub-agents. Do this work directly, or report back so the main agent can delegate it." };
15596
15758
  }
15597
15759
  const requestedType = typeof input.subagent_type === "string" ? input.subagent_type : "";
15598
- const agent = (0, agentTypes_1.findAgentType)(agentTypes, requestedType);
15760
+ let agent = (0, agentTypes_1.findAgentType)(agentTypes, requestedType);
15761
+ let knownTypes = agentTypes;
15762
+ if (requestedType && !agent) {
15763
+ knownTypes = (0, agentTypes_1.loadAgentTypes)(options.workDir);
15764
+ agent = (0, agentTypes_1.findAgentType)(knownTypes, requestedType);
15765
+ }
15599
15766
  if (requestedType && !agent) {
15600
- const known = agentTypes.map((a) => a.name).join(", ") || "(none defined)";
15601
- return { error: `Unknown subagent_type "${requestedType}". Available types: ${known}.` };
15767
+ const known = knownTypes.map((a) => a.name).join(", ") || "(none defined)";
15768
+ return {
15769
+ error: `Unknown subagent_type "${requestedType}". Available types: ${known}.
15770
+ 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."
15771
+ };
15602
15772
  }
15603
15773
  const subNexrallMd = agent ? `# Sub-agent role: ${agent.name}
15604
15774
  ${agent.prompt}` + (options.nexrallMd ? `
@@ -15608,10 +15778,12 @@ ${agent.prompt}` + (options.nexrallMd ? `
15608
15778
  ${options.nexrallMd}` : "") : options.nexrallMd;
15609
15779
  const allowed = agent?.tools ? new Set(agent.tools) : null;
15610
15780
  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;
15781
+ if (allowed && !allowed.has(req.tool)) {
15782
+ 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.`);
15783
+ }
15784
+ if (agent?.testFilesOnly && !allowsTestOnlyWrite(req.tool, req.input)) {
15785
+ 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.`);
15786
+ }
15615
15787
  return options.requestPermission(req);
15616
15788
  };
15617
15789
  const subMessages = [
@@ -16297,13 +16469,16 @@ Continue the work from here.` }] });
16297
16469
  }
16298
16470
  const description = humanDescription(name, input);
16299
16471
  let permitted;
16472
+ let deniedReason = null;
16300
16473
  try {
16301
16474
  permitted = await options.requestPermission({ tool: name, input, description });
16302
- } catch {
16475
+ } catch (err) {
16303
16476
  permitted = false;
16477
+ if (err instanceof ToolNotAllowedError)
16478
+ deniedReason = err.message;
16304
16479
  }
16305
16480
  if (!permitted) {
16306
- result = { error: "Permission denied by user" };
16481
+ result = { error: deniedReason ?? "Permission denied by user" };
16307
16482
  } else if (name === "task") {
16308
16483
  result = depth === 0 ? await _subTaskLimit(() => runSubTask(input, options, agentTypes)) : await runSubTask(input, options, agentTypes);
16309
16484
  } else {
@@ -17744,6 +17919,175 @@ var require_destructive = __commonJS({
17744
17919
  }
17745
17920
  });
17746
17921
 
17922
+ // ../core/dist/plugins/sources.js
17923
+ var require_sources = __commonJS({
17924
+ "../core/dist/plugins/sources.js"(exports) {
17925
+ "use strict";
17926
+ var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m2, k, k2) {
17927
+ if (k2 === void 0)
17928
+ k2 = k;
17929
+ var desc = Object.getOwnPropertyDescriptor(m2, k);
17930
+ if (!desc || ("get" in desc ? !m2.__esModule : desc.writable || desc.configurable)) {
17931
+ desc = { enumerable: true, get: function() {
17932
+ return m2[k];
17933
+ } };
17934
+ }
17935
+ Object.defineProperty(o, k2, desc);
17936
+ } : function(o, m2, k, k2) {
17937
+ if (k2 === void 0)
17938
+ k2 = k;
17939
+ o[k2] = m2[k];
17940
+ });
17941
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? function(o, v) {
17942
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
17943
+ } : function(o, v) {
17944
+ o["default"] = v;
17945
+ });
17946
+ var __importStar = exports && exports.__importStar || /* @__PURE__ */ function() {
17947
+ var ownKeys = function(o) {
17948
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
17949
+ var ar = [];
17950
+ for (var k in o2)
17951
+ if (Object.prototype.hasOwnProperty.call(o2, k))
17952
+ ar[ar.length] = k;
17953
+ return ar;
17954
+ };
17955
+ return ownKeys(o);
17956
+ };
17957
+ return function(mod) {
17958
+ if (mod && mod.__esModule)
17959
+ return mod;
17960
+ var result = {};
17961
+ if (mod != null) {
17962
+ for (var k = ownKeys(mod), i2 = 0; i2 < k.length; i2++)
17963
+ if (k[i2] !== "default")
17964
+ __createBinding(result, mod, k[i2]);
17965
+ }
17966
+ __setModuleDefault(result, mod);
17967
+ return result;
17968
+ };
17969
+ }();
17970
+ Object.defineProperty(exports, "__esModule", { value: true });
17971
+ exports.loadDeclaredSources = loadDeclaredSources2;
17972
+ exports.allowedSourcePatterns = allowedSourcePatterns2;
17973
+ exports.isSourceAllowed = isSourceAllowed2;
17974
+ exports.resolveSourceAlias = resolveSourceAlias2;
17975
+ exports.writeDeclaredSource = writeDeclaredSource2;
17976
+ exports.removeDeclaredSource = removeDeclaredSource2;
17977
+ exports.sourcesFileFor = sourcesFileFor2;
17978
+ var fs9 = __importStar(__require("fs"));
17979
+ var path6 = __importStar(__require("path"));
17980
+ var os6 = __importStar(__require("os"));
17981
+ function managedSettingsPath() {
17982
+ if (process.platform === "darwin")
17983
+ return "/Library/Application Support/Nexrall/managed-settings.json";
17984
+ if (process.platform === "win32")
17985
+ return path6.join(process.env.PROGRAMDATA ?? "C:\\ProgramData", "Nexrall", "managed-settings.json");
17986
+ return "/etc/nexrall/managed-settings.json";
17987
+ }
17988
+ function readJson(file) {
17989
+ try {
17990
+ return JSON.parse(fs9.readFileSync(file, "utf-8"));
17991
+ } catch {
17992
+ return null;
17993
+ }
17994
+ }
17995
+ var ALIAS_RE = /^[a-z0-9][\w.-]{0,63}$/i;
17996
+ function readSpec(value) {
17997
+ if (typeof value === "string")
17998
+ return value.trim() || null;
17999
+ if (value && typeof value === "object") {
18000
+ const v = value;
18001
+ const inner = v.source ?? v.repo ?? v.url;
18002
+ if (typeof inner === "string")
18003
+ return inner.trim() || null;
18004
+ if (inner && typeof inner === "object") {
18005
+ const deep = inner.repo ?? inner.url;
18006
+ if (typeof deep === "string")
18007
+ return deep.trim() || null;
18008
+ }
18009
+ }
18010
+ return null;
18011
+ }
18012
+ function collect(file, tier, into) {
18013
+ const obj = readJson(file);
18014
+ const raw = obj?.pluginSources;
18015
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
18016
+ return;
18017
+ for (const [name, value] of Object.entries(raw)) {
18018
+ if (!ALIAS_RE.test(name))
18019
+ continue;
18020
+ const spec = readSpec(value);
18021
+ if (!spec)
18022
+ continue;
18023
+ if (into.has(name))
18024
+ continue;
18025
+ into.set(name, { name, spec, tier, file });
18026
+ }
18027
+ }
18028
+ function loadDeclaredSources2(workDir, opts = {}) {
18029
+ const out = /* @__PURE__ */ new Map();
18030
+ collect(managedSettingsPath(), "managed", out);
18031
+ collect(path6.join(os6.homedir(), ".nexrall", "settings.json"), "user", out);
18032
+ if (opts.includeProject) {
18033
+ collect(path6.join(workDir, ".nexrall", "settings.json"), "project", out);
18034
+ collect(path6.join(workDir, ".nexrall", "settings.local.json"), "project", out);
18035
+ }
18036
+ return [...out.values()];
18037
+ }
18038
+ function allowedSourcePatterns2() {
18039
+ const obj = readJson(managedSettingsPath());
18040
+ const v = obj?.allowedPluginSources;
18041
+ if (!Array.isArray(v))
18042
+ return null;
18043
+ const pats = v.filter((x2) => typeof x2 === "string" && x2.trim().length > 0);
18044
+ return pats.length ? pats : null;
18045
+ }
18046
+ function isSourceAllowed2(spec, patterns = allowedSourcePatterns2()) {
18047
+ if (!patterns)
18048
+ return true;
18049
+ const cleaned = spec.trim().replace(/^https?:\/\/(?:www\.)?github\.com\//i, "").replace(/\.git$/, "");
18050
+ const base = cleaned.split("#")[0].split("/").slice(0, 2).join("/").toLowerCase();
18051
+ return patterns.some((p) => {
18052
+ const pat = p.trim().toLowerCase().replace(/^https?:\/\/(?:www\.)?github\.com\//i, "");
18053
+ if (pat === "*")
18054
+ return true;
18055
+ if (pat.endsWith("/*"))
18056
+ return base.startsWith(pat.slice(0, -1));
18057
+ return base === pat.split("#")[0].split("/").slice(0, 2).join("/");
18058
+ });
18059
+ }
18060
+ function resolveSourceAlias2(alias, workDir, opts = {}) {
18061
+ const want = alias.trim().toLowerCase();
18062
+ return loadDeclaredSources2(workDir, opts).find((s2) => s2.name.toLowerCase() === want) ?? null;
18063
+ }
18064
+ function writeDeclaredSource2(file, name, spec) {
18065
+ if (!ALIAS_RE.test(name)) {
18066
+ throw new Error(`Invalid source name "${name}". Use letters, numbers, dot, dash or underscore.`);
18067
+ }
18068
+ const obj = readJson(file) ?? {};
18069
+ const existing = obj.pluginSources && typeof obj.pluginSources === "object" && !Array.isArray(obj.pluginSources) ? obj.pluginSources : {};
18070
+ obj.pluginSources = { ...existing, [name]: spec };
18071
+ fs9.mkdirSync(path6.dirname(file), { recursive: true });
18072
+ fs9.writeFileSync(file, JSON.stringify(obj, null, 2) + "\n", "utf-8");
18073
+ }
18074
+ function removeDeclaredSource2(file, name) {
18075
+ const obj = readJson(file);
18076
+ if (!obj?.pluginSources || typeof obj.pluginSources !== "object")
18077
+ return false;
18078
+ const sources = obj.pluginSources;
18079
+ if (!(name in sources))
18080
+ return false;
18081
+ delete sources[name];
18082
+ fs9.writeFileSync(file, JSON.stringify(obj, null, 2) + "\n", "utf-8");
18083
+ return true;
18084
+ }
18085
+ function sourcesFileFor2(scope, workDir) {
18086
+ return scope === "user" ? path6.join(os6.homedir(), ".nexrall", "settings.json") : path6.join(workDir, ".nexrall", "settings.json");
18087
+ }
18088
+ }
18089
+ });
18090
+
17747
18091
  // ../core/dist/plugins/installer.js
17748
18092
  var require_installer = __commonJS({
17749
18093
  "../core/dist/plugins/installer.js"(exports) {
@@ -17796,8 +18140,8 @@ var require_installer = __commonJS({
17796
18140
  exports.searchRegistry = searchRegistry2;
17797
18141
  exports.getRegistryPlugin = getRegistryPlugin2;
17798
18142
  exports.reportInstall = reportInstall2;
17799
- exports.parsePluginSource = parsePluginSource;
17800
- exports.inspectPluginDir = inspectPluginDir;
18143
+ exports.parsePluginSource = parsePluginSource2;
18144
+ exports.inspectPluginDir = inspectPluginDir2;
17801
18145
  exports.installPlugin = installPlugin2;
17802
18146
  exports.readReceipt = readReceipt2;
17803
18147
  exports.removePlugin = removePlugin2;
@@ -17807,6 +18151,8 @@ var require_installer = __commonJS({
17807
18151
  var os6 = __importStar(__require("os"));
17808
18152
  var child_process_1 = __require("child_process");
17809
18153
  var client_1 = require_client();
18154
+ var index_1 = require_plugins();
18155
+ var sources_1 = require_sources();
17810
18156
  var RECEIPT_FILE = "_install.json";
17811
18157
  async function searchRegistry2(query, limit = 50) {
17812
18158
  const u = new URL(`${client_1.API_BASE}/api/code/plugins`);
@@ -17836,7 +18182,7 @@ var require_installer = __commonJS({
17836
18182
  fetch(`${client_1.API_BASE}/api/code/plugins/${encodeURIComponent(name)}/installed`, { method: "POST" }).catch(() => {
17837
18183
  });
17838
18184
  }
17839
- function parsePluginSource(spec) {
18185
+ function parsePluginSource2(spec) {
17840
18186
  const raw = spec.trim();
17841
18187
  if (!raw)
17842
18188
  throw new Error("Empty plugin source.");
@@ -17883,29 +18229,45 @@ var require_installer = __commonJS({
17883
18229
  return [];
17884
18230
  }
17885
18231
  }
17886
- function inspectPluginDir(dir) {
18232
+ function inspectPluginDir2(dir) {
17887
18233
  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 {
18234
+ const manifest = (0, index_1.resolvePluginFile)(dir, "manifest");
18235
+ if (manifest) {
18236
+ try {
18237
+ const j = JSON.parse(fs9.readFileSync(manifest, "utf-8"));
18238
+ meta = {
18239
+ name: typeof j.name === "string" ? j.name : void 0,
18240
+ version: typeof j.version === "string" ? j.version : void 0,
18241
+ description: typeof j.description === "string" ? j.description : void 0
18242
+ };
18243
+ } catch {
18244
+ }
17896
18245
  }
17897
18246
  return {
17898
18247
  commands: listMd(path6.join(dir, "commands")),
17899
18248
  agents: listMd(path6.join(dir, "agents")),
17900
- hasHooks: fs9.existsSync(path6.join(dir, "hooks.json")),
17901
- hasMcp: fs9.existsSync(path6.join(dir, "mcp.json")),
18249
+ hasHooks: (0, index_1.resolvePluginFile)(dir, "hooks") !== null,
18250
+ hasMcp: (0, index_1.resolvePluginFile)(dir, "mcp") !== null,
17902
18251
  meta
17903
18252
  };
17904
18253
  }
17905
18254
  function looksLikePlugin(dir) {
17906
- const i2 = inspectPluginDir(dir);
18255
+ const i2 = inspectPluginDir2(dir);
17907
18256
  return i2.commands.length > 0 || i2.agents.length > 0 || i2.hasHooks || i2.hasMcp || i2.meta.name !== void 0;
17908
18257
  }
18258
+ async function resolveCommitSha(repo, ref) {
18259
+ try {
18260
+ const res = await fetch(`https://api.github.com/repos/${repo}/commits/${encodeURIComponent(ref || "HEAD")}`, {
18261
+ headers: { Accept: "application/vnd.github.sha", "User-Agent": "nexrall-code" }
18262
+ });
18263
+ if (!res.ok)
18264
+ return null;
18265
+ const sha = (await res.text()).trim();
18266
+ return /^[0-9a-f]{40}$/i.test(sha) ? sha : null;
18267
+ } catch {
18268
+ return null;
18269
+ }
18270
+ }
17909
18271
  async function downloadTarball(repo, ref, destFile) {
17910
18272
  const r2 = ref || "HEAD";
17911
18273
  const url = `https://codeload.github.com/${repo}/tar.gz/${encodeURIComponent(r2)}`;
@@ -17956,28 +18318,33 @@ var require_installer = __commonJS({
17956
18318
  return scope === "project" ? path6.join(workDir || process.cwd(), ".nexrall", "plugins") : path6.join(os6.homedir(), ".nexrall", "plugins");
17957
18319
  }
17958
18320
  async function installPlugin2(spec, opts = {}) {
17959
- const source = parsePluginSource(spec);
18321
+ const source2 = parsePluginSource2(spec);
17960
18322
  const scope = opts.scope ?? "global";
18323
+ const patterns = (0, sources_1.allowedSourcePatterns)();
18324
+ if (patterns && source2.kind === "github" && !(0, sources_1.isSourceAllowed)(source2.raw, patterns)) {
18325
+ throw new Error(`Blocked by your organisation's policy: plugins may only be installed from ${patterns.join(", ")}.`);
18326
+ }
17961
18327
  const tmpBase = fs9.mkdtempSync(path6.join(os6.tmpdir(), "nexrall-plugin-"));
17962
18328
  try {
17963
18329
  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}`);
18330
+ if (source2.kind === "local") {
18331
+ if (!fs9.existsSync(source2.repo) || !fs9.statSync(source2.repo).isDirectory()) {
18332
+ throw new Error(`Local plugin path not found: ${source2.repo}`);
17967
18333
  }
17968
- repoRoot = source.repo;
18334
+ repoRoot = source2.repo;
17969
18335
  } else {
17970
18336
  const tarFile = path6.join(tmpBase, "plugin.tar.gz");
17971
- await downloadTarball(source.repo, source.ref, tarFile);
18337
+ await downloadTarball(source2.repo, source2.ref, tarFile);
17972
18338
  repoRoot = extractTarball(tarFile, path6.join(tmpBase, "x"));
17973
18339
  }
17974
- let pluginRoot = source.subdir ? path6.join(repoRoot, source.subdir) : repoRoot;
18340
+ const sha = source2.kind === "github" ? await resolveCommitSha(source2.repo, source2.ref) : null;
18341
+ let pluginRoot = source2.subdir ? path6.join(repoRoot, source2.subdir) : repoRoot;
17975
18342
  pluginRoot = path6.resolve(pluginRoot);
17976
18343
  if (!path6.resolve(pluginRoot).startsWith(path6.resolve(repoRoot))) {
17977
18344
  throw new Error("Plugin subdirectory escapes the repository \u2014 refusing.");
17978
18345
  }
17979
18346
  if (!fs9.existsSync(pluginRoot) || !fs9.statSync(pluginRoot).isDirectory()) {
17980
- throw new Error(`Subdirectory not found in repo: ${source.subdir}`);
18347
+ throw new Error(`Subdirectory not found in repo: ${source2.subdir}`);
17981
18348
  }
17982
18349
  if (!looksLikePlugin(pluginRoot)) {
17983
18350
  const candidates = ["plugin", ".nexrall-plugin"].map((c) => path6.join(pluginRoot, c));
@@ -17988,12 +18355,16 @@ var require_installer = __commonJS({
17988
18355
  throw new Error("No plugin found: expected plugin.json, commands/, agents/, hooks.json or mcp.json at the source root.");
17989
18356
  }
17990
18357
  }
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));
18358
+ const inspection = inspectPluginDir2(pluginRoot);
18359
+ 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
18360
  if (!/^[\w.-]+$/.test(name))
17994
18361
  throw new Error(`Invalid plugin name "${name}".`);
17995
18362
  if (opts.confirm) {
17996
- const ok = await opts.confirm(inspection, name);
18363
+ const ok = await opts.confirm(inspection, name, {
18364
+ sha: sha ?? void 0,
18365
+ previousSha: opts._previousSha,
18366
+ isUpdate: Boolean(opts._previousSha) || Boolean(opts.force)
18367
+ });
17997
18368
  if (!ok)
17998
18369
  throw new Error("Installation cancelled.");
17999
18370
  }
@@ -18006,14 +18377,15 @@ var require_installer = __commonJS({
18006
18377
  }
18007
18378
  copyPluginDir(pluginRoot, dest);
18008
18379
  const receipt = {
18009
- source: source.raw,
18010
- kind: source.kind,
18011
- ref: source.ref,
18012
- subdir: source.subdir,
18013
- installedAt: (/* @__PURE__ */ new Date()).toISOString()
18380
+ source: source2.raw,
18381
+ kind: source2.kind,
18382
+ ref: source2.ref,
18383
+ subdir: source2.subdir,
18384
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
18385
+ ...sha ? { sha } : {}
18014
18386
  };
18015
18387
  fs9.writeFileSync(path6.join(dest, RECEIPT_FILE), JSON.stringify(receipt, null, 2));
18016
- return { name, dir: dest, scope, inspection };
18388
+ return { name, dir: dest, scope, inspection, sha: sha ?? void 0, previousSha: opts._previousSha };
18017
18389
  } finally {
18018
18390
  fs9.rmSync(tmpBase, { recursive: true, force: true });
18019
18391
  }
@@ -18052,7 +18424,14 @@ var require_installer = __commonJS({
18052
18424
  if (receipt.kind === "local") {
18053
18425
  throw new Error(`Plugin "${name}" was installed from a local path \u2014 re-run 'nex plugin install ${receipt.source} --force'.`);
18054
18426
  }
18055
- return installPlugin2(receipt.source, { scope: s2, workDir: opts.workDir, name, force: true, confirm: opts.confirm });
18427
+ return installPlugin2(receipt.source, {
18428
+ scope: s2,
18429
+ workDir: opts.workDir,
18430
+ name,
18431
+ force: true,
18432
+ confirm: opts.confirm,
18433
+ _previousSha: receipt.sha
18434
+ });
18056
18435
  }
18057
18436
  throw new Error(`Plugin "${name}" is not installed.`);
18058
18437
  }
@@ -18107,6 +18486,7 @@ var require_dist2 = __commonJS({
18107
18486
  __exportStar(require_destructive(), exports);
18108
18487
  __exportStar(require_plugins(), exports);
18109
18488
  __exportStar(require_installer(), exports);
18489
+ __exportStar(require_sources(), exports);
18110
18490
  }
18111
18491
  });
18112
18492
 
@@ -20922,23 +21302,23 @@ var require_react_reconciler_production = __commonJS({
20922
21302
  return "\nError generating stack: " + x2.message + "\n" + x2.stack;
20923
21303
  }
20924
21304
  }
20925
- function createCapturedValueAtFiber(value, source) {
21305
+ function createCapturedValueAtFiber(value, source2) {
20926
21306
  if ("object" === typeof value && null !== value) {
20927
21307
  var existing = CapturedStacks.get(value);
20928
21308
  if (void 0 !== existing)
20929
21309
  return existing;
20930
- source = {
21310
+ source2 = {
20931
21311
  value,
20932
- source,
20933
- stack: getStackByFiberInDevAndProd(source)
21312
+ source: source2,
21313
+ stack: getStackByFiberInDevAndProd(source2)
20934
21314
  };
20935
- CapturedStacks.set(value, source);
20936
- return source;
21315
+ CapturedStacks.set(value, source2);
21316
+ return source2;
20937
21317
  }
20938
21318
  return {
20939
21319
  value,
20940
- source,
20941
- stack: getStackByFiberInDevAndProd(source)
21320
+ source: source2,
21321
+ stack: getStackByFiberInDevAndProd(source2)
20942
21322
  };
20943
21323
  }
20944
21324
  function pushTreeFork(workInProgress2, totalChildren) {
@@ -30110,23 +30490,23 @@ var require_react_reconciler_development = __commonJS({
30110
30490
  function describeFunctionComponentFrameWithoutLineNumber(fn) {
30111
30491
  return (fn = fn ? fn.displayName || fn.name : "") ? describeBuiltInComponentFrame(fn) : "";
30112
30492
  }
30113
- function createCapturedValueAtFiber(value, source) {
30493
+ function createCapturedValueAtFiber(value, source2) {
30114
30494
  if ("object" === typeof value && null !== value) {
30115
30495
  var existing = CapturedStacks.get(value);
30116
30496
  if (void 0 !== existing)
30117
30497
  return existing;
30118
- source = {
30498
+ source2 = {
30119
30499
  value,
30120
- source,
30121
- stack: getStackByFiberInDevAndProd(source)
30500
+ source: source2,
30501
+ stack: getStackByFiberInDevAndProd(source2)
30122
30502
  };
30123
- CapturedStacks.set(value, source);
30124
- return source;
30503
+ CapturedStacks.set(value, source2);
30504
+ return source2;
30125
30505
  }
30126
30506
  return {
30127
30507
  value,
30128
- source,
30129
- stack: getStackByFiberInDevAndProd(source)
30508
+ source: source2,
30509
+ stack: getStackByFiberInDevAndProd(source2)
30130
30510
  };
30131
30511
  }
30132
30512
  function pushTreeFork(workInProgress2, totalChildren) {
@@ -42684,9 +43064,9 @@ var require_buffer_util = __commonJS({
42684
43064
  }
42685
43065
  return target;
42686
43066
  }
42687
- function _mask(source, mask, output, offset, length) {
43067
+ function _mask(source2, mask, output, offset, length) {
42688
43068
  for (let i2 = 0; i2 < length; i2++) {
42689
- output[offset + i2] = source[i2] ^ mask[i2 & 3];
43069
+ output[offset + i2] = source2[i2] ^ mask[i2 & 3];
42690
43070
  }
42691
43071
  }
42692
43072
  function _unmask(buffer, mask) {
@@ -42725,11 +43105,11 @@ var require_buffer_util = __commonJS({
42725
43105
  if (!process.env.WS_NO_BUFFER_UTIL) {
42726
43106
  try {
42727
43107
  const bufferUtil = __require("bufferutil");
42728
- module.exports.mask = function(source, mask, output, offset, length) {
43108
+ module.exports.mask = function(source2, mask, output, offset, length) {
42729
43109
  if (length < 48)
42730
- _mask(source, mask, output, offset, length);
43110
+ _mask(source2, mask, output, offset, length);
42731
43111
  else
42732
- bufferUtil.mask(source, mask, output, offset, length);
43112
+ bufferUtil.mask(source2, mask, output, offset, length);
42733
43113
  };
42734
43114
  module.exports.unmask = function(buffer, mask) {
42735
43115
  if (buffer.length < 32)
@@ -49446,16 +49826,16 @@ var require_dist3 = __commonJS({
49446
49826
  }
49447
49827
  function _objectSpread(target) {
49448
49828
  for (var i2 = 1; i2 < arguments.length; i2++) {
49449
- var source = arguments[i2] != null ? arguments[i2] : {};
49829
+ var source2 = arguments[i2] != null ? arguments[i2] : {};
49450
49830
  if (i2 % 2) {
49451
- ownKeys(Object(source), true).forEach(function(key) {
49452
- _defineProperty(target, key, source[key]);
49831
+ ownKeys(Object(source2), true).forEach(function(key) {
49832
+ _defineProperty(target, key, source2[key]);
49453
49833
  });
49454
49834
  } else if (Object.getOwnPropertyDescriptors) {
49455
- Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
49835
+ Object.defineProperties(target, Object.getOwnPropertyDescriptors(source2));
49456
49836
  } else {
49457
- ownKeys(Object(source)).forEach(function(key) {
49458
- Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
49837
+ ownKeys(Object(source2)).forEach(function(key) {
49838
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source2, key));
49459
49839
  });
49460
49840
  }
49461
49841
  }
@@ -54175,6 +54555,16 @@ function trustGrantedByEnv() {
54175
54555
  const v = process.env[TRUST_ENV_VAR];
54176
54556
  return v === "1" || v === "true";
54177
54557
  }
54558
+ function declaresPluginSources(dir) {
54559
+ try {
54560
+ const raw = fs5.readFileSync(path3.join(dir, ".nexrall", "settings.json"), "utf-8");
54561
+ const obj = JSON.parse(raw);
54562
+ const s2 = obj.pluginSources;
54563
+ return Boolean(s2 && typeof s2 === "object" && !Array.isArray(s2) && Object.keys(s2).length > 0);
54564
+ } catch {
54565
+ return false;
54566
+ }
54567
+ }
54178
54568
  function detectTrustSignals(dir) {
54179
54569
  const signals = [];
54180
54570
  const exists = (...p) => {
@@ -54194,7 +54584,9 @@ function detectTrustSignals(dir) {
54194
54584
  signals.push(".nexrall/permissions.json \u2014 pre-approved tool permissions");
54195
54585
  }
54196
54586
  if (exists(".nexrall", "settings.json")) {
54197
- signals.push(".nexrall/settings.json \u2014 project settings and permission rules");
54587
+ signals.push(
54588
+ 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"
54589
+ );
54198
54590
  }
54199
54591
  if (exists("nexrall.md")) {
54200
54592
  signals.push("nexrall.md \u2014 project instructions added to the agent\u2019s prompt");
@@ -60703,15 +61095,15 @@ var generateLineNumbers = (line, around) => {
60703
61095
  }
60704
61096
  return lineNumbers;
60705
61097
  };
60706
- var codeExcerpt = (source, line, options = {}) => {
61098
+ var codeExcerpt = (source2, line, options = {}) => {
60707
61099
  var _a;
60708
- if (typeof source !== "string") {
61100
+ if (typeof source2 !== "string") {
60709
61101
  throw new TypeError("Source code is missing.");
60710
61102
  }
60711
61103
  if (!line || line < 1) {
60712
61104
  throw new TypeError("Line number must start from `1`.");
60713
61105
  }
60714
- const lines = dist_default3(source).split(/\r?\n/);
61106
+ const lines = dist_default3(source2).split(/\r?\n/);
60715
61107
  if (line > lines.length) {
60716
61108
  return;
60717
61109
  }
@@ -63166,7 +63558,7 @@ var InkReadlineAdapter = class extends EventEmitter3 {
63166
63558
  };
63167
63559
 
63168
63560
  // src/commands/chat.ts
63169
- var CLI_VERSION = "0.5.49";
63561
+ var CLI_VERSION = "0.5.50";
63170
63562
  var MODEL_LABELS = {
63171
63563
  turbo: "Nexrall Turbo",
63172
63564
  pro: "Nexrall Pro",
@@ -63384,15 +63776,23 @@ function formatContextUsage(messages, modelAlias) {
63384
63776
  return lines.join("\n");
63385
63777
  }
63386
63778
  function formatAgentsList(workDir) {
63387
- const types3 = (0, import_code_core3.loadAgentTypes)(workDir);
63779
+ const { types: types3, warnings } = (0, import_code_core3.loadAgentTypesWithWarnings)(workDir);
63388
63780
  if (!types3.length)
63389
63781
  return source_default.dim(" No agent types found (this should not happen \u2014 builtins always register).");
63390
63782
  const lines = types3.map((t2) => {
63391
- const tools = t2.tools ? source_default.dim(` \u2014 tools: ${t2.tools.join(", ")}`) : source_default.dim(" \u2014 all tools");
63783
+ const tools = t2.tools ? source_default.dim(` \u2014 tools: ${t2.tools.join(", ")}`) : source_default.dim(" \u2014 ALL tools (no allowlist)");
63392
63784
  const model = t2.model ? source_default.dim(` \u2014 model: ${t2.model}`) : "";
63393
63785
  const scope = source_default.dim(` (${t2.source})`);
63394
63786
  return " " + source_default.cyan(t2.name.padEnd(16)) + source_default.white(t2.description) + scope + tools + model;
63395
63787
  });
63788
+ if (warnings.length) {
63789
+ lines.push("");
63790
+ lines.push(" " + source_default.yellow.bold(`\u26A0 ${warnings.length} problem${warnings.length > 1 ? "s" : ""} in your agent definitions:`));
63791
+ for (const w of warnings) {
63792
+ lines.push(" " + source_default.yellow(`\u2022 ${w.agent}`) + source_default.dim(` \u2014 ${w.message}`));
63793
+ lines.push(" " + source_default.dim(w.file));
63794
+ }
63795
+ }
63396
63796
  return lines.join("\n");
63397
63797
  }
63398
63798
  async function runTurn(messages, modelAlias, workDir, abortSignal, env3, nexrallMd, mode, effort, checkpointManager, onProgress) {
@@ -64509,11 +64909,23 @@ ${caption}` : `[Attached: ${rel}]`;
64509
64909
  // src/commands/plugin.ts
64510
64910
  var import_prompts = __toESM(require_prompts3(), 1);
64511
64911
  var import_code_core4 = __toESM(require_dist2(), 1);
64512
- function describeInspection(i2, name) {
64912
+ function describeInspection(i2, name, ctx) {
64513
64913
  console.log();
64514
64914
  console.log(source_default.bold(` Plugin: ${name}`) + (i2.meta.version ? source_default.dim(` v${i2.meta.version}`) : ""));
64515
64915
  if (i2.meta.description)
64516
64916
  console.log(source_default.dim(` ${i2.meta.description}`));
64917
+ if (ctx?.sha) {
64918
+ const short = (s2) => s2.slice(0, 7);
64919
+ if (ctx.previousSha && ctx.previousSha !== ctx.sha) {
64920
+ console.log(source_default.dim(" commit: ") + source_default.yellow(`${short(ctx.previousSha)} \u2192 ${short(ctx.sha)}`));
64921
+ } else if (ctx.previousSha === ctx.sha) {
64922
+ console.log(source_default.dim(` commit: ${short(ctx.sha)} (already up to date)`));
64923
+ } else {
64924
+ console.log(source_default.dim(` commit: ${short(ctx.sha)}`));
64925
+ }
64926
+ } else if (ctx?.isUpdate) {
64927
+ console.log(source_default.dim(" commit: unknown (could not reach the GitHub API)"));
64928
+ }
64517
64929
  if (i2.commands.length)
64518
64930
  console.log(` ${source_default.cyan("commands:")} ${i2.commands.map((c) => "/" + c).join(", ")}`);
64519
64931
  if (i2.agents.length)
@@ -64523,10 +64935,16 @@ function describeInspection(i2, name) {
64523
64935
  if (i2.hasMcp)
64524
64936
  console.log(` ${source_default.yellow("mcp:")} yes`);
64525
64937
  }
64526
- async function confirmInstall(i2, name, autoYes) {
64527
- describeInspection(i2, name);
64938
+ async function confirmInstall(i2, name, autoYes, ctx) {
64939
+ describeInspection(i2, name, ctx);
64528
64940
  const dangerous = i2.hasHooks || i2.hasMcp;
64529
64941
  if (dangerous) {
64942
+ if (ctx?.isUpdate) {
64943
+ console.log();
64944
+ console.log(source_default.red.bold(" \u26A0 THIS UPDATE SHIPS CODE EXECUTION"));
64945
+ console.log(source_default.red(" A version of this plugin you already trust is being replaced by one"));
64946
+ console.log(source_default.red(" that runs commands. Review the diff before continuing."));
64947
+ }
64530
64948
  console.log();
64531
64949
  console.log(source_default.red.bold(" \u26A0 SECURITY WARNING"));
64532
64950
  if (i2.hasHooks) {
@@ -64562,35 +64980,43 @@ async function confirmInstall(i2, name, autoYes) {
64562
64980
  async function pluginInstallCommand(spec, opts) {
64563
64981
  const scope = opts.project ? "project" : "global";
64564
64982
  try {
64565
- let source = spec;
64983
+ let source2 = spec;
64566
64984
  let registryName;
64567
64985
  const isShortName = /^[\w.-]+$/.test(spec.trim()) && !spec.includes("/");
64568
64986
  if (isShortName) {
64987
+ const alias = (0, import_code_core4.resolveSourceAlias)(spec.trim(), process.cwd(), { includeProject: true });
64988
+ if (alias) {
64989
+ source2 = alias.spec;
64990
+ console.log(source_default.dim(` Source: ${alias.name} (${alias.tier}) \u2190 ${alias.spec}`));
64991
+ }
64992
+ }
64993
+ if (isShortName && source2 === spec) {
64569
64994
  const entry = await (0, import_code_core4.getRegistryPlugin)(spec.trim()).catch(() => null);
64570
64995
  if (entry) {
64571
- source = entry.source;
64996
+ source2 = entry.source;
64572
64997
  registryName = entry.name;
64573
64998
  const badge = entry.official ? source_default.magenta(" [official]") : entry.verified ? source_default.green(" [verified]") : "";
64574
64999
  console.log(source_default.dim(` Registry: ${entry.display_name}${badge} \u2190 ${entry.source}`));
64575
65000
  } 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"));
65001
+ console.error(source_default.red(` \u2717 "${spec}" is not a declared source or a registry plugin.`));
65002
+ console.error(source_default.dim(" Search: nex plugin search <keyword>"));
65003
+ console.error(source_default.dim(" From GitHub: nex plugin install owner/repo"));
65004
+ console.error(source_default.dim(" Declare it: nex plugin source add " + spec.trim() + " owner/repo"));
64579
65005
  process.exitCode = 1;
64580
65006
  return;
64581
65007
  }
64582
65008
  }
64583
- const res = await (0, import_code_core4.installPlugin)(source, {
65009
+ const res = await (0, import_code_core4.installPlugin)(source2, {
64584
65010
  scope,
64585
65011
  workDir: process.cwd(),
64586
65012
  name: opts.name ?? registryName,
64587
65013
  force: opts.force,
64588
- confirm: (i2, name) => confirmInstall(i2, name, opts.yes === true)
65014
+ confirm: (i2, name, ctx) => confirmInstall(i2, name, opts.yes === true, ctx)
64589
65015
  });
64590
65016
  if (registryName)
64591
65017
  (0, import_code_core4.reportInstall)(registryName);
64592
65018
  console.log();
64593
- console.log(source_default.green(` \u2713 Installed "${res.name}" (${res.scope}) \u2192 ${res.dir}`));
65019
+ 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
65020
  if (res.inspection.commands.length) {
64595
65021
  console.log(source_default.dim(` Try: ${res.inspection.commands.map((c) => "/" + c).join(", ")}`));
64596
65022
  }
@@ -64624,9 +65050,10 @@ async function pluginUpdateCommand(name, opts) {
64624
65050
  try {
64625
65051
  const res = await (0, import_code_core4.updatePlugin)(t2, {
64626
65052
  workDir,
64627
- confirm: (i2, n) => confirmInstall(i2, n, opts.yes === true)
65053
+ confirm: (i2, n, ctx) => confirmInstall(i2, n, opts.yes === true, ctx)
64628
65054
  });
64629
- console.log(source_default.green(` \u2713 Updated "${res.name}"`));
65055
+ 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)") : "";
65056
+ console.log(source_default.green(` \u2713 Updated "${res.name}"`) + moved);
64630
65057
  } catch (err) {
64631
65058
  console.error(source_default.red(` \u2717 ${t2}: ${err.message}`));
64632
65059
  process.exitCode = 1;
@@ -64708,24 +65135,98 @@ function pluginListCommand() {
64708
65135
  for (const p of plugins) {
64709
65136
  const receipt = (0, import_code_core4.readReceipt)(p.dir);
64710
65137
  const src = receipt ? source_default.dim(` \u2190 ${receipt.source}`) : source_default.dim(" (manual)");
65138
+ const at = receipt?.sha ? source_default.dim(` @ ${receipt.sha.slice(0, 7)}`) : "";
65139
+ const insp = (0, import_code_core4.inspectPluginDir)(p.dir);
65140
+ const runs = insp.hasHooks || insp.hasMcp ? source_default.yellow(` \u26A0 ${[insp.hasHooks && "hooks", insp.hasMcp && "mcp"].filter(Boolean).join("+")}`) : "";
64711
65141
  console.log(
64712
- " " + source_default.cyan(p.name.padEnd(24)) + source_default.dim(`${(p.version ?? "").padEnd(10)}${p.scope.padEnd(9)}`) + src
65142
+ " " + source_default.cyan(p.name.padEnd(24)) + source_default.dim(`${(p.version ?? "").padEnd(10)}${p.scope.padEnd(9)}`) + src + at + runs
64713
65143
  );
64714
65144
  }
64715
65145
  console.log();
64716
65146
  }
65147
+ function pluginSourceListCommand() {
65148
+ const workDir = process.cwd();
65149
+ const sources = (0, import_code_core4.loadDeclaredSources)(workDir, { includeProject: true });
65150
+ const patterns = (0, import_code_core4.allowedSourcePatterns)();
65151
+ if (patterns) {
65152
+ console.log();
65153
+ console.log(source_default.bold(" Organisation policy: ") + source_default.dim(`installs restricted to ${patterns.join(", ")}`));
65154
+ }
65155
+ if (!sources.length) {
65156
+ console.log(source_default.dim("\n No plugin sources declared."));
65157
+ console.log(source_default.dim(" Add one: nex plugin source add <alias> owner/repo"));
65158
+ console.log();
65159
+ return;
65160
+ }
65161
+ console.log();
65162
+ console.log(source_default.bold(" Declared plugin sources:"));
65163
+ for (const s2 of sources) {
65164
+ const tier = s2.tier === "project" ? source_default.yellow(s2.tier.padEnd(9)) : source_default.dim(s2.tier.padEnd(9));
65165
+ const blocked = !(0, import_code_core4.isSourceAllowed)(s2.spec, patterns) ? source_default.red(" \u2717 blocked by policy") : "";
65166
+ console.log(" " + source_default.cyan(s2.name.padEnd(20)) + tier + source_default.white(s2.spec) + blocked);
65167
+ }
65168
+ console.log();
65169
+ console.log(source_default.dim(" Install from one: nex plugin install <alias>"));
65170
+ console.log(source_default.dim(" Declaring a source does not install anything."));
65171
+ console.log();
65172
+ }
65173
+ function pluginSourceAddCommand(name, spec, opts) {
65174
+ try {
65175
+ (0, import_code_core4.parsePluginSource)(spec);
65176
+ const patterns = (0, import_code_core4.allowedSourcePatterns)();
65177
+ if (patterns && !(0, import_code_core4.isSourceAllowed)(spec, patterns)) {
65178
+ console.error(source_default.red(` \u2717 "${spec}" is not permitted by your organisation's policy.`));
65179
+ console.error(source_default.dim(` Allowed: ${patterns.join(", ")}`));
65180
+ process.exitCode = 1;
65181
+ return;
65182
+ }
65183
+ const scope = opts.project ? "project" : "user";
65184
+ const file = (0, import_code_core4.sourcesFileFor)(scope, process.cwd());
65185
+ (0, import_code_core4.writeDeclaredSource)(file, name, spec);
65186
+ console.log();
65187
+ console.log(source_default.green(` \u2713 Declared "${name}" \u2192 ${spec}`) + source_default.dim(` (${scope})`));
65188
+ console.log(source_default.dim(` ${file}`));
65189
+ console.log(source_default.dim(` Nothing installed yet \u2014 run: nex plugin install ${name}`));
65190
+ console.log();
65191
+ } catch (err) {
65192
+ console.error(source_default.red(` \u2717 ${err.message}`));
65193
+ process.exitCode = 1;
65194
+ }
65195
+ }
65196
+ function pluginSourceRemoveCommand(name, opts) {
65197
+ const workDir = process.cwd();
65198
+ const scope = opts.project ? "project" : "user";
65199
+ const file = (0, import_code_core4.sourcesFileFor)(scope, workDir);
65200
+ if ((0, import_code_core4.removeDeclaredSource)(file, name)) {
65201
+ console.log(source_default.green(` \u2713 Removed source "${name}" (${scope})`));
65202
+ console.log(source_default.dim(" Plugins already installed from it are untouched \u2014 remove them with nex plugin remove <name>"));
65203
+ return;
65204
+ }
65205
+ const found = (0, import_code_core4.resolveSourceAlias)(name, workDir, { includeProject: true });
65206
+ if (found) {
65207
+ console.error(source_default.red(` \u2717 "${name}" is declared in the ${found.tier} tier, not ${scope}.`));
65208
+ console.error(source_default.dim(` ${found.file}`));
65209
+ if (found.tier === "project")
65210
+ console.error(source_default.dim(" Retry with --project"));
65211
+ if (found.tier === "managed")
65212
+ console.error(source_default.dim(" Managed sources are set by your organisation and cannot be removed here."));
65213
+ } else {
65214
+ console.error(source_default.red(` \u2717 No source named "${name}".`));
65215
+ }
65216
+ process.exitCode = 1;
65217
+ }
64717
65218
 
64718
65219
  // src/index.ts
64719
65220
  var program2 = new Command();
64720
- program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version("0.5.49").enablePositionalOptions();
65221
+ program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version("0.5.50").enablePositionalOptions();
64721
65222
  program2.command("auth").description("Login to your Nexrall account").action(authCommand);
64722
65223
  program2.command("logout").description("Log out of your Nexrall account").action(logoutCommand);
64723
65224
  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
65225
  await updateCommand({ check: opts.check, yes: opts.yes });
64725
65226
  });
64726
65227
  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);
65228
+ 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) => {
65229
+ await pluginInstallCommand(source2, opts);
64729
65230
  });
64730
65231
  plugin.command("remove <name>").alias("uninstall").description("Remove an installed plugin").option("--project", "Only look in ./.nexrall/plugins").action((name, opts) => {
64731
65232
  pluginRemoveCommand(name, opts);
@@ -64742,6 +65243,16 @@ plugin.command("search [query]").description("Search the Nexrall plugin registry
64742
65243
  plugin.command("info <name>").description("Show registry details for a plugin").action(async (name) => {
64743
65244
  await pluginInfoCommand(name);
64744
65245
  });
65246
+ var source = plugin.command("source").description("Declare where plugins may be installed from (aliases for plugin repos)");
65247
+ source.command("list").alias("ls").description("List declared plugin sources").action(() => {
65248
+ pluginSourceListCommand();
65249
+ });
65250
+ 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) => {
65251
+ pluginSourceAddCommand(name, spec, opts);
65252
+ });
65253
+ 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) => {
65254
+ pluginSourceRemoveCommand(name, opts);
65255
+ });
64745
65256
  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
65257
  if (opts.clearAll) {
64747
65258
  const sessions2 = listSessions();