big-gulp-reads 0.0.0-stage → 1.0.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/CHANGELOG.md +9 -0
- package/LICENSE +9 -0
- package/README.md +26 -2
- package/extensions/big-read.ts +203 -0
- package/package.json +17 -5
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [1.0.1] - 2026-11-14
|
|
4
|
+
|
|
5
|
+
- Fixed the oversized-first-line notice to report the line's actual size instead of repeating the limit.
|
|
6
|
+
|
|
7
|
+
## [1.0.0] - 2026-11-14
|
|
8
|
+
|
|
9
|
+
- Initial release: read tool override with configurable KB/line limits (default 200KB / 5000 lines) and /big-gulp-reads command.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Taylor Satula
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
-
#
|
|
1
|
+
# big-gulp-reads
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A [pi](https://pi.dev) package that replaces the built-in `read` tool with one whose truncation limit you control. The built-in caps file reads at **50KB / 2000 lines** with constants no setting can change; big-gulp-reads raises that to **200KB / 5000 lines** by default and lets you change it at any time without restarting.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
pi install npm:big-gulp-reads
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
New sessions pick it up automatically — the extension registers a tool named `read` that replaces the built-in. The `offset`/`limit` parameters, continuation notices, and image attachments all work exactly as before.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
| Command | Effect |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `/big-gulp-reads` | Show current limits |
|
|
18
|
+
| `/big-gulp-reads 400` | Set the byte cap to 400KB |
|
|
19
|
+
| `/big-gulp-reads lines 10000` | Set the line cap to 10000 |
|
|
20
|
+
| `/big-gulp-reads reset` | Back to defaults (200KB / 5000 lines) |
|
|
21
|
+
|
|
22
|
+
Limits persist across sessions in `~/.pi/agent/big-gulp-reads.json` and apply to the current session immediately.
|
|
23
|
+
|
|
24
|
+
## Caveats
|
|
25
|
+
|
|
26
|
+
- **Watch your context window.** A full 131k-token context is only ~650KB of prose, so a couple of unbounded reads can consume it — pick a limit that fits your model.
|
|
27
|
+
- **Images are not auto-resized.** pi does not export its image-resize helper, so image reads are sent as attachments at full size; providers with image dimension caps may reject very large images.
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* big-gulp-reads — override pi's built-in `read` tool with a configurable truncation limit.
|
|
3
|
+
*
|
|
4
|
+
* The built-in caps output at 50KB / 2000 lines via constants that no option can
|
|
5
|
+
* change (ReadToolOptions has no truncation knobs), so this tool re-implements
|
|
6
|
+
* the text path with pi's own truncateHead() but reads the limits from
|
|
7
|
+
* ~/.pi/agent/big-gulp-reads.json, editable at runtime via /big-gulp-reads.
|
|
8
|
+
*
|
|
9
|
+
* Images are returned as attachments like the built-in does, but without
|
|
10
|
+
* auto-resizing — pi does not export processImage, so oversized images may be
|
|
11
|
+
* rejected by providers that cap image dimensions.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
15
|
+
import { formatSize, getAgentDir, truncateHead, withFileMutationQueue } from "@earendil-works/pi-coding-agent";
|
|
16
|
+
import { constants } from "fs";
|
|
17
|
+
import { access, appendFile, readFile, writeFile } from "fs/promises";
|
|
18
|
+
import { extname, join, resolve } from "path";
|
|
19
|
+
import { Type } from "typebox";
|
|
20
|
+
|
|
21
|
+
const CONFIG_FILE = join(getAgentDir(), "big-gulp-reads.json");
|
|
22
|
+
|
|
23
|
+
const DEFAULTS = { maxKB: 200, maxLines: 5000 };
|
|
24
|
+
const HARD_CAPS = { maxKB: 16384, maxLines: 100_000 };
|
|
25
|
+
|
|
26
|
+
interface Limits {
|
|
27
|
+
maxKB: number;
|
|
28
|
+
maxLines: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let limits: Limits = { ...DEFAULTS };
|
|
32
|
+
|
|
33
|
+
const readSchema = Type.Object({
|
|
34
|
+
path: Type.String({ description: "Path to the file to read (relative or absolute)" }),
|
|
35
|
+
offset: Type.Optional(Type.Number({ description: "Line number to start reading from (1-indexed)" })),
|
|
36
|
+
limit: Type.Optional(Type.Number({ description: "Maximum number of lines to read" })),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
async function loadConfig() {
|
|
40
|
+
try {
|
|
41
|
+
const raw = JSON.parse(await readFile(CONFIG_FILE, "utf-8")) as Partial<Limits>;
|
|
42
|
+
if (typeof raw.maxKB === "number" && raw.maxKB >= 1) limits.maxKB = Math.min(raw.maxKB, HARD_CAPS.maxKB);
|
|
43
|
+
if (typeof raw.maxLines === "number" && raw.maxLines >= 1) limits.maxLines = Math.min(raw.maxLines, HARD_CAPS.maxLines);
|
|
44
|
+
} catch {
|
|
45
|
+
// Missing or corrupt config falls back to defaults; the /big-gulp-reads command rewrites it.
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function saveConfig() {
|
|
50
|
+
await withFileMutationQueue(CONFIG_FILE, async () => {
|
|
51
|
+
await writeFile(CONFIG_FILE, JSON.stringify(limits, null, "\t") + "\n", "utf-8");
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function logOverride(path: string) {
|
|
56
|
+
// Audit line proving which limit was live when a read ran; cheap and greppable.
|
|
57
|
+
const line = `[big-gulp-reads] ${path} (limit ${limits.maxKB}KB / ${limits.maxLines} lines)\n`;
|
|
58
|
+
try {
|
|
59
|
+
await withFileMutationQueue(CONFIG_FILE + ".log", async () => {
|
|
60
|
+
await appendFile(CONFIG_FILE + ".log", line);
|
|
61
|
+
});
|
|
62
|
+
} catch {
|
|
63
|
+
// Logging must never fail a read.
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const IMAGE_MIMES: Record<string, string> = {
|
|
68
|
+
".jpg": "image/jpeg",
|
|
69
|
+
".jpeg": "image/jpeg",
|
|
70
|
+
".png": "image/png",
|
|
71
|
+
".gif": "image/gif",
|
|
72
|
+
".webp": "image/webp",
|
|
73
|
+
".bmp": "image/bmp",
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
function describeLimits(): string {
|
|
77
|
+
return `Read the contents of a file. Supports text files and images (jpg, png, gif, webp, bmp). Images are sent as attachments without auto-resizing. For text files, output is truncated to the limits configured via /big-gulp-reads (currently ${limits.maxKB}KB / ${limits.maxLines} lines). Use offset/limit for large files. When you need the full file, continue with offset until complete.`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export default function (pi: ExtensionAPI) {
|
|
81
|
+
void loadConfig();
|
|
82
|
+
|
|
83
|
+
pi.registerTool({
|
|
84
|
+
name: "read", // Same name as the built-in: replaces it without a warning.
|
|
85
|
+
label: "read (big-gulp-reads)",
|
|
86
|
+
description: describeLimits(),
|
|
87
|
+
parameters: readSchema,
|
|
88
|
+
|
|
89
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
90
|
+
// The harness does not enforce required schema fields before execute runs.
|
|
91
|
+
if (typeof params.path !== "string" || params.path.length === 0) {
|
|
92
|
+
return { content: [{ type: "text", text: "Error: path is required." }] };
|
|
93
|
+
}
|
|
94
|
+
// Built-in tools strip a leading @ from paths; some models include it.
|
|
95
|
+
const rawPath = params.path.startsWith("@") ? params.path.slice(1) : params.path;
|
|
96
|
+
const absolutePath = resolve(ctx.cwd, rawPath);
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
await access(absolutePath, constants.R_OK);
|
|
100
|
+
const buffer = await readFile(absolutePath);
|
|
101
|
+
|
|
102
|
+
const mime = IMAGE_MIMES[extname(absolutePath).toLowerCase()];
|
|
103
|
+
if (mime) {
|
|
104
|
+
return {
|
|
105
|
+
content: [
|
|
106
|
+
{ type: "text", text: `Read image file [${mime}] (no auto-resize)` },
|
|
107
|
+
{ type: "image", data: buffer.toString("base64"), mimeType: mime },
|
|
108
|
+
],
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Text path mirrors the built-in read tool's messages so the model's
|
|
113
|
+
// offset/limit habits keep working.
|
|
114
|
+
const content = buffer.toString("utf-8");
|
|
115
|
+
const lines = content.split("\n");
|
|
116
|
+
if (content.endsWith("\n")) lines.pop();
|
|
117
|
+
const totalFileLines = lines.length;
|
|
118
|
+
const startLine = params.offset ? Math.max(0, params.offset - 1) : 0;
|
|
119
|
+
const startLineDisplay = startLine + 1;
|
|
120
|
+
if (startLine >= totalFileLines) {
|
|
121
|
+
return { content: [{ type: "text", text: `Error: Offset ${params.offset} is beyond end of file (${totalFileLines} lines total)` }] };
|
|
122
|
+
}
|
|
123
|
+
const selected = params.limit !== undefined ? lines.slice(startLine, startLine + params.limit) : lines.slice(startLine);
|
|
124
|
+
|
|
125
|
+
const maxBytes = limits.maxKB * 1024;
|
|
126
|
+
const truncation = truncateHead(selected.join("\n"), { maxLines: limits.maxLines, maxBytes });
|
|
127
|
+
void logOverride(absolutePath);
|
|
128
|
+
|
|
129
|
+
let outputText: string;
|
|
130
|
+
if (truncation.firstLineExceedsLimit) {
|
|
131
|
+
// truncateHead does not report the oversized line's byte count, so measure it here.
|
|
132
|
+
const firstLineBytes = Buffer.byteLength(selected[0] ?? "", "utf-8");
|
|
133
|
+
outputText = `[Line ${startLineDisplay} is ${formatSize(firstLineBytes)}, exceeds ${formatSize(maxBytes)} limit. Use bash: sed -n '${startLineDisplay}p' ${params.path} | head -c ${maxBytes}]`;
|
|
134
|
+
} else if (truncation.truncated) {
|
|
135
|
+
const endLineDisplay = startLineDisplay + truncation.outputLines - 1;
|
|
136
|
+
const nextOffset = endLineDisplay + 1;
|
|
137
|
+
outputText = truncation.content;
|
|
138
|
+
if (truncation.truncatedBy === "lines") {
|
|
139
|
+
outputText += `\n\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}. Use offset=${nextOffset} to continue.]`;
|
|
140
|
+
} else {
|
|
141
|
+
outputText += `\n\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines} (${formatSize(maxBytes)} limit). Use offset=${nextOffset} to continue.]`;
|
|
142
|
+
}
|
|
143
|
+
} else if (params.limit !== undefined && startLine + truncation.outputLines < totalFileLines) {
|
|
144
|
+
const remaining = totalFileLines - (startLine + truncation.outputLines);
|
|
145
|
+
const nextOffset = startLine + truncation.outputLines + 1;
|
|
146
|
+
outputText = `${truncation.content}\n\n[${remaining} more lines in file. Use offset=${nextOffset} to continue.]`;
|
|
147
|
+
} else {
|
|
148
|
+
outputText = truncation.content;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return { content: [{ type: "text", text: outputText }], details: { truncation } };
|
|
152
|
+
} catch (error) {
|
|
153
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
154
|
+
return { content: [{ type: "text", text: `Error reading file: ${message}` }] };
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
pi.registerCommand("big-gulp-reads", {
|
|
160
|
+
description: "Show or set the big-gulp-reads read limits. Usage: /big-gulp-reads [<KB> | lines <N> | reset]",
|
|
161
|
+
getArgumentCompletions: (prefix) => {
|
|
162
|
+
const items = ["reset", "lines", "50", "100", "200", "500", "1000", "2000"].map((v) => ({ value: v, label: v }));
|
|
163
|
+
const filtered = items.filter((i) => i.value.startsWith(prefix));
|
|
164
|
+
return filtered.length > 0 ? filtered : null;
|
|
165
|
+
},
|
|
166
|
+
handler: async (args, ctx) => {
|
|
167
|
+
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
168
|
+
|
|
169
|
+
if (tokens.length === 0) {
|
|
170
|
+
ctx.ui.notify(`big-gulp-reads read limits: ${limits.maxKB}KB / ${limits.maxLines} lines (defaults: ${DEFAULTS.maxKB}KB / ${DEFAULTS.maxLines})`, "info");
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (tokens[0] === "reset") {
|
|
175
|
+
limits = { ...DEFAULTS };
|
|
176
|
+
await saveConfig();
|
|
177
|
+
ctx.ui.notify(`big-gulp-reads reset to defaults: ${limits.maxKB}KB / ${limits.maxLines} lines`, "info");
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (tokens[0] === "lines") {
|
|
182
|
+
const n = Number(tokens[1]);
|
|
183
|
+
if (tokens.length !== 2 || !Number.isInteger(n) || n < 1 || n > HARD_CAPS.maxLines) {
|
|
184
|
+
ctx.ui.notify(`Invalid line limit. Usage: /big-gulp-reads lines <1..${HARD_CAPS.maxLines}>`, "error");
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
limits.maxLines = n;
|
|
188
|
+
await saveConfig();
|
|
189
|
+
ctx.ui.notify(`big-gulp-reads line limit set to ${n} (byte limit: ${limits.maxKB}KB)`, "info");
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const kb = Number(tokens[0]);
|
|
194
|
+
if (tokens.length !== 1 || !Number.isInteger(kb) || kb < 1 || kb > HARD_CAPS.maxKB) {
|
|
195
|
+
ctx.ui.notify(`Invalid KB limit. Usage: /big-gulp-reads <1..${HARD_CAPS.maxKB}> | lines <N> | reset`, "error");
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
limits.maxKB = kb;
|
|
199
|
+
await saveConfig();
|
|
200
|
+
ctx.ui.notify(`big-gulp-reads byte limit set to ${kb}KB (line limit: ${limits.maxLines})`, "info");
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
"name": "big-gulp-reads",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Override pi's built-in read tool with a larger, configurable truncation limit",
|
|
5
|
+
"keywords": ["pi-package"],
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/taylorsatula/big-gulp-reads.git"
|
|
10
|
+
},
|
|
11
|
+
"files": ["extensions", "README.md", "CHANGELOG.md"],
|
|
12
|
+
"pi": {
|
|
13
|
+
"extensions": ["./extensions/big-read.ts"]
|
|
14
|
+
},
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
17
|
+
}
|
|
18
|
+
}
|