@chankov/agent-skills 0.3.0 → 0.3.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/.versions/0.3.2/.claude/commands/build.md +18 -0
- package/.versions/0.3.2/.claude/commands/code-simplify.md +22 -0
- package/.versions/0.3.2/.claude/commands/design-agent.md +14 -0
- package/.versions/0.3.2/.claude/commands/doctor-agent-skills.md +13 -0
- package/.versions/0.3.2/.claude/commands/plan.md +16 -0
- package/.versions/0.3.2/.claude/commands/prime.md +22 -0
- package/.versions/0.3.2/.claude/commands/review.md +16 -0
- package/.versions/0.3.2/.claude/commands/setup-agent-skills.md +19 -0
- package/.versions/0.3.2/.claude/commands/ship.md +17 -0
- package/.versions/0.3.2/.claude/commands/spec.md +15 -0
- package/.versions/0.3.2/.claude/commands/test.md +19 -0
- package/.versions/0.3.2/.opencode/commands/as-build.md +17 -0
- package/.versions/0.3.2/.opencode/commands/as-code-simplify.md +16 -0
- package/.versions/0.3.2/.opencode/commands/as-design-agent.md +15 -0
- package/.versions/0.3.2/.opencode/commands/as-doctor-agent-skills.md +11 -0
- package/.versions/0.3.2/.opencode/commands/as-plan.md +16 -0
- package/.versions/0.3.2/.opencode/commands/as-prime.md +22 -0
- package/.versions/0.3.2/.opencode/commands/as-review.md +15 -0
- package/.versions/0.3.2/.opencode/commands/as-setup-agent-skills.md +11 -0
- package/.versions/0.3.2/.opencode/commands/as-ship.md +16 -0
- package/.versions/0.3.2/.opencode/commands/as-spec.md +16 -0
- package/.versions/0.3.2/.opencode/commands/as-test.md +21 -0
- package/.versions/0.3.2/.pi/agents/agent-chain.yaml +49 -0
- package/.versions/0.3.2/.pi/agents/bowser.md +19 -0
- package/.versions/0.3.2/.pi/agents/pi-pi/agent-expert.md +98 -0
- package/.versions/0.3.2/.pi/agents/pi-pi/cli-expert.md +41 -0
- package/.versions/0.3.2/.pi/agents/pi-pi/config-expert.md +63 -0
- package/.versions/0.3.2/.pi/agents/pi-pi/ext-expert.md +43 -0
- package/.versions/0.3.2/.pi/agents/pi-pi/keybinding-expert.md +134 -0
- package/.versions/0.3.2/.pi/agents/pi-pi/pi-orchestrator.md +57 -0
- package/.versions/0.3.2/.pi/agents/pi-pi/prompt-expert.md +70 -0
- package/.versions/0.3.2/.pi/agents/pi-pi/skill-expert.md +42 -0
- package/.versions/0.3.2/.pi/agents/pi-pi/theme-expert.md +40 -0
- package/.versions/0.3.2/.pi/agents/pi-pi/tui-expert.md +85 -0
- package/.versions/0.3.2/.pi/agents/teams.yaml +31 -0
- package/.versions/0.3.2/.pi/damage-control-rules.yaml +278 -0
- package/.versions/0.3.2/.pi/extensions/agent-skills-update-check/README.md +58 -0
- package/.versions/0.3.2/.pi/extensions/agent-skills-update-check/index.ts +161 -0
- package/.versions/0.3.2/.pi/extensions/agent-skills-update-check/package.json +6 -0
- package/.versions/0.3.2/.pi/extensions/chrome-devtools-mcp/README.md +39 -0
- package/.versions/0.3.2/.pi/extensions/chrome-devtools-mcp/index.ts +61 -0
- package/.versions/0.3.2/.pi/extensions/chrome-devtools-mcp/package.json +6 -0
- package/.versions/0.3.2/.pi/extensions/compact-and-continue/README.md +42 -0
- package/.versions/0.3.2/.pi/extensions/compact-and-continue/index.ts +120 -0
- package/.versions/0.3.2/.pi/extensions/compact-and-continue/package.json +6 -0
- package/.versions/0.3.2/.pi/extensions/mcp-bridge/README.md +46 -0
- package/.versions/0.3.2/.pi/extensions/mcp-bridge/index.ts +206 -0
- package/.versions/0.3.2/.pi/extensions/mcp-bridge/package.json +6 -0
- package/.versions/0.3.2/.pi/extensions/package-lock.json +1143 -0
- package/.versions/0.3.2/.pi/extensions/package.json +9 -0
- package/.versions/0.3.2/.pi/harnesses/agent-chain/README.md +37 -0
- package/.versions/0.3.2/.pi/harnesses/agent-chain/index.ts +795 -0
- package/.versions/0.3.2/.pi/harnesses/agent-chain/package.json +6 -0
- package/.versions/0.3.2/.pi/harnesses/agent-team/README.md +38 -0
- package/.versions/0.3.2/.pi/harnesses/agent-team/index.ts +732 -0
- package/.versions/0.3.2/.pi/harnesses/agent-team/package.json +6 -0
- package/.versions/0.3.2/.pi/harnesses/coms/README.md +36 -0
- package/.versions/0.3.2/.pi/harnesses/coms/index.ts +1595 -0
- package/.versions/0.3.2/.pi/harnesses/coms/package.json +6 -0
- package/.versions/0.3.2/.pi/harnesses/coms-net/README.md +46 -0
- package/.versions/0.3.2/.pi/harnesses/coms-net/index.ts +1637 -0
- package/.versions/0.3.2/.pi/harnesses/coms-net/package.json +6 -0
- package/.versions/0.3.2/.pi/harnesses/damage-control/README.md +38 -0
- package/.versions/0.3.2/.pi/harnesses/damage-control/index.ts +207 -0
- package/.versions/0.3.2/.pi/harnesses/damage-control/package.json +6 -0
- package/.versions/0.3.2/.pi/harnesses/damage-control-continue/README.md +37 -0
- package/.versions/0.3.2/.pi/harnesses/damage-control-continue/index.ts +234 -0
- package/.versions/0.3.2/.pi/harnesses/damage-control-continue/package.json +6 -0
- package/.versions/0.3.2/.pi/harnesses/minimal/README.md +27 -0
- package/.versions/0.3.2/.pi/harnesses/minimal/index.ts +32 -0
- package/.versions/0.3.2/.pi/harnesses/minimal/package.json +6 -0
- package/.versions/0.3.2/.pi/harnesses/package-lock.json +35 -0
- package/.versions/0.3.2/.pi/harnesses/package.json +9 -0
- package/.versions/0.3.2/.pi/harnesses/pi-pi/README.md +39 -0
- package/.versions/0.3.2/.pi/harnesses/pi-pi/index.ts +631 -0
- package/.versions/0.3.2/.pi/harnesses/pi-pi/package.json +6 -0
- package/.versions/0.3.2/.pi/harnesses/purpose-gate/README.md +27 -0
- package/.versions/0.3.2/.pi/harnesses/purpose-gate/index.ts +82 -0
- package/.versions/0.3.2/.pi/harnesses/purpose-gate/package.json +6 -0
- package/.versions/0.3.2/.pi/harnesses/session-replay/README.md +28 -0
- package/.versions/0.3.2/.pi/harnesses/session-replay/index.ts +214 -0
- package/.versions/0.3.2/.pi/harnesses/session-replay/package.json +6 -0
- package/.versions/0.3.2/.pi/harnesses/subagent-widget/README.md +36 -0
- package/.versions/0.3.2/.pi/harnesses/subagent-widget/index.ts +479 -0
- package/.versions/0.3.2/.pi/harnesses/subagent-widget/package.json +6 -0
- package/.versions/0.3.2/.pi/harnesses/system-select/README.md +39 -0
- package/.versions/0.3.2/.pi/harnesses/system-select/index.ts +165 -0
- package/.versions/0.3.2/.pi/harnesses/system-select/package.json +6 -0
- package/.versions/0.3.2/.pi/harnesses/tilldone/README.md +35 -0
- package/.versions/0.3.2/.pi/harnesses/tilldone/index.ts +724 -0
- package/.versions/0.3.2/.pi/harnesses/tilldone/package.json +6 -0
- package/.versions/0.3.2/.pi/harnesses/tool-counter/README.md +31 -0
- package/.versions/0.3.2/.pi/harnesses/tool-counter/index.ts +100 -0
- package/.versions/0.3.2/.pi/harnesses/tool-counter/package.json +6 -0
- package/.versions/0.3.2/.pi/harnesses/tool-counter-widget/README.md +27 -0
- package/.versions/0.3.2/.pi/harnesses/tool-counter-widget/index.ts +66 -0
- package/.versions/0.3.2/.pi/harnesses/tool-counter-widget/package.json +6 -0
- package/.versions/0.3.2/.pi/prompts/build.md +24 -0
- package/.versions/0.3.2/.pi/prompts/code-simplify.md +22 -0
- package/.versions/0.3.2/.pi/prompts/doctor-agent-skills.md +13 -0
- package/.versions/0.3.2/.pi/prompts/plan.md +16 -0
- package/.versions/0.3.2/.pi/prompts/review.md +16 -0
- package/.versions/0.3.2/.pi/prompts/setup-agent-skills.md +19 -0
- package/.versions/0.3.2/.pi/prompts/ship.md +17 -0
- package/.versions/0.3.2/.pi/prompts/spec.md +15 -0
- package/.versions/0.3.2/.pi/prompts/test.md +19 -0
- package/.versions/0.3.2/.pi/skills/bowser/SKILL.md +114 -0
- package/.versions/0.3.2/.version +1 -0
- package/.versions/0.3.2/agents/builder.md +6 -0
- package/.versions/0.3.2/agents/code-reviewer.md +93 -0
- package/.versions/0.3.2/agents/documenter.md +6 -0
- package/.versions/0.3.2/agents/plan-reviewer.md +22 -0
- package/.versions/0.3.2/agents/planner.md +6 -0
- package/.versions/0.3.2/agents/scout.md +6 -0
- package/.versions/0.3.2/agents/security-auditor.md +97 -0
- package/.versions/0.3.2/agents/test-engineer.md +89 -0
- package/.versions/0.3.2/hooks/SIMPLIFY-IGNORE.md +90 -0
- package/.versions/0.3.2/hooks/hooks.json +14 -0
- package/.versions/0.3.2/hooks/session-start.sh +74 -0
- package/.versions/0.3.2/hooks/simplify-ignore-test.sh +247 -0
- package/.versions/0.3.2/hooks/simplify-ignore.sh +302 -0
- package/.versions/0.3.2/references/accessibility-checklist.md +159 -0
- package/.versions/0.3.2/references/performance-checklist.md +121 -0
- package/.versions/0.3.2/references/prompting-patterns.md +380 -0
- package/.versions/0.3.2/references/security-checklist.md +134 -0
- package/.versions/0.3.2/references/testing-patterns.md +236 -0
- package/.versions/0.3.2/skills/api-and-interface-design/SKILL.md +294 -0
- package/.versions/0.3.2/skills/browser-testing-with-devtools/SKILL.md +335 -0
- package/.versions/0.3.2/skills/ci-cd-and-automation/SKILL.md +390 -0
- package/.versions/0.3.2/skills/code-review-and-quality/SKILL.md +347 -0
- package/.versions/0.3.2/skills/code-simplification/SKILL.md +331 -0
- package/.versions/0.3.2/skills/context-engineering/SKILL.md +291 -0
- package/.versions/0.3.2/skills/debugging-and-error-recovery/SKILL.md +300 -0
- package/.versions/0.3.2/skills/deprecation-and-migration/SKILL.md +206 -0
- package/.versions/0.3.2/skills/designing-agents/SKILL.md +394 -0
- package/.versions/0.3.2/skills/designing-agents/pi-harness-authoring.md +213 -0
- package/.versions/0.3.2/skills/documentation-and-adrs/SKILL.md +278 -0
- package/.versions/0.3.2/skills/frontend-ui-engineering/SKILL.md +322 -0
- package/.versions/0.3.2/skills/git-workflow-and-versioning/SKILL.md +316 -0
- package/.versions/0.3.2/skills/guided-workspace-setup/SKILL.md +345 -0
- package/.versions/0.3.2/skills/idea-refine/SKILL.md +178 -0
- package/.versions/0.3.2/skills/idea-refine/examples.md +238 -0
- package/.versions/0.3.2/skills/idea-refine/frameworks.md +99 -0
- package/.versions/0.3.2/skills/idea-refine/refinement-criteria.md +113 -0
- package/.versions/0.3.2/skills/idea-refine/scripts/idea-refine.sh +15 -0
- package/.versions/0.3.2/skills/incremental-implementation/SKILL.md +279 -0
- package/.versions/0.3.2/skills/performance-optimization/SKILL.md +350 -0
- package/.versions/0.3.2/skills/planning-and-task-breakdown/SKILL.md +237 -0
- package/.versions/0.3.2/skills/security-and-hardening/SKILL.md +349 -0
- package/.versions/0.3.2/skills/shipping-and-launch/SKILL.md +309 -0
- package/.versions/0.3.2/skills/source-driven-development/SKILL.md +194 -0
- package/.versions/0.3.2/skills/spec-driven-development/SKILL.md +237 -0
- package/.versions/0.3.2/skills/test-driven-development/SKILL.md +379 -0
- package/.versions/0.3.2/skills/using-agent-skills/SKILL.md +176 -0
- package/CHANGELOG.md +36 -0
- package/bin/lib/bootstrap.js +56 -1
- package/docs/npm-install.md +30 -0
- package/package.json +1 -1
- package/skills/guided-workspace-setup/SKILL.md +16 -2
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { CompactionResult, ExtensionAPI, ExtensionContext } from '@mariozechner/pi-coding-agent';
|
|
2
|
+
import { Type } from 'typebox';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_COMPACTION_INSTRUCTIONS = 'Preserve task goal, completed work, changed/read files, decisions, blockers, and next steps.';
|
|
5
|
+
|
|
6
|
+
const RequestCompactionParams = Type.Object({
|
|
7
|
+
customInstructions: Type.Optional(Type.String({
|
|
8
|
+
description: 'Instructions for the compaction summary. Include what must be preserved for continuing safely.',
|
|
9
|
+
})),
|
|
10
|
+
reason: Type.Optional(Type.String({
|
|
11
|
+
description: 'Short explanation recorded in tool details for why immediate compaction was requested.',
|
|
12
|
+
})),
|
|
13
|
+
continuationPrompt: Type.Optional(Type.String({
|
|
14
|
+
description: 'Self-contained summary of the remaining plan to execute after compaction. Do not copy the original prompt verbatim. Include the next concrete actions. If the original plan explicitly requires later compaction checkpoints, include them; otherwise do not repeat the same compaction request.',
|
|
15
|
+
})),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
function compact(ctx: ExtensionContext, customInstructions: string): Promise<CompactionResult> {
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
ctx.compact({
|
|
21
|
+
customInstructions,
|
|
22
|
+
onComplete: resolve,
|
|
23
|
+
onError: reject,
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default function compactAndContinueExtension(pi: ExtensionAPI) {
|
|
29
|
+
type PendingCompaction = {
|
|
30
|
+
customInstructions: string;
|
|
31
|
+
reason: string;
|
|
32
|
+
continuationPrompt?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const pendingCompactions: PendingCompaction[] = [];
|
|
36
|
+
let compactionRunning = false;
|
|
37
|
+
|
|
38
|
+
async function runQueuedCompactions(ctx: ExtensionContext): Promise<void> {
|
|
39
|
+
if (compactionRunning || pendingCompactions.length === 0) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
compactionRunning = true;
|
|
44
|
+
const pending = pendingCompactions.splice(0);
|
|
45
|
+
const customInstructions = pending
|
|
46
|
+
.map((item, index) => [
|
|
47
|
+
`Queued compaction request ${index + 1}/${pending.length}.`,
|
|
48
|
+
`Reason: ${item.reason}`,
|
|
49
|
+
item.customInstructions,
|
|
50
|
+
].join(' '))
|
|
51
|
+
.join('\n\n');
|
|
52
|
+
const continuationRequest = [...pending].reverse().find(item => item.continuationPrompt !== undefined);
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
ctx.ui.notify('Compacting queued context request.', 'info');
|
|
56
|
+
await compact(ctx, customInstructions);
|
|
57
|
+
ctx.ui.notify('Queued compaction finished.', 'info');
|
|
58
|
+
|
|
59
|
+
if (continuationRequest?.continuationPrompt) {
|
|
60
|
+
ctx.ui.notify('Continuing summarized plan after compaction.', 'info');
|
|
61
|
+
setTimeout(() => pi.sendUserMessage(continuationRequest.continuationPrompt!), 0);
|
|
62
|
+
}
|
|
63
|
+
} catch (error) {
|
|
64
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
65
|
+
ctx.ui.notify(`Queued compaction failed: ${message}`, 'error');
|
|
66
|
+
} finally {
|
|
67
|
+
compactionRunning = false;
|
|
68
|
+
if (pendingCompactions.length > 0) {
|
|
69
|
+
setTimeout(() => void runQueuedCompactions(ctx), 0);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
pi.on('agent_end', async (_event, ctx) => {
|
|
75
|
+
if (pendingCompactions.length > 0 && !compactionRunning) {
|
|
76
|
+
setTimeout(() => void runQueuedCompactions(ctx), 0);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
pi.registerTool({
|
|
81
|
+
name: 'request_compaction',
|
|
82
|
+
label: 'Request Compaction',
|
|
83
|
+
description: 'Queue pi context compaction to run as soon as the current agent turn finishes. Use only when the user, a skill, or a prompt explicitly asks for compaction. If more work should continue after compaction, provide continuationPrompt as a concise summary of the remaining plan rather than the original prompt verbatim. This tool does not ask for confirmation; workflows that need confirmation must ask the user before calling it.',
|
|
84
|
+
promptSnippet: 'request_compaction(customInstructions?, reason?, continuationPrompt?) - queue context compaction after the current turn, then optionally continue from a summarized remaining plan.',
|
|
85
|
+
promptGuidelines: [
|
|
86
|
+
'Use request_compaction only when compaction was explicitly requested by the user, skill, or prompt.',
|
|
87
|
+
'If user confirmation is required, ask the user before calling request_compaction; this tool does not ask for confirmation.',
|
|
88
|
+
'Use request_compaction when the user asks to compact now or before continuing to later steps in the same task.',
|
|
89
|
+
'Provide customInstructions that preserve goals, decisions, files read/modified, blockers, and next steps.',
|
|
90
|
+
'When work should continue after compaction, provide continuationPrompt as a concise, self-contained summary of the remaining plan; do not copy the original prompt verbatim.',
|
|
91
|
+
'If the original plan explicitly requires later compaction checkpoints, include those checkpoints in continuationPrompt so request_compaction can be called again later; otherwise do not repeat the same compaction request.',
|
|
92
|
+
],
|
|
93
|
+
parameters: RequestCompactionParams,
|
|
94
|
+
executionMode: 'sequential',
|
|
95
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
96
|
+
const customInstructions = params.customInstructions?.trim() || DEFAULT_COMPACTION_INSTRUCTIONS;
|
|
97
|
+
const reason = params.reason?.trim() || 'The current instructions requested immediate context compaction.';
|
|
98
|
+
const continuationPrompt = params.continuationPrompt?.trim() || undefined;
|
|
99
|
+
|
|
100
|
+
pendingCompactions.push({ customInstructions, reason, continuationPrompt });
|
|
101
|
+
ctx.ui.notify(
|
|
102
|
+
continuationPrompt
|
|
103
|
+
? 'Compaction queued after the current turn. The summarized remaining plan will continue after compaction.'
|
|
104
|
+
: 'Compaction queued after the current turn.',
|
|
105
|
+
'info',
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
content: [{
|
|
110
|
+
type: 'text',
|
|
111
|
+
text: continuationPrompt
|
|
112
|
+
? 'Compaction queued. It will run after this agent turn finishes, then the summarized remaining plan will continue.'
|
|
113
|
+
: 'Compaction queued. It will run after this agent turn finishes.',
|
|
114
|
+
}],
|
|
115
|
+
details: { queued: true, continueAfterCompaction: continuationPrompt !== undefined, customInstructions, reason, continuationPrompt },
|
|
116
|
+
terminate: true,
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# mcp-bridge
|
|
2
|
+
|
|
3
|
+
A reusable factory for turning any stdio MCP server into a pi extension.
|
|
4
|
+
|
|
5
|
+
This is a **library**, not a standalone pi extension. It is consumed by other extensions in `.pi/extensions/` (for example, `chrome-devtools-mcp/`). pi will not load it directly.
|
|
6
|
+
|
|
7
|
+
## What it does
|
|
8
|
+
|
|
9
|
+
Given a config describing how to spawn an MCP server, the factory returns a pi extension that:
|
|
10
|
+
|
|
11
|
+
1. Spawns the MCP server over stdio (`StdioClientTransport`).
|
|
12
|
+
2. Lists the server's tools via `listTools`.
|
|
13
|
+
3. Registers each tool with pi under a configurable prefix.
|
|
14
|
+
4. Forwards calls and normalizes MCP content (`text`, `image`, `resource`) back to pi.
|
|
15
|
+
5. Registers a status command and cleans up on `session_shutdown`.
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { createMcpBridgeExtension } from "../mcp-bridge/index.js";
|
|
21
|
+
|
|
22
|
+
export default createMcpBridgeExtension({
|
|
23
|
+
prefix: "my_server__",
|
|
24
|
+
command: "npx",
|
|
25
|
+
args: ["-y", "some-mcp-server@latest"],
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Config
|
|
30
|
+
|
|
31
|
+
| Field | Required | Default | Description |
|
|
32
|
+
|---|---|---|---|
|
|
33
|
+
| `prefix` | yes | — | Prefix prepended to every registered pi tool name (e.g. `chrome_devtools__`). |
|
|
34
|
+
| `command` | yes | — | Executable used to spawn the MCP server (e.g. `npx`, `node`, an absolute path). |
|
|
35
|
+
| `args` | yes | — | Arguments passed to `command`. |
|
|
36
|
+
| `clientName` | no | `pi-<prefix>` | Identifier sent to the MCP server during connection. |
|
|
37
|
+
| `labelPrefix` | no | humanized `prefix` | Shown in pi tool labels and status messages. |
|
|
38
|
+
| `statusCommandName` | no | `<prefix>-status` | Slash command that reports bridge status. |
|
|
39
|
+
|
|
40
|
+
## Why this exists
|
|
41
|
+
|
|
42
|
+
pi does not yet have first-class MCP support. This bridge is a **stopgap**: it lets pi consume MCP servers today. When pi gains native MCP support, individual wrapper extensions can be retired or replaced; this library will then be deprecated.
|
|
43
|
+
|
|
44
|
+
## Dependencies
|
|
45
|
+
|
|
46
|
+
Runtime dependencies (`@modelcontextprotocol/sdk`, `typebox`) are declared once at `.pi/extensions/package.json` and installed via `npm ci` in the cloned `agent-skills` repo. Wrapper extensions resolve them from the hoisted `node_modules/` — no per-wrapper install required. `@mariozechner/pi-coding-agent` is supplied by the pi runtime that loads the extension.
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
|
|
4
|
+
export interface McpBridgeConfig {
|
|
5
|
+
prefix: string;
|
|
6
|
+
command: string;
|
|
7
|
+
args: string[];
|
|
8
|
+
env?: Record<string, string>;
|
|
9
|
+
stderr?: "inherit" | "pipe" | "overlapped";
|
|
10
|
+
clientName?: string;
|
|
11
|
+
labelPrefix?: string;
|
|
12
|
+
statusCommandName?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type JsonObject = Record<string, unknown>;
|
|
16
|
+
|
|
17
|
+
type McpTool = {
|
|
18
|
+
name: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
inputSchema?: JsonObject;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type McpClient = {
|
|
24
|
+
listTools(): Promise<{ tools?: McpTool[] }>;
|
|
25
|
+
callTool(input: { name: string; arguments: JsonObject }): Promise<{ isError?: boolean; content?: unknown }>;
|
|
26
|
+
close(): Promise<void>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type McpTransport = {
|
|
30
|
+
close?: () => Promise<void> | void;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function humanizePrefix(prefix: string): string {
|
|
34
|
+
return prefix
|
|
35
|
+
.replace(/__+$/, "")
|
|
36
|
+
.replace(/[_-]+/g, " ")
|
|
37
|
+
.replace(/\b\w/g, (c) => c.toUpperCase())
|
|
38
|
+
.trim();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function toPiToolName(prefix: string, mcpToolName: string): string {
|
|
42
|
+
const safeName = mcpToolName.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
43
|
+
return `${prefix}${safeName}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function normalizeInputSchema(tool: McpTool) {
|
|
47
|
+
const schema = tool.inputSchema as JsonObject | undefined;
|
|
48
|
+
if (!schema || typeof schema !== "object") {
|
|
49
|
+
return Type.Object({});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
type: "object",
|
|
54
|
+
properties: {},
|
|
55
|
+
additionalProperties: true,
|
|
56
|
+
...schema,
|
|
57
|
+
} as ReturnType<typeof Type.Object>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function stringifyMcpContent(content: unknown): string {
|
|
61
|
+
if (!Array.isArray(content)) {
|
|
62
|
+
return JSON.stringify(content, null, 2);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return content
|
|
66
|
+
.map((part) => {
|
|
67
|
+
if (!part || typeof part !== "object") return String(part);
|
|
68
|
+
const item = part as JsonObject;
|
|
69
|
+
|
|
70
|
+
if (item.type === "text" && typeof item.text === "string") {
|
|
71
|
+
return item.text;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (item.type === "image") {
|
|
75
|
+
const mimeType = typeof item.mimeType === "string" ? item.mimeType : "image/*";
|
|
76
|
+
return `[Image returned by MCP bridge: ${mimeType}]`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (item.type === "resource") {
|
|
80
|
+
return `[Resource returned by MCP bridge]\n${JSON.stringify(item.resource ?? item, null, 2)}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return JSON.stringify(item, null, 2);
|
|
84
|
+
})
|
|
85
|
+
.join("\n\n");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function errorWithStderr(error: unknown, stderr: string): Error {
|
|
89
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
90
|
+
const stderrExcerpt = stderr.trim().slice(-2_000);
|
|
91
|
+
return new Error(stderrExcerpt ? `${message}\n${stderrExcerpt}` : message);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// pi auto-discovers `.pi/extensions/*/index.ts` files. This module is primarily
|
|
95
|
+
// a library consumed by wrapper extensions (for example, `chrome-devtools-mcp`),
|
|
96
|
+
// but when the directory itself is symlinked as recommended it is also loaded as
|
|
97
|
+
// a project extension. Export a harmless default factory so that direct loading
|
|
98
|
+
// succeeds without registering tools; wrappers should import `createMcpBridgeExtension`.
|
|
99
|
+
export default function mcpBridgeLibraryExtension(_pi: ExtensionAPI) {
|
|
100
|
+
// Intentionally empty.
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function createMcpBridgeExtension(config: McpBridgeConfig) {
|
|
104
|
+
const {
|
|
105
|
+
prefix,
|
|
106
|
+
command,
|
|
107
|
+
args,
|
|
108
|
+
env,
|
|
109
|
+
stderr = "inherit",
|
|
110
|
+
clientName = `pi-${prefix.replace(/__+$/, "")}`,
|
|
111
|
+
labelPrefix = humanizePrefix(prefix),
|
|
112
|
+
statusCommandName = `${prefix.replace(/__+$/, "")}-status`,
|
|
113
|
+
} = config;
|
|
114
|
+
|
|
115
|
+
async function connect(): Promise<{ client: McpClient; transport: McpTransport }> {
|
|
116
|
+
const [{ Client }, { StdioClientTransport }] = await Promise.all([
|
|
117
|
+
import("@modelcontextprotocol/sdk/client/index.js"),
|
|
118
|
+
import("@modelcontextprotocol/sdk/client/stdio.js"),
|
|
119
|
+
]);
|
|
120
|
+
const client = new Client({ name: clientName, version: "1.0.0" });
|
|
121
|
+
const transport = new StdioClientTransport({ command, args, env, stderr });
|
|
122
|
+
let stderrOutput = "";
|
|
123
|
+
transport.stderr?.on("data", (chunk: Buffer) => {
|
|
124
|
+
stderrOutput += chunk.toString();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
await client.connect(transport);
|
|
129
|
+
return { client, transport };
|
|
130
|
+
} catch (error) {
|
|
131
|
+
await transport.close().catch(() => undefined);
|
|
132
|
+
throw errorWithStderr(error, stderrOutput);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return async function mcpBridgeExtension(pi: ExtensionAPI) {
|
|
137
|
+
let client: McpClient | undefined;
|
|
138
|
+
let transport: McpTransport | undefined;
|
|
139
|
+
let tools: McpTool[] = [];
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const connection = await connect();
|
|
143
|
+
client = connection.client;
|
|
144
|
+
transport = connection.transport;
|
|
145
|
+
tools = (await client.listTools()).tools ?? [];
|
|
146
|
+
} catch (error) {
|
|
147
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
148
|
+
pi.registerCommand(statusCommandName, {
|
|
149
|
+
description: `Show ${labelPrefix} MCP bridge status`,
|
|
150
|
+
handler: async (_args, ctx) => {
|
|
151
|
+
ctx.ui.notify(`${labelPrefix} MCP failed to start: ${message}`, "error");
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
for (const tool of tools) {
|
|
158
|
+
const piToolName = toPiToolName(prefix, tool.name);
|
|
159
|
+
|
|
160
|
+
pi.registerTool({
|
|
161
|
+
name: piToolName,
|
|
162
|
+
label: `${labelPrefix}: ${tool.name}`,
|
|
163
|
+
description: `${tool.description ?? `${labelPrefix} MCP tool`}\n\nWrapped MCP tool: ${tool.name}`,
|
|
164
|
+
promptSnippet: `${piToolName}: use ${labelPrefix} MCP tool '${tool.name}'.`,
|
|
165
|
+
parameters: normalizeInputSchema(tool),
|
|
166
|
+
executionMode: "sequential",
|
|
167
|
+
execute: async (_toolCallId, params, signal) => {
|
|
168
|
+
if (!client) {
|
|
169
|
+
throw new Error(`${labelPrefix} MCP client is not connected`);
|
|
170
|
+
}
|
|
171
|
+
if (signal?.aborted) {
|
|
172
|
+
throw new Error(`${labelPrefix} MCP tool call was aborted before it started`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const result = await client.callTool({
|
|
176
|
+
name: tool.name,
|
|
177
|
+
arguments: params as JsonObject,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
if (result.isError) {
|
|
181
|
+
throw new Error(stringifyMcpContent(result.content));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
content: [{ type: "text", text: stringifyMcpContent(result.content) }],
|
|
186
|
+
details: { mcpToolName: tool.name, rawResult: result },
|
|
187
|
+
};
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
pi.registerCommand(statusCommandName, {
|
|
193
|
+
description: `Show ${labelPrefix} MCP bridge status`,
|
|
194
|
+
handler: async (_args, ctx) => {
|
|
195
|
+
ctx.ui.notify(`${labelPrefix} MCP connected. Registered ${tools.length} tool(s).`, "success");
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
pi.on("session_shutdown", async () => {
|
|
200
|
+
await client?.close().catch(() => undefined);
|
|
201
|
+
await transport?.close?.().catch(() => undefined);
|
|
202
|
+
client = undefined;
|
|
203
|
+
transport = undefined;
|
|
204
|
+
});
|
|
205
|
+
};
|
|
206
|
+
}
|