@noir-ai/cli 1.13.0-beta.1 → 1.14.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 +432 -130
- package/dist/bin.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +450 -148
- package/dist/index.js.map +1 -1
- package/dist/tui/{chunk-KMLSJRYV.js → chunk-6LO4LXEH.js} +419 -125
- package/dist/tui/chunk-6LO4LXEH.js.map +1 -0
- package/dist/tui/{create-HYFBTBLR.js → create-FD4E626Z.js} +2 -2
- package/dist/tui/index.js +1 -1
- package/dist/tui/{sync-QDOJ7INZ.js → sync-N633SQSS.js} +2 -2
- package/package.json +11 -11
- package/dist/tui/chunk-KMLSJRYV.js.map +0 -1
- /package/dist/tui/{create-HYFBTBLR.js.map → create-FD4E626Z.js.map} +0 -0
- /package/dist/tui/{sync-QDOJ7INZ.js.map → sync-N633SQSS.js.map} +0 -0
|
@@ -28,13 +28,13 @@ import {
|
|
|
28
28
|
} from "./chunk-ESZTOQEC.js";
|
|
29
29
|
|
|
30
30
|
// src/commands/status.ts
|
|
31
|
-
import { loadProjectInfo as
|
|
31
|
+
import { loadProjectInfo as loadProjectInfo14, 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
36
|
import {
|
|
37
|
-
loadProjectInfo as
|
|
37
|
+
loadProjectInfo as loadProjectInfo13,
|
|
38
38
|
NOIR_VERSION as NOIR_VERSION4,
|
|
39
39
|
readWorkspaceMarker as readWorkspaceMarker2,
|
|
40
40
|
workspaceStoreDbPath
|
|
@@ -42,9 +42,11 @@ import {
|
|
|
42
42
|
import {
|
|
43
43
|
ensureDaemonRunning as ensureDaemonRunning3,
|
|
44
44
|
pidAlive as pidAlive4,
|
|
45
|
-
|
|
45
|
+
readDaemonToken as readDaemonToken2,
|
|
46
|
+
readProjectDaemonRecord as readProjectDaemonRecord3,
|
|
46
47
|
readWorkspaceDaemonRecord as readWorkspaceDaemonRecord2,
|
|
47
|
-
resolveGateConfig
|
|
48
|
+
resolveGateConfig,
|
|
49
|
+
tokenPath
|
|
48
50
|
} from "@noir-ai/daemon";
|
|
49
51
|
import { createMemoryEngine } from "@noir-ai/memory";
|
|
50
52
|
import { openStore } from "@noir-ai/store";
|
|
@@ -225,10 +227,11 @@ function renderStatus(data, opts) {
|
|
|
225
227
|
// src/commands/daemon.ts
|
|
226
228
|
import { loadProjectInfo as loadProjectInfo2 } from "@noir-ai/core";
|
|
227
229
|
import {
|
|
228
|
-
|
|
230
|
+
clearProjectDaemonRecord,
|
|
229
231
|
ensureDaemonRunning,
|
|
230
232
|
pidAlive as pidAlive2,
|
|
231
|
-
|
|
233
|
+
readDaemonToken,
|
|
234
|
+
readProjectDaemonRecord,
|
|
232
235
|
spawnDetachedDaemon
|
|
233
236
|
} from "@noir-ai/daemon";
|
|
234
237
|
|
|
@@ -584,7 +587,8 @@ async function daemonStart(opts) {
|
|
|
584
587
|
if (opts.detachChild === true) {
|
|
585
588
|
const child = await ensureDaemonRunning({
|
|
586
589
|
project,
|
|
587
|
-
idleTimeoutSec: project.config.daemon.idleTimeoutSec
|
|
590
|
+
idleTimeoutSec: project.config.daemon.idleTimeoutSec,
|
|
591
|
+
...project.config.daemon.port !== void 0 ? { port: project.config.daemon.port } : {}
|
|
588
592
|
});
|
|
589
593
|
if (child.started) {
|
|
590
594
|
if (opts.json === true) {
|
|
@@ -607,7 +611,7 @@ async function daemonStart(opts) {
|
|
|
607
611
|
return;
|
|
608
612
|
}
|
|
609
613
|
if (opts.detach === true) {
|
|
610
|
-
const existing =
|
|
614
|
+
const existing = readProjectDaemonRecord(project.id);
|
|
611
615
|
if (existing && pidAlive2(existing.pid) && await isHealthy(existing.port, existing.pid, project.id)) {
|
|
612
616
|
if (opts.json === true) {
|
|
613
617
|
process.stdout.write(
|
|
@@ -647,7 +651,8 @@ async function daemonStart(opts) {
|
|
|
647
651
|
try {
|
|
648
652
|
ensured = await ensureDaemonRunning({
|
|
649
653
|
project,
|
|
650
|
-
idleTimeoutSec: project.config.daemon.idleTimeoutSec
|
|
654
|
+
idleTimeoutSec: project.config.daemon.idleTimeoutSec,
|
|
655
|
+
...project.config.daemon.port !== void 0 ? { port: project.config.daemon.port } : {}
|
|
651
656
|
});
|
|
652
657
|
} catch (err) {
|
|
653
658
|
ds.fail("Daemon failed to start");
|
|
@@ -690,7 +695,19 @@ async function daemonStart(opts) {
|
|
|
690
695
|
log(`Noir daemon already running at ${ensured.url}`, opts);
|
|
691
696
|
}
|
|
692
697
|
async function daemonStop(opts) {
|
|
693
|
-
const
|
|
698
|
+
const callerProject = resolveCallerProjectId();
|
|
699
|
+
if (callerProject === void 0) {
|
|
700
|
+
if (opts.json === true) {
|
|
701
|
+
process.stdout.write(
|
|
702
|
+
`${JSON.stringify({ ok: true, data: { running: false, stopped: false } })}
|
|
703
|
+
`
|
|
704
|
+
);
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
info("No Noir daemon is running for this directory (not a Noir project).", opts);
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
const rec = readProjectDaemonRecord(callerProject);
|
|
694
711
|
if (!rec) {
|
|
695
712
|
if (opts.json === true) {
|
|
696
713
|
process.stdout.write(
|
|
@@ -705,9 +722,7 @@ async function daemonStop(opts) {
|
|
|
705
722
|
const ds = spinner(`Stopping daemon (pid ${rec.pid})...`, opts).start();
|
|
706
723
|
let signalled = false;
|
|
707
724
|
let errMsg2;
|
|
708
|
-
const
|
|
709
|
-
const wrongProject = rec.projectId !== void 0 && callerProject !== void 0 && rec.projectId !== callerProject;
|
|
710
|
-
const healthy = !wrongProject && pidAlive2(rec.pid) && await isHealthy(rec.port, rec.pid, rec.projectId === void 0 ? void 0 : callerProject);
|
|
725
|
+
const healthy = pidAlive2(rec.pid) && await isHealthy(rec.port, rec.pid, callerProject);
|
|
711
726
|
if (healthy) {
|
|
712
727
|
try {
|
|
713
728
|
process.kill(rec.pid, "SIGTERM");
|
|
@@ -716,7 +731,7 @@ async function daemonStop(opts) {
|
|
|
716
731
|
errMsg2 = err instanceof Error ? err.message : String(err);
|
|
717
732
|
}
|
|
718
733
|
}
|
|
719
|
-
|
|
734
|
+
clearProjectDaemonRecord(callerProject);
|
|
720
735
|
if (opts.json === true) {
|
|
721
736
|
process.stdout.write(
|
|
722
737
|
`${JSON.stringify({
|
|
@@ -735,26 +750,22 @@ async function daemonStop(opts) {
|
|
|
735
750
|
if (signalled) {
|
|
736
751
|
ds.succeed(`Stopped Noir daemon (pid ${rec.pid})`);
|
|
737
752
|
} else {
|
|
738
|
-
const reason =
|
|
753
|
+
const reason = errMsg2 !== void 0 ? errMsg2 : "the recorded pid did not answer as our daemon (stale record cleared)";
|
|
739
754
|
ds.warn(`Daemon (pid ${rec.pid}) could not be signalled: ${reason}`);
|
|
740
755
|
info(`Noir daemon (pid ${rec.pid}) could not be signalled: ${reason}`, opts);
|
|
741
756
|
}
|
|
742
757
|
}
|
|
743
758
|
async function daemonStatus(opts) {
|
|
744
|
-
const
|
|
759
|
+
const callerProject = resolveCallerProjectId();
|
|
760
|
+
if (callerProject === void 0) {
|
|
761
|
+
fail(EXIT.DAEMON_DOWN, "Noir daemon is not running (not a Noir project).", opts);
|
|
762
|
+
}
|
|
763
|
+
const rec = readProjectDaemonRecord(callerProject);
|
|
745
764
|
if (!rec) {
|
|
746
765
|
fail(EXIT.DAEMON_DOWN, "Noir daemon is not running (start with `noir daemon start`).", opts);
|
|
747
766
|
}
|
|
748
|
-
const callerProject = resolveCallerProjectId();
|
|
749
|
-
if (rec.projectId !== void 0 && callerProject !== void 0 && rec.projectId !== callerProject) {
|
|
750
|
-
fail(
|
|
751
|
-
EXIT.DAEMON_DOWN,
|
|
752
|
-
"Noir daemon is not running for this project (a daemon for another project is running).",
|
|
753
|
-
opts
|
|
754
|
-
);
|
|
755
|
-
}
|
|
756
767
|
if (!pidAlive2(rec.pid)) {
|
|
757
|
-
|
|
768
|
+
clearProjectDaemonRecord(callerProject);
|
|
758
769
|
fail(EXIT.DAEMON_DOWN, "Noir daemon is not running (stale record removed).", opts);
|
|
759
770
|
}
|
|
760
771
|
const hs = spinner("Probing daemon health...", opts).start();
|
|
@@ -772,7 +783,7 @@ async function daemonStatus(opts) {
|
|
|
772
783
|
const pidOk = typeof health?.pid === "number" && health.pid === rec.pid;
|
|
773
784
|
if (health?.ok !== true || !pidOk) {
|
|
774
785
|
hs.fail("Daemon not responding");
|
|
775
|
-
|
|
786
|
+
clearProjectDaemonRecord(callerProject);
|
|
776
787
|
fail(
|
|
777
788
|
EXIT.DAEMON_DOWN,
|
|
778
789
|
health !== null && !pidOk ? "Noir daemon is not running (pid missing/mismatched \u2014 a foreign process holds the recorded port; stale record removed)." : "Noir daemon is not running (port unresponsive; stale record removed).",
|
|
@@ -788,7 +799,8 @@ async function daemonStatus(opts) {
|
|
|
788
799
|
startedAt: rec.startedAt,
|
|
789
800
|
uptimeSec,
|
|
790
801
|
// Honest ownership: the detached child writes `mode:'detached'`; anything
|
|
791
|
-
// else (
|
|
802
|
+
// else (a record written before the field existed, foreground start)
|
|
803
|
+
// reports 'foreground'.
|
|
792
804
|
mode: rec.mode ?? MODE
|
|
793
805
|
};
|
|
794
806
|
if (opts.json === true) {
|
|
@@ -801,6 +813,42 @@ async function daemonStatus(opts) {
|
|
|
801
813
|
opts
|
|
802
814
|
);
|
|
803
815
|
}
|
|
816
|
+
async function daemonToken(opts) {
|
|
817
|
+
const callerProject = resolveCallerProjectId();
|
|
818
|
+
if (callerProject === void 0) {
|
|
819
|
+
fail(EXIT.DAEMON_DOWN, "Noir daemon is not running (not a Noir project).", opts);
|
|
820
|
+
}
|
|
821
|
+
const rec = readProjectDaemonRecord(callerProject);
|
|
822
|
+
if (!rec) {
|
|
823
|
+
fail(
|
|
824
|
+
EXIT.DAEMON_DOWN,
|
|
825
|
+
"Noir daemon is not running (no daemon record \u2014 start it with `noir daemon start`).",
|
|
826
|
+
opts
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
if (!pidAlive2(rec.pid) || !await isHealthy(rec.port, rec.pid, callerProject)) {
|
|
830
|
+
fail(
|
|
831
|
+
EXIT.DAEMON_DOWN,
|
|
832
|
+
"Noir daemon is not running (stale record \u2014 start it with `noir daemon start`).",
|
|
833
|
+
opts
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
const token = readDaemonToken(callerProject);
|
|
837
|
+
if (token === null) {
|
|
838
|
+
fail(
|
|
839
|
+
EXIT.DAEMON_DOWN,
|
|
840
|
+
"Noir daemon is not running (the record has no token beside it \u2014 restart it with `noir daemon start`).",
|
|
841
|
+
opts
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
if (opts.json === true) {
|
|
845
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: { token } })}
|
|
846
|
+
`);
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
process.stdout.write(`${token}
|
|
850
|
+
`);
|
|
851
|
+
}
|
|
804
852
|
async function daemonRestart(opts) {
|
|
805
853
|
await daemonStop({ ...opts, json: false, quiet: true });
|
|
806
854
|
await daemonStart(opts);
|
|
@@ -815,6 +863,7 @@ import { resolveAdapter as resolveAdapter2 } from "@noir-ai/adapters";
|
|
|
815
863
|
import {
|
|
816
864
|
detectActiveMethod,
|
|
817
865
|
latestVersionFromCache,
|
|
866
|
+
loadNoirEnv,
|
|
818
867
|
loadProjectInfo as loadProjectInfo3,
|
|
819
868
|
NOIR_VERSION,
|
|
820
869
|
paths,
|
|
@@ -822,7 +871,7 @@ import {
|
|
|
822
871
|
readUpdateCache
|
|
823
872
|
} from "@noir-ai/core";
|
|
824
873
|
import { CURRENT_SCAFFOLD_VERSION, readScaffoldVersion } from "@noir-ai/create";
|
|
825
|
-
import { pidAlive as pidAlive3,
|
|
874
|
+
import { pidAlive as pidAlive3, readProjectDaemonRecord as readProjectDaemonRecord2 } from "@noir-ai/daemon";
|
|
826
875
|
import { resolveModelConfig } from "@noir-ai/model";
|
|
827
876
|
async function probeOnnx() {
|
|
828
877
|
try {
|
|
@@ -886,8 +935,16 @@ function checkConfig(root) {
|
|
|
886
935
|
};
|
|
887
936
|
}
|
|
888
937
|
}
|
|
889
|
-
async function checkDaemon(checks) {
|
|
890
|
-
|
|
938
|
+
async function checkDaemon(checks, project) {
|
|
939
|
+
if (project === void 0) {
|
|
940
|
+
checks.push({
|
|
941
|
+
name: "daemon",
|
|
942
|
+
status: "warn",
|
|
943
|
+
detail: "skipped \u2014 project not initialized"
|
|
944
|
+
});
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
const rec = readProjectDaemonRecord2(project.id);
|
|
891
948
|
if (!rec) {
|
|
892
949
|
checks.push({
|
|
893
950
|
name: "daemon",
|
|
@@ -979,7 +1036,8 @@ async function checkStore(checks, project, root) {
|
|
|
979
1036
|
}
|
|
980
1037
|
}
|
|
981
1038
|
}
|
|
982
|
-
|
|
1039
|
+
var ENV_REFUSAL_PREFIX = ".noir/.env: refusing to load";
|
|
1040
|
+
function checkNoirEnv(checks, root, loaded) {
|
|
983
1041
|
const path = join2(paths.noirDir(root), ".env");
|
|
984
1042
|
let st;
|
|
985
1043
|
try {
|
|
@@ -987,6 +1045,14 @@ function checkNoirEnv(checks, root) {
|
|
|
987
1045
|
} catch {
|
|
988
1046
|
return;
|
|
989
1047
|
}
|
|
1048
|
+
if (loaded.warnings.some((w) => w.startsWith(ENV_REFUSAL_PREFIX))) {
|
|
1049
|
+
checks.push({
|
|
1050
|
+
name: "noir-env",
|
|
1051
|
+
status: "warn",
|
|
1052
|
+
detail: `${path} is tracked by git \u2014 refused, so NONE of its keys are in effect. Fix: add \`.noir/.env\` to .gitignore and run \`git rm --cached .noir/.env\`.`
|
|
1053
|
+
});
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
990
1056
|
const mode = st.mode & 511;
|
|
991
1057
|
const tooOpen = (st.mode & 63) !== 0;
|
|
992
1058
|
checks.push(
|
|
@@ -996,6 +1062,10 @@ function checkNoirEnv(checks, root) {
|
|
|
996
1062
|
detail: `${path} is ${mode.toString(8)} \u2014 readable by others; run chmod 600`
|
|
997
1063
|
} : { name: "noir-env", status: "ok", detail: `${path} permissions ok (${mode.toString(8)})` }
|
|
998
1064
|
);
|
|
1065
|
+
for (const [key, source] of Object.entries(loaded.sources)) {
|
|
1066
|
+
if (source !== "file") continue;
|
|
1067
|
+
checks.push({ name: `noir-env:${key}`, status: "ok", detail: "from .noir/.env" });
|
|
1068
|
+
}
|
|
999
1069
|
}
|
|
1000
1070
|
function checkEmbedder(checks, project, vecOk) {
|
|
1001
1071
|
if (!project) {
|
|
@@ -1027,7 +1097,7 @@ function checkEmbedder(checks, project, vecOk) {
|
|
|
1027
1097
|
detail: `${kind}/${model} (${emb.dim}-dim) \xB7 vec backend available`
|
|
1028
1098
|
});
|
|
1029
1099
|
}
|
|
1030
|
-
function checkProvider(checks, project) {
|
|
1100
|
+
function checkProvider(checks, project, sources) {
|
|
1031
1101
|
if (!project) {
|
|
1032
1102
|
checks.push({ name: "provider", status: "warn", detail: "skipped \u2014 not initialized" });
|
|
1033
1103
|
return;
|
|
@@ -1047,7 +1117,8 @@ function checkProvider(checks, project) {
|
|
|
1047
1117
|
for (const name of names) {
|
|
1048
1118
|
const p = resolved.providers[name];
|
|
1049
1119
|
if (!p) continue;
|
|
1050
|
-
const
|
|
1120
|
+
const fromFile = p.apiKeyEnv !== void 0 && sources[p.apiKeyEnv] === "file";
|
|
1121
|
+
const key = p.hasKey ? fromFile ? "key present (from .noir/.env)" : "key present" : p.apiKeyEnv ? `missing ${p.apiKeyEnv}` : "anonymous";
|
|
1051
1122
|
if (!p.hasKey && p.apiKeyEnv) missing = true;
|
|
1052
1123
|
parts.push(`${name}/${p.model ?? "?"} (${key})`);
|
|
1053
1124
|
}
|
|
@@ -1411,12 +1482,13 @@ async function doctor(opts = {}) {
|
|
|
1411
1482
|
await checkRuntime(checks);
|
|
1412
1483
|
const { project, result: configResult } = checkConfig(root);
|
|
1413
1484
|
checks.push(configResult);
|
|
1414
|
-
await checkDaemon(checks);
|
|
1485
|
+
await checkDaemon(checks, project);
|
|
1415
1486
|
const { vecOk } = await checkNativeDeps(checks);
|
|
1416
1487
|
await checkStore(checks, project, root);
|
|
1417
1488
|
checkEmbedder(checks, project, vecOk);
|
|
1418
|
-
|
|
1419
|
-
|
|
1489
|
+
const noirEnv = loadNoirEnv(root);
|
|
1490
|
+
checkProvider(checks, project, noirEnv.sources);
|
|
1491
|
+
checkNoirEnv(checks, root, noirEnv);
|
|
1420
1492
|
const scaffold2 = checkScaffoldVersion(checks, root);
|
|
1421
1493
|
const rules = checkRulesMdBudget(checks, root, project);
|
|
1422
1494
|
const host = checkHostArtifacts(checks, root, project);
|
|
@@ -1460,6 +1532,94 @@ async function doctor(opts = {}) {
|
|
|
1460
1532
|
}
|
|
1461
1533
|
}
|
|
1462
1534
|
|
|
1535
|
+
// src/commands/env.ts
|
|
1536
|
+
import { loadNoirEnv as loadNoirEnv2, loadProjectInfo as loadProjectInfo4 } from "@noir-ai/core";
|
|
1537
|
+
import { resolveModelConfig as resolveModelConfig2 } from "@noir-ai/model";
|
|
1538
|
+
var CURATED_AMBIENT_KEYS = [
|
|
1539
|
+
"CLICKUP_API_TOKEN",
|
|
1540
|
+
"OPENAI_API_KEY",
|
|
1541
|
+
"VOYAGE_API_KEY",
|
|
1542
|
+
"COHERE_API_KEY",
|
|
1543
|
+
"OLLAMA_BASE_URL",
|
|
1544
|
+
"ANTHROPIC_API_KEY",
|
|
1545
|
+
"NOIR_PROFILE"
|
|
1546
|
+
];
|
|
1547
|
+
var ambientBaseline;
|
|
1548
|
+
function captureAmbientEnv(env2 = process.env) {
|
|
1549
|
+
ambientBaseline = { ...env2 };
|
|
1550
|
+
}
|
|
1551
|
+
var SHORT_VALUE_MAX = 8;
|
|
1552
|
+
function redactShape(value) {
|
|
1553
|
+
if (value.length <= SHORT_VALUE_MAX) return `\u2026(${value.length})`;
|
|
1554
|
+
return `${value.slice(0, 3)}\u2026(${value.length})`;
|
|
1555
|
+
}
|
|
1556
|
+
function configApiKeyEnvNames(root) {
|
|
1557
|
+
try {
|
|
1558
|
+
const project = loadProjectInfo4(root);
|
|
1559
|
+
const names = /* @__PURE__ */ new Set();
|
|
1560
|
+
for (const provider of Object.values(resolveModelConfig2(project.config.model).providers)) {
|
|
1561
|
+
if (provider.apiKeyEnv !== void 0 && provider.apiKeyEnv.length > 0) {
|
|
1562
|
+
names.add(provider.apiKeyEnv);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
return [...names];
|
|
1566
|
+
} catch {
|
|
1567
|
+
return [];
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
function resolveEnv(root, ambientEnv) {
|
|
1571
|
+
const { overlay, warnings, sources } = loadNoirEnv2(root, ambientEnv);
|
|
1572
|
+
const curated = new Set(CURATED_AMBIENT_KEYS);
|
|
1573
|
+
for (const name of configApiKeyEnvNames(root)) curated.add(name);
|
|
1574
|
+
const candidates = new Set(Object.keys(sources));
|
|
1575
|
+
for (const key of curated) {
|
|
1576
|
+
if (ambientEnv[key] !== void 0) candidates.add(key);
|
|
1577
|
+
}
|
|
1578
|
+
const vars = [];
|
|
1579
|
+
const shapes = {};
|
|
1580
|
+
for (const key of [...candidates].sort()) {
|
|
1581
|
+
const source = sources[key] ?? "env";
|
|
1582
|
+
if (source === "env" && !curated.has(key)) continue;
|
|
1583
|
+
const value = source === "file" ? overlay[key] ?? "" : ambientEnv[key] ?? "";
|
|
1584
|
+
const shadowed = source === "file" && ambientEnv[key] !== void 0 && ambientEnv[key] !== value;
|
|
1585
|
+
vars.push({
|
|
1586
|
+
key,
|
|
1587
|
+
source,
|
|
1588
|
+
...shadowed ? { shadowed: true } : {},
|
|
1589
|
+
valueLength: value.length
|
|
1590
|
+
});
|
|
1591
|
+
shapes[key] = redactShape(value);
|
|
1592
|
+
}
|
|
1593
|
+
return { vars, warnings, shapes };
|
|
1594
|
+
}
|
|
1595
|
+
function describeSource(row) {
|
|
1596
|
+
if (row.source !== "file") return "environment";
|
|
1597
|
+
return row.shadowed === true ? ".noir/.env (shadows environment)" : ".noir/.env";
|
|
1598
|
+
}
|
|
1599
|
+
async function env(opts = {}) {
|
|
1600
|
+
const report = resolveEnv(process.cwd(), ambientBaseline ?? process.env);
|
|
1601
|
+
if (opts.json === true) {
|
|
1602
|
+
json({ ok: true, data: { vars: report.vars, warnings: report.warnings } });
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1605
|
+
if (report.vars.length === 0) {
|
|
1606
|
+
info(
|
|
1607
|
+
"nothing to report \u2014 .noir/.env defines no keys in effect and no Noir-relevant environment variable is set",
|
|
1608
|
+
opts
|
|
1609
|
+
);
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1612
|
+
table(
|
|
1613
|
+
report.vars.map((row) => ({
|
|
1614
|
+
KEY: row.key,
|
|
1615
|
+
SOURCE: describeSource(row),
|
|
1616
|
+
VALUE: report.shapes[row.key] ?? ""
|
|
1617
|
+
})),
|
|
1618
|
+
["KEY", "SOURCE", "VALUE"],
|
|
1619
|
+
opts
|
|
1620
|
+
);
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1463
1623
|
// src/commands/handoff.ts
|
|
1464
1624
|
import { mkdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
1465
1625
|
import { join as join3 } from "path";
|
|
@@ -1468,12 +1628,12 @@ import {
|
|
|
1468
1628
|
hostLaunchDirective,
|
|
1469
1629
|
resolveAdapter as resolveAdapter3
|
|
1470
1630
|
} from "@noir-ai/adapters";
|
|
1471
|
-
import { loadProjectInfo as
|
|
1631
|
+
import { loadProjectInfo as loadProjectInfo6, NOIR_DIR, resolveArtifactPath } from "@noir-ai/core";
|
|
1472
1632
|
|
|
1473
1633
|
// src/commands/task.ts
|
|
1474
1634
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1475
1635
|
import { writeFileSync } from "fs";
|
|
1476
|
-
import { loadProjectInfo as
|
|
1636
|
+
import { loadProjectInfo as loadProjectInfo5, nextArtifactSequence, sha256Hex } from "@noir-ai/core";
|
|
1477
1637
|
import {
|
|
1478
1638
|
PHASES,
|
|
1479
1639
|
TASK_CLASSES,
|
|
@@ -1497,7 +1657,7 @@ function skillFor(phase) {
|
|
|
1497
1657
|
}
|
|
1498
1658
|
function writeDoneArtifacts(opts, taskId) {
|
|
1499
1659
|
try {
|
|
1500
|
-
const project =
|
|
1660
|
+
const project = loadProjectInfo5(process.cwd());
|
|
1501
1661
|
const conflict = { conflictPolicy: "preserve", interactive: false };
|
|
1502
1662
|
writeChangelogStub(project.root, `- ${taskId}: completed (verify gate passed)`, conflict);
|
|
1503
1663
|
const nextN = nextArtifactSequence(project.root, "adr");
|
|
@@ -1821,7 +1981,7 @@ function runCheck(cmd) {
|
|
|
1821
1981
|
async function taskVerify(opts) {
|
|
1822
1982
|
let project;
|
|
1823
1983
|
try {
|
|
1824
|
-
project =
|
|
1984
|
+
project = loadProjectInfo5(process.cwd());
|
|
1825
1985
|
} catch {
|
|
1826
1986
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
1827
1987
|
}
|
|
@@ -2144,7 +2304,7 @@ async function probeOnly(opts) {
|
|
|
2144
2304
|
async function handoff(opts) {
|
|
2145
2305
|
let project;
|
|
2146
2306
|
try {
|
|
2147
|
-
project =
|
|
2307
|
+
project = loadProjectInfo6(process.cwd());
|
|
2148
2308
|
} catch {
|
|
2149
2309
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
2150
2310
|
}
|
|
@@ -2193,7 +2353,7 @@ import { hostLaunchDirective as hostLaunchDirective2 } from "@noir-ai/adapters";
|
|
|
2193
2353
|
import {
|
|
2194
2354
|
detectActiveMethod as detectActiveMethod2,
|
|
2195
2355
|
latestVersionFromCache as latestVersionFromCache2,
|
|
2196
|
-
loadProjectInfo as
|
|
2356
|
+
loadProjectInfo as loadProjectInfo7,
|
|
2197
2357
|
NOIR_VERSION as NOIR_VERSION2,
|
|
2198
2358
|
readInstallRecord as readInstallRecord2,
|
|
2199
2359
|
readUpdateCache as readUpdateCache2
|
|
@@ -2487,7 +2647,7 @@ async function resolveSections(commands) {
|
|
|
2487
2647
|
// src/commands/home.ts
|
|
2488
2648
|
function tryProject() {
|
|
2489
2649
|
try {
|
|
2490
|
-
const info2 =
|
|
2650
|
+
const info2 = loadProjectInfo7(process.cwd());
|
|
2491
2651
|
return { id: info2.id, name: info2.name, host: info2.config.host };
|
|
2492
2652
|
} catch {
|
|
2493
2653
|
return null;
|
|
@@ -2651,7 +2811,7 @@ async function runMenu(opts, deps) {
|
|
|
2651
2811
|
env: process.env,
|
|
2652
2812
|
configUpdate: (() => {
|
|
2653
2813
|
try {
|
|
2654
|
-
const info2 =
|
|
2814
|
+
const info2 = loadProjectInfo7(process.cwd());
|
|
2655
2815
|
return info2.config.update ?? DEFAULT_UPDATE_CONFIG;
|
|
2656
2816
|
} catch {
|
|
2657
2817
|
return DEFAULT_UPDATE_CONFIG;
|
|
@@ -2701,12 +2861,12 @@ ${COMMANDS_HINT}
|
|
|
2701
2861
|
|
|
2702
2862
|
// src/commands/memory.ts
|
|
2703
2863
|
import { readFileSync as readFileSync3 } from "fs";
|
|
2704
|
-
import { loadProjectInfo as
|
|
2864
|
+
import { loadProjectInfo as loadProjectInfo8 } from "@noir-ai/core";
|
|
2705
2865
|
function resolveWorkspace() {
|
|
2706
2866
|
const routing = workspaceRoutingTarget(process.cwd());
|
|
2707
2867
|
if (routing === null) return null;
|
|
2708
2868
|
try {
|
|
2709
|
-
return { routing, project:
|
|
2869
|
+
return { routing, project: loadProjectInfo8(process.cwd()) };
|
|
2710
2870
|
} catch {
|
|
2711
2871
|
return null;
|
|
2712
2872
|
}
|
|
@@ -3098,9 +3258,9 @@ async function memoryConsolidate(opts) {
|
|
|
3098
3258
|
}
|
|
3099
3259
|
return;
|
|
3100
3260
|
}
|
|
3101
|
-
const
|
|
3102
|
-
const reason = (typeof
|
|
3103
|
-
const logged =
|
|
3261
|
+
const env2 = res;
|
|
3262
|
+
const reason = (typeof env2.reason === "string" ? env2.reason : void 0) ?? (typeof env2.error === "string" ? env2.error : void 0) ?? "unknown";
|
|
3263
|
+
const logged = env2.logged === true || env2.degraded === true;
|
|
3104
3264
|
const suffix = logged ? " (logged)" : "";
|
|
3105
3265
|
fail(EXIT.ERROR, `memory consolidate did not run: ${reason}${suffix}`, opts);
|
|
3106
3266
|
}
|
|
@@ -3202,10 +3362,10 @@ async function release(opts) {
|
|
|
3202
3362
|
}
|
|
3203
3363
|
|
|
3204
3364
|
// src/commands/run.ts
|
|
3205
|
-
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
3365
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
3206
3366
|
import { join as join4 } from "path";
|
|
3207
3367
|
import { SUPPORTED_HOSTS } from "@noir-ai/adapters";
|
|
3208
|
-
import { parseConfig } from "@noir-ai/core";
|
|
3368
|
+
import { loadNoirEnv as loadNoirEnv3, NOIR_DIR as NOIR_DIR2, parseConfig } from "@noir-ai/core";
|
|
3209
3369
|
|
|
3210
3370
|
// src/orchestrator.ts
|
|
3211
3371
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -3219,10 +3379,10 @@ var PROBE_TIMEOUT_MS2 = 3e3;
|
|
|
3219
3379
|
var NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._+-]*$/;
|
|
3220
3380
|
var SUPPORTED_SHELLS = /* @__PURE__ */ new Set(["zsh", "bash", "fish"]);
|
|
3221
3381
|
function isEligibleForShellFallback(name, opts = {}) {
|
|
3222
|
-
const { env = process.env, platform = process.platform } = opts;
|
|
3382
|
+
const { env: env2 = process.env, platform = process.platform } = opts;
|
|
3223
3383
|
if (platform === "win32") return false;
|
|
3224
3384
|
if (name.length === 0 || name.includes("/") || !NAME_RE.test(name)) return false;
|
|
3225
|
-
const shell =
|
|
3385
|
+
const shell = env2.SHELL;
|
|
3226
3386
|
if (!shell || shell.length === 0) return false;
|
|
3227
3387
|
return SUPPORTED_SHELLS.has(basename(shell).toLowerCase());
|
|
3228
3388
|
}
|
|
@@ -3296,9 +3456,9 @@ function probeCommand(name, shell, opts) {
|
|
|
3296
3456
|
}
|
|
3297
3457
|
var pathCache = /* @__PURE__ */ new Map();
|
|
3298
3458
|
async function resolveCommandViaShell(name, opts = {}) {
|
|
3299
|
-
const { env = process.env } = opts;
|
|
3459
|
+
const { env: env2 = process.env } = opts;
|
|
3300
3460
|
if (!isEligibleForShellFallback(name, opts)) return { kind: "unsupported" };
|
|
3301
|
-
const shell =
|
|
3461
|
+
const shell = env2.SHELL;
|
|
3302
3462
|
const cached = pathCache.get(`${shell}:${name}`);
|
|
3303
3463
|
if (cached) return { kind: "path", path: cached, shell };
|
|
3304
3464
|
const probe = await probeCommand(name, shell, opts);
|
|
@@ -3569,24 +3729,24 @@ async function shellFallback(binary, args, opts, resolve, reject, originalErr) {
|
|
|
3569
3729
|
}
|
|
3570
3730
|
|
|
3571
3731
|
// src/run-profiles.ts
|
|
3572
|
-
import { loadProjectInfo as
|
|
3732
|
+
import { loadProjectInfo as loadProjectInfo9 } from "@noir-ai/core";
|
|
3573
3733
|
function loadRunConfig(root) {
|
|
3574
3734
|
try {
|
|
3575
|
-
return
|
|
3735
|
+
return loadProjectInfo9(root).config;
|
|
3576
3736
|
} catch {
|
|
3577
3737
|
return null;
|
|
3578
3738
|
}
|
|
3579
3739
|
}
|
|
3580
|
-
function expandEnvVars(value,
|
|
3740
|
+
function expandEnvVars(value, env2) {
|
|
3581
3741
|
return value.replace(
|
|
3582
3742
|
/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g,
|
|
3583
|
-
(match, name) =>
|
|
3743
|
+
(match, name) => env2[name] !== void 0 ? env2[name] : match
|
|
3584
3744
|
);
|
|
3585
3745
|
}
|
|
3586
|
-
function resolveRunProfile(requested, config,
|
|
3746
|
+
function resolveRunProfile(requested, config, env2) {
|
|
3587
3747
|
const profiles = config.run?.profiles ?? {};
|
|
3588
3748
|
const names = Object.keys(profiles);
|
|
3589
|
-
const name = requested ??
|
|
3749
|
+
const name = requested ?? env2.NOIR_PROFILE ?? config.run?.defaultProfile;
|
|
3590
3750
|
if (name === void 0 || name.length === 0) {
|
|
3591
3751
|
return { ok: true, profile: {} };
|
|
3592
3752
|
}
|
|
@@ -3601,7 +3761,7 @@ function resolveRunProfile(requested, config, env) {
|
|
|
3601
3761
|
const envExpanded = {};
|
|
3602
3762
|
if (profile.env) {
|
|
3603
3763
|
for (const [k, v] of Object.entries(profile.env)) {
|
|
3604
|
-
envExpanded[k] = v === null ? void 0 : expandEnvVars(v,
|
|
3764
|
+
envExpanded[k] = v === null ? void 0 : expandEnvVars(v, env2);
|
|
3605
3765
|
}
|
|
3606
3766
|
}
|
|
3607
3767
|
return {
|
|
@@ -3620,6 +3780,25 @@ function listProfiles(config) {
|
|
|
3620
3780
|
}
|
|
3621
3781
|
|
|
3622
3782
|
// src/commands/run.ts
|
|
3783
|
+
var CREDENTIAL_ENV_VARS = [
|
|
3784
|
+
"ANTHROPIC_API_KEY",
|
|
3785
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
3786
|
+
"ANTHROPIC_BASE_URL"
|
|
3787
|
+
];
|
|
3788
|
+
function credentialNote(env2, sources) {
|
|
3789
|
+
const named = CREDENTIAL_ENV_VARS.filter((name) => {
|
|
3790
|
+
const value = env2[name];
|
|
3791
|
+
return value !== void 0 && value.length > 0;
|
|
3792
|
+
}).map(
|
|
3793
|
+
(name) => `${name} (from ${sources[name] === "file" ? `${NOIR_DIR2}/.env` : "the environment"})`
|
|
3794
|
+
);
|
|
3795
|
+
if (named.length === 0) return "";
|
|
3796
|
+
const one = named.length === 1;
|
|
3797
|
+
const subject = one ? `${named[0]} is set` : `${named.join(", ")} are set`;
|
|
3798
|
+
const pronoun = one ? "it" : "them";
|
|
3799
|
+
const verb = one ? "overrides" : "override";
|
|
3800
|
+
return ` Note: ${subject} \u2014 ${pronoun} ${verb} the logged-in account in \`-p\` mode; unset ${pronoun} in the named source (or fix the value) if you meant to use your subscription.`;
|
|
3801
|
+
}
|
|
3623
3802
|
function formatUsage(u) {
|
|
3624
3803
|
const cost = u.totalCostUsd > 0 ? ` $${u.totalCostUsd.toFixed(2)}` : "";
|
|
3625
3804
|
const turns = u.numTurns > 0 ? ` \xB7 ${u.numTurns} turns` : "";
|
|
@@ -3643,13 +3822,21 @@ async function run(prompt, opts) {
|
|
|
3643
3822
|
const usage = selectors.length > 0 ? `noir run ${selectors.join(" ")} <prompt>` : "noir run <prompt>";
|
|
3644
3823
|
fail(EXIT.USAGE, `a prompt is required: \`${usage}\``, opts);
|
|
3645
3824
|
}
|
|
3646
|
-
const
|
|
3825
|
+
const root = process.cwd();
|
|
3826
|
+
const envSources = loadNoirEnv3(root).sources;
|
|
3827
|
+
const config = loadRunConfig(root) ?? parseConfig({});
|
|
3647
3828
|
const resolved = resolveRunProfile(opts.profile, config, process.env);
|
|
3648
3829
|
if (!resolved.ok) fail(EXIT.USAGE, resolved.message, opts);
|
|
3649
3830
|
const profile = resolved.profile;
|
|
3650
3831
|
const customBinary = opts.command ?? profile.binary;
|
|
3651
3832
|
const extraArgs = profile.args;
|
|
3652
|
-
const
|
|
3833
|
+
const env2 = profile.env ? mergeEnv(process.env, profile.env) : void 0;
|
|
3834
|
+
if (!existsSync3(join4(root, NOIR_DIR2, ".env"))) {
|
|
3835
|
+
log(
|
|
3836
|
+
`This project has no ${NOIR_DIR2}/.env \u2014 that is where Noir reads project credentials and run config from. Run \`noir init\` to scaffold one (\`noir run\` works without it).`,
|
|
3837
|
+
opts
|
|
3838
|
+
);
|
|
3839
|
+
}
|
|
3653
3840
|
const transcriptLines = [];
|
|
3654
3841
|
let result;
|
|
3655
3842
|
try {
|
|
@@ -3658,7 +3845,7 @@ async function run(prompt, opts) {
|
|
|
3658
3845
|
prompt,
|
|
3659
3846
|
customBinary,
|
|
3660
3847
|
extraArgs,
|
|
3661
|
-
env,
|
|
3848
|
+
env: env2,
|
|
3662
3849
|
onLine: (line) => transcriptLines.push(line),
|
|
3663
3850
|
onEvent: (event) => streamEvent(event, opts)
|
|
3664
3851
|
});
|
|
@@ -3679,9 +3866,7 @@ async function run(prompt, opts) {
|
|
|
3679
3866
|
let message = `host '${binary}' failed (exit ${result.exitCode}): ${reason}`;
|
|
3680
3867
|
if (isAuth) {
|
|
3681
3868
|
message += ` Open a terminal and run \`${binary} /login\` (interactive-only \u2014 it cannot run inside \`noir run\`), then retry.`;
|
|
3682
|
-
|
|
3683
|
-
message += ` Note: ANTHROPIC_API_KEY is set in your environment \u2014 it overrides the logged-in account in \`-p\` mode; unset it (or fix the key) if you meant to use your subscription.`;
|
|
3684
|
-
}
|
|
3869
|
+
message += credentialNote(env2 ?? process.env, envSources);
|
|
3685
3870
|
}
|
|
3686
3871
|
message += ` If you use another profile, pass \`--command <binary>\` or define a run profile under run.profiles. transcript: ${transcript}`;
|
|
3687
3872
|
fail(EXIT.ERROR, message, opts);
|
|
@@ -3724,7 +3909,7 @@ function writeTranscript(host, lines) {
|
|
|
3724
3909
|
|
|
3725
3910
|
// src/commands/skills.ts
|
|
3726
3911
|
import { resolveAdapter as resolveAdapter4 } from "@noir-ai/adapters";
|
|
3727
|
-
import { loadProjectInfo as
|
|
3912
|
+
import { loadProjectInfo as loadProjectInfo10 } from "@noir-ai/core";
|
|
3728
3913
|
import {
|
|
3729
3914
|
buildRegistry,
|
|
3730
3915
|
discoverAll,
|
|
@@ -3820,7 +4005,7 @@ async function skillsSync(opts) {
|
|
|
3820
4005
|
const root = process.cwd();
|
|
3821
4006
|
let project;
|
|
3822
4007
|
try {
|
|
3823
|
-
project =
|
|
4008
|
+
project = loadProjectInfo10(root);
|
|
3824
4009
|
} catch {
|
|
3825
4010
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
3826
4011
|
}
|
|
@@ -3954,7 +4139,7 @@ async function skillsRegistry(opts) {
|
|
|
3954
4139
|
|
|
3955
4140
|
// src/init.ts
|
|
3956
4141
|
import { resolveAdapter as resolveAdapter5 } from "@noir-ai/adapters";
|
|
3957
|
-
import { loadProjectInfo as
|
|
4142
|
+
import { loadProjectInfo as loadProjectInfo11 } from "@noir-ai/core";
|
|
3958
4143
|
import { scaffold } from "@noir-ai/create";
|
|
3959
4144
|
import { emitSkillsToDir as emitSkillsToDir2 } from "@noir-ai/skills";
|
|
3960
4145
|
|
|
@@ -4327,7 +4512,7 @@ async function init(root, opts) {
|
|
|
4327
4512
|
await emitHostSkills(root, host, conflictOpts, interactive);
|
|
4328
4513
|
let projectInfo;
|
|
4329
4514
|
try {
|
|
4330
|
-
projectInfo =
|
|
4515
|
+
projectInfo = loadProjectInfo11(root);
|
|
4331
4516
|
} catch {
|
|
4332
4517
|
projectInfo = void 0;
|
|
4333
4518
|
}
|
|
@@ -4346,7 +4531,10 @@ function reportPlannedWrites(res) {
|
|
|
4346
4531
|
log("Dry run \u2014 no files were written.");
|
|
4347
4532
|
if (res.written.length > 0) {
|
|
4348
4533
|
log("Planned writes:");
|
|
4349
|
-
for (const p of res.written)
|
|
4534
|
+
for (const p of res.written) {
|
|
4535
|
+
const mode = res.fileModes?.[p];
|
|
4536
|
+
log(mode === void 0 ? ` ${p}` : ` ${p} (mode ${mode.toString(8).padStart(3, "0")})`);
|
|
4537
|
+
}
|
|
4350
4538
|
}
|
|
4351
4539
|
if (res.skipped.length > 0) {
|
|
4352
4540
|
log("Would leave as-is (already present):");
|
|
@@ -4406,11 +4594,11 @@ function assertLocalhostUrl(raw) {
|
|
|
4406
4594
|
}
|
|
4407
4595
|
|
|
4408
4596
|
// src/serve.ts
|
|
4409
|
-
import { applyNoirEnv, loadProjectInfo as
|
|
4597
|
+
import { applyNoirEnv, loadProjectInfo as loadProjectInfo12 } from "@noir-ai/core";
|
|
4410
4598
|
import { ensureDaemonRunning as ensureDaemonRunning2, startStdioServer } from "@noir-ai/daemon";
|
|
4411
4599
|
async function serve(opts) {
|
|
4412
4600
|
applyNoirEnv(process.cwd());
|
|
4413
|
-
const project =
|
|
4601
|
+
const project = loadProjectInfo12(process.cwd());
|
|
4414
4602
|
if (opts.stdio) {
|
|
4415
4603
|
await startStdioServer({ project, transport: "stdio", daemon: false });
|
|
4416
4604
|
return;
|
|
@@ -4418,10 +4606,11 @@ async function serve(opts) {
|
|
|
4418
4606
|
try {
|
|
4419
4607
|
const { url } = await ensureDaemonRunning2({
|
|
4420
4608
|
project,
|
|
4421
|
-
idleTimeoutSec: project.config.daemon.idleTimeoutSec
|
|
4609
|
+
idleTimeoutSec: project.config.daemon.idleTimeoutSec,
|
|
4610
|
+
...project.config.daemon.port !== void 0 ? { port: project.config.daemon.port } : {}
|
|
4422
4611
|
});
|
|
4423
4612
|
process.stderr.write(
|
|
4424
|
-
`Noir daemon available at ${url}. (For HTTP clients, use this URL in .mcp.json.)
|
|
4613
|
+
`Noir daemon available at ${url}. (For HTTP clients, use this URL in .mcp.json.) Add the daemon's bearer token via the host MCP headersHelper (run \`noir daemon token\`).
|
|
4425
4614
|
`
|
|
4426
4615
|
);
|
|
4427
4616
|
} catch (err) {
|
|
@@ -4534,6 +4723,7 @@ function createProgram() {
|
|
|
4534
4723
|
);
|
|
4535
4724
|
}
|
|
4536
4725
|
}
|
|
4726
|
+
if (actionCmd.name() === "env") captureAmbientEnv();
|
|
4537
4727
|
applyNoirEnv2(process.cwd());
|
|
4538
4728
|
const nonInteractive = opts.json === true || opts.input === false;
|
|
4539
4729
|
if (nonInteractive) process.env.NOIR_NON_INTERACTIVE = "1";
|
|
@@ -4583,7 +4773,7 @@ function createProgram() {
|
|
|
4583
4773
|
const force = opts.force === true;
|
|
4584
4774
|
const host = parseHost(opts.host);
|
|
4585
4775
|
const dryRun = opts.dryRun === true || opts.preview === true;
|
|
4586
|
-
const { create } = await import("./create-
|
|
4776
|
+
const { create } = await import("./create-FD4E626Z.js");
|
|
4587
4777
|
const result = await create(dir, {
|
|
4588
4778
|
transport,
|
|
4589
4779
|
url: opts.url,
|
|
@@ -4622,7 +4812,7 @@ function createProgram() {
|
|
|
4622
4812
|
).choices(SUPPORTED_HOSTS2)
|
|
4623
4813
|
).action(
|
|
4624
4814
|
async (opts, cmd) => {
|
|
4625
|
-
const { sync } = await import("./sync-
|
|
4815
|
+
const { sync } = await import("./sync-N633SQSS.js");
|
|
4626
4816
|
const host = parseHost(opts.host);
|
|
4627
4817
|
const force = opts.force === true;
|
|
4628
4818
|
const merge = opts.merge === true;
|
|
@@ -4676,6 +4866,9 @@ function createProgram() {
|
|
|
4676
4866
|
daemonGrp.command("status").description("report daemon pid/uptime/mode (exit 4 if not running)").action(async (...args) => {
|
|
4677
4867
|
await daemonStatus(toCliOptions(trailingCmd(args).optsWithGlobals()));
|
|
4678
4868
|
});
|
|
4869
|
+
daemonGrp.command("token").description("print the daemon bearer token to stdout (for a host headersHelper)").action(async (...args) => {
|
|
4870
|
+
await daemonToken(toCliOptions(trailingCmd(args).optsWithGlobals()));
|
|
4871
|
+
});
|
|
4679
4872
|
daemonGrp.command("restart").description("stop then start the daemon").option("--detach", "run the daemon in the background and exit").action(async (...args) => {
|
|
4680
4873
|
const cmd = trailingCmd(args);
|
|
4681
4874
|
const g = cmd.optsWithGlobals();
|
|
@@ -4685,8 +4878,19 @@ function createProgram() {
|
|
|
4685
4878
|
});
|
|
4686
4879
|
});
|
|
4687
4880
|
daemonGrp.action(() => {
|
|
4688
|
-
throw new NoirCliError(EXIT.USAGE, "Usage: noir daemon start|join|stop|status|restart");
|
|
4881
|
+
throw new NoirCliError(EXIT.USAGE, "Usage: noir daemon start|join|stop|status|restart|token");
|
|
4689
4882
|
});
|
|
4883
|
+
daemonGrp.addHelpText(
|
|
4884
|
+
"after",
|
|
4885
|
+
[
|
|
4886
|
+
"",
|
|
4887
|
+
"Shared workspaces (two repos, one product \u2014 e.g. backend + frontend):",
|
|
4888
|
+
" noir daemon start --workspace <name> --detach found a workspace from this repo",
|
|
4889
|
+
" noir daemon join <name> join it from the other repo",
|
|
4890
|
+
"",
|
|
4891
|
+
"Guide: docs/how-to/shared-workspaces.md"
|
|
4892
|
+
].join("\n")
|
|
4893
|
+
);
|
|
4690
4894
|
const workspaceGrp = program2.command("workspace").description("shared cross-repo workspaces");
|
|
4691
4895
|
workspaceGrp.command("list").description("list workspaces on this machine").action(async (...args) => {
|
|
4692
4896
|
await workspaceList(toCliOptions(trailingCmd(args).optsWithGlobals()));
|
|
@@ -4715,6 +4919,17 @@ function createProgram() {
|
|
|
4715
4919
|
...name !== void 0 ? { name } : {}
|
|
4716
4920
|
});
|
|
4717
4921
|
});
|
|
4922
|
+
workspaceGrp.addHelpText(
|
|
4923
|
+
"after",
|
|
4924
|
+
[
|
|
4925
|
+
"",
|
|
4926
|
+
"This group only inspects and leaves workspaces. To CREATE or JOIN one:",
|
|
4927
|
+
" noir daemon start --workspace <name> --detach found it (from the first repo)",
|
|
4928
|
+
" noir daemon join <name> join it (from the second repo)",
|
|
4929
|
+
"",
|
|
4930
|
+
"Guide: docs/how-to/shared-workspaces.md"
|
|
4931
|
+
].join("\n")
|
|
4932
|
+
);
|
|
4718
4933
|
workspaceGrp.action(() => {
|
|
4719
4934
|
throw new NoirCliError(EXIT.USAGE, "Usage: noir workspace list|status|leave|stop");
|
|
4720
4935
|
});
|
|
@@ -4729,6 +4944,9 @@ function createProgram() {
|
|
|
4729
4944
|
program2.command("status").description("project + daemon + workflow + store snapshot").action(async (...args) => {
|
|
4730
4945
|
await status(toStatusOptions(actionGlobals(args)));
|
|
4731
4946
|
});
|
|
4947
|
+
program2.command("env").description("which configuration is in effect and where each value comes from").action(async (...args) => {
|
|
4948
|
+
await env(toCliOptions(actionGlobals(args)));
|
|
4949
|
+
});
|
|
4732
4950
|
const contextGrp = program2.command("context").description("context engine");
|
|
4733
4951
|
contextGrp.command("search").description("hybrid search over the indexed context").argument("<query>", "search query").option("--limit <n>", "max results", "10").action(async (...args) => {
|
|
4734
4952
|
const cmd = trailingCmd(args);
|
|
@@ -5154,7 +5372,18 @@ function failDaemonDown(opts, cause) {
|
|
|
5154
5372
|
fail(EXIT.DAEMON_DOWN, DAEMON_DOWN_HINT, opts);
|
|
5155
5373
|
}
|
|
5156
5374
|
async function probeDaemon(opts = {}) {
|
|
5157
|
-
const
|
|
5375
|
+
const expectedProject = opts.project?.id ?? (() => {
|
|
5376
|
+
try {
|
|
5377
|
+
return loadProjectInfo13(process.cwd()).id;
|
|
5378
|
+
} catch {
|
|
5379
|
+
return void 0;
|
|
5380
|
+
}
|
|
5381
|
+
})();
|
|
5382
|
+
if (expectedProject === void 0) {
|
|
5383
|
+
if (opts.verbose) process.stderr.write("noir: daemon probe: no project (uninitialized)\n");
|
|
5384
|
+
return { running: false };
|
|
5385
|
+
}
|
|
5386
|
+
const rec = readProjectDaemonRecord3(expectedProject);
|
|
5158
5387
|
if (!rec) {
|
|
5159
5388
|
if (opts.verbose) process.stderr.write("noir: daemon probe: no daemon record\n");
|
|
5160
5389
|
return { running: false };
|
|
@@ -5175,19 +5404,10 @@ async function probeDaemon(opts = {}) {
|
|
|
5175
5404
|
}
|
|
5176
5405
|
const body = await res.json().catch(() => null);
|
|
5177
5406
|
const pidOk = typeof body?.pid === "number" && body.pid === rec.pid;
|
|
5178
|
-
|
|
5179
|
-
try {
|
|
5180
|
-
return loadProjectInfo12(process.cwd()).id;
|
|
5181
|
-
} catch {
|
|
5182
|
-
return void 0;
|
|
5183
|
-
}
|
|
5184
|
-
})();
|
|
5185
|
-
const projectOk = expectedProject === void 0 || body?.projectId !== void 0 && body.projectId === expectedProject;
|
|
5186
|
-
if (body?.ok !== true || !pidOk || !projectOk) {
|
|
5407
|
+
if (body?.ok !== true || !pidOk) {
|
|
5187
5408
|
if (opts.verbose)
|
|
5188
5409
|
process.stderr.write(
|
|
5189
|
-
!pidOk ? "noir: daemon probe: pid missing/mismatched (foreign process on recorded port)\n" :
|
|
5190
|
-
` : "noir: daemon probe: /health body not ok\n"
|
|
5410
|
+
!pidOk ? "noir: daemon probe: pid missing/mismatched (foreign process on recorded port)\n" : "noir: daemon probe: /health body not ok\n"
|
|
5191
5411
|
);
|
|
5192
5412
|
return { running: false };
|
|
5193
5413
|
}
|
|
@@ -5207,25 +5427,112 @@ async function probeDaemon(opts = {}) {
|
|
|
5207
5427
|
async function resolveDaemon(opts) {
|
|
5208
5428
|
let project;
|
|
5209
5429
|
try {
|
|
5210
|
-
project = opts.project ??
|
|
5430
|
+
project = opts.project ?? loadProjectInfo13(process.cwd());
|
|
5211
5431
|
} catch {
|
|
5212
5432
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
5213
5433
|
}
|
|
5214
5434
|
const idleTimeoutSec = opts.idleTimeoutSec ?? project.config.daemon.idleTimeoutSec;
|
|
5435
|
+
const preferred = project.config.daemon.port;
|
|
5436
|
+
if (preferred !== void 0 && preferred !== 0) {
|
|
5437
|
+
const direct = `http://127.0.0.1:${preferred}/mcp`;
|
|
5438
|
+
const probe = newClient();
|
|
5439
|
+
let probeTimer;
|
|
5440
|
+
try {
|
|
5441
|
+
await Promise.race([
|
|
5442
|
+
probe.connect(httpTransport(direct, project.id)),
|
|
5443
|
+
new Promise((_, reject) => {
|
|
5444
|
+
probeTimer = setTimeout(
|
|
5445
|
+
() => reject(new Error(`no response within ${PROBE_TIMEOUT_MS}ms`)),
|
|
5446
|
+
PROBE_TIMEOUT_MS
|
|
5447
|
+
);
|
|
5448
|
+
})
|
|
5449
|
+
]);
|
|
5450
|
+
return { client: probe, stop: async () => {
|
|
5451
|
+
} };
|
|
5452
|
+
} catch (err) {
|
|
5453
|
+
if (opts.verbose) {
|
|
5454
|
+
process.stderr.write(
|
|
5455
|
+
`noir: no daemon at the configured port ${preferred} (${describeCause(err)})
|
|
5456
|
+
`
|
|
5457
|
+
);
|
|
5458
|
+
}
|
|
5459
|
+
await probe.close().catch(() => {
|
|
5460
|
+
});
|
|
5461
|
+
} finally {
|
|
5462
|
+
if (probeTimer !== void 0) clearTimeout(probeTimer);
|
|
5463
|
+
}
|
|
5464
|
+
}
|
|
5465
|
+
let url;
|
|
5466
|
+
let stop;
|
|
5215
5467
|
try {
|
|
5216
|
-
const ensured = await ensureDaemonRunning3({
|
|
5217
|
-
|
|
5468
|
+
const ensured = await ensureDaemonRunning3({
|
|
5469
|
+
project,
|
|
5470
|
+
idleTimeoutSec,
|
|
5471
|
+
...preferred !== void 0 ? { port: preferred } : {}
|
|
5472
|
+
});
|
|
5473
|
+
url = ensured.url;
|
|
5474
|
+
stop = ensured.stop;
|
|
5218
5475
|
} catch (err) {
|
|
5219
5476
|
failDaemonDown(opts, err);
|
|
5220
5477
|
}
|
|
5478
|
+
const client = newClient();
|
|
5479
|
+
try {
|
|
5480
|
+
await connectClient(client, url, opts, project.id);
|
|
5481
|
+
} catch (err) {
|
|
5482
|
+
await client.close().catch(() => {
|
|
5483
|
+
});
|
|
5484
|
+
await stop().catch(() => {
|
|
5485
|
+
});
|
|
5486
|
+
throw err;
|
|
5487
|
+
}
|
|
5488
|
+
return { client, stop };
|
|
5489
|
+
}
|
|
5490
|
+
function httpTransport(url, scopeKey) {
|
|
5491
|
+
const token = scopeKey === null ? null : readDaemonToken2(scopeKey);
|
|
5492
|
+
return new StreamableHTTPClientTransport(
|
|
5493
|
+
new URL(url),
|
|
5494
|
+
// Verified present: StreamableHTTPClientTransportOptions.requestInit?: RequestInit
|
|
5495
|
+
token ? { requestInit: { headers: { Authorization: `Bearer ${token}` } } } : {}
|
|
5496
|
+
);
|
|
5497
|
+
}
|
|
5498
|
+
function callerScopeKey(opts) {
|
|
5499
|
+
try {
|
|
5500
|
+
return (opts.project ?? loadProjectInfo13(process.cwd())).id;
|
|
5501
|
+
} catch {
|
|
5502
|
+
return null;
|
|
5503
|
+
}
|
|
5504
|
+
}
|
|
5505
|
+
function newClient() {
|
|
5506
|
+
return new Client(
|
|
5507
|
+
{ name: "noir-cli", version: NOIR_VERSION4 },
|
|
5508
|
+
{ versionNegotiation: { mode: "auto" } }
|
|
5509
|
+
);
|
|
5510
|
+
}
|
|
5511
|
+
async function connectRaw(client, url, scopeKey) {
|
|
5512
|
+
await client.connect(httpTransport(url, scopeKey));
|
|
5221
5513
|
}
|
|
5222
|
-
async function connectClient(client, url, opts) {
|
|
5514
|
+
async function connectClient(client, url, opts, scopeKey) {
|
|
5223
5515
|
try {
|
|
5224
|
-
await client
|
|
5516
|
+
await connectRaw(client, url, scopeKey);
|
|
5225
5517
|
} catch (err) {
|
|
5226
5518
|
failDaemonDown(opts, err);
|
|
5227
5519
|
}
|
|
5228
5520
|
}
|
|
5521
|
+
function failWorkspaceConnect(opts, routing, cause) {
|
|
5522
|
+
const text = String(cause).toLowerCase();
|
|
5523
|
+
if (text.includes("401") || text.includes("unauthorized")) {
|
|
5524
|
+
if (opts.verbose) {
|
|
5525
|
+
process.stderr.write(`noir: daemon transport detail: ${describeCause(cause)}
|
|
5526
|
+
`);
|
|
5527
|
+
}
|
|
5528
|
+
fail(
|
|
5529
|
+
EXIT.DAEMON_DOWN,
|
|
5530
|
+
`workspace ${JSON.stringify(routing.name)} daemon is running but rejected this client's token (HTTP 401) \u2014 its token file is ${tokenPath(routing.name)}; make sure it is readable, or restart the workspace daemon with \`noir daemon start --workspace ${routing.name}\` to mint a fresh one.`,
|
|
5531
|
+
opts
|
|
5532
|
+
);
|
|
5533
|
+
}
|
|
5534
|
+
failDaemonDown(opts, cause);
|
|
5535
|
+
}
|
|
5229
5536
|
async function callToolParse(client, opts, name, args) {
|
|
5230
5537
|
const result = await client.callTool({ name, arguments: args }).catch((err) => failDaemonDown(opts, err));
|
|
5231
5538
|
const block = result.content?.[0];
|
|
@@ -5256,21 +5563,12 @@ function buildCaller(client, opts) {
|
|
|
5256
5563
|
};
|
|
5257
5564
|
}
|
|
5258
5565
|
async function withDaemon(opts, fn) {
|
|
5259
|
-
const {
|
|
5260
|
-
let client;
|
|
5566
|
+
const { client, stop } = await resolveDaemon(opts);
|
|
5261
5567
|
try {
|
|
5262
|
-
|
|
5263
|
-
{ name: "noir-cli", version: NOIR_VERSION4 },
|
|
5264
|
-
{ versionNegotiation: { mode: "auto" } }
|
|
5265
|
-
);
|
|
5266
|
-
const connected = client;
|
|
5267
|
-
await connectClient(connected, url, opts);
|
|
5268
|
-
return await fn(buildCaller(connected, opts));
|
|
5568
|
+
return await fn(buildCaller(client, opts));
|
|
5269
5569
|
} finally {
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
});
|
|
5273
|
-
}
|
|
5570
|
+
await client.close().catch(() => {
|
|
5571
|
+
});
|
|
5274
5572
|
await stop().catch(() => {
|
|
5275
5573
|
});
|
|
5276
5574
|
}
|
|
@@ -5281,12 +5579,9 @@ async function withRunningDaemon(opts, fn, probe) {
|
|
|
5281
5579
|
const url = `http://127.0.0.1:${p.port}/mcp`;
|
|
5282
5580
|
let client;
|
|
5283
5581
|
try {
|
|
5284
|
-
client =
|
|
5285
|
-
{ name: "noir-cli", version: NOIR_VERSION4 },
|
|
5286
|
-
{ versionNegotiation: { mode: "auto" } }
|
|
5287
|
-
);
|
|
5582
|
+
client = newClient();
|
|
5288
5583
|
const connected = client;
|
|
5289
|
-
await connected.connect(
|
|
5584
|
+
await connected.connect(httpTransport(url, callerScopeKey(opts)));
|
|
5290
5585
|
return await fn(buildCaller(connected, opts));
|
|
5291
5586
|
} catch {
|
|
5292
5587
|
return null;
|
|
@@ -5303,7 +5598,7 @@ async function callDaemonTool(opts, name, args = {}) {
|
|
|
5303
5598
|
async function withInProcessRead(opts, fn) {
|
|
5304
5599
|
let project;
|
|
5305
5600
|
try {
|
|
5306
|
-
project = opts.project ??
|
|
5601
|
+
project = opts.project ?? loadProjectInfo13(process.cwd());
|
|
5307
5602
|
} catch {
|
|
5308
5603
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
5309
5604
|
}
|
|
@@ -5350,7 +5645,7 @@ function workspaceRoutingTarget(root) {
|
|
|
5350
5645
|
const name = readWorkspaceMarker2(root);
|
|
5351
5646
|
if (name === null) return null;
|
|
5352
5647
|
try {
|
|
5353
|
-
return { name, projectId:
|
|
5648
|
+
return { name, projectId: loadProjectInfo13(root).id };
|
|
5354
5649
|
} catch {
|
|
5355
5650
|
return null;
|
|
5356
5651
|
}
|
|
@@ -5384,12 +5679,11 @@ async function withWorkspaceDaemon(opts, routing, fn) {
|
|
|
5384
5679
|
const url = `http://127.0.0.1:${probe.port}/mcp?p=${routing.projectId}`;
|
|
5385
5680
|
let client;
|
|
5386
5681
|
try {
|
|
5387
|
-
client =
|
|
5388
|
-
{ name: "noir-cli", version: NOIR_VERSION4 },
|
|
5389
|
-
{ versionNegotiation: { mode: "auto" } }
|
|
5390
|
-
);
|
|
5682
|
+
client = newClient();
|
|
5391
5683
|
const connected = client;
|
|
5392
|
-
await
|
|
5684
|
+
await connectRaw(connected, url, routing.name).catch(
|
|
5685
|
+
(err) => failWorkspaceConnect(opts, routing, err)
|
|
5686
|
+
);
|
|
5393
5687
|
return await fn(buildCaller(connected, opts));
|
|
5394
5688
|
} finally {
|
|
5395
5689
|
if (client) {
|
|
@@ -5553,7 +5847,7 @@ function renderHuman2(p, opts) {
|
|
|
5553
5847
|
async function gatherStatusPayload(opts) {
|
|
5554
5848
|
let project;
|
|
5555
5849
|
try {
|
|
5556
|
-
project =
|
|
5850
|
+
project = loadProjectInfo14(process.cwd());
|
|
5557
5851
|
} catch {
|
|
5558
5852
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
5559
5853
|
}
|
|
@@ -5583,7 +5877,7 @@ async function status(opts) {
|
|
|
5583
5877
|
env: process.env,
|
|
5584
5878
|
configUpdate: (() => {
|
|
5585
5879
|
try {
|
|
5586
|
-
const info2 =
|
|
5880
|
+
const info2 = loadProjectInfo14(process.cwd());
|
|
5587
5881
|
return info2.config.update ?? DEFAULT_UPDATE_CONFIG;
|
|
5588
5882
|
} catch {
|
|
5589
5883
|
return DEFAULT_UPDATE_CONFIG;
|
|
@@ -5615,4 +5909,4 @@ export {
|
|
|
5615
5909
|
assertTransportUrl,
|
|
5616
5910
|
buildPaletteCommandsForTui
|
|
5617
5911
|
};
|
|
5618
|
-
//# sourceMappingURL=chunk-
|
|
5912
|
+
//# sourceMappingURL=chunk-6LO4LXEH.js.map
|