anentrypoint-design 0.0.63 → 0.0.65
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/package.json +1 -1
- package/src/desktop/freddie-dashboard.css +16 -32
- package/src/desktop/freddie-dashboard.js +307 -282
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anentrypoint-design",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.65",
|
|
4
4
|
"description": "247420 design system SDK — webjsx + modified ripple-ui, single-file ESM bundle for reproducible use of the AnEntrypoint design.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/247420.js",
|
|
@@ -1,32 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
.
|
|
5
|
-
.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
.fdash-panel pre { font-family: var(--ff-mono, JetBrains Mono, monospace); font-size: 11px; white-space: pre-wrap; word-break: break-all; margin: 0; max-height: 280px; overflow: auto; }
|
|
18
|
-
.fdash-panel table { width: 100%; border-collapse: collapse; font-size: 12px; }
|
|
19
|
-
.fdash-panel th, .fdash-panel td { padding: 4px 8px; text-align: left; border-bottom: 1px solid var(--panel-2, #DDD3BC); }
|
|
20
|
-
.fdash-panel th { font-weight: 600; opacity: 0.7; font-size: 10px; text-transform: uppercase; }
|
|
21
|
-
.fdash-row { display: flex; align-items: center; gap: 8px; padding: 4px 0; font-size: 12px; }
|
|
22
|
-
.fdash-row .code { font-family: var(--ff-mono, JetBrains Mono, monospace); opacity: 0.6; }
|
|
23
|
-
.fdash-row .meta { margin-left: auto; opacity: 0.5; font-size: 11px; }
|
|
24
|
-
.fdash-form { display: flex; gap: 4px; flex-wrap: wrap; align-items: center; margin-bottom: 8px; }
|
|
25
|
-
.fdash-form input, .fdash-form textarea, .fdash-form select { font: inherit; padding: 4px 8px; border: 1px solid var(--panel-2, #DDD3BC); border-radius: var(--r-1, 6px); background: var(--panel-0, #F5F0E4); color: inherit; }
|
|
26
|
-
.fdash-form button { padding: 4px 12px; background: var(--panel-accent, #3F8A4A); color: #fff; border: 0; border-radius: var(--r-1, 6px); cursor: pointer; }
|
|
27
|
-
.fdash-form button.danger { background: #c44; }
|
|
28
|
-
.fdash-chip { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 11px; margin: 2px; }
|
|
29
|
-
.fdash-chip.ok { background: var(--panel-select, #C8E4CA); }
|
|
30
|
-
.fdash-chip.miss { background: var(--panel-2, #DDD3BC); opacity: 0.6; }
|
|
31
|
-
.fdash-empty { padding: 20px; text-align: center; opacity: 0.5; font-size: 12px; }
|
|
32
|
-
@media (max-width: 600px) { .fdash-side { flex: 0 0 56px; } .fdash-nav button .label { display: none; } }
|
|
1
|
+
/* freddie-dashboard.css — minimal supplemental rule.
|
|
2
|
+
The dashboard now renders through bible components (AppShell/Panel/Kpi/
|
|
3
|
+
Table/Receipt/Hero/Row/EmptyState/Chip) that ship their own classes via
|
|
4
|
+
247420.css. The only extra: a styled <pre> for raw JSON dumps. */
|
|
5
|
+
.app-fd .fd-pre {
|
|
6
|
+
font-family: var(--ff-mono, JetBrains Mono, monospace);
|
|
7
|
+
font-size: 12px;
|
|
8
|
+
white-space: pre-wrap;
|
|
9
|
+
word-break: break-all;
|
|
10
|
+
margin: 0;
|
|
11
|
+
max-height: 320px;
|
|
12
|
+
overflow: auto;
|
|
13
|
+
background: var(--panel-2, #DDD3BC);
|
|
14
|
+
padding: 8px 10px;
|
|
15
|
+
border-radius: var(--r-1, 6px);
|
|
16
|
+
}
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
import * as webjsx from '../../vendor/webjsx/index.js';
|
|
2
|
+
import * as components from '../components.js';
|
|
3
|
+
|
|
4
|
+
const h = webjsx.createElement;
|
|
5
|
+
const {
|
|
6
|
+
AppShell, Topbar, Side, Crumb, Status, Brand, Glyph,
|
|
7
|
+
Panel, Row, RowLink, Hero, Receipt, Kpi, Table, Section,
|
|
8
|
+
EmptyState, Chip,
|
|
9
|
+
} = components;
|
|
10
|
+
|
|
1
11
|
const ROUTES = [
|
|
2
12
|
{ path: 'projects', label: 'projects', glyph: '◆' },
|
|
3
13
|
{ path: 'home', label: 'home', glyph: '⌂' },
|
|
@@ -16,106 +26,40 @@ const ROUTES = [
|
|
|
16
26
|
{ path: 'gateway', label: 'gateway', glyph: '⇌' },
|
|
17
27
|
];
|
|
18
28
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
else if (k === 'text') e.textContent = attrs.text;
|
|
26
|
-
else e.setAttribute(k, attrs[k]);
|
|
27
|
-
}
|
|
28
|
-
return e;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function kpi(items) {
|
|
32
|
-
const c = el('div', 'fdash-kpi');
|
|
33
|
-
for (const [v, l] of items) {
|
|
34
|
-
const k = el('div', 'k');
|
|
35
|
-
k.appendChild(el('div', 'v', { text: String(v) }));
|
|
36
|
-
k.appendChild(el('div', 'l', { text: String(l) }));
|
|
37
|
-
c.appendChild(k);
|
|
38
|
-
}
|
|
39
|
-
return c;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function panel(title, body, count) {
|
|
43
|
-
const p = el('div', 'fdash-panel');
|
|
44
|
-
const h = el('h3'); h.textContent = title + (count != null ? ' · ' + count : '');
|
|
45
|
-
p.appendChild(h);
|
|
46
|
-
if (body instanceof Node) p.appendChild(body);
|
|
47
|
-
else if (Array.isArray(body)) for (const n of body) if (n) p.appendChild(n);
|
|
48
|
-
else if (typeof body === 'string') { const pre = el('pre'); pre.textContent = body; p.appendChild(pre); }
|
|
49
|
-
return p;
|
|
50
|
-
}
|
|
29
|
+
const OS_ROUTE_DEFS = [
|
|
30
|
+
{ path: 'os-instances', label: 'instances', glyph: '◫' },
|
|
31
|
+
{ path: 'os-windows', label: 'windows', glyph: '▭' },
|
|
32
|
+
{ path: 'os-x', label: 'x-server', glyph: '✕' },
|
|
33
|
+
{ path: 'os-fs', label: 'fs', glyph: '📁' },
|
|
34
|
+
];
|
|
51
35
|
|
|
52
|
-
function
|
|
53
|
-
|
|
54
|
-
if (opts.code) r.appendChild(el('span', 'code', { text: opts.code }));
|
|
55
|
-
r.appendChild(el('span', 'title', { text: opts.title || '' }));
|
|
56
|
-
if (opts.sub) r.appendChild(el('span', 'sub', { text: ' — ' + opts.sub }));
|
|
57
|
-
if (opts.meta) r.appendChild(el('span', 'meta', { text: opts.meta }));
|
|
58
|
-
return r;
|
|
36
|
+
function pre(obj) {
|
|
37
|
+
return h('pre', { class: 'fd-pre' }, typeof obj === 'string' ? obj : JSON.stringify(obj, null, 2));
|
|
59
38
|
}
|
|
60
39
|
|
|
61
|
-
function
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const tr = el('tr');
|
|
69
|
-
for (const c of r) tr.appendChild(el('td', null, { text: String(c) }));
|
|
70
|
-
tb.appendChild(tr);
|
|
71
|
-
}
|
|
72
|
-
t.appendChild(tb);
|
|
73
|
-
return t;
|
|
40
|
+
function form(opts) {
|
|
41
|
+
const { fields = [], submit = 'submit', onSubmit } = opts;
|
|
42
|
+
return h('form', { class: 'row-form', onsubmit: (ev) => { ev.preventDefault(); onSubmit && onSubmit(ev); } },
|
|
43
|
+
...fields.map(f => f.kind === 'textarea'
|
|
44
|
+
? h('textarea', { name: f.name, placeholder: f.placeholder || '', rows: f.rows || 4 })
|
|
45
|
+
: h('input', { name: f.name, type: f.type || 'text', placeholder: f.placeholder || '', value: f.value || '', required: f.required ? 'true' : null })),
|
|
46
|
+
h('button', { type: 'submit', class: 'btn-primary' }, submit));
|
|
74
47
|
}
|
|
75
48
|
|
|
76
|
-
function pre(obj) { return el('pre', null, { text: typeof obj === 'string' ? obj : JSON.stringify(obj, null, 2) }); }
|
|
77
|
-
|
|
78
49
|
export function createFreddieDashboard({ instance, bootHost, osSurfaces }) {
|
|
79
|
-
const root =
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const main = el('div', 'fdash-main');
|
|
83
|
-
side.appendChild(nav);
|
|
84
|
-
root.appendChild(side); root.appendChild(main);
|
|
50
|
+
const root = document.createElement('div');
|
|
51
|
+
root.className = 'app-fd ds-247420';
|
|
52
|
+
root.style.cssText = 'height:100%;overflow:hidden;display:flex;flex-direction:column;';
|
|
85
53
|
|
|
86
|
-
|
|
54
|
+
const state = {
|
|
55
|
+
active: 'home',
|
|
56
|
+
ts: new Date().toLocaleTimeString(),
|
|
57
|
+
body: null,
|
|
58
|
+
error: null,
|
|
59
|
+
};
|
|
87
60
|
let host = instance.host || null;
|
|
88
61
|
|
|
89
|
-
|
|
90
|
-
active = p;
|
|
91
|
-
for (const b of nav.querySelectorAll('button')) b.classList.toggle('active', b.dataset.path === p);
|
|
92
|
-
render();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const OS_ROUTES = osSurfaces ? [
|
|
96
|
-
{ path: 'os-instances', label: 'instances', glyph: '◫' },
|
|
97
|
-
{ path: 'os-windows', label: 'windows', glyph: '▭' },
|
|
98
|
-
{ path: 'os-x', label: 'x-server', glyph: '✕' },
|
|
99
|
-
{ path: 'os-fs', label: 'fs', glyph: '📁' },
|
|
100
|
-
] : [];
|
|
101
|
-
|
|
102
|
-
function navHead(text) {
|
|
103
|
-
const h = el('div', 'group-head', { text });
|
|
104
|
-
nav.appendChild(h);
|
|
105
|
-
}
|
|
106
|
-
function navBtn(r) {
|
|
107
|
-
const b = el('button', null, { 'data-path': r.path, on: { click: () => setActive(r.path) } });
|
|
108
|
-
b.appendChild(el('span', 'glyph', { text: r.glyph }));
|
|
109
|
-
b.appendChild(document.createTextNode(' '));
|
|
110
|
-
b.appendChild(el('span', 'label', { text: r.label }));
|
|
111
|
-
nav.appendChild(b);
|
|
112
|
-
}
|
|
113
|
-
navHead('freddie');
|
|
114
|
-
for (const r of ROUTES) navBtn(r);
|
|
115
|
-
if (OS_ROUTES.length) {
|
|
116
|
-
navHead('os');
|
|
117
|
-
for (const r of OS_ROUTES) navBtn(r);
|
|
118
|
-
}
|
|
62
|
+
const allRoutes = osSurfaces ? [...ROUTES, ...OS_ROUTE_DEFS] : ROUTES;
|
|
119
63
|
|
|
120
64
|
async function ensureHost() {
|
|
121
65
|
if (host) return host;
|
|
@@ -124,281 +68,362 @@ export function createFreddieDashboard({ instance, bootHost, osSurfaces }) {
|
|
|
124
68
|
return host;
|
|
125
69
|
}
|
|
126
70
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
71
|
+
function setActive(p) {
|
|
72
|
+
state.active = p;
|
|
73
|
+
rerender();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function buildSide() {
|
|
77
|
+
const sections = [{
|
|
78
|
+
group: 'FREDDIE',
|
|
79
|
+
items: ROUTES.map(r => ({
|
|
80
|
+
glyph: r.glyph, label: r.label, href: '#fd-' + r.path,
|
|
81
|
+
active: state.active === r.path,
|
|
82
|
+
onClick: (ev) => { ev.preventDefault(); setActive(r.path); },
|
|
83
|
+
})),
|
|
84
|
+
}];
|
|
85
|
+
if (osSurfaces) sections.push({
|
|
86
|
+
group: 'OS',
|
|
87
|
+
items: OS_ROUTE_DEFS.map(r => ({
|
|
88
|
+
glyph: r.glyph, label: r.label, href: '#fd-' + r.path,
|
|
89
|
+
active: state.active === r.path,
|
|
90
|
+
onClick: (ev) => { ev.preventDefault(); setActive(r.path); },
|
|
91
|
+
})),
|
|
92
|
+
});
|
|
93
|
+
return Side({ sections });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function view() {
|
|
97
|
+
const route = allRoutes.find(r => r.path === state.active) || ROUTES[1];
|
|
98
|
+
return AppShell({
|
|
99
|
+
topbar: Topbar({ brand: 'freddie', leaf: 'dashboard', items: [], active: '' }),
|
|
100
|
+
crumb: Crumb({ trail: ['freddie', instance.id], leaf: route.path, right: state.error ? Chip({ tone: 'miss', children: 'error' }) : Chip({ tone: 'ok', children: 'live' }) }),
|
|
101
|
+
side: buildSide(),
|
|
102
|
+
main: state.body || EmptyState({ text: 'loading…', glyph: '◌' }),
|
|
103
|
+
status: Status({ left: ['ds-247420 · webjsx · ' + allRoutes.length + ' routes', 'instance=' + instance.id], right: [state.ts] }),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function rerender() {
|
|
108
|
+
webjsx.applyDiff(root, view());
|
|
109
|
+
loadActive();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function loadActive() {
|
|
132
113
|
try {
|
|
133
|
-
const
|
|
134
|
-
const
|
|
135
|
-
|
|
114
|
+
const h0 = await ensureHost();
|
|
115
|
+
const page = PAGES[state.active] || PAGES.home;
|
|
116
|
+
state.body = await page(h0, instance);
|
|
117
|
+
state.error = null;
|
|
136
118
|
} catch (e) {
|
|
137
|
-
|
|
119
|
+
state.error = String(e && e.stack || e);
|
|
120
|
+
state.body = Panel({ title: 'error', children: pre(state.error) });
|
|
138
121
|
}
|
|
122
|
+
state.ts = new Date().toLocaleTimeString();
|
|
123
|
+
webjsx.applyDiff(root, view());
|
|
139
124
|
}
|
|
140
125
|
|
|
141
126
|
const PAGES = {
|
|
142
|
-
async projects(
|
|
143
|
-
const list =
|
|
144
|
-
const
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
form.appendChild(el('button', null, { type: 'submit', text: 'add' }));
|
|
153
|
-
const rows = list.map(p => {
|
|
154
|
-
const r = el('div', 'fdash-row');
|
|
155
|
-
r.appendChild(el('span', 'code', { text: p.name === active?.name ? '●' : '○' }));
|
|
156
|
-
r.appendChild(el('span', null, { text: p.name + (p.name === active?.name ? ' (active)' : '') }));
|
|
157
|
-
r.appendChild(el('span', 'meta', { text: p.path }));
|
|
158
|
-
if (p.name !== 'default') {
|
|
159
|
-
const del = el('button', null, { type: 'button', text: 'remove', on: { click: () => { try { h.pi.projects.remove(p.name); render(); } catch (e) { alert(e.message); } } } });
|
|
160
|
-
r.appendChild(del);
|
|
161
|
-
}
|
|
162
|
-
if (p.name !== active?.name) {
|
|
163
|
-
const sw = el('button', null, { type: 'button', text: 'switch', on: { click: () => { h.pi.projects.setActive(p.name); render(); } } });
|
|
164
|
-
r.appendChild(sw);
|
|
165
|
-
}
|
|
166
|
-
return r;
|
|
167
|
-
});
|
|
127
|
+
async projects(h0) {
|
|
128
|
+
const list = h0.pi.projects.list();
|
|
129
|
+
const activeProj = (typeof h0.pi.projects.active === 'function') ? h0.pi.projects.active() : null;
|
|
130
|
+
const rows = list.map(p => Row({
|
|
131
|
+
key: p.name,
|
|
132
|
+
code: p.name === activeProj?.name ? '●' : '○',
|
|
133
|
+
title: p.name + (p.name === activeProj?.name ? ' (active)' : ''),
|
|
134
|
+
meta: p.path,
|
|
135
|
+
onClick: () => { if (p.name !== activeProj?.name) try { h0.pi.projects.setActive(p.name); rerender(); } catch (e) { alert(e.message); } },
|
|
136
|
+
}));
|
|
168
137
|
return [
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
138
|
+
Hero({ title: 'projects', body: 'each project is its own ~/.freddie home: separate sessions, agents, skills, config, env, cron, batches.', accent: activeProj ? 'active · ' + activeProj.name : 'no active project' }),
|
|
139
|
+
Kpi({ items: [[list.length, 'projects'], [activeProj?.name || '—', 'active'], [activeProj?.path?.length > 30 ? '…' + activeProj.path.slice(-28) : (activeProj?.path || '—'), 'path']] }),
|
|
140
|
+
Panel({ title: 'add a project', children: form({
|
|
141
|
+
fields: [{ name: 'name', placeholder: 'project name', required: true }, { name: 'path', placeholder: '/abs/path' }],
|
|
142
|
+
submit: 'add',
|
|
143
|
+
onSubmit: (ev) => { try { h0.pi.projects.create({ name: ev.target.elements.name.value, path: ev.target.elements.path.value }); rerender(); } catch (e) { alert(e.message); } },
|
|
144
|
+
}) }),
|
|
145
|
+
Panel({ title: 'all projects', count: list.length, children: rows.length ? rows : EmptyState({ text: 'no projects', glyph: '◆' }) }),
|
|
146
|
+
Panel({ title: 'how encapsulation works', children: Receipt({ rows: [
|
|
147
|
+
['sessions db', '<project>/sessions.db'],
|
|
148
|
+
['config', '<project>/config.json'],
|
|
149
|
+
['skills', '<project>/skills/'],
|
|
150
|
+
['plugins', '<project>/plugins/'],
|
|
151
|
+
['cron', '<project>/cron.db'],
|
|
152
|
+
['batches', '<project>/batches/'],
|
|
153
|
+
['logs', '<project>/logs/'],
|
|
154
|
+
['auth', '<project>/auth.json'],
|
|
155
|
+
] }) }),
|
|
172
156
|
];
|
|
173
157
|
},
|
|
174
|
-
async home(
|
|
175
|
-
const sessions = await
|
|
176
|
-
const tools =
|
|
177
|
-
const skills =
|
|
178
|
-
const health =
|
|
158
|
+
async home(h0) {
|
|
159
|
+
const sessions = await h0.pi.sessions.list();
|
|
160
|
+
const tools = h0.pi.tools.size;
|
|
161
|
+
const skills = h0.pi.skills.size;
|
|
162
|
+
const health = (typeof h0.pi.health === 'function') ? h0.pi.health() : { ok: true };
|
|
179
163
|
return [
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
['
|
|
184
|
-
['list
|
|
185
|
-
['
|
|
186
|
-
|
|
187
|
-
|
|
164
|
+
Hero({ title: 'freddie', body: 'open js agent harness — pi-mono · xstate · floosie · anentrypoint-design.', accent: h0.version || 'web' }),
|
|
165
|
+
Kpi({ items: [[sessions.length, 'sessions'], [tools, 'tools'], [skills, 'skills']] }),
|
|
166
|
+
Panel({ title: 'quick start', children: Receipt({ rows: [
|
|
167
|
+
['open chat', "click 'chat' in sidebar"],
|
|
168
|
+
['list tools', '/tools in chat → tools tab'],
|
|
169
|
+
['list skills', '/skills → skills tab'],
|
|
170
|
+
['set api key', 'keys tab → click chip'],
|
|
171
|
+
['add cron', 'cron tab → form'],
|
|
172
|
+
] }) }),
|
|
173
|
+
Panel({ title: 'host', children: Receipt({ rows: Object.entries(health).map(([k, v]) => [k, String(v)]) }) }),
|
|
188
174
|
];
|
|
189
175
|
},
|
|
190
|
-
async chat(
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
176
|
+
async chat(h0) {
|
|
177
|
+
try { if (typeof window !== 'undefined' && window.__debug?.shell?.openApp) window.__debug.shell.openApp('chat'); } catch {}
|
|
178
|
+
return [
|
|
179
|
+
Panel({ title: 'chat', children: EmptyState({ text: 'opening chat window — chat lives in its own thebird app.', glyph: '⌨' }) }),
|
|
180
|
+
Panel({ title: 'cli surface', count: h0.pi.cli.size,
|
|
181
|
+
children: Table({ headers: ['command', 'description'], rows: [...h0.pi.cli.values()].map(c => [c.name, c.description || '']) }) }),
|
|
182
|
+
];
|
|
196
183
|
},
|
|
197
|
-
async sessions(
|
|
198
|
-
const list = await
|
|
184
|
+
async sessions(h0) {
|
|
185
|
+
const list = await h0.pi.sessions.list();
|
|
199
186
|
return [
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
?
|
|
203
|
-
:
|
|
187
|
+
Kpi({ items: [[list.length, 'sessions']] }),
|
|
188
|
+
Panel({ title: 'recent sessions', count: list.length, children: list.length === 0
|
|
189
|
+
? EmptyState({ text: 'no sessions yet — start a chat', glyph: '✉' })
|
|
190
|
+
: Table({ headers: ['id', 'title', 'platform', 'model', 'turns'],
|
|
191
|
+
rows: list.map(s => [(s.id || '').slice(0, 8), s.title || '—', s.platform || '—', s.model || '—', s.turn_count || 0]) }) }),
|
|
204
192
|
];
|
|
205
193
|
},
|
|
206
|
-
async agents(
|
|
207
|
-
const a = await
|
|
194
|
+
async agents(h0) {
|
|
195
|
+
const a = (typeof h0.pi.agents === 'function') ? await h0.pi.agents() : { count: 0, turns: 0, active: null };
|
|
208
196
|
return [
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
['total turns', String(a.turns)],
|
|
197
|
+
Kpi({ items: [[a.count || 0, 'active'], [a.turns || 0, 'turns']] }),
|
|
198
|
+
Panel({ title: 'agent overview', children: Receipt({ rows: [
|
|
199
|
+
['total turns', String(a.turns || 0)],
|
|
212
200
|
['active session', a.active || '(none)'],
|
|
213
201
|
['last activity', a.last_activity ? new Date(a.last_activity).toLocaleString() : '—'],
|
|
214
|
-
])),
|
|
202
|
+
] }) }),
|
|
215
203
|
];
|
|
216
204
|
},
|
|
217
|
-
async analytics(
|
|
218
|
-
const list = await
|
|
219
|
-
const tools = [...
|
|
205
|
+
async analytics(h0) {
|
|
206
|
+
const list = await h0.pi.sessions.list();
|
|
207
|
+
const tools = [...h0.pi.tools.values()];
|
|
220
208
|
const byPlatform = list.reduce((a, s) => { const k = s.platform || '?'; a[k] = (a[k] || 0) + 1; return a; }, {});
|
|
221
209
|
const byModel = list.reduce((a, s) => { const k = s.model || '?'; a[k] = (a[k] || 0) + 1; return a; }, {});
|
|
210
|
+
const byToolset = tools.reduce((a, t) => { (a[t.toolset || 'core'] = a[t.toolset || 'core'] || []).push(t.name); return a; }, {});
|
|
222
211
|
return [
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
212
|
+
Kpi({ items: [[list.length, 'sessions'], [tools.length, 'tools']] }),
|
|
213
|
+
Panel({ title: 'sessions by platform', children: Object.keys(byPlatform).length === 0
|
|
214
|
+
? EmptyState({ text: 'no data', glyph: '◉' })
|
|
215
|
+
: Table({ headers: ['platform', 'count'], rows: Object.entries(byPlatform).sort((a, b) => b[1] - a[1]) }) }),
|
|
216
|
+
Panel({ title: 'sessions by model', children: Object.keys(byModel).length === 0
|
|
217
|
+
? EmptyState({ text: 'no data', glyph: '◎' })
|
|
218
|
+
: Table({ headers: ['model', 'count'], rows: Object.entries(byModel).sort((a, b) => b[1] - a[1]) }) }),
|
|
219
|
+
Panel({ title: 'tool distribution', children: Table({ headers: ['toolset', 'count', 'tools'],
|
|
220
|
+
rows: Object.entries(byToolset).map(([k, v]) => [k, v.length, v.slice(0, 4).join(', ') + (v.length > 4 ? '…' : '')]) }) }),
|
|
227
221
|
];
|
|
228
222
|
},
|
|
229
|
-
async models(
|
|
230
|
-
const cfg =
|
|
223
|
+
async models(h0) {
|
|
224
|
+
const cfg = (typeof h0.pi.config?.load === 'function') ? await h0.pi.config.load() : {};
|
|
231
225
|
const agent = cfg.agent || {};
|
|
232
|
-
const form = el('form', 'fdash-form', { on: { submit: (ev) => {
|
|
233
|
-
ev.preventDefault();
|
|
234
|
-
h.pi.config.saveValue('agent.provider', ev.target.elements.provider.value);
|
|
235
|
-
h.pi.config.saveValue('agent.model', ev.target.elements.model.value);
|
|
236
|
-
render();
|
|
237
|
-
} } });
|
|
238
|
-
form.appendChild(el('input', null, { name: 'provider', placeholder: 'provider', value: agent.provider || '' }));
|
|
239
|
-
form.appendChild(el('input', null, { name: 'model', placeholder: 'model id', value: agent.model || '' }));
|
|
240
|
-
form.appendChild(el('button', null, { type: 'submit', text: 'update' }));
|
|
241
226
|
return [
|
|
242
|
-
|
|
243
|
-
|
|
227
|
+
Kpi({ items: [[agent.provider || '—', 'provider'], [agent.model || '—', 'model']] }),
|
|
228
|
+
Panel({ title: 'active model', children: Receipt({ rows: [
|
|
244
229
|
['provider', agent.provider || '(unset)'],
|
|
245
230
|
['model', agent.model || '(unset)'],
|
|
246
231
|
['max_iterations', String(agent.max_iterations || '—')],
|
|
247
|
-
|
|
248
|
-
|
|
232
|
+
['max_tokens', String(agent.max_tokens || '—')],
|
|
233
|
+
['temperature', String(agent.temperature ?? '—')],
|
|
234
|
+
] }) }),
|
|
235
|
+
Panel({ title: 'change model', children: form({
|
|
236
|
+
fields: [{ name: 'provider', placeholder: 'provider', value: agent.provider || '' }, { name: 'model', placeholder: 'model id', value: agent.model || '' }],
|
|
237
|
+
submit: 'update',
|
|
238
|
+
onSubmit: async (ev) => {
|
|
239
|
+
await h0.pi.config.saveValue('agent.provider', ev.target.elements.provider.value);
|
|
240
|
+
await h0.pi.config.saveValue('agent.model', ev.target.elements.model.value);
|
|
241
|
+
rerender();
|
|
242
|
+
},
|
|
243
|
+
}) }),
|
|
249
244
|
];
|
|
250
245
|
},
|
|
251
|
-
async logs(
|
|
252
|
-
const dbg =
|
|
253
|
-
return [
|
|
254
|
-
panel('host debug snapshot', pre(dbg)),
|
|
255
|
-
];
|
|
246
|
+
async logs(h0) {
|
|
247
|
+
const dbg = (typeof h0.pi.debug === 'function') ? h0.pi.debug() : { note: 'no debug surface' };
|
|
248
|
+
return [Panel({ title: 'host debug snapshot', children: pre(dbg) })];
|
|
256
249
|
},
|
|
257
|
-
async cron(
|
|
258
|
-
const list = await
|
|
259
|
-
const form = el('form', 'fdash-form', { on: { submit: async (ev) => {
|
|
260
|
-
ev.preventDefault();
|
|
261
|
-
try { await h.pi.cron.create({ cron: ev.target.elements.cron.value, prompt: ev.target.elements.prompt.value }); render(); }
|
|
262
|
-
catch (e) { alert(e.message); }
|
|
263
|
-
} } });
|
|
264
|
-
form.appendChild(el('input', null, { name: 'cron', placeholder: '* * * * *', required: 'true' }));
|
|
265
|
-
form.appendChild(el('input', null, { name: 'prompt', placeholder: 'prompt', required: 'true' }));
|
|
266
|
-
form.appendChild(el('button', null, { type: 'submit', text: 'create' }));
|
|
267
|
-
const tbl = list.length === 0
|
|
268
|
-
? el('div', 'fdash-empty', { text: 'no cron jobs' })
|
|
269
|
-
: table(['id', 'cron', 'prompt', 'enabled'], list.map(j => [j.id, j.cron, (j.prompt || '').slice(0, 40), j.enabled ? 'yes' : 'no']));
|
|
250
|
+
async cron(h0) {
|
|
251
|
+
const list = await h0.pi.cron.list();
|
|
270
252
|
return [
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
253
|
+
Kpi({ items: [[list.length, 'cron jobs']] }),
|
|
254
|
+
Panel({ title: 'add job', children: form({
|
|
255
|
+
fields: [{ name: 'cron', placeholder: '* * * * *', required: true }, { name: 'prompt', placeholder: 'prompt', required: true }],
|
|
256
|
+
submit: 'create',
|
|
257
|
+
onSubmit: async (ev) => { try { await h0.pi.cron.create({ cron: ev.target.elements.cron.value, prompt: ev.target.elements.prompt.value }); rerender(); } catch (e) { alert(e.message); } },
|
|
258
|
+
}) }),
|
|
259
|
+
Panel({ title: 'scheduled jobs', count: list.length, children: list.length === 0
|
|
260
|
+
? EmptyState({ text: 'no cron jobs — add one above', glyph: '◷' })
|
|
261
|
+
: Table({ headers: ['id', 'cron', 'prompt', 'enabled'],
|
|
262
|
+
rows: list.map(j => [j.id, j.cron, (j.prompt || '').slice(0, 40), j.enabled ? 'yes' : 'no']) }) }),
|
|
274
263
|
];
|
|
275
264
|
},
|
|
276
|
-
async skills(
|
|
277
|
-
const list = [...
|
|
265
|
+
async skills(h0) {
|
|
266
|
+
const list = [...h0.pi.skills.values()];
|
|
278
267
|
const byCat = list.reduce((a, s) => { (a[s.category || 'other'] = a[s.category || 'other'] || []).push(s); return a; }, {});
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
268
|
+
return [
|
|
269
|
+
Kpi({ items: [[list.length, 'skills'], [Object.keys(byCat).length, 'categories']] }),
|
|
270
|
+
...Object.entries(byCat).map(([cat, ss]) => Panel({ title: cat, count: ss.length,
|
|
271
|
+
children: ss.length === 0 ? EmptyState({ text: 'none', glyph: '◈' })
|
|
272
|
+
: Table({ headers: ['name', 'description'], rows: ss.map(s => [s.shortName || s.name, (s.description || '').slice(0, 100)]) }) })),
|
|
273
|
+
];
|
|
284
274
|
},
|
|
285
|
-
async config(
|
|
286
|
-
const cfg =
|
|
287
|
-
const profiles =
|
|
288
|
-
const commands =
|
|
289
|
-
const form = el('form', 'fdash-form', { on: { submit: (ev) => {
|
|
290
|
-
ev.preventDefault();
|
|
291
|
-
let v = ev.target.elements.value.value;
|
|
292
|
-
try { v = JSON.parse(v); } catch {}
|
|
293
|
-
h.pi.config.saveValue(ev.target.elements.key.value, v);
|
|
294
|
-
render();
|
|
295
|
-
} } });
|
|
296
|
-
form.appendChild(el('input', null, { name: 'key', placeholder: 'dotted.key', required: 'true' }));
|
|
297
|
-
form.appendChild(el('input', null, { name: 'value', placeholder: 'value (json or string)', required: 'true' }));
|
|
298
|
-
form.appendChild(el('button', null, { type: 'submit', text: 'save' }));
|
|
275
|
+
async config(h0) {
|
|
276
|
+
const cfg = (typeof h0.pi.config?.load === 'function') ? await h0.pi.config.load() : {};
|
|
277
|
+
const profiles = (typeof h0.pi.profiles?.list === 'function') ? h0.pi.profiles.list() : [];
|
|
278
|
+
const commands = (typeof h0.pi.commands?.list === 'function') ? h0.pi.commands.list() : [];
|
|
299
279
|
return [
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
280
|
+
Kpi({ items: [[profiles.length, 'profiles'], [commands.length, 'commands'], [cfg._config_version || 0, 'config version']] }),
|
|
281
|
+
Panel({ title: 'set config value', children: form({
|
|
282
|
+
fields: [{ name: 'key', placeholder: 'dotted.key (e.g. agent.model)', required: true }, { name: 'value', placeholder: 'value (json or string)', required: true }],
|
|
283
|
+
submit: 'save',
|
|
284
|
+
onSubmit: async (ev) => {
|
|
285
|
+
let v = ev.target.elements.value.value;
|
|
286
|
+
try { v = JSON.parse(v); } catch {}
|
|
287
|
+
await h0.pi.config.saveValue(ev.target.elements.key.value, v);
|
|
288
|
+
rerender();
|
|
289
|
+
},
|
|
290
|
+
}) }),
|
|
291
|
+
Panel({ title: 'commands', count: commands.length,
|
|
292
|
+
children: Table({ headers: ['name', 'category', 'description'], rows: commands.map(c => [c.name, c.category || '', c.description || '']) }) }),
|
|
293
|
+
Panel({ title: 'active config', children: pre(cfg) }),
|
|
304
294
|
];
|
|
305
295
|
},
|
|
306
|
-
async env(
|
|
307
|
-
const list =
|
|
296
|
+
async env(h0) {
|
|
297
|
+
const list = (typeof h0.pi.env?.list === 'function') ? h0.pi.env.list() : [];
|
|
308
298
|
const setCount = list.filter(k => k.set).length;
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
299
|
+
const chipNodes = list.map(k => h(
|
|
300
|
+
'span',
|
|
301
|
+
{
|
|
302
|
+
key: k.key,
|
|
303
|
+
onclick: () => {
|
|
304
|
+
const v = prompt('set ' + k.key + ' (empty to unset):');
|
|
305
|
+
if (v == null) return;
|
|
306
|
+
if (typeof h0.pi.env.set === 'function') { h0.pi.env.set(k.key, v); rerender(); }
|
|
307
|
+
},
|
|
308
|
+
style: 'cursor:pointer',
|
|
309
|
+
},
|
|
310
|
+
Chip({ tone: k.set ? 'ok' : 'miss', children: k.key + (k.set ? ' ✓' : ' ·') })
|
|
311
|
+
));
|
|
320
312
|
return [
|
|
321
|
-
|
|
322
|
-
|
|
313
|
+
Kpi({ items: [[setCount, 'set'], [list.length - setCount, 'missing'], [list.length, 'total known']] }),
|
|
314
|
+
Panel({
|
|
315
|
+
title: 'environment variables',
|
|
316
|
+
right: h('span', {}, Chip({ tone: 'ok', children: setCount + ' set' }), ' ', Chip({ tone: 'miss', children: (list.length - setCount) + ' missing' })),
|
|
317
|
+
children: h('div', { style: 'padding:8px 4px;display:flex;flex-wrap:wrap;gap:6px' }, ...chipNodes),
|
|
318
|
+
}),
|
|
323
319
|
];
|
|
324
320
|
},
|
|
325
|
-
async tools(
|
|
326
|
-
const list = [...
|
|
321
|
+
async tools(h0) {
|
|
322
|
+
const list = [...h0.pi.tools.values()];
|
|
323
|
+
const byToolset = list.reduce((a, t) => { (a[t.toolset || 'core'] = a[t.toolset || 'core'] || []).push(t); return a; }, {});
|
|
327
324
|
return [
|
|
328
|
-
|
|
329
|
-
|
|
325
|
+
Kpi({ items: [[list.length, 'tools'], [Object.keys(byToolset).length, 'toolsets']] }),
|
|
326
|
+
...Object.entries(byToolset).map(([ts, items]) => Panel({ title: 'toolset · ' + ts, count: items.length,
|
|
327
|
+
children: items.map(t => Row({ key: t.name, code: '⚒', title: t.name, sub: (t.description || (t.schema && t.schema.description) || '').slice(0, 80) })) })),
|
|
330
328
|
];
|
|
331
329
|
},
|
|
332
|
-
async batch(
|
|
333
|
-
const out =
|
|
334
|
-
const form = el('form', 'fdash-form', { on: { submit: async (ev) => {
|
|
335
|
-
ev.preventDefault();
|
|
336
|
-
const prompts = ev.target.elements.prompts.value.split('\n').map(s => s.trim()).filter(Boolean);
|
|
337
|
-
if (!prompts.length) return;
|
|
338
|
-
out.textContent = 'running…';
|
|
339
|
-
try { const r = await h.pi.batch.run({ prompts, concurrency: Number(ev.target.elements.conc.value) || 4 });
|
|
340
|
-
out.innerHTML = ''; out.appendChild(pre(r));
|
|
341
|
-
} catch (e) { out.textContent = 'error: ' + (e.message || e); }
|
|
342
|
-
} } });
|
|
343
|
-
const ta = el('textarea', null, { name: 'prompts', rows: '5', placeholder: 'one prompt per line' });
|
|
344
|
-
form.appendChild(ta);
|
|
345
|
-
form.appendChild(el('input', null, { name: 'conc', type: 'number', value: '4' }));
|
|
346
|
-
form.appendChild(el('button', null, { type: 'submit', text: 'run' }));
|
|
330
|
+
async batch(h0) {
|
|
331
|
+
const out = h('div', { id: 'fd-batch-out' });
|
|
347
332
|
return [
|
|
348
|
-
|
|
349
|
-
|
|
333
|
+
Section({ title: '// batch runner', children: [
|
|
334
|
+
Panel({ title: 'run prompts', children: form({
|
|
335
|
+
fields: [{ name: 'prompts', kind: 'textarea', placeholder: 'one prompt per line' }, { name: 'concurrency', type: 'number', value: '4' }],
|
|
336
|
+
submit: 'run',
|
|
337
|
+
onSubmit: async (ev) => {
|
|
338
|
+
const prompts = ev.target.elements.prompts.value.split('\n').map(s => s.trim()).filter(Boolean);
|
|
339
|
+
if (!prompts.length) return;
|
|
340
|
+
const node = root.querySelector('#fd-batch-out');
|
|
341
|
+
if (node) node.textContent = 'running…';
|
|
342
|
+
try {
|
|
343
|
+
const r = await h0.pi.batch.run({ prompts, concurrency: Number(ev.target.elements.concurrency.value) || 4 });
|
|
344
|
+
if (node) { node.innerHTML = ''; node.appendChild(document.createTextNode(JSON.stringify(r, null, 2))); }
|
|
345
|
+
} catch (e) { if (node) node.textContent = 'error: ' + (e.message || e); }
|
|
346
|
+
},
|
|
347
|
+
}) }),
|
|
348
|
+
Panel({ title: 'results', children: out }),
|
|
349
|
+
Panel({ title: 'cli usage', children: Receipt({ rows: [
|
|
350
|
+
['run batch file', 'freddie batch prompts.txt'],
|
|
351
|
+
['set concurrency', 'freddie batch prompts.txt --concurrency 8'],
|
|
352
|
+
['jsonl output', 'freddie batch prompts.txt > out.jsonl'],
|
|
353
|
+
] }) }),
|
|
354
|
+
] }),
|
|
350
355
|
];
|
|
351
356
|
},
|
|
352
|
-
async gateway(
|
|
353
|
-
const platforms =
|
|
357
|
+
async gateway(h0) {
|
|
358
|
+
const platforms = (typeof h0.pi.gateway?.platforms === 'function') ? h0.pi.gateway.platforms() : [];
|
|
359
|
+
const active = platforms.filter(p => p.enabled);
|
|
354
360
|
return [
|
|
355
|
-
|
|
356
|
-
|
|
361
|
+
Kpi({ items: [[platforms.length, 'platforms'], [active.length, 'active']] }),
|
|
362
|
+
Panel({ title: 'platforms', count: platforms.length,
|
|
363
|
+
right: active.length > 0 ? Chip({ tone: 'ok', children: active.length + ' active' }) : Chip({ tone: 'miss', children: 'none active' }),
|
|
364
|
+
children: platforms.length === 0 ? EmptyState({ text: 'no platforms registered', glyph: '⇌' })
|
|
365
|
+
: platforms.map(p => Row({ key: p.name, code: p.enabled ? '●' : '○', title: p.name, sub: p.note || '', meta: p.enabled ? 'enabled' : '' })) }),
|
|
366
|
+
Panel({ title: 'start gateway', children: Receipt({ rows: [
|
|
367
|
+
['webhook + api_server', 'freddie gateway --port 3000'],
|
|
368
|
+
['specific platform', 'TELEGRAM_BOT_TOKEN=… freddie gateway'],
|
|
369
|
+
['all platforms', 'set env vars per platform, then freddie gateway'],
|
|
370
|
+
] }) }),
|
|
357
371
|
];
|
|
358
372
|
},
|
|
359
373
|
async ['os-instances']() {
|
|
360
374
|
const list = (osSurfaces && osSurfaces.instances && osSurfaces.instances()) || [];
|
|
361
375
|
const activeId = osSurfaces && osSurfaces.activeInstanceId && osSurfaces.activeInstanceId();
|
|
362
376
|
return [
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
377
|
+
Kpi({ items: [[list.length, 'instances'], [activeId || '—', 'active']] }),
|
|
378
|
+
Panel({ title: 'instances', count: list.length, children: list.length === 0
|
|
379
|
+
? EmptyState({ text: 'no instances', glyph: '◫' })
|
|
380
|
+
: Table({ headers: ['id', 'active', 'shells', 'windows'],
|
|
381
|
+
rows: list.map(i => [i.id, i.id === activeId ? '●' : '', String((i.shells || []).length), String((i.windows || []).length)]) }) }),
|
|
366
382
|
];
|
|
367
383
|
},
|
|
368
384
|
async ['os-windows']() {
|
|
369
385
|
const wins = (osSurfaces && osSurfaces.wm && osSurfaces.wm.list && osSurfaces.wm.list()) || [];
|
|
370
386
|
const focused = osSurfaces && osSurfaces.wm && osSurfaces.wm.focused;
|
|
371
387
|
return [
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
388
|
+
Kpi({ items: [[wins.length, 'windows'], [focused ? (focused.id || focused.title || '?') : '—', 'focused']] }),
|
|
389
|
+
Panel({ title: 'windows', count: wins.length, children: wins.length === 0
|
|
390
|
+
? EmptyState({ text: 'no windows open', glyph: '▭' })
|
|
391
|
+
: Table({ headers: ['id', 'title', 'min', 'max', 'pos'],
|
|
392
|
+
rows: wins.map(w => [w.id || '?', w.title || '', w.min ? '●' : '', w.max ? '●' : '',
|
|
393
|
+
(w.el ? `${w.el.offsetLeft},${w.el.offsetTop} ${w.el.offsetWidth}×${w.el.offsetHeight}` : '')]) }) }),
|
|
376
394
|
];
|
|
377
395
|
},
|
|
378
396
|
async ['os-x']() {
|
|
379
397
|
const x = osSurfaces && osSurfaces.xServer && osSurfaces.xServer();
|
|
380
|
-
if (!x) return [
|
|
398
|
+
if (!x) return [Panel({ title: 'x-server', children: EmptyState({ text: 'x-server not running in this instance', glyph: '✕' }) })];
|
|
381
399
|
return [
|
|
382
|
-
|
|
383
|
-
|
|
400
|
+
Kpi({ items: [[x.windows, 'windows'], [x.pixmaps, 'pixmaps'], [x.gcs, 'gcs'], [x.atoms, 'atoms'], [x.cursors, 'cursors']] }),
|
|
401
|
+
Panel({ title: 'display', children: pre(x) }),
|
|
384
402
|
];
|
|
385
403
|
},
|
|
386
404
|
async ['os-fs']() {
|
|
387
405
|
const list = await instance.fs.list('/');
|
|
388
406
|
return [
|
|
389
|
-
|
|
390
|
-
|
|
407
|
+
Kpi({ items: [[list.length, 'paths'], [instance.id, 'instance']] }),
|
|
408
|
+
Panel({ title: 'paths', count: list.length, children: list.length === 0
|
|
409
|
+
? EmptyState({ text: 'empty fs', glyph: '📁' })
|
|
410
|
+
: pre(list.join('\n')) }),
|
|
391
411
|
];
|
|
392
412
|
},
|
|
393
413
|
};
|
|
394
414
|
|
|
395
|
-
|
|
415
|
+
rerender();
|
|
396
416
|
|
|
397
417
|
if (typeof window !== 'undefined') {
|
|
398
418
|
window.__debug = window.__debug || {};
|
|
399
419
|
window.__debug.instances = window.__debug.instances || {};
|
|
400
420
|
window.__debug.instances[instance.id] = window.__debug.instances[instance.id] || {};
|
|
401
|
-
window.__debug.instances[instance.id].dashboard = {
|
|
421
|
+
window.__debug.instances[instance.id].dashboard = {
|
|
422
|
+
root,
|
|
423
|
+
routes: allRoutes.map(r => r.path),
|
|
424
|
+
setActive,
|
|
425
|
+
get active() { return state.active; },
|
|
426
|
+
};
|
|
402
427
|
}
|
|
403
428
|
|
|
404
429
|
return { node: root, dispose() {} };
|