@pentect/pi 0.0.2 → 0.0.26

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,19 +1,19 @@
1
1
  # @pentect/pi
2
2
 
3
- Pentect protection for Pi.
4
-
5
- ## Install
6
-
7
- Install `pentect`, then run:
3
+ Run [Pi](https://github.com/badlogic/pi-mono) through Pentect's local HTTP
4
+ protection without changing Pi's saved configuration.
8
5
 
9
6
  ```sh
10
- pi install npm:@pentect/pi
7
+ npx @pentect/pi --model openai/gpt-5
11
8
  ```
12
9
 
13
- Start Pi normally:
10
+ For a permanent command:
14
11
 
15
12
  ```sh
16
- pi
13
+ npm install --global @pentect/pi
14
+ pentect-pi --model openai/gpt-5
17
15
  ```
18
16
 
19
- Pentect runs behind Pi's existing interface. No separate launcher is required.
17
+ The package installs matching Pentect and Pi versions. It is a small launcher,
18
+ not a prompt hook: requests go through the same loopback gateway as
19
+ `pentect pi`.
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from "node:child_process";
4
+ import { readFileSync } from "node:fs";
5
+ import { dirname, resolve } from "node:path";
6
+ import { createRequire } from "node:module";
7
+ import { invocation, piBinaryFromEntry } from "../lib/command.js";
8
+
9
+ const require = createRequire(import.meta.url);
10
+
11
+ function packageBinary(name, binary) {
12
+ const manifestPath = require.resolve(`${name}/package.json`);
13
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
14
+ const relative =
15
+ typeof manifest.bin === "string" ? manifest.bin : manifest.bin?.[binary];
16
+ if (typeof relative !== "string" || relative.length === 0) {
17
+ throw new Error(`${name} does not provide the expected ${binary} command`);
18
+ }
19
+ return resolve(dirname(manifestPath), relative);
20
+ }
21
+
22
+ try {
23
+ const pentectCli = packageBinary("pentect", "pentect");
24
+ // Pi exports dist/index.js but intentionally does not export package.json.
25
+ // Its pinned package exposes the adjacent dist/cli.js as the `pi` binary.
26
+ const piCli = piBinaryFromEntry(
27
+ require.resolve("@mariozechner/pi-coding-agent"),
28
+ );
29
+ const next = invocation(pentectCli, piCli, process.argv.slice(2));
30
+ const result = spawnSync(next.command, next.args, { stdio: "inherit" });
31
+ if (result.error) throw result.error;
32
+ if (result.signal) process.kill(process.pid, result.signal);
33
+ process.exitCode = result.status ?? 1;
34
+ } catch (error) {
35
+ const message = error instanceof Error ? error.message : String(error);
36
+ console.error(`pentect-pi: ${message}`);
37
+ process.exitCode = 1;
38
+ }
package/lib/command.js ADDED
@@ -0,0 +1,20 @@
1
+ import { dirname, resolve } from "node:path";
2
+
3
+ export function invocation(pentectCli, piCli, userArgs, node = process.execPath) {
4
+ return {
5
+ command: node,
6
+ args: [
7
+ pentectCli,
8
+ "pi",
9
+ "--pi",
10
+ node,
11
+ "--",
12
+ piCli,
13
+ ...userArgs,
14
+ ],
15
+ };
16
+ }
17
+
18
+ export function piBinaryFromEntry(entry) {
19
+ return resolve(dirname(entry), "cli.js");
20
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pentect/pi",
3
- "version": "0.0.2",
4
- "description": "Pentect protection for Pi",
3
+ "version": "0.0.26",
4
+ "description": "Run Pi through Pentect's local HTTP protection",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -9,35 +9,33 @@
9
9
  "url": "git+https://github.com/EdamAme-x/pentect.git",
10
10
  "directory": "integrations/pi"
11
11
  },
12
- "homepage": "https://github.com/EdamAme-x/pentect/tree/main/integrations/pi",
12
+ "homepage": "https://pentect.dev/clients/pi/",
13
13
  "bugs": "https://github.com/EdamAme-x/pentect/issues",
14
14
  "publishConfig": {
15
15
  "access": "public"
16
16
  },
17
17
  "keywords": [
18
- "pi-package",
19
- "pi-extension",
20
- "pentect"
18
+ "ai-security",
19
+ "pi-coding-agent",
20
+ "secret-detection"
21
21
  ],
22
+ "bin": {
23
+ "pentect-pi": "bin/pentect-pi.js"
24
+ },
22
25
  "files": [
23
- "extensions"
26
+ "bin",
27
+ "lib",
28
+ "README.md"
24
29
  ],
25
- "pi": {
26
- "extensions": [
27
- "./extensions/pentect.js"
28
- ]
29
- },
30
30
  "scripts": {
31
- "check": "node --check extensions/pentect.js",
31
+ "check": "node --check bin/pentect-pi.js && node --check lib/command.js",
32
32
  "test": "node --test"
33
33
  },
34
- "peerDependencies": {
35
- "@earendil-works/pi-coding-agent": ">=0.80.7 <0.81.0"
36
- },
37
- "devDependencies": {
38
- "@earendil-works/pi-coding-agent": "0.80.7"
34
+ "dependencies": {
35
+ "@mariozechner/pi-coding-agent": "0.73.1",
36
+ "pentect": "0.0.26"
39
37
  },
40
38
  "engines": {
41
- "node": ">=22.19.0"
39
+ "node": ">=20.6.0"
42
40
  }
43
41
  }
@@ -1,252 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import {
3
- createBashTool,
4
- createLocalBashOperations,
5
- } from "@earendil-works/pi-coding-agent";
6
-
7
- const SAFE_TEXT = "[Pentect: content unavailable]";
8
- const SAFE_RESULT = {
9
- content: [{ type: "text", text: SAFE_TEXT }],
10
- details: undefined,
11
- isError: true,
12
- };
13
- const BRIDGE_REQUEST_TIMEOUT_MS = 10_000;
14
- const SESSION_ENVIRONMENT = new Set([
15
- "PENTECT_BIN",
16
- "PENTECT_MEMORY_STORE_ADDR",
17
- "PENTECT_MEMORY_STORE_TOKEN",
18
- "PENTECT_PROCESS_HOST_READ_TOKEN",
19
- "PENTECT_PROCESS_HOST_WRITE_TOKEN",
20
- "PENTECT_PROCESS_HOST_ROOT",
21
- "PENTECT_AGENT_LAUNCHED",
22
- ]);
23
-
24
- function replaceObject(target, source) {
25
- for (const key of Object.keys(target)) delete target[key];
26
- Object.assign(target, source);
27
- }
28
-
29
- function createPentectBridge() {
30
- const child = spawn(process.env.PENTECT_BIN || "pentect", ["bridge"], {
31
- stdio: ["pipe", "pipe", "ignore"],
32
- windowsHide: true,
33
- });
34
- let nextId = 1;
35
- const pending = new Map();
36
- let buffered = "";
37
- let closed = false;
38
-
39
- const fail = () => {
40
- if (closed) return;
41
- closed = true;
42
- for (const { reject, timer } of pending.values()) {
43
- clearTimeout(timer);
44
- reject(new Error("Pentect unavailable"));
45
- }
46
- pending.clear();
47
- };
48
-
49
- child.stdout.setEncoding("utf8");
50
- child.stdout.on("data", (chunk) => {
51
- buffered += chunk;
52
- for (;;) {
53
- const end = buffered.indexOf("\n");
54
- if (end < 0) break;
55
- const line = buffered.slice(0, end);
56
- buffered = buffered.slice(end + 1);
57
- let response;
58
- try {
59
- response = JSON.parse(line);
60
- } catch {
61
- child.kill();
62
- fail();
63
- return;
64
- }
65
- const waiter = pending.get(response.id);
66
- if (!waiter) continue;
67
- pending.delete(response.id);
68
- clearTimeout(waiter.timer);
69
- if (response.ok) waiter.resolve(response.value);
70
- else waiter.reject(new Error("Pentect rejected the operation"));
71
- }
72
- });
73
- child.on("error", fail);
74
- child.on("exit", fail);
75
-
76
- return {
77
- request(op, fields = {}) {
78
- if (closed) return Promise.reject(new Error("Pentect unavailable"));
79
- const id = nextId++;
80
- return new Promise((resolve, reject) => {
81
- const timer = setTimeout(() => {
82
- if (!pending.delete(id)) return;
83
- reject(new Error("Pentect unavailable"));
84
- child.kill();
85
- fail();
86
- }, BRIDGE_REQUEST_TIMEOUT_MS);
87
- pending.set(id, { resolve, reject, timer });
88
- try {
89
- child.stdin.write(
90
- `${JSON.stringify({ id, op, ...fields })}\n`,
91
- (error) => {
92
- if (!error) return;
93
- const waiter = pending.get(id);
94
- if (!waiter) return;
95
- pending.delete(id);
96
- clearTimeout(waiter.timer);
97
- reject(new Error("Pentect unavailable"));
98
- child.kill();
99
- fail();
100
- },
101
- );
102
- } catch {
103
- pending.delete(id);
104
- clearTimeout(timer);
105
- reject(new Error("Pentect unavailable"));
106
- child.kill();
107
- fail();
108
- }
109
- });
110
- },
111
- close() {
112
- if (closed) return;
113
- for (const { reject, timer } of pending.values()) {
114
- clearTimeout(timer);
115
- reject(new Error("Pentect unavailable"));
116
- }
117
- pending.clear();
118
- closed = true;
119
- child.kill();
120
- },
121
- };
122
- }
123
-
124
- function readSessionEnvironment(values) {
125
- if (!values || typeof values !== "object" || Array.isArray(values)) {
126
- throw new Error("Pentect returned an invalid session");
127
- }
128
- for (const [name, value] of Object.entries(values)) {
129
- if (!SESSION_ENVIRONMENT.has(name) || typeof value !== "string" || !value) {
130
- throw new Error("Pentect returned an invalid session");
131
- }
132
- }
133
- for (const required of SESSION_ENVIRONMENT) {
134
- if (required === "PENTECT_BIN") continue;
135
- if (typeof values[required] !== "string" || !values[required]) {
136
- throw new Error("Pentect returned an incomplete session");
137
- }
138
- }
139
- return Object.freeze({ ...values });
140
- }
141
-
142
- export default function pentectExtension(pi) {
143
- const localBash = createLocalBashOperations();
144
- let bridge;
145
- let contract = "";
146
- let sessionEnvironment;
147
-
148
- const activeBridge = () => {
149
- if (!bridge) throw new Error("Pentect unavailable");
150
- return bridge;
151
- };
152
-
153
- const protectedBash = {
154
- async exec(command, cwd, options) {
155
- const next = await activeBridge().request("before", {
156
- tool: "bash",
157
- value: { command },
158
- });
159
- if (!next || typeof next.command !== "string") {
160
- throw new Error("Pentect rejected the command");
161
- }
162
- if (!sessionEnvironment) throw new Error("Pentect unavailable");
163
- // The bridge wraps the command with `pentect exec`, which masks each
164
- // streamed chunk before Pi's onData callback receives it.
165
- return localBash.exec(next.command, cwd, {
166
- ...options,
167
- env: { ...options.env, ...sessionEnvironment },
168
- });
169
- },
170
- };
171
-
172
- pi.on("session_start", async (_event, ctx) => {
173
- bridge?.close();
174
- sessionEnvironment = undefined;
175
- contract = "";
176
- bridge = createPentectBridge();
177
- try {
178
- const session = await bridge.request("session");
179
- if (!session || typeof session.contract !== "string") {
180
- throw new Error("Pentect returned an invalid session");
181
- }
182
- sessionEnvironment = readSessionEnvironment(session.environment);
183
- contract = session.contract;
184
- pi.registerTool(
185
- createBashTool(ctx.cwd, {
186
- operations: protectedBash,
187
- }),
188
- );
189
- } catch {
190
- bridge.close();
191
- bridge = undefined;
192
- throw new Error("Pentect unavailable");
193
- }
194
- });
195
-
196
- pi.on("before_agent_start", async (event) => {
197
- if (!contract || event.systemPrompt.includes(contract)) return {};
198
- return { systemPrompt: `${event.systemPrompt}\n\n${contract}` };
199
- });
200
-
201
- pi.on("input", async (event, ctx) => {
202
- try {
203
- const text = await activeBridge().request("prompt", { value: event.text });
204
- const images = event.images
205
- ? await activeBridge().request("media", { value: event.images })
206
- : event.images;
207
- return { action: "transform", text, images };
208
- } catch {
209
- if (ctx.hasUI) ctx.ui.notify("Pentect unavailable", "error");
210
- return { action: "handled" };
211
- }
212
- });
213
-
214
- pi.on("tool_call", async (event) => {
215
- if (event.toolName === "bash") return {};
216
- try {
217
- const next = await activeBridge().request("before", {
218
- tool: event.toolName,
219
- value: event.input,
220
- });
221
- replaceObject(event.input, next);
222
- return {};
223
- } catch {
224
- return { block: true, reason: "Pentect unavailable" };
225
- }
226
- });
227
-
228
- pi.on("tool_result", async (event) => {
229
- try {
230
- return await activeBridge().request("after", {
231
- tool: event.toolName,
232
- input: event.input,
233
- value: {
234
- content: event.content,
235
- details: event.details,
236
- isError: event.isError,
237
- },
238
- });
239
- } catch {
240
- return SAFE_RESULT;
241
- }
242
- });
243
-
244
- pi.on("user_bash", async () => ({ operations: protectedBash }));
245
-
246
- pi.on("session_shutdown", async () => {
247
- bridge?.close();
248
- bridge = undefined;
249
- contract = "";
250
- sessionEnvironment = undefined;
251
- });
252
- }