total-recall-brain 3.23.3 → 3.24.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.
Files changed (43) hide show
  1. package/bin/total-recall.mjs +1 -0
  2. package/frontend/dist/assets/{index-c6GHGS_j.js → index-CtCDaZYZ.js} +1 -1
  3. package/frontend/dist/index.html +1 -1
  4. package/frontend/src/pages/SecretsPage.tsx +111 -52
  5. package/package.json +3 -2
  6. package/scaffold/.agent/skills/total-recall/SKILL.md +199 -7
  7. package/scaffold/.agent/skills/total-recall/memory-vault/concepts/cli-help-reference.md +13 -4
  8. package/scaffold/.agent/skills/total-recall/memory-vault/invariants/operating-instructions.md +2 -0
  9. package/scaffold/.agent/skills/total-recall/references/api-reference.md +1 -1
  10. package/scaffold/.agent/skills/total-recall/references/cli-reference.md +1 -2
  11. package/scaffold/.agent/skills/total-recall/research-queue.jsonl +7 -0
  12. package/scaffold/.agent/skills/total-recall/skills-registry/index.yaml +2461 -6
  13. package/src/cli/backfill.mjs +176 -0
  14. package/src/cli/init.mjs +1 -1
  15. package/src/cli/proposals.mjs +3 -3
  16. package/src/cli/secret.mjs +84 -0
  17. package/src/core/clarity-rewriter.mjs +25 -39
  18. package/src/core/conclusion-writer.mjs +20 -10
  19. package/src/core/crypto.mjs +86 -16
  20. package/src/core/daemon-loop.mjs +2 -2
  21. package/src/core/dream.mjs +49 -58
  22. package/src/core/env-import.mjs +18 -0
  23. package/src/core/inference-engine.mjs +15 -17
  24. package/src/core/migrate.mjs +5 -1
  25. package/src/core/okf-adapter.mjs +2 -0
  26. package/src/core/post-mortem.mjs +30 -26
  27. package/src/core/proposal-applier.mjs +26 -10
  28. package/src/core/repo-sync.mjs +51 -39
  29. package/src/core/scheduler.mjs +1 -0
  30. package/src/core/schema.mjs +24 -1
  31. package/src/core/search.mjs +1 -1
  32. package/src/core/secrets-env-diff.mjs +132 -0
  33. package/src/core/secrets-rekey.mjs +0 -0
  34. package/src/core/snapshot.mjs +32 -9
  35. package/src/core/ssss-kernel-bridge.mjs +46 -6
  36. package/src/core/task-envelope.mjs +1 -0
  37. package/src/core/validated-write.mjs +68 -5
  38. package/src/core/vault-backfill.mjs +259 -0
  39. package/src/core/vault.mjs +22 -4
  40. package/src/server/auth.mjs +42 -8
  41. package/src/server/routes/brains.mjs +1 -1
  42. package/src/server/routes/proposals.mjs +4 -4
  43. package/src/server/routes/secrets.mjs +1 -1
@@ -17,7 +17,7 @@
17
17
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
18
18
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
19
19
  <title>Total Recall — Portable Memory</title>
20
- <script type="module" crossorigin src="/assets/index-c6GHGS_j.js"></script>
20
+ <script type="module" crossorigin src="/assets/index-CtCDaZYZ.js"></script>
21
21
  <link rel="stylesheet" crossorigin href="/assets/index-C7_Xpk27.css">
22
22
  </head>
23
23
  <body>
@@ -1,4 +1,4 @@
1
- import { useState, useEffect, useCallback, useMemo, useRef, type CSSProperties, type ReactNode } from 'react'
1
+ import { useState, useEffect, useCallback, useMemo, useRef, memo, type CSSProperties, type ReactNode } from 'react'
2
2
  import {
3
3
  listApiKeys,
4
4
  issueApiKey,
@@ -294,36 +294,36 @@ export default function SecretsPage() {
294
294
  }
295
295
  }
296
296
 
297
+ // The loaders below set state synchronously before their first await, which
298
+ // react-hooks/set-state-in-effect rightly rejects inside an effect body. The
299
+ // original code dodged it with setTimeout(..., 0); yielding a microtask does
300
+ // the same job a macrotask sooner, and unlike the timeout it can be cancelled
301
+ // — so a fast unmount no longer sets state on a dead component.
297
302
  useEffect(() => {
298
- if (tab === 'sync') {
299
- setTimeout(() => void loadSyncData(), 0)
303
+ if (tab !== 'sync') return
304
+ let cancelled = false
305
+ void Promise.resolve().then(() => {
306
+ if (!cancelled) void loadSyncData()
307
+ })
308
+ return () => {
309
+ cancelled = true
300
310
  }
301
311
  }, [tab, loadSyncData])
302
312
 
313
+ /**
314
+ * Loads the secrets catalog — and ONLY the catalog.
315
+ *
316
+ * This used to await, in series: config, then the Gemini / Claude / OpenAI /
317
+ * OpenRouter model lists, and finally the catalog. The page's actual content
318
+ * therefore sat behind four external vendor round-trips, and the spinner ran
319
+ * until the slowest one answered. The model lists belong to the config tab
320
+ * and are fetched separately (and lazily) below.
321
+ */
303
322
  const loadCatalog = useCallback(async () => {
304
323
  setLoading(true)
305
324
  setError(null)
306
325
  try {
307
-
308
- try {
309
- const config = await fetchConfigJson()
310
- if (!config.secrets) config.secrets = {}
311
- if (!config.brain) config.brain = {}
312
- setConfigData(config)
313
-
314
- const gem = await fetchGeminiModels().catch(() => [])
315
- setGeminiModels(gem)
316
- const cla = await fetchClaudeModels().catch(() => [])
317
- setClaudeModels(cla)
318
- const open = await fetchOpenaiModels().catch(() => [])
319
- setOpenaiModels(open)
320
- const or = await fetchOpenRouterModels().catch(() => [])
321
- setOrModels(or)
322
- } catch {
323
- // ignore fetching models error
324
- }
325
-
326
- const data = await fetchSecretsCatalog()
326
+ const data = await fetchSecretsCatalog()
327
327
  setCatalog(data)
328
328
  const skey = selectedKeyRef.current
329
329
  if (skey) {
@@ -342,6 +342,35 @@ export default function SecretsPage() {
342
342
  }
343
343
  }, [])
344
344
 
345
+ const loadConfig = useCallback(async () => {
346
+ try {
347
+ const config = await fetchConfigJson()
348
+ if (!config.secrets) config.secrets = {}
349
+ if (!config.brain) config.brain = {}
350
+ setConfigData(config)
351
+ } catch {
352
+ // config is only needed by the config tab; its absence must not blank the page
353
+ }
354
+ }, [])
355
+
356
+ /**
357
+ * Vendor model lists, concurrently rather than in series. Each already
358
+ * degrades to [] on its own, so one slow or unauthorized vendor cannot hold
359
+ * up the other three.
360
+ */
361
+ const loadModels = useCallback(async () => {
362
+ const [gem, cla, open, or] = await Promise.all([
363
+ fetchGeminiModels().catch(() => []),
364
+ fetchClaudeModels().catch(() => []),
365
+ fetchOpenaiModels().catch(() => []),
366
+ fetchOpenRouterModels().catch(() => []),
367
+ ])
368
+ setGeminiModels(gem)
369
+ setClaudeModels(cla)
370
+ setOpenaiModels(open)
371
+ setOrModels(or)
372
+ }, [])
373
+
345
374
  const loadPats = useCallback(async () => {
346
375
  try {
347
376
  const r = await listApiKeys()
@@ -360,12 +389,27 @@ export default function SecretsPage() {
360
389
  }, [])
361
390
 
362
391
  useEffect(() => {
363
- setTimeout(() => {
392
+ let cancelled = false
393
+ void Promise.resolve().then(() => {
394
+ if (cancelled) return
364
395
  void loadCatalog()
365
396
  void loadPats()
366
397
  void loadWebAuthn()
367
- }, 0)
368
- }, [loadCatalog, loadPats, loadWebAuthn])
398
+ void loadConfig()
399
+ })
400
+ return () => {
401
+ cancelled = true
402
+ }
403
+ }, [loadCatalog, loadPats, loadWebAuthn, loadConfig])
404
+
405
+ // Vendor model lists are only rendered by the 'cloud' tab — don't pay for
406
+ // them until it is opened, and only pay once.
407
+ const modelsLoadedRef = useRef(false)
408
+ useEffect(() => {
409
+ if (tab !== 'cloud' || modelsLoadedRef.current) return
410
+ modelsLoadedRef.current = true
411
+ void loadModels()
412
+ }, [tab, loadModels])
369
413
 
370
414
 
371
415
  const updateSecretsProp = (prop: string, value: string) => {
@@ -402,7 +446,11 @@ export default function SecretsPage() {
402
446
 
403
447
  // metaForm hoisted
404
448
 
405
- function openKey(k: SecretCatalogKey, sectionId?: string) {
449
+ // useCallback with an empty dep list: every setter below is a stable React
450
+ // setter and the ref is stable, so this identity never needs to change. That
451
+ // is what lets the memoized SecretKeyRow skip re-rendering — a fresh arrow
452
+ // function per row per render would defeat memo() entirely.
453
+ const openKey = useCallback((k: SecretCatalogKey, sectionId?: string) => {
406
454
  selectedKeyRef.current = k.key
407
455
  setSelected(k)
408
456
  setEdit(metaForm(k))
@@ -418,7 +466,7 @@ export default function SecretsPage() {
418
466
  return next
419
467
  })
420
468
  }
421
- }
469
+ }, [])
422
470
 
423
471
  async function registerPasskey() {
424
472
  if (!browserSupportsWebAuthn()) {
@@ -623,30 +671,33 @@ export default function SecretsPage() {
623
671
  }
624
672
  }
625
673
 
626
- const keys = (catalog?.keys || []).filter((k) => {
627
- if (!filter.trim()) return true
628
- const q = filter.toLowerCase()
629
- return (
630
- k.key.toLowerCase().includes(q) ||
631
- (k.provider || '').toLowerCase().includes(q) ||
632
- (k.repos || []).some((r) => r.toLowerCase().includes(q)) ||
633
- (k.label || '').toLowerCase().includes(q)
674
+ /**
675
+ * Filtered key list.
676
+ *
677
+ * There were two copies of this predicate — this one, recomputed on EVERY
678
+ * render, and a second inside the repoSections memo. With ~35 pieces of state
679
+ * on this component, "every render" means every keystroke anywhere, every
680
+ * save, every reveal. One memoized predicate now feeds both consumers, so
681
+ * they can also never disagree about what "matching" means.
682
+ */
683
+ const keys = useMemo(() => {
684
+ const all = catalog?.keys || []
685
+ const q = filter.trim().toLowerCase()
686
+ if (!q) return all
687
+ return all.filter(
688
+ (k) =>
689
+ k.key.toLowerCase().includes(q) ||
690
+ (k.provider || '').toLowerCase().includes(q) ||
691
+ (k.repos || []).some((r) => r.toLowerCase().includes(q)) ||
692
+ (k.label || '').toLowerCase().includes(q)
634
693
  )
635
- })
694
+ }, [catalog, filter])
636
695
 
637
696
  /** Group filtered keys into repo sections (one key → one repo; multi = error; none = developer). */
638
697
  const repoSections = useMemo<{ id: string; label: string; kind: 'error' | 'developer' | 'repo' | 'provider'; keys: SecretCatalogKey[] }[]>(() => {
639
698
  if (!catalog?.keys) return []
640
- const f = filter.toLowerCase()
641
- const filtered = catalog.keys.filter(
642
- (k) =>
643
- k.key.toLowerCase().includes(f) ||
644
- (k.provider || '').toLowerCase().includes(f) ||
645
- (k.repos || []).some((r) => r.toLowerCase().includes(f)) ||
646
- (k.label || '').toLowerCase().includes(f)
647
- )
648
- return groupBy === 'api' ? groupKeysByProvider(filtered) : groupKeysByRepo(filtered)
649
- }, [catalog, filter, groupBy])
699
+ return groupBy === 'api' ? groupKeysByProvider(keys) : groupKeysByRepo(keys)
700
+ }, [catalog, keys, groupBy])
650
701
 
651
702
  // Default accordion: open errors + first product/developer section only
652
703
  useEffect(() => {
@@ -1324,8 +1375,9 @@ export default function SecretsPage() {
1324
1375
  <SecretKeyRow
1325
1376
  key={`${sec.id}::${k.key}`}
1326
1377
  k={k}
1378
+ sectionId={sec.id}
1327
1379
  selected={selected?.key === k.key}
1328
- onOpen={() => openKey(k, sec.id)}
1380
+ onOpen={openKey}
1329
1381
  groupBy={groupBy}
1330
1382
  />
1331
1383
  ))}
@@ -2565,21 +2617,28 @@ function groupKeysByRepo(keys: SecretCatalogKey[]): {
2565
2617
  }
2566
2618
 
2567
2619
  /** Compact single-line-ish row for long catalogs */
2568
- function SecretKeyRow({
2620
+ /**
2621
+ * memo()'d because the catalog renders ~100 of these and the list re-renders
2622
+ * on every keystroke in the filter box. Paired with the stable `onOpen`
2623
+ * above, an unrelated state change now re-renders zero rows.
2624
+ */
2625
+ const SecretKeyRow = memo(function SecretKeyRow({
2569
2626
  k,
2570
2627
  selected,
2628
+ sectionId,
2571
2629
  onOpen,
2572
2630
  groupBy,
2573
2631
  }: {
2574
2632
  k: SecretCatalogKey
2575
2633
  selected: boolean
2576
- onOpen: () => void
2634
+ sectionId?: string
2635
+ onOpen: (k: SecretCatalogKey, sectionId?: string) => void
2577
2636
  groupBy?: 'repo' | 'api'
2578
2637
  }) {
2579
2638
  return (
2580
2639
  <button
2581
2640
  type="button"
2582
- onClick={onOpen}
2641
+ onClick={() => onOpen(k, sectionId)}
2583
2642
  style={{
2584
2643
  width: '100%',
2585
2644
  display: 'grid',
@@ -2668,7 +2727,7 @@ function SecretKeyRow({
2668
2727
  <span style={{ fontSize: 11, color: 'var(--text-tertiary)', flexShrink: 0 }}>›</span>
2669
2728
  </button>
2670
2729
  )
2671
- }
2730
+ })
2672
2731
 
2673
2732
  function SummaryCard({ label, value, accent }: { label: string; value: string; accent: string }) {
2674
2733
  return (
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "total-recall-brain",
3
- "version": "3.23.3",
3
+ "version": "3.24.0",
4
4
  "description": "Personal AI system — database-free, SSSS-powered intelligence engine deployable to any POSIX host.",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -42,7 +42,8 @@
42
42
  "routes:manifest": "node scripts/generate-route-manifest.mjs",
43
43
  "clean": "rm -rf scratch/* .remember/* *.log",
44
44
  "check:dist": "node scripts/check-dist-freshness.mjs",
45
- "prepublishOnly": "npm run check:dist"
45
+ "prepublishOnly": "npm run check:dist",
46
+ "check:ssss-registry": "node scripts/check-ssss-registry.mjs"
46
47
  },
47
48
  "repository": {
48
49
  "type": "git",
@@ -3,15 +3,16 @@ name: total-recall
3
3
  provenance: total-recall
4
4
  description: >-
5
5
  Use this skill to operate Total Recall — portable memory, instructions,
6
- openwiki, skill deploy, and secrets. MANDATORY: Read this file before changing
7
- TR setup. Nested packages under modules/ are NOT agent skills.
8
- version: 3.14.1
6
+ openwiki, skills management, secrets store, mesh/headscale control server, and
7
+ the test suite. MANDATORY: Read this file before changing TR setup. Nested
8
+ packages under modules/ are NOT agent skills.
9
+ version: 3.23.3
9
10
  repo_scoped: true
10
11
  ---
11
12
 
12
13
  # Total Recall — Master Agent Skill
13
14
 
14
- > **Product focus:** portable personal memory + IDE instructions + openwiki + skill deploy + secrets.
15
+ > **Product focus:** portable personal memory + IDE instructions + openwiki + skills management + secrets + mesh.
15
16
  > **Only agent skill in this package.** Implementation helpers live in `modules/` (not skills).
16
17
  > Openwiki ships with the brain at `openwiki/`.
17
18
 
@@ -60,6 +61,18 @@ You MUST actively reference and apply this skill under the following specific ru
60
61
  * **Trigger**: Immediately after performing any memory node writes.
61
62
  * **Action**: Rebuild the instruction shims. If using CLI commands (`npx total-recall remember` / `forget`), recompilation runs automatically in the background (asynchronously via detached subprocesses) to minimize latency. If editing vault files directly, trigger manual compilation by sending a POST request to `/api/vault/compile` or running `npx total-recall compile`.
62
63
 
64
+ ### 5b. When handling ANY credential, API key, or password
65
+ Never invent a new place to keep it. It goes in the encrypted secrets store —
66
+ see **SECRETS STORE** below for `--stdin`, the one-repo binding rule, the
67
+ `.env` migration path, and the health gates.
68
+
69
+ ### 5c. When reaching another machine, or editing device/node details
70
+ Use the **MESH / CONTROL SERVER** section. Node details are entity fields on
71
+ SSSS `mesh_node` documents — never hardcoded hostnames.
72
+
73
+ ### 5d. When running tests, typechecks, or builds
74
+ See **TESTS**. Heavy runs go to the Mac Mini or the droplet, never the laptop.
75
+
63
76
  ### 6. When troubleshooting connections, port blocks, or sync errors
64
77
  * **Trigger**: If the REST server is unreachable, ports are blocked, or the upstream sync tool encounters errors.
65
78
  * **Action**: Refer to the active diagnostics manual within this skill to heal the local runtime.
@@ -106,6 +119,9 @@ node .agent/skills/total-recall/scripts/sync-repo.mjs
106
119
  ```
107
120
  This utility fetches standard skill and invariant definitions from the upstream repository, merges them non-destructively preserving custom user nodes, and auto-recompiles the workspace.
108
121
 
122
+ Everything below is the CLI equivalent — see **SKILLS MANAGEMENT** for the full
123
+ registry/multi-repo reference.
124
+
109
125
  ### 5. Premium Browser Setup Wizard Onboarding
110
126
  For first-time system installations, migrations, or editor reconnections, launch the visual onboarding setup wizard:
111
127
  ```bash
@@ -315,6 +331,45 @@ curl -H "Authorization: Bearer <YOUR_PAT_TOKEN>" \
315
331
 
316
332
  ---
317
333
 
334
+ ## 🧩 SKILLS MANAGEMENT (`skill` subcommand)
335
+
336
+ Two distinct layers. Confusing them is the usual cause of "my edit disappeared".
337
+
338
+ | Layer | What it is | Commands |
339
+ |---|---|---|
340
+ | **Local brain** | Skills installed in one brain's `skills/` dir | `find` `install` `create` `edit` `scan` `list` `remove` `publish` |
341
+ | **Registry + install map** | The catalog and every repo copy deployed from it | `register` `registry` `deploy` `status` `track` `discover` `sync` `push` `pull` |
342
+
343
+ ```bash
344
+ npx total-recall skill list # what this brain has
345
+ npx total-recall skill registry # catalog entries (skills-registry/index.yaml)
346
+ npx total-recall skill status # registry + install map + DRIFT summary
347
+ npx total-recall skill status <id> # drift for one skill
348
+
349
+ npx total-recall skill track /path/to/repo # start managing a repo's .agent/skills
350
+ npx total-recall skill discover # scan tracked repos for skills
351
+ npx total-recall skill sync # two-way reconcile across repos
352
+ npx total-recall skill push # one-way: catalog/source → all installs
353
+ npx total-recall skill pull # one-way: installs → source → fan-out
354
+ npx total-recall skill deploy <id> --repo <path> # copy into one repo
355
+ ```
356
+
357
+ **Which direction to use.** `sync` picks a winner per skill (`--prefer
358
+ newest|registry|install`, default `newest`). When you have just edited the
359
+ canonical copy and want every repo to match it, use `push` — `sync` can pick a
360
+ repo copy instead if its mtime is newer, which silently reverts your edit.
361
+ `--dry-run` first, always.
362
+
363
+ **No hardcoded repo paths.** Repos enter the set only via `skill track`, the
364
+ project registry, `--repo`, `TR_SYNC_REPOS`, or cwd detection. Core must never
365
+ name a product repo (this is an open-source invariant, not a preference).
366
+
367
+ **`--include-core`** is off by default: the `total-recall` skill itself is not
368
+ fanned out unless you ask, so a repo-local edit cannot clobber the master copy
369
+ by accident.
370
+
371
+ ---
372
+
318
373
  ## 🔐 SECRETS STORE (separate from the memory vault — `secret` subcommand)
319
374
 
320
375
  API keys and credentials are **not** memory nodes. They live encrypted at
@@ -324,13 +379,88 @@ INSTRUCTIONS.md/skill surface (`secret check-surfaces` fails the build if one
324
379
  ever leaks in). Full reference: `npx total-recall secret --help`.
325
380
 
326
381
  ```bash
327
- npx total-recall secret set <key> <value> # store (0600, optional AES)
328
- npx total-recall secret rotate <key> <new-value> # replace + mark rotated
382
+ npx total-recall secret set <key> --stdin # store (0600, AES); --stdin keeps it out of argv/history
383
+ npx total-recall secret rotate <key> --stdin # replace + mark rotated
329
384
  npx total-recall secret get <key> # print value (audited)
330
385
  npx total-recall secret list # metadata only, no values
331
- npx total-recall secret export-env --path <repo> # SSOT → local .env.local
386
+ npx total-recall secret catalog # keys + providers + usage + rotation + cost
387
+ npx total-recall secret export-env --path <repo> # SSOT → local .env
388
+ ```
389
+
390
+ **Always prefer `--stdin`** (or `--from-clipboard` for `rotate`). A value passed
391
+ as an argv string lands in shell history, in `ps` output, and in this
392
+ transcript. `set`, `rotate` and `rekey` all accept it.
393
+
394
+ ### Master password and `rekey`
395
+
396
+ The store is AES-256-GCM; the key is scrypt-derived (N=2^16, ~64 MB) from
397
+ `TR_SECRETS_PASSWORD`. On macOS the password belongs in the **Keychain**
398
+ (`security add-generic-password -s total-recall-secrets`), not in a dotfile —
399
+ `rekey` picks the Keychain entry up automatically and rotates every carrier
400
+ (LaunchAgent plist, env file, Keychain) in one transaction, verifying the
401
+ retired password no longer decrypts before it commits.
402
+
403
+ > **Processes hold the old password until restarted.** After a rekey, anything
404
+ > still running with the retired value fails with `Failed to decrypt secrets
405
+ > store: Unsupported state or unable to authenticate data`. Restart the daemon
406
+ > and any launchd jobs.
407
+
408
+ The derived key is cached in-process (bounded, keyed on salt **and** password
409
+ digest, so neither a re-encrypt nor a rotation can be served a stale key). This
410
+ matters because the KDF costs ~200 ms and a single dashboard load reads the
411
+ store several times. `TR_SECRETS_KEY_CACHE=0` disables it.
412
+
413
+ ### Getting secrets OUT of `.env` files and INTO the store
414
+
415
+ `import-env` is the **one-time migration**, not the steady state:
416
+
417
+ ```bash
418
+ npx total-recall secret import-env --dry-run --all # scan every known repo, change nothing
419
+ npx total-recall secret import-env --all # import everything new
420
+ npx total-recall secret import-env KEY1 KEY2 # or just these
332
421
  ```
333
422
 
423
+ > **One credential → exactly ONE repo.** The store rejects a multi-repo binding,
424
+ > and `import-env` derives the binding from *where it found the value* — so a
425
+ > key sitting in six repos' `.env` files fails to import and `--repo` will not
426
+ > override it. That is the rule working: one value in six repos means a leak of
427
+ > any one repo burns all six. Either issue a distinct key per app, or bind it to
428
+ > its owning repo explicitly:
429
+ >
430
+ > ```bash
431
+ > printf '%s' "$VALUE" | npx total-recall secret set <key> --stdin --repos <owning-repo>
432
+ > ```
433
+
434
+ Once stored, the flow reverses — `.env` becomes a **projection of the store**:
435
+
436
+ ```bash
437
+ npx total-recall secret export-env --path <repo> --dry-run
438
+ npx total-recall secret export-env --all-projects # every registered project
439
+ npx total-recall secret export-env --no-global # only keys bound to this repo
440
+ ```
441
+
442
+ By default `export-env` merges only the TR-managed block, leaving hand-written
443
+ lines alone; `--replace-all` overwrites the whole file.
444
+
445
+ ### Health gates (all exit non-zero on a finding)
446
+
447
+ ```bash
448
+ npx total-recall secret check-surfaces # a secret VALUE leaked into a compiled surface/openwiki
449
+ npx total-recall secret shared # same value reused under several key names / apps
450
+ npx total-recall secret duplicates # duplicate entries
451
+ npx total-recall secret tracking-health # a set secret with no live account/usage binding
452
+ npx total-recall secret rotation-due # overdue keys (--enqueue to create daemon tasks)
453
+ ```
454
+
455
+ `shared` is the one that catches real risk: it reports when one credential backs
456
+ multiple keys or apps, which defeats per-app revocation. `secret meta <key>
457
+ --shared-ok` waives it deliberately.
458
+
459
+ **Never** put a secret value in vault markdown, openwiki, a compiled
460
+ INSTRUCTIONS.md, a skill surface, or a `.env.bak-*` file. Backups of `.env` are
461
+ a common accident — they are usually **not** gitignored, so a `git add -A`
462
+ commits live keys.
463
+
334
464
  ### Executed rotation — `rotate-auto` / `rotate-browser` (added 3.22.0)
335
465
 
336
466
  Rotation used to only ever **print instructions**. It now executes. Every key
@@ -413,6 +543,68 @@ that dead-ends back at a manual dashboard step.
413
543
  <!-- END INJECTED MEMORY -->
414
544
 
415
545
 
546
+ ---
547
+
548
+ ## 🕸️ MESH / CONTROL SERVER (`mesh` subcommand)
549
+
550
+ Headscale administration — node inventory, ACL policy, and Tailscale SSH. This
551
+ is how one machine reaches another (laptop → Mac Mini → droplet) without
552
+ hardcoding hostnames anywhere.
553
+
554
+ ```bash
555
+ npx total-recall mesh status # control-server reachability + credential state
556
+ npx total-recall mesh nodes # every node and how to reach it
557
+ npx total-recall mesh access <node> # resolved access for one node
558
+ npx total-recall mesh access import # propose access from ~/.ssh/config (--apply to save)
559
+ npx total-recall mesh ssh <node> 'uptime' # run a command on a node
560
+ npx total-recall mesh preauthkey # enrollment key for `tailscale up --authkey`
561
+ npx total-recall mesh policy get
562
+ npx total-recall mesh policy init-ssh --dry-run
563
+ ```
564
+
565
+ **Device details are entity fields, never literals.** Hostname, mesh IP, role,
566
+ OS, location, capabilities and latency history live on the node's SSSS
567
+ `mesh_node` document plus live discovery. Product code and UI bind to those
568
+ fields — never to a personal machine name.
569
+
570
+ **Mesh membership is the trust boundary.** Every node admitted to the tailnet
571
+ inherits whatever the policy grants; audit `mesh nodes` before widening it.
572
+ `policy` commands require the control server running with `policy.mode:
573
+ database`.
574
+
575
+ **WAN timeouts.** Laptop↔cloud probes need **≥10 s**. Shorter timeouts (1.5 s/3 s)
576
+ falsely report a reachable cloud node as down while raw `curl /health` succeeds.
577
+
578
+ ---
579
+
580
+ ## 🧪 TESTS
581
+
582
+ ```bash
583
+ npm test # vitest run — whole suite
584
+ npx vitest run <path> # one spec file
585
+ ```
586
+
587
+ Current baseline: **288 spec files / 1493 tests, ~160 s** on the Mac Mini.
588
+
589
+ > **Never run the full suite, a full typecheck, or a production build on the
590
+ > laptop.** These go to the Mac Mini or the droplet — local runs cause slowdowns
591
+ > and OOM kills. Sync and run remotely:
592
+ >
593
+ > ```bash
594
+ > rsync -a --delete --exclude=node_modules/ --exclude=.git/ ./ mac-mini:~/github/total-recall/
595
+ > ssh mac-mini 'export PATH=/opt/homebrew/bin:$PATH; cd ~/github/total-recall && npx vitest run'
596
+ > ```
597
+ >
598
+ > A non-login `ssh` shell has no Homebrew on PATH — export it or use `bash -lc`,
599
+ > otherwise the run dies with `command not found: npx` **and still exits 0**.
600
+
601
+ **Never skip tests** to make a release look clean, and never use `--force` on a
602
+ checker. Audit and clean up any rows, files, or mock entries a suite leaves
603
+ behind in a live database.
604
+
605
+ `npx` itself adds ~2 s of resolution overhead per invocation. For tight loops
606
+ call the local binary directly: `node bin/total-recall.mjs <cmd>`.
607
+
416
608
  ---
417
609
 
418
610
  ## Operational modules only (not skills)
@@ -19,10 +19,10 @@ tags:
19
19
  - budget
20
20
  - sandbox
21
21
  - watchdog
22
- created: 2026-05-25T00:51:00.000Z
23
- updated: 2026-05-25T00:56:00.000Z
22
+ created: '2026-05-25T00:51:00.000Z'
23
+ updated: '2026-01-04T02:42:09.572Z'
24
24
  sentiment_polarity: descriptive
25
- last_accessed: 2026-05-26T23:03:03.507Z
25
+ last_accessed: '2026-01-04T02:42:09.572Z'
26
26
  source:
27
27
  type: manual
28
28
  session_id: migration-repair
@@ -39,7 +39,16 @@ object: Total Recall Command & Architecture Help Reference
39
39
  decay:
40
40
  half_life_days: 180
41
41
  access_count: 1
42
- x_temporal_context: 2026-05-26T23:05:06.474Z
42
+ x_temporal_context: '2026-05-26T23:05:06.474Z'
43
+ description: Total Recall Command & Architecture Help Reference
44
+ timestamp: '2026-05-25T00:56:00.000Z'
45
+ x_citations:
46
+ - source: manual
47
+ title: Total Recall Command & Architecture Help Reference
48
+ url: 'session://migration-repair'
49
+ published: '2026-05-26T23:05:06.474Z'
50
+ relevance: 1
51
+ accessed: '2026-08-18T23:41:45.533Z'
43
52
  ---
44
53
  # Total Recall Command & Architecture Help Reference
45
54
 
@@ -3,6 +3,8 @@ type: memory
3
3
  slug: operating-instructions
4
4
  category: invariants
5
5
  title: Total Recall Core Operating Protocol
6
+ description: "The absolute operating protocol for agents using Total Recall: SSSS-governed memory, CLI-first mutations, no external database."
7
+ timestamp: 2026-05-15T00:00:00.000Z
6
8
  schema_version: 2
7
9
  status: active
8
10
  confidence: 1
@@ -43,7 +43,7 @@ Returns the status of the vault, embeddings index, and LLM reachability.
43
43
  "vfs": {
44
44
  "exists": true,
45
45
  "skill_exists": true,
46
- "path": "~/.agent"
46
+ "path": "/Users/greg/.agent"
47
47
  }
48
48
  }
49
49
  ```
@@ -23,8 +23,7 @@ Rebuild the derived indexes and compile memory surfaces.
23
23
  ### 1.3 `connect`
24
24
  Configure an IDE or external client to bind to the Total Recall local server.
25
25
  * **Syntax**: `npx total-recall connect <client> [options]`
26
- * **Supported Clients**: `vscode`, `pi`, `hermes`, `openclaw`, `cursor`, `cline`, `claude-code`, `codex`, `antigravity`, `gemini`, `aider`, `http-api`, `obsidian`, `generic`.
27
- Any host application not listed here connects through the generic `http-api` client.
26
+ * **Supported Clients**: `cursor`, `claude-code`, `antigravity`, `gemini`, `vscode`, `windsurf`, `aider`, `ultrachat`, `obsidian`.
28
27
  * **Options**:
29
28
  * `--brain <url>`: Override default brain server URL.
30
29
  * `--token <PAT>`: Inject bearer PAT credentials for authenticating requests.
@@ -0,0 +1,7 @@
1
+ {"id":"7478a515-ea8e-4360-93b8-a10cf0642d88","topic":"npm Registry API documentation and developer endpoints","status":"pending","priority":"medium","notes":"Consult official npm documentation for registry APIs, authentication, and package metadata without using raw key strings.","node_slug":null,"research_phase":"acquisition","created_at":"2026-08-18T20:58:08.631Z","updated_at":"2026-08-18T20:58:08.631Z","completed_at":null,"summary":{"id":"7478a515-ea8e-4360-93b8-a10cf0642d88","topic":"npm Registry API documentation and developer endpoints","status":"pending","phase":"acquisition","last_updated":"2026-08-18T20:58:08.631Z","conclusions":[],"ongoing_directions":["Research project is enqueued / in progress in phase: acquisition"],"sources_count":0}}
2
+ {"id":"f9757282-dad8-4c3b-b44d-c796555468c5","topic":"Supabase Client, Auth, and REST APIs","status":"pending","priority":"medium","notes":"Use official Supabase documentation to understand authentication, database, and storage API surfaces.","node_slug":null,"research_phase":"acquisition","created_at":"2026-08-18T20:57:04.280Z","updated_at":"2026-08-18T20:57:04.280Z","completed_at":null,"summary":{"id":"f9757282-dad8-4c3b-b44d-c796555468c5","topic":"Supabase Client, Auth, and REST APIs","status":"pending","phase":"acquisition","last_updated":"2026-08-18T20:57:04.280Z","conclusions":[],"ongoing_directions":["Research project is enqueued / in progress in phase: acquisition"],"sources_count":0}}
3
+ {"id":"e196b27d-c799-4e60-88d0-585c479b6053","topic":"Supabase Database, Auth, Storage, and Management APIs","status":"pending","priority":"medium","notes":"Consult official Supabase documentation for REST API, SDK, and platform integration; do not use raw secret key string.","node_slug":null,"research_phase":"acquisition","created_at":"2026-08-18T20:56:34.347Z","updated_at":"2026-08-18T20:56:34.347Z","completed_at":null,"summary":{"id":"e196b27d-c799-4e60-88d0-585c479b6053","topic":"Supabase Database, Auth, Storage, and Management APIs","status":"pending","phase":"acquisition","last_updated":"2026-08-18T20:56:34.347Z","conclusions":[],"ongoing_directions":["Research project is enqueued / in progress in phase: acquisition"],"sources_count":0}}
4
+ {"id":"f6c787c9-212c-4468-a276-5af7d7d78dbc","topic":"Supabase REST API and Client SDKs","status":"pending","priority":"medium","notes":"Use official Supabase documentation for client SDKs, REST API, Database, and Auth endpoints. Do not search using raw key values.","node_slug":null,"research_phase":"acquisition","created_at":"2026-08-18T20:56:21.400Z","updated_at":"2026-08-18T20:56:21.400Z","completed_at":null,"summary":{"id":"f6c787c9-212c-4468-a276-5af7d7d78dbc","topic":"Supabase REST API and Client SDKs","status":"pending","phase":"acquisition","last_updated":"2026-08-18T20:56:21.400Z","conclusions":[],"ongoing_directions":["Research project is enqueued / in progress in phase: acquisition"],"sources_count":0}}
5
+ {"id":"c95b76a3-9bd3-42fd-8c37-0765cabef6a8","topic":"Documenso document signing and template REST API","status":"pending","priority":"medium","notes":"Consult official Documenso documentation for REST API endpoints, document signing workflows, and API key authentication. Do not query using the raw key string.","node_slug":null,"research_phase":"acquisition","created_at":"2026-08-18T20:55:37.372Z","updated_at":"2026-08-18T20:55:37.372Z","completed_at":null,"summary":{"id":"c95b76a3-9bd3-42fd-8c37-0765cabef6a8","topic":"Documenso document signing and template REST API","status":"pending","phase":"acquisition","last_updated":"2026-08-18T20:55:37.372Z","conclusions":[],"ongoing_directions":["Research project is enqueued / in progress in phase: acquisition"],"sources_count":0}}
6
+ {"id":"9b1f9703-76c3-4743-916a-a3340c2b74f4","topic":"Claude Code messaging API and CLI integration","status":"pending","priority":"medium","notes":"Consult official Anthropic Claude Code documentation for API and integration details; do not use the raw key string.","node_slug":null,"research_phase":"acquisition","created_at":"2026-08-18T20:55:25.048Z","updated_at":"2026-08-18T20:55:25.048Z","completed_at":null,"summary":{"id":"9b1f9703-76c3-4743-916a-a3340c2b74f4","topic":"Claude Code messaging API and CLI integration","status":"pending","phase":"acquisition","last_updated":"2026-08-18T20:55:25.048Z","conclusions":[],"ongoing_directions":["Research project is enqueued / in progress in phase: acquisition"],"sources_count":0}}
7
+ {"id":"0efa65c2-0231-4267-a304-e0285e50011e","topic":"Total Recall repository improvement research: SSSS validated write pipeline, filesystem-native memory engines, local-first semantic indexing, Express 5 API hardening, 2026 agent memory architecture patterns","status":"pending","priority":"high","notes":"Queued from repo-wide analysis request on 2026-06-01. Focus on current best practices and meta-patterns for filesystem-native memory OS, validated operation envelopes, append-only logs, cache invalidation, research queue architecture, and secure local-first AI tools.","node_slug":null,"research_phase":"acquisition","created_at":"2026-06-01T06:07:40.102Z","updated_at":"2026-06-01T06:07:40.126Z","completed_at":null,"summary":{"id":"0efa65c2-0231-4267-a304-e0285e50011e","topic":"Total Recall repository improvement research: SSSS validated write pipeline, filesystem-native memory engines, local-first semantic indexing, Express 5 API hardening, 2026 agent memory architecture patterns","status":"pending","phase":"acquisition","last_updated":"2026-06-01T06:07:40.126Z","conclusions":[],"ongoing_directions":["Research project is enqueued / in progress in phase: acquisition"],"sources_count":0}}