@noir-ai/cli 1.12.0-beta.1 → 1.13.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.js +731 -112
- package/dist/bin.js.map +1 -1
- package/dist/index.js +736 -117
- package/dist/index.js.map +1 -1
- package/dist/tui/{chunk-J77RHAWG.js → chunk-KMLSJRYV.js} +705 -97
- package/dist/tui/chunk-KMLSJRYV.js.map +1 -0
- package/dist/tui/{create-BTYLBVV6.js → create-HYFBTBLR.js} +2 -2
- package/dist/tui/index.js +1 -1
- package/dist/tui/{sync-LRHPJ5VV.js → sync-QDOJ7INZ.js} +2 -2
- package/package.json +11 -11
- package/dist/tui/chunk-J77RHAWG.js.map +0 -1
- /package/dist/tui/{create-BTYLBVV6.js.map → create-HYFBTBLR.js.map} +0 -0
- /package/dist/tui/{sync-LRHPJ5VV.js.map → sync-QDOJ7INZ.js.map} +0 -0
|
@@ -28,16 +28,22 @@ import {
|
|
|
28
28
|
} from "./chunk-ESZTOQEC.js";
|
|
29
29
|
|
|
30
30
|
// src/commands/status.ts
|
|
31
|
-
import { loadProjectInfo as
|
|
31
|
+
import { loadProjectInfo as loadProjectInfo13, NOIR_VERSION as NOIR_VERSION5 } from "@noir-ai/core";
|
|
32
32
|
|
|
33
33
|
// src/daemon-client.ts
|
|
34
34
|
import { Client, StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
|
|
35
35
|
import { ContextEngine, createEmbedFn, resolveEmbedderConfig } from "@noir-ai/context";
|
|
36
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
loadProjectInfo as loadProjectInfo12,
|
|
38
|
+
NOIR_VERSION as NOIR_VERSION4,
|
|
39
|
+
readWorkspaceMarker as readWorkspaceMarker2,
|
|
40
|
+
workspaceStoreDbPath
|
|
41
|
+
} from "@noir-ai/core";
|
|
37
42
|
import {
|
|
38
43
|
ensureDaemonRunning as ensureDaemonRunning3,
|
|
39
|
-
pidAlive as
|
|
44
|
+
pidAlive as pidAlive4,
|
|
40
45
|
readDaemonRecord as readDaemonRecord3,
|
|
46
|
+
readWorkspaceDaemonRecord as readWorkspaceDaemonRecord2,
|
|
41
47
|
resolveGateConfig
|
|
42
48
|
} from "@noir-ai/daemon";
|
|
43
49
|
import { createMemoryEngine } from "@noir-ai/memory";
|
|
@@ -217,14 +223,319 @@ function renderStatus(data, opts) {
|
|
|
217
223
|
}
|
|
218
224
|
|
|
219
225
|
// src/commands/daemon.ts
|
|
220
|
-
import { loadProjectInfo } from "@noir-ai/core";
|
|
226
|
+
import { loadProjectInfo as loadProjectInfo2 } from "@noir-ai/core";
|
|
221
227
|
import {
|
|
222
228
|
clearDaemonRecord,
|
|
223
229
|
ensureDaemonRunning,
|
|
224
|
-
pidAlive,
|
|
230
|
+
pidAlive as pidAlive2,
|
|
225
231
|
readDaemonRecord,
|
|
226
232
|
spawnDetachedDaemon
|
|
227
233
|
} from "@noir-ai/daemon";
|
|
234
|
+
|
|
235
|
+
// src/commands/workspace.ts
|
|
236
|
+
import { readdirSync } from "fs";
|
|
237
|
+
import {
|
|
238
|
+
clearWorkspaceMarker,
|
|
239
|
+
ensureWorkspaceRegistry,
|
|
240
|
+
isValidWorkspaceName,
|
|
241
|
+
isWorkspaceMember,
|
|
242
|
+
loadProjectInfo,
|
|
243
|
+
readWorkspaceMarker,
|
|
244
|
+
readWorkspaceRegistry,
|
|
245
|
+
removeWorkspaceMember,
|
|
246
|
+
upsertWorkspaceMember,
|
|
247
|
+
workspaceHomeDir,
|
|
248
|
+
writeWorkspaceMarker
|
|
249
|
+
} from "@noir-ai/core";
|
|
250
|
+
import {
|
|
251
|
+
clearWorkspaceDaemonRecord,
|
|
252
|
+
ensureWorkspaceDaemonRunning,
|
|
253
|
+
pidAlive,
|
|
254
|
+
readWorkspaceDaemonRecord,
|
|
255
|
+
spawnDetachedWorkspaceDaemon
|
|
256
|
+
} from "@noir-ai/daemon";
|
|
257
|
+
|
|
258
|
+
// src/workspace-mcp.ts
|
|
259
|
+
import { existsSync, readFileSync } from "fs";
|
|
260
|
+
import { join } from "path";
|
|
261
|
+
import { resolveAdapter } from "@noir-ai/adapters";
|
|
262
|
+
import { atomicWriteFile, resolveNoirCommand } from "@noir-ai/core";
|
|
263
|
+
function mcpPathFor(root, host) {
|
|
264
|
+
return resolveAdapter(host).mcpConfigPath?.({ root }) ?? join(root, ".mcp.json");
|
|
265
|
+
}
|
|
266
|
+
function readJson(path) {
|
|
267
|
+
if (!existsSync(path)) return null;
|
|
268
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
269
|
+
}
|
|
270
|
+
function loadExisting(path, opts) {
|
|
271
|
+
let existing;
|
|
272
|
+
try {
|
|
273
|
+
existing = readJson(path);
|
|
274
|
+
} catch {
|
|
275
|
+
if (opts.force !== true) {
|
|
276
|
+
fail(
|
|
277
|
+
EXIT.ERROR,
|
|
278
|
+
`Refusing to rewrite ${path}: it is not valid JSON (add --force to overwrite).`,
|
|
279
|
+
opts
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
existing = null;
|
|
283
|
+
}
|
|
284
|
+
if (existing !== null && !isNoirEmitted(existing.mcpServers) && opts.force !== true) {
|
|
285
|
+
fail(
|
|
286
|
+
EXIT.ERROR,
|
|
287
|
+
`Refusing to rewrite ${path}: it does not look like a Noir-emitted config (add --force to overwrite).`,
|
|
288
|
+
opts
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
return existing;
|
|
292
|
+
}
|
|
293
|
+
function isNoirEmitted(mcpServers) {
|
|
294
|
+
const noir = mcpServers?.noir;
|
|
295
|
+
if (!noir) return false;
|
|
296
|
+
if (typeof noir.command === "string") return true;
|
|
297
|
+
return noir.type === "http";
|
|
298
|
+
}
|
|
299
|
+
function writeWorkspaceHttpEntry(root, host, url, projectId, opts) {
|
|
300
|
+
const path = mcpPathFor(root, host);
|
|
301
|
+
const existing = loadExisting(path, opts);
|
|
302
|
+
const next = {
|
|
303
|
+
...existing ?? {},
|
|
304
|
+
mcpServers: {
|
|
305
|
+
...existing?.mcpServers ?? {},
|
|
306
|
+
noir: { type: "http", url: `${url}?p=${projectId}` }
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
atomicWriteFile(path, `${JSON.stringify(next, null, 2)}
|
|
310
|
+
`);
|
|
311
|
+
}
|
|
312
|
+
function writeStdioEntry(root, host, opts) {
|
|
313
|
+
const path = mcpPathFor(root, host);
|
|
314
|
+
const existing = loadExisting(path, opts);
|
|
315
|
+
const noir = { command: resolveNoirCommand(), args: ["mcp", "serve", "--stdio"] };
|
|
316
|
+
const next = { ...existing ?? {}, mcpServers: { ...existing?.mcpServers ?? {}, noir } };
|
|
317
|
+
atomicWriteFile(path, `${JSON.stringify(next, null, 2)}
|
|
318
|
+
`);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// src/commands/workspace.ts
|
|
322
|
+
function loadProjectOrFail(opts) {
|
|
323
|
+
try {
|
|
324
|
+
return loadProjectInfo(process.cwd());
|
|
325
|
+
} catch {
|
|
326
|
+
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
function finishJoin(project, name, url, opts) {
|
|
330
|
+
writeWorkspaceMarker(project.root, name);
|
|
331
|
+
writeWorkspaceHttpEntry(project.root, project.config.host, url, project.id, opts);
|
|
332
|
+
}
|
|
333
|
+
async function daemonStartWorkspace(opts) {
|
|
334
|
+
const project = loadProjectOrFail(opts);
|
|
335
|
+
if (!isValidWorkspaceName(opts.name)) {
|
|
336
|
+
fail(
|
|
337
|
+
EXIT.USAGE,
|
|
338
|
+
`invalid workspace name ${JSON.stringify(opts.name)} (lowercase letters, digits, and dashes).`,
|
|
339
|
+
opts
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
upsertWorkspaceMember(ensureWorkspaceRegistry(opts.name), {
|
|
343
|
+
projectId: project.id,
|
|
344
|
+
root: project.root,
|
|
345
|
+
joinedAt: Date.now()
|
|
346
|
+
});
|
|
347
|
+
if (opts.detachChild === true) {
|
|
348
|
+
const ensured2 = await ensureWorkspaceDaemonRunning({
|
|
349
|
+
name: opts.name,
|
|
350
|
+
project,
|
|
351
|
+
idleTimeoutSec: project.config.workspace.idleTimeoutSec
|
|
352
|
+
});
|
|
353
|
+
if (opts.json === true) {
|
|
354
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: { mode: "detached" } })}
|
|
355
|
+
`);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
info("noir workspace daemon: detached foreground. Ctrl+C to stop.", opts);
|
|
359
|
+
log(`workspace daemon listening at ${ensured2.url}`, opts);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (opts.detach === true) {
|
|
363
|
+
const spawned = await spawnDetachedWorkspaceDaemon({ name: opts.name, project });
|
|
364
|
+
finishJoin(project, opts.name, `http://127.0.0.1:${spawned.port}/mcp`, opts);
|
|
365
|
+
if (opts.json === true) {
|
|
366
|
+
process.stdout.write(
|
|
367
|
+
`${JSON.stringify({ ok: true, data: { mode: "detached", pid: spawned.pid, port: spawned.port } })}
|
|
368
|
+
`
|
|
369
|
+
);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
info(
|
|
373
|
+
`workspace "${opts.name}" daemon started (pid ${spawned.pid}, port ${spawned.port}).`,
|
|
374
|
+
opts
|
|
375
|
+
);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const ensured = await ensureWorkspaceDaemonRunning({
|
|
379
|
+
name: opts.name,
|
|
380
|
+
project,
|
|
381
|
+
idleTimeoutSec: project.config.workspace.idleTimeoutSec
|
|
382
|
+
});
|
|
383
|
+
finishJoin(project, opts.name, ensured.url, opts);
|
|
384
|
+
if (opts.json === true) {
|
|
385
|
+
process.stdout.write(
|
|
386
|
+
`${JSON.stringify({ ok: true, data: { url: ensured.url, port: ensured.port, reused: !ensured.started } })}
|
|
387
|
+
`
|
|
388
|
+
);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
info("noir workspace daemon: foreground mode. Ctrl+C to stop.", opts);
|
|
392
|
+
log(`workspace daemon listening at ${ensured.url}`, opts);
|
|
393
|
+
}
|
|
394
|
+
async function daemonJoin(opts) {
|
|
395
|
+
const project = loadProjectOrFail(opts);
|
|
396
|
+
if (!isValidWorkspaceName(opts.name)) {
|
|
397
|
+
fail(EXIT.USAGE, `invalid workspace name ${JSON.stringify(opts.name)}.`, opts);
|
|
398
|
+
}
|
|
399
|
+
const reg = readWorkspaceRegistry(opts.name);
|
|
400
|
+
if (!reg) {
|
|
401
|
+
fail(
|
|
402
|
+
EXIT.ERROR,
|
|
403
|
+
`workspace ${JSON.stringify(opts.name)} not found \u2014 start it from a member repo with \`noir daemon start --workspace ${opts.name}\`.`,
|
|
404
|
+
opts
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
const ensured = await ensureWorkspaceDaemonRunning({
|
|
408
|
+
name: opts.name,
|
|
409
|
+
project,
|
|
410
|
+
idleTimeoutSec: project.config.workspace.idleTimeoutSec
|
|
411
|
+
});
|
|
412
|
+
upsertWorkspaceMember(reg, { projectId: project.id, root: project.root, joinedAt: Date.now() });
|
|
413
|
+
finishJoin(project, opts.name, ensured.url, opts);
|
|
414
|
+
if (opts.json === true) {
|
|
415
|
+
process.stdout.write(
|
|
416
|
+
`${JSON.stringify({ ok: true, data: { joined: true, url: ensured.url, port: ensured.port } })}
|
|
417
|
+
`
|
|
418
|
+
);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
info(`joined workspace ${JSON.stringify(opts.name)} (${ensured.url}).`, opts);
|
|
422
|
+
}
|
|
423
|
+
async function workspaceList(opts) {
|
|
424
|
+
let names = [];
|
|
425
|
+
try {
|
|
426
|
+
names = readdirSync(workspaceHomeDir(), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
427
|
+
} catch {
|
|
428
|
+
names = [];
|
|
429
|
+
}
|
|
430
|
+
const rows = names.map((n) => {
|
|
431
|
+
const reg = readWorkspaceRegistry(n);
|
|
432
|
+
return {
|
|
433
|
+
name: n,
|
|
434
|
+
members: reg?.members.length ?? 0,
|
|
435
|
+
// A record file whose pid is dead (daemon killed without cleanup) must not
|
|
436
|
+
// read as "running" — mirror the pidAlive liveness `workspace status` uses.
|
|
437
|
+
running: (() => {
|
|
438
|
+
const rec = readWorkspaceDaemonRecord(n);
|
|
439
|
+
return rec !== null && pidAlive(rec.pid);
|
|
440
|
+
})()
|
|
441
|
+
};
|
|
442
|
+
});
|
|
443
|
+
if (opts.json === true) {
|
|
444
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: { workspaces: rows } })}
|
|
445
|
+
`);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
if (rows.length === 0) {
|
|
449
|
+
info("No workspaces yet.", opts);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
for (const r of rows) {
|
|
453
|
+
log(`${r.name} \u2014 ${r.members} member(s)${r.running ? ", running" : ""}`, opts);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
async function workspaceStatus(opts) {
|
|
457
|
+
const name = opts.name ?? readWorkspaceMarker(process.cwd()) ?? void 0;
|
|
458
|
+
if (name === void 0) {
|
|
459
|
+
fail(EXIT.USAGE, "workspace status requires a name (or run from a joined repo).", opts);
|
|
460
|
+
}
|
|
461
|
+
if (!isValidWorkspaceName(name)) {
|
|
462
|
+
fail(EXIT.USAGE, `invalid workspace name ${JSON.stringify(name)}.`, opts);
|
|
463
|
+
}
|
|
464
|
+
const reg = readWorkspaceRegistry(name);
|
|
465
|
+
const rec = readWorkspaceDaemonRecord(name);
|
|
466
|
+
const running = rec !== null && pidAlive(rec.pid);
|
|
467
|
+
const data = {
|
|
468
|
+
name,
|
|
469
|
+
members: reg?.members.map((m) => m.projectId) ?? [],
|
|
470
|
+
running,
|
|
471
|
+
...rec ? { pid: rec.pid, port: rec.port } : {}
|
|
472
|
+
};
|
|
473
|
+
if (opts.json === true) {
|
|
474
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data })}
|
|
475
|
+
`);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
log(
|
|
479
|
+
`workspace ${name}: ${running ? `running (pid ${rec?.pid}, port ${rec?.port})` : "not running"}`,
|
|
480
|
+
opts
|
|
481
|
+
);
|
|
482
|
+
for (const m of data.members) log(` member: ${m}`, opts);
|
|
483
|
+
}
|
|
484
|
+
async function workspaceLeave(opts) {
|
|
485
|
+
const project = loadProjectOrFail(opts);
|
|
486
|
+
const name = readWorkspaceMarker(project.root);
|
|
487
|
+
if (name === null) {
|
|
488
|
+
fail(EXIT.USAGE, "this repo is not joined to a workspace.", opts);
|
|
489
|
+
}
|
|
490
|
+
writeStdioEntry(project.root, project.config.host, opts);
|
|
491
|
+
const reg = readWorkspaceRegistry(name);
|
|
492
|
+
if (reg && isWorkspaceMember(reg, project.id)) {
|
|
493
|
+
removeWorkspaceMember(reg, project.id);
|
|
494
|
+
}
|
|
495
|
+
clearWorkspaceMarker(project.root);
|
|
496
|
+
if (opts.json === true) {
|
|
497
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: { left: name } })}
|
|
498
|
+
`);
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
info(`left workspace ${JSON.stringify(name)}.`, opts);
|
|
502
|
+
}
|
|
503
|
+
async function workspaceStop(opts) {
|
|
504
|
+
const name = opts.name ?? readWorkspaceMarker(process.cwd()) ?? void 0;
|
|
505
|
+
if (name === void 0) {
|
|
506
|
+
fail(EXIT.USAGE, "workspace stop requires a name (or run from a joined repo).", opts);
|
|
507
|
+
}
|
|
508
|
+
if (!isValidWorkspaceName(name)) {
|
|
509
|
+
fail(EXIT.USAGE, `invalid workspace name ${JSON.stringify(name)}.`, opts);
|
|
510
|
+
}
|
|
511
|
+
const rec = readWorkspaceDaemonRecord(name);
|
|
512
|
+
if (rec === null || !pidAlive(rec.pid)) {
|
|
513
|
+
if (opts.json === true) {
|
|
514
|
+
process.stdout.write(
|
|
515
|
+
`${JSON.stringify({ ok: true, data: { running: false, stopped: false } })}
|
|
516
|
+
`
|
|
517
|
+
);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
info("workspace daemon is not running.", opts);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
try {
|
|
524
|
+
process.kill(rec.pid, "SIGTERM");
|
|
525
|
+
} catch {
|
|
526
|
+
}
|
|
527
|
+
clearWorkspaceDaemonRecord(name);
|
|
528
|
+
if (opts.json === true) {
|
|
529
|
+
process.stdout.write(
|
|
530
|
+
`${JSON.stringify({ ok: true, data: { running: false, stopped: true, pid: rec.pid } })}
|
|
531
|
+
`
|
|
532
|
+
);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
info(`stopped workspace daemon (pid ${rec.pid}).`, opts);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// src/commands/daemon.ts
|
|
228
539
|
var MODE = "foreground";
|
|
229
540
|
function formatUptime(sec) {
|
|
230
541
|
if (!Number.isFinite(sec) || sec < 0) return "unknown";
|
|
@@ -234,7 +545,7 @@ function formatUptime(sec) {
|
|
|
234
545
|
}
|
|
235
546
|
function resolveCallerProjectId() {
|
|
236
547
|
try {
|
|
237
|
-
return
|
|
548
|
+
return loadProjectInfo2(process.cwd()).id;
|
|
238
549
|
} catch {
|
|
239
550
|
return void 0;
|
|
240
551
|
}
|
|
@@ -255,9 +566,18 @@ async function isHealthy(port, expectedPid, expectedProjectId) {
|
|
|
255
566
|
}
|
|
256
567
|
}
|
|
257
568
|
async function daemonStart(opts) {
|
|
569
|
+
if (opts.workspace !== void 0) {
|
|
570
|
+
await daemonStartWorkspace({
|
|
571
|
+
...opts,
|
|
572
|
+
name: opts.workspace,
|
|
573
|
+
...opts.detach === true ? { detach: true } : {},
|
|
574
|
+
...opts.detachChild === true ? { detachChild: true } : {}
|
|
575
|
+
});
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
258
578
|
let project;
|
|
259
579
|
try {
|
|
260
|
-
project =
|
|
580
|
+
project = loadProjectInfo2(process.cwd());
|
|
261
581
|
} catch {
|
|
262
582
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
263
583
|
}
|
|
@@ -288,7 +608,7 @@ async function daemonStart(opts) {
|
|
|
288
608
|
}
|
|
289
609
|
if (opts.detach === true) {
|
|
290
610
|
const existing = readDaemonRecord();
|
|
291
|
-
if (existing &&
|
|
611
|
+
if (existing && pidAlive2(existing.pid) && await isHealthy(existing.port, existing.pid, project.id)) {
|
|
292
612
|
if (opts.json === true) {
|
|
293
613
|
process.stdout.write(
|
|
294
614
|
`${JSON.stringify({ ok: true, data: { mode: "detached", reused: true } })}
|
|
@@ -387,7 +707,7 @@ async function daemonStop(opts) {
|
|
|
387
707
|
let errMsg2;
|
|
388
708
|
const callerProject = resolveCallerProjectId();
|
|
389
709
|
const wrongProject = rec.projectId !== void 0 && callerProject !== void 0 && rec.projectId !== callerProject;
|
|
390
|
-
const healthy = !wrongProject &&
|
|
710
|
+
const healthy = !wrongProject && pidAlive2(rec.pid) && await isHealthy(rec.port, rec.pid, rec.projectId === void 0 ? void 0 : callerProject);
|
|
391
711
|
if (healthy) {
|
|
392
712
|
try {
|
|
393
713
|
process.kill(rec.pid, "SIGTERM");
|
|
@@ -433,7 +753,7 @@ async function daemonStatus(opts) {
|
|
|
433
753
|
opts
|
|
434
754
|
);
|
|
435
755
|
}
|
|
436
|
-
if (!
|
|
756
|
+
if (!pidAlive2(rec.pid)) {
|
|
437
757
|
clearDaemonRecord();
|
|
438
758
|
fail(EXIT.DAEMON_DOWN, "Noir daemon is not running (stale record removed).", opts);
|
|
439
759
|
}
|
|
@@ -488,21 +808,21 @@ async function daemonRestart(opts) {
|
|
|
488
808
|
|
|
489
809
|
// src/commands/doctor.ts
|
|
490
810
|
import { spawnSync } from "child_process";
|
|
491
|
-
import { existsSync, readdirSync, readFileSync, statSync } from "fs";
|
|
492
|
-
import { dirname, join, relative } from "path";
|
|
811
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync } from "fs";
|
|
812
|
+
import { dirname, join as join2, relative } from "path";
|
|
493
813
|
import { fileURLToPath } from "url";
|
|
494
|
-
import { resolveAdapter } from "@noir-ai/adapters";
|
|
814
|
+
import { resolveAdapter as resolveAdapter2 } from "@noir-ai/adapters";
|
|
495
815
|
import {
|
|
496
816
|
detectActiveMethod,
|
|
497
817
|
latestVersionFromCache,
|
|
498
|
-
loadProjectInfo as
|
|
818
|
+
loadProjectInfo as loadProjectInfo3,
|
|
499
819
|
NOIR_VERSION,
|
|
500
820
|
paths,
|
|
501
821
|
readInstallRecord,
|
|
502
822
|
readUpdateCache
|
|
503
823
|
} from "@noir-ai/core";
|
|
504
824
|
import { CURRENT_SCAFFOLD_VERSION, readScaffoldVersion } from "@noir-ai/create";
|
|
505
|
-
import { pidAlive as
|
|
825
|
+
import { pidAlive as pidAlive3, readDaemonRecord as readDaemonRecord2 } from "@noir-ai/daemon";
|
|
506
826
|
import { resolveModelConfig } from "@noir-ai/model";
|
|
507
827
|
async function probeOnnx() {
|
|
508
828
|
try {
|
|
@@ -539,7 +859,7 @@ async function checkRuntime(checks) {
|
|
|
539
859
|
}
|
|
540
860
|
function checkConfig(root) {
|
|
541
861
|
const configPath = paths.config(root);
|
|
542
|
-
if (!
|
|
862
|
+
if (!existsSync2(configPath)) {
|
|
543
863
|
return {
|
|
544
864
|
project: void 0,
|
|
545
865
|
result: {
|
|
@@ -550,7 +870,7 @@ function checkConfig(root) {
|
|
|
550
870
|
};
|
|
551
871
|
}
|
|
552
872
|
try {
|
|
553
|
-
const project =
|
|
873
|
+
const project = loadProjectInfo3(root);
|
|
554
874
|
return {
|
|
555
875
|
project,
|
|
556
876
|
result: {
|
|
@@ -576,7 +896,7 @@ async function checkDaemon(checks) {
|
|
|
576
896
|
});
|
|
577
897
|
return;
|
|
578
898
|
}
|
|
579
|
-
if (!
|
|
899
|
+
if (!pidAlive3(rec.pid)) {
|
|
580
900
|
checks.push({
|
|
581
901
|
name: "daemon",
|
|
582
902
|
status: "warn",
|
|
@@ -631,7 +951,7 @@ async function checkStore(checks, project, root) {
|
|
|
631
951
|
return;
|
|
632
952
|
}
|
|
633
953
|
const dbPath = paths.storeDb(root, project.id);
|
|
634
|
-
if (!
|
|
954
|
+
if (!existsSync2(dbPath)) {
|
|
635
955
|
checks.push({
|
|
636
956
|
name: "store",
|
|
637
957
|
status: "warn",
|
|
@@ -660,7 +980,7 @@ async function checkStore(checks, project, root) {
|
|
|
660
980
|
}
|
|
661
981
|
}
|
|
662
982
|
function checkNoirEnv(checks, root) {
|
|
663
|
-
const path =
|
|
983
|
+
const path = join2(paths.noirDir(root), ".env");
|
|
664
984
|
let st;
|
|
665
985
|
try {
|
|
666
986
|
st = statSync(path);
|
|
@@ -770,7 +1090,7 @@ function checkRulesMdBudget(checks, root, project) {
|
|
|
770
1090
|
return null;
|
|
771
1091
|
}
|
|
772
1092
|
const rulesPath = paths.rulesMd(root);
|
|
773
|
-
if (!
|
|
1093
|
+
if (!existsSync2(rulesPath)) {
|
|
774
1094
|
checks.push({
|
|
775
1095
|
name: "rules budget",
|
|
776
1096
|
status: "ok",
|
|
@@ -780,7 +1100,7 @@ function checkRulesMdBudget(checks, root, project) {
|
|
|
780
1100
|
}
|
|
781
1101
|
let content;
|
|
782
1102
|
try {
|
|
783
|
-
content =
|
|
1103
|
+
content = readFileSync2(rulesPath, "utf8");
|
|
784
1104
|
} catch (e) {
|
|
785
1105
|
checks.push({
|
|
786
1106
|
name: "rules budget",
|
|
@@ -824,7 +1144,7 @@ function checkHostArtifacts(checks, root, project) {
|
|
|
824
1144
|
return null;
|
|
825
1145
|
}
|
|
826
1146
|
const host = project.config.host;
|
|
827
|
-
const adapter =
|
|
1147
|
+
const adapter = resolveAdapter2(host);
|
|
828
1148
|
const ectx = { root };
|
|
829
1149
|
const expected = [];
|
|
830
1150
|
const emitsAgentsMd = host === "agents-md" || host === "cursor" || host === "opencode";
|
|
@@ -834,7 +1154,7 @@ function checkHostArtifacts(checks, root, project) {
|
|
|
834
1154
|
if (host === "claude") expected.push("CLAUDE.md");
|
|
835
1155
|
else if (host === "gemini") expected.push("GEMINI.md");
|
|
836
1156
|
expected.push(relOr(adapter.mcpConfigPath?.(ectx) ?? ".mcp.json", root));
|
|
837
|
-
const missing = expected.filter((p) => !
|
|
1157
|
+
const missing = expected.filter((p) => !existsSync2(joinRoot(root, p)));
|
|
838
1158
|
const status2 = missing.length === 0 ? "ok" : "warn";
|
|
839
1159
|
const detail = missing.length === 0 ? `host=${host} \xB7 all ${expected.length} primary artifacts present` : `host=${host} \xB7 missing ${missing.join(", ")} (run \`noir sync\` to restore)`;
|
|
840
1160
|
checks.push({ name: "host artifacts", status: status2, detail });
|
|
@@ -851,7 +1171,7 @@ function checkNestedNoir(checks, root) {
|
|
|
851
1171
|
".noir/.npmignore",
|
|
852
1172
|
".noir/.prettierignore"
|
|
853
1173
|
];
|
|
854
|
-
const found = candidates.filter((rel) =>
|
|
1174
|
+
const found = candidates.filter((rel) => existsSync2(join2(root, rel)));
|
|
855
1175
|
const detected = found.length > 0;
|
|
856
1176
|
checks.push({
|
|
857
1177
|
name: "nested .noir",
|
|
@@ -864,11 +1184,11 @@ function collectDedupCandidates(root) {
|
|
|
864
1184
|
const rels = ["CLAUDE.md", "AGENTS.md", "GEMINI.md", ".noir/rules/RULES.md"];
|
|
865
1185
|
const out = [];
|
|
866
1186
|
for (const rel of rels) {
|
|
867
|
-
const abs =
|
|
868
|
-
if (!
|
|
1187
|
+
const abs = join2(root, rel);
|
|
1188
|
+
if (!existsSync2(abs)) continue;
|
|
869
1189
|
let text;
|
|
870
1190
|
try {
|
|
871
|
-
text =
|
|
1191
|
+
text = readFileSync2(abs, "utf8");
|
|
872
1192
|
} catch {
|
|
873
1193
|
continue;
|
|
874
1194
|
}
|
|
@@ -925,13 +1245,13 @@ async function checkSemanticDupDoctor(checks, root, project, opts = {}) {
|
|
|
925
1245
|
function resolveWorkspacePackagesDir() {
|
|
926
1246
|
let here = dirname(fileURLToPath(import.meta.url));
|
|
927
1247
|
for (let i = 0; i < 8; i++) {
|
|
928
|
-
const pkgPath =
|
|
929
|
-
if (
|
|
1248
|
+
const pkgPath = join2(here, "package.json");
|
|
1249
|
+
if (existsSync2(pkgPath)) {
|
|
930
1250
|
try {
|
|
931
|
-
const pkg = JSON.parse(
|
|
1251
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
|
|
932
1252
|
if (pkg.name === "@noir-ai/cli") {
|
|
933
1253
|
const packagesDir = dirname(here);
|
|
934
|
-
if (
|
|
1254
|
+
if (existsSync2(join2(dirname(packagesDir), "pnpm-workspace.yaml"))) {
|
|
935
1255
|
return packagesDir;
|
|
936
1256
|
}
|
|
937
1257
|
return null;
|
|
@@ -977,7 +1297,7 @@ function checkPublish(checks, packagesDir, opts = {}) {
|
|
|
977
1297
|
}
|
|
978
1298
|
let entries = [];
|
|
979
1299
|
try {
|
|
980
|
-
entries =
|
|
1300
|
+
entries = readdirSync2(packagesDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
981
1301
|
} catch (e) {
|
|
982
1302
|
checks.push({
|
|
983
1303
|
name: "publish",
|
|
@@ -990,11 +1310,11 @@ function checkPublish(checks, packagesDir, opts = {}) {
|
|
|
990
1310
|
let checked = 0;
|
|
991
1311
|
let cliDir = null;
|
|
992
1312
|
for (const name of entries) {
|
|
993
|
-
const pkgPath =
|
|
994
|
-
if (!
|
|
1313
|
+
const pkgPath = join2(packagesDir, name, "package.json");
|
|
1314
|
+
if (!existsSync2(pkgPath)) continue;
|
|
995
1315
|
let pkg;
|
|
996
1316
|
try {
|
|
997
|
-
pkg = JSON.parse(
|
|
1317
|
+
pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
|
|
998
1318
|
} catch (e) {
|
|
999
1319
|
issues.push(`${name}/package.json: unparseable (${msg(e)})`);
|
|
1000
1320
|
checked++;
|
|
@@ -1016,7 +1336,7 @@ function checkPublish(checks, packagesDir, opts = {}) {
|
|
|
1016
1336
|
if (pkg.bin == null || typeof pkg.bin === "object" && Object.keys(pkg.bin).length === 0) {
|
|
1017
1337
|
issues.push(`${name}/package.json: bin missing (cli package must declare a bin)`);
|
|
1018
1338
|
}
|
|
1019
|
-
cliDir =
|
|
1339
|
+
cliDir = join2(packagesDir, name);
|
|
1020
1340
|
}
|
|
1021
1341
|
}
|
|
1022
1342
|
let packNote = "";
|
|
@@ -1142,18 +1462,18 @@ async function doctor(opts = {}) {
|
|
|
1142
1462
|
|
|
1143
1463
|
// src/commands/handoff.ts
|
|
1144
1464
|
import { mkdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
1145
|
-
import { join as
|
|
1465
|
+
import { join as join3 } from "path";
|
|
1146
1466
|
import {
|
|
1147
1467
|
defaultHandoffBlock,
|
|
1148
1468
|
hostLaunchDirective,
|
|
1149
|
-
resolveAdapter as
|
|
1469
|
+
resolveAdapter as resolveAdapter3
|
|
1150
1470
|
} from "@noir-ai/adapters";
|
|
1151
|
-
import { loadProjectInfo as
|
|
1471
|
+
import { loadProjectInfo as loadProjectInfo5, NOIR_DIR, resolveArtifactPath } from "@noir-ai/core";
|
|
1152
1472
|
|
|
1153
1473
|
// src/commands/task.ts
|
|
1154
1474
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1155
1475
|
import { writeFileSync } from "fs";
|
|
1156
|
-
import { loadProjectInfo as
|
|
1476
|
+
import { loadProjectInfo as loadProjectInfo4, nextArtifactSequence, sha256Hex } from "@noir-ai/core";
|
|
1157
1477
|
import {
|
|
1158
1478
|
PHASES,
|
|
1159
1479
|
TASK_CLASSES,
|
|
@@ -1177,7 +1497,7 @@ function skillFor(phase) {
|
|
|
1177
1497
|
}
|
|
1178
1498
|
function writeDoneArtifacts(opts, taskId) {
|
|
1179
1499
|
try {
|
|
1180
|
-
const project =
|
|
1500
|
+
const project = loadProjectInfo4(process.cwd());
|
|
1181
1501
|
const conflict = { conflictPolicy: "preserve", interactive: false };
|
|
1182
1502
|
writeChangelogStub(project.root, `- ${taskId}: completed (verify gate passed)`, conflict);
|
|
1183
1503
|
const nextN = nextArtifactSequence(project.root, "adr");
|
|
@@ -1501,7 +1821,7 @@ function runCheck(cmd) {
|
|
|
1501
1821
|
async function taskVerify(opts) {
|
|
1502
1822
|
let project;
|
|
1503
1823
|
try {
|
|
1504
|
-
project =
|
|
1824
|
+
project = loadProjectInfo4(process.cwd());
|
|
1505
1825
|
} catch {
|
|
1506
1826
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
1507
1827
|
}
|
|
@@ -1781,7 +2101,7 @@ function toHostPayload(p) {
|
|
|
1781
2101
|
}
|
|
1782
2102
|
function resolveDirective(root, p) {
|
|
1783
2103
|
const ctx = { root };
|
|
1784
|
-
const adapter =
|
|
2104
|
+
const adapter = resolveAdapter3(p.host);
|
|
1785
2105
|
const hostPayload = toHostPayload(p);
|
|
1786
2106
|
if (typeof adapter.emitHandoff === "function") {
|
|
1787
2107
|
return adapter.emitHandoff(ctx, hostPayload);
|
|
@@ -1824,7 +2144,7 @@ async function probeOnly(opts) {
|
|
|
1824
2144
|
async function handoff(opts) {
|
|
1825
2145
|
let project;
|
|
1826
2146
|
try {
|
|
1827
|
-
project =
|
|
2147
|
+
project = loadProjectInfo5(process.cwd());
|
|
1828
2148
|
} catch {
|
|
1829
2149
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
1830
2150
|
}
|
|
@@ -1856,7 +2176,7 @@ async function handoff(opts) {
|
|
|
1856
2176
|
if (opts.write === true) {
|
|
1857
2177
|
const md = renderMarkdown(full);
|
|
1858
2178
|
const path = handoffFilePath(project.root, task?.taskId, project.id);
|
|
1859
|
-
mkdirSync(
|
|
2179
|
+
mkdirSync(join3(project.root, NOIR_DIR, "handoff"), { recursive: true });
|
|
1860
2180
|
writeFileSync2(path, md, "utf8");
|
|
1861
2181
|
info(`handoff written to ${path}`, opts);
|
|
1862
2182
|
}
|
|
@@ -1873,7 +2193,7 @@ import { hostLaunchDirective as hostLaunchDirective2 } from "@noir-ai/adapters";
|
|
|
1873
2193
|
import {
|
|
1874
2194
|
detectActiveMethod as detectActiveMethod2,
|
|
1875
2195
|
latestVersionFromCache as latestVersionFromCache2,
|
|
1876
|
-
loadProjectInfo as
|
|
2196
|
+
loadProjectInfo as loadProjectInfo6,
|
|
1877
2197
|
NOIR_VERSION as NOIR_VERSION2,
|
|
1878
2198
|
readInstallRecord as readInstallRecord2,
|
|
1879
2199
|
readUpdateCache as readUpdateCache2
|
|
@@ -2167,7 +2487,7 @@ async function resolveSections(commands) {
|
|
|
2167
2487
|
// src/commands/home.ts
|
|
2168
2488
|
function tryProject() {
|
|
2169
2489
|
try {
|
|
2170
|
-
const info2 =
|
|
2490
|
+
const info2 = loadProjectInfo6(process.cwd());
|
|
2171
2491
|
return { id: info2.id, name: info2.name, host: info2.config.host };
|
|
2172
2492
|
} catch {
|
|
2173
2493
|
return null;
|
|
@@ -2331,7 +2651,7 @@ async function runMenu(opts, deps) {
|
|
|
2331
2651
|
env: process.env,
|
|
2332
2652
|
configUpdate: (() => {
|
|
2333
2653
|
try {
|
|
2334
|
-
const info2 =
|
|
2654
|
+
const info2 = loadProjectInfo6(process.cwd());
|
|
2335
2655
|
return info2.config.update ?? DEFAULT_UPDATE_CONFIG;
|
|
2336
2656
|
} catch {
|
|
2337
2657
|
return DEFAULT_UPDATE_CONFIG;
|
|
@@ -2380,6 +2700,27 @@ ${COMMANDS_HINT}
|
|
|
2380
2700
|
}
|
|
2381
2701
|
|
|
2382
2702
|
// src/commands/memory.ts
|
|
2703
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
2704
|
+
import { loadProjectInfo as loadProjectInfo7 } from "@noir-ai/core";
|
|
2705
|
+
function resolveWorkspace() {
|
|
2706
|
+
const routing = workspaceRoutingTarget(process.cwd());
|
|
2707
|
+
if (routing === null) return null;
|
|
2708
|
+
try {
|
|
2709
|
+
return { routing, project: loadProjectInfo7(process.cwd()) };
|
|
2710
|
+
} catch {
|
|
2711
|
+
return null;
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
async function callMemory(opts, name, args = {}) {
|
|
2715
|
+
const ws = resolveWorkspace();
|
|
2716
|
+
if (ws === null) return callDaemonTool(opts, name, args);
|
|
2717
|
+
return withWorkspaceDaemon(opts, ws.routing, (caller) => caller.callTool(name, args));
|
|
2718
|
+
}
|
|
2719
|
+
async function withMemoryDaemon(opts, fn) {
|
|
2720
|
+
const ws = resolveWorkspace();
|
|
2721
|
+
if (ws === null) return withDaemon(opts, fn);
|
|
2722
|
+
return withWorkspaceDaemon(opts, ws.routing, fn);
|
|
2723
|
+
}
|
|
2383
2724
|
function parseLimit2(raw, label, opts) {
|
|
2384
2725
|
if (raw === void 0) return void 0;
|
|
2385
2726
|
const n = Number(raw);
|
|
@@ -2418,6 +2759,39 @@ function stamp(ts) {
|
|
|
2418
2759
|
}
|
|
2419
2760
|
async function memoryRecall(opts) {
|
|
2420
2761
|
const limit = parseLimit2(opts.limit, "memory recall", opts);
|
|
2762
|
+
const ws = resolveWorkspace();
|
|
2763
|
+
if (ws !== null) {
|
|
2764
|
+
const probe2 = await probeWorkspaceDaemon(ws.routing);
|
|
2765
|
+
if (!probe2.running) {
|
|
2766
|
+
await withWorkspaceMemoryRead(opts, ws.routing, ws.project, async (memory) => {
|
|
2767
|
+
const { hits: hits3, degraded, mode } = await memory.recallWithMeta(opts.query, {
|
|
2768
|
+
...limit === void 0 ? {} : { limit }
|
|
2769
|
+
});
|
|
2770
|
+
const data3 = { query: opts.query, hits: hits3, degraded, mode };
|
|
2771
|
+
if (opts.json === true) {
|
|
2772
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: data3 })}
|
|
2773
|
+
`);
|
|
2774
|
+
return;
|
|
2775
|
+
}
|
|
2776
|
+
renderRecall(data3.query, data3.hits, data3.degraded, opts);
|
|
2777
|
+
});
|
|
2778
|
+
return;
|
|
2779
|
+
}
|
|
2780
|
+
const res2 = await callMemory(opts, "memory_recall", {
|
|
2781
|
+
query: opts.query,
|
|
2782
|
+
...limit === void 0 ? {} : { limit }
|
|
2783
|
+
});
|
|
2784
|
+
if (res2.ok !== true) failTool2("memory recall", res2, opts);
|
|
2785
|
+
const hits2 = Array.isArray(res2.results) ? res2.results.map(toHit2) : [];
|
|
2786
|
+
const data2 = { query: opts.query, hits: hits2, degraded: res2.degraded === true };
|
|
2787
|
+
if (opts.json === true) {
|
|
2788
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: data2 })}
|
|
2789
|
+
`);
|
|
2790
|
+
return;
|
|
2791
|
+
}
|
|
2792
|
+
renderRecall(data2.query, hits2, data2.degraded, opts);
|
|
2793
|
+
return;
|
|
2794
|
+
}
|
|
2421
2795
|
let probe;
|
|
2422
2796
|
try {
|
|
2423
2797
|
probe = await probeDaemon(opts);
|
|
@@ -2479,7 +2853,7 @@ async function memorySave(opts) {
|
|
|
2479
2853
|
const args = { content };
|
|
2480
2854
|
if (typeof opts.type === "string" && opts.type.length > 0) args.type = opts.type;
|
|
2481
2855
|
if (files !== void 0) args.files = files;
|
|
2482
|
-
const res = await
|
|
2856
|
+
const res = await callMemory(opts, "memory_save", args);
|
|
2483
2857
|
if (res.ok !== true) failTool2("memory save", res, opts);
|
|
2484
2858
|
const id = typeof res.id === "string" ? res.id : "";
|
|
2485
2859
|
const observation = res.observation ?? {};
|
|
@@ -2523,7 +2897,98 @@ function renderObservation(obs, opts) {
|
|
|
2523
2897
|
if (typeof content === "string") log(`
|
|
2524
2898
|
${content}`, opts);
|
|
2525
2899
|
}
|
|
2900
|
+
async function memoryCapture(opts) {
|
|
2901
|
+
const content = await resolveCaptureContent(opts);
|
|
2902
|
+
const args = { content };
|
|
2903
|
+
if (typeof opts.eventType === "string" && opts.eventType.length > 0)
|
|
2904
|
+
args.eventType = opts.eventType;
|
|
2905
|
+
const res = await callMemory(opts, "memory_capture", args);
|
|
2906
|
+
if (res.ok !== true) failTool2("memory capture", res, opts);
|
|
2907
|
+
const id = typeof res.id === "string" ? res.id : "";
|
|
2908
|
+
const observation = res.observation ?? {};
|
|
2909
|
+
if (opts.json === true) {
|
|
2910
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: { id, observation } })}
|
|
2911
|
+
`);
|
|
2912
|
+
return;
|
|
2913
|
+
}
|
|
2914
|
+
log(`Captured memory ${id}.`, opts);
|
|
2915
|
+
renderObservation(observation, opts);
|
|
2916
|
+
}
|
|
2917
|
+
async function resolveCaptureContent(opts) {
|
|
2918
|
+
if (typeof opts.content === "string" && opts.content.length > 0) return opts.content;
|
|
2919
|
+
if (typeof opts.file === "string" && opts.file.length > 0) {
|
|
2920
|
+
try {
|
|
2921
|
+
return readFileSync3(opts.file, "utf8");
|
|
2922
|
+
} catch {
|
|
2923
|
+
fail(EXIT.USAGE, `memory capture could not read --file ${opts.file}`, opts);
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
if (!process.stdin.isTTY) {
|
|
2927
|
+
try {
|
|
2928
|
+
return readFileSync3(0, "utf8");
|
|
2929
|
+
} catch {
|
|
2930
|
+
fail(
|
|
2931
|
+
EXIT.USAGE,
|
|
2932
|
+
"memory capture could not read piped stdin \u2014 pass --content <text> or --file <path>.",
|
|
2933
|
+
opts
|
|
2934
|
+
);
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
if (!isInteractive(opts)) {
|
|
2938
|
+
fail(
|
|
2939
|
+
EXIT.USAGE,
|
|
2940
|
+
"memory capture requires --content <text> or --file <path> (or piped stdin).",
|
|
2941
|
+
opts
|
|
2942
|
+
);
|
|
2943
|
+
}
|
|
2944
|
+
const clack = await import("@clack/prompts");
|
|
2945
|
+
const value = await clack.text({ message: "Captured memory content:" });
|
|
2946
|
+
if (clack.isCancel(value)) {
|
|
2947
|
+
clack.cancel("Cancelled.");
|
|
2948
|
+
fail(EXIT.CANCELLED, "cancelled", opts);
|
|
2949
|
+
}
|
|
2950
|
+
return String(value);
|
|
2951
|
+
}
|
|
2526
2952
|
async function memorySessions(opts) {
|
|
2953
|
+
const ws = resolveWorkspace();
|
|
2954
|
+
if (ws !== null) {
|
|
2955
|
+
const probe2 = await probeWorkspaceDaemon(ws.routing);
|
|
2956
|
+
if (!probe2.running) {
|
|
2957
|
+
await withWorkspaceMemoryRead(opts, ws.routing, ws.project, async (memory) => {
|
|
2958
|
+
const sessions3 = memory.sessions().map((s) => ({
|
|
2959
|
+
id: s.id,
|
|
2960
|
+
count: s.count,
|
|
2961
|
+
lastTs: s.lastTs
|
|
2962
|
+
}));
|
|
2963
|
+
const data3 = { sessions: sessions3 };
|
|
2964
|
+
if (opts.json === true) {
|
|
2965
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: data3 })}
|
|
2966
|
+
`);
|
|
2967
|
+
return;
|
|
2968
|
+
}
|
|
2969
|
+
renderSessions(sessions3, opts);
|
|
2970
|
+
});
|
|
2971
|
+
return;
|
|
2972
|
+
}
|
|
2973
|
+
const res2 = await callMemory(opts, "memory_sessions");
|
|
2974
|
+
if (res2.ok !== true) failTool2("memory sessions", res2, opts);
|
|
2975
|
+
const sessions2 = Array.isArray(res2.sessions) ? res2.sessions.map((raw) => {
|
|
2976
|
+
const s = raw ?? {};
|
|
2977
|
+
return {
|
|
2978
|
+
id: typeof s.id === "string" ? s.id : "",
|
|
2979
|
+
count: typeof s.count === "number" ? s.count : 0,
|
|
2980
|
+
lastTs: typeof s.lastTs === "number" ? s.lastTs : 0
|
|
2981
|
+
};
|
|
2982
|
+
}) : [];
|
|
2983
|
+
const data2 = { sessions: sessions2 };
|
|
2984
|
+
if (opts.json === true) {
|
|
2985
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: data2 })}
|
|
2986
|
+
`);
|
|
2987
|
+
return;
|
|
2988
|
+
}
|
|
2989
|
+
renderSessions(sessions2, opts);
|
|
2990
|
+
return;
|
|
2991
|
+
}
|
|
2527
2992
|
let probe;
|
|
2528
2993
|
try {
|
|
2529
2994
|
probe = await probeDaemon(opts);
|
|
@@ -2578,7 +3043,7 @@ async function memoryForget(opts) {
|
|
|
2578
3043
|
if (opts.ids.length === 0) {
|
|
2579
3044
|
fail(EXIT.USAGE, "memory forget requires at least one <id>", opts);
|
|
2580
3045
|
}
|
|
2581
|
-
const res = await
|
|
3046
|
+
const res = await callMemory(opts, "memory_forget", {
|
|
2582
3047
|
ids: opts.ids
|
|
2583
3048
|
});
|
|
2584
3049
|
if (res.ok !== true) failTool2("memory forget", res, opts);
|
|
@@ -2592,11 +3057,11 @@ async function memoryForget(opts) {
|
|
|
2592
3057
|
log(`Forgot ${deleted} observation${deleted === 1 ? "" : "s"}.`, opts);
|
|
2593
3058
|
}
|
|
2594
3059
|
async function memoryConsolidate(opts) {
|
|
2595
|
-
const exposed = await
|
|
3060
|
+
const exposed = await withMemoryDaemon(opts, async (caller) => caller.listTools());
|
|
2596
3061
|
if (!exposed.includes("memory_consolidate")) {
|
|
2597
3062
|
fail(
|
|
2598
3063
|
EXIT.ERROR,
|
|
2599
|
-
"memory consolidate: the daemon does not expose the memory_consolidate tool. Enable it in .noir/config under memory.consolidation (enabled: true + a provider + model), then restart the daemon.",
|
|
3064
|
+
resolveWorkspace() !== null ? "memory consolidate is not supported on shared workspaces (the workspace memory store has no consolidation model)." : "memory consolidate: the daemon does not expose the memory_consolidate tool. Enable it in .noir/config under memory.consolidation (enabled: true + a provider + model), then restart the daemon.",
|
|
2600
3065
|
opts
|
|
2601
3066
|
);
|
|
2602
3067
|
}
|
|
@@ -2605,7 +3070,7 @@ async function memoryConsolidate(opts) {
|
|
|
2605
3070
|
const args = {};
|
|
2606
3071
|
if (types !== void 0) args.types = types;
|
|
2607
3072
|
if (limit !== void 0) args.limit = limit;
|
|
2608
|
-
const res = await
|
|
3073
|
+
const res = await callMemory(
|
|
2609
3074
|
opts,
|
|
2610
3075
|
"memory_consolidate",
|
|
2611
3076
|
args
|
|
@@ -2738,7 +3203,7 @@ async function release(opts) {
|
|
|
2738
3203
|
|
|
2739
3204
|
// src/commands/run.ts
|
|
2740
3205
|
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
2741
|
-
import { join as
|
|
3206
|
+
import { join as join4 } from "path";
|
|
2742
3207
|
import { SUPPORTED_HOSTS } from "@noir-ai/adapters";
|
|
2743
3208
|
import { parseConfig } from "@noir-ai/core";
|
|
2744
3209
|
|
|
@@ -3104,10 +3569,10 @@ async function shellFallback(binary, args, opts, resolve, reject, originalErr) {
|
|
|
3104
3569
|
}
|
|
3105
3570
|
|
|
3106
3571
|
// src/run-profiles.ts
|
|
3107
|
-
import { loadProjectInfo as
|
|
3572
|
+
import { loadProjectInfo as loadProjectInfo8 } from "@noir-ai/core";
|
|
3108
3573
|
function loadRunConfig(root) {
|
|
3109
3574
|
try {
|
|
3110
|
-
return
|
|
3575
|
+
return loadProjectInfo8(root).config;
|
|
3111
3576
|
} catch {
|
|
3112
3577
|
return null;
|
|
3113
3578
|
}
|
|
@@ -3246,8 +3711,8 @@ function mergeEnv(base, overlay) {
|
|
|
3246
3711
|
}
|
|
3247
3712
|
function writeTranscript(host, lines) {
|
|
3248
3713
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3249
|
-
const dir =
|
|
3250
|
-
const file =
|
|
3714
|
+
const dir = join4(process.cwd(), ".noir", "transcripts");
|
|
3715
|
+
const file = join4(dir, `${host}-${ts}.jsonl`);
|
|
3251
3716
|
try {
|
|
3252
3717
|
mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
3253
3718
|
writeFileSync3(file, `${lines.join("\n")}${lines.length > 0 ? "\n" : ""}`, { mode: 384 });
|
|
@@ -3258,8 +3723,8 @@ function writeTranscript(host, lines) {
|
|
|
3258
3723
|
}
|
|
3259
3724
|
|
|
3260
3725
|
// src/commands/skills.ts
|
|
3261
|
-
import { resolveAdapter as
|
|
3262
|
-
import { loadProjectInfo as
|
|
3726
|
+
import { resolveAdapter as resolveAdapter4 } from "@noir-ai/adapters";
|
|
3727
|
+
import { loadProjectInfo as loadProjectInfo9 } from "@noir-ai/core";
|
|
3263
3728
|
import {
|
|
3264
3729
|
buildRegistry,
|
|
3265
3730
|
discoverAll,
|
|
@@ -3355,11 +3820,11 @@ async function skillsSync(opts) {
|
|
|
3355
3820
|
const root = process.cwd();
|
|
3356
3821
|
let project;
|
|
3357
3822
|
try {
|
|
3358
|
-
project =
|
|
3823
|
+
project = loadProjectInfo9(root);
|
|
3359
3824
|
} catch {
|
|
3360
3825
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
3361
3826
|
}
|
|
3362
|
-
const adapter =
|
|
3827
|
+
const adapter = resolveAdapter4(project.config.host);
|
|
3363
3828
|
if (!adapter.skillsDir) {
|
|
3364
3829
|
const data2 = {
|
|
3365
3830
|
emitted: [],
|
|
@@ -3488,8 +3953,8 @@ async function skillsRegistry(opts) {
|
|
|
3488
3953
|
}
|
|
3489
3954
|
|
|
3490
3955
|
// src/init.ts
|
|
3491
|
-
import { resolveAdapter as
|
|
3492
|
-
import { loadProjectInfo as
|
|
3956
|
+
import { resolveAdapter as resolveAdapter5 } from "@noir-ai/adapters";
|
|
3957
|
+
import { loadProjectInfo as loadProjectInfo10 } from "@noir-ai/core";
|
|
3493
3958
|
import { scaffold } from "@noir-ai/create";
|
|
3494
3959
|
import { emitSkillsToDir as emitSkillsToDir2 } from "@noir-ai/skills";
|
|
3495
3960
|
|
|
@@ -3595,8 +4060,8 @@ function labelFor(resolution) {
|
|
|
3595
4060
|
}
|
|
3596
4061
|
|
|
3597
4062
|
// src/dedup-write.ts
|
|
3598
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
3599
|
-
import { join as
|
|
4063
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync4 } from "fs";
|
|
4064
|
+
import { join as join5 } from "path";
|
|
3600
4065
|
import { sha256Hex as sha256Hex2, sha256Hex12 } from "@noir-ai/core";
|
|
3601
4066
|
var HOST_CONTEXT_RELS = /* @__PURE__ */ new Set(["CLAUDE.md", "AGENTS.md", "GEMINI.md", ".noir/rules/RULES.md"]);
|
|
3602
4067
|
var ACTION_THRESHOLD = 0.95;
|
|
@@ -3612,10 +4077,10 @@ async function checkWritePathDedup(root, res, opts) {
|
|
|
3612
4077
|
if (embed === void 0) return result;
|
|
3613
4078
|
const cachedEmbed = makeCachedEmbed(cache, embed);
|
|
3614
4079
|
for (const proposedRel of writtenHostFiles) {
|
|
3615
|
-
const proposedAbs =
|
|
4080
|
+
const proposedAbs = join5(root, proposedRel);
|
|
3616
4081
|
let proposedText;
|
|
3617
4082
|
try {
|
|
3618
|
-
proposedText =
|
|
4083
|
+
proposedText = readFileSync4(proposedAbs, "utf8");
|
|
3619
4084
|
} catch {
|
|
3620
4085
|
continue;
|
|
3621
4086
|
}
|
|
@@ -3659,7 +4124,7 @@ async function checkWritePathDedup(root, res, opts) {
|
|
|
3659
4124
|
`
|
|
3660
4125
|
);
|
|
3661
4126
|
}
|
|
3662
|
-
const matchedText = safeRead(
|
|
4127
|
+
const matchedText = safeRead(join5(root, matchedRel));
|
|
3663
4128
|
result.conflicts.push({
|
|
3664
4129
|
path: proposedRel,
|
|
3665
4130
|
mode: "artifact",
|
|
@@ -3743,21 +4208,21 @@ async function promptDedupAction(root, proposedRel, matchedRel, proposedText, si
|
|
|
3743
4208
|
if (clack.isCancel(choice)) return "preserve";
|
|
3744
4209
|
if (choice === "skip") {
|
|
3745
4210
|
try {
|
|
3746
|
-
rmSync(
|
|
4211
|
+
rmSync(join5(root, proposedRel), { force: true });
|
|
3747
4212
|
} catch {
|
|
3748
4213
|
}
|
|
3749
4214
|
return "preserve";
|
|
3750
4215
|
} else if (choice === "replace") {
|
|
3751
4216
|
try {
|
|
3752
|
-
writeFileSync4(
|
|
4217
|
+
writeFileSync4(join5(root, matchedRel), proposedText, "utf8");
|
|
3753
4218
|
} catch {
|
|
3754
4219
|
}
|
|
3755
4220
|
return "replace";
|
|
3756
4221
|
} else if (choice === "mirror") {
|
|
3757
4222
|
try {
|
|
3758
|
-
const existing =
|
|
4223
|
+
const existing = readFileSync4(join5(root, matchedRel), "utf8");
|
|
3759
4224
|
writeFileSync4(
|
|
3760
|
-
|
|
4225
|
+
join5(root, matchedRel),
|
|
3761
4226
|
`${existing}
|
|
3762
4227
|
|
|
3763
4228
|
<!-- mirrored from ${proposedRel} -->
|
|
@@ -3775,11 +4240,11 @@ function emptyCache() {
|
|
|
3775
4240
|
return { version: DEDUP_CACHE_VERSION, model: "", entries: {} };
|
|
3776
4241
|
}
|
|
3777
4242
|
function cachePath(root) {
|
|
3778
|
-
return
|
|
4243
|
+
return join5(root, ".noir", "dedup-cache.json");
|
|
3779
4244
|
}
|
|
3780
4245
|
function loadCache(root) {
|
|
3781
4246
|
try {
|
|
3782
|
-
const raw =
|
|
4247
|
+
const raw = readFileSync4(cachePath(root), "utf8");
|
|
3783
4248
|
const parsed = JSON.parse(raw);
|
|
3784
4249
|
if (parsed.version !== DEDUP_CACHE_VERSION) return emptyCache();
|
|
3785
4250
|
if (typeof parsed.entries !== "object" || parsed.entries === null) return emptyCache();
|
|
@@ -3794,7 +4259,7 @@ function loadCache(root) {
|
|
|
3794
4259
|
}
|
|
3795
4260
|
function saveCache(root, cache) {
|
|
3796
4261
|
try {
|
|
3797
|
-
mkdirSync3(
|
|
4262
|
+
mkdirSync3(join5(root, ".noir"), { recursive: true });
|
|
3798
4263
|
writeFileSync4(cachePath(root), JSON.stringify(cache), "utf8");
|
|
3799
4264
|
} catch {
|
|
3800
4265
|
}
|
|
@@ -3811,7 +4276,7 @@ function makeCachedEmbed(cache, embed) {
|
|
|
3811
4276
|
}
|
|
3812
4277
|
function safeRead(abs) {
|
|
3813
4278
|
try {
|
|
3814
|
-
return
|
|
4279
|
+
return readFileSync4(abs, "utf8");
|
|
3815
4280
|
} catch {
|
|
3816
4281
|
return void 0;
|
|
3817
4282
|
}
|
|
@@ -3862,7 +4327,7 @@ async function init(root, opts) {
|
|
|
3862
4327
|
await emitHostSkills(root, host, conflictOpts, interactive);
|
|
3863
4328
|
let projectInfo;
|
|
3864
4329
|
try {
|
|
3865
|
-
projectInfo =
|
|
4330
|
+
projectInfo = loadProjectInfo10(root);
|
|
3866
4331
|
} catch {
|
|
3867
4332
|
projectInfo = void 0;
|
|
3868
4333
|
}
|
|
@@ -3893,7 +4358,7 @@ function reportPlannedWrites(res) {
|
|
|
3893
4358
|
}
|
|
3894
4359
|
}
|
|
3895
4360
|
async function emitHostSkills(root, host, conflictOpts, interactive) {
|
|
3896
|
-
const adapter =
|
|
4361
|
+
const adapter = resolveAdapter5(host);
|
|
3897
4362
|
const skillsDir = adapter.skillsDir?.({ root });
|
|
3898
4363
|
if (skillsDir === void 0) {
|
|
3899
4364
|
process.stderr.write(`host '${host}' has no skill emitter; skipping skills
|
|
@@ -3941,11 +4406,11 @@ function assertLocalhostUrl(raw) {
|
|
|
3941
4406
|
}
|
|
3942
4407
|
|
|
3943
4408
|
// src/serve.ts
|
|
3944
|
-
import { applyNoirEnv, loadProjectInfo as
|
|
4409
|
+
import { applyNoirEnv, loadProjectInfo as loadProjectInfo11 } from "@noir-ai/core";
|
|
3945
4410
|
import { ensureDaemonRunning as ensureDaemonRunning2, startStdioServer } from "@noir-ai/daemon";
|
|
3946
4411
|
async function serve(opts) {
|
|
3947
4412
|
applyNoirEnv(process.cwd());
|
|
3948
|
-
const project =
|
|
4413
|
+
const project = loadProjectInfo11(process.cwd());
|
|
3949
4414
|
if (opts.stdio) {
|
|
3950
4415
|
await startStdioServer({ project, transport: "stdio", daemon: false });
|
|
3951
4416
|
return;
|
|
@@ -4118,7 +4583,7 @@ function createProgram() {
|
|
|
4118
4583
|
const force = opts.force === true;
|
|
4119
4584
|
const host = parseHost(opts.host);
|
|
4120
4585
|
const dryRun = opts.dryRun === true || opts.preview === true;
|
|
4121
|
-
const { create } = await import("./create-
|
|
4586
|
+
const { create } = await import("./create-HYFBTBLR.js");
|
|
4122
4587
|
const result = await create(dir, {
|
|
4123
4588
|
transport,
|
|
4124
4589
|
url: opts.url,
|
|
@@ -4157,7 +4622,7 @@ function createProgram() {
|
|
|
4157
4622
|
).choices(SUPPORTED_HOSTS2)
|
|
4158
4623
|
).action(
|
|
4159
4624
|
async (opts, cmd) => {
|
|
4160
|
-
const { sync } = await import("./sync-
|
|
4625
|
+
const { sync } = await import("./sync-QDOJ7INZ.js");
|
|
4161
4626
|
const host = parseHost(opts.host);
|
|
4162
4627
|
const force = opts.force === true;
|
|
4163
4628
|
const merge = opts.merge === true;
|
|
@@ -4183,30 +4648,75 @@ function createProgram() {
|
|
|
4183
4648
|
throw new NoirCliError(EXIT.USAGE, "Usage: noir mcp serve [--stdio]");
|
|
4184
4649
|
});
|
|
4185
4650
|
const daemonGrp = program2.command("daemon").description("control the Noir daemon");
|
|
4186
|
-
daemonGrp.command("start").description("start the Noir daemon (foreground, or background with --detach)").option("--detach", "run the daemon in the background and exit").addOption(
|
|
4651
|
+
daemonGrp.command("start").description("start the Noir daemon (foreground, or background with --detach)").option("--detach", "run the daemon in the background and exit").option("--workspace <name>", "start a shared cross-repo workspace daemon instead").option("--force", "overwrite a non-Noir .mcp.json entry (--workspace only)").addOption(
|
|
4187
4652
|
new Option("--_detached-child", "reserved: detached daemon child (internal)").hideHelp()
|
|
4188
4653
|
).action(async (...args) => {
|
|
4189
4654
|
const cmd = trailingCmd(args);
|
|
4190
4655
|
const g = cmd.optsWithGlobals();
|
|
4191
4656
|
const detach = g.detach === true;
|
|
4192
4657
|
const detachChild = g._detachedChild === true;
|
|
4658
|
+
const workspace = typeof g.workspace === "string" ? g.workspace : void 0;
|
|
4193
4659
|
await daemonStart({
|
|
4194
4660
|
...toCliOptions(g),
|
|
4195
4661
|
...detach ? { detach } : {},
|
|
4196
|
-
...detachChild ? { detachChild } : {}
|
|
4662
|
+
...detachChild ? { detachChild } : {},
|
|
4663
|
+
...workspace !== void 0 ? { workspace } : {},
|
|
4664
|
+
...g.force === true ? { force: true } : {}
|
|
4197
4665
|
});
|
|
4198
4666
|
});
|
|
4667
|
+
daemonGrp.command("join").description("join a shared workspace from this repo").argument("<name>", "workspace name").option("--force", "overwrite a non-Noir .mcp.json entry").action(async (...args) => {
|
|
4668
|
+
const cmd = trailingCmd(args);
|
|
4669
|
+
const g = cmd.optsWithGlobals();
|
|
4670
|
+
const name = typeof args[0] === "string" ? args[0] : "";
|
|
4671
|
+
await daemonJoin({ ...toCliOptions(g), name, ...g.force === true ? { force: true } : {} });
|
|
4672
|
+
});
|
|
4199
4673
|
daemonGrp.command("stop").description("stop the Noir daemon").action(async (...args) => {
|
|
4200
4674
|
await daemonStop(toCliOptions(trailingCmd(args).optsWithGlobals()));
|
|
4201
4675
|
});
|
|
4202
4676
|
daemonGrp.command("status").description("report daemon pid/uptime/mode (exit 4 if not running)").action(async (...args) => {
|
|
4203
4677
|
await daemonStatus(toCliOptions(trailingCmd(args).optsWithGlobals()));
|
|
4204
4678
|
});
|
|
4205
|
-
daemonGrp.command("restart").description("stop then start the daemon").action(async (...args) => {
|
|
4206
|
-
|
|
4679
|
+
daemonGrp.command("restart").description("stop then start the daemon").option("--detach", "run the daemon in the background and exit").action(async (...args) => {
|
|
4680
|
+
const cmd = trailingCmd(args);
|
|
4681
|
+
const g = cmd.optsWithGlobals();
|
|
4682
|
+
await daemonRestart({
|
|
4683
|
+
...toCliOptions(g),
|
|
4684
|
+
...g.detach === true ? { detach: true } : {}
|
|
4685
|
+
});
|
|
4207
4686
|
});
|
|
4208
4687
|
daemonGrp.action(() => {
|
|
4209
|
-
throw new NoirCliError(EXIT.USAGE, "Usage: noir daemon start|stop|status|restart");
|
|
4688
|
+
throw new NoirCliError(EXIT.USAGE, "Usage: noir daemon start|join|stop|status|restart");
|
|
4689
|
+
});
|
|
4690
|
+
const workspaceGrp = program2.command("workspace").description("shared cross-repo workspaces");
|
|
4691
|
+
workspaceGrp.command("list").description("list workspaces on this machine").action(async (...args) => {
|
|
4692
|
+
await workspaceList(toCliOptions(trailingCmd(args).optsWithGlobals()));
|
|
4693
|
+
});
|
|
4694
|
+
workspaceGrp.command("status").description("members + daemon liveness for a workspace").argument("[name]", "workspace name (defaults to this repo's joined workspace)").action(async (...args) => {
|
|
4695
|
+
const cmd = trailingCmd(args);
|
|
4696
|
+
const name = typeof args[0] === "string" && args[0].length > 0 ? args[0] : void 0;
|
|
4697
|
+
await workspaceStatus({
|
|
4698
|
+
...toCliOptions(cmd.optsWithGlobals()),
|
|
4699
|
+
...name !== void 0 ? { name } : {}
|
|
4700
|
+
});
|
|
4701
|
+
});
|
|
4702
|
+
workspaceGrp.command("leave").description("leave the workspace this repo joined (restore stdio)").option("--force", "overwrite a non-Noir .mcp.json entry").action(async (...args) => {
|
|
4703
|
+
const cmd = trailingCmd(args);
|
|
4704
|
+
const g = cmd.optsWithGlobals();
|
|
4705
|
+
await workspaceLeave({
|
|
4706
|
+
...toCliOptions(g),
|
|
4707
|
+
...g.force === true ? { force: true } : {}
|
|
4708
|
+
});
|
|
4709
|
+
});
|
|
4710
|
+
workspaceGrp.command("stop").description("stop a workspace daemon (membership retained)").argument("[name]", "workspace name (defaults to this repo's joined workspace)").action(async (...args) => {
|
|
4711
|
+
const cmd = trailingCmd(args);
|
|
4712
|
+
const name = typeof args[0] === "string" && args[0].length > 0 ? args[0] : void 0;
|
|
4713
|
+
await workspaceStop({
|
|
4714
|
+
...toCliOptions(cmd.optsWithGlobals()),
|
|
4715
|
+
...name !== void 0 ? { name } : {}
|
|
4716
|
+
});
|
|
4717
|
+
});
|
|
4718
|
+
workspaceGrp.action(() => {
|
|
4719
|
+
throw new NoirCliError(EXIT.USAGE, "Usage: noir workspace list|status|leave|stop");
|
|
4210
4720
|
});
|
|
4211
4721
|
program2.command("doctor").description("environment + project health").option(
|
|
4212
4722
|
"--dedup",
|
|
@@ -4302,10 +4812,23 @@ function createProgram() {
|
|
|
4302
4812
|
...limit === void 0 ? {} : { limit }
|
|
4303
4813
|
});
|
|
4304
4814
|
});
|
|
4815
|
+
memoryGrp.command("capture").description("distill a transcript/notes file (or stdin) into a memory (manual)").argument("[file]", "transcript/notes file (or pipe stdin)").option("--content <text>", "inline distilled content").option("--event-type <type>", "capture hook label (defaults to Stop)").action(async (...args) => {
|
|
4816
|
+
const cmd = trailingCmd(args);
|
|
4817
|
+
const g = cmd.optsWithGlobals();
|
|
4818
|
+
const file = typeof args[0] === "string" && args[0].length > 0 ? args[0] : void 0;
|
|
4819
|
+
const content = typeof g.content === "string" ? g.content : void 0;
|
|
4820
|
+
const eventType = typeof g.eventType === "string" ? g.eventType : void 0;
|
|
4821
|
+
await memoryCapture({
|
|
4822
|
+
...toCliOptions(g),
|
|
4823
|
+
...file === void 0 ? {} : { file },
|
|
4824
|
+
...content === void 0 ? {} : { content },
|
|
4825
|
+
...eventType === void 0 ? {} : { eventType }
|
|
4826
|
+
});
|
|
4827
|
+
});
|
|
4305
4828
|
memoryGrp.action(() => {
|
|
4306
4829
|
throw new NoirCliError(
|
|
4307
4830
|
EXIT.USAGE,
|
|
4308
|
-
"Usage: noir memory recall|save|sessions|forget|consolidate"
|
|
4831
|
+
"Usage: noir memory recall|save|capture|sessions|forget|consolidate"
|
|
4309
4832
|
);
|
|
4310
4833
|
});
|
|
4311
4834
|
const skillsGrp = program2.command("skills").description("builtin skills");
|
|
@@ -4636,7 +5159,7 @@ async function probeDaemon(opts = {}) {
|
|
|
4636
5159
|
if (opts.verbose) process.stderr.write("noir: daemon probe: no daemon record\n");
|
|
4637
5160
|
return { running: false };
|
|
4638
5161
|
}
|
|
4639
|
-
if (!
|
|
5162
|
+
if (!pidAlive4(rec.pid)) {
|
|
4640
5163
|
if (opts.verbose) process.stderr.write(`noir: daemon probe: pid ${rec.pid} not alive
|
|
4641
5164
|
`);
|
|
4642
5165
|
return { running: false };
|
|
@@ -4654,7 +5177,7 @@ async function probeDaemon(opts = {}) {
|
|
|
4654
5177
|
const pidOk = typeof body?.pid === "number" && body.pid === rec.pid;
|
|
4655
5178
|
const expectedProject = opts.project?.id ?? (() => {
|
|
4656
5179
|
try {
|
|
4657
|
-
return
|
|
5180
|
+
return loadProjectInfo12(process.cwd()).id;
|
|
4658
5181
|
} catch {
|
|
4659
5182
|
return void 0;
|
|
4660
5183
|
}
|
|
@@ -4684,7 +5207,7 @@ async function probeDaemon(opts = {}) {
|
|
|
4684
5207
|
async function resolveDaemon(opts) {
|
|
4685
5208
|
let project;
|
|
4686
5209
|
try {
|
|
4687
|
-
project = opts.project ??
|
|
5210
|
+
project = opts.project ?? loadProjectInfo12(process.cwd());
|
|
4688
5211
|
} catch {
|
|
4689
5212
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
4690
5213
|
}
|
|
@@ -4780,7 +5303,7 @@ async function callDaemonTool(opts, name, args = {}) {
|
|
|
4780
5303
|
async function withInProcessRead(opts, fn) {
|
|
4781
5304
|
let project;
|
|
4782
5305
|
try {
|
|
4783
|
-
project = opts.project ??
|
|
5306
|
+
project = opts.project ?? loadProjectInfo12(process.cwd());
|
|
4784
5307
|
} catch {
|
|
4785
5308
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
4786
5309
|
}
|
|
@@ -4823,6 +5346,91 @@ async function withInProcessRead(opts, fn) {
|
|
|
4823
5346
|
});
|
|
4824
5347
|
}
|
|
4825
5348
|
}
|
|
5349
|
+
function workspaceRoutingTarget(root) {
|
|
5350
|
+
const name = readWorkspaceMarker2(root);
|
|
5351
|
+
if (name === null) return null;
|
|
5352
|
+
try {
|
|
5353
|
+
return { name, projectId: loadProjectInfo12(root).id };
|
|
5354
|
+
} catch {
|
|
5355
|
+
return null;
|
|
5356
|
+
}
|
|
5357
|
+
}
|
|
5358
|
+
async function probeWorkspaceDaemon(routing) {
|
|
5359
|
+
const rec = readWorkspaceDaemonRecord2(routing.name);
|
|
5360
|
+
if (rec === null || !pidAlive4(rec.pid)) return { running: false };
|
|
5361
|
+
try {
|
|
5362
|
+
const res = await fetch(`http://127.0.0.1:${rec.port}/health`, {
|
|
5363
|
+
signal: AbortSignal.timeout(PROBE_TIMEOUT_MS)
|
|
5364
|
+
});
|
|
5365
|
+
if (!res.ok) return { running: false };
|
|
5366
|
+
const body = await res.json().catch(() => null);
|
|
5367
|
+
if (body?.ok !== true || body.workspace !== routing.name || body.pid !== rec.pid) {
|
|
5368
|
+
return { running: false };
|
|
5369
|
+
}
|
|
5370
|
+
return { running: true, pid: rec.pid, port: rec.port };
|
|
5371
|
+
} catch {
|
|
5372
|
+
return { running: false };
|
|
5373
|
+
}
|
|
5374
|
+
}
|
|
5375
|
+
async function withWorkspaceDaemon(opts, routing, fn) {
|
|
5376
|
+
const probe = await probeWorkspaceDaemon(routing);
|
|
5377
|
+
if (!probe.running || probe.port === void 0) {
|
|
5378
|
+
fail(
|
|
5379
|
+
EXIT.DAEMON_DOWN,
|
|
5380
|
+
`workspace ${JSON.stringify(routing.name)} daemon is not running \u2014 start it with \`noir daemon start --workspace ${routing.name}\` (or run \`noir workspace status\`).`,
|
|
5381
|
+
opts
|
|
5382
|
+
);
|
|
5383
|
+
}
|
|
5384
|
+
const url = `http://127.0.0.1:${probe.port}/mcp?p=${routing.projectId}`;
|
|
5385
|
+
let client;
|
|
5386
|
+
try {
|
|
5387
|
+
client = new Client(
|
|
5388
|
+
{ name: "noir-cli", version: NOIR_VERSION4 },
|
|
5389
|
+
{ versionNegotiation: { mode: "auto" } }
|
|
5390
|
+
);
|
|
5391
|
+
const connected = client;
|
|
5392
|
+
await connectClient(connected, url, opts);
|
|
5393
|
+
return await fn(buildCaller(connected, opts));
|
|
5394
|
+
} finally {
|
|
5395
|
+
if (client) {
|
|
5396
|
+
await client.close().catch(() => {
|
|
5397
|
+
});
|
|
5398
|
+
}
|
|
5399
|
+
}
|
|
5400
|
+
}
|
|
5401
|
+
async function withWorkspaceMemoryRead(opts, routing, project, fn) {
|
|
5402
|
+
let store;
|
|
5403
|
+
try {
|
|
5404
|
+
store = await openStore({
|
|
5405
|
+
projectId: `ws-${routing.name}`,
|
|
5406
|
+
root: project.root,
|
|
5407
|
+
dbPath: workspaceStoreDbPath(routing.name),
|
|
5408
|
+
readonly: true
|
|
5409
|
+
});
|
|
5410
|
+
} catch (err) {
|
|
5411
|
+
fail(
|
|
5412
|
+
EXIT.DAEMON_DOWN,
|
|
5413
|
+
`workspace ${JSON.stringify(routing.name)} store is unavailable (${err instanceof Error ? err.message : String(err)}) \u2014 start its daemon with \`noir daemon start --workspace ${routing.name}\` (or run \`noir workspace status\`).`,
|
|
5414
|
+
opts
|
|
5415
|
+
);
|
|
5416
|
+
}
|
|
5417
|
+
const embedderCfg = resolveEmbedderConfig(project.config.context);
|
|
5418
|
+
const embed = createEmbedFn(embedderCfg).embed;
|
|
5419
|
+
const memory = createMemoryEngine({
|
|
5420
|
+
store,
|
|
5421
|
+
root: project.root,
|
|
5422
|
+
projectId: `ws-${routing.name}`,
|
|
5423
|
+
embed,
|
|
5424
|
+
storeDegraded: true,
|
|
5425
|
+
softForget: true
|
|
5426
|
+
});
|
|
5427
|
+
try {
|
|
5428
|
+
return await fn(memory);
|
|
5429
|
+
} finally {
|
|
5430
|
+
await store.close().catch(() => {
|
|
5431
|
+
});
|
|
5432
|
+
}
|
|
5433
|
+
}
|
|
4826
5434
|
|
|
4827
5435
|
// src/commands/status.ts
|
|
4828
5436
|
async function tryTool(caller, name, args) {
|
|
@@ -4945,7 +5553,7 @@ function renderHuman2(p, opts) {
|
|
|
4945
5553
|
async function gatherStatusPayload(opts) {
|
|
4946
5554
|
let project;
|
|
4947
5555
|
try {
|
|
4948
|
-
project =
|
|
5556
|
+
project = loadProjectInfo13(process.cwd());
|
|
4949
5557
|
} catch {
|
|
4950
5558
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
4951
5559
|
}
|
|
@@ -4975,7 +5583,7 @@ async function status(opts) {
|
|
|
4975
5583
|
env: process.env,
|
|
4976
5584
|
configUpdate: (() => {
|
|
4977
5585
|
try {
|
|
4978
|
-
const info2 =
|
|
5586
|
+
const info2 = loadProjectInfo13(process.cwd());
|
|
4979
5587
|
return info2.config.update ?? DEFAULT_UPDATE_CONFIG;
|
|
4980
5588
|
} catch {
|
|
4981
5589
|
return DEFAULT_UPDATE_CONFIG;
|
|
@@ -5007,4 +5615,4 @@ export {
|
|
|
5007
5615
|
assertTransportUrl,
|
|
5008
5616
|
buildPaletteCommandsForTui
|
|
5009
5617
|
};
|
|
5010
|
-
//# sourceMappingURL=chunk-
|
|
5618
|
+
//# sourceMappingURL=chunk-KMLSJRYV.js.map
|