@jhizzard/termdeck 0.18.0 → 1.0.1
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 +1 -1
- package/packages/cli/src/init-mnestra.js +40 -1
- package/packages/cli/src/init-rumen.js +337 -29
- package/packages/client/public/app.js +131 -1
- package/packages/server/src/agent-adapters/claude.js +55 -0
- package/packages/server/src/agent-adapters/codex.js +82 -0
- package/packages/server/src/agent-adapters/gemini.js +57 -0
- package/packages/server/src/agent-adapters/grok.js +82 -0
- package/packages/server/src/index.js +132 -0
- package/packages/server/src/setup/audit-upgrade.js +302 -0
- package/packages/server/src/setup/index.js +2 -1
- package/packages/server/src/setup/mnestra-migrations/013_reclassify_uncertain.sql +39 -0
- package/packages/server/src/setup/mnestra-migrations/014_explicit_grants.sql +46 -0
- package/packages/server/src/setup/mnestra-migrations/015_source_agent.sql +51 -0
- package/packages/server/src/setup/mnestra-migrations/016_mnestra_doctor_probes.sql +117 -0
- package/packages/server/src/setup/preconditions.js +36 -4
- package/packages/server/src/setup/rumen/functions/graph-inference/index.ts +6 -2
- package/packages/server/src/setup/rumen/functions/rumen-tick/index.ts +6 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jhizzard/termdeck",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Browser-based terminal multiplexer with metadata overlays, panel flashback memory recall, and AI-aware session management",
|
|
5
5
|
"bin": {
|
|
6
6
|
"termdeck": "./packages/cli/src/index.js"
|
|
@@ -45,7 +45,8 @@ const {
|
|
|
45
45
|
supabaseUrl: urlHelper,
|
|
46
46
|
migrations,
|
|
47
47
|
pgRunner,
|
|
48
|
-
preconditions
|
|
48
|
+
preconditions,
|
|
49
|
+
auditUpgrade: auditUpgradeMod
|
|
49
50
|
} = require(SETUP_DIR);
|
|
50
51
|
|
|
51
52
|
const HELP = [
|
|
@@ -339,6 +340,43 @@ async function applyMigrations(client, dryRun) {
|
|
|
339
340
|
}
|
|
340
341
|
}
|
|
341
342
|
|
|
343
|
+
// Sprint 51.5 T1 — schema-introspection audit-upgrade. Runs AFTER the
|
|
344
|
+
// fresh-install applyMigrations() loop completes, but reports separately.
|
|
345
|
+
// On a Sprint-37-era project that pre-dated several mnestra migrations,
|
|
346
|
+
// applyMigrations now has the full bundled set (Sprint 51.5 synced 013-015
|
|
347
|
+
// from canonical engram), so this audit is mostly a belt-and-suspenders
|
|
348
|
+
// confirmation. It still surfaces drift if, e.g., the user manually
|
|
349
|
+
// dropped a column post-install. Mnestra-kind only — rumen cron probes
|
|
350
|
+
// reference vault secrets the user hasn't set up yet at this point. Run
|
|
351
|
+
// init-rumen for the rumen-side audit.
|
|
352
|
+
async function runMnestraAudit(client, projectRef, dryRun) {
|
|
353
|
+
step('Audit-upgrade: probing for missing mnestra schema artifacts...');
|
|
354
|
+
if (dryRun) { ok('(dry-run)'); return; }
|
|
355
|
+
const probes = auditUpgradeMod.PROBES.filter((p) => p.kind === 'mnestra');
|
|
356
|
+
let result;
|
|
357
|
+
try {
|
|
358
|
+
result = await auditUpgradeMod.auditUpgrade({
|
|
359
|
+
pgClient: client,
|
|
360
|
+
projectRef,
|
|
361
|
+
probes
|
|
362
|
+
});
|
|
363
|
+
} catch (err) {
|
|
364
|
+
fail(err.message);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
if (result.applied.length === 0 && result.errors.length === 0) {
|
|
368
|
+
ok(`(install up to date — ${result.probed.length} probes all present)`);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
ok(`(probed ${result.probed.length}, applied ${result.applied.length})`);
|
|
372
|
+
for (const name of result.applied) {
|
|
373
|
+
process.stdout.write(` ✓ applied ${name}\n`);
|
|
374
|
+
}
|
|
375
|
+
for (const e of result.errors) {
|
|
376
|
+
process.stdout.write(` ! ${e.name}: ${e.error}\n`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
342
380
|
async function checkExistingStore(client) {
|
|
343
381
|
step('Checking for existing memory_items table...');
|
|
344
382
|
try {
|
|
@@ -538,6 +576,7 @@ async function main(argv) {
|
|
|
538
576
|
try {
|
|
539
577
|
await checkExistingStore(client);
|
|
540
578
|
await applyMigrations(client, false);
|
|
579
|
+
await runMnestraAudit(client, inputs.projectUrl.projectRef, false);
|
|
541
580
|
writeYamlConfig(false);
|
|
542
581
|
// v0.6.9: post-write outcome verification. Confirms each migration's
|
|
543
582
|
// expected schema bits actually landed — including memory_items.
|
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
// 4. Apply rumen migration 001 via pg
|
|
21
21
|
// 5. supabase functions deploy rumen-tick AND graph-inference (Sprint 43 T3)
|
|
22
22
|
// from a single staging dir with multi-function supabase/config.toml
|
|
23
|
-
// 6. supabase secrets set DATABASE_URL
|
|
23
|
+
// 6. supabase secrets set, ONE call per key (DATABASE_URL, ANTHROPIC_API_KEY,
|
|
24
|
+
// [OPENAI_API_KEY], [GRAPH_LLM_CLASSIFY=1]) — per-secret to avoid the
|
|
25
|
+
// v2.90.0 multi-arg drop documented in INSTALLER-PITFALLS.md Class J.
|
|
24
26
|
// 7. Test rumen-tick with a manual POST (graph-inference is cron-only)
|
|
25
27
|
// 8. Apply pg_cron schedule migrations 002 (rumen-tick) AND 003 (graph-inference)
|
|
26
28
|
// with project ref substituted
|
|
@@ -38,7 +40,8 @@ const {
|
|
|
38
40
|
migrations,
|
|
39
41
|
migrationTemplating,
|
|
40
42
|
pgRunner,
|
|
41
|
-
preconditions
|
|
43
|
+
preconditions,
|
|
44
|
+
auditUpgrade: auditUpgradeMod
|
|
42
45
|
} = require(SETUP_DIR);
|
|
43
46
|
|
|
44
47
|
const {
|
|
@@ -303,6 +306,57 @@ async function applyRumenTables(secrets, dryRun) {
|
|
|
303
306
|
}
|
|
304
307
|
}
|
|
305
308
|
|
|
309
|
+
// Sprint 51.5 T1 — schema-introspection audit-upgrade. Probes for missing
|
|
310
|
+
// mnestra schema artifacts AND missing rumen cron schedules. Runs before
|
|
311
|
+
// the existing init-rumen flow so the user sees what's about to be applied
|
|
312
|
+
// up front. Idempotent: a re-run on an up-to-date project reports
|
|
313
|
+
// "install up to date" and applies nothing.
|
|
314
|
+
//
|
|
315
|
+
// Brad's 2026-05-02 jizzard-brain report (INSTALLER-PITFALLS.md ledger #13)
|
|
316
|
+
// is the originating motivation: he upgraded npm packages but his database
|
|
317
|
+
// stayed frozen at first-kickstart because no installer code path diffed an
|
|
318
|
+
// existing install against the bundled migration set. After v1.0.1 ships,
|
|
319
|
+
// `npm install -g @jhizzard/termdeck@1.0.1 && termdeck init --rumen` will
|
|
320
|
+
// surface and apply every missing artifact in one pass.
|
|
321
|
+
//
|
|
322
|
+
// Errors are surfaced inline but do NOT abort the wizard — a single
|
|
323
|
+
// failing probe (e.g., pg_cron not enabled when probing for cron.job)
|
|
324
|
+
// shouldn't block the rest of the audit or the rest of the init flow.
|
|
325
|
+
async function runRumenAudit(projectRef, secrets, dryRun) {
|
|
326
|
+
step('Audit-upgrade: probing for missing schema + cron artifacts...');
|
|
327
|
+
if (dryRun) { ok('(dry-run)'); return true; }
|
|
328
|
+
let client;
|
|
329
|
+
try {
|
|
330
|
+
client = await pgRunner.connect(secrets.DATABASE_URL);
|
|
331
|
+
} catch (err) {
|
|
332
|
+
fail(err.message);
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
try {
|
|
336
|
+
const result = await auditUpgradeMod.auditUpgrade({
|
|
337
|
+
pgClient: client,
|
|
338
|
+
projectRef
|
|
339
|
+
});
|
|
340
|
+
if (result.applied.length === 0 && result.errors.length === 0) {
|
|
341
|
+
ok(`(install up to date — ${result.probed.length} probes all present)`);
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
ok(`(probed ${result.probed.length}, applied ${result.applied.length})`);
|
|
345
|
+
for (const name of result.applied) {
|
|
346
|
+
process.stdout.write(` ✓ applied ${name}\n`);
|
|
347
|
+
}
|
|
348
|
+
for (const e of result.errors) {
|
|
349
|
+
process.stdout.write(` ! ${e.name}: ${e.error}\n`);
|
|
350
|
+
}
|
|
351
|
+
return true;
|
|
352
|
+
} catch (err) {
|
|
353
|
+
fail(err.message);
|
|
354
|
+
return true; // non-blocking
|
|
355
|
+
} finally {
|
|
356
|
+
try { await client.end(); } catch (_err) { /* ignore */ }
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
306
360
|
// Sprint 43 T3: rumen-tick is the only function with a `__RUMEN_VERSION__`
|
|
307
361
|
// placeholder (its `npm:@jhizzard/rumen@<ver>` import is rewritten at deploy
|
|
308
362
|
// time). graph-inference pins its own deps (`npm:postgres@3.4.4`) and is
|
|
@@ -400,29 +454,238 @@ ${fnBlocks}`;
|
|
|
400
454
|
return stage;
|
|
401
455
|
}
|
|
402
456
|
|
|
403
|
-
|
|
457
|
+
// Sprint 51.5 T3: per-secret CLI loop. Pre-Sprint-51.5 this issued a single
|
|
458
|
+
// `supabase secrets set KEY1=VAL1 KEY2=VAL2 ...` call with all keys as
|
|
459
|
+
// positional args. Brad's 2026-05-03 4-project install pass observed
|
|
460
|
+
// supabase CLI v2.90.0 silently dropping some args from a multi-arg call —
|
|
461
|
+
// even materializing stray entries from misparsed argv (his email landed as
|
|
462
|
+
// a secret name). Documented as INSTALLER-PITFALLS.md Class J. The
|
|
463
|
+
// deterministic fix is one CLI invocation per secret, exit code checked per
|
|
464
|
+
// call, stderr surfaced with the failing key name. Order is preserved for
|
|
465
|
+
// log readability (DATABASE_URL → ANTHROPIC_API_KEY → optional → optional).
|
|
466
|
+
function setFunctionSecrets(secrets, dryRun, opts = {}) {
|
|
467
|
+
const orderedKeys = ['DATABASE_URL', 'ANTHROPIC_API_KEY'];
|
|
468
|
+
if (secrets.OPENAI_API_KEY) orderedKeys.push('OPENAI_API_KEY');
|
|
469
|
+
if (secrets.GRAPH_LLM_CLASSIFY === '1') orderedKeys.push('GRAPH_LLM_CLASSIFY');
|
|
470
|
+
|
|
404
471
|
const haveOpenAI = Boolean(secrets.OPENAI_API_KEY);
|
|
405
|
-
|
|
406
|
-
? 'DATABASE_URL, ANTHROPIC_API_KEY, OPENAI_API_KEY'
|
|
407
|
-
: 'DATABASE_URL, ANTHROPIC_API_KEY';
|
|
408
|
-
step(`Setting function secrets (${label})...`);
|
|
472
|
+
step(`Setting function secrets per-call (${orderedKeys.join(', ')})...`);
|
|
409
473
|
if (dryRun) { ok('(dry-run)'); return true; }
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
474
|
+
|
|
475
|
+
// Test surface: opts.runner is an optional injected function that mimics
|
|
476
|
+
// runShellCaptured((bin, args) => { ok, code, stdout, stderr }). Production
|
|
477
|
+
// path passes nothing and shells out to the real supabase CLI.
|
|
478
|
+
const runner = (typeof opts.runner === 'function') ? opts.runner : runShellCaptured;
|
|
479
|
+
|
|
480
|
+
for (const key of orderedKeys) {
|
|
481
|
+
const value = secrets[key];
|
|
482
|
+
if (value === undefined || value === null || value === '') {
|
|
483
|
+
fail(`secret ${key} missing from in-memory secrets map — wizard wiring bug`);
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
const r = runner('supabase', ['secrets', 'set', `${key}=${value}`]);
|
|
487
|
+
if (!r || !r.ok) {
|
|
488
|
+
fail(`supabase secrets set ${key} failed (exit ${r ? r.code : 'no-result'})`);
|
|
489
|
+
if (r && r.stderr) process.stderr.write(r.stderr + '\n');
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
const llmTag = secrets.GRAPH_LLM_CLASSIFY === '1' ? ', graph LLM classify on' : '';
|
|
494
|
+
ok(`${haveOpenAI ? '(hybrid mode' : '(keyword-only mode — OPENAI_API_KEY not set'}${llmTag})`);
|
|
495
|
+
return true;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Sprint 51.5 T3: Build a Supabase SQL-Editor deeplink that pre-fills the
|
|
499
|
+
// vault.create_secret() call for one secret. Used as the fallback when
|
|
500
|
+
// auto-apply via pgRunner can't write to vault.secrets (permission denied,
|
|
501
|
+
// missing extension, etc.) AND as the manual-fix surface in any wizard text
|
|
502
|
+
// that previously instructed users to "click Vault in the dashboard" — the
|
|
503
|
+
// Vault dashboard panel was quietly removed/relocated in current Supabase
|
|
504
|
+
// UIs (Brad 2026-05-03 takeaway #2; INSTALLER-PITFALLS.md Class B).
|
|
505
|
+
//
|
|
506
|
+
// vault.create_secret signature is `(secret text, name text [, description text])`
|
|
507
|
+
// — value-then-name. Both arguments are escaped as Postgres string literals
|
|
508
|
+
// (single-quote doubling). The full URL is roughly:
|
|
509
|
+
// https://supabase.com/dashboard/project/<ref>/sql/new?content=<encoded SQL>
|
|
510
|
+
// Click → SQL Editor opens with the call pre-filled → user clicks Run.
|
|
511
|
+
function vaultSqlEditorUrl(projectRef, secretName, secretValue) {
|
|
512
|
+
if (!projectRef || typeof projectRef !== 'string') {
|
|
513
|
+
throw new Error('vaultSqlEditorUrl: projectRef is required');
|
|
514
|
+
}
|
|
515
|
+
const value = String(secretValue == null ? '' : secretValue).replace(/'/g, "''");
|
|
516
|
+
const name = String(secretName == null ? '' : secretName).replace(/'/g, "''");
|
|
517
|
+
const sql = `select vault.create_secret('${value}', '${name}');`;
|
|
518
|
+
return `https://supabase.com/dashboard/project/${projectRef}/sql/new?content=${encodeURIComponent(sql)}`;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Sprint 51.5 T3: Ensure the two Vault secrets the cron schedules need are
|
|
522
|
+
// present, auto-creating them via the user's pg connection when possible.
|
|
523
|
+
//
|
|
524
|
+
// Required:
|
|
525
|
+
// - rumen_service_role_key (used by 002_pg_cron_schedule.sql)
|
|
526
|
+
// - graph_inference_service_role_key (used by 003_graph_inference_schedule.sql)
|
|
527
|
+
//
|
|
528
|
+
// Both keys hold the same value (`secrets.SUPABASE_SERVICE_ROLE_KEY`). Brad's
|
|
529
|
+
// 2026-05-02 recovery on jizzard-brain literally cloned rumen → graph_inference
|
|
530
|
+
// in vault.
|
|
531
|
+
//
|
|
532
|
+
// Strategy:
|
|
533
|
+
// 1. Open a pg connection to DATABASE_URL (same path as applyRumenTables).
|
|
534
|
+
// 2. Probe vault.secrets for both names. (Reads vault.secrets, NOT
|
|
535
|
+
// vault.decrypted_secrets — we don't need the decrypted value, just
|
|
536
|
+
// presence.)
|
|
537
|
+
// 3. For any missing name, call `vault.create_secret($value, $name)`.
|
|
538
|
+
// 4. On per-secret failure (permission denied, etc.), surface a SQL-Editor
|
|
539
|
+
// deeplink the user can click; do not fail the wizard hard — the
|
|
540
|
+
// preconditions audit will catch a still-missing rumen_service_role_key
|
|
541
|
+
// with its own hint, and the user has the actionable URL in front of
|
|
542
|
+
// them either way.
|
|
543
|
+
//
|
|
544
|
+
// Returns `{ ok, created: [...], deeplinks: [{ name, url, error }] }`.
|
|
545
|
+
async function ensureVaultSecrets({ projectRef, secrets, dryRun, _pgClient }) {
|
|
546
|
+
const required = [
|
|
547
|
+
{ name: 'rumen_service_role_key', value: secrets.SUPABASE_SERVICE_ROLE_KEY },
|
|
548
|
+
{ name: 'graph_inference_service_role_key', value: secrets.SUPABASE_SERVICE_ROLE_KEY }
|
|
414
549
|
];
|
|
415
|
-
|
|
416
|
-
|
|
550
|
+
|
|
551
|
+
step('Ensuring Vault secrets (rumen_service_role_key, graph_inference_service_role_key)...');
|
|
552
|
+
if (dryRun) { ok('(dry-run)'); return { ok: true, created: [], deeplinks: [] }; }
|
|
553
|
+
|
|
554
|
+
if (!secrets.SUPABASE_SERVICE_ROLE_KEY) {
|
|
555
|
+
fail('SUPABASE_SERVICE_ROLE_KEY missing from in-memory secrets — preflight should have rejected');
|
|
556
|
+
return { ok: false, created: [], deeplinks: [], error: 'service-role-key-missing' };
|
|
417
557
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
558
|
+
|
|
559
|
+
let client = _pgClient;
|
|
560
|
+
let ownsClient = false;
|
|
561
|
+
if (!client) {
|
|
562
|
+
try {
|
|
563
|
+
client = await pgRunner.connect(secrets.DATABASE_URL);
|
|
564
|
+
ownsClient = true;
|
|
565
|
+
} catch (err) {
|
|
566
|
+
fail(err.message);
|
|
567
|
+
return { ok: false, created: [], deeplinks: [], error: 'pg-connect-failed' };
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
let existing = new Set();
|
|
572
|
+
try {
|
|
573
|
+
const probe = await client.query(
|
|
574
|
+
'SELECT name FROM vault.secrets WHERE name = ANY($1::text[])',
|
|
575
|
+
[required.map((x) => x.name)]
|
|
576
|
+
);
|
|
577
|
+
existing = new Set((probe.rows || []).map((r) => r.name));
|
|
578
|
+
} catch (err) {
|
|
579
|
+
fail(`vault.secrets probe failed: ${err.message}`);
|
|
580
|
+
if (ownsClient) { try { await client.end(); } catch (_e) { /* ignore */ } }
|
|
581
|
+
// Emit deeplinks for both — user can click through manually.
|
|
582
|
+
const deeplinks = required.map(({ name, value }) => ({
|
|
583
|
+
name,
|
|
584
|
+
url: vaultSqlEditorUrl(projectRef, name, value),
|
|
585
|
+
error: err.message
|
|
586
|
+
}));
|
|
587
|
+
printVaultDeeplinks(deeplinks);
|
|
588
|
+
return { ok: false, created: [], deeplinks, error: 'vault-probe-failed' };
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const missing = required.filter((x) => !existing.has(x.name));
|
|
592
|
+
if (missing.length === 0) {
|
|
593
|
+
if (ownsClient) { try { await client.end(); } catch (_e) { /* ignore */ } }
|
|
594
|
+
ok('(both already present)');
|
|
595
|
+
return { ok: true, created: [], deeplinks: [] };
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const created = [];
|
|
599
|
+
const deeplinks = [];
|
|
600
|
+
for (const { name, value } of missing) {
|
|
601
|
+
try {
|
|
602
|
+
await client.query('SELECT vault.create_secret($1, $2)', [value, name]);
|
|
603
|
+
created.push(name);
|
|
604
|
+
} catch (err) {
|
|
605
|
+
deeplinks.push({
|
|
606
|
+
name,
|
|
607
|
+
url: vaultSqlEditorUrl(projectRef, name, value),
|
|
608
|
+
error: err && err.message ? err.message : String(err)
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
if (ownsClient) { try { await client.end(); } catch (_e) { /* ignore */ } }
|
|
613
|
+
|
|
614
|
+
if (deeplinks.length > 0) {
|
|
615
|
+
fail(`auto-created ${created.length} of ${missing.length}; ${deeplinks.length} need a manual SQL Editor click`);
|
|
616
|
+
printVaultDeeplinks(deeplinks);
|
|
617
|
+
return { ok: false, created, deeplinks };
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
ok(`(created ${created.length}: ${created.map((n) => n).join(', ')})`);
|
|
621
|
+
return { ok: true, created, deeplinks: [] };
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function printVaultDeeplinks(deeplinks) {
|
|
625
|
+
process.stderr.write(
|
|
626
|
+
'\nThe Supabase Vault dashboard panel has been removed in current Supabase UIs.\n' +
|
|
627
|
+
'Open each link below and click Run in SQL Editor to create the secret:\n\n'
|
|
628
|
+
);
|
|
629
|
+
for (const d of deeplinks) {
|
|
630
|
+
process.stderr.write(` ${d.name}\n ${d.url}\n`);
|
|
631
|
+
if (d.error) process.stderr.write(` (auto-apply error: ${d.error})\n`);
|
|
632
|
+
process.stderr.write('\n');
|
|
423
633
|
}
|
|
424
|
-
|
|
425
|
-
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Sprint 51.5 T3: Install-time prompt for the GRAPH_LLM_CLASSIFY toggle.
|
|
637
|
+
//
|
|
638
|
+
// graph-inference (the daily cron Edge Function) defaults every new edge to
|
|
639
|
+
// `relates_to` unless GRAPH_LLM_CLASSIFY=1 AND ANTHROPIC_API_KEY are set as
|
|
640
|
+
// Edge Function secrets. Pre-Sprint-51.5, no install path covered this — the
|
|
641
|
+
// wizard set ANTHROPIC_API_KEY (already required) but never set
|
|
642
|
+
// GRAPH_LLM_CLASSIFY, leaving the LLM classifier off by default. This is
|
|
643
|
+
// INSTALLER-PITFALLS.md Class F (default-vs-runtime asymmetry — Joshua may
|
|
644
|
+
// or may not have set the flag manually; new installs definitely don't).
|
|
645
|
+
//
|
|
646
|
+
// Side-effect: mutates `secrets.GRAPH_LLM_CLASSIFY` to '1' on Y-path. The
|
|
647
|
+
// per-secret loop in setFunctionSecrets reads that key and pushes it into
|
|
648
|
+
// the supabase secrets set sequence. On N-path the key is left undefined
|
|
649
|
+
// and the loop skips it; the wizard prints the manual flip command so the
|
|
650
|
+
// user can opt in later without re-running the whole wizard.
|
|
651
|
+
//
|
|
652
|
+
// --yes accepts the default (Y). --dry-run reports the plan and assumes Y.
|
|
653
|
+
async function promptGraphLlmClassify({ secrets, flags }) {
|
|
654
|
+
const explainer =
|
|
655
|
+
'\nGraph edge classification\n' +
|
|
656
|
+
'─────────────────────────\n' +
|
|
657
|
+
'When enabled, the daily graph-inference cron uses Claude Haiku 4.5 to label\n' +
|
|
658
|
+
'each new edge with a relationship type (supersedes, contradicts, elaborates,\n' +
|
|
659
|
+
'caused_by, blocks, inspired_by, cross_project_link, relates_to).\n' +
|
|
660
|
+
'\n' +
|
|
661
|
+
'Cost: ~$0.003 per 1k edges classified (a typical project sees a few hundred\n' +
|
|
662
|
+
'new edges per day). Disabled = every edge is typed "relates_to".\n\n';
|
|
663
|
+
|
|
664
|
+
if (flags.dryRun) {
|
|
665
|
+
process.stdout.write(explainer);
|
|
666
|
+
process.stdout.write('? Enable AI-classified graph edges? [Y/n] (dry-run, defaulting Y)\n');
|
|
667
|
+
secrets.GRAPH_LLM_CLASSIFY = '1';
|
|
668
|
+
return { enabled: true, source: 'dry-run' };
|
|
669
|
+
}
|
|
670
|
+
if (flags.yes) {
|
|
671
|
+
process.stdout.write(explainer);
|
|
672
|
+
process.stdout.write('? Enable AI-classified graph edges? [Y/n] (--yes, defaulting Y)\n');
|
|
673
|
+
secrets.GRAPH_LLM_CLASSIFY = '1';
|
|
674
|
+
return { enabled: true, source: '--yes' };
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
process.stdout.write(explainer);
|
|
678
|
+
const yes = await prompts.confirm('? Enable AI-classified graph edges?', { defaultYes: true });
|
|
679
|
+
if (yes) {
|
|
680
|
+
secrets.GRAPH_LLM_CLASSIFY = '1';
|
|
681
|
+
process.stdout.write(' → Will set GRAPH_LLM_CLASSIFY=1 + ANTHROPIC_API_KEY in Edge Function secrets.\n\n');
|
|
682
|
+
return { enabled: true, source: 'prompt' };
|
|
683
|
+
}
|
|
684
|
+
process.stdout.write(
|
|
685
|
+
' → GRAPH_LLM_CLASSIFY left unset. Edges will default to "relates_to".\n' +
|
|
686
|
+
' To enable later: supabase secrets set GRAPH_LLM_CLASSIFY=1\n\n'
|
|
687
|
+
);
|
|
688
|
+
return { enabled: false, source: 'prompt' };
|
|
426
689
|
}
|
|
427
690
|
|
|
428
691
|
async function testFunction(projectRef, secrets, dryRun) {
|
|
@@ -601,13 +864,22 @@ function wireAccessTokenInMcpJson({ token, mcpJsonPath, _testFs } = {}) {
|
|
|
601
864
|
return { status: 'updated', path: targetPath };
|
|
602
865
|
}
|
|
603
866
|
|
|
604
|
-
function printNextSteps(projectRef) {
|
|
867
|
+
function printNextSteps(projectRef, vaultResult, llmResult) {
|
|
605
868
|
const rumenTickUrl = `https://${projectRef}.supabase.co/functions/v1/rumen-tick`;
|
|
606
869
|
const graphInferenceUrl = `https://${projectRef}.supabase.co/functions/v1/graph-inference`;
|
|
607
870
|
const now = new Date();
|
|
608
871
|
// Round up to the next 15-minute mark so the hint is accurate.
|
|
609
872
|
const next = new Date(now.getTime());
|
|
610
873
|
next.setUTCMinutes(Math.ceil((now.getUTCMinutes() + 1) / 15) * 15, 0, 0);
|
|
874
|
+
|
|
875
|
+
const vaultLine = (vaultResult && vaultResult.ok)
|
|
876
|
+
? ' Vault secrets: rumen_service_role_key + graph_inference_service_role_key in place.'
|
|
877
|
+
: ' Vault secrets: open the SQL Editor URLs above and click Run for any deeplinks shown.';
|
|
878
|
+
|
|
879
|
+
const llmLine = llmResult && llmResult.enabled
|
|
880
|
+
? ' Graph edges: classified by Claude Haiku 4.5 (GRAPH_LLM_CLASSIFY=1).'
|
|
881
|
+
: ' Graph edges: untyped (relates_to). To enable: supabase secrets set GRAPH_LLM_CLASSIFY=1';
|
|
882
|
+
|
|
611
883
|
process.stdout.write(`
|
|
612
884
|
Rumen is deployed.
|
|
613
885
|
|
|
@@ -618,13 +890,12 @@ Edge Functions:
|
|
|
618
890
|
${graphInferenceUrl}
|
|
619
891
|
|
|
620
892
|
Next steps:
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
5. TermDeck's Flashback will surface cross-project patterns automatically.
|
|
893
|
+
${vaultLine}
|
|
894
|
+
${llmLine}
|
|
895
|
+
Monitor rumen jobs: psql "$DATABASE_URL" -c "SELECT * FROM rumen_jobs ORDER BY started_at DESC LIMIT 5"
|
|
896
|
+
Rumen insights flow back into Mnestra's memory_items via rumen_insights.
|
|
897
|
+
graph-inference fills memory_relationships edges nightly (cosine similarity ≥ 0.85).
|
|
898
|
+
TermDeck's Flashback will surface cross-project patterns automatically.
|
|
628
899
|
`);
|
|
629
900
|
}
|
|
630
901
|
|
|
@@ -658,6 +929,22 @@ async function main(argv) {
|
|
|
658
929
|
}
|
|
659
930
|
}
|
|
660
931
|
|
|
932
|
+
// Sprint 51.5 T3: ensure both Vault secrets are present BEFORE the
|
|
933
|
+
// precondition audit (which checks vault.decrypted_secrets for
|
|
934
|
+
// rumen_service_role_key). Auto-applies via pgRunner; on permission
|
|
935
|
+
// failure prints SQL-Editor deeplinks and lets the audit's own hint
|
|
936
|
+
// catch the still-missing secret. The Vault dashboard panel was quietly
|
|
937
|
+
// removed in current Supabase UIs (Brad 2026-05-03 takeaway #2;
|
|
938
|
+
// INSTALLER-PITFALLS.md Class B), which is why we no longer instruct
|
|
939
|
+
// users to "click Vault."
|
|
940
|
+
let vaultResult = { ok: true, created: [], deeplinks: [] };
|
|
941
|
+
if (!flags.dryRun) {
|
|
942
|
+
vaultResult = await ensureVaultSecrets({ projectRef, secrets, dryRun: false });
|
|
943
|
+
// Continue regardless — preconditions audit will catch a still-missing
|
|
944
|
+
// secret with its own hint, and the user already has the deeplinks if
|
|
945
|
+
// any auto-apply failed.
|
|
946
|
+
}
|
|
947
|
+
|
|
661
948
|
// v0.6.9: front-loaded precondition audit. Runs BEFORE link so we don't
|
|
662
949
|
// create state (function deploy, function secrets, schedule SQL) that the
|
|
663
950
|
// user would have to manually clean up if a precondition is missing. Every
|
|
@@ -691,6 +978,14 @@ async function main(argv) {
|
|
|
691
978
|
// already-set) are silent — they're all expected paths.
|
|
692
979
|
}
|
|
693
980
|
|
|
981
|
+
// Sprint 51.5 T1 — audit-upgrade BEFORE the rest of the flow. Surfaces
|
|
982
|
+
// and applies any drift between the bundled artifact set and what's
|
|
983
|
+
// actually live on the user's project. Non-blocking on failure (probe
|
|
984
|
+
// errors get logged inline; main flow continues).
|
|
985
|
+
if (!flags.dryRun) {
|
|
986
|
+
await runRumenAudit(projectRef, secrets, flags.dryRun);
|
|
987
|
+
}
|
|
988
|
+
|
|
694
989
|
if (!(await applyRumenTables(secrets, flags.dryRun))) return 5;
|
|
695
990
|
|
|
696
991
|
step('Resolving @jhizzard/rumen version from npm registry...');
|
|
@@ -703,6 +998,12 @@ async function main(argv) {
|
|
|
703
998
|
}
|
|
704
999
|
|
|
705
1000
|
if (!deployFunctions(resolved.version, flags.dryRun)) return 6;
|
|
1001
|
+
|
|
1002
|
+
// Sprint 51.5 T3: install-time prompt for AI edge classification. Sets
|
|
1003
|
+
// secrets.GRAPH_LLM_CLASSIFY in-memory; the per-secret loop below picks
|
|
1004
|
+
// it up. On --yes / --dry-run defaults to enabled (Y).
|
|
1005
|
+
const llmResult = await promptGraphLlmClassify({ secrets, flags });
|
|
1006
|
+
|
|
706
1007
|
if (!setFunctionSecrets(secrets, flags.dryRun)) return 7;
|
|
707
1008
|
if (!(await testFunction(projectRef, secrets, flags.dryRun))) return 8;
|
|
708
1009
|
if (!flags.skipSchedule) {
|
|
@@ -720,7 +1021,7 @@ async function main(argv) {
|
|
|
720
1021
|
process.stdout.write('→ Skipping pg_cron schedule (per --skip-schedule) ✓\n');
|
|
721
1022
|
}
|
|
722
1023
|
|
|
723
|
-
printNextSteps(projectRef);
|
|
1024
|
+
printNextSteps(projectRef, vaultResult, llmResult);
|
|
724
1025
|
return 0;
|
|
725
1026
|
}
|
|
726
1027
|
|
|
@@ -741,3 +1042,10 @@ module.exports._wireAccessTokenInMcpJson = wireAccessTokenInMcpJson;
|
|
|
741
1042
|
// Sprint 43 T3: stage helper exposed so init-rumen-deploy.test.js can pin
|
|
742
1043
|
// the multi-function staging contract without shelling out to `supabase`.
|
|
743
1044
|
module.exports._stageRumenFunctions = stageRumenFunctions;
|
|
1045
|
+
// Sprint 51.5 T3: per-secret CLI loop, Vault SQL-Editor URL builder, and
|
|
1046
|
+
// Vault-secret ensure helper exposed for tests/init-rumen-secrets-per-call,
|
|
1047
|
+
// init-rumen-graph-llm, and init-rumen-vault-deeplinks.
|
|
1048
|
+
module.exports._setFunctionSecrets = setFunctionSecrets;
|
|
1049
|
+
module.exports._vaultSqlEditorUrl = vaultSqlEditorUrl;
|
|
1050
|
+
module.exports._ensureVaultSecrets = ensureVaultSecrets;
|
|
1051
|
+
module.exports._promptGraphLlmClassify = promptGraphLlmClassify;
|