my-pi 0.0.1 → 0.0.2
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 +145 -23
- package/dist/api.js +50 -0
- package/dist/api.js.map +1 -0
- package/dist/index.js +88 -196
- package/dist/index.js.map +1 -1
- package/package.json +11 -7
- package/src/extensions/chain.test.ts +194 -0
- package/src/extensions/chain.ts +458 -0
- package/src/extensions/filter-output.test.ts +161 -0
- package/src/extensions/filter-output.ts +125 -0
- package/src/extensions/handoff.ts +87 -0
- package/src/extensions/mcp.ts +173 -0
- package/src/extensions/recall.ts +251 -0
- package/src/extensions/skills.ts +143 -0
- package/src/mcp/client.ts +144 -0
- package/src/mcp/config.test.ts +153 -0
- package/src/mcp/config.ts +41 -0
- package/src/skills/config.test.ts +58 -0
- package/src/skills/config.ts +70 -0
- package/src/skills/manager.ts +123 -0
- package/src/skills/scanner.ts +175 -0
package/README.md
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# my-pi
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
integration.
|
|
3
|
+
Composable [pi](https://pi.dev) coding agent for humans and agents.
|
|
5
4
|
|
|
6
5
|
Built on the
|
|
7
6
|
[@mariozechner/pi-coding-agent](https://github.com/badlogic/pi-mono)
|
|
8
|
-
SDK. Adds MCP server support
|
|
9
|
-
|
|
7
|
+
SDK. Adds MCP server support, extension stacking, JSON output for
|
|
8
|
+
agent consumption, and a programmatic API.
|
|
9
|
+
|
|
10
|
+
Extension stacking patterns inspired by
|
|
11
|
+
[pi-vs-claude-code](https://github.com/disler/pi-vs-claude-code).
|
|
10
12
|
|
|
11
13
|
## Setup
|
|
12
14
|
|
|
@@ -31,7 +33,7 @@ priority order):
|
|
|
31
33
|
### Interactive mode (full TUI)
|
|
32
34
|
|
|
33
35
|
```bash
|
|
34
|
-
|
|
36
|
+
my-pi
|
|
35
37
|
```
|
|
36
38
|
|
|
37
39
|
Pi's full terminal UI with editor, `/commands`, model switching
|
|
@@ -40,20 +42,62 @@ Pi's full terminal UI with editor, `/commands`, model switching
|
|
|
40
42
|
### Print mode (one-shot)
|
|
41
43
|
|
|
42
44
|
```bash
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
my-pi "your prompt here"
|
|
46
|
+
my-pi -P "explicit print mode"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### JSON output (for agents)
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
my-pi --json "list all TODO comments"
|
|
53
|
+
echo "plan a login page" | my-pi --json
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Outputs NDJSON events — one JSON object per line — for programmatic
|
|
57
|
+
consumption by other agents or scripts.
|
|
58
|
+
|
|
59
|
+
### Extension stacking
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
my-pi -e ./ext/damage-control.ts -e ./ext/tool-counter.ts
|
|
63
|
+
my-pi --no-builtin -e ./ext/custom.ts "do something"
|
|
45
64
|
```
|
|
46
65
|
|
|
47
|
-
|
|
66
|
+
Stack arbitrary Pi extensions via `-e`. Use `--no-builtin` to skip the
|
|
67
|
+
built-in MCP and skills extensions.
|
|
68
|
+
|
|
69
|
+
### Stdin piping
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
echo "review this code" | my-pi
|
|
73
|
+
cat plan.md | my-pi --json
|
|
74
|
+
```
|
|
48
75
|
|
|
49
|
-
When
|
|
50
|
-
|
|
76
|
+
When stdin is piped, it's read as the prompt and print mode runs
|
|
77
|
+
automatically.
|
|
78
|
+
|
|
79
|
+
### Programmatic API
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { createMyPi, runPrintMode } from 'my-pi';
|
|
83
|
+
|
|
84
|
+
const runtime = await createMyPi({
|
|
85
|
+
extensions: ['./my-ext.ts'],
|
|
86
|
+
builtins: true,
|
|
87
|
+
});
|
|
88
|
+
await runPrintMode(runtime, {
|
|
89
|
+
mode: 'json',
|
|
90
|
+
initialMessage: 'hello',
|
|
91
|
+
initialImages: [],
|
|
92
|
+
messages: [],
|
|
93
|
+
});
|
|
94
|
+
```
|
|
51
95
|
|
|
52
96
|
## MCP Servers
|
|
53
97
|
|
|
54
|
-
MCP servers are configured via `mcp.json` files
|
|
55
|
-
|
|
56
|
-
`
|
|
98
|
+
MCP servers are configured via `mcp.json` files and managed as a pi
|
|
99
|
+
extension. Servers are spawned on startup and their tools registered
|
|
100
|
+
via `pi.registerTool()`.
|
|
57
101
|
|
|
58
102
|
### Global config
|
|
59
103
|
|
|
@@ -91,24 +135,102 @@ server as a child process over stdio and bridges their tools into pi's
|
|
|
91
135
|
Project servers merge with global servers. If both define the same
|
|
92
136
|
server name, the project config wins.
|
|
93
137
|
|
|
138
|
+
### Commands
|
|
139
|
+
|
|
140
|
+
In interactive mode:
|
|
141
|
+
|
|
142
|
+
- `/mcp list` — show connected servers and tool counts
|
|
143
|
+
- `/mcp enable <server>` — enable a disabled server's tools
|
|
144
|
+
- `/mcp disable <server>` — disable a server's tools
|
|
145
|
+
- `/skills list` — show loaded commands
|
|
146
|
+
- `/skills tools` — show all registered tools
|
|
147
|
+
|
|
94
148
|
### How it works
|
|
95
149
|
|
|
96
|
-
1.
|
|
97
|
-
2.
|
|
98
|
-
3.
|
|
99
|
-
4.
|
|
100
|
-
5.
|
|
150
|
+
1. Pi extension loads `mcp.json` configs (global + project)
|
|
151
|
+
2. Spawns each MCP server as a child process (stdio transport)
|
|
152
|
+
3. Performs the MCP `initialize` handshake
|
|
153
|
+
4. Calls `tools/list` to discover available tools
|
|
154
|
+
5. Registers each tool via `pi.registerTool()` as
|
|
155
|
+
`mcp__<server>__<tool>`
|
|
156
|
+
6. `/mcp enable/disable` toggles tools via `pi.setActiveTools()`
|
|
157
|
+
7. Cleanup on `session_shutdown`
|
|
158
|
+
|
|
159
|
+
## Agent Chains
|
|
160
|
+
|
|
161
|
+
Define sequential agent pipelines in `.pi/agents/agent-chain.yaml`:
|
|
162
|
+
|
|
163
|
+
```yaml
|
|
164
|
+
scout-plan:
|
|
165
|
+
description: 'Scout the codebase then plan implementation'
|
|
166
|
+
steps:
|
|
167
|
+
- agent: scout
|
|
168
|
+
prompt: 'Explore and analyze: $INPUT'
|
|
169
|
+
- agent: planner
|
|
170
|
+
prompt: 'Based on this analysis, create a plan:\n\n$INPUT'
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Agent definitions live in `.pi/agents/*.md` with frontmatter:
|
|
174
|
+
|
|
175
|
+
```markdown
|
|
176
|
+
---
|
|
177
|
+
name: scout
|
|
178
|
+
description: Codebase exploration and analysis
|
|
179
|
+
tools: read,grep,find,ls
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
You are a scout agent. Explore the codebase and report findings.
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The chain extension injects context into the system prompt so the LLM
|
|
186
|
+
knows when and how to use `run_chain`. Use `/chain` to switch active
|
|
187
|
+
chains and `/agents` to list available agents.
|
|
188
|
+
|
|
189
|
+
## Secret Redaction
|
|
190
|
+
|
|
191
|
+
The filter-output extension automatically redacts secrets (API keys,
|
|
192
|
+
tokens, passwords, private keys) from tool output before the LLM sees
|
|
193
|
+
them. Detection patterns from
|
|
194
|
+
[nopeek](https://github.com/spences10/nopeek).
|
|
195
|
+
|
|
196
|
+
Use `/redact-stats` to see how many secrets were caught. Disable with
|
|
197
|
+
`--no-filter`.
|
|
198
|
+
|
|
199
|
+
## Session Handoff
|
|
200
|
+
|
|
201
|
+
Use `/handoff <task>` to export conversation context as a markdown
|
|
202
|
+
file that can be piped into a new session:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
# In session 1: /handoff continue the auth refactor
|
|
206
|
+
# Then:
|
|
207
|
+
my-pi < handoff-1234567890.md
|
|
208
|
+
```
|
|
101
209
|
|
|
102
210
|
## Project Structure
|
|
103
211
|
|
|
104
212
|
```
|
|
105
213
|
src/
|
|
106
|
-
index.ts
|
|
214
|
+
index.ts CLI entry point (citty + pi SDK)
|
|
215
|
+
api.ts Programmatic API (create_my_pi + re-exports)
|
|
216
|
+
extensions/
|
|
217
|
+
mcp.ts MCP server integration
|
|
218
|
+
skills.ts Skill discovery and toggle
|
|
219
|
+
chain.ts Agent chain pipelines
|
|
220
|
+
filter-output.ts Secret redaction in tool output
|
|
221
|
+
handoff.ts Session context export
|
|
107
222
|
mcp/
|
|
108
|
-
client.ts
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
223
|
+
client.ts Minimal MCP stdio client (JSON-RPC 2.0)
|
|
224
|
+
config.ts Loads and merges mcp.json configs
|
|
225
|
+
skills/
|
|
226
|
+
manager.ts Skill enable/disable state management
|
|
227
|
+
scanner.ts Skill discovery across sources
|
|
228
|
+
config.ts Persistent skills config (~/.config/my-pi/)
|
|
229
|
+
.pi/
|
|
230
|
+
agents/
|
|
231
|
+
*.md Agent definitions (frontmatter + system prompt)
|
|
232
|
+
agent-chain.yaml Chain pipeline definitions
|
|
233
|
+
mcp.json Project MCP server config
|
|
112
234
|
```
|
|
113
235
|
|
|
114
236
|
## Development
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { InteractiveMode, SessionManager, SettingsManager, createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, getAgentDir, runPrintMode } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
//#region src/api.ts
|
|
5
|
+
const ext_dir = resolve(dirname(fileURLToPath(import.meta.url)), "..", "src", "extensions");
|
|
6
|
+
async function create_my_pi(options = {}) {
|
|
7
|
+
const { cwd = process.cwd(), extensions = [], extensionFactories: user_factories = [], mcp = true, skills = true, chain = true, filter_output = true, handoff = true, recall = true, model } = options;
|
|
8
|
+
const resolved_extensions = extensions.map((p) => resolve(cwd, p));
|
|
9
|
+
const builtin_extension_paths = [
|
|
10
|
+
...mcp ? [resolve(ext_dir, "mcp.ts")] : [],
|
|
11
|
+
...skills ? [resolve(ext_dir, "skills.ts")] : [],
|
|
12
|
+
...chain ? [resolve(ext_dir, "chain.ts")] : [],
|
|
13
|
+
...filter_output ? [resolve(ext_dir, "filter-output.ts")] : [],
|
|
14
|
+
...handoff ? [resolve(ext_dir, "handoff.ts")] : [],
|
|
15
|
+
...recall ? [resolve(ext_dir, "recall.ts")] : []
|
|
16
|
+
];
|
|
17
|
+
const create_runtime = async ({ cwd: runtime_cwd, sessionManager, sessionStartEvent }) => {
|
|
18
|
+
const settings_manager = model ? (() => {
|
|
19
|
+
const sm = SettingsManager.create(runtime_cwd);
|
|
20
|
+
sm.setDefaultModel(model);
|
|
21
|
+
return sm;
|
|
22
|
+
})() : void 0;
|
|
23
|
+
const services = await createAgentSessionServices({
|
|
24
|
+
cwd: runtime_cwd,
|
|
25
|
+
...settings_manager && { settingsManager: settings_manager },
|
|
26
|
+
resourceLoaderOptions: {
|
|
27
|
+
additionalExtensionPaths: [...builtin_extension_paths, ...resolved_extensions],
|
|
28
|
+
extensionFactories: [...user_factories]
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
...await createAgentSessionFromServices({
|
|
33
|
+
services,
|
|
34
|
+
sessionManager,
|
|
35
|
+
sessionStartEvent
|
|
36
|
+
}),
|
|
37
|
+
services,
|
|
38
|
+
diagnostics: services.diagnostics
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
return createAgentSessionRuntime(create_runtime, {
|
|
42
|
+
cwd,
|
|
43
|
+
agentDir: getAgentDir(),
|
|
44
|
+
sessionManager: SessionManager.create(cwd)
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { InteractiveMode, create_my_pi, runPrintMode };
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","names":[],"sources":["../src/api.ts"],"sourcesContent":["// Composable programmatic API for my-pi\n// Extension loading patterns inspired by https://github.com/disler/pi-vs-claude-code\n\nimport {\n\ttype AgentSessionRuntime,\n\tcreateAgentSessionFromServices,\n\tcreateAgentSessionRuntime,\n\ttype CreateAgentSessionRuntimeFactory,\n\tcreateAgentSessionServices,\n\ttype ExtensionFactory,\n\tgetAgentDir,\n\tSessionManager,\n\tSettingsManager,\n} from '@mariozechner/pi-coding-agent';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst ext_dir = resolve(__dirname, '..', 'src', 'extensions');\n\nexport interface CreateMyPiOptions {\n\tcwd?: string;\n\textensions?: string[];\n\textensionFactories?: ExtensionFactory[];\n\t/** Enable MCP extension (default true) */\n\tmcp?: boolean;\n\t/** Enable skills extension (default true) */\n\tskills?: boolean;\n\t/** Enable chain extension (default true) */\n\tchain?: boolean;\n\t/** Enable filter-output extension for secret redaction (default true) */\n\tfilter_output?: boolean;\n\t/** Enable handoff extension (default true) */\n\thandoff?: boolean;\n\t/** Enable recall extension for searching past sessions (default true) */\n\trecall?: boolean;\n\t/** Override the default model (e.g. \"claude-sonnet-4-5-20241022\") */\n\tmodel?: string;\n}\n\nexport async function create_my_pi(\n\toptions: CreateMyPiOptions = {},\n): Promise<AgentSessionRuntime> {\n\tconst {\n\t\tcwd = process.cwd(),\n\t\textensions = [],\n\t\textensionFactories: user_factories = [],\n\t\tmcp = true,\n\t\tskills = true,\n\t\tchain = true,\n\t\tfilter_output = true,\n\t\thandoff = true,\n\t\trecall = true,\n\t\tmodel,\n\t} = options;\n\n\tconst resolved_extensions = extensions.map((p) => resolve(cwd, p));\n\n\t// All built-in extensions loaded by path so Pi shows filenames\n\tconst builtin_extension_paths: string[] = [\n\t\t...(mcp ? [resolve(ext_dir, 'mcp.ts')] : []),\n\t\t...(skills ? [resolve(ext_dir, 'skills.ts')] : []),\n\t\t...(chain ? [resolve(ext_dir, 'chain.ts')] : []),\n\t\t...(filter_output ? [resolve(ext_dir, 'filter-output.ts')] : []),\n\t\t...(handoff ? [resolve(ext_dir, 'handoff.ts')] : []),\n\t\t...(recall ? [resolve(ext_dir, 'recall.ts')] : []),\n\t];\n\n\tconst create_runtime: CreateAgentSessionRuntimeFactory = async ({\n\t\tcwd: runtime_cwd,\n\t\tsessionManager,\n\t\tsessionStartEvent,\n\t}) => {\n\t\tconst settings_manager = model\n\t\t\t? (() => {\n\t\t\t\t\tconst sm = SettingsManager.create(runtime_cwd);\n\t\t\t\t\tsm.setDefaultModel(model);\n\t\t\t\t\treturn sm;\n\t\t\t\t})()\n\t\t\t: undefined;\n\n\t\tconst services = await createAgentSessionServices({\n\t\t\tcwd: runtime_cwd,\n\t\t\t...(settings_manager && { settingsManager: settings_manager }),\n\t\t\tresourceLoaderOptions: {\n\t\t\t\tadditionalExtensionPaths: [\n\t\t\t\t\t...builtin_extension_paths,\n\t\t\t\t\t...resolved_extensions,\n\t\t\t\t],\n\t\t\t\textensionFactories: [...user_factories],\n\t\t\t},\n\t\t});\n\n\t\treturn {\n\t\t\t...(await createAgentSessionFromServices({\n\t\t\t\tservices,\n\t\t\t\tsessionManager,\n\t\t\t\tsessionStartEvent,\n\t\t\t})),\n\t\t\tservices,\n\t\t\tdiagnostics: services.diagnostics,\n\t\t};\n\t};\n\n\treturn createAgentSessionRuntime(create_runtime, {\n\t\tcwd,\n\t\tagentDir: getAgentDir(),\n\t\tsessionManager: SessionManager.create(cwd),\n\t});\n}\n\nexport {\n\tInteractiveMode,\n\trunPrintMode,\n} from '@mariozechner/pi-coding-agent';\n\nexport type {\n\tAgentSessionRuntime,\n\tExtensionFactory,\n\tInteractiveModeOptions,\n\tPrintModeOptions,\n} from '@mariozechner/pi-coding-agent';\n"],"mappings":";;;;AAkBA,MAAM,UAAU,QADE,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC,EACtB,MAAM,OAAO,aAAa;AAsB7D,eAAsB,aACrB,UAA6B,EAAE,EACA;CAC/B,MAAM,EACL,MAAM,QAAQ,KAAK,EACnB,aAAa,EAAE,EACf,oBAAoB,iBAAiB,EAAE,EACvC,MAAM,MACN,SAAS,MACT,QAAQ,MACR,gBAAgB,MAChB,UAAU,MACV,SAAS,MACT,UACG;CAEJ,MAAM,sBAAsB,WAAW,KAAK,MAAM,QAAQ,KAAK,EAAE,CAAC;CAGlE,MAAM,0BAAoC;EACzC,GAAI,MAAM,CAAC,QAAQ,SAAS,SAAS,CAAC,GAAG,EAAE;EAC3C,GAAI,SAAS,CAAC,QAAQ,SAAS,YAAY,CAAC,GAAG,EAAE;EACjD,GAAI,QAAQ,CAAC,QAAQ,SAAS,WAAW,CAAC,GAAG,EAAE;EAC/C,GAAI,gBAAgB,CAAC,QAAQ,SAAS,mBAAmB,CAAC,GAAG,EAAE;EAC/D,GAAI,UAAU,CAAC,QAAQ,SAAS,aAAa,CAAC,GAAG,EAAE;EACnD,GAAI,SAAS,CAAC,QAAQ,SAAS,YAAY,CAAC,GAAG,EAAE;EACjD;CAED,MAAM,iBAAmD,OAAO,EAC/D,KAAK,aACL,gBACA,wBACK;EACL,MAAM,mBAAmB,eACf;GACP,MAAM,KAAK,gBAAgB,OAAO,YAAY;AAC9C,MAAG,gBAAgB,MAAM;AACzB,UAAO;MACJ,GACH,KAAA;EAEH,MAAM,WAAW,MAAM,2BAA2B;GACjD,KAAK;GACL,GAAI,oBAAoB,EAAE,iBAAiB,kBAAkB;GAC7D,uBAAuB;IACtB,0BAA0B,CACzB,GAAG,yBACH,GAAG,oBACH;IACD,oBAAoB,CAAC,GAAG,eAAe;IACvC;GACD,CAAC;AAEF,SAAO;GACN,GAAI,MAAM,+BAA+B;IACxC;IACA;IACA;IACA,CAAC;GACF;GACA,aAAa,SAAS;GACtB;;AAGF,QAAO,0BAA0B,gBAAgB;EAChD;EACA,UAAU,aAAa;EACvB,gBAAgB,eAAe,OAAO,IAAI;EAC1C,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,177 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { create_my_pi } from "./api.js";
|
|
3
|
+
import { InteractiveMode, runPrintMode } from "@mariozechner/pi-coding-agent";
|
|
3
4
|
import { defineCommand, runMain } from "citty";
|
|
4
|
-
import {
|
|
5
|
-
import { dirname, join } from "node:path";
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import { dirname, join, resolve } from "node:path";
|
|
6
7
|
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
8
|
//#region src/index.ts
|
|
168
9
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
169
10
|
const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
|
|
11
|
+
function parse_extension_paths(argv) {
|
|
12
|
+
const paths = [];
|
|
13
|
+
for (let i = 0; i < argv.length; i++) if ((argv[i] === "-e" || argv[i] === "--extension") && i + 1 < argv.length) paths.push(resolve(argv[++i]));
|
|
14
|
+
return paths;
|
|
15
|
+
}
|
|
16
|
+
async function read_stdin() {
|
|
17
|
+
const chunks = [];
|
|
18
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
19
|
+
return Buffer.concat(chunks).toString("utf-8").trim();
|
|
20
|
+
}
|
|
170
21
|
runMain(defineCommand({
|
|
171
22
|
meta: {
|
|
172
23
|
name: "my-pi",
|
|
173
24
|
version: pkg.version,
|
|
174
|
-
description: "
|
|
25
|
+
description: "Composable pi coding agent with MCP tools and extension stacking"
|
|
175
26
|
},
|
|
176
27
|
args: {
|
|
177
28
|
print: {
|
|
@@ -180,6 +31,52 @@ runMain(defineCommand({
|
|
|
180
31
|
description: "Print mode (non-interactive, one-shot)",
|
|
181
32
|
default: false
|
|
182
33
|
},
|
|
34
|
+
json: {
|
|
35
|
+
type: "boolean",
|
|
36
|
+
alias: "j",
|
|
37
|
+
description: "Output NDJSON events (for agent consumption)",
|
|
38
|
+
default: false
|
|
39
|
+
},
|
|
40
|
+
"no-builtin": {
|
|
41
|
+
type: "boolean",
|
|
42
|
+
description: "Disable all built-in extensions",
|
|
43
|
+
default: false
|
|
44
|
+
},
|
|
45
|
+
"no-mcp": {
|
|
46
|
+
type: "boolean",
|
|
47
|
+
description: "Disable built-in MCP extension",
|
|
48
|
+
default: false
|
|
49
|
+
},
|
|
50
|
+
"no-skills": {
|
|
51
|
+
type: "boolean",
|
|
52
|
+
description: "Disable built-in skills extension",
|
|
53
|
+
default: false
|
|
54
|
+
},
|
|
55
|
+
"no-chain": {
|
|
56
|
+
type: "boolean",
|
|
57
|
+
description: "Disable built-in chain extension",
|
|
58
|
+
default: false
|
|
59
|
+
},
|
|
60
|
+
"no-filter": {
|
|
61
|
+
type: "boolean",
|
|
62
|
+
description: "Disable secret redaction in tool output",
|
|
63
|
+
default: false
|
|
64
|
+
},
|
|
65
|
+
"no-handoff": {
|
|
66
|
+
type: "boolean",
|
|
67
|
+
description: "Disable handoff extension",
|
|
68
|
+
default: false
|
|
69
|
+
},
|
|
70
|
+
"no-recall": {
|
|
71
|
+
type: "boolean",
|
|
72
|
+
description: "Disable recall extension",
|
|
73
|
+
default: false
|
|
74
|
+
},
|
|
75
|
+
model: {
|
|
76
|
+
type: "string",
|
|
77
|
+
alias: "m",
|
|
78
|
+
description: "Model to use (e.g. claude-sonnet-4-5-20241022)"
|
|
79
|
+
},
|
|
183
80
|
prompt: {
|
|
184
81
|
type: "positional",
|
|
185
82
|
description: "Initial prompt (optional)",
|
|
@@ -188,45 +85,40 @@ runMain(defineCommand({
|
|
|
188
85
|
},
|
|
189
86
|
async run({ args }) {
|
|
190
87
|
const cwd = process.cwd();
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const
|
|
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, {
|
|
88
|
+
const extension_paths = parse_extension_paths(process.argv);
|
|
89
|
+
let prompt = args.prompt;
|
|
90
|
+
if (!prompt && !process.stdin.isTTY) prompt = await read_stdin();
|
|
91
|
+
const runtime = await create_my_pi({
|
|
208
92
|
cwd,
|
|
209
|
-
|
|
210
|
-
|
|
93
|
+
extensions: extension_paths,
|
|
94
|
+
mcp: !args["no-builtin"] && !args["no-mcp"],
|
|
95
|
+
skills: !args["no-builtin"] && !args["no-skills"],
|
|
96
|
+
chain: !args["no-builtin"] && !args["no-chain"],
|
|
97
|
+
filter_output: !args["no-builtin"] && !args["no-filter"],
|
|
98
|
+
handoff: !args["no-builtin"] && !args["no-handoff"],
|
|
99
|
+
recall: !args["no-builtin"] && !args["no-recall"],
|
|
100
|
+
model: args.model
|
|
211
101
|
});
|
|
212
|
-
if (
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if (args.print || args.prompt) {
|
|
217
|
-
await runPrintMode(runtime, {
|
|
218
|
-
mode: "text",
|
|
219
|
-
initialMessage: args.prompt || "",
|
|
102
|
+
if (args.print || args.json || prompt) {
|
|
103
|
+
const code = await runPrintMode(runtime, {
|
|
104
|
+
mode: args.json ? "json" : "text",
|
|
105
|
+
initialMessage: prompt || "",
|
|
220
106
|
initialImages: [],
|
|
221
107
|
messages: []
|
|
222
108
|
});
|
|
223
|
-
|
|
109
|
+
process.exit(code);
|
|
224
110
|
} else if (!process.stdout.isTTY) {
|
|
225
|
-
console.log(`my-pi v${pkg.version} — pi coding agent
|
|
111
|
+
console.log(`my-pi v${pkg.version} — composable pi coding agent\n`);
|
|
226
112
|
console.log("Usage:");
|
|
227
|
-
console.log(" my-pi \"prompt\"
|
|
228
|
-
console.log(" my-pi
|
|
229
|
-
console.log(" my-pi -P \"prompt\"
|
|
113
|
+
console.log(" my-pi \"prompt\" One-shot print mode");
|
|
114
|
+
console.log(" my-pi Interactive TUI mode");
|
|
115
|
+
console.log(" my-pi -P \"prompt\" Explicit print mode");
|
|
116
|
+
console.log(" my-pi --json \"prompt\" NDJSON output for agents");
|
|
117
|
+
console.log(" my-pi -e ext.ts Stack an extension");
|
|
118
|
+
console.log(" my-pi -e a.ts -e b.ts Stack multiple extensions");
|
|
119
|
+
console.log(" echo \"prompt\" | my-pi --json Pipe stdin as prompt");
|
|
120
|
+
console.log(" my-pi -m claude-haiku-4-5-20241022 Set initial model");
|
|
121
|
+
console.log(" my-pi --no-builtin -e ext.ts Skip mcp+skills builtins");
|
|
230
122
|
} else await new InteractiveMode(runtime, {
|
|
231
123
|
migratedProviders: [],
|
|
232
124
|
modelFallbackMessage: void 0,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\n// CLI for my-pi — composable pi coding agent\n// Extension stacking patterns inspired by https://github.com/disler/pi-vs-claude-code\n\nimport {\n\tInteractiveMode,\n\trunPrintMode,\n} from '@mariozechner/pi-coding-agent';\nimport { defineCommand, runMain } from 'citty';\nimport { readFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { create_my_pi } from './api.js';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst pkg = JSON.parse(\n\treadFileSync(join(__dirname, '..', 'package.json'), 'utf-8'),\n);\n\n// citty can't handle repeatable args, so parse -e from argv directly\n// (citty uses strict: false, so unknown flags are silently ignored)\nfunction parse_extension_paths(argv: string[]): string[] {\n\tconst paths: string[] = [];\n\tfor (let i = 0; i < argv.length; i++) {\n\t\tif (\n\t\t\t(argv[i] === '-e' || argv[i] === '--extension') &&\n\t\t\ti + 1 < argv.length\n\t\t) {\n\t\t\tpaths.push(resolve(argv[++i]));\n\t\t}\n\t}\n\treturn paths;\n}\n\nasync function read_stdin(): Promise<string> {\n\tconst chunks: Buffer[] = [];\n\tfor await (const chunk of process.stdin) {\n\t\tchunks.push(chunk as Buffer);\n\t}\n\treturn Buffer.concat(chunks).toString('utf-8').trim();\n}\n\nconst main = defineCommand({\n\tmeta: {\n\t\tname: 'my-pi',\n\t\tversion: pkg.version,\n\t\tdescription:\n\t\t\t'Composable pi coding agent with MCP tools and extension stacking',\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\tjson: {\n\t\t\ttype: 'boolean',\n\t\t\talias: 'j',\n\t\t\tdescription: 'Output NDJSON events (for agent consumption)',\n\t\t\tdefault: false,\n\t\t},\n\t\t'no-builtin': {\n\t\t\ttype: 'boolean',\n\t\t\tdescription: 'Disable all built-in extensions',\n\t\t\tdefault: false,\n\t\t},\n\t\t'no-mcp': {\n\t\t\ttype: 'boolean',\n\t\t\tdescription: 'Disable built-in MCP extension',\n\t\t\tdefault: false,\n\t\t},\n\t\t'no-skills': {\n\t\t\ttype: 'boolean',\n\t\t\tdescription: 'Disable built-in skills extension',\n\t\t\tdefault: false,\n\t\t},\n\t\t'no-chain': {\n\t\t\ttype: 'boolean',\n\t\t\tdescription: 'Disable built-in chain extension',\n\t\t\tdefault: false,\n\t\t},\n\t\t'no-filter': {\n\t\t\ttype: 'boolean',\n\t\t\tdescription: 'Disable secret redaction in tool output',\n\t\t\tdefault: false,\n\t\t},\n\t\t'no-handoff': {\n\t\t\ttype: 'boolean',\n\t\t\tdescription: 'Disable handoff extension',\n\t\t\tdefault: false,\n\t\t},\n\t\t'no-recall': {\n\t\t\ttype: 'boolean',\n\t\t\tdescription: 'Disable recall extension',\n\t\t\tdefault: false,\n\t\t},\n\t\tmodel: {\n\t\t\ttype: 'string',\n\t\t\talias: 'm',\n\t\t\tdescription: 'Model to use (e.g. claude-sonnet-4-5-20241022)',\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 extension_paths = parse_extension_paths(process.argv);\n\n\t\t// Stdin piping: read all stdin as prompt when piped\n\t\tlet prompt = args.prompt;\n\t\tif (!prompt && !process.stdin.isTTY) {\n\t\t\tprompt = await read_stdin();\n\t\t}\n\n\t\tconst runtime = await create_my_pi({\n\t\t\tcwd,\n\t\t\textensions: extension_paths,\n\t\t\tmcp: !args['no-builtin'] && !args['no-mcp'],\n\t\t\tskills: !args['no-builtin'] && !args['no-skills'],\n\t\t\tchain: !args['no-builtin'] && !args['no-chain'],\n\t\t\tfilter_output: !args['no-builtin'] && !args['no-filter'],\n\t\t\thandoff: !args['no-builtin'] && !args['no-handoff'],\n\t\t\trecall: !args['no-builtin'] && !args['no-recall'],\n\t\t\tmodel: args.model,\n\t\t});\n\n\t\tif (args.print || args.json || prompt) {\n\t\t\tconst code = await runPrintMode(runtime, {\n\t\t\t\tmode: args.json ? 'json' : 'text',\n\t\t\t\tinitialMessage: prompt || '',\n\t\t\t\tinitialImages: [],\n\t\t\t\tmessages: [],\n\t\t\t});\n\t\t\tprocess.exit(code);\n\t\t} else if (!process.stdout.isTTY) {\n\t\t\tconsole.log(\n\t\t\t\t`my-pi v${pkg.version} — composable pi coding agent\\n`,\n\t\t\t);\n\t\t\tconsole.log('Usage:');\n\t\t\tconsole.log(\n\t\t\t\t' my-pi \"prompt\" One-shot print mode',\n\t\t\t);\n\t\t\tconsole.log(\n\t\t\t\t' my-pi Interactive TUI mode',\n\t\t\t);\n\t\t\tconsole.log(\n\t\t\t\t' my-pi -P \"prompt\" Explicit print mode',\n\t\t\t);\n\t\t\tconsole.log(\n\t\t\t\t' my-pi --json \"prompt\" NDJSON output for agents',\n\t\t\t);\n\t\t\tconsole.log(\n\t\t\t\t' my-pi -e ext.ts Stack an extension',\n\t\t\t);\n\t\t\tconsole.log(\n\t\t\t\t' my-pi -e a.ts -e b.ts Stack multiple extensions',\n\t\t\t);\n\t\t\tconsole.log(\n\t\t\t\t' echo \"prompt\" | my-pi --json Pipe stdin as prompt',\n\t\t\t);\n\t\t\tconsole.log(\n\t\t\t\t' my-pi -m claude-haiku-4-5-20241022 Set initial model',\n\t\t\t);\n\t\t\tconsole.log(\n\t\t\t\t' my-pi --no-builtin -e ext.ts Skip mcp+skills builtins',\n\t\t\t);\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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"}
|