@mnemom/mnemom 0.11.0 → 0.12.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.
@@ -11,27 +11,53 @@ const PROTECTION_MODES = ["off", "observe", "nudge", "enforce"];
11
11
  const SURFACE_KEYS = ["incoming", "outgoing", "tool_calls", "tool_responses"];
12
12
  // Per ADR-037 Decision 4: deny public LLM endpoints + public DNS providers,
13
13
  // and the any-host CIDRs, at write time.
14
+ // T8-4 (2026-05-19) extended the corpus per
15
+ // `safe-house-hardening/audit/deny-list-completeness.md`: added emerging
16
+ // LLM providers (Replicate, Together, Anyscale, Fireworks, xAI,
17
+ // Perplexity, DeepSeek) and three reserved IP ranges (link-local v4/v6
18
+ // + multicast v4).
19
+ // Mirror lives at `mnemom-api/src/composition/validate.ts` —
20
+ // must stay in sync; T8-4 F3 (filed) extracts to a shared package.
14
21
  const DENY_DOMAINS = new Set([
22
+ // OpenAI + Anthropic + Google
15
23
  "api.openai.com",
16
24
  "api.anthropic.com",
17
25
  "generativelanguage.googleapis.com",
26
+ "cloud.google.com",
27
+ // Other model-API providers (T8-4 additions 2026-05-19)
18
28
  "api.cohere.ai",
19
29
  "api.mistral.ai",
20
30
  "api.groq.com",
21
- "cloud.google.com",
31
+ "replicate.com",
32
+ "api.replicate.com",
33
+ "api.together.xyz",
34
+ "api.together.ai",
35
+ "api.endpoints.anyscale.com",
36
+ "api.fireworks.ai",
37
+ "api.x.ai",
38
+ "api.perplexity.ai",
39
+ "api.deepseek.com",
40
+ // Public DNS providers
22
41
  "dns.google",
23
42
  "cloudflare-dns.com",
24
43
  "one.one.one.one",
25
44
  "dns.quad9.net",
26
45
  ]);
27
46
  const DENY_IP_PREFIXES = [
47
+ // Any-host
28
48
  "0.0.0.0/0",
29
49
  "::/0",
50
+ // Public DNS provider /24s
30
51
  "8.8.8.0/24",
31
52
  "8.8.4.0/24",
32
53
  "1.1.1.0/24",
33
54
  "1.0.0.0/24",
34
55
  "9.9.9.0/24",
56
+ // T8-4 additions 2026-05-19: reserved ranges with no legitimate
57
+ // trusted-source semantics. Link-local + multicast.
58
+ "169.254.0.0/16", // link-local IPv4 (RFC 3927) — SSRF/imds vector
59
+ "fe80::/10", // link-local IPv6 (RFC 4291)
60
+ "224.0.0.0/4", // multicast IPv4 (RFC 5771) — never legitimate trust target
35
61
  ];
36
62
  const DOMAIN_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+(:\d{1,5})?$/i;
37
63
  const AGENT_ID_RE = /^mnm-[a-z0-9-]{4,}$/i;
@@ -41,6 +67,12 @@ function isObject(v) {
41
67
  }
42
68
  function validateDomain(d) {
43
69
  const lower = d.toLowerCase();
70
+ // T8-4: explicit wildcard rejection. DOMAIN_RE already implicitly rejects
71
+ // a sole '*' (no dot), but `*.example.com` shapes also need an explicit
72
+ // guard — operator footgun if we ever loosened DOMAIN_RE.
73
+ if (lower.includes("*")) {
74
+ return "wildcards are not permitted in trusted_sources.domains; enumerate the specific hosts";
75
+ }
44
76
  if (DENY_DOMAINS.has(lower.split(":")[0])) {
45
77
  return "domain is on the static deny-list (public LLM/DNS endpoint)";
46
78
  }
@@ -191,7 +223,9 @@ export function validateProtectionCard(card) {
191
223
  });
192
224
  bad = true;
193
225
  }
194
- if (nums.quarantine !== undefined && nums.block !== undefined && nums.quarantine > nums.block) {
226
+ if (nums.quarantine !== undefined &&
227
+ nums.block !== undefined &&
228
+ nums.quarantine > nums.block) {
195
229
  checks.push({
196
230
  name: "thresholds.quarantine",
197
231
  passed: false,
@@ -214,11 +248,15 @@ export function validateProtectionCard(card) {
214
248
  checks.push({
215
249
  name: "screen_surfaces",
216
250
  passed: false,
217
- message: 'Must be an object of booleans, not an array. Per ADR-037 use { incoming: true, outgoing: true, tool_calls: true, tool_responses: true }.',
251
+ message: "Must be an object of booleans, not an array. Per ADR-037 use { incoming: true, outgoing: true, tool_calls: true, tool_responses: true }.",
218
252
  });
219
253
  }
220
254
  else if (!isObject(card.screen_surfaces)) {
221
- checks.push({ name: "screen_surfaces", passed: false, message: "Must be an object if present" });
255
+ checks.push({
256
+ name: "screen_surfaces",
257
+ passed: false,
258
+ message: "Must be an object if present",
259
+ });
222
260
  }
223
261
  else {
224
262
  const s = card.screen_surfaces;
@@ -245,7 +283,7 @@ export function validateProtectionCard(card) {
245
283
  }
246
284
  }
247
285
  if (!bad) {
248
- const enabled = SURFACE_KEYS.filter(k => s[k] === true).length;
286
+ const enabled = SURFACE_KEYS.filter((k) => s[k] === true).length;
249
287
  checks.push({
250
288
  name: "screen_surfaces",
251
289
  passed: true,
@@ -260,11 +298,15 @@ export function validateProtectionCard(card) {
260
298
  checks.push({
261
299
  name: "trusted_sources",
262
300
  passed: false,
263
- message: 'Must be an object of typed buckets, not an array. Per ADR-037 use { domains: [...], agent_ids: [...], ip_ranges: [...] } — the legacy [{pattern, ...}] shape is no longer accepted.',
301
+ message: "Must be an object of typed buckets, not an array. Per ADR-037 use { domains: [...], agent_ids: [...], ip_ranges: [...] } — the legacy [{pattern, ...}] shape is no longer accepted.",
264
302
  });
265
303
  }
266
304
  else if (!isObject(card.trusted_sources)) {
267
- checks.push({ name: "trusted_sources", passed: false, message: "Must be an object if present" });
305
+ checks.push({
306
+ name: "trusted_sources",
307
+ passed: false,
308
+ message: "Must be an object if present",
309
+ });
268
310
  }
269
311
  else {
270
312
  const ts = card.trusted_sources;
@@ -290,7 +332,7 @@ export function validateProtectionCard(card) {
290
332
  function parseProtectionFile(filePath) {
291
333
  const raw = fs.readFileSync(filePath, "utf-8");
292
334
  const ext = path.extname(filePath).toLowerCase();
293
- const format = (ext === ".yaml" || ext === ".yml") ? "yaml" : "json";
335
+ const format = ext === ".yaml" || ext === ".yml" ? "yaml" : "json";
294
336
  if (format === "yaml") {
295
337
  const parsed = yaml.load(raw);
296
338
  if (!parsed || typeof parsed !== "object") {
@@ -450,19 +492,20 @@ export async function protectionEditCommand(agentName, options = {}) {
450
492
  if (!original) {
451
493
  console.log(fmt.warn("No protection card found. Creating a template..."));
452
494
  }
453
- const cardYaml = original || yaml.dump({
454
- card_version: "protection/2026-04-26",
455
- agent_id: agentId,
456
- mode: "observe",
457
- thresholds: { warn: 0.3, quarantine: 0.6, block: 0.9 },
458
- screen_surfaces: {
459
- incoming: true,
460
- outgoing: true,
461
- tool_calls: true,
462
- tool_responses: true,
463
- },
464
- trusted_sources: { domains: [], agent_ids: [], ip_ranges: [] },
465
- }, { lineWidth: 120, noRefs: true });
495
+ const cardYaml = original ||
496
+ yaml.dump({
497
+ card_version: "protection/2026-04-26",
498
+ agent_id: agentId,
499
+ mode: "observe",
500
+ thresholds: { warn: 0.3, quarantine: 0.6, block: 0.9 },
501
+ screen_surfaces: {
502
+ incoming: true,
503
+ outgoing: true,
504
+ tool_calls: true,
505
+ tool_responses: true,
506
+ },
507
+ trusted_sources: { domains: [], agent_ids: [], ip_ranges: [] },
508
+ }, { lineWidth: 120, noRefs: true });
466
509
  const tmpDir = os.tmpdir();
467
510
  const tmpFile = path.join(tmpDir, `mnemom-protection-${agentId}.yaml`);
468
511
  fs.writeFileSync(tmpFile, cardYaml);
@@ -474,14 +517,18 @@ export async function protectionEditCommand(agentName, options = {}) {
474
517
  try {
475
518
  fs.unlinkSync(tmpFile);
476
519
  }
477
- catch { /* ignore */ }
520
+ catch {
521
+ /* ignore */
522
+ }
478
523
  process.exit(1);
479
524
  }
480
525
  const edited = fs.readFileSync(tmpFile, "utf-8");
481
526
  try {
482
527
  fs.unlinkSync(tmpFile);
483
528
  }
484
- catch { /* ignore */ }
529
+ catch {
530
+ /* ignore */
531
+ }
485
532
  if (edited === cardYaml) {
486
533
  console.log("\nNo changes made.\n");
487
534
  return;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * `mnemom recipes ...` commands — AEGIS-6b (Phase 2 Day 5).
3
+ *
4
+ * mnemom recipes report-fn <recipe-id> [--summary "..."] [--evidence "..."]
5
+ * [--agent <id>] [--checkpoint <id>]
6
+ * [--json]
7
+ * mnemom recipes report-fp <recipe-id> [--summary "..."] [--evidence "..."]
8
+ * [--agent <id>] [--checkpoint <id>]
9
+ * [--json]
10
+ *
11
+ * POSTs to /v1/recipes/:id/report (AEGIS-6a; mnemom-api#508). Authentication
12
+ * uses the same JWT/API-key resolution as every other authed CLI command.
13
+ * Idempotency-Key is generated per call by postApi(); the server folds the
14
+ * request body into the fingerprint per `feedback_idempotency_key_body_hash`.
15
+ *
16
+ * Output (human mode):
17
+ * - candidate_id of the resulting sideband_analyses row
18
+ * - status hint ("pending — admin review or auto-mode promotion will
19
+ * determine the outcome")
20
+ * - a pointer to the dashboard URL where the customer can track review
21
+ * status. The customer-facing dashboard form (AEGIS-6c) is a future
22
+ * follow-up; until it lands, the URL points to the static "report
23
+ * filed" anchor and the candidate_id is the primary track-handle.
24
+ */
25
+ interface ReportOpts {
26
+ summary?: string;
27
+ evidence?: string;
28
+ agent?: string;
29
+ checkpoint?: string;
30
+ json?: boolean;
31
+ }
32
+ export declare function recipesReportFnCommand(recipeId: string, opts: ReportOpts): Promise<void>;
33
+ export declare function recipesReportFpCommand(recipeId: string, opts: ReportOpts): Promise<void>;
34
+ export {};
@@ -0,0 +1,80 @@
1
+ /**
2
+ * `mnemom recipes ...` commands — AEGIS-6b (Phase 2 Day 5).
3
+ *
4
+ * mnemom recipes report-fn <recipe-id> [--summary "..."] [--evidence "..."]
5
+ * [--agent <id>] [--checkpoint <id>]
6
+ * [--json]
7
+ * mnemom recipes report-fp <recipe-id> [--summary "..."] [--evidence "..."]
8
+ * [--agent <id>] [--checkpoint <id>]
9
+ * [--json]
10
+ *
11
+ * POSTs to /v1/recipes/:id/report (AEGIS-6a; mnemom-api#508). Authentication
12
+ * uses the same JWT/API-key resolution as every other authed CLI command.
13
+ * Idempotency-Key is generated per call by postApi(); the server folds the
14
+ * request body into the fingerprint per `feedback_idempotency_key_body_hash`.
15
+ *
16
+ * Output (human mode):
17
+ * - candidate_id of the resulting sideband_analyses row
18
+ * - status hint ("pending — admin review or auto-mode promotion will
19
+ * determine the outcome")
20
+ * - a pointer to the dashboard URL where the customer can track review
21
+ * status. The customer-facing dashboard form (AEGIS-6c) is a future
22
+ * follow-up; until it lands, the URL points to the static "report
23
+ * filed" anchor and the candidate_id is the primary track-handle.
24
+ */
25
+ import chalk from "chalk";
26
+ import { reportRecipeFnFp } from "../lib/api.js";
27
+ import { requireAuth } from "../lib/auth.js";
28
+ async function readSummary(opts) {
29
+ if (typeof opts.summary === "string" && opts.summary.trim()) {
30
+ return opts.summary.trim();
31
+ }
32
+ // Allow piping a longer description via stdin: `cat report.md | mnemom
33
+ // recipes report-fn rcp_abc`. The server requires a non-empty summary;
34
+ // we'd rather fail-here with a clear CLI message than send an empty
35
+ // body that 400s.
36
+ if (!process.stdin.isTTY) {
37
+ const chunks = [];
38
+ for await (const chunk of process.stdin) {
39
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
40
+ }
41
+ const piped = Buffer.concat(chunks).toString("utf8").trim();
42
+ if (piped)
43
+ return piped;
44
+ }
45
+ throw new Error('summary required: pass --summary "…" or pipe text on stdin.');
46
+ }
47
+ async function runReport(recipeId, type, opts) {
48
+ await requireAuth();
49
+ const summary = await readSummary(opts);
50
+ const input = {
51
+ type,
52
+ summary,
53
+ ...(opts.evidence ? { evidence: opts.evidence } : {}),
54
+ ...(opts.agent ? { agent_id: opts.agent } : {}),
55
+ ...(opts.checkpoint ? { checkpoint_id: opts.checkpoint } : {}),
56
+ };
57
+ const result = await reportRecipeFnFp(recipeId, input);
58
+ if (opts.json) {
59
+ process.stdout.write(JSON.stringify(result) + "\n");
60
+ return;
61
+ }
62
+ const verb = type === "fn" ? "false-negative" : "false-positive";
63
+ const label = type === "fn" ? "FN" : "FP";
64
+ process.stdout.write([
65
+ chalk.green(`✓ ${label} report filed`),
66
+ ` candidate_id: ${chalk.bold(result.candidate_id)}`,
67
+ ` related_recipe_id: ${chalk.dim(result.related_recipe_id)}`,
68
+ ` status: ${chalk.dim("pending — awaiting admin review or auto-mode promotion")}`,
69
+ "",
70
+ chalk.dim(`Track this ${verb} report under candidate id ${result.candidate_id}.`),
71
+ chalk.dim("Once the customer dashboard form (AEGIS-6c) ships, a direct URL will appear here."),
72
+ "",
73
+ ].join("\n"));
74
+ }
75
+ export async function recipesReportFnCommand(recipeId, opts) {
76
+ await runReport(recipeId, "fn", opts);
77
+ }
78
+ export async function recipesReportFpCommand(recipeId, opts) {
79
+ await runReport(recipeId, "fp", opts);
80
+ }
@@ -1,26 +1,13 @@
1
1
  import { getGatewayUrl } from "../lib/config.js";
2
2
  import { resolveAgentId, getAgent, getIntegrity, getTraces } from "../lib/api.js";
3
3
  import { isLoggedIn, getAuthInfo } from "../lib/auth.js";
4
- import { detectOpenClaw, detectProviders, getCurrentModel, getSmoltbotConfiguredProviders, PROVIDER_CONFIG_KEYS, } from "../lib/openclaw.js";
5
- import { formatModelName, detectProvider } from "../lib/models.js";
6
- import { refreshModelCache } from "../lib/model-cache.js";
7
4
  import { fmt } from "../lib/format.js";
8
5
  const DASHBOARD_URL = "https://mnemom.ai";
9
- const PROVIDER_LABELS = {
10
- anthropic: "Anthropic",
11
- openai: "OpenAI",
12
- gemini: "Gemini",
13
- };
14
- const AIP_SUPPORT = {
15
- anthropic: "Full (thinking blocks)",
16
- openai: "Via reasoning summaries",
17
- gemini: "Full (thought parts)",
18
- };
19
6
  export async function statusCommand(agentName) {
20
7
  console.log(fmt.header("mnemom status"));
21
8
  console.log();
22
9
  const checks = [];
23
- // 1. Check auth status
10
+ // 1. Auth status
24
11
  const authCheck = await checkAuthStatus();
25
12
  checks.push(authCheck);
26
13
  if (authCheck.status === "error") {
@@ -31,55 +18,25 @@ export async function statusCommand(agentName) {
31
18
  // Resolve agent ID from server
32
19
  const agentId = await resolveAgentId(agentName);
33
20
  const gatewayUrl = getGatewayUrl();
34
- // 2. Check OpenClaw configuration
35
- const openclawCheck = checkOpenClawConfig();
36
- checks.push(openclawCheck);
37
- // 3. Check configured providers
38
- const providerChecks = checkConfiguredProviders();
39
- checks.push(...providerChecks);
40
- // 4. Check current model
41
- const modelCheck = checkCurrentModel();
42
- checks.push(modelCheck);
43
- // 5. Test gateway connectivity
21
+ // 2. Gateway connectivity
44
22
  const gatewayCheck = await checkGatewayConnectivity(gatewayUrl);
45
23
  checks.push(gatewayCheck);
46
- // 6. Test API connectivity
24
+ // 3. API connectivity
47
25
  const apiCheck = await checkApiConnectivity(agentId);
48
26
  checks.push(apiCheck);
49
27
  // Print all checks
50
28
  printChecks(checks);
51
- // Show configuration details
29
+ // Configuration details
52
30
  console.log(fmt.section("Configuration"));
53
31
  console.log();
54
32
  console.log(fmt.label("Agent ID: ", agentId));
55
33
  console.log(fmt.label("Gateway: ", gatewayUrl));
56
34
  console.log(fmt.label("Dashboard:", ` ${DASHBOARD_URL}/agents/${agentId}`));
57
- // Show current model info
58
- const { fullPath, provider, modelId } = getCurrentModel();
59
- if (fullPath) {
60
- console.log(`\nCurrent Model: ${fullPath}`);
61
- if (modelId) {
62
- console.log(` (${formatModelName(modelId)})`);
63
- }
64
- if (provider && (provider === "smoltbot" || provider.startsWith("smoltbot"))) {
65
- console.log(" Status: Traced mode ACTIVE");
66
- }
67
- else {
68
- console.log(" Status: Traced mode NOT ACTIVE");
69
- if (modelId) {
70
- const detectedProvider = detectProvider(modelId);
71
- const configKey = detectedProvider ? PROVIDER_CONFIG_KEYS[detectedProvider] : "mnemom";
72
- console.log(`\n To enable: openclaw models set ${configKey}/${modelId}`);
73
- }
74
- }
75
- }
76
- // Show provider summary
77
- showProviderSummary();
78
- // Show trace summary if available
35
+ // Trace summary if API connectivity is healthy
79
36
  if (apiCheck.status === "ok") {
80
37
  await showTraceSummary(agentId);
81
38
  }
82
- // Show overall status
39
+ // Overall status
83
40
  const hasErrors = checks.some((c) => c.status === "error");
84
41
  const hasWarnings = checks.some((c) => c.status === "warning");
85
42
  if (hasErrors) {
@@ -94,8 +51,6 @@ export async function statusCommand(agentName) {
94
51
  console.log("\n" + fmt.header("Status: ALL SYSTEMS GO"));
95
52
  console.log();
96
53
  }
97
- // Refresh model cache in background (non-blocking)
98
- refreshModelCache().catch(() => { });
99
54
  }
100
55
  async function checkAuthStatus() {
101
56
  const loggedIn = await isLoggedIn();
@@ -122,124 +77,6 @@ async function checkAuthStatus() {
122
77
  message: "Authenticated via API key",
123
78
  };
124
79
  }
125
- function checkOpenClawConfig() {
126
- const detection = detectOpenClaw();
127
- if (!detection.installed) {
128
- return {
129
- name: "OpenClaw",
130
- status: "error",
131
- message: "Not installed",
132
- details: "Install from https://openclaw.ai",
133
- };
134
- }
135
- if (!detection.hasApiKey) {
136
- if (detection.isOAuth) {
137
- return {
138
- name: "OpenClaw",
139
- status: "error",
140
- message: "OAuth auth (not supported)",
141
- details: "mnemom requires API key authentication",
142
- };
143
- }
144
- // Check if any provider has a key (not just Anthropic)
145
- const providerDetection = detectProviders();
146
- const anyKey = Object.values(providerDetection.providers).some((p) => p.hasApiKey);
147
- if (anyKey) {
148
- return {
149
- name: "OpenClaw",
150
- status: "ok",
151
- message: "API key(s) found",
152
- };
153
- }
154
- return {
155
- name: "OpenClaw",
156
- status: "error",
157
- message: "No API keys configured",
158
- details: "Run `openclaw auth` to add your API key",
159
- };
160
- }
161
- if (!detection.smoltbotAlreadyConfigured) {
162
- return {
163
- name: "OpenClaw",
164
- status: "warning",
165
- message: "mnemom provider not configured",
166
- details: "Run `mnemom register <name>` to configure",
167
- };
168
- }
169
- return {
170
- name: "OpenClaw",
171
- status: "ok",
172
- message: "mnemom provider configured",
173
- };
174
- }
175
- function checkConfiguredProviders() {
176
- const results = [];
177
- const providerDetection = detectProviders();
178
- if (!providerDetection.installed)
179
- return results;
180
- const configuredProviders = getSmoltbotConfiguredProviders();
181
- for (const provider of ["anthropic", "openai", "gemini"]) {
182
- const info = providerDetection.providers[provider];
183
- const isConfigured = configuredProviders.includes(provider);
184
- if (info.hasApiKey && isConfigured) {
185
- results.push({
186
- name: `${PROVIDER_LABELS[provider]}`,
187
- status: "ok",
188
- message: `Configured (AIP: ${AIP_SUPPORT[provider]})`,
189
- });
190
- }
191
- else if (info.hasApiKey && !isConfigured) {
192
- results.push({
193
- name: `${PROVIDER_LABELS[provider]}`,
194
- status: "warning",
195
- message: "API key found but not configured",
196
- details: "Run `mnemom register <name>` to configure",
197
- });
198
- }
199
- // Don't show providers without keys (too noisy)
200
- }
201
- return results;
202
- }
203
- function checkCurrentModel() {
204
- const { fullPath, provider, modelId } = getCurrentModel();
205
- if (!fullPath) {
206
- return {
207
- name: "Current Model",
208
- status: "warning",
209
- message: "No default model set",
210
- details: "Run `openclaw models set mnemom/<model>`",
211
- };
212
- }
213
- if (provider && (provider === "smoltbot" || provider.startsWith("smoltbot"))) {
214
- return {
215
- name: "Current Model",
216
- status: "ok",
217
- message: `${modelId} (traced)`,
218
- };
219
- }
220
- return {
221
- name: "Current Model",
222
- status: "warning",
223
- message: `${fullPath} (not traced)`,
224
- details: `Switch with: openclaw models set mnemom/${modelId}`,
225
- };
226
- }
227
- function showProviderSummary() {
228
- const providerDetection = detectProviders();
229
- if (!providerDetection.installed)
230
- return;
231
- const configuredProviders = getSmoltbotConfiguredProviders();
232
- if (configuredProviders.length === 0)
233
- return;
234
- console.log(fmt.section("Configured Providers"));
235
- console.log();
236
- for (const provider of configuredProviders) {
237
- const label = PROVIDER_LABELS[provider];
238
- const aip = AIP_SUPPORT[provider];
239
- const configKey = PROVIDER_CONFIG_KEYS[provider];
240
- console.log(` ${label}: ${configKey}/* (AIP: ${aip})`);
241
- }
242
- }
243
80
  async function checkGatewayConnectivity(gatewayUrl) {
244
81
  try {
245
82
  const response = await fetch(`${gatewayUrl}/health`, {
@@ -21,7 +21,7 @@
21
21
  * grant managed via `mnemom team admin`.
22
22
  */
23
23
  import { readFileSync } from "node:fs";
24
- import { listMyTeams, getTeam, getTeamTemplate, putTeamTemplate, deleteTeamTemplate, previewComposeTeamTemplate, grantTeamAdmin, revokeTeamAdmin, listTeamAdmins, } from "../lib/api.js";
24
+ import { listMyTeams, getTeam, getTeamTemplate, putTeamTemplate, deleteTeamTemplate, previewComposeTeamTemplate, grantTeamAdmin, revokeTeamAdmin, listTeamAdmins, getTeamSidebandCoverage, } from "../lib/api.js";
25
25
  import { requireAuth } from "../lib/auth.js";
26
26
  import { fmt } from "../lib/format.js";
27
27
  // ─── mnemom team list ────────────────────────────────────────────────────
@@ -52,10 +52,7 @@ export async function teamListCommand(opts) {
52
52
  const idW = 38;
53
53
  const orgW = 22;
54
54
  const memberW = 8;
55
- const header = "Name".padEnd(nameW) +
56
- "Team ID".padEnd(idW) +
57
- "Org".padEnd(orgW) +
58
- "Members".padEnd(memberW);
55
+ const header = "Name".padEnd(nameW) + "Team ID".padEnd(idW) + "Org".padEnd(orgW) + "Members".padEnd(memberW);
59
56
  console.log(` ${header}`);
60
57
  console.log(` ${"─".repeat(nameW + idW + orgW + memberW)}`);
61
58
  for (const team of teams) {
@@ -241,7 +238,8 @@ export async function teamPreviewComposeCommand(teamId, opts) {
241
238
  // Read from stdin if no file specified.
242
239
  body = await readAllStdin();
243
240
  if (!body.trim()) {
244
- console.log(fmt.error("No template body supplied. Pass --from <file> or pipe YAML/JSON via stdin.") + "\n");
241
+ console.log(fmt.error("No template body supplied. Pass --from <file> or pipe YAML/JSON via stdin.") +
242
+ "\n");
245
243
  process.exit(1);
246
244
  return;
247
245
  }
@@ -403,9 +401,7 @@ export async function teamAdminListCommand(teamId, opts) {
403
401
  const userW = 36;
404
402
  const grantedByW = 36;
405
403
  const grantedAtW = 22;
406
- const header = "User ID".padEnd(userW) +
407
- "Granted by".padEnd(grantedByW) +
408
- "Granted at".padEnd(grantedAtW);
404
+ const header = "User ID".padEnd(userW) + "Granted by".padEnd(grantedByW) + "Granted at".padEnd(grantedAtW);
409
405
  console.log(` ${header}`);
410
406
  console.log(` ${"─".repeat(userW + grantedByW + grantedAtW)}`);
411
407
  for (const grant of result.admins) {
@@ -436,7 +432,6 @@ async function readAllStdin() {
436
432
  // last 30 days + last-swept heartbeat. Surfaces the data SOC 2 / EU AI Act
437
433
  // control mappings need to answer "show evidence the detector ran during
438
434
  // the audit window" — answers it from a single endpoint.
439
- import { getTeamSidebandCoverage, } from "../lib/api.js";
440
435
  import chalk from "chalk";
441
436
  function freshnessColor(lastSweptAt) {
442
437
  if (!lastSweptAt)
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import { fmt } from "../lib/format.js";
16
16
  import { requireAuth } from "../lib/auth.js";
17
- import { getSafeHouseHarnessState, } from "../lib/api.js";
17
+ import { getSafeHouseHarnessState } from "../lib/api.js";
18
18
  /**
19
19
  * Parse `--max-age=...` strings: 30m, 2h, 24h, 1d, etc.
20
20
  * Returns milliseconds. Defaults to 24h.
@@ -112,7 +112,8 @@ export async function validateSafeHouseCommand(opts) {
112
112
  state = await getSafeHouseHarnessState();
113
113
  }
114
114
  catch (err) {
115
- console.log(fmt.error(`Failed to fetch harness state: ${err instanceof Error ? err.message : err}`) + "\n");
115
+ console.log(fmt.error(`Failed to fetch harness state: ${err instanceof Error ? err.message : err}`) +
116
+ "\n");
116
117
  process.exit(1);
117
118
  return;
118
119
  }
@@ -123,8 +124,10 @@ export async function validateSafeHouseCommand(opts) {
123
124
  const fullVerdict = evaluateLane("full", state.full, maxAgeMs);
124
125
  const fastVerdict = evaluateLane("fast", state.fast, maxAgeMs);
125
126
  const wantedLane = opts.lane ?? "both";
126
- const verdicts = wantedLane === "full" ? [fullVerdict]
127
- : wantedLane === "fast" ? [fastVerdict]
127
+ const verdicts = wantedLane === "full"
128
+ ? [fullVerdict]
129
+ : wantedLane === "fast"
130
+ ? [fastVerdict]
128
131
  : [fullVerdict, fastVerdict];
129
132
  console.log(fmt.header(`Safe House harness — ${against}`));
130
133
  console.log("");