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 CHANGED
@@ -1,12 +1,14 @@
1
1
  # my-pi
2
2
 
3
- Personal [pi](https://pi.dev) coding agent wrapper with MCP tool
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 so models without built-in web search
9
- (like Mistral) can still use external tools.
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
- node dist/index.js
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
- node dist/index.js "your prompt here"
44
- node dist/index.js -P "explicit print mode"
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
- ### Non-TTY
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 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.
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. my-pi spawns each
55
- server as a child process over stdio and bridges their tools into pi's
56
- `customTools`.
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. 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>`
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 CLI entry point (citty + pi SDK)
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 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
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
@@ -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 { InteractiveMode, SessionManager, createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, defineTool, getAgentDir, runPrintMode } from "@mariozechner/pi-coding-agent";
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 { existsSync, readFileSync } from "node:fs";
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: "Personal pi coding agent with MCP tool integration"
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 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, {
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
- agentDir,
210
- sessionManager: SessionManager.create(cwd)
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 (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 || "",
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
- await mcp?.cleanup();
109
+ process.exit(code);
224
110
  } else if (!process.stdout.isTTY) {
225
- console.log(`my-pi v${pkg.version} — pi coding agent with MCP tools\n`);
111
+ console.log(`my-pi v${pkg.version} — composable pi coding agent\n`);
226
112
  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");
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":";;;;;;;;AAeA,MAAM,YAAY,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AACzD,MAAM,MAAM,KAAK,MAChB,aAAa,KAAK,WAAW,MAAM,eAAe,EAAE,QAAQ,CAC5D;AAID,SAAS,sBAAsB,MAA0B;CACxD,MAAM,QAAkB,EAAE;AAC1B,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,IAChC,MACE,KAAK,OAAO,QAAQ,KAAK,OAAO,kBACjC,IAAI,IAAI,KAAK,OAEb,OAAM,KAAK,QAAQ,KAAK,EAAE,GAAG,CAAC;AAGhC,QAAO;;AAGR,eAAe,aAA8B;CAC5C,MAAM,SAAmB,EAAE;AAC3B,YAAW,MAAM,SAAS,QAAQ,MACjC,QAAO,KAAK,MAAgB;AAE7B,QAAO,OAAO,OAAO,OAAO,CAAC,SAAS,QAAQ,CAAC,MAAM;;AAgJjD,QA7IQ,cAAc;CAC1B,MAAM;EACL,MAAM;EACN,SAAS,IAAI;EACb,aACC;EACD;CACD,MAAM;EACL,OAAO;GACN,MAAM;GACN,OAAO;GACP,aAAa;GACb,SAAS;GACT;EACD,MAAM;GACL,MAAM;GACN,OAAO;GACP,aAAa;GACb,SAAS;GACT;EACD,cAAc;GACb,MAAM;GACN,aAAa;GACb,SAAS;GACT;EACD,UAAU;GACT,MAAM;GACN,aAAa;GACb,SAAS;GACT;EACD,aAAa;GACZ,MAAM;GACN,aAAa;GACb,SAAS;GACT;EACD,YAAY;GACX,MAAM;GACN,aAAa;GACb,SAAS;GACT;EACD,aAAa;GACZ,MAAM;GACN,aAAa;GACb,SAAS;GACT;EACD,cAAc;GACb,MAAM;GACN,aAAa;GACb,SAAS;GACT;EACD,aAAa;GACZ,MAAM;GACN,aAAa;GACb,SAAS;GACT;EACD,OAAO;GACN,MAAM;GACN,OAAO;GACP,aAAa;GACb;EACD,QAAQ;GACP,MAAM;GACN,aAAa;GACb,UAAU;GACV;EACD;CACD,MAAM,IAAI,EAAE,QAAQ;EACnB,MAAM,MAAM,QAAQ,KAAK;EACzB,MAAM,kBAAkB,sBAAsB,QAAQ,KAAK;EAG3D,IAAI,SAAS,KAAK;AAClB,MAAI,CAAC,UAAU,CAAC,QAAQ,MAAM,MAC7B,UAAS,MAAM,YAAY;EAG5B,MAAM,UAAU,MAAM,aAAa;GAClC;GACA,YAAY;GACZ,KAAK,CAAC,KAAK,iBAAiB,CAAC,KAAK;GAClC,QAAQ,CAAC,KAAK,iBAAiB,CAAC,KAAK;GACrC,OAAO,CAAC,KAAK,iBAAiB,CAAC,KAAK;GACpC,eAAe,CAAC,KAAK,iBAAiB,CAAC,KAAK;GAC5C,SAAS,CAAC,KAAK,iBAAiB,CAAC,KAAK;GACtC,QAAQ,CAAC,KAAK,iBAAiB,CAAC,KAAK;GACrC,OAAO,KAAK;GACZ,CAAC;AAEF,MAAI,KAAK,SAAS,KAAK,QAAQ,QAAQ;GACtC,MAAM,OAAO,MAAM,aAAa,SAAS;IACxC,MAAM,KAAK,OAAO,SAAS;IAC3B,gBAAgB,UAAU;IAC1B,eAAe,EAAE;IACjB,UAAU,EAAE;IACZ,CAAC;AACF,WAAQ,KAAK,KAAK;aACR,CAAC,QAAQ,OAAO,OAAO;AACjC,WAAQ,IACP,UAAU,IAAI,QAAQ,iCACtB;AACD,WAAQ,IAAI,SAAS;AACrB,WAAQ,IACP,2DACA;AACD,WAAQ,IACP,0DACA;AACD,WAAQ,IACP,2DACA;AACD,WAAQ,IACP,gEACA;AACD,WAAQ,IACP,wDACA;AACD,WAAQ,IACP,+DACA;AACD,WAAQ,IACP,4DACA;AACD,WAAQ,IACP,0DACA;AACD,WAAQ,IACP,8DACA;QASD,OAPa,IAAI,gBAAgB,SAAS;GACzC,mBAAmB,EAAE;GACrB,sBAAsB,KAAA;GACtB,gBAAgB,KAAA;GAChB,eAAe,EAAE;GACjB,iBAAiB,EAAE;GACnB,CAAC,CACS,KAAK;;CAGlB,CAAC,CAEgB"}