antri_cli 1.26.1 → 1.28.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.
Files changed (39) hide show
  1. package/README.md +89 -42
  2. package/assets/desktop_panel.png +0 -0
  3. package/assets/home.png +0 -0
  4. package/dist/cli/promptToolkit.d.ts.map +1 -1
  5. package/dist/cli/promptToolkit.js +2 -0
  6. package/dist/cli/promptToolkit.js.map +1 -1
  7. package/dist/cli/shortcuts.d.ts.map +1 -1
  8. package/dist/cli/shortcuts.js +13 -0
  9. package/dist/cli/shortcuts.js.map +1 -1
  10. package/dist/core/config.js +1 -1
  11. package/dist/core/config.js.map +1 -1
  12. package/dist/core/updater.d.ts +1 -1
  13. package/dist/core/updater.js +1 -1
  14. package/dist/desktop/public/app.js +699 -0
  15. package/dist/desktop/public/index.html +338 -0
  16. package/dist/desktop/public/style.css +962 -0
  17. package/dist/desktop/server.d.ts +11 -0
  18. package/dist/desktop/server.d.ts.map +1 -0
  19. package/dist/desktop/server.js +361 -0
  20. package/dist/desktop/server.js.map +1 -0
  21. package/dist/index.js +30 -2
  22. package/dist/index.js.map +1 -1
  23. package/dist/memory/episodic.d.ts +1 -0
  24. package/dist/memory/episodic.d.ts.map +1 -1
  25. package/dist/memory/episodic.js +3 -0
  26. package/dist/memory/episodic.js.map +1 -1
  27. package/dist/memory/manager.d.ts +6 -0
  28. package/dist/memory/manager.d.ts.map +1 -1
  29. package/dist/memory/manager.js +8 -0
  30. package/dist/memory/manager.js.map +1 -1
  31. package/dist/mobile/public/app.js +449 -0
  32. package/dist/mobile/public/index.html +287 -0
  33. package/dist/mobile/public/manifest.json +16 -0
  34. package/dist/mobile/public/style.css +589 -0
  35. package/dist/mobile/server.d.ts +14 -0
  36. package/dist/mobile/server.d.ts.map +1 -0
  37. package/dist/mobile/server.js +232 -0
  38. package/dist/mobile/server.js.map +1 -0
  39. package/package.json +5 -2
@@ -0,0 +1,699 @@
1
+ // ANTRI Desktop Control Plane Client Engine
2
+
3
+ let currentConfig = null;
4
+ let activeTab = 'chat';
5
+ let attachedFiles = [];
6
+ let availableCommands = [];
7
+ let activePaletteMatches = [];
8
+ let paletteSelectedIndex = 0;
9
+ let activePaletteMode = null; // 'slash' | 'file' | null
10
+
11
+ // Initialize on Load
12
+ document.addEventListener('DOMContentLoaded', async () => {
13
+ await loadStatus();
14
+ await loadCommands();
15
+ await loadProfiles();
16
+ await loadSkills();
17
+ await loadMemory();
18
+ });
19
+
20
+ // Load System Status & Config
21
+ async function loadStatus() {
22
+ try {
23
+ const res = await fetch('/api/status');
24
+ const data = await res.json();
25
+ currentConfig = data.config;
26
+
27
+ // Update Mode buttons
28
+ switchMode(currentConfig.mode || 'vibe', false);
29
+
30
+ // Update Perms badge
31
+ updatePermsBadge(currentConfig.alwaysAllow);
32
+
33
+ // Update Provider selector
34
+ const provSelect = document.getElementById('select-provider');
35
+ if (provSelect) provSelect.value = currentConfig.provider;
36
+
37
+ await loadModels();
38
+ } catch (err) {
39
+ console.error('Failed to load status:', err);
40
+ }
41
+ }
42
+
43
+ // Load Slash Commands for Prompt Toolkit
44
+ async function loadCommands() {
45
+ try {
46
+ const res = await fetch('/api/commands');
47
+ const data = await res.json();
48
+ availableCommands = data.commands || [];
49
+ } catch (err) {
50
+ console.error('Failed to load commands:', err);
51
+ }
52
+ }
53
+
54
+ // Load Models for Provider
55
+ async function loadModels() {
56
+ try {
57
+ const res = await fetch('/api/models');
58
+ const data = await res.json();
59
+ const modelSelect = document.getElementById('select-model');
60
+ modelSelect.innerHTML = '';
61
+
62
+ data.models.forEach((m) => {
63
+ const opt = document.createElement('option');
64
+ opt.value = m.id;
65
+ opt.textContent = `${m.name} (${m.category})`;
66
+ if (m.id === currentConfig.model) opt.selected = true;
67
+ modelSelect.appendChild(opt);
68
+ });
69
+ } catch (err) {
70
+ console.error('Failed to load models:', err);
71
+ }
72
+ }
73
+
74
+ // Switch Mode (Plan / Vibe)
75
+ async function switchMode(mode, triggerSave = true) {
76
+ const btnVibe = document.getElementById('btn-mode-vibe');
77
+ const btnPlan = document.getElementById('btn-mode-plan');
78
+
79
+ if (mode === 'plan') {
80
+ btnPlan.classList.add('active');
81
+ btnVibe.classList.remove('active');
82
+ } else {
83
+ btnVibe.classList.add('active');
84
+ btnPlan.classList.remove('active');
85
+ }
86
+
87
+ if (triggerSave && currentConfig) {
88
+ currentConfig.mode = mode;
89
+ await fetch('/api/config', {
90
+ method: 'POST',
91
+ headers: { 'Content-Type': 'application/json' },
92
+ body: JSON.stringify({ mode }),
93
+ });
94
+ }
95
+ }
96
+
97
+ // Toggle Always-Allow Permissions
98
+ async function toggleAlwaysAllow() {
99
+ if (!currentConfig) return;
100
+ const next = !currentConfig.alwaysAllow;
101
+ currentConfig.alwaysAllow = next;
102
+ updatePermsBadge(next);
103
+
104
+ await fetch('/api/config', {
105
+ method: 'POST',
106
+ headers: { 'Content-Type': 'application/json' },
107
+ body: JSON.stringify({ alwaysAllow: next }),
108
+ });
109
+ }
110
+
111
+ function updatePermsBadge(alwaysAllow) {
112
+ const badge = document.getElementById('perms-text');
113
+ if (alwaysAllow) {
114
+ badge.textContent = 'Always-Allow';
115
+ } else {
116
+ badge.textContent = 'Ask-First';
117
+ }
118
+ }
119
+
120
+ // Provider & Model Handlers
121
+ async function onProviderChange(provider) {
122
+ await fetch('/api/config', {
123
+ method: 'POST',
124
+ headers: { 'Content-Type': 'application/json' },
125
+ body: JSON.stringify({ provider }),
126
+ });
127
+ await loadStatus();
128
+ }
129
+
130
+ async function onModelChange(model) {
131
+ await fetch('/api/config', {
132
+ method: 'POST',
133
+ headers: { 'Content-Type': 'application/json' },
134
+ body: JSON.stringify({ model }),
135
+ });
136
+ }
137
+
138
+ // Tab Switching
139
+ function showTab(tabName) {
140
+ activeTab = tabName;
141
+ document.querySelectorAll('.tab-panel').forEach((el) => el.classList.remove('active'));
142
+ document.querySelectorAll('.nav-item').forEach((el) => el.classList.remove('active'));
143
+
144
+ const targetPanel = document.getElementById(`tab-${tabName}`);
145
+ if (targetPanel) targetPanel.classList.add('active');
146
+
147
+ const navIndex = ['chat', 'dialectic', 'goal', 'profiles', 'skills', 'memory'].indexOf(tabName);
148
+ const navButtons = document.querySelectorAll('.nav-item');
149
+ if (navButtons[navIndex]) navButtons[navIndex].classList.add('active');
150
+ }
151
+
152
+ // File Upload Handler (Images & Files)
153
+ async function handleFileSelected(event) {
154
+ const files = event.target.files;
155
+ if (!files || files.length === 0) return;
156
+
157
+ for (const file of files) {
158
+ const reader = new FileReader();
159
+ reader.onload = async (e) => {
160
+ const data = e.target.result;
161
+ try {
162
+ const res = await fetch('/api/upload', {
163
+ method: 'POST',
164
+ headers: { 'Content-Type': 'application/json' },
165
+ body: JSON.stringify({
166
+ fileName: file.name,
167
+ fileType: file.type || 'text/plain',
168
+ data,
169
+ }),
170
+ });
171
+ const uploadRes = await res.json();
172
+ if (uploadRes.success) {
173
+ attachedFiles.push({
174
+ name: file.name,
175
+ path: uploadRes.filePath,
176
+ isImage: uploadRes.isImage,
177
+ dataUrl: uploadRes.isImage ? data : null,
178
+ });
179
+ renderAttachmentChips();
180
+ }
181
+ } catch (err) {
182
+ console.error('File upload failed:', err);
183
+ }
184
+ };
185
+ if (file.type.startsWith('image/')) {
186
+ reader.readAsDataURL(file);
187
+ } else {
188
+ reader.readAsText(file);
189
+ }
190
+ }
191
+
192
+ // Reset input
193
+ event.target.value = '';
194
+ }
195
+
196
+ function renderAttachmentChips() {
197
+ const tray = document.getElementById('attachment-preview-tray');
198
+ if (!tray) return;
199
+
200
+ if (attachedFiles.length === 0) {
201
+ tray.classList.add('hidden');
202
+ tray.innerHTML = '';
203
+ return;
204
+ }
205
+
206
+ tray.classList.remove('hidden');
207
+ tray.innerHTML = '';
208
+
209
+ attachedFiles.forEach((file, index) => {
210
+ const chip = document.createElement('div');
211
+ chip.className = 'attachment-chip';
212
+ if (file.isImage && file.dataUrl) {
213
+ chip.innerHTML = `
214
+ <img src="${file.dataUrl}" alt="preview" />
215
+ <span>${file.name}</span>
216
+ <button class="remove-chip-btn" onclick="removeAttachment(${index})">×</button>
217
+ `;
218
+ } else {
219
+ chip.innerHTML = `
220
+ <span>${file.name}</span>
221
+ <button class="remove-chip-btn" onclick="removeAttachment(${index})">×</button>
222
+ `;
223
+ }
224
+ tray.appendChild(chip);
225
+ });
226
+ }
227
+
228
+ function removeAttachment(index) {
229
+ attachedFiles.splice(index, 1);
230
+ renderAttachmentChips();
231
+ }
232
+
233
+ // Prompt Toolkit Text & Key Handler
234
+ async function handleInputText(event) {
235
+ const val = event.target.value;
236
+ const cursorPos = event.target.selectionStart;
237
+
238
+ // 1. Slash command mode
239
+ if (val.startsWith('/')) {
240
+ const query = val.toLowerCase();
241
+ activePaletteMatches = availableCommands.filter((cmd) => {
242
+ const baseName = cmd.name.split(' ')[0].toLowerCase();
243
+ return baseName.startsWith(query) || cmd.name.toLowerCase().startsWith(query);
244
+ });
245
+ activePaletteMode = 'slash';
246
+ renderPalette('Commands', activePaletteMatches);
247
+ return;
248
+ }
249
+
250
+ // 2. Attachment file mode (@)
251
+ const lastAt = val.lastIndexOf('@', cursorPos - 1);
252
+ if (lastAt !== -1 && (lastAt === 0 || val[lastAt - 1] === ' ')) {
253
+ const query = val.slice(lastAt + 1, cursorPos);
254
+ if (!query.includes(' ')) {
255
+ try {
256
+ const res = await fetch(`/api/files?query=${encodeURIComponent(query)}`);
257
+ const data = await res.json();
258
+ activePaletteMatches = data.items.map((item) => ({
259
+ name: item.name,
260
+ description: item.isDirectory ? 'Directory' : item.relativePath,
261
+ relativePath: item.relativePath,
262
+ isDirectory: item.isDirectory,
263
+ }));
264
+ activePaletteMode = 'file';
265
+ renderPalette(`Files: ${data.currentDir}`, activePaletteMatches);
266
+ return;
267
+ } catch (e) {}
268
+ }
269
+ }
270
+
271
+ hidePalette();
272
+ }
273
+
274
+ function renderPalette(title, items) {
275
+ const palette = document.getElementById('prompt-toolkit-palette');
276
+ const header = document.getElementById('palette-header');
277
+ const list = document.getElementById('palette-list');
278
+
279
+ if (!palette || !items || items.length === 0) {
280
+ hidePalette();
281
+ return;
282
+ }
283
+
284
+ header.textContent = title;
285
+ list.innerHTML = '';
286
+ paletteSelectedIndex = Math.min(paletteSelectedIndex, items.length - 1);
287
+ if (paletteSelectedIndex < 0) paletteSelectedIndex = 0;
288
+
289
+ items.slice(0, 10).forEach((item, idx) => {
290
+ const el = document.createElement('div');
291
+ el.className = `palette-item ${idx === paletteSelectedIndex ? 'active' : ''}`;
292
+ el.innerHTML = `
293
+ <span class="palette-name">${item.name}</span>
294
+ <span class="palette-desc">${item.description || ''}</span>
295
+ ${item.isDirectory ? '<span class="palette-tag">dir</span>' : ''}
296
+ `;
297
+ el.onclick = () => selectPaletteItem(idx);
298
+ list.appendChild(el);
299
+ });
300
+
301
+ palette.classList.remove('hidden');
302
+ }
303
+
304
+ function hidePalette() {
305
+ const palette = document.getElementById('prompt-toolkit-palette');
306
+ if (palette) palette.classList.add('hidden');
307
+ activePaletteMode = null;
308
+ activePaletteMatches = [];
309
+ paletteSelectedIndex = 0;
310
+ }
311
+
312
+ function selectPaletteItem(index) {
313
+ const item = activePaletteMatches[index];
314
+ if (!item) return;
315
+
316
+ const input = document.getElementById('prompt-input');
317
+
318
+ if (activePaletteMode === 'slash') {
319
+ const rawCmd = item.name.split(' ')[0];
320
+ input.value = rawCmd + ' ';
321
+ hidePalette();
322
+ input.focus();
323
+ } else if (activePaletteMode === 'file') {
324
+ const val = input.value;
325
+ const cursorPos = input.selectionStart;
326
+ const lastAt = val.lastIndexOf('@', cursorPos - 1);
327
+ if (lastAt !== -1) {
328
+ input.value = val.slice(0, lastAt) + '@' + item.relativePath + ' ' + val.slice(cursorPos);
329
+ }
330
+ hidePalette();
331
+ input.focus();
332
+ }
333
+ }
334
+
335
+ function handleInputKey(event) {
336
+ const palette = document.getElementById('prompt-toolkit-palette');
337
+ const isPaletteVisible = palette && !palette.classList.contains('hidden');
338
+
339
+ if (isPaletteVisible && activePaletteMatches.length > 0) {
340
+ if (event.key === 'ArrowUp') {
341
+ event.preventDefault();
342
+ paletteSelectedIndex = (paletteSelectedIndex - 1 + activePaletteMatches.length) % activePaletteMatches.length;
343
+ renderPalette(document.getElementById('palette-header').textContent, activePaletteMatches);
344
+ return;
345
+ }
346
+ if (event.key === 'ArrowDown') {
347
+ event.preventDefault();
348
+ paletteSelectedIndex = (paletteSelectedIndex + 1) % activePaletteMatches.length;
349
+ renderPalette(document.getElementById('palette-header').textContent, activePaletteMatches);
350
+ return;
351
+ }
352
+ if (event.key === 'Tab' || event.key === 'Enter') {
353
+ if (!event.ctrlKey && !event.metaKey) {
354
+ event.preventDefault();
355
+ selectPaletteItem(paletteSelectedIndex);
356
+ return;
357
+ }
358
+ }
359
+ if (event.key === 'Escape') {
360
+ hidePalette();
361
+ return;
362
+ }
363
+ }
364
+
365
+ // Ctrl + Enter to submit prompt
366
+ if (event.key === 'Enter' && (event.ctrlKey || event.metaKey)) {
367
+ event.preventDefault();
368
+ submitPrompt();
369
+ }
370
+ }
371
+
372
+ // Chat Prompt Submission with SSE Streaming
373
+ async function submitPrompt() {
374
+ const input = document.getElementById('prompt-input');
375
+ let prompt = input.value.trim();
376
+
377
+ // Attach any uploaded files to prompt
378
+ if (attachedFiles.length > 0) {
379
+ const attachmentsText = attachedFiles.map((f) => `\n[Attached File: ${f.name} (${f.path})]`).join('');
380
+ prompt = prompt + '\n' + attachmentsText;
381
+ attachedFiles = [];
382
+ renderAttachmentChips();
383
+ }
384
+
385
+ if (!prompt) return;
386
+
387
+ input.value = '';
388
+ hidePalette();
389
+
390
+ // Intercept /debate or /goal inside chat
391
+ if (prompt.startsWith('/debate')) {
392
+ showTab('dialectic');
393
+ document.getElementById('debate-query-input').value = prompt.replace('/debate', '').trim();
394
+ startDebate();
395
+ return;
396
+ }
397
+ if (prompt.startsWith('/goal') || prompt.startsWith('/loop')) {
398
+ showTab('goal');
399
+ document.getElementById('goal-objective-input').value = prompt.replace(/^\/(goal|loop)/, '').trim();
400
+ startGoalLoop();
401
+ return;
402
+ }
403
+
404
+ appendMessage('user', prompt);
405
+
406
+ const assistantMsgEl = appendMessage('assistant', '');
407
+ const contentEl = assistantMsgEl.querySelector('.msg-content');
408
+
409
+ const sendBtn = document.getElementById('send-btn');
410
+ sendBtn.disabled = true;
411
+ sendBtn.textContent = 'Thinking...';
412
+
413
+ try {
414
+ const response = await fetch('/api/chat', {
415
+ method: 'POST',
416
+ headers: { 'Content-Type': 'application/json' },
417
+ body: JSON.stringify({ prompt }),
418
+ });
419
+
420
+ const reader = response.body.getReader();
421
+ const decoder = new TextDecoder();
422
+ let accumulated = '';
423
+
424
+ while (true) {
425
+ const { done, value } = await reader.read();
426
+ if (done) break;
427
+
428
+ const chunk = decoder.decode(value);
429
+ const lines = chunk.split('\n');
430
+
431
+ for (const line of lines) {
432
+ if (line.startsWith('data: ')) {
433
+ try {
434
+ const data = JSON.parse(line.slice(6));
435
+ if (data.token) {
436
+ accumulated += data.token;
437
+ contentEl.textContent = accumulated;
438
+ scrollToBottom();
439
+ } else if (data.name && data.arguments) {
440
+ // Tool call badge
441
+ const toolBadge = document.createElement('div');
442
+ toolBadge.className = 'tool-badge-pill';
443
+ toolBadge.textContent = `Tool: ${data.name}`;
444
+ assistantMsgEl.insertBefore(toolBadge, contentEl);
445
+ }
446
+ } catch (e) {}
447
+ }
448
+ }
449
+ }
450
+ } catch (err) {
451
+ contentEl.textContent = `Error: ${err.message}`;
452
+ } finally {
453
+ sendBtn.disabled = false;
454
+ sendBtn.innerHTML = '<span>Send</span>';
455
+ }
456
+ }
457
+
458
+ function appendMessage(role, text) {
459
+ const container = document.getElementById('chat-messages');
460
+ const row = document.createElement('div');
461
+ row.className = `msg-row ${role}`;
462
+ const content = document.createElement('div');
463
+ content.className = 'msg-content';
464
+ content.textContent = text;
465
+ row.appendChild(content);
466
+ container.appendChild(row);
467
+ scrollToBottom();
468
+ return row;
469
+ }
470
+
471
+ function scrollToBottom() {
472
+ const container = document.getElementById('chat-messages');
473
+ container.scrollTop = container.scrollHeight;
474
+ }
475
+
476
+ function setPrompt(text) {
477
+ const input = document.getElementById('prompt-input');
478
+ input.value = text;
479
+ input.focus();
480
+ }
481
+
482
+ // Dialectic Debate Runner
483
+ async function startDebate() {
484
+ const input = document.getElementById('debate-query-input');
485
+ const query = input.value.trim();
486
+ if (!query) return;
487
+
488
+ const depth = document.getElementById('debate-depth-select').value;
489
+
490
+ document.getElementById('dialectic-thesis').textContent = 'Generating initial thesis & hypothesis...';
491
+ document.getElementById('dialectic-antithesis').textContent = 'Awaiting thesis to challenge assumptions...';
492
+ document.getElementById('dialectic-verification').textContent = 'Researcher standby for fact-checking...';
493
+ document.getElementById('dialectic-synthesis').textContent = 'Synthesizer awaiting debate completion...';
494
+
495
+ try {
496
+ const res = await fetch('/api/debate', {
497
+ method: 'POST',
498
+ headers: { 'Content-Type': 'application/json' },
499
+ body: JSON.stringify({ query, depth }),
500
+ });
501
+
502
+ const reader = res.body.getReader();
503
+ const decoder = new TextDecoder();
504
+
505
+ while (true) {
506
+ const { done, value } = await reader.read();
507
+ if (done) break;
508
+
509
+ const chunk = decoder.decode(value);
510
+ const lines = chunk.split('\n');
511
+
512
+ for (const line of lines) {
513
+ if (line.startsWith('data: ')) {
514
+ try {
515
+ const data = JSON.parse(line.slice(6));
516
+ if (data.thesis) document.getElementById('dialectic-thesis').textContent = data.thesis;
517
+ if (data.antithesis) document.getElementById('dialectic-antithesis').textContent = data.antithesis;
518
+ if (data.verification) document.getElementById('dialectic-verification').textContent = data.verification;
519
+ if (data.synthesis) document.getElementById('dialectic-synthesis').textContent = data.synthesis;
520
+ } catch (e) {}
521
+ }
522
+ }
523
+ }
524
+ } catch (err) {
525
+ document.getElementById('dialectic-synthesis').textContent = `Debate error: ${err.message}`;
526
+ }
527
+ }
528
+
529
+ // Goal Loop Runner
530
+ async function startGoalLoop() {
531
+ const input = document.getElementById('goal-objective-input');
532
+ const objective = input.value.trim();
533
+ if (!objective) return;
534
+
535
+ document.getElementById('goal-stage-1-content').textContent = 'Stage 1 executing: Drafting initial plan and solution...';
536
+ document.getElementById('goal-stage-2-content').textContent = 'Stage 2 standby: Awaiting draft for adversarial critique...';
537
+ document.getElementById('goal-stage-3-content').textContent = 'Stage 3 standby: Awaiting synthesis for hardened output...';
538
+
539
+ try {
540
+ const res = await fetch('/api/goal', {
541
+ method: 'POST',
542
+ headers: { 'Content-Type': 'application/json' },
543
+ body: JSON.stringify({ objective }),
544
+ });
545
+
546
+ const reader = res.body.getReader();
547
+ const decoder = new TextDecoder();
548
+
549
+ while (true) {
550
+ const { done, value } = await reader.read();
551
+ if (done) break;
552
+
553
+ const chunk = decoder.decode(value);
554
+ const lines = chunk.split('\n');
555
+
556
+ for (const line of lines) {
557
+ if (line.startsWith('data: ')) {
558
+ try {
559
+ const data = JSON.parse(line.slice(6));
560
+ if (data.draft) document.getElementById('goal-stage-1-content').textContent = data.draft;
561
+ if (data.critique) document.getElementById('goal-stage-2-content').textContent = data.critique;
562
+ if (data.finalOutput) document.getElementById('goal-stage-3-content').textContent = data.finalOutput;
563
+ } catch (e) {}
564
+ }
565
+ }
566
+ }
567
+ } catch (err) {
568
+ document.getElementById('goal-stage-3-content').textContent = `Goal execution error: ${err.message}`;
569
+ }
570
+ }
571
+
572
+ // Profile Management
573
+ async function loadProfiles() {
574
+ try {
575
+ const res = await fetch('/api/profiles');
576
+ const data = await res.json();
577
+
578
+ const select = document.getElementById('select-profile');
579
+ const list = document.getElementById('profile-items-list');
580
+ select.innerHTML = '';
581
+ list.innerHTML = '';
582
+
583
+ data.profiles.forEach((p) => {
584
+ // Dropdown option
585
+ const opt = document.createElement('option');
586
+ opt.value = p.name;
587
+ opt.textContent = p.name;
588
+ if (p.name === data.activeName) opt.selected = true;
589
+ select.appendChild(opt);
590
+
591
+ // List button
592
+ const btn = document.createElement('button');
593
+ btn.className = `profile-item-btn ${p.name === data.activeName ? 'active' : ''}`;
594
+ btn.textContent = `${p.name}.md`;
595
+ btn.onclick = () => selectProfile(p.name);
596
+ list.appendChild(btn);
597
+ });
598
+
599
+ document.getElementById('active-profile-title').textContent = `${data.activeName}.md`;
600
+ document.getElementById('profile-editor').value = data.activeContent || '';
601
+ } catch (err) {
602
+ console.error('Failed to load profiles:', err);
603
+ }
604
+ }
605
+
606
+ async function selectProfile(name) {
607
+ await fetch('/api/profile/select', {
608
+ method: 'POST',
609
+ headers: { 'Content-Type': 'application/json' },
610
+ body: JSON.stringify({ name }),
611
+ });
612
+ await loadProfiles();
613
+ }
614
+
615
+ async function createProfile() {
616
+ const input = document.getElementById('new-profile-name');
617
+ const name = input.value.trim();
618
+ if (!name) return;
619
+
620
+ await fetch('/api/profile/create', {
621
+ method: 'POST',
622
+ headers: { 'Content-Type': 'application/json' },
623
+ body: JSON.stringify({ name }),
624
+ });
625
+
626
+ input.value = '';
627
+ await loadProfiles();
628
+ }
629
+
630
+ async function saveActiveProfile() {
631
+ const content = document.getElementById('profile-editor').value;
632
+ await fetch('/api/profile/save', {
633
+ method: 'POST',
634
+ headers: { 'Content-Type': 'application/json' },
635
+ body: JSON.stringify({ content }),
636
+ });
637
+ alert('Profile saved.');
638
+ }
639
+
640
+ async function onProfileChange(name) {
641
+ await selectProfile(name);
642
+ }
643
+
644
+ // Skills & Memory Loaders
645
+ async function loadSkills() {
646
+ try {
647
+ const res = await fetch('/api/skills');
648
+ const data = await res.json();
649
+ const grid = document.getElementById('skills-catalog-grid');
650
+ grid.innerHTML = '';
651
+
652
+ data.allTools.forEach((t) => {
653
+ const card = document.createElement('div');
654
+ card.className = 'skill-card';
655
+ card.innerHTML = `
656
+ <h4>${t.name}</h4>
657
+ <p>${t.description}</p>
658
+ <button class="skill-btn" onclick="testSkill('${t.name}')">Dry-Run Skill</button>
659
+ `;
660
+ grid.appendChild(card);
661
+ });
662
+ } catch (err) {
663
+ console.error('Failed to load skills:', err);
664
+ }
665
+ }
666
+
667
+ async function testSkill(skillName) {
668
+ try {
669
+ const res = await fetch('/api/skill/test', {
670
+ method: 'POST',
671
+ headers: { 'Content-Type': 'application/json' },
672
+ body: JSON.stringify({ skillName, args: {} }),
673
+ });
674
+ const data = await res.json();
675
+ alert(`Skill '${skillName}' Output:\n${data.output || 'Execution complete'}`);
676
+ } catch (err) {
677
+ alert(`Execution failed: ${err.message}`);
678
+ }
679
+ }
680
+
681
+ async function loadMemory() {
682
+ try {
683
+ const res = await fetch('/api/memory');
684
+ const data = await res.json();
685
+
686
+ const semanticList = document.getElementById('semantic-memory-list');
687
+ const episodicList = document.getElementById('episodic-memory-list');
688
+
689
+ semanticList.innerHTML = (data.semanticItems || [])
690
+ .map((item) => `<div style="margin-bottom: 0.75rem; border-bottom: 1px solid var(--border-light); padding-bottom: 0.5rem;"><b>[${item.category}]</b> ${item.text}</div>`)
691
+ .join('') || '<div>No semantic vectors stored yet.</div>';
692
+
693
+ episodicList.innerHTML = (data.episodes || [])
694
+ .map((ep) => `<div style="margin-bottom: 0.75rem; border-bottom: 1px solid var(--border-light); padding-bottom: 0.5rem;"><b>Query:</b> ${ep.query}<br/><span style="color: var(--text-tertiary);">${ep.response.slice(0, 100)}...</span></div>`)
695
+ .join('') || '<div>No episodes recorded in current session.</div>';
696
+ } catch (err) {
697
+ console.error('Failed to load memory:', err);
698
+ }
699
+ }