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.
- package/README.md +15 -7
- package/dist/cli.js +307 -15
- 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
|
|
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.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
|
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 =
|
|
101282
|
-
module2.exports.spawn =
|
|
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(
|
|
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 <
|
|
213003
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
213613
|
-
|
|
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
|
|
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.
|
|
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": "
|
|
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": "
|
|
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"
|