@noir-ai/cli 1.0.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/LICENSE +21 -0
- package/README.md +51 -0
- package/dist/bin.d.ts +14 -0
- package/dist/bin.js +1445 -0
- package/dist/bin.js.map +1 -0
- package/dist/chunk-UXT7YWQY.js +530 -0
- package/dist/chunk-UXT7YWQY.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/output-De3lKbZq.d.ts +38 -0
- package/dist/sync-7YLRJYMR.js +20 -0
- package/dist/sync-7YLRJYMR.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/output.ts
|
|
4
|
+
import Table from "cli-table3";
|
|
5
|
+
import { CommanderError } from "commander";
|
|
6
|
+
import ora from "ora";
|
|
7
|
+
import pc from "picocolors";
|
|
8
|
+
var EXIT = {
|
|
9
|
+
OK: 0,
|
|
10
|
+
ERROR: 1,
|
|
11
|
+
USAGE: 2,
|
|
12
|
+
NOT_FOUND: 3,
|
|
13
|
+
DAEMON_DOWN: 4,
|
|
14
|
+
CANCELLED: 5
|
|
15
|
+
};
|
|
16
|
+
var NoirCliError = class extends Error {
|
|
17
|
+
exitCode;
|
|
18
|
+
constructor(exitCode, message) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = "NoirCliError";
|
|
21
|
+
this.exitCode = exitCode;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var NOIR_ERROR_CODE = "noir.error";
|
|
25
|
+
function isJsonMode(opts) {
|
|
26
|
+
return opts.json === true;
|
|
27
|
+
}
|
|
28
|
+
function isQuietMode(opts) {
|
|
29
|
+
return opts.quiet === true;
|
|
30
|
+
}
|
|
31
|
+
function isNoInput(opts = {}) {
|
|
32
|
+
return opts.noInput === true || opts.input === false;
|
|
33
|
+
}
|
|
34
|
+
function envFlagSet(name) {
|
|
35
|
+
const v = process.env[name];
|
|
36
|
+
return v !== void 0 && v !== "";
|
|
37
|
+
}
|
|
38
|
+
function isCiEnv() {
|
|
39
|
+
const v = process.env.CI;
|
|
40
|
+
if (v === void 0 || v === "") return false;
|
|
41
|
+
return v !== "0" && v !== "false";
|
|
42
|
+
}
|
|
43
|
+
function isInteractive(opts = {}) {
|
|
44
|
+
if (isJsonMode(opts) || isNoInput(opts)) return false;
|
|
45
|
+
if (isCiEnv() || envFlagSet("NO_COLOR")) return false;
|
|
46
|
+
return process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
47
|
+
}
|
|
48
|
+
function log(msg2, opts = {}) {
|
|
49
|
+
if (isJsonMode(opts) || isQuietMode(opts)) return;
|
|
50
|
+
process.stderr.write(`${msg2}
|
|
51
|
+
`);
|
|
52
|
+
}
|
|
53
|
+
function info(msg2, opts = {}) {
|
|
54
|
+
if (isJsonMode(opts) || isQuietMode(opts)) return;
|
|
55
|
+
process.stderr.write(`${pc.cyan(msg2)}
|
|
56
|
+
`);
|
|
57
|
+
}
|
|
58
|
+
function success(msg2, opts = {}) {
|
|
59
|
+
if (isJsonMode(opts) || isQuietMode(opts)) return;
|
|
60
|
+
process.stderr.write(`${pc.green(msg2)}
|
|
61
|
+
`);
|
|
62
|
+
}
|
|
63
|
+
function warn(msg2, opts = {}) {
|
|
64
|
+
if (isJsonMode(opts)) return;
|
|
65
|
+
process.stderr.write(`${pc.yellow(msg2)}
|
|
66
|
+
`);
|
|
67
|
+
}
|
|
68
|
+
function error(msg2, opts = {}) {
|
|
69
|
+
if (isJsonMode(opts)) return;
|
|
70
|
+
process.stderr.write(`${pc.red(msg2)}
|
|
71
|
+
`);
|
|
72
|
+
}
|
|
73
|
+
function table(rows, cols, opts = {}) {
|
|
74
|
+
if (isJsonMode(opts)) return;
|
|
75
|
+
if (rows.length === 0) {
|
|
76
|
+
info("(no rows)", opts);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const t = new Table({ head: [...cols] });
|
|
80
|
+
for (const row of rows) {
|
|
81
|
+
t.push(cols.map((col) => formatCell(row[col])));
|
|
82
|
+
}
|
|
83
|
+
process.stderr.write(`${t.toString()}
|
|
84
|
+
`);
|
|
85
|
+
}
|
|
86
|
+
function formatCell(value) {
|
|
87
|
+
if (value === void 0 || value === null) return "";
|
|
88
|
+
if (typeof value === "string") return value;
|
|
89
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
90
|
+
return String(value);
|
|
91
|
+
}
|
|
92
|
+
return JSON.stringify(value);
|
|
93
|
+
}
|
|
94
|
+
function fail(exitCode, message, opts = {}) {
|
|
95
|
+
if (isJsonMode(opts)) {
|
|
96
|
+
process.stdout.write(`${JSON.stringify({ ok: false, error: { code: exitCode, message } })}
|
|
97
|
+
`);
|
|
98
|
+
} else if (message.length > 0) {
|
|
99
|
+
process.stderr.write(`${message}
|
|
100
|
+
`);
|
|
101
|
+
}
|
|
102
|
+
throw new CommanderError(exitCode, NOIR_ERROR_CODE, message);
|
|
103
|
+
}
|
|
104
|
+
function commanderExitCode(err) {
|
|
105
|
+
switch (err.code) {
|
|
106
|
+
case "commander.helpDisplayed":
|
|
107
|
+
case "commander.versionDisplayed":
|
|
108
|
+
return EXIT.OK;
|
|
109
|
+
case "commander.unknownCommand":
|
|
110
|
+
return EXIT.NOT_FOUND;
|
|
111
|
+
default:
|
|
112
|
+
return EXIT.USAGE;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function inferExitCode(err) {
|
|
116
|
+
if (err instanceof NoirCliError) return err.exitCode;
|
|
117
|
+
if (err instanceof CommanderError) {
|
|
118
|
+
if (err.code.startsWith("noir.")) return err.exitCode;
|
|
119
|
+
return commanderExitCode(err);
|
|
120
|
+
}
|
|
121
|
+
return EXIT.ERROR;
|
|
122
|
+
}
|
|
123
|
+
function handleError(err) {
|
|
124
|
+
if (err instanceof NoirCliError) {
|
|
125
|
+
if (err.message.length > 0) process.stderr.write(`${err.message}
|
|
126
|
+
`);
|
|
127
|
+
} else if (err instanceof CommanderError) {
|
|
128
|
+
} else {
|
|
129
|
+
process.stderr.write(`noir: ${err instanceof Error ? err.message : String(err)}
|
|
130
|
+
`);
|
|
131
|
+
}
|
|
132
|
+
process.exitCode = inferExitCode(err);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/commands/doctor.ts
|
|
136
|
+
import { existsSync } from "fs";
|
|
137
|
+
import { loadProjectInfo, NOIR_VERSION, paths } from "@noir-ai/core";
|
|
138
|
+
import { pidAlive, readDaemonRecord } from "@noir-ai/daemon";
|
|
139
|
+
import { resolveModelConfig } from "@noir-ai/model";
|
|
140
|
+
async function probeOnnx() {
|
|
141
|
+
try {
|
|
142
|
+
const spec = "onnxruntime-node";
|
|
143
|
+
await import(spec);
|
|
144
|
+
return { ok: true, reason: "loadable" };
|
|
145
|
+
} catch (e) {
|
|
146
|
+
return { ok: false, reason: e instanceof Error ? e.message : String(e) };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function describeOnnx(o) {
|
|
150
|
+
if (o.ok) return "loadable";
|
|
151
|
+
if (/Cannot find (module|package)/i.test(o.reason)) {
|
|
152
|
+
return "not resolvable from CLI probe (best-effort)";
|
|
153
|
+
}
|
|
154
|
+
const r = o.reason.length > 60 ? `${o.reason.slice(0, 59)}\u2026` : o.reason;
|
|
155
|
+
return `probe failed: ${r}`;
|
|
156
|
+
}
|
|
157
|
+
function fmtUptime(sec) {
|
|
158
|
+
if (!Number.isFinite(sec) || sec < 0) return "unknown";
|
|
159
|
+
if (sec < 60) return `${sec}s`;
|
|
160
|
+
if (sec < 3600) return `${Math.floor(sec / 60)}m ${sec % 60}s`;
|
|
161
|
+
return `${Math.floor(sec / 3600)}h ${Math.floor(sec % 3600 / 60)}m`;
|
|
162
|
+
}
|
|
163
|
+
function msg(e) {
|
|
164
|
+
return e instanceof Error ? e.message : String(e);
|
|
165
|
+
}
|
|
166
|
+
async function checkRuntime(checks) {
|
|
167
|
+
checks.push({
|
|
168
|
+
name: "runtime",
|
|
169
|
+
status: "ok",
|
|
170
|
+
detail: `noir ${NOIR_VERSION} \xB7 node ${process.version} \xB7 ${process.platform}`
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
function checkConfig(root) {
|
|
174
|
+
const configPath = paths.config(root);
|
|
175
|
+
if (!existsSync(configPath)) {
|
|
176
|
+
return {
|
|
177
|
+
project: void 0,
|
|
178
|
+
result: {
|
|
179
|
+
name: "config",
|
|
180
|
+
status: "warn",
|
|
181
|
+
detail: `no .noir/config.yml in ${root} \u2014 run \`noir init\``
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
const project = loadProjectInfo(root);
|
|
187
|
+
return {
|
|
188
|
+
project,
|
|
189
|
+
result: {
|
|
190
|
+
name: "config",
|
|
191
|
+
status: "ok",
|
|
192
|
+
detail: `host=${project.config.host} \xB7 mode=${project.config.mode}`
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
} catch (e) {
|
|
196
|
+
return {
|
|
197
|
+
project: void 0,
|
|
198
|
+
result: { name: "config", status: "fail", detail: `config parse error: ${msg(e)}` }
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
async function checkDaemon(checks) {
|
|
203
|
+
const rec = readDaemonRecord();
|
|
204
|
+
if (!rec) {
|
|
205
|
+
checks.push({
|
|
206
|
+
name: "daemon",
|
|
207
|
+
status: "warn",
|
|
208
|
+
detail: "not running (start with `noir daemon start`)"
|
|
209
|
+
});
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (!pidAlive(rec.pid)) {
|
|
213
|
+
checks.push({
|
|
214
|
+
name: "daemon",
|
|
215
|
+
status: "warn",
|
|
216
|
+
detail: `stale record (pid ${rec.pid} not alive)`
|
|
217
|
+
});
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
const res = await fetch(`http://127.0.0.1:${rec.port}/health`);
|
|
222
|
+
const body = res.status === 200 ? await res.json() : null;
|
|
223
|
+
if (body && body.ok === true) {
|
|
224
|
+
checks.push({
|
|
225
|
+
name: "daemon",
|
|
226
|
+
status: "ok",
|
|
227
|
+
detail: `running (pid ${rec.pid}, port ${rec.port}, up ${fmtUptime(body.uptimeSec ?? 0)})`
|
|
228
|
+
});
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
checks.push({
|
|
232
|
+
name: "daemon",
|
|
233
|
+
status: "warn",
|
|
234
|
+
detail: `record present but /health unreachable on port ${rec.port}`
|
|
235
|
+
});
|
|
236
|
+
} catch {
|
|
237
|
+
checks.push({
|
|
238
|
+
name: "daemon",
|
|
239
|
+
status: "warn",
|
|
240
|
+
detail: `record present but /health unreachable on port ${rec.port}`
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
async function checkNativeDeps(checks) {
|
|
245
|
+
const { vecAvailability } = await import("@noir-ai/store");
|
|
246
|
+
const vec = vecAvailability();
|
|
247
|
+
const onnx = await probeOnnx();
|
|
248
|
+
const vecOk = vec.ok === true;
|
|
249
|
+
const onnxOk = onnx.ok === true;
|
|
250
|
+
const status = vecOk ? onnxOk ? "ok" : "warn" : "fail";
|
|
251
|
+
checks.push({
|
|
252
|
+
name: "native deps",
|
|
253
|
+
status,
|
|
254
|
+
detail: `better-sqlite3+sqlite-vec: ${vecOk ? "loadable" : vec.ok === false ? vec.reason : "unknown"} \xB7 onnxruntime-node: ${describeOnnx(onnx)}`
|
|
255
|
+
});
|
|
256
|
+
return { vecOk };
|
|
257
|
+
}
|
|
258
|
+
async function checkStore(checks, project, root) {
|
|
259
|
+
if (!project) {
|
|
260
|
+
checks.push({ name: "store", status: "warn", detail: "skipped \u2014 not initialized" });
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const { openStore } = await import("@noir-ai/store");
|
|
264
|
+
let store;
|
|
265
|
+
try {
|
|
266
|
+
store = await openStore({ projectId: project.id, root, readonly: true });
|
|
267
|
+
checks.push({
|
|
268
|
+
name: "store",
|
|
269
|
+
status: "ok",
|
|
270
|
+
detail: `opens readonly (db: ${paths.storeDb(root, project.id)})`
|
|
271
|
+
});
|
|
272
|
+
} catch (e) {
|
|
273
|
+
checks.push({ name: "store", status: "fail", detail: `open failed: ${msg(e)}` });
|
|
274
|
+
} finally {
|
|
275
|
+
if (store) {
|
|
276
|
+
try {
|
|
277
|
+
await Promise.resolve(store.close());
|
|
278
|
+
} catch {
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function checkEmbedder(checks, project, vecOk) {
|
|
284
|
+
if (!project) {
|
|
285
|
+
checks.push({ name: "embedder", status: "warn", detail: "skipped \u2014 not initialized" });
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const emb = project.config.context.embedder;
|
|
289
|
+
const kind = emb.kind;
|
|
290
|
+
const model = typeof emb.model === "string" && emb.model.length > 0 ? emb.model : "<unset>";
|
|
291
|
+
if (kind === "none") {
|
|
292
|
+
checks.push({
|
|
293
|
+
name: "embedder",
|
|
294
|
+
status: "ok",
|
|
295
|
+
detail: `kind=none (BM25-only) \xB7 dim=${emb.dim}`
|
|
296
|
+
});
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (!vecOk) {
|
|
300
|
+
checks.push({
|
|
301
|
+
name: "embedder",
|
|
302
|
+
status: "warn",
|
|
303
|
+
detail: `${kind}/${model} (${emb.dim}-dim) configured but vec native layer unavailable \u2014 search degrades to BM25`
|
|
304
|
+
});
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
checks.push({
|
|
308
|
+
name: "embedder",
|
|
309
|
+
status: "ok",
|
|
310
|
+
detail: `${kind}/${model} (${emb.dim}-dim) \xB7 vec backend available`
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
function checkProvider(checks, project) {
|
|
314
|
+
if (!project) {
|
|
315
|
+
checks.push({ name: "provider", status: "warn", detail: "skipped \u2014 not initialized" });
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const resolved = resolveModelConfig(project.config.model);
|
|
319
|
+
const names = Object.keys(resolved.providers);
|
|
320
|
+
if (names.length === 0) {
|
|
321
|
+
checks.push({
|
|
322
|
+
name: "provider",
|
|
323
|
+
status: "ok",
|
|
324
|
+
detail: "none configured (offline mode; drafting degrades to templates)"
|
|
325
|
+
});
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const parts = [];
|
|
329
|
+
let missing = false;
|
|
330
|
+
for (const name of names) {
|
|
331
|
+
const p = resolved.providers[name];
|
|
332
|
+
if (!p) continue;
|
|
333
|
+
const key = p.hasKey ? "key present" : p.apiKeyEnv ? `missing ${p.apiKeyEnv}` : "anonymous";
|
|
334
|
+
if (!p.hasKey && p.apiKeyEnv) missing = true;
|
|
335
|
+
parts.push(`${name}/${p.model ?? "?"} (${key})`);
|
|
336
|
+
}
|
|
337
|
+
checks.push({
|
|
338
|
+
name: "provider",
|
|
339
|
+
status: missing ? "warn" : "ok",
|
|
340
|
+
detail: parts.join(" \xB7 ")
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
function summarize(checks) {
|
|
344
|
+
let ok = 0;
|
|
345
|
+
let warnN = 0;
|
|
346
|
+
let fail2 = 0;
|
|
347
|
+
for (const c of checks) {
|
|
348
|
+
if (c.status === "ok") ok++;
|
|
349
|
+
else if (c.status === "warn") warnN++;
|
|
350
|
+
else fail2++;
|
|
351
|
+
}
|
|
352
|
+
return { ok, warn: warnN, fail: fail2 };
|
|
353
|
+
}
|
|
354
|
+
function renderHuman(payload, opts) {
|
|
355
|
+
log(
|
|
356
|
+
`noir doctor \u2014 ${payload.checks.length} check${payload.checks.length === 1 ? "" : "s"}`,
|
|
357
|
+
opts
|
|
358
|
+
);
|
|
359
|
+
table(
|
|
360
|
+
payload.checks.map((c) => ({
|
|
361
|
+
Check: c.name,
|
|
362
|
+
Status: c.status.toUpperCase(),
|
|
363
|
+
Detail: c.detail
|
|
364
|
+
})),
|
|
365
|
+
["Check", "Status", "Detail"],
|
|
366
|
+
opts
|
|
367
|
+
);
|
|
368
|
+
const { ok, warn: warnN, fail: fail2 } = payload.summary;
|
|
369
|
+
if (fail2 > 0) {
|
|
370
|
+
error(
|
|
371
|
+
`${fail2} critical check${fail2 === 1 ? "" : "s"} failed (${warnN} warning${warnN === 1 ? "" : "s"}, ${ok} ok)`,
|
|
372
|
+
opts
|
|
373
|
+
);
|
|
374
|
+
} else if (warnN > 0) {
|
|
375
|
+
warn(`all critical checks passed (${warnN} warning${warnN === 1 ? "" : "s"}, ${ok} ok)`, opts);
|
|
376
|
+
} else {
|
|
377
|
+
success(`all ${ok} check${ok === 1 ? "" : "s"} passed`, opts);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
async function doctor(opts = {}) {
|
|
381
|
+
const root = process.cwd();
|
|
382
|
+
const checks = [];
|
|
383
|
+
await checkRuntime(checks);
|
|
384
|
+
const { project, result: configResult } = checkConfig(root);
|
|
385
|
+
checks.push(configResult);
|
|
386
|
+
await checkDaemon(checks);
|
|
387
|
+
const { vecOk } = await checkNativeDeps(checks);
|
|
388
|
+
await checkStore(checks, project, root);
|
|
389
|
+
checkEmbedder(checks, project, vecOk);
|
|
390
|
+
checkProvider(checks, project);
|
|
391
|
+
const summary = summarize(checks);
|
|
392
|
+
const payload = { noir: NOIR_VERSION, checks, summary };
|
|
393
|
+
if (opts.json === true) {
|
|
394
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: payload })}
|
|
395
|
+
`);
|
|
396
|
+
} else {
|
|
397
|
+
renderHuman(payload, opts);
|
|
398
|
+
}
|
|
399
|
+
if (summary.fail > 0) {
|
|
400
|
+
throw new NoirCliError(
|
|
401
|
+
EXIT.ERROR,
|
|
402
|
+
opts.json === true ? "" : `noir doctor: ${summary.fail} critical check(s) failed`
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// src/init.ts
|
|
408
|
+
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
409
|
+
import { join } from "path";
|
|
410
|
+
import { claudeAdapter } from "@noir-ai/adapters";
|
|
411
|
+
import { CONTEXT_BLOCK_BEGIN, CONTEXT_BLOCK_END, createProjectId, paths as paths2 } from "@noir-ai/core";
|
|
412
|
+
import { emitSkillsToDir } from "@noir-ai/skills";
|
|
413
|
+
async function init(root, opts) {
|
|
414
|
+
if (opts.transport === "streamable-http" && opts.url === void 0) {
|
|
415
|
+
throw new Error("--transport streamable-http requires --url");
|
|
416
|
+
}
|
|
417
|
+
if (opts.url !== void 0) {
|
|
418
|
+
assertLocalhostUrl(opts.url);
|
|
419
|
+
}
|
|
420
|
+
mkdirSync(paths2.noirDir(root), { recursive: true });
|
|
421
|
+
const id = createProjectId();
|
|
422
|
+
writeFileSync(paths2.projectId(root), `${id}
|
|
423
|
+
`, "utf8");
|
|
424
|
+
writeFileSync(paths2.config(root), "host: claude\nmode: full\n", "utf8");
|
|
425
|
+
writeFileSync(
|
|
426
|
+
paths2.noirMd(root),
|
|
427
|
+
`# Noir context
|
|
428
|
+
|
|
429
|
+
Project id: \`${id}\`
|
|
430
|
+
|
|
431
|
+
<!-- Noir auto-manages this file. Host context files @import it. -->
|
|
432
|
+
`,
|
|
433
|
+
"utf8"
|
|
434
|
+
);
|
|
435
|
+
writeFileSync(
|
|
436
|
+
join(root, ".mcp.json"),
|
|
437
|
+
`${claudeAdapter.emitMcpConfig({ root }, opts)}
|
|
438
|
+
`,
|
|
439
|
+
"utf8"
|
|
440
|
+
);
|
|
441
|
+
const existing = safeRead(join(root, "CLAUDE.md"));
|
|
442
|
+
writeFileSync(
|
|
443
|
+
join(root, "CLAUDE.md"),
|
|
444
|
+
replaceBlock(existing, claudeAdapter.emitContext({ root })),
|
|
445
|
+
"utf8"
|
|
446
|
+
);
|
|
447
|
+
if (claudeAdapter.skillsDir) {
|
|
448
|
+
const summary = await emitSkillsToDir(claudeAdapter.skillsDir({ root }));
|
|
449
|
+
process.stderr.write(`Emitted ${summary.emitted.length} Noir skills to .claude/skills/.
|
|
450
|
+
`);
|
|
451
|
+
}
|
|
452
|
+
process.stderr.write(`Noir initialized in ${root} (transport: ${opts.transport}).
|
|
453
|
+
`);
|
|
454
|
+
}
|
|
455
|
+
function safeRead(file) {
|
|
456
|
+
try {
|
|
457
|
+
return readFileSync(file, "utf8");
|
|
458
|
+
} catch {
|
|
459
|
+
return "";
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
function replaceBlock(content, block) {
|
|
463
|
+
const begin = CONTEXT_BLOCK_BEGIN;
|
|
464
|
+
const end = CONTEXT_BLOCK_END;
|
|
465
|
+
const re = new RegExp(`${escapeRe(begin)}[\\s\\S]*?${escapeRe(end)}\\n?`, "g");
|
|
466
|
+
const stripped = content.replace(re, "");
|
|
467
|
+
return `${stripped ? `${stripped.trimEnd()}
|
|
468
|
+
|
|
469
|
+
` : ""}${block}`;
|
|
470
|
+
}
|
|
471
|
+
function escapeRe(s) {
|
|
472
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
473
|
+
}
|
|
474
|
+
function assertLocalhostUrl(raw) {
|
|
475
|
+
let url;
|
|
476
|
+
try {
|
|
477
|
+
url = new URL(raw);
|
|
478
|
+
} catch {
|
|
479
|
+
throw new Error(`Invalid URL: ${raw}`);
|
|
480
|
+
}
|
|
481
|
+
if (!["http:", "https:"].includes(url.protocol)) {
|
|
482
|
+
throw new Error("Only http/https URLs are supported");
|
|
483
|
+
}
|
|
484
|
+
if (!["localhost", "127.0.0.1", "::1"].includes(url.hostname)) {
|
|
485
|
+
throw new Error(`Only localhost URLs are supported (got ${url.hostname})`);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// src/serve.ts
|
|
490
|
+
import { loadProjectInfo as loadProjectInfo2 } from "@noir-ai/core";
|
|
491
|
+
import { ensureDaemonRunning, startStdioServer } from "@noir-ai/daemon";
|
|
492
|
+
async function serve(opts) {
|
|
493
|
+
const project = loadProjectInfo2(process.cwd());
|
|
494
|
+
if (opts.stdio) {
|
|
495
|
+
await startStdioServer({ project, transport: "stdio", daemon: false });
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
try {
|
|
499
|
+
const { url } = await ensureDaemonRunning({
|
|
500
|
+
project,
|
|
501
|
+
idleTimeoutSec: project.config.daemon.idleTimeoutSec
|
|
502
|
+
});
|
|
503
|
+
process.stderr.write(
|
|
504
|
+
`Noir daemon available at ${url}. (For HTTP clients, use this URL in .mcp.json.)
|
|
505
|
+
`
|
|
506
|
+
);
|
|
507
|
+
} catch (err) {
|
|
508
|
+
process.stderr.write(
|
|
509
|
+
`Noir daemon unavailable (${err instanceof Error ? err.message : String(err)}); falling back to stdio.
|
|
510
|
+
`
|
|
511
|
+
);
|
|
512
|
+
await startStdioServer({ project, transport: "stdio", daemon: false });
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
export {
|
|
517
|
+
EXIT,
|
|
518
|
+
NoirCliError,
|
|
519
|
+
isInteractive,
|
|
520
|
+
log,
|
|
521
|
+
info,
|
|
522
|
+
table,
|
|
523
|
+
fail,
|
|
524
|
+
inferExitCode,
|
|
525
|
+
handleError,
|
|
526
|
+
doctor,
|
|
527
|
+
init,
|
|
528
|
+
serve
|
|
529
|
+
};
|
|
530
|
+
//# sourceMappingURL=chunk-UXT7YWQY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/output.ts","../src/commands/doctor.ts","../src/init.ts","../src/serve.ts"],"sourcesContent":["// S9 t2 — centralized output + exit-code infrastructure for `@noir-ai/cli`.\n//\n// Stream discipline (S9 DS-4): machine-readable data → stdout; every other\n// diagnostic (progress, warnings, errors, tables, help) → stderr. Under `--json`\n// the decorated stderr helpers are silenced so stdout stays pristine JSON, and\n// `fail()` emits a structured `{ok:false,error}` envelope to stdout instead.\n//\n// Decoration (picocolors / cli-table3 / ora) auto-disables under `--json`,\n// `--quiet`, `CI`, `NO_COLOR`, or a non-TTY, so the same code path is safe in an\n// interactive shell, a pipe, and CI.\n//\n// Exit codes (S9 DS-4): 0 ok · 1 error · 2 usage · 3 not-found · 4 daemon-down ·\n// 5 cancelled. These constants live HERE (the single source of truth); bin.ts\n// re-exports them so existing imports from `./bin.js` keep working.\n\nimport Table from 'cli-table3';\nimport { CommanderError } from 'commander';\nimport ora, { type Ora } from 'ora';\nimport pc from 'picocolors';\n\n// ---------------------------------------------------------------------------\n// Exit-code contract + error types\n// ---------------------------------------------------------------------------\nexport const EXIT = {\n OK: 0,\n ERROR: 1,\n USAGE: 2,\n NOT_FOUND: 3,\n DAEMON_DOWN: 4,\n CANCELLED: 5,\n} as const;\n\nexport type ExitCode = (typeof EXIT)[keyof typeof EXIT];\n\n/**\n * Application-level CLI error carrying an S9 exit code. Thrown directly by\n * bin.ts for legacy usage paths; `fail()` is the ergonomic throw helper.\n * `inferExitCode` / `handleError` map it onto `process.exitCode`.\n */\nexport class NoirCliError extends Error {\n readonly exitCode: number;\n constructor(exitCode: number, message: string) {\n super(message);\n this.name = 'NoirCliError';\n this.exitCode = exitCode;\n }\n}\n\n/**\n * The `code` stamped onto every CommanderError our own `fail()` throws, so\n * `inferExitCode` can tell \"we threw this on purpose with an authoritative\n * exitCode\" apart from \"commander threw this for help/usage/unknown-command\"\n * (which must be remapped onto the S9 contract by `commanderExitCode`).\n */\nconst NOIR_ERROR_CODE = 'noir.error';\n\n// ---------------------------------------------------------------------------\n// Global-option shape. Commander stores `--no-input` under the attribute\n// `input` (default `true`; the flag sets it `false`) — see commander's\n// `Option.attributeName()` which strips the leading `no-`. Both `input`\n// (commander's real key) and `noInput` (the spec's intent) are accepted so\n// callers may pass either commander's raw globals or a hand-built object.\n// ---------------------------------------------------------------------------\nexport interface CliOptions {\n readonly json?: boolean;\n readonly quiet?: boolean;\n readonly verbose?: boolean;\n /** Commander's storage for `--no-input` (`false` ⇒ no input). */\n readonly input?: boolean;\n /** Convenience alias matching the `--no-input` intent (`true` ⇒ no input). */\n readonly noInput?: boolean;\n}\n\nfunction isJsonMode(opts: CliOptions): boolean {\n return opts.json === true;\n}\n\nfunction isQuietMode(opts: CliOptions): boolean {\n return opts.quiet === true;\n}\n\n/** True when the user has asked for no interactive input (either spelling). */\nexport function isNoInput(opts: CliOptions = {}): boolean {\n return opts.noInput === true || opts.input === false;\n}\n\nfunction envFlagSet(name: string): boolean {\n // NO_COLOR spec: present AND non-empty (regardless of value) disables color.\n const v = process.env[name];\n return v !== undefined && v !== '';\n}\n\nfunction isCiEnv(): boolean {\n // CI conventions: set to `true`, `1`, or a server name. Treat explicit\n // opt-outs (\"0\"/\"false\") as \"not CI\" so users can force-disable the guard.\n const v = process.env.CI;\n if (v === undefined || v === '') return false;\n return v !== '0' && v !== 'false';\n}\n\n// ---------------------------------------------------------------------------\n// Interactivity gate (DS-3/DS-7). Drives both the @clack home menu and the\n// decoration of every helper below. Requires BOTH stdin and stdout to be TTYs:\n// @clack reads keypresses from stdin while ora/picocolors render to stdout.\n// ---------------------------------------------------------------------------\nexport function isInteractive(opts: CliOptions = {}): boolean {\n if (isJsonMode(opts) || isNoInput(opts)) return false;\n if (isCiEnv() || envFlagSet('NO_COLOR')) return false;\n return process.stdin.isTTY === true && process.stdout.isTTY === true;\n}\n\n// ---------------------------------------------------------------------------\n// Data output (stdout). The ONLY helper here that writes to stdout; under\n// `--json` a command calls this exactly once with its full payload and never\n// mixes other stdout writes, keeping machine output pristine.\n// ---------------------------------------------------------------------------\nexport function json(obj: unknown): void {\n process.stdout.write(`${JSON.stringify(obj)}\\n`);\n}\n\n// ---------------------------------------------------------------------------\n// Human diagnostics (stderr). picocolors auto-strips ANSI under NO_COLOR /\n// non-TTY, and each helper additionally short-circuits under `--json`\n// (decoration off, payload is the output) — and `info`/`log`/`success` also\n// under `--quiet`. `warn`/`error` survive `--quiet` (they carry signal).\n// ---------------------------------------------------------------------------\nexport function log(msg: string, opts: CliOptions = {}): void {\n if (isJsonMode(opts) || isQuietMode(opts)) return;\n process.stderr.write(`${msg}\\n`);\n}\n\nexport function info(msg: string, opts: CliOptions = {}): void {\n if (isJsonMode(opts) || isQuietMode(opts)) return;\n process.stderr.write(`${pc.cyan(msg)}\\n`);\n}\n\nexport function success(msg: string, opts: CliOptions = {}): void {\n if (isJsonMode(opts) || isQuietMode(opts)) return;\n process.stderr.write(`${pc.green(msg)}\\n`);\n}\n\nexport function warn(msg: string, opts: CliOptions = {}): void {\n if (isJsonMode(opts)) return;\n process.stderr.write(`${pc.yellow(msg)}\\n`);\n}\n\nexport function error(msg: string, opts: CliOptions = {}): void {\n if (isJsonMode(opts)) return;\n process.stderr.write(`${pc.red(msg)}\\n`);\n}\n\n// ---------------------------------------------------------------------------\n// Tables (stderr). Suppressed entirely under `--json` — the command has\n// already emitted the rows as a JSON array via `json()`. cli-table3 itself is\n// isTTY-aware, but we gate it here so JSON pipes never see table text.\n// ---------------------------------------------------------------------------\nexport function table(\n rows: readonly Record<string, unknown>[],\n cols: readonly string[],\n opts: CliOptions = {},\n): void {\n if (isJsonMode(opts)) return;\n if (rows.length === 0) {\n info('(no rows)', opts);\n return;\n }\n const t = new Table({ head: [...cols] });\n for (const row of rows) {\n t.push(cols.map((col) => formatCell(row[col])));\n }\n process.stderr.write(`${t.toString()}\\n`);\n}\n\nfunction formatCell(value: unknown): string {\n if (value === undefined || value === null) return '';\n if (typeof value === 'string') return value;\n if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {\n return String(value);\n }\n return JSON.stringify(value);\n}\n\n// ---------------------------------------------------------------------------\n// Spinner (stderr). ora when interactive; a no-op otherwise so scriptable /\n// CI / `--json` / `--quiet` runs pay nothing and never animate a pipe.\n// ---------------------------------------------------------------------------\nexport interface Spinner {\n start(text?: string): Spinner;\n stop(): Spinner;\n clear(): Spinner;\n succeed(text?: string): void;\n fail(text?: string): void;\n warn(text?: string): void;\n info(text?: string): void;\n text: string;\n}\n\nclass NoopSpinner implements Spinner {\n text = '';\n start(_text?: string): Spinner {\n return this;\n }\n stop(): Spinner {\n return this;\n }\n clear(): Spinner {\n return this;\n }\n succeed(_text?: string): void {}\n fail(_text?: string): void {}\n warn(_text?: string): void {}\n info(_text?: string): void {}\n}\n\n/** Wraps an `ora` instance so the `Spinner` interface stays decoupled from ora's exact type. */\nclass OraSpinner implements Spinner {\n private readonly handle: Ora;\n\n constructor(initialText: string) {\n this.handle = ora(initialText);\n }\n\n get text(): string {\n return this.handle.text;\n }\n set text(value: string) {\n this.handle.text = value;\n }\n\n start(text?: string): Spinner {\n if (text !== undefined) this.handle.text = text;\n this.handle.start();\n return this;\n }\n\n stop(): Spinner {\n this.handle.stop();\n return this;\n }\n\n clear(): Spinner {\n this.handle.clear();\n return this;\n }\n\n succeed(text?: string): void {\n this.handle.succeed(text);\n }\n\n fail(text?: string): void {\n this.handle.fail(text);\n }\n\n warn(text?: string): void {\n this.handle.warn(text);\n }\n\n info(text?: string): void {\n this.handle.info(text);\n }\n}\n\nexport function spinner(text = '', opts: CliOptions = {}): Spinner {\n if (isJsonMode(opts) || isQuietMode(opts) || !isInteractive(opts)) {\n return new NoopSpinner();\n }\n return new OraSpinner(text);\n}\n\n// ---------------------------------------------------------------------------\n// Failure + exit-code mapping\n// ---------------------------------------------------------------------------\n\n/**\n * Write a diagnostic and throw a `CommanderError` carrying the S9 exit code,\n * so bin.ts's `exitOverride` + `handleError` surface it as `process.exitCode`\n * without any mid-action `process.exit`. Under `--json` the message becomes a\n * structured `{ok:false,error}` envelope on STDOUT; otherwise plain text on\n * STDERR. Always throws (`never`).\n */\nexport function fail(exitCode: number, message: string, opts: CliOptions = {}): never {\n if (isJsonMode(opts)) {\n process.stdout.write(`${JSON.stringify({ ok: false, error: { code: exitCode, message } })}\\n`);\n } else if (message.length > 0) {\n process.stderr.write(`${message}\\n`);\n }\n throw new CommanderError(exitCode, NOIR_ERROR_CODE, message);\n}\n\n/**\n * Guard for commands that would otherwise block on a prompt. Honors the S9\n * hard rule: \"no blocking prompts when !isTTY or --no-input — error exit 2\n * naming the missing flag instead\". `hint` describes the operation so the\n * message points the user at the flag-based alternative.\n */\nexport function requireInteractive(opts: CliOptions, hint: string): void {\n if (!isInteractive(opts)) {\n fail(\n EXIT.USAGE,\n `${hint} needs an interactive terminal, but input is disabled (non-TTY, --no-input, --json, CI, or NO_COLOR). Re-run in a TTY or supply the value as a flag.`,\n opts,\n );\n }\n}\n\n// Remap commander's own exit codes onto the S9 contract:\n// help/version → 0 · unknown command → 3 · every other usage error → 2\nfunction commanderExitCode(err: CommanderError): number {\n switch (err.code) {\n case 'commander.helpDisplayed':\n case 'commander.versionDisplayed':\n return EXIT.OK;\n case 'commander.unknownCommand':\n return EXIT.NOT_FOUND;\n default:\n return EXIT.USAGE;\n }\n}\n\n/** Map any thrown value onto the S9 exit-code contract. */\nexport function inferExitCode(err: unknown): number {\n if (err instanceof NoirCliError) return err.exitCode;\n if (err instanceof CommanderError) {\n // Errors our own `fail()` threw carry a `noir.*` code and an authoritative\n // exitCode — respect it. Commander's own errors are remapped by `code`.\n if (err.code.startsWith('noir.')) return err.exitCode;\n return commanderExitCode(err);\n }\n return EXIT.ERROR;\n}\n\n/**\n * Map a thrown error onto stderr + `process.exitCode`. Never throws, never\n * calls `process.exit` (commander's `exitOverride` already prevented that for\n * commander's own errors). Used by the bin entry's `run()`.\n */\nexport function handleError(err: unknown): void {\n if (err instanceof NoirCliError) {\n if (err.message.length > 0) process.stderr.write(`${err.message}\\n`);\n } else if (err instanceof CommanderError) {\n // If `fail()` threw it, the message is already written (stderr or the JSON\n // stdout envelope); if commander threw it, `configureOutput.writeErr`\n // already wrote it during parse. Don't double-write either way.\n } else {\n process.stderr.write(`noir: ${err instanceof Error ? err.message : String(err)}\\n`);\n }\n process.exitCode = inferExitCode(err);\n}\n","// S9 t6 — `noir doctor`.\n//\n// Environment + project health. Runs a fixed set of checks and renders a\n// results table (human, stderr) or the versioned `{ok,data}` envelope (--json,\n// stdout). Exit 1 if any CRITICAL (fail) check is unhealthy, else 0.\n//\n// Severity model (S9 spec F8 / t6):\n// - ok — healthy.\n// - warn — degraded but usable (daemon down, onnx missing, provider key\n// missing, project not initialized). NEVER triggers exit 1.\n// - fail — CRITICAL: the product is broken on this host. Only `config` (parse\n// error), `native deps` (better-sqlite3 / sqlite-vec will not load),\n// and `store` (open fails) can fail. Exit 1 if any fail.\n//\n// Honesty rules: provider status uses `resolveModelConfig` — a PURE projection\n// of the user's config + env-var NAME presence; it makes NO live call (blueprint\n// D5/DS-6). Native-dep probes are best-effort try/catch (the store's native\n// binaries are probed via `vecAvailability`, the single cross-package surface;\n// onnxruntime-node is the context engine's own dep and is probed separately).\n//\n// Cold-start (NF6): the @noir-ai/store import (which loads better-sqlite3) is\n// DYNAMIC, inside the action, so merely running `noir status` / `noir --help`\n// never pays the native-bindings cost. @noir-ai/model is imported eagerly — its\n// provider self-registration is light (SDKs load lazily inside `complete()`).\n//\n// Stream discipline (S9 DS-4): `--json` writes `{ok:true, data:{checks,summary}}`\n// to stdout (the only stdout write). Doctor always emits `ok:true` because the\n// COMMAND succeeded in producing a diagnosis; the system-health pass/fail is\n// signaled by the EXIT CODE (0 healthy / 1 critical failure) — the same shape\n// `npm audit --json` uses (valid JSON out, non-zero exit when issues found).\n\nimport { existsSync } from 'node:fs';\nimport { loadProjectInfo, NOIR_VERSION, type ProjectInfo, paths } from '@noir-ai/core';\nimport { pidAlive, readDaemonRecord } from '@noir-ai/daemon';\nimport { resolveModelConfig } from '@noir-ai/model';\nimport {\n type CliOptions,\n EXIT,\n error as err,\n log,\n NoirCliError,\n success,\n table,\n warn,\n} from '../output.js';\n\n/** Options accepted by `doctor` (the global flags only). */\nexport interface DoctorOptions extends CliOptions {}\n\ntype Severity = 'ok' | 'warn' | 'fail';\n\n/** One row of the doctor report (table cell + `--json` element). */\nexport interface CheckResult {\n name: string;\n status: Severity;\n detail: string;\n}\n\n/** The `data` payload of `noir doctor --json`. */\nexport interface DoctorPayload {\n noir: string;\n checks: CheckResult[];\n summary: { ok: number; warn: number; fail: number };\n}\n\n// ---------------------------------------------------------------------------\n// Small helpers\n// ---------------------------------------------------------------------------\n\n/** Best-effort probe of the `onnxruntime-node` native binding. */\nasync function probeOnnx(): Promise<{ ok: boolean; reason: string }> {\n try {\n // Variable specifier (not a string literal) so tsc does NOT try to resolve\n // a module the CLI package does not directly depend on — onnxruntime-node\n // is the context engine's dep and resolves through ITS node_modules at\n // runtime. Best-effort: hosts that hoist it resolve; otherwise not-loadable.\n const spec = 'onnxruntime-node';\n await import(spec);\n return { ok: true, reason: 'loadable' };\n } catch (e) {\n return { ok: false, reason: e instanceof Error ? e.message : String(e) };\n }\n}\n\n/** Human label for the onnx probe outcome (hides confusing module-not-found noise). */\nfunction describeOnnx(o: { ok: boolean; reason: string }): string {\n if (o.ok) return 'loadable';\n if (/Cannot find (module|package)/i.test(o.reason)) {\n return 'not resolvable from CLI probe (best-effort)';\n }\n const r = o.reason.length > 60 ? `${o.reason.slice(0, 59)}…` : o.reason;\n return `probe failed: ${r}`;\n}\n\nfunction fmtUptime(sec: number): string {\n if (!Number.isFinite(sec) || sec < 0) return 'unknown';\n if (sec < 60) return `${sec}s`;\n if (sec < 3600) return `${Math.floor(sec / 60)}m ${sec % 60}s`;\n return `${Math.floor(sec / 3600)}h ${Math.floor((sec % 3600) / 60)}m`;\n}\n\nfunction msg(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n\n// ---------------------------------------------------------------------------\n// Checks. Each appends to `checks`; project-dependent ones short-circuit to a\n// `warn` \"skipped\" row when the project isn't initialized (so the table still\n// accounts for them, but a pre-init `noir doctor` never exit-1s on them).\n// ---------------------------------------------------------------------------\n\nasync function checkRuntime(checks: CheckResult[]): Promise<void> {\n checks.push({\n name: 'runtime',\n status: 'ok',\n detail: `noir ${NOIR_VERSION} · node ${process.version} · ${process.platform}`,\n });\n}\n\nfunction checkConfig(root: string): { project: ProjectInfo | undefined; result: CheckResult } {\n const configPath = paths.config(root);\n if (!existsSync(configPath)) {\n return {\n project: undefined,\n result: {\n name: 'config',\n status: 'warn',\n detail: `no .noir/config.yml in ${root} — run \\`noir init\\``,\n },\n };\n }\n try {\n const project = loadProjectInfo(root);\n return {\n project,\n result: {\n name: 'config',\n status: 'ok',\n detail: `host=${project.config.host} · mode=${project.config.mode}`,\n },\n };\n } catch (e) {\n return {\n project: undefined,\n result: { name: 'config', status: 'fail', detail: `config parse error: ${msg(e)}` },\n };\n }\n}\n\nasync function checkDaemon(checks: CheckResult[]): Promise<void> {\n const rec = readDaemonRecord();\n if (!rec) {\n checks.push({\n name: 'daemon',\n status: 'warn',\n detail: 'not running (start with `noir daemon start`)',\n });\n return;\n }\n if (!pidAlive(rec.pid)) {\n checks.push({\n name: 'daemon',\n status: 'warn',\n detail: `stale record (pid ${rec.pid} not alive)`,\n });\n return;\n }\n try {\n const res = await fetch(`http://127.0.0.1:${rec.port}/health`);\n const body =\n res.status === 200 ? ((await res.json()) as { ok?: boolean; uptimeSec?: number }) : null;\n if (body && body.ok === true) {\n checks.push({\n name: 'daemon',\n status: 'ok',\n detail: `running (pid ${rec.pid}, port ${rec.port}, up ${fmtUptime(body.uptimeSec ?? 0)})`,\n });\n return;\n }\n checks.push({\n name: 'daemon',\n status: 'warn',\n detail: `record present but /health unreachable on port ${rec.port}`,\n });\n } catch {\n checks.push({\n name: 'daemon',\n status: 'warn',\n detail: `record present but /health unreachable on port ${rec.port}`,\n });\n }\n}\n\nasync function checkNativeDeps(checks: CheckResult[]): Promise<{ vecOk: boolean }> {\n // vecAvailability probes better-sqlite3 + sqlite-vec together (the store opens\n // a Database and loads sqlite-vec). Dynamic import keeps the native bindings\n // out of cold-start for every other command.\n const { vecAvailability } = await import('@noir-ai/store');\n const vec = vecAvailability();\n const onnx = await probeOnnx();\n const vecOk = vec.ok === true;\n const onnxOk = onnx.ok === true;\n // The sqlite side is CRITICAL (the store cannot open without it). onnx only\n // backs local embeddings, so its absence is a warning (search → BM25-only).\n const status: Severity = vecOk ? (onnxOk ? 'ok' : 'warn') : 'fail';\n checks.push({\n name: 'native deps',\n status,\n detail: `better-sqlite3+sqlite-vec: ${vecOk ? 'loadable' : vec.ok === false ? vec.reason : 'unknown'} · onnxruntime-node: ${describeOnnx(onnx)}`,\n });\n return { vecOk };\n}\n\nasync function checkStore(\n checks: CheckResult[],\n project: ProjectInfo | undefined,\n root: string,\n): Promise<void> {\n if (!project) {\n checks.push({ name: 'store', status: 'warn', detail: 'skipped — not initialized' });\n return;\n }\n const { openStore } = await import('@noir-ai/store');\n let store: { close(): Promise<void> | void } | undefined;\n try {\n store = await openStore({ projectId: project.id, root, readonly: true });\n checks.push({\n name: 'store',\n status: 'ok',\n detail: `opens readonly (db: ${paths.storeDb(root, project.id)})`,\n });\n } catch (e) {\n checks.push({ name: 'store', status: 'fail', detail: `open failed: ${msg(e)}` });\n } finally {\n if (store) {\n try {\n await Promise.resolve(store.close());\n } catch {\n /* a close error must not mask the open result */\n }\n }\n }\n}\n\nfunction checkEmbedder(\n checks: CheckResult[],\n project: ProjectInfo | undefined,\n vecOk: boolean,\n): void {\n if (!project) {\n checks.push({ name: 'embedder', status: 'warn', detail: 'skipped — not initialized' });\n return;\n }\n const emb = project.config.context.embedder;\n const kind = emb.kind;\n const model = typeof emb.model === 'string' && emb.model.length > 0 ? emb.model : '<unset>';\n if (kind === 'none') {\n checks.push({\n name: 'embedder',\n status: 'ok',\n detail: `kind=none (BM25-only) · dim=${emb.dim}`,\n });\n return;\n }\n if (!vecOk) {\n // vec native layer is the embedder's hard prereq (vecs write into vec0).\n checks.push({\n name: 'embedder',\n status: 'warn',\n detail: `${kind}/${model} (${emb.dim}-dim) configured but vec native layer unavailable — search degrades to BM25`,\n });\n return;\n }\n checks.push({\n name: 'embedder',\n status: 'ok',\n detail: `${kind}/${model} (${emb.dim}-dim) · vec backend available`,\n });\n}\n\nfunction checkProvider(checks: CheckResult[], project: ProjectInfo | undefined): void {\n if (!project) {\n checks.push({ name: 'provider', status: 'warn', detail: 'skipped — not initialized' });\n return;\n }\n // PURE projection — resolveModelConfig reads config + env-var NAMES only; it\n // NEVER makes a live call and NEVER infers a provider from env-var presence.\n const resolved = resolveModelConfig(project.config.model);\n const names = Object.keys(resolved.providers);\n if (names.length === 0) {\n checks.push({\n name: 'provider',\n status: 'ok',\n detail: 'none configured (offline mode; drafting degrades to templates)',\n });\n return;\n }\n const parts: string[] = [];\n let missing = false;\n for (const name of names) {\n const p = resolved.providers[name];\n if (!p) continue;\n const key = p.hasKey ? 'key present' : p.apiKeyEnv ? `missing ${p.apiKeyEnv}` : 'anonymous';\n if (!p.hasKey && p.apiKeyEnv) missing = true;\n parts.push(`${name}/${p.model ?? '?'} (${key})`);\n }\n // Provider readiness is never CRITICAL — the model layer degrades to `null`\n // (templates) when a key is missing, so this is an ok/warn signal only.\n checks.push({\n name: 'provider',\n status: missing ? 'warn' : 'ok',\n detail: parts.join(' · '),\n });\n}\n\n// ---------------------------------------------------------------------------\n// Rendering + exit\n// ---------------------------------------------------------------------------\n\nfunction summarize(checks: CheckResult[]): DoctorPayload['summary'] {\n let ok = 0;\n let warnN = 0;\n let fail = 0;\n for (const c of checks) {\n if (c.status === 'ok') ok++;\n else if (c.status === 'warn') warnN++;\n else fail++;\n }\n return { ok, warn: warnN, fail };\n}\n\nfunction renderHuman(payload: DoctorPayload, opts: CliOptions): void {\n log(\n `noir doctor — ${payload.checks.length} check${payload.checks.length === 1 ? '' : 's'}`,\n opts,\n );\n table(\n payload.checks.map((c) => ({\n Check: c.name,\n Status: c.status.toUpperCase(),\n Detail: c.detail,\n })),\n ['Check', 'Status', 'Detail'],\n opts,\n );\n const { ok, warn: warnN, fail } = payload.summary;\n if (fail > 0) {\n err(\n `${fail} critical check${fail === 1 ? '' : 's'} failed (${warnN} warning${warnN === 1 ? '' : 's'}, ${ok} ok)`,\n opts,\n );\n } else if (warnN > 0) {\n warn(`all critical checks passed (${warnN} warning${warnN === 1 ? '' : 's'}, ${ok} ok)`, opts);\n } else {\n success(`all ${ok} check${ok === 1 ? '' : 's'} passed`, opts);\n }\n}\n\n/**\n * `noir doctor`: run all checks, render, and exit.\n *\n * Under `--json` writes `{ok:true, data: DoctorPayload}` to stdout (always —\n * the command produced a diagnosis). The exit code carries health: 0 when no\n * check failed, 1 when any CRITICAL (`fail`) check is unhealthy.\n */\nexport async function doctor(opts: DoctorOptions = {}): Promise<void> {\n const root = process.cwd();\n const checks: CheckResult[] = [];\n\n await checkRuntime(checks);\n const { project, result: configResult } = checkConfig(root);\n checks.push(configResult);\n await checkDaemon(checks);\n const { vecOk } = await checkNativeDeps(checks);\n await checkStore(checks, project, root);\n checkEmbedder(checks, project, vecOk);\n checkProvider(checks, project);\n\n const summary = summarize(checks);\n const payload: DoctorPayload = { noir: NOIR_VERSION, checks, summary };\n\n if (opts.json === true) {\n process.stdout.write(`${JSON.stringify({ ok: true, data: payload })}\\n`);\n } else {\n renderHuman(payload, opts);\n }\n\n if (summary.fail > 0) {\n // Under --json the data envelope is already on stdout; throw with an empty\n // message so handleError sets exitCode=1 without a redundant stderr line.\n // Under human mode the summary line above already named the failures.\n throw new NoirCliError(\n EXIT.ERROR,\n opts.json === true ? '' : `noir doctor: ${summary.fail} critical check(s) failed`,\n );\n }\n}\n","import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { claudeAdapter } from '@noir-ai/adapters';\nimport { CONTEXT_BLOCK_BEGIN, CONTEXT_BLOCK_END, createProjectId, paths } from '@noir-ai/core';\nimport { emitSkillsToDir } from '@noir-ai/skills';\n\nexport interface InitOptions {\n transport: 'stdio' | 'streamable-http';\n url?: string;\n}\n\nexport async function init(root: string, opts: InitOptions): Promise<void> {\n if (opts.transport === 'streamable-http' && opts.url === undefined) {\n throw new Error('--transport streamable-http requires --url');\n }\n if (opts.url !== undefined) {\n assertLocalhostUrl(opts.url);\n }\n\n mkdirSync(paths.noirDir(root), { recursive: true });\n\n const id = createProjectId();\n writeFileSync(paths.projectId(root), `${id}\\n`, 'utf8');\n writeFileSync(paths.config(root), 'host: claude\\nmode: full\\n', 'utf8');\n writeFileSync(\n paths.noirMd(root),\n `# Noir context\\n\\nProject id: \\`${id}\\`\\n\\n<!-- Noir auto-manages this file. Host context files @import it. -->\\n`,\n 'utf8',\n );\n\n writeFileSync(\n join(root, '.mcp.json'),\n `${claudeAdapter.emitMcpConfig({ root }, opts)}\\n`,\n 'utf8',\n );\n\n const existing = safeRead(join(root, 'CLAUDE.md'));\n writeFileSync(\n join(root, 'CLAUDE.md'),\n replaceBlock(existing, claudeAdapter.emitContext({ root })),\n 'utf8',\n );\n\n if (claudeAdapter.skillsDir) {\n const summary = await emitSkillsToDir(claudeAdapter.skillsDir({ root }));\n process.stderr.write(`Emitted ${summary.emitted.length} Noir skills to .claude/skills/.\\n`);\n }\n\n process.stderr.write(`Noir initialized in ${root} (transport: ${opts.transport}).\\n`);\n}\n\nfunction safeRead(file: string): string {\n try {\n return readFileSync(file, 'utf8');\n } catch {\n return '';\n }\n}\nfunction replaceBlock(content: string, block: string): string {\n const begin = CONTEXT_BLOCK_BEGIN;\n const end = CONTEXT_BLOCK_END;\n const re = new RegExp(`${escapeRe(begin)}[\\\\s\\\\S]*?${escapeRe(end)}\\\\n?`, 'g');\n const stripped = content.replace(re, '');\n return `${stripped ? `${stripped.trimEnd()}\\n\\n` : ''}${block}`;\n}\nfunction escapeRe(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n// Validate a streamable-http --url is http(s) and localhost-only. Gate 2's\n// daemon binds 127.0.0.1, so persisting a non-localhost URL is a footgun.\n// Not exported: the single call site is init(); thrown errors propagate to\n// bin.ts's main().catch (stderr + exitCode 1).\nfunction assertLocalhostUrl(raw: string): void {\n let url: URL;\n try {\n url = new URL(raw);\n } catch {\n throw new Error(`Invalid URL: ${raw}`);\n }\n if (!['http:', 'https:'].includes(url.protocol)) {\n throw new Error('Only http/https URLs are supported');\n }\n if (!['localhost', '127.0.0.1', '::1'].includes(url.hostname)) {\n throw new Error(`Only localhost URLs are supported (got ${url.hostname})`);\n }\n}\n","import { loadProjectInfo } from '@noir-ai/core';\nimport { ensureDaemonRunning, startStdioServer } from '@noir-ai/daemon';\n\nexport async function serve(opts: { stdio: boolean }): Promise<void> {\n const project = loadProjectInfo(process.cwd());\n if (opts.stdio) {\n await startStdioServer({ project, transport: 'stdio', daemon: false });\n return; // stdio server runs until stdin closes\n }\n // Prefer the shared daemon; on failure fall back to in-process stdio so Noir\n // keeps working even when the daemon cannot start (FS-degradation path).\n try {\n const { url } = await ensureDaemonRunning({\n project,\n idleTimeoutSec: project.config.daemon.idleTimeoutSec,\n });\n process.stderr.write(\n `Noir daemon available at ${url}. (For HTTP clients, use this URL in .mcp.json.)\\n`,\n );\n } catch (err) {\n process.stderr.write(\n `Noir daemon unavailable (${err instanceof Error ? err.message : String(err)}); falling back to stdio.\\n`,\n );\n await startStdioServer({ project, transport: 'stdio', daemon: false });\n }\n}\n"],"mappings":"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","names":["msg","fail","paths","loadProjectInfo"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { C as CliOptions } from './output-De3lKbZq.js';
|
|
2
|
+
|
|
3
|
+
/** Options accepted by `doctor` (the global flags only). */
|
|
4
|
+
interface DoctorOptions extends CliOptions {
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* `noir doctor`: run all checks, render, and exit.
|
|
8
|
+
*
|
|
9
|
+
* Under `--json` writes `{ok:true, data: DoctorPayload}` to stdout (always —
|
|
10
|
+
* the command produced a diagnosis). The exit code carries health: 0 when no
|
|
11
|
+
* check failed, 1 when any CRITICAL (`fail`) check is unhealthy.
|
|
12
|
+
*/
|
|
13
|
+
declare function doctor(opts?: DoctorOptions): Promise<void>;
|
|
14
|
+
|
|
15
|
+
interface InitOptions {
|
|
16
|
+
transport: 'stdio' | 'streamable-http';
|
|
17
|
+
url?: string;
|
|
18
|
+
}
|
|
19
|
+
declare function init(root: string, opts: InitOptions): Promise<void>;
|
|
20
|
+
|
|
21
|
+
declare function serve(opts: {
|
|
22
|
+
stdio: boolean;
|
|
23
|
+
}): Promise<void>;
|
|
24
|
+
|
|
25
|
+
export { type InitOptions, doctor, init, serve };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|