@mnemom/mnemom 0.7.1 → 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,763 +0,0 @@
1
- import { configExists, loadConfig, saveConfig, generateAgentId, deriveAgentId, } from "../lib/config.js";
2
- import { detectProviders, configureSmoltbotProviders, setDefaultModel, PROVIDER_CONFIG_KEYS, } from "../lib/openclaw.js";
3
- import { detectProvider, getModelDefinition, formatModelName, getLatestModels, } from "../lib/models.js";
4
- import { refreshModelCache } from "../lib/model-cache.js";
5
- import { askYesNo, askInput, askMultiSelect, askSelect, isInteractive } from "../lib/prompt.js";
6
- import { fmt } from "../lib/format.js";
7
- const GATEWAY_URL = "https://gateway.mnemom.ai";
8
- const DASHBOARD_URL = "https://mnemom.ai";
9
- // ============================================================================
10
- // Entry point — dispatches to OpenClaw or standalone flow
11
- // ============================================================================
12
- export async function initCommand(options = {}) {
13
- console.log(fmt.header("smoltbot init - Transparent AI Agent Tracing"));
14
- console.log();
15
- // Step 1: Check for existing smoltbot config
16
- const existingConfig = await handleExistingConfig(options);
17
- if (existingConfig === "abort") {
18
- return;
19
- }
20
- // Step 2: Determine mode
21
- if (options.openclaw) {
22
- // Explicit --openclaw: run OpenClaw flow, fail if not installed
23
- return openclawFlow(options, existingConfig);
24
- }
25
- if (options.standalone) {
26
- // Explicit --standalone: skip OpenClaw entirely
27
- return standaloneFlow(options, existingConfig);
28
- }
29
- // No flags: detect OpenClaw and prompt if found
30
- const detection = detectProviders();
31
- if (detection.installed && isInteractive()) {
32
- const choice = await askSelect("OpenClaw detected. Configure for OpenClaw or standalone?", ["OpenClaw (use existing API keys from OpenClaw)", "Standalone (enter API keys directly)"]);
33
- if (choice && choice.startsWith("Standalone")) {
34
- return standaloneFlow(options, existingConfig);
35
- }
36
- // Default to OpenClaw if selected or null
37
- return openclawFlowWithDetection(options, existingConfig, detection);
38
- }
39
- if (detection.installed) {
40
- // Non-interactive with OpenClaw detected: use OpenClaw
41
- return openclawFlowWithDetection(options, existingConfig, detection);
42
- }
43
- // No OpenClaw: go straight to standalone
44
- return standaloneFlow(options, existingConfig);
45
- }
46
- // ============================================================================
47
- // Standalone flow — prompt for API keys directly
48
- // ============================================================================
49
- const PROVIDER_LABELS = {
50
- anthropic: "Anthropic",
51
- openai: "OpenAI",
52
- gemini: "Gemini",
53
- };
54
- const KEY_FORMAT_PREFIXES = {
55
- anthropic: { prefix: "sk-ant-", description: "starts with sk-ant-" },
56
- openai: { prefix: "sk-", description: "starts with sk-" },
57
- gemini: { prefix: "AIza", description: "starts with AIza" },
58
- };
59
- // Gateway route patterns (from gateway/src/index.ts):
60
- // /anthropic/* → handleProviderProxy(..., 'anthropic')
61
- // /openai/* → handleProviderProxy(..., 'openai')
62
- // /gemini/* → handleProviderProxy(..., 'gemini')
63
- const GATEWAY_BASE_URLS = {
64
- anthropic: `${GATEWAY_URL}/anthropic`,
65
- openai: `${GATEWAY_URL}/openai/v1`,
66
- gemini: `${GATEWAY_URL}/gemini`,
67
- };
68
- async function standaloneFlow(options, existingConfig) {
69
- console.log("Standalone setup (no OpenClaw required)\n");
70
- // Step 1: Select providers
71
- let selectedProviderNames;
72
- if (!isInteractive()) {
73
- console.log("Non-interactive mode: configuring all providers.\n");
74
- selectedProviderNames = ["Anthropic", "OpenAI", "Gemini"];
75
- }
76
- else {
77
- selectedProviderNames = await askMultiSelect("Which providers do you want to configure?", ["Anthropic", "OpenAI", "Gemini"]);
78
- }
79
- if (selectedProviderNames.length === 0) {
80
- console.log("\nNo providers selected. At least one provider is required.\n");
81
- process.exit(1);
82
- }
83
- // Map display names back to Provider type
84
- const nameToProvider = {
85
- Anthropic: "anthropic",
86
- OpenAI: "openai",
87
- Gemini: "gemini",
88
- };
89
- const selectedProviders = selectedProviderNames.map((n) => nameToProvider[n]).filter(Boolean);
90
- console.log();
91
- // Step 2: Prompt for API key for each selected provider
92
- const verifiedProviders = [];
93
- for (const provider of selectedProviders) {
94
- const label = PROVIDER_LABELS[provider];
95
- const format = KEY_FORMAT_PREFIXES[provider];
96
- let apiKey = "";
97
- let valid = false;
98
- while (!valid) {
99
- if (isInteractive()) {
100
- apiKey = await askInput(`${label} API key (${format.description}):`, true);
101
- }
102
- else {
103
- // Non-interactive: read from env
104
- const envVarMap = {
105
- anthropic: "ANTHROPIC_API_KEY",
106
- openai: "OPENAI_API_KEY",
107
- gemini: "GEMINI_API_KEY",
108
- };
109
- apiKey = process.env[envVarMap[provider]] || "";
110
- if (!apiKey) {
111
- console.log(` ${label}: No API key in ${envVarMap[provider]}, skipping`);
112
- break;
113
- }
114
- }
115
- if (!apiKey) {
116
- console.log(` Skipping ${label} (no key entered)\n`);
117
- break;
118
- }
119
- // Validate format
120
- if (!apiKey.startsWith(format.prefix)) {
121
- console.log(` ${fmt.error(`Invalid format: expected key ${format.description}`)}`);
122
- if (!isInteractive())
123
- break;
124
- console.log(" Try again.\n");
125
- continue;
126
- }
127
- // Verify with test API call
128
- console.log(` Verifying ${label} API key...`);
129
- const verification = await verifyProviderApiKey(provider, apiKey);
130
- if (!verification.valid) {
131
- console.log(` ${fmt.error(verification.error || "Verification failed")}`);
132
- if (!isInteractive())
133
- break;
134
- console.log(" Try again.\n");
135
- continue;
136
- }
137
- console.log(` ${fmt.success(`${label} API key verified`)}\n`);
138
- verifiedProviders.push({ provider, apiKey });
139
- valid = true;
140
- }
141
- }
142
- if (verifiedProviders.length === 0) {
143
- console.log(fmt.error("No valid API keys configured") + "\n");
144
- console.log("At least one provider is required. Run smoltbot init again.\n");
145
- process.exit(1);
146
- }
147
- // Step 3: Prompt for optional Mnemom API key (billing identity)
148
- const mnemomApiKey = await promptMnemomApiKey(existingConfig);
149
- // Step 4: Create config
150
- const firstApiKey = verifiedProviders[0].apiKey;
151
- const existingDefaultAgent = existingConfig?.agents?.[existingConfig.defaultAgent];
152
- const agentId = firstApiKey
153
- ? deriveAgentId(firstApiKey)
154
- : existingDefaultAgent?.agentId || generateAgentId();
155
- const providerNames = verifiedProviders.map((p) => p.provider);
156
- const existingV2 = loadConfig();
157
- const config = {
158
- version: 2,
159
- defaultAgent: "default",
160
- gateway: GATEWAY_URL,
161
- ...(mnemomApiKey ? { mnemomApiKey } : {}),
162
- ...(existingV2?.licenseJwt ? { licenseJwt: existingV2.licenseJwt } : {}),
163
- agents: {
164
- ...(existingV2?.agents ?? {}),
165
- default: {
166
- agentId,
167
- openclawConfigured: false,
168
- providers: providerNames,
169
- configuredAt: new Date().toISOString(),
170
- },
171
- },
172
- };
173
- saveConfig(config);
174
- console.log(fmt.success("Created ~/.smoltbot/config.json") + "\n");
175
- // Step 5: Show success + setup instructions
176
- showStandaloneSuccess(agentId, verifiedProviders, mnemomApiKey);
177
- }
178
- /**
179
- * Show standalone success message with SDK snippets per verified provider.
180
- */
181
- function showStandaloneSuccess(agentId, verifiedProviders, mnemomApiKey) {
182
- console.log(fmt.header("smoltbot initialized successfully!"));
183
- console.log();
184
- console.log(fmt.label("Agent ID:", agentId) + "\n");
185
- console.log("Verified providers:");
186
- for (const { provider } of verifiedProviders) {
187
- console.log(` ${fmt.success(PROVIDER_LABELS[provider])}`);
188
- }
189
- if (mnemomApiKey) {
190
- console.log(` ${fmt.success("Mnemom API key configured")}`);
191
- }
192
- console.log();
193
- console.log(fmt.section("Configure your agent to use the gateway"));
194
- console.log();
195
- if (mnemomApiKey) {
196
- console.log(" Set your Mnemom API key as an environment variable:\n");
197
- console.log(` export MNEMOM_API_KEY=<your-mnemom-api-key>\n`);
198
- }
199
- for (const { provider } of verifiedProviders) {
200
- const label = PROVIDER_LABELS[provider];
201
- const baseUrl = GATEWAY_BASE_URLS[provider];
202
- console.log(` ${label}:`);
203
- if (provider === "anthropic") {
204
- if (mnemomApiKey) {
205
- console.log(` Python:`);
206
- console.log(` client = Anthropic(`);
207
- console.log(` base_url="${baseUrl}",`);
208
- console.log(` default_headers={"x-mnemom-api-key": os.environ["MNEMOM_API_KEY"]}`);
209
- console.log(` )`);
210
- console.log(` TypeScript:`);
211
- console.log(` new Anthropic({`);
212
- console.log(` baseURL: "${baseUrl}",`);
213
- console.log(` defaultHeaders: { "x-mnemom-api-key": process.env.MNEMOM_API_KEY }`);
214
- console.log(` })`);
215
- }
216
- else {
217
- console.log(` Python: client = Anthropic(base_url="${baseUrl}")`);
218
- console.log(` TypeScript: new Anthropic({ baseURL: "${baseUrl}" })`);
219
- }
220
- console.log(` Env var: export ANTHROPIC_BASE_URL=${baseUrl}`);
221
- }
222
- else if (provider === "openai") {
223
- if (mnemomApiKey) {
224
- console.log(` Python:`);
225
- console.log(` client = OpenAI(`);
226
- console.log(` base_url="${baseUrl}",`);
227
- console.log(` default_headers={"x-mnemom-api-key": os.environ["MNEMOM_API_KEY"]}`);
228
- console.log(` )`);
229
- console.log(` TypeScript:`);
230
- console.log(` new OpenAI({`);
231
- console.log(` baseURL: "${baseUrl}",`);
232
- console.log(` defaultHeaders: { "x-mnemom-api-key": process.env.MNEMOM_API_KEY }`);
233
- console.log(` })`);
234
- }
235
- else {
236
- console.log(` Python: client = OpenAI(base_url="${baseUrl}")`);
237
- console.log(` TypeScript: new OpenAI({ baseURL: "${baseUrl}" })`);
238
- }
239
- console.log(` Env var: export OPENAI_BASE_URL=${baseUrl}`);
240
- }
241
- else if (provider === "gemini") {
242
- console.log(` REST: POST ${baseUrl}/v1beta/models/{model}:generateContent`);
243
- if (mnemomApiKey) {
244
- console.log(` Header: x-mnemom-api-key: $MNEMOM_API_KEY`);
245
- }
246
- console.log(` Env var: export GEMINI_BASE_URL=${baseUrl}`);
247
- }
248
- console.log();
249
- }
250
- console.log("Your provider API key is passed through to the provider via the gateway.");
251
- if (mnemomApiKey) {
252
- console.log("Your Mnemom API key identifies your billing account for quota tracking.");
253
- }
254
- console.log("The gateway traces requests for transparency — no keys are stored.\n");
255
- console.log(`Your traces will appear at:\n`);
256
- console.log(` ${DASHBOARD_URL}/agents/${agentId}\n`);
257
- console.log(fmt.section("Link to your Mnemom account"));
258
- console.log();
259
- console.log(" Sign in (or create a free account) to see your agent's");
260
- console.log(" traces and manage its alignment card.\n");
261
- console.log(` ${DASHBOARD_URL}/claim/${agentId}\n`);
262
- console.log(fmt.section("Useful commands"));
263
- console.log("\n smoltbot status - Check configuration and connectivity");
264
- console.log(" smoltbot logs - View recent traces");
265
- console.log(" smoltbot integrity - View integrity score");
266
- console.log(" smoltbot register <n> - Register additional named agents");
267
- console.log(" smoltbot agents - List all registered agents\n");
268
- }
269
- // ============================================================================
270
- // OpenClaw flow — existing behavior (unchanged)
271
- // ============================================================================
272
- /**
273
- * OpenClaw flow entry: detects OpenClaw, fails if not installed.
274
- */
275
- async function openclawFlow(options, existingConfig) {
276
- console.log("Detecting OpenClaw installation...\n");
277
- const detection = detectProviders();
278
- if (!detection.installed) {
279
- console.log(fmt.error("OpenClaw not found") + "\n");
280
- console.log(detection.error || "OpenClaw is not installed.");
281
- console.log("\nInstall OpenClaw first: https://openclaw.ai\n");
282
- process.exit(1);
283
- }
284
- return openclawFlowWithDetection(options, existingConfig, detection);
285
- }
286
- /**
287
- * OpenClaw flow with pre-detected result. This is the original initCommand logic.
288
- */
289
- async function openclawFlowWithDetection(options, existingConfig, detection) {
290
- console.log(fmt.success("OpenClaw installation detected") + "\n");
291
- // Scan all providers for API keys
292
- const availableProviders = [];
293
- for (const provider of ["anthropic", "openai", "gemini"]) {
294
- const info = detection.providers[provider];
295
- if (info.isOAuth) {
296
- console.log(` ${PROVIDER_LABELS[provider]}: OAuth detected (not supported, skipping)`);
297
- }
298
- else if (info.invalidFormat) {
299
- console.log(` ${PROVIDER_LABELS[provider]}: Invalid API key format (skipping)`);
300
- }
301
- else if (info.hasApiKey && info.apiKey) {
302
- console.log(fmt.success(`${PROVIDER_LABELS[provider]} API key found`));
303
- availableProviders.push({ provider, apiKey: info.apiKey });
304
- }
305
- else {
306
- console.log(` ${PROVIDER_LABELS[provider]}: No API key found`);
307
- }
308
- }
309
- console.log();
310
- // Require at least one provider
311
- if (availableProviders.length === 0) {
312
- console.log(fmt.error("No provider API keys found") + "\n");
313
- console.log("smoltbot requires at least one provider API key.");
314
- console.log("Configure API keys in OpenClaw:\n");
315
- console.log(" Anthropic: https://console.anthropic.com/settings/keys");
316
- console.log(" OpenAI: https://platform.openai.com/api-keys");
317
- console.log(" Gemini: https://aistudio.google.com/apikey\n");
318
- console.log("Then run `openclaw auth` to add your key(s).\n");
319
- process.exit(1);
320
- }
321
- console.log(` ${availableProviders.length} provider(s) with API keys\n`);
322
- // Verify each provider's API key
323
- const verifiedProviders = [];
324
- for (const { provider, apiKey } of availableProviders) {
325
- console.log(`Verifying ${PROVIDER_LABELS[provider]} API key...`);
326
- const verification = await verifyProviderApiKey(provider, apiKey);
327
- if (!verification.valid) {
328
- console.log(` ${fmt.error(`${verification.error} (skipping ${PROVIDER_LABELS[provider]})`)}\n`);
329
- }
330
- else {
331
- console.log(fmt.success(`${PROVIDER_LABELS[provider]} API key verified`) + "\n");
332
- verifiedProviders.push({ provider, apiKey });
333
- }
334
- }
335
- if (verifiedProviders.length === 0) {
336
- console.log(fmt.error("No valid API keys found") + "\n");
337
- console.log("All detected API keys failed verification.");
338
- console.log("Check your API keys and try again.\n");
339
- process.exit(1);
340
- }
341
- // Detect current model
342
- const { modelId, provider: currentProvider } = parseCurrentModel(detection);
343
- if (!modelId) {
344
- console.log(fmt.error("No default model configured in OpenClaw") + "\n");
345
- console.log("Configure a default model first:");
346
- console.log(" openclaw models set anthropic/claude-opus-4-5-20251101\n");
347
- process.exit(1);
348
- }
349
- console.log(fmt.success(`Current model: ${currentProvider}/${modelId}`));
350
- console.log(` (${formatModelName(modelId)})\n`);
351
- // Determine models to add per provider
352
- const verifiedProviderSet = new Set(verifiedProviders.map((p) => p.provider));
353
- const modelsPerProvider = determineModelsToAdd(modelId, detection, verifiedProviderSet);
354
- console.log("Models to configure for smoltbot providers:");
355
- for (const [provider, models] of Object.entries(modelsPerProvider)) {
356
- if (models.length === 0)
357
- continue;
358
- const configKey = PROVIDER_CONFIG_KEYS[provider];
359
- for (const model of models) {
360
- console.log(` - ${configKey}/${model.id} (${model.name})`);
361
- }
362
- }
363
- console.log();
364
- // Configure all providers that have verified keys
365
- const alreadyConfigured = detection.smoltbotConfiguredProviders;
366
- if (alreadyConfigured.length > 0 && !options.force) {
367
- const configuredNames = alreadyConfigured.map((p) => PROVIDER_LABELS[p]).join(", ");
368
- console.log(fmt.warn(`smoltbot already configured for: ${configuredNames}`) + "\n");
369
- if (isInteractive() && !options.yes) {
370
- const reconfigure = await askYesNo("Reconfigure smoltbot providers?", true);
371
- if (!reconfigure) {
372
- console.log("\nKeeping existing configuration.\n");
373
- const mnemomKey = await promptMnemomApiKey(existingConfig);
374
- const firstApiKey = verifiedProviders[0]?.apiKey;
375
- const agentId = await createSmoltbotConfig(existingConfig, firstApiKey, mnemomKey);
376
- showOpenClawSuccessMessage(agentId, modelId, currentProvider, verifiedProviders.map((p) => p.provider), modelsPerProvider);
377
- return;
378
- }
379
- }
380
- console.log("Reconfiguring smoltbot providers...\n");
381
- }
382
- console.log("Configuring smoltbot providers in OpenClaw...");
383
- const providerKeys = {};
384
- for (const { provider, apiKey } of verifiedProviders) {
385
- const models = modelsPerProvider[provider] || [];
386
- if (models.length > 0) {
387
- providerKeys[provider] = { apiKey, models };
388
- }
389
- }
390
- const configuredProviders = configureSmoltbotProviders(providerKeys);
391
- for (const provider of configuredProviders) {
392
- console.log(fmt.success(`${PROVIDER_LABELS[provider]} provider configured (${PROVIDER_CONFIG_KEYS[provider]})`));
393
- }
394
- console.log();
395
- // Offer to switch default model
396
- const modelProvider = detectProvider(modelId);
397
- const smoltbotConfigKey = modelProvider ? PROVIDER_CONFIG_KEYS[modelProvider] : PROVIDER_CONFIG_KEYS.anthropic;
398
- const smoltbotModelPath = `${smoltbotConfigKey}/${modelId}`;
399
- const alreadyUsingSmoltbot = currentProvider !== null &&
400
- Object.values(PROVIDER_CONFIG_KEYS).includes(currentProvider);
401
- let shouldSwitch = false;
402
- if (!alreadyUsingSmoltbot) {
403
- shouldSwitch = await promptModelSwitch(modelId, currentProvider, smoltbotModelPath, configuredProviders, modelsPerProvider, options);
404
- if (shouldSwitch) {
405
- console.log(`Setting default model to ${smoltbotModelPath}...`);
406
- setDefaultModel(smoltbotModelPath);
407
- console.log(fmt.success(`Default model set to ${smoltbotModelPath}`) + "\n");
408
- }
409
- else {
410
- console.log(`Default model unchanged (${currentProvider}/${modelId})\n`);
411
- console.log("To enable traced mode later:");
412
- console.log(` openclaw models set ${smoltbotModelPath}\n`);
413
- }
414
- }
415
- // Prompt for optional Mnemom API key
416
- const mnemomApiKey = await promptMnemomApiKey(existingConfig);
417
- // Create smoltbot config
418
- const firstApiKey = verifiedProviders[0]?.apiKey;
419
- const agentId = await createSmoltbotConfig(existingConfig, firstApiKey, mnemomApiKey);
420
- // Show success
421
- const tracedModeActive = shouldSwitch || alreadyUsingSmoltbot;
422
- showOpenClawSuccessMessage(agentId, modelId, currentProvider, configuredProviders, modelsPerProvider, tracedModeActive);
423
- // Trigger model cache refresh in background
424
- refreshModelCache().catch(() => { });
425
- }
426
- // ============================================================================
427
- // Shared helpers
428
- // ============================================================================
429
- /**
430
- * Prompt for optional Mnemom platform API key (billing identity).
431
- * The key is created at mnemom.ai/settings/api-keys and sent as
432
- * x-mnemom-api-key header to the gateway for quota/billing tracking.
433
- */
434
- async function promptMnemomApiKey(existingConfig) {
435
- console.log(fmt.section("Mnemom API Key (optional)"));
436
- console.log();
437
- console.log("If you have a paid plan, enter your Mnemom API key");
438
- console.log("for gateway billing and quota tracking.");
439
- console.log(`Create one at: ${DASHBOARD_URL}/settings/api-keys\n`);
440
- if (existingConfig?.mnemomApiKey) {
441
- console.log(` Existing key: [CONFIGURED]\n`);
442
- }
443
- if (!isInteractive()) {
444
- const envKey = process.env.MNEMOM_API_KEY || "";
445
- if (envKey && envKey.startsWith("mnm_")) {
446
- console.log(` ${fmt.success("Mnemom API key found in MNEMOM_API_KEY")}\n`);
447
- return envKey;
448
- }
449
- console.log(" No MNEMOM_API_KEY env var found, skipping.\n");
450
- return existingConfig?.mnemomApiKey;
451
- }
452
- const key = await askInput("Mnemom API key (mnm_..., or press Enter to skip):", true);
453
- if (!key) {
454
- console.log(" Skipped (free tier or configure later)\n");
455
- return existingConfig?.mnemomApiKey;
456
- }
457
- if (!key.startsWith("mnm_")) {
458
- console.log(` ${fmt.error("Invalid format: Mnemom API keys start with mnm_")}`);
459
- console.log(" Skipping. You can add it later in ~/.smoltbot/config.json\n");
460
- return existingConfig?.mnemomApiKey;
461
- }
462
- console.log(` ${fmt.success("Mnemom API key configured")}\n`);
463
- return key;
464
- }
465
- /**
466
- * Handle existing smoltbot config.
467
- * Returns "abort" if user doesn't want to reconfigure, or the existing config.
468
- */
469
- async function handleExistingConfig(options) {
470
- if (!configExists()) {
471
- return null;
472
- }
473
- const existingConfig = loadConfig();
474
- if (!existingConfig) {
475
- return null;
476
- }
477
- console.log("smoltbot is already initialized.\n");
478
- console.log(fmt.label(" Gateway:", ` ${existingConfig.gateway || GATEWAY_URL}`));
479
- console.log();
480
- // Show registered agents
481
- const agentNames = Object.keys(existingConfig.agents);
482
- console.log(" Registered agents:");
483
- for (const name of agentNames) {
484
- const agent = existingConfig.agents[name];
485
- const isDefault = name === existingConfig.defaultAgent;
486
- const marker = isDefault ? " (default)" : "";
487
- console.log(` ${name}${marker} — ${agent.agentId}`);
488
- }
489
- console.log();
490
- console.log(" To add another agent:");
491
- console.log(" smoltbot register <name>\n");
492
- if (options.force) {
493
- console.log("Reconfiguring (--force)...\n");
494
- return existingConfig;
495
- }
496
- return "abort";
497
- }
498
- /**
499
- * Parse the current model from detection results.
500
- */
501
- function parseCurrentModel(detection) {
502
- let modelId = detection.currentModelId || null;
503
- let provider = detection.currentProvider || null;
504
- if (provider && Object.values(PROVIDER_CONFIG_KEYS).includes(provider)) {
505
- // Already using a smoltbot provider
506
- }
507
- else if (!provider && modelId) {
508
- const detected = detectProvider(modelId);
509
- provider = detected || "anthropic";
510
- }
511
- return { modelId, provider };
512
- }
513
- /**
514
- * Determine which models to add per provider.
515
- */
516
- function determineModelsToAdd(currentModelId, detection, verifiedProviders) {
517
- const result = {
518
- anthropic: [],
519
- openai: [],
520
- gemini: [],
521
- };
522
- const latestModels = getLatestModels();
523
- for (const provider of verifiedProviders) {
524
- const addedIds = new Set();
525
- const models = [];
526
- const currentModelProvider = detectProvider(currentModelId);
527
- if (currentModelProvider === provider) {
528
- const modelDef = getModelDefinition(currentModelId);
529
- models.push(modelDef);
530
- addedIds.add(currentModelId);
531
- }
532
- for (const model of latestModels[provider]) {
533
- if (!addedIds.has(model.id)) {
534
- models.push(model);
535
- addedIds.add(model.id);
536
- }
537
- }
538
- result[provider] = models;
539
- }
540
- return result;
541
- }
542
- /**
543
- * Prompt user to switch default model (OpenClaw flow only).
544
- */
545
- async function promptModelSwitch(modelId, currentProvider, smoltbotModelPath, configuredProviders, modelsPerProvider, options) {
546
- if (options.yes)
547
- return true;
548
- if (!isInteractive())
549
- return true;
550
- console.log(fmt.section("Switch to traced mode now?"));
551
- console.log();
552
- console.log(` Current: ${currentProvider}/${modelId}`);
553
- console.log(` Traced: ${smoltbotModelPath}\n`);
554
- console.log("When using smoltbot models, all API calls are logged for");
555
- console.log("transparency and alignment verification.\n");
556
- if (configuredProviders.length > 1) {
557
- console.log("Available traced models across providers:");
558
- for (const provider of configuredProviders) {
559
- const models = modelsPerProvider[provider] || [];
560
- if (models.length > 0) {
561
- const configKey = PROVIDER_CONFIG_KEYS[provider];
562
- console.log(` ${PROVIDER_LABELS[provider]}:`);
563
- for (const model of models) {
564
- console.log(` openclaw models set ${configKey}/${model.id}`);
565
- }
566
- }
567
- }
568
- console.log();
569
- }
570
- return askYesNo("Switch to traced model?", true);
571
- }
572
- /**
573
- * Create or update smoltbot config (OpenClaw flow).
574
- */
575
- async function createSmoltbotConfig(existingConfig, apiKey, mnemomApiKey) {
576
- const defaultAgent = existingConfig?.agents?.[existingConfig.defaultAgent];
577
- const agentId = apiKey
578
- ? deriveAgentId(apiKey)
579
- : defaultAgent?.agentId || generateAgentId();
580
- const existingV2 = loadConfig();
581
- const config = {
582
- version: 2,
583
- defaultAgent: "default",
584
- gateway: GATEWAY_URL,
585
- ...(mnemomApiKey ? { mnemomApiKey } : {}),
586
- ...(existingV2?.licenseJwt ? { licenseJwt: existingV2.licenseJwt } : {}),
587
- agents: {
588
- ...(existingV2?.agents ?? {}),
589
- default: {
590
- agentId,
591
- openclawConfigured: true,
592
- configuredAt: new Date().toISOString(),
593
- },
594
- },
595
- };
596
- saveConfig(config);
597
- console.log(fmt.success("Created ~/.smoltbot/config.json") + "\n");
598
- return agentId;
599
- }
600
- /**
601
- * Show success message for OpenClaw flow.
602
- */
603
- function showOpenClawSuccessMessage(agentId, modelId, currentProvider, configuredProviders, modelsPerProvider, switched = true) {
604
- console.log(fmt.header("smoltbot initialized successfully!"));
605
- console.log();
606
- console.log(fmt.label("Agent ID:", agentId) + "\n");
607
- console.log("Configured providers:");
608
- for (const provider of configuredProviders) {
609
- const configKey = PROVIDER_CONFIG_KEYS[provider];
610
- const models = modelsPerProvider[provider] || [];
611
- const modelNames = models.map((m) => m.name).join(", ");
612
- console.log(` ${fmt.success(`${PROVIDER_LABELS[provider]} (${configKey}) — ${modelNames}`)}`);
613
- }
614
- console.log();
615
- if (switched) {
616
- console.log(fmt.success("Traced mode is now active") + "\n");
617
- console.log("All OpenClaw API calls will be traced. Your traces will");
618
- console.log("appear at:\n");
619
- }
620
- else {
621
- console.log("Traced mode is ready but not active.\n");
622
- const modelProvider = detectProvider(modelId);
623
- const tracedConfigKey = modelProvider ? PROVIDER_CONFIG_KEYS[modelProvider] : PROVIDER_CONFIG_KEYS.anthropic;
624
- console.log("To enable traced mode, run:");
625
- console.log(` openclaw models set ${tracedConfigKey}/${modelId}\n`);
626
- console.log("Once enabled, your traces will appear at:\n");
627
- }
628
- console.log(` ${DASHBOARD_URL}/agents/${agentId}\n`);
629
- console.log(fmt.section("Link to your Mnemom account"));
630
- console.log();
631
- console.log(" Sign in (or create a free account) to see your agent's");
632
- console.log(" traces and manage its alignment card.\n");
633
- console.log(` ${DASHBOARD_URL}/claim/${agentId}\n`);
634
- console.log(fmt.section("Useful commands"));
635
- console.log("\n smoltbot status - Check configuration and connectivity");
636
- console.log(" smoltbot logs - View recent traces");
637
- console.log(" smoltbot integrity - View integrity score");
638
- console.log(" smoltbot register <n> - Register additional named agents");
639
- console.log(" smoltbot agents - List all registered agents\n");
640
- console.log(fmt.section("Switch between traced and untraced mode"));
641
- console.log();
642
- for (const provider of configuredProviders) {
643
- const configKey = PROVIDER_CONFIG_KEYS[provider];
644
- const models = modelsPerProvider[provider] || [];
645
- if (models.length > 0) {
646
- const firstModel = models[0];
647
- console.log(` Traced (${PROVIDER_LABELS[provider]}): openclaw models set ${configKey}/${firstModel.id}`);
648
- console.log(` Untraced (${PROVIDER_LABELS[provider]}): openclaw models set ${provider}/${firstModel.id}`);
649
- }
650
- }
651
- console.log();
652
- }
653
- // ============================================================================
654
- // API key verification (shared by both flows)
655
- // ============================================================================
656
- /**
657
- * Verify an API key for a given provider.
658
- * Fail-open: network errors and 5xx responses don't block init.
659
- */
660
- async function verifyProviderApiKey(provider, apiKey) {
661
- switch (provider) {
662
- case "anthropic":
663
- return verifyAnthropicApiKey(apiKey);
664
- case "openai":
665
- return verifyOpenAIApiKey(apiKey);
666
- case "gemini":
667
- return verifyGeminiApiKey(apiKey);
668
- default:
669
- return { valid: true };
670
- }
671
- }
672
- async function verifyAnthropicApiKey(apiKey) {
673
- try {
674
- const response = await fetch("https://api.anthropic.com/v1/messages", {
675
- method: "POST",
676
- headers: {
677
- "Content-Type": "application/json",
678
- "x-api-key": apiKey,
679
- "anthropic-version": "2023-06-01",
680
- },
681
- body: JSON.stringify({
682
- model: "claude-haiku-4-5-20251001",
683
- max_tokens: 1,
684
- messages: [{ role: "user", content: "hi" }],
685
- }),
686
- signal: AbortSignal.timeout(10000),
687
- });
688
- if (response.ok || response.status === 429) {
689
- return { valid: true };
690
- }
691
- if (response.status === 401 || response.status === 403) {
692
- return {
693
- valid: false,
694
- error: "Anthropic API key is invalid or has been revoked.",
695
- };
696
- }
697
- return { valid: true };
698
- }
699
- catch {
700
- console.log(" Could not verify API key (network error). Proceeding anyway.\n");
701
- return { valid: true };
702
- }
703
- }
704
- async function verifyOpenAIApiKey(apiKey) {
705
- try {
706
- const response = await fetch("https://api.openai.com/v1/chat/completions", {
707
- method: "POST",
708
- headers: {
709
- "Content-Type": "application/json",
710
- "Authorization": `Bearer ${apiKey}`,
711
- },
712
- body: JSON.stringify({
713
- model: "gpt-5-mini",
714
- max_tokens: 1,
715
- messages: [{ role: "user", content: "hi" }],
716
- }),
717
- signal: AbortSignal.timeout(10000),
718
- });
719
- if (response.ok || response.status === 429) {
720
- return { valid: true };
721
- }
722
- if (response.status === 401 || response.status === 403) {
723
- return {
724
- valid: false,
725
- error: "OpenAI API key is invalid or has been revoked.",
726
- };
727
- }
728
- return { valid: true };
729
- }
730
- catch {
731
- console.log(" Could not verify API key (network error). Proceeding anyway.\n");
732
- return { valid: true };
733
- }
734
- }
735
- async function verifyGeminiApiKey(apiKey) {
736
- try {
737
- const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${apiKey}`, {
738
- method: "POST",
739
- headers: {
740
- "Content-Type": "application/json",
741
- },
742
- body: JSON.stringify({
743
- contents: [{ parts: [{ text: "hi" }] }],
744
- generationConfig: { maxOutputTokens: 1 },
745
- }),
746
- signal: AbortSignal.timeout(10000),
747
- });
748
- if (response.ok || response.status === 429) {
749
- return { valid: true };
750
- }
751
- if (response.status === 400 || response.status === 401 || response.status === 403) {
752
- return {
753
- valid: false,
754
- error: "Gemini API key is invalid or has been revoked.",
755
- };
756
- }
757
- return { valid: true };
758
- }
759
- catch {
760
- console.log(" Could not verify API key (network error). Proceeding anyway.\n");
761
- return { valid: true };
762
- }
763
- }