name: extending-pi description: Plan and build Pi for Excel extensions safely: choose skill vs extension plugin vs connection, create flat SKILL.md skills, and handle API keys without asking users to paste secrets in chat. compatibility: Pi for Excel runtime. Plugin = runtime extension module. Assumes access to extensions_manager and skills tools. metadata: docs: docs/extensions.md related: docs/agent-skills-interop.md
Extending Pi (Pi for Excel)
Use this skill when the user asks to extend Pi, add capabilities, or integrate an external API/tool.
Default behavior (non-negotiable)
- Prefer solutions the assistant can set up and run itself.
- Never ask users to paste API keys/tokens/passwords in chat.
- Never create
/set-key ...style commands that take secrets from the prompt input. - Prefer first-class connection setup UX (
/tools→ Connections) for secret entry. - For API-backed plugin implementation/testing, proactively recommend local bridge setup:
- Preferred: Python bridge (best for API prototyping, payload transforms, quick debugging scripts).
- Fallback: tmux bridge (assistant can still run
curl, Node scripts, and CLI checks). - If neither is available, still scaffold the extension, but explicitly note reduced verification and offer setup help.
When recommending bridge setup, read the relevant setup skill before giving commands:
python-bridgetmux-bridge
1) Choose the right extension surface
| Goal | Build | Why |
|---|---|---|
| Teach Pi a workflow, process, or decision rubric | Skill | Lowest maintenance; mostly instruction logic |
| Add runtime behavior (slash command, tool, widget, event handling, storage/http) | Extension plugin | Requires executable code |
| Add secure credential/config setup for extension tools | Extension connection | First-class setup/status/preflight/error guidance |
| Save one-off user/workbook preferences | instructions tool | Not an extension |
Heuristic: if instructions + existing tools are enough, build a skill first.
2) Skill authoring (adapted from skill-creator, flat profile)
For this repo, default to flat skills: one skills/<name>/SKILL.md file.
Only add extra folders if clearly needed.
Core principles
- Conciseness: only include domain-specific guidance the model cannot infer reliably.
- Activation quality: trigger matching comes from frontmatter
description; make it precise. - Context over directives: explain why an approach works, not just rigid commands.
- Progressive disclosure (lightweight): keep SKILL.md focused; avoid dumping long reference text.
Required format and naming
- Required frontmatter:
name,description - Directory name must equal
name - Name rules:
- 1-64 chars
- lowercase letters, digits, hyphens
- no leading/trailing/consecutive hyphens
Workflow
- Capture 2-4 concrete user prompts the skill should handle.
- Create
skills/<name>/SKILL.md. - Write frontmatter:
---
name: my-skill
description: What it does + when to use it.
compatibility: Optional constraints.
---
- Write body with:
- purpose and boundaries
- mapping to relevant tools/integrations
- step-by-step workflow
- guardrails/pitfalls
- Install/update via
skillstool (install/uninstall) when available; otherwise use/extensions→ Skills. - Validate with
skillstool:action="list"action="read"for the new skill.
3) Extension plugin workflow
Use when the user needs new executable behavior.
- Build a single-file ES module with
activate(api). - Register only required surfaces (
registerCommand,registerTool, widget, etc.). - Keep capabilities least-privilege.
- Install from chat using
extensions_manager(install_code,set_enabled,reload,uninstall). - Validate by invoking the new command/tool end-to-end.
Minimal skeleton:
export function activate(api) {
api.registerCommand("hello_ext", {
description: "Example command",
handler: () => api.toast("Hello from extension"),
});
return () => {
api.widget.dismiss();
api.overlay.dismiss();
};
}
4) API keys/secrets: secure-by-default pattern
Tier 1 (default): host-injected auth via http.fetch(..., { connection })
For API-backed extension tools:
- Register a connection in
activate(api). - Add
httpAuthon the connection definition with a strictallowedHostslist. - Mark tools with
requiresConnection. - Direct user to
/tools→ Connections for secret entry. - Call
api.http.fetch(url, { connection: "<id>" }).
Connection registration template:
api.connections.register({
id: "acme",
title: "Acme API",
capability: "query Acme records",
authKind: "api_key",
secretFields: [{ id: "apiKey", label: "API key", required: true, maskInUi: true }],
httpAuth: {
placement: "header",
headerName: "Authorization",
valueTemplate: "Bearer {apiKey}",
allowedHosts: ["api.acme.com"],
},
setupHint: "Open /tools → Connections → Extension connections",
});
Tool call template:
const response = await api.http.fetch("https://api.acme.com/v1/search?q=...", {
method: "GET",
connection: "acme",
});
Tier 2 (escape hatch): connections.getSecrets
Only when Tier 1 cannot support the integration (SDK bootstrap, custom request signing, etc.):
const secrets = await api.connections.getSecrets("acme");
Guardrails:
- Requires
connections.secrets.readcapability. - Never echo/log secrets.
- Still keep user secret entry in
/tools→ Connections.
Forbidden patterns
/set-key ...slash commands- "Paste your API key in chat"
- Logging/echoing secrets in tool output/errors
5) Standard reusable “secure connection bundle”
When generating API-backed extension code, use docs/extensions-secure-connection-bundle.md as the default scaffold.
Default scaffold requirements:
- connection definition +
httpAuth - strict
allowedHosts - tool
requiresConnection - host-injected auth path first
- no-chat-secret policy in comments/docs
References
docs/extensions.mddocs/extensions-secure-connection-bundle.mddocs/agent-skills-interop.md- skill:
skill-creator