@noir-ai/cli 1.2.0-beta.2 → 1.3.0-beta.2

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.js CHANGED
@@ -1,26 +1,28 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ doctor,
4
+ serve
5
+ } from "./chunk-J5HAAQI4.js";
6
+ import {
7
+ init
8
+ } from "./chunk-J2Y3BOVK.js";
2
9
  import {
3
10
  EXIT,
4
11
  NoirCliError,
5
- doctor,
6
12
  fail,
7
13
  handleError,
8
14
  inferExitCode,
9
15
  info,
10
16
  isInteractive,
11
17
  log,
12
- serve,
13
18
  table,
14
19
  warn
15
- } from "./chunk-I3L3KNJM.js";
16
- import {
17
- init
18
- } from "./chunk-7WKKZ452.js";
20
+ } from "./chunk-OF4OO5W6.js";
19
21
 
20
22
  // src/bin.ts
21
23
  import { realpathSync } from "fs";
22
24
  import { pathToFileURL } from "url";
23
- import { SUPPORTED_HOSTS } from "@noir-ai/adapters";
25
+ import { SUPPORTED_HOSTS as SUPPORTED_HOSTS2 } from "@noir-ai/adapters";
24
26
  import { NOIR_VERSION as NOIR_VERSION3 } from "@noir-ai/core";
25
27
  import { Command, Option } from "commander";
26
28
 
@@ -460,15 +462,50 @@ async function daemonRestart(opts) {
460
462
  }
461
463
 
462
464
  // src/commands/home.ts
465
+ import { SUPPORTED_HOSTS } from "@noir-ai/adapters";
463
466
  import { loadProjectInfo as loadProjectInfo3 } from "@noir-ai/core";
467
+
468
+ // src/banner.ts
469
+ import pc from "picocolors";
470
+ var NOIR_BLOCK = [
471
+ "\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 ",
472
+ "\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557",
473
+ "\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551",
474
+ "\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551",
475
+ "\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551",
476
+ "\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D"
477
+ ];
478
+ var ROW_COLORS = [pc.magenta, pc.magentaBright, pc.blue, pc.blueBright, pc.cyan, pc.cyanBright];
479
+ var NOIR_TAGLINE = "discipline, context, and memory layer for agentic CLIs";
480
+ function renderBanner(opts = {}) {
481
+ const width = opts.width ?? process.stdout.columns ?? 80;
482
+ const color = opts.color ?? true;
483
+ if (width < 50) {
484
+ return color ? `${pc.magenta("\u25C6")} noir` : "\u25C6 noir";
485
+ }
486
+ return NOIR_BLOCK.map((line, i) => color ? (ROW_COLORS[i] ?? pc.cyan)(line) : line).join("\n");
487
+ }
488
+ function shouldShowBanner(opts) {
489
+ if (opts.quiet === true || opts.json === true) return false;
490
+ const v = process.env.NOIR_NO_BANNER;
491
+ if (v !== void 0 && v !== "") return false;
492
+ return true;
493
+ }
494
+
495
+ // src/commands/home.ts
464
496
  function tryProject() {
465
497
  try {
466
498
  const info2 = loadProjectInfo3(process.cwd());
467
- return { id: info2.id, name: info2.name };
499
+ return { id: info2.id, name: info2.name, host: info2.config.host };
468
500
  } catch {
469
501
  return null;
470
502
  }
471
503
  }
504
+ var COMMANDS_HINT = "Commands: init \xB7 create \xB7 sync \xB7 status \xB7 context \xB7 memory \xB7 skills \xB7 task \xB7 daemon \xB7 doctor";
505
+ function hostDirection(host) {
506
+ const others = SUPPORTED_HOSTS.filter((h) => h !== host);
507
+ return `\u2192 host: ${host}. Open \`${host}\` to start development \u2014 Noir set the rules, skills, and memory; ${host} runs the code. (other hosts: ${others.join(", ")})`;
508
+ }
472
509
  async function home(opts, deps) {
473
510
  if (isInteractive(opts)) {
474
511
  await runMenu(opts, deps);
@@ -483,8 +520,20 @@ async function home(opts, deps) {
483
520
  async function runMenu(opts, deps) {
484
521
  const clack = await import("@clack/prompts");
485
522
  const project = tryProject();
486
- const banner = project ? `noir \u2014 ${project.name}` : "noir";
487
- clack.intro(banner);
523
+ if (shouldShowBanner(opts)) {
524
+ const host = project?.host ?? "claude";
525
+ process.stderr.write(
526
+ `
527
+ ${renderBanner()}
528
+ ${NOIR_TAGLINE}
529
+
530
+ ${hostDirection(host)}
531
+ ${COMMANDS_HINT}
532
+
533
+ `
534
+ );
535
+ }
536
+ clack.intro(project ? `noir \u2014 ${project.name}` : "noir");
488
537
  const choice = await clack.select({
489
538
  message: "What would you like to do?",
490
539
  initialValue: "status",
@@ -1240,11 +1289,14 @@ function createProgram() {
1240
1289
  );
1241
1290
  }
1242
1291
  }
1292
+ const nonInteractive = opts.json === true || opts.input === false;
1293
+ if (nonInteractive) process.env.NOIR_NON_INTERACTIVE = "1";
1294
+ else delete process.env.NOIR_NON_INTERACTIVE;
1243
1295
  });
1244
1296
  program2.command("init").description("scaffold Noir in the current project (.noir/, .mcp.json, CLAUDE.md, skills)").option("--transport <transport>", "stdio | streamable-http (default: stdio)", "stdio").option("--url <url>", "streamable-http daemon URL (localhost only)").option(
1245
1297
  "--upgrade",
1246
1298
  "run scaffold migrations before re-emitting (re-run on an existing project)"
1247
- ).addOption(
1299
+ ).option("--force", "re-scaffold even if already initialized (bypasses the already-init no-op)").addOption(
1248
1300
  // S10: target host. Defaults to `'claude'` (the regression anchor). The
1249
1301
  // chosen host is forwarded to scaffold() + skills emission via
1250
1302
  // resolveAdapter. The choice list mirrors `SUPPORTED_HOSTS` so a new host
@@ -1252,39 +1304,49 @@ function createProgram() {
1252
1304
  new Option(
1253
1305
  "--host <id>",
1254
1306
  "target agentic CLI (default: claude) \u2014 drives host-side emission"
1255
- ).choices(SUPPORTED_HOSTS)
1307
+ ).choices(SUPPORTED_HOSTS2)
1256
1308
  ).action(
1257
1309
  async (opts) => {
1258
1310
  const transport = opts.transport === "streamable-http" ? "streamable-http" : "stdio";
1259
1311
  const upgrade = opts.upgrade === true;
1312
+ const force = opts.force === true;
1260
1313
  const host = parseHost(opts.host);
1261
1314
  await init(process.cwd(), {
1262
1315
  transport,
1263
1316
  url: opts.url,
1264
1317
  ...upgrade ? { upgrade } : {},
1318
+ ...force ? { force } : {},
1265
1319
  ...host !== void 0 ? { host } : {}
1266
1320
  });
1267
1321
  }
1268
1322
  );
1269
- program2.command("create [dir]").description("bootstrap the Noir AI layer in a new or empty directory").option("--transport <transport>", "stdio | streamable-http (default: stdio)", "stdio").option("--url <url>", "streamable-http daemon URL (localhost only)").addOption(
1323
+ program2.command("create [dir]").description("bootstrap the Noir AI layer in a new or empty directory").option("--transport <transport>", "stdio | streamable-http (default: stdio)", "stdio").option("--url <url>", "streamable-http daemon URL (localhost only)").option("--force", "re-scaffold even if already initialized (bypasses the already-init no-op)").addOption(
1270
1324
  new Option(
1271
1325
  "--host <id>",
1272
1326
  "target agentic CLI (default: claude) \u2014 drives host-side emission"
1273
- ).choices(SUPPORTED_HOSTS)
1327
+ ).choices(SUPPORTED_HOSTS2)
1274
1328
  ).action(
1275
1329
  async (dir, opts) => {
1276
1330
  const transport = opts.transport === "streamable-http" ? "streamable-http" : "stdio";
1331
+ const force = opts.force === true;
1277
1332
  const host = parseHost(opts.host);
1278
- const { create } = await import("./create-ZK2EBXG3.js");
1333
+ const { create } = await import("./create-VK7JX6IB.js");
1279
1334
  await create(dir, {
1280
1335
  transport,
1281
1336
  url: opts.url,
1337
+ ...force ? { force } : {},
1282
1338
  ...host !== void 0 ? { host } : {}
1283
1339
  });
1284
1340
  }
1285
1341
  );
1286
1342
  program2.command("sync").description(
1287
1343
  "re-emit Noir managed files (.mcp.json, CLAUDE.md blocks, NOIR.md brief, ignores) + skills"
1344
+ ).option(
1345
+ "--force",
1346
+ "overwrite differing regenerated files without prompting (bypasses the conflict menu)"
1347
+ ).option(
1348
+ "--merge",
1349
+ "three-way merge managed regions (preserve hand-edits inside <!-- noir:* --> markers)"
1288
1350
  ).addOption(
1289
1351
  // S10: optional `--host` override. When omitted, sync reads host from
1290
1352
  // `.noir/config.yml` (whatever `noir init --host <id>` persisted). The
@@ -1292,14 +1354,20 @@ function createProgram() {
1292
1354
  new Option(
1293
1355
  "--host <id>",
1294
1356
  "override the configured host (advanced; default reads .noir/config.yml)"
1295
- ).choices(SUPPORTED_HOSTS)
1357
+ ).choices(SUPPORTED_HOSTS2)
1296
1358
  ).action(async (opts) => {
1297
- const { sync } = await import("./sync-IEH2BVXU.js");
1359
+ const { sync } = await import("./sync-7XFGCRJN.js");
1298
1360
  const host = parseHost(opts.host);
1299
- if (host === void 0) {
1361
+ const force = opts.force === true;
1362
+ const merge = opts.merge === true;
1363
+ if (host === void 0 && !force && !merge) {
1300
1364
  await sync(process.cwd());
1301
1365
  } else {
1302
- await sync(process.cwd(), { host });
1366
+ await sync(process.cwd(), {
1367
+ ...host !== void 0 ? { host } : {},
1368
+ ...force ? { force } : {},
1369
+ ...merge ? { merge } : {}
1370
+ });
1303
1371
  }
1304
1372
  });
1305
1373
  const mcpCmd = program2.command("mcp").description("MCP server control");
@@ -1328,8 +1396,13 @@ function createProgram() {
1328
1396
  daemonGrp.action(() => {
1329
1397
  throw new NoirCliError(EXIT.USAGE, "Usage: noir daemon start|stop|status|restart");
1330
1398
  });
1331
- program2.command("doctor").description("environment + project health").action(async (...args) => {
1332
- await doctor(toCliOptions(actionGlobals(args)));
1399
+ program2.command("doctor").description("environment + project health").option(
1400
+ "--dedup",
1401
+ "scan host-context + .noir/ docs for semantic near-duplicates (loads the local embedder)"
1402
+ ).action(async (...args) => {
1403
+ const g = actionGlobals(args);
1404
+ const dedup = trailingCmd(args).opts().dedup === true;
1405
+ await doctor({ ...toCliOptions(g), ...dedup ? { dedup: true } : {} });
1333
1406
  });
1334
1407
  program2.command("status").description("project + daemon + workflow + store snapshot").action(async (...args) => {
1335
1408
  await status(toStatusOptions(actionGlobals(args)));