context-mode 1.0.96 → 1.0.98
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.openclaw-plugin/openclaw.plugin.json +1 -1
- package/.openclaw-plugin/package.json +1 -1
- package/build/server.js +22 -8
- package/cli.bundle.mjs +75 -75
- package/hooks/core/mcp-ready.mjs +57 -14
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/scripts/postinstall.mjs +8 -26
- package/server.bundle.mjs +58 -58
- package/start.mjs +60 -29
package/start.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execSync } from "node:child_process";
|
|
3
|
-
import { existsSync, chmodSync, readFileSync, writeFileSync, readdirSync, symlinkSync, mkdirSync, lstatSync } from "node:fs";
|
|
4
|
-
import { dirname, resolve, join } from "node:path";
|
|
3
|
+
import { existsSync, chmodSync, readFileSync, writeFileSync, readdirSync, symlinkSync, mkdirSync, lstatSync, unlinkSync } from "node:fs";
|
|
4
|
+
import { dirname, resolve, join, sep } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
|
|
@@ -59,7 +59,7 @@ if (cacheMatch) {
|
|
|
59
59
|
if (newest && newest !== myVersion) {
|
|
60
60
|
const ip = JSON.parse(readFileSync(ipPath, "utf-8"));
|
|
61
61
|
for (const [key, entries] of Object.entries(ip.plugins || {})) {
|
|
62
|
-
if (
|
|
62
|
+
if (key !== "context-mode@context-mode") continue;
|
|
63
63
|
for (const entry of entries) {
|
|
64
64
|
entry.installPath = resolve(cacheParent, newest);
|
|
65
65
|
entry.version = newest;
|
|
@@ -71,19 +71,23 @@ if (cacheMatch) {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
// Reverse heal: if registry points to non-existent dir, create symlink to us
|
|
74
|
+
const cacheRoot = resolve(homedir(), ".claude", "plugins", "cache");
|
|
74
75
|
if (existsSync(ipPath)) {
|
|
75
76
|
const ip = JSON.parse(readFileSync(ipPath, "utf-8"));
|
|
76
77
|
for (const [key, entries] of Object.entries(ip.plugins || {})) {
|
|
77
|
-
if (
|
|
78
|
+
if (key !== "context-mode@context-mode") continue;
|
|
78
79
|
for (const entry of entries) {
|
|
79
80
|
const rp = entry.installPath;
|
|
80
|
-
if (rp
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
if (!rp || existsSync(rp) || rp === __dirname) continue;
|
|
82
|
+
// Path traversal guard: only allow paths inside plugin cache
|
|
83
|
+
if (!resolve(rp).startsWith(cacheRoot + sep)) continue;
|
|
84
|
+
try {
|
|
85
|
+
// Remove dangling symlink before creating new one
|
|
86
|
+
try { if (lstatSync(rp).isSymbolicLink()) unlinkSync(rp); } catch {}
|
|
87
|
+
const rpParent = dirname(rp);
|
|
88
|
+
if (!existsSync(rpParent)) mkdirSync(rpParent, { recursive: true });
|
|
89
|
+
symlinkSync(__dirname, rp, process.platform === "win32" ? "junction" : undefined);
|
|
90
|
+
} catch { /* best effort */ }
|
|
87
91
|
}
|
|
88
92
|
}
|
|
89
93
|
}
|
|
@@ -92,43 +96,70 @@ if (cacheMatch) {
|
|
|
92
96
|
}
|
|
93
97
|
}
|
|
94
98
|
|
|
95
|
-
// ── Self-heal Layer 4: Deploy global SessionStart hook ──
|
|
99
|
+
// ── Self-heal Layer 4: Deploy global SessionStart hook + register in settings.json ──
|
|
96
100
|
// This hook lives outside the plugin directory (~/.claude/hooks/) so it works
|
|
97
101
|
// even when the plugin cache is completely broken. It creates symlinks for any
|
|
98
102
|
// missing plugin cache directories on every session start.
|
|
103
|
+
// Pure Node.js — no bash dependency. Works on Windows, macOS (SIP), Linux.
|
|
99
104
|
try {
|
|
100
105
|
const globalHooksDir = resolve(homedir(), ".claude", "hooks");
|
|
101
|
-
const healHookPath = resolve(globalHooksDir, "context-mode-cache-heal.
|
|
106
|
+
const healHookPath = resolve(globalHooksDir, "context-mode-cache-heal.mjs");
|
|
107
|
+
// Clean up old bash version if it exists
|
|
108
|
+
const oldBashHook = resolve(globalHooksDir, "context-mode-cache-heal.sh");
|
|
109
|
+
if (existsSync(oldBashHook)) {
|
|
110
|
+
try { unlinkSync(oldBashHook); } catch {}
|
|
111
|
+
}
|
|
102
112
|
if (!existsSync(healHookPath)) {
|
|
103
113
|
if (!existsSync(globalHooksDir)) mkdirSync(globalHooksDir, { recursive: true });
|
|
104
|
-
const healScript = `#!/usr/bin/env
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
node
|
|
111
|
-
const fs=require("fs"),path=require("path");
|
|
114
|
+
const healScript = `#!/usr/bin/env node
|
|
115
|
+
// context-mode plugin cache self-heal (auto-deployed)
|
|
116
|
+
// Fixes anthropics/claude-code#46915: auto-update breaks CLAUDE_PLUGIN_ROOT
|
|
117
|
+
// Pure Node.js — no bash/shell dependency.
|
|
118
|
+
import{existsSync,readdirSync,statSync,symlinkSync,lstatSync,unlinkSync,readFileSync}from"node:fs";
|
|
119
|
+
import{dirname,join,resolve,sep}from"node:path";
|
|
120
|
+
import{homedir}from"node:os";
|
|
112
121
|
try{
|
|
113
|
-
const
|
|
122
|
+
const f=resolve(homedir(),".claude","plugins","installed_plugins.json");
|
|
123
|
+
if(!existsSync(f))process.exit(0);
|
|
124
|
+
const cacheRoot=resolve(homedir(),".claude","plugins","cache");
|
|
125
|
+
const ip=JSON.parse(readFileSync(f,"utf-8"));
|
|
114
126
|
for(const[k,es]of Object.entries(ip.plugins||{})){
|
|
115
|
-
if(
|
|
127
|
+
if(k!=="context-mode@context-mode")continue;
|
|
116
128
|
for(const e of es){
|
|
117
129
|
const p=e.installPath;
|
|
118
|
-
if(!p||
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
130
|
+
if(!p||existsSync(p))continue;
|
|
131
|
+
if(!resolve(p).startsWith(cacheRoot+sep))continue;
|
|
132
|
+
const parent=dirname(p);
|
|
133
|
+
if(!existsSync(parent))continue;
|
|
134
|
+
try{if(lstatSync(p).isSymbolicLink())unlinkSync(p)}catch{}
|
|
135
|
+
const dirs=readdirSync(parent).filter(d=>/^\\d+\\.\\d+/.test(d)&&statSync(join(parent,d)).isDirectory());
|
|
122
136
|
if(!dirs.length)continue;
|
|
123
137
|
dirs.sort((a,b)=>{const pa=a.split(".").map(Number),pb=b.split(".").map(Number);for(let i=0;i<3;i++){if((pa[i]||0)!==(pb[i]||0))return(pa[i]||0)-(pb[i]||0)}return 0});
|
|
124
|
-
try{
|
|
138
|
+
try{symlinkSync(join(parent,dirs[dirs.length-1]),p,process.platform==="win32"?"junction":undefined)}catch{}
|
|
125
139
|
}
|
|
126
140
|
}
|
|
127
141
|
}catch{}
|
|
128
|
-
' "$PLUGINS_FILE" 2>/dev/null || true
|
|
129
142
|
`;
|
|
130
143
|
writeFileSync(healHookPath, healScript, { mode: 0o755 });
|
|
131
144
|
}
|
|
145
|
+
// Register the hook in ~/.claude/settings.json (Claude Code doesn't auto-discover hook files)
|
|
146
|
+
const settingsPath = resolve(homedir(), ".claude", "settings.json");
|
|
147
|
+
if (existsSync(settingsPath)) {
|
|
148
|
+
const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
149
|
+
const hooks = settings.hooks ?? {};
|
|
150
|
+
const sessionStart = hooks.SessionStart ?? [];
|
|
151
|
+
const alreadyRegistered = sessionStart.some((h) =>
|
|
152
|
+
h.hooks?.some((hh) => hh.command?.includes("context-mode-cache-heal")),
|
|
153
|
+
);
|
|
154
|
+
if (!alreadyRegistered) {
|
|
155
|
+
sessionStart.push({
|
|
156
|
+
hooks: [{ type: "command", command: `node ${healHookPath}` }],
|
|
157
|
+
});
|
|
158
|
+
hooks.SessionStart = sessionStart;
|
|
159
|
+
settings.hooks = hooks;
|
|
160
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
161
|
+
}
|
|
162
|
+
}
|
|
132
163
|
} catch { /* best effort */ }
|
|
133
164
|
|
|
134
165
|
// Ensure native dependencies + ABI compatibility (shared with hooks via ensure-deps.mjs)
|