@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,23 @@
1
+ import { NextResponse, type NextRequest } from 'next/server';
2
+ import { openDb } from '@/lib/db';
3
+ import { requireAuthJWT } from '@/lib/rev4a-auth';
4
+
5
+ export async function GET(request: NextRequest): Promise<NextResponse> {
6
+ const denied = await requireAuthJWT(request); if (denied) return denied as any;
7
+ try {
8
+ const db = openDb();
9
+ const sessions = db.prepare(`
10
+ SELECT s.*,
11
+ l.label AS lineage_label,
12
+ l.agent_name AS lineage_agent_name
13
+ FROM sessions s
14
+ LEFT JOIN lineage l ON s.session_id = l.child_id
15
+ ORDER BY s.started_at DESC
16
+ LIMIT 2000
17
+ `).all();
18
+ db.close();
19
+ return NextResponse.json(sessions);
20
+ } catch (e: unknown) {
21
+ return NextResponse.json({ error: (e as Error).message }, { status: 500 });
22
+ }
23
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * GET /api/setup/agent-image
3
+ *
4
+ * Checks if openclaw-agent-base:latest exists locally or needs downloading.
5
+ * Returns status and a hint for the UI.
6
+ *
7
+ * POST /api/setup/agent-image
8
+ *
9
+ * Downloads openclaw-agent-base:latest from ghcr.io (public registry).
10
+ * Streams progress via SSE so the setup wizard can show a live progress bar.
11
+ *
12
+ * Only one download at a time — returns 409 if already running.
13
+ * Cleans up the docker process if the client disconnects.
14
+ */
15
+ import { NextResponse } from 'next/server';
16
+ import { downloadAgentImage, getIsDownloading } from '@/lib/buildAgentImage';
17
+ import { execSync } from 'child_process';
18
+
19
+ export const dynamic = 'force-dynamic';
20
+
21
+ function dockerOk(): boolean {
22
+ try {
23
+ execSync('docker version --format "{{.Client.Version}}" 2>/dev/null', {
24
+ encoding: 'utf-8',
25
+ timeout: 2000,
26
+ });
27
+ return true;
28
+ } catch {
29
+ return false;
30
+ }
31
+ }
32
+
33
+ export async function GET(): Promise<NextResponse> {
34
+ if (!dockerOk()) {
35
+ return NextResponse.json({ exists: false });
36
+ }
37
+ try {
38
+ const { exec } = await import('child_process');
39
+ const result = await new Promise<string>((resolve, reject) => {
40
+ exec(
41
+ 'docker image inspect openclaw-agent-base:latest --format "{{.ID}}" 2>/dev/null',
42
+ { timeout: 5000 },
43
+ (err, stdout) => {
44
+ if (err) return reject(err);
45
+ resolve(stdout.trim());
46
+ },
47
+ );
48
+ });
49
+ if (result) {
50
+ return NextResponse.json({ exists: true, imageId: result });
51
+ }
52
+ return NextResponse.json({ exists: false });
53
+ } catch {
54
+ return NextResponse.json({ exists: false });
55
+ }
56
+ }
57
+
58
+ export async function POST(): Promise<Response> {
59
+ if (getIsDownloading()) {
60
+ return new Response(
61
+ JSON.stringify({ error: 'A download is already in progress' }),
62
+ { status: 409, headers: { 'Content-Type': 'application/json' } },
63
+ );
64
+ }
65
+
66
+ const encoder = new TextEncoder();
67
+
68
+ const stream = new ReadableStream({
69
+ async start(controller) {
70
+ const send = (data: string) => {
71
+ try {
72
+ controller.enqueue(encoder.encode(`data: ${data}\n\n`));
73
+ } catch { /* stream closed */ }
74
+ };
75
+
76
+ const ctrl = controller as ReadableStreamDefaultController & { signal?: AbortSignal };
77
+
78
+ try {
79
+ await downloadAgentImage({
80
+ force: false, // setup: skip if already exists
81
+ onEvent: send,
82
+ signal: ctrl.signal,
83
+ });
84
+ } catch (e: unknown) {
85
+ send(JSON.stringify({
86
+ type: 'error',
87
+ message: (e as Error).message || 'Failed to download agent image',
88
+ }));
89
+ }
90
+ controller.close();
91
+ },
92
+ cancel() { /* downloadAgentImage handles cleanup via signal */ },
93
+ });
94
+
95
+ return new Response(stream, {
96
+ headers: {
97
+ 'Content-Type': 'text/event-stream',
98
+ 'Cache-Control': 'no-cache',
99
+ Connection: 'keep-alive',
100
+ },
101
+ });
102
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * POST /api/setup/password
3
+ *
4
+ * No auth required. Sets the admin password on first run.
5
+ * Refuses if a password is already configured.
6
+ * Also generates a random JWT secret and API token if missing.
7
+ *
8
+ * Body: { password: string }
9
+ *
10
+ * Does NOT restart the server — the password takes effect
11
+ * after the full setup wizard completes and the server restarts.
12
+ */
13
+ import { NextResponse } from 'next/server';
14
+ import * as fs from 'fs';
15
+ import * as path from 'path';
16
+ import * as crypto from 'crypto';
17
+ import { ENV_FILE } from '@/lib/rev4a-paths';
18
+
19
+ export const dynamic = 'force-dynamic';
20
+ export const runtime = 'nodejs';
21
+
22
+ function generateSecret(len: number): string {
23
+ return crypto.randomBytes(len).toString('base64url').slice(0, len);
24
+ }
25
+
26
+ export async function POST(request: Request): Promise<NextResponse> {
27
+ // Check both env and .env file (same fallback pattern as login/status routes)
28
+ let currentPassword = process.env.REV4A_PASSWORD;
29
+ if (typeof currentPassword !== 'string' || currentPassword.length === 0) {
30
+ try {
31
+ if (fs.existsSync(ENV_FILE)) {
32
+ const content = fs.readFileSync(ENV_FILE, 'utf-8');
33
+ const match = content.match(/^REV4A_PASSWORD=(.+)$/m);
34
+ if (match && match[1].trim()) {
35
+ currentPassword = match[1].trim();
36
+ }
37
+ }
38
+ } catch { /* ignore */ }
39
+ }
40
+ if (typeof currentPassword === 'string' && currentPassword.length > 0) {
41
+ return NextResponse.json(
42
+ { error: 'Password is already configured. Use the Config page to change it.' },
43
+ { status: 409 },
44
+ );
45
+ }
46
+
47
+ let body: { password?: string };
48
+ try {
49
+ body = await request.json();
50
+ } catch {
51
+ return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 });
52
+ }
53
+
54
+ if (!body.password || typeof body.password !== 'string') {
55
+ return NextResponse.json({ error: 'password is required' }, { status: 400 });
56
+ }
57
+
58
+ if (body.password.length < 8) {
59
+ return NextResponse.json(
60
+ { error: 'Password must be at least 8 characters' },
61
+ { status: 400 },
62
+ );
63
+ }
64
+
65
+ try {
66
+ // Ensure parent directory exists
67
+ const dir = path.dirname(ENV_FILE);
68
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
69
+
70
+ let content = '';
71
+ if (fs.existsSync(ENV_FILE)) {
72
+ content = fs.readFileSync(ENV_FILE, 'utf-8');
73
+ }
74
+
75
+ // Ensure password line
76
+ if (content.includes('REV4A_PASSWORD=')) {
77
+ content = content
78
+ .split('\n')
79
+ .map((line) =>
80
+ line.startsWith('REV4A_PASSWORD=')
81
+ ? 'REV4A_PASSWORD=' + body.password
82
+ : line,
83
+ )
84
+ .join('\n');
85
+ } else {
86
+ content += '\nREV4A_PASSWORD=' + body.password + '\n';
87
+ }
88
+
89
+ // Generate JWT secret if missing
90
+ if (!content.includes('REV4A_JWT_SECRET=')) {
91
+ const secret = generateSecret(64);
92
+ content += '\nREV4A_JWT_SECRET=' + secret + '\n';
93
+ }
94
+
95
+ // Generate API token if missing
96
+ if (!content.includes('REV4A_TOKEN=')) {
97
+ const token = generateSecret(32);
98
+ content += '\nREV4A_TOKEN=' + token + '\n';
99
+ }
100
+
101
+ fs.writeFileSync(ENV_FILE, content.trim() + '\n', { encoding: 'utf-8', mode: 0o600 });
102
+
103
+ // Update in-memory so proxy redirects work before restart
104
+ process.env.REV4A_PASSWORD = body.password;
105
+
106
+ return NextResponse.json({ status: 'ok' });
107
+ } catch (e: unknown) {
108
+ console.error('[setup/password]', e);
109
+ return NextResponse.json({ error: 'Failed to write password' }, { status: 500 });
110
+ }
111
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * POST /api/setup/restart
3
+ *
4
+ * No auth required. Restarts the Rev4a server so that changes to
5
+ * .env (especially REV4A_PASSWORD) take effect.
6
+ *
7
+ * Next.js inlines env vars at build time, so a runtime process.env
8
+ * mutation is not enough — the process must restart to reload .env.
9
+ *
10
+ * Strategy:
11
+ * 1. If running under systemd (rev4a.service), restart via systemctl.
12
+ * 2. Otherwise send SIGTERM to the current process. The parent process
13
+ * manager (npx rev4a all or a shell wrapper) should restart it.
14
+ */
15
+ import { NextResponse } from 'next/server';
16
+ import * as fs from 'fs';
17
+ import { execSync } from 'child_process';
18
+ import { ENV_FILE } from '@/lib/rev4a-paths';
19
+
20
+ export const dynamic = 'force-dynamic';
21
+ export const runtime = 'nodejs';
22
+
23
+ export async function POST(): Promise<NextResponse> {
24
+ try {
25
+ if (!fs.existsSync(ENV_FILE)) {
26
+ return NextResponse.json({ error: '.env file not found' }, { status: 500 });
27
+ }
28
+
29
+ const envContent = fs.readFileSync(ENV_FILE, 'utf-8');
30
+ const match = envContent.match(/^REV4A_PASSWORD=(.*)$/m);
31
+ if (!match || !match[1].trim()) {
32
+ return NextResponse.json(
33
+ { error: 'REV4A_PASSWORD is empty or missing in .env' },
34
+ { status: 400 },
35
+ );
36
+ }
37
+
38
+ // Try systemd first (production).
39
+ // If it fails, fall back to SIGTERM (dev mode / container).
40
+ let usedSystemd = false;
41
+ try {
42
+ execSync('sudo systemctl daemon-reload', { timeout: 5000 });
43
+ usedSystemd = true;
44
+ } catch {
45
+ // systemctl not available — fine, we'll SIGTERM
46
+ }
47
+
48
+ const response = NextResponse.json({
49
+ status: 'ok',
50
+ restarting: true,
51
+ mode: usedSystemd ? 'systemd' : 'process',
52
+ });
53
+
54
+ if (usedSystemd) {
55
+ setTimeout(() => {
56
+ try {
57
+ execSync('sudo systemctl restart rev4a.service', { timeout: 15000 });
58
+ } catch (err) {
59
+ console.error('[setup/restart] systemd restart failed:', err);
60
+ }
61
+ }, 2000);
62
+ } else {
63
+ // Dev mode / container: send SIGTERM after flushing response.
64
+ // The parent (npx rev4a all or shell) will restart the process,
65
+ // which will re-read .env at startup.
66
+ setTimeout(() => {
67
+ process.kill(process.pid, 'SIGTERM');
68
+ }, 2000);
69
+ }
70
+
71
+ return response;
72
+ } catch (e: unknown) {
73
+ console.error('[setup/restart]', e);
74
+ return NextResponse.json({ error: 'Failed to restart' }, { status: 500 });
75
+ }
76
+ }
@@ -0,0 +1,105 @@
1
+ import { NextResponse } from 'next/server';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { execFileSync } from 'child_process';
5
+ import { requireAuthJWT } from '@/lib/rev4a-auth';
6
+
7
+ const SHARED_SKILLS_DIR = '/docker/shared-skills';
8
+
9
+ /**
10
+ * DELETE /api/skills/delete
11
+ *
12
+ * Deletes a skill. Supports two modes:
13
+ * - shared: removes from /docker/shared-skills/
14
+ * - workspace/container: removes from container via docker exec rm -rf
15
+ *
16
+ * Body: { skillName, containerName? }
17
+ */
18
+ export async function DELETE(request) {
19
+ const denied = await requireAuthJWT(request);
20
+ if (denied) return denied;
21
+
22
+ try {
23
+ const { skillName, containerName } = await request.json();
24
+
25
+ if (!skillName) {
26
+ return NextResponse.json(
27
+ { error: 'skillName is required' },
28
+ { status: 400 }
29
+ );
30
+ }
31
+
32
+ // Sanitize: prevent path traversal
33
+ if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\/[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/.test(skillName)) {
34
+ return NextResponse.json(
35
+ { error: 'Invalid skill name format' },
36
+ { status: 400 }
37
+ );
38
+ }
39
+
40
+ if (containerName) {
41
+ // Delete from agent container workspace
42
+ const skillDir = `/root/.openclaw/workspace/skills/${skillName}`;
43
+
44
+ // Verify container exists
45
+ try {
46
+ execFileSync('docker', ['inspect', containerName], { encoding: 'utf8', timeout: 5000 });
47
+ } catch {
48
+ return NextResponse.json(
49
+ { error: `Container ${containerName} not found` },
50
+ { status: 404 }
51
+ );
52
+ }
53
+
54
+ // Check if skill exists in container
55
+ try {
56
+ execFileSync('docker', ['exec', containerName, 'test', '-d', skillDir], { encoding: 'utf8', timeout: 5000 });
57
+ } catch {
58
+ return NextResponse.json(
59
+ { error: `Skill "${skillName}" not found in container ${containerName}` },
60
+ { status: 404 }
61
+ );
62
+ }
63
+
64
+ execFileSync('docker', ['exec', containerName, 'rm', '-rf', skillDir], { encoding: 'utf8', timeout: 10000 });
65
+
66
+ return NextResponse.json({
67
+ ok: true,
68
+ skillName,
69
+ containerName,
70
+ deleted: true,
71
+ from: 'workspace',
72
+ });
73
+ }
74
+
75
+ // Delete from shared skills directory
76
+ const skillDir = path.join(SHARED_SKILLS_DIR, skillName);
77
+
78
+ if (!fs.existsSync(skillDir)) {
79
+ return NextResponse.json(
80
+ { error: `Skill "${skillName}" not found in shared skills` },
81
+ { status: 404 }
82
+ );
83
+ }
84
+
85
+ // Prevent deletion of non-shared paths (safety check)
86
+ if (!skillDir.startsWith(SHARED_SKILLS_DIR)) {
87
+ return NextResponse.json(
88
+ { error: 'Invalid path' },
89
+ { status: 400 }
90
+ );
91
+ }
92
+
93
+ fs.rmSync(skillDir, { recursive: true, force: true });
94
+
95
+ return NextResponse.json({
96
+ ok: true,
97
+ skillName,
98
+ deleted: true,
99
+ from: 'shared',
100
+ });
101
+ } catch (err) {
102
+ const msg = err instanceof Error ? err.message : 'Internal error';
103
+ return NextResponse.json({ error: msg }, { status: 500 });
104
+ }
105
+ }
@@ -0,0 +1,129 @@
1
+ import { NextResponse } from 'next/server';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { execSync, execFileSync } from 'child_process';
5
+ import { requireAuthJWT } from '@/lib/rev4a-auth';
6
+
7
+ const SHARED_SKILLS_DIR = '/docker/shared-skills';
8
+
9
+ /**
10
+ * POST /api/skills/promote
11
+ *
12
+ * Copies a skill from an agent container's workspace to the shared skills
13
+ * directory, making it available to all agents via extraDirs.
14
+ *
15
+ * Body: {
16
+ * containerName: "openclaw-atlas",
17
+ * skillName: "my-custom-skill"
18
+ * }
19
+ */
20
+ export async function POST(request) {
21
+ const denied = await requireAuthJWT(request);
22
+ if (denied) return denied;
23
+
24
+ try {
25
+ const { containerName, skillName } = await request.json();
26
+
27
+ if (!containerName || !skillName) {
28
+ return NextResponse.json(
29
+ { error: 'containerName and skillName are required' },
30
+ { status: 400 }
31
+ );
32
+ }
33
+
34
+ // Sanitize: skill name must be a valid directory name
35
+ if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\/[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/.test(skillName)) {
36
+ return NextResponse.json(
37
+ { error: 'Invalid skill name format' },
38
+ { status: 400 }
39
+ );
40
+ }
41
+
42
+ // Verify the container exists and has an AGENT_ID label
43
+ let agentId;
44
+ try {
45
+ agentId = execFileSync('docker', [
46
+ 'inspect', containerName,
47
+ '--format', '{{index .Config.Labels "AGENT_ID"}}'
48
+ ], { encoding: 'utf8', timeout: 5000 }).trim();
49
+ if (!agentId) {
50
+ return NextResponse.json(
51
+ { error: `Container ${containerName} has no AGENT_ID label` },
52
+ { status: 400 }
53
+ );
54
+ }
55
+ } catch {
56
+ return NextResponse.json(
57
+ { error: `Container ${containerName} not found` },
58
+ { status: 404 }
59
+ );
60
+ }
61
+
62
+ // Read SKILL.md content from the container
63
+ const srcPath = `/root/.openclaw/workspace/skills/${skillName}/SKILL.md`;
64
+ let content;
65
+ try {
66
+ content = execFileSync('docker', ['exec', containerName, 'cat', srcPath], {
67
+ encoding: 'utf8', timeout: 5000
68
+ });
69
+ } catch {
70
+ return NextResponse.json(
71
+ { error: `Skill "${skillName}" not found in container ${containerName}` },
72
+ { status: 404 }
73
+ );
74
+ }
75
+
76
+ // Check for additional files in the skill directory (assets, scripts, etc.)
77
+ const extraFiles = [];
78
+ try {
79
+ const listRaw = execFileSync('docker', ['exec', containerName, 'ls', '-1p', `/root/.openclaw/workspace/skills/${skillName}/`], {
80
+ encoding: 'utf8', timeout: 5000
81
+ }).trim();
82
+ // Exclude SKILL.md and directories from the extras list
83
+ extraFiles.push(
84
+ ...listRaw.split('\n')
85
+ .map(f => f.trim())
86
+ .filter(f => f && !f.endsWith('/') && f !== 'SKILL.md')
87
+ );
88
+ } catch {
89
+ // No extra files — that's fine
90
+ }
91
+
92
+ // Create shared skills directory if it doesn't exist
93
+ const destDir = path.join(SHARED_SKILLS_DIR, skillName);
94
+ fs.mkdirSync(destDir, { recursive: true });
95
+
96
+ // Write SKILL.md
97
+ const destPath = path.join(destDir, 'SKILL.md');
98
+ const overwritten = fs.existsSync(destPath);
99
+ fs.writeFileSync(destPath, content, 'utf8');
100
+
101
+ // Copy extra files from container
102
+ let extraCopied = 0;
103
+ for (const file of extraFiles) {
104
+ try {
105
+ const fileContent = execFileSync('docker', ['exec', containerName, 'cat', `/root/.openclaw/workspace/skills/${skillName}/${file}`], {
106
+ encoding: 'utf8', timeout: 5000
107
+ });
108
+ fs.writeFileSync(path.join(destDir, file), fileContent, 'utf8');
109
+ extraCopied++;
110
+ } catch {
111
+ // Skip files that can't be read
112
+ }
113
+ }
114
+
115
+ return NextResponse.json({
116
+ ok: true,
117
+ skillName,
118
+ agentId,
119
+ containerName,
120
+ destPath,
121
+ overwritten,
122
+ extraCopied,
123
+ });
124
+ } catch (err) {
125
+ // Only return message for known Error objects, never raw unknown throws
126
+ const msg = err instanceof Error ? err.message : 'Internal error';
127
+ return NextResponse.json({ error: msg }, { status: 500 });
128
+ }
129
+ }