@mariozechner/pi-coding-agent 0.22.4 → 0.23.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/CHANGELOG.md +18 -0
- package/README.md +54 -2
- package/dist/cli/args.d.ts +1 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +5 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session.d.ts +9 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +64 -11
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/custom-tools/index.d.ts +6 -0
- package/dist/core/custom-tools/index.d.ts.map +1 -0
- package/dist/core/custom-tools/index.js +5 -0
- package/dist/core/custom-tools/index.js.map +1 -0
- package/dist/core/custom-tools/loader.d.ts +24 -0
- package/dist/core/custom-tools/loader.d.ts.map +1 -0
- package/dist/core/custom-tools/loader.js +210 -0
- package/dist/core/custom-tools/loader.js.map +1 -0
- package/dist/core/custom-tools/types.d.ts +81 -0
- package/dist/core/custom-tools/types.d.ts.map +1 -0
- package/dist/core/custom-tools/types.js +8 -0
- package/dist/core/custom-tools/types.js.map +1 -0
- package/dist/core/hooks/index.d.ts +1 -1
- package/dist/core/hooks/index.d.ts.map +1 -1
- package/dist/core/hooks/index.js.map +1 -1
- package/dist/core/hooks/types.d.ts +14 -19
- package/dist/core/hooks/types.d.ts.map +1 -1
- package/dist/core/hooks/types.js.map +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +4 -0
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +3 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +7 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +22 -3
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +4 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +64 -20
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +11 -2
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +66 -12
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +35 -9
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +27 -2
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/docs/custom-tools.md +341 -0
- package/docs/hooks.md +54 -34
- package/examples/custom-tools/README.md +101 -0
- package/examples/custom-tools/hello.ts +20 -0
- package/examples/custom-tools/question.ts +83 -0
- package/examples/custom-tools/todo.ts +192 -0
- package/examples/hooks/README.md +79 -0
- package/examples/hooks/file-trigger.ts +36 -0
- package/examples/hooks/git-checkpoint.ts +48 -0
- package/examples/hooks/permission-gate.ts +38 -0
- package/examples/hooks/protected-paths.ts +30 -0
- package/package.json +7 -6
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Todo Tool - Demonstrates state management via session entries
|
|
3
|
+
*
|
|
4
|
+
* This tool stores state in tool result details (not external files),
|
|
5
|
+
* which allows proper branching - when you branch, the todo state
|
|
6
|
+
* is automatically correct for that point in history.
|
|
7
|
+
*
|
|
8
|
+
* The onSession callback reconstructs state by scanning past tool results.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Type } from "@mariozechner/pi-coding-agent";
|
|
12
|
+
import { StringEnum } from "@mariozechner/pi-ai";
|
|
13
|
+
import { Text } from "@mariozechner/pi-tui";
|
|
14
|
+
import type { CustomAgentTool, CustomToolFactory, ToolSessionEvent } from "@mariozechner/pi-coding-agent";
|
|
15
|
+
|
|
16
|
+
interface Todo {
|
|
17
|
+
id: number;
|
|
18
|
+
text: string;
|
|
19
|
+
done: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// State stored in tool result details
|
|
23
|
+
interface TodoDetails {
|
|
24
|
+
action: "list" | "add" | "toggle" | "clear";
|
|
25
|
+
todos: Todo[];
|
|
26
|
+
nextId: number;
|
|
27
|
+
error?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Define schema separately for proper type inference
|
|
31
|
+
const TodoParams = Type.Object({
|
|
32
|
+
action: StringEnum(["list", "add", "toggle", "clear"] as const),
|
|
33
|
+
text: Type.Optional(Type.String({ description: "Todo text (for add)" })),
|
|
34
|
+
id: Type.Optional(Type.Number({ description: "Todo ID (for toggle)" })),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const factory: CustomToolFactory = (_pi) => {
|
|
38
|
+
// In-memory state (reconstructed from session on load)
|
|
39
|
+
let todos: Todo[] = [];
|
|
40
|
+
let nextId = 1;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Reconstruct state from session entries.
|
|
44
|
+
* Scans tool results for this tool and applies them in order.
|
|
45
|
+
*/
|
|
46
|
+
const reconstructState = (event: ToolSessionEvent) => {
|
|
47
|
+
todos = [];
|
|
48
|
+
nextId = 1;
|
|
49
|
+
|
|
50
|
+
for (const entry of event.entries) {
|
|
51
|
+
if (entry.type !== "message") continue;
|
|
52
|
+
const msg = entry.message;
|
|
53
|
+
|
|
54
|
+
// Tool results have role "toolResult"
|
|
55
|
+
if (msg.role !== "toolResult") continue;
|
|
56
|
+
if (msg.toolName !== "todo") continue;
|
|
57
|
+
|
|
58
|
+
const details = msg.details as TodoDetails | undefined;
|
|
59
|
+
if (details) {
|
|
60
|
+
todos = details.todos;
|
|
61
|
+
nextId = details.nextId;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const tool: CustomAgentTool<typeof TodoParams, TodoDetails> = {
|
|
67
|
+
name: "todo",
|
|
68
|
+
label: "Todo",
|
|
69
|
+
description: "Manage a todo list. Actions: list, add (text), toggle (id), clear",
|
|
70
|
+
parameters: TodoParams,
|
|
71
|
+
|
|
72
|
+
// Called on session start/switch/branch/clear
|
|
73
|
+
onSession: reconstructState,
|
|
74
|
+
|
|
75
|
+
async execute(_toolCallId, params) {
|
|
76
|
+
switch (params.action) {
|
|
77
|
+
case "list":
|
|
78
|
+
return {
|
|
79
|
+
content: [{ type: "text", text: todos.length ? todos.map((t) => `[${t.done ? "x" : " "}] #${t.id}: ${t.text}`).join("\n") : "No todos" }],
|
|
80
|
+
details: { action: "list", todos: [...todos], nextId },
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
case "add":
|
|
84
|
+
if (!params.text) {
|
|
85
|
+
return {
|
|
86
|
+
content: [{ type: "text", text: "Error: text required for add" }],
|
|
87
|
+
details: { action: "add", todos: [...todos], nextId, error: "text required" },
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const newTodo: Todo = { id: nextId++, text: params.text, done: false };
|
|
91
|
+
todos.push(newTodo);
|
|
92
|
+
return {
|
|
93
|
+
content: [{ type: "text", text: `Added todo #${newTodo.id}: ${newTodo.text}` }],
|
|
94
|
+
details: { action: "add", todos: [...todos], nextId },
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
case "toggle":
|
|
98
|
+
if (params.id === undefined) {
|
|
99
|
+
return {
|
|
100
|
+
content: [{ type: "text", text: "Error: id required for toggle" }],
|
|
101
|
+
details: { action: "toggle", todos: [...todos], nextId, error: "id required" },
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const todo = todos.find((t) => t.id === params.id);
|
|
105
|
+
if (!todo) {
|
|
106
|
+
return {
|
|
107
|
+
content: [{ type: "text", text: `Todo #${params.id} not found` }],
|
|
108
|
+
details: { action: "toggle", todos: [...todos], nextId, error: `#${params.id} not found` },
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
todo.done = !todo.done;
|
|
112
|
+
return {
|
|
113
|
+
content: [{ type: "text", text: `Todo #${todo.id} ${todo.done ? "completed" : "uncompleted"}` }],
|
|
114
|
+
details: { action: "toggle", todos: [...todos], nextId },
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
case "clear":
|
|
118
|
+
const count = todos.length;
|
|
119
|
+
todos = [];
|
|
120
|
+
nextId = 1;
|
|
121
|
+
return {
|
|
122
|
+
content: [{ type: "text", text: `Cleared ${count} todos` }],
|
|
123
|
+
details: { action: "clear", todos: [], nextId: 1 },
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
default:
|
|
127
|
+
return {
|
|
128
|
+
content: [{ type: "text", text: `Unknown action: ${params.action}` }],
|
|
129
|
+
details: { action: "list", todos: [...todos], nextId, error: `unknown action: ${params.action}` },
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
renderCall(args, theme) {
|
|
135
|
+
let text = theme.fg("toolTitle", theme.bold("todo ")) + theme.fg("muted", args.action);
|
|
136
|
+
if (args.text) text += " " + theme.fg("dim", `"${args.text}"`);
|
|
137
|
+
if (args.id !== undefined) text += " " + theme.fg("accent", `#${args.id}`);
|
|
138
|
+
return new Text(text, 0, 0);
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
renderResult(result, { expanded }, theme) {
|
|
142
|
+
const { details } = result;
|
|
143
|
+
if (!details) {
|
|
144
|
+
const text = result.content[0];
|
|
145
|
+
return new Text(text?.type === "text" ? text.text : "", 0, 0);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Error
|
|
149
|
+
if (details.error) {
|
|
150
|
+
return new Text(theme.fg("error", `Error: ${details.error}`), 0, 0);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const todoList = details.todos;
|
|
154
|
+
|
|
155
|
+
switch (details.action) {
|
|
156
|
+
case "list":
|
|
157
|
+
if (todoList.length === 0) {
|
|
158
|
+
return new Text(theme.fg("dim", "No todos"), 0, 0);
|
|
159
|
+
}
|
|
160
|
+
let listText = theme.fg("muted", `${todoList.length} todo(s):`);
|
|
161
|
+
const display = expanded ? todoList : todoList.slice(0, 5);
|
|
162
|
+
for (const t of display) {
|
|
163
|
+
const check = t.done ? theme.fg("success", "✓") : theme.fg("dim", "○");
|
|
164
|
+
const itemText = t.done ? theme.fg("dim", t.text) : theme.fg("muted", t.text);
|
|
165
|
+
listText += "\n" + check + " " + theme.fg("accent", `#${t.id}`) + " " + itemText;
|
|
166
|
+
}
|
|
167
|
+
if (!expanded && todoList.length > 5) {
|
|
168
|
+
listText += "\n" + theme.fg("dim", `... ${todoList.length - 5} more`);
|
|
169
|
+
}
|
|
170
|
+
return new Text(listText, 0, 0);
|
|
171
|
+
|
|
172
|
+
case "add": {
|
|
173
|
+
const added = todoList[todoList.length - 1];
|
|
174
|
+
return new Text(theme.fg("success", "✓ Added ") + theme.fg("accent", `#${added.id}`) + " " + theme.fg("muted", added.text), 0, 0);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
case "toggle": {
|
|
178
|
+
const text = result.content[0];
|
|
179
|
+
const msg = text?.type === "text" ? text.text : "";
|
|
180
|
+
return new Text(theme.fg("success", "✓ ") + theme.fg("muted", msg), 0, 0);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
case "clear":
|
|
184
|
+
return new Text(theme.fg("success", "✓ ") + theme.fg("muted", "Cleared all todos"), 0, 0);
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
return tool;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export default factory;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Hooks Examples
|
|
2
|
+
|
|
3
|
+
Example hooks for pi-coding-agent.
|
|
4
|
+
|
|
5
|
+
## Examples
|
|
6
|
+
|
|
7
|
+
### permission-gate.ts
|
|
8
|
+
Prompts for confirmation before running dangerous bash commands (rm -rf, sudo, chmod 777, etc.).
|
|
9
|
+
|
|
10
|
+
### git-checkpoint.ts
|
|
11
|
+
Creates git stash checkpoints at each turn, allowing code restoration when branching.
|
|
12
|
+
|
|
13
|
+
### protected-paths.ts
|
|
14
|
+
Blocks writes to protected paths (.env, .git/, node_modules/).
|
|
15
|
+
|
|
16
|
+
### file-trigger.ts
|
|
17
|
+
Watches a trigger file and injects its contents into the conversation. Useful for external systems (CI, file watchers, webhooks) to send messages to the agent.
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Test directly
|
|
23
|
+
pi --hook examples/hooks/permission-gate.ts
|
|
24
|
+
|
|
25
|
+
# Or copy to hooks directory for persistent use
|
|
26
|
+
cp permission-gate.ts ~/.pi/agent/hooks/
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Writing Hooks
|
|
30
|
+
|
|
31
|
+
See [docs/hooks.md](../../docs/hooks.md) for full documentation.
|
|
32
|
+
|
|
33
|
+
### Key Points
|
|
34
|
+
|
|
35
|
+
**Hook structure:**
|
|
36
|
+
```typescript
|
|
37
|
+
import type { HookAPI } from "@mariozechner/pi-coding-agent/hooks";
|
|
38
|
+
|
|
39
|
+
export default function (pi: HookAPI) {
|
|
40
|
+
pi.on("session", async (event, ctx) => {
|
|
41
|
+
// event.reason: "start" | "switch" | "clear"
|
|
42
|
+
// ctx.ui, ctx.exec, ctx.cwd, ctx.sessionFile, ctx.hasUI
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
46
|
+
// Can block tool execution
|
|
47
|
+
if (dangerous) {
|
|
48
|
+
return { block: true, reason: "Blocked" };
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
pi.on("tool_result", async (event, ctx) => {
|
|
54
|
+
// Can modify result
|
|
55
|
+
return { result: "modified result" };
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Available events:**
|
|
61
|
+
- `session` - startup, session switch, clear
|
|
62
|
+
- `branch` - before branching (can skip conversation restore)
|
|
63
|
+
- `agent_start` / `agent_end` - per user prompt
|
|
64
|
+
- `turn_start` / `turn_end` - per LLM turn
|
|
65
|
+
- `tool_call` - before tool execution (can block)
|
|
66
|
+
- `tool_result` - after tool execution (can modify)
|
|
67
|
+
|
|
68
|
+
**UI methods:**
|
|
69
|
+
```typescript
|
|
70
|
+
const choice = await ctx.ui.select("Title", ["Option A", "Option B"]);
|
|
71
|
+
const confirmed = await ctx.ui.confirm("Title", "Are you sure?");
|
|
72
|
+
const input = await ctx.ui.input("Title", "placeholder");
|
|
73
|
+
ctx.ui.notify("Message", "info"); // or "warning", "error"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Sending messages:**
|
|
77
|
+
```typescript
|
|
78
|
+
pi.send("Message to inject into conversation");
|
|
79
|
+
```
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Trigger Hook
|
|
3
|
+
*
|
|
4
|
+
* Watches a trigger file and injects its contents into the conversation.
|
|
5
|
+
* Useful for external systems to send messages to the agent.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* echo "Run the tests" > /tmp/agent-trigger.txt
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as fs from "node:fs";
|
|
12
|
+
import type { HookAPI } from "@mariozechner/pi-coding-agent/hooks";
|
|
13
|
+
|
|
14
|
+
export default function (pi: HookAPI) {
|
|
15
|
+
pi.on("session", async (event, ctx) => {
|
|
16
|
+
if (event.reason !== "start") return;
|
|
17
|
+
|
|
18
|
+
const triggerFile = "/tmp/agent-trigger.txt";
|
|
19
|
+
|
|
20
|
+
fs.watch(triggerFile, () => {
|
|
21
|
+
try {
|
|
22
|
+
const content = fs.readFileSync(triggerFile, "utf-8").trim();
|
|
23
|
+
if (content) {
|
|
24
|
+
pi.send(`External trigger: ${content}`);
|
|
25
|
+
fs.writeFileSync(triggerFile, ""); // Clear after reading
|
|
26
|
+
}
|
|
27
|
+
} catch {
|
|
28
|
+
// File might not exist yet
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (ctx.hasUI) {
|
|
33
|
+
ctx.ui.notify(`Watching ${triggerFile}`, "info");
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git Checkpoint Hook
|
|
3
|
+
*
|
|
4
|
+
* Creates git stash checkpoints at each turn so /branch can restore code state.
|
|
5
|
+
* When branching, offers to restore code to that point in history.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { HookAPI } from "@mariozechner/pi-coding-agent/hooks";
|
|
9
|
+
|
|
10
|
+
export default function (pi: HookAPI) {
|
|
11
|
+
const checkpoints = new Map<number, string>();
|
|
12
|
+
|
|
13
|
+
pi.on("turn_start", async (event, ctx) => {
|
|
14
|
+
// Create a git stash entry before LLM makes changes
|
|
15
|
+
const { stdout } = await ctx.exec("git", ["stash", "create"]);
|
|
16
|
+
const ref = stdout.trim();
|
|
17
|
+
if (ref) {
|
|
18
|
+
checkpoints.set(event.turnIndex, ref);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
pi.on("branch", async (event, ctx) => {
|
|
23
|
+
const ref = checkpoints.get(event.targetTurnIndex);
|
|
24
|
+
if (!ref) return undefined;
|
|
25
|
+
|
|
26
|
+
if (!ctx.hasUI) {
|
|
27
|
+
// In non-interactive mode, don't restore automatically
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const choice = await ctx.ui.select("Restore code state?", [
|
|
32
|
+
"Yes, restore code to that point",
|
|
33
|
+
"No, keep current code",
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
if (choice?.startsWith("Yes")) {
|
|
37
|
+
await ctx.exec("git", ["stash", "apply", ref]);
|
|
38
|
+
ctx.ui.notify("Code restored to checkpoint", "info");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return undefined;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
pi.on("agent_end", async () => {
|
|
45
|
+
// Clear checkpoints after agent completes
|
|
46
|
+
checkpoints.clear();
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission Gate Hook
|
|
3
|
+
*
|
|
4
|
+
* Prompts for confirmation before running potentially dangerous bash commands.
|
|
5
|
+
* Patterns checked: rm -rf, sudo, chmod/chown 777
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { HookAPI } from "@mariozechner/pi-coding-agent/hooks";
|
|
9
|
+
|
|
10
|
+
export default function (pi: HookAPI) {
|
|
11
|
+
const dangerousPatterns = [
|
|
12
|
+
/\brm\s+(-rf?|--recursive)/i,
|
|
13
|
+
/\bsudo\b/i,
|
|
14
|
+
/\b(chmod|chown)\b.*777/i,
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
18
|
+
if (event.toolName !== "bash") return undefined;
|
|
19
|
+
|
|
20
|
+
const command = event.input.command as string;
|
|
21
|
+
const isDangerous = dangerousPatterns.some((p) => p.test(command));
|
|
22
|
+
|
|
23
|
+
if (isDangerous) {
|
|
24
|
+
if (!ctx.hasUI) {
|
|
25
|
+
// In non-interactive mode, block by default
|
|
26
|
+
return { block: true, reason: "Dangerous command blocked (no UI for confirmation)" };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const choice = await ctx.ui.select(`⚠️ Dangerous command:\n\n ${command}\n\nAllow?`, ["Yes", "No"]);
|
|
30
|
+
|
|
31
|
+
if (choice !== "Yes") {
|
|
32
|
+
return { block: true, reason: "Blocked by user" };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return undefined;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Protected Paths Hook
|
|
3
|
+
*
|
|
4
|
+
* Blocks write and edit operations to protected paths.
|
|
5
|
+
* Useful for preventing accidental modifications to sensitive files.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { HookAPI } from "@mariozechner/pi-coding-agent/hooks";
|
|
9
|
+
|
|
10
|
+
export default function (pi: HookAPI) {
|
|
11
|
+
const protectedPaths = [".env", ".git/", "node_modules/"];
|
|
12
|
+
|
|
13
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
14
|
+
if (event.toolName !== "write" && event.toolName !== "edit") {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const path = event.input.path as string;
|
|
19
|
+
const isProtected = protectedPaths.some((p) => path.includes(p));
|
|
20
|
+
|
|
21
|
+
if (isProtected) {
|
|
22
|
+
if (ctx.hasUI) {
|
|
23
|
+
ctx.ui.notify(`Blocked write to protected path: ${path}`, "warning");
|
|
24
|
+
}
|
|
25
|
+
return { block: true, reason: `Path "${path}" is protected` };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return undefined;
|
|
29
|
+
});
|
|
30
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mariozechner/pi-coding-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"piConfig": {
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"files": [
|
|
26
26
|
"dist",
|
|
27
27
|
"docs",
|
|
28
|
+
"examples",
|
|
28
29
|
"CHANGELOG.md"
|
|
29
30
|
],
|
|
30
31
|
"scripts": {
|
|
@@ -32,16 +33,16 @@
|
|
|
32
33
|
"build": "tsgo -p tsconfig.build.json && chmod +x dist/cli.js && npm run copy-assets",
|
|
33
34
|
"build:binary": "npm run build && bun build --compile ./dist/cli.js --outfile dist/pi && npm run copy-binary-assets",
|
|
34
35
|
"copy-assets": "mkdir -p dist/modes/interactive/theme && cp src/modes/interactive/theme/*.json dist/modes/interactive/theme/",
|
|
35
|
-
"copy-binary-assets": "cp package.json dist/ && cp README.md dist/ && cp CHANGELOG.md dist/ && mkdir -p dist/theme && cp src/modes/interactive/theme/*.json dist/theme/ && cp -r docs dist/",
|
|
36
|
+
"copy-binary-assets": "cp package.json dist/ && cp README.md dist/ && cp CHANGELOG.md dist/ && mkdir -p dist/theme && cp src/modes/interactive/theme/*.json dist/theme/ && cp -r docs dist/ && cp -r examples dist/",
|
|
36
37
|
"dev": "tsgo -p tsconfig.build.json --watch --preserveWatchOutput",
|
|
37
|
-
"check": "tsgo --noEmit",
|
|
38
|
+
"check": "tsgo --noEmit && tsc -p tsconfig.examples.json",
|
|
38
39
|
"test": "vitest --run",
|
|
39
40
|
"prepublishOnly": "npm run clean && npm run build"
|
|
40
41
|
},
|
|
41
42
|
"dependencies": {
|
|
42
|
-
"@mariozechner/pi-agent-core": "^0.
|
|
43
|
-
"@mariozechner/pi-ai": "^0.
|
|
44
|
-
"@mariozechner/pi-tui": "^0.
|
|
43
|
+
"@mariozechner/pi-agent-core": "^0.23.0",
|
|
44
|
+
"@mariozechner/pi-ai": "^0.23.0",
|
|
45
|
+
"@mariozechner/pi-tui": "^0.23.0",
|
|
45
46
|
"chalk": "^5.5.0",
|
|
46
47
|
"diff": "^8.0.2",
|
|
47
48
|
"glob": "^11.0.3",
|