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.
- package/dist/cli/banner.d.ts.map +1 -1
- package/dist/cli/banner.js +16 -1
- package/dist/cli/banner.js.map +1 -1
- package/dist/cli/prompt.d.ts.map +1 -1
- package/dist/cli/prompt.js +8 -0
- package/dist/cli/prompt.js.map +1 -1
- package/dist/cli/shortcuts.d.ts.map +1 -1
- package/dist/cli/shortcuts.js +62 -16
- package/dist/cli/shortcuts.js.map +1 -1
- package/dist/cloud/auth.d.ts.map +1 -1
- package/dist/cloud/auth.js +15 -6
- package/dist/cloud/auth.js.map +1 -1
- package/dist/cloud/firestore.d.ts.map +1 -1
- package/dist/cloud/firestore.js +49 -16
- package/dist/cloud/firestore.js.map +1 -1
- package/dist/core/agent.d.ts +2 -2
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +49 -26
- package/dist/core/agent.js.map +1 -1
- package/dist/core/config.d.ts +1 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +9 -4
- package/dist/core/config.js.map +1 -1
- package/dist/core/tools.d.ts.map +1 -1
- package/dist/core/tools.js +83 -15
- package/dist/core/tools.js.map +1 -1
- package/dist/core/updater.d.ts +1 -1
- package/dist/core/updater.d.ts.map +1 -1
- package/dist/core/updater.js +1 -1
- package/dist/core/updater.js.map +1 -1
- package/dist/desktop/public/app.js +412 -31
- package/dist/desktop/public/index.html +92 -10
- package/dist/desktop/public/style.css +316 -28
- package/dist/desktop/server.d.ts.map +1 -1
- package/dist/desktop/server.js +84 -4
- package/dist/desktop/server.js.map +1 -1
- package/dist/memory/manager.d.ts.map +1 -1
- package/dist/memory/manager.js +4 -0
- package/dist/memory/manager.js.map +1 -1
- package/dist/mobile/server.d.ts.map +1 -1
- package/dist/mobile/server.js +8 -2
- package/dist/mobile/server.js.map +1 -1
- package/dist/profiles/profileManager.d.ts +17 -4
- package/dist/profiles/profileManager.d.ts.map +1 -1
- package/dist/profiles/profileManager.js +202 -12
- package/dist/profiles/profileManager.js.map +1 -1
- package/dist/skills/skillManager.d.ts +62 -0
- package/dist/skills/skillManager.d.ts.map +1 -0
- package/dist/skills/skillManager.js +645 -0
- package/dist/skills/skillManager.js.map +1 -0
- 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
|
|
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 = '
|
|
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 =
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
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
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
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.
|
|
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
|
|
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()"
|
|
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
|
-
<
|
|
287
|
-
|
|
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:
|
|
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>
|
|
299
|
-
<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="
|
|
304
|
-
<!--
|
|
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>
|