context-mode 1.0.114 → 1.0.115
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 +6 -2
- package/build/util/project-dir.d.ts +32 -2
- package/build/util/project-dir.js +109 -3
- package/cli.bundle.mjs +103 -102
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server.bundle.mjs +98 -97
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Claude Code plugins by Mert Koseoğlu",
|
|
9
|
-
"version": "1.0.
|
|
9
|
+
"version": "1.0.115"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
13
13
|
"name": "context-mode",
|
|
14
14
|
"source": "./",
|
|
15
15
|
"description": "Claude Code MCP plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
|
|
16
|
-
"version": "1.0.
|
|
16
|
+
"version": "1.0.115",
|
|
17
17
|
"author": {
|
|
18
18
|
"name": "Mert Koseoğlu"
|
|
19
19
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.115",
|
|
4
4
|
"description": "MCP server that saves 98% of your context window with session continuity. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and automatic state restore across compactions.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Mert Koseoğlu",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "Context Mode",
|
|
4
4
|
"kind": "tool",
|
|
5
5
|
"description": "OpenClaw plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
|
|
6
|
-
"version": "1.0.
|
|
6
|
+
"version": "1.0.115",
|
|
7
7
|
"sandbox": {
|
|
8
8
|
"mode": "permissive",
|
|
9
9
|
"filesystem_access": "full",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.115",
|
|
4
4
|
"description": "OpenClaw plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Mert Koseoğlu",
|
package/build/server.js
CHANGED
|
@@ -184,12 +184,16 @@ function getSessionDir() {
|
|
|
184
184
|
function getProjectDir() {
|
|
185
185
|
// Delegated to the shared resolver so the env-var chain rejects plugin
|
|
186
186
|
// install paths (set by a prior MCP boot's start.mjs after `/ctx-upgrade`)
|
|
187
|
-
// and prefers the shell-set PWD before the chdir'd cwd.
|
|
188
|
-
//
|
|
187
|
+
// and prefers the shell-set PWD before the chdir'd cwd. v1.0.115 adds
|
|
188
|
+
// the Claude Code transcript heuristic — read `cwd` from the most-recently-
|
|
189
|
+
// modified `~/.claude/projects/<encoded>/<session>.jsonl` to recover the
|
|
190
|
+
// real project dir when MCP was launched from a non-project cwd (desktop-
|
|
191
|
+
// app launch, /ctx-upgrade respawn). See src/util/project-dir.ts.
|
|
189
192
|
return resolveProjectDir({
|
|
190
193
|
env: process.env,
|
|
191
194
|
cwd: process.cwd(),
|
|
192
195
|
pwd: process.env.PWD,
|
|
196
|
+
transcriptsRoot: join(homedir(), ".claude", "projects"),
|
|
193
197
|
});
|
|
194
198
|
}
|
|
195
199
|
/**
|
|
@@ -26,6 +26,30 @@
|
|
|
26
26
|
* suffix pattern.
|
|
27
27
|
*/
|
|
28
28
|
export declare function isPluginInstallPath(p: string): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Read the per-session project dir from Claude Code's transcript files.
|
|
31
|
+
*
|
|
32
|
+
* Claude Code writes session transcripts under
|
|
33
|
+
* `~/.claude/projects/<encoded-cwd>/<session-id>.jsonl`. Each line is a JSON
|
|
34
|
+
* event; an early line (typically line 2) carries a `cwd` field with the
|
|
35
|
+
* literal project directory the session is running against. The encoded dir
|
|
36
|
+
* name itself is lossy (`/` and `.` both become `-`), so we read the JSONL.
|
|
37
|
+
*
|
|
38
|
+
* This is the strongest available signal when Claude Code does NOT propagate
|
|
39
|
+
* `CLAUDE_PROJECT_DIR` to the spawned MCP env (the common case when Claude
|
|
40
|
+
* Code is launched from the desktop app rather than `cd <project> && claude`).
|
|
41
|
+
*
|
|
42
|
+
* Returns `undefined` when no transcript exists, the projects dir is empty,
|
|
43
|
+
* or no transcript carries a `cwd` field — caller falls through.
|
|
44
|
+
*
|
|
45
|
+
* Multi-window safety: the most-recently-modified jsonl wins. When the user
|
|
46
|
+
* actively talks to one Claude Code window, that window's transcript is the
|
|
47
|
+
* one being written to RIGHT NOW, so its mtime is freshest. Other windows'
|
|
48
|
+
* transcripts have older mtimes and are correctly ignored.
|
|
49
|
+
*/
|
|
50
|
+
export declare function resolveProjectDirFromTranscript(opts: {
|
|
51
|
+
projectsRoot: string;
|
|
52
|
+
}): string | undefined;
|
|
29
53
|
/**
|
|
30
54
|
* Pure project-dir resolver. Mirror of the env-var chain inside
|
|
31
55
|
* `src/server.ts getProjectDir()`, but takes its inputs explicitly so the
|
|
@@ -34,10 +58,14 @@ export declare function isPluginInstallPath(p: string): boolean;
|
|
|
34
58
|
* Resolution order:
|
|
35
59
|
* 1. Adapter-priority env vars (CLAUDE / GEMINI / VSCODE / OPENCODE / PI /
|
|
36
60
|
* IDEA / CONTEXT_MODE) — first non-empty AND non-plugin-path wins.
|
|
37
|
-
* 2.
|
|
61
|
+
* 2. Claude Code transcript heuristic — read `cwd` from the most-recently-
|
|
62
|
+
* modified `~/.claude/projects/<encoded>/<session>.jsonl`. This is the
|
|
63
|
+
* most reliable signal when Claude Code launched MCP from a non-project
|
|
64
|
+
* cwd (desktop-app launch, `/ctx-upgrade` respawn, etc.).
|
|
65
|
+
* 3. `process.env.PWD` — shell-set, NOT updated by `process.chdir()`, so
|
|
38
66
|
* it survives the `start.mjs` chdir into the plugin dir. Skipped if
|
|
39
67
|
* it too points at a plugin install path.
|
|
40
|
-
*
|
|
68
|
+
* 4. `cwd` — last resort. Returned even if it is a plugin path; the
|
|
41
69
|
* caller is responsible for rendering a graceful "no project context"
|
|
42
70
|
* message rather than panicking. Keeping the function total preserves
|
|
43
71
|
* operation of project-independent tools (sandbox execute, fetch).
|
|
@@ -46,4 +74,6 @@ export declare function resolveProjectDir(opts: {
|
|
|
46
74
|
env: Record<string, string | undefined>;
|
|
47
75
|
cwd: string;
|
|
48
76
|
pwd: string | undefined;
|
|
77
|
+
/** Optional override; production code passes `~/.claude/projects`. */
|
|
78
|
+
transcriptsRoot?: string;
|
|
49
79
|
}): string;
|
|
@@ -30,6 +30,103 @@ export function isPluginInstallPath(p) {
|
|
|
30
30
|
return false;
|
|
31
31
|
return /[/\\]\.claude[/\\]plugins[/\\](cache|marketplaces)[/\\]/.test(p);
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Read the per-session project dir from Claude Code's transcript files.
|
|
35
|
+
*
|
|
36
|
+
* Claude Code writes session transcripts under
|
|
37
|
+
* `~/.claude/projects/<encoded-cwd>/<session-id>.jsonl`. Each line is a JSON
|
|
38
|
+
* event; an early line (typically line 2) carries a `cwd` field with the
|
|
39
|
+
* literal project directory the session is running against. The encoded dir
|
|
40
|
+
* name itself is lossy (`/` and `.` both become `-`), so we read the JSONL.
|
|
41
|
+
*
|
|
42
|
+
* This is the strongest available signal when Claude Code does NOT propagate
|
|
43
|
+
* `CLAUDE_PROJECT_DIR` to the spawned MCP env (the common case when Claude
|
|
44
|
+
* Code is launched from the desktop app rather than `cd <project> && claude`).
|
|
45
|
+
*
|
|
46
|
+
* Returns `undefined` when no transcript exists, the projects dir is empty,
|
|
47
|
+
* or no transcript carries a `cwd` field — caller falls through.
|
|
48
|
+
*
|
|
49
|
+
* Multi-window safety: the most-recently-modified jsonl wins. When the user
|
|
50
|
+
* actively talks to one Claude Code window, that window's transcript is the
|
|
51
|
+
* one being written to RIGHT NOW, so its mtime is freshest. Other windows'
|
|
52
|
+
* transcripts have older mtimes and are correctly ignored.
|
|
53
|
+
*/
|
|
54
|
+
export function resolveProjectDirFromTranscript(opts) {
|
|
55
|
+
// Inline imports kept private to this function — keeps the module test-
|
|
56
|
+
// friendly when fs is stubbed at the call sites that don't use this path.
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
58
|
+
const fs = require("node:fs");
|
|
59
|
+
const path = require("node:path");
|
|
60
|
+
if (!fs.existsSync(opts.projectsRoot))
|
|
61
|
+
return undefined;
|
|
62
|
+
let bestPath;
|
|
63
|
+
let bestMtime = 0;
|
|
64
|
+
try {
|
|
65
|
+
for (const dir of fs.readdirSync(opts.projectsRoot)) {
|
|
66
|
+
const dirPath = path.join(opts.projectsRoot, dir);
|
|
67
|
+
let stat;
|
|
68
|
+
try {
|
|
69
|
+
stat = fs.statSync(dirPath);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (!stat.isDirectory())
|
|
75
|
+
continue;
|
|
76
|
+
let files;
|
|
77
|
+
try {
|
|
78
|
+
files = fs.readdirSync(dirPath);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
for (const f of files) {
|
|
84
|
+
if (!f.endsWith(".jsonl"))
|
|
85
|
+
continue;
|
|
86
|
+
const fp = path.join(dirPath, f);
|
|
87
|
+
try {
|
|
88
|
+
const m = fs.statSync(fp).mtimeMs;
|
|
89
|
+
if (m > bestMtime) {
|
|
90
|
+
bestMtime = m;
|
|
91
|
+
bestPath = fp;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch { /* skip */ }
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
if (!bestPath)
|
|
102
|
+
return undefined;
|
|
103
|
+
// Read first ~10 lines until we find a cwd field. The jsonl is
|
|
104
|
+
// append-only and can be huge (60+ MB on long sessions) — never load it
|
|
105
|
+
// into memory; stream a small head buffer.
|
|
106
|
+
try {
|
|
107
|
+
const fd = fs.openSync(bestPath, "r");
|
|
108
|
+
try {
|
|
109
|
+
const buf = Buffer.alloc(8192);
|
|
110
|
+
const bytes = fs.readSync(fd, buf, 0, buf.length, 0);
|
|
111
|
+
const text = buf.subarray(0, bytes).toString("utf-8");
|
|
112
|
+
for (const line of text.split("\n").slice(0, 10)) {
|
|
113
|
+
if (!line.trim())
|
|
114
|
+
continue;
|
|
115
|
+
try {
|
|
116
|
+
const obj = JSON.parse(line);
|
|
117
|
+
if (typeof obj.cwd === "string" && obj.cwd.length > 0)
|
|
118
|
+
return obj.cwd;
|
|
119
|
+
}
|
|
120
|
+
catch { /* skip malformed line */ }
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
fs.closeSync(fd);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch { /* file vanished mid-read */ }
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
33
130
|
/**
|
|
34
131
|
* Pure project-dir resolver. Mirror of the env-var chain inside
|
|
35
132
|
* `src/server.ts getProjectDir()`, but takes its inputs explicitly so the
|
|
@@ -38,16 +135,20 @@ export function isPluginInstallPath(p) {
|
|
|
38
135
|
* Resolution order:
|
|
39
136
|
* 1. Adapter-priority env vars (CLAUDE / GEMINI / VSCODE / OPENCODE / PI /
|
|
40
137
|
* IDEA / CONTEXT_MODE) — first non-empty AND non-plugin-path wins.
|
|
41
|
-
* 2.
|
|
138
|
+
* 2. Claude Code transcript heuristic — read `cwd` from the most-recently-
|
|
139
|
+
* modified `~/.claude/projects/<encoded>/<session>.jsonl`. This is the
|
|
140
|
+
* most reliable signal when Claude Code launched MCP from a non-project
|
|
141
|
+
* cwd (desktop-app launch, `/ctx-upgrade` respawn, etc.).
|
|
142
|
+
* 3. `process.env.PWD` — shell-set, NOT updated by `process.chdir()`, so
|
|
42
143
|
* it survives the `start.mjs` chdir into the plugin dir. Skipped if
|
|
43
144
|
* it too points at a plugin install path.
|
|
44
|
-
*
|
|
145
|
+
* 4. `cwd` — last resort. Returned even if it is a plugin path; the
|
|
45
146
|
* caller is responsible for rendering a graceful "no project context"
|
|
46
147
|
* message rather than panicking. Keeping the function total preserves
|
|
47
148
|
* operation of project-independent tools (sandbox execute, fetch).
|
|
48
149
|
*/
|
|
49
150
|
export function resolveProjectDir(opts) {
|
|
50
|
-
const { env, cwd, pwd } = opts;
|
|
151
|
+
const { env, cwd, pwd, transcriptsRoot } = opts;
|
|
51
152
|
const candidates = [
|
|
52
153
|
env.CLAUDE_PROJECT_DIR,
|
|
53
154
|
env.GEMINI_PROJECT_DIR,
|
|
@@ -61,6 +162,11 @@ export function resolveProjectDir(opts) {
|
|
|
61
162
|
if (c && !isPluginInstallPath(c))
|
|
62
163
|
return c;
|
|
63
164
|
}
|
|
165
|
+
if (transcriptsRoot) {
|
|
166
|
+
const fromTranscript = resolveProjectDirFromTranscript({ projectsRoot: transcriptsRoot });
|
|
167
|
+
if (fromTranscript && !isPluginInstallPath(fromTranscript))
|
|
168
|
+
return fromTranscript;
|
|
169
|
+
}
|
|
64
170
|
if (pwd && !isPluginInstallPath(pwd))
|
|
65
171
|
return pwd;
|
|
66
172
|
return cwd;
|