memnox 0.1.1 → 0.2.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/index.js +405 -285
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -113,8 +113,60 @@ function explain(err) {
|
|
|
113
113
|
// src/program.ts
|
|
114
114
|
import { Command } from "commander";
|
|
115
115
|
|
|
116
|
+
// src/banner.ts
|
|
117
|
+
var LETTERS = [
|
|
118
|
+
["## ##", "### ###", "## # ##", "## ##", "## ##"],
|
|
119
|
+
["######", "##", "#####", "##", "######"],
|
|
120
|
+
["## ##", "### ###", "## # ##", "## ##", "## ##"],
|
|
121
|
+
["## ##", "### ##", "## # ##", "## ###", "## ##"],
|
|
122
|
+
[" #####", "## ##", "## ##", "## ##", " #####"],
|
|
123
|
+
["## ##", " ## ##", " ###", " ## ##", "## ##"]
|
|
124
|
+
];
|
|
125
|
+
var ROWS = 5;
|
|
126
|
+
var TRACKING = 1;
|
|
127
|
+
var FACE = "\u2588";
|
|
128
|
+
var SHADOW = "\u2584";
|
|
129
|
+
var DRAWN = "#";
|
|
130
|
+
function faceGrid() {
|
|
131
|
+
const grid = Array.from({ length: ROWS }, () => []);
|
|
132
|
+
for (const letter of LETTERS) {
|
|
133
|
+
const width = Math.max(...letter.map((row3) => row3.length));
|
|
134
|
+
for (let row3 = 0; row3 < ROWS; row3 += 1) {
|
|
135
|
+
const art = letter[row3] ?? "";
|
|
136
|
+
for (let column = 0; column < width + TRACKING; column += 1) {
|
|
137
|
+
grid[row3].push(art[column] === DRAWN);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return grid;
|
|
142
|
+
}
|
|
143
|
+
var at = (grid, row3, column) => row3 >= 0 && column >= 0 && (grid[row3]?.[column] ?? false);
|
|
144
|
+
var shadowed = (grid, row3, column) => row3 === ROWS && at(grid, ROWS - 1, column - 1);
|
|
145
|
+
function wordmark(style, plain) {
|
|
146
|
+
if (!style.decorated) return [plain];
|
|
147
|
+
const grid = faceGrid();
|
|
148
|
+
const width = grid[0]?.length ?? 0;
|
|
149
|
+
const lines = [];
|
|
150
|
+
for (let row3 = 0; row3 <= ROWS; row3 += 1) {
|
|
151
|
+
let line2 = "";
|
|
152
|
+
for (let column = 0; column < width; column += 1) {
|
|
153
|
+
if (at(grid, row3, column)) line2 += style.bold(FACE);
|
|
154
|
+
else if (shadowed(grid, row3, column)) line2 += style.dim(SHADOW);
|
|
155
|
+
else line2 += " ";
|
|
156
|
+
}
|
|
157
|
+
lines.push(line2);
|
|
158
|
+
}
|
|
159
|
+
return lines;
|
|
160
|
+
}
|
|
161
|
+
function masthead(out, style) {
|
|
162
|
+
if (!style.decorated) return;
|
|
163
|
+
out.note("");
|
|
164
|
+
for (const line2 of wordmark(style, "memnox")) out.note(line2);
|
|
165
|
+
out.note("");
|
|
166
|
+
}
|
|
167
|
+
|
|
116
168
|
// src/defaults.ts
|
|
117
|
-
var CLI_VERSION = "0.
|
|
169
|
+
var CLI_VERSION = "0.2.1";
|
|
118
170
|
|
|
119
171
|
// src/commands/scan.command.ts
|
|
120
172
|
import { homedir as homedir3 } from "os";
|
|
@@ -210,6 +262,8 @@ function reaches(policy, action) {
|
|
|
210
262
|
// src/scan/machine-report.ts
|
|
211
263
|
import {
|
|
212
264
|
AGENT_APPROVAL,
|
|
265
|
+
agentRefOf,
|
|
266
|
+
agentsReachingPath,
|
|
213
267
|
approvalOf,
|
|
214
268
|
describeBrowser,
|
|
215
269
|
describeCombined,
|
|
@@ -313,7 +367,7 @@ function renderMachine(context, report3, counts) {
|
|
|
313
367
|
out.line(`${surfaces.length} execution surfaces.`);
|
|
314
368
|
if (counts.approvedAgents.length === 0 && report3.agents.length > 0) {
|
|
315
369
|
out.note(
|
|
316
|
-
|
|
370
|
+
`No agent has been approved or refused here. Decide with "memnox config set approvedAgents ${report3.agents.map((agent) => agent.kind).join(",")}".`
|
|
317
371
|
);
|
|
318
372
|
}
|
|
319
373
|
out.line("");
|
|
@@ -342,7 +396,11 @@ function renderMachine(context, report3, counts) {
|
|
|
342
396
|
function renderCredentials(context, report3) {
|
|
343
397
|
const { out, style } = context;
|
|
344
398
|
if (report3.credentials.length === 0) return;
|
|
345
|
-
const
|
|
399
|
+
const refs = report3.agents.map(agentRefOf);
|
|
400
|
+
const reach2 = (path) => {
|
|
401
|
+
const count = agentsReachingPath(path, refs, report3.surfaces).length;
|
|
402
|
+
return `${count} agent${count === 1 ? "" : "s"}`;
|
|
403
|
+
};
|
|
346
404
|
out.line("");
|
|
347
405
|
out.line(style.bold("CREDENTIALS THESE AGENTS CAN READ"));
|
|
348
406
|
out.line("");
|
|
@@ -351,17 +409,16 @@ function renderCredentials(context, report3) {
|
|
|
351
409
|
...report3.envFiles.map((each) => each.path.length)
|
|
352
410
|
) + PATH_GUTTER;
|
|
353
411
|
for (const credential of report3.credentials) {
|
|
354
|
-
|
|
355
|
-
|
|
412
|
+
out.line(
|
|
413
|
+
` ${style.warn("!")} ${credential.path.padEnd(width)}${reach2(credential.path)}`
|
|
414
|
+
);
|
|
356
415
|
if (credential.detail !== void 0) {
|
|
357
416
|
out.line(` ${style.dim(credential.detail)}`);
|
|
358
417
|
}
|
|
359
418
|
}
|
|
360
419
|
for (const env of report3.envFiles) {
|
|
361
420
|
const keys = env.keyLike === 0 ? "" : env.keyLike === 1 ? ", 1 looks like a credential" : `, ${env.keyLike} look like credentials`;
|
|
362
|
-
out.line(
|
|
363
|
-
` ${style.warn("!")} ${env.path.padEnd(width)}${agents} agent${agents === 1 ? "" : "s"}`
|
|
364
|
-
);
|
|
421
|
+
out.line(` ${style.warn("!")} ${env.path.padEnd(width)}${reach2(env.path)}`);
|
|
365
422
|
out.line(` ${style.dim(`${env.variables} variables${keys}`)}`);
|
|
366
423
|
}
|
|
367
424
|
}
|
|
@@ -878,22 +935,22 @@ function watchConfigPaths(paths, seams = {}) {
|
|
|
878
935
|
async next(timeoutMs) {
|
|
879
936
|
if (pending) {
|
|
880
937
|
pending = false;
|
|
881
|
-
await new Promise((
|
|
938
|
+
await new Promise((resolve6) => setTimer(resolve6, SETTLE_MS));
|
|
882
939
|
return true;
|
|
883
940
|
}
|
|
884
|
-
const changed = await new Promise((
|
|
941
|
+
const changed = await new Promise((resolve6) => {
|
|
885
942
|
const timer = setTimer(() => {
|
|
886
943
|
wake = null;
|
|
887
|
-
|
|
944
|
+
resolve6(false);
|
|
888
945
|
}, timeoutMs);
|
|
889
946
|
wake = () => {
|
|
890
947
|
clearTimer(timer);
|
|
891
|
-
|
|
948
|
+
resolve6(true);
|
|
892
949
|
};
|
|
893
950
|
});
|
|
894
951
|
if (changed) {
|
|
895
952
|
pending = false;
|
|
896
|
-
await new Promise((
|
|
953
|
+
await new Promise((resolve6) => setTimer(resolve6, SETTLE_MS));
|
|
897
954
|
}
|
|
898
955
|
return changed;
|
|
899
956
|
},
|
|
@@ -1163,31 +1220,79 @@ async function resolve(milestones, id) {
|
|
|
1163
1220
|
}
|
|
1164
1221
|
|
|
1165
1222
|
// src/commands/check.command.ts
|
|
1166
|
-
import { existsSync as existsSync2 } from "fs";
|
|
1167
1223
|
import { homedir as homedir5 } from "os";
|
|
1168
|
-
import { join as join3 } from "path";
|
|
1169
1224
|
import {
|
|
1170
1225
|
DECISION_EFFECT as DECISION_EFFECT2,
|
|
1171
1226
|
INTENT_KIND,
|
|
1172
1227
|
LocalGate,
|
|
1173
|
-
loadPolicySet as loadPolicySet3,
|
|
1174
|
-
MEMNOX_HOME as MEMNOX_HOME2,
|
|
1175
1228
|
preflightFor,
|
|
1176
1229
|
overlaysInForce,
|
|
1177
|
-
readPolicyRegistry as readPolicyRegistry3,
|
|
1178
1230
|
stateFactsInForce
|
|
1179
1231
|
} from "@memnox/core";
|
|
1180
1232
|
|
|
1181
1233
|
// src/policy-path.ts
|
|
1182
1234
|
import { existsSync } from "fs";
|
|
1235
|
+
import { resolve as resolve3 } from "path";
|
|
1236
|
+
import { loadPolicySet as loadPolicySet3, readPolicyRegistry as readPolicyRegistry4 } from "@memnox/core";
|
|
1237
|
+
|
|
1238
|
+
// src/policy-registry.ts
|
|
1239
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
1240
|
+
import { dirname as dirname2, join as join3, resolve as resolve2 } from "path";
|
|
1241
|
+
import { readPolicyRegistry as readPolicyRegistry3 } from "@memnox/core";
|
|
1242
|
+
var CONFIG_DIR2 = ".memnox";
|
|
1243
|
+
var REGISTRY_FILE3 = "policies.json";
|
|
1244
|
+
var DIR_MODE = 448;
|
|
1245
|
+
function policyRegistryPath(homeDir) {
|
|
1246
|
+
return join3(homeDir, CONFIG_DIR2, REGISTRY_FILE3);
|
|
1247
|
+
}
|
|
1248
|
+
async function registerPolicyFile(homeDir, filePath) {
|
|
1249
|
+
const absolute = resolve2(filePath);
|
|
1250
|
+
const existing = await readPolicyRegistry3(policyRegistryPath(homeDir));
|
|
1251
|
+
if (existing.includes(absolute)) return existing;
|
|
1252
|
+
const files = [...existing, absolute];
|
|
1253
|
+
const path = policyRegistryPath(homeDir);
|
|
1254
|
+
await mkdir(dirname2(path), { recursive: true, mode: DIR_MODE });
|
|
1255
|
+
await writeFile(path, `${JSON.stringify({ files }, null, 2)}
|
|
1256
|
+
`, "utf8");
|
|
1257
|
+
return files;
|
|
1258
|
+
}
|
|
1259
|
+
async function forgetPolicyFiles(homeDir, drop) {
|
|
1260
|
+
const gone = new Set(drop);
|
|
1261
|
+
const path = policyRegistryPath(homeDir);
|
|
1262
|
+
const files = (await readPolicyRegistry3(path)).filter((file) => !gone.has(file));
|
|
1263
|
+
await mkdir(dirname2(path), { recursive: true, mode: DIR_MODE });
|
|
1264
|
+
await writeFile(path, `${JSON.stringify({ files }, null, 2)}
|
|
1265
|
+
`, "utf8");
|
|
1266
|
+
return files;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
// src/policy-path.ts
|
|
1183
1270
|
var POLICY_FILES = ["memnox.policies.toml", "memnox.policies.yaml"];
|
|
1184
1271
|
function resolvePolicyFile(explicit, exists2 = existsSync) {
|
|
1185
1272
|
if (explicit !== void 0) return explicit;
|
|
1186
1273
|
return POLICY_FILES.find((candidate) => exists2(candidate)) ?? POLICY_FILES[0];
|
|
1187
1274
|
}
|
|
1275
|
+
async function policyFilesInForce(homeDir, explicit, exists2 = existsSync) {
|
|
1276
|
+
const here = resolvePolicyFile(explicit, exists2);
|
|
1277
|
+
if (explicit !== void 0) return [here];
|
|
1278
|
+
const files = new Set(await readPolicyRegistry4(policyRegistryPath(homeDir)));
|
|
1279
|
+
if (exists2(here)) files.add(resolve3(here));
|
|
1280
|
+
return [...files];
|
|
1281
|
+
}
|
|
1282
|
+
async function policySetInForce(homeDir, explicit) {
|
|
1283
|
+
return loadPolicySet3(await policyFilesInForce(homeDir, explicit));
|
|
1284
|
+
}
|
|
1285
|
+
function sayWhatDidNotLoad(context, set) {
|
|
1286
|
+
for (const broken of set.unreadable) {
|
|
1287
|
+
const count = broken.issues.length;
|
|
1288
|
+
context.out.note(
|
|
1289
|
+
`${broken.file} would not load \u2014 ${count} problem${count === 1 ? "" : "s"}, so its rules are not in force.`
|
|
1290
|
+
);
|
|
1291
|
+
context.out.note(` fix them with "memnox policy check --fix ${broken.file}"`);
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1188
1294
|
|
|
1189
1295
|
// src/commands/check.command.ts
|
|
1190
|
-
var REGISTRY_FILE3 = "policies.json";
|
|
1191
1296
|
function registerCheckCommand(program, context, now = () => (/* @__PURE__ */ new Date()).toISOString()) {
|
|
1192
1297
|
program.command("check <intent>").description(
|
|
1193
1298
|
"Decide before the loop starts: what this would do, and what would stop it"
|
|
@@ -1203,7 +1308,8 @@ function registerCheckCommand(program, context, now = () => (/* @__PURE__ */ new
|
|
|
1203
1308
|
out.line("Nothing on this machine does that, so there is nothing to decide.");
|
|
1204
1309
|
return;
|
|
1205
1310
|
}
|
|
1206
|
-
const gate = await buildGate(options.agent, now());
|
|
1311
|
+
const { gate, rules: rules2 } = await buildGate(options.agent, now());
|
|
1312
|
+
sayWhatDidNotLoad(context, rules2);
|
|
1207
1313
|
const rulings = preflight.actions.map((action) => ({
|
|
1208
1314
|
action: action.action,
|
|
1209
1315
|
verdict: gate.evaluate({
|
|
@@ -1219,16 +1325,15 @@ function registerCheckCommand(program, context, now = () => (/* @__PURE__ */ new
|
|
|
1219
1325
|
}
|
|
1220
1326
|
async function buildGate(agent, moment) {
|
|
1221
1327
|
const home = homedir5();
|
|
1222
|
-
const
|
|
1223
|
-
const here = resolvePolicyFile();
|
|
1224
|
-
const files = new Set(registered);
|
|
1225
|
-
if (existsSync2(here)) files.add(here);
|
|
1226
|
-
const set = await loadPolicySet3([...files]);
|
|
1328
|
+
const rules2 = await policySetInForce(home);
|
|
1227
1329
|
const overlays = await overlaysInForce(home);
|
|
1228
|
-
return
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1330
|
+
return {
|
|
1331
|
+
rules: rules2,
|
|
1332
|
+
gate: new LocalGate(rules2.policies, {
|
|
1333
|
+
agentName: agent,
|
|
1334
|
+
stateFacts: stateFactsInForce(overlays, moment)
|
|
1335
|
+
})
|
|
1336
|
+
};
|
|
1232
1337
|
}
|
|
1233
1338
|
var ORDER = {
|
|
1234
1339
|
[DECISION_EFFECT2.DENY]: 0,
|
|
@@ -1249,16 +1354,35 @@ function render2(context, preflight, rulings) {
|
|
|
1249
1354
|
const shown = [...rulings].sort(
|
|
1250
1355
|
(a, b) => (ORDER[a.verdict.effect] ?? 3) - (ORDER[b.verdict.effect] ?? 3)
|
|
1251
1356
|
);
|
|
1357
|
+
const reasons = new Set(rulings.map((ruling) => ruling.verdict.reason));
|
|
1358
|
+
const shared = reasons.size === 1 ? [...reasons][0] : void 0;
|
|
1252
1359
|
for (const ruling of shown) {
|
|
1253
1360
|
const effect = ruling.verdict.effect;
|
|
1254
1361
|
const label = effect === DECISION_EFFECT2.DENY ? style.warn("deny ") : effect === DECISION_EFFECT2.ASK ? style.warn("ask ") : style.dim("allow");
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1362
|
+
const reason = shared === void 0 ? style.dim(ruling.verdict.reason) : "";
|
|
1363
|
+
out.line(` ${label} ${ruling.action.padEnd(width)}${reason}`.trimEnd());
|
|
1364
|
+
}
|
|
1365
|
+
if (shared !== void 0) {
|
|
1366
|
+
out.line("");
|
|
1367
|
+
out.line(` ${style.dim(shared)}`);
|
|
1258
1368
|
}
|
|
1259
1369
|
out.line("");
|
|
1260
1370
|
if (stopped.length === 0) {
|
|
1261
|
-
|
|
1371
|
+
const unruled = rulings.filter(
|
|
1372
|
+
(ruling) => ruling.verdict.matchedPolicies.length === 0
|
|
1373
|
+
).length;
|
|
1374
|
+
if (unruled === rulings.length) {
|
|
1375
|
+
out.line(
|
|
1376
|
+
`Nothing here would stop, and no rule covers any of it. ${rulings.length} action(s) checked.`
|
|
1377
|
+
);
|
|
1378
|
+
out.line(
|
|
1379
|
+
` ${style.dim("memnox protect")} put the dangerous ones behind ask or deny`
|
|
1380
|
+
);
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
out.line(
|
|
1384
|
+
unruled === 0 ? `Nothing here would stop. ${rulings.length} action(s) checked.` : `Nothing here would stop. ${rulings.length} action(s) checked, ${unruled} of them covered by no rule.`
|
|
1385
|
+
);
|
|
1262
1386
|
return;
|
|
1263
1387
|
}
|
|
1264
1388
|
out.line(
|
|
@@ -1278,22 +1402,22 @@ import { homedir as homedir6 } from "os";
|
|
|
1278
1402
|
|
|
1279
1403
|
// src/memnox-paths.ts
|
|
1280
1404
|
import { join as join4 } from "path";
|
|
1281
|
-
import { MEMNOX_HOME as
|
|
1405
|
+
import { MEMNOX_HOME as MEMNOX_HOME2 } from "@memnox/core";
|
|
1282
1406
|
var GUARD_DIR = "guard";
|
|
1283
1407
|
var GUARD_PROFILE = "memnox.sb";
|
|
1284
1408
|
var TRANSCRIPT_DIR = "transcripts";
|
|
1285
1409
|
var BACKUP_DIR = "backup";
|
|
1286
1410
|
function guardProfilePath(home) {
|
|
1287
|
-
return join4(home,
|
|
1411
|
+
return join4(home, MEMNOX_HOME2, GUARD_DIR, GUARD_PROFILE);
|
|
1288
1412
|
}
|
|
1289
1413
|
function landlockRulesetPath(home) {
|
|
1290
|
-
return join4(home,
|
|
1414
|
+
return join4(home, MEMNOX_HOME2, GUARD_DIR, "landlock.json");
|
|
1291
1415
|
}
|
|
1292
1416
|
function transcriptPathFor(home, sessionId) {
|
|
1293
|
-
return join4(home,
|
|
1417
|
+
return join4(home, MEMNOX_HOME2, TRANSCRIPT_DIR, `${sessionId}.log`);
|
|
1294
1418
|
}
|
|
1295
1419
|
function backupPathFor(home, path) {
|
|
1296
|
-
return join4(home,
|
|
1420
|
+
return join4(home, MEMNOX_HOME2, BACKUP_DIR, path.replace(/[/\\ ]/g, "_"));
|
|
1297
1421
|
}
|
|
1298
1422
|
|
|
1299
1423
|
// src/commands/trace.command.ts
|
|
@@ -1371,9 +1495,9 @@ async function renderStreams(context, event, home) {
|
|
|
1371
1495
|
|
|
1372
1496
|
// src/commands/explain.command.ts
|
|
1373
1497
|
import { cwd } from "process";
|
|
1374
|
-
import { existsSync as
|
|
1498
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1375
1499
|
import { homedir as homedir7 } from "os";
|
|
1376
|
-
import { join as
|
|
1500
|
+
import { join as join7 } from "path";
|
|
1377
1501
|
import {
|
|
1378
1502
|
actionForVerb,
|
|
1379
1503
|
hasTag,
|
|
@@ -1393,9 +1517,9 @@ import {
|
|
|
1393
1517
|
} from "@memnox/core";
|
|
1394
1518
|
|
|
1395
1519
|
// src/health-probe.ts
|
|
1396
|
-
import { existsSync as
|
|
1520
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1397
1521
|
import { readdir, readFile as readFile2, stat } from "fs/promises";
|
|
1398
|
-
import { delimiter, join as
|
|
1522
|
+
import { delimiter, join as join5 } from "path";
|
|
1399
1523
|
import {
|
|
1400
1524
|
CONFIG_FORMAT,
|
|
1401
1525
|
configPathFor,
|
|
@@ -1422,39 +1546,6 @@ import {
|
|
|
1422
1546
|
realPath,
|
|
1423
1547
|
resolveReal
|
|
1424
1548
|
} from "@memnox/interceptors";
|
|
1425
|
-
|
|
1426
|
-
// src/policy-registry.ts
|
|
1427
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
1428
|
-
import { dirname as dirname2, join as join5, resolve as resolve2 } from "path";
|
|
1429
|
-
import { readPolicyRegistry as readPolicyRegistry4 } from "@memnox/core";
|
|
1430
|
-
var CONFIG_DIR2 = ".memnox";
|
|
1431
|
-
var REGISTRY_FILE4 = "policies.json";
|
|
1432
|
-
var DIR_MODE = 448;
|
|
1433
|
-
function policyRegistryPath(homeDir) {
|
|
1434
|
-
return join5(homeDir, CONFIG_DIR2, REGISTRY_FILE4);
|
|
1435
|
-
}
|
|
1436
|
-
async function registerPolicyFile(homeDir, filePath) {
|
|
1437
|
-
const absolute = resolve2(filePath);
|
|
1438
|
-
const existing = await readPolicyRegistry4(policyRegistryPath(homeDir));
|
|
1439
|
-
if (existing.includes(absolute)) return existing;
|
|
1440
|
-
const files = [...existing, absolute];
|
|
1441
|
-
const path = policyRegistryPath(homeDir);
|
|
1442
|
-
await mkdir(dirname2(path), { recursive: true, mode: DIR_MODE });
|
|
1443
|
-
await writeFile(path, `${JSON.stringify({ files }, null, 2)}
|
|
1444
|
-
`, "utf8");
|
|
1445
|
-
return files;
|
|
1446
|
-
}
|
|
1447
|
-
async function forgetPolicyFiles(homeDir, drop) {
|
|
1448
|
-
const gone = new Set(drop);
|
|
1449
|
-
const path = policyRegistryPath(homeDir);
|
|
1450
|
-
const files = (await readPolicyRegistry4(path)).filter((file) => !gone.has(file));
|
|
1451
|
-
await mkdir(dirname2(path), { recursive: true, mode: DIR_MODE });
|
|
1452
|
-
await writeFile(path, `${JSON.stringify({ files }, null, 2)}
|
|
1453
|
-
`, "utf8");
|
|
1454
|
-
return files;
|
|
1455
|
-
}
|
|
1456
|
-
|
|
1457
|
-
// src/health-probe.ts
|
|
1458
1549
|
var RULE_FILES = POLICY_FILES;
|
|
1459
1550
|
async function exists(path) {
|
|
1460
1551
|
try {
|
|
@@ -1466,7 +1557,7 @@ async function exists(path) {
|
|
|
1466
1557
|
}
|
|
1467
1558
|
async function rules(dir) {
|
|
1468
1559
|
for (const name of RULE_FILES) {
|
|
1469
|
-
const path =
|
|
1560
|
+
const path = join5(dir, name);
|
|
1470
1561
|
if (!await exists(path)) continue;
|
|
1471
1562
|
try {
|
|
1472
1563
|
const policies = await loadPoliciesFromFile(path);
|
|
@@ -1490,7 +1581,7 @@ async function proxyWiring(home, dir) {
|
|
|
1490
1581
|
let servers = 0;
|
|
1491
1582
|
let wrapped = 0;
|
|
1492
1583
|
for (const location of MCP_CONFIG_LOCATIONS) {
|
|
1493
|
-
const path =
|
|
1584
|
+
const path = join5(location.scope === "home" ? home : dir, location.relative);
|
|
1494
1585
|
if (!await exists(path)) continue;
|
|
1495
1586
|
let raw;
|
|
1496
1587
|
try {
|
|
@@ -1587,13 +1678,13 @@ async function readLedger(home) {
|
|
|
1587
1678
|
function presentBinaries(home, env) {
|
|
1588
1679
|
const path = realPath(env["PATH"] ?? "", home);
|
|
1589
1680
|
return interceptedBinaries().filter(
|
|
1590
|
-
(binary) => resolveReal(binary, path,
|
|
1681
|
+
(binary) => resolveReal(binary, path, existsSync2) !== null
|
|
1591
1682
|
);
|
|
1592
1683
|
}
|
|
1593
1684
|
|
|
1594
1685
|
// src/protect/shell-profile.ts
|
|
1595
1686
|
import { readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
1596
|
-
import { basename, join as
|
|
1687
|
+
import { basename, join as join6 } from "path";
|
|
1597
1688
|
import { interceptorDirFor as interceptorDirFor2 } from "@memnox/interceptors";
|
|
1598
1689
|
var BEGIN = "# >>> memnox >>>";
|
|
1599
1690
|
var END = "# <<< memnox <<<";
|
|
@@ -1606,7 +1697,7 @@ function profilesFor(shell, home) {
|
|
|
1606
1697
|
const name = basename(shell || "zsh");
|
|
1607
1698
|
const known = Object.entries(PROFILES).find(([key]) => name.includes(key));
|
|
1608
1699
|
const relative2 = known === void 0 ? PROFILES["zsh"] : known[1];
|
|
1609
|
-
return relative2.map((each) =>
|
|
1700
|
+
return relative2.map((each) => join6(home, each));
|
|
1610
1701
|
}
|
|
1611
1702
|
function pathLineFor(shell, home) {
|
|
1612
1703
|
const directory = interceptorDirFor2(home);
|
|
@@ -1681,7 +1772,7 @@ function renderTrace(context, trace) {
|
|
|
1681
1772
|
);
|
|
1682
1773
|
out.line("");
|
|
1683
1774
|
}
|
|
1684
|
-
async function answerQuestion(question, inventory,
|
|
1775
|
+
async function answerQuestion(question, inventory, rules2, here) {
|
|
1685
1776
|
const agent = inventory.agents.find(
|
|
1686
1777
|
(each) => each.kind.toLowerCase().includes(question.agent.toLowerCase())
|
|
1687
1778
|
);
|
|
@@ -1703,14 +1794,14 @@ async function answerQuestion(question, inventory, policyFile) {
|
|
|
1703
1794
|
const technically = matchingPath.length > 0 ? `yes \u2014 ${matchingPath[0]?.path} is reachable by ${agent.kind}` : reachesShell ? `yes \u2014 ${agent.kind} holds a shell, which reaches anything you can` : tools.length > 0 ? `unclear \u2014 ${tools.length} tool(s) reachable, none named for ${question.resource}` : `no \u2014 nothing ${agent.kind} holds here reaches ${question.resource}`;
|
|
1704
1795
|
const runtime = reachesShell ? "a shell is present, so the runtime restricts nothing by itself" : `${tools.length} tool(s) across ${inventory.mcpServers.length} server(s)`;
|
|
1705
1796
|
const action = actionForVerb(question.verb);
|
|
1706
|
-
if (
|
|
1797
|
+
if (rules2.policies.length === 0) {
|
|
1707
1798
|
return {
|
|
1708
1799
|
technically,
|
|
1709
1800
|
runtime,
|
|
1710
|
-
policy: `no rules at ${
|
|
1801
|
+
policy: `no rules at ${here} \u2014 nothing here would stop it`
|
|
1711
1802
|
};
|
|
1712
1803
|
}
|
|
1713
|
-
const gate =
|
|
1804
|
+
const gate = new LocalGate2(rules2.policies, { agentName: agent.kind });
|
|
1714
1805
|
const verdict = gate.evaluate({ action, target: question.resource });
|
|
1715
1806
|
const rule = verdict.matchedPolicies[0];
|
|
1716
1807
|
const named = rule === void 0 ? "no rule matched" : `rule ${rule.name}`;
|
|
@@ -1760,11 +1851,14 @@ function registerExplainCommand(program, context, buildSeams = defaultScanSeams)
|
|
|
1760
1851
|
if (subject.trim().includes(" ")) {
|
|
1761
1852
|
const { question, error } = parseQuestion(subject);
|
|
1762
1853
|
if (question === void 0) throw new Error(error);
|
|
1854
|
+
const rules2 = await policySetInForce(homedir7(), options.file);
|
|
1763
1855
|
const answer = await answerQuestion(
|
|
1764
1856
|
question,
|
|
1765
1857
|
inventoryOf2(report3, snapshot.takenAt),
|
|
1858
|
+
rules2,
|
|
1766
1859
|
resolvePolicyFile(options.file)
|
|
1767
1860
|
);
|
|
1861
|
+
sayWhatDidNotLoad(context, rules2);
|
|
1768
1862
|
if (options.json === true) {
|
|
1769
1863
|
context.out.json({ question, ...answer });
|
|
1770
1864
|
return;
|
|
@@ -1968,8 +2062,8 @@ async function coverageFacts(dir) {
|
|
|
1968
2062
|
return {
|
|
1969
2063
|
interceptorsInstalled: health.interceptorsInstalled.length > 0,
|
|
1970
2064
|
interceptorsFirstOnPath: health.interceptorDirFirstOnPath,
|
|
1971
|
-
gitHooksInstalled:
|
|
1972
|
-
osGuardWritten:
|
|
2065
|
+
gitHooksInstalled: existsSync3(join7(dir, ".git", "hooks", "pre-push")),
|
|
2066
|
+
osGuardWritten: existsSync3(guardProfilePath(homedir7())),
|
|
1973
2067
|
egressProxySet: PROXY_VARS.some((name) => (process.env[name] ?? "") !== ""),
|
|
1974
2068
|
loginPathConfigured: await loginPathConfigured(
|
|
1975
2069
|
process.env["SHELL"] ?? "zsh",
|
|
@@ -2086,14 +2180,14 @@ function registerConfigCommand(program, context, home = homedir8) {
|
|
|
2086
2180
|
|
|
2087
2181
|
// src/commands/doctor.command.ts
|
|
2088
2182
|
import { homedir as homedir9 } from "os";
|
|
2089
|
-
import { join as
|
|
2183
|
+
import { join as join8 } from "path";
|
|
2090
2184
|
import {
|
|
2091
2185
|
chainsFor as chainsFor2,
|
|
2092
2186
|
CHECK,
|
|
2093
2187
|
checkInstallation,
|
|
2094
2188
|
discover as discover2,
|
|
2095
2189
|
lastProbed,
|
|
2096
|
-
MEMNOX_HOME as
|
|
2190
|
+
MEMNOX_HOME as MEMNOX_HOME3,
|
|
2097
2191
|
NodeMachineReader as NodeMachineReader2,
|
|
2098
2192
|
NodeSnapshotStore as NodeSnapshotStore2,
|
|
2099
2193
|
rankAgents,
|
|
@@ -2102,7 +2196,7 @@ import {
|
|
|
2102
2196
|
withToolsFrom
|
|
2103
2197
|
} from "@memnox/core";
|
|
2104
2198
|
var SEVERITY_WIDTH = 10;
|
|
2105
|
-
function registerDoctorCommand(program, context, buildReader = () => new NodeMachineReader2(homedir9()), cwd3 = () => process.cwd(), buildSnapshots = () => new NodeSnapshotStore2(
|
|
2199
|
+
function registerDoctorCommand(program, context, buildReader = () => new NodeMachineReader2(homedir9()), cwd3 = () => process.cwd(), buildSnapshots = () => new NodeSnapshotStore2(join8(homedir9(), MEMNOX_HOME3))) {
|
|
2106
2200
|
program.command("doctor").description(
|
|
2107
2201
|
"What on this machine is risky, why, and the one change that closes each"
|
|
2108
2202
|
).option("--json", "emit the findings as JSON").option(
|
|
@@ -2158,7 +2252,9 @@ function registerDoctorCommand(program, context, buildReader = () => new NodeMac
|
|
|
2158
2252
|
out.line("");
|
|
2159
2253
|
for (const finding of report3.findings) {
|
|
2160
2254
|
out.line(` ${severity(style, finding).padEnd(SEVERITY_WIDTH)}${finding.title}`);
|
|
2161
|
-
|
|
2255
|
+
if (!finding.title.includes(finding.evidence)) {
|
|
2256
|
+
out.line(` ${" ".repeat(SEVERITY_WIDTH)}${style.dim(finding.evidence)}`);
|
|
2257
|
+
}
|
|
2162
2258
|
const remediation = finding.remediation;
|
|
2163
2259
|
if (remediation !== void 0) {
|
|
2164
2260
|
out.line(
|
|
@@ -2245,17 +2341,17 @@ import {
|
|
|
2245
2341
|
|
|
2246
2342
|
// src/protect/harden-state.ts
|
|
2247
2343
|
import { homedir as homedir10 } from "os";
|
|
2248
|
-
import { join as
|
|
2344
|
+
import { join as join9 } from "path";
|
|
2249
2345
|
import {
|
|
2250
2346
|
HARDEN_TARGET,
|
|
2251
|
-
MEMNOX_HOME as
|
|
2347
|
+
MEMNOX_HOME as MEMNOX_HOME4,
|
|
2252
2348
|
NodeHardenWriter,
|
|
2253
2349
|
NodeMachineReader as NodeMachineReader3,
|
|
2254
2350
|
NodeSnapshotStore as NodeSnapshotStore3
|
|
2255
2351
|
} from "@memnox/core";
|
|
2256
2352
|
function defaultSeams() {
|
|
2257
2353
|
const home = homedir10();
|
|
2258
|
-
const root =
|
|
2354
|
+
const root = join9(home, MEMNOX_HOME4);
|
|
2259
2355
|
return {
|
|
2260
2356
|
reader: new NodeMachineReader3(home),
|
|
2261
2357
|
writer: new NodeHardenWriter(root),
|
|
@@ -2264,7 +2360,7 @@ function defaultSeams() {
|
|
|
2264
2360
|
registerPolicy: async (path) => {
|
|
2265
2361
|
await registerPolicyFile(home, path);
|
|
2266
2362
|
},
|
|
2267
|
-
absolute: (path) =>
|
|
2363
|
+
absolute: (path) => join9(root, path)
|
|
2268
2364
|
};
|
|
2269
2365
|
}
|
|
2270
2366
|
async function registerApplied(seams, applied) {
|
|
@@ -2344,10 +2440,10 @@ async function runInteractive(context, takeRecommended, ask) {
|
|
|
2344
2440
|
}
|
|
2345
2441
|
|
|
2346
2442
|
// src/protect/native-permissions.ts
|
|
2347
|
-
import { existsSync as
|
|
2443
|
+
import { existsSync as existsSync4 } from "fs";
|
|
2348
2444
|
import { readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
|
|
2349
2445
|
import { homedir as homedir12 } from "os";
|
|
2350
|
-
import { join as
|
|
2446
|
+
import { join as join10 } from "path";
|
|
2351
2447
|
import {
|
|
2352
2448
|
applyNative,
|
|
2353
2449
|
applyOpenClaw,
|
|
@@ -2365,7 +2461,7 @@ import {
|
|
|
2365
2461
|
var TARGETS = [
|
|
2366
2462
|
{
|
|
2367
2463
|
product: "Claude Code",
|
|
2368
|
-
path:
|
|
2464
|
+
path: join10(".claude", "settings.json"),
|
|
2369
2465
|
apply: (raw, policies) => {
|
|
2370
2466
|
const translation = toClaudeCodePermissions(policies);
|
|
2371
2467
|
const { allow, ask, deny } = translation.permissions;
|
|
@@ -2379,7 +2475,7 @@ var TARGETS = [
|
|
|
2379
2475
|
},
|
|
2380
2476
|
{
|
|
2381
2477
|
product: "OpenClaw",
|
|
2382
|
-
path:
|
|
2478
|
+
path: join10(".openclaw", "openclaw.json"),
|
|
2383
2479
|
apply: (raw, policies) => {
|
|
2384
2480
|
const translation = toOpenClawTools(policies);
|
|
2385
2481
|
const { allow, deny } = translation.tools;
|
|
@@ -2400,7 +2496,7 @@ var TARGETS = [
|
|
|
2400
2496
|
},
|
|
2401
2497
|
{
|
|
2402
2498
|
product: "Hermes",
|
|
2403
|
-
path:
|
|
2499
|
+
path: join10(".hermes", "config.yaml"),
|
|
2404
2500
|
text: true,
|
|
2405
2501
|
apply: (raw, policies) => {
|
|
2406
2502
|
const translation = toHermesApprovals(
|
|
@@ -2427,11 +2523,11 @@ function json(value) {
|
|
|
2427
2523
|
async function runNative(context, reverting, home = homedir12) {
|
|
2428
2524
|
const present = TARGETS.map((target) => ({
|
|
2429
2525
|
target,
|
|
2430
|
-
path:
|
|
2431
|
-
})).filter((each) =>
|
|
2526
|
+
path: join10(home(), target.path)
|
|
2527
|
+
})).filter((each) => existsSync4(each.path));
|
|
2432
2528
|
if (present.length === 0) {
|
|
2433
2529
|
throw new Error(
|
|
2434
|
-
"No agent here publishes a permission file Memnox can write. Looked for:\n" + TARGETS.map((each) => ` ${
|
|
2530
|
+
"No agent here publishes a permission file Memnox can write. Looked for:\n" + TARGETS.map((each) => ` ${join10(home(), each.path)} (${each.product})`).join(
|
|
2435
2531
|
"\n"
|
|
2436
2532
|
)
|
|
2437
2533
|
);
|
|
@@ -2475,17 +2571,17 @@ async function runNative(context, reverting, home = homedir12) {
|
|
|
2475
2571
|
}
|
|
2476
2572
|
async function rulesToWrite() {
|
|
2477
2573
|
const rules2 = resolvePolicyFile();
|
|
2478
|
-
if (!
|
|
2574
|
+
if (!existsSync4(rules2)) {
|
|
2479
2575
|
throw new Error(`No rules at ${rules2} to write. Try "memnox protect --interactive".`);
|
|
2480
2576
|
}
|
|
2481
2577
|
return loadPoliciesFromFile2(rules2);
|
|
2482
2578
|
}
|
|
2483
2579
|
|
|
2484
2580
|
// src/protect/seam-install.ts
|
|
2485
|
-
import { existsSync as
|
|
2581
|
+
import { existsSync as existsSync5, realpathSync } from "fs";
|
|
2486
2582
|
import { mkdir as mkdir2, writeFile as writeFile4 } from "fs/promises";
|
|
2487
2583
|
import { homedir as homedir13, release } from "os";
|
|
2488
|
-
import { basename as basename2, dirname as dirname3, join as
|
|
2584
|
+
import { basename as basename2, dirname as dirname3, join as join11 } from "path";
|
|
2489
2585
|
import {
|
|
2490
2586
|
guardFor,
|
|
2491
2587
|
guardPlanFrom,
|
|
@@ -2544,7 +2640,7 @@ async function runOsGuard(context, repoDir, home = homedir13()) {
|
|
|
2544
2640
|
const { out, style } = context;
|
|
2545
2641
|
const support = guardFor(process.platform, release());
|
|
2546
2642
|
const file = resolvePolicyFile();
|
|
2547
|
-
if (!
|
|
2643
|
+
if (!existsSync5(file)) {
|
|
2548
2644
|
throw new Error(`No rules at ${file}. Write some first: memnox protect --yes`);
|
|
2549
2645
|
}
|
|
2550
2646
|
const plan = guardPlanFrom(await loadPoliciesFromFile3(file), home, [repoDir]);
|
|
@@ -2618,7 +2714,7 @@ async function runPathLine(context, reverting, env = process.env) {
|
|
|
2618
2714
|
);
|
|
2619
2715
|
}
|
|
2620
2716
|
async function firstExisting(paths) {
|
|
2621
|
-
for (const path of paths) if (
|
|
2717
|
+
for (const path of paths) if (existsSync5(path)) return path;
|
|
2622
2718
|
return null;
|
|
2623
2719
|
}
|
|
2624
2720
|
function throughSymlinks(policy) {
|
|
@@ -2636,7 +2732,7 @@ function resolveThrough(path) {
|
|
|
2636
2732
|
const tail = [];
|
|
2637
2733
|
while (head !== dirname3(head)) {
|
|
2638
2734
|
try {
|
|
2639
|
-
return
|
|
2735
|
+
return join11(realpathSync(head), ...tail);
|
|
2640
2736
|
} catch {
|
|
2641
2737
|
tail.unshift(basename2(head));
|
|
2642
2738
|
head = dirname3(head);
|
|
@@ -2972,7 +3068,8 @@ function render4(context, event) {
|
|
|
2972
3068
|
);
|
|
2973
3069
|
out.line("");
|
|
2974
3070
|
row(context.out, "when", event.at);
|
|
2975
|
-
|
|
3071
|
+
const named = event.agent.toLowerCase().includes(event.actorType.toLowerCase());
|
|
3072
|
+
row(context.out, "agent", named ? event.agent : `${event.agent} (${event.actorType})`);
|
|
2976
3073
|
row(context.out, "surface", event.surface);
|
|
2977
3074
|
row(context.out, "class", event.class);
|
|
2978
3075
|
row(context.out, "reason", event.reason);
|
|
@@ -3455,7 +3552,7 @@ function registerFreezeCommand(program, context, home = homedir20, now = () => /
|
|
|
3455
3552
|
|
|
3456
3553
|
// src/commands/lock.command.ts
|
|
3457
3554
|
import { homedir as homedir21 } from "os";
|
|
3458
|
-
import { relative, resolve as
|
|
3555
|
+
import { relative, resolve as resolve4 } from "path";
|
|
3459
3556
|
import {
|
|
3460
3557
|
DEFAULT_LEASE_MINUTES,
|
|
3461
3558
|
LEASE_OUTCOME,
|
|
@@ -3520,7 +3617,7 @@ function registerLockCommand(program, context, home = homedir21, now = () => /*
|
|
|
3520
3617
|
"A lease is repository-relative, and this is not a repository."
|
|
3521
3618
|
);
|
|
3522
3619
|
}
|
|
3523
|
-
const wanted = normalizeLeasePath(relative(root,
|
|
3620
|
+
const wanted = normalizeLeasePath(relative(root, resolve4(process.cwd(), path)));
|
|
3524
3621
|
if (wanted === null) {
|
|
3525
3622
|
throw new Error(`${path} is outside this repository, so nothing can lease it.`);
|
|
3526
3623
|
}
|
|
@@ -3743,7 +3840,6 @@ function asUnit(value) {
|
|
|
3743
3840
|
|
|
3744
3841
|
// src/commands/next.command.ts
|
|
3745
3842
|
import { homedir as homedir25 } from "os";
|
|
3746
|
-
import { existsSync as existsSync7 } from "fs";
|
|
3747
3843
|
import {
|
|
3748
3844
|
delegations,
|
|
3749
3845
|
describeLevel,
|
|
@@ -3773,8 +3869,8 @@ function registerNextCommand(program, context, home = homedir25, now = () => /*
|
|
|
3773
3869
|
const ready = promotable(found);
|
|
3774
3870
|
const asked = interruptions(events, week);
|
|
3775
3871
|
const config = await loadOrCreateConfig6(home());
|
|
3776
|
-
const
|
|
3777
|
-
const gate =
|
|
3872
|
+
const rules2 = await policySetInForce(home());
|
|
3873
|
+
const gate = rules2.policies.length === 0 ? null : new LocalGate3(rules2.policies, { agentName: "agent" });
|
|
3778
3874
|
const standing = standingOf({
|
|
3779
3875
|
enforcing: config.mode === ENFORCEMENT_MODE2.ENFORCE,
|
|
3780
3876
|
rules: gate?.rules().length ?? 0,
|
|
@@ -3952,6 +4048,7 @@ function merge(accepted, wanted, at2) {
|
|
|
3952
4048
|
}
|
|
3953
4049
|
return [...by.values()];
|
|
3954
4050
|
}
|
|
4051
|
+
var NEW_SHOWN = 12;
|
|
3955
4052
|
function render9(context, findings) {
|
|
3956
4053
|
const { out, style } = context;
|
|
3957
4054
|
if (findings.length === 0) {
|
|
@@ -3973,7 +4070,18 @@ function render9(context, findings) {
|
|
|
3973
4070
|
out.line("");
|
|
3974
4071
|
out.line(style.bold("NEW"));
|
|
3975
4072
|
out.line("");
|
|
3976
|
-
|
|
4073
|
+
const ordered = [...fresh].sort((a, b) => b.reaches.length - a.reaches.length);
|
|
4074
|
+
for (const skill of ordered.slice(0, NEW_SHOWN)) {
|
|
4075
|
+
out.line(` ${style.dim("+")} ${describeSkill(skill)}`);
|
|
4076
|
+
}
|
|
4077
|
+
const rest = ordered.length - NEW_SHOWN;
|
|
4078
|
+
if (rest > 0) {
|
|
4079
|
+
const quiet = ordered.slice(NEW_SHOWN).filter((each) => each.reaches.length === 0);
|
|
4080
|
+
out.line(
|
|
4081
|
+
` ${style.dim(`\u2026 and ${rest} more, ${quiet.length} of which name no tool this knows`)}`
|
|
4082
|
+
);
|
|
4083
|
+
out.note('"memnox skills --json" lists every one.');
|
|
4084
|
+
}
|
|
3977
4085
|
}
|
|
3978
4086
|
const known = findings.length - held.length - fresh.length;
|
|
3979
4087
|
out.line("");
|
|
@@ -3985,14 +4093,13 @@ function render9(context, findings) {
|
|
|
3985
4093
|
}
|
|
3986
4094
|
|
|
3987
4095
|
// src/commands/autopilot.command.ts
|
|
3988
|
-
import {
|
|
4096
|
+
import { homedir as homedir28 } from "os";
|
|
3989
4097
|
import {
|
|
3990
4098
|
BAND,
|
|
3991
4099
|
LocalGate as LocalGate4,
|
|
3992
4100
|
actionsForCli,
|
|
3993
4101
|
blastRadiusOf,
|
|
3994
4102
|
boundaryFor,
|
|
3995
|
-
loadPoliciesFromFile as loadPoliciesFromFile4,
|
|
3996
4103
|
rolesIn,
|
|
3997
4104
|
standingFor,
|
|
3998
4105
|
inBand,
|
|
@@ -4002,19 +4109,25 @@ import {
|
|
|
4002
4109
|
function registerAutopilotCommand(program, context) {
|
|
4003
4110
|
program.command("autopilot [agent]").description("What an agent would do on its own, and what would still be asked").option("-f, --file <path>", "policy file (default: whichever exists)").option("--role <name>", "show the boundary of a job rather than of a product").option("--roles", "every job the rules name, and what each may do").option("--json", "machine-readable output").action(
|
|
4004
4111
|
async (agent, options) => {
|
|
4005
|
-
const
|
|
4006
|
-
if (
|
|
4112
|
+
const rules2 = await policySetInForce(homedir28(), options.file);
|
|
4113
|
+
if (rules2.policies.length === 0) {
|
|
4007
4114
|
throw new Error(
|
|
4008
|
-
`No rules at ${file}, so there is no boundary to show. Write one: memnox protect --yes`
|
|
4115
|
+
`No rules at ${resolvePolicyFile(options.file)}, so there is no boundary to show. Write one: memnox protect --yes`
|
|
4009
4116
|
);
|
|
4010
4117
|
}
|
|
4118
|
+
sayWhatDidNotLoad(context, rules2);
|
|
4011
4119
|
const candidatesFor = () => interceptedBinaries2().flatMap((binary) => actionsForCli(binary));
|
|
4012
4120
|
if (options.roles === true) {
|
|
4013
|
-
await renderWorkforce(
|
|
4121
|
+
await renderWorkforce(
|
|
4122
|
+
context,
|
|
4123
|
+
rules2.policies,
|
|
4124
|
+
candidatesFor(),
|
|
4125
|
+
options.json === true
|
|
4126
|
+
);
|
|
4014
4127
|
return;
|
|
4015
4128
|
}
|
|
4016
4129
|
const name = agent ?? "claude-code";
|
|
4017
|
-
const gate =
|
|
4130
|
+
const gate = new LocalGate4(rules2.policies, {
|
|
4018
4131
|
agentName: name,
|
|
4019
4132
|
/* Evaluated as the job, so a `roles:` rule fires. Without this the screen
|
|
4020
4133
|
would show what the product may do and call it what the role may do. */
|
|
@@ -4107,8 +4220,8 @@ function band(context, title, entries, mark) {
|
|
|
4107
4220
|
context.out.line(` ${context.style.dim(`and ${entries.length - 12} more`)}`);
|
|
4108
4221
|
}
|
|
4109
4222
|
}
|
|
4110
|
-
async function renderWorkforce(context,
|
|
4111
|
-
const roles = rolesIn(
|
|
4223
|
+
async function renderWorkforce(context, policies, candidates, asJson) {
|
|
4224
|
+
const roles = rolesIn([...policies]);
|
|
4112
4225
|
const { out, style } = context;
|
|
4113
4226
|
if (roles.length === 0) {
|
|
4114
4227
|
out.line("No rule here names a job, so there is no workforce to show.");
|
|
@@ -4119,7 +4232,7 @@ async function renderWorkforce(context, file, candidates, asJson) {
|
|
|
4119
4232
|
}
|
|
4120
4233
|
const standings = [];
|
|
4121
4234
|
for (const role of roles) {
|
|
4122
|
-
const gate =
|
|
4235
|
+
const gate = new LocalGate4([...policies], { agentName: role, agentRole: role });
|
|
4123
4236
|
const boundary = boundaryFor(role, candidates, (action) => {
|
|
4124
4237
|
const verdict = gate.evaluate({ action });
|
|
4125
4238
|
return {
|
|
@@ -4150,11 +4263,11 @@ async function renderWorkforce(context, file, candidates, asJson) {
|
|
|
4150
4263
|
|
|
4151
4264
|
// src/commands/env.command.ts
|
|
4152
4265
|
import { delimiter as delimiter2 } from "path";
|
|
4153
|
-
import { homedir as
|
|
4266
|
+
import { homedir as homedir29 } from "os";
|
|
4154
4267
|
import { SESSION_VAR as SESSION_VAR2 } from "@memnox/core";
|
|
4155
4268
|
import { interceptorDirFor as interceptorDirFor4, REAL_SHELL_VAR } from "@memnox/interceptors";
|
|
4156
4269
|
var FORMATS = ["sh", "systemd", "docker"];
|
|
4157
|
-
function registerEnvCommand(program, context, home =
|
|
4270
|
+
function registerEnvCommand(program, context, home = homedir29) {
|
|
4158
4271
|
program.command("env").description("The environment an agent needs when something else starts it").option("--format <format>", `one of: ${FORMATS.join(", ")}`, "sh").option("--shell <path>", "shell the agent should use", "memnox-shell").option("--session <id>", "group this run in the timeline under one session").action(async (options) => {
|
|
4159
4272
|
if (!FORMATS.includes(options.format)) {
|
|
4160
4273
|
throw new Error(`--format takes one of: ${FORMATS.join(", ")}`);
|
|
@@ -4202,7 +4315,7 @@ function expand(key, value) {
|
|
|
4202
4315
|
|
|
4203
4316
|
// src/commands/verify.command.ts
|
|
4204
4317
|
import { readFile as readFile7 } from "fs/promises";
|
|
4205
|
-
import { homedir as
|
|
4318
|
+
import { homedir as homedir30 } from "os";
|
|
4206
4319
|
import {
|
|
4207
4320
|
describeProof,
|
|
4208
4321
|
enforcementFailed,
|
|
@@ -4214,13 +4327,13 @@ import {
|
|
|
4214
4327
|
|
|
4215
4328
|
// src/verify/enforcement.ts
|
|
4216
4329
|
import { execFile as execFile3, spawn } from "child_process";
|
|
4217
|
-
import { existsSync as
|
|
4330
|
+
import { existsSync as existsSync6 } from "fs";
|
|
4218
4331
|
import { mkdir as mkdir3, mkdtemp, writeFile as writeFile6 } from "fs/promises";
|
|
4219
4332
|
import { tmpdir } from "os";
|
|
4220
|
-
import { join as
|
|
4333
|
+
import { join as join12 } from "path";
|
|
4221
4334
|
import { promisify as promisify3 } from "util";
|
|
4222
4335
|
import {
|
|
4223
|
-
MEMNOX_HOME as
|
|
4336
|
+
MEMNOX_HOME as MEMNOX_HOME5,
|
|
4224
4337
|
PROOF,
|
|
4225
4338
|
PROXY_BINARY as PROXY_BINARY2,
|
|
4226
4339
|
SURFACE_KIND as SURFACE_KIND2
|
|
@@ -4246,12 +4359,12 @@ else if(m.id!==undefined)send({jsonrpc:'2.0',id:m.id,result:{content:[{type:'tex
|
|
|
4246
4359
|
}});
|
|
4247
4360
|
`;
|
|
4248
4361
|
async function proveEnforcement(context) {
|
|
4249
|
-
const sandbox = await mkdtemp(
|
|
4250
|
-
const rules2 =
|
|
4362
|
+
const sandbox = await mkdtemp(join12(tmpdir(), "memnox-selftest-"));
|
|
4363
|
+
const rules2 = join12(sandbox, "memnox.policies.toml");
|
|
4251
4364
|
await writeFile6(rules2, PROBE_RULES, "utf8");
|
|
4252
|
-
await mkdir3(
|
|
4365
|
+
await mkdir3(join12(sandbox, MEMNOX_HOME5), { recursive: true, mode: 448 });
|
|
4253
4366
|
await writeFile6(
|
|
4254
|
-
|
|
4367
|
+
join12(sandbox, MEMNOX_HOME5, "policies.json"),
|
|
4255
4368
|
`${JSON.stringify({ files: [rules2] }, null, 2)}
|
|
4256
4369
|
`,
|
|
4257
4370
|
"utf8"
|
|
@@ -4266,7 +4379,7 @@ async function proveEnforcement(context) {
|
|
|
4266
4379
|
}
|
|
4267
4380
|
async function proveMcp(sandbox) {
|
|
4268
4381
|
const seam = SURFACE_KIND2.MCP;
|
|
4269
|
-
const stub =
|
|
4382
|
+
const stub = join12(sandbox, "stub.mjs");
|
|
4270
4383
|
await writeFile6(stub, STUB_SERVER, "utf8");
|
|
4271
4384
|
const call = `{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"memnox-self-test","version":"1"}}}
|
|
4272
4385
|
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"memnox_selftest_delete","arguments":{}}}
|
|
@@ -4313,14 +4426,14 @@ function resolvedThrough(sandbox) {
|
|
|
4313
4426
|
return env;
|
|
4314
4427
|
}
|
|
4315
4428
|
function speak(command, args, input, env) {
|
|
4316
|
-
return new Promise((
|
|
4429
|
+
return new Promise((resolve6, reject) => {
|
|
4317
4430
|
const child = spawn(command, [...args], { env, stdio: ["pipe", "pipe", "pipe"] });
|
|
4318
4431
|
let out = "";
|
|
4319
4432
|
child.stdout.on("data", (chunk) => {
|
|
4320
4433
|
out += chunk.toString();
|
|
4321
4434
|
});
|
|
4322
4435
|
child.on("error", reject);
|
|
4323
|
-
child.on("close", () =>
|
|
4436
|
+
child.on("close", () => resolve6(out));
|
|
4324
4437
|
const timer = setTimeout(() => child.kill(), PROBE_MS);
|
|
4325
4438
|
timer.unref();
|
|
4326
4439
|
child.stdin.end(input);
|
|
@@ -4328,8 +4441,8 @@ function speak(command, args, input, env) {
|
|
|
4328
4441
|
}
|
|
4329
4442
|
async function proveShell(home, sandbox) {
|
|
4330
4443
|
const seam = SURFACE_KIND2.SHELL;
|
|
4331
|
-
const binary =
|
|
4332
|
-
if (!
|
|
4444
|
+
const binary = join12(interceptorDirFor5(home), "git");
|
|
4445
|
+
if (!existsSync6(binary)) {
|
|
4333
4446
|
return {
|
|
4334
4447
|
seam,
|
|
4335
4448
|
state: PROOF.ABSENT,
|
|
@@ -4361,9 +4474,9 @@ function refusedIn(seam, output) {
|
|
|
4361
4474
|
};
|
|
4362
4475
|
}
|
|
4363
4476
|
function proveGitHook(dir) {
|
|
4364
|
-
const hook =
|
|
4477
|
+
const hook = join12(dir, ".git", "hooks", "pre-push");
|
|
4365
4478
|
const seam = SURFACE_KIND2.GIT;
|
|
4366
|
-
if (!
|
|
4479
|
+
if (!existsSync6(hook)) {
|
|
4367
4480
|
return {
|
|
4368
4481
|
seam,
|
|
4369
4482
|
state: PROOF.ABSENT,
|
|
@@ -4379,7 +4492,7 @@ function proveGitHook(dir) {
|
|
|
4379
4492
|
}
|
|
4380
4493
|
function proveOsGuard(home) {
|
|
4381
4494
|
const seam = SURFACE_KIND2.FILESYSTEM;
|
|
4382
|
-
if (!
|
|
4495
|
+
if (!existsSync6(guardProfilePath(home))) {
|
|
4383
4496
|
return {
|
|
4384
4497
|
seam,
|
|
4385
4498
|
state: PROOF.ABSENT,
|
|
@@ -4410,7 +4523,7 @@ function proveEgress() {
|
|
|
4410
4523
|
}
|
|
4411
4524
|
|
|
4412
4525
|
// src/commands/verify.command.ts
|
|
4413
|
-
function registerVerifyCommand(program, context, probe = proveEnforcement, home =
|
|
4526
|
+
function registerVerifyCommand(program, context, probe = proveEnforcement, home = homedir30, dir = () => process.cwd()) {
|
|
4414
4527
|
program.command("verify [bundle]").description("Check an exported bundle, or prove the seams actually refuse").option(
|
|
4415
4528
|
"--enforcement",
|
|
4416
4529
|
"ask every seam to refuse something, and report what came back"
|
|
@@ -4482,8 +4595,8 @@ async function renderEnforcement(context, proofs) {
|
|
|
4482
4595
|
}
|
|
4483
4596
|
|
|
4484
4597
|
// src/commands/daemon.command.ts
|
|
4485
|
-
import { homedir as
|
|
4486
|
-
import { existsSync as
|
|
4598
|
+
import { homedir as homedir31 } from "os";
|
|
4599
|
+
import { existsSync as existsSync7 } from "fs";
|
|
4487
4600
|
import { LocalGate as LocalGate5 } from "@memnox/core";
|
|
4488
4601
|
import { readBudgets as readBudgets4, readFleetSpend, SessionPauses as SessionPauses4 } from "@memnox/core";
|
|
4489
4602
|
|
|
@@ -4541,9 +4654,9 @@ Use a shorter home directory. The interceptors still evaluate in process, which
|
|
|
4541
4654
|
await rm2(path, { force: true });
|
|
4542
4655
|
const server = createServer((socket) => this.serve(socket));
|
|
4543
4656
|
this.server = server;
|
|
4544
|
-
await new Promise((
|
|
4657
|
+
await new Promise((resolve6, reject) => {
|
|
4545
4658
|
server.once("error", reject);
|
|
4546
|
-
server.listen(path, () =>
|
|
4659
|
+
server.listen(path, () => resolve6());
|
|
4547
4660
|
});
|
|
4548
4661
|
await chmod(path, 384);
|
|
4549
4662
|
return path;
|
|
@@ -4551,7 +4664,7 @@ Use a shorter home directory. The interceptors still evaluate in process, which
|
|
|
4551
4664
|
async close() {
|
|
4552
4665
|
const server = this.server;
|
|
4553
4666
|
if (server === null) return;
|
|
4554
|
-
await new Promise((
|
|
4667
|
+
await new Promise((resolve6) => server.close(() => resolve6()));
|
|
4555
4668
|
this.server = null;
|
|
4556
4669
|
}
|
|
4557
4670
|
serve(socket) {
|
|
@@ -4742,11 +4855,11 @@ import { readAccount as readAccount2 } from "@memnox/core";
|
|
|
4742
4855
|
|
|
4743
4856
|
// src/sync/heartbeat.ts
|
|
4744
4857
|
import { readFile as readFile10 } from "fs/promises";
|
|
4745
|
-
import { join as
|
|
4858
|
+
import { join as join15 } from "path";
|
|
4746
4859
|
import {
|
|
4747
4860
|
fleetBudgets,
|
|
4748
4861
|
HOLD_ANSWER as HOLD_ANSWER2,
|
|
4749
|
-
MEMNOX_HOME as
|
|
4862
|
+
MEMNOX_HOME as MEMNOX_HOME8,
|
|
4750
4863
|
NodeSnapshotStore as NodeSnapshotStore4,
|
|
4751
4864
|
PendingApprovals as PendingApprovals3,
|
|
4752
4865
|
readBudgets as readBudgets3,
|
|
@@ -4758,10 +4871,10 @@ import { readAccount } from "@memnox/core";
|
|
|
4758
4871
|
|
|
4759
4872
|
// src/sync/bundle.ts
|
|
4760
4873
|
import { mkdir as mkdir5, readFile as readFile8, rename, rm as rm3, writeFile as writeFile7 } from "fs/promises";
|
|
4761
|
-
import { dirname as dirname5, join as
|
|
4874
|
+
import { dirname as dirname5, join as join13 } from "path";
|
|
4762
4875
|
import {
|
|
4763
|
-
loadPoliciesFromFile as
|
|
4764
|
-
MEMNOX_HOME as
|
|
4876
|
+
loadPoliciesFromFile as loadPoliciesFromFile4,
|
|
4877
|
+
MEMNOX_HOME as MEMNOX_HOME6,
|
|
4765
4878
|
orgConditionsPathFor,
|
|
4766
4879
|
orgOverlaysFrom,
|
|
4767
4880
|
writeOrgConditions
|
|
@@ -4848,7 +4961,7 @@ var PULL_OUTCOME = {
|
|
|
4848
4961
|
LAPSED: "lapsed"
|
|
4849
4962
|
};
|
|
4850
4963
|
function orgPolicyPath(home) {
|
|
4851
|
-
return
|
|
4964
|
+
return join13(home, MEMNOX_HOME6, ORG_POLICY_FILE);
|
|
4852
4965
|
}
|
|
4853
4966
|
async function pullBundle(home, account, held, now = Date.now) {
|
|
4854
4967
|
const answer = await callCloud({
|
|
@@ -4882,7 +4995,7 @@ async function applyBundle(home, bundle, now = Date.now) {
|
|
|
4882
4995
|
mode: OWNER_ONLY
|
|
4883
4996
|
});
|
|
4884
4997
|
try {
|
|
4885
|
-
await
|
|
4998
|
+
await loadPoliciesFromFile4(staging);
|
|
4886
4999
|
} catch (err) {
|
|
4887
5000
|
await rm3(staging, { force: true });
|
|
4888
5001
|
return {
|
|
@@ -4933,11 +5046,11 @@ async function touchOrgConditions(home, at2) {
|
|
|
4933
5046
|
// src/sync/push.ts
|
|
4934
5047
|
import { createSign, sign as signBytes } from "crypto";
|
|
4935
5048
|
import { readFile as readFile9, writeFile as writeFile8, mkdir as mkdir6 } from "fs/promises";
|
|
4936
|
-
import { dirname as dirname6, join as
|
|
5049
|
+
import { dirname as dirname6, join as join14 } from "path";
|
|
4937
5050
|
import {
|
|
4938
5051
|
DECISION_EFFECT as DECISION_EFFECT8,
|
|
4939
5052
|
EXECUTION,
|
|
4940
|
-
MEMNOX_HOME as
|
|
5053
|
+
MEMNOX_HOME as MEMNOX_HOME7
|
|
4941
5054
|
} from "@memnox/core";
|
|
4942
5055
|
|
|
4943
5056
|
// src/sync/census.ts
|
|
@@ -5020,7 +5133,7 @@ var PUSH_OUTCOME = {
|
|
|
5020
5133
|
REFUSED: "refused"
|
|
5021
5134
|
};
|
|
5022
5135
|
function cursorPathFor(home) {
|
|
5023
|
-
return
|
|
5136
|
+
return join14(home, MEMNOX_HOME7, CURSOR_FILE);
|
|
5024
5137
|
}
|
|
5025
5138
|
async function readCursor(home) {
|
|
5026
5139
|
try {
|
|
@@ -5256,7 +5369,7 @@ async function onePass(home) {
|
|
|
5256
5369
|
const census = await pushCensus(
|
|
5257
5370
|
home,
|
|
5258
5371
|
account,
|
|
5259
|
-
await new NodeSnapshotStore4(
|
|
5372
|
+
await new NodeSnapshotStore4(join15(home, MEMNOX_HOME8)).latest()
|
|
5260
5373
|
);
|
|
5261
5374
|
if (census.outcome === PUSH_OUTCOME.REVOKED) {
|
|
5262
5375
|
return { pull, push, census, revoked: true };
|
|
@@ -5356,10 +5469,10 @@ async function syncLoop(home, running, seams = {}) {
|
|
|
5356
5469
|
}
|
|
5357
5470
|
|
|
5358
5471
|
// src/commands/daemon.command.ts
|
|
5359
|
-
function registerDaemonCommand(program, context, home =
|
|
5472
|
+
function registerDaemonCommand(program, context, home = homedir31) {
|
|
5360
5473
|
program.command("daemon").description("Hold the rules in one process, so an interceptor pays a connect").option("-f, --file <path>", "policy file (default: whichever exists)").option("--no-sync", "hold the rules but talk to nothing").action(async (options) => {
|
|
5361
5474
|
const file = resolvePolicyFile(options.file);
|
|
5362
|
-
const gate =
|
|
5475
|
+
const gate = existsSync7(file) ? await LocalGate5.fromFiles([file], { agentName: "agent" }) : void 0;
|
|
5363
5476
|
const budgets = await readBudgets4(home());
|
|
5364
5477
|
const spentAlready = budgets.length === 0 ? [] : await withEvents(home(), (store) => store.query({ limit: 2e4 }));
|
|
5365
5478
|
const daemon = new MemnoxDaemon({
|
|
@@ -5391,10 +5504,10 @@ function registerDaemonCommand(program, context, home = homedir30) {
|
|
|
5391
5504
|
log: (message) => context.out.note(message)
|
|
5392
5505
|
});
|
|
5393
5506
|
}
|
|
5394
|
-
await new Promise((
|
|
5507
|
+
await new Promise((resolve6) => {
|
|
5395
5508
|
const stop = () => {
|
|
5396
5509
|
running = false;
|
|
5397
|
-
void daemon.close().then(
|
|
5510
|
+
void daemon.close().then(resolve6);
|
|
5398
5511
|
};
|
|
5399
5512
|
process.once("SIGINT", stop);
|
|
5400
5513
|
process.once("SIGTERM", stop);
|
|
@@ -5403,17 +5516,17 @@ function registerDaemonCommand(program, context, home = homedir30) {
|
|
|
5403
5516
|
}
|
|
5404
5517
|
|
|
5405
5518
|
// src/commands/uninstall.command.ts
|
|
5406
|
-
import { homedir as
|
|
5519
|
+
import { homedir as homedir32 } from "os";
|
|
5407
5520
|
import { cwd as cwd2 } from "process";
|
|
5408
|
-
import { existsSync as
|
|
5521
|
+
import { existsSync as existsSync8 } from "fs";
|
|
5409
5522
|
import { rm as rm4 } from "fs/promises";
|
|
5410
|
-
import { join as
|
|
5523
|
+
import { join as join16 } from "path";
|
|
5411
5524
|
import {
|
|
5412
5525
|
fleetSpendPathFor,
|
|
5413
5526
|
LEASE_OUTCOME as LEASE_OUTCOME2,
|
|
5414
5527
|
LeaseRegistry as LeaseRegistry3,
|
|
5415
5528
|
leaseDirFor,
|
|
5416
|
-
MEMNOX_HOME as
|
|
5529
|
+
MEMNOX_HOME as MEMNOX_HOME9,
|
|
5417
5530
|
pauseDirFor,
|
|
5418
5531
|
pendingDirFor,
|
|
5419
5532
|
taskDirFor
|
|
@@ -5433,7 +5546,7 @@ async function clearOperationalState(home) {
|
|
|
5433
5546
|
];
|
|
5434
5547
|
let cleared = 0;
|
|
5435
5548
|
for (const path of paths) {
|
|
5436
|
-
if (!
|
|
5549
|
+
if (!existsSync8(path)) continue;
|
|
5437
5550
|
await rm4(path, { recursive: true, force: true });
|
|
5438
5551
|
cleared += 1;
|
|
5439
5552
|
}
|
|
@@ -5455,7 +5568,7 @@ async function releaseEveryLease(home, now) {
|
|
|
5455
5568
|
}
|
|
5456
5569
|
function registerUninstallCommand(program, context, deps = {}) {
|
|
5457
5570
|
program.command("uninstall").description("Remove the interceptors, the hooks and the wrapping from this machine").option("--purge", "also delete ~/.memnox, including the history and your rules").action(async (options) => {
|
|
5458
|
-
const home = (deps.home ??
|
|
5571
|
+
const home = (deps.home ?? homedir32)();
|
|
5459
5572
|
const dir = (deps.dir ?? cwd2)();
|
|
5460
5573
|
const { out } = context;
|
|
5461
5574
|
const interceptors = await removeInterceptors(home);
|
|
@@ -5488,14 +5601,14 @@ function registerUninstallCommand(program, context, deps = {}) {
|
|
|
5488
5601
|
}
|
|
5489
5602
|
if (options.purge !== true) {
|
|
5490
5603
|
out.line("");
|
|
5491
|
-
out.line(`Your rules and history are still in ${
|
|
5604
|
+
out.line(`Your rules and history are still in ${join16(home, MEMNOX_HOME9)}.`);
|
|
5492
5605
|
out.line("Add --purge to delete those too.");
|
|
5493
5606
|
return;
|
|
5494
5607
|
}
|
|
5495
|
-
await rm4(
|
|
5608
|
+
await rm4(join16(home, MEMNOX_HOME9), { recursive: true, force: true });
|
|
5496
5609
|
out.line("");
|
|
5497
|
-
out.line(`Deleted ${
|
|
5498
|
-
const rules2 = POLICY_FILES.map((name) =>
|
|
5610
|
+
out.line(`Deleted ${join16(home, MEMNOX_HOME9)}.`);
|
|
5611
|
+
const rules2 = POLICY_FILES.map((name) => join16(dir, name)).filter(existsSync8);
|
|
5499
5612
|
if (rules2.length === 0) {
|
|
5500
5613
|
out.line("Nothing of Memnox is left on this machine.");
|
|
5501
5614
|
} else {
|
|
@@ -5513,11 +5626,11 @@ function registerUninstallCommand(program, context, deps = {}) {
|
|
|
5513
5626
|
|
|
5514
5627
|
// src/commands/run.command.ts
|
|
5515
5628
|
import { spawn as spawn2 } from "child_process";
|
|
5516
|
-
import { createWriteStream, existsSync as
|
|
5629
|
+
import { createWriteStream, existsSync as existsSync9, mkdirSync } from "fs";
|
|
5517
5630
|
import { join as join18 } from "path";
|
|
5518
|
-
import { homedir as
|
|
5631
|
+
import { homedir as homedir33, release as release2 } from "os";
|
|
5519
5632
|
import { randomUUID } from "crypto";
|
|
5520
|
-
import { delimiter as
|
|
5633
|
+
import { delimiter as delimiter4 } from "path";
|
|
5521
5634
|
import {
|
|
5522
5635
|
guardFor as guardFor2,
|
|
5523
5636
|
isEmptyScope,
|
|
@@ -5536,12 +5649,40 @@ import {
|
|
|
5536
5649
|
interceptorDirFor as interceptorDirFor7,
|
|
5537
5650
|
REAL_SHELL_VAR as REAL_SHELL_VAR2
|
|
5538
5651
|
} from "@memnox/interceptors";
|
|
5652
|
+
|
|
5653
|
+
// src/on-path.ts
|
|
5654
|
+
import { accessSync, constants } from "fs";
|
|
5655
|
+
import { delimiter as delimiter3, join as join17 } from "path";
|
|
5656
|
+
import { DISCOVERED_AGENT_KIND } from "@memnox/core";
|
|
5657
|
+
function onPath(binary, path = process.env["PATH"] ?? "") {
|
|
5658
|
+
if (binary.includes("/")) return executable(binary);
|
|
5659
|
+
return path.split(delimiter3).some((entry) => entry !== "" && executable(join17(entry, binary)));
|
|
5660
|
+
}
|
|
5661
|
+
function executable(candidate) {
|
|
5662
|
+
try {
|
|
5663
|
+
accessSync(candidate, constants.X_OK);
|
|
5664
|
+
return true;
|
|
5665
|
+
} catch {
|
|
5666
|
+
return false;
|
|
5667
|
+
}
|
|
5668
|
+
}
|
|
5669
|
+
var BINARY_FOR = {
|
|
5670
|
+
[DISCOVERED_AGENT_KIND.CLAUDE_CODE]: "claude",
|
|
5671
|
+
[DISCOVERED_AGENT_KIND.CODEX_CLI]: "codex",
|
|
5672
|
+
[DISCOVERED_AGENT_KIND.CLAUDE_DESKTOP]: "claude"
|
|
5673
|
+
};
|
|
5674
|
+
function binaryMeantBy(name) {
|
|
5675
|
+
const binary = BINARY_FOR[name];
|
|
5676
|
+
return binary === void 0 || !onPath(binary) ? null : binary;
|
|
5677
|
+
}
|
|
5678
|
+
|
|
5679
|
+
// src/commands/run.command.ts
|
|
5539
5680
|
function environmentFor(base, home, sessionId, shellBinary) {
|
|
5540
5681
|
const interceptors = interceptorDirFor7(home);
|
|
5541
5682
|
const path = base["PATH"] ?? "";
|
|
5542
5683
|
return {
|
|
5543
5684
|
...base,
|
|
5544
|
-
PATH: path.startsWith(interceptors) ? path : `${interceptors}${
|
|
5685
|
+
PATH: path.startsWith(interceptors) ? path : `${interceptors}${delimiter4}${path}`,
|
|
5545
5686
|
SHELL: shellBinary,
|
|
5546
5687
|
/* The shell we displace, so the wrapper has something to hand the command to and
|
|
5547
5688
|
never reads `SHELL` back to find itself. */
|
|
@@ -5552,11 +5693,11 @@ function environmentFor(base, home, sessionId, shellBinary) {
|
|
|
5552
5693
|
function newSessionId() {
|
|
5553
5694
|
return `ses_${randomUUID().slice(0, 12)}`;
|
|
5554
5695
|
}
|
|
5555
|
-
var defaultStart = (command, args, env, transcript) => new Promise((
|
|
5696
|
+
var defaultStart = (command, args, env, transcript) => new Promise((resolve6) => {
|
|
5556
5697
|
if (transcript === void 0) {
|
|
5557
5698
|
const child2 = spawn2(command, [...args], { stdio: "inherit", env });
|
|
5558
|
-
child2.on("exit", (code) =>
|
|
5559
|
-
child2.on("error", () =>
|
|
5699
|
+
child2.on("exit", (code) => resolve6(code ?? 1));
|
|
5700
|
+
child2.on("error", () => resolve6(127));
|
|
5560
5701
|
return;
|
|
5561
5702
|
}
|
|
5562
5703
|
mkdirSync(join18(transcript, ".."), { recursive: true, mode: 448 });
|
|
@@ -5571,11 +5712,11 @@ var defaultStart = (command, args, env, transcript) => new Promise((resolve5) =>
|
|
|
5571
5712
|
child.stderr?.pipe(log);
|
|
5572
5713
|
child.on("exit", (code) => {
|
|
5573
5714
|
log.end();
|
|
5574
|
-
|
|
5715
|
+
resolve6(code ?? 1);
|
|
5575
5716
|
});
|
|
5576
5717
|
child.on("error", () => {
|
|
5577
5718
|
log.end();
|
|
5578
|
-
|
|
5719
|
+
resolve6(127);
|
|
5579
5720
|
});
|
|
5580
5721
|
});
|
|
5581
5722
|
function registerRunCommand(program, context, deps = {}) {
|
|
@@ -5588,7 +5729,14 @@ function registerRunCommand(program, context, deps = {}) {
|
|
|
5588
5729
|
if (binary === void 0) {
|
|
5589
5730
|
throw new Error("Name the command to run: memnox run -- claude");
|
|
5590
5731
|
}
|
|
5591
|
-
|
|
5732
|
+
if (!(deps.onPath ?? onPath)(binary)) {
|
|
5733
|
+
const meant = (deps.binaryMeantBy ?? binaryMeantBy)(binary);
|
|
5734
|
+
throw new Error(
|
|
5735
|
+
`"${binary}" is not on PATH, so there is nothing to start.` + (meant === null ? "" : `
|
|
5736
|
+
The binary for ${binary} is "${meant}": memnox run -- ${meant}`)
|
|
5737
|
+
);
|
|
5738
|
+
}
|
|
5739
|
+
const home = (deps.home ?? homedir33)();
|
|
5592
5740
|
const sessionId = (deps.newId ?? newSessionId)();
|
|
5593
5741
|
const env = environmentFor(process.env, home, sessionId, options.shell);
|
|
5594
5742
|
if (options.role !== void 0) env[AGENT_ROLE_VAR] = options.role;
|
|
@@ -5612,9 +5760,9 @@ function registerRunCommand(program, context, deps = {}) {
|
|
|
5612
5760
|
const transcript = options.transcript === true ? transcriptPathFor(home, sessionId) : void 0;
|
|
5613
5761
|
if (transcript !== void 0) context.out.note(`transcript ${transcript}`);
|
|
5614
5762
|
const start = deps.start ?? defaultStart;
|
|
5615
|
-
const [
|
|
5763
|
+
const [executable2, ...args] = guarded;
|
|
5616
5764
|
try {
|
|
5617
|
-
process.exitCode = await start(
|
|
5765
|
+
process.exitCode = await start(executable2 ?? binary, args, env, transcript);
|
|
5618
5766
|
} finally {
|
|
5619
5767
|
const let_go = await releaseLeases(home, sessionId, deps);
|
|
5620
5768
|
if (let_go > 0) {
|
|
@@ -5630,7 +5778,7 @@ function sandboxed(command, home, wanted, seams = {}) {
|
|
|
5630
5778
|
const kernel = seams.kernel ?? release2();
|
|
5631
5779
|
if (guardFor2(platform, kernel).guard !== OS_GUARD2.SEATBELT) return command;
|
|
5632
5780
|
const profile = guardProfilePath(home);
|
|
5633
|
-
if (!(seams.exists ??
|
|
5781
|
+
if (!(seams.exists ?? existsSync9)(profile)) return command;
|
|
5634
5782
|
return sandboxCommand(profile, command);
|
|
5635
5783
|
}
|
|
5636
5784
|
async function declareTask(home, sessionId, options, deps) {
|
|
@@ -5681,6 +5829,7 @@ async function keepMilestone(sessionId, binary, deps) {
|
|
|
5681
5829
|
sessionId,
|
|
5682
5830
|
note: `before ${binary}`
|
|
5683
5831
|
});
|
|
5832
|
+
await milestones.forget();
|
|
5684
5833
|
return taken.id;
|
|
5685
5834
|
} catch {
|
|
5686
5835
|
return null;
|
|
@@ -5688,8 +5837,7 @@ async function keepMilestone(sessionId, binary, deps) {
|
|
|
5688
5837
|
}
|
|
5689
5838
|
|
|
5690
5839
|
// src/commands/mcp.command.ts
|
|
5691
|
-
import {
|
|
5692
|
-
import { homedir as homedir33 } from "os";
|
|
5840
|
+
import { homedir as homedir34 } from "os";
|
|
5693
5841
|
import { dirname as dirname7, join as join19 } from "path";
|
|
5694
5842
|
import { copyFile, mkdir as mkdir7, readFile as readFile11, writeFile as writeFile9 } from "fs/promises";
|
|
5695
5843
|
import {
|
|
@@ -5759,18 +5907,11 @@ async function writeConfig(home, file, servers, changed) {
|
|
|
5759
5907
|
await writeFile9(file.path, `${JSON.stringify(next, null, 2)}
|
|
5760
5908
|
`, "utf8");
|
|
5761
5909
|
}
|
|
5762
|
-
function proxyOnPath(
|
|
5763
|
-
return
|
|
5910
|
+
function proxyOnPath(resolve6 = defaultResolve) {
|
|
5911
|
+
return resolve6(PROXY_BINARY3);
|
|
5764
5912
|
}
|
|
5765
|
-
var defaultResolve = (binary) =>
|
|
5766
|
-
|
|
5767
|
-
execFileSync("command", ["-v", binary], { stdio: "ignore", shell: true });
|
|
5768
|
-
return true;
|
|
5769
|
-
} catch {
|
|
5770
|
-
return false;
|
|
5771
|
-
}
|
|
5772
|
-
};
|
|
5773
|
-
function registerMcpCommand(program, context, home = homedir33, resolveBinary = defaultResolve, project = () => process.cwd()) {
|
|
5913
|
+
var defaultResolve = (binary) => onPath(binary);
|
|
5914
|
+
function registerMcpCommand(program, context, home = homedir34, resolveBinary = defaultResolve, project = () => process.cwd()) {
|
|
5774
5915
|
const mcp = program.command("mcp").description("Route this machine\u2019s MCP servers through the Memnox proxy");
|
|
5775
5916
|
mcp.command("wrap").description("Repoint every MCP server at the proxy, keeping a backup").option("--dry-run", "print what would change and write nothing").action(async (options) => {
|
|
5776
5917
|
if (options.dryRun !== true && !proxyOnPath(resolveBinary)) {
|
|
@@ -5846,19 +5987,21 @@ Install the CLI first (npm install -g memnox), then run this again.`
|
|
|
5846
5987
|
}
|
|
5847
5988
|
|
|
5848
5989
|
// src/commands/policy.command.ts
|
|
5849
|
-
import { existsSync as
|
|
5850
|
-
import {
|
|
5851
|
-
import {
|
|
5990
|
+
import { existsSync as existsSync10 } from "fs";
|
|
5991
|
+
import { mkdir as mkdir8, readFile as readFile12, writeFile as writeFile10 } from "fs/promises";
|
|
5992
|
+
import { homedir as homedir35 } from "os";
|
|
5993
|
+
import { dirname as dirname8, join as join20, resolve as resolve5 } from "path";
|
|
5852
5994
|
import {
|
|
5853
5995
|
DECISION_EFFECT as DECISION_EFFECT9,
|
|
5854
|
-
loadPoliciesFromFile as
|
|
5996
|
+
loadPoliciesFromFile as loadPoliciesFromFile5,
|
|
5855
5997
|
loadPolicySet as loadPolicySet4,
|
|
5856
5998
|
LocalGate as LocalGate6,
|
|
5857
|
-
MEMNOX_HOME as
|
|
5999
|
+
MEMNOX_HOME as MEMNOX_HOME10,
|
|
5858
6000
|
readPolicyRegistry as readPolicyRegistry6,
|
|
6001
|
+
renameEffectsIn,
|
|
5859
6002
|
resolveAction
|
|
5860
6003
|
} from "@memnox/core";
|
|
5861
|
-
var
|
|
6004
|
+
var REGISTRY_FILE4 = "policies.json";
|
|
5862
6005
|
function splitCommand(input) {
|
|
5863
6006
|
return (input.match(/"[^"]*"|'[^']*'|\S+/g) ?? []).map(
|
|
5864
6007
|
(word) => word.replace(/^["']|["']$/g, "")
|
|
@@ -5881,34 +6024,38 @@ function requestFor(input, options) {
|
|
|
5881
6024
|
}
|
|
5882
6025
|
function registerPolicyCommand(program, context) {
|
|
5883
6026
|
const policy = program.command("policy").description("Inspect the rules in force");
|
|
5884
|
-
policy.command("check [file]").description("Read every rule file on this machine and say what will not load").option("--prune", "forget registered files that are no longer on the disk").
|
|
5885
|
-
|
|
5886
|
-
|
|
6027
|
+
policy.command("check [file]").description("Read every rule file on this machine and say what will not load").option("--prune", "forget registered files that are no longer on the disk").option("--fix", "rewrite effects this version renamed, keeping a backup").action(
|
|
6028
|
+
async (file, options) => {
|
|
6029
|
+
if (options.fix === true) await fixRenamedEffects(context, file);
|
|
6030
|
+
await checkPolicyFiles(context, file, options.prune === true);
|
|
6031
|
+
}
|
|
6032
|
+
);
|
|
5887
6033
|
policy.command("use [file]").description('Register a rule file, so the seams load it and not only "policy test"').action(async (file) => {
|
|
5888
|
-
const path =
|
|
5889
|
-
if (!
|
|
6034
|
+
const path = resolve5(resolvePolicyFile(file));
|
|
6035
|
+
if (!existsSync10(path)) {
|
|
5890
6036
|
throw new Error(
|
|
5891
6037
|
`No rule file at ${path}. Write one with "memnox protect --yes".`
|
|
5892
6038
|
);
|
|
5893
6039
|
}
|
|
5894
|
-
const policies = await
|
|
6040
|
+
const policies = await loadPoliciesFromFile5(path);
|
|
5895
6041
|
const before = await readPolicyRegistry6(
|
|
5896
|
-
join20(
|
|
6042
|
+
join20(homedir35(), MEMNOX_HOME10, REGISTRY_FILE4)
|
|
5897
6043
|
);
|
|
5898
|
-
await registerPolicyFile(
|
|
6044
|
+
await registerPolicyFile(homedir35(), path);
|
|
5899
6045
|
context.out.line(
|
|
5900
6046
|
before.includes(path) ? `${path} was already registered; ${policies.length} rule(s) load at every seam.` : `Registered ${path} \u2014 ${policies.length} rule(s) now load at every seam.`
|
|
5901
6047
|
);
|
|
5902
6048
|
context.out.note('Check it with "memnox doctor --wiring".');
|
|
5903
6049
|
});
|
|
5904
6050
|
policy.command("test <action>").description("Evaluate one action against the rules, changing nothing").option("-f, --file <path>", "policy file (default: whichever exists)").option("-a, --agent <name>", "agent the rules are matched against", "agent").option("-t, --target <target>", "what the action operates on").action(async (action, options) => {
|
|
5905
|
-
const
|
|
5906
|
-
if (
|
|
6051
|
+
const rules2 = await policySetInForce(homedir35(), options.file);
|
|
6052
|
+
if (rules2.policies.length === 0 && rules2.unreadable.length === 0) {
|
|
5907
6053
|
throw new Error(
|
|
5908
|
-
`No rules at ${file}. Write some first: memnox protect --interactive`
|
|
6054
|
+
`No rules at ${resolvePolicyFile(options.file)}. Write some first: memnox protect --interactive`
|
|
5909
6055
|
);
|
|
5910
6056
|
}
|
|
5911
|
-
|
|
6057
|
+
sayWhatDidNotLoad(context, rules2);
|
|
6058
|
+
const gate = new LocalGate6(rules2.policies, {
|
|
5912
6059
|
agentName: options.agent
|
|
5913
6060
|
});
|
|
5914
6061
|
const verdict = gate.evaluate(requestFor(action, options));
|
|
@@ -5940,7 +6087,7 @@ async function checkPolicyFiles(context, file, prune) {
|
|
|
5940
6087
|
out.line(`${style.dim("gone")} ${missing}`);
|
|
5941
6088
|
}
|
|
5942
6089
|
if (set.missing.length > 0 && prune && file === void 0) {
|
|
5943
|
-
await forgetPolicyFiles(
|
|
6090
|
+
await forgetPolicyFiles(homedir35(), set.missing);
|
|
5944
6091
|
out.line(`${style.dim("forgot")} ${set.missing.length} path(s) that are gone`);
|
|
5945
6092
|
}
|
|
5946
6093
|
for (const broken of set.unreadable) {
|
|
@@ -5954,65 +6101,37 @@ async function checkPolicyFiles(context, file, prune) {
|
|
|
5954
6101
|
}
|
|
5955
6102
|
if (set.unreadable.length > 0) process.exitCode = 1;
|
|
5956
6103
|
}
|
|
6104
|
+
async function fixRenamedEffects(context, file) {
|
|
6105
|
+
const { out, style } = context;
|
|
6106
|
+
const files = file !== void 0 ? [resolve5(file)] : await allPolicyFiles();
|
|
6107
|
+
let touched = 0;
|
|
6108
|
+
for (const path of files) {
|
|
6109
|
+
let source;
|
|
6110
|
+
try {
|
|
6111
|
+
source = await readFile12(path, "utf8");
|
|
6112
|
+
} catch {
|
|
6113
|
+
continue;
|
|
6114
|
+
}
|
|
6115
|
+
const { text, renamed } = renameEffectsIn(source);
|
|
6116
|
+
if (renamed.length === 0) continue;
|
|
6117
|
+
const backup = backupPathFor(homedir35(), path);
|
|
6118
|
+
await mkdir8(dirname8(backup), { recursive: true, mode: 448 });
|
|
6119
|
+
await writeFile10(backup, source, "utf8");
|
|
6120
|
+
await writeFile10(path, text, "utf8");
|
|
6121
|
+
touched += 1;
|
|
6122
|
+
out.line(`${style.ok("fixed")} ${path} ${renamed.length} effect(s)`);
|
|
6123
|
+
for (const each of new Set(renamed)) out.line(` ${each}`);
|
|
6124
|
+
out.note(` kept the original at ${backup}`);
|
|
6125
|
+
}
|
|
6126
|
+
if (touched > 0) out.line("");
|
|
6127
|
+
}
|
|
5957
6128
|
async function allPolicyFiles() {
|
|
5958
|
-
|
|
5959
|
-
join20(homedir34(), MEMNOX_HOME11, REGISTRY_FILE5)
|
|
5960
|
-
);
|
|
5961
|
-
const here = resolvePolicyFile();
|
|
5962
|
-
const found = new Set(registered);
|
|
5963
|
-
if (existsSync13(here)) found.add(here);
|
|
5964
|
-
return [...found];
|
|
6129
|
+
return policyFilesInForce(homedir35());
|
|
5965
6130
|
}
|
|
5966
6131
|
|
|
5967
6132
|
// src/commands/login.command.ts
|
|
5968
6133
|
import { spawn as spawn3 } from "child_process";
|
|
5969
|
-
import { homedir as
|
|
5970
|
-
|
|
5971
|
-
// src/banner.ts
|
|
5972
|
-
var LETTERS = [
|
|
5973
|
-
["## ##", "### ###", "## # ##", "## ##", "## ##"],
|
|
5974
|
-
["######", "##", "#####", "##", "######"],
|
|
5975
|
-
["## ##", "### ###", "## # ##", "## ##", "## ##"],
|
|
5976
|
-
["## ##", "### ##", "## # ##", "## ###", "## ##"],
|
|
5977
|
-
[" #####", "## ##", "## ##", "## ##", " #####"],
|
|
5978
|
-
["## ##", " ## ##", " ###", " ## ##", "## ##"]
|
|
5979
|
-
];
|
|
5980
|
-
var ROWS = 5;
|
|
5981
|
-
var TRACKING = 1;
|
|
5982
|
-
var FACE = "\u2588";
|
|
5983
|
-
var SHADOW = "\u2584";
|
|
5984
|
-
var DRAWN = "#";
|
|
5985
|
-
function faceGrid() {
|
|
5986
|
-
const grid = Array.from({ length: ROWS }, () => []);
|
|
5987
|
-
for (const letter of LETTERS) {
|
|
5988
|
-
const width = Math.max(...letter.map((row3) => row3.length));
|
|
5989
|
-
for (let row3 = 0; row3 < ROWS; row3 += 1) {
|
|
5990
|
-
const art = letter[row3] ?? "";
|
|
5991
|
-
for (let column = 0; column < width + TRACKING; column += 1) {
|
|
5992
|
-
grid[row3].push(art[column] === DRAWN);
|
|
5993
|
-
}
|
|
5994
|
-
}
|
|
5995
|
-
}
|
|
5996
|
-
return grid;
|
|
5997
|
-
}
|
|
5998
|
-
var at = (grid, row3, column) => row3 >= 0 && column >= 0 && (grid[row3]?.[column] ?? false);
|
|
5999
|
-
var shadowed = (grid, row3, column) => row3 === ROWS && at(grid, ROWS - 1, column - 1);
|
|
6000
|
-
function wordmark(style, plain) {
|
|
6001
|
-
if (!style.decorated) return [plain];
|
|
6002
|
-
const grid = faceGrid();
|
|
6003
|
-
const width = grid[0]?.length ?? 0;
|
|
6004
|
-
const lines = [];
|
|
6005
|
-
for (let row3 = 0; row3 <= ROWS; row3 += 1) {
|
|
6006
|
-
let line2 = "";
|
|
6007
|
-
for (let column = 0; column < width; column += 1) {
|
|
6008
|
-
if (at(grid, row3, column)) line2 += style.bold(FACE);
|
|
6009
|
-
else if (shadowed(grid, row3, column)) line2 += style.dim(SHADOW);
|
|
6010
|
-
else line2 += " ";
|
|
6011
|
-
}
|
|
6012
|
-
lines.push(line2);
|
|
6013
|
-
}
|
|
6014
|
-
return lines;
|
|
6015
|
-
}
|
|
6134
|
+
import { homedir as homedir36 } from "os";
|
|
6016
6135
|
|
|
6017
6136
|
// src/flow.ts
|
|
6018
6137
|
var RAIL = "\u2502";
|
|
@@ -6040,13 +6159,8 @@ var Flow = class {
|
|
|
6040
6159
|
say(mark, text) {
|
|
6041
6160
|
this.out.note(`${this.gutter(mark)}${text}`);
|
|
6042
6161
|
}
|
|
6043
|
-
/** The
|
|
6162
|
+
/** The command's own name on a filled block. The wordmark above it is the program's. */
|
|
6044
6163
|
open(name) {
|
|
6045
|
-
if (this.on) {
|
|
6046
|
-
this.out.note("");
|
|
6047
|
-
for (const line2 of wordmark(this.style, "memnox")) this.out.note(line2);
|
|
6048
|
-
this.out.note("");
|
|
6049
|
-
}
|
|
6050
6164
|
this.say(START, this.style.chip(name));
|
|
6051
6165
|
this.say(RAIL, "");
|
|
6052
6166
|
}
|
|
@@ -6135,8 +6249,10 @@ async function requestCode(options, publicKey) {
|
|
|
6135
6249
|
}
|
|
6136
6250
|
});
|
|
6137
6251
|
if (answer.status !== 200 && answer.status !== 201) {
|
|
6252
|
+
const missing = answer.status === 404 || answer.status === 501;
|
|
6138
6253
|
throw new EnrolmentRefused(
|
|
6139
|
-
|
|
6254
|
+
missing ? `${options.baseUrl} has no device-enrolment endpoint (${answer.status}), so it is not a Memnox control plane.
|
|
6255
|
+
Point at the right one with "memnox login --url <base>". This machine is unchanged.` : `the control plane refused the request (${answer.status}).`
|
|
6140
6256
|
);
|
|
6141
6257
|
}
|
|
6142
6258
|
const offer = answer.body;
|
|
@@ -6188,7 +6304,7 @@ function accountFrom(options, keys, collected, at2) {
|
|
|
6188
6304
|
|
|
6189
6305
|
// src/commands/login.command.ts
|
|
6190
6306
|
var DEFAULT_BASE_URL = "https://api.memnox.com";
|
|
6191
|
-
function registerLoginCommand(program, context, home =
|
|
6307
|
+
function registerLoginCommand(program, context, home = homedir36, seams = {}) {
|
|
6192
6308
|
program.command("login").description("Connect this machine to a workspace, so it gets your rules").option("--url <base>", "the control plane", DEFAULT_BASE_URL).option("--enforce", "start in enforce rather than observe").option("--no-open", "print the URL instead of opening a browser").action(async (options) => {
|
|
6193
6309
|
const { out, style } = context;
|
|
6194
6310
|
const enrolment = {
|
|
@@ -6297,7 +6413,7 @@ function openBrowser(url) {
|
|
|
6297
6413
|
}
|
|
6298
6414
|
|
|
6299
6415
|
// src/commands/spend.command.ts
|
|
6300
|
-
import { homedir as
|
|
6416
|
+
import { homedir as homedir37 } from "os";
|
|
6301
6417
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
6302
6418
|
import {
|
|
6303
6419
|
DECISION_EFFECT as DECISION_EFFECT10,
|
|
@@ -6308,7 +6424,7 @@ import {
|
|
|
6308
6424
|
validateEvent
|
|
6309
6425
|
} from "@memnox/core";
|
|
6310
6426
|
var DEFAULT_OPERATION = "llm.completion";
|
|
6311
|
-
function registerSpendCommand(program, context, home =
|
|
6427
|
+
function registerSpendCommand(program, context, home = homedir37, now = () => /* @__PURE__ */ new Date(), newId = randomUUID2) {
|
|
6312
6428
|
program.command("spend <usd>").description("Record what an agent spent, so budgets and the breaker can see it").option("-s, --session <id>", `session it belongs to (default: $${SESSION_VAR4})`).option("-a, --agent <name>", "agent that spent it", "agent").option(
|
|
6313
6429
|
"-f, --for <action>",
|
|
6314
6430
|
"what it was spent on, in the action grammar rules use",
|
|
@@ -6354,9 +6470,9 @@ function registerSpendCommand(program, context, home = homedir36, now = () => /*
|
|
|
6354
6470
|
}
|
|
6355
6471
|
|
|
6356
6472
|
// src/commands/sync.command.ts
|
|
6357
|
-
import { homedir as
|
|
6473
|
+
import { homedir as homedir38 } from "os";
|
|
6358
6474
|
import { readAccount as readAccount4 } from "@memnox/core";
|
|
6359
|
-
function registerSyncCommand(program, context, home =
|
|
6475
|
+
function registerSyncCommand(program, context, home = homedir38) {
|
|
6360
6476
|
const sync = program.command("sync").description("Pull the rules this workspace publishes, and send what happened");
|
|
6361
6477
|
sync.command("now", { isDefault: true }).description("Do a pass now rather than waiting for the next heartbeat").option("--json", "machine-readable output").action(async (options) => {
|
|
6362
6478
|
if (await readAccount4(home()) === null) {
|
|
@@ -6451,6 +6567,10 @@ function reportPush(context, result) {
|
|
|
6451
6567
|
// src/program.ts
|
|
6452
6568
|
function buildProgram(context) {
|
|
6453
6569
|
const program = new Command().name("memnox").description("Memnox \u2014 the execution trust layer for AI agents").version(CLI_VERSION);
|
|
6570
|
+
program.hook("preAction", (_program, command) => {
|
|
6571
|
+
if (command.opts()["json"] === true) return;
|
|
6572
|
+
masthead(context.out, context.style);
|
|
6573
|
+
});
|
|
6454
6574
|
registerScanCommand(program, context);
|
|
6455
6575
|
registerDiffCommand(program, context);
|
|
6456
6576
|
registerExplainCommand(program, context);
|