@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.
- package/dist/commands/agents.d.ts +0 -14
- package/dist/commands/agents.js +35 -295
- package/dist/commands/auth.js +2 -7
- package/dist/commands/card.d.ts +21 -8
- package/dist/commands/card.js +328 -275
- package/dist/commands/integrity.js +6 -6
- package/dist/commands/license.js +14 -29
- package/dist/commands/logs.js +8 -8
- package/dist/commands/policy.d.ts +10 -23
- package/dist/commands/policy.js +20 -533
- package/dist/commands/protection.d.ts +14 -0
- package/dist/commands/protection.js +308 -0
- package/dist/commands/status.js +42 -50
- package/dist/index.js +114 -160
- package/dist/lib/api.d.ts +41 -0
- package/dist/lib/api.js +131 -1
- package/dist/lib/auth.d.ts +30 -21
- package/dist/lib/auth.js +95 -49
- package/dist/lib/config.d.ts +11 -98
- package/dist/lib/config.js +12 -220
- package/dist/lib/model-cache.js +5 -6
- package/dist/smoltbot-shim.js +1 -1
- package/package.json +2 -2
- package/dist/commands/init.d.ts +0 -7
- package/dist/commands/init.js +0 -763
- package/dist/commands/migrate-config.d.ts +0 -2
- package/dist/commands/migrate-config.js +0 -72
- package/dist/commands/register.d.ts +0 -6
- package/dist/commands/register.js +0 -362
package/dist/commands/init.js
DELETED
|
@@ -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
|
-
}
|