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,419 @@
1
+ import { createHash } from 'crypto';
2
+ import { expandPermissions } from './permissions';
3
+ import type { AgentTokenPayload } from '../types';
4
+ import { normalizeScope } from './credential-scope';
5
+
6
+ export type AgentProfileId =
7
+ | 'strict'
8
+ | 'dev'
9
+ | 'admin';
10
+
11
+ export const LOCAL_AGENT_PROFILE_MODES = ['strict', 'dev', 'admin'] as const;
12
+ export type LocalAgentProfileMode = typeof LOCAL_AGENT_PROFILE_MODES[number];
13
+
14
+ export interface AgentPolicyProfileV1 {
15
+ id: AgentProfileId;
16
+ version: 'v1';
17
+ displayName: string;
18
+ description: string;
19
+ rationale: string;
20
+ permissions: string[];
21
+ credentialAccess: {
22
+ readScopes: string[];
23
+ writeScopes: string[];
24
+ excludeFields: string[];
25
+ maxReads?: number;
26
+ };
27
+ tokenDefaults: {
28
+ ttlSeconds: number;
29
+ maxReads?: number;
30
+ };
31
+ warnings: string[];
32
+ }
33
+
34
+ export interface ResolveProfileInput {
35
+ profileId: string;
36
+ profileVersion?: string;
37
+ overrides?: {
38
+ ttlSeconds?: number;
39
+ maxReads?: number;
40
+ scope?: string[];
41
+ readScopes?: string[];
42
+ writeScopes?: string[];
43
+ excludeFields?: string[];
44
+ };
45
+ }
46
+
47
+ export interface ResolvedProfilePolicy {
48
+ profile: {
49
+ id: AgentProfileId;
50
+ version: 'v1';
51
+ displayName: string;
52
+ rationale: string;
53
+ };
54
+ permissions: string[];
55
+ ttlSeconds: number;
56
+ credentialAccess: NonNullable<AgentTokenPayload['credentialAccess']>;
57
+ warnings: string[];
58
+ overrideDelta: string[];
59
+ effectivePolicyHash: string;
60
+ }
61
+
62
+ export class AgentProfileError extends Error {
63
+ code: string;
64
+ constructor(code: string, message: string) {
65
+ super(message);
66
+ this.name = 'AgentProfileError';
67
+ this.code = code;
68
+ }
69
+ }
70
+
71
+ const BUILTIN_PROFILES: AgentPolicyProfileV1[] = [
72
+ {
73
+ id: 'strict',
74
+ version: 'v1',
75
+ displayName: 'Strict',
76
+ description: 'Manual-approval-first baseline profile for local agents.',
77
+ rationale: 'Use when you want every agent token request reviewed by a human.',
78
+ permissions: ['secret:read'],
79
+ credentialAccess: {
80
+ readScopes: ['vault:agent'],
81
+ writeScopes: [],
82
+ excludeFields: ['password', 'cvv', 'privateKey', 'seedPhrase', 'refresh_token'],
83
+ maxReads: 50,
84
+ },
85
+ tokenDefaults: { ttlSeconds: 900, maxReads: 50 },
86
+ warnings: ['Pair with trust.localAutoApprove=false for strict local approval flow. Credential access is limited to the agent vault only.'],
87
+ },
88
+ {
89
+ id: 'dev',
90
+ version: 'v1',
91
+ displayName: 'Dev',
92
+ description: 'Developer profile for local agent automation with non-financial scope.',
93
+ rationale: 'Use for day-to-day local dev workflows without granting financial operations.',
94
+ permissions: ['wallet:list', 'secret:read', 'secret:write', 'action:create', 'action:read', 'action:resolve'],
95
+ credentialAccess: {
96
+ readScopes: ['vault:*'],
97
+ writeScopes: ['vault:*'],
98
+ excludeFields: ['cvv', 'seedPhrase', 'privateKey', 'refresh_token'],
99
+ maxReads: 500,
100
+ },
101
+ tokenDefaults: { ttlSeconds: 3600, maxReads: 500 },
102
+ warnings: ['Includes secret:write. Prefer dedicated non-primary vaults for agent-managed credentials.'],
103
+ },
104
+ {
105
+ id: 'admin',
106
+ version: 'v1',
107
+ displayName: 'Admin',
108
+ description: 'Full-access local agent profile.',
109
+ rationale: 'Use only when you explicitly trust the local agent process with broad access.',
110
+ permissions: ['admin:*'],
111
+ credentialAccess: {
112
+ readScopes: ['*'],
113
+ writeScopes: ['*'],
114
+ excludeFields: [],
115
+ },
116
+ tokenDefaults: { ttlSeconds: 3600 },
117
+ warnings: ['Dangerous profile: grants admin:* (full access). Not recommended for primary vault workflows.'],
118
+ },
119
+ ];
120
+
121
+ export function listProfiles(): AgentPolicyProfileV1[] {
122
+ return BUILTIN_PROFILES.map((p) => ({ ...p }));
123
+ }
124
+
125
+ export function getProfile(id: string, version: string = 'v1'): AgentPolicyProfileV1 {
126
+ const anyVersion = BUILTIN_PROFILES.find((p) => p.id === id);
127
+ if (!anyVersion) {
128
+ throw new AgentProfileError('AGENT_PROFILE_NOT_FOUND', `Unknown profile: ${id}@${version}`);
129
+ }
130
+ const profile = BUILTIN_PROFILES.find((p) => p.id === id && p.version === version);
131
+ if (!profile) {
132
+ throw new AgentProfileError('AGENT_PROFILE_VERSION_UNSUPPORTED', `Unsupported profile version: ${id}@${version}`);
133
+ }
134
+ return profile;
135
+ }
136
+
137
+ function canonicalScopes(scopes: string[] | undefined): string[] {
138
+ const normalized = (scopes || []).map(normalizeScope);
139
+ if (normalized.some((scope) => scope.includes('**') || scope.includes('(') || scope.includes(')'))) {
140
+ throw new AgentProfileError('AGENT_PROFILE_SCOPE_INVALID', 'Scope selectors contain unsupported wildcard/pattern syntax');
141
+ }
142
+ return Array.from(new Set(normalized)).sort();
143
+ }
144
+
145
+ function canonicalStringList(values: string[] | undefined): string[] {
146
+ return Array.from(new Set((values || []).map((v) => v.trim()).filter(Boolean))).sort();
147
+ }
148
+
149
+ function hashPolicy(policy: Omit<ResolvedProfilePolicy, 'effectivePolicyHash'>): string {
150
+ const stable = JSON.stringify({
151
+ permissions: Array.from(new Set(policy.permissions)).sort(),
152
+ credentialAccess: {
153
+ read: Array.from(new Set(policy.credentialAccess.read || [])).sort(),
154
+ write: Array.from(new Set(policy.credentialAccess.write || [])).sort(),
155
+ excludeFields: Array.from(new Set(policy.credentialAccess.excludeFields || [])).sort(),
156
+ maxReads: typeof policy.credentialAccess.maxReads === 'number' ? policy.credentialAccess.maxReads : null,
157
+ },
158
+ ttlSeconds: policy.ttlSeconds,
159
+ maxReads: typeof policy.credentialAccess.maxReads === 'number' ? policy.credentialAccess.maxReads : null,
160
+ rateBudget: {
161
+ state: 'none',
162
+ requests: null,
163
+ windowSeconds: null,
164
+ source: 'none',
165
+ },
166
+ });
167
+ return createHash('sha256').update(stable).digest('hex');
168
+ }
169
+
170
+ export function resolveProfileToEffectivePolicy(input: ResolveProfileInput): ResolvedProfilePolicy {
171
+ const profile = getProfile(input.profileId, input.profileVersion || 'v1');
172
+ const overrides = input.overrides || {};
173
+
174
+ const profileReadScopes = canonicalScopes(profile.credentialAccess.readScopes);
175
+ const profileWriteScopes = canonicalScopes(profile.credentialAccess.writeScopes);
176
+ const profileExclude = canonicalStringList(profile.credentialAccess.excludeFields);
177
+
178
+ const overrideDelta: string[] = [];
179
+
180
+ let ttlSeconds = profile.tokenDefaults.ttlSeconds;
181
+ if (overrides.ttlSeconds !== undefined) {
182
+ if (overrides.ttlSeconds <= 0 || overrides.ttlSeconds > ttlSeconds) {
183
+ throw new AgentProfileError('AGENT_PROFILE_OVERRIDE_NOT_ALLOWED', 'ttlSeconds override must be positive and tighten-only');
184
+ }
185
+ ttlSeconds = overrides.ttlSeconds;
186
+ overrideDelta.push('ttlSeconds');
187
+ }
188
+
189
+ let maxReads = profile.credentialAccess.maxReads ?? profile.tokenDefaults.maxReads;
190
+ if (overrides.maxReads !== undefined) {
191
+ if (overrides.maxReads <= 0 || (typeof maxReads === 'number' && overrides.maxReads > maxReads)) {
192
+ throw new AgentProfileError('AGENT_PROFILE_OVERRIDE_NOT_ALLOWED', 'maxReads override must be positive and tighten-only');
193
+ }
194
+ maxReads = overrides.maxReads;
195
+ overrideDelta.push('maxReads');
196
+ }
197
+
198
+ const profilePermissions = Array.from(new Set(expandPermissions(profile.permissions))).sort();
199
+ const scopePermissions = overrides.scope ? Array.from(new Set(expandPermissions(overrides.scope))).sort() : profilePermissions;
200
+ if (overrides.scope) {
201
+ if (!scopePermissions.every((permission) => profilePermissions.includes(permission))) {
202
+ throw new AgentProfileError('AGENT_PROFILE_OVERRIDE_NOT_ALLOWED', 'scope override cannot broaden profile permissions');
203
+ }
204
+ overrideDelta.push('scope');
205
+ }
206
+
207
+ const readScopes = overrides.readScopes ? canonicalScopes(overrides.readScopes) : profileReadScopes;
208
+ const writeScopes = overrides.writeScopes ? canonicalScopes(overrides.writeScopes) : profileWriteScopes;
209
+
210
+ if (overrides.readScopes) {
211
+ if (!readScopes.every((scope) => profileReadScopes.includes(scope))) {
212
+ throw new AgentProfileError('AGENT_PROFILE_OVERRIDE_NOT_ALLOWED', 'readScopes override cannot broaden profile scope');
213
+ }
214
+ overrideDelta.push('readScopes');
215
+ }
216
+
217
+ if (overrides.writeScopes) {
218
+ if (!writeScopes.every((scope) => profileWriteScopes.includes(scope))) {
219
+ throw new AgentProfileError('AGENT_PROFILE_OVERRIDE_NOT_ALLOWED', 'writeScopes override cannot broaden profile scope');
220
+ }
221
+ overrideDelta.push('writeScopes');
222
+ }
223
+
224
+ if (readScopes.length === 0 && writeScopes.length === 0) {
225
+ throw new AgentProfileError('AGENT_PROFILE_SCOPE_EMPTY', 'Resolved profile selectors are empty');
226
+ }
227
+
228
+ const excludeFields = overrides.excludeFields ? canonicalStringList(overrides.excludeFields) : profileExclude;
229
+ if (overrides.excludeFields) {
230
+ const attemptedRemoval = profileExclude.some((field) => !excludeFields.includes(field));
231
+ if (attemptedRemoval) {
232
+ throw new AgentProfileError('AGENT_PROFILE_OVERRIDE_NOT_ALLOWED', 'excludeFields override cannot remove profile-required exclusions');
233
+ }
234
+ overrideDelta.push('excludeFields');
235
+ }
236
+
237
+ const resolvedBase: Omit<ResolvedProfilePolicy, 'effectivePolicyHash'> = {
238
+ profile: {
239
+ id: profile.id,
240
+ version: profile.version,
241
+ displayName: profile.displayName,
242
+ rationale: profile.rationale,
243
+ },
244
+ permissions: scopePermissions,
245
+ ttlSeconds,
246
+ credentialAccess: {
247
+ read: readScopes,
248
+ write: writeScopes,
249
+ excludeFields,
250
+ ...(typeof maxReads === 'number' ? { maxReads } : {}),
251
+ },
252
+ warnings: [...profile.warnings],
253
+ overrideDelta: Array.from(new Set(overrideDelta)).sort(),
254
+ };
255
+
256
+ return {
257
+ ...resolvedBase,
258
+ effectivePolicyHash: hashPolicy(resolvedBase),
259
+ };
260
+ }
261
+
262
+ export function summarizeEffectivePolicy(policy: ResolvedProfilePolicy): string {
263
+ return [
264
+ `${policy.profile.id}@${policy.profile.version}`,
265
+ `permissions=${policy.permissions.join(',')}`,
266
+ `ttl=${policy.ttlSeconds}s`,
267
+ `read=${(policy.credentialAccess.read || []).join(',') || 'none'}`,
268
+ `write=${(policy.credentialAccess.write || []).join(',') || 'none'}`,
269
+ ].join(' | ');
270
+ }
271
+
272
+ // ---------------------------------------------------------------------------
273
+ // Human-readable profile/policy descriptions
274
+ // ---------------------------------------------------------------------------
275
+
276
+ const PERMISSION_DESCRIPTIONS: Record<string, string> = {
277
+ 'swap': 'Swap tokens via DEX',
278
+ 'send:hot': 'Send from hot wallets',
279
+ 'send:temp': 'Send from temp wallets',
280
+ 'fund': 'Transfer cold → hot',
281
+ 'launch': 'Launch tokens',
282
+ 'wallet:create:hot': 'Create hot wallets',
283
+ 'wallet:create:temp': 'Create temp wallets',
284
+ 'wallet:export': 'Export private keys',
285
+ 'wallet:list': 'List wallets',
286
+ 'wallet:rename': 'Rename wallets',
287
+ 'wallet:write': 'Modify wallets',
288
+ 'wallet:tx:add': 'Add transactions',
289
+ 'wallet:asset:add': 'Add assets',
290
+ 'wallet:asset:remove': 'Remove assets',
291
+ 'secret:read': 'Read credentials',
292
+ 'secret:write': 'Write credentials',
293
+ 'totp:read': 'Read TOTP codes',
294
+ 'action:create': 'Create action requests',
295
+ 'action:read': 'View action requests',
296
+ 'action:resolve': 'Approve/reject requests',
297
+ 'apikey:get': 'Read API keys',
298
+ 'apikey:set': 'Manage API keys',
299
+ 'workspace:modify': 'Modify workspace',
300
+ 'strategy:read': 'View strategies',
301
+ 'strategy:manage': 'Manage strategies',
302
+ 'app:storage': 'App storage',
303
+ 'app:storage:all': 'Full app storage',
304
+ 'app:accesskey': 'App access keys',
305
+ 'adapter:manage': 'Manage adapters',
306
+ 'addressbook:write': 'Modify address book',
307
+ 'bookmark:write': 'Modify bookmarks',
308
+ 'trade:all': 'All trading operations',
309
+ 'extension:*': 'All extensions',
310
+ 'admin:*': 'Full admin access',
311
+ };
312
+
313
+ function formatTtlReadable(seconds: number): string {
314
+ if (seconds < 60) return `${seconds} seconds`;
315
+ if (seconds < 3600) return `${Math.floor(seconds / 60)} minutes`;
316
+ const hours = Math.floor(seconds / 3600);
317
+ return hours === 1 ? '1 hour' : `${hours} hours`;
318
+ }
319
+
320
+ function describeScopes(scopes: string[]): string {
321
+ if (scopes.length === 0) return 'None';
322
+ if (scopes.includes('*')) return 'All credentials';
323
+ if (scopes.length === 1 && scopes[0] === 'vault:*') return 'All vaults';
324
+ return scopes.map((s) => {
325
+ if (s.startsWith('vault:')) return `Vault "${s.slice(6)}"`;
326
+ if (s.startsWith('tag:')) return `Tag "${s.slice(4)}"`;
327
+ return s;
328
+ }).join(', ');
329
+ }
330
+
331
+ export interface ProfileDescription {
332
+ name: string;
333
+ summary: string;
334
+ permissions: string[];
335
+ vaultAccess: string;
336
+ canWrite: boolean;
337
+ hiddenFields: string[];
338
+ ttl: string;
339
+ maxReads: string;
340
+ warnings: string[];
341
+ }
342
+
343
+ export function describeProfile(profileId: string, version: string = 'v1'): ProfileDescription {
344
+ const profile = getProfile(profileId, version);
345
+ return describeProfilePolicy(profile);
346
+ }
347
+
348
+ export function describeProfilePolicy(profile: AgentPolicyProfileV1): ProfileDescription {
349
+ const permissions = profile.permissions.map((p) => PERMISSION_DESCRIPTIONS[p] || p);
350
+ const readScopes = profile.credentialAccess.readScopes;
351
+ const writeScopes = profile.credentialAccess.writeScopes;
352
+ const canWrite = writeScopes.length > 0;
353
+ const vaultAccess = describeScopes(readScopes);
354
+ const hiddenFields = profile.credentialAccess.excludeFields;
355
+ const ttl = formatTtlReadable(profile.tokenDefaults.ttlSeconds);
356
+ const maxReads = profile.credentialAccess.maxReads
357
+ ? `${profile.credentialAccess.maxReads} reads`
358
+ : 'Unlimited';
359
+
360
+ const parts: string[] = [];
361
+ parts.push(profile.description);
362
+ if (canWrite) {
363
+ parts.push(`Read/write access to ${vaultAccess.toLowerCase()}.`);
364
+ } else {
365
+ parts.push(`Read-only access to ${vaultAccess.toLowerCase()}.`);
366
+ }
367
+ if (hiddenFields.length > 0) {
368
+ parts.push(`Hidden fields: ${hiddenFields.join(', ')}.`);
369
+ }
370
+ parts.push(`Expires after ${ttl}.`);
371
+
372
+ return {
373
+ name: profile.displayName,
374
+ summary: parts.join(' '),
375
+ permissions,
376
+ vaultAccess,
377
+ canWrite,
378
+ hiddenFields,
379
+ ttl,
380
+ maxReads,
381
+ warnings: profile.warnings,
382
+ };
383
+ }
384
+
385
+ export function describeResolvedPolicy(policy: ResolvedProfilePolicy): ProfileDescription {
386
+ const permissions = policy.permissions.map((p) => PERMISSION_DESCRIPTIONS[p] || p);
387
+ const readScopes = policy.credentialAccess.read || [];
388
+ const writeScopes = policy.credentialAccess.write || [];
389
+ const canWrite = writeScopes.length > 0;
390
+ const vaultAccess = describeScopes(readScopes);
391
+ const hiddenFields = policy.credentialAccess.excludeFields || [];
392
+ const ttl = formatTtlReadable(policy.ttlSeconds);
393
+ const maxReads = typeof policy.credentialAccess.maxReads === 'number'
394
+ ? `${policy.credentialAccess.maxReads} reads`
395
+ : 'Unlimited';
396
+
397
+ const parts: string[] = [];
398
+ if (canWrite) {
399
+ parts.push(`Read/write access to ${vaultAccess.toLowerCase()}.`);
400
+ } else {
401
+ parts.push(`Read-only access to ${vaultAccess.toLowerCase()}.`);
402
+ }
403
+ if (hiddenFields.length > 0) {
404
+ parts.push(`Hidden fields: ${hiddenFields.join(', ')}.`);
405
+ }
406
+ parts.push(`Expires after ${ttl}.`);
407
+
408
+ return {
409
+ name: policy.profile.displayName,
410
+ summary: parts.join(' '),
411
+ permissions,
412
+ vaultAccess,
413
+ canWrite,
414
+ hiddenFields,
415
+ ttl,
416
+ maxReads,
417
+ warnings: policy.warnings,
418
+ };
419
+ }
@@ -0,0 +1,285 @@
1
+ /**
2
+ * AI Client Module
3
+ * ================
4
+ * Multi-provider AI abstraction supporting:
5
+ * - Claude CLI (subscription/OAuth via `claude` binary)
6
+ * - Claude API (direct Anthropic SDK with API key)
7
+ * - Codex CLI (OpenAI's `codex` binary)
8
+ * - OpenAI API (direct OpenAI SDK with API key)
9
+ *
10
+ * Provider selection read from system defaults (ai.provider); model auto-derived per provider.
11
+ * Extracted from strategy/hooks.ts so it's reusable beyond strategies.
12
+ */
13
+
14
+ import Anthropic from '@anthropic-ai/sdk';
15
+ import OpenAI from 'openai';
16
+ import { execFile } from 'child_process';
17
+ import {
18
+ ensureApiKeysMigrated,
19
+ hasActiveApiKeyCredential,
20
+ readApiKeyValueByService,
21
+ } from './apikey-migration';
22
+ import { getDefault, onDefaultChanged } from './defaults';
23
+
24
+ // ─── Types ─────────────────────────────────────────────────────────
25
+
26
+ export type AiProviderMode = 'claude-cli' | 'claude-api' | 'codex-cli' | 'openai-api';
27
+
28
+ export type ModelTier = 'fast' | 'standard' | 'powerful';
29
+
30
+ export interface ProviderStatus {
31
+ mode: AiProviderMode;
32
+ label: string;
33
+ available: boolean;
34
+ reason: string;
35
+ models: string[];
36
+ }
37
+
38
+ // ─── Model Maps ────────────────────────────────────────────────────
39
+
40
+ /** Claude model short names → full SDK model IDs */
41
+ const MODEL_MAP: Record<string, string> = {
42
+ haiku: 'claude-haiku-4-5-20251001',
43
+ sonnet: 'claude-sonnet-4-5-20250929',
44
+ opus: 'claude-opus-4-6',
45
+ };
46
+
47
+ /** Codex/OpenAI model short names → full SDK model IDs */
48
+ const CODEX_MODEL_MAP: Record<string, string> = {
49
+ 'codex-mini': 'gpt-5.1-codex-mini',
50
+ 'codex': 'gpt-5.3-codex',
51
+ 'codex-max': 'gpt-5.1-codex-max',
52
+ };
53
+
54
+ /** Available models per provider */
55
+ export const PROVIDER_MODELS: Record<AiProviderMode, string[]> = {
56
+ 'claude-cli': ['haiku', 'sonnet', 'opus'],
57
+ 'claude-api': ['haiku', 'sonnet', 'opus'],
58
+ 'codex-cli': ['codex-mini', 'codex', 'codex-max'],
59
+ 'openai-api': ['codex-mini', 'codex', 'codex-max'],
60
+ };
61
+
62
+ /** Model tiers per provider — fast/standard/powerful mapped to model short names */
63
+ export const MODEL_TIERS: Record<AiProviderMode, Record<ModelTier, string>> = {
64
+ 'claude-cli': { fast: 'haiku', standard: 'sonnet', powerful: 'opus' },
65
+ 'claude-api': { fast: 'haiku', standard: 'sonnet', powerful: 'opus' },
66
+ 'codex-cli': { fast: 'codex-mini', standard: 'codex', powerful: 'codex-max' },
67
+ 'openai-api': { fast: 'codex-mini', standard: 'codex', powerful: 'codex-max' },
68
+ };
69
+
70
+ /** Permissions that trigger the 'powerful' tier (financial operations) */
71
+ const POWERFUL_PERMISSIONS = new Set(['swap', 'send:hot', 'send:temp', 'fund', 'launch', 'admin:*']);
72
+
73
+ /** Permissions that trigger the 'standard' tier (write operations) */
74
+ const STANDARD_PERMISSIONS = new Set(['wallet:create:hot', 'wallet:create:temp']);
75
+
76
+ /**
77
+ * Select a model tier based on hook name and token permissions.
78
+ * - init/shutdown → fast (lightweight lifecycle hooks)
79
+ * - Financial or admin permissions → powerful
80
+ * - Write permissions → standard
81
+ * - No token or read-only → fast
82
+ */
83
+ export function selectModelTier(hookName: string, permissions: string[]): ModelTier {
84
+ // Lifecycle hooks always use fast tier
85
+ if (hookName === 'init' || hookName === 'shutdown') return 'fast';
86
+
87
+ // Check for powerful-tier permissions
88
+ for (const perm of permissions) {
89
+ if (POWERFUL_PERMISSIONS.has(perm)) return 'powerful';
90
+ }
91
+
92
+ // Check for standard-tier permissions
93
+ for (const perm of permissions) {
94
+ if (STANDARD_PERMISSIONS.has(perm)) return 'standard';
95
+ }
96
+
97
+ // No token or read-only permissions
98
+ return 'fast';
99
+ }
100
+
101
+ // ─── Cached Clients ────────────────────────────────────────────────
102
+
103
+ let cachedAnthropicClient: Anthropic | null = null;
104
+ let cachedOpenAiClient: OpenAI | null = null;
105
+
106
+ /** @internal Reset cached clients (for testing only) */
107
+ export function __resetCachedClient() {
108
+ cachedAnthropicClient = null;
109
+ cachedOpenAiClient = null;
110
+ }
111
+
112
+ // Reset cached clients when provider changes
113
+ onDefaultChanged('ai.provider', () => {
114
+ cachedAnthropicClient = null;
115
+ cachedOpenAiClient = null;
116
+ });
117
+
118
+ // ─── Provider & Model Selection ────────────────────────────────────
119
+
120
+ /**
121
+ * Get the active AI provider mode from system defaults.
122
+ */
123
+ export async function getProviderMode(): Promise<AiProviderMode> {
124
+ return getDefault<AiProviderMode>('ai.provider', 'claude-cli');
125
+ }
126
+
127
+ /**
128
+ * Get the system-wide default model (standard tier), derived from the active provider.
129
+ */
130
+ export async function getDefaultModel(): Promise<string> {
131
+ const provider = await getProviderMode();
132
+ return MODEL_TIERS[provider].standard;
133
+ }
134
+
135
+ /**
136
+ * Resolve a model short name to the full SDK model ID based on provider.
137
+ * CLI modes return the short name directly (CLIs handle them natively).
138
+ * API modes map to full model IDs.
139
+ */
140
+ export function resolveModelId(name: string, provider: AiProviderMode): string {
141
+ switch (provider) {
142
+ case 'claude-cli':
143
+ return name; // Claude CLI handles short names natively
144
+ case 'claude-api':
145
+ return MODEL_MAP[name] || name;
146
+ case 'codex-cli':
147
+ return name; // Codex CLI handles short names natively
148
+ case 'openai-api':
149
+ return CODEX_MODEL_MAP[name] || name;
150
+ default:
151
+ return name;
152
+ }
153
+ }
154
+
155
+ // ─── SDK Clients ───────────────────────────────────────────────────
156
+
157
+ /**
158
+ * Get an Anthropic SDK client (for claude-api mode).
159
+ * Priority: ANTHROPIC_API_KEY env → vault API key credential (service: 'anthropic').
160
+ */
161
+ export async function getAnthropicClient(): Promise<Anthropic> {
162
+ if (cachedAnthropicClient) return cachedAnthropicClient;
163
+
164
+ if (process.env.ANTHROPIC_API_KEY) {
165
+ cachedAnthropicClient = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
166
+ return cachedAnthropicClient;
167
+ }
168
+
169
+ await ensureApiKeysMigrated();
170
+ const apiKey = readApiKeyValueByService('anthropic');
171
+ if (apiKey) {
172
+ cachedAnthropicClient = new Anthropic({ apiKey });
173
+ return cachedAnthropicClient;
174
+ }
175
+
176
+ throw new Error(
177
+ 'No Anthropic credentials found. Add an API key via Settings, set ANTHROPIC_API_KEY, or use Claude CLI mode (subscription).'
178
+ );
179
+ }
180
+
181
+ /**
182
+ * Get an OpenAI SDK client (for openai-api mode).
183
+ * Priority: OPENAI_API_KEY env → vault API key credential (service: 'openai').
184
+ */
185
+ export async function getOpenAiClient(): Promise<OpenAI> {
186
+ if (cachedOpenAiClient) return cachedOpenAiClient;
187
+
188
+ if (process.env.OPENAI_API_KEY) {
189
+ cachedOpenAiClient = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
190
+ return cachedOpenAiClient;
191
+ }
192
+
193
+ await ensureApiKeysMigrated();
194
+ const apiKey = readApiKeyValueByService('openai');
195
+ if (apiKey) {
196
+ cachedOpenAiClient = new OpenAI({ apiKey });
197
+ return cachedOpenAiClient;
198
+ }
199
+
200
+ throw new Error(
201
+ 'No OpenAI credentials found. Add an API key via Settings or set OPENAI_API_KEY.'
202
+ );
203
+ }
204
+
205
+ // ─── Provider Status ───────────────────────────────────────────────
206
+
207
+ /** Check if a CLI binary is available in PATH */
208
+ function checkCliAvailable(binary: string): Promise<boolean> {
209
+ return new Promise((resolve) => {
210
+ execFile('which', [binary], (err) => {
211
+ resolve(!err);
212
+ });
213
+ });
214
+ }
215
+
216
+ /** Check if an API key exists for a service (env or vault credential) */
217
+ async function hasApiKey(service: string, envVar: string): Promise<boolean> {
218
+ if (process.env[envVar]) return true;
219
+ await ensureApiKeysMigrated();
220
+ return hasActiveApiKeyCredential(service);
221
+ }
222
+
223
+ /**
224
+ * Get availability status for each provider.
225
+ * Used by the dashboard UI to show status indicators.
226
+ */
227
+ export async function getProviderStatus(): Promise<ProviderStatus[]> {
228
+ const [claudeCliOk, codexCliOk, anthropicKeyOk, openaiKeyOk] = await Promise.all([
229
+ checkCliAvailable('claude'),
230
+ checkCliAvailable('codex'),
231
+ hasApiKey('anthropic', 'ANTHROPIC_API_KEY'),
232
+ hasApiKey('openai', 'OPENAI_API_KEY'),
233
+ ]);
234
+
235
+ return [
236
+ {
237
+ mode: 'claude-cli',
238
+ label: 'Claude Max (CLI)',
239
+ available: claudeCliOk,
240
+ reason: claudeCliOk ? 'claude CLI found in PATH' : 'claude CLI not found in PATH',
241
+ models: PROVIDER_MODELS['claude-cli'],
242
+ },
243
+ {
244
+ mode: 'claude-api',
245
+ label: 'Claude API Key',
246
+ available: anthropicKeyOk,
247
+ reason: anthropicKeyOk ? 'Anthropic API key configured' : 'No Anthropic API key configured',
248
+ models: PROVIDER_MODELS['claude-api'],
249
+ },
250
+ {
251
+ mode: 'codex-cli',
252
+ label: 'Codex Max (CLI)',
253
+ available: codexCliOk,
254
+ reason: codexCliOk ? 'codex CLI found in PATH' : 'codex CLI not found in PATH',
255
+ models: PROVIDER_MODELS['codex-cli'],
256
+ },
257
+ {
258
+ mode: 'openai-api',
259
+ label: 'OpenAI API Key',
260
+ available: openaiKeyOk,
261
+ reason: openaiKeyOk ? 'OpenAI API key configured' : 'No OpenAI API key configured',
262
+ models: PROVIDER_MODELS['openai-api'],
263
+ },
264
+ ];
265
+ }
266
+
267
+ // ─── Backward Compatibility ────────────────────────────────────────
268
+
269
+ /**
270
+ * @deprecated Use getProviderMode() instead.
271
+ * Kept temporarily for backward compatibility.
272
+ */
273
+ export async function shouldUseCli(): Promise<boolean> {
274
+ const mode = await getProviderMode();
275
+ return mode === 'claude-cli' || mode === 'codex-cli';
276
+ }
277
+
278
+ /**
279
+ * @deprecated Use getProviderMode() instead.
280
+ * Returns 'sdk' if provider uses direct API, 'cli' otherwise.
281
+ */
282
+ export async function getAiProvider(): Promise<'sdk' | 'cli'> {
283
+ const mode = await getProviderMode();
284
+ return (mode === 'claude-api' || mode === 'openai-api') ? 'sdk' : 'cli';
285
+ }