@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,105 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+
5
+ interface VersionInfo {
6
+ installed: string;
7
+ latest: string | null;
8
+ updateAvailable: boolean;
9
+ }
10
+
11
+ const DISMISS_KEY = 'rev4a-update-dismissed';
12
+
13
+ export default function VersionBanner() {
14
+ const [info, setInfo] = useState<VersionInfo | null>(null);
15
+ const [dismissed, setDismissed] = useState(true);
16
+ const [updating, setUpdating] = useState(false);
17
+ const [updateError, setUpdateError] = useState<string | null>(null);
18
+ const [updateOk, setUpdateOk] = useState<string | null>(null);
19
+
20
+ useEffect(() => {
21
+ fetch('/api/update-check?check=1')
22
+ .then((r) => r.json())
23
+ .then((d: VersionInfo) => {
24
+ if (d.updateAvailable) {
25
+ setInfo(d);
26
+ const saved = localStorage.getItem(DISMISS_KEY);
27
+ if (saved === d.latest) setDismissed(true);
28
+ else setDismissed(false);
29
+ }
30
+ })
31
+ .catch(() => {});
32
+ }, []);
33
+
34
+ function handleDismiss() {
35
+ if (info?.latest) {
36
+ localStorage.setItem(DISMISS_KEY, info.latest);
37
+ }
38
+ setDismissed(true);
39
+ }
40
+
41
+ async function handleUpdate() {
42
+ setUpdating(true);
43
+ setUpdateError(null);
44
+ setUpdateOk(null);
45
+ try {
46
+ const res = await fetch('/api/update-check', {
47
+ method: 'POST',
48
+ credentials: 'include',
49
+ headers: { 'Content-Type': 'application/json' },
50
+ });
51
+ const data = await res.json();
52
+ if (data.success) {
53
+ setUpdateOk('Update started. Restarting\u2026');
54
+ localStorage.removeItem(DISMISS_KEY);
55
+ setTimeout(function() { window.location.reload(); }, 5000);
56
+ } else {
57
+ setUpdateError(data.error || 'Update failed');
58
+ setUpdating(false);
59
+ }
60
+ } catch (err) {
61
+ setUpdateError(err instanceof Error ? err.message : 'Network error');
62
+ setUpdating(false);
63
+ }
64
+ }
65
+
66
+ if (!info || dismissed) return null;
67
+
68
+ return (
69
+ <div className="update-banner">
70
+ <div className="update-banner__inner">
71
+ <span className="update-banner__text">
72
+ {updateOk ? (
73
+ <span className="update-banner__ok">{updateOk}</span>
74
+ ) : updateError ? (
75
+ <span className="update-banner__err">{updateError}</span>
76
+ ) : (
77
+ <>
78
+ <span className="update-banner__label">Update available</span>
79
+ <span className="update-banner__sep">&mdash;</span>
80
+ <span className="update-banner__version">v{info.latest}</span>
81
+ <span className="update-banner__sep">(installed: v{info.installed})</span>
82
+ </>
83
+ )}
84
+ </span>
85
+
86
+ {!updateOk && (
87
+ <div className="update-banner__actions">
88
+ {updateError ? (
89
+ <button className="ui-btn ui-btn--primary ui-btn--sm" onClick={handleUpdate} disabled={updating}>
90
+ {updating ? 'Updating\u2026' : 'Retry'}
91
+ </button>
92
+ ) : (
93
+ <button className="ui-btn ui-btn--primary ui-btn--sm" onClick={handleUpdate} disabled={updating}>
94
+ {updating ? 'Updating\u2026' : 'Update now'}
95
+ </button>
96
+ )}
97
+ <button className="ui-btn ui-btn--ghost ui-btn--sm" onClick={handleDismiss}>
98
+ Dismiss
99
+ </button>
100
+ </div>
101
+ )}
102
+ </div>
103
+ </div>
104
+ );
105
+ }
@@ -0,0 +1,30 @@
1
+ 'use client';
2
+
3
+ import { useRouter } from 'next/navigation';
4
+
5
+ /**
6
+ * WizardButton
7
+ *
8
+ * Floating button (bottom-left) that opens the wizard.
9
+ * The wizard is a full-screen experience — this button is just
10
+ * a shortcut to revisit it after completion.
11
+ *
12
+ * The outer .floating-actions container handles positioning.
13
+ */
14
+ export default function WizardButton() {
15
+ const router = useRouter();
16
+
17
+ return (
18
+ <button
19
+ className="ochat__trigger"
20
+ aria-label="Wizard"
21
+ title="Wizard"
22
+ onClick={() => router.push('/wizard')}
23
+ >
24
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
25
+ <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" />
26
+ </svg>
27
+ <span className="floating-actions__label">Wizard</span>
28
+ </button>
29
+ );
30
+ }
@@ -0,0 +1,30 @@
1
+ 'use client';
2
+
3
+ import type { ReactNode } from 'react';
4
+ import type { Tone } from './tokens';
5
+ import { toneVars } from './tokens';
6
+
7
+ interface BadgeProps {
8
+ children: ReactNode;
9
+ tone?: Tone;
10
+ }
11
+
12
+ export function Badge({ children, tone = 'neutral' }: BadgeProps) {
13
+ const c = toneVars[tone] ?? toneVars.neutral;
14
+ return (
15
+ <span
16
+ style={{
17
+ display: 'inline-block',
18
+ border: `1px solid ${c.border}`,
19
+ color: c.text,
20
+ background: c.bg,
21
+ borderRadius: 0,
22
+ padding: '2px 8px',
23
+ fontSize: 10,
24
+ letterSpacing: '0.04em',
25
+ }}
26
+ >
27
+ {children}
28
+ </span>
29
+ );
30
+ }
@@ -0,0 +1,59 @@
1
+ 'use client';
2
+
3
+ import type { ButtonHTMLAttributes, ReactNode } from 'react';
4
+ import type { Tone } from './tokens';
5
+
6
+ type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'ghost' | 'success';
7
+ type ButtonSize = 'sm' | 'md';
8
+
9
+ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
10
+ children: ReactNode;
11
+ variant?: ButtonVariant;
12
+ size?: ButtonSize;
13
+ tone?: Tone;
14
+ loading?: boolean;
15
+ }
16
+
17
+ const variantClasses: Record<ButtonVariant, string> = {
18
+ primary: 'ui-btn--primary',
19
+ secondary: 'ui-btn--secondary',
20
+ danger: 'ui-btn--danger',
21
+ ghost: 'ui-btn--ghost',
22
+ success: 'ui-btn--success',
23
+ };
24
+
25
+ const sizeClasses: Record<ButtonSize, string> = {
26
+ sm: 'ui-btn--sm',
27
+ md: 'ui-btn--md',
28
+ };
29
+
30
+ export function Button({
31
+ children,
32
+ variant = 'secondary',
33
+ size = 'md',
34
+ tone,
35
+ loading = false,
36
+ disabled,
37
+ className = '',
38
+ ...rest
39
+ }: ButtonProps) {
40
+ const cls = [
41
+ 'ui-btn',
42
+ variantClasses[variant],
43
+ sizeClasses[size],
44
+ tone ? `ui-tone--${tone}` : '',
45
+ loading ? 'ui-btn--loading' : '',
46
+ className,
47
+ ].filter(Boolean).join(' ');
48
+
49
+ return (
50
+ <button className={cls} disabled={disabled || loading} {...rest}>
51
+ {loading && <span className="ui-btn__spinner" style={{
52
+ width: 16, height: 16, border: '2px solid currentColor', borderTopColor: 'transparent',
53
+ borderRadius: '50%', display: 'inline-block', marginRight: 8, verticalAlign: 'middle',
54
+ animation: 'ui-spin 0.7s linear infinite',
55
+ }} />}
56
+ {children}
57
+ </button>
58
+ );
59
+ }
@@ -0,0 +1,46 @@
1
+ 'use client';
2
+
3
+ import { Modal } from './Modal';
4
+ import { Button } from './Button';
5
+
6
+ interface ConfirmModalProps {
7
+ open: boolean;
8
+ onClose: () => void;
9
+ onConfirm: () => void;
10
+ title: string;
11
+ message: string;
12
+ confirmLabel?: string;
13
+ cancelLabel?: string;
14
+ tone?: 'danger' | 'warning';
15
+ }
16
+
17
+ export function ConfirmModal({
18
+ open,
19
+ onClose,
20
+ onConfirm,
21
+ title,
22
+ message,
23
+ confirmLabel = 'Confirm',
24
+ cancelLabel = 'Cancel',
25
+ tone = 'danger',
26
+ }: ConfirmModalProps) {
27
+ return (
28
+ <Modal open={open} onClose={onClose} title={title} tone={tone} maxWidth={420}>
29
+ <div style={{ fontSize: 13, lineHeight: 1.6, color: '#aaa', marginBottom: 24, whiteSpace: 'pre-wrap' }}>
30
+ {message}
31
+ </div>
32
+ <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
33
+ <Button variant="secondary" size="sm" onClick={onClose}>
34
+ {cancelLabel}
35
+ </Button>
36
+ <Button
37
+ variant={tone === 'danger' ? 'danger' : 'primary'}
38
+ size="sm"
39
+ onClick={onConfirm}
40
+ >
41
+ {confirmLabel}
42
+ </Button>
43
+ </div>
44
+ </Modal>
45
+ );
46
+ }
@@ -0,0 +1,33 @@
1
+ 'use client';
2
+
3
+ import { Button } from './Button';
4
+
5
+ type FilterOption = 'all' | 'running' | 'exited';
6
+
7
+ interface FilterBarProps {
8
+ options: { value: FilterOption; label: string }[];
9
+ active: FilterOption;
10
+ onChange: (v: FilterOption) => void;
11
+ counts?: Partial<Record<FilterOption, number>>;
12
+ }
13
+
14
+ export function FilterBar({ options, active, onChange, counts }: FilterBarProps) {
15
+ return (
16
+ <div className="ui-filter-bar">
17
+ {options.map((opt) => {
18
+ const count = counts?.[opt.value];
19
+ const label = count !== undefined ? `${opt.label} (${count})` : opt.label;
20
+ return (
21
+ <Button
22
+ key={opt.value}
23
+ variant={active === opt.value ? 'primary' : 'ghost'}
24
+ size="sm"
25
+ onClick={() => onChange(opt.value)}
26
+ >
27
+ {label}
28
+ </Button>
29
+ );
30
+ })}
31
+ </div>
32
+ );
33
+ }
@@ -0,0 +1,25 @@
1
+ 'use client';
2
+
3
+ import { useId, type InputHTMLAttributes } from 'react';
4
+
5
+ interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
6
+ label?: string;
7
+ error?: string;
8
+ }
9
+
10
+ export function Input({ label, error, id, className = '', ...rest }: InputProps) {
11
+ const generatedId = useId();
12
+ const inputId = id || generatedId;
13
+
14
+ return (
15
+ <div className="ui-field">
16
+ {label && <label className="ui-field__label" htmlFor={inputId}>{label}</label>}
17
+ <input
18
+ id={inputId}
19
+ className={`ui-input ${error ? 'ui-input--error' : ''} ${className}`}
20
+ {...rest}
21
+ />
22
+ {error && <div className="ui-field__error">{error}</div>}
23
+ </div>
24
+ );
25
+ }
@@ -0,0 +1,83 @@
1
+ 'use client';
2
+
3
+ import type { ReactNode } from 'react';
4
+
5
+ interface ListItemProps {
6
+ children: ReactNode;
7
+ active?: boolean;
8
+ onClick: () => void;
9
+ disabled?: boolean;
10
+ style?: React.CSSProperties;
11
+ }
12
+
13
+ interface ListItemSeparatorProps {
14
+ label: string;
15
+ }
16
+
17
+ interface ListContainerProps {
18
+ children: ReactNode;
19
+ style?: React.CSSProperties;
20
+ }
21
+
22
+ export function ListContainer({ children, style }: ListContainerProps) {
23
+ return (
24
+ <div
25
+ style={{
26
+ borderRight: '1px solid var(--border)',
27
+ overflow: 'auto',
28
+ flexShrink: 0,
29
+ ...style,
30
+ }}
31
+ >
32
+ {children}
33
+ </div>
34
+ );
35
+ }
36
+
37
+ export function ListItem({ children, active, onClick, disabled, style }: ListItemProps) {
38
+ return (
39
+ <button
40
+ type="button"
41
+ onClick={onClick}
42
+ disabled={disabled}
43
+ style={{
44
+ display: 'flex',
45
+ flexDirection: 'column',
46
+ width: '100%',
47
+ textAlign: 'left',
48
+ background: active ? '#1a1030' : 'transparent',
49
+ border: 'none',
50
+ borderBottom: '1px solid var(--border)',
51
+ color: 'var(--text)',
52
+ padding: '10px 12px',
53
+ cursor: disabled ? 'not-allowed' : 'pointer',
54
+ fontSize: 12,
55
+ fontFamily: 'var(--font-mono-stack)',
56
+ opacity: disabled ? 0.5 : 1,
57
+ outline: 'none',
58
+ transition: 'background 0.1s',
59
+ ...style,
60
+ }}
61
+ >
62
+ {children}
63
+ </button>
64
+ );
65
+ }
66
+
67
+ export function ListItemSeparator({ label }: ListItemSeparatorProps) {
68
+ return (
69
+ <div
70
+ style={{
71
+ padding: '8px 12px',
72
+ fontSize: 10,
73
+ letterSpacing: '0.08em',
74
+ color: 'var(--violet)',
75
+ borderBottom: '1px solid var(--border)',
76
+ background: 'var(--bg2)',
77
+ textTransform: 'uppercase',
78
+ }}
79
+ >
80
+ {label}
81
+ </div>
82
+ );
83
+ }
@@ -0,0 +1,30 @@
1
+ 'use client';
2
+
3
+ interface LoadingSpinnerProps {
4
+ label?: string;
5
+ compact?: boolean;
6
+ }
7
+
8
+ export function LoadingSpinner({ label, compact }: LoadingSpinnerProps) {
9
+ if (compact) {
10
+ return (
11
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 10, padding: '40px 20px' }}>
12
+ <div className="ui-spinner" />
13
+ {label && <span style={{ fontSize: 11, color: 'var(--text-dim)', letterSpacing: '0.06em' }}>{label}</span>}
14
+ </div>
15
+ );
16
+ }
17
+
18
+ return (
19
+ <div style={{
20
+ position: 'fixed', inset: 0, zIndex: 9999,
21
+ backdropFilter: 'blur(6px)', WebkitBackdropFilter: 'blur(6px)',
22
+ background: 'rgba(0,0,0,0.35)',
23
+ display: 'flex', alignItems: 'center', justifyContent: 'center',
24
+ flexDirection: 'column', gap: 16,
25
+ }}>
26
+ <div className="ui-spinner ui-spinner--lg" />
27
+ {label && <div style={{ color: 'var(--text)', fontSize: 14, letterSpacing: '0.03em' }}>{label}</div>}
28
+ </div>
29
+ );
30
+ }
@@ -0,0 +1,22 @@
1
+ import type { ReactNode } from 'react';
2
+ import { SkeletonLines, SkeletonMetric } from '../Skeleton';
3
+ import type { Tone } from './tokens';
4
+ import { Surface } from './Surface';
5
+
6
+ interface MetricProps {
7
+ title: string;
8
+ value: ReactNode;
9
+ subtitle?: ReactNode;
10
+ tone?: Tone;
11
+ loading?: boolean;
12
+ }
13
+
14
+ export function Metric({ title, value, subtitle, tone = 'neutral', loading = false }: MetricProps) {
15
+ return (
16
+ <Surface as="article" tone={tone}>
17
+ <div className="ui-kicker">{title}</div>
18
+ <div className="ui-metric-value">{loading ? <SkeletonMetric /> : value}</div>
19
+ {subtitle !== undefined && <div className="ui-muted ui-metric-subtitle">{loading ? <SkeletonLines count={1} /> : subtitle}</div>}
20
+ </Surface>
21
+ );
22
+ }
@@ -0,0 +1,42 @@
1
+ 'use client';
2
+
3
+ import { useEffect, type ReactNode } from 'react';
4
+ import type { Tone } from './tokens';
5
+
6
+ interface ModalProps {
7
+ open: boolean;
8
+ onClose: () => void;
9
+ title: string;
10
+ children: ReactNode;
11
+ tone?: Tone;
12
+ maxWidth?: number;
13
+ }
14
+
15
+ export function Modal({ open, onClose, title, children, maxWidth = 800, tone }: ModalProps) {
16
+ useEffect(() => {
17
+ if (!open) return;
18
+ const handler = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };
19
+ window.addEventListener('keydown', handler);
20
+ return () => window.removeEventListener('keydown', handler);
21
+ }, [open, onClose]);
22
+
23
+ if (!open) return null;
24
+
25
+ return (
26
+ <div className="ui-modal-overlay" onClick={onClose}>
27
+ <div
28
+ className={`ui-modal ${tone ? `ui-tone--${tone}` : ''}`}
29
+ style={{ maxWidth }}
30
+ onClick={(e) => e.stopPropagation()}
31
+ >
32
+ <div className="ui-modal__head">
33
+ <span className="ui-modal__title">{title}</span>
34
+ <button type="button" className="ui-modal__close" onClick={onClose} aria-label="Close">✕</button>
35
+ </div>
36
+ <div className="ui-modal__body">
37
+ {children}
38
+ </div>
39
+ </div>
40
+ </div>
41
+ );
42
+ }
@@ -0,0 +1,18 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export function Page({ children, maxWidth = 1280 }: { children: ReactNode; maxWidth?: number }) {
4
+ return <div className="ui-page"><div className="ui-page__inner" style={{ maxWidth }}>{children}</div></div>;
5
+ }
6
+
7
+ export function PageHeader({ eyebrow, title, description, action }: { eyebrow?: string; title: string; description?: ReactNode; action?: ReactNode }) {
8
+ return (
9
+ <header className="ui-page-header">
10
+ <div>
11
+ {eyebrow && <div className="ui-eyebrow">{eyebrow}</div>}
12
+ <h1 className="ui-page-title">{title}</h1>
13
+ {description && <p className="ui-page-description">{description}</p>}
14
+ </div>
15
+ {action}
16
+ </header>
17
+ );
18
+ }
@@ -0,0 +1,6 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { Tone } from './tokens';
3
+
4
+ export function Pill({ children, tone = 'neutral' }: { children: ReactNode; tone?: Tone }) {
5
+ return <span className={`ui-pill ui-tone--${tone}`}>{children}</span>;
6
+ }
@@ -0,0 +1,24 @@
1
+ 'use client';
2
+
3
+ import { Fragment, type ReactNode } from 'react';
4
+ import type { Tone } from './tokens';
5
+
6
+ interface PropertyListProps {
7
+ rows: { label: string; value: ReactNode; tone?: Tone; dim?: boolean }[];
8
+ }
9
+
10
+ export function PropertyList({ rows }: PropertyListProps) {
11
+ return (
12
+ <div className="ui-property-list">
13
+ {rows.map((r, i) => (
14
+ <Fragment key={i}>
15
+ <span className="ui-property-label">{r.label}</span>
16
+ <span
17
+ className="ui-property-value"
18
+ data-tone={r.tone || (r.dim ? 'dim' : '')}
19
+ >{r.value}</span>
20
+ </Fragment>
21
+ ))}
22
+ </div>
23
+ );
24
+ }
@@ -0,0 +1,26 @@
1
+ 'use client';
2
+
3
+ import { useId, type SelectHTMLAttributes, type ReactNode } from 'react';
4
+
5
+ interface SelectProps extends SelectHTMLAttributes<HTMLSelectElement> {
6
+ label?: string;
7
+ error?: string;
8
+ options: { value: string; label: ReactNode }[];
9
+ }
10
+
11
+ export function Select({ label, error, options, id, className = '', ...rest }: SelectProps) {
12
+ const generatedId = useId();
13
+ const selectId = id || generatedId;
14
+
15
+ return (
16
+ <div className="ui-field">
17
+ {label && <label className="ui-field__label" htmlFor={selectId}>{label}</label>}
18
+ <select id={selectId} className={`ui-select ${error ? 'ui-select--error' : ''} ${className}`} {...rest}>
19
+ {options.map((o) => (
20
+ <option key={o.value} value={o.value}>{o.label}</option>
21
+ ))}
22
+ </select>
23
+ {error && <div className="ui-field__error">{error}</div>}
24
+ </div>
25
+ );
26
+ }
@@ -0,0 +1,32 @@
1
+ import type { ReactNode } from 'react';
2
+ import { SkeletonLines } from '../Skeleton';
3
+ import { Surface } from './Surface';
4
+ import { toneFromHealth } from './tokens';
5
+
6
+ type Health = 'ok' | 'warning' | 'error';
7
+
8
+ interface StatusCardProps {
9
+ title: string;
10
+ health: Health;
11
+ rows: [string, ReactNode][];
12
+ loading?: boolean;
13
+ }
14
+
15
+ export function StatusCard({ title, health, rows, loading = false }: StatusCardProps) {
16
+ return (
17
+ <Surface as="article" tone={toneFromHealth(health)}>
18
+ <div className="ui-card-head">
19
+ <div className="ui-kicker ui-kicker--accent">{title}</div>
20
+ <div className={`ui-health ui-health--${health}`}>{health}</div>
21
+ </div>
22
+ <div className="ui-key-values">
23
+ {loading ? <SkeletonLines count={3} /> : rows.map(([label, value]) => (
24
+ <div key={label} className="ui-key-value">
25
+ <span className="ui-muted">{label}</span>
26
+ <span>{value}</span>
27
+ </div>
28
+ ))}
29
+ </div>
30
+ </Surface>
31
+ );
32
+ }
@@ -0,0 +1,14 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { SurfaceVariant, Tone } from './tokens';
3
+
4
+ interface SurfaceProps {
5
+ children: ReactNode;
6
+ as?: 'section' | 'article' | 'aside' | 'div';
7
+ variant?: SurfaceVariant;
8
+ tone?: Tone;
9
+ className?: string;
10
+ }
11
+
12
+ export function Surface({ children, as: Tag = 'section', variant = 'card', tone = 'neutral', className = '' }: SurfaceProps) {
13
+ return <Tag className={`ui-surface ui-surface--${variant} ui-tone--${tone} ${className}`.trim()}>{children}</Tag>;
14
+ }