@flame0510/project-aether 1.1.9

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 (290) hide show
  1. package/README.md +150 -0
  2. package/agent-templates/README.md +37 -0
  3. package/agent-templates/argus/AGENTS.md +52 -0
  4. package/agent-templates/argus/IDENTITY.md +7 -0
  5. package/agent-templates/argus/MEMORY.md +9 -0
  6. package/agent-templates/argus/SOUL.md +24 -0
  7. package/agent-templates/argus/TOOLS.md +8 -0
  8. package/agent-templates/atlas/AGENTS.md +47 -0
  9. package/agent-templates/atlas/HEARTBEAT.md +10 -0
  10. package/agent-templates/atlas/IDENTITY.md +6 -0
  11. package/agent-templates/atlas/MEMORY.md +9 -0
  12. package/agent-templates/atlas/SOUL.md +21 -0
  13. package/agent-templates/atlas/TOOLS.md +4 -0
  14. package/agent-templates/base-image/.trigger +0 -0
  15. package/agent-templates/base-image/Dockerfile +90 -0
  16. package/agent-templates/base-image/entrypoint.sh +37 -0
  17. package/agent-templates/custom/AGENTS.md +29 -0
  18. package/agent-templates/custom/BOOTSTRAP.md +56 -0
  19. package/agent-templates/custom/MEMORY.md +8 -0
  20. package/agent-templates/prometheus/AGENTS.md +37 -0
  21. package/agent-templates/prometheus/IDENTITY.md +6 -0
  22. package/agent-templates/prometheus/MEMORY.md +9 -0
  23. package/agent-templates/prometheus/SOUL.md +21 -0
  24. package/agent-templates/prometheus/TOOLS.md +4 -0
  25. package/app/agents/ChannelManager.tsx +465 -0
  26. package/app/agents/ImageDownloadBanner.tsx +159 -0
  27. package/app/agents/PageClient.tsx +1285 -0
  28. package/app/agents/create/PageClient.tsx +437 -0
  29. package/app/agents/create/page.tsx +126 -0
  30. package/app/agents/loading.tsx +14 -0
  31. package/app/agents/page.tsx +5 -0
  32. package/app/api/agents/[id]/backup/route.ts +169 -0
  33. package/app/api/agents/[id]/channels/pairing/route.ts +69 -0
  34. package/app/api/agents/[id]/channels/route.ts +14 -0
  35. package/app/api/agents/[id]/channels/telegram/route.ts +50 -0
  36. package/app/api/agents/[id]/lifecycle/route.ts +94 -0
  37. package/app/api/agents/[id]/recreate/route.ts +199 -0
  38. package/app/api/agents/[id]/restart/route.ts +33 -0
  39. package/app/api/agents/[id]/restore/route.ts +81 -0
  40. package/app/api/agents/[id]/route.ts +280 -0
  41. package/app/api/agents/channels-summary/route.ts +39 -0
  42. package/app/api/agents/config-json/route.ts +58 -0
  43. package/app/api/agents/create/route.ts +488 -0
  44. package/app/api/agents/download-image/route.ts +32 -0
  45. package/app/api/agents/image-status/route.ts +126 -0
  46. package/app/api/agents/route.ts +213 -0
  47. package/app/api/agents/token/route.ts +94 -0
  48. package/app/api/agents-active/route.ts +190 -0
  49. package/app/api/agents-config/route.ts +629 -0
  50. package/app/api/alerts/smoke/route.ts +26 -0
  51. package/app/api/aliases/add/route.ts +28 -0
  52. package/app/api/aliases/remove/route.ts +27 -0
  53. package/app/api/assistant/route.ts +204 -0
  54. package/app/api/auth/check/route.ts +38 -0
  55. package/app/api/auth/login/route.ts +85 -0
  56. package/app/api/auth/logout/route.ts +7 -0
  57. package/app/api/auth/status/route.ts +33 -0
  58. package/app/api/config/env/route.ts +132 -0
  59. package/app/api/config/restart/route.ts +26 -0
  60. package/app/api/containers/route.ts +75 -0
  61. package/app/api/cost-override/route.ts +46 -0
  62. package/app/api/costs/route.ts +84 -0
  63. package/app/api/credentials/[id]/reveal/route.ts +21 -0
  64. package/app/api/credentials/[id]/route.ts +52 -0
  65. package/app/api/credentials/[id]/sync/route.ts +58 -0
  66. package/app/api/credentials/detect/route.ts +246 -0
  67. package/app/api/credentials/route.ts +33 -0
  68. package/app/api/crons/[id]/route.ts +69 -0
  69. package/app/api/crons/route.ts +12 -0
  70. package/app/api/crons/runs/route.ts +31 -0
  71. package/app/api/debug/route.ts +11 -0
  72. package/app/api/envcheck/route.ts +18 -0
  73. package/app/api/events/route.ts +24 -0
  74. package/app/api/gateway/agent/route.ts +154 -0
  75. package/app/api/gateway/provider/balance/route.ts +38 -0
  76. package/app/api/gateway/provider/keys.ts +38 -0
  77. package/app/api/gateway/provider/oauth/route.ts +304 -0
  78. package/app/api/gateway/provider/route.ts +209 -0
  79. package/app/api/gateway/route.ts +223 -0
  80. package/app/api/gateway/sync.ts +189 -0
  81. package/app/api/lineage/route.ts +54 -0
  82. package/app/api/memory-context/route.ts +15 -0
  83. package/app/api/metrics/route.ts +35 -0
  84. package/app/api/models/route.ts +77 -0
  85. package/app/api/plugins/route.js +37 -0
  86. package/app/api/provider/upstream.ts +142 -0
  87. package/app/api/provider/v1/chat/completions/route.ts +146 -0
  88. package/app/api/provider/v1/models/route.ts +167 -0
  89. package/app/api/provider-usage/route.ts +629 -0
  90. package/app/api/session/route.ts +39 -0
  91. package/app/api/sessions/[id]/route.ts +38 -0
  92. package/app/api/sessions/route.ts +23 -0
  93. package/app/api/setup/agent-image/route.ts +102 -0
  94. package/app/api/setup/password/route.ts +111 -0
  95. package/app/api/setup/restart/route.ts +76 -0
  96. package/app/api/skills/delete/route.js +105 -0
  97. package/app/api/skills/promote/route.js +129 -0
  98. package/app/api/skills/route.js +203 -0
  99. package/app/api/skills/save/route.js +142 -0
  100. package/app/api/stats/route.ts +34 -0
  101. package/app/api/stats-since/route.ts +20 -0
  102. package/app/api/stream/route.ts +70 -0
  103. package/app/api/system-health/route.ts +233 -0
  104. package/app/api/tool-calls/route.ts +24 -0
  105. package/app/api/tools-config/route.ts +163 -0
  106. package/app/api/update-check/route.ts +127 -0
  107. package/app/api/version/route.ts +25 -0
  108. package/app/api/wizard/complete/route.ts +32 -0
  109. package/app/api/wizard/reset/route.ts +28 -0
  110. package/app/api/wizard/status/route.ts +28 -0
  111. package/app/api/workspace/route.ts +392 -0
  112. package/app/api/workspace/stream/route.ts +136 -0
  113. package/app/components/AppShell.tsx +33 -0
  114. package/app/components/AuthGuard.tsx +91 -0
  115. package/app/components/CostBreakdown.tsx +37 -0
  116. package/app/components/DashboardHeader.tsx +73 -0
  117. package/app/components/DashboardLayout.tsx +76 -0
  118. package/app/components/DashboardToolbar.tsx +61 -0
  119. package/app/components/FloatingActions.tsx +33 -0
  120. package/app/components/Icons.tsx +78 -0
  121. package/app/components/LineageGraphPage.tsx +142 -0
  122. package/app/components/LiveFeed.tsx +48 -0
  123. package/app/components/MobileBottomNav.tsx +84 -0
  124. package/app/components/ModelPickerModal.tsx +94 -0
  125. package/app/components/PasswordInput.tsx +86 -0
  126. package/app/components/PulseChat.tsx +362 -0
  127. package/app/components/Rev4aLoader.tsx +48 -0
  128. package/app/components/SessionDrawer.tsx +263 -0
  129. package/app/components/SessionTopology.tsx +289 -0
  130. package/app/components/Sidebar.tsx +294 -0
  131. package/app/components/Skeleton.tsx +133 -0
  132. package/app/components/SystemCockpit.tsx +169 -0
  133. package/app/components/VersionBanner.tsx +105 -0
  134. package/app/components/WizardButton.tsx +30 -0
  135. package/app/components/ui/Badge.tsx +30 -0
  136. package/app/components/ui/Button.tsx +59 -0
  137. package/app/components/ui/ConfirmModal.tsx +46 -0
  138. package/app/components/ui/FilterBar.tsx +33 -0
  139. package/app/components/ui/Input.tsx +25 -0
  140. package/app/components/ui/ItemList.tsx +83 -0
  141. package/app/components/ui/LoadingSpinner.tsx +30 -0
  142. package/app/components/ui/Metric.tsx +22 -0
  143. package/app/components/ui/Modal.tsx +42 -0
  144. package/app/components/ui/Page.tsx +18 -0
  145. package/app/components/ui/Pill.tsx +6 -0
  146. package/app/components/ui/PropertyList.tsx +24 -0
  147. package/app/components/ui/Select.tsx +26 -0
  148. package/app/components/ui/StatusCard.tsx +32 -0
  149. package/app/components/ui/Surface.tsx +14 -0
  150. package/app/components/ui/Tabs.tsx +55 -0
  151. package/app/components/ui/TemplateOption.tsx +59 -0
  152. package/app/components/ui/Toast.tsx +44 -0
  153. package/app/components/ui/index.ts +19 -0
  154. package/app/components/ui/tokens.ts +18 -0
  155. package/app/config/PageClient.tsx +293 -0
  156. package/app/config/loading.tsx +10 -0
  157. package/app/config/page.tsx +7 -0
  158. package/app/containers/ContainersClient.tsx +129 -0
  159. package/app/containers/loading.tsx +21 -0
  160. package/app/containers/page.tsx +5 -0
  161. package/app/containers/terminal/[id]/TerminalClient.tsx +313 -0
  162. package/app/containers/terminal/[id]/page.tsx +6 -0
  163. package/app/credentials/PageClient.tsx +598 -0
  164. package/app/credentials/loading.tsx +10 -0
  165. package/app/credentials/page.tsx +7 -0
  166. package/app/crons/PageClient.tsx +538 -0
  167. package/app/crons/loading.tsx +10 -0
  168. package/app/crons/page.tsx +5 -0
  169. package/app/design-system.ts +57 -0
  170. package/app/gateway/PageClient.tsx +1048 -0
  171. package/app/gateway/loading.tsx +24 -0
  172. package/app/gateway/page.tsx +5 -0
  173. package/app/globals.css +1474 -0
  174. package/app/layout.tsx +45 -0
  175. package/app/lib/models-context.tsx +41 -0
  176. package/app/lineage/loading.tsx +10 -0
  177. package/app/lineage/page.tsx +13 -0
  178. package/app/loading.tsx +16 -0
  179. package/app/login/page.tsx +160 -0
  180. package/app/memory/MemoryContextPageClient.tsx +196 -0
  181. package/app/memory/loading.tsx +10 -0
  182. package/app/memory/page.tsx +8 -0
  183. package/app/page.tsx +31 -0
  184. package/app/plugins/PageClient.tsx +296 -0
  185. package/app/plugins/loading.tsx +10 -0
  186. package/app/plugins/page.tsx +5 -0
  187. package/app/setup/PageClient.tsx +227 -0
  188. package/app/setup/loading.tsx +21 -0
  189. package/app/setup/page.tsx +7 -0
  190. package/app/skills/PageClient.tsx +499 -0
  191. package/app/skills/loading.tsx +10 -0
  192. package/app/skills/page.tsx +5 -0
  193. package/app/tools/ToolsPageClient.tsx +308 -0
  194. package/app/tools/loading.tsx +10 -0
  195. package/app/tools/page.tsx +62 -0
  196. package/app/tools/tools-catalog.ts +141 -0
  197. package/app/wizard/PageClient.tsx +485 -0
  198. package/app/wizard/icons.tsx +89 -0
  199. package/app/wizard/loading.tsx +24 -0
  200. package/app/wizard/page.tsx +7 -0
  201. package/app/wizard/storage.ts +33 -0
  202. package/app/wizard/useWizard.ts +101 -0
  203. package/app/workspace/WorkspaceClient.tsx +633 -0
  204. package/app/workspace/loading.tsx +36 -0
  205. package/app/workspace/page.tsx +8 -0
  206. package/bin/docker-entrypoint.sh +21 -0
  207. package/bin/postinstall.js +20 -0
  208. package/bin/rev4a.js +696 -0
  209. package/daemon.js +783 -0
  210. package/docs/ARCHITECTURE.md +557 -0
  211. package/docs/CONTAINER-TERMINAL.md +280 -0
  212. package/docs/DESIGN-SYSTEM.md +99 -0
  213. package/docs/FRONTEND-ARCHITECTURE.md +119 -0
  214. package/docs/REV4A.md +504 -0
  215. package/docs/dev/API-REFERENCE.md +1835 -0
  216. package/docs/dev/DATABASE.md +224 -0
  217. package/docs/dev/GATEWAY.md +300 -0
  218. package/docs/dev/PROVIDERS.md +229 -0
  219. package/docs/dev/SESSION-MAINTENANCE-PLAN.md +607 -0
  220. package/docs/dev/WORKSPACE.md +199 -0
  221. package/docs/rag/DATA-FRESHNESS.md +109 -0
  222. package/docs/rag/GLOSSARY.md +125 -0
  223. package/docs/rag/REV4A-OVERVIEW.md +135 -0
  224. package/docs/rag/WHAT-I-CAN-ANSWER.md +124 -0
  225. package/lib/agent-setup.ts +130 -0
  226. package/lib/alerts.ts +115 -0
  227. package/lib/apiFetch.ts +7 -0
  228. package/lib/auth.ts +28 -0
  229. package/lib/billing.ts +100 -0
  230. package/lib/buildAgentImage.ts +346 -0
  231. package/lib/channelManager.ts +386 -0
  232. package/lib/container.ts +60 -0
  233. package/lib/credentials/db.ts +86 -0
  234. package/lib/credentials/delivery.ts +448 -0
  235. package/lib/credentials/detect.ts +149 -0
  236. package/lib/credentials/hash.ts +9 -0
  237. package/lib/credentials/providers.ts +110 -0
  238. package/lib/credentials/vault.ts +195 -0
  239. package/lib/db-bootstrap.d.ts +3 -0
  240. package/lib/db-bootstrap.mjs +144 -0
  241. package/lib/db.ts +25 -0
  242. package/lib/docker-utils.ts +60 -0
  243. package/lib/hooks/useDashboard.ts +264 -0
  244. package/lib/hooks/useRev4aTimezone.ts +28 -0
  245. package/lib/hooks/useScreenshot.ts +30 -0
  246. package/lib/memory-context.ts +385 -0
  247. package/lib/model-catalogue.ts +131 -0
  248. package/lib/model-pricing.ts +63 -0
  249. package/lib/openclaw-cron.ts +350 -0
  250. package/lib/patterns/ApiAdapter.ts +73 -0
  251. package/lib/patterns/ApiClient.ts +65 -0
  252. package/lib/patterns/EventBus.ts +113 -0
  253. package/lib/patterns/FilterStrategy.ts +79 -0
  254. package/lib/patterns/SessionFactory.ts +117 -0
  255. package/lib/patterns/sessionPresentation.ts +117 -0
  256. package/lib/provider-balance.ts +163 -0
  257. package/lib/requireAuth.tsx +38 -0
  258. package/lib/rev4a-auth.d.ts +3 -0
  259. package/lib/rev4a-auth.js +36 -0
  260. package/lib/rev4a-paths.ts +145 -0
  261. package/lib/timezone.ts +46 -0
  262. package/lib/types/index.ts +96 -0
  263. package/lib/utils/format.ts +82 -0
  264. package/lineage.js +56 -0
  265. package/model-pricing.json +482 -0
  266. package/models.config.json +844 -0
  267. package/next-env.d.ts +6 -0
  268. package/next.config.mjs +7 -0
  269. package/package.json +82 -0
  270. package/public/apple-touch-icon.png +0 -0
  271. package/public/favicon-64.png +0 -0
  272. package/public/favicon.svg +58 -0
  273. package/public/icon-192-maskable.png +0 -0
  274. package/public/icon-192.png +0 -0
  275. package/public/icon-512-maskable.png +0 -0
  276. package/public/icon-512.png +0 -0
  277. package/public/manifest.json +38 -0
  278. package/public/rev4a-logo.png +0 -0
  279. package/public/rev4a-logo.svg +58 -0
  280. package/rev4a-rules/AGENTS.md +37 -0
  281. package/scripts/add-auth.cjs +185 -0
  282. package/scripts/backup.sh +66 -0
  283. package/scripts/check-providers.mjs +143 -0
  284. package/scripts/docker/traefik/docker-compose.yml +32 -0
  285. package/scripts/docker/traefik/dynamic/.gitkeep +18 -0
  286. package/scripts/docker/traefik/traefik.yml +37 -0
  287. package/scripts/remove-auth-profile.js +56 -0
  288. package/scripts/restore.sh +82 -0
  289. package/terminal-ws-server.js +139 -0
  290. package/tsconfig.json +42 -0
@@ -0,0 +1,294 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect } from 'react';
4
+ import Link from 'next/link';
5
+ import { usePathname, useRouter } from 'next/navigation';
6
+ import MobileBottomNav from './MobileBottomNav';
7
+
8
+ interface NavItem {
9
+ href: string;
10
+ label: string;
11
+ icon: React.ReactNode;
12
+ }
13
+
14
+ const NAV: NavItem[] = [
15
+ {
16
+ href: '/',
17
+ label: 'Dashboard',
18
+ icon: (
19
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
20
+ <rect x="1" y="1" width="6" height="6" rx="1" stroke="currentColor" strokeWidth="1.5" />
21
+ <rect x="9" y="1" width="6" height="6" rx="1" stroke="currentColor" strokeWidth="1.5" />
22
+ <rect x="1" y="9" width="6" height="6" rx="1" stroke="currentColor" strokeWidth="1.5" />
23
+ <rect x="9" y="9" width="6" height="6" rx="1" stroke="currentColor" strokeWidth="1.5" />
24
+ </svg>
25
+ ),
26
+ },
27
+
28
+ {
29
+ href: '/lineage?active=1&cron=0&period=1d',
30
+ label: 'Lineage',
31
+ icon: (
32
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
33
+ <circle cx="4" cy="4" r="2" stroke="currentColor" strokeWidth="1.3" />
34
+ <circle cx="12" cy="4" r="2" stroke="currentColor" strokeWidth="1.3" />
35
+ <circle cx="8" cy="12" r="2" stroke="currentColor" strokeWidth="1.3" />
36
+ <path d="M5.7 5.2 7.1 10M10.3 5.2 8.9 10M6 4h4" stroke="currentColor" strokeWidth="1.1" strokeLinecap="round" />
37
+ </svg>
38
+ ),
39
+ },
40
+ {
41
+ href: '/agents',
42
+ label: 'Agents',
43
+ icon: (
44
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
45
+ <circle cx="8" cy="5" r="3" stroke="currentColor" strokeWidth="1.5" />
46
+ <path d="M2 14c0-3.314 2.686-5 6-5s6 1.686 6 5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
47
+ </svg>
48
+ ),
49
+ },
50
+ {
51
+ href: '/containers',
52
+ label: 'Containers',
53
+ icon: (
54
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
55
+ <rect x="1.5" y="1.5" width="5" height="5" rx="1" stroke="currentColor" strokeWidth="1.3" />
56
+ <rect x="9.5" y="1.5" width="5" height="5" rx="1" stroke="currentColor" strokeWidth="1.3" />
57
+ <rect x="1.5" y="9.5" width="5" height="5" rx="1" stroke="currentColor" strokeWidth="1.3" />
58
+ <rect x="9.5" y="9.5" width="5" height="5" rx="1" stroke="currentColor" strokeWidth="1.3" />
59
+ </svg>
60
+ ),
61
+ },
62
+ {
63
+ href: '/workspace',
64
+ label: 'Workspace',
65
+ icon: (
66
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
67
+ <path d="M1.75 4.5a1.75 1.75 0 0 1 1.75-1.75h2.35l1.2 1.5h5.45a1.75 1.75 0 0 1 1.75 1.75v5.5a1.75 1.75 0 0 1-1.75 1.75H3.5a1.75 1.75 0 0 1-1.75-1.75v-7Z" stroke="currentColor" strokeWidth="1.3" strokeLinejoin="round"/>
68
+ <path d="M1.75 6h12.5" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round"/>
69
+ </svg>
70
+ ),
71
+ },
72
+ {
73
+ href: '/gateway',
74
+ label: 'Gateway',
75
+ icon: (
76
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
77
+ <path d="M3 4.5h4.5M8.5 4.5H13M5.25 4.5a1.75 1.75 0 1 0 0 .01ZM10.75 4.5a1.75 1.75 0 1 0 0 .01ZM3 11.5h2.5M5.5 11.5H13M7.25 11.5a1.75 1.75 0 1 0 0 .01Z" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round"/>
78
+ </svg>
79
+ ),
80
+ },
81
+ {
82
+ href: '/crons',
83
+ label: 'Crons',
84
+ icon: (
85
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
86
+ <circle cx="8" cy="8" r="6" stroke="currentColor" strokeWidth="1.5" />
87
+ <path d="M8 4.5V8l2.5 2" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
88
+ </svg>
89
+ ),
90
+ },
91
+ {
92
+ href: '/config',
93
+ label: 'Config',
94
+ icon: (
95
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
96
+ <circle cx="8" cy="8" r="2.5" stroke="currentColor" strokeWidth="1.3" />
97
+ <path d="M8 1v2M8 13v2M1 8h2M13 8h2M3.05 3.05l1.41 1.41M11.54 11.54l1.41 1.41M3.05 12.95l1.41-1.41M11.54 4.46l1.41-1.41" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" />
98
+ </svg>
99
+ ),
100
+ },
101
+ {
102
+ href: '/plugins',
103
+ label: 'Plugins',
104
+ icon: (
105
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
106
+ <path d="M6.2 1.8v3.1H3.1a1.2 1.2 0 0 0 0 2.4h3.1v3.1a1.2 1.2 0 0 0 2.4 0V7.3h3.1a1.2 1.2 0 0 0 0-2.4H8.6V1.8a1.2 1.2 0 1 0-2.4 0Z" stroke="currentColor" strokeWidth="1.2"/>
107
+ <rect x="1.5" y="1.5" width="13" height="13" rx="3" stroke="currentColor" strokeWidth="1.2"/>
108
+ </svg>
109
+ ),
110
+ },
111
+ {
112
+ href: '/skills',
113
+ label: 'Skills',
114
+ icon: (
115
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
116
+ <path d="M3 2.5h7.5a2 2 0 0 1 2 2V13l-3-1.5L6.5 13V4.5a2 2 0 0 0-2-2Z" stroke="currentColor" strokeWidth="1.2"/>
117
+ <path d="M3 2.5h7.5" stroke="currentColor" strokeWidth="1.2"/>
118
+ </svg>
119
+ ),
120
+ },
121
+ {
122
+ href: '/credentials',
123
+ label: 'Credentials',
124
+ icon: (
125
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
126
+ <rect x="1.5" y="6" width="13" height="8.5" rx="1" stroke="currentColor" strokeWidth="1.3" />
127
+ <path d="M5 6V4.5a3 3 0 0 1 6 0V6" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" />
128
+ <circle cx="8" cy="10" r="1.5" stroke="currentColor" strokeWidth="1.2" />
129
+ <path d="M8 11.5v1.5" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" />
130
+ </svg>
131
+ ),
132
+ },
133
+ {
134
+ href: '/tools',
135
+ label: 'Tools',
136
+ icon: (
137
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
138
+ <path d="M10.5 2a3.5 3.5 0 0 1 0 7 3.5 3.5 0 0 1-3.36-2.5H2v-1h5.14A3.5 3.5 0 0 1 10.5 2Zm0 1.5a2 2 0 1 0 0 4 2 2 0 0 0 0-4Z" fill="currentColor"/>
139
+ <path d="M2 10.5h5.14a3.5 3.5 0 1 0 0-1H2v1Z" fill="none" stroke="currentColor" strokeWidth="1.2"/>
140
+ <circle cx="10.5" cy="5.5" r="2" stroke="currentColor" strokeWidth="1.2" fill="none"/>
141
+ <path d="M2 10.5h4.5M2 13h3" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round"/>
142
+ <circle cx="11" cy="12" r="2.5" stroke="currentColor" strokeWidth="1.2" fill="none"/>
143
+ <path d="M11 10.5v1.5l1 1" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round"/>
144
+ </svg>
145
+ ),
146
+ },
147
+ {
148
+ href: '/memory',
149
+ label: 'Memory / Context',
150
+ icon: (
151
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
152
+ <path d="M3 3.5A1.5 1.5 0 0 1 4.5 2H13v11.5a.5.5 0 0 1-.8.4L9.5 12 6.8 13.9a.5.5 0 0 1-.8-.4V4.5A1.5 1.5 0 0 0 4.5 3H13" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round"/>
153
+ <path d="M4.5 2C3.67 2 3 2.67 3 3.5S3.67 5 4.5 5H6" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round"/>
154
+ <path d="M8 5.5h3M8 8h3" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round"/>
155
+ </svg>
156
+ ),
157
+ },
158
+ {
159
+ href: '/wizard',
160
+ label: 'Wizard',
161
+ icon: (
162
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
163
+ <path d="M12 2l3.09 6.26L22 9.27l-5 4.87L18.18 22 12 18.56 5.82 22 7 14.14l-5-4.87 6.91-1.01L12 2z" />
164
+ </svg>
165
+ ),
166
+ },
167
+ ];
168
+
169
+ export default function Sidebar() {
170
+ const pathname = usePathname();
171
+ const router = useRouter();
172
+ const [mobileOpen, setMobileOpen] = useState(false);
173
+ const [ocVersion, setOcVersion] = useState<string | null>(null);
174
+ useEffect(() => {
175
+ fetch('/api/version')
176
+ .then((r) => r.json())
177
+ .then((d) => setOcVersion(d.version))
178
+ .catch(() => {});
179
+ }, []);
180
+
181
+ const isActive = (href: string) => {
182
+ const targetPath = href.split('?')[0] || '/';
183
+ return targetPath === '/' ? pathname === '/' : pathname.startsWith(targetPath);
184
+ };
185
+
186
+ const handleLogout = async () => {
187
+ try {
188
+ const res = await fetch('/api/auth/logout', { method: 'POST' });
189
+ if (res.ok) {
190
+ router.push('/login');
191
+ }
192
+ } catch (err) {
193
+ console.error('Logout failed:', err);
194
+ }
195
+ };
196
+
197
+ const navItems = (
198
+ <nav className="sidebar__nav">
199
+ {NAV.map(({ href, label, icon }) => (
200
+ <Link
201
+ key={href}
202
+ href={href}
203
+ className={`sidebar__item${isActive(href) ? ' sidebar__item--active' : ''}`}
204
+ onClick={() => setMobileOpen(false)}
205
+ onMouseEnter={() => router.prefetch(href)}
206
+ prefetch={true}
207
+ >
208
+ <span className="sidebar__icon">{icon}</span>
209
+ <span className="sidebar__label">{label}</span>
210
+ </Link>
211
+ ))}
212
+ </nav>
213
+ );
214
+
215
+ const EyeLogo = () => (
216
+ <img src="/rev4a-logo.svg" alt="Rev4a" width="20" height="20" style={{ display: 'block', objectFit: 'contain' }} />
217
+ );
218
+
219
+ return (
220
+ <>
221
+ {/* Mobile top bar */}
222
+ <header className={`mobile-topbar${mobileOpen ? ' mobile-topbar--open' : ''}`}>
223
+ <div className="mobile-topbar__row">
224
+ <div className="mobile-topbar__brand">
225
+ <EyeLogo />
226
+ <span>REV4A</span>
227
+ </div>
228
+ <button
229
+ className="mobile-topbar__menu"
230
+ aria-label={mobileOpen ? 'Close menu' : 'Menu'}
231
+ onClick={() => setMobileOpen((v) => !v)}
232
+ >
233
+ {mobileOpen ? (
234
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
235
+ <path
236
+ d="M5 5l10 10M15 5L5 15"
237
+ stroke="currentColor"
238
+ strokeWidth="1.5"
239
+ strokeLinecap="round"
240
+ />
241
+ </svg>
242
+ ) : (
243
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
244
+ <path
245
+ d="M3 5h14M3 10h14M3 15h14"
246
+ stroke="currentColor"
247
+ strokeWidth="1.5"
248
+ strokeLinecap="round"
249
+ />
250
+ </svg>
251
+ )}
252
+ </button>
253
+ </div>
254
+ {/* Drawer — grows below the top bar row */}
255
+ <div className="mobile-drawer__wrapper" aria-hidden={!mobileOpen}>
256
+ <nav className="mobile-drawer">
257
+ {navItems}
258
+ </nav>
259
+ </div>
260
+ </header>
261
+
262
+ {/* Backdrop */}
263
+ {mobileOpen && (
264
+ <div className="mobile-drawer__backdrop" onClick={() => setMobileOpen(false)} />
265
+ )}
266
+
267
+ <aside className="sidebar sidebar--desktop">
268
+ <div className="sidebar__logo">
269
+ <EyeLogo />
270
+ <span className="sidebar__title">REV4A</span>
271
+ </div>
272
+ {navItems}
273
+ <div className="sidebar__bottom">
274
+ <button
275
+ onClick={handleLogout}
276
+ className="sidebar__item sidebar__item--logout"
277
+ >
278
+ <span className="sidebar__icon">
279
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
280
+ <path d="M10 3h2a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2h-2M6 12l-4-4 4-4M2 8h8" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
281
+ </svg>
282
+ </span>
283
+ <span className="sidebar__label">Logout</span>
284
+ </button>
285
+ <div className="sidebar__footer">
286
+ {ocVersion || ''}
287
+ </div>
288
+ </div>
289
+ </aside>
290
+
291
+ <MobileBottomNav />
292
+ </>
293
+ );
294
+ }
@@ -0,0 +1,133 @@
1
+ 'use client';
2
+
3
+ type SkeletonProps = {
4
+ width?: string | number;
5
+ height?: string | number;
6
+ /** Extra accent glow — violet tint */
7
+ accent?: boolean;
8
+ /** Shape variant */
9
+ variant?: 'text' | 'block' | 'card' | 'circle';
10
+ /** Inline display */
11
+ inline?: boolean;
12
+ className?: string;
13
+ style?: React.CSSProperties;
14
+ };
15
+
16
+ export function Skeleton({
17
+ width = '100%',
18
+ height,
19
+ accent = false,
20
+ variant = 'block',
21
+ inline = false,
22
+ className,
23
+ style,
24
+ }: SkeletonProps) {
25
+ const base: React.CSSProperties = {
26
+ display: inline ? 'inline-block' : 'block',
27
+ width,
28
+ height: height ?? (variant === 'text' ? '1em' : variant === 'block' ? 16 : variant === 'circle' ? 32 : 80),
29
+ background: accent
30
+ ? 'linear-gradient(90deg, rgba(146,91,252,0.06), rgba(146,91,252,0.14), rgba(146,91,252,0.06))'
31
+ : 'linear-gradient(90deg, rgba(255,255,255,0.035), rgba(255,255,255,0.07), rgba(255,255,255,0.035))',
32
+ backgroundSize: '220% 100%',
33
+ animation: 'skeleton-shimmer 1.35s ease-in-out infinite',
34
+ borderRadius: variant === 'circle' ? '50%' : 0,
35
+ verticalAlign: inline ? 'middle' : undefined,
36
+ ...style,
37
+ };
38
+ return <div className={className} style={base} aria-hidden="true" />;
39
+ }
40
+
41
+ /** Tree skeleton — simulates file tree structure like workspace sidebar */
42
+ export function TreeSkeleton({ rows = 12, indentLevels = true }: { rows?: number; indentLevels?: boolean }) {
43
+ const baseIndent = 14;
44
+ const depths = indentLevels
45
+ ? [0, 0, 1, 2, 0, 1, 1, 0, 0, 0, 1, 2, 3, 0, 1, 0, 0, 2, 0, 0]
46
+ : Array.from({ length: rows }, () => 0);
47
+
48
+ return (
49
+ <div style={{ padding: '6px 0 24px', display: 'flex', flexDirection: 'column', gap: 4 }}>
50
+ {depths.slice(0, rows).map((depth, i) => {
51
+ const hasChevron = depth < (depths[i + 1] ?? -1);
52
+ return (
53
+ <div
54
+ key={i}
55
+ style={{
56
+ display: 'flex',
57
+ alignItems: 'center',
58
+ gap: 6,
59
+ padding: `3px 8px 3px ${8 + depth * baseIndent}px`,
60
+ }}
61
+ >
62
+ {/* chevron / spacer */}
63
+ {hasChevron ? (
64
+ <Skeleton width={10} height={10} accent />
65
+ ) : (
66
+ <div style={{ width: 10, flexShrink: 0 }} />
67
+ )}
68
+ {/* icon */}
69
+ <Skeleton width={14} height={14} accent />
70
+ {/* name */}
71
+ <Skeleton
72
+ width={`${35 + Math.sin(i * 2.1) * 25 + (i % 3) * 10}%`}
73
+ height={10}
74
+ variant="block"
75
+ />
76
+ {/* size */}
77
+ {i % 3 === 1 && <Skeleton width={32} height={10} style={{ marginLeft: 'auto' }} />}
78
+ </div>
79
+ );
80
+ })}
81
+ </div>
82
+ );
83
+ }
84
+
85
+ /** Code skeleton — simulates code editor lines */
86
+ export function CodeSkeleton({ lines = 12 }: { lines?: number }) {
87
+ const lineWidths = [0.82, 0.45, 0.68, 1, 0.55, 0.72, 0.38, 0.91, 0.6, 0.33, 0.77, 0.5, 0.65, 0.88, 0.42, 0.74, 0.57, 0.49];
88
+
89
+ return (
90
+ <div style={{ padding: '16px 20px', display: 'flex', flexDirection: 'column', gap: 10, flex: 1 }}>
91
+ {lineWidths.slice(0, lines).map((w, i) => (
92
+ <Skeleton key={i} width={`${w * 100}%`} height={11} />
93
+ ))}
94
+ </div>
95
+ );
96
+ }
97
+
98
+ /* ─── Legacy aliases (used by existing components) ────────────────────────── */
99
+ /** Stack of horizontal skeleton lines */
100
+ export function SkeletonLines({ count = 3, width }: { count?: number; width?: string | number }) {
101
+ return (
102
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
103
+ {Array.from({ length: count }).map((_, i) => (
104
+ <Skeleton key={i} width={width ?? `${85 - i * 7}%`} height={12} />
105
+ ))}
106
+ </div>
107
+ );
108
+ }
109
+
110
+ /** Metric skeleton — a label line above a large value bar */
111
+ export function SkeletonMetric({ width: w }: { width?: number }) {
112
+ return (
113
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
114
+ <Skeleton width={w ?? 64} height={10} />
115
+ <Skeleton width={w ?? 88} height={24} accent />
116
+ </div>
117
+ );
118
+ }
119
+
120
+ /** Card rows skeleton — simulates a grid/list of cards */
121
+ export function CardRowSkeleton({ count = 3, height = 56 }: { count?: number; height?: number }) {
122
+ return (
123
+ <div style={{ display: 'grid', gap: 8 }}>
124
+ {Array.from({ length: count }).map((_, i) => (
125
+ <Skeleton
126
+ key={i}
127
+ height={height}
128
+ style={{ border: '1px solid rgba(255,255,255,0.06)' }}
129
+ />
130
+ ))}
131
+ </div>
132
+ );
133
+ }
@@ -0,0 +1,169 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useMemo, useState } from 'react';
4
+ import Link from 'next/link';
5
+ import type { Costs, FilterConfig, Session, SessionEvent } from '@/lib/types';
6
+ import { isSessionActive } from '@/lib/patterns/sessionPresentation';
7
+ import { SkeletonLines, SkeletonMetric } from './Skeleton';
8
+ import { Metric, StatusCard, Surface, toneFromHealth } from './ui';
9
+ import { apiFetch } from '@/lib/apiFetch';
10
+ import LiveFeed from './LiveFeed';
11
+ import CostBreakdown from './CostBreakdown';
12
+
13
+ interface SystemCheck {
14
+ id: string;
15
+ label: string;
16
+ health: 'ok' | 'warning' | 'error';
17
+ value: string | number;
18
+ details?: string;
19
+ source: 'runtime' | 'memory' | 'cron' | 'cost' | 'trello';
20
+ }
21
+
22
+ interface Recommendation {
23
+ id: string;
24
+ severity: 'info' | 'warning' | 'critical';
25
+ source: SystemCheck['source'];
26
+ title: string;
27
+ details: string;
28
+ actionHref?: string;
29
+ dismissible: boolean;
30
+ createdAt: string;
31
+ trelloCardId?: string;
32
+ }
33
+
34
+ interface SystemHealthPayload {
35
+ health: 'ok' | 'warning' | 'error';
36
+ checks: SystemCheck[];
37
+ recommendations: Recommendation[];
38
+ generatedAt: string;
39
+ }
40
+
41
+ interface SystemCockpitProps {
42
+ sessions: Session[];
43
+ events: SessionEvent[];
44
+ costs: Costs;
45
+ filter: FilterConfig;
46
+ visibleEvents: SessionEvent[];
47
+ loading?: boolean;
48
+ }
49
+
50
+ function fmtMoney(value?: number | null): string {
51
+ return `$${Number(value ?? 0).toFixed(2)}`;
52
+ }
53
+
54
+ function fmtAge(ts?: number | null): string {
55
+ if (!ts) return 'n/a';
56
+ const ms = Date.now() - (ts > 10_000_000_000 ? ts : ts * 1000);
57
+ if (ms < 60_000) return 'now';
58
+ if (ms < 3_600_000) return `${Math.floor(ms / 60_000)}m ago`;
59
+ if (ms < 86_400_000) return `${Math.floor(ms / 3_600_000)}h ago`;
60
+ return `${Math.floor(ms / 86_400_000)}d ago`;
61
+ }
62
+
63
+ function severityHealth(severity: Recommendation['severity']): 'ok' | 'warning' | 'error' | 'info' {
64
+ if (severity === 'critical') return 'error';
65
+ if (severity === 'warning') return 'warning';
66
+ return 'info';
67
+ }
68
+
69
+ function findCheck(checks: SystemCheck[], id: string): SystemCheck | undefined {
70
+ return checks.find((check) => check.id === id);
71
+ }
72
+
73
+ export default function SystemCockpit({
74
+ sessions, events, costs, filter, visibleEvents,
75
+ loading = false,
76
+ }: SystemCockpitProps) {
77
+ const [systemHealth, setSystemHealth] = useState<SystemHealthPayload | null>(null);
78
+
79
+ useEffect(() => {
80
+ const load = () => {
81
+ apiFetch('/api/system-health')
82
+ .then((r) => (r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`))))
83
+ .then(setSystemHealth)
84
+ .catch((error) => setSystemHealth({
85
+ health: 'warning',
86
+ checks: [{ id: 'system-health.api', label: 'System health API', health: 'warning', value: 'warning', details: error.message, source: 'runtime' }],
87
+ recommendations: [{ id: 'system-health.api', severity: 'warning', source: 'runtime', title: 'System health unreadable', details: error.message, dismissible: false, createdAt: new Date().toISOString() }],
88
+ generatedAt: new Date().toISOString(),
89
+ }));
90
+ };
91
+ load();
92
+ const t = setInterval(load, 30_000);
93
+ return () => clearInterval(t);
94
+ }, []);
95
+
96
+ const metrics = useMemo(() => {
97
+ const active = sessions.filter(isSessionActive).length;
98
+ const errors = events.filter((event) => String(event.type ?? event.event ?? '').toLowerCase().includes('error')).length;
99
+ const latest = Math.max(...sessions.map((session) => Number(session.updated_at ?? session.started_at ?? 0)), 0);
100
+ return { active, errors, latest };
101
+ }, [sessions, events]);
102
+
103
+ const systemLoading = loading || !systemHealth;
104
+ const checks = systemHealth?.checks ?? [];
105
+ const cronCheck = findCheck(checks, 'cron.jobs') ?? findCheck(checks, 'cron.api');
106
+ const runtimeCheck = findCheck(checks, 'runtime.sessions');
107
+ const costCheck = findCheck(checks, 'cost.usageBasedToday');
108
+ const health = systemHealth?.health ?? 'warning';
109
+ const recommendations = systemHealth?.recommendations ?? [];
110
+ const runtimeFreshness = runtimeCheck?.details ?? fmtAge(metrics.latest);
111
+
112
+ return (
113
+ <section className="cockpit">
114
+ <div className="cockpit__metrics">
115
+ <Surface as="article" tone={toneFromHealth(health)}>
116
+ <div className="ui-kicker">Rev4a System Health</div>
117
+ <div className="cockpit__health-row">
118
+ {systemLoading ? <SkeletonMetric width={120} /> : <span className={`cockpit__health cockpit__health--${health}`}>{health}</span>}
119
+ <span className="ui-muted" style={{ fontSize: 11 }}>{runtimeFreshness}</span>
120
+ </div>
121
+ <div className="ui-muted" style={{ marginTop: 10, fontSize: 11 }}>Server-side monitor from `/api/system-health`.</div>
122
+ </Surface>
123
+ <Metric title="Active sessions" value={metrics.active} subtitle={`${sessions.length} total`} loading={loading} />
124
+ <Metric title="Usage cost" value={String(costCheck?.value ?? fmtMoney(0))} subtitle={costCheck?.details ?? `DB estimate ${fmtMoney(costs.today)}`} tone="accent" loading={systemLoading} />
125
+ </div>
126
+
127
+ <div className="cockpit__main">
128
+ <div className="cockpit__checks">
129
+ <StatusCard loading={systemLoading} title="Runtime Rev4a" health={runtimeCheck?.health ?? 'warning'} rows={[[runtimeCheck?.label ?? 'Total sessions', runtimeCheck?.value ?? sessions.length], ['Last update', runtimeFreshness], ['Recent feed errors', metrics.errors]]} />
130
+ <StatusCard loading={systemLoading} title="Cron / Watchdog" health={cronCheck?.health ?? 'warning'} rows={[[cronCheck?.label ?? 'Cron', cronCheck?.value ?? 'n/a'], ['Detail', cronCheck?.details ?? 'n/a'], ['Source', '/api/system-health']]} />
131
+ <StatusCard loading={loading} title="Lineage" health={sessions.length ? 'ok' : 'warning'} rows={[[ 'Total sessions', sessions.length], ['Live events', events.length], ['Full view', <Link key="lineage" href="/lineage" className="ui-link">Open graph →</Link>]]} />
132
+ </div>
133
+
134
+ <Surface as="aside">
135
+ <div className="ui-kicker ui-kicker--accent" style={{ marginBottom: 14 }}>Recommended actions</div>
136
+ {systemLoading ? (
137
+ <SkeletonLines count={5} />
138
+ ) : recommendations.length ? (
139
+ <ul className="cockpit__recommendations">
140
+ {recommendations.map((item) => (
141
+ <li key={item.id} className={`cockpit__recommendation cockpit__recommendation--${severityHealth(item.severity)}`}>
142
+ <div className="cockpit__recommendation-head">
143
+ <strong>{item.title}</strong>
144
+ <span>{item.severity}</span>
145
+ </div>
146
+ <div className="ui-muted" style={{ marginTop: 4 }}>{item.details}</div>
147
+ {item.actionHref && <Link href={item.actionHref} className="ui-link" style={{ display: 'inline-block', marginTop: 6 }}>Apri →</Link>}
148
+ </li>
149
+ ))}
150
+ </ul>
151
+ ) : (
152
+ <div style={{ color: 'var(--green)', fontSize: 13 }}>No immediate warnings. System in operational state.</div>
153
+ )}
154
+ <div className="cockpit__quick-links">
155
+ <Link className="agent-btn" href="/memory">Memory</Link>
156
+ <Link className="agent-btn" href="/crons">Crons</Link>
157
+ <Link className="agent-btn" href="/agents">Agents</Link>
158
+ <Link className="agent-btn" href="/lineage">Lineage</Link>
159
+ </div>
160
+ </Surface>
161
+ </div>
162
+
163
+ <div className="cockpit__feed-section">
164
+ <LiveFeed events={visibleEvents.slice(0, 10)} loading={loading} />
165
+ <CostBreakdown byModel={costs.byModel} loading={loading} />
166
+ </div>
167
+ </section>
168
+ );
169
+ }