nexrall-code 0.5.160 → 0.5.162

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 +534 -42
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3186,7 +3186,7 @@ var require_auth = __commonJS({
3186
3186
  }();
3187
3187
  Object.defineProperty(exports2, "__esModule", { value: true });
3188
3188
  exports2.saveAuth = saveAuth2;
3189
- exports2.loadAuth = loadAuth2;
3189
+ exports2.loadAuth = loadAuth3;
3190
3190
  exports2.clearAuth = clearAuth2;
3191
3191
  exports2.isAuthenticated = isAuthenticated4;
3192
3192
  exports2.getToken = getToken2;
@@ -3207,7 +3207,7 @@ var require_auth = __commonJS({
3207
3207
  ensureConfigDir();
3208
3208
  fs27.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 384 });
3209
3209
  }
3210
- function loadAuth2() {
3210
+ function loadAuth3() {
3211
3211
  const envToken = process.env.NEXRALL_TOKEN;
3212
3212
  if (envToken && envToken.trim().length > 0) {
3213
3213
  return { token: envToken.trim() };
@@ -3235,19 +3235,19 @@ var require_auth = __commonJS({
3235
3235
  }
3236
3236
  }
3237
3237
  function isAuthenticated4() {
3238
- const auth = loadAuth2();
3239
- return auth !== null && auth.token.length > 0;
3238
+ const auth2 = loadAuth3();
3239
+ return auth2 !== null && auth2.token.length > 0;
3240
3240
  }
3241
3241
  function getToken2() {
3242
- const auth = loadAuth2();
3243
- return auth?.token ?? null;
3242
+ const auth2 = loadAuth3();
3243
+ return auth2?.token ?? null;
3244
3244
  }
3245
3245
  function getRefreshToken() {
3246
3246
  const envToken = process.env.NEXRALL_TOKEN;
3247
3247
  if (envToken && envToken.trim().length > 0)
3248
3248
  return null;
3249
- const auth = loadAuth2();
3250
- const rt = auth?.refreshToken;
3249
+ const auth2 = loadAuth3();
3250
+ const rt = auth2?.refreshToken;
3251
3251
  return typeof rt === "string" && rt.length > 0 ? rt : null;
3252
3252
  }
3253
3253
  function updateTokens(token, refreshToken) {
@@ -10369,9 +10369,18 @@ var require_client = __commonJS({
10369
10369
  const haveCompleteMessage = () => completedMessage !== null;
10370
10370
  const partialInputs = {};
10371
10371
  let heartbeatWatchdog;
10372
+ let onControllerAbort;
10372
10373
  try {
10373
10374
  await new Promise((resolve11, reject) => {
10374
10375
  const stream = response.body;
10376
+ onControllerAbort = () => {
10377
+ stream.destroy?.();
10378
+ resolve11();
10379
+ };
10380
+ if (controller.signal.aborted)
10381
+ onControllerAbort();
10382
+ else
10383
+ controller.signal.addEventListener("abort", onControllerAbort, { once: true });
10375
10384
  const HEARTBEAT_TIMEOUT_MS = 9e4;
10376
10385
  const FIRST_EVENT_TIMEOUT_MS = Number(process.env.NEXRALL_FIRST_EVENT_TIMEOUT_MS) || 3e5;
10377
10386
  const PROGRESS_TIMEOUT_MS = Number(process.env.NEXRALL_PROGRESS_TIMEOUT_MS) || 15e4;
@@ -10646,6 +10655,8 @@ var require_client = __commonJS({
10646
10655
  } finally {
10647
10656
  if (heartbeatWatchdog !== void 0)
10648
10657
  clearInterval(heartbeatWatchdog);
10658
+ if (onControllerAbort)
10659
+ controller.signal.removeEventListener("abort", onControllerAbort);
10649
10660
  }
10650
10661
  if (completedMessage) {
10651
10662
  return completedMessage;
@@ -12129,7 +12140,7 @@ var require_agentTypes = __commonJS({
12129
12140
  exports2.parseAgentHooks = parseAgentHooks;
12130
12141
  exports2.loadAgentTypes = loadAgentTypes;
12131
12142
  exports2.loadAgentTypesWithWarnings = loadAgentTypesWithWarnings2;
12132
- exports2.knownToolNames = knownToolNames;
12143
+ exports2.knownToolNames = knownToolNames3;
12133
12144
  exports2.builtinAgents = builtinAgents;
12134
12145
  exports2.summariseAgents = summariseAgents;
12135
12146
  exports2.findAgentType = findAgentType;
@@ -12695,7 +12706,7 @@ var require_agentTypes = __commonJS({
12695
12706
  });
12696
12707
  return { types: types3, warnings };
12697
12708
  }
12698
- function knownToolNames() {
12709
+ function knownToolNames3() {
12699
12710
  return [...KNOWN_TOOL_NAMES].sort();
12700
12711
  }
12701
12712
  function builtinAgents() {
@@ -104661,6 +104672,8 @@ var require_rules = __commonJS({
104661
104672
  }();
104662
104673
  Object.defineProperty(exports2, "__esModule", { value: true });
104663
104674
  exports2.setSessionRules = setSessionRules2;
104675
+ exports2.setSessionSettings = setSessionSettings2;
104676
+ exports2.getSessionSettings = getSessionSettings;
104664
104677
  exports2.loadSettings = loadSettings5;
104665
104678
  exports2.evaluatePermission = evaluatePermission4;
104666
104679
  var fs27 = __importStar(__require("fs"));
@@ -104689,6 +104702,13 @@ var require_rules = __commonJS({
104689
104702
  function setSessionRules2(r2) {
104690
104703
  sessionRules = { allow: [...r2.allow ?? []], ask: [...r2.ask ?? []], deny: [...r2.deny ?? []] };
104691
104704
  }
104705
+ var sessionSettings = null;
104706
+ function setSessionSettings2(obj2) {
104707
+ sessionSettings = obj2;
104708
+ }
104709
+ function getSessionSettings() {
104710
+ return sessionSettings;
104711
+ }
104692
104712
  function loadSettings5(workDir) {
104693
104713
  const tiers = [
104694
104714
  managedSettingsPath(),
@@ -104708,6 +104728,13 @@ var require_rules = __commonJS({
104708
104728
  merged.ask.push(...r2.ask);
104709
104729
  merged.deny.push(...r2.deny);
104710
104730
  }
104731
+ if (sessionSettings) {
104732
+ Object.assign(raw, sessionSettings);
104733
+ const r2 = extractRules(sessionSettings);
104734
+ merged.allow.push(...r2.allow);
104735
+ merged.ask.push(...r2.ask);
104736
+ merged.deny.push(...r2.deny);
104737
+ }
104711
104738
  merged.allow.push(...sessionRules.allow);
104712
104739
  merged.ask.push(...sessionRules.ask);
104713
104740
  merged.deny.push(...sessionRules.deny);
@@ -106876,7 +106903,9 @@ var require_loop = __commonJS({
106876
106903
  Object.defineProperty(exports2, "__esModule", { value: true });
106877
106904
  exports2.VERIFY_CMD_RE = exports2.WRITE_TOOL_NAMES = exports2.ToolNotAllowedError = exports2.AGENT_MEMORY_TOOL_SCHEMA = exports2.AGENT_MEMORY_TOOL = exports2._stallLimits = exports2._emptyTurnRetry = exports2.bashNeedsRepoLock = void 0;
106878
106905
  exports2.runLifecycleHooks = runLifecycleHooks;
106906
+ exports2.runPermissionRequestHooks = runPermissionRequestHooks2;
106879
106907
  exports2.fireNotificationHooks = fireNotificationHooks2;
106908
+ exports2.fireManualPostCompactHook = fireManualPostCompactHook2;
106880
106909
  exports2.fireManualPreCompactHook = fireManualPreCompactHook2;
106881
106910
  exports2.emptyTurnBackoffMs = emptyTurnBackoffMs;
106882
106911
  exports2.shouldRetryEmptyTurn = shouldRetryEmptyTurn;
@@ -106970,6 +106999,15 @@ var require_loop = __commonJS({
106970
106999
  fromSettings = JSON.parse(fs27.readFileSync(p, "utf-8")).hooks ?? {};
106971
107000
  } catch {
106972
107001
  }
107002
+ const sess = (0, rules_1.getSessionSettings)()?.hooks;
107003
+ if (sess && typeof sess === "object") {
107004
+ const base = { ...fromSettings };
107005
+ for (const [phase, list] of Object.entries(sess)) {
107006
+ if (Array.isArray(list))
107007
+ base[phase] = [...base[phase] ?? [], ...list];
107008
+ }
107009
+ fromSettings = base;
107010
+ }
106973
107011
  const fromPlugins = (0, index_1.pluginHooks)(workDir);
106974
107012
  const merged = { ...fromSettings };
106975
107013
  for (const phase of Object.keys(fromPlugins)) {
@@ -107121,6 +107159,15 @@ var require_loop = __commonJS({
107121
107159
  if (out.startsWith("{")) {
107122
107160
  try {
107123
107161
  const j = JSON.parse(out);
107162
+ if (event === "PermissionRequest") {
107163
+ const d = j.hookSpecificOutput?.decision?.behavior ?? (typeof j.decision === "string" ? j.decision : j.decision?.behavior);
107164
+ if (d === "allow" || d === "approve")
107165
+ outcome.allow = true;
107166
+ else if (d === "deny" || d === "block") {
107167
+ outcome.block = true;
107168
+ outcome.reason = j.reason ?? j.hookSpecificOutput?.decision?.message ?? outcome.reason ?? "Denied by PermissionRequest hook";
107169
+ }
107170
+ }
107124
107171
  if (j.decision === "block") {
107125
107172
  outcome.block = true;
107126
107173
  outcome.reason = j.reason ?? outcome.reason ?? `Blocked by ${event} hook`;
@@ -107139,6 +107186,20 @@ var require_loop = __commonJS({
107139
107186
  }
107140
107187
  return outcome;
107141
107188
  }
107189
+ async function runPermissionRequestHooks2(workDir, tool, input, sessionId = "") {
107190
+ try {
107191
+ const items = loadHooks(workDir).PermissionRequest;
107192
+ if (!items?.length)
107193
+ return { answer: null };
107194
+ const o = await runLifecycleHooks(items, "PermissionRequest", workDir, { session_id: sessionId, tool_name: tool, tool_input: input }, tool);
107195
+ if (o.block)
107196
+ return { answer: "deny", reason: o.reason };
107197
+ if (o.allow)
107198
+ return { answer: "allow" };
107199
+ } catch {
107200
+ }
107201
+ return { answer: null };
107202
+ }
107142
107203
  function fireNotificationHooks2(workDir, message, type = "permission_prompt") {
107143
107204
  try {
107144
107205
  const items = loadHooks(workDir).Notification;
@@ -107149,6 +107210,15 @@ var require_loop = __commonJS({
107149
107210
  } catch {
107150
107211
  }
107151
107212
  }
107213
+ async function fireManualPostCompactHook2(workDir, sessionId, messagesSummarized) {
107214
+ try {
107215
+ const items = loadHooks(workDir).PostCompact;
107216
+ if (!items?.length)
107217
+ return;
107218
+ await runLifecycleHooks(items, "PostCompact", workDir, { trigger: "manual", session_id: sessionId, messages_summarized: messagesSummarized }, "manual");
107219
+ } catch {
107220
+ }
107221
+ }
107152
107222
  async function fireManualPreCompactHook2(workDir, sessionId, messagesToSummarize) {
107153
107223
  try {
107154
107224
  const items = loadHooks(workDir).PreCompact;
@@ -107960,6 +108030,23 @@ ${stats}${isoNote}`;
107960
108030
  try {
107961
108031
  if (started)
107962
108032
  started.value = true;
108033
+ {
108034
+ const subAgentName = agent?.name ?? (typeof input.subagent_type === "string" && input.subagent_type ? input.subagent_type : "general-purpose");
108035
+ const startItems = loadHooks(options.workDir).SubagentStart;
108036
+ if (startItems?.length) {
108037
+ const o = await runLifecycleHooks(startItems, "SubagentStart", options.workDir, { session_id: options.sessionId ?? "", agent_type: subAgentName, description: String(input.description ?? "") }, subAgentName).catch(() => ({ block: false }));
108038
+ if (o.context) {
108039
+ const last = subMessages[subMessages.length - 1];
108040
+ if (last && Array.isArray(last.content)) {
108041
+ subMessages[subMessages.length - 1] = { ...last, content: [...last.content, { type: "text", text: `
108042
+
108043
+ <hook-context>
108044
+ ${o.context}
108045
+ </hook-context>` }] };
108046
+ }
108047
+ }
108048
+ }
108049
+ }
107963
108050
  const childRun = runAgentLoop2(subMessages, {
107964
108051
  ...options,
107965
108052
  _depth: depth + 1,
@@ -108511,6 +108598,11 @@ Do NOT call any tools and do NOT continue the task. Reply with text only: a conc
108511
108598
  }
108512
108599
  const toSummarize = messages.slice(0, cut);
108513
108600
  const kept = messages.slice(cut);
108601
+ const firePostCompact = async () => {
108602
+ if ((options._depth ?? 0) !== 0)
108603
+ return;
108604
+ await runLifecycleHooks(loadHooks(options.workDir).PostCompact, "PostCompact", options.workDir, { trigger: "auto", session_id: options.sessionId ?? "", messages_summarized: cut }, "auto").catch(() => void 0);
108605
+ };
108514
108606
  const originalTask = originalTaskText(toSummarize);
108515
108607
  const summaryPrompt = `Summarize this coding-session transcript into concise bullet points the assistant needs to continue the work: key decisions, files changed (and how), commands run, unresolved problems, and user preferences. Max 400 words.
108516
108608
 
@@ -108579,6 +108671,7 @@ ${taskBlock}${ledgerBlock}Summary of the earlier conversation so far:
108579
108671
  ${summary}
108580
108672
 
108581
108673
  Continue the work from here.` }] });
108674
+ await firePostCompact();
108582
108675
  return true;
108583
108676
  }
108584
108677
  async function maybeCompactMemory(scope, options) {
@@ -108755,7 +108848,7 @@ Continue the work from here.` }] });
108755
108848
  async function runAgentLoop2(initialMessages, options) {
108756
108849
  const messages = [...initialMessages];
108757
108850
  const model = options.model ?? "turbo";
108758
- const hooks = withAgentHooks(loadHooks(options.workDir), options._agentHooks);
108851
+ const hooks = options.disableHooks ? withAgentHooks({}, options._agentHooks) : withAgentHooks(loadHooks(options.workDir), options._agentHooks);
108759
108852
  const depth = options._depth ?? 0;
108760
108853
  const agentScope = options._agentScope ?? "root";
108761
108854
  const isMainAgent = depth === 0;
@@ -109292,6 +109385,15 @@ ${ctx}
109292
109385
  void maybeCompactMemory(scope, options);
109293
109386
  }
109294
109387
  const post = await runToolHooks(hooks.PostToolUse, "PostToolUse", name, input, options.workDir, result);
109388
+ if (result.error !== void 0 && (hooks.PostToolUseFailure?.length ?? 0) > 0) {
109389
+ const f3 = await runLifecycleHooks(hooks.PostToolUseFailure, "PostToolUseFailure", options.workDir, { session_id: options.sessionId ?? "", tool_name: name, tool_input: input, error: String(result.error).slice(0, 4e3) }, name).catch(() => ({ block: false }));
109390
+ if (f3.context)
109391
+ post.context = [post.context, f3.context].filter(Boolean).join("\n");
109392
+ if (f3.block) {
109393
+ post.block = true;
109394
+ post.reason = [post.reason, f3.reason].filter(Boolean).join("; ");
109395
+ }
109396
+ }
109295
109397
  const injected = [pre.context, post.context].filter(Boolean).join("\n");
109296
109398
  if (injected) {
109297
109399
  if (result.error !== void 0)
@@ -109419,6 +109521,9 @@ ${result.output}` : `Error: ${result.error}` : result.output ?? "",
109419
109521
  throw err;
109420
109522
  throw new types_1.AgentTurnError(err?.message || "The turn ended unexpectedly", trimToResumableBoundary(messages), completedRounds, err);
109421
109523
  } finally {
109524
+ if (depth === 0 && (hooks.SessionEnd?.length ?? 0) > 0) {
109525
+ await runLifecycleHooks(hooks.SessionEnd, "SessionEnd", options.workDir, { session_id: options.sessionId ?? "", reason: stopReason }, stopReason).catch(() => void 0);
109526
+ }
109422
109527
  if (depth === 0)
109423
109528
  await runSimpleHooks(hooks.OnStop, options.workDir);
109424
109529
  else
@@ -151562,6 +151667,13 @@ async function requestPermissionInner(req) {
151562
151667
  console.error(paint.yellow(` \u2298 Plan mode \u2014 refused ${tool} (read-only until you switch mode).`));
151563
151668
  return false;
151564
151669
  }
151670
+ const hook = await (0, import_code_core5.runPermissionRequestHooks)(_workDir, tool, input);
151671
+ if (hook.answer === "allow")
151672
+ return true;
151673
+ if (hook.answer === "deny") {
151674
+ console.error(paint.red(` \u2716 Denied by PermissionRequest hook: ${hook.reason ?? tool}`));
151675
+ return false;
151676
+ }
151565
151677
  return serialisePrompt(() => askFlow(req));
151566
151678
  }
151567
151679
  async function requestPermission(req) {
@@ -151897,6 +152009,8 @@ function checkHeadlessOnly(f3) {
151897
152009
  return "--max-budget-usd only works with --output-format (a one-shot, non-interactive run).";
151898
152010
  if (f3.noSessionPersistence)
151899
152011
  return "--no-session-persistence only works with --output-format (a one-shot, non-interactive run).";
152012
+ if (f3.maxTurns !== void 0)
152013
+ return "--max-turns only works with --output-format (a one-shot, non-interactive run).";
151900
152014
  return null;
151901
152015
  }
151902
152016
  function headlessVerdict(i2) {
@@ -152065,6 +152179,182 @@ function validateJsonSchema(value, schema, at = "$") {
152065
152179
  }
152066
152180
  return errs;
152067
152181
  }
152182
+ function parseMaxTurns(raw) {
152183
+ const s2 = String(raw).trim();
152184
+ const n = Number(s2);
152185
+ if (!/^\d+$/.test(s2) || !Number.isSafeInteger(n) || n < 1) {
152186
+ return { ok: false, error: `Invalid --max-turns "${raw}". Use a whole number of 1 or more, e.g. 10` };
152187
+ }
152188
+ return { ok: true, value: n };
152189
+ }
152190
+ function readPromptFile(file, cwd2, flag = "--append-system-prompt-file") {
152191
+ const abs = path7.resolve(cwd2, file);
152192
+ let text;
152193
+ try {
152194
+ text = fs6.readFileSync(abs, "utf-8");
152195
+ } catch {
152196
+ return { ok: false, error: `${flag}: cannot read "${file}"` };
152197
+ }
152198
+ if (!text.trim())
152199
+ return { ok: false, error: `${flag}: "${file}" is empty` };
152200
+ return { ok: true, value: text.trim() };
152201
+ }
152202
+ var CLAUDE_TOOL_ALIASES = {
152203
+ Read: ["read_file", "list_directory", "search_files", "glob", "notebook_read"],
152204
+ Edit: ["edit_file", "multi_edit", "notebook_edit"],
152205
+ Write: ["write_file", "write_docx", "write_xlsx", "write_pptx"],
152206
+ Bash: ["bash", "bash_output", "kill_shell"],
152207
+ Glob: ["glob"],
152208
+ Grep: ["search_files"],
152209
+ WebFetch: ["fetch_url"],
152210
+ WebSearch: ["web_search"],
152211
+ Task: ["task"],
152212
+ TodoWrite: ["todo_write", "todo_read"],
152213
+ NotebookEdit: ["notebook_edit"]
152214
+ };
152215
+ function parseToolsFlag(raw, known) {
152216
+ const out = /* @__PURE__ */ new Set();
152217
+ const trimmed = String(raw).trim();
152218
+ if (trimmed === "")
152219
+ return { ok: true, value: [] };
152220
+ if (trimmed.toLowerCase() === "default")
152221
+ return { ok: true, value: [...known] };
152222
+ for (const piece of parseToolList(trimmed)) {
152223
+ if (CLAUDE_TOOL_ALIASES[piece]) {
152224
+ CLAUDE_TOOL_ALIASES[piece].forEach((t2) => out.add(t2));
152225
+ continue;
152226
+ }
152227
+ if (known.includes(piece) || piece.startsWith("mcp__")) {
152228
+ out.add(piece);
152229
+ continue;
152230
+ }
152231
+ return { ok: false, error: `--tools: unknown tool "${piece}". Known: ${[...Object.keys(CLAUDE_TOOL_ALIASES), ...known].join(", ")}` };
152232
+ }
152233
+ return { ok: true, value: [...out] };
152234
+ }
152235
+ function parseSessionId(raw) {
152236
+ const s2 = String(raw).trim();
152237
+ if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/.test(s2)) {
152238
+ return { ok: false, error: `Invalid --session-id "${raw}". Use letters, digits, "-" or "_" (max 64), e.g. a UUID` };
152239
+ }
152240
+ return { ok: true, value: s2 };
152241
+ }
152242
+ function parseSessionName(raw) {
152243
+ const s2 = String(raw).replace(/\s+/g, " ").trim();
152244
+ if (!s2)
152245
+ return { ok: false, error: "Invalid --name: it is empty" };
152246
+ return { ok: true, value: s2.slice(0, 80) };
152247
+ }
152248
+ function checkSessionIdentityFlags(f3) {
152249
+ const resuming = f3.resume !== void 0 || !!f3.continue;
152250
+ if (f3.fork && !resuming)
152251
+ return "--fork-session needs --resume <id> or --continue (there is nothing to fork otherwise).";
152252
+ if (f3.sessionId && resuming && !f3.fork)
152253
+ return "--session-id starts a new conversation; with --resume/--continue add --fork-session to branch it under that id.";
152254
+ return null;
152255
+ }
152256
+ function matchSessionByName(sessions, name) {
152257
+ const want = name.trim().toLowerCase();
152258
+ const hits = sessions.filter((s2) => s2.title.trim().toLowerCase() === want).sort((a, b) => b.updatedAt - a.updatedAt);
152259
+ return hits[0] ?? null;
152260
+ }
152261
+ function parseSettingsArg(spec, cwd2) {
152262
+ const s2 = String(spec).trim();
152263
+ let text = s2;
152264
+ if (!s2.startsWith("{")) {
152265
+ try {
152266
+ text = fs6.readFileSync(path7.resolve(cwd2, s2), "utf-8");
152267
+ } catch {
152268
+ return { ok: false, error: `--settings: cannot read "${s2}"` };
152269
+ }
152270
+ }
152271
+ let v;
152272
+ try {
152273
+ v = JSON.parse(text);
152274
+ } catch {
152275
+ return { ok: false, error: `--settings: ${s2.startsWith("{") ? "the inline value" : `"${s2}"`} is not valid JSON` };
152276
+ }
152277
+ if (!v || typeof v !== "object" || Array.isArray(v))
152278
+ return { ok: false, error: "--settings: expected a JSON object" };
152279
+ return { ok: true, value: v };
152280
+ }
152281
+ function authStatusReport(a, env4) {
152282
+ if (!a || !a.token)
152283
+ return { loggedIn: false, source: null };
152284
+ const fromEnv = !!env4.NEXRALL_TOKEN && env4.NEXRALL_TOKEN.trim().length > 0;
152285
+ return { loggedIn: true, source: fromEnv ? "env" : "login", ...a.email ? { email: a.email } : {} };
152286
+ }
152287
+ function isFallbackWorthy(message) {
152288
+ const m2 = String(message);
152289
+ if (/\b(401|402|403|400|404|413|422)\b/.test(m2) && !/\b(429|5\d\d)\b/.test(m2))
152290
+ return false;
152291
+ return /\b(429|500|502|503|504|529)\b|overload|rate.?limit|ECONNRESET|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|socket hang up|fetch failed|stream (failed|stalled|ended)|timed? ?out/i.test(m2);
152292
+ }
152293
+ function parseFallbackModel(raw, primary) {
152294
+ const s2 = String(raw).trim();
152295
+ if (!s2)
152296
+ return { ok: false, error: "Invalid --fallback-model: it is empty" };
152297
+ if (s2 === primary)
152298
+ return { ok: false, error: `--fallback-model "${s2}" is the same as the main model; pick a different one` };
152299
+ return { ok: true, value: s2 };
152300
+ }
152301
+ function resolveSystemPrompt(f3) {
152302
+ if (f3.text !== void 0 && f3.file !== void 0) {
152303
+ return { ok: false, error: "--system-prompt and --system-prompt-file are mutually exclusive \u2014 pick one." };
152304
+ }
152305
+ if (f3.file !== void 0) {
152306
+ const r2 = readPromptFile(f3.file, f3.cwd, "--system-prompt-file");
152307
+ if (!r2.ok)
152308
+ return r2;
152309
+ if (!r2.value.trim())
152310
+ return { ok: false, error: `--system-prompt-file: ${f3.file} is empty.` };
152311
+ return { ok: true, value: r2.value };
152312
+ }
152313
+ if (f3.text !== void 0) {
152314
+ if (!f3.text.trim())
152315
+ return { ok: false, error: "--system-prompt: the value is empty." };
152316
+ return { ok: true, value: f3.text };
152317
+ }
152318
+ return { ok: true, value: void 0 };
152319
+ }
152320
+ function checkPartialMessages(f3) {
152321
+ if (!f3.includePartial)
152322
+ return null;
152323
+ if (f3.outputFormat !== "stream-json") {
152324
+ return "--include-partial-messages needs --output-format stream-json (it streams model deltas as they arrive).";
152325
+ }
152326
+ return null;
152327
+ }
152328
+ function checkReplayUserMessages(f3) {
152329
+ if (!f3.replay)
152330
+ return null;
152331
+ if (f3.inputFormat !== "stream-json") {
152332
+ return "--replay-user-messages needs --input-format stream-json (there is nothing to replay otherwise).";
152333
+ }
152334
+ if (f3.outputFormat !== "stream-json") {
152335
+ return "--replay-user-messages needs --output-format stream-json (the echo is written as an output event).";
152336
+ }
152337
+ return null;
152338
+ }
152339
+ function streamEventPayload(kind, chunk, extra = {}) {
152340
+ const delta = kind === "text" ? { type: "text_delta", text: chunk } : kind === "thinking" ? { type: "thinking_delta", thinking: chunk } : { type: "input_json_delta", partial_json: chunk };
152341
+ return { type: "stream_event", event: { type: "content_block_delta", delta }, ...extra };
152342
+ }
152343
+ function initEventPayload(i2) {
152344
+ return {
152345
+ type: "system",
152346
+ subtype: "init",
152347
+ session_id: i2.sessionId ?? "",
152348
+ cwd: i2.cwd,
152349
+ model: i2.model,
152350
+ tools: i2.tools,
152351
+ ...i2.mcpServers ? { mcp_servers: i2.mcpServers } : {},
152352
+ ...i2.permissionMode ? { permission_mode: i2.permissionMode } : {}
152353
+ };
152354
+ }
152355
+ function userReplayPayload(text) {
152356
+ return { type: "user", message: { role: "user", content: [{ type: "text", text }] } };
152357
+ }
152068
152358
 
152069
152359
  // src/commands/chat.ts
152070
152360
  var import_code_core17 = __toESM(require_dist3(), 1);
@@ -167955,6 +168245,10 @@ function refreshEnvForTurn(env4, workDir) {
167955
168245
  ...collectGitEnv(workDir)
167956
168246
  };
167957
168247
  }
168248
+ var noDiscovery = false;
168249
+ function setNoDiscovery(v) {
168250
+ noDiscovery = v;
168251
+ }
167958
168252
  var NEXRALL_MD_CANDIDATES = [
167959
168253
  "nexrall.md",
167960
168254
  "NEXRALL.md",
@@ -167969,6 +168263,8 @@ var tryRead = (p) => {
167969
168263
  }
167970
168264
  };
167971
168265
  function readNexrallMd(workDir) {
168266
+ if (noDiscovery)
168267
+ return void 0;
167972
168268
  const parts = [];
167973
168269
  const memContent = (0, import_code_core15.readAllMemory)(workDir);
167974
168270
  if (memContent)
@@ -168217,6 +168513,11 @@ var _mcpManager = null;
168217
168513
  var _mcpConfig = null;
168218
168514
  var sessionTouched = [];
168219
168515
  async function initMcp(workDir, override, strict) {
168516
+ if (noDiscovery && !override) {
168517
+ _mcpConfig = { mcpServers: {} };
168518
+ _mcpManager = null;
168519
+ return;
168520
+ }
168220
168521
  _mcpConfig = strict && override ? { mcpServers: {} } : import_code_core15.McpManager.loadConfig(workDir);
168221
168522
  if (override)
168222
168523
  Object.assign(_mcpConfig.mcpServers, override.mcpServers);
@@ -168588,6 +168889,7 @@ function printErrorReport(err, aborted) {
168588
168889
  for (const l2 of errorReportLines(err, aborted))
168589
168890
  console.error(l2);
168590
168891
  }
168892
+ var activeSessionIdGetter = () => void 0;
168591
168893
  async function runTurn(messages, modelAlias, workDir, abortSignal, env4, nexrallMd, mode, effort, checkpointManager, onProgress, takePendingInput, worktreeState, peerCtx) {
168592
168894
  let lastUsage;
168593
168895
  turnStats = freshTurnStats();
@@ -168622,6 +168924,7 @@ async function runTurn(messages, modelAlias, workDir, abortSignal, env4, nexrall
168622
168924
  spinner.start("thinking\u2026", { hint: false, verbs: true });
168623
168925
  const result = await (0, import_code_core15.runAgentLoop)(messages, {
168624
168926
  workDir,
168927
+ sessionId: activeSessionIdGetter(),
168625
168928
  model: modelAlias,
168626
168929
  env: refreshEnvForTurn(env4, workDir),
168627
168930
  nexrallMd,
@@ -168654,6 +168957,7 @@ async function runTurn(messages, modelAlias, workDir, abortSignal, env4, nexrall
168654
168957
  onInjectedInput: () => {
168655
168958
  },
168656
168959
  backgroundAgents: cliBackgroundAgents,
168960
+ disableHooks: noDiscovery,
168657
168961
  // The backend streams a cumulative output-token count (routes/code.js's
168658
168962
  // sendProgress, throttled to ≤5/s) covering thinking, visible text AND
168659
168963
  // tool-argument JSON. This used to be stored in a variable and rendered only
@@ -168947,13 +169251,27 @@ async function runTurnHeadless(messages, modelAlias, workDir, _abortSignal, env4
168947
169251
  let resultText = "";
168948
169252
  let toolCallCount = 0;
168949
169253
  let budgetHit = false;
169254
+ let turnsHit = false;
168950
169255
  const emit = (obj2) => {
168951
169256
  if (format === "stream-json")
168952
169257
  process.stdout.write(JSON.stringify(obj2) + "\n");
168953
169258
  };
169259
+ const verbose = (line) => {
169260
+ if (_headlessPolicy.verbose)
169261
+ process.stderr.write(line + "\n");
169262
+ };
169263
+ emit(initEventPayload({
169264
+ sessionId: activeSessionIdGetter(),
169265
+ cwd: workDir,
169266
+ model: modelAlias,
169267
+ tools: _headlessPolicy.onlyTools ? [..._headlessPolicy.onlyTools].sort() : (0, import_code_core15.knownToolNames)(),
169268
+ permissionMode: _headlessPolicy.permissionMode
169269
+ }));
168954
169270
  emit({ type: "start", model: modelAlias, workDir });
169271
+ verbose(`[verbose] model=${modelAlias} cwd=${workDir} mode=${mode} session=${activeSessionIdGetter() ?? "-"}`);
168955
169272
  const result = await (0, import_code_core15.runAgentLoop)(messages, {
168956
169273
  workDir,
169274
+ sessionId: activeSessionIdGetter(),
168957
169275
  model: modelAlias,
168958
169276
  env: refreshEnvForTurn(env4, workDir),
168959
169277
  nexrallMd,
@@ -168961,11 +169279,26 @@ async function runTurnHeadless(messages, modelAlias, workDir, _abortSignal, env4
168961
169279
  planMode: mode === "plan",
168962
169280
  worktree: worktreeState,
168963
169281
  backgroundAgents: cliBackgroundAgents,
169282
+ disableHooks: noDiscovery,
168964
169283
  effort,
168965
169284
  abortSignal: _abortSignal,
169285
+ // `--max-turns`: a hard round cap. autoContinue off, or the loop would extend it.
169286
+ ..._headlessPolicy.maxTurns !== void 0 ? { maxIterations: _headlessPolicy.maxTurns, autoContinue: false } : {},
169287
+ // `--tools`: the allowlist is the same one sub-agents use, so removed tools are not even offered.
169288
+ ..._headlessPolicy.onlyTools ? { _allowedTools: _headlessPolicy.onlyTools } : {},
169289
+ // Setting this callback makes the loop stop printing its own stop notice, so re-emit it —
169290
+ // otherwise every stop reason (stalled, no balance…) would go silent in headless runs.
169291
+ _onStopReason: (reason, notice) => {
169292
+ if (reason === "budget" && _headlessPolicy.maxTurns !== void 0)
169293
+ turnsHit = true;
169294
+ if (notice)
169295
+ emit({ type: "notice", text: notice });
169296
+ },
168966
169297
  onText: (text) => {
168967
169298
  resultText += text;
168968
169299
  emit({ type: "text", text });
169300
+ if (_headlessPolicy.includePartial)
169301
+ emit(streamEventPayload("text", text));
168969
169302
  },
168970
169303
  // ── Reasoning-phase liveness ──────────────────────────────────────
168971
169304
  // These used to be UNWIRED here while runTurn() (interactive) wired all
@@ -168996,6 +169329,8 @@ async function runTurnHeadless(messages, modelAlias, workDir, _abortSignal, env4
168996
169329
  // dropped on the floor for every headless run.
168997
169330
  onThinking: (text) => {
168998
169331
  emit({ type: "thinking", text });
169332
+ if (_headlessPolicy.includePartial)
169333
+ emit(streamEventPayload("thinking", text));
168999
169334
  },
169000
169335
  // onThinkingDelta is intentionally NOT wired. Its chunks concatenate to the
169001
169336
  // same string `onThinking` emits in full above, so emitting both would
@@ -169012,6 +169347,7 @@ async function runTurnHeadless(messages, modelAlias, workDir, _abortSignal, env4
169012
169347
  onToolUse: (name, input, isSubTask, meta) => {
169013
169348
  debugLog("tool_use", { tool: name, sub: !!isSubTask });
169014
169349
  emit({ type: "tool_use", tool: name, input, ...toolIds(isSubTask, meta) });
169350
+ verbose(`[verbose] -> ${name}${isSubTask ? " (sub-agent)" : ""} ${toolTarget(name, input)}`);
169015
169351
  },
169016
169352
  // Live bash progress for scripted/CI consumers — lets a wrapper tail a
169017
169353
  // long-running build/test command instead of blocking silently until
@@ -169019,12 +169355,15 @@ async function runTurnHeadless(messages, modelAlias, workDir, _abortSignal, env4
169019
169355
  // which is head/tail-capped), so a chatty command can emit many of these.
169020
169356
  onToolStreamChunk: (name, chunk, isSubTask, meta) => {
169021
169357
  emit({ type: "tool_stream", tool: name, chunk, ...toolIds(isSubTask, meta) });
169358
+ if (_headlessPolicy.includePartial)
169359
+ emit(streamEventPayload("tool", chunk, { tool: name, ...toolIds(isSubTask, meta) }));
169022
169360
  },
169023
169361
  onToolResult: (name, res, isSubTask, meta) => {
169024
169362
  if (!isSubTask)
169025
169363
  toolCallCount++;
169026
169364
  debugLog("tool_result", { tool: name, ok: res.error === void 0, error: res.error, sub: !!isSubTask });
169027
169365
  emit({ type: "tool_result", tool: name, ok: res.error === void 0, ...res.error ? { error: res.error } : {}, ...toolIds(isSubTask, meta) });
169366
+ verbose(`[verbose] <- ${name} ${res.error === void 0 ? "ok" : "FAILED: " + String(res.error).slice(0, 200)}`);
169028
169367
  },
169029
169368
  // Headless consumers get the partial report too — tagged, so a script can account
169030
169369
  // for the real cost of a restarted turn — but it never becomes `lastUsage`, which
@@ -169065,6 +169404,10 @@ async function runTurnHeadless(messages, modelAlias, workDir, _abortSignal, env4
169065
169404
  // no human to confirm, they are denied unless NEXRALL_ALLOW_DESTRUCTIVE=1 is
169066
169405
  // explicitly set for this CI run.
169067
169406
  requestPermission: async ({ tool, input }) => {
169407
+ if (_headlessPolicy.onlyTools && !_headlessPolicy.onlyTools.has(tool) && !tool.startsWith("mcp__")) {
169408
+ emit({ type: "permission_denied", tool, reason: "tools" });
169409
+ return false;
169410
+ }
169068
169411
  const d = (0, import_code_core15.isDestructiveBash)(tool, input);
169069
169412
  const v = headlessVerdict({
169070
169413
  rule: (0, import_code_core17.evaluatePermission)((0, import_code_core17.loadSettings)(workDir).permissions, tool, input, workDir),
@@ -169104,6 +169447,7 @@ async function runTurnHeadless(messages, modelAlias, workDir, _abortSignal, env4
169104
169447
  }
169105
169448
  const extra = {
169106
169449
  ..._headlessPolicy.budgetUsd !== void 0 ? { cost_usd: _headlessPolicy.spent, ...budgetHit ? { stopped: "budget" } : {} } : {},
169450
+ ...turnsHit ? { stopped: "max_turns", max_turns: _headlessPolicy.maxTurns } : {},
169107
169451
  ...structured ? { structured_output: structured.value } : {},
169108
169452
  ...schemaError ? { schema_error: schemaError } : {}
169109
169453
  };
@@ -169126,6 +169470,8 @@ async function runTurnHeadless(messages, modelAlias, workDir, _abortSignal, env4
169126
169470
  }
169127
169471
  if (budgetHit)
169128
169472
  process.exitCode = 2;
169473
+ else if (turnsHit)
169474
+ process.exitCode = 4;
169129
169475
  return { messages: result, usage: lastUsage };
169130
169476
  }
169131
169477
  function kbKey(action) {
@@ -169306,6 +169652,8 @@ function printSessions() {
169306
169652
  console.log();
169307
169653
  }
169308
169654
  async function startChatSession(options) {
169655
+ if (options.bare)
169656
+ setNoDiscovery(true);
169309
169657
  (0, import_code_core15.enableAgentTranscriptPersistence)();
169310
169658
  const headless = options.outputFormat === "json" || options.outputFormat === "stream-json";
169311
169659
  const requestedWorkDir = options.workDir;
@@ -169401,8 +169749,12 @@ Resume it with: nex --worktree ${worktreeState.name}
169401
169749
  });
169402
169750
  }
169403
169751
  const env4 = collectEnv(workDir);
169404
- const nexrallMd = [readNexrallMd(workDir), options.appendSystemPrompt?.trim(), addDirNote(options.addDirs ?? []), options.jsonSchema ? schemaInstruction(options.jsonSchema) : ""].filter(Boolean).join("\n\n") || void 0;
169405
- _headlessPolicy = { strict: !!options.strictPermissions, budgetUsd: options.budgetUsd, spent: 0, schema: options.jsonSchema };
169752
+ const briefBase = options.systemPrompt?.trim() ?? readNexrallMd(workDir);
169753
+ const nexrallMd = [briefBase, options.appendSystemPrompt?.trim(), addDirNote(options.addDirs ?? []), options.jsonSchema ? schemaInstruction(options.jsonSchema) : ""].filter(Boolean).join("\n\n") || void 0;
169754
+ _headlessPolicy = { strict: !!options.strictPermissions, budgetUsd: options.budgetUsd, spent: 0, schema: options.jsonSchema, maxTurns: options.maxTurns, onlyTools: options.onlyTools ? new Set(options.onlyTools) : void 0 };
169755
+ _headlessPolicy.verbose = !!options.verbose;
169756
+ _headlessPolicy.includePartial = !!options.includePartialMessages;
169757
+ _headlessPolicy.permissionMode = String(options.initialMode ?? "");
169406
169758
  const saveSession2 = options.noSessionPersistence ? () => {
169407
169759
  } : saveSession;
169408
169760
  const interactive = !headless && !options.prompt && !options.stdinText;
@@ -169478,8 +169830,9 @@ Resume it with: nex --worktree ${worktreeState.name}
169478
169830
  ));
169479
169831
  }
169480
169832
  let messages = [];
169481
- let sessionId = crypto6.randomBytes(8).toString("hex");
169482
- let sessionTitle = "";
169833
+ let sessionId = options.newSessionId ?? crypto6.randomBytes(8).toString("hex");
169834
+ activeSessionIdGetter = () => sessionId;
169835
+ let sessionTitle = options.sessionName ?? "";
169483
169836
  const modeLocked = options.lockMode === true;
169484
169837
  let agentMode = modeLocked ? "plan" : options.initialMode ?? "auto";
169485
169838
  const refuseIfLocked = (what) => {
@@ -169501,7 +169854,7 @@ Resume it with: nex --worktree ${worktreeState.name}
169501
169854
  for (const n of themeConfig.notes)
169502
169855
  console.log(paint.dim(" theme: " + n));
169503
169856
  }
169504
- let skills = (0, import_code_core15.loadSkills)(workDir);
169857
+ let skills = noDiscovery ? [] : (0, import_code_core15.loadSkills)(workDir);
169505
169858
  if (skills.length && !headless)
169506
169859
  console.log(paint.dim(` ${skills.length} skill(s) loaded`));
169507
169860
  let contextSet = loadContextSet(workDir);
@@ -169531,7 +169884,7 @@ Resume it with: nex --worktree ${worktreeState.name}
169531
169884
  process.exit(1);
169532
169885
  }
169533
169886
  if (options.debug) {
169534
- const dbgPath = enableDebug();
169887
+ const dbgPath = enableDebug(options.debugFile);
169535
169888
  if (dbgPath) {
169536
169889
  debugLog("start", { version: CLI_VERSION, node: process.version, platform: process.platform, model: modelAlias, mode: agentMode, locked: modeLocked, headless, interactive });
169537
169890
  if (!headless)
@@ -169544,7 +169897,7 @@ Resume it with: nex --worktree ${worktreeState.name}
169544
169897
  }
169545
169898
  }
169546
169899
  if (options.resume || options.continueLast) {
169547
- const stored = options.continueLast ? lastSessionForDir(workDir) : options.resume === "last" ? lastSession() : loadSession(options.resume);
169900
+ const stored = options.continueLast ? lastSessionForDir(workDir) : options.resume === "last" ? lastSession() : loadSession(options.resume) ?? matchSessionByName(listSessions(), options.resume);
169548
169901
  if (!stored) {
169549
169902
  if (options.continueLast) {
169550
169903
  console.error(paint.red(" No conversation to continue in this folder."));
@@ -169558,7 +169911,13 @@ Resume it with: nex --worktree ${worktreeState.name}
169558
169911
  messages = stored.messages;
169559
169912
  sessionId = stored.id;
169560
169913
  sessionTitle = stored.title;
169561
- startSessionLog(sessionId, true, { workDir });
169914
+ if (options.forkSession) {
169915
+ sessionId = options.newSessionId ?? crypto6.randomBytes(8).toString("hex");
169916
+ sessionTitle = options.sessionName ?? forkTitle(stored.title, "");
169917
+ } else if (options.sessionName) {
169918
+ sessionTitle = options.sessionName;
169919
+ }
169920
+ startSessionLog(sessionId, true, options.forkSession ? { workDir, forkOf: stored.id } : { workDir });
169562
169921
  try {
169563
169922
  const compacted = await (0, import_code_core15.compactMessagesForResume)(messages, {
169564
169923
  workDir,
@@ -169632,6 +169991,9 @@ Resume it with: nex --worktree ${worktreeState.name}
169632
169991
  ${text}` : "");
169633
169992
  }
169634
169993
  checkpoints.beginTurn(text, messages.length);
169994
+ if (options.replayUserMessages && options.inputFormatWasStreamJson && options.outputFormat === "stream-json") {
169995
+ process.stdout.write(JSON.stringify(userReplayPayload(text)) + "\n");
169996
+ }
169635
169997
  messages.push({ role: "user", content: [{ type: "text", text }] });
169636
169998
  const abortSignal2 = { aborted: false };
169637
169999
  const onSigint = () => {
@@ -169640,8 +170002,31 @@ ${text}` : "");
169640
170002
  process.once("SIGINT", onSigint);
169641
170003
  try {
169642
170004
  const fmt = options.outputFormat;
170005
+ const replay = (text2) => {
170006
+ if (options.replayUserMessages && fmt === "stream-json")
170007
+ process.stdout.write(JSON.stringify(userReplayPayload(text2)) + "\n");
170008
+ };
169643
170009
  const runOnce = () => fmt === "json" || fmt === "stream-json" ? runTurnHeadless(messages, modelAlias, workDir, abortSignal2, env4, nexrallMd, agentMode, effortLevel, fmt, checkpoints, saveProgress, worktreeState) : runTurn(messages, modelAlias, workDir, abortSignal2, env4, nexrallMd, agentMode, effortLevel, checkpoints, saveProgress, void 0, worktreeState, peerCtx);
169644
- let result = await runOnce();
170010
+ const runWithFallback = async () => {
170011
+ try {
170012
+ return await runOnce();
170013
+ } catch (e2) {
170014
+ if (!options.fallbackModel || modelAlias === options.fallbackModel || abortSignal2.aborted || !isFallbackWorthy(String(e2.message)))
170015
+ throw e2;
170016
+ const from = modelAlias;
170017
+ modelAlias = options.fallbackModel;
170018
+ const salvagedMsgs = (0, import_code_core15.salvageHistory)(e2);
170019
+ if (salvagedMsgs)
170020
+ messages = salvagedMsgs;
170021
+ const note = `Model ${from} failed (${String(e2.message).slice(0, 120)}); retrying on ${modelAlias}`;
170022
+ if (fmt === "stream-json")
170023
+ process.stdout.write(JSON.stringify({ type: "notice", text: note }) + "\n");
170024
+ else
170025
+ console.error(note);
170026
+ return await runOnce();
170027
+ }
170028
+ };
170029
+ let result = await runWithFallback();
169645
170030
  messages = result.messages;
169646
170031
  while (!abortSignal2.aborted && (cliBackgroundAgents.runningCount() > 0 || cliBackgroundAgents.hasPending())) {
169647
170032
  if (!cliBackgroundAgents.hasPending()) {
@@ -169658,6 +170043,7 @@ ${text}` : "");
169658
170043
  break;
169659
170044
  commitCheckpoint();
169660
170045
  checkpoints.beginTurn(follow, messages.length);
170046
+ replay(follow);
169661
170047
  messages.push({ role: "user", content: [{ type: "text", text: follow }] });
169662
170048
  result = await runOnce();
169663
170049
  messages = result.messages;
@@ -170565,6 +170951,7 @@ ${summaryText}` }] },
170565
170951
  saveSession2(sessionId, sessionTitle || "Untitled", workDir, messages);
170566
170952
  console.log(paint.green(` Compacted ${toSummarize.length} messages \u2192 ${messages.length} messages.`));
170567
170953
  emitEvent("context.compacted", { where: "manual", before: toSummarize.length + KEEP, after: messages.length });
170954
+ await (0, import_code_core16.fireManualPostCompactHook)(workDir, sessionId, toSummarize.length);
170568
170955
  if (pin.attachments.length)
170569
170956
  console.log(paint.dim(` ${pin.attachments.length} pinned file(s) re-attached.`));
170570
170957
  for (const n of pin.notes)
@@ -172905,7 +173292,17 @@ var require3 = createRequire2(import.meta.url);
172905
173292
  var NEX_VERSION = require3("../package.json").version;
172906
173293
  var program2 = new Command();
172907
173294
  program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version(NEX_VERSION).enablePositionalOptions();
172908
- program2.command("auth").description("Login to your Nexrall account").action(authCommand);
173295
+ var auth = program2.command("auth").description("Login to your Nexrall account").action(authCommand);
173296
+ auth.command("status").description("Show whether you are logged in (exit 0 = yes, 1 = no). --json for scripts; the token is never printed").option("--json", "Machine-readable output").action((opts) => {
173297
+ const r2 = authStatusReport((0, import_code_core21.loadAuth)(), process.env);
173298
+ if (opts.json)
173299
+ console.log(JSON.stringify(r2));
173300
+ else if (!r2.loggedIn)
173301
+ console.log("Not logged in. Run: nex auth");
173302
+ else
173303
+ console.log(`Logged in${r2.email ? ` as ${r2.email}` : ""} (${r2.source === "env" ? "NEXRALL_TOKEN" : "saved login"})`);
173304
+ process.exit(r2.loggedIn ? 0 : 1);
173305
+ });
172909
173306
  program2.command("logout").description("Log out of your Nexrall account").action(logoutCommand);
172910
173307
  program2.command("doctor").description("Check Node, sign-in, network, terminal, MCP and storage, with a fix for each problem").option("-d, --dir <path>", "Project folder to check (default: current directory)").action(async (opts) => {
172911
173308
  const workDir = opts.dir ? path26.resolve(opts.dir) : process.cwd();
@@ -173104,7 +173501,7 @@ program2.command("sessions").description("List, export, import, or delete saved
173104
173501
  console.log(paint.dim(" nex sessions --events <id> [--json] to follow its event stream"));
173105
173502
  console.log();
173106
173503
  });
173107
- program2.option("-p, --pro", "Use Claude Opus 5.5 (most capable)").option("-t, --turbo", "Use Claude Sonnet 5.5 (fast)").option("-u, --ultra", "Use Claude Fable 5.1 (most powerful)").option("-y, --yolo", "Auto-approve all tool calls (no permission prompts)").option("-d, --dir <path>", "Set working directory (default: current directory)").option("-m, --model <name>", "Explicit model, e.g. claude-opus-5-5 | gpt-5.4 | gpt-4.1").option("-r, --resume [id]", "Resume last session, or a specific session id").option("-c, --continue", "Continue the latest conversation in this folder").option("--plan", "Start in plan mode (read-only; /mode or Shift+Tab can leave it)").option("--readonly", "Read-only for the whole session: plan mode that cannot be switched off").option("--auto", "Start in auto mode (the default \u2014 edits and commands run without asking)").option("--mode <mode>", "Starting permission mode: ask | edit | auto | plan").option("--fullscreen", "Draw the whole session on the terminal's alternate screen: the transcript re-wraps on resize and PgUp/PgDn page through it (no terminal scrollback while it is on; /fullscreen toggles it)").option("--bg", "Run the prompt in the background (headless); returns a job id. See: nex jobs | logs | attach | kill").option("--debug", "Write request timing, retries and errors to a debug log file").option("--no-color", "Disable colored output (also honours NO_COLOR)").option("-w, --worktree [name]", "Isolate this session in its own worktree \u2014 a separate directory (and, in a git repo, its own branch) so it can never overwrite files another session (or you) are touching in the main checkout. Omit a name for an auto-generated one; pass an existing name to resume that same worktree.").option("-e, --effort <level>", "Thinking effort \u2014 real levels depend on the model (e.g. DeepSeek: low|high|max, Qwen: low|high); run `nex --model <id>` then `/effort` with no argument to see a model's scale").option("--no-banner", "Skip the ASCII banner (useful in scripts/pipes)").option("--output-format <fmt>", "One-shot output format: text | json | stream-json (implies auto-approve)").option("--audit [path]", "Log every tool call (and refusal) to a JSONL trail (default: .nexrall/audit/<session>.jsonl)").option("--permission-mode <mode>", "Permission mode: ask | edit | auto | plan (also Claude Code names: default | acceptEdits | bypassPermissions). With --output-format, anything that would prompt is DENIED instead of auto-approved").option("--allowedTools, --allowed-tools <tools...>", 'Tools/rules to allow without asking, e.g. "Bash(git *)" Edit (comma or space separated; put the prompt BEFORE this flag)').option("--disallowedTools, --disallowed-tools <tools...>", 'Tools/rules to deny, e.g. "Bash(rm *)" (deny always wins)').option("--append-system-prompt <text>", "Extra instructions appended to the project brief for this run").option("--max-budget-usd <amount>", "Stop a one-shot run once it has spent this many dollars (needs --output-format)").option("--mcp-config <configs...>", 'Load MCP servers from JSON files or inline JSON ({"mcpServers":{\u2026}}) for this run').option("--strict-mcp-config", "With --mcp-config: use ONLY those servers, ignoring ~/.nexrall/mcp.json and the project one").option("--add-dir <dirs...>", "Extra directories the agent may read and edit besides the project folder").option("--input-format <format>", "text (default) | stream-json \u2014 read the prompt as Claude-Code-style NDJSON user messages on stdin").option("--json-schema <schema>", 'JSON Schema (inline or file) the final answer must satisfy; needs --output-format. Result gains "structured_output"; exit 3 if it does not validate').option("--no-session-persistence", "Do not save this run to disk (needs --output-format; it cannot be resumed)").argument("[prompt...]", "One-shot prompt \u2014 if omitted, starts interactive mode").action(async (promptParts, options) => {
173504
+ program2.option("-p, --pro", "Use Claude Opus 5.5 (most capable)").option("-t, --turbo", "Use Claude Sonnet 5.5 (fast)").option("-u, --ultra", "Use Claude Fable 5.1 (most powerful)").option("-y, --yolo", "Auto-approve all tool calls (no permission prompts)").option("-d, --dir <path>", "Set working directory (default: current directory)").option("-m, --model <name>", "Explicit model, e.g. claude-opus-5-5 | gpt-5.4 | gpt-4.1").option("-r, --resume [id]", "Resume last session, or a specific session id").option("-c, --continue", "Continue the latest conversation in this folder").option("--plan", "Start in plan mode (read-only; /mode or Shift+Tab can leave it)").option("--readonly", "Read-only for the whole session: plan mode that cannot be switched off").option("--auto", "Start in auto mode (the default \u2014 edits and commands run without asking)").option("--mode <mode>", "Starting permission mode: ask | edit | auto | plan").option("--fullscreen", "Draw the whole session on the terminal's alternate screen: the transcript re-wraps on resize and PgUp/PgDn page through it (no terminal scrollback while it is on; /fullscreen toggles it)").option("--bg", "Run the prompt in the background (headless); returns a job id. See: nex jobs | logs | attach | kill").option("--debug", "Write request timing, retries and errors to a debug log file").option("--no-color", "Disable colored output (also honours NO_COLOR)").option("-w, --worktree [name]", "Isolate this session in its own worktree \u2014 a separate directory (and, in a git repo, its own branch) so it can never overwrite files another session (or you) are touching in the main checkout. Omit a name for an auto-generated one; pass an existing name to resume that same worktree.").option("-e, --effort <level>", "Thinking effort \u2014 real levels depend on the model (e.g. DeepSeek: low|high|max, Qwen: low|high); run `nex --model <id>` then `/effort` with no argument to see a model's scale").option("--no-banner", "Skip the ASCII banner (useful in scripts/pipes)").option("--output-format <fmt>", "One-shot output format: text | json | stream-json (implies auto-approve)").option("--audit [path]", "Log every tool call (and refusal) to a JSONL trail (default: .nexrall/audit/<session>.jsonl)").option("--permission-mode <mode>", "Permission mode: ask | edit | auto | plan (also Claude Code names: default | acceptEdits | bypassPermissions). With --output-format, anything that would prompt is DENIED instead of auto-approved").option("--allowedTools, --allowed-tools <tools...>", 'Tools/rules to allow without asking, e.g. "Bash(git *)" Edit (comma or space separated; put the prompt BEFORE this flag)').option("--disallowedTools, --disallowed-tools <tools...>", 'Tools/rules to deny, e.g. "Bash(rm *)" (deny always wins)').option("--append-system-prompt <text>", "Extra instructions appended to the project brief for this run").option("--max-budget-usd <amount>", "Stop a one-shot run once it has spent this many dollars (needs --output-format)").option("--mcp-config <configs...>", 'Load MCP servers from JSON files or inline JSON ({"mcpServers":{\u2026}}) for this run').option("--strict-mcp-config", "With --mcp-config: use ONLY those servers, ignoring ~/.nexrall/mcp.json and the project one").option("--add-dir <dirs...>", "Extra directories the agent may read and edit besides the project folder").option("--input-format <format>", "text (default) | stream-json \u2014 read the prompt as Claude-Code-style NDJSON user messages on stdin").option("--json-schema <schema>", 'JSON Schema (inline or file) the final answer must satisfy; needs --output-format. Result gains "structured_output"; exit 3 if it does not validate').option("--no-session-persistence", "Do not save this run to disk (needs --output-format; it cannot be resumed)").option("--max-turns <n>", "Stop a headless run after N agent rounds (needs --output-format; exit 4 when it hits the limit)").option("--append-system-prompt-file <path>", "Like --append-system-prompt, read from a file").option("--system-prompt <text>", "REPLACE the project brief (nexrall.md + memory + global instructions) with this text for this run").option("--system-prompt-file <path>", "Like --system-prompt, read from a file").option("--verbose", "Print progress detail (tool calls, timings, usage) to stderr while a one-shot run works").option("--include-partial-messages", "Stream model deltas as they arrive (needs --output-format stream-json)").option("--replay-user-messages", "Echo each stdin message back on stdout as it is consumed (needs --input-format stream-json --output-format stream-json)").option("--bare", "Start without discovery: no nexrall.md/memory, skills, plugins, hooks or MCP config files").option("--safe-mode", "Same as --bare (Claude Code's troubleshooting name)").option("--tools <list>", 'Exactly these built-in tools ("Bash,Read,Edit", "default", or "" for none); the rest are removed, not just unapproved').option("--session-id <id>", "Use this id for a NEW conversation (letters, digits, - _), so a script can find it again").option("-n, --name <name>", "Name the conversation; `nex -r <name>` resumes it by that name").option("--fork-session", "With --resume/--continue: branch into a new session instead of continuing the original").option("--fallback-model <name>", "If the main model is overloaded or unreachable, retry the turn once on this model (needs --output-format)").option("--settings <file|json>", "Settings (permissions, hooks\u2026) for this run only: a JSON file or an inline JSON object; merged over the user/project settings").option("--debug-file <path>", "Write the --debug diagnostics log to this file (implies --debug)").argument("[prompt...]", "One-shot prompt \u2014 if omitted, starts interactive mode").action(async (promptParts, options) => {
173108
173505
  if (!(0, import_code_core20.isAuthenticated)()) {
173109
173506
  console.error("Not logged in. Run: nex auth");
173110
173507
  process.exit(1);
@@ -173136,8 +173533,43 @@ program2.option("-p, --pro", "Use Claude Opus 5.5 (most capable)").option("-t, -
173136
173533
  console.error(sessErr);
173137
173534
  process.exit(1);
173138
173535
  }
173536
+ let newSessionId;
173537
+ let sessionName;
173538
+ if (options.sessionId !== void 0) {
173539
+ const r2 = parseSessionId(options.sessionId);
173540
+ if (!r2.ok) {
173541
+ console.error(r2.error);
173542
+ process.exit(1);
173543
+ }
173544
+ newSessionId = r2.value;
173545
+ }
173546
+ if (options.name !== void 0) {
173547
+ const r2 = parseSessionName(options.name);
173548
+ if (!r2.ok) {
173549
+ console.error(r2.error);
173550
+ process.exit(1);
173551
+ }
173552
+ sessionName = r2.value;
173553
+ }
173554
+ const identErr = checkSessionIdentityFlags({ sessionId: newSessionId, resume: options.resume, continue: options.continue, fork: options.forkSession });
173555
+ if (identErr) {
173556
+ console.error(identErr);
173557
+ process.exit(1);
173558
+ }
173559
+ if (newSessionId && loadSession(newSessionId) && !options.forkSession) {
173560
+ console.error(`--session-id "${newSessionId}" already exists. Resume it with: nex --resume ${newSessionId}`);
173561
+ process.exit(1);
173562
+ }
173139
173563
  if (options.yolo || permBypass)
173140
173564
  setAutoApprove("all");
173565
+ if (options.settings !== void 0) {
173566
+ const st = parseSettingsArg(options.settings, process.cwd());
173567
+ if (!st.ok) {
173568
+ console.error(st.error);
173569
+ process.exit(1);
173570
+ }
173571
+ (0, import_code_core21.setSessionSettings)(st.value);
173572
+ }
173141
173573
  const allowRules = parseToolList(options.allowedTools);
173142
173574
  const denyRules = parseToolList(options.disallowedTools);
173143
173575
  (0, import_code_core21.setSessionRules)({ allow: allowRules, deny: denyRules });
@@ -173152,11 +173584,57 @@ program2.option("-p, --pro", "Use Claude Opus 5.5 (most capable)").option("-t, -
173152
173584
  }
173153
173585
  if (options.sessionPersistence === false)
173154
173586
  process.env.NEXRALL_EVENTS = "off";
173155
- const headlessErr = checkHeadlessOnly({ outputFormat: options.outputFormat, budget: budgetUsd, noSessionPersistence: options.sessionPersistence === false });
173587
+ let maxTurns;
173588
+ if (options.maxTurns !== void 0) {
173589
+ const t2 = parseMaxTurns(options.maxTurns);
173590
+ if (!t2.ok) {
173591
+ console.error(t2.error);
173592
+ process.exit(1);
173593
+ }
173594
+ maxTurns = t2.value;
173595
+ }
173596
+ const headlessErr = checkHeadlessOnly({ outputFormat: options.outputFormat, budget: budgetUsd, noSessionPersistence: options.sessionPersistence === false, maxTurns });
173156
173597
  if (headlessErr) {
173157
173598
  console.error(headlessErr);
173158
173599
  process.exit(1);
173159
173600
  }
173601
+ const sysPrompt = resolveSystemPrompt({ text: typeof options.systemPrompt === "string" ? options.systemPrompt : void 0, file: options.systemPromptFile, cwd: process.cwd() });
173602
+ if (!sysPrompt.ok) {
173603
+ console.error(sysPrompt.error);
173604
+ process.exit(1);
173605
+ }
173606
+ let appendSystemPrompt = options.appendSystemPrompt;
173607
+ if (options.appendSystemPromptFile !== void 0) {
173608
+ const f3 = readPromptFile(options.appendSystemPromptFile, process.cwd());
173609
+ if (!f3.ok) {
173610
+ console.error(f3.error);
173611
+ process.exit(1);
173612
+ }
173613
+ appendSystemPrompt = [appendSystemPrompt, f3.value].filter(Boolean).join("\n\n");
173614
+ }
173615
+ let onlyTools;
173616
+ if (options.tools !== void 0) {
173617
+ const t2 = parseToolsFlag(options.tools === true ? "" : options.tools, (0, import_code_core21.knownToolNames)());
173618
+ if (!t2.ok) {
173619
+ console.error(t2.error);
173620
+ process.exit(1);
173621
+ }
173622
+ onlyTools = t2.value;
173623
+ }
173624
+ let fallbackModel;
173625
+ if (options.fallbackModel !== void 0) {
173626
+ if (!options.outputFormat) {
173627
+ console.error("--fallback-model only works with --output-format (a one-shot, non-interactive run).");
173628
+ process.exit(1);
173629
+ }
173630
+ const f3 = parseFallbackModel(options.fallbackModel, model);
173631
+ if (!f3.ok) {
173632
+ console.error(f3.error);
173633
+ process.exit(1);
173634
+ }
173635
+ fallbackModel = f3.value;
173636
+ }
173637
+ const debugFile = options.debugFile !== void 0 ? path26.resolve(String(options.debugFile)) : void 0;
173160
173638
  let mcpOverride;
173161
173639
  if (options.mcpConfig) {
173162
173640
  const m2 = parseMcpConfigs(options.mcpConfig, process.cwd());
@@ -173169,24 +173647,6 @@ program2.option("-p, --pro", "Use Claude Opus 5.5 (most capable)").option("-t, -
173169
173647
  console.error("--strict-mcp-config needs --mcp-config.");
173170
173648
  process.exit(1);
173171
173649
  }
173172
- if (options.bg) {
173173
- if (!promptParts.length) {
173174
- console.error('--bg needs a prompt: nex --bg "your task"');
173175
- process.exit(1);
173176
- }
173177
- if (process.env.NEXRALL_BG_JOB) {
173178
- console.error("--bg cannot be nested inside a background job.");
173179
- process.exit(1);
173180
- }
173181
- const args = stripBgFlag(process.argv.slice(2));
173182
- if (!options.outputFormat)
173183
- args.push("--output-format", "stream-json");
173184
- const job = startJob({ script: process.argv[1], args, cwd: process.cwd(), prompt: promptParts.join(" ") });
173185
- console.log(`Started background job ${job.id}`);
173186
- console.log(` nex logs ${job.id} -f follow its output`);
173187
- console.log(` nex jobs list jobs \xB7 nex kill ${job.id} stop it`);
173188
- process.exit(0);
173189
- }
173190
173650
  const addDirs = parseAddDirs(options.addDir, process.cwd());
173191
173651
  if (!addDirs.ok) {
173192
173652
  console.error(addDirs.error);
@@ -173211,6 +173671,28 @@ program2.option("-p, --pro", "Use Claude Opus 5.5 (most capable)").option("-t, -
173211
173671
  console.error(`Invalid --input-format "${options.inputFormat}". Use: text | stream-json`);
173212
173672
  process.exit(1);
173213
173673
  }
173674
+ if (options.bg) {
173675
+ if (!promptParts.length) {
173676
+ console.error('--bg needs a prompt: nex --bg "your task"');
173677
+ process.exit(1);
173678
+ }
173679
+ if (inFmt === "stream-json") {
173680
+ console.error("--bg cannot read stream-json from stdin (a background job has no stdin). Pass the prompt as an argument.");
173681
+ process.exit(1);
173682
+ }
173683
+ if (process.env.NEXRALL_BG_JOB) {
173684
+ console.error("--bg cannot be nested inside a background job.");
173685
+ process.exit(1);
173686
+ }
173687
+ const args = stripBgFlag(process.argv.slice(2));
173688
+ if (!options.outputFormat)
173689
+ args.push("--output-format", "stream-json");
173690
+ const job = startJob({ script: process.argv[1], args, cwd: process.cwd(), prompt: promptParts.join(" ") });
173691
+ console.log(`Started background job ${job.id}`);
173692
+ console.log(` nex logs ${job.id} -f follow its output`);
173693
+ console.log(` nex jobs list jobs \xB7 nex kill ${job.id} stop it`);
173694
+ process.exit(0);
173695
+ }
173214
173696
  const resume = options.resume === true ? "last" : typeof options.resume === "string" ? options.resume : void 0;
173215
173697
  const worktree = options.worktree === true ? true : typeof options.worktree === "string" ? options.worktree : void 0;
173216
173698
  let stdinText;
@@ -173245,6 +173727,16 @@ program2.option("-p, --pro", "Use Claude Opus 5.5 (most capable)").option("-t, -
173245
173727
  }
173246
173728
  outputFormat = fmt;
173247
173729
  }
173730
+ const partialErr = checkPartialMessages({ includePartial: !!options.includePartialMessages, outputFormat });
173731
+ if (partialErr) {
173732
+ console.error(partialErr);
173733
+ process.exit(1);
173734
+ }
173735
+ const replayErr = checkReplayUserMessages({ replay: !!options.replayUserMessages, inputFormat: options.inputFormat, outputFormat });
173736
+ if (replayErr) {
173737
+ console.error(replayErr);
173738
+ process.exit(1);
173739
+ }
173248
173740
  const fsErr = checkFullscreenFlag({
173249
173741
  fullscreen: !!options.fullscreen,
173250
173742
  isTTY: !!process.stdout.isTTY,
@@ -173264,7 +173756,7 @@ program2.option("-p, --pro", "Use Claude Opus 5.5 (most capable)").option("-t, -
173264
173756
  }
173265
173757
  effort = lvl;
173266
173758
  }
173267
- await startChatSession({ model, workDir, prompt: prompt2, resume, continueLast: !!options.continue, initialMode: modeRes.mode, lockMode: modeRes.locked, debug: !!options.debug, stdinText, stdinFollowUps, showBanner, outputFormat, effort, audit: options.audit, worktree, fullscreen: !!options.fullscreen, strictPermissions: options.permissionMode !== void 0, budgetUsd, appendSystemPrompt: options.appendSystemPrompt, mcpOverride, strictMcp: !!options.strictMcpConfig, noSessionPersistence: options.sessionPersistence === false, addDirs: addDirs.value, jsonSchema });
173759
+ await startChatSession({ model, workDir, prompt: prompt2, resume, continueLast: !!options.continue, initialMode: modeRes.mode, lockMode: modeRes.locked, debug: !!options.debug || debugFile !== void 0, debugFile, fallbackModel, maxTurns, onlyTools, newSessionId, sessionName, forkSession: !!options.forkSession, stdinText, stdinFollowUps, showBanner, outputFormat, effort, audit: options.audit, worktree, fullscreen: !!options.fullscreen, strictPermissions: options.permissionMode !== void 0, budgetUsd, appendSystemPrompt, mcpOverride, strictMcp: !!options.strictMcpConfig, systemPrompt: sysPrompt.value, verbose: !!options.verbose, includePartialMessages: !!options.includePartialMessages, replayUserMessages: !!options.replayUserMessages, inputFormatWasStreamJson: options.inputFormat !== void 0 && String(options.inputFormat).toLowerCase() === "stream-json", bare: !!(options.bare || options.safeMode), noSessionPersistence: options.sessionPersistence === false, addDirs: addDirs.value, jsonSchema });
173268
173760
  });
173269
173761
  program2.parse();
173270
173762
  /*! Bundled license information:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexrall-code",
3
- "version": "0.5.160",
3
+ "version": "0.5.162",
4
4
  "description": "Nexrall Code — AI coding assistant for your terminal (headless agent for scripts, CI and automation)",
5
5
  "keywords": [
6
6
  "ai",