@lotargo/memory_plugin 1.4.620 → 1.5.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/README.md +352 -334
- package/mcp-server/admin/auth.js +293 -42
- package/mcp-server/cli/direct_commands.js +313 -0
- package/mcp-server/cli/handlers/cloud_actions.js +138 -0
- package/mcp-server/cli/handlers/diagnostics_actions.js +107 -0
- package/mcp-server/cli/handlers/engine_actions.js +214 -0
- package/mcp-server/cli/handlers/prompt_actions.js +24 -0
- package/mcp-server/cli/handlers/storage_actions.js +749 -0
- package/mcp-server/cli/quick_stats.js +39 -0
- package/mcp-server/cli/ui.js +565 -0
- package/mcp-server/cli.js +324 -1945
- package/mcp-server/config/auth_store.js +178 -19
- package/mcp-server/config/config_manager.js +1 -0
- package/mcp-server/db/database.js +18 -3
- package/mcp-server/db/migrations.js +28 -0
- package/mcp-server/fact_format.js +244 -177
- package/mcp-server/identity.js +152 -0
- package/mcp-server/index.js +42 -679
- package/mcp-server/memory.js +50 -63
- package/mcp-server/prompt_manager.js +1 -1
- package/mcp-server/setup.js +41 -0
- package/mcp-server/tools/helpers.js +39 -0
- package/mcp-server/tools/identity_tools.js +277 -0
- package/mcp-server/tools/index.js +9 -0
- package/mcp-server/tools/memory_tools.js +506 -0
- package/mcp-server/tools/rag_tools.js +235 -0
- package/opencode-plugin/index.js +460 -48
- package/package.json +7 -3
- package/skills/using-memory/SKILL.md +31 -14
- package/mcp-server/benchmarks/fetch_real_corpus.js +0 -351
- package/mcp-server/benchmarks/gpu_profile_benchmark.js +0 -170
- package/mcp-server/benchmarks/quality_evaluator.js +0 -600
- package/mcp-server/benchmarks/run_benchmarks.js +0 -347
- package/mcp-server/benchmarks/stress_ingestion.js +0 -195
- package/mcp-server/benchmarks/test_dual_layer.js +0 -140
package/mcp-server/admin/auth.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
2
|
import crypto from "node:crypto";
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
|
-
import { saveSecrets, deleteSecrets } from "../config/auth_store.js";
|
|
5
|
-
import { updateConfig } from "../config/config_manager.js";
|
|
4
|
+
import { saveSecrets, deleteSecrets, loadSecrets, resolveEnvSecrets, getSecretsSource, invalidateAuthCache, onSecretsChanged } from "../config/auth_store.js";
|
|
5
|
+
import { getConfig, updateConfig } from "../config/config_manager.js";
|
|
6
|
+
|
|
7
|
+
// Register callback so resolveCloudSecrets() cache is cleared when secrets change
|
|
8
|
+
onSecretsChanged(() => { _cachedResolvedSecrets = undefined; _cachedResolvedAt = 0; });
|
|
6
9
|
|
|
7
10
|
export const TURSO_API_BASE = () => process.env.TURSO_API_BASE || "https://api.turso.tech";
|
|
8
11
|
|
|
@@ -273,6 +276,86 @@ function dbHostname(org, dbName) {
|
|
|
273
276
|
return `${dbName}-${org}.turso.io`;
|
|
274
277
|
}
|
|
275
278
|
|
|
279
|
+
// Shared post-auth resolution: validate happens in the caller. Steps:
|
|
280
|
+
// 1. Resolve an organization (explicit, first available, or username fallback).
|
|
281
|
+
// 2. Pick or create a database.
|
|
282
|
+
// 3. Mint a full-access token for that database.
|
|
283
|
+
// 4. Persist the encrypted token + dbUrl and mark the session as authorized.
|
|
284
|
+
async function finalizeCloudLogin({ token, username, org = null, databaseName = null, autoCreate = true, persist = true, apiToken = null }) {
|
|
285
|
+
const accountUsername = username || "user";
|
|
286
|
+
|
|
287
|
+
// Step 1: resolve organization + database namespace
|
|
288
|
+
const orgs = await listOrganizations(token);
|
|
289
|
+
let orgSlug;
|
|
290
|
+
let orgName;
|
|
291
|
+
if (orgs && orgs.length > 0) {
|
|
292
|
+
const requested = org ? orgs.find((o) => (o.slug || o.name || o.id) === org) : null;
|
|
293
|
+
const chosen = requested || orgs[0];
|
|
294
|
+
orgSlug = chosen.slug || chosen.name || chosen.id || String(chosen);
|
|
295
|
+
orgName = chosen.name || orgSlug;
|
|
296
|
+
} else {
|
|
297
|
+
// Personal accounts are not listed in /v1/organizations, but their own
|
|
298
|
+
// username acts as the organization namespace in the Platform API.
|
|
299
|
+
orgSlug = accountUsername;
|
|
300
|
+
orgName = accountUsername;
|
|
301
|
+
console.log(` [CLOUD] No organizations found. Using personal account "${orgSlug}" as the database namespace.`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const dbs = await listDatabases(token, orgSlug);
|
|
305
|
+
if (dbs.length > 0) {
|
|
306
|
+
console.log(`\n [CLOUD] Databases in organization "${orgName}":`);
|
|
307
|
+
dbs.forEach((d, i) => console.log(` ${i + 1}. ${d.name}`));
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
let dbName = databaseName;
|
|
311
|
+
if (!dbName) {
|
|
312
|
+
if (dbs.length > 0) {
|
|
313
|
+
dbName = dbs[0].name;
|
|
314
|
+
console.log(`\n [CLOUD] Using existing database: "${dbName}"`);
|
|
315
|
+
} else if (autoCreate) {
|
|
316
|
+
dbName = `memory-${accountUsername}`;
|
|
317
|
+
console.log(`\n [CLOUD] No database found. Creating "${dbName}"...`);
|
|
318
|
+
await createDatabase(token, orgSlug, dbName);
|
|
319
|
+
console.log(` [OK] Database "${dbName}" created.`);
|
|
320
|
+
} else {
|
|
321
|
+
throw new Error("No databases found and autoCreate is disabled.");
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Step 2: mint a full-access token for the database
|
|
326
|
+
console.log(" [CLOUD] Issuing database access token...");
|
|
327
|
+
const dbJwt = await createDatabaseToken(token, orgSlug, dbName);
|
|
328
|
+
if (!dbJwt) {
|
|
329
|
+
throw new Error("Failed to create database auth token.");
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const dbUrl = `libsql://${dbHostname(orgSlug, dbName)}`;
|
|
333
|
+
|
|
334
|
+
// Step 3: persist secrets and mark authorized
|
|
335
|
+
if (persist) {
|
|
336
|
+
saveSecrets({
|
|
337
|
+
token: dbJwt,
|
|
338
|
+
dbUrl,
|
|
339
|
+
username: accountUsername,
|
|
340
|
+
org: orgSlug,
|
|
341
|
+
db: dbName,
|
|
342
|
+
authorized: true,
|
|
343
|
+
...(apiToken ? { apiToken } : {}),
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
updateConfig({ tursoUrl: dbUrl, authorized: true, username: accountUsername });
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
token: dbJwt,
|
|
350
|
+
dbUrl,
|
|
351
|
+
username: accountUsername,
|
|
352
|
+
org: orgSlug,
|
|
353
|
+
db: dbName,
|
|
354
|
+
authorized: true,
|
|
355
|
+
...(apiToken ? { apiToken } : {}),
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
276
359
|
// Perform the full cloud login flow:
|
|
277
360
|
// 1. OAuth browser flow against Turso (api.turso.tech).
|
|
278
361
|
// 2. Validate the received account JWT.
|
|
@@ -285,6 +368,7 @@ export async function loginToCloud({
|
|
|
285
368
|
simulatedParams = null,
|
|
286
369
|
autoCreate = true,
|
|
287
370
|
databaseName = null,
|
|
371
|
+
org = null,
|
|
288
372
|
} = {}) {
|
|
289
373
|
const state = crypto.randomBytes(16).toString("hex");
|
|
290
374
|
const loginUrl = `${TURSO_API_BASE()}/?port=${customPort}&redirect=true&state=${state}&type=cli`;
|
|
@@ -324,62 +408,229 @@ export async function loginToCloud({
|
|
|
324
408
|
const accountUsername = username || userInfo?.username || userInfo?.name || "user";
|
|
325
409
|
console.log(` [OK] Token is valid. User: ${accountUsername}`);
|
|
326
410
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
411
|
+
const secrets = await finalizeCloudLogin({ token, username: accountUsername, org, databaseName, autoCreate });
|
|
412
|
+
|
|
413
|
+
console.log(`\n \x1b[32m[OK] Successfully signed in to the cloud! Endpoint: ${secrets.dbUrl}\x1b[0m`);
|
|
414
|
+
return secrets;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Headless login with a Turso account API token (no browser, no loopback).
|
|
418
|
+
// Create one at https://console.turso.tech or via `turso auth api-tokens create`.
|
|
419
|
+
// The same Platform API is used to resolve the organization + database and to
|
|
420
|
+
// mint a per-database token, exactly like the browser flow.
|
|
421
|
+
export async function loginWithApiToken({
|
|
422
|
+
token,
|
|
423
|
+
org = null,
|
|
424
|
+
databaseName = null,
|
|
425
|
+
autoCreate = true,
|
|
426
|
+
username = null,
|
|
427
|
+
persist = true,
|
|
428
|
+
} = {}) {
|
|
429
|
+
if (!token) throw new Error("An account API token is required.");
|
|
430
|
+
console.log("\n [CLOUD] Validating account API token...");
|
|
431
|
+
let userInfo = null;
|
|
432
|
+
try {
|
|
433
|
+
userInfo = await validateTursoToken(token);
|
|
434
|
+
} catch (err) {
|
|
435
|
+
throw new Error(`Token validation failed: ${err.message}`);
|
|
340
436
|
}
|
|
437
|
+
const accountUsername = username || userInfo?.username || userInfo?.name || "user";
|
|
438
|
+
console.log(` [OK] API token is valid. User: ${accountUsername}`);
|
|
341
439
|
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
440
|
+
const secrets = await finalizeCloudLogin({
|
|
441
|
+
token,
|
|
442
|
+
username: accountUsername,
|
|
443
|
+
org,
|
|
444
|
+
databaseName,
|
|
445
|
+
autoCreate,
|
|
446
|
+
persist,
|
|
447
|
+
apiToken: persist ? token : null,
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
console.log(`\n \x1b[32m[OK] Successfully signed in to the cloud! Endpoint: ${secrets.dbUrl}\x1b[0m`);
|
|
451
|
+
return secrets;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Direct headless login with an existing database URL + auth token.
|
|
455
|
+
// No Platform API calls are made; org/db are derived from the endpoint.
|
|
456
|
+
export async function loginWithDatabaseToken({
|
|
457
|
+
token,
|
|
458
|
+
dbUrl,
|
|
459
|
+
username = "",
|
|
460
|
+
org = "",
|
|
461
|
+
db = "",
|
|
462
|
+
validate = true,
|
|
463
|
+
} = {}) {
|
|
464
|
+
if (!token || !dbUrl) throw new Error("Both a database auth token and a libsql:// URL are required.");
|
|
465
|
+
|
|
466
|
+
// Derive org + database name from the endpoint (libsql://<db>-<org>.turso.io)
|
|
467
|
+
let resolvedOrg = org;
|
|
468
|
+
let resolvedDb = db;
|
|
469
|
+
const m = String(dbUrl).match(/^libsql:\/\/(.+)\.turso\.io$/);
|
|
470
|
+
if (m) {
|
|
471
|
+
const host = m[1];
|
|
472
|
+
const sep = host.lastIndexOf("-");
|
|
473
|
+
if (sep > 0) {
|
|
474
|
+
resolvedDb = resolvedDb || host.slice(0, sep);
|
|
475
|
+
resolvedOrg = resolvedOrg || host.slice(sep + 1);
|
|
476
|
+
}
|
|
346
477
|
}
|
|
347
478
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
console.log(
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
} else {
|
|
359
|
-
throw new Error("No databases found and autoCreate is disabled.");
|
|
479
|
+
if (validate) {
|
|
480
|
+
console.log(" [CLOUD] Validating database token against the endpoint...");
|
|
481
|
+
try {
|
|
482
|
+
const { createClient } = await import("@libsql/client");
|
|
483
|
+
const client = createClient({ url: dbUrl, authToken: token });
|
|
484
|
+
await client.execute("SELECT 1");
|
|
485
|
+
client.close();
|
|
486
|
+
console.log(" [OK] Database token validated.");
|
|
487
|
+
} catch (err) {
|
|
488
|
+
throw new Error(`Database token validation failed: ${err.message}`);
|
|
360
489
|
}
|
|
361
490
|
}
|
|
362
491
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
492
|
+
saveSecrets({ token, dbUrl, username, org: resolvedOrg, db: resolvedDb, authorized: true });
|
|
493
|
+
updateConfig({ tursoUrl: dbUrl, authorized: true, username });
|
|
494
|
+
|
|
495
|
+
console.log(`\n \x1b[32m[OK] Successfully signed in to the cloud! Endpoint: ${dbUrl}\x1b[0m`);
|
|
496
|
+
return { token, dbUrl, username, org: resolvedOrg, db: resolvedDb, authorized: true };
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Pick up credentials from the environment or MEMORY_DIR/.env.
|
|
500
|
+
// - TURSO_DB_URL + TURSO_DB_TOKEN: direct endpoint login (preferred, no API calls).
|
|
501
|
+
// - TURSO_API_TOKEN: account API-token flow resolving org/db via the Platform API.
|
|
502
|
+
// When persist is true the resolved secrets are also written to the encrypted store.
|
|
503
|
+
export async function loginFromEnv({ persist = false } = {}) {
|
|
504
|
+
const env = resolveEnvSecrets();
|
|
505
|
+
if (!env) {
|
|
506
|
+
return { ok: false, reason: "No cloud secrets found in the environment or MEMORY_DIR/.env." };
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (env.dbUrl && env.token) {
|
|
510
|
+
const secrets = {
|
|
511
|
+
token: env.token,
|
|
512
|
+
dbUrl: env.dbUrl,
|
|
513
|
+
username: env.username || "",
|
|
514
|
+
org: env.org || "",
|
|
515
|
+
db: env.database || "",
|
|
516
|
+
authorized: true,
|
|
517
|
+
};
|
|
518
|
+
if (persist) saveSecrets(secrets);
|
|
519
|
+
updateConfig({ tursoUrl: env.dbUrl, authorized: true, username: secrets.username });
|
|
520
|
+
console.log(`\n \x1b[32m[OK] Cloud credentials imported from the environment! Endpoint: ${env.dbUrl}\x1b[0m`);
|
|
521
|
+
return { ok: true, secrets, source: "env" };
|
|
368
522
|
}
|
|
369
523
|
|
|
370
|
-
|
|
524
|
+
if (env.apiToken) {
|
|
525
|
+
// Resolve lazily; the raw API token stays in the environment and is NOT
|
|
526
|
+
// persisted to the encrypted store (explicit `login --api-token` does that).
|
|
527
|
+
const secrets = await loginWithApiToken({
|
|
528
|
+
token: env.apiToken,
|
|
529
|
+
org: env.org || null,
|
|
530
|
+
databaseName: env.database || null,
|
|
531
|
+
username: env.username || null,
|
|
532
|
+
persist: false,
|
|
533
|
+
});
|
|
534
|
+
return { ok: true, secrets: { ...secrets, apiToken: env.apiToken }, source: "env" };
|
|
535
|
+
}
|
|
371
536
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
537
|
+
return {
|
|
538
|
+
ok: false,
|
|
539
|
+
reason: "Incomplete cloud secrets. Set TURSO_DB_URL + TURSO_DB_TOKEN (preferred) or TURSO_API_TOKEN (with optional TURSO_ORG / TURSO_DATABASE).",
|
|
540
|
+
};
|
|
541
|
+
}
|
|
375
542
|
|
|
376
|
-
|
|
377
|
-
|
|
543
|
+
// Async resolution of the working cloud credentials (a DB URL + auth token).
|
|
544
|
+
// Used by database.js at startup so a raw TURSO_API_TOKEN environment token
|
|
545
|
+
// (which can only call the Platform API, not libsql) gets minted into a
|
|
546
|
+
// per-database JWT without any interactive step.
|
|
547
|
+
let _cachedResolvedSecrets = undefined;
|
|
548
|
+
let _cachedResolvedAt = 0;
|
|
549
|
+
const RESOLVED_CACHE_TTL_MS = 60_000; // 60s — avoids re-minting JWT on every getDatabase()
|
|
550
|
+
|
|
551
|
+
export function invalidateResolvedCache() {
|
|
552
|
+
_cachedResolvedSecrets = undefined;
|
|
553
|
+
_cachedResolvedAt = 0;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
export async function resolveCloudSecrets() {
|
|
557
|
+
const now = Date.now();
|
|
558
|
+
if (_cachedResolvedSecrets !== undefined && (now - _cachedResolvedAt) < RESOLVED_CACHE_TTL_MS) {
|
|
559
|
+
return _cachedResolvedSecrets;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const secrets = loadSecrets();
|
|
563
|
+
if (!secrets) { _cachedResolvedSecrets = null; return null; }
|
|
564
|
+
if (secrets.apiToken && secrets.needsResolution) {
|
|
565
|
+
const resolved = await loginWithApiToken({
|
|
566
|
+
token: secrets.apiToken,
|
|
567
|
+
org: secrets.org || null,
|
|
568
|
+
databaseName: secrets.db || null,
|
|
569
|
+
username: secrets.username || null,
|
|
570
|
+
persist: false,
|
|
571
|
+
});
|
|
572
|
+
_cachedResolvedSecrets = { ...resolved, source: "env" };
|
|
573
|
+
_cachedResolvedAt = Date.now();
|
|
574
|
+
return _cachedResolvedSecrets;
|
|
575
|
+
}
|
|
576
|
+
_cachedResolvedSecrets = secrets;
|
|
577
|
+
_cachedResolvedAt = Date.now();
|
|
578
|
+
return _cachedResolvedSecrets;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Store/replace a Turso account API token. Alias of the headless login flow:
|
|
582
|
+
// the token is validated, an org/database is resolved and a per-database JWT
|
|
583
|
+
// is minted, then both the API token and the resolved session are persisted.
|
|
584
|
+
export async function setApiKey(token, { org = null, databaseName = null } = {}) {
|
|
585
|
+
if (!token || typeof token !== "string" || !token.trim()) {
|
|
586
|
+
throw new Error("An account API token is required.");
|
|
587
|
+
}
|
|
588
|
+
const secrets = await loginWithApiToken({ token: token.trim(), org, databaseName });
|
|
589
|
+
return { ok: true, secrets };
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Remove a stored API token. The resolved database session is kept as a plain
|
|
593
|
+
// browser/database session so an already-synced deployment keeps working.
|
|
594
|
+
export function clearApiKey() {
|
|
595
|
+
const existing = loadSecrets();
|
|
596
|
+
if (!existing || !existing.apiToken) return { removed: false };
|
|
597
|
+
const rest = { ...existing };
|
|
598
|
+
delete rest.apiToken;
|
|
599
|
+
if (rest.token && rest.dbUrl) {
|
|
600
|
+
saveSecrets({ ...rest, authorized: true });
|
|
601
|
+
invalidateAuthCache();
|
|
602
|
+
invalidateResolvedCache();
|
|
603
|
+
return { removed: true, keptDbSession: true };
|
|
604
|
+
}
|
|
605
|
+
deleteSecrets();
|
|
606
|
+
invalidateAuthCache();
|
|
607
|
+
invalidateResolvedCache();
|
|
608
|
+
return { removed: true, keptDbSession: false };
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// Non-throwing status report used by `auth-status` and the TUI.
|
|
612
|
+
export function getAuthStatus() {
|
|
613
|
+
const secrets = loadSecrets();
|
|
614
|
+
const config = getConfig();
|
|
615
|
+
const source = getSecretsSource();
|
|
616
|
+
return {
|
|
617
|
+
source: source || "none",
|
|
618
|
+
configured: !!(secrets?.dbUrl || config.tursoUrl),
|
|
619
|
+
authorized: !!config.authorized || source === "env" || source === "api-key",
|
|
620
|
+
hasApiKey: !!secrets?.apiToken,
|
|
621
|
+
dbUrl: secrets?.dbUrl || config.tursoUrl || "",
|
|
622
|
+
username: secrets?.username || config.username || "",
|
|
623
|
+
org: secrets?.org || "",
|
|
624
|
+
database: secrets?.db || "",
|
|
625
|
+
mode: config.mode || "only-local",
|
|
626
|
+
};
|
|
378
627
|
}
|
|
379
628
|
|
|
380
629
|
// Logout and reset configurations
|
|
381
630
|
export function logoutFromCloud() {
|
|
382
631
|
const deleted = deleteSecrets();
|
|
632
|
+
invalidateAuthCache();
|
|
633
|
+
invalidateResolvedCache();
|
|
383
634
|
updateConfig({ tursoUrl: "", mode: "only-local", authorized: false, username: "" });
|
|
384
635
|
return deleted;
|
|
385
636
|
}
|