@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.
- package/README.md +150 -0
- package/agent-templates/README.md +37 -0
- package/agent-templates/argus/AGENTS.md +52 -0
- package/agent-templates/argus/IDENTITY.md +7 -0
- package/agent-templates/argus/MEMORY.md +9 -0
- package/agent-templates/argus/SOUL.md +24 -0
- package/agent-templates/argus/TOOLS.md +8 -0
- package/agent-templates/atlas/AGENTS.md +47 -0
- package/agent-templates/atlas/HEARTBEAT.md +10 -0
- package/agent-templates/atlas/IDENTITY.md +6 -0
- package/agent-templates/atlas/MEMORY.md +9 -0
- package/agent-templates/atlas/SOUL.md +21 -0
- package/agent-templates/atlas/TOOLS.md +4 -0
- package/agent-templates/base-image/.trigger +0 -0
- package/agent-templates/base-image/Dockerfile +90 -0
- package/agent-templates/base-image/entrypoint.sh +37 -0
- package/agent-templates/custom/AGENTS.md +29 -0
- package/agent-templates/custom/BOOTSTRAP.md +56 -0
- package/agent-templates/custom/MEMORY.md +8 -0
- package/agent-templates/prometheus/AGENTS.md +37 -0
- package/agent-templates/prometheus/IDENTITY.md +6 -0
- package/agent-templates/prometheus/MEMORY.md +9 -0
- package/agent-templates/prometheus/SOUL.md +21 -0
- package/agent-templates/prometheus/TOOLS.md +4 -0
- package/app/agents/ChannelManager.tsx +465 -0
- package/app/agents/ImageDownloadBanner.tsx +159 -0
- package/app/agents/PageClient.tsx +1285 -0
- package/app/agents/create/PageClient.tsx +437 -0
- package/app/agents/create/page.tsx +126 -0
- package/app/agents/loading.tsx +14 -0
- package/app/agents/page.tsx +5 -0
- package/app/api/agents/[id]/backup/route.ts +169 -0
- package/app/api/agents/[id]/channels/pairing/route.ts +69 -0
- package/app/api/agents/[id]/channels/route.ts +14 -0
- package/app/api/agents/[id]/channels/telegram/route.ts +50 -0
- package/app/api/agents/[id]/lifecycle/route.ts +94 -0
- package/app/api/agents/[id]/recreate/route.ts +199 -0
- package/app/api/agents/[id]/restart/route.ts +33 -0
- package/app/api/agents/[id]/restore/route.ts +81 -0
- package/app/api/agents/[id]/route.ts +280 -0
- package/app/api/agents/channels-summary/route.ts +39 -0
- package/app/api/agents/config-json/route.ts +58 -0
- package/app/api/agents/create/route.ts +488 -0
- package/app/api/agents/download-image/route.ts +32 -0
- package/app/api/agents/image-status/route.ts +126 -0
- package/app/api/agents/route.ts +213 -0
- package/app/api/agents/token/route.ts +94 -0
- package/app/api/agents-active/route.ts +190 -0
- package/app/api/agents-config/route.ts +629 -0
- package/app/api/alerts/smoke/route.ts +26 -0
- package/app/api/aliases/add/route.ts +28 -0
- package/app/api/aliases/remove/route.ts +27 -0
- package/app/api/assistant/route.ts +204 -0
- package/app/api/auth/check/route.ts +38 -0
- package/app/api/auth/login/route.ts +85 -0
- package/app/api/auth/logout/route.ts +7 -0
- package/app/api/auth/status/route.ts +33 -0
- package/app/api/config/env/route.ts +132 -0
- package/app/api/config/restart/route.ts +26 -0
- package/app/api/containers/route.ts +75 -0
- package/app/api/cost-override/route.ts +46 -0
- package/app/api/costs/route.ts +84 -0
- package/app/api/credentials/[id]/reveal/route.ts +21 -0
- package/app/api/credentials/[id]/route.ts +52 -0
- package/app/api/credentials/[id]/sync/route.ts +58 -0
- package/app/api/credentials/detect/route.ts +246 -0
- package/app/api/credentials/route.ts +33 -0
- package/app/api/crons/[id]/route.ts +69 -0
- package/app/api/crons/route.ts +12 -0
- package/app/api/crons/runs/route.ts +31 -0
- package/app/api/debug/route.ts +11 -0
- package/app/api/envcheck/route.ts +18 -0
- package/app/api/events/route.ts +24 -0
- package/app/api/gateway/agent/route.ts +154 -0
- package/app/api/gateway/provider/balance/route.ts +38 -0
- package/app/api/gateway/provider/keys.ts +38 -0
- package/app/api/gateway/provider/oauth/route.ts +304 -0
- package/app/api/gateway/provider/route.ts +209 -0
- package/app/api/gateway/route.ts +223 -0
- package/app/api/gateway/sync.ts +189 -0
- package/app/api/lineage/route.ts +54 -0
- package/app/api/memory-context/route.ts +15 -0
- package/app/api/metrics/route.ts +35 -0
- package/app/api/models/route.ts +77 -0
- package/app/api/plugins/route.js +37 -0
- package/app/api/provider/upstream.ts +142 -0
- package/app/api/provider/v1/chat/completions/route.ts +146 -0
- package/app/api/provider/v1/models/route.ts +167 -0
- package/app/api/provider-usage/route.ts +629 -0
- package/app/api/session/route.ts +39 -0
- package/app/api/sessions/[id]/route.ts +38 -0
- package/app/api/sessions/route.ts +23 -0
- package/app/api/setup/agent-image/route.ts +102 -0
- package/app/api/setup/password/route.ts +111 -0
- package/app/api/setup/restart/route.ts +76 -0
- package/app/api/skills/delete/route.js +105 -0
- package/app/api/skills/promote/route.js +129 -0
- package/app/api/skills/route.js +203 -0
- package/app/api/skills/save/route.js +142 -0
- package/app/api/stats/route.ts +34 -0
- package/app/api/stats-since/route.ts +20 -0
- package/app/api/stream/route.ts +70 -0
- package/app/api/system-health/route.ts +233 -0
- package/app/api/tool-calls/route.ts +24 -0
- package/app/api/tools-config/route.ts +163 -0
- package/app/api/update-check/route.ts +127 -0
- package/app/api/version/route.ts +25 -0
- package/app/api/wizard/complete/route.ts +32 -0
- package/app/api/wizard/reset/route.ts +28 -0
- package/app/api/wizard/status/route.ts +28 -0
- package/app/api/workspace/route.ts +392 -0
- package/app/api/workspace/stream/route.ts +136 -0
- package/app/components/AppShell.tsx +33 -0
- package/app/components/AuthGuard.tsx +91 -0
- package/app/components/CostBreakdown.tsx +37 -0
- package/app/components/DashboardHeader.tsx +73 -0
- package/app/components/DashboardLayout.tsx +76 -0
- package/app/components/DashboardToolbar.tsx +61 -0
- package/app/components/FloatingActions.tsx +33 -0
- package/app/components/Icons.tsx +78 -0
- package/app/components/LineageGraphPage.tsx +142 -0
- package/app/components/LiveFeed.tsx +48 -0
- package/app/components/MobileBottomNav.tsx +84 -0
- package/app/components/ModelPickerModal.tsx +94 -0
- package/app/components/PasswordInput.tsx +86 -0
- package/app/components/PulseChat.tsx +362 -0
- package/app/components/Rev4aLoader.tsx +48 -0
- package/app/components/SessionDrawer.tsx +263 -0
- package/app/components/SessionTopology.tsx +289 -0
- package/app/components/Sidebar.tsx +294 -0
- package/app/components/Skeleton.tsx +133 -0
- package/app/components/SystemCockpit.tsx +169 -0
- package/app/components/VersionBanner.tsx +105 -0
- package/app/components/WizardButton.tsx +30 -0
- package/app/components/ui/Badge.tsx +30 -0
- package/app/components/ui/Button.tsx +59 -0
- package/app/components/ui/ConfirmModal.tsx +46 -0
- package/app/components/ui/FilterBar.tsx +33 -0
- package/app/components/ui/Input.tsx +25 -0
- package/app/components/ui/ItemList.tsx +83 -0
- package/app/components/ui/LoadingSpinner.tsx +30 -0
- package/app/components/ui/Metric.tsx +22 -0
- package/app/components/ui/Modal.tsx +42 -0
- package/app/components/ui/Page.tsx +18 -0
- package/app/components/ui/Pill.tsx +6 -0
- package/app/components/ui/PropertyList.tsx +24 -0
- package/app/components/ui/Select.tsx +26 -0
- package/app/components/ui/StatusCard.tsx +32 -0
- package/app/components/ui/Surface.tsx +14 -0
- package/app/components/ui/Tabs.tsx +55 -0
- package/app/components/ui/TemplateOption.tsx +59 -0
- package/app/components/ui/Toast.tsx +44 -0
- package/app/components/ui/index.ts +19 -0
- package/app/components/ui/tokens.ts +18 -0
- package/app/config/PageClient.tsx +293 -0
- package/app/config/loading.tsx +10 -0
- package/app/config/page.tsx +7 -0
- package/app/containers/ContainersClient.tsx +129 -0
- package/app/containers/loading.tsx +21 -0
- package/app/containers/page.tsx +5 -0
- package/app/containers/terminal/[id]/TerminalClient.tsx +313 -0
- package/app/containers/terminal/[id]/page.tsx +6 -0
- package/app/credentials/PageClient.tsx +598 -0
- package/app/credentials/loading.tsx +10 -0
- package/app/credentials/page.tsx +7 -0
- package/app/crons/PageClient.tsx +538 -0
- package/app/crons/loading.tsx +10 -0
- package/app/crons/page.tsx +5 -0
- package/app/design-system.ts +57 -0
- package/app/gateway/PageClient.tsx +1048 -0
- package/app/gateway/loading.tsx +24 -0
- package/app/gateway/page.tsx +5 -0
- package/app/globals.css +1474 -0
- package/app/layout.tsx +45 -0
- package/app/lib/models-context.tsx +41 -0
- package/app/lineage/loading.tsx +10 -0
- package/app/lineage/page.tsx +13 -0
- package/app/loading.tsx +16 -0
- package/app/login/page.tsx +160 -0
- package/app/memory/MemoryContextPageClient.tsx +196 -0
- package/app/memory/loading.tsx +10 -0
- package/app/memory/page.tsx +8 -0
- package/app/page.tsx +31 -0
- package/app/plugins/PageClient.tsx +296 -0
- package/app/plugins/loading.tsx +10 -0
- package/app/plugins/page.tsx +5 -0
- package/app/setup/PageClient.tsx +227 -0
- package/app/setup/loading.tsx +21 -0
- package/app/setup/page.tsx +7 -0
- package/app/skills/PageClient.tsx +499 -0
- package/app/skills/loading.tsx +10 -0
- package/app/skills/page.tsx +5 -0
- package/app/tools/ToolsPageClient.tsx +308 -0
- package/app/tools/loading.tsx +10 -0
- package/app/tools/page.tsx +62 -0
- package/app/tools/tools-catalog.ts +141 -0
- package/app/wizard/PageClient.tsx +485 -0
- package/app/wizard/icons.tsx +89 -0
- package/app/wizard/loading.tsx +24 -0
- package/app/wizard/page.tsx +7 -0
- package/app/wizard/storage.ts +33 -0
- package/app/wizard/useWizard.ts +101 -0
- package/app/workspace/WorkspaceClient.tsx +633 -0
- package/app/workspace/loading.tsx +36 -0
- package/app/workspace/page.tsx +8 -0
- package/bin/docker-entrypoint.sh +21 -0
- package/bin/postinstall.js +20 -0
- package/bin/rev4a.js +696 -0
- package/daemon.js +783 -0
- package/docs/ARCHITECTURE.md +557 -0
- package/docs/CONTAINER-TERMINAL.md +280 -0
- package/docs/DESIGN-SYSTEM.md +99 -0
- package/docs/FRONTEND-ARCHITECTURE.md +119 -0
- package/docs/REV4A.md +504 -0
- package/docs/dev/API-REFERENCE.md +1835 -0
- package/docs/dev/DATABASE.md +224 -0
- package/docs/dev/GATEWAY.md +300 -0
- package/docs/dev/PROVIDERS.md +229 -0
- package/docs/dev/SESSION-MAINTENANCE-PLAN.md +607 -0
- package/docs/dev/WORKSPACE.md +199 -0
- package/docs/rag/DATA-FRESHNESS.md +109 -0
- package/docs/rag/GLOSSARY.md +125 -0
- package/docs/rag/REV4A-OVERVIEW.md +135 -0
- package/docs/rag/WHAT-I-CAN-ANSWER.md +124 -0
- package/lib/agent-setup.ts +130 -0
- package/lib/alerts.ts +115 -0
- package/lib/apiFetch.ts +7 -0
- package/lib/auth.ts +28 -0
- package/lib/billing.ts +100 -0
- package/lib/buildAgentImage.ts +346 -0
- package/lib/channelManager.ts +386 -0
- package/lib/container.ts +60 -0
- package/lib/credentials/db.ts +86 -0
- package/lib/credentials/delivery.ts +448 -0
- package/lib/credentials/detect.ts +149 -0
- package/lib/credentials/hash.ts +9 -0
- package/lib/credentials/providers.ts +110 -0
- package/lib/credentials/vault.ts +195 -0
- package/lib/db-bootstrap.d.ts +3 -0
- package/lib/db-bootstrap.mjs +144 -0
- package/lib/db.ts +25 -0
- package/lib/docker-utils.ts +60 -0
- package/lib/hooks/useDashboard.ts +264 -0
- package/lib/hooks/useRev4aTimezone.ts +28 -0
- package/lib/hooks/useScreenshot.ts +30 -0
- package/lib/memory-context.ts +385 -0
- package/lib/model-catalogue.ts +131 -0
- package/lib/model-pricing.ts +63 -0
- package/lib/openclaw-cron.ts +350 -0
- package/lib/patterns/ApiAdapter.ts +73 -0
- package/lib/patterns/ApiClient.ts +65 -0
- package/lib/patterns/EventBus.ts +113 -0
- package/lib/patterns/FilterStrategy.ts +79 -0
- package/lib/patterns/SessionFactory.ts +117 -0
- package/lib/patterns/sessionPresentation.ts +117 -0
- package/lib/provider-balance.ts +163 -0
- package/lib/requireAuth.tsx +38 -0
- package/lib/rev4a-auth.d.ts +3 -0
- package/lib/rev4a-auth.js +36 -0
- package/lib/rev4a-paths.ts +145 -0
- package/lib/timezone.ts +46 -0
- package/lib/types/index.ts +96 -0
- package/lib/utils/format.ts +82 -0
- package/lineage.js +56 -0
- package/model-pricing.json +482 -0
- package/models.config.json +844 -0
- package/next-env.d.ts +6 -0
- package/next.config.mjs +7 -0
- package/package.json +82 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/favicon-64.png +0 -0
- package/public/favicon.svg +58 -0
- package/public/icon-192-maskable.png +0 -0
- package/public/icon-192.png +0 -0
- package/public/icon-512-maskable.png +0 -0
- package/public/icon-512.png +0 -0
- package/public/manifest.json +38 -0
- package/public/rev4a-logo.png +0 -0
- package/public/rev4a-logo.svg +58 -0
- package/rev4a-rules/AGENTS.md +37 -0
- package/scripts/add-auth.cjs +185 -0
- package/scripts/backup.sh +66 -0
- package/scripts/check-providers.mjs +143 -0
- package/scripts/docker/traefik/docker-compose.yml +32 -0
- package/scripts/docker/traefik/dynamic/.gitkeep +18 -0
- package/scripts/docker/traefik/traefik.yml +37 -0
- package/scripts/remove-auth-profile.js +56 -0
- package/scripts/restore.sh +82 -0
- package/terminal-ws-server.js +139 -0
- package/tsconfig.json +42 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useMemo, useState, type CSSProperties, type ReactNode } from 'react';
|
|
4
|
+
import { TOOL_CATALOG, TOTAL_TOOL_COUNT, type ToolStatus } from './tools-catalog';
|
|
5
|
+
import { Button, Input, Pill, LoadingSpinner } from '../components/ui';
|
|
6
|
+
import type { Tone } from '../components/ui';
|
|
7
|
+
|
|
8
|
+
const API_FETCH_OPTIONS: RequestInit = {
|
|
9
|
+
cache: 'no-store',
|
|
10
|
+
credentials: 'same-origin',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
interface AudioModel {
|
|
14
|
+
provider: string;
|
|
15
|
+
model: string;
|
|
16
|
+
baseUrl?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface AudioConfig {
|
|
20
|
+
enabled?: boolean;
|
|
21
|
+
timeoutSeconds?: number;
|
|
22
|
+
maxBytes?: number;
|
|
23
|
+
models?: AudioModel[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const CONFIGURABLE_TOOL_COUNT = 1;
|
|
27
|
+
|
|
28
|
+
const statusTone: Record<ToolStatus, Tone> = {
|
|
29
|
+
Configured: 'success',
|
|
30
|
+
Available: 'info',
|
|
31
|
+
'Coming soon': 'neutral',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function sanitizeAudioConfig(value: unknown): AudioConfig {
|
|
35
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
|
|
36
|
+
const raw = value as Record<string, unknown>;
|
|
37
|
+
const audio: AudioConfig = {};
|
|
38
|
+
if (typeof raw.enabled === 'boolean') audio.enabled = raw.enabled;
|
|
39
|
+
if (typeof raw.timeoutSeconds === 'number' && Number.isFinite(raw.timeoutSeconds)) audio.timeoutSeconds = raw.timeoutSeconds;
|
|
40
|
+
if (typeof raw.maxBytes === 'number' && Number.isFinite(raw.maxBytes)) audio.maxBytes = raw.maxBytes;
|
|
41
|
+
if (Array.isArray(raw.models)) {
|
|
42
|
+
audio.models = raw.models
|
|
43
|
+
.filter((model): model is Record<string, unknown> => Boolean(model) && typeof model === 'object' && !Array.isArray(model))
|
|
44
|
+
.map((model) => ({
|
|
45
|
+
provider: typeof model.provider === 'string' ? model.provider : '',
|
|
46
|
+
model: typeof model.model === 'string' ? model.model : '',
|
|
47
|
+
baseUrl: typeof model.baseUrl === 'string' ? model.baseUrl : '',
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
return audio;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function hasAudioConfig(audio: AudioConfig | null): boolean {
|
|
54
|
+
if (!audio) return false;
|
|
55
|
+
if (audio.enabled !== undefined) return true;
|
|
56
|
+
if (audio.timeoutSeconds !== undefined) return true;
|
|
57
|
+
if (audio.maxBytes !== undefined) return true;
|
|
58
|
+
return Boolean(audio.models?.some((model) => model.provider || model.model || model.baseUrl));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function StatusBadge({ status }: { status: ToolStatus }) {
|
|
62
|
+
return <Pill tone={statusTone[status]}>{status}</Pill>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const TIMEZONE_OPTIONS = [
|
|
66
|
+
'Europe/Rome', 'UTC', 'Europe/London', 'Europe/Berlin', 'Europe/Paris',
|
|
67
|
+
'America/New_York', 'America/Chicago', 'America/Denver', 'America/Los_Angeles',
|
|
68
|
+
'Asia/Tokyo', 'Asia/Shanghai', 'Asia/Dubai', 'Asia/Kolkata',
|
|
69
|
+
'Australia/Sydney', 'Pacific/Auckland',
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
export default function ToolsPageClient({ initialAudio = {}, initialTimezone = 'Europe/Rome', initialError = null }: { initialAudio?: AudioConfig | null; initialTimezone?: string; initialError?: string | null }) {
|
|
73
|
+
const [audio, setAudio] = useState<AudioConfig | null>(sanitizeAudioConfig(initialAudio));
|
|
74
|
+
const [timezone, setTimezone] = useState(initialTimezone);
|
|
75
|
+
const [loading, setLoading] = useState(false);
|
|
76
|
+
const [saving, setSaving] = useState(false);
|
|
77
|
+
const [error, setError] = useState<string | null>(initialError);
|
|
78
|
+
const [success, setSuccess] = useState(false);
|
|
79
|
+
const [clock, setClock] = useState('');
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
const tick = () => setClock(new Date().toLocaleString('it-IT', { timeZone: timezone, hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }));
|
|
83
|
+
tick();
|
|
84
|
+
const id = setInterval(tick, 1000);
|
|
85
|
+
return () => clearInterval(id);
|
|
86
|
+
}, [timezone]);
|
|
87
|
+
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
const controller = new AbortController();
|
|
90
|
+
fetch('/api/tools-config', { ...API_FETCH_OPTIONS, signal: controller.signal })
|
|
91
|
+
.then((r) => r.json())
|
|
92
|
+
.then((data) => {
|
|
93
|
+
if (controller.signal.aborted) return;
|
|
94
|
+
setAudio(sanitizeAudioConfig(data.audio));
|
|
95
|
+
if (data.timezone) setTimezone(data.timezone);
|
|
96
|
+
if (data.error) setError(String(data.error));
|
|
97
|
+
})
|
|
98
|
+
.catch((e: unknown) => {
|
|
99
|
+
if (controller.signal.aborted) return;
|
|
100
|
+
setError((e as Error).message);
|
|
101
|
+
});
|
|
102
|
+
return () => controller.abort();
|
|
103
|
+
}, []);
|
|
104
|
+
|
|
105
|
+
async function handleSave() {
|
|
106
|
+
setSaving(true);
|
|
107
|
+
setError(null);
|
|
108
|
+
setSuccess(false);
|
|
109
|
+
try {
|
|
110
|
+
const res = await fetch('/api/tools-config', {
|
|
111
|
+
method: 'PUT',
|
|
112
|
+
headers: { 'Content-Type': 'application/json' },
|
|
113
|
+
credentials: 'same-origin',
|
|
114
|
+
body: JSON.stringify({ audio }),
|
|
115
|
+
});
|
|
116
|
+
const data = (await res.json()) as { ok?: boolean; error?: string; audio?: AudioConfig };
|
|
117
|
+
if (!res.ok) throw new Error(data.error ?? 'Save failed');
|
|
118
|
+
if (data.audio) setAudio(data.audio);
|
|
119
|
+
setSuccess(true);
|
|
120
|
+
setTimeout(() => setSuccess(false), 3000);
|
|
121
|
+
} catch (e: unknown) {
|
|
122
|
+
setError((e as Error).message);
|
|
123
|
+
} finally {
|
|
124
|
+
setSaving(false);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function updateModel(field: keyof AudioModel, value: string) {
|
|
129
|
+
setAudio((prev) => {
|
|
130
|
+
if (!prev) return prev;
|
|
131
|
+
const models = prev.models ? [...prev.models] : [{ provider: '', model: '' }];
|
|
132
|
+
models[0] = { ...models[0], [field]: value };
|
|
133
|
+
return { ...prev, models };
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const audioStatus: ToolStatus = hasAudioConfig(audio) ? 'Configured' : 'Available';
|
|
138
|
+
const model = audio?.models?.[0] ?? { provider: '', model: '', baseUrl: '' };
|
|
139
|
+
const configuredCount = audioStatus === 'Configured' ? 1 : 0;
|
|
140
|
+
const catalog = useMemo(() => TOOL_CATALOG.map((category) => ({ ...category, tools: category.tools.map((tool) => ({ ...tool, status: tool.name === 'tts' ? audioStatus : tool.status ?? 'Available' })) })), [audioStatus]);
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<div style={{ flex: 1, background: 'var(--bg)', color: 'var(--text)', fontFamily: 'var(--font-mono-stack)', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
|
|
144
|
+
<header style={{ height: '48px', padding: '0 14px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexShrink: 0, boxSizing: 'border-box' }}>
|
|
145
|
+
<span style={{ fontFamily: 'var(--font-serif-stack)', fontSize: '20px', letterSpacing: '4px', color: 'var(--violet)' }}>TOOLS</span>
|
|
146
|
+
<div style={heroPillStyle}>{configuredCount}/{CONFIGURABLE_TOOL_COUNT} configurable ready</div>
|
|
147
|
+
</header>
|
|
148
|
+
|
|
149
|
+
<div style={pageScrollStyle}>
|
|
150
|
+
<div style={pageInnerStyle}>
|
|
151
|
+
<header style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: '18px', marginBottom: '18px', paddingBottom: '18px', borderBottom: '1px solid var(--border)' }}>
|
|
152
|
+
<div style={{ minWidth: 0 }}>
|
|
153
|
+
<p style={eyebrowStyle}>OpenClaw inventory</p>
|
|
154
|
+
<h1 style={titleStyle}>Tools</h1>
|
|
155
|
+
<p style={ledeStyle}>A compact inventory of OpenClaw tools. Configure the exposed base tools here, then use the catalog as a quick reference for available capabilities.</p>
|
|
156
|
+
</div>
|
|
157
|
+
</header>
|
|
158
|
+
|
|
159
|
+
<section aria-label="Tools overview" style={overviewStyle}>
|
|
160
|
+
{[[ 'Total tools', TOTAL_TOOL_COUNT ], [ 'Configurable', CONFIGURABLE_TOOL_COUNT ], [ 'Configured', configuredCount ], [ 'Categories', TOOL_CATALOG.length ]].map(([label, value]) => (
|
|
161
|
+
<div key={label} style={metricStyle}><span style={metricLabelStyle}>{label}</span><strong style={metricValueStyle}>{value}</strong></div>
|
|
162
|
+
))}
|
|
163
|
+
</section>
|
|
164
|
+
|
|
165
|
+
{loading && <LoadingSpinner compact />}
|
|
166
|
+
|
|
167
|
+
{!loading && audio && (
|
|
168
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '28px' }}>
|
|
169
|
+
<section>
|
|
170
|
+
<SectionHeading title="Base tools" description="Editable tool configuration currently exposed in Rev4a. Credentials remain hidden." action={<StatusBadge status={audioStatus} />} />
|
|
171
|
+
<div style={configCardStyle}>
|
|
172
|
+
<div style={configHeaderStyle}>
|
|
173
|
+
<div>
|
|
174
|
+
<h3 style={{ fontSize: '16px', fontWeight: 700, margin: 0, color: 'var(--foreground)' }}>Audio Transcription</h3>
|
|
175
|
+
<p style={{ margin: '5px 0 0', color: 'var(--text-dim)', fontSize: '13px', lineHeight: 1.5 }}>Primary speech-to-text provider settings used by the audio tool.</p>
|
|
176
|
+
</div>
|
|
177
|
+
<label style={switchRowStyle}>
|
|
178
|
+
<input type="checkbox" checked={audio.enabled ?? false} onChange={(e) => setAudio((p) => (p ? { ...p, enabled: e.target.checked } : p))} style={{ width: '16px', height: '16px', cursor: 'pointer' }} />
|
|
179
|
+
<span>Enabled</span>
|
|
180
|
+
</label>
|
|
181
|
+
</div>
|
|
182
|
+
<div style={formGridStyle}>
|
|
183
|
+
<Field label="Timeout (seconds)"><Input type="number" value={audio.timeoutSeconds ?? ''} onChange={(e) => setAudio((p) => (p ? { ...p, timeoutSeconds: Number(e.target.value) } : p))} /></Field>
|
|
184
|
+
<Field label="Max bytes"><Input type="number" value={audio.maxBytes ?? ''} onChange={(e) => setAudio((p) => (p ? { ...p, maxBytes: Number(e.target.value) } : p))} /></Field>
|
|
185
|
+
</div>
|
|
186
|
+
<div style={subsectionStyle}>
|
|
187
|
+
<p style={subsectionTitleStyle}>Model (primary)</p>
|
|
188
|
+
<div style={formGridStyle}>
|
|
189
|
+
<Field label="Provider"><Input type="text" value={model.provider} onChange={(e) => updateModel('provider', e.target.value)} /></Field>
|
|
190
|
+
<Field label="Model"><Input type="text" value={model.model} onChange={(e) => updateModel('model', e.target.value)} /></Field>
|
|
191
|
+
<Field label="Base URL"><Input type="text" value={model.baseUrl ?? ''} onChange={(e) => updateModel('baseUrl', e.target.value)} /></Field>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
{error && <p style={{ color: 'var(--destructive, #ef4444)', fontSize: '13px', margin: '16px 0 0' }}>{error}</p>}
|
|
195
|
+
{success && <p style={{ color: 'var(--success, #22c55e)', fontSize: '13px', margin: '16px 0 0' }}>Saved successfully.</p>}
|
|
196
|
+
<div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: '18px' }}>
|
|
197
|
+
<Button variant="primary" size="sm" loading={saving} onClick={() => void handleSave()}>
|
|
198
|
+
{saving ? 'Saving…' : 'Save changes'}
|
|
199
|
+
</Button>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
</section>
|
|
203
|
+
|
|
204
|
+
<section>
|
|
205
|
+
<SectionHeading title="System Settings" description="Global runtime preferences for the Rev4a dashboard." />
|
|
206
|
+
<div style={configCardStyle}>
|
|
207
|
+
<div style={configHeaderStyle}>
|
|
208
|
+
<div>
|
|
209
|
+
<h3 style={{ fontSize: '16px', fontWeight: 700, margin: 0, color: 'var(--foreground)' }}>Time Zone</h3>
|
|
210
|
+
<p style={{ margin: '5px 0 0', color: 'var(--text-dim)', fontSize: '13px', lineHeight: 1.5 }}>Used by all dashboards to display session timestamps. Current time: <strong style={{ color: 'var(--foreground)' }}>{clock}</strong> in {timezone}.</p>
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
<div style={formGridStyle}>
|
|
214
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
|
|
215
|
+
<label style={fieldLabelStyle}>Time zone</label>
|
|
216
|
+
<select value={timezone} onChange={(e) => setTimezone(e.target.value)} style={{ ...inputStyle, cursor: 'pointer' }} className="ui-select">
|
|
217
|
+
{TIMEZONE_OPTIONS.map((tz) => <option key={tz} value={tz}>{tz}</option>)}
|
|
218
|
+
</select>
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
<div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: '18px' }}>
|
|
222
|
+
<Button variant="primary" size="sm" loading={saving} onClick={async () => {
|
|
223
|
+
setSaving(true); setError(null); setSuccess(false);
|
|
224
|
+
try {
|
|
225
|
+
const res = await fetch('/api/tools-config', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, credentials: 'same-origin', body: JSON.stringify({ timezone }) });
|
|
226
|
+
const data = (await res.json()) as { ok?: boolean; error?: string };
|
|
227
|
+
if (!res.ok) throw new Error(data.error ?? 'Save failed');
|
|
228
|
+
setSuccess(true);
|
|
229
|
+
setTimeout(() => setSuccess(false), 3000);
|
|
230
|
+
} catch (e: unknown) { setError((e as Error).message); }
|
|
231
|
+
finally { setSaving(false); }
|
|
232
|
+
}}>{saving ? 'Saving…' : 'Save timezone'}</Button>
|
|
233
|
+
</div>
|
|
234
|
+
</div>
|
|
235
|
+
</section>
|
|
236
|
+
|
|
237
|
+
<section>
|
|
238
|
+
<SectionHeading title="Tool catalog" description="Full inventory grouped by capability. Rows are lighter here because most tools do not need page-level configuration yet." />
|
|
239
|
+
<div style={catalogGridStyle}>
|
|
240
|
+
{catalog.map((category) => (
|
|
241
|
+
<div key={category.name} style={categoryCardStyle}>
|
|
242
|
+
<div style={categoryHeaderStyle}>
|
|
243
|
+
<svg viewBox={category.viewBox || '0 0 24 24'} style={{ width: 22, height: 22, flex: '0 0 auto' }} fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
|
|
244
|
+
<path d={category.icon} />
|
|
245
|
+
</svg>
|
|
246
|
+
<div style={{ minWidth: 0 }}>
|
|
247
|
+
<h3 style={{ fontSize: '15px', fontWeight: 700, margin: 0, color: 'var(--foreground)' }}>{category.name}</h3>
|
|
248
|
+
<p style={{ margin: '5px 0 0', color: 'var(--text-dim)', fontSize: '12px', lineHeight: 1.45 }}>{category.description}</p>
|
|
249
|
+
</div>
|
|
250
|
+
<span style={countPillStyle}>{category.tools.length}</span>
|
|
251
|
+
</div>
|
|
252
|
+
<div style={toolListStyle}>
|
|
253
|
+
{category.tools.map((tool) => (
|
|
254
|
+
<div key={tool.name} style={toolRowStyle}>
|
|
255
|
+
<svg viewBox="0 0 24 24" style={{ width: 16, height: 16, flex: '0 0 auto', marginTop: 2, stroke: 'var(--text-dim)' }} fill="none" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
|
|
256
|
+
<path d={tool.icon} />
|
|
257
|
+
</svg>
|
|
258
|
+
<div style={{ minWidth: 0 }}><p style={{ margin: '0 0 4px', color: 'var(--foreground)', fontSize: '13px', fontWeight: 650 }}>{tool.name}</p><p style={{ margin: 0, color: 'var(--text-dim)', fontSize: '12px', lineHeight: 1.45 }}>{tool.description}</p></div>
|
|
259
|
+
<StatusBadge status={tool.status} />
|
|
260
|
+
</div>
|
|
261
|
+
))}
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
))}
|
|
265
|
+
</div>
|
|
266
|
+
</section>
|
|
267
|
+
</div>
|
|
268
|
+
)}
|
|
269
|
+
</div>
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function SectionHeading({ title, description, action }: { title: string; description: string; action?: ReactNode }) {
|
|
276
|
+
return <div style={sectionHeadingStyle}><div><h2 style={{ fontSize: '17px', fontWeight: 700, margin: 0, color: 'var(--foreground)' }}>{title}</h2><p style={{ margin: '5px 0 0', color: 'var(--text-dim)', fontSize: '13px', lineHeight: 1.5 }}>{description}</p></div>{action}</div>;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function Field({ label, children }: { label: string; children: ReactNode }) {
|
|
280
|
+
return <div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}><label style={fieldLabelStyle}>{label}</label>{children}</div>;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const pageScrollStyle: CSSProperties = { height: '100%', minHeight: 0, overflowY: 'auto', overflowX: 'hidden' };
|
|
284
|
+
const pageInnerStyle: CSSProperties = { width: '100%', maxWidth: '1180px', padding: '24px 24px 40px' };
|
|
285
|
+
const heroStyle: CSSProperties = { display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: '18px', marginBottom: '18px', paddingBottom: '18px', borderBottom: '1px solid var(--border)' };
|
|
286
|
+
const eyebrowStyle: CSSProperties = { margin: 0, color: 'var(--text-dim)', fontSize: '12px', fontWeight: 700, letterSpacing: '0.08em', textTransform: 'uppercase' };
|
|
287
|
+
const titleStyle: CSSProperties = { fontSize: '26px', fontWeight: 750, margin: '6px 0 0', color: 'var(--foreground)' };
|
|
288
|
+
const ledeStyle: CSSProperties = { maxWidth: '760px', margin: '8px 0 0', color: 'var(--text-dim)', fontSize: '14px', lineHeight: 1.6 };
|
|
289
|
+
const heroPillStyle: CSSProperties = { flex: '0 0 auto', border: '1px solid rgba(99, 102, 241, 0.26)', borderRadius: '999px', padding: '7px 11px', background: 'rgba(99, 102, 241, 0.1)', color: '#a5b4fc', fontSize: '12px', fontWeight: 700, whiteSpace: 'nowrap' };
|
|
290
|
+
const overviewStyle: CSSProperties = { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', gap: '1px', overflow: 'hidden', border: '1px solid var(--border)', borderRadius: 0, background: 'var(--border)', marginBottom: '26px' };
|
|
291
|
+
const metricStyle: CSSProperties = { padding: '14px 16px', background: 'rgba(15, 23, 42, 0.42)' };
|
|
292
|
+
const metricLabelStyle: CSSProperties = { display: 'block', color: 'var(--text-dim)', fontSize: '11px', fontWeight: 700, letterSpacing: '0.07em', textTransform: 'uppercase' };
|
|
293
|
+
const metricValueStyle: CSSProperties = { display: 'block', marginTop: '7px', color: 'var(--foreground)', fontSize: '24px', lineHeight: 1 };
|
|
294
|
+
const sectionHeadingStyle: CSSProperties = { display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: '12px', marginBottom: '12px' };
|
|
295
|
+
const configCardStyle: CSSProperties = { border: '1px solid var(--border)', borderRadius: '14px', padding: '18px', background: 'linear-gradient(180deg, rgba(255,255,255,0.035), rgba(255,255,255,0.015))' };
|
|
296
|
+
const configHeaderStyle: CSSProperties = { display: 'flex', justifyContent: 'space-between', gap: '16px', alignItems: 'flex-start', marginBottom: '18px' };
|
|
297
|
+
const switchRowStyle: CSSProperties = { display: 'inline-flex', alignItems: 'center', gap: '9px', cursor: 'pointer', border: '1px solid var(--border)', borderRadius: '999px', padding: '7px 10px', color: 'var(--foreground)', fontSize: '13px', fontWeight: 650, whiteSpace: 'nowrap' };
|
|
298
|
+
const formGridStyle: CSSProperties = { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: '14px' };
|
|
299
|
+
const subsectionStyle: CSSProperties = { borderTop: '1px solid var(--border)', marginTop: '18px', paddingTop: '18px' };
|
|
300
|
+
const subsectionTitleStyle: CSSProperties = { fontSize: '12px', color: 'var(--text-dim)', textTransform: 'uppercase', letterSpacing: '0.07em', fontWeight: 700, margin: '0 0 12px' };
|
|
301
|
+
const fieldLabelStyle: CSSProperties = { fontSize: '12px', color: 'var(--text-dim)', fontWeight: 650 };
|
|
302
|
+
const catalogGridStyle: CSSProperties = { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))', gap: '14px' };
|
|
303
|
+
const categoryCardStyle: CSSProperties = { border: '1px solid var(--border)', borderRadius: '14px', background: 'rgba(255,255,255,0.018)', overflow: 'hidden' };
|
|
304
|
+
const categoryHeaderStyle: CSSProperties = { display: 'flex', justifyContent: 'space-between', gap: '12px', alignItems: 'flex-start', padding: '15px 16px', borderBottom: '1px solid var(--border)', background: 'rgba(255,255,255,0.025)' };
|
|
305
|
+
const countPillStyle: CSSProperties = { border: '1px solid var(--border)', borderRadius: '999px', padding: '3px 8px', color: 'var(--text-dim)', fontSize: '11px', fontWeight: 700 };
|
|
306
|
+
const toolListStyle: CSSProperties = { display: 'flex', flexDirection: 'column' };
|
|
307
|
+
const toolRowStyle: CSSProperties = { display: 'flex', justifyContent: 'space-between', gap: '10px', alignItems: 'flex-start', padding: '12px 16px', borderTop: '1px solid rgba(148, 163, 184, 0.12)' };
|
|
308
|
+
const inputStyle: CSSProperties = { padding: '7px 10px', borderRadius: '6px', border: '1px solid var(--border)', background: 'var(--input)', color: 'var(--foreground)', fontSize: '14px', width: '100%' };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Skeleton, CardRowSkeleton } from '../components/Skeleton';
|
|
2
|
+
|
|
3
|
+
const PageSkeleton = () => (
|
|
4
|
+
<div style={{ padding: '24px 20px', maxWidth: 1080, margin: '0 auto' }}>
|
|
5
|
+
<Skeleton width={120} height={20} accent style={{ marginBottom: 20 }} />
|
|
6
|
+
<CardRowSkeleton count={5} height={48} />
|
|
7
|
+
</div>
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
export default PageSkeleton;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import ToolsPageClient, { type AudioConfig } from './ToolsPageClient';
|
|
3
|
+
|
|
4
|
+
export const dynamic = 'force-dynamic';
|
|
5
|
+
|
|
6
|
+
const CONFIG_PATH = '/data/.openclaw/openclaw.json';
|
|
7
|
+
const REV4A_SETTINGS_PATH = '/data/rev4a/settings.json';
|
|
8
|
+
|
|
9
|
+
type JsonObject = Record<string, unknown>;
|
|
10
|
+
|
|
11
|
+
function asJsonObject(value: unknown): JsonObject {
|
|
12
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? (value as JsonObject) : {};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function sanitizeAudioConfig(value: unknown): AudioConfig {
|
|
16
|
+
const raw = asJsonObject(value);
|
|
17
|
+
const audio: AudioConfig = {};
|
|
18
|
+
if (typeof raw.enabled === 'boolean') audio.enabled = raw.enabled;
|
|
19
|
+
if (typeof raw.timeoutSeconds === 'number' && Number.isFinite(raw.timeoutSeconds)) audio.timeoutSeconds = raw.timeoutSeconds;
|
|
20
|
+
if (typeof raw.maxBytes === 'number' && Number.isFinite(raw.maxBytes)) audio.maxBytes = raw.maxBytes;
|
|
21
|
+
if (Array.isArray(raw.models)) {
|
|
22
|
+
audio.models = raw.models
|
|
23
|
+
.filter((model): model is JsonObject => Boolean(model) && typeof model === 'object' && !Array.isArray(model))
|
|
24
|
+
.map((model) => ({
|
|
25
|
+
provider: typeof model.provider === 'string' ? model.provider : '',
|
|
26
|
+
model: typeof model.model === 'string' ? model.model : '',
|
|
27
|
+
baseUrl: typeof model.baseUrl === 'string' ? model.baseUrl : '',
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
return audio;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function readInitialTimezone(): string {
|
|
34
|
+
try {
|
|
35
|
+
const settings = JSON.parse(fs.readFileSync(REV4A_SETTINGS_PATH, 'utf8')) as JsonObject;
|
|
36
|
+
return typeof settings.timezone === 'string' && settings.timezone ? settings.timezone : 'Europe/Rome';
|
|
37
|
+
} catch {
|
|
38
|
+
return 'Europe/Rome';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getInitialData(): { audio: AudioConfig; timezone: string; error: string | null } {
|
|
43
|
+
try {
|
|
44
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')) as JsonObject;
|
|
45
|
+
const tools = asJsonObject(config.tools);
|
|
46
|
+
const media = asJsonObject(tools.media);
|
|
47
|
+
const timezone = readInitialTimezone();
|
|
48
|
+
return { audio: sanitizeAudioConfig(media.audio), timezone, error: null };
|
|
49
|
+
} catch (error: unknown) {
|
|
50
|
+
return {
|
|
51
|
+
audio: {},
|
|
52
|
+
timezone: 'Europe/Rome',
|
|
53
|
+
error: `Could not read initial config: ${(error as Error).message}`,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default function ToolsPage() {
|
|
59
|
+
|
|
60
|
+
const { audio, timezone, error } = getInitialData();
|
|
61
|
+
return <ToolsPageClient initialAudio={audio} initialTimezone={timezone} initialError={error} />;
|
|
62
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
export type ToolStatus = 'Configured' | 'Available' | 'Coming soon';
|
|
2
|
+
|
|
3
|
+
export interface ToolCatalogItem {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
icon: string;
|
|
7
|
+
status?: ToolStatus;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ToolCatalogCategory {
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
icon: string; // SVG path data (d attribute)
|
|
14
|
+
viewBox?: string;
|
|
15
|
+
tools: ToolCatalogItem[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const TOOL_CATALOG: ToolCatalogCategory[] = [
|
|
19
|
+
{
|
|
20
|
+
name: 'File & Code',
|
|
21
|
+
description: 'Read, write, and patch files in the active workspace.',
|
|
22
|
+
icon: 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8zM6 2v4h8V2M6 6l12 0',
|
|
23
|
+
viewBox: '0 0 24 24',
|
|
24
|
+
tools: [
|
|
25
|
+
{ name: 'read', icon: 'M4 6h16M4 12h16M4 18h12', description: 'Read text files and supported images.' },
|
|
26
|
+
{ name: 'write', icon: 'M12 3v14m-7-7l7 7 7-7', description: 'Create or overwrite workspace files.' },
|
|
27
|
+
{ name: 'edit', icon: 'M15.232 5.232l3.536 3.536M9 11l-3 3V8l3-3a2 2 0 0 1 2.828 0L16.5 8.5a2 2 0 0 1 0 2.828l-5.5 5.5', description: 'Apply precise text replacements.' },
|
|
28
|
+
{ name: 'apply_patch', icon: 'M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2m-6 9l2 2 4-4', description: 'Apply structured multi-file patches.' },
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'Shell & Processes',
|
|
33
|
+
description: 'Run commands and manage long-running sessions.',
|
|
34
|
+
icon: 'M4 17l6-6L4 5m8 14h8',
|
|
35
|
+
viewBox: '0 0 24 24',
|
|
36
|
+
tools: [
|
|
37
|
+
{ name: 'exec', icon: 'M4 17l6-6L4 5', description: 'Execute shell commands with optional PTY/background mode.' },
|
|
38
|
+
{ name: 'process', icon: 'M12 8v4l3 3M12 2a10 10 0 1 0 10 10', description: 'Inspect, poll, and control running command sessions.' },
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'Web',
|
|
43
|
+
description: 'Search, fetch, and automate browser workflows.',
|
|
44
|
+
icon: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z',
|
|
45
|
+
viewBox: '0 0 24 24',
|
|
46
|
+
tools: [
|
|
47
|
+
{ name: 'web_search', icon: 'M21 21l-6-6m2-5a7 7 0 1 1-14 0 7 7 0 0 1 14 0z', description: 'Search the web for current information.' },
|
|
48
|
+
{ name: 'web_fetch', icon: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z', description: 'Fetch readable markdown or text from URLs.' },
|
|
49
|
+
{ name: 'browser', icon: 'M21 12a9 9 0 0 1-9 9m9-9a9 9 0 0 0-9-9m9 9H3m9 9a9 9 0 0 1-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 0 1 9-9', description: 'Control browser tabs, pages, snapshots, and actions.' },
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'Devices & Nodes',
|
|
54
|
+
description: 'Interact with paired phones, desktops, and node capabilities.',
|
|
55
|
+
icon: 'M17 1H7a2 2 0 0 0-2 2v18a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2zm0 17H7V6h10v12z',
|
|
56
|
+
viewBox: '0 0 24 24',
|
|
57
|
+
tools: [{ name: 'nodes', icon: 'M4 7a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V7zm4 10V7m8 10V7', description: 'List nodes, notify devices, capture screens, photos, and status.' }],
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'Automation',
|
|
61
|
+
description: 'Scheduled and durable operational automation.',
|
|
62
|
+
icon: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67V7z',
|
|
63
|
+
viewBox: '0 0 24 24',
|
|
64
|
+
tools: [{ name: 'cron', icon: 'M12 8v4l3 3M12 2a10 10 0 1 0 10 10', description: 'Schedule recurring jobs and maintenance tasks.', status: 'Coming soon' }],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'Messaging',
|
|
68
|
+
description: 'Send and manage messages across connected channels.',
|
|
69
|
+
icon: 'M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H5.17L4 17.17V4h16v12z',
|
|
70
|
+
viewBox: '0 0 24 24',
|
|
71
|
+
tools: [{ name: 'message', icon: 'M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v10z', description: 'Send messages, attachments, reactions, polls, and channel actions.' }],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'Gateway & Config',
|
|
75
|
+
description: 'Gateway lifecycle and configuration operations.',
|
|
76
|
+
icon: 'M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.49.49 0 0 0 .12-.61l-1.92-3.32a.488.488 0 0 0-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54a.484.484 0 0 0-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58a.49.49 0 0 0-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6A3.6 3.6 0 1 1 12 8.4a3.6 3.6 0 0 1 0 7.2z',
|
|
77
|
+
viewBox: '0 0 24 24',
|
|
78
|
+
tools: [{ name: 'gateway', icon: 'M19.14 12.94a3.6 3.6 0 1 0-7.14-1.88M12 8.4V12l1.5 1.5', description: 'Inspect and update OpenClaw gateway configuration.', status: 'Coming soon' }],
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'Sessions & Agents',
|
|
82
|
+
description: 'Inspect sessions and coordinate agent work.',
|
|
83
|
+
icon: 'M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5s-3 1.34-3 3 1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z',
|
|
84
|
+
viewBox: '0 0 24 24',
|
|
85
|
+
tools: [
|
|
86
|
+
{ name: 'agents_list', icon: 'M16 7a4 4 0 1 1-8 0 4 4 0 0 1 8 0zM4 21v-2a4 4 0 0 1 4-4h8a4 4 0 0 1 4 4v2', description: 'List available agents and their status.' },
|
|
87
|
+
{ name: 'sessions_list', icon: 'M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2M9 12h6m-6 4h6', description: 'List active and recent sessions.' },
|
|
88
|
+
{ name: 'sessions_history', icon: 'M12 8v4l3 3M12 2a10 10 0 1 0 10 10', description: 'Read session transcript history.' },
|
|
89
|
+
{ name: 'sessions_send', icon: 'M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z', description: 'Send messages into existing sessions.' },
|
|
90
|
+
{ name: 'sessions_spawn', icon: 'M17 5l4 4-4 4m-6 0H3m14 6l4-4-4-4', description: 'Create delegated agent sessions.' },
|
|
91
|
+
{ name: 'sessions_yield', icon: 'M17 5l-4 4 4 4m6-4H3', description: 'Yield while waiting for spawned work to complete.' },
|
|
92
|
+
{ name: 'subagents', icon: 'M12 4.354a4 4 0 1 1 0 5.292M9 15H3a1 1 0 0 0-1 1v4m18-5h-6m-3-5a4 4 0 1 1-8 0 4 4 0 0 1 8 0z', description: 'Coordinate subagent execution and results.' },
|
|
93
|
+
{ name: 'session_status', icon: 'M9 12l2 2 4-4m6 2a9 9 0 1 1-18 0 9 9 0 0 1 18 0z', description: 'Inspect current runtime, model, date, and capability status.' },
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: 'Media & AI',
|
|
98
|
+
description: 'Analyze or generate media with configured AI providers.',
|
|
99
|
+
icon: 'M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z',
|
|
100
|
+
viewBox: '0 0 24 24',
|
|
101
|
+
tools: [
|
|
102
|
+
{ name: 'image', icon: 'M15 8h.01M21 19V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2zM3 16l5-5 4 4 5-5 4 4', description: 'Analyze images with a vision model.' },
|
|
103
|
+
{ name: 'image_generate', icon: 'M4 16l4-4 5 5 3-3 4 4m-5-9a2 2 0 1 1 4 0 2 2 0 0 1-4 0zm2-6a9 9 0 1 0 0 18 9 9 0 0 0 0-18z', description: 'Generate or edit images.' },
|
|
104
|
+
{ name: 'music_generate', icon: 'M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2z', description: 'Create music, songs, loops, or instrumentals.' },
|
|
105
|
+
{ name: 'video_generate', icon: 'M15 10l4.553-2.276A1 1 0 0 1 21 8.618v6.764a1 1 0 0 1-1.447.894L15 14M5 18h8a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2z', description: 'Generate videos from text and media references.' },
|
|
106
|
+
{ name: 'tts', icon: 'M19 11a7 7 0 0 1-7 7m0 0a7 7 0 0 1-7-7m7 7v4m-4 0h8M5 8h1m7 0h1m-4 3h2', description: 'Convert explicit speech requests into audio.' },
|
|
107
|
+
{ name: 'pdf', icon: 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8zM6 2v4h8V2m-2 6v6m-3-3l3 3 3-3', description: 'Analyze PDF documents.' },
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: 'File Transfer',
|
|
112
|
+
description: 'Move files and directory listings between paired nodes and the gateway.',
|
|
113
|
+
icon: 'M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z',
|
|
114
|
+
viewBox: '0 0 24 24',
|
|
115
|
+
tools: [
|
|
116
|
+
{ name: 'dir_fetch', icon: 'M19 9l-7 7-7-7', description: 'Fetch directory trees from paired nodes.' },
|
|
117
|
+
{ name: 'dir_list', icon: 'M9 5H5a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2zm10 0h-4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2zM9 15H5a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2zm10 0h-4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2z', description: 'List node directory metadata.' },
|
|
118
|
+
{ name: 'file_fetch', icon: 'M4 16v1a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3v-1m-4-8l-4-4m0 0L8 8m4-4v12', description: 'Retrieve files from paired nodes.' },
|
|
119
|
+
{ name: 'file_write', icon: 'M4 16v1a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3v-1m-4 0l-4 4m0 0l-4-4m4 4V4', description: 'Write files to paired nodes.' },
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'Memory',
|
|
124
|
+
description: 'Search and read persisted workspace memory.',
|
|
125
|
+
icon: 'M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z',
|
|
126
|
+
viewBox: '0 0 24 24',
|
|
127
|
+
tools: [
|
|
128
|
+
{ name: 'memory_get', icon: 'M4 7v10c0 2 1.5 3 4 3s4-1 4-3V7c0-2-1.5-3-4-3S4 5 4 7zm12 0v10c0 2 1.5 3 4 3s4-1 4-3V7c0-2-1.5-3-4-3s-4 1-4 3z', description: 'Read bounded excerpts from memory files.' },
|
|
129
|
+
{ name: 'memory_search', icon: 'M21 21l-6-6m2-5a7 7 0 1 1-14 0 7 7 0 0 1 14 0z', description: 'Semantic search over memory and session records.' },
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: 'Planning',
|
|
134
|
+
description: 'Keep multi-step work visible and current.',
|
|
135
|
+
icon: 'M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14zM17.99 9l-1.41-1.42-6.59 6.59-2.58-2.57-1.42 1.41 4 3.99z',
|
|
136
|
+
viewBox: '0 0 24 24',
|
|
137
|
+
tools: [{ name: 'update_plan', icon: 'M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2m-6 9l2 2 4-4', description: 'Create and update the current run plan.' }],
|
|
138
|
+
},
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
export const TOTAL_TOOL_COUNT = TOOL_CATALOG.reduce((total, category) => total + category.tools.length, 0);
|