Skip to content

Commit 605023b

Browse files
committed
add auto update notice
1 parent f932847 commit 605023b

3 files changed

Lines changed: 92 additions & 4 deletions

File tree

src/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { stripJsoncComments } from "./services/jsonc.js";
55
import { loadCredentials } from "./services/auth.js";
66

77
const CONFIG_DIR = join(homedir(), ".config", "opencode");
8+
export const PLUGIN_VERSION = "2.0.6";
89
const CONFIG_FILES = [
910
join(CONFIG_DIR, "supermemory.jsonc"),
1011
join(CONFIG_DIR, "supermemory.json"),

src/index.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ import { getTags } from "./services/tags.js";
88
import { stripPrivateContent, isFullyPrivate } from "./services/privacy.js";
99
import { createCompactionHook, type CompactionContext } from "./services/compaction.js";
1010

11-
import { isConfigured, CONFIG } from "./config.js";
11+
import { isConfigured, CONFIG, PLUGIN_VERSION } from "./config.js";
1212
import { log } from "./services/logger.js";
13+
import { checkNpmUpdate, formatUpdateNotice } from "./services/version-check.js";
1314
import type { MemoryScope, MemoryType } from "./types/index.js";
1415

1516
const CODE_BLOCK_PATTERN = /```[\s\S]*?```/g;
@@ -26,6 +27,7 @@ Extract the key information the user wants remembered and save it as a concise,
2627
- Choose an appropriate \`type\`: "preference", "project-config", "learned-pattern", etc.
2728
2829
DO NOT skip this step. The user explicitly asked you to remember.`;
30+
const UPDATE_COMMAND = "bunx opencode-supermemory@latest install";
2931

3032
function removeCodeBlocks(text: string): string {
3133
return text.replace(CODE_BLOCK_PATTERN, "").replace(INLINE_CODE_PATTERN, "");
@@ -36,6 +38,10 @@ function detectMemoryKeyword(text: string): boolean {
3638
return MEMORY_KEYWORD_PATTERN.test(textWithoutCode);
3739
}
3840

41+
function combineContextParts(parts: Array<string | null | undefined>): string {
42+
return parts.map((part) => part?.trim()).filter(Boolean).join("\n\n");
43+
}
44+
3945
export const SupermemoryPlugin: Plugin = async (ctx: PluginInput) => {
4046
const { directory } = ctx;
4147
const tags = getTags(directory);
@@ -128,6 +134,11 @@ export const SupermemoryPlugin: Plugin = async (ctx: PluginInput) => {
128134
injectedSessions.add(input.sessionID);
129135

130136
let memoryContext = "";
137+
const updateCheck = checkNpmUpdate(
138+
"opencode-supermemory",
139+
PLUGIN_VERSION,
140+
UPDATE_COMMAND
141+
).then((info) => (info ? formatUpdateNotice(info) : null));
131142

132143
if (CONFIG.autoRecallEveryPrompt) {
133144
const [profileResult, userMemoriesResult, projectMemoriesListResult] = await Promise.all([
@@ -163,13 +174,16 @@ export const SupermemoryPlugin: Plugin = async (ctx: PluginInput) => {
163174
memoryContext = formatContextForPrompt(profile, { results: [] }, { results: [] });
164175
}
165176

166-
if (memoryContext) {
177+
const updateNotice = await updateCheck;
178+
const firstMessageContext = combineContextParts([memoryContext, updateNotice]);
179+
180+
if (firstMessageContext) {
167181
const contextPart: Part = {
168182
id: `prt_supermemory-context-${Date.now()}`,
169183
sessionID: input.sessionID,
170184
messageID: output.message.id,
171185
type: "text",
172-
text: memoryContext,
186+
text: firstMessageContext,
173187
synthetic: true,
174188
};
175189

@@ -178,7 +192,7 @@ export const SupermemoryPlugin: Plugin = async (ctx: PluginInput) => {
178192
const duration = Date.now() - start;
179193
log("chat.message: context injected", {
180194
duration,
181-
contextLength: memoryContext.length,
195+
contextLength: firstMessageContext.length,
182196
});
183197
}
184198
}

src/services/version-check.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
const NPM_REGISTRY_URL = "https://registry.npmjs.org";
2+
const CHECK_TIMEOUT_MS = 3000;
3+
4+
export interface UpdateInfo {
5+
currentVersion: string;
6+
latestVersion: string;
7+
updateCommand: string;
8+
}
9+
10+
function parseVersion(version: string): { parts: number[]; prerelease: string | null } | null {
11+
const normalized = version.trim().replace(/^v/i, "");
12+
const match = normalized.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/);
13+
if (!match) return null;
14+
15+
return {
16+
parts: [Number(match[1]), Number(match[2]), Number(match[3])],
17+
prerelease: match[4] ?? null,
18+
};
19+
}
20+
21+
function isVersionNewer(latestVersion: string, currentVersion: string): boolean {
22+
const latest = parseVersion(latestVersion);
23+
const current = parseVersion(currentVersion);
24+
if (!latest || !current) return latestVersion !== currentVersion;
25+
26+
for (let i = 0; i < 3; i++) {
27+
const latestPart = latest.parts[i] ?? 0;
28+
const currentPart = current.parts[i] ?? 0;
29+
if (latestPart > currentPart) return true;
30+
if (latestPart < currentPart) return false;
31+
}
32+
33+
if (!latest.prerelease && current.prerelease) return true;
34+
if (latest.prerelease && !current.prerelease) return false;
35+
return latest.prerelease !== current.prerelease && latest.prerelease !== null;
36+
}
37+
38+
export async function checkNpmUpdate(
39+
packageName: string,
40+
currentVersion: string,
41+
updateCommand: string,
42+
): Promise<UpdateInfo | null> {
43+
const controller = new AbortController();
44+
const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS);
45+
46+
try {
47+
const encodedPackage = packageName.startsWith("@")
48+
? packageName.replace("/", "%2F")
49+
: encodeURIComponent(packageName);
50+
const response = await fetch(`${NPM_REGISTRY_URL}/${encodedPackage}/latest`, {
51+
signal: controller.signal,
52+
});
53+
if (!response.ok) return null;
54+
55+
const data = (await response.json()) as { version?: unknown };
56+
const latestVersion = typeof data.version === "string" ? data.version : null;
57+
if (!latestVersion || !isVersionNewer(latestVersion, currentVersion)) return null;
58+
59+
return { currentVersion, latestVersion, updateCommand };
60+
} catch {
61+
return null;
62+
} finally {
63+
clearTimeout(timeout);
64+
}
65+
}
66+
67+
export function formatUpdateNotice(info: UpdateInfo): string {
68+
return [
69+
"[SUPERMEMORY UPDATE]",
70+
`Supermemory update available: v${info.currentVersion} -> v${info.latestVersion}`,
71+
`Run: ${info.updateCommand}`,
72+
].join("\n");
73+
}

0 commit comments

Comments
 (0)