@mnemom/mnemom 0.14.5 → 0.15.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/activity.d.ts +2 -0
- package/dist/commands/{integrity.js → activity.js} +9 -3
- package/dist/commands/agents.js +44 -2
- package/dist/commands/api-key.js +20 -4
- package/dist/commands/auth.js +39 -23
- package/dist/commands/card.js +17 -2
- package/dist/commands/logs.js +13 -5
- package/dist/commands/org.js +15 -2
- package/dist/commands/protection.d.ts +2 -1
- package/dist/commands/protection.js +163 -3
- package/dist/commands/status.js +7 -1
- package/dist/commands/try-me.d.ts +52 -0
- package/dist/commands/try-me.js +361 -0
- package/dist/index.js +64 -4
- package/dist/lib/api.js +5 -6
- package/dist/lib/auth.d.ts +33 -10
- package/dist/lib/auth.js +71 -201
- package/dist/lib/oauth.d.ts +121 -0
- package/dist/lib/oauth.js +450 -0
- package/dist/lib/onboarding.d.ts +28 -0
- package/dist/lib/onboarding.js +35 -0
- package/dist/lib/prompt.d.ts +3 -1
- package/dist/lib/prompt.js +47 -1
- package/dist/lib/try-me.d.ts +181 -0
- package/dist/lib/try-me.js +245 -0
- package/package.json +3 -2
- package/dist/commands/integrity.d.ts +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolveAgentId, getIntegrity, MnemomApiError } from "../lib/api.js";
|
|
2
2
|
import { fmt } from "../lib/format.js";
|
|
3
|
-
|
|
3
|
+
import { TRACES_PENDING_NOTE } from "../lib/onboarding.js";
|
|
4
|
+
export async function activityCommand(agentName) {
|
|
4
5
|
const agentId = await resolveAgentId(agentName);
|
|
5
6
|
console.log("\nFetching agent activity...\n");
|
|
6
7
|
try {
|
|
@@ -18,7 +19,8 @@ export async function integrityCommand(agentName) {
|
|
|
18
19
|
console.log("\n" + fmt.warn("You have integrity violations. Run `mnemom logs` to investigate.") + "\n");
|
|
19
20
|
}
|
|
20
21
|
else if (integrity.total_traces === 0) {
|
|
21
|
-
console.log("\nNo traces recorded yet. Start using Claude to build your integrity score
|
|
22
|
+
console.log("\nNo traces recorded yet. Start using Claude to build your integrity score.");
|
|
23
|
+
console.log("\n" + fmt.dim(TRACES_PENDING_NOTE) + "\n");
|
|
22
24
|
}
|
|
23
25
|
else {
|
|
24
26
|
console.log("\n" + fmt.success("Your agent has a clean integrity record!") + "\n");
|
|
@@ -35,7 +37,8 @@ export async function integrityCommand(agentName) {
|
|
|
35
37
|
console.log(` ${fmt.label("Total: ", "0 traces")}`);
|
|
36
38
|
console.log(` ${fmt.label("Verified: ", "0")}`);
|
|
37
39
|
console.log(` ${fmt.label("Violations:", " 0")}`);
|
|
38
|
-
console.log("\nNo traces recorded yet. Start using Claude to build your integrity score
|
|
40
|
+
console.log("\nNo traces recorded yet. Start using Claude to build your integrity score.");
|
|
41
|
+
console.log("\n" + fmt.dim(TRACES_PENDING_NOTE) + "\n");
|
|
39
42
|
}
|
|
40
43
|
else {
|
|
41
44
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -44,6 +47,9 @@ export async function integrityCommand(agentName) {
|
|
|
44
47
|
}
|
|
45
48
|
}
|
|
46
49
|
}
|
|
50
|
+
// `mnemom integrity` is retained as a deprecated alias of `mnemom activity`
|
|
51
|
+
// (AAP = Activity). It still works and prints a deprecation notice; no breaking change.
|
|
52
|
+
export const integrityCommand = activityCommand;
|
|
47
53
|
function generateScoreBar(score) {
|
|
48
54
|
const filled = Math.round(score * 10);
|
|
49
55
|
const empty = 10 - filled;
|
package/dist/commands/agents.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { listAgents, listOrgAgents, listMyOrgs, getAgentByName, claimAgent, MnemomApiError, } from "../lib/api.js";
|
|
2
|
+
import { listAgents, listOrgAgents, listMyOrgs, getAgent, getAgentByName, claimAgent, MnemomApiError, } from "../lib/api.js";
|
|
3
3
|
import { requireAuth } from "../lib/auth.js";
|
|
4
4
|
import { fmt } from "../lib/format.js";
|
|
5
5
|
export async function agentsListCommand(options = {}) {
|
|
@@ -66,7 +66,7 @@ const FULL_PROOF_RE = /^[0-9a-f]{64}$/;
|
|
|
66
66
|
// digest and break the claim handshake entirely, so SHA-256 is mandated by the
|
|
67
67
|
// protocol — the "insufficient computational effort" alert is a false positive.
|
|
68
68
|
function sha256Hex(input) {
|
|
69
|
-
//
|
|
69
|
+
// codeql[js/insufficient-password-hash] — protocol proof, not password storage; see block comment above
|
|
70
70
|
return createHash("sha256").update(input, "utf8").digest("hex"); // codeql[js/insufficient-password-hash]
|
|
71
71
|
}
|
|
72
72
|
/**
|
|
@@ -152,6 +152,15 @@ export async function agentsClaimCommand(idOrName, options = {}) {
|
|
|
152
152
|
return;
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
|
+
else if (!derivationName && options.key && !options.hashProof) {
|
|
156
|
+
// ID given, no --name, --key will be used for proof derivation: try to
|
|
157
|
+
// auto-resolve the agent's provisioned name so `claim <id> --key <k>`
|
|
158
|
+
// works on the first try without requiring an explicit --name.
|
|
159
|
+
const found = await getAgent(idOrName).catch(() => null);
|
|
160
|
+
if (found?.name) {
|
|
161
|
+
derivationName = found.name;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
155
164
|
// 2. Establish the hash proof: --hash-proof (raw) takes precedence, else
|
|
156
165
|
// derive from --key. Exactly one source is needed.
|
|
157
166
|
let hashProof;
|
|
@@ -242,6 +251,22 @@ export async function agentsClaimCommand(idOrName, options = {}) {
|
|
|
242
251
|
process.exit(1);
|
|
243
252
|
return;
|
|
244
253
|
}
|
|
254
|
+
if (err instanceof MnemomApiError &&
|
|
255
|
+
err.effectiveStatus === 403 &&
|
|
256
|
+
(err.code === "invalid_hash_proof" || /hash[ _]?proof/i.test(err.message))) {
|
|
257
|
+
// The proof derived from --key (and the agent's name) didn't match the
|
|
258
|
+
// agent's stored proof. Name the real cause instead of the bare "Invalid
|
|
259
|
+
// hash proof": verify the key, and let the user supply the name when it
|
|
260
|
+
// couldn't be auto-resolved from the id. Ordered AFTER the not-a-member
|
|
261
|
+
// 403 (also a 403, distinguished by isNotAMemberError) so it doesn't
|
|
262
|
+
// shadow the org-teaching path.
|
|
263
|
+
console.log(fmt.error("Failed to claim agent: the --key (and agent name) didn't match the agent's stored proof (invalid hash proof)."));
|
|
264
|
+
console.log(fmt.dim(` Confirm --key is the exact provider key the agent runs with.\n` +
|
|
265
|
+
` If the name couldn't be auto-resolved from the id, pass it explicitly:\n` +
|
|
266
|
+
` mnemom agents claim ${agentId} --key <key> --name <provisioned-name>`) + "\n");
|
|
267
|
+
process.exit(1);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
245
270
|
const msg = err instanceof Error ? err.message : String(err);
|
|
246
271
|
console.log(fmt.error(`Failed to claim agent: ${msg}`) + "\n");
|
|
247
272
|
process.exit(1);
|
|
@@ -264,8 +289,25 @@ export async function agentsClaimCommand(idOrName, options = {}) {
|
|
|
264
289
|
}
|
|
265
290
|
console.log(fmt.success(`Claimed ${result.agent_id}.`));
|
|
266
291
|
console.log(fmt.label(" Landed in:", where));
|
|
292
|
+
if (derivationName) {
|
|
293
|
+
console.log(fmt.label(" Name:", derivationName));
|
|
294
|
+
}
|
|
267
295
|
if (result.claimed_at) {
|
|
268
296
|
console.log(fmt.label(" Claimed at:", new Date(result.claimed_at).toLocaleString()));
|
|
269
297
|
}
|
|
298
|
+
// Bridge to the next step (MNE-610). The dogfood dead-end was: claim with a
|
|
299
|
+
// friendly --name, then `mnemom card publish --agent <name>` returns an
|
|
300
|
+
// opaque "agent not found" while the freshly-claimed agent propagates into
|
|
301
|
+
// the listing the name-resolver reads. Always surface a ready-to-paste
|
|
302
|
+
// command keyed on the CANONICAL agent id, which `resolveAgentId` accepts
|
|
303
|
+
// verbatim (no list lookup) — so the publish step never dead-ends regardless
|
|
304
|
+
// of listing propagation. The friendly name is shown as the eventual
|
|
305
|
+
// shorthand, not the recommended first move.
|
|
306
|
+
console.log();
|
|
307
|
+
console.log(fmt.dim(" Next — publish your alignment card with the canonical id:"));
|
|
308
|
+
console.log(fmt.dim(` mnemom card publish <file.yaml> --agent ${result.agent_id}`));
|
|
309
|
+
if (derivationName) {
|
|
310
|
+
console.log(fmt.dim(` Once it appears in \`mnemom agents\`, you can use the name instead: --agent ${derivationName}`));
|
|
311
|
+
}
|
|
270
312
|
console.log();
|
|
271
313
|
}
|
package/dist/commands/api-key.js
CHANGED
|
@@ -64,7 +64,11 @@ function formatRow(key) {
|
|
|
64
64
|
}
|
|
65
65
|
// ─── mnemom api-key list ─────────────────────────────────────────────────
|
|
66
66
|
export async function apiKeyListCommand(opts) {
|
|
67
|
-
await requireAuth();
|
|
67
|
+
const cred = await requireAuth();
|
|
68
|
+
if (cred.type === "api-key") {
|
|
69
|
+
console.log(fmt.warn("API keys are agent-scoped; run mnemom login to manage personal API keys"));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
68
72
|
let keys;
|
|
69
73
|
try {
|
|
70
74
|
keys = await listApiKeys();
|
|
@@ -92,7 +96,11 @@ export async function apiKeyListCommand(opts) {
|
|
|
92
96
|
}
|
|
93
97
|
// ─── mnemom api-key create ───────────────────────────────────────────────
|
|
94
98
|
export async function apiKeyCreateCommand(opts) {
|
|
95
|
-
await requireAuth();
|
|
99
|
+
const cred = await requireAuth();
|
|
100
|
+
if (cred.type === "api-key") {
|
|
101
|
+
console.log(fmt.warn("API keys are agent-scoped; run mnemom login to manage personal API keys"));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
96
104
|
if (!opts.name || opts.name.trim() === "") {
|
|
97
105
|
console.error(fmt.error("--name is required. Example: --name 'ci-prod'"));
|
|
98
106
|
process.exit(1);
|
|
@@ -133,7 +141,11 @@ export async function apiKeyCreateCommand(opts) {
|
|
|
133
141
|
}
|
|
134
142
|
// ─── mnemom api-key rotate ───────────────────────────────────────────────
|
|
135
143
|
export async function apiKeyRotateCommand(keyId, opts) {
|
|
136
|
-
await requireAuth();
|
|
144
|
+
const cred = await requireAuth();
|
|
145
|
+
if (cred.type === "api-key") {
|
|
146
|
+
console.log(fmt.warn("API keys are agent-scoped; run mnemom login to manage personal API keys"));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
137
149
|
if (!keyId) {
|
|
138
150
|
console.error(fmt.error("Usage: mnemom api-key rotate <key_id>"));
|
|
139
151
|
process.exit(1);
|
|
@@ -163,7 +175,11 @@ export async function apiKeyRotateCommand(keyId, opts) {
|
|
|
163
175
|
}
|
|
164
176
|
// ─── mnemom api-key revoke ───────────────────────────────────────────────
|
|
165
177
|
export async function apiKeyRevokeCommand(keyId) {
|
|
166
|
-
await requireAuth();
|
|
178
|
+
const cred = await requireAuth();
|
|
179
|
+
if (cred.type === "api-key") {
|
|
180
|
+
console.log(fmt.warn("API keys are agent-scoped; run mnemom login to manage personal API keys"));
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
167
183
|
if (!keyId) {
|
|
168
184
|
console.error(fmt.error("Usage: mnemom api-key revoke <key_id>"));
|
|
169
185
|
process.exit(1);
|
package/dist/commands/auth.js
CHANGED
|
@@ -1,29 +1,13 @@
|
|
|
1
|
-
import { getAuthInfo, clearAuthTokens, loginWithBrowser,
|
|
1
|
+
import { getAuthInfo, clearAuthTokens, loginWithBrowser, loginWithDeviceFlow, resolveAuth, } from "../lib/auth.js";
|
|
2
2
|
import { fmt } from "../lib/format.js";
|
|
3
|
-
import { askInput } from "../lib/prompt.js";
|
|
4
3
|
export async function loginCommand(options = {}) {
|
|
5
4
|
try {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (!email) {
|
|
10
|
-
console.log(fmt.error("Email is required."));
|
|
11
|
-
process.exit(1);
|
|
12
|
-
}
|
|
13
|
-
const password = await askInput("Password:", true);
|
|
14
|
-
if (!password) {
|
|
15
|
-
console.log(fmt.error("Password is required."));
|
|
16
|
-
process.exit(1);
|
|
17
|
-
}
|
|
18
|
-
tokens = await loginWithPassword(email, password);
|
|
19
|
-
}
|
|
20
|
-
else {
|
|
21
|
-
tokens = await loginWithBrowser();
|
|
22
|
-
}
|
|
5
|
+
// --no-browser → RFC 8628 device flow (headless / SSH / no local browser).
|
|
6
|
+
// Default → OAuth 2.1 authorization-code + PKCE with a loopback redirect.
|
|
7
|
+
const tokens = options.noBrowser ? await loginWithDeviceFlow() : await loginWithBrowser();
|
|
23
8
|
console.log();
|
|
24
9
|
console.log(fmt.success("Logged in successfully!"));
|
|
25
|
-
|
|
26
|
-
console.log(fmt.label(" User ID:", ` ${tokens.userId}`));
|
|
10
|
+
printGrant(tokens);
|
|
27
11
|
console.log();
|
|
28
12
|
}
|
|
29
13
|
catch (error) {
|
|
@@ -32,11 +16,37 @@ export async function loginCommand(options = {}) {
|
|
|
32
16
|
process.exit(1);
|
|
33
17
|
}
|
|
34
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Print what the issued token actually grants. OAuth access tokens are scoped
|
|
21
|
+
* and (deliberately) carry no user identity, so we show scope + expiry rather
|
|
22
|
+
* than fabricating an email/user id we don't have.
|
|
23
|
+
*/
|
|
24
|
+
function printGrant(tokens) {
|
|
25
|
+
if (tokens.email)
|
|
26
|
+
console.log(fmt.label(" Email: ", ` ${tokens.email}`));
|
|
27
|
+
if (tokens.scope)
|
|
28
|
+
console.log(fmt.label(" Scope: ", ` ${tokens.scope}`));
|
|
29
|
+
const expiresDate = new Date(tokens.expiresAt * 1000).toISOString();
|
|
30
|
+
console.log(fmt.label(" Expires:", ` ${expiresDate}`));
|
|
31
|
+
}
|
|
35
32
|
export async function logoutCommand() {
|
|
36
33
|
clearAuthTokens();
|
|
37
34
|
console.log(fmt.success("Logged out."));
|
|
38
35
|
}
|
|
39
36
|
export async function whoamiCommand() {
|
|
37
|
+
const cred = await resolveAuth();
|
|
38
|
+
if (cred.type === "none") {
|
|
39
|
+
console.log("\nNot logged in. Run `mnemom login` to authenticate.\n");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (cred.type === "api-key") {
|
|
43
|
+
console.log(fmt.header("Auth Status"));
|
|
44
|
+
console.log();
|
|
45
|
+
console.log(fmt.label(" Credential Type:", " API key"));
|
|
46
|
+
console.log(fmt.label(" Status: ", " valid (no expiry)"));
|
|
47
|
+
console.log();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
40
50
|
const auth = getAuthInfo();
|
|
41
51
|
if (!auth) {
|
|
42
52
|
console.log("\nNot logged in. Run `mnemom login` to authenticate.\n");
|
|
@@ -47,8 +57,14 @@ export async function whoamiCommand() {
|
|
|
47
57
|
const expiresDate = new Date(auth.expiresAt * 1000).toISOString();
|
|
48
58
|
console.log(fmt.header("Auth Status"));
|
|
49
59
|
console.log();
|
|
50
|
-
|
|
51
|
-
|
|
60
|
+
// OAuth access tokens are opaque and carry no identity; legacy sessions may
|
|
61
|
+
// still have email/userId. Show whatever the stored grant actually has.
|
|
62
|
+
if (auth.email)
|
|
63
|
+
console.log(fmt.label(" Email: ", ` ${auth.email}`));
|
|
64
|
+
if (auth.userId)
|
|
65
|
+
console.log(fmt.label(" User ID:", ` ${auth.userId}`));
|
|
66
|
+
if (auth.scope)
|
|
67
|
+
console.log(fmt.label(" Scope: ", ` ${auth.scope}`));
|
|
52
68
|
console.log(fmt.label(" Token: ", expired ? " expired" : ` valid until ${expiresDate}`));
|
|
53
69
|
console.log();
|
|
54
70
|
}
|
package/dist/commands/card.js
CHANGED
|
@@ -654,8 +654,23 @@ export async function cardPublishCommand(file, agentName, options = {}) {
|
|
|
654
654
|
console.log();
|
|
655
655
|
}
|
|
656
656
|
catch (error) {
|
|
657
|
-
|
|
658
|
-
|
|
657
|
+
if (error instanceof MnemomApiError) {
|
|
658
|
+
if (error.effectiveStatus === 404) {
|
|
659
|
+
console.log("\n" +
|
|
660
|
+
fmt.error("Agent found but not writable: you can see this agent locally, but cannot publish to it in its current organization context.") +
|
|
661
|
+
"\n");
|
|
662
|
+
}
|
|
663
|
+
else if (error.effectiveStatus === 401) {
|
|
664
|
+
console.log("\n" + fmt.error(`Authentication failed: ${error.message}`) + "\n");
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
console.log("\n" + fmt.error(`Failed to publish card: ${error.message}`) + "\n");
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
else {
|
|
671
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
672
|
+
console.log("\n" + fmt.error(`Failed to publish card: ${message}`) + "\n");
|
|
673
|
+
}
|
|
659
674
|
process.exit(1);
|
|
660
675
|
}
|
|
661
676
|
}
|
package/dist/commands/logs.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolveAgentId, getTraces, MnemomApiError, } from "../lib/api.js";
|
|
2
2
|
import { getGatewayUrl, getWebsiteUrl } from "../lib/config.js";
|
|
3
3
|
import { fmt } from "../lib/format.js";
|
|
4
|
+
import { TRACES_PENDING_NOTE, gatewayIntegrationHelp } from "../lib/onboarding.js";
|
|
4
5
|
export async function logsCommand(options = {}) {
|
|
5
6
|
const agentId = await resolveAgentId(options.agentName);
|
|
6
7
|
const gatewayUrl = getGatewayUrl();
|
|
@@ -10,9 +11,8 @@ export async function logsCommand(options = {}) {
|
|
|
10
11
|
const traces = await getTraces(agentId, limit);
|
|
11
12
|
if (traces.length === 0) {
|
|
12
13
|
console.log(fmt.header("No traces found"));
|
|
13
|
-
console.log("\
|
|
14
|
-
console.log(
|
|
15
|
-
console.log(` export ANTHROPIC_BASE_URL="${gatewayUrl}/v1/proxy/${agentId}"\n`);
|
|
14
|
+
console.log("\n" + TRACES_PENDING_NOTE + "\n");
|
|
15
|
+
console.log(gatewayIntegrationHelp(gatewayUrl, options.agentName) + "\n");
|
|
16
16
|
return;
|
|
17
17
|
}
|
|
18
18
|
console.log(fmt.header(`Recent Traces (${traces.length})`));
|
|
@@ -29,7 +29,8 @@ export async function logsCommand(options = {}) {
|
|
|
29
29
|
// effectiveStatus surfaces the true status (=== status when documented).
|
|
30
30
|
if (error instanceof MnemomApiError && error.effectiveStatus === 404) {
|
|
31
31
|
console.log(fmt.header("No traces found"));
|
|
32
|
-
console.log("\
|
|
32
|
+
console.log("\n" + TRACES_PENDING_NOTE + "\n");
|
|
33
|
+
console.log(gatewayIntegrationHelp(gatewayUrl, options.agentName) + "\n");
|
|
33
34
|
}
|
|
34
35
|
else {
|
|
35
36
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -51,8 +52,15 @@ function displayTrace(trace) {
|
|
|
51
52
|
// shape); fall back to the action type, then a dash.
|
|
52
53
|
const actionLabel = trace.action?.name ?? trace.action?.type ?? "—";
|
|
53
54
|
console.log(` ${fmt.label("Action:", ` ${actionLabel}`)}`);
|
|
55
|
+
// A pure-LLM inference turn carries category "escalation_trigger" on the wire
|
|
56
|
+
// (it is the *point at which* an escalation could fire, not a turn that demanded
|
|
57
|
+
// one). Rendering that raw reads as if every benign turn escalated, so present a
|
|
58
|
+
// friendly label instead. Presentation-only — the underlying category is unchanged.
|
|
54
59
|
if (trace.action?.category) {
|
|
55
|
-
|
|
60
|
+
const categoryLabel = trace.action.category === "escalation_trigger" && trace.action.name === "inference"
|
|
61
|
+
? "inference (no tool)"
|
|
62
|
+
: trace.action.category;
|
|
63
|
+
console.log(` ${fmt.label("Type: ", ` ${categoryLabel}`)}`);
|
|
56
64
|
}
|
|
57
65
|
const reasoning = trace.decision?.selection_reasoning;
|
|
58
66
|
if (reasoning) {
|
package/dist/commands/org.js
CHANGED
|
@@ -10,7 +10,11 @@ import { fmt } from "../lib/format.js";
|
|
|
10
10
|
* machine-readable output.
|
|
11
11
|
*/
|
|
12
12
|
export async function orgListCommand(opts) {
|
|
13
|
-
await requireAuth();
|
|
13
|
+
const cred = await requireAuth();
|
|
14
|
+
if (cred.type === "api-key") {
|
|
15
|
+
console.log(fmt.warn("API keys are agent-scoped; run mnemom login for org/key management"));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
14
18
|
let orgs;
|
|
15
19
|
try {
|
|
16
20
|
orgs = await listMyOrgs();
|
|
@@ -56,7 +60,16 @@ export async function orgListCommand(opts) {
|
|
|
56
60
|
* exactly one membership, that one).
|
|
57
61
|
*/
|
|
58
62
|
export async function orgShowCommand(orgIdArg, opts) {
|
|
59
|
-
await requireAuth();
|
|
63
|
+
const cred = await requireAuth();
|
|
64
|
+
if (cred.type === "api-key") {
|
|
65
|
+
if (opts.personal || orgIdArg) {
|
|
66
|
+
console.log(fmt.warn("API keys cannot filter by organization. Run mnemom login to authenticate as a user and list org-scoped agents."));
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
console.log(fmt.warn("API keys are agent-scoped; run mnemom login for org/key management"));
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
60
73
|
let target;
|
|
61
74
|
try {
|
|
62
75
|
if (opts.personal) {
|
|
@@ -9,7 +9,8 @@ export interface ValidationCheck {
|
|
|
9
9
|
* Required: card_version, agent_id, mode (off|observe|nudge|enforce).
|
|
10
10
|
* Optional: thresholds (warn ≤ quarantine ≤ block, all in [0,1]),
|
|
11
11
|
* screen_surfaces (object of bools with the four named keys),
|
|
12
|
-
* trusted_sources (object of typed buckets, per-bucket deny-lists)
|
|
12
|
+
* trusted_sources (object of typed buckets, per-bucket deny-lists),
|
|
13
|
+
* protected_surface (org-declared asset policy — MNE-830/833).
|
|
13
14
|
*/
|
|
14
15
|
export declare function validateProtectionCard(card: Record<string, unknown>): ValidationCheck[];
|
|
15
16
|
export declare function protectionShowCommand(agentName?: string): Promise<void>;
|
|
@@ -9,6 +9,8 @@ import { fmt } from "../lib/format.js";
|
|
|
9
9
|
import { askYesNo, isInteractive } from "../lib/prompt.js";
|
|
10
10
|
const PROTECTION_MODES = ["off", "observe", "nudge", "enforce"];
|
|
11
11
|
const SURFACE_KEYS = ["incoming", "outgoing", "tool_calls", "tool_responses"];
|
|
12
|
+
// Mirrors mnemom-api/src/composition/validate.ts OP_SEVERITIES (MNE-833).
|
|
13
|
+
const OP_SEVERITIES = ["low", "medium", "high", "critical"];
|
|
12
14
|
// Per ADR-037 Decision 4: deny public LLM endpoints + public DNS providers,
|
|
13
15
|
// and the any-host CIDRs, at write time.
|
|
14
16
|
// T8-4 (2026-05-19) extended the corpus per
|
|
@@ -133,13 +135,152 @@ function validateTrustedBucket(name, bucket, checks, perEntry) {
|
|
|
133
135
|
});
|
|
134
136
|
}
|
|
135
137
|
}
|
|
138
|
+
// ── protected_surface helpers (MNE-833) ─────────────────────────────────────
|
|
139
|
+
// Mirrors mnemom-api/src/composition/validate.ts validateProtectedSurface +
|
|
140
|
+
// validateOpArray. Both codebases live in different repos; keep in sync.
|
|
141
|
+
function validateOpArray(name, value, checks, withSeverity) {
|
|
142
|
+
if (value === undefined)
|
|
143
|
+
return;
|
|
144
|
+
if (!Array.isArray(value)) {
|
|
145
|
+
checks.push({
|
|
146
|
+
name: `protected_surface.${name}`,
|
|
147
|
+
passed: false,
|
|
148
|
+
message: `protected_surface.${name} must be an array.`,
|
|
149
|
+
});
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
value.forEach((entry, i) => {
|
|
153
|
+
if (!isObject(entry)) {
|
|
154
|
+
checks.push({
|
|
155
|
+
name: `protected_surface.${name}[${i}]`,
|
|
156
|
+
passed: false,
|
|
157
|
+
message: `each protected_surface.${name} entry must be an object.`,
|
|
158
|
+
});
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const op = entry;
|
|
162
|
+
if (typeof op.pattern !== "string" || op.pattern.length === 0) {
|
|
163
|
+
checks.push({
|
|
164
|
+
name: `protected_surface.${name}[${i}].pattern`,
|
|
165
|
+
passed: false,
|
|
166
|
+
message: `protected_surface.${name}[].pattern is required (non-empty string).`,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
if (op.applies_to !== undefined) {
|
|
170
|
+
if (!Array.isArray(op.applies_to)) {
|
|
171
|
+
checks.push({
|
|
172
|
+
name: `protected_surface.${name}[${i}].applies_to`,
|
|
173
|
+
passed: false,
|
|
174
|
+
message: `protected_surface.${name}[].applies_to must be an array of asset-identity strings.`,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
else if (!op.applies_to.every((x) => typeof x === "string")) {
|
|
178
|
+
checks.push({
|
|
179
|
+
name: `protected_surface.${name}[${i}].applies_to`,
|
|
180
|
+
passed: false,
|
|
181
|
+
message: `protected_surface.${name}[].applies_to entries must be strings.`,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (withSeverity &&
|
|
186
|
+
op.severity !== undefined &&
|
|
187
|
+
!OP_SEVERITIES.includes(String(op.severity))) {
|
|
188
|
+
checks.push({
|
|
189
|
+
name: `protected_surface.${name}[${i}].severity`,
|
|
190
|
+
passed: false,
|
|
191
|
+
message: `protected_surface.${name}[].severity must be one of: ${OP_SEVERITIES.join(", ")}.`,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
if (op.reason !== undefined && typeof op.reason !== "string") {
|
|
195
|
+
checks.push({
|
|
196
|
+
name: `protected_surface.${name}[${i}].reason`,
|
|
197
|
+
passed: false,
|
|
198
|
+
message: `protected_surface.${name}[].reason must be a string.`,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
function validateProtectedSurface(input, checks) {
|
|
204
|
+
if (!isObject(input)) {
|
|
205
|
+
checks.push({
|
|
206
|
+
name: "protected_surface",
|
|
207
|
+
passed: false,
|
|
208
|
+
message: "protected_surface must be an object with assets, forbidden_operations, escalation_required arrays.",
|
|
209
|
+
});
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const ps = input;
|
|
213
|
+
// assets[] — each needs a non-empty string kind + selector (intrinsic identity).
|
|
214
|
+
if (ps.assets !== undefined) {
|
|
215
|
+
if (!Array.isArray(ps.assets)) {
|
|
216
|
+
checks.push({
|
|
217
|
+
name: "protected_surface.assets",
|
|
218
|
+
passed: false,
|
|
219
|
+
message: "protected_surface.assets must be an array.",
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
ps.assets.forEach((entry, i) => {
|
|
224
|
+
if (!isObject(entry)) {
|
|
225
|
+
checks.push({
|
|
226
|
+
name: `protected_surface.assets[${i}]`,
|
|
227
|
+
passed: false,
|
|
228
|
+
message: "each protected_surface.assets entry must be an object.",
|
|
229
|
+
});
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const a = entry;
|
|
233
|
+
if (typeof a.kind !== "string" || a.kind.length === 0) {
|
|
234
|
+
checks.push({
|
|
235
|
+
name: `protected_surface.assets[${i}].kind`,
|
|
236
|
+
passed: false,
|
|
237
|
+
message: "protected_surface.assets[].kind is required (non-empty string).",
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
if (typeof a.selector !== "string" || a.selector.length === 0) {
|
|
241
|
+
checks.push({
|
|
242
|
+
name: `protected_surface.assets[${i}].selector`,
|
|
243
|
+
passed: false,
|
|
244
|
+
message: "protected_surface.assets[].selector is required (non-empty string).",
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
if (a.label !== undefined && typeof a.label !== "string") {
|
|
248
|
+
checks.push({
|
|
249
|
+
name: `protected_surface.assets[${i}].label`,
|
|
250
|
+
passed: false,
|
|
251
|
+
message: "protected_surface.assets[].label must be a string.",
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
if (a.reason !== undefined && typeof a.reason !== "string") {
|
|
255
|
+
checks.push({
|
|
256
|
+
name: `protected_surface.assets[${i}].reason`,
|
|
257
|
+
passed: false,
|
|
258
|
+
message: "protected_surface.assets[].reason must be a string.",
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
validateOpArray("forbidden_operations", ps.forbidden_operations, checks, /* withSeverity */ true);
|
|
265
|
+
validateOpArray("escalation_required", ps.escalation_required, checks, /* withSeverity */ false);
|
|
266
|
+
for (const key of Object.keys(ps)) {
|
|
267
|
+
if (!["assets", "forbidden_operations", "escalation_required"].includes(key)) {
|
|
268
|
+
checks.push({
|
|
269
|
+
name: `protected_surface.${key}`,
|
|
270
|
+
passed: false,
|
|
271
|
+
message: `protected_surface.${key} is not a recognized key (allowed: assets, forbidden_operations, escalation_required).`,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
136
276
|
/**
|
|
137
277
|
* Validate a protection card against ADR-037 canonical form.
|
|
138
278
|
*
|
|
139
279
|
* Required: card_version, agent_id, mode (off|observe|nudge|enforce).
|
|
140
280
|
* Optional: thresholds (warn ≤ quarantine ≤ block, all in [0,1]),
|
|
141
281
|
* screen_surfaces (object of bools with the four named keys),
|
|
142
|
-
* trusted_sources (object of typed buckets, per-bucket deny-lists)
|
|
282
|
+
* trusted_sources (object of typed buckets, per-bucket deny-lists),
|
|
283
|
+
* protected_surface (org-declared asset policy — MNE-830/833).
|
|
143
284
|
*/
|
|
144
285
|
export function validateProtectionCard(card) {
|
|
145
286
|
const checks = [];
|
|
@@ -324,6 +465,10 @@ export function validateProtectionCard(card) {
|
|
|
324
465
|
}
|
|
325
466
|
}
|
|
326
467
|
}
|
|
468
|
+
// ── protected_surface (optional, MNE-830/833) ──
|
|
469
|
+
if (card.protected_surface !== undefined) {
|
|
470
|
+
validateProtectedSurface(card.protected_surface, checks);
|
|
471
|
+
}
|
|
327
472
|
return checks;
|
|
328
473
|
}
|
|
329
474
|
// ============================================================================
|
|
@@ -438,8 +583,23 @@ export async function protectionPublishCommand(file, agentName, options = {}) {
|
|
|
438
583
|
console.log();
|
|
439
584
|
}
|
|
440
585
|
catch (error) {
|
|
441
|
-
|
|
442
|
-
|
|
586
|
+
if (error instanceof MnemomApiError) {
|
|
587
|
+
if (error.effectiveStatus === 404) {
|
|
588
|
+
console.log("\n" +
|
|
589
|
+
fmt.error("Agent found but not writable: you can see this agent locally, but cannot publish to it in its current organization context.") +
|
|
590
|
+
"\n");
|
|
591
|
+
}
|
|
592
|
+
else if (error.effectiveStatus === 401) {
|
|
593
|
+
console.log("\n" + fmt.error(`Authentication failed: ${error.message}`) + "\n");
|
|
594
|
+
}
|
|
595
|
+
else {
|
|
596
|
+
console.log("\n" + fmt.error(`Failed to publish protection card: ${error.message}`) + "\n");
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
else {
|
|
600
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
601
|
+
console.log("\n" + fmt.error(`Failed to publish protection card: ${message}`) + "\n");
|
|
602
|
+
}
|
|
443
603
|
process.exit(1);
|
|
444
604
|
}
|
|
445
605
|
}
|
package/dist/commands/status.js
CHANGED
|
@@ -2,6 +2,7 @@ import { getGatewayUrl, getWebsiteUrl } from "../lib/config.js";
|
|
|
2
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
|
+
import { TRACES_PENDING_NOTE } from "../lib/onboarding.js";
|
|
5
6
|
export async function statusCommand(agentName) {
|
|
6
7
|
console.log(fmt.header("mnemom status"));
|
|
7
8
|
console.log();
|
|
@@ -63,10 +64,13 @@ async function checkAuthStatus() {
|
|
|
63
64
|
}
|
|
64
65
|
const auth = getAuthInfo();
|
|
65
66
|
if (auth) {
|
|
67
|
+
// OAuth tokens carry no identity; fall back to the granted scope (or a
|
|
68
|
+
// generic message) rather than printing "Logged in as undefined".
|
|
69
|
+
const who = auth.email ?? (auth.scope ? `scope ${auth.scope}` : "OAuth session");
|
|
66
70
|
return {
|
|
67
71
|
name: "Authentication",
|
|
68
72
|
status: "ok",
|
|
69
|
-
message: `Logged in as ${
|
|
73
|
+
message: `Logged in as ${who}`,
|
|
70
74
|
};
|
|
71
75
|
}
|
|
72
76
|
// API key auth (no email available)
|
|
@@ -195,6 +199,8 @@ async function showTraceSummary(agentId) {
|
|
|
195
199
|
}
|
|
196
200
|
else {
|
|
197
201
|
console.log("\nLast Activity: None");
|
|
202
|
+
// Eventual consistency: don't let a just-sent request read as "broken".
|
|
203
|
+
console.log("\n" + fmt.dim(TRACES_PENDING_NOTE));
|
|
198
204
|
}
|
|
199
205
|
}
|
|
200
206
|
catch {
|