anentrypoint-design 0.0.65 → 0.0.67

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anentrypoint-design",
3
- "version": "0.0.65",
3
+ "version": "0.0.67",
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",
@@ -188,3 +188,11 @@ export function ProjectView({ project, copied, onCopy }) {
188
188
  Changelog({ entries: project.changelog })
189
189
  ];
190
190
  }
191
+
192
+ export function Form({ fields = [], submit = 'submit', onSubmit }) {
193
+ return h('form', { class: 'row-form', onsubmit: ev => { ev.preventDefault(); onSubmit && onSubmit(ev); } },
194
+ ...fields.map(f => f.kind === 'textarea'
195
+ ? h('textarea', { name: f.name, placeholder: f.placeholder || '', rows: f.rows || 4 })
196
+ : h('input', { name: f.name, type: f.type || 'text', placeholder: f.placeholder || '', value: f.value || '', required: f.required ? 'true' : null })),
197
+ h('button', { type: 'submit', class: 'btn-primary' }, submit));
198
+ }
@@ -86,9 +86,6 @@ export function Status({ left = [], right = [] } = {}) {
86
86
 
87
87
  export function AppShell({ topbar, crumb, side, main, status, narrow } = {}) {
88
88
  const hasSide = Boolean(side);
89
- const sideMotionClass = hasSide
90
- ? ' animate__animated animate__fadeInLeft'
91
- : ' animate__animated animate__fadeOutLeft';
92
89
  const sideNode = hasSide
93
90
  ? side
94
91
  : h('aside', { class: 'app-side', 'aria-hidden': 'true' });
@@ -97,7 +94,7 @@ export function AppShell({ topbar, crumb, side, main, status, narrow } = {}) {
97
94
  topbar || null,
98
95
  crumb || null,
99
96
  h('div', { class: 'app-body' + (hasSide ? '' : ' no-side') },
100
- h('div', { class: 'app-side-shell' + sideMotionClass }, sideNode),
97
+ h('div', { class: 'app-side-shell' }, sideNode),
101
98
  h('main', { class: 'app-main' + (narrow ? ' narrow' : '') }, ...(Array.isArray(main) ? main : [main]))
102
99
  ),
103
100
  status || null
package/src/components.js CHANGED
@@ -12,7 +12,7 @@ export {
12
12
  Hero, Install, Receipt, Changelog,
13
13
  WorksList, WritingList, Manifesto, Section,
14
14
  Kpi, Table,
15
- HomeView, ProjectView
15
+ HomeView, ProjectView, Form
16
16
  } from './components/content.js';
17
17
 
18
18
  export {
@@ -46,19 +46,58 @@ function form(opts) {
46
46
  h('button', { type: 'submit', class: 'btn-primary' }, submit));
47
47
  }
48
48
 
49
+ function getRecentPaths() {
50
+ try { return JSON.parse(localStorage.getItem('fd_recent_cwds') || '[]'); } catch { return []; }
51
+ }
52
+
53
+ function saveRecentPath(p) {
54
+ if (!p) return;
55
+ try {
56
+ const prev = getRecentPaths().filter(x => x !== p);
57
+ localStorage.setItem('fd_recent_cwds', JSON.stringify([p, ...prev].slice(0, 5)));
58
+ } catch {}
59
+ }
60
+
61
+ function skillLabel(s) {
62
+ if (s.shortName) return s.shortName;
63
+ const n = s.name || '';
64
+ return n.replace(/^gm:/, '').replace(/^software-development$/, 'software dev').replace(/-/g, ' ');
65
+ }
66
+
67
+ function renderChatMessages(container, messages) {
68
+ if (!container) return;
69
+ container.innerHTML = '';
70
+ for (const m of messages) {
71
+ if (m.role === 'tool') {
72
+ const det = document.createElement('details');
73
+ det.style.cssText = 'margin:4px 0;padding:4px 8px;background:rgba(0,0,0,0.18);border-radius:4px;font-family:monospace;font-size:0.85em;';
74
+ const sum = document.createElement('summary');
75
+ sum.style.cssText = 'cursor:pointer;color:var(--color-warn,#fc9);padding:2px 0;';
76
+ sum.textContent = '⚒ ' + m.name + (m.argsSummary ? ' ' + m.argsSummary : '');
77
+ det.appendChild(sum);
78
+ const body = document.createElement('pre');
79
+ body.style.cssText = 'margin:4px 0 0;white-space:pre-wrap;word-break:break-all;max-height:200px;overflow-y:auto;';
80
+ body.textContent = m.content || '';
81
+ det.appendChild(body);
82
+ container.appendChild(det);
83
+ } else {
84
+ const el = document.createElement('div');
85
+ el.style.cssText = 'padding:6px 10px;border-bottom:1px solid rgba(128,128,128,0.15);white-space:pre-wrap;word-break:break-word;';
86
+ el.style.color = m.role === 'assistant' ? 'var(--color-accent,#7c9)' : 'inherit';
87
+ el.textContent = (m.role === 'assistant' ? '◈ ' : '▷ ') + (m.content || '');
88
+ container.appendChild(el);
89
+ }
90
+ }
91
+ container.scrollTop = container.scrollHeight;
92
+ }
93
+
49
94
  export function createFreddieDashboard({ instance, bootHost, osSurfaces }) {
50
95
  const root = document.createElement('div');
51
96
  root.className = 'app-fd ds-247420';
52
97
  root.style.cssText = 'height:100%;overflow:hidden;display:flex;flex-direction:column;';
53
98
 
54
- const state = {
55
- active: 'home',
56
- ts: new Date().toLocaleTimeString(),
57
- body: null,
58
- error: null,
59
- };
99
+ const state = { active: 'home', ts: new Date().toLocaleTimeString(), body: null, error: null };
60
100
  let host = instance.host || null;
61
-
62
101
  const allRoutes = osSurfaces ? [...ROUTES, ...OS_ROUTE_DEFS] : ROUTES;
63
102
 
64
103
  async function ensureHost() {
@@ -68,10 +107,9 @@ export function createFreddieDashboard({ instance, bootHost, osSurfaces }) {
68
107
  return host;
69
108
  }
70
109
 
71
- function setActive(p) {
72
- state.active = p;
73
- rerender();
74
- }
110
+ function setActive(p) { state.active = p; rerender(); }
111
+
112
+ if (typeof window !== 'undefined') window.__fd_nav = setActive;
75
113
 
76
114
  function buildSide() {
77
115
  const sections = [{
@@ -104,10 +142,7 @@ export function createFreddieDashboard({ instance, bootHost, osSurfaces }) {
104
142
  });
105
143
  }
106
144
 
107
- function rerender() {
108
- webjsx.applyDiff(root, view());
109
- loadActive();
110
- }
145
+ function rerender() { webjsx.applyDiff(root, view()); loadActive(); }
111
146
 
112
147
  async function loadActive() {
113
148
  try {
@@ -164,31 +199,215 @@ export function createFreddieDashboard({ instance, bootHost, osSurfaces }) {
164
199
  Hero({ title: 'freddie', body: 'open js agent harness — pi-mono · xstate · floosie · anentrypoint-design.', accent: h0.version || 'web' }),
165
200
  Kpi({ items: [[sessions.length, 'sessions'], [tools, 'tools'], [skills, 'skills']] }),
166
201
  Panel({ title: 'quick start', children: Receipt({ rows: [
167
- ['open chat', "click 'chat' in sidebar"],
202
+ ['open chat', "click 'chat' in sidebar — set a working directory and pick a skill"],
203
+ ['pick skill', "software dev, research, planning — shown with descriptions"],
204
+ ['pick model', "select a configured provider + model in the chat bar"],
168
205
  ['list tools', '/tools in chat → tools tab'],
169
- ['list skills', '/skillsskills tab'],
170
- ['set api key', 'keys tab → click chip'],
206
+ ['set api key', 'keys tab click chip to set value'],
171
207
  ['add cron', 'cron tab → form'],
172
208
  ] }) }),
173
209
  Panel({ title: 'host', children: Receipt({ rows: Object.entries(health).map(([k, v]) => [k, String(v)]) }) }),
174
210
  ];
175
211
  },
176
212
  async chat(h0) {
177
- try { if (typeof window !== 'undefined' && window.__debug?.shell?.openApp) window.__debug.shell.openApp('chat'); } catch {}
213
+ const skills = [...h0.pi.skills.values()];
214
+ const providers = await fetch('/api/providers').then(r => r.json()).catch(() => []);
215
+ const configuredProviders = providers.filter(p => p.configured);
216
+
217
+ const chatState = window.__fd_chatState = window.__fd_chatState || {
218
+ cwd: '', skill: '', provider: '', model: '', messages: [], busy: false, sessionId: null,
219
+ };
220
+ if (!chatState.cwd) chatState.cwd = (getRecentPaths()[0] || '');
221
+
222
+ function getMsgsContainer() { return root.querySelector('#fd-chat-msgs'); }
223
+
224
+ function newSession() {
225
+ if (chatState.busy) return;
226
+ chatState.messages = [];
227
+ chatState.sessionId = null;
228
+ renderChatMessages(getMsgsContainer(), chatState.messages);
229
+ }
230
+
231
+ const parseSseEvents = (text) => {
232
+ const events = [];
233
+ let curEvent = null, curData = '';
234
+ for (const line of text.split('\n')) {
235
+ if (line.startsWith('event: ')) { curEvent = line.slice(7).trim(); }
236
+ else if (line.startsWith('data: ')) { curData = line.slice(6).trim(); }
237
+ else if (line === '' && curEvent) {
238
+ try { events.push({ event: curEvent, data: JSON.parse(curData) }); } catch {}
239
+ curEvent = null; curData = '';
240
+ }
241
+ }
242
+ return events;
243
+ };
244
+
245
+ const sendChat = async (ev) => {
246
+ ev.preventDefault();
247
+ if (chatState.busy) return;
248
+ const promptEl = ev.target.elements.prompt;
249
+ const prompt = promptEl.value.trim();
250
+ if (!prompt) return;
251
+ chatState.messages.push({ role: 'user', content: prompt });
252
+ promptEl.value = '';
253
+ promptEl.style.height = 'auto';
254
+ chatState.busy = true;
255
+ saveRecentPath(chatState.cwd);
256
+ renderChatMessages(getMsgsContainer(), chatState.messages);
257
+ try {
258
+ const body = { prompt, cwd: chatState.cwd || undefined, skill: chatState.skill || undefined, provider: chatState.provider || undefined, model: chatState.model || undefined, sessionId: chatState.sessionId || undefined };
259
+ const resp = await fetch('/api/chat', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) });
260
+ const text = await resp.text();
261
+ const events = parseSseEvents(text);
262
+ let assistantContent = '';
263
+ for (const { event, data } of events) {
264
+ if (event === 'start' && data.sessionId) chatState.sessionId = data.sessionId;
265
+ if (event === 'done' && data.sessionId) chatState.sessionId = data.sessionId;
266
+ if (event === 'message') {
267
+ const role = data.role;
268
+ if (role === 'assistant') {
269
+ const content = Array.isArray(data.content) ? data.content : [{ type: 'text', text: String(data.content || '') }];
270
+ for (const block of content) {
271
+ if (block.type === 'text') assistantContent += block.text;
272
+ if (block.type === 'tool_use') {
273
+ if (assistantContent) { chatState.messages.push({ role: 'assistant', content: assistantContent }); assistantContent = ''; }
274
+ const argsSummary = JSON.stringify(block.input || {}).slice(0, 60);
275
+ chatState.messages.push({ role: 'tool', name: block.name, argsSummary, content: JSON.stringify(block.input || {}, null, 2) });
276
+ }
277
+ }
278
+ } else if (role === 'tool') {
279
+ const tc = Array.isArray(data.content) ? data.content[0] : data;
280
+ chatState.messages.push({ role: 'tool', name: 'result', argsSummary: '', content: String(tc?.content || tc?.text || JSON.stringify(tc)) });
281
+ }
282
+ }
283
+ if (event === 'done' && data.result) {
284
+ if (!assistantContent) assistantContent = data.result;
285
+ }
286
+ if (event === 'error') assistantContent = 'error: ' + (data.error || 'unknown');
287
+ }
288
+ if (assistantContent) chatState.messages.push({ role: 'assistant', content: assistantContent });
289
+ if (!events.length) chatState.messages.push({ role: 'assistant', content: '(no response)' });
290
+ } catch (e) {
291
+ chatState.messages.push({ role: 'assistant', content: 'error: ' + e.message });
292
+ }
293
+ chatState.busy = false;
294
+ renderChatMessages(getMsgsContainer(), chatState.messages);
295
+ };
296
+
297
+ const recentPaths = getRecentPaths();
298
+ const datalistId = 'fd-cwd-list';
299
+ const byCat = skills.reduce((a, s) => { const c = s.category || 'other'; (a[c] = a[c] || []).push(s); return a; }, {});
300
+
301
+ setTimeout(() => renderChatMessages(getMsgsContainer(), chatState.messages), 50);
302
+
178
303
  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 || '']) }) }),
304
+ Panel({
305
+ title: 'chat',
306
+ right: h('button', {
307
+ class: 'btn-primary', style: 'padding:2px 10px;font-size:0.8em;',
308
+ onclick: (ev) => { ev.preventDefault(); newSession(); },
309
+ disabled: chatState.busy ? 'true' : null,
310
+ }, '+ new session'),
311
+ children: [
312
+ h('datalist', { id: datalistId }, ...recentPaths.map(p => h('option', { value: p }))),
313
+ h('form', { class: 'row-form', style: 'display:flex;flex-direction:column;gap:8px;', onsubmit: sendChat },
314
+ h('div', { style: 'display:flex;flex-direction:column;gap:4px;' },
315
+ h('label', { style: 'font-size:0.75em;opacity:0.7;letter-spacing:0.05em;' }, 'WORKING DIRECTORY'),
316
+ h('input', {
317
+ name: 'cwd', type: 'text', placeholder: 'e.g. C:/dev/myproject or /home/user/project',
318
+ value: chatState.cwd, list: datalistId,
319
+ style: 'width:100%;box-sizing:border-box;',
320
+ oninput: (ev) => { chatState.cwd = ev.target.value; },
321
+ })
322
+ ),
323
+ h('div', { style: 'display:flex;gap:8px;flex-wrap:wrap;' },
324
+ h('div', { style: 'display:flex;flex-direction:column;gap:4px;flex:2;min-width:160px;' },
325
+ h('label', { style: 'font-size:0.75em;opacity:0.7;letter-spacing:0.05em;' }, 'SKILL'),
326
+ h('select', { name: 'skill', onchange: (ev) => { chatState.skill = ev.target.value; } },
327
+ h('option', { value: '' }, '— no skill —'),
328
+ ...Object.entries(byCat).map(([cat, ss]) =>
329
+ h('optgroup', { label: cat },
330
+ ...ss.map(s => h('option', {
331
+ value: s.name,
332
+ selected: chatState.skill === s.name ? 'true' : null,
333
+ title: s.description || s.name,
334
+ }, skillLabel(s)))
335
+ )
336
+ )
337
+ )
338
+ ),
339
+ h('div', { style: 'display:flex;flex-direction:column;gap:4px;flex:2;min-width:140px;' },
340
+ h('label', { style: 'font-size:0.75em;opacity:0.7;letter-spacing:0.05em;' }, 'PROVIDER'),
341
+ h('select', { name: 'provider', onchange: (ev) => { chatState.provider = ev.target.value; } },
342
+ h('option', { value: '' }, configuredProviders.length ? '— auto —' : '— no providers configured —'),
343
+ ...configuredProviders.map(p => h('option', {
344
+ value: p.name,
345
+ selected: chatState.provider === p.name ? 'true' : null,
346
+ }, (p.available ? '● ' : '○ ') + p.name))
347
+ )
348
+ ),
349
+ h('div', { style: 'display:flex;flex-direction:column;gap:4px;flex:2;min-width:120px;' },
350
+ h('label', { style: 'font-size:0.75em;opacity:0.7;letter-spacing:0.05em;' }, 'MODEL (optional)'),
351
+ h('input', {
352
+ name: 'model', type: 'text',
353
+ placeholder: configuredProviders.find(p => p.name === chatState.provider)?.defaultModel || 'default',
354
+ value: chatState.model,
355
+ oninput: (ev) => { chatState.model = ev.target.value; },
356
+ })
357
+ )
358
+ ),
359
+ h('div', { style: 'display:flex;gap:8px;align-items:flex-end;' },
360
+ h('textarea', {
361
+ name: 'prompt', placeholder: 'describe what you want to do in the working directory…',
362
+ rows: 4, style: 'flex:1;resize:none;min-height:80px;',
363
+ oninput: (ev) => {
364
+ ev.target.style.height = 'auto';
365
+ ev.target.style.height = Math.min(ev.target.scrollHeight, 240) + 'px';
366
+ },
367
+ }),
368
+ h('button', {
369
+ type: 'submit', class: 'btn-primary', style: 'align-self:flex-end;',
370
+ disabled: chatState.busy ? 'true' : null,
371
+ }, chatState.busy ? '…' : 'send')
372
+ )
373
+ ),
374
+ h('div', { id: 'fd-chat-msgs', style: 'max-height:420px;overflow-y:auto;background:rgba(0,0,0,0.12);border-radius:4px;padding:4px;margin-top:8px;' }),
375
+ ],
376
+ }),
377
+ configuredProviders.length === 0
378
+ ? Panel({ title: 'no providers configured', children: Receipt({ rows: [
379
+ ['set API key', 'go to keys tab, click a provider chip to set its key'],
380
+ ['then reload', 'refresh this page to see providers here'],
381
+ ['or use acptoapi', 'run acptoapi server on localhost:4800 for local LLMs'],
382
+ ] }) })
383
+ : Panel({ title: 'configured providers', children: h('div', { style: 'display:flex;flex-wrap:wrap;gap:6px;padding:8px 4px;' },
384
+ ...providers.map(p => Chip({ tone: p.configured ? (p.available ? 'ok' : 'warn') : 'miss', children: p.name + (p.configured ? (p.available ? ' ●' : ' ○') : '') }))
385
+ ) }),
182
386
  ];
183
387
  },
184
388
  async sessions(h0) {
185
389
  const list = await h0.pi.sessions.list();
390
+ const rows = list.map(s => {
391
+ const cont = h('button', {
392
+ class: 'btn-primary', style: 'padding:2px 8px;font-size:0.8em;',
393
+ onclick: async () => {
394
+ const msgs = await h0.pi.sessions.getMessages(s.id);
395
+ const cs = window.__fd_chatState = window.__fd_chatState || { messages: [], busy: false, sessionId: null, cwd: '', skill: '', provider: '', model: '' };
396
+ cs.sessionId = s.id;
397
+ cs.messages = msgs.map(m => ({ role: m.role, content: String(m.content || '') }));
398
+ if (s.cwd) cs.cwd = s.cwd;
399
+ if (s.skill) cs.skill = s.skill;
400
+ if (typeof window.__fd_nav === 'function') window.__fd_nav('chat');
401
+ },
402
+ }, 'continue');
403
+ return [(s.id || '').slice(0, 8), s.title || '—', s.platform || '—', s.model || '—', s.cwd ? s.cwd.slice(-30) : '—', s.skill ? skillLabel({ name: s.skill }) : '—', cont];
404
+ });
186
405
  return [
187
406
  Kpi({ items: [[list.length, 'sessions']] }),
188
407
  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]) }) }),
408
+ ? EmptyState({ text: 'no sessions yet — open chat and send a message', glyph: '✉' })
409
+ : Table({ headers: ['id', 'title', 'platform', 'model', 'cwd', 'skill', ''],
410
+ rows }) }),
192
411
  ];
193
412
  },
194
413
  async agents(h0) {
@@ -223,6 +442,7 @@ export function createFreddieDashboard({ instance, bootHost, osSurfaces }) {
223
442
  async models(h0) {
224
443
  const cfg = (typeof h0.pi.config?.load === 'function') ? await h0.pi.config.load() : {};
225
444
  const agent = cfg.agent || {};
445
+ const providers = await fetch('/api/providers').then(r => r.json()).catch(() => []);
226
446
  return [
227
447
  Kpi({ items: [[agent.provider || '—', 'provider'], [agent.model || '—', 'model']] }),
228
448
  Panel({ title: 'active model', children: Receipt({ rows: [
@@ -241,6 +461,9 @@ export function createFreddieDashboard({ instance, bootHost, osSurfaces }) {
241
461
  rerender();
242
462
  },
243
463
  }) }),
464
+ Panel({ title: 'provider availability', children: h('div', { style: 'display:flex;flex-wrap:wrap;gap:6px;padding:8px 4px;' },
465
+ ...providers.map(p => Chip({ tone: p.configured ? (p.available ? 'ok' : 'warn') : 'miss', children: p.name + (p.configured ? (p.available ? ' ●' : ' ○') : ' ·') }))
466
+ ) }),
244
467
  ];
245
468
  },
246
469
  async logs(h0) {
@@ -267,10 +490,11 @@ export function createFreddieDashboard({ instance, bootHost, osSurfaces }) {
267
490
  const byCat = list.reduce((a, s) => { (a[s.category || 'other'] = a[s.category || 'other'] || []).push(s); return a; }, {});
268
491
  return [
269
492
  Kpi({ items: [[list.length, 'skills'], [Object.keys(byCat).length, 'categories']] }),
493
+ list.length === 0 ? EmptyState({ text: 'no skills loaded — add SKILL.md files to ~/.freddie/skills/', glyph: '◈' }) : null,
270
494
  ...Object.entries(byCat).map(([cat, ss]) => Panel({ title: cat, count: ss.length,
271
495
  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
- ];
496
+ : Table({ headers: ['name', 'description'], rows: ss.map(s => [skillLabel(s), (s.description || '').slice(0, 120)]) }) })),
497
+ ].filter(Boolean);
274
498
  },
275
499
  async config(h0) {
276
500
  const cfg = (typeof h0.pi.config?.load === 'function') ? await h0.pi.config.load() : {};