pi-lean-ctx 3.5.25 → 3.6.0
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 +31 -39
- package/extensions/index.ts +56 -22
- package/extensions/mcp-bridge.ts +2 -0
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -1,34 +1,36 @@
|
|
|
1
1
|
# pi-lean-ctx
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[Pi Coding Agent](https://github.com/badlogic/pi-mono) extension that provides `ctx_`-prefixed tools backed by [lean-ctx](https://leanctx.com) for **60–90% token savings**.
|
|
4
4
|
|
|
5
5
|
- **Default**: CLI-only (no MCP required)
|
|
6
6
|
- **Optional**: enable MCP tools (`LEAN_CTX_PI_ENABLE_MCP=1`) or run `lean-ctx init --agent pi --mode mcp`
|
|
7
7
|
|
|
8
8
|
## What it does
|
|
9
9
|
|
|
10
|
-
###
|
|
10
|
+
### ctx_ Tools (CLI-backed)
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Replaces Pi's built-in `read`, `bash`, `ls`, `find`, `grep` with `ctx_`-prefixed versions:
|
|
13
13
|
|
|
14
|
-
| Tool | Compression |
|
|
15
|
-
|
|
16
|
-
| `
|
|
17
|
-
| `
|
|
18
|
-
| `grep` | Results grouped and compressed via ripgrep + lean-ctx |
|
|
19
|
-
| `find` | File listings compressed and .gitignore-aware |
|
|
20
|
-
| `ls` | Directory output compressed |
|
|
14
|
+
| Tool | Replaces | Compression |
|
|
15
|
+
|------|----------|-------------|
|
|
16
|
+
| `ctx_read` | `read` | Smart mode selection (full/map/signatures) based on file type and size |
|
|
17
|
+
| `ctx_shell` | `bash` | All shell commands compressed via lean-ctx's 95+ patterns |
|
|
18
|
+
| `ctx_grep` | `grep` | Results grouped and compressed via ripgrep + lean-ctx |
|
|
19
|
+
| `ctx_find` | `find` | File listings compressed and .gitignore-aware |
|
|
20
|
+
| `ctx_ls` | `ls` | Directory output compressed |
|
|
21
|
+
|
|
22
|
+
Pi's `edit` and `write` builtins remain unchanged.
|
|
21
23
|
|
|
22
24
|
### Direct lean-ctx CLI tool
|
|
23
25
|
|
|
24
|
-
The
|
|
26
|
+
The `lean_ctx` tool runs `lean-ctx` directly (no nested compression).
|
|
25
27
|
Use it for commands like:
|
|
26
28
|
|
|
27
|
-
- `
|
|
28
|
-
- `
|
|
29
|
-
- `
|
|
30
|
-
- `
|
|
31
|
-
- `
|
|
29
|
+
- `lean_ctx overview`
|
|
30
|
+
- `lean_ctx session …`
|
|
31
|
+
- `lean_ctx knowledge …`
|
|
32
|
+
- `lean_ctx gain` / `lean_ctx stats`
|
|
33
|
+
- `lean_ctx index …`
|
|
32
34
|
|
|
33
35
|
### Optional MCP Tools (Embedded Bridge)
|
|
34
36
|
|
|
@@ -43,24 +45,11 @@ server and registers advanced tools directly in Pi:
|
|
|
43
45
|
| `ctx_overview` | Codebase overview and architecture analysis |
|
|
44
46
|
| `ctx_compress` | Manual compression control |
|
|
45
47
|
| `ctx_metrics` | Token savings dashboard |
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
| `ctx_delta` | Changed-lines-only reads |
|
|
52
|
-
| `ctx_edit` | Read-modify-write in one call |
|
|
53
|
-
| `ctx_dedup` | Duplicate context elimination |
|
|
54
|
-
| `ctx_fill` | Template completion |
|
|
55
|
-
| `ctx_intent` | Intent-based task routing |
|
|
56
|
-
| `ctx_response` | Response optimization |
|
|
57
|
-
| `ctx_wrapped` | Wrapped command execution |
|
|
58
|
-
| `ctx_benchmark` | Compression benchmarking |
|
|
59
|
-
| `ctx_analyze` | Code analysis |
|
|
60
|
-
| `ctx_cache` | Cache management |
|
|
61
|
-
| `ctx_execute` | Direct command execution |
|
|
62
|
-
|
|
63
|
-
If you don’t want MCP: keep it disabled and use the CLI overrides + `lean_ctx` tool only.
|
|
48
|
+
| `ctx_multi_read` | Batch file reads |
|
|
49
|
+
| `ctx_search` | MCP-native search |
|
|
50
|
+
| `ctx_tree` | File tree listing |
|
|
51
|
+
|
|
52
|
+
If you don't want MCP: keep it disabled and use the `ctx_` CLI tools + `lean_ctx` tool only.
|
|
64
53
|
|
|
65
54
|
## Install
|
|
66
55
|
|
|
@@ -83,14 +72,17 @@ lean-ctx init --agent pi
|
|
|
83
72
|
|
|
84
73
|
## How it works
|
|
85
74
|
|
|
86
|
-
###
|
|
75
|
+
### ctx_ tools (CLI-backed)
|
|
87
76
|
|
|
88
|
-
These tools invoke the `lean-ctx` binary via CLI with `LEAN_CTX_COMPRESS=1`.
|
|
77
|
+
These tools invoke the `lean-ctx` binary via CLI with `LEAN_CTX_COMPRESS=1`.
|
|
78
|
+
The built-in tools they replace (`read`, `bash`, `ls`, `find`, `grep`) are disabled
|
|
79
|
+
via `pi.setActiveTools()` so only the `ctx_` versions are available to the LLM.
|
|
89
80
|
|
|
90
81
|
### Optional MCP bridge (all other tools)
|
|
91
82
|
|
|
92
83
|
If you enable the MCP bridge, pi-lean-ctx spawns the `lean-ctx` binary as an MCP server (JSON-RPC over stdio).
|
|
93
|
-
It discovers available tools via `list_tools`, filters out those already covered by CLI
|
|
84
|
+
It discovers available tools via `list_tools`, filters out those already covered by `ctx_` CLI tools,
|
|
85
|
+
and registers the rest as native Pi tools.
|
|
94
86
|
|
|
95
87
|
If `lean-ctx` is already configured as an MCP server via [pi-mcp-adapter](https://github.com/nicobailon/pi-mcp-adapter) in `~/.pi/agent/mcp.json`, the embedded bridge is skipped to avoid duplicate tools.
|
|
96
88
|
|
|
@@ -150,7 +142,7 @@ The extension locates the `lean-ctx` binary in this order:
|
|
|
150
142
|
|
|
151
143
|
## Smart Read Modes
|
|
152
144
|
|
|
153
|
-
The `
|
|
145
|
+
The `ctx_read` tool automatically selects the optimal lean-ctx mode:
|
|
154
146
|
|
|
155
147
|
| File Type | Size | Mode |
|
|
156
148
|
|-----------|------|------|
|
|
@@ -166,7 +158,7 @@ The `read` tool automatically selects the optimal lean-ctx mode:
|
|
|
166
158
|
Use `/lean-ctx` in Pi to check:
|
|
167
159
|
- Which binary is being used
|
|
168
160
|
- MCP bridge status (disabled / embedded / adapter)
|
|
169
|
-
-
|
|
161
|
+
- Active `ctx_` tool names
|
|
170
162
|
|
|
171
163
|
## Disabling specific tools
|
|
172
164
|
|
package/extensions/index.ts
CHANGED
|
@@ -2,7 +2,6 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
|
2
2
|
import {
|
|
3
3
|
createBashToolDefinition,
|
|
4
4
|
createReadToolDefinition,
|
|
5
|
-
DEFAULT_MAX_BYTES,
|
|
6
5
|
DEFAULT_MAX_LINES,
|
|
7
6
|
getLanguageFromPath,
|
|
8
7
|
highlightCode,
|
|
@@ -36,10 +35,22 @@ const IMAGE_EXTENSIONS = new Set([".png", ".jpg", ".jpeg", ".gif", ".webp"]);
|
|
|
36
35
|
const CODE_FULL_READ_MAX_BYTES = 8 * 1024;
|
|
37
36
|
const CODE_SIGNATURES_MIN_BYTES = 96 * 1024;
|
|
38
37
|
|
|
38
|
+
// Pi builtins we replace with ctx_ prefixed versions
|
|
39
|
+
const DISABLED_BUILTIN_TOOLS = new Set(["read", "bash", "ls", "find", "grep"]);
|
|
40
|
+
// Max bytes constant for truncation warnings (same as Pi's DEFAULT_MAX_BYTES)
|
|
41
|
+
const DEFAULT_MAX_BYTES = 8192;
|
|
42
|
+
|
|
43
|
+
const readModeSchema = Type.Union([
|
|
44
|
+
Type.Literal("full"),
|
|
45
|
+
Type.Literal("map"),
|
|
46
|
+
Type.Literal("signatures"),
|
|
47
|
+
], { description: "Override auto-selection: full (complete content), map (deps+API signatures), signatures (AST only)" });
|
|
48
|
+
|
|
39
49
|
const readSchema = Type.Object({
|
|
40
50
|
path: Type.String({ description: "Path to the file to read (relative or absolute)" }),
|
|
41
51
|
offset: Type.Optional(Type.Number({ description: "Line number to start reading from (1-indexed)" })),
|
|
42
52
|
limit: Type.Optional(Type.Number({ description: "Maximum number of lines to read" })),
|
|
53
|
+
mode: Type.Optional(readModeSchema),
|
|
43
54
|
});
|
|
44
55
|
|
|
45
56
|
const lsSchema = Type.Object({
|
|
@@ -276,6 +287,13 @@ async function execLeanCtx(pi: ExtensionAPI, args: string[]) {
|
|
|
276
287
|
}
|
|
277
288
|
|
|
278
289
|
export default async function (pi: ExtensionAPI) {
|
|
290
|
+
// Defer setActiveTools to session_start — runtime actions aren't available during extension load
|
|
291
|
+
// Must run on every session_start since active tools are per-session
|
|
292
|
+
pi.on("session_start", () => {
|
|
293
|
+
const activeTools = pi.getActiveTools().filter((name) => !DISABLED_BUILTIN_TOOLS.has(name));
|
|
294
|
+
pi.setActiveTools(activeTools);
|
|
295
|
+
});
|
|
296
|
+
|
|
279
297
|
const baseBashTool = createBashToolDefinition(process.cwd(), {
|
|
280
298
|
spawnHook: ({ command, cwd, env }) => {
|
|
281
299
|
const bin = resolveBinary();
|
|
@@ -295,19 +313,21 @@ export default async function (pi: ExtensionAPI) {
|
|
|
295
313
|
raw: Type.Optional(Type.Boolean({ description: "Skip compression, return full uncompressed output" })),
|
|
296
314
|
});
|
|
297
315
|
|
|
316
|
+
// ── ctx_shell (replaces bash) ─────────────────────────────────────────
|
|
298
317
|
pi.registerTool({
|
|
299
|
-
|
|
300
|
-
|
|
318
|
+
name: "ctx_shell",
|
|
319
|
+
label: "ctx_shell",
|
|
301
320
|
description:
|
|
302
|
-
"Execute a
|
|
303
|
-
+ "IMPORTANT: Do NOT use
|
|
304
|
-
+ "Do NOT use
|
|
321
|
+
"Execute a shell command. Output is auto-compressed by lean-ctx. "
|
|
322
|
+
+ "IMPORTANT: Do NOT use ctx_shell to read files (cat/head/tail) — use ctx_read instead. "
|
|
323
|
+
+ "Do NOT use ctx_shell for grep/find/ls — use ctx_grep, ctx_find, ctx_ls. "
|
|
305
324
|
+ "Set raw=true to skip compression when exact output matters. "
|
|
306
325
|
+ "Use timeout (seconds) to prevent hanging commands.",
|
|
307
|
-
promptSnippet: "Run shell commands (not for file reading — use
|
|
326
|
+
promptSnippet: "Run shell commands (not for file reading — use ctx_read)",
|
|
308
327
|
promptGuidelines: [
|
|
309
|
-
"Use
|
|
328
|
+
"Use ctx_shell only for commands with side effects: build, test, install, git, run scripts.",
|
|
310
329
|
],
|
|
330
|
+
parameters: bashSchemaWithRaw,
|
|
311
331
|
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
312
332
|
const isRaw = !!params.raw;
|
|
313
333
|
const toolParams = { command: params.command, timeout: params.timeout };
|
|
@@ -335,19 +355,22 @@ export default async function (pi: ExtensionAPI) {
|
|
|
335
355
|
},
|
|
336
356
|
});
|
|
337
357
|
|
|
358
|
+
// ── ctx_read (replaces read) ──────────────────────────────────────────
|
|
338
359
|
const nativeReadTool = createReadToolDefinition(process.cwd());
|
|
339
360
|
|
|
340
361
|
pi.registerTool({
|
|
341
|
-
name: "
|
|
342
|
-
label: "
|
|
362
|
+
name: "ctx_read",
|
|
363
|
+
label: "ctx_read",
|
|
343
364
|
description:
|
|
344
|
-
"Read file contents. ALWAYS use
|
|
365
|
+
"Read file contents. ALWAYS use ctx_read instead of cat/head/tail via ctx_shell. "
|
|
345
366
|
+ "Auto-selects mode: configs (.yaml/.json/.toml/.env) are always full-read. "
|
|
346
367
|
+ "Code files: full (<8KB), map (8-96KB), signatures (>96KB). "
|
|
368
|
+
+ "Add mode=full to get complete file content (bypasses cache). "
|
|
347
369
|
+ "Use offset and limit to read specific line ranges.",
|
|
348
370
|
promptSnippet: "Read file contents (always use instead of cat)",
|
|
349
371
|
promptGuidelines: [
|
|
350
|
-
"Use
|
|
372
|
+
"Use ctx_read to inspect file contents instead of cat or less.",
|
|
373
|
+
"Use mode=full if you need the complete file content.",
|
|
351
374
|
],
|
|
352
375
|
parameters: readSchema,
|
|
353
376
|
renderCall(args, theme, context) {
|
|
@@ -386,11 +409,11 @@ export default async function (pi: ExtensionAPI) {
|
|
|
386
409
|
| undefined;
|
|
387
410
|
if (truncation?.truncated) {
|
|
388
411
|
if (truncation.firstLineExceedsLimit) {
|
|
389
|
-
text += `\n${theme.fg("warning", `[First line exceeds ${Math.round((truncation.maxBytes ??
|
|
412
|
+
text += `\n${theme.fg("warning", `[First line exceeds ${Math.round((truncation.maxBytes ?? 8192) / 1024)}KB limit]`)}`;
|
|
390
413
|
} else if (truncation.truncatedBy === "lines") {
|
|
391
414
|
text += `\n${theme.fg("warning", `[Truncated: ${truncation.outputLines} of ${truncation.totalLines} lines]`)}`;
|
|
392
415
|
} else {
|
|
393
|
-
text += `\n${theme.fg("warning", `[Truncated: ${truncation.outputLines} lines (${Math.round((truncation.maxBytes ??
|
|
416
|
+
text += `\n${theme.fg("warning", `[Truncated: ${truncation.outputLines} lines (${Math.round((truncation.maxBytes ?? 8192) / 1024)}KB limit)]`)}`;
|
|
394
417
|
}
|
|
395
418
|
}
|
|
396
419
|
|
|
@@ -431,8 +454,9 @@ export default async function (pi: ExtensionAPI) {
|
|
|
431
454
|
return nativeReadTool.execute(_toolCallId, { ...params, path: absolutePath }, signal, onUpdate, ctx);
|
|
432
455
|
}
|
|
433
456
|
|
|
434
|
-
const
|
|
435
|
-
const
|
|
457
|
+
const isExplicitFull = params.mode === "full";
|
|
458
|
+
const mode = params.mode ?? await chooseReadMode(absolutePath);
|
|
459
|
+
const args = ["read", absolutePath, "-m", mode, ...(isExplicitFull ? ["--fresh"] : [])];
|
|
436
460
|
const output = await execLeanCtx(pi, args);
|
|
437
461
|
const originalText = await readFile(absolutePath, "utf8");
|
|
438
462
|
const decorated = withFooter(output, { originalText, always: true, preferEstimate: true });
|
|
@@ -444,9 +468,10 @@ export default async function (pi: ExtensionAPI) {
|
|
|
444
468
|
},
|
|
445
469
|
});
|
|
446
470
|
|
|
471
|
+
// ── ctx_ls (replaces ls) ──────────────────────────────────────────────
|
|
447
472
|
pi.registerTool({
|
|
448
|
-
name: "
|
|
449
|
-
label: "
|
|
473
|
+
name: "ctx_ls",
|
|
474
|
+
label: "ctx_ls",
|
|
450
475
|
description: "List directory contents. Use limit to reduce output size.",
|
|
451
476
|
promptSnippet: "List directory contents",
|
|
452
477
|
parameters: lsSchema,
|
|
@@ -462,9 +487,10 @@ export default async function (pi: ExtensionAPI) {
|
|
|
462
487
|
},
|
|
463
488
|
});
|
|
464
489
|
|
|
490
|
+
// ── ctx_find (replaces find) ──────────────────────────────────────────
|
|
465
491
|
pi.registerTool({
|
|
466
|
-
name: "
|
|
467
|
-
label: "
|
|
492
|
+
name: "ctx_find",
|
|
493
|
+
label: "ctx_find",
|
|
468
494
|
description: "Find files by glob pattern (respects .gitignore). Use limit to reduce output size.",
|
|
469
495
|
promptSnippet: "Find files by glob pattern",
|
|
470
496
|
parameters: findSchema,
|
|
@@ -480,9 +506,10 @@ export default async function (pi: ExtensionAPI) {
|
|
|
480
506
|
},
|
|
481
507
|
});
|
|
482
508
|
|
|
509
|
+
// ── ctx_grep (replaces grep) ──────────────────────────────────────────
|
|
483
510
|
pi.registerTool({
|
|
484
|
-
name: "
|
|
485
|
-
label: "
|
|
511
|
+
name: "ctx_grep",
|
|
512
|
+
label: "ctx_grep",
|
|
486
513
|
description: "Search file contents with ripgrep. Use limit to cap matches and context for surrounding lines.",
|
|
487
514
|
promptSnippet: "Search file contents for patterns",
|
|
488
515
|
parameters: grepSchema,
|
|
@@ -512,6 +539,7 @@ export default async function (pi: ExtensionAPI) {
|
|
|
512
539
|
},
|
|
513
540
|
});
|
|
514
541
|
|
|
542
|
+
// ── lean_ctx (CLI passthrough) ────────────────────────────────────────
|
|
515
543
|
pi.registerTool({
|
|
516
544
|
name: "lean_ctx",
|
|
517
545
|
label: "lean_ctx",
|
|
@@ -575,6 +603,12 @@ export default async function (pi: ExtensionAPI) {
|
|
|
575
603
|
}
|
|
576
604
|
}
|
|
577
605
|
|
|
606
|
+
// Show active ctx_ tools
|
|
607
|
+
const ctxTools = pi.getActiveTools().filter((n) => n.startsWith("ctx_") || n === "lean_ctx");
|
|
608
|
+
if (ctxTools.length > 0) {
|
|
609
|
+
lines.push(`Active tools: ${ctxTools.join(", ")}`);
|
|
610
|
+
}
|
|
611
|
+
|
|
578
612
|
const ok = found && (adapterConfigured || !enableMcpBridge || (status?.connected ?? false));
|
|
579
613
|
ctx.ui.notify(lines.join("\n"), ok ? "info" : "warning");
|
|
580
614
|
},
|
package/extensions/mcp-bridge.ts
CHANGED
|
@@ -12,6 +12,8 @@ const CLI_OVERRIDE_TOOLS = new Set([
|
|
|
12
12
|
"ctx_tree",
|
|
13
13
|
]);
|
|
14
14
|
|
|
15
|
+
// No additional prefix filter — CLI_OVERRIDE_TOOLS covers exactly the tools we overwrite
|
|
16
|
+
|
|
15
17
|
const MAX_RECONNECT_ATTEMPTS = 3;
|
|
16
18
|
const RECONNECT_DELAY_MS = 2000;
|
|
17
19
|
const TOOL_CALL_TIMEOUT_MS = 120000;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-lean-ctx",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "Pi Coding Agent extension (CLI-first)
|
|
3
|
+
"version": "3.6.0",
|
|
4
|
+
"description": "Pi Coding Agent extension (CLI-first) — routes bash/read/grep/find/ls through lean-ctx CLI for strong token savings. Optional MCP bridge can register advanced tools.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
7
7
|
"lean-ctx",
|
|
@@ -36,5 +36,8 @@
|
|
|
36
36
|
"files": [
|
|
37
37
|
"extensions/",
|
|
38
38
|
"README.md"
|
|
39
|
-
]
|
|
39
|
+
],
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"typescript": "^6.0.3"
|
|
42
|
+
}
|
|
40
43
|
}
|