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,338 @@
1
+ /**
2
+ * Token price lookup with cascading fallback:
3
+ * DexScreener → CoinGecko → Alchemy (if key exists)
4
+ *
5
+ * In-memory cache with 60-second TTL. No DB writes.
6
+ */
7
+
8
+ import { isSolanaChain, normalizeAddress, getNativeCurrency } from './address';
9
+ import { getAlchemyKey, ALCHEMY_PATHS } from './config';
10
+ import { getEthToUsd, getSolToUsd } from './prices';
11
+
12
+ export interface PriceResult {
13
+ priceUsd: string;
14
+ source: string;
15
+ cached: boolean;
16
+ }
17
+
18
+ interface CacheEntry {
19
+ priceUsd: string;
20
+ source: string;
21
+ fetchedAt: number;
22
+ }
23
+
24
+ const CACHE_TTL_MS = 60_000; // 60 seconds
25
+
26
+ const priceCache = new Map<string, CacheEntry>();
27
+
28
+ /** Clear cache — exposed for tests */
29
+ export function clearPriceCache(): void {
30
+ priceCache.clear();
31
+ }
32
+
33
+ // CoinGecko platform ID mapping
34
+ const COINGECKO_PLATFORMS: Record<string, string> = {
35
+ base: 'base',
36
+ ethereum: 'ethereum',
37
+ solana: 'solana',
38
+ polygon: 'polygon-pos',
39
+ arbitrum: 'arbitrum-one',
40
+ optimism: 'optimistic-ethereum',
41
+ };
42
+
43
+ /**
44
+ * Get USD price for a token. Returns null if no source has a price.
45
+ */
46
+ export async function getTokenPrice(address: string, chain: string): Promise<PriceResult | null> {
47
+ // Native token shortcut — use existing cached prices from cron
48
+ if (address === 'native') {
49
+ return getNativePrice(chain);
50
+ }
51
+
52
+ const normalized = normalizeAddress(address, chain);
53
+ const cacheKey = `${chain}:${normalized}`;
54
+
55
+ // Check cache
56
+ const cached = priceCache.get(cacheKey);
57
+ if (cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) {
58
+ return { priceUsd: cached.priceUsd, source: cached.source, cached: true };
59
+ }
60
+
61
+ // Cascading fallback
62
+ let result = await fetchDexScreener(normalized, chain);
63
+ if (!result) result = await fetchCoinGecko(normalized, chain);
64
+ if (!result) result = await fetchAlchemy(normalized, chain);
65
+
66
+ if (!result) return null;
67
+
68
+ // Cache the result
69
+ priceCache.set(cacheKey, {
70
+ priceUsd: result.priceUsd,
71
+ source: result.source,
72
+ fetchedAt: Date.now(),
73
+ });
74
+
75
+ return { ...result, cached: false };
76
+ }
77
+
78
+ /**
79
+ * Batch price lookup — efficient for portfolio valuation.
80
+ * Uses CoinGecko batch (comma-separated per platform) first,
81
+ * then DexScreener in parallel for misses, then Alchemy batch.
82
+ */
83
+ export async function getTokenPrices(
84
+ tokens: { address: string; chain: string }[],
85
+ ): Promise<Map<string, PriceResult>> {
86
+ const results = new Map<string, PriceResult>();
87
+ if (tokens.length === 0) return results;
88
+
89
+ const now = Date.now();
90
+ const misses: { address: string; chain: string; normalized: string; cacheKey: string }[] = [];
91
+
92
+ // 1. Check cache
93
+ for (const { address, chain } of tokens) {
94
+ const normalized = normalizeAddress(address, chain);
95
+ const cacheKey = `${chain}:${normalized}`;
96
+ const cached = priceCache.get(cacheKey);
97
+ if (cached && now - cached.fetchedAt < CACHE_TTL_MS) {
98
+ results.set(cacheKey, { priceUsd: cached.priceUsd, source: cached.source, cached: true });
99
+ } else {
100
+ misses.push({ address, chain, normalized, cacheKey });
101
+ }
102
+ }
103
+
104
+ if (misses.length === 0) return results;
105
+
106
+ // 2. CoinGecko batch — group by platform, comma-separate addresses
107
+ const remaining = await batchCoinGecko(misses, results);
108
+
109
+ // 3. DexScreener in parallel for CoinGecko misses
110
+ const afterDex = await batchDexScreener(remaining, results);
111
+
112
+ // 4. Alchemy batch for remaining misses
113
+ await batchAlchemy(afterDex, results);
114
+
115
+ return results;
116
+ }
117
+
118
+ /** CoinGecko batch: one API call per platform with comma-separated addresses */
119
+ async function batchCoinGecko(
120
+ tokens: { address: string; chain: string; normalized: string; cacheKey: string }[],
121
+ results: Map<string, PriceResult>,
122
+ ): Promise<typeof tokens> {
123
+ // Group by platform
124
+ const byPlatform = new Map<string, typeof tokens>();
125
+ const unsupported: typeof tokens = [];
126
+
127
+ for (const t of tokens) {
128
+ const platformId = COINGECKO_PLATFORMS[t.chain];
129
+ if (!platformId) {
130
+ unsupported.push(t);
131
+ continue;
132
+ }
133
+ const group = byPlatform.get(platformId) || [];
134
+ group.push(t);
135
+ byPlatform.set(platformId, group);
136
+ }
137
+
138
+ const remaining = [...unsupported];
139
+
140
+ await Promise.all(
141
+ Array.from(byPlatform.entries()).map(async ([platformId, group]) => {
142
+ const addresses = group.map((t) => t.normalized).join(',');
143
+ try {
144
+ const res = await fetch(
145
+ `https://api.coingecko.com/api/v3/simple/token_price/${platformId}?contract_addresses=${addresses}&vs_currencies=usd`,
146
+ { signal: AbortSignal.timeout(5000) },
147
+ );
148
+ if (!res.ok) {
149
+ remaining.push(...group);
150
+ return;
151
+ }
152
+ const data = await res.json();
153
+
154
+ for (const t of group) {
155
+ const price = data[t.normalized.toLowerCase()]?.usd;
156
+ if (price !== undefined && price !== null) {
157
+ const entry = { priceUsd: price.toString(), source: 'coingecko' as const };
158
+ results.set(t.cacheKey, { ...entry, cached: false });
159
+ priceCache.set(t.cacheKey, { ...entry, fetchedAt: Date.now() });
160
+ } else {
161
+ remaining.push(t);
162
+ }
163
+ }
164
+ } catch {
165
+ remaining.push(...group);
166
+ }
167
+ }),
168
+ );
169
+
170
+ return remaining;
171
+ }
172
+
173
+ /** DexScreener: one API call per token, run in parallel */
174
+ async function batchDexScreener(
175
+ tokens: { address: string; chain: string; normalized: string; cacheKey: string }[],
176
+ results: Map<string, PriceResult>,
177
+ ): Promise<typeof tokens> {
178
+ if (tokens.length === 0) return [];
179
+
180
+ const remaining: typeof tokens = [];
181
+
182
+ await Promise.all(
183
+ tokens.map(async (t) => {
184
+ const result = await fetchDexScreener(t.normalized, t.chain);
185
+ if (result) {
186
+ results.set(t.cacheKey, { ...result, cached: false });
187
+ priceCache.set(t.cacheKey, { ...result, fetchedAt: Date.now() });
188
+ } else {
189
+ remaining.push(t);
190
+ }
191
+ }),
192
+ );
193
+
194
+ return remaining;
195
+ }
196
+
197
+ /** Alchemy batch: one API call with multiple addresses (EVM only) */
198
+ async function batchAlchemy(
199
+ tokens: { address: string; chain: string; normalized: string; cacheKey: string }[],
200
+ results: Map<string, PriceResult>,
201
+ ): Promise<void> {
202
+ if (tokens.length === 0) return;
203
+
204
+ const apiKey = await getAlchemyKey();
205
+ if (!apiKey) return;
206
+
207
+ // Filter to EVM only
208
+ const evmTokens = tokens.filter((t) => !isSolanaChain(t.chain) && ALCHEMY_PATHS[t.chain]);
209
+ if (evmTokens.length === 0) return;
210
+
211
+ const addressPayload = evmTokens.map((t) => ({
212
+ network: ALCHEMY_PATHS[t.chain].path,
213
+ address: t.normalized,
214
+ }));
215
+
216
+ try {
217
+ const res = await fetch(
218
+ `https://api.g.alchemy.com/prices/v1/${apiKey}/tokens/by-address`,
219
+ {
220
+ method: 'POST',
221
+ headers: { 'Content-Type': 'application/json' },
222
+ body: JSON.stringify({ addresses: addressPayload }),
223
+ signal: AbortSignal.timeout(5000),
224
+ },
225
+ );
226
+ if (!res.ok) return;
227
+ const data = await res.json();
228
+
229
+ for (let i = 0; i < evmTokens.length; i++) {
230
+ const priceEntry = data.data?.[i]?.prices?.find(
231
+ (p: any) => p.currency === 'usd' || p.currency === 'USD',
232
+ );
233
+ if (priceEntry?.value) {
234
+ const entry = { priceUsd: priceEntry.value, source: 'alchemy' as const };
235
+ results.set(evmTokens[i].cacheKey, { ...entry, cached: false });
236
+ priceCache.set(evmTokens[i].cacheKey, { ...entry, fetchedAt: Date.now() });
237
+ }
238
+ }
239
+ } catch {
240
+ // Alchemy failed — prices just won't be available for these tokens
241
+ }
242
+ }
243
+
244
+ async function getNativePrice(chain: string): Promise<PriceResult | null> {
245
+ const currency = getNativeCurrency(chain);
246
+ const price = currency === 'SOL' ? await getSolToUsd() : await getEthToUsd();
247
+ if (price === null) return null;
248
+ return { priceUsd: price.toString(), source: 'cache', cached: true };
249
+ }
250
+
251
+ /**
252
+ * DexScreener: free, 300 req/min, no key needed
253
+ */
254
+ async function fetchDexScreener(address: string, chain: string): Promise<{ priceUsd: string; source: string } | null> {
255
+ try {
256
+ const res = await fetch(
257
+ `https://api.dexscreener.com/latest/dex/tokens/${address}`,
258
+ { signal: AbortSignal.timeout(5000) },
259
+ );
260
+ if (!res.ok) return null;
261
+ const data = await res.json();
262
+
263
+ const pairs = data.pairs
264
+ ?.filter((p: any) => p.chainId === chain)
265
+ ?.sort((a: any, b: any) => (b.liquidity?.usd || 0) - (a.liquidity?.usd || 0));
266
+
267
+ const best = pairs?.[0];
268
+ if (!best?.priceUsd) return null;
269
+
270
+ return { priceUsd: best.priceUsd, source: 'dexscreener' };
271
+ } catch {
272
+ return null;
273
+ }
274
+ }
275
+
276
+ /**
277
+ * CoinGecko: free tier, 5-15 req/min, no key needed
278
+ */
279
+ async function fetchCoinGecko(address: string, chain: string): Promise<{ priceUsd: string; source: string } | null> {
280
+ const platformId = COINGECKO_PLATFORMS[chain];
281
+ if (!platformId) return null;
282
+
283
+ try {
284
+ const res = await fetch(
285
+ `https://api.coingecko.com/api/v3/simple/token_price/${platformId}?contract_addresses=${address}&vs_currencies=usd`,
286
+ { signal: AbortSignal.timeout(5000) },
287
+ );
288
+ if (!res.ok) return null;
289
+ const data = await res.json();
290
+
291
+ const key = address.toLowerCase();
292
+ const price = data[key]?.usd;
293
+ if (price === undefined || price === null) return null;
294
+
295
+ return { priceUsd: price.toString(), source: 'coingecko' };
296
+ } catch {
297
+ return null;
298
+ }
299
+ }
300
+
301
+ /**
302
+ * Alchemy: requires API key, EVM only
303
+ */
304
+ async function fetchAlchemy(address: string, chain: string): Promise<{ priceUsd: string; source: string } | null> {
305
+ // Skip for Solana chains — Alchemy price API is EVM only
306
+ if (isSolanaChain(chain)) return null;
307
+
308
+ const apiKey = await getAlchemyKey();
309
+ if (!apiKey) return null;
310
+
311
+ const alchemyConfig = ALCHEMY_PATHS[chain];
312
+ if (!alchemyConfig) return null;
313
+
314
+ try {
315
+ const res = await fetch(
316
+ `https://api.g.alchemy.com/prices/v1/${apiKey}/tokens/by-address`,
317
+ {
318
+ method: 'POST',
319
+ headers: { 'Content-Type': 'application/json' },
320
+ body: JSON.stringify({
321
+ addresses: [{ network: alchemyConfig.path, address }],
322
+ }),
323
+ signal: AbortSignal.timeout(5000),
324
+ },
325
+ );
326
+ if (!res.ok) return null;
327
+ const data = await res.json();
328
+
329
+ const priceEntry = data.data?.[0]?.prices?.find(
330
+ (p: any) => p.currency === 'usd' || p.currency === 'USD',
331
+ );
332
+ if (!priceEntry?.value) return null;
333
+
334
+ return { priceUsd: priceEntry.value, source: 'alchemy' };
335
+ } catch {
336
+ return null;
337
+ }
338
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Price helpers — read cached native currency prices from DB.
3
+ * Prices are written by the cron server's native-price job.
4
+ */
5
+
6
+ import { prisma } from './db';
7
+
8
+ /**
9
+ * Get cached ETH→USD price. Returns null if no cached price exists.
10
+ */
11
+ export async function getEthToUsd(): Promise<number | null> {
12
+ try {
13
+ const row = await prisma.nativePrice.findUnique({ where: { currency: 'ETH' } });
14
+ if (!row) return null;
15
+ const price = parseFloat(row.priceUsd);
16
+ return isNaN(price) ? null : price;
17
+ } catch {
18
+ return null;
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Get cached SOL→USD price. Returns null if no cached price exists.
24
+ */
25
+ export async function getSolToUsd(): Promise<number | null> {
26
+ try {
27
+ const row = await prisma.nativePrice.findUnique({ where: { currency: 'SOL' } });
28
+ if (!row) return null;
29
+ const price = parseFloat(row.priceUsd);
30
+ return isNaN(price) ? null : price;
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
@@ -0,0 +1,297 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { createHash } from 'crypto';
4
+ import { execFileSync } from 'child_process';
5
+
6
+ export type ProjectScopeCode =
7
+ | 'PROJECT_SCOPE_MISSING_AURA'
8
+ | 'PROJECT_SCOPE_INVALID_AURA'
9
+ | 'PROJECT_SCOPE_DENIED'
10
+ | 'PROJECT_SCOPE_OVERRIDE_USED';
11
+
12
+ export type ProjectScopeMode = 'auto' | 'strict' | 'off';
13
+
14
+ export interface ScopeCandidate {
15
+ id?: string;
16
+ name: string;
17
+ vaultName: string | null;
18
+ }
19
+
20
+ export interface ScopeDecision {
21
+ allowed: boolean;
22
+ code: ProjectScopeCode | null;
23
+ remediation: string;
24
+ projectScopeMode: ProjectScopeMode;
25
+ normalizedIdentity: { vaultName: string | null; credentialName: string };
26
+ projectRoot: string | null;
27
+ auraFingerprint: string | null;
28
+ allowedCandidates: ScopeCandidate[];
29
+ overrideUsed?: boolean;
30
+ }
31
+
32
+ interface ParsedRef {
33
+ vaultName: string | null;
34
+ credentialName: string;
35
+ }
36
+
37
+ function normalize(str: string): string {
38
+ return str.trim().toLowerCase();
39
+ }
40
+
41
+ function isTruthyFlag(value: string | undefined): boolean {
42
+ if (!value) return false;
43
+ const normalized = value.trim().toLowerCase();
44
+ return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on';
45
+ }
46
+
47
+ export function shouldEmitProjectScopeEvent(env: NodeJS.ProcessEnv = process.env): boolean {
48
+ return isTruthyFlag(env.AURA_PROJECT_SCOPE_DEBUG);
49
+ }
50
+
51
+ function parseReference(ref: string): ParsedRef {
52
+ if (ref.startsWith('@')) {
53
+ const parts = ref.slice(1).split('/');
54
+ if (parts.length < 3 || !parts[0] || !parts[1]) {
55
+ throw new Error(`Invalid vault reference: ${ref}`);
56
+ }
57
+ return { vaultName: parts[0], credentialName: parts[1] };
58
+ }
59
+ const parts = ref.split('/');
60
+ if (parts.length < 2 || !parts[0]) {
61
+ throw new Error(`Invalid reference: ${ref}`);
62
+ }
63
+ return { vaultName: null, credentialName: parts[0] };
64
+ }
65
+
66
+ function parseAuraAllowlist(auraPath: string): ParsedRef[] {
67
+ const content = fs.readFileSync(auraPath, 'utf-8');
68
+ const refs: ParsedRef[] = [];
69
+
70
+ for (const rawLine of content.split('\n')) {
71
+ const line = rawLine.trim();
72
+ if (!line || line.startsWith('#')) continue;
73
+ const idx = line.indexOf('=');
74
+ if (idx === -1) throw new Error(`Invalid line: ${line}`);
75
+ const ref = line.slice(idx + 1).trim();
76
+ refs.push(parseReference(ref));
77
+ }
78
+
79
+ return refs;
80
+ }
81
+
82
+ function normalizeProjectScopeMode(raw: unknown): ProjectScopeMode {
83
+ const value = String(raw || '').trim().toLowerCase();
84
+ if (value === 'strict') return 'strict';
85
+ if (value === 'off') return 'off';
86
+ return 'auto';
87
+ }
88
+
89
+ function findNearestAura(startDir: string): string | null {
90
+ let current = startDir;
91
+ while (true) {
92
+ const candidate = path.join(current, '.aura');
93
+ if (fs.existsSync(candidate)) return candidate;
94
+ const parent = path.dirname(current);
95
+ if (parent === current) return null;
96
+ current = parent;
97
+ }
98
+ }
99
+
100
+ function resolveGitRoot(startDir: string): string | null {
101
+ try {
102
+ const root = execFileSync('git', ['rev-parse', '--show-toplevel'], {
103
+ cwd: startDir,
104
+ encoding: 'utf-8',
105
+ stdio: ['ignore', 'pipe', 'ignore'],
106
+ }).trim();
107
+ return root || null;
108
+ } catch {
109
+ return null;
110
+ }
111
+ }
112
+
113
+ function resolveAuraPath(opts: { cwd: string; projectRootOverride?: string }): { projectRoot: string | null; auraPath: string | null } {
114
+ const explicit = opts.projectRootOverride || process.env.AURA_PROJECT_ROOT;
115
+ if (explicit) {
116
+ const root = path.resolve(explicit);
117
+ return { projectRoot: root, auraPath: path.join(root, '.aura') };
118
+ }
119
+
120
+ const gitRoot = resolveGitRoot(opts.cwd);
121
+ if (gitRoot) {
122
+ return { projectRoot: gitRoot, auraPath: path.join(gitRoot, '.aura') };
123
+ }
124
+
125
+ const nearestAura = findNearestAura(opts.cwd);
126
+ if (nearestAura) {
127
+ return { projectRoot: path.dirname(nearestAura), auraPath: nearestAura };
128
+ }
129
+
130
+ return { projectRoot: null, auraPath: null };
131
+ }
132
+
133
+ function fingerprint(content: string): string {
134
+ return createHash('sha256').update(content).digest('hex').slice(0, 16);
135
+ }
136
+
137
+ function isAllowed(candidate: ScopeCandidate, refs: ParsedRef[]): boolean {
138
+ const candName = normalize(candidate.name);
139
+ const candVault = candidate.vaultName ? normalize(candidate.vaultName) : null;
140
+ return refs.some((ref) => {
141
+ if (normalize(ref.credentialName) !== candName) return false;
142
+ if (!ref.vaultName) return true;
143
+ return candVault === normalize(ref.vaultName);
144
+ });
145
+ }
146
+
147
+ export function evaluateProjectScopeAccess(input: {
148
+ surface: 'cli_vault_get' | 'cli_env' | 'mcp_get_secret';
149
+ requested: { vaultName: string | null; credentialName: string };
150
+ candidates: ScopeCandidate[];
151
+ actor?: string;
152
+ cwd?: string;
153
+ projectRootOverride?: string;
154
+ projectScopeMode?: ProjectScopeMode;
155
+ }): ScopeDecision {
156
+ const projectScopeMode = normalizeProjectScopeMode(input.projectScopeMode || process.env.AURA_PROJECT_SCOPE_MODE || 'auto');
157
+ const normalizedIdentity = {
158
+ vaultName: input.requested.vaultName,
159
+ credentialName: input.requested.credentialName,
160
+ };
161
+ const bypass = process.env.AURA_PROJECT_SCOPE_BYPASS === '1';
162
+
163
+ if (bypass) {
164
+ return {
165
+ allowed: true,
166
+ code: 'PROJECT_SCOPE_OVERRIDE_USED',
167
+ remediation: 'Unset AURA_PROJECT_SCOPE_BYPASS to restore strict project scoping.',
168
+ projectScopeMode,
169
+ normalizedIdentity,
170
+ projectRoot: null,
171
+ auraFingerprint: null,
172
+ allowedCandidates: input.candidates,
173
+ overrideUsed: true,
174
+ };
175
+ }
176
+
177
+ if (projectScopeMode === 'off') {
178
+ return {
179
+ allowed: true,
180
+ code: null,
181
+ remediation: '',
182
+ projectScopeMode,
183
+ normalizedIdentity,
184
+ projectRoot: null,
185
+ auraFingerprint: null,
186
+ allowedCandidates: input.candidates,
187
+ };
188
+ }
189
+
190
+ const cwd = input.cwd || process.cwd();
191
+ const { projectRoot, auraPath } = resolveAuraPath({ cwd, projectRootOverride: input.projectRootOverride });
192
+ if (!auraPath || !fs.existsSync(auraPath)) {
193
+ if (projectScopeMode === 'auto') {
194
+ return {
195
+ allowed: true,
196
+ code: null,
197
+ remediation: '',
198
+ projectScopeMode,
199
+ normalizedIdentity,
200
+ projectRoot,
201
+ auraFingerprint: null,
202
+ allowedCandidates: input.candidates,
203
+ };
204
+ }
205
+ return {
206
+ allowed: false,
207
+ code: 'PROJECT_SCOPE_MISSING_AURA',
208
+ remediation: 'Add a .aura file in the project root (or set AURA_PROJECT_ROOT / --project-root).',
209
+ projectScopeMode,
210
+ normalizedIdentity,
211
+ projectRoot,
212
+ auraFingerprint: null,
213
+ allowedCandidates: [],
214
+ };
215
+ }
216
+
217
+ let refs: ParsedRef[];
218
+ let auraFingerprint: string;
219
+ try {
220
+ const content = fs.readFileSync(auraPath, 'utf-8');
221
+ auraFingerprint = fingerprint(content);
222
+ refs = parseAuraAllowlist(auraPath);
223
+ } catch {
224
+ return {
225
+ allowed: false,
226
+ code: 'PROJECT_SCOPE_INVALID_AURA',
227
+ remediation: 'Fix .aura syntax (ENV=@vault/name/field or ENV=name/field) and retry.',
228
+ projectScopeMode,
229
+ normalizedIdentity,
230
+ projectRoot,
231
+ auraFingerprint: null,
232
+ allowedCandidates: [],
233
+ };
234
+ }
235
+
236
+ const allowedCandidates = input.candidates.filter((candidate) => isAllowed(candidate, refs));
237
+ if (allowedCandidates.length === 0) {
238
+ return {
239
+ allowed: false,
240
+ code: 'PROJECT_SCOPE_DENIED',
241
+ remediation: `Add '${input.requested.credentialName}' to .aura (or use an allowed credential).`,
242
+ projectScopeMode,
243
+ normalizedIdentity,
244
+ projectRoot,
245
+ auraFingerprint,
246
+ allowedCandidates: [],
247
+ };
248
+ }
249
+
250
+ if (!input.requested.vaultName && allowedCandidates.length > 1) {
251
+ return {
252
+ allowed: false,
253
+ code: 'PROJECT_SCOPE_DENIED',
254
+ remediation: `Credential '${input.requested.credentialName}' is mapped in multiple vaults. Re-run with explicit --vault.`,
255
+ projectScopeMode,
256
+ normalizedIdentity,
257
+ projectRoot,
258
+ auraFingerprint,
259
+ allowedCandidates: [],
260
+ };
261
+ }
262
+
263
+ return {
264
+ allowed: true,
265
+ code: null,
266
+ remediation: '',
267
+ projectScopeMode,
268
+ normalizedIdentity,
269
+ projectRoot,
270
+ auraFingerprint,
271
+ allowedCandidates,
272
+ };
273
+ }
274
+
275
+ export function emitProjectScopeEvent(input: {
276
+ actor?: string;
277
+ surface: 'cli_vault_get' | 'cli_env' | 'mcp_get_secret';
278
+ requestedCredential: { vaultName: string | null; credentialName: string };
279
+ decision: ScopeDecision;
280
+ }): void {
281
+ if (!shouldEmitProjectScopeEvent()) {
282
+ return;
283
+ }
284
+
285
+ const event = {
286
+ actor: input.actor || 'unknown',
287
+ surface: input.surface,
288
+ projectScopeMode: input.decision.projectScopeMode,
289
+ projectRoot: input.decision.projectRoot,
290
+ auraFingerprint: input.decision.auraFingerprint,
291
+ requestedCredential: input.requestedCredential,
292
+ code: input.decision.code,
293
+ timestamp: new Date().toISOString(),
294
+ overrideUsed: Boolean(input.decision.overrideUsed),
295
+ };
296
+ console.warn(`[project-scope] ${JSON.stringify(event)}`);
297
+ }