@tokagent/tokagentos 2.0.33 → 2.0.35

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokagent/tokagentos",
3
- "version": "2.0.33",
3
+ "version": "2.0.35",
4
4
  "description": "tokagentOS CLI - Create and upgrade tokagentOS project templates",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Remove LLM provider API keys (OPENROUTER, ANTHROPIC, OPENAI, GOOGLE, GROQ)
4
+ * from the agents DB table. mergeDbSettings() in @elizaos/core loads
5
+ * agents.secrets and agents.settings.secrets into character.settings.secrets
6
+ * on every boot, and runtime.getSetting() checks character.settings.secrets
7
+ * BEFORE process.env — so a stale DB-stored key shadows the .env value
8
+ * forever. After running this, .env becomes the source of truth.
9
+ *
10
+ * Run with the agent STOPPED (`Ctrl-C` your `bun run dev`):
11
+ * bun run scripts/clear-db-llm-secrets.mjs
12
+ */
13
+ import path from "node:path";
14
+ import { fileURLToPath } from "node:url";
15
+ import { existsSync } from "node:fs";
16
+
17
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
18
+ const PROJECT_ROOT = path.resolve(__dirname, "..");
19
+ const DB_DIR = path.join(PROJECT_ROOT, ".eliza", ".elizadb");
20
+
21
+ if (!existsSync(DB_DIR)) {
22
+ console.error(`PGLite directory not found at ${DB_DIR}`);
23
+ process.exit(1);
24
+ }
25
+
26
+ const LLM_KEYS = [
27
+ "OPENROUTER_API_KEY",
28
+ "ANTHROPIC_API_KEY",
29
+ "OPENAI_API_KEY",
30
+ "GOOGLE_GENERATIVE_AI_API_KEY",
31
+ "GOOGLE_API_KEY",
32
+ "GROQ_API_KEY",
33
+ ];
34
+
35
+ const { PGlite } = await import("@electric-sql/pglite");
36
+ const db = new PGlite(DB_DIR);
37
+ await db.waitReady;
38
+
39
+ const before = await db.query(
40
+ "SELECT id, name, secrets, settings FROM agents",
41
+ );
42
+ console.log(`Agents in DB: ${before.rows.length}`);
43
+ for (const row of before.rows) {
44
+ const id = row.id;
45
+ const name = row.name;
46
+ const topSecrets = row.secrets && typeof row.secrets === "object" ? row.secrets : {};
47
+ const settingsSecrets =
48
+ row.settings &&
49
+ typeof row.settings === "object" &&
50
+ row.settings.secrets &&
51
+ typeof row.settings.secrets === "object"
52
+ ? row.settings.secrets
53
+ : {};
54
+ const allKeys = new Set([
55
+ ...Object.keys(topSecrets),
56
+ ...Object.keys(settingsSecrets),
57
+ ]);
58
+ const llmKeysPresent = [...allKeys].filter((k) => LLM_KEYS.includes(k));
59
+ if (llmKeysPresent.length === 0) {
60
+ console.log(` ${name} (${id}): no LLM keys stored ✓`);
61
+ continue;
62
+ }
63
+ console.log(` ${name} (${id}): clearing ${llmKeysPresent.join(", ")}`);
64
+ const newTopSecrets = { ...topSecrets };
65
+ const newSettings =
66
+ row.settings && typeof row.settings === "object" ? { ...row.settings } : {};
67
+ const newSettingsSecrets = { ...settingsSecrets };
68
+ for (const k of llmKeysPresent) {
69
+ delete newTopSecrets[k];
70
+ delete newSettingsSecrets[k];
71
+ }
72
+ newSettings.secrets = newSettingsSecrets;
73
+ await db.query(
74
+ "UPDATE agents SET secrets = $1, settings = $2 WHERE id = $3",
75
+ [
76
+ JSON.stringify(newTopSecrets),
77
+ JSON.stringify(newSettings),
78
+ id,
79
+ ],
80
+ );
81
+ }
82
+
83
+ console.log("\nDone. Restart `bun run dev` — .env is now the source of truth.");
84
+ await db.close();
@@ -229,6 +229,148 @@ if (env.OPENROUTER_API_KEY) {
229
229
  }
230
230
  }
231
231
 
232
+ /**
233
+ * DB-shadow check.
234
+ *
235
+ * Failure mode observed in the field: an LLM key set during first-boot
236
+ * onboarding is persisted into the `agents` table (`secrets` and
237
+ * `settings.secrets` columns). On every restart, mergeDbSettings() in
238
+ * @elizaos/core copies it into character.settings.secrets, and
239
+ * runtime.getSetting() reads that BEFORE process.env. So rotating .env
240
+ * does nothing — the DB value wins forever, even after .env is changed.
241
+ *
242
+ * We open PGLite read-only, scan agents.secrets and agents.settings.secrets,
243
+ * and warn loudly if any LLM key differs from .env. We don't auto-mutate
244
+ * the DB — clearing secrets is a destructive operation that should be
245
+ * explicit (use scripts/clear-db-llm-secrets.mjs).
246
+ *
247
+ * Best-effort only: skipped if PGLite isn't installed, the DB dir doesn't
248
+ * exist (fresh project), or the `agents` table isn't there yet (pre-boot).
249
+ */
250
+ async function checkDbShadow() {
251
+ const dbDir = path.join(PROJECT_ROOT, ".eliza", ".elizadb");
252
+ if (!existsSync(dbDir)) return;
253
+ let PGlite;
254
+ try {
255
+ ({ PGlite } = await import("@electric-sql/pglite"));
256
+ } catch {
257
+ return;
258
+ }
259
+ let db;
260
+ try {
261
+ db = new PGlite(dbDir);
262
+ await db.waitReady;
263
+ } catch {
264
+ return;
265
+ }
266
+ try {
267
+ const result = await db.query(
268
+ "SELECT id, name, secrets, settings FROM agents",
269
+ );
270
+ for (const row of result.rows) {
271
+ const topSecrets =
272
+ row.secrets && typeof row.secrets === "object" ? row.secrets : {};
273
+ const settingsSecrets =
274
+ row.settings &&
275
+ typeof row.settings === "object" &&
276
+ row.settings.secrets &&
277
+ typeof row.settings.secrets === "object"
278
+ ? row.settings.secrets
279
+ : {};
280
+ for (const key of SHADOWED_KEYS) {
281
+ const dbValue = topSecrets[key] ?? settingsSecrets[key];
282
+ const dotenvValue = env[key];
283
+ if (!dbValue) continue;
284
+ if (!dotenvValue) {
285
+ // DB has it, .env doesn't. Probably set via onboarding UI and not
286
+ // mirrored. Not necessarily a bug — but flag it so the user knows.
287
+ continue;
288
+ }
289
+ if (dbValue !== dotenvValue) {
290
+ fail(
291
+ `${key} in PGLite database (agent "${row.name}") differs from .env.\n` +
292
+ ` db : ${String(dbValue).slice(0, 12)}…${String(dbValue).slice(-4)} (this is what the agent reads)\n` +
293
+ ` .env : ${String(dotenvValue).slice(0, 12)}…${String(dotenvValue).slice(-4)} (this is what you probably edited)\n` +
294
+ ` mergeDbSettings() in @elizaos/core loads the DB value into character.settings.secrets, and\n` +
295
+ ` runtime.getSetting() reads that BEFORE process.env. Editing .env doesn't help.\n` +
296
+ ` Fix: stop \`bun run dev\` and run \`bun run scripts/clear-db-llm-secrets.mjs\`,\n` +
297
+ ` then start again. .env will then be the source of truth.`,
298
+ );
299
+ }
300
+ }
301
+ }
302
+ } catch {
303
+ // agents table doesn't exist yet (fresh boot) — nothing to check.
304
+ } finally {
305
+ try {
306
+ await db.close();
307
+ } catch {}
308
+ }
309
+ }
310
+
311
+ await checkDbShadow();
312
+
313
+ /**
314
+ * No-provider-configured check.
315
+ *
316
+ * Failure mode observed in the field: the user clears all direct provider
317
+ * keys (ANTHROPIC_API_KEY, OPENROUTER_API_KEY, etc.) intending to run in
318
+ * x402-only mode, but doesn't set BILLING_CHAT_KEY. The runtime then has
319
+ * NO TEXT_SMALL/TEXT_LARGE provider registered — every chat request fails
320
+ * with `[router] No provider registered for TEXT_SMALL`, which is opaque
321
+ * about the actual fix (mint a sk-ai-* key and set BILLING_CHAT_KEY).
322
+ *
323
+ * Wire (see core-plugins.ts:configureBillingChatMirror): when both
324
+ * BILLING_CHAT_KEY and TOKAGENT_GATEWAY_URL are set, OPENAI_API_KEY +
325
+ * OPENAI_BASE_URL are mirrored onto the gateway and plugin-openai loads
326
+ * as the gateway provider. Either one alone is a no-op.
327
+ */
328
+ const DIRECT_LLM_KEYS = [
329
+ "ANTHROPIC_API_KEY",
330
+ "OPENAI_API_KEY",
331
+ "OPENROUTER_API_KEY",
332
+ "GOOGLE_GENERATIVE_AI_API_KEY",
333
+ "GOOGLE_API_KEY",
334
+ "GEMINI_API_KEY",
335
+ "GROQ_API_KEY",
336
+ "XAI_API_KEY",
337
+ "DEEPSEEK_API_KEY",
338
+ "MISTRAL_API_KEY",
339
+ "TOGETHER_API_KEY",
340
+ "OLLAMA_BASE_URL",
341
+ ];
342
+ const hasDirectKey = DIRECT_LLM_KEYS.some((k) => env[k]?.trim());
343
+ const hasBillingChatKey = !!env.BILLING_CHAT_KEY?.trim();
344
+ const hasGatewayUrl = !!env.TOKAGENT_GATEWAY_URL?.trim();
345
+ const hasLiteLLM =
346
+ !!env.LITELLM_BASE_URL?.trim() && !!env.LITELLM_API_KEY?.trim();
347
+ if (!hasDirectKey && !hasBillingChatKey && !hasLiteLLM) {
348
+ fail(
349
+ `No LLM provider configured. The agent has nothing to call for chat.\n` +
350
+ ` Pick one of these three paths:\n` +
351
+ `\n` +
352
+ ` [A] Direct provider: set one of ${DIRECT_LLM_KEYS.slice(0, 4).join(", ")},\n` +
353
+ ` etc. in .env. Plugin auto-loads, chat hits the provider directly.\n` +
354
+ `\n` +
355
+ ` [B] x402 billing (Tokamak rail): mint a sk-ai-* key from the billing\n` +
356
+ ` dashboard, then set BILLING_CHAT_KEY=sk-ai-... in .env.\n` +
357
+ ` Open http://localhost:3000/tokagent-billing/v1/billing/dashboard/\n` +
358
+ ` → connect wallet → top up PTON → mint key. Without this, the\n` +
359
+ ` runtime router has zero TEXT_SMALL/TEXT_LARGE providers.\n` +
360
+ `\n` +
361
+ ` [C] LiteLLM proxy: set LITELLM_BASE_URL + LITELLM_API_KEY in .env\n` +
362
+ ` (plus LITELLM_SMALL_MODEL / LITELLM_LARGE_MODEL).`,
363
+ );
364
+ } else if (hasBillingChatKey && !hasGatewayUrl) {
365
+ fail(
366
+ `BILLING_CHAT_KEY is set but TOKAGENT_GATEWAY_URL is not. The billing\n` +
367
+ ` mirror in core-plugins.ts requires BOTH to wire the gateway as an\n` +
368
+ ` OpenAI-compatible provider — set TOKAGENT_GATEWAY_URL to the URL of\n` +
369
+ ` the tokagent-billing-server you're a client of (the hosted default is\n` +
370
+ ` https://billing-service-production-a8e7.up.railway.app).`,
371
+ );
372
+ }
373
+
232
374
  if (hadFailure) {
233
375
  console.error(
234
376
  "\n\x1b[31m[verify-llm-plugins]\x1b[0m One or more LLM-provider checks failed. Chat will not work until this is resolved.\n",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": "1.0.0",
3
- "generatedAt": "2026-05-27T09:03:47.237Z",
3
+ "generatedAt": "2026-05-27T09:29:35.264Z",
4
4
  "repoUrl": "https://github.com/elizaos/eliza",
5
5
  "templates": [
6
6
  {