@mnemom/mnemom 0.7.0

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.
Files changed (47) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +123 -0
  3. package/dist/commands/agents.d.ts +15 -0
  4. package/dist/commands/agents.js +303 -0
  5. package/dist/commands/auth.d.ts +5 -0
  6. package/dist/commands/auth.js +60 -0
  7. package/dist/commands/card.d.ts +23 -0
  8. package/dist/commands/card.js +460 -0
  9. package/dist/commands/claim.d.ts +1 -0
  10. package/dist/commands/claim.js +72 -0
  11. package/dist/commands/init.d.ts +7 -0
  12. package/dist/commands/init.js +763 -0
  13. package/dist/commands/integrity.d.ts +1 -0
  14. package/dist/commands/integrity.js +49 -0
  15. package/dist/commands/license.d.ts +3 -0
  16. package/dist/commands/license.js +163 -0
  17. package/dist/commands/logs.d.ts +5 -0
  18. package/dist/commands/logs.js +73 -0
  19. package/dist/commands/migrate-config.d.ts +2 -0
  20. package/dist/commands/migrate-config.js +72 -0
  21. package/dist/commands/policy.d.ts +31 -0
  22. package/dist/commands/policy.js +543 -0
  23. package/dist/commands/register.d.ts +6 -0
  24. package/dist/commands/register.js +362 -0
  25. package/dist/commands/status.d.ts +1 -0
  26. package/dist/commands/status.js +383 -0
  27. package/dist/index.d.ts +2 -0
  28. package/dist/index.js +381 -0
  29. package/dist/lib/api.d.ts +133 -0
  30. package/dist/lib/api.js +207 -0
  31. package/dist/lib/auth.d.ts +60 -0
  32. package/dist/lib/auth.js +281 -0
  33. package/dist/lib/config.d.ts +105 -0
  34. package/dist/lib/config.js +253 -0
  35. package/dist/lib/format.d.ts +35 -0
  36. package/dist/lib/format.js +60 -0
  37. package/dist/lib/model-cache.d.ts +16 -0
  38. package/dist/lib/model-cache.js +138 -0
  39. package/dist/lib/models.d.ts +41 -0
  40. package/dist/lib/models.js +357 -0
  41. package/dist/lib/openclaw.d.ts +221 -0
  42. package/dist/lib/openclaw.js +474 -0
  43. package/dist/lib/prompt.d.ts +26 -0
  44. package/dist/lib/prompt.js +150 -0
  45. package/dist/smoltbot-shim.d.ts +2 -0
  46. package/dist/smoltbot-shim.js +7 -0
  47. package/package.json +61 -0
@@ -0,0 +1,543 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { requireAgent } from "../lib/config.js";
4
+ import { fmt } from "../lib/format.js";
5
+ import { askYesNo, isInteractive } from "../lib/prompt.js";
6
+ import { getPolicy, publishPolicy } from "../lib/api.js";
7
+ import { requireAuth } from "../lib/auth.js";
8
+ import { validatePolicySchema, evaluatePolicy, } from "@mnemom/policy-engine";
9
+ // ============================================================================
10
+ // YAML parsing (minimal — parse structured YAML without external dep)
11
+ // ============================================================================
12
+ /**
13
+ * Parse a policy file (JSON or YAML).
14
+ * For YAML, we use a minimal parser that handles the policy schema.
15
+ */
16
+ function parsePolicyFile(raw, filePath) {
17
+ // Try JSON first
18
+ try {
19
+ return JSON.parse(raw);
20
+ }
21
+ catch {
22
+ // Not JSON — try YAML via JSON conversion (user must install js-yaml or use JSON)
23
+ try {
24
+ // Dynamic import would be async; for simplicity, require JSON for now
25
+ // and provide clear error message
26
+ throw new Error("not json");
27
+ }
28
+ catch {
29
+ throw new Error(`Failed to parse ${path.basename(filePath)}. ` +
30
+ `Policy files must be valid JSON. ` +
31
+ `YAML support coming soon — convert with: npx js-yaml ${filePath}`);
32
+ }
33
+ }
34
+ }
35
+ // ============================================================================
36
+ // Policy display
37
+ // ============================================================================
38
+ function displayPolicy(policy) {
39
+ const p = policy.policy_json;
40
+ console.log(fmt.header("Policy"));
41
+ console.log();
42
+ console.log(fmt.label(" Policy ID:", ` ${policy.id}`));
43
+ console.log(fmt.label(" Name: ", ` ${policy.name}`));
44
+ if (policy.description) {
45
+ console.log(fmt.label(" Desc: ", ` ${policy.description}`));
46
+ }
47
+ console.log(fmt.label(" Version: ", ` ${policy.version}`));
48
+ console.log(fmt.label(" Created: ", ` ${policy.created_at}`));
49
+ // Capability mappings
50
+ const mappings = p.capability_mappings;
51
+ if (mappings && Object.keys(mappings).length > 0) {
52
+ console.log(fmt.section("Capability Mappings"));
53
+ console.log();
54
+ for (const [name, mapping] of Object.entries(mappings)) {
55
+ console.log(` ${fmt.success(name)}`);
56
+ if (mapping.description) {
57
+ console.log(` ${mapping.description}`);
58
+ }
59
+ console.log(` Tools: ${mapping.tools.join(", ")}`);
60
+ console.log(` Card actions: ${mapping.card_actions.join(", ")}`);
61
+ }
62
+ }
63
+ // Forbidden rules
64
+ const forbidden = p.forbidden;
65
+ if (Array.isArray(forbidden) && forbidden.length > 0) {
66
+ console.log(fmt.section("Forbidden Rules"));
67
+ console.log();
68
+ for (const rule of forbidden) {
69
+ console.log(` ${fmt.error(`${rule.pattern} [${rule.severity}]`)}`);
70
+ console.log(` ${rule.reason}`);
71
+ }
72
+ }
73
+ // Escalation triggers
74
+ const triggers = p.escalation_triggers;
75
+ if (Array.isArray(triggers) && triggers.length > 0) {
76
+ console.log(fmt.section("Escalation Triggers"));
77
+ console.log();
78
+ for (const trigger of triggers) {
79
+ console.log(` ${fmt.warn(`${trigger.condition} -> ${trigger.action}`)}`);
80
+ console.log(` ${trigger.reason}`);
81
+ }
82
+ }
83
+ // Defaults
84
+ const defaults = p.defaults;
85
+ if (defaults) {
86
+ console.log(fmt.section("Defaults"));
87
+ console.log();
88
+ console.log(fmt.label(" Unmapped action: ", defaults.unmapped_tool_action));
89
+ console.log(fmt.label(" Unmapped severity:", ` ${defaults.unmapped_severity}`));
90
+ console.log(fmt.label(" Fail open: ", ` ${defaults.fail_open}`));
91
+ }
92
+ console.log();
93
+ }
94
+ // ============================================================================
95
+ // Subcommands
96
+ // ============================================================================
97
+ /**
98
+ * smoltbot policy init — scaffold a policy.json with commented examples
99
+ */
100
+ export async function policyInitCommand() {
101
+ const outputPath = path.resolve("policy.json");
102
+ if (fs.existsSync(outputPath)) {
103
+ console.log("\n" + fmt.warn("policy.json already exists in current directory") + "\n");
104
+ if (isInteractive()) {
105
+ const overwrite = await askYesNo("Overwrite?", false);
106
+ if (!overwrite) {
107
+ console.log("\nCancelled.\n");
108
+ return;
109
+ }
110
+ }
111
+ else {
112
+ process.exit(1);
113
+ }
114
+ }
115
+ const scaffold = {
116
+ meta: {
117
+ schema_version: "1.0",
118
+ name: "my-policy",
119
+ description: "Policy for my agent",
120
+ scope: "agent",
121
+ },
122
+ capability_mappings: {
123
+ web_fetch: {
124
+ description: "Web browsing and fetching",
125
+ tools: [
126
+ "WebFetch",
127
+ "WebSearch",
128
+ "mcp__browser__*",
129
+ "mcp__chrome-devtools__navigate_page",
130
+ ],
131
+ card_actions: ["web_fetch", "web_browse"],
132
+ },
133
+ file_system: {
134
+ description: "File system operations",
135
+ tools: ["Read", "Write", "Edit", "Glob"],
136
+ card_actions: ["file_read", "file_write"],
137
+ },
138
+ code_execution: {
139
+ description: "Code and shell execution",
140
+ tools: ["Bash", "mcp__*__evaluate_script", "mcp__*__execute*"],
141
+ card_actions: ["code_execution"],
142
+ },
143
+ },
144
+ forbidden: [
145
+ {
146
+ pattern: "mcp__*__delete*",
147
+ reason: "Destructive deletion forbidden",
148
+ severity: "critical",
149
+ },
150
+ ],
151
+ escalation_triggers: [
152
+ {
153
+ condition: "tool_matches('*payment*')",
154
+ action: "escalate",
155
+ reason: "Payment tools require human approval",
156
+ },
157
+ ],
158
+ defaults: {
159
+ unmapped_tool_action: "warn",
160
+ unmapped_severity: "medium",
161
+ fail_open: true,
162
+ },
163
+ };
164
+ const tmpPath = `${outputPath}.${process.pid}.tmp`;
165
+ fs.writeFileSync(tmpPath, JSON.stringify(scaffold, null, 2) + "\n");
166
+ fs.renameSync(tmpPath, outputPath);
167
+ console.log(fmt.success(`Created policy.json`));
168
+ console.log(fmt.label(" Path:", ` ${outputPath}`));
169
+ console.log("\nEdit the file, then validate with:\n");
170
+ console.log(" smoltbot policy validate policy.json\n");
171
+ }
172
+ /**
173
+ * smoltbot policy validate <file> — local-only validation
174
+ */
175
+ export async function policyValidateCommand(file) {
176
+ const filePath = path.resolve(file);
177
+ if (!fs.existsSync(filePath)) {
178
+ console.log("\n" + fmt.error(`File not found: ${filePath}`) + "\n");
179
+ process.exit(1);
180
+ }
181
+ let raw;
182
+ try {
183
+ raw = fs.readFileSync(filePath, "utf-8");
184
+ }
185
+ catch (e) {
186
+ const msg = e instanceof Error ? e.message : String(e);
187
+ console.log("\n" + fmt.error(`Could not read file: ${msg}`) + "\n");
188
+ process.exit(1);
189
+ }
190
+ let parsed;
191
+ try {
192
+ parsed = parsePolicyFile(raw, filePath);
193
+ }
194
+ catch (e) {
195
+ const msg = e instanceof Error ? e.message : String(e);
196
+ console.log("\n" + fmt.error(msg) + "\n");
197
+ process.exit(1);
198
+ }
199
+ const result = validatePolicySchema(parsed);
200
+ console.log(fmt.header("Policy Validation Report"));
201
+ console.log();
202
+ console.log(fmt.label(" File:", ` ${filePath}`));
203
+ console.log();
204
+ if (result.valid) {
205
+ console.log(fmt.success("Policy schema is valid"));
206
+ const meta = parsed.meta;
207
+ if (meta) {
208
+ console.log(fmt.label(" Name: ", ` ${meta.name}`));
209
+ console.log(fmt.label(" Scope:", ` ${meta.scope}`));
210
+ }
211
+ const mappings = parsed.capability_mappings;
212
+ if (mappings) {
213
+ console.log(fmt.label(" Capabilities:", ` ${Object.keys(mappings).length}`));
214
+ }
215
+ }
216
+ else {
217
+ for (const error of result.errors) {
218
+ console.log(fmt.error(`${error.path}: ${error.message}`));
219
+ }
220
+ console.log();
221
+ console.log(fmt.error(`${result.errors.length} validation error(s)`) + "\n");
222
+ process.exit(1);
223
+ }
224
+ console.log();
225
+ }
226
+ /**
227
+ * smoltbot policy publish <file> — validate + upload to API
228
+ */
229
+ export async function policyPublishCommand(file, agentName) {
230
+ const agent = await requireAgent(agentName);
231
+ const filePath = path.resolve(file);
232
+ if (!fs.existsSync(filePath)) {
233
+ console.log("\n" + fmt.error(`File not found: ${filePath}`) + "\n");
234
+ process.exit(1);
235
+ }
236
+ let raw;
237
+ try {
238
+ raw = fs.readFileSync(filePath, "utf-8");
239
+ }
240
+ catch (e) {
241
+ const msg = e instanceof Error ? e.message : String(e);
242
+ console.log("\n" + fmt.error(`Could not read file: ${msg}`) + "\n");
243
+ process.exit(1);
244
+ }
245
+ let parsed;
246
+ try {
247
+ parsed = parsePolicyFile(raw, filePath);
248
+ }
249
+ catch (e) {
250
+ const msg = e instanceof Error ? e.message : String(e);
251
+ console.log("\n" + fmt.error(msg) + "\n");
252
+ process.exit(1);
253
+ }
254
+ // Validate locally first
255
+ const validation = validatePolicySchema(parsed);
256
+ console.log(fmt.header("Policy Validation"));
257
+ console.log();
258
+ if (!validation.valid) {
259
+ for (const error of validation.errors) {
260
+ console.log(fmt.error(`${error.path}: ${error.message}`));
261
+ }
262
+ console.log();
263
+ console.log(fmt.error("Validation failed. Fix the errors above before publishing.") + "\n");
264
+ process.exit(1);
265
+ }
266
+ console.log(fmt.success("Policy schema is valid"));
267
+ console.log();
268
+ // Require authentication
269
+ await requireAuth();
270
+ // Confirm
271
+ if (isInteractive()) {
272
+ const meta = parsed.meta;
273
+ const confirm = await askYesNo(`Publish policy "${meta?.name || "unnamed"}" for agent ${agent.agentId}?`, false);
274
+ if (!confirm) {
275
+ console.log("\nPublish cancelled.\n");
276
+ return;
277
+ }
278
+ }
279
+ // Publish
280
+ try {
281
+ console.log("\nPublishing policy...");
282
+ const result = await publishPolicy(agent.agentId, parsed);
283
+ console.log(fmt.success("Policy published successfully!"));
284
+ console.log(fmt.label(" Policy ID:", ` ${result.id}`));
285
+ console.log(fmt.label(" Version: ", ` ${result.version}`) + "\n");
286
+ }
287
+ catch (error) {
288
+ const message = error instanceof Error ? error.message : String(error);
289
+ console.log("\n" + fmt.error(`Failed to publish policy: ${message}`) + "\n");
290
+ process.exit(1);
291
+ }
292
+ }
293
+ /**
294
+ * smoltbot policy list — list active policies for current agent
295
+ */
296
+ export async function policyListCommand(agentName) {
297
+ const agent = await requireAgent(agentName);
298
+ console.log("\nFetching policy...\n");
299
+ try {
300
+ const response = await getPolicy(agent.agentId);
301
+ if (!response || !response.policy) {
302
+ console.log(fmt.warn("No active policy"));
303
+ console.log("\nCreate a policy with:\n");
304
+ console.log(" smoltbot policy init");
305
+ console.log(" smoltbot policy publish policy.json\n");
306
+ return;
307
+ }
308
+ displayPolicy(response.policy);
309
+ }
310
+ catch (error) {
311
+ const message = error instanceof Error ? error.message : String(error);
312
+ console.log("\n" + fmt.error(`Failed to fetch policy: ${message}`) + "\n");
313
+ process.exit(1);
314
+ }
315
+ }
316
+ /**
317
+ * smoltbot policy test <file> --against-traces — dry-run against historical traces
318
+ */
319
+ export async function policyTestCommand(file, agentName) {
320
+ const agent = await requireAgent(agentName);
321
+ const filePath = path.resolve(file);
322
+ if (!fs.existsSync(filePath)) {
323
+ console.log("\n" + fmt.error(`File not found: ${filePath}`) + "\n");
324
+ process.exit(1);
325
+ }
326
+ let raw;
327
+ try {
328
+ raw = fs.readFileSync(filePath, "utf-8");
329
+ }
330
+ catch (e) {
331
+ const msg = e instanceof Error ? e.message : String(e);
332
+ console.log("\n" + fmt.error(`Could not read file: ${msg}`) + "\n");
333
+ process.exit(1);
334
+ }
335
+ let parsed;
336
+ try {
337
+ parsed = parsePolicyFile(raw, filePath);
338
+ }
339
+ catch (e) {
340
+ const msg = e instanceof Error ? e.message : String(e);
341
+ console.log("\n" + fmt.error(msg) + "\n");
342
+ process.exit(1);
343
+ }
344
+ // Validate first
345
+ const validation = validatePolicySchema(parsed);
346
+ if (!validation.valid) {
347
+ console.log(fmt.error("Policy validation failed:"));
348
+ for (const error of validation.errors) {
349
+ console.log(fmt.error(` ${error.path}: ${error.message}`));
350
+ }
351
+ console.log();
352
+ process.exit(1);
353
+ }
354
+ // Require authentication
355
+ await requireAuth();
356
+ console.log(`\nTesting policy against historical traces for agent ${agent.agentId}...\n`);
357
+ try {
358
+ const { testPolicyHistorical } = await import("../lib/api.js");
359
+ const result = await testPolicyHistorical(agent.agentId, parsed);
360
+ console.log(fmt.header("Policy Test Results"));
361
+ console.log();
362
+ console.log(fmt.label(" Policy: ", ` ${result.policy_name}`));
363
+ console.log(fmt.label(" Total traces:", ` ${result.total_traces}`));
364
+ console.log();
365
+ const s = result.summary;
366
+ if (s.pass > 0)
367
+ console.log(fmt.success(`${s.pass} passed`));
368
+ if (s.warn > 0)
369
+ console.log(fmt.warn(`${s.warn} warnings`));
370
+ if (s.fail > 0)
371
+ console.log(fmt.error(`${s.fail} failed`));
372
+ if (s.skipped > 0)
373
+ console.log(fmt.label(" Skipped:", ` ${s.skipped} (no tools)`));
374
+ // Show first few failures
375
+ const failures = result.results.filter((r) => r.verdict === "fail");
376
+ if (failures.length > 0) {
377
+ console.log(fmt.section("Failed Traces (first 5)"));
378
+ console.log();
379
+ for (const f of failures.slice(0, 5)) {
380
+ console.log(fmt.error(` ${f.trace_id}`));
381
+ for (const v of f.violations ?? []) {
382
+ console.log(` ${v.type}: ${v.tool} — ${v.reason}`);
383
+ }
384
+ }
385
+ }
386
+ console.log();
387
+ }
388
+ catch (error) {
389
+ const message = error instanceof Error ? error.message : String(error);
390
+ console.log("\n" + fmt.error(`Failed to test policy: ${message}`) + "\n");
391
+ process.exit(1);
392
+ }
393
+ }
394
+ /**
395
+ * smoltbot policy evaluate <policy-file> --card <card-file> --tools <tools> — local CI/CD evaluation
396
+ *
397
+ * Runs entirely locally using the embedded policy engine. No API key needed.
398
+ */
399
+ export async function policyEvaluateCommand(file, options) {
400
+ // 1. Read + validate policy file
401
+ const policyPath = path.resolve(file);
402
+ if (!fs.existsSync(policyPath)) {
403
+ console.log("\n" + fmt.error(`Policy file not found: ${policyPath}`) + "\n");
404
+ process.exit(1);
405
+ }
406
+ let policyRaw;
407
+ try {
408
+ policyRaw = fs.readFileSync(policyPath, "utf-8");
409
+ }
410
+ catch (e) {
411
+ const msg = e instanceof Error ? e.message : String(e);
412
+ console.log("\n" + fmt.error(`Could not read policy file: ${msg}`) + "\n");
413
+ process.exit(1);
414
+ }
415
+ let policyParsed;
416
+ try {
417
+ policyParsed = parsePolicyFile(policyRaw, policyPath);
418
+ }
419
+ catch (e) {
420
+ const msg = e instanceof Error ? e.message : String(e);
421
+ console.log("\n" + fmt.error(msg) + "\n");
422
+ process.exit(1);
423
+ }
424
+ const validation = validatePolicySchema(policyParsed);
425
+ if (!validation.valid) {
426
+ console.log(fmt.error("Policy validation failed:"));
427
+ for (const error of validation.errors) {
428
+ console.log(fmt.error(` ${error.path}: ${error.message}`));
429
+ }
430
+ console.log();
431
+ process.exit(1);
432
+ }
433
+ // 2. Read + parse card file
434
+ let cardContent = {};
435
+ if (options.card) {
436
+ const cardPath = path.resolve(options.card);
437
+ if (!fs.existsSync(cardPath)) {
438
+ console.log("\n" + fmt.error(`Card file not found: ${cardPath}`) + "\n");
439
+ process.exit(1);
440
+ }
441
+ try {
442
+ const cardRaw = fs.readFileSync(cardPath, "utf-8");
443
+ cardContent = JSON.parse(cardRaw);
444
+ }
445
+ catch (e) {
446
+ const msg = e instanceof Error ? e.message : String(e);
447
+ console.log("\n" + fmt.error(`Could not read card file: ${msg}`) + "\n");
448
+ process.exit(1);
449
+ }
450
+ }
451
+ // 3. Parse tool list from --tools or --tool-manifest
452
+ let tools = [];
453
+ if (options.tools) {
454
+ tools = options.tools.split(",").map((t) => ({ name: t.trim() })).filter((t) => t.name);
455
+ }
456
+ else if (options.toolManifest) {
457
+ const manifestPath = path.resolve(options.toolManifest);
458
+ if (!fs.existsSync(manifestPath)) {
459
+ console.log("\n" + fmt.error(`Tool manifest file not found: ${manifestPath}`) + "\n");
460
+ process.exit(1);
461
+ }
462
+ try {
463
+ const manifestRaw = fs.readFileSync(manifestPath, "utf-8");
464
+ const manifest = JSON.parse(manifestRaw);
465
+ if (Array.isArray(manifest)) {
466
+ tools = manifest.map((t) => typeof t === "string" ? { name: t } : { name: t.name });
467
+ }
468
+ }
469
+ catch (e) {
470
+ const msg = e instanceof Error ? e.message : String(e);
471
+ console.log("\n" + fmt.error(`Could not read tool manifest: ${msg}`) + "\n");
472
+ process.exit(1);
473
+ }
474
+ }
475
+ if (tools.length === 0) {
476
+ console.log("\n" + fmt.error("No tools specified. Use --tools or --tool-manifest") + "\n");
477
+ process.exit(1);
478
+ }
479
+ // 4. Run evaluation
480
+ const result = evaluatePolicy({
481
+ context: "cicd",
482
+ policy: policyParsed,
483
+ card: cardContent,
484
+ tools,
485
+ });
486
+ // 5. Display results
487
+ console.log(fmt.header("Policy Evaluation Report"));
488
+ console.log();
489
+ console.log(fmt.label(" Policy:", ` ${policyParsed.meta?.name ?? "unknown"}`));
490
+ console.log(fmt.label(" Context:", " cicd"));
491
+ console.log(fmt.label(" Tools:", ` ${tools.length} (${tools.map((t) => t.name).join(", ")})`));
492
+ console.log();
493
+ // Verdict
494
+ if (result.verdict === "pass") {
495
+ console.log(fmt.success("PASS — all tools comply with policy"));
496
+ }
497
+ else if (result.verdict === "warn") {
498
+ console.log(fmt.warn("WARN — policy warnings detected"));
499
+ }
500
+ else {
501
+ console.log(fmt.error("FAIL — policy violations detected"));
502
+ }
503
+ console.log();
504
+ // Violations
505
+ if (result.violations.length > 0) {
506
+ console.log(fmt.section("Violations"));
507
+ console.log();
508
+ for (const v of result.violations) {
509
+ console.log(fmt.error(` ${v.tool} [${v.severity}] — ${v.type}`));
510
+ console.log(` ${v.reason}`);
511
+ }
512
+ }
513
+ // Warnings
514
+ if (result.warnings.length > 0) {
515
+ console.log(fmt.section("Warnings"));
516
+ console.log();
517
+ for (const w of result.warnings) {
518
+ console.log(fmt.warn(` ${w.tool} — ${w.type}`));
519
+ console.log(` ${w.reason}`);
520
+ }
521
+ }
522
+ // Coverage
523
+ const cov = result.coverage;
524
+ console.log(fmt.section("Coverage"));
525
+ console.log();
526
+ console.log(fmt.label(" Card actions:", ` ${cov.total_card_actions}`));
527
+ console.log(fmt.label(" Mapped: ", ` ${cov.mapped_card_actions.length}`));
528
+ console.log(fmt.label(" Unmapped: ", ` ${cov.unmapped_card_actions.length}`));
529
+ console.log(fmt.label(" Coverage: ", ` ${cov.coverage_pct.toFixed(1)}%`));
530
+ if (cov.unmapped_card_actions.length > 0) {
531
+ console.log(fmt.label(" Unmapped list:", ` ${cov.unmapped_card_actions.join(", ")}`));
532
+ }
533
+ console.log();
534
+ console.log(fmt.label(" Duration:", ` ${result.duration_ms}ms`));
535
+ console.log();
536
+ // 6. Exit code
537
+ if (result.verdict === "fail") {
538
+ process.exit(1);
539
+ }
540
+ if (options.strict && result.verdict === "warn") {
541
+ process.exit(1);
542
+ }
543
+ }
@@ -0,0 +1,6 @@
1
+ export interface RegisterOptions {
2
+ openclaw?: boolean;
3
+ standalone?: boolean;
4
+ setDefault?: boolean;
5
+ }
6
+ export declare function registerCommand(name: string, options?: RegisterOptions): Promise<void>;