@polderlabs/bizar 2.3.0 → 2.6.0
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/cli/bin.mjs +73 -0
- package/cli/copy.mjs +42 -2
- package/cli/dashboard/api.mjs +473 -0
- package/cli/dashboard/browser.mjs +40 -0
- package/cli/dashboard/server.mjs +366 -0
- package/cli/dashboard/state.mjs +438 -0
- package/cli/dashboard/tasks-store.mjs +203 -0
- package/cli/dashboard/watcher.mjs +81 -0
- package/cli/dashboard.mjs +97 -0
- package/cli/install.mjs +17 -4
- package/config/commands/bizar.md +18 -0
- package/config/commands/plan.md +26 -0
- package/config/commands/visual-plan.md +15 -0
- package/config/opencode.json +259 -1
- package/dist/assets/index-BVvY22Gt.css +1 -0
- package/dist/assets/index-CO3c8O32.js +285 -0
- package/dist/assets/index-CO3c8O32.js.map +1 -0
- package/dist/index.html +18 -0
- package/package.json +26 -2
- package/src/App.tsx +233 -0
- package/src/components/Button.tsx +55 -0
- package/src/components/Card.tsx +40 -0
- package/src/components/EmptyState.tsx +30 -0
- package/src/components/Modal.tsx +137 -0
- package/src/components/Spinner.tsx +19 -0
- package/src/components/StatusBadge.tsx +25 -0
- package/src/components/Tag.tsx +28 -0
- package/src/components/Toast.tsx +142 -0
- package/src/components/Topbar.tsx +88 -0
- package/src/index.html +17 -0
- package/src/lib/api.ts +71 -0
- package/src/lib/markdown.tsx +59 -0
- package/src/lib/types.ts +200 -0
- package/src/lib/utils.ts +79 -0
- package/src/lib/ws.ts +132 -0
- package/src/main.tsx +12 -0
- package/src/styles/main.css +2324 -0
- package/src/views/Agents.tsx +199 -0
- package/src/views/Chat.tsx +255 -0
- package/src/views/Config.tsx +250 -0
- package/src/views/Overview.tsx +267 -0
- package/src/views/Plans.tsx +667 -0
- package/src/views/Projects.tsx +155 -0
- package/src/views/Settings.tsx +253 -0
- package/src/views/Tasks.tsx +567 -0
- package/tsconfig.json +23 -0
- package/vite.config.ts +24 -0
- package/config/opencode.json.template +0 -52
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// src/views/Overview.tsx — system overview: counts, activity, quick actions.
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
Bot,
|
|
5
|
+
CheckSquare,
|
|
6
|
+
Folder,
|
|
7
|
+
LayoutDashboard,
|
|
8
|
+
Map,
|
|
9
|
+
MessageSquare,
|
|
10
|
+
RefreshCw,
|
|
11
|
+
PlayCircle,
|
|
12
|
+
ShieldCheck,
|
|
13
|
+
FileText,
|
|
14
|
+
Zap,
|
|
15
|
+
} from 'lucide-react';
|
|
16
|
+
import { Card, CardTitle, CardMeta } from '../components/Card';
|
|
17
|
+
import { Button } from '../components/Button';
|
|
18
|
+
import { EmptyState } from '../components/EmptyState';
|
|
19
|
+
import { Spinner } from '../components/Spinner';
|
|
20
|
+
import { useToast } from '../components/Toast';
|
|
21
|
+
import { useModal } from '../components/Modal';
|
|
22
|
+
import { api } from '../lib/api';
|
|
23
|
+
import { formatRelative, formatTime } from '../lib/utils';
|
|
24
|
+
import type { Overview, Settings, Snapshot, ActivityItem } from '../lib/types';
|
|
25
|
+
|
|
26
|
+
type Props = {
|
|
27
|
+
snapshot: Snapshot;
|
|
28
|
+
settings: Settings;
|
|
29
|
+
activeTab: string;
|
|
30
|
+
setActiveTab: (id: string) => void;
|
|
31
|
+
refreshSnapshot: () => Promise<void>;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type StatCard = {
|
|
35
|
+
key: keyof Overview['counts'];
|
|
36
|
+
label: string;
|
|
37
|
+
Icon: typeof Bot;
|
|
38
|
+
tab: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const STATS: StatCard[] = [
|
|
42
|
+
{ key: 'agents', label: 'Agents', Icon: Bot, tab: 'agents' },
|
|
43
|
+
{ key: 'plans', label: 'Plans', Icon: Map, tab: 'plans' },
|
|
44
|
+
{ key: 'projects', label: 'Projects', Icon: Folder, tab: 'projects' },
|
|
45
|
+
{ key: 'sessions', label: 'Sessions', Icon: MessageSquare, tab: 'chat' },
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
export function Overview({
|
|
49
|
+
snapshot,
|
|
50
|
+
setActiveTab,
|
|
51
|
+
refreshSnapshot,
|
|
52
|
+
}: Props) {
|
|
53
|
+
const toast = useToast();
|
|
54
|
+
const modal = useModal();
|
|
55
|
+
const [overview, setOverview] = useState<Overview | null>(
|
|
56
|
+
snapshot.overview ?? null,
|
|
57
|
+
);
|
|
58
|
+
const [loading, setLoading] = useState(!snapshot.overview);
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (snapshot.overview) {
|
|
62
|
+
setOverview(snapshot.overview);
|
|
63
|
+
setLoading(false);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
let cancelled = false;
|
|
67
|
+
api
|
|
68
|
+
.get<Overview>('/overview')
|
|
69
|
+
.then((o) => {
|
|
70
|
+
if (!cancelled) {
|
|
71
|
+
setOverview(o);
|
|
72
|
+
setLoading(false);
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
.catch((err) => {
|
|
76
|
+
if (!cancelled) {
|
|
77
|
+
setLoading(false);
|
|
78
|
+
toast.error(`Could not load overview: ${err.message}`);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
return () => {
|
|
82
|
+
cancelled = true;
|
|
83
|
+
};
|
|
84
|
+
}, [snapshot.overview, toast]);
|
|
85
|
+
|
|
86
|
+
const onRefresh = async () => {
|
|
87
|
+
toast.info('Refreshing…', 1500);
|
|
88
|
+
await refreshSnapshot();
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const onAudit = () => {
|
|
92
|
+
modal.open({
|
|
93
|
+
title: 'Run audit',
|
|
94
|
+
children: (
|
|
95
|
+
<div>
|
|
96
|
+
<p>
|
|
97
|
+
The full security audit runs in the opencode TUI for proper
|
|
98
|
+
reporting and exit codes.
|
|
99
|
+
</p>
|
|
100
|
+
<p>Run this in your terminal:</p>
|
|
101
|
+
<pre className="code-block">
|
|
102
|
+
<code>bizar audit</code>
|
|
103
|
+
</pre>
|
|
104
|
+
</div>
|
|
105
|
+
),
|
|
106
|
+
footer: (
|
|
107
|
+
<Button variant="primary" onClick={() => modal.close()}>
|
|
108
|
+
Got it
|
|
109
|
+
</Button>
|
|
110
|
+
),
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
if (loading || !overview) {
|
|
115
|
+
return (
|
|
116
|
+
<div className="view-loading">
|
|
117
|
+
<Spinner size="lg" />
|
|
118
|
+
<p>Loading overview…</p>
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<div className="view view-overview">
|
|
125
|
+
<header className="view-header">
|
|
126
|
+
<div className="view-header-text">
|
|
127
|
+
<h2 className="view-title">
|
|
128
|
+
<LayoutDashboard size={18} />
|
|
129
|
+
System Overview
|
|
130
|
+
</h2>
|
|
131
|
+
<p className="view-subtitle">
|
|
132
|
+
Live snapshot of agents, plans, projects, and recent activity.
|
|
133
|
+
</p>
|
|
134
|
+
</div>
|
|
135
|
+
</header>
|
|
136
|
+
|
|
137
|
+
<section className="stat-grid">
|
|
138
|
+
{STATS.map((s) => {
|
|
139
|
+
const Icon = s.Icon;
|
|
140
|
+
return (
|
|
141
|
+
<Card
|
|
142
|
+
key={s.key}
|
|
143
|
+
variant="elevated"
|
|
144
|
+
interactive
|
|
145
|
+
className="stat-card"
|
|
146
|
+
onClick={() => setActiveTab(s.tab)}
|
|
147
|
+
role="button"
|
|
148
|
+
tabIndex={0}
|
|
149
|
+
onKeyDown={(e) => {
|
|
150
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
151
|
+
e.preventDefault();
|
|
152
|
+
setActiveTab(s.tab);
|
|
153
|
+
}
|
|
154
|
+
}}
|
|
155
|
+
>
|
|
156
|
+
<div className="stat-card-icon">
|
|
157
|
+
<Icon size={20} />
|
|
158
|
+
</div>
|
|
159
|
+
<div className="stat-card-body">
|
|
160
|
+
<div className="stat-card-value tabular-nums">
|
|
161
|
+
{overview.counts[s.key]}
|
|
162
|
+
</div>
|
|
163
|
+
<div className="stat-card-label">{s.label}</div>
|
|
164
|
+
</div>
|
|
165
|
+
<div className="stat-card-arrow">→</div>
|
|
166
|
+
</Card>
|
|
167
|
+
);
|
|
168
|
+
})}
|
|
169
|
+
</section>
|
|
170
|
+
|
|
171
|
+
<Card className="quick-actions">
|
|
172
|
+
<CardTitle>Quick actions</CardTitle>
|
|
173
|
+
<CardMeta>Jump straight to common workflows.</CardMeta>
|
|
174
|
+
<div className="quick-actions-row">
|
|
175
|
+
<Button
|
|
176
|
+
variant="secondary"
|
|
177
|
+
onClick={() => setActiveTab('chat')}
|
|
178
|
+
iconOnly={false}
|
|
179
|
+
>
|
|
180
|
+
<MessageSquare size={14} /> New chat
|
|
181
|
+
</Button>
|
|
182
|
+
<Button variant="secondary" onClick={() => setActiveTab('plans')}>
|
|
183
|
+
<Map size={14} /> New plan
|
|
184
|
+
</Button>
|
|
185
|
+
<Button variant="secondary" onClick={() => setActiveTab('tasks')}>
|
|
186
|
+
<CheckSquare size={14} /> Add task
|
|
187
|
+
</Button>
|
|
188
|
+
<Button variant="secondary" onClick={() => setActiveTab('projects')}>
|
|
189
|
+
<Folder size={14} /> Switch project
|
|
190
|
+
</Button>
|
|
191
|
+
<Button variant="secondary" onClick={onAudit}>
|
|
192
|
+
<ShieldCheck size={14} /> Run audit
|
|
193
|
+
</Button>
|
|
194
|
+
<Button variant="primary" onClick={onRefresh}>
|
|
195
|
+
<RefreshCw size={14} /> Refresh
|
|
196
|
+
</Button>
|
|
197
|
+
</div>
|
|
198
|
+
</Card>
|
|
199
|
+
|
|
200
|
+
<div className="overview-cols">
|
|
201
|
+
<Card>
|
|
202
|
+
<CardTitle>
|
|
203
|
+
<Zap size={14} /> Recent activity
|
|
204
|
+
</CardTitle>
|
|
205
|
+
<CardMeta>Last 30 events from .bizar/activity.log</CardMeta>
|
|
206
|
+
{overview.recentActivity.length === 0 ? (
|
|
207
|
+
<EmptyState
|
|
208
|
+
icon={<FileText size={28} />}
|
|
209
|
+
title="No activity yet"
|
|
210
|
+
message="Use the chat or invoke a Bizar command to start a feed."
|
|
211
|
+
/>
|
|
212
|
+
) : (
|
|
213
|
+
<ul className="activity-list">
|
|
214
|
+
{overview.recentActivity.slice(0, 30).map((it, idx) => (
|
|
215
|
+
<li key={idx} className="activity-item">
|
|
216
|
+
<span className="activity-ts tabular-nums">
|
|
217
|
+
{formatRelative(it.ts)}
|
|
218
|
+
</span>
|
|
219
|
+
<span className="activity-kind">{it.kind}</span>
|
|
220
|
+
<span className="activity-msg">{formatActivity(it)}</span>
|
|
221
|
+
</li>
|
|
222
|
+
))}
|
|
223
|
+
</ul>
|
|
224
|
+
)}
|
|
225
|
+
</Card>
|
|
226
|
+
|
|
227
|
+
<Card>
|
|
228
|
+
<CardTitle>Environment</CardTitle>
|
|
229
|
+
<CardMeta>Runtime + paths</CardMeta>
|
|
230
|
+
<dl className="env-table">
|
|
231
|
+
<dt>Node</dt>
|
|
232
|
+
<dd className="mono">{overview.versions.node}</dd>
|
|
233
|
+
<dt>Platform</dt>
|
|
234
|
+
<dd className="mono">{overview.versions.platform}</dd>
|
|
235
|
+
<dt>Project root</dt>
|
|
236
|
+
<dd className="mono ellipsis" title={overview.versions.projectRoot}>
|
|
237
|
+
{overview.versions.projectRoot}
|
|
238
|
+
</dd>
|
|
239
|
+
<dt>Bizar root</dt>
|
|
240
|
+
<dd className="mono ellipsis" title={overview.versions.bizarRoot}>
|
|
241
|
+
{overview.versions.bizarRoot}
|
|
242
|
+
</dd>
|
|
243
|
+
<dt>Generated</dt>
|
|
244
|
+
<dd className="mono tabular-nums">
|
|
245
|
+
{formatTime(overview.generatedAt)}
|
|
246
|
+
</dd>
|
|
247
|
+
</dl>
|
|
248
|
+
</Card>
|
|
249
|
+
</div>
|
|
250
|
+
|
|
251
|
+
<div className="overview-footer">
|
|
252
|
+
<PlayCircle size={14} /> Dashboard built {formatTime(overview.generatedAt)}
|
|
253
|
+
</div>
|
|
254
|
+
</div>
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function formatActivity(it: ActivityItem): string {
|
|
259
|
+
if (typeof it.message === 'string') return it.message;
|
|
260
|
+
if (typeof it.prompt === 'string') return it.prompt;
|
|
261
|
+
if (typeof it.slug === 'string') {
|
|
262
|
+
const title = typeof it.title === 'string' ? ` title=${it.title}` : '';
|
|
263
|
+
return `slug=${it.slug}${title}`;
|
|
264
|
+
}
|
|
265
|
+
if (typeof it.name === 'string') return `name=${it.name}`;
|
|
266
|
+
return JSON.stringify(it);
|
|
267
|
+
}
|