@tomsun28/pizza 0.0.1 → 0.0.4

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/src/tools/edit.ts DELETED
@@ -1,50 +0,0 @@
1
- import type { AgentTool } from "@mariozechner/pi-agent-core";
2
- import { Type } from "@sinclair/typebox";
3
- import { readFile, writeFile } from "node:fs/promises";
4
- import { resolve, isAbsolute } from "node:path";
5
-
6
- const schema = Type.Object({
7
- path: Type.String({ description: "Path to the file to edit (relative or absolute)" }),
8
- oldText: Type.String({ description: "Exact text to find and replace (must match exactly)" }),
9
- newText: Type.String({ description: "New text to replace the old text with" }),
10
- });
11
-
12
- export function createEditTool(cwd: string): AgentTool<typeof schema> {
13
- return {
14
- name: "edit",
15
- label: "edit",
16
- description: "Edit a file by replacing exact text. The oldText must match exactly (including whitespace).",
17
- parameters: schema,
18
- async execute(_toolCallId, { path, oldText, newText }, signal) {
19
- const absolutePath = isAbsolute(path) ? path : resolve(cwd, path);
20
-
21
- if (signal?.aborted) throw new Error("aborted");
22
-
23
- const content = await readFile(absolutePath, "utf-8");
24
-
25
- const index = content.indexOf(oldText);
26
- if (index === -1) {
27
- throw new Error(
28
- `Could not find the exact text in ${path}. The old text must match exactly including whitespace and newlines.`
29
- );
30
- }
31
-
32
- // Check for multiple occurrences
33
- const secondIndex = content.indexOf(oldText, index + 1);
34
- if (secondIndex !== -1) {
35
- throw new Error(
36
- `Found multiple occurrences of the text in ${path}. Provide more context to make it unique.`
37
- );
38
- }
39
-
40
- const newContent = content.substring(0, index) + newText + content.substring(index + oldText.length);
41
-
42
- await writeFile(absolutePath, newContent, "utf-8");
43
-
44
- return {
45
- content: [{ type: "text", text: `Edited ${path}` }],
46
- details: {},
47
- };
48
- },
49
- };
50
- }
package/src/tools/grep.ts DELETED
@@ -1,124 +0,0 @@
1
- import type { AgentTool } from "@mariozechner/pi-agent-core";
2
- import { Type } from "@sinclair/typebox";
3
- import { readdir, readFile, stat } from "node:fs/promises";
4
- import { resolve, isAbsolute, join, relative } from "node:path";
5
-
6
- const MAX_RESULTS = 200;
7
- const MAX_FILE_SIZE = 1024 * 1024; // Skip files > 1MB
8
-
9
- const schema = Type.Object({
10
- pattern: Type.String({ description: "Regex pattern to search for" }),
11
- path: Type.Optional(Type.String({ description: "Directory to search in (default: current directory)" })),
12
- include: Type.Optional(Type.String({ description: "Glob-like file extension filter, e.g. '*.ts'" })),
13
- });
14
-
15
- export function createGrepTool(cwd: string): AgentTool<typeof schema> {
16
- return {
17
- name: "grep",
18
- label: "grep",
19
- description: `Search file contents with regex. Returns matching lines with file paths and line numbers. Max ${MAX_RESULTS} results.`,
20
- parameters: schema,
21
- async execute(_toolCallId, { pattern, path, include }, signal) {
22
- const searchDir = path ? (isAbsolute(path) ? path : resolve(cwd, path)) : cwd;
23
-
24
- if (signal?.aborted) throw new Error("aborted");
25
-
26
- let regex: RegExp;
27
- try {
28
- regex = new RegExp(pattern, "g");
29
- } catch (e: any) {
30
- throw new Error(`Invalid regex pattern: ${e.message}`);
31
- }
32
-
33
- const extFilter = include ? parseExtFilter(include) : null;
34
- const results: string[] = [];
35
-
36
- await searchDirectory(searchDir, regex, extFilter, results, cwd, signal);
37
-
38
- if (results.length === 0) {
39
- return { content: [{ type: "text", text: "No matches found." }], details: {} };
40
- }
41
-
42
- let output = results.join("\n");
43
- if (results.length >= MAX_RESULTS) {
44
- output += `\n\n[Truncated at ${MAX_RESULTS} results]`;
45
- }
46
-
47
- return {
48
- content: [{ type: "text", text: output }],
49
- details: {},
50
- };
51
- },
52
- };
53
- }
54
-
55
- function parseExtFilter(include: string): string | null {
56
- // Handle "*.ts" → ".ts"
57
- if (include.startsWith("*.")) {
58
- return include.substring(1);
59
- }
60
- return include;
61
- }
62
-
63
- const SKIP_DIRS = new Set([
64
- "node_modules", ".git", "dist", "build", ".next", "__pycache__",
65
- ".venv", "venv", ".cache", "coverage",
66
- ]);
67
-
68
- async function searchDirectory(
69
- dir: string,
70
- regex: RegExp,
71
- extFilter: string | null,
72
- results: string[],
73
- cwd: string,
74
- signal?: AbortSignal,
75
- ): Promise<void> {
76
- if (results.length >= MAX_RESULTS) return;
77
- if (signal?.aborted) return;
78
-
79
- let entries: string[];
80
- try {
81
- entries = await readdir(dir);
82
- } catch {
83
- return;
84
- }
85
-
86
- for (const entry of entries) {
87
- if (results.length >= MAX_RESULTS) return;
88
- if (signal?.aborted) return;
89
-
90
- const fullPath = join(dir, entry);
91
-
92
- let s;
93
- try {
94
- s = await stat(fullPath);
95
- } catch {
96
- continue;
97
- }
98
-
99
- if (s.isDirectory()) {
100
- if (!SKIP_DIRS.has(entry)) {
101
- await searchDirectory(fullPath, regex, extFilter, results, cwd, signal);
102
- }
103
- } else if (s.isFile()) {
104
- if (s.size > MAX_FILE_SIZE) continue;
105
- if (extFilter && !entry.endsWith(extFilter)) continue;
106
-
107
- try {
108
- const content = await readFile(fullPath, "utf-8");
109
- const lines = content.split("\n");
110
- const relPath = relative(cwd, fullPath);
111
-
112
- for (let i = 0; i < lines.length; i++) {
113
- regex.lastIndex = 0;
114
- if (regex.test(lines[i])) {
115
- results.push(`${relPath}:${i + 1}: ${lines[i].trimEnd()}`);
116
- if (results.length >= MAX_RESULTS) return;
117
- }
118
- }
119
- } catch {
120
- // Skip binary/unreadable files
121
- }
122
- }
123
- }
124
- }
@@ -1,25 +0,0 @@
1
- import type { AgentTool } from "@mariozechner/pi-agent-core";
2
- import { createReadTool } from "./read.js";
3
- import { createWriteTool } from "./write.js";
4
- import { createEditTool } from "./edit.js";
5
- import { createLsTool } from "./ls.js";
6
- import { createGrepTool } from "./grep.js";
7
- import { createBashTool } from "./bash.js";
8
-
9
- export function createAllTools(cwd: string): AgentTool<any>[] {
10
- return [
11
- createReadTool(cwd),
12
- createWriteTool(cwd),
13
- createEditTool(cwd),
14
- createLsTool(cwd),
15
- createGrepTool(cwd),
16
- createBashTool(cwd),
17
- ];
18
- }
19
-
20
- export { createReadTool } from "./read.js";
21
- export { createWriteTool } from "./write.js";
22
- export { createEditTool } from "./edit.js";
23
- export { createLsTool } from "./ls.js";
24
- export { createGrepTool } from "./grep.js";
25
- export { createBashTool } from "./bash.js";
package/src/tools/ls.ts DELETED
@@ -1,52 +0,0 @@
1
- import type { AgentTool } from "@mariozechner/pi-agent-core";
2
- import { Type } from "@sinclair/typebox";
3
- import { readdir, stat } from "node:fs/promises";
4
- import { resolve, isAbsolute, join } from "node:path";
5
-
6
- const schema = Type.Object({
7
- path: Type.Optional(Type.String({ description: "Directory path (default: current directory)" })),
8
- });
9
-
10
- export function createLsTool(cwd: string): AgentTool<typeof schema> {
11
- return {
12
- name: "ls",
13
- label: "ls",
14
- description: "List directory contents with file types and sizes.",
15
- parameters: schema,
16
- async execute(_toolCallId, { path }, signal) {
17
- const dirPath = path ? (isAbsolute(path) ? path : resolve(cwd, path)) : cwd;
18
-
19
- if (signal?.aborted) throw new Error("aborted");
20
-
21
- const entries = await readdir(dirPath);
22
- const lines: string[] = [];
23
-
24
- for (const entry of entries.sort()) {
25
- try {
26
- const fullPath = join(dirPath, entry);
27
- const s = await stat(fullPath);
28
- const type = s.isDirectory() ? "dir" : "file";
29
- const size = s.isDirectory() ? "-" : formatSize(s.size);
30
- lines.push(`${type}\t${size}\t${entry}`);
31
- } catch {
32
- lines.push(`?\t?\t${entry}`);
33
- }
34
- }
35
-
36
- if (lines.length === 0) {
37
- return { content: [{ type: "text", text: "(empty directory)" }], details: {} };
38
- }
39
-
40
- return {
41
- content: [{ type: "text", text: lines.join("\n") }],
42
- details: {},
43
- };
44
- },
45
- };
46
- }
47
-
48
- function formatSize(bytes: number): string {
49
- if (bytes < 1024) return `${bytes}B`;
50
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
51
- return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
52
- }
package/src/tools/read.ts DELETED
@@ -1,69 +0,0 @@
1
- import type { AgentTool } from "@mariozechner/pi-agent-core";
2
- import { Type } from "@sinclair/typebox";
3
- import { readFile } from "node:fs/promises";
4
- import { resolve, isAbsolute } from "node:path";
5
-
6
- const MAX_LINES = 2000;
7
- const MAX_BYTES = 256 * 1024;
8
-
9
- const schema = Type.Object({
10
- path: Type.String({ description: "Path to the file to read (relative or absolute)" }),
11
- offset: Type.Optional(Type.Number({ description: "Line number to start reading from (1-indexed)" })),
12
- limit: Type.Optional(Type.Number({ description: "Maximum number of lines to read" })),
13
- });
14
-
15
- export function createReadTool(cwd: string): AgentTool<typeof schema> {
16
- return {
17
- name: "read",
18
- label: "read",
19
- description: `Read file contents. Output truncated to ${MAX_LINES} lines or ${MAX_BYTES / 1024}KB. Use offset/limit for large files.`,
20
- parameters: schema,
21
- async execute(_toolCallId, { path, offset, limit }, signal) {
22
- const absolutePath = isAbsolute(path) ? path : resolve(cwd, path);
23
-
24
- if (signal?.aborted) throw new Error("aborted");
25
-
26
- const buffer = await readFile(absolutePath);
27
- const text = buffer.toString("utf-8");
28
- const allLines = text.split("\n");
29
- const totalLines = allLines.length;
30
-
31
- const startLine = offset ? Math.max(0, offset - 1) : 0;
32
- if (startLine >= totalLines) {
33
- throw new Error(`Offset ${offset} is beyond end of file (${totalLines} lines)`);
34
- }
35
-
36
- let selectedLines: string[];
37
- if (limit !== undefined) {
38
- selectedLines = allLines.slice(startLine, startLine + limit);
39
- } else {
40
- selectedLines = allLines.slice(startLine);
41
- }
42
-
43
- // Truncate by lines
44
- if (selectedLines.length > MAX_LINES) {
45
- selectedLines = selectedLines.slice(0, MAX_LINES);
46
- }
47
-
48
- let output = selectedLines.join("\n");
49
-
50
- // Truncate by bytes
51
- if (Buffer.byteLength(output, "utf-8") > MAX_BYTES) {
52
- const buf = Buffer.from(output, "utf-8");
53
- output = buf.subarray(0, MAX_BYTES).toString("utf-8");
54
- }
55
-
56
- const shownLines = output.split("\n").length;
57
- const remaining = totalLines - startLine - shownLines;
58
- if (remaining > 0) {
59
- const nextOffset = startLine + shownLines + 1;
60
- output += `\n\n[Showing ${shownLines} of ${totalLines} lines. Use offset=${nextOffset} to continue.]`;
61
- }
62
-
63
- return {
64
- content: [{ type: "text", text: output }],
65
- details: {},
66
- };
67
- },
68
- };
69
- }
@@ -1,31 +0,0 @@
1
- import type { AgentTool } from "@mariozechner/pi-agent-core";
2
- import { Type } from "@sinclair/typebox";
3
- import { writeFile, mkdir } from "node:fs/promises";
4
- import { resolve, isAbsolute, dirname } from "node:path";
5
-
6
- const schema = Type.Object({
7
- path: Type.String({ description: "Path to the file to write (relative or absolute)" }),
8
- content: Type.String({ description: "Content to write to the file" }),
9
- });
10
-
11
- export function createWriteTool(cwd: string): AgentTool<typeof schema> {
12
- return {
13
- name: "write",
14
- label: "write",
15
- description: "Write content to a file. Creates the file and parent directories if they don't exist, overwrites if they do.",
16
- parameters: schema,
17
- async execute(_toolCallId, { path, content }, signal) {
18
- const absolutePath = isAbsolute(path) ? path : resolve(cwd, path);
19
-
20
- if (signal?.aborted) throw new Error("aborted");
21
-
22
- await mkdir(dirname(absolutePath), { recursive: true });
23
- await writeFile(absolutePath, content, "utf-8");
24
-
25
- return {
26
- content: [{ type: "text", text: `Wrote ${content.length} bytes to ${path}` }],
27
- details: {},
28
- };
29
- },
30
- };
31
- }
package/src/ui/render.ts DELETED
@@ -1,131 +0,0 @@
1
- import type { AgentEvent } from "@mariozechner/pi-agent-core";
2
-
3
- // ANSI color helpers
4
- const dim = (s: string) => `\x1b[2m${s}\x1b[0m`;
5
- const bold = (s: string) => `\x1b[1m${s}\x1b[0m`;
6
- const green = (s: string) => `\x1b[32m${s}\x1b[0m`;
7
- const yellow = (s: string) => `\x1b[33m${s}\x1b[0m`;
8
- const cyan = (s: string) => `\x1b[36m${s}\x1b[0m`;
9
- const red = (s: string) => `\x1b[31m${s}\x1b[0m`;
10
- const magenta = (s: string) => `\x1b[35m${s}\x1b[0m`;
11
-
12
- export function createRenderer(): (event: AgentEvent) => void {
13
- let currentToolName: string | undefined;
14
- let isFirstTextDelta = true;
15
-
16
- return (event: AgentEvent) => {
17
- switch (event.type) {
18
- case "agent_start":
19
- isFirstTextDelta = true;
20
- break;
21
-
22
- case "message_update": {
23
- const e = event.assistantMessageEvent;
24
- if (e.type === "text_delta") {
25
- if (isFirstTextDelta) {
26
- process.stdout.write("\n");
27
- isFirstTextDelta = false;
28
- }
29
- process.stdout.write(e.delta);
30
- } else if (e.type === "thinking_delta") {
31
- process.stdout.write(dim(e.delta));
32
- }
33
- break;
34
- }
35
-
36
- case "message_end": {
37
- const msg = event.message;
38
- if (msg.role === "assistant") {
39
- // Check if the message had text content
40
- const hasText = Array.isArray(msg.content)
41
- && msg.content.some((c: any) => c.type === "text" && c.text?.trim());
42
- if (hasText) {
43
- process.stdout.write("\n");
44
- }
45
- }
46
- break;
47
- }
48
-
49
- case "tool_execution_start":
50
- currentToolName = event.toolName;
51
- process.stdout.write(`\n${cyan(">")} ${bold(event.toolName)}`);
52
- if (event.toolName === "bash") {
53
- const cmd = (event.args as any)?.command;
54
- if (cmd) process.stdout.write(` ${yellow(cmd)}`);
55
- } else if (event.toolName === "read") {
56
- const path = (event.args as any)?.path;
57
- if (path) process.stdout.write(` ${magenta(path)}`);
58
- } else if (event.toolName === "write") {
59
- const path = (event.args as any)?.path;
60
- if (path) process.stdout.write(` ${magenta(path)}`);
61
- } else if (event.toolName === "edit") {
62
- const path = (event.args as any)?.path;
63
- if (path) process.stdout.write(` ${magenta(path)}`);
64
- } else if (event.toolName === "ls") {
65
- const path = (event.args as any)?.path || ".";
66
- process.stdout.write(` ${magenta(path)}`);
67
- } else if (event.toolName === "grep") {
68
- const pattern = (event.args as any)?.pattern;
69
- if (pattern) process.stdout.write(` ${yellow(pattern)}`);
70
- }
71
- process.stdout.write("\n");
72
- break;
73
-
74
- case "tool_execution_end": {
75
- const resultText = event.result?.content
76
- ?.filter((c: any) => c.type === "text")
77
- .map((c: any) => c.text)
78
- .join("\n") ?? "";
79
-
80
- if (event.isError) {
81
- const preview = truncatePreview(resultText, 5);
82
- process.stdout.write(`${red(preview)}\n`);
83
- } else {
84
- const preview = truncatePreview(resultText, 8);
85
- if (preview) {
86
- process.stdout.write(`${dim(preview)}\n`);
87
- }
88
- }
89
- currentToolName = undefined;
90
- isFirstTextDelta = true;
91
- break;
92
- }
93
-
94
- case "agent_end": {
95
- // Show cost/token info if available
96
- const messages = event.messages;
97
- const assistantMsgs = messages.filter((m: any) => m.role === "assistant" && m.usage);
98
- if (assistantMsgs.length > 0) {
99
- let totalInput = 0;
100
- let totalOutput = 0;
101
- let totalCost = 0;
102
- for (const m of assistantMsgs) {
103
- const usage = (m as any).usage;
104
- if (usage) {
105
- totalInput += usage.input || 0;
106
- totalOutput += usage.output || 0;
107
- totalCost += usage.cost?.total || 0;
108
- }
109
- }
110
- if (totalInput > 0 || totalOutput > 0) {
111
- process.stdout.write(
112
- dim(`\n[tokens: ${totalInput}→${totalOutput}` +
113
- (totalCost > 0 ? ` | $${totalCost.toFixed(4)}` : "") +
114
- `]\n`)
115
- );
116
- }
117
- }
118
- break;
119
- }
120
- }
121
- };
122
- }
123
-
124
- function truncatePreview(text: string, maxLines: number): string {
125
- if (!text) return "";
126
- const lines = text.split("\n");
127
- if (lines.length <= maxLines) return text;
128
- const shown = lines.slice(0, maxLines);
129
- const remaining = lines.length - maxLines;
130
- return shown.join("\n") + `\n... (${remaining} more lines)`;
131
- }
package/system-prompt.txt DELETED
@@ -1,55 +0,0 @@
1
- 你是一个命令行助手,运行在用户的终端环境中,帮助用户完成各类软件工程任务。
2
-
3
- 当你需要执行命令时,使用 [CLI]命令[/CLI] 格式。你可以在一次回复中执行多个命令。执行结果会以 [RESULT]...[/RESULT] 返回。
4
-
5
- ## 示例
6
-
7
- 用户:项目结构是什么样的?package.json 里有哪些依赖?
8
-
9
- 助手:看一下项目结构和依赖。
10
-
11
- [CLI]ls -la /Users/user/project[/CLI]
12
- [CLI]cat /Users/user/project/package.json[/CLI]
13
-
14
- [RESULT id=1]
15
- total 24
16
- drwxr-xr-x 5 user staff 160 Mar 26 10:00 .
17
- drwxr-xr-x 10 user staff 320 Mar 26 09:00 ..
18
- drwxr-xr-x 3 user staff 96 Mar 26 10:00 src
19
- -rw-r--r-- 1 user staff 256 Mar 26 10:00 package.json
20
- -rw-r--r-- 1 user staff 128 Mar 26 09:30 tsconfig.json
21
- [/RESULT]
22
-
23
- [RESULT id=2]
24
- {
25
- "name": "my-app",
26
- "dependencies": {
27
- "express": "^4.18.0"
28
- },
29
- "devDependencies": {
30
- "typescript": "^5.0.0"
31
- }
32
- }
33
- [/RESULT]
34
-
35
- 项目包含 src 目录和两个配置文件。依赖了 express 和 typescript。
36
-
37
- 用户:构建一下项目,同时检查有没有 lint 错误
38
-
39
- 助手:
40
-
41
- [CLI]cd /Users/user/project && npm run build[/CLI]
42
- [CLI]cd /Users/user/project && npm run lint[/CLI]
43
-
44
- [RESULT id=1]
45
- Successfully compiled 3 files.
46
- [/RESULT]
47
-
48
- [RESULT id=2]
49
- /Users/user/project/src/index.ts
50
- 12:5 warning Unexpected console statement no-console
51
-
52
- ✖ 1 problem (0 errors, 1 warning)
53
- [/RESULT]
54
-
55
- 构建成功。lint 有一个警告:src/index.ts 第 12 行有个 console 语句。
package/tsconfig.json DELETED
@@ -1,15 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "Node16",
5
- "moduleResolution": "Node16",
6
- "outDir": "dist",
7
- "rootDir": "src",
8
- "strict": true,
9
- "esModuleInterop": true,
10
- "skipLibCheck": true,
11
- "declaration": true,
12
- "sourceMap": true
13
- },
14
- "include": ["src/**/*"]
15
- }