pi-lean-ctx 3.5.25 → 3.6.1

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 CHANGED
@@ -1,34 +1,47 @@
1
1
  # pi-lean-ctx
2
2
 
3
- CLI-first [Pi Coding Agent](https://github.com/badlogic/pi-mono) extension that routes Pi’s built-in tools through [lean-ctx](https://leanctx.com) for **60–90% token savings**.
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
- - **Default**: CLI-only (no MCP required)
5
+ - **Default**: CLI-only, additive mode (no MCP required, Pi builtins preserved)
6
6
  - **Optional**: enable MCP tools (`LEAN_CTX_PI_ENABLE_MCP=1`) or run `lean-ctx init --agent pi --mode mcp`
7
+ - **Optional**: replace mode (`LEAN_CTX_PI_MODE=replace`) disables Pi builtins
8
+
9
+ ## Tool Mode
10
+
11
+ By default, pi-lean-ctx runs in **additive mode**: Pi's built-in tools (`read`, `bash`, `ls`, `find`, `grep`) remain available alongside the `ctx_*` tools. Agents can use either set.
12
+
13
+ To switch to **replace mode** (disables Pi builtins, only `ctx_*` tools available):
14
+
15
+ ```bash
16
+ export LEAN_CTX_PI_MODE=replace
17
+ ```
7
18
 
8
19
  ## What it does
9
20
 
10
- ### Built-in Tool Overrides (CLI)
21
+ ### ctx_ Tools (CLI-backed)
11
22
 
12
- Overrides Pi's built-in tools to route them through `lean-ctx`:
23
+ Adds `ctx_`-prefixed tools alongside Pi's builtins (or replaces them in `replace` mode):
13
24
 
14
- | Tool | Compression |
15
- |------|------------|
16
- | `bash` | All shell commands compressed via lean-ctx's 95+ patterns |
17
- | `read` | Smart mode selection (full/map/signatures) based on file type and size |
18
- | `grep` | Results grouped and compressed via ripgrep + lean-ctx |
19
- | `find` | File listings compressed and .gitignore-aware |
20
- | `ls` | Directory output compressed |
25
+ | Tool | Replaces | Compression |
26
+ |------|----------|-------------|
27
+ | `ctx_read` | `read` | Smart mode selection (full/map/signatures) based on file type and size |
28
+ | `ctx_shell` | `bash` | All shell commands compressed via lean-ctx's 95+ patterns |
29
+ | `ctx_grep` | `grep` | Results grouped and compressed via ripgrep + lean-ctx |
30
+ | `ctx_find` | `find` | File listings compressed and .gitignore-aware |
31
+ | `ctx_ls` | `ls` | Directory output compressed |
32
+
33
+ Pi's `edit` and `write` builtins remain unchanged.
21
34
 
22
35
  ### Direct lean-ctx CLI tool
23
36
 
24
- The extension registers a `lean_ctx` tool that runs `lean-ctx` directly (no nested compression).
37
+ The `lean_ctx` tool runs `lean-ctx` directly (no nested compression).
25
38
  Use it for commands like:
26
39
 
27
- - `lean-ctx overview`
28
- - `lean-ctx session …`
29
- - `lean-ctx knowledge …`
30
- - `lean-ctx gain` / `lean-ctx stats`
31
- - `lean-ctx index …`
40
+ - `lean_ctx overview`
41
+ - `lean_ctx session …`
42
+ - `lean_ctx knowledge …`
43
+ - `lean_ctx gain` / `lean_ctx stats`
44
+ - `lean_ctx index …`
32
45
 
33
46
  ### Optional MCP Tools (Embedded Bridge)
34
47
 
@@ -43,24 +56,11 @@ server and registers advanced tools directly in Pi:
43
56
  | `ctx_overview` | Codebase overview and architecture analysis |
44
57
  | `ctx_compress` | Manual compression control |
45
58
  | `ctx_metrics` | Token savings dashboard |
46
- | `ctx_agent` | Multi-agent coordination and handoffs |
47
- | `ctx_graph` | Dependency graph analysis |
48
- | `ctx_discover` | Smart code discovery |
49
- | `ctx_context` | Context window management |
50
- | `ctx_preload` | Predictive file preloading |
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.
59
+ | `ctx_multi_read` | Batch file reads |
60
+ | `ctx_search` | MCP-native search |
61
+ | `ctx_tree` | File tree listing |
62
+
63
+ If you don't want MCP: keep it disabled and use the `ctx_` CLI tools + `lean_ctx` tool only.
64
64
 
65
65
  ## Install
66
66
 
@@ -83,14 +83,17 @@ lean-ctx init --agent pi
83
83
 
84
84
  ## How it works
85
85
 
86
- ### CLI overrides (bash, read, grep, find, ls)
86
+ ### ctx_ tools (CLI-backed)
87
87
 
88
- These tools invoke the `lean-ctx` binary via CLI with `LEAN_CTX_COMPRESS=1`. The output is parsed for compression stats and displayed with a token savings footer.
88
+ These tools invoke the `lean-ctx` binary via CLI with `LEAN_CTX_COMPRESS=1`.
89
+ The built-in tools they replace (`read`, `bash`, `ls`, `find`, `grep`) are disabled
90
+ via `pi.setActiveTools()` so only the `ctx_` versions are available to the LLM.
89
91
 
90
92
  ### Optional MCP bridge (all other tools)
91
93
 
92
94
  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 overrides, and registers the rest as native Pi tools.
95
+ It discovers available tools via `list_tools`, filters out those already covered by `ctx_` CLI tools,
96
+ and registers the rest as native Pi tools.
94
97
 
95
98
  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
99
 
@@ -150,7 +153,7 @@ The extension locates the `lean-ctx` binary in this order:
150
153
 
151
154
  ## Smart Read Modes
152
155
 
153
- The `read` tool automatically selects the optimal lean-ctx mode:
156
+ The `ctx_read` tool automatically selects the optimal lean-ctx mode:
154
157
 
155
158
  | File Type | Size | Mode |
156
159
  |-----------|------|------|
@@ -166,7 +169,7 @@ The `read` tool automatically selects the optimal lean-ctx mode:
166
169
  Use `/lean-ctx` in Pi to check:
167
170
  - Which binary is being used
168
171
  - MCP bridge status (disabled / embedded / adapter)
169
- - Number and names of registered MCP tools
172
+ - Active `ctx_` tool names
170
173
 
171
174
  ## Disabling specific tools
172
175
 
@@ -1,14 +1,13 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/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,
9
8
  truncateHead,
10
- } from "@mariozechner/pi-coding-agent";
11
- import { Text } from "@mariozechner/pi-tui";
9
+ } from "@earendil-works/pi-coding-agent";
10
+ import { Text } from "@earendil-works/pi-tui";
12
11
  import { Type } from "@sinclair/typebox";
13
12
  import { existsSync, readFileSync } from "node:fs";
14
13
  import { readFile, stat } from "node:fs/promises";
@@ -36,10 +35,26 @@ 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 that can be replaced with ctx_ prefixed versions.
39
+ // LEAN_CTX_PI_MODE controls behavior:
40
+ // "additive" (default) — keep Pi builtins, add ctx_* alongside
41
+ // "replace" — disable Pi builtins, only expose ctx_*
42
+ const DISABLED_BUILTIN_TOOLS = new Set(["read", "bash", "ls", "find", "grep"]);
43
+ const PI_MODE = (process.env.LEAN_CTX_PI_MODE || "additive").toLowerCase();
44
+ // Max bytes constant for truncation warnings (same as Pi's DEFAULT_MAX_BYTES)
45
+ const DEFAULT_MAX_BYTES = 8192;
46
+
47
+ const readModeSchema = Type.Union([
48
+ Type.Literal("full"),
49
+ Type.Literal("map"),
50
+ Type.Literal("signatures"),
51
+ ], { description: "Override auto-selection: full (complete content), map (deps+API signatures), signatures (AST only)" });
52
+
39
53
  const readSchema = Type.Object({
40
54
  path: Type.String({ description: "Path to the file to read (relative or absolute)" }),
41
55
  offset: Type.Optional(Type.Number({ description: "Line number to start reading from (1-indexed)" })),
42
56
  limit: Type.Optional(Type.Number({ description: "Maximum number of lines to read" })),
57
+ mode: Type.Optional(readModeSchema),
43
58
  });
44
59
 
45
60
  const lsSchema = Type.Object({
@@ -267,7 +282,7 @@ function isMcpAdapterConfigured(): boolean {
267
282
 
268
283
  async function execLeanCtx(pi: ExtensionAPI, args: string[]) {
269
284
  const bin = resolveBinary();
270
- const result = await pi.exec(bin, args, { env: { ...process.env, LEAN_CTX_COMPRESS: "1" } });
285
+ const result = await pi.exec(bin, args, { env: { ...process.env, LEAN_CTX_COMPRESS: "1", LEAN_CTX_SAVINGS_FOOTER: "always" } });
271
286
  if (result.code !== 0) {
272
287
  const msg = (result.stderr || result.stdout || `lean-ctx failed: ${args.join(" ")}`).trim();
273
288
  throw new Error(msg);
@@ -276,13 +291,23 @@ async function execLeanCtx(pi: ExtensionAPI, args: string[]) {
276
291
  }
277
292
 
278
293
  export default async function (pi: ExtensionAPI) {
294
+ // Defer setActiveTools to session_start — runtime actions aren't available during extension load
295
+ // In "replace" mode, disable Pi builtins and only expose ctx_* tools.
296
+ // In "additive" mode (default), keep Pi builtins alongside ctx_* tools.
297
+ if (PI_MODE === "replace") {
298
+ pi.on("session_start", () => {
299
+ const activeTools = pi.getActiveTools().filter((name) => !DISABLED_BUILTIN_TOOLS.has(name));
300
+ pi.setActiveTools(activeTools);
301
+ });
302
+ }
303
+
279
304
  const baseBashTool = createBashToolDefinition(process.cwd(), {
280
305
  spawnHook: ({ command, cwd, env }) => {
281
306
  const bin = resolveBinary();
282
307
  return {
283
- command: `${shellQuote(bin)} -c sh -lc ${shellQuote(command)}`,
308
+ command: `${shellQuote(bin)} -c ${shellQuote(command)}`,
284
309
  cwd,
285
- env: { ...env, LEAN_CTX_COMPRESS: "1" },
310
+ env: { ...env, LEAN_CTX_COMPRESS: "1", LEAN_CTX_SAVINGS_FOOTER: "always" },
286
311
  };
287
312
  },
288
313
  });
@@ -295,19 +320,31 @@ export default async function (pi: ExtensionAPI) {
295
320
  raw: Type.Optional(Type.Boolean({ description: "Skip compression, return full uncompressed output" })),
296
321
  });
297
322
 
323
+ // ── ctx_shell (replaces bash) ─────────────────────────────────────────
298
324
  pi.registerTool({
299
- ...baseBashTool,
300
- parameters: bashSchemaWithRaw,
325
+ name: "ctx_shell",
326
+ label: "ctx_shell",
301
327
  description:
302
- "Execute a bash command. Output is auto-compressed by lean-ctx. "
303
- + "IMPORTANT: Do NOT use bash to read files (cat/head/tail) — use the read tool instead. "
304
- + "Do NOT use bash for grep/find/ls — use the dedicated tools. "
328
+ "Execute a shell command. Output is auto-compressed by lean-ctx. "
329
+ + "IMPORTANT: Do NOT use ctx_shell to read files (cat/head/tail) — use ctx_read instead. "
330
+ + "Do NOT use ctx_shell for grep/find/ls — use ctx_grep, ctx_find, ctx_ls. "
305
331
  + "Set raw=true to skip compression when exact output matters. "
306
332
  + "Use timeout (seconds) to prevent hanging commands.",
307
- promptSnippet: "Run shell commands (not for file reading — use read tool)",
333
+ promptSnippet: "Run shell commands (not for file reading — use ctx_read)",
308
334
  promptGuidelines: [
309
- "Use bash only for commands with side effects: build, test, install, git, run scripts.",
335
+ "Use ctx_shell only for commands with side effects: build, test, install, git, run scripts.",
310
336
  ],
337
+ parameters: bashSchemaWithRaw,
338
+ renderCall(args, theme, context) {
339
+ return baseBashTool.renderCall
340
+ ? baseBashTool.renderCall(args, theme, context)
341
+ : (context.lastComponent ?? new Text("", 0, 0));
342
+ },
343
+ renderResult(result, options, theme, context) {
344
+ return baseBashTool.renderResult
345
+ ? baseBashTool.renderResult(result, options, theme, context)
346
+ : (context.lastComponent ?? new Text("", 0, 0));
347
+ },
311
348
  async execute(toolCallId, params, signal, onUpdate, ctx) {
312
349
  const isRaw = !!params.raw;
313
350
  const toolParams = { command: params.command, timeout: params.timeout };
@@ -335,19 +372,22 @@ export default async function (pi: ExtensionAPI) {
335
372
  },
336
373
  });
337
374
 
375
+ // ── ctx_read (replaces read) ──────────────────────────────────────────
338
376
  const nativeReadTool = createReadToolDefinition(process.cwd());
339
377
 
340
378
  pi.registerTool({
341
- name: "read",
342
- label: "Read",
379
+ name: "ctx_read",
380
+ label: "ctx_read",
343
381
  description:
344
- "Read file contents. ALWAYS use this instead of cat/head/tail via bash. "
382
+ "Read file contents. ALWAYS use ctx_read instead of cat/head/tail via ctx_shell. "
345
383
  + "Auto-selects mode: configs (.yaml/.json/.toml/.env) are always full-read. "
346
384
  + "Code files: full (<8KB), map (8-96KB), signatures (>96KB). "
385
+ + "Add mode=full to get complete file content (bypasses cache). "
347
386
  + "Use offset and limit to read specific line ranges.",
348
387
  promptSnippet: "Read file contents (always use instead of cat)",
349
388
  promptGuidelines: [
350
- "Use read to inspect file contents instead of cat or less.",
389
+ "Use ctx_read to inspect file contents instead of cat or less.",
390
+ "Use mode=full if you need the complete file content.",
351
391
  ],
352
392
  parameters: readSchema,
353
393
  renderCall(args, theme, context) {
@@ -386,11 +426,11 @@ export default async function (pi: ExtensionAPI) {
386
426
  | undefined;
387
427
  if (truncation?.truncated) {
388
428
  if (truncation.firstLineExceedsLimit) {
389
- text += `\n${theme.fg("warning", `[First line exceeds ${Math.round((truncation.maxBytes ?? DEFAULT_MAX_BYTES) / 1024)}KB limit]`)}`;
429
+ text += `\n${theme.fg("warning", `[First line exceeds ${Math.round((truncation.maxBytes ?? 8192) / 1024)}KB limit]`)}`;
390
430
  } else if (truncation.truncatedBy === "lines") {
391
431
  text += `\n${theme.fg("warning", `[Truncated: ${truncation.outputLines} of ${truncation.totalLines} lines]`)}`;
392
432
  } else {
393
- text += `\n${theme.fg("warning", `[Truncated: ${truncation.outputLines} lines (${Math.round((truncation.maxBytes ?? DEFAULT_MAX_BYTES) / 1024)}KB limit)]`)}`;
433
+ text += `\n${theme.fg("warning", `[Truncated: ${truncation.outputLines} lines (${Math.round((truncation.maxBytes ?? 8192) / 1024)}KB limit)]`)}`;
394
434
  }
395
435
  }
396
436
 
@@ -431,8 +471,9 @@ export default async function (pi: ExtensionAPI) {
431
471
  return nativeReadTool.execute(_toolCallId, { ...params, path: absolutePath }, signal, onUpdate, ctx);
432
472
  }
433
473
 
434
- const mode = await chooseReadMode(absolutePath);
435
- const args = mode === "full" ? ["read", absolutePath] : ["read", absolutePath, "-m", mode];
474
+ const isExplicitFull = params.mode === "full";
475
+ const mode = params.mode ?? await chooseReadMode(absolutePath);
476
+ const args = ["read", absolutePath, "-m", mode, ...(isExplicitFull ? ["--fresh"] : [])];
436
477
  const output = await execLeanCtx(pi, args);
437
478
  const originalText = await readFile(absolutePath, "utf8");
438
479
  const decorated = withFooter(output, { originalText, always: true, preferEstimate: true });
@@ -444,9 +485,10 @@ export default async function (pi: ExtensionAPI) {
444
485
  },
445
486
  });
446
487
 
488
+ // ── ctx_ls (replaces ls) ──────────────────────────────────────────────
447
489
  pi.registerTool({
448
- name: "ls",
449
- label: "ls",
490
+ name: "ctx_ls",
491
+ label: "ctx_ls",
450
492
  description: "List directory contents. Use limit to reduce output size.",
451
493
  promptSnippet: "List directory contents",
452
494
  parameters: lsSchema,
@@ -462,9 +504,10 @@ export default async function (pi: ExtensionAPI) {
462
504
  },
463
505
  });
464
506
 
507
+ // ── ctx_find (replaces find) ──────────────────────────────────────────
465
508
  pi.registerTool({
466
- name: "find",
467
- label: "find",
509
+ name: "ctx_find",
510
+ label: "ctx_find",
468
511
  description: "Find files by glob pattern (respects .gitignore). Use limit to reduce output size.",
469
512
  promptSnippet: "Find files by glob pattern",
470
513
  parameters: findSchema,
@@ -480,9 +523,10 @@ export default async function (pi: ExtensionAPI) {
480
523
  },
481
524
  });
482
525
 
526
+ // ── ctx_grep (replaces grep) ──────────────────────────────────────────
483
527
  pi.registerTool({
484
- name: "grep",
485
- label: "grep",
528
+ name: "ctx_grep",
529
+ label: "ctx_grep",
486
530
  description: "Search file contents with ripgrep. Use limit to cap matches and context for surrounding lines.",
487
531
  promptSnippet: "Search file contents for patterns",
488
532
  parameters: grepSchema,
@@ -498,7 +542,7 @@ export default async function (pi: ExtensionAPI) {
498
542
  searchArgs.push(params.pattern, absolutePath);
499
543
 
500
544
  const bin = resolveBinary();
501
- const result = await pi.exec(bin, ["-c", ...searchArgs], { env: { ...process.env, LEAN_CTX_COMPRESS: "1" } });
545
+ const result = await pi.exec(bin, ["-c", ...searchArgs], { env: { ...process.env, LEAN_CTX_COMPRESS: "1", LEAN_CTX_SAVINGS_FOOTER: "always" } });
502
546
  if (result.code >= 2) {
503
547
  const msg = (result.stderr || result.stdout || `lean-ctx grep failed: ${params.pattern}`).trim();
504
548
  throw new Error(msg);
@@ -512,6 +556,7 @@ export default async function (pi: ExtensionAPI) {
512
556
  },
513
557
  });
514
558
 
559
+ // ── lean_ctx (CLI passthrough) ────────────────────────────────────────
515
560
  pi.registerTool({
516
561
  name: "lean_ctx",
517
562
  label: "lean_ctx",
@@ -575,6 +620,12 @@ export default async function (pi: ExtensionAPI) {
575
620
  }
576
621
  }
577
622
 
623
+ // Show active ctx_ tools
624
+ const ctxTools = pi.getActiveTools().filter((n) => n.startsWith("ctx_") || n === "lean_ctx");
625
+ if (ctxTools.length > 0) {
626
+ lines.push(`Active tools: ${ctxTools.join(", ")}`);
627
+ }
628
+
578
629
  const ok = found && (adapterConfigured || !enableMcpBridge || (status?.connected ?? false));
579
630
  ctx.ui.notify(lines.join("\n"), ok ? "info" : "warning");
580
631
  },
@@ -1,6 +1,6 @@
1
1
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
2
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
3
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
4
4
  import { Type } from "@sinclair/typebox";
5
5
  import type { McpBridgeRetryState, McpBridgeStatus } from "./types.js";
6
6
 
@@ -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.5.25",
4
- "description": "Pi Coding Agent extension (CLI-first) \u2014 routes bash/read/grep/find/ls through lean-ctx CLI for strong token savings. Optional MCP bridge can register advanced tools.",
3
+ "version": "3.6.1",
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",
@@ -25,8 +25,8 @@
25
25
  "@modelcontextprotocol/sdk": "^1.29.0"
26
26
  },
27
27
  "peerDependencies": {
28
- "@mariozechner/pi-coding-agent": ">=0.50.0",
29
- "@mariozechner/pi-tui": "*"
28
+ "@earendil-works/pi-coding-agent": ">=0.50.0",
29
+ "@earendil-works/pi-tui": "*"
30
30
  },
31
31
  "pi": {
32
32
  "extensions": [
@@ -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
  }