@oh-my-pi/cli 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/icon.png +0 -0
- package/.github/logo.png +0 -0
- package/.github/workflows/publish.yml +1 -1
- package/LICENSE +21 -0
- package/README.md +243 -138
- package/biome.json +1 -1
- package/bun.lock +59 -0
- package/dist/cli.js +6311 -2900
- package/dist/commands/config.d.ts +32 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/enable.d.ts +1 -0
- package/dist/commands/enable.d.ts.map +1 -1
- package/dist/commands/env.d.ts +14 -0
- package/dist/commands/env.d.ts.map +1 -0
- package/dist/commands/features.d.ts +25 -0
- package/dist/commands/features.d.ts.map +1 -0
- package/dist/commands/info.d.ts +1 -0
- package/dist/commands/info.d.ts.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/install.d.ts +37 -0
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/link.d.ts +2 -0
- package/dist/commands/link.d.ts.map +1 -1
- package/dist/commands/list.d.ts +1 -0
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/outdated.d.ts +1 -0
- package/dist/commands/outdated.d.ts.map +1 -1
- package/dist/commands/search.d.ts.map +1 -1
- package/dist/commands/uninstall.d.ts +1 -0
- package/dist/commands/uninstall.d.ts.map +1 -1
- package/dist/commands/update.d.ts +1 -0
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/why.d.ts +1 -0
- package/dist/commands/why.d.ts.map +1 -1
- package/dist/conflicts.d.ts +9 -1
- package/dist/conflicts.d.ts.map +1 -1
- package/dist/errors.d.ts +8 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/index.d.ts +18 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/lock.d.ts +3 -0
- package/dist/lock.d.ts.map +1 -0
- package/dist/lockfile.d.ts +52 -0
- package/dist/lockfile.d.ts.map +1 -0
- package/dist/manifest.d.ts +60 -25
- package/dist/manifest.d.ts.map +1 -1
- package/dist/npm.d.ts +14 -2
- package/dist/npm.d.ts.map +1 -1
- package/dist/paths.d.ts +34 -3
- package/dist/paths.d.ts.map +1 -1
- package/dist/runtime.d.ts +14 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/symlinks.d.ts +43 -7
- package/dist/symlinks.d.ts.map +1 -1
- package/package.json +11 -5
- package/plugins/exa/README.md +153 -0
- package/plugins/exa/package.json +56 -0
- package/plugins/exa/tools/exa/company.ts +35 -0
- package/plugins/exa/tools/exa/index.ts +66 -0
- package/plugins/exa/tools/exa/linkedin.ts +35 -0
- package/plugins/exa/tools/exa/researcher.ts +40 -0
- package/plugins/exa/tools/exa/runtime.json +4 -0
- package/plugins/exa/tools/exa/search.ts +46 -0
- package/plugins/exa/tools/exa/shared.ts +230 -0
- package/plugins/exa/tools/exa/websets.ts +62 -0
- package/plugins/metal-theme/package.json +7 -2
- package/plugins/subagents/package.json +7 -2
- package/plugins/user-prompt/README.md +130 -0
- package/plugins/user-prompt/package.json +19 -0
- package/plugins/user-prompt/tools/user-prompt/index.ts +235 -0
- package/src/cli.ts +133 -58
- package/src/commands/config.ts +384 -0
- package/src/commands/create.ts +51 -1
- package/src/commands/doctor.ts +95 -7
- package/src/commands/enable.ts +25 -8
- package/src/commands/env.ts +38 -0
- package/src/commands/features.ts +295 -0
- package/src/commands/info.ts +41 -5
- package/src/commands/init.ts +20 -2
- package/src/commands/install.ts +453 -80
- package/src/commands/link.ts +60 -9
- package/src/commands/list.ts +122 -7
- package/src/commands/outdated.ts +17 -6
- package/src/commands/search.ts +20 -3
- package/src/commands/uninstall.ts +57 -6
- package/src/commands/update.ts +67 -9
- package/src/commands/why.ts +47 -16
- package/src/conflicts.ts +33 -1
- package/src/errors.ts +22 -0
- package/src/index.ts +18 -25
- package/src/lock.ts +46 -0
- package/src/lockfile.ts +132 -0
- package/src/manifest.ts +219 -71
- package/src/npm.ts +74 -18
- package/src/paths.ts +77 -12
- package/src/runtime.ts +116 -0
- package/src/symlinks.ts +291 -35
- package/tsconfig.json +7 -3
- package/CHECK.md +0 -352
- package/dist/migrate.d.ts +0 -9
- package/dist/migrate.d.ts.map +0 -1
- package/src/migrate.ts +0 -181
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for Exa MCP tools
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
import * as os from "node:os";
|
|
7
|
+
import * as path from "node:path";
|
|
8
|
+
import type { TSchema } from "@sinclair/typebox";
|
|
9
|
+
import type { CustomAgentTool } from "@mariozechner/pi-coding-agent";
|
|
10
|
+
|
|
11
|
+
// MCP endpoints
|
|
12
|
+
export const EXA_MCP_URL = "https://mcp.exa.ai/mcp";
|
|
13
|
+
export const WEBSETS_MCP_URL = "https://websetsmcp.exa.ai/mcp";
|
|
14
|
+
|
|
15
|
+
export interface MCPTool {
|
|
16
|
+
name: string;
|
|
17
|
+
description: string;
|
|
18
|
+
inputSchema: TSchema;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface MCPToolsResponse {
|
|
22
|
+
result?: {
|
|
23
|
+
tools: MCPTool[];
|
|
24
|
+
};
|
|
25
|
+
error?: {
|
|
26
|
+
code: number;
|
|
27
|
+
message: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Parse a .env file and return key-value pairs
|
|
33
|
+
*/
|
|
34
|
+
function parseEnvFile(filePath: string): Record<string, string> {
|
|
35
|
+
const result: Record<string, string> = {};
|
|
36
|
+
if (!fs.existsSync(filePath)) return result;
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
40
|
+
for (const line of content.split("\n")) {
|
|
41
|
+
const trimmed = line.trim();
|
|
42
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
43
|
+
|
|
44
|
+
const eqIndex = trimmed.indexOf("=");
|
|
45
|
+
if (eqIndex === -1) continue;
|
|
46
|
+
|
|
47
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
48
|
+
let value = trimmed.slice(eqIndex + 1).trim();
|
|
49
|
+
|
|
50
|
+
// Remove surrounding quotes
|
|
51
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
52
|
+
value = value.slice(1, -1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
result[key] = value;
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
// Ignore read errors
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Find EXA_API_KEY from environment or .env files
|
|
66
|
+
*/
|
|
67
|
+
export function findApiKey(): string | null {
|
|
68
|
+
// 1. Check environment variable
|
|
69
|
+
if (process.env.EXA_API_KEY) {
|
|
70
|
+
return process.env.EXA_API_KEY;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 2. Check .env in current directory
|
|
74
|
+
const localEnv = parseEnvFile(path.join(process.cwd(), ".env"));
|
|
75
|
+
if (localEnv.EXA_API_KEY) {
|
|
76
|
+
return localEnv.EXA_API_KEY;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 3. Check ~/.env
|
|
80
|
+
const homeEnv = parseEnvFile(path.join(os.homedir(), ".env"));
|
|
81
|
+
if (homeEnv.EXA_API_KEY) {
|
|
82
|
+
return homeEnv.EXA_API_KEY;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Call an MCP server endpoint
|
|
90
|
+
*/
|
|
91
|
+
async function callMCP(url: string, method: string, params?: Record<string, unknown>): Promise<unknown> {
|
|
92
|
+
const body = {
|
|
93
|
+
jsonrpc: "2.0",
|
|
94
|
+
method,
|
|
95
|
+
params: params ?? {},
|
|
96
|
+
id: 1,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const response = await fetch(url, {
|
|
100
|
+
method: "POST",
|
|
101
|
+
headers: {
|
|
102
|
+
"Content-Type": "application/json",
|
|
103
|
+
Accept: "application/json, text/event-stream",
|
|
104
|
+
},
|
|
105
|
+
body: JSON.stringify(body),
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const text = await response.text();
|
|
109
|
+
|
|
110
|
+
// Parse SSE response format
|
|
111
|
+
let jsonData: string | null = null;
|
|
112
|
+
for (const line of text.split("\n")) {
|
|
113
|
+
if (line.startsWith("data: ")) {
|
|
114
|
+
jsonData = line.slice(6);
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!jsonData) {
|
|
120
|
+
// Try parsing as plain JSON
|
|
121
|
+
try {
|
|
122
|
+
return JSON.parse(text);
|
|
123
|
+
} catch {
|
|
124
|
+
throw new Error(`Failed to parse MCP response: ${text.slice(0, 500)}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return JSON.parse(jsonData);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Fetch available tools from Exa MCP server
|
|
133
|
+
*/
|
|
134
|
+
export async function fetchExaTools(apiKey: string, toolNames: string[]): Promise<MCPTool[]> {
|
|
135
|
+
const url = `${EXA_MCP_URL}?exaApiKey=${apiKey}&tools=${toolNames.join(",")}`;
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const response = (await callMCP(url, "tools/list")) as MCPToolsResponse;
|
|
139
|
+
if (response.error) {
|
|
140
|
+
throw new Error(response.error.message);
|
|
141
|
+
}
|
|
142
|
+
return response.result?.tools ?? [];
|
|
143
|
+
} catch (error) {
|
|
144
|
+
console.error(`Failed to fetch Exa tools:`, error);
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Fetch available tools from Websets MCP server
|
|
151
|
+
*/
|
|
152
|
+
export async function fetchWebsetsTools(apiKey: string): Promise<MCPTool[]> {
|
|
153
|
+
const url = `${WEBSETS_MCP_URL}?exaApiKey=${apiKey}`;
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
const response = (await callMCP(url, "tools/list")) as MCPToolsResponse;
|
|
157
|
+
if (response.error) {
|
|
158
|
+
throw new Error(response.error.message);
|
|
159
|
+
}
|
|
160
|
+
return response.result?.tools ?? [];
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error(`Failed to fetch Websets tools:`, error);
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Call a tool on Exa MCP server
|
|
169
|
+
*/
|
|
170
|
+
export async function callExaTool(apiKey: string, toolNames: string[], toolName: string, args: Record<string, unknown>): Promise<unknown> {
|
|
171
|
+
const url = `${EXA_MCP_URL}?exaApiKey=${apiKey}&tools=${toolNames.join(",")}`;
|
|
172
|
+
return callMCPTool(url, toolName, args);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Call a tool on Websets MCP server
|
|
177
|
+
*/
|
|
178
|
+
export async function callWebsetsTool(apiKey: string, toolName: string, args: Record<string, unknown>): Promise<unknown> {
|
|
179
|
+
const url = `${WEBSETS_MCP_URL}?exaApiKey=${apiKey}`;
|
|
180
|
+
return callMCPTool(url, toolName, args);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Call a tool on an MCP server
|
|
185
|
+
*/
|
|
186
|
+
async function callMCPTool(url: string, toolName: string, args: Record<string, unknown>): Promise<unknown> {
|
|
187
|
+
const response = (await callMCP(url, "tools/call", {
|
|
188
|
+
name: toolName,
|
|
189
|
+
arguments: args,
|
|
190
|
+
})) as { result?: { content?: Array<{ text?: string }> }; error?: { message: string } };
|
|
191
|
+
|
|
192
|
+
if (response.error) {
|
|
193
|
+
throw new Error(response.error.message);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Extract text content from MCP response
|
|
197
|
+
const content = response.result?.content;
|
|
198
|
+
if (Array.isArray(content)) {
|
|
199
|
+
const texts = content.filter((c) => c.text).map((c) => c.text);
|
|
200
|
+
if (texts.length === 1) {
|
|
201
|
+
// Try to parse as JSON
|
|
202
|
+
try {
|
|
203
|
+
return JSON.parse(texts[0]!);
|
|
204
|
+
} catch {
|
|
205
|
+
return texts[0];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return texts.join("\n\n");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return response.result;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Create a tool wrapper for an MCP tool
|
|
216
|
+
*/
|
|
217
|
+
export function createToolWrapper(
|
|
218
|
+
mcpTool: MCPTool,
|
|
219
|
+
renamedName: string,
|
|
220
|
+
callFn: (toolName: string, args: Record<string, unknown>) => Promise<unknown>
|
|
221
|
+
): CustomAgentTool<TSchema, unknown> {
|
|
222
|
+
return {
|
|
223
|
+
name: renamedName,
|
|
224
|
+
description: mcpTool.description,
|
|
225
|
+
parameters: mcpTool.inputSchema,
|
|
226
|
+
async execute(args) {
|
|
227
|
+
return callFn(mcpTool.name, args as Record<string, unknown>);
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exa Websets Tools - Entity collection management
|
|
3
|
+
*
|
|
4
|
+
* Tools:
|
|
5
|
+
* - webset_create: Create entity collections with search/enrichments
|
|
6
|
+
* - webset_list: List all websets
|
|
7
|
+
* - webset_get: Get webset details
|
|
8
|
+
* - webset_update: Update webset metadata
|
|
9
|
+
* - webset_delete: Delete a webset
|
|
10
|
+
* - webset_items_list: List items in a webset
|
|
11
|
+
* - webset_item_get: Get item details
|
|
12
|
+
* - webset_search_create: Add search to webset
|
|
13
|
+
* - webset_search_get: Check search status
|
|
14
|
+
* - webset_search_cancel: Cancel running search
|
|
15
|
+
* - webset_enrichment_create: Extract custom data from items
|
|
16
|
+
* - webset_enrichment_get: Get enrichment details
|
|
17
|
+
* - webset_enrichment_update: Update enrichment metadata
|
|
18
|
+
* - webset_enrichment_delete: Delete enrichment
|
|
19
|
+
* - webset_enrichment_cancel: Cancel running enrichment
|
|
20
|
+
* - webset_monitor_create: Auto-update webset on schedule
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type { TSchema } from "@sinclair/typebox";
|
|
24
|
+
import type { CustomAgentTool, CustomToolFactory, ToolAPI } from "@mariozechner/pi-coding-agent";
|
|
25
|
+
import { callWebsetsTool, createToolWrapper, fetchWebsetsTools, findApiKey } from "./shared";
|
|
26
|
+
|
|
27
|
+
// Tool name mapping: MCP name -> exposed name
|
|
28
|
+
const NAME_MAP: Record<string, string> = {
|
|
29
|
+
"create_webset": "webset_create",
|
|
30
|
+
"list_websets": "webset_list",
|
|
31
|
+
"get_webset": "webset_get",
|
|
32
|
+
"update_webset": "webset_update",
|
|
33
|
+
"delete_webset": "webset_delete",
|
|
34
|
+
"list_webset_items": "webset_items_list",
|
|
35
|
+
"get_item": "webset_item_get",
|
|
36
|
+
"create_search": "webset_search_create",
|
|
37
|
+
"get_search": "webset_search_get",
|
|
38
|
+
"cancel_search": "webset_search_cancel",
|
|
39
|
+
"create_enrichment": "webset_enrichment_create",
|
|
40
|
+
"get_enrichment": "webset_enrichment_get",
|
|
41
|
+
"update_enrichment": "webset_enrichment_update",
|
|
42
|
+
"delete_enrichment": "webset_enrichment_delete",
|
|
43
|
+
"cancel_enrichment": "webset_enrichment_cancel",
|
|
44
|
+
"create_monitor": "webset_monitor_create",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const factory: CustomToolFactory = async (_toolApi: ToolAPI): Promise<CustomAgentTool<TSchema, unknown>[] | null> => {
|
|
48
|
+
const apiKey = findApiKey();
|
|
49
|
+
if (!apiKey) return null;
|
|
50
|
+
|
|
51
|
+
const mcpTools = await fetchWebsetsTools(apiKey);
|
|
52
|
+
if (mcpTools.length === 0) return null;
|
|
53
|
+
|
|
54
|
+
const callFn = (toolName: string, args: Record<string, unknown>) =>
|
|
55
|
+
callWebsetsTool(apiKey, toolName, args);
|
|
56
|
+
|
|
57
|
+
return mcpTools.map((tool) =>
|
|
58
|
+
createToolWrapper(tool, NAME_MAP[tool.name] ?? tool.name, callFn)
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export default factory;
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/metal-theme",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Metal theme for pi",
|
|
5
5
|
"keywords": ["omp-plugin", "theme", "metal"],
|
|
6
|
-
"author": "",
|
|
6
|
+
"author": "Can Bölük <me@can.ac>",
|
|
7
7
|
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/can1357/oh-my-pi.git",
|
|
11
|
+
"directory": "plugins/metal-theme"
|
|
12
|
+
},
|
|
8
13
|
"omp": {
|
|
9
14
|
"install": [
|
|
10
15
|
{ "src": "themes/metal.json", "dest": "agent/themes/metal.json" }
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/subagents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Task delegation system with specialized subagents (task, planner, explore, reviewer)",
|
|
5
5
|
"keywords": ["omp-plugin", "agents", "task-delegation"],
|
|
6
|
-
"author": "",
|
|
6
|
+
"author": "Can Bölük <me@can.ac>",
|
|
7
7
|
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/can1357/oh-my-pi.git",
|
|
11
|
+
"directory": "plugins/subagents"
|
|
12
|
+
},
|
|
8
13
|
"omp": {
|
|
9
14
|
"install": [
|
|
10
15
|
{ "src": "tools/task/index.ts", "dest": "agent/tools/task/index.ts" },
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# User Prompt Plugin
|
|
2
|
+
|
|
3
|
+
Interactive user prompting tool for gathering user input during agent execution.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
omp install oh-my-pi/plugins/user-prompt
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Tool
|
|
12
|
+
|
|
13
|
+
### `user_prompt`
|
|
14
|
+
|
|
15
|
+
Asks the user questions during execution and returns their response. Useful for:
|
|
16
|
+
|
|
17
|
+
- Gathering user preferences or requirements
|
|
18
|
+
- Clarifying ambiguous instructions
|
|
19
|
+
- Getting decisions on implementation choices
|
|
20
|
+
- Offering choices about what direction to take
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
### Enhanced UI (when available)
|
|
25
|
+
|
|
26
|
+
The plugin provides custom TUI components that integrate directly into pi's interface:
|
|
27
|
+
|
|
28
|
+
**Single-select with inline "Other" input:**
|
|
29
|
+
```
|
|
30
|
+
─────────────────────────────────────────────
|
|
31
|
+
Which database would you like to use?
|
|
32
|
+
|
|
33
|
+
→ PostgreSQL (Recommended)
|
|
34
|
+
MySQL
|
|
35
|
+
SQLite
|
|
36
|
+
MongoDB
|
|
37
|
+
Other (type your own)
|
|
38
|
+
|
|
39
|
+
↑↓ navigate · enter select · esc cancel
|
|
40
|
+
─────────────────────────────────────────────
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
When "Other" is selected, an inline text input appears - no separate dialog needed.
|
|
44
|
+
|
|
45
|
+
**Multi-select with checkboxes:**
|
|
46
|
+
```
|
|
47
|
+
─────────────────────────────────────────────
|
|
48
|
+
Which features should I implement?
|
|
49
|
+
|
|
50
|
+
→ [X] Authentication
|
|
51
|
+
[X] API endpoints
|
|
52
|
+
[ ] Database models
|
|
53
|
+
[ ] Unit tests
|
|
54
|
+
[ ] Documentation
|
|
55
|
+
|
|
56
|
+
↑↓ navigate · space toggle · enter confirm · esc cancel
|
|
57
|
+
─────────────────────────────────────────────
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Space toggles selection, Enter confirms. Selected items show `[X]` in green with white text.
|
|
61
|
+
|
|
62
|
+
### Fallback Mode
|
|
63
|
+
|
|
64
|
+
If the enhanced UI cannot be loaded, the plugin gracefully falls back to using pi's built-in `select()` and `input()` methods.
|
|
65
|
+
|
|
66
|
+
## Parameters
|
|
67
|
+
|
|
68
|
+
| Parameter | Type | Required | Description |
|
|
69
|
+
|-----------|------|----------|-------------|
|
|
70
|
+
| `question` | string | Yes | The question to ask the user |
|
|
71
|
+
| `options` | array | Yes | Array of `{label: string}` options to present |
|
|
72
|
+
| `multiSelect` | boolean | No | Allow multiple selections (default: false) |
|
|
73
|
+
|
|
74
|
+
## Usage Notes
|
|
75
|
+
|
|
76
|
+
- Users can always select "Other" to provide custom text input
|
|
77
|
+
- Use `multiSelect: true` to allow multiple answers to be selected
|
|
78
|
+
- If you recommend a specific option, make that the first option and add "(Recommended)" at the end of the label
|
|
79
|
+
|
|
80
|
+
## Examples
|
|
81
|
+
|
|
82
|
+
### Single-choice question
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"question": "Which database would you like to use?",
|
|
87
|
+
"options": [
|
|
88
|
+
{"label": "PostgreSQL (Recommended)"},
|
|
89
|
+
{"label": "MySQL"},
|
|
90
|
+
{"label": "SQLite"},
|
|
91
|
+
{"label": "MongoDB"}
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Multi-select question
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"question": "Which features should I implement?",
|
|
101
|
+
"options": [
|
|
102
|
+
{"label": "Authentication"},
|
|
103
|
+
{"label": "API endpoints"},
|
|
104
|
+
{"label": "Database models"},
|
|
105
|
+
{"label": "Unit tests"},
|
|
106
|
+
{"label": "Documentation"}
|
|
107
|
+
],
|
|
108
|
+
"multiSelect": true
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Response Format
|
|
113
|
+
|
|
114
|
+
The tool returns the user's selection in a structured format:
|
|
115
|
+
|
|
116
|
+
- **Single selection**: `"User selected: PostgreSQL (Recommended)"`
|
|
117
|
+
- **Multi-selection**: `"User selected: Authentication, API endpoints, Unit tests"`
|
|
118
|
+
- **Custom input**: `"User provided custom input: Use Redis for caching"`
|
|
119
|
+
- **Cancelled**: `"User cancelled the selection"`
|
|
120
|
+
|
|
121
|
+
## How It Works
|
|
122
|
+
|
|
123
|
+
The plugin hooks into pi's interactive mode at runtime to provide custom TUI components. It:
|
|
124
|
+
|
|
125
|
+
1. Dynamically imports pi's theme for consistent styling
|
|
126
|
+
2. Locates the InteractiveMode instance to access the editor container
|
|
127
|
+
3. Swaps in custom components (MultiSelectList, SelectWithInput) when prompting
|
|
128
|
+
4. Restores the normal editor when done
|
|
129
|
+
|
|
130
|
+
This approach provides a seamless, native-feeling UI without requiring upstream changes to pi.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@oh-my-pi/user-prompt",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Interactive user prompting tool for gathering user input during execution",
|
|
5
|
+
"keywords": ["omp-plugin", "user-prompt", "interactive", "questions", "input"],
|
|
6
|
+
"author": "Can Bölük <me@can.ac>",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/can1357/oh-my-pi.git",
|
|
11
|
+
"directory": "plugins/user-prompt"
|
|
12
|
+
},
|
|
13
|
+
"omp": {
|
|
14
|
+
"install": [
|
|
15
|
+
{ "src": "tools/user-prompt/index.ts", "dest": "agent/tools/user-prompt/index.ts" }
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
"files": ["tools"]
|
|
19
|
+
}
|