@noir-ai/cli 1.7.4-beta.1 → 1.8.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 +39 -4
- package/dist/bin.js +316 -28
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-B7OKXQKF.js → chunk-BM3VEJ2J.js} +117 -4
- package/dist/chunk-BM3VEJ2J.js.map +1 -0
- package/dist/{create-RHFPB5Q2.js → create-UO3QTGL7.js} +11 -6
- package/dist/create-UO3QTGL7.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +2 -4
- package/dist/{sync-ZGCGL3OU.js → sync-MZETGPOJ.js} +10 -3
- package/dist/sync-MZETGPOJ.js.map +1 -0
- package/dist/tui/{chunk-MGRKJMHG.js → chunk-XFBZ5X3B.js} +117 -4
- package/dist/tui/chunk-XFBZ5X3B.js.map +1 -0
- package/dist/tui/{create-DH2VPDVS.js → create-Z5HEBB7K.js} +11 -6
- package/dist/tui/create-Z5HEBB7K.js.map +1 -0
- package/dist/tui/index.js +1311 -476
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/{sync-DT4JKXZK.js → sync-5M2UEOKW.js} +10 -3
- package/dist/tui/sync-5M2UEOKW.js.map +1 -0
- package/package.json +11 -9
- package/dist/chunk-B7OKXQKF.js.map +0 -1
- package/dist/chunk-KQYVVGOH.js +0 -102
- package/dist/chunk-KQYVVGOH.js.map +0 -1
- package/dist/create-RHFPB5Q2.js.map +0 -1
- package/dist/sync-ZGCGL3OU.js.map +0 -1
- package/dist/tui/chunk-KBM2UXAC.js +0 -101
- package/dist/tui/chunk-KBM2UXAC.js.map +0 -1
- package/dist/tui/chunk-MGRKJMHG.js.map +0 -1
- package/dist/tui/create-DH2VPDVS.js.map +0 -1
- package/dist/tui/sync-DT4JKXZK.js.map +0 -1
package/dist/bin.d.ts
CHANGED
|
@@ -2,6 +2,41 @@ import { Command } from 'commander';
|
|
|
2
2
|
import { C as CliOptions } from './output-BNE9htEm.js';
|
|
3
3
|
export { E as EXIT, N as NoirCliError, f as fail, i as inferExitCode } from './output-BNE9htEm.js';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* A palette entry projecting a leaf `noir` subcommand for the TUI palette.
|
|
7
|
+
*/
|
|
8
|
+
interface PaletteCommand {
|
|
9
|
+
/** Space-joined argv — the stable dispatch key (e.g. `context search`). */
|
|
10
|
+
readonly id: string;
|
|
11
|
+
/** Title-cased argv joined by `: ` for the palette's human label. */
|
|
12
|
+
readonly label: string;
|
|
13
|
+
/**
|
|
14
|
+
* The raw token path, root name excluded. This is what the palette re-dispatches
|
|
15
|
+
* (it is NOT a live reference to commander's parsed argv).
|
|
16
|
+
*/
|
|
17
|
+
readonly argv: readonly string[];
|
|
18
|
+
/** One-line summary; falls back to `argv.join(' ')` when the command omits one. */
|
|
19
|
+
readonly description: string;
|
|
20
|
+
/** Top-level group the command belongs to (`argv[0]`), for palette grouping. */
|
|
21
|
+
readonly category: string;
|
|
22
|
+
/** Search keywords — currently the argv tokens, used by the palette's matcher. */
|
|
23
|
+
readonly keywords: readonly string[];
|
|
24
|
+
/**
|
|
25
|
+
* True for commands that mutate the store / workflow / host artifacts (context
|
|
26
|
+
* index/forget, memory save/forget/consolidate, task new/advance, skills sync,
|
|
27
|
+
* daemon start/stop/restart, init, create, sync, install, update). The palette
|
|
28
|
+
* uses this to require confirmation before dispatch.
|
|
29
|
+
*/
|
|
30
|
+
readonly destructive: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The TUI palette source (B3). Walks a FRESH {@link createProgram} at `noir tui`
|
|
35
|
+
* launch and projects every leaf subcommand into a {@link PaletteCommand}.
|
|
36
|
+
* Defined here (not in the tui) so the palette derives from the REAL command
|
|
37
|
+
* tree without the tui graph importing the bin (which would be circular).
|
|
38
|
+
*/
|
|
39
|
+
declare function buildPaletteCommandsForTui(): PaletteCommand[];
|
|
5
40
|
/** One entry in the {@link DEPRECATIONS} registry. */
|
|
6
41
|
interface DeprecationEntry {
|
|
7
42
|
/** Old command form as a path prefix, e.g. `['legacy-status']` or
|
|
@@ -15,9 +50,9 @@ interface DeprecationEntry {
|
|
|
15
50
|
}
|
|
16
51
|
/**
|
|
17
52
|
* Deprecation registry. **Empty today** — no `noir` command is deprecated.
|
|
18
|
-
* When adding an entry,
|
|
19
|
-
* form still routes, add a redirect). {@link emitDeprecationHintsFor}
|
|
20
|
-
* this on every dispatch and emits one `tip()` per match; `--no-tips` /
|
|
53
|
+
* When adding an entry, update the CHANGELOG + `docs/reference/cli.md` (and,
|
|
54
|
+
* if the old form still routes, add a redirect). {@link emitDeprecationHintsFor}
|
|
55
|
+
* scans this on every dispatch and emits one `tip()` per match; `--no-tips` /
|
|
21
56
|
* `--json` suppress the hint (see {@link tip}).
|
|
22
57
|
*/
|
|
23
58
|
declare const DEPRECATIONS: DeprecationEntry[];
|
|
@@ -39,4 +74,4 @@ declare const program: Command;
|
|
|
39
74
|
*/
|
|
40
75
|
declare function run(argv?: readonly string[]): Promise<number>;
|
|
41
76
|
|
|
42
|
-
export { DEPRECATIONS, type DeprecationEntry, createProgram, emitDeprecationHintsFor, program, run };
|
|
77
|
+
export { DEPRECATIONS, type DeprecationEntry, buildPaletteCommandsForTui, createProgram, emitDeprecationHintsFor, program, run };
|
package/dist/bin.js
CHANGED
|
@@ -8,11 +8,9 @@ import {
|
|
|
8
8
|
} from "./chunk-JUVY6L3F.js";
|
|
9
9
|
import "./chunk-L7XR7EA3.js";
|
|
10
10
|
import {
|
|
11
|
+
doctor,
|
|
11
12
|
init
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import {
|
|
14
|
-
doctor
|
|
15
|
-
} from "./chunk-B7OKXQKF.js";
|
|
13
|
+
} from "./chunk-BM3VEJ2J.js";
|
|
16
14
|
import {
|
|
17
15
|
EXIT,
|
|
18
16
|
NoirCliError,
|
|
@@ -44,9 +42,14 @@ import { Command, Option } from "commander";
|
|
|
44
42
|
|
|
45
43
|
// src/daemon-client.ts
|
|
46
44
|
import { Client, StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
|
|
45
|
+
import { ContextEngine, createEmbedFn, resolveEmbedderConfig } from "@noir-ai/context";
|
|
47
46
|
import { loadProjectInfo, NOIR_VERSION } from "@noir-ai/core";
|
|
48
47
|
import { ensureDaemonRunning, pidAlive, readDaemonRecord } from "@noir-ai/daemon";
|
|
48
|
+
import { createMemoryEngine } from "@noir-ai/memory";
|
|
49
|
+
import { openStore } from "@noir-ai/store";
|
|
50
|
+
import { WorkflowEngine } from "@noir-ai/workflow";
|
|
49
51
|
var DAEMON_DOWN_HINT = "daemon not reachable \u2014 try `noir daemon start`";
|
|
52
|
+
var PROBE_TIMEOUT_MS = 1500;
|
|
50
53
|
function describeCause(cause) {
|
|
51
54
|
if (cause instanceof Error) return `${cause.name}: ${cause.message}`;
|
|
52
55
|
try {
|
|
@@ -74,7 +77,9 @@ async function probeDaemon(opts = {}) {
|
|
|
74
77
|
return { running: false };
|
|
75
78
|
}
|
|
76
79
|
try {
|
|
77
|
-
const res = await fetch(`http://127.0.0.1:${rec.port}/health
|
|
80
|
+
const res = await fetch(`http://127.0.0.1:${rec.port}/health`, {
|
|
81
|
+
signal: AbortSignal.timeout(PROBE_TIMEOUT_MS)
|
|
82
|
+
});
|
|
78
83
|
if (!res.ok) {
|
|
79
84
|
if (opts.verbose) process.stderr.write(`noir: daemon probe: /health \u2192 HTTP ${res.status}
|
|
80
85
|
`);
|
|
@@ -189,6 +194,33 @@ async function withRunningDaemon(opts, fn, probe) {
|
|
|
189
194
|
async function callDaemonTool(opts, name, args = {}) {
|
|
190
195
|
return withDaemon(opts, (caller) => caller.callTool(name, args));
|
|
191
196
|
}
|
|
197
|
+
async function withInProcessRead(opts, fn) {
|
|
198
|
+
const project = opts.project ?? loadProjectInfo(process.cwd());
|
|
199
|
+
const store = await openStore({ projectId: project.id, root: project.root, readonly: true });
|
|
200
|
+
try {
|
|
201
|
+
const embedderCfg = resolveEmbedderConfig(project.config.context);
|
|
202
|
+
const embed = createEmbedFn(embedderCfg).embed;
|
|
203
|
+
const context = new ContextEngine({
|
|
204
|
+
store,
|
|
205
|
+
root: project.root,
|
|
206
|
+
projectId: project.id,
|
|
207
|
+
embedderCfg,
|
|
208
|
+
storeDegraded: true
|
|
209
|
+
});
|
|
210
|
+
const memory = createMemoryEngine({
|
|
211
|
+
store,
|
|
212
|
+
root: project.root,
|
|
213
|
+
projectId: project.id,
|
|
214
|
+
embed,
|
|
215
|
+
storeDegraded: true
|
|
216
|
+
});
|
|
217
|
+
const workflow = new WorkflowEngine(store, project.root, project.id);
|
|
218
|
+
return await fn({ context, memory, workflow });
|
|
219
|
+
} finally {
|
|
220
|
+
await store.close().catch(() => {
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
192
224
|
|
|
193
225
|
// src/commands/context.ts
|
|
194
226
|
function parseLimit(raw, label, opts) {
|
|
@@ -214,6 +246,39 @@ function toHit(raw) {
|
|
|
214
246
|
}
|
|
215
247
|
async function contextSearch(opts) {
|
|
216
248
|
const limit = parseLimit(opts.limit, "context search", opts);
|
|
249
|
+
let probe;
|
|
250
|
+
try {
|
|
251
|
+
probe = await probeDaemon(opts);
|
|
252
|
+
} catch {
|
|
253
|
+
probe = { running: true };
|
|
254
|
+
}
|
|
255
|
+
if (!probe.running) {
|
|
256
|
+
await withInProcessRead(opts, async (engines) => {
|
|
257
|
+
const result = await engines.context.search(opts.query, {
|
|
258
|
+
...limit === void 0 ? {} : { limit }
|
|
259
|
+
});
|
|
260
|
+
const data2 = {
|
|
261
|
+
query: opts.query,
|
|
262
|
+
hits: result.results.map((h) => ({
|
|
263
|
+
path: h.path,
|
|
264
|
+
score: h.score,
|
|
265
|
+
snippet: h.snippet,
|
|
266
|
+
source: h.source
|
|
267
|
+
})),
|
|
268
|
+
consumedTokens: result.consumedTokens,
|
|
269
|
+
truncated: result.truncated,
|
|
270
|
+
degraded: result.degraded,
|
|
271
|
+
mode: result.mode
|
|
272
|
+
};
|
|
273
|
+
if (opts.json === true) {
|
|
274
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: data2 })}
|
|
275
|
+
`);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
renderSearch(data2, opts);
|
|
279
|
+
});
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
217
282
|
const res = await callDaemonTool(opts, "context_search", {
|
|
218
283
|
query: opts.query,
|
|
219
284
|
...limit === void 0 ? {} : { limit }
|
|
@@ -253,13 +318,8 @@ function renderSearch(data, opts) {
|
|
|
253
318
|
);
|
|
254
319
|
}
|
|
255
320
|
async function contextIndex(opts) {
|
|
256
|
-
if (opts.force === true) {
|
|
257
|
-
info(
|
|
258
|
-
"context index: --force is recognized but not yet honored (content-hash is always incremental).",
|
|
259
|
-
opts
|
|
260
|
-
);
|
|
261
|
-
}
|
|
262
321
|
const args = opts.paths && opts.paths.length > 0 ? { paths: opts.paths } : {};
|
|
322
|
+
if (opts.force === true) args.force = true;
|
|
263
323
|
const res = await callDaemonTool(opts, "context_index", args);
|
|
264
324
|
if (res.ok !== true) failTool("context index", res, opts);
|
|
265
325
|
const data = {
|
|
@@ -333,7 +393,8 @@ import {
|
|
|
333
393
|
clearDaemonRecord,
|
|
334
394
|
ensureDaemonRunning as ensureDaemonRunning2,
|
|
335
395
|
pidAlive as pidAlive2,
|
|
336
|
-
readDaemonRecord as readDaemonRecord2
|
|
396
|
+
readDaemonRecord as readDaemonRecord2,
|
|
397
|
+
spawnDetachedDaemon
|
|
337
398
|
} from "@noir-ai/daemon";
|
|
338
399
|
var MODE = "foreground";
|
|
339
400
|
function formatUptime(sec) {
|
|
@@ -342,12 +403,69 @@ function formatUptime(sec) {
|
|
|
342
403
|
if (sec < 3600) return `${Math.floor(sec / 60)}m ${sec % 60}s`;
|
|
343
404
|
return `${Math.floor(sec / 3600)}h ${Math.floor(sec % 3600 / 60)}m`;
|
|
344
405
|
}
|
|
406
|
+
async function isHealthy(port) {
|
|
407
|
+
try {
|
|
408
|
+
const res = await fetch(`http://127.0.0.1:${port}/health`);
|
|
409
|
+
return res.status === 200;
|
|
410
|
+
} catch {
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
345
414
|
async function daemonStart(opts) {
|
|
415
|
+
const project = loadProjectInfo2(process.cwd());
|
|
416
|
+
if (opts.detachChild === true) {
|
|
417
|
+
const child = await ensureDaemonRunning2({
|
|
418
|
+
project,
|
|
419
|
+
idleTimeoutSec: project.config.daemon.idleTimeoutSec
|
|
420
|
+
});
|
|
421
|
+
if (child.started) {
|
|
422
|
+
if (opts.json === true) {
|
|
423
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: { mode: "detached" } })}
|
|
424
|
+
`);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
info("noir daemon: detached foreground. Ctrl+C to stop.", opts);
|
|
428
|
+
log(`Noir daemon listening at ${child.url}`, opts);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (opts.json === true) {
|
|
432
|
+
process.stdout.write(
|
|
433
|
+
`${JSON.stringify({ ok: true, data: { mode: "detached", reused: true } })}
|
|
434
|
+
`
|
|
435
|
+
);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
info("Noir daemon already running; detached child exiting.", opts);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
346
441
|
if (opts.detach === true) {
|
|
347
|
-
|
|
442
|
+
const existing = readDaemonRecord2();
|
|
443
|
+
if (existing && pidAlive2(existing.pid) && await isHealthy(existing.port)) {
|
|
444
|
+
if (opts.json === true) {
|
|
445
|
+
process.stdout.write(
|
|
446
|
+
`${JSON.stringify({ ok: true, data: { mode: "detached", reused: true } })}
|
|
447
|
+
`
|
|
448
|
+
);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
info(
|
|
452
|
+
`Noir daemon already running at http://127.0.0.1:${existing.port}/mcp (detach not needed).`,
|
|
453
|
+
opts
|
|
454
|
+
);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
const spawned = await spawnDetachedDaemon({ project });
|
|
458
|
+
if (opts.json === true) {
|
|
459
|
+
process.stdout.write(
|
|
460
|
+
`${JSON.stringify({ ok: true, data: { mode: "detached", pid: spawned.pid, port: spawned.port } })}
|
|
461
|
+
`
|
|
462
|
+
);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
info(`Noir daemon started in the background (pid ${spawned.pid}, port ${spawned.port}).`, opts);
|
|
466
|
+
return;
|
|
348
467
|
}
|
|
349
468
|
const ds = spinner("Starting Noir daemon...", opts).start();
|
|
350
|
-
const project = loadProjectInfo2(process.cwd());
|
|
351
469
|
const ensured = await ensureDaemonRunning2({
|
|
352
470
|
project,
|
|
353
471
|
idleTimeoutSec: project.config.daemon.idleTimeoutSec
|
|
@@ -370,7 +488,7 @@ async function daemonStart(opts) {
|
|
|
370
488
|
);
|
|
371
489
|
return;
|
|
372
490
|
}
|
|
373
|
-
info("noir daemon: foreground mode
|
|
491
|
+
info("noir daemon: foreground mode. Ctrl+C to stop.", opts);
|
|
374
492
|
log(`Noir daemon listening at ${ensured.url}`, opts);
|
|
375
493
|
return;
|
|
376
494
|
}
|
|
@@ -466,7 +584,9 @@ async function daemonStatus(opts) {
|
|
|
466
584
|
port: rec.port,
|
|
467
585
|
startedAt: rec.startedAt,
|
|
468
586
|
uptimeSec,
|
|
469
|
-
mode:
|
|
587
|
+
// Honest ownership: the detached child writes `mode:'detached'`; anything
|
|
588
|
+
// else (legacy record, foreground start) reports 'foreground'.
|
|
589
|
+
mode: rec.mode ?? MODE
|
|
470
590
|
};
|
|
471
591
|
if (opts.json === true) {
|
|
472
592
|
process.stdout.write(`${JSON.stringify({ ok: true, data })}
|
|
@@ -655,6 +775,7 @@ async function status(opts) {
|
|
|
655
775
|
}
|
|
656
776
|
|
|
657
777
|
// src/commands/task.ts
|
|
778
|
+
import { PHASES } from "@noir-ai/workflow";
|
|
658
779
|
var PHASE_SKILL = {
|
|
659
780
|
intake: "noir-intake",
|
|
660
781
|
clarify: "noir-clarify",
|
|
@@ -698,7 +819,49 @@ function renderStatusRow(s, opts) {
|
|
|
698
819
|
opts
|
|
699
820
|
);
|
|
700
821
|
}
|
|
822
|
+
var GATE_PHASES = ["spec", "plan", "verify"];
|
|
823
|
+
function nextGateAfter(phase) {
|
|
824
|
+
const cur = PHASES.indexOf(phase);
|
|
825
|
+
for (const p of GATE_PHASES) {
|
|
826
|
+
if (PHASES.indexOf(p) > cur) return p;
|
|
827
|
+
}
|
|
828
|
+
return null;
|
|
829
|
+
}
|
|
701
830
|
async function taskStatus(opts) {
|
|
831
|
+
let probe;
|
|
832
|
+
try {
|
|
833
|
+
probe = await probeDaemon(opts);
|
|
834
|
+
} catch {
|
|
835
|
+
probe = { running: true };
|
|
836
|
+
}
|
|
837
|
+
if (!probe.running) {
|
|
838
|
+
const s2 = await withInProcessRead(opts, async (engines) => {
|
|
839
|
+
const id = typeof opts.id === "string" && opts.id.length > 0 ? opts.id : null;
|
|
840
|
+
const taskId = id ?? engines.workflow.activeTaskId();
|
|
841
|
+
if (!taskId) fail(EXIT.NOT_FOUND, "task: no active task", opts);
|
|
842
|
+
const task = engines.workflow.status(taskId);
|
|
843
|
+
if (!task) fail(EXIT.NOT_FOUND, `task: unknown task '${taskId}'`, opts);
|
|
844
|
+
const stopped = task.state === "blocked" || task.state === "abandoned";
|
|
845
|
+
return {
|
|
846
|
+
ok: true,
|
|
847
|
+
taskId: task.taskId,
|
|
848
|
+
phase: task.phase,
|
|
849
|
+
state: task.state,
|
|
850
|
+
mode: task.mode,
|
|
851
|
+
nextGate: stopped ? null : nextGateAfter(task.phase),
|
|
852
|
+
history: task.history,
|
|
853
|
+
updatedAt: task.updatedAt,
|
|
854
|
+
degraded: true
|
|
855
|
+
};
|
|
856
|
+
});
|
|
857
|
+
if (opts.json === true) {
|
|
858
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: s2 })}
|
|
859
|
+
`);
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
renderStatusRow(s2, opts);
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
702
865
|
const s = await fetchStatus(opts, opts.id);
|
|
703
866
|
if (opts.json === true) {
|
|
704
867
|
process.stdout.write(`${JSON.stringify({ ok: true, data: s })}
|
|
@@ -1179,6 +1342,27 @@ function stamp(ts) {
|
|
|
1179
1342
|
}
|
|
1180
1343
|
async function memoryRecall(opts) {
|
|
1181
1344
|
const limit = parseLimit2(opts.limit, "memory recall", opts);
|
|
1345
|
+
let probe;
|
|
1346
|
+
try {
|
|
1347
|
+
probe = await probeDaemon(opts);
|
|
1348
|
+
} catch {
|
|
1349
|
+
probe = { running: true };
|
|
1350
|
+
}
|
|
1351
|
+
if (!probe.running) {
|
|
1352
|
+
await withInProcessRead(opts, async (engines) => {
|
|
1353
|
+
const result = await engines.memory.recall(opts.query, {
|
|
1354
|
+
...limit === void 0 ? {} : { limit }
|
|
1355
|
+
});
|
|
1356
|
+
const data2 = { query: opts.query, hits: result, degraded: false };
|
|
1357
|
+
if (opts.json === true) {
|
|
1358
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: data2 })}
|
|
1359
|
+
`);
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
renderRecall(data2.query, result, data2.degraded, opts);
|
|
1363
|
+
});
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1182
1366
|
const res = await callDaemonTool(opts, "memory_recall", {
|
|
1183
1367
|
query: opts.query,
|
|
1184
1368
|
...limit === void 0 ? {} : { limit }
|
|
@@ -1264,6 +1448,30 @@ function renderObservation(obs, opts) {
|
|
|
1264
1448
|
${content}`, opts);
|
|
1265
1449
|
}
|
|
1266
1450
|
async function memorySessions(opts) {
|
|
1451
|
+
let probe;
|
|
1452
|
+
try {
|
|
1453
|
+
probe = await probeDaemon(opts);
|
|
1454
|
+
} catch {
|
|
1455
|
+
probe = { running: true };
|
|
1456
|
+
}
|
|
1457
|
+
if (!probe.running) {
|
|
1458
|
+
await withInProcessRead(opts, async (engines) => {
|
|
1459
|
+
const raw = engines.memory.sessions();
|
|
1460
|
+
const sessions2 = raw.map((s) => ({
|
|
1461
|
+
id: s.id,
|
|
1462
|
+
count: s.count,
|
|
1463
|
+
lastTs: s.lastTs
|
|
1464
|
+
}));
|
|
1465
|
+
const data2 = { sessions: sessions2 };
|
|
1466
|
+
if (opts.json === true) {
|
|
1467
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: data2 })}
|
|
1468
|
+
`);
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1471
|
+
renderSessions(sessions2, opts);
|
|
1472
|
+
});
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1267
1475
|
const res = await callDaemonTool(opts, "memory_sessions");
|
|
1268
1476
|
if (res.ok !== true) failTool2("memory sessions", res, opts);
|
|
1269
1477
|
const sessions = Array.isArray(res.sessions) ? res.sessions.map((raw) => {
|
|
@@ -1280,6 +1488,9 @@ async function memorySessions(opts) {
|
|
|
1280
1488
|
`);
|
|
1281
1489
|
return;
|
|
1282
1490
|
}
|
|
1491
|
+
renderSessions(sessions, opts);
|
|
1492
|
+
}
|
|
1493
|
+
function renderSessions(sessions, opts) {
|
|
1283
1494
|
log(`memory sessions \u2014 ${sessions.length} session${sessions.length === 1 ? "" : "s"}`, opts);
|
|
1284
1495
|
table(
|
|
1285
1496
|
sessions.map((s) => ({ Session: s.id, Observations: s.count, "Last seen": stamp(s.lastTs) })),
|
|
@@ -1491,7 +1702,70 @@ async function skillsSync(opts) {
|
|
|
1491
1702
|
}
|
|
1492
1703
|
}
|
|
1493
1704
|
|
|
1705
|
+
// src/tui/commands/registry.ts
|
|
1706
|
+
var DESTRUCTIVE_PREFIXES = [
|
|
1707
|
+
["context", "index"],
|
|
1708
|
+
["context", "forget"],
|
|
1709
|
+
["memory", "save"],
|
|
1710
|
+
["memory", "forget"],
|
|
1711
|
+
["memory", "consolidate"],
|
|
1712
|
+
["task", "new"],
|
|
1713
|
+
["task", "advance"],
|
|
1714
|
+
["skills", "sync"],
|
|
1715
|
+
["daemon", "start"],
|
|
1716
|
+
["daemon", "stop"],
|
|
1717
|
+
["daemon", "restart"],
|
|
1718
|
+
["init"],
|
|
1719
|
+
["create"],
|
|
1720
|
+
["sync"],
|
|
1721
|
+
["install"],
|
|
1722
|
+
["update"]
|
|
1723
|
+
];
|
|
1724
|
+
function isDestructive(argv) {
|
|
1725
|
+
return DESTRUCTIVE_PREFIXES.some((prefix) => {
|
|
1726
|
+
if (argv.length < prefix.length) return false;
|
|
1727
|
+
return prefix.every((tok, i) => argv[i] === tok);
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1730
|
+
function capitalizeFirst(s) {
|
|
1731
|
+
if (s.length === 0) return s;
|
|
1732
|
+
return `${s[0]?.toUpperCase() ?? ""}${s.slice(1)}`;
|
|
1733
|
+
}
|
|
1734
|
+
function hasAction(cmd) {
|
|
1735
|
+
return cmd._actionHandler != null;
|
|
1736
|
+
}
|
|
1737
|
+
function collectLeaves(cmd, path, out) {
|
|
1738
|
+
const children = cmd.commands;
|
|
1739
|
+
const isLeaf = hasAction(cmd) && children.length === 0;
|
|
1740
|
+
if (isLeaf) {
|
|
1741
|
+
const argv = path;
|
|
1742
|
+
const id = argv.join(" ");
|
|
1743
|
+
const description = cmd.description() || argv.join(" ");
|
|
1744
|
+
out.push({
|
|
1745
|
+
id,
|
|
1746
|
+
label: argv.map(capitalizeFirst).join(": "),
|
|
1747
|
+
argv,
|
|
1748
|
+
description,
|
|
1749
|
+
category: argv[0] ?? "",
|
|
1750
|
+
keywords: argv,
|
|
1751
|
+
destructive: isDestructive(argv)
|
|
1752
|
+
});
|
|
1753
|
+
return;
|
|
1754
|
+
}
|
|
1755
|
+
for (const child of children) {
|
|
1756
|
+
collectLeaves(child, [...path, child.name()], out);
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
function buildPaletteCommands(program2) {
|
|
1760
|
+
const out = [];
|
|
1761
|
+
collectLeaves(program2, [], out);
|
|
1762
|
+
return out;
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1494
1765
|
// src/bin.ts
|
|
1766
|
+
function buildPaletteCommandsForTui() {
|
|
1767
|
+
return buildPaletteCommands(createProgram());
|
|
1768
|
+
}
|
|
1495
1769
|
function actionGlobals(args) {
|
|
1496
1770
|
const last = args[args.length - 1];
|
|
1497
1771
|
return last?.optsWithGlobals() ?? {};
|
|
@@ -1596,7 +1870,7 @@ function createProgram() {
|
|
|
1596
1870
|
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(
|
|
1597
1871
|
"--upgrade",
|
|
1598
1872
|
"run scaffold migrations before re-emitting (re-run on an existing project)"
|
|
1599
|
-
).option("--force", "re-scaffold even if already initialized (bypasses the already-init no-op)").addOption(
|
|
1873
|
+
).option("--force", "re-scaffold even if already initialized (bypasses the already-init no-op)").option("--dry-run", "report planned writes without writing anything").option("--preview", "alias for --dry-run").addOption(
|
|
1600
1874
|
// S10: target host. Defaults to `'claude'` (the regression anchor). The
|
|
1601
1875
|
// chosen host is forwarded to scaffold() + skills emission via
|
|
1602
1876
|
// resolveAdapter. The choice list mirrors `SUPPORTED_HOSTS` so a new host
|
|
@@ -1611,19 +1885,21 @@ function createProgram() {
|
|
|
1611
1885
|
const upgrade = opts.upgrade === true;
|
|
1612
1886
|
const force = opts.force === true;
|
|
1613
1887
|
const host = parseHost(opts.host);
|
|
1888
|
+
const dryRun = opts.dryRun === true || opts.preview === true;
|
|
1614
1889
|
const result = await init(process.cwd(), {
|
|
1615
1890
|
transport,
|
|
1616
1891
|
url: opts.url,
|
|
1617
1892
|
...upgrade ? { upgrade } : {},
|
|
1618
1893
|
...force ? { force } : {},
|
|
1619
|
-
...host !== void 0 ? { host } : {}
|
|
1894
|
+
...host !== void 0 ? { host } : {},
|
|
1895
|
+
...dryRun ? { dryRun } : {}
|
|
1620
1896
|
});
|
|
1621
1897
|
if (cmd.optsWithGlobals().json === true && result !== void 0) {
|
|
1622
1898
|
json({ ok: true, data: result });
|
|
1623
1899
|
}
|
|
1624
1900
|
}
|
|
1625
1901
|
);
|
|
1626
|
-
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(
|
|
1902
|
+
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)").option("--dry-run", "report planned writes without writing anything").option("--preview", "alias for --dry-run").addOption(
|
|
1627
1903
|
new Option(
|
|
1628
1904
|
"--host <id>",
|
|
1629
1905
|
"target agentic CLI (default: claude) \u2014 drives host-side emission"
|
|
@@ -1633,12 +1909,14 @@ function createProgram() {
|
|
|
1633
1909
|
const transport = opts.transport === "streamable-http" ? "streamable-http" : "stdio";
|
|
1634
1910
|
const force = opts.force === true;
|
|
1635
1911
|
const host = parseHost(opts.host);
|
|
1636
|
-
const
|
|
1912
|
+
const dryRun = opts.dryRun === true || opts.preview === true;
|
|
1913
|
+
const { create } = await import("./create-UO3QTGL7.js");
|
|
1637
1914
|
const result = await create(dir, {
|
|
1638
1915
|
transport,
|
|
1639
1916
|
url: opts.url,
|
|
1640
1917
|
...force ? { force } : {},
|
|
1641
|
-
...host !== void 0 ? { host } : {}
|
|
1918
|
+
...host !== void 0 ? { host } : {},
|
|
1919
|
+
...dryRun ? { dryRun } : {}
|
|
1642
1920
|
});
|
|
1643
1921
|
if (cmd.optsWithGlobals().json === true && result !== void 0) {
|
|
1644
1922
|
json({ ok: true, data: result });
|
|
@@ -1653,7 +1931,7 @@ function createProgram() {
|
|
|
1653
1931
|
).option(
|
|
1654
1932
|
"--merge",
|
|
1655
1933
|
"three-way merge managed regions (default since 1.3.0; flag kept for compatibility)"
|
|
1656
|
-
).addOption(
|
|
1934
|
+
).option("--dry-run", "report planned writes without writing anything").option("--preview", "alias for --dry-run").addOption(
|
|
1657
1935
|
// Opt OUT of managed-region merge (restore strip-replace). Commander
|
|
1658
1936
|
// stores `--no-merge-regions` under `mergeRegions` (default true; flag →
|
|
1659
1937
|
// false). Bare `noir sync` keeps the merge default (true).
|
|
@@ -1671,16 +1949,18 @@ function createProgram() {
|
|
|
1671
1949
|
).choices(SUPPORTED_HOSTS)
|
|
1672
1950
|
).action(
|
|
1673
1951
|
async (opts, cmd) => {
|
|
1674
|
-
const { sync } = await import("./sync-
|
|
1952
|
+
const { sync } = await import("./sync-MZETGPOJ.js");
|
|
1675
1953
|
const host = parseHost(opts.host);
|
|
1676
1954
|
const force = opts.force === true;
|
|
1677
1955
|
const merge = opts.merge === true;
|
|
1678
1956
|
const noMergeRegions = opts.mergeRegions === false;
|
|
1679
|
-
const
|
|
1957
|
+
const dryRun = opts.dryRun === true || opts.preview === true;
|
|
1958
|
+
const result = host === void 0 && !force && !merge && !noMergeRegions && !dryRun ? await sync(process.cwd()) : await sync(process.cwd(), {
|
|
1680
1959
|
...host !== void 0 ? { host } : {},
|
|
1681
1960
|
...force ? { force } : {},
|
|
1682
1961
|
...merge ? { merge } : {},
|
|
1683
|
-
...noMergeRegions ? { mergeManagedRegions: false } : {}
|
|
1962
|
+
...noMergeRegions ? { mergeManagedRegions: false } : {},
|
|
1963
|
+
...dryRun ? { dryRun } : {}
|
|
1684
1964
|
});
|
|
1685
1965
|
if (cmd.optsWithGlobals().json === true) {
|
|
1686
1966
|
json({ ok: true, data: result });
|
|
@@ -1695,11 +1975,18 @@ function createProgram() {
|
|
|
1695
1975
|
throw new NoirCliError(EXIT.USAGE, "Usage: noir mcp serve [--stdio]");
|
|
1696
1976
|
});
|
|
1697
1977
|
const daemonGrp = program2.command("daemon").description("control the Noir daemon");
|
|
1698
|
-
daemonGrp.command("start").description("start the Noir daemon (foreground
|
|
1978
|
+
daemonGrp.command("start").description("start the Noir daemon (foreground, or background with --detach)").option("--detach", "run the daemon in the background and exit").addOption(
|
|
1979
|
+
new Option("--_detached-child", "reserved: detached daemon child (internal)").hideHelp()
|
|
1980
|
+
).action(async (...args) => {
|
|
1699
1981
|
const cmd = trailingCmd(args);
|
|
1700
1982
|
const g = cmd.optsWithGlobals();
|
|
1701
1983
|
const detach = g.detach === true;
|
|
1702
|
-
|
|
1984
|
+
const detachChild = g._detachedChild === true;
|
|
1985
|
+
await daemonStart({
|
|
1986
|
+
...toCliOptions(g),
|
|
1987
|
+
...detach ? { detach } : {},
|
|
1988
|
+
...detachChild ? { detachChild } : {}
|
|
1989
|
+
});
|
|
1703
1990
|
});
|
|
1704
1991
|
daemonGrp.command("stop").description("stop the Noir daemon").action(async (...args) => {
|
|
1705
1992
|
await daemonStop(toCliOptions(trailingCmd(args).optsWithGlobals()));
|
|
@@ -1741,7 +2028,7 @@ function createProgram() {
|
|
|
1741
2028
|
"path to index (repeatable)",
|
|
1742
2029
|
(val, acc) => [...acc, val],
|
|
1743
2030
|
[]
|
|
1744
|
-
).option("--force", "
|
|
2031
|
+
).option("--force", "force a full reindex (drop all chunks+vectors, re-index from scratch)").action(async (...args) => {
|
|
1745
2032
|
const cmd = trailingCmd(args);
|
|
1746
2033
|
const g = cmd.optsWithGlobals();
|
|
1747
2034
|
const rawPaths = Array.isArray(g.path) ? g.path.filter((p) => typeof p === "string") : [];
|
|
@@ -1963,6 +2250,7 @@ export {
|
|
|
1963
2250
|
DEPRECATIONS,
|
|
1964
2251
|
EXIT,
|
|
1965
2252
|
NoirCliError,
|
|
2253
|
+
buildPaletteCommandsForTui,
|
|
1966
2254
|
createProgram,
|
|
1967
2255
|
emitDeprecationHintsFor,
|
|
1968
2256
|
fail,
|