@mnemom/mnemom 0.7.2 → 0.8.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.
@@ -1,2 +0,0 @@
1
- import { Command } from "commander";
2
- export declare function makeMigrateConfigCommand(): Command;
@@ -1,72 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as os from "node:os";
3
- import * as path from "node:path";
4
- import { Command } from "commander";
5
- const OPENCLAW_CONFIG_PATH = path.join(os.homedir(), ".openclaw", "openclaw.json");
6
- export function makeMigrateConfigCommand() {
7
- const cmd = new Command("migrate-config");
8
- cmd
9
- .description("Migrate ~/.openclaw/openclaw.json provider keys from smoltbot* to mnemom*")
10
- .action(async () => {
11
- // 1. Check if OpenClaw config exists
12
- if (!fs.existsSync(OPENCLAW_CONFIG_PATH)) {
13
- console.log("OpenClaw not detected at ~/.openclaw/openclaw.json. Nothing to migrate.");
14
- return;
15
- }
16
- // 2. Read and parse the config
17
- let config = null;
18
- try {
19
- const raw = fs.readFileSync(OPENCLAW_CONFIG_PATH, "utf-8");
20
- config = JSON.parse(raw);
21
- }
22
- catch {
23
- console.error("Error: ~/.openclaw/openclaw.json is malformed JSON.");
24
- process.exit(1);
25
- return;
26
- }
27
- if (!config)
28
- return;
29
- // 3. Get models.providers — must be an object
30
- const models = config.models;
31
- const providers = models?.providers;
32
- if (!providers || typeof providers !== "object" || Object.keys(providers).length === 0) {
33
- console.log("No providers found.");
34
- return;
35
- }
36
- // 4. Find all keys matching /^smoltbot/
37
- const keysToMigrate = Object.keys(providers).filter((k) => /^smoltbot/.test(k));
38
- if (keysToMigrate.length === 0) {
39
- console.log("No smoltbot* provider keys found. Nothing to migrate.");
40
- return;
41
- }
42
- // 5. Migrate each key
43
- const migrations = [];
44
- for (const oldKey of keysToMigrate) {
45
- const newKey = oldKey.replace(/^smoltbot/, "mnemom");
46
- const entry = providers[oldKey];
47
- // Deep-copy the provider entry
48
- const newEntry = { ...entry };
49
- // Rename x-smoltbot-agent header if present
50
- const defaultHeaders = newEntry.defaultHeaders;
51
- if (defaultHeaders && "x-smoltbot-agent" in defaultHeaders) {
52
- const headersCopy = { ...defaultHeaders };
53
- headersCopy["x-mnemom-agent"] = headersCopy["x-smoltbot-agent"];
54
- delete headersCopy["x-smoltbot-agent"];
55
- newEntry.defaultHeaders = headersCopy;
56
- }
57
- // Remove old key, add new key
58
- delete providers[oldKey];
59
- providers[newKey] = newEntry;
60
- migrations.push({ from: oldKey, to: newKey });
61
- }
62
- // 6. Write back
63
- fs.writeFileSync(OPENCLAW_CONFIG_PATH, JSON.stringify(config, null, 2));
64
- // 7. Print summary
65
- console.log(`✓ Migrated ${migrations.length} provider${migrations.length !== 1 ? "s" : ""}:`);
66
- for (const { from, to } of migrations) {
67
- console.log(` ${from} → ${to}`);
68
- }
69
- console.log("\nYour agent config at ~/.smoltbot/ is unchanged.\n");
70
- });
71
- return cmd;
72
- }
@@ -1,6 +0,0 @@
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>;
@@ -1,362 +0,0 @@
1
- import { exec } from "node:child_process";
2
- import * as crypto from "node:crypto";
3
- import { loadConfig, saveConfig, deriveAgentIdWithName, } from "../lib/config.js";
4
- import { detectProviders, configureNamedAgentProviders, } from "../lib/openclaw.js";
5
- import { getLatestModels, } from "../lib/models.js";
6
- import { askYesNo, askInput, askSelect, isInteractive } from "../lib/prompt.js";
7
- import { fmt } from "../lib/format.js";
8
- const GATEWAY_URL = "https://gateway.mnemom.ai";
9
- const DASHBOARD_URL = "https://mnemom.ai";
10
- /** Validate agent name: alphanumeric + hyphens, 1-32 chars, not "default" */
11
- function validateAgentName(name) {
12
- if (name.toLowerCase() === "default") {
13
- return '"default" is reserved. Choose a different name.';
14
- }
15
- if (!/^[a-zA-Z0-9][a-zA-Z0-9-]{0,30}[a-zA-Z0-9]$/.test(name) && !/^[a-zA-Z0-9]{1,2}$/.test(name)) {
16
- return "Name must be 1-32 alphanumeric characters or hyphens, cannot start/end with hyphen.";
17
- }
18
- return null;
19
- }
20
- export async function registerCommand(name, options = {}) {
21
- console.log(fmt.header("smoltbot register - Add a named agent"));
22
- console.log();
23
- // Validate name
24
- const nameError = validateAgentName(name);
25
- if (nameError) {
26
- console.log(fmt.error(nameError) + "\n");
27
- process.exit(1);
28
- }
29
- // Load config
30
- const config = loadConfig();
31
- if (!config) {
32
- console.log(fmt.error("smoltbot is not initialized") + "\n");
33
- console.log("Run `smoltbot init` first to set up the default agent.\n");
34
- process.exit(1);
35
- }
36
- // Check for duplicate
37
- if (config.agents[name]) {
38
- console.log(fmt.warn(`Agent "${name}" already exists`) + "\n");
39
- console.log(fmt.label(" Agent ID:", ` ${config.agents[name].agentId}`));
40
- console.log();
41
- if (!isInteractive()) {
42
- process.exit(1);
43
- }
44
- const overwrite = await askYesNo(`Overwrite agent "${name}"?`, false);
45
- if (!overwrite) {
46
- console.log("\nNo changes made.\n");
47
- return;
48
- }
49
- console.log();
50
- }
51
- // Detect providers
52
- const detection = detectProviders();
53
- if (!detection.installed && !options.standalone) {
54
- console.log(fmt.error("OpenClaw not found. Use --standalone for standalone mode.") + "\n");
55
- process.exit(1);
56
- }
57
- // Find API key for agent ID derivation
58
- let apiKey;
59
- let selectedProvider = "anthropic";
60
- const PROVIDER_LABELS = {
61
- anthropic: "Anthropic",
62
- openai: "OpenAI",
63
- gemini: "Gemini",
64
- };
65
- const KEY_PREFIXES = {
66
- anthropic: "sk-ant-",
67
- openai: "sk-",
68
- gemini: "AIza",
69
- };
70
- const ENV_VARS = {
71
- anthropic: "ANTHROPIC_API_KEY",
72
- openai: "OPENAI_API_KEY",
73
- gemini: "GEMINI_API_KEY",
74
- };
75
- if (options.standalone || !detection.installed) {
76
- // Standalone: ask which provider, then prompt for key
77
- if (isInteractive()) {
78
- const choice = await askSelect("Which provider will this agent use?", ["Anthropic", "OpenAI", "Gemini"]);
79
- const providerMap = {
80
- Anthropic: "anthropic",
81
- OpenAI: "openai",
82
- Gemini: "gemini",
83
- };
84
- selectedProvider = (choice ? providerMap[choice] : "anthropic") || "anthropic";
85
- apiKey = await askInput(`${PROVIDER_LABELS[selectedProvider]} API key (${KEY_PREFIXES[selectedProvider]}...):`, true);
86
- }
87
- else {
88
- apiKey = process.env.ANTHROPIC_API_KEY
89
- || process.env.OPENAI_API_KEY
90
- || process.env.GEMINI_API_KEY;
91
- }
92
- }
93
- else {
94
- // OpenClaw: use first available key
95
- for (const provider of ["anthropic", "openai", "gemini"]) {
96
- const info = detection.providers[provider];
97
- if (info.hasApiKey && info.apiKey) {
98
- apiKey = info.apiKey;
99
- break;
100
- }
101
- }
102
- }
103
- if (!apiKey) {
104
- console.log(fmt.error("No API key available for agent identity") + "\n");
105
- process.exit(1);
106
- }
107
- // Derive agent ID with name
108
- const agentId = deriveAgentIdWithName(apiKey, name);
109
- console.log(fmt.label("Agent ID:", ` ${agentId}`));
110
- console.log(fmt.label("Name: ", ` ${name}`));
111
- console.log(fmt.label("Gateway: ", ` ${config.gateway} (x-smoltbot-agent: ${name})`));
112
- console.log();
113
- // Configure OpenClaw providers for named agent
114
- if (detection.installed && !options.standalone) {
115
- console.log("Configuring OpenClaw providers for named agent...\n");
116
- const verifiedProviders = [];
117
- for (const provider of ["anthropic", "openai", "gemini"]) {
118
- const info = detection.providers[provider];
119
- if (info.hasApiKey && info.apiKey) {
120
- verifiedProviders.push({ provider, apiKey: info.apiKey });
121
- }
122
- }
123
- if (verifiedProviders.length > 0) {
124
- const latestModels = getLatestModels();
125
- const providerData = {};
126
- for (const { provider, apiKey: key } of verifiedProviders) {
127
- providerData[provider] = {
128
- apiKey: key,
129
- models: latestModels[provider],
130
- };
131
- }
132
- const configured = configureNamedAgentProviders(name, providerData);
133
- for (const provider of configured) {
134
- const configKey = `smoltbot-${name}` + (provider === "anthropic" ? "" : `-${provider}`);
135
- console.log(fmt.success(`${provider} provider configured (${configKey})`));
136
- }
137
- console.log();
138
- }
139
- }
140
- // Save agent to config
141
- const agentConfig = {
142
- agentId,
143
- openclawConfigured: detection.installed && !options.standalone,
144
- providers: options.standalone ? [selectedProvider] : undefined,
145
- configuredAt: new Date().toISOString(),
146
- };
147
- config.agents[name] = agentConfig;
148
- if (options.setDefault) {
149
- config.defaultAgent = name;
150
- console.log(fmt.success(`Set "${name}" as default agent`) + "\n");
151
- }
152
- saveConfig(config);
153
- console.log(fmt.success(`Agent "${name}" registered locally`) + "\n");
154
- // Make a real API call through the gateway to verify key AND create agent on server
155
- // This MUST succeed — the agent doesn't exist on the server until this call goes through
156
- const gatewayProvider = options.standalone ? selectedProvider : "anthropic";
157
- let verified = false;
158
- while (!verified) {
159
- console.log(`Connecting to gateway...`);
160
- const testResult = await testGatewayCall(config.gateway, name, gatewayProvider, apiKey, config.mnemomApiKey);
161
- if (testResult.ok && testResult.response) {
162
- // Update stored agent ID to match the server-assigned UUID (scale/step-25b).
163
- // The gateway now generates mnm-{uuid} IDs — the locally-derived ID is a placeholder.
164
- if (testResult.agentId && config.agents[name]) {
165
- config.agents[name].agentId = testResult.agentId;
166
- saveConfig(config);
167
- }
168
- console.log(fmt.success("Connected! First response from " + name + ":") + "\n");
169
- console.log(` "${testResult.response}"\n`);
170
- console.log(fmt.success("Agent created on mnemom.ai") + "\n");
171
- verified = true;
172
- }
173
- else if (testResult.authError) {
174
- console.log(fmt.error(`API key invalid: ${testResult.error}`) + "\n");
175
- console.log(" Check your API key and try again with:");
176
- console.log(` smoltbot register ${name}\n`);
177
- return;
178
- }
179
- else {
180
- console.log(fmt.error(`Connection failed: ${testResult.error}`) + "\n");
181
- if (!isInteractive()) {
182
- console.log(" Run `smoltbot register " + name + "` to retry.\n");
183
- return;
184
- }
185
- const retry = await askYesNo("Retry?", true);
186
- if (!retry) {
187
- console.log("\n Agent is saved locally but NOT created on the server.");
188
- console.log(` Run \`smoltbot register ${name}\` to try again.\n`);
189
- return;
190
- }
191
- console.log();
192
- }
193
- }
194
- // Only show claim + next steps after successful gateway verification
195
- // Include hash proof in URL so the website can auto-fill it
196
- // Hash proof must match how the gateway computed agent_hash:
197
- // named agents use hash(apiKey + '|' + name), default uses hash(apiKey)
198
- // eslint-disable-next-line -- not password hashing: hash proof must match website's SHA-256 verification
199
- const hashInput = apiKey + "|" + name;
200
- const hashProof = crypto.createHash("sha256").update(hashInput).digest("hex");
201
- const claimUrl = `${DASHBOARD_URL}/claim/${agentId}?hash=${hashProof}`;
202
- console.log(fmt.section("Link to your Mnemom account"));
203
- console.log();
204
- console.log(` Sign in (or create a free account) to see your agent's`);
205
- console.log(` traces and manage its alignment card.\n`);
206
- console.log(` ${claimUrl}\n`);
207
- if (isInteractive()) {
208
- const openBrowser = await askYesNo("Open in browser?", true);
209
- if (openBrowser) {
210
- openUrl(claimUrl);
211
- console.log();
212
- }
213
- else {
214
- console.log();
215
- }
216
- }
217
- // Show usage instructions
218
- console.log(fmt.section("Start using your agent"));
219
- console.log();
220
- if (detection.installed && !options.standalone) {
221
- console.log(` openclaw models set smoltbot-${name}/<model-id>`);
222
- }
223
- else {
224
- console.log(` Add the x-smoltbot-agent header to your API calls:\n`);
225
- console.log(` x-smoltbot-agent: ${name}\n`);
226
- if (selectedProvider === "anthropic") {
227
- console.log(` Python: client = Anthropic(base_url="${config.gateway}/anthropic",`);
228
- console.log(` default_headers={"x-smoltbot-agent": "${name}"})`);
229
- console.log(` TypeScript: new Anthropic({ baseURL: "${config.gateway}/anthropic",`);
230
- console.log(` defaultHeaders: { "x-smoltbot-agent": "${name}" } })`);
231
- }
232
- else if (selectedProvider === "openai") {
233
- console.log(` Python: client = OpenAI(base_url="${config.gateway}/openai/v1",`);
234
- console.log(` default_headers={"x-smoltbot-agent": "${name}"})`);
235
- console.log(` TypeScript: new OpenAI({ baseURL: "${config.gateway}/openai/v1",`);
236
- console.log(` defaultHeaders: { "x-smoltbot-agent": "${name}" } })`);
237
- }
238
- else {
239
- console.log(` Add header: x-smoltbot-agent: ${name}`);
240
- console.log(` Endpoint: ${config.gateway}/gemini/v1beta/models/{model}:generateContent`);
241
- }
242
- }
243
- console.log();
244
- console.log(` smoltbot status --agent ${name} Check status`);
245
- console.log(` smoltbot agents List all agents`);
246
- console.log();
247
- console.log(` Dashboard: ${DASHBOARD_URL}/agents/${agentId}\n`);
248
- }
249
- const HELLO_PROMPT = "Say hello in one short sentence. Keep it under 15 words.";
250
- /**
251
- * Make a real API call through the gateway to verify the key,
252
- * create the agent on the server, and return the first response.
253
- */
254
- async function testGatewayCall(gateway, agentName, provider, apiKey, mnemomApiKey) {
255
- const agentHeader = { "x-smoltbot-agent": agentName };
256
- try {
257
- let url;
258
- let headers;
259
- let body;
260
- if (provider === "anthropic") {
261
- url = new URL(`${gateway}/anthropic/v1/messages`).href;
262
- headers = {
263
- "Content-Type": "application/json",
264
- "x-api-key": apiKey,
265
- "anthropic-version": "2023-06-01",
266
- ...agentHeader,
267
- ...(mnemomApiKey ? { "x-mnemom-api-key": mnemomApiKey } : {}),
268
- };
269
- body = JSON.stringify({
270
- model: "claude-haiku-4-5-20251001",
271
- max_tokens: 16000,
272
- messages: [{ role: "user", content: HELLO_PROMPT }],
273
- });
274
- }
275
- else if (provider === "openai") {
276
- url = new URL(`${gateway}/openai/v1/chat/completions`).href;
277
- headers = {
278
- "Content-Type": "application/json",
279
- "Authorization": `Bearer ${apiKey}`,
280
- ...agentHeader,
281
- ...(mnemomApiKey ? { "x-mnemom-api-key": mnemomApiKey } : {}),
282
- };
283
- body = JSON.stringify({
284
- model: "gpt-4o-mini",
285
- max_tokens: 150,
286
- messages: [{ role: "user", content: HELLO_PROMPT }],
287
- });
288
- }
289
- else {
290
- // Gemini
291
- url = new URL(`${gateway}/gemini/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`).href;
292
- headers = {
293
- "Content-Type": "application/json",
294
- ...agentHeader,
295
- ...(mnemomApiKey ? { "x-mnemom-api-key": mnemomApiKey } : {}),
296
- };
297
- body = JSON.stringify({
298
- contents: [{ parts: [{ text: HELLO_PROMPT }] }],
299
- generationConfig: { maxOutputTokens: 150 },
300
- });
301
- }
302
- const response = await fetch(url, {
303
- method: "POST",
304
- headers,
305
- body,
306
- signal: AbortSignal.timeout(30000),
307
- });
308
- if (response.status === 401 || response.status === 403) {
309
- return { ok: false, authError: true, error: "API key is invalid or revoked" };
310
- }
311
- if (response.status === 429) {
312
- const agentId = response.headers.get("x-mnemom-agent") ?? undefined;
313
- return { ok: true, response: "(rate limited — but key is valid)", agentId };
314
- }
315
- if (!response.ok) {
316
- const errorBody = await response.text().catch(() => "");
317
- return { ok: false, error: `HTTP ${response.status}: ${errorBody.slice(0, 200)}` };
318
- }
319
- // Extract server-assigned agent ID from response header (scale/step-25b)
320
- const agentId = response.headers.get("x-mnemom-agent") ?? undefined;
321
- // Extract response text from provider-specific format
322
- const data = await response.json();
323
- const text = extractResponseText(data, provider);
324
- return { ok: true, response: text || "(empty response)", agentId };
325
- }
326
- catch (err) {
327
- const message = err instanceof Error ? err.message : String(err);
328
- if (message.includes("abort") || message.includes("timeout")) {
329
- return { ok: false, error: "request timed out (30s)" };
330
- }
331
- return { ok: false, error: message };
332
- }
333
- }
334
- function extractResponseText(data, provider) {
335
- if (provider === "anthropic") {
336
- // { content: [{ type: "text", text: "..." }] }
337
- const blocks = data?.content;
338
- if (Array.isArray(blocks)) {
339
- for (const block of blocks) {
340
- if (block.type === "text" && block.text)
341
- return block.text.trim();
342
- }
343
- }
344
- }
345
- else if (provider === "openai") {
346
- // { choices: [{ message: { content: "..." } }] }
347
- return data?.choices?.[0]?.message?.content?.trim() || null;
348
- }
349
- else {
350
- // Gemini: { candidates: [{ content: { parts: [{ text: "..." }] } }] }
351
- return data?.candidates?.[0]?.content?.parts?.[0]?.text?.trim() || null;
352
- }
353
- return null;
354
- }
355
- function openUrl(url) {
356
- const cmd = process.platform === "darwin"
357
- ? `open "${url}"`
358
- : process.platform === "win32"
359
- ? `start "${url}"`
360
- : `xdg-open "${url}"`;
361
- exec(cmd, () => { });
362
- }