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,74 @@
1
+ /**
2
+ * CLI helper for creating credentials via the server API.
3
+ */
4
+
5
+ import { serverUrl } from './http';
6
+
7
+ interface CreateCredentialOpts {
8
+ token: string;
9
+ vaultId: string;
10
+ name: string;
11
+ type?: string;
12
+ fields: Array<{ key: string; value: string }>;
13
+ }
14
+
15
+ interface CreateResult {
16
+ success: boolean;
17
+ credential?: { id: string; name: string };
18
+ error?: string;
19
+ }
20
+
21
+ /**
22
+ * Create a credential via POST /credentials.
23
+ * Fields are sent as sensitiveFields (server encrypts them).
24
+ */
25
+ export async function createCredentialViaApi(opts: CreateCredentialOpts): Promise<CreateResult> {
26
+ const base = serverUrl();
27
+ const body = {
28
+ vaultId: opts.vaultId,
29
+ type: opts.type || 'api',
30
+ name: opts.name,
31
+ sensitiveFields: opts.fields.map(f => ({
32
+ key: f.key,
33
+ value: f.value,
34
+ sensitive: true,
35
+ })),
36
+ };
37
+
38
+ const res = await fetch(`${base}/credentials`, {
39
+ method: 'POST',
40
+ headers: {
41
+ 'Content-Type': 'application/json',
42
+ 'Authorization': `Bearer ${opts.token}`,
43
+ },
44
+ body: JSON.stringify(body),
45
+ signal: AbortSignal.timeout(10000),
46
+ });
47
+
48
+ const data = await res.json() as CreateResult;
49
+ if (!res.ok) {
50
+ return { success: false, error: data.error || `HTTP ${res.status}` };
51
+ }
52
+ return data;
53
+ }
54
+
55
+ /**
56
+ * Get the primary vault ID.
57
+ */
58
+ export async function getPrimaryVaultId(token: string): Promise<string> {
59
+ const base = serverUrl();
60
+ const res = await fetch(`${base}/vaults/credential`, {
61
+ headers: { 'Authorization': `Bearer ${token}` },
62
+ signal: AbortSignal.timeout(5000),
63
+ });
64
+
65
+ if (!res.ok) throw new Error(`Failed to list vaults: HTTP ${res.status}`);
66
+
67
+ const data = await res.json() as { vaults: Array<{ id: string; name: string; isPrimary: boolean }> };
68
+ const primary = data.vaults?.find(v => v.isPrimary);
69
+ if (!primary) {
70
+ if (data.vaults?.length > 0) return data.vaults[0].id;
71
+ throw new Error('No vaults found. Run `npx auramaxx` first.');
72
+ }
73
+ return primary.id;
74
+ }
@@ -0,0 +1,280 @@
1
+ /**
2
+ * Shared credential resolution logic for env.ts and shell-hook.ts
3
+ *
4
+ * Consolidates search → read → decrypt flow to prevent drift between
5
+ * the two consumers (audit finding #3).
6
+ */
7
+
8
+ import { getErrorMessage } from '../../lib/error';
9
+ import {
10
+ evaluateProjectScopeAccess,
11
+ emitProjectScopeEvent,
12
+ type ProjectScopeMode,
13
+ } from '../../lib/project-scope';
14
+
15
+ // ── Types ──
16
+
17
+ export interface CredentialMeta {
18
+ id: string;
19
+ name: string;
20
+ type: string;
21
+ vaultId: string;
22
+ }
23
+
24
+ export interface DecryptedCredential {
25
+ id: string;
26
+ vaultId: string;
27
+ type: string;
28
+ fields: Array<{ key: string; value: string }>;
29
+ }
30
+
31
+ export interface AuraMapping {
32
+ envVar: string;
33
+ vault: string | null;
34
+ credentialName: string;
35
+ field: string;
36
+ }
37
+
38
+ export interface ResolveResult {
39
+ resolved: Map<string, string>;
40
+ errors: string[];
41
+ missing: AuraMapping[];
42
+ }
43
+
44
+ // ── Env var name validation (audit finding #6) ──
45
+
46
+ const ENV_VAR_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
47
+
48
+ export function isValidEnvVarName(name: string): boolean {
49
+ return ENV_VAR_NAME_RE.test(name);
50
+ }
51
+
52
+ export function validateEnvVarName(name: string): void {
53
+ if (!isValidEnvVarName(name)) {
54
+ throw new Error(
55
+ `Invalid env var name '${name}': must match [A-Za-z_][A-Za-z0-9_]*`
56
+ );
57
+ }
58
+ }
59
+
60
+ // ── Shell escaping (audit finding #1) ──
61
+
62
+ /**
63
+ * Escape a value for safe use in shell export statements.
64
+ * Uses ANSI-C quoting ($'...') to safely handle newlines, tabs,
65
+ * single quotes, backslashes, and other control characters.
66
+ */
67
+ export function escapeForShell(value: string): string {
68
+ // Use ANSI-C $'...' quoting which handles all special chars
69
+ const escaped = value
70
+ .replace(/\\/g, '\\\\')
71
+ .replace(/'/g, "\\'")
72
+ .replace(/\n/g, '\\n')
73
+ .replace(/\r/g, '\\r')
74
+ .replace(/\t/g, '\\t')
75
+ .replace(/\0/g, '\\0')
76
+ // Escape any other control characters
77
+ .replace(/[\x01-\x08\x0b\x0c\x0e-\x1f\x7f]/g, (ch) => {
78
+ return '\\x' + ch.charCodeAt(0).toString(16).padStart(2, '0');
79
+ });
80
+ return `$'${escaped}'`;
81
+ }
82
+
83
+ function normalizeProjectScopeMode(raw: unknown): ProjectScopeMode {
84
+ const value = String(raw || '').trim().toLowerCase();
85
+ if (value === 'strict') return 'strict';
86
+ if (value === 'off') return 'off';
87
+ return 'auto';
88
+ }
89
+
90
+ async function fetchProjectScopeMode(baseUrl: string): Promise<ProjectScopeMode> {
91
+ try {
92
+ const res = await fetch(`${baseUrl}/setup`, {
93
+ signal: AbortSignal.timeout(5000),
94
+ });
95
+ if (!res.ok) return 'auto';
96
+ const data = await res.json() as { projectScopeMode?: unknown };
97
+ return normalizeProjectScopeMode(data.projectScopeMode);
98
+ } catch {
99
+ return 'auto';
100
+ }
101
+ }
102
+
103
+ // ── Credential search with exact-match preference (audit finding #4) ──
104
+
105
+ export async function searchCredential(
106
+ baseUrl: string,
107
+ token: string,
108
+ name: string,
109
+ ): Promise<CredentialMeta | null> {
110
+ for (const param of [
111
+ `q=${encodeURIComponent(name)}`,
112
+ `tag=${encodeURIComponent(name)}`,
113
+ ]) {
114
+ const res = await fetch(`${baseUrl}/credentials?${param}`, {
115
+ headers: { Authorization: `Bearer ${token}` },
116
+ signal: AbortSignal.timeout(5000),
117
+ });
118
+ if (!res.ok) continue;
119
+
120
+ const data = (await res.json()) as { credentials: CredentialMeta[] };
121
+ const creds = data.credentials;
122
+ if (!creds || creds.length === 0) continue;
123
+
124
+ // Prefer exact name match (audit finding #4: ambiguous matching)
125
+ const exact = creds.find(
126
+ (c) => c.name.toLowerCase() === name.toLowerCase(),
127
+ );
128
+ if (exact) return exact;
129
+
130
+ // Warn if multiple non-exact matches
131
+ if (creds.length > 1) {
132
+ console.error(
133
+ `aura: warning: '${name}' matched ${creds.length} credentials, using first. ` +
134
+ `Matches: ${creds.map((c) => c.name).join(', ')}`,
135
+ );
136
+ }
137
+ return creds[0];
138
+ }
139
+ return null;
140
+ }
141
+
142
+ // ── Credential read + decrypt ──
143
+
144
+ export async function readCredential(
145
+ baseUrl: string,
146
+ readToken: string,
147
+ credentialId: string,
148
+ decryptFn: (encrypted: string) => string,
149
+ ): Promise<DecryptedCredential> {
150
+ const res = await fetch(`${baseUrl}/credentials/${credentialId}/read`, {
151
+ method: 'POST',
152
+ headers: { Authorization: `Bearer ${readToken}` },
153
+ signal: AbortSignal.timeout(5000),
154
+ });
155
+ if (!res.ok) {
156
+ const text = await res.text();
157
+ throw new Error(`Read failed (${res.status}): ${text}`);
158
+ }
159
+ const data = (await res.json()) as { encrypted: string };
160
+ const plaintext = decryptFn(data.encrypted);
161
+ return JSON.parse(plaintext);
162
+ }
163
+
164
+ // ── Resolve mappings to env vars ──
165
+
166
+ export async function resolveMappings(
167
+ mappings: AuraMapping[],
168
+ baseUrl: string,
169
+ token: string,
170
+ readToken: string,
171
+ decryptFn: (encrypted: string) => string,
172
+ ): Promise<ResolveResult> {
173
+ const resolved = new Map<string, string>();
174
+ const errors: string[] = [];
175
+ const missing: AuraMapping[] = [];
176
+
177
+ const credentialCache = new Map<string, DecryptedCredential | null>();
178
+ const CONCURRENCY = 5;
179
+
180
+ let vaultNameById = new Map<string, string>();
181
+ const projectScopeMode = await fetchProjectScopeMode(baseUrl);
182
+ try {
183
+ const vaultRes = await fetch(`${baseUrl}/setup/vaults`, { signal: AbortSignal.timeout(5000) });
184
+ if (vaultRes.ok) {
185
+ const vaultData = await vaultRes.json() as { vaults?: Array<{ id: string; name: string }> };
186
+ vaultNameById = new Map((vaultData.vaults || []).map((v) => [v.id, v.name]));
187
+ }
188
+ } catch {
189
+ // best effort only
190
+ }
191
+
192
+ const uniqueTargets = new Map<string, AuraMapping>();
193
+ for (const mapping of mappings) {
194
+ const key = `${(mapping.vault || '').toLowerCase()}::${mapping.credentialName.toLowerCase()}`;
195
+ if (!uniqueTargets.has(key)) uniqueTargets.set(key, mapping);
196
+ }
197
+
198
+ const targetList = [...uniqueTargets.values()];
199
+
200
+ for (let i = 0; i < targetList.length; i += CONCURRENCY) {
201
+ const batch = targetList.slice(i, i + CONCURRENCY);
202
+ await Promise.all(
203
+ batch.map(async (mapping) => {
204
+ const cacheKey = `${(mapping.vault || '').toLowerCase()}::${mapping.credentialName.toLowerCase()}`;
205
+ const meta = await searchCredential(baseUrl, token, mapping.credentialName);
206
+ if (!meta) {
207
+ credentialCache.set(cacheKey, null);
208
+ return;
209
+ }
210
+
211
+ const decision = evaluateProjectScopeAccess({
212
+ surface: 'cli_env',
213
+ requested: { vaultName: mapping.vault, credentialName: mapping.credentialName },
214
+ candidates: [{ id: meta.id, name: meta.name, vaultName: vaultNameById.get(meta.vaultId) || null }],
215
+ actor: 'cli-env',
216
+ projectScopeMode,
217
+ });
218
+ emitProjectScopeEvent({
219
+ actor: 'cli-env',
220
+ surface: 'cli_env',
221
+ requestedCredential: { vaultName: mapping.vault, credentialName: mapping.credentialName },
222
+ decision,
223
+ });
224
+ if (!decision.allowed) {
225
+ credentialCache.set(cacheKey, null);
226
+ errors.push(`credential '${mapping.credentialName}': ${decision.code}: ${decision.remediation}`);
227
+ return;
228
+ }
229
+
230
+ try {
231
+ const decrypted = await readCredential(
232
+ baseUrl,
233
+ readToken,
234
+ meta.id,
235
+ decryptFn,
236
+ );
237
+ credentialCache.set(cacheKey, decrypted);
238
+ } catch (err) {
239
+ credentialCache.set(cacheKey, null);
240
+ errors.push(`credential '${mapping.credentialName}': ${getErrorMessage(err)}`);
241
+ }
242
+ }),
243
+ );
244
+ }
245
+
246
+ for (const mapping of mappings) {
247
+ const cacheKey = `${(mapping.vault || '').toLowerCase()}::${mapping.credentialName.toLowerCase()}`;
248
+ const cred = credentialCache.get(cacheKey);
249
+ if (!cred) {
250
+ if (
251
+ !errors.some((e) =>
252
+ e.startsWith(`credential '${mapping.credentialName}'`),
253
+ )
254
+ ) {
255
+ errors.push(
256
+ `${mapping.envVar}: credential '${mapping.credentialName}' not found`,
257
+ );
258
+ } else {
259
+ errors.push(
260
+ `${mapping.envVar}: credential '${mapping.credentialName}' failed to resolve`,
261
+ );
262
+ }
263
+ missing.push(mapping);
264
+ continue;
265
+ }
266
+ const field = cred.fields.find(
267
+ (f) => f.key.toLowerCase() === mapping.field.toLowerCase(),
268
+ );
269
+ if (!field) {
270
+ const available = cred.fields.map((f) => f.key).join(', ');
271
+ errors.push(
272
+ `${mapping.envVar}: field '${mapping.field}' not found in '${mapping.credentialName}' (available: ${available})`,
273
+ );
274
+ continue;
275
+ }
276
+ resolved.set(mapping.envVar, field.value);
277
+ }
278
+
279
+ return { resolved, errors, missing };
280
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Shared dotenv → vault migration logic.
3
+ * Used by both `aura init --from-dotenv` and `aura env init`.
4
+ */
5
+
6
+ import * as fs from 'fs';
7
+ import * as path from 'path';
8
+ import { parseDotenv, groupByPrefix, noGrouping, generateAuraFile, type CredentialGroup } from './dotenv-parser';
9
+ import { createCredentialViaApi, getPrimaryVaultId } from './credential-create';
10
+
11
+ export interface MigrateOptions {
12
+ token: string;
13
+ envPath: string;
14
+ noGroup?: boolean;
15
+ dryRun?: boolean;
16
+ }
17
+
18
+ export interface MigrateResult {
19
+ groups: CredentialGroup[];
20
+ created: number;
21
+ failed: number;
22
+ auraPath: string;
23
+ }
24
+
25
+ /**
26
+ * Migrate a .env file into the vault and generate a .aura file.
27
+ * Returns summary of what was done.
28
+ */
29
+ export async function migrateDotenv(opts: MigrateOptions): Promise<MigrateResult> {
30
+ const { token, envPath, noGroup, dryRun } = opts;
31
+
32
+ if (!fs.existsSync(envPath)) {
33
+ throw new Error(`No .env file found at ${envPath}`);
34
+ }
35
+
36
+ const auraPath = path.join(path.dirname(envPath), '.aura');
37
+ const shouldWriteAura = !fs.existsSync(auraPath);
38
+
39
+ if (!shouldWriteAura && !dryRun) {
40
+ console.log(`\n Note: existing .aura file found at ${auraPath} — skipping overwrite.`);
41
+ }
42
+
43
+ const envContent = fs.readFileSync(envPath, 'utf-8');
44
+ const vars = parseDotenv(envContent);
45
+
46
+ if (vars.size === 0) {
47
+ throw new Error('No variables found in .env file.');
48
+ }
49
+
50
+ const groups = noGroup ? noGrouping(vars) : groupByPrefix(vars);
51
+
52
+ if (dryRun) {
53
+ console.log(`\n .env → .aura migration plan (${vars.size} variables → ${groups.length} credentials)\n`);
54
+ for (const group of groups) {
55
+ console.log(` 📦 ${group.name} (${group.fields.length} field${group.fields.length > 1 ? 's' : ''})`);
56
+ for (const field of group.fields) {
57
+ const preview = field.value.length > 20 ? field.value.substring(0, 20) + '...' : field.value;
58
+ console.log(` ${field.envVar} → ${group.name}/${field.key} (${preview})`);
59
+ }
60
+ }
61
+ console.log(`\n Run without --dry-run to execute.\n`);
62
+ return { groups, created: 0, failed: 0, auraPath };
63
+ }
64
+
65
+ const vaultId = await getPrimaryVaultId(token);
66
+
67
+ console.log(`\n Migrating ${vars.size} variables → ${groups.length} credentials\n`);
68
+
69
+ let created = 0;
70
+ let failed = 0;
71
+
72
+ for (const group of groups) {
73
+ const result = await createCredentialViaApi({
74
+ token,
75
+ vaultId,
76
+ name: group.name,
77
+ fields: group.fields.map(f => ({ key: f.key, value: f.value })),
78
+ });
79
+
80
+ if (result.success) {
81
+ console.log(` ✓ Created credential: ${group.name} (${group.fields.length} field${group.fields.length > 1 ? 's' : ''})`);
82
+ created++;
83
+ } else if (result.error?.includes('already exists') || result.error?.includes('duplicate')) {
84
+ console.log(` ⚠ Skipped credential: ${group.name} (already exists)`);
85
+ } else {
86
+ console.error(` ✗ Failed to create ${group.name}: ${result.error}`);
87
+ failed++;
88
+ }
89
+ }
90
+
91
+ // Generate .aura file when needed
92
+ if (shouldWriteAura) {
93
+ const auraContent = generateAuraFile(groups);
94
+ fs.writeFileSync(auraPath, auraContent, 'utf-8');
95
+ console.log(`\n ✓ Generated .aura file (${groups.length} credential${groups.length > 1 ? 's' : ''})`);
96
+ }
97
+
98
+ // Add .env to .gitignore
99
+ const gitignorePath = path.join(path.dirname(envPath), '.gitignore');
100
+ if (fs.existsSync(gitignorePath)) {
101
+ const gitignore = fs.readFileSync(gitignorePath, 'utf-8');
102
+ if (!gitignore.includes('.env')) {
103
+ fs.appendFileSync(gitignorePath, '\n.env\n');
104
+ console.log(' ✓ Added .env to .gitignore');
105
+ }
106
+ }
107
+
108
+ console.log(`\n Summary: ${created} created, ${failed} failed`);
109
+ if (created > 0) {
110
+ console.log(' Your .env variables are now in the vault.');
111
+ console.log(' Use `aura env -- <cmd>` to run commands with vault-injected env vars.');
112
+ console.log(' You can safely delete your .env file.\n');
113
+ }
114
+
115
+ return { groups, created, failed, auraPath };
116
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * .env file parser and credential grouping for `aura env init`
3
+ */
4
+
5
+ import { isValidEnvVarName } from './credential-resolve';
6
+
7
+ export interface CredentialGroup {
8
+ name: string;
9
+ fields: Array<{ key: string; value: string; envVar: string }>;
10
+ }
11
+
12
+ /**
13
+ * Parse a .env file into key-value pairs.
14
+ * Handles: comments, blank lines, `export` prefix, single/double quotes, escaped chars.
15
+ * Does NOT handle: multiline values, variable expansion.
16
+ */
17
+ export function parseDotenv(content: string): Map<string, string> {
18
+ const result = new Map<string, string>();
19
+
20
+ for (const rawLine of content.split('\n')) {
21
+ const line = rawLine.trim();
22
+
23
+ // Skip empty lines and comments
24
+ if (!line || line.startsWith('#')) continue;
25
+
26
+ // Strip optional `export ` prefix
27
+ const stripped = line.startsWith('export ') ? line.slice(7).trim() : line;
28
+
29
+ const eqIdx = stripped.indexOf('=');
30
+ if (eqIdx === -1) continue;
31
+
32
+ const key = stripped.substring(0, eqIdx).trim();
33
+ if (!key) continue;
34
+
35
+ // Validate env var name (audit finding #6)
36
+ if (!isValidEnvVarName(key)) {
37
+ console.error(`warning: skipping invalid env var name '${key}'`);
38
+ continue;
39
+ }
40
+
41
+ let value = stripped.substring(eqIdx + 1).trim();
42
+
43
+ // Handle quoted values
44
+ if ((value.startsWith('"') && value.endsWith('"')) ||
45
+ (value.startsWith("'") && value.endsWith("'"))) {
46
+ const quote = value[0];
47
+ value = value.slice(1, -1);
48
+ if (quote === '"') {
49
+ // Unescape double-quoted values
50
+ value = value
51
+ .replace(/\\n/g, '\n')
52
+ .replace(/\\r/g, '\r')
53
+ .replace(/\\t/g, '\t')
54
+ .replace(/\\"/g, '"')
55
+ .replace(/\\\\/g, '\\');
56
+ }
57
+ // Single-quoted: literal, no escaping
58
+ } else {
59
+ // Unquoted: strip inline comment
60
+ const commentIdx = value.indexOf(' #');
61
+ if (commentIdx !== -1) {
62
+ value = value.substring(0, commentIdx).trim();
63
+ }
64
+ }
65
+
66
+ result.set(key, value);
67
+ }
68
+
69
+ return result;
70
+ }
71
+
72
+ /**
73
+ * Group env vars by common prefix for credential creation.
74
+ *
75
+ * Rules:
76
+ * - Split var name by `_`, use first segment as group prefix
77
+ * - Groups with 1 member: credential name = full lowercased var name, field = "value"
78
+ * - Groups with 2+ members: credential name = prefix, field = rest lowercased
79
+ */
80
+ export function groupByPrefix(vars: Map<string, string>): CredentialGroup[] {
81
+ const prefixGroups = new Map<string, Array<{ key: string; value: string; envVar: string }>>();
82
+
83
+ for (const [envVar, value] of vars) {
84
+ const underscoreIdx = envVar.indexOf('_');
85
+ const prefix = underscoreIdx === -1
86
+ ? envVar.toLowerCase()
87
+ : envVar.substring(0, underscoreIdx).toLowerCase();
88
+
89
+ if (!prefixGroups.has(prefix)) {
90
+ prefixGroups.set(prefix, []);
91
+ }
92
+
93
+ const fieldName = underscoreIdx === -1
94
+ ? 'value'
95
+ : envVar.substring(underscoreIdx + 1).toLowerCase();
96
+
97
+ prefixGroups.get(prefix)!.push({ key: fieldName, value, envVar });
98
+ }
99
+
100
+ const groups: CredentialGroup[] = [];
101
+
102
+ for (const [prefix, fields] of prefixGroups) {
103
+ if (fields.length === 1 && fields[0].key !== 'value') {
104
+ groups.push({
105
+ name: fields[0].envVar.toLowerCase(),
106
+ fields: [{ key: 'value', value: fields[0].value, envVar: fields[0].envVar }],
107
+ });
108
+ } else {
109
+ groups.push({ name: prefix, fields });
110
+ }
111
+ }
112
+
113
+ return groups;
114
+ }
115
+
116
+ /**
117
+ * No grouping — one credential per env var, field always "value".
118
+ */
119
+ export function noGrouping(vars: Map<string, string>): CredentialGroup[] {
120
+ const groups: CredentialGroup[] = [];
121
+ for (const [envVar, value] of vars) {
122
+ groups.push({
123
+ name: envVar.toLowerCase(),
124
+ fields: [{ key: 'value', value, envVar }],
125
+ });
126
+ }
127
+ return groups;
128
+ }
129
+
130
+ /**
131
+ * Generate .aura file content from credential groups.
132
+ */
133
+ export function generateAuraFile(groups: CredentialGroup[]): string {
134
+ const lines = ['# Generated by aura env init'];
135
+
136
+ for (const group of groups) {
137
+ for (const field of group.fields) {
138
+ const ref = field.key === 'value'
139
+ ? `${group.name}/value`
140
+ : `${group.name}/${field.key}`;
141
+ lines.push(`${field.envVar}=${ref}`);
142
+ }
143
+ }
144
+
145
+ return lines.join('\n') + '\n';
146
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Shared 403 escalation handler for CLI commands.
3
+ *
4
+ * Parses structured permission-denied responses (from server/lib/permissions.ts)
5
+ * and outputs structured JSON guidance to stderr so agents can self-escalate.
6
+ */
7
+
8
+ interface EscalationInfo {
9
+ via?: string;
10
+ permissions?: string[];
11
+ dashboard?: string;
12
+ note?: string;
13
+ }
14
+
15
+ interface PermissionDeniedBody {
16
+ error?: string;
17
+ required?: string[];
18
+ have?: string[];
19
+ escalation?: EscalationInfo;
20
+ }
21
+
22
+ function isPermissionDeniedBody(body: unknown): body is PermissionDeniedBody {
23
+ if (!body || typeof body !== 'object' || Array.isArray(body)) return false;
24
+ const obj = body as Record<string, unknown>;
25
+ return typeof obj.error === 'string' && obj.escalation !== undefined;
26
+ }
27
+
28
+ /**
29
+ * Detect a 403 permission-denied response and print structured JSON guidance.
30
+ *
31
+ * Returns `true` if the error was handled (caller should exit),
32
+ * `false` if this isn't a permission-denied response.
33
+ */
34
+ export function handlePermissionDenied(status: number, body: unknown): boolean {
35
+ if (status !== 403) return false;
36
+ if (!isPermissionDeniedBody(body)) return false;
37
+
38
+ const esc = body.escalation;
39
+ if (!esc || esc.via !== 'auth') return false;
40
+
41
+ const perms = esc.permissions || body.required || [];
42
+ const needsAdmin = perms.includes('admin:*');
43
+ const suggestedProfile = needsAdmin ? 'admin' : 'strict';
44
+
45
+ const guidance = {
46
+ error: body.error,
47
+ status: 403,
48
+ requiresHumanApproval: true,
49
+ approveAt: esc.dashboard || 'http://localhost:4747',
50
+ nextStep: `npx auramaxx auth request --profile ${suggestedProfile}`,
51
+ required: perms,
52
+ ...(body.have ? { have: body.have } : {}),
53
+ };
54
+
55
+ console.error(JSON.stringify(guidance, null, 2));
56
+ return true;
57
+ }