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,387 @@
1
+ 'use client';
2
+
3
+ import React, { useEffect, useMemo, useState } from 'react';
4
+ import { createPortal } from 'react-dom';
5
+ import { Bell, X, Clock } from 'lucide-react';
6
+ import { Drawer, Modal } from '@/components/design-system';
7
+ import type { HumanAction } from '@/hooks/useAgentActions';
8
+
9
+ interface NotificationDrawerProps {
10
+ notifications: HumanAction[];
11
+ onDismiss: (id: string) => void;
12
+ /** Render only the bell trigger (compact mode for tablet sidebar) */
13
+ compact?: boolean;
14
+ }
15
+
16
+ interface NotificationMeta {
17
+ summary?: string;
18
+ agentId?: string;
19
+ limit?: number;
20
+ requestedLimitExplicit?: boolean;
21
+ defaultFundLimit?: number;
22
+ verifiedSummary?: { oneLiner?: string };
23
+ }
24
+
25
+ function normalizeNotificationId(id: unknown): string {
26
+ if (typeof id === 'string') return id.trim();
27
+ if (typeof id === 'number') return String(id);
28
+ return '';
29
+ }
30
+
31
+ function timeAgo(dateStr: string): string {
32
+ const diff = Date.now() - new Date(dateStr).getTime();
33
+ const mins = Math.floor(diff / 60000);
34
+ if (mins < 1) return 'just now';
35
+ if (mins < 60) return `${mins}m ago`;
36
+ const hrs = Math.floor(mins / 60);
37
+ if (hrs < 24) return `${hrs}h ago`;
38
+ return `${Math.floor(hrs / 24)}d ago`;
39
+ }
40
+
41
+ function parseMeta(raw?: string): NotificationMeta {
42
+ if (!raw) return {};
43
+ try {
44
+ const parsed = JSON.parse(raw);
45
+ return parsed && typeof parsed === 'object' ? parsed as NotificationMeta : {};
46
+ } catch {
47
+ return {};
48
+ }
49
+ }
50
+
51
+ function getNotificationSummary(n: HumanAction): string {
52
+ const meta = parseMeta(n.metadata);
53
+ if (meta.verifiedSummary?.oneLiner) return meta.verifiedSummary.oneLiner;
54
+ if (meta.summary) return meta.summary;
55
+
56
+ if ((n.type === 'auth' || n.type === 'agent_access') && typeof meta.limit === 'number') {
57
+ const agent = meta.agentId || 'agent';
58
+ if (meta.requestedLimitExplicit === false) {
59
+ return `${agent} requesting access`;
60
+ }
61
+ return `${agent} requesting ${meta.limit} ETH access`;
62
+ }
63
+
64
+ return n.type;
65
+ }
66
+
67
+ function notificationStatusLabel(n: HumanAction): string {
68
+ if (n.status === 'pending') return 'PENDING';
69
+ if (n.status === 'approved') return 'APPROVED';
70
+ if (n.status === 'rejected') return 'REJECTED';
71
+ if (n.type === 'notify') return 'ALERT';
72
+ return 'INFO';
73
+ }
74
+
75
+ function notificationStatusColors(n: HumanAction): { bg: string; fg: string; border: string } {
76
+ if (n.status === 'pending') {
77
+ return {
78
+ bg: 'color-mix(in srgb, var(--color-warning,#ff4d00) 15%, transparent)',
79
+ fg: 'var(--color-warning,#ff4d00)',
80
+ border: 'color-mix(in srgb, var(--color-warning,#ff4d00) 35%, transparent)',
81
+ };
82
+ }
83
+ if (n.status === 'approved') {
84
+ return {
85
+ bg: 'color-mix(in srgb, var(--color-success,#10b981) 15%, transparent)',
86
+ fg: 'var(--color-success,#10b981)',
87
+ border: 'color-mix(in srgb, var(--color-success,#10b981) 35%, transparent)',
88
+ };
89
+ }
90
+ if (n.status === 'rejected') {
91
+ return {
92
+ bg: 'color-mix(in srgb, var(--color-danger,#ef4444) 15%, transparent)',
93
+ fg: 'var(--color-danger,#ef4444)',
94
+ border: 'color-mix(in srgb, var(--color-danger,#ef4444) 35%, transparent)',
95
+ };
96
+ }
97
+ return {
98
+ bg: 'var(--color-background-alt,#f4f4f5)',
99
+ fg: 'var(--color-text-muted,#6b7280)',
100
+ border: 'var(--color-border,#d4d4d8)',
101
+ };
102
+ }
103
+
104
+ export const NotificationDrawer: React.FC<NotificationDrawerProps> = ({
105
+ notifications,
106
+ onDismiss,
107
+ compact = false,
108
+ }) => {
109
+ const [open, setOpen] = useState(false);
110
+ const [mounted, setMounted] = useState(false);
111
+ const [locallyDismissedKeys, setLocallyDismissedKeys] = useState<Set<string>>(new Set());
112
+ const [selectedNotification, setSelectedNotification] = useState<HumanAction | null>(null);
113
+
114
+ const getNotificationKey = (notification: HumanAction): string => {
115
+ const normalizedId = normalizeNotificationId(notification.id);
116
+ if (normalizedId) return `id:${normalizedId}`;
117
+ return `fallback:${notification.type}:${notification.createdAt}:${getNotificationSummary(notification)}`;
118
+ };
119
+
120
+ const visibleNotifications = useMemo(
121
+ () => notifications.filter((notification) => !locallyDismissedKeys.has(getNotificationKey(notification))),
122
+ [notifications, locallyDismissedKeys],
123
+ );
124
+
125
+ const pendingBadgeCount = useMemo(
126
+ () => visibleNotifications.filter((notification) => notification.status === 'pending' && notification.type !== 'notify').length,
127
+ [visibleNotifications],
128
+ );
129
+
130
+ useEffect(() => {
131
+ setMounted(true);
132
+ return () => setMounted(false);
133
+ }, []);
134
+
135
+ return (
136
+ <>
137
+ {/* Bell trigger */}
138
+ <div className="relative">
139
+ <button
140
+ type="button"
141
+ onClick={() => setOpen((prev) => !prev)}
142
+ className={`${compact ? 'p-0' : 'p-1.5'} text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] hover:bg-[var(--color-surface,#ffffff)]/50 transition-colors rounded`}
143
+ title="Notifications"
144
+ aria-label="Notifications"
145
+ >
146
+ <Bell size={compact ? 10 : 14} />
147
+ </button>
148
+ {pendingBadgeCount > 0 && (
149
+ <span className="absolute -top-0.5 -right-0.5 min-w-[14px] h-[14px] bg-[var(--color-warning,#ff4d00)] text-white font-mono text-[7px] font-bold flex items-center justify-center px-0.5">
150
+ {pendingBadgeCount > 9 ? '9+' : pendingBadgeCount}
151
+ </span>
152
+ )}
153
+ </div>
154
+
155
+ {/* Drawer */}
156
+ {mounted && createPortal(
157
+ <>
158
+ {open && (
159
+ <button
160
+ type="button"
161
+ aria-label="Close notifications"
162
+ onClick={() => setOpen(false)}
163
+ className="fixed inset-0 z-30 bg-[var(--color-text,#0a0a0a)]/20"
164
+ />
165
+ )}
166
+ <div className="fixed inset-y-0 right-0 z-40">
167
+ <Drawer
168
+ isOpen={open}
169
+ onClose={() => setOpen(false)}
170
+ title="NOTIFICATIONS"
171
+ subtitle="Approvals + alerts"
172
+ width="sm"
173
+ >
174
+ {visibleNotifications.length === 0 ? (
175
+ <div className="py-8 text-center tyvek-label corner-marks mx-2">
176
+ <Bell size={24} className="mx-auto mb-2 text-[var(--color-text-faint,#9ca3af)]" />
177
+ <div className="label-specimen text-[var(--color-text-muted,#6b7280)]">NO ALERTS</div>
178
+ </div>
179
+ ) : (
180
+ <div className="space-y-2">
181
+ {visibleNotifications.map((n) => {
182
+ return (
183
+ <div
184
+ key={getNotificationKey(n)}
185
+ className="w-full text-left flex items-start gap-2 p-3 clip-specimen-sm border-mech bg-[var(--color-surface-alt,#fafafa)] group cursor-pointer corner-marks transition-all hover:shadow-mech-hover"
186
+ >
187
+ <button
188
+ type="button"
189
+ onClick={() => setSelectedNotification(n)}
190
+ className="flex-1 min-w-0 text-left"
191
+ title="View details"
192
+ >
193
+ <div className="mb-1.5">
194
+ <span
195
+ className="inline-flex items-center px-1.5 py-0.5 font-mono text-[7px] font-bold tracking-[0.15em] uppercase"
196
+ style={{
197
+ background: notificationStatusColors(n).bg,
198
+ color: notificationStatusColors(n).fg,
199
+ border: `1px solid ${notificationStatusColors(n).border}`,
200
+ }}
201
+ >
202
+ {notificationStatusLabel(n)}
203
+ </span>
204
+ </div>
205
+ <div className="font-mono text-[10px] text-[var(--color-text,#0a0a0a)]">
206
+ {getNotificationSummary(n)}
207
+ </div>
208
+ <div className="font-mono text-[8px] text-[var(--color-text-faint,#9ca3af)] flex items-center gap-1 mt-1">
209
+ <Clock size={7} />
210
+ {timeAgo(n.createdAt)}
211
+ </div>
212
+ </button>
213
+ <button
214
+ type="button"
215
+ onClick={() => {
216
+ const notificationKey = getNotificationKey(n);
217
+ setLocallyDismissedKeys((prev) => {
218
+ const next = new Set(prev);
219
+ next.add(notificationKey);
220
+ return next;
221
+ });
222
+ const normalizedId = normalizeNotificationId(n.id);
223
+ if (normalizedId) {
224
+ onDismiss(normalizedId);
225
+ }
226
+ }}
227
+ className="p-1 hover:bg-[var(--color-background-alt,#e5e5e5)] transition-colors shrink-0"
228
+ title="Dismiss"
229
+ >
230
+ <X size={10} className="text-[var(--color-text-muted,#6b7280)]" />
231
+ </button>
232
+ </div>
233
+ );
234
+ })}
235
+ </div>
236
+ )}
237
+ </Drawer>
238
+ </div>
239
+ </>,
240
+ document.body,
241
+ )}
242
+
243
+ {/* Detail Modal */}
244
+ <NotificationDetailModal
245
+ notification={selectedNotification}
246
+ onClose={() => setSelectedNotification(null)}
247
+ />
248
+ </>
249
+ );
250
+ };
251
+
252
+ function NotificationDetailModal({ notification, onClose }: { notification: HumanAction | null; onClose: () => void }) {
253
+ // Keep a ref to the last notification so content stays visible during exit animation
254
+ const [lastNotification, setLastNotification] = React.useState<HumanAction | null>(null);
255
+ React.useEffect(() => {
256
+ if (notification) setLastNotification(notification);
257
+ }, [notification]);
258
+
259
+ const display = notification || lastNotification;
260
+ const meta = display ? parseMeta(display.metadata) : {};
261
+ const summary = display?.humanSummary;
262
+ const colors = display ? notificationStatusColors(display) : { bg: '', fg: '', border: '' };
263
+ const agentId = meta.agentId || 'unknown';
264
+
265
+ return (
266
+ <Modal
267
+ isOpen={!!notification}
268
+ onClose={onClose}
269
+ title={summary?.actionLabel || display?.type.toUpperCase() || 'DETAILS'}
270
+ size="sm"
271
+ >
272
+ <div className="space-y-4">
273
+ {/* Status + Time */}
274
+ {display && (
275
+ <div className="flex items-center justify-between">
276
+ <span
277
+ className="inline-flex items-center px-1.5 py-0.5 font-mono text-[7px] font-bold tracking-[0.15em] uppercase"
278
+ style={{
279
+ background: colors.bg,
280
+ color: colors.fg,
281
+ border: `1px solid ${colors.border}`,
282
+ }}
283
+ >
284
+ {notificationStatusLabel(display)}
285
+ </span>
286
+ <span className="font-mono text-[8px] text-[var(--color-text-faint,#9ca3af)] flex items-center gap-1">
287
+ <Clock size={7} />
288
+ {timeAgo(display.createdAt)}
289
+ </span>
290
+ </div>
291
+ )}
292
+
293
+ {/* One-liner */}
294
+ {summary?.oneLiner && (
295
+ <div className="text-[10px] text-[var(--color-text-muted,#6b7280)] tracking-wider">
296
+ {summary.oneLiner}
297
+ </div>
298
+ )}
299
+
300
+ {/* Details */}
301
+ <div className="space-y-2">
302
+ <div className="flex items-baseline justify-between gap-2">
303
+ <span className="text-[8px] text-[var(--color-text-faint,#9ca3af)] tracking-widest uppercase">Agent</span>
304
+ <span className="text-[10px] text-[var(--color-text,#0a0a0a)] tracking-wider text-right">{agentId}</span>
305
+ </div>
306
+ <div className="flex items-baseline justify-between gap-2">
307
+ <span className="text-[8px] text-[var(--color-text-faint,#9ca3af)] tracking-widest uppercase">Type</span>
308
+ <span className="text-[10px] text-[var(--color-text,#0a0a0a)] font-bold tracking-wider text-right">{display?.type}</span>
309
+ </div>
310
+ {summary?.profileLabel && (
311
+ <div className="flex items-baseline justify-between gap-2">
312
+ <span className="text-[8px] text-[var(--color-text-faint,#9ca3af)] tracking-widest uppercase">Profile</span>
313
+ <span className="text-[10px] text-[var(--color-text,#0a0a0a)] tracking-wider text-right">{summary.profileLabel}</span>
314
+ </div>
315
+ )}
316
+ {summary?.riskHint && (
317
+ <div className="flex items-baseline justify-between gap-2">
318
+ <span className="text-[8px] text-[var(--color-text-faint,#9ca3af)] tracking-widest uppercase">Risk</span>
319
+ <span className={`text-[10px] font-bold tracking-wider ${
320
+ summary.riskHint.toLowerCase().includes('high') ? 'text-[var(--color-danger,#ef4444)]'
321
+ : summary.riskHint.toLowerCase().includes('medium') ? 'text-[var(--color-warning,#ff4d00)]'
322
+ : 'text-[var(--color-text,#0a0a0a)]'
323
+ }`}>{summary.riskHint}</span>
324
+ </div>
325
+ )}
326
+ {summary?.expiresIn && (
327
+ <div className="flex items-baseline justify-between gap-2">
328
+ <span className="text-[8px] text-[var(--color-text-faint,#9ca3af)] tracking-widest uppercase">Expires</span>
329
+ <span className="text-[10px] text-[var(--color-text,#0a0a0a)] tracking-wider text-right">{summary.expiresIn}</span>
330
+ </div>
331
+ )}
332
+ {typeof meta.limit === 'number' && (
333
+ <div className="flex items-baseline justify-between gap-2">
334
+ <span className="text-[8px] text-[var(--color-text-faint,#9ca3af)] tracking-widest uppercase">Limit</span>
335
+ <span className="text-[10px] text-[var(--color-text,#0a0a0a)] font-bold tracking-wider text-right">{meta.limit} ETH</span>
336
+ </div>
337
+ )}
338
+ <div className="text-[7px] text-[var(--color-text-faint,#9ca3af)] tracking-widest break-all">
339
+ ID: {display?.id}
340
+ </div>
341
+ </div>
342
+
343
+ {/* Permissions (can) */}
344
+ {summary?.can && summary.can.length > 0 && (
345
+ <div className="border-t border-[var(--color-border,#d4d4d8)] pt-3">
346
+ <div className="text-[8px] text-[var(--color-text-faint,#9ca3af)] tracking-widest uppercase mb-1.5">Permissions</div>
347
+ <div className="space-y-1">
348
+ {summary.can.map((item) => (
349
+ <div key={item} className="text-[10px] text-[var(--color-text,#0a0a0a)] tracking-wider pl-2 border-l-2 border-[var(--color-border,#d4d4d8)]">
350
+ {item}
351
+ </div>
352
+ ))}
353
+ </div>
354
+ </div>
355
+ )}
356
+
357
+ {/* Restrictions (cannot) */}
358
+ {summary?.cannot && summary.cannot.length > 0 && (
359
+ <div className="border-t border-[var(--color-border,#d4d4d8)] pt-3">
360
+ <div className="text-[8px] text-[var(--color-text-faint,#9ca3af)] tracking-widest uppercase mb-1.5">Restrictions</div>
361
+ <div className="space-y-1">
362
+ {summary.cannot.map((item) => (
363
+ <div key={item} className="text-[10px] text-[var(--color-danger,#ef4444)] tracking-wider pl-2 border-l-2 border-[var(--color-danger,#ef4444)]/30">
364
+ {item}
365
+ </div>
366
+ ))}
367
+ </div>
368
+ </div>
369
+ )}
370
+
371
+ {/* Scope */}
372
+ {summary?.scope && summary.scope.length > 0 && (
373
+ <div className="border-t border-[var(--color-border,#d4d4d8)] pt-3">
374
+ <div className="text-[8px] text-[var(--color-text-faint,#9ca3af)] tracking-widest uppercase mb-1.5">Scope</div>
375
+ <div className="space-y-1">
376
+ {summary.scope.map((item) => (
377
+ <div key={item} className="text-[10px] text-[var(--color-text,#0a0a0a)] tracking-wider pl-2 border-l-2 border-[var(--color-border,#d4d4d8)]">
378
+ {item}
379
+ </div>
380
+ ))}
381
+ </div>
382
+ </div>
383
+ )}
384
+ </div>
385
+ </Modal>
386
+ );
387
+ }
@@ -0,0 +1,235 @@
1
+ 'use client';
2
+
3
+ import React, { useEffect, useState, useCallback } from 'react';
4
+ import { KeyRound, Loader2, Check } from 'lucide-react';
5
+ import { Modal, Button } from '@/components/design-system';
6
+ import { api, Api } from '@/lib/api';
7
+
8
+ type PromptState = 'hidden' | 'prompt' | 'registering' | 'success' | 'error';
9
+
10
+ const DISMISS_KEY = 'aura:passkey:dismissed';
11
+
12
+ /** Convert base64url string to ArrayBuffer */
13
+ function base64urlToBuffer(b: string): ArrayBuffer {
14
+ let s = b.replace(/-/g, '+').replace(/_/g, '/');
15
+ while (s.length % 4) s += '=';
16
+ const bin = atob(s);
17
+ const a = new Uint8Array(bin.length);
18
+ for (let i = 0; i < bin.length; i++) a[i] = bin.charCodeAt(i);
19
+ return a.buffer;
20
+ }
21
+
22
+ /** Convert ArrayBuffer to base64url string */
23
+ function bufferToBase64url(b: ArrayBuffer): string {
24
+ const bytes = new Uint8Array(b);
25
+ let bin = '';
26
+ for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
27
+ return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
28
+ }
29
+
30
+ interface PasskeyEnrollmentPromptProps {
31
+ isUnlocked: boolean;
32
+ }
33
+
34
+ export function PasskeyEnrollmentPrompt({ isUnlocked }: PasskeyEnrollmentPromptProps) {
35
+ const [promptState, setPromptState] = useState<PromptState>('hidden');
36
+ const [errorMsg, setErrorMsg] = useState('');
37
+
38
+ // Check eligibility on mount / when unlock state changes
39
+ useEffect(() => {
40
+ if (!isUnlocked) return;
41
+ if (typeof window === 'undefined') return;
42
+ if (!window.PublicKeyCredential) return;
43
+ if (localStorage.getItem(DISMISS_KEY) === 'true') return;
44
+
45
+ let cancelled = false;
46
+ (async () => {
47
+ try {
48
+ const status = await api.get<{ registered: boolean; count: number }>(
49
+ Api.Wallet,
50
+ '/auth/passkey/status',
51
+ );
52
+ if (cancelled) return;
53
+ if (!status.registered) {
54
+ setPromptState('prompt');
55
+ }
56
+ } catch {
57
+ // Silently ignore — don't block the user
58
+ }
59
+ })();
60
+ return () => { cancelled = true; };
61
+ }, [isUnlocked]);
62
+
63
+ const handleSetUp = useCallback(async () => {
64
+ setPromptState('registering');
65
+ setErrorMsg('');
66
+ try {
67
+ // 1. Get registration options from server (requires admin token — api helper adds it)
68
+ const options = await api.post<{
69
+ challenge: string;
70
+ rp: { name: string; id: string };
71
+ user: { id: string; name: string; displayName: string };
72
+ pubKeyCredParams: Array<{ type: string; alg: number }>;
73
+ timeout: number;
74
+ attestation: string;
75
+ excludeCredentials: Array<{ id: string; transports?: string[] }>;
76
+ authenticatorSelection: {
77
+ authenticatorAttachment?: string;
78
+ residentKey?: string;
79
+ userVerification?: string;
80
+ };
81
+ }>(Api.Wallet, '/auth/passkey/register/options', {});
82
+
83
+ // 2. Convert server options for navigator.credentials.create()
84
+ const publicKey: PublicKeyCredentialCreationOptions = {
85
+ challenge: base64urlToBuffer(options.challenge),
86
+ rp: { name: options.rp.name, id: options.rp.id },
87
+ user: {
88
+ id: base64urlToBuffer(options.user.id),
89
+ name: options.user.name,
90
+ displayName: options.user.displayName,
91
+ },
92
+ pubKeyCredParams: options.pubKeyCredParams.map((p) => ({
93
+ type: p.type as 'public-key',
94
+ alg: p.alg,
95
+ })),
96
+ timeout: options.timeout,
97
+ attestation: (options.attestation || 'none') as AttestationConveyancePreference,
98
+ excludeCredentials: (options.excludeCredentials || []).map((c) => ({
99
+ type: 'public-key' as const,
100
+ id: base64urlToBuffer(c.id),
101
+ transports: c.transports as AuthenticatorTransport[] | undefined,
102
+ })),
103
+ authenticatorSelection: {
104
+ authenticatorAttachment: options.authenticatorSelection?.authenticatorAttachment as AuthenticatorAttachment | undefined,
105
+ residentKey: options.authenticatorSelection?.residentKey as ResidentKeyRequirement | undefined,
106
+ userVerification: options.authenticatorSelection?.userVerification as UserVerificationRequirement | undefined,
107
+ },
108
+ };
109
+
110
+ // 3. Call WebAuthn create
111
+ const credential = await navigator.credentials.create({ publicKey }) as PublicKeyCredential | null;
112
+ if (!credential) {
113
+ setPromptState('prompt');
114
+ return;
115
+ }
116
+
117
+ const response = credential.response as AuthenticatorAttestationResponse;
118
+
119
+ // 4. Send credential to server for verification
120
+ const verifyPayload = {
121
+ credential: {
122
+ id: bufferToBase64url(credential.rawId),
123
+ rawId: bufferToBase64url(credential.rawId),
124
+ type: credential.type,
125
+ response: {
126
+ clientDataJSON: bufferToBase64url(response.clientDataJSON),
127
+ attestationObject: bufferToBase64url(response.attestationObject),
128
+ transports: response.getTransports?.() || [],
129
+ },
130
+ },
131
+ };
132
+
133
+ const result = await api.post<{ success: boolean; error?: string }>(
134
+ Api.Wallet,
135
+ '/auth/passkey/register/verify',
136
+ verifyPayload,
137
+ );
138
+
139
+ if (result.success) {
140
+ setPromptState('success');
141
+ // Auto-dismiss after 2s
142
+ setTimeout(() => setPromptState('hidden'), 2000);
143
+ } else {
144
+ setErrorMsg(result.error || 'Registration failed');
145
+ setPromptState('error');
146
+ }
147
+ } catch (err) {
148
+ if (err instanceof Error && err.name === 'NotAllowedError') {
149
+ // User cancelled the WebAuthn prompt
150
+ setPromptState('prompt');
151
+ return;
152
+ }
153
+ setErrorMsg(err instanceof Error ? err.message : 'Registration failed');
154
+ setPromptState('error');
155
+ }
156
+ }, []);
157
+
158
+ const handleNotNow = useCallback(() => {
159
+ setPromptState('hidden');
160
+ }, []);
161
+
162
+ const handleDontAskAgain = useCallback(() => {
163
+ localStorage.setItem(DISMISS_KEY, 'true');
164
+ setPromptState('hidden');
165
+ }, []);
166
+
167
+ const isOpen = promptState !== 'hidden';
168
+
169
+ return (
170
+ <Modal
171
+ isOpen={isOpen}
172
+ onClose={handleNotNow}
173
+ title="Passkey Setup"
174
+ subtitle="Security"
175
+ icon={<KeyRound size={16} className="text-[var(--color-accent,#ccff00)]" />}
176
+ size="sm"
177
+ >
178
+ {promptState === 'success' ? (
179
+ <div className="flex flex-col items-center gap-3 py-4">
180
+ <div className="w-10 h-10 bg-[var(--color-text,#0a0a0a)] flex items-center justify-center">
181
+ <Check size={20} className="text-[var(--color-accent,#ccff00)]" />
182
+ </div>
183
+ <div className="font-mono text-xs tracking-widest text-[var(--color-text,#0a0a0a)] uppercase">
184
+ Passkey enabled!
185
+ </div>
186
+ <div className="font-mono text-[10px] text-[var(--color-text-muted,#6b7280)]">
187
+ You can now unlock with Face ID / biometrics.
188
+ </div>
189
+ </div>
190
+ ) : (
191
+ <div className="space-y-4">
192
+ <div className="font-mono text-xs text-[var(--color-text,#0a0a0a)] leading-relaxed">
193
+ Enable Face ID / Passkey to unlock your vault without typing a password.
194
+ </div>
195
+
196
+ {promptState === 'error' && errorMsg && (
197
+ <div className="p-2 border border-[var(--color-warning,#ff4d00)]/30 bg-[var(--color-warning,#ff4d00)]/5">
198
+ <div className="font-mono text-[10px] text-[var(--color-warning,#ff4d00)]">
199
+ {errorMsg}
200
+ </div>
201
+ </div>
202
+ )}
203
+
204
+ <div className="flex flex-col gap-2">
205
+ <Button
206
+ variant="primary"
207
+ size="md"
208
+ loading={promptState === 'registering'}
209
+ onClick={handleSetUp}
210
+ disabled={promptState === 'registering'}
211
+ icon={<KeyRound size={14} />}
212
+ >
213
+ {promptState === 'registering' ? 'REGISTERING...' : 'SET UP'}
214
+ </Button>
215
+
216
+ <div className="flex items-center justify-between pt-1">
217
+ <button
218
+ onClick={handleNotNow}
219
+ className="font-mono text-[10px] tracking-widest text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] transition-colors"
220
+ >
221
+ NOT NOW
222
+ </button>
223
+ <button
224
+ onClick={handleDontAskAgain}
225
+ className="font-mono text-[10px] tracking-widest text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] transition-colors"
226
+ >
227
+ DON&apos;T ASK AGAIN
228
+ </button>
229
+ </div>
230
+ </div>
231
+ </div>
232
+ )}
233
+ </Modal>
234
+ );
235
+ }