antri_cli 1.44.0 → 1.46.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 (51) 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 +62 -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 +15 -6
  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.d.ts.map +1 -1
  29. package/dist/core/updater.js +1 -1
  30. package/dist/core/updater.js.map +1 -1
  31. package/dist/desktop/public/app.js +412 -31
  32. package/dist/desktop/public/index.html +92 -10
  33. package/dist/desktop/public/style.css +316 -28
  34. package/dist/desktop/server.d.ts.map +1 -1
  35. package/dist/desktop/server.js +84 -4
  36. package/dist/desktop/server.js.map +1 -1
  37. package/dist/memory/manager.d.ts.map +1 -1
  38. package/dist/memory/manager.js +4 -0
  39. package/dist/memory/manager.js.map +1 -1
  40. package/dist/mobile/server.d.ts.map +1 -1
  41. package/dist/mobile/server.js +8 -2
  42. package/dist/mobile/server.js.map +1 -1
  43. package/dist/profiles/profileManager.d.ts +17 -4
  44. package/dist/profiles/profileManager.d.ts.map +1 -1
  45. package/dist/profiles/profileManager.js +202 -12
  46. package/dist/profiles/profileManager.js.map +1 -1
  47. package/dist/skills/skillManager.d.ts +62 -0
  48. package/dist/skills/skillManager.d.ts.map +1 -0
  49. package/dist/skills/skillManager.js +645 -0
  50. package/dist/skills/skillManager.js.map +1 -0
  51. package/package.json +2 -2
@@ -8,8 +8,15 @@ 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 () => {
19
+ await checkAuthStatus();
13
20
  await loadStatus();
14
21
  await loadCommands();
15
22
  await loadProfiles();
@@ -17,6 +24,23 @@ document.addEventListener('DOMContentLoaded', async () => {
17
24
  await loadMemory();
18
25
  });
19
26
 
27
+ // Toast Notification
28
+ function showToast(message, isError = false) {
29
+ let toast = document.getElementById('antri-toast');
30
+ if (!toast) {
31
+ toast = document.createElement('div');
32
+ toast.id = 'antri-toast';
33
+ toast.className = 'antri-toast';
34
+ document.body.appendChild(toast);
35
+ }
36
+ toast.textContent = message;
37
+ toast.style.background = isError ? '#991b1b' : '#1c1917';
38
+ toast.classList.add('show');
39
+ setTimeout(() => {
40
+ toast.classList.remove('show');
41
+ }, 2500);
42
+ }
43
+
20
44
  // Load System Status & Config
21
45
  async function loadStatus() {
22
46
  try {
@@ -369,7 +393,7 @@ function handleInputKey(event) {
369
393
  }
370
394
  }
371
395
 
372
- // Chat Prompt Submission with SSE Streaming
396
+ // Chat Prompt Submission with Real-Time Word-by-Word Side-by-Side Streaming
373
397
  async function submitPrompt() {
374
398
  const input = document.getElementById('prompt-input');
375
399
  let prompt = input.value.trim();
@@ -408,7 +432,7 @@ async function submitPrompt() {
408
432
 
409
433
  const sendBtn = document.getElementById('send-btn');
410
434
  sendBtn.disabled = true;
411
- sendBtn.textContent = 'Thinking...';
435
+ sendBtn.textContent = 'Streaming...';
412
436
 
413
437
  try {
414
438
  const response = await fetch('/api/chat', {
@@ -425,7 +449,7 @@ async function submitPrompt() {
425
449
  const { done, value } = await reader.read();
426
450
  if (done) break;
427
451
 
428
- const chunk = decoder.decode(value);
452
+ const chunk = decoder.decode(value, { stream: true });
429
453
  const lines = chunk.split('\n');
430
454
 
431
455
  for (const line of lines) {
@@ -440,8 +464,9 @@ async function submitPrompt() {
440
464
  // Tool call badge
441
465
  const toolBadge = document.createElement('div');
442
466
  toolBadge.className = 'tool-badge-pill';
443
- toolBadge.textContent = `Tool: ${data.name}`;
467
+ toolBadge.textContent = `• Tool: ${data.name}`;
444
468
  assistantMsgEl.insertBefore(toolBadge, contentEl);
469
+ scrollToBottom();
445
470
  }
446
471
  } catch (e) {}
447
472
  }
@@ -569,7 +594,9 @@ async function startGoalLoop() {
569
594
  }
570
595
  }
571
596
 
572
- // Profile Management
597
+ // ==========================================
598
+ // Thinking Profile Management
599
+ // ==========================================
573
600
  async function loadProfiles() {
574
601
  try {
575
602
  const res = await fetch('/api/profiles');
@@ -625,6 +652,31 @@ async function createProfile() {
625
652
 
626
653
  input.value = '';
627
654
  await loadProfiles();
655
+ showToast(`Profile '${name}' created.`);
656
+ }
657
+
658
+ async function handleProfileImport(event) {
659
+ const files = event.target.files;
660
+ if (!files || files.length === 0) return;
661
+
662
+ for (const file of files) {
663
+ const reader = new FileReader();
664
+ reader.onload = async (e) => {
665
+ const content = e.target.result;
666
+ const res = await fetch('/api/profile/import', {
667
+ method: 'POST',
668
+ headers: { 'Content-Type': 'application/json' },
669
+ body: JSON.stringify({ name: file.name, content }),
670
+ });
671
+ const data = await res.json();
672
+ if (data.success) {
673
+ await loadProfiles();
674
+ showToast(`Profile '${file.name}' imported successfully.`);
675
+ }
676
+ };
677
+ reader.readAsText(file);
678
+ }
679
+ event.target.value = '';
628
680
  }
629
681
 
630
682
  async function saveActiveProfile() {
@@ -634,50 +686,286 @@ async function saveActiveProfile() {
634
686
  headers: { 'Content-Type': 'application/json' },
635
687
  body: JSON.stringify({ content }),
636
688
  });
637
- alert('Profile saved.');
689
+ showToast('Profile saved successfully.');
690
+ }
691
+
692
+ async function pushProfilesToCloud() {
693
+ try {
694
+ showToast('Pushing profiles to Google Cloud Firestore...');
695
+ const res = await fetch('/api/profile/push', { method: 'POST' });
696
+ const data = await res.json();
697
+ if (data.success) {
698
+ showToast(`Pushed ${data.count} profile(s) to Google Cloud Firestore.`);
699
+ } else {
700
+ showToast(`Push failed: ${data.error || 'Check network connection'}`, true);
701
+ }
702
+ } catch (err) {
703
+ showToast(`Push failed: ${err.message}`, true);
704
+ }
705
+ }
706
+
707
+ async function pullProfilesFromCloud() {
708
+ try {
709
+ showToast('Pulling profiles from Google Cloud Firestore...');
710
+ const res = await fetch('/api/profile/pull', { method: 'POST' });
711
+ const data = await res.json();
712
+ if (data.success) {
713
+ await loadProfiles();
714
+ showToast(`Pulled ${data.count} profile(s) from Google Cloud Firestore.`);
715
+ } else {
716
+ showToast(`Pull failed: ${data.error || 'Check network connection'}`, true);
717
+ }
718
+ } catch (err) {
719
+ showToast(`Pull failed: ${err.message}`, true);
720
+ }
721
+ }
722
+
723
+ function exportActiveProfile() {
724
+ const activeTitle = document.getElementById('active-profile-title').textContent || 'profile.md';
725
+ const content = document.getElementById('profile-editor').value;
726
+ const blob = new Blob([content], { type: 'text/markdown;charset=utf-8;' });
727
+ const url = URL.createObjectURL(blob);
728
+ const link = document.createElement('a');
729
+ link.setAttribute('href', url);
730
+ link.setAttribute('download', activeTitle);
731
+ document.body.appendChild(link);
732
+ link.click();
733
+ document.body.removeChild(link);
734
+ }
735
+
736
+ async function deleteActiveProfile() {
737
+ const activeTitle = (document.getElementById('active-profile-title').textContent || '').replace('.md', '');
738
+ if (activeTitle === 'profile_1') {
739
+ alert('Cannot delete default profile_1.');
740
+ return;
741
+ }
742
+ if (!confirm(`Are you sure you want to delete profile '${activeTitle}.md'?`)) return;
743
+
744
+ const res = await fetch('/api/profile/delete', {
745
+ method: 'POST',
746
+ headers: { 'Content-Type': 'application/json' },
747
+ body: JSON.stringify({ name: activeTitle }),
748
+ });
749
+ const data = await res.json();
750
+ if (data.success) {
751
+ await loadProfiles();
752
+ showToast(`Profile '${activeTitle}' deleted.`);
753
+ }
638
754
  }
639
755
 
640
756
  async function onProfileChange(name) {
641
757
  await selectProfile(name);
642
758
  }
643
759
 
644
- // Skills & Memory Loaders
760
+ // ==========================================
761
+ // Markdown Skills Studio Management
762
+ // ==========================================
645
763
  async function loadSkills() {
646
764
  try {
647
765
  const res = await fetch('/api/skills');
648
766
  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
- });
767
+ allSkillsList = data.markdownSkills || [];
768
+ renderSkillList();
769
+
770
+ if (!selectedSkillId && allSkillsList.length > 0) {
771
+ selectSkill(allSkillsList[0].id);
772
+ }
662
773
  } catch (err) {
663
774
  console.error('Failed to load skills:', err);
664
775
  }
665
776
  }
666
777
 
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}`);
778
+ function setSkillCategory(category) {
779
+ currentSkillCategory = category;
780
+ document.querySelectorAll('.category-pill').forEach((btn) => {
781
+ btn.classList.toggle('active', btn.textContent.toLowerCase() === category.toLowerCase());
782
+ });
783
+ renderSkillList();
784
+ }
785
+
786
+ function filterSkills(query) {
787
+ skillSearchQuery = (query || '').toLowerCase().trim();
788
+ renderSkillList();
789
+ }
790
+
791
+ function renderSkillList() {
792
+ const listContainer = document.getElementById('skills-catalog-list');
793
+ if (!listContainer) return;
794
+ listContainer.innerHTML = '';
795
+
796
+ let filtered = allSkillsList;
797
+ if (currentSkillCategory !== 'all') {
798
+ if (currentSkillCategory === 'Core') {
799
+ filtered = filtered.filter((s) => s.isCore);
800
+ } else if (currentSkillCategory === 'Custom') {
801
+ filtered = filtered.filter((s) => !s.isCore);
802
+ } else {
803
+ filtered = filtered.filter((s) => s.category && s.category.toLowerCase() === currentSkillCategory.toLowerCase());
804
+ }
805
+ }
806
+
807
+ if (skillSearchQuery) {
808
+ filtered = filtered.filter(
809
+ (s) =>
810
+ s.name.toLowerCase().includes(skillSearchQuery) ||
811
+ s.description.toLowerCase().includes(skillSearchQuery) ||
812
+ (s.triggers && s.triggers.some((t) => t.includes(skillSearchQuery)))
813
+ );
814
+ }
815
+
816
+ if (filtered.length === 0) {
817
+ listContainer.innerHTML = '<div class="empty-state">No skills match the search criteria.</div>';
818
+ return;
819
+ }
820
+
821
+ filtered.forEach((skill) => {
822
+ const card = document.createElement('div');
823
+ card.className = `skill-item-card ${skill.id === selectedSkillId ? 'active' : ''}`;
824
+ card.innerHTML = `
825
+ <div class="skill-item-header">
826
+ <span class="skill-item-name">${skill.name}</span>
827
+ <span class="skill-type-tag ${skill.isCore ? 'core' : 'custom'}">${skill.isCore ? 'Core' : 'Custom'}</span>
828
+ </div>
829
+ <div class="skill-item-desc">${skill.description}</div>
830
+ <div class="skill-item-footer">
831
+ <span class="skill-category-badge">${skill.category}</span>
832
+ <span class="skill-version-tag">v${skill.version}</span>
833
+ </div>
834
+ `;
835
+ card.onclick = () => selectSkill(skill.id);
836
+ listContainer.appendChild(card);
837
+ });
838
+ }
839
+
840
+ function selectSkill(skillId) {
841
+ selectedSkillId = skillId;
842
+ const skill = allSkillsList.find((s) => s.id === skillId);
843
+ if (!skill) return;
844
+
845
+ document.getElementById('active-skill-title').textContent = `${skill.name} (${skill.id}.md)`;
846
+ document.getElementById('active-skill-meta').textContent = `Category: ${skill.category} · Author: ${skill.author} · Version: ${skill.version} · Triggers: ${skill.triggers.join(', ') || 'Auto'}`;
847
+ document.getElementById('skill-editor').value = skill.content || skill.instructions;
848
+
849
+ // Toggle Delete button (allow delete only on custom skills)
850
+ const deleteBtn = document.getElementById('btn-delete-skill');
851
+ if (deleteBtn) {
852
+ deleteBtn.style.display = skill.isCore ? 'none' : 'inline-flex';
853
+ }
854
+
855
+ renderSkillList();
856
+ }
857
+
858
+ async function saveCurrentSkill() {
859
+ if (!selectedSkillId) return;
860
+ const content = document.getElementById('skill-editor').value;
861
+
862
+ const res = await fetch('/api/skill/save', {
863
+ method: 'POST',
864
+ headers: { 'Content-Type': 'application/json' },
865
+ body: JSON.stringify({ id: selectedSkillId, content }),
866
+ });
867
+ const data = await res.json();
868
+ if (data.success) {
869
+ await loadSkills();
870
+ selectSkill(selectedSkillId);
871
+ showToast('Skill markdown saved successfully.');
872
+ }
873
+ }
874
+
875
+ async function createNewSkillPrompt() {
876
+ const name = prompt('Enter name for the new skill (e.g., "fastapi_specialist"):');
877
+ if (!name) return;
878
+ const description = prompt('Enter a short description for what this skill does:', 'Specialist guidelines and heuristics.');
879
+
880
+ const res = await fetch('/api/skill/create', {
881
+ method: 'POST',
882
+ headers: { 'Content-Type': 'application/json' },
883
+ body: JSON.stringify({ name, description, category: 'Custom' }),
884
+ });
885
+ const data = await res.json();
886
+ if (data.success && data.skill) {
887
+ await loadSkills();
888
+ selectSkill(data.skill.id);
889
+ showToast(`Skill '${data.skill.name}' created!`);
678
890
  }
679
891
  }
680
892
 
893
+ async function handleSkillImport(event) {
894
+ const files = event.target.files;
895
+ if (!files || files.length === 0) return;
896
+
897
+ for (const file of files) {
898
+ const reader = new FileReader();
899
+ reader.onload = async (e) => {
900
+ const content = e.target.result;
901
+ const res = await fetch('/api/skill/import', {
902
+ method: 'POST',
903
+ headers: { 'Content-Type': 'application/json' },
904
+ body: JSON.stringify({ name: file.name, content }),
905
+ });
906
+ const data = await res.json();
907
+ if (data.success && data.skill) {
908
+ await loadSkills();
909
+ selectSkill(data.skill.id);
910
+ showToast(`Skill '${data.skill.name}' imported successfully.`);
911
+ }
912
+ };
913
+ reader.readAsText(file);
914
+ }
915
+ event.target.value = '';
916
+ }
917
+
918
+ function exportCurrentSkill() {
919
+ if (!selectedSkillId) return;
920
+ const skill = allSkillsList.find((s) => s.id === selectedSkillId);
921
+ const content = document.getElementById('skill-editor').value;
922
+ const filename = `${skill ? skill.id : 'skill'}.md`;
923
+
924
+ const blob = new Blob([content], { type: 'text/markdown;charset=utf-8;' });
925
+ const url = URL.createObjectURL(blob);
926
+ const link = document.createElement('a');
927
+ link.setAttribute('href', url);
928
+ link.setAttribute('download', filename);
929
+ document.body.appendChild(link);
930
+ link.click();
931
+ document.body.removeChild(link);
932
+ }
933
+
934
+ async function deleteCurrentSkill() {
935
+ if (!selectedSkillId) return;
936
+ const skill = allSkillsList.find((s) => s.id === selectedSkillId);
937
+ if (skill && skill.isCore) {
938
+ alert('Cannot delete built-in core skills.');
939
+ return;
940
+ }
941
+ if (!confirm(`Are you sure you want to delete skill '${skill ? skill.name : selectedSkillId}'?`)) return;
942
+
943
+ const res = await fetch('/api/skill/delete', {
944
+ method: 'POST',
945
+ headers: { 'Content-Type': 'application/json' },
946
+ body: JSON.stringify({ id: selectedSkillId }),
947
+ });
948
+ const data = await res.json();
949
+ if (data.success) {
950
+ selectedSkillId = null;
951
+ await loadSkills();
952
+ showToast('Skill deleted.');
953
+ }
954
+ }
955
+
956
+ function activateCurrentSkillInChat() {
957
+ if (!selectedSkillId) return;
958
+ const skill = allSkillsList.find((s) => s.id === selectedSkillId);
959
+ if (!skill) return;
960
+
961
+ showTab('chat');
962
+ const input = document.getElementById('prompt-input');
963
+ input.value = `[Apply Skill: ${skill.name}] `;
964
+ input.focus();
965
+ showToast(`Skill '${skill.name}' loaded into prompt.`);
966
+ }
967
+
968
+ // Memory Loader
681
969
  async function loadMemory() {
682
970
  try {
683
971
  const res = await fetch('/api/memory');
@@ -697,3 +985,96 @@ async function loadMemory() {
697
985
  console.error('Failed to load memory:', err);
698
986
  }
699
987
  }
988
+
989
+ // Authentication Helpers
990
+ let currentAuthUser = null;
991
+
992
+ async function checkAuthStatus() {
993
+ try {
994
+ const res = await fetch('/api/auth/status');
995
+ const data = await res.json();
996
+ const dot = document.getElementById('auth-status-dot');
997
+ const text = document.getElementById('auth-status-text');
998
+
999
+ if (data.isAuthenticated && data.user) {
1000
+ currentAuthUser = data.user;
1001
+ if (dot) dot.classList.add('logged-in');
1002
+ if (text) text.textContent = data.user.email.split('@')[0];
1003
+ } else {
1004
+ currentAuthUser = null;
1005
+ if (dot) dot.classList.remove('logged-in');
1006
+ if (text) text.textContent = 'Login';
1007
+ }
1008
+ } catch (e) {
1009
+ console.error('Failed to check auth status:', e);
1010
+ }
1011
+ }
1012
+
1013
+ function openAuthModal() {
1014
+ const modal = document.getElementById('auth-modal');
1015
+ const formView = document.getElementById('auth-form-view');
1016
+ const loggedView = document.getElementById('auth-logged-view');
1017
+ const emailText = document.getElementById('logged-user-email');
1018
+ const idText = document.getElementById('logged-user-id');
1019
+
1020
+ if (currentAuthUser) {
1021
+ if (formView) formView.style.display = 'none';
1022
+ if (loggedView) loggedView.style.display = 'block';
1023
+ if (emailText) emailText.textContent = currentAuthUser.email;
1024
+ if (idText) idText.textContent = `Cloud Partition: ${currentAuthUser.userId}`;
1025
+ } else {
1026
+ if (formView) formView.style.display = 'block';
1027
+ if (loggedView) loggedView.style.display = 'none';
1028
+ }
1029
+
1030
+ if (modal) modal.style.display = 'flex';
1031
+ }
1032
+
1033
+ function closeAuthModal() {
1034
+ const modal = document.getElementById('auth-modal');
1035
+ if (modal) modal.style.display = 'none';
1036
+ }
1037
+
1038
+ async function submitDesktopLogin() {
1039
+ const emailInput = document.getElementById('modal-email-input');
1040
+ const passInput = document.getElementById('modal-pass-input');
1041
+ const email = (emailInput ? emailInput.value : '').trim();
1042
+ const password = passInput ? passInput.value : '';
1043
+
1044
+ if (!email || !email.includes('@')) {
1045
+ alert('Please enter a valid email address.');
1046
+ return;
1047
+ }
1048
+
1049
+ try {
1050
+ const res = await fetch('/api/auth/login', {
1051
+ method: 'POST',
1052
+ headers: { 'Content-Type': 'application/json' },
1053
+ body: JSON.stringify({ email, password }),
1054
+ });
1055
+ const data = await res.json();
1056
+ if (data.success && data.user) {
1057
+ currentAuthUser = data.user;
1058
+ closeAuthModal();
1059
+ await checkAuthStatus();
1060
+ await loadProfiles();
1061
+ showToast(`Logged in as ${data.user.email}`);
1062
+ } else {
1063
+ alert(data.error || 'Login failed.');
1064
+ }
1065
+ } catch (err) {
1066
+ alert('Login error: ' + err.message);
1067
+ }
1068
+ }
1069
+
1070
+ async function submitDesktopLogout() {
1071
+ try {
1072
+ await fetch('/api/auth/logout', { method: 'POST' });
1073
+ currentAuthUser = null;
1074
+ closeAuthModal();
1075
+ await checkAuthStatus();
1076
+ showToast('Logged out of ANTRI.');
1077
+ } catch (err) {
1078
+ console.error('Logout error:', err);
1079
+ }
1080
+ }
@@ -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.46.0</span>
19
19
  </div>
20
20
 
21
21
  <!-- Mode Toggle -->
@@ -63,6 +63,11 @@
63
63
  <button id="btn-perms-toggle" class="perms-badge" onclick="toggleAlwaysAllow()">
64
64
  <span id="perms-text">Ask-First</span>
65
65
  </button>
66
+
67
+ <button id="btn-auth-status" class="auth-header-btn" onclick="openAuthModal()">
68
+ <span id="auth-status-dot" class="status-dot"></span>
69
+ <span id="auth-status-text">Login</span>
70
+ </button>
66
71
  </div>
67
72
  </header>
68
73
 
@@ -269,11 +274,15 @@
269
274
  <div class="panel-header">
270
275
  <div>
271
276
  <h2>Thinking Profile Studio</h2>
272
- <p>Manage user preferences, coding styles, and automatically extracted notes.</p>
277
+ <p>Manage user preferences, coding styles, and automatically extracted notes in <code>~/.antri/profiles/</code>.</p>
273
278
  </div>
274
279
  <div class="debate-controls">
280
+ <input type="file" id="profile-import-input" accept=".md,.txt" style="display:none;" onchange="handleProfileImport(event)" />
275
281
  <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>
282
+ <button class="action-btn" onclick="createProfile()">+ Create</button>
283
+ <button class="action-btn-secondary" onclick="document.getElementById('profile-import-input').click()">📁 Import .md</button>
284
+ <button class="action-btn-secondary" onclick="pushProfilesToCloud()">☁️ Push</button>
285
+ <button class="action-btn-secondary" onclick="pullProfilesFromCloud()">☁️ Pull</button>
277
286
  </div>
278
287
  </div>
279
288
 
@@ -283,25 +292,68 @@
283
292
  </div>
284
293
  <div class="profile-editor-container">
285
294
  <div class="editor-header">
286
- <h3 id="active-profile-title">profile_1.md</h3>
287
- <button class="save-btn" onclick="saveActiveProfile()">Save Profile</button>
295
+ <div>
296
+ <h3 id="active-profile-title">profile_1.md</h3>
297
+ <span class="editor-subtitle">Editing active thinking profile instructions</span>
298
+ </div>
299
+ <div class="editor-action-group">
300
+ <button class="btn-sm-action" onclick="exportActiveProfile()">⬇️ Export</button>
301
+ <button class="btn-sm-action btn-danger" onclick="deleteActiveProfile()">🗑️ Delete</button>
302
+ <button class="save-btn" onclick="saveActiveProfile()">💾 Save Profile</button>
303
+ </div>
288
304
  </div>
289
305
  <textarea id="profile-editor" class="profile-textarea" placeholder="Profile markdown content..."></textarea>
290
306
  </div>
291
307
  </div>
292
308
  </section>
293
309
 
294
- <!-- TAB 5: Dynamic Skills -->
310
+ <!-- TAB 5: Markdown Skills Studio -->
295
311
  <section id="tab-skills" class="tab-panel">
296
312
  <div class="panel-header">
297
313
  <div>
298
- <h2>Dynamic Skill Studio</h2>
299
- <p>Tools synthesized autonomously by the agent in <code>~/.agent-cli/skills/</code>.</p>
314
+ <h2>Markdown (.md) Skill Studio</h2>
315
+ <p>Specialist capability directives loaded from <code>~/.antri/skills/</code>. Add or import new skills as Markdown files.</p>
316
+ </div>
317
+ <div class="debate-controls">
318
+ <input type="file" id="skill-import-input" accept=".md,.txt" style="display:none;" onchange="handleSkillImport(event)" />
319
+ <input type="text" id="skill-search-input" placeholder="Search skills by name, category, or trigger..." oninput="filterSkills(this.value)" />
320
+ <button class="action-btn" onclick="createNewSkillPrompt()">+ Create Skill</button>
321
+ <button class="action-btn-secondary" onclick="document.getElementById('skill-import-input').click()">📁 Import .md</button>
300
322
  </div>
301
323
  </div>
302
324
 
303
- <div class="skills-grid" id="skills-catalog-grid">
304
- <!-- Populated dynamically -->
325
+ <div class="skill-studio-layout">
326
+ <!-- Left: Skills List & Filters -->
327
+ <div class="skill-list-sidebar">
328
+ <div class="skill-category-filters" id="skill-category-filters">
329
+ <button class="category-pill active" onclick="setSkillCategory('all')">All</button>
330
+ <button class="category-pill" onclick="setSkillCategory('Core')">Core</button>
331
+ <button class="category-pill" onclick="setSkillCategory('Custom')">Custom</button>
332
+ <button class="category-pill" onclick="setSkillCategory('Engineering')">Engineering</button>
333
+ <button class="category-pill" onclick="setSkillCategory('Architecture')">Architecture</button>
334
+ <button class="category-pill" onclick="setSkillCategory('Security')">Security</button>
335
+ </div>
336
+ <div class="skill-items-container" id="skills-catalog-list">
337
+ <!-- Populated dynamically -->
338
+ </div>
339
+ </div>
340
+
341
+ <!-- Right: Live Skill Markdown Editor -->
342
+ <div class="skill-editor-container">
343
+ <div class="editor-header">
344
+ <div>
345
+ <h3 id="active-skill-title">Select a Skill</h3>
346
+ <span id="active-skill-meta" class="editor-subtitle">Click any skill on the left to inspect or edit instructions</span>
347
+ </div>
348
+ <div class="editor-action-group">
349
+ <button class="btn-sm-action" id="btn-activate-skill" onclick="activateCurrentSkillInChat()">⚡ Activate in Chat</button>
350
+ <button class="btn-sm-action" id="btn-export-skill" onclick="exportCurrentSkill()">⬇️ Export</button>
351
+ <button class="btn-sm-action btn-danger" id="btn-delete-skill" onclick="deleteCurrentSkill()">🗑️ Delete</button>
352
+ <button class="save-btn" id="btn-save-skill" onclick="saveCurrentSkill()">💾 Save Skill</button>
353
+ </div>
354
+ </div>
355
+ <textarea id="skill-editor" class="profile-textarea" placeholder="Skill markdown content (# Skill Title, instructions, heuristics...)" spellcheck="false"></textarea>
356
+ </div>
305
357
  </div>
306
358
  </section>
307
359
 
@@ -333,6 +385,36 @@
333
385
  </main>
334
386
  </div>
335
387
 
388
+ <!-- AUTH MODAL -->
389
+ <div id="auth-modal" class="modal-backdrop" style="display:none;" onclick="if(event.target===this)closeAuthModal()">
390
+ <div class="modal-box">
391
+ <div class="modal-header">
392
+ <h3 id="modal-auth-title">ANTRI Account</h3>
393
+ <button class="modal-close" onclick="closeAuthModal()">✕</button>
394
+ </div>
395
+ <div class="modal-body" id="modal-auth-body">
396
+ <div id="auth-form-view">
397
+ <p style="font-size:13px;color:var(--text-muted);margin-bottom:16px;">Sign in to sync your thinking profiles and memories across CLI, Desktop, and Mobile.</p>
398
+ <div style="margin-bottom:12px;">
399
+ <label style="display:block;font-size:12px;font-weight:600;margin-bottom:6px;">Email Address</label>
400
+ <input type="email" id="modal-email-input" class="modal-input" placeholder="user@gmail.com" />
401
+ </div>
402
+ <div style="margin-bottom:16px;">
403
+ <label style="display:block;font-size:12px;font-weight:600;margin-bottom:6px;">Password</label>
404
+ <input type="password" id="modal-pass-input" class="modal-input" placeholder="••••••••" />
405
+ </div>
406
+ <button class="action-btn" style="width:100%;margin-bottom:8px;" onclick="submitDesktopLogin()">Sign In / Register</button>
407
+ </div>
408
+ <div id="auth-logged-view" style="display:none;text-align:center;padding:12px 0;">
409
+ <div style="font-size:32px;margin-bottom:8px;">👤</div>
410
+ <div id="logged-user-email" style="font-weight:700;font-size:15px;color:var(--primary);margin-bottom:4px;"></div>
411
+ <div id="logged-user-id" style="font-size:12px;color:var(--text-muted);margin-bottom:16px;"></div>
412
+ <button class="action-btn-danger" style="width:100%;" onclick="submitDesktopLogout()">Logout</button>
413
+ </div>
414
+ </div>
415
+ </div>
416
+ </div>
417
+
336
418
  <script src="app.js"></script>
337
419
  </body>
338
420
  </html>