@noir-ai/cli 1.12.0-beta.1 → 1.13.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +731 -112
- package/dist/bin.js.map +1 -1
- package/dist/index.js +736 -117
- package/dist/index.js.map +1 -1
- package/dist/tui/{chunk-J77RHAWG.js → chunk-KMLSJRYV.js} +705 -97
- package/dist/tui/chunk-KMLSJRYV.js.map +1 -0
- package/dist/tui/{create-BTYLBVV6.js → create-HYFBTBLR.js} +2 -2
- package/dist/tui/index.js +1 -1
- package/dist/tui/{sync-LRHPJ5VV.js → sync-QDOJ7INZ.js} +2 -2
- package/package.json +11 -11
- package/dist/tui/chunk-J77RHAWG.js.map +0 -1
- /package/dist/tui/{create-BTYLBVV6.js.map → create-HYFBTBLR.js.map} +0 -0
- /package/dist/tui/{sync-LRHPJ5VV.js.map → sync-QDOJ7INZ.js.map} +0 -0
package/dist/bin.js
CHANGED
|
@@ -17,11 +17,17 @@ var __export = (target, all) => {
|
|
|
17
17
|
// src/daemon-client.ts
|
|
18
18
|
import { Client, StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
|
|
19
19
|
import { ContextEngine, createEmbedFn, resolveEmbedderConfig } from "@noir-ai/context";
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
loadProjectInfo,
|
|
22
|
+
NOIR_VERSION,
|
|
23
|
+
readWorkspaceMarker,
|
|
24
|
+
workspaceStoreDbPath
|
|
25
|
+
} from "@noir-ai/core";
|
|
21
26
|
import {
|
|
22
27
|
ensureDaemonRunning,
|
|
23
28
|
pidAlive,
|
|
24
29
|
readDaemonRecord,
|
|
30
|
+
readWorkspaceDaemonRecord,
|
|
25
31
|
resolveGateConfig
|
|
26
32
|
} from "@noir-ai/daemon";
|
|
27
33
|
import { createMemoryEngine } from "@noir-ai/memory";
|
|
@@ -235,6 +241,91 @@ async function withInProcessRead(opts, fn) {
|
|
|
235
241
|
});
|
|
236
242
|
}
|
|
237
243
|
}
|
|
244
|
+
function workspaceRoutingTarget(root) {
|
|
245
|
+
const name = readWorkspaceMarker(root);
|
|
246
|
+
if (name === null) return null;
|
|
247
|
+
try {
|
|
248
|
+
return { name, projectId: loadProjectInfo(root).id };
|
|
249
|
+
} catch {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
async function probeWorkspaceDaemon(routing) {
|
|
254
|
+
const rec = readWorkspaceDaemonRecord(routing.name);
|
|
255
|
+
if (rec === null || !pidAlive(rec.pid)) return { running: false };
|
|
256
|
+
try {
|
|
257
|
+
const res = await fetch(`http://127.0.0.1:${rec.port}/health`, {
|
|
258
|
+
signal: AbortSignal.timeout(PROBE_TIMEOUT_MS)
|
|
259
|
+
});
|
|
260
|
+
if (!res.ok) return { running: false };
|
|
261
|
+
const body = await res.json().catch(() => null);
|
|
262
|
+
if (body?.ok !== true || body.workspace !== routing.name || body.pid !== rec.pid) {
|
|
263
|
+
return { running: false };
|
|
264
|
+
}
|
|
265
|
+
return { running: true, pid: rec.pid, port: rec.port };
|
|
266
|
+
} catch {
|
|
267
|
+
return { running: false };
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
async function withWorkspaceDaemon(opts, routing, fn) {
|
|
271
|
+
const probe = await probeWorkspaceDaemon(routing);
|
|
272
|
+
if (!probe.running || probe.port === void 0) {
|
|
273
|
+
fail(
|
|
274
|
+
EXIT.DAEMON_DOWN,
|
|
275
|
+
`workspace ${JSON.stringify(routing.name)} daemon is not running \u2014 start it with \`noir daemon start --workspace ${routing.name}\` (or run \`noir workspace status\`).`,
|
|
276
|
+
opts
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
const url = `http://127.0.0.1:${probe.port}/mcp?p=${routing.projectId}`;
|
|
280
|
+
let client;
|
|
281
|
+
try {
|
|
282
|
+
client = new Client(
|
|
283
|
+
{ name: "noir-cli", version: NOIR_VERSION },
|
|
284
|
+
{ versionNegotiation: { mode: "auto" } }
|
|
285
|
+
);
|
|
286
|
+
const connected = client;
|
|
287
|
+
await connectClient(connected, url, opts);
|
|
288
|
+
return await fn(buildCaller(connected, opts));
|
|
289
|
+
} finally {
|
|
290
|
+
if (client) {
|
|
291
|
+
await client.close().catch(() => {
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
async function withWorkspaceMemoryRead(opts, routing, project, fn) {
|
|
297
|
+
let store;
|
|
298
|
+
try {
|
|
299
|
+
store = await openStore({
|
|
300
|
+
projectId: `ws-${routing.name}`,
|
|
301
|
+
root: project.root,
|
|
302
|
+
dbPath: workspaceStoreDbPath(routing.name),
|
|
303
|
+
readonly: true
|
|
304
|
+
});
|
|
305
|
+
} catch (err) {
|
|
306
|
+
fail(
|
|
307
|
+
EXIT.DAEMON_DOWN,
|
|
308
|
+
`workspace ${JSON.stringify(routing.name)} store is unavailable (${err instanceof Error ? err.message : String(err)}) \u2014 start its daemon with \`noir daemon start --workspace ${routing.name}\` (or run \`noir workspace status\`).`,
|
|
309
|
+
opts
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
const embedderCfg = resolveEmbedderConfig(project.config.context);
|
|
313
|
+
const embed = createEmbedFn(embedderCfg).embed;
|
|
314
|
+
const memory = createMemoryEngine({
|
|
315
|
+
store,
|
|
316
|
+
root: project.root,
|
|
317
|
+
projectId: `ws-${routing.name}`,
|
|
318
|
+
embed,
|
|
319
|
+
storeDegraded: true,
|
|
320
|
+
softForget: true
|
|
321
|
+
});
|
|
322
|
+
try {
|
|
323
|
+
return await fn(memory);
|
|
324
|
+
} finally {
|
|
325
|
+
await store.close().catch(() => {
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
}
|
|
238
329
|
var DAEMON_DOWN_HINT, PROBE_TIMEOUT_MS;
|
|
239
330
|
var init_daemon_client = __esm({
|
|
240
331
|
"src/daemon-client.ts"() {
|
|
@@ -771,12 +862,326 @@ var init_context = __esm({
|
|
|
771
862
|
}
|
|
772
863
|
});
|
|
773
864
|
|
|
865
|
+
// src/workspace-mcp.ts
|
|
866
|
+
import { existsSync, readFileSync } from "fs";
|
|
867
|
+
import { join } from "path";
|
|
868
|
+
import { resolveAdapter } from "@noir-ai/adapters";
|
|
869
|
+
import { atomicWriteFile, resolveNoirCommand } from "@noir-ai/core";
|
|
870
|
+
function mcpPathFor(root, host) {
|
|
871
|
+
return resolveAdapter(host).mcpConfigPath?.({ root }) ?? join(root, ".mcp.json");
|
|
872
|
+
}
|
|
873
|
+
function readJson(path) {
|
|
874
|
+
if (!existsSync(path)) return null;
|
|
875
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
876
|
+
}
|
|
877
|
+
function loadExisting(path, opts) {
|
|
878
|
+
let existing;
|
|
879
|
+
try {
|
|
880
|
+
existing = readJson(path);
|
|
881
|
+
} catch {
|
|
882
|
+
if (opts.force !== true) {
|
|
883
|
+
fail(
|
|
884
|
+
EXIT.ERROR,
|
|
885
|
+
`Refusing to rewrite ${path}: it is not valid JSON (add --force to overwrite).`,
|
|
886
|
+
opts
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
existing = null;
|
|
890
|
+
}
|
|
891
|
+
if (existing !== null && !isNoirEmitted(existing.mcpServers) && opts.force !== true) {
|
|
892
|
+
fail(
|
|
893
|
+
EXIT.ERROR,
|
|
894
|
+
`Refusing to rewrite ${path}: it does not look like a Noir-emitted config (add --force to overwrite).`,
|
|
895
|
+
opts
|
|
896
|
+
);
|
|
897
|
+
}
|
|
898
|
+
return existing;
|
|
899
|
+
}
|
|
900
|
+
function isNoirEmitted(mcpServers) {
|
|
901
|
+
const noir = mcpServers?.noir;
|
|
902
|
+
if (!noir) return false;
|
|
903
|
+
if (typeof noir.command === "string") return true;
|
|
904
|
+
return noir.type === "http";
|
|
905
|
+
}
|
|
906
|
+
function writeWorkspaceHttpEntry(root, host, url, projectId, opts) {
|
|
907
|
+
const path = mcpPathFor(root, host);
|
|
908
|
+
const existing = loadExisting(path, opts);
|
|
909
|
+
const next = {
|
|
910
|
+
...existing ?? {},
|
|
911
|
+
mcpServers: {
|
|
912
|
+
...existing?.mcpServers ?? {},
|
|
913
|
+
noir: { type: "http", url: `${url}?p=${projectId}` }
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
atomicWriteFile(path, `${JSON.stringify(next, null, 2)}
|
|
917
|
+
`);
|
|
918
|
+
}
|
|
919
|
+
function writeStdioEntry(root, host, opts) {
|
|
920
|
+
const path = mcpPathFor(root, host);
|
|
921
|
+
const existing = loadExisting(path, opts);
|
|
922
|
+
const noir = { command: resolveNoirCommand(), args: ["mcp", "serve", "--stdio"] };
|
|
923
|
+
const next = { ...existing ?? {}, mcpServers: { ...existing?.mcpServers ?? {}, noir } };
|
|
924
|
+
atomicWriteFile(path, `${JSON.stringify(next, null, 2)}
|
|
925
|
+
`);
|
|
926
|
+
}
|
|
927
|
+
var init_workspace_mcp = __esm({
|
|
928
|
+
"src/workspace-mcp.ts"() {
|
|
929
|
+
"use strict";
|
|
930
|
+
init_output();
|
|
931
|
+
}
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
// src/commands/workspace.ts
|
|
935
|
+
import { readdirSync } from "fs";
|
|
936
|
+
import {
|
|
937
|
+
clearWorkspaceMarker,
|
|
938
|
+
ensureWorkspaceRegistry,
|
|
939
|
+
isValidWorkspaceName,
|
|
940
|
+
isWorkspaceMember,
|
|
941
|
+
loadProjectInfo as loadProjectInfo2,
|
|
942
|
+
readWorkspaceMarker as readWorkspaceMarker2,
|
|
943
|
+
readWorkspaceRegistry,
|
|
944
|
+
removeWorkspaceMember,
|
|
945
|
+
upsertWorkspaceMember,
|
|
946
|
+
workspaceHomeDir,
|
|
947
|
+
writeWorkspaceMarker
|
|
948
|
+
} from "@noir-ai/core";
|
|
949
|
+
import {
|
|
950
|
+
clearWorkspaceDaemonRecord,
|
|
951
|
+
ensureWorkspaceDaemonRunning,
|
|
952
|
+
pidAlive as pidAlive2,
|
|
953
|
+
readWorkspaceDaemonRecord as readWorkspaceDaemonRecord2,
|
|
954
|
+
spawnDetachedWorkspaceDaemon
|
|
955
|
+
} from "@noir-ai/daemon";
|
|
956
|
+
function loadProjectOrFail(opts) {
|
|
957
|
+
try {
|
|
958
|
+
return loadProjectInfo2(process.cwd());
|
|
959
|
+
} catch {
|
|
960
|
+
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
function finishJoin(project, name, url, opts) {
|
|
964
|
+
writeWorkspaceMarker(project.root, name);
|
|
965
|
+
writeWorkspaceHttpEntry(project.root, project.config.host, url, project.id, opts);
|
|
966
|
+
}
|
|
967
|
+
async function daemonStartWorkspace(opts) {
|
|
968
|
+
const project = loadProjectOrFail(opts);
|
|
969
|
+
if (!isValidWorkspaceName(opts.name)) {
|
|
970
|
+
fail(
|
|
971
|
+
EXIT.USAGE,
|
|
972
|
+
`invalid workspace name ${JSON.stringify(opts.name)} (lowercase letters, digits, and dashes).`,
|
|
973
|
+
opts
|
|
974
|
+
);
|
|
975
|
+
}
|
|
976
|
+
upsertWorkspaceMember(ensureWorkspaceRegistry(opts.name), {
|
|
977
|
+
projectId: project.id,
|
|
978
|
+
root: project.root,
|
|
979
|
+
joinedAt: Date.now()
|
|
980
|
+
});
|
|
981
|
+
if (opts.detachChild === true) {
|
|
982
|
+
const ensured2 = await ensureWorkspaceDaemonRunning({
|
|
983
|
+
name: opts.name,
|
|
984
|
+
project,
|
|
985
|
+
idleTimeoutSec: project.config.workspace.idleTimeoutSec
|
|
986
|
+
});
|
|
987
|
+
if (opts.json === true) {
|
|
988
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: { mode: "detached" } })}
|
|
989
|
+
`);
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
info("noir workspace daemon: detached foreground. Ctrl+C to stop.", opts);
|
|
993
|
+
log(`workspace daemon listening at ${ensured2.url}`, opts);
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
if (opts.detach === true) {
|
|
997
|
+
const spawned = await spawnDetachedWorkspaceDaemon({ name: opts.name, project });
|
|
998
|
+
finishJoin(project, opts.name, `http://127.0.0.1:${spawned.port}/mcp`, opts);
|
|
999
|
+
if (opts.json === true) {
|
|
1000
|
+
process.stdout.write(
|
|
1001
|
+
`${JSON.stringify({ ok: true, data: { mode: "detached", pid: spawned.pid, port: spawned.port } })}
|
|
1002
|
+
`
|
|
1003
|
+
);
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
info(
|
|
1007
|
+
`workspace "${opts.name}" daemon started (pid ${spawned.pid}, port ${spawned.port}).`,
|
|
1008
|
+
opts
|
|
1009
|
+
);
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
const ensured = await ensureWorkspaceDaemonRunning({
|
|
1013
|
+
name: opts.name,
|
|
1014
|
+
project,
|
|
1015
|
+
idleTimeoutSec: project.config.workspace.idleTimeoutSec
|
|
1016
|
+
});
|
|
1017
|
+
finishJoin(project, opts.name, ensured.url, opts);
|
|
1018
|
+
if (opts.json === true) {
|
|
1019
|
+
process.stdout.write(
|
|
1020
|
+
`${JSON.stringify({ ok: true, data: { url: ensured.url, port: ensured.port, reused: !ensured.started } })}
|
|
1021
|
+
`
|
|
1022
|
+
);
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
info("noir workspace daemon: foreground mode. Ctrl+C to stop.", opts);
|
|
1026
|
+
log(`workspace daemon listening at ${ensured.url}`, opts);
|
|
1027
|
+
}
|
|
1028
|
+
async function daemonJoin(opts) {
|
|
1029
|
+
const project = loadProjectOrFail(opts);
|
|
1030
|
+
if (!isValidWorkspaceName(opts.name)) {
|
|
1031
|
+
fail(EXIT.USAGE, `invalid workspace name ${JSON.stringify(opts.name)}.`, opts);
|
|
1032
|
+
}
|
|
1033
|
+
const reg = readWorkspaceRegistry(opts.name);
|
|
1034
|
+
if (!reg) {
|
|
1035
|
+
fail(
|
|
1036
|
+
EXIT.ERROR,
|
|
1037
|
+
`workspace ${JSON.stringify(opts.name)} not found \u2014 start it from a member repo with \`noir daemon start --workspace ${opts.name}\`.`,
|
|
1038
|
+
opts
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
const ensured = await ensureWorkspaceDaemonRunning({
|
|
1042
|
+
name: opts.name,
|
|
1043
|
+
project,
|
|
1044
|
+
idleTimeoutSec: project.config.workspace.idleTimeoutSec
|
|
1045
|
+
});
|
|
1046
|
+
upsertWorkspaceMember(reg, { projectId: project.id, root: project.root, joinedAt: Date.now() });
|
|
1047
|
+
finishJoin(project, opts.name, ensured.url, opts);
|
|
1048
|
+
if (opts.json === true) {
|
|
1049
|
+
process.stdout.write(
|
|
1050
|
+
`${JSON.stringify({ ok: true, data: { joined: true, url: ensured.url, port: ensured.port } })}
|
|
1051
|
+
`
|
|
1052
|
+
);
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
info(`joined workspace ${JSON.stringify(opts.name)} (${ensured.url}).`, opts);
|
|
1056
|
+
}
|
|
1057
|
+
async function workspaceList(opts) {
|
|
1058
|
+
let names = [];
|
|
1059
|
+
try {
|
|
1060
|
+
names = readdirSync(workspaceHomeDir(), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
1061
|
+
} catch {
|
|
1062
|
+
names = [];
|
|
1063
|
+
}
|
|
1064
|
+
const rows = names.map((n) => {
|
|
1065
|
+
const reg = readWorkspaceRegistry(n);
|
|
1066
|
+
return {
|
|
1067
|
+
name: n,
|
|
1068
|
+
members: reg?.members.length ?? 0,
|
|
1069
|
+
// A record file whose pid is dead (daemon killed without cleanup) must not
|
|
1070
|
+
// read as "running" — mirror the pidAlive liveness `workspace status` uses.
|
|
1071
|
+
running: (() => {
|
|
1072
|
+
const rec = readWorkspaceDaemonRecord2(n);
|
|
1073
|
+
return rec !== null && pidAlive2(rec.pid);
|
|
1074
|
+
})()
|
|
1075
|
+
};
|
|
1076
|
+
});
|
|
1077
|
+
if (opts.json === true) {
|
|
1078
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: { workspaces: rows } })}
|
|
1079
|
+
`);
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
if (rows.length === 0) {
|
|
1083
|
+
info("No workspaces yet.", opts);
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
for (const r of rows) {
|
|
1087
|
+
log(`${r.name} \u2014 ${r.members} member(s)${r.running ? ", running" : ""}`, opts);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
async function workspaceStatus(opts) {
|
|
1091
|
+
const name = opts.name ?? readWorkspaceMarker2(process.cwd()) ?? void 0;
|
|
1092
|
+
if (name === void 0) {
|
|
1093
|
+
fail(EXIT.USAGE, "workspace status requires a name (or run from a joined repo).", opts);
|
|
1094
|
+
}
|
|
1095
|
+
if (!isValidWorkspaceName(name)) {
|
|
1096
|
+
fail(EXIT.USAGE, `invalid workspace name ${JSON.stringify(name)}.`, opts);
|
|
1097
|
+
}
|
|
1098
|
+
const reg = readWorkspaceRegistry(name);
|
|
1099
|
+
const rec = readWorkspaceDaemonRecord2(name);
|
|
1100
|
+
const running = rec !== null && pidAlive2(rec.pid);
|
|
1101
|
+
const data = {
|
|
1102
|
+
name,
|
|
1103
|
+
members: reg?.members.map((m) => m.projectId) ?? [],
|
|
1104
|
+
running,
|
|
1105
|
+
...rec ? { pid: rec.pid, port: rec.port } : {}
|
|
1106
|
+
};
|
|
1107
|
+
if (opts.json === true) {
|
|
1108
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data })}
|
|
1109
|
+
`);
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
log(
|
|
1113
|
+
`workspace ${name}: ${running ? `running (pid ${rec?.pid}, port ${rec?.port})` : "not running"}`,
|
|
1114
|
+
opts
|
|
1115
|
+
);
|
|
1116
|
+
for (const m of data.members) log(` member: ${m}`, opts);
|
|
1117
|
+
}
|
|
1118
|
+
async function workspaceLeave(opts) {
|
|
1119
|
+
const project = loadProjectOrFail(opts);
|
|
1120
|
+
const name = readWorkspaceMarker2(project.root);
|
|
1121
|
+
if (name === null) {
|
|
1122
|
+
fail(EXIT.USAGE, "this repo is not joined to a workspace.", opts);
|
|
1123
|
+
}
|
|
1124
|
+
writeStdioEntry(project.root, project.config.host, opts);
|
|
1125
|
+
const reg = readWorkspaceRegistry(name);
|
|
1126
|
+
if (reg && isWorkspaceMember(reg, project.id)) {
|
|
1127
|
+
removeWorkspaceMember(reg, project.id);
|
|
1128
|
+
}
|
|
1129
|
+
clearWorkspaceMarker(project.root);
|
|
1130
|
+
if (opts.json === true) {
|
|
1131
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: { left: name } })}
|
|
1132
|
+
`);
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
info(`left workspace ${JSON.stringify(name)}.`, opts);
|
|
1136
|
+
}
|
|
1137
|
+
async function workspaceStop(opts) {
|
|
1138
|
+
const name = opts.name ?? readWorkspaceMarker2(process.cwd()) ?? void 0;
|
|
1139
|
+
if (name === void 0) {
|
|
1140
|
+
fail(EXIT.USAGE, "workspace stop requires a name (or run from a joined repo).", opts);
|
|
1141
|
+
}
|
|
1142
|
+
if (!isValidWorkspaceName(name)) {
|
|
1143
|
+
fail(EXIT.USAGE, `invalid workspace name ${JSON.stringify(name)}.`, opts);
|
|
1144
|
+
}
|
|
1145
|
+
const rec = readWorkspaceDaemonRecord2(name);
|
|
1146
|
+
if (rec === null || !pidAlive2(rec.pid)) {
|
|
1147
|
+
if (opts.json === true) {
|
|
1148
|
+
process.stdout.write(
|
|
1149
|
+
`${JSON.stringify({ ok: true, data: { running: false, stopped: false } })}
|
|
1150
|
+
`
|
|
1151
|
+
);
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
info("workspace daemon is not running.", opts);
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
try {
|
|
1158
|
+
process.kill(rec.pid, "SIGTERM");
|
|
1159
|
+
} catch {
|
|
1160
|
+
}
|
|
1161
|
+
clearWorkspaceDaemonRecord(name);
|
|
1162
|
+
if (opts.json === true) {
|
|
1163
|
+
process.stdout.write(
|
|
1164
|
+
`${JSON.stringify({ ok: true, data: { running: false, stopped: true, pid: rec.pid } })}
|
|
1165
|
+
`
|
|
1166
|
+
);
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
info(`stopped workspace daemon (pid ${rec.pid}).`, opts);
|
|
1170
|
+
}
|
|
1171
|
+
var init_workspace = __esm({
|
|
1172
|
+
"src/commands/workspace.ts"() {
|
|
1173
|
+
"use strict";
|
|
1174
|
+
init_output();
|
|
1175
|
+
init_workspace_mcp();
|
|
1176
|
+
}
|
|
1177
|
+
});
|
|
1178
|
+
|
|
774
1179
|
// src/commands/daemon.ts
|
|
775
|
-
import { loadProjectInfo as
|
|
1180
|
+
import { loadProjectInfo as loadProjectInfo3 } from "@noir-ai/core";
|
|
776
1181
|
import {
|
|
777
1182
|
clearDaemonRecord,
|
|
778
1183
|
ensureDaemonRunning as ensureDaemonRunning2,
|
|
779
|
-
pidAlive as
|
|
1184
|
+
pidAlive as pidAlive3,
|
|
780
1185
|
readDaemonRecord as readDaemonRecord2,
|
|
781
1186
|
spawnDetachedDaemon
|
|
782
1187
|
} from "@noir-ai/daemon";
|
|
@@ -788,7 +1193,7 @@ function formatUptime(sec) {
|
|
|
788
1193
|
}
|
|
789
1194
|
function resolveCallerProjectId() {
|
|
790
1195
|
try {
|
|
791
|
-
return
|
|
1196
|
+
return loadProjectInfo3(process.cwd()).id;
|
|
792
1197
|
} catch {
|
|
793
1198
|
return void 0;
|
|
794
1199
|
}
|
|
@@ -809,9 +1214,18 @@ async function isHealthy(port, expectedPid, expectedProjectId) {
|
|
|
809
1214
|
}
|
|
810
1215
|
}
|
|
811
1216
|
async function daemonStart(opts) {
|
|
1217
|
+
if (opts.workspace !== void 0) {
|
|
1218
|
+
await daemonStartWorkspace({
|
|
1219
|
+
...opts,
|
|
1220
|
+
name: opts.workspace,
|
|
1221
|
+
...opts.detach === true ? { detach: true } : {},
|
|
1222
|
+
...opts.detachChild === true ? { detachChild: true } : {}
|
|
1223
|
+
});
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
812
1226
|
let project;
|
|
813
1227
|
try {
|
|
814
|
-
project =
|
|
1228
|
+
project = loadProjectInfo3(process.cwd());
|
|
815
1229
|
} catch {
|
|
816
1230
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
817
1231
|
}
|
|
@@ -842,7 +1256,7 @@ async function daemonStart(opts) {
|
|
|
842
1256
|
}
|
|
843
1257
|
if (opts.detach === true) {
|
|
844
1258
|
const existing = readDaemonRecord2();
|
|
845
|
-
if (existing &&
|
|
1259
|
+
if (existing && pidAlive3(existing.pid) && await isHealthy(existing.port, existing.pid, project.id)) {
|
|
846
1260
|
if (opts.json === true) {
|
|
847
1261
|
process.stdout.write(
|
|
848
1262
|
`${JSON.stringify({ ok: true, data: { mode: "detached", reused: true } })}
|
|
@@ -941,7 +1355,7 @@ async function daemonStop(opts) {
|
|
|
941
1355
|
let errMsg2;
|
|
942
1356
|
const callerProject = resolveCallerProjectId();
|
|
943
1357
|
const wrongProject = rec.projectId !== void 0 && callerProject !== void 0 && rec.projectId !== callerProject;
|
|
944
|
-
const healthy = !wrongProject &&
|
|
1358
|
+
const healthy = !wrongProject && pidAlive3(rec.pid) && await isHealthy(rec.port, rec.pid, rec.projectId === void 0 ? void 0 : callerProject);
|
|
945
1359
|
if (healthy) {
|
|
946
1360
|
try {
|
|
947
1361
|
process.kill(rec.pid, "SIGTERM");
|
|
@@ -987,7 +1401,7 @@ async function daemonStatus(opts) {
|
|
|
987
1401
|
opts
|
|
988
1402
|
);
|
|
989
1403
|
}
|
|
990
|
-
if (!
|
|
1404
|
+
if (!pidAlive3(rec.pid)) {
|
|
991
1405
|
clearDaemonRecord();
|
|
992
1406
|
fail(EXIT.DAEMON_DOWN, "Noir daemon is not running (stale record removed).", opts);
|
|
993
1407
|
}
|
|
@@ -1045,27 +1459,28 @@ var init_daemon = __esm({
|
|
|
1045
1459
|
"use strict";
|
|
1046
1460
|
init_daemon_client();
|
|
1047
1461
|
init_output();
|
|
1462
|
+
init_workspace();
|
|
1048
1463
|
MODE = "foreground";
|
|
1049
1464
|
}
|
|
1050
1465
|
});
|
|
1051
1466
|
|
|
1052
1467
|
// src/commands/doctor.ts
|
|
1053
1468
|
import { spawnSync } from "child_process";
|
|
1054
|
-
import { existsSync, readdirSync, readFileSync, statSync } from "fs";
|
|
1055
|
-
import { dirname, join, relative } from "path";
|
|
1469
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync } from "fs";
|
|
1470
|
+
import { dirname, join as join2, relative } from "path";
|
|
1056
1471
|
import { fileURLToPath } from "url";
|
|
1057
|
-
import { resolveAdapter } from "@noir-ai/adapters";
|
|
1472
|
+
import { resolveAdapter as resolveAdapter2 } from "@noir-ai/adapters";
|
|
1058
1473
|
import {
|
|
1059
1474
|
detectActiveMethod,
|
|
1060
1475
|
latestVersionFromCache,
|
|
1061
|
-
loadProjectInfo as
|
|
1476
|
+
loadProjectInfo as loadProjectInfo4,
|
|
1062
1477
|
NOIR_VERSION as NOIR_VERSION2,
|
|
1063
1478
|
paths,
|
|
1064
1479
|
readInstallRecord,
|
|
1065
1480
|
readUpdateCache
|
|
1066
1481
|
} from "@noir-ai/core";
|
|
1067
1482
|
import { CURRENT_SCAFFOLD_VERSION, readScaffoldVersion } from "@noir-ai/create";
|
|
1068
|
-
import { pidAlive as
|
|
1483
|
+
import { pidAlive as pidAlive4, readDaemonRecord as readDaemonRecord3 } from "@noir-ai/daemon";
|
|
1069
1484
|
import { resolveModelConfig } from "@noir-ai/model";
|
|
1070
1485
|
async function probeOnnx() {
|
|
1071
1486
|
try {
|
|
@@ -1102,7 +1517,7 @@ async function checkRuntime(checks) {
|
|
|
1102
1517
|
}
|
|
1103
1518
|
function checkConfig(root) {
|
|
1104
1519
|
const configPath = paths.config(root);
|
|
1105
|
-
if (!
|
|
1520
|
+
if (!existsSync2(configPath)) {
|
|
1106
1521
|
return {
|
|
1107
1522
|
project: void 0,
|
|
1108
1523
|
result: {
|
|
@@ -1113,7 +1528,7 @@ function checkConfig(root) {
|
|
|
1113
1528
|
};
|
|
1114
1529
|
}
|
|
1115
1530
|
try {
|
|
1116
|
-
const project =
|
|
1531
|
+
const project = loadProjectInfo4(root);
|
|
1117
1532
|
return {
|
|
1118
1533
|
project,
|
|
1119
1534
|
result: {
|
|
@@ -1139,7 +1554,7 @@ async function checkDaemon(checks) {
|
|
|
1139
1554
|
});
|
|
1140
1555
|
return;
|
|
1141
1556
|
}
|
|
1142
|
-
if (!
|
|
1557
|
+
if (!pidAlive4(rec.pid)) {
|
|
1143
1558
|
checks.push({
|
|
1144
1559
|
name: "daemon",
|
|
1145
1560
|
status: "warn",
|
|
@@ -1194,7 +1609,7 @@ async function checkStore(checks, project, root) {
|
|
|
1194
1609
|
return;
|
|
1195
1610
|
}
|
|
1196
1611
|
const dbPath = paths.storeDb(root, project.id);
|
|
1197
|
-
if (!
|
|
1612
|
+
if (!existsSync2(dbPath)) {
|
|
1198
1613
|
checks.push({
|
|
1199
1614
|
name: "store",
|
|
1200
1615
|
status: "warn",
|
|
@@ -1223,7 +1638,7 @@ async function checkStore(checks, project, root) {
|
|
|
1223
1638
|
}
|
|
1224
1639
|
}
|
|
1225
1640
|
function checkNoirEnv(checks, root) {
|
|
1226
|
-
const path =
|
|
1641
|
+
const path = join2(paths.noirDir(root), ".env");
|
|
1227
1642
|
let st;
|
|
1228
1643
|
try {
|
|
1229
1644
|
st = statSync(path);
|
|
@@ -1333,7 +1748,7 @@ function checkRulesMdBudget(checks, root, project) {
|
|
|
1333
1748
|
return null;
|
|
1334
1749
|
}
|
|
1335
1750
|
const rulesPath = paths.rulesMd(root);
|
|
1336
|
-
if (!
|
|
1751
|
+
if (!existsSync2(rulesPath)) {
|
|
1337
1752
|
checks.push({
|
|
1338
1753
|
name: "rules budget",
|
|
1339
1754
|
status: "ok",
|
|
@@ -1343,7 +1758,7 @@ function checkRulesMdBudget(checks, root, project) {
|
|
|
1343
1758
|
}
|
|
1344
1759
|
let content;
|
|
1345
1760
|
try {
|
|
1346
|
-
content =
|
|
1761
|
+
content = readFileSync2(rulesPath, "utf8");
|
|
1347
1762
|
} catch (e) {
|
|
1348
1763
|
checks.push({
|
|
1349
1764
|
name: "rules budget",
|
|
@@ -1387,7 +1802,7 @@ function checkHostArtifacts(checks, root, project) {
|
|
|
1387
1802
|
return null;
|
|
1388
1803
|
}
|
|
1389
1804
|
const host = project.config.host;
|
|
1390
|
-
const adapter =
|
|
1805
|
+
const adapter = resolveAdapter2(host);
|
|
1391
1806
|
const ectx = { root };
|
|
1392
1807
|
const expected = [];
|
|
1393
1808
|
const emitsAgentsMd = host === "agents-md" || host === "cursor" || host === "opencode";
|
|
@@ -1397,7 +1812,7 @@ function checkHostArtifacts(checks, root, project) {
|
|
|
1397
1812
|
if (host === "claude") expected.push("CLAUDE.md");
|
|
1398
1813
|
else if (host === "gemini") expected.push("GEMINI.md");
|
|
1399
1814
|
expected.push(relOr(adapter.mcpConfigPath?.(ectx) ?? ".mcp.json", root));
|
|
1400
|
-
const missing = expected.filter((p) => !
|
|
1815
|
+
const missing = expected.filter((p) => !existsSync2(joinRoot(root, p)));
|
|
1401
1816
|
const status2 = missing.length === 0 ? "ok" : "warn";
|
|
1402
1817
|
const detail = missing.length === 0 ? `host=${host} \xB7 all ${expected.length} primary artifacts present` : `host=${host} \xB7 missing ${missing.join(", ")} (run \`noir sync\` to restore)`;
|
|
1403
1818
|
checks.push({ name: "host artifacts", status: status2, detail });
|
|
@@ -1414,7 +1829,7 @@ function checkNestedNoir(checks, root) {
|
|
|
1414
1829
|
".noir/.npmignore",
|
|
1415
1830
|
".noir/.prettierignore"
|
|
1416
1831
|
];
|
|
1417
|
-
const found = candidates.filter((rel) =>
|
|
1832
|
+
const found = candidates.filter((rel) => existsSync2(join2(root, rel)));
|
|
1418
1833
|
const detected = found.length > 0;
|
|
1419
1834
|
checks.push({
|
|
1420
1835
|
name: "nested .noir",
|
|
@@ -1427,11 +1842,11 @@ function collectDedupCandidates(root) {
|
|
|
1427
1842
|
const rels = ["CLAUDE.md", "AGENTS.md", "GEMINI.md", ".noir/rules/RULES.md"];
|
|
1428
1843
|
const out = [];
|
|
1429
1844
|
for (const rel of rels) {
|
|
1430
|
-
const abs =
|
|
1431
|
-
if (!
|
|
1845
|
+
const abs = join2(root, rel);
|
|
1846
|
+
if (!existsSync2(abs)) continue;
|
|
1432
1847
|
let text;
|
|
1433
1848
|
try {
|
|
1434
|
-
text =
|
|
1849
|
+
text = readFileSync2(abs, "utf8");
|
|
1435
1850
|
} catch {
|
|
1436
1851
|
continue;
|
|
1437
1852
|
}
|
|
@@ -1488,13 +1903,13 @@ async function checkSemanticDupDoctor(checks, root, project, opts = {}) {
|
|
|
1488
1903
|
function resolveWorkspacePackagesDir() {
|
|
1489
1904
|
let here = dirname(fileURLToPath(import.meta.url));
|
|
1490
1905
|
for (let i = 0; i < 8; i++) {
|
|
1491
|
-
const pkgPath =
|
|
1492
|
-
if (
|
|
1906
|
+
const pkgPath = join2(here, "package.json");
|
|
1907
|
+
if (existsSync2(pkgPath)) {
|
|
1493
1908
|
try {
|
|
1494
|
-
const pkg = JSON.parse(
|
|
1909
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
|
|
1495
1910
|
if (pkg.name === "@noir-ai/cli") {
|
|
1496
1911
|
const packagesDir = dirname(here);
|
|
1497
|
-
if (
|
|
1912
|
+
if (existsSync2(join2(dirname(packagesDir), "pnpm-workspace.yaml"))) {
|
|
1498
1913
|
return packagesDir;
|
|
1499
1914
|
}
|
|
1500
1915
|
return null;
|
|
@@ -1539,7 +1954,7 @@ function checkPublish(checks, packagesDir, opts = {}) {
|
|
|
1539
1954
|
}
|
|
1540
1955
|
let entries = [];
|
|
1541
1956
|
try {
|
|
1542
|
-
entries =
|
|
1957
|
+
entries = readdirSync2(packagesDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
1543
1958
|
} catch (e) {
|
|
1544
1959
|
checks.push({
|
|
1545
1960
|
name: "publish",
|
|
@@ -1552,11 +1967,11 @@ function checkPublish(checks, packagesDir, opts = {}) {
|
|
|
1552
1967
|
let checked = 0;
|
|
1553
1968
|
let cliDir = null;
|
|
1554
1969
|
for (const name of entries) {
|
|
1555
|
-
const pkgPath =
|
|
1556
|
-
if (!
|
|
1970
|
+
const pkgPath = join2(packagesDir, name, "package.json");
|
|
1971
|
+
if (!existsSync2(pkgPath)) continue;
|
|
1557
1972
|
let pkg;
|
|
1558
1973
|
try {
|
|
1559
|
-
pkg = JSON.parse(
|
|
1974
|
+
pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
|
|
1560
1975
|
} catch (e) {
|
|
1561
1976
|
issues.push(`${name}/package.json: unparseable (${msg(e)})`);
|
|
1562
1977
|
checked++;
|
|
@@ -1578,7 +1993,7 @@ function checkPublish(checks, packagesDir, opts = {}) {
|
|
|
1578
1993
|
if (pkg.bin == null || typeof pkg.bin === "object" && Object.keys(pkg.bin).length === 0) {
|
|
1579
1994
|
issues.push(`${name}/package.json: bin missing (cli package must declare a bin)`);
|
|
1580
1995
|
}
|
|
1581
|
-
cliDir =
|
|
1996
|
+
cliDir = join2(packagesDir, name);
|
|
1582
1997
|
}
|
|
1583
1998
|
}
|
|
1584
1999
|
let packNote = "";
|
|
@@ -1720,13 +2135,13 @@ __export(install_exports, {
|
|
|
1720
2135
|
installManagedNode: () => installManagedNode
|
|
1721
2136
|
});
|
|
1722
2137
|
import { chmodSync, mkdirSync } from "fs";
|
|
1723
|
-
import { join as
|
|
2138
|
+
import { join as join3 } from "path";
|
|
1724
2139
|
import {
|
|
1725
|
-
atomicWriteFile,
|
|
2140
|
+
atomicWriteFile as atomicWriteFile2,
|
|
1726
2141
|
detectActiveMethod as detectActiveMethod2,
|
|
1727
2142
|
detectInstallMethods,
|
|
1728
2143
|
ensureShimExecutable,
|
|
1729
|
-
loadProjectInfo as
|
|
2144
|
+
loadProjectInfo as loadProjectInfo5,
|
|
1730
2145
|
MANAGED_NODE_VERSION,
|
|
1731
2146
|
NOIR_VERSION as NOIR_VERSION3,
|
|
1732
2147
|
noirHome,
|
|
@@ -1772,8 +2187,8 @@ function buildMigrationPlan(opts) {
|
|
|
1772
2187
|
async function installManagedNode(opts) {
|
|
1773
2188
|
const spec = opts.version ?? opts.channel ?? "latest";
|
|
1774
2189
|
const home2 = noirHome();
|
|
1775
|
-
const cliDir =
|
|
1776
|
-
const binDir =
|
|
2190
|
+
const cliDir = join3(home2, "cli");
|
|
2191
|
+
const binDir = join3(home2, "bin");
|
|
1777
2192
|
mkdirSync(cliDir, { recursive: true });
|
|
1778
2193
|
mkdirSync(binDir, { recursive: true });
|
|
1779
2194
|
let runtime;
|
|
@@ -1799,17 +2214,17 @@ async function installManagedNode(opts) {
|
|
|
1799
2214
|
if (code !== 0) {
|
|
1800
2215
|
return { ok: false, version: null, error: `npm install failed: ${stderr.slice(0, 300)}` };
|
|
1801
2216
|
}
|
|
1802
|
-
const shim =
|
|
2217
|
+
const shim = join3(binDir, "noir");
|
|
1803
2218
|
const shimBody = `#!/usr/bin/env bash
|
|
1804
|
-
"${nodeBin}" "${
|
|
2219
|
+
"${nodeBin}" "${join3(cliDir, "lib", "node_modules", "@noir-ai", "cli", "dist", "bin.js")}" "$@"
|
|
1805
2220
|
`;
|
|
1806
|
-
|
|
2221
|
+
atomicWriteFile2(shim, shimBody);
|
|
1807
2222
|
chmodSync(shim, 493);
|
|
1808
2223
|
ensureShimExecutable();
|
|
1809
2224
|
const ver = await runManagerCmd(
|
|
1810
2225
|
[
|
|
1811
2226
|
nodeBin,
|
|
1812
|
-
|
|
2227
|
+
join3(cliDir, "lib", "node_modules", "@noir-ai", "cli", "dist", "bin.js"),
|
|
1813
2228
|
"--version"
|
|
1814
2229
|
],
|
|
1815
2230
|
{ env: opts.env, timeoutMs: 3e4 }
|
|
@@ -1865,7 +2280,7 @@ async function install(opts = {}) {
|
|
|
1865
2280
|
const installedRecord = readInstallRecord2();
|
|
1866
2281
|
let minVersion = "1.6.0";
|
|
1867
2282
|
try {
|
|
1868
|
-
minVersion =
|
|
2283
|
+
minVersion = loadProjectInfo5(process.cwd()).config.update.minVersion;
|
|
1869
2284
|
} catch {
|
|
1870
2285
|
}
|
|
1871
2286
|
const plan = buildMigrationPlan({
|
|
@@ -1942,7 +2357,7 @@ import {
|
|
|
1942
2357
|
detectActiveMethod as detectActiveMethod3,
|
|
1943
2358
|
ensureShimExecutable as ensureShimExecutable2,
|
|
1944
2359
|
fetchLatestVersion,
|
|
1945
|
-
loadProjectInfo as
|
|
2360
|
+
loadProjectInfo as loadProjectInfo6,
|
|
1946
2361
|
readInstallRecord as readInstallRecord3,
|
|
1947
2362
|
readUpdateCache as readUpdateCache2,
|
|
1948
2363
|
runManagerCmd as runManagerCmd2,
|
|
@@ -2022,7 +2437,7 @@ async function update(opts = {}) {
|
|
|
2022
2437
|
fetchSpin.succeed(`Latest: ${latest}`);
|
|
2023
2438
|
let minVersion = DEFAULT_UPDATE_CONFIG.minVersion;
|
|
2024
2439
|
try {
|
|
2025
|
-
minVersion =
|
|
2440
|
+
minVersion = loadProjectInfo6(process.cwd()).config.update.minVersion;
|
|
2026
2441
|
} catch {
|
|
2027
2442
|
}
|
|
2028
2443
|
const target = buildUpdateTarget({
|
|
@@ -2172,7 +2587,7 @@ var init_update = __esm({
|
|
|
2172
2587
|
});
|
|
2173
2588
|
|
|
2174
2589
|
// src/commands/status.ts
|
|
2175
|
-
import { loadProjectInfo as
|
|
2590
|
+
import { loadProjectInfo as loadProjectInfo7, NOIR_VERSION as NOIR_VERSION4 } from "@noir-ai/core";
|
|
2176
2591
|
async function tryTool(caller, name, args) {
|
|
2177
2592
|
try {
|
|
2178
2593
|
return await caller.callTool(name, args);
|
|
@@ -2293,7 +2708,7 @@ function renderHuman2(p, opts) {
|
|
|
2293
2708
|
async function gatherStatusPayload(opts) {
|
|
2294
2709
|
let project;
|
|
2295
2710
|
try {
|
|
2296
|
-
project =
|
|
2711
|
+
project = loadProjectInfo7(process.cwd());
|
|
2297
2712
|
} catch {
|
|
2298
2713
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
2299
2714
|
}
|
|
@@ -2323,7 +2738,7 @@ async function status(opts) {
|
|
|
2323
2738
|
env: process.env,
|
|
2324
2739
|
configUpdate: (() => {
|
|
2325
2740
|
try {
|
|
2326
|
-
const info2 =
|
|
2741
|
+
const info2 = loadProjectInfo7(process.cwd());
|
|
2327
2742
|
return info2.config.update ?? DEFAULT_UPDATE_CONFIG;
|
|
2328
2743
|
} catch {
|
|
2329
2744
|
return DEFAULT_UPDATE_CONFIG;
|
|
@@ -2352,7 +2767,7 @@ var init_status = __esm({
|
|
|
2352
2767
|
// src/commands/task.ts
|
|
2353
2768
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
2354
2769
|
import { writeFileSync } from "fs";
|
|
2355
|
-
import { loadProjectInfo as
|
|
2770
|
+
import { loadProjectInfo as loadProjectInfo8, nextArtifactSequence, sha256Hex } from "@noir-ai/core";
|
|
2356
2771
|
import {
|
|
2357
2772
|
PHASES,
|
|
2358
2773
|
TASK_CLASSES,
|
|
@@ -2365,7 +2780,7 @@ function skillFor(phase) {
|
|
|
2365
2780
|
}
|
|
2366
2781
|
function writeDoneArtifacts(opts, taskId) {
|
|
2367
2782
|
try {
|
|
2368
|
-
const project =
|
|
2783
|
+
const project = loadProjectInfo8(process.cwd());
|
|
2369
2784
|
const conflict = { conflictPolicy: "preserve", interactive: false };
|
|
2370
2785
|
writeChangelogStub(project.root, `- ${taskId}: completed (verify gate passed)`, conflict);
|
|
2371
2786
|
const nextN = nextArtifactSequence(project.root, "adr");
|
|
@@ -2688,7 +3103,7 @@ function runCheck(cmd) {
|
|
|
2688
3103
|
async function taskVerify(opts) {
|
|
2689
3104
|
let project;
|
|
2690
3105
|
try {
|
|
2691
|
-
project =
|
|
3106
|
+
project = loadProjectInfo8(process.cwd());
|
|
2692
3107
|
} catch {
|
|
2693
3108
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
2694
3109
|
}
|
|
@@ -2894,13 +3309,13 @@ var init_task = __esm({
|
|
|
2894
3309
|
|
|
2895
3310
|
// src/commands/handoff.ts
|
|
2896
3311
|
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
2897
|
-
import { join as
|
|
3312
|
+
import { join as join4 } from "path";
|
|
2898
3313
|
import {
|
|
2899
3314
|
defaultHandoffBlock,
|
|
2900
3315
|
hostLaunchDirective,
|
|
2901
|
-
resolveAdapter as
|
|
3316
|
+
resolveAdapter as resolveAdapter3
|
|
2902
3317
|
} from "@noir-ai/adapters";
|
|
2903
|
-
import { loadProjectInfo as
|
|
3318
|
+
import { loadProjectInfo as loadProjectInfo9, NOIR_DIR, resolveArtifactPath } from "@noir-ai/core";
|
|
2904
3319
|
function heading(name, id) {
|
|
2905
3320
|
return `# Noir handoff \u2014 ${name} (${id})`;
|
|
2906
3321
|
}
|
|
@@ -2996,7 +3411,7 @@ function toHostPayload(p) {
|
|
|
2996
3411
|
}
|
|
2997
3412
|
function resolveDirective(root, p) {
|
|
2998
3413
|
const ctx = { root };
|
|
2999
|
-
const adapter =
|
|
3414
|
+
const adapter = resolveAdapter3(p.host);
|
|
3000
3415
|
const hostPayload = toHostPayload(p);
|
|
3001
3416
|
if (typeof adapter.emitHandoff === "function") {
|
|
3002
3417
|
return adapter.emitHandoff(ctx, hostPayload);
|
|
@@ -3039,7 +3454,7 @@ async function probeOnly(opts) {
|
|
|
3039
3454
|
async function handoff(opts) {
|
|
3040
3455
|
let project;
|
|
3041
3456
|
try {
|
|
3042
|
-
project =
|
|
3457
|
+
project = loadProjectInfo9(process.cwd());
|
|
3043
3458
|
} catch {
|
|
3044
3459
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
3045
3460
|
}
|
|
@@ -3071,7 +3486,7 @@ async function handoff(opts) {
|
|
|
3071
3486
|
if (opts.write === true) {
|
|
3072
3487
|
const md = renderMarkdown(full);
|
|
3073
3488
|
const path = handoffFilePath(project.root, task?.taskId, project.id);
|
|
3074
|
-
mkdirSync2(
|
|
3489
|
+
mkdirSync2(join4(project.root, NOIR_DIR, "handoff"), { recursive: true });
|
|
3075
3490
|
writeFileSync2(path, md, "utf8");
|
|
3076
3491
|
info(`handoff written to ${path}`, opts);
|
|
3077
3492
|
}
|
|
@@ -3403,14 +3818,14 @@ import { hostLaunchDirective as hostLaunchDirective2 } from "@noir-ai/adapters";
|
|
|
3403
3818
|
import {
|
|
3404
3819
|
detectActiveMethod as detectActiveMethod4,
|
|
3405
3820
|
latestVersionFromCache as latestVersionFromCache2,
|
|
3406
|
-
loadProjectInfo as
|
|
3821
|
+
loadProjectInfo as loadProjectInfo10,
|
|
3407
3822
|
NOIR_VERSION as NOIR_VERSION5,
|
|
3408
3823
|
readInstallRecord as readInstallRecord4,
|
|
3409
3824
|
readUpdateCache as readUpdateCache3
|
|
3410
3825
|
} from "@noir-ai/core";
|
|
3411
3826
|
function tryProject() {
|
|
3412
3827
|
try {
|
|
3413
|
-
const info2 =
|
|
3828
|
+
const info2 = loadProjectInfo10(process.cwd());
|
|
3414
3829
|
return { id: info2.id, name: info2.name, host: info2.config.host };
|
|
3415
3830
|
} catch {
|
|
3416
3831
|
return null;
|
|
@@ -3573,7 +3988,7 @@ async function runMenu(opts, deps) {
|
|
|
3573
3988
|
env: process.env,
|
|
3574
3989
|
configUpdate: (() => {
|
|
3575
3990
|
try {
|
|
3576
|
-
const info2 =
|
|
3991
|
+
const info2 = loadProjectInfo10(process.cwd());
|
|
3577
3992
|
return info2.config.update ?? DEFAULT_UPDATE_CONFIG;
|
|
3578
3993
|
} catch {
|
|
3579
3994
|
return DEFAULT_UPDATE_CONFIG;
|
|
@@ -3635,6 +4050,27 @@ var init_home = __esm({
|
|
|
3635
4050
|
});
|
|
3636
4051
|
|
|
3637
4052
|
// src/commands/memory.ts
|
|
4053
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
4054
|
+
import { loadProjectInfo as loadProjectInfo11 } from "@noir-ai/core";
|
|
4055
|
+
function resolveWorkspace() {
|
|
4056
|
+
const routing = workspaceRoutingTarget(process.cwd());
|
|
4057
|
+
if (routing === null) return null;
|
|
4058
|
+
try {
|
|
4059
|
+
return { routing, project: loadProjectInfo11(process.cwd()) };
|
|
4060
|
+
} catch {
|
|
4061
|
+
return null;
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
4064
|
+
async function callMemory(opts, name, args = {}) {
|
|
4065
|
+
const ws = resolveWorkspace();
|
|
4066
|
+
if (ws === null) return callDaemonTool(opts, name, args);
|
|
4067
|
+
return withWorkspaceDaemon(opts, ws.routing, (caller) => caller.callTool(name, args));
|
|
4068
|
+
}
|
|
4069
|
+
async function withMemoryDaemon(opts, fn) {
|
|
4070
|
+
const ws = resolveWorkspace();
|
|
4071
|
+
if (ws === null) return withDaemon(opts, fn);
|
|
4072
|
+
return withWorkspaceDaemon(opts, ws.routing, fn);
|
|
4073
|
+
}
|
|
3638
4074
|
function parseLimit2(raw, label, opts) {
|
|
3639
4075
|
if (raw === void 0) return void 0;
|
|
3640
4076
|
const n = Number(raw);
|
|
@@ -3673,6 +4109,39 @@ function stamp(ts) {
|
|
|
3673
4109
|
}
|
|
3674
4110
|
async function memoryRecall(opts) {
|
|
3675
4111
|
const limit = parseLimit2(opts.limit, "memory recall", opts);
|
|
4112
|
+
const ws = resolveWorkspace();
|
|
4113
|
+
if (ws !== null) {
|
|
4114
|
+
const probe2 = await probeWorkspaceDaemon(ws.routing);
|
|
4115
|
+
if (!probe2.running) {
|
|
4116
|
+
await withWorkspaceMemoryRead(opts, ws.routing, ws.project, async (memory) => {
|
|
4117
|
+
const { hits: hits3, degraded, mode } = await memory.recallWithMeta(opts.query, {
|
|
4118
|
+
...limit === void 0 ? {} : { limit }
|
|
4119
|
+
});
|
|
4120
|
+
const data3 = { query: opts.query, hits: hits3, degraded, mode };
|
|
4121
|
+
if (opts.json === true) {
|
|
4122
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: data3 })}
|
|
4123
|
+
`);
|
|
4124
|
+
return;
|
|
4125
|
+
}
|
|
4126
|
+
renderRecall(data3.query, data3.hits, data3.degraded, opts);
|
|
4127
|
+
});
|
|
4128
|
+
return;
|
|
4129
|
+
}
|
|
4130
|
+
const res2 = await callMemory(opts, "memory_recall", {
|
|
4131
|
+
query: opts.query,
|
|
4132
|
+
...limit === void 0 ? {} : { limit }
|
|
4133
|
+
});
|
|
4134
|
+
if (res2.ok !== true) failTool2("memory recall", res2, opts);
|
|
4135
|
+
const hits2 = Array.isArray(res2.results) ? res2.results.map(toHit2) : [];
|
|
4136
|
+
const data2 = { query: opts.query, hits: hits2, degraded: res2.degraded === true };
|
|
4137
|
+
if (opts.json === true) {
|
|
4138
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: data2 })}
|
|
4139
|
+
`);
|
|
4140
|
+
return;
|
|
4141
|
+
}
|
|
4142
|
+
renderRecall(data2.query, hits2, data2.degraded, opts);
|
|
4143
|
+
return;
|
|
4144
|
+
}
|
|
3676
4145
|
let probe;
|
|
3677
4146
|
try {
|
|
3678
4147
|
probe = await probeDaemon(opts);
|
|
@@ -3734,7 +4203,7 @@ async function memorySave(opts) {
|
|
|
3734
4203
|
const args = { content };
|
|
3735
4204
|
if (typeof opts.type === "string" && opts.type.length > 0) args.type = opts.type;
|
|
3736
4205
|
if (files !== void 0) args.files = files;
|
|
3737
|
-
const res = await
|
|
4206
|
+
const res = await callMemory(opts, "memory_save", args);
|
|
3738
4207
|
if (res.ok !== true) failTool2("memory save", res, opts);
|
|
3739
4208
|
const id = typeof res.id === "string" ? res.id : "";
|
|
3740
4209
|
const observation = res.observation ?? {};
|
|
@@ -3778,7 +4247,98 @@ function renderObservation(obs, opts) {
|
|
|
3778
4247
|
if (typeof content === "string") log(`
|
|
3779
4248
|
${content}`, opts);
|
|
3780
4249
|
}
|
|
4250
|
+
async function memoryCapture(opts) {
|
|
4251
|
+
const content = await resolveCaptureContent(opts);
|
|
4252
|
+
const args = { content };
|
|
4253
|
+
if (typeof opts.eventType === "string" && opts.eventType.length > 0)
|
|
4254
|
+
args.eventType = opts.eventType;
|
|
4255
|
+
const res = await callMemory(opts, "memory_capture", args);
|
|
4256
|
+
if (res.ok !== true) failTool2("memory capture", res, opts);
|
|
4257
|
+
const id = typeof res.id === "string" ? res.id : "";
|
|
4258
|
+
const observation = res.observation ?? {};
|
|
4259
|
+
if (opts.json === true) {
|
|
4260
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: { id, observation } })}
|
|
4261
|
+
`);
|
|
4262
|
+
return;
|
|
4263
|
+
}
|
|
4264
|
+
log(`Captured memory ${id}.`, opts);
|
|
4265
|
+
renderObservation(observation, opts);
|
|
4266
|
+
}
|
|
4267
|
+
async function resolveCaptureContent(opts) {
|
|
4268
|
+
if (typeof opts.content === "string" && opts.content.length > 0) return opts.content;
|
|
4269
|
+
if (typeof opts.file === "string" && opts.file.length > 0) {
|
|
4270
|
+
try {
|
|
4271
|
+
return readFileSync3(opts.file, "utf8");
|
|
4272
|
+
} catch {
|
|
4273
|
+
fail(EXIT.USAGE, `memory capture could not read --file ${opts.file}`, opts);
|
|
4274
|
+
}
|
|
4275
|
+
}
|
|
4276
|
+
if (!process.stdin.isTTY) {
|
|
4277
|
+
try {
|
|
4278
|
+
return readFileSync3(0, "utf8");
|
|
4279
|
+
} catch {
|
|
4280
|
+
fail(
|
|
4281
|
+
EXIT.USAGE,
|
|
4282
|
+
"memory capture could not read piped stdin \u2014 pass --content <text> or --file <path>.",
|
|
4283
|
+
opts
|
|
4284
|
+
);
|
|
4285
|
+
}
|
|
4286
|
+
}
|
|
4287
|
+
if (!isInteractive(opts)) {
|
|
4288
|
+
fail(
|
|
4289
|
+
EXIT.USAGE,
|
|
4290
|
+
"memory capture requires --content <text> or --file <path> (or piped stdin).",
|
|
4291
|
+
opts
|
|
4292
|
+
);
|
|
4293
|
+
}
|
|
4294
|
+
const clack = await import("@clack/prompts");
|
|
4295
|
+
const value = await clack.text({ message: "Captured memory content:" });
|
|
4296
|
+
if (clack.isCancel(value)) {
|
|
4297
|
+
clack.cancel("Cancelled.");
|
|
4298
|
+
fail(EXIT.CANCELLED, "cancelled", opts);
|
|
4299
|
+
}
|
|
4300
|
+
return String(value);
|
|
4301
|
+
}
|
|
3781
4302
|
async function memorySessions(opts) {
|
|
4303
|
+
const ws = resolveWorkspace();
|
|
4304
|
+
if (ws !== null) {
|
|
4305
|
+
const probe2 = await probeWorkspaceDaemon(ws.routing);
|
|
4306
|
+
if (!probe2.running) {
|
|
4307
|
+
await withWorkspaceMemoryRead(opts, ws.routing, ws.project, async (memory) => {
|
|
4308
|
+
const sessions3 = memory.sessions().map((s) => ({
|
|
4309
|
+
id: s.id,
|
|
4310
|
+
count: s.count,
|
|
4311
|
+
lastTs: s.lastTs
|
|
4312
|
+
}));
|
|
4313
|
+
const data3 = { sessions: sessions3 };
|
|
4314
|
+
if (opts.json === true) {
|
|
4315
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: data3 })}
|
|
4316
|
+
`);
|
|
4317
|
+
return;
|
|
4318
|
+
}
|
|
4319
|
+
renderSessions(sessions3, opts);
|
|
4320
|
+
});
|
|
4321
|
+
return;
|
|
4322
|
+
}
|
|
4323
|
+
const res2 = await callMemory(opts, "memory_sessions");
|
|
4324
|
+
if (res2.ok !== true) failTool2("memory sessions", res2, opts);
|
|
4325
|
+
const sessions2 = Array.isArray(res2.sessions) ? res2.sessions.map((raw) => {
|
|
4326
|
+
const s = raw ?? {};
|
|
4327
|
+
return {
|
|
4328
|
+
id: typeof s.id === "string" ? s.id : "",
|
|
4329
|
+
count: typeof s.count === "number" ? s.count : 0,
|
|
4330
|
+
lastTs: typeof s.lastTs === "number" ? s.lastTs : 0
|
|
4331
|
+
};
|
|
4332
|
+
}) : [];
|
|
4333
|
+
const data2 = { sessions: sessions2 };
|
|
4334
|
+
if (opts.json === true) {
|
|
4335
|
+
process.stdout.write(`${JSON.stringify({ ok: true, data: data2 })}
|
|
4336
|
+
`);
|
|
4337
|
+
return;
|
|
4338
|
+
}
|
|
4339
|
+
renderSessions(sessions2, opts);
|
|
4340
|
+
return;
|
|
4341
|
+
}
|
|
3782
4342
|
let probe;
|
|
3783
4343
|
try {
|
|
3784
4344
|
probe = await probeDaemon(opts);
|
|
@@ -3833,7 +4393,7 @@ async function memoryForget(opts) {
|
|
|
3833
4393
|
if (opts.ids.length === 0) {
|
|
3834
4394
|
fail(EXIT.USAGE, "memory forget requires at least one <id>", opts);
|
|
3835
4395
|
}
|
|
3836
|
-
const res = await
|
|
4396
|
+
const res = await callMemory(opts, "memory_forget", {
|
|
3837
4397
|
ids: opts.ids
|
|
3838
4398
|
});
|
|
3839
4399
|
if (res.ok !== true) failTool2("memory forget", res, opts);
|
|
@@ -3847,11 +4407,11 @@ async function memoryForget(opts) {
|
|
|
3847
4407
|
log(`Forgot ${deleted} observation${deleted === 1 ? "" : "s"}.`, opts);
|
|
3848
4408
|
}
|
|
3849
4409
|
async function memoryConsolidate(opts) {
|
|
3850
|
-
const exposed = await
|
|
4410
|
+
const exposed = await withMemoryDaemon(opts, async (caller) => caller.listTools());
|
|
3851
4411
|
if (!exposed.includes("memory_consolidate")) {
|
|
3852
4412
|
fail(
|
|
3853
4413
|
EXIT.ERROR,
|
|
3854
|
-
"memory consolidate: the daemon does not expose the memory_consolidate tool. Enable it in .noir/config under memory.consolidation (enabled: true + a provider + model), then restart the daemon.",
|
|
4414
|
+
resolveWorkspace() !== null ? "memory consolidate is not supported on shared workspaces (the workspace memory store has no consolidation model)." : "memory consolidate: the daemon does not expose the memory_consolidate tool. Enable it in .noir/config under memory.consolidation (enabled: true + a provider + model), then restart the daemon.",
|
|
3855
4415
|
opts
|
|
3856
4416
|
);
|
|
3857
4417
|
}
|
|
@@ -3860,7 +4420,7 @@ async function memoryConsolidate(opts) {
|
|
|
3860
4420
|
const args = {};
|
|
3861
4421
|
if (types !== void 0) args.types = types;
|
|
3862
4422
|
if (limit !== void 0) args.limit = limit;
|
|
3863
|
-
const res = await
|
|
4423
|
+
const res = await callMemory(
|
|
3864
4424
|
opts,
|
|
3865
4425
|
"memory_consolidate",
|
|
3866
4426
|
args
|
|
@@ -4379,10 +4939,10 @@ var init_orchestrator = __esm({
|
|
|
4379
4939
|
});
|
|
4380
4940
|
|
|
4381
4941
|
// src/run-profiles.ts
|
|
4382
|
-
import { loadProjectInfo as
|
|
4942
|
+
import { loadProjectInfo as loadProjectInfo12 } from "@noir-ai/core";
|
|
4383
4943
|
function loadRunConfig(root) {
|
|
4384
4944
|
try {
|
|
4385
|
-
return
|
|
4945
|
+
return loadProjectInfo12(root).config;
|
|
4386
4946
|
} catch {
|
|
4387
4947
|
return null;
|
|
4388
4948
|
}
|
|
@@ -4436,7 +4996,7 @@ var init_run_profiles = __esm({
|
|
|
4436
4996
|
|
|
4437
4997
|
// src/commands/run.ts
|
|
4438
4998
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
4439
|
-
import { join as
|
|
4999
|
+
import { join as join5 } from "path";
|
|
4440
5000
|
import { SUPPORTED_HOSTS } from "@noir-ai/adapters";
|
|
4441
5001
|
import { parseConfig } from "@noir-ai/core";
|
|
4442
5002
|
function formatUsage(u) {
|
|
@@ -4530,8 +5090,8 @@ function mergeEnv(base, overlay) {
|
|
|
4530
5090
|
}
|
|
4531
5091
|
function writeTranscript(host, lines) {
|
|
4532
5092
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4533
|
-
const dir =
|
|
4534
|
-
const file =
|
|
5093
|
+
const dir = join5(process.cwd(), ".noir", "transcripts");
|
|
5094
|
+
const file = join5(dir, `${host}-${ts}.jsonl`);
|
|
4535
5095
|
try {
|
|
4536
5096
|
mkdirSync3(dir, { recursive: true, mode: 448 });
|
|
4537
5097
|
writeFileSync3(file, `${lines.join("\n")}${lines.length > 0 ? "\n" : ""}`, { mode: 384 });
|
|
@@ -4550,8 +5110,8 @@ var init_run = __esm({
|
|
|
4550
5110
|
});
|
|
4551
5111
|
|
|
4552
5112
|
// src/commands/skills.ts
|
|
4553
|
-
import { resolveAdapter as
|
|
4554
|
-
import { loadProjectInfo as
|
|
5113
|
+
import { resolveAdapter as resolveAdapter4 } from "@noir-ai/adapters";
|
|
5114
|
+
import { loadProjectInfo as loadProjectInfo13 } from "@noir-ai/core";
|
|
4555
5115
|
import {
|
|
4556
5116
|
buildRegistry,
|
|
4557
5117
|
discoverAll,
|
|
@@ -4619,11 +5179,11 @@ async function skillsSync(opts) {
|
|
|
4619
5179
|
const root = process.cwd();
|
|
4620
5180
|
let project;
|
|
4621
5181
|
try {
|
|
4622
|
-
project =
|
|
5182
|
+
project = loadProjectInfo13(root);
|
|
4623
5183
|
} catch {
|
|
4624
5184
|
fail(EXIT.ERROR, "Noir is not initialized in this directory. Run `noir init` first.", opts);
|
|
4625
5185
|
}
|
|
4626
|
-
const adapter =
|
|
5186
|
+
const adapter = resolveAdapter4(project.config.host);
|
|
4627
5187
|
if (!adapter.skillsDir) {
|
|
4628
5188
|
const data2 = {
|
|
4629
5189
|
emitted: [],
|
|
@@ -4895,8 +5455,8 @@ var init_conflict = __esm({
|
|
|
4895
5455
|
});
|
|
4896
5456
|
|
|
4897
5457
|
// src/dedup-write.ts
|
|
4898
|
-
import { mkdirSync as mkdirSync4, readFileSync as
|
|
4899
|
-
import { join as
|
|
5458
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync4 } from "fs";
|
|
5459
|
+
import { join as join6 } from "path";
|
|
4900
5460
|
import { sha256Hex as sha256Hex2, sha256Hex12 } from "@noir-ai/core";
|
|
4901
5461
|
async function checkWritePathDedup(root, res, opts) {
|
|
4902
5462
|
const result = { found: [], conflicts: [] };
|
|
@@ -4907,10 +5467,10 @@ async function checkWritePathDedup(root, res, opts) {
|
|
|
4907
5467
|
if (embed === void 0) return result;
|
|
4908
5468
|
const cachedEmbed = makeCachedEmbed(cache, embed);
|
|
4909
5469
|
for (const proposedRel of writtenHostFiles) {
|
|
4910
|
-
const proposedAbs =
|
|
5470
|
+
const proposedAbs = join6(root, proposedRel);
|
|
4911
5471
|
let proposedText;
|
|
4912
5472
|
try {
|
|
4913
|
-
proposedText =
|
|
5473
|
+
proposedText = readFileSync4(proposedAbs, "utf8");
|
|
4914
5474
|
} catch {
|
|
4915
5475
|
continue;
|
|
4916
5476
|
}
|
|
@@ -4954,7 +5514,7 @@ async function checkWritePathDedup(root, res, opts) {
|
|
|
4954
5514
|
`
|
|
4955
5515
|
);
|
|
4956
5516
|
}
|
|
4957
|
-
const matchedText = safeRead(
|
|
5517
|
+
const matchedText = safeRead(join6(root, matchedRel));
|
|
4958
5518
|
result.conflicts.push({
|
|
4959
5519
|
path: proposedRel,
|
|
4960
5520
|
mode: "artifact",
|
|
@@ -5038,21 +5598,21 @@ async function promptDedupAction(root, proposedRel, matchedRel, proposedText, si
|
|
|
5038
5598
|
if (clack.isCancel(choice)) return "preserve";
|
|
5039
5599
|
if (choice === "skip") {
|
|
5040
5600
|
try {
|
|
5041
|
-
rmSync(
|
|
5601
|
+
rmSync(join6(root, proposedRel), { force: true });
|
|
5042
5602
|
} catch {
|
|
5043
5603
|
}
|
|
5044
5604
|
return "preserve";
|
|
5045
5605
|
} else if (choice === "replace") {
|
|
5046
5606
|
try {
|
|
5047
|
-
writeFileSync4(
|
|
5607
|
+
writeFileSync4(join6(root, matchedRel), proposedText, "utf8");
|
|
5048
5608
|
} catch {
|
|
5049
5609
|
}
|
|
5050
5610
|
return "replace";
|
|
5051
5611
|
} else if (choice === "mirror") {
|
|
5052
5612
|
try {
|
|
5053
|
-
const existing =
|
|
5613
|
+
const existing = readFileSync4(join6(root, matchedRel), "utf8");
|
|
5054
5614
|
writeFileSync4(
|
|
5055
|
-
|
|
5615
|
+
join6(root, matchedRel),
|
|
5056
5616
|
`${existing}
|
|
5057
5617
|
|
|
5058
5618
|
<!-- mirrored from ${proposedRel} -->
|
|
@@ -5070,11 +5630,11 @@ function emptyCache() {
|
|
|
5070
5630
|
return { version: DEDUP_CACHE_VERSION, model: "", entries: {} };
|
|
5071
5631
|
}
|
|
5072
5632
|
function cachePath(root) {
|
|
5073
|
-
return
|
|
5633
|
+
return join6(root, ".noir", "dedup-cache.json");
|
|
5074
5634
|
}
|
|
5075
5635
|
function loadCache(root) {
|
|
5076
5636
|
try {
|
|
5077
|
-
const raw =
|
|
5637
|
+
const raw = readFileSync4(cachePath(root), "utf8");
|
|
5078
5638
|
const parsed = JSON.parse(raw);
|
|
5079
5639
|
if (parsed.version !== DEDUP_CACHE_VERSION) return emptyCache();
|
|
5080
5640
|
if (typeof parsed.entries !== "object" || parsed.entries === null) return emptyCache();
|
|
@@ -5089,7 +5649,7 @@ function loadCache(root) {
|
|
|
5089
5649
|
}
|
|
5090
5650
|
function saveCache(root, cache) {
|
|
5091
5651
|
try {
|
|
5092
|
-
mkdirSync4(
|
|
5652
|
+
mkdirSync4(join6(root, ".noir"), { recursive: true });
|
|
5093
5653
|
writeFileSync4(cachePath(root), JSON.stringify(cache), "utf8");
|
|
5094
5654
|
} catch {
|
|
5095
5655
|
}
|
|
@@ -5106,7 +5666,7 @@ function makeCachedEmbed(cache, embed) {
|
|
|
5106
5666
|
}
|
|
5107
5667
|
function safeRead(abs) {
|
|
5108
5668
|
try {
|
|
5109
|
-
return
|
|
5669
|
+
return readFileSync4(abs, "utf8");
|
|
5110
5670
|
} catch {
|
|
5111
5671
|
return void 0;
|
|
5112
5672
|
}
|
|
@@ -5144,8 +5704,8 @@ var init_dedup_write = __esm({
|
|
|
5144
5704
|
});
|
|
5145
5705
|
|
|
5146
5706
|
// src/init.ts
|
|
5147
|
-
import { resolveAdapter as
|
|
5148
|
-
import { loadProjectInfo as
|
|
5707
|
+
import { resolveAdapter as resolveAdapter5 } from "@noir-ai/adapters";
|
|
5708
|
+
import { loadProjectInfo as loadProjectInfo14 } from "@noir-ai/core";
|
|
5149
5709
|
import { scaffold } from "@noir-ai/create";
|
|
5150
5710
|
import { emitSkillsToDir as emitSkillsToDir2 } from "@noir-ai/skills";
|
|
5151
5711
|
async function init(root, opts) {
|
|
@@ -5174,7 +5734,7 @@ async function init(root, opts) {
|
|
|
5174
5734
|
await emitHostSkills(root, host, conflictOpts, interactive);
|
|
5175
5735
|
let projectInfo;
|
|
5176
5736
|
try {
|
|
5177
|
-
projectInfo =
|
|
5737
|
+
projectInfo = loadProjectInfo14(root);
|
|
5178
5738
|
} catch {
|
|
5179
5739
|
projectInfo = void 0;
|
|
5180
5740
|
}
|
|
@@ -5205,7 +5765,7 @@ function reportPlannedWrites(res) {
|
|
|
5205
5765
|
}
|
|
5206
5766
|
}
|
|
5207
5767
|
async function emitHostSkills(root, host, conflictOpts, interactive) {
|
|
5208
|
-
const adapter =
|
|
5768
|
+
const adapter = resolveAdapter5(host);
|
|
5209
5769
|
const skillsDir = adapter.skillsDir?.({ root });
|
|
5210
5770
|
if (skillsDir === void 0) {
|
|
5211
5771
|
process.stderr.write(`host '${host}' has no skill emitter; skipping skills
|
|
@@ -5261,11 +5821,11 @@ var init_init = __esm({
|
|
|
5261
5821
|
});
|
|
5262
5822
|
|
|
5263
5823
|
// src/serve.ts
|
|
5264
|
-
import { applyNoirEnv, loadProjectInfo as
|
|
5824
|
+
import { applyNoirEnv, loadProjectInfo as loadProjectInfo15 } from "@noir-ai/core";
|
|
5265
5825
|
import { ensureDaemonRunning as ensureDaemonRunning3, startStdioServer } from "@noir-ai/daemon";
|
|
5266
5826
|
async function serve(opts) {
|
|
5267
5827
|
applyNoirEnv(process.cwd());
|
|
5268
|
-
const project =
|
|
5828
|
+
const project = loadProjectInfo15(process.cwd());
|
|
5269
5829
|
if (opts.stdio) {
|
|
5270
5830
|
await startStdioServer({ project, transport: "stdio", daemon: false });
|
|
5271
5831
|
return;
|
|
@@ -5299,8 +5859,8 @@ __export(create_exports, {
|
|
|
5299
5859
|
create: () => create
|
|
5300
5860
|
});
|
|
5301
5861
|
import { resolve } from "path";
|
|
5302
|
-
import { resolveAdapter as
|
|
5303
|
-
import { loadProjectInfo as
|
|
5862
|
+
import { resolveAdapter as resolveAdapter6 } from "@noir-ai/adapters";
|
|
5863
|
+
import { loadProjectInfo as loadProjectInfo16 } from "@noir-ai/core";
|
|
5304
5864
|
import { scaffold as scaffold2 } from "@noir-ai/create";
|
|
5305
5865
|
import { emitSkillsToDir as emitSkillsToDir3 } from "@noir-ai/skills";
|
|
5306
5866
|
async function create(dir, opts) {
|
|
@@ -5326,7 +5886,7 @@ async function create(dir, opts) {
|
|
|
5326
5886
|
return res;
|
|
5327
5887
|
}
|
|
5328
5888
|
if (res.noop) return res;
|
|
5329
|
-
const adapter =
|
|
5889
|
+
const adapter = resolveAdapter6(host);
|
|
5330
5890
|
const skillsDir = adapter.skillsDir?.({ root });
|
|
5331
5891
|
if (skillsDir === void 0) {
|
|
5332
5892
|
process.stderr.write(`host '${host}' has no skill emitter; skipping skills
|
|
@@ -5351,7 +5911,7 @@ async function create(dir, opts) {
|
|
|
5351
5911
|
}
|
|
5352
5912
|
let projectInfo;
|
|
5353
5913
|
try {
|
|
5354
|
-
projectInfo =
|
|
5914
|
+
projectInfo = loadProjectInfo16(root);
|
|
5355
5915
|
} catch {
|
|
5356
5916
|
projectInfo = void 0;
|
|
5357
5917
|
}
|
|
@@ -5377,9 +5937,9 @@ var sync_exports = {};
|
|
|
5377
5937
|
__export(sync_exports, {
|
|
5378
5938
|
sync: () => sync
|
|
5379
5939
|
});
|
|
5380
|
-
import { existsSync as
|
|
5381
|
-
import { resolveAdapter as
|
|
5382
|
-
import { loadProjectInfo as
|
|
5940
|
+
import { existsSync as existsSync3 } from "fs";
|
|
5941
|
+
import { resolveAdapter as resolveAdapter7 } from "@noir-ai/adapters";
|
|
5942
|
+
import { loadProjectInfo as loadProjectInfo17, paths as paths2 } from "@noir-ai/core";
|
|
5383
5943
|
import { scaffold as scaffold3 } from "@noir-ai/create";
|
|
5384
5944
|
import { emitSkillsToDir as emitSkillsToDir4 } from "@noir-ai/skills";
|
|
5385
5945
|
async function sync(root, opts = {}) {
|
|
@@ -5388,7 +5948,7 @@ async function sync(root, opts = {}) {
|
|
|
5388
5948
|
const conflictOpts = buildConflictOpts({ force: opts.force, interactive });
|
|
5389
5949
|
let projectInfo;
|
|
5390
5950
|
try {
|
|
5391
|
-
projectInfo =
|
|
5951
|
+
projectInfo = loadProjectInfo17(root);
|
|
5392
5952
|
} catch {
|
|
5393
5953
|
projectInfo = void 0;
|
|
5394
5954
|
}
|
|
@@ -5410,7 +5970,7 @@ async function sync(root, opts = {}) {
|
|
|
5410
5970
|
if (res.written.length === 0 && res.identical.length > 0) {
|
|
5411
5971
|
process.stderr.write("Nothing to sync (Noir-managed files are up to date).\n");
|
|
5412
5972
|
}
|
|
5413
|
-
const adapter =
|
|
5973
|
+
const adapter = resolveAdapter7(host);
|
|
5414
5974
|
const skillsDir = adapter.skillsDir?.({ root });
|
|
5415
5975
|
if (skillsDir === void 0) {
|
|
5416
5976
|
process.stderr.write(`host '${host}' has no skill emitter; skipping skills
|
|
@@ -5446,9 +6006,9 @@ async function sync(root, opts = {}) {
|
|
|
5446
6006
|
}
|
|
5447
6007
|
function resolveSyncHost(root, opts) {
|
|
5448
6008
|
if (opts.host !== void 0) return opts.host;
|
|
5449
|
-
if (!
|
|
6009
|
+
if (!existsSync3(paths2.config(root))) return "claude";
|
|
5450
6010
|
try {
|
|
5451
|
-
return
|
|
6011
|
+
return loadProjectInfo17(root).config.host;
|
|
5452
6012
|
} catch {
|
|
5453
6013
|
return "claude";
|
|
5454
6014
|
}
|
|
@@ -5682,30 +6242,75 @@ function createProgram() {
|
|
|
5682
6242
|
throw new NoirCliError(EXIT.USAGE, "Usage: noir mcp serve [--stdio]");
|
|
5683
6243
|
});
|
|
5684
6244
|
const daemonGrp = program2.command("daemon").description("control the Noir daemon");
|
|
5685
|
-
daemonGrp.command("start").description("start the Noir daemon (foreground, or background with --detach)").option("--detach", "run the daemon in the background and exit").addOption(
|
|
6245
|
+
daemonGrp.command("start").description("start the Noir daemon (foreground, or background with --detach)").option("--detach", "run the daemon in the background and exit").option("--workspace <name>", "start a shared cross-repo workspace daemon instead").option("--force", "overwrite a non-Noir .mcp.json entry (--workspace only)").addOption(
|
|
5686
6246
|
new Option("--_detached-child", "reserved: detached daemon child (internal)").hideHelp()
|
|
5687
6247
|
).action(async (...args) => {
|
|
5688
6248
|
const cmd = trailingCmd(args);
|
|
5689
6249
|
const g = cmd.optsWithGlobals();
|
|
5690
6250
|
const detach = g.detach === true;
|
|
5691
6251
|
const detachChild = g._detachedChild === true;
|
|
6252
|
+
const workspace = typeof g.workspace === "string" ? g.workspace : void 0;
|
|
5692
6253
|
await daemonStart({
|
|
5693
6254
|
...toCliOptions(g),
|
|
5694
6255
|
...detach ? { detach } : {},
|
|
5695
|
-
...detachChild ? { detachChild } : {}
|
|
6256
|
+
...detachChild ? { detachChild } : {},
|
|
6257
|
+
...workspace !== void 0 ? { workspace } : {},
|
|
6258
|
+
...g.force === true ? { force: true } : {}
|
|
5696
6259
|
});
|
|
5697
6260
|
});
|
|
6261
|
+
daemonGrp.command("join").description("join a shared workspace from this repo").argument("<name>", "workspace name").option("--force", "overwrite a non-Noir .mcp.json entry").action(async (...args) => {
|
|
6262
|
+
const cmd = trailingCmd(args);
|
|
6263
|
+
const g = cmd.optsWithGlobals();
|
|
6264
|
+
const name = typeof args[0] === "string" ? args[0] : "";
|
|
6265
|
+
await daemonJoin({ ...toCliOptions(g), name, ...g.force === true ? { force: true } : {} });
|
|
6266
|
+
});
|
|
5698
6267
|
daemonGrp.command("stop").description("stop the Noir daemon").action(async (...args) => {
|
|
5699
6268
|
await daemonStop(toCliOptions(trailingCmd(args).optsWithGlobals()));
|
|
5700
6269
|
});
|
|
5701
6270
|
daemonGrp.command("status").description("report daemon pid/uptime/mode (exit 4 if not running)").action(async (...args) => {
|
|
5702
6271
|
await daemonStatus(toCliOptions(trailingCmd(args).optsWithGlobals()));
|
|
5703
6272
|
});
|
|
5704
|
-
daemonGrp.command("restart").description("stop then start the daemon").action(async (...args) => {
|
|
5705
|
-
|
|
6273
|
+
daemonGrp.command("restart").description("stop then start the daemon").option("--detach", "run the daemon in the background and exit").action(async (...args) => {
|
|
6274
|
+
const cmd = trailingCmd(args);
|
|
6275
|
+
const g = cmd.optsWithGlobals();
|
|
6276
|
+
await daemonRestart({
|
|
6277
|
+
...toCliOptions(g),
|
|
6278
|
+
...g.detach === true ? { detach: true } : {}
|
|
6279
|
+
});
|
|
5706
6280
|
});
|
|
5707
6281
|
daemonGrp.action(() => {
|
|
5708
|
-
throw new NoirCliError(EXIT.USAGE, "Usage: noir daemon start|stop|status|restart");
|
|
6282
|
+
throw new NoirCliError(EXIT.USAGE, "Usage: noir daemon start|join|stop|status|restart");
|
|
6283
|
+
});
|
|
6284
|
+
const workspaceGrp = program2.command("workspace").description("shared cross-repo workspaces");
|
|
6285
|
+
workspaceGrp.command("list").description("list workspaces on this machine").action(async (...args) => {
|
|
6286
|
+
await workspaceList(toCliOptions(trailingCmd(args).optsWithGlobals()));
|
|
6287
|
+
});
|
|
6288
|
+
workspaceGrp.command("status").description("members + daemon liveness for a workspace").argument("[name]", "workspace name (defaults to this repo's joined workspace)").action(async (...args) => {
|
|
6289
|
+
const cmd = trailingCmd(args);
|
|
6290
|
+
const name = typeof args[0] === "string" && args[0].length > 0 ? args[0] : void 0;
|
|
6291
|
+
await workspaceStatus({
|
|
6292
|
+
...toCliOptions(cmd.optsWithGlobals()),
|
|
6293
|
+
...name !== void 0 ? { name } : {}
|
|
6294
|
+
});
|
|
6295
|
+
});
|
|
6296
|
+
workspaceGrp.command("leave").description("leave the workspace this repo joined (restore stdio)").option("--force", "overwrite a non-Noir .mcp.json entry").action(async (...args) => {
|
|
6297
|
+
const cmd = trailingCmd(args);
|
|
6298
|
+
const g = cmd.optsWithGlobals();
|
|
6299
|
+
await workspaceLeave({
|
|
6300
|
+
...toCliOptions(g),
|
|
6301
|
+
...g.force === true ? { force: true } : {}
|
|
6302
|
+
});
|
|
6303
|
+
});
|
|
6304
|
+
workspaceGrp.command("stop").description("stop a workspace daemon (membership retained)").argument("[name]", "workspace name (defaults to this repo's joined workspace)").action(async (...args) => {
|
|
6305
|
+
const cmd = trailingCmd(args);
|
|
6306
|
+
const name = typeof args[0] === "string" && args[0].length > 0 ? args[0] : void 0;
|
|
6307
|
+
await workspaceStop({
|
|
6308
|
+
...toCliOptions(cmd.optsWithGlobals()),
|
|
6309
|
+
...name !== void 0 ? { name } : {}
|
|
6310
|
+
});
|
|
6311
|
+
});
|
|
6312
|
+
workspaceGrp.action(() => {
|
|
6313
|
+
throw new NoirCliError(EXIT.USAGE, "Usage: noir workspace list|status|leave|stop");
|
|
5709
6314
|
});
|
|
5710
6315
|
program2.command("doctor").description("environment + project health").option(
|
|
5711
6316
|
"--dedup",
|
|
@@ -5801,10 +6406,23 @@ function createProgram() {
|
|
|
5801
6406
|
...limit === void 0 ? {} : { limit }
|
|
5802
6407
|
});
|
|
5803
6408
|
});
|
|
6409
|
+
memoryGrp.command("capture").description("distill a transcript/notes file (or stdin) into a memory (manual)").argument("[file]", "transcript/notes file (or pipe stdin)").option("--content <text>", "inline distilled content").option("--event-type <type>", "capture hook label (defaults to Stop)").action(async (...args) => {
|
|
6410
|
+
const cmd = trailingCmd(args);
|
|
6411
|
+
const g = cmd.optsWithGlobals();
|
|
6412
|
+
const file = typeof args[0] === "string" && args[0].length > 0 ? args[0] : void 0;
|
|
6413
|
+
const content = typeof g.content === "string" ? g.content : void 0;
|
|
6414
|
+
const eventType = typeof g.eventType === "string" ? g.eventType : void 0;
|
|
6415
|
+
await memoryCapture({
|
|
6416
|
+
...toCliOptions(g),
|
|
6417
|
+
...file === void 0 ? {} : { file },
|
|
6418
|
+
...content === void 0 ? {} : { content },
|
|
6419
|
+
...eventType === void 0 ? {} : { eventType }
|
|
6420
|
+
});
|
|
6421
|
+
});
|
|
5804
6422
|
memoryGrp.action(() => {
|
|
5805
6423
|
throw new NoirCliError(
|
|
5806
6424
|
EXIT.USAGE,
|
|
5807
|
-
"Usage: noir memory recall|save|sessions|forget|consolidate"
|
|
6425
|
+
"Usage: noir memory recall|save|capture|sessions|forget|consolidate"
|
|
5808
6426
|
);
|
|
5809
6427
|
});
|
|
5810
6428
|
const skillsGrp = program2.command("skills").description("builtin skills");
|
|
@@ -6097,6 +6715,7 @@ var init_bin = __esm({
|
|
|
6097
6715
|
init_skills();
|
|
6098
6716
|
init_status();
|
|
6099
6717
|
init_task();
|
|
6718
|
+
init_workspace();
|
|
6100
6719
|
init_init();
|
|
6101
6720
|
init_output();
|
|
6102
6721
|
init_run_profiles();
|