opencode-mem 2.1.1 → 2.3.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 (33) hide show
  1. package/README.md +60 -77
  2. package/dist/config.d.ts +1 -2
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +11 -16
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +27 -86
  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 +5 -3
  16. package/dist/services/api-handlers.d.ts.map +1 -1
  17. package/dist/services/api-handlers.js +165 -42
  18. package/dist/services/auto-capture.js +2 -2
  19. package/dist/services/context.d.ts +1 -1
  20. package/dist/services/context.d.ts.map +1 -1
  21. package/dist/services/context.js +1 -10
  22. package/dist/services/deduplication-service.d.ts.map +1 -1
  23. package/dist/services/deduplication-service.js +1 -3
  24. package/dist/services/logger.js +1 -1
  25. package/dist/services/user-memory-learning.d.ts +1 -1
  26. package/dist/services/user-memory-learning.d.ts.map +1 -1
  27. package/dist/services/user-memory-learning.js +5 -3
  28. package/dist/services/user-profile/user-profile-manager.d.ts.map +1 -1
  29. package/dist/services/web-server-worker.js +30 -3
  30. package/dist/web/app.js +254 -47
  31. package/dist/web/index.html +31 -15
  32. package/dist/web/styles.css +309 -0
  33. package/package.json +1 -1
package/dist/web/app.js CHANGED
@@ -1,18 +1,19 @@
1
1
  const API_BASE = "";
2
2
 
3
3
  const state = {
4
- tags: { user: [], project: [] },
4
+ tags: { project: [] },
5
5
  memories: [],
6
6
  currentPage: 1,
7
7
  pageSize: 20,
8
8
  totalPages: 1,
9
9
  totalItems: 0,
10
10
  selectedTag: "",
11
- currentScope: "project",
11
+ currentView: "project",
12
12
  searchQuery: "",
13
13
  isSearching: false,
14
14
  selectedMemories: new Set(),
15
15
  autoRefreshInterval: null,
16
+ userProfile: null,
16
17
  };
17
18
 
18
19
  marked.setOptions({
@@ -53,7 +54,7 @@ function populateTagDropdowns() {
53
54
  tagFilter.innerHTML = '<option value="">All Tags</option>';
54
55
  addTag.innerHTML = '<option value="">Select tag</option>';
55
56
 
56
- const scopeTags = state.currentScope === "user" ? state.tags.user : state.tags.project;
57
+ const scopeTags = state.tags.project;
57
58
 
58
59
  scopeTags.forEach((tagInfo) => {
59
60
  const displayText = tagInfo.displayName || tagInfo.tag;
@@ -75,7 +76,7 @@ function populateTagDropdowns() {
75
76
  async function loadMemories() {
76
77
  showRefreshIndicator(true);
77
78
 
78
- let endpoint = `/api/memories?page=${state.currentPage}&pageSize=${state.pageSize}&scope=${state.currentScope}&includePrompts=true`;
79
+ let endpoint = `/api/memories?page=${state.currentPage}&pageSize=${state.pageSize}&includePrompts=true`;
79
80
 
80
81
  if (state.isSearching && state.searchQuery) {
81
82
  endpoint = `/api/search?q=${encodeURIComponent(state.searchQuery)}&page=${state.currentPage}&pageSize=${state.pageSize}`;
@@ -143,7 +144,7 @@ function renderPromptCard(prompt) {
143
144
  <input type="checkbox" class="memory-checkbox" data-id="${prompt.id}" ${isSelected ? "checked" : ""} />
144
145
  <i data-lucide="message-circle" class="icon"></i>
145
146
  <span class="badge badge-prompt">USER PROMPT</span>
146
- ${isLinked ? '<span class="badge badge-linked">🔗 LINKED</span>' : ""}
147
+ ${isLinked ? '<span class="badge badge-linked"><i data-lucide="link" class="icon-sm"></i> LINKED</span>' : ""}
147
148
  <span class="prompt-date">${promptDate}</span>
148
149
  </div>
149
150
  <div class="prompt-actions">
@@ -156,7 +157,7 @@ function renderPromptCard(prompt) {
156
157
  <div class="prompt-content">
157
158
  ${escapeHtml(prompt.content)}
158
159
  </div>
159
- ${isLinked ? '<div class="link-indicator">↓ Generated memory above ↑</div>' : ""}
160
+ ${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
161
  </div>
161
162
  `;
162
163
  }
@@ -171,15 +172,13 @@ function renderMemoryCard(memory) {
171
172
  : "";
172
173
 
173
174
  let displayInfo = memory.displayName || memory.id;
174
- if (memory.scope === "project" && memory.projectPath) {
175
+ if (memory.projectPath) {
175
176
  const pathParts = memory.projectPath.split("/");
176
177
  displayInfo = pathParts[pathParts.length - 1] || memory.projectPath;
177
178
  }
178
179
 
179
180
  let subtitle = "";
180
- if (memory.scope === "user" && memory.userEmail) {
181
- subtitle = `<span class="memory-subtitle">${escapeHtml(memory.userEmail)}</span>`;
182
- } else if (memory.scope === "project" && memory.projectPath) {
181
+ if (memory.projectPath) {
183
182
  subtitle = `<span class="memory-subtitle">${escapeHtml(memory.projectPath)}</span>`;
184
183
  }
185
184
 
@@ -200,9 +199,8 @@ function renderMemoryCard(memory) {
200
199
  <div class="memory-header">
201
200
  <div class="meta">
202
201
  <input type="checkbox" class="memory-checkbox" data-id="${memory.id}" ${isSelected ? "checked" : ""} />
203
- <span class="badge badge-${memory.scope}">${memory.scope}</span>
204
202
  ${memory.memoryType ? `<span class="badge badge-type">${memory.memoryType}</span>` : ""}
205
- ${isLinked ? '<span class="badge badge-linked">🔗 LINKED</span>' : ""}
203
+ ${isLinked ? '<span class="badge badge-linked"><i data-lucide="link" class="icon-sm"></i> LINKED</span>' : ""}
206
204
  ${similarityHtml}
207
205
  ${isPinned ? '<span class="badge badge-pinned">PINNED</span>' : ""}
208
206
  <span class="memory-display-name">${escapeHtml(displayInfo)}</span>
@@ -218,7 +216,7 @@ function renderMemoryCard(memory) {
218
216
  </div>
219
217
  </div>
220
218
  <div class="memory-content markdown-content">${renderMarkdown(memory.content)}</div>
221
- ${isLinked ? '<div class="link-indicator">↑ From prompt below ↓</div>' : ""}
219
+ ${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
220
  <div class="memory-footer">
223
221
  ${dateInfo}
224
222
  <span>ID: ${memory.id}</span>
@@ -278,10 +276,9 @@ function updatePagination() {
278
276
  }
279
277
 
280
278
  function updateSectionTitle() {
281
- const scopeName = state.currentScope.toUpperCase();
282
279
  const title = state.isSearching
283
280
  ? `└─ SEARCH RESULTS (${state.totalItems}) ──`
284
- : `└─ ${scopeName} MEMORIES (${state.totalItems}) ──`;
281
+ : `└─ PROJECT MEMORIES (${state.totalItems}) ──`;
285
282
  document.getElementById("section-title").textContent = title;
286
283
  }
287
284
 
@@ -289,26 +286,9 @@ async function loadStats() {
289
286
  const result = await fetchAPI("/api/stats");
290
287
  if (result.success) {
291
288
  document.getElementById("stats-total").textContent = `Total: ${result.data.total}`;
292
- document.getElementById("stats-user").textContent = `User: ${result.data.byScope.user}`;
293
- document.getElementById("stats-project").textContent =
294
- `Project: ${result.data.byScope.project}`;
295
289
  }
296
290
  }
297
291
 
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
292
  async function addMemory(e) {
313
293
  e.preventDefault();
314
294
 
@@ -507,21 +487,18 @@ function changePage(delta) {
507
487
  }
508
488
 
509
489
  function handleAddScopeChange() {
510
- const scope = document.getElementById("add-scope").value;
511
490
  const tagDropdown = document.getElementById("add-tag");
512
491
 
513
492
  tagDropdown.innerHTML = '<option value="">Select tag</option>';
514
493
 
515
- if (!scope) return;
516
-
517
- const tags = scope === "user" ? state.tags.user : state.tags.project;
494
+ const tags = state.tags.project;
518
495
  tags.forEach((tagInfo) => {
519
496
  const displayText = tagInfo.displayName || tagInfo.tag;
520
497
  const shortDisplay =
521
498
  displayText.length > 50 ? displayText.substring(0, 50) + "..." : displayText;
522
499
  const option = document.createElement("option");
523
500
  option.value = tagInfo.tag;
524
- option.textContent = `[${scope}] ${shortDisplay}`;
501
+ option.textContent = shortDisplay;
525
502
  tagDropdown.appendChild(option);
526
503
  });
527
504
  }
@@ -551,12 +528,6 @@ function showRefreshIndicator(show) {
551
528
  }
552
529
  }
553
530
 
554
- function escapeHtml(text) {
555
- const div = document.createElement("div");
556
- div.textContent = text;
557
- return div.innerHTML;
558
- }
559
-
560
531
  function formatDate(isoString) {
561
532
  const date = new Date(isoString);
562
533
  return date.toLocaleString("en-US", {
@@ -722,9 +693,247 @@ async function runMigration(strategy) {
722
693
  }
723
694
  }
724
695
 
696
+ async function loadUserProfile() {
697
+ const result = await fetchAPI("/api/user-profile");
698
+ if (result.success) {
699
+ state.userProfile = result.data;
700
+ renderUserProfile();
701
+ } else {
702
+ showError(result.error || "Failed to load profile");
703
+ }
704
+ }
705
+
706
+ function renderUserProfile() {
707
+ const container = document.getElementById("profile-content");
708
+ const profile = state.userProfile;
709
+
710
+ if (!profile.exists) {
711
+ container.innerHTML = `
712
+ <div class="empty-state">
713
+ <i data-lucide="user-x" class="icon-large"></i>
714
+ <p>${profile.message}</p>
715
+ </div>
716
+ `;
717
+ lucide.createIcons();
718
+ return;
719
+ }
720
+
721
+ const data = profile.profileData;
722
+ const preferences = data.preferences || [];
723
+ const patterns = data.patterns || [];
724
+ const workflows = data.workflows || [];
725
+ const skillLevel = data.skillLevel || {};
726
+
727
+ container.innerHTML = `
728
+ <div class="profile-header">
729
+ <div class="profile-info">
730
+ <h3>${profile.displayName || profile.userId}</h3>
731
+ <p class="profile-meta">
732
+ <span>Version: ${profile.version}</span>
733
+ <span class="separator">|</span>
734
+ <span>Analyzed: ${profile.totalPromptsAnalyzed} prompts</span>
735
+ <span class="separator">|</span>
736
+ <span>Updated: ${formatDate(profile.lastAnalyzedAt)}</span>
737
+ </p>
738
+ </div>
739
+ <button id="view-changelog-btn" class="btn-secondary">
740
+ <i data-lucide="history" class="icon"></i> Version History
741
+ </button>
742
+ </div>
743
+
744
+ <div class="profile-section">
745
+ <h4><i data-lucide="heart" class="icon"></i> Preferences (${preferences.length})</h4>
746
+ ${
747
+ preferences.length === 0
748
+ ? '<p class="empty-text">No preferences learned yet</p>'
749
+ : `
750
+ <div class="preferences-list">
751
+ ${preferences
752
+ .map(
753
+ (p) => `
754
+ <div class="preference-item">
755
+ <div class="preference-header">
756
+ <span class="preference-name">${escapeHtml(p.description)}</span>
757
+ <span class="confidence-badge">${Math.round(p.confidence * 100)}%</span>
758
+ </div>
759
+ <div class="confidence-bar">
760
+ <div class="confidence-fill" style="width: ${p.confidence * 100}%"></div>
761
+ </div>
762
+ <p class="preference-evidence">${escapeHtml(Array.isArray(p.evidence) ? p.evidence.join(", ") : p.evidence)}</p>
763
+ <p class="preference-meta">Category: ${escapeHtml(p.category)}</p>
764
+ </div>
765
+ `
766
+ )
767
+ .join("")}
768
+ </div>
769
+ `
770
+ }
771
+ </div>
772
+
773
+ <div class="profile-section">
774
+ <h4><i data-lucide="activity" class="icon"></i> Patterns (${patterns.length})</h4>
775
+ ${
776
+ patterns.length === 0
777
+ ? '<p class="empty-text">No patterns detected yet</p>'
778
+ : `
779
+ <div class="patterns-list">
780
+ ${patterns
781
+ .map(
782
+ (p) => `
783
+ <div class="pattern-item">
784
+ <div class="pattern-header">
785
+ <span class="pattern-name">${escapeHtml(p.description)}</span>
786
+ <span class="category-badge">${escapeHtml(p.category)}</span>
787
+ </div>
788
+ </div>
789
+ `
790
+ )
791
+ .join("")}
792
+ </div>
793
+ `
794
+ }
795
+ </div>
796
+
797
+ <div class="profile-section">
798
+ <h4><i data-lucide="workflow" class="icon"></i> Workflows (${workflows.length})</h4>
799
+ ${
800
+ workflows.length === 0
801
+ ? '<p class="empty-text">No workflows identified yet</p>'
802
+ : `
803
+ <div class="workflows-list">
804
+ ${workflows
805
+ .map(
806
+ (w) => `
807
+ <div class="workflow-item">
808
+ <div class="workflow-header">
809
+ <span class="workflow-name">${escapeHtml(w.description)}</span>
810
+ </div>
811
+ <div class="workflow-steps">
812
+ ${w.steps
813
+ .map(
814
+ (step) => `
815
+ <div class="workflow-step">
816
+ <span class="step-text">${escapeHtml(step)}</span>
817
+ </div>
818
+ `
819
+ )
820
+ .join("")}
821
+ </div>
822
+ </div>
823
+ `
824
+ )
825
+ .join("")}
826
+ </div>
827
+ `
828
+ }
829
+ </div>
830
+
831
+ <div class="profile-section">
832
+ <h4><i data-lucide="award" class="icon"></i> Skill Level</h4>
833
+ <div class="skill-level">
834
+ <div class="skill-item">
835
+ <span class="skill-label">Overall</span>
836
+ <span class="skill-value">${escapeHtml(skillLevel.overall || "unknown")}</span>
837
+ </div>
838
+ ${Object.entries(skillLevel.domains || {})
839
+ .map(
840
+ ([domain, level]) => `
841
+ <div class="skill-item">
842
+ <span class="skill-label">${escapeHtml(domain)}</span>
843
+ <span class="skill-value">${escapeHtml(level)}</span>
844
+ </div>
845
+ `
846
+ )
847
+ .join("")}
848
+ </div>
849
+ </div>
850
+ `;
851
+
852
+ document.getElementById("view-changelog-btn")?.addEventListener("click", showChangelog);
853
+ lucide.createIcons();
854
+ }
855
+
856
+ async function showChangelog() {
857
+ const modal = document.getElementById("changelog-modal");
858
+ const list = document.getElementById("changelog-list");
859
+
860
+ modal.classList.remove("hidden");
861
+ list.innerHTML = '<div class="loading">Loading changelog...</div>';
862
+
863
+ const result = await fetchAPI(
864
+ `/api/user-profile/changelog?profileId=${state.userProfile.id}&limit=10`
865
+ );
866
+
867
+ if (result.success && result.data.length > 0) {
868
+ list.innerHTML = result.data
869
+ .map(
870
+ (c) => `
871
+ <div class="changelog-item">
872
+ <div class="changelog-header">
873
+ <span class="changelog-version">v${c.version}</span>
874
+ <span class="changelog-type">${c.changeType}</span>
875
+ <span class="changelog-date">${formatDate(c.createdAt)}</span>
876
+ </div>
877
+ <p class="changelog-summary">${escapeHtml(c.changeSummary)}</p>
878
+ </div>
879
+ `
880
+ )
881
+ .join("");
882
+ } else {
883
+ list.innerHTML = '<div class="empty-state">No changelog available</div>';
884
+ }
885
+ }
886
+
887
+ async function refreshProfile() {
888
+ showToast("Refreshing profile...", "info");
889
+ const result = await fetchAPI("/api/user-profile/refresh", {
890
+ method: "POST",
891
+ headers: { "Content-Type": "application/json" },
892
+ body: JSON.stringify({}),
893
+ });
894
+
895
+ if (result.success) {
896
+ showToast(result.data.message, "success");
897
+ await loadUserProfile();
898
+ } else {
899
+ showToast(result.error || "Failed to refresh profile", "error");
900
+ }
901
+ }
902
+
903
+ function switchView(view) {
904
+ state.currentView = view;
905
+
906
+ document.querySelectorAll(".tab-btn").forEach((btn) => btn.classList.remove("active"));
907
+
908
+ if (view === "project") {
909
+ document.getElementById("tab-project").classList.add("active");
910
+ document.getElementById("project-section").classList.remove("hidden");
911
+ document.getElementById("profile-section").classList.add("hidden");
912
+ document.querySelector(".controls").classList.remove("hidden");
913
+ document.querySelector(".add-section").classList.remove("hidden");
914
+ } else if (view === "profile") {
915
+ document.getElementById("tab-profile").classList.add("active");
916
+ document.getElementById("project-section").classList.add("hidden");
917
+ document.getElementById("profile-section").classList.remove("hidden");
918
+ document.querySelector(".controls").classList.add("hidden");
919
+ document.querySelector(".add-section").classList.add("hidden");
920
+ loadUserProfile();
921
+ }
922
+ }
923
+
924
+ function escapeHtml(text) {
925
+ const div = document.createElement("div");
926
+ div.textContent = text;
927
+ return div.innerHTML;
928
+ }
929
+
725
930
  document.addEventListener("DOMContentLoaded", async () => {
726
- document.getElementById("tab-project").addEventListener("click", () => switchScope("project"));
727
- document.getElementById("tab-user").addEventListener("click", () => switchScope("user"));
931
+ document.getElementById("tab-project").addEventListener("click", () => switchView("project"));
932
+ document.getElementById("tab-profile").addEventListener("click", () => switchView("profile"));
933
+ document.getElementById("refresh-profile-btn")?.addEventListener("click", refreshProfile);
934
+ document.getElementById("changelog-close")?.addEventListener("click", () => {
935
+ document.getElementById("changelog-modal").classList.add("hidden");
936
+ });
728
937
 
729
938
  document.getElementById("tag-filter").addEventListener("change", () => {
730
939
  state.selectedTag = document.getElementById("tag-filter").value;
@@ -732,8 +941,6 @@ document.addEventListener("DOMContentLoaded", async () => {
732
941
  loadMemories();
733
942
  });
734
943
 
735
- document.getElementById("add-scope").addEventListener("change", handleAddScopeChange);
736
-
737
944
  document.getElementById("search-btn").addEventListener("click", performSearch);
738
945
  document.getElementById("clear-search-btn").addEventListener("click", clearSearch);
739
946
  document.getElementById("search-input").addEventListener("keypress", (e) => {
@@ -27,8 +27,6 @@
27
27
  </div>
28
28
  <div class="stats-bar">
29
29
  <span id="stats-total">Total: 0</span>
30
- <span id="stats-user">User: 0</span>
31
- <span id="stats-project">Project: 0</span>
32
30
  <span id="refresh-indicator" class="hidden"
33
31
  ><i data-lucide="rotate-cw" class="icon icon-spin"></i
34
32
  ></span>
@@ -38,11 +36,11 @@
38
36
  <div class="scope-tabs">
39
37
  <button id="tab-project" class="tab-btn active">
40
38
  <i data-lucide="folder" class="icon"></i>
41
- PROJECT TIMELINE
39
+ PROJECT MEMORIES
42
40
  </button>
43
- <button id="tab-user" class="tab-btn">
41
+ <button id="tab-profile" class="tab-btn">
44
42
  <i data-lucide="user" class="icon"></i>
45
- USER TIMELINE
43
+ USER PROFILE
46
44
  </button>
47
45
  </div>
48
46
 
@@ -99,7 +97,7 @@
99
97
  </div>
100
98
  </div>
101
99
 
102
- <div class="memories-section">
100
+ <div class="memories-section" id="project-section">
103
101
  <div class="section-header">
104
102
  <h2 id="section-title">└─ PROJECT MEMORIES (0) ──</h2>
105
103
  <div class="pagination" id="pagination-top">
@@ -128,19 +126,23 @@
128
126
  </div>
129
127
  </div>
130
128
 
129
+ <div class="profile-section hidden" id="profile-section">
130
+ <div class="section-header">
131
+ <h2>└─ USER PROFILE ──</h2>
132
+ <button id="refresh-profile-btn" class="btn-secondary">
133
+ <i data-lucide="refresh-cw" class="icon"></i> Refresh
134
+ </button>
135
+ </div>
136
+
137
+ <div id="profile-content" class="profile-content">
138
+ <div class="loading">Loading profile...</div>
139
+ </div>
140
+ </div>
141
+
131
142
  <div class="add-section">
132
143
  <h2>└─ ADD NEW MEMORY ──</h2>
133
144
  <form id="add-form">
134
145
  <div class="form-row">
135
- <div class="form-group">
136
- <label>Scope:</label>
137
- <select id="add-scope" required>
138
- <option value="">Select scope</option>
139
- <option value="user">User</option>
140
- <option value="project">Project</option>
141
- </select>
142
- </div>
143
-
144
146
  <div class="form-group">
145
147
  <label>Tag:</label>
146
148
  <select id="add-tag" required>
@@ -201,6 +203,20 @@
201
203
 
202
204
  <div id="toast" class="toast hidden"></div>
203
205
 
206
+ <div id="changelog-modal" class="modal hidden">
207
+ <div class="modal-content">
208
+ <div class="modal-header">
209
+ <h3>Profile Version History</h3>
210
+ <button class="modal-close" id="changelog-close">
211
+ <i data-lucide="x" class="icon"></i>
212
+ </button>
213
+ </div>
214
+ <div id="changelog-list" class="changelog-list">
215
+ <div class="loading">Loading changelog...</div>
216
+ </div>
217
+ </div>
218
+ </div>
219
+
204
220
  <script src="/app.js"></script>
205
221
  </body>
206
222
  </html>