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,881 @@
1
+ 'use client';
2
+
3
+ import React, { useEffect, useMemo, useRef, useState } from 'react';
4
+ import { Layers, Key, CreditCard, FileText, Star, Tag, Lock, Unlock, Plus, RefreshCw, Terminal, ShieldCheck, Archive, Trash2, ChevronRight, Wallet } from 'lucide-react';
5
+ import { Button, DownloadButton } from '@/components/design-system';
6
+ import type { VaultInfo, VaultFilters, CategoryFilter, CredentialType } from './types';
7
+
8
+ type VaultSurface = 'credentials' | 'audit' | 'apiKeys';
9
+ type CredentialCreateStart = 'default' | 'type-picker';
10
+ type CredentialCreatePrefillType = Exclude<CredentialType, 'api' | 'passkey'>;
11
+
12
+ interface VaultSidebarProps {
13
+ vaults: VaultInfo[];
14
+ filters: VaultFilters;
15
+ categoryCounts: Record<CategoryFilter, number>;
16
+ tags: string[];
17
+ favoritesCount: number;
18
+ onFilterChange: (filters: Partial<VaultFilters>) => void;
19
+ onLock: () => void;
20
+ onLockVault?: (vaultId: string) => void;
21
+ onUnlockVault?: (vault: VaultInfo) => void;
22
+ onCreateCredential: (start?: CredentialCreateStart, prefillType?: CredentialCreatePrefillType) => void;
23
+ onCreateVault: (parentVaultId?: string) => void;
24
+ mode?: 'desktop' | 'tablet' | 'mobile';
25
+ onNavigate?: () => void;
26
+ notifications?: unknown[];
27
+ onDismissNotification?: (id: string) => void;
28
+ pendingActionCount?: number;
29
+ surface?: VaultSurface;
30
+ onSurfaceChange?: (surface: VaultSurface) => void;
31
+ onSettings?: () => void;
32
+ onDeleteVault?: (vault: VaultInfo) => void;
33
+ }
34
+
35
+ const categories: { key: CategoryFilter; label: string; icon: React.FC<{ size: number; className?: string }> }[] = [
36
+ { key: 'all', label: 'All Credentials', icon: Layers },
37
+ { key: 'login', label: 'Logins', icon: Key },
38
+ { key: 'card', label: 'Cards', icon: CreditCard },
39
+ { key: 'plain_note', label: 'Plain Notes', icon: FileText },
40
+ { key: 'note', label: 'Secret Notes', icon: FileText },
41
+ { key: 'hot_wallet', label: 'Hot Wallets', icon: Wallet },
42
+ { key: 'oauth2', label: 'OAuth2', icon: RefreshCw },
43
+ { key: 'ssh', label: 'SSH Keys', icon: Terminal },
44
+ { key: 'gpg', label: 'GPG Keys', icon: ShieldCheck },
45
+ ];
46
+
47
+ const CATEGORY_CREATE_TYPE: Partial<Record<CategoryFilter, CredentialCreatePrefillType>> = {
48
+ login: 'login',
49
+ card: 'card',
50
+ plain_note: 'plain_note',
51
+ note: 'note',
52
+ hot_wallet: 'hot_wallet',
53
+ oauth2: 'oauth2',
54
+ ssh: 'ssh',
55
+ gpg: 'gpg',
56
+ };
57
+
58
+ const lifecycleFilters: {
59
+ key: VaultFilters['lifecycle'];
60
+ label: string;
61
+ icon: React.FC<{ size: number; className?: string }>;
62
+ }[] = [
63
+ { key: 'archive', label: 'Archived', icon: Archive },
64
+ { key: 'recently_deleted', label: 'Recently Deleted', icon: Trash2 },
65
+ ];
66
+
67
+ const KEYBOARD_SHORTCUTS: Array<{ combo: string; action: string }> = [
68
+ { combo: 'Cmd/Ctrl + K', action: 'Focus search' },
69
+ { combo: '/', action: 'Focus search (while not typing)' },
70
+ { combo: 'Cmd/Ctrl + Alt + N', action: 'Create new credential' },
71
+ { combo: '↑ / ↓', action: 'Navigate list selection' },
72
+ { combo: 'Enter', action: 'Open selected credential' },
73
+ ];
74
+
75
+ type SidebarSectionKey = 'vaults' | 'categories' | 'favorites' | 'tags' | 'lifecycle';
76
+
77
+ type SidebarSectionState = Record<SidebarSectionKey, boolean>;
78
+ type VaultNodeCollapseState = Record<string, boolean>;
79
+
80
+ const SIDEBAR_SECTION_STORAGE_KEY = 'auramaxx:vault-sidebar-collapsible-sections';
81
+ const SIDEBAR_VAULT_NODE_STORAGE_KEY = 'auramaxx:vault-sidebar-collapsed-vault-nodes';
82
+
83
+ const DEFAULT_SECTION_STATE: SidebarSectionState = {
84
+ vaults: false,
85
+ categories: true,
86
+ favorites: true,
87
+ tags: true,
88
+ lifecycle: true,
89
+ };
90
+
91
+ function normalizeSectionState(input: unknown): SidebarSectionState | null {
92
+ if (!input || typeof input !== 'object') return null;
93
+
94
+ return {
95
+ vaults: Boolean((input as Partial<SidebarSectionState>).vaults),
96
+ categories: Boolean((input as Partial<SidebarSectionState>).categories),
97
+ favorites: Boolean((input as Partial<SidebarSectionState>).favorites),
98
+ tags: Boolean((input as Partial<SidebarSectionState>).tags),
99
+ lifecycle: Boolean((input as Partial<SidebarSectionState>).lifecycle),
100
+ };
101
+ }
102
+
103
+ function normalizeVaultNodeState(input: unknown): VaultNodeCollapseState | null {
104
+ if (!input || typeof input !== 'object') return null;
105
+ const next: VaultNodeCollapseState = {};
106
+ for (const [key, value] of Object.entries(input as Record<string, unknown>)) {
107
+ if (!key) continue;
108
+ next[key] = Boolean(value);
109
+ }
110
+ return next;
111
+ }
112
+
113
+ type RenderedVaultEntry = {
114
+ vault: VaultInfo;
115
+ depth: number;
116
+ };
117
+
118
+ function getParentVaultId(vault: VaultInfo): string | undefined {
119
+ return vault.parentVaultId || vault.linkedTo;
120
+ }
121
+
122
+ function buildRenderedVaultEntries(vaults: VaultInfo[]): RenderedVaultEntry[] {
123
+ const byId = new Map(vaults.map((vault) => [vault.id, vault]));
124
+ const byOrder = new Map(vaults.map((vault, index) => [vault.id, index]));
125
+ const childrenByParent = new Map<string, VaultInfo[]>();
126
+
127
+ for (const vault of vaults) {
128
+ const parentVaultId = getParentVaultId(vault);
129
+ if (!parentVaultId || !byId.has(parentVaultId) || parentVaultId === vault.id) continue;
130
+ const bucket = childrenByParent.get(parentVaultId) || [];
131
+ bucket.push(vault);
132
+ childrenByParent.set(parentVaultId, bucket);
133
+ }
134
+
135
+ const sortByInputOrder = (a: VaultInfo, b: VaultInfo) => {
136
+ return (byOrder.get(a.id) || 0) - (byOrder.get(b.id) || 0);
137
+ };
138
+ for (const children of childrenByParent.values()) {
139
+ children.sort(sortByInputOrder);
140
+ }
141
+
142
+ const roots = vaults
143
+ .filter((vault) => {
144
+ const parentVaultId = getParentVaultId(vault);
145
+ return !parentVaultId || !byId.has(parentVaultId) || parentVaultId === vault.id;
146
+ })
147
+ .sort(sortByInputOrder);
148
+
149
+ const ordered: RenderedVaultEntry[] = [];
150
+ const visited = new Set<string>();
151
+ const walk = (vault: VaultInfo, depth: number) => {
152
+ if (visited.has(vault.id)) return;
153
+ visited.add(vault.id);
154
+ ordered.push({ vault, depth });
155
+ for (const child of childrenByParent.get(vault.id) || []) {
156
+ walk(child, depth + 1);
157
+ }
158
+ };
159
+
160
+ for (const root of roots) {
161
+ walk(root, 0);
162
+ }
163
+ for (const vault of [...vaults].sort(sortByInputOrder)) {
164
+ walk(vault, 0);
165
+ }
166
+ return ordered;
167
+ }
168
+
169
+ function buildTreeMaps(vaults: VaultInfo[]) {
170
+ const parentByChild = new Map<string, string>();
171
+ const childCountByParent = new Map<string, number>();
172
+
173
+ for (const vault of vaults) {
174
+ const parent = getParentVaultId(vault);
175
+ if (!parent || parent === vault.id) continue;
176
+ parentByChild.set(vault.id, parent);
177
+ childCountByParent.set(parent, (childCountByParent.get(parent) || 0) + 1);
178
+ }
179
+
180
+ return { parentByChild, childCountByParent };
181
+ }
182
+
183
+ export const VaultSidebar: React.FC<VaultSidebarProps> = ({
184
+ vaults,
185
+ filters,
186
+ categoryCounts,
187
+ tags,
188
+ favoritesCount,
189
+ onFilterChange,
190
+ onLock,
191
+ onLockVault,
192
+ onUnlockVault,
193
+ onCreateCredential,
194
+ onCreateVault,
195
+ mode = 'desktop',
196
+ onNavigate,
197
+ notifications = [],
198
+ onDismissNotification,
199
+ pendingActionCount = 0,
200
+ surface = 'credentials',
201
+ onSurfaceChange,
202
+ onSettings,
203
+ onDeleteVault,
204
+ }) => {
205
+ const isCompact = mode === 'tablet';
206
+ const sidebarWidth = mode === 'tablet'
207
+ ? 'calc(48px * var(--ui-scale-factor, 1))'
208
+ : mode === 'mobile'
209
+ ? 'calc(220px * var(--ui-scale-factor, 1))'
210
+ : 'calc(200px * var(--ui-scale-factor, 1))';
211
+ const createDisabled = filters.lifecycle !== 'active';
212
+ const renderedVaults = useMemo(() => buildRenderedVaultEntries(vaults), [vaults]);
213
+ const { parentByChild, childCountByParent } = useMemo(() => buildTreeMaps(vaults), [vaults]);
214
+ const [collapsedSections, setCollapsedSections] = useState<SidebarSectionState>(DEFAULT_SECTION_STATE);
215
+ const [sectionStateHydrated, setSectionStateHydrated] = useState(false);
216
+ const [collapsedVaultNodes, setCollapsedVaultNodes] = useState<VaultNodeCollapseState>({});
217
+ const [nodeStateHydrated, setNodeStateHydrated] = useState(false);
218
+ const [shortcutPopoverOpen, setShortcutPopoverOpen] = useState(false);
219
+ const shortcutPopoverRef = useRef<HTMLDivElement | null>(null);
220
+
221
+ useEffect(() => {
222
+ try {
223
+ const raw = window.localStorage.getItem(SIDEBAR_SECTION_STORAGE_KEY);
224
+ if (!raw) return;
225
+ const parsed = normalizeSectionState(JSON.parse(raw));
226
+ if (!parsed) return;
227
+ setCollapsedSections(parsed);
228
+ } catch {
229
+ // Ignore malformed localStorage payloads and use defaults.
230
+ } finally {
231
+ setSectionStateHydrated(true);
232
+ }
233
+ }, []);
234
+
235
+ useEffect(() => {
236
+ if (!sectionStateHydrated) return;
237
+ try {
238
+ window.localStorage.setItem(SIDEBAR_SECTION_STORAGE_KEY, JSON.stringify(collapsedSections));
239
+ } catch {
240
+ // Ignore localStorage failures (private mode / quota).
241
+ }
242
+ }, [collapsedSections, sectionStateHydrated]);
243
+
244
+ useEffect(() => {
245
+ try {
246
+ const raw = window.localStorage.getItem(SIDEBAR_VAULT_NODE_STORAGE_KEY);
247
+ if (!raw) return;
248
+ const parsed = normalizeVaultNodeState(JSON.parse(raw));
249
+ if (!parsed) return;
250
+ setCollapsedVaultNodes(parsed);
251
+ } catch {
252
+ // Ignore malformed localStorage payloads and use defaults.
253
+ } finally {
254
+ setNodeStateHydrated(true);
255
+ }
256
+ }, []);
257
+
258
+ useEffect(() => {
259
+ if (!nodeStateHydrated) return;
260
+ try {
261
+ window.localStorage.setItem(SIDEBAR_VAULT_NODE_STORAGE_KEY, JSON.stringify(collapsedVaultNodes));
262
+ } catch {
263
+ // Ignore localStorage failures.
264
+ }
265
+ }, [collapsedVaultNodes, nodeStateHydrated]);
266
+
267
+ useEffect(() => {
268
+ const selectedVaultId = filters.vaultId;
269
+ if (!selectedVaultId) return;
270
+ const ancestors: string[] = [];
271
+ let cursor = parentByChild.get(selectedVaultId);
272
+ while (cursor) {
273
+ ancestors.push(cursor);
274
+ cursor = parentByChild.get(cursor);
275
+ }
276
+ if (ancestors.length === 0) return;
277
+ setCollapsedVaultNodes((prev) => {
278
+ let changed = false;
279
+ const next = { ...prev };
280
+ for (const id of ancestors) {
281
+ if (next[id]) {
282
+ delete next[id];
283
+ changed = true;
284
+ }
285
+ }
286
+ return changed ? next : prev;
287
+ });
288
+ }, [filters.vaultId, parentByChild]);
289
+
290
+ useEffect(() => {
291
+ if (!shortcutPopoverOpen) return;
292
+ const onPointerDown = (event: MouseEvent) => {
293
+ const target = event.target as Node;
294
+ if (shortcutPopoverRef.current && !shortcutPopoverRef.current.contains(target)) {
295
+ setShortcutPopoverOpen(false);
296
+ }
297
+ };
298
+ const onKeyDown = (event: KeyboardEvent) => {
299
+ if (event.key === 'Escape') setShortcutPopoverOpen(false);
300
+ };
301
+ document.addEventListener('mousedown', onPointerDown);
302
+ document.addEventListener('keydown', onKeyDown);
303
+ return () => {
304
+ document.removeEventListener('mousedown', onPointerDown);
305
+ document.removeEventListener('keydown', onKeyDown);
306
+ };
307
+ }, [shortcutPopoverOpen]);
308
+
309
+ const visibleRenderedVaults = useMemo(() => {
310
+ const hiddenAncestorStack: string[] = [];
311
+ const visible: RenderedVaultEntry[] = [];
312
+
313
+ for (const entry of renderedVaults) {
314
+ while (hiddenAncestorStack.length > entry.depth) hiddenAncestorStack.pop();
315
+ if (hiddenAncestorStack.length > 0) continue;
316
+ visible.push(entry);
317
+ if (collapsedVaultNodes[entry.vault.id]) hiddenAncestorStack.push(entry.vault.id);
318
+ }
319
+ return visible;
320
+ }, [renderedVaults, collapsedVaultNodes]);
321
+
322
+ const toggleSection = (section: SidebarSectionKey) => {
323
+ setCollapsedSections((prev) => ({ ...prev, [section]: !prev[section] }));
324
+ };
325
+
326
+ const toggleVaultNode = (vaultId: string) => {
327
+ setCollapsedVaultNodes((prev) => ({ ...prev, [vaultId]: !prev[vaultId] }));
328
+ };
329
+
330
+ const handleSectionToggleKeyDown = (
331
+ event: React.KeyboardEvent<HTMLButtonElement>,
332
+ section: SidebarSectionKey,
333
+ ) => {
334
+ if (event.key !== 'Enter' && event.key !== ' ') return;
335
+ event.preventDefault();
336
+ toggleSection(section);
337
+ };
338
+
339
+ const handleFilterAction = (partial: Partial<VaultFilters>) => {
340
+ onSurfaceChange?.('credentials');
341
+ onFilterChange(partial);
342
+ onNavigate?.();
343
+ };
344
+
345
+ const handleCreateCredential = (
346
+ start: CredentialCreateStart = 'default',
347
+ prefillType?: CredentialCreatePrefillType,
348
+ ) => {
349
+ onSurfaceChange?.('credentials');
350
+ if (prefillType) {
351
+ onCreateCredential(start, prefillType);
352
+ } else {
353
+ onCreateCredential(start);
354
+ }
355
+ onNavigate?.();
356
+ };
357
+
358
+ const handleCreateVault = (parentVaultId?: string) => {
359
+ onSurfaceChange?.('credentials');
360
+ onCreateVault(parentVaultId);
361
+ onNavigate?.();
362
+ };
363
+
364
+ const handleLock = () => {
365
+ onLock();
366
+ onNavigate?.();
367
+ };
368
+
369
+ const handleOpenAudit = () => {
370
+ onSurfaceChange?.('audit');
371
+ onNavigate?.();
372
+ };
373
+
374
+ const handleOpenApiKeys = () => {
375
+ onSurfaceChange?.('apiKeys');
376
+ onNavigate?.();
377
+ };
378
+
379
+ const renderSectionToggle = (section: SidebarSectionKey, label: string, className = '') => (
380
+ <button
381
+ type="button"
382
+ onClick={() => toggleSection(section)}
383
+ onKeyDown={(event) => handleSectionToggleKeyDown(event, section)}
384
+ aria-expanded={!collapsedSections[section]}
385
+ aria-controls={`vault-sidebar-section-${section}`}
386
+ aria-label={`${label} section`}
387
+ className={`w-full flex items-center transition-colors hover:bg-[var(--color-background-alt,#f4f4f5)] ${isCompact ? 'justify-center py-1' : 'justify-between px-2 py-1'} ${className}`}
388
+ title={`${label} section`}
389
+ >
390
+ {!isCompact && (
391
+ <span className="text-[8px] tracking-widest uppercase text-[var(--color-text-muted,#6b7280)] font-bold">
392
+ {label}
393
+ </span>
394
+ )}
395
+ <ChevronRight
396
+ size={10}
397
+ className={`text-[var(--color-text-faint,#9ca3af)] transition-transform ${collapsedSections[section] ? '' : 'rotate-90'}`}
398
+ />
399
+ </button>
400
+ );
401
+
402
+ return (
403
+ <div
404
+ className="h-full flex flex-col border-r border-[var(--color-border,#d4d4d8)] font-mono relative overflow-hidden shrink-0"
405
+ style={{
406
+ width: sidebarWidth,
407
+ minWidth: sidebarWidth,
408
+ background: 'var(--color-surface, #f4f4f2)',
409
+ fontSize: 'var(--font-size-sm)',
410
+ }}
411
+ >
412
+ {/* Subtle dot texture (matches WalletSidebar) */}
413
+ <div className="absolute inset-0 opacity-[0.02] pointer-events-none bg-[radial-gradient(var(--color-text,#000)_1px,transparent_1px)] bg-[size:4px_4px]" />
414
+
415
+ {/* Header */}
416
+ <div className={`flex items-center justify-between border-b border-[var(--color-border,#d4d4d8)] relative z-10 ${isCompact ? 'px-2 py-2' : 'px-3 py-3'}`}>
417
+ <div className={`flex items-center ${isCompact ? 'justify-center w-full' : 'gap-2'}`}>
418
+ <img src="/logo.webp" alt="Aura" className="w-[length:var(--font-size-sm)] h-[length:var(--font-size-sm)] object-contain" />
419
+ {!isCompact && (
420
+ <span
421
+ data-testid="vault-sidebar-header-brand"
422
+ className="text-[length:var(--font-size-sm)] font-bold tracking-widest uppercase text-[var(--color-text,#0a0a0a)]"
423
+ >
424
+ AURAMAXX
425
+ </span>
426
+ )}
427
+ </div>
428
+ {/* <DownloadButton compact={isCompact} /> — waiting for Apple to approve cert */}
429
+ </div>
430
+
431
+ {/* Scrollable content */}
432
+ <div className="flex-1 overflow-y-auto overflow-x-hidden relative z-10">
433
+ {/* Vault list */}
434
+ <div className="border-b border-[var(--color-border,#d4d4d8)]">
435
+ {renderSectionToggle('vaults', 'Vaults')}
436
+ {!collapsedSections.vaults && (
437
+ <div
438
+ id="vault-sidebar-section-vaults"
439
+ className={`${isCompact ? 'px-1 pb-2' : 'px-2 pb-2'} overflow-x-hidden`}
440
+ >
441
+ <div
442
+ className={`flex items-center ${isCompact ? 'gap-1' : 'gap-1.5'}`}
443
+ style={{
444
+ borderLeft: filters.vaultId === null ? '2px solid var(--color-accent, #ccff00)' : '2px solid transparent',
445
+ }}
446
+ >
447
+ <button
448
+ onClick={() => handleFilterAction({ vaultId: null, lifecycle: 'active', tag: null })}
449
+ className={`flex-1 flex items-center transition-colors hover:bg-[var(--color-background-alt,#f4f4f5)] ${isCompact ? 'justify-center px-1 py-1.5' : 'gap-2 px-2 py-1.5 text-left'}`}
450
+ title="All Vaults (Active)"
451
+ >
452
+ {isCompact ? (
453
+ <Layers size={11} className="text-[var(--color-text-muted,#6b7280)]" />
454
+ ) : (
455
+ <span className="text-[9px] tracking-widest uppercase text-[var(--color-text,#0a0a0a)] font-bold">
456
+ All Vaults
457
+ </span>
458
+ )}
459
+ </button>
460
+ <Button
461
+ variant="ghost"
462
+ size="sm"
463
+ onClick={handleLock}
464
+ className={isCompact ? '!px-0 !h-6 !w-6' : '!px-1.5 !h-6'}
465
+ title="Lock all vaults"
466
+ >
467
+ <Lock size={10} />
468
+ </Button>
469
+ <Button
470
+ variant="ghost"
471
+ size="sm"
472
+ onClick={() => handleCreateVault()}
473
+ className={isCompact ? '!px-0 !h-6 !w-6' : '!px-1.5 !h-6'}
474
+ title="Create vault"
475
+ >
476
+ <Plus size={10} />
477
+ </Button>
478
+ </div>
479
+ {visibleRenderedVaults.map(({ vault, depth }) => {
480
+ const displayName = vault.name || (vault.isPrimary ? 'Primary' : `Vault ${vault.id.slice(0, 6)}`);
481
+ const isTopLevel = !getParentVaultId(vault);
482
+ const canLockTopLevel = isTopLevel && !vault.isPrimary && vault.isUnlocked && Boolean(onLockVault);
483
+ const hasChildren = (childCountByParent.get(vault.id) || 0) > 0;
484
+ const parentId = parentByChild.get(vault.id);
485
+ const parentName = parentId ? (vaults.find((v) => v.id === parentId)?.name || parentId.slice(0, 6)) : null;
486
+ const isVaultActive = filters.vaultId === vault.id;
487
+
488
+ return (
489
+ <div
490
+ key={vault.id}
491
+ className="w-full min-w-0 flex items-center group"
492
+ style={{
493
+ borderLeft: isVaultActive ? '2px solid var(--color-accent, #ccff00)' : '2px solid transparent',
494
+ }}
495
+ >
496
+ <div className="ml-0.5 flex items-center">
497
+ {hasChildren ? (
498
+ <button
499
+ type="button"
500
+ data-testid={`vault-toggle-${vault.id}`}
501
+ aria-label={`Toggle children for ${displayName}`}
502
+ onClick={(event) => {
503
+ event.stopPropagation();
504
+ toggleVaultNode(vault.id);
505
+ }}
506
+ className="w-5 h-5 flex items-center justify-center text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)]"
507
+ >
508
+ <ChevronRight size={9} className={`transition-transform ${collapsedVaultNodes[vault.id] ? '' : 'rotate-90'}`} />
509
+ </button>
510
+ ) : (
511
+ <span className="w-5" />
512
+ )}
513
+ </div>
514
+ <button
515
+ data-testid={`vault-item-${vault.id}`}
516
+ data-vault-depth={depth}
517
+ onClick={() => {
518
+ if (!vault.isUnlocked) {
519
+ onUnlockVault?.(vault);
520
+ return;
521
+ }
522
+ handleFilterAction({ vaultId: vault.id });
523
+ }}
524
+ className={`flex-1 min-w-0 flex items-center transition-colors hover:bg-[var(--color-background-alt,#f4f4f5)] ${isCompact ? 'justify-center py-1.5 pr-1' : 'gap-2 py-1.5 pr-2 text-left'}`}
525
+ style={{
526
+ paddingLeft: isCompact
527
+ ? `${Math.max(2, 2 + depth * 4)}px`
528
+ : `${8 + depth * 12}px`,
529
+ }}
530
+ title={displayName}
531
+ >
532
+ {isCompact ? (
533
+ <div className="flex items-center gap-1">
534
+ <div className="w-4 h-4 border border-[var(--color-border,#d4d4d8)] rounded-full flex items-center justify-center text-[7px] uppercase text-[var(--color-text-muted,#6b7280)]">
535
+ {(vault.name || 'V').slice(0, 1)}
536
+ </div>
537
+ {vault.isUnlocked ? (
538
+ <Unlock size={8} className="text-[var(--color-text-muted,#6b7280)]" />
539
+ ) : (
540
+ <Lock size={8} className="text-[var(--color-text-faint,#9ca3af)]" />
541
+ )}
542
+ </div>
543
+ ) : (
544
+ <>
545
+ {depth > 0 && (
546
+ <span data-testid={`vault-lineage-${vault.id}`} className="text-[8px] text-[var(--color-text-faint,#9ca3af)]" title={parentName ? `Child of ${parentName}` : 'Child vault'}>
547
+
548
+ </span>
549
+ )}
550
+ <span className="min-w-0 flex-1 text-[9px] tracking-widest uppercase text-[var(--color-text-muted,#6b7280)] truncate">
551
+ {displayName}
552
+ </span>
553
+ <span
554
+ className="shrink-0 text-[8px] text-[var(--color-text-faint,#9ca3af)] tabular-nums"
555
+ title={`${vault.credentialCount ?? 0} credentials`}
556
+ >
557
+ {vault.credentialCount ?? 0}
558
+ </span>
559
+ </>
560
+ )}
561
+ </button>
562
+ {!isCompact && (
563
+ <div className="mr-1 flex items-center gap-0.5">
564
+ {canLockTopLevel && (
565
+ <button
566
+ type="button"
567
+ data-testid={`vault-status-lock-${vault.id}`}
568
+ title={vault.isPrimary ? 'Lock all vaults' : `Lock vault ${displayName}`}
569
+ aria-label={vault.isPrimary ? 'Lock all vaults' : `Lock vault ${displayName}`}
570
+ onClick={(event) => {
571
+ event.stopPropagation();
572
+ if (vault.isPrimary) {
573
+ handleLock();
574
+ return;
575
+ }
576
+ onLockVault?.(vault.id);
577
+ }}
578
+ className="w-5 h-5 flex items-center justify-center text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] hover:bg-[var(--color-background-alt,#f4f4f5)] opacity-0 group-hover:opacity-100 transition-opacity"
579
+ >
580
+ <Lock size={9} />
581
+ </button>
582
+ )}
583
+ {onDeleteVault && (
584
+ <button
585
+ type="button"
586
+ title={`Delete vault ${displayName}`}
587
+ aria-label={`Delete vault ${displayName}`}
588
+ onClick={(event) => {
589
+ event.stopPropagation();
590
+ onDeleteVault(vault);
591
+ }}
592
+ className="w-5 h-5 flex items-center justify-center text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] hover:bg-[var(--color-background-alt,#f4f4f5)] opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity"
593
+ >
594
+ <Trash2 size={9} />
595
+ </button>
596
+ )}
597
+ <button
598
+ type="button"
599
+ title={`Create child vault in ${displayName}`}
600
+ aria-label={`Create child vault in ${displayName}`}
601
+ onClick={(event) => {
602
+ event.stopPropagation();
603
+ handleCreateVault(vault.id);
604
+ }}
605
+ className="w-5 h-5 shrink-0 flex items-center justify-center text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] hover:bg-[var(--color-background-alt,#f4f4f5)]"
606
+ >
607
+ <Plus size={9} />
608
+ </button>
609
+ </div>
610
+ )}
611
+ </div>
612
+ );
613
+ })}
614
+ </div>
615
+ )}
616
+ </div>
617
+
618
+ {/* Categories */}
619
+ <div className="border-b border-[var(--color-border,#d4d4d8)]">
620
+ {renderSectionToggle('categories', 'Categories')}
621
+ {!collapsedSections.categories && (
622
+ <div
623
+ id="vault-sidebar-section-categories"
624
+ className={isCompact ? 'px-1 pb-2' : 'px-2 pb-2'}
625
+ >
626
+ {categories.map(({ key, label, icon: Icon }) => {
627
+ const showCreateCredential = true;
628
+ return (
629
+ <div key={key} className="w-full flex items-center gap-1 group">
630
+ <button
631
+ onClick={() => handleFilterAction({ category: key })}
632
+ className={`${showCreateCredential ? 'flex-1 min-w-0' : 'w-full'} flex items-center transition-colors hover:bg-[var(--color-background-alt,#f4f4f5)] ${isCompact ? 'justify-center px-1 py-1.5' : 'gap-2 px-2 py-1.5 text-left'}`}
633
+ style={{
634
+ borderLeft: filters.category === key ? '2px solid var(--color-accent, #ccff00)' : '2px solid transparent',
635
+ }}
636
+ title={label}
637
+ >
638
+ <Icon size={11} className={filters.category === key ? 'text-[var(--color-text,#0a0a0a)]' : 'text-[var(--color-text-muted,#6b7280)]'} />
639
+ {!isCompact && (
640
+ <>
641
+ <span className={`text-[9px] tracking-widest uppercase flex-1 ${filters.category === key ? 'text-[var(--color-text,#0a0a0a)] font-bold' : 'text-[var(--color-text-muted,#6b7280)]'}`}>
642
+ {label}
643
+ </span>
644
+ <span className="text-[8px] text-[var(--color-text-faint,#9ca3af)]">
645
+ {categoryCounts[key]}
646
+ </span>
647
+ </>
648
+ )}
649
+ </button>
650
+ {showCreateCredential && (
651
+ <Button
652
+ variant="ghost"
653
+ size="sm"
654
+ onClick={() => {
655
+ const createType = CATEGORY_CREATE_TYPE[key];
656
+ if (createType) {
657
+ handleCreateCredential('default', createType);
658
+ return;
659
+ }
660
+ handleCreateCredential('type-picker');
661
+ }}
662
+ className={`${isCompact ? '!px-0 !h-6 !w-6' : '!px-1.5 !h-6'} opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity`}
663
+ title={createDisabled ? `Click All Credentials to return to Active and create ${label}` : `Create ${label}`}
664
+ disabled={createDisabled}
665
+ >
666
+ <Plus size={10} />
667
+ </Button>
668
+ )}
669
+ </div>
670
+ );
671
+ })}
672
+ </div>
673
+ )}
674
+ </div>
675
+
676
+ {/* Favorites */}
677
+ <div className="border-b border-[var(--color-border,#d4d4d8)]">
678
+ {renderSectionToggle('favorites', 'Favorites')}
679
+ {!collapsedSections.favorites && (
680
+ <div
681
+ id="vault-sidebar-section-favorites"
682
+ className={isCompact ? 'px-1 pb-2' : 'px-2 pb-2'}
683
+ >
684
+ <button
685
+ onClick={() => handleFilterAction({ favoritesOnly: !filters.favoritesOnly })}
686
+ className={`w-full flex items-center transition-colors hover:bg-[var(--color-background-alt,#f4f4f5)] ${isCompact ? 'justify-center px-1 py-1.5' : 'gap-2 px-2 py-1.5 text-left'}`}
687
+ style={{
688
+ borderLeft: filters.favoritesOnly ? '2px solid var(--color-accent, #ccff00)' : '2px solid transparent',
689
+ }}
690
+ title="Favorites"
691
+ >
692
+ <Star
693
+ size={11}
694
+ className={filters.favoritesOnly ? 'text-[var(--color-text,#0a0a0a)]' : 'text-[var(--color-text-muted,#6b7280)]'}
695
+ style={filters.favoritesOnly ? { fill: 'var(--color-accent,#ccff00)', color: 'var(--color-accent,#ccff00)' } : undefined}
696
+ />
697
+ {!isCompact && (
698
+ <>
699
+ <span className={`text-[9px] tracking-widest uppercase flex-1 ${filters.favoritesOnly ? 'text-[var(--color-text,#0a0a0a)] font-bold' : 'text-[var(--color-text-muted,#6b7280)]'}`}>
700
+ Favorites
701
+ </span>
702
+ <span className="text-[8px] text-[var(--color-text-faint,#9ca3af)]">
703
+ {favoritesCount}
704
+ </span>
705
+ </>
706
+ )}
707
+ </button>
708
+ </div>
709
+ )}
710
+ </div>
711
+
712
+ {/* Tags */}
713
+ {!isCompact && tags.length > 0 && (
714
+ <div className="border-b border-[var(--color-border,#d4d4d8)]">
715
+ {renderSectionToggle('tags', 'Tags')}
716
+ {!collapsedSections.tags && (
717
+ <div id="vault-sidebar-section-tags" className="px-2 pb-2 overflow-y-auto">
718
+ {tags.map((tag) => (
719
+ <button
720
+ key={tag}
721
+ onClick={() => handleFilterAction({ tag: filters.tag === tag ? null : tag })}
722
+ className="w-full flex items-center gap-2 px-2 py-1 text-left transition-colors hover:bg-[var(--color-background-alt,#f4f4f5)]"
723
+ style={{
724
+ borderLeft: filters.tag === tag ? '2px solid var(--color-accent, #ccff00)' : '2px solid transparent',
725
+ }}
726
+ >
727
+ <Tag size={9} className={filters.tag === tag ? 'text-[var(--color-text,#0a0a0a)]' : 'text-[var(--color-text-faint,#9ca3af)]'} />
728
+ <span className={`text-[9px] tracking-widest uppercase truncate ${filters.tag === tag ? 'text-[var(--color-text,#0a0a0a)] font-bold' : 'text-[var(--color-text-muted,#6b7280)]'}`}>
729
+ {tag}
730
+ </span>
731
+ </button>
732
+ ))}
733
+ </div>
734
+ )}
735
+ </div>
736
+ )}
737
+
738
+ {/* Lifecycle */}
739
+ {!isCompact && (
740
+ <div className="border-b border-[var(--color-border,#d4d4d8)]">
741
+ {renderSectionToggle('lifecycle', 'Lifecycle')}
742
+ {!collapsedSections.lifecycle && (
743
+ <div id="vault-sidebar-section-lifecycle" className="px-2 pb-2">
744
+ {lifecycleFilters.map(({ key, label, icon: Icon }) => (
745
+ <button
746
+ key={key}
747
+ onClick={() => handleFilterAction({ lifecycle: key, tag: null })}
748
+ className="w-full flex items-center gap-2 px-2 py-1 text-left transition-colors hover:bg-[var(--color-background-alt,#f4f4f5)]"
749
+ style={{
750
+ borderLeft: filters.lifecycle === key ? '2px solid var(--color-accent, #ccff00)' : '2px solid transparent',
751
+ }}
752
+ >
753
+ <Icon size={9} className={filters.lifecycle === key ? 'text-[var(--color-text,#0a0a0a)]' : 'text-[var(--color-text-faint,#9ca3af)]'} />
754
+ <span className={`text-[9px] tracking-widest uppercase truncate ${filters.lifecycle === key ? 'text-[var(--color-text,#0a0a0a)] font-bold' : 'text-[var(--color-text-muted,#6b7280)]'}`}>
755
+ {label}
756
+ </span>
757
+ </button>
758
+ ))}
759
+ </div>
760
+ )}
761
+ </div>
762
+ )}
763
+
764
+ </div>{/* end scrollable content */}
765
+
766
+ {/* Bottom */}
767
+ <div className={`border-t border-[var(--color-border,#d4d4d8)] ${isCompact ? 'px-1 py-2' : 'px-3 py-3'}`}>
768
+ {pendingActionCount > 0 && (
769
+ <div className={`flex items-center gap-1.5 mb-2 ${isCompact ? 'justify-center' : 'px-1'}`}>
770
+ <span
771
+ className="min-w-[16px] h-[16px] flex items-center justify-center px-1 font-mono text-[8px] font-bold"
772
+ style={{
773
+ background: 'var(--color-danger, #ef4444)',
774
+ color: 'var(--color-danger-foreground, #ffffff)',
775
+ borderRadius: 'var(--radius-sm)',
776
+ }}
777
+ >
778
+ {pendingActionCount > 9 ? '9+' : pendingActionCount}
779
+ </span>
780
+ {!isCompact && (
781
+ <span className="font-mono text-[8px] font-bold tracking-widest uppercase text-[var(--color-danger,#ef4444)]">
782
+ PENDING
783
+ </span>
784
+ )}
785
+ </div>
786
+ )}
787
+ {isCompact && (
788
+ <div className="mb-2 flex items-center gap-1">
789
+ <Button
790
+ variant="ghost"
791
+ size="sm"
792
+ onClick={handleOpenApiKeys}
793
+ icon={<Key size={10} />}
794
+ className="flex-1 !px-0"
795
+ title="API Keys"
796
+ />
797
+ <Button
798
+ variant="ghost"
799
+ size="sm"
800
+ onClick={handleLock}
801
+ icon={<Lock size={10} />}
802
+ className="flex-1 !px-0"
803
+ title="Lock"
804
+ />
805
+ </div>
806
+ )}
807
+ {!isCompact && (
808
+ <div className="flex items-center gap-2 mt-2 justify-center">
809
+ <button
810
+ type="button"
811
+ onClick={handleOpenAudit}
812
+ className={`text-[8px] tracking-widest uppercase transition-colors ${
813
+ surface === 'audit'
814
+ ? 'text-[var(--color-text,#0a0a0a)] font-bold'
815
+ : 'text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)]'
816
+ }`}
817
+ aria-current={surface === 'audit' ? 'page' : undefined}
818
+ >
819
+ AUDIT
820
+ </button>
821
+ <button
822
+ type="button"
823
+ onClick={handleOpenApiKeys}
824
+ className={`text-[8px] tracking-widest uppercase transition-colors ${
825
+ surface === 'apiKeys'
826
+ ? 'text-[var(--color-text,#0a0a0a)] font-bold'
827
+ : 'text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)]'
828
+ }`}
829
+ aria-current={surface === 'apiKeys' ? 'page' : undefined}
830
+ >
831
+ API KEYS
832
+ </button>
833
+ <button
834
+ type="button"
835
+ onClick={handleLock}
836
+ className="text-[8px] tracking-widest uppercase transition-colors text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)]"
837
+ >
838
+ LOCK
839
+ </button>
840
+ </div>
841
+ )}
842
+ <div
843
+ className={`flex items-center gap-2 ${isCompact ? 'mt-2' : 'mt-3'}`}
844
+ data-testid="vault-sidebar-footer-barcode"
845
+ >
846
+ <div className="h-4 flex-1 bg-[repeating-linear-gradient(90deg,var(--color-text,#000),var(--color-text,#000)_1px,transparent_1px,transparent_3px)] opacity-30" />
847
+ {!isCompact && (
848
+ <div className="relative" ref={shortcutPopoverRef}>
849
+ <button
850
+ type="button"
851
+ data-testid="shortcut-hint-trigger"
852
+ onClick={() => setShortcutPopoverOpen((prev) => !prev)}
853
+ className="font-mono text-[8px] text-[var(--color-text-faint,#9ca3af)] tracking-widest hover:text-[var(--color-text-muted,#6b7280)] transition-colors"
854
+ aria-expanded={shortcutPopoverOpen ? 'true' : 'false'}
855
+ aria-label="Keyboard shortcuts"
856
+ >
857
+ Cmd/Ctrl+K
858
+ </button>
859
+ {shortcutPopoverOpen && (
860
+ <div
861
+ data-testid="shortcut-hint-popover"
862
+ className="absolute bottom-full right-0 mb-1 border border-[var(--color-border-focus,#0a0a0a)] bg-[var(--color-surface,#ffffff)] shadow-mech z-30 p-2 w-[170px]"
863
+ >
864
+ <div className="text-[8px] tracking-widest uppercase text-[var(--color-text-muted,#6b7280)] mb-1">Keyboard Shortcuts</div>
865
+ <div className="space-y-1">
866
+ {KEYBOARD_SHORTCUTS.map((shortcut) => (
867
+ <div key={shortcut.combo} className="flex items-center justify-between gap-2">
868
+ <span className="text-[8px] tracking-widest uppercase text-[var(--color-text,#0a0a0a)]">{shortcut.combo}</span>
869
+ <span className="text-[8px] text-[var(--color-text-muted,#6b7280)]">{shortcut.action}</span>
870
+ </div>
871
+ ))}
872
+ </div>
873
+ </div>
874
+ )}
875
+ </div>
876
+ )}
877
+ </div>
878
+ </div>
879
+ </div>
880
+ );
881
+ };