auramaxx 1.0.0-alpha.4

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 (363) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +112 -0
  3. package/bin/aurawallet.js +121 -0
  4. package/docs/ADAPTERS.md +467 -0
  5. package/docs/API.md +2679 -0
  6. package/docs/APPS.md +198 -0
  7. package/docs/ARCHITECTURE.md +350 -0
  8. package/docs/AUTH.md +698 -0
  9. package/docs/BEST-PRACTICES.md +121 -0
  10. package/docs/CLI.md +61 -0
  11. package/docs/DEVELOPING-APPS.md +452 -0
  12. package/docs/EXTENSION.md +97 -0
  13. package/docs/JOBS.md +33 -0
  14. package/docs/MCP.md +76 -0
  15. package/docs/PROTOCOL.md +142 -0
  16. package/docs/SETUP.md +219 -0
  17. package/docs/WORKSPACE.md +672 -0
  18. package/docs/agent-auth.md +63 -0
  19. package/docs/aura-file.md +48 -0
  20. package/docs/credentials.md +53 -0
  21. package/docs/external/getting-started.md +65 -0
  22. package/docs/external/overview.md +45 -0
  23. package/docs/external/use-cases.md +48 -0
  24. package/docs/external/why-aura.md +35 -0
  25. package/docs/jobs/connect-agent.md +77 -0
  26. package/docs/jobs/migrate-from-dotenv.md +79 -0
  27. package/docs/jobs/recover-from-lockout.md +72 -0
  28. package/docs/jobs/secure-ci.md +63 -0
  29. package/docs/oauth2.md +42 -0
  30. package/docs/passkeys.md +60 -0
  31. package/docs/security.md +540 -0
  32. package/docs/specs/aura-open-protocol.md +61 -0
  33. package/docs/specs/aura-provider-plugin.md +24 -0
  34. package/docs/specs/aura-registry-model.md +31 -0
  35. package/docs/specs/fixtures/invalid-bad-key.aura +1 -0
  36. package/docs/specs/fixtures/invalid-bad-unicode-escape.aura +1 -0
  37. package/docs/specs/fixtures/invalid-duplicate-key.aura +2 -0
  38. package/docs/specs/fixtures/valid-basic.aura +4 -0
  39. package/docs/specs/fixtures/valid-provider-ref.aura +1 -0
  40. package/docs/specs/fixtures/valid-quoted-escapes.aura +2 -0
  41. package/docs/templates/RELEASE_NOTES_TEMPLATE.md +22 -0
  42. package/docs/totp.md +40 -0
  43. package/docs/wallet/AI.md +508 -0
  44. package/docs/wallet/DEVELOPING-STRATEGIES.md +713 -0
  45. package/docs/wallet/README.md +47 -0
  46. package/docs/wallet/STRATEGY.md +89 -0
  47. package/next.config.ts +21 -0
  48. package/package.json +151 -0
  49. package/postcss.config.mjs +8 -0
  50. package/prisma/migrations/20260214170000_baseline/migration.sql +511 -0
  51. package/prisma/migrations/20260216214537_add_passkey_model/migration.sql +18 -0
  52. package/prisma/migrations/20260217150500_add_credential_access_audit/migration.sql +31 -0
  53. package/prisma/migrations/migration_lock.toml +3 -0
  54. package/prisma/schema.prisma +447 -0
  55. package/public/logo-chevron.svg +31 -0
  56. package/public/logo-concentric.svg +31 -0
  57. package/public/logo-crosshatch.svg +39 -0
  58. package/public/logo-dashed.svg +39 -0
  59. package/public/logo-horizontal.svg +31 -0
  60. package/public/logo-m56.svg +64 -0
  61. package/public/logo.webp +0 -0
  62. package/scripts/add-app.js +245 -0
  63. package/scripts/init.sh +57 -0
  64. package/scripts/migrate-apikeys-to-credentials.ts +35 -0
  65. package/scripts/sandbox-agent-flow.sh +235 -0
  66. package/scripts/sandbox.sh +175 -0
  67. package/scripts/validate-job-docs.mjs +125 -0
  68. package/server/abi/SwapHelper.json +438 -0
  69. package/server/cli/approval.ts +447 -0
  70. package/server/cli/commands/app.ts +204 -0
  71. package/server/cli/commands/cron.ts +24 -0
  72. package/server/cli/commands/doctor.ts +1007 -0
  73. package/server/cli/commands/env.ts +456 -0
  74. package/server/cli/commands/init.ts +752 -0
  75. package/server/cli/commands/mcp.ts +125 -0
  76. package/server/cli/commands/restore.ts +314 -0
  77. package/server/cli/commands/shell-hook.ts +468 -0
  78. package/server/cli/commands/start.ts +62 -0
  79. package/server/cli/commands/status.ts +59 -0
  80. package/server/cli/commands/stop.ts +14 -0
  81. package/server/cli/commands/token.ts +180 -0
  82. package/server/cli/commands/unlock.ts +49 -0
  83. package/server/cli/commands/vault.ts +417 -0
  84. package/server/cli/index.ts +328 -0
  85. package/server/cli/lib/aura-parser.ts +64 -0
  86. package/server/cli/lib/credential-create.ts +74 -0
  87. package/server/cli/lib/credential-resolve.ts +254 -0
  88. package/server/cli/lib/dotenv-migrate.ts +116 -0
  89. package/server/cli/lib/dotenv-parser.ts +146 -0
  90. package/server/cli/lib/http.ts +91 -0
  91. package/server/cli/lib/init-steps.ts +76 -0
  92. package/server/cli/lib/local-agent-trust.ts +45 -0
  93. package/server/cli/lib/process.ts +136 -0
  94. package/server/cli/lib/prompt.ts +85 -0
  95. package/server/cli/lib/theme.ts +240 -0
  96. package/server/cli/socket.ts +570 -0
  97. package/server/cli/transport-client.ts +50 -0
  98. package/server/cron/index.ts +137 -0
  99. package/server/cron/job.ts +31 -0
  100. package/server/cron/jobs/balance-sync.ts +436 -0
  101. package/server/cron/jobs/incoming-scan.ts +506 -0
  102. package/server/cron/jobs/native-price.ts +70 -0
  103. package/server/cron/jobs/orphan-cleanup.ts +40 -0
  104. package/server/cron/jobs/strategy-runner.ts +175 -0
  105. package/server/cron/scheduler.ts +125 -0
  106. package/server/index.ts +406 -0
  107. package/server/lib/adapters/factory.ts +110 -0
  108. package/server/lib/adapters/index.ts +19 -0
  109. package/server/lib/adapters/router.ts +297 -0
  110. package/server/lib/adapters/telegram.ts +645 -0
  111. package/server/lib/adapters/types.ts +89 -0
  112. package/server/lib/adapters/webhook.ts +95 -0
  113. package/server/lib/address.ts +49 -0
  114. package/server/lib/agent-auth/contracts.ts +1194 -0
  115. package/server/lib/agent-profiles.ts +328 -0
  116. package/server/lib/ai.ts +285 -0
  117. package/server/lib/api-registry/contracts.ts +86 -0
  118. package/server/lib/api-registry/validation.ts +172 -0
  119. package/server/lib/apikey-migration.ts +189 -0
  120. package/server/lib/app-installer.ts +505 -0
  121. package/server/lib/app-tokens.ts +247 -0
  122. package/server/lib/auth.ts +314 -0
  123. package/server/lib/batch.ts +242 -0
  124. package/server/lib/cold.ts +874 -0
  125. package/server/lib/config.ts +381 -0
  126. package/server/lib/credential-access-audit.ts +85 -0
  127. package/server/lib/credential-access-policy.ts +110 -0
  128. package/server/lib/credential-health.ts +343 -0
  129. package/server/lib/credential-import.ts +487 -0
  130. package/server/lib/credential-scope.ts +87 -0
  131. package/server/lib/credential-shares.ts +190 -0
  132. package/server/lib/credential-transport.ts +342 -0
  133. package/server/lib/credential-vault.ts +77 -0
  134. package/server/lib/credentials.ts +333 -0
  135. package/server/lib/crypto.ts +8 -0
  136. package/server/lib/db.ts +15 -0
  137. package/server/lib/defaults.ts +366 -0
  138. package/server/lib/dex/index.ts +80 -0
  139. package/server/lib/dex/relay.ts +235 -0
  140. package/server/lib/dex/types.ts +59 -0
  141. package/server/lib/dex/uniswap.ts +370 -0
  142. package/server/lib/e2e-agent/artifacts.ts +36 -0
  143. package/server/lib/e2e-agent/contracts.ts +112 -0
  144. package/server/lib/e2e-agent/validation.ts +135 -0
  145. package/server/lib/encrypt.ts +128 -0
  146. package/server/lib/error.ts +20 -0
  147. package/server/lib/events.ts +205 -0
  148. package/server/lib/hot.ts +357 -0
  149. package/server/lib/key-fingerprint.ts +28 -0
  150. package/server/lib/logger.ts +331 -0
  151. package/server/lib/network.ts +137 -0
  152. package/server/lib/notifications.ts +219 -0
  153. package/server/lib/oauth2-refresh.ts +241 -0
  154. package/server/lib/oursecret.ts +54 -0
  155. package/server/lib/passkey-credential.ts +360 -0
  156. package/server/lib/passkey.ts +68 -0
  157. package/server/lib/permissions.ts +248 -0
  158. package/server/lib/pino.ts +24 -0
  159. package/server/lib/policy-preview.ts +138 -0
  160. package/server/lib/price.ts +338 -0
  161. package/server/lib/prices.ts +34 -0
  162. package/server/lib/project-scope.ts +239 -0
  163. package/server/lib/resolve-action.ts +427 -0
  164. package/server/lib/resolve.ts +36 -0
  165. package/server/lib/sessions.ts +632 -0
  166. package/server/lib/solana/connection.ts +26 -0
  167. package/server/lib/solana/jupiter.ts +128 -0
  168. package/server/lib/solana/transfer.ts +108 -0
  169. package/server/lib/solana/wallet.ts +136 -0
  170. package/server/lib/strategy/emits.ts +21 -0
  171. package/server/lib/strategy/engine.ts +1305 -0
  172. package/server/lib/strategy/executor.ts +115 -0
  173. package/server/lib/strategy/hook-context.ts +158 -0
  174. package/server/lib/strategy/hooks.ts +990 -0
  175. package/server/lib/strategy/index.ts +28 -0
  176. package/server/lib/strategy/installer.ts +305 -0
  177. package/server/lib/strategy/loader.ts +256 -0
  178. package/server/lib/strategy/message.ts +235 -0
  179. package/server/lib/strategy/repository.ts +218 -0
  180. package/server/lib/strategy/session-logger.ts +693 -0
  181. package/server/lib/strategy/sources.ts +288 -0
  182. package/server/lib/strategy/state.ts +189 -0
  183. package/server/lib/strategy/templates.ts +403 -0
  184. package/server/lib/strategy/tick.ts +404 -0
  185. package/server/lib/strategy/types.ts +230 -0
  186. package/server/lib/swap.ts +3 -0
  187. package/server/lib/temp.ts +86 -0
  188. package/server/lib/token-metadata.ts +86 -0
  189. package/server/lib/token-safety.ts +200 -0
  190. package/server/lib/token-search.ts +444 -0
  191. package/server/lib/totp.ts +194 -0
  192. package/server/lib/transactions.ts +123 -0
  193. package/server/lib/transport.ts +75 -0
  194. package/server/lib/txhistory/decoder.ts +262 -0
  195. package/server/lib/txhistory/enricher.ts +652 -0
  196. package/server/lib/txhistory/index.ts +391 -0
  197. package/server/lib/txhistory/signatures.ts +59 -0
  198. package/server/lib/verified-summary.ts +421 -0
  199. package/server/mcp/profile-policy.ts +30 -0
  200. package/server/mcp/server.ts +619 -0
  201. package/server/mcp/tools.ts +523 -0
  202. package/server/middleware/auth.ts +119 -0
  203. package/server/middleware/requestLogger.ts +84 -0
  204. package/server/routes/actions.ts +459 -0
  205. package/server/routes/adapters.ts +703 -0
  206. package/server/routes/addressbook.ts +113 -0
  207. package/server/routes/ai.ts +34 -0
  208. package/server/routes/apikeys.ts +295 -0
  209. package/server/routes/apps.ts +601 -0
  210. package/server/routes/auth.ts +457 -0
  211. package/server/routes/backup.ts +340 -0
  212. package/server/routes/batch.ts +270 -0
  213. package/server/routes/bookmarks.ts +162 -0
  214. package/server/routes/credential-shares.ts +198 -0
  215. package/server/routes/credential-vaults.ts +154 -0
  216. package/server/routes/credentials.ts +1290 -0
  217. package/server/routes/dashboard.ts +71 -0
  218. package/server/routes/defaults.ts +124 -0
  219. package/server/routes/fund.ts +229 -0
  220. package/server/routes/import.ts +352 -0
  221. package/server/routes/launch.ts +665 -0
  222. package/server/routes/lock.ts +54 -0
  223. package/server/routes/logs.ts +68 -0
  224. package/server/routes/nuke.ts +111 -0
  225. package/server/routes/passkey-credentials.ts +99 -0
  226. package/server/routes/passkey.ts +346 -0
  227. package/server/routes/portfolio.ts +217 -0
  228. package/server/routes/price.ts +63 -0
  229. package/server/routes/resolve.ts +31 -0
  230. package/server/routes/security.ts +45 -0
  231. package/server/routes/send-evm.ts +241 -0
  232. package/server/routes/send-solana.ts +281 -0
  233. package/server/routes/send.ts +178 -0
  234. package/server/routes/setup.ts +210 -0
  235. package/server/routes/strategy.ts +894 -0
  236. package/server/routes/swap-evm.ts +353 -0
  237. package/server/routes/swap-solana.ts +177 -0
  238. package/server/routes/swap.ts +356 -0
  239. package/server/routes/token.ts +247 -0
  240. package/server/routes/unlock.ts +403 -0
  241. package/server/routes/wallet-assets.ts +361 -0
  242. package/server/routes/wallet-transactions.ts +515 -0
  243. package/server/routes/wallet.ts +710 -0
  244. package/server/types.ts +146 -0
  245. package/skills/aurawallet/SKILL.md +739 -0
  246. package/skills/aurawallet-setup/SKILL.md +74 -0
  247. package/skills/security-review/SKILL.md +148 -0
  248. package/src/app/api/agent-requests/route.ts +30 -0
  249. package/src/app/api/apps/install/route.ts +126 -0
  250. package/src/app/api/apps/manifests/route.ts +16 -0
  251. package/src/app/api/apps/static/[...path]/route.ts +57 -0
  252. package/src/app/api/events/route.ts +92 -0
  253. package/src/app/api/page.tsx +212 -0
  254. package/src/app/api/workspace/[id]/apps/[wid]/route.ts +119 -0
  255. package/src/app/api/workspace/[id]/apps/route.ts +81 -0
  256. package/src/app/api/workspace/[id]/export/route.ts +67 -0
  257. package/src/app/api/workspace/[id]/route.ts +168 -0
  258. package/src/app/api/workspace/auth.ts +34 -0
  259. package/src/app/api/workspace/config/route.ts +106 -0
  260. package/src/app/api/workspace/import/route.ts +127 -0
  261. package/src/app/api/workspace/route.ts +116 -0
  262. package/src/app/app/page.tsx +2122 -0
  263. package/src/app/apple-icon.png +0 -0
  264. package/src/app/docs/page.tsx +178 -0
  265. package/src/app/favicon.ico +0 -0
  266. package/src/app/globals.css +572 -0
  267. package/src/app/health/page.tsx +5 -0
  268. package/src/app/hello/page.tsx +15 -0
  269. package/src/app/icon.png +0 -0
  270. package/src/app/layout.tsx +34 -0
  271. package/src/app/page.tsx +986 -0
  272. package/src/app/providers.tsx +90 -0
  273. package/src/app/share/[token]/page.tsx +295 -0
  274. package/src/components/ChainSelector.tsx +144 -0
  275. package/src/components/HumanActionBar.tsx +695 -0
  276. package/src/components/NotificationDrawer.tsx +129 -0
  277. package/src/components/apps/AgentKeysApp.tsx +490 -0
  278. package/src/components/apps/App.tsx +153 -0
  279. package/src/components/apps/AppGrid.tsx +15 -0
  280. package/src/components/apps/DetailedAddressDrawer.tsx +325 -0
  281. package/src/components/apps/DraggableApp.tsx +562 -0
  282. package/src/components/apps/IFrameApp.tsx +73 -0
  283. package/src/components/apps/LogsApp.tsx +360 -0
  284. package/src/components/apps/SendApp.tsx +394 -0
  285. package/src/components/apps/SetupWizardApp.tsx +1004 -0
  286. package/src/components/apps/SystemDefaultsApp.tsx +845 -0
  287. package/src/components/apps/ThirdPartyApp.tsx +428 -0
  288. package/src/components/apps/TokenApp.tsx +319 -0
  289. package/src/components/apps/TransactionsApp.tsx +438 -0
  290. package/src/components/apps/WalletDetailApp.tsx +1505 -0
  291. package/src/components/apps/index.ts +13 -0
  292. package/src/components/design-system/Button.tsx +53 -0
  293. package/src/components/design-system/ChainIndicator.tsx +65 -0
  294. package/src/components/design-system/ChainSelector.tsx +137 -0
  295. package/src/components/design-system/ConfirmationModal.tsx +106 -0
  296. package/src/components/design-system/ConfirmationPopover.tsx +81 -0
  297. package/src/components/design-system/Drawer.tsx +123 -0
  298. package/src/components/design-system/FilterDropdown.tsx +72 -0
  299. package/src/components/design-system/Modal.tsx +206 -0
  300. package/src/components/design-system/Popover.tsx +142 -0
  301. package/src/components/design-system/TextInput.tsx +85 -0
  302. package/src/components/design-system/Toggle.tsx +58 -0
  303. package/src/components/design-system/index.ts +11 -0
  304. package/src/components/docs/DocsThemeToggle.tsx +49 -0
  305. package/src/components/health/CredentialHealthDashboard.tsx +214 -0
  306. package/src/components/icons/ChainIcons.tsx +72 -0
  307. package/src/components/layout/AppStoreDrawer.tsx +369 -0
  308. package/src/components/layout/ContentArea.tsx +21 -0
  309. package/src/components/layout/TabBar.tsx +278 -0
  310. package/src/components/layout/WalletSidebar.tsx +1033 -0
  311. package/src/components/layout/index.ts +4 -0
  312. package/src/components/marketing/AuraWalletSpecOverlay.tsx +635 -0
  313. package/src/components/marketing/DeviceMorphExperience.tsx +216 -0
  314. package/src/components/vault/ApiKeysConsole.tsx +1080 -0
  315. package/src/components/vault/AuditConsole.tsx +584 -0
  316. package/src/components/vault/CredentialDetail.tsx +455 -0
  317. package/src/components/vault/CredentialEmpty.tsx +55 -0
  318. package/src/components/vault/CredentialField.tsx +361 -0
  319. package/src/components/vault/CredentialForm.tsx +1212 -0
  320. package/src/components/vault/CredentialList.tsx +165 -0
  321. package/src/components/vault/CredentialRow.tsx +97 -0
  322. package/src/components/vault/CredentialShareModal.tsx +178 -0
  323. package/src/components/vault/CredentialVault.tsx +754 -0
  324. package/src/components/vault/CredentialWalletWidget.tsx +103 -0
  325. package/src/components/vault/ImportCredentialsModal.tsx +515 -0
  326. package/src/components/vault/LargeTypeModal.tsx +64 -0
  327. package/src/components/vault/PasswordGenerator.tsx +224 -0
  328. package/src/components/vault/TOTPDisplay.tsx +123 -0
  329. package/src/components/vault/VaultSidebar.tsx +413 -0
  330. package/src/components/vault/types.ts +54 -0
  331. package/src/context/AuthContext.tsx +337 -0
  332. package/src/context/PriceContext.tsx +113 -0
  333. package/src/context/ThemeContext.tsx +164 -0
  334. package/src/context/WebSocketContext.tsx +269 -0
  335. package/src/context/WorkspaceContext.tsx +668 -0
  336. package/src/hooks/index.ts +3 -0
  337. package/src/hooks/useAgentActions.ts +368 -0
  338. package/src/hooks/useBalance.ts +103 -0
  339. package/src/hooks/useBalances.ts +129 -0
  340. package/src/instrumentation.ts +12 -0
  341. package/src/lib/api.ts +449 -0
  342. package/src/lib/app-loader.ts +148 -0
  343. package/src/lib/app-registry.ts +178 -0
  344. package/src/lib/app-sdk.ts +157 -0
  345. package/src/lib/audit-console-adapter.ts +151 -0
  346. package/src/lib/auth-client.ts +75 -0
  347. package/src/lib/config.ts +74 -0
  348. package/src/lib/crypto.ts +112 -0
  349. package/src/lib/db.ts +21 -0
  350. package/src/lib/docs.ts +390 -0
  351. package/src/lib/events.ts +361 -0
  352. package/src/lib/pino.ts +24 -0
  353. package/src/lib/theme-handlers.ts +168 -0
  354. package/src/lib/theme.ts +351 -0
  355. package/src/lib/tokenData.ts +378 -0
  356. package/src/lib/vault-crypto.ts +129 -0
  357. package/src/lib/websocket-server.ts +302 -0
  358. package/src/lib/websocket-setup.ts +79 -0
  359. package/src/lib/wordlist.ts +2050 -0
  360. package/src/lib/workspace-handlers.ts +285 -0
  361. package/start.sh +80 -0
  362. package/tailwind.config.ts +99 -0
  363. package/tsconfig.json +42 -0
@@ -0,0 +1,505 @@
1
+ /**
2
+ * App Installer
3
+ * ================
4
+ * Core logic for installing, removing, listing, and updating apps
5
+ * from external sources (git repos, tarballs, zips, local paths).
6
+ *
7
+ * Used by both CLI (server/cli/commands/app.ts) and
8
+ * dashboard API (src/app/api/apps/install/route.ts).
9
+ */
10
+
11
+ import fs from 'fs';
12
+ import path from 'path';
13
+ import os from 'os';
14
+ import { execFileSync } from 'child_process';
15
+ import { parse as parseYaml } from 'yaml';
16
+ import { getErrorMessage } from './error';
17
+ import { getDefaultSync } from './defaults';
18
+
19
+ // ─── Types ──────────────────────────────────────────────────────────
20
+
21
+ export interface SourceInfo {
22
+ type: 'git' | 'tarball' | 'zip' | 'local';
23
+ url: string;
24
+ ref: string | null;
25
+ subdir: string | null;
26
+ installedAt: string;
27
+ }
28
+
29
+ export interface InstalledApp {
30
+ id: string;
31
+ name: string;
32
+ description: string;
33
+ permissions: string[];
34
+ source: SourceInfo | null;
35
+ path: string;
36
+ }
37
+
38
+ export interface InstallOptions {
39
+ name?: string;
40
+ force?: boolean;
41
+ }
42
+
43
+ export interface InstallResult {
44
+ id: string;
45
+ name: string;
46
+ path: string;
47
+ source: SourceInfo;
48
+ }
49
+
50
+ // ─── Constants ──────────────────────────────────────────────────────
51
+
52
+ function getMaxFileSize(): number {
53
+ return getDefaultSync<number>('app.max_file_size_mb', 5) * 1024 * 1024;
54
+ }
55
+ function getMaxTotalSize(): number {
56
+ return getDefaultSync<number>('app.max_total_size_mb', 20) * 1024 * 1024;
57
+ }
58
+
59
+ function appsDir(): string {
60
+ return path.join(process.cwd(), 'apps');
61
+ }
62
+
63
+ // ─── Source Detection ───────────────────────────────────────────────
64
+
65
+ interface ParsedSource {
66
+ type: 'git' | 'tarball' | 'zip' | 'local';
67
+ url: string;
68
+ subdir: string | null;
69
+ }
70
+
71
+ function parseSource(source: string): ParsedSource {
72
+ let subdir: string | null = null;
73
+
74
+ // Extract #path=subdir fragment
75
+ const hashIdx = source.indexOf('#path=');
76
+ let cleanSource = source;
77
+ if (hashIdx !== -1) {
78
+ subdir = source.slice(hashIdx + 6);
79
+ cleanSource = source.slice(0, hashIdx);
80
+ }
81
+
82
+ // Local path
83
+ if (cleanSource.startsWith('.') || cleanSource.startsWith('/')) {
84
+ return { type: 'local', url: cleanSource, subdir };
85
+ }
86
+
87
+ // Tarball
88
+ if (cleanSource.endsWith('.tar.gz') || cleanSource.endsWith('.tgz')) {
89
+ return { type: 'tarball', url: cleanSource, subdir };
90
+ }
91
+
92
+ // Zip
93
+ if (cleanSource.endsWith('.zip')) {
94
+ return { type: 'zip', url: cleanSource, subdir };
95
+ }
96
+
97
+ // Git (github shorthand or URL)
98
+ let gitUrl = cleanSource;
99
+ if (gitUrl.startsWith('git@') || gitUrl.startsWith('ext::')) {
100
+ throw new Error('Only HTTPS git URLs are allowed. git@ and ext:: transports are rejected for security.');
101
+ }
102
+ if (!gitUrl.startsWith('http://') && !gitUrl.startsWith('https://')) {
103
+ gitUrl = `https://${gitUrl}`;
104
+ }
105
+ // Ensure .git suffix for HTTPS URLs
106
+ if (gitUrl.startsWith('https://') && !gitUrl.endsWith('.git')) {
107
+ gitUrl = `${gitUrl}.git`;
108
+ }
109
+
110
+ return { type: 'git', url: gitUrl, subdir };
111
+ }
112
+
113
+ // ─── Validation ─────────────────────────────────────────────────────
114
+
115
+ function validateApp(dir: string): { name: string; description: string; permissions: string[] } {
116
+ const appMdPath = path.join(dir, 'app.md');
117
+
118
+ if (!fs.existsSync(appMdPath)) {
119
+ throw new Error('Missing app.md manifest');
120
+ }
121
+
122
+ // Parse YAML frontmatter
123
+ const raw = fs.readFileSync(appMdPath, 'utf-8');
124
+ const match = raw.match(/^---\n([\s\S]*?)\n---/);
125
+ if (!match) {
126
+ throw new Error('app.md missing YAML frontmatter (--- delimiters)');
127
+ }
128
+
129
+ let manifest: Record<string, unknown>;
130
+ try {
131
+ manifest = parseYaml(match[1]);
132
+ } catch {
133
+ throw new Error('app.md has invalid YAML frontmatter');
134
+ }
135
+
136
+ if (!manifest) {
137
+ throw new Error('app.md has empty YAML frontmatter');
138
+ }
139
+
140
+ // Extract description from body
141
+ const parts = raw.split('---');
142
+ const body = parts.length >= 3 ? parts.slice(2).join('---').trim() : '';
143
+ const description = body.split('\n\n')[0].replace(/^#+\s*/, '').trim();
144
+
145
+ return {
146
+ name: (manifest.name as string) || '',
147
+ description,
148
+ permissions: (manifest.permissions as string[]) || [],
149
+ };
150
+ }
151
+
152
+ function checkSizeAndSymlinks(dir: string): void {
153
+ const maxFileSize = getMaxFileSize();
154
+ const maxTotalSize = getMaxTotalSize();
155
+ let totalSize = 0;
156
+
157
+ function walk(current: string): void {
158
+ const entries = fs.readdirSync(current, { withFileTypes: true });
159
+ for (const entry of entries) {
160
+ const fullPath = path.join(current, entry.name);
161
+
162
+ // Check for symlinks escaping the directory
163
+ if (entry.isSymbolicLink()) {
164
+ const resolved = fs.realpathSync(fullPath);
165
+ if (!resolved.startsWith(dir)) {
166
+ throw new Error(`Symlink escapes app directory: ${entry.name}`);
167
+ }
168
+ }
169
+
170
+ if (entry.isDirectory()) {
171
+ walk(fullPath);
172
+ } else if (entry.isFile()) {
173
+ const stat = fs.statSync(fullPath);
174
+ if (stat.size > maxFileSize) {
175
+ throw new Error(`File too large (>${maxFileSize / (1024 * 1024)}MB): ${path.relative(dir, fullPath)}`);
176
+ }
177
+ totalSize += stat.size;
178
+ if (totalSize > maxTotalSize) {
179
+ throw new Error(`Total app size exceeds ${maxTotalSize / (1024 * 1024)}MB limit`);
180
+ }
181
+ }
182
+ }
183
+ }
184
+
185
+ walk(dir);
186
+ }
187
+
188
+ // ─── Fetch to Temp Dir ──────────────────────────────────────────────
189
+
190
+ function fetchSource(parsed: ParsedSource, tmpDir: string): string {
191
+ switch (parsed.type) {
192
+ case 'local': {
193
+ const absPath = path.resolve(parsed.url);
194
+ if (!fs.existsSync(absPath)) {
195
+ throw new Error(`Local path not found: ${absPath}`);
196
+ }
197
+ // Copy to temp dir
198
+ copyDirSync(absPath, tmpDir);
199
+ return tmpDir;
200
+ }
201
+
202
+ case 'git': {
203
+ try {
204
+ execFileSync('git', ['clone', '--depth', '1', parsed.url, tmpDir], {
205
+ stdio: 'pipe',
206
+ timeout: 60000,
207
+ });
208
+ } catch (err) {
209
+ const msg = getErrorMessage(err);
210
+ throw new Error(`git clone failed: ${msg}`);
211
+ }
212
+ // Remove .git directory
213
+ const gitDir = path.join(tmpDir, '.git');
214
+ if (fs.existsSync(gitDir)) {
215
+ fs.rmSync(gitDir, { recursive: true, force: true });
216
+ }
217
+ return tmpDir;
218
+ }
219
+
220
+ case 'tarball': {
221
+ const archivePath = path.join(tmpDir, 'archive.tar.gz');
222
+ downloadFile(parsed.url, archivePath);
223
+ const extractDir = path.join(tmpDir, 'extracted');
224
+ fs.mkdirSync(extractDir, { recursive: true });
225
+ try {
226
+ execFileSync('tar', ['xzf', archivePath, '-C', extractDir], {
227
+ stdio: 'pipe',
228
+ timeout: 30000,
229
+ });
230
+ } catch {
231
+ throw new Error('Failed to extract tarball');
232
+ }
233
+ // If tarball contained a single directory, use that
234
+ return findAppRoot(extractDir);
235
+ }
236
+
237
+ case 'zip': {
238
+ const archivePath = path.join(tmpDir, 'archive.zip');
239
+ downloadFile(parsed.url, archivePath);
240
+ const extractDir = path.join(tmpDir, 'extracted');
241
+ fs.mkdirSync(extractDir, { recursive: true });
242
+ try {
243
+ execFileSync('unzip', ['-o', archivePath, '-d', extractDir], {
244
+ stdio: 'pipe',
245
+ timeout: 30000,
246
+ });
247
+ } catch {
248
+ throw new Error('Failed to extract zip');
249
+ }
250
+ return findAppRoot(extractDir);
251
+ }
252
+ }
253
+ }
254
+
255
+ /** If extracted archive has a single subdirectory, use that as root */
256
+ function findAppRoot(dir: string): string {
257
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
258
+ const dirs = entries.filter(e => e.isDirectory());
259
+
260
+ // If there's a single directory and no app.md at root level, descend
261
+ if (dirs.length === 1 && !fs.existsSync(path.join(dir, 'app.md'))) {
262
+ return path.join(dir, dirs[0].name);
263
+ }
264
+ return dir;
265
+ }
266
+
267
+ function downloadFile(url: string, dest: string): void {
268
+ // Use curl for downloading — universally available, restricted to HTTPS
269
+ try {
270
+ execFileSync('curl', ['-fsSL', '--proto', '=https', '-o', dest, url], {
271
+ stdio: 'pipe',
272
+ timeout: 60000,
273
+ });
274
+ } catch {
275
+ throw new Error(`Failed to download: ${url}`);
276
+ }
277
+ }
278
+
279
+ function copyDirSync(src: string, dest: string): void {
280
+ fs.mkdirSync(dest, { recursive: true });
281
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
282
+ const srcPath = path.join(src, entry.name);
283
+ const destPath = path.join(dest, entry.name);
284
+ if (entry.isSymbolicLink()) {
285
+ // Preserve symlinks (checkSizeAndSymlinks validates them later)
286
+ const target = fs.readlinkSync(srcPath);
287
+ fs.symlinkSync(target, destPath);
288
+ } else if (entry.isDirectory()) {
289
+ copyDirSync(srcPath, destPath);
290
+ } else {
291
+ fs.copyFileSync(srcPath, destPath);
292
+ }
293
+ }
294
+ }
295
+
296
+ /** Derive a app ID from the source URL/path */
297
+ function deriveAppId(parsed: ParsedSource): string {
298
+ if (parsed.subdir) {
299
+ return path.basename(parsed.subdir);
300
+ }
301
+ switch (parsed.type) {
302
+ case 'local':
303
+ return path.basename(path.resolve(parsed.url));
304
+ case 'git': {
305
+ // https://github.com/user/my-app.git → my-app
306
+ const name = path.basename(parsed.url).replace(/\.git$/, '');
307
+ return name || 'app';
308
+ }
309
+ case 'tarball': {
310
+ // https://example.com/my-app.tar.gz → my-app
311
+ return path.basename(parsed.url).replace(/\.(tar\.gz|tgz)$/, '') || 'app';
312
+ }
313
+ case 'zip': {
314
+ return path.basename(parsed.url).replace(/\.zip$/, '') || 'app';
315
+ }
316
+ }
317
+ }
318
+
319
+ // ─── Public API ─────────────────────────────────────────────────────
320
+
321
+ /**
322
+ * Install a app from a source (git URL, tarball, zip, or local path).
323
+ */
324
+ export function installApp(source: string, opts: InstallOptions = {}): InstallResult {
325
+ const parsed = parseSource(source);
326
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aura-app-'));
327
+
328
+ try {
329
+ // Fetch source to temp directory
330
+ let appRoot = fetchSource(parsed, tmpDir);
331
+
332
+ // Handle #path=subdir
333
+ if (parsed.subdir) {
334
+ const subPath = path.join(appRoot, parsed.subdir);
335
+ if (!fs.existsSync(subPath)) {
336
+ throw new Error(`Subdirectory not found: ${parsed.subdir}`);
337
+ }
338
+ appRoot = subPath;
339
+ }
340
+
341
+ // Validate the app
342
+ const manifest = validateApp(appRoot);
343
+ checkSizeAndSymlinks(appRoot);
344
+
345
+ // Determine app ID
346
+ const appId = opts.name || deriveAppId(parsed);
347
+ if (!appId || appId === '.' || appId === '..') {
348
+ throw new Error('Could not determine app ID. Use --name to specify one.');
349
+ }
350
+
351
+ // Check for conflicts
352
+ const targetDir = path.join(appsDir(), appId);
353
+ if (fs.existsSync(targetDir)) {
354
+ if (!opts.force) {
355
+ throw new Error(`App "${appId}" already exists. Use --force to overwrite.`);
356
+ }
357
+ fs.rmSync(targetDir, { recursive: true, force: true });
358
+ }
359
+
360
+ // Ensure apps/ directory exists
361
+ fs.mkdirSync(appsDir(), { recursive: true });
362
+
363
+ // Copy to final location
364
+ copyDirSync(appRoot, targetDir);
365
+
366
+ // Write .source.json for provenance
367
+ const sourceInfo: SourceInfo = {
368
+ type: parsed.type,
369
+ url: parsed.type === 'local' ? path.resolve(parsed.url) : parsed.url,
370
+ ref: null,
371
+ subdir: parsed.subdir,
372
+ installedAt: new Date().toISOString(),
373
+ };
374
+ fs.writeFileSync(
375
+ path.join(targetDir, '.source.json'),
376
+ JSON.stringify(sourceInfo, null, 2) + '\n',
377
+ );
378
+
379
+ return {
380
+ id: appId,
381
+ name: manifest.name || appId,
382
+ path: targetDir,
383
+ source: sourceInfo,
384
+ };
385
+ } finally {
386
+ // Cleanup temp dir
387
+ try {
388
+ fs.rmSync(tmpDir, { recursive: true, force: true });
389
+ } catch {
390
+ // Best-effort cleanup
391
+ }
392
+ }
393
+ }
394
+
395
+ /**
396
+ * Remove an installed app by ID.
397
+ */
398
+ export function removeApp(id: string): void {
399
+ const targetDir = path.join(appsDir(), id);
400
+
401
+ if (!fs.existsSync(targetDir)) {
402
+ throw new Error(`App "${id}" not found`);
403
+ }
404
+
405
+ // Safety check: must have app.md
406
+ if (!fs.existsSync(path.join(targetDir, 'app.md'))) {
407
+ throw new Error(`"${id}" does not appear to be a app (no app.md)`);
408
+ }
409
+
410
+ fs.rmSync(targetDir, { recursive: true, force: true });
411
+ }
412
+
413
+ /**
414
+ * List all installed apps with source info.
415
+ */
416
+ export function listApps(): InstalledApp[] {
417
+ const dir = appsDir();
418
+ if (!fs.existsSync(dir)) return [];
419
+
420
+ const apps: InstalledApp[] = [];
421
+
422
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
423
+ if (!entry.isDirectory()) continue;
424
+
425
+ const appDir = path.join(dir, entry.name);
426
+ const mdPath = path.join(appDir, 'app.md');
427
+ if (!fs.existsSync(mdPath)) continue;
428
+
429
+ // Read manifest
430
+ let name = entry.name;
431
+ let description = '';
432
+ let permissions: string[] = [];
433
+ try {
434
+ const raw = fs.readFileSync(mdPath, 'utf-8');
435
+ const match = raw.match(/^---\n([\s\S]*?)\n---/);
436
+ if (match) {
437
+ const manifest = parseYaml(match[1]);
438
+ if (manifest) {
439
+ name = (manifest.name as string) || entry.name;
440
+ permissions = (manifest.permissions as string[]) || [];
441
+ }
442
+ }
443
+ const parts = raw.split('---');
444
+ if (parts.length >= 3) {
445
+ const body = parts.slice(2).join('---').trim();
446
+ description = body.split('\n\n')[0].replace(/^#+\s*/, '').trim();
447
+ }
448
+ } catch {
449
+ // Use defaults
450
+ }
451
+
452
+ // Read .source.json if it exists
453
+ let source: SourceInfo | null = null;
454
+ const sourcePath = path.join(appDir, '.source.json');
455
+ if (fs.existsSync(sourcePath)) {
456
+ try {
457
+ source = JSON.parse(fs.readFileSync(sourcePath, 'utf-8'));
458
+ } catch {
459
+ // Ignore parse errors
460
+ }
461
+ }
462
+
463
+ apps.push({
464
+ id: entry.name,
465
+ name,
466
+ description,
467
+ permissions,
468
+ source,
469
+ path: appDir,
470
+ });
471
+ }
472
+
473
+ return apps;
474
+ }
475
+
476
+ /**
477
+ * Update a app by re-installing from its original source.
478
+ */
479
+ export function updateApp(id: string): InstallResult {
480
+ const targetDir = path.join(appsDir(), id);
481
+ const sourcePath = path.join(targetDir, '.source.json');
482
+
483
+ if (!fs.existsSync(targetDir)) {
484
+ throw new Error(`App "${id}" not found`);
485
+ }
486
+
487
+ if (!fs.existsSync(sourcePath)) {
488
+ throw new Error(`App "${id}" has no .source.json — cannot determine original source`);
489
+ }
490
+
491
+ let sourceInfo: SourceInfo;
492
+ try {
493
+ sourceInfo = JSON.parse(fs.readFileSync(sourcePath, 'utf-8'));
494
+ } catch {
495
+ throw new Error(`App "${id}" has invalid .source.json`);
496
+ }
497
+
498
+ // Re-construct the source string
499
+ let source = sourceInfo.url;
500
+ if (sourceInfo.subdir) {
501
+ source += `#path=${sourceInfo.subdir}`;
502
+ }
503
+
504
+ return installApp(source, { name: id, force: true });
505
+ }