@memtensor/memos-local-openclaw-plugin 1.0.1 → 1.0.2-beta.2
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/README.md +1 -1
- package/dist/viewer/html.d.ts +1 -1
- package/dist/viewer/html.d.ts.map +1 -1
- package/dist/viewer/html.js +25 -2
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +2 -0
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +53 -0
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +74 -26
- package/package.json +1 -1
- package/scripts/postinstall.cjs +72 -0
- package/src/viewer/html.ts +25 -2
- package/src/viewer/server.ts +51 -0
package/scripts/postinstall.cjs
CHANGED
|
@@ -33,6 +33,78 @@ ${CYAN}${BOLD}┌─────────────────────
|
|
|
33
33
|
log(`Plugin dir: ${DIM}${pluginDir}${RESET}`);
|
|
34
34
|
log(`Node: ${process.version} Platform: ${process.platform}-${process.arch}`);
|
|
35
35
|
|
|
36
|
+
/* ═══════════════════════════════════════════════════════════
|
|
37
|
+
* Pre-phase: Clean stale build artifacts on upgrade
|
|
38
|
+
* When openclaw re-installs a new version over an existing
|
|
39
|
+
* extensions dir, old dist/node_modules can conflict.
|
|
40
|
+
* We nuke them so npm install gets a clean slate, but
|
|
41
|
+
* preserve user data (.env, data/).
|
|
42
|
+
* ═══════════════════════════════════════════════════════════ */
|
|
43
|
+
|
|
44
|
+
function cleanStaleArtifacts() {
|
|
45
|
+
const isExtensionsDir = pluginDir.includes(path.join(".openclaw", "extensions"));
|
|
46
|
+
if (!isExtensionsDir) return;
|
|
47
|
+
|
|
48
|
+
const pkgPath = path.join(pluginDir, "package.json");
|
|
49
|
+
if (!fs.existsSync(pkgPath)) return;
|
|
50
|
+
|
|
51
|
+
let installedVer = "unknown";
|
|
52
|
+
try {
|
|
53
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
54
|
+
installedVer = pkg.version || "unknown";
|
|
55
|
+
} catch { /* ignore */ }
|
|
56
|
+
|
|
57
|
+
const markerPath = path.join(pluginDir, ".installed-version");
|
|
58
|
+
let prevVer = "";
|
|
59
|
+
try { prevVer = fs.readFileSync(markerPath, "utf-8").trim(); } catch { /* first install */ }
|
|
60
|
+
|
|
61
|
+
if (prevVer === installedVer) {
|
|
62
|
+
log(`Version unchanged (${installedVer}), skipping artifact cleanup.`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (prevVer) {
|
|
67
|
+
log(`Upgrade detected: ${DIM}${prevVer}${RESET} → ${GREEN}${installedVer}${RESET}`);
|
|
68
|
+
} else {
|
|
69
|
+
log(`Fresh install: ${GREEN}${installedVer}${RESET}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const dirsToClean = ["dist", "node_modules"];
|
|
73
|
+
let cleaned = 0;
|
|
74
|
+
for (const dir of dirsToClean) {
|
|
75
|
+
const full = path.join(pluginDir, dir);
|
|
76
|
+
if (fs.existsSync(full)) {
|
|
77
|
+
try {
|
|
78
|
+
fs.rmSync(full, { recursive: true, force: true });
|
|
79
|
+
ok(`Cleaned stale ${dir}/`);
|
|
80
|
+
cleaned++;
|
|
81
|
+
} catch (e) {
|
|
82
|
+
warn(`Could not remove ${dir}/: ${e.message}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const filesToClean = ["package-lock.json"];
|
|
88
|
+
for (const f of filesToClean) {
|
|
89
|
+
const full = path.join(pluginDir, f);
|
|
90
|
+
if (fs.existsSync(full)) {
|
|
91
|
+
try { fs.unlinkSync(full); ok(`Removed stale ${f}`); cleaned++; } catch { /* ignore */ }
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try { fs.writeFileSync(markerPath, installedVer + "\n", "utf-8"); } catch { /* ignore */ }
|
|
96
|
+
|
|
97
|
+
if (cleaned > 0) {
|
|
98
|
+
ok(`Cleaned ${cleaned} stale artifact(s). Fresh install will follow.`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
cleanStaleArtifacts();
|
|
104
|
+
} catch (e) {
|
|
105
|
+
warn(`Artifact cleanup error: ${e.message}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
36
108
|
/* ═══════════════════════════════════════════════════════════
|
|
37
109
|
* Phase 0: Ensure all dependencies are installed
|
|
38
110
|
* ═══════════════════════════════════════════════════════════ */
|
package/src/viewer/html.ts
CHANGED
|
@@ -1615,7 +1615,10 @@ const I18N={
|
|
|
1615
1615
|
'skill.cancel':'Cancel',
|
|
1616
1616
|
'skill.delete.confirm':'Are you sure you want to delete this skill? This will also remove all associated files and cannot be undone.',
|
|
1617
1617
|
'skill.delete.error':'Failed to delete skill: ',
|
|
1618
|
-
'skill.save.error':'Failed to save skill: '
|
|
1618
|
+
'skill.save.error':'Failed to save skill: ',
|
|
1619
|
+
'update.available':'New version available',
|
|
1620
|
+
'update.run':'Run',
|
|
1621
|
+
'update.dismiss':'Dismiss'
|
|
1619
1622
|
},
|
|
1620
1623
|
zh:{
|
|
1621
1624
|
'title':'OpenClaw 记忆',
|
|
@@ -1921,7 +1924,10 @@ const I18N={
|
|
|
1921
1924
|
'skill.cancel':'取消',
|
|
1922
1925
|
'skill.delete.confirm':'确定要删除此技能吗?关联的文件也会被删除,此操作不可撤销。',
|
|
1923
1926
|
'skill.delete.error':'删除技能失败:',
|
|
1924
|
-
'skill.save.error':'保存技能失败:'
|
|
1927
|
+
'skill.save.error':'保存技能失败:',
|
|
1928
|
+
'update.available':'发现新版本',
|
|
1929
|
+
'update.run':'执行命令',
|
|
1930
|
+
'update.dismiss':'关闭'
|
|
1925
1931
|
}
|
|
1926
1932
|
};
|
|
1927
1933
|
const LANG_KEY='memos-viewer-lang';
|
|
@@ -3277,6 +3283,7 @@ async function loadAll(){
|
|
|
3277
3283
|
await Promise.all([loadStats(),loadMemories()]);
|
|
3278
3284
|
checkMigrateStatus();
|
|
3279
3285
|
connectPPSSE();
|
|
3286
|
+
checkForUpdate();
|
|
3280
3287
|
}
|
|
3281
3288
|
|
|
3282
3289
|
async function loadStats(){
|
|
@@ -4213,6 +4220,22 @@ function initViewerTheme(){const s=localStorage.getItem(VIEWER_THEME_KEY);const
|
|
|
4213
4220
|
function toggleViewerTheme(){const el=document.documentElement;const cur=el.getAttribute('data-theme')||'dark';const next=cur==='dark'?'light':'dark';el.setAttribute('data-theme',next);localStorage.setItem(VIEWER_THEME_KEY,next);}
|
|
4214
4221
|
initViewerTheme();
|
|
4215
4222
|
|
|
4223
|
+
/* ─── Update check ─── */
|
|
4224
|
+
async function checkForUpdate(){
|
|
4225
|
+
try{
|
|
4226
|
+
const r=await fetch('/api/update-check');
|
|
4227
|
+
if(!r.ok)return;
|
|
4228
|
+
const d=await r.json();
|
|
4229
|
+
if(!d.updateAvailable)return;
|
|
4230
|
+
const banner=document.createElement('div');
|
|
4231
|
+
banner.id='updateBanner';
|
|
4232
|
+
banner.style.cssText='position:fixed;top:0;left:0;right:0;z-index:9999;background:linear-gradient(135deg,#f59e0b,#d97706);color:#fff;padding:10px 20px;display:flex;align-items:center;justify-content:space-between;font-size:14px;box-shadow:0 2px 8px rgba(0,0,0,.25)';
|
|
4233
|
+
banner.innerHTML='<span>🔔 '+t('update.available')+': <b>v'+esc(d.current)+'</b> → <b>v'+esc(d.latest)+'</b> — '+t('update.run')+': <code style="background:rgba(0,0,0,.2);padding:2px 8px;border-radius:4px;margin:0 4px">openclaw plugins install '+esc(d.packageName)+'</code></span><button onclick="this.parentElement.remove();document.body.style.paddingTop=\\'\\';" style="background:none;border:none;color:#fff;font-size:18px;cursor:pointer;padding:0 4px">×</button>';
|
|
4234
|
+
document.body.prepend(banner);
|
|
4235
|
+
document.body.style.paddingTop='48px';
|
|
4236
|
+
}catch(e){}
|
|
4237
|
+
}
|
|
4238
|
+
|
|
4216
4239
|
/* ─── Init ─── */
|
|
4217
4240
|
document.getElementById('modalOverlay').addEventListener('click',e=>{if(e.target.id==='modalOverlay')closeModal()});
|
|
4218
4241
|
document.getElementById('searchInput').addEventListener('keydown',e=>{if(e.key==='Escape'){e.target.value='';loadMemories()}});
|
package/src/viewer/server.ts
CHANGED
|
@@ -217,6 +217,7 @@ export class ViewerServer {
|
|
|
217
217
|
else if (p === "/api/config" && req.method === "PUT") this.handleSaveConfig(req, res);
|
|
218
218
|
else if (p === "/api/test-model" && req.method === "POST") this.handleTestModel(req, res);
|
|
219
219
|
else if (p === "/api/fallback-model" && req.method === "GET") this.serveFallbackModel(res);
|
|
220
|
+
else if (p === "/api/update-check" && req.method === "GET") this.handleUpdateCheck(res);
|
|
220
221
|
else if (p === "/api/auth/logout" && req.method === "POST") this.handleLogout(req, res);
|
|
221
222
|
else if (p === "/api/migrate/scan" && req.method === "GET") this.handleMigrateScan(res);
|
|
222
223
|
else if (p === "/api/migrate/start" && req.method === "POST") this.handleMigrateStart(req, res);
|
|
@@ -1080,6 +1081,56 @@ export class ViewerServer {
|
|
|
1080
1081
|
}
|
|
1081
1082
|
}
|
|
1082
1083
|
|
|
1084
|
+
private findPluginPackageJson(): string | null {
|
|
1085
|
+
let dir = __dirname;
|
|
1086
|
+
for (let i = 0; i < 6; i++) {
|
|
1087
|
+
const candidate = path.join(dir, "package.json");
|
|
1088
|
+
if (fs.existsSync(candidate)) {
|
|
1089
|
+
try {
|
|
1090
|
+
const pkg = JSON.parse(fs.readFileSync(candidate, "utf-8"));
|
|
1091
|
+
if (pkg.name && pkg.name.includes("memos-local")) return candidate;
|
|
1092
|
+
} catch { /* skip */ }
|
|
1093
|
+
}
|
|
1094
|
+
dir = path.dirname(dir);
|
|
1095
|
+
}
|
|
1096
|
+
return null;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
private async handleUpdateCheck(res: http.ServerResponse): Promise<void> {
|
|
1100
|
+
try {
|
|
1101
|
+
const pkgPath = this.findPluginPackageJson();
|
|
1102
|
+
if (!pkgPath) {
|
|
1103
|
+
this.jsonResponse(res, { updateAvailable: false, error: "package.json not found" });
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
1107
|
+
const current = pkg.version as string;
|
|
1108
|
+
const name = pkg.name as string;
|
|
1109
|
+
if (!current || !name) {
|
|
1110
|
+
this.jsonResponse(res, { updateAvailable: false, current });
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
const npmResp = await fetch(`https://registry.npmjs.org/${name}/latest`, {
|
|
1114
|
+
signal: AbortSignal.timeout(6_000),
|
|
1115
|
+
});
|
|
1116
|
+
if (!npmResp.ok) {
|
|
1117
|
+
this.jsonResponse(res, { updateAvailable: false, current });
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
const data = await npmResp.json() as { version?: string };
|
|
1121
|
+
const latest = data.version ?? current;
|
|
1122
|
+
this.jsonResponse(res, {
|
|
1123
|
+
updateAvailable: latest !== current,
|
|
1124
|
+
current,
|
|
1125
|
+
latest,
|
|
1126
|
+
packageName: name,
|
|
1127
|
+
});
|
|
1128
|
+
} catch (e) {
|
|
1129
|
+
this.log.warn(`handleUpdateCheck error: ${e}`);
|
|
1130
|
+
this.jsonResponse(res, { updateAvailable: false, error: String(e) });
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1083
1134
|
private async testEmbeddingModel(provider: string, model: string, endpoint: string, apiKey: string): Promise<void> {
|
|
1084
1135
|
if (provider === "local") {
|
|
1085
1136
|
return;
|