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,578 @@
1
+ 'use client';
2
+
3
+ import React, { useState, useRef, useCallback, useMemo } from 'react';
4
+ import { Upload, FileText, AlertCircle, CheckCircle } from 'lucide-react';
5
+ import { Modal, Button, ItemPicker, FilterDropdown } from '@/components/design-system';
6
+
7
+ // ---------------------------------------------------------------------------
8
+ // Types
9
+ // ---------------------------------------------------------------------------
10
+
11
+ interface ImportSource {
12
+ label: string;
13
+ format: string;
14
+ supported: boolean;
15
+ }
16
+
17
+ const IMPORT_SOURCES: ImportSource[] = [
18
+ { label: '1Password', format: '1password-csv', supported: true },
19
+ { label: 'Bitwarden', format: 'bitwarden-csv', supported: true },
20
+ { label: 'Chrome', format: 'chrome-csv', supported: true },
21
+ { label: 'Firefox', format: 'firefox-csv', supported: true },
22
+ { label: 'iCloud Keychain', format: 'icloud-csv', supported: true },
23
+ { label: 'LastPass', format: 'lastpass-csv', supported: true },
24
+ ];
25
+
26
+ type DuplicateStrategy = 'skip' | 'rename' | 'overwrite';
27
+
28
+ const STRATEGY_LABELS: Record<DuplicateStrategy, string> = {
29
+ skip: 'Skip',
30
+ rename: 'Rename',
31
+ overwrite: 'Create Anyway',
32
+ };
33
+
34
+ interface PreviewCredential {
35
+ name: string;
36
+ type: string;
37
+ url?: string;
38
+ fieldCount: number;
39
+ isDuplicate: boolean;
40
+ duplicateMatch?: string;
41
+ }
42
+
43
+ interface PreviewResult {
44
+ success: boolean;
45
+ total: number;
46
+ duplicates: number;
47
+ credentials: PreviewCredential[];
48
+ error?: string;
49
+ }
50
+
51
+ interface ImportResult {
52
+ success: boolean;
53
+ imported: number;
54
+ skipped: number;
55
+ errors: { row: number; reason: string }[];
56
+ error?: string;
57
+ }
58
+
59
+ type Step = 'select' | 'preview' | 'result';
60
+
61
+ interface ImportCredentialsModalProps {
62
+ isOpen: boolean;
63
+ onClose: () => void;
64
+ onComplete: () => void;
65
+ vaults: Array<{
66
+ id: string;
67
+ name?: string;
68
+ isPrimary: boolean;
69
+ isUnlocked: boolean;
70
+ }>;
71
+ selectedVaultId: string;
72
+ onSelectedVaultIdChange: (vaultId: string) => void;
73
+ onAddVault: () => void;
74
+ walletBaseUrl: string;
75
+ }
76
+
77
+ // ---------------------------------------------------------------------------
78
+ // Helpers
79
+ // ---------------------------------------------------------------------------
80
+
81
+ function getToken(): string | null {
82
+ if (typeof window === 'undefined') return null;
83
+ const localToken = localStorage.getItem('auramaxx_admin_token');
84
+ if (localToken) return localToken;
85
+ return sessionStorage.getItem('auramaxx_admin_token');
86
+ }
87
+
88
+ // ---------------------------------------------------------------------------
89
+ // Component
90
+ // ---------------------------------------------------------------------------
91
+
92
+ export const ImportCredentialsModal: React.FC<ImportCredentialsModalProps> = ({
93
+ isOpen,
94
+ onClose,
95
+ onComplete,
96
+ vaults,
97
+ selectedVaultId,
98
+ onSelectedVaultIdChange,
99
+ onAddVault,
100
+ walletBaseUrl,
101
+ }) => {
102
+ const [step, setStep] = useState<Step>('select');
103
+ const [selectedFormat, setSelectedFormat] = useState<string>('1password-csv');
104
+ const [file, setFile] = useState<File | null>(null);
105
+ const [duplicateStrategy, setDuplicateStrategy] = useState<DuplicateStrategy>('skip');
106
+ const [preview, setPreview] = useState<PreviewResult | null>(null);
107
+ const [result, setResult] = useState<ImportResult | null>(null);
108
+ const [loading, setLoading] = useState(false);
109
+ const [error, setError] = useState<string | null>(null);
110
+ const fileInputRef = useRef<HTMLInputElement>(null);
111
+
112
+ const vaultOptions = useMemo(
113
+ () => vaults.map((vault) => ({
114
+ value: vault.id,
115
+ label: vault.name || (vault.isPrimary ? 'Primary' : `Vault ${vault.id.slice(0, 6)}`),
116
+ })),
117
+ [vaults],
118
+ );
119
+
120
+ const selectedVault = useMemo(
121
+ () => vaults.find((vault) => vault.id === selectedVaultId) || null,
122
+ [vaults, selectedVaultId],
123
+ );
124
+
125
+ const reset = useCallback(() => {
126
+ setStep('select');
127
+ setSelectedFormat('1password-csv');
128
+ setFile(null);
129
+ setDuplicateStrategy('skip');
130
+ setPreview(null);
131
+ setResult(null);
132
+ setLoading(false);
133
+ setError(null);
134
+ if (fileInputRef.current) fileInputRef.current.value = '';
135
+ }, []);
136
+
137
+ const handleClose = useCallback(() => {
138
+ reset();
139
+ onClose();
140
+ }, [reset, onClose]);
141
+
142
+ const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
143
+
144
+ const handleFileChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
145
+ const f = e.target.files?.[0] ?? null;
146
+ if (f && f.size > MAX_FILE_SIZE) {
147
+ setError(`File too large (${(f.size / 1024 / 1024).toFixed(1)}MB). Maximum is 10MB.`);
148
+ setFile(null);
149
+ if (fileInputRef.current) fileInputRef.current.value = '';
150
+ return;
151
+ }
152
+ setFile(f);
153
+ setError(null);
154
+ }, []);
155
+
156
+ const buildFormData = useCallback(
157
+ (dryRun: boolean) => {
158
+ if (!file) return null;
159
+ const fd = new FormData();
160
+ fd.append('file', file);
161
+ fd.append('format', selectedFormat);
162
+ fd.append('vaultId', selectedVaultId || 'primary');
163
+ if (dryRun) fd.append('dryRun', 'true');
164
+ fd.append('duplicateStrategy', duplicateStrategy);
165
+ return fd;
166
+ },
167
+ [file, selectedFormat, selectedVaultId, duplicateStrategy],
168
+ );
169
+
170
+ const handlePreview = useCallback(async () => {
171
+ if (!file) return;
172
+ setLoading(true);
173
+ setError(null);
174
+ try {
175
+ const fd = buildFormData(true);
176
+ if (!fd) return;
177
+ const token = getToken();
178
+ const res = await fetch(`${walletBaseUrl}/credentials/import`, {
179
+ method: 'POST',
180
+ headers: token ? { Authorization: `Bearer ${token}` } : {},
181
+ body: fd,
182
+ });
183
+ const contentType = res.headers.get('content-type') || '';
184
+ if (!contentType.includes('application/json')) {
185
+ setError(`Server error: ${res.status} ${res.statusText}`);
186
+ return;
187
+ }
188
+ const data: PreviewResult = await res.json();
189
+ if (!res.ok || !data.success) {
190
+ setError(data.error || `Request failed: ${res.status}`);
191
+ return;
192
+ }
193
+ setPreview(data);
194
+ setStep('preview');
195
+ } catch (err) {
196
+ setError(err instanceof Error ? err.message : 'Preview failed');
197
+ } finally {
198
+ setLoading(false);
199
+ }
200
+ }, [file, buildFormData, walletBaseUrl]);
201
+
202
+ const handleImport = useCallback(async () => {
203
+ setLoading(true);
204
+ setError(null);
205
+ try {
206
+ const fd = buildFormData(false);
207
+ if (!fd) return;
208
+ const token = getToken();
209
+ const res = await fetch(`${walletBaseUrl}/credentials/import`, {
210
+ method: 'POST',
211
+ headers: token ? { Authorization: `Bearer ${token}` } : {},
212
+ body: fd,
213
+ });
214
+ const ct = res.headers.get('content-type') || '';
215
+ if (!ct.includes('application/json')) {
216
+ setError(`Server error: ${res.status} ${res.statusText}`);
217
+ return;
218
+ }
219
+ const data: ImportResult = await res.json();
220
+ if (!res.ok) {
221
+ setError(data.error || `Request failed: ${res.status}`);
222
+ return;
223
+ }
224
+ setResult(data);
225
+ setStep('result');
226
+ } catch (err) {
227
+ setError(err instanceof Error ? err.message : 'Import failed');
228
+ } finally {
229
+ setLoading(false);
230
+ }
231
+ }, [buildFormData, walletBaseUrl]);
232
+
233
+ const handleDone = useCallback(() => {
234
+ onComplete();
235
+ handleClose();
236
+ }, [onComplete, handleClose]);
237
+
238
+ // -------------------------------------------------------------------------
239
+ // Render helpers
240
+ // -------------------------------------------------------------------------
241
+
242
+ const selectedSource = IMPORT_SOURCES.find((s) => s.format === selectedFormat);
243
+
244
+ const renderSelectStep = () => (
245
+ <div className="space-y-5">
246
+ {/* Source selector */}
247
+ <div>
248
+ <label className="block text-[9px] font-bold tracking-widest uppercase text-[var(--color-text-muted,#6b7280)] mb-2">
249
+ Source
250
+ </label>
251
+ <ItemPicker
252
+ ariaLabel="Import source"
253
+ value={selectedFormat}
254
+ onChange={(value: string) => {
255
+ const source = IMPORT_SOURCES.find((s) => s.format === value);
256
+ if (source?.supported) setSelectedFormat(source.format);
257
+ }}
258
+ options={IMPORT_SOURCES.map((source) => ({
259
+ value: source.format,
260
+ label: source.label,
261
+ description: source.supported ? undefined : 'Coming soon',
262
+ icon: <FileText size={12} />,
263
+ }))}
264
+ />
265
+ </div>
266
+
267
+ {/* File input */}
268
+ <div>
269
+ <label className="block text-[9px] font-bold tracking-widest uppercase text-[var(--color-text-muted,#6b7280)] mb-2">
270
+ File
271
+ </label>
272
+ <input
273
+ ref={fileInputRef}
274
+ type="file"
275
+ accept=".csv"
276
+ onChange={handleFileChange}
277
+ className="hidden"
278
+ />
279
+ <button
280
+ onClick={() => fileInputRef.current?.click()}
281
+ className="w-full flex items-center justify-center gap-2 px-4 py-4 border-2 border-dashed border-[var(--color-border,#d4d4d8)] hover:border-[var(--color-text-muted,#6b7280)] transition-colors"
282
+ >
283
+ <Upload size={14} className="text-[var(--color-text-muted,#6b7280)]" />
284
+ <span className="text-[10px] font-mono tracking-wider text-[var(--color-text-muted,#6b7280)]">
285
+ {file ? file.name : 'Choose CSV file'}
286
+ </span>
287
+ </button>
288
+ </div>
289
+
290
+ {/* Vault target */}
291
+ <div>
292
+ <div className="flex items-end gap-2">
293
+ <div className="flex-1">
294
+ <FilterDropdown
295
+ label="Target Vault"
296
+ ariaLabel="TARGET VAULT"
297
+ options={vaultOptions}
298
+ value={selectedVaultId}
299
+ onChange={onSelectedVaultIdChange}
300
+ compact
301
+ searchable
302
+ menuPosition="top"
303
+ searchPlaceholder="Search vault..."
304
+ emptyMessage="No vault matches"
305
+ />
306
+ </div>
307
+ <Button
308
+ type="button"
309
+ variant="secondary"
310
+ size="sm"
311
+ onClick={onAddVault}
312
+ className="!h-[var(--control-height-sm)] !px-3"
313
+ >
314
+ ADD
315
+ </Button>
316
+ </div>
317
+ {selectedVault && !selectedVault.isUnlocked && (
318
+ <div className="mt-2 text-[9px] text-[var(--color-warning,#ff4d00)] border border-[var(--color-warning,#ff4d00)]/30 bg-[var(--color-warning,#ff4d00)]/10 px-3 py-2">
319
+ Selected vault is locked. Unlock it before preview/import.
320
+ </div>
321
+ )}
322
+ </div>
323
+
324
+ {/* Error */}
325
+ {error && (
326
+ <div className="flex items-start gap-2 px-3 py-2 bg-red-50 border border-red-200">
327
+ <AlertCircle size={12} className="text-red-500 flex-shrink-0 mt-0.5" />
328
+ <span className="text-[10px] font-mono text-red-700">{error}</span>
329
+ </div>
330
+ )}
331
+
332
+ </div>
333
+ );
334
+
335
+ const renderPreviewStep = () => {
336
+ if (!preview) return null;
337
+ return (
338
+ <div className="space-y-4">
339
+ {/* Summary */}
340
+ <div className="flex gap-4">
341
+ <div className="text-center">
342
+ <div className="text-[18px] font-bold font-mono text-[var(--color-text,#0a0a0a)]">
343
+ {preview.total}
344
+ </div>
345
+ <div className="text-[8px] font-mono tracking-widest uppercase text-[var(--color-text-muted,#6b7280)]">
346
+ Total
347
+ </div>
348
+ </div>
349
+ {preview.duplicates > 0 && (
350
+ <div className="text-center">
351
+ <div className="text-[18px] font-bold font-mono text-amber-600">
352
+ {preview.duplicates}
353
+ </div>
354
+ <div className="text-[8px] font-mono tracking-widest uppercase text-[var(--color-text-muted,#6b7280)]">
355
+ Duplicates
356
+ </div>
357
+ </div>
358
+ )}
359
+ </div>
360
+
361
+ {/* Preview list */}
362
+ <div className="max-h-[240px] overflow-y-auto border border-[var(--color-border,#d4d4d8)]">
363
+ {preview.credentials.length === 0 ? (
364
+ <div className="px-4 py-8 text-center text-[10px] font-mono text-[var(--color-text-muted,#6b7280)]">
365
+ No credentials found in file
366
+ </div>
367
+ ) : (
368
+ preview.credentials.map((c, i) => (
369
+ <div
370
+ key={i}
371
+ className={`flex items-center gap-2 px-3 py-1.5 border-b border-[var(--color-border,#d4d4d8)] last:border-b-0 ${
372
+ c.isDuplicate ? 'bg-amber-50/50' : ''
373
+ }`}
374
+ >
375
+ <div className="flex-1 min-w-0">
376
+ <div className="text-[10px] font-mono text-[var(--color-text,#0a0a0a)] truncate">
377
+ {c.name}
378
+ </div>
379
+ {c.url && (
380
+ <div className="text-[8px] font-mono text-[var(--color-text-faint,#9ca3af)] truncate">
381
+ {c.url}
382
+ </div>
383
+ )}
384
+ </div>
385
+ <span className="text-[8px] font-mono tracking-wider uppercase text-[var(--color-text-faint,#9ca3af)] flex-shrink-0">
386
+ {c.type}
387
+ </span>
388
+ {c.isDuplicate && (
389
+ <span className="text-[8px] font-mono tracking-wider uppercase text-amber-600 flex-shrink-0">
390
+ DUP
391
+ </span>
392
+ )}
393
+ </div>
394
+ ))
395
+ )}
396
+ {preview.total > preview.credentials.length && (
397
+ <div className="px-3 py-1.5 text-[9px] font-mono text-[var(--color-text-faint,#9ca3af)] text-center">
398
+ + {preview.total - preview.credentials.length} more
399
+ </div>
400
+ )}
401
+ </div>
402
+
403
+ {/* Duplicate strategy */}
404
+ {preview.duplicates > 0 && (
405
+ <div>
406
+ <label className="block text-[9px] font-bold tracking-widest uppercase text-[var(--color-text-muted,#6b7280)] mb-1.5">
407
+ Duplicate handling
408
+ </label>
409
+ <div className="flex gap-1.5">
410
+ {(['skip', 'rename', 'overwrite'] as DuplicateStrategy[]).map((s) => (
411
+ <button
412
+ key={s}
413
+ onClick={() => setDuplicateStrategy(s)}
414
+ className={`px-3 py-1.5 text-[9px] font-mono tracking-wider uppercase border transition-colors ${
415
+ duplicateStrategy === s
416
+ ? 'border-[var(--color-accent,#ccff00)] bg-[var(--color-accent,#ccff00)]/10 text-[var(--color-text,#0a0a0a)]'
417
+ : 'border-[var(--color-border,#d4d4d8)] text-[var(--color-text-muted,#6b7280)] hover:bg-[var(--color-background-alt,#f4f4f5)]'
418
+ }`}
419
+ >
420
+ {STRATEGY_LABELS[s]}
421
+ </button>
422
+ ))}
423
+ </div>
424
+ </div>
425
+ )}
426
+
427
+ {/* Error */}
428
+ {error && (
429
+ <div className="flex items-start gap-2 px-3 py-2 bg-red-50 border border-red-200">
430
+ <AlertCircle size={12} className="text-red-500 flex-shrink-0 mt-0.5" />
431
+ <span className="text-[10px] font-mono text-red-700">{error}</span>
432
+ </div>
433
+ )}
434
+
435
+ </div>
436
+ );
437
+ };
438
+
439
+ const renderResultStep = () => {
440
+ if (!result) return null;
441
+ const hasErrors = result.errors.length > 0;
442
+ return (
443
+ <div className="space-y-4">
444
+ {/* Result icon */}
445
+ <div className="flex items-center gap-3">
446
+ {result.success ? (
447
+ <CheckCircle size={20} className="text-green-600" />
448
+ ) : (
449
+ <AlertCircle size={20} className="text-red-500" />
450
+ )}
451
+ <div>
452
+ <div className="text-[11px] font-mono font-bold text-[var(--color-text,#0a0a0a)]">
453
+ {result.success ? 'Import Complete' : 'Import Failed'}
454
+ </div>
455
+ </div>
456
+ </div>
457
+
458
+ {/* Counts */}
459
+ <div className="flex gap-6">
460
+ <div className="text-center">
461
+ <div className="text-[18px] font-bold font-mono text-green-600">{result.imported}</div>
462
+ <div className="text-[8px] font-mono tracking-widest uppercase text-[var(--color-text-muted,#6b7280)]">
463
+ Imported
464
+ </div>
465
+ </div>
466
+ {result.skipped > 0 && (
467
+ <div className="text-center">
468
+ <div className="text-[18px] font-bold font-mono text-[var(--color-text-muted,#6b7280)]">
469
+ {result.skipped}
470
+ </div>
471
+ <div className="text-[8px] font-mono tracking-widest uppercase text-[var(--color-text-muted,#6b7280)]">
472
+ Skipped
473
+ </div>
474
+ </div>
475
+ )}
476
+ {hasErrors && (
477
+ <div className="text-center">
478
+ <div className="text-[18px] font-bold font-mono text-red-500">
479
+ {result.errors.length}
480
+ </div>
481
+ <div className="text-[8px] font-mono tracking-widest uppercase text-[var(--color-text-muted,#6b7280)]">
482
+ Errors
483
+ </div>
484
+ </div>
485
+ )}
486
+ </div>
487
+
488
+ {/* Error details */}
489
+ {hasErrors && (
490
+ <div className="max-h-[120px] overflow-y-auto border border-red-200 bg-red-50">
491
+ {result.errors.map((e, i) => (
492
+ <div
493
+ key={i}
494
+ className="px-3 py-1 text-[9px] font-mono text-red-700 border-b border-red-100 last:border-b-0"
495
+ >
496
+ Row {e.row}: {e.reason}
497
+ </div>
498
+ ))}
499
+ </div>
500
+ )}
501
+
502
+ </div>
503
+ );
504
+ };
505
+
506
+ const renderFooter = () => {
507
+ if (step === 'select') {
508
+ return (
509
+ <div className="flex justify-end gap-2">
510
+ <Button variant="secondary" size="sm" onClick={handleClose}>
511
+ CANCEL
512
+ </Button>
513
+ <Button
514
+ size="sm"
515
+ onClick={handlePreview}
516
+ loading={loading}
517
+ disabled={!file || !selectedSource?.supported}
518
+ >
519
+ PREVIEW
520
+ </Button>
521
+ </div>
522
+ );
523
+ }
524
+
525
+ if (step === 'preview' && preview) {
526
+ const importCount = duplicateStrategy === 'skip' ? preview.total - preview.duplicates : preview.total;
527
+ return (
528
+ <div className="flex justify-end gap-2">
529
+ <Button
530
+ variant="secondary"
531
+ size="sm"
532
+ onClick={() => {
533
+ setStep('select');
534
+ setError(null);
535
+ if (fileInputRef.current) fileInputRef.current.value = '';
536
+ }}
537
+ >
538
+ BACK
539
+ </Button>
540
+ <Button
541
+ size="sm"
542
+ onClick={handleImport}
543
+ loading={loading}
544
+ disabled={preview.credentials.length === 0}
545
+ >
546
+ IMPORT {importCount} CREDENTIAL{importCount !== 1 ? 'S' : ''}
547
+ </Button>
548
+ </div>
549
+ );
550
+ }
551
+
552
+ if (step === 'result') {
553
+ return (
554
+ <div className="flex justify-end">
555
+ <Button size="sm" onClick={handleDone}>
556
+ DONE
557
+ </Button>
558
+ </div>
559
+ );
560
+ }
561
+
562
+ return null;
563
+ };
564
+
565
+ return (
566
+ <Modal
567
+ isOpen={isOpen}
568
+ onClose={handleClose}
569
+ title="Import Credentials"
570
+ size="md"
571
+ footer={renderFooter()}
572
+ >
573
+ {step === 'select' && renderSelectStep()}
574
+ {step === 'preview' && renderPreviewStep()}
575
+ {step === 'result' && renderResultStep()}
576
+ </Modal>
577
+ );
578
+ };
@@ -0,0 +1,88 @@
1
+ 'use client';
2
+
3
+ import React, { useState, useEffect, useRef, useCallback } from 'react';
4
+ import { Modal, Button } from '@/components/design-system';
5
+
6
+ interface LargeTypeModalProps {
7
+ isOpen: boolean;
8
+ onClose: () => void;
9
+ value: string;
10
+ }
11
+
12
+ export const LargeTypeModal: React.FC<LargeTypeModalProps> = ({ isOpen, onClose, value }) => {
13
+ const [copied, setCopied] = useState(false);
14
+ const copyTimerRef = useRef<ReturnType<typeof setTimeout>>(null);
15
+ const characters = Array.from(value ?? '');
16
+
17
+ const handleCopy = useCallback(async () => {
18
+ try {
19
+ await navigator.clipboard.writeText(value);
20
+ setCopied(true);
21
+ if (copyTimerRef.current) clearTimeout(copyTimerRef.current);
22
+ copyTimerRef.current = setTimeout(() => setCopied(false), 2000);
23
+ } catch {
24
+ // Clipboard might be unavailable in some contexts.
25
+ }
26
+ }, [value]);
27
+
28
+ useEffect(() => {
29
+ if (!isOpen) setCopied(false);
30
+ return () => {
31
+ if (copyTimerRef.current) clearTimeout(copyTimerRef.current);
32
+ };
33
+ }, [isOpen]);
34
+
35
+ const renderCharacter = (char: string): string => {
36
+ if (char === ' ') return '␠';
37
+ if (char === '\n') return '↵';
38
+ if (char === '\t') return '⇥';
39
+ return char;
40
+ };
41
+
42
+ return (
43
+ <Modal
44
+ isOpen={isOpen}
45
+ onClose={onClose}
46
+ title="Expanded Value"
47
+ size="lg"
48
+ contentClassName="!p-0"
49
+ footer={(
50
+ <div className="flex items-center gap-2">
51
+ <Button variant="secondary" size="sm" onClick={onClose}>
52
+ Cancel
53
+ </Button>
54
+ <Button variant="primary" size="sm" onClick={() => { void handleCopy(); }}>
55
+ {copied ? 'COPIED' : 'COPY'}
56
+ </Button>
57
+ </div>
58
+ )}
59
+ >
60
+ <div className="w-full min-h-[360px] px-6 py-8 flex flex-col items-center justify-center text-center">
61
+ <div className="w-full max-w-[900px] max-h-[70vh] overflow-auto px-1">
62
+ <div className="grid gap-2 [grid-template-columns:repeat(auto-fit,minmax(56px,1fr))]">
63
+ {characters.map((char, index) => (
64
+ <div
65
+ key={`${index}-${char}`}
66
+ data-testid={`large-type-char-${index + 1}`}
67
+ className="rounded-sm border border-[var(--color-border,#d4d4d8)] bg-[var(--color-surface,#ffffff)] px-1 py-2"
68
+ >
69
+ <div className="font-mono text-3xl font-bold leading-none text-[var(--color-text,#0a0a0a)] select-all">
70
+ {renderCharacter(char)}
71
+ </div>
72
+ <div
73
+ data-testid={`large-type-index-${index + 1}`}
74
+ className="mt-2 font-mono text-[9px] uppercase tracking-widest text-[var(--color-text-faint,#9ca3af)]"
75
+ >
76
+ {index + 1}
77
+ </div>
78
+ </div>
79
+ ))}
80
+ </div>
81
+ </div>
82
+ <div className="mt-8 font-mono text-[9px] tracking-widest uppercase text-[var(--color-text-faint,#9ca3af)]">
83
+ Use copy to move this value to clipboard
84
+ </div>
85
+ </div>
86
+ </Modal>
87
+ );
88
+ };