@noir-ai/cli 1.3.0-beta.6 → 1.4.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/README.md +7 -7
- package/dist/bin.d.ts +30 -2
- package/dist/bin.js +869 -582
- package/dist/bin.js.map +1 -1
- package/dist/chunk-A5W2E3X2.js +33 -0
- package/dist/chunk-A5W2E3X2.js.map +1 -0
- package/dist/{chunk-J2Y3BOVK.js → chunk-SVFQGAOF.js} +32 -8
- package/dist/chunk-SVFQGAOF.js.map +1 -0
- package/dist/{chunk-3QMQXBII.js → chunk-YES5UCU5.js} +648 -40
- package/dist/chunk-YES5UCU5.js.map +1 -0
- package/dist/{create-VK7JX6IB.js → create-7ERBL42K.js} +31 -7
- package/dist/create-7ERBL42K.js.map +1 -0
- package/dist/index.d.ts +9 -2
- package/dist/index.js +5 -4
- package/dist/{output-De3lKbZq.d.ts → output-BNE9htEm.d.ts} +9 -0
- package/dist/sync-SCAF6Z5H.js +82 -0
- package/dist/sync-SCAF6Z5H.js.map +1 -0
- package/dist/tui/chunk-OMF7XZMN.js +99 -0
- package/dist/tui/chunk-OMF7XZMN.js.map +1 -0
- package/dist/tui/chunk-YNEBQJXA.js +1243 -0
- package/dist/tui/chunk-YNEBQJXA.js.map +1 -0
- package/dist/tui/create-U2YJH5QC.js +72 -0
- package/dist/tui/create-U2YJH5QC.js.map +1 -0
- package/dist/tui/index.js +2253 -0
- package/dist/tui/index.js.map +1 -0
- package/dist/tui/sync-VYRXCGK4.js +81 -0
- package/dist/tui/sync-VYRXCGK4.js.map +1 -0
- package/package.json +15 -11
- package/dist/chunk-3QMQXBII.js.map +0 -1
- package/dist/chunk-J2Y3BOVK.js.map +0 -1
- package/dist/chunk-OF4OO5W6.js +0 -190
- package/dist/chunk-OF4OO5W6.js.map +0 -1
- package/dist/create-VK7JX6IB.js.map +0 -1
- package/dist/sync-7XFGCRJN.js +0 -55
- package/dist/sync-7XFGCRJN.js.map +0 -1
package/dist/bin.js
CHANGED
|
@@ -1,28 +1,36 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
doctor,
|
|
4
3
|
serve
|
|
5
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-A5W2E3X2.js";
|
|
6
5
|
import {
|
|
7
6
|
init
|
|
8
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-SVFQGAOF.js";
|
|
9
8
|
import {
|
|
10
9
|
EXIT,
|
|
11
10
|
NoirCliError,
|
|
11
|
+
accessibleMode,
|
|
12
|
+
badge,
|
|
13
|
+
c,
|
|
14
|
+
definitionList,
|
|
15
|
+
doctor,
|
|
12
16
|
fail,
|
|
13
17
|
handleError,
|
|
14
18
|
inferExitCode,
|
|
15
19
|
info,
|
|
16
20
|
isInteractive,
|
|
21
|
+
json,
|
|
17
22
|
log,
|
|
23
|
+
requireInteractive,
|
|
18
24
|
table,
|
|
25
|
+
tip,
|
|
26
|
+
useColor,
|
|
19
27
|
warn
|
|
20
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-YES5UCU5.js";
|
|
21
29
|
|
|
22
30
|
// src/bin.ts
|
|
23
31
|
import { realpathSync } from "fs";
|
|
24
32
|
import { pathToFileURL } from "url";
|
|
25
|
-
import { SUPPORTED_HOSTS
|
|
33
|
+
import { SUPPORTED_HOSTS } from "@noir-ai/adapters";
|
|
26
34
|
import { NOIR_VERSION as NOIR_VERSION3 } from "@noir-ai/core";
|
|
27
35
|
import { Command, Option } from "commander";
|
|
28
36
|
|
|
@@ -219,7 +227,7 @@ async function contextSearch(opts) {
|
|
|
219
227
|
renderSearch(data, opts);
|
|
220
228
|
}
|
|
221
229
|
function renderSearch(data, opts) {
|
|
222
|
-
const flag = data.degraded ? "
|
|
230
|
+
const flag = data.degraded ? ` ${badge("warn", "degraded: BM25-only")}` : "";
|
|
223
231
|
const trunc = data.truncated ? " (budget hit \u2014 results truncated)" : "";
|
|
224
232
|
log(
|
|
225
233
|
`context search \u2014 ${data.hits.length} hit${data.hits.length === 1 ? "" : "s"} \xB7 ${data.mode} \xB7 ${data.consumedTokens} tokens${flag}${trunc}`,
|
|
@@ -263,17 +271,16 @@ async function contextIndex(opts) {
|
|
|
263
271
|
}
|
|
264
272
|
function renderIndex(data, paths, opts) {
|
|
265
273
|
const scope = paths && paths.length > 0 ? paths.join(", ") : ".";
|
|
266
|
-
const flag = data.degraded ? "
|
|
274
|
+
const flag = data.degraded ? ` ${badge("warn", "degraded: vectors skipped")}` : "";
|
|
267
275
|
log(`context index \u2014 ${scope}${flag}`, opts);
|
|
268
|
-
|
|
276
|
+
definitionList(
|
|
269
277
|
[
|
|
270
|
-
{
|
|
271
|
-
{
|
|
272
|
-
{
|
|
273
|
-
{
|
|
274
|
-
{
|
|
278
|
+
{ label: "Indexed (new)", value: data.indexed },
|
|
279
|
+
{ label: "Skipped (unchanged)", value: data.skipped },
|
|
280
|
+
{ label: "Deleted (removed)", value: data.deleted },
|
|
281
|
+
{ label: "Failed", value: data.failed },
|
|
282
|
+
{ label: "Total chunks", value: data.totalChunks }
|
|
275
283
|
],
|
|
276
|
-
["Field", "Value"],
|
|
277
284
|
opts
|
|
278
285
|
);
|
|
279
286
|
if (data.failed > 0) {
|
|
@@ -297,18 +304,17 @@ function describeEmbedder(e) {
|
|
|
297
304
|
return `${e.kind} \xB7 ${model} (${e.dim}-dim)`;
|
|
298
305
|
}
|
|
299
306
|
function renderStatus(data, opts) {
|
|
300
|
-
const flag = data.degraded ? "
|
|
307
|
+
const flag = data.degraded ? ` ${badge("warn", "degraded")}` : "";
|
|
301
308
|
log(`context status \u2014 ${data.projectId}${flag}`, opts);
|
|
302
|
-
|
|
309
|
+
definitionList(
|
|
303
310
|
[
|
|
304
|
-
{
|
|
305
|
-
{
|
|
306
|
-
{
|
|
307
|
-
{
|
|
308
|
-
{
|
|
309
|
-
{
|
|
311
|
+
{ label: "Project", value: data.projectId },
|
|
312
|
+
{ label: "Docs", value: data.docCount },
|
|
313
|
+
{ label: "Vectors", value: data.vecCount },
|
|
314
|
+
{ label: "Indexed files", value: data.indexedFiles },
|
|
315
|
+
{ label: "Embedder", value: describeEmbedder(data.embedder) },
|
|
316
|
+
{ label: "Degraded", value: data.degraded ? "yes" : "no" }
|
|
310
317
|
],
|
|
311
|
-
["Field", "Value"],
|
|
312
318
|
opts
|
|
313
319
|
);
|
|
314
320
|
}
|
|
@@ -461,191 +467,676 @@ async function daemonRestart(opts) {
|
|
|
461
467
|
await daemonStart(opts);
|
|
462
468
|
}
|
|
463
469
|
|
|
464
|
-
// src/commands/
|
|
465
|
-
import {
|
|
466
|
-
import {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
"\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557",
|
|
474
|
-
"\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D",
|
|
475
|
-
"\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551",
|
|
476
|
-
"\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",
|
|
477
|
-
"\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D"
|
|
478
|
-
];
|
|
479
|
-
var NOIR_GRADIENT = gradient("#2c5282", "#3b82f6", "#7dd3fc");
|
|
480
|
-
var NOIR_TAGLINE = "discipline, context, and memory layer for agentic CLIs";
|
|
481
|
-
function renderBanner(opts = {}) {
|
|
482
|
-
const width = opts.width ?? process.stdout.columns ?? 80;
|
|
483
|
-
const color = opts.color ?? true;
|
|
484
|
-
if (width < 50) {
|
|
485
|
-
return color ? `${pc.magenta("\u25C6")} noir` : "\u25C6 noir";
|
|
486
|
-
}
|
|
487
|
-
const block = NOIR_BLOCK.join("\n");
|
|
488
|
-
return color ? NOIR_GRADIENT.multiline(block) : block;
|
|
489
|
-
}
|
|
490
|
-
function shouldShowBanner(opts) {
|
|
491
|
-
if (opts.quiet === true || opts.json === true) return false;
|
|
492
|
-
const v = process.env.NOIR_NO_BANNER;
|
|
493
|
-
if (v !== void 0 && v !== "") return false;
|
|
494
|
-
return true;
|
|
495
|
-
}
|
|
470
|
+
// src/commands/handoff.ts
|
|
471
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
472
|
+
import { join } from "path";
|
|
473
|
+
import {
|
|
474
|
+
defaultHandoffBlock,
|
|
475
|
+
hostLaunchDirective,
|
|
476
|
+
resolveAdapter
|
|
477
|
+
} from "@noir-ai/adapters";
|
|
478
|
+
import { loadProjectInfo as loadProjectInfo4, NOIR_DIR } from "@noir-ai/core";
|
|
496
479
|
|
|
497
|
-
// src/commands/
|
|
498
|
-
|
|
480
|
+
// src/commands/status.ts
|
|
481
|
+
import { loadProjectInfo as loadProjectInfo3, NOIR_VERSION as NOIR_VERSION2 } from "@noir-ai/core";
|
|
482
|
+
async function tryTool(caller, name, args) {
|
|
499
483
|
try {
|
|
500
|
-
|
|
501
|
-
return { id: info2.id, name: info2.name, host: info2.config.host };
|
|
484
|
+
return await caller.callTool(name, args);
|
|
502
485
|
} catch {
|
|
503
486
|
return null;
|
|
504
487
|
}
|
|
505
488
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
if (opts.json === true) {
|
|
517
|
-
await deps.dispatch(["status", "--json"]);
|
|
518
|
-
return;
|
|
519
|
-
}
|
|
520
|
-
await deps.dispatch(["status"]);
|
|
489
|
+
function pickStore(s) {
|
|
490
|
+
if (s?.ok !== true) return null;
|
|
491
|
+
return {
|
|
492
|
+
docCount: s.docCount,
|
|
493
|
+
vecCount: s.vecCount,
|
|
494
|
+
dbPath: s.dbPath,
|
|
495
|
+
degraded: s.degraded === true
|
|
496
|
+
};
|
|
521
497
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
const host = project?.host ?? "claude";
|
|
527
|
-
process.stderr.write(
|
|
528
|
-
`
|
|
529
|
-
${renderBanner()}
|
|
530
|
-
${NOIR_TAGLINE}
|
|
531
|
-
|
|
532
|
-
${hostDirection(host)}
|
|
533
|
-
${COMMANDS_HINT}
|
|
534
|
-
|
|
535
|
-
`
|
|
536
|
-
);
|
|
537
|
-
}
|
|
538
|
-
clack.intro(project ? `noir \u2014 ${project.name}` : "noir");
|
|
539
|
-
const choice = await clack.select({
|
|
540
|
-
message: "What would you like to do?",
|
|
541
|
-
initialValue: "status",
|
|
542
|
-
options: [
|
|
543
|
-
{ value: "status", label: "Status", hint: "project + daemon + store snapshot" },
|
|
544
|
-
{ value: "index", label: "Index project", hint: "(re)index files into context" },
|
|
545
|
-
{ value: "recall", label: "Recall memory", hint: "search cross-session memory" },
|
|
546
|
-
{ value: "next", label: "Next task", hint: "suggest next phase + skill" },
|
|
547
|
-
{ value: "daemon", label: "Start daemon", hint: "foreground daemon" },
|
|
548
|
-
{ value: "sync", label: "Sync skills", hint: "re-emit builtin skills" },
|
|
549
|
-
{ value: "exit", label: "Exit" }
|
|
550
|
-
]
|
|
551
|
-
});
|
|
552
|
-
if (clack.isCancel(choice)) {
|
|
553
|
-
clack.cancel("Cancelled.");
|
|
554
|
-
fail(EXIT.CANCELLED, "cancelled", opts);
|
|
555
|
-
}
|
|
556
|
-
const argv = await argvForChoice(choice, clack, opts);
|
|
557
|
-
if (argv === null) {
|
|
558
|
-
clack.outro("bye");
|
|
559
|
-
return;
|
|
560
|
-
}
|
|
561
|
-
await deps.dispatch(argv);
|
|
562
|
-
clack.outro("done");
|
|
498
|
+
function describeEmbedder2(e) {
|
|
499
|
+
if (!e || e.kind === "none") return "none (BM25-only)";
|
|
500
|
+
const model = typeof e.model === "string" && e.model.length > 0 ? e.model : "<unset>";
|
|
501
|
+
return `${e.kind} \xB7 ${model} (${e.dim}-dim)`;
|
|
563
502
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
return ["daemon", "start"];
|
|
574
|
-
case "sync":
|
|
575
|
-
return ["sync"];
|
|
576
|
-
case "exit":
|
|
577
|
-
return null;
|
|
578
|
-
case "recall": {
|
|
579
|
-
const query = await clack.text({
|
|
580
|
-
message: "Recall query:",
|
|
581
|
-
placeholder: "e.g. auth flow, ContextEngine, deploy steps"
|
|
582
|
-
});
|
|
583
|
-
if (clack.isCancel(query)) {
|
|
584
|
-
clack.cancel("Cancelled.");
|
|
585
|
-
fail(EXIT.CANCELLED, "cancelled", opts);
|
|
586
|
-
}
|
|
587
|
-
return ["memory", "recall", String(query)];
|
|
588
|
-
}
|
|
589
|
-
default:
|
|
590
|
-
return null;
|
|
591
|
-
}
|
|
503
|
+
function pickContext(c2) {
|
|
504
|
+
if (c2?.ok !== true) return null;
|
|
505
|
+
return {
|
|
506
|
+
docCount: c2.docCount,
|
|
507
|
+
vecCount: c2.vecCount,
|
|
508
|
+
indexedFiles: c2.indexedFiles,
|
|
509
|
+
embedder: describeEmbedder2(c2.embedder),
|
|
510
|
+
degraded: c2.degraded === true
|
|
511
|
+
};
|
|
592
512
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
513
|
+
function pickWorkflow(w) {
|
|
514
|
+
if (w?.ok !== true) return null;
|
|
515
|
+
return {
|
|
516
|
+
taskId: w.taskId,
|
|
517
|
+
phase: w.phase,
|
|
518
|
+
state: w.state,
|
|
519
|
+
mode: w.mode,
|
|
520
|
+
nextGate: w.nextGate,
|
|
521
|
+
degraded: w.degraded === true
|
|
522
|
+
};
|
|
602
523
|
}
|
|
603
|
-
function
|
|
604
|
-
if (
|
|
605
|
-
|
|
606
|
-
|
|
524
|
+
function pickMemory(m) {
|
|
525
|
+
if (m?.ok !== true || !Array.isArray(m.sessions)) return null;
|
|
526
|
+
let observations = 0;
|
|
527
|
+
for (const s of m.sessions) observations += s.count;
|
|
528
|
+
return { sessions: m.sessions.length, observations };
|
|
607
529
|
}
|
|
608
|
-
function
|
|
609
|
-
const h = raw ?? {};
|
|
610
|
-
const arr = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
|
|
530
|
+
function buildPayload(project, probe, host, store, context, workflow, memory) {
|
|
611
531
|
return {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
532
|
+
noir: NOIR_VERSION2,
|
|
533
|
+
project: { id: project.id, name: project.name },
|
|
534
|
+
host: project.config.host,
|
|
535
|
+
daemon: {
|
|
536
|
+
running: probe.running,
|
|
537
|
+
...probe.pid !== void 0 ? { pid: probe.pid } : {},
|
|
538
|
+
...probe.uptimeSec !== void 0 ? { uptimeSec: probe.uptimeSec } : {},
|
|
539
|
+
...host && typeof host.transport === "string" ? { transport: host.transport } : {}
|
|
540
|
+
},
|
|
541
|
+
store: pickStore(store),
|
|
542
|
+
context: pickContext(context),
|
|
543
|
+
workflow: pickWorkflow(workflow),
|
|
544
|
+
memory: pickMemory(memory)
|
|
621
545
|
};
|
|
622
546
|
}
|
|
623
|
-
function
|
|
624
|
-
|
|
625
|
-
|
|
547
|
+
function formatDuration(sec) {
|
|
548
|
+
if (typeof sec !== "number" || sec < 0) return "unknown";
|
|
549
|
+
if (sec < 60) return `${sec}s`;
|
|
550
|
+
if (sec < 3600) return `${Math.floor(sec / 60)}m ${sec % 60}s`;
|
|
551
|
+
return `${Math.floor(sec / 3600)}h ${Math.floor(sec % 3600 / 60)}m`;
|
|
626
552
|
}
|
|
627
|
-
function
|
|
628
|
-
if (
|
|
629
|
-
|
|
553
|
+
function describeDaemon(d) {
|
|
554
|
+
if (!d.running) return "not running (start with `noir daemon start`)";
|
|
555
|
+
const pid = typeof d.pid === "number" ? `pid ${d.pid}` : "pid unknown";
|
|
556
|
+
const up = typeof d.uptimeSec === "number" ? `, up ${formatDuration(d.uptimeSec)}` : "";
|
|
557
|
+
const transport = typeof d.transport === "string" ? `, ${d.transport}` : "";
|
|
558
|
+
return `running (${pid}${up}${transport})`;
|
|
630
559
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
const
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
if (
|
|
638
|
-
const
|
|
639
|
-
|
|
560
|
+
function describeStore(s) {
|
|
561
|
+
if (!s) return "unavailable (store engine not wired)";
|
|
562
|
+
const flag = s.degraded ? ` ${badge("warn", "degraded: read-only")}` : "";
|
|
563
|
+
return `${s.docCount} docs / ${s.vecCount} vecs${flag}`;
|
|
564
|
+
}
|
|
565
|
+
function describeContext(c2) {
|
|
566
|
+
if (!c2) return "unavailable (context engine not wired)";
|
|
567
|
+
const flag = c2.degraded ? ` ${badge("warn", "degraded")}` : "";
|
|
568
|
+
return `${c2.docCount} docs, ${c2.vecCount} vecs, ${c2.indexedFiles} files \xB7 embedder ${c2.embedder}${flag}`;
|
|
569
|
+
}
|
|
570
|
+
function describeWorkflow(w) {
|
|
571
|
+
if (!w) return "no active task";
|
|
572
|
+
const gate = w.nextGate ? ` \u2192 next gate: ${w.nextGate}` : "";
|
|
573
|
+
const flag = w.degraded ? ` ${badge("warn", "degraded")}` : "";
|
|
574
|
+
return `${w.phase} (${w.state}, ${w.mode}) \xB7 task ${w.taskId}${gate}${flag}`;
|
|
575
|
+
}
|
|
576
|
+
function describeMemory(m) {
|
|
577
|
+
if (!m) return "unavailable (memory engine not wired)";
|
|
578
|
+
return `${m.observations} observations across ${m.sessions} session${m.sessions === 1 ? "" : "s"}`;
|
|
579
|
+
}
|
|
580
|
+
function renderHuman(p, opts) {
|
|
581
|
+
log(`noir status \u2014 ${p.project.name} (${p.project.id})`, opts);
|
|
582
|
+
definitionList(
|
|
583
|
+
[
|
|
584
|
+
{ label: "Project", value: `${p.project.name} (${p.project.id})` },
|
|
585
|
+
{ label: "Host", value: p.host },
|
|
586
|
+
{ label: "Noir", value: p.noir },
|
|
587
|
+
{ label: "Daemon", value: describeDaemon(p.daemon) },
|
|
588
|
+
{ label: "Store", value: describeStore(p.store) },
|
|
589
|
+
{ label: "Context", value: describeContext(p.context) },
|
|
590
|
+
{ label: "Workflow", value: describeWorkflow(p.workflow) },
|
|
591
|
+
{ label: "Memory", value: describeMemory(p.memory) }
|
|
592
|
+
],
|
|
593
|
+
opts
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
async function gatherStatusPayload(opts) {
|
|
597
|
+
const project = loadProjectInfo3(process.cwd());
|
|
598
|
+
const probe = await probeDaemon(opts);
|
|
599
|
+
let host = null;
|
|
600
|
+
let store = null;
|
|
601
|
+
let context = null;
|
|
602
|
+
let workflow = null;
|
|
603
|
+
let memory = null;
|
|
604
|
+
if (probe.running) {
|
|
605
|
+
await withRunningDaemon(
|
|
606
|
+
opts,
|
|
607
|
+
async (caller) => {
|
|
608
|
+
host = await tryTool(caller, "host_status");
|
|
609
|
+
store = await tryTool(caller, "store_status");
|
|
610
|
+
context = await tryTool(caller, "context_status");
|
|
611
|
+
workflow = await tryTool(caller, "workflow_status");
|
|
612
|
+
memory = await tryTool(caller, "memory_sessions");
|
|
613
|
+
},
|
|
614
|
+
probe
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
return buildPayload(project, probe, host, store, context, workflow, memory);
|
|
618
|
+
}
|
|
619
|
+
async function status(opts) {
|
|
620
|
+
const payload = await gatherStatusPayload(opts);
|
|
640
621
|
if (opts.json === true) {
|
|
641
|
-
process.stdout.write(`${JSON.stringify({ ok: true, data })}
|
|
622
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: payload })}
|
|
642
623
|
`);
|
|
643
624
|
return;
|
|
644
625
|
}
|
|
645
|
-
|
|
626
|
+
renderHuman(payload, opts);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// src/commands/task.ts
|
|
630
|
+
var PHASE_SKILL = {
|
|
631
|
+
intake: "noir-intake",
|
|
632
|
+
clarify: "noir-clarify",
|
|
633
|
+
spec: "noir-spec",
|
|
634
|
+
plan: "noir-plan",
|
|
635
|
+
execute: "noir-execute",
|
|
636
|
+
verify: "noir-verify",
|
|
637
|
+
document: "noir-document"
|
|
638
|
+
};
|
|
639
|
+
function skillFor(phase) {
|
|
640
|
+
if (typeof phase !== "string") return null;
|
|
641
|
+
return PHASE_SKILL[phase] ?? null;
|
|
642
|
+
}
|
|
643
|
+
async function fetchStatus(opts, taskId) {
|
|
644
|
+
const args = typeof taskId === "string" && taskId.length > 0 ? { taskId } : {};
|
|
645
|
+
const res = await callDaemonTool(
|
|
646
|
+
opts,
|
|
647
|
+
"workflow_status",
|
|
648
|
+
args
|
|
649
|
+
);
|
|
650
|
+
if (res.ok === true) return res;
|
|
651
|
+
const detail = res.error ?? "task not found";
|
|
652
|
+
fail(EXIT.NOT_FOUND, `task: ${detail}`, opts);
|
|
653
|
+
}
|
|
654
|
+
function formatStamp(ms) {
|
|
655
|
+
if (typeof ms !== "number" || ms <= 0) return "-";
|
|
656
|
+
return new Date(ms).toISOString().replace("T", " ").replace(/\..*$/, "Z");
|
|
657
|
+
}
|
|
658
|
+
function renderStatusRow(s, opts) {
|
|
659
|
+
const flag = s.degraded === true ? ` ${badge("warn", "degraded")}` : "";
|
|
660
|
+
log(`task status \u2014 ${s.taskId}${flag}`, opts);
|
|
661
|
+
definitionList(
|
|
662
|
+
[
|
|
663
|
+
{ label: "Task", value: s.taskId },
|
|
664
|
+
{ label: "Phase", value: s.phase },
|
|
665
|
+
{ label: "State", value: s.state },
|
|
666
|
+
{ label: "Mode", value: s.mode },
|
|
667
|
+
{ label: "Next gate", value: s.nextGate ?? "\u2014" },
|
|
668
|
+
{ label: "Updated", value: formatStamp(s.updatedAt) }
|
|
669
|
+
],
|
|
670
|
+
opts
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
async function taskStatus(opts) {
|
|
674
|
+
const s = await fetchStatus(opts, opts.id);
|
|
675
|
+
if (opts.json === true) {
|
|
676
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: s })}
|
|
677
|
+
`);
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
renderStatusRow(s, opts);
|
|
681
|
+
}
|
|
682
|
+
async function taskNext(opts) {
|
|
683
|
+
const s = await fetchStatus(opts);
|
|
684
|
+
const suggestion = skillFor(s.phase);
|
|
685
|
+
const nextSkill = skillFor(s.nextGate);
|
|
686
|
+
const data = {
|
|
687
|
+
taskId: s.taskId,
|
|
688
|
+
phase: s.phase,
|
|
689
|
+
state: s.state,
|
|
690
|
+
mode: s.mode,
|
|
691
|
+
nextGate: s.nextGate,
|
|
692
|
+
suggestion,
|
|
693
|
+
nextSkill,
|
|
694
|
+
degraded: s.degraded === true
|
|
695
|
+
};
|
|
696
|
+
if (opts.json === true) {
|
|
697
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data })}
|
|
698
|
+
`);
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
const flag = data.degraded ? ` ${badge("warn", "degraded")}` : "";
|
|
702
|
+
log(`task next \u2014 ${s.taskId} \xB7 phase ${s.phase} (${s.state}, ${s.mode})${flag}`, opts);
|
|
703
|
+
if (s.nextGate) {
|
|
704
|
+
info(`next gate: ${s.nextGate}`, opts);
|
|
705
|
+
} else {
|
|
706
|
+
info("no further gate ahead (past verify, or stopped).", opts);
|
|
707
|
+
}
|
|
708
|
+
if (suggestion) info(`skill for current phase (${s.phase}): ${suggestion}`, opts);
|
|
709
|
+
if (nextSkill) info(`skill for next gate (${s.nextGate}): ${nextSkill}`, opts);
|
|
710
|
+
if (!suggestion && !nextSkill) {
|
|
711
|
+
info("run `noir skills list` to see applicable skills.", opts);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
async function taskNew(opts) {
|
|
715
|
+
let mode;
|
|
716
|
+
if (opts.mode !== void 0) {
|
|
717
|
+
if (opts.mode !== "full" && opts.mode !== "quick") {
|
|
718
|
+
fail(EXIT.USAGE, `task new: invalid mode '${opts.mode}' (expected 'full' or 'quick')`, opts);
|
|
719
|
+
}
|
|
720
|
+
mode = opts.mode;
|
|
721
|
+
}
|
|
722
|
+
const res = await callDaemonTool(
|
|
723
|
+
opts,
|
|
724
|
+
"workflow_start",
|
|
725
|
+
{
|
|
726
|
+
taskId: opts.slug,
|
|
727
|
+
slug: opts.slug,
|
|
728
|
+
...mode === void 0 ? {} : { mode }
|
|
729
|
+
}
|
|
730
|
+
);
|
|
731
|
+
if (res.ok !== true) {
|
|
732
|
+
const detail = typeof res.error === "string" && res.error.length > 0 ? res.error : "start failed";
|
|
733
|
+
fail(EXIT.ERROR, `task new --slug ${opts.slug}: ${detail}`, opts);
|
|
734
|
+
}
|
|
735
|
+
if (opts.json === true) {
|
|
736
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: res })}
|
|
737
|
+
`);
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
renderStatusRow(res, opts);
|
|
741
|
+
}
|
|
742
|
+
async function taskAdvance(opts) {
|
|
743
|
+
const args = {};
|
|
744
|
+
if (typeof opts.to === "string" && opts.to.length > 0) {
|
|
745
|
+
if (!Object.keys(PHASE_SKILL).includes(opts.to)) {
|
|
746
|
+
fail(EXIT.USAGE, `task advance: invalid phase '${opts.to}'`, opts);
|
|
747
|
+
}
|
|
748
|
+
args.to = opts.to;
|
|
749
|
+
}
|
|
750
|
+
if (typeof opts.force === "string" && opts.force.length > 0) {
|
|
751
|
+
args.force = { reason: opts.force };
|
|
752
|
+
}
|
|
753
|
+
const res = await callDaemonTool(
|
|
754
|
+
opts,
|
|
755
|
+
"workflow_advance",
|
|
756
|
+
args
|
|
757
|
+
);
|
|
758
|
+
if (res.ok !== true) {
|
|
759
|
+
const detail = typeof res.error === "string" && res.error.length > 0 ? res.error : "advance failed";
|
|
760
|
+
fail(EXIT.ERROR, `task advance: ${detail}`, opts);
|
|
761
|
+
}
|
|
762
|
+
if (typeof opts.to === "string" && opts.to === "verify") {
|
|
763
|
+
tip("run `noir handoff` for a ready-to-paste host prompt", opts);
|
|
764
|
+
}
|
|
765
|
+
if (opts.json === true) {
|
|
766
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: res })}
|
|
767
|
+
`);
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
renderStatusRow(res, opts);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// src/commands/handoff.ts
|
|
774
|
+
var SEED_LIMIT = 5;
|
|
775
|
+
function heading(name, id) {
|
|
776
|
+
return `# Noir handoff \u2014 ${name} (${id})`;
|
|
777
|
+
}
|
|
778
|
+
function renderMarkdown(p) {
|
|
779
|
+
const lines = [];
|
|
780
|
+
lines.push(heading(p.project.name, p.project.id));
|
|
781
|
+
lines.push("");
|
|
782
|
+
if (p.task) {
|
|
783
|
+
const gate = p.task.nextGate ?? "\u2014";
|
|
784
|
+
lines.push(`**Phase:** ${p.task.phase} \u2014 next gate: ${gate}`);
|
|
785
|
+
} else {
|
|
786
|
+
lines.push("**Phase:** no active task");
|
|
787
|
+
}
|
|
788
|
+
lines.push("");
|
|
789
|
+
lines.push("## Open host");
|
|
790
|
+
lines.push(p.directive);
|
|
791
|
+
lines.push("");
|
|
792
|
+
lines.push("## Next step");
|
|
793
|
+
if (p.task?.nextSkill) {
|
|
794
|
+
lines.push(`Run \`${p.task.nextSkill}\` \u2014 the host's next gate skill.`);
|
|
795
|
+
} else {
|
|
796
|
+
lines.push("No further gate ahead (past verify, or no active task).");
|
|
797
|
+
}
|
|
798
|
+
lines.push("");
|
|
799
|
+
lines.push("## Extracted context (seed)");
|
|
800
|
+
if (p.contextSeed && p.contextSeed.length > 0) {
|
|
801
|
+
for (const h of p.contextSeed) {
|
|
802
|
+
lines.push(`- \`${h.path}\` (score ${h.score.toFixed(4)})`);
|
|
803
|
+
const snip = h.snippet.trim();
|
|
804
|
+
if (snip.length > 0) lines.push(` > ${snip.replace(/\n/g, "\n > ")}`);
|
|
805
|
+
}
|
|
806
|
+
} else if (p.degraded.embedderMissing) {
|
|
807
|
+
lines.push(
|
|
808
|
+
"_Embedder unavailable \u2014 degraded to BM25-only or no hits. Start `noir daemon start` for live context._"
|
|
809
|
+
);
|
|
810
|
+
} else {
|
|
811
|
+
lines.push("_No context hits (or daemon down). Start `noir daemon start` for live context._");
|
|
812
|
+
}
|
|
813
|
+
lines.push("");
|
|
814
|
+
lines.push("## Extracted memory (seed)");
|
|
815
|
+
if (p.memorySeed && p.memorySeed.length > 0) {
|
|
816
|
+
for (const m of p.memorySeed) {
|
|
817
|
+
lines.push(`- \`${m.id}\` \u2014 ${m.summary}`);
|
|
818
|
+
}
|
|
819
|
+
} else {
|
|
820
|
+
lines.push(
|
|
821
|
+
"_No memory observations (or daemon down). Start `noir daemon start` for live memory._"
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
lines.push("");
|
|
825
|
+
lines.push("## Live data \u2014 call these from the host (already wired via the host MCP config)");
|
|
826
|
+
lines.push("- `noir.workflow_status` \xB7 `noir.context_search` \xB7 `noir.memory_recall`");
|
|
827
|
+
if (p.degraded.daemonDown) {
|
|
828
|
+
lines.push("");
|
|
829
|
+
lines.push("> **Note:** daemon not running \u2014 start `noir daemon start` for live task/memory.");
|
|
830
|
+
}
|
|
831
|
+
lines.push("");
|
|
832
|
+
return lines.join("\n");
|
|
833
|
+
}
|
|
834
|
+
function toContextHit(raw) {
|
|
835
|
+
const h = raw ?? {};
|
|
836
|
+
return {
|
|
837
|
+
path: typeof h.path === "string" ? h.path : "<unknown>",
|
|
838
|
+
score: typeof h.score === "number" ? h.score : 0,
|
|
839
|
+
snippet: typeof h.snippet === "string" ? h.snippet : ""
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
function observationSummary(raw) {
|
|
843
|
+
const obs = raw.observation;
|
|
844
|
+
if (obs && typeof obs === "object") {
|
|
845
|
+
const content = obs.content;
|
|
846
|
+
if (typeof content === "string" && content.length > 0) {
|
|
847
|
+
return content.length > 160 ? `${content.slice(0, 157)}\u2026` : content;
|
|
848
|
+
}
|
|
849
|
+
const type = obs.type;
|
|
850
|
+
if (typeof type === "string") return `(${type})`;
|
|
851
|
+
}
|
|
852
|
+
return "";
|
|
853
|
+
}
|
|
854
|
+
function toMemoryHit(raw) {
|
|
855
|
+
const h = raw ?? {};
|
|
856
|
+
return {
|
|
857
|
+
id: typeof h.id === "string" ? h.id : "<unknown>",
|
|
858
|
+
summary: observationSummary(h)
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
function toHostPayload(p) {
|
|
862
|
+
return {
|
|
863
|
+
project: p.project,
|
|
864
|
+
host: p.host,
|
|
865
|
+
task: p.task
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
function resolveDirective(root, p) {
|
|
869
|
+
const ctx = { root };
|
|
870
|
+
const adapter = resolveAdapter(p.host);
|
|
871
|
+
const hostPayload = toHostPayload(p);
|
|
872
|
+
if (typeof adapter.emitHandoff === "function") {
|
|
873
|
+
return adapter.emitHandoff(ctx, hostPayload);
|
|
874
|
+
}
|
|
875
|
+
return defaultHandoffBlock(ctx, hostPayload);
|
|
876
|
+
}
|
|
877
|
+
function handoffFilePath(root, taskId, projectId) {
|
|
878
|
+
const id = typeof taskId === "string" && taskId.length > 0 ? taskId : projectId;
|
|
879
|
+
return join(root, NOIR_DIR, "handoff", `${id}.md`);
|
|
880
|
+
}
|
|
881
|
+
async function extractSeeds(opts, probe, query) {
|
|
882
|
+
if (!probe.running) return { context: null, memory: null };
|
|
883
|
+
let context = null;
|
|
884
|
+
let memory = null;
|
|
885
|
+
await withRunningDaemon(
|
|
886
|
+
opts,
|
|
887
|
+
async (caller) => {
|
|
888
|
+
const ctxRes = await tryTool(caller, "context_search", {
|
|
889
|
+
query,
|
|
890
|
+
limit: SEED_LIMIT
|
|
891
|
+
});
|
|
892
|
+
if (ctxRes && ctxRes.ok !== false && Array.isArray(ctxRes.results)) {
|
|
893
|
+
context = ctxRes.results.slice(0, SEED_LIMIT).map(toContextHit).filter((h) => h.path !== "<unknown>" || h.snippet.length > 0);
|
|
894
|
+
}
|
|
895
|
+
const memRes = await tryTool(caller, "memory_recall", {
|
|
896
|
+
query,
|
|
897
|
+
limit: SEED_LIMIT
|
|
898
|
+
});
|
|
899
|
+
if (memRes && memRes.ok !== false && Array.isArray(memRes.results)) {
|
|
900
|
+
memory = memRes.results.slice(0, SEED_LIMIT).map(toMemoryHit).filter((h) => h.summary.length > 0);
|
|
901
|
+
}
|
|
902
|
+
},
|
|
903
|
+
probe
|
|
904
|
+
);
|
|
905
|
+
return { context, memory };
|
|
906
|
+
}
|
|
907
|
+
async function probeOnly(opts) {
|
|
908
|
+
return probeDaemon(opts);
|
|
909
|
+
}
|
|
910
|
+
async function handoff(opts) {
|
|
911
|
+
const project = loadProjectInfo4(process.cwd());
|
|
912
|
+
const host = project.config.host;
|
|
913
|
+
const snapshot = await gatherStatusPayload(opts);
|
|
914
|
+
const probe = await probeOnly(opts);
|
|
915
|
+
const task = snapshot.workflow ? {
|
|
916
|
+
taskId: snapshot.workflow.taskId,
|
|
917
|
+
phase: snapshot.workflow.phase,
|
|
918
|
+
nextGate: snapshot.workflow.nextGate,
|
|
919
|
+
nextSkill: skillFor(snapshot.workflow.nextGate) ?? skillFor(snapshot.workflow.phase)
|
|
920
|
+
} : null;
|
|
921
|
+
const query = task?.taskId ?? project.name;
|
|
922
|
+
const { context, memory } = await extractSeeds(opts, probe, query);
|
|
923
|
+
const full = {
|
|
924
|
+
project: { id: project.id, name: project.name },
|
|
925
|
+
host,
|
|
926
|
+
directive: "",
|
|
927
|
+
// filled after the host payload is assembled (below)
|
|
928
|
+
task,
|
|
929
|
+
contextSeed: context,
|
|
930
|
+
memorySeed: memory,
|
|
931
|
+
degraded: {
|
|
932
|
+
daemonDown: !probe.running,
|
|
933
|
+
embedderMissing: context === null && probe.running
|
|
934
|
+
}
|
|
935
|
+
};
|
|
936
|
+
full.directive = resolveDirective(project.root, full);
|
|
937
|
+
if (opts.json === true) {
|
|
938
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: full })}
|
|
939
|
+
`);
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
const md = renderMarkdown(full);
|
|
943
|
+
if (opts.write === true) {
|
|
944
|
+
const path = handoffFilePath(project.root, task?.taskId, project.id);
|
|
945
|
+
mkdirSync(join(project.root, NOIR_DIR, "handoff"), { recursive: true });
|
|
946
|
+
writeFileSync(path, md, "utf8");
|
|
947
|
+
info(`handoff written to ${path}`, opts);
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
process.stdout.write(md);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// src/commands/home.ts
|
|
954
|
+
import { hostLaunchDirective as hostLaunchDirective2 } from "@noir-ai/adapters";
|
|
955
|
+
import { loadProjectInfo as loadProjectInfo5 } from "@noir-ai/core";
|
|
956
|
+
|
|
957
|
+
// src/banner.ts
|
|
958
|
+
import gradient from "gradient-string";
|
|
959
|
+
var NOIR_BLOCK = [
|
|
960
|
+
"\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 ",
|
|
961
|
+
"\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557",
|
|
962
|
+
"\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D",
|
|
963
|
+
"\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551",
|
|
964
|
+
"\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",
|
|
965
|
+
"\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D"
|
|
966
|
+
];
|
|
967
|
+
var NOIR_GRADIENT = gradient("#2c5282", "#3b82f6", "#7dd3fc");
|
|
968
|
+
var NOIR_TAGLINE = "discipline, context, and memory layer for agentic CLIs";
|
|
969
|
+
function renderBanner(opts = {}) {
|
|
970
|
+
const width = opts.width ?? process.stdout.columns ?? 80;
|
|
971
|
+
const colorOn = opts.color !== false && useColor();
|
|
972
|
+
if (width < 50) {
|
|
973
|
+
return colorOn ? `${c.accent("\u25C6")} noir` : "\u25C6 noir";
|
|
974
|
+
}
|
|
975
|
+
const block = NOIR_BLOCK.join("\n");
|
|
976
|
+
if (!colorOn) return block;
|
|
977
|
+
if (accessibleMode()) {
|
|
978
|
+
return block.split("\n").map((line) => c.accent(line)).join("\n");
|
|
979
|
+
}
|
|
980
|
+
return NOIR_GRADIENT.multiline(block);
|
|
981
|
+
}
|
|
982
|
+
function shouldShowBanner(opts) {
|
|
983
|
+
if (opts.quiet === true || opts.json === true) return false;
|
|
984
|
+
const v = process.env.NOIR_NO_BANNER;
|
|
985
|
+
if (v !== void 0 && v !== "") return false;
|
|
986
|
+
return true;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// src/commands/home.ts
|
|
990
|
+
function tryProject() {
|
|
991
|
+
try {
|
|
992
|
+
const info2 = loadProjectInfo5(process.cwd());
|
|
993
|
+
return { id: info2.id, name: info2.name, host: info2.config.host };
|
|
994
|
+
} catch {
|
|
995
|
+
return null;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
var COMMANDS_HINT = "Commands: init \xB7 create \xB7 sync \xB7 status \xB7 context \xB7 memory \xB7 skills \xB7 task \xB7 daemon \xB7 doctor";
|
|
999
|
+
async function home(opts, deps) {
|
|
1000
|
+
if (isInteractive(opts) && opts.tui !== false) {
|
|
1001
|
+
await runMenu(opts, deps);
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
if (opts.json === true) {
|
|
1005
|
+
await deps.dispatch(["status", "--json"]);
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
await deps.dispatch(["status"]);
|
|
1009
|
+
}
|
|
1010
|
+
async function runMenu(opts, deps) {
|
|
1011
|
+
const clack = await import("@clack/prompts");
|
|
1012
|
+
const project = tryProject();
|
|
1013
|
+
if (shouldShowBanner(opts)) {
|
|
1014
|
+
const host = project?.host ?? "claude";
|
|
1015
|
+
process.stderr.write(
|
|
1016
|
+
`
|
|
1017
|
+
${renderBanner()}
|
|
1018
|
+
${NOIR_TAGLINE}
|
|
1019
|
+
|
|
1020
|
+
${hostLaunchDirective2(host)}
|
|
1021
|
+
${COMMANDS_HINT}
|
|
1022
|
+
|
|
1023
|
+
`
|
|
1024
|
+
);
|
|
1025
|
+
}
|
|
1026
|
+
clack.intro(project ? `noir \u2014 ${project.name}` : "noir");
|
|
1027
|
+
const choice = await clack.select({
|
|
1028
|
+
message: "What would you like to do?",
|
|
1029
|
+
initialValue: "status",
|
|
1030
|
+
options: [
|
|
1031
|
+
{ value: "status", label: "Status", hint: "project + daemon + store snapshot" },
|
|
1032
|
+
{ value: "index", label: "Index project", hint: "(re)index files into context" },
|
|
1033
|
+
{ value: "recall", label: "Recall memory", hint: "search cross-session memory" },
|
|
1034
|
+
{ value: "next", label: "Next task", hint: "suggest next phase + skill" },
|
|
1035
|
+
{ value: "handoff", label: "Handoff", hint: "ready-to-paste host prompt" },
|
|
1036
|
+
{ value: "daemon", label: "Start daemon", hint: "foreground daemon" },
|
|
1037
|
+
{ value: "sync", label: "Sync skills", hint: "re-emit builtin skills" },
|
|
1038
|
+
{ value: "exit", label: "Exit" }
|
|
1039
|
+
]
|
|
1040
|
+
});
|
|
1041
|
+
if (clack.isCancel(choice)) {
|
|
1042
|
+
clack.cancel("Cancelled.");
|
|
1043
|
+
fail(EXIT.CANCELLED, "cancelled", opts);
|
|
1044
|
+
}
|
|
1045
|
+
const argv = await argvForChoice(choice, clack, opts);
|
|
1046
|
+
if (argv === null) {
|
|
1047
|
+
clack.outro("bye");
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
await deps.dispatch(argv);
|
|
1051
|
+
clack.outro("done");
|
|
1052
|
+
}
|
|
1053
|
+
async function argvForChoice(choice, clack, opts) {
|
|
1054
|
+
switch (choice) {
|
|
1055
|
+
case "status":
|
|
1056
|
+
return ["status"];
|
|
1057
|
+
case "index":
|
|
1058
|
+
return ["context", "index"];
|
|
1059
|
+
case "next":
|
|
1060
|
+
return ["task", "next"];
|
|
1061
|
+
case "handoff":
|
|
1062
|
+
return ["handoff"];
|
|
1063
|
+
case "daemon":
|
|
1064
|
+
return ["daemon", "start"];
|
|
1065
|
+
case "sync":
|
|
1066
|
+
return ["sync"];
|
|
1067
|
+
case "exit":
|
|
1068
|
+
return null;
|
|
1069
|
+
case "recall": {
|
|
1070
|
+
const query = await clack.text({
|
|
1071
|
+
message: "Recall query:",
|
|
1072
|
+
placeholder: "e.g. auth flow, ContextEngine, deploy steps"
|
|
1073
|
+
});
|
|
1074
|
+
if (clack.isCancel(query)) {
|
|
1075
|
+
clack.cancel("Cancelled.");
|
|
1076
|
+
fail(EXIT.CANCELLED, "cancelled", opts);
|
|
1077
|
+
}
|
|
1078
|
+
return ["memory", "recall", String(query)];
|
|
1079
|
+
}
|
|
1080
|
+
default:
|
|
1081
|
+
return null;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
// src/commands/memory.ts
|
|
1086
|
+
function parseLimit2(raw, label, opts) {
|
|
1087
|
+
if (raw === void 0) return void 0;
|
|
1088
|
+
const n = Number(raw);
|
|
1089
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
1090
|
+
fail(EXIT.USAGE, `${label}: --limit must be a positive integer (got '${raw}')`, opts);
|
|
1091
|
+
}
|
|
1092
|
+
return n;
|
|
1093
|
+
}
|
|
1094
|
+
function splitCsv(raw) {
|
|
1095
|
+
if (raw === void 0) return void 0;
|
|
1096
|
+
const out = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
1097
|
+
return out.length > 0 ? out : void 0;
|
|
1098
|
+
}
|
|
1099
|
+
function toHit2(raw) {
|
|
1100
|
+
const h = raw ?? {};
|
|
1101
|
+
const arr = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
|
|
1102
|
+
return {
|
|
1103
|
+
id: typeof h.id === "string" ? h.id : "",
|
|
1104
|
+
type: typeof h.type === "string" ? h.type : "",
|
|
1105
|
+
score: typeof h.score === "number" ? h.score : 0,
|
|
1106
|
+
content: typeof h.content === "string" ? h.content : "",
|
|
1107
|
+
concepts: arr(h.concepts),
|
|
1108
|
+
files: arr(h.files),
|
|
1109
|
+
ts: typeof h.ts === "number" ? h.ts : 0,
|
|
1110
|
+
importance: typeof h.importance === "number" ? h.importance : 0,
|
|
1111
|
+
source: typeof h.source === "string" ? h.source : ""
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
function failTool2(label, envelope, opts) {
|
|
1115
|
+
const detail = envelope.error ?? envelope.reason ?? "unknown failure";
|
|
1116
|
+
fail(EXIT.ERROR, `${label}: ${detail}`, opts);
|
|
1117
|
+
}
|
|
1118
|
+
function stamp(ts) {
|
|
1119
|
+
if (typeof ts !== "number" || ts <= 0) return "-";
|
|
1120
|
+
return new Date(ts).toISOString().replace("T", " ").replace(/\..*$/, "Z");
|
|
1121
|
+
}
|
|
1122
|
+
async function memoryRecall(opts) {
|
|
1123
|
+
const limit = parseLimit2(opts.limit, "memory recall", opts);
|
|
1124
|
+
const res = await callDaemonTool(opts, "memory_recall", {
|
|
1125
|
+
query: opts.query,
|
|
1126
|
+
...limit === void 0 ? {} : { limit }
|
|
1127
|
+
});
|
|
1128
|
+
if (res.ok !== true) failTool2("memory recall", res, opts);
|
|
1129
|
+
const hits = Array.isArray(res.results) ? res.results.map(toHit2) : [];
|
|
1130
|
+
const data = { query: opts.query, hits, degraded: res.degraded === true };
|
|
1131
|
+
if (opts.json === true) {
|
|
1132
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data })}
|
|
1133
|
+
`);
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
renderRecall(data.query, hits, data.degraded, opts);
|
|
646
1137
|
}
|
|
647
1138
|
function renderRecall(query, hits, degraded, opts) {
|
|
648
|
-
const flag = degraded ? "
|
|
1139
|
+
const flag = degraded ? ` ${badge("warn", "degraded: BM25-only")}` : "";
|
|
649
1140
|
log(
|
|
650
1141
|
`memory recall \u2014 ${hits.length} hit${hits.length === 1 ? "" : "s"} for '${query}'${flag}`,
|
|
651
1142
|
opts
|
|
@@ -707,9 +1198,9 @@ async function resolveContent(opts) {
|
|
|
707
1198
|
function renderObservation(obs, opts) {
|
|
708
1199
|
const rows = [];
|
|
709
1200
|
for (const key of ["id", "type", "importance", "ts", "source", "project", "sessionId"]) {
|
|
710
|
-
if (obs[key] !== void 0) rows.push({
|
|
1201
|
+
if (obs[key] !== void 0) rows.push({ label: key, value: obs[key] });
|
|
711
1202
|
}
|
|
712
|
-
if (rows.length > 0)
|
|
1203
|
+
if (rows.length > 0) definitionList(rows, opts);
|
|
713
1204
|
const content = obs.content;
|
|
714
1205
|
if (typeof content === "string") log(`
|
|
715
1206
|
${content}`, opts);
|
|
@@ -806,7 +1297,7 @@ async function memoryConsolidate(opts) {
|
|
|
806
1297
|
|
|
807
1298
|
// src/commands/skills.ts
|
|
808
1299
|
import { claudeAdapter } from "@noir-ai/adapters";
|
|
809
|
-
import { loadProjectInfo as
|
|
1300
|
+
import { loadProjectInfo as loadProjectInfo6 } from "@noir-ai/core";
|
|
810
1301
|
import {
|
|
811
1302
|
discoverAll,
|
|
812
1303
|
emitSkillsToDir
|
|
@@ -835,400 +1326,111 @@ var CATEGORY = {
|
|
|
835
1326
|
"noir-worktree": "git",
|
|
836
1327
|
"noir-recall": "memory",
|
|
837
1328
|
"noir-remember": "memory",
|
|
838
|
-
"noir-checkpoint": "memory",
|
|
839
|
-
"noir-context": "context",
|
|
840
|
-
"noir-frontend": "domain",
|
|
841
|
-
"noir-backend": "domain",
|
|
842
|
-
"noir-doctor": "meta",
|
|
843
|
-
"noir-skill-author": "meta",
|
|
844
|
-
"noir-sync": "meta",
|
|
845
|
-
"noir-wrap": "meta"
|
|
846
|
-
};
|
|
847
|
-
function categoryOf(name) {
|
|
848
|
-
const mapped = CATEGORY[name];
|
|
849
|
-
if (mapped !== void 0) return mapped;
|
|
850
|
-
return name.replace(/^noir-/, "") || "general";
|
|
851
|
-
}
|
|
852
|
-
function toRow(s, kind = "builtin") {
|
|
853
|
-
const description = typeof s.frontmatter.description === "string" ? s.frontmatter.description : "";
|
|
854
|
-
return { name: s.name, category: categoryOf(s.name), description, kind };
|
|
855
|
-
}
|
|
856
|
-
function truncate(text, max) {
|
|
857
|
-
if (text.length <= max) return text;
|
|
858
|
-
const slice = text.slice(0, Math.max(0, max - 1));
|
|
859
|
-
const boundary = slice.lastIndexOf(" ");
|
|
860
|
-
const head = boundary > 0 ? slice.slice(0, boundary) : slice;
|
|
861
|
-
return `${head.trimEnd()}\u2026`;
|
|
862
|
-
}
|
|
863
|
-
async function skillsList(opts) {
|
|
864
|
-
let builtins;
|
|
865
|
-
let integrations;
|
|
866
|
-
try {
|
|
867
|
-
const all = discoverAll();
|
|
868
|
-
builtins = all.builtins;
|
|
869
|
-
integrations = all.integrations;
|
|
870
|
-
} catch (err) {
|
|
871
|
-
fail(
|
|
872
|
-
EXIT.ERROR,
|
|
873
|
-
`skills list: could not discover skills (${err instanceof Error ? err.message : String(err)})`,
|
|
874
|
-
opts
|
|
875
|
-
);
|
|
876
|
-
}
|
|
877
|
-
const rows = [
|
|
878
|
-
...builtins.map((b) => toRow(b, "builtin")),
|
|
879
|
-
...integrations.map((i) => toRow(i, "integration"))
|
|
880
|
-
];
|
|
881
|
-
const data = { count: rows.length, skills: rows };
|
|
882
|
-
if (opts.json === true) {
|
|
883
|
-
process.stdout.write(`${JSON.stringify({ ok: true, data })}
|
|
884
|
-
`);
|
|
885
|
-
return;
|
|
886
|
-
}
|
|
887
|
-
const intCount = integrations.length;
|
|
888
|
-
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"}`;
|
|
889
|
-
log(banner, opts);
|
|
890
|
-
table(
|
|
891
|
-
rows.map((r) => ({
|
|
892
|
-
Skill: r.name,
|
|
893
|
-
Kind: r.kind,
|
|
894
|
-
Category: r.category,
|
|
895
|
-
Description: truncate(r.description, 80)
|
|
896
|
-
})),
|
|
897
|
-
["Skill", "Kind", "Category", "Description"],
|
|
898
|
-
opts
|
|
899
|
-
);
|
|
900
|
-
}
|
|
901
|
-
async function skillsSync(opts) {
|
|
902
|
-
const root = process.cwd();
|
|
903
|
-
const project = loadProjectInfo4(root);
|
|
904
|
-
if (!claudeAdapter.skillsDir) {
|
|
905
|
-
const data2 = {
|
|
906
|
-
emitted: [],
|
|
907
|
-
references: 0,
|
|
908
|
-
host: project.config.host,
|
|
909
|
-
reason: "no-skill-emitter"
|
|
910
|
-
};
|
|
911
|
-
if (opts.json === true) {
|
|
912
|
-
process.stdout.write(`${JSON.stringify({ ok: true, data: data2 })}
|
|
913
|
-
`);
|
|
914
|
-
return;
|
|
915
|
-
}
|
|
916
|
-
info("This host has no skill emitter; nothing to sync.", opts);
|
|
917
|
-
return;
|
|
918
|
-
}
|
|
919
|
-
const dir = claudeAdapter.skillsDir({ root });
|
|
920
|
-
const summary = await emitSkillsToDir(dir);
|
|
921
|
-
const pruned = summary.pruned ?? [];
|
|
922
|
-
const data = {
|
|
923
|
-
emitted: summary.emitted,
|
|
924
|
-
references: summary.references,
|
|
925
|
-
dir,
|
|
926
|
-
pruned
|
|
927
|
-
};
|
|
928
|
-
if (opts.json === true) {
|
|
929
|
-
process.stdout.write(`${JSON.stringify({ ok: true, data })}
|
|
930
|
-
`);
|
|
931
|
-
return;
|
|
932
|
-
}
|
|
933
|
-
log(
|
|
934
|
-
`Synced ${summary.emitted.length} Noir skill${summary.emitted.length === 1 ? "" : "s"} to ${dir}.`,
|
|
935
|
-
opts
|
|
936
|
-
);
|
|
937
|
-
if (pruned.length > 0) {
|
|
938
|
-
warn(
|
|
939
|
-
`Pruned ${pruned.length} stale noir-* skill dir${pruned.length === 1 ? "" : "s"}: ${pruned.join(", ")}`,
|
|
940
|
-
opts
|
|
941
|
-
);
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
// src/commands/status.ts
|
|
946
|
-
import { loadProjectInfo as loadProjectInfo5, NOIR_VERSION as NOIR_VERSION2 } from "@noir-ai/core";
|
|
947
|
-
async function tryTool(caller, name) {
|
|
948
|
-
try {
|
|
949
|
-
return await caller.callTool(name);
|
|
950
|
-
} catch {
|
|
951
|
-
return null;
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
function pickStore(s) {
|
|
955
|
-
if (s?.ok !== true) return null;
|
|
956
|
-
return {
|
|
957
|
-
docCount: s.docCount,
|
|
958
|
-
vecCount: s.vecCount,
|
|
959
|
-
dbPath: s.dbPath,
|
|
960
|
-
degraded: s.degraded === true
|
|
961
|
-
};
|
|
962
|
-
}
|
|
963
|
-
function describeEmbedder2(e) {
|
|
964
|
-
if (!e || e.kind === "none") return "none (BM25-only)";
|
|
965
|
-
const model = typeof e.model === "string" && e.model.length > 0 ? e.model : "<unset>";
|
|
966
|
-
return `${e.kind} \xB7 ${model} (${e.dim}-dim)`;
|
|
967
|
-
}
|
|
968
|
-
function pickContext(c) {
|
|
969
|
-
if (c?.ok !== true) return null;
|
|
970
|
-
return {
|
|
971
|
-
docCount: c.docCount,
|
|
972
|
-
vecCount: c.vecCount,
|
|
973
|
-
indexedFiles: c.indexedFiles,
|
|
974
|
-
embedder: describeEmbedder2(c.embedder),
|
|
975
|
-
degraded: c.degraded === true
|
|
976
|
-
};
|
|
977
|
-
}
|
|
978
|
-
function pickWorkflow(w) {
|
|
979
|
-
if (w?.ok !== true) return null;
|
|
980
|
-
return {
|
|
981
|
-
taskId: w.taskId,
|
|
982
|
-
phase: w.phase,
|
|
983
|
-
state: w.state,
|
|
984
|
-
mode: w.mode,
|
|
985
|
-
nextGate: w.nextGate,
|
|
986
|
-
degraded: w.degraded === true
|
|
987
|
-
};
|
|
988
|
-
}
|
|
989
|
-
function pickMemory(m) {
|
|
990
|
-
if (m?.ok !== true || !Array.isArray(m.sessions)) return null;
|
|
991
|
-
let observations = 0;
|
|
992
|
-
for (const s of m.sessions) observations += s.count;
|
|
993
|
-
return { sessions: m.sessions.length, observations };
|
|
994
|
-
}
|
|
995
|
-
function buildPayload(project, probe, host, store, context, workflow, memory) {
|
|
996
|
-
return {
|
|
997
|
-
noir: NOIR_VERSION2,
|
|
998
|
-
project: { id: project.id, name: project.name },
|
|
999
|
-
host: project.config.host,
|
|
1000
|
-
daemon: {
|
|
1001
|
-
running: probe.running,
|
|
1002
|
-
...probe.pid !== void 0 ? { pid: probe.pid } : {},
|
|
1003
|
-
...probe.uptimeSec !== void 0 ? { uptimeSec: probe.uptimeSec } : {},
|
|
1004
|
-
...host && typeof host.transport === "string" ? { transport: host.transport } : {}
|
|
1005
|
-
},
|
|
1006
|
-
store: pickStore(store),
|
|
1007
|
-
context: pickContext(context),
|
|
1008
|
-
workflow: pickWorkflow(workflow),
|
|
1009
|
-
memory: pickMemory(memory)
|
|
1010
|
-
};
|
|
1011
|
-
}
|
|
1012
|
-
function formatDuration(sec) {
|
|
1013
|
-
if (typeof sec !== "number" || sec < 0) return "unknown";
|
|
1014
|
-
if (sec < 60) return `${sec}s`;
|
|
1015
|
-
if (sec < 3600) return `${Math.floor(sec / 60)}m ${sec % 60}s`;
|
|
1016
|
-
return `${Math.floor(sec / 3600)}h ${Math.floor(sec % 3600 / 60)}m`;
|
|
1017
|
-
}
|
|
1018
|
-
function describeDaemon(d) {
|
|
1019
|
-
if (!d.running) return "not running (start with `noir daemon start`)";
|
|
1020
|
-
const pid = typeof d.pid === "number" ? `pid ${d.pid}` : "pid unknown";
|
|
1021
|
-
const up = typeof d.uptimeSec === "number" ? `, up ${formatDuration(d.uptimeSec)}` : "";
|
|
1022
|
-
const transport = typeof d.transport === "string" ? `, ${d.transport}` : "";
|
|
1023
|
-
return `running (${pid}${up}${transport})`;
|
|
1024
|
-
}
|
|
1025
|
-
function describeStore(s) {
|
|
1026
|
-
if (!s) return "unavailable (store engine not wired)";
|
|
1027
|
-
const flag = s.degraded ? " [degraded: read-only]" : "";
|
|
1028
|
-
return `${s.docCount} docs / ${s.vecCount} vecs${flag}`;
|
|
1029
|
-
}
|
|
1030
|
-
function describeContext(c) {
|
|
1031
|
-
if (!c) return "unavailable (context engine not wired)";
|
|
1032
|
-
const flag = c.degraded ? " [degraded]" : "";
|
|
1033
|
-
return `${c.docCount} docs, ${c.vecCount} vecs, ${c.indexedFiles} files \xB7 embedder ${c.embedder}${flag}`;
|
|
1034
|
-
}
|
|
1035
|
-
function describeWorkflow(w) {
|
|
1036
|
-
if (!w) return "no active task";
|
|
1037
|
-
const gate = w.nextGate ? ` \u2192 next gate: ${w.nextGate}` : "";
|
|
1038
|
-
const flag = w.degraded ? " [degraded]" : "";
|
|
1039
|
-
return `${w.phase} (${w.state}, ${w.mode}) \xB7 task ${w.taskId}${gate}${flag}`;
|
|
1329
|
+
"noir-checkpoint": "memory",
|
|
1330
|
+
"noir-context": "context",
|
|
1331
|
+
"noir-frontend": "domain",
|
|
1332
|
+
"noir-backend": "domain",
|
|
1333
|
+
"noir-doctor": "meta",
|
|
1334
|
+
"noir-skill-author": "meta",
|
|
1335
|
+
"noir-sync": "meta",
|
|
1336
|
+
"noir-wrap": "meta"
|
|
1337
|
+
};
|
|
1338
|
+
function categoryOf(name) {
|
|
1339
|
+
const mapped = CATEGORY[name];
|
|
1340
|
+
if (mapped !== void 0) return mapped;
|
|
1341
|
+
return name.replace(/^noir-/, "") || "general";
|
|
1040
1342
|
}
|
|
1041
|
-
function
|
|
1042
|
-
|
|
1043
|
-
return
|
|
1343
|
+
function toRow(s, kind = "builtin") {
|
|
1344
|
+
const description = typeof s.frontmatter.description === "string" ? s.frontmatter.description : "";
|
|
1345
|
+
return { name: s.name, category: categoryOf(s.name), description, kind };
|
|
1044
1346
|
}
|
|
1045
|
-
function
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
{ Field: "Noir", Value: p.noir },
|
|
1052
|
-
{ Field: "Daemon", Value: describeDaemon(p.daemon) },
|
|
1053
|
-
{ Field: "Store", Value: describeStore(p.store) },
|
|
1054
|
-
{ Field: "Context", Value: describeContext(p.context) },
|
|
1055
|
-
{ Field: "Workflow", Value: describeWorkflow(p.workflow) },
|
|
1056
|
-
{ Field: "Memory", Value: describeMemory(p.memory) }
|
|
1057
|
-
],
|
|
1058
|
-
["Field", "Value"],
|
|
1059
|
-
opts
|
|
1060
|
-
);
|
|
1347
|
+
function truncate(text, max) {
|
|
1348
|
+
if (text.length <= max) return text;
|
|
1349
|
+
const slice = text.slice(0, Math.max(0, max - 1));
|
|
1350
|
+
const boundary = slice.lastIndexOf(" ");
|
|
1351
|
+
const head = boundary > 0 ? slice.slice(0, boundary) : slice;
|
|
1352
|
+
return `${head.trimEnd()}\u2026`;
|
|
1061
1353
|
}
|
|
1062
|
-
async function
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
host = await tryTool(caller, "host_status");
|
|
1075
|
-
store = await tryTool(caller, "store_status");
|
|
1076
|
-
context = await tryTool(caller, "context_status");
|
|
1077
|
-
workflow = await tryTool(caller, "workflow_status");
|
|
1078
|
-
memory = await tryTool(caller, "memory_sessions");
|
|
1079
|
-
},
|
|
1080
|
-
probe
|
|
1354
|
+
async function skillsList(opts) {
|
|
1355
|
+
let builtins;
|
|
1356
|
+
let integrations;
|
|
1357
|
+
try {
|
|
1358
|
+
const all = discoverAll();
|
|
1359
|
+
builtins = all.builtins;
|
|
1360
|
+
integrations = all.integrations;
|
|
1361
|
+
} catch (err) {
|
|
1362
|
+
fail(
|
|
1363
|
+
EXIT.ERROR,
|
|
1364
|
+
`skills list: could not discover skills (${err instanceof Error ? err.message : String(err)})`,
|
|
1365
|
+
opts
|
|
1081
1366
|
);
|
|
1082
1367
|
}
|
|
1083
|
-
const
|
|
1368
|
+
const rows = [
|
|
1369
|
+
...builtins.map((b) => toRow(b, "builtin")),
|
|
1370
|
+
...integrations.map((i) => toRow(i, "integration"))
|
|
1371
|
+
];
|
|
1372
|
+
const data = { count: rows.length, skills: rows };
|
|
1084
1373
|
if (opts.json === true) {
|
|
1085
|
-
process.stdout.write(`${JSON.stringify({ ok: true, data
|
|
1374
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data })}
|
|
1086
1375
|
`);
|
|
1087
1376
|
return;
|
|
1088
1377
|
}
|
|
1089
|
-
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
// src/commands/task.ts
|
|
1093
|
-
var PHASE_SKILL = {
|
|
1094
|
-
intake: "noir-intake",
|
|
1095
|
-
clarify: "noir-clarify",
|
|
1096
|
-
spec: "noir-spec",
|
|
1097
|
-
plan: "noir-plan",
|
|
1098
|
-
execute: "noir-execute",
|
|
1099
|
-
verify: "noir-verify",
|
|
1100
|
-
document: "noir-document"
|
|
1101
|
-
};
|
|
1102
|
-
function skillFor(phase) {
|
|
1103
|
-
if (typeof phase !== "string") return null;
|
|
1104
|
-
return PHASE_SKILL[phase] ?? null;
|
|
1105
|
-
}
|
|
1106
|
-
async function fetchStatus(opts, taskId) {
|
|
1107
|
-
const args = typeof taskId === "string" && taskId.length > 0 ? { taskId } : {};
|
|
1108
|
-
const res = await callDaemonTool(
|
|
1109
|
-
opts,
|
|
1110
|
-
"workflow_status",
|
|
1111
|
-
args
|
|
1112
|
-
);
|
|
1113
|
-
if (res.ok === true) return res;
|
|
1114
|
-
const detail = res.error ?? "task not found";
|
|
1115
|
-
fail(EXIT.NOT_FOUND, `task: ${detail}`, opts);
|
|
1116
|
-
}
|
|
1117
|
-
function formatStamp(ms) {
|
|
1118
|
-
if (typeof ms !== "number" || ms <= 0) return "-";
|
|
1119
|
-
return new Date(ms).toISOString().replace("T", " ").replace(/\..*$/, "Z");
|
|
1120
|
-
}
|
|
1121
|
-
function renderStatusRow(s, opts) {
|
|
1122
|
-
const flag = s.degraded === true ? " [degraded]" : "";
|
|
1123
|
-
log(`task status \u2014 ${s.taskId}${flag}`, opts);
|
|
1378
|
+
const intCount = integrations.length;
|
|
1379
|
+
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"}`;
|
|
1380
|
+
log(banner, opts);
|
|
1124
1381
|
table(
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
],
|
|
1133
|
-
["Field", "Value"],
|
|
1382
|
+
rows.map((r) => ({
|
|
1383
|
+
Skill: r.name,
|
|
1384
|
+
Kind: r.kind,
|
|
1385
|
+
Category: r.category,
|
|
1386
|
+
Description: truncate(r.description, 80)
|
|
1387
|
+
})),
|
|
1388
|
+
["Skill", "Kind", "Category", "Description"],
|
|
1134
1389
|
opts
|
|
1135
1390
|
);
|
|
1136
1391
|
}
|
|
1137
|
-
async function
|
|
1138
|
-
const
|
|
1139
|
-
|
|
1140
|
-
|
|
1392
|
+
async function skillsSync(opts) {
|
|
1393
|
+
const root = process.cwd();
|
|
1394
|
+
const project = loadProjectInfo6(root);
|
|
1395
|
+
if (!claudeAdapter.skillsDir) {
|
|
1396
|
+
const data2 = {
|
|
1397
|
+
emitted: [],
|
|
1398
|
+
references: 0,
|
|
1399
|
+
host: project.config.host,
|
|
1400
|
+
reason: "no-skill-emitter"
|
|
1401
|
+
};
|
|
1402
|
+
if (opts.json === true) {
|
|
1403
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: data2 })}
|
|
1141
1404
|
`);
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
info("This host has no skill emitter; nothing to sync.", opts);
|
|
1142
1408
|
return;
|
|
1143
1409
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
const s = await fetchStatus(opts);
|
|
1148
|
-
const suggestion = skillFor(s.phase);
|
|
1149
|
-
const nextSkill = skillFor(s.nextGate);
|
|
1410
|
+
const dir = claudeAdapter.skillsDir({ root });
|
|
1411
|
+
const summary = await emitSkillsToDir(dir);
|
|
1412
|
+
const pruned = summary.pruned ?? [];
|
|
1150
1413
|
const data = {
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
nextGate: s.nextGate,
|
|
1156
|
-
suggestion,
|
|
1157
|
-
nextSkill,
|
|
1158
|
-
degraded: s.degraded === true
|
|
1414
|
+
emitted: summary.emitted,
|
|
1415
|
+
references: summary.references,
|
|
1416
|
+
dir,
|
|
1417
|
+
pruned
|
|
1159
1418
|
};
|
|
1160
1419
|
if (opts.json === true) {
|
|
1161
1420
|
process.stdout.write(`${JSON.stringify({ ok: true, data })}
|
|
1162
1421
|
`);
|
|
1163
1422
|
return;
|
|
1164
1423
|
}
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
info(`next gate: ${s.nextGate}`, opts);
|
|
1169
|
-
} else {
|
|
1170
|
-
info("no further gate ahead (past verify, or stopped).", opts);
|
|
1171
|
-
}
|
|
1172
|
-
if (suggestion) info(`skill for current phase (${s.phase}): ${suggestion}`, opts);
|
|
1173
|
-
if (nextSkill) info(`skill for next gate (${s.nextGate}): ${nextSkill}`, opts);
|
|
1174
|
-
if (!suggestion && !nextSkill) {
|
|
1175
|
-
info("run `noir skills list` to see applicable skills.", opts);
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
|
-
async function taskNew(opts) {
|
|
1179
|
-
let mode;
|
|
1180
|
-
if (opts.mode !== void 0) {
|
|
1181
|
-
if (opts.mode !== "full" && opts.mode !== "quick") {
|
|
1182
|
-
fail(EXIT.USAGE, `task new: invalid mode '${opts.mode}' (expected 'full' or 'quick')`, opts);
|
|
1183
|
-
}
|
|
1184
|
-
mode = opts.mode;
|
|
1185
|
-
}
|
|
1186
|
-
const res = await callDaemonTool(
|
|
1187
|
-
opts,
|
|
1188
|
-
"workflow_start",
|
|
1189
|
-
{
|
|
1190
|
-
taskId: opts.slug,
|
|
1191
|
-
slug: opts.slug,
|
|
1192
|
-
...mode === void 0 ? {} : { mode }
|
|
1193
|
-
}
|
|
1194
|
-
);
|
|
1195
|
-
if (res.ok !== true) {
|
|
1196
|
-
const detail = typeof res.error === "string" && res.error.length > 0 ? res.error : "start failed";
|
|
1197
|
-
fail(EXIT.ERROR, `task new --slug ${opts.slug}: ${detail}`, opts);
|
|
1198
|
-
}
|
|
1199
|
-
if (opts.json === true) {
|
|
1200
|
-
process.stdout.write(`${JSON.stringify({ ok: true, data: res })}
|
|
1201
|
-
`);
|
|
1202
|
-
return;
|
|
1203
|
-
}
|
|
1204
|
-
renderStatusRow(res, opts);
|
|
1205
|
-
}
|
|
1206
|
-
async function taskAdvance(opts) {
|
|
1207
|
-
const args = {};
|
|
1208
|
-
if (typeof opts.to === "string" && opts.to.length > 0) {
|
|
1209
|
-
if (!Object.keys(PHASE_SKILL).includes(opts.to)) {
|
|
1210
|
-
fail(EXIT.USAGE, `task advance: invalid phase '${opts.to}'`, opts);
|
|
1211
|
-
}
|
|
1212
|
-
args.to = opts.to;
|
|
1213
|
-
}
|
|
1214
|
-
if (typeof opts.force === "string" && opts.force.length > 0) {
|
|
1215
|
-
args.force = { reason: opts.force };
|
|
1216
|
-
}
|
|
1217
|
-
const res = await callDaemonTool(
|
|
1218
|
-
opts,
|
|
1219
|
-
"workflow_advance",
|
|
1220
|
-
args
|
|
1424
|
+
log(
|
|
1425
|
+
`Synced ${summary.emitted.length} Noir skill${summary.emitted.length === 1 ? "" : "s"} to ${dir}.`,
|
|
1426
|
+
opts
|
|
1221
1427
|
);
|
|
1222
|
-
if (
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
process.stdout.write(`${JSON.stringify({ ok: true, data: res })}
|
|
1228
|
-
`);
|
|
1229
|
-
return;
|
|
1428
|
+
if (pruned.length > 0) {
|
|
1429
|
+
warn(
|
|
1430
|
+
`Pruned ${pruned.length} stale noir-* skill dir${pruned.length === 1 ? "" : "s"}: ${pruned.join(", ")}`,
|
|
1431
|
+
opts
|
|
1432
|
+
);
|
|
1230
1433
|
}
|
|
1231
|
-
renderStatusRow(res, opts);
|
|
1232
1434
|
}
|
|
1233
1435
|
|
|
1234
1436
|
// src/bin.ts
|
|
@@ -1251,7 +1453,15 @@ function toCliOptions(g) {
|
|
|
1251
1453
|
json: g.json === true,
|
|
1252
1454
|
quiet: g.quiet === true,
|
|
1253
1455
|
verbose: g.verbose === true,
|
|
1254
|
-
input: g.input !== false
|
|
1456
|
+
input: g.input !== false,
|
|
1457
|
+
// TUI policy — additive only. `--tui`/`--no-tui` land on `tui`
|
|
1458
|
+
// (true/false; absent when neither flag is given). Spread conditionally so
|
|
1459
|
+
// the default-args case (no flag) does NOT add a `tui` key — keeps the
|
|
1460
|
+
// exact-shape assertions in bin.test.ts green.
|
|
1461
|
+
...g.tui === true || g.tui === false ? { tui: g.tui === true } : {},
|
|
1462
|
+
// `--no-tips` is stored under `tips` (default true; flag ⇒ false). Map to
|
|
1463
|
+
// the additive `noTips` spelling used by CliOptions + `tip()`.
|
|
1464
|
+
...g.tips === false ? { noTips: true } : {}
|
|
1255
1465
|
};
|
|
1256
1466
|
}
|
|
1257
1467
|
function toStatusOptions(g) {
|
|
@@ -1266,9 +1476,38 @@ function parseHost(raw) {
|
|
|
1266
1476
|
if (raw === void 0) return void 0;
|
|
1267
1477
|
return raw;
|
|
1268
1478
|
}
|
|
1479
|
+
var DEPRECATIONS = [];
|
|
1480
|
+
function commandPath(cmd) {
|
|
1481
|
+
const path = [];
|
|
1482
|
+
let c2 = cmd;
|
|
1483
|
+
while (c2 !== null) {
|
|
1484
|
+
path.unshift(c2.name());
|
|
1485
|
+
c2 = c2.parent;
|
|
1486
|
+
}
|
|
1487
|
+
return path;
|
|
1488
|
+
}
|
|
1489
|
+
function emitDeprecationHintsFor(cmd, opts) {
|
|
1490
|
+
if (DEPRECATIONS.length === 0) return;
|
|
1491
|
+
const rel = commandPath(cmd).slice(1);
|
|
1492
|
+
for (const d of DEPRECATIONS) {
|
|
1493
|
+
if (d.oldArgv.every((a, i) => rel[i] === a)) {
|
|
1494
|
+
tip(
|
|
1495
|
+
`\`noir ${d.oldArgv.join(" ")}\` is deprecated since v${d.since}; use \`noir ${d.newArgv.join(" ")}\`.`,
|
|
1496
|
+
opts
|
|
1497
|
+
);
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1269
1501
|
function createProgram() {
|
|
1270
1502
|
const program2 = new Command();
|
|
1271
|
-
program2.name("noir").description("Noir \u2014 discipline, context, and memory layer for agentic CLIs.").version(NOIR_VERSION3, "-v, --version").addOption(new Option("--json", "emit machine-readable JSON to stdout")).addOption(new Option("--no-input", "never prompt; error if input is required")).addOption(new Option("--quiet", "suppress non-essential diagnostics")).addOption(new Option("--verbose", "show additional diagnostic detail")).addOption(new Option("--cwd <path>", "run as if started in <path>")).
|
|
1503
|
+
program2.name("noir").description("Noir \u2014 discipline, context, and memory layer for agentic CLIs.").version(NOIR_VERSION3, "-v, --version").addOption(new Option("--json", "emit machine-readable JSON to stdout")).addOption(new Option("--no-input", "never prompt; error if input is required")).addOption(new Option("--quiet", "suppress non-essential diagnostics")).addOption(new Option("--verbose", "show additional diagnostic detail")).addOption(new Option("--cwd <path>", "run as if started in <path>")).addOption(
|
|
1504
|
+
new Option("--tui", "prefer the interactive home menu for bare `noir` (advisory; TTY-only)")
|
|
1505
|
+
).addOption(
|
|
1506
|
+
new Option(
|
|
1507
|
+
"--no-tui",
|
|
1508
|
+
"route bare `noir` to the non-interactive `status` path even in a TTY"
|
|
1509
|
+
)
|
|
1510
|
+
).addOption(new Option("--no-tips", "suppress redirect / deprecation hints on stderr")).exitOverride((err) => {
|
|
1272
1511
|
throw err;
|
|
1273
1512
|
}).configureOutput({
|
|
1274
1513
|
// Stream discipline: --help/--version → stdout (conventional; users pipe
|
|
@@ -1294,6 +1533,7 @@ function createProgram() {
|
|
|
1294
1533
|
const nonInteractive = opts.json === true || opts.input === false;
|
|
1295
1534
|
if (nonInteractive) process.env.NOIR_NON_INTERACTIVE = "1";
|
|
1296
1535
|
else delete process.env.NOIR_NON_INTERACTIVE;
|
|
1536
|
+
emitDeprecationHintsFor(actionCmd, toCliOptions(opts));
|
|
1297
1537
|
});
|
|
1298
1538
|
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(
|
|
1299
1539
|
"--upgrade",
|
|
@@ -1306,39 +1546,45 @@ function createProgram() {
|
|
|
1306
1546
|
new Option(
|
|
1307
1547
|
"--host <id>",
|
|
1308
1548
|
"target agentic CLI (default: claude) \u2014 drives host-side emission"
|
|
1309
|
-
).choices(
|
|
1549
|
+
).choices(SUPPORTED_HOSTS)
|
|
1310
1550
|
).action(
|
|
1311
|
-
async (opts) => {
|
|
1551
|
+
async (opts, cmd) => {
|
|
1312
1552
|
const transport = opts.transport === "streamable-http" ? "streamable-http" : "stdio";
|
|
1313
1553
|
const upgrade = opts.upgrade === true;
|
|
1314
1554
|
const force = opts.force === true;
|
|
1315
1555
|
const host = parseHost(opts.host);
|
|
1316
|
-
await init(process.cwd(), {
|
|
1556
|
+
const result = await init(process.cwd(), {
|
|
1317
1557
|
transport,
|
|
1318
1558
|
url: opts.url,
|
|
1319
1559
|
...upgrade ? { upgrade } : {},
|
|
1320
1560
|
...force ? { force } : {},
|
|
1321
1561
|
...host !== void 0 ? { host } : {}
|
|
1322
1562
|
});
|
|
1563
|
+
if (cmd.optsWithGlobals().json === true && result !== void 0) {
|
|
1564
|
+
json({ ok: true, data: result });
|
|
1565
|
+
}
|
|
1323
1566
|
}
|
|
1324
1567
|
);
|
|
1325
1568
|
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(
|
|
1326
1569
|
new Option(
|
|
1327
1570
|
"--host <id>",
|
|
1328
1571
|
"target agentic CLI (default: claude) \u2014 drives host-side emission"
|
|
1329
|
-
).choices(
|
|
1572
|
+
).choices(SUPPORTED_HOSTS)
|
|
1330
1573
|
).action(
|
|
1331
|
-
async (dir, opts) => {
|
|
1574
|
+
async (dir, opts, cmd) => {
|
|
1332
1575
|
const transport = opts.transport === "streamable-http" ? "streamable-http" : "stdio";
|
|
1333
1576
|
const force = opts.force === true;
|
|
1334
1577
|
const host = parseHost(opts.host);
|
|
1335
|
-
const { create } = await import("./create-
|
|
1336
|
-
await create(dir, {
|
|
1578
|
+
const { create } = await import("./create-7ERBL42K.js");
|
|
1579
|
+
const result = await create(dir, {
|
|
1337
1580
|
transport,
|
|
1338
1581
|
url: opts.url,
|
|
1339
1582
|
...force ? { force } : {},
|
|
1340
1583
|
...host !== void 0 ? { host } : {}
|
|
1341
1584
|
});
|
|
1585
|
+
if (cmd.optsWithGlobals().json === true && result !== void 0) {
|
|
1586
|
+
json({ ok: true, data: result });
|
|
1587
|
+
}
|
|
1342
1588
|
}
|
|
1343
1589
|
);
|
|
1344
1590
|
program2.command("sync").description(
|
|
@@ -1348,7 +1594,15 @@ function createProgram() {
|
|
|
1348
1594
|
"overwrite differing regenerated files without prompting (bypasses the conflict menu)"
|
|
1349
1595
|
).option(
|
|
1350
1596
|
"--merge",
|
|
1351
|
-
"three-way merge managed regions (
|
|
1597
|
+
"three-way merge managed regions (default since 1.3.0; flag kept for compatibility)"
|
|
1598
|
+
).addOption(
|
|
1599
|
+
// Opt OUT of managed-region merge (restore strip-replace). Commander
|
|
1600
|
+
// stores `--no-merge-regions` under `mergeRegions` (default true; flag →
|
|
1601
|
+
// false). Bare `noir sync` keeps the merge default (true).
|
|
1602
|
+
new Option(
|
|
1603
|
+
"--no-merge-regions",
|
|
1604
|
+
"strip-replace managed regions (discard hand-edits inside <!-- noir:* --> markers)"
|
|
1605
|
+
)
|
|
1352
1606
|
).addOption(
|
|
1353
1607
|
// S10: optional `--host` override. When omitted, sync reads host from
|
|
1354
1608
|
// `.noir/config.yml` (whatever `noir init --host <id>` persisted). The
|
|
@@ -1356,22 +1610,25 @@ function createProgram() {
|
|
|
1356
1610
|
new Option(
|
|
1357
1611
|
"--host <id>",
|
|
1358
1612
|
"override the configured host (advanced; default reads .noir/config.yml)"
|
|
1359
|
-
).choices(
|
|
1360
|
-
).action(
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
await sync(process.cwd(), {
|
|
1613
|
+
).choices(SUPPORTED_HOSTS)
|
|
1614
|
+
).action(
|
|
1615
|
+
async (opts, cmd) => {
|
|
1616
|
+
const { sync } = await import("./sync-SCAF6Z5H.js");
|
|
1617
|
+
const host = parseHost(opts.host);
|
|
1618
|
+
const force = opts.force === true;
|
|
1619
|
+
const merge = opts.merge === true;
|
|
1620
|
+
const noMergeRegions = opts.mergeRegions === false;
|
|
1621
|
+
const result = host === void 0 && !force && !merge && !noMergeRegions ? await sync(process.cwd()) : await sync(process.cwd(), {
|
|
1369
1622
|
...host !== void 0 ? { host } : {},
|
|
1370
1623
|
...force ? { force } : {},
|
|
1371
|
-
...merge ? { merge } : {}
|
|
1624
|
+
...merge ? { merge } : {},
|
|
1625
|
+
...noMergeRegions ? { mergeManagedRegions: false } : {}
|
|
1372
1626
|
});
|
|
1627
|
+
if (cmd.optsWithGlobals().json === true) {
|
|
1628
|
+
json({ ok: true, data: result });
|
|
1629
|
+
}
|
|
1373
1630
|
}
|
|
1374
|
-
|
|
1631
|
+
);
|
|
1375
1632
|
const mcpCmd = program2.command("mcp").description("MCP server control");
|
|
1376
1633
|
mcpCmd.command("serve").description("run the Noir MCP server (stdio, or via the shared daemon)").option("--stdio", "force the stdio transport").action(async (opts) => {
|
|
1377
1634
|
await serve({ stdio: opts.stdio === true });
|
|
@@ -1443,7 +1700,7 @@ function createProgram() {
|
|
|
1443
1700
|
contextGrp.action(() => {
|
|
1444
1701
|
throw new NoirCliError(EXIT.USAGE, "Usage: noir context search|index|status");
|
|
1445
1702
|
});
|
|
1446
|
-
const memoryGrp = program2.command("memory").description("memory engine
|
|
1703
|
+
const memoryGrp = program2.command("memory").description("memory engine");
|
|
1447
1704
|
memoryGrp.command("recall").description("recall memories for a query").argument("<query>", "recall query").option("--limit <n>", "max results", "10").action(async (...args) => {
|
|
1448
1705
|
const cmd = trailingCmd(args);
|
|
1449
1706
|
const g = cmd.optsWithGlobals();
|
|
@@ -1508,7 +1765,7 @@ function createProgram() {
|
|
|
1508
1765
|
skillsGrp.action(() => {
|
|
1509
1766
|
throw new NoirCliError(EXIT.USAGE, "Usage: noir skills list|sync");
|
|
1510
1767
|
});
|
|
1511
|
-
const taskGrp = program2.command("task").description("workflow task control
|
|
1768
|
+
const taskGrp = program2.command("task").description("workflow task control");
|
|
1512
1769
|
taskGrp.command("new").description("start a new workflow task").requiredOption("--slug <slug>", "task slug").option("--mode <mode>", "full | quick").action(async (...args) => {
|
|
1513
1770
|
const cmd = trailingCmd(args);
|
|
1514
1771
|
const g = cmd.optsWithGlobals();
|
|
@@ -1547,6 +1804,34 @@ function createProgram() {
|
|
|
1547
1804
|
taskGrp.action(() => {
|
|
1548
1805
|
throw new NoirCliError(EXIT.USAGE, "Usage: noir task new|status|advance|next");
|
|
1549
1806
|
});
|
|
1807
|
+
function buildHandoffOptions(g) {
|
|
1808
|
+
return { ...toCliOptions(g), ...g.write === true ? { write: true } : {} };
|
|
1809
|
+
}
|
|
1810
|
+
program2.command("handoff").description("emit a ready-to-paste host handoff prompt").option("--write", "persist to .noir/handoff/<id>.md (gitignored)").action(async (...args) => {
|
|
1811
|
+
await handoff(buildHandoffOptions(trailingCmd(args).optsWithGlobals()));
|
|
1812
|
+
});
|
|
1813
|
+
program2.command("wrap").description("session-end alias for `noir handoff`").option("--write", "persist to .noir/handoff/<id>.md (gitignored)").action(async (...args) => {
|
|
1814
|
+
await handoff(buildHandoffOptions(trailingCmd(args).optsWithGlobals()));
|
|
1815
|
+
});
|
|
1816
|
+
program2.command("tui").description("interactive Ink dashboard (host \xB7 phase \xB7 daemon + /command dispatch)").action(async (...args) => {
|
|
1817
|
+
const g = actionGlobals(args);
|
|
1818
|
+
const opts = toCliOptions(g);
|
|
1819
|
+
requireInteractive(opts, "`noir tui`");
|
|
1820
|
+
const tuiUrl = new URL("./tui/index.js", import.meta.url).href;
|
|
1821
|
+
const { runTui } = await import(
|
|
1822
|
+
/* @vite-ignore */
|
|
1823
|
+
tuiUrl
|
|
1824
|
+
);
|
|
1825
|
+
const dispatch = async (argv) => {
|
|
1826
|
+
const sub = createProgram();
|
|
1827
|
+
try {
|
|
1828
|
+
await sub.parseAsync([...argv], { from: "user" });
|
|
1829
|
+
} catch (err) {
|
|
1830
|
+
handleError(err);
|
|
1831
|
+
}
|
|
1832
|
+
};
|
|
1833
|
+
await runTui(opts, dispatch);
|
|
1834
|
+
});
|
|
1550
1835
|
const homeDeps = {
|
|
1551
1836
|
dispatch: async (argv) => {
|
|
1552
1837
|
const sub = createProgram();
|
|
@@ -1593,9 +1878,11 @@ if (isMainModule) {
|
|
|
1593
1878
|
void run(process.argv.slice(2));
|
|
1594
1879
|
}
|
|
1595
1880
|
export {
|
|
1881
|
+
DEPRECATIONS,
|
|
1596
1882
|
EXIT,
|
|
1597
1883
|
NoirCliError,
|
|
1598
1884
|
createProgram,
|
|
1885
|
+
emitDeprecationHintsFor,
|
|
1599
1886
|
fail,
|
|
1600
1887
|
inferExitCode,
|
|
1601
1888
|
program,
|