total-recall-brain 3.40.0 → 3.40.2

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 (187) hide show
  1. package/README.md +152 -41
  2. package/bin/total-recall.mjs +16 -76
  3. package/docs/reference/cli-reference.md +10 -10
  4. package/extension/background.js +6 -2
  5. package/extension/config.js +8 -0
  6. package/extension/lib/brain-client.js +29 -4
  7. package/extension/options/options.html +1 -0
  8. package/extension/sidepanel/sidepanel.html +1 -0
  9. package/frontend/dist/assets/{index-CvQRdT2j.css → index-AGUes-VG.css} +1 -1
  10. package/frontend/dist/assets/index-Bt5iVPZj.js +5618 -0
  11. package/frontend/dist/index.html +2 -2
  12. package/frontend/src/api/keys.ts +9 -2
  13. package/frontend/src/api/plugins.ts +4 -0
  14. package/frontend/src/components/plugins/card/CardEngine.tsx +2 -1
  15. package/frontend/src/components/plugins/card/card.css +1 -1
  16. package/frontend/src/pages/IntegrationsPage.tsx +315 -8
  17. package/frontend/src/pages/PluginsPage.tsx +532 -87
  18. package/package.json +6 -5
  19. package/scaffold/.agent/skills/meta-harness/references/optimized/original.md +1 -1
  20. package/scaffold/.agent/skills/meta-harness/references/optimized/section-09.md +1 -1
  21. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +10 -10
  22. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -6
  23. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +0 -1
  24. package/scaffold/.agent/skills/total-recall/plugins/auth/src/browser-session.mjs +309 -4
  25. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +44 -34
  26. package/scaffold/.agent/skills/total-recall/plugins/auth/src/google-oauth.mjs +6 -4
  27. package/scaffold/.agent/skills/total-recall/plugins/auth/src/provider-catalog.mjs +1 -1
  28. package/scaffold/.agent/skills/total-recall/plugins/auth/src/provider-rotation-recipes.mjs +118 -4
  29. package/scaffold/.agent/skills/total-recall/plugins/auth/src/routes/auth.mjs +2 -2
  30. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-env-export.mjs +3 -3
  31. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-remote-deploy.mjs +1 -1
  32. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-rotate.mjs +24 -6
  33. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/card.js +164 -123
  34. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/README.md +65 -0
  35. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/cli.mjs +76 -0
  36. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/package.json +19 -0
  37. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/plugin.json +290 -0
  38. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/adapters/dsh.mjs +446 -0
  39. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/documentation.mjs +152 -0
  40. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/executor.mjs +71 -0
  41. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/runtime.mjs +36 -0
  42. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/setup.mjs +35 -0
  43. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/worker.mjs +175 -0
  44. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/card.js +18 -0
  45. package/scaffold/.agent/skills/total-recall/plugins/code-quality/LICENSE +21 -0
  46. package/scaffold/.agent/skills/total-recall/plugins/code-quality/README.md +84 -0
  47. package/scaffold/.agent/skills/total-recall/plugins/code-quality/cli.mjs +79 -0
  48. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/check.mjs +6 -0
  49. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/report.mjs +6 -0
  50. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/run-core.mjs +27 -0
  51. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/snapshot.mjs +163 -0
  52. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +757 -0
  53. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/config.schema.json +86 -0
  54. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/detect.mjs +89 -0
  55. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report-data.mjs +145 -0
  56. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report.mjs +210 -0
  57. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/state.mjs +20 -0
  58. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +2869 -0
  59. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +28 -0
  60. package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +186 -0
  61. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/DESIGN.md +18 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/card.js +272 -0
  63. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/code-quality-report.mjs +129 -0
  64. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/LICENSE +21 -0
  65. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/README.md +73 -0
  66. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/cli.mjs +375 -0
  67. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/examples/commands/echo-tool.mjs +23 -0
  68. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/examples/commands/group.mjs +262 -0
  69. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/examples/commands/project-id.mjs +74 -0
  70. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +2869 -0
  71. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +31 -0
  72. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +221 -0
  73. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/schemas/cli_command.schema.json +107 -0
  74. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/skills/composable-cli/SKILL.md +63 -0
  75. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/command.mjs +313 -0
  76. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/compile.mjs +19 -0
  77. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/compose.mjs +144 -0
  78. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/contract.mjs +110 -0
  79. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/library.mjs +23 -0
  80. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/registry.mjs +192 -0
  81. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/resolve.mjs +221 -0
  82. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/risk.mjs +126 -0
  83. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/ssss-documents.mjs +29 -0
  84. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/DESIGN.md +18 -0
  85. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/card.js +273 -0
  86. package/scaffold/.agent/skills/total-recall/plugins/embeddings/LICENSE +21 -0
  87. package/scaffold/.agent/skills/total-recall/plugins/embeddings/README.md +20 -0
  88. package/scaffold/.agent/skills/total-recall/plugins/embeddings/cli.mjs +310 -0
  89. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +2826 -0
  90. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +11 -0
  91. package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +62 -0
  92. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/card.js +297 -0
  93. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/LICENSE +21 -0
  94. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/README.md +43 -0
  95. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/cli.mjs +258 -0
  96. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +4005 -0
  97. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +20 -0
  98. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/plugin.json +125 -0
  99. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/DESIGN.md +18 -0
  100. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/card.js +278 -0
  101. package/scaffold/.agent/skills/total-recall/plugins/mesh/README.md +9 -0
  102. package/scaffold/.agent/skills/total-recall/plugins/mesh/cli.mjs +6 -0
  103. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +4721 -0
  104. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +3 -21
  105. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +14 -1
  106. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/mesh.mjs +10 -0
  107. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/headscale-client.mjs +5 -1
  108. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/leader-election.mjs +4 -13
  109. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh-access-verify.mjs +22 -0
  110. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh-files.mjs +18 -0
  111. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh.mjs +2 -0
  112. package/scaffold/.agent/skills/total-recall/plugins/mesh/test-support/memory-root.mjs +14 -0
  113. package/scaffold/.agent/skills/total-recall/plugins/openwiki/LICENSE +21 -0
  114. package/scaffold/.agent/skills/total-recall/plugins/openwiki/README.md +35 -0
  115. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +124 -0
  116. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +4097 -0
  117. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +12 -0
  118. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +165 -0
  119. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/ingest.mjs +127 -0
  120. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +18 -0
  121. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +279 -0
  122. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/panel.js +539 -0
  123. package/scaffold/.agent/skills/total-recall/plugins/security/LICENSE +21 -0
  124. package/scaffold/.agent/skills/total-recall/plugins/security/README.md +67 -0
  125. package/scaffold/.agent/skills/total-recall/plugins/security/cli.mjs +932 -0
  126. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +3256 -0
  127. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +25 -0
  128. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +297 -0
  129. package/scaffold/.agent/skills/total-recall/plugins/security/scripts/build-ui.mjs +4 -0
  130. package/scaffold/.agent/skills/total-recall/plugins/security/skills/security/SKILL.md +37 -0
  131. package/scaffold/.agent/skills/total-recall/plugins/security/src/android.mjs +21 -0
  132. package/scaffold/.agent/skills/total-recall/plugins/security/src/devices.mjs +94 -0
  133. package/scaffold/.agent/skills/total-recall/plugins/security/src/machines.mjs +164 -0
  134. package/scaffold/.agent/skills/total-recall/plugins/security/src/scanner.mjs +41 -0
  135. package/scaffold/.agent/skills/total-recall/plugins/security/src/windows.mjs +42 -0
  136. package/scaffold/.agent/skills/total-recall/plugins/security/ui/DESIGN.md +18 -0
  137. package/scaffold/.agent/skills/total-recall/plugins/security/ui/card.js +95 -0
  138. package/scaffold/.agent/skills/total-recall/plugins/security/ui/panel.js +98 -0
  139. package/scaffold/.agent/skills/total-recall/references/auth.md +1 -1
  140. package/src/cli/backup.mjs +71 -3
  141. package/src/cli/brief.mjs +33 -9
  142. package/src/cli/command.mjs +2 -7
  143. package/src/cli/config.mjs +1 -0
  144. package/src/cli/connect.mjs +5 -0
  145. package/src/cli/context.mjs +2 -1
  146. package/src/cli/daemon.mjs +36 -1
  147. package/src/cli/doctor.mjs +6 -0
  148. package/src/cli/harness.mjs +3 -0
  149. package/src/cli/help.mjs +26 -0
  150. package/src/cli/init.mjs +4 -4
  151. package/src/cli/memory-entry.mjs +1 -0
  152. package/src/cli/memory-init.mjs +2 -0
  153. package/src/cli/plugin/index.mjs +11 -0
  154. package/src/cli/plugin/install.mjs +2 -1
  155. package/src/cli/rebuild.mjs +3 -1
  156. package/src/cli/startup.mjs +2 -1
  157. package/src/cli/update.mjs +19 -0
  158. package/src/core/automatic-brain-updates.mjs +67 -0
  159. package/src/core/brain-updates.mjs +21 -2
  160. package/src/core/cli-commands.mjs +71 -0
  161. package/src/core/cli-maintenance.mjs +45 -0
  162. package/src/core/cli-quick-reference.mjs +20 -0
  163. package/src/core/cli-semantic-index.mjs +722 -0
  164. package/src/core/context-compiler.mjs +27 -1
  165. package/src/core/crons.mjs +2 -13
  166. package/src/core/daemon-control.mjs +6 -4
  167. package/src/core/daemon-loop.mjs +12 -8
  168. package/src/core/default-plugins.mjs +5 -0
  169. package/src/core/global-brain-updates.mjs +39 -0
  170. package/src/core/mesh-brain-updates.mjs +30 -0
  171. package/src/core/meta-harness.mjs +108 -0
  172. package/src/core/plugin-dependencies.mjs +2 -1
  173. package/src/core/plugin-loader.mjs +2 -1
  174. package/src/core/plugin-runner.mjs +12 -4
  175. package/src/core/plugin-setup.mjs +20 -0
  176. package/src/core/plugin-store.mjs +22 -4
  177. package/src/core/plugin-tasks.mjs +1 -1
  178. package/src/core/project-brain.mjs +2 -0
  179. package/src/core/project-status.mjs +64 -0
  180. package/src/core/required-plugins.mjs +46 -0
  181. package/src/core/scaffold-plugins.mjs +17 -3
  182. package/src/core/source-brain-updates.mjs +47 -0
  183. package/src/core/startup-health.mjs +5 -3
  184. package/src/core/surface.mjs +44 -10
  185. package/src/server/routes/extension.mjs +53 -8
  186. package/templates/onboarding-interview.md +1 -1
  187. package/frontend/dist/assets/index-SOVUlZqm.js +0 -5596
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "total-recall-brain",
3
- "version": "3.40.0",
4
- "description": "Portable SSSS memory, local retrieval and task instructions with explicit plugin loading.",
3
+ "version": "3.40.2",
4
+ "description": "The Vibe Coding Brain & Autonomous Software Factory: memory, rules, secrets, and 33 visual plugins automatically managed across all your devices.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "total-recall": "bin/total-recall.mjs",
8
+ "vibebrain": "bin/total-recall.mjs",
8
9
  "antigravity": "bin/antigravity.mjs",
9
10
  "total-recall-memory": "bin/total-recall-memory.mjs"
10
11
  },
@@ -99,7 +100,8 @@
99
100
  "@testing-library/react": "^16.3.2",
100
101
  "@testing-library/user-event": "^14.6.1",
101
102
  "@vitejs/plugin-react": "^6.0.3",
102
- "@vitest/coverage-v8": "^4.1.11",
103
+ "@vitest/coverage-v8": "^4.1.12",
104
+ "gray-matter": "^4.0.3",
103
105
  "jsdom": "^29.1.1",
104
106
  "react": "^19.2.7",
105
107
  "react-dom": "^19.2.7",
@@ -107,8 +109,7 @@
107
109
  "recharts": "^3.9.2",
108
110
  "skills": "^1.5.7",
109
111
  "supertest": "^7.2.2",
110
- "vitest": "^4.1.11",
111
- "gray-matter": "^4.0.3"
112
+ "vitest": "^4.1.12"
112
113
  },
113
114
  "workspaces": [
114
115
  "frontend"
@@ -86,7 +86,7 @@ npx total-recall agent spawn claude "Answer from memory only" --no-tools
86
86
  ```
87
87
 
88
88
  Claude Code authenticates headlessly with `CLAUDE_CODE_OAUTH_TOKEN` (create it with
89
- `claude setup-token`, store it with `total-recall secret set CLAUDE_CODE_OAUTH_TOKEN --stdin`).
89
+ `claude setup-token`, store it with `total-recall auth secret set CLAUDE_CODE_OAUTH_TOKEN --stdin`).
90
90
  A spawn reads it from the secret store when the environment lacks it, so SSH and
91
91
  launchd sessions that cannot reach a login keychain still authenticate.
92
92
 
@@ -15,6 +15,6 @@ npx total-recall agent spawn claude "Answer from memory only" --no-tools
15
15
  ```
16
16
 
17
17
  Claude Code authenticates headlessly with `CLAUDE_CODE_OAUTH_TOKEN` (create it with
18
- `claude setup-token`, store it with `total-recall secret set CLAUDE_CODE_OAUTH_TOKEN --stdin`).
18
+ `claude setup-token`, store it with `total-recall auth secret set CLAUDE_CODE_OAUTH_TOKEN --stdin`).
19
19
  A spawn reads it from the secret store when the environment lacks it, so SSH and
20
20
  launchd sessions that cannot reach a login keychain still authenticate.
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "tr-plugin-auth",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "tr-plugin-auth",
9
- "version": "1.0.0",
9
+ "version": "1.0.2",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@simplewebauthn/server": "^13.3.2",
@@ -14,7 +14,7 @@
14
14
  "express": "^5.2.1",
15
15
  "express-rate-limit": "^8.5.1",
16
16
  "jsonwebtoken": "^9.0.3",
17
- "total-recall-brain": "^3.39.1",
17
+ "total-recall-brain": "3.40.1",
18
18
  "yaml": "^2.9.0"
19
19
  },
20
20
  "devDependencies": {
@@ -695,9 +695,9 @@
695
695
  },
696
696
  "node_modules/@ssss/cli": {
697
697
  "name": "@gregiteen/ssss-cli",
698
- "version": "0.10.3",
699
- "resolved": "https://registry.npmjs.org/@gregiteen/ssss-cli/-/ssss-cli-0.10.3.tgz",
700
- "integrity": "sha512-fCRZLrPSTALj/px5fHJNFZGOmC28FHKqEAWybxWHVARfMdo924RbGS70ax84w9QoCQ5NlnTe17cYmS0xXgwrVQ==",
698
+ "version": "0.11.2",
699
+ "resolved": "https://registry.npmjs.org/@gregiteen/ssss-cli/-/ssss-cli-0.11.2.tgz",
700
+ "integrity": "sha512-DTkBPCOv9uN79YoRtsa/GnXPbbeHOU5PQcQ995axHg+SLWdkL6xmrTRs5Xxshcje/Iv6+lKkYOc9UL+Arn9kHg==",
701
701
  "license": "ISC",
702
702
  "bin": {
703
703
  "ssss": "scripts/ssss.mjs"
@@ -3578,16 +3578,16 @@
3578
3578
  }
3579
3579
  },
3580
3580
  "node_modules/total-recall-brain": {
3581
- "version": "3.39.1",
3582
- "resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.39.1.tgz",
3583
- "integrity": "sha512-kZ3tN5vJBUUNUbgcqMOaoFjcsoSeg6KMNrn5LHCNjDm42j6bPtKYQZ8J5qMlX2guLVdKUn7+4Minxdchem1HTQ==",
3581
+ "version": "3.40.1",
3582
+ "resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.40.1.tgz",
3583
+ "integrity": "sha512-tZMnDHXsMT1sa698oF26Np/G4k+Cn4ZXpTdo+BTNOvUMYEhxWR+7Tl+upHLMhcEEPuEM0EiAObL53a9JELzGag==",
3584
3584
  "license": "MIT",
3585
3585
  "workspaces": [
3586
3586
  "frontend"
3587
3587
  ],
3588
3588
  "dependencies": {
3589
3589
  "@simplewebauthn/server": "^13.3.2",
3590
- "@ssss/cli": "npm:@gregiteen/ssss-cli@0.10.3",
3590
+ "@ssss/cli": "npm:@gregiteen/ssss-cli@0.11.2",
3591
3591
  "bcrypt": "^6.0.0",
3592
3592
  "better-sqlite3": "^12.11.1",
3593
3593
  "cookie-parser": "^1.4.7",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tr-plugin-auth",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "White-label Total Recall auth plugin: Passkey biometric FIDO2 and Google OAuth2 authentication.",
5
5
  "type": "module",
6
6
  "main": "cli.mjs",
@@ -15,12 +15,7 @@
15
15
  "google"
16
16
  ],
17
17
  "license": "MIT",
18
- "devDependencies": {
19
- "vitest": "^5.0.3",
20
- "supertest": "^7.2.2"
21
- },
22
18
  "dependencies": {
23
- "total-recall-brain": "^3.39.1",
24
19
  "yaml": "^2.9.0",
25
20
  "bcrypt": "^6.0.0",
26
21
  "jsonwebtoken": "^9.0.3",
@@ -17,7 +17,6 @@
17
17
  "font": "chivo-mono"
18
18
  },
19
19
  "card_face": {
20
- "skin": "winamp-classic",
21
20
  "theme": "vibrant",
22
21
  "headline": "Passkey & Google OAuth2 Authentication Engine",
23
22
  "primary_color": "#6366f1",
@@ -14,20 +14,297 @@
14
14
 
15
15
  import fs from 'node:fs';
16
16
  import path from 'node:path';
17
+ import { execFileSync } from 'node:child_process';
17
18
 
18
19
  const PROFILE_DIRNAME = 'browser-profile';
19
20
 
20
21
  const PROFILE_README = `# TR browser profile — DO NOT SYNC, DO NOT COPY
21
22
 
22
23
  This directory holds live authenticated sessions (Stripe, GitHub, cloud consoles)
23
- used by \`total-recall secret rotate-auto\`.
24
+ used by \`total-recall auth secret rotate-auto\`.
24
25
 
25
26
  Treat it as equivalent to secrets.enc:
26
27
  - never commit it
27
28
  - never include it in backups that leave this machine
28
- - delete it with \`total-recall secret browser-logout\` when no longer needed
29
+ - delete it with \`total-recall auth secret browser-logout\` when no longer needed
29
30
  `;
30
31
 
32
+ /**
33
+ * Check if Google Chrome is currently running on macOS.
34
+ * @returns {boolean}
35
+ */
36
+ export function isMacChromeRunning() {
37
+ if (process.platform !== 'darwin') return false;
38
+ try {
39
+ const res = execFileSync('pgrep', ['-x', 'Google Chrome'], {
40
+ encoding: 'utf8',
41
+ stdio: ['ignore', 'pipe', 'ignore'],
42
+ });
43
+ return !!res.trim();
44
+ } catch {
45
+ return false;
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Native macOS Chrome driver using AppleScript/JXA. Connects to the operator's
51
+ * already-running, authenticated Google Chrome instance so rotations can drive
52
+ * authenticated consoles without needing to re-login.
53
+ */
54
+ export function createMacChromeContext(opts = {}) {
55
+ let activeUrl = '';
56
+
57
+ const runJxa = (js) => {
58
+ return execFileSync('osascript', ['-l', 'JavaScript', '-e', js], {
59
+ encoding: 'utf8',
60
+ stdio: ['ignore', 'pipe', 'ignore'],
61
+ }).trim();
62
+ };
63
+
64
+ const page = {
65
+ async goto(url) {
66
+ activeUrl = url;
67
+ let host = '';
68
+ try {
69
+ host = new URL(url).hostname;
70
+ } catch {}
71
+ const script = `
72
+ const chrome = Application("Google Chrome");
73
+ let target = null;
74
+ for (const w of chrome.windows()) {
75
+ for (const t of w.tabs()) {
76
+ if (t.url().includes("${host}")) {
77
+ target = t;
78
+ w.activeTabIndex = w.tabs.indexOf(t) + 1;
79
+ break;
80
+ }
81
+ }
82
+ if (target) break;
83
+ }
84
+ if (!target) {
85
+ const w = chrome.windows()[0] || chrome.Window().make();
86
+ target = chrome.Tab({ url: "${url}" });
87
+ w.tabs.push(target);
88
+ } else if (target.url() !== "${url}") {
89
+ target.url = "${url}";
90
+ }
91
+ target.url();
92
+ `;
93
+ try {
94
+ runJxa(script);
95
+ } catch {}
96
+ await page.waitForTimeout(1500);
97
+ },
98
+
99
+ async url() {
100
+ let host = '';
101
+ try {
102
+ host = new URL(activeUrl || 'https://portal.telnyx.com').hostname;
103
+ } catch {}
104
+ const script = `
105
+ const chrome = Application("Google Chrome");
106
+ let u = "";
107
+ for (const w of chrome.windows()) {
108
+ for (const t of w.tabs()) {
109
+ if (t.url().includes("${host}")) {
110
+ u = t.url();
111
+ break;
112
+ }
113
+ }
114
+ if (u) break;
115
+ }
116
+ u;
117
+ `;
118
+ try {
119
+ const res = runJxa(script);
120
+ if (res) return res;
121
+ } catch {}
122
+ return activeUrl;
123
+ },
124
+
125
+ async waitForTimeout(ms) {
126
+ return new Promise((r) => setTimeout(r, ms));
127
+ },
128
+
129
+ async trustedKeystroke(text) {
130
+ if (process.platform === 'darwin') {
131
+ const escaped = String(text).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
132
+ const script = `
133
+ tell application "Google Chrome" to activate
134
+ delay 0.1
135
+ tell application "System Events"
136
+ keystroke "${escaped}"
137
+ end tell
138
+ `;
139
+ try {
140
+ execFileSync('osascript', ['-e', script], { stdio: 'ignore' });
141
+ } catch {}
142
+ }
143
+ },
144
+
145
+ async trustedPress(keyName) {
146
+ if (process.platform === 'darwin') {
147
+ let code = 'space';
148
+ if (keyName === 'Enter' || keyName === 'return') code = 'return';
149
+ else if (keyName === 'space' || keyName === 'Space') code = 'space';
150
+ const script = `
151
+ tell application "Google Chrome" to activate
152
+ delay 0.1
153
+ tell application "System Events"
154
+ keystroke ${code}
155
+ end tell
156
+ `;
157
+ try {
158
+ execFileSync('osascript', ['-e', script], { stdio: 'ignore' });
159
+ } catch {}
160
+ }
161
+ },
162
+
163
+ async evaluate(fn, ...args) {
164
+ let host = '';
165
+ try {
166
+ host = new URL(activeUrl || 'https://portal.telnyx.com').hostname;
167
+ } catch {}
168
+
169
+ const evalChrome = (code) => {
170
+ const script = `
171
+ const chrome = Application("Google Chrome");
172
+ let res = null;
173
+ for (const w of chrome.windows()) {
174
+ for (const t of w.tabs()) {
175
+ if (t.url().includes("${host}")) {
176
+ res = t.execute({ javascript: ${JSON.stringify(code)} });
177
+ break;
178
+ }
179
+ }
180
+ if (res !== null) break;
181
+ }
182
+ JSON.stringify(res);
183
+ `;
184
+ try {
185
+ const out = runJxa(script);
186
+ return out ? JSON.parse(out) : null;
187
+ } catch {
188
+ return null;
189
+ }
190
+ };
191
+
192
+ const fnStr = typeof fn === 'function' ? `(${fn.toString()})(${args.map((a) => JSON.stringify(a)).join(',')})` : String(fn);
193
+ const wrapped = `
194
+ (() => {
195
+ window.__tr_pending = true;
196
+ window.__tr_result = null;
197
+ window.__tr_error = null;
198
+ try {
199
+ const out = ${fnStr};
200
+ if (out && typeof out.then === 'function') {
201
+ out.then(
202
+ r => { window.__tr_result = r; window.__tr_pending = false; },
203
+ e => { window.__tr_error = String(e); window.__tr_pending = false; }
204
+ );
205
+ } else {
206
+ window.__tr_result = out;
207
+ window.__tr_pending = false;
208
+ }
209
+ } catch (err) {
210
+ window.__tr_error = String(err);
211
+ window.__tr_pending = false;
212
+ }
213
+ })()
214
+ `;
215
+
216
+ evalChrome(wrapped);
217
+
218
+ const deadline = Date.now() + 35_000;
219
+ while (Date.now() < deadline) {
220
+ const pending = evalChrome('window.__tr_pending');
221
+ if (pending === false) {
222
+ const err = evalChrome('window.__tr_error');
223
+ if (err) throw new Error(err);
224
+ return evalChrome('window.__tr_result');
225
+ }
226
+ await page.waitForTimeout(200);
227
+ }
228
+ return null;
229
+ },
230
+
231
+ locator(selector) {
232
+ return {
233
+ first() {
234
+ return this;
235
+ },
236
+ filter() {
237
+ return this;
238
+ },
239
+ async isVisible() {
240
+ return (
241
+ (await page.evaluate((sel) => {
242
+ const el = document.querySelector(sel);
243
+ return !!(el && (el.offsetWidth || el.offsetHeight || el.getClientRects().length));
244
+ }, selector)) || false
245
+ );
246
+ },
247
+ async waitFor(options = {}) {
248
+ const timeout = options.timeout ?? 10_000;
249
+ const started = Date.now();
250
+ while (Date.now() - started < timeout) {
251
+ const vis = await this.isVisible();
252
+ if (vis) return true;
253
+ await page.waitForTimeout(250);
254
+ }
255
+ return false;
256
+ },
257
+ async count() {
258
+ return (await page.evaluate((sel) => document.querySelectorAll(sel).length, selector)) || 0;
259
+ },
260
+ async click() {
261
+ return await page.evaluate((sel) => {
262
+ const el = document.querySelector(sel);
263
+ if (el) el.click();
264
+ }, selector);
265
+ },
266
+ async fill(text) {
267
+ return await page.evaluate(
268
+ ({ sel, text }) => {
269
+ const el = document.querySelector(sel);
270
+ if (el) {
271
+ el.focus();
272
+ const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')?.set;
273
+ if (setter) setter.call(el, text);
274
+ else el.value = text;
275
+ el.dispatchEvent(new Event('input', { bubbles: true }));
276
+ el.dispatchEvent(new Event('change', { bubbles: true }));
277
+ }
278
+ },
279
+ { sel: selector, text }
280
+ );
281
+ },
282
+ async press(key) {
283
+ return await page.evaluate(
284
+ ({ sel, key }) => {
285
+ const el = document.querySelector(sel);
286
+ if (el) {
287
+ el.dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: true }));
288
+ el.dispatchEvent(new KeyboardEvent('keyup', { key, bubbles: true }));
289
+ }
290
+ },
291
+ { sel: selector, key }
292
+ );
293
+ },
294
+ };
295
+ },
296
+ };
297
+
298
+ return {
299
+ isMacChrome: true,
300
+ pages: () => [page],
301
+ newPage: async () => page,
302
+ grantPermissions: async () => {},
303
+ close: async () => {},
304
+ setDefaultTimeout: () => {},
305
+ };
306
+ }
307
+
31
308
  /**
32
309
  * Load chromium if playwright is installed. Mirrors source-adapters.getPlaywright
33
310
  * so browser support stays an optional dependency.
@@ -76,6 +353,16 @@ export function ensureProfileDir(brainDir) {
76
353
  * @returns {Promise<{ ok: boolean, context?: any, error?: string, profileDir?: string }>}
77
354
  */
78
355
  export async function launchRotationContext(brainDir, opts = {}) {
356
+ // If Chrome is already running on macOS and not opted out, use the operator's active session
357
+ if (opts.useChrome !== false && (opts.chrome || isMacChromeRunning())) {
358
+ return {
359
+ ok: true,
360
+ context: createMacChromeContext(opts),
361
+ profileDir: 'running-google-chrome',
362
+ browser: 'running Google Chrome (macOS active session)',
363
+ };
364
+ }
365
+
79
366
  const chromium = await getChromium();
80
367
  if (!chromium) {
81
368
  return {
@@ -88,12 +375,30 @@ export async function launchRotationContext(brainDir, opts = {}) {
88
375
  const profileDir = ensureProfileDir(brainDir);
89
376
  // Headed by default: provider logins routinely require human 2FA, and a
90
377
  // headless first-run would simply hang on an invisible challenge.
91
- const headless = opts.headless === true;
378
+ //
379
+ // But a headed launch needs a display, and a server usually has none: the window cannot open,
380
+ // Chromium exits at once, and launchPersistentContext reports only "Target page, context or
381
+ // browser has been closed" for every candidate browser. That reads as "no browser installed"
382
+ // and left the operator with a live, un-rotated credential. With no display there is nothing
383
+ // for a human to see anyway, so fall back to headless rather than fail.
384
+ const headless = opts.headless === true || (process.platform === 'linux' && !process.env.DISPLAY);
92
385
 
93
386
  const base = {
94
387
  headless,
95
388
  viewport: { width: 1440, height: 900 },
96
- args: ['--disable-blink-features=AutomationControlled', '--no-first-run'],
389
+ // Chromium refuses to start as root with its sandbox on, and it dies so fast that
390
+ // launchPersistentContext reports only "Target page, context or browser has been closed" —
391
+ // the exact failure every rotation attempt hit on this host, from every candidate browser,
392
+ // which reads as "no browser installed" and is why the key was never rotated. Add the flags
393
+ // root needs, and keep /dev/shm out of it because it is small in a container and stalls the
394
+ // renderer.
395
+ args: [
396
+ '--disable-blink-features=AutomationControlled',
397
+ '--no-first-run',
398
+ ...(typeof process.getuid === 'function' && process.getuid() === 0
399
+ ? ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage']
400
+ : []),
401
+ ],
97
402
  };
98
403
 
99
404
  // Playwright's bundled Chromium is not available on every host — it has