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.
Files changed (54) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +99 -22
  4. package/build/adapters/claude-code/index.d.ts +4 -0
  5. package/build/adapters/claude-code/index.js +56 -40
  6. package/build/adapters/cursor/config.d.ts +4 -0
  7. package/build/adapters/cursor/config.js +4 -0
  8. package/build/adapters/cursor/hooks.d.ts +40 -0
  9. package/build/adapters/cursor/hooks.js +53 -0
  10. package/build/adapters/cursor/index.d.ts +40 -0
  11. package/build/adapters/cursor/index.js +413 -0
  12. package/build/adapters/detect.d.ts +1 -0
  13. package/build/adapters/detect.js +19 -0
  14. package/build/adapters/opencode/index.js +3 -1
  15. package/build/adapters/types.d.ts +1 -1
  16. package/build/cli.js +5 -0
  17. package/build/executor.js +2 -0
  18. package/build/opencode-plugin.js +8 -0
  19. package/build/server.js +35 -3
  20. package/build/session/db.js +21 -0
  21. package/build/sync/batcher.d.ts +23 -0
  22. package/build/sync/batcher.js +74 -0
  23. package/build/sync/cloud-post.d.ts +12 -0
  24. package/build/sync/cloud-post.js +38 -0
  25. package/build/sync/config.d.ts +4 -0
  26. package/build/sync/config.js +64 -0
  27. package/build/sync/index.d.ts +12 -0
  28. package/build/sync/index.js +55 -0
  29. package/build/sync/sanitizer.d.ts +13 -0
  30. package/build/sync/sanitizer.js +86 -0
  31. package/build/sync/sender.d.ts +15 -0
  32. package/build/sync/sender.js +30 -0
  33. package/build/sync/types.d.ts +31 -0
  34. package/build/sync/types.js +1 -0
  35. package/cli.bundle.mjs +2 -2
  36. package/configs/cursor/hooks.json +16 -0
  37. package/configs/cursor/mcp.json +7 -0
  38. package/hooks/core/formatters.mjs +16 -0
  39. package/hooks/core/routing.mjs +18 -4
  40. package/hooks/cursor/posttooluse.mjs +70 -0
  41. package/hooks/cursor/pretooluse.mjs +26 -0
  42. package/hooks/cursor/sessionstart.mjs +97 -0
  43. package/hooks/formatters/cursor.mjs +37 -0
  44. package/hooks/gemini-cli/sessionstart.mjs +7 -0
  45. package/hooks/session-helpers.mjs +22 -0
  46. package/hooks/vscode-copilot/sessionstart.mjs +7 -0
  47. package/package.json +3 -2
  48. package/server.bundle.mjs +2 -2
  49. package/skills/context-mode/SKILL.md +7 -9
  50. package/skills/ctx-cloud-setup/SKILL.md +98 -0
  51. package/skills/ctx-cloud-status/SKILL.md +96 -0
  52. package/skills/ctx-doctor/SKILL.md +1 -1
  53. package/skills/ctx-stats/SKILL.md +1 -1
  54. 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.14",
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.13";process.on("unhandledRejection",t=>{process.stderr.write(`[context-mode] unhandledRejection: ${t}
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. Trigger phrases: "analyze logs", "summarize output", "process data",
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
- "extract elements", "page snapshot", "browser snapshot", "take a snapshot",
9
- "DOM structure", "inspect page", "form fields", "element selectors",
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", "hit endpoint",
14
- "call API", "check response", "query results", "show tables",
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 (Playwright, Context7, GitHub API) that
18
- may exceed 20 lines, and any operation where output size is uncertain.
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
  Run context-mode diagnostics. Checks runtimes, hooks, FTS5,
5
5
  plugin registration, npm and marketplace versions.
6
6
  Trigger: /context-mode:ctx-doctor
7
- user_invocable: true
7
+ user-invocable: true
8
8
  ---
9
9
 
10
10
  # Context Mode Doctor
@@ -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
- user_invocable: true
7
+ user-invocable: true
8
8
  ---
9
9
 
10
10
  # Context Mode Stats
@@ -4,7 +4,7 @@ description: |
4
4
  Update context-mode from GitHub and fix hooks/settings.
5
5
  Pulls latest, builds, installs, updates npm global, configures hooks.
6
6
  Trigger: /context-mode:ctx-upgrade
7
- user_invocable: true
7
+ user-invocable: true
8
8
  ---
9
9
 
10
10
  # Context Mode Upgrade