@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
package/dist/index.js
CHANGED
|
@@ -540,12 +540,326 @@ var init_context = __esm({
|
|
|
540
540
|
}
|
|
541
541
|
});
|
|
542
542
|
|
|
543
|
+
// src/workspace-mcp.ts
|
|
544
|
+
import { existsSync, readFileSync } from "fs";
|
|
545
|
+
import { join } from "path";
|
|
546
|
+
import { resolveAdapter } from "@noir-ai/adapters";
|
|
547
|
+
import { atomicWriteFile, resolveNoirCommand } from "@noir-ai/core";
|
|
548
|
+
function mcpPathFor(root, host) {
|
|
549
|
+
return resolveAdapter(host).mcpConfigPath?.({ root }) ?? join(root, ".mcp.json");
|
|
550
|
+
}
|
|
551
|
+
function readJson(path) {
|
|
552
|
+
if (!existsSync(path)) return null;
|
|
553
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
554
|
+
}
|
|
555
|
+
function loadExisting(path, opts) {
|
|
556
|
+
let existing;
|
|
557
|
+
try {
|
|
558
|
+
existing = readJson(path);
|
|
559
|
+
} catch {
|
|
560
|
+
if (opts.force !== true) {
|
|
561
|
+
fail(
|
|
562
|
+
EXIT.ERROR,
|
|
563
|
+
`Refusing to rewrite ${path}: it is not valid JSON (add --force to overwrite).`,
|
|
564
|
+
opts
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
existing = null;
|
|
568
|
+
}
|
|
569
|
+
if (existing !== null && !isNoirEmitted(existing.mcpServers) && opts.force !== true) {
|
|
570
|
+
fail(
|
|
571
|
+
EXIT.ERROR,
|
|
572
|
+
`Refusing to rewrite ${path}: it does not look like a Noir-emitted config (add --force to overwrite).`,
|
|
573
|
+
opts
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
return existing;
|
|
577
|
+
}
|
|
578
|
+
function isNoirEmitted(mcpServers) {
|
|
579
|
+
const noir = mcpServers?.noir;
|
|
580
|
+
if (!noir) return false;
|
|
581
|
+
if (typeof noir.command === "string") return true;
|
|
582
|
+
return noir.type === "http";
|
|
583
|
+
}
|
|
584
|
+
function writeWorkspaceHttpEntry(root, host, url, projectId, opts) {
|
|
585
|
+
const path = mcpPathFor(root, host);
|
|
586
|
+
const existing = loadExisting(path, opts);
|
|
587
|
+
const next = {
|
|
588
|
+
...existing ?? {},
|
|
589
|
+
mcpServers: {
|
|
590
|
+
...existing?.mcpServers ?? {},
|
|
591
|
+
noir: { type: "http", url: `${url}?p=${projectId}` }
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
atomicWriteFile(path, `${JSON.stringify(next, null, 2)}
|
|
595
|
+
`);
|
|
596
|
+
}
|
|
597
|
+
function writeStdioEntry(root, host, opts) {
|
|
598
|
+
const path = mcpPathFor(root, host);
|
|
599
|
+
const existing = loadExisting(path, opts);
|
|
600
|
+
const noir = { command: resolveNoirCommand(), args: ["mcp", "serve", "--stdio"] };
|
|
601
|
+
const next = { ...existing ?? {}, mcpServers: { ...existing?.mcpServers ?? {}, noir } };
|
|
602
|
+
atomicWriteFile(path, `${JSON.stringify(next, null, 2)}
|
|
603
|
+
`);
|
|
604
|
+
}
|
|
605
|
+
var init_workspace_mcp = __esm({
|
|
606
|
+
"src/workspace-mcp.ts"() {
|
|
607
|
+
"use strict";
|
|
608
|
+
init_output();
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
// src/commands/workspace.ts
|
|
613
|
+
import { readdirSync } from "fs";
|
|
614
|
+
import {
|
|
615
|
+
clearWorkspaceMarker,
|
|
616
|
+
ensureWorkspaceRegistry,
|
|
617
|
+
isValidWorkspaceName,
|
|
618
|
+
isWorkspaceMember,
|
|
619
|
+
loadProjectInfo,
|
|
620
|
+
readWorkspaceMarker,
|
|
621
|
+
readWorkspaceRegistry,
|
|
622
|
+
removeWorkspaceMember,
|
|
623
|
+
upsertWorkspaceMember,
|
|
624
|
+
workspaceHomeDir,
|
|
625
|
+
writeWorkspaceMarker
|
|
626
|
+
} from "@noir-ai/core";
|
|
627
|
+
import {
|
|
628
|
+
clearWorkspaceDaemonRecord,
|
|
629
|
+
ensureWorkspaceDaemonRunning,
|
|
630
|
+
pidAlive,
|
|
631
|
+
readWorkspaceDaemonRecord,
|
|
632
|
+
spawnDetachedWorkspaceDaemon
|
|
633
|
+
} from "@noir-ai/daemon";
|
|
634
|
+
function loadProjectOrFail(opts) {
|
|
635
|
+
try {
|
|
636
|
+
return loadProjectInfo(process.cwd());
|
|
637
|
+
} catch {
|
|
638
|
+
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
function finishJoin(project, name, url, opts) {
|
|
642
|
+
writeWorkspaceMarker(project.root, name);
|
|
643
|
+
writeWorkspaceHttpEntry(project.root, project.config.host, url, project.id, opts);
|
|
644
|
+
}
|
|
645
|
+
async function daemonStartWorkspace(opts) {
|
|
646
|
+
const project = loadProjectOrFail(opts);
|
|
647
|
+
if (!isValidWorkspaceName(opts.name)) {
|
|
648
|
+
fail(
|
|
649
|
+
EXIT.USAGE,
|
|
650
|
+
`invalid workspace name ${JSON.stringify(opts.name)} (lowercase letters, digits, and dashes).`,
|
|
651
|
+
opts
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
upsertWorkspaceMember(ensureWorkspaceRegistry(opts.name), {
|
|
655
|
+
projectId: project.id,
|
|
656
|
+
root: project.root,
|
|
657
|
+
joinedAt: Date.now()
|
|
658
|
+
});
|
|
659
|
+
if (opts.detachChild === true) {
|
|
660
|
+
const ensured2 = await ensureWorkspaceDaemonRunning({
|
|
661
|
+
name: opts.name,
|
|
662
|
+
project,
|
|
663
|
+
idleTimeoutSec: project.config.workspace.idleTimeoutSec
|
|
664
|
+
});
|
|
665
|
+
if (opts.json === true) {
|
|
666
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: { mode: "detached" } })}
|
|
667
|
+
`);
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
info("noir workspace daemon: detached foreground. Ctrl+C to stop.", opts);
|
|
671
|
+
log(`workspace daemon listening at ${ensured2.url}`, opts);
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
if (opts.detach === true) {
|
|
675
|
+
const spawned = await spawnDetachedWorkspaceDaemon({ name: opts.name, project });
|
|
676
|
+
finishJoin(project, opts.name, `http://127.0.0.1:${spawned.port}/mcp`, opts);
|
|
677
|
+
if (opts.json === true) {
|
|
678
|
+
process.stdout.write(
|
|
679
|
+
`${JSON.stringify({ ok: true, data: { mode: "detached", pid: spawned.pid, port: spawned.port } })}
|
|
680
|
+
`
|
|
681
|
+
);
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
info(
|
|
685
|
+
`workspace "${opts.name}" daemon started (pid ${spawned.pid}, port ${spawned.port}).`,
|
|
686
|
+
opts
|
|
687
|
+
);
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
const ensured = await ensureWorkspaceDaemonRunning({
|
|
691
|
+
name: opts.name,
|
|
692
|
+
project,
|
|
693
|
+
idleTimeoutSec: project.config.workspace.idleTimeoutSec
|
|
694
|
+
});
|
|
695
|
+
finishJoin(project, opts.name, ensured.url, opts);
|
|
696
|
+
if (opts.json === true) {
|
|
697
|
+
process.stdout.write(
|
|
698
|
+
`${JSON.stringify({ ok: true, data: { url: ensured.url, port: ensured.port, reused: !ensured.started } })}
|
|
699
|
+
`
|
|
700
|
+
);
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
info("noir workspace daemon: foreground mode. Ctrl+C to stop.", opts);
|
|
704
|
+
log(`workspace daemon listening at ${ensured.url}`, opts);
|
|
705
|
+
}
|
|
706
|
+
async function daemonJoin(opts) {
|
|
707
|
+
const project = loadProjectOrFail(opts);
|
|
708
|
+
if (!isValidWorkspaceName(opts.name)) {
|
|
709
|
+
fail(EXIT.USAGE, `invalid workspace name ${JSON.stringify(opts.name)}.`, opts);
|
|
710
|
+
}
|
|
711
|
+
const reg = readWorkspaceRegistry(opts.name);
|
|
712
|
+
if (!reg) {
|
|
713
|
+
fail(
|
|
714
|
+
EXIT.ERROR,
|
|
715
|
+
`workspace ${JSON.stringify(opts.name)} not found \u2014 start it from a member repo with \`noir daemon start --workspace ${opts.name}\`.`,
|
|
716
|
+
opts
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
const ensured = await ensureWorkspaceDaemonRunning({
|
|
720
|
+
name: opts.name,
|
|
721
|
+
project,
|
|
722
|
+
idleTimeoutSec: project.config.workspace.idleTimeoutSec
|
|
723
|
+
});
|
|
724
|
+
upsertWorkspaceMember(reg, { projectId: project.id, root: project.root, joinedAt: Date.now() });
|
|
725
|
+
finishJoin(project, opts.name, ensured.url, opts);
|
|
726
|
+
if (opts.json === true) {
|
|
727
|
+
process.stdout.write(
|
|
728
|
+
`${JSON.stringify({ ok: true, data: { joined: true, url: ensured.url, port: ensured.port } })}
|
|
729
|
+
`
|
|
730
|
+
);
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
info(`joined workspace ${JSON.stringify(opts.name)} (${ensured.url}).`, opts);
|
|
734
|
+
}
|
|
735
|
+
async function workspaceList(opts) {
|
|
736
|
+
let names = [];
|
|
737
|
+
try {
|
|
738
|
+
names = readdirSync(workspaceHomeDir(), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
739
|
+
} catch {
|
|
740
|
+
names = [];
|
|
741
|
+
}
|
|
742
|
+
const rows = names.map((n) => {
|
|
743
|
+
const reg = readWorkspaceRegistry(n);
|
|
744
|
+
return {
|
|
745
|
+
name: n,
|
|
746
|
+
members: reg?.members.length ?? 0,
|
|
747
|
+
// A record file whose pid is dead (daemon killed without cleanup) must not
|
|
748
|
+
// read as "running" — mirror the pidAlive liveness `workspace status` uses.
|
|
749
|
+
running: (() => {
|
|
750
|
+
const rec = readWorkspaceDaemonRecord(n);
|
|
751
|
+
return rec !== null && pidAlive(rec.pid);
|
|
752
|
+
})()
|
|
753
|
+
};
|
|
754
|
+
});
|
|
755
|
+
if (opts.json === true) {
|
|
756
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: { workspaces: rows } })}
|
|
757
|
+
`);
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
if (rows.length === 0) {
|
|
761
|
+
info("No workspaces yet.", opts);
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
for (const r of rows) {
|
|
765
|
+
log(`${r.name} \u2014 ${r.members} member(s)${r.running ? ", running" : ""}`, opts);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
async function workspaceStatus(opts) {
|
|
769
|
+
const name = opts.name ?? readWorkspaceMarker(process.cwd()) ?? void 0;
|
|
770
|
+
if (name === void 0) {
|
|
771
|
+
fail(EXIT.USAGE, "workspace status requires a name (or run from a joined repo).", opts);
|
|
772
|
+
}
|
|
773
|
+
if (!isValidWorkspaceName(name)) {
|
|
774
|
+
fail(EXIT.USAGE, `invalid workspace name ${JSON.stringify(name)}.`, opts);
|
|
775
|
+
}
|
|
776
|
+
const reg = readWorkspaceRegistry(name);
|
|
777
|
+
const rec = readWorkspaceDaemonRecord(name);
|
|
778
|
+
const running = rec !== null && pidAlive(rec.pid);
|
|
779
|
+
const data = {
|
|
780
|
+
name,
|
|
781
|
+
members: reg?.members.map((m) => m.projectId) ?? [],
|
|
782
|
+
running,
|
|
783
|
+
...rec ? { pid: rec.pid, port: rec.port } : {}
|
|
784
|
+
};
|
|
785
|
+
if (opts.json === true) {
|
|
786
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data })}
|
|
787
|
+
`);
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
log(
|
|
791
|
+
`workspace ${name}: ${running ? `running (pid ${rec?.pid}, port ${rec?.port})` : "not running"}`,
|
|
792
|
+
opts
|
|
793
|
+
);
|
|
794
|
+
for (const m of data.members) log(` member: ${m}`, opts);
|
|
795
|
+
}
|
|
796
|
+
async function workspaceLeave(opts) {
|
|
797
|
+
const project = loadProjectOrFail(opts);
|
|
798
|
+
const name = readWorkspaceMarker(project.root);
|
|
799
|
+
if (name === null) {
|
|
800
|
+
fail(EXIT.USAGE, "this repo is not joined to a workspace.", opts);
|
|
801
|
+
}
|
|
802
|
+
writeStdioEntry(project.root, project.config.host, opts);
|
|
803
|
+
const reg = readWorkspaceRegistry(name);
|
|
804
|
+
if (reg && isWorkspaceMember(reg, project.id)) {
|
|
805
|
+
removeWorkspaceMember(reg, project.id);
|
|
806
|
+
}
|
|
807
|
+
clearWorkspaceMarker(project.root);
|
|
808
|
+
if (opts.json === true) {
|
|
809
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: { left: name } })}
|
|
810
|
+
`);
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
info(`left workspace ${JSON.stringify(name)}.`, opts);
|
|
814
|
+
}
|
|
815
|
+
async function workspaceStop(opts) {
|
|
816
|
+
const name = opts.name ?? readWorkspaceMarker(process.cwd()) ?? void 0;
|
|
817
|
+
if (name === void 0) {
|
|
818
|
+
fail(EXIT.USAGE, "workspace stop requires a name (or run from a joined repo).", opts);
|
|
819
|
+
}
|
|
820
|
+
if (!isValidWorkspaceName(name)) {
|
|
821
|
+
fail(EXIT.USAGE, `invalid workspace name ${JSON.stringify(name)}.`, opts);
|
|
822
|
+
}
|
|
823
|
+
const rec = readWorkspaceDaemonRecord(name);
|
|
824
|
+
if (rec === null || !pidAlive(rec.pid)) {
|
|
825
|
+
if (opts.json === true) {
|
|
826
|
+
process.stdout.write(
|
|
827
|
+
`${JSON.stringify({ ok: true, data: { running: false, stopped: false } })}
|
|
828
|
+
`
|
|
829
|
+
);
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
info("workspace daemon is not running.", opts);
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
try {
|
|
836
|
+
process.kill(rec.pid, "SIGTERM");
|
|
837
|
+
} catch {
|
|
838
|
+
}
|
|
839
|
+
clearWorkspaceDaemonRecord(name);
|
|
840
|
+
if (opts.json === true) {
|
|
841
|
+
process.stdout.write(
|
|
842
|
+
`${JSON.stringify({ ok: true, data: { running: false, stopped: true, pid: rec.pid } })}
|
|
843
|
+
`
|
|
844
|
+
);
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
info(`stopped workspace daemon (pid ${rec.pid}).`, opts);
|
|
848
|
+
}
|
|
849
|
+
var init_workspace = __esm({
|
|
850
|
+
"src/commands/workspace.ts"() {
|
|
851
|
+
"use strict";
|
|
852
|
+
init_output();
|
|
853
|
+
init_workspace_mcp();
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
|
|
543
857
|
// src/commands/daemon.ts
|
|
544
|
-
import { loadProjectInfo } from "@noir-ai/core";
|
|
858
|
+
import { loadProjectInfo as loadProjectInfo2 } from "@noir-ai/core";
|
|
545
859
|
import {
|
|
546
860
|
clearDaemonRecord,
|
|
547
861
|
ensureDaemonRunning,
|
|
548
|
-
pidAlive,
|
|
862
|
+
pidAlive as pidAlive2,
|
|
549
863
|
readDaemonRecord,
|
|
550
864
|
spawnDetachedDaemon
|
|
551
865
|
} from "@noir-ai/daemon";
|
|
@@ -557,7 +871,7 @@ function formatUptime(sec) {
|
|
|
557
871
|
}
|
|
558
872
|
function resolveCallerProjectId() {
|
|
559
873
|
try {
|
|
560
|
-
return
|
|
874
|
+
return loadProjectInfo2(process.cwd()).id;
|
|
561
875
|
} catch {
|
|
562
876
|
return void 0;
|
|
563
877
|
}
|
|
@@ -578,9 +892,18 @@ async function isHealthy(port, expectedPid, expectedProjectId) {
|
|
|
578
892
|
}
|
|
579
893
|
}
|
|
580
894
|
async function daemonStart(opts) {
|
|
895
|
+
if (opts.workspace !== void 0) {
|
|
896
|
+
await daemonStartWorkspace({
|
|
897
|
+
...opts,
|
|
898
|
+
name: opts.workspace,
|
|
899
|
+
...opts.detach === true ? { detach: true } : {},
|
|
900
|
+
...opts.detachChild === true ? { detachChild: true } : {}
|
|
901
|
+
});
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
581
904
|
let project;
|
|
582
905
|
try {
|
|
583
|
-
project =
|
|
906
|
+
project = loadProjectInfo2(process.cwd());
|
|
584
907
|
} catch {
|
|
585
908
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
586
909
|
}
|
|
@@ -611,7 +934,7 @@ async function daemonStart(opts) {
|
|
|
611
934
|
}
|
|
612
935
|
if (opts.detach === true) {
|
|
613
936
|
const existing = readDaemonRecord();
|
|
614
|
-
if (existing &&
|
|
937
|
+
if (existing && pidAlive2(existing.pid) && await isHealthy(existing.port, existing.pid, project.id)) {
|
|
615
938
|
if (opts.json === true) {
|
|
616
939
|
process.stdout.write(
|
|
617
940
|
`${JSON.stringify({ ok: true, data: { mode: "detached", reused: true } })}
|
|
@@ -710,7 +1033,7 @@ async function daemonStop(opts) {
|
|
|
710
1033
|
let errMsg2;
|
|
711
1034
|
const callerProject = resolveCallerProjectId();
|
|
712
1035
|
const wrongProject = rec.projectId !== void 0 && callerProject !== void 0 && rec.projectId !== callerProject;
|
|
713
|
-
const healthy = !wrongProject &&
|
|
1036
|
+
const healthy = !wrongProject && pidAlive2(rec.pid) && await isHealthy(rec.port, rec.pid, rec.projectId === void 0 ? void 0 : callerProject);
|
|
714
1037
|
if (healthy) {
|
|
715
1038
|
try {
|
|
716
1039
|
process.kill(rec.pid, "SIGTERM");
|
|
@@ -756,7 +1079,7 @@ async function daemonStatus(opts) {
|
|
|
756
1079
|
opts
|
|
757
1080
|
);
|
|
758
1081
|
}
|
|
759
|
-
if (!
|
|
1082
|
+
if (!pidAlive2(rec.pid)) {
|
|
760
1083
|
clearDaemonRecord();
|
|
761
1084
|
fail(EXIT.DAEMON_DOWN, "Noir daemon is not running (stale record removed).", opts);
|
|
762
1085
|
}
|
|
@@ -814,6 +1137,7 @@ var init_daemon = __esm({
|
|
|
814
1137
|
"use strict";
|
|
815
1138
|
init_daemon_client();
|
|
816
1139
|
init_output();
|
|
1140
|
+
init_workspace();
|
|
817
1141
|
MODE = "foreground";
|
|
818
1142
|
}
|
|
819
1143
|
});
|
|
@@ -826,13 +1150,13 @@ __export(install_exports, {
|
|
|
826
1150
|
installManagedNode: () => installManagedNode
|
|
827
1151
|
});
|
|
828
1152
|
import { chmodSync, mkdirSync } from "fs";
|
|
829
|
-
import { join } from "path";
|
|
1153
|
+
import { join as join2 } from "path";
|
|
830
1154
|
import {
|
|
831
|
-
atomicWriteFile,
|
|
1155
|
+
atomicWriteFile as atomicWriteFile2,
|
|
832
1156
|
detectActiveMethod,
|
|
833
1157
|
detectInstallMethods,
|
|
834
1158
|
ensureShimExecutable,
|
|
835
|
-
loadProjectInfo as
|
|
1159
|
+
loadProjectInfo as loadProjectInfo3,
|
|
836
1160
|
MANAGED_NODE_VERSION,
|
|
837
1161
|
NOIR_VERSION,
|
|
838
1162
|
noirHome,
|
|
@@ -878,8 +1202,8 @@ function buildMigrationPlan(opts) {
|
|
|
878
1202
|
async function installManagedNode(opts) {
|
|
879
1203
|
const spec = opts.version ?? opts.channel ?? "latest";
|
|
880
1204
|
const home2 = noirHome();
|
|
881
|
-
const cliDir =
|
|
882
|
-
const binDir =
|
|
1205
|
+
const cliDir = join2(home2, "cli");
|
|
1206
|
+
const binDir = join2(home2, "bin");
|
|
883
1207
|
mkdirSync(cliDir, { recursive: true });
|
|
884
1208
|
mkdirSync(binDir, { recursive: true });
|
|
885
1209
|
let runtime;
|
|
@@ -905,17 +1229,17 @@ async function installManagedNode(opts) {
|
|
|
905
1229
|
if (code !== 0) {
|
|
906
1230
|
return { ok: false, version: null, error: `npm install failed: ${stderr.slice(0, 300)}` };
|
|
907
1231
|
}
|
|
908
|
-
const shim =
|
|
1232
|
+
const shim = join2(binDir, "noir");
|
|
909
1233
|
const shimBody = `#!/usr/bin/env bash
|
|
910
|
-
"${nodeBin}" "${
|
|
1234
|
+
"${nodeBin}" "${join2(cliDir, "lib", "node_modules", "@noir-ai", "cli", "dist", "bin.js")}" "$@"
|
|
911
1235
|
`;
|
|
912
|
-
|
|
1236
|
+
atomicWriteFile2(shim, shimBody);
|
|
913
1237
|
chmodSync(shim, 493);
|
|
914
1238
|
ensureShimExecutable();
|
|
915
1239
|
const ver = await runManagerCmd(
|
|
916
1240
|
[
|
|
917
1241
|
nodeBin,
|
|
918
|
-
|
|
1242
|
+
join2(cliDir, "lib", "node_modules", "@noir-ai", "cli", "dist", "bin.js"),
|
|
919
1243
|
"--version"
|
|
920
1244
|
],
|
|
921
1245
|
{ env: opts.env, timeoutMs: 3e4 }
|
|
@@ -971,7 +1295,7 @@ async function install(opts = {}) {
|
|
|
971
1295
|
const installedRecord = readInstallRecord();
|
|
972
1296
|
let minVersion = "1.6.0";
|
|
973
1297
|
try {
|
|
974
|
-
minVersion =
|
|
1298
|
+
minVersion = loadProjectInfo3(process.cwd()).config.update.minVersion;
|
|
975
1299
|
} catch {
|
|
976
1300
|
}
|
|
977
1301
|
const plan = buildMigrationPlan({
|
|
@@ -1048,7 +1372,7 @@ import {
|
|
|
1048
1372
|
detectActiveMethod as detectActiveMethod2,
|
|
1049
1373
|
ensureShimExecutable as ensureShimExecutable2,
|
|
1050
1374
|
fetchLatestVersion,
|
|
1051
|
-
loadProjectInfo as
|
|
1375
|
+
loadProjectInfo as loadProjectInfo4,
|
|
1052
1376
|
readInstallRecord as readInstallRecord2,
|
|
1053
1377
|
readUpdateCache,
|
|
1054
1378
|
runManagerCmd as runManagerCmd2,
|
|
@@ -1128,7 +1452,7 @@ async function update(opts = {}) {
|
|
|
1128
1452
|
fetchSpin.succeed(`Latest: ${latest}`);
|
|
1129
1453
|
let minVersion = DEFAULT_UPDATE_CONFIG.minVersion;
|
|
1130
1454
|
try {
|
|
1131
|
-
minVersion =
|
|
1455
|
+
minVersion = loadProjectInfo4(process.cwd()).config.update.minVersion;
|
|
1132
1456
|
} catch {
|
|
1133
1457
|
}
|
|
1134
1458
|
const target = buildUpdateTarget({
|
|
@@ -1278,7 +1602,7 @@ var init_update = __esm({
|
|
|
1278
1602
|
});
|
|
1279
1603
|
|
|
1280
1604
|
// src/commands/status.ts
|
|
1281
|
-
import { loadProjectInfo as
|
|
1605
|
+
import { loadProjectInfo as loadProjectInfo5, NOIR_VERSION as NOIR_VERSION2 } from "@noir-ai/core";
|
|
1282
1606
|
async function tryTool(caller, name, args) {
|
|
1283
1607
|
try {
|
|
1284
1608
|
return await caller.callTool(name, args);
|
|
@@ -1399,7 +1723,7 @@ function renderHuman(p, opts) {
|
|
|
1399
1723
|
async function gatherStatusPayload(opts) {
|
|
1400
1724
|
let project;
|
|
1401
1725
|
try {
|
|
1402
|
-
project =
|
|
1726
|
+
project = loadProjectInfo5(process.cwd());
|
|
1403
1727
|
} catch {
|
|
1404
1728
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
1405
1729
|
}
|
|
@@ -1429,7 +1753,7 @@ async function status(opts) {
|
|
|
1429
1753
|
env: process.env,
|
|
1430
1754
|
configUpdate: (() => {
|
|
1431
1755
|
try {
|
|
1432
|
-
const info2 =
|
|
1756
|
+
const info2 = loadProjectInfo5(process.cwd());
|
|
1433
1757
|
return info2.config.update ?? DEFAULT_UPDATE_CONFIG;
|
|
1434
1758
|
} catch {
|
|
1435
1759
|
return DEFAULT_UPDATE_CONFIG;
|
|
@@ -1458,7 +1782,7 @@ var init_status = __esm({
|
|
|
1458
1782
|
// src/commands/task.ts
|
|
1459
1783
|
import { spawnSync } from "child_process";
|
|
1460
1784
|
import { writeFileSync } from "fs";
|
|
1461
|
-
import { loadProjectInfo as
|
|
1785
|
+
import { loadProjectInfo as loadProjectInfo6, nextArtifactSequence, sha256Hex } from "@noir-ai/core";
|
|
1462
1786
|
import {
|
|
1463
1787
|
PHASES,
|
|
1464
1788
|
TASK_CLASSES,
|
|
@@ -1471,7 +1795,7 @@ function skillFor(phase) {
|
|
|
1471
1795
|
}
|
|
1472
1796
|
function writeDoneArtifacts(opts, taskId) {
|
|
1473
1797
|
try {
|
|
1474
|
-
const project =
|
|
1798
|
+
const project = loadProjectInfo6(process.cwd());
|
|
1475
1799
|
const conflict = { conflictPolicy: "preserve", interactive: false };
|
|
1476
1800
|
writeChangelogStub(project.root, `- ${taskId}: completed (verify gate passed)`, conflict);
|
|
1477
1801
|
const nextN = nextArtifactSequence(project.root, "adr");
|
|
@@ -1794,7 +2118,7 @@ function runCheck(cmd) {
|
|
|
1794
2118
|
async function taskVerify(opts) {
|
|
1795
2119
|
let project;
|
|
1796
2120
|
try {
|
|
1797
|
-
project =
|
|
2121
|
+
project = loadProjectInfo6(process.cwd());
|
|
1798
2122
|
} catch {
|
|
1799
2123
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
1800
2124
|
}
|
|
@@ -2000,13 +2324,13 @@ var init_task = __esm({
|
|
|
2000
2324
|
|
|
2001
2325
|
// src/commands/handoff.ts
|
|
2002
2326
|
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
2003
|
-
import { join as
|
|
2327
|
+
import { join as join3 } from "path";
|
|
2004
2328
|
import {
|
|
2005
2329
|
defaultHandoffBlock,
|
|
2006
2330
|
hostLaunchDirective,
|
|
2007
|
-
resolveAdapter
|
|
2331
|
+
resolveAdapter as resolveAdapter2
|
|
2008
2332
|
} from "@noir-ai/adapters";
|
|
2009
|
-
import { loadProjectInfo as
|
|
2333
|
+
import { loadProjectInfo as loadProjectInfo7, NOIR_DIR, resolveArtifactPath } from "@noir-ai/core";
|
|
2010
2334
|
function heading(name, id) {
|
|
2011
2335
|
return `# Noir handoff \u2014 ${name} (${id})`;
|
|
2012
2336
|
}
|
|
@@ -2102,7 +2426,7 @@ function toHostPayload(p) {
|
|
|
2102
2426
|
}
|
|
2103
2427
|
function resolveDirective(root, p) {
|
|
2104
2428
|
const ctx = { root };
|
|
2105
|
-
const adapter =
|
|
2429
|
+
const adapter = resolveAdapter2(p.host);
|
|
2106
2430
|
const hostPayload = toHostPayload(p);
|
|
2107
2431
|
if (typeof adapter.emitHandoff === "function") {
|
|
2108
2432
|
return adapter.emitHandoff(ctx, hostPayload);
|
|
@@ -2145,7 +2469,7 @@ async function probeOnly(opts) {
|
|
|
2145
2469
|
async function handoff(opts) {
|
|
2146
2470
|
let project;
|
|
2147
2471
|
try {
|
|
2148
|
-
project =
|
|
2472
|
+
project = loadProjectInfo7(process.cwd());
|
|
2149
2473
|
} catch {
|
|
2150
2474
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
2151
2475
|
}
|
|
@@ -2177,7 +2501,7 @@ async function handoff(opts) {
|
|
|
2177
2501
|
if (opts.write === true) {
|
|
2178
2502
|
const md = renderMarkdown(full);
|
|
2179
2503
|
const path = handoffFilePath(project.root, task?.taskId, project.id);
|
|
2180
|
-
mkdirSync2(
|
|
2504
|
+
mkdirSync2(join3(project.root, NOIR_DIR, "handoff"), { recursive: true });
|
|
2181
2505
|
writeFileSync2(path, md, "utf8");
|
|
2182
2506
|
info(`handoff written to ${path}`, opts);
|
|
2183
2507
|
}
|
|
@@ -2509,14 +2833,14 @@ import { hostLaunchDirective as hostLaunchDirective2 } from "@noir-ai/adapters";
|
|
|
2509
2833
|
import {
|
|
2510
2834
|
detectActiveMethod as detectActiveMethod3,
|
|
2511
2835
|
latestVersionFromCache,
|
|
2512
|
-
loadProjectInfo as
|
|
2836
|
+
loadProjectInfo as loadProjectInfo8,
|
|
2513
2837
|
NOIR_VERSION as NOIR_VERSION3,
|
|
2514
2838
|
readInstallRecord as readInstallRecord3,
|
|
2515
2839
|
readUpdateCache as readUpdateCache2
|
|
2516
2840
|
} from "@noir-ai/core";
|
|
2517
2841
|
function tryProject() {
|
|
2518
2842
|
try {
|
|
2519
|
-
const info2 =
|
|
2843
|
+
const info2 = loadProjectInfo8(process.cwd());
|
|
2520
2844
|
return { id: info2.id, name: info2.name, host: info2.config.host };
|
|
2521
2845
|
} catch {
|
|
2522
2846
|
return null;
|
|
@@ -2679,7 +3003,7 @@ async function runMenu(opts, deps) {
|
|
|
2679
3003
|
env: process.env,
|
|
2680
3004
|
configUpdate: (() => {
|
|
2681
3005
|
try {
|
|
2682
|
-
const info2 =
|
|
3006
|
+
const info2 = loadProjectInfo8(process.cwd());
|
|
2683
3007
|
return info2.config.update ?? DEFAULT_UPDATE_CONFIG;
|
|
2684
3008
|
} catch {
|
|
2685
3009
|
return DEFAULT_UPDATE_CONFIG;
|
|
@@ -2741,6 +3065,27 @@ var init_home = __esm({
|
|
|
2741
3065
|
});
|
|
2742
3066
|
|
|
2743
3067
|
// src/commands/memory.ts
|
|
3068
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
3069
|
+
import { loadProjectInfo as loadProjectInfo9 } from "@noir-ai/core";
|
|
3070
|
+
function resolveWorkspace() {
|
|
3071
|
+
const routing = workspaceRoutingTarget(process.cwd());
|
|
3072
|
+
if (routing === null) return null;
|
|
3073
|
+
try {
|
|
3074
|
+
return { routing, project: loadProjectInfo9(process.cwd()) };
|
|
3075
|
+
} catch {
|
|
3076
|
+
return null;
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
async function callMemory(opts, name, args = {}) {
|
|
3080
|
+
const ws = resolveWorkspace();
|
|
3081
|
+
if (ws === null) return callDaemonTool(opts, name, args);
|
|
3082
|
+
return withWorkspaceDaemon(opts, ws.routing, (caller) => caller.callTool(name, args));
|
|
3083
|
+
}
|
|
3084
|
+
async function withMemoryDaemon(opts, fn) {
|
|
3085
|
+
const ws = resolveWorkspace();
|
|
3086
|
+
if (ws === null) return withDaemon(opts, fn);
|
|
3087
|
+
return withWorkspaceDaemon(opts, ws.routing, fn);
|
|
3088
|
+
}
|
|
2744
3089
|
function parseLimit2(raw, label, opts) {
|
|
2745
3090
|
if (raw === void 0) return void 0;
|
|
2746
3091
|
const n = Number(raw);
|
|
@@ -2779,6 +3124,39 @@ function stamp(ts) {
|
|
|
2779
3124
|
}
|
|
2780
3125
|
async function memoryRecall(opts) {
|
|
2781
3126
|
const limit = parseLimit2(opts.limit, "memory recall", opts);
|
|
3127
|
+
const ws = resolveWorkspace();
|
|
3128
|
+
if (ws !== null) {
|
|
3129
|
+
const probe2 = await probeWorkspaceDaemon(ws.routing);
|
|
3130
|
+
if (!probe2.running) {
|
|
3131
|
+
await withWorkspaceMemoryRead(opts, ws.routing, ws.project, async (memory) => {
|
|
3132
|
+
const { hits: hits3, degraded, mode } = await memory.recallWithMeta(opts.query, {
|
|
3133
|
+
...limit === void 0 ? {} : { limit }
|
|
3134
|
+
});
|
|
3135
|
+
const data3 = { query: opts.query, hits: hits3, degraded, mode };
|
|
3136
|
+
if (opts.json === true) {
|
|
3137
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: data3 })}
|
|
3138
|
+
`);
|
|
3139
|
+
return;
|
|
3140
|
+
}
|
|
3141
|
+
renderRecall(data3.query, data3.hits, data3.degraded, opts);
|
|
3142
|
+
});
|
|
3143
|
+
return;
|
|
3144
|
+
}
|
|
3145
|
+
const res2 = await callMemory(opts, "memory_recall", {
|
|
3146
|
+
query: opts.query,
|
|
3147
|
+
...limit === void 0 ? {} : { limit }
|
|
3148
|
+
});
|
|
3149
|
+
if (res2.ok !== true) failTool2("memory recall", res2, opts);
|
|
3150
|
+
const hits2 = Array.isArray(res2.results) ? res2.results.map(toHit2) : [];
|
|
3151
|
+
const data2 = { query: opts.query, hits: hits2, degraded: res2.degraded === true };
|
|
3152
|
+
if (opts.json === true) {
|
|
3153
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: data2 })}
|
|
3154
|
+
`);
|
|
3155
|
+
return;
|
|
3156
|
+
}
|
|
3157
|
+
renderRecall(data2.query, hits2, data2.degraded, opts);
|
|
3158
|
+
return;
|
|
3159
|
+
}
|
|
2782
3160
|
let probe;
|
|
2783
3161
|
try {
|
|
2784
3162
|
probe = await probeDaemon(opts);
|
|
@@ -2840,7 +3218,7 @@ async function memorySave(opts) {
|
|
|
2840
3218
|
const args = { content };
|
|
2841
3219
|
if (typeof opts.type === "string" && opts.type.length > 0) args.type = opts.type;
|
|
2842
3220
|
if (files !== void 0) args.files = files;
|
|
2843
|
-
const res = await
|
|
3221
|
+
const res = await callMemory(opts, "memory_save", args);
|
|
2844
3222
|
if (res.ok !== true) failTool2("memory save", res, opts);
|
|
2845
3223
|
const id = typeof res.id === "string" ? res.id : "";
|
|
2846
3224
|
const observation = res.observation ?? {};
|
|
@@ -2884,7 +3262,98 @@ function renderObservation(obs, opts) {
|
|
|
2884
3262
|
if (typeof content === "string") log(`
|
|
2885
3263
|
${content}`, opts);
|
|
2886
3264
|
}
|
|
3265
|
+
async function memoryCapture(opts) {
|
|
3266
|
+
const content = await resolveCaptureContent(opts);
|
|
3267
|
+
const args = { content };
|
|
3268
|
+
if (typeof opts.eventType === "string" && opts.eventType.length > 0)
|
|
3269
|
+
args.eventType = opts.eventType;
|
|
3270
|
+
const res = await callMemory(opts, "memory_capture", args);
|
|
3271
|
+
if (res.ok !== true) failTool2("memory capture", res, opts);
|
|
3272
|
+
const id = typeof res.id === "string" ? res.id : "";
|
|
3273
|
+
const observation = res.observation ?? {};
|
|
3274
|
+
if (opts.json === true) {
|
|
3275
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: { id, observation } })}
|
|
3276
|
+
`);
|
|
3277
|
+
return;
|
|
3278
|
+
}
|
|
3279
|
+
log(`Captured memory ${id}.`, opts);
|
|
3280
|
+
renderObservation(observation, opts);
|
|
3281
|
+
}
|
|
3282
|
+
async function resolveCaptureContent(opts) {
|
|
3283
|
+
if (typeof opts.content === "string" && opts.content.length > 0) return opts.content;
|
|
3284
|
+
if (typeof opts.file === "string" && opts.file.length > 0) {
|
|
3285
|
+
try {
|
|
3286
|
+
return readFileSync2(opts.file, "utf8");
|
|
3287
|
+
} catch {
|
|
3288
|
+
fail(EXIT.USAGE, `memory capture could not read --file ${opts.file}`, opts);
|
|
3289
|
+
}
|
|
3290
|
+
}
|
|
3291
|
+
if (!process.stdin.isTTY) {
|
|
3292
|
+
try {
|
|
3293
|
+
return readFileSync2(0, "utf8");
|
|
3294
|
+
} catch {
|
|
3295
|
+
fail(
|
|
3296
|
+
EXIT.USAGE,
|
|
3297
|
+
"memory capture could not read piped stdin \u2014 pass --content <text> or --file <path>.",
|
|
3298
|
+
opts
|
|
3299
|
+
);
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
if (!isInteractive(opts)) {
|
|
3303
|
+
fail(
|
|
3304
|
+
EXIT.USAGE,
|
|
3305
|
+
"memory capture requires --content <text> or --file <path> (or piped stdin).",
|
|
3306
|
+
opts
|
|
3307
|
+
);
|
|
3308
|
+
}
|
|
3309
|
+
const clack = await import("@clack/prompts");
|
|
3310
|
+
const value = await clack.text({ message: "Captured memory content:" });
|
|
3311
|
+
if (clack.isCancel(value)) {
|
|
3312
|
+
clack.cancel("Cancelled.");
|
|
3313
|
+
fail(EXIT.CANCELLED, "cancelled", opts);
|
|
3314
|
+
}
|
|
3315
|
+
return String(value);
|
|
3316
|
+
}
|
|
2887
3317
|
async function memorySessions(opts) {
|
|
3318
|
+
const ws = resolveWorkspace();
|
|
3319
|
+
if (ws !== null) {
|
|
3320
|
+
const probe2 = await probeWorkspaceDaemon(ws.routing);
|
|
3321
|
+
if (!probe2.running) {
|
|
3322
|
+
await withWorkspaceMemoryRead(opts, ws.routing, ws.project, async (memory) => {
|
|
3323
|
+
const sessions3 = memory.sessions().map((s) => ({
|
|
3324
|
+
id: s.id,
|
|
3325
|
+
count: s.count,
|
|
3326
|
+
lastTs: s.lastTs
|
|
3327
|
+
}));
|
|
3328
|
+
const data3 = { sessions: sessions3 };
|
|
3329
|
+
if (opts.json === true) {
|
|
3330
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: data3 })}
|
|
3331
|
+
`);
|
|
3332
|
+
return;
|
|
3333
|
+
}
|
|
3334
|
+
renderSessions(sessions3, opts);
|
|
3335
|
+
});
|
|
3336
|
+
return;
|
|
3337
|
+
}
|
|
3338
|
+
const res2 = await callMemory(opts, "memory_sessions");
|
|
3339
|
+
if (res2.ok !== true) failTool2("memory sessions", res2, opts);
|
|
3340
|
+
const sessions2 = Array.isArray(res2.sessions) ? res2.sessions.map((raw) => {
|
|
3341
|
+
const s = raw ?? {};
|
|
3342
|
+
return {
|
|
3343
|
+
id: typeof s.id === "string" ? s.id : "",
|
|
3344
|
+
count: typeof s.count === "number" ? s.count : 0,
|
|
3345
|
+
lastTs: typeof s.lastTs === "number" ? s.lastTs : 0
|
|
3346
|
+
};
|
|
3347
|
+
}) : [];
|
|
3348
|
+
const data2 = { sessions: sessions2 };
|
|
3349
|
+
if (opts.json === true) {
|
|
3350
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: data2 })}
|
|
3351
|
+
`);
|
|
3352
|
+
return;
|
|
3353
|
+
}
|
|
3354
|
+
renderSessions(sessions2, opts);
|
|
3355
|
+
return;
|
|
3356
|
+
}
|
|
2888
3357
|
let probe;
|
|
2889
3358
|
try {
|
|
2890
3359
|
probe = await probeDaemon(opts);
|
|
@@ -2939,7 +3408,7 @@ async function memoryForget(opts) {
|
|
|
2939
3408
|
if (opts.ids.length === 0) {
|
|
2940
3409
|
fail(EXIT.USAGE, "memory forget requires at least one <id>", opts);
|
|
2941
3410
|
}
|
|
2942
|
-
const res = await
|
|
3411
|
+
const res = await callMemory(opts, "memory_forget", {
|
|
2943
3412
|
ids: opts.ids
|
|
2944
3413
|
});
|
|
2945
3414
|
if (res.ok !== true) failTool2("memory forget", res, opts);
|
|
@@ -2953,11 +3422,11 @@ async function memoryForget(opts) {
|
|
|
2953
3422
|
log(`Forgot ${deleted} observation${deleted === 1 ? "" : "s"}.`, opts);
|
|
2954
3423
|
}
|
|
2955
3424
|
async function memoryConsolidate(opts) {
|
|
2956
|
-
const exposed = await
|
|
3425
|
+
const exposed = await withMemoryDaemon(opts, async (caller) => caller.listTools());
|
|
2957
3426
|
if (!exposed.includes("memory_consolidate")) {
|
|
2958
3427
|
fail(
|
|
2959
3428
|
EXIT.ERROR,
|
|
2960
|
-
"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.",
|
|
3429
|
+
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.",
|
|
2961
3430
|
opts
|
|
2962
3431
|
);
|
|
2963
3432
|
}
|
|
@@ -2966,7 +3435,7 @@ async function memoryConsolidate(opts) {
|
|
|
2966
3435
|
const args = {};
|
|
2967
3436
|
if (types !== void 0) args.types = types;
|
|
2968
3437
|
if (limit !== void 0) args.limit = limit;
|
|
2969
|
-
const res = await
|
|
3438
|
+
const res = await callMemory(
|
|
2970
3439
|
opts,
|
|
2971
3440
|
"memory_consolidate",
|
|
2972
3441
|
args
|
|
@@ -3485,10 +3954,10 @@ var init_orchestrator = __esm({
|
|
|
3485
3954
|
});
|
|
3486
3955
|
|
|
3487
3956
|
// src/run-profiles.ts
|
|
3488
|
-
import { loadProjectInfo as
|
|
3957
|
+
import { loadProjectInfo as loadProjectInfo10 } from "@noir-ai/core";
|
|
3489
3958
|
function loadRunConfig(root) {
|
|
3490
3959
|
try {
|
|
3491
|
-
return
|
|
3960
|
+
return loadProjectInfo10(root).config;
|
|
3492
3961
|
} catch {
|
|
3493
3962
|
return null;
|
|
3494
3963
|
}
|
|
@@ -3542,7 +4011,7 @@ var init_run_profiles = __esm({
|
|
|
3542
4011
|
|
|
3543
4012
|
// src/commands/run.ts
|
|
3544
4013
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
3545
|
-
import { join as
|
|
4014
|
+
import { join as join4 } from "path";
|
|
3546
4015
|
import { SUPPORTED_HOSTS } from "@noir-ai/adapters";
|
|
3547
4016
|
import { parseConfig } from "@noir-ai/core";
|
|
3548
4017
|
function formatUsage(u) {
|
|
@@ -3636,8 +4105,8 @@ function mergeEnv(base, overlay) {
|
|
|
3636
4105
|
}
|
|
3637
4106
|
function writeTranscript(host, lines) {
|
|
3638
4107
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3639
|
-
const dir =
|
|
3640
|
-
const file =
|
|
4108
|
+
const dir = join4(process.cwd(), ".noir", "transcripts");
|
|
4109
|
+
const file = join4(dir, `${host}-${ts}.jsonl`);
|
|
3641
4110
|
try {
|
|
3642
4111
|
mkdirSync3(dir, { recursive: true, mode: 448 });
|
|
3643
4112
|
writeFileSync3(file, `${lines.join("\n")}${lines.length > 0 ? "\n" : ""}`, { mode: 384 });
|
|
@@ -3656,8 +4125,8 @@ var init_run = __esm({
|
|
|
3656
4125
|
});
|
|
3657
4126
|
|
|
3658
4127
|
// src/commands/skills.ts
|
|
3659
|
-
import { resolveAdapter as
|
|
3660
|
-
import { loadProjectInfo as
|
|
4128
|
+
import { resolveAdapter as resolveAdapter3 } from "@noir-ai/adapters";
|
|
4129
|
+
import { loadProjectInfo as loadProjectInfo11 } from "@noir-ai/core";
|
|
3661
4130
|
import {
|
|
3662
4131
|
buildRegistry,
|
|
3663
4132
|
discoverAll,
|
|
@@ -3725,11 +4194,11 @@ async function skillsSync(opts) {
|
|
|
3725
4194
|
const root = process.cwd();
|
|
3726
4195
|
let project;
|
|
3727
4196
|
try {
|
|
3728
|
-
project =
|
|
4197
|
+
project = loadProjectInfo11(root);
|
|
3729
4198
|
} catch {
|
|
3730
4199
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
3731
4200
|
}
|
|
3732
|
-
const adapter =
|
|
4201
|
+
const adapter = resolveAdapter3(project.config.host);
|
|
3733
4202
|
if (!adapter.skillsDir) {
|
|
3734
4203
|
const data2 = {
|
|
3735
4204
|
emitted: [],
|
|
@@ -4001,8 +4470,8 @@ var init_conflict = __esm({
|
|
|
4001
4470
|
});
|
|
4002
4471
|
|
|
4003
4472
|
// src/dedup-write.ts
|
|
4004
|
-
import { mkdirSync as mkdirSync4, readFileSync, rmSync, writeFileSync as writeFileSync4 } from "fs";
|
|
4005
|
-
import { join as
|
|
4473
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync4 } from "fs";
|
|
4474
|
+
import { join as join5 } from "path";
|
|
4006
4475
|
import { sha256Hex as sha256Hex2, sha256Hex12 } from "@noir-ai/core";
|
|
4007
4476
|
async function checkWritePathDedup(root, res, opts) {
|
|
4008
4477
|
const result = { found: [], conflicts: [] };
|
|
@@ -4013,10 +4482,10 @@ async function checkWritePathDedup(root, res, opts) {
|
|
|
4013
4482
|
if (embed === void 0) return result;
|
|
4014
4483
|
const cachedEmbed = makeCachedEmbed(cache, embed);
|
|
4015
4484
|
for (const proposedRel of writtenHostFiles) {
|
|
4016
|
-
const proposedAbs =
|
|
4485
|
+
const proposedAbs = join5(root, proposedRel);
|
|
4017
4486
|
let proposedText;
|
|
4018
4487
|
try {
|
|
4019
|
-
proposedText =
|
|
4488
|
+
proposedText = readFileSync3(proposedAbs, "utf8");
|
|
4020
4489
|
} catch {
|
|
4021
4490
|
continue;
|
|
4022
4491
|
}
|
|
@@ -4060,7 +4529,7 @@ async function checkWritePathDedup(root, res, opts) {
|
|
|
4060
4529
|
`
|
|
4061
4530
|
);
|
|
4062
4531
|
}
|
|
4063
|
-
const matchedText = safeRead(
|
|
4532
|
+
const matchedText = safeRead(join5(root, matchedRel));
|
|
4064
4533
|
result.conflicts.push({
|
|
4065
4534
|
path: proposedRel,
|
|
4066
4535
|
mode: "artifact",
|
|
@@ -4144,21 +4613,21 @@ async function promptDedupAction(root, proposedRel, matchedRel, proposedText, si
|
|
|
4144
4613
|
if (clack.isCancel(choice)) return "preserve";
|
|
4145
4614
|
if (choice === "skip") {
|
|
4146
4615
|
try {
|
|
4147
|
-
rmSync(
|
|
4616
|
+
rmSync(join5(root, proposedRel), { force: true });
|
|
4148
4617
|
} catch {
|
|
4149
4618
|
}
|
|
4150
4619
|
return "preserve";
|
|
4151
4620
|
} else if (choice === "replace") {
|
|
4152
4621
|
try {
|
|
4153
|
-
writeFileSync4(
|
|
4622
|
+
writeFileSync4(join5(root, matchedRel), proposedText, "utf8");
|
|
4154
4623
|
} catch {
|
|
4155
4624
|
}
|
|
4156
4625
|
return "replace";
|
|
4157
4626
|
} else if (choice === "mirror") {
|
|
4158
4627
|
try {
|
|
4159
|
-
const existing =
|
|
4628
|
+
const existing = readFileSync3(join5(root, matchedRel), "utf8");
|
|
4160
4629
|
writeFileSync4(
|
|
4161
|
-
|
|
4630
|
+
join5(root, matchedRel),
|
|
4162
4631
|
`${existing}
|
|
4163
4632
|
|
|
4164
4633
|
<!-- mirrored from ${proposedRel} -->
|
|
@@ -4176,11 +4645,11 @@ function emptyCache() {
|
|
|
4176
4645
|
return { version: DEDUP_CACHE_VERSION, model: "", entries: {} };
|
|
4177
4646
|
}
|
|
4178
4647
|
function cachePath(root) {
|
|
4179
|
-
return
|
|
4648
|
+
return join5(root, ".noir", "dedup-cache.json");
|
|
4180
4649
|
}
|
|
4181
4650
|
function loadCache(root) {
|
|
4182
4651
|
try {
|
|
4183
|
-
const raw =
|
|
4652
|
+
const raw = readFileSync3(cachePath(root), "utf8");
|
|
4184
4653
|
const parsed = JSON.parse(raw);
|
|
4185
4654
|
if (parsed.version !== DEDUP_CACHE_VERSION) return emptyCache();
|
|
4186
4655
|
if (typeof parsed.entries !== "object" || parsed.entries === null) return emptyCache();
|
|
@@ -4195,7 +4664,7 @@ function loadCache(root) {
|
|
|
4195
4664
|
}
|
|
4196
4665
|
function saveCache(root, cache) {
|
|
4197
4666
|
try {
|
|
4198
|
-
mkdirSync4(
|
|
4667
|
+
mkdirSync4(join5(root, ".noir"), { recursive: true });
|
|
4199
4668
|
writeFileSync4(cachePath(root), JSON.stringify(cache), "utf8");
|
|
4200
4669
|
} catch {
|
|
4201
4670
|
}
|
|
@@ -4212,7 +4681,7 @@ function makeCachedEmbed(cache, embed) {
|
|
|
4212
4681
|
}
|
|
4213
4682
|
function safeRead(abs) {
|
|
4214
4683
|
try {
|
|
4215
|
-
return
|
|
4684
|
+
return readFileSync3(abs, "utf8");
|
|
4216
4685
|
} catch {
|
|
4217
4686
|
return void 0;
|
|
4218
4687
|
}
|
|
@@ -4250,8 +4719,8 @@ var init_dedup_write = __esm({
|
|
|
4250
4719
|
});
|
|
4251
4720
|
|
|
4252
4721
|
// src/init.ts
|
|
4253
|
-
import { resolveAdapter as
|
|
4254
|
-
import { loadProjectInfo as
|
|
4722
|
+
import { resolveAdapter as resolveAdapter4 } from "@noir-ai/adapters";
|
|
4723
|
+
import { loadProjectInfo as loadProjectInfo12 } from "@noir-ai/core";
|
|
4255
4724
|
import { scaffold } from "@noir-ai/create";
|
|
4256
4725
|
import { emitSkillsToDir as emitSkillsToDir2 } from "@noir-ai/skills";
|
|
4257
4726
|
async function init(root, opts) {
|
|
@@ -4280,7 +4749,7 @@ async function init(root, opts) {
|
|
|
4280
4749
|
await emitHostSkills(root, host, conflictOpts, interactive);
|
|
4281
4750
|
let projectInfo;
|
|
4282
4751
|
try {
|
|
4283
|
-
projectInfo =
|
|
4752
|
+
projectInfo = loadProjectInfo12(root);
|
|
4284
4753
|
} catch {
|
|
4285
4754
|
projectInfo = void 0;
|
|
4286
4755
|
}
|
|
@@ -4311,7 +4780,7 @@ function reportPlannedWrites(res) {
|
|
|
4311
4780
|
}
|
|
4312
4781
|
}
|
|
4313
4782
|
async function emitHostSkills(root, host, conflictOpts, interactive) {
|
|
4314
|
-
const adapter =
|
|
4783
|
+
const adapter = resolveAdapter4(host);
|
|
4315
4784
|
const skillsDir = adapter.skillsDir?.({ root });
|
|
4316
4785
|
if (skillsDir === void 0) {
|
|
4317
4786
|
process.stderr.write(`host '${host}' has no skill emitter; skipping skills
|
|
@@ -4367,11 +4836,11 @@ var init_init = __esm({
|
|
|
4367
4836
|
});
|
|
4368
4837
|
|
|
4369
4838
|
// src/serve.ts
|
|
4370
|
-
import { applyNoirEnv, loadProjectInfo as
|
|
4839
|
+
import { applyNoirEnv, loadProjectInfo as loadProjectInfo13 } from "@noir-ai/core";
|
|
4371
4840
|
import { ensureDaemonRunning as ensureDaemonRunning2, startStdioServer } from "@noir-ai/daemon";
|
|
4372
4841
|
async function serve(opts) {
|
|
4373
4842
|
applyNoirEnv(process.cwd());
|
|
4374
|
-
const project =
|
|
4843
|
+
const project = loadProjectInfo13(process.cwd());
|
|
4375
4844
|
if (opts.stdio) {
|
|
4376
4845
|
await startStdioServer({ project, transport: "stdio", daemon: false });
|
|
4377
4846
|
return;
|
|
@@ -4405,8 +4874,8 @@ __export(create_exports, {
|
|
|
4405
4874
|
create: () => create
|
|
4406
4875
|
});
|
|
4407
4876
|
import { resolve } from "path";
|
|
4408
|
-
import { resolveAdapter as
|
|
4409
|
-
import { loadProjectInfo as
|
|
4877
|
+
import { resolveAdapter as resolveAdapter5 } from "@noir-ai/adapters";
|
|
4878
|
+
import { loadProjectInfo as loadProjectInfo14 } from "@noir-ai/core";
|
|
4410
4879
|
import { scaffold as scaffold2 } from "@noir-ai/create";
|
|
4411
4880
|
import { emitSkillsToDir as emitSkillsToDir3 } from "@noir-ai/skills";
|
|
4412
4881
|
async function create(dir, opts) {
|
|
@@ -4432,7 +4901,7 @@ async function create(dir, opts) {
|
|
|
4432
4901
|
return res;
|
|
4433
4902
|
}
|
|
4434
4903
|
if (res.noop) return res;
|
|
4435
|
-
const adapter =
|
|
4904
|
+
const adapter = resolveAdapter5(host);
|
|
4436
4905
|
const skillsDir = adapter.skillsDir?.({ root });
|
|
4437
4906
|
if (skillsDir === void 0) {
|
|
4438
4907
|
process.stderr.write(`host '${host}' has no skill emitter; skipping skills
|
|
@@ -4457,7 +4926,7 @@ async function create(dir, opts) {
|
|
|
4457
4926
|
}
|
|
4458
4927
|
let projectInfo;
|
|
4459
4928
|
try {
|
|
4460
|
-
projectInfo =
|
|
4929
|
+
projectInfo = loadProjectInfo14(root);
|
|
4461
4930
|
} catch {
|
|
4462
4931
|
projectInfo = void 0;
|
|
4463
4932
|
}
|
|
@@ -4483,9 +4952,9 @@ var sync_exports = {};
|
|
|
4483
4952
|
__export(sync_exports, {
|
|
4484
4953
|
sync: () => sync
|
|
4485
4954
|
});
|
|
4486
|
-
import { existsSync } from "fs";
|
|
4487
|
-
import { resolveAdapter as
|
|
4488
|
-
import { loadProjectInfo as
|
|
4955
|
+
import { existsSync as existsSync2 } from "fs";
|
|
4956
|
+
import { resolveAdapter as resolveAdapter6 } from "@noir-ai/adapters";
|
|
4957
|
+
import { loadProjectInfo as loadProjectInfo15, paths } from "@noir-ai/core";
|
|
4489
4958
|
import { scaffold as scaffold3 } from "@noir-ai/create";
|
|
4490
4959
|
import { emitSkillsToDir as emitSkillsToDir4 } from "@noir-ai/skills";
|
|
4491
4960
|
async function sync(root, opts = {}) {
|
|
@@ -4494,7 +4963,7 @@ async function sync(root, opts = {}) {
|
|
|
4494
4963
|
const conflictOpts = buildConflictOpts({ force: opts.force, interactive });
|
|
4495
4964
|
let projectInfo;
|
|
4496
4965
|
try {
|
|
4497
|
-
projectInfo =
|
|
4966
|
+
projectInfo = loadProjectInfo15(root);
|
|
4498
4967
|
} catch {
|
|
4499
4968
|
projectInfo = void 0;
|
|
4500
4969
|
}
|
|
@@ -4516,7 +4985,7 @@ async function sync(root, opts = {}) {
|
|
|
4516
4985
|
if (res.written.length === 0 && res.identical.length > 0) {
|
|
4517
4986
|
process.stderr.write("Nothing to sync (Noir-managed files are up to date).\n");
|
|
4518
4987
|
}
|
|
4519
|
-
const adapter =
|
|
4988
|
+
const adapter = resolveAdapter6(host);
|
|
4520
4989
|
const skillsDir = adapter.skillsDir?.({ root });
|
|
4521
4990
|
if (skillsDir === void 0) {
|
|
4522
4991
|
process.stderr.write(`host '${host}' has no skill emitter; skipping skills
|
|
@@ -4552,9 +5021,9 @@ async function sync(root, opts = {}) {
|
|
|
4552
5021
|
}
|
|
4553
5022
|
function resolveSyncHost(root, opts) {
|
|
4554
5023
|
if (opts.host !== void 0) return opts.host;
|
|
4555
|
-
if (!
|
|
5024
|
+
if (!existsSync2(paths.config(root))) return "claude";
|
|
4556
5025
|
try {
|
|
4557
|
-
return
|
|
5026
|
+
return loadProjectInfo15(root).config.host;
|
|
4558
5027
|
} catch {
|
|
4559
5028
|
return "claude";
|
|
4560
5029
|
}
|
|
@@ -4785,30 +5254,75 @@ function createProgram() {
|
|
|
4785
5254
|
throw new NoirCliError(EXIT.USAGE, "Usage: noir mcp serve [--stdio]");
|
|
4786
5255
|
});
|
|
4787
5256
|
const daemonGrp = program2.command("daemon").description("control the Noir daemon");
|
|
4788
|
-
daemonGrp.command("start").description("start the Noir daemon (foreground, or background with --detach)").option("--detach", "run the daemon in the background and exit").addOption(
|
|
5257
|
+
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(
|
|
4789
5258
|
new Option("--_detached-child", "reserved: detached daemon child (internal)").hideHelp()
|
|
4790
5259
|
).action(async (...args) => {
|
|
4791
5260
|
const cmd = trailingCmd(args);
|
|
4792
5261
|
const g = cmd.optsWithGlobals();
|
|
4793
5262
|
const detach = g.detach === true;
|
|
4794
5263
|
const detachChild = g._detachedChild === true;
|
|
5264
|
+
const workspace = typeof g.workspace === "string" ? g.workspace : void 0;
|
|
4795
5265
|
await daemonStart({
|
|
4796
5266
|
...toCliOptions(g),
|
|
4797
5267
|
...detach ? { detach } : {},
|
|
4798
|
-
...detachChild ? { detachChild } : {}
|
|
5268
|
+
...detachChild ? { detachChild } : {},
|
|
5269
|
+
...workspace !== void 0 ? { workspace } : {},
|
|
5270
|
+
...g.force === true ? { force: true } : {}
|
|
4799
5271
|
});
|
|
4800
5272
|
});
|
|
5273
|
+
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) => {
|
|
5274
|
+
const cmd = trailingCmd(args);
|
|
5275
|
+
const g = cmd.optsWithGlobals();
|
|
5276
|
+
const name = typeof args[0] === "string" ? args[0] : "";
|
|
5277
|
+
await daemonJoin({ ...toCliOptions(g), name, ...g.force === true ? { force: true } : {} });
|
|
5278
|
+
});
|
|
4801
5279
|
daemonGrp.command("stop").description("stop the Noir daemon").action(async (...args) => {
|
|
4802
5280
|
await daemonStop(toCliOptions(trailingCmd(args).optsWithGlobals()));
|
|
4803
5281
|
});
|
|
4804
5282
|
daemonGrp.command("status").description("report daemon pid/uptime/mode (exit 4 if not running)").action(async (...args) => {
|
|
4805
5283
|
await daemonStatus(toCliOptions(trailingCmd(args).optsWithGlobals()));
|
|
4806
5284
|
});
|
|
4807
|
-
daemonGrp.command("restart").description("stop then start the daemon").action(async (...args) => {
|
|
4808
|
-
|
|
5285
|
+
daemonGrp.command("restart").description("stop then start the daemon").option("--detach", "run the daemon in the background and exit").action(async (...args) => {
|
|
5286
|
+
const cmd = trailingCmd(args);
|
|
5287
|
+
const g = cmd.optsWithGlobals();
|
|
5288
|
+
await daemonRestart({
|
|
5289
|
+
...toCliOptions(g),
|
|
5290
|
+
...g.detach === true ? { detach: true } : {}
|
|
5291
|
+
});
|
|
4809
5292
|
});
|
|
4810
5293
|
daemonGrp.action(() => {
|
|
4811
|
-
throw new NoirCliError(EXIT.USAGE, "Usage: noir daemon start|stop|status|restart");
|
|
5294
|
+
throw new NoirCliError(EXIT.USAGE, "Usage: noir daemon start|join|stop|status|restart");
|
|
5295
|
+
});
|
|
5296
|
+
const workspaceGrp = program2.command("workspace").description("shared cross-repo workspaces");
|
|
5297
|
+
workspaceGrp.command("list").description("list workspaces on this machine").action(async (...args) => {
|
|
5298
|
+
await workspaceList(toCliOptions(trailingCmd(args).optsWithGlobals()));
|
|
5299
|
+
});
|
|
5300
|
+
workspaceGrp.command("status").description("members + daemon liveness for a workspace").argument("[name]", "workspace name (defaults to this repo's joined workspace)").action(async (...args) => {
|
|
5301
|
+
const cmd = trailingCmd(args);
|
|
5302
|
+
const name = typeof args[0] === "string" && args[0].length > 0 ? args[0] : void 0;
|
|
5303
|
+
await workspaceStatus({
|
|
5304
|
+
...toCliOptions(cmd.optsWithGlobals()),
|
|
5305
|
+
...name !== void 0 ? { name } : {}
|
|
5306
|
+
});
|
|
5307
|
+
});
|
|
5308
|
+
workspaceGrp.command("leave").description("leave the workspace this repo joined (restore stdio)").option("--force", "overwrite a non-Noir .mcp.json entry").action(async (...args) => {
|
|
5309
|
+
const cmd = trailingCmd(args);
|
|
5310
|
+
const g = cmd.optsWithGlobals();
|
|
5311
|
+
await workspaceLeave({
|
|
5312
|
+
...toCliOptions(g),
|
|
5313
|
+
...g.force === true ? { force: true } : {}
|
|
5314
|
+
});
|
|
5315
|
+
});
|
|
5316
|
+
workspaceGrp.command("stop").description("stop a workspace daemon (membership retained)").argument("[name]", "workspace name (defaults to this repo's joined workspace)").action(async (...args) => {
|
|
5317
|
+
const cmd = trailingCmd(args);
|
|
5318
|
+
const name = typeof args[0] === "string" && args[0].length > 0 ? args[0] : void 0;
|
|
5319
|
+
await workspaceStop({
|
|
5320
|
+
...toCliOptions(cmd.optsWithGlobals()),
|
|
5321
|
+
...name !== void 0 ? { name } : {}
|
|
5322
|
+
});
|
|
5323
|
+
});
|
|
5324
|
+
workspaceGrp.action(() => {
|
|
5325
|
+
throw new NoirCliError(EXIT.USAGE, "Usage: noir workspace list|status|leave|stop");
|
|
4812
5326
|
});
|
|
4813
5327
|
program2.command("doctor").description("environment + project health").option(
|
|
4814
5328
|
"--dedup",
|
|
@@ -4904,10 +5418,23 @@ function createProgram() {
|
|
|
4904
5418
|
...limit === void 0 ? {} : { limit }
|
|
4905
5419
|
});
|
|
4906
5420
|
});
|
|
5421
|
+
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) => {
|
|
5422
|
+
const cmd = trailingCmd(args);
|
|
5423
|
+
const g = cmd.optsWithGlobals();
|
|
5424
|
+
const file = typeof args[0] === "string" && args[0].length > 0 ? args[0] : void 0;
|
|
5425
|
+
const content = typeof g.content === "string" ? g.content : void 0;
|
|
5426
|
+
const eventType = typeof g.eventType === "string" ? g.eventType : void 0;
|
|
5427
|
+
await memoryCapture({
|
|
5428
|
+
...toCliOptions(g),
|
|
5429
|
+
...file === void 0 ? {} : { file },
|
|
5430
|
+
...content === void 0 ? {} : { content },
|
|
5431
|
+
...eventType === void 0 ? {} : { eventType }
|
|
5432
|
+
});
|
|
5433
|
+
});
|
|
4907
5434
|
memoryGrp.action(() => {
|
|
4908
5435
|
throw new NoirCliError(
|
|
4909
5436
|
EXIT.USAGE,
|
|
4910
|
-
"Usage: noir memory recall|save|sessions|forget|consolidate"
|
|
5437
|
+
"Usage: noir memory recall|save|capture|sessions|forget|consolidate"
|
|
4911
5438
|
);
|
|
4912
5439
|
});
|
|
4913
5440
|
const skillsGrp = program2.command("skills").description("builtin skills");
|
|
@@ -5201,6 +5728,7 @@ var init_bin = __esm({
|
|
|
5201
5728
|
init_skills();
|
|
5202
5729
|
init_status();
|
|
5203
5730
|
init_task();
|
|
5731
|
+
init_workspace();
|
|
5204
5732
|
init_init();
|
|
5205
5733
|
init_output();
|
|
5206
5734
|
init_run_profiles();
|
|
@@ -5240,11 +5768,17 @@ var init_bin = __esm({
|
|
|
5240
5768
|
// src/daemon-client.ts
|
|
5241
5769
|
import { Client, StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
|
|
5242
5770
|
import { ContextEngine, createEmbedFn, resolveEmbedderConfig } from "@noir-ai/context";
|
|
5243
|
-
import {
|
|
5771
|
+
import {
|
|
5772
|
+
loadProjectInfo as loadProjectInfo16,
|
|
5773
|
+
NOIR_VERSION as NOIR_VERSION5,
|
|
5774
|
+
readWorkspaceMarker as readWorkspaceMarker2,
|
|
5775
|
+
workspaceStoreDbPath
|
|
5776
|
+
} from "@noir-ai/core";
|
|
5244
5777
|
import {
|
|
5245
5778
|
ensureDaemonRunning as ensureDaemonRunning3,
|
|
5246
|
-
pidAlive as
|
|
5779
|
+
pidAlive as pidAlive3,
|
|
5247
5780
|
readDaemonRecord as readDaemonRecord2,
|
|
5781
|
+
readWorkspaceDaemonRecord as readWorkspaceDaemonRecord2,
|
|
5248
5782
|
resolveGateConfig
|
|
5249
5783
|
} from "@noir-ai/daemon";
|
|
5250
5784
|
import { createMemoryEngine } from "@noir-ai/memory";
|
|
@@ -5271,7 +5805,7 @@ async function probeDaemon(opts = {}) {
|
|
|
5271
5805
|
if (opts.verbose) process.stderr.write("noir: daemon probe: no daemon record\n");
|
|
5272
5806
|
return { running: false };
|
|
5273
5807
|
}
|
|
5274
|
-
if (!
|
|
5808
|
+
if (!pidAlive3(rec.pid)) {
|
|
5275
5809
|
if (opts.verbose) process.stderr.write(`noir: daemon probe: pid ${rec.pid} not alive
|
|
5276
5810
|
`);
|
|
5277
5811
|
return { running: false };
|
|
@@ -5289,7 +5823,7 @@ async function probeDaemon(opts = {}) {
|
|
|
5289
5823
|
const pidOk = typeof body?.pid === "number" && body.pid === rec.pid;
|
|
5290
5824
|
const expectedProject = opts.project?.id ?? (() => {
|
|
5291
5825
|
try {
|
|
5292
|
-
return
|
|
5826
|
+
return loadProjectInfo16(process.cwd()).id;
|
|
5293
5827
|
} catch {
|
|
5294
5828
|
return void 0;
|
|
5295
5829
|
}
|
|
@@ -5319,7 +5853,7 @@ async function probeDaemon(opts = {}) {
|
|
|
5319
5853
|
async function resolveDaemon(opts) {
|
|
5320
5854
|
let project;
|
|
5321
5855
|
try {
|
|
5322
|
-
project = opts.project ??
|
|
5856
|
+
project = opts.project ?? loadProjectInfo16(process.cwd());
|
|
5323
5857
|
} catch {
|
|
5324
5858
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
5325
5859
|
}
|
|
@@ -5415,7 +5949,7 @@ async function callDaemonTool(opts, name, args = {}) {
|
|
|
5415
5949
|
async function withInProcessRead(opts, fn) {
|
|
5416
5950
|
let project;
|
|
5417
5951
|
try {
|
|
5418
|
-
project = opts.project ??
|
|
5952
|
+
project = opts.project ?? loadProjectInfo16(process.cwd());
|
|
5419
5953
|
} catch {
|
|
5420
5954
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
5421
5955
|
}
|
|
@@ -5458,6 +5992,91 @@ async function withInProcessRead(opts, fn) {
|
|
|
5458
5992
|
});
|
|
5459
5993
|
}
|
|
5460
5994
|
}
|
|
5995
|
+
function workspaceRoutingTarget(root) {
|
|
5996
|
+
const name = readWorkspaceMarker2(root);
|
|
5997
|
+
if (name === null) return null;
|
|
5998
|
+
try {
|
|
5999
|
+
return { name, projectId: loadProjectInfo16(root).id };
|
|
6000
|
+
} catch {
|
|
6001
|
+
return null;
|
|
6002
|
+
}
|
|
6003
|
+
}
|
|
6004
|
+
async function probeWorkspaceDaemon(routing) {
|
|
6005
|
+
const rec = readWorkspaceDaemonRecord2(routing.name);
|
|
6006
|
+
if (rec === null || !pidAlive3(rec.pid)) return { running: false };
|
|
6007
|
+
try {
|
|
6008
|
+
const res = await fetch(`http://127.0.0.1:${rec.port}/health`, {
|
|
6009
|
+
signal: AbortSignal.timeout(PROBE_TIMEOUT_MS)
|
|
6010
|
+
});
|
|
6011
|
+
if (!res.ok) return { running: false };
|
|
6012
|
+
const body = await res.json().catch(() => null);
|
|
6013
|
+
if (body?.ok !== true || body.workspace !== routing.name || body.pid !== rec.pid) {
|
|
6014
|
+
return { running: false };
|
|
6015
|
+
}
|
|
6016
|
+
return { running: true, pid: rec.pid, port: rec.port };
|
|
6017
|
+
} catch {
|
|
6018
|
+
return { running: false };
|
|
6019
|
+
}
|
|
6020
|
+
}
|
|
6021
|
+
async function withWorkspaceDaemon(opts, routing, fn) {
|
|
6022
|
+
const probe = await probeWorkspaceDaemon(routing);
|
|
6023
|
+
if (!probe.running || probe.port === void 0) {
|
|
6024
|
+
fail(
|
|
6025
|
+
EXIT.DAEMON_DOWN,
|
|
6026
|
+
`workspace ${JSON.stringify(routing.name)} daemon is not running \u2014 start it with \`noir daemon start --workspace ${routing.name}\` (or run \`noir workspace status\`).`,
|
|
6027
|
+
opts
|
|
6028
|
+
);
|
|
6029
|
+
}
|
|
6030
|
+
const url = `http://127.0.0.1:${probe.port}/mcp?p=${routing.projectId}`;
|
|
6031
|
+
let client;
|
|
6032
|
+
try {
|
|
6033
|
+
client = new Client(
|
|
6034
|
+
{ name: "noir-cli", version: NOIR_VERSION5 },
|
|
6035
|
+
{ versionNegotiation: { mode: "auto" } }
|
|
6036
|
+
);
|
|
6037
|
+
const connected = client;
|
|
6038
|
+
await connectClient(connected, url, opts);
|
|
6039
|
+
return await fn(buildCaller(connected, opts));
|
|
6040
|
+
} finally {
|
|
6041
|
+
if (client) {
|
|
6042
|
+
await client.close().catch(() => {
|
|
6043
|
+
});
|
|
6044
|
+
}
|
|
6045
|
+
}
|
|
6046
|
+
}
|
|
6047
|
+
async function withWorkspaceMemoryRead(opts, routing, project, fn) {
|
|
6048
|
+
let store;
|
|
6049
|
+
try {
|
|
6050
|
+
store = await openStore({
|
|
6051
|
+
projectId: `ws-${routing.name}`,
|
|
6052
|
+
root: project.root,
|
|
6053
|
+
dbPath: workspaceStoreDbPath(routing.name),
|
|
6054
|
+
readonly: true
|
|
6055
|
+
});
|
|
6056
|
+
} catch (err) {
|
|
6057
|
+
fail(
|
|
6058
|
+
EXIT.DAEMON_DOWN,
|
|
6059
|
+
`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\`).`,
|
|
6060
|
+
opts
|
|
6061
|
+
);
|
|
6062
|
+
}
|
|
6063
|
+
const embedderCfg = resolveEmbedderConfig(project.config.context);
|
|
6064
|
+
const embed = createEmbedFn(embedderCfg).embed;
|
|
6065
|
+
const memory = createMemoryEngine({
|
|
6066
|
+
store,
|
|
6067
|
+
root: project.root,
|
|
6068
|
+
projectId: `ws-${routing.name}`,
|
|
6069
|
+
embed,
|
|
6070
|
+
storeDegraded: true,
|
|
6071
|
+
softForget: true
|
|
6072
|
+
});
|
|
6073
|
+
try {
|
|
6074
|
+
return await fn(memory);
|
|
6075
|
+
} finally {
|
|
6076
|
+
await store.close().catch(() => {
|
|
6077
|
+
});
|
|
6078
|
+
}
|
|
6079
|
+
}
|
|
5461
6080
|
var DAEMON_DOWN_HINT, PROBE_TIMEOUT_MS;
|
|
5462
6081
|
var init_daemon_client = __esm({
|
|
5463
6082
|
"src/daemon-client.ts"() {
|
|
@@ -5470,21 +6089,21 @@ var init_daemon_client = __esm({
|
|
|
5470
6089
|
|
|
5471
6090
|
// src/commands/doctor.ts
|
|
5472
6091
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
5473
|
-
import { existsSync as
|
|
5474
|
-
import { dirname, join as
|
|
6092
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync } from "fs";
|
|
6093
|
+
import { dirname, join as join6, relative } from "path";
|
|
5475
6094
|
import { fileURLToPath } from "url";
|
|
5476
|
-
import { resolveAdapter as
|
|
6095
|
+
import { resolveAdapter as resolveAdapter7 } from "@noir-ai/adapters";
|
|
5477
6096
|
import {
|
|
5478
6097
|
detectActiveMethod as detectActiveMethod4,
|
|
5479
6098
|
latestVersionFromCache as latestVersionFromCache2,
|
|
5480
|
-
loadProjectInfo as
|
|
6099
|
+
loadProjectInfo as loadProjectInfo17,
|
|
5481
6100
|
NOIR_VERSION as NOIR_VERSION6,
|
|
5482
6101
|
paths as paths2,
|
|
5483
6102
|
readInstallRecord as readInstallRecord4,
|
|
5484
6103
|
readUpdateCache as readUpdateCache3
|
|
5485
6104
|
} from "@noir-ai/core";
|
|
5486
6105
|
import { CURRENT_SCAFFOLD_VERSION, readScaffoldVersion } from "@noir-ai/create";
|
|
5487
|
-
import { pidAlive as
|
|
6106
|
+
import { pidAlive as pidAlive4, readDaemonRecord as readDaemonRecord3 } from "@noir-ai/daemon";
|
|
5488
6107
|
import { resolveModelConfig } from "@noir-ai/model";
|
|
5489
6108
|
async function probeOnnx() {
|
|
5490
6109
|
try {
|
|
@@ -5521,7 +6140,7 @@ async function checkRuntime(checks) {
|
|
|
5521
6140
|
}
|
|
5522
6141
|
function checkConfig(root) {
|
|
5523
6142
|
const configPath = paths2.config(root);
|
|
5524
|
-
if (!
|
|
6143
|
+
if (!existsSync3(configPath)) {
|
|
5525
6144
|
return {
|
|
5526
6145
|
project: void 0,
|
|
5527
6146
|
result: {
|
|
@@ -5532,7 +6151,7 @@ function checkConfig(root) {
|
|
|
5532
6151
|
};
|
|
5533
6152
|
}
|
|
5534
6153
|
try {
|
|
5535
|
-
const project =
|
|
6154
|
+
const project = loadProjectInfo17(root);
|
|
5536
6155
|
return {
|
|
5537
6156
|
project,
|
|
5538
6157
|
result: {
|
|
@@ -5558,7 +6177,7 @@ async function checkDaemon(checks) {
|
|
|
5558
6177
|
});
|
|
5559
6178
|
return;
|
|
5560
6179
|
}
|
|
5561
|
-
if (!
|
|
6180
|
+
if (!pidAlive4(rec.pid)) {
|
|
5562
6181
|
checks.push({
|
|
5563
6182
|
name: "daemon",
|
|
5564
6183
|
status: "warn",
|
|
@@ -5613,7 +6232,7 @@ async function checkStore(checks, project, root) {
|
|
|
5613
6232
|
return;
|
|
5614
6233
|
}
|
|
5615
6234
|
const dbPath = paths2.storeDb(root, project.id);
|
|
5616
|
-
if (!
|
|
6235
|
+
if (!existsSync3(dbPath)) {
|
|
5617
6236
|
checks.push({
|
|
5618
6237
|
name: "store",
|
|
5619
6238
|
status: "warn",
|
|
@@ -5642,7 +6261,7 @@ async function checkStore(checks, project, root) {
|
|
|
5642
6261
|
}
|
|
5643
6262
|
}
|
|
5644
6263
|
function checkNoirEnv(checks, root) {
|
|
5645
|
-
const path =
|
|
6264
|
+
const path = join6(paths2.noirDir(root), ".env");
|
|
5646
6265
|
let st;
|
|
5647
6266
|
try {
|
|
5648
6267
|
st = statSync(path);
|
|
@@ -5752,7 +6371,7 @@ function checkRulesMdBudget(checks, root, project) {
|
|
|
5752
6371
|
return null;
|
|
5753
6372
|
}
|
|
5754
6373
|
const rulesPath = paths2.rulesMd(root);
|
|
5755
|
-
if (!
|
|
6374
|
+
if (!existsSync3(rulesPath)) {
|
|
5756
6375
|
checks.push({
|
|
5757
6376
|
name: "rules budget",
|
|
5758
6377
|
status: "ok",
|
|
@@ -5762,7 +6381,7 @@ function checkRulesMdBudget(checks, root, project) {
|
|
|
5762
6381
|
}
|
|
5763
6382
|
let content;
|
|
5764
6383
|
try {
|
|
5765
|
-
content =
|
|
6384
|
+
content = readFileSync4(rulesPath, "utf8");
|
|
5766
6385
|
} catch (e) {
|
|
5767
6386
|
checks.push({
|
|
5768
6387
|
name: "rules budget",
|
|
@@ -5806,7 +6425,7 @@ function checkHostArtifacts(checks, root, project) {
|
|
|
5806
6425
|
return null;
|
|
5807
6426
|
}
|
|
5808
6427
|
const host = project.config.host;
|
|
5809
|
-
const adapter =
|
|
6428
|
+
const adapter = resolveAdapter7(host);
|
|
5810
6429
|
const ectx = { root };
|
|
5811
6430
|
const expected = [];
|
|
5812
6431
|
const emitsAgentsMd = host === "agents-md" || host === "cursor" || host === "opencode";
|
|
@@ -5816,7 +6435,7 @@ function checkHostArtifacts(checks, root, project) {
|
|
|
5816
6435
|
if (host === "claude") expected.push("CLAUDE.md");
|
|
5817
6436
|
else if (host === "gemini") expected.push("GEMINI.md");
|
|
5818
6437
|
expected.push(relOr(adapter.mcpConfigPath?.(ectx) ?? ".mcp.json", root));
|
|
5819
|
-
const missing = expected.filter((p) => !
|
|
6438
|
+
const missing = expected.filter((p) => !existsSync3(joinRoot(root, p)));
|
|
5820
6439
|
const status2 = missing.length === 0 ? "ok" : "warn";
|
|
5821
6440
|
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)`;
|
|
5822
6441
|
checks.push({ name: "host artifacts", status: status2, detail });
|
|
@@ -5833,7 +6452,7 @@ function checkNestedNoir(checks, root) {
|
|
|
5833
6452
|
".noir/.npmignore",
|
|
5834
6453
|
".noir/.prettierignore"
|
|
5835
6454
|
];
|
|
5836
|
-
const found = candidates.filter((rel) =>
|
|
6455
|
+
const found = candidates.filter((rel) => existsSync3(join6(root, rel)));
|
|
5837
6456
|
const detected = found.length > 0;
|
|
5838
6457
|
checks.push({
|
|
5839
6458
|
name: "nested .noir",
|
|
@@ -5846,11 +6465,11 @@ function collectDedupCandidates(root) {
|
|
|
5846
6465
|
const rels = ["CLAUDE.md", "AGENTS.md", "GEMINI.md", ".noir/rules/RULES.md"];
|
|
5847
6466
|
const out = [];
|
|
5848
6467
|
for (const rel of rels) {
|
|
5849
|
-
const abs =
|
|
5850
|
-
if (!
|
|
6468
|
+
const abs = join6(root, rel);
|
|
6469
|
+
if (!existsSync3(abs)) continue;
|
|
5851
6470
|
let text;
|
|
5852
6471
|
try {
|
|
5853
|
-
text =
|
|
6472
|
+
text = readFileSync4(abs, "utf8");
|
|
5854
6473
|
} catch {
|
|
5855
6474
|
continue;
|
|
5856
6475
|
}
|
|
@@ -5907,13 +6526,13 @@ async function checkSemanticDupDoctor(checks, root, project, opts = {}) {
|
|
|
5907
6526
|
function resolveWorkspacePackagesDir() {
|
|
5908
6527
|
let here = dirname(fileURLToPath(import.meta.url));
|
|
5909
6528
|
for (let i = 0; i < 8; i++) {
|
|
5910
|
-
const pkgPath =
|
|
5911
|
-
if (
|
|
6529
|
+
const pkgPath = join6(here, "package.json");
|
|
6530
|
+
if (existsSync3(pkgPath)) {
|
|
5912
6531
|
try {
|
|
5913
|
-
const pkg = JSON.parse(
|
|
6532
|
+
const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
|
|
5914
6533
|
if (pkg.name === "@noir-ai/cli") {
|
|
5915
6534
|
const packagesDir = dirname(here);
|
|
5916
|
-
if (
|
|
6535
|
+
if (existsSync3(join6(dirname(packagesDir), "pnpm-workspace.yaml"))) {
|
|
5917
6536
|
return packagesDir;
|
|
5918
6537
|
}
|
|
5919
6538
|
return null;
|
|
@@ -5958,7 +6577,7 @@ function checkPublish(checks, packagesDir, opts = {}) {
|
|
|
5958
6577
|
}
|
|
5959
6578
|
let entries = [];
|
|
5960
6579
|
try {
|
|
5961
|
-
entries =
|
|
6580
|
+
entries = readdirSync2(packagesDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
5962
6581
|
} catch (e) {
|
|
5963
6582
|
checks.push({
|
|
5964
6583
|
name: "publish",
|
|
@@ -5971,11 +6590,11 @@ function checkPublish(checks, packagesDir, opts = {}) {
|
|
|
5971
6590
|
let checked = 0;
|
|
5972
6591
|
let cliDir = null;
|
|
5973
6592
|
for (const name of entries) {
|
|
5974
|
-
const pkgPath =
|
|
5975
|
-
if (!
|
|
6593
|
+
const pkgPath = join6(packagesDir, name, "package.json");
|
|
6594
|
+
if (!existsSync3(pkgPath)) continue;
|
|
5976
6595
|
let pkg;
|
|
5977
6596
|
try {
|
|
5978
|
-
pkg = JSON.parse(
|
|
6597
|
+
pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
|
|
5979
6598
|
} catch (e) {
|
|
5980
6599
|
issues.push(`${name}/package.json: unparseable (${msg(e)})`);
|
|
5981
6600
|
checked++;
|
|
@@ -5997,7 +6616,7 @@ function checkPublish(checks, packagesDir, opts = {}) {
|
|
|
5997
6616
|
if (pkg.bin == null || typeof pkg.bin === "object" && Object.keys(pkg.bin).length === 0) {
|
|
5998
6617
|
issues.push(`${name}/package.json: bin missing (cli package must declare a bin)`);
|
|
5999
6618
|
}
|
|
6000
|
-
cliDir =
|
|
6619
|
+
cliDir = join6(packagesDir, name);
|
|
6001
6620
|
}
|
|
6002
6621
|
}
|
|
6003
6622
|
let packNote = "";
|