@mnemom/mnemom 0.12.1 → 0.14.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.
- package/dist/commands/agents.d.ts +37 -1
- package/dist/commands/agents.js +234 -9
- package/dist/commands/card.d.ts +4 -1
- package/dist/commands/card.js +134 -19
- package/dist/commands/integrity.js +7 -3
- package/dist/commands/license.js +30 -42
- package/dist/commands/logs.js +25 -9
- package/dist/commands/protection.d.ts +4 -1
- package/dist/commands/protection.js +79 -4
- package/dist/commands/status.js +12 -4
- package/dist/index.d.ts +2 -1
- package/dist/index.js +52 -13
- package/dist/lib/api.d.ts +301 -111
- package/dist/lib/api.js +450 -294
- package/dist/lib/webhooks-api.js +10 -5
- package/dist/version.d.ts +1 -0
- package/dist/version.js +16 -0
- package/package.json +7 -2
package/dist/commands/logs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveAgentId, getTraces } from "../lib/api.js";
|
|
1
|
+
import { resolveAgentId, getTraces, MnemomApiError } from "../lib/api.js";
|
|
2
2
|
import { getGatewayUrl } from "../lib/config.js";
|
|
3
3
|
import { fmt } from "../lib/format.js";
|
|
4
4
|
export async function logsCommand(options = {}) {
|
|
@@ -23,12 +23,16 @@ export async function logsCommand(options = {}) {
|
|
|
23
23
|
console.log(`Dashboard: https://mnemon.ai/dashboard/${agentId}\n`);
|
|
24
24
|
}
|
|
25
25
|
catch (error) {
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
// A 404 means the agent isn't registered yet — treat as the empty state.
|
|
27
|
+
// Branch on effectiveStatus (not status): the enforce hook rewrites an
|
|
28
|
+
// undocumented 404 to a synthetic 500 carrying spec_deviation.original_status;
|
|
29
|
+
// effectiveStatus surfaces the true status (=== status when documented).
|
|
30
|
+
if (error instanceof MnemomApiError && error.effectiveStatus === 404) {
|
|
28
31
|
console.log(fmt.header("No traces found"));
|
|
29
32
|
console.log("\nStart using Claude to generate traces.\n");
|
|
30
33
|
}
|
|
31
34
|
else {
|
|
35
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
32
36
|
console.log("\n" + fmt.error(`Failed to fetch traces: ${message}`) + "\n");
|
|
33
37
|
process.exit(1);
|
|
34
38
|
}
|
|
@@ -36,16 +40,28 @@ export async function logsCommand(options = {}) {
|
|
|
36
40
|
}
|
|
37
41
|
function displayTrace(trace) {
|
|
38
42
|
const timestamp = formatTimestamp(trace.timestamp);
|
|
39
|
-
|
|
43
|
+
// `verification` is null when the trace hasn't been verified yet — that is NOT
|
|
44
|
+
// a violation. Only flag [VIOLATION] when verification exists AND verified===false.
|
|
45
|
+
// (The old flat `trace.verified` was always undefined on the nested wire → every
|
|
46
|
+
// trace was mis-flagged [VIOLATION] and the action rendered as "[object Object]".)
|
|
47
|
+
const verified = trace.verification?.verified ?? true;
|
|
48
|
+
const statusMsg = verified ? fmt.success(timestamp) : fmt.error(`${timestamp} [VIOLATION]`);
|
|
40
49
|
console.log(`\n ${statusMsg}`);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
50
|
+
// action.name is the canonical label (the tool name lives here in the AIP-nested
|
|
51
|
+
// shape); fall back to the action type, then a dash.
|
|
52
|
+
const actionLabel = trace.action?.name ?? trace.action?.type ?? "—";
|
|
53
|
+
console.log(` ${fmt.label("Action:", ` ${actionLabel}`)}`);
|
|
54
|
+
if (trace.action?.category) {
|
|
55
|
+
console.log(` ${fmt.label("Type: ", ` ${trace.action.category}`)}`);
|
|
44
56
|
}
|
|
45
|
-
|
|
46
|
-
|
|
57
|
+
const reasoning = trace.decision?.selection_reasoning;
|
|
58
|
+
if (reasoning) {
|
|
59
|
+
const preview = truncate(reasoning, 60);
|
|
47
60
|
console.log(` ${fmt.label("Reason:", ` ${preview}`)}`);
|
|
48
61
|
}
|
|
62
|
+
if (trace.verification && trace.verification.violations.length > 0) {
|
|
63
|
+
console.log(` ${fmt.label("Issues:", ` ${trace.verification.violations.join(", ")}`)}`);
|
|
64
|
+
}
|
|
49
65
|
}
|
|
50
66
|
function formatTimestamp(iso) {
|
|
51
67
|
try {
|
|
@@ -16,7 +16,10 @@ export declare function protectionShowCommand(agentName?: string): Promise<void>
|
|
|
16
16
|
export declare function protectionPublishCommand(file: string, agentName?: string, options?: {
|
|
17
17
|
idempotencyKey?: string;
|
|
18
18
|
}): Promise<void>;
|
|
19
|
-
export declare function protectionValidateCommand(file: string
|
|
19
|
+
export declare function protectionValidateCommand(file: string, opts?: {
|
|
20
|
+
offline?: boolean;
|
|
21
|
+
agent?: string;
|
|
22
|
+
}): Promise<void>;
|
|
20
23
|
export declare function protectionEditCommand(agentName?: string, options?: {
|
|
21
24
|
idempotencyKey?: string;
|
|
22
25
|
}): Promise<void>;
|
|
@@ -3,7 +3,7 @@ import * as path from "node:path";
|
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
5
|
import yaml from "js-yaml";
|
|
6
|
-
import { PROTECTION_CARD_MAX_BYTES, getProtectionCard, putProtectionCard, resolveAgentId, } from "../lib/api.js";
|
|
6
|
+
import { PROTECTION_CARD_MAX_BYTES, getProtectionCard, putProtectionCard, resolveAgentId, getAgentByName, previewComposeAgentCard, MnemomApiError, } from "../lib/api.js";
|
|
7
7
|
import { requireAuth } from "../lib/auth.js";
|
|
8
8
|
import { fmt } from "../lib/format.js";
|
|
9
9
|
import { askYesNo, isInteractive } from "../lib/prompt.js";
|
|
@@ -443,7 +443,28 @@ export async function protectionPublishCommand(file, agentName, options = {}) {
|
|
|
443
443
|
process.exit(1);
|
|
444
444
|
}
|
|
445
445
|
}
|
|
446
|
-
|
|
446
|
+
// Agent-resolution regex (distinct from the trusted-sources AGENT_ID_RE above,
|
|
447
|
+
// which validates card-embedded agent ids). This matches the gateway agent-id
|
|
448
|
+
// shapes used by resolveAgentId so we can use a value directly without a lookup.
|
|
449
|
+
const RESOLVE_AGENT_ID_RE = /^(smolt-[0-9a-f]{8}|mnm-[0-9a-f-]{36})$/;
|
|
450
|
+
/**
|
|
451
|
+
* Soft agent resolution for `validate`: returns an agent id WITHOUT exiting the
|
|
452
|
+
* process. null ⇒ no agent configured / unresolvable → offline fallback.
|
|
453
|
+
*/
|
|
454
|
+
async function softResolveAgentId(agent) {
|
|
455
|
+
const name = agent ?? process.env.MNEMOM_AGENT;
|
|
456
|
+
if (!name)
|
|
457
|
+
return null;
|
|
458
|
+
if (RESOLVE_AGENT_ID_RE.test(name))
|
|
459
|
+
return name;
|
|
460
|
+
try {
|
|
461
|
+
return (await getAgentByName(name))?.id ?? null;
|
|
462
|
+
}
|
|
463
|
+
catch {
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
export async function protectionValidateCommand(file, opts = {}) {
|
|
447
468
|
const filePath = path.resolve(file);
|
|
448
469
|
if (!fs.existsSync(filePath)) {
|
|
449
470
|
console.log("\n" + fmt.error(`File not found: ${filePath}`) + "\n");
|
|
@@ -458,6 +479,30 @@ export async function protectionValidateCommand(file) {
|
|
|
458
479
|
console.log("\n" + fmt.error(`Could not parse file: ${msg}`) + "\n");
|
|
459
480
|
process.exit(1);
|
|
460
481
|
}
|
|
482
|
+
// Prefer server-authoritative validation when online + an agent is available;
|
|
483
|
+
// fall back to the local validator on 401/network. `--offline` forces local. (#9)
|
|
484
|
+
if (!opts.offline) {
|
|
485
|
+
const agentId = await softResolveAgentId(opts.agent);
|
|
486
|
+
if (agentId) {
|
|
487
|
+
const contentType = parsed.format === "yaml" ? "text/yaml" : "application/json";
|
|
488
|
+
try {
|
|
489
|
+
const result = await previewComposeAgentCard(agentId, "protection", parsed.raw, contentType);
|
|
490
|
+
renderServerProtectionValidation(result, filePath, parsed.format);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
catch (err) {
|
|
494
|
+
if (err instanceof MnemomApiError && err.status !== 401) {
|
|
495
|
+
console.log("\n" + fmt.error(`Server validation failed: ${err.message}`) + "\n");
|
|
496
|
+
process.exit(1);
|
|
497
|
+
}
|
|
498
|
+
process.stderr.write(fmt.warn("offline validation — server rules may differ") + "\n");
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
else if (!opts.agent && !process.env.MNEMOM_AGENT) {
|
|
502
|
+
process.stderr.write(fmt.dim("tip: pass --agent <name> to validate against the server (org/platform floor)") +
|
|
503
|
+
"\n");
|
|
504
|
+
}
|
|
505
|
+
}
|
|
461
506
|
const checks = validateProtectionCard(parsed.parsed);
|
|
462
507
|
const allPassed = checks.every((c) => c.passed);
|
|
463
508
|
const passCount = checks.filter((c) => c.passed).length;
|
|
@@ -484,6 +529,33 @@ export async function protectionValidateCommand(file) {
|
|
|
484
529
|
process.exit(1);
|
|
485
530
|
}
|
|
486
531
|
}
|
|
532
|
+
/** Render a server-authoritative preview-compose result; exit 1 if invalid. */
|
|
533
|
+
function renderServerProtectionValidation(result, filePath, format) {
|
|
534
|
+
console.log(fmt.header("Protection Card Validation (server-authoritative)"));
|
|
535
|
+
console.log();
|
|
536
|
+
console.log(fmt.label(" File:", ` ${filePath}`));
|
|
537
|
+
console.log(fmt.label(" Format:", ` ${format.toUpperCase()}`));
|
|
538
|
+
console.log();
|
|
539
|
+
if (result.valid) {
|
|
540
|
+
console.log(fmt.success("Server accepted and composed the card."));
|
|
541
|
+
const conflicts = result.conflicts ?? [];
|
|
542
|
+
if (conflicts.length > 0) {
|
|
543
|
+
console.log(fmt.warn(`${conflicts.length} field(s) tightened by the org/platform floor:`));
|
|
544
|
+
console.log(fmt.json(conflicts));
|
|
545
|
+
}
|
|
546
|
+
console.log();
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
console.log(fmt.error(`Server rejected the card${result.error?.code ? ` (${result.error.code})` : ""}:`));
|
|
550
|
+
if (result.error?.message)
|
|
551
|
+
console.log(` ${result.error.message}`);
|
|
552
|
+
if (result.error?.details !== undefined) {
|
|
553
|
+
console.log();
|
|
554
|
+
console.log(fmt.json(result.error.details));
|
|
555
|
+
}
|
|
556
|
+
console.log();
|
|
557
|
+
process.exit(1);
|
|
558
|
+
}
|
|
487
559
|
export async function protectionEditCommand(agentName, options = {}) {
|
|
488
560
|
const agentId = await resolveAgentId(agentName);
|
|
489
561
|
await requireAuth();
|
|
@@ -506,8 +578,11 @@ export async function protectionEditCommand(agentName, options = {}) {
|
|
|
506
578
|
},
|
|
507
579
|
trusted_sources: { domains: [], agent_ids: [], ip_ranges: [] },
|
|
508
580
|
}, { lineWidth: 120, noRefs: true });
|
|
509
|
-
|
|
510
|
-
|
|
581
|
+
// Per-invocation temp dir via mkdtemp (mode 0700, unpredictable suffix) so
|
|
582
|
+
// the editor file can't be pre-created or symlink-raced by another user in
|
|
583
|
+
// the shared os.tmpdir().
|
|
584
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "mnemom-protection-"));
|
|
585
|
+
const tmpFile = path.join(tmpDir, `${agentId}.yaml`);
|
|
511
586
|
fs.writeFileSync(tmpFile, cardYaml);
|
|
512
587
|
const editor = process.env.EDITOR || process.env.VISUAL || "vi";
|
|
513
588
|
console.log(`Opening ${editor}...`);
|
package/dist/commands/status.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getGatewayUrl } from "../lib/config.js";
|
|
2
|
-
import { resolveAgentId, getAgent, getIntegrity, getTraces } from "../lib/api.js";
|
|
2
|
+
import { resolveAgentId, getAgent, getIntegrity, getTraces, MnemomApiError } from "../lib/api.js";
|
|
3
3
|
import { isLoggedIn, getAuthInfo } from "../lib/auth.js";
|
|
4
4
|
import { fmt } from "../lib/format.js";
|
|
5
5
|
const DASHBOARD_URL = "https://mnemom.ai";
|
|
@@ -133,8 +133,10 @@ async function checkApiConnectivity(agentId) {
|
|
|
133
133
|
};
|
|
134
134
|
}
|
|
135
135
|
catch (error) {
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
// 404 = agent not registered yet. Branch on effectiveStatus (enforce hook may
|
|
137
|
+
// rewrite an undocumented 404 → synthetic 500 + spec_deviation.original_status;
|
|
138
|
+
// effectiveStatus === status when documented). getAgent → fetchApi → MnemomApiError.
|
|
139
|
+
if (error instanceof MnemomApiError && error.effectiveStatus === 404) {
|
|
138
140
|
return {
|
|
139
141
|
name: "API",
|
|
140
142
|
status: "warning",
|
|
@@ -142,7 +144,13 @@ async function checkApiConnectivity(agentId) {
|
|
|
142
144
|
details: "Will register on first traced API call",
|
|
143
145
|
};
|
|
144
146
|
}
|
|
145
|
-
|
|
147
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
148
|
+
// Transport-level timeout (no HTTP status, not an envelope error) — detect by
|
|
149
|
+
// the AbortSignal.timeout error name, with a message fallback for runtimes that
|
|
150
|
+
// don't set it.
|
|
151
|
+
if ((error instanceof Error && error.name === "TimeoutError") ||
|
|
152
|
+
message.includes("timeout") ||
|
|
153
|
+
message.includes("TIMEOUT")) {
|
|
146
154
|
return {
|
|
147
155
|
name: "API",
|
|
148
156
|
status: "error",
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
2
3
|
import { program } from "commander";
|
|
4
|
+
import { CLI_VERSION } from "./version.js";
|
|
3
5
|
import { statusCommand } from "./commands/status.js";
|
|
4
6
|
import { integrityCommand } from "./commands/integrity.js";
|
|
5
7
|
import { logsCommand } from "./commands/logs.js";
|
|
@@ -7,7 +9,7 @@ import { licenseActivateCommand, licenseStatusCommand, licenseDeactivateCommand,
|
|
|
7
9
|
import { cardShowCommand, cardPublishCommand, cardValidateCommand, cardEditCommand, cardEvaluateCommand, } from "./commands/card.js";
|
|
8
10
|
import { policyInitCommand, policyValidateCommand, policyPublishCommand, policyListCommand, policyTestCommand, policyEvaluateCommand, } from "./commands/policy.js";
|
|
9
11
|
import { protectionShowCommand, protectionPublishCommand, protectionValidateCommand, protectionEditCommand, } from "./commands/protection.js";
|
|
10
|
-
import { agentsListCommand } from "./commands/agents.js";
|
|
12
|
+
import { agentsListCommand, agentsClaimCommand } from "./commands/agents.js";
|
|
11
13
|
import { orgListCommand, orgShowCommand } from "./commands/org.js";
|
|
12
14
|
import { teamListCommand, teamShowCommand, teamTemplateCommand, teamPreviewComposeCommand, teamAdminGrantCommand, teamAdminRevokeCommand, teamAdminListCommand, teamCoverageCommand, } from "./commands/team.js";
|
|
13
15
|
import { advisoriesListCommand, advisoriesShowCommand } from "./commands/advisories.js";
|
|
@@ -20,7 +22,7 @@ import { listenCommand } from "./commands/listen.js";
|
|
|
20
22
|
program
|
|
21
23
|
.name("mnemom")
|
|
22
24
|
.description("Transparent AI agent tracing")
|
|
23
|
-
.version(
|
|
25
|
+
.version(CLI_VERSION)
|
|
24
26
|
.option("--agent <name>", "Select agent by name (or set MNEMOM_AGENT)");
|
|
25
27
|
program
|
|
26
28
|
.command("status")
|
|
@@ -149,10 +151,12 @@ cardCmd
|
|
|
149
151
|
cardCmd
|
|
150
152
|
.command("validate")
|
|
151
153
|
.argument("<file>", "Path to alignment card file (YAML or JSON)")
|
|
152
|
-
.description("Validate alignment card
|
|
153
|
-
.
|
|
154
|
+
.description("Validate an alignment card (server-authoritative when --agent is set; --offline forces local)")
|
|
155
|
+
.option("--offline", "Validate locally only (skip the server preview-compose)")
|
|
156
|
+
.action(async (file, subOpts) => {
|
|
154
157
|
try {
|
|
155
|
-
|
|
158
|
+
const opts = program.opts();
|
|
159
|
+
await cardValidateCommand(file, { offline: subOpts.offline, agent: opts.agent });
|
|
156
160
|
}
|
|
157
161
|
catch (error) {
|
|
158
162
|
console.error("Error:", error instanceof Error ? error.message : error);
|
|
@@ -224,10 +228,12 @@ protectionCmd
|
|
|
224
228
|
protectionCmd
|
|
225
229
|
.command("validate")
|
|
226
230
|
.argument("<file>", "Path to protection card file (YAML or JSON)")
|
|
227
|
-
.description("Validate protection card
|
|
228
|
-
.
|
|
231
|
+
.description("Validate a protection card (server-authoritative when --agent is set; --offline forces local)")
|
|
232
|
+
.option("--offline", "Validate locally only (skip the server preview-compose)")
|
|
233
|
+
.action(async (file, subOpts) => {
|
|
229
234
|
try {
|
|
230
|
-
|
|
235
|
+
const opts = program.opts();
|
|
236
|
+
await protectionValidateCommand(file, { offline: subOpts.offline, agent: opts.agent });
|
|
231
237
|
}
|
|
232
238
|
catch (error) {
|
|
233
239
|
console.error("Error:", error instanceof Error ? error.message : error);
|
|
@@ -290,12 +296,36 @@ policyCmd
|
|
|
290
296
|
// ============================================================================
|
|
291
297
|
// Agent management
|
|
292
298
|
// ============================================================================
|
|
293
|
-
program
|
|
299
|
+
const agentsCmd = program
|
|
294
300
|
.command("agents")
|
|
295
|
-
.description("List agents
|
|
296
|
-
.
|
|
301
|
+
.description("List agents across the orgs you belong to (ADR-062 org-scoped)")
|
|
302
|
+
.option("--org <id>", "Scope to a single org (default: all orgs you belong to)")
|
|
303
|
+
.action(async (opts) => {
|
|
297
304
|
try {
|
|
298
|
-
await agentsListCommand();
|
|
305
|
+
await agentsListCommand({ org: opts.org });
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
309
|
+
process.exit(1);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
agentsCmd
|
|
313
|
+
.command("claim <id-or-name>")
|
|
314
|
+
.description("Claim an agent into an org (ADR-062 claim-to-org)")
|
|
315
|
+
.option("--org <slug>", "Org slug or id to claim into (default: your personal org)")
|
|
316
|
+
.option("--key <key>", "The agent's provider API key — the CLI derives the hash proof from it")
|
|
317
|
+
.option("--hash-proof <hex>", "A pre-computed 64-hex SHA-256 proof (advanced/CI; alternative to --key)")
|
|
318
|
+
.option("--name <name>", "Provisioned agent name used in proof derivation (defaults to a name-shaped positional arg)")
|
|
319
|
+
.option("--json", "Output JSON instead of human-readable text")
|
|
320
|
+
.action(async (idOrName, opts) => {
|
|
321
|
+
try {
|
|
322
|
+
await agentsClaimCommand(idOrName, {
|
|
323
|
+
org: opts.org,
|
|
324
|
+
key: opts.key,
|
|
325
|
+
hashProof: opts.hashProof,
|
|
326
|
+
name: opts.name,
|
|
327
|
+
json: opts.json,
|
|
328
|
+
});
|
|
299
329
|
}
|
|
300
330
|
catch (error) {
|
|
301
331
|
console.error("Error:", error instanceof Error ? error.message : error);
|
|
@@ -1291,4 +1321,13 @@ program
|
|
|
1291
1321
|
process.exit(1);
|
|
1292
1322
|
}
|
|
1293
1323
|
});
|
|
1294
|
-
program.
|
|
1324
|
+
// Export the fully-assembled commander program so tooling (e.g. the
|
|
1325
|
+
// command-tree snapshot generator in scripts/gen-command-tree.mjs) can
|
|
1326
|
+
// statically introspect the command surface WITHOUT executing the CLI.
|
|
1327
|
+
export { program };
|
|
1328
|
+
// Parse argv only when invoked as the CLI entrypoint — not when imported.
|
|
1329
|
+
// (ESM has no `require.main`; compare this module's URL to argv[1].)
|
|
1330
|
+
const invokedDirectly = process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
1331
|
+
if (invokedDirectly) {
|
|
1332
|
+
program.parse();
|
|
1333
|
+
}
|