auramaxx 0.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.
Files changed (418) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +77 -0
  3. package/apps/desktop-electron/main.js +428 -0
  4. package/bin/auramaxx.js +1063 -0
  5. package/docs/ADAPTERS.md +466 -0
  6. package/docs/AGENT_SETUP.md +159 -0
  7. package/docs/API.md +127 -0
  8. package/docs/APPS.md +199 -0
  9. package/docs/ARCHITECTURE.md +235 -0
  10. package/docs/AUTH.md +318 -0
  11. package/docs/BEST-PRACTICES.md +82 -0
  12. package/docs/CLI.md +141 -0
  13. package/docs/DESKTOP_ELECTRON.md +26 -0
  14. package/docs/DEVELOPING-APPS.md +453 -0
  15. package/docs/MCP.md +122 -0
  16. package/docs/PACKAGING_POLICY.md +19 -0
  17. package/docs/PERMISSION.md +137 -0
  18. package/docs/PROTOCOL.md +142 -0
  19. package/docs/README.md +50 -0
  20. package/docs/SKILLS.md +132 -0
  21. package/docs/TROUBLESHOOTING.md +376 -0
  22. package/docs/WORKSPACE.md +673 -0
  23. package/docs/agent-auth.md +14 -0
  24. package/docs/api/authentication.md +79 -0
  25. package/docs/api/secrets/api-keys.md +28 -0
  26. package/docs/api/secrets/credentials.md +80 -0
  27. package/docs/api/secrets/sharing.md +48 -0
  28. package/docs/api/system.md +41 -0
  29. package/docs/api/wallets/apps-strategies.md +66 -0
  30. package/docs/api/wallets/core.md +46 -0
  31. package/docs/api/wallets/data-portfolio.md +42 -0
  32. package/docs/aura-file.md +48 -0
  33. package/docs/core-concepts/FEATURES.md +114 -0
  34. package/docs/credentials.md +120 -0
  35. package/docs/external/HOW_TO_AURAMAXX/GETTING_SECRETS.md +33 -0
  36. package/docs/external/HOW_TO_AURAMAXX/README.md +45 -0
  37. package/docs/external/getting-started.md +10 -0
  38. package/docs/external/overview.md +19 -0
  39. package/docs/external/persona-paths.md +7 -0
  40. package/docs/external/share-secret.md +76 -0
  41. package/docs/external/why-aura.md +7 -0
  42. package/docs/security.md +227 -0
  43. package/docs/templates/RELEASE_NOTES_TEMPLATE.md +22 -0
  44. package/docs/wallet/AI.md +508 -0
  45. package/docs/wallet/DEVELOPING-STRATEGIES.md +713 -0
  46. package/docs/wallet/README.md +47 -0
  47. package/docs/wallet/STRATEGY.md +89 -0
  48. package/next.config.ts +28 -0
  49. package/package.json +167 -0
  50. package/postcss.config.mjs +8 -0
  51. package/prisma/migrations/20260214170000_baseline/migration.sql +511 -0
  52. package/prisma/migrations/20260216214537_add_passkey_model/migration.sql +18 -0
  53. package/prisma/migrations/20260217150500_add_credential_access_audit/migration.sql +31 -0
  54. package/prisma/migrations/20260222090000_update_admin_ttl_default/migration.sql +10 -0
  55. package/prisma/migrations/migration_lock.toml +3 -0
  56. package/prisma/schema.prisma +447 -0
  57. package/public/logo.webp +0 -0
  58. package/scripts/add-app.js +245 -0
  59. package/server/abi/SwapHelper.json +438 -0
  60. package/server/cli/approval.ts +447 -0
  61. package/server/cli/commands/actions.ts +474 -0
  62. package/server/cli/commands/api.ts +220 -0
  63. package/server/cli/commands/apikey.ts +277 -0
  64. package/server/cli/commands/app.ts +204 -0
  65. package/server/cli/commands/auth.ts +464 -0
  66. package/server/cli/commands/cron.ts +24 -0
  67. package/server/cli/commands/diary.ts +274 -0
  68. package/server/cli/commands/doctor.ts +1247 -0
  69. package/server/cli/commands/env.ts +476 -0
  70. package/server/cli/commands/experimental.ts +69 -0
  71. package/server/cli/commands/init.ts +798 -0
  72. package/server/cli/commands/lock.ts +157 -0
  73. package/server/cli/commands/mcp.ts +285 -0
  74. package/server/cli/commands/quickhack.ts +86 -0
  75. package/server/cli/commands/release-check.ts +231 -0
  76. package/server/cli/commands/restore.ts +314 -0
  77. package/server/cli/commands/service.ts +320 -0
  78. package/server/cli/commands/shell-hook.ts +512 -0
  79. package/server/cli/commands/skill.ts +216 -0
  80. package/server/cli/commands/start.ts +139 -0
  81. package/server/cli/commands/status.ts +59 -0
  82. package/server/cli/commands/stop.ts +36 -0
  83. package/server/cli/commands/token.ts +180 -0
  84. package/server/cli/commands/unlock.ts +50 -0
  85. package/server/cli/commands/vault.ts +1323 -0
  86. package/server/cli/commands/wallet.ts +209 -0
  87. package/server/cli/index.ts +280 -0
  88. package/server/cli/lib/approval-poll.ts +94 -0
  89. package/server/cli/lib/aura-parser.ts +64 -0
  90. package/server/cli/lib/credential-create.ts +74 -0
  91. package/server/cli/lib/credential-resolve.ts +280 -0
  92. package/server/cli/lib/dotenv-migrate.ts +116 -0
  93. package/server/cli/lib/dotenv-parser.ts +146 -0
  94. package/server/cli/lib/escalation.ts +57 -0
  95. package/server/cli/lib/http.ts +91 -0
  96. package/server/cli/lib/init-steps.ts +76 -0
  97. package/server/cli/lib/local-agent-trust.ts +45 -0
  98. package/server/cli/lib/lock-unlock-helper.ts +71 -0
  99. package/server/cli/lib/process.ts +162 -0
  100. package/server/cli/lib/prompt.ts +294 -0
  101. package/server/cli/lib/theme.ts +240 -0
  102. package/server/cli/socket.ts +579 -0
  103. package/server/cli/transport-client.ts +50 -0
  104. package/server/cron/index.ts +137 -0
  105. package/server/cron/job.ts +31 -0
  106. package/server/cron/jobs/balance-sync.ts +436 -0
  107. package/server/cron/jobs/incoming-scan.ts +506 -0
  108. package/server/cron/jobs/native-price.ts +70 -0
  109. package/server/cron/jobs/orphan-cleanup.ts +40 -0
  110. package/server/cron/jobs/strategy-runner.ts +175 -0
  111. package/server/cron/scheduler.ts +125 -0
  112. package/server/index.ts +420 -0
  113. package/server/lib/adapters/factory.ts +119 -0
  114. package/server/lib/adapters/index.ts +19 -0
  115. package/server/lib/adapters/router.ts +297 -0
  116. package/server/lib/adapters/telegram.ts +645 -0
  117. package/server/lib/adapters/types.ts +89 -0
  118. package/server/lib/adapters/webhook.ts +95 -0
  119. package/server/lib/address.ts +49 -0
  120. package/server/lib/agent-auth/contracts.ts +1194 -0
  121. package/server/lib/agent-profiles.ts +419 -0
  122. package/server/lib/ai.ts +285 -0
  123. package/server/lib/api-registry/contracts.ts +86 -0
  124. package/server/lib/api-registry/validation.ts +172 -0
  125. package/server/lib/apikey-migration.ts +258 -0
  126. package/server/lib/app-installer.ts +505 -0
  127. package/server/lib/app-tokens.ts +247 -0
  128. package/server/lib/approval-link.ts +27 -0
  129. package/server/lib/auth.ts +314 -0
  130. package/server/lib/auto-execute.ts +160 -0
  131. package/server/lib/batch.ts +242 -0
  132. package/server/lib/cold.ts +1048 -0
  133. package/server/lib/config.ts +408 -0
  134. package/server/lib/credential-access-audit.ts +85 -0
  135. package/server/lib/credential-access-policy.ts +111 -0
  136. package/server/lib/credential-health.ts +343 -0
  137. package/server/lib/credential-import.ts +608 -0
  138. package/server/lib/credential-scope.ts +102 -0
  139. package/server/lib/credential-shares.ts +190 -0
  140. package/server/lib/credential-transport.ts +533 -0
  141. package/server/lib/credential-vault.ts +77 -0
  142. package/server/lib/credentials.ts +422 -0
  143. package/server/lib/crypto.ts +8 -0
  144. package/server/lib/db.ts +58 -0
  145. package/server/lib/defaults.ts +386 -0
  146. package/server/lib/dex/index.ts +80 -0
  147. package/server/lib/dex/relay.ts +235 -0
  148. package/server/lib/dex/types.ts +59 -0
  149. package/server/lib/dex/uniswap.ts +370 -0
  150. package/server/lib/diary.ts +34 -0
  151. package/server/lib/dont-ask-again-policy.ts +41 -0
  152. package/server/lib/e2e-agent/artifacts.ts +36 -0
  153. package/server/lib/e2e-agent/contracts.ts +112 -0
  154. package/server/lib/e2e-agent/validation.ts +135 -0
  155. package/server/lib/encrypt.ts +114 -0
  156. package/server/lib/error.ts +20 -0
  157. package/server/lib/events.ts +217 -0
  158. package/server/lib/feature-flags.ts +93 -0
  159. package/server/lib/hot.ts +357 -0
  160. package/server/lib/human-action-summary.ts +80 -0
  161. package/server/lib/key-fingerprint.ts +28 -0
  162. package/server/lib/logger.ts +340 -0
  163. package/server/lib/network.ts +137 -0
  164. package/server/lib/notifications.ts +230 -0
  165. package/server/lib/oauth2-refresh.ts +241 -0
  166. package/server/lib/oursecret.ts +71 -0
  167. package/server/lib/passkey-credential.ts +360 -0
  168. package/server/lib/passkey.ts +68 -0
  169. package/server/lib/permissions.ts +299 -0
  170. package/server/lib/pino.ts +24 -0
  171. package/server/lib/policy-preview.ts +138 -0
  172. package/server/lib/price.ts +338 -0
  173. package/server/lib/prices.ts +34 -0
  174. package/server/lib/project-scope.ts +297 -0
  175. package/server/lib/resolve-action.ts +328 -0
  176. package/server/lib/resolve.ts +36 -0
  177. package/server/lib/secret-gist-share.ts +296 -0
  178. package/server/lib/sessions.ts +634 -0
  179. package/server/lib/socket-path.ts +56 -0
  180. package/server/lib/solana/connection.ts +26 -0
  181. package/server/lib/solana/jupiter.ts +128 -0
  182. package/server/lib/solana/transfer.ts +108 -0
  183. package/server/lib/solana/wallet.ts +136 -0
  184. package/server/lib/strategy/emits.ts +21 -0
  185. package/server/lib/strategy/engine.ts +1305 -0
  186. package/server/lib/strategy/executor.ts +115 -0
  187. package/server/lib/strategy/hook-context.ts +159 -0
  188. package/server/lib/strategy/hooks.ts +990 -0
  189. package/server/lib/strategy/index.ts +28 -0
  190. package/server/lib/strategy/installer.ts +305 -0
  191. package/server/lib/strategy/loader.ts +256 -0
  192. package/server/lib/strategy/message.ts +237 -0
  193. package/server/lib/strategy/repository.ts +218 -0
  194. package/server/lib/strategy/session-logger.ts +693 -0
  195. package/server/lib/strategy/sources.ts +288 -0
  196. package/server/lib/strategy/state.ts +189 -0
  197. package/server/lib/strategy/templates.ts +403 -0
  198. package/server/lib/strategy/tick.ts +404 -0
  199. package/server/lib/strategy/types.ts +230 -0
  200. package/server/lib/swap.ts +3 -0
  201. package/server/lib/temp.ts +86 -0
  202. package/server/lib/token-metadata.ts +86 -0
  203. package/server/lib/token-safety.ts +200 -0
  204. package/server/lib/token-search.ts +444 -0
  205. package/server/lib/totp.ts +194 -0
  206. package/server/lib/transactions.ts +123 -0
  207. package/server/lib/transport.ts +84 -0
  208. package/server/lib/txhistory/decoder.ts +262 -0
  209. package/server/lib/txhistory/enricher.ts +652 -0
  210. package/server/lib/txhistory/index.ts +391 -0
  211. package/server/lib/txhistory/signatures.ts +59 -0
  212. package/server/lib/update-check.ts +35 -0
  213. package/server/lib/verified-summary.ts +414 -0
  214. package/server/lib/view-registry.ts +80 -0
  215. package/server/mcp/profile-policy.ts +30 -0
  216. package/server/mcp/server.ts +1589 -0
  217. package/server/mcp/tools.ts +276 -0
  218. package/server/middleware/auth.ts +119 -0
  219. package/server/middleware/requestLogger.ts +84 -0
  220. package/server/routes/actions.ts +539 -0
  221. package/server/routes/adapters.ts +711 -0
  222. package/server/routes/addressbook.ts +113 -0
  223. package/server/routes/ai.ts +34 -0
  224. package/server/routes/apikeys.ts +343 -0
  225. package/server/routes/apps.ts +601 -0
  226. package/server/routes/auth.ts +406 -0
  227. package/server/routes/backup.ts +404 -0
  228. package/server/routes/batch.ts +270 -0
  229. package/server/routes/bookmarks.ts +162 -0
  230. package/server/routes/credential-shares.ts +380 -0
  231. package/server/routes/credential-vaults.ts +159 -0
  232. package/server/routes/credentials.ts +1782 -0
  233. package/server/routes/dashboard.ts +97 -0
  234. package/server/routes/defaults.ts +124 -0
  235. package/server/routes/flags.ts +11 -0
  236. package/server/routes/fund.ts +225 -0
  237. package/server/routes/heartbeat.ts +375 -0
  238. package/server/routes/import.ts +364 -0
  239. package/server/routes/launch.ts +665 -0
  240. package/server/routes/lock.ts +54 -0
  241. package/server/routes/logs.ts +68 -0
  242. package/server/routes/nuke.ts +111 -0
  243. package/server/routes/passkey-credentials.ts +99 -0
  244. package/server/routes/passkey.ts +366 -0
  245. package/server/routes/portfolio.ts +217 -0
  246. package/server/routes/price.ts +63 -0
  247. package/server/routes/resolve.ts +31 -0
  248. package/server/routes/security.ts +45 -0
  249. package/server/routes/send-evm.ts +241 -0
  250. package/server/routes/send-solana.ts +281 -0
  251. package/server/routes/send.ts +178 -0
  252. package/server/routes/setup.ts +210 -0
  253. package/server/routes/strategy.ts +894 -0
  254. package/server/routes/swap-evm.ts +352 -0
  255. package/server/routes/swap-solana.ts +176 -0
  256. package/server/routes/swap.ts +356 -0
  257. package/server/routes/token.ts +247 -0
  258. package/server/routes/unlock.ts +467 -0
  259. package/server/routes/views.ts +41 -0
  260. package/server/routes/wallet-assets.ts +361 -0
  261. package/server/routes/wallet-transactions.ts +515 -0
  262. package/server/routes/wallet.ts +709 -0
  263. package/server/types.ts +146 -0
  264. package/shared/credential-field-schema.ts +248 -0
  265. package/skills/auramaxx/HEARTBEAT.md +78 -0
  266. package/skills/auramaxx/SKILL.md +745 -0
  267. package/skills/auramaxx/docs/AGENT_SETUP.md +155 -0
  268. package/skills/auramaxx/docs/API.md +127 -0
  269. package/skills/auramaxx/docs/AUTH.md +318 -0
  270. package/skills/auramaxx/docs/CLI.md +130 -0
  271. package/skills/auramaxx/docs/MCP.md +122 -0
  272. package/skills/auramaxx/docs/TROUBLESHOOTING.md +357 -0
  273. package/skills/auramaxx/docs/WORKSPACE.md +673 -0
  274. package/skills/auramaxx/docs/security.md +227 -0
  275. package/skills/task-lifecycle/SKILL.md +378 -0
  276. package/src/app/api/[...doc]/page.tsx +36 -0
  277. package/src/app/api/agent-requests/route.ts +30 -0
  278. package/src/app/api/apps/install/route.ts +132 -0
  279. package/src/app/api/apps/manifests/route.ts +16 -0
  280. package/src/app/api/apps/static/[...path]/route.ts +57 -0
  281. package/src/app/api/docs/plain/route.ts +74 -0
  282. package/src/app/api/events/route.ts +92 -0
  283. package/src/app/api/page.tsx +290 -0
  284. package/src/app/api/workspace/[id]/apps/[wid]/route.ts +119 -0
  285. package/src/app/api/workspace/[id]/apps/route.ts +81 -0
  286. package/src/app/api/workspace/[id]/export/route.ts +67 -0
  287. package/src/app/api/workspace/[id]/route.ts +168 -0
  288. package/src/app/api/workspace/auth.ts +40 -0
  289. package/src/app/api/workspace/config/route.ts +121 -0
  290. package/src/app/api/workspace/import/route.ts +127 -0
  291. package/src/app/api/workspace/route.ts +116 -0
  292. package/src/app/app-legacy-do-not-use/page.tsx +2245 -0
  293. package/src/app/apple-icon.png +0 -0
  294. package/src/app/approve/[actionId]/page.tsx +409 -0
  295. package/src/app/docs/DocsPageContent.tsx +269 -0
  296. package/src/app/docs/[...doc]/page.tsx +41 -0
  297. package/src/app/docs/page.tsx +38 -0
  298. package/src/app/favicon.ico +0 -0
  299. package/src/app/globals.css +819 -0
  300. package/src/app/health/page.tsx +5 -0
  301. package/src/app/hello/page.tsx +102 -0
  302. package/src/app/icon.png +0 -0
  303. package/src/app/layout.tsx +39 -0
  304. package/src/app/page.tsx +1964 -0
  305. package/src/app/privacy/page.tsx +63 -0
  306. package/src/app/providers.tsx +87 -0
  307. package/src/app/share/[token]/page.tsx +295 -0
  308. package/src/app/terms/page.tsx +80 -0
  309. package/src/components/ChainSelector.tsx +44 -0
  310. package/src/components/HumanActionBar.tsx +697 -0
  311. package/src/components/NotificationDrawer.tsx +387 -0
  312. package/src/components/PasskeyEnrollmentPrompt.tsx +235 -0
  313. package/src/components/apps/AgentKeysApp.tsx +490 -0
  314. package/src/components/apps/App.tsx +153 -0
  315. package/src/components/apps/AppGrid.tsx +15 -0
  316. package/src/components/apps/DetailedAddressDrawer.tsx +325 -0
  317. package/src/components/apps/DraggableApp.tsx +562 -0
  318. package/src/components/apps/IFrameApp.tsx +73 -0
  319. package/src/components/apps/LogsApp.tsx +360 -0
  320. package/src/components/apps/SendApp.tsx +394 -0
  321. package/src/components/apps/SetupWizardApp.tsx +1004 -0
  322. package/src/components/apps/SystemDefaultsApp.tsx +845 -0
  323. package/src/components/apps/ThirdPartyApp.tsx +428 -0
  324. package/src/components/apps/TokenApp.tsx +319 -0
  325. package/src/components/apps/TransactionsApp.tsx +438 -0
  326. package/src/components/apps/WalletDetailApp.tsx +1505 -0
  327. package/src/components/apps/index.ts +13 -0
  328. package/src/components/design-system/Button.tsx +88 -0
  329. package/src/components/design-system/ChainIndicator.tsx +65 -0
  330. package/src/components/design-system/ChainSelector.tsx +147 -0
  331. package/src/components/design-system/ConfirmationModal.tsx +107 -0
  332. package/src/components/design-system/ConfirmationPopover.tsx +81 -0
  333. package/src/components/design-system/DownloadButton.tsx +149 -0
  334. package/src/components/design-system/Drawer.tsx +133 -0
  335. package/src/components/design-system/FilterDropdown.tsx +183 -0
  336. package/src/components/design-system/ItemPicker.tsx +157 -0
  337. package/src/components/design-system/Modal.tsx +296 -0
  338. package/src/components/design-system/Popover.tsx +142 -0
  339. package/src/components/design-system/TextInput.tsx +85 -0
  340. package/src/components/design-system/Toggle.tsx +65 -0
  341. package/src/components/design-system/TyvekCollapsibleSection.tsx +55 -0
  342. package/src/components/design-system/index.ts +14 -0
  343. package/src/components/docs/ClientSideMarkdown.tsx +51 -0
  344. package/src/components/docs/DocsSearchBar.tsx +118 -0
  345. package/src/components/docs/DocsThemeToggle.tsx +38 -0
  346. package/src/components/docs/PersistentDocGroup.tsx +91 -0
  347. package/src/components/docs/ShareUrlButton.tsx +33 -0
  348. package/src/components/docs/SidebarScrollMemory.tsx +56 -0
  349. package/src/components/health/CredentialHealthDashboard.tsx +214 -0
  350. package/src/components/icons/ChainIcons.tsx +72 -0
  351. package/src/components/layout/AppStoreDrawer.tsx +369 -0
  352. package/src/components/layout/ContentArea.tsx +21 -0
  353. package/src/components/layout/CreateViewModal.tsx +88 -0
  354. package/src/components/layout/LeftRail.tsx +114 -0
  355. package/src/components/layout/TabBar.tsx +284 -0
  356. package/src/components/layout/WalletSidebar.tsx +1030 -0
  357. package/src/components/layout/index.ts +6 -0
  358. package/src/components/marketing/AuraMaxxSpecOverlay.tsx +653 -0
  359. package/src/components/marketing/DeviceMorphExperience.tsx +216 -0
  360. package/src/components/vault/ApiKeysConsole.tsx +1272 -0
  361. package/src/components/vault/AuditConsole.tsx +600 -0
  362. package/src/components/vault/CredentialDetail.tsx +625 -0
  363. package/src/components/vault/CredentialEmpty.tsx +55 -0
  364. package/src/components/vault/CredentialField.tsx +583 -0
  365. package/src/components/vault/CredentialForm.tsx +1484 -0
  366. package/src/components/vault/CredentialList.tsx +265 -0
  367. package/src/components/vault/CredentialRow.tsx +130 -0
  368. package/src/components/vault/CredentialShareModal.tsx +273 -0
  369. package/src/components/vault/CredentialVault.tsx +1662 -0
  370. package/src/components/vault/CredentialWalletWidget.tsx +103 -0
  371. package/src/components/vault/DocsConsole.tsx +113 -0
  372. package/src/components/vault/ImportCredentialsModal.tsx +578 -0
  373. package/src/components/vault/LargeTypeModal.tsx +88 -0
  374. package/src/components/vault/PasswordGenerator.tsx +232 -0
  375. package/src/components/vault/TOTPDisplay.tsx +108 -0
  376. package/src/components/vault/TotpSetupPanel.tsx +198 -0
  377. package/src/components/vault/VaultSidebar.tsx +881 -0
  378. package/src/components/vault/credentialFormName.ts +91 -0
  379. package/src/components/vault/hooks/useVaultKeyboardShortcuts.ts +69 -0
  380. package/src/components/vault/types.ts +56 -0
  381. package/src/context/AuthContext.tsx +365 -0
  382. package/src/context/PriceContext.tsx +113 -0
  383. package/src/context/ThemeContext.tsx +164 -0
  384. package/src/context/WebSocketContext.tsx +269 -0
  385. package/src/context/WorkspaceContext.tsx +668 -0
  386. package/src/hooks/index.ts +4 -0
  387. package/src/hooks/useAgentActions.ts +552 -0
  388. package/src/hooks/useBalance.ts +103 -0
  389. package/src/hooks/useBalances.ts +129 -0
  390. package/src/hooks/useTheme.ts +156 -0
  391. package/src/instrumentation.ts +12 -0
  392. package/src/lib/api-docs.ts +154 -0
  393. package/src/lib/api.ts +474 -0
  394. package/src/lib/app-loader.ts +148 -0
  395. package/src/lib/app-registry.ts +178 -0
  396. package/src/lib/app-sdk.ts +157 -0
  397. package/src/lib/audit-console-adapter.ts +151 -0
  398. package/src/lib/auth-client.ts +75 -0
  399. package/src/lib/config.ts +74 -0
  400. package/src/lib/credential-field-schema.ts +11 -0
  401. package/src/lib/crypto.ts +112 -0
  402. package/src/lib/db.ts +21 -0
  403. package/src/lib/docs.ts +544 -0
  404. package/src/lib/events.ts +363 -0
  405. package/src/lib/pino.ts +24 -0
  406. package/src/lib/theme-handlers.ts +168 -0
  407. package/src/lib/theme.ts +351 -0
  408. package/src/lib/tokenData.ts +378 -0
  409. package/src/lib/totp-import.ts +57 -0
  410. package/src/lib/vault-crypto.ts +129 -0
  411. package/src/lib/view-registry.ts +57 -0
  412. package/src/lib/websocket-server.ts +302 -0
  413. package/src/lib/websocket-setup.ts +79 -0
  414. package/src/lib/wordlist.ts +2050 -0
  415. package/src/lib/workspace-handlers.ts +285 -0
  416. package/start.sh +170 -0
  417. package/tailwind.config.ts +99 -0
  418. package/tsconfig.json +42 -0
@@ -0,0 +1,1247 @@
1
+ /**
2
+ * auramaxx doctor — deterministic onboarding/runtime diagnostics
3
+ */
4
+
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+ import net from 'net';
8
+ import os from 'os';
9
+ import { spawnSync } from 'child_process';
10
+ import { fetchJson, serverUrl, type SetupStatus } from '../lib/http';
11
+ import { parseAuraFile, type AuraMapping } from '../lib/aura-parser';
12
+ import { getErrorMessage } from '../../lib/error';
13
+ import { printBanner, checkBadge, printSection } from '../lib/theme';
14
+ import { resolveAuraSocketCandidates } from '../../lib/socket-path';
15
+
16
+ export type CheckStatus = 'pass' | 'warn' | 'fail';
17
+ export type CheckSeverity = 'info' | 'low' | 'medium' | 'high' | 'critical';
18
+
19
+ export interface DoctorCheck {
20
+ id: string;
21
+ code: string;
22
+ severity: CheckSeverity;
23
+ status: CheckStatus;
24
+ finding: string;
25
+ evidence: string;
26
+ remediation: string;
27
+ }
28
+
29
+ interface DoctorResult {
30
+ ok: boolean;
31
+ mode: 'default' | 'strict';
32
+ summary: { pass: number; warn: number; fail: number };
33
+ checks: DoctorCheck[];
34
+ fixes: string[];
35
+ }
36
+
37
+ interface DoctorOptions {
38
+ json: boolean;
39
+ strict: boolean;
40
+ fix: boolean;
41
+ }
42
+
43
+ interface CredentialHealthSummary {
44
+ totalAnalyzed: number;
45
+ safe: number;
46
+ weak: number;
47
+ reused: number;
48
+ breached: number;
49
+ unknown: number;
50
+ lastScanAt: string | null;
51
+ }
52
+
53
+ interface TokenValidateResponse {
54
+ valid: boolean;
55
+ error?: string;
56
+ payload?: {
57
+ permissions?: string[];
58
+ };
59
+ }
60
+
61
+ interface AuthProbeState {
62
+ socketViable: boolean;
63
+ tokenPresent: boolean;
64
+ tokenShapeValid: boolean;
65
+ tokenMask: string;
66
+ tokenValidate?: TokenValidateResponse;
67
+ }
68
+
69
+ const EXIT = {
70
+ OK: 0,
71
+ FAIL: 1,
72
+ INTERNAL: 2,
73
+ ARGS: 3,
74
+ } as const;
75
+
76
+ const MAX_SECURITY_ENTRIES = 200;
77
+ const SECURITY_TIME_BUDGET_MS = 2000;
78
+ const MAX_AURA_MAPPINGS = 100;
79
+ const MAX_UNIQUE_CREDENTIAL_PROBES = 25;
80
+ const HTTP_TIMEOUT_MS = 1500;
81
+ const AURA_RC_BLOCK_START = '# >>> Aura CLI managed fallback >>>';
82
+ const AURA_RC_BLOCK_END = '# <<< Aura CLI managed fallback <<<';
83
+
84
+ function withTimeout<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T> {
85
+ return new Promise<T>((resolve, reject) => {
86
+ const timer = setTimeout(() => reject(new Error(`${label}-timeout`)), timeoutMs);
87
+ promise
88
+ .then((value) => {
89
+ clearTimeout(timer);
90
+ resolve(value);
91
+ })
92
+ .catch((err) => {
93
+ clearTimeout(timer);
94
+ reject(err);
95
+ });
96
+ });
97
+ }
98
+
99
+ function parseArgs(argv: string[]): DoctorOptions {
100
+ const flags = new Set(argv);
101
+ for (const flag of flags) {
102
+ if (!['--json', '--strict', '--fix', '--help', '-h'].includes(flag)) {
103
+ throw new Error(`Unknown flag: ${flag}`);
104
+ }
105
+ }
106
+
107
+ if (flags.has('--help') || flags.has('-h')) {
108
+ console.log('Usage: npx auramaxx doctor [--json] [--strict] [--fix]');
109
+ process.exit(EXIT.OK);
110
+ }
111
+
112
+ return {
113
+ json: flags.has('--json'),
114
+ strict: flags.has('--strict'),
115
+ fix: flags.has('--fix'),
116
+ };
117
+ }
118
+
119
+ function findCommandInPath(commandName: string): string | null {
120
+ const pathEntries = (process.env.PATH || '')
121
+ .split(path.delimiter)
122
+ .map((entry) => entry.trim())
123
+ .filter(Boolean);
124
+
125
+ const extCandidates = process.platform === 'win32'
126
+ ? (process.env.PATHEXT || '.COM;.EXE;.BAT;.CMD')
127
+ .split(';')
128
+ .map((ext) => ext.trim())
129
+ .filter(Boolean)
130
+ : [''];
131
+
132
+ for (const dir of pathEntries) {
133
+ for (const ext of extCandidates) {
134
+ const candidate = process.platform === 'win32'
135
+ ? path.join(dir, `${commandName}${ext}`)
136
+ : path.join(dir, commandName);
137
+ try {
138
+ fs.accessSync(candidate, fs.constants.X_OK);
139
+ return candidate;
140
+ } catch {
141
+ // continue searching
142
+ }
143
+ }
144
+ }
145
+
146
+ return null;
147
+ }
148
+
149
+ function resolveShellRcTarget(): string | null {
150
+ const shell = path.basename(process.env.SHELL || '');
151
+ switch (shell) {
152
+ case 'zsh':
153
+ return path.join(os.homedir(), '.zshrc');
154
+ case 'bash':
155
+ return path.join(os.homedir(), '.bashrc');
156
+ default:
157
+ return null;
158
+ }
159
+ }
160
+
161
+ function renderManagedFallbackBlock(): string {
162
+ return [
163
+ AURA_RC_BLOCK_START,
164
+ '# Ensures aura/auramaxx work across restarts without global install.',
165
+ 'aura() { npx auramaxx "$@"; }',
166
+ 'auramaxx() { npx auramaxx "$@"; }',
167
+ AURA_RC_BLOCK_END,
168
+ ].join('\n');
169
+ }
170
+
171
+ function stripLegacyAuraAliasLines(content: string): { content: string; removed: boolean } {
172
+ const lines = content.split(/\r?\n/);
173
+ const kept: string[] = [];
174
+ let removed = false;
175
+
176
+ for (const line of lines) {
177
+ const isAuraNpxAlias = /^\s*alias\s+aura=.+\bnpx\s+auramaxx\b.*$/.test(line);
178
+ const isAuramaxxNpxAlias = /^\s*alias\s+auramaxx=.+\bnpx\s+auramaxx\b.*$/.test(line);
179
+ const isAuraAliasComment = /^\s*#\s*Aura CLI shorthand\s*$/.test(line);
180
+ if (isAuraNpxAlias || isAuramaxxNpxAlias || isAuraAliasComment) {
181
+ removed = true;
182
+ continue;
183
+ }
184
+ kept.push(line);
185
+ }
186
+
187
+ return {
188
+ content: kept.join('\n').replace(/\n+$/, ''),
189
+ removed,
190
+ };
191
+ }
192
+
193
+ function upsertManagedFallbackBlock(content: string): string {
194
+ const block = renderManagedFallbackBlock();
195
+ const start = content.indexOf(AURA_RC_BLOCK_START);
196
+ const end = content.indexOf(AURA_RC_BLOCK_END);
197
+
198
+ if (start !== -1 && end !== -1 && end > start) {
199
+ const endWithMarker = end + AURA_RC_BLOCK_END.length;
200
+ const nextChar = content[endWithMarker] === '\n' ? endWithMarker + 1 : endWithMarker;
201
+ return `${content.slice(0, start)}${block}${content.slice(nextChar)}`;
202
+ }
203
+
204
+ return `${content}${content.trim() ? '\n\n' : ''}${block}`;
205
+ }
206
+
207
+ function fixShellFallback(): {
208
+ ok: boolean;
209
+ rcFile?: string;
210
+ message: string;
211
+ } {
212
+ const rcFile = resolveShellRcTarget();
213
+ if (!rcFile) {
214
+ return {
215
+ ok: false,
216
+ message: 'unsupported-shell',
217
+ };
218
+ }
219
+
220
+ let original = '';
221
+ try {
222
+ if (fs.existsSync(rcFile)) {
223
+ original = fs.readFileSync(rcFile, 'utf-8');
224
+ }
225
+ } catch {
226
+ return {
227
+ ok: false,
228
+ rcFile,
229
+ message: 'read-failed',
230
+ };
231
+ }
232
+
233
+ const stripped = stripLegacyAuraAliasLines(original);
234
+ const updated = `${upsertManagedFallbackBlock(stripped.content).replace(/\n+$/, '')}\n`;
235
+ const originalNormalized = `${original.replace(/\r\n/g, '\n').replace(/\n+$/, '')}\n`;
236
+ if (updated === originalNormalized) {
237
+ return {
238
+ ok: true,
239
+ rcFile,
240
+ message: 'already-present',
241
+ };
242
+ }
243
+
244
+ try {
245
+ fs.writeFileSync(rcFile, updated);
246
+ return {
247
+ ok: true,
248
+ rcFile,
249
+ message: stripped.removed ? 'installed-and-migrated' : 'installed',
250
+ };
251
+ } catch {
252
+ return {
253
+ ok: false,
254
+ rcFile,
255
+ message: 'write-failed',
256
+ };
257
+ }
258
+ }
259
+
260
+ function maskToken(token?: string): string {
261
+ if (!token) return 'absent';
262
+ const trimmed = token.trim();
263
+ if (!trimmed) return 'present-empty';
264
+ return `tok_****${trimmed.slice(-4)}`;
265
+ }
266
+
267
+ function checkTokenShape(token?: string): boolean {
268
+ if (!token) return false;
269
+ return token.trim().length >= 16;
270
+ }
271
+
272
+ async function probeSocketViability(): Promise<{ viable: boolean; evidence: string }> {
273
+ const uid = process.getuid?.() ?? os.userInfo().uid;
274
+ const socketPaths = resolveAuraSocketCandidates({
275
+ uid,
276
+ serverUrl: serverUrl(),
277
+ serverPort: process.env.WALLET_SERVER_PORT,
278
+ });
279
+ let sawExistingSocket = false;
280
+ let lastFailure = 'socket-missing';
281
+
282
+ for (const socketPath of socketPaths) {
283
+ let st: fs.Stats;
284
+ try {
285
+ st = fs.statSync(socketPath);
286
+ } catch {
287
+ continue;
288
+ }
289
+
290
+ sawExistingSocket = true;
291
+
292
+ if (!st.isSocket()) {
293
+ lastFailure = 'socket-path-not-unix-socket';
294
+ continue;
295
+ }
296
+ if (st.uid !== uid) {
297
+ lastFailure = 'socket-owner-mismatch';
298
+ continue;
299
+ }
300
+ if ((st.mode & 0o777) > 0o600) {
301
+ lastFailure = 'socket-perms-too-open';
302
+ continue;
303
+ }
304
+
305
+ const connectOk = await new Promise<boolean>((resolve) => {
306
+ const client = net.createConnection(socketPath);
307
+ const timer = setTimeout(() => {
308
+ client.destroy();
309
+ resolve(false);
310
+ }, 500);
311
+
312
+ let done = false;
313
+ const finish = (value: boolean) => {
314
+ if (done) return;
315
+ done = true;
316
+ clearTimeout(timer);
317
+ client.destroy();
318
+ resolve(value);
319
+ };
320
+
321
+ client.once('connect', () => {
322
+ client.write('{"type":"ping"}\n');
323
+ });
324
+ client.once('data', (buf) => {
325
+ const msg = buf.toString('utf8');
326
+ finish(msg.includes('pong'));
327
+ });
328
+ client.once('error', () => finish(false));
329
+ client.once('close', () => finish(false));
330
+ });
331
+
332
+ if (connectOk) {
333
+ return { viable: true, evidence: `socket-connect-ping-ok:${socketPath}` };
334
+ }
335
+ lastFailure = `socket-connect-failed:${socketPath}`;
336
+ }
337
+
338
+ if (!sawExistingSocket) return { viable: false, evidence: 'socket-missing' };
339
+ return { viable: false, evidence: lastFailure };
340
+ }
341
+
342
+ function normalizeStatus(status: number): CheckStatus {
343
+ if (status >= 500) return 'fail';
344
+ if (status >= 400) return 'warn';
345
+ return 'pass';
346
+ }
347
+
348
+ export function evaluateCredentialHealthSeverity(summary: CredentialHealthSummary): {
349
+ status: CheckStatus;
350
+ severity: CheckSeverity;
351
+ code: string;
352
+ finding: string;
353
+ remediation: string;
354
+ evidence: string;
355
+ } {
356
+ if (summary.breached > 0) {
357
+ return {
358
+ status: 'fail',
359
+ severity: 'high',
360
+ code: 'AURA_DOCTOR_CREDENTIAL_HEALTH_BREACHED',
361
+ finding: 'Credential health check found breached credentials.',
362
+ remediation: 'Rotate breached credentials immediately and rerun scan',
363
+ evidence: `breached=${summary.breached},weak=${summary.weak},reused=${summary.reused},unknown=${summary.unknown}`,
364
+ };
365
+ }
366
+
367
+ if (summary.weak > 0 || summary.reused > 0 || summary.unknown > 0) {
368
+ const unknownRemediation = summary.unknown > 0
369
+ ? ' Retry health scan with HEALTH_BREACH_CHECK=true to resolve unknown breach status.'
370
+ : '';
371
+ return {
372
+ status: 'warn',
373
+ severity: 'medium',
374
+ code: summary.unknown > 0
375
+ ? 'AURA_DOCTOR_CREDENTIAL_HEALTH_WARN_UNKNOWN'
376
+ : 'AURA_DOCTOR_CREDENTIAL_HEALTH_WARN_RISK',
377
+ finding: 'Credential health check found weak/reused/unknown-risk credentials.',
378
+ remediation: `Fix weak/reused credentials and rerun scan.${unknownRemediation}`.trim(),
379
+ evidence: `breached=${summary.breached},weak=${summary.weak},reused=${summary.reused},unknown=${summary.unknown}`,
380
+ };
381
+ }
382
+
383
+ return {
384
+ status: 'pass',
385
+ severity: 'info',
386
+ code: 'AURA_DOCTOR_CREDENTIAL_HEALTH_PASS',
387
+ finding: 'Credential health check found no weak/reused/breached/unknown credentials.',
388
+ remediation: 'none',
389
+ evidence: `breached=0,weak=0,reused=0,unknown=0`,
390
+ };
391
+ }
392
+
393
+ async function fetchWithStatus(url: string, init?: RequestInit): Promise<{ ok: boolean; status: number; text: string }> {
394
+ const res = await withTimeout(fetch(url, init), HTTP_TIMEOUT_MS, 'http');
395
+ const text = await res.text();
396
+ return { ok: res.ok, status: res.status, text };
397
+ }
398
+
399
+ function findAuraFile(): string | null {
400
+ let dir = process.cwd();
401
+ while (true) {
402
+ const candidate = path.join(dir, '.aura');
403
+ try {
404
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) return candidate;
405
+ } catch { /* ignore stat errors */ }
406
+ const parent = path.dirname(dir);
407
+ if (parent === dir) return null;
408
+ dir = parent;
409
+ }
410
+ }
411
+
412
+ function listLikelySecretFiles(cwd: string): { checked: number; matches: string[]; timedOut: boolean } {
413
+ const started = Date.now();
414
+ const queue: Array<{ dir: string; depth: number }> = [{ dir: cwd, depth: 0 }];
415
+ let checked = 0;
416
+ const matches: string[] = [];
417
+
418
+ while (queue.length > 0) {
419
+ if (Date.now() - started > SECURITY_TIME_BUDGET_MS) {
420
+ return { checked, matches, timedOut: true };
421
+ }
422
+ const { dir, depth } = queue.shift()!;
423
+
424
+ let entries: fs.Dirent[];
425
+ try {
426
+ entries = fs.readdirSync(dir, { withFileTypes: true });
427
+ } catch {
428
+ continue;
429
+ }
430
+
431
+ for (const entry of entries) {
432
+ checked += 1;
433
+ if (checked > MAX_SECURITY_ENTRIES) {
434
+ return { checked, matches, timedOut: false };
435
+ }
436
+
437
+ const full = path.join(dir, entry.name);
438
+ if (entry.isDirectory() && depth < 2 && entry.name !== 'node_modules' && !entry.name.startsWith('.git')) {
439
+ queue.push({ dir: full, depth: depth + 1 });
440
+ continue;
441
+ }
442
+
443
+ if (!entry.isFile()) continue;
444
+ if (/\.env($|\.)/i.test(entry.name) || /token/i.test(entry.name)) {
445
+ matches.push(path.relative(cwd, full));
446
+ }
447
+ }
448
+ }
449
+
450
+ return { checked, matches, timedOut: false };
451
+ }
452
+
453
+ async function runDoctor(options: DoctorOptions): Promise<DoctorResult> {
454
+ const checks: DoctorCheck[] = [];
455
+ const fixes: string[] = [];
456
+
457
+ const addCheck = (check: DoctorCheck) => checks.push(check);
458
+
459
+ // cli.command.persistence
460
+ const auraPath = findCommandInPath('aura');
461
+ const auramaxxPath = findCommandInPath('auramaxx');
462
+ const commandsAvailable = Boolean(auraPath && auramaxxPath);
463
+ if (commandsAvailable) {
464
+ addCheck({
465
+ id: 'cli.command.persistence',
466
+ code: 'AURA_DOCTOR_CLI_COMMANDS_AVAILABLE',
467
+ severity: 'info',
468
+ status: 'pass',
469
+ finding: 'Aura CLI commands are available on PATH.',
470
+ evidence: `aura=${auraPath};auramaxx=${auramaxxPath}`,
471
+ remediation: 'none',
472
+ });
473
+ } else if (options.fix) {
474
+ const fixResult = fixShellFallback();
475
+ if (fixResult.ok) {
476
+ const rcEvidence = fixResult.rcFile ? `rc=${fixResult.rcFile}` : 'rc=unknown';
477
+ addCheck({
478
+ id: 'cli.command.persistence',
479
+ code: 'AURA_DOCTOR_CLI_FALLBACK_INSTALLED',
480
+ severity: 'info',
481
+ status: 'pass',
482
+ finding: 'Installed persistent shell fallback for aura/auramaxx commands.',
483
+ evidence: `${fixResult.message};${rcEvidence}`,
484
+ remediation: fixResult.rcFile ? `Run: source ${fixResult.rcFile}` : 'Open a new shell',
485
+ });
486
+ fixes.push(
487
+ fixResult.rcFile
488
+ ? `Installed/updated Aura fallback block in ${fixResult.rcFile}.`
489
+ : 'Installed/updated Aura fallback block.'
490
+ );
491
+ } else {
492
+ addCheck({
493
+ id: 'cli.command.persistence',
494
+ code: fixResult.message === 'unsupported-shell'
495
+ ? 'AURA_DOCTOR_CLI_FALLBACK_UNSUPPORTED_SHELL'
496
+ : 'AURA_DOCTOR_CLI_FALLBACK_INSTALL_FAILED',
497
+ severity: fixResult.message === 'unsupported-shell' ? 'medium' : 'high',
498
+ status: 'warn',
499
+ finding: 'Aura CLI commands are missing and shell fallback auto-fix did not complete.',
500
+ evidence: `fix-error=${fixResult.message}${fixResult.rcFile ? `;rc=${fixResult.rcFile}` : ''}`,
501
+ remediation: 'Install globally: npm install -g auramaxx',
502
+ });
503
+ }
504
+ } else {
505
+ addCheck({
506
+ id: 'cli.command.persistence',
507
+ code: 'AURA_DOCTOR_CLI_COMMANDS_MISSING',
508
+ severity: 'medium',
509
+ status: 'warn',
510
+ finding: 'Aura CLI commands are not available on PATH.',
511
+ evidence: 'command-not-found',
512
+ remediation: 'Run: npx auramaxx doctor --fix (or install globally: npm install -g auramaxx)',
513
+ });
514
+ }
515
+
516
+ // runtime.api.health
517
+ let apiHealthy = false;
518
+ try {
519
+ const health = await fetchWithStatus(`${serverUrl()}/health`);
520
+ apiHealthy = health.ok;
521
+ addCheck({
522
+ id: 'runtime.api.health',
523
+ code: health.ok ? 'AURA_DOCTOR_RUNTIME_API_HEALTHY' : 'AURA_DOCTOR_RUNTIME_API_UNREACHABLE',
524
+ severity: health.ok ? 'info' : 'critical',
525
+ status: health.ok ? 'pass' : 'fail',
526
+ finding: health.ok ? 'Aura API is reachable.' : 'Aura API is unreachable.',
527
+ evidence: health.ok ? `health-status-${health.status}` : `health-status-${health.status}`,
528
+ remediation: health.ok ? 'none' : 'Run: npx auramaxx',
529
+ });
530
+ } catch (err) {
531
+ addCheck({
532
+ id: 'runtime.api.health',
533
+ code: 'AURA_DOCTOR_RUNTIME_API_UNREACHABLE',
534
+ severity: 'critical',
535
+ status: 'fail',
536
+ finding: 'Aura API is unreachable.',
537
+ evidence: `health-error-${getErrorMessage(err)}`,
538
+ remediation: 'Run: npx auramaxx',
539
+ });
540
+ }
541
+
542
+ // runtime.api.setup + vault checks
543
+ let setup: SetupStatus | null = null;
544
+ try {
545
+ setup = await fetchJson<SetupStatus>('/setup');
546
+ addCheck({
547
+ id: 'runtime.api.setup',
548
+ code: 'AURA_DOCTOR_RUNTIME_SETUP_OK',
549
+ severity: 'info',
550
+ status: 'pass',
551
+ finding: 'Setup endpoint responded.',
552
+ evidence: 'setup-response-valid',
553
+ remediation: 'none',
554
+ });
555
+ } catch (err) {
556
+ addCheck({
557
+ id: 'runtime.api.setup',
558
+ code: 'AURA_DOCTOR_RUNTIME_SETUP_ERROR',
559
+ severity: 'high',
560
+ status: 'fail',
561
+ finding: 'Setup endpoint check failed.',
562
+ evidence: `setup-error-${getErrorMessage(err)}`,
563
+ remediation: 'Run: npx auramaxx',
564
+ });
565
+ }
566
+
567
+ // runtime.dashboard.reachability (never fail)
568
+ if (!apiHealthy) {
569
+ addCheck({
570
+ id: 'runtime.dashboard.reachability',
571
+ code: 'AURA_DOCTOR_DASHBOARD_SKIPPED_API_UNHEALTHY',
572
+ severity: 'low',
573
+ status: 'warn',
574
+ finding: 'Dashboard check skipped while API is unhealthy.',
575
+ evidence: 'api-unhealthy-dashboard-check-skipped',
576
+ remediation: 'Run: npx auramaxx',
577
+ });
578
+ } else {
579
+ try {
580
+ const dashboard = await withTimeout(fetch('http://localhost:4747'), 1500, 'dashboard');
581
+ const reachable = dashboard.ok || (dashboard.status >= 300 && dashboard.status < 400);
582
+ addCheck({
583
+ id: 'runtime.dashboard.reachability',
584
+ code: reachable ? 'AURA_DOCTOR_DASHBOARD_REACHABLE' : 'AURA_DOCTOR_DASHBOARD_UNREACHABLE',
585
+ severity: reachable ? 'info' : 'low',
586
+ status: reachable ? 'pass' : 'warn',
587
+ finding: reachable ? 'Dashboard endpoint is reachable.' : 'Dashboard endpoint is not reachable in current mode.',
588
+ evidence: reachable ? 'dashboard-reachable' : 'headless-or-dashboard-not-running',
589
+ remediation: reachable ? 'none' : 'If UI needed: npx auramaxx',
590
+ });
591
+ } catch {
592
+ addCheck({
593
+ id: 'runtime.dashboard.reachability',
594
+ code: 'AURA_DOCTOR_DASHBOARD_UNREACHABLE',
595
+ severity: 'low',
596
+ status: 'warn',
597
+ finding: 'Dashboard endpoint is not reachable in current mode.',
598
+ evidence: 'headless-or-dashboard-not-running',
599
+ remediation: 'If UI needed: npx auramaxx',
600
+ });
601
+ }
602
+ }
603
+
604
+ const hasWallet = !!setup?.hasWallet;
605
+ const unlocked = !!setup?.unlocked;
606
+ const hasAddress = !!setup?.address;
607
+
608
+ // vault.exists
609
+ addCheck({
610
+ id: 'vault.exists',
611
+ code: hasWallet ? 'AURA_DOCTOR_VAULT_EXISTS' : 'AURA_DOCTOR_VAULT_MISSING',
612
+ severity: hasWallet ? 'info' : 'high',
613
+ status: hasWallet ? 'pass' : 'fail',
614
+ finding: hasWallet ? 'Primary vault exists.' : 'No vault found.',
615
+ evidence: hasWallet ? 'hasWallet=true' : 'hasWallet=false',
616
+ remediation: hasWallet ? 'none' : 'Run: npx auramaxx',
617
+ });
618
+
619
+ // vault.unlock_state
620
+ addCheck({
621
+ id: 'vault.unlock_state',
622
+ code: unlocked ? 'AURA_DOCTOR_VAULT_UNLOCKED' : 'AURA_DOCTOR_VAULT_LOCKED',
623
+ severity: unlocked ? 'info' : 'medium',
624
+ status: unlocked ? 'pass' : 'warn',
625
+ finding: unlocked ? 'Vault is unlocked.' : 'Vault is locked.',
626
+ evidence: `unlocked=${unlocked}`,
627
+ remediation: unlocked ? 'none' : 'Run: npx auramaxx unlock',
628
+ });
629
+
630
+ // vault.primary.address tuple
631
+ let addressStatus: CheckStatus = 'pass';
632
+ let addressSeverity: CheckSeverity = 'info';
633
+ let addressEvidence = 'wallet-locked-address-not-required';
634
+ let addressCode = 'AURA_DOCTOR_VAULT_PRIMARY_ADDRESS_NA_LOCKED';
635
+ let addressFinding = 'Primary address is not required while locked.';
636
+ let addressRemediation = 'none';
637
+
638
+ if (!hasWallet) {
639
+ addressStatus = 'fail';
640
+ addressSeverity = 'high';
641
+ addressEvidence = 'no-wallet';
642
+ addressCode = 'AURA_DOCTOR_VAULT_NO_WALLET';
643
+ addressFinding = 'Primary address unavailable because no vault exists.';
644
+ addressRemediation = 'Run: npx auramaxx';
645
+ } else if (unlocked && hasAddress) {
646
+ addressStatus = 'pass';
647
+ addressSeverity = 'info';
648
+ addressEvidence = 'primary-address-present';
649
+ addressCode = 'AURA_DOCTOR_VAULT_PRIMARY_ADDRESS_PRESENT';
650
+ addressFinding = 'Primary address is present.';
651
+ addressRemediation = 'none';
652
+ } else if (unlocked && !hasAddress) {
653
+ addressStatus = 'warn';
654
+ addressSeverity = 'medium';
655
+ addressEvidence = 'unlocked-without-primary-address';
656
+ addressCode = 'AURA_DOCTOR_VAULT_PRIMARY_ADDRESS_MISSING';
657
+ addressFinding = 'Vault is unlocked but primary address is missing.';
658
+ addressRemediation = 'Run: npx auramaxx';
659
+ }
660
+
661
+ addCheck({
662
+ id: 'vault.primary.address',
663
+ code: addressCode,
664
+ severity: addressSeverity,
665
+ status: addressStatus,
666
+ finding: addressFinding,
667
+ evidence: addressEvidence,
668
+ remediation: addressRemediation,
669
+ });
670
+
671
+ // auth.socket.path
672
+ const socketProbe = await probeSocketViability();
673
+ addCheck({
674
+ id: 'auth.socket.path',
675
+ code: socketProbe.viable ? 'AURA_DOCTOR_AUTH_SOCKET_VIABLE' : 'AURA_DOCTOR_AUTH_SOCKET_NOT_VIABLE',
676
+ severity: socketProbe.viable ? 'info' : 'low',
677
+ status: socketProbe.viable ? 'pass' : 'warn',
678
+ finding: socketProbe.viable ? 'Unix socket auth is viable.' : 'Unix socket auth is not viable.',
679
+ evidence: socketProbe.evidence,
680
+ remediation: socketProbe.viable ? 'none' : 'Run: npx auramaxx',
681
+ });
682
+
683
+ // auth.token.env
684
+ const envToken = process.env.AURA_TOKEN;
685
+ const tokenPresent = !!envToken;
686
+ const tokenShapeValid = checkTokenShape(envToken);
687
+ addCheck({
688
+ id: 'auth.token.env',
689
+ code: tokenPresent ? (tokenShapeValid ? 'AURA_DOCTOR_AUTH_TOKEN_PRESENT' : 'AURA_DOCTOR_AUTH_TOKEN_INVALID_SHAPE') : 'AURA_DOCTOR_AUTH_TOKEN_MISSING',
690
+ severity: tokenPresent ? (tokenShapeValid ? 'info' : 'medium') : 'low',
691
+ status: tokenPresent ? (tokenShapeValid ? 'pass' : 'warn') : 'warn',
692
+ finding: tokenPresent ? (tokenShapeValid ? 'AURA_TOKEN is present.' : 'AURA_TOKEN is present but malformed.') : 'AURA_TOKEN is not set.',
693
+ evidence: maskToken(envToken),
694
+ remediation: tokenPresent ? (tokenShapeValid ? 'none' : 'Export a valid AURA_TOKEN') : 'Export AURA_TOKEN or rely on socket auth',
695
+ });
696
+
697
+ // auth.mode.viability matrix
698
+ const tokenViable = tokenPresent && tokenShapeValid;
699
+ const authViable = socketProbe.viable || tokenViable;
700
+ addCheck({
701
+ id: 'auth.mode.viability',
702
+ code: authViable ? 'AURA_DOCTOR_AUTH_MODE_VIABLE' : 'AURA_DOCTOR_AUTH_MODE_UNAVAILABLE',
703
+ severity: authViable ? 'info' : 'critical',
704
+ status: authViable ? 'pass' : 'fail',
705
+ finding: authViable ? 'At least one auth bootstrap mode is viable.' : 'No viable auth bootstrap mode found.',
706
+ evidence: authViable
707
+ ? (socketProbe.viable && tokenViable ? 'socket-and-token-available' : socketProbe.viable ? 'socket-available' : 'token-available')
708
+ : 'no-viable-auth-bootstrap',
709
+ remediation: authViable ? 'none' : 'Start daemon for socket (npx auramaxx) or export AURA_TOKEN',
710
+ });
711
+
712
+ const authProbeState: AuthProbeState = {
713
+ socketViable: socketProbe.viable,
714
+ tokenPresent,
715
+ tokenShapeValid,
716
+ tokenMask: maskToken(envToken),
717
+ };
718
+
719
+ // token introspection (only if env token present + shape valid)
720
+ if (tokenViable) {
721
+ try {
722
+ const validate = await fetchJson<TokenValidateResponse>('/auth/validate', {
723
+ body: { token: envToken },
724
+ });
725
+ authProbeState.tokenValidate = validate;
726
+ } catch (err) {
727
+ authProbeState.tokenValidate = {
728
+ valid: false,
729
+ error: getErrorMessage(err),
730
+ };
731
+ }
732
+ }
733
+
734
+ // credential.list.readiness (read-only)
735
+ if (!tokenViable) {
736
+ addCheck({
737
+ id: 'credential.list.readiness',
738
+ code: 'AURA_DOCTOR_CREDENTIAL_LIST_TOKEN_UNAVAILABLE',
739
+ severity: 'medium',
740
+ status: 'warn',
741
+ finding: 'Credential list readiness requires explicit token audit mode.',
742
+ evidence: 'socket-mode-no-explicit-token',
743
+ remediation: 'Export AURA_TOKEN for explicit credential-read checks',
744
+ });
745
+ } else {
746
+ try {
747
+ const response = await fetchWithStatus(`${serverUrl()}/credentials`, {
748
+ headers: {
749
+ Authorization: `Bearer ${envToken}`,
750
+ },
751
+ });
752
+
753
+ const status = normalizeStatus(response.status);
754
+ const code = response.ok
755
+ ? 'AURA_DOCTOR_CREDENTIAL_LIST_READY'
756
+ : response.status === 401 || response.status === 403
757
+ ? 'AURA_DOCTOR_CREDENTIAL_LIST_UNAUTHORIZED'
758
+ : 'AURA_DOCTOR_CREDENTIAL_LIST_ERROR';
759
+
760
+ addCheck({
761
+ id: 'credential.list.readiness',
762
+ code,
763
+ severity: status === 'pass' ? 'info' : status === 'warn' ? 'medium' : 'high',
764
+ status,
765
+ finding: response.ok ? 'Credential list endpoint is readable.' : 'Credential list endpoint is not readable.',
766
+ evidence: `credentials-list-http-${response.status}`,
767
+ remediation: response.ok ? 'none' : 'Grant secret:read scope or refresh token',
768
+ });
769
+ } catch (err) {
770
+ addCheck({
771
+ id: 'credential.list.readiness',
772
+ code: 'AURA_DOCTOR_CREDENTIAL_LIST_ERROR',
773
+ severity: 'high',
774
+ status: 'fail',
775
+ finding: 'Credential list endpoint check failed.',
776
+ evidence: `credentials-list-error-${getErrorMessage(err)}`,
777
+ remediation: 'Verify Aura API health and token scope',
778
+ });
779
+ }
780
+ }
781
+
782
+ // credential.scope.sanity
783
+ if (!tokenViable) {
784
+ addCheck({
785
+ id: 'credential.scope.sanity',
786
+ code: 'AURA_DOCTOR_SCOPE_TOKEN_INTROSPECTION_UNAVAILABLE',
787
+ severity: 'low',
788
+ status: 'warn',
789
+ finding: 'Token scope introspection unavailable without explicit token.',
790
+ evidence: 'socket-mode-no-explicit-token',
791
+ remediation: 'Export AURA_TOKEN for explicit scope audit',
792
+ });
793
+ } else if (!authProbeState.tokenValidate?.valid) {
794
+ addCheck({
795
+ id: 'credential.scope.sanity',
796
+ code: 'AURA_DOCTOR_SCOPE_TOKEN_INVALID',
797
+ severity: 'high',
798
+ status: 'fail',
799
+ finding: 'Provided token failed validation.',
800
+ evidence: 'token-validation-failed',
801
+ remediation: 'Export a valid AURA_TOKEN with secret:read permissions',
802
+ });
803
+ } else {
804
+ const permissions = authProbeState.tokenValidate.payload?.permissions || [];
805
+ const hasSecretRead = permissions.includes('admin:*') || permissions.includes('secret:read');
806
+ addCheck({
807
+ id: 'credential.scope.sanity',
808
+ code: hasSecretRead ? 'AURA_DOCTOR_SCOPE_OK' : 'AURA_DOCTOR_SCOPE_MISSING_SECRET_READ',
809
+ severity: hasSecretRead ? 'info' : 'high',
810
+ status: hasSecretRead ? 'pass' : 'fail',
811
+ finding: hasSecretRead ? 'Token scope includes secret read access.' : 'Token scope is missing secret:read access.',
812
+ evidence: hasSecretRead ? 'required-scope-present' : 'required-scope-missing-secret-read',
813
+ remediation: hasSecretRead ? 'none' : 'Issue token with secret:read (or admin:*) permission',
814
+ });
815
+ }
816
+
817
+ // credential.health.summary
818
+ if (!tokenViable) {
819
+ addCheck({
820
+ id: 'credential.health.summary',
821
+ code: 'AURA_DOCTOR_CREDENTIAL_HEALTH_TOKEN_UNAVAILABLE',
822
+ severity: 'low',
823
+ status: 'warn',
824
+ finding: 'Credential health summary requires explicit token mode.',
825
+ evidence: 'socket-mode-no-explicit-token',
826
+ remediation: 'Export AURA_TOKEN for credential health summary check',
827
+ });
828
+ } else {
829
+ try {
830
+ const response = await fetchWithStatus(`${serverUrl()}/credentials/health/summary`, {
831
+ headers: { Authorization: `Bearer ${envToken}` },
832
+ });
833
+
834
+ if (!response.ok) {
835
+ addCheck({
836
+ id: 'credential.health.summary',
837
+ code: response.status === 401 || response.status === 403
838
+ ? 'AURA_DOCTOR_CREDENTIAL_HEALTH_UNAUTHORIZED'
839
+ : 'AURA_DOCTOR_CREDENTIAL_HEALTH_ERROR',
840
+ severity: response.status === 401 || response.status === 403 ? 'medium' : 'high',
841
+ status: normalizeStatus(response.status),
842
+ finding: 'Credential health summary endpoint is not readable.',
843
+ evidence: `credential-health-summary-http-${response.status}`,
844
+ remediation: 'Grant secret:read scope or refresh token',
845
+ });
846
+ } else {
847
+ const data = JSON.parse(response.text) as { summary?: CredentialHealthSummary };
848
+ if (!data.summary) {
849
+ addCheck({
850
+ id: 'credential.health.summary',
851
+ code: 'AURA_DOCTOR_CREDENTIAL_HEALTH_MALFORMED',
852
+ severity: 'high',
853
+ status: 'fail',
854
+ finding: 'Credential health summary payload is malformed.',
855
+ evidence: 'summary-missing',
856
+ remediation: 'Upgrade Aura server and rerun doctor',
857
+ });
858
+ } else {
859
+ const evaluated = evaluateCredentialHealthSeverity(data.summary);
860
+ addCheck({
861
+ id: 'credential.health.summary',
862
+ code: evaluated.code,
863
+ severity: evaluated.severity,
864
+ status: evaluated.status,
865
+ finding: evaluated.finding,
866
+ evidence: evaluated.evidence,
867
+ remediation: evaluated.remediation,
868
+ });
869
+ }
870
+ }
871
+ } catch (err) {
872
+ addCheck({
873
+ id: 'credential.health.summary',
874
+ code: 'AURA_DOCTOR_CREDENTIAL_HEALTH_ERROR',
875
+ severity: 'high',
876
+ status: 'fail',
877
+ finding: 'Credential health summary check failed.',
878
+ evidence: `credential-health-summary-error-${getErrorMessage(err)}`,
879
+ remediation: 'Verify Aura API health and token scope',
880
+ });
881
+ }
882
+ }
883
+
884
+ // .aura checks
885
+ const auraFile = findAuraFile();
886
+ if (!auraFile) {
887
+ addCheck({
888
+ id: 'aura_file.discovery',
889
+ code: 'AURA_DOCTOR_AURA_FILE_MISSING',
890
+ severity: 'low',
891
+ status: 'warn',
892
+ finding: 'No .aura file found in current/parent directories.',
893
+ evidence: 'aura-file-not-found',
894
+ remediation: 'Run: npx auramaxx env init',
895
+ });
896
+
897
+ addCheck({
898
+ id: 'aura_file.parse',
899
+ code: 'AURA_DOCTOR_AURA_FILE_PARSE_SKIPPED',
900
+ severity: 'info',
901
+ status: 'pass',
902
+ finding: '.aura parse check skipped.',
903
+ evidence: 'no-aura-file',
904
+ remediation: 'none',
905
+ });
906
+
907
+ addCheck({
908
+ id: 'aura_file.mapping_resolution',
909
+ code: 'AURA_DOCTOR_AURA_MAPPING_SKIPPED',
910
+ severity: 'info',
911
+ status: 'pass',
912
+ finding: '.aura mapping resolution skipped.',
913
+ evidence: 'no-aura-file',
914
+ remediation: 'none',
915
+ });
916
+ } else {
917
+ addCheck({
918
+ id: 'aura_file.discovery',
919
+ code: 'AURA_DOCTOR_AURA_FILE_FOUND',
920
+ severity: 'info',
921
+ status: 'pass',
922
+ finding: '.aura file discovered.',
923
+ evidence: path.relative(process.cwd(), auraFile) || '.aura',
924
+ remediation: 'none',
925
+ });
926
+
927
+ let mappings: AuraMapping[] = [];
928
+ let parseOk = false;
929
+ try {
930
+ mappings = parseAuraFile(auraFile);
931
+ parseOk = true;
932
+ addCheck({
933
+ id: 'aura_file.parse',
934
+ code: 'AURA_DOCTOR_AURA_FILE_PARSE_OK',
935
+ severity: 'info',
936
+ status: 'pass',
937
+ finding: '.aura file parsed successfully.',
938
+ evidence: `mappings=${mappings.length}`,
939
+ remediation: 'none',
940
+ });
941
+ } catch (err) {
942
+ addCheck({
943
+ id: 'aura_file.parse',
944
+ code: 'AURA_DOCTOR_AURA_FILE_PARSE_FAILED',
945
+ severity: 'high',
946
+ status: 'fail',
947
+ finding: '.aura file parse failed.',
948
+ evidence: getErrorMessage(err),
949
+ remediation: 'Fix .aura syntax and rerun npx auramaxx doctor',
950
+ });
951
+ }
952
+
953
+ if (!parseOk) {
954
+ addCheck({
955
+ id: 'aura_file.mapping_resolution',
956
+ code: 'AURA_DOCTOR_AURA_MAPPING_SKIPPED_PARSE_FAIL',
957
+ severity: 'info',
958
+ status: 'pass',
959
+ finding: '.aura mapping resolution skipped due to parse failure.',
960
+ evidence: 'parse-failed',
961
+ remediation: 'none',
962
+ });
963
+ } else if (!tokenViable) {
964
+ addCheck({
965
+ id: 'aura_file.mapping_resolution',
966
+ code: 'AURA_DOCTOR_AURA_MAPPING_TOKEN_UNAVAILABLE',
967
+ severity: 'medium',
968
+ status: 'warn',
969
+ finding: '.aura mapping resolution requires explicit token mode.',
970
+ evidence: 'socket-mode-no-explicit-token',
971
+ remediation: 'Export AURA_TOKEN for mapping resolution audit',
972
+ });
973
+ } else {
974
+ const cappedMappings = mappings.slice(0, MAX_AURA_MAPPINGS);
975
+ const uniqueCredentialNames = [...new Set(cappedMappings.map((m) => m.credentialName))].slice(0, MAX_UNIQUE_CREDENTIAL_PROBES);
976
+ const credIdByName = new Map<string, string>();
977
+ const failures = new Set<string>();
978
+
979
+ for (const credName of uniqueCredentialNames) {
980
+ try {
981
+ const resp = await fetchWithStatus(`${serverUrl()}/credentials?q=${encodeURIComponent(credName)}`, {
982
+ headers: { Authorization: `Bearer ${envToken}` },
983
+ });
984
+
985
+ if (!resp.ok) {
986
+ failures.add(`lookup:${credName}`);
987
+ continue;
988
+ }
989
+
990
+ const data = JSON.parse(resp.text) as { credentials?: Array<{ id: string; name: string }> };
991
+ const list = data.credentials || [];
992
+ const exact = list.find((c) => c.name.toLowerCase() === credName.toLowerCase()) || list[0];
993
+ if (!exact) failures.add(`missing-credential:${credName}`);
994
+ else credIdByName.set(credName, exact.id);
995
+ } catch {
996
+ failures.add(`lookup:${credName}`);
997
+ }
998
+ }
999
+
1000
+ for (const mapping of cappedMappings) {
1001
+ const id = credIdByName.get(mapping.credentialName);
1002
+ if (!id) {
1003
+ failures.add(`${mapping.envVar}->${mapping.credentialName}.${mapping.field}`);
1004
+ continue;
1005
+ }
1006
+
1007
+ try {
1008
+ const resp = await fetchWithStatus(`${serverUrl()}/credentials/${id}/read`, {
1009
+ method: 'POST',
1010
+ headers: { Authorization: `Bearer ${envToken}` },
1011
+ });
1012
+
1013
+ if (!resp.ok) {
1014
+ failures.add(`${mapping.envVar}->${mapping.credentialName}.${mapping.field}`);
1015
+ }
1016
+ } catch {
1017
+ failures.add(`${mapping.envVar}->${mapping.credentialName}.${mapping.field}`);
1018
+ }
1019
+ }
1020
+
1021
+ const failureList = [...failures];
1022
+ const limitedEvidence = failureList.slice(0, 10);
1023
+ const more = failureList.length > 10 ? ` (+${failureList.length - 10} more)` : '';
1024
+
1025
+ addCheck({
1026
+ id: 'aura_file.mapping_resolution',
1027
+ code: failureList.length === 0 ? 'AURA_DOCTOR_AURA_MAPPING_OK' : 'AURA_DOCTOR_AURA_MAPPING_FAILED',
1028
+ severity: failureList.length === 0 ? 'info' : 'high',
1029
+ status: failureList.length === 0 ? 'pass' : 'fail',
1030
+ finding: failureList.length === 0 ? '.aura mappings are resolvable.' : 'One or more .aura mappings are not resolvable.',
1031
+ evidence: failureList.length === 0 ? `checked=${cappedMappings.length}` : `${limitedEvidence.join(', ')}${more}`,
1032
+ remediation: failureList.length === 0 ? 'none' : 'Fix missing credentials/fields or token scope',
1033
+ });
1034
+ }
1035
+ }
1036
+
1037
+ // MCP checks (read-only)
1038
+ const mcpHelp = spawnSync('npx', ['auramaxx', 'mcp', '--help'], {
1039
+ encoding: 'utf8',
1040
+ timeout: 1500,
1041
+ });
1042
+ const mcpAvailable = !mcpHelp.error && mcpHelp.status === 0;
1043
+ addCheck({
1044
+ id: 'mcp.command.available',
1045
+ code: mcpAvailable ? 'AURA_DOCTOR_MCP_COMMAND_AVAILABLE' : 'AURA_DOCTOR_MCP_COMMAND_UNAVAILABLE',
1046
+ severity: mcpAvailable ? 'info' : 'medium',
1047
+ status: mcpAvailable ? 'pass' : 'warn',
1048
+ finding: mcpAvailable ? 'MCP command is invokable.' : 'MCP command is not invokable.',
1049
+ evidence: mcpAvailable ? 'mcp-help-ok:auramaxx' : `mcp-help-failed-${mcpHelp.status ?? 'error'}`,
1050
+ remediation: mcpAvailable ? 'none' : 'Install dependencies and ensure npx auramaxx mcp is available',
1051
+ });
1052
+
1053
+ const mcpConfigs = [
1054
+ path.join(process.cwd(), '.mcp.json'),
1055
+ path.join(process.cwd(), '.vscode', 'mcp.json'),
1056
+ path.join(os.homedir(), '.cursor', 'mcp.json'),
1057
+ path.join(os.homedir(), '.windsurf', 'mcp.json'),
1058
+ path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'),
1059
+ ];
1060
+
1061
+ const existingConfigs = mcpConfigs.filter((p) => fs.existsSync(p));
1062
+ let parseFailures = 0;
1063
+ for (const file of existingConfigs) {
1064
+ try {
1065
+ JSON.parse(fs.readFileSync(file, 'utf8'));
1066
+ } catch {
1067
+ parseFailures += 1;
1068
+ }
1069
+ }
1070
+
1071
+ addCheck({
1072
+ id: 'mcp.config.footprint',
1073
+ code: parseFailures > 0 ? 'AURA_DOCTOR_MCP_CONFIG_INVALID' : existingConfigs.length > 0 ? 'AURA_DOCTOR_MCP_CONFIG_PRESENT' : 'AURA_DOCTOR_MCP_CONFIG_ABSENT',
1074
+ severity: parseFailures > 0 ? 'medium' : existingConfigs.length > 0 ? 'info' : 'low',
1075
+ status: parseFailures > 0 ? 'warn' : existingConfigs.length > 0 ? 'pass' : 'warn',
1076
+ finding: parseFailures > 0
1077
+ ? 'One or more MCP config files are invalid JSON.'
1078
+ : existingConfigs.length > 0
1079
+ ? 'MCP config footprint detected.'
1080
+ : 'No MCP config footprint detected.',
1081
+ evidence: parseFailures > 0
1082
+ ? `invalid-json-count=${parseFailures}`
1083
+ : existingConfigs.length > 0
1084
+ ? `configs=${existingConfigs.length}`
1085
+ : 'no-known-mcp-config-files',
1086
+ remediation: parseFailures > 0
1087
+ ? 'Fix malformed MCP config JSON files'
1088
+ : existingConfigs.length > 0
1089
+ ? 'none'
1090
+ : 'Run: npx auramaxx mcp --install',
1091
+ });
1092
+
1093
+ addCheck({
1094
+ id: 'mcp.auth.forecast',
1095
+ code: authViable ? 'AURA_DOCTOR_MCP_AUTH_FORECAST_READY' : 'AURA_DOCTOR_MCP_AUTH_FORECAST_BLOCKED',
1096
+ severity: authViable ? 'info' : 'high',
1097
+ status: authViable ? 'pass' : 'fail',
1098
+ finding: authViable ? 'MCP auth bootstrap appears ready.' : 'MCP auth bootstrap is blocked.',
1099
+ evidence: authViable ? 'auth-bootstrap-viable' : 'no-viable-auth-bootstrap',
1100
+ remediation: authViable ? 'none' : 'Start daemon socket or export AURA_TOKEN before MCP use',
1101
+ });
1102
+
1103
+ // Extension check
1104
+ addCheck({
1105
+ id: 'extension.detectability.cli_mode',
1106
+ code: 'AURA_DOCTOR_EXTENSION_NOT_DETECTABLE_CLI_MODE',
1107
+ severity: 'low',
1108
+ status: 'warn',
1109
+ finding: 'Extension session state is not detectable in CLI mode.',
1110
+ evidence: 'not-detectable-in-this-mode',
1111
+ remediation: 'Use extension UI diagnostics for handshake details',
1112
+ });
1113
+
1114
+ // Security checks
1115
+ if (!tokenViable) {
1116
+ addCheck({
1117
+ id: 'security.token_scope.breadth',
1118
+ code: 'AURA_DOCTOR_SECURITY_TOKEN_INTROSPECTION_UNAVAILABLE',
1119
+ severity: 'low',
1120
+ status: 'warn',
1121
+ finding: 'Cannot evaluate token scope breadth without explicit token.',
1122
+ evidence: 'socket-mode-no-explicit-token',
1123
+ remediation: 'Export AURA_TOKEN for explicit scope audit',
1124
+ });
1125
+ } else {
1126
+ const permissions = authProbeState.tokenValidate?.payload?.permissions || [];
1127
+ const broad = permissions.some((p) => p === 'admin:*' || p === '*' || p.endsWith(':*'));
1128
+ addCheck({
1129
+ id: 'security.token_scope.breadth',
1130
+ code: broad ? 'AURA_DOCTOR_SECURITY_SCOPE_BROAD' : 'AURA_DOCTOR_SECURITY_SCOPE_LEAST_PRIVILEGE',
1131
+ severity: broad ? 'medium' : 'info',
1132
+ status: broad ? 'warn' : 'pass',
1133
+ finding: broad ? 'Token includes broad wildcard/admin scope.' : 'Token scope appears least-privilege oriented.',
1134
+ evidence: broad ? 'broad-scope-detected' : 'no-broad-scope-detected',
1135
+ remediation: broad ? 'Issue a narrower token for routine automation' : 'none',
1136
+ });
1137
+ }
1138
+
1139
+ const scan = listLikelySecretFiles(process.cwd());
1140
+ addCheck({
1141
+ id: 'security.plaintext_token.artifacts',
1142
+ code: scan.timedOut
1143
+ ? 'AURA_DOCTOR_SECURITY_SCAN_TIME_BUDGET_EXCEEDED'
1144
+ : scan.matches.length > 0
1145
+ ? 'AURA_DOCTOR_SECURITY_ARTIFACT_HINTS_FOUND'
1146
+ : 'AURA_DOCTOR_SECURITY_ARTIFACT_HINTS_NONE',
1147
+ severity: scan.timedOut ? 'low' : scan.matches.length > 0 ? 'medium' : 'info',
1148
+ status: scan.timedOut ? 'warn' : scan.matches.length > 0 ? 'warn' : 'pass',
1149
+ finding: scan.timedOut
1150
+ ? 'Security artifact scan hit time budget.'
1151
+ : scan.matches.length > 0
1152
+ ? 'Potential plaintext secret artifacts detected.'
1153
+ : 'No obvious plaintext secret artifacts detected.',
1154
+ evidence: scan.timedOut
1155
+ ? `scan-time-budget-exceeded checked=${scan.checked}`
1156
+ : scan.matches.length > 0
1157
+ ? `matches=${scan.matches.slice(0, 10).join(',')}${scan.matches.length > 10 ? ' (+more)' : ''}`
1158
+ : `checked=${scan.checked}`,
1159
+ remediation: scan.matches.length > 0
1160
+ ? 'Move secrets to Aura vault and remove plaintext files'
1161
+ : scan.timedOut
1162
+ ? 'Rerun in a smaller working directory for complete scan'
1163
+ : 'none',
1164
+ });
1165
+
1166
+ const stalenessHours = process.env.AURA_TOKEN_ISSUED_AT
1167
+ ? Math.floor((Date.now() - new Date(process.env.AURA_TOKEN_ISSUED_AT).getTime()) / (1000 * 60 * 60))
1168
+ : null;
1169
+ const stale = stalenessHours !== null && Number.isFinite(stalenessHours) && stalenessHours > 24;
1170
+ addCheck({
1171
+ id: 'security.auth_artifact.staleness',
1172
+ code: stale ? 'AURA_DOCTOR_SECURITY_AUTH_ARTIFACT_STALE' : 'AURA_DOCTOR_SECURITY_AUTH_ARTIFACT_FRESH_OR_UNKNOWN',
1173
+ severity: stale ? 'low' : 'info',
1174
+ status: stale ? 'warn' : 'pass',
1175
+ finding: stale ? 'Auth artifact appears stale.' : 'Auth artifact freshness is acceptable or unavailable.',
1176
+ evidence: stale ? `token-age-hours=${stalenessHours}` : 'no-staleness-metadata',
1177
+ remediation: stale ? 'Rotate token to reduce exposure window' : 'none',
1178
+ });
1179
+
1180
+ const summary = checks.reduce(
1181
+ (acc, check) => {
1182
+ acc[check.status] += 1;
1183
+ return acc;
1184
+ },
1185
+ { pass: 0, warn: 0, fail: 0 }
1186
+ );
1187
+
1188
+ const hasBlocking = summary.fail > 0 || (options.strict && summary.warn > 0);
1189
+
1190
+ return {
1191
+ ok: !hasBlocking,
1192
+ mode: options.strict ? 'strict' : 'default',
1193
+ summary,
1194
+ checks,
1195
+ fixes,
1196
+ };
1197
+ }
1198
+
1199
+ function formatHuman(checks: DoctorCheck[]): string {
1200
+ return checks
1201
+ .map((c) => `${checkBadge(c.status)} ${c.id}\n ${c.finding}\n evidence: ${c.evidence}\n remediation: ${c.remediation}`)
1202
+ .join('\n');
1203
+ }
1204
+
1205
+ export function mapExitCode(result: DoctorResult): number {
1206
+ return result.ok ? EXIT.OK : EXIT.FAIL;
1207
+ }
1208
+
1209
+ async function main() {
1210
+ try {
1211
+ const options = parseArgs(process.argv.slice(2));
1212
+ const result = await runDoctor(options);
1213
+
1214
+ if (options.json) {
1215
+ console.log(JSON.stringify(result, null, 2));
1216
+ } else {
1217
+ printBanner('DOCTOR');
1218
+ console.log(formatHuman(result.checks));
1219
+ if (result.fixes.length > 0) {
1220
+ printSection('Fixes Applied');
1221
+ for (const fix of result.fixes) {
1222
+ console.log(` - ${fix}`);
1223
+ }
1224
+ }
1225
+ printSection('Summary');
1226
+ console.log(` pass=${result.summary.pass} warn=${result.summary.warn} fail=${result.summary.fail}`);
1227
+ if (options.strict) console.log('Mode: strict');
1228
+ }
1229
+
1230
+ process.exit(mapExitCode(result));
1231
+ } catch (err) {
1232
+ const message = getErrorMessage(err);
1233
+ if (message.toLowerCase().startsWith('unknown flag:')) {
1234
+ console.error(message);
1235
+ process.exit(EXIT.ARGS);
1236
+ }
1237
+
1238
+ console.error(`Doctor internal error: ${message}`);
1239
+ process.exit(EXIT.INTERNAL);
1240
+ }
1241
+ }
1242
+
1243
+ if (require.main === module) {
1244
+ main();
1245
+ }
1246
+
1247
+ export { runDoctor, parseArgs };