@noir-ai/cli 1.13.0 → 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
package/dist/bin.js
CHANGED
|
@@ -26,9 +26,11 @@ import {
|
|
|
26
26
|
import {
|
|
27
27
|
ensureDaemonRunning,
|
|
28
28
|
pidAlive,
|
|
29
|
-
|
|
29
|
+
readDaemonToken,
|
|
30
|
+
readProjectDaemonRecord,
|
|
30
31
|
readWorkspaceDaemonRecord,
|
|
31
|
-
resolveGateConfig
|
|
32
|
+
resolveGateConfig,
|
|
33
|
+
tokenPath
|
|
32
34
|
} from "@noir-ai/daemon";
|
|
33
35
|
import { createMemoryEngine } from "@noir-ai/memory";
|
|
34
36
|
import { openStore } from "@noir-ai/store";
|
|
@@ -49,7 +51,18 @@ function failDaemonDown(opts, cause) {
|
|
|
49
51
|
fail(EXIT.DAEMON_DOWN, DAEMON_DOWN_HINT, opts);
|
|
50
52
|
}
|
|
51
53
|
async function probeDaemon(opts = {}) {
|
|
52
|
-
const
|
|
54
|
+
const expectedProject = opts.project?.id ?? (() => {
|
|
55
|
+
try {
|
|
56
|
+
return loadProjectInfo(process.cwd()).id;
|
|
57
|
+
} catch {
|
|
58
|
+
return void 0;
|
|
59
|
+
}
|
|
60
|
+
})();
|
|
61
|
+
if (expectedProject === void 0) {
|
|
62
|
+
if (opts.verbose) process.stderr.write("noir: daemon probe: no project (uninitialized)\n");
|
|
63
|
+
return { running: false };
|
|
64
|
+
}
|
|
65
|
+
const rec = readProjectDaemonRecord(expectedProject);
|
|
53
66
|
if (!rec) {
|
|
54
67
|
if (opts.verbose) process.stderr.write("noir: daemon probe: no daemon record\n");
|
|
55
68
|
return { running: false };
|
|
@@ -70,19 +83,10 @@ async function probeDaemon(opts = {}) {
|
|
|
70
83
|
}
|
|
71
84
|
const body = await res.json().catch(() => null);
|
|
72
85
|
const pidOk = typeof body?.pid === "number" && body.pid === rec.pid;
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
return loadProjectInfo(process.cwd()).id;
|
|
76
|
-
} catch {
|
|
77
|
-
return void 0;
|
|
78
|
-
}
|
|
79
|
-
})();
|
|
80
|
-
const projectOk = expectedProject === void 0 || body?.projectId !== void 0 && body.projectId === expectedProject;
|
|
81
|
-
if (body?.ok !== true || !pidOk || !projectOk) {
|
|
86
|
+
if (body?.ok !== true || !pidOk) {
|
|
82
87
|
if (opts.verbose)
|
|
83
88
|
process.stderr.write(
|
|
84
|
-
!pidOk ? "noir: daemon probe: pid missing/mismatched (foreign process on recorded port)\n" :
|
|
85
|
-
` : "noir: daemon probe: /health body not ok\n"
|
|
89
|
+
!pidOk ? "noir: daemon probe: pid missing/mismatched (foreign process on recorded port)\n" : "noir: daemon probe: /health body not ok\n"
|
|
86
90
|
);
|
|
87
91
|
return { running: false };
|
|
88
92
|
}
|
|
@@ -107,20 +111,107 @@ async function resolveDaemon(opts) {
|
|
|
107
111
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
108
112
|
}
|
|
109
113
|
const idleTimeoutSec = opts.idleTimeoutSec ?? project.config.daemon.idleTimeoutSec;
|
|
114
|
+
const preferred = project.config.daemon.port;
|
|
115
|
+
if (preferred !== void 0 && preferred !== 0) {
|
|
116
|
+
const direct = `http://127.0.0.1:${preferred}/mcp`;
|
|
117
|
+
const probe = newClient();
|
|
118
|
+
let probeTimer;
|
|
119
|
+
try {
|
|
120
|
+
await Promise.race([
|
|
121
|
+
probe.connect(httpTransport(direct, project.id)),
|
|
122
|
+
new Promise((_, reject) => {
|
|
123
|
+
probeTimer = setTimeout(
|
|
124
|
+
() => reject(new Error(`no response within ${PROBE_TIMEOUT_MS}ms`)),
|
|
125
|
+
PROBE_TIMEOUT_MS
|
|
126
|
+
);
|
|
127
|
+
})
|
|
128
|
+
]);
|
|
129
|
+
return { client: probe, stop: async () => {
|
|
130
|
+
} };
|
|
131
|
+
} catch (err) {
|
|
132
|
+
if (opts.verbose) {
|
|
133
|
+
process.stderr.write(
|
|
134
|
+
`noir: no daemon at the configured port ${preferred} (${describeCause(err)})
|
|
135
|
+
`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
await probe.close().catch(() => {
|
|
139
|
+
});
|
|
140
|
+
} finally {
|
|
141
|
+
if (probeTimer !== void 0) clearTimeout(probeTimer);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
let url;
|
|
145
|
+
let stop;
|
|
110
146
|
try {
|
|
111
|
-
const ensured = await ensureDaemonRunning({
|
|
112
|
-
|
|
147
|
+
const ensured = await ensureDaemonRunning({
|
|
148
|
+
project,
|
|
149
|
+
idleTimeoutSec,
|
|
150
|
+
...preferred !== void 0 ? { port: preferred } : {}
|
|
151
|
+
});
|
|
152
|
+
url = ensured.url;
|
|
153
|
+
stop = ensured.stop;
|
|
113
154
|
} catch (err) {
|
|
114
155
|
failDaemonDown(opts, err);
|
|
115
156
|
}
|
|
157
|
+
const client = newClient();
|
|
158
|
+
try {
|
|
159
|
+
await connectClient(client, url, opts, project.id);
|
|
160
|
+
} catch (err) {
|
|
161
|
+
await client.close().catch(() => {
|
|
162
|
+
});
|
|
163
|
+
await stop().catch(() => {
|
|
164
|
+
});
|
|
165
|
+
throw err;
|
|
166
|
+
}
|
|
167
|
+
return { client, stop };
|
|
168
|
+
}
|
|
169
|
+
function httpTransport(url, scopeKey) {
|
|
170
|
+
const token = scopeKey === null ? null : readDaemonToken(scopeKey);
|
|
171
|
+
return new StreamableHTTPClientTransport(
|
|
172
|
+
new URL(url),
|
|
173
|
+
// Verified present: StreamableHTTPClientTransportOptions.requestInit?: RequestInit
|
|
174
|
+
token ? { requestInit: { headers: { Authorization: `Bearer ${token}` } } } : {}
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
function callerScopeKey(opts) {
|
|
178
|
+
try {
|
|
179
|
+
return (opts.project ?? loadProjectInfo(process.cwd())).id;
|
|
180
|
+
} catch {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function newClient() {
|
|
185
|
+
return new Client(
|
|
186
|
+
{ name: "noir-cli", version: NOIR_VERSION },
|
|
187
|
+
{ versionNegotiation: { mode: "auto" } }
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
async function connectRaw(client, url, scopeKey) {
|
|
191
|
+
await client.connect(httpTransport(url, scopeKey));
|
|
116
192
|
}
|
|
117
|
-
async function connectClient(client, url, opts) {
|
|
193
|
+
async function connectClient(client, url, opts, scopeKey) {
|
|
118
194
|
try {
|
|
119
|
-
await client
|
|
195
|
+
await connectRaw(client, url, scopeKey);
|
|
120
196
|
} catch (err) {
|
|
121
197
|
failDaemonDown(opts, err);
|
|
122
198
|
}
|
|
123
199
|
}
|
|
200
|
+
function failWorkspaceConnect(opts, routing, cause) {
|
|
201
|
+
const text = String(cause).toLowerCase();
|
|
202
|
+
if (text.includes("401") || text.includes("unauthorized")) {
|
|
203
|
+
if (opts.verbose) {
|
|
204
|
+
process.stderr.write(`noir: daemon transport detail: ${describeCause(cause)}
|
|
205
|
+
`);
|
|
206
|
+
}
|
|
207
|
+
fail(
|
|
208
|
+
EXIT.DAEMON_DOWN,
|
|
209
|
+
`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.`,
|
|
210
|
+
opts
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
failDaemonDown(opts, cause);
|
|
214
|
+
}
|
|
124
215
|
async function callToolParse(client, opts, name, args) {
|
|
125
216
|
const result = await client.callTool({ name, arguments: args }).catch((err) => failDaemonDown(opts, err));
|
|
126
217
|
const block = result.content?.[0];
|
|
@@ -151,21 +242,12 @@ function buildCaller(client, opts) {
|
|
|
151
242
|
};
|
|
152
243
|
}
|
|
153
244
|
async function withDaemon(opts, fn) {
|
|
154
|
-
const {
|
|
155
|
-
let client;
|
|
245
|
+
const { client, stop } = await resolveDaemon(opts);
|
|
156
246
|
try {
|
|
157
|
-
|
|
158
|
-
{ name: "noir-cli", version: NOIR_VERSION },
|
|
159
|
-
{ versionNegotiation: { mode: "auto" } }
|
|
160
|
-
);
|
|
161
|
-
const connected = client;
|
|
162
|
-
await connectClient(connected, url, opts);
|
|
163
|
-
return await fn(buildCaller(connected, opts));
|
|
247
|
+
return await fn(buildCaller(client, opts));
|
|
164
248
|
} finally {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
});
|
|
168
|
-
}
|
|
249
|
+
await client.close().catch(() => {
|
|
250
|
+
});
|
|
169
251
|
await stop().catch(() => {
|
|
170
252
|
});
|
|
171
253
|
}
|
|
@@ -176,12 +258,9 @@ async function withRunningDaemon(opts, fn, probe) {
|
|
|
176
258
|
const url = `http://127.0.0.1:${p.port}/mcp`;
|
|
177
259
|
let client;
|
|
178
260
|
try {
|
|
179
|
-
client =
|
|
180
|
-
{ name: "noir-cli", version: NOIR_VERSION },
|
|
181
|
-
{ versionNegotiation: { mode: "auto" } }
|
|
182
|
-
);
|
|
261
|
+
client = newClient();
|
|
183
262
|
const connected = client;
|
|
184
|
-
await connected.connect(
|
|
263
|
+
await connected.connect(httpTransport(url, callerScopeKey(opts)));
|
|
185
264
|
return await fn(buildCaller(connected, opts));
|
|
186
265
|
} catch {
|
|
187
266
|
return null;
|
|
@@ -279,12 +358,11 @@ async function withWorkspaceDaemon(opts, routing, fn) {
|
|
|
279
358
|
const url = `http://127.0.0.1:${probe.port}/mcp?p=${routing.projectId}`;
|
|
280
359
|
let client;
|
|
281
360
|
try {
|
|
282
|
-
client =
|
|
283
|
-
{ name: "noir-cli", version: NOIR_VERSION },
|
|
284
|
-
{ versionNegotiation: { mode: "auto" } }
|
|
285
|
-
);
|
|
361
|
+
client = newClient();
|
|
286
362
|
const connected = client;
|
|
287
|
-
await
|
|
363
|
+
await connectRaw(connected, url, routing.name).catch(
|
|
364
|
+
(err) => failWorkspaceConnect(opts, routing, err)
|
|
365
|
+
);
|
|
288
366
|
return await fn(buildCaller(connected, opts));
|
|
289
367
|
} finally {
|
|
290
368
|
if (client) {
|
|
@@ -1179,10 +1257,11 @@ var init_workspace = __esm({
|
|
|
1179
1257
|
// src/commands/daemon.ts
|
|
1180
1258
|
import { loadProjectInfo as loadProjectInfo3 } from "@noir-ai/core";
|
|
1181
1259
|
import {
|
|
1182
|
-
|
|
1260
|
+
clearProjectDaemonRecord,
|
|
1183
1261
|
ensureDaemonRunning as ensureDaemonRunning2,
|
|
1184
1262
|
pidAlive as pidAlive3,
|
|
1185
|
-
|
|
1263
|
+
readDaemonToken as readDaemonToken2,
|
|
1264
|
+
readProjectDaemonRecord as readProjectDaemonRecord2,
|
|
1186
1265
|
spawnDetachedDaemon
|
|
1187
1266
|
} from "@noir-ai/daemon";
|
|
1188
1267
|
function formatUptime(sec) {
|
|
@@ -1232,7 +1311,8 @@ async function daemonStart(opts) {
|
|
|
1232
1311
|
if (opts.detachChild === true) {
|
|
1233
1312
|
const child = await ensureDaemonRunning2({
|
|
1234
1313
|
project,
|
|
1235
|
-
idleTimeoutSec: project.config.daemon.idleTimeoutSec
|
|
1314
|
+
idleTimeoutSec: project.config.daemon.idleTimeoutSec,
|
|
1315
|
+
...project.config.daemon.port !== void 0 ? { port: project.config.daemon.port } : {}
|
|
1236
1316
|
});
|
|
1237
1317
|
if (child.started) {
|
|
1238
1318
|
if (opts.json === true) {
|
|
@@ -1255,7 +1335,7 @@ async function daemonStart(opts) {
|
|
|
1255
1335
|
return;
|
|
1256
1336
|
}
|
|
1257
1337
|
if (opts.detach === true) {
|
|
1258
|
-
const existing =
|
|
1338
|
+
const existing = readProjectDaemonRecord2(project.id);
|
|
1259
1339
|
if (existing && pidAlive3(existing.pid) && await isHealthy(existing.port, existing.pid, project.id)) {
|
|
1260
1340
|
if (opts.json === true) {
|
|
1261
1341
|
process.stdout.write(
|
|
@@ -1295,7 +1375,8 @@ async function daemonStart(opts) {
|
|
|
1295
1375
|
try {
|
|
1296
1376
|
ensured = await ensureDaemonRunning2({
|
|
1297
1377
|
project,
|
|
1298
|
-
idleTimeoutSec: project.config.daemon.idleTimeoutSec
|
|
1378
|
+
idleTimeoutSec: project.config.daemon.idleTimeoutSec,
|
|
1379
|
+
...project.config.daemon.port !== void 0 ? { port: project.config.daemon.port } : {}
|
|
1299
1380
|
});
|
|
1300
1381
|
} catch (err) {
|
|
1301
1382
|
ds.fail("Daemon failed to start");
|
|
@@ -1338,7 +1419,19 @@ async function daemonStart(opts) {
|
|
|
1338
1419
|
log(`Noir daemon already running at ${ensured.url}`, opts);
|
|
1339
1420
|
}
|
|
1340
1421
|
async function daemonStop(opts) {
|
|
1341
|
-
const
|
|
1422
|
+
const callerProject = resolveCallerProjectId();
|
|
1423
|
+
if (callerProject === void 0) {
|
|
1424
|
+
if (opts.json === true) {
|
|
1425
|
+
process.stdout.write(
|
|
1426
|
+
`${JSON.stringify({ ok: true, data: { running: false, stopped: false } })}
|
|
1427
|
+
`
|
|
1428
|
+
);
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1431
|
+
info("No Noir daemon is running for this directory (not a Noir project).", opts);
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
const rec = readProjectDaemonRecord2(callerProject);
|
|
1342
1435
|
if (!rec) {
|
|
1343
1436
|
if (opts.json === true) {
|
|
1344
1437
|
process.stdout.write(
|
|
@@ -1353,9 +1446,7 @@ async function daemonStop(opts) {
|
|
|
1353
1446
|
const ds = spinner(`Stopping daemon (pid ${rec.pid})...`, opts).start();
|
|
1354
1447
|
let signalled = false;
|
|
1355
1448
|
let errMsg2;
|
|
1356
|
-
const
|
|
1357
|
-
const wrongProject = rec.projectId !== void 0 && callerProject !== void 0 && rec.projectId !== callerProject;
|
|
1358
|
-
const healthy = !wrongProject && pidAlive3(rec.pid) && await isHealthy(rec.port, rec.pid, rec.projectId === void 0 ? void 0 : callerProject);
|
|
1449
|
+
const healthy = pidAlive3(rec.pid) && await isHealthy(rec.port, rec.pid, callerProject);
|
|
1359
1450
|
if (healthy) {
|
|
1360
1451
|
try {
|
|
1361
1452
|
process.kill(rec.pid, "SIGTERM");
|
|
@@ -1364,7 +1455,7 @@ async function daemonStop(opts) {
|
|
|
1364
1455
|
errMsg2 = err instanceof Error ? err.message : String(err);
|
|
1365
1456
|
}
|
|
1366
1457
|
}
|
|
1367
|
-
|
|
1458
|
+
clearProjectDaemonRecord(callerProject);
|
|
1368
1459
|
if (opts.json === true) {
|
|
1369
1460
|
process.stdout.write(
|
|
1370
1461
|
`${JSON.stringify({
|
|
@@ -1383,26 +1474,22 @@ async function daemonStop(opts) {
|
|
|
1383
1474
|
if (signalled) {
|
|
1384
1475
|
ds.succeed(`Stopped Noir daemon (pid ${rec.pid})`);
|
|
1385
1476
|
} else {
|
|
1386
|
-
const reason =
|
|
1477
|
+
const reason = errMsg2 !== void 0 ? errMsg2 : "the recorded pid did not answer as our daemon (stale record cleared)";
|
|
1387
1478
|
ds.warn(`Daemon (pid ${rec.pid}) could not be signalled: ${reason}`);
|
|
1388
1479
|
info(`Noir daemon (pid ${rec.pid}) could not be signalled: ${reason}`, opts);
|
|
1389
1480
|
}
|
|
1390
1481
|
}
|
|
1391
1482
|
async function daemonStatus(opts) {
|
|
1392
|
-
const
|
|
1483
|
+
const callerProject = resolveCallerProjectId();
|
|
1484
|
+
if (callerProject === void 0) {
|
|
1485
|
+
fail(EXIT.DAEMON_DOWN, "Noir daemon is not running (not a Noir project).", opts);
|
|
1486
|
+
}
|
|
1487
|
+
const rec = readProjectDaemonRecord2(callerProject);
|
|
1393
1488
|
if (!rec) {
|
|
1394
1489
|
fail(EXIT.DAEMON_DOWN, "Noir daemon is not running (start with `noir daemon start`).", opts);
|
|
1395
1490
|
}
|
|
1396
|
-
const callerProject = resolveCallerProjectId();
|
|
1397
|
-
if (rec.projectId !== void 0 && callerProject !== void 0 && rec.projectId !== callerProject) {
|
|
1398
|
-
fail(
|
|
1399
|
-
EXIT.DAEMON_DOWN,
|
|
1400
|
-
"Noir daemon is not running for this project (a daemon for another project is running).",
|
|
1401
|
-
opts
|
|
1402
|
-
);
|
|
1403
|
-
}
|
|
1404
1491
|
if (!pidAlive3(rec.pid)) {
|
|
1405
|
-
|
|
1492
|
+
clearProjectDaemonRecord(callerProject);
|
|
1406
1493
|
fail(EXIT.DAEMON_DOWN, "Noir daemon is not running (stale record removed).", opts);
|
|
1407
1494
|
}
|
|
1408
1495
|
const hs = spinner("Probing daemon health...", opts).start();
|
|
@@ -1420,7 +1507,7 @@ async function daemonStatus(opts) {
|
|
|
1420
1507
|
const pidOk = typeof health?.pid === "number" && health.pid === rec.pid;
|
|
1421
1508
|
if (health?.ok !== true || !pidOk) {
|
|
1422
1509
|
hs.fail("Daemon not responding");
|
|
1423
|
-
|
|
1510
|
+
clearProjectDaemonRecord(callerProject);
|
|
1424
1511
|
fail(
|
|
1425
1512
|
EXIT.DAEMON_DOWN,
|
|
1426
1513
|
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).",
|
|
@@ -1436,7 +1523,8 @@ async function daemonStatus(opts) {
|
|
|
1436
1523
|
startedAt: rec.startedAt,
|
|
1437
1524
|
uptimeSec,
|
|
1438
1525
|
// Honest ownership: the detached child writes `mode:'detached'`; anything
|
|
1439
|
-
// else (
|
|
1526
|
+
// else (a record written before the field existed, foreground start)
|
|
1527
|
+
// reports 'foreground'.
|
|
1440
1528
|
mode: rec.mode ?? MODE
|
|
1441
1529
|
};
|
|
1442
1530
|
if (opts.json === true) {
|
|
@@ -1449,6 +1537,42 @@ async function daemonStatus(opts) {
|
|
|
1449
1537
|
opts
|
|
1450
1538
|
);
|
|
1451
1539
|
}
|
|
1540
|
+
async function daemonToken(opts) {
|
|
1541
|
+
const callerProject = resolveCallerProjectId();
|
|
1542
|
+
if (callerProject === void 0) {
|
|
1543
|
+
fail(EXIT.DAEMON_DOWN, "Noir daemon is not running (not a Noir project).", opts);
|
|
1544
|
+
}
|
|
1545
|
+
const rec = readProjectDaemonRecord2(callerProject);
|
|
1546
|
+
if (!rec) {
|
|
1547
|
+
fail(
|
|
1548
|
+
EXIT.DAEMON_DOWN,
|
|
1549
|
+
"Noir daemon is not running (no daemon record \u2014 start it with `noir daemon start`).",
|
|
1550
|
+
opts
|
|
1551
|
+
);
|
|
1552
|
+
}
|
|
1553
|
+
if (!pidAlive3(rec.pid) || !await isHealthy(rec.port, rec.pid, callerProject)) {
|
|
1554
|
+
fail(
|
|
1555
|
+
EXIT.DAEMON_DOWN,
|
|
1556
|
+
"Noir daemon is not running (stale record \u2014 start it with `noir daemon start`).",
|
|
1557
|
+
opts
|
|
1558
|
+
);
|
|
1559
|
+
}
|
|
1560
|
+
const token = readDaemonToken2(callerProject);
|
|
1561
|
+
if (token === null) {
|
|
1562
|
+
fail(
|
|
1563
|
+
EXIT.DAEMON_DOWN,
|
|
1564
|
+
"Noir daemon is not running (the record has no token beside it \u2014 restart it with `noir daemon start`).",
|
|
1565
|
+
opts
|
|
1566
|
+
);
|
|
1567
|
+
}
|
|
1568
|
+
if (opts.json === true) {
|
|
1569
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: { token } })}
|
|
1570
|
+
`);
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1573
|
+
process.stdout.write(`${token}
|
|
1574
|
+
`);
|
|
1575
|
+
}
|
|
1452
1576
|
async function daemonRestart(opts) {
|
|
1453
1577
|
await daemonStop({ ...opts, json: false, quiet: true });
|
|
1454
1578
|
await daemonStart(opts);
|
|
@@ -1473,6 +1597,7 @@ import { resolveAdapter as resolveAdapter2 } from "@noir-ai/adapters";
|
|
|
1473
1597
|
import {
|
|
1474
1598
|
detectActiveMethod,
|
|
1475
1599
|
latestVersionFromCache,
|
|
1600
|
+
loadNoirEnv,
|
|
1476
1601
|
loadProjectInfo as loadProjectInfo4,
|
|
1477
1602
|
NOIR_VERSION as NOIR_VERSION2,
|
|
1478
1603
|
paths,
|
|
@@ -1480,7 +1605,7 @@ import {
|
|
|
1480
1605
|
readUpdateCache
|
|
1481
1606
|
} from "@noir-ai/core";
|
|
1482
1607
|
import { CURRENT_SCAFFOLD_VERSION, readScaffoldVersion } from "@noir-ai/create";
|
|
1483
|
-
import { pidAlive as pidAlive4,
|
|
1608
|
+
import { pidAlive as pidAlive4, readProjectDaemonRecord as readProjectDaemonRecord3 } from "@noir-ai/daemon";
|
|
1484
1609
|
import { resolveModelConfig } from "@noir-ai/model";
|
|
1485
1610
|
async function probeOnnx() {
|
|
1486
1611
|
try {
|
|
@@ -1544,8 +1669,16 @@ function checkConfig(root) {
|
|
|
1544
1669
|
};
|
|
1545
1670
|
}
|
|
1546
1671
|
}
|
|
1547
|
-
async function checkDaemon(checks) {
|
|
1548
|
-
|
|
1672
|
+
async function checkDaemon(checks, project) {
|
|
1673
|
+
if (project === void 0) {
|
|
1674
|
+
checks.push({
|
|
1675
|
+
name: "daemon",
|
|
1676
|
+
status: "warn",
|
|
1677
|
+
detail: "skipped \u2014 project not initialized"
|
|
1678
|
+
});
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
const rec = readProjectDaemonRecord3(project.id);
|
|
1549
1682
|
if (!rec) {
|
|
1550
1683
|
checks.push({
|
|
1551
1684
|
name: "daemon",
|
|
@@ -1637,7 +1770,7 @@ async function checkStore(checks, project, root) {
|
|
|
1637
1770
|
}
|
|
1638
1771
|
}
|
|
1639
1772
|
}
|
|
1640
|
-
function checkNoirEnv(checks, root) {
|
|
1773
|
+
function checkNoirEnv(checks, root, loaded) {
|
|
1641
1774
|
const path = join2(paths.noirDir(root), ".env");
|
|
1642
1775
|
let st;
|
|
1643
1776
|
try {
|
|
@@ -1645,6 +1778,14 @@ function checkNoirEnv(checks, root) {
|
|
|
1645
1778
|
} catch {
|
|
1646
1779
|
return;
|
|
1647
1780
|
}
|
|
1781
|
+
if (loaded.warnings.some((w) => w.startsWith(ENV_REFUSAL_PREFIX))) {
|
|
1782
|
+
checks.push({
|
|
1783
|
+
name: "noir-env",
|
|
1784
|
+
status: "warn",
|
|
1785
|
+
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\`.`
|
|
1786
|
+
});
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1648
1789
|
const mode = st.mode & 511;
|
|
1649
1790
|
const tooOpen = (st.mode & 63) !== 0;
|
|
1650
1791
|
checks.push(
|
|
@@ -1654,6 +1795,10 @@ function checkNoirEnv(checks, root) {
|
|
|
1654
1795
|
detail: `${path} is ${mode.toString(8)} \u2014 readable by others; run chmod 600`
|
|
1655
1796
|
} : { name: "noir-env", status: "ok", detail: `${path} permissions ok (${mode.toString(8)})` }
|
|
1656
1797
|
);
|
|
1798
|
+
for (const [key, source] of Object.entries(loaded.sources)) {
|
|
1799
|
+
if (source !== "file") continue;
|
|
1800
|
+
checks.push({ name: `noir-env:${key}`, status: "ok", detail: "from .noir/.env" });
|
|
1801
|
+
}
|
|
1657
1802
|
}
|
|
1658
1803
|
function checkEmbedder(checks, project, vecOk) {
|
|
1659
1804
|
if (!project) {
|
|
@@ -1685,7 +1830,7 @@ function checkEmbedder(checks, project, vecOk) {
|
|
|
1685
1830
|
detail: `${kind}/${model} (${emb.dim}-dim) \xB7 vec backend available`
|
|
1686
1831
|
});
|
|
1687
1832
|
}
|
|
1688
|
-
function checkProvider(checks, project) {
|
|
1833
|
+
function checkProvider(checks, project, sources) {
|
|
1689
1834
|
if (!project) {
|
|
1690
1835
|
checks.push({ name: "provider", status: "warn", detail: "skipped \u2014 not initialized" });
|
|
1691
1836
|
return;
|
|
@@ -1705,7 +1850,8 @@ function checkProvider(checks, project) {
|
|
|
1705
1850
|
for (const name of names) {
|
|
1706
1851
|
const p = resolved.providers[name];
|
|
1707
1852
|
if (!p) continue;
|
|
1708
|
-
const
|
|
1853
|
+
const fromFile = p.apiKeyEnv !== void 0 && sources[p.apiKeyEnv] === "file";
|
|
1854
|
+
const key = p.hasKey ? fromFile ? "key present (from .noir/.env)" : "key present" : p.apiKeyEnv ? `missing ${p.apiKeyEnv}` : "anonymous";
|
|
1709
1855
|
if (!p.hasKey && p.apiKeyEnv) missing = true;
|
|
1710
1856
|
parts.push(`${name}/${p.model ?? "?"} (${key})`);
|
|
1711
1857
|
}
|
|
@@ -2068,12 +2214,13 @@ async function doctor(opts = {}) {
|
|
|
2068
2214
|
await checkRuntime(checks);
|
|
2069
2215
|
const { project, result: configResult } = checkConfig(root);
|
|
2070
2216
|
checks.push(configResult);
|
|
2071
|
-
await checkDaemon(checks);
|
|
2217
|
+
await checkDaemon(checks, project);
|
|
2072
2218
|
const { vecOk } = await checkNativeDeps(checks);
|
|
2073
2219
|
await checkStore(checks, project, root);
|
|
2074
2220
|
checkEmbedder(checks, project, vecOk);
|
|
2075
|
-
|
|
2076
|
-
|
|
2221
|
+
const noirEnv = loadNoirEnv(root);
|
|
2222
|
+
checkProvider(checks, project, noirEnv.sources);
|
|
2223
|
+
checkNoirEnv(checks, root, noirEnv);
|
|
2077
2224
|
const scaffold4 = checkScaffoldVersion(checks, root);
|
|
2078
2225
|
const rules = checkRulesMdBudget(checks, root, project);
|
|
2079
2226
|
const host = checkHostArtifacts(checks, root, project);
|
|
@@ -2116,17 +2263,112 @@ async function doctor(opts = {}) {
|
|
|
2116
2263
|
);
|
|
2117
2264
|
}
|
|
2118
2265
|
}
|
|
2119
|
-
var SEMVER_ISH;
|
|
2266
|
+
var ENV_REFUSAL_PREFIX, SEMVER_ISH;
|
|
2120
2267
|
var init_doctor = __esm({
|
|
2121
2268
|
"src/commands/doctor.ts"() {
|
|
2122
2269
|
"use strict";
|
|
2123
2270
|
init_daemon_client();
|
|
2124
2271
|
init_output();
|
|
2125
2272
|
init_theme();
|
|
2273
|
+
ENV_REFUSAL_PREFIX = ".noir/.env: refusing to load";
|
|
2126
2274
|
SEMVER_ISH = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
2127
2275
|
}
|
|
2128
2276
|
});
|
|
2129
2277
|
|
|
2278
|
+
// src/commands/env.ts
|
|
2279
|
+
import { loadNoirEnv as loadNoirEnv2, loadProjectInfo as loadProjectInfo5 } from "@noir-ai/core";
|
|
2280
|
+
import { resolveModelConfig as resolveModelConfig2 } from "@noir-ai/model";
|
|
2281
|
+
function captureAmbientEnv(env2 = process.env) {
|
|
2282
|
+
ambientBaseline = { ...env2 };
|
|
2283
|
+
}
|
|
2284
|
+
function redactShape(value) {
|
|
2285
|
+
if (value.length <= SHORT_VALUE_MAX) return `\u2026(${value.length})`;
|
|
2286
|
+
return `${value.slice(0, 3)}\u2026(${value.length})`;
|
|
2287
|
+
}
|
|
2288
|
+
function configApiKeyEnvNames(root) {
|
|
2289
|
+
try {
|
|
2290
|
+
const project = loadProjectInfo5(root);
|
|
2291
|
+
const names = /* @__PURE__ */ new Set();
|
|
2292
|
+
for (const provider of Object.values(resolveModelConfig2(project.config.model).providers)) {
|
|
2293
|
+
if (provider.apiKeyEnv !== void 0 && provider.apiKeyEnv.length > 0) {
|
|
2294
|
+
names.add(provider.apiKeyEnv);
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
return [...names];
|
|
2298
|
+
} catch {
|
|
2299
|
+
return [];
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
function resolveEnv(root, ambientEnv) {
|
|
2303
|
+
const { overlay, warnings, sources } = loadNoirEnv2(root, ambientEnv);
|
|
2304
|
+
const curated = new Set(CURATED_AMBIENT_KEYS);
|
|
2305
|
+
for (const name of configApiKeyEnvNames(root)) curated.add(name);
|
|
2306
|
+
const candidates = new Set(Object.keys(sources));
|
|
2307
|
+
for (const key of curated) {
|
|
2308
|
+
if (ambientEnv[key] !== void 0) candidates.add(key);
|
|
2309
|
+
}
|
|
2310
|
+
const vars = [];
|
|
2311
|
+
const shapes = {};
|
|
2312
|
+
for (const key of [...candidates].sort()) {
|
|
2313
|
+
const source = sources[key] ?? "env";
|
|
2314
|
+
if (source === "env" && !curated.has(key)) continue;
|
|
2315
|
+
const value = source === "file" ? overlay[key] ?? "" : ambientEnv[key] ?? "";
|
|
2316
|
+
const shadowed = source === "file" && ambientEnv[key] !== void 0 && ambientEnv[key] !== value;
|
|
2317
|
+
vars.push({
|
|
2318
|
+
key,
|
|
2319
|
+
source,
|
|
2320
|
+
...shadowed ? { shadowed: true } : {},
|
|
2321
|
+
valueLength: value.length
|
|
2322
|
+
});
|
|
2323
|
+
shapes[key] = redactShape(value);
|
|
2324
|
+
}
|
|
2325
|
+
return { vars, warnings, shapes };
|
|
2326
|
+
}
|
|
2327
|
+
function describeSource(row) {
|
|
2328
|
+
if (row.source !== "file") return "environment";
|
|
2329
|
+
return row.shadowed === true ? ".noir/.env (shadows environment)" : ".noir/.env";
|
|
2330
|
+
}
|
|
2331
|
+
async function env(opts = {}) {
|
|
2332
|
+
const report = resolveEnv(process.cwd(), ambientBaseline ?? process.env);
|
|
2333
|
+
if (opts.json === true) {
|
|
2334
|
+
json({ ok: true, data: { vars: report.vars, warnings: report.warnings } });
|
|
2335
|
+
return;
|
|
2336
|
+
}
|
|
2337
|
+
if (report.vars.length === 0) {
|
|
2338
|
+
info(
|
|
2339
|
+
"nothing to report \u2014 .noir/.env defines no keys in effect and no Noir-relevant environment variable is set",
|
|
2340
|
+
opts
|
|
2341
|
+
);
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2344
|
+
table(
|
|
2345
|
+
report.vars.map((row) => ({
|
|
2346
|
+
KEY: row.key,
|
|
2347
|
+
SOURCE: describeSource(row),
|
|
2348
|
+
VALUE: report.shapes[row.key] ?? ""
|
|
2349
|
+
})),
|
|
2350
|
+
["KEY", "SOURCE", "VALUE"],
|
|
2351
|
+
opts
|
|
2352
|
+
);
|
|
2353
|
+
}
|
|
2354
|
+
var CURATED_AMBIENT_KEYS, ambientBaseline, SHORT_VALUE_MAX;
|
|
2355
|
+
var init_env = __esm({
|
|
2356
|
+
"src/commands/env.ts"() {
|
|
2357
|
+
"use strict";
|
|
2358
|
+
init_output();
|
|
2359
|
+
CURATED_AMBIENT_KEYS = [
|
|
2360
|
+
"CLICKUP_API_TOKEN",
|
|
2361
|
+
"OPENAI_API_KEY",
|
|
2362
|
+
"VOYAGE_API_KEY",
|
|
2363
|
+
"COHERE_API_KEY",
|
|
2364
|
+
"OLLAMA_BASE_URL",
|
|
2365
|
+
"ANTHROPIC_API_KEY",
|
|
2366
|
+
"NOIR_PROFILE"
|
|
2367
|
+
];
|
|
2368
|
+
SHORT_VALUE_MAX = 8;
|
|
2369
|
+
}
|
|
2370
|
+
});
|
|
2371
|
+
|
|
2130
2372
|
// src/commands/install.ts
|
|
2131
2373
|
var install_exports = {};
|
|
2132
2374
|
__export(install_exports, {
|
|
@@ -2141,7 +2383,7 @@ import {
|
|
|
2141
2383
|
detectActiveMethod as detectActiveMethod2,
|
|
2142
2384
|
detectInstallMethods,
|
|
2143
2385
|
ensureShimExecutable,
|
|
2144
|
-
loadProjectInfo as
|
|
2386
|
+
loadProjectInfo as loadProjectInfo6,
|
|
2145
2387
|
MANAGED_NODE_VERSION,
|
|
2146
2388
|
NOIR_VERSION as NOIR_VERSION3,
|
|
2147
2389
|
noirHome,
|
|
@@ -2280,7 +2522,7 @@ async function install(opts = {}) {
|
|
|
2280
2522
|
const installedRecord = readInstallRecord2();
|
|
2281
2523
|
let minVersion = "1.6.0";
|
|
2282
2524
|
try {
|
|
2283
|
-
minVersion =
|
|
2525
|
+
minVersion = loadProjectInfo6(process.cwd()).config.update.minVersion;
|
|
2284
2526
|
} catch {
|
|
2285
2527
|
}
|
|
2286
2528
|
const plan = buildMigrationPlan({
|
|
@@ -2357,7 +2599,7 @@ import {
|
|
|
2357
2599
|
detectActiveMethod as detectActiveMethod3,
|
|
2358
2600
|
ensureShimExecutable as ensureShimExecutable2,
|
|
2359
2601
|
fetchLatestVersion,
|
|
2360
|
-
loadProjectInfo as
|
|
2602
|
+
loadProjectInfo as loadProjectInfo7,
|
|
2361
2603
|
readInstallRecord as readInstallRecord3,
|
|
2362
2604
|
readUpdateCache as readUpdateCache2,
|
|
2363
2605
|
runManagerCmd as runManagerCmd2,
|
|
@@ -2437,7 +2679,7 @@ async function update(opts = {}) {
|
|
|
2437
2679
|
fetchSpin.succeed(`Latest: ${latest}`);
|
|
2438
2680
|
let minVersion = DEFAULT_UPDATE_CONFIG.minVersion;
|
|
2439
2681
|
try {
|
|
2440
|
-
minVersion =
|
|
2682
|
+
minVersion = loadProjectInfo7(process.cwd()).config.update.minVersion;
|
|
2441
2683
|
} catch {
|
|
2442
2684
|
}
|
|
2443
2685
|
const target = buildUpdateTarget({
|
|
@@ -2587,7 +2829,7 @@ var init_update = __esm({
|
|
|
2587
2829
|
});
|
|
2588
2830
|
|
|
2589
2831
|
// src/commands/status.ts
|
|
2590
|
-
import { loadProjectInfo as
|
|
2832
|
+
import { loadProjectInfo as loadProjectInfo8, NOIR_VERSION as NOIR_VERSION4 } from "@noir-ai/core";
|
|
2591
2833
|
async function tryTool(caller, name, args) {
|
|
2592
2834
|
try {
|
|
2593
2835
|
return await caller.callTool(name, args);
|
|
@@ -2708,7 +2950,7 @@ function renderHuman2(p, opts) {
|
|
|
2708
2950
|
async function gatherStatusPayload(opts) {
|
|
2709
2951
|
let project;
|
|
2710
2952
|
try {
|
|
2711
|
-
project =
|
|
2953
|
+
project = loadProjectInfo8(process.cwd());
|
|
2712
2954
|
} catch {
|
|
2713
2955
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
2714
2956
|
}
|
|
@@ -2738,7 +2980,7 @@ async function status(opts) {
|
|
|
2738
2980
|
env: process.env,
|
|
2739
2981
|
configUpdate: (() => {
|
|
2740
2982
|
try {
|
|
2741
|
-
const info2 =
|
|
2983
|
+
const info2 = loadProjectInfo8(process.cwd());
|
|
2742
2984
|
return info2.config.update ?? DEFAULT_UPDATE_CONFIG;
|
|
2743
2985
|
} catch {
|
|
2744
2986
|
return DEFAULT_UPDATE_CONFIG;
|
|
@@ -2767,7 +3009,7 @@ var init_status = __esm({
|
|
|
2767
3009
|
// src/commands/task.ts
|
|
2768
3010
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
2769
3011
|
import { writeFileSync } from "fs";
|
|
2770
|
-
import { loadProjectInfo as
|
|
3012
|
+
import { loadProjectInfo as loadProjectInfo9, nextArtifactSequence, sha256Hex } from "@noir-ai/core";
|
|
2771
3013
|
import {
|
|
2772
3014
|
PHASES,
|
|
2773
3015
|
TASK_CLASSES,
|
|
@@ -2780,7 +3022,7 @@ function skillFor(phase) {
|
|
|
2780
3022
|
}
|
|
2781
3023
|
function writeDoneArtifacts(opts, taskId) {
|
|
2782
3024
|
try {
|
|
2783
|
-
const project =
|
|
3025
|
+
const project = loadProjectInfo9(process.cwd());
|
|
2784
3026
|
const conflict = { conflictPolicy: "preserve", interactive: false };
|
|
2785
3027
|
writeChangelogStub(project.root, `- ${taskId}: completed (verify gate passed)`, conflict);
|
|
2786
3028
|
const nextN = nextArtifactSequence(project.root, "adr");
|
|
@@ -3103,7 +3345,7 @@ function runCheck(cmd) {
|
|
|
3103
3345
|
async function taskVerify(opts) {
|
|
3104
3346
|
let project;
|
|
3105
3347
|
try {
|
|
3106
|
-
project =
|
|
3348
|
+
project = loadProjectInfo9(process.cwd());
|
|
3107
3349
|
} catch {
|
|
3108
3350
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
3109
3351
|
}
|
|
@@ -3315,7 +3557,7 @@ import {
|
|
|
3315
3557
|
hostLaunchDirective,
|
|
3316
3558
|
resolveAdapter as resolveAdapter3
|
|
3317
3559
|
} from "@noir-ai/adapters";
|
|
3318
|
-
import { loadProjectInfo as
|
|
3560
|
+
import { loadProjectInfo as loadProjectInfo10, NOIR_DIR, resolveArtifactPath } from "@noir-ai/core";
|
|
3319
3561
|
function heading(name, id) {
|
|
3320
3562
|
return `# Noir handoff \u2014 ${name} (${id})`;
|
|
3321
3563
|
}
|
|
@@ -3454,7 +3696,7 @@ async function probeOnly(opts) {
|
|
|
3454
3696
|
async function handoff(opts) {
|
|
3455
3697
|
let project;
|
|
3456
3698
|
try {
|
|
3457
|
-
project =
|
|
3699
|
+
project = loadProjectInfo10(process.cwd());
|
|
3458
3700
|
} catch {
|
|
3459
3701
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
3460
3702
|
}
|
|
@@ -3818,14 +4060,14 @@ import { hostLaunchDirective as hostLaunchDirective2 } from "@noir-ai/adapters";
|
|
|
3818
4060
|
import {
|
|
3819
4061
|
detectActiveMethod as detectActiveMethod4,
|
|
3820
4062
|
latestVersionFromCache as latestVersionFromCache2,
|
|
3821
|
-
loadProjectInfo as
|
|
4063
|
+
loadProjectInfo as loadProjectInfo11,
|
|
3822
4064
|
NOIR_VERSION as NOIR_VERSION5,
|
|
3823
4065
|
readInstallRecord as readInstallRecord4,
|
|
3824
4066
|
readUpdateCache as readUpdateCache3
|
|
3825
4067
|
} from "@noir-ai/core";
|
|
3826
4068
|
function tryProject() {
|
|
3827
4069
|
try {
|
|
3828
|
-
const info2 =
|
|
4070
|
+
const info2 = loadProjectInfo11(process.cwd());
|
|
3829
4071
|
return { id: info2.id, name: info2.name, host: info2.config.host };
|
|
3830
4072
|
} catch {
|
|
3831
4073
|
return null;
|
|
@@ -3988,7 +4230,7 @@ async function runMenu(opts, deps) {
|
|
|
3988
4230
|
env: process.env,
|
|
3989
4231
|
configUpdate: (() => {
|
|
3990
4232
|
try {
|
|
3991
|
-
const info2 =
|
|
4233
|
+
const info2 = loadProjectInfo11(process.cwd());
|
|
3992
4234
|
return info2.config.update ?? DEFAULT_UPDATE_CONFIG;
|
|
3993
4235
|
} catch {
|
|
3994
4236
|
return DEFAULT_UPDATE_CONFIG;
|
|
@@ -4051,12 +4293,12 @@ var init_home = __esm({
|
|
|
4051
4293
|
|
|
4052
4294
|
// src/commands/memory.ts
|
|
4053
4295
|
import { readFileSync as readFileSync3 } from "fs";
|
|
4054
|
-
import { loadProjectInfo as
|
|
4296
|
+
import { loadProjectInfo as loadProjectInfo12 } from "@noir-ai/core";
|
|
4055
4297
|
function resolveWorkspace() {
|
|
4056
4298
|
const routing = workspaceRoutingTarget(process.cwd());
|
|
4057
4299
|
if (routing === null) return null;
|
|
4058
4300
|
try {
|
|
4059
|
-
return { routing, project:
|
|
4301
|
+
return { routing, project: loadProjectInfo12(process.cwd()) };
|
|
4060
4302
|
} catch {
|
|
4061
4303
|
return null;
|
|
4062
4304
|
}
|
|
@@ -4448,9 +4690,9 @@ async function memoryConsolidate(opts) {
|
|
|
4448
4690
|
}
|
|
4449
4691
|
return;
|
|
4450
4692
|
}
|
|
4451
|
-
const
|
|
4452
|
-
const reason = (typeof
|
|
4453
|
-
const logged =
|
|
4693
|
+
const env2 = res;
|
|
4694
|
+
const reason = (typeof env2.reason === "string" ? env2.reason : void 0) ?? (typeof env2.error === "string" ? env2.error : void 0) ?? "unknown";
|
|
4695
|
+
const logged = env2.logged === true || env2.degraded === true;
|
|
4454
4696
|
const suffix = logged ? " (logged)" : "";
|
|
4455
4697
|
fail(EXIT.ERROR, `memory consolidate did not run: ${reason}${suffix}`, opts);
|
|
4456
4698
|
}
|
|
@@ -4570,10 +4812,10 @@ var init_release = __esm({
|
|
|
4570
4812
|
import { spawn } from "child_process";
|
|
4571
4813
|
import { basename } from "path";
|
|
4572
4814
|
function isEligibleForShellFallback(name, opts = {}) {
|
|
4573
|
-
const { env = process.env, platform = process.platform } = opts;
|
|
4815
|
+
const { env: env2 = process.env, platform = process.platform } = opts;
|
|
4574
4816
|
if (platform === "win32") return false;
|
|
4575
4817
|
if (name.length === 0 || name.includes("/") || !NAME_RE.test(name)) return false;
|
|
4576
|
-
const shell =
|
|
4818
|
+
const shell = env2.SHELL;
|
|
4577
4819
|
if (!shell || shell.length === 0) return false;
|
|
4578
4820
|
return SUPPORTED_SHELLS.has(basename(shell).toLowerCase());
|
|
4579
4821
|
}
|
|
@@ -4636,9 +4878,9 @@ function probeCommand(name, shell, opts) {
|
|
|
4636
4878
|
});
|
|
4637
4879
|
}
|
|
4638
4880
|
async function resolveCommandViaShell(name, opts = {}) {
|
|
4639
|
-
const { env = process.env } = opts;
|
|
4881
|
+
const { env: env2 = process.env } = opts;
|
|
4640
4882
|
if (!isEligibleForShellFallback(name, opts)) return { kind: "unsupported" };
|
|
4641
|
-
const shell =
|
|
4883
|
+
const shell = env2.SHELL;
|
|
4642
4884
|
const cached = pathCache.get(`${shell}:${name}`);
|
|
4643
4885
|
if (cached) return { kind: "path", path: cached, shell };
|
|
4644
4886
|
const probe = await probeCommand(name, shell, opts);
|
|
@@ -4939,24 +5181,24 @@ var init_orchestrator = __esm({
|
|
|
4939
5181
|
});
|
|
4940
5182
|
|
|
4941
5183
|
// src/run-profiles.ts
|
|
4942
|
-
import { loadProjectInfo as
|
|
5184
|
+
import { loadProjectInfo as loadProjectInfo13 } from "@noir-ai/core";
|
|
4943
5185
|
function loadRunConfig(root) {
|
|
4944
5186
|
try {
|
|
4945
|
-
return
|
|
5187
|
+
return loadProjectInfo13(root).config;
|
|
4946
5188
|
} catch {
|
|
4947
5189
|
return null;
|
|
4948
5190
|
}
|
|
4949
5191
|
}
|
|
4950
|
-
function expandEnvVars(value,
|
|
5192
|
+
function expandEnvVars(value, env2) {
|
|
4951
5193
|
return value.replace(
|
|
4952
5194
|
/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g,
|
|
4953
|
-
(match, name) =>
|
|
5195
|
+
(match, name) => env2[name] !== void 0 ? env2[name] : match
|
|
4954
5196
|
);
|
|
4955
5197
|
}
|
|
4956
|
-
function resolveRunProfile(requested, config,
|
|
5198
|
+
function resolveRunProfile(requested, config, env2) {
|
|
4957
5199
|
const profiles = config.run?.profiles ?? {};
|
|
4958
5200
|
const names = Object.keys(profiles);
|
|
4959
|
-
const name = requested ??
|
|
5201
|
+
const name = requested ?? env2.NOIR_PROFILE ?? config.run?.defaultProfile;
|
|
4960
5202
|
if (name === void 0 || name.length === 0) {
|
|
4961
5203
|
return { ok: true, profile: {} };
|
|
4962
5204
|
}
|
|
@@ -4971,7 +5213,7 @@ function resolveRunProfile(requested, config, env) {
|
|
|
4971
5213
|
const envExpanded = {};
|
|
4972
5214
|
if (profile.env) {
|
|
4973
5215
|
for (const [k, v] of Object.entries(profile.env)) {
|
|
4974
|
-
envExpanded[k] = v === null ? void 0 : expandEnvVars(v,
|
|
5216
|
+
envExpanded[k] = v === null ? void 0 : expandEnvVars(v, env2);
|
|
4975
5217
|
}
|
|
4976
5218
|
}
|
|
4977
5219
|
return {
|
|
@@ -4995,10 +5237,24 @@ var init_run_profiles = __esm({
|
|
|
4995
5237
|
});
|
|
4996
5238
|
|
|
4997
5239
|
// src/commands/run.ts
|
|
4998
|
-
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
5240
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
4999
5241
|
import { join as join5 } from "path";
|
|
5000
5242
|
import { SUPPORTED_HOSTS } from "@noir-ai/adapters";
|
|
5001
|
-
import { parseConfig } from "@noir-ai/core";
|
|
5243
|
+
import { loadNoirEnv as loadNoirEnv3, NOIR_DIR as NOIR_DIR2, parseConfig } from "@noir-ai/core";
|
|
5244
|
+
function credentialNote(env2, sources) {
|
|
5245
|
+
const named = CREDENTIAL_ENV_VARS.filter((name) => {
|
|
5246
|
+
const value = env2[name];
|
|
5247
|
+
return value !== void 0 && value.length > 0;
|
|
5248
|
+
}).map(
|
|
5249
|
+
(name) => `${name} (from ${sources[name] === "file" ? `${NOIR_DIR2}/.env` : "the environment"})`
|
|
5250
|
+
);
|
|
5251
|
+
if (named.length === 0) return "";
|
|
5252
|
+
const one = named.length === 1;
|
|
5253
|
+
const subject = one ? `${named[0]} is set` : `${named.join(", ")} are set`;
|
|
5254
|
+
const pronoun = one ? "it" : "them";
|
|
5255
|
+
const verb = one ? "overrides" : "override";
|
|
5256
|
+
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.`;
|
|
5257
|
+
}
|
|
5002
5258
|
function formatUsage(u) {
|
|
5003
5259
|
const cost = u.totalCostUsd > 0 ? ` $${u.totalCostUsd.toFixed(2)}` : "";
|
|
5004
5260
|
const turns = u.numTurns > 0 ? ` \xB7 ${u.numTurns} turns` : "";
|
|
@@ -5022,13 +5278,21 @@ async function run(prompt, opts) {
|
|
|
5022
5278
|
const usage = selectors.length > 0 ? `noir run ${selectors.join(" ")} <prompt>` : "noir run <prompt>";
|
|
5023
5279
|
fail(EXIT.USAGE, `a prompt is required: \`${usage}\``, opts);
|
|
5024
5280
|
}
|
|
5025
|
-
const
|
|
5281
|
+
const root = process.cwd();
|
|
5282
|
+
const envSources = loadNoirEnv3(root).sources;
|
|
5283
|
+
const config = loadRunConfig(root) ?? parseConfig({});
|
|
5026
5284
|
const resolved = resolveRunProfile(opts.profile, config, process.env);
|
|
5027
5285
|
if (!resolved.ok) fail(EXIT.USAGE, resolved.message, opts);
|
|
5028
5286
|
const profile = resolved.profile;
|
|
5029
5287
|
const customBinary = opts.command ?? profile.binary;
|
|
5030
5288
|
const extraArgs = profile.args;
|
|
5031
|
-
const
|
|
5289
|
+
const env2 = profile.env ? mergeEnv(process.env, profile.env) : void 0;
|
|
5290
|
+
if (!existsSync3(join5(root, NOIR_DIR2, ".env"))) {
|
|
5291
|
+
log(
|
|
5292
|
+
`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).`,
|
|
5293
|
+
opts
|
|
5294
|
+
);
|
|
5295
|
+
}
|
|
5032
5296
|
const transcriptLines = [];
|
|
5033
5297
|
let result;
|
|
5034
5298
|
try {
|
|
@@ -5037,7 +5301,7 @@ async function run(prompt, opts) {
|
|
|
5037
5301
|
prompt,
|
|
5038
5302
|
customBinary,
|
|
5039
5303
|
extraArgs,
|
|
5040
|
-
env,
|
|
5304
|
+
env: env2,
|
|
5041
5305
|
onLine: (line) => transcriptLines.push(line),
|
|
5042
5306
|
onEvent: (event) => streamEvent(event, opts)
|
|
5043
5307
|
});
|
|
@@ -5058,9 +5322,7 @@ async function run(prompt, opts) {
|
|
|
5058
5322
|
let message = `host '${binary}' failed (exit ${result.exitCode}): ${reason}`;
|
|
5059
5323
|
if (isAuth) {
|
|
5060
5324
|
message += ` Open a terminal and run \`${binary} /login\` (interactive-only \u2014 it cannot run inside \`noir run\`), then retry.`;
|
|
5061
|
-
|
|
5062
|
-
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.`;
|
|
5063
|
-
}
|
|
5325
|
+
message += credentialNote(env2 ?? process.env, envSources);
|
|
5064
5326
|
}
|
|
5065
5327
|
message += ` If you use another profile, pass \`--command <binary>\` or define a run profile under run.profiles. transcript: ${transcript}`;
|
|
5066
5328
|
fail(EXIT.ERROR, message, opts);
|
|
@@ -5100,18 +5362,24 @@ function writeTranscript(host, lines) {
|
|
|
5100
5362
|
}
|
|
5101
5363
|
return file;
|
|
5102
5364
|
}
|
|
5365
|
+
var CREDENTIAL_ENV_VARS;
|
|
5103
5366
|
var init_run = __esm({
|
|
5104
5367
|
"src/commands/run.ts"() {
|
|
5105
5368
|
"use strict";
|
|
5106
5369
|
init_orchestrator();
|
|
5107
5370
|
init_output();
|
|
5108
5371
|
init_run_profiles();
|
|
5372
|
+
CREDENTIAL_ENV_VARS = [
|
|
5373
|
+
"ANTHROPIC_API_KEY",
|
|
5374
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
5375
|
+
"ANTHROPIC_BASE_URL"
|
|
5376
|
+
];
|
|
5109
5377
|
}
|
|
5110
5378
|
});
|
|
5111
5379
|
|
|
5112
5380
|
// src/commands/skills.ts
|
|
5113
5381
|
import { resolveAdapter as resolveAdapter4 } from "@noir-ai/adapters";
|
|
5114
|
-
import { loadProjectInfo as
|
|
5382
|
+
import { loadProjectInfo as loadProjectInfo14 } from "@noir-ai/core";
|
|
5115
5383
|
import {
|
|
5116
5384
|
buildRegistry,
|
|
5117
5385
|
discoverAll,
|
|
@@ -5179,7 +5447,7 @@ async function skillsSync(opts) {
|
|
|
5179
5447
|
const root = process.cwd();
|
|
5180
5448
|
let project;
|
|
5181
5449
|
try {
|
|
5182
|
-
project =
|
|
5450
|
+
project = loadProjectInfo14(root);
|
|
5183
5451
|
} catch {
|
|
5184
5452
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
5185
5453
|
}
|
|
@@ -5705,7 +5973,7 @@ var init_dedup_write = __esm({
|
|
|
5705
5973
|
|
|
5706
5974
|
// src/init.ts
|
|
5707
5975
|
import { resolveAdapter as resolveAdapter5 } from "@noir-ai/adapters";
|
|
5708
|
-
import { loadProjectInfo as
|
|
5976
|
+
import { loadProjectInfo as loadProjectInfo15 } from "@noir-ai/core";
|
|
5709
5977
|
import { scaffold } from "@noir-ai/create";
|
|
5710
5978
|
import { emitSkillsToDir as emitSkillsToDir2 } from "@noir-ai/skills";
|
|
5711
5979
|
async function init(root, opts) {
|
|
@@ -5734,7 +6002,7 @@ async function init(root, opts) {
|
|
|
5734
6002
|
await emitHostSkills(root, host, conflictOpts, interactive);
|
|
5735
6003
|
let projectInfo;
|
|
5736
6004
|
try {
|
|
5737
|
-
projectInfo =
|
|
6005
|
+
projectInfo = loadProjectInfo15(root);
|
|
5738
6006
|
} catch {
|
|
5739
6007
|
projectInfo = void 0;
|
|
5740
6008
|
}
|
|
@@ -5753,7 +6021,10 @@ function reportPlannedWrites(res) {
|
|
|
5753
6021
|
log("Dry run \u2014 no files were written.");
|
|
5754
6022
|
if (res.written.length > 0) {
|
|
5755
6023
|
log("Planned writes:");
|
|
5756
|
-
for (const p of res.written)
|
|
6024
|
+
for (const p of res.written) {
|
|
6025
|
+
const mode = res.fileModes?.[p];
|
|
6026
|
+
log(mode === void 0 ? ` ${p}` : ` ${p} (mode ${mode.toString(8).padStart(3, "0")})`);
|
|
6027
|
+
}
|
|
5757
6028
|
}
|
|
5758
6029
|
if (res.skipped.length > 0) {
|
|
5759
6030
|
log("Would leave as-is (already present):");
|
|
@@ -5821,11 +6092,11 @@ var init_init = __esm({
|
|
|
5821
6092
|
});
|
|
5822
6093
|
|
|
5823
6094
|
// src/serve.ts
|
|
5824
|
-
import { applyNoirEnv, loadProjectInfo as
|
|
6095
|
+
import { applyNoirEnv, loadProjectInfo as loadProjectInfo16 } from "@noir-ai/core";
|
|
5825
6096
|
import { ensureDaemonRunning as ensureDaemonRunning3, startStdioServer } from "@noir-ai/daemon";
|
|
5826
6097
|
async function serve(opts) {
|
|
5827
6098
|
applyNoirEnv(process.cwd());
|
|
5828
|
-
const project =
|
|
6099
|
+
const project = loadProjectInfo16(process.cwd());
|
|
5829
6100
|
if (opts.stdio) {
|
|
5830
6101
|
await startStdioServer({ project, transport: "stdio", daemon: false });
|
|
5831
6102
|
return;
|
|
@@ -5833,10 +6104,11 @@ async function serve(opts) {
|
|
|
5833
6104
|
try {
|
|
5834
6105
|
const { url } = await ensureDaemonRunning3({
|
|
5835
6106
|
project,
|
|
5836
|
-
idleTimeoutSec: project.config.daemon.idleTimeoutSec
|
|
6107
|
+
idleTimeoutSec: project.config.daemon.idleTimeoutSec,
|
|
6108
|
+
...project.config.daemon.port !== void 0 ? { port: project.config.daemon.port } : {}
|
|
5837
6109
|
});
|
|
5838
6110
|
process.stderr.write(
|
|
5839
|
-
`Noir daemon available at ${url}. (For HTTP clients, use this URL in .mcp.json.)
|
|
6111
|
+
`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\`).
|
|
5840
6112
|
`
|
|
5841
6113
|
);
|
|
5842
6114
|
} catch (err) {
|
|
@@ -5860,7 +6132,7 @@ __export(create_exports, {
|
|
|
5860
6132
|
});
|
|
5861
6133
|
import { resolve } from "path";
|
|
5862
6134
|
import { resolveAdapter as resolveAdapter6 } from "@noir-ai/adapters";
|
|
5863
|
-
import { loadProjectInfo as
|
|
6135
|
+
import { loadProjectInfo as loadProjectInfo17 } from "@noir-ai/core";
|
|
5864
6136
|
import { scaffold as scaffold2 } from "@noir-ai/create";
|
|
5865
6137
|
import { emitSkillsToDir as emitSkillsToDir3 } from "@noir-ai/skills";
|
|
5866
6138
|
async function create(dir, opts) {
|
|
@@ -5911,7 +6183,7 @@ async function create(dir, opts) {
|
|
|
5911
6183
|
}
|
|
5912
6184
|
let projectInfo;
|
|
5913
6185
|
try {
|
|
5914
|
-
projectInfo =
|
|
6186
|
+
projectInfo = loadProjectInfo17(root);
|
|
5915
6187
|
} catch {
|
|
5916
6188
|
projectInfo = void 0;
|
|
5917
6189
|
}
|
|
@@ -5937,9 +6209,9 @@ var sync_exports = {};
|
|
|
5937
6209
|
__export(sync_exports, {
|
|
5938
6210
|
sync: () => sync
|
|
5939
6211
|
});
|
|
5940
|
-
import { existsSync as
|
|
6212
|
+
import { existsSync as existsSync4 } from "fs";
|
|
5941
6213
|
import { resolveAdapter as resolveAdapter7 } from "@noir-ai/adapters";
|
|
5942
|
-
import { loadProjectInfo as
|
|
6214
|
+
import { loadProjectInfo as loadProjectInfo18, paths as paths2 } from "@noir-ai/core";
|
|
5943
6215
|
import { scaffold as scaffold3 } from "@noir-ai/create";
|
|
5944
6216
|
import { emitSkillsToDir as emitSkillsToDir4 } from "@noir-ai/skills";
|
|
5945
6217
|
async function sync(root, opts = {}) {
|
|
@@ -5948,7 +6220,7 @@ async function sync(root, opts = {}) {
|
|
|
5948
6220
|
const conflictOpts = buildConflictOpts({ force: opts.force, interactive });
|
|
5949
6221
|
let projectInfo;
|
|
5950
6222
|
try {
|
|
5951
|
-
projectInfo =
|
|
6223
|
+
projectInfo = loadProjectInfo18(root);
|
|
5952
6224
|
} catch {
|
|
5953
6225
|
projectInfo = void 0;
|
|
5954
6226
|
}
|
|
@@ -6006,9 +6278,9 @@ async function sync(root, opts = {}) {
|
|
|
6006
6278
|
}
|
|
6007
6279
|
function resolveSyncHost(root, opts) {
|
|
6008
6280
|
if (opts.host !== void 0) return opts.host;
|
|
6009
|
-
if (!
|
|
6281
|
+
if (!existsSync4(paths2.config(root))) return "claude";
|
|
6010
6282
|
try {
|
|
6011
|
-
return
|
|
6283
|
+
return loadProjectInfo18(root).config.host;
|
|
6012
6284
|
} catch {
|
|
6013
6285
|
return "claude";
|
|
6014
6286
|
}
|
|
@@ -6128,6 +6400,7 @@ function createProgram() {
|
|
|
6128
6400
|
);
|
|
6129
6401
|
}
|
|
6130
6402
|
}
|
|
6403
|
+
if (actionCmd.name() === "env") captureAmbientEnv();
|
|
6131
6404
|
applyNoirEnv2(process.cwd());
|
|
6132
6405
|
const nonInteractive = opts.json === true || opts.input === false;
|
|
6133
6406
|
if (nonInteractive) process.env.NOIR_NON_INTERACTIVE = "1";
|
|
@@ -6270,6 +6543,9 @@ function createProgram() {
|
|
|
6270
6543
|
daemonGrp.command("status").description("report daemon pid/uptime/mode (exit 4 if not running)").action(async (...args) => {
|
|
6271
6544
|
await daemonStatus(toCliOptions(trailingCmd(args).optsWithGlobals()));
|
|
6272
6545
|
});
|
|
6546
|
+
daemonGrp.command("token").description("print the daemon bearer token to stdout (for a host headersHelper)").action(async (...args) => {
|
|
6547
|
+
await daemonToken(toCliOptions(trailingCmd(args).optsWithGlobals()));
|
|
6548
|
+
});
|
|
6273
6549
|
daemonGrp.command("restart").description("stop then start the daemon").option("--detach", "run the daemon in the background and exit").action(async (...args) => {
|
|
6274
6550
|
const cmd = trailingCmd(args);
|
|
6275
6551
|
const g = cmd.optsWithGlobals();
|
|
@@ -6279,8 +6555,19 @@ function createProgram() {
|
|
|
6279
6555
|
});
|
|
6280
6556
|
});
|
|
6281
6557
|
daemonGrp.action(() => {
|
|
6282
|
-
throw new NoirCliError(EXIT.USAGE, "Usage: noir daemon start|join|stop|status|restart");
|
|
6558
|
+
throw new NoirCliError(EXIT.USAGE, "Usage: noir daemon start|join|stop|status|restart|token");
|
|
6283
6559
|
});
|
|
6560
|
+
daemonGrp.addHelpText(
|
|
6561
|
+
"after",
|
|
6562
|
+
[
|
|
6563
|
+
"",
|
|
6564
|
+
"Shared workspaces (two repos, one product \u2014 e.g. backend + frontend):",
|
|
6565
|
+
" noir daemon start --workspace <name> --detach found a workspace from this repo",
|
|
6566
|
+
" noir daemon join <name> join it from the other repo",
|
|
6567
|
+
"",
|
|
6568
|
+
"Guide: docs/how-to/shared-workspaces.md"
|
|
6569
|
+
].join("\n")
|
|
6570
|
+
);
|
|
6284
6571
|
const workspaceGrp = program2.command("workspace").description("shared cross-repo workspaces");
|
|
6285
6572
|
workspaceGrp.command("list").description("list workspaces on this machine").action(async (...args) => {
|
|
6286
6573
|
await workspaceList(toCliOptions(trailingCmd(args).optsWithGlobals()));
|
|
@@ -6309,6 +6596,17 @@ function createProgram() {
|
|
|
6309
6596
|
...name !== void 0 ? { name } : {}
|
|
6310
6597
|
});
|
|
6311
6598
|
});
|
|
6599
|
+
workspaceGrp.addHelpText(
|
|
6600
|
+
"after",
|
|
6601
|
+
[
|
|
6602
|
+
"",
|
|
6603
|
+
"This group only inspects and leaves workspaces. To CREATE or JOIN one:",
|
|
6604
|
+
" noir daemon start --workspace <name> --detach found it (from the first repo)",
|
|
6605
|
+
" noir daemon join <name> join it (from the second repo)",
|
|
6606
|
+
"",
|
|
6607
|
+
"Guide: docs/how-to/shared-workspaces.md"
|
|
6608
|
+
].join("\n")
|
|
6609
|
+
);
|
|
6312
6610
|
workspaceGrp.action(() => {
|
|
6313
6611
|
throw new NoirCliError(EXIT.USAGE, "Usage: noir workspace list|status|leave|stop");
|
|
6314
6612
|
});
|
|
@@ -6323,6 +6621,9 @@ function createProgram() {
|
|
|
6323
6621
|
program2.command("status").description("project + daemon + workflow + store snapshot").action(async (...args) => {
|
|
6324
6622
|
await status(toStatusOptions(actionGlobals(args)));
|
|
6325
6623
|
});
|
|
6624
|
+
program2.command("env").description("which configuration is in effect and where each value comes from").action(async (...args) => {
|
|
6625
|
+
await env(toCliOptions(actionGlobals(args)));
|
|
6626
|
+
});
|
|
6326
6627
|
const contextGrp = program2.command("context").description("context engine");
|
|
6327
6628
|
contextGrp.command("search").description("hybrid search over the indexed context").argument("<query>", "search query").option("--limit <n>", "max results", "10").action(async (...args) => {
|
|
6328
6629
|
const cmd = trailingCmd(args);
|
|
@@ -6707,6 +7008,7 @@ var init_bin = __esm({
|
|
|
6707
7008
|
init_context();
|
|
6708
7009
|
init_daemon();
|
|
6709
7010
|
init_doctor();
|
|
7011
|
+
init_env();
|
|
6710
7012
|
init_handoff();
|
|
6711
7013
|
init_home();
|
|
6712
7014
|
init_memory();
|