opencode-mem 2.0.1 → 2.2.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 (45) hide show
  1. package/README.md +54 -11
  2. package/dist/config.d.ts +5 -0
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +62 -6
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +18 -11
  7. package/dist/plugin.d.ts.map +1 -1
  8. package/dist/plugin.js +0 -4
  9. package/dist/services/ai/providers/anthropic-messages.d.ts.map +1 -1
  10. package/dist/services/ai/providers/anthropic-messages.js +11 -17
  11. package/dist/services/ai/providers/openai-chat-completion.d.ts.map +1 -1
  12. package/dist/services/ai/providers/openai-chat-completion.js +11 -17
  13. package/dist/services/ai/providers/openai-responses.d.ts.map +1 -1
  14. package/dist/services/ai/providers/openai-responses.js +11 -17
  15. package/dist/services/api-handlers.d.ts +4 -0
  16. package/dist/services/api-handlers.d.ts.map +1 -1
  17. package/dist/services/api-handlers.js +155 -1
  18. package/dist/services/auto-capture.js +2 -2
  19. package/dist/services/client.d.ts +1 -16
  20. package/dist/services/client.d.ts.map +1 -1
  21. package/dist/services/client.js +0 -35
  22. package/dist/services/context.d.ts +1 -7
  23. package/dist/services/context.d.ts.map +1 -1
  24. package/dist/services/context.js +6 -14
  25. package/dist/services/deduplication-service.d.ts.map +1 -1
  26. package/dist/services/deduplication-service.js +1 -3
  27. package/dist/services/logger.js +1 -1
  28. package/dist/services/user-memory-learning.d.ts.map +1 -1
  29. package/dist/services/user-memory-learning.js +122 -84
  30. package/dist/services/user-profile/profile-context.d.ts +2 -0
  31. package/dist/services/user-profile/profile-context.d.ts.map +1 -0
  32. package/dist/services/user-profile/profile-context.js +40 -0
  33. package/dist/services/user-profile/types.d.ts +51 -0
  34. package/dist/services/user-profile/types.d.ts.map +1 -0
  35. package/dist/services/user-profile/types.js +1 -0
  36. package/dist/services/user-profile/user-profile-manager.d.ts +22 -0
  37. package/dist/services/user-profile/user-profile-manager.d.ts.map +1 -0
  38. package/dist/services/user-profile/user-profile-manager.js +267 -0
  39. package/dist/services/web-server-worker.js +29 -1
  40. package/dist/types/index.d.ts +1 -3
  41. package/dist/types/index.d.ts.map +1 -1
  42. package/dist/web/app.js +249 -29
  43. package/dist/web/index.html +31 -5
  44. package/dist/web/styles.css +309 -0
  45. package/package.json +1 -1
package/dist/web/app.js CHANGED
@@ -9,10 +9,12 @@ const state = {
9
9
  totalItems: 0,
10
10
  selectedTag: "",
11
11
  currentScope: "project",
12
+ currentView: "project",
12
13
  searchQuery: "",
13
14
  isSearching: false,
14
15
  selectedMemories: new Set(),
15
16
  autoRefreshInterval: null,
17
+ userProfile: null,
16
18
  };
17
19
 
18
20
  marked.setOptions({
@@ -143,7 +145,7 @@ function renderPromptCard(prompt) {
143
145
  <input type="checkbox" class="memory-checkbox" data-id="${prompt.id}" ${isSelected ? "checked" : ""} />
144
146
  <i data-lucide="message-circle" class="icon"></i>
145
147
  <span class="badge badge-prompt">USER PROMPT</span>
146
- ${isLinked ? '<span class="badge badge-linked">🔗 LINKED</span>' : ""}
148
+ ${isLinked ? '<span class="badge badge-linked"><i data-lucide="link" class="icon-sm"></i> LINKED</span>' : ""}
147
149
  <span class="prompt-date">${promptDate}</span>
148
150
  </div>
149
151
  <div class="prompt-actions">
@@ -156,7 +158,7 @@ function renderPromptCard(prompt) {
156
158
  <div class="prompt-content">
157
159
  ${escapeHtml(prompt.content)}
158
160
  </div>
159
- ${isLinked ? '<div class="link-indicator">↓ Generated memory above ↑</div>' : ""}
161
+ ${isLinked ? '<div class="link-indicator"><i data-lucide="arrow-down" class="icon-sm"></i> Generated memory above <i data-lucide="arrow-up" class="icon-sm"></i></div>' : ""}
160
162
  </div>
161
163
  `;
162
164
  }
@@ -202,7 +204,7 @@ function renderMemoryCard(memory) {
202
204
  <input type="checkbox" class="memory-checkbox" data-id="${memory.id}" ${isSelected ? "checked" : ""} />
203
205
  <span class="badge badge-${memory.scope}">${memory.scope}</span>
204
206
  ${memory.memoryType ? `<span class="badge badge-type">${memory.memoryType}</span>` : ""}
205
- ${isLinked ? '<span class="badge badge-linked">🔗 LINKED</span>' : ""}
207
+ ${isLinked ? '<span class="badge badge-linked"><i data-lucide="link" class="icon-sm"></i> LINKED</span>' : ""}
206
208
  ${similarityHtml}
207
209
  ${isPinned ? '<span class="badge badge-pinned">PINNED</span>' : ""}
208
210
  <span class="memory-display-name">${escapeHtml(displayInfo)}</span>
@@ -218,7 +220,7 @@ function renderMemoryCard(memory) {
218
220
  </div>
219
221
  </div>
220
222
  <div class="memory-content markdown-content">${renderMarkdown(memory.content)}</div>
221
- ${isLinked ? '<div class="link-indicator">↑ From prompt below ↓</div>' : ""}
223
+ ${isLinked ? '<div class="link-indicator"><i data-lucide="arrow-up" class="icon-sm"></i> From prompt below <i data-lucide="arrow-down" class="icon-sm"></i></div>' : ""}
222
224
  <div class="memory-footer">
223
225
  ${dateInfo}
224
226
  <span>ID: ${memory.id}</span>
@@ -295,20 +297,6 @@ async function loadStats() {
295
297
  }
296
298
  }
297
299
 
298
- function switchScope(scope) {
299
- state.currentScope = scope;
300
- state.currentPage = 1;
301
- state.selectedTag = "";
302
-
303
- document.querySelectorAll(".tab-btn").forEach((btn) => {
304
- btn.classList.remove("active");
305
- });
306
- document.getElementById(`tab-${scope}`).classList.add("active");
307
-
308
- populateTagDropdowns();
309
- loadMemories();
310
- }
311
-
312
300
  async function addMemory(e) {
313
301
  e.preventDefault();
314
302
 
@@ -512,16 +500,16 @@ function handleAddScopeChange() {
512
500
 
513
501
  tagDropdown.innerHTML = '<option value="">Select tag</option>';
514
502
 
515
- if (!scope) return;
503
+ if (!scope || scope !== "project") return;
516
504
 
517
- const tags = scope === "user" ? state.tags.user : state.tags.project;
505
+ const tags = state.tags.project;
518
506
  tags.forEach((tagInfo) => {
519
507
  const displayText = tagInfo.displayName || tagInfo.tag;
520
508
  const shortDisplay =
521
509
  displayText.length > 50 ? displayText.substring(0, 50) + "..." : displayText;
522
510
  const option = document.createElement("option");
523
511
  option.value = tagInfo.tag;
524
- option.textContent = `[${scope}] ${shortDisplay}`;
512
+ option.textContent = shortDisplay;
525
513
  tagDropdown.appendChild(option);
526
514
  });
527
515
  }
@@ -551,12 +539,6 @@ function showRefreshIndicator(show) {
551
539
  }
552
540
  }
553
541
 
554
- function escapeHtml(text) {
555
- const div = document.createElement("div");
556
- div.textContent = text;
557
- return div.innerHTML;
558
- }
559
-
560
542
  function formatDate(isoString) {
561
543
  const date = new Date(isoString);
562
544
  return date.toLocaleString("en-US", {
@@ -722,9 +704,247 @@ async function runMigration(strategy) {
722
704
  }
723
705
  }
724
706
 
707
+ async function loadUserProfile() {
708
+ const result = await fetchAPI("/api/user-profile");
709
+ if (result.success) {
710
+ state.userProfile = result.data;
711
+ renderUserProfile();
712
+ } else {
713
+ showError(result.error || "Failed to load profile");
714
+ }
715
+ }
716
+
717
+ function renderUserProfile() {
718
+ const container = document.getElementById("profile-content");
719
+ const profile = state.userProfile;
720
+
721
+ if (!profile.exists) {
722
+ container.innerHTML = `
723
+ <div class="empty-state">
724
+ <i data-lucide="user-x" class="icon-large"></i>
725
+ <p>${profile.message}</p>
726
+ </div>
727
+ `;
728
+ lucide.createIcons();
729
+ return;
730
+ }
731
+
732
+ const data = profile.profileData;
733
+ const preferences = data.preferences || [];
734
+ const patterns = data.patterns || [];
735
+ const workflows = data.workflows || [];
736
+ const skillLevel = data.skillLevel || {};
737
+
738
+ container.innerHTML = `
739
+ <div class="profile-header">
740
+ <div class="profile-info">
741
+ <h3>${profile.displayName || profile.userId}</h3>
742
+ <p class="profile-meta">
743
+ <span>Version: ${profile.version}</span>
744
+ <span class="separator">|</span>
745
+ <span>Analyzed: ${profile.totalPromptsAnalyzed} prompts</span>
746
+ <span class="separator">|</span>
747
+ <span>Updated: ${formatDate(profile.lastAnalyzedAt)}</span>
748
+ </p>
749
+ </div>
750
+ <button id="view-changelog-btn" class="btn-secondary">
751
+ <i data-lucide="history" class="icon"></i> Version History
752
+ </button>
753
+ </div>
754
+
755
+ <div class="profile-section">
756
+ <h4><i data-lucide="heart" class="icon"></i> Preferences (${preferences.length})</h4>
757
+ ${
758
+ preferences.length === 0
759
+ ? '<p class="empty-text">No preferences learned yet</p>'
760
+ : `
761
+ <div class="preferences-list">
762
+ ${preferences
763
+ .map(
764
+ (p) => `
765
+ <div class="preference-item">
766
+ <div class="preference-header">
767
+ <span class="preference-name">${escapeHtml(p.description)}</span>
768
+ <span class="confidence-badge">${Math.round(p.confidence * 100)}%</span>
769
+ </div>
770
+ <div class="confidence-bar">
771
+ <div class="confidence-fill" style="width: ${p.confidence * 100}%"></div>
772
+ </div>
773
+ <p class="preference-evidence">${escapeHtml(Array.isArray(p.evidence) ? p.evidence.join(", ") : p.evidence)}</p>
774
+ <p class="preference-meta">Category: ${escapeHtml(p.category)}</p>
775
+ </div>
776
+ `
777
+ )
778
+ .join("")}
779
+ </div>
780
+ `
781
+ }
782
+ </div>
783
+
784
+ <div class="profile-section">
785
+ <h4><i data-lucide="activity" class="icon"></i> Patterns (${patterns.length})</h4>
786
+ ${
787
+ patterns.length === 0
788
+ ? '<p class="empty-text">No patterns detected yet</p>'
789
+ : `
790
+ <div class="patterns-list">
791
+ ${patterns
792
+ .map(
793
+ (p) => `
794
+ <div class="pattern-item">
795
+ <div class="pattern-header">
796
+ <span class="pattern-name">${escapeHtml(p.description)}</span>
797
+ <span class="category-badge">${escapeHtml(p.category)}</span>
798
+ </div>
799
+ </div>
800
+ `
801
+ )
802
+ .join("")}
803
+ </div>
804
+ `
805
+ }
806
+ </div>
807
+
808
+ <div class="profile-section">
809
+ <h4><i data-lucide="workflow" class="icon"></i> Workflows (${workflows.length})</h4>
810
+ ${
811
+ workflows.length === 0
812
+ ? '<p class="empty-text">No workflows identified yet</p>'
813
+ : `
814
+ <div class="workflows-list">
815
+ ${workflows
816
+ .map(
817
+ (w) => `
818
+ <div class="workflow-item">
819
+ <div class="workflow-header">
820
+ <span class="workflow-name">${escapeHtml(w.description)}</span>
821
+ </div>
822
+ <div class="workflow-steps">
823
+ ${w.steps
824
+ .map(
825
+ (step) => `
826
+ <div class="workflow-step">
827
+ <span class="step-text">${escapeHtml(step)}</span>
828
+ </div>
829
+ `
830
+ )
831
+ .join("")}
832
+ </div>
833
+ </div>
834
+ `
835
+ )
836
+ .join("")}
837
+ </div>
838
+ `
839
+ }
840
+ </div>
841
+
842
+ <div class="profile-section">
843
+ <h4><i data-lucide="award" class="icon"></i> Skill Level</h4>
844
+ <div class="skill-level">
845
+ <div class="skill-item">
846
+ <span class="skill-label">Overall</span>
847
+ <span class="skill-value">${escapeHtml(skillLevel.overall || "unknown")}</span>
848
+ </div>
849
+ ${Object.entries(skillLevel.domains || {})
850
+ .map(
851
+ ([domain, level]) => `
852
+ <div class="skill-item">
853
+ <span class="skill-label">${escapeHtml(domain)}</span>
854
+ <span class="skill-value">${escapeHtml(level)}</span>
855
+ </div>
856
+ `
857
+ )
858
+ .join("")}
859
+ </div>
860
+ </div>
861
+ `;
862
+
863
+ document.getElementById("view-changelog-btn")?.addEventListener("click", showChangelog);
864
+ lucide.createIcons();
865
+ }
866
+
867
+ async function showChangelog() {
868
+ const modal = document.getElementById("changelog-modal");
869
+ const list = document.getElementById("changelog-list");
870
+
871
+ modal.classList.remove("hidden");
872
+ list.innerHTML = '<div class="loading">Loading changelog...</div>';
873
+
874
+ const result = await fetchAPI(
875
+ `/api/user-profile/changelog?profileId=${state.userProfile.id}&limit=10`
876
+ );
877
+
878
+ if (result.success && result.data.length > 0) {
879
+ list.innerHTML = result.data
880
+ .map(
881
+ (c) => `
882
+ <div class="changelog-item">
883
+ <div class="changelog-header">
884
+ <span class="changelog-version">v${c.version}</span>
885
+ <span class="changelog-type">${c.changeType}</span>
886
+ <span class="changelog-date">${formatDate(c.createdAt)}</span>
887
+ </div>
888
+ <p class="changelog-summary">${escapeHtml(c.changeSummary)}</p>
889
+ </div>
890
+ `
891
+ )
892
+ .join("");
893
+ } else {
894
+ list.innerHTML = '<div class="empty-state">No changelog available</div>';
895
+ }
896
+ }
897
+
898
+ async function refreshProfile() {
899
+ showToast("Refreshing profile...", "info");
900
+ const result = await fetchAPI("/api/user-profile/refresh", {
901
+ method: "POST",
902
+ headers: { "Content-Type": "application/json" },
903
+ body: JSON.stringify({}),
904
+ });
905
+
906
+ if (result.success) {
907
+ showToast(result.data.message, "success");
908
+ await loadUserProfile();
909
+ } else {
910
+ showToast(result.error || "Failed to refresh profile", "error");
911
+ }
912
+ }
913
+
914
+ function switchView(view) {
915
+ state.currentView = view;
916
+
917
+ document.querySelectorAll(".tab-btn").forEach((btn) => btn.classList.remove("active"));
918
+
919
+ if (view === "project") {
920
+ document.getElementById("tab-project").classList.add("active");
921
+ document.getElementById("project-section").classList.remove("hidden");
922
+ document.getElementById("profile-section").classList.add("hidden");
923
+ document.querySelector(".controls").classList.remove("hidden");
924
+ document.querySelector(".add-section").classList.remove("hidden");
925
+ } else if (view === "profile") {
926
+ document.getElementById("tab-profile").classList.add("active");
927
+ document.getElementById("project-section").classList.add("hidden");
928
+ document.getElementById("profile-section").classList.remove("hidden");
929
+ document.querySelector(".controls").classList.add("hidden");
930
+ document.querySelector(".add-section").classList.add("hidden");
931
+ loadUserProfile();
932
+ }
933
+ }
934
+
935
+ function escapeHtml(text) {
936
+ const div = document.createElement("div");
937
+ div.textContent = text;
938
+ return div.innerHTML;
939
+ }
940
+
725
941
  document.addEventListener("DOMContentLoaded", async () => {
726
- document.getElementById("tab-project").addEventListener("click", () => switchScope("project"));
727
- document.getElementById("tab-user").addEventListener("click", () => switchScope("user"));
942
+ document.getElementById("tab-project").addEventListener("click", () => switchView("project"));
943
+ document.getElementById("tab-profile").addEventListener("click", () => switchView("profile"));
944
+ document.getElementById("refresh-profile-btn")?.addEventListener("click", refreshProfile);
945
+ document.getElementById("changelog-close")?.addEventListener("click", () => {
946
+ document.getElementById("changelog-modal").classList.add("hidden");
947
+ });
728
948
 
729
949
  document.getElementById("tag-filter").addEventListener("change", () => {
730
950
  state.selectedTag = document.getElementById("tag-filter").value;
@@ -38,11 +38,11 @@
38
38
  <div class="scope-tabs">
39
39
  <button id="tab-project" class="tab-btn active">
40
40
  <i data-lucide="folder" class="icon"></i>
41
- PROJECT TIMELINE
41
+ PROJECT MEMORIES
42
42
  </button>
43
- <button id="tab-user" class="tab-btn">
43
+ <button id="tab-profile" class="tab-btn">
44
44
  <i data-lucide="user" class="icon"></i>
45
- USER TIMELINE
45
+ USER PROFILE
46
46
  </button>
47
47
  </div>
48
48
 
@@ -99,7 +99,7 @@
99
99
  </div>
100
100
  </div>
101
101
 
102
- <div class="memories-section">
102
+ <div class="memories-section" id="project-section">
103
103
  <div class="section-header">
104
104
  <h2 id="section-title">└─ PROJECT MEMORIES (0) ──</h2>
105
105
  <div class="pagination" id="pagination-top">
@@ -128,6 +128,19 @@
128
128
  </div>
129
129
  </div>
130
130
 
131
+ <div class="profile-section hidden" id="profile-section">
132
+ <div class="section-header">
133
+ <h2>└─ USER PROFILE ──</h2>
134
+ <button id="refresh-profile-btn" class="btn-secondary">
135
+ <i data-lucide="refresh-cw" class="icon"></i> Refresh
136
+ </button>
137
+ </div>
138
+
139
+ <div id="profile-content" class="profile-content">
140
+ <div class="loading">Loading profile...</div>
141
+ </div>
142
+ </div>
143
+
131
144
  <div class="add-section">
132
145
  <h2>└─ ADD NEW MEMORY ──</h2>
133
146
  <form id="add-form">
@@ -136,7 +149,6 @@
136
149
  <label>Scope:</label>
137
150
  <select id="add-scope" required>
138
151
  <option value="">Select scope</option>
139
- <option value="user">User</option>
140
152
  <option value="project">Project</option>
141
153
  </select>
142
154
  </div>
@@ -201,6 +213,20 @@
201
213
 
202
214
  <div id="toast" class="toast hidden"></div>
203
215
 
216
+ <div id="changelog-modal" class="modal hidden">
217
+ <div class="modal-content">
218
+ <div class="modal-header">
219
+ <h3>Profile Version History</h3>
220
+ <button class="modal-close" id="changelog-close">
221
+ <i data-lucide="x" class="icon"></i>
222
+ </button>
223
+ </div>
224
+ <div id="changelog-list" class="changelog-list">
225
+ <div class="loading">Loading changelog...</div>
226
+ </div>
227
+ </div>
228
+ </div>
229
+
204
230
  <script src="/app.js"></script>
205
231
  </body>
206
232
  </html>