@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,110 @@
1
+ export interface CredentialField {
2
+ key: string;
3
+ label: string;
4
+ placeholder: string;
5
+ type: 'text' | 'password';
6
+ hint?: string;
7
+ }
8
+
9
+ export interface CredentialProviderDef {
10
+ id: string;
11
+ label: string;
12
+ category: 'vcs' | 'pm' | 'cloud' | 'db';
13
+ description: string;
14
+ authType: 'static-token' | 'api-key-pair';
15
+ fields: CredentialField[];
16
+ deliveryMethod: string;
17
+ docsUrl?: string;
18
+ }
19
+
20
+ export const CREDENTIAL_PROVIDERS: CredentialProviderDef[] = [
21
+ {
22
+ id: 'github-pat',
23
+ label: 'GitHub',
24
+ category: 'vcs',
25
+ description: 'Personal Access Token for GitHub API and git operations',
26
+ authType: 'static-token',
27
+ fields: [
28
+ {
29
+ key: 'token',
30
+ label: 'Personal Access Token',
31
+ placeholder: 'ghp_...',
32
+ type: 'password',
33
+ hint: 'Requires repo, read:org scopes at minimum',
34
+ },
35
+ ],
36
+ deliveryMethod: 'gh auth login --with-token',
37
+ docsUrl: 'https://github.com/settings/tokens',
38
+ },
39
+ {
40
+ id: 'trello',
41
+ label: 'Trello',
42
+ category: 'pm',
43
+ description: 'API Key and Token for Trello board access',
44
+ authType: 'api-key-pair',
45
+ fields: [
46
+ { key: 'apiKey', label: 'API Key', placeholder: 'd3c4a0fee...', type: 'password' },
47
+ { key: 'token', label: 'Token', placeholder: 'ATTA...', type: 'password', hint: 'Click "Generate Trello Token" below to authorize' },
48
+ ],
49
+ deliveryMethod: 'writes ~/.trello-cli/default/config.json',
50
+ docsUrl: 'https://trello.com/power-ups/admin',
51
+ },
52
+ {
53
+ id: 'vercel',
54
+ label: 'Vercel',
55
+ category: 'cloud',
56
+ description: 'Access Token for Vercel deployments and project management',
57
+ authType: 'static-token',
58
+ fields: [
59
+ {
60
+ key: 'token',
61
+ label: 'Access Token',
62
+ placeholder: 'vcp_...',
63
+ type: 'password',
64
+ hint: 'Create in Vercel Settings → Tokens',
65
+ },
66
+ ],
67
+ deliveryMethod: 'writes ~/.local/share/com.vercel.cli/auth.json',
68
+ docsUrl: 'https://vercel.com/account/tokens',
69
+ },
70
+ {
71
+ id: 'supabase',
72
+ label: 'Supabase',
73
+ category: 'db',
74
+ description: 'Access token for Supabase CLI — grants access to all projects on your account',
75
+ authType: 'static-token',
76
+ fields: [
77
+ {
78
+ key: 'accessToken',
79
+ label: 'Access Token',
80
+ placeholder: 'sbp_...',
81
+ type: 'password',
82
+ hint: 'Supabase CLI personal access token. Create at supabase.com/dashboard/account/tokens. Grants access to all your projects for migrations, edge functions, and database management.',
83
+ },
84
+ ],
85
+ deliveryMethod: 'supabase login --token *** --no-browser',
86
+ docsUrl: 'https://supabase.com/dashboard/account/tokens',
87
+ },
88
+ {
89
+ id: 'notion',
90
+ label: 'Notion',
91
+ category: 'pm',
92
+ description: 'API Token for Notion integration — enables the agent to read/write pages, databases, and blocks',
93
+ authType: 'static-token',
94
+ fields: [
95
+ {
96
+ key: 'token',
97
+ label: 'API Token (Internal Integration)',
98
+ placeholder: 'ntn_... or secret_...',
99
+ type: 'password',
100
+ hint: 'Create at notion.so/my-integrations. Grant Read + Insert + Update capabilities.',
101
+ },
102
+ ],
103
+ deliveryMethod: 'writes ~/.config/notion/config.json',
104
+ docsUrl: 'https://www.notion.so/my-integrations',
105
+ },
106
+ ];
107
+
108
+ export function getProvider(id: string): CredentialProviderDef | undefined {
109
+ return CREDENTIAL_PROVIDERS.find((p) => p.id === id);
110
+ }
@@ -0,0 +1,195 @@
1
+ import Database from 'better-sqlite3';
2
+ import { openCredentialsDb as openDb } from './db';
3
+ import { getProvider } from './providers';
4
+
5
+ export interface CredentialProfile {
6
+ id: string;
7
+ providerId: string;
8
+ label: string;
9
+ status: 'active' | 'error';
10
+ secretMasked: string | null;
11
+ createdAt: number;
12
+ updatedAt: number;
13
+ lastSyncedAt: number | null;
14
+ }
15
+
16
+ export type CredentialSecret = Record<string, string>;
17
+
18
+ function mask(value: string): string {
19
+ if (!value) return '••••••••';
20
+ if (value.length <= 8) return '•'.repeat(value.length);
21
+ return value.slice(0, 4) + '••••' + value.slice(-4);
22
+ }
23
+
24
+ /**
25
+ * Find the first password field in a provider to use for masked display.
26
+ * Falls back to the first field if no password field exists.
27
+ */
28
+ function firstPasswordField(provider: { fields: { key: string; type: string }[] }): string | undefined {
29
+ const pwField = provider.fields.find((f) => f.type === 'password');
30
+ return pwField?.key ?? provider.fields[0]?.key;
31
+ }
32
+
33
+ export function appendAudit(db: Database.Database, action: string, profileId: string, result: string): void {
34
+ try {
35
+ db.prepare(
36
+ 'INSERT INTO credential_audit_log (ts, actor, action, profile_id, result) VALUES (?, ?, ?, ?, ?)',
37
+ ).run(Date.now(), 'system', action, profileId, result);
38
+ } catch {
39
+ // Audit failures are non-fatal
40
+ }
41
+ }
42
+
43
+ export function listProfiles(): CredentialProfile[] {
44
+ const db = openDb(true);
45
+ try {
46
+ return db.prepare(
47
+ `SELECT id, provider_id AS providerId, label, status,
48
+ secret_masked AS secretMasked, created_at AS createdAt,
49
+ updated_at AS updatedAt, last_synced_at AS lastSyncedAt
50
+ FROM credential_profiles ORDER BY created_at DESC`,
51
+ ).all() as CredentialProfile[];
52
+ } finally {
53
+ db.close();
54
+ }
55
+ }
56
+
57
+ export function getProfile(id: string): CredentialProfile | null {
58
+ const db = openDb(true);
59
+ try {
60
+ return db.prepare(
61
+ `SELECT id, provider_id AS providerId, label, status,
62
+ secret_masked AS secretMasked, created_at AS createdAt,
63
+ updated_at AS updatedAt, last_synced_at AS lastSyncedAt
64
+ FROM credential_profiles WHERE id = ?`,
65
+ ).get(id) as CredentialProfile | null;
66
+ } finally {
67
+ db.close();
68
+ }
69
+ }
70
+
71
+ export function getSecret(profileId: string): CredentialSecret {
72
+ const db = openDb(true);
73
+ try {
74
+ const row = db.prepare(
75
+ 'SELECT payload FROM credential_secrets WHERE profile_id = ?',
76
+ ).get(profileId) as { payload: string } | null;
77
+ if (!row) return {};
78
+ return JSON.parse(row.payload) as CredentialSecret;
79
+ } finally {
80
+ db.close();
81
+ }
82
+ }
83
+
84
+ export function createProfile(
85
+ providerId: string,
86
+ label: string,
87
+ secret: CredentialSecret,
88
+ ): string {
89
+ const provider = getProvider(providerId);
90
+ if (!provider) throw new Error(`Unknown provider: ${providerId}`);
91
+
92
+ const id = `cred_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
93
+ const now = Date.now();
94
+ const maskKey = firstPasswordField(provider);
95
+ const secretMasked = mask(maskKey ? (secret[maskKey] ?? '') : '');
96
+ const payload = JSON.stringify(secret);
97
+
98
+ const db = openDb(false);
99
+ try {
100
+ db.prepare(
101
+ `INSERT INTO credential_profiles
102
+ (id, provider_id, label, status, secret_masked, created_at, updated_at, last_synced_at)
103
+ VALUES (?, ?, ?, 'active', ?, ?, ?, NULL)`,
104
+ ).run(id, providerId, label, secretMasked, now, now);
105
+
106
+ db.prepare(
107
+ 'INSERT INTO credential_secrets (profile_id, payload) VALUES (?, ?)',
108
+ ).run(id, payload);
109
+
110
+ appendAudit(db, 'create', id, 'ok');
111
+ return id;
112
+ } finally {
113
+ db.close();
114
+ }
115
+ }
116
+
117
+ export function updateProfile(
118
+ id: string,
119
+ label: string,
120
+ secret: CredentialSecret,
121
+ ): void {
122
+ const profile = getProfile(id);
123
+ if (!profile) throw new Error(`Profile not found: ${id}`);
124
+
125
+ const now = Date.now();
126
+ const db = openDb(false);
127
+ try {
128
+ // Only update secret fields if at least one field has a non-empty value
129
+ const hasValues = Object.values(secret).some((v) => v && v.trim().length > 0);
130
+
131
+ if (hasValues) {
132
+ const provider = getProvider(profile.providerId);
133
+ if (!provider) throw new Error(`Unknown provider: ${profile.providerId}`);
134
+
135
+ const maskKey = firstPasswordField(provider);
136
+ const secretMasked = mask(maskKey ? (secret[maskKey] ?? '') : '');
137
+ const payload = JSON.stringify(secret);
138
+
139
+ db.prepare(
140
+ 'UPDATE credential_profiles SET label = ?, secret_masked = ?, updated_at = ? WHERE id = ?',
141
+ ).run(label, secretMasked, now, id);
142
+
143
+ db.prepare(
144
+ 'UPDATE credential_secrets SET payload = ? WHERE profile_id = ?',
145
+ ).run(payload, id);
146
+ } else {
147
+ // Only update label, keep existing secret and secret_masked
148
+ db.prepare(
149
+ 'UPDATE credential_profiles SET label = ?, updated_at = ? WHERE id = ?',
150
+ ).run(label, now, id);
151
+ }
152
+
153
+ appendAudit(db, 'update', id, 'ok');
154
+ } finally {
155
+ db.close();
156
+ }
157
+ }
158
+
159
+ export function deleteProfile(id: string): void {
160
+ const db = openDb(false);
161
+ try {
162
+ db.prepare('DELETE FROM credential_secrets WHERE profile_id = ?').run(id);
163
+ db.prepare('DELETE FROM credential_profiles WHERE id = ?').run(id);
164
+ appendAudit(db, 'delete', id, 'ok');
165
+ } finally {
166
+ db.close();
167
+ }
168
+ }
169
+
170
+ export function markSynced(id: string, status: 'active' | 'error'): void {
171
+ const db = openDb(false);
172
+ try {
173
+ db.prepare(
174
+ 'UPDATE credential_profiles SET last_synced_at = ?, status = ?, updated_at = ? WHERE id = ?',
175
+ ).run(Date.now(), status, Date.now(), id);
176
+ appendAudit(db, 'sync', id, status);
177
+ } finally {
178
+ db.close();
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Reveal a secret payload and log the access in the audit trail.
184
+ * Use sparingly — every call is logged.
185
+ */
186
+ export function revealSecret(profileId: string): CredentialSecret {
187
+ const db = openDb(true);
188
+ try {
189
+ const secret = getSecret(profileId);
190
+ appendAudit(db, 'reveal', profileId, 'ok');
191
+ return secret;
192
+ } finally {
193
+ db.close();
194
+ }
195
+ }
@@ -0,0 +1,3 @@
1
+ declare module '*/db-bootstrap.mjs' {
2
+ export function initializeRev4aDb(dbPath: string): void;
3
+ }
@@ -0,0 +1,144 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import Database from 'better-sqlite3';
4
+
5
+ const SESSION_COLUMNS = [
6
+ ['parent_id', 'TEXT'],
7
+ ['label', 'TEXT'],
8
+ ['model', 'TEXT'],
9
+ ['tokens_in', 'INTEGER DEFAULT 0'],
10
+ ['tokens_out', 'INTEGER DEFAULT 0'],
11
+ ['cost_usd', 'REAL DEFAULT 0'],
12
+ ['status', "TEXT DEFAULT 'idle'"],
13
+ ['task_preview', 'TEXT'],
14
+ ['started_at', 'INTEGER'],
15
+ ['ended_at', 'INTEGER'],
16
+ ['updated_at', 'INTEGER'],
17
+ ['trello_card_url', 'TEXT'],
18
+ ];
19
+
20
+ const LINEAGE_COLUMNS = [
21
+ ['agent_name', 'TEXT'],
22
+ ['label', 'TEXT'],
23
+ ];
24
+
25
+ function ensureParentDir(dbPath) {
26
+ const dir = path.dirname(dbPath);
27
+ if (!dir || dir === '.') return;
28
+ fs.mkdirSync(dir, { recursive: true });
29
+ }
30
+
31
+ function addMissingColumns(db, tableName, columns) {
32
+ const existing = new Set(db.prepare(`PRAGMA table_info(${tableName})`).all().map((col) => col.name));
33
+ for (const [name, type] of columns) {
34
+ if (!existing.has(name)) {
35
+ db.exec(`ALTER TABLE ${tableName} ADD COLUMN ${name} ${type}`);
36
+ }
37
+ }
38
+ }
39
+
40
+ export function initializeRev4aDb(dbPath) {
41
+ ensureParentDir(dbPath);
42
+ const db = new Database(dbPath);
43
+ db.pragma('journal_mode = WAL');
44
+ db.exec(`
45
+ CREATE TABLE IF NOT EXISTS sessions (
46
+ session_id TEXT PRIMARY KEY,
47
+ parent_id TEXT,
48
+ label TEXT,
49
+ model TEXT,
50
+ tokens_in INTEGER DEFAULT 0,
51
+ tokens_out INTEGER DEFAULT 0,
52
+ cost_usd REAL DEFAULT 0,
53
+ status TEXT DEFAULT 'idle',
54
+ task_preview TEXT,
55
+ started_at INTEGER,
56
+ ended_at INTEGER,
57
+ updated_at INTEGER,
58
+ trello_card_url TEXT
59
+ );
60
+
61
+ CREATE TABLE IF NOT EXISTS events (
62
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
63
+ ts INTEGER NOT NULL,
64
+ session_id TEXT NOT NULL,
65
+ type TEXT NOT NULL,
66
+ data TEXT
67
+ );
68
+
69
+ CREATE TABLE IF NOT EXISTS tool_calls (
70
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
71
+ ts INTEGER NOT NULL,
72
+ session_id TEXT NOT NULL,
73
+ tool_name TEXT,
74
+ status TEXT,
75
+ duration_ms INTEGER,
76
+ data TEXT
77
+ );
78
+
79
+ CREATE TABLE IF NOT EXISTS lineage (
80
+ child_id TEXT PRIMARY KEY,
81
+ parent_id TEXT NOT NULL,
82
+ declared_at INTEGER NOT NULL,
83
+ agent_name TEXT,
84
+ label TEXT
85
+ );
86
+
87
+ CREATE TABLE IF NOT EXISTS cost_override (
88
+ month TEXT PRIMARY KEY,
89
+ amount REAL,
90
+ note TEXT,
91
+ updated_at INTEGER
92
+ );
93
+
94
+ CREATE TABLE IF NOT EXISTS system_metrics (
95
+ ts INTEGER PRIMARY KEY,
96
+ cpu_percent REAL,
97
+ ram_used_mb INTEGER,
98
+ ram_total_mb INTEGER,
99
+ disk_used_gb REAL,
100
+ disk_total_gb REAL,
101
+ load_avg_1m REAL
102
+ );
103
+
104
+ CREATE TABLE IF NOT EXISTS alert_state (
105
+ alert_key TEXT PRIMARY KEY,
106
+ kind TEXT NOT NULL,
107
+ state TEXT NOT NULL,
108
+ last_sent_at INTEGER,
109
+ last_resolved_at INTEGER,
110
+ updated_at INTEGER NOT NULL,
111
+ payload TEXT
112
+ );
113
+
114
+ CREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id, ts);
115
+ CREATE INDEX IF NOT EXISTS idx_sessions_updated ON sessions(updated_at);
116
+ CREATE INDEX IF NOT EXISTS idx_sessions_started ON sessions(started_at);
117
+ CREATE INDEX IF NOT EXISTS idx_metrics_ts ON system_metrics(ts);
118
+ CREATE INDEX IF NOT EXISTS idx_tool_calls_session ON tool_calls(session_id, ts);
119
+ CREATE INDEX IF NOT EXISTS idx_alert_state_updated ON alert_state(updated_at);
120
+ `);
121
+
122
+ addMissingColumns(db, 'sessions', SESSION_COLUMNS);
123
+ addMissingColumns(db, 'lineage', LINEAGE_COLUMNS);
124
+
125
+ // Chat messages table
126
+ db.exec(`
127
+ CREATE TABLE IF NOT EXISTS chat_messages (
128
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
129
+ ts INTEGER NOT NULL,
130
+ user_id TEXT NOT NULL,
131
+ role TEXT NOT NULL,
132
+ content TEXT NOT NULL,
133
+ openclaw_session_id TEXT
134
+ );
135
+ CREATE INDEX IF NOT EXISTS idx_chat_user ON chat_messages(user_id, ts);
136
+ `);
137
+
138
+ addMissingColumns(db, 'chat_messages', [
139
+ ['model', 'TEXT'],
140
+ ]);
141
+
142
+
143
+ db.close();
144
+ }
package/lib/db.ts ADDED
@@ -0,0 +1,25 @@
1
+ // Shared DB helpers for API routes
2
+ import Database from 'better-sqlite3';
3
+ import type { NextRequest } from 'next/server';
4
+ import { NextResponse } from 'next/server';
5
+ import { initializeRev4aDb } from './db-bootstrap.mjs';
6
+ import { DB_FILE } from './rev4a-paths';
7
+
8
+ export const DB_PATH = process.env.REV4A_DB || DB_FILE;
9
+ export const TOKEN = process.env.REV4A_TOKEN ?? '';
10
+
11
+ export function requireAuth(request: NextRequest): NextResponse | null {
12
+ const token = new URL(request.url).searchParams.get('token');
13
+ const auth = request.headers.get('authorization');
14
+ const ok = token === TOKEN || auth === `Bearer ${TOKEN}`;
15
+ return ok ? null : NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
16
+ }
17
+
18
+ export function ensureDbReady(): void {
19
+ initializeRev4aDb(DB_PATH);
20
+ }
21
+
22
+ export function openDb(readonly = true): Database.Database {
23
+ ensureDbReady();
24
+ return new Database(DB_PATH, { readonly });
25
+ }
@@ -0,0 +1,60 @@
1
+ import { networkInterfaces } from 'os';
2
+
3
+ /**
4
+ * Detect the host's best public-facing IPv4 address for agent URLs.
5
+ *
6
+ * Priority:
7
+ * 1. External IP detection via icanhazip.com (works from Docker containers)
8
+ * 2. Hostname resolution (filters out 172.x Docker internal IPs)
9
+ * 3. First non-internal IPv4 network interface
10
+ * 4. 'localhost' fallback
11
+ */
12
+ export function detectPublicHost(): string {
13
+ // 1. External detection (works from inside Docker containers)
14
+ try {
15
+ const { execSync } = require('child_process');
16
+ const ip = execSync(
17
+ 'curl -fs --max-time 3 icanhazip.com 2>/dev/null || curl -fs --max-time 3 ifconfig.me 2>/dev/null',
18
+ { encoding: 'utf-8', timeout: 5000 },
19
+ ).trim();
20
+ if (ip && /^\d+\.\d+\.\d+\.\d+$/.test(ip) && !ip.startsWith('172.')) {
21
+ return ip;
22
+ }
23
+ } catch { /* ignore */ }
24
+
25
+ // 2. Hostname (often gives public IP on VPS, but Docker internal 172.x is filtered)
26
+ try {
27
+ const { execSync } = require('child_process');
28
+ const hostname = execSync('hostname -I 2>/dev/null || hostname 2>/dev/null', {
29
+ encoding: 'utf-8', timeout: 2000,
30
+ }).trim().split(/\s+/)[0];
31
+ if (hostname && hostname !== '127.0.0.1' && hostname !== '::1' && !hostname.startsWith('172.')) {
32
+ return hostname;
33
+ }
34
+ } catch { /* ignore */ }
35
+
36
+ // 3. Network interfaces (bare metal)
37
+ try {
38
+ const ifaces = networkInterfaces();
39
+ for (const [, addrs] of Object.entries(ifaces)) {
40
+ if (!addrs) continue;
41
+ for (const addr of addrs) {
42
+ if (addr.family === 'IPv4' && !addr.internal) {
43
+ return addr.address;
44
+ }
45
+ }
46
+ }
47
+ } catch { /* fall through */ }
48
+
49
+ // 4. Fallback
50
+ return 'localhost';
51
+ }
52
+
53
+ /**
54
+ * Build the provider gateway base URL used to sync model config into agent
55
+ * containers. Agents reach Rev4a through the Docker host gateway
56
+ * (host.docker.internal), injected at container creation via --add-host.
57
+ */
58
+ export function detectProviderGatewayUrl(): string {
59
+ return 'http://host.docker.internal:3740/api/provider/v1';
60
+ }