dsh-context-mode 0.5.1 → 0.5.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 +29 -3
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +51 -15
- package/package.json +1 -1
- package/skills/ctx-doctor/SKILL.md +22 -0
- package/skills/ctx-index/SKILL.md +46 -0
- package/skills/ctx-insight/SKILL.md +24 -0
- package/skills/ctx-purge/SKILL.md +49 -0
- package/skills/ctx-search/SKILL.md +35 -0
- package/skills/ctx-stats/SKILL.md +26 -0
- package/skills/ctx-upgrade/SKILL.md +31 -0
- package/vendor/context-mode/server.bundle.mjs +1 -1
package/README.md
CHANGED
|
@@ -1,9 +1,26 @@
|
|
|
1
1
|
# context-mode for DSH
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/dsh-context-mode)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
|
|
3
6
|
`dsh-context-mode` exposes a self-contained context-mode server as native
|
|
4
7
|
DeepSeek Harness tools. It registers the full `ctx_*` catalog at runtime and
|
|
5
8
|
adds model-facing routing guidance, so dsh-TUI can use sandboxed execution,
|
|
6
|
-
indexing, FTS5 retrieval, and web fetching without a second MCP client.
|
|
9
|
+
indexing, FTS5 retrieval, and web fetching without a second MCP client. It also
|
|
10
|
+
registers the eight user-invocable skills from the upstream package, so the
|
|
11
|
+
DSH skill surface includes `/context-mode`, `/ctx-doctor`, `/ctx-index`,
|
|
12
|
+
`/ctx-insight`, `/ctx-purge`, `/ctx-search`, `/ctx-stats`, and `/ctx-upgrade`.
|
|
13
|
+
|
|
14
|
+
> **Part of a pair.** This is the *tool* half: the `ctx_*` tools, plus the
|
|
15
|
+
> archiver that files each compaction's transcript into the knowledge base.
|
|
16
|
+
> Its companion
|
|
17
|
+
> [`dsh-context-mode-compaction`](https://github.com/52sujiu/dsh-context-mode-compaction)
|
|
18
|
+
> is the *strategy* half: it replaces DSH's compaction engine so a checkpoint
|
|
19
|
+
> rebuilds the conversation instead of only summarizing it.
|
|
20
|
+
>
|
|
21
|
+
> **Install both.** Without the strategy package, the archive is still written
|
|
22
|
+
> but no checkpoint points at it; without this one, checkpoints point at an
|
|
23
|
+
> archive nothing fills.
|
|
7
24
|
|
|
8
25
|
The engine is vendored: `vendor/context-mode/` carries the source and this
|
|
9
26
|
package builds its own `server.bundle.mjs` from it. There is **no `context-mode`
|
|
@@ -30,8 +47,17 @@ Elastic License 2.0.
|
|
|
30
47
|
dsh plugin --profile dsh-tui add dsh-context-mode
|
|
31
48
|
```
|
|
32
49
|
|
|
33
|
-
|
|
34
|
-
|
|
50
|
+
To get the full pair — this package plus the compaction strategy that turns a
|
|
51
|
+
checkpoint into a searchable transcript — add both and run the wiring step:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
dsh plugin --profile dsh-tui add dsh-context-mode
|
|
55
|
+
dsh plugin --profile dsh-tui add dsh-context-mode-compaction
|
|
56
|
+
npx dsh-context-mode-compaction # point the preset's compaction row here
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Restart the profile afterwards either way. Removing a package removes its
|
|
60
|
+
profile row as well:
|
|
35
61
|
|
|
36
62
|
```sh
|
|
37
63
|
dsh plugin --profile dsh-tui remove dsh-context-mode
|
package/lib/types/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAYlD,eAAO,MAAM,IAAI,qBAAqB,CAAA;AAEtC,qDAAqD;AACrD,MAAM,WAAW,MAAM;IACrB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,+DAA+D;IAC/D,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,eAAO,MAAM,MAAM,EAAE,WAAW,CAAC,MAAM,CAOrC,CAAA;AAWF,eAAO,MAAM,YAAY,QAMb,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAYlD,eAAO,MAAM,IAAI,qBAAqB,CAAA;AAEtC,qDAAqD;AACrD,MAAM,WAAW,MAAM;IACrB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,+DAA+D;IAC/D,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,eAAO,MAAM,MAAM,EAAE,WAAW,CAAC,MAAM,CAOrC,CAAA;AAWF,eAAO,MAAM,YAAY,QAMb,CAAA;AA6EZ,+EAA+E;AAC/E,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,MAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAoF5E"}
|
package/lib/types/index.js
CHANGED
|
@@ -58,14 +58,47 @@ const EXCLUSIVE_CONTEXT_TOOLS = new Set([
|
|
|
58
58
|
'ctx_purge',
|
|
59
59
|
'ctx_upgrade',
|
|
60
60
|
]);
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
const BUNDLED_SKILLS = [
|
|
62
|
+
{
|
|
63
|
+
name: 'context-mode',
|
|
64
|
+
description: 'Route large, inspectable, or data-heavy work through ctx_* tools instead of raw Bash, web calls, or large output.',
|
|
65
|
+
whenToUse: 'Use automatically for logs, tests, builds, git history, API responses, web docs, dependency audits, structured data, or output that may exceed 20 lines.',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'ctx-doctor',
|
|
69
|
+
description: 'Run context-mode diagnostics for runtimes, storage, bridge health, and registration.',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'ctx-index',
|
|
73
|
+
description: 'Index a local file or directory into the persistent context-mode knowledge base.',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'ctx-insight',
|
|
77
|
+
description: 'Open the hosted context-mode Insight analytics dashboard.',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'ctx-purge',
|
|
81
|
+
description: 'Permanently purge context-mode indexed content with an explicit scope and confirmation.',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'ctx-search',
|
|
85
|
+
description: 'Search previously indexed project content and session memory.',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'ctx-stats',
|
|
89
|
+
description: 'Show context-mode token consumption, savings ratio, and per-tool statistics.',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'ctx-upgrade',
|
|
93
|
+
description: 'Upgrade context-mode and report the resulting installation checklist.',
|
|
94
|
+
},
|
|
95
|
+
].map(skill => ({
|
|
96
|
+
...skill,
|
|
97
|
+
path: `skills/${skill.name}/SKILL.md`,
|
|
66
98
|
provider: name,
|
|
67
99
|
source: 'bundled',
|
|
68
|
-
}
|
|
100
|
+
invocation: { modelInvocable: true, userInvocable: true },
|
|
101
|
+
}));
|
|
69
102
|
/** Register the plugin and bridge context-mode's MCP tool catalog into DSH. */
|
|
70
103
|
export async function apply(ctx, config = {}) {
|
|
71
104
|
const resolved = {
|
|
@@ -100,7 +133,7 @@ export async function apply(ctx, config = {}) {
|
|
|
100
133
|
disposers.push(memoryDisposer);
|
|
101
134
|
const precompactDisposer = installPrecompactArchive(ctx, () => client, { enabled: resolved.precompact });
|
|
102
135
|
disposers.push(precompactDisposer);
|
|
103
|
-
const skillDisposer =
|
|
136
|
+
const skillDisposer = registerBundledSkills(ctx);
|
|
104
137
|
if (skillDisposer !== undefined)
|
|
105
138
|
disposers.push(skillDisposer);
|
|
106
139
|
try {
|
|
@@ -146,18 +179,21 @@ export async function apply(ctx, config = {}) {
|
|
|
146
179
|
ctx.logger.warn(`dsh-context-mode: bridge unavailable; plugin is inactive (${errorMessage(error)})`);
|
|
147
180
|
}
|
|
148
181
|
}
|
|
149
|
-
function
|
|
182
|
+
function registerBundledSkills(ctx) {
|
|
150
183
|
const skills = ctx.get('skills', false);
|
|
151
184
|
if (skills === undefined)
|
|
152
185
|
return undefined;
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
186
|
+
const disposers = [];
|
|
187
|
+
for (const skill of BUNDLED_SKILLS) {
|
|
188
|
+
try {
|
|
189
|
+
const content = readFileSync(new URL(`../../${skill.path}`, import.meta.url), 'utf8');
|
|
190
|
+
disposers.push(skills.register({ ...skill, content }));
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
ctx.logger.warn(`dsh-context-mode: bundled skill unavailable (${skill.name}: ${errorMessage(error)})`);
|
|
194
|
+
}
|
|
160
195
|
}
|
|
196
|
+
return disposers.length === 0 ? undefined : () => disposers.forEach(dispose => dispose());
|
|
161
197
|
}
|
|
162
198
|
function toDefinition(tool, client) {
|
|
163
199
|
return {
|
package/package.json
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ctx-doctor
|
|
3
|
+
description: |
|
|
4
|
+
Run context-mode diagnostics. Checks runtimes, hooks, FTS5,
|
|
5
|
+
plugin registration, npm and marketplace versions.
|
|
6
|
+
Trigger: /context-mode:ctx-doctor
|
|
7
|
+
user-invocable: true
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Context Mode Doctor
|
|
11
|
+
|
|
12
|
+
Run diagnostics and display results directly in the conversation.
|
|
13
|
+
|
|
14
|
+
## Instructions
|
|
15
|
+
|
|
16
|
+
1. Call the `ctx_doctor` MCP tool directly. It runs all checks server-side and returns a plain-text status report.
|
|
17
|
+
2. Display the results verbatim — they are already formatted with plain-text status prefixes: `[OK]` PASS, `[FAIL]` FAIL, `[WARN]` WARN. Renderer-safe (no markdown task-list syntax) for cross-client compatibility (e.g., Z.ai GLM).
|
|
18
|
+
3. **Fallback** (only if MCP tool call fails): Derive the **plugin root** from this skill's base directory (go up 2 levels — remove `/skills/ctx-doctor`), then run with Bash:
|
|
19
|
+
```
|
|
20
|
+
CLI="<PLUGIN_ROOT>/cli.bundle.mjs"; [ ! -f "$CLI" ] && CLI="<PLUGIN_ROOT>/build/cli.js"; node "$CLI" doctor
|
|
21
|
+
```
|
|
22
|
+
Re-display results verbatim with the same `[OK]`/`[FAIL]`/`[WARN]` prefixes.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ctx-index
|
|
3
|
+
description: |
|
|
4
|
+
Index a local file or directory into context-mode's persistent FTS5 knowledge base
|
|
5
|
+
so future ctx_search calls can retrieve focused snippets without rereading raw files.
|
|
6
|
+
Trigger: /context-mode:ctx-index
|
|
7
|
+
user-invocable: true
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Context Mode Index
|
|
11
|
+
|
|
12
|
+
Index local project content for later search.
|
|
13
|
+
|
|
14
|
+
## Instructions
|
|
15
|
+
|
|
16
|
+
1. Prefer the `ctx_index` MCP tool when it is available.
|
|
17
|
+
2. Ask for a path only if the user did not provide one and the current project root is ambiguous.
|
|
18
|
+
3. Use `path`, not large inline `content`, so file bytes do not enter the conversation.
|
|
19
|
+
4. For repository indexing, pass conservative bounds and a clear source label:
|
|
20
|
+
|
|
21
|
+
```javascript
|
|
22
|
+
ctx_index({
|
|
23
|
+
path: ".",
|
|
24
|
+
source: "project:<name>",
|
|
25
|
+
maxDepth: 5,
|
|
26
|
+
maxFiles: 200
|
|
27
|
+
})
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
5. If MCP tools are unavailable, fall back to the CLI:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
context-mode index . --source project:<name>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
6. Report the indexed source label, file count or section count, and the matching search command:
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
ctx_search({ source: "project:<name>", queries: ["..."] })
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Safety
|
|
43
|
+
|
|
44
|
+
- Do not index dependency directories, build outputs, secrets, or generated artifacts.
|
|
45
|
+
- Prefer `--exclude` or `exclude` for project-specific noisy paths.
|
|
46
|
+
- For broad repos, ask the user before raising `maxFiles` above 500.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ctx-insight
|
|
3
|
+
description: |
|
|
4
|
+
Open the context-mode Insight dashboard in your default browser.
|
|
5
|
+
Insight is the hosted analytics layer for AI-assisted engineering teams —
|
|
6
|
+
per-engineer productive rate, retry waste, blocker detection, role-narrowed views.
|
|
7
|
+
Trigger: /context-mode:ctx-insight
|
|
8
|
+
user-invocable: true
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Context Mode Insight
|
|
12
|
+
|
|
13
|
+
Open the hosted Insight dashboard in the user's default browser.
|
|
14
|
+
|
|
15
|
+
## Instructions
|
|
16
|
+
|
|
17
|
+
1. Call the `ctx_insight` MCP tool (no parameters). It opens
|
|
18
|
+
<https://context-mode.com/insight> in the default browser and returns a
|
|
19
|
+
confirmation line.
|
|
20
|
+
2. Display the tool's output to the user.
|
|
21
|
+
3. Tell the user:
|
|
22
|
+
- "Insight opened at https://context-mode.com/insight"
|
|
23
|
+
- The landing page at context-mode.com/insight is the single source of truth for sign-in and pricing details.
|
|
24
|
+
- If the browser did not open automatically, share the URL so they can open it manually.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ctx-purge
|
|
3
|
+
description: |
|
|
4
|
+
Purge the context-mode knowledge base. Permanently deletes all indexed content
|
|
5
|
+
and resets session stats. This is destructive and cannot be undone.
|
|
6
|
+
Trigger: /context-mode:ctx-purge
|
|
7
|
+
user-invocable: true
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Context Mode Purge
|
|
11
|
+
|
|
12
|
+
Permanently deletes session data for this project. Two scopes are supported (issue #520):
|
|
13
|
+
|
|
14
|
+
- **Project scope** (`scope: "project"`): wipes EVERYTHING — knowledge base, all session DB rows for every session, events markdown, and stats.
|
|
15
|
+
- **Session scope** (`sessionId: "<id>"` or `scope: "session"`): wipes ONLY the matching session's rows + FTS5 chunks. Sibling sessions, project stats, and the FTS5 store file are preserved.
|
|
16
|
+
|
|
17
|
+
## Instructions
|
|
18
|
+
|
|
19
|
+
1. **Decide the scope first** with the user:
|
|
20
|
+
- "Wipe just one session?" → ask for the `sessionId`.
|
|
21
|
+
- "Wipe the whole project?" → confirm scope:'project' (this is the destructive, irreversible default).
|
|
22
|
+
2. **Warn the user about scope:'project'**. Everything will be deleted:
|
|
23
|
+
- FTS5 knowledge base (all indexed content from `ctx_index`, `ctx_fetch_and_index`, `ctx_batch_execute`)
|
|
24
|
+
- Session events DB (analytics, metadata, resume snapshots) for ALL sessions in the project
|
|
25
|
+
- Session events markdown file
|
|
26
|
+
- In-memory session stats + persisted stats file
|
|
27
|
+
3. Call the `mcp__context-mode__ctx_purge` MCP tool with the chosen parameters:
|
|
28
|
+
- Scoped: `{ confirm: true, sessionId: "<id>" }` — implies scope:'session'.
|
|
29
|
+
- Project: `{ confirm: true, scope: "project" }` — explicit destructive form.
|
|
30
|
+
- Bare `{ confirm: true }` still works but emits a deprecation warning. Prefer the explicit forms.
|
|
31
|
+
4. Report the result to the user — the response lists exactly what was deleted and (for scoped purges) confirms that other sessions and project stats were preserved.
|
|
32
|
+
|
|
33
|
+
## Schema rules
|
|
34
|
+
|
|
35
|
+
- `confirm: true` is always required.
|
|
36
|
+
- `sessionId` and `scope: "project"` together is REJECTED as ambiguous (the sessionId implies session scope; combining with project scope contradicts intent).
|
|
37
|
+
- `scope: "session"` without `sessionId` throws — sessionId is required.
|
|
38
|
+
|
|
39
|
+
## When to Use
|
|
40
|
+
|
|
41
|
+
- **Scoped (per-session)**: scratch acceptance scenarios, drill replays, isolating a polluted session without losing the main working session's stats.
|
|
42
|
+
- **Project**: KB contains stale or incorrect content polluting search results, switching between unrelated projects in the same session, completely fresh start.
|
|
43
|
+
|
|
44
|
+
## Important
|
|
45
|
+
|
|
46
|
+
- `ctx_purge` is the **only** way to delete session data. No other mechanism exists.
|
|
47
|
+
- `ctx_stats` is read-only — shows statistics only.
|
|
48
|
+
- `/clear` and `/compact` do NOT affect any context-mode data.
|
|
49
|
+
- There is no undo. Re-index content if you need it again.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ctx-search
|
|
3
|
+
description: |
|
|
4
|
+
Search context-mode's persistent FTS5 knowledge base for previously indexed
|
|
5
|
+
local project content, documentation, or session memory.
|
|
6
|
+
Trigger: /context-mode:ctx-search
|
|
7
|
+
user-invocable: true
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Context Mode Search
|
|
11
|
+
|
|
12
|
+
Search indexed content without rereading raw sources into conversation context.
|
|
13
|
+
|
|
14
|
+
## Instructions
|
|
15
|
+
|
|
16
|
+
1. Prefer the `ctx_search` MCP tool when it is available.
|
|
17
|
+
2. Batch all related questions in one `queries` array.
|
|
18
|
+
3. Scope with `source` when the user names a project or indexed label.
|
|
19
|
+
4. Use short, specific queries of two to four technical terms.
|
|
20
|
+
|
|
21
|
+
```javascript
|
|
22
|
+
ctx_search({
|
|
23
|
+
source: "project:<name>",
|
|
24
|
+
queries: ["authentication middleware", "token refresh"],
|
|
25
|
+
limit: 5
|
|
26
|
+
})
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
5. If MCP tools are unavailable, fall back to the CLI:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
context-mode search "authentication middleware" --source project:<name> --limit 5
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
6. If the index is empty, tell the user to run `/context-mode:ctx-index` or `context-mode index <path>` first.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ctx-stats
|
|
3
|
+
description: |
|
|
4
|
+
Show how much context window context-mode saved this session.
|
|
5
|
+
Displays token consumption, context savings ratio, and per-tool breakdown.
|
|
6
|
+
Read-only — shows stats only, no reset capability.
|
|
7
|
+
To wipe the knowledge base entirely, use ctx_purge instead.
|
|
8
|
+
Trigger: /context-mode:ctx-stats
|
|
9
|
+
user-invocable: true
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Context Mode Stats
|
|
13
|
+
|
|
14
|
+
Show context savings for the current session.
|
|
15
|
+
|
|
16
|
+
## Instructions
|
|
17
|
+
|
|
18
|
+
1. Call the `mcp__context-mode__ctx_stats` MCP tool (no parameters needed).
|
|
19
|
+
2. **CRITICAL**: You MUST copy-paste the ENTIRE tool output as markdown text directly into your response message. Do NOT summarize, do NOT collapse, do NOT paraphrase. The user must see the full tables without pressing ctrl+o. Copy every line exactly as returned by the tool.
|
|
20
|
+
3. After the full output, add ONE sentence highlighting the key savings metric, e.g.:
|
|
21
|
+
- "context-mode saved **12.4x** — 92% of data stayed in sandbox."
|
|
22
|
+
- If no data yet: "No context-mode calls yet this session."
|
|
23
|
+
|
|
24
|
+
## Purge
|
|
25
|
+
|
|
26
|
+
- **`ctx_purge(confirm: true)`** — Permanently deletes all indexed content from the knowledge base. Use `/context-mode:ctx-purge` for this.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ctx-upgrade
|
|
3
|
+
description: |
|
|
4
|
+
Update context-mode from GitHub and fix hooks/settings.
|
|
5
|
+
Pulls latest, builds, installs, updates npm global, configures hooks.
|
|
6
|
+
Trigger: /context-mode:ctx-upgrade
|
|
7
|
+
user-invocable: true
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Context Mode Upgrade
|
|
11
|
+
|
|
12
|
+
Pull latest from GitHub and reinstall the plugin.
|
|
13
|
+
|
|
14
|
+
## Instructions
|
|
15
|
+
|
|
16
|
+
1. Call the `ctx_upgrade` MCP tool directly. It returns a shell command to execute.
|
|
17
|
+
2. Run the returned command using your shell execution tool (Bash, shell_execute, etc.).
|
|
18
|
+
3. Display results as a markdown checklist:
|
|
19
|
+
```
|
|
20
|
+
## context-mode upgrade
|
|
21
|
+
- [x] Pulled latest from GitHub
|
|
22
|
+
- [x] Built and installed v1.0.39
|
|
23
|
+
- [x] Hooks configured
|
|
24
|
+
- [x] Doctor: all checks PASS
|
|
25
|
+
```
|
|
26
|
+
Use `[x]` for success, `[ ]` for failure. Show actual version numbers.
|
|
27
|
+
4. Tell the user to **restart their session** to pick up the new version.
|
|
28
|
+
5. **Fallback** (only if MCP tool call fails): Derive the **plugin root** from this skill's base directory (go up 2 levels — remove `/skills/ctx-upgrade`), then run with Bash:
|
|
29
|
+
```
|
|
30
|
+
CLI="<PLUGIN_ROOT>/cli.bundle.mjs"; [ ! -f "$CLI" ] && CLI="<PLUGIN_ROOT>/build/cli.js"; node "$CLI" upgrade
|
|
31
|
+
```
|
|
@@ -582,7 +582,7 @@ ${s}`}}import{execFileSync as WN}from"node:child_process";function GN(){if(proce
|
|
|
582
582
|
WHERE tool IN ('ctx_search', 'ctx_fetch_and_index')`).get();m?.bytes&&(a+=Number(m.bytes))}catch{}}}finally{h.close()}}catch{}}let u=0;t.sessionId&&t.contentDbPath&&(u=UD(t.sessionId,t.contentDbPath,{loadDatabase:t.loadDatabase}),i+=u);let l=Math.floor((o+i+c)/4);return{eventDataBytes:o,bytesAvoided:i,bytesReturned:a,snapshotBytes:c,contentBytes:u,totalSavedTokens:l}}function am(t){let e=pc({worktreeHash:t.worktreeHash,sessionsDir:t.sessionsDir}),r=pc({sessionId:t.sessionId,worktreeHash:t.worktreeHash,sessionsDir:t.sessionsDir,contentDbPath:t.contentDbPath}),n=r.bytesReturned,s=e.bytesAvoided+e.bytesReturned,o=Math.max(0,s-n);return{eventDataBytes:e.eventDataBytes,bytesAvoided:o,bytesReturned:n,snapshotBytes:e.snapshotBytes,contentBytes:r.contentBytes,totalSavedTokens:Math.floor((e.eventDataBytes+o+e.snapshotBytes)/4)}}var qD={minEvents:100,minProjects:5,recencyMs:30*864e5,minAvgBytes:50};function BD(t,e,r){let n={name:t.name,eventCount:0,sessionCount:0,dataBytes:0,rescueBytes:0,contentBytes:0,uuidConvs:0,projectDirs:[],firstMs:Number.POSITIVE_INFINITY,lastMs:0,isReal:!1};if(!_r(t.sessionsDir))return n;let s=[];try{s=ks(t.sessionsDir).filter(l=>l.endsWith(".db"))}catch{return n}if(s.length===0)return n;let o=null;try{o=e()}catch{return n}if(!o)return n;let i=new Set,a=new Set;for(let l of s){let d=vt(t.sessionsDir,l);try{let f=new o(d,{readonly:!0});try{let h=f.prepare("SELECT COUNT(*) AS cnt, COALESCE(SUM(LENGTH(data)), 0) AS bytes FROM session_events").get();h&&(n.eventCount+=Number(h.cnt??0),n.dataBytes+=Number(h.bytes??0));try{let p=f.prepare("SELECT COUNT(*) AS cnt FROM session_meta").get();n.sessionCount+=Number(p?.cnt??0)}catch{}try{let p=f.prepare("SELECT COALESCE(SUM(length(snapshot)), 0) AS bytes FROM session_resume WHERE consumed = 1").get();p?.bytes&&(n.rescueBytes+=Number(p.bytes))}catch{}try{let p=f.prepare("SELECT MIN(created_at) AS mn, MAX(created_at) AS mx FROM session_events").get();if(p?.mn){let m=Date.parse(p.mn+(p.mn.endsWith("Z")?"":"Z"));Number.isFinite(m)&&m<n.firstMs&&(n.firstMs=m)}if(p?.mx){let m=Date.parse(p.mx+(p.mx.endsWith("Z")?"":"Z"));Number.isFinite(m)&&m>n.lastMs&&(n.lastMs=m)}}catch{}try{let p=f.prepare("SELECT DISTINCT project_dir AS p FROM session_events WHERE project_dir != ''").all();for(let m of p)m.p&&i.add(m.p)}catch{}try{let p=f.prepare("SELECT DISTINCT session_id AS s FROM session_events").all();for(let m of p)m.s&&a.add(m.s)}catch{}}finally{f.close()}}catch{}}n.projectDirs=Array.from(i),n.uuidConvs=a.size;let c=n.eventCount>0?n.dataBytes/n.eventCount:0,u=n.lastMs>0&&r.nowMs-n.lastMs<=r.recencyMs;return n.isReal=n.eventCount>=r.minEvents&&i.size>=r.minProjects&&u&&c>=r.minAvgBytes,n}function gc(t){let e=FD({home:t?.home}),r=t?.loadDatabase??He,n={...qD,...t?.filter??{},nowMs:t?.filter?.nowMs??Date.now()},s=[],o=0,i=0,a=0;for(let c of e){if(!_r(c.sessionsDir))continue;let u=BD(c,r,n);s.push(u),o+=u.eventCount,i+=u.sessionCount,a+=u.dataBytes+u.rescueBytes}return{totalEvents:o,totalSessions:i,totalBytes:a,perAdapter:s}}var SS={project:"What you're building",feedback:"How you work",user:"Who you are",reference:"Where to look",memory:"Long-term context",other:"Other notes"},HD={"claude-code":"Claude Code","gemini-cli":"Gemini CLI",antigravity:"Antigravity","antigravity-cli":"Antigravity CLI",openclaw:"Openclaw",codex:"Codex CLI",cursor:"Cursor","vscode-copilot":"VS Code Copilot","copilot-cli":"GitHub Copilot CLI",kiro:"Kiro",pi:"Pi",omp:"OMP","qwen-code":"Qwen Code",kilo:"Kilo",opencode:"OpenCode",zed:"Zed","jetbrains-copilot":"JetBrains"};function fc(t){return HD[t]??t}function Ve(t){if(!Number.isFinite(t)||t<=0)return"0 B";if(t<1024)return`${Math.round(t)} B`;let e=t/1024;if(e<1024)return e<100?`${e.toFixed(1)} KB`:`${Math.round(e)} KB`;let r=e/1024;if(r<1024)return r<100?`${r.toFixed(1)} MB`:`${Math.round(r)} MB`;let n=r/1024;return n<100?`${n.toFixed(2)} GB`:`${n.toFixed(1)} GB`}function VD(t){let e=parseFloat(t);if(isNaN(e)||e<1)return"< 1 min";if(e<60)return`${Math.round(e)} min`;let r=Math.floor(e/60),n=Math.round(e%60);return n>0?`${r}h ${n}m`:`${r}h`}function lc(t){if(!t)return!1;try{return Intl.DateTimeFormat.supportedLocalesOf(t).length===0?!1:(new Intl.DateTimeFormat(t),!0)}catch{return!1}}function WD(){let t=process.env??{},e=t.CONTEXT_MODE_LOCALE??"";if(e&&!lc(e)&&(e=""),!e){if(process.platform==="darwin"){try{let n=zD("defaults",["read","-g","AppleLocale"],{encoding:"utf8",timeout:500}).trim();n&&(e=n.replace(/_/g,"-"))}catch{}e&&!lc(e)&&(e="")}if(!e&&(t.LC_TIME||t.LANG)){let n=(t.LC_TIME||t.LANG||"").split(".")[0];n&&(e=n.replace(/_/g,"-")),e&&!lc(e)&&(e="")}if(!e)try{e=new Intl.DateTimeFormat().resolvedOptions().locale}catch{e="en-US"}}let r=t.CONTEXT_MODE_TZ??"";if(!r)try{r=new Intl.DateTimeFormat().resolvedOptions().timeZone}catch{r="UTC"}return lc(e)||(e="en-US"),{locale:e,tz:r||"UTC"}}function pS(t){let e=hc();return e?t===e?"~":t.startsWith(e+LD)?"~"+t.slice(e.length):t:t}function GD(t,e,r){if(!Number.isFinite(e)||e<=0)return[];let n=e*ni(),s=(y,b=2)=>y.toFixed(b),o=Math.round(n/20),i=(n/200).toFixed(1),a=Math.round(n/73.67),c=Math.round(n*10),u=r>0?Math.round(n*10/r*365):0,l=(e*3/1e6).toFixed(2),d=(e*2.5/1e6).toFixed(2),f=(e*1.25/1e6).toFixed(2),h=(e*1/1e6).toFixed(2),p=process.env.PI_CONTEXT_MODE_PRICE_OUTPUT_PER_TOKEN!==void 0,m=process.env.PI_CONTEXT_MODE_MODEL_ID,g=[];return p&&m?g.push(` $${s(n)} of ${m} tokens your team didn't burn.`):p?g.push(` $${s(n)} of tokens your team didn't burn.`):g.push(` $${s(n)} of Opus 4.7 tokens your team didn't burn.`),g.push(` context-mode kept ${Ve(t)} out of context \u2014 that's ${o} months of Cursor Pro paid for itself.`),c>0&&u>0&&(g.push(""),g.push(` Scale across a 10-dev team and that's ~$${u.toLocaleString("en-US")}/year saved.`)),p||(g.push(""),g.push(" (Opus rates shown for context. On cheaper models the dollar number drops; the savings ratio holds.)")),g}function KD(t){let{conversation:e,lifetime:r,multiAdapter:n,realBytes:s,cwd:o,locale:i,tz:a,now:c,version:u,latestVersion:l}=t,d=[],f=e.events*yS,h=Math.round((e.snapshotBytes??0)/4),p=f+h,m=s?.conversation?.totalSavedTokens??0,g=Math.max(p,m),y=(r?.totalEvents??0)*yS,b=Math.round((r?.rescueBytes??0)/4),_=y+b,x=s?.lifetime?.totalSavedTokens??0,w=Math.max(_,x),I=s?.lifetime?.bytesReturned??0,R=s?.lifetime?.bytesAvoided??0,M=I+R>0?Math.max(1,Math.floor(I/4)):Math.max(1,Math.round(w*.02)),z=n?.totalBytes&&n.totalBytes>0?n.totalBytes:w*4,W=s?.conversation?s.conversation.eventDataBytes+s.conversation.bytesAvoided+s.conversation.snapshotBytes:g*4,$=e.daysAlive>=1?`${e.daysAlive.toFixed(1)} days alive \xB7 still going`:`${Math.max(1,Math.round(e.daysAlive*24))} hr alive \xB7 still going`,T=r?.firstEventMs??n?.perAdapter?.[0]?.firstMs??0,F=T>0?Math.max(1,Math.round((c-T)/864e5)):0,de=n?.totalSessions??r?.totalSessions??1,nt=n?.perAdapter.filter(Pe=>Pe.isReal).length??0,ir;if(n&&nt>=2)ir=`across ${nt} AI tools`;else if(n&&nt===1){let Pe=n.perAdapter.find(Ct=>Ct.isReal);ir=`in ${Pe?fc(Pe.name):"Claude Code"}`}else ir="in Claude Code";F>0?d.push(` Across ${F} days you ran ${qt(de)} conversations ${ir}.`):d.push(` You ran ${qt(de)} conversations ${ir}.`);let Ac=F>0?z/F:0;d.push(` context-mode kept ${Ve(z)} out of your context window \u2014 about ${Ve(Ac)} every single day.`),d.push(""),d.push(""),d.push(" \u2500\u2500\u2500 1. Where you are now \u2500\u2500\u2500"),d.push("");let Em=e.firstEventMs&&e.firstEventMs>0?fS(e.firstEventMs,i,a):"";if(Em?d.push(` This conversation started ${Em} in ${pS(o)}.`):d.push(` This conversation lives in ${pS(o)}.`),d.push(` ${$}.`),e.snapshotsConsumed>0&&e.snapshotBytes>0){let Pe=e.lastRescueMs&&e.lastRescueMs>0?fS(e.lastRescueMs,i,a):"",Ct=Math.round(e.snapshotBytes/1024);Pe?d.push(` On ${Pe}, /compact fired \u2014 ${Ct} KB rescued from snapshot.`):d.push(` /compact fired \u2014 ${Ct} KB rescued from snapshot.`),d.push(" Without that, you'd be re-explaining everything to a blank model right now.")}d.push("");let km=s?.conversation,Tm=km?.bytesAvoided??0,Nc=km?.bytesReturned??0;if(Tm+Nc===0)d.push(" No measurable redirect activity captured yet \u2014 bars will appear once context-mode diverts its first payload."),d.push("");else{let Pe=Tm+Nc,Ct=Math.max(1,Nc),It=Math.max(1,Math.floor(Pe/4)),ar=Math.max(1,Math.floor(Ct/4)),Dc=Ur(It,It,32),XS=Ur(ar,It,32),YS=(1-ar/It)*100,QS=Math.max(1,Math.round(It/ar));d.push(` Without context-mode ${Ve(Pe).padStart(8)} ${Dc} ${qt(It).padStart(7)} tokens`),d.push(` With context-mode ${Ve(Ct).padStart(8)} ${XS} ${qt(ar).padStart(7)} tokens`),d.push(` ${YS.toFixed(1)}% kept out of context \xB7 your AI ran ${QS}\xD7 longer before /compact fired`),d.push("")}if(e.byDay&&e.byDay.length>0){let Pe=e.lastEventMs&&e.firstEventMs?Math.max(1,Math.round((e.lastEventMs-e.firstEventMs)/864e5)+1):e.byDay.length;d.push(` How that ${Ve(W)} built up \u2014 ${Pe} days, ${e.byDay.length} active:`),d.push(""),d.push(...XD(e.byDay,i,a))}d.push(""),d.push(""),d.push(" \u2500\u2500\u2500 2. What this chat captured (used when you --continue or /resume here) \u2500\u2500\u2500"),d.push("");let WS=e.byCategory.reduce((Pe,Ct)=>Pe+Ct.count,0).toLocaleString(i);d.push(` ${WS} things \u2014 files, errors, decisions, agent runs:`),d.push("");let GS=e.byCategory[0]?.count??1;for(let Pe of e.byCategory)d.push(` ${Pe.label.padEnd(26)} ${String(Pe.count).padStart(5)} ${Ur(Pe.count,GS,28)}`);d.push(""),d.push(""),d.push(" \u2500\u2500\u2500 3. The scope, getting wider \u2500\u2500\u2500"),d.push("");let $m=e.firstEventMs&&e.firstEventMs>0?new Intl.DateTimeFormat(i,{timeZone:a,year:"numeric",month:"short",day:"numeric"}).format(new Date(e.firstEventMs)):"",Rm=T>0?new Intl.DateTimeFormat(i,{timeZone:a,year:"numeric",month:"short",day:"numeric"}).format(new Date(T)):"",Pm=r?.distinctProjects??0,KS=r?.totalEvents??n?.totalEvents??0;if(d.push(` This chat: ${Ve(W)} kept out \xB7 ${e.events.toLocaleString(i)} captures${$m?` \xB7 started ${$m}`:""}.`),d.push(` All your work: ${Ve(z)} kept out \xB7 ${KS.toLocaleString(i)} captures across ${Pm} project${Pm===1?"":"s"}${Rm?` \xB7 since ${Rm}`:""}.`),d.push(""),d.push(""),d.push(" \u2500\u2500\u2500 4. The bottom line \u2500\u2500\u2500"),d.push(""),d.push(...GD(z,w,F)),d.push(""),d.push(""),d.push(" \u2500\u2500\u2500 5. What context-mode learned about how you work \u2500\u2500\u2500"),d.push(""),r&&r.autoMemoryCount>0){d.push(` ${r.autoMemoryCount} preferences picked up across ${r.autoMemoryProjects} project${r.autoMemoryProjects===1?"":"s"}:`);let Pe=Object.entries(r.autoMemoryByPrefix).sort((It,ar)=>ar[1]-It[1]),Ct=Pe.length>0?Pe[0][1]:1;for(let[It,ar]of Pe){let Dc=SS[It]??It;d.push(` ${Dc.padEnd(26)} ${String(ar).padStart(2)} ${Ur(ar,Ct,20)}`)}}else d.push(" No preferences learned yet \u2014 context-mode picks them up automatically.");d.push(""),d.push(""),d.push(" Your AI talks less, remembers more, costs less."),d.push(` Locale ${i} \xB7 timezone ${a} \xB7 pricing examples for illustration only.`),d.push("");let JS=u?`v${u}`:"context-mode";return d.push(` ${JS}`),u&&l&&l!=="unknown"&&bS(l,u)&&d.push(` Update available: v${u} -> v${l} | ctx_upgrade`),JD(d)}function JD(t){let e=[],r=0;for(let n of t)n===""?(r++,r<=2&&e.push(n)):(r=0,e.push(n));for(;e.length>0&&e[e.length-1]==="";)e.pop();return e}function XD(t,e,r){if(t.length===0)return[];let n=[...t].sort((f,h)=>f.ms-h.ms),s=n[0],o=n[n.length-1],i=Math.max(1,o.ms-s.ms),a=n[0];for(let f of n)f.count>a.count&&(a=f);let c=56,u=Array.from({length:c},()=>"\u2500");for(let f of n){let h=Math.round((f.ms-s.ms)/i*(c-1)),p="\u25CF";f===a&&(p="\u2588"),(f.rescueBytes??0)>0&&(p="\u25C6"),u[h]=p}let l=f=>{let h=new Intl.DateTimeFormat(e,{timeZone:r,month:"short",day:"numeric"}).formatToParts(new Date(f)),p=(h.find(g=>g.type==="month")?.value??"").toLowerCase(),m=h.find(g=>g.type==="day")?.value??"";return`${p} ${m}`},d=[];d.push(` ${l(s.ms)} ${u.join("")} ${l(o.ms)}`),d.push("");for(let f of n){let h=l(f.ms).padEnd(7),p=`${f.count} captures`,m=f===a?" \u2190 peak":"",g=(f.rescueBytes??0)>0?` \u25C6 /compact rescued ${Math.round((f.rescueBytes??0)/1024)} KB`:"";d.push(` ${h} ${p}${m}${g}`)}return d.push(""),d.push(" \u25CF active day \u2588 peak day \u25C6 /compact rescue"),d}function fS(t,e,r){if(!Number.isFinite(t)||t<=0)return"";let n=new Date(t);if(Number.isNaN(n.getTime()))return"";let s=new Intl.DateTimeFormat(e,{timeZone:r,year:"numeric",month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",hour12:!1}).formatToParts(n),o=d=>s.find(f=>f.type===d)?.value??"",i=o("day"),a=o("month"),c=o("year"),u=o("hour"),l=o("minute");return u==="24"&&(u="00"),`${i} ${a} ${c} at ${u}:${l} (${r})`}function qt(t){return t>=1e6?`${(t/1e6).toFixed(1)}M`:t>=1e3?`${(t/1e3).toFixed(1)}K`:String(t)}function ni(){let t=process.env.PI_CONTEXT_MODE_PRICE_OUTPUT_PER_TOKEN;if(t!==void 0&&t!==""){let e=Number(t);if(Number.isFinite(e)&&e>0)return e}return 5/1e6}var eB=5/1e6;function mc(t){return`$${((Number.isFinite(t)&&t>0?t:0)*ni()).toFixed(2)}`}function Ur(t,e,r=40){if(e<=0)return"\u2591".repeat(r);let n=Math.max(1,Math.round(t/e*r));return"\u2588".repeat(Math.min(n,r))+"\u2591".repeat(Math.max(0,r-n))}function mS(t,e){let r=e?.sessionTokensSaved??0;if(t.total_events===0&&(e?.lifetime?.totalEvents??0)===0&&r===0&&(e?.multiAdapter?.totalEvents??0)===0)return[];let n=e?.topN??Number.POSITIVE_INFINITY,s=[];s.push("");let o=e?.multiAdapter,i=o?.perAdapter.filter(m=>m.isReal).length??0,a=o?.totalEvents??e?.lifetime?.totalEvents??t.total_events,c=o?.totalSessions??e?.lifetime?.totalSessions??t.session_count,u=e?.lifetime?.distinctProjects;if(a>0&&u&&u>0){let m=i>=2?" everywhere":"";s.push(` All your work${m} \xB7 ${qt(a)} events captured across ${u} project${u===1?"":"s"} \xB7 ${qt(c)} conversations`)}else{s.push("Persistent memory \u2713 preserved across compact, restart & upgrade");let m=c===0&&r>0?1:c,g=m===1?"1 session":`${qt(m)} sessions`,y=a*256+r;s.push(` ${qt(a)} events \xB7 ${g} \xB7 ~${mc(y)} saved lifetime`)}s.push("");let l=e?.lifetime?.categoryCounts,d;l&&Object.keys(l).length>0?d=Object.entries(l).filter(([,m])=>m>0).map(([m,g])=>({category:m,count:g,label:dc[m]||m})).sort((m,g)=>g.count-m.count):d=(t.by_category??[]).filter(m=>m&&m.count>0);let f=d.slice(0,n),h=f.length>0?f[0].count:1;for(let m of f)s.push(` ${m.label.padEnd(26)} ${String(m.count).padStart(5)} ${Ur(m.count,h,30)}`);let p=Math.max(0,d.length-n);return p>0&&s.push(` ... ${p} more categor${p===1?"y":"ies"}`),s}function hS(t){if(!t||t.autoMemoryCount===0)return[];let e=[];e.push(""),e.push(` Preferences learned \xB7 ${t.autoMemoryCount} across ${t.autoMemoryProjects} project${t.autoMemoryProjects===1?"":"s"}`);let r=Object.entries(t.autoMemoryByPrefix).sort((s,o)=>o[1]-s[1]).slice(0,6),n=r.length>0?r[0][1]:1;for(let[s,o]of r){let i=SS[s]??s;e.push(` ${i.padEnd(26)} ${String(o).padStart(2)} ${Ur(o,n,20)}`)}return e}function gS(t,e){let r=[],n=mc(t),s=(e?.totalEvents??0)*256+t,o=mc(s);return r.push(""),r.push("\u2500".repeat(65)),r.push("Your AI talks less, remembers more, costs less."),r.push(`${n} this session \xB7 ${o} lifetime`),r.push("\u2500".repeat(65)),r}var yS=256;function _S(t){if(!t)return[];let e=[],r=[];for(let s of t.perAdapter)(s.isReal?e:r).push(s);if(e.length===0&&r.length===0)return[];let n=[];if(e.length>0){n.push(""),n.push("Where it came from (tools you actually used \u2014 fixtures + probes filtered):"),n.push("");let s=16,o=10,i=10,a=16;n.push(` ${"Tool".padEnd(s)}${"Captures".padStart(o)}${"Indexed".padStart(i)}${"Total kept out".padStart(a)}`);let c=[...e].sort((u,l)=>l.dataBytes+l.rescueBytes-(u.dataBytes+u.rescueBytes));for(let u of c){let l=u.dataBytes+u.rescueBytes,d=u.eventCount>0?qt(u.eventCount):"\u2014",f=Ve(u.dataBytes),h=Ve(l);n.push(` ${fc(u.name).padEnd(s)}${d.padStart(o)}${f.padStart(i)}${h.padStart(a)}`)}}if(r.length>0){e.length>0&&n.push("");let s=r.map(o=>fc(o.name)).join(", ");n.push(` Skipped (${r.length}): ${s}`),n.push(" These adapters have DBs on disk but only test fixtures, dev skeletons,"),n.push(" or detection probes \u2014 no real chat activity.")}return n}function yc(t,e,r,n){let s=[],o=VD(t.session.uptime_min),i=n?.lifetime,a=n?.mcpUsage,c=n?.conversation,u=n?.realBytes,l=n?.multiAdapter,d=l?.perAdapter.filter(I=>I.isReal).length??0;if(l&&d>0){let I=l.totalSessions||i?.totalSessions||0,R=i?.firstEventMs??0,M=R>0?Math.max(1,Math.round((Date.now()-R)/864e5)):0,z=M>0?`Across ${M} day${M===1?"":"s"} `:"",W=I>0?`you ran ${qt(I)} conversation${I===1?"":"s"} `:"you ran ",$;if(d>=2)$=`across ${d} AI tools`;else{let T=l.perAdapter.find(F=>F.isReal);$=`in ${T?fc(T.name):"Claude Code"}`}s.push(`${z}${W}${$}.`),s.push("")}if(c&&c.events>0){s.length>0&&(s.length=0);let I=WD(),R=n?.cwd??process.cwd(),M=n?.now??Date.now(),z=n?.locale??I.locale,W=n?.tz??I.tz;return s.push(...KD({conversation:c,lifetime:i,multiAdapter:l,realBytes:u,cwd:R,locale:z,tz:W,now:M,version:e,latestVersion:r})),s.join(`
|
|
583
583
|
`)}let f=t.savings.kept_out+(t.cache?t.cache.bytes_saved:0),h=t.savings.total_bytes_returned,p=t.savings.total_calls,m=f+h,g=m>0?f/m*100:0,y=Math.round(f/4),b=h>0?Math.max(1,Math.round(m/Math.max(h,1))):0;if(f===0){s.push(`context-mode ${o} ${p} calls`),s.push(""),p===0?s.push("No tool calls yet. Use batch_execute or execute to start saving tokens."):s.push(`${Ve(h)} entered context | 0 tokens saved`),s.push(...mS(t.projectMemory,{lifetime:i,multiAdapter:l,sessionTokensSaved:0})),s.push(..._S(l)),s.push(...hS(i)),s.push(...gS(0,i)),s.push("");let I=e?`v${e}`:"context-mode";return s.push(I),e&&r&&r!=="unknown"&&bS(r,e)&&s.push(`Update available: v${e} -> v${r} | ctx_upgrade`),s.join(`
|
|
584
584
|
`)}s.push(`${qt(y)} tokens saved \xB7 ${g.toFixed(1)}% reduction \xB7 ${o} \xB7 ~${mc(y)} saved (Opus)`),s.push(""),s.push(`Without context-mode |${Ur(m,m)}| ${Ve(m)}`),s.push(`With context-mode |${Ur(h,m)}| ${Ve(h)}`),s.push(""),b>=2?s.push(`${Ve(f)} kept out of your conversation \u2014 ${b}\xD7 longer sessions before compact.`):s.push(`${Ve(f)} kept out of your conversation. Never entered context.`),s.push("");let _=[`${p} calls`];t.cache&&t.cache.hits>0&&_.push(`${t.cache.hits} cache hits (+${Ve(t.cache.bytes_saved)})`),s.push(_.join(" \xB7 "));let x=t.savings.by_tool.filter(I=>I.calls>0);if(x.length>=2){s.push("");let I=x.map(R=>{let M=R.context_kb*1024,z=g<100?M/(1-g/100):M,W=Math.max(0,z-M);return{...R,returnedBytes:M,estimatedSaved:W}}).sort((R,M)=>M.estimatedSaved-R.estimatedSaved);for(let R of I){let M=R.tool.length>22?R.tool.slice(0,19)+"...":R.tool;s.push(` ${M.padEnd(22)} ${String(R.calls).padStart(4)} calls ${Ve(R.estimatedSaved).padStart(8)} saved`)}}if(a&&a.length>0){let I=a.filter(R=>R.median_concurrency!=null&&(R.max_concurrency??1)>1);if(I.length>0){s.push(""),s.push("Parallel I/O \u2713 one call did the work of many \u2014 faster runs, lower bill, same answer.");for(let R of I){let M=R.tool_name.replace(/^mcp__.*?__/,"");s.push(` ${M.padEnd(22)} ${R.calls} batches \xB7 ${R.median_concurrency} typical, ${R.max_concurrency} peak`)}}}s.push(...mS(t.projectMemory,{lifetime:i,multiAdapter:l,sessionTokensSaved:y})),s.push(..._S(l)),s.push(...hS(i)),s.push(...gS(y,i)),s.push("");let w=e?`v${e}`:"context-mode";return s.push(w),e&&r&&r!=="unknown"&&r!==e&&s.push(`Update available: v${e} -> v${r} | ctx_upgrade`),s.join(`
|
|
585
|
-
`)}var xc=Os(sz(import.meta.url)),sr=(()=>{let t="0.5.
|
|
585
|
+
`)}var xc=Os(sz(import.meta.url)),sr=(()=>{let t="0.5.2";if(t!==void 0&&t.trim().length>0)return t.trim();for(let e of["../../package.json","../package.json","./package.json"]){let r=rt(xc,e);if(Ie(r))try{return JSON.parse(oi(r,"utf8")).version}catch{}}return"unknown"})(),Ic=process.env.CONTEXT_MODE_UPSTREAM_CHECK==="1";function LS(){return Ie(rt(xc,"package.json"))?xc:Os(xc)}function cz(t){try{let e=process.platform==="win32"?kc("cmd.exe",["/d","/s","/c","codex plugin list"],{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:5e3}):kc("codex",["plugin","list"],{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:5e3});if(e.status!==0)return t;let r=Lf(String(e.stdout));if(r&&Ie(rt(r,".codex-plugin","hooks.json")))return r}catch{}return t}function jS(t){let e=LS();return t==="codex"?cz(e):e}process.env.CONTEXT_MODE_EMBEDDED_PLUGIN_TOOLS!=="1"&&(process.on("unhandledRejection",t=>{process.stderr.write(`[context-mode] unhandledRejection: ${t}
|
|
586
586
|
`)}),process.on("uncaughtException",t=>{try{YD(2,`[context-mode] uncaughtException: ${t?.message??t}
|
|
587
587
|
`)}finally{process.exit(1)}}));var An=Va(),Sc=Hv(An),Se=new Ua({name:"context-mode",version:sr}),uz=[];function lz(t={}){if((t.embedded??process.env.CONTEXT_MODE_EMBEDDED_PLUGIN_TOOLS)==="1")return!1;let r=t.platform??mt().platform;if(r!=="opencode"&&r!=="kilo")return!1;let n=t.settings??dz(r);return pz(n)&&fz(n)}function dz(t){let e=t==="kilo"?"kilo":"opencode",r=[rt(`${e}.json`),rt(`${e}.jsonc`),rt(`.${e}`,`${e}.json`),rt(`.${e}`,`${e}.jsonc`),gt(Is(),".config",e,`${e}.json`),gt(Is(),".config",e,`${e}.jsonc`)];for(let n of r)try{if(!Ie(n))continue;return JSON.parse(uS(oi(n,"utf8")))}catch{}return null}function pz(t){let e=t?.plugin;return Array.isArray(e)&&e.some(r=>typeof r=="string"&&r.includes("context-mode"))}function fz(t){let e=t?.mcp;return!!(e&&typeof e=="object"&&!Array.isArray(e)&&Object.prototype.hasOwnProperty.call(e,"context-mode"))}var FS=lz(),pm=!1;function mz(t={}){if(pm)return;pm=!0;let e=t.write??(n=>{process.stderr.write(n)}),r=t.platform??"opencode/kilo";e(`[context-mode] ctx_* tools/list intentionally empty on this MCP child: legacy mcp.context-mode block coexists with plugin: ["context-mode"] in ${r}.json \u2014 plugin-native tools are the supported path (#623). Run \`context-mode upgrade\` to remove the legacy block (preserves other MCP servers).
|
|
588
588
|
`)}function ZB(){pm=!1}function hz(t=Se){t.server.registerCapabilities({tools:{listChanged:!1}}),t.server.setRequestHandler(mn,async()=>({tools:[]}))}var gz=Se.registerTool.bind(Se);Se.registerTool=(...t)=>{let[e,r,n]=t;if(FS){mz();return}let s=yz(e,n);return uz.push({name:e,config:r,handler:s}),t[2]=s,gz(...t)};function yz(t,e){return async r=>{$x();try{return await e(r)}catch(n){let s=Ez(n);if(s)try{return V(t,s)}catch(o){if(o instanceof Zr)return s;throw o}throw n}finally{Rx()}}}FS&&process.env.CONTEXT_MODE_EMBEDDED_PLUGIN_TOOLS!=="1"&&hz(Se);var _m=new az;async function UB(t,e){let r=typeof t=="string"?{projectDir:t}:t;return _m.run(r,e)}Se.server.registerCapabilities({prompts:{listChanged:!1},resources:{listChanged:!1}});Se.server.setRequestHandler(Yn,async()=>({prompts:[]}));Se.server.setRequestHandler(Jn,async()=>({resources:[]}));Se.server.setRequestHandler(Xn,async()=>({resourceTemplates:[]}));function fm(t){if(Array.isArray(t))return t.map(fm);if(t===null||typeof t!="object")return t;let e={};for(let[r,n]of Object.entries(t))if(r!=="additionalProperties"){if(r==="const"){e.enum=[n];continue}e[r]=fm(n)}return e}function _z(t=Se){try{let r=t.server._requestHandlers?.get("tools/list");if(typeof r!="function")return;t.server.setRequestHandler(mn,async(n,s)=>{let o=await r(n,s);if(o&&Array.isArray(o.tools)){for(let i of o.tools)if(!(!i||i.inputSchema==null))try{i.inputSchema=fm(i.inputSchema)}catch{}}return o})}catch{}}var ii=new qo({runtimes:An,projectRoot:()=>ht()}),Oc=gt(ym(),`cm-fs-preload-${process.pid}.js`);Ec(Oc,`(function(){var __cm_fs=0;process.on('exit',function(){if(__cm_fs>0)try{process.stderr.write('__CM_FS__:'+__cm_fs+'\\n')}catch(e){}});try{var f=require('fs');var ors=f.readFileSync;f.readFileSync=function(){var r=ors.apply(this,arguments);if(Buffer.isBuffer(r))__cm_fs+=r.length;else if(typeof r==='string')__cm_fs+=Buffer.byteLength(r);return r;};}catch(e){}})();
|