antri_cli 1.44.0 → 1.45.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 (49) hide show
  1. package/dist/cli/banner.d.ts.map +1 -1
  2. package/dist/cli/banner.js +16 -1
  3. package/dist/cli/banner.js.map +1 -1
  4. package/dist/cli/prompt.d.ts.map +1 -1
  5. package/dist/cli/prompt.js +8 -0
  6. package/dist/cli/prompt.js.map +1 -1
  7. package/dist/cli/shortcuts.d.ts.map +1 -1
  8. package/dist/cli/shortcuts.js +39 -16
  9. package/dist/cli/shortcuts.js.map +1 -1
  10. package/dist/cloud/auth.d.ts.map +1 -1
  11. package/dist/cloud/auth.js +1 -3
  12. package/dist/cloud/auth.js.map +1 -1
  13. package/dist/cloud/firestore.d.ts.map +1 -1
  14. package/dist/cloud/firestore.js +49 -16
  15. package/dist/cloud/firestore.js.map +1 -1
  16. package/dist/core/agent.d.ts +2 -2
  17. package/dist/core/agent.d.ts.map +1 -1
  18. package/dist/core/agent.js +49 -26
  19. package/dist/core/agent.js.map +1 -1
  20. package/dist/core/config.d.ts +1 -0
  21. package/dist/core/config.d.ts.map +1 -1
  22. package/dist/core/config.js +9 -4
  23. package/dist/core/config.js.map +1 -1
  24. package/dist/core/tools.d.ts.map +1 -1
  25. package/dist/core/tools.js +83 -15
  26. package/dist/core/tools.js.map +1 -1
  27. package/dist/core/updater.d.ts +1 -1
  28. package/dist/core/updater.js +1 -1
  29. package/dist/desktop/public/app.js +287 -31
  30. package/dist/desktop/public/index.html +55 -10
  31. package/dist/desktop/public/style.css +214 -28
  32. package/dist/desktop/server.d.ts.map +1 -1
  33. package/dist/desktop/server.js +60 -4
  34. package/dist/desktop/server.js.map +1 -1
  35. package/dist/memory/manager.d.ts.map +1 -1
  36. package/dist/memory/manager.js +4 -0
  37. package/dist/memory/manager.js.map +1 -1
  38. package/dist/mobile/server.d.ts.map +1 -1
  39. package/dist/mobile/server.js +8 -2
  40. package/dist/mobile/server.js.map +1 -1
  41. package/dist/profiles/profileManager.d.ts +17 -4
  42. package/dist/profiles/profileManager.d.ts.map +1 -1
  43. package/dist/profiles/profileManager.js +202 -12
  44. package/dist/profiles/profileManager.js.map +1 -1
  45. package/dist/skills/skillManager.d.ts +62 -0
  46. package/dist/skills/skillManager.d.ts.map +1 -0
  47. package/dist/skills/skillManager.js +645 -0
  48. package/dist/skills/skillManager.js.map +1 -0
  49. package/package.json +1 -1
@@ -8,6 +8,12 @@ let activePaletteMatches = [];
8
8
  let paletteSelectedIndex = 0;
9
9
  let activePaletteMode = null; // 'slash' | 'file' | null
10
10
 
11
+ // Skills State
12
+ let allSkillsList = [];
13
+ let selectedSkillId = null;
14
+ let currentSkillCategory = 'all';
15
+ let skillSearchQuery = '';
16
+
11
17
  // Initialize on Load
12
18
  document.addEventListener('DOMContentLoaded', async () => {
13
19
  await loadStatus();
@@ -17,6 +23,23 @@ document.addEventListener('DOMContentLoaded', async () => {
17
23
  await loadMemory();
18
24
  });
19
25
 
26
+ // Toast Notification
27
+ function showToast(message, isError = false) {
28
+ let toast = document.getElementById('antri-toast');
29
+ if (!toast) {
30
+ toast = document.createElement('div');
31
+ toast.id = 'antri-toast';
32
+ toast.className = 'antri-toast';
33
+ document.body.appendChild(toast);
34
+ }
35
+ toast.textContent = message;
36
+ toast.style.background = isError ? '#991b1b' : '#1c1917';
37
+ toast.classList.add('show');
38
+ setTimeout(() => {
39
+ toast.classList.remove('show');
40
+ }, 2500);
41
+ }
42
+
20
43
  // Load System Status & Config
21
44
  async function loadStatus() {
22
45
  try {
@@ -369,7 +392,7 @@ function handleInputKey(event) {
369
392
  }
370
393
  }
371
394
 
372
- // Chat Prompt Submission with SSE Streaming
395
+ // Chat Prompt Submission with Real-Time Word-by-Word Side-by-Side Streaming
373
396
  async function submitPrompt() {
374
397
  const input = document.getElementById('prompt-input');
375
398
  let prompt = input.value.trim();
@@ -408,7 +431,7 @@ async function submitPrompt() {
408
431
 
409
432
  const sendBtn = document.getElementById('send-btn');
410
433
  sendBtn.disabled = true;
411
- sendBtn.textContent = 'Thinking...';
434
+ sendBtn.textContent = 'Streaming...';
412
435
 
413
436
  try {
414
437
  const response = await fetch('/api/chat', {
@@ -425,7 +448,7 @@ async function submitPrompt() {
425
448
  const { done, value } = await reader.read();
426
449
  if (done) break;
427
450
 
428
- const chunk = decoder.decode(value);
451
+ const chunk = decoder.decode(value, { stream: true });
429
452
  const lines = chunk.split('\n');
430
453
 
431
454
  for (const line of lines) {
@@ -440,8 +463,9 @@ async function submitPrompt() {
440
463
  // Tool call badge
441
464
  const toolBadge = document.createElement('div');
442
465
  toolBadge.className = 'tool-badge-pill';
443
- toolBadge.textContent = `Tool: ${data.name}`;
466
+ toolBadge.textContent = `• Tool: ${data.name}`;
444
467
  assistantMsgEl.insertBefore(toolBadge, contentEl);
468
+ scrollToBottom();
445
469
  }
446
470
  } catch (e) {}
447
471
  }
@@ -569,7 +593,9 @@ async function startGoalLoop() {
569
593
  }
570
594
  }
571
595
 
572
- // Profile Management
596
+ // ==========================================
597
+ // Thinking Profile Management
598
+ // ==========================================
573
599
  async function loadProfiles() {
574
600
  try {
575
601
  const res = await fetch('/api/profiles');
@@ -625,6 +651,31 @@ async function createProfile() {
625
651
 
626
652
  input.value = '';
627
653
  await loadProfiles();
654
+ showToast(`Profile '${name}' created.`);
655
+ }
656
+
657
+ async function handleProfileImport(event) {
658
+ const files = event.target.files;
659
+ if (!files || files.length === 0) return;
660
+
661
+ for (const file of files) {
662
+ const reader = new FileReader();
663
+ reader.onload = async (e) => {
664
+ const content = e.target.result;
665
+ const res = await fetch('/api/profile/import', {
666
+ method: 'POST',
667
+ headers: { 'Content-Type': 'application/json' },
668
+ body: JSON.stringify({ name: file.name, content }),
669
+ });
670
+ const data = await res.json();
671
+ if (data.success) {
672
+ await loadProfiles();
673
+ showToast(`Profile '${file.name}' imported successfully.`);
674
+ }
675
+ };
676
+ reader.readAsText(file);
677
+ }
678
+ event.target.value = '';
628
679
  }
629
680
 
630
681
  async function saveActiveProfile() {
@@ -634,50 +685,255 @@ async function saveActiveProfile() {
634
685
  headers: { 'Content-Type': 'application/json' },
635
686
  body: JSON.stringify({ content }),
636
687
  });
637
- alert('Profile saved.');
688
+ showToast('Profile saved successfully.');
689
+ }
690
+
691
+ function exportActiveProfile() {
692
+ const activeTitle = document.getElementById('active-profile-title').textContent || 'profile.md';
693
+ const content = document.getElementById('profile-editor').value;
694
+ const blob = new Blob([content], { type: 'text/markdown;charset=utf-8;' });
695
+ const url = URL.createObjectURL(blob);
696
+ const link = document.createElement('a');
697
+ link.setAttribute('href', url);
698
+ link.setAttribute('download', activeTitle);
699
+ document.body.appendChild(link);
700
+ link.click();
701
+ document.body.removeChild(link);
702
+ }
703
+
704
+ async function deleteActiveProfile() {
705
+ const activeTitle = (document.getElementById('active-profile-title').textContent || '').replace('.md', '');
706
+ if (activeTitle === 'profile_1') {
707
+ alert('Cannot delete default profile_1.');
708
+ return;
709
+ }
710
+ if (!confirm(`Are you sure you want to delete profile '${activeTitle}.md'?`)) return;
711
+
712
+ const res = await fetch('/api/profile/delete', {
713
+ method: 'POST',
714
+ headers: { 'Content-Type': 'application/json' },
715
+ body: JSON.stringify({ name: activeTitle }),
716
+ });
717
+ const data = await res.json();
718
+ if (data.success) {
719
+ await loadProfiles();
720
+ showToast(`Profile '${activeTitle}' deleted.`);
721
+ }
638
722
  }
639
723
 
640
724
  async function onProfileChange(name) {
641
725
  await selectProfile(name);
642
726
  }
643
727
 
644
- // Skills & Memory Loaders
728
+ // ==========================================
729
+ // Markdown Skills Studio Management
730
+ // ==========================================
645
731
  async function loadSkills() {
646
732
  try {
647
733
  const res = await fetch('/api/skills');
648
734
  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
- });
735
+ allSkillsList = data.markdownSkills || [];
736
+ renderSkillList();
737
+
738
+ if (!selectedSkillId && allSkillsList.length > 0) {
739
+ selectSkill(allSkillsList[0].id);
740
+ }
662
741
  } catch (err) {
663
742
  console.error('Failed to load skills:', err);
664
743
  }
665
744
  }
666
745
 
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}`);
746
+ function setSkillCategory(category) {
747
+ currentSkillCategory = category;
748
+ document.querySelectorAll('.category-pill').forEach((btn) => {
749
+ btn.classList.toggle('active', btn.textContent.toLowerCase() === category.toLowerCase());
750
+ });
751
+ renderSkillList();
752
+ }
753
+
754
+ function filterSkills(query) {
755
+ skillSearchQuery = (query || '').toLowerCase().trim();
756
+ renderSkillList();
757
+ }
758
+
759
+ function renderSkillList() {
760
+ const listContainer = document.getElementById('skills-catalog-list');
761
+ if (!listContainer) return;
762
+ listContainer.innerHTML = '';
763
+
764
+ let filtered = allSkillsList;
765
+ if (currentSkillCategory !== 'all') {
766
+ if (currentSkillCategory === 'Core') {
767
+ filtered = filtered.filter((s) => s.isCore);
768
+ } else if (currentSkillCategory === 'Custom') {
769
+ filtered = filtered.filter((s) => !s.isCore);
770
+ } else {
771
+ filtered = filtered.filter((s) => s.category && s.category.toLowerCase() === currentSkillCategory.toLowerCase());
772
+ }
773
+ }
774
+
775
+ if (skillSearchQuery) {
776
+ filtered = filtered.filter(
777
+ (s) =>
778
+ s.name.toLowerCase().includes(skillSearchQuery) ||
779
+ s.description.toLowerCase().includes(skillSearchQuery) ||
780
+ (s.triggers && s.triggers.some((t) => t.includes(skillSearchQuery)))
781
+ );
782
+ }
783
+
784
+ if (filtered.length === 0) {
785
+ listContainer.innerHTML = '<div class="empty-state">No skills match the search criteria.</div>';
786
+ return;
678
787
  }
788
+
789
+ filtered.forEach((skill) => {
790
+ const card = document.createElement('div');
791
+ card.className = `skill-item-card ${skill.id === selectedSkillId ? 'active' : ''}`;
792
+ card.innerHTML = `
793
+ <div class="skill-item-header">
794
+ <span class="skill-item-name">${skill.name}</span>
795
+ <span class="skill-type-tag ${skill.isCore ? 'core' : 'custom'}">${skill.isCore ? 'Core' : 'Custom'}</span>
796
+ </div>
797
+ <div class="skill-item-desc">${skill.description}</div>
798
+ <div class="skill-item-footer">
799
+ <span class="skill-category-badge">${skill.category}</span>
800
+ <span class="skill-version-tag">v${skill.version}</span>
801
+ </div>
802
+ `;
803
+ card.onclick = () => selectSkill(skill.id);
804
+ listContainer.appendChild(card);
805
+ });
806
+ }
807
+
808
+ function selectSkill(skillId) {
809
+ selectedSkillId = skillId;
810
+ const skill = allSkillsList.find((s) => s.id === skillId);
811
+ if (!skill) return;
812
+
813
+ document.getElementById('active-skill-title').textContent = `${skill.name} (${skill.id}.md)`;
814
+ document.getElementById('active-skill-meta').textContent = `Category: ${skill.category} · Author: ${skill.author} · Version: ${skill.version} · Triggers: ${skill.triggers.join(', ') || 'Auto'}`;
815
+ document.getElementById('skill-editor').value = skill.content || skill.instructions;
816
+
817
+ // Toggle Delete button (allow delete only on custom skills)
818
+ const deleteBtn = document.getElementById('btn-delete-skill');
819
+ if (deleteBtn) {
820
+ deleteBtn.style.display = skill.isCore ? 'none' : 'inline-flex';
821
+ }
822
+
823
+ renderSkillList();
824
+ }
825
+
826
+ async function saveCurrentSkill() {
827
+ if (!selectedSkillId) return;
828
+ const content = document.getElementById('skill-editor').value;
829
+
830
+ const res = await fetch('/api/skill/save', {
831
+ method: 'POST',
832
+ headers: { 'Content-Type': 'application/json' },
833
+ body: JSON.stringify({ id: selectedSkillId, content }),
834
+ });
835
+ const data = await res.json();
836
+ if (data.success) {
837
+ await loadSkills();
838
+ selectSkill(selectedSkillId);
839
+ showToast('Skill markdown saved successfully.');
840
+ }
841
+ }
842
+
843
+ async function createNewSkillPrompt() {
844
+ const name = prompt('Enter name for the new skill (e.g., "fastapi_specialist"):');
845
+ if (!name) return;
846
+ const description = prompt('Enter a short description for what this skill does:', 'Specialist guidelines and heuristics.');
847
+
848
+ const res = await fetch('/api/skill/create', {
849
+ method: 'POST',
850
+ headers: { 'Content-Type': 'application/json' },
851
+ body: JSON.stringify({ name, description, category: 'Custom' }),
852
+ });
853
+ const data = await res.json();
854
+ if (data.success && data.skill) {
855
+ await loadSkills();
856
+ selectSkill(data.skill.id);
857
+ showToast(`Skill '${data.skill.name}' created!`);
858
+ }
859
+ }
860
+
861
+ async function handleSkillImport(event) {
862
+ const files = event.target.files;
863
+ if (!files || files.length === 0) return;
864
+
865
+ for (const file of files) {
866
+ const reader = new FileReader();
867
+ reader.onload = async (e) => {
868
+ const content = e.target.result;
869
+ const res = await fetch('/api/skill/import', {
870
+ method: 'POST',
871
+ headers: { 'Content-Type': 'application/json' },
872
+ body: JSON.stringify({ name: file.name, content }),
873
+ });
874
+ const data = await res.json();
875
+ if (data.success && data.skill) {
876
+ await loadSkills();
877
+ selectSkill(data.skill.id);
878
+ showToast(`Skill '${data.skill.name}' imported successfully.`);
879
+ }
880
+ };
881
+ reader.readAsText(file);
882
+ }
883
+ event.target.value = '';
884
+ }
885
+
886
+ function exportCurrentSkill() {
887
+ if (!selectedSkillId) return;
888
+ const skill = allSkillsList.find((s) => s.id === selectedSkillId);
889
+ const content = document.getElementById('skill-editor').value;
890
+ const filename = `${skill ? skill.id : 'skill'}.md`;
891
+
892
+ const blob = new Blob([content], { type: 'text/markdown;charset=utf-8;' });
893
+ const url = URL.createObjectURL(blob);
894
+ const link = document.createElement('a');
895
+ link.setAttribute('href', url);
896
+ link.setAttribute('download', filename);
897
+ document.body.appendChild(link);
898
+ link.click();
899
+ document.body.removeChild(link);
900
+ }
901
+
902
+ async function deleteCurrentSkill() {
903
+ if (!selectedSkillId) return;
904
+ const skill = allSkillsList.find((s) => s.id === selectedSkillId);
905
+ if (skill && skill.isCore) {
906
+ alert('Cannot delete built-in core skills.');
907
+ return;
908
+ }
909
+ if (!confirm(`Are you sure you want to delete skill '${skill ? skill.name : selectedSkillId}'?`)) return;
910
+
911
+ const res = await fetch('/api/skill/delete', {
912
+ method: 'POST',
913
+ headers: { 'Content-Type': 'application/json' },
914
+ body: JSON.stringify({ id: selectedSkillId }),
915
+ });
916
+ const data = await res.json();
917
+ if (data.success) {
918
+ selectedSkillId = null;
919
+ await loadSkills();
920
+ showToast('Skill deleted.');
921
+ }
922
+ }
923
+
924
+ function activateCurrentSkillInChat() {
925
+ if (!selectedSkillId) return;
926
+ const skill = allSkillsList.find((s) => s.id === selectedSkillId);
927
+ if (!skill) return;
928
+
929
+ showTab('chat');
930
+ const input = document.getElementById('prompt-input');
931
+ input.value = `[Apply Skill: ${skill.name}] `;
932
+ input.focus();
933
+ showToast(`Skill '${skill.name}' loaded into prompt.`);
679
934
  }
680
935
 
936
+ // Memory Loader
681
937
  async function loadMemory() {
682
938
  try {
683
939
  const res = await fetch('/api/memory');
@@ -15,7 +15,7 @@
15
15
  <header class="app-header">
16
16
  <div class="brand-zone">
17
17
  <span class="logo-mark">ANTRI</span>
18
- <span class="version-tag">v1.44.0</span>
18
+ <span class="version-tag">v1.45.0</span>
19
19
  </div>
20
20
 
21
21
  <!-- Mode Toggle -->
@@ -269,11 +269,13 @@
269
269
  <div class="panel-header">
270
270
  <div>
271
271
  <h2>Thinking Profile Studio</h2>
272
- <p>Manage user preferences, coding styles, and automatically extracted notes.</p>
272
+ <p>Manage user preferences, coding styles, and automatically extracted notes in <code>~/.antri/profiles/</code>.</p>
273
273
  </div>
274
274
  <div class="debate-controls">
275
+ <input type="file" id="profile-import-input" accept=".md,.txt" style="display:none;" onchange="handleProfileImport(event)" />
275
276
  <input type="text" id="new-profile-name" placeholder="New profile name (e.g. backend_architect)" />
276
- <button class="action-btn" onclick="createProfile()">Create Profile</button>
277
+ <button class="action-btn" onclick="createProfile()">+ Create</button>
278
+ <button class="action-btn-secondary" onclick="document.getElementById('profile-import-input').click()">📁 Import .md</button>
277
279
  </div>
278
280
  </div>
279
281
 
@@ -283,25 +285,68 @@
283
285
  </div>
284
286
  <div class="profile-editor-container">
285
287
  <div class="editor-header">
286
- <h3 id="active-profile-title">profile_1.md</h3>
287
- <button class="save-btn" onclick="saveActiveProfile()">Save Profile</button>
288
+ <div>
289
+ <h3 id="active-profile-title">profile_1.md</h3>
290
+ <span class="editor-subtitle">Editing active thinking profile instructions</span>
291
+ </div>
292
+ <div class="editor-action-group">
293
+ <button class="btn-sm-action" onclick="exportActiveProfile()">⬇️ Export</button>
294
+ <button class="btn-sm-action btn-danger" onclick="deleteActiveProfile()">🗑️ Delete</button>
295
+ <button class="save-btn" onclick="saveActiveProfile()">💾 Save Profile</button>
296
+ </div>
288
297
  </div>
289
298
  <textarea id="profile-editor" class="profile-textarea" placeholder="Profile markdown content..."></textarea>
290
299
  </div>
291
300
  </div>
292
301
  </section>
293
302
 
294
- <!-- TAB 5: Dynamic Skills -->
303
+ <!-- TAB 5: Markdown Skills Studio -->
295
304
  <section id="tab-skills" class="tab-panel">
296
305
  <div class="panel-header">
297
306
  <div>
298
- <h2>Dynamic Skill Studio</h2>
299
- <p>Tools synthesized autonomously by the agent in <code>~/.agent-cli/skills/</code>.</p>
307
+ <h2>Markdown (.md) Skill Studio</h2>
308
+ <p>Specialist capability directives loaded from <code>~/.antri/skills/</code>. Add or import new skills as Markdown files.</p>
309
+ </div>
310
+ <div class="debate-controls">
311
+ <input type="file" id="skill-import-input" accept=".md,.txt" style="display:none;" onchange="handleSkillImport(event)" />
312
+ <input type="text" id="skill-search-input" placeholder="Search skills by name, category, or trigger..." oninput="filterSkills(this.value)" />
313
+ <button class="action-btn" onclick="createNewSkillPrompt()">+ Create Skill</button>
314
+ <button class="action-btn-secondary" onclick="document.getElementById('skill-import-input').click()">📁 Import .md</button>
300
315
  </div>
301
316
  </div>
302
317
 
303
- <div class="skills-grid" id="skills-catalog-grid">
304
- <!-- Populated dynamically -->
318
+ <div class="skill-studio-layout">
319
+ <!-- Left: Skills List & Filters -->
320
+ <div class="skill-list-sidebar">
321
+ <div class="skill-category-filters" id="skill-category-filters">
322
+ <button class="category-pill active" onclick="setSkillCategory('all')">All</button>
323
+ <button class="category-pill" onclick="setSkillCategory('Core')">Core</button>
324
+ <button class="category-pill" onclick="setSkillCategory('Custom')">Custom</button>
325
+ <button class="category-pill" onclick="setSkillCategory('Engineering')">Engineering</button>
326
+ <button class="category-pill" onclick="setSkillCategory('Architecture')">Architecture</button>
327
+ <button class="category-pill" onclick="setSkillCategory('Security')">Security</button>
328
+ </div>
329
+ <div class="skill-items-container" id="skills-catalog-list">
330
+ <!-- Populated dynamically -->
331
+ </div>
332
+ </div>
333
+
334
+ <!-- Right: Live Skill Markdown Editor -->
335
+ <div class="skill-editor-container">
336
+ <div class="editor-header">
337
+ <div>
338
+ <h3 id="active-skill-title">Select a Skill</h3>
339
+ <span id="active-skill-meta" class="editor-subtitle">Click any skill on the left to inspect or edit instructions</span>
340
+ </div>
341
+ <div class="editor-action-group">
342
+ <button class="btn-sm-action" id="btn-activate-skill" onclick="activateCurrentSkillInChat()">⚡ Activate in Chat</button>
343
+ <button class="btn-sm-action" id="btn-export-skill" onclick="exportCurrentSkill()">⬇️ Export</button>
344
+ <button class="btn-sm-action btn-danger" id="btn-delete-skill" onclick="deleteCurrentSkill()">🗑️ Delete</button>
345
+ <button class="save-btn" id="btn-save-skill" onclick="saveCurrentSkill()">💾 Save Skill</button>
346
+ </div>
347
+ </div>
348
+ <textarea id="skill-editor" class="profile-textarea" placeholder="Skill markdown content (# Skill Title, instructions, heuristics...)" spellcheck="false"></textarea>
349
+ </div>
305
350
  </div>
306
351
  </section>
307
352