opennori 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +5 -0
  2. package/package.json +1 -1
  3. package/src/cli.js +101 -3
package/README.md CHANGED
@@ -28,6 +28,9 @@ judgment.
28
28
  npx opennori
29
29
  ```
30
30
 
31
+ It shows a short project setup preview and asks before writing `.opennori/` or the OpenNori Skill
32
+ Pack. Agents and CI can add `--json` for the deterministic machine-readable protocol.
33
+
31
34
  For a project install:
32
35
 
33
36
  ```bash
@@ -82,6 +85,8 @@ language requests to the deterministic CLI state layer.
82
85
  - `bootstrap` gives agents one short entry for readiness checks and first-time preview. Lower-level
83
86
  `install`, `upgrade`, and `uninstall` still support preview-first workflows; destructive writes
84
87
  require explicit confirmation.
88
+ - In a human terminal, `npx opennori` is interactive. With `--json` or non-interactive stdio it
89
+ returns structured JSON for agents and automation.
85
90
  - `doctor` reports whether project state is `ready`, `needs-action`, or `broken`, with recovery
86
91
  actions.
87
92
  - Nori Profile records required Skills, preferred stacks, avoided tools, and install policy without
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opennori",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "OpenNori: human-centered Nori Contracts and evidence records for coding agents.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -375,6 +375,10 @@ function printJson(payload) {
375
375
  console.log(JSON.stringify(payload, null, 2));
376
376
  }
377
377
 
378
+ function printText(line = "") {
379
+ process.stdout.write(`${line}\n`);
380
+ }
381
+
378
382
  function argValue(args, name, fallback = undefined) {
379
383
  const index = args.indexOf(name);
380
384
  if (index === -1 || index + 1 >= args.length) return fallback;
@@ -385,6 +389,14 @@ function hasFlag(args, name) {
385
389
  return args.includes(name);
386
390
  }
387
391
 
392
+ function wantsJson(args) {
393
+ return hasFlag(args, "--json");
394
+ }
395
+
396
+ function isInteractive(args) {
397
+ return !wantsJson(args) && process.stdin.isTTY && process.stdout.isTTY;
398
+ }
399
+
388
400
  const CLI_NAME = "opennori";
389
401
  const TOP_LEVEL_USAGE = `${CLI_NAME} <bootstrap|doctor|install|upgrade|uninstall|brainstorm|draft|init|list|check|approve|criterion|profile|resume|next|evidence|evaluate|status|report|context|changes|archive|skill>`;
390
402
 
@@ -419,6 +431,94 @@ function usageFor(args) {
419
431
  return TOP_LEVEL_USAGE;
420
432
  }
421
433
 
434
+ function describeBootstrapAction(action) {
435
+ if (action.action === "create") return `create ${action.path}`;
436
+ if (action.action === "skip") return `keep existing ${action.path}`;
437
+ if (action.action === "exists") return `already exists ${action.path}`;
438
+ if (action.action === "update") return `update ${action.path}`;
439
+ if (action.action === "overwrite") return `overwrite ${action.path}`;
440
+ return `${action.action} ${action.path}`;
441
+ }
442
+
443
+ function printBootstrapPreview(payload) {
444
+ const data = payload.data;
445
+ printText("");
446
+ printText("OpenNori project setup");
447
+ printText(`Project: ${data.root}`);
448
+ printText("");
449
+
450
+ if (data.status === "ready") {
451
+ printText("OpenNori is already ready in this project.");
452
+ printText("Next: tell your agent the goal and ask it to use OpenNori.");
453
+ return;
454
+ }
455
+
456
+ printText("This will prepare OpenNori for this project:");
457
+ for (const action of data.install_plan.actions.filter((item) => item.would_write).slice(0, 8)) {
458
+ printText(`- ${describeBootstrapAction(action)}`);
459
+ }
460
+ const remaining = data.install_plan.summary.would_write - Math.min(data.install_plan.summary.would_write, 8);
461
+ if (remaining > 0) printText(`- plus ${remaining} more OpenNori project assets`);
462
+ printText("");
463
+ printText("No files have been written yet.");
464
+ }
465
+
466
+ function printBootstrapResult(payload) {
467
+ const data = payload.data;
468
+ printText("");
469
+ if (data.status === "installed") {
470
+ printText("OpenNori installed.");
471
+ printText(`Created or refreshed ${data.install_plan.summary.will_write} project assets.`);
472
+ printText("Next: tell your agent the goal and ask it to use OpenNori.");
473
+ return;
474
+ }
475
+ if (data.status === "ready") {
476
+ printText("OpenNori is ready.");
477
+ printText("Next: tell your agent the goal and ask it to use OpenNori.");
478
+ return;
479
+ }
480
+ printText(data.next || "OpenNori bootstrap finished.");
481
+ }
482
+
483
+ async function promptConfirm(message) {
484
+ process.stdout.write(`${message} [y/N] `);
485
+ return new Promise((resolve) => {
486
+ process.stdin.setEncoding("utf8");
487
+ process.stdin.once("data", (chunk) => {
488
+ process.stdin.pause();
489
+ resolve(/^y(es)?$/i.test(String(chunk).trim()));
490
+ });
491
+ });
492
+ }
493
+
494
+ async function runBootstrap(args) {
495
+ const root = resolveRoot(args);
496
+ const confirmed = hasFlag(args, "--confirm");
497
+
498
+ if (!isInteractive(args)) {
499
+ printJson(bootstrap(root, { confirmed }));
500
+ return;
501
+ }
502
+
503
+ if (confirmed) {
504
+ printBootstrapResult(bootstrap(root, { confirmed: true }));
505
+ return;
506
+ }
507
+
508
+ const preview = bootstrap(root, { confirmed: false });
509
+ printBootstrapPreview(preview);
510
+ if (preview.data.status === "ready") return;
511
+
512
+ const shouldInstall = await promptConfirm("Install OpenNori here?");
513
+ if (!shouldInstall) {
514
+ printText("");
515
+ printText("No changes made.");
516
+ return;
517
+ }
518
+
519
+ printBootstrapResult(bootstrap(root, { confirmed: true }));
520
+ }
521
+
422
522
  function argValues(args, name) {
423
523
  const values = [];
424
524
  for (let index = 0; index < args.length; index += 1) {
@@ -1495,9 +1595,7 @@ export async function main(args) {
1495
1595
  }
1496
1596
 
1497
1597
  if (command === "bootstrap") {
1498
- const root = resolveRoot(args);
1499
- const confirmed = hasFlag(args, "--confirm");
1500
- printJson(bootstrap(root, { confirmed }));
1598
+ await runBootstrap(args);
1501
1599
  return;
1502
1600
  }
1503
1601