@noir-ai/cli 1.14.0 → 1.15.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 +2 -2
- package/dist/bin.d.ts +10 -3
- package/dist/bin.js +1240 -365
- package/dist/bin.js.map +1 -1
- package/dist/index.d.ts +39 -12
- package/dist/index.js +1258 -383
- package/dist/index.js.map +1 -1
- package/dist/{output-BNE9htEm.d.ts → output-2T2lGR7a.d.ts} +3 -3
- package/dist/tui/{chunk-6LO4LXEH.js → chunk-5KWETEMF.js} +1191 -352
- package/dist/tui/chunk-5KWETEMF.js.map +1 -0
- package/dist/tui/{chunk-ESZTOQEC.js → chunk-KETMM3BA.js} +25 -3
- package/dist/tui/chunk-KETMM3BA.js.map +1 -0
- package/dist/tui/{chunk-2RK6AFZQ.js → chunk-TPNJEN4B.js} +2 -2
- package/dist/tui/chunk-TPNJEN4B.js.map +1 -0
- package/dist/tui/{create-FD4E626Z.js → create-6KDIFPUL.js} +10 -6
- package/dist/tui/create-6KDIFPUL.js.map +1 -0
- package/dist/tui/index.js +994 -120
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/{install-YGTUSLRO.js → install-KA35ONQP.js} +2 -2
- package/dist/tui/{sync-N633SQSS.js → sync-AII5WCRO.js} +8 -4
- package/dist/tui/sync-AII5WCRO.js.map +1 -0
- package/dist/tui/{update-LET5BWOU.js → update-XGKJ4KBR.js} +3 -3
- package/package.json +11 -11
- package/dist/tui/chunk-2RK6AFZQ.js.map +0 -1
- package/dist/tui/chunk-6LO4LXEH.js.map +0 -1
- package/dist/tui/chunk-ESZTOQEC.js.map +0 -1
- package/dist/tui/create-FD4E626Z.js.map +0 -1
- package/dist/tui/sync-N633SQSS.js.map +0 -1
- /package/dist/tui/{install-YGTUSLRO.js.map → install-KA35ONQP.js.map} +0 -0
- /package/dist/tui/{update-LET5BWOU.js.map → update-XGKJ4KBR.js.map} +0 -0
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
DEFAULT_UPDATE_CONFIG,
|
|
3
3
|
buildUpdateNotice,
|
|
4
4
|
runAsyncUpdateCheck
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-TPNJEN4B.js";
|
|
6
6
|
import {
|
|
7
7
|
EXIT,
|
|
8
8
|
NoirCliError,
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
definitionList,
|
|
13
13
|
error,
|
|
14
14
|
fail,
|
|
15
|
+
failAndExit,
|
|
15
16
|
handleError,
|
|
16
17
|
info,
|
|
17
18
|
isInteractive,
|
|
@@ -25,7 +26,7 @@ import {
|
|
|
25
26
|
tip,
|
|
26
27
|
useColor,
|
|
27
28
|
warn
|
|
28
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-KETMM3BA.js";
|
|
29
30
|
|
|
30
31
|
// src/commands/status.ts
|
|
31
32
|
import { loadProjectInfo as loadProjectInfo14, NOIR_VERSION as NOIR_VERSION5 } from "@noir-ai/core";
|
|
@@ -174,8 +175,8 @@ async function contextIndex(opts) {
|
|
|
174
175
|
}
|
|
175
176
|
renderIndex(data, opts.paths, opts);
|
|
176
177
|
}
|
|
177
|
-
function renderIndex(data,
|
|
178
|
-
const scope =
|
|
178
|
+
function renderIndex(data, paths4, opts) {
|
|
179
|
+
const scope = paths4 && paths4.length > 0 ? paths4.join(", ") : ".";
|
|
179
180
|
const flag = data.degraded ? ` ${badge("warn", "degraded: vectors skipped")}` : "";
|
|
180
181
|
log(`context index \u2014 ${scope}${flag}`, opts);
|
|
181
182
|
definitionList(
|
|
@@ -870,7 +871,13 @@ import {
|
|
|
870
871
|
readInstallRecord,
|
|
871
872
|
readUpdateCache
|
|
872
873
|
} from "@noir-ai/core";
|
|
873
|
-
import {
|
|
874
|
+
import {
|
|
875
|
+
CURRENT_SCAFFOLD_VERSION,
|
|
876
|
+
isStaleSeed,
|
|
877
|
+
loadTemplate,
|
|
878
|
+
readScaffoldVersion,
|
|
879
|
+
render
|
|
880
|
+
} from "@noir-ai/create";
|
|
874
881
|
import { pidAlive as pidAlive3, readProjectDaemonRecord as readProjectDaemonRecord2 } from "@noir-ai/daemon";
|
|
875
882
|
import { resolveModelConfig } from "@noir-ai/model";
|
|
876
883
|
async function probeOnnx() {
|
|
@@ -1117,9 +1124,10 @@ function checkProvider(checks, project, sources) {
|
|
|
1117
1124
|
for (const name of names) {
|
|
1118
1125
|
const p = resolved.providers[name];
|
|
1119
1126
|
if (!p) continue;
|
|
1120
|
-
const fromFile = p.apiKeyEnv !== void 0 && sources[p.apiKeyEnv] === "file";
|
|
1121
|
-
const
|
|
1122
|
-
|
|
1127
|
+
const fromFile = p.apiKeyEnv !== void 0 && sources[p.apiKeyEnv] === "file" || p.authTokenEnv !== void 0 && sources[p.authTokenEnv] === "file";
|
|
1128
|
+
const credentialName = p.apiKeyEnv ?? p.authTokenEnv;
|
|
1129
|
+
const key = p.hasKey ? fromFile ? "key present (from .noir/.env)" : "key present" : credentialName ? `missing ${credentialName}` : "anonymous";
|
|
1130
|
+
if (!p.hasKey && credentialName) missing = true;
|
|
1123
1131
|
parts.push(`${name}/${p.model ?? "?"} (${key})`);
|
|
1124
1132
|
}
|
|
1125
1133
|
checks.push({
|
|
@@ -1155,6 +1163,23 @@ function checkScaffoldVersion(checks, root) {
|
|
|
1155
1163
|
checks.push({ name: "scaffold version", status: status2, detail });
|
|
1156
1164
|
return { onDisk, current, drift };
|
|
1157
1165
|
}
|
|
1166
|
+
function checkSeedDrift(checks, root) {
|
|
1167
|
+
const seedPath = join2(root, ".noir", ".env.example");
|
|
1168
|
+
let bytes;
|
|
1169
|
+
try {
|
|
1170
|
+
bytes = readFileSync2(seedPath, "utf8");
|
|
1171
|
+
} catch {
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
const currentRender = render(loadTemplate("env.example.tmpl"), {});
|
|
1175
|
+
if (isStaleSeed("envExample", bytes, currentRender)) {
|
|
1176
|
+
checks.push({
|
|
1177
|
+
name: "doc seed",
|
|
1178
|
+
status: "warn",
|
|
1179
|
+
detail: `.noir/.env.example is an unedited copy of an older Noir \u2014 run \`noir init --upgrade\` to refresh it`
|
|
1180
|
+
});
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1158
1183
|
function checkRulesMdBudget(checks, root, project) {
|
|
1159
1184
|
if (!project) {
|
|
1160
1185
|
checks.push({ name: "rules budget", status: "warn", detail: "skipped \u2014 not initialized" });
|
|
@@ -1490,6 +1515,7 @@ async function doctor(opts = {}) {
|
|
|
1490
1515
|
checkProvider(checks, project, noirEnv.sources);
|
|
1491
1516
|
checkNoirEnv(checks, root, noirEnv);
|
|
1492
1517
|
const scaffold2 = checkScaffoldVersion(checks, root);
|
|
1518
|
+
checkSeedDrift(checks, root);
|
|
1493
1519
|
const rules = checkRulesMdBudget(checks, root, project);
|
|
1494
1520
|
const host = checkHostArtifacts(checks, root, project);
|
|
1495
1521
|
checkNestedNoir(checks, root);
|
|
@@ -1542,6 +1568,9 @@ var CURATED_AMBIENT_KEYS = [
|
|
|
1542
1568
|
"COHERE_API_KEY",
|
|
1543
1569
|
"OLLAMA_BASE_URL",
|
|
1544
1570
|
"ANTHROPIC_API_KEY",
|
|
1571
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
1572
|
+
"ANTHROPIC_BASE_URL",
|
|
1573
|
+
"API_TIMEOUT_MS",
|
|
1545
1574
|
"NOIR_PROFILE"
|
|
1546
1575
|
];
|
|
1547
1576
|
var ambientBaseline;
|
|
@@ -1597,12 +1626,12 @@ function describeSource(row) {
|
|
|
1597
1626
|
return row.shadowed === true ? ".noir/.env (shadows environment)" : ".noir/.env";
|
|
1598
1627
|
}
|
|
1599
1628
|
async function env(opts = {}) {
|
|
1600
|
-
const
|
|
1629
|
+
const report2 = resolveEnv(process.cwd(), ambientBaseline ?? process.env);
|
|
1601
1630
|
if (opts.json === true) {
|
|
1602
|
-
json({ ok: true, data: { vars:
|
|
1631
|
+
json({ ok: true, data: { vars: report2.vars, warnings: report2.warnings } });
|
|
1603
1632
|
return;
|
|
1604
1633
|
}
|
|
1605
|
-
if (
|
|
1634
|
+
if (report2.vars.length === 0) {
|
|
1606
1635
|
info(
|
|
1607
1636
|
"nothing to report \u2014 .noir/.env defines no keys in effect and no Noir-relevant environment variable is set",
|
|
1608
1637
|
opts
|
|
@@ -1610,10 +1639,10 @@ async function env(opts = {}) {
|
|
|
1610
1639
|
return;
|
|
1611
1640
|
}
|
|
1612
1641
|
table(
|
|
1613
|
-
|
|
1642
|
+
report2.vars.map((row) => ({
|
|
1614
1643
|
KEY: row.key,
|
|
1615
1644
|
SOURCE: describeSource(row),
|
|
1616
|
-
VALUE:
|
|
1645
|
+
VALUE: report2.shapes[row.key] ?? ""
|
|
1617
1646
|
})),
|
|
1618
1647
|
["KEY", "SOURCE", "VALUE"],
|
|
1619
1648
|
opts
|
|
@@ -1641,7 +1670,7 @@ import {
|
|
|
1641
1670
|
writeDecisionStub
|
|
1642
1671
|
} from "@noir-ai/workflow";
|
|
1643
1672
|
var PHASE_SKILL = {
|
|
1644
|
-
//
|
|
1673
|
+
// Skill curation: intake+clarify merged into brainstorming;
|
|
1645
1674
|
// execute → executing-plans; verify → verifying; document → wrap.
|
|
1646
1675
|
intake: "noir-brainstorming",
|
|
1647
1676
|
clarify: "noir-brainstorming",
|
|
@@ -2053,7 +2082,7 @@ async function taskVerify(opts) {
|
|
|
2053
2082
|
`${JSON.stringify({
|
|
2054
2083
|
ok: false,
|
|
2055
2084
|
error: { code: EXIT.ERROR, message: `task verify: ${detail}` },
|
|
2056
|
-
//
|
|
2085
|
+
// `{ok:false, error}` + a payload — recovery context + evidence nest
|
|
2057
2086
|
// under `data`, not as top-level siblings (same envelope shape as the
|
|
2058
2087
|
// success path; consumers read `data` for the full picture).
|
|
2059
2088
|
data: {
|
|
@@ -2416,6 +2445,14 @@ function isDestructive(argv) {
|
|
|
2416
2445
|
return prefix.every((tok, i) => argv[i] === tok);
|
|
2417
2446
|
});
|
|
2418
2447
|
}
|
|
2448
|
+
function runPromptFrom(argv) {
|
|
2449
|
+
if (argv[0] !== "run") return null;
|
|
2450
|
+
const rest = argv.slice(1);
|
|
2451
|
+
if (rest.length === 0) return null;
|
|
2452
|
+
if (rest.some((token) => token.startsWith("-"))) return null;
|
|
2453
|
+
const prompt = rest.join(" ").trim();
|
|
2454
|
+
return prompt.length > 0 ? prompt : null;
|
|
2455
|
+
}
|
|
2419
2456
|
function capitalizeFirst(s) {
|
|
2420
2457
|
if (s.length === 0) return s;
|
|
2421
2458
|
return `${s[0]?.toUpperCase() ?? ""}${s.slice(1)}`;
|
|
@@ -2423,6 +2460,10 @@ function capitalizeFirst(s) {
|
|
|
2423
2460
|
function hasAction(cmd) {
|
|
2424
2461
|
return cmd._actionHandler != null;
|
|
2425
2462
|
}
|
|
2463
|
+
function requiredArgLabel(cmd) {
|
|
2464
|
+
const required = cmd.registeredArguments.find((arg) => arg.required);
|
|
2465
|
+
return required?.name();
|
|
2466
|
+
}
|
|
2426
2467
|
function collectLeaves(cmd, path, out) {
|
|
2427
2468
|
const children = cmd.commands;
|
|
2428
2469
|
const isLeaf = hasAction(cmd) && children.length === 0;
|
|
@@ -2437,7 +2478,8 @@ function collectLeaves(cmd, path, out) {
|
|
|
2437
2478
|
description,
|
|
2438
2479
|
category: argv[0] ?? "",
|
|
2439
2480
|
keywords: argv,
|
|
2440
|
-
destructive: isDestructive(argv)
|
|
2481
|
+
destructive: isDestructive(argv),
|
|
2482
|
+
needsArg: requiredArgLabel(cmd)
|
|
2441
2483
|
});
|
|
2442
2484
|
return;
|
|
2443
2485
|
}
|
|
@@ -2503,7 +2545,15 @@ var HOME_SECTIONS = [
|
|
|
2503
2545
|
label: "Save memory",
|
|
2504
2546
|
hint: "save an observation \xB7 \u26A0 destructive",
|
|
2505
2547
|
destructive: true,
|
|
2506
|
-
|
|
2548
|
+
// The value travels as `--content <text>` (memory save declares no
|
|
2549
|
+
// positional), so the flag rides in the dispatch prefix and the
|
|
2550
|
+
// collected value is appended after it.
|
|
2551
|
+
dispatch: ["memory", "save", "--content"],
|
|
2552
|
+
needsArg: {
|
|
2553
|
+
label: "content",
|
|
2554
|
+
prompt: "Memory content:",
|
|
2555
|
+
placeholder: "what you want to remember"
|
|
2556
|
+
}
|
|
2507
2557
|
},
|
|
2508
2558
|
{
|
|
2509
2559
|
id: "memory sessions",
|
|
@@ -2527,8 +2577,21 @@ var HOME_SECTIONS = [
|
|
|
2527
2577
|
{
|
|
2528
2578
|
id: "workflow",
|
|
2529
2579
|
label: "Workflow",
|
|
2530
|
-
hint: "next \xB7 status \xB7 advance \xB7 handoff \xB7 wrap",
|
|
2580
|
+
hint: "run \xB7 next \xB7 status \xB7 advance \xB7 handoff \xB7 wrap",
|
|
2531
2581
|
items: [
|
|
2582
|
+
{
|
|
2583
|
+
id: "run",
|
|
2584
|
+
label: "Ask the host",
|
|
2585
|
+
hint: "ask the host agent a question \xB7 \u26A0 destructive",
|
|
2586
|
+
destructive: true,
|
|
2587
|
+
// The prompt is not a required argument (the command also takes flags
|
|
2588
|
+
// like --list-profiles), so the palette only learns to ask for it here.
|
|
2589
|
+
needsArg: {
|
|
2590
|
+
label: "prompt",
|
|
2591
|
+
prompt: "Prompt for the host:",
|
|
2592
|
+
placeholder: "e.g. summarise the auth flow"
|
|
2593
|
+
}
|
|
2594
|
+
},
|
|
2532
2595
|
{
|
|
2533
2596
|
id: "task next",
|
|
2534
2597
|
label: "Next task",
|
|
@@ -3362,16 +3425,19 @@ async function release(opts) {
|
|
|
3362
3425
|
}
|
|
3363
3426
|
|
|
3364
3427
|
// src/commands/run.ts
|
|
3365
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as
|
|
3428
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
3366
3429
|
import { join as join4 } from "path";
|
|
3367
3430
|
import { SUPPORTED_HOSTS } from "@noir-ai/adapters";
|
|
3368
|
-
import { loadNoirEnv as loadNoirEnv3, NOIR_DIR as NOIR_DIR2, parseConfig } from "@noir-ai/core";
|
|
3431
|
+
import { isDeniedEnvKey as isDeniedEnvKey2, loadNoirEnv as loadNoirEnv3, NOIR_DIR as NOIR_DIR2, parseConfig, paths as paths2 } from "@noir-ai/core";
|
|
3369
3432
|
|
|
3370
3433
|
// src/orchestrator.ts
|
|
3371
3434
|
import { spawn as spawn2 } from "child_process";
|
|
3372
3435
|
import { constants } from "os";
|
|
3373
3436
|
import { createInterface } from "readline";
|
|
3374
3437
|
|
|
3438
|
+
// src/run-timing.ts
|
|
3439
|
+
var INTERRUPT_GRACE_MS = 5e3;
|
|
3440
|
+
|
|
3375
3441
|
// src/shell-bridge.ts
|
|
3376
3442
|
import { spawn } from "child_process";
|
|
3377
3443
|
import { basename } from "path";
|
|
@@ -3419,7 +3485,7 @@ var PROBE_SCRIPTS = {
|
|
|
3419
3485
|
function probeCommand(name, shell, opts) {
|
|
3420
3486
|
const kind = shellKind(shell);
|
|
3421
3487
|
const timeoutMs = opts.timeoutMs ?? PROBE_TIMEOUT_MS2;
|
|
3422
|
-
return new Promise((
|
|
3488
|
+
return new Promise((resolve2) => {
|
|
3423
3489
|
const script = kind ? PROBE_SCRIPTS[kind] : "";
|
|
3424
3490
|
const flags = kind ? PROBE_FLAGS[kind] : "-lic";
|
|
3425
3491
|
const probeArgs = kind === "fish" ? [name] : ["noirbridge", name];
|
|
@@ -3442,15 +3508,15 @@ function probeCommand(name, shell, opts) {
|
|
|
3442
3508
|
} catch {
|
|
3443
3509
|
}
|
|
3444
3510
|
}
|
|
3445
|
-
|
|
3511
|
+
resolve2({ stdout, timedOut: true });
|
|
3446
3512
|
}, timeoutMs);
|
|
3447
3513
|
child.on("error", () => {
|
|
3448
3514
|
clearTimeout(timer);
|
|
3449
|
-
|
|
3515
|
+
resolve2({ stdout, timedOut: false });
|
|
3450
3516
|
});
|
|
3451
3517
|
child.on("close", () => {
|
|
3452
3518
|
clearTimeout(timer);
|
|
3453
|
-
|
|
3519
|
+
resolve2({ stdout, timedOut: false });
|
|
3454
3520
|
});
|
|
3455
3521
|
});
|
|
3456
3522
|
}
|
|
@@ -3506,7 +3572,7 @@ var HOST_BINARY = {
|
|
|
3506
3572
|
// emits AGENTS.md — a file host, not a spawnable CLI
|
|
3507
3573
|
};
|
|
3508
3574
|
var HOST_FLAGS = {
|
|
3509
|
-
claude: ["-p", "--output-format", "stream-json", "--verbose"],
|
|
3575
|
+
claude: ["-p", "--output-format", "stream-json", "--verbose", "--include-partial-messages"],
|
|
3510
3576
|
gemini: ["-p", "--output-format", "stream-json"],
|
|
3511
3577
|
opencode: ["run"],
|
|
3512
3578
|
cursor: ["-p"],
|
|
@@ -3539,18 +3605,23 @@ function usageFrom(v) {
|
|
|
3539
3605
|
if (input === void 0 && output === void 0 && cache === void 0) return void 0;
|
|
3540
3606
|
return { inputTokens: input, outputTokens: output, cacheReadTokens: cache };
|
|
3541
3607
|
}
|
|
3542
|
-
function
|
|
3543
|
-
if (!message) return
|
|
3608
|
+
function messageContent(message) {
|
|
3609
|
+
if (!message) return {};
|
|
3544
3610
|
const content = message.content;
|
|
3545
|
-
if (typeof content === "string") return content;
|
|
3546
|
-
if (Array.isArray(content)) {
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
if (
|
|
3611
|
+
if (typeof content === "string") return { text: content };
|
|
3612
|
+
if (!Array.isArray(content)) return {};
|
|
3613
|
+
const parts = [];
|
|
3614
|
+
const tools = [];
|
|
3615
|
+
for (const block of content) {
|
|
3616
|
+
const b = asObj(block);
|
|
3617
|
+
if (!b) continue;
|
|
3618
|
+
if (typeof b.text === "string") parts.push(b.text);
|
|
3619
|
+
else if (b.type === "tool_use" && typeof b.name === "string") tools.push(b.name);
|
|
3552
3620
|
}
|
|
3553
|
-
return
|
|
3621
|
+
return {
|
|
3622
|
+
...parts.length > 0 ? { text: parts.join("") } : {},
|
|
3623
|
+
...tools.length > 0 ? { tools } : {}
|
|
3624
|
+
};
|
|
3554
3625
|
}
|
|
3555
3626
|
function parseStreamLine(line) {
|
|
3556
3627
|
const trimmed = line.trim();
|
|
@@ -3569,15 +3640,14 @@ function normalizeStreamEvent(raw) {
|
|
|
3569
3640
|
if (r.subtype === "init") {
|
|
3570
3641
|
return { kind: "init", sessionId: str(r.session_id), model: str(r.model) };
|
|
3571
3642
|
}
|
|
3572
|
-
return { kind: "other" };
|
|
3643
|
+
return { kind: "other", subtype: str(r.subtype) ?? "system" };
|
|
3573
3644
|
}
|
|
3574
3645
|
if (type === "assistant") {
|
|
3575
3646
|
const message = asObj(r.message);
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
usage: message ? usageFrom(message.usage) : void 0,
|
|
3647
|
+
const content = messageContent(message);
|
|
3648
|
+
const messageId = message ? str(message.id) : void 0;
|
|
3649
|
+
const usage = message ? usageFrom(message.usage) : void 0;
|
|
3650
|
+
const error2 = {
|
|
3581
3651
|
// An API error message is a diagnostic, not the answer — flag it so the
|
|
3582
3652
|
// caller never streams it as assistant text (optional-when-true keeps
|
|
3583
3653
|
// exact-shape consumers of the existing union intact).
|
|
@@ -3586,6 +3656,20 @@ function normalizeStreamEvent(raw) {
|
|
|
3586
3656
|
...typeof r.error === "string" ? { errorCategory: r.error } : {}
|
|
3587
3657
|
} : {}
|
|
3588
3658
|
};
|
|
3659
|
+
if (content.text !== void 0) {
|
|
3660
|
+
return {
|
|
3661
|
+
kind: "assistant",
|
|
3662
|
+
messageId,
|
|
3663
|
+
text: content.text,
|
|
3664
|
+
usage,
|
|
3665
|
+
...content.tools === void 0 ? {} : { tools: content.tools },
|
|
3666
|
+
...error2
|
|
3667
|
+
};
|
|
3668
|
+
}
|
|
3669
|
+
if (content.tools !== void 0) {
|
|
3670
|
+
return { kind: "tool", name: content.tools[0], messageId, usage };
|
|
3671
|
+
}
|
|
3672
|
+
return { kind: "assistant", messageId, usage, ...error2 };
|
|
3589
3673
|
}
|
|
3590
3674
|
if (type === "result") {
|
|
3591
3675
|
return {
|
|
@@ -3597,7 +3681,29 @@ function normalizeStreamEvent(raw) {
|
|
|
3597
3681
|
usage: usageFrom(r.usage)
|
|
3598
3682
|
};
|
|
3599
3683
|
}
|
|
3600
|
-
|
|
3684
|
+
if (type === "stream_event") {
|
|
3685
|
+
return normalizeStreamEventInner(asObj(r.event));
|
|
3686
|
+
}
|
|
3687
|
+
return { kind: "other", ...typeof type === "string" ? { subtype: type } : {} };
|
|
3688
|
+
}
|
|
3689
|
+
function normalizeStreamEventInner(inner) {
|
|
3690
|
+
if (!inner) return { kind: "other", subtype: "stream_event" };
|
|
3691
|
+
const innerType = str(inner.type);
|
|
3692
|
+
if (innerType === "content_block_start") {
|
|
3693
|
+
const block = asObj(inner.content_block);
|
|
3694
|
+
const name = block ? str(block.name) : void 0;
|
|
3695
|
+
if (block && str(block.type) === "tool_use" && name !== void 0) {
|
|
3696
|
+
return { kind: "tool", name };
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
if (innerType === "content_block_delta") {
|
|
3700
|
+
const delta = asObj(inner.delta);
|
|
3701
|
+
const text = delta ? str(delta.text) : void 0;
|
|
3702
|
+
if (delta && delta.type === "text_delta" && text !== void 0) {
|
|
3703
|
+
return { kind: "delta", text };
|
|
3704
|
+
}
|
|
3705
|
+
}
|
|
3706
|
+
return { kind: "other", subtype: innerType ?? "stream_event" };
|
|
3601
3707
|
}
|
|
3602
3708
|
var UsageReducer = class {
|
|
3603
3709
|
maxByMessage = /* @__PURE__ */ new Map();
|
|
@@ -3607,27 +3713,29 @@ var UsageReducer = class {
|
|
|
3607
3713
|
turns = 0;
|
|
3608
3714
|
/** Feed one normalized event into the reducer. */
|
|
3609
3715
|
add(event) {
|
|
3610
|
-
if (event.kind === "
|
|
3611
|
-
const key = event.messageId;
|
|
3612
|
-
const input = event.usage.inputTokens ?? 0;
|
|
3613
|
-
const output = event.usage.outputTokens ?? 0;
|
|
3614
|
-
const prev = this.maxByMessage.get(key);
|
|
3615
|
-
if (!prev) {
|
|
3616
|
-
this.maxByMessage.set(key, { input, output });
|
|
3617
|
-
this.input += input;
|
|
3618
|
-
this.output += output;
|
|
3619
|
-
} else {
|
|
3620
|
-
this.input += Math.max(0, input - prev.input);
|
|
3621
|
-
this.output += Math.max(0, output - prev.output);
|
|
3622
|
-
this.maxByMessage.set(key, {
|
|
3623
|
-
input: Math.max(prev.input, input),
|
|
3624
|
-
output: Math.max(prev.output, output)
|
|
3625
|
-
});
|
|
3626
|
-
}
|
|
3627
|
-
} else if (event.kind === "result") {
|
|
3716
|
+
if (event.kind === "result") {
|
|
3628
3717
|
if (event.totalCostUsd !== void 0) this.costUsd = event.totalCostUsd;
|
|
3629
3718
|
if (event.numTurns !== void 0) this.turns = event.numTurns;
|
|
3719
|
+
return;
|
|
3720
|
+
}
|
|
3721
|
+
const usage = event.kind === "assistant" || event.kind === "tool" ? event.usage : void 0;
|
|
3722
|
+
const key = event.kind === "assistant" || event.kind === "tool" ? event.messageId : void 0;
|
|
3723
|
+
if (!key || !usage) return;
|
|
3724
|
+
const input = usage.inputTokens ?? 0;
|
|
3725
|
+
const output = usage.outputTokens ?? 0;
|
|
3726
|
+
const prev = this.maxByMessage.get(key);
|
|
3727
|
+
if (!prev) {
|
|
3728
|
+
this.maxByMessage.set(key, { input, output });
|
|
3729
|
+
this.input += input;
|
|
3730
|
+
this.output += output;
|
|
3731
|
+
return;
|
|
3630
3732
|
}
|
|
3733
|
+
this.input += Math.max(0, input - prev.input);
|
|
3734
|
+
this.output += Math.max(0, output - prev.output);
|
|
3735
|
+
this.maxByMessage.set(key, {
|
|
3736
|
+
input: Math.max(prev.input, input),
|
|
3737
|
+
output: Math.max(prev.output, output)
|
|
3738
|
+
});
|
|
3631
3739
|
}
|
|
3632
3740
|
/** The current accumulator snapshot. */
|
|
3633
3741
|
snapshot() {
|
|
@@ -3639,6 +3747,12 @@ var UsageReducer = class {
|
|
|
3639
3747
|
};
|
|
3640
3748
|
}
|
|
3641
3749
|
};
|
|
3750
|
+
function signalChild(child, signal) {
|
|
3751
|
+
if (child.pid === void 0) return;
|
|
3752
|
+
child.kill(signal);
|
|
3753
|
+
}
|
|
3754
|
+
var BRIDGE_POLITE_RESEND_MS = 250;
|
|
3755
|
+
var BRIDGE_POLITE_WINDOW_MS = INTERRUPT_GRACE_MS;
|
|
3642
3756
|
function runHost(opts) {
|
|
3643
3757
|
const spec = resolveHostRun(opts.host, opts.customBinary);
|
|
3644
3758
|
if (!spec) {
|
|
@@ -3653,12 +3767,13 @@ function runHost(opts) {
|
|
|
3653
3767
|
false
|
|
3654
3768
|
);
|
|
3655
3769
|
}
|
|
3656
|
-
function spawnAndConsume(binary, args, opts, shellRun) {
|
|
3657
|
-
return new Promise((
|
|
3770
|
+
function spawnAndConsume(binary, args, opts, shellRun, bridged = false) {
|
|
3771
|
+
return new Promise((resolve2, reject) => {
|
|
3658
3772
|
const child = spawn2(binary, [...args], {
|
|
3659
3773
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3660
3774
|
...opts.env ? { env: opts.env } : {}
|
|
3661
3775
|
});
|
|
3776
|
+
opts.onChild?.(child);
|
|
3662
3777
|
const reducer = new UsageReducer();
|
|
3663
3778
|
let eventCount = 0;
|
|
3664
3779
|
let stderrBuf = "";
|
|
@@ -3666,10 +3781,40 @@ function spawnAndConsume(binary, args, opts, shellRun) {
|
|
|
3666
3781
|
let errorText;
|
|
3667
3782
|
let errorCategory;
|
|
3668
3783
|
let spawnError = null;
|
|
3784
|
+
const { signal } = opts;
|
|
3785
|
+
let resend;
|
|
3786
|
+
let offersLeft = Math.ceil(BRIDGE_POLITE_WINDOW_MS / BRIDGE_POLITE_RESEND_MS);
|
|
3787
|
+
const stopResend = () => {
|
|
3788
|
+
if (resend === void 0) return;
|
|
3789
|
+
clearTimeout(resend);
|
|
3790
|
+
resend = void 0;
|
|
3791
|
+
};
|
|
3792
|
+
const offerAgain = () => {
|
|
3793
|
+
if (offersLeft <= 0) return;
|
|
3794
|
+
offersLeft -= 1;
|
|
3795
|
+
resend = setTimeout(() => {
|
|
3796
|
+
resend = void 0;
|
|
3797
|
+
signalChild(child, "SIGTERM");
|
|
3798
|
+
offerAgain();
|
|
3799
|
+
}, BRIDGE_POLITE_RESEND_MS);
|
|
3800
|
+
};
|
|
3801
|
+
const onAbort = () => {
|
|
3802
|
+
signalChild(child, "SIGTERM");
|
|
3803
|
+
if (bridged) offerAgain();
|
|
3804
|
+
};
|
|
3805
|
+
if (signal) {
|
|
3806
|
+
if (signal.aborted) onAbort();
|
|
3807
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
3808
|
+
}
|
|
3809
|
+
const detachAbort = () => {
|
|
3810
|
+
signal?.removeEventListener("abort", onAbort);
|
|
3811
|
+
stopResend();
|
|
3812
|
+
};
|
|
3669
3813
|
child.on("error", (err) => {
|
|
3670
3814
|
spawnError = err;
|
|
3815
|
+
detachAbort();
|
|
3671
3816
|
if (!shellRun && err.code === "ENOENT") {
|
|
3672
|
-
void shellFallback(binary, args, opts,
|
|
3817
|
+
void shellFallback(binary, args, opts, resolve2, reject, err);
|
|
3673
3818
|
return;
|
|
3674
3819
|
}
|
|
3675
3820
|
reject(err);
|
|
@@ -3696,31 +3841,32 @@ function spawnAndConsume(binary, args, opts, shellRun) {
|
|
|
3696
3841
|
child.stderr.on("data", (chunk) => {
|
|
3697
3842
|
stderrBuf += chunk;
|
|
3698
3843
|
});
|
|
3699
|
-
child.on("close", (code,
|
|
3844
|
+
child.on("close", (code, signal2) => {
|
|
3845
|
+
detachAbort();
|
|
3700
3846
|
if (spawnError !== null) return;
|
|
3701
|
-
const killed = code === null &&
|
|
3702
|
-
|
|
3703
|
-
exitCode: killed ? 128 + (signalNumber(
|
|
3847
|
+
const killed = code === null && signal2 != null;
|
|
3848
|
+
resolve2({
|
|
3849
|
+
exitCode: killed ? 128 + (signalNumber(signal2) ?? 0) : code ?? 0,
|
|
3704
3850
|
usage: reducer.snapshot(),
|
|
3705
3851
|
eventCount,
|
|
3706
3852
|
stderr: stderrBuf,
|
|
3707
3853
|
isError: errored || killed,
|
|
3708
|
-
errorText: killed && errorText === void 0 ? `terminated by signal ${
|
|
3854
|
+
errorText: killed && errorText === void 0 ? `terminated by signal ${signal2}` : errorText,
|
|
3709
3855
|
errorCategory
|
|
3710
3856
|
});
|
|
3711
3857
|
});
|
|
3712
3858
|
});
|
|
3713
3859
|
}
|
|
3714
|
-
async function shellFallback(binary, args, opts,
|
|
3860
|
+
async function shellFallback(binary, args, opts, resolve2, reject, originalErr) {
|
|
3715
3861
|
try {
|
|
3716
3862
|
const res = await resolveCommandViaShell(binary, { env: opts.env ?? process.env });
|
|
3717
3863
|
if (res.kind === "path") {
|
|
3718
|
-
|
|
3864
|
+
resolve2(await spawnAndConsume(res.path, args, opts, true));
|
|
3719
3865
|
return;
|
|
3720
3866
|
}
|
|
3721
3867
|
if (res.kind === "alias" || res.kind === "function") {
|
|
3722
3868
|
const bridge = buildBridgeArgs(binary, args, res.shell);
|
|
3723
|
-
|
|
3869
|
+
resolve2(await spawnAndConsume(bridge.binary, bridge.args, opts, true, true));
|
|
3724
3870
|
return;
|
|
3725
3871
|
}
|
|
3726
3872
|
} catch {
|
|
@@ -3728,8 +3874,339 @@ async function shellFallback(binary, args, opts, resolve, reject, originalErr) {
|
|
|
3728
3874
|
reject(originalErr);
|
|
3729
3875
|
}
|
|
3730
3876
|
|
|
3877
|
+
// src/run-actions.ts
|
|
3878
|
+
import { writeFileSync as writeFileSync3 } from "fs";
|
|
3879
|
+
import { resolve } from "path";
|
|
3880
|
+
import { CommanderError } from "commander";
|
|
3881
|
+
var CAPTURE_EVENT = "capture";
|
|
3882
|
+
var RESEARCH_SOURCE = "noir run";
|
|
3883
|
+
var RESEARCH_TEXT_LIMIT = 220;
|
|
3884
|
+
var ACTION_LABEL = {
|
|
3885
|
+
memory: "Saving to memory",
|
|
3886
|
+
research: "Recording the finding",
|
|
3887
|
+
handoff: "Writing the handoff",
|
|
3888
|
+
resume: "Continuing the session",
|
|
3889
|
+
save: "Writing the file",
|
|
3890
|
+
dismiss: "Dismissing"
|
|
3891
|
+
};
|
|
3892
|
+
function postRunActions(input) {
|
|
3893
|
+
const answer = input.answer.trim();
|
|
3894
|
+
const canContinue = input.canContinue ?? (input.sessionId !== void 0 && input.resume !== void 0);
|
|
3895
|
+
const options = [];
|
|
3896
|
+
if (answer.length > 0) {
|
|
3897
|
+
options.push({
|
|
3898
|
+
value: "memory",
|
|
3899
|
+
label: "Save answer to memory",
|
|
3900
|
+
hint: "distil it into this project's memory (needs the daemon)"
|
|
3901
|
+
});
|
|
3902
|
+
options.push({
|
|
3903
|
+
value: "research",
|
|
3904
|
+
label: "Add as task research",
|
|
3905
|
+
hint: "record it as a finding on the active task"
|
|
3906
|
+
});
|
|
3907
|
+
}
|
|
3908
|
+
options.push({
|
|
3909
|
+
value: "handoff",
|
|
3910
|
+
label: "Write a handoff artifact",
|
|
3911
|
+
hint: "snapshot the task, the next step and a seed for your host"
|
|
3912
|
+
});
|
|
3913
|
+
if (canContinue) {
|
|
3914
|
+
options.push({
|
|
3915
|
+
value: "resume",
|
|
3916
|
+
label: "Continue this session",
|
|
3917
|
+
hint: `ask ${input.host} a follow-up in the same session`,
|
|
3918
|
+
needsValue: {
|
|
3919
|
+
label: "follow-up prompt",
|
|
3920
|
+
placeholder: "e.g. now write the tests for that"
|
|
3921
|
+
}
|
|
3922
|
+
});
|
|
3923
|
+
}
|
|
3924
|
+
if (answer.length > 0) {
|
|
3925
|
+
options.push({
|
|
3926
|
+
value: "save",
|
|
3927
|
+
label: "Save answer to a file",
|
|
3928
|
+
hint: "write the answer text to a path you choose",
|
|
3929
|
+
needsValue: { label: "file path", placeholder: "e.g. notes/answer.md" }
|
|
3930
|
+
});
|
|
3931
|
+
}
|
|
3932
|
+
options.push({
|
|
3933
|
+
value: "dismiss",
|
|
3934
|
+
label: "Dismiss",
|
|
3935
|
+
hint: "nothing further \u2014 the run is already complete"
|
|
3936
|
+
});
|
|
3937
|
+
return options;
|
|
3938
|
+
}
|
|
3939
|
+
async function performPostRunAction(choice, input, value) {
|
|
3940
|
+
const label = ACTION_LABEL[choice];
|
|
3941
|
+
try {
|
|
3942
|
+
switch (choice) {
|
|
3943
|
+
case "memory":
|
|
3944
|
+
await saveToMemory(input);
|
|
3945
|
+
return { ok: true, message: "" };
|
|
3946
|
+
case "research":
|
|
3947
|
+
await recordAsResearch(input);
|
|
3948
|
+
return { ok: true, message: "" };
|
|
3949
|
+
case "handoff":
|
|
3950
|
+
await writeHandoff(input);
|
|
3951
|
+
return { ok: true, message: "" };
|
|
3952
|
+
case "resume":
|
|
3953
|
+
return await continueSession(input, value);
|
|
3954
|
+
case "save":
|
|
3955
|
+
return saveAnswerToFile(input, value);
|
|
3956
|
+
case "dismiss":
|
|
3957
|
+
return { ok: true, message: "" };
|
|
3958
|
+
}
|
|
3959
|
+
} catch (err) {
|
|
3960
|
+
const quoted = err instanceof CommanderError || err instanceof NoirCliError ? void 0 : `${label}: ${err instanceof Error ? err.message : String(err)}`;
|
|
3961
|
+
return {
|
|
3962
|
+
ok: false,
|
|
3963
|
+
message: `${label} did not complete \u2014 the run's result stands.`,
|
|
3964
|
+
...quoted === void 0 ? {} : { detail: quoted }
|
|
3965
|
+
};
|
|
3966
|
+
}
|
|
3967
|
+
}
|
|
3968
|
+
async function offerPostRunActions(input) {
|
|
3969
|
+
if (!isInteractive(input.opts)) return;
|
|
3970
|
+
const options = postRunActions(input);
|
|
3971
|
+
if (options.length <= 1) return;
|
|
3972
|
+
const menu = await openMenu(options);
|
|
3973
|
+
if (menu === void 0) return;
|
|
3974
|
+
const chosen = options.find((o) => o.value === menu.choice);
|
|
3975
|
+
if (chosen === void 0) return;
|
|
3976
|
+
const value = chosen.needsValue === void 0 ? void 0 : await askForValue(menu.clack, chosen, input);
|
|
3977
|
+
if (chosen.needsValue !== void 0 && value === void 0) return;
|
|
3978
|
+
report(await performPostRunAction(menu.choice, input, value), input.opts);
|
|
3979
|
+
}
|
|
3980
|
+
function report(outcome, opts) {
|
|
3981
|
+
const caution = outcome.tone === "caution" || !outcome.ok && outcome.tone !== "confirm";
|
|
3982
|
+
if (outcome.detail !== void 0) warn(outcome.detail, opts);
|
|
3983
|
+
if (outcome.message.length === 0) return;
|
|
3984
|
+
if (caution) warn(outcome.message, opts);
|
|
3985
|
+
else success(outcome.message, opts);
|
|
3986
|
+
}
|
|
3987
|
+
async function openMenu(options) {
|
|
3988
|
+
try {
|
|
3989
|
+
const clack = await import("@clack/prompts");
|
|
3990
|
+
const choice = await clack.select({
|
|
3991
|
+
message: "The run finished. What next?",
|
|
3992
|
+
initialValue: "dismiss",
|
|
3993
|
+
// Spread because the prompt library takes a mutable list; the rows
|
|
3994
|
+
// themselves are the shared descriptors and are never rewritten.
|
|
3995
|
+
options: [...options]
|
|
3996
|
+
});
|
|
3997
|
+
if (clack.isCancel(choice)) return void 0;
|
|
3998
|
+
return { clack, choice };
|
|
3999
|
+
} catch {
|
|
4000
|
+
return void 0;
|
|
4001
|
+
}
|
|
4002
|
+
}
|
|
4003
|
+
async function askForValue(clack, option, input) {
|
|
4004
|
+
const needs = option.needsValue;
|
|
4005
|
+
if (needs === void 0) return void 0;
|
|
4006
|
+
const message = option.value === "save" ? `Write the answer to a file (the raw transcript is at ${input.transcript})` : `What should ${input.host} do next?`;
|
|
4007
|
+
try {
|
|
4008
|
+
const written = await clack.text({ message, placeholder: needs.placeholder });
|
|
4009
|
+
if (clack.isCancel(written)) return void 0;
|
|
4010
|
+
return String(written ?? "");
|
|
4011
|
+
} catch {
|
|
4012
|
+
return void 0;
|
|
4013
|
+
}
|
|
4014
|
+
}
|
|
4015
|
+
async function saveToMemory(input) {
|
|
4016
|
+
await memoryCapture({
|
|
4017
|
+
...input.opts,
|
|
4018
|
+
content: input.answer,
|
|
4019
|
+
eventType: CAPTURE_EVENT
|
|
4020
|
+
});
|
|
4021
|
+
}
|
|
4022
|
+
async function recordAsResearch(input) {
|
|
4023
|
+
await taskResearchRecord({
|
|
4024
|
+
...input.opts,
|
|
4025
|
+
type: "discovery",
|
|
4026
|
+
text: researchText(input.answer),
|
|
4027
|
+
source: RESEARCH_SOURCE
|
|
4028
|
+
});
|
|
4029
|
+
}
|
|
4030
|
+
function researchText(answer) {
|
|
4031
|
+
const condensed = answer.replace(/\s+/g, " ").trim();
|
|
4032
|
+
if (condensed.length <= RESEARCH_TEXT_LIMIT) return condensed;
|
|
4033
|
+
return `${condensed.slice(0, RESEARCH_TEXT_LIMIT - 1)}\u2026`;
|
|
4034
|
+
}
|
|
4035
|
+
async function writeHandoff(input) {
|
|
4036
|
+
await handoff({ ...input.opts, write: true });
|
|
4037
|
+
}
|
|
4038
|
+
async function continueSession(input, value) {
|
|
4039
|
+
const { sessionId, resume } = input;
|
|
4040
|
+
if (sessionId === void 0 || resume === void 0) return { ok: true, message: "" };
|
|
4041
|
+
const prompt = (value ?? "").trim();
|
|
4042
|
+
if (prompt.length === 0) {
|
|
4043
|
+
return {
|
|
4044
|
+
ok: true,
|
|
4045
|
+
tone: "caution",
|
|
4046
|
+
message: "No prompt given \u2014 the session was not continued."
|
|
4047
|
+
};
|
|
4048
|
+
}
|
|
4049
|
+
await resume(prompt, sessionId);
|
|
4050
|
+
return { ok: true, message: "" };
|
|
4051
|
+
}
|
|
4052
|
+
function saveAnswerToFile(input, value) {
|
|
4053
|
+
const fallback = `noir-run-${stamp2()}.md`;
|
|
4054
|
+
const chosen = (value ?? "").trim();
|
|
4055
|
+
const target = resolve(chosen.length > 0 ? chosen : fallback);
|
|
4056
|
+
const body = input.answer.endsWith("\n") ? input.answer : `${input.answer}
|
|
4057
|
+
`;
|
|
4058
|
+
try {
|
|
4059
|
+
writeFileSync3(target, body, { mode: 384 });
|
|
4060
|
+
} catch (err) {
|
|
4061
|
+
return {
|
|
4062
|
+
ok: false,
|
|
4063
|
+
tone: "caution",
|
|
4064
|
+
message: `could not write ${target}: ${err instanceof Error ? err.message : String(err)}`
|
|
4065
|
+
};
|
|
4066
|
+
}
|
|
4067
|
+
return { ok: true, message: `Answer written to ${target}` };
|
|
4068
|
+
}
|
|
4069
|
+
function stamp2() {
|
|
4070
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4071
|
+
}
|
|
4072
|
+
|
|
4073
|
+
// src/run-interrupt.ts
|
|
4074
|
+
import { constants as constants2 } from "os";
|
|
4075
|
+
var INTERRUPT_SIGNALS = ["SIGINT", "SIGTERM"];
|
|
4076
|
+
function exitCodeForSignal(signal) {
|
|
4077
|
+
return 128 + (constants2.signals[signal] ?? 0);
|
|
4078
|
+
}
|
|
4079
|
+
function interruptedNotice(transcript) {
|
|
4080
|
+
return `interrupted \xB7 transcript: ${transcript}`;
|
|
4081
|
+
}
|
|
4082
|
+
var RunInterrupt = class {
|
|
4083
|
+
graceMs;
|
|
4084
|
+
exitNow;
|
|
4085
|
+
controller = new AbortController();
|
|
4086
|
+
/** The live child, or undefined between spawns. */
|
|
4087
|
+
child;
|
|
4088
|
+
/** The child the ladder already ran for — a repeat call is not a new ladder. */
|
|
4089
|
+
stopping;
|
|
4090
|
+
grace;
|
|
4091
|
+
signalName;
|
|
4092
|
+
stopRequested = false;
|
|
4093
|
+
forced = false;
|
|
4094
|
+
watching = false;
|
|
4095
|
+
/** The installed listeners, keyed by the signal each answers. */
|
|
4096
|
+
handlers = /* @__PURE__ */ new Map();
|
|
4097
|
+
constructor(opts = {}) {
|
|
4098
|
+
this.graceMs = opts.graceMs ?? INTERRUPT_GRACE_MS;
|
|
4099
|
+
this.exitNow = opts.exitNow ?? ((code) => {
|
|
4100
|
+
if (this.child !== void 0) signalChild(this.child, "SIGKILL");
|
|
4101
|
+
process.exit(code);
|
|
4102
|
+
});
|
|
4103
|
+
}
|
|
4104
|
+
/**
|
|
4105
|
+
* The cancel signal for the spawn. Aborting it is what a caller holding only
|
|
4106
|
+
* the signal can do; the ladder itself does not depend on it, which matters
|
|
4107
|
+
* because the child a run ends up driving is not always the first one spawned.
|
|
4108
|
+
*/
|
|
4109
|
+
get signal() {
|
|
4110
|
+
return this.controller.signal;
|
|
4111
|
+
}
|
|
4112
|
+
/** The signal that stopped the run, or undefined when nothing did. */
|
|
4113
|
+
get interruptedBy() {
|
|
4114
|
+
return this.signalName;
|
|
4115
|
+
}
|
|
4116
|
+
/** True once a child had to be forced — it did not honour the polite signal. */
|
|
4117
|
+
get escalated() {
|
|
4118
|
+
return this.forced;
|
|
4119
|
+
}
|
|
4120
|
+
/**
|
|
4121
|
+
* Hand over the child a spawn produced. Called for every spawn of one run: a
|
|
4122
|
+
* command resolved through the shell bridge is its own child, and a stop that
|
|
4123
|
+
* arrived before that child existed has to land on it too.
|
|
4124
|
+
*/
|
|
4125
|
+
track(child) {
|
|
4126
|
+
this.child = child;
|
|
4127
|
+
child.once("exit", () => this.forget(child));
|
|
4128
|
+
if (this.stopRequested) this.terminate();
|
|
4129
|
+
}
|
|
4130
|
+
/**
|
|
4131
|
+
* Stop the host. The polite signal goes to the child itself, and a child still
|
|
4132
|
+
* alive after the grace is forced. Repeat calls are the same call: the ladder
|
|
4133
|
+
* runs once per child, so a keystroke held down is not a stream of signals.
|
|
4134
|
+
*/
|
|
4135
|
+
terminate() {
|
|
4136
|
+
this.stopRequested = true;
|
|
4137
|
+
if (!this.controller.signal.aborted) this.controller.abort();
|
|
4138
|
+
const child = this.child;
|
|
4139
|
+
if (child === void 0 || child.pid === void 0 || child === this.stopping) return;
|
|
4140
|
+
this.stopping = child;
|
|
4141
|
+
this.clearGrace();
|
|
4142
|
+
signalChild(child, "SIGTERM");
|
|
4143
|
+
this.grace = setTimeout(() => {
|
|
4144
|
+
this.grace = void 0;
|
|
4145
|
+
if (this.child !== child) return;
|
|
4146
|
+
this.forced = true;
|
|
4147
|
+
signalChild(child, "SIGKILL");
|
|
4148
|
+
}, this.graceMs);
|
|
4149
|
+
}
|
|
4150
|
+
/**
|
|
4151
|
+
* Skip the grace and force the host now. The "you asked twice" path: the
|
|
4152
|
+
* terminal leaves at once on a second signal, and the run screen forces and
|
|
4153
|
+
* returns to the dashboard rather than holding a keystroke while the grace
|
|
4154
|
+
* runs out. Does nothing when no stop is in flight.
|
|
4155
|
+
*/
|
|
4156
|
+
forceNow() {
|
|
4157
|
+
const child = this.child;
|
|
4158
|
+
if (child === void 0 || child.pid === void 0 || child !== this.stopping) return false;
|
|
4159
|
+
this.forced = true;
|
|
4160
|
+
this.clearGrace();
|
|
4161
|
+
signalChild(child, "SIGKILL");
|
|
4162
|
+
return true;
|
|
4163
|
+
}
|
|
4164
|
+
/**
|
|
4165
|
+
* Install the terminal interrupt handlers. Only a run installs these, and only
|
|
4166
|
+
* for as long as it is driving the host: the process's own handling has to
|
|
4167
|
+
* come back afterwards, or a later command inherits an interrupt contract with
|
|
4168
|
+
* nothing left to interrupt.
|
|
4169
|
+
*/
|
|
4170
|
+
watch() {
|
|
4171
|
+
if (this.watching) return;
|
|
4172
|
+
this.watching = true;
|
|
4173
|
+
for (const signal of INTERRUPT_SIGNALS) {
|
|
4174
|
+
const listener = () => this.onSignal(signal);
|
|
4175
|
+
this.handlers.set(signal, listener);
|
|
4176
|
+
process.on(signal, listener);
|
|
4177
|
+
}
|
|
4178
|
+
}
|
|
4179
|
+
/** Put the process's own handling back, whatever the run left behind. */
|
|
4180
|
+
unwatch() {
|
|
4181
|
+
if (!this.watching) return;
|
|
4182
|
+
this.watching = false;
|
|
4183
|
+
for (const [signal, listener] of this.handlers) process.off(signal, listener);
|
|
4184
|
+
this.handlers.clear();
|
|
4185
|
+
}
|
|
4186
|
+
onSignal(signal) {
|
|
4187
|
+
if (this.signalName !== void 0) {
|
|
4188
|
+
this.exitNow(exitCodeForSignal(signal));
|
|
4189
|
+
return;
|
|
4190
|
+
}
|
|
4191
|
+
this.signalName = signal;
|
|
4192
|
+
this.terminate();
|
|
4193
|
+
}
|
|
4194
|
+
/** The child is gone: stop tracking it and disarm its ladder. */
|
|
4195
|
+
forget(child) {
|
|
4196
|
+
if (this.child !== child) return;
|
|
4197
|
+
this.child = void 0;
|
|
4198
|
+
this.stopping = void 0;
|
|
4199
|
+
this.clearGrace();
|
|
4200
|
+
}
|
|
4201
|
+
clearGrace() {
|
|
4202
|
+
if (this.grace === void 0) return;
|
|
4203
|
+
clearTimeout(this.grace);
|
|
4204
|
+
this.grace = void 0;
|
|
4205
|
+
}
|
|
4206
|
+
};
|
|
4207
|
+
|
|
3731
4208
|
// src/run-profiles.ts
|
|
3732
|
-
import { loadProjectInfo as loadProjectInfo9 } from "@noir-ai/core";
|
|
4209
|
+
import { isDeniedEnvKey, loadProjectInfo as loadProjectInfo9 } from "@noir-ai/core";
|
|
3733
4210
|
function loadRunConfig(root) {
|
|
3734
4211
|
try {
|
|
3735
4212
|
return loadProjectInfo9(root).config;
|
|
@@ -3764,6 +4241,14 @@ function resolveRunProfile(requested, config, env2) {
|
|
|
3764
4241
|
envExpanded[k] = v === null ? void 0 : expandEnvVars(v, env2);
|
|
3765
4242
|
}
|
|
3766
4243
|
}
|
|
4244
|
+
for (const key of Object.keys(envExpanded)) {
|
|
4245
|
+
if (isDeniedEnvKey(key)) {
|
|
4246
|
+
return {
|
|
4247
|
+
ok: false,
|
|
4248
|
+
message: `run profile "${name}" refuses process-injection key "${key}" \u2014 a profile env block in .noir/config.yml must not set ${key} (it can inject into the spawned host process). Remove "${key}" from the profile.`
|
|
4249
|
+
};
|
|
4250
|
+
}
|
|
4251
|
+
}
|
|
3767
4252
|
return {
|
|
3768
4253
|
ok: true,
|
|
3769
4254
|
profile: { binary: profile.binary, env: envExpanded, args: profile.args, profileName: name }
|
|
@@ -3779,6 +4264,183 @@ function listProfiles(config) {
|
|
|
3779
4264
|
}));
|
|
3780
4265
|
}
|
|
3781
4266
|
|
|
4267
|
+
// src/run-status.ts
|
|
4268
|
+
var HOST_STDERR_TAIL_LINES = 20;
|
|
4269
|
+
function humanizeElapsed(ms) {
|
|
4270
|
+
const total = Math.max(0, Math.floor(ms / 1e3));
|
|
4271
|
+
const seconds = total % 60;
|
|
4272
|
+
const minutes = Math.floor(total / 60) % 60;
|
|
4273
|
+
const hours = Math.floor(total / 3600);
|
|
4274
|
+
if (hours > 0) return `${hours}h ${String(minutes).padStart(2, "0")}m`;
|
|
4275
|
+
if (minutes > 0) return `${minutes}m ${String(seconds).padStart(2, "0")}s`;
|
|
4276
|
+
return `${seconds}s`;
|
|
4277
|
+
}
|
|
4278
|
+
var RunStatusLine = class {
|
|
4279
|
+
host;
|
|
4280
|
+
enabled;
|
|
4281
|
+
animated;
|
|
4282
|
+
sharesCursor;
|
|
4283
|
+
write;
|
|
4284
|
+
now;
|
|
4285
|
+
/**
|
|
4286
|
+
* Running token totals for the live line. This is a SECOND accumulator: the
|
|
4287
|
+
* snapshot the run reports at the end is reduced independently from the full
|
|
4288
|
+
* event stream, and both apply the same `max usage per message id` rule, so
|
|
4289
|
+
* the number on screen and the number in the summary agree.
|
|
4290
|
+
*/
|
|
4291
|
+
usage = new UsageReducer();
|
|
4292
|
+
startedAt;
|
|
4293
|
+
model;
|
|
4294
|
+
streaming = false;
|
|
4295
|
+
begun = false;
|
|
4296
|
+
ended = false;
|
|
4297
|
+
/** True while the status text is sitting on the row the cursor is currently at. */
|
|
4298
|
+
onRow = false;
|
|
4299
|
+
/** True when the last stdout write ended a line — the only safe moment to move the cursor. */
|
|
4300
|
+
stdoutAtRowStart = true;
|
|
4301
|
+
/** The text currently on the row (empty when the row is not ours). */
|
|
4302
|
+
rendered = "";
|
|
4303
|
+
constructor(opts) {
|
|
4304
|
+
this.host = opts.host;
|
|
4305
|
+
this.enabled = opts.json !== true && opts.quiet !== true;
|
|
4306
|
+
this.animated = this.enabled && opts.stderrIsTty === true;
|
|
4307
|
+
this.sharesCursor = opts.stdoutSharesCursor !== false;
|
|
4308
|
+
this.write = opts.write ?? ((chunk) => void process.stderr.write(chunk));
|
|
4309
|
+
this.now = opts.now ?? (() => performance.now());
|
|
4310
|
+
this.startedAt = this.now();
|
|
4311
|
+
}
|
|
4312
|
+
/**
|
|
4313
|
+
* Claim the row: show the seeded "waiting" state immediately, before the host
|
|
4314
|
+
* has said anything, so the spawn itself is never blank. Calling this twice
|
|
4315
|
+
* is a no-op.
|
|
4316
|
+
*/
|
|
4317
|
+
begin() {
|
|
4318
|
+
if (!this.enabled || this.begun) return;
|
|
4319
|
+
this.begun = true;
|
|
4320
|
+
this.startedAt = this.now();
|
|
4321
|
+
if (this.animated) {
|
|
4322
|
+
this.draw();
|
|
4323
|
+
return;
|
|
4324
|
+
}
|
|
4325
|
+
this.write(`${this.render()}
|
|
4326
|
+
`);
|
|
4327
|
+
}
|
|
4328
|
+
/** Feed one normalized host event. Anything unrenderable is ignored. */
|
|
4329
|
+
event(e) {
|
|
4330
|
+
if (!this.enabled || this.ended || !this.begun) return;
|
|
4331
|
+
if (e.kind === "result") {
|
|
4332
|
+
this.end();
|
|
4333
|
+
return;
|
|
4334
|
+
}
|
|
4335
|
+
if (e.kind === "init") {
|
|
4336
|
+
if (e.model !== void 0) this.model = e.model;
|
|
4337
|
+
} else if (e.kind === "assistant" || e.kind === "tool") {
|
|
4338
|
+
this.streaming = true;
|
|
4339
|
+
this.usage.add(e);
|
|
4340
|
+
} else if (e.kind === "delta") {
|
|
4341
|
+
this.streaming = true;
|
|
4342
|
+
} else {
|
|
4343
|
+
return;
|
|
4344
|
+
}
|
|
4345
|
+
this.draw();
|
|
4346
|
+
}
|
|
4347
|
+
/**
|
|
4348
|
+
* Announce the chunk the caller is about to write to STDOUT — before writing
|
|
4349
|
+
* it. Warning the line first lets it finish its row: the answer would
|
|
4350
|
+
* otherwise start glued to the tail of the status text. It also records
|
|
4351
|
+
* whether that write ended a line, which is the only moment the cursor can be
|
|
4352
|
+
* moved without landing inside the answer.
|
|
4353
|
+
*/
|
|
4354
|
+
beforeStdout(chunk) {
|
|
4355
|
+
if (chunk.length === 0) return;
|
|
4356
|
+
if (this.animated && this.sharesCursor && this.onRow) {
|
|
4357
|
+
this.write("\n");
|
|
4358
|
+
this.onRow = false;
|
|
4359
|
+
}
|
|
4360
|
+
this.stdoutAtRowStart = chunk.endsWith("\n");
|
|
4361
|
+
}
|
|
4362
|
+
/**
|
|
4363
|
+
* Finish the row the host's answer left open. A failure message printed onto
|
|
4364
|
+
* a half-written answer reads as part of the answer, so a caller about to
|
|
4365
|
+
* report a failure asks for the row to be terminated first. A no-op unless
|
|
4366
|
+
* this line is drawing on a terminal stdout also writes to and the answer
|
|
4367
|
+
* stopped mid-line — the only state where the cursor is inside the answer.
|
|
4368
|
+
*/
|
|
4369
|
+
finishRow() {
|
|
4370
|
+
if (!this.animated || !this.sharesCursor || this.stdoutAtRowStart) return;
|
|
4371
|
+
this.write("\n");
|
|
4372
|
+
this.stdoutAtRowStart = true;
|
|
4373
|
+
}
|
|
4374
|
+
/**
|
|
4375
|
+
* Close the line out. On a terminal the line is erased so the run summary
|
|
4376
|
+
* takes its place — unless the answer has since moved the cursor off it, in
|
|
4377
|
+
* which case the text stays as the record of how long the wait was. On a pipe
|
|
4378
|
+
* it becomes the final marker. Safe to call twice.
|
|
4379
|
+
*/
|
|
4380
|
+
end() {
|
|
4381
|
+
if (!this.enabled || this.ended) return;
|
|
4382
|
+
this.ended = true;
|
|
4383
|
+
if (!this.animated) {
|
|
4384
|
+
this.write(`${this.render(true)}
|
|
4385
|
+
`);
|
|
4386
|
+
return;
|
|
4387
|
+
}
|
|
4388
|
+
if (!this.onRow || !this.cursorFree()) return;
|
|
4389
|
+
this.write("\r\x1B[K");
|
|
4390
|
+
this.rendered = "";
|
|
4391
|
+
this.onRow = false;
|
|
4392
|
+
}
|
|
4393
|
+
/** Rewrite the current row in place, skipping no-op rewrites and unsafe moments. */
|
|
4394
|
+
draw() {
|
|
4395
|
+
if (!this.animated || !this.cursorFree()) return;
|
|
4396
|
+
const text = this.render();
|
|
4397
|
+
if (text === this.rendered) return;
|
|
4398
|
+
this.write(`\r\x1B[K${text}`);
|
|
4399
|
+
this.rendered = text;
|
|
4400
|
+
this.onRow = true;
|
|
4401
|
+
}
|
|
4402
|
+
/**
|
|
4403
|
+
* Whether the cursor is at the start of a row nobody else is using. On stdout
|
|
4404
|
+
* that is not this terminal there is no shared cursor to disturb; when it is,
|
|
4405
|
+
* only a write that ended a line leaves one free.
|
|
4406
|
+
*/
|
|
4407
|
+
cursorFree() {
|
|
4408
|
+
return !this.sharesCursor || this.stdoutAtRowStart;
|
|
4409
|
+
}
|
|
4410
|
+
/**
|
|
4411
|
+
* `final` marks the line the run ends on: a run that never got past the spawn
|
|
4412
|
+
* is over, so saying it is still waiting — directly above whatever the run
|
|
4413
|
+
* reported — would be a lie.
|
|
4414
|
+
*/
|
|
4415
|
+
render(final = false) {
|
|
4416
|
+
const parts = [];
|
|
4417
|
+
if (this.streaming) {
|
|
4418
|
+
const u = this.usage.snapshot();
|
|
4419
|
+
parts.push(
|
|
4420
|
+
this.model ?? this.host,
|
|
4421
|
+
this.elapsed(),
|
|
4422
|
+
`\u2193${u.inputTokens.toLocaleString()}\u2191${u.outputTokens.toLocaleString()} tokens`
|
|
4423
|
+
);
|
|
4424
|
+
return `\u25CF ${parts.join(" \xB7 ")}`;
|
|
4425
|
+
}
|
|
4426
|
+
if (this.model !== void 0) return `\u25B6 ${[this.host, this.model, this.elapsed()].join(" \xB7 ")}`;
|
|
4427
|
+
if (final) return `\u25B6 ${this.host} \xB7 no output after ${this.elapsed()}`;
|
|
4428
|
+
return `\u25B6 ${this.host} \xB7 waiting for first event\u2026`;
|
|
4429
|
+
}
|
|
4430
|
+
elapsed() {
|
|
4431
|
+
return humanizeElapsed(this.now() - this.startedAt);
|
|
4432
|
+
}
|
|
4433
|
+
};
|
|
4434
|
+
function hostStderrTail(stderr, maxLines = HOST_STDERR_TAIL_LINES) {
|
|
4435
|
+
if (maxLines <= 0) return "";
|
|
4436
|
+
const lines = stderr.split("\n").map((line) => line.trimEnd());
|
|
4437
|
+
while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
4438
|
+
if (lines.length === 0) return "";
|
|
4439
|
+
if (lines.length <= maxLines) return lines.join("\n");
|
|
4440
|
+
const omitted = lines.length - maxLines;
|
|
4441
|
+
return [`\u2026 (${omitted} earlier lines omitted)`, ...lines.slice(-maxLines)].join("\n");
|
|
4442
|
+
}
|
|
4443
|
+
|
|
3782
4444
|
// src/commands/run.ts
|
|
3783
4445
|
var CREDENTIAL_ENV_VARS = [
|
|
3784
4446
|
"ANTHROPIC_API_KEY",
|
|
@@ -3804,13 +4466,24 @@ function formatUsage(u) {
|
|
|
3804
4466
|
const turns = u.numTurns > 0 ? ` \xB7 ${u.numTurns} turns` : "";
|
|
3805
4467
|
return `${u.inputTokens.toLocaleString()} in / ${u.outputTokens.toLocaleString()} out${cost}${turns}`;
|
|
3806
4468
|
}
|
|
3807
|
-
function
|
|
3808
|
-
if (opts.json === true) return;
|
|
4469
|
+
function answerText(event) {
|
|
3809
4470
|
if (event.kind === "assistant" && event.text && event.text.length > 0 && event.isError !== true) {
|
|
3810
|
-
|
|
4471
|
+
return event.text;
|
|
4472
|
+
}
|
|
4473
|
+
return void 0;
|
|
4474
|
+
}
|
|
4475
|
+
function streamEvent(event, opts, status2) {
|
|
4476
|
+
if (opts.json === true) return;
|
|
4477
|
+
const text = answerText(event);
|
|
4478
|
+
if (text !== void 0) {
|
|
4479
|
+
status2.beforeStdout(text);
|
|
4480
|
+
process.stdout.write(text);
|
|
3811
4481
|
}
|
|
3812
4482
|
}
|
|
3813
4483
|
async function run(prompt, opts) {
|
|
4484
|
+
await runOnce(prompt, opts, void 0);
|
|
4485
|
+
}
|
|
4486
|
+
async function runOnce(prompt, opts, resumeSessionId) {
|
|
3814
4487
|
const host = opts.host ?? "claude";
|
|
3815
4488
|
if (!SUPPORTED_HOSTS.includes(host)) {
|
|
3816
4489
|
fail(EXIT.USAGE, `unknown host '${host}' (supported: ${SUPPORTED_HOSTS.join(", ")})`, opts);
|
|
@@ -3829,7 +4502,7 @@ async function run(prompt, opts) {
|
|
|
3829
4502
|
if (!resolved.ok) fail(EXIT.USAGE, resolved.message, opts);
|
|
3830
4503
|
const profile = resolved.profile;
|
|
3831
4504
|
const customBinary = opts.command ?? profile.binary;
|
|
3832
|
-
const extraArgs = profile.args;
|
|
4505
|
+
const extraArgs = resumeSessionId === void 0 ? profile.args : [...profile.args ?? [], "--resume", resumeSessionId];
|
|
3833
4506
|
const env2 = profile.env ? mergeEnv(process.env, profile.env) : void 0;
|
|
3834
4507
|
if (!existsSync3(join4(root, NOIR_DIR2, ".env"))) {
|
|
3835
4508
|
log(
|
|
@@ -3838,6 +4511,37 @@ async function run(prompt, opts) {
|
|
|
3838
4511
|
);
|
|
3839
4512
|
}
|
|
3840
4513
|
const transcriptLines = [];
|
|
4514
|
+
const answer = [];
|
|
4515
|
+
let sessionId;
|
|
4516
|
+
const binary = customBinary && customBinary.length > 0 ? customBinary : host;
|
|
4517
|
+
const status2 = new RunStatusLine({
|
|
4518
|
+
host: binary,
|
|
4519
|
+
json: opts.json,
|
|
4520
|
+
quiet: opts.quiet,
|
|
4521
|
+
stderrIsTty: process.stderr.isTTY === true,
|
|
4522
|
+
stdoutSharesCursor: process.stdout.isTTY === true
|
|
4523
|
+
});
|
|
4524
|
+
status2.begin();
|
|
4525
|
+
let verdictWritten = false;
|
|
4526
|
+
const claimVerdict = () => {
|
|
4527
|
+
if (verdictWritten) return false;
|
|
4528
|
+
verdictWritten = true;
|
|
4529
|
+
return true;
|
|
4530
|
+
};
|
|
4531
|
+
const interrupt = new RunInterrupt({
|
|
4532
|
+
// A second interrupt leaves at once, and leaves the same verdict behind as
|
|
4533
|
+
// the first: the run owes its consumer an envelope under --json, and this is
|
|
4534
|
+
// the one path where `fail()` cannot write it (there is no caller left to
|
|
4535
|
+
// catch its throw). The child is forced first, so leaving does not leave a
|
|
4536
|
+
// host behind.
|
|
4537
|
+
exitNow: (code) => {
|
|
4538
|
+
interrupt.forceNow();
|
|
4539
|
+
if (claimVerdict()) {
|
|
4540
|
+
failAndExit(code, interruptedNotice(safeTranscript(host, transcriptLines)), opts);
|
|
4541
|
+
}
|
|
4542
|
+
}
|
|
4543
|
+
});
|
|
4544
|
+
interrupt.watch();
|
|
3841
4545
|
let result;
|
|
3842
4546
|
try {
|
|
3843
4547
|
result = await runHost({
|
|
@@ -3847,28 +4551,61 @@ async function run(prompt, opts) {
|
|
|
3847
4551
|
extraArgs,
|
|
3848
4552
|
env: env2,
|
|
3849
4553
|
onLine: (line) => transcriptLines.push(line),
|
|
3850
|
-
onEvent: (event) =>
|
|
4554
|
+
onEvent: (event) => {
|
|
4555
|
+
if (event.kind === "init" && sessionId === void 0) sessionId = event.sessionId;
|
|
4556
|
+
status2.event(event);
|
|
4557
|
+
streamEvent(event, opts, status2);
|
|
4558
|
+
const text = answerText(event);
|
|
4559
|
+
if (text !== void 0) answer.push(text);
|
|
4560
|
+
},
|
|
4561
|
+
signal: interrupt.signal,
|
|
4562
|
+
onChild: (child) => interrupt.track(child)
|
|
3851
4563
|
});
|
|
3852
4564
|
} catch (err) {
|
|
3853
|
-
|
|
4565
|
+
status2.end();
|
|
4566
|
+
status2.finishRow();
|
|
4567
|
+
if (interrupt.interruptedBy !== void 0) {
|
|
4568
|
+
if (claimVerdict()) {
|
|
4569
|
+
failInterrupted(interrupt.interruptedBy, host, transcriptLines, opts);
|
|
4570
|
+
}
|
|
4571
|
+
return;
|
|
4572
|
+
}
|
|
3854
4573
|
const detail = err instanceof Error ? err.message : String(err);
|
|
3855
4574
|
const enoent = err?.code === "ENOENT";
|
|
3856
4575
|
const guidance = enoent ? ` No executable '${binary}' was found. Shell aliases and functions (e.g. from .zshrc) are invisible to noir \u2014 use an executable on PATH, an absolute path, or a launcher script such as ~/.local/bin/${binary}.` : "";
|
|
3857
4576
|
const subject = customBinary && customBinary.length > 0 ? `custom command '${customBinary}'` : `host '${host}'`;
|
|
3858
4577
|
fail(EXIT.ERROR, `failed to run ${subject}: ${detail}.${guidance}`, opts);
|
|
4578
|
+
} finally {
|
|
4579
|
+
interrupt.unwatch();
|
|
4580
|
+
}
|
|
4581
|
+
if (interrupt.interruptedBy !== void 0) {
|
|
4582
|
+
status2.end();
|
|
4583
|
+
status2.finishRow();
|
|
4584
|
+
if (claimVerdict()) {
|
|
4585
|
+
failInterrupted(interrupt.interruptedBy, host, transcriptLines, opts);
|
|
4586
|
+
}
|
|
4587
|
+
return;
|
|
3859
4588
|
}
|
|
3860
|
-
const transcript =
|
|
4589
|
+
const transcript = safeTranscript(host, transcriptLines);
|
|
3861
4590
|
const failed = result.exitCode !== 0 || result.isError;
|
|
3862
4591
|
if (failed) {
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
const
|
|
4592
|
+
status2.end();
|
|
4593
|
+
status2.finishRow();
|
|
4594
|
+
const stderrTail = hostStderrTail(result.stderr);
|
|
4595
|
+
const reason = result.errorText && result.errorText.trim().length > 0 ? result.errorText.trim() : `exit code ${result.exitCode}`;
|
|
4596
|
+
const isAuth = result.errorCategory === "authentication_failed" || result.errorCategory === "oauth_org_not_allowed" || /not logged|login|authenticate|invalid api key/i.test(`${reason}
|
|
4597
|
+
${stderrTail}`);
|
|
3866
4598
|
let message = `host '${binary}' failed (exit ${result.exitCode}): ${reason}`;
|
|
3867
4599
|
if (isAuth) {
|
|
3868
4600
|
message += ` Open a terminal and run \`${binary} /login\` (interactive-only \u2014 it cannot run inside \`noir run\`), then retry.`;
|
|
3869
4601
|
message += credentialNote(env2 ?? process.env, envSources);
|
|
3870
4602
|
}
|
|
3871
4603
|
message += ` If you use another profile, pass \`--command <binary>\` or define a run profile under run.profiles. transcript: ${transcript}`;
|
|
4604
|
+
if (opts.json !== true && stderrTail.length > 0) {
|
|
4605
|
+
message += `
|
|
4606
|
+
${binary} stderr (last ${HOST_STDERR_TAIL_LINES} lines):
|
|
4607
|
+
${stderrTail}`;
|
|
4608
|
+
}
|
|
3872
4609
|
fail(EXIT.ERROR, message, opts);
|
|
3873
4610
|
}
|
|
3874
4611
|
if (opts.json === true) {
|
|
@@ -3881,26 +4618,59 @@ async function run(prompt, opts) {
|
|
|
3881
4618
|
usage: result.usage,
|
|
3882
4619
|
numTurns: result.usage.numTurns,
|
|
3883
4620
|
events: result.eventCount,
|
|
3884
|
-
transcript
|
|
4621
|
+
transcript,
|
|
4622
|
+
// The answer as plain words, so a consumer does not have to reassemble
|
|
4623
|
+
// it from the stream-json transcript.
|
|
4624
|
+
answerText: answer.join(""),
|
|
4625
|
+
...sessionId === void 0 ? {} : { sessionId }
|
|
3885
4626
|
}
|
|
3886
4627
|
});
|
|
3887
4628
|
return;
|
|
3888
4629
|
}
|
|
4630
|
+
status2.end();
|
|
3889
4631
|
process.stdout.write("\n");
|
|
3890
4632
|
success(`usage: ${formatUsage(result.usage)} (API-equivalent estimate, not billed)`, opts);
|
|
3891
4633
|
log(`transcript: ${transcript}`, opts);
|
|
4634
|
+
await offerPostRunActions({
|
|
4635
|
+
answer: answer.join(""),
|
|
4636
|
+
transcript,
|
|
4637
|
+
sessionId,
|
|
4638
|
+
host: binary,
|
|
4639
|
+
opts,
|
|
4640
|
+
// Only the first run of a session can be continued: a resumed run is
|
|
4641
|
+
// itself the continuation, so the menu never nests more than one deep.
|
|
4642
|
+
...resumeSessionId === void 0 && sessionId !== void 0 ? { resume: (nextPrompt, id) => runOnce(nextPrompt, opts, id) } : {}
|
|
4643
|
+
});
|
|
3892
4644
|
}
|
|
3893
4645
|
function mergeEnv(base, overlay) {
|
|
4646
|
+
for (const key of Object.keys(overlay)) {
|
|
4647
|
+
if (isDeniedEnvKey2(key)) {
|
|
4648
|
+
fail(
|
|
4649
|
+
EXIT.ERROR,
|
|
4650
|
+
`refusing to run the host: the profile env defines process-injection key "${key}" (it can inject into the spawned host process)`
|
|
4651
|
+
);
|
|
4652
|
+
}
|
|
4653
|
+
}
|
|
3894
4654
|
const merged = { ...base, ...overlay };
|
|
3895
4655
|
return Object.fromEntries(Object.entries(merged).filter(([, v]) => v !== void 0));
|
|
3896
4656
|
}
|
|
3897
|
-
function
|
|
4657
|
+
function failInterrupted(signal, host, lines, opts) {
|
|
4658
|
+
return fail(exitCodeForSignal(signal), interruptedNotice(safeTranscript(host, lines)), opts);
|
|
4659
|
+
}
|
|
4660
|
+
function safeTranscript(host, lines) {
|
|
4661
|
+
try {
|
|
4662
|
+
return writeTranscript(host, lines);
|
|
4663
|
+
} catch {
|
|
4664
|
+
return "(not persisted)";
|
|
4665
|
+
}
|
|
4666
|
+
}
|
|
4667
|
+
function writeTranscript(host, lines, root = process.cwd()) {
|
|
3898
4668
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3899
|
-
const dir =
|
|
4669
|
+
const dir = paths2.transcriptsDir(root);
|
|
3900
4670
|
const file = join4(dir, `${host}-${ts}.jsonl`);
|
|
3901
4671
|
try {
|
|
3902
4672
|
mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
3903
|
-
|
|
4673
|
+
writeFileSync4(file, `${lines.join("\n")}${lines.length > 0 ? "\n" : ""}`, { mode: 384 });
|
|
3904
4674
|
} catch {
|
|
3905
4675
|
return "(not persisted)";
|
|
3906
4676
|
}
|
|
@@ -3908,252 +4678,35 @@ function writeTranscript(host, lines) {
|
|
|
3908
4678
|
}
|
|
3909
4679
|
|
|
3910
4680
|
// src/commands/skills.ts
|
|
3911
|
-
import { resolveAdapter as
|
|
3912
|
-
import { loadProjectInfo as
|
|
4681
|
+
import { resolveAdapter as resolveAdapter5 } from "@noir-ai/adapters";
|
|
4682
|
+
import { loadProjectInfo as loadProjectInfo11 } from "@noir-ai/core";
|
|
3913
4683
|
import {
|
|
3914
4684
|
buildRegistry,
|
|
3915
4685
|
discoverAll,
|
|
3916
|
-
emitSkillsToDir,
|
|
4686
|
+
emitSkillsToDir as emitSkillsToDir2,
|
|
3917
4687
|
lintSkill
|
|
3918
4688
|
} from "@noir-ai/skills";
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
"noir-shipping": "git",
|
|
3937
|
-
"noir-worktree": "git",
|
|
3938
|
-
"noir-recall": "memory",
|
|
3939
|
-
"noir-remember": "memory",
|
|
3940
|
-
"noir-context": "context",
|
|
3941
|
-
"noir-frontend": "domain",
|
|
3942
|
-
"noir-backend": "domain",
|
|
3943
|
-
"noir-doctor": "meta",
|
|
3944
|
-
"noir-writing-skills": "meta",
|
|
3945
|
-
"noir-rules": "meta"
|
|
3946
|
-
};
|
|
3947
|
-
function categoryOf(name) {
|
|
3948
|
-
const mapped = CATEGORY[name];
|
|
3949
|
-
if (mapped !== void 0) return mapped;
|
|
3950
|
-
return name.replace(/^noir-/, "") || "general";
|
|
3951
|
-
}
|
|
3952
|
-
function toRow(s, kind = "builtin") {
|
|
3953
|
-
const description = typeof s.frontmatter.description === "string" ? s.frontmatter.description : "";
|
|
3954
|
-
const status2 = s.skillMd.includes("> **Stub:**") ? "stub" : "full";
|
|
3955
|
-
const category = s.frontmatter.metadata?.category?.trim() || categoryOf(s.name);
|
|
3956
|
-
return { name: s.name, category, description, kind, status: status2 };
|
|
3957
|
-
}
|
|
3958
|
-
function truncate(text, max) {
|
|
3959
|
-
if (text.length <= max) return text;
|
|
3960
|
-
const slice = text.slice(0, Math.max(0, max - 1));
|
|
3961
|
-
const boundary = slice.lastIndexOf(" ");
|
|
3962
|
-
const head = boundary > 0 ? slice.slice(0, boundary) : slice;
|
|
3963
|
-
return `${head.trimEnd()}\u2026`;
|
|
4689
|
+
|
|
4690
|
+
// src/init.ts
|
|
4691
|
+
import { existsSync as existsSync4 } from "fs";
|
|
4692
|
+
import { resolveAdapter as resolveAdapter4 } from "@noir-ai/adapters";
|
|
4693
|
+
import { loadProjectInfo as loadProjectInfo10, paths as paths3 } from "@noir-ai/core";
|
|
4694
|
+
import { scaffold } from "@noir-ai/create";
|
|
4695
|
+
import {
|
|
4696
|
+
emitSkillsToDir
|
|
4697
|
+
} from "@noir-ai/skills";
|
|
4698
|
+
|
|
4699
|
+
// src/conflict.ts
|
|
4700
|
+
import {
|
|
4701
|
+
lineDiff
|
|
4702
|
+
} from "@noir-ai/create";
|
|
4703
|
+
function flaggedNonInteractive() {
|
|
4704
|
+
const v = process.env.NOIR_NON_INTERACTIVE;
|
|
4705
|
+
return v !== void 0 && v !== "";
|
|
3964
4706
|
}
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
try {
|
|
3969
|
-
const all = discoverAll();
|
|
3970
|
-
builtins = all.builtins;
|
|
3971
|
-
integrations = all.integrations;
|
|
3972
|
-
} catch (err) {
|
|
3973
|
-
fail(
|
|
3974
|
-
EXIT.ERROR,
|
|
3975
|
-
`skills list: could not discover skills (${err instanceof Error ? err.message : String(err)})`,
|
|
3976
|
-
opts
|
|
3977
|
-
);
|
|
3978
|
-
}
|
|
3979
|
-
const rows = [
|
|
3980
|
-
...builtins.map((b) => toRow(b, "builtin")),
|
|
3981
|
-
...integrations.map((i) => toRow(i, "integration"))
|
|
3982
|
-
];
|
|
3983
|
-
const data = { count: rows.length, skills: rows };
|
|
3984
|
-
if (opts.json === true) {
|
|
3985
|
-
process.stdout.write(`${JSON.stringify({ ok: true, data })}
|
|
3986
|
-
`);
|
|
3987
|
-
return;
|
|
3988
|
-
}
|
|
3989
|
-
const intCount = integrations.length;
|
|
3990
|
-
const banner = intCount > 0 ? `noir skills \u2014 ${rows.length} skill${rows.length === 1 ? "" : "s"} (${builtins.length} builtin, ${intCount} integration${intCount === 1 ? "" : "s"})` : `noir skills \u2014 ${rows.length} builtin skill${rows.length === 1 ? "" : "s"}`;
|
|
3991
|
-
log(banner, opts);
|
|
3992
|
-
table(
|
|
3993
|
-
rows.map((r) => ({
|
|
3994
|
-
Skill: r.name,
|
|
3995
|
-
Kind: r.kind,
|
|
3996
|
-
Category: r.category,
|
|
3997
|
-
Status: r.status,
|
|
3998
|
-
Description: truncate(r.description, 80)
|
|
3999
|
-
})),
|
|
4000
|
-
["Skill", "Kind", "Category", "Status", "Description"],
|
|
4001
|
-
opts
|
|
4002
|
-
);
|
|
4003
|
-
}
|
|
4004
|
-
async function skillsSync(opts) {
|
|
4005
|
-
const root = process.cwd();
|
|
4006
|
-
let project;
|
|
4007
|
-
try {
|
|
4008
|
-
project = loadProjectInfo10(root);
|
|
4009
|
-
} catch {
|
|
4010
|
-
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
4011
|
-
}
|
|
4012
|
-
const adapter = resolveAdapter4(project.config.host);
|
|
4013
|
-
if (!adapter.skillsDir) {
|
|
4014
|
-
const data2 = {
|
|
4015
|
-
emitted: [],
|
|
4016
|
-
references: 0,
|
|
4017
|
-
host: project.config.host,
|
|
4018
|
-
reason: "no-skill-emitter"
|
|
4019
|
-
};
|
|
4020
|
-
if (opts.json === true) {
|
|
4021
|
-
process.stdout.write(`${JSON.stringify({ ok: true, data: data2 })}
|
|
4022
|
-
`);
|
|
4023
|
-
return;
|
|
4024
|
-
}
|
|
4025
|
-
info("This host has no skill emitter; nothing to sync.", opts);
|
|
4026
|
-
return;
|
|
4027
|
-
}
|
|
4028
|
-
const dir = adapter.skillsDir({ root });
|
|
4029
|
-
const target = project.config.host;
|
|
4030
|
-
const summary = await emitSkillsToDir(dir, { target });
|
|
4031
|
-
const pruned = summary.pruned ?? [];
|
|
4032
|
-
const data = {
|
|
4033
|
-
emitted: summary.emitted,
|
|
4034
|
-
references: summary.references,
|
|
4035
|
-
dir,
|
|
4036
|
-
pruned
|
|
4037
|
-
};
|
|
4038
|
-
if (opts.json === true) {
|
|
4039
|
-
process.stdout.write(`${JSON.stringify({ ok: true, data })}
|
|
4040
|
-
`);
|
|
4041
|
-
return;
|
|
4042
|
-
}
|
|
4043
|
-
log(
|
|
4044
|
-
`Synced ${summary.emitted.length} Noir skill${summary.emitted.length === 1 ? "" : "s"} to ${dir}.`,
|
|
4045
|
-
opts
|
|
4046
|
-
);
|
|
4047
|
-
if (pruned.length > 0) {
|
|
4048
|
-
warn(
|
|
4049
|
-
`Pruned ${pruned.length} stale noir-* skill dir${pruned.length === 1 ? "" : "s"}: ${pruned.join(", ")}`,
|
|
4050
|
-
opts
|
|
4051
|
-
);
|
|
4052
|
-
}
|
|
4053
|
-
}
|
|
4054
|
-
async function skillsLint(opts) {
|
|
4055
|
-
let skills;
|
|
4056
|
-
try {
|
|
4057
|
-
const all = discoverAll();
|
|
4058
|
-
skills = [...all.builtins, ...all.integrations].map(lintSkill);
|
|
4059
|
-
} catch (err) {
|
|
4060
|
-
fail(
|
|
4061
|
-
EXIT.ERROR,
|
|
4062
|
-
`skills lint: could not lint skills (${err instanceof Error ? err.message : String(err)})`,
|
|
4063
|
-
opts
|
|
4064
|
-
);
|
|
4065
|
-
}
|
|
4066
|
-
const errored = skills.filter((s) => s.errors.length > 0);
|
|
4067
|
-
const warned = skills.filter((s) => s.warnings.length > 0 && s.errors.length === 0);
|
|
4068
|
-
const data = { count: skills.length, errored: errored.length, skills };
|
|
4069
|
-
if (errored.length > 0) {
|
|
4070
|
-
const message = `skills lint: ${errored.length} skill${errored.length === 1 ? "" : "s"} failed validation`;
|
|
4071
|
-
if (opts.json === true) {
|
|
4072
|
-
process.stdout.write(
|
|
4073
|
-
`${JSON.stringify({ ok: false, error: { code: EXIT.ERROR, message }, data })}
|
|
4074
|
-
`
|
|
4075
|
-
);
|
|
4076
|
-
process.exitCode = EXIT.ERROR;
|
|
4077
|
-
return;
|
|
4078
|
-
}
|
|
4079
|
-
warn(
|
|
4080
|
-
`noir skills lint \u2014 ${errored.length} skill${errored.length === 1 ? "" : "s"} FAIL validation:`,
|
|
4081
|
-
opts
|
|
4082
|
-
);
|
|
4083
|
-
for (const s of errored) {
|
|
4084
|
-
for (const e of s.errors) warn(` ${s.name}: ${e}`, opts);
|
|
4085
|
-
}
|
|
4086
|
-
if (warned.length > 0) {
|
|
4087
|
-
info(` ${warned.length} skill${warned.length === 1 ? "" : "s"} carry lint warnings:`, opts);
|
|
4088
|
-
for (const s of warned) {
|
|
4089
|
-
for (const w of s.warnings) info(` ${s.name}: ${w}`, opts);
|
|
4090
|
-
}
|
|
4091
|
-
}
|
|
4092
|
-
fail(EXIT.ERROR, message, opts);
|
|
4093
|
-
}
|
|
4094
|
-
if (opts.json === true) {
|
|
4095
|
-
process.stdout.write(`${JSON.stringify({ ok: true, data })}
|
|
4096
|
-
`);
|
|
4097
|
-
return;
|
|
4098
|
-
}
|
|
4099
|
-
log(`noir skills lint \u2014 ${skills.length} skills, all validate clean.`, opts);
|
|
4100
|
-
if (warned.length > 0) {
|
|
4101
|
-
info(` ${warned.length} skill${warned.length === 1 ? "" : "s"} carry lint warnings:`, opts);
|
|
4102
|
-
for (const s of warned) {
|
|
4103
|
-
for (const w of s.warnings) info(` ${s.name}: ${w}`, opts);
|
|
4104
|
-
}
|
|
4105
|
-
}
|
|
4106
|
-
}
|
|
4107
|
-
async function skillsRegistry(opts) {
|
|
4108
|
-
let reg;
|
|
4109
|
-
try {
|
|
4110
|
-
reg = buildRegistry();
|
|
4111
|
-
} catch (err) {
|
|
4112
|
-
fail(
|
|
4113
|
-
EXIT.ERROR,
|
|
4114
|
-
`skills registry: could not build registry (${err instanceof Error ? err.message : String(err)})`,
|
|
4115
|
-
opts
|
|
4116
|
-
);
|
|
4117
|
-
}
|
|
4118
|
-
const data = { count: reg.length, skills: reg };
|
|
4119
|
-
if (opts.json === true) {
|
|
4120
|
-
process.stdout.write(`${JSON.stringify({ ok: true, data })}
|
|
4121
|
-
`);
|
|
4122
|
-
return;
|
|
4123
|
-
}
|
|
4124
|
-
log(`noir skills registry \u2014 ${reg.length} skills.`, opts);
|
|
4125
|
-
table(
|
|
4126
|
-
reg.map((r) => ({
|
|
4127
|
-
Skill: r.name,
|
|
4128
|
-
Kind: r.kind,
|
|
4129
|
-
Category: r.category,
|
|
4130
|
-
Version: r.version,
|
|
4131
|
-
Status: r.status,
|
|
4132
|
-
Refs: String(r.referenceCount),
|
|
4133
|
-
Lines: String(r.lines)
|
|
4134
|
-
})),
|
|
4135
|
-
["Skill", "Kind", "Category", "Version", "Status", "Refs", "Lines"],
|
|
4136
|
-
opts
|
|
4137
|
-
);
|
|
4138
|
-
}
|
|
4139
|
-
|
|
4140
|
-
// src/init.ts
|
|
4141
|
-
import { resolveAdapter as resolveAdapter5 } from "@noir-ai/adapters";
|
|
4142
|
-
import { loadProjectInfo as loadProjectInfo11 } from "@noir-ai/core";
|
|
4143
|
-
import { scaffold } from "@noir-ai/create";
|
|
4144
|
-
import { emitSkillsToDir as emitSkillsToDir2 } from "@noir-ai/skills";
|
|
4145
|
-
|
|
4146
|
-
// src/conflict.ts
|
|
4147
|
-
import {
|
|
4148
|
-
lineDiff
|
|
4149
|
-
} from "@noir-ai/create";
|
|
4150
|
-
function flaggedNonInteractive() {
|
|
4151
|
-
const v = process.env.NOIR_NON_INTERACTIVE;
|
|
4152
|
-
return v !== void 0 && v !== "";
|
|
4153
|
-
}
|
|
4154
|
-
function buildConflictOpts(input = {}) {
|
|
4155
|
-
if (input.force === true) {
|
|
4156
|
-
return { conflictPolicy: "overwrite" };
|
|
4707
|
+
function buildConflictOpts(input = {}) {
|
|
4708
|
+
if (input.force === true) {
|
|
4709
|
+
return { conflictPolicy: "overwrite" };
|
|
4157
4710
|
}
|
|
4158
4711
|
if (input.interactive === false) {
|
|
4159
4712
|
return { conflictPolicy: "preserve" };
|
|
@@ -4245,7 +4798,7 @@ function labelFor(resolution) {
|
|
|
4245
4798
|
}
|
|
4246
4799
|
|
|
4247
4800
|
// src/dedup-write.ts
|
|
4248
|
-
import { mkdirSync as mkdirSync3, readFileSync as readFileSync4, rmSync, writeFileSync as
|
|
4801
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync5 } from "fs";
|
|
4249
4802
|
import { join as join5 } from "path";
|
|
4250
4803
|
import { sha256Hex as sha256Hex2, sha256Hex12 } from "@noir-ai/core";
|
|
4251
4804
|
var HOST_CONTEXT_RELS = /* @__PURE__ */ new Set(["CLAUDE.md", "AGENTS.md", "GEMINI.md", ".noir/rules/RULES.md"]);
|
|
@@ -4399,14 +4952,14 @@ async function promptDedupAction(root, proposedRel, matchedRel, proposedText, si
|
|
|
4399
4952
|
return "preserve";
|
|
4400
4953
|
} else if (choice === "replace") {
|
|
4401
4954
|
try {
|
|
4402
|
-
|
|
4955
|
+
writeFileSync5(join5(root, matchedRel), proposedText, "utf8");
|
|
4403
4956
|
} catch {
|
|
4404
4957
|
}
|
|
4405
4958
|
return "replace";
|
|
4406
4959
|
} else if (choice === "mirror") {
|
|
4407
4960
|
try {
|
|
4408
4961
|
const existing = readFileSync4(join5(root, matchedRel), "utf8");
|
|
4409
|
-
|
|
4962
|
+
writeFileSync5(
|
|
4410
4963
|
join5(root, matchedRel),
|
|
4411
4964
|
`${existing}
|
|
4412
4965
|
|
|
@@ -4445,7 +4998,7 @@ function loadCache(root) {
|
|
|
4445
4998
|
function saveCache(root, cache) {
|
|
4446
4999
|
try {
|
|
4447
5000
|
mkdirSync3(join5(root, ".noir"), { recursive: true });
|
|
4448
|
-
|
|
5001
|
+
writeFileSync5(cachePath(root), JSON.stringify(cache), "utf8");
|
|
4449
5002
|
} catch {
|
|
4450
5003
|
}
|
|
4451
5004
|
}
|
|
@@ -4467,16 +5020,16 @@ function safeRead(abs) {
|
|
|
4467
5020
|
}
|
|
4468
5021
|
}
|
|
4469
5022
|
function withTimeout(p, ms) {
|
|
4470
|
-
return new Promise((
|
|
4471
|
-
const t = setTimeout(() =>
|
|
5023
|
+
return new Promise((resolve2) => {
|
|
5024
|
+
const t = setTimeout(() => resolve2(void 0), ms);
|
|
4472
5025
|
p.then(
|
|
4473
5026
|
(v) => {
|
|
4474
5027
|
clearTimeout(t);
|
|
4475
|
-
|
|
5028
|
+
resolve2(v);
|
|
4476
5029
|
},
|
|
4477
5030
|
() => {
|
|
4478
5031
|
clearTimeout(t);
|
|
4479
|
-
|
|
5032
|
+
resolve2(void 0);
|
|
4480
5033
|
}
|
|
4481
5034
|
);
|
|
4482
5035
|
});
|
|
@@ -4488,7 +5041,7 @@ function errMsg(e) {
|
|
|
4488
5041
|
// src/init.ts
|
|
4489
5042
|
async function init(root, opts) {
|
|
4490
5043
|
assertTransportUrl(opts);
|
|
4491
|
-
const host = opts
|
|
5044
|
+
const host = resolveInitHost(root, opts);
|
|
4492
5045
|
const interactive = resolveInteractive();
|
|
4493
5046
|
const conflictOpts = buildConflictOpts({ force: opts.force, interactive });
|
|
4494
5047
|
const dryRun = opts.dryRun === true || opts.preview === true;
|
|
@@ -4509,15 +5062,29 @@ async function init(root, opts) {
|
|
|
4509
5062
|
return res;
|
|
4510
5063
|
}
|
|
4511
5064
|
if (res.noop) return res;
|
|
4512
|
-
await emitHostSkills(root, host, conflictOpts, interactive);
|
|
5065
|
+
const skillSummary = await emitHostSkills(root, host, conflictOpts, interactive);
|
|
5066
|
+
res.skillConflicts = skillSummary?.conflicts ?? [];
|
|
5067
|
+
res.preservedSkills = skillSummary?.preserved ?? [];
|
|
4513
5068
|
let projectInfo;
|
|
4514
5069
|
try {
|
|
4515
|
-
projectInfo =
|
|
5070
|
+
projectInfo = loadProjectInfo10(root);
|
|
4516
5071
|
} catch {
|
|
4517
5072
|
projectInfo = void 0;
|
|
4518
5073
|
}
|
|
4519
5074
|
const dedup = await checkWritePathDedup(root, res, { interactive, project: projectInfo });
|
|
4520
5075
|
if (dedup.conflicts.length > 0) res.conflicts.push(...dedup.conflicts);
|
|
5076
|
+
if (res.refreshed.length > 0) {
|
|
5077
|
+
process.stderr.write(
|
|
5078
|
+
`Refreshed ${res.refreshed.length} unedited doc seed(s): ${res.refreshed.join(", ")}
|
|
5079
|
+
`
|
|
5080
|
+
);
|
|
5081
|
+
}
|
|
5082
|
+
if (res.migrationChanged.length > 0) {
|
|
5083
|
+
process.stderr.write(
|
|
5084
|
+
`Migrated ${res.migrationChanged.length} file(s): ${res.migrationChanged.join(", ")}
|
|
5085
|
+
`
|
|
5086
|
+
);
|
|
5087
|
+
}
|
|
4521
5088
|
process.stderr.write(
|
|
4522
5089
|
`Noir initialized in ${root} (host: ${host}, transport: ${opts.transport}).
|
|
4523
5090
|
`
|
|
@@ -4527,6 +5094,19 @@ async function init(root, opts) {
|
|
|
4527
5094
|
);
|
|
4528
5095
|
return res;
|
|
4529
5096
|
}
|
|
5097
|
+
function resolveInitHost(root, opts) {
|
|
5098
|
+
if (opts.host !== void 0) return opts.host;
|
|
5099
|
+
if (!existsSync4(paths3.config(root))) return "claude";
|
|
5100
|
+
try {
|
|
5101
|
+
return loadProjectInfo10(root).config.host;
|
|
5102
|
+
} catch {
|
|
5103
|
+
process.stderr.write(
|
|
5104
|
+
`Could not read the configured host from ${paths3.config(root)}; using 'claude'.
|
|
5105
|
+
`
|
|
5106
|
+
);
|
|
5107
|
+
return "claude";
|
|
5108
|
+
}
|
|
5109
|
+
}
|
|
4530
5110
|
function reportPlannedWrites(res) {
|
|
4531
5111
|
log("Dry run \u2014 no files were written.");
|
|
4532
5112
|
if (res.written.length > 0) {
|
|
@@ -4536,6 +5116,10 @@ function reportPlannedWrites(res) {
|
|
|
4536
5116
|
log(mode === void 0 ? ` ${p}` : ` ${p} (mode ${mode.toString(8).padStart(3, "0")})`);
|
|
4537
5117
|
}
|
|
4538
5118
|
}
|
|
5119
|
+
if (res.refreshed.length > 0) {
|
|
5120
|
+
log("Would refresh (unedited older copy):");
|
|
5121
|
+
for (const p of res.refreshed) log(` ${p}`);
|
|
5122
|
+
}
|
|
4539
5123
|
if (res.skipped.length > 0) {
|
|
4540
5124
|
log("Would leave as-is (already present):");
|
|
4541
5125
|
for (const p of res.skipped) log(` ${p}`);
|
|
@@ -4546,12 +5130,12 @@ function reportPlannedWrites(res) {
|
|
|
4546
5130
|
}
|
|
4547
5131
|
}
|
|
4548
5132
|
async function emitHostSkills(root, host, conflictOpts, interactive) {
|
|
4549
|
-
const adapter =
|
|
5133
|
+
const adapter = resolveAdapter4(host);
|
|
4550
5134
|
const skillsDir = adapter.skillsDir?.({ root });
|
|
4551
5135
|
if (skillsDir === void 0) {
|
|
4552
5136
|
process.stderr.write(`host '${host}' has no skill emitter; skipping skills
|
|
4553
5137
|
`);
|
|
4554
|
-
return;
|
|
5138
|
+
return void 0;
|
|
4555
5139
|
}
|
|
4556
5140
|
const target = host;
|
|
4557
5141
|
const skillOpts = {
|
|
@@ -4563,12 +5147,20 @@ async function emitHostSkills(root, host, conflictOpts, interactive) {
|
|
|
4563
5147
|
if (conflictOpts.onConflict !== void 0) {
|
|
4564
5148
|
skillOpts.onConflict = conflictOpts.onConflict;
|
|
4565
5149
|
}
|
|
4566
|
-
const summary = await
|
|
5150
|
+
const summary = await emitSkillsToDir(skillsDir, skillOpts);
|
|
4567
5151
|
const relDir = skillsDir.replace(`${root}/`, "");
|
|
4568
5152
|
process.stderr.write(
|
|
4569
5153
|
`Emitted ${summary.emitted.length} Noir skills to ${relDir}/ (target: ${target}).
|
|
4570
5154
|
`
|
|
4571
5155
|
);
|
|
5156
|
+
const stale = preservedStaleLine(summary.preserved ?? []);
|
|
5157
|
+
if (stale !== void 0) process.stderr.write(`${stale}
|
|
5158
|
+
`);
|
|
5159
|
+
return summary;
|
|
5160
|
+
}
|
|
5161
|
+
function preservedStaleLine(preserved) {
|
|
5162
|
+
if (preserved.length === 0) return void 0;
|
|
5163
|
+
return `${preserved.length} skill(s) preserved as stale (interactive TTY required to refresh): ${preserved.join(", ")}`;
|
|
4572
5164
|
}
|
|
4573
5165
|
function assertTransportUrl(opts) {
|
|
4574
5166
|
if (opts.transport === "streamable-http" && opts.url === void 0) {
|
|
@@ -4593,6 +5185,235 @@ function assertLocalhostUrl(raw) {
|
|
|
4593
5185
|
}
|
|
4594
5186
|
}
|
|
4595
5187
|
|
|
5188
|
+
// src/commands/skills.ts
|
|
5189
|
+
var CATEGORY = {
|
|
5190
|
+
"noir-brainstorming": "discovery",
|
|
5191
|
+
"noir-sync": "discovery",
|
|
5192
|
+
"noir-checkpoint": "discovery",
|
|
5193
|
+
"noir-exploring": "discovery",
|
|
5194
|
+
"noir-spec": "spec",
|
|
5195
|
+
"noir-planning": "plan",
|
|
5196
|
+
"noir-prd": "plan",
|
|
5197
|
+
"noir-executing-plans": "execute",
|
|
5198
|
+
"noir-test-driven-development": "execute",
|
|
5199
|
+
"noir-systematic-debugging": "execute",
|
|
5200
|
+
"noir-subagent": "execute",
|
|
5201
|
+
"noir-parallel": "execute",
|
|
5202
|
+
"noir-verifying": "verify",
|
|
5203
|
+
"noir-security": "verify",
|
|
5204
|
+
"noir-wrap": "document",
|
|
5205
|
+
"noir-readme": "document",
|
|
5206
|
+
"noir-shipping": "git",
|
|
5207
|
+
"noir-worktree": "git",
|
|
5208
|
+
"noir-recall": "memory",
|
|
5209
|
+
"noir-remember": "memory",
|
|
5210
|
+
"noir-context": "context",
|
|
5211
|
+
"noir-frontend": "domain",
|
|
5212
|
+
"noir-backend": "domain",
|
|
5213
|
+
"noir-doctor": "meta",
|
|
5214
|
+
"noir-writing-skills": "meta",
|
|
5215
|
+
"noir-rules": "meta"
|
|
5216
|
+
};
|
|
5217
|
+
function categoryOf(name) {
|
|
5218
|
+
const mapped = CATEGORY[name];
|
|
5219
|
+
if (mapped !== void 0) return mapped;
|
|
5220
|
+
return name.replace(/^noir-/, "") || "general";
|
|
5221
|
+
}
|
|
5222
|
+
function toRow(s, kind = "builtin") {
|
|
5223
|
+
const description = typeof s.frontmatter.description === "string" ? s.frontmatter.description : "";
|
|
5224
|
+
const status2 = s.skillMd.includes("> **Stub:**") ? "stub" : "full";
|
|
5225
|
+
const category = s.frontmatter.metadata?.category?.trim() || categoryOf(s.name);
|
|
5226
|
+
return { name: s.name, category, description, kind, status: status2 };
|
|
5227
|
+
}
|
|
5228
|
+
function truncate(text, max) {
|
|
5229
|
+
if (text.length <= max) return text;
|
|
5230
|
+
const slice = text.slice(0, Math.max(0, max - 1));
|
|
5231
|
+
const boundary = slice.lastIndexOf(" ");
|
|
5232
|
+
const head = boundary > 0 ? slice.slice(0, boundary) : slice;
|
|
5233
|
+
return `${head.trimEnd()}\u2026`;
|
|
5234
|
+
}
|
|
5235
|
+
async function skillsList(opts) {
|
|
5236
|
+
let builtins;
|
|
5237
|
+
let integrations;
|
|
5238
|
+
try {
|
|
5239
|
+
const all = discoverAll();
|
|
5240
|
+
builtins = all.builtins;
|
|
5241
|
+
integrations = all.integrations;
|
|
5242
|
+
} catch (err) {
|
|
5243
|
+
fail(
|
|
5244
|
+
EXIT.ERROR,
|
|
5245
|
+
`skills list: could not discover skills (${err instanceof Error ? err.message : String(err)})`,
|
|
5246
|
+
opts
|
|
5247
|
+
);
|
|
5248
|
+
}
|
|
5249
|
+
const rows = [
|
|
5250
|
+
...builtins.map((b) => toRow(b, "builtin")),
|
|
5251
|
+
...integrations.map((i) => toRow(i, "integration"))
|
|
5252
|
+
];
|
|
5253
|
+
const data = { count: rows.length, skills: rows };
|
|
5254
|
+
if (opts.json === true) {
|
|
5255
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data })}
|
|
5256
|
+
`);
|
|
5257
|
+
return;
|
|
5258
|
+
}
|
|
5259
|
+
const intCount = integrations.length;
|
|
5260
|
+
const banner = intCount > 0 ? `noir skills \u2014 ${rows.length} skill${rows.length === 1 ? "" : "s"} (${builtins.length} builtin, ${intCount} integration${intCount === 1 ? "" : "s"})` : `noir skills \u2014 ${rows.length} builtin skill${rows.length === 1 ? "" : "s"}`;
|
|
5261
|
+
log(banner, opts);
|
|
5262
|
+
table(
|
|
5263
|
+
rows.map((r) => ({
|
|
5264
|
+
Skill: r.name,
|
|
5265
|
+
Kind: r.kind,
|
|
5266
|
+
Category: r.category,
|
|
5267
|
+
Status: r.status,
|
|
5268
|
+
Description: truncate(r.description, 80)
|
|
5269
|
+
})),
|
|
5270
|
+
["Skill", "Kind", "Category", "Status", "Description"],
|
|
5271
|
+
opts
|
|
5272
|
+
);
|
|
5273
|
+
}
|
|
5274
|
+
async function skillsSync(opts) {
|
|
5275
|
+
const root = process.cwd();
|
|
5276
|
+
let project;
|
|
5277
|
+
try {
|
|
5278
|
+
project = loadProjectInfo11(root);
|
|
5279
|
+
} catch {
|
|
5280
|
+
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
5281
|
+
}
|
|
5282
|
+
const adapter = resolveAdapter5(project.config.host);
|
|
5283
|
+
if (!adapter.skillsDir) {
|
|
5284
|
+
const data2 = {
|
|
5285
|
+
emitted: [],
|
|
5286
|
+
references: 0,
|
|
5287
|
+
host: project.config.host,
|
|
5288
|
+
reason: "no-skill-emitter"
|
|
5289
|
+
};
|
|
5290
|
+
if (opts.json === true) {
|
|
5291
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: data2 })}
|
|
5292
|
+
`);
|
|
5293
|
+
return;
|
|
5294
|
+
}
|
|
5295
|
+
info("This host has no skill emitter; nothing to sync.", opts);
|
|
5296
|
+
return;
|
|
5297
|
+
}
|
|
5298
|
+
const dir = adapter.skillsDir({ root });
|
|
5299
|
+
const target = project.config.host;
|
|
5300
|
+
const summary = await emitSkillsToDir2(dir, { target });
|
|
5301
|
+
const pruned = summary.pruned ?? [];
|
|
5302
|
+
const preserved = summary.preserved ?? [];
|
|
5303
|
+
const data = {
|
|
5304
|
+
emitted: summary.emitted,
|
|
5305
|
+
references: summary.references,
|
|
5306
|
+
dir,
|
|
5307
|
+
pruned,
|
|
5308
|
+
// What the count above does NOT cover: skills whose files were preserved
|
|
5309
|
+
// (stale on disk, by design, in a non-interactive run). Same field `noir
|
|
5310
|
+
// init --json` carries, so a consumer reads one shape from every emitter.
|
|
5311
|
+
preserved
|
|
5312
|
+
};
|
|
5313
|
+
if (opts.json === true) {
|
|
5314
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data })}
|
|
5315
|
+
`);
|
|
5316
|
+
return;
|
|
5317
|
+
}
|
|
5318
|
+
log(
|
|
5319
|
+
`Synced ${summary.emitted.length} Noir skill${summary.emitted.length === 1 ? "" : "s"} to ${dir}.`,
|
|
5320
|
+
opts
|
|
5321
|
+
);
|
|
5322
|
+
if (pruned.length > 0) {
|
|
5323
|
+
warn(
|
|
5324
|
+
`Pruned ${pruned.length} stale noir-* skill dir${pruned.length === 1 ? "" : "s"}: ${pruned.join(", ")}`,
|
|
5325
|
+
opts
|
|
5326
|
+
);
|
|
5327
|
+
}
|
|
5328
|
+
const stale = preservedStaleLine(preserved);
|
|
5329
|
+
if (stale !== void 0) warn(stale, opts);
|
|
5330
|
+
}
|
|
5331
|
+
async function skillsLint(opts) {
|
|
5332
|
+
let skills;
|
|
5333
|
+
try {
|
|
5334
|
+
const all = discoverAll();
|
|
5335
|
+
skills = [...all.builtins, ...all.integrations].map(lintSkill);
|
|
5336
|
+
} catch (err) {
|
|
5337
|
+
fail(
|
|
5338
|
+
EXIT.ERROR,
|
|
5339
|
+
`skills lint: could not lint skills (${err instanceof Error ? err.message : String(err)})`,
|
|
5340
|
+
opts
|
|
5341
|
+
);
|
|
5342
|
+
}
|
|
5343
|
+
const errored = skills.filter((s) => s.errors.length > 0);
|
|
5344
|
+
const warned = skills.filter((s) => s.warnings.length > 0 && s.errors.length === 0);
|
|
5345
|
+
const data = { count: skills.length, errored: errored.length, skills };
|
|
5346
|
+
if (errored.length > 0) {
|
|
5347
|
+
const message = `skills lint: ${errored.length} skill${errored.length === 1 ? "" : "s"} failed validation`;
|
|
5348
|
+
if (opts.json === true) {
|
|
5349
|
+
process.stdout.write(
|
|
5350
|
+
`${JSON.stringify({ ok: false, error: { code: EXIT.ERROR, message }, data })}
|
|
5351
|
+
`
|
|
5352
|
+
);
|
|
5353
|
+
process.exitCode = EXIT.ERROR;
|
|
5354
|
+
return;
|
|
5355
|
+
}
|
|
5356
|
+
warn(
|
|
5357
|
+
`noir skills lint \u2014 ${errored.length} skill${errored.length === 1 ? "" : "s"} FAIL validation:`,
|
|
5358
|
+
opts
|
|
5359
|
+
);
|
|
5360
|
+
for (const s of errored) {
|
|
5361
|
+
for (const e of s.errors) warn(` ${s.name}: ${e}`, opts);
|
|
5362
|
+
}
|
|
5363
|
+
if (warned.length > 0) {
|
|
5364
|
+
info(` ${warned.length} skill${warned.length === 1 ? "" : "s"} carry lint warnings:`, opts);
|
|
5365
|
+
for (const s of warned) {
|
|
5366
|
+
for (const w of s.warnings) info(` ${s.name}: ${w}`, opts);
|
|
5367
|
+
}
|
|
5368
|
+
}
|
|
5369
|
+
fail(EXIT.ERROR, message, opts);
|
|
5370
|
+
}
|
|
5371
|
+
if (opts.json === true) {
|
|
5372
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data })}
|
|
5373
|
+
`);
|
|
5374
|
+
return;
|
|
5375
|
+
}
|
|
5376
|
+
log(`noir skills lint \u2014 ${skills.length} skills, all validate clean.`, opts);
|
|
5377
|
+
if (warned.length > 0) {
|
|
5378
|
+
info(` ${warned.length} skill${warned.length === 1 ? "" : "s"} carry lint warnings:`, opts);
|
|
5379
|
+
for (const s of warned) {
|
|
5380
|
+
for (const w of s.warnings) info(` ${s.name}: ${w}`, opts);
|
|
5381
|
+
}
|
|
5382
|
+
}
|
|
5383
|
+
}
|
|
5384
|
+
async function skillsRegistry(opts) {
|
|
5385
|
+
let reg;
|
|
5386
|
+
try {
|
|
5387
|
+
reg = buildRegistry();
|
|
5388
|
+
} catch (err) {
|
|
5389
|
+
fail(
|
|
5390
|
+
EXIT.ERROR,
|
|
5391
|
+
`skills registry: could not build registry (${err instanceof Error ? err.message : String(err)})`,
|
|
5392
|
+
opts
|
|
5393
|
+
);
|
|
5394
|
+
}
|
|
5395
|
+
const data = { count: reg.length, skills: reg };
|
|
5396
|
+
if (opts.json === true) {
|
|
5397
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data })}
|
|
5398
|
+
`);
|
|
5399
|
+
return;
|
|
5400
|
+
}
|
|
5401
|
+
log(`noir skills registry \u2014 ${reg.length} skills.`, opts);
|
|
5402
|
+
table(
|
|
5403
|
+
reg.map((r) => ({
|
|
5404
|
+
Skill: r.name,
|
|
5405
|
+
Kind: r.kind,
|
|
5406
|
+
Category: r.category,
|
|
5407
|
+
Version: r.version,
|
|
5408
|
+
Status: r.status,
|
|
5409
|
+
Refs: String(r.referenceCount),
|
|
5410
|
+
Lines: String(r.lines)
|
|
5411
|
+
})),
|
|
5412
|
+
["Skill", "Kind", "Category", "Version", "Status", "Refs", "Lines"],
|
|
5413
|
+
opts
|
|
5414
|
+
);
|
|
5415
|
+
}
|
|
5416
|
+
|
|
4596
5417
|
// src/serve.ts
|
|
4597
5418
|
import { applyNoirEnv, loadProjectInfo as loadProjectInfo12 } from "@noir-ai/core";
|
|
4598
5419
|
import { ensureDaemonRunning as ensureDaemonRunning2, startStdioServer } from "@noir-ai/daemon";
|
|
@@ -4734,13 +5555,15 @@ function createProgram() {
|
|
|
4734
5555
|
"--upgrade",
|
|
4735
5556
|
"run scaffold migrations before re-emitting (re-run on an existing project)"
|
|
4736
5557
|
).option("--force", "re-scaffold even if already initialized (bypasses the already-init no-op)").option("--dry-run", "report planned writes without writing anything").option("--preview", "alias for --dry-run").addOption(
|
|
4737
|
-
//
|
|
4738
|
-
//
|
|
4739
|
-
//
|
|
4740
|
-
//
|
|
5558
|
+
// Target host. When the flag is absent the host is read from an existing
|
|
5559
|
+
// `.noir/config.yml`, falling back to `'claude'` (so a fresh project is
|
|
5560
|
+
// unaffected). The chosen host is forwarded to scaffold() + skills
|
|
5561
|
+
// emission via resolveAdapter. The choice list mirrors `SUPPORTED_HOSTS`
|
|
5562
|
+
// so a new host lands here automatically; commander rejects anything else
|
|
5563
|
+
// as usage=2.
|
|
4741
5564
|
new Option(
|
|
4742
5565
|
"--host <id>",
|
|
4743
|
-
"target agentic CLI (default:
|
|
5566
|
+
"target agentic CLI (default: the host in .noir/config.yml, else claude)"
|
|
4744
5567
|
).choices(SUPPORTED_HOSTS2)
|
|
4745
5568
|
).action(
|
|
4746
5569
|
async (opts, cmd) => {
|
|
@@ -4773,7 +5596,7 @@ function createProgram() {
|
|
|
4773
5596
|
const force = opts.force === true;
|
|
4774
5597
|
const host = parseHost(opts.host);
|
|
4775
5598
|
const dryRun = opts.dryRun === true || opts.preview === true;
|
|
4776
|
-
const { create } = await import("./create-
|
|
5599
|
+
const { create } = await import("./create-6KDIFPUL.js");
|
|
4777
5600
|
const result = await create(dir, {
|
|
4778
5601
|
transport,
|
|
4779
5602
|
url: opts.url,
|
|
@@ -4803,7 +5626,7 @@ function createProgram() {
|
|
|
4803
5626
|
"strip-replace managed regions (discard hand-edits inside <!-- noir:* --> markers)"
|
|
4804
5627
|
)
|
|
4805
5628
|
).addOption(
|
|
4806
|
-
//
|
|
5629
|
+
// Optional `--host` override. When omitted, sync reads host from
|
|
4807
5630
|
// `.noir/config.yml` (whatever `noir init --host <id>` persisted). The
|
|
4808
5631
|
// override is rarely needed — documented as advanced.
|
|
4809
5632
|
new Option(
|
|
@@ -4812,7 +5635,7 @@ function createProgram() {
|
|
|
4812
5635
|
).choices(SUPPORTED_HOSTS2)
|
|
4813
5636
|
).action(
|
|
4814
5637
|
async (opts, cmd) => {
|
|
4815
|
-
const { sync } = await import("./sync-
|
|
5638
|
+
const { sync } = await import("./sync-AII5WCRO.js");
|
|
4816
5639
|
const host = parseHost(opts.host);
|
|
4817
5640
|
const force = opts.force === true;
|
|
4818
5641
|
const merge = opts.merge === true;
|
|
@@ -5198,7 +6021,7 @@ function createProgram() {
|
|
|
5198
6021
|
"dismiss the migration banner for the current version (persists in install.json)"
|
|
5199
6022
|
).argument("[spec]", "channel ('latest'|'beta') or exact version (default: latest)").action(
|
|
5200
6023
|
async (spec, opts, cmd) => {
|
|
5201
|
-
const { install } = await import("./install-
|
|
6024
|
+
const { install } = await import("./install-KA35ONQP.js");
|
|
5202
6025
|
const cli = cmd.optsWithGlobals();
|
|
5203
6026
|
await install({
|
|
5204
6027
|
...cli,
|
|
@@ -5211,7 +6034,7 @@ function createProgram() {
|
|
|
5211
6034
|
);
|
|
5212
6035
|
installCmd.alias("migrate");
|
|
5213
6036
|
program2.command("update").description("update Noir to the latest version via the active install method").option("--check", "check for a new version without changing anything").argument("[spec]", "channel ('latest'|'beta') or exact version (default: latest)").action(async (spec, opts, cmd) => {
|
|
5214
|
-
const { update } = await import("./update-
|
|
6037
|
+
const { update } = await import("./update-XGKJ4KBR.js");
|
|
5215
6038
|
const cli = cmd.optsWithGlobals();
|
|
5216
6039
|
await update({ ...cli, spec, check: opts.check === true });
|
|
5217
6040
|
});
|
|
@@ -5328,7 +6151,7 @@ var homeDeps = {
|
|
|
5328
6151
|
}
|
|
5329
6152
|
},
|
|
5330
6153
|
// The grouped home menu resolves its sections against the LIVE palette
|
|
5331
|
-
// registry
|
|
6154
|
+
// registry — the same source the TUI palette
|
|
5332
6155
|
// uses — so the menu cannot drift from the commander tree.
|
|
5333
6156
|
commands: buildPaletteCommands(createProgram())
|
|
5334
6157
|
};
|
|
@@ -5902,11 +6725,27 @@ export {
|
|
|
5902
6725
|
describeMemory,
|
|
5903
6726
|
gatherStatusPayload,
|
|
5904
6727
|
isDestructive,
|
|
6728
|
+
runPromptFrom,
|
|
5905
6729
|
resolveSections,
|
|
6730
|
+
INTERRUPT_GRACE_MS,
|
|
6731
|
+
parseStreamLine,
|
|
6732
|
+
normalizeStreamEvent,
|
|
6733
|
+
UsageReducer,
|
|
6734
|
+
runHost,
|
|
6735
|
+
postRunActions,
|
|
6736
|
+
performPostRunAction,
|
|
6737
|
+
interruptedNotice,
|
|
6738
|
+
RunInterrupt,
|
|
6739
|
+
loadRunConfig,
|
|
6740
|
+
resolveRunProfile,
|
|
6741
|
+
humanizeElapsed,
|
|
6742
|
+
mergeEnv,
|
|
6743
|
+
writeTranscript,
|
|
5906
6744
|
buildConflictOpts,
|
|
5907
6745
|
checkWritePathDedup,
|
|
5908
6746
|
reportPlannedWrites,
|
|
6747
|
+
preservedStaleLine,
|
|
5909
6748
|
assertTransportUrl,
|
|
5910
6749
|
buildPaletteCommandsForTui
|
|
5911
6750
|
};
|
|
5912
|
-
//# sourceMappingURL=chunk-
|
|
6751
|
+
//# sourceMappingURL=chunk-5KWETEMF.js.map
|