@noir-ai/cli 1.10.1 → 1.11.0-beta.1

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/dist/bin.d.ts CHANGED
@@ -19,7 +19,7 @@ interface PaletteCommand {
19
19
  readonly description: string;
20
20
  /** Top-level group the command belongs to (`argv[0]`), for palette grouping. */
21
21
  readonly category: string;
22
- /** Search keywords — currently the argv tokens, used by the palette's matcher. */
22
+ /** Search keywords — extra tokens the palette matcher scores beyond the label. */
23
23
  readonly keywords: readonly string[];
24
24
  /**
25
25
  * True for commands that mutate the store / workflow / host artifacts (context
package/dist/bin.js CHANGED
@@ -4,8 +4,9 @@ import {
4
4
  } from "./chunk-A5W2E3X2.js";
5
5
  import {
6
6
  DEFAULT_UPDATE_CONFIG,
7
+ buildUpdateNotice,
7
8
  runAsyncUpdateCheck
8
- } from "./chunk-PED37JWV.js";
9
+ } from "./chunk-Y4M2YC6U.js";
9
10
  import "./chunk-US567S5T.js";
10
11
  import {
11
12
  doctor,
@@ -37,7 +38,7 @@ import {
37
38
  // src/bin.ts
38
39
  import { realpathSync } from "fs";
39
40
  import { pathToFileURL } from "url";
40
- import { SUPPORTED_HOSTS } from "@noir-ai/adapters";
41
+ import { SUPPORTED_HOSTS as SUPPORTED_HOSTS2 } from "@noir-ai/adapters";
41
42
  import { NOIR_VERSION as NOIR_VERSION4 } from "@noir-ai/core";
42
43
  import { Command, Option } from "commander";
43
44
 
@@ -1466,9 +1467,12 @@ async function handoff(opts) {
1466
1467
  // src/commands/home.ts
1467
1468
  import { hostLaunchDirective as hostLaunchDirective2 } from "@noir-ai/adapters";
1468
1469
  import {
1470
+ detectActiveMethod,
1471
+ latestVersionFromCache,
1469
1472
  loadProjectInfo as loadProjectInfo6,
1470
1473
  NOIR_VERSION as NOIR_VERSION3,
1471
- readInstallRecord
1474
+ readInstallRecord,
1475
+ readUpdateCache
1472
1476
  } from "@noir-ai/core";
1473
1477
 
1474
1478
  // src/banner.ts
@@ -1506,7 +1510,6 @@ function shouldShowBanner(opts) {
1506
1510
  // src/tui/commands/registry.ts
1507
1511
  var DESTRUCTIVE_PREFIXES = [
1508
1512
  ["context", "index"],
1509
- ["context", "forget"],
1510
1513
  ["memory", "save"],
1511
1514
  ["memory", "forget"],
1512
1515
  ["memory", "consolidate"],
@@ -1520,7 +1523,8 @@ var DESTRUCTIVE_PREFIXES = [
1520
1523
  ["create"],
1521
1524
  ["sync"],
1522
1525
  ["install"],
1523
- ["update"]
1526
+ ["update"],
1527
+ ["run"]
1524
1528
  ];
1525
1529
  function isDestructive(argv) {
1526
1530
  return DESTRUCTIVE_PREFIXES.some((prefix) => {
@@ -1569,7 +1573,6 @@ var HOME_SECTIONS = [
1569
1573
  id: "status",
1570
1574
  label: "Status & context",
1571
1575
  hint: "snapshot \xB7 index \xB7 search \xB7 doctor",
1572
- key: "1",
1573
1576
  items: [
1574
1577
  {
1575
1578
  id: "status",
@@ -1604,7 +1607,6 @@ var HOME_SECTIONS = [
1604
1607
  id: "memory",
1605
1608
  label: "Memory",
1606
1609
  hint: "recall \xB7 save \xB7 sessions \xB7 forget \xB7 consolidate",
1607
- key: "2",
1608
1610
  items: [
1609
1611
  {
1610
1612
  id: "memory recall",
@@ -1642,7 +1644,6 @@ var HOME_SECTIONS = [
1642
1644
  id: "workflow",
1643
1645
  label: "Workflow",
1644
1646
  hint: "next \xB7 status \xB7 advance \xB7 handoff \xB7 wrap",
1645
- key: "3",
1646
1647
  items: [
1647
1648
  {
1648
1649
  id: "task next",
@@ -1677,7 +1678,6 @@ var HOME_SECTIONS = [
1677
1678
  id: "setup",
1678
1679
  label: "Setup & maintenance",
1679
1680
  hint: "init \xB7 create \xB7 sync \xB7 skills \xB7 install \xB7 update",
1680
- key: "4",
1681
1681
  items: [
1682
1682
  {
1683
1683
  id: "init",
@@ -1726,7 +1726,6 @@ var HOME_SECTIONS = [
1726
1726
  id: "dashboard",
1727
1727
  label: "Dashboard (full-screen)",
1728
1728
  hint: "noir tui \xB7 noir palette \xB7 home",
1729
- key: "5",
1730
1729
  items: [
1731
1730
  {
1732
1731
  id: "tui",
@@ -1955,6 +1954,19 @@ ${COMMANDS_HINT}
1955
1954
  `
1956
1955
  ${c.warn(`noir installed via ${rec.method}`)} \u2014 consider \`noir install\` for the native path (auto-update, no npm prefix/PATH issues). Dismiss with: \`noir install --dismiss\` (persisted per version).
1957
1956
 
1957
+ `
1958
+ );
1959
+ }
1960
+ const updateNotice = buildUpdateNotice({
1961
+ method: detectActiveMethod(),
1962
+ currentVersion: NOIR_VERSION3,
1963
+ latestKnown: latestVersionFromCache(readUpdateCache(), "latest")
1964
+ });
1965
+ if (updateNotice) {
1966
+ process.stderr.write(
1967
+ `
1968
+ ${c.warn(`noir ${updateNotice.latestVersion} is available`)} (you have ${updateNotice.currentVersion}) \u2014 ${updateNotice.advice}.
1969
+
1958
1970
  `
1959
1971
  );
1960
1972
  }
@@ -2320,6 +2332,255 @@ async function release(opts) {
2320
2332
  }
2321
2333
  }
2322
2334
 
2335
+ // src/commands/run.ts
2336
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
2337
+ import { join as join2 } from "path";
2338
+ import { SUPPORTED_HOSTS } from "@noir-ai/adapters";
2339
+
2340
+ // src/orchestrator.ts
2341
+ import { spawn } from "child_process";
2342
+ import { createInterface } from "readline";
2343
+ var HOST_BINARY = {
2344
+ claude: "claude",
2345
+ gemini: "gemini",
2346
+ opencode: "opencode",
2347
+ cursor: "cursor-agent",
2348
+ "agents-md": null
2349
+ // emits AGENTS.md — a file host, not a spawnable CLI
2350
+ };
2351
+ var HOST_FLAGS = {
2352
+ claude: ["-p", "--output-format", "stream-json", "--verbose"],
2353
+ gemini: ["-p", "--output-format", "stream-json"],
2354
+ opencode: ["run"],
2355
+ cursor: ["-p"],
2356
+ "agents-md": []
2357
+ };
2358
+ function resolveHostRun(host, customBinary) {
2359
+ const binary = customBinary && customBinary.length > 0 ? customBinary : HOST_BINARY[host];
2360
+ if (!binary) return null;
2361
+ return { binary, flags: HOST_FLAGS[host] };
2362
+ }
2363
+ function asObj(v) {
2364
+ return typeof v === "object" && v !== null ? v : null;
2365
+ }
2366
+ function str(v) {
2367
+ return typeof v === "string" ? v : void 0;
2368
+ }
2369
+ function num(v) {
2370
+ return typeof v === "number" && Number.isFinite(v) ? v : void 0;
2371
+ }
2372
+ function usageFrom(v) {
2373
+ const o = asObj(v);
2374
+ if (!o) return void 0;
2375
+ const input = num(o.input_tokens);
2376
+ const output = num(o.output_tokens);
2377
+ const cache = num(o.cache_read_input_tokens) ?? num(o.cache_creation_input_tokens);
2378
+ if (input === void 0 && output === void 0 && cache === void 0) return void 0;
2379
+ return { inputTokens: input, outputTokens: output, cacheReadTokens: cache };
2380
+ }
2381
+ function messageText(message) {
2382
+ if (!message) return void 0;
2383
+ const content = message.content;
2384
+ if (typeof content === "string") return content;
2385
+ if (Array.isArray(content)) {
2386
+ const parts = content.map((block) => {
2387
+ const b = asObj(block);
2388
+ return b && typeof b.text === "string" ? b.text : null;
2389
+ }).filter((t) => t !== null);
2390
+ if (parts.length > 0) return parts.join("");
2391
+ }
2392
+ return void 0;
2393
+ }
2394
+ function parseStreamLine(line) {
2395
+ const trimmed = line.trim();
2396
+ if (trimmed.length === 0) return null;
2397
+ try {
2398
+ return JSON.parse(trimmed);
2399
+ } catch {
2400
+ return null;
2401
+ }
2402
+ }
2403
+ function normalizeStreamEvent(raw) {
2404
+ const r = asObj(raw);
2405
+ if (!r) return null;
2406
+ const type = r.type;
2407
+ if (type === "system") {
2408
+ if (r.subtype === "init") {
2409
+ return { kind: "init", sessionId: str(r.session_id), model: str(r.model) };
2410
+ }
2411
+ return { kind: "other" };
2412
+ }
2413
+ if (type === "assistant") {
2414
+ const message = asObj(r.message);
2415
+ return {
2416
+ kind: "assistant",
2417
+ messageId: message ? str(message.id) : void 0,
2418
+ text: messageText(message),
2419
+ usage: message ? usageFrom(message.usage) : void 0
2420
+ };
2421
+ }
2422
+ if (type === "result") {
2423
+ return {
2424
+ kind: "result",
2425
+ isError: r.is_error === true,
2426
+ durationMs: num(r.duration_ms),
2427
+ numTurns: num(r.num_turns),
2428
+ totalCostUsd: num(r.total_cost_usd),
2429
+ usage: usageFrom(r.usage)
2430
+ };
2431
+ }
2432
+ return { kind: "other" };
2433
+ }
2434
+ var UsageReducer = class {
2435
+ maxByMessage = /* @__PURE__ */ new Map();
2436
+ input = 0;
2437
+ output = 0;
2438
+ costUsd = 0;
2439
+ turns = 0;
2440
+ /** Feed one normalized event into the reducer. */
2441
+ add(event) {
2442
+ if (event.kind === "assistant" && event.messageId && event.usage) {
2443
+ const key = event.messageId;
2444
+ const input = event.usage.inputTokens ?? 0;
2445
+ const output = event.usage.outputTokens ?? 0;
2446
+ const prev = this.maxByMessage.get(key);
2447
+ if (!prev) {
2448
+ this.maxByMessage.set(key, { input, output });
2449
+ this.input += input;
2450
+ this.output += output;
2451
+ } else {
2452
+ this.input += Math.max(0, input - prev.input);
2453
+ this.output += Math.max(0, output - prev.output);
2454
+ this.maxByMessage.set(key, {
2455
+ input: Math.max(prev.input, input),
2456
+ output: Math.max(prev.output, output)
2457
+ });
2458
+ }
2459
+ } else if (event.kind === "result") {
2460
+ if (event.totalCostUsd !== void 0) this.costUsd = event.totalCostUsd;
2461
+ if (event.numTurns !== void 0) this.turns = event.numTurns;
2462
+ }
2463
+ }
2464
+ /** The current accumulator snapshot. */
2465
+ snapshot() {
2466
+ return {
2467
+ inputTokens: this.input,
2468
+ outputTokens: this.output,
2469
+ totalCostUsd: this.costUsd,
2470
+ numTurns: this.turns
2471
+ };
2472
+ }
2473
+ };
2474
+ function runHost(opts) {
2475
+ const spec = resolveHostRun(opts.host, opts.customBinary);
2476
+ return new Promise((resolve, reject) => {
2477
+ if (!spec) {
2478
+ reject(new Error(`host '${opts.host}' is not a spawnable CLI (no default command)`));
2479
+ return;
2480
+ }
2481
+ const child = spawn(spec.binary, [...spec.flags, opts.prompt], {
2482
+ stdio: ["ignore", "pipe", "pipe"]
2483
+ });
2484
+ const reducer = new UsageReducer();
2485
+ let eventCount = 0;
2486
+ let stderrBuf = "";
2487
+ child.on("error", (err) => reject(err));
2488
+ const rl = createInterface({ input: child.stdout });
2489
+ rl.on("line", (line) => {
2490
+ opts.onLine?.(line);
2491
+ const raw = parseStreamLine(line);
2492
+ if (raw === null) return;
2493
+ const event = normalizeStreamEvent(raw);
2494
+ if (event === null) return;
2495
+ eventCount += 1;
2496
+ reducer.add(event);
2497
+ opts.onEvent?.(event);
2498
+ });
2499
+ child.stderr.setEncoding("utf8");
2500
+ child.stderr.on("data", (chunk) => {
2501
+ stderrBuf += chunk;
2502
+ });
2503
+ child.on("close", (code) => {
2504
+ resolve({ exitCode: code ?? 0, usage: reducer.snapshot(), eventCount, stderr: stderrBuf });
2505
+ });
2506
+ });
2507
+ }
2508
+
2509
+ // src/commands/run.ts
2510
+ function formatUsage(u) {
2511
+ const cost = u.totalCostUsd > 0 ? ` $${u.totalCostUsd.toFixed(2)}` : "";
2512
+ const turns = u.numTurns > 0 ? ` \xB7 ${u.numTurns} turns` : "";
2513
+ return `${u.inputTokens.toLocaleString()} in / ${u.outputTokens.toLocaleString()} out${cost}${turns}`;
2514
+ }
2515
+ function streamEvent(event, opts) {
2516
+ if (opts.json === true) return;
2517
+ if (event.kind === "assistant" && event.text && event.text.length > 0) {
2518
+ process.stdout.write(event.text);
2519
+ }
2520
+ }
2521
+ async function run(prompt, opts) {
2522
+ const host = opts.host ?? "claude";
2523
+ if (!SUPPORTED_HOSTS.includes(host)) {
2524
+ fail(EXIT.USAGE, `unknown host '${host}' (supported: ${SUPPORTED_HOSTS.join(", ")})`, opts);
2525
+ }
2526
+ if (prompt.length === 0) {
2527
+ fail(EXIT.USAGE, "a prompt is required: `noir run <prompt>`", opts);
2528
+ }
2529
+ const transcriptLines = [];
2530
+ try {
2531
+ const result = await runHost({
2532
+ host,
2533
+ prompt,
2534
+ customBinary: opts.command,
2535
+ onLine: (line) => transcriptLines.push(line),
2536
+ onEvent: (event) => streamEvent(event, opts)
2537
+ });
2538
+ if (opts.json !== true) {
2539
+ process.stdout.write("\n");
2540
+ }
2541
+ const transcript = writeTranscript(host, transcriptLines);
2542
+ if (opts.json === true) {
2543
+ json({
2544
+ ok: true,
2545
+ data: {
2546
+ host,
2547
+ prompt,
2548
+ exitCode: result.exitCode,
2549
+ usage: result.usage,
2550
+ numTurns: result.usage.numTurns,
2551
+ events: result.eventCount,
2552
+ transcript
2553
+ }
2554
+ });
2555
+ return;
2556
+ }
2557
+ if (result.exitCode !== 0) {
2558
+ log(`host exited ${result.exitCode}`, opts);
2559
+ if (result.stderr.length > 0) log(result.stderr.trim(), opts);
2560
+ }
2561
+ success(`usage: ${formatUsage(result.usage)} (API-equivalent estimate, not billed)`, opts);
2562
+ log(`transcript: ${transcript}`, opts);
2563
+ } catch (err) {
2564
+ fail(
2565
+ EXIT.ERROR,
2566
+ `failed to run host '${host}': ${err instanceof Error ? err.message : String(err)}`,
2567
+ opts
2568
+ );
2569
+ }
2570
+ }
2571
+ function writeTranscript(host, lines) {
2572
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2573
+ const dir = join2(process.cwd(), ".noir", "transcripts");
2574
+ const file = join2(dir, `${host}-${ts}.jsonl`);
2575
+ try {
2576
+ mkdirSync2(dir, { recursive: true });
2577
+ writeFileSync2(file, `${lines.join("\n")}${lines.length > 0 ? "\n" : ""}`);
2578
+ } catch {
2579
+ return "(not persisted)";
2580
+ }
2581
+ return file;
2582
+ }
2583
+
2323
2584
  // src/commands/skills.ts
2324
2585
  import { claudeAdapter } from "@noir-ai/adapters";
2325
2586
  import { loadProjectInfo as loadProjectInfo7 } from "@noir-ai/core";
@@ -2612,8 +2873,8 @@ function createProgram() {
2612
2873
  // them), errors → stderr. --json data is emitted by command actions via
2613
2874
  // process.stdout directly, NOT by commander's writeOut, so routing help/
2614
2875
  // version to stdout does not conflict with --json payloads.
2615
- writeOut: (str) => process.stdout.write(str),
2616
- writeErr: (str) => process.stderr.write(str)
2876
+ writeOut: (str2) => process.stdout.write(str2),
2877
+ writeErr: (str2) => process.stderr.write(str2)
2617
2878
  });
2618
2879
  program2.hook("preAction", (_thisCmd, actionCmd) => {
2619
2880
  const opts = actionCmd.optsWithGlobals();
@@ -2644,7 +2905,7 @@ function createProgram() {
2644
2905
  new Option(
2645
2906
  "--host <id>",
2646
2907
  "target agentic CLI (default: claude) \u2014 drives host-side emission"
2647
- ).choices(SUPPORTED_HOSTS)
2908
+ ).choices(SUPPORTED_HOSTS2)
2648
2909
  ).action(
2649
2910
  async (opts, cmd) => {
2650
2911
  const transport = opts.transport === "streamable-http" ? "streamable-http" : "stdio";
@@ -2669,7 +2930,7 @@ function createProgram() {
2669
2930
  new Option(
2670
2931
  "--host <id>",
2671
2932
  "target agentic CLI (default: claude) \u2014 drives host-side emission"
2672
- ).choices(SUPPORTED_HOSTS)
2933
+ ).choices(SUPPORTED_HOSTS2)
2673
2934
  ).action(
2674
2935
  async (dir, opts, cmd) => {
2675
2936
  const transport = opts.transport === "streamable-http" ? "streamable-http" : "stdio";
@@ -2712,7 +2973,7 @@ function createProgram() {
2712
2973
  new Option(
2713
2974
  "--host <id>",
2714
2975
  "override the configured host (advanced; default reads .noir/config.yml)"
2715
- ).choices(SUPPORTED_HOSTS)
2976
+ ).choices(SUPPORTED_HOSTS2)
2716
2977
  ).action(
2717
2978
  async (opts, cmd) => {
2718
2979
  const { sync } = await import("./sync-FKWXQOWT.js");
@@ -3031,7 +3292,7 @@ function createProgram() {
3031
3292
  );
3032
3293
  installCmd.alias("migrate");
3033
3294
  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) => {
3034
- const { update } = await import("./update-6P7SEZ67.js");
3295
+ const { update } = await import("./update-S3IZXXKO.js");
3035
3296
  const cli = cmd.optsWithGlobals();
3036
3297
  await update({ ...cli, spec, check: opts.check === true });
3037
3298
  });
@@ -3092,6 +3353,16 @@ function createProgram() {
3092
3353
  };
3093
3354
  await runPalette(opts, dispatch);
3094
3355
  });
3356
+ program2.command("run").description("drive the host CLI headless and render its stream-json (v2)").argument("[prompt...]", "prompt to send to the host").addOption(new Option("--host <id>", "host to drive (default claude)").choices(SUPPORTED_HOSTS2)).option("--command <binary>", "custom host binary (e.g. claude-work)").action(async (...args) => {
3357
+ const g = trailingCmd(args).optsWithGlobals();
3358
+ const prompt = args.filter((a) => typeof a === "string").join(" ").trim();
3359
+ const opts = {
3360
+ ...toCliOptions(g),
3361
+ ...typeof g.host === "string" ? { host: g.host } : {},
3362
+ ...typeof g.command === "string" ? { command: g.command } : {}
3363
+ };
3364
+ await run(prompt, opts);
3365
+ });
3095
3366
  program2.action(async (...args) => {
3096
3367
  const cmd = trailingCmd(args);
3097
3368
  const leftovers = cmd.args;
@@ -3121,7 +3392,7 @@ var homeDeps = {
3121
3392
  // uses — so the menu cannot drift from the commander tree.
3122
3393
  commands: buildPaletteCommands(createProgram())
3123
3394
  };
3124
- async function run(argv = []) {
3395
+ async function run2(argv = []) {
3125
3396
  try {
3126
3397
  await program.parseAsync([...argv], { from: "user" });
3127
3398
  } catch (err) {
@@ -3139,7 +3410,7 @@ var isMainModule = (() => {
3139
3410
  }
3140
3411
  })();
3141
3412
  if (isMainModule) {
3142
- void run(process.argv.slice(2));
3413
+ void run2(process.argv.slice(2));
3143
3414
  }
3144
3415
  export {
3145
3416
  DEPRECATIONS,
@@ -3151,6 +3422,6 @@ export {
3151
3422
  fail,
3152
3423
  inferExitCode,
3153
3424
  program,
3154
- run
3425
+ run2 as run
3155
3426
  };
3156
3427
  //# sourceMappingURL=bin.js.map