antri_cli 1.49.0 → 1.52.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.
@@ -0,0 +1,188 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ const CHATS_DIR = path.join(os.homedir(), '.antri', 'chats');
5
+ const ACTIVE_SESSION_FILE = path.join(CHATS_DIR, '.active_session');
6
+ export class SessionManager {
7
+ chatsDir;
8
+ activeSessionId = '';
9
+ constructor(customDir) {
10
+ this.chatsDir = customDir || CHATS_DIR;
11
+ this.ensureDirectory();
12
+ this.initActiveSession();
13
+ }
14
+ ensureDirectory() {
15
+ if (!fs.existsSync(this.chatsDir)) {
16
+ fs.mkdirSync(this.chatsDir, { recursive: true });
17
+ }
18
+ }
19
+ initActiveSession() {
20
+ try {
21
+ if (fs.existsSync(ACTIVE_SESSION_FILE)) {
22
+ const id = fs.readFileSync(ACTIVE_SESSION_FILE, 'utf-8').trim();
23
+ if (id && fs.existsSync(this.getSessionFilePath(id))) {
24
+ this.activeSessionId = id;
25
+ return;
26
+ }
27
+ }
28
+ }
29
+ catch { }
30
+ // Pick first existing session or create a default session
31
+ const sessions = this.listSessions();
32
+ if (sessions.length > 0) {
33
+ this.activeSessionId = sessions[0].id;
34
+ }
35
+ else {
36
+ const newSession = this.createSession('New Chat');
37
+ this.activeSessionId = newSession.id;
38
+ }
39
+ this.saveActiveSessionId();
40
+ }
41
+ getSessionFilePath(id) {
42
+ const cleanId = id.replace(/[^a-zA-Z0-9_-]/g, '_');
43
+ return path.join(this.chatsDir, `${cleanId}.json`);
44
+ }
45
+ saveActiveSessionId() {
46
+ try {
47
+ this.ensureDirectory();
48
+ fs.writeFileSync(ACTIVE_SESSION_FILE, this.activeSessionId, 'utf-8');
49
+ }
50
+ catch { }
51
+ }
52
+ getActiveSessionId() {
53
+ return this.activeSessionId;
54
+ }
55
+ setActiveSessionId(id) {
56
+ const session = this.getSession(id);
57
+ if (session) {
58
+ this.activeSessionId = id;
59
+ this.saveActiveSessionId();
60
+ return session;
61
+ }
62
+ return null;
63
+ }
64
+ createSession(title = 'New Chat') {
65
+ this.ensureDirectory();
66
+ const id = `chat_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
67
+ const session = {
68
+ id,
69
+ title,
70
+ createdAt: Date.now(),
71
+ updatedAt: Date.now(),
72
+ messages: [],
73
+ };
74
+ this.saveSession(session);
75
+ this.activeSessionId = id;
76
+ this.saveActiveSessionId();
77
+ return session;
78
+ }
79
+ getSession(id) {
80
+ const filePath = this.getSessionFilePath(id);
81
+ if (fs.existsSync(filePath)) {
82
+ try {
83
+ const raw = fs.readFileSync(filePath, 'utf-8');
84
+ return JSON.parse(raw);
85
+ }
86
+ catch { }
87
+ }
88
+ return null;
89
+ }
90
+ getActiveSession() {
91
+ const session = this.getSession(this.activeSessionId);
92
+ if (session)
93
+ return session;
94
+ return this.createSession('New Chat');
95
+ }
96
+ saveSession(session) {
97
+ this.ensureDirectory();
98
+ session.updatedAt = Date.now();
99
+ const filePath = this.getSessionFilePath(session.id);
100
+ fs.writeFileSync(filePath, JSON.stringify(session, null, 2), 'utf-8');
101
+ }
102
+ addMessageToActiveSession(message) {
103
+ const session = this.getActiveSession();
104
+ session.messages.push({
105
+ ...message,
106
+ timestamp: message.timestamp || Date.now(),
107
+ });
108
+ // Auto-generate title on first user message if title is "New Chat"
109
+ if (session.title === 'New Chat' && message.role === 'user') {
110
+ const cleanPrompt = message.content.replace(/\[Attached File:[^\]]+\]/g, '').trim();
111
+ if (cleanPrompt) {
112
+ session.title = cleanPrompt.length > 35 ? `${cleanPrompt.slice(0, 32)}...` : cleanPrompt;
113
+ }
114
+ }
115
+ this.saveSession(session);
116
+ return session;
117
+ }
118
+ listSessions() {
119
+ this.ensureDirectory();
120
+ try {
121
+ const files = fs.readdirSync(this.chatsDir).filter((f) => f.endsWith('.json') && !f.startsWith('.'));
122
+ const summaries = [];
123
+ for (const file of files) {
124
+ try {
125
+ const filePath = path.join(this.chatsDir, file);
126
+ const raw = fs.readFileSync(filePath, 'utf-8');
127
+ const data = JSON.parse(raw);
128
+ if (data && data.id) {
129
+ const lastUserMsg = data.messages?.filter((m) => m.role === 'user').pop();
130
+ summaries.push({
131
+ id: data.id,
132
+ title: data.title || 'Untitled Chat',
133
+ createdAt: data.createdAt || 0,
134
+ updatedAt: data.updatedAt || 0,
135
+ messageCount: data.messages?.length || 0,
136
+ preview: lastUserMsg?.content?.slice(0, 80) || '',
137
+ isActive: data.id === this.activeSessionId,
138
+ });
139
+ }
140
+ }
141
+ catch { }
142
+ }
143
+ return summaries.sort((a, b) => b.updatedAt - a.updatedAt);
144
+ }
145
+ catch {
146
+ return [];
147
+ }
148
+ }
149
+ deleteSession(id) {
150
+ const filePath = this.getSessionFilePath(id);
151
+ try {
152
+ if (fs.existsSync(filePath)) {
153
+ fs.unlinkSync(filePath);
154
+ }
155
+ if (this.activeSessionId === id) {
156
+ const remaining = this.listSessions();
157
+ if (remaining.length > 0) {
158
+ this.activeSessionId = remaining[0].id;
159
+ }
160
+ else {
161
+ const fresh = this.createSession('New Chat');
162
+ this.activeSessionId = fresh.id;
163
+ }
164
+ this.saveActiveSessionId();
165
+ }
166
+ return true;
167
+ }
168
+ catch {
169
+ return false;
170
+ }
171
+ }
172
+ renameSession(id, newTitle) {
173
+ const session = this.getSession(id);
174
+ if (session) {
175
+ session.title = newTitle.trim() || 'Untitled Chat';
176
+ this.saveSession(session);
177
+ return true;
178
+ }
179
+ return false;
180
+ }
181
+ clearActiveSessionMessages() {
182
+ const session = this.getActiveSession();
183
+ session.messages = [];
184
+ this.saveSession(session);
185
+ }
186
+ }
187
+ export const sessionManager = new SessionManager();
188
+ //# sourceMappingURL=sessionManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionManager.js","sourceRoot":"","sources":["../../src/core/sessionManager.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  export declare class Updater {
2
2
  static readonly PACKAGE_NAME = "antri_cli";
3
- static readonly CURRENT_VERSION = "1.49.0";
3
+ static readonly CURRENT_VERSION = "1.52.0";
4
4
  /**
5
5
  * Fetches latest release version directly from registry.npmjs.org (cache-free)
6
6
  */
@@ -5,7 +5,7 @@ import ora from 'ora';
5
5
  const execPromise = util.promisify(exec);
6
6
  export class Updater {
7
7
  static PACKAGE_NAME = 'antri_cli';
8
- static CURRENT_VERSION = '1.49.0';
8
+ static CURRENT_VERSION = '1.52.0';
9
9
  /**
10
10
  * Fetches latest release version directly from registry.npmjs.org (cache-free)
11
11
  */
@@ -19,6 +19,7 @@ document.addEventListener('DOMContentLoaded', async () => {
19
19
  await checkAuthStatus();
20
20
  await loadStatus();
21
21
  await loadCommands();
22
+ await loadChatSessions();
22
23
  await loadProfiles();
23
24
  await loadSkills();
24
25
  await loadMemory();
@@ -401,6 +402,146 @@ function handleInputKey(event) {
401
402
  }
402
403
  }
403
404
 
405
+ // ==========================================
406
+ // Multi-Chat Session Management
407
+ // ==========================================
408
+ let currentActiveChatId = '';
409
+ let allChatSessions = [];
410
+
411
+ async function loadChatSessions(renderMessages = true) {
412
+ try {
413
+ const res = await fetch('/api/chats');
414
+ const data = await res.json();
415
+ allChatSessions = data.sessions || [];
416
+ currentActiveChatId = data.activeId || (allChatSessions[0]?.id || '');
417
+
418
+ const select = document.getElementById('select-chat-session');
419
+ if (select) {
420
+ select.innerHTML = '';
421
+ allChatSessions.forEach((s) => {
422
+ const opt = document.createElement('option');
423
+ opt.value = s.id;
424
+ opt.textContent = `${s.title} (${s.messageCount || 0})`;
425
+ if (s.id === currentActiveChatId) opt.selected = true;
426
+ select.appendChild(opt);
427
+ });
428
+ }
429
+
430
+ if (renderMessages && data.activeSession) {
431
+ renderActiveSessionMessages(data.activeSession.messages || []);
432
+ }
433
+ } catch (err) {
434
+ console.error('Failed to load chat sessions:', err);
435
+ }
436
+ }
437
+
438
+ function renderActiveSessionMessages(messages) {
439
+ const container = document.getElementById('chat-messages');
440
+ if (!container) return;
441
+ container.innerHTML = '';
442
+
443
+ if (!messages || messages.length === 0) {
444
+ container.innerHTML = `
445
+ <div class="welcome-card" id="chat-welcome-card">
446
+ <h2>ANTRI Control Plane</h2>
447
+ <p>Minimalist environment for autonomous coding, architectural planning, and self-refinement. State, memory, profiles, and skills are synchronized across CLI and Desktop.</p>
448
+ <div class="quick-action-pills">
449
+ <button onclick="setPrompt('Plan the architecture for a real-time collaborative code editor')">Plan Editor Architecture</button>
450
+ <button onclick="setPrompt('/debate What are the trade-offs between WebSockets vs Server-Sent Events?')">Start Dialectic Debate</button>
451
+ <button onclick="setPrompt('/goal Implement a zero-dependency LRU cache with TTL in TypeScript')">Run Goal Loop</button>
452
+ </div>
453
+ </div>
454
+ `;
455
+ return;
456
+ }
457
+
458
+ messages.forEach((msg) => {
459
+ if (msg.role === 'user' || msg.role === 'assistant') {
460
+ appendMessage(msg.role, msg.content);
461
+ }
462
+ });
463
+ }
464
+
465
+ async function createNewChatSession() {
466
+ try {
467
+ const res = await fetch('/api/chats/new', {
468
+ method: 'POST',
469
+ headers: { 'Content-Type': 'application/json' },
470
+ body: JSON.stringify({ title: 'New Chat' }),
471
+ });
472
+ const data = await res.json();
473
+ if (data.success && data.session) {
474
+ currentActiveChatId = data.session.id;
475
+ await loadChatSessions(true);
476
+ showToast('Started new chat session.');
477
+ }
478
+ } catch (err) {
479
+ console.error('Failed to create new chat session:', err);
480
+ }
481
+ }
482
+
483
+ async function onChatSessionSelect(id) {
484
+ try {
485
+ const res = await fetch('/api/chats/select', {
486
+ method: 'POST',
487
+ headers: { 'Content-Type': 'application/json' },
488
+ body: JSON.stringify({ id }),
489
+ });
490
+ const data = await res.json();
491
+ if (data.success && data.session) {
492
+ currentActiveChatId = data.session.id;
493
+ renderActiveSessionMessages(data.session.messages || []);
494
+ await loadChatSessions(false);
495
+ }
496
+ } catch (err) {
497
+ console.error('Failed to select chat session:', err);
498
+ }
499
+ }
500
+
501
+ async function clearCurrentChatContext() {
502
+ if (!confirm('Clear all conversation messages in this chat session?')) return;
503
+ const container = document.getElementById('chat-messages');
504
+ if (container) {
505
+ container.innerHTML = `
506
+ <div class="welcome-card" id="chat-welcome-card">
507
+ <h2>ANTRI Control Plane</h2>
508
+ <p>Minimalist environment for autonomous coding, architectural planning, and self-refinement. State, memory, profiles, and skills are synchronized across CLI and Desktop.</p>
509
+ <div class="quick-action-pills">
510
+ <button onclick="setPrompt('Plan the architecture for a real-time collaborative code editor')">Plan Editor Architecture</button>
511
+ <button onclick="setPrompt('/debate What are the trade-offs between WebSockets vs Server-Sent Events?')">Start Dialectic Debate</button>
512
+ <button onclick="setPrompt('/goal Implement a zero-dependency LRU cache with TTL in TypeScript')">Run Goal Loop</button>
513
+ </div>
514
+ </div>
515
+ `;
516
+ }
517
+ await fetch('/api/chat', {
518
+ method: 'POST',
519
+ headers: { 'Content-Type': 'application/json' },
520
+ body: JSON.stringify({ prompt: '/clear' }),
521
+ }).catch(() => {});
522
+ await loadChatSessions(false);
523
+ showToast('Chat context cleared.');
524
+ }
525
+
526
+ async function deleteCurrentChatSession() {
527
+ if (!currentActiveChatId) return;
528
+ if (!confirm('Are you sure you want to delete this chat session?')) return;
529
+ try {
530
+ const res = await fetch('/api/chats/delete', {
531
+ method: 'POST',
532
+ headers: { 'Content-Type': 'application/json' },
533
+ body: JSON.stringify({ id: currentActiveChatId }),
534
+ });
535
+ const data = await res.json();
536
+ if (data.success) {
537
+ await loadChatSessions(true);
538
+ showToast('Chat session deleted.');
539
+ }
540
+ } catch (err) {
541
+ console.error('Failed to delete chat session:', err);
542
+ }
543
+ }
544
+
404
545
  // Chat Prompt Submission with Real-Time Word-by-Word Side-by-Side Streaming
405
546
  async function submitPrompt() {
406
547
  const input = document.getElementById('prompt-input');
@@ -419,6 +560,12 @@ async function submitPrompt() {
419
560
  input.value = '';
420
561
  hidePalette();
421
562
 
563
+ // Remove welcome card if present
564
+ const welcomeCard = document.getElementById('chat-welcome-card');
565
+ if (welcomeCard) {
566
+ welcomeCard.remove();
567
+ }
568
+
422
569
  // Intercept /debate or /goal inside chat
423
570
  if (prompt.startsWith('/debate')) {
424
571
  showTab('dialectic');
@@ -502,6 +649,7 @@ async function submitPrompt() {
502
649
  } finally {
503
650
  sendBtn.disabled = false;
504
651
  sendBtn.innerHTML = '<span>Send</span>';
652
+ await loadChatSessions(false);
505
653
  }
506
654
  }
507
655
 
@@ -640,13 +788,23 @@ async function startGoalLoop() {
640
788
  }
641
789
 
642
790
  // ==========================================
643
- // Thinking Profile Management
791
+ // Thinking Profile & Notes Management
644
792
  // ==========================================
793
+ let currentViewedProfile = 'profile_1';
794
+ let currentActiveProfile = 'profile_1';
795
+ let currentNotesScope = 'global';
796
+ let cachedGlobalNotes = '';
797
+ let cachedWorkspaceNotes = '';
798
+
645
799
  async function loadProfiles() {
646
800
  try {
647
801
  const res = await fetch('/api/profiles');
648
802
  const data = await res.json();
649
803
 
804
+ currentActiveProfile = data.activeName || 'profile_1';
805
+ cachedGlobalNotes = data.globalNotes || '';
806
+ cachedWorkspaceNotes = data.workspaceNotes || '';
807
+
650
808
  const select = document.getElementById('select-profile');
651
809
  const list = document.getElementById('profile-items-list');
652
810
  select.innerHTML = '';
@@ -656,39 +814,156 @@ async function loadProfiles() {
656
814
  // Dropdown option
657
815
  const opt = document.createElement('option');
658
816
  opt.value = p.name;
659
- opt.textContent = p.name;
660
- if (p.name === data.activeName) opt.selected = true;
817
+ opt.textContent = `${p.name}.md`;
818
+ if (p.name === currentActiveProfile) opt.selected = true;
661
819
  select.appendChild(opt);
662
820
 
663
821
  // List button
822
+ const isCurrentViewed = p.name === currentViewedProfile || (!currentViewedProfile && p.name === currentActiveProfile);
664
823
  const btn = document.createElement('button');
665
- btn.className = `profile-item-btn ${p.name === data.activeName ? 'active' : ''}`;
824
+ btn.className = `profile-item-btn ${isCurrentViewed ? 'active' : ''}`;
666
825
  btn.innerHTML = `
667
826
  <div style="display:flex;justify-content:space-between;align-items:center;width:100%;">
668
827
  <span style="font-weight:600;">${p.name}.md</span>
669
- <span style="font-size:10px;opacity:0.7;">${p.notesCount || 0} notes</span>
828
+ <span style="font-size:10px;opacity:0.8;display:flex;align-items:center;gap:4px;">
829
+ ${p.name === currentActiveProfile ? '<span style="color:#10b981;font-weight:700;">● Active</span>' : ''}
830
+ <span>${p.notesCount || 0} notes</span>
831
+ </span>
670
832
  </div>
671
833
  `;
672
- btn.onclick = () => selectProfile(p.name);
834
+ btn.onclick = () => viewProfile(p.name);
673
835
  list.appendChild(btn);
674
836
  });
675
837
 
676
- document.getElementById('active-profile-title').textContent = `${data.activeName}.md`;
677
- document.getElementById('profile-editor').value = data.activeContent || '';
838
+ if (!currentViewedProfile || !data.profiles.some((p) => p.name === currentViewedProfile)) {
839
+ currentViewedProfile = currentActiveProfile;
840
+ }
841
+
842
+ await viewProfile(currentViewedProfile, false);
843
+
844
+ // Also populate notes editor
845
+ const notesEditor = document.getElementById('notes-editor');
846
+ if (notesEditor) {
847
+ notesEditor.value = currentNotesScope === 'workspace' ? cachedWorkspaceNotes : cachedGlobalNotes;
848
+ }
678
849
  } catch (err) {
679
850
  console.error('Failed to load profiles:', err);
680
851
  }
681
852
  }
682
853
 
854
+ async function viewProfile(name, updateListHighlight = true) {
855
+ try {
856
+ currentViewedProfile = name;
857
+ const res = await fetch(`/api/profile?name=${encodeURIComponent(name)}`);
858
+ const data = await res.json();
859
+
860
+ document.getElementById('active-profile-title').textContent = `${data.name}.md`;
861
+ const subtitle = document.getElementById('active-profile-subtitle');
862
+ const activateBtn = document.getElementById('btn-set-active-profile');
863
+
864
+ if (data.name === currentActiveProfile) {
865
+ if (subtitle) subtitle.textContent = 'Active thinking profile in LLM context';
866
+ if (activateBtn) activateBtn.style.display = 'none';
867
+ } else {
868
+ if (subtitle) subtitle.textContent = 'Viewing profile instructions (Click "Set as Active" to apply)';
869
+ if (activateBtn) activateBtn.style.display = 'inline-block';
870
+ }
871
+
872
+ document.getElementById('profile-editor').value = data.content || '';
873
+
874
+ if (updateListHighlight) {
875
+ document.querySelectorAll('#profile-items-list .profile-item-btn').forEach((btn, idx) => {
876
+ const text = btn.textContent || '';
877
+ if (text.includes(`${name}.md`)) {
878
+ btn.classList.add('active');
879
+ } else {
880
+ btn.classList.remove('active');
881
+ }
882
+ });
883
+ }
884
+ } catch (err) {
885
+ console.error('Failed to view profile:', err);
886
+ }
887
+ }
888
+
889
+ async function activateViewedProfile() {
890
+ if (!currentViewedProfile) return;
891
+ await selectProfile(currentViewedProfile);
892
+ showToast(`Active profile switched to '${currentViewedProfile}.md'`);
893
+ }
894
+
683
895
  async function selectProfile(name) {
684
896
  await fetch('/api/profile/select', {
685
897
  method: 'POST',
686
898
  headers: { 'Content-Type': 'application/json' },
687
899
  body: JSON.stringify({ name }),
688
900
  });
901
+ currentActiveProfile = name;
902
+ currentViewedProfile = name;
689
903
  await loadProfiles();
690
904
  }
691
905
 
906
+ function switchProfileSubtab(subtab) {
907
+ const btnProfiles = document.getElementById('btn-subtab-profiles');
908
+ const btnNotes = document.getElementById('btn-subtab-notes');
909
+ const viewProfiles = document.getElementById('subtab-view-profiles');
910
+ const viewNotes = document.getElementById('subtab-view-notes');
911
+
912
+ if (subtab === 'profiles') {
913
+ btnProfiles.classList.add('active');
914
+ btnNotes.classList.remove('active');
915
+ viewProfiles.classList.remove('hidden');
916
+ viewNotes.classList.add('hidden');
917
+ } else {
918
+ btnNotes.classList.add('active');
919
+ btnProfiles.classList.remove('active');
920
+ viewNotes.classList.remove('hidden');
921
+ viewProfiles.classList.add('hidden');
922
+ }
923
+ }
924
+
925
+ function selectNotesScope(scope) {
926
+ currentNotesScope = scope;
927
+ const btnGlobal = document.getElementById('btn-note-global');
928
+ const btnWorkspace = document.getElementById('btn-note-workspace');
929
+ const title = document.getElementById('notes-scope-title');
930
+ const editor = document.getElementById('notes-editor');
931
+
932
+ if (scope === 'workspace') {
933
+ btnWorkspace.classList.add('active');
934
+ btnGlobal.classList.remove('active');
935
+ if (title) title.textContent = 'Workspace Local Notes (.antri/profiles/notes.md)';
936
+ if (editor) editor.value = cachedWorkspaceNotes;
937
+ } else {
938
+ btnGlobal.classList.add('active');
939
+ btnWorkspace.classList.remove('active');
940
+ if (title) title.textContent = 'Global User Notes (~/.antri/profiles/notes.md)';
941
+ if (editor) editor.value = cachedGlobalNotes;
942
+ }
943
+ }
944
+
945
+ async function saveNotesContent() {
946
+ const content = document.getElementById('notes-editor').value;
947
+ try {
948
+ const res = await fetch('/api/profile/notes/save', {
949
+ method: 'POST',
950
+ headers: { 'Content-Type': 'application/json' },
951
+ body: JSON.stringify({ type: currentNotesScope, content }),
952
+ });
953
+ const data = await res.json();
954
+ if (data.success) {
955
+ if (currentNotesScope === 'workspace') {
956
+ cachedWorkspaceNotes = content;
957
+ } else {
958
+ cachedGlobalNotes = content;
959
+ }
960
+ showToast(`Notes (${currentNotesScope}) saved & synced.`);
961
+ }
962
+ } catch (err) {
963
+ showToast(`Failed to save notes: ${err.message}`, true);
964
+ }
965
+ }
966
+
692
967
  async function createProfile() {
693
968
  const input = document.getElementById('new-profile-name');
694
969
  const name = input.value.trim();
@@ -701,6 +976,7 @@ async function createProfile() {
701
976
  });
702
977
 
703
978
  input.value = '';
979
+ currentViewedProfile = name.toLowerCase().replace(/[^a-z0-9_-]/g, '_');
704
980
  await loadProfiles();
705
981
  showToast(`Profile '${name}' created.`);
706
982
  }
@@ -720,6 +996,7 @@ async function handleProfileImport(event) {
720
996
  });
721
997
  const data = await res.json();
722
998
  if (data.success) {
999
+ currentViewedProfile = file.name.replace(/\.md$/, '');
723
1000
  await loadProfiles();
724
1001
  showToast(`Profile '${file.name}' imported successfully.`);
725
1002
  }
@@ -734,9 +1011,9 @@ async function saveActiveProfile() {
734
1011
  await fetch('/api/profile/save', {
735
1012
  method: 'POST',
736
1013
  headers: { 'Content-Type': 'application/json' },
737
- body: JSON.stringify({ content }),
1014
+ body: JSON.stringify({ name: currentViewedProfile, content }),
738
1015
  });
739
- showToast('Profile saved successfully.');
1016
+ showToast(`Profile '${currentViewedProfile}.md' saved successfully.`);
740
1017
  }
741
1018
 
742
1019
  async function pushProfilesToCloud() {
@@ -773,7 +1050,7 @@ async function pullProfilesFromCloud() {
773
1050
  }
774
1051
 
775
1052
  function exportActiveProfile() {
776
- const activeTitle = document.getElementById('active-profile-title').textContent || 'profile.md';
1053
+ const activeTitle = document.getElementById('active-profile-title').textContent || `${currentViewedProfile}.md`;
777
1054
  const content = document.getElementById('profile-editor').value;
778
1055
  const blob = new Blob([content], { type: 'text/markdown;charset=utf-8;' });
779
1056
  const url = URL.createObjectURL(blob);
@@ -786,22 +1063,23 @@ function exportActiveProfile() {
786
1063
  }
787
1064
 
788
1065
  async function deleteActiveProfile() {
789
- const activeTitle = (document.getElementById('active-profile-title').textContent || '').replace('.md', '');
790
- if (activeTitle === 'profile_1') {
1066
+ const target = currentViewedProfile || 'profile_1';
1067
+ if (target === 'profile_1') {
791
1068
  alert('Cannot delete default profile_1.');
792
1069
  return;
793
1070
  }
794
- if (!confirm(`Are you sure you want to delete profile '${activeTitle}.md'?`)) return;
1071
+ if (!confirm(`Are you sure you want to delete profile '${target}.md' from both your device and the cloud? It will not be pulled again.`)) return;
795
1072
 
796
1073
  const res = await fetch('/api/profile/delete', {
797
1074
  method: 'POST',
798
1075
  headers: { 'Content-Type': 'application/json' },
799
- body: JSON.stringify({ name: activeTitle }),
1076
+ body: JSON.stringify({ name: target }),
800
1077
  });
801
1078
  const data = await res.json();
802
1079
  if (data.success) {
1080
+ currentViewedProfile = 'profile_1';
803
1081
  await loadProfiles();
804
- showToast(`Profile '${activeTitle}' deleted.`);
1082
+ showToast(`Profile '${target}.md' deleted from device and cloud.`);
805
1083
  }
806
1084
  }
807
1085