mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
Add Claude Code plugin structure for marketplace distribution
- Rename apps/hooks → apps/hook (singular) - Add .claude-plugin/plugin.json manifest - Add hooks/hooks.json for PermissionRequest hook - Add root .claude-plugin/marketplace.json for install via: /plugin marketplace add backnotprop/plannotator - Refactor server to read stdin directly (removes bash/jq dependency) - Delete shell wrapper script Plugin tested successfully with: claude --plugin-dir ./apps/hook 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "plannotator-marketplace",
|
||||
"owner": {
|
||||
"name": "backnotprop"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "plannotator",
|
||||
"source": "./apps/hook",
|
||||
"description": "Interactive Plan Review: Mark up and refine your plans using native Claude Code hooks, then easily share for team collaboration."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "plannotator",
|
||||
"description": "Interactive Plan Review: Mark up and refine your plans using native Claude Code hooks, then easily share for team collaboration.",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "backnotprop"
|
||||
},
|
||||
"repository": "https://github.com/backnotprop/plannotator",
|
||||
"keywords": ["planning", "review", "hooks", "ExitPlanMode", "collaboration"]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"hooks": {
|
||||
"PermissionRequest": [
|
||||
{
|
||||
"matcher": "ExitPlanMode",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bun run ${CLAUDE_PLUGIN_ROOT}/server/index.ts",
|
||||
"timeout": 600
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -4,16 +4,26 @@
|
||||
* Spawned by ExitPlanMode hook to serve Plannotator UI and handle approve/deny decisions.
|
||||
* Uses random port to support multiple concurrent Claude Code sessions.
|
||||
*
|
||||
* Usage: bun run server/index.ts "<plan markdown content>"
|
||||
* Reads hook event from stdin, extracts plan content, serves UI, returns decision.
|
||||
*/
|
||||
|
||||
import { $ } from "bun";
|
||||
import { join, dirname } from "path";
|
||||
|
||||
const planContent = process.argv[2] || Bun.env.PLAN_CONTENT || "";
|
||||
// Read hook event from stdin
|
||||
const eventJson = await Bun.stdin.text();
|
||||
|
||||
let planContent = "";
|
||||
try {
|
||||
const event = JSON.parse(eventJson);
|
||||
planContent = event.tool_input?.plan || "";
|
||||
} catch {
|
||||
console.error("Failed to parse hook event from stdin");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!planContent) {
|
||||
console.error("No plan content provided. Usage: bun run server/index.ts \"<plan>\"");
|
||||
console.error("No plan content in hook event");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -63,7 +73,6 @@ const server = Bun.serve({
|
||||
|
||||
const file = Bun.file(join(distDir, filePath));
|
||||
if (await file.exists()) {
|
||||
// Set appropriate content types
|
||||
const contentType = getContentType(filePath);
|
||||
return new Response(file, {
|
||||
headers: contentType ? { "Content-Type": contentType } : {}
|
||||
@@ -1,39 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# ExitPlanMode Hook - Launches Plannotator for plan approval/denial
|
||||
#
|
||||
# This hook intercepts ExitPlanMode, spawns a Bun server with the plan content,
|
||||
# and blocks until the user approves or denies (with feedback).
|
||||
#
|
||||
# Exit codes:
|
||||
# 0 = User approved → ExitPlanMode proceeds
|
||||
# 2 = User denied → feedback sent to Claude via stderr
|
||||
#
|
||||
|
||||
# Get the directory where this hook lives
|
||||
HOOK_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PLANNING_HOOK_DIR="$(dirname "$HOOK_DIR")"
|
||||
SERVER_SCRIPT="$PLANNING_HOOK_DIR/server/index.ts"
|
||||
|
||||
# Read the event from stdin
|
||||
EVENT=$(cat)
|
||||
|
||||
# Check if this is ExitPlanMode
|
||||
TOOL_NAME=$(echo "$EVENT" | jq -r '.tool_name // empty')
|
||||
|
||||
if [ "$TOOL_NAME" = "ExitPlanMode" ]; then
|
||||
# Extract plan content
|
||||
PLAN=$(echo "$EVENT" | jq -r '.tool_input.plan // empty')
|
||||
|
||||
if [ -n "$PLAN" ] && [ "$PLAN" != "null" ]; then
|
||||
# Launch Bun server with plan content
|
||||
# Server blocks until user makes a decision (approve/deny)
|
||||
# Server exits 0 for approve, 2 for deny (with feedback to stderr)
|
||||
cd "$PLANNING_HOOK_DIR"
|
||||
bun run "$SERVER_SCRIPT" "$PLAN"
|
||||
exit $?
|
||||
fi
|
||||
fi
|
||||
|
||||
# If not ExitPlanMode or no plan, allow through
|
||||
exit 0
|
||||
+2
-2
@@ -3,10 +3,10 @@
|
||||
"private": true,
|
||||
"workspaces": ["apps/*", "packages/*"],
|
||||
"scripts": {
|
||||
"dev:hooks": "bun run --cwd apps/hooks dev",
|
||||
"dev:hook": "bun run --cwd apps/hook dev",
|
||||
"dev:portal": "bun run --cwd apps/portal dev",
|
||||
"dev:marketing": "bun run --cwd apps/marketing dev",
|
||||
"build:hooks": "bun run --cwd apps/hooks build",
|
||||
"build:hook": "bun run --cwd apps/hook build",
|
||||
"build:portal": "bun run --cwd apps/portal build",
|
||||
"build:marketing": "bun run --cwd apps/marketing build"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user