@mnemom/mnemom 0.16.1 → 0.16.3
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/README.md +1 -0
- package/dist/commands/agents.d.ts +14 -0
- package/dist/commands/agents.js +100 -2
- package/dist/commands/card.d.ts +43 -0
- package/dist/commands/card.js +153 -102
- package/dist/commands/logs.js +11 -1
- package/dist/commands/onboard.d.ts +59 -0
- package/dist/commands/onboard.js +395 -0
- package/dist/commands/org.d.ts +13 -0
- package/dist/commands/org.js +63 -2
- package/dist/commands/protection.d.ts +10 -0
- package/dist/commands/protection.js +109 -0
- package/dist/commands/status.js +5 -0
- package/dist/commands/try-me.js +16 -1
- package/dist/commands/usage.d.ts +35 -0
- package/dist/commands/usage.js +265 -0
- package/dist/commands/wrap.d.ts +25 -0
- package/dist/commands/wrap.js +331 -0
- package/dist/index.js +192 -6
- package/dist/lib/agent-config.d.ts +27 -0
- package/dist/lib/agent-config.js +86 -0
- package/dist/lib/api.d.ts +122 -1
- package/dist/lib/api.js +128 -183
- package/dist/lib/auth.js +21 -1
- package/dist/lib/cli-config.d.ts +33 -0
- package/dist/lib/cli-config.js +70 -0
- package/dist/lib/config.d.ts +12 -0
- package/dist/lib/config.js +55 -3
- package/dist/lib/keyed-identity.d.ts +35 -0
- package/dist/lib/keyed-identity.js +363 -0
- package/dist/lib/oauth.d.ts +26 -4
- package/dist/lib/oauth.js +98 -29
- package/dist/lib/protection-drift.d.ts +117 -0
- package/dist/lib/protection-drift.js +180 -0
- package/dist/lib/skills.js +25 -12
- package/dist/lib/version-gate.d.ts +37 -0
- package/dist/lib/version-gate.js +84 -0
- package/package.json +7 -7
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mnemom wrap` — instrument an existing production agent through the Mnemom gateway (MNE-935, A4).
|
|
3
|
+
*
|
|
4
|
+
* Asks the developer for their provider + framework + agent name, makes a birth
|
|
5
|
+
* call to the gateway to provision the agent identity, seeds starter alignment +
|
|
6
|
+
* protection cards, and emits a drop-in code example. No reasoning change to the
|
|
7
|
+
* host agent.
|
|
8
|
+
*
|
|
9
|
+
* AUTH MODEL: Two credentials are involved:
|
|
10
|
+
* - Mnemom auth (MNEMOM_API_KEY or `mnemom login` JWT) — used for card writes
|
|
11
|
+
* - Provider API key (ANTHROPIC_API_KEY / OPENAI_API_KEY / GOOGLE_API_KEY) — used
|
|
12
|
+
* for the one-time gateway birth call; this is the same key the user's agent
|
|
13
|
+
* already holds, so the birth requires no new credential
|
|
14
|
+
*
|
|
15
|
+
* See SKILL-RUNNER-CONTRACT.md §2–§4 for the lifecycle + output contract.
|
|
16
|
+
*/
|
|
17
|
+
import { requireAuth } from "../lib/auth.js";
|
|
18
|
+
import { putAlignmentCard, putProtectionCard, MnemomApiError } from "../lib/api.js";
|
|
19
|
+
import { mergeAgentConfig } from "../lib/agent-config.js";
|
|
20
|
+
import { askSelect, askInput, isInteractive } from "../lib/prompt.js";
|
|
21
|
+
import { getGatewayUrl } from "../lib/config.js";
|
|
22
|
+
import { fmt } from "../lib/format.js";
|
|
23
|
+
const AGENT_HEADER = "x-mnemom-agent";
|
|
24
|
+
const PROVIDERS = {
|
|
25
|
+
anthropic: {
|
|
26
|
+
label: "Anthropic",
|
|
27
|
+
keyEnv: "ANTHROPIC_API_KEY",
|
|
28
|
+
keyHeader: "x-api-key",
|
|
29
|
+
defaultModel: "claude-haiku-4-5-20251001",
|
|
30
|
+
basePath: "/anthropic",
|
|
31
|
+
},
|
|
32
|
+
openai: {
|
|
33
|
+
label: "OpenAI",
|
|
34
|
+
keyEnv: "OPENAI_API_KEY",
|
|
35
|
+
keyHeader: "Authorization",
|
|
36
|
+
defaultModel: "gpt-4o-mini",
|
|
37
|
+
basePath: "/openai",
|
|
38
|
+
},
|
|
39
|
+
gemini: {
|
|
40
|
+
label: "Google Gemini",
|
|
41
|
+
keyEnv: "GOOGLE_API_KEY",
|
|
42
|
+
keyHeader: "x-goog-api-key",
|
|
43
|
+
defaultModel: "gemini-2.0-flash",
|
|
44
|
+
basePath: "/gemini",
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
export async function wrapCommand(options = {}) {
|
|
48
|
+
const json = !!options.json;
|
|
49
|
+
const nonInteractive = !!options.yes || json || !isInteractive();
|
|
50
|
+
const steps = [];
|
|
51
|
+
const say = (line = "") => {
|
|
52
|
+
if (!json)
|
|
53
|
+
console.log(line);
|
|
54
|
+
};
|
|
55
|
+
say(fmt.header("Mnemom — wrap"));
|
|
56
|
+
say();
|
|
57
|
+
say(fmt.dim("Instrument your existing agent through the Mnemom gateway."));
|
|
58
|
+
say();
|
|
59
|
+
// Phase 1 — Resolve + Auth (Mnemom session for card writes)
|
|
60
|
+
await requireAuth();
|
|
61
|
+
const gatewayUrl = getGatewayUrl().replace(/\/$/, "");
|
|
62
|
+
const provider = await resolveProvider(options, nonInteractive);
|
|
63
|
+
const framework = await resolveFramework(options, nonInteractive);
|
|
64
|
+
const agentName = await resolveAgentName(options, nonInteractive);
|
|
65
|
+
const providerKey = await resolveProviderKey(options, PROVIDERS[provider], nonInteractive, say);
|
|
66
|
+
steps.push({
|
|
67
|
+
step: "resolve",
|
|
68
|
+
status: "ok",
|
|
69
|
+
detail: `provider=${provider} framework=${framework} name=${agentName}`,
|
|
70
|
+
});
|
|
71
|
+
// Phase 2 — Birth: one gateway call to provision the identity
|
|
72
|
+
say();
|
|
73
|
+
say(`${fmt.badge("birth", "cyan")} Provisioning ${fmt.badge(agentName, "magenta")} through the gateway…`);
|
|
74
|
+
let agentId;
|
|
75
|
+
try {
|
|
76
|
+
agentId = await birthThroughGateway(gatewayUrl, provider, agentName, providerKey);
|
|
77
|
+
steps.push({ step: "birth", status: "ok", detail: agentId });
|
|
78
|
+
say(fmt.success(`Provisioned → ${agentId}`));
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
82
|
+
steps.push({ step: "birth", status: "error", detail: msg });
|
|
83
|
+
if (json) {
|
|
84
|
+
console.log(JSON.stringify({
|
|
85
|
+
skill: "wrap",
|
|
86
|
+
verdict: "error",
|
|
87
|
+
agent_id: null,
|
|
88
|
+
gateway_base_url: gatewayUrl,
|
|
89
|
+
next_step: null,
|
|
90
|
+
steps,
|
|
91
|
+
}, null, 2));
|
|
92
|
+
}
|
|
93
|
+
throw new Error(`Gateway birth failed: ${msg}`, { cause: err });
|
|
94
|
+
}
|
|
95
|
+
mergeAgentConfig({ agent_id: agentId, agent_name: agentName, gateway_url: gatewayUrl });
|
|
96
|
+
// Phase 2 — Seed alignment card
|
|
97
|
+
say();
|
|
98
|
+
say(`${fmt.badge("alignment", "cyan")} Seeding starter alignment card…`);
|
|
99
|
+
try {
|
|
100
|
+
await putAlignmentCard(agentId, JSON.stringify(buildStarterAlignmentCard(agentId)), "application/json");
|
|
101
|
+
steps.push({ step: "alignment", status: "ok" });
|
|
102
|
+
say(fmt.success("Alignment card set."));
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
106
|
+
steps.push({ step: "alignment", status: "error", detail: msg });
|
|
107
|
+
say(fmt.warn(`Alignment card skipped — set it later with \`mnemom card publish\`. (${msg})`));
|
|
108
|
+
}
|
|
109
|
+
// Phase 2 — Seed protection card
|
|
110
|
+
say();
|
|
111
|
+
say(`${fmt.badge("protection", "cyan")} Seeding starter protection card…`);
|
|
112
|
+
try {
|
|
113
|
+
await putProtectionCard(agentId, JSON.stringify(buildStarterProtectionCard(agentId)), "application/json");
|
|
114
|
+
steps.push({ step: "protection", status: "ok" });
|
|
115
|
+
say(fmt.success("Protection card set."));
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
119
|
+
steps.push({ step: "protection", status: "error", detail: msg });
|
|
120
|
+
say(fmt.warn(`Protection card skipped — set it later with \`mnemom protection publish\`. (${msg})`));
|
|
121
|
+
}
|
|
122
|
+
// Phase 3 — Report
|
|
123
|
+
say();
|
|
124
|
+
say(renderCodeExample(framework, provider, gatewayUrl, agentName));
|
|
125
|
+
say();
|
|
126
|
+
say(renderClaimInstructions(agentId));
|
|
127
|
+
if (json) {
|
|
128
|
+
console.log(JSON.stringify({
|
|
129
|
+
skill: "wrap",
|
|
130
|
+
verdict: "ok",
|
|
131
|
+
agent_id: agentId,
|
|
132
|
+
gateway_base_url: gatewayUrl,
|
|
133
|
+
next_step: "claim",
|
|
134
|
+
steps,
|
|
135
|
+
}, null, 2));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// ── Phase 1 resolvers ─────────────────────────────────────────────────────────
|
|
139
|
+
async function resolveProvider(options, nonInteractive) {
|
|
140
|
+
if (options.provider) {
|
|
141
|
+
const p = options.provider.toLowerCase();
|
|
142
|
+
if (!PROVIDERS[p]) {
|
|
143
|
+
throw new Error(`Unknown provider "${options.provider}". Choose: anthropic, openai, gemini`);
|
|
144
|
+
}
|
|
145
|
+
return p;
|
|
146
|
+
}
|
|
147
|
+
if (nonInteractive)
|
|
148
|
+
return "anthropic";
|
|
149
|
+
const choice = await askSelect("Which AI provider does your agent use?", [
|
|
150
|
+
"Anthropic (Claude)",
|
|
151
|
+
"OpenAI (GPT)",
|
|
152
|
+
"Google Gemini",
|
|
153
|
+
]);
|
|
154
|
+
if (choice?.startsWith("OpenAI"))
|
|
155
|
+
return "openai";
|
|
156
|
+
if (choice?.startsWith("Google"))
|
|
157
|
+
return "gemini";
|
|
158
|
+
return "anthropic";
|
|
159
|
+
}
|
|
160
|
+
async function resolveFramework(options, nonInteractive) {
|
|
161
|
+
if (options.framework) {
|
|
162
|
+
const f = options.framework.toLowerCase();
|
|
163
|
+
if (f === "python" || f === "py")
|
|
164
|
+
return "python";
|
|
165
|
+
if (f === "node" || f === "nodejs" || f === "js" || f === "ts" || f === "typescript")
|
|
166
|
+
return "node";
|
|
167
|
+
throw new Error(`Unknown framework "${options.framework}". Choose: python, node`);
|
|
168
|
+
}
|
|
169
|
+
if (nonInteractive)
|
|
170
|
+
return "python";
|
|
171
|
+
const choice = await askSelect("Which language/framework is your agent using?", [
|
|
172
|
+
"Python",
|
|
173
|
+
"Node.js / TypeScript",
|
|
174
|
+
]);
|
|
175
|
+
return choice?.startsWith("Node") ? "node" : "python";
|
|
176
|
+
}
|
|
177
|
+
async function resolveAgentName(options, nonInteractive) {
|
|
178
|
+
if (options.name?.trim())
|
|
179
|
+
return options.name.trim();
|
|
180
|
+
if (nonInteractive)
|
|
181
|
+
return "my-agent";
|
|
182
|
+
const name = (await askInput("What should we call this agent? (x-mnemom-agent value)")).trim();
|
|
183
|
+
return name || "my-agent";
|
|
184
|
+
}
|
|
185
|
+
async function resolveProviderKey(options, prov, nonInteractive, say) {
|
|
186
|
+
if (options.providerKey?.trim())
|
|
187
|
+
return options.providerKey.trim();
|
|
188
|
+
const fromEnv = process.env[prov.keyEnv]?.trim();
|
|
189
|
+
if (fromEnv) {
|
|
190
|
+
say(fmt.dim(`Using ${prov.keyEnv} from environment.`));
|
|
191
|
+
return fromEnv;
|
|
192
|
+
}
|
|
193
|
+
if (nonInteractive) {
|
|
194
|
+
throw new Error(`No ${prov.label} API key found. Set ${prov.keyEnv} or pass --provider-key <key>.`);
|
|
195
|
+
}
|
|
196
|
+
const key = (await askInput(`Enter your ${prov.label} API key (one-time birth call — never stored):`)).trim();
|
|
197
|
+
if (!key)
|
|
198
|
+
throw new Error(`A ${prov.label} API key is required to birth the agent.`);
|
|
199
|
+
return key;
|
|
200
|
+
}
|
|
201
|
+
// ── Birth call ────────────────────────────────────────────────────────────────
|
|
202
|
+
async function birthThroughGateway(gatewayUrl, provider, agentName, providerKey) {
|
|
203
|
+
const prov = PROVIDERS[provider];
|
|
204
|
+
const authValue = provider === "openai" ? `Bearer ${providerKey}` : providerKey;
|
|
205
|
+
const headers = {
|
|
206
|
+
"Content-Type": "application/json",
|
|
207
|
+
Accept: "application/json",
|
|
208
|
+
[prov.keyHeader]: authValue,
|
|
209
|
+
[AGENT_HEADER]: agentName,
|
|
210
|
+
};
|
|
211
|
+
if (provider === "anthropic") {
|
|
212
|
+
headers["anthropic-version"] = "2023-06-01";
|
|
213
|
+
}
|
|
214
|
+
let url;
|
|
215
|
+
let body;
|
|
216
|
+
switch (provider) {
|
|
217
|
+
case "openai":
|
|
218
|
+
url = `${gatewayUrl}${prov.basePath}/v1/chat/completions`;
|
|
219
|
+
body = JSON.stringify({
|
|
220
|
+
model: prov.defaultModel,
|
|
221
|
+
max_tokens: 16,
|
|
222
|
+
messages: [{ role: "user", content: "hello" }],
|
|
223
|
+
});
|
|
224
|
+
break;
|
|
225
|
+
case "gemini":
|
|
226
|
+
url = `${gatewayUrl}${prov.basePath}/v1beta/models/${prov.defaultModel}:generateContent`;
|
|
227
|
+
body = JSON.stringify({ contents: [{ parts: [{ text: "hello" }] }] });
|
|
228
|
+
break;
|
|
229
|
+
case "anthropic":
|
|
230
|
+
default:
|
|
231
|
+
url = `${gatewayUrl}${prov.basePath}/v1/messages`;
|
|
232
|
+
body = JSON.stringify({
|
|
233
|
+
model: prov.defaultModel,
|
|
234
|
+
max_tokens: 16,
|
|
235
|
+
messages: [{ role: "user", content: "hello" }],
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
const response = await fetch(url, { method: "POST", headers, body });
|
|
239
|
+
const agentId = response.headers.get(AGENT_HEADER);
|
|
240
|
+
if (!response.ok && !agentId) {
|
|
241
|
+
const text = await response.text().catch(() => "");
|
|
242
|
+
throw new MnemomApiError(response.status, `Gateway returned ${response.status}: ${text.slice(0, 300)}`);
|
|
243
|
+
}
|
|
244
|
+
if (!agentId || !/^mnm-/.test(agentId)) {
|
|
245
|
+
throw new Error(`Gateway returned no mnm- agent id on the ${AGENT_HEADER} response header (status ${response.status}).`);
|
|
246
|
+
}
|
|
247
|
+
return agentId;
|
|
248
|
+
}
|
|
249
|
+
// ── Starter cards ─────────────────────────────────────────────────────────────
|
|
250
|
+
function buildStarterAlignmentCard(agentId) {
|
|
251
|
+
return {
|
|
252
|
+
card_version: "unified/2026-04-26",
|
|
253
|
+
agent_id: agentId,
|
|
254
|
+
autonomy_mode: "observe",
|
|
255
|
+
integrity_mode: "observe",
|
|
256
|
+
principal: { type: "agent", identifier: agentId, relationship: "delegated_authority" },
|
|
257
|
+
values: { declared: ["transparency", "safety", "honesty"] },
|
|
258
|
+
autonomy: {
|
|
259
|
+
bounded_actions: ["respond_to_prompts"],
|
|
260
|
+
forbidden_actions: [],
|
|
261
|
+
escalation_triggers: [],
|
|
262
|
+
},
|
|
263
|
+
audit: { retention_days: 30, queryable: false, trace_format: "otel" },
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function buildStarterProtectionCard(agentId) {
|
|
267
|
+
return {
|
|
268
|
+
card_version: "protection/2026-04-26",
|
|
269
|
+
agent_id: agentId,
|
|
270
|
+
mode: "observe",
|
|
271
|
+
thresholds: { warn: 0.3, quarantine: 0.6, block: 0.9 },
|
|
272
|
+
screen_surfaces: {
|
|
273
|
+
incoming: true,
|
|
274
|
+
outgoing: true,
|
|
275
|
+
tool_calls: true,
|
|
276
|
+
tool_responses: true,
|
|
277
|
+
},
|
|
278
|
+
trusted_sources: { domains: [], agent_ids: [], ip_ranges: [] },
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
// ── Code example rendering ────────────────────────────────────────────────────
|
|
282
|
+
function renderCodeExample(framework, provider, gatewayUrl, agentName) {
|
|
283
|
+
const prov = PROVIDERS[provider];
|
|
284
|
+
const lines = [fmt.section("Drop-in gateway wrapper"), ""];
|
|
285
|
+
if (framework === "python") {
|
|
286
|
+
renderPythonSnippet(lines, provider, prov, gatewayUrl, agentName);
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
renderNodeSnippet(lines, provider, prov, gatewayUrl, agentName);
|
|
290
|
+
}
|
|
291
|
+
lines.push("", fmt.dim("No other changes needed — your agent logic is unchanged."));
|
|
292
|
+
return lines.join("\n");
|
|
293
|
+
}
|
|
294
|
+
function renderPythonSnippet(lines, provider, prov, gatewayUrl, agentName) {
|
|
295
|
+
const base = `${gatewayUrl}${prov.basePath}`;
|
|
296
|
+
if (provider === "anthropic") {
|
|
297
|
+
lines.push(" import anthropic", "", " client = anthropic.Anthropic(", ` base_url="${base}",`, ` default_headers={"x-mnemom-agent": "${agentName}"},`, " )");
|
|
298
|
+
}
|
|
299
|
+
else if (provider === "openai") {
|
|
300
|
+
lines.push(" from openai import OpenAI", "", " client = OpenAI(", ` base_url="${base}/v1",`, ` default_headers={"x-mnemom-agent": "${agentName}"},`, " )");
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
lines.push(" # Google Gemini via Mnemom gateway", ` # Set base URL to: ${base}`, ` # Add header: x-mnemom-agent: ${agentName}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function renderNodeSnippet(lines, provider, prov, gatewayUrl, agentName) {
|
|
307
|
+
const base = `${gatewayUrl}${prov.basePath}`;
|
|
308
|
+
if (provider === "anthropic") {
|
|
309
|
+
lines.push(' import Anthropic from "@anthropic-ai/sdk";', "", " const client = new Anthropic({", ` baseURL: "${base}",`, ` defaultHeaders: { "x-mnemom-agent": "${agentName}" },`, " });");
|
|
310
|
+
}
|
|
311
|
+
else if (provider === "openai") {
|
|
312
|
+
lines.push(' import OpenAI from "openai";', "", " const client = new OpenAI({", ` baseURL: "${base}/v1",`, ` defaultHeaders: { "x-mnemom-agent": "${agentName}" },`, " });");
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
lines.push(" // Google Gemini via Mnemom gateway", ` // Set base URL to: ${base}`, ` // Add header: "x-mnemom-agent": "${agentName}"`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function renderClaimInstructions(agentId) {
|
|
319
|
+
const lines = [
|
|
320
|
+
fmt.section("Next step — claim this agent"),
|
|
321
|
+
"",
|
|
322
|
+
fmt.label(" Agent ID:", agentId),
|
|
323
|
+
"",
|
|
324
|
+
fmt.dim(" Claim this agent so you own its identity on the platform:"),
|
|
325
|
+
"",
|
|
326
|
+
` mnemom agents claim ${agentId}`,
|
|
327
|
+
"",
|
|
328
|
+
fmt.dim(" After claiming, traces appear in `mnemom logs`."),
|
|
329
|
+
];
|
|
330
|
+
return lines.join("\n");
|
|
331
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -8,9 +8,9 @@ import { logsCommand } from "./commands/logs.js";
|
|
|
8
8
|
import { licenseActivateCommand, licenseStatusCommand, licenseDeactivateCommand, } from "./commands/license.js";
|
|
9
9
|
import { cardShowCommand, cardPublishCommand, cardValidateCommand, cardEditCommand, cardEvaluateCommand, } from "./commands/card.js";
|
|
10
10
|
import { policyInitCommand, policyValidateCommand, policyPublishCommand, policyListCommand, policyTestCommand, policyEvaluateCommand, } from "./commands/policy.js";
|
|
11
|
-
import { protectionShowCommand, protectionPublishCommand, protectionValidateCommand, protectionEditCommand, } from "./commands/protection.js";
|
|
12
|
-
import { agentsListCommand, agentsClaimCommand } from "./commands/agents.js";
|
|
13
|
-
import { orgListCommand, orgShowCommand } from "./commands/org.js";
|
|
11
|
+
import { protectionShowCommand, protectionPublishCommand, protectionValidateCommand, protectionEditCommand, protectionDriftCommand, } from "./commands/protection.js";
|
|
12
|
+
import { agentsListCommand, agentsClaimCommand, agentsMoveCommand } from "./commands/agents.js";
|
|
13
|
+
import { orgListCommand, orgShowCommand, orgUseCommand } from "./commands/org.js";
|
|
14
14
|
import { teamListCommand, teamShowCommand, teamTemplateCommand, teamPreviewComposeCommand, teamAdminGrantCommand, teamAdminRevokeCommand, teamAdminListCommand, teamCoverageCommand, } from "./commands/team.js";
|
|
15
15
|
import { advisoriesListCommand, advisoriesShowCommand } from "./commands/advisories.js";
|
|
16
16
|
import { postureListCommand, postureShowCommand, postureCreateCommand, postureUpdateCommand, postureCloneCommand, postureRevisionsCommand, postureDiffCommand, postureAssignCommand, postureUnassignCommand, posturePreviewComposeCommand, postureDeleteCommand, } from "./commands/posture.js";
|
|
@@ -20,7 +20,10 @@ import { apiKeyListCommand, apiKeyCreateCommand, apiKeyRotateCommand, apiKeyRevo
|
|
|
20
20
|
import { webhooksListCommand, webhooksGetCommand, webhooksCreateCommand, webhooksUpdateCommand, webhooksDeleteCommand, webhooksRotateSecretCommand, webhooksTriggerCommand, webhooksListDeliveriesCommand, webhooksRedeliverCommand, webhooksReplayCommand, } from "./commands/webhooks.js";
|
|
21
21
|
import { listenCommand } from "./commands/listen.js";
|
|
22
22
|
import { tryMeCommand } from "./commands/try-me.js";
|
|
23
|
+
import { wrapCommand } from "./commands/wrap.js";
|
|
24
|
+
import { onboardCommand } from "./commands/onboard.js";
|
|
23
25
|
import { skillsListCommand, skillsDescribeCommand } from "./commands/skills.js";
|
|
26
|
+
import { usageCommand, parseNumericFlag, parseDaysFlag } from "./commands/usage.js";
|
|
24
27
|
program
|
|
25
28
|
.name("mnemom")
|
|
26
29
|
.description("Transparent AI agent tracing")
|
|
@@ -71,6 +74,78 @@ program
|
|
|
71
74
|
process.exit(1);
|
|
72
75
|
}
|
|
73
76
|
});
|
|
77
|
+
// ── wrap (MNE-935, A4) — instrument an existing agent through the gateway ────
|
|
78
|
+
//
|
|
79
|
+
// `mnemom wrap` is the "bring your production agent" skill: it asks for
|
|
80
|
+
// provider + framework + agent name, births the agent through the gateway,
|
|
81
|
+
// seeds starter alignment + protection cards, and emits a drop-in code snippet.
|
|
82
|
+
// Extends the skill-runner contract from A1 (MNE-932). See commands/wrap.ts.
|
|
83
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
84
|
+
program
|
|
85
|
+
.command("wrap")
|
|
86
|
+
.description("Instrument an existing agent through the Mnemom gateway (born → cards → snippet)")
|
|
87
|
+
.option("--provider <provider>", "AI provider: anthropic, openai, gemini (default: prompt)")
|
|
88
|
+
.option("--framework <framework>", "Language/framework: python, node (default: prompt)")
|
|
89
|
+
.option("--name <name>", "Agent name used as the x-mnemom-agent header value")
|
|
90
|
+
.option("--provider-key <key>", "Provider API key for the one-time birth call (default: reads env)")
|
|
91
|
+
.option("-y, --yes", "Non-interactive: accept defaults, skip all prompts")
|
|
92
|
+
.option("--json", "Emit machine-readable result (skill / verdict / agent_id / steps)")
|
|
93
|
+
.action(async (opts) => {
|
|
94
|
+
try {
|
|
95
|
+
await wrapCommand({
|
|
96
|
+
provider: opts.provider,
|
|
97
|
+
framework: opts.framework,
|
|
98
|
+
name: opts.name,
|
|
99
|
+
providerKey: opts.providerKey,
|
|
100
|
+
yes: opts.yes,
|
|
101
|
+
json: opts.json,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
// ── onboard (MNE-933, A2) — self-onboard the calling agent end-to-end ────────
|
|
110
|
+
//
|
|
111
|
+
// `mnemom onboard` runs the sovereignty path for the CALLING agent itself:
|
|
112
|
+
// scan its trust posture → claim its identity → declare an alignment card →
|
|
113
|
+
// surface its Trust Rating → hand back a public badge URL. One command, no
|
|
114
|
+
// manifest. Additive + human-in-the-loop preserving: the only mutation is the
|
|
115
|
+
// agent's own alignment-card declaration (its standing scoped token). The agent
|
|
116
|
+
// authenticates as its OWN device-grant principal (MNE-944). The Trust Rating
|
|
117
|
+
// is PROVISIONAL until the observer pipeline generates traces — the signed
|
|
118
|
+
// rating + rendered badge are computed server-side, post-hoc. See
|
|
119
|
+
// commands/onboard.ts for the auth model.
|
|
120
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
121
|
+
program
|
|
122
|
+
.command("onboard")
|
|
123
|
+
.description("Self-onboard the calling agent (scan → claim → declare → rating → badge); the Trust Rating is provisional until the observer pipeline generates traces")
|
|
124
|
+
.option("--json", "Emit machine-readable step outcomes (implies non-interactive)")
|
|
125
|
+
.option("-y, --yes", "Non-interactive: accept defaults, skip all prompts")
|
|
126
|
+
.option("--key <key>", "The agent's provider API key — the CLI derives the claim hash proof")
|
|
127
|
+
.option("--hash-proof <hex>", "A pre-computed 64-hex hash proof (advanced/CI; alternative to --key)")
|
|
128
|
+
.option("--no-open", "Never auto-open the badge URL in a browser — just print it")
|
|
129
|
+
.action(async (opts) => {
|
|
130
|
+
try {
|
|
131
|
+
// The calling agent is selected via the program-level `--agent` (which
|
|
132
|
+
// shadows any subcommand `--agent` under commander@12 — the MNE-238
|
|
133
|
+
// class), mirroring how `status`/`logs` read it.
|
|
134
|
+
const parentOpts = program.opts();
|
|
135
|
+
await onboardCommand({
|
|
136
|
+
json: opts.json,
|
|
137
|
+
yes: opts.yes,
|
|
138
|
+
key: opts.key,
|
|
139
|
+
hashProof: opts.hashProof,
|
|
140
|
+
open: opts.open,
|
|
141
|
+
agent: parentOpts.agent,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
74
149
|
// ── skills (MNE-1324) — discover the zero-install skill surface ──────────────
|
|
75
150
|
const skills = program
|
|
76
151
|
.command("skills")
|
|
@@ -346,6 +421,25 @@ protectionCmd
|
|
|
346
421
|
process.exit(1);
|
|
347
422
|
}
|
|
348
423
|
});
|
|
424
|
+
protectionCmd
|
|
425
|
+
.command("drift")
|
|
426
|
+
.argument("<file>", "Path to a committed protection-card snapshot (YAML or JSON)")
|
|
427
|
+
.description("Compare a committed snapshot against the live canonical card (read-only)")
|
|
428
|
+
.option("--strict", "Also fail on live fields the snapshot does not record")
|
|
429
|
+
.option("--json", "Emit the drift result as JSON")
|
|
430
|
+
.action(async (file, subOpts) => {
|
|
431
|
+
try {
|
|
432
|
+
const opts = program.opts();
|
|
433
|
+
await protectionDriftCommand(file, opts.agent, {
|
|
434
|
+
strict: subOpts.strict,
|
|
435
|
+
json: subOpts.json,
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
catch (error) {
|
|
439
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
440
|
+
process.exit(1);
|
|
441
|
+
}
|
|
442
|
+
});
|
|
349
443
|
// ============================================================================
|
|
350
444
|
// Policy commands (removed — stubs with migration guidance)
|
|
351
445
|
// ============================================================================
|
|
@@ -418,15 +512,27 @@ const agentsCmd = program
|
|
|
418
512
|
agentsCmd
|
|
419
513
|
.command("claim <id-or-name>")
|
|
420
514
|
.description("Claim an agent into an org (ADR-062 claim-to-org)")
|
|
421
|
-
.option("--org <slug>", "Org slug or id to claim into (default: your personal org)")
|
|
515
|
+
.option("--org <slug>", "Org slug or id to claim into (default: your ACTIVE org from `mnemom org use`, else your personal org)")
|
|
422
516
|
.option("--key <key>", "The agent's provider API key — the CLI derives the hash proof from it")
|
|
423
517
|
.option("--hash-proof <hex>", "A pre-computed 64-hex SHA-256 proof (advanced/CI; alternative to --key)")
|
|
424
518
|
.option("--name <name>", "Provisioned agent name for proof derivation — auto-resolved from the agent id when possible; pass only to override")
|
|
425
519
|
.option("--json", "Output JSON instead of human-readable text")
|
|
426
520
|
.action(async (idOrName, opts) => {
|
|
427
521
|
try {
|
|
522
|
+
// `agentsCmd` (the parent `agents` command) ALSO defines `--org` (for
|
|
523
|
+
// `mnemom agents --org <id>` list-scoping). Commander@12 binds a flag
|
|
524
|
+
// shared by a parent and a subcommand to whichever of the two owns it
|
|
525
|
+
// and runs first in the parse chain — here that's the parent — so
|
|
526
|
+
// `agents claim <id> --org <slug>` had its --org silently swallowed by
|
|
527
|
+
// `agentsCmd` before this subcommand's own `--org` option ever saw it
|
|
528
|
+
// (MNE-2496: confirmed live, the agent always landed in the caller's
|
|
529
|
+
// personal org regardless of --org). Same shadow class as the global
|
|
530
|
+
// `--agent` fix at MNE-238 (see the `advisories list`/`show` actions
|
|
531
|
+
// below) — resolve the local value first, falling back to the
|
|
532
|
+
// parent's captured value so either parse order still works.
|
|
533
|
+
const org = opts.org ?? agentsCmd.opts().org;
|
|
428
534
|
await agentsClaimCommand(idOrName, {
|
|
429
|
-
org
|
|
535
|
+
org,
|
|
430
536
|
key: opts.key,
|
|
431
537
|
hashProof: opts.hashProof,
|
|
432
538
|
name: opts.name,
|
|
@@ -438,6 +544,21 @@ agentsCmd
|
|
|
438
544
|
process.exit(1);
|
|
439
545
|
}
|
|
440
546
|
});
|
|
547
|
+
agentsCmd
|
|
548
|
+
.command("move <id-or-name>")
|
|
549
|
+
.description("Move an agent to another org — requires owner/admin in BOTH the current and destination org " +
|
|
550
|
+
"(role-based; no agent key needed, unlike re-claim)")
|
|
551
|
+
.option("--to <slug-or-id>", "Destination org (required; never defaults to the active org)")
|
|
552
|
+
.option("--json", "Output JSON instead of human-readable text")
|
|
553
|
+
.action(async (idOrName, opts) => {
|
|
554
|
+
try {
|
|
555
|
+
await agentsMoveCommand(idOrName, { to: opts.to, json: opts.json });
|
|
556
|
+
}
|
|
557
|
+
catch (error) {
|
|
558
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
559
|
+
process.exit(1);
|
|
560
|
+
}
|
|
561
|
+
});
|
|
441
562
|
// ============================================================================
|
|
442
563
|
// Organization management (ADR-044, Piece 1 of T1-3.1)
|
|
443
564
|
// ============================================================================
|
|
@@ -457,6 +578,21 @@ orgCmd
|
|
|
457
578
|
process.exit(1);
|
|
458
579
|
}
|
|
459
580
|
});
|
|
581
|
+
orgCmd
|
|
582
|
+
.command("use [slug-or-id]")
|
|
583
|
+
.description("Set the ACTIVE org — the default for org-scoped commands like `agents claim` " +
|
|
584
|
+
"(login binds no org; without this, claims land in your personal org). " +
|
|
585
|
+
"No argument shows the current setting.")
|
|
586
|
+
.option("--clear", "Forget the active org (org-scoped commands revert to your personal org)")
|
|
587
|
+
.action(async (slugOrId, options) => {
|
|
588
|
+
try {
|
|
589
|
+
await orgUseCommand(slugOrId, { clear: options.clear });
|
|
590
|
+
}
|
|
591
|
+
catch (error) {
|
|
592
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
593
|
+
process.exit(1);
|
|
594
|
+
}
|
|
595
|
+
});
|
|
460
596
|
orgCmd
|
|
461
597
|
.command("show [org_id]")
|
|
462
598
|
.description("Show details of an org (default: your personal org if --personal, else single membership)")
|
|
@@ -1060,7 +1196,8 @@ postureCmd
|
|
|
1060
1196
|
// ============================================================================
|
|
1061
1197
|
program
|
|
1062
1198
|
.command("login")
|
|
1063
|
-
.description("Authenticate with your Mnemom account"
|
|
1199
|
+
.description("Authenticate with your Mnemom account (note: login binds no org — set a default " +
|
|
1200
|
+
"for org-scoped commands with `mnemom org use <slug>`)")
|
|
1064
1201
|
.option("--no-browser", "Use the device code flow instead of opening a browser")
|
|
1065
1202
|
.action(async (options) => {
|
|
1066
1203
|
try {
|
|
@@ -1443,6 +1580,55 @@ program
|
|
|
1443
1580
|
process.exit(1);
|
|
1444
1581
|
}
|
|
1445
1582
|
});
|
|
1583
|
+
// ============================================================================
|
|
1584
|
+
// Usage attribution (MNE-3215 W6 / issue #1221)
|
|
1585
|
+
//
|
|
1586
|
+
// `mnemom usage --org <id>` shows per-person token and request consumption
|
|
1587
|
+
// for an org window. Gated by USAGE_ATTRIBUTION_API_ENABLED on the API side
|
|
1588
|
+
// (defaults off in every environment). Reports consumption only — no currency,
|
|
1589
|
+
// no spend, no cost.
|
|
1590
|
+
//
|
|
1591
|
+
// Every flag below maps 1:1 onto a query parameter the endpoint documents in
|
|
1592
|
+
// the API's `openapi.json` (`days`, `person_id`, `provider`, `model`, `limit`,
|
|
1593
|
+
// `cursor`). It is deliberately not a friendlier invented vocabulary: the first
|
|
1594
|
+
// version of this command offered `--period` and `--page`, neither of which the
|
|
1595
|
+
// endpoint accepts, so the server silently ignored both and returned an
|
|
1596
|
+
// unfiltered first page.
|
|
1597
|
+
// ============================================================================
|
|
1598
|
+
program
|
|
1599
|
+
.command("usage")
|
|
1600
|
+
.description("Show per-person token and request consumption for an org")
|
|
1601
|
+
.requiredOption("--org <id>", "Org ID to show consumption for")
|
|
1602
|
+
.option("--days <7|30|90>", "Reporting window in days (default: 30)")
|
|
1603
|
+
.option("--person <id>", "Filter to one person's consumption")
|
|
1604
|
+
.option("--provider <name>", "Filter to one provider")
|
|
1605
|
+
.option("--model <name>", "Filter to one model")
|
|
1606
|
+
.option("--limit <n>", "Max rows per page (default: 100, max 200)")
|
|
1607
|
+
.option("--cursor <cursor>", "Continue from a previous page's cursor")
|
|
1608
|
+
.option("--json", "Emit raw JSON instead of rendered output")
|
|
1609
|
+
.action(async (options) => {
|
|
1610
|
+
try {
|
|
1611
|
+
// A miskeyed numeric flag (e.g. `--limit foo`, `--limit 2abc`,
|
|
1612
|
+
// `--limit 0`, `--days 45`) warns and falls back to the default rather
|
|
1613
|
+
// than silently acting on a value the user did not type. Both live in
|
|
1614
|
+
// commands/usage.ts so their reject-and-warn arms are unit-testable
|
|
1615
|
+
// without driving Commander.
|
|
1616
|
+
await usageCommand({
|
|
1617
|
+
org: options.org,
|
|
1618
|
+
days: parseDaysFlag(options.days),
|
|
1619
|
+
person: options.person,
|
|
1620
|
+
provider: options.provider,
|
|
1621
|
+
model: options.model,
|
|
1622
|
+
limit: parseNumericFlag("--limit", options.limit),
|
|
1623
|
+
cursor: options.cursor,
|
|
1624
|
+
json: options.json,
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1627
|
+
catch (error) {
|
|
1628
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
1629
|
+
process.exit(1);
|
|
1630
|
+
}
|
|
1631
|
+
});
|
|
1446
1632
|
// Export the fully-assembled commander program so tooling (e.g. the
|
|
1447
1633
|
// command-tree snapshot generator in scripts/gen-command-tree.mjs) can
|
|
1448
1634
|
// statically introspect the command surface WITHOUT executing the CLI.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent identity config store — ~/.mnemom/agent.json.
|
|
3
|
+
*
|
|
4
|
+
* Persists the calling agent's resolved identity so skill verbs (try-me,
|
|
5
|
+
* onboard, wrap) write on first success and read thereafter — no re-prompt for
|
|
6
|
+
* identity on subsequent invocations (MNE-937).
|
|
7
|
+
*
|
|
8
|
+
* Co-located with auth.ts → auth.json. Separate by design: auth.json holds
|
|
9
|
+
* bearer credentials (wiped on logout); agent.json holds identity metadata
|
|
10
|
+
* that survives re-login and provider key rotation.
|
|
11
|
+
*/
|
|
12
|
+
export interface AgentConfig {
|
|
13
|
+
agent_id?: string;
|
|
14
|
+
agent_name?: string;
|
|
15
|
+
org_id?: string;
|
|
16
|
+
gateway_url?: string;
|
|
17
|
+
}
|
|
18
|
+
/** Load the agent config; a missing or corrupt file returns `{}`. */
|
|
19
|
+
export declare function loadAgentConfig(): AgentConfig;
|
|
20
|
+
/** Persist the full agent config (replaces the file). */
|
|
21
|
+
export declare function saveAgentConfig(config: AgentConfig): void;
|
|
22
|
+
/** Shallow-merge `partial` into the existing config and persist. */
|
|
23
|
+
export declare function mergeAgentConfig(partial: Partial<AgentConfig>): void;
|
|
24
|
+
export declare function getAgentId(): string | undefined;
|
|
25
|
+
export declare function getAgentName(): string | undefined;
|
|
26
|
+
export declare function getOrgId(): string | undefined;
|
|
27
|
+
export declare function getGatewayUrl(): string | undefined;
|