@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/bin/rev4a.js ADDED
@@ -0,0 +1,696 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Rev4a CLI — Revolution for Agents
4
+ *
5
+ * Usage:
6
+ * rev4a serve [--port <port>] Start everything (default)
7
+ * rev4a build Manual build
8
+ * rev4a --help / --version Info
9
+ *
10
+ * rev4a serve:
11
+ * 1. Ensures .env exists (auto-generates on first run)
12
+ * 2. Ensures .next build exists (builds if missing)
13
+ * 3. Ensures Docker is installed (installs if missing + root)
14
+ * 4. Ensures agent base image is built (builds if missing)
15
+ * 5. Starts Next.js + daemon + terminal WS as a single process group
16
+ *
17
+ * rev4a serve auto-restarts when rev4a update triggers exit code 42.
18
+ * No systemd required.
19
+ *
20
+ * Graceful shutdown on Ctrl+C / SIGTERM stops all children cleanly.
21
+ */
22
+
23
+ const { spawn, execSync, spawnSync } = require('child_process');
24
+ const { existsSync, mkdirSync, writeFileSync, readFileSync } = require('fs');
25
+ const { join, dirname } = require('path');
26
+ const crypto = require('crypto');
27
+ const os = require('os');
28
+
29
+ const ROOT = join(__dirname, '..');
30
+ const NEXT_DIR = join(ROOT, '.next');
31
+ const PKG_JSON = join(ROOT, 'package.json');
32
+ // Config lives outside the npm package so it survives npm update / reinstall
33
+ const CONFIG_DIR = join(os.homedir(), '.config', 'rev4a');
34
+ const DATA_DIR = join(CONFIG_DIR, 'data');
35
+ const ENV_SOURCE = join(CONFIG_DIR, '.env');
36
+ const ENV_LINK = join(ROOT, '.env');
37
+ const DOCKERFILE = join(ROOT, 'agent-templates', 'base-image', 'Dockerfile');
38
+ const AGENT_IMAGE = 'openclaw-agent-base:latest';
39
+ const AGENT_IMAGE_REGISTRY = 'ghcr.io/flame0510/rev4a/openclaw-agent-base:latest';
40
+ const DOCKER_NETWORK = 'rev4a-network';
41
+
42
+ let pkg;
43
+ try { pkg = require(PKG_JSON); } catch { pkg = { version: '0.0.0' }; }
44
+
45
+ const DEFAULT_PORT = 3740;
46
+
47
+ function log(tag, msg) {
48
+ const ts = new Date().toISOString().replace('T', ' ').slice(0, 19);
49
+ console.log(`[${ts}] [${tag}] ${msg}`);
50
+ }
51
+
52
+ function randomString(len) {
53
+ return crypto.randomBytes(len).toString('base64url').slice(0, len);
54
+ }
55
+
56
+ // ── Helpers ─────────────────────────────────────────────────────────────────
57
+
58
+ function isRoot() {
59
+ return process.getuid && process.getuid() === 0;
60
+ }
61
+
62
+ function isLinux() {
63
+ return os.platform() === 'linux';
64
+ }
65
+
66
+ function hasCmd(name) {
67
+ try {
68
+ execSync(`which ${name}`, { stdio: 'ignore' });
69
+ return true;
70
+ } catch {
71
+ return false;
72
+ }
73
+ }
74
+
75
+ function whichDistro() {
76
+ if (!isLinux()) return null;
77
+ try {
78
+ const id = execSync(
79
+ '. /etc/os-release 2>/dev/null && echo "${ID:-}"',
80
+ { shell: '/bin/bash' }
81
+ ).toString().trim();
82
+ return id || null;
83
+ } catch {
84
+ return null;
85
+ }
86
+ }
87
+
88
+ // ── Docker auto-install ─────────────────────────────────────────────────────
89
+
90
+ function ensureDocker() {
91
+ if (hasCmd('docker')) {
92
+ log('DOCKER', 'Docker is available');
93
+ return;
94
+ }
95
+
96
+ if (!isLinux()) {
97
+ log('DOCKER', '⚠ Docker not found. Rev4a needs Docker to create agent containers.');
98
+ log('DOCKER', ' Install Docker: https://docs.docker.com/get-docker/');
99
+ log('DOCKER', ' Then run: rev4a serve');
100
+ return;
101
+ }
102
+
103
+ if (!isRoot()) {
104
+ log('DOCKER', '⚠ Docker not found. Run Rev4a as root to auto-install:');
105
+ log('DOCKER', ' sudo rev4a serve');
106
+ return;
107
+ }
108
+
109
+ const distro = whichDistro();
110
+ log('DOCKER', `Installing Docker (distro: ${distro || 'unknown'})…`);
111
+
112
+ try {
113
+ execSync('apt-get update -qq && apt-get install -y -qq curl gnupg lsb-release ca-certificates', {
114
+ stdio: 'inherit',
115
+ timeout: 120_000,
116
+ });
117
+
118
+ // Docker's official convenience script (fastest path)
119
+ execSync(
120
+ 'curl -fsSL https://get.docker.com | sh',
121
+ { stdio: 'inherit', timeout: 180_000 },
122
+ );
123
+
124
+ log('DOCKER', 'Docker installed successfully');
125
+ } catch (err) {
126
+ log('DOCKER', '⚠ Docker auto-install failed. Install manually:');
127
+ log('DOCKER', ' https://docs.docker.com/engine/install/');
128
+ log('DOCKER', ' Then run: rev4a serve');
129
+ }
130
+ }
131
+
132
+ // ── Agent image auto-pull ───────────────────────────────────────────────────
133
+
134
+ function ensureAgentImage() {
135
+ if (!hasCmd('docker')) return; // Docker not available — skip, warn later
136
+
137
+ // Check if image already exists locally
138
+ try {
139
+ const r = execSync(
140
+ `docker image inspect ${AGENT_IMAGE} --format '{{.Id}}'`,
141
+ { stdio: 'pipe', timeout: 10_000 },
142
+ );
143
+ if (r.toString().trim()) {
144
+ log('IMAGE', `Agent base image ${AGENT_IMAGE} already exists`);
145
+ return;
146
+ }
147
+ } catch {
148
+ // Image doesn't exist locally
149
+ }
150
+
151
+ log('IMAGE', `Pulling agent base image from ${AGENT_IMAGE_REGISTRY}…`);
152
+
153
+ try {
154
+ execSync(`docker pull ${AGENT_IMAGE_REGISTRY}`, {
155
+ stdio: 'inherit',
156
+ timeout: 300_000, // 5 min timeout
157
+ });
158
+ // Tag locally for Rev4a internal use
159
+ execSync(`docker tag ${AGENT_IMAGE_REGISTRY} ${AGENT_IMAGE}`, {
160
+ stdio: 'inherit',
161
+ timeout: 10_000,
162
+ });
163
+ log('IMAGE', `Agent base image pulled and tagged as ${AGENT_IMAGE}`);
164
+ } catch {
165
+ log('IMAGE', `⚠ Pull from registry failed — attempting local build as fallback…`);
166
+
167
+ if (!existsSync(DOCKERFILE)) {
168
+ log('IMAGE', `⚠ Agent base image Dockerfile not found at ${DOCKERFILE}`);
169
+ log('IMAGE', ' Agent creation will be unavailable until the image is available.');
170
+ return;
171
+ }
172
+
173
+ log('IMAGE', `Building agent base image ${AGENT_IMAGE} (this will take a few minutes)…`);
174
+ log('IMAGE', ' → pulling node:24-bookworm-slim + installing OpenClaw + tooling');
175
+
176
+ try {
177
+ execSync(`docker build -t ${AGENT_IMAGE} -f "${DOCKERFILE}" "${join(DOCKERFILE, '..')}"`, {
178
+ stdio: 'inherit',
179
+ timeout: 600_000, // 10 min timeout
180
+ });
181
+ log('IMAGE', `Agent base image built successfully`);
182
+ } catch {
183
+ log('IMAGE', '⚠ Agent base image build failed. Agent creation will be unavailable.');
184
+ log('IMAGE', ' Pull the image manually: docker pull ' + AGENT_IMAGE_REGISTRY);
185
+ }
186
+ }
187
+ }
188
+
189
+ // ── Docker network ──────────────────────────────────────────────────────────
190
+
191
+ function ensureNetwork() {
192
+ if (!hasCmd('docker')) return;
193
+ try {
194
+ execSync(`docker network inspect ${DOCKER_NETWORK} > /dev/null 2>&1`);
195
+ log('NETWORK', `Docker network ${DOCKER_NETWORK} exists`);
196
+ } catch {
197
+ log('NETWORK', `Creating Docker network ${DOCKER_NETWORK}…`);
198
+ try {
199
+ execSync(`docker network create ${DOCKER_NETWORK}`);
200
+ log('NETWORK', `Network ${DOCKER_NETWORK} created`);
201
+ } catch (err) {
202
+ log('NETWORK', `⚠ Failed to create network: ${err.message}`);
203
+ }
204
+ }
205
+ }
206
+
207
+ // ── Rev4a system rules ───────────────────────────────────────────────────
208
+
209
+ function ensureRev4aRules() {
210
+ // Ensure all Rev4a host directories exist (shared-skills, rev4a-rules, …).
211
+ // Defined centrally in lib/agent-setup so create & recreate routes stay in sync.
212
+ try {
213
+ const { ensureHostDirs } = require(join(ROOT, '.next', 'server', 'lib', 'agent-setup.js'));
214
+ if (typeof ensureHostDirs === 'function') ensureHostDirs();
215
+ } catch { /* .next not built yet — dirs may already exist */ }
216
+
217
+ const rev4aRulesDir = '/docker/rev4a-rules';
218
+ const ruleFile = join(rev4aRulesDir, 'AGENTS.md');
219
+ const source = join(ROOT, 'rev4a-rules', 'AGENTS.md');
220
+
221
+ if (!existsSync(source)) {
222
+ log('RULES', 'No rev4a-rules source found, skipping');
223
+ return;
224
+ }
225
+
226
+ const sourceContent = readFileSync(source, 'utf-8');
227
+ let deployedContent = '';
228
+ try {
229
+ deployedContent = existsSync(ruleFile) ? readFileSync(ruleFile, 'utf-8') : '';
230
+ } catch { /* file doesn't exist yet */ }
231
+
232
+ if (sourceContent === deployedContent) {
233
+ log('RULES', 'Rev4a system rules up to date');
234
+ return;
235
+ }
236
+
237
+ try {
238
+ const sudo = isRoot() ? '' : 'sudo ';
239
+ if (!existsSync(rev4aRulesDir)) {
240
+ execSync(`${sudo}mkdir -p ${rev4aRulesDir}`);
241
+ }
242
+ const tmpFile = `/tmp/rev4a-rules-${Date.now()}.md`;
243
+ writeFileSync(tmpFile, sourceContent, 'utf-8');
244
+ execSync(`${sudo}cp ${tmpFile} ${ruleFile} && ${sudo}rm ${tmpFile}`);
245
+ log('RULES', 'Deployed rev4a system rules to /docker/rev4a-rules/');
246
+ } catch (err) {
247
+ log('RULES', `⚠ Failed to deploy rev4a rules: ${err.message}`);
248
+ }
249
+ }
250
+
251
+ // ── .env setup ──────────────────────────────────────────────────────────────
252
+
253
+ function loadEnv() {
254
+ const envFile = existsSync(ENV_SOURCE) ? ENV_SOURCE : null;
255
+ if (!envFile) return {};
256
+
257
+ const result = {};
258
+ try {
259
+ const content = readFileSync(envFile, 'utf-8');
260
+ for (const line of content.split('\n')) {
261
+ const trimmed = line.trim();
262
+ if (trimmed.startsWith('#') || !trimmed.includes('=')) continue;
263
+ const eqIdx = trimmed.indexOf('=');
264
+ const key = trimmed.slice(0, eqIdx).trim();
265
+ let val = trimmed.slice(eqIdx + 1).trim();
266
+
267
+ // Strip surrounding quotes (single or double)
268
+ if (
269
+ (val.startsWith('"') && val.endsWith('"')) ||
270
+ (val.startsWith("'") && val.endsWith("'"))
271
+ ) {
272
+ val = val.slice(1, -1);
273
+ }
274
+
275
+ if (key) result[key] = val;
276
+ }
277
+ } catch (err) {
278
+ log('ENV', `Warning: failed to read ${envFile} — ${err.message}`);
279
+ }
280
+ return result;
281
+ }
282
+
283
+ function ensureEnv() {
284
+ if (existsSync(ENV_SOURCE)) {
285
+ log('ENV', 'Configuration found');
286
+ // Ensure symlink so the app can find .env at the root
287
+ if (!existsSync(ENV_LINK)) {
288
+ try { require('fs').symlinkSync(ENV_SOURCE, ENV_LINK); } catch {}
289
+ }
290
+ // Seed agent token file if missing
291
+ seedAgentToken();
292
+ return;
293
+ }
294
+
295
+ log('ENV', 'Generating default configuration…');
296
+
297
+ mkdirSync(CONFIG_DIR, { recursive: true });
298
+
299
+ const jwt = randomString(48);
300
+ const token = randomString(32);
301
+ const apiKey = 'oly_' + randomString(48);
302
+ const gatewayToken = randomString(48);
303
+
304
+ const env = [
305
+ '# Rev4a environment — auto-generated on first run',
306
+ `# Generated: ${new Date().toISOString()}`,
307
+ '',
308
+ '# Dashboard login password (set via setup wizard on first visit)',
309
+ 'REV4A_PASSWORD=',
310
+ '',
311
+ '# JWT secret for session tokens (auto-generated)',
312
+ `REV4A_JWT_SECRET=${jwt}`,
313
+ '',
314
+ '# API token for Rev4a → agents communication (auto-generated)',
315
+ `REV4A_TOKEN=${token}`,
316
+ '',
317
+ '# API key for Gateway provider endpoint (auto-generated)',
318
+ `REV4A_API_KEY=${apiKey}`,
319
+ '',
320
+ '# Gateway token shared with agent containers (auto-generated)',
321
+ `GATEWAY_TOKEN=${gatewayToken}`,
322
+ '',
323
+ ].join('\n') + '\n';
324
+
325
+ writeFileSync(ENV_SOURCE, env, { mode: 0o600 });
326
+ log('ENV', `Written ${ENV_SOURCE}`);
327
+
328
+ // Symlink in package root so Next.js server finds it
329
+ try { require('fs').symlinkSync(ENV_SOURCE, ENV_LINK); } catch {}
330
+
331
+ seedAgentToken();
332
+ }
333
+
334
+ function seedAgentToken() {
335
+ const agentsTokenPath = join(DATA_DIR, 'agents-token.json');
336
+ mkdirSync(DATA_DIR, { recursive: true });
337
+ if (existsSync(agentsTokenPath)) return;
338
+
339
+ const envVars = loadEnv();
340
+ const gatewayToken = envVars.GATEWAY_TOKEN || randomString(48);
341
+ const agentsToken = { token: gatewayToken, updated_at: Date.now() };
342
+ writeFileSync(agentsTokenPath, JSON.stringify(agentsToken, null, 2), { mode: 0o600 });
343
+ log('ENV', 'Agent token file seeded');
344
+ }
345
+
346
+ // ── Build ───────────────────────────────────────────────────────────────────
347
+
348
+ function build() {
349
+ log('BUILD', 'Building Next.js app…');
350
+ try {
351
+ execSync('npx next build', { stdio: 'inherit', cwd: ROOT, timeout: 120_000 });
352
+ log('BUILD', 'Build complete');
353
+ } catch (err) {
354
+ log('BUILD', `Build failed: ${err.message}`);
355
+ process.exit(1);
356
+ }
357
+ }
358
+
359
+ // ── Version check ───────────────────────────────────────────────────────────
360
+
361
+ function checkVersion() {
362
+ const pkgName = '@flame0510/project-aether';
363
+ try {
364
+ const latest = execSync(
365
+ `npm view ${pkgName} version --registry https://registry.npmjs.org 2>/dev/null`,
366
+ { stdio: 'pipe', timeout: 8000 },
367
+ ).toString().trim();
368
+ if (!latest || latest === pkg.version) return;
369
+
370
+ console.log('');
371
+ console.log(' ╔══════════════════════════════════════════╗');
372
+ console.log(' ║ 🆕 Rev4a update available! ║');
373
+ console.log(' ║ Latest: ' + `v${latest}`.padEnd(31) + ' ║');
374
+ console.log(' ║ Installed: ' + `v${pkg.version}`.padEnd(28) + ' ║');
375
+ console.log(' ║ ║');
376
+ console.log(' ║ Run: npm install -g @flame0510/project-aether ║');
377
+ console.log(' ║ Then: restart rev4a ║');
378
+ console.log(' ╚══════════════════════════════════════════╝');
379
+ console.log('');
380
+ } catch {
381
+ // No internet or npm not configured — skip silently
382
+ }
383
+ }
384
+
385
+ // ── Preflight ───────────────────────────────────────────────────────────────
386
+
387
+ function preflight() {
388
+ log('PREFLIGHT', 'Running pre-flight checks…');
389
+ ensureEnv();
390
+ ensureDocker();
391
+ ensureNetwork();
392
+ ensureAgentImage();
393
+ ensureRev4aRules();
394
+
395
+ if (!existsSync(NEXT_DIR)) {
396
+ log('PREFLIGHT', 'No build found — running build first…');
397
+ build();
398
+ }
399
+ }
400
+
401
+ // ── Serve (start all 3 processes) ───────────────────────────────────────────
402
+
403
+ function serve(port) {
404
+ preflight();
405
+ // Version check is non-blocking — runs after startup
406
+ setTimeout(checkVersion, 2000);
407
+
408
+ // Load .env vars so child processes (especially Next.js) can read REV4A_*
409
+ // at runtime. process.env may not have them if rev4a serve was started
410
+ // without the vars already exported in the shell.
411
+ const envVars = loadEnv();
412
+ const childEnv = {
413
+ ...process.env,
414
+ ...envVars,
415
+ PORT: String(port),
416
+ NODE_ENV: 'production',
417
+ };
418
+
419
+ const children = [];
420
+ const wsPort = process.env.TERMINAL_WS_PORT || '3741';
421
+
422
+ log('SERVE', `Starting Rev4a on port ${port}…`);
423
+
424
+ // Next.js
425
+ const next = spawn('npx', ['next', 'start', '-p', String(port)], {
426
+ cwd: ROOT,
427
+ stdio: 'inherit',
428
+ env: childEnv,
429
+ });
430
+ children.push(next);
431
+ log('SERVE', `Next.js PID ${next.pid}`);
432
+
433
+ // Daemon
434
+ const daemon = spawn(process.execPath, ['daemon.js'], {
435
+ cwd: ROOT,
436
+ stdio: 'inherit',
437
+ env: childEnv,
438
+ });
439
+ children.push(daemon);
440
+ log('SERVE', `Daemon PID ${daemon.pid}`);
441
+
442
+ // Terminal WebSocket
443
+ const ws = spawn(process.execPath, ['terminal-ws-server.js'], {
444
+ cwd: ROOT,
445
+ stdio: 'inherit',
446
+ env: { ...childEnv, TERMINAL_WS_PORT: wsPort },
447
+ });
448
+ children.push(ws);
449
+ log('SERVE', `Terminal WS PID ${ws.pid}`);
450
+
451
+ let shutdownSignal = null;
452
+ let shuttingDown = false;
453
+ const cleanup = (signal) => {
454
+ if (shuttingDown) return;
455
+ shuttingDown = true;
456
+ shutdownSignal = signal;
457
+ log('SERVE', `${signal} — stopping all services…`);
458
+ for (const child of children) {
459
+ try { child.kill('SIGTERM'); } catch {}
460
+ }
461
+ // Force kill after 8s if any child survives
462
+ setTimeout(() => {
463
+ for (const child of children) {
464
+ try { child.kill('SIGKILL'); } catch {}
465
+ }
466
+ process.exit(0);
467
+ }, 8000);
468
+ };
469
+
470
+ process.on('SIGINT', () => cleanup('SIGINT'));
471
+ process.on('SIGTERM', () => cleanup('SIGTERM'));
472
+
473
+ // Watch for restart flag (written by rev4a update command)
474
+ const RESTART_FLAG = join(DATA_DIR, '.restart-flag');
475
+ const restartWatcher = setInterval(() => {
476
+ if (existsSync(RESTART_FLAG)) {
477
+ try { require('fs').unlinkSync(RESTART_FLAG); } catch {}
478
+ cleanup('UPDATE');
479
+ }
480
+ }, 2000);
481
+ restartWatcher.unref();
482
+
483
+ // Wait for all children to exit, then restart if it was an update
484
+ let exited = 0;
485
+ for (const child of children) {
486
+ child.on('exit', (code) => {
487
+ exited++;
488
+ log(child.spawnfile, `Exited (code ${code})`);
489
+ if (exited >= children.length) {
490
+ if (shutdownSignal === 'UPDATE') {
491
+ // Poll until the port is actually free before restarting
492
+ const net = require('net');
493
+ const checkPort = () => {
494
+ const tester = net.createServer();
495
+ tester.once('error', () => {
496
+ // Port still in use — retry
497
+ setTimeout(checkPort, 500);
498
+ });
499
+ tester.once('listening', () => {
500
+ tester.close(() => process.exit(42));
501
+ });
502
+ tester.listen(port, '0.0.0.0');
503
+ };
504
+ checkPort();
505
+ } else {
506
+ process.exit(0);
507
+ }
508
+ }
509
+ });
510
+ }
511
+
512
+ // Print URL after a short delay (give Next.js time to bind)
513
+ setTimeout(() => {
514
+ console.log('');
515
+ console.log(' ╔══════════════════════════════════════╗');
516
+ console.log(' ║ Rev4a is running ║');
517
+ console.log(` ║ http://localhost:${String(port).padEnd(5)} ║`);
518
+ console.log(' ╚══════════════════════════════════════╝');
519
+ console.log('');
520
+ }, 2000);
521
+ }
522
+
523
+ // ── CLI entry point ─────────────────────────────────────────────────────────
524
+
525
+ const args = process.argv.slice(2);
526
+ const cmd = args[0];
527
+
528
+ const BANNER = `
529
+ ██
530
+ ███████╗ ██╗ █████╗ ██╗
531
+ ██╔═══██╗ ██║ ██╔══██╗ ██║
532
+ ██║ ██║ ██║ ████████║ ██║
533
+ ██║ ██║ ██║ ██╔════╝ ██║
534
+ ███████╔╝ ██████╗ ██║ ██║
535
+ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝
536
+
537
+ `;
538
+
539
+ const HELP_TEXT = `
540
+ USAGE:
541
+ rev4a serve [--port <port>] Start the dashboard + daemon + terminal WS
542
+ rev4a build Build the Next.js app
543
+ rev4a update Check for updates and install latest
544
+ rev4a restart Signal a running rev4a to restart
545
+ rev4a --help / --version Show help or version
546
+
547
+ EXAMPLES:
548
+ rev4a serve → http://localhost:3740
549
+ rev4a serve --port 8080 → http://localhost:8080
550
+
551
+ On first run, rev4a serve automatically:
552
+ • Generates .env (JWT secret + API token)
553
+ • Installs Docker if missing (requires root)
554
+ • Builds the agent base image (openclaw-agent-base:latest)
555
+ • Builds the Next.js app if no build is found
556
+ • Starts Next.js, daemon, and terminal WebSocket as a single process group
557
+ • Cleanly stops all services on Ctrl+C / SIGTERM
558
+
559
+ CONFIGURATION:
560
+ All settings live in .env (auto-generated on first run).
561
+ Set your admin password via the web setup wizard at first visit.
562
+ `;
563
+
564
+ if (!cmd || cmd === '--help' || cmd === '-h' || cmd === 'help') {
565
+ console.log(BANNER);
566
+ console.log(`Rev4a v${pkg.version} — Agency Monitor Dashboard`);
567
+ console.log(HELP_TEXT);
568
+ process.exit(0);
569
+ }
570
+
571
+ if (cmd === 'serve') {
572
+ console.log(BANNER);
573
+ const portIdx = args.indexOf('--port');
574
+ const port = portIdx >= 0 ? parseInt(args[portIdx + 1], 10) : (parseInt(process.env.PORT, 10) || DEFAULT_PORT);
575
+
576
+ // Kill any stale rev4a processes on this port before starting.
577
+ // Prevents zombie process accumulation on systems without systemd.
578
+ try {
579
+ execSync(`fuser -k ${port}/tcp 2>/dev/null || true`, { stdio: 'pipe', timeout: 5000 });
580
+ } catch { /* best-effort */ }
581
+
582
+ // Auto-restart loop: when rev4a update triggers exit code 42,
583
+ // re-exec rev4a serve so the new binary is loaded. Works with
584
+ // or without systemd (systemd Restart=always also handles it).
585
+ let exitCode = 42;
586
+ while (exitCode === 42) {
587
+ const result = spawnSync(
588
+ process.execPath,
589
+ [process.argv[1] || join(__dirname, 'rev4a.js'), '_run', String(port)],
590
+ { stdio: 'inherit', env: process.env }
591
+ );
592
+ exitCode = result.signal ? 0 : (result.status ?? 0);
593
+ if (exitCode === 42) {
594
+ console.log('[REV4A] Restarting with updated binary…');
595
+ // Wait for the port to be fully released before respawning
596
+ const net = require('net');
597
+ const portNum = port;
598
+ try {
599
+ const { execSync } = require('child_process');
600
+ execSync('sleep 1', { stdio: 'ignore' });
601
+ } catch {}
602
+ }
603
+ }
604
+ process.exit(exitCode);
605
+ }
606
+
607
+ // Internal entry point: called by the auto-restart spawn above
608
+ if (cmd === '_run') {
609
+ console.log(BANNER);
610
+ const port = parseInt(args[args.indexOf('_run') + 1], 10) || DEFAULT_PORT;
611
+ serve(port);
612
+ return;
613
+ }
614
+
615
+ if (cmd === '--help' || cmd === '-h' || cmd === 'help') {
616
+ console.log(HELP_TEXT);
617
+ process.exit(0);
618
+ }
619
+
620
+ if (cmd === '--version' || cmd === '-v' || cmd === 'version') {
621
+ console.log(`rev4a v${pkg.version}`);
622
+ process.exit(0);
623
+ }
624
+
625
+ if (cmd === 'build') {
626
+ ensureEnv();
627
+ build();
628
+ process.exit(0);
629
+ }
630
+
631
+ if (cmd === 'restart') {
632
+ // Find and kill the running rev4a serve process, then restart it.
633
+ // Uses the restart flag file as the primary mechanism (works with
634
+ // the watcher inside _run), but also does a direct kill + respawn
635
+ // as a fallback for environments where the watcher might miss the flag
636
+ // (e.g. multiple zombie processes, nohup edge cases).
637
+ const RESTART_FLAG = join(DATA_DIR, '.restart-flag');
638
+ writeFileSync(RESTART_FLAG, '');
639
+ console.log('Restart signal sent. The running rev4a serve process will restart.');
640
+
641
+ // Also kill the port directly — ensures restart even if watcher misses the flag
642
+ const port = parseInt(process.env.PORT, 10) || DEFAULT_PORT;
643
+ try {
644
+ execSync(`fuser -k ${port}/tcp 2>/dev/null || true`, { stdio: 'pipe', timeout: 5000 });
645
+ } catch { /* best-effort */ }
646
+ process.exit(0);
647
+ }
648
+
649
+ if (cmd === 'update') {
650
+ console.log(`Checking for updates…`);
651
+ try {
652
+ const latest = execSync(
653
+ 'npm view @flame0510/project-aether version --registry https://registry.npmjs.org 2>/dev/null',
654
+ { stdio: 'pipe', timeout: 10_000 },
655
+ ).toString().trim();
656
+ if (latest === pkg.version) {
657
+ console.log(`Already up to date (v${pkg.version}).`);
658
+ process.exit(0);
659
+ }
660
+ console.log(`Updating from v${pkg.version} to v${latest}…`);
661
+ const root = process.env.REV4A_ROOT || ROOT;
662
+ const isGitRepo = existsSync(join(root, '.git'));
663
+ if (isGitRepo) {
664
+ console.log('Detected git repo — pulling and building…');
665
+ execSync(`cd ${root} && git pull && npm install && npm run build`, {
666
+ stdio: 'inherit',
667
+ timeout: 120_000,
668
+ });
669
+ } else {
670
+ console.log('Using npm global install…');
671
+ execSync('npm install -g @flame0510/project-aether@latest', {
672
+ stdio: 'inherit',
673
+ timeout: 120_000,
674
+ });
675
+ // Wipe old build so rev4a serve rebuilds with the new version
676
+ const nextDir = join(ROOT, '.next');
677
+ if (existsSync(nextDir)) {
678
+ try { execSync(`rm -rf "${nextDir}"`, { stdio: 'inherit' }); } catch {}
679
+ }
680
+ }
681
+ console.log('Update complete. Restarting…');
682
+ try {
683
+ execSync('rev4a restart', { stdio: 'inherit', timeout: 10_000 });
684
+ } catch {
685
+ // rev4a serve not running — restart flag written, harmless
686
+ }
687
+ } catch (err) {
688
+ console.error('Update failed:', err.message);
689
+ process.exit(1);
690
+ }
691
+ process.exit(0);
692
+ }
693
+
694
+ console.error(`Unknown command: ${cmd}`);
695
+ console.error('Run rev4a --help for usage.');
696
+ process.exit(1);