Create extension
Skill Poorgramer-Zack/copilot-cli-things/plugins/create-extension/skills/create-extension
A curated collection of extensions, skills, and plugins for GitHub Copilot CLI.
npx -y skills add Poorgramer-Zack/copilot-cli-things --skill create-extensionAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Copilot CLI extension development: extension.mjs, hooks, tools, events, SDK API, JSON-RPC. Use when creating, building, debugging, or inspecting extensions, or asking about lifecycle, permissions, onPermissionRequest, joinSession, or session events.
SKILL.md
7.4 KB, as published. Nobody here has run it
Create Copilot CLI Extension
Prerequisites
Extensions require experimental mode. Before creating or testing an extension, ensure it is active:
- Launch flag:
copilot --experimental - Slash command (persistent):
/experimentalinside a running session
Without experimental mode, extensions will not be discovered or loaded.
What is an Extension
An extension is a JavaScript (.mjs) child process that communicates with the Copilot CLI via JSON-RPC over stdio. Extensions can register custom tools, intercept lifecycle events via hooks, and subscribe to real-time session events.
Extensions can: register tools the agent calls, intercept/modify prompts and tool calls, inject hidden context, send messages programmatically, subscribe to events, control error handling.
Extensions cannot: render interactive UI, use console.log(), share tool names with other extensions, use TypeScript directly.
File Location
Project-level (per-repo):
<git-root>/.github/extensions/<name>/extension.mjs
User-level (global, all repos):
~/.copilot/extensions/<name>/extension.mjs
The file must be named extension.mjs. Only immediate subdirectories are scanned. Project extensions shadow user extensions on name collision.
Extension Lifecycle
- Discovery — CLI scans extension directories for subdirectories containing
extension.mjs - Launch — Each extension is forked as a child process with the SDK module resolver pre-configured
- Connection — Extension calls
joinSession()to establish JSON-RPC over stdio - Registration — Tools and hooks from session options are registered with the CLI
- Active — Extension responds to tool calls, hooks fire on events, session APIs are available
- Reload — Extensions are stopped and re-launched on
/clear,extensions_reload(), or session replacement - Shutdown — On CLI exit: SIGTERM, then SIGKILL after 5 seconds
All in-memory state is lost on reload. Persist important data to disk.
Core Concepts
Tools
Custom functions the agent can call. Each tool requires:
name— Globally unique identifier (snake_case recommended)description— What it does (agent reads this to decide when to call it)parameters— JSON Schema for arguments (optional)handler— Async function returningstring,{ textResultForLlm, resultType }, orundefined
Result types: "success", "failure", "rejected", "denied". Throwing an error sends failure.
Hooks
Lifecycle interceptors. All receive (input, invocation) where input includes timestamp and cwd.
| Hook | When | Can Return |
|---|---|---|
onUserPromptSubmitted | User sends message | modifiedPrompt, additionalContext |
onPreToolUse | Before tool executes | permissionDecision, modifiedArgs, additionalContext |
onPostToolUse | After tool executes | modifiedResult, additionalContext |
onSessionStart | Session starts/resumes | additionalContext |
onSessionEnd | Session ends | sessionSummary, cleanupActions |
onErrorOccurred | Error occurs | errorHandling, retryCount, userNotification |
Events
Subscribe via session.on(eventType, handler). Returns an unsubscribe function.
| Event | Key Data Fields |
|---|---|
assistant.message | content, messageId |
tool.execution_start | toolCallId, toolName, arguments |
tool.execution_complete | toolCallId, toolName, success, result, error |
user.message | content, attachments, source |
session.idle | backgroundTasks |
session.shutdown | shutdownType, totalPremiumRequests |
Session Object
Returned by joinSession():
session.send({ prompt, attachments? })— Fire-and-forget message to agentsession.sendAndWait({ prompt }, timeout?)— Send and wait for agent replysession.log(message, { level?, ephemeral? }?)— Write to CLI timelinesession.on(eventType, handler)— Subscribe to eventssession.workspacePath— Session workspace directory path
Permission Handling
Every extension must provide onPermissionRequest. Use approveAll from the SDK for simple cases, or implement custom logic:
import { approveAll } from "@github/copilot-sdk";
// onPermissionRequest: approveAll
Critical Rules
- File MUST be
extension.mjs— No.js,.ts,.cjs - Never use
console.log()— stdout is JSON-RPC transport; usesession.log() - Tool names must be globally unique — Collision causes the second extension to fail
- Don't install
@github/copilot-sdk— Auto-provided by the CLI runtime - Always wrap handlers in try/catch — Unhandled errors crash the extension process
- No
session.send()insideonUserPromptSubmitted— Causes infinite loops; usesetTimeout(() => session.send(...), 0) - Use ES module syntax —
import/export, notrequire()/module.exports
Creation Workflow
Step 1: Determine Extension Location
Ask the user or infer from context:
- Project-level:
.github/extensions/<name>/— For repo-specific tools - User-level:
~/.copilot/extensions/<name>/— For personal/global tools
Step 2: Create Directory and File
Create the extension directory and copy the template:
<location>/<name>/extension.mjs
Use the template from examples/extension-template.mjs as the starting point. The template includes all tool, hook, and event patterns commented out.
Alternatively, run the scaffold script:
bash scripts/scaffold-extension.sh <extension-name>
Step 3: Implement Required Functionality
Based on the user's requirements:
- Uncomment and fill in the relevant tool, hook, or event sections
- Delete sections that are not needed
- Add imports for any Node.js built-in modules (e.g.,
child_process,fs)
Step 4: Reload Extensions
extensions_reload()
Step 5: Verify
extensions_manage({ operation: "inspect", name: "<name>" })
If the extension shows as "failed", check for:
- Syntax errors in the
.mjsfile - Tool name collisions with other extensions
- Missing
onPermissionRequesthandler - Use of
console.log()(breaks JSON-RPC)
Platform Notes
Handle platform differences when running shell commands:
const isWindows = process.platform === "win32";
const shell = isWindows ? "powershell" : "bash";
const shellArgs = isWindows
? ["-NoProfile", "-Command", command]
: ["-c", command];
| Concern | Windows | macOS/Linux |
|---|---|---|
| Clipboard | clip | pbcopy / xclip |
| Shell | PowerShell | bash |
| Path separator | \ | / |
Quick Reference
- Full API spec:
references/extension-spec.md - Patterns and recipes:
references/patterns-and-recipes.md - Template file:
examples/extension-template.mjs - Scaffold script:
scripts/scaffold-extension.sh