my-pi 0.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/LICENSE +21 -0
- package/README.md +125 -0
- package/dist/index.js +242 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Scott Spence
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# my-pi
|
|
2
|
+
|
|
3
|
+
Personal [pi](https://pi.dev) coding agent wrapper with MCP tool
|
|
4
|
+
integration.
|
|
5
|
+
|
|
6
|
+
Built on the
|
|
7
|
+
[@mariozechner/pi-coding-agent](https://github.com/badlogic/pi-mono)
|
|
8
|
+
SDK. Adds MCP server support so models without built-in web search
|
|
9
|
+
(like Mistral) can still use external tools.
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm install
|
|
15
|
+
pnpm run build
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### API Keys
|
|
19
|
+
|
|
20
|
+
Pi handles authentication natively via `AuthStorage`. Options (in
|
|
21
|
+
priority order):
|
|
22
|
+
|
|
23
|
+
1. **`pi auth`** — interactive login, stores credentials in
|
|
24
|
+
`~/.pi/agent/auth.json`
|
|
25
|
+
2. **Environment variables** — `ANTHROPIC_API_KEY`, `MISTRAL_API_KEY`,
|
|
26
|
+
etc.
|
|
27
|
+
3. **OAuth** — supported for providers that offer it
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
### Interactive mode (full TUI)
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
node dist/index.js
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Pi's full terminal UI with editor, `/commands`, model switching
|
|
38
|
+
(`Ctrl+L`), session tree (`/tree`), and message queuing.
|
|
39
|
+
|
|
40
|
+
### Print mode (one-shot)
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
node dist/index.js "your prompt here"
|
|
44
|
+
node dist/index.js -P "explicit print mode"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Non-TTY
|
|
48
|
+
|
|
49
|
+
When run without a prompt in a non-TTY environment (e.g. piped or from
|
|
50
|
+
an LLM agent), shows usage help instead of launching the TUI.
|
|
51
|
+
|
|
52
|
+
## MCP Servers
|
|
53
|
+
|
|
54
|
+
MCP servers are configured via `mcp.json` files. my-pi spawns each
|
|
55
|
+
server as a child process over stdio and bridges their tools into pi's
|
|
56
|
+
`customTools`.
|
|
57
|
+
|
|
58
|
+
### Global config
|
|
59
|
+
|
|
60
|
+
`~/.pi/agent/mcp.json` — available to all projects:
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"mcpServers": {
|
|
65
|
+
"mcp-sqlite-tools": {
|
|
66
|
+
"command": "npx",
|
|
67
|
+
"args": ["-y", "mcp-sqlite-tools"]
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Project config
|
|
74
|
+
|
|
75
|
+
`./mcp.json` in the project root — overrides global servers by name:
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"mcpServers": {
|
|
80
|
+
"my-search": {
|
|
81
|
+
"command": "npx",
|
|
82
|
+
"args": ["-y", "some-mcp-server"],
|
|
83
|
+
"env": {
|
|
84
|
+
"API_KEY": "..."
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Project servers merge with global servers. If both define the same
|
|
92
|
+
server name, the project config wins.
|
|
93
|
+
|
|
94
|
+
### How it works
|
|
95
|
+
|
|
96
|
+
1. Spawns each MCP server as a child process (stdio transport)
|
|
97
|
+
2. Performs the MCP `initialize` handshake
|
|
98
|
+
3. Calls `tools/list` to discover available tools
|
|
99
|
+
4. Wraps each tool via pi's `defineTool()` as a `customTool`
|
|
100
|
+
5. Tools are available to the model as `mcp__<server>__<tool>`
|
|
101
|
+
|
|
102
|
+
## Project Structure
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
src/
|
|
106
|
+
index.ts CLI entry point (citty + pi SDK)
|
|
107
|
+
mcp/
|
|
108
|
+
client.ts Minimal MCP stdio client (JSON-RPC 2.0)
|
|
109
|
+
bridge.ts Converts MCP tools to pi customTools
|
|
110
|
+
config.ts Loads and merges mcp.json configs
|
|
111
|
+
mcp.json Project MCP server config
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Development
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
pnpm run dev # Watch mode
|
|
118
|
+
pnpm run check # Lint + type check
|
|
119
|
+
pnpm run test # Run tests
|
|
120
|
+
pnpm run build # Production build
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { InteractiveMode, SessionManager, createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, defineTool, getAgentDir, runPrintMode } from "@mariozechner/pi-coding-agent";
|
|
3
|
+
import { defineCommand, runMain } from "citty";
|
|
4
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
import { homedir } from "node:os";
|
|
9
|
+
//#region src/mcp/client.ts
|
|
10
|
+
var McpClient = class {
|
|
11
|
+
#proc = null;
|
|
12
|
+
#config;
|
|
13
|
+
#nextId = 1;
|
|
14
|
+
#pending = /* @__PURE__ */ new Map();
|
|
15
|
+
#buffer = "";
|
|
16
|
+
constructor(config) {
|
|
17
|
+
this.#config = config;
|
|
18
|
+
}
|
|
19
|
+
async connect() {
|
|
20
|
+
const { command, args = [], env } = this.#config;
|
|
21
|
+
this.#proc = spawn(command, args, {
|
|
22
|
+
stdio: [
|
|
23
|
+
"pipe",
|
|
24
|
+
"pipe",
|
|
25
|
+
"pipe"
|
|
26
|
+
],
|
|
27
|
+
env: {
|
|
28
|
+
...process.env,
|
|
29
|
+
...env
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
this.#proc.stdout.setEncoding("utf8");
|
|
33
|
+
this.#proc.stdout.on("data", (chunk) => {
|
|
34
|
+
this.#buffer += chunk;
|
|
35
|
+
const lines = this.#buffer.split("\n");
|
|
36
|
+
this.#buffer = lines.pop() || "";
|
|
37
|
+
for (const line of lines) {
|
|
38
|
+
if (!line.trim()) continue;
|
|
39
|
+
try {
|
|
40
|
+
const msg = JSON.parse(line);
|
|
41
|
+
if (msg.id != null && this.#pending.has(msg.id)) {
|
|
42
|
+
const p = this.#pending.get(msg.id);
|
|
43
|
+
this.#pending.delete(msg.id);
|
|
44
|
+
if (msg.error) p.reject(/* @__PURE__ */ new Error(`MCP error ${msg.error.code}: ${msg.error.message}`));
|
|
45
|
+
else p.resolve(msg.result);
|
|
46
|
+
}
|
|
47
|
+
} catch {}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
await this.#request("initialize", {
|
|
51
|
+
protocolVersion: "2024-11-05",
|
|
52
|
+
capabilities: {},
|
|
53
|
+
clientInfo: {
|
|
54
|
+
name: "my-pi",
|
|
55
|
+
version: "0.0.1"
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
this.#send({
|
|
59
|
+
jsonrpc: "2.0",
|
|
60
|
+
method: "notifications/initialized"
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
async listTools() {
|
|
64
|
+
return (await this.#request("tools/list", {})).tools;
|
|
65
|
+
}
|
|
66
|
+
async callTool(name, args) {
|
|
67
|
+
return this.#request("tools/call", {
|
|
68
|
+
name,
|
|
69
|
+
arguments: args
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async disconnect() {
|
|
73
|
+
if (this.#proc) {
|
|
74
|
+
this.#proc.kill();
|
|
75
|
+
this.#proc = null;
|
|
76
|
+
}
|
|
77
|
+
this.#pending.clear();
|
|
78
|
+
}
|
|
79
|
+
#request(method, params) {
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
const id = this.#nextId++;
|
|
82
|
+
this.#pending.set(id, {
|
|
83
|
+
resolve,
|
|
84
|
+
reject
|
|
85
|
+
});
|
|
86
|
+
this.#send({
|
|
87
|
+
jsonrpc: "2.0",
|
|
88
|
+
id,
|
|
89
|
+
method,
|
|
90
|
+
params
|
|
91
|
+
});
|
|
92
|
+
setTimeout(() => {
|
|
93
|
+
if (this.#pending.has(id)) {
|
|
94
|
+
this.#pending.delete(id);
|
|
95
|
+
reject(/* @__PURE__ */ new Error(`MCP request ${method} timed out`));
|
|
96
|
+
}
|
|
97
|
+
}, 3e4);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
#send(msg) {
|
|
101
|
+
if (!this.#proc?.stdin?.writable) throw new Error("MCP server not connected");
|
|
102
|
+
this.#proc.stdin.write(JSON.stringify(msg) + "\n");
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/mcp/bridge.ts
|
|
107
|
+
async function create_mcp_tools(configs) {
|
|
108
|
+
const clients = [];
|
|
109
|
+
const tools = [];
|
|
110
|
+
for (const config of configs) {
|
|
111
|
+
const client = new McpClient(config);
|
|
112
|
+
await client.connect();
|
|
113
|
+
clients.push(client);
|
|
114
|
+
const mcp_tools = await client.listTools();
|
|
115
|
+
for (const mcp_tool of mcp_tools) {
|
|
116
|
+
const tool_name = `mcp__${config.name}__${mcp_tool.name}`;
|
|
117
|
+
tools.push(defineTool({
|
|
118
|
+
name: tool_name,
|
|
119
|
+
label: `${config.name}: ${mcp_tool.name}`,
|
|
120
|
+
description: mcp_tool.description || mcp_tool.name,
|
|
121
|
+
parameters: mcp_tool.inputSchema || {
|
|
122
|
+
type: "object",
|
|
123
|
+
properties: {}
|
|
124
|
+
},
|
|
125
|
+
execute: async (_toolCallId, params) => {
|
|
126
|
+
const result = await client.callTool(mcp_tool.name, params);
|
|
127
|
+
return {
|
|
128
|
+
content: [{
|
|
129
|
+
type: "text",
|
|
130
|
+
text: result?.content?.map((c) => c.text || "").join("\n") || JSON.stringify(result)
|
|
131
|
+
}],
|
|
132
|
+
details: {}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
tools,
|
|
140
|
+
async cleanup() {
|
|
141
|
+
for (const client of clients) await client.disconnect();
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
//#endregion
|
|
146
|
+
//#region src/mcp/config.ts
|
|
147
|
+
function read_config(path) {
|
|
148
|
+
if (!existsSync(path)) return {};
|
|
149
|
+
const raw = readFileSync(path, "utf-8");
|
|
150
|
+
return JSON.parse(raw).mcpServers || {};
|
|
151
|
+
}
|
|
152
|
+
function load_mcp_config(cwd) {
|
|
153
|
+
const global_servers = read_config(join(homedir(), ".pi", "agent", "mcp.json"));
|
|
154
|
+
const project_servers = read_config(join(cwd, "mcp.json"));
|
|
155
|
+
const merged = {
|
|
156
|
+
...global_servers,
|
|
157
|
+
...project_servers
|
|
158
|
+
};
|
|
159
|
+
return Object.entries(merged).map(([name, server]) => ({
|
|
160
|
+
name,
|
|
161
|
+
command: server.command,
|
|
162
|
+
args: server.args,
|
|
163
|
+
env: server.env
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
//#endregion
|
|
167
|
+
//#region src/index.ts
|
|
168
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
169
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
|
|
170
|
+
runMain(defineCommand({
|
|
171
|
+
meta: {
|
|
172
|
+
name: "my-pi",
|
|
173
|
+
version: pkg.version,
|
|
174
|
+
description: "Personal pi coding agent with MCP tool integration"
|
|
175
|
+
},
|
|
176
|
+
args: {
|
|
177
|
+
print: {
|
|
178
|
+
type: "boolean",
|
|
179
|
+
alias: "P",
|
|
180
|
+
description: "Print mode (non-interactive, one-shot)",
|
|
181
|
+
default: false
|
|
182
|
+
},
|
|
183
|
+
prompt: {
|
|
184
|
+
type: "positional",
|
|
185
|
+
description: "Initial prompt (optional)",
|
|
186
|
+
required: false
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
async run({ args }) {
|
|
190
|
+
const cwd = process.cwd();
|
|
191
|
+
const agentDir = getAgentDir();
|
|
192
|
+
const mcp_configs = load_mcp_config(cwd);
|
|
193
|
+
const mcp = mcp_configs.length > 0 ? await create_mcp_tools(mcp_configs) : null;
|
|
194
|
+
const createRuntime = async ({ cwd: runtime_cwd, sessionManager, sessionStartEvent }) => {
|
|
195
|
+
const services = await createAgentSessionServices({ cwd: runtime_cwd });
|
|
196
|
+
return {
|
|
197
|
+
...await createAgentSessionFromServices({
|
|
198
|
+
services,
|
|
199
|
+
sessionManager,
|
|
200
|
+
sessionStartEvent,
|
|
201
|
+
customTools: mcp?.tools
|
|
202
|
+
}),
|
|
203
|
+
services,
|
|
204
|
+
diagnostics: services.diagnostics
|
|
205
|
+
};
|
|
206
|
+
};
|
|
207
|
+
const runtime = await createAgentSessionRuntime(createRuntime, {
|
|
208
|
+
cwd,
|
|
209
|
+
agentDir,
|
|
210
|
+
sessionManager: SessionManager.create(cwd)
|
|
211
|
+
});
|
|
212
|
+
if (mcp_configs.length > 0) {
|
|
213
|
+
const names = mcp_configs.map((c) => c.name).join(", ");
|
|
214
|
+
console.error(`MCP servers: ${names}`);
|
|
215
|
+
}
|
|
216
|
+
if (args.print || args.prompt) {
|
|
217
|
+
await runPrintMode(runtime, {
|
|
218
|
+
mode: "text",
|
|
219
|
+
initialMessage: args.prompt || "",
|
|
220
|
+
initialImages: [],
|
|
221
|
+
messages: []
|
|
222
|
+
});
|
|
223
|
+
await mcp?.cleanup();
|
|
224
|
+
} else if (!process.stdout.isTTY) {
|
|
225
|
+
console.log(`my-pi v${pkg.version} — pi coding agent with MCP tools\n`);
|
|
226
|
+
console.log("Usage:");
|
|
227
|
+
console.log(" my-pi \"prompt\" One-shot print mode");
|
|
228
|
+
console.log(" my-pi Interactive TUI mode");
|
|
229
|
+
console.log(" my-pi -P \"prompt\" Explicit print mode");
|
|
230
|
+
} else await new InteractiveMode(runtime, {
|
|
231
|
+
migratedProviders: [],
|
|
232
|
+
modelFallbackMessage: void 0,
|
|
233
|
+
initialMessage: void 0,
|
|
234
|
+
initialImages: [],
|
|
235
|
+
initialMessages: []
|
|
236
|
+
}).run();
|
|
237
|
+
}
|
|
238
|
+
}));
|
|
239
|
+
//#endregion
|
|
240
|
+
export {};
|
|
241
|
+
|
|
242
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["#config","#proc","#buffer","#pending","#request","#send","#nextId"],"sources":["../src/mcp/client.ts","../src/mcp/bridge.ts","../src/mcp/config.ts","../src/index.ts"],"sourcesContent":["import { spawn, type ChildProcess } from 'node:child_process';\n\nexport interface McpServerConfig {\n\tname: string;\n\tcommand: string;\n\targs?: string[];\n\tenv?: Record<string, string>;\n}\n\ninterface JsonRpcRequest {\n\tjsonrpc: '2.0';\n\tid: number;\n\tmethod: string;\n\tparams?: unknown;\n}\n\ninterface JsonRpcResponse {\n\tjsonrpc: '2.0';\n\tid: number;\n\tresult?: unknown;\n\terror?: { code: number; message: string };\n}\n\nexport interface McpToolInfo {\n\tname: string;\n\tdescription?: string;\n\tinputSchema?: Record<string, unknown>;\n}\n\nexport class McpClient {\n\t#proc: ChildProcess | null = null;\n\t#config: McpServerConfig;\n\t#nextId = 1;\n\t#pending = new Map<\n\t\tnumber,\n\t\t{\n\t\t\tresolve: (v: unknown) => void;\n\t\t\treject: (e: Error) => void;\n\t\t}\n\t>();\n\t#buffer = '';\n\n\tconstructor(config: McpServerConfig) {\n\t\tthis.#config = config;\n\t}\n\n\tasync connect(): Promise<void> {\n\t\tconst { command, args = [], env } = this.#config;\n\n\t\tthis.#proc = spawn(command, args, {\n\t\t\tstdio: ['pipe', 'pipe', 'pipe'],\n\t\t\tenv: { ...process.env, ...env },\n\t\t});\n\n\t\tthis.#proc.stdout!.setEncoding('utf8');\n\t\tthis.#proc.stdout!.on('data', (chunk: string) => {\n\t\t\tthis.#buffer += chunk;\n\t\t\tconst lines = this.#buffer.split('\\n');\n\t\t\tthis.#buffer = lines.pop() || '';\n\n\t\t\tfor (const line of lines) {\n\t\t\t\tif (!line.trim()) continue;\n\t\t\t\ttry {\n\t\t\t\t\tconst msg = JSON.parse(line) as JsonRpcResponse;\n\t\t\t\t\tif (msg.id != null && this.#pending.has(msg.id)) {\n\t\t\t\t\t\tconst p = this.#pending.get(msg.id)!;\n\t\t\t\t\t\tthis.#pending.delete(msg.id);\n\t\t\t\t\t\tif (msg.error) {\n\t\t\t\t\t\t\tp.reject(\n\t\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t\t`MCP error ${msg.error.code}: ${msg.error.message}`,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tp.resolve(msg.result);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// ignore non-JSON lines\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\t// Initialize handshake\n\t\tawait this.#request('initialize', {\n\t\t\tprotocolVersion: '2024-11-05',\n\t\t\tcapabilities: {},\n\t\t\tclientInfo: { name: 'my-pi', version: '0.0.1' },\n\t\t});\n\n\t\t// Send initialized notification (no response expected)\n\t\tthis.#send({\n\t\t\tjsonrpc: '2.0',\n\t\t\tmethod: 'notifications/initialized',\n\t\t} as unknown as JsonRpcRequest);\n\t}\n\n\tasync listTools(): Promise<McpToolInfo[]> {\n\t\tconst result = (await this.#request('tools/list', {})) as {\n\t\t\ttools: McpToolInfo[];\n\t\t};\n\t\treturn result.tools;\n\t}\n\n\tasync callTool(\n\t\tname: string,\n\t\targs: Record<string, unknown>,\n\t): Promise<unknown> {\n\t\treturn this.#request('tools/call', {\n\t\t\tname,\n\t\t\targuments: args,\n\t\t});\n\t}\n\n\tasync disconnect(): Promise<void> {\n\t\tif (this.#proc) {\n\t\t\tthis.#proc.kill();\n\t\t\tthis.#proc = null;\n\t\t}\n\t\tthis.#pending.clear();\n\t}\n\n\t#request(method: string, params: unknown): Promise<unknown> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst id = this.#nextId++;\n\t\t\tthis.#pending.set(id, { resolve, reject });\n\t\t\tthis.#send({ jsonrpc: '2.0', id, method, params });\n\n\t\t\tsetTimeout(() => {\n\t\t\t\tif (this.#pending.has(id)) {\n\t\t\t\t\tthis.#pending.delete(id);\n\t\t\t\t\treject(new Error(`MCP request ${method} timed out`));\n\t\t\t\t}\n\t\t\t}, 30_000);\n\t\t});\n\t}\n\n\t#send(msg: JsonRpcRequest) {\n\t\tif (!this.#proc?.stdin?.writable) {\n\t\t\tthrow new Error('MCP server not connected');\n\t\t}\n\t\tthis.#proc.stdin.write(JSON.stringify(msg) + '\\n');\n\t}\n}\n","import { defineTool } from '@mariozechner/pi-coding-agent';\nimport { McpClient, type McpServerConfig } from './client.js';\n\nexport async function create_mcp_tools(configs: McpServerConfig[]) {\n\tconst clients: McpClient[] = [];\n\tconst tools: ReturnType<typeof defineTool>[] = [];\n\n\tfor (const config of configs) {\n\t\tconst client = new McpClient(config);\n\t\tawait client.connect();\n\t\tclients.push(client);\n\n\t\tconst mcp_tools = await client.listTools();\n\n\t\tfor (const mcp_tool of mcp_tools) {\n\t\t\tconst tool_name = `mcp__${config.name}__${mcp_tool.name}`;\n\n\t\t\ttools.push(\n\t\t\t\tdefineTool({\n\t\t\t\t\tname: tool_name,\n\t\t\t\t\tlabel: `${config.name}: ${mcp_tool.name}`,\n\t\t\t\t\tdescription: mcp_tool.description || mcp_tool.name,\n\t\t\t\t\tparameters: (mcp_tool.inputSchema || {\n\t\t\t\t\t\ttype: 'object',\n\t\t\t\t\t\tproperties: {},\n\t\t\t\t\t}) as Parameters<typeof defineTool>[0]['parameters'],\n\t\t\t\t\texecute: async (_toolCallId, params) => {\n\t\t\t\t\t\tconst result = (await client.callTool(\n\t\t\t\t\t\t\tmcp_tool.name,\n\t\t\t\t\t\t\tparams as Record<string, unknown>,\n\t\t\t\t\t\t)) as {\n\t\t\t\t\t\t\tcontent?: Array<{\n\t\t\t\t\t\t\t\ttype: string;\n\t\t\t\t\t\t\t\ttext?: string;\n\t\t\t\t\t\t\t}>;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tconst text =\n\t\t\t\t\t\t\tresult?.content?.map((c) => c.text || '').join('\\n') ||\n\t\t\t\t\t\t\tJSON.stringify(result);\n\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [{ type: 'text' as const, text }],\n\t\t\t\t\t\t\tdetails: {},\n\t\t\t\t\t\t};\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t}\n\n\treturn {\n\t\ttools,\n\t\tasync cleanup() {\n\t\t\tfor (const client of clients) {\n\t\t\t\tawait client.disconnect();\n\t\t\t}\n\t\t},\n\t};\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport type { McpServerConfig } from './client.js';\n\ninterface McpConfigFile {\n\tmcpServers: Record<\n\t\tstring,\n\t\t{\n\t\t\tcommand: string;\n\t\t\targs?: string[];\n\t\t\tenv?: Record<string, string>;\n\t\t}\n\t>;\n}\n\nfunction read_config(path: string): McpConfigFile['mcpServers'] {\n\tif (!existsSync(path)) return {};\n\tconst raw = readFileSync(path, 'utf-8');\n\tconst config = JSON.parse(raw) as McpConfigFile;\n\treturn config.mcpServers || {};\n}\n\nexport function load_mcp_config(cwd: string): McpServerConfig[] {\n\t// Global: ~/.pi/agent/mcp.json\n\tconst global_servers = read_config(\n\t\tjoin(homedir(), '.pi', 'agent', 'mcp.json'),\n\t);\n\n\t// Project: ./mcp.json (overrides global by name)\n\tconst project_servers = read_config(join(cwd, 'mcp.json'));\n\n\tconst merged = { ...global_servers, ...project_servers };\n\n\treturn Object.entries(merged).map(([name, server]) => ({\n\t\tname,\n\t\tcommand: server.command,\n\t\targs: server.args,\n\t\tenv: server.env,\n\t}));\n}\n","#!/usr/bin/env node\n\nimport {\n\ttype CreateAgentSessionRuntimeFactory,\n\tcreateAgentSessionFromServices,\n\tcreateAgentSessionRuntime,\n\tcreateAgentSessionServices,\n\tgetAgentDir,\n\tInteractiveMode,\n\trunPrintMode,\n\tSessionManager,\n} from '@mariozechner/pi-coding-agent';\nimport { defineCommand, runMain } from 'citty';\nimport { readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { create_mcp_tools } from './mcp/bridge.js';\nimport { load_mcp_config } from './mcp/config.js';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst pkg = JSON.parse(\n\treadFileSync(join(__dirname, '..', 'package.json'), 'utf-8'),\n);\n\nconst main = defineCommand({\n\tmeta: {\n\t\tname: 'my-pi',\n\t\tversion: pkg.version,\n\t\tdescription: 'Personal pi coding agent with MCP tool integration',\n\t},\n\targs: {\n\t\tprint: {\n\t\t\ttype: 'boolean',\n\t\t\talias: 'P',\n\t\t\tdescription: 'Print mode (non-interactive, one-shot)',\n\t\t\tdefault: false,\n\t\t},\n\t\tprompt: {\n\t\t\ttype: 'positional',\n\t\t\tdescription: 'Initial prompt (optional)',\n\t\t\trequired: false,\n\t\t},\n\t},\n\tasync run({ args }) {\n\t\tconst cwd = process.cwd();\n\t\tconst agentDir = getAgentDir();\n\n\t\t// Load MCP servers from mcp.json\n\t\tconst mcp_configs = load_mcp_config(cwd);\n\t\tconst mcp =\n\t\t\tmcp_configs.length > 0\n\t\t\t\t? await create_mcp_tools(mcp_configs)\n\t\t\t\t: null;\n\n\t\tconst createRuntime: CreateAgentSessionRuntimeFactory = async ({\n\t\t\tcwd: runtime_cwd,\n\t\t\tsessionManager,\n\t\t\tsessionStartEvent,\n\t\t}) => {\n\t\t\tconst services = await createAgentSessionServices({\n\t\t\t\tcwd: runtime_cwd,\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\t...(await createAgentSessionFromServices({\n\t\t\t\t\tservices,\n\t\t\t\t\tsessionManager,\n\t\t\t\t\tsessionStartEvent,\n\t\t\t\t\tcustomTools: mcp?.tools,\n\t\t\t\t})),\n\t\t\t\tservices,\n\t\t\t\tdiagnostics: services.diagnostics,\n\t\t\t};\n\t\t};\n\n\t\tconst runtime = await createAgentSessionRuntime(createRuntime, {\n\t\t\tcwd,\n\t\t\tagentDir,\n\t\t\tsessionManager: SessionManager.create(cwd),\n\t\t});\n\n\t\tif (mcp_configs.length > 0) {\n\t\t\tconst names = mcp_configs.map((c) => c.name).join(', ');\n\t\t\tconsole.error(`MCP servers: ${names}`);\n\t\t}\n\n\t\tif (args.print || args.prompt) {\n\t\t\tawait runPrintMode(runtime, {\n\t\t\t\tmode: 'text',\n\t\t\t\tinitialMessage: args.prompt || '',\n\t\t\t\tinitialImages: [],\n\t\t\t\tmessages: [],\n\t\t\t});\n\t\t\tawait mcp?.cleanup();\n\t\t} else if (!process.stdout.isTTY) {\n\t\t\t// Non-TTY without prompt: show help for LLM agents\n\t\t\tconsole.log(\n\t\t\t\t`my-pi v${pkg.version} — pi coding agent with MCP tools\\n`,\n\t\t\t);\n\t\t\tconsole.log('Usage:');\n\t\t\tconsole.log(' my-pi \"prompt\" One-shot print mode');\n\t\t\tconsole.log(' my-pi Interactive TUI mode');\n\t\t\tconsole.log(' my-pi -P \"prompt\" Explicit print mode');\n\t\t} else {\n\t\t\tconst mode = new InteractiveMode(runtime, {\n\t\t\t\tmigratedProviders: [],\n\t\t\t\tmodelFallbackMessage: undefined,\n\t\t\t\tinitialMessage: undefined,\n\t\t\t\tinitialImages: [],\n\t\t\t\tinitialMessages: [],\n\t\t\t});\n\t\t\tawait mode.run();\n\t\t}\n\t},\n});\n\nvoid runMain(main);\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-pi",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Personal pi coding agent wrapper with MCP tool integration",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cli",
|
|
7
|
+
"coding-agent",
|
|
8
|
+
"mcp",
|
|
9
|
+
"pi"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"author": "Scott Spence <spences10apps@gmail.com>",
|
|
13
|
+
"bin": {
|
|
14
|
+
"my-pi": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"main": "./dist/index.js",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@mariozechner/pi-ai": "^0.66.1",
|
|
24
|
+
"@mariozechner/pi-coding-agent": "^0.66.1",
|
|
25
|
+
"@tmcp/adapter-valibot": "^0.1.5",
|
|
26
|
+
"@tmcp/transport-stdio": "^0.4.2",
|
|
27
|
+
"citty": "^0.2.2",
|
|
28
|
+
"tmcp": "^1.19.3",
|
|
29
|
+
"valibot": "^1.3.1"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@changesets/cli": "^2.30.0",
|
|
33
|
+
"@types/node": "^25.6.0",
|
|
34
|
+
"vite-plus": "^0.1.16",
|
|
35
|
+
"vitest": "^4.1.4"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=22.0.0"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "vp pack",
|
|
42
|
+
"dev": "vp pack --watch",
|
|
43
|
+
"start": "node ./dist/index.js",
|
|
44
|
+
"check": "vp check",
|
|
45
|
+
"check:fix": "vp check --fix",
|
|
46
|
+
"format": "vp check --fix",
|
|
47
|
+
"test": "vp test",
|
|
48
|
+
"test:watch": "vp test watch",
|
|
49
|
+
"changeset": "changeset",
|
|
50
|
+
"version": "changeset version",
|
|
51
|
+
"release": "pnpm run build && changeset publish"
|
|
52
|
+
}
|
|
53
|
+
}
|