context-mode 1.0.14 → 1.0.16
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/README.md +99 -22
- package/build/adapters/claude-code/index.d.ts +4 -0
- package/build/adapters/claude-code/index.js +56 -40
- package/build/adapters/cursor/config.d.ts +4 -0
- package/build/adapters/cursor/config.js +4 -0
- package/build/adapters/cursor/hooks.d.ts +40 -0
- package/build/adapters/cursor/hooks.js +53 -0
- package/build/adapters/cursor/index.d.ts +40 -0
- package/build/adapters/cursor/index.js +413 -0
- package/build/adapters/detect.d.ts +1 -0
- package/build/adapters/detect.js +19 -0
- package/build/adapters/opencode/index.js +3 -1
- package/build/adapters/types.d.ts +1 -1
- package/build/cli.js +5 -0
- package/build/executor.js +2 -0
- package/build/opencode-plugin.js +8 -0
- package/build/server.js +35 -3
- package/build/session/db.js +21 -0
- package/build/sync/batcher.d.ts +23 -0
- package/build/sync/batcher.js +74 -0
- package/build/sync/cloud-post.d.ts +12 -0
- package/build/sync/cloud-post.js +38 -0
- package/build/sync/config.d.ts +4 -0
- package/build/sync/config.js +64 -0
- package/build/sync/index.d.ts +12 -0
- package/build/sync/index.js +55 -0
- package/build/sync/sanitizer.d.ts +13 -0
- package/build/sync/sanitizer.js +86 -0
- package/build/sync/sender.d.ts +15 -0
- package/build/sync/sender.js +30 -0
- package/build/sync/types.d.ts +31 -0
- package/build/sync/types.js +1 -0
- package/cli.bundle.mjs +2 -2
- package/configs/cursor/hooks.json +16 -0
- package/configs/cursor/mcp.json +7 -0
- package/hooks/core/formatters.mjs +16 -0
- package/hooks/core/routing.mjs +18 -4
- package/hooks/cursor/posttooluse.mjs +70 -0
- package/hooks/cursor/pretooluse.mjs +26 -0
- package/hooks/cursor/sessionstart.mjs +97 -0
- package/hooks/formatters/cursor.mjs +37 -0
- package/hooks/gemini-cli/sessionstart.mjs +7 -0
- package/hooks/session-helpers.mjs +22 -0
- package/hooks/vscode-copilot/sessionstart.mjs +7 -0
- package/package.json +3 -2
- package/server.bundle.mjs +2 -2
- package/skills/context-mode/SKILL.md +7 -9
- package/skills/ctx-cloud-setup/SKILL.md +98 -0
- package/skills/ctx-cloud-status/SKILL.md +96 -0
- package/skills/ctx-doctor/SKILL.md +1 -1
- package/skills/ctx-stats/SKILL.md +1 -1
- package/skills/ctx-upgrade/SKILL.md +1 -1
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "../suppress-stderr.mjs";
|
|
3
|
+
/**
|
|
4
|
+
* Cursor preToolUse hook for context-mode.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { dirname, resolve } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { readStdin, getInputProjectDir, CURSOR_OPTS } from "../session-helpers.mjs";
|
|
10
|
+
import { routePreToolUse, initSecurity } from "../core/routing.mjs";
|
|
11
|
+
import { formatDecision } from "../core/formatters.mjs";
|
|
12
|
+
|
|
13
|
+
const __hookDir = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
await initSecurity(resolve(__hookDir, "..", "..", "build"));
|
|
15
|
+
|
|
16
|
+
const raw = await readStdin();
|
|
17
|
+
const input = JSON.parse(raw);
|
|
18
|
+
const tool = input.tool_name ?? "";
|
|
19
|
+
const toolInput = input.tool_input ?? {};
|
|
20
|
+
const projectDir = getInputProjectDir(input, CURSOR_OPTS);
|
|
21
|
+
|
|
22
|
+
const decision = routePreToolUse(tool, toolInput, projectDir);
|
|
23
|
+
const response = formatDecision("cursor", decision);
|
|
24
|
+
// Cursor treats empty stdout as an invalid hook response,
|
|
25
|
+
// so even passthrough decisions must emit a syntactically valid no-op payload.
|
|
26
|
+
process.stdout.write(JSON.stringify(response ?? { agent_message: "" }) + "\n");
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "../suppress-stderr.mjs";
|
|
3
|
+
/**
|
|
4
|
+
* Cursor sessionStart hook for context-mode.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ROUTING_BLOCK } from "../routing-block.mjs";
|
|
8
|
+
import {
|
|
9
|
+
writeSessionEventsFile,
|
|
10
|
+
buildSessionDirective,
|
|
11
|
+
getSessionEvents,
|
|
12
|
+
getLatestSessionEvents,
|
|
13
|
+
} from "../session-directive.mjs";
|
|
14
|
+
import {
|
|
15
|
+
readStdin,
|
|
16
|
+
getSessionId,
|
|
17
|
+
getSessionDBPath,
|
|
18
|
+
getSessionEventsPath,
|
|
19
|
+
getCleanupFlagPath,
|
|
20
|
+
getInputProjectDir,
|
|
21
|
+
CURSOR_OPTS,
|
|
22
|
+
} from "../session-helpers.mjs";
|
|
23
|
+
import { join } from "node:path";
|
|
24
|
+
import { readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
|
25
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
26
|
+
|
|
27
|
+
const HOOK_DIR = fileURLToPath(new URL(".", import.meta.url));
|
|
28
|
+
const PKG_SESSION = join(HOOK_DIR, "..", "..", "build", "session");
|
|
29
|
+
const OPTS = CURSOR_OPTS;
|
|
30
|
+
|
|
31
|
+
let additionalContext = ROUTING_BLOCK;
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const raw = await readStdin();
|
|
35
|
+
const input = JSON.parse(raw);
|
|
36
|
+
const source = input.source ?? input.trigger ?? "startup";
|
|
37
|
+
const projectDir = getInputProjectDir(input, CURSOR_OPTS);
|
|
38
|
+
|
|
39
|
+
if (projectDir && !process.env.CURSOR_CWD) {
|
|
40
|
+
process.env.CURSOR_CWD = projectDir;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (source === "compact" || source === "resume") {
|
|
44
|
+
const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
|
|
45
|
+
const dbPath = getSessionDBPath(OPTS);
|
|
46
|
+
const db = new SessionDB({ dbPath });
|
|
47
|
+
|
|
48
|
+
if (source === "compact") {
|
|
49
|
+
const sessionId = getSessionId(input, OPTS);
|
|
50
|
+
const resume = db.getResume(sessionId);
|
|
51
|
+
if (resume && !resume.consumed) {
|
|
52
|
+
db.markResumeConsumed(sessionId);
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
try { unlinkSync(getCleanupFlagPath(OPTS)); } catch { /* no flag */ }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const events = source === "compact"
|
|
59
|
+
? getSessionEvents(db, getSessionId(input, OPTS))
|
|
60
|
+
: getLatestSessionEvents(db);
|
|
61
|
+
if (events.length > 0) {
|
|
62
|
+
const eventMeta = writeSessionEventsFile(events, getSessionEventsPath(OPTS));
|
|
63
|
+
additionalContext += buildSessionDirective(source, eventMeta);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
db.close();
|
|
67
|
+
} else if (source === "startup") {
|
|
68
|
+
const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
|
|
69
|
+
const dbPath = getSessionDBPath(OPTS);
|
|
70
|
+
const db = new SessionDB({ dbPath });
|
|
71
|
+
try { unlinkSync(getSessionEventsPath(OPTS)); } catch { /* no stale file */ }
|
|
72
|
+
|
|
73
|
+
const cleanupFlag = getCleanupFlagPath(OPTS);
|
|
74
|
+
let previousWasFresh = false;
|
|
75
|
+
try { readFileSync(cleanupFlag); previousWasFresh = true; } catch { /* no flag */ }
|
|
76
|
+
|
|
77
|
+
if (previousWasFresh) {
|
|
78
|
+
db.cleanupOldSessions(0);
|
|
79
|
+
} else {
|
|
80
|
+
db.cleanupOldSessions(7);
|
|
81
|
+
}
|
|
82
|
+
db.db.exec(`DELETE FROM session_events WHERE session_id NOT IN (SELECT session_id FROM session_meta)`);
|
|
83
|
+
writeFileSync(cleanupFlag, new Date().toISOString(), "utf-8");
|
|
84
|
+
|
|
85
|
+
const sessionId = getSessionId(input, OPTS);
|
|
86
|
+
db.ensureSession(sessionId, projectDir);
|
|
87
|
+
|
|
88
|
+
db.close();
|
|
89
|
+
}
|
|
90
|
+
// clear => routing block only
|
|
91
|
+
} catch {
|
|
92
|
+
// Cursor treats stderr as hook failure; swallow and continue.
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Cursor treats empty stdout as an invalid hook response,
|
|
96
|
+
// so SessionStart always emits an explicit additional_context payload.
|
|
97
|
+
process.stdout.write(JSON.stringify({ additional_context: additionalContext }) + "\n");
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor formatter — converts routing decisions into native Cursor hook output.
|
|
3
|
+
*
|
|
4
|
+
* Cursor expects flat response objects:
|
|
5
|
+
* { permission: "deny" | "ask", user_message?: string }
|
|
6
|
+
* { updated_input: { ... } }
|
|
7
|
+
* { agent_message: "..." }
|
|
8
|
+
*/
|
|
9
|
+
export function formatDecision(decision) {
|
|
10
|
+
if (!decision) return null;
|
|
11
|
+
|
|
12
|
+
switch (decision.action) {
|
|
13
|
+
case "deny":
|
|
14
|
+
return {
|
|
15
|
+
permission: "deny",
|
|
16
|
+
user_message: decision.reason ?? "Blocked by context-mode",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
case "ask":
|
|
20
|
+
return {
|
|
21
|
+
permission: "ask",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
case "modify":
|
|
25
|
+
return {
|
|
26
|
+
updated_input: decision.updatedInput,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
case "context":
|
|
30
|
+
return {
|
|
31
|
+
agent_message: decision.additionalContext ?? "",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
default:
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -85,6 +85,13 @@ try {
|
|
|
85
85
|
const sessionId = getSessionId(input, OPTS);
|
|
86
86
|
const projectDir = getProjectDir(OPTS);
|
|
87
87
|
db.ensureSession(sessionId, projectDir);
|
|
88
|
+
|
|
89
|
+
// Auto-write GEMINI.md on startup if missing or not merged yet
|
|
90
|
+
try {
|
|
91
|
+
const { GeminiCLIAdapter } = await import(pathToFileURL(join(HOOK_DIR, "..", "..", "build", "adapters", "gemini-cli", "index.js")).href);
|
|
92
|
+
new GeminiCLIAdapter().writeRoutingInstructions(projectDir, join(HOOK_DIR, "..", ".."));
|
|
93
|
+
} catch { /* best effort — don't block session start */ }
|
|
94
|
+
|
|
88
95
|
const ruleFilePaths = [
|
|
89
96
|
join(homedir(), ".gemini", "GEMINI.md"),
|
|
90
97
|
join(projectDir, "GEMINI.md"),
|
|
@@ -33,6 +33,13 @@ export const VSCODE_OPTS = {
|
|
|
33
33
|
sessionIdEnv: undefined,
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
+
/** Cursor platform options. */
|
|
37
|
+
export const CURSOR_OPTS = {
|
|
38
|
+
configDir: ".cursor",
|
|
39
|
+
projectDirEnv: "CURSOR_CWD",
|
|
40
|
+
sessionIdEnv: "CURSOR_SESSION_ID",
|
|
41
|
+
};
|
|
42
|
+
|
|
36
43
|
/**
|
|
37
44
|
* Read all of stdin as a string (event-based, cross-platform safe).
|
|
38
45
|
*/
|
|
@@ -55,6 +62,20 @@ export function getProjectDir(opts = CLAUDE_OPTS) {
|
|
|
55
62
|
return process.env[opts.projectDirEnv] || process.cwd();
|
|
56
63
|
}
|
|
57
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Get the project directory from hook input when available.
|
|
67
|
+
* Falls back to the platform env var and finally process.cwd().
|
|
68
|
+
*/
|
|
69
|
+
export function getInputProjectDir(input, opts = CLAUDE_OPTS) {
|
|
70
|
+
if (typeof input?.cwd === "string" && input.cwd.length > 0) {
|
|
71
|
+
return input.cwd;
|
|
72
|
+
}
|
|
73
|
+
if (Array.isArray(input?.workspace_roots) && input.workspace_roots.length > 0) {
|
|
74
|
+
return String(input.workspace_roots[0]);
|
|
75
|
+
}
|
|
76
|
+
return getProjectDir(opts);
|
|
77
|
+
}
|
|
78
|
+
|
|
58
79
|
/**
|
|
59
80
|
* Derive session ID from hook input.
|
|
60
81
|
* Priority: transcript_path UUID > sessionId (camelCase) > session_id > env var > ppid fallback.
|
|
@@ -64,6 +85,7 @@ export function getSessionId(input, opts = CLAUDE_OPTS) {
|
|
|
64
85
|
const match = input.transcript_path.match(/([a-f0-9-]{36})\.jsonl$/);
|
|
65
86
|
if (match) return match[1];
|
|
66
87
|
}
|
|
88
|
+
if (input.conversation_id) return input.conversation_id;
|
|
67
89
|
if (input.sessionId) return input.sessionId;
|
|
68
90
|
if (input.session_id) return input.session_id;
|
|
69
91
|
if (opts.sessionIdEnv && process.env[opts.sessionIdEnv]) {
|
|
@@ -85,6 +85,13 @@ try {
|
|
|
85
85
|
const sessionId = getSessionId(input, OPTS);
|
|
86
86
|
const projectDir = getProjectDir(OPTS);
|
|
87
87
|
db.ensureSession(sessionId, projectDir);
|
|
88
|
+
|
|
89
|
+
// Auto-write copilot-instructions.md on first startup if not present
|
|
90
|
+
try {
|
|
91
|
+
const { VSCodeCopilotAdapter } = await import(pathToFileURL(join(HOOK_DIR, "..", "..", "build", "adapters", "vscode-copilot", "index.js")).href);
|
|
92
|
+
new VSCodeCopilotAdapter().writeRoutingInstructions(projectDir, join(HOOK_DIR, "..", ".."));
|
|
93
|
+
} catch { /* best effort — don't block session start */ }
|
|
94
|
+
|
|
88
95
|
const ruleFilePaths = [
|
|
89
96
|
join(projectDir, ".github", "copilot-instructions.md"),
|
|
90
97
|
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.16",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MCP plugin that saves 98% of your context window. Works with Claude Code, Gemini CLI, VS Code Copilot, OpenCode, and Codex CLI. Sandboxed code execution, FTS5 knowledge base, and intent-driven search.",
|
|
6
6
|
"author": "Mert Koseoğlu",
|
|
@@ -81,5 +81,6 @@
|
|
|
81
81
|
"tsx": "^4.21.0",
|
|
82
82
|
"typescript": "^5.7.0",
|
|
83
83
|
"vitest": "^4.0.18"
|
|
84
|
-
}
|
|
84
|
+
},
|
|
85
|
+
"packageManager": "pnpm@10.23.0+sha512.21c4e5698002ade97e4efe8b8b4a89a8de3c85a37919f957e7a0f30f38fbc5bbdd05980ffe29179b2fb6e6e691242e098d945d1601772cad0fef5fb6411e2a4b"
|
|
85
86
|
}
|
package/server.bundle.mjs
CHANGED
|
@@ -79,7 +79,7 @@ ${r}`),n==="elixir"&&gy(Fi(this.#r,"mix.exs"))&&(r=`Path.wildcard(Path.join(${JS
|
|
|
79
79
|
|
|
80
80
|
${r}`);let s=Fi(e,`script.${o[n]}`);return n==="shell"?my(s,r,{encoding:"utf-8",mode:448}):my(s,r,"utf-8"),s}async#a(e,r,n){let o=fr?".exe":"",s=e.replace(/\.rs$/,"")+o;try{_y(`rustc ${e} -o ${s}`,{cwd:r,timeout:Math.min(n,3e4),encoding:"utf-8",stdio:["pipe","pipe","pipe"]})}catch(i){return{stdout:"",stderr:`Compilation failed:
|
|
81
81
|
${i instanceof Error?i.stderr||i.message:String(i)}`,exitCode:1,timedOut:!1}}return this.#s([s],r,n)}async#s(e,r,n,o=!1){return new Promise(s=>{let i=fr&&["tsx","ts-node","elixir"].includes(e[0]),a=e[0],c;fr&&e.length===2&&e[1]?c=[e[1].replace(/\\/g,"/")]:c=fr?e.slice(1).map(y=>y.replace(/\\/g,"/")):e.slice(1);let u=hR(a,c,{cwd:r,stdio:["ignore","pipe","pipe"],env:this.#c(r),shell:i}),l=!1,d=!1,f=setTimeout(()=>{if(l=!0,o){d=!0,u.pid&&this.#o.add(u.pid),u.unref(),u.stdout.destroy(),u.stderr.destroy();let y=Buffer.concat(m).toString("utf-8"),v=Buffer.concat(p).toString("utf-8"),b=this.#e;s({stdout:Fo(y,b),stderr:Fo(v,b),exitCode:0,timedOut:!0,backgrounded:!0})}else Ld(u)},n),m=[],p=[],h=0,g=!1;u.stdout.on("data",y=>{h+=y.length,h<=this.#t?m.push(y):g||(g=!0,Ld(u))}),u.stderr.on("data",y=>{h+=y.length,h<=this.#t?p.push(y):g||(g=!0,Ld(u))}),u.on("close",y=>{if(clearTimeout(f),d)return;let v=Buffer.concat(m).toString("utf-8"),b=Buffer.concat(p).toString("utf-8");g&&(b+=`
|
|
82
|
-
[output capped at ${(this.#t/1024/1024).toFixed(0)}MB \u2014 process killed]`);let w=this.#e,R=Fo(v,w),ie=Fo(b,w);s({stdout:R,stderr:ie,exitCode:l?1:y??1,timedOut:l})}),u.on("error",y=>{clearTimeout(f),!d&&s({stdout:"",stderr:y.message,exitCode:1,timedOut:!1})})})}#c(e){let r=process.env.HOME??process.env.USERPROFILE??e,n=["GH_TOKEN","GITHUB_TOKEN","GH_HOST","AWS_ACCESS_KEY_ID","AWS_SECRET_ACCESS_KEY","AWS_SESSION_TOKEN","AWS_REGION","AWS_DEFAULT_REGION","AWS_PROFILE","GOOGLE_APPLICATION_CREDENTIALS","CLOUDSDK_CONFIG","DOCKER_HOST","KUBECONFIG","NPM_TOKEN","NODE_AUTH_TOKEN","npm_config_registry","HTTP_PROXY","HTTPS_PROXY","NO_PROXY","SSL_CERT_FILE","CURL_CA_BUNDLE","XDG_CONFIG_HOME","XDG_DATA_HOME","SSH_AUTH_SOCK","SSH_AGENT_PID","DIRENV_DIR","DIRENV_FILE","DIRENV_DIFF","DIRENV_WATCHES","DIRENV_LAYOUT_DIR","NIX_PATH","NIX_PROFILES","NIX_SSL_CERT_FILE","NIX_CC","NIX_STORE","NIX_BUILD_CORES","IN_NIX_SHELL","LOCALE_ARCHIVE","LD_LIBRARY_PATH","DYLD_LIBRARY_PATH","LIBRARY_PATH","C_INCLUDE_PATH","CPLUS_INCLUDE_PATH","PKG_CONFIG_PATH","CMAKE_PREFIX_PATH","GOPATH","GOROOT","CARGO_HOME","RUSTUP_HOME","ASDF_DIR","ASDF_DATA_DIR","MISE_DATA_DIR","VIRTUAL_ENV","CONDA_PREFIX","CONDA_DEFAULT_ENV","PYTHONPATH","GEM_HOME","GEM_PATH","BUNDLE_PATH","RBENV_ROOT","JAVA_HOME","SDKMAN_DIR"],o={PATH:process.env.PATH??(fr?"":"/usr/local/bin:/usr/bin:/bin"),HOME:r,TMPDIR:e,LANG:"en_US.UTF-8",PYTHONDONTWRITEBYTECODE:"1",PYTHONUNBUFFERED:"1",PYTHONUTF8:"1",NO_COLOR:"1"};if(fr){let s=["SYSTEMROOT","SystemRoot","COMSPEC","PATHEXT","USERPROFILE","APPDATA","LOCALAPPDATA","TEMP","TMP"];for(let c of s)process.env[c]&&(o[c]=process.env[c]);o.MSYS_NO_PATHCONV="1",o.MSYS2_ARG_CONV_EXCL="*";let i="C:\\Program Files\\Git\\usr\\bin",a="C:\\Program Files\\Git\\bin";o.PATH.includes(i)||(o.PATH=`${i};${a};${o.PATH}`)}for(let s of n)process.env[s]&&(o[s]=process.env[s]);if(!o.SSL_CERT_FILE){let s=fr?[]:["/etc/ssl/cert.pem","/etc/ssl/certs/ca-certificates.crt","/etc/pki/tls/certs/ca-bundle.crt","/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"];for(let i of s)if(gy(i)){o.SSL_CERT_FILE=i;break}}return o}#u(e,r,n){let o=JSON.stringify(e);switch(r){case"javascript":case"typescript":return`const FILE_CONTENT_PATH = ${o};
|
|
82
|
+
[output capped at ${(this.#t/1024/1024).toFixed(0)}MB \u2014 process killed]`);let w=this.#e,R=Fo(v,w),ie=Fo(b,w);s({stdout:R,stderr:ie,exitCode:l?1:y??1,timedOut:l})}),u.on("error",y=>{clearTimeout(f),!d&&s({stdout:"",stderr:y.message,exitCode:1,timedOut:!1})})})}#c(e){let r=process.env.HOME??process.env.USERPROFILE??e,n=["GH_TOKEN","GITHUB_TOKEN","GH_HOST","AWS_ACCESS_KEY_ID","AWS_SECRET_ACCESS_KEY","AWS_SESSION_TOKEN","AWS_REGION","AWS_DEFAULT_REGION","AWS_PROFILE","GOOGLE_APPLICATION_CREDENTIALS","CLOUDSDK_CONFIG","DOCKER_HOST","KUBECONFIG","NPM_TOKEN","NODE_AUTH_TOKEN","npm_config_registry","HTTP_PROXY","HTTPS_PROXY","NO_PROXY","SSL_CERT_FILE","CURL_CA_BUNDLE","NODE_EXTRA_CA_CERTS","REQUESTS_CA_BUNDLE","XDG_CONFIG_HOME","XDG_DATA_HOME","SSH_AUTH_SOCK","SSH_AGENT_PID","DIRENV_DIR","DIRENV_FILE","DIRENV_DIFF","DIRENV_WATCHES","DIRENV_LAYOUT_DIR","NIX_PATH","NIX_PROFILES","NIX_SSL_CERT_FILE","NIX_CC","NIX_STORE","NIX_BUILD_CORES","IN_NIX_SHELL","LOCALE_ARCHIVE","LD_LIBRARY_PATH","DYLD_LIBRARY_PATH","LIBRARY_PATH","C_INCLUDE_PATH","CPLUS_INCLUDE_PATH","PKG_CONFIG_PATH","CMAKE_PREFIX_PATH","GOPATH","GOROOT","CARGO_HOME","RUSTUP_HOME","ASDF_DIR","ASDF_DATA_DIR","MISE_DATA_DIR","VIRTUAL_ENV","CONDA_PREFIX","CONDA_DEFAULT_ENV","PYTHONPATH","GEM_HOME","GEM_PATH","BUNDLE_PATH","RBENV_ROOT","JAVA_HOME","SDKMAN_DIR"],o={PATH:process.env.PATH??(fr?"":"/usr/local/bin:/usr/bin:/bin"),HOME:r,TMPDIR:e,LANG:"en_US.UTF-8",PYTHONDONTWRITEBYTECODE:"1",PYTHONUNBUFFERED:"1",PYTHONUTF8:"1",NO_COLOR:"1"};if(fr){let s=["SYSTEMROOT","SystemRoot","COMSPEC","PATHEXT","USERPROFILE","APPDATA","LOCALAPPDATA","TEMP","TMP"];for(let c of s)process.env[c]&&(o[c]=process.env[c]);o.MSYS_NO_PATHCONV="1",o.MSYS2_ARG_CONV_EXCL="*";let i="C:\\Program Files\\Git\\usr\\bin",a="C:\\Program Files\\Git\\bin";o.PATH.includes(i)||(o.PATH=`${i};${a};${o.PATH}`)}for(let s of n)process.env[s]&&(o[s]=process.env[s]);if(!o.SSL_CERT_FILE){let s=fr?[]:["/etc/ssl/cert.pem","/etc/ssl/certs/ca-certificates.crt","/etc/pki/tls/certs/ca-bundle.crt","/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"];for(let i of s)if(gy(i)){o.SSL_CERT_FILE=i;break}}return o}#u(e,r,n){let o=JSON.stringify(e);switch(r){case"javascript":case"typescript":return`const FILE_CONTENT_PATH = ${o};
|
|
83
83
|
const FILE_CONTENT = require("fs").readFileSync(FILE_CONTENT_PATH, "utf-8");
|
|
84
84
|
${n}`;case"python":return`FILE_CONTENT_PATH = ${o}
|
|
85
85
|
with open(FILE_CONTENT_PATH, "r", encoding="utf-8") as _f:
|
|
@@ -227,7 +227,7 @@ stdout:
|
|
|
227
227
|
${n}
|
|
228
228
|
|
|
229
229
|
stderr:
|
|
230
|
-
${o}`}}var lx="1.0.
|
|
230
|
+
${o}`}}var lx="1.0.15";process.on("unhandledRejection",t=>{process.stderr.write(`[context-mode] unhandledRejection: ${t}
|
|
231
231
|
`)});process.on("uncaughtException",t=>{process.stderr.write(`[context-mode] uncaughtException: ${t?.message??t}
|
|
232
232
|
`)});var hp=Ui(),uz=py(hp),Nt=new Zi({name:"context-mode",version:lx}),Wo=new Hi({runtimes:hp,projectRoot:process.env.CLAUDE_PROJECT_DIR}),Dn=null;function lz(t){try{let e=ra(ux(),".claude","context-mode","sessions");if(!cx(e))return;let r=oz(e).filter(n=>n.endsWith("-events.md"));for(let n of r){let o=ra(e,n);try{t.index({path:o,source:"session-events"}),nz(o)}catch{}}}catch{}}function Yo(){return Dn||(Dn=new Vi),lz(Dn),Dn}var qe={calls:{},bytesReturned:{},bytesIndexed:0,bytesSandboxed:0,sessionStart:Date.now()};function J(t,e){let r=e.content.reduce((n,o)=>n+Buffer.byteLength(o.text),0);return qe.calls[t]=(qe.calls[t]||0)+1,qe.bytesReturned[t]=(qe.bytesReturned[t]||0)+r,e}function gr(t){qe.bytesIndexed+=t}function gp(t,e){try{let r=Fd(process.env.CLAUDE_PROJECT_DIR),n=Hd(t,r);if(n.decision==="deny")return J(e,{content:[{type:"text",text:`Command blocked by security policy: matches deny pattern ${n.matchedPattern}`}],isError:!0})}catch{}return null}function dx(t,e,r){try{let n=Iy(t,e);if(n.length===0)return null;let o=Fd(process.env.CLAUDE_PROJECT_DIR);for(let s of n){let i=Hd(s,o);if(i.decision==="deny")return J(r,{content:[{type:"text",text:`Command blocked by security policy: embedded shell command "${s}" matches deny pattern ${i.matchedPattern}`}],isError:!0})}}catch{}return null}function dz(t,e){try{let r=zy("Read",process.env.CLAUDE_PROJECT_DIR),n=Oy(t,r);if(n.denied)return J(e,{content:[{type:"text",text:`File access blocked by security policy: path matches Read deny pattern ${n.matchedPattern}`}],isError:!0})}catch{}return null}var pz=uz.join(", "),fz=Zd()?" (Bun detected \u2014 JS/TS runs 3-5x faster)":"",mz="",hz="";function gz(t){let e=[],r=0,n=0;for(;n<t.length;)if(t[n]===mz){for(e.push(r),n++;n<t.length&&t[n]!==hz;)r++,n++;n<t.length&&n++}else r++,n++;return e}function px(t,e,r=1500,n){if(t.length<=r)return t;let o=[];if(n)for(let u of gz(n))o.push(u);if(o.length===0){let u=e.toLowerCase().split(/\s+/).filter(d=>d.length>2),l=t.toLowerCase();for(let d of u){let f=l.indexOf(d);for(;f!==-1;)o.push(f),f=l.indexOf(d,f+1)}}if(o.length===0)return t.slice(0,r)+`
|
|
233
233
|
\u2026`;o.sort((u,l)=>u-l);let s=300,i=[];for(let u of o){let l=Math.max(0,u-s),d=Math.min(t.length,u+s);i.length>0&&l<=i[i.length-1][1]?i[i.length-1][1]=d:i.push([l,d])}let a=[],c=0;for(let[u,l]of i){if(c>=r)break;let d=t.slice(u,Math.min(l,u+(r-c)));a.push((u>0?"\u2026":"")+d+(l<t.length?"\u2026":"")),c+=d.length}return a.join(`
|
|
@@ -2,21 +2,19 @@
|
|
|
2
2
|
name: context-mode
|
|
3
3
|
description: |
|
|
4
4
|
Use context-mode tools (ctx_execute, ctx_execute_file) instead of Bash/cat when processing
|
|
5
|
-
large outputs.
|
|
5
|
+
large outputs. Triggers: "analyze logs", "summarize output", "process data",
|
|
6
6
|
"parse JSON", "filter results", "extract errors", "check build output",
|
|
7
7
|
"analyze dependencies", "process API response", "large file analysis",
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"web page structure", "accessibility tree", "Playwright snapshot",
|
|
8
|
+
"page snapshot", "browser snapshot", "DOM structure", "inspect page",
|
|
9
|
+
"accessibility tree", "Playwright snapshot",
|
|
11
10
|
"run tests", "test output", "coverage report", "git log", "recent commits",
|
|
12
11
|
"diff between branches", "list containers", "pod status", "disk usage",
|
|
13
|
-
"fetch docs", "API reference", "index documentation",
|
|
14
|
-
"call API", "check response", "query results",
|
|
12
|
+
"fetch docs", "API reference", "index documentation",
|
|
13
|
+
"call API", "check response", "query results",
|
|
15
14
|
"find TODOs", "count lines", "codebase statistics", "security audit",
|
|
16
15
|
"outdated packages", "dependency tree", "cloud resources", "CI/CD output".
|
|
17
|
-
Also triggers on ANY MCP tool output
|
|
18
|
-
|
|
19
|
-
Subagent routing is handled automatically via PreToolUse hook — no manual tool names needed in prompts.
|
|
16
|
+
Also triggers on ANY MCP tool output that may exceed 20 lines.
|
|
17
|
+
Subagent routing is handled automatically via PreToolUse hook.
|
|
20
18
|
---
|
|
21
19
|
|
|
22
20
|
# Context Mode: Default for All Large Output
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ctx-cloud-setup
|
|
3
|
+
description: |
|
|
4
|
+
Connect context-mode to Context Mode Cloud.
|
|
5
|
+
Guides through API URL, token, and org ID configuration.
|
|
6
|
+
Saves config to ~/.context-mode/sync.json and tests the connection.
|
|
7
|
+
Trigger: /context-mode:ctx-cloud-setup
|
|
8
|
+
user-invocable: true
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Context Mode Cloud Setup
|
|
12
|
+
|
|
13
|
+
Interactive onboarding flow to connect this plugin to Context Mode Cloud.
|
|
14
|
+
|
|
15
|
+
## Instructions
|
|
16
|
+
|
|
17
|
+
1. **Check existing config** — read `~/.context-mode/sync.json` using Bash:
|
|
18
|
+
```
|
|
19
|
+
cat ~/.context-mode/sync.json 2>/dev/null || echo "NOT_FOUND"
|
|
20
|
+
```
|
|
21
|
+
- If the file exists and contains a non-empty `api_token`, inform the user that cloud sync is **already configured** and show the current `api_url` and `organization_id` (never reveal the token — show only the last 4 characters masked as `ctx_****abcd`).
|
|
22
|
+
- Ask if they want to **reconfigure** or **keep the current settings**. If they want to keep, stop here.
|
|
23
|
+
|
|
24
|
+
2. **Collect configuration** — ask the user for three values, one at a time:
|
|
25
|
+
|
|
26
|
+
**a) API URL**
|
|
27
|
+
- Default: `https://api.context-mode.com`
|
|
28
|
+
- Tell the user: *"Press Enter to use the default, or paste your self-hosted API URL."*
|
|
29
|
+
- If the user says "default", use `https://api.context-mode.com`.
|
|
30
|
+
|
|
31
|
+
**b) API Token**
|
|
32
|
+
- Tell the user: *"Paste your API token from the Context Mode dashboard: **Settings > API Tokens**."*
|
|
33
|
+
- This field is **required** — do not proceed without it.
|
|
34
|
+
- Validate format: token should start with `ctx_` and be at least 20 characters. If invalid, warn and ask again.
|
|
35
|
+
|
|
36
|
+
**c) Organization ID**
|
|
37
|
+
- Tell the user: *"Paste your Organization ID from the dashboard: **Settings > Team**."*
|
|
38
|
+
- This field is **required** — do not proceed without it.
|
|
39
|
+
|
|
40
|
+
3. **Save config** — write the merged config to `~/.context-mode/sync.json` using Bash:
|
|
41
|
+
```bash
|
|
42
|
+
mkdir -p ~/.context-mode
|
|
43
|
+
cat > ~/.context-mode/sync.json << 'JSONEOF'
|
|
44
|
+
{
|
|
45
|
+
"enabled": true,
|
|
46
|
+
"api_url": "<API_URL>",
|
|
47
|
+
"api_token": "<API_TOKEN>",
|
|
48
|
+
"organization_id": "<ORG_ID>",
|
|
49
|
+
"batch_size": 50,
|
|
50
|
+
"flush_interval_ms": 30000
|
|
51
|
+
}
|
|
52
|
+
JSONEOF
|
|
53
|
+
chmod 600 ~/.context-mode/sync.json
|
|
54
|
+
```
|
|
55
|
+
Replace `<API_URL>`, `<API_TOKEN>`, and `<ORG_ID>` with the collected values.
|
|
56
|
+
|
|
57
|
+
4. **Test the connection** — send a health check using Bash:
|
|
58
|
+
```bash
|
|
59
|
+
curl -sf -o /dev/null -w "%{http_code}" \
|
|
60
|
+
-H "Authorization: Bearer <API_TOKEN>" \
|
|
61
|
+
"<API_URL>/api/health"
|
|
62
|
+
```
|
|
63
|
+
- `200` = success
|
|
64
|
+
- Any other code or failure = connection error
|
|
65
|
+
|
|
66
|
+
5. **Display results** as markdown directly in the conversation:
|
|
67
|
+
|
|
68
|
+
On **success**:
|
|
69
|
+
```
|
|
70
|
+
## context-mode cloud setup
|
|
71
|
+
- [x] Config saved to ~/.context-mode/sync.json
|
|
72
|
+
- [x] Connection test: PASS (200 OK)
|
|
73
|
+
- [x] Organization: <ORG_ID>
|
|
74
|
+
|
|
75
|
+
Cloud sync is now active. Events will be sent to the dashboard
|
|
76
|
+
on your next Claude Code session. Run `/ctx-cloud-status` to
|
|
77
|
+
check sync health at any time.
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
On **failure**:
|
|
81
|
+
```
|
|
82
|
+
## context-mode cloud setup
|
|
83
|
+
- [x] Config saved to ~/.context-mode/sync.json
|
|
84
|
+
- [ ] Connection test: FAIL (<error details>)
|
|
85
|
+
|
|
86
|
+
Config was saved but the connection test failed. Check that:
|
|
87
|
+
1. Your API URL is reachable
|
|
88
|
+
2. Your API token is valid and not expired
|
|
89
|
+
3. Your network allows outbound HTTPS
|
|
90
|
+
|
|
91
|
+
Run `/ctx-cloud-setup` again to reconfigure.
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Security Notes
|
|
95
|
+
|
|
96
|
+
- Never log or display the full API token. Always mask it.
|
|
97
|
+
- Set file permissions to `600` (owner read/write only).
|
|
98
|
+
- The token is sent only over HTTPS in the `Authorization` header.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ctx-cloud-status
|
|
3
|
+
description: |
|
|
4
|
+
Show current Context Mode Cloud sync status.
|
|
5
|
+
Displays connection health, sync config, and event statistics.
|
|
6
|
+
Trigger: /context-mode:ctx-cloud-status
|
|
7
|
+
user-invocable: true
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Context Mode Cloud Status
|
|
11
|
+
|
|
12
|
+
Display the current cloud sync configuration, connection health, and event statistics.
|
|
13
|
+
|
|
14
|
+
## Instructions
|
|
15
|
+
|
|
16
|
+
1. **Read sync config** using Bash:
|
|
17
|
+
```
|
|
18
|
+
cat ~/.context-mode/sync.json 2>/dev/null || echo "NOT_CONFIGURED"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
2. **If not configured** (file missing or empty), display:
|
|
22
|
+
```
|
|
23
|
+
## context-mode cloud status
|
|
24
|
+
- [ ] Cloud sync: NOT CONFIGURED
|
|
25
|
+
|
|
26
|
+
Run `/ctx-cloud-setup` to connect to Context Mode Cloud.
|
|
27
|
+
```
|
|
28
|
+
Stop here.
|
|
29
|
+
|
|
30
|
+
3. **If configured**, extract the config values. **Never display the full API token** — mask it as `ctx_****<last4>`.
|
|
31
|
+
|
|
32
|
+
4. **Run health check** using Bash:
|
|
33
|
+
```bash
|
|
34
|
+
curl -sf -o /dev/null -w "%{http_code}" \
|
|
35
|
+
-H "Authorization: Bearer <API_TOKEN>" \
|
|
36
|
+
"<API_URL>/api/health"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
5. **Check sync stats** — read the stats file if it exists:
|
|
40
|
+
```
|
|
41
|
+
cat ~/.context-mode/sync-stats.json 2>/dev/null || echo "NO_STATS"
|
|
42
|
+
```
|
|
43
|
+
This file may contain: `events_sent`, `last_sync_at`, `errors_count`, `last_error`.
|
|
44
|
+
|
|
45
|
+
6. **Display results** as markdown directly in the conversation:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
## context-mode cloud status
|
|
49
|
+
|
|
50
|
+
### Connection
|
|
51
|
+
- [x] Cloud sync: ENABLED
|
|
52
|
+
- [x] API URL: https://api.context-mode.com
|
|
53
|
+
- [x] API Token: ctx_****abcd
|
|
54
|
+
- [x] Organization: org_abc123
|
|
55
|
+
- [x] Health check: PASS (200 OK)
|
|
56
|
+
|
|
57
|
+
### Sync Settings
|
|
58
|
+
- Batch size: 50
|
|
59
|
+
- Flush interval: 30s
|
|
60
|
+
|
|
61
|
+
### Statistics
|
|
62
|
+
- Events sent: 1,247
|
|
63
|
+
- Last sync: 2 minutes ago
|
|
64
|
+
- Errors: 0
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Use `[x]` for healthy items, `[ ]` for issues, `[-]` for warnings.
|
|
68
|
+
|
|
69
|
+
**Variations:**
|
|
70
|
+
|
|
71
|
+
- If `enabled` is `false`:
|
|
72
|
+
```
|
|
73
|
+
- [-] Cloud sync: DISABLED (config exists but sync is turned off)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
- If health check fails:
|
|
77
|
+
```
|
|
78
|
+
- [ ] Health check: FAIL (<http_code> or connection error)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
- If no stats file exists:
|
|
82
|
+
```
|
|
83
|
+
### Statistics
|
|
84
|
+
- No sync data yet. Events will appear after the next Claude Code session.
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
- If there are recent errors:
|
|
88
|
+
```
|
|
89
|
+
- [-] Errors: 3 (last: "Sync failed: 401 Unauthorized")
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
7. **Actionable guidance** — after the status display, add context-specific advice:
|
|
93
|
+
- If everything is healthy: *"Cloud sync is working normally."*
|
|
94
|
+
- If health check fails: *"Run `/ctx-cloud-setup` to reconfigure your connection."*
|
|
95
|
+
- If sync is disabled: *"To re-enable, set `enabled: true` in `~/.context-mode/sync.json`."*
|
|
96
|
+
- If errors are present: *"Check your API token validity in the dashboard: **Settings > API Tokens**."*
|
|
@@ -4,7 +4,7 @@ description: |
|
|
|
4
4
|
Show how much context window context-mode saved this session.
|
|
5
5
|
Displays token consumption, context savings ratio, and per-tool breakdown.
|
|
6
6
|
Trigger: /context-mode:ctx-stats
|
|
7
|
-
|
|
7
|
+
user-invocable: true
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# Context Mode Stats
|