@treenity/mods 3.0.2 → 3.0.4
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/agent/client.ts +2 -0
- package/agent/guardian.ts +492 -0
- package/agent/seed.ts +74 -0
- package/agent/server.ts +4 -0
- package/agent/service.ts +644 -0
- package/agent/types.ts +184 -0
- package/agent/view.tsx +431 -0
- package/dist/agent/client.d.ts +3 -0
- package/dist/agent/client.d.ts.map +1 -0
- package/dist/agent/client.js +3 -0
- package/dist/agent/client.js.map +1 -0
- package/dist/agent/guardian.d.ts +47 -0
- package/dist/agent/guardian.d.ts.map +1 -0
- package/dist/agent/guardian.js +452 -0
- package/dist/agent/guardian.js.map +1 -0
- package/dist/agent/seed.d.ts +2 -0
- package/dist/agent/seed.d.ts.map +1 -0
- package/dist/agent/seed.js +68 -0
- package/dist/agent/seed.js.map +1 -0
- package/dist/agent/server.d.ts +5 -0
- package/dist/agent/server.d.ts.map +1 -0
- package/dist/agent/server.js +5 -0
- package/dist/agent/server.js.map +1 -0
- package/dist/agent/service.d.ts +2 -0
- package/dist/agent/service.d.ts.map +1 -0
- package/dist/agent/service.js +556 -0
- package/dist/agent/service.js.map +1 -0
- package/dist/agent/types.d.ts +115 -0
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/agent/types.js +168 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/agent/view.d.ts +2 -0
- package/dist/agent/view.d.ts.map +1 -0
- package/dist/agent/view.js +137 -0
- package/dist/agent/view.js.map +1 -0
- package/dist/mcp/mcp-server.d.ts +16 -0
- package/dist/mcp/mcp-server.d.ts.map +1 -0
- package/dist/mcp/mcp-server.js +344 -0
- package/dist/mcp/mcp-server.js.map +1 -0
- package/dist/mcp/server.d.ts +3 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +3 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/service.d.ts +2 -0
- package/dist/mcp/service.d.ts.map +1 -0
- package/dist/mcp/service.js +16 -0
- package/dist/mcp/service.js.map +1 -0
- package/dist/mcp/types.d.ts +4 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +6 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/metatron/claude.d.ts +30 -0
- package/dist/metatron/claude.d.ts.map +1 -0
- package/dist/metatron/claude.js +201 -0
- package/dist/metatron/claude.js.map +1 -0
- package/dist/metatron/client.d.ts +3 -0
- package/dist/metatron/client.d.ts.map +1 -0
- package/dist/metatron/client.js +3 -0
- package/dist/metatron/client.js.map +1 -0
- package/dist/metatron/mentions.d.ts +9 -0
- package/dist/metatron/mentions.d.ts.map +1 -0
- package/dist/metatron/mentions.js +21 -0
- package/dist/metatron/mentions.js.map +1 -0
- package/dist/metatron/permissions.d.ts +16 -0
- package/dist/metatron/permissions.d.ts.map +1 -0
- package/dist/metatron/permissions.js +52 -0
- package/dist/metatron/permissions.js.map +1 -0
- package/dist/metatron/seed.d.ts +2 -0
- package/dist/metatron/seed.d.ts.map +1 -0
- package/dist/metatron/seed.js +41 -0
- package/dist/metatron/seed.js.map +1 -0
- package/dist/metatron/server.d.ts +4 -0
- package/dist/metatron/server.d.ts.map +1 -0
- package/dist/metatron/server.js +4 -0
- package/dist/metatron/server.js.map +1 -0
- package/dist/metatron/service.d.ts +2 -0
- package/dist/metatron/service.d.ts.map +1 -0
- package/dist/metatron/service.js +361 -0
- package/dist/metatron/service.js.map +1 -0
- package/dist/metatron/types.d.ts +76 -0
- package/dist/metatron/types.d.ts.map +1 -0
- package/dist/metatron/types.js +112 -0
- package/dist/metatron/types.js.map +1 -0
- package/dist/metatron/view.d.ts +4 -0
- package/dist/metatron/view.d.ts.map +1 -0
- package/dist/metatron/view.js +5 -0
- package/dist/metatron/view.js.map +1 -0
- package/dist/metatron/views/config.d.ts +2 -0
- package/dist/metatron/views/config.d.ts.map +1 -0
- package/dist/metatron/views/config.js +116 -0
- package/dist/metatron/views/config.js.map +1 -0
- package/dist/metatron/views/log.d.ts +18 -0
- package/dist/metatron/views/log.d.ts.map +1 -0
- package/dist/metatron/views/log.js +224 -0
- package/dist/metatron/views/log.js.map +1 -0
- package/dist/metatron/views/shared.d.ts +13 -0
- package/dist/metatron/views/shared.d.ts.map +1 -0
- package/dist/metatron/views/shared.js +33 -0
- package/dist/metatron/views/shared.js.map +1 -0
- package/dist/metatron/views/task.d.ts +4 -0
- package/dist/metatron/views/task.d.ts.map +1 -0
- package/dist/metatron/views/task.js +106 -0
- package/dist/metatron/views/task.js.map +1 -0
- package/dist/metatron/views/workspace.d.ts +2 -0
- package/dist/metatron/views/workspace.d.ts.map +1 -0
- package/dist/metatron/views/workspace.js +138 -0
- package/dist/metatron/views/workspace.js.map +1 -0
- package/mcp/mcp-server.ts +393 -0
- package/mcp/server.ts +2 -0
- package/mcp/service.ts +18 -0
- package/mcp/types.ts +6 -0
- package/metatron/CLAUDE.md +22 -0
- package/metatron/claude.ts +258 -0
- package/metatron/client.ts +2 -0
- package/metatron/mentions.ts +31 -0
- package/metatron/permissions.ts +76 -0
- package/metatron/seed.ts +50 -0
- package/metatron/server.ts +3 -0
- package/metatron/service.ts +406 -0
- package/metatron/types.ts +120 -0
- package/metatron/view.tsx +4 -0
- package/metatron/views/config.tsx +408 -0
- package/metatron/views/log.tsx +412 -0
- package/metatron/views/shared.tsx +40 -0
- package/metatron/views/task.tsx +255 -0
- package/metatron/views/workspace.tsx +418 -0
- package/package.json +6 -2
- package/dist/mindmap/radial-tree.d.ts +0 -14
- package/dist/mindmap/radial-tree.d.ts.map +0 -1
- package/dist/mindmap/radial-tree.js +0 -184
- package/dist/mindmap/radial-tree.js.map +0 -1
- package/dist/mindmap/use-tree-data.d.ts +0 -14
- package/dist/mindmap/use-tree-data.d.ts.map +0 -1
- package/dist/mindmap/use-tree-data.js +0 -95
- package/dist/mindmap/use-tree-data.js.map +0 -1
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import { register } from '@treenity/core';
|
|
2
|
+
import { type View } from '@treenity/react/context';
|
|
3
|
+
import { execute, set, useChildren, useNavigate } from '@treenity/react/hooks';
|
|
4
|
+
import { cn } from '@treenity/react/lib/utils';
|
|
5
|
+
import { useCallback, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import type { MetatronConfig } from '../types';
|
|
8
|
+
import { LogRenderer } from './log';
|
|
9
|
+
import { formatTime, StatusDot } from './shared';
|
|
10
|
+
|
|
11
|
+
// ── Permission Manager ──
|
|
12
|
+
|
|
13
|
+
function PermissionManager({ configPath }: { configPath: string }) {
|
|
14
|
+
const permissions = useChildren(`${configPath}/permissions`, { watch: true, watchNew: true });
|
|
15
|
+
const [adding, setAdding] = useState(false);
|
|
16
|
+
const [tool, setTool] = useState('');
|
|
17
|
+
const [policy, setPolicy] = useState<'allow' | 'deny'>('deny');
|
|
18
|
+
|
|
19
|
+
const rules = (permissions ?? []).filter(n => n.$type === 'metatron.permission');
|
|
20
|
+
|
|
21
|
+
const handleAdd = useCallback(async () => {
|
|
22
|
+
if (!tool.trim()) return;
|
|
23
|
+
const trpc = (await import('@treenity/react/trpc')).trpc;
|
|
24
|
+
const { createNode } = await import('@treenity/core');
|
|
25
|
+
const id = `rule-${Date.now()}`;
|
|
26
|
+
const path = `${configPath}/permissions/${id}`;
|
|
27
|
+
await trpc.set.mutate({ node: createNode(path, 'metatron.permission', {
|
|
28
|
+
tool: tool.trim(),
|
|
29
|
+
policy,
|
|
30
|
+
createdAt: Date.now(),
|
|
31
|
+
}) as Record<string, unknown> });
|
|
32
|
+
setTool('');
|
|
33
|
+
setAdding(false);
|
|
34
|
+
}, [tool, policy, configPath]);
|
|
35
|
+
|
|
36
|
+
const handleRemove = useCallback(async (path: string) => {
|
|
37
|
+
const trpc = (await import('@treenity/react/trpc')).trpc;
|
|
38
|
+
await trpc.remove.mutate({ path });
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div className="flex flex-col gap-2">
|
|
43
|
+
<div className="flex items-center gap-2">
|
|
44
|
+
<span className="text-[10px] text-zinc-600 uppercase tracking-wider font-medium">Permissions</span>
|
|
45
|
+
<button
|
|
46
|
+
onClick={() => setAdding(!adding)}
|
|
47
|
+
className="ml-auto text-[11px] text-zinc-600 hover:text-violet-400 transition-colors duration-200"
|
|
48
|
+
>
|
|
49
|
+
{adding ? 'cancel' : '+ rule'}
|
|
50
|
+
</button>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
{adding && (
|
|
54
|
+
<div className="flex gap-2 items-end">
|
|
55
|
+
<div className="flex-1">
|
|
56
|
+
<input
|
|
57
|
+
value={tool}
|
|
58
|
+
onChange={e => setTool(e.target.value)}
|
|
59
|
+
onKeyDown={e => { if (e.key === 'Enter') handleAdd(); }}
|
|
60
|
+
placeholder="mcp__treenity__remove_node"
|
|
61
|
+
className="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-3 py-1.5 text-xs text-zinc-200 focus:outline-none focus:border-violet-600/50 placeholder:text-zinc-700"
|
|
62
|
+
/>
|
|
63
|
+
</div>
|
|
64
|
+
<select
|
|
65
|
+
value={policy}
|
|
66
|
+
onChange={e => setPolicy(e.target.value as 'allow' | 'deny')}
|
|
67
|
+
className="bg-zinc-950 border border-zinc-800 rounded-lg px-2 py-1.5 text-xs text-zinc-300"
|
|
68
|
+
>
|
|
69
|
+
<option value="allow">allow</option>
|
|
70
|
+
<option value="deny">deny</option>
|
|
71
|
+
</select>
|
|
72
|
+
<button
|
|
73
|
+
onClick={handleAdd}
|
|
74
|
+
disabled={!tool.trim()}
|
|
75
|
+
className="px-3 py-1.5 rounded-lg text-[11px] font-medium bg-violet-600 hover:bg-violet-500 text-white disabled:bg-zinc-800 disabled:text-zinc-600 transition-all shrink-0"
|
|
76
|
+
>
|
|
77
|
+
Add
|
|
78
|
+
</button>
|
|
79
|
+
</div>
|
|
80
|
+
)}
|
|
81
|
+
|
|
82
|
+
{rules.length > 0 ? (
|
|
83
|
+
<div className="flex flex-col gap-1">
|
|
84
|
+
{rules.map(r => (
|
|
85
|
+
<div key={r.$path} className="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-zinc-950/60 border border-zinc-800/60">
|
|
86
|
+
<span className={cn(
|
|
87
|
+
'text-[10px] font-semibold uppercase px-1.5 py-0.5 rounded',
|
|
88
|
+
r.policy === 'deny' ? 'bg-red-500/15 text-red-400' : 'bg-emerald-500/15 text-emerald-400'
|
|
89
|
+
)}>
|
|
90
|
+
{String(r.policy)}
|
|
91
|
+
</span>
|
|
92
|
+
<span className="text-xs text-zinc-300 font-mono truncate flex-1">{String(r.tool)}</span>
|
|
93
|
+
{r.pathPattern ? (
|
|
94
|
+
<span className="text-[10px] text-zinc-600 font-mono truncate">{String(r.pathPattern)}</span>
|
|
95
|
+
) : null}
|
|
96
|
+
<button
|
|
97
|
+
onClick={() => handleRemove(r.$path)}
|
|
98
|
+
className="text-zinc-700 hover:text-red-400 transition-colors p-0.5 shrink-0"
|
|
99
|
+
title="Remove rule"
|
|
100
|
+
>
|
|
101
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M18 6L6 18M6 6l12 12"/></svg>
|
|
102
|
+
</button>
|
|
103
|
+
</div>
|
|
104
|
+
))}
|
|
105
|
+
</div>
|
|
106
|
+
) : (
|
|
107
|
+
<p className="text-[11px] text-zinc-700 italic">No rules — all MCP tools allowed</p>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ── Done section ──
|
|
114
|
+
|
|
115
|
+
function DoneSection({ tasks }: { tasks: Array<Record<string, unknown>> }) {
|
|
116
|
+
const [open, setOpen] = useState(false);
|
|
117
|
+
return (
|
|
118
|
+
<div className="flex flex-col gap-2">
|
|
119
|
+
<button
|
|
120
|
+
onClick={() => setOpen(!open)}
|
|
121
|
+
className="flex items-center gap-1.5 text-[10px] text-zinc-600 uppercase tracking-wider font-medium hover:text-zinc-400 transition-colors text-left"
|
|
122
|
+
>
|
|
123
|
+
<svg
|
|
124
|
+
width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"
|
|
125
|
+
className={cn('transition-transform duration-200', open && 'rotate-90')}
|
|
126
|
+
>
|
|
127
|
+
<path d="M9 18l6-6-6-6" />
|
|
128
|
+
</svg>
|
|
129
|
+
Completed ({tasks.length})
|
|
130
|
+
</button>
|
|
131
|
+
{open && (
|
|
132
|
+
<div className="flex flex-col gap-1.5">
|
|
133
|
+
{tasks.map(task => <TaskRowInline key={String(task.$path)} task={task} />)}
|
|
134
|
+
</div>
|
|
135
|
+
)}
|
|
136
|
+
</div>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Inline simplified TaskRow for DoneSection (avoids circular import)
|
|
141
|
+
function TaskRowInline({ task }: { task: Record<string, unknown> }) {
|
|
142
|
+
const status = (task.status as string) || 'pending';
|
|
143
|
+
const prompt = (task.prompt as string) || '';
|
|
144
|
+
const result = (task.result as string) || '';
|
|
145
|
+
const taskLog = (task.log as string) || '';
|
|
146
|
+
const hasLog = !!taskLog;
|
|
147
|
+
const [expanded, setExpanded] = useState(false);
|
|
148
|
+
const [tab, setTab] = useState<'result' | 'log'>(hasLog ? 'log' : 'result');
|
|
149
|
+
const time = task.createdAt ? formatTime(task.createdAt as number) : '';
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<div className={cn(
|
|
153
|
+
'rounded-xl border transition-all duration-200',
|
|
154
|
+
expanded ? 'bg-zinc-900/80 border-zinc-800' : 'bg-zinc-900/40 border-zinc-800/60 hover:border-zinc-700'
|
|
155
|
+
)}>
|
|
156
|
+
<div
|
|
157
|
+
className="flex items-center gap-2.5 px-3.5 py-2.5 cursor-pointer select-none"
|
|
158
|
+
onClick={() => setExpanded(!expanded)}
|
|
159
|
+
>
|
|
160
|
+
<StatusDot status={status} />
|
|
161
|
+
<span className="text-sm text-zinc-300 truncate flex-1">{prompt}</span>
|
|
162
|
+
<span className="text-[10px] text-zinc-600 shrink-0 font-mono">{time}</span>
|
|
163
|
+
<svg
|
|
164
|
+
width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"
|
|
165
|
+
className={cn('text-zinc-600 transition-transform duration-200 shrink-0', expanded && 'rotate-90')}
|
|
166
|
+
>
|
|
167
|
+
<path d="M9 18l6-6-6-6" />
|
|
168
|
+
</svg>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
{expanded && (
|
|
172
|
+
<div className="border-t border-zinc-800/60">
|
|
173
|
+
<div className="flex gap-1 px-2 py-1">
|
|
174
|
+
{(['log', 'result'] as const).map(t => (
|
|
175
|
+
<button
|
|
176
|
+
key={t}
|
|
177
|
+
onClick={() => setTab(t)}
|
|
178
|
+
className={cn(
|
|
179
|
+
'px-2 py-0.5 rounded-md text-[11px] font-medium transition-all duration-150',
|
|
180
|
+
tab === t ? 'bg-zinc-800 text-zinc-200' : 'text-zinc-600 hover:text-zinc-400'
|
|
181
|
+
)}
|
|
182
|
+
>
|
|
183
|
+
{t}
|
|
184
|
+
</button>
|
|
185
|
+
))}
|
|
186
|
+
</div>
|
|
187
|
+
<div className="max-h-[60vh] overflow-y-auto">
|
|
188
|
+
<LogRenderer text={tab === 'result' ? (result || 'No result yet') : (taskLog || result || 'No log yet')} />
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
)}
|
|
192
|
+
</div>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ── Config View ──
|
|
197
|
+
|
|
198
|
+
const ConfigView: View<MetatronConfig> = ({ value, ctx }) => {
|
|
199
|
+
const navigate = useNavigate();
|
|
200
|
+
const path = ctx!.path;
|
|
201
|
+
const children = useChildren(`${path}/tasks`, { watch: true, watchNew: true });
|
|
202
|
+
const wsChildren = useChildren(`${path}/workspaces`, { watch: true, watchNew: true });
|
|
203
|
+
const templateChildren = useChildren(`${path}/templates`, { watch: true });
|
|
204
|
+
const [prompt, setPrompt] = useState('');
|
|
205
|
+
const [sending, setSending] = useState(false);
|
|
206
|
+
const [showConfig, setShowConfig] = useState(false);
|
|
207
|
+
|
|
208
|
+
const allTasks = (children ?? [])
|
|
209
|
+
.filter(c => c.$type === 'metatron.task')
|
|
210
|
+
.sort((a, b) => ((b.createdAt as number) || 0) - ((a.createdAt as number) || 0));
|
|
211
|
+
|
|
212
|
+
const workspaces = (wsChildren ?? []).filter(c => c.$type === 'metatron.workspace');
|
|
213
|
+
const templates = (templateChildren ?? []).filter(c => c.$type === 'metatron.template' && c.prompt);
|
|
214
|
+
const active = allTasks.filter(t => t.status !== 'done');
|
|
215
|
+
const done = allTasks.filter(t => t.status === 'done');
|
|
216
|
+
|
|
217
|
+
const handleRun = useCallback(async () => {
|
|
218
|
+
const text = prompt.trim();
|
|
219
|
+
if (!text) return;
|
|
220
|
+
setSending(true);
|
|
221
|
+
try {
|
|
222
|
+
await execute(path, 'task', { prompt: text });
|
|
223
|
+
setPrompt('');
|
|
224
|
+
} finally {
|
|
225
|
+
setSending(false);
|
|
226
|
+
}
|
|
227
|
+
}, [prompt, path]);
|
|
228
|
+
|
|
229
|
+
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
|
230
|
+
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) handleRun();
|
|
231
|
+
}, [handleRun]);
|
|
232
|
+
|
|
233
|
+
const node = ctx!.node;
|
|
234
|
+
|
|
235
|
+
const onSystemPromptChange = useCallback((val: string) => {
|
|
236
|
+
set({ ...node, systemPrompt: val });
|
|
237
|
+
}, [node]);
|
|
238
|
+
|
|
239
|
+
const clearSession = useCallback(() => {
|
|
240
|
+
set({ ...node, sessionId: '' });
|
|
241
|
+
}, [node]);
|
|
242
|
+
|
|
243
|
+
const handleNewWorkspace = useCallback(async () => {
|
|
244
|
+
const name = window.prompt('Workspace name:');
|
|
245
|
+
if (!name?.trim()) return;
|
|
246
|
+
const trpc = (await import('@treenity/react/trpc')).trpc;
|
|
247
|
+
const { createNode } = await import('@treenity/core');
|
|
248
|
+
const id = `ws-${Date.now()}`;
|
|
249
|
+
const wsPath = `${path}/workspaces/${id}`;
|
|
250
|
+
await trpc.set.mutate({ node: createNode(wsPath, 'metatron.workspace', {
|
|
251
|
+
name: name.trim(),
|
|
252
|
+
columns: [],
|
|
253
|
+
}) });
|
|
254
|
+
navigate(wsPath);
|
|
255
|
+
}, [path]);
|
|
256
|
+
|
|
257
|
+
const systemPrompt = String(value.systemPrompt ?? '');
|
|
258
|
+
const hasSession = !!(value.sessionId as string);
|
|
259
|
+
|
|
260
|
+
return (
|
|
261
|
+
<div className="flex flex-col gap-6 p-5 max-w-2xl mx-auto">
|
|
262
|
+
{/* Header */}
|
|
263
|
+
<div className="flex items-center gap-3">
|
|
264
|
+
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-violet-600 to-indigo-700 flex items-center justify-center text-white text-sm font-bold shadow-lg shadow-violet-600/20">
|
|
265
|
+
M
|
|
266
|
+
</div>
|
|
267
|
+
<div className="flex-1">
|
|
268
|
+
<h1 className="text-base font-semibold text-zinc-100 tracking-tight">Metatron</h1>
|
|
269
|
+
<p className="text-[11px] text-zinc-500">AI task orchestrator</p>
|
|
270
|
+
</div>
|
|
271
|
+
<button
|
|
272
|
+
onClick={() => setShowConfig(!showConfig)}
|
|
273
|
+
className={cn(
|
|
274
|
+
'w-7 h-7 rounded-md flex items-center justify-center text-zinc-500 transition-all duration-200',
|
|
275
|
+
showConfig ? 'bg-zinc-800 text-zinc-300' : 'hover:bg-zinc-800/60 hover:text-zinc-400'
|
|
276
|
+
)}
|
|
277
|
+
title="Settings"
|
|
278
|
+
>
|
|
279
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><circle cx="12" cy="12" r="3"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>
|
|
280
|
+
</button>
|
|
281
|
+
</div>
|
|
282
|
+
|
|
283
|
+
{/* Config panel (collapsible) */}
|
|
284
|
+
{showConfig && (
|
|
285
|
+
<div className="flex flex-col gap-3 p-4 rounded-xl bg-zinc-900/80 border border-zinc-800">
|
|
286
|
+
<div className="flex items-center gap-2">
|
|
287
|
+
{hasSession ? (
|
|
288
|
+
<span className="flex items-center gap-1.5 text-[11px] text-zinc-500 font-mono">
|
|
289
|
+
session {(value.sessionId as string).slice(0, 8)}
|
|
290
|
+
<button onClick={clearSession} className="text-zinc-600 hover:text-red-400 transition-colors" title="Clear session">
|
|
291
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M18 6L6 18M6 6l12 12"/></svg>
|
|
292
|
+
</button>
|
|
293
|
+
</span>
|
|
294
|
+
) : (
|
|
295
|
+
<span className="text-[11px] text-zinc-600">no session</span>
|
|
296
|
+
)}
|
|
297
|
+
{value.lastRun ? (
|
|
298
|
+
<span className="text-[11px] text-zinc-600 ml-auto">last run {formatTime(value.lastRun as number)}</span>
|
|
299
|
+
) : null}
|
|
300
|
+
</div>
|
|
301
|
+
<label className="text-[10px] text-zinc-600 uppercase tracking-wider font-medium">System prompt</label>
|
|
302
|
+
<textarea
|
|
303
|
+
value={systemPrompt}
|
|
304
|
+
onChange={e => onSystemPromptChange(e.target.value)}
|
|
305
|
+
rows={10}
|
|
306
|
+
placeholder="Metatron operating instructions..."
|
|
307
|
+
className="bg-zinc-950 border border-zinc-800 rounded-lg px-3 py-2.5 text-xs font-mono text-zinc-300 resize-y focus:outline-none focus:border-violet-600/50 focus:ring-1 focus:ring-violet-600/20 transition-all duration-200 placeholder:text-zinc-700"
|
|
308
|
+
/>
|
|
309
|
+
<PermissionManager configPath={path} />
|
|
310
|
+
</div>
|
|
311
|
+
)}
|
|
312
|
+
|
|
313
|
+
{/* Workspaces */}
|
|
314
|
+
{(workspaces.length > 0 || true) && (
|
|
315
|
+
<div className="flex flex-col gap-2">
|
|
316
|
+
<div className="flex items-center gap-2">
|
|
317
|
+
<span className="text-[10px] text-zinc-600 uppercase tracking-wider font-medium">Workspaces</span>
|
|
318
|
+
<button
|
|
319
|
+
onClick={handleNewWorkspace}
|
|
320
|
+
className="ml-auto text-[11px] text-zinc-600 hover:text-violet-400 transition-colors duration-200"
|
|
321
|
+
>
|
|
322
|
+
+ new
|
|
323
|
+
</button>
|
|
324
|
+
</div>
|
|
325
|
+
{workspaces.length > 0 ? (
|
|
326
|
+
<div className="flex gap-2 overflow-x-auto pb-1">
|
|
327
|
+
{workspaces.map(ws => (
|
|
328
|
+
<button
|
|
329
|
+
key={ws.$path}
|
|
330
|
+
onClick={() => navigate(ws.$path as string)}
|
|
331
|
+
className="flex flex-col gap-1 px-4 py-3 rounded-xl bg-zinc-900/60 border border-zinc-800 hover:border-zinc-700 hover:bg-zinc-800/60 transition-all duration-200 min-w-[140px] group"
|
|
332
|
+
>
|
|
333
|
+
<span className="text-sm text-zinc-300 font-medium truncate group-hover:text-zinc-100 transition-colors">
|
|
334
|
+
{String(ws.name) || (ws.$path as string).split('/').at(-1)}
|
|
335
|
+
</span>
|
|
336
|
+
<span className="text-[10px] text-zinc-600 font-mono">
|
|
337
|
+
{Array.isArray(ws.columns) ? ws.columns.length : 0} columns
|
|
338
|
+
</span>
|
|
339
|
+
</button>
|
|
340
|
+
))}
|
|
341
|
+
</div>
|
|
342
|
+
) : (
|
|
343
|
+
<p className="text-[11px] text-zinc-700 italic">No workspaces yet</p>
|
|
344
|
+
)}
|
|
345
|
+
</div>
|
|
346
|
+
)}
|
|
347
|
+
|
|
348
|
+
{/* Templates (horizontal pills) */}
|
|
349
|
+
{templates.length > 0 && (
|
|
350
|
+
<div className="flex gap-1.5 overflow-x-auto pb-0.5">
|
|
351
|
+
{templates.map(t => (
|
|
352
|
+
<button
|
|
353
|
+
key={t.$path}
|
|
354
|
+
onClick={() => setPrompt(String(t.prompt))}
|
|
355
|
+
className="px-3 py-1.5 rounded-lg text-[11px] font-medium bg-zinc-900/60 border border-zinc-800 text-zinc-400 hover:border-violet-600/40 hover:text-violet-300 transition-all duration-200 whitespace-nowrap shrink-0"
|
|
356
|
+
>
|
|
357
|
+
{String(t.name) || String(t.prompt).slice(0, 30)}
|
|
358
|
+
</button>
|
|
359
|
+
))}
|
|
360
|
+
</div>
|
|
361
|
+
)}
|
|
362
|
+
|
|
363
|
+
{/* Task input */}
|
|
364
|
+
<div className="flex flex-col gap-2">
|
|
365
|
+
<div className="relative">
|
|
366
|
+
<textarea
|
|
367
|
+
value={prompt}
|
|
368
|
+
onChange={e => setPrompt(e.target.value)}
|
|
369
|
+
onKeyDown={handleKeyDown}
|
|
370
|
+
rows={2}
|
|
371
|
+
placeholder="What should Metatron do?"
|
|
372
|
+
className="w-full bg-zinc-900/60 border border-zinc-800 rounded-xl px-4 py-3 pr-20 text-sm text-zinc-200 resize-y focus:outline-none focus:border-violet-600/50 focus:ring-1 focus:ring-violet-600/20 transition-all duration-200 placeholder:text-zinc-600"
|
|
373
|
+
/>
|
|
374
|
+
<button
|
|
375
|
+
onClick={handleRun}
|
|
376
|
+
disabled={sending || !prompt.trim()}
|
|
377
|
+
className={cn(
|
|
378
|
+
'absolute right-2 bottom-2 px-3.5 py-1.5 rounded-lg text-xs font-semibold transition-all duration-200',
|
|
379
|
+
prompt.trim()
|
|
380
|
+
? 'bg-violet-600 hover:bg-violet-500 text-white shadow-sm shadow-violet-600/20'
|
|
381
|
+
: 'bg-zinc-800 text-zinc-600 cursor-not-allowed'
|
|
382
|
+
)}
|
|
383
|
+
>
|
|
384
|
+
{sending ? '...' : 'Run'}
|
|
385
|
+
</button>
|
|
386
|
+
</div>
|
|
387
|
+
<span className="text-[10px] text-zinc-700 pl-1">Cmd+Enter to send</span>
|
|
388
|
+
</div>
|
|
389
|
+
|
|
390
|
+
{/* Active tasks */}
|
|
391
|
+
{active.length > 0 && (
|
|
392
|
+
<div className="flex flex-col gap-2">
|
|
393
|
+
<span className="text-[10px] text-zinc-600 uppercase tracking-wider font-medium">
|
|
394
|
+
Active ({active.length})
|
|
395
|
+
</span>
|
|
396
|
+
<div className="flex flex-col gap-1.5">
|
|
397
|
+
{active.map(task => <TaskRowInline key={String(task.$path)} task={task} />)}
|
|
398
|
+
</div>
|
|
399
|
+
</div>
|
|
400
|
+
)}
|
|
401
|
+
|
|
402
|
+
{/* Done */}
|
|
403
|
+
{done.length > 0 && <DoneSection tasks={done} />}
|
|
404
|
+
</div>
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
register('metatron.config', 'react', ConfigView);
|