@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
package/app/layout.tsx ADDED
@@ -0,0 +1,45 @@
1
+ import type { Metadata, Viewport } from 'next';
2
+ import { Instrument_Serif, JetBrains_Mono } from 'next/font/google';
3
+ import './globals.css';
4
+ import AppShell from './components/AppShell';
5
+ import { ModelsProvider } from './lib/models-context';
6
+ import { ToastContainer } from './components/ui';
7
+
8
+ const jetBrainsMono = JetBrains_Mono({ subsets: ['latin'], variable: '--font-mono' });
9
+ const instrumentSerif = Instrument_Serif({
10
+ subsets: ['latin'],
11
+ weight: '400',
12
+ variable: '--font-serif',
13
+ });
14
+
15
+ export const viewport: Viewport = {
16
+ themeColor: '#925BFC',
17
+ };
18
+
19
+ export const metadata: Metadata = {
20
+ title: 'REV4A - Agency Monitor',
21
+ description: 'Mobile-ready monitoring dashboard for OpenClaw workspaces.',
22
+ manifest: '/manifest.json',
23
+ appleWebApp: { capable: true, statusBarStyle: 'black-translucent', title: 'REV4A' },
24
+ icons: {
25
+ icon: [
26
+ { url: '/favicon.svg', type: 'image/svg+xml' },
27
+ { url: '/favicon-64.png', sizes: '64x64', type: 'image/png' },
28
+ { url: '/icon-192.png', sizes: '192x192', type: 'image/png' },
29
+ { url: '/icon-512.png', sizes: '512x512', type: 'image/png' },
30
+ { url: '/icon-192-maskable.png', sizes: '192x192', type: 'image/png' },
31
+ { url: '/icon-512-maskable.png', sizes: '512x512', type: 'image/png' },
32
+ ],
33
+ apple: [{ url: '/apple-touch-icon.png', sizes: '180x180', type: 'image/png' }],
34
+ },
35
+ };
36
+
37
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
38
+ return (
39
+ <html lang="en">
40
+ <body className={`${jetBrainsMono.variable} ${instrumentSerif.variable}`}>
41
+ <ModelsProvider><AppShell>{children}<ToastContainer /></AppShell></ModelsProvider>
42
+ </body>
43
+ </html>
44
+ );
45
+ }
@@ -0,0 +1,41 @@
1
+ 'use client';
2
+
3
+ import { createContext, useContext, useEffect, useState } from 'react';
4
+
5
+ interface ProviderEntry {
6
+ provider: string;
7
+ emoji: string;
8
+ models: { id: string; label: string }[];
9
+ }
10
+
11
+ interface ModelsContextValue {
12
+ providers: ProviderEntry[];
13
+ loaded: boolean;
14
+ }
15
+
16
+ const ModelsContext = createContext<ModelsContextValue>({ providers: [], loaded: false });
17
+
18
+ export function ModelsProvider({ children }: { children: React.ReactNode }) {
19
+ const [providers, setProviders] = useState<ProviderEntry[]>([]);
20
+ const [loaded, setLoaded] = useState(false);
21
+
22
+ useEffect(() => {
23
+ fetch('/api/models')
24
+ .then((r) => r.json())
25
+ .then((data) => {
26
+ if (Array.isArray(data) && data.length) setProviders(data);
27
+ setLoaded(true);
28
+ })
29
+ .catch(() => setLoaded(true)); // fallback silenzioso
30
+ }, []);
31
+
32
+ return (
33
+ <ModelsContext.Provider value={{ providers, loaded }}>
34
+ {children}
35
+ </ModelsContext.Provider>
36
+ );
37
+ }
38
+
39
+ export function useModels() {
40
+ return useContext(ModelsContext);
41
+ }
@@ -0,0 +1,10 @@
1
+ import { Skeleton, CardRowSkeleton } from '../components/Skeleton';
2
+
3
+ const PageSkeleton = () => (
4
+ <div style={{ padding: '24px 20px', maxWidth: 1080, margin: '0 auto' }}>
5
+ <Skeleton width={120} height={20} accent style={{ marginBottom: 20 }} />
6
+ <CardRowSkeleton count={5} height={48} />
7
+ </div>
8
+ );
9
+
10
+ export default PageSkeleton;
@@ -0,0 +1,13 @@
1
+ import { Suspense } from 'react';
2
+ import LineageGraphPage from '../components/LineageGraphPage';
3
+
4
+ export const dynamic = 'force-dynamic';
5
+
6
+ export default function LineagePage() {
7
+
8
+ return (
9
+ <Suspense>
10
+ <LineageGraphPage initialCosts={{ today: 0, allTime: 0, byModel: [] }} />
11
+ </Suspense>
12
+ );
13
+ }
@@ -0,0 +1,16 @@
1
+ import Rev4aLoader from './components/Rev4aLoader';
2
+
3
+ export default function Loading() {
4
+ return (
5
+ <div
6
+ style={{
7
+ height: '100%',
8
+ boxSizing: 'border-box',
9
+ paddingBottom: 72,
10
+ background: 'radial-gradient(circle at center, rgba(146,91,252,0.10), transparent 34%), var(--bg)',
11
+ }}
12
+ >
13
+ <Rev4aLoader label="LOADING SECTION" fullHeight />
14
+ </div>
15
+ );
16
+ }
@@ -0,0 +1,160 @@
1
+ 'use client';
2
+
3
+ import { useState, useCallback, useEffect, type FormEvent, type KeyboardEvent } from 'react';
4
+ import { useRouter } from 'next/navigation';
5
+ import { isWizardComplete } from '../wizard/storage';
6
+ import { Button, Input } from '../components/ui';
7
+ import { EyeIcon, EyeOffIcon } from '../components/Icons';
8
+
9
+ export default function LoginPage() {
10
+ const router = useRouter();
11
+ const [password, setPassword] = useState('');
12
+ const [showPassword, setShowPassword] = useState(false);
13
+ const [error, setError] = useState('');
14
+ const [loading, setLoading] = useState(false);
15
+ const [checking, setChecking] = useState(true);
16
+
17
+ // Redirect to /setup if no password is configured
18
+ useEffect(() => {
19
+ fetch('/api/auth/status')
20
+ .then((r) => r.json())
21
+ .then((d: { configured: boolean }) => {
22
+ if (!d.configured) {
23
+ router.replace('/setup');
24
+ return;
25
+ }
26
+ setChecking(false);
27
+ })
28
+ .catch(() => setChecking(false));
29
+ }, [router]);
30
+
31
+ // Enter key handler for the password input
32
+ const handleKeyDown = useCallback((e: KeyboardEvent<HTMLInputElement>) => {
33
+ if (e.key === 'Enter' && password) {
34
+ const form = (e.target as HTMLInputElement).closest('form');
35
+ if (form) form.requestSubmit();
36
+ }
37
+ }, [password]);
38
+
39
+ async function handleSubmit(e: FormEvent<HTMLFormElement>) {
40
+ e.preventDefault();
41
+ setLoading(true);
42
+ setError('');
43
+ try {
44
+ const res = await fetch('/api/auth/login', {
45
+ method: 'POST',
46
+ headers: { 'Content-Type': 'application/json' },
47
+ credentials: 'same-origin',
48
+ cache: 'no-store',
49
+ body: JSON.stringify({ password }),
50
+ });
51
+ if (res.ok) {
52
+ // Check wizard completion via API
53
+ try {
54
+ const done = await isWizardComplete();
55
+ if (!done) {
56
+ router.replace('/wizard');
57
+ return;
58
+ }
59
+ } catch { /* fallback to dashboard */ }
60
+ router.replace('/');
61
+ } else {
62
+ const data = (await res.json()) as { error?: string };
63
+ setError(data.error ?? 'Invalid password');
64
+ }
65
+ } catch {
66
+ setError('Network error');
67
+ } finally {
68
+ setLoading(false);
69
+ }
70
+ }
71
+
72
+ if (checking) return null;
73
+
74
+ return (
75
+ <div
76
+ style={{
77
+ minHeight: '100vh',
78
+ display: 'flex',
79
+ alignItems: 'center',
80
+ justifyContent: 'center',
81
+ background: 'radial-gradient(circle at top, rgba(146,91,252,0.12), transparent 32%), var(--bg)',
82
+ fontFamily: 'var(--font-mono-stack, monospace)',
83
+ padding: '24px',
84
+ }}
85
+ >
86
+ <div
87
+ style={{
88
+ width: 'min(92vw, 392px)',
89
+ background: 'linear-gradient(180deg, rgba(18,18,18,0.98), rgba(12,12,12,0.98))',
90
+ border: '1px solid var(--border)',
91
+ borderRadius: 0,
92
+ boxShadow: '0 24px 64px rgba(0,0,0,0.42)',
93
+ overflow: 'hidden',
94
+ }}
95
+ >
96
+ <div style={{ padding: '22px 24px 18px', borderBottom: '1px solid var(--border)', display: 'flex', flexDirection: 'column', alignItems: 'center', textAlign: 'center' }}>
97
+ <img src="/rev4a-logo.svg" alt="Rev4a" width="76" height="76" style={{ objectFit: 'contain', display: 'block', marginBottom: 14, filter: 'drop-shadow(0 0 12px rgba(146,91,252,0.25))' }} />
98
+ <div style={{ fontFamily: 'var(--font-serif-stack, serif)', fontSize: 26, letterSpacing: 4, color: 'var(--violet)' }}>REV4A</div>
99
+ <div style={{ marginTop: 8, color: 'var(--text-dim)', fontSize: 11, letterSpacing: '0.12em' }}>
100
+ REVOLUTION FOR AGENTS — RESTRICTED ACCESS
101
+ </div>
102
+ </div>
103
+
104
+ <form onSubmit={handleSubmit} style={{ padding: '20px 24px 24px' }}>
105
+ <div style={{ color: 'var(--violet)', fontSize: 10, letterSpacing: '0.12em', marginBottom: 8 }}>
106
+ PASSWORD
107
+ </div>
108
+ <div style={{ position: 'relative', marginBottom: 12 }}>
109
+ <Input
110
+ type={showPassword ? 'text' : 'password'}
111
+ value={password}
112
+ onChange={(e) => setPassword(e.target.value)}
113
+ onKeyDown={handleKeyDown}
114
+ placeholder="Enter admin password"
115
+ autoFocus
116
+ />
117
+ <button
118
+ type="button"
119
+ onClick={() => setShowPassword(!showPassword)}
120
+ aria-label={showPassword ? 'Hide password' : 'Show password'}
121
+ style={{
122
+ position: 'absolute',
123
+ right: 10,
124
+ top: '50%',
125
+ transform: 'translateY(-50%)',
126
+ color: 'var(--violet)',
127
+ cursor: 'pointer',
128
+ padding: '4px',
129
+ display: 'flex',
130
+ alignItems: 'center',
131
+ justifyContent: 'center',
132
+ background: 'none',
133
+ border: 'none',
134
+ outline: 'none',
135
+ }}
136
+ >
137
+ {showPassword ? <EyeOffIcon /> : <EyeIcon />}
138
+ </button>
139
+ </div>
140
+ {error && (
141
+ <div style={{ color: '#ef4444', fontSize: 12, marginBottom: 12 }}>{error}</div>
142
+ )}
143
+ <Button
144
+ type="submit"
145
+ variant="primary"
146
+ size="md"
147
+ loading={loading}
148
+ disabled={!password || loading}
149
+ style={{ width: '100%' }}
150
+ >
151
+ {loading ? 'ACCESSING...' : 'LOGIN'}
152
+ </Button>
153
+ <div style={{ marginTop: 14, fontSize: 10, color: 'var(--text-dim)', textAlign: 'center', letterSpacing: '0.06em' }}>
154
+ Internal Rev4a access
155
+ </div>
156
+ </form>
157
+ </div>
158
+ </div>
159
+ );
160
+ }
@@ -0,0 +1,196 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useMemo, useState } from 'react';
4
+ import type { MemoryContextPayload } from '@/lib/memory-context';
5
+ import { Pill, Metric, Surface, Page, PageHeader, toneFromHealth } from '../components/ui';
6
+ import { apiFetch } from '@/lib/apiFetch';
7
+ import { SkeletonLines } from '../components/Skeleton';
8
+ import { useRev4aTimezone } from '@/lib/hooks/useRev4aTimezone';
9
+ import { formatDateTimeInTimezone } from '@/lib/timezone';
10
+
11
+ function formatBytes(value: number | null): string {
12
+ if (value === null) return '—';
13
+ if (value < 1024) return `${value} B`;
14
+ if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)} KB`;
15
+ return `${(value / (1024 * 1024)).toFixed(1)} MB`;
16
+ }
17
+
18
+ function formatDate(value: string | null, timezone: string): string {
19
+ if (!value) return '—';
20
+ return formatDateTimeInTimezone(value, {}, timezone);
21
+ }
22
+
23
+ export default function MemoryContextPageClient() {
24
+ const [data, setData] = useState<MemoryContextPayload | null>(null);
25
+ const [error, setError] = useState('');
26
+ const [loading, setLoading] = useState(true);
27
+ const timezone = useRev4aTimezone();
28
+
29
+ useEffect(() => {
30
+ const controller = new AbortController();
31
+ async function load() {
32
+ try {
33
+ const res = await apiFetch('/api/memory-context', { signal: controller.signal });
34
+ if (controller.signal.aborted) return;
35
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
36
+ setData((await res.json()) as MemoryContextPayload);
37
+ } catch (err) {
38
+ if (controller.signal.aborted) return;
39
+ setError((err as Error).message);
40
+ } finally {
41
+ if (!controller.signal.aborted) setLoading(false);
42
+ }
43
+ }
44
+ void load();
45
+ return () => controller.abort();
46
+ }, []);
47
+
48
+ const metrics = useMemo(() => {
49
+ if (!data) return [];
50
+ return [
51
+ { label: 'Agents', value: data.summary.totalAgents },
52
+ { label: 'USER linked', value: data.summary.userLinked },
53
+ { label: 'Warnings', value: data.summary.warnings },
54
+ { label: 'Global files', value: data.summary.globalFiles },
55
+ ];
56
+ }, [data]);
57
+
58
+ return (
59
+ <Page>
60
+ <div style={{ height: '48px', padding: '0 14px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexShrink: 0, boxSizing: 'border-box' }}>
61
+ <span style={{ fontFamily: 'var(--font-serif-stack)', fontSize: '20px', letterSpacing: '4px', color: 'var(--violet)' }}>MEMORY</span>
62
+ {data?.strategy ? <Pill tone={toneFromHealth(data.strategy.health)}>health: {data.strategy.health}</Pill> : undefined}
63
+ </div>
64
+
65
+ <div className="ui-page" style={{ padding: '24px 20px 40px', overflow: 'auto' }}>
66
+ <div className="ui-page__inner" style={{ maxWidth: 1280, display: 'flex', flexDirection: 'column', gap: 18 }}>
67
+ <div style={{ marginBottom: 8 }}>
68
+ <p style={{ margin: 0, color: 'var(--text-dim)', fontSize: '13px', lineHeight: 1.5 }}>Shared USER for common identity, local MEMORY per agent, local SOUL and AGENTS as operating bootstrap.</p>
69
+ </div>
70
+
71
+ {loading && (
72
+ <>
73
+ <section style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: 12 }}>
74
+ {Array.from({ length: 4 }).map((_, index) => (
75
+ <Metric key={index} title="" value="" loading />
76
+ ))}
77
+ </section>
78
+ <Surface variant="panel">
79
+ <SkeletonLines count={8} />
80
+ </Surface>
81
+ </>
82
+ )}
83
+ {error && <div style={{ color: '#fca5a5', fontSize: 13 }}>{error}</div>}
84
+
85
+ {data && (
86
+ <>
87
+ <section style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: 12 }}>
88
+ {metrics.map((metric) => (
89
+ <Metric key={metric.label} title={metric.label} value={metric.value} tone="accent" />
90
+ ))}
91
+ </section>
92
+
93
+ <Surface variant="panel">
94
+ <div style={{ padding: '12px 14px', borderBottom: '1px solid var(--border)', display: 'flex', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
95
+ <strong style={{ fontSize: 12, letterSpacing: '0.08em', color: 'var(--violet)' }}>Agent Memory Health</strong>
96
+ <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
97
+ <Pill>user: {data.strategy.userProfile}</Pill>
98
+ <Pill>memory: {data.strategy.memory}</Pill>
99
+ </div>
100
+ </div>
101
+ <div style={{ overflowX: 'auto' }}>
102
+ <table style={{ width: '100%', borderCollapse: 'collapse', minWidth: 980 }}>
103
+ <thead>
104
+ <tr style={{ textAlign: 'left', color: '#8a8a92', fontSize: 11 }}>
105
+ {['Agent', 'Workspace', 'USER', 'MEMORY', 'AGENTS', 'SOUL', 'HEARTBEAT', 'Strategy', 'Warnings'].map((label) => (
106
+ <th key={label} style={{ padding: '10px 14px', borderBottom: '1px solid var(--border)', fontWeight: 500 }}>{label}</th>
107
+ ))}
108
+ </tr>
109
+ </thead>
110
+ <tbody>
111
+ {data.agents.map((agent) => (
112
+ <tr key={`${agent.agentId}:${agent.workspace}`} style={{ verticalAlign: 'top' }}>
113
+ <td style={{ padding: '12px 14px', borderBottom: '1px solid var(--border)' }}>
114
+ <div style={{ fontSize: 13 }}>{agent.displayName || agent.name}</div>
115
+ <div style={{ color: '#8a8a92', fontSize: 11, marginTop: 4 }}>{agent.agentId} · {agent.source.join(', ')}</div>
116
+ </td>
117
+ <td style={{ padding: '12px 14px', borderBottom: '1px solid var(--border)', fontSize: 12, color: '#c7c7d0', maxWidth: 220, wordBreak: 'break-word' }}>
118
+ <div>{agent.workspace}</div>
119
+ <div style={{ color: agent.bootstrapBytes > agent.bootstrapBudgetBytes ? 'var(--warning, #f6c66b)' : '#8a8a92', fontSize: 11, marginTop: 6 }}>
120
+ bootstrap {formatBytes(agent.bootstrapBytes)} / {formatBytes(agent.bootstrapBudgetBytes)}
121
+ </div>
122
+ </td>
123
+ {Object.values(agent.files).map((file) => (
124
+ <td key={`${agent.agentId}-${file.key}`} style={{ padding: '12px 14px', borderBottom: '1px solid var(--border)', fontSize: 11, minWidth: 150 }}>
125
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
126
+ <Pill tone={toneFromHealth(file.exists ? 'ok' : 'warning')}>{file.exists ? (file.isSymlink ? 'linked' : 'local') : 'missing'}</Pill>
127
+ <div style={{ color: '#8a8a92' }}>{formatBytes(file.size)} · {formatDate(file.mtime, timezone)}</div>
128
+ <div style={{ color: '#73737c', wordBreak: 'break-word' }}>{file.path}</div>
129
+ {file.symlinkTarget && <div style={{ color: '#b8b8c2', wordBreak: 'break-word' }}>→ {file.symlinkTarget}</div>}
130
+ </div>
131
+ </td>
132
+ ))}
133
+ <td style={{ padding: '12px 14px', borderBottom: '1px solid var(--border)', fontSize: 11 }}>
134
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
135
+ <Pill tone={toneFromHealth(agent.strategy.health)}>{agent.strategy.health}</Pill>
136
+ <div>USER: {agent.strategy.userProfile}</div>
137
+ <div>MEMORY: {agent.strategy.memory}</div>
138
+ </div>
139
+ </td>
140
+ <td style={{ padding: '12px 14px', borderBottom: '1px solid var(--border)', fontSize: 11 }}>
141
+ {agent.warnings.length ? (
142
+ <ul style={{ margin: 0, paddingLeft: 16, color: 'var(--warning, #f6c66b)' }}>
143
+ {agent.warnings.map((warning) => <li key={warning}>{warning}</li>)}
144
+ </ul>
145
+ ) : (
146
+ <span style={{ color: 'var(--green)' }}>No warnings</span>
147
+ )}
148
+ </td>
149
+ </tr>
150
+ ))}
151
+ </tbody>
152
+ </table>
153
+ </div>
154
+ </Surface>
155
+
156
+ <Surface variant="panel">
157
+ <div style={{ padding: '12px 14px', borderBottom: '1px solid var(--border)' }}>
158
+ <strong style={{ fontSize: 12, letterSpacing: '0.08em', color: 'var(--violet)' }}>Global Context files</strong>
159
+ </div>
160
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 12, padding: 14 }}>
161
+ {data.globalContext.map((file) => (
162
+ <Surface key={file.key} as="article" tone={toneFromHealth(file.exists ? 'ok' : 'warning')}>
163
+ <div style={{ display: 'flex', justifyContent: 'space-between', gap: 10, alignItems: 'center' }}>
164
+ <strong style={{ fontSize: 13 }}>{file.key}</strong>
165
+ <Pill tone={toneFromHealth(file.exists ? 'ok' : 'warning')}>{file.exists ? 'present' : 'missing'}</Pill>
166
+ </div>
167
+ <div style={{ color: '#8a8a92', fontSize: 11, wordBreak: 'break-word', marginTop: 8 }}>{file.path}</div>
168
+ <div style={{ color: '#b8b8c2', fontSize: 11, marginTop: 4 }}>{formatBytes(file.size)} · {formatDate(file.mtime, timezone)}</div>
169
+ {file.symlinkTarget && <div style={{ color: '#b8b8c2', fontSize: 11, marginTop: 4, wordBreak: 'break-word' }}>→ {file.symlinkTarget}</div>}
170
+ {file.warnings.length > 0 && (
171
+ <ul style={{ margin: '8px 0 0', paddingLeft: 16, color: 'var(--warning, #f6c66b)', fontSize: 11 }}>
172
+ {file.warnings.map((warning) => <li key={warning}>{warning}</li>)}
173
+ </ul>
174
+ )}
175
+ </Surface>
176
+ ))}
177
+ </div>
178
+ </Surface>
179
+
180
+ {data.strategy.warnings.length > 0 && (
181
+ <Surface variant="panel" tone="warning">
182
+ <div style={{ padding: '12px 14px', borderBottom: '1px solid var(--border)' }}>
183
+ <strong style={{ fontSize: 12, letterSpacing: '0.08em', color: 'var(--violet)' }}>Warnings</strong>
184
+ </div>
185
+ <ul style={{ margin: 0, padding: '12px 14px 12px 32px', color: 'var(--warning, #f6c66b)', fontSize: 12, lineHeight: 1.5 }}>
186
+ {data.strategy.warnings.map((warning) => <li key={warning}>{warning}</li>)}
187
+ </ul>
188
+ </Surface>
189
+ )}
190
+ </>
191
+ )}
192
+ </div>
193
+ </div>
194
+ </Page>
195
+ );
196
+ }
@@ -0,0 +1,10 @@
1
+ import { Skeleton, CardRowSkeleton } from '../components/Skeleton';
2
+
3
+ const PageSkeleton = () => (
4
+ <div style={{ padding: '24px 20px', maxWidth: 1080, margin: '0 auto' }}>
5
+ <Skeleton width={120} height={20} accent style={{ marginBottom: 20 }} />
6
+ <CardRowSkeleton count={5} height={48} />
7
+ </div>
8
+ );
9
+
10
+ export default PageSkeleton;
@@ -0,0 +1,8 @@
1
+ import MemoryContextPageClient from './MemoryContextPageClient';
2
+
3
+ export const dynamic = 'force-dynamic';
4
+
5
+ export default function MemoryPage() {
6
+
7
+ return <MemoryContextPageClient />;
8
+ }
package/app/page.tsx ADDED
@@ -0,0 +1,31 @@
1
+ import { Suspense } from 'react';
2
+ import DashboardLayout from './components/DashboardLayout';
3
+ import { Skeleton } from './components/Skeleton';
4
+
5
+ export const dynamic = 'force-dynamic';
6
+
7
+ export default function HomePage() {
8
+ return (
9
+ <Suspense fallback={<DashboardSkeleton />}>
10
+ <DashboardLayout initialCosts={{ today: 0, allTime: 0, byModel: [] }} />
11
+ </Suspense>
12
+ );
13
+ }
14
+
15
+ function DashboardSkeleton() {
16
+ return (
17
+ <main className="shell">
18
+ <div style={{ padding: '12px 16px', borderBottom: '1px solid var(--border)' }}>
19
+ <Skeleton width={120} height={20} accent />
20
+ </div>
21
+ <section className="layout layout--cockpit">
22
+ <div style={{ padding: 16, display: 'grid', gap: 12 }}>
23
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))', gap: 12 }}>
24
+ {[1, 2, 3].map(i => <Skeleton key={i} height={80} />)}
25
+ </div>
26
+ <Skeleton height={120} />
27
+ </div>
28
+ </section>
29
+ </main>
30
+ );
31
+ }