norn-cli 4.1.0 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +15 -7
  2. package/dist/cli.js +307 -15
  3. package/package.json +10 -8
package/README.md CHANGED
@@ -74,23 +74,31 @@ npm install -g norn-cli
74
74
  norn ./tests/smoke.norn -e dev
75
75
  ```
76
76
 
77
+ Update an existing global installation from npm with:
78
+
79
+ ```bash
80
+ norn update
81
+ ```
82
+
77
83
  For a hosted agent workflow, select one sequence and supply its runtime inputs explicitly:
78
84
 
79
85
  ```bash
80
86
  norn run ./workflows/ticket.norn \
81
87
  --sequence HandleTicket \
82
- --param jiraTicket=NORN-482 \
88
+ --param NORN-482 \
83
89
  --param-json context=./inputs/ticket-context.json \
84
90
  --recording ./artifacts/ticket-run.json \
85
91
  --json
86
92
  ```
87
93
 
88
- `--param name=value` is repeatable and preserves the value as a string.
89
- `--param-json name=file.json` is also repeatable; it parses the file and assigns its typed JSON
90
- value directly to the declared sequence parameter. CLI values override authored defaults. Both
91
- forms require one file plus `--sequence`, reject undeclared or missing required parameters before
92
- execution, and cannot be combined with `@data`/`@theory` cases. `--recording` writes the normal
93
- secret-masked agent recording to an exact artifact path instead of the rolling local cache.
94
+ `--param [name=]value` is repeatable and preserves the value as a string.
95
+ `--param-json [name=]file.json` is also repeatable; it parses the file and assigns its typed JSON
96
+ value directly to the declared sequence parameter. Unnamed values follow declaration order and
97
+ skip parameters supplied by name, so named and positional inputs can be mixed. CLI values override
98
+ authored defaults. Both forms require one file plus `--sequence`, reject undeclared or missing
99
+ required parameters before execution, and cannot be combined with `@data`/`@theory` cases.
100
+ `--recording` writes the normal secret-masked agent recording to an exact artifact path instead of
101
+ the rolling local cache.
94
102
 
95
103
  Ordinary sequence runs handle `SIGINT` and `SIGTERM` as graceful cancellation requests. A repeated
96
104
  signal forces exit; cancelled processes use status 130 (`SIGINT`) or 143 (`SIGTERM`).
package/dist/cli.js CHANGED
@@ -101266,7 +101266,7 @@ var require_cross_spawn = __commonJS({
101266
101266
  var cp2 = require("child_process");
101267
101267
  var parse6 = require_parse2();
101268
101268
  var enoent = require_enoent();
101269
- function spawn7(command, args, options) {
101269
+ function spawn8(command, args, options) {
101270
101270
  const parsed = parse6(command, args, options);
101271
101271
  const spawned = cp2.spawn(parsed.command, parsed.args, parsed.options);
101272
101272
  enoent.hookChildProcess(spawned, parsed);
@@ -101278,8 +101278,8 @@ var require_cross_spawn = __commonJS({
101278
101278
  result.error = result.error || enoent.verifyENOENTSync(result.status, parsed);
101279
101279
  return result;
101280
101280
  }
101281
- module2.exports = spawn7;
101282
- module2.exports.spawn = spawn7;
101281
+ module2.exports = spawn8;
101282
+ module2.exports.spawn = spawn8;
101283
101283
  module2.exports.sync = spawnSync3;
101284
101284
  module2.exports._parse = parse6;
101285
101285
  module2.exports._enoent = enoent;
@@ -186020,6 +186020,25 @@ function mcpToolAllowsAdditionalProperties(source) {
186020
186020
  return schema.additionalProperties !== false;
186021
186021
  }
186022
186022
 
186023
+ // src/agents/agentActivity.ts
186024
+ function describeAgentActivity(event) {
186025
+ if (event.state === "completed") {
186026
+ return `Completed agent ${event.agent}`;
186027
+ }
186028
+ switch (event.kind) {
186029
+ case "preparing":
186030
+ return `${event.agent} \xB7 Preparing tools and context`;
186031
+ case "model":
186032
+ return `${event.agent} \xB7 Waiting for model response`;
186033
+ case "mcp":
186034
+ return `${event.agent} \xB7 Calling MCP tool ${event.name ?? "tool"}`;
186035
+ case "knowledge":
186036
+ return `${event.agent} \xB7 Loading knowledge`;
186037
+ case "agent":
186038
+ return `${event.agent} \xB7 Invoking agent ${event.name ?? "agent"}`;
186039
+ }
186040
+ }
186041
+
186023
186042
  // src/agents/judgeExpectations.ts
186024
186043
  var fs14 = __toESM(require("fs"));
186025
186044
  var path15 = __toESM(require("path"));
@@ -199525,6 +199544,18 @@ var AgentInvocationRunner = class {
199525
199544
  nextInvocationId() {
199526
199545
  return `agent-${(0, import_crypto7.randomUUID)()}`;
199527
199546
  }
199547
+ emitActivity(hop, state, kind, name) {
199548
+ this.options.onInvocationActivity?.({
199549
+ state,
199550
+ invocationId: hop.invocationId,
199551
+ ...hop.parentInvocationId ? { parentInvocationId: hop.parentInvocationId } : {},
199552
+ ...hop.hopPath ? { hopPath: hop.hopPath } : {},
199553
+ agent: hop.agent,
199554
+ depth: hop.depth,
199555
+ kind,
199556
+ ...name ? { name } : {}
199557
+ });
199558
+ }
199528
199559
  validateContract(agent, kind, value) {
199529
199560
  const builtIn = agent.builtInContracts?.[kind];
199530
199561
  if (builtIn) {
@@ -199665,6 +199696,11 @@ var AgentInvocationRunner = class {
199665
199696
  };
199666
199697
  parentHop.output.toolCalls.push(record2);
199667
199698
  const startedAt = Date.now();
199699
+ const activityKind = definition.kind === "agent" ? "agent" : definition.kind === "knowledge" ? "knowledge" : "mcp";
199700
+ const reportsActivity = definition.kind !== "context";
199701
+ if (reportsActivity) {
199702
+ this.emitActivity(parentHop, "active", activityKind, definition.name);
199703
+ }
199668
199704
  try {
199669
199705
  const output2 = await execute(input2, context);
199670
199706
  record2.output = cloneSnapshotValue(output2, null);
@@ -199677,6 +199713,9 @@ var AgentInvocationRunner = class {
199677
199713
  if (context.childInvocationId) {
199678
199714
  record2.childInvocationId = context.childInvocationId;
199679
199715
  }
199716
+ if (reportsActivity) {
199717
+ this.emitActivity(parentHop, "active", "model");
199718
+ }
199680
199719
  }
199681
199720
  }
199682
199721
  };
@@ -199739,6 +199778,7 @@ var AgentInvocationRunner = class {
199739
199778
  depth,
199740
199779
  cycle: hop.cycle
199741
199780
  });
199781
+ this.emitActivity(hop, "active", "preparing");
199742
199782
  let resolvedModel;
199743
199783
  let fatalToolError;
199744
199784
  let preparedKnowledge;
@@ -199866,6 +199906,7 @@ var AgentInvocationRunner = class {
199866
199906
  let providerResponse;
199867
199907
  let correctionConversation = conversation;
199868
199908
  for (; ; ) {
199909
+ this.emitActivity(hop, "active", "model");
199869
199910
  providerResponse = await adapter2.run({
199870
199911
  ...providerRequest,
199871
199912
  conversation: correctionConversation
@@ -199964,11 +200005,15 @@ var AgentInvocationRunner = class {
199964
200005
  }
199965
200006
  } finally {
199966
200007
  await this.options.mcpSessionManager?.closeScope(invocationId);
200008
+ if (this.options.signal?.aborted) {
200009
+ this.emitActivity(hop, "completed", "model");
200010
+ }
199967
200011
  }
199968
200012
  hop.output.model = hop.output.model || resolvedModel?.displayName || "";
199969
200013
  hop.output.ms = hop.output.ms || Date.now() - startedAt;
199970
200014
  this.options.knowledgeCoordinator?.recordInvocation(agent, hop, preparedKnowledge);
199971
200015
  this.options.onInvocationProgress?.({ phase: "completed", hop });
200016
+ this.emitActivity(hop, "completed", "model");
199972
200017
  return hop;
199973
200018
  }
199974
200019
  /**
@@ -206997,6 +207042,16 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
206997
207042
  nestingDepth,
206998
207043
  agentEvent
206999
207044
  });
207045
+ },
207046
+ onInvocationActivity: (agentActivity) => {
207047
+ onProgress?.({
207048
+ currentStep: stepIdx + 1,
207049
+ totalSteps,
207050
+ stepType: "judge",
207051
+ stepDescription: describeAgentActivity(agentActivity),
207052
+ nestingDepth,
207053
+ agentActivity
207054
+ });
207000
207055
  }
207001
207056
  });
207002
207057
  const run = await runner.run(
@@ -207135,6 +207190,16 @@ ${describeUnmetExpectations(verdict).map((line2) => ` - ${line2}`).join("\n")}`
207135
207190
  nestingDepth,
207136
207191
  agentEvent
207137
207192
  });
207193
+ },
207194
+ onInvocationActivity: (agentActivity) => {
207195
+ onProgress?.({
207196
+ currentStep: stepIdx + 1,
207197
+ totalSteps,
207198
+ stepType: "agent",
207199
+ stepDescription: describeAgentActivity(agentActivity),
207200
+ nestingDepth,
207201
+ agentActivity
207202
+ });
207138
207203
  }
207139
207204
  });
207140
207205
  const run = await runner.run(agent, input2);
@@ -212996,11 +213061,19 @@ var PARAMETER_NAME = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
212996
213061
  function parseCliSequenceParameterAssignment(authored2, source) {
212997
213062
  const flag = source === "json" ? "--param-json" : "--param";
212998
213063
  if (authored2 === void 0) {
212999
- throw new Error(`${flag} requires <name>=${source === "json" ? "<file.json>" : "<value>"}.`);
213064
+ throw new Error(
213065
+ `${flag} requires ${source === "json" ? "<file.json>" : "<value>"} or <name>=${source === "json" ? "<file.json>" : "<value>"}.`
213066
+ );
213000
213067
  }
213001
213068
  const separator = authored2.indexOf("=");
213002
- if (separator < 1) {
213003
- throw new Error(`${flag} requires <name>=${source === "json" ? "<file.json>" : "<value>"}.`);
213069
+ if (separator < 0) {
213070
+ if (source === "json" && authored2.trim().length === 0) {
213071
+ throw new Error("--param-json requires a non-empty .json file path.");
213072
+ }
213073
+ return { source, value: authored2 };
213074
+ }
213075
+ if (separator === 0) {
213076
+ throw new Error(`${flag} requires a parameter name before '='.`);
213004
213077
  }
213005
213078
  const name = authored2.slice(0, separator).trim();
213006
213079
  const value = authored2.slice(separator + 1);
@@ -213014,9 +213087,10 @@ function parseCliSequenceParameterAssignment(authored2, source) {
213014
213087
  }
213015
213088
  function loadJsonParameter(assignment, cwd) {
213016
213089
  const filePath = path41.resolve(cwd, assignment.value);
213090
+ const parameterLabel = assignment.name ? ` '${assignment.name}'` : "";
213017
213091
  if (path41.extname(filePath).toLowerCase() !== ".json") {
213018
213092
  throw new Error(
213019
- `JSON parameter '${assignment.name}' must reference a .json file: ${assignment.value}`
213093
+ `JSON parameter${parameterLabel} must reference a .json file: ${assignment.value}`
213020
213094
  );
213021
213095
  }
213022
213096
  let content;
@@ -213024,14 +213098,14 @@ function loadJsonParameter(assignment, cwd) {
213024
213098
  content = fs35.readFileSync(filePath, "utf8");
213025
213099
  } catch (error2) {
213026
213100
  throw new Error(
213027
- `Could not read JSON parameter '${assignment.name}' from '${filePath}': ${error2 instanceof Error ? error2.message : String(error2)}`
213101
+ `Could not read JSON parameter${parameterLabel} from '${filePath}': ${error2 instanceof Error ? error2.message : String(error2)}`
213028
213102
  );
213029
213103
  }
213030
213104
  try {
213031
213105
  return JSON.parse(content.replace(/^\uFEFF/, ""));
213032
213106
  } catch (error2) {
213033
213107
  throw new Error(
213034
- `Could not parse JSON parameter '${assignment.name}' from '${filePath}': ${error2 instanceof Error ? error2.message : String(error2)}`
213108
+ `Could not parse JSON parameter${parameterLabel} from '${filePath}': ${error2 instanceof Error ? error2.message : String(error2)}`
213035
213109
  );
213036
213110
  }
213037
213111
  }
@@ -213044,7 +213118,10 @@ function resolveCliSequenceParameters(declarations, assignments, cwd = process.c
213044
213118
  }
213045
213119
  }
213046
213120
  const supplied = /* @__PURE__ */ new Set();
213047
- for (const assignment of assignments) {
213121
+ const namedAssignments = assignments.filter(
213122
+ (assignment) => assignment.name !== void 0
213123
+ );
213124
+ for (const assignment of namedAssignments) {
213048
213125
  if (!declaredByName.has(assignment.name)) {
213049
213126
  const available = declarations.map((parameter) => parameter.name).join(", ") || "none";
213050
213127
  throw new Error(
@@ -213057,6 +213134,19 @@ function resolveCliSequenceParameters(declarations, assignments, cwd = process.c
213057
213134
  supplied.add(assignment.name);
213058
213135
  resolved[assignment.name] = assignment.source === "json" ? loadJsonParameter(assignment, cwd) : assignment.value;
213059
213136
  }
213137
+ const positionalAssignments = assignments.filter((assignment) => assignment.name === void 0);
213138
+ const availableDeclarations = declarations.filter((parameter) => !supplied.has(parameter.name));
213139
+ if (positionalAssignments.length > availableDeclarations.length) {
213140
+ throw new Error(
213141
+ `Too many positional sequence parameters: received ${positionalAssignments.length}, but only ${availableDeclarations.length} parameter${availableDeclarations.length === 1 ? "" : "s"} remain.`
213142
+ );
213143
+ }
213144
+ for (let index = 0; index < positionalAssignments.length; index++) {
213145
+ const assignment = positionalAssignments[index];
213146
+ const parameter = availableDeclarations[index];
213147
+ supplied.add(parameter.name);
213148
+ resolved[parameter.name] = assignment.source === "json" ? loadJsonParameter({ ...assignment, name: parameter.name }, cwd) : assignment.value;
213149
+ }
213060
213150
  const missing = declarations.filter((parameter) => parameter.defaultValue === void 0 && !supplied.has(parameter.name)).map((parameter) => parameter.name);
213061
213151
  if (missing.length > 0) {
213062
213152
  throw new Error(
@@ -213097,6 +213187,170 @@ function installCliRunCancellation(onCancellationRequested, forceExit = (code) =
213097
213187
  };
213098
213188
  }
213099
213189
 
213190
+ // src/cli/agentActivityProgress.ts
213191
+ var liveDisplays = /* @__PURE__ */ new Set();
213192
+ function clearAllAgentActivityProgressDisplays() {
213193
+ for (const display of [...liveDisplays]) {
213194
+ display.clear();
213195
+ }
213196
+ }
213197
+ var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
213198
+ function formatElapsed(ms) {
213199
+ const seconds = Math.max(0, Math.floor(ms / 1e3));
213200
+ if (seconds < 60) {
213201
+ return `${seconds}s`;
213202
+ }
213203
+ return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
213204
+ }
213205
+ function fitLine(value, columns) {
213206
+ const terminalColumns = columns && columns >= 40 ? columns : 100;
213207
+ const width = Math.max(24, terminalColumns - 1);
213208
+ return value.length <= width ? value : `${value.slice(0, Math.max(1, width - 1))}\u2026`;
213209
+ }
213210
+ function createAgentActivityProgressDisplay(stream4 = process.stderr, now = Date.now) {
213211
+ const enabled = stream4.isTTY === true;
213212
+ const active = /* @__PURE__ */ new Map();
213213
+ let order = 0;
213214
+ let frame = 0;
213215
+ let lineVisible = false;
213216
+ let timer;
213217
+ let display;
213218
+ const current = () => [...active.values()].sort((left, right) => right.event.depth - left.event.depth || right.order - left.order)[0];
213219
+ const render = () => {
213220
+ if (!enabled) {
213221
+ return;
213222
+ }
213223
+ const item = current();
213224
+ if (!item) {
213225
+ if (lineVisible) {
213226
+ stream4.write("\r\x1B[2K");
213227
+ lineVisible = false;
213228
+ }
213229
+ return;
213230
+ }
213231
+ const elapsed = formatElapsed(now() - item.startedAt);
213232
+ const line2 = fitLine(`${FRAMES[frame++ % FRAMES.length]} ${item.description} ${elapsed}`, stream4.columns);
213233
+ stream4.write(`\r\x1B[2K${line2}`);
213234
+ lineVisible = true;
213235
+ };
213236
+ const ensureTimer = () => {
213237
+ if (!enabled || timer) {
213238
+ return;
213239
+ }
213240
+ timer = setInterval(render, 120);
213241
+ timer.unref?.();
213242
+ };
213243
+ const stopTimer = () => {
213244
+ if (!timer) {
213245
+ return;
213246
+ }
213247
+ clearInterval(timer);
213248
+ timer = void 0;
213249
+ };
213250
+ const clear = () => {
213251
+ active.clear();
213252
+ stopTimer();
213253
+ render();
213254
+ liveDisplays.delete(display);
213255
+ };
213256
+ display = {
213257
+ update: (event, description) => {
213258
+ if (!enabled) {
213259
+ return;
213260
+ }
213261
+ if (event.state === "completed") {
213262
+ active.delete(event.invocationId);
213263
+ } else {
213264
+ active.set(event.invocationId, {
213265
+ event,
213266
+ description,
213267
+ startedAt: now(),
213268
+ order: order++
213269
+ });
213270
+ }
213271
+ if (active.size > 0) {
213272
+ liveDisplays.add(display);
213273
+ ensureTimer();
213274
+ } else {
213275
+ stopTimer();
213276
+ liveDisplays.delete(display);
213277
+ }
213278
+ render();
213279
+ },
213280
+ clear,
213281
+ dispose: clear
213282
+ };
213283
+ return display;
213284
+ }
213285
+
213286
+ // src/cli/update.ts
213287
+ var import_child_process5 = require("child_process");
213288
+ var NORN_NPM_PACKAGE = "norn-cli@latest";
213289
+ function resolveUpdateInvocation(platform2 = process.platform, commandShell = process.env.ComSpec) {
213290
+ if (platform2 === "win32") {
213291
+ return {
213292
+ command: commandShell || "cmd.exe",
213293
+ args: ["/d", "/s", "/c", `npm install --global ${NORN_NPM_PACKAGE}`]
213294
+ };
213295
+ }
213296
+ return {
213297
+ command: "npm",
213298
+ args: ["install", "--global", NORN_NPM_PACKAGE]
213299
+ };
213300
+ }
213301
+ function printUpdateHelp() {
213302
+ console.log(`
213303
+ Usage: norn update
213304
+
213305
+ Updates the globally installed Norn CLI to the latest npm release.
213306
+ Equivalent to: npm install --global ${NORN_NPM_PACKAGE}
213307
+ `);
213308
+ }
213309
+ async function handleUpdateCommand(args, spawnProcess = import_child_process5.spawn) {
213310
+ if (args.length === 1 && (args[0] === "--help" || args[0] === "-h")) {
213311
+ printUpdateHelp();
213312
+ return 0;
213313
+ }
213314
+ if (args.length > 0) {
213315
+ console.error(`Error: norn update does not accept arguments: ${args.join(" ")}`);
213316
+ console.error("Run norn update --help for usage.");
213317
+ return 1;
213318
+ }
213319
+ console.log("Updating Norn CLI to the latest version...");
213320
+ const invocation = resolveUpdateInvocation();
213321
+ return new Promise((resolve29) => {
213322
+ let settled = false;
213323
+ const finish = (exitCode) => {
213324
+ if (!settled) {
213325
+ settled = true;
213326
+ resolve29(exitCode);
213327
+ }
213328
+ };
213329
+ const child = spawnProcess(invocation.command, invocation.args, {
213330
+ stdio: "inherit",
213331
+ windowsHide: true
213332
+ });
213333
+ child.once("error", (error2) => {
213334
+ console.error(`Unable to start npm: ${error2.message}`);
213335
+ console.error(`Run npm install --global ${NORN_NPM_PACKAGE} manually.`);
213336
+ finish(1);
213337
+ });
213338
+ child.once("close", (code, signal) => {
213339
+ if (settled) {
213340
+ return;
213341
+ }
213342
+ if (code === 0) {
213343
+ console.log("Norn CLI update complete. Run norn --version to verify the installed version.");
213344
+ finish(0);
213345
+ return;
213346
+ }
213347
+ const detail = signal ? `signal ${signal}` : `exit code ${code ?? "unknown"}`;
213348
+ console.error(`Norn CLI update failed (${detail}).`);
213349
+ finish(1);
213350
+ });
213351
+ });
213352
+ }
213353
+
213100
213354
  // src/cli.ts
213101
213355
  function handleImportResolutionErrors(errors, colors) {
213102
213356
  const { blockingErrors, warningErrors } = splitImportResolutionErrors(errors);
@@ -213245,10 +213499,19 @@ function redactSequenceResultForJson(result, redaction) {
213245
213499
  }
213246
213500
  function createAgentProgressCallback(sequenceName, colors, redaction, verbose) {
213247
213501
  let printedHeading = false;
213502
+ const activityDisplay = createAgentActivityProgressDisplay();
213248
213503
  return (info) => {
213504
+ if (info.agentActivity) {
213505
+ activityDisplay.update(
213506
+ info.agentActivity,
213507
+ redactString(describeAgentActivity(info.agentActivity), redaction)
213508
+ );
213509
+ return;
213510
+ }
213249
213511
  if (info.stepType !== "agent" || !info.stepResult?.agent) {
213250
213512
  return;
213251
213513
  }
213514
+ activityDisplay.clear();
213252
213515
  if (!printedHeading) {
213253
213516
  console.log("");
213254
213517
  console.log(colors.bold(`Agent trace: ${sequenceName}`));
@@ -213505,7 +213768,8 @@ function parseArgs(args) {
213505
213768
  refactorRegionPattern: false,
213506
213769
  writeRefactor: false,
213507
213770
  yes: false,
213508
- sequenceParameters: []
213771
+ sequenceParameters: [],
213772
+ unexpectedPositionals: []
213509
213773
  };
213510
213774
  for (let i2 = 0; i2 < args.length; i2++) {
213511
213775
  const arg = args[i2];
@@ -213587,7 +213851,11 @@ function parseArgs(args) {
213587
213851
  printHelp();
213588
213852
  process.exit(0);
213589
213853
  } else if (!arg.startsWith("-")) {
213590
- options.file = arg;
213854
+ if (options.file) {
213855
+ options.unexpectedPositionals.push(arg);
213856
+ } else {
213857
+ options.file = arg;
213858
+ }
213591
213859
  }
213592
213860
  }
213593
213861
  return options;
@@ -213597,6 +213865,7 @@ function printHelp() {
213597
213865
  Norn CLI - Run HTTP requests and test sequences
213598
213866
 
213599
213867
  Usage: norn <file.norn|directory> [options]
213868
+ norn update
213600
213869
  norn replay <recording.json> [--json] [--from <agent-or-hop>] [--env <name>]
213601
213870
  norn secrets <command> [options]
213602
213871
  norn contracts <sync|check> <file.nornapi> [options]
@@ -213609,9 +213878,11 @@ Options:
213609
213878
  -s, --sequence <name> Run a specific sequence by name (single file only)
213610
213879
  -r, --request <name> Run a specific named request (single file only)
213611
213880
  -e, --env <name> Use environment from .nornenv (e.g., dev, prod)
213612
- --param <name>=<value> Supply a string parameter to --sequence (repeatable)
213613
- --param-json <name>=<file.json>
213881
+ --param [<name>=]<value>
213882
+ Supply a string parameter to --sequence (repeatable)
213883
+ --param-json [<name>=]<file.json>
213614
213884
  Parse a JSON file into a --sequence parameter (repeatable)
213885
+ Unnamed parameters follow sequence declaration order
213615
213886
  --recording <file.json>
213616
213887
  Write the agent recording to an exact artifact path
213617
213888
  -t, --timeout <time> Request timeout override (e.g. 180s, 3m, 300000ms; default: norn.config.json or 30s)
@@ -213631,8 +213902,12 @@ Options:
213631
213902
  --refactor-region-pattern
213632
213903
  Refactor a flat .nornenv STAGE_REGION matrix to templates
213633
213904
  --write Apply --refactor-region-pattern instead of printing result
213905
+ -V, --version Show the installed Norn CLI version
213634
213906
  -h, --help Show this help message
213635
213907
 
213908
+ Maintenance:
213909
+ norn update Update the global Norn CLI to the latest npm release
213910
+
213636
213911
  Report Generation:
213637
213912
  Use --output-dir for auto-generated timestamped filenames:
213638
213913
  norn tests.norn --output-dir ./reports
@@ -213665,7 +213940,7 @@ Examples:
213665
213940
  norn tests/ # Run all tests in directory (recursive)
213666
213941
  norn tests/Regression/ -e prelive # Run regression suite with environment
213667
213942
  norn api-tests.norn -s auth # Run 'auth' sequence by name
213668
- norn agent-flow.norn -s HandleTicket --param jiraTicket=NORN-482
213943
+ norn agent-flow.norn -s HandleTicket --param NORN-482
213669
213944
  norn agent-flow.norn -s HandleTicket --param-json context=./ticket.json
213670
213945
  norn agent-flow.norn -s HandleTicket --recording ./artifacts/run.json
213671
213946
  norn api-tests.norn -r LoginRequest # Run 'LoginRequest' named request
@@ -213999,6 +214274,10 @@ async function runAllSequences(fileContent, variables, cookieJar, workingDir, ap
213999
214274
  }
214000
214275
  async function main() {
214001
214276
  const rawArgs = process.argv.slice(2);
214277
+ if (rawArgs[0] === "update") {
214278
+ const exitCode = await handleUpdateCommand(rawArgs.slice(1));
214279
+ process.exit(exitCode);
214280
+ }
214002
214281
  if (rawArgs[0] === "replay") {
214003
214282
  const exitCode = await handleReplayCommand(rawArgs.slice(1));
214004
214283
  process.exit(exitCode);
@@ -214016,11 +214295,22 @@ async function main() {
214016
214295
  process.exit(exitCode);
214017
214296
  }
214018
214297
  const args = rawArgs[0] === "run" ? rawArgs.slice(1) : rawArgs;
214298
+ if (args.length === 1 && (args[0] === "--version" || args[0] === "-V")) {
214299
+ console.log(`Norn CLI ${"4.3.0"}`);
214300
+ return;
214301
+ }
214019
214302
  if (args.length === 0) {
214020
214303
  printHelp();
214021
214304
  process.exit(1);
214022
214305
  }
214023
214306
  const options = parseArgs(args);
214307
+ if (options.unexpectedPositionals.length > 0) {
214308
+ const unexpected = options.unexpectedPositionals[0];
214309
+ console.error(
214310
+ `Error: Unexpected positional argument '${unexpected}'. To pass a sequence value, use --param ${unexpected} or --param name=${unexpected}.`
214311
+ );
214312
+ process.exit(1);
214313
+ }
214024
214314
  if (options.contracts) {
214025
214315
  setContractModeOverride(true);
214026
214316
  }
@@ -214130,6 +214420,7 @@ async function main() {
214130
214420
  }
214131
214421
  if (!options.request) {
214132
214422
  activeRunCancellation = installCliRunCancellation((signal) => {
214423
+ clearAllAgentActivityProgressDisplays();
214133
214424
  console.error(colors.warning(`Cancellation requested by ${signal}; stopping the current run...`));
214134
214425
  });
214135
214426
  }
@@ -214581,6 +214872,7 @@ ${colors.error("Errors:")}`);
214581
214872
  activeRunCancellation = void 0;
214582
214873
  }
214583
214874
  main().catch((error2) => {
214875
+ clearAllAgentActivityProgressDisplays();
214584
214876
  if (isSequenceCancelledError(error2) && activeRunCancellation?.getReceivedSignal()) {
214585
214877
  const exitCode = activeRunCancellation.getExitCode();
214586
214878
  activeRunCancellation.dispose();
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "norn-cli",
3
3
  "displayName": "Norn",
4
4
  "description": "Version-controlled API and database tests. Author in VS Code, then run the same files from the CLI and CI.",
5
- "version": "4.1.0",
5
+ "version": "4.3.0",
6
6
  "publisher": "Norn-PeterKrustanov",
7
7
  "author": {
8
8
  "name": "Peter Krastanov"
@@ -61,6 +61,7 @@
61
61
  "onCommand:norn.debugSequence",
62
62
  "onCommand:norn.debug.runToNextContractFailure",
63
63
  "onCommand:norn.showAgentGraph",
64
+ "onCommand:norn.showAgentRunResults",
64
65
  "onDebugResolve:norn"
65
66
  ],
66
67
  "main": "./dist/extension.js",
@@ -92,6 +93,11 @@
92
93
  "title": "Show Agent Graph",
93
94
  "category": "Norn"
94
95
  },
96
+ {
97
+ "command": "norn.showAgentRunResults",
98
+ "title": "Show Agent Run Results",
99
+ "category": "Norn"
100
+ },
95
101
  {
96
102
  "command": "norn.knowledge.toggleMarkup",
97
103
  "title": "Knowledge: Toggle Markup",
@@ -578,11 +584,6 @@
578
584
  },
579
585
  "description": "Workspace-relative glob patterns for .norn files or folders to hide from the Norn Test Explorer. Useful for negative fixtures, live-only tests, or documentation demos."
580
586
  },
581
- "norn.terminal.focusMode": {
582
- "type": "boolean",
583
- "default": true,
584
- "description": "Close the primary Side Bar and bottom Panel when opening the Norn terminal."
585
- },
586
587
  "norn.terminal.intelliSense.selectFirstSuggestion": {
587
588
  "type": "boolean",
588
589
  "default": false,
@@ -630,7 +631,7 @@
630
631
  "test:regression": "node ./tests/test-scripts/run-regression.js",
631
632
  "test:prerelease": "npm test && npm run test:regression",
632
633
  "publish:npm": "node -e \"const fs=require('fs');const p=require('./package.json');p.name='norn-cli';fs.writeFileSync('package.json',JSON.stringify(p,null,2)+'\\n')\" && npm publish; exit_code=$?; node -e \"const fs=require('fs');const p=require('./package.json');p.name='norn';fs.writeFileSync('package.json',JSON.stringify(p,null,2)+'\\n')\"; exit $exit_code",
633
- "publish:vsce": "node -e \"const p=require('./package.json');p.name='norn';require('fs').writeFileSync('package.json',JSON.stringify(p,null,2))\" && npx vsce publish",
634
+ "publish:vsce": "vsce publish",
634
635
  "publish:all": "npm run publish:npm && npm run publish:vsce"
635
636
  },
636
637
  "devDependencies": {
@@ -642,6 +643,7 @@
642
643
  "@types/vscode": "^1.125.0",
643
644
  "@vscode/test-cli": "^0.0.15",
644
645
  "@vscode/test-electron": "^3.1.0",
646
+ "@vscode/vsce": "^3.9.2",
645
647
  "@xterm/addon-fit": "^0.11.0",
646
648
  "@xterm/xterm": "^6.0.0",
647
649
  "esbuild": "^0.27.2",
@@ -659,7 +661,7 @@
659
661
  "axios": "^1.13.2",
660
662
  "chalk": "^5.4.1",
661
663
  "mssql": "^12.2.0",
662
- "norn-cli": "^1.10.6",
664
+ "norn-cli": "4.2.0",
663
665
  "openai": "^6.49.0",
664
666
  "pg": "^8.20.0",
665
667
  "tough-cookie": "^6.0.0"