一键导入
extension-authoring
Step-by-step guide to building, installing, and testing a SynapsCLI extension — manifest, handshake, tool, hook, install, and test.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Step-by-step guide to building, installing, and testing a SynapsCLI extension — manifest, handshake, tool, hook, install, and test.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | extension-authoring |
| description | Step-by-step guide to building, installing, and testing a SynapsCLI extension — manifest, handshake, tool, hook, install, and test. |
Source of truth:
{baseDir}/../../../docs/extensions/tutorial.mdRead that file for the complete worked example. This skill is a structured summary + checklist you can keep in context while authoring.
A separate process SynapsCLI spawns and talks to over stdin/stdout using
JSON-RPC 2.0 with Content-Length framing. The runtime calls methods on
you; you only respond. Any language works — Python 3 (no deps) is the tutorial
language.
Two capabilities:
tools.register permission required).hook.* permission required per hook type).plugin.json)Create .synaps-plugin/plugin.json in your extension root:
{
"name": "hello-ext",
"version": "0.1.0",
"description": "Hello-world extension",
"extension": {
"entry_point": "python3 main.py",
"extension_protocol_version": 1,
"permissions": [
"tools.register",
"hooks.before_tool_call"
]
}
}
Key fields:
entry_point — shell command to start your process (resolved from plugin root).extension_protocol_version — must be 1.permissions — declare every capability you use; undeclared calls are rejected.Every message is a Content-Length: N\r\n\r\n header followed by N bytes of
JSON. Write helpers once:
import sys, json
def send(obj):
body = json.dumps(obj).encode()
sys.stdout.buffer.write(b"Content-Length: " + str(len(body)).encode() + b"\r\n\r\n" + body)
sys.stdout.buffer.flush()
def recv():
header = b""
while not header.endswith(b"\r\n\r\n"):
header += sys.stdin.buffer.read(1)
n = int(header.split(b"Content-Length:")[1].strip().split()[0])
return json.loads(sys.stdin.buffer.read(n))
initialize)The runtime sends initialize first. You must respond with your capabilities:
def handle_initialize(req):
send({
"jsonrpc": "2.0", "id": req["id"],
"result": {
"name": "hello-ext",
"version": "0.1.0",
"protocol_version": 1,
"capabilities": {
"tools": [
{
"name": "hello",
"description": "Greet someone",
"input_schema": {
"type": "object",
"properties": {"name": {"type": "string"}},
"required": ["name"]
}
}
],
"hooks": [{"name": "before_tool_call"}]
}
}
})
Only declare tools/hooks whose permissions you listed in plugin.json.
tool.call)def handle_tool_call(req):
name_arg = req["params"]["input"].get("name", "world")
send({
"jsonrpc": "2.0", "id": req["id"],
"result": {"output": f"Hello, {name_arg}!"}
})
params.name — the tool name.
params.input — the JSON object matching your input_schema.
Return result.output (string shown to the model).
hook.handle)def handle_hook(req):
# Inspect req["params"]["hook"] and req["params"]["context"]
# To allow: return result: {}
# To block: return result: {"action": "block", "reason": "..."}
send({"jsonrpc": "2.0", "id": req["id"], "result": {}})
Available actions (depending on hook type): allow, block, modify,
confirm, inject. See docs/extensions/hooks.md for per-hook details.
def main():
while True:
req = recv()
method = req.get("method", "")
if method == "initialize":
handle_initialize(req)
elif method == "tool.call":
handle_tool_call(req)
elif method == "hook.handle":
handle_hook(req)
elif method == "shutdown":
send({"jsonrpc": "2.0", "id": req["id"], "result": {}})
break
if __name__ == "__main__":
main()
Tolerate unknown methods — return result: {} or ignore them; never crash.
# Option A: copy into the user plugins dir
cp -r hello-ext ~/.synaps-cli/plugins/
# Option B: symlink (dev mode)
ln -s $(pwd)/hello-ext ~/.synaps-cli/plugins/hello-ext
Then inside SynapsCLI:
/extensions reload
/extensions list # hello-ext should appear as "loaded"
Or start SynapsCLI fresh — plugins in ~/.synaps-cli/plugins/ auto-load.
Drive your extension over pipes to verify framing and logic before connecting to SynapsCLI:
# test_hello_ext.py
import subprocess, json, threading
def framed(obj):
body = json.dumps(obj).encode()
return b"Content-Length: " + str(len(body)).encode() + b"\r\n\r\n" + body
proc = subprocess.Popen(["python3", "main.py"],
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
proc.stdin.write(framed({"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}))
proc.stdin.flush()
# ... read response, assert capabilities ...
See docs/extensions/tutorial.md § "Step 8 — Test it without the runtime" for
the full reference test.
plugin.json present at .synaps-plugin/plugin.jsonextension_protocol_version: 1 setpermissionsinitialize response includes ALL tools and hooks you will use\r\n\r\n (not \n\n)shutdown response sent before exitAll paths relative to the repo root:
| File | Purpose |
|---|---|
docs/extensions/tutorial.md | Full worked example (this skill summarises it) |
docs/extensions/protocol.md | Authoritative wire protocol |
docs/extensions/hooks.md | Every hook, permission, allowed actions |
docs/extensions/permissions.md | What each permission unlocks |
docs/extensions/contract.json | Machine-readable contract (CI-checked) |
examples/extensions/hello-ext/ | Finished reference implementation |