@tokagent/tokagentos 2.0.32 → 2.0.34

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.32",
3
+ "version": "2.0.34",
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();
@@ -149,6 +149,47 @@ function readDotenv() {
149
149
  }
150
150
 
151
151
  const env = readDotenv();
152
+
153
+ /**
154
+ * Shell-env vs .env conflict check.
155
+ *
156
+ * Failure mode observed in the field: user rotates an API key in .env, but
157
+ * an old value is still exported in their shell from a prior `export` or a
158
+ * line in ~/.zshrc / ~/.bashrc. dotenv defaults to `override: false`, which
159
+ * means existing process.env values are NOT overwritten by .env. The agent
160
+ * sees the stale shell value, OpenRouter returns 401 "User not found", and
161
+ * the AI SDK surfaces the opaque `AI_NoOutputGeneratedError`. Changing the
162
+ * .env does nothing because the shell value still wins.
163
+ *
164
+ * We don't flip dotenv's `override` flag — that breaks legitimate CI/devops
165
+ * setups that intentionally pass secrets via env vars. Instead we detect
166
+ * the mismatch and tell the user which value will actually be used and how
167
+ * to fix it.
168
+ */
169
+ const SHADOWED_KEYS = [
170
+ "OPENROUTER_API_KEY",
171
+ "ANTHROPIC_API_KEY",
172
+ "OPENAI_API_KEY",
173
+ "GOOGLE_GENERATIVE_AI_API_KEY",
174
+ "GROQ_API_KEY",
175
+ "TAVILY_API_KEY",
176
+ ];
177
+ for (const key of SHADOWED_KEYS) {
178
+ const dotenvValue = env[key];
179
+ const shellValue = process.env[key];
180
+ if (dotenvValue && shellValue && dotenvValue !== shellValue) {
181
+ fail(
182
+ `${key} differs between your shell environment and .env.\n` +
183
+ ` shell : ${shellValue.slice(0, 12)}…${shellValue.slice(-4)} (this is what the agent reads)\n` +
184
+ ` .env : ${dotenvValue.slice(0, 12)}…${dotenvValue.slice(-4)} (this is what you probably edited)\n` +
185
+ ` dotenv runs with override:false by default, so the shell value wins. To fix:\n` +
186
+ ` unset ${key} # remove from the current shell\n` +
187
+ ` grep -n ${key} ~/.zshrc ~/.zprofile ~/.bashrc ~/.bash_profile 2>/dev/null\n` +
188
+ ` # delete any matching lines, restart the shell, then re-run \`bun run dev\``,
189
+ );
190
+ }
191
+ }
192
+
152
193
  if (env.OPENROUTER_API_KEY) {
153
194
  const MODEL_KEYS = [
154
195
  "OPENROUTER_SMALL_MODEL",
@@ -188,6 +229,87 @@ if (env.OPENROUTER_API_KEY) {
188
229
  }
189
230
  }
190
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
+
191
313
  if (hadFailure) {
192
314
  console.error(
193
315
  "\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-27T08:46:59.428Z",
3
+ "generatedAt": "2026-05-27T09:21:58.814Z",
4
4
  "repoUrl": "https://github.com/elizaos/eliza",
5
5
  "templates": [
6
6
  {