Consolidate into single self-contained script

Collapse the entire multi-file structure (bin/, lib/, prompts/) into
one executable bash script at the repo root. Everything is inlined:
frontmatter helpers, prompt strings, and the Claude stream-json
progress display (now a function calling node --input-type=module).

- plan-status is now `plan-refine status` subcommand
- Remove dead Puppeteer/Brave CDP code (chatgpt-send.mjs,
  plan-refine-brave) — superseded by Codex CLI approach
- Remove npm artifacts (package.json, package-lock.json) — sole
  dependency was puppeteer-core for the dead browser automation
- Simplify install.sh to just symlink + prereq checks
- Update .gitignore: node_modules/ -> *.feedback-*.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Taylor Eernisse
2026-02-08 15:00:48 -05:00
parent 6c93d7225c
commit 482eb87b4c
11 changed files with 474 additions and 1765 deletions

2
.gitignore vendored
View File

@@ -1 +1 @@
node_modules/
*.feedback-*.md

View File

@@ -1,257 +0,0 @@
#!/usr/bin/env bash
# plan-refine — Fully automated plan iteration: ChatGPT review + Claude integration
# Usage: plan-refine <plan-file> [options]
#
# Connects to your running Brave browser via CDP, sends the plan to ChatGPT,
# captures the response, then runs Claude CLI to integrate the feedback.
# Zero copy/paste required.
set -euo pipefail
# Resolve symlinks to find real script location
SOURCE="${BASH_SOURCE[0]}"
while [[ -L "$SOURCE" ]]; do
DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
done
SCRIPT_DIR="$(cd -P "$(dirname "$SOURCE")/.." && pwd)"
PROMPTS_DIR="$SCRIPT_DIR/prompts"
LIB_DIR="$SCRIPT_DIR/lib"
source "$SCRIPT_DIR/lib/frontmatter.sh"
# Defaults
CLAUDE_MODEL=""
DRY_RUN=false
NO_INTEGRATE=false
PLAN_FILE=""
INIT_ONLY=false
CDP_PORT="${CHATGPT_CDP_PORT:-9222}"
CHATGPT_TIMEOUT=600
usage() {
cat <<'EOF'
plan-refine — Fully automated plan iteration via Brave + Claude CLI
Usage: plan-refine <plan-file> [options]
Runs the full cycle automatically:
1. Sends plan to ChatGPT via your running Brave browser (CDP)
2. Captures ChatGPT's response
3. Claude CLI integrates feedback back into the plan file
Requires: Brave running with --remote-debugging-port=9222
Relaunch: open -a "Brave Browser" --args --remote-debugging-port=9222
Options:
--dry-run Preview what would happen
--no-integrate Get ChatGPT feedback only, skip Claude integration
--claude-model <m> Claude model for integration (default: your default)
--timeout <seconds> ChatGPT response timeout (default: 600)
--cdp-port <port> Brave CDP port (default: 9222, or CHATGPT_CDP_PORT env)
--init Add plan frontmatter to file without running anything
-h, --help Show this help
EOF
exit 0
}
while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run) DRY_RUN=true; shift ;;
--no-integrate) NO_INTEGRATE=true; shift ;;
--claude-model) CLAUDE_MODEL="$2"; shift 2 ;;
--timeout) CHATGPT_TIMEOUT="$2"; shift 2 ;;
--cdp-port) CDP_PORT="$2"; shift 2 ;;
--init) INIT_ONLY=true; shift ;;
-h|--help) usage ;;
-*) echo "Unknown option: $1" >&2; exit 2 ;;
*) PLAN_FILE="$1"; shift ;;
esac
done
if [[ -z "$PLAN_FILE" ]]; then
echo "Error: No plan file specified" >&2
echo "Usage: plan-refine <plan-file>" >&2
exit 2
fi
# Resolve to absolute path
PLAN_FILE="$(cd "$(dirname "$PLAN_FILE")" && pwd)/$(basename "$PLAN_FILE")"
if [[ ! -f "$PLAN_FILE" ]]; then
echo "Error: Plan file not found: $PLAN_FILE" >&2
exit 1
fi
# Ensure frontmatter exists
if ! head -1 "$PLAN_FILE" | grep -q '^---$'; then
echo "No frontmatter found. Initializing..."
init_frontmatter "$PLAN_FILE"
fi
if ! is_plan_file "$PLAN_FILE"; then
set_frontmatter "$PLAN_FILE" "plan" "true"
fi
if [[ "$INIT_ONLY" == "true" ]]; then
init_frontmatter "$PLAN_FILE"
echo "Frontmatter initialized for: $PLAN_FILE"
exit 0
fi
# Read current state
ITERATION=$(get_frontmatter "$PLAN_FILE" "iteration" "0")
TARGET=$(get_frontmatter "$PLAN_FILE" "target_iterations" "8")
STATUS=$(get_frontmatter "$PLAN_FILE" "status" "drafting")
NEXT_ITERATION=$((ITERATION + 1))
PLAN_DIR=$(dirname "$PLAN_FILE")
# Build paths
FEEDBACK_FILE="${PLAN_FILE%.md}.feedback-${NEXT_ITERATION}.md"
# Read prompts
EVAL_PROMPT=$(cat "$PROMPTS_DIR/chatgpt-eval.md")
INTEGRATE_PROMPT=$(cat "$PROMPTS_DIR/claude-integrate.md")
# Status check
if [[ "$STATUS" == "ready" || "$STATUS" == "implementing" || "$STATUS" == "completed" ]]; then
echo "Warning: Plan status is '$STATUS' -- already past iteration phase."
echo "Continue anyway? (y/N)"
read -r confirm
[[ "$confirm" != "y" && "$confirm" != "Y" ]] && exit 0
fi
echo "=== plan-refine ==="
echo " Plan: $(basename "$PLAN_FILE")"
echo " Status: $STATUS"
echo " Iteration: $ITERATION -> $NEXT_ITERATION (target: $TARGET)"
echo " Feedback: $(basename "$FEEDBACK_FILE")"
if [[ "$NO_INTEGRATE" == "true" ]]; then
echo " Mode: ChatGPT review only"
else
echo " Mode: Full cycle (ChatGPT + Claude integration)"
fi
echo ""
if [[ "$DRY_RUN" == "true" ]]; then
echo "=== DRY RUN ==="
echo ""
echo "Step 1: ChatGPT via Brave CDP (port $CDP_PORT)"
echo " node $LIB_DIR/chatgpt-send.mjs <prompt-file> $FEEDBACK_FILE --timeout $CHATGPT_TIMEOUT"
echo ""
if [[ "$NO_INTEGRATE" != "true" ]]; then
echo "Step 2: Claude CLI integration"
echo " claude -p <integration-prompt> --allowedTools Read,Edit,Write --permission-mode acceptEdits"
fi
exit 0
fi
# ──────────────────────────────────────────────
# Check Brave CDP connectivity
# ──────────────────────────────────────────────
if ! curl -s "http://127.0.0.1:${CDP_PORT}/json/version" >/dev/null 2>&1; then
echo "Error: Cannot connect to Brave on port $CDP_PORT" >&2
echo "" >&2
echo "Brave needs to be running with CDP enabled. Do this once:" >&2
echo " 1. Quit Brave (Cmd+Q)" >&2
echo " 2. open -a \"Brave Browser\" --args --remote-debugging-port=$CDP_PORT" >&2
echo "" >&2
echo "Or set CHATGPT_CDP_PORT if using a different port." >&2
exit 1
fi
# ──────────────────────────────────────────────
# Step 1: Send to ChatGPT via Brave
# ──────────────────────────────────────────────
echo "[Step 1] Sending plan to ChatGPT via Brave..."
# Build the full prompt file (eval prompt + plan content)
PROMPT_TMPFILE=$(mktemp /tmp/plan-refine-prompt-XXXXXX.md)
{
echo "$EVAL_PROMPT"
echo ""
echo "---"
echo ""
cat "$PLAN_FILE"
} > "$PROMPT_TMPFILE"
CHATGPT_CDP_PORT="$CDP_PORT" node "$LIB_DIR/chatgpt-send.mjs" \
"$PROMPT_TMPFILE" \
"$FEEDBACK_FILE" \
--timeout "$CHATGPT_TIMEOUT"
CHATGPT_EXIT=$?
rm -f "$PROMPT_TMPFILE"
if [[ $CHATGPT_EXIT -ne 0 ]]; then
echo "Error: ChatGPT send failed (exit $CHATGPT_EXIT)" >&2
exit $CHATGPT_EXIT
fi
echo "ChatGPT feedback saved to: $FEEDBACK_FILE"
# ──────────────────────────────────────────────
# Step 2: Claude integration
# ──────────────────────────────────────────────
if [[ "$NO_INTEGRATE" == "true" ]]; then
echo ""
echo "Skipping integration (--no-integrate)."
echo "Run manually: plan-refine $(basename "$PLAN_FILE") --integrate-only"
else
echo ""
echo "[Step 2] Claude integrating feedback into plan..."
CLAUDE_PROMPT="Read the original plan at: ${PLAN_FILE}
Read ChatGPT's feedback at: ${FEEDBACK_FILE}
${INTEGRATE_PROMPT}
Important instructions:
- Write the updated plan back to: ${PLAN_FILE}
- Preserve the YAML frontmatter block (between the --- delimiters) at the top unchanged.
- Only modify the content below the frontmatter.
- Do NOT output the plan to stdout. Write it directly to the file."
CLAUDE_MODEL_ARGS=()
if [[ -n "$CLAUDE_MODEL" ]]; then
CLAUDE_MODEL_ARGS+=(--model "$CLAUDE_MODEL")
fi
claude -p "$CLAUDE_PROMPT" \
--allowedTools "Read,Edit,Write" \
--permission-mode acceptEdits \
--add-dir "$PLAN_DIR" \
"${CLAUDE_MODEL_ARGS[@]}"
CLAUDE_EXIT=$?
if [[ $CLAUDE_EXIT -ne 0 ]]; then
echo "Error: Claude integration failed (exit $CLAUDE_EXIT)" >&2
echo "Feedback still available at: $FEEDBACK_FILE" >&2
exit $CLAUDE_EXIT
fi
echo "Integration complete. Plan updated."
fi
# ──────────────────────────────────────────────
# Update frontmatter
# ──────────────────────────────────────────────
set_frontmatter "$PLAN_FILE" "iteration" "$NEXT_ITERATION"
set_frontmatter "$PLAN_FILE" "updated" "$(date +%Y-%m-%d)"
if [[ "$STATUS" == "drafting" ]]; then
set_frontmatter "$PLAN_FILE" "status" "iterating"
fi
echo ""
echo "=== Iteration $NEXT_ITERATION/$TARGET complete ==="
if [[ $NEXT_ITERATION -ge $TARGET ]]; then
echo ""
echo "Target iterations reached. Plan may be ready for bead splitting."
fi

View File

@@ -1,191 +0,0 @@
#!/usr/bin/env bash
# plan-status — Scan projects for plan files and show pipeline status
# Usage: plan-status [search-root] [options]
set -euo pipefail
# Resolve symlinks to find real script location
SOURCE="${BASH_SOURCE[0]}"
while [[ -L "$SOURCE" ]]; do
DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
done
SCRIPT_DIR="$(cd -P "$(dirname "$SOURCE")/.." && pwd)"
source "$SCRIPT_DIR/lib/frontmatter.sh"
SEARCH_ROOT="${HOME}/projects"
JSON_OUTPUT=false
FILTER_STATUS=""
usage() {
cat <<'EOF'
plan-status — Dashboard for plan files across projects
Usage: plan-status [search-root] [options]
Scans for markdown files with plan: true frontmatter and shows
their position in the refinement pipeline.
Pipeline: drafting -> iterating -> splitting -> refining -> ready -> implementing -> completed
Options:
--json Machine-readable JSON output
--status <status> Filter to plans with this status
--root <path> Search root (default: ~/projects)
-h, --help Show this help
EOF
exit 0
}
while [[ $# -gt 0 ]]; do
case "$1" in
--json) JSON_OUTPUT=true; shift ;;
--status) FILTER_STATUS="$2"; shift 2 ;;
--root) SEARCH_ROOT="$2"; shift 2 ;;
-h|--help) usage ;;
-*) echo "Unknown option: $1" >&2; exit 2 ;;
*) SEARCH_ROOT="$1"; shift ;;
esac
done
if [[ ! -d "$SEARCH_ROOT" ]]; then
echo "Error: Search root not found: $SEARCH_ROOT" >&2
exit 1
fi
# Collect plan files
declare -a PLAN_FILES=()
while IFS= read -r -d '' file; do
if is_plan_file "$file"; then
PLAN_FILES+=("$file")
fi
done < <(find "$SEARCH_ROOT" \
-name "*.md" \
-not -path "*/node_modules/*" \
-not -path "*/.git/*" \
-not -path "*/.jj/*" \
-not -path "*/target/*" \
-not -name "*.feedback-*" \
-print0 2>/dev/null)
if [[ ${#PLAN_FILES[@]} -eq 0 ]]; then
if [[ "$JSON_OUTPUT" == "true" ]]; then
echo '{"plans":[],"summary":{"total":0}}'
else
echo "No plan files found under $SEARCH_ROOT"
echo "Initialize a plan: plan-refine <file.md> --init"
fi
exit 0
fi
if [[ "$JSON_OUTPUT" == "true" ]]; then
# JSON output
echo '{"plans":['
first=true
for file in "${PLAN_FILES[@]}"; do
status=$(get_frontmatter "$file" "status" "unknown")
[[ -n "$FILTER_STATUS" && "$status" != "$FILTER_STATUS" ]] && continue
title=$(get_frontmatter "$file" "title" "$(basename "$file" .md)")
iteration=$(get_frontmatter "$file" "iteration" "0")
target=$(get_frontmatter "$file" "target_iterations" "")
beads_rev=$(get_frontmatter "$file" "beads_revision" "0")
updated=$(get_frontmatter "$file" "updated" "")
project=$(echo "$file" | sed "s|${SEARCH_ROOT}/||" | cut -d/ -f1)
# Count feedback files
feedback_count=$(ls "${file%.md}".feedback-*.md 2>/dev/null | wc -l | tr -d ' ')
[[ "$first" != "true" ]] && echo ","
first=false
cat <<ENTRY
{"path":"$file","project":"$project","title":"$title","status":"$status","iteration":$iteration,"target_iterations":${target:-null},"beads_revision":$beads_rev,"feedback_files":$feedback_count,"updated":"$updated"}
ENTRY
done
echo ']}'
else
# Human-readable table
echo ""
echo " Plan Status Dashboard"
echo " $(date '+%Y-%m-%d %H:%M')"
echo " ════════════════════════════════════════════════════════════════════════"
printf " %-18s %-28s %-12s %-14s %s\n" "Project" "Plan" "Status" "Iterations" "Updated"
echo " ────────────────────────────────────────────────────────────────────────"
# Counters
total=0
by_status_drafting=0
by_status_iterating=0
by_status_splitting=0
by_status_refining=0
by_status_ready=0
by_status_implementing=0
by_status_completed=0
for file in "${PLAN_FILES[@]}"; do
status=$(get_frontmatter "$file" "status" "unknown")
[[ -n "$FILTER_STATUS" && "$status" != "$FILTER_STATUS" ]] && continue
title=$(get_frontmatter "$file" "title" "")
iteration=$(get_frontmatter "$file" "iteration" "0")
target=$(get_frontmatter "$file" "target_iterations" "")
beads_rev=$(get_frontmatter "$file" "beads_revision" "0")
updated=$(get_frontmatter "$file" "updated" "")
project=$(echo "$file" | sed "s|${SEARCH_ROOT}/||" | cut -d/ -f1)
plan_name=$(basename "$file" .md)
# Truncate long names
[[ ${#project} -gt 18 ]] && project="${project:0:15}..."
[[ ${#plan_name} -gt 28 ]] && plan_name="${plan_name:0:25}..."
# Format iteration progress
if [[ -n "$target" && "$target" != "0" ]]; then
iter_display="${iteration}/${target}"
else
iter_display="${iteration}"
fi
# Add beads revision if applicable
if [[ "$beads_rev" != "0" ]]; then
iter_display="${iter_display} br:${beads_rev}"
fi
printf " %-18s %-28s %-12s %-14s %s\n" \
"$project" "$plan_name" "$status" "$iter_display" "$updated"
total=$((total + 1))
case "$status" in
drafting) by_status_drafting=$((by_status_drafting + 1)) ;;
iterating) by_status_iterating=$((by_status_iterating + 1)) ;;
splitting) by_status_splitting=$((by_status_splitting + 1)) ;;
refining) by_status_refining=$((by_status_refining + 1)) ;;
ready) by_status_ready=$((by_status_ready + 1)) ;;
implementing) by_status_implementing=$((by_status_implementing + 1)) ;;
completed) by_status_completed=$((by_status_completed + 1)) ;;
esac
done
echo " ════════════════════════════════════════════════════════════════════════"
echo ""
# Summary line
summary_parts=()
[[ $by_status_drafting -gt 0 ]] && summary_parts+=("${by_status_drafting} drafting")
[[ $by_status_iterating -gt 0 ]] && summary_parts+=("${by_status_iterating} iterating")
[[ $by_status_splitting -gt 0 ]] && summary_parts+=("${by_status_splitting} splitting")
[[ $by_status_refining -gt 0 ]] && summary_parts+=("${by_status_refining} refining")
[[ $by_status_ready -gt 0 ]] && summary_parts+=("${by_status_ready} ready")
[[ $by_status_implementing -gt 0 ]] && summary_parts+=("${by_status_implementing} implementing")
[[ $by_status_completed -gt 0 ]] && summary_parts+=("${by_status_completed} completed")
IFS=", "
echo " ${total} plans: ${summary_parts[*]}"
echo ""
# Pipeline visualization
echo " Pipeline: drafting -> iterating -> splitting -> refining -> ready -> implementing -> completed"
echo ""
fi

View File

@@ -1,72 +1,9 @@
#!/usr/bin/env bash
# install.sh — Set up plan-tools on this machine
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
BIN_DIR="$SCRIPT_DIR/bin"
echo "plan-tools setup"
echo "════════════════"
echo ""
# Make scripts executable
chmod +x "$BIN_DIR/plan-refine"
chmod +x "$BIN_DIR/plan-status"
echo "Made scripts executable."
# Check for Oracle
if command -v oracle &>/dev/null; then
echo "Oracle: $(oracle --version 2>/dev/null || echo 'installed')"
else
echo "Oracle not found. Installing via npm..."
npm install -g @steipete/oracle
fi
# Set up Oracle browser profile (first-time login)
ORACLE_PROFILE="$HOME/.oracle/browser-profile"
if [[ ! -d "$ORACLE_PROFILE" ]]; then
echo ""
echo "Oracle needs a one-time browser login to ChatGPT."
echo "This will open Chrome — log into ChatGPT, then close the browser."
read -rp "Run browser login now? (y/N) " confirm
if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then
oracle --engine browser \
--browser-manual-login \
--browser-keep-browser \
-p "Hello, this is a test. Reply with: Oracle setup complete." \
--write-output /dev/null || true
echo "Browser profile saved."
else
echo "Skipped. Run this later:"
echo " oracle --engine browser --browser-manual-login --browser-keep-browser -p 'test'"
fi
fi
# Symlink to PATH
echo ""
TARGET_BIN="$HOME/.local/bin"
mkdir -p "$TARGET_BIN"
for script in plan-refine plan-status; do
target="$TARGET_BIN/$script"
if [[ -L "$target" ]]; then
rm "$target"
fi
ln -s "$BIN_DIR/$script" "$target"
echo "Linked: $script -> $target"
done
# Check PATH
if ! echo "$PATH" | tr ':' '\n' | grep -q "^${TARGET_BIN}$"; then
echo ""
echo "NOTE: $TARGET_BIN is not in your PATH."
echo "Add this to your shell profile:"
echo " export PATH=\"\$HOME/.local/bin:\$PATH\""
fi
echo ""
echo "Setup complete. Available commands:"
echo " plan-refine <plan.md> Run one ChatGPT evaluation iteration"
echo " plan-refine <plan.md> --init Add plan frontmatter to a file"
echo " plan-status Show all plans and their pipeline stage"
echo ""
mkdir -p ~/.local/bin
ln -sf "$SCRIPT_DIR/plan-refine" ~/.local/bin/plan-refine
echo "Linked: plan-refine -> ~/.local/bin/plan-refine"
[[ ":$PATH:" != *":$HOME/.local/bin:"* ]] && echo "NOTE: Add ~/.local/bin to your PATH"
command -v codex &>/dev/null || echo "NOTE: Install codex CLI (npm i -g @openai/codex)"
command -v claude &>/dev/null || echo "NOTE: Install claude CLI"

View File

@@ -1,196 +0,0 @@
#!/usr/bin/env node
// chatgpt-send.mjs — Send a prompt to ChatGPT via the user's running Brave browser
// Connects via Chrome DevTools Protocol to an already-authenticated session.
//
// Usage: node chatgpt-send.mjs <prompt-file> <output-file> [--timeout <seconds>]
//
// Requires: Brave running with --remote-debugging-port=9222
import puppeteer from 'puppeteer-core';
import { readFileSync, writeFileSync } from 'fs';
const CDP_URL = `http://127.0.0.1:${process.env.CHATGPT_CDP_PORT || '9222'}`;
const DEFAULT_TIMEOUT_SEC = 600; // 10 minutes for long responses
function parseArgs() {
const args = process.argv.slice(2);
let promptFile = null;
let outputFile = null;
let timeoutSec = DEFAULT_TIMEOUT_SEC;
for (let i = 0; i < args.length; i++) {
if (args[i] === '--timeout' && args[i + 1]) {
timeoutSec = parseInt(args[i + 1], 10);
i++;
} else if (!promptFile) {
promptFile = args[i];
} else if (!outputFile) {
outputFile = args[i];
}
}
if (!promptFile || !outputFile) {
console.error('Usage: node chatgpt-send.mjs <prompt-file> <output-file> [--timeout <seconds>]');
process.exit(2);
}
return { promptFile, outputFile, timeoutSec };
}
async function waitForSelector(page, selector, timeout) {
return page.waitForSelector(selector, { timeout });
}
async function main() {
const { promptFile, outputFile, timeoutSec } = parseArgs();
const prompt = readFileSync(promptFile, 'utf-8').trim();
const timeoutMs = timeoutSec * 1000;
// Connect to running Brave
let browser;
try {
browser = await puppeteer.connect({ browserURL: CDP_URL });
} catch (err) {
console.error(`Cannot connect to Brave at ${CDP_URL}`);
console.error('Make sure Brave is running with: --remote-debugging-port=9222');
console.error('');
console.error('Relaunch Brave:');
console.error(' 1. Quit Brave (Cmd+Q)');
console.error(' 2. Run: open -a "Brave Browser" --args --remote-debugging-port=9222');
process.exit(1);
}
// Open new tab for ChatGPT
const page = await browser.newPage();
try {
console.error('Navigating to ChatGPT...');
await page.goto('https://chatgpt.com/', { waitUntil: 'networkidle2', timeout: 30000 });
// Verify we're logged in by checking for the composer
const composerSelector = '#prompt-textarea, [id="prompt-textarea"], div[contenteditable="true"][data-placeholder]';
try {
await waitForSelector(page, composerSelector, 15000);
} catch {
// Check if login button is present
const loginBtn = await page.$('button[data-testid="login-button"], a[href*="auth"]');
if (loginBtn) {
console.error('ERROR: Not logged into ChatGPT. Log in via Brave first.');
process.exit(1);
}
console.error('ERROR: Could not find ChatGPT composer. The UI may have changed.');
process.exit(1);
}
console.error('Logged in. Sending prompt...');
// Find and focus the composer
const composer = await page.$(composerSelector);
await composer.click();
// Type the prompt — use clipboard for large prompts
await page.evaluate(async (text) => {
const composer = document.querySelector('#prompt-textarea, [id="prompt-textarea"], div[contenteditable="true"][data-placeholder]');
if (composer) {
// Use execCommand for contenteditable divs
composer.focus();
// Clear existing content
document.execCommand('selectAll', false, null);
// Insert via clipboard API for reliability with large text
const clipItem = new ClipboardItem({
'text/plain': new Blob([text], { type: 'text/plain' })
});
await navigator.clipboard.write([clipItem]);
document.execCommand('paste');
}
}, prompt);
// Small delay to ensure content is rendered
await new Promise(r => setTimeout(r, 500));
// Verify content was entered
const composerText = await page.evaluate(() => {
const el = document.querySelector('#prompt-textarea, [id="prompt-textarea"], div[contenteditable="true"][data-placeholder]');
return el ? el.textContent.length : 0;
});
if (composerText < 10) {
// Fallback: type directly (slower but more reliable)
console.error('Clipboard paste failed, typing directly...');
await composer.click({ clickCount: 3 }); // select all
await page.keyboard.type(prompt, { delay: 1 });
}
// Find and click the send button
const sendSelector = 'button[data-testid="send-button"], button[aria-label="Send prompt"], button[aria-label*="Send"]';
const sendBtn = await page.$(sendSelector);
if (sendBtn) {
await sendBtn.click();
} else {
// Fallback: press Enter
await page.keyboard.press('Enter');
}
console.error('Prompt sent. Waiting for response...');
// Wait for the response to complete
// Strategy: watch for the stop button to appear then disappear
const stopSelector = 'button[data-testid="stop-button"], button[aria-label="Stop generating"], button[aria-label*="Stop"]';
// Wait for generation to start (stop button appears)
try {
await waitForSelector(page, stopSelector, 30000);
console.error('Generating...');
} catch {
// Stop button might not appear for very fast responses
console.error('Response may have completed quickly.');
}
// Wait for generation to finish (stop button disappears)
await page.waitForFunction(
(sel) => !document.querySelector(sel),
{ timeout: timeoutMs, polling: 1000 },
stopSelector
);
// Small delay for final rendering
await new Promise(r => setTimeout(r, 2000));
console.error('Response complete. Extracting...');
// Extract the last assistant message
const response = await page.evaluate(() => {
// ChatGPT renders assistant messages in article elements or divs with specific data attributes
const messages = document.querySelectorAll(
'[data-message-author-role="assistant"], article[data-testid*="conversation-turn"]'
);
if (messages.length === 0) return null;
const lastMessage = messages[messages.length - 1];
// Try to get markdown content via the copy button's data
// Fallback to innerText
const markdownEl = lastMessage.querySelector('.markdown, .prose');
if (markdownEl) return markdownEl.innerText;
return lastMessage.innerText;
});
if (!response) {
console.error('ERROR: Could not extract response from page.');
process.exit(1);
}
writeFileSync(outputFile, response, 'utf-8');
console.error(`Response saved to: ${outputFile} (${response.length} chars)`);
} finally {
// Close the tab we opened, don't close the browser
await page.close();
browser.disconnect();
}
}
main().catch(err => {
console.error(`Fatal: ${err.message}`);
process.exit(1);
});

View File

@@ -1,103 +0,0 @@
#!/usr/bin/env bash
# frontmatter.sh — Read/write YAML frontmatter in plan files
# Read a value from YAML frontmatter
# Usage: get_frontmatter <file> <key> [default]
get_frontmatter() {
local file="$1"
local key="$2"
local default="${3:-}"
if ! head -1 "$file" | grep -q '^---$'; then
echo "$default"
return
fi
local value
value=$(awk '/^---$/{n++; next} n==1{print}' "$file" \
| grep "^${key}:" \
| head -1 \
| sed "s/^${key}: *//; s/^ *//; s/ *$//; s/^[\"']//; s/[\"']$//")
echo "${value:-$default}"
}
# Set a value in YAML frontmatter (adds frontmatter if missing)
# Usage: set_frontmatter <file> <key> <value>
set_frontmatter() {
local file="$1"
local key="$2"
local value="$3"
# If file has no frontmatter, add it
if ! head -1 "$file" | grep -q '^---$'; then
local tmp
tmp=$(mktemp)
{
echo "---"
echo "${key}: ${value}"
echo "---"
cat "$file"
} > "$tmp"
mv "$tmp" "$file"
return
fi
if grep -q "^${key}:" "$file"; then
# Replace existing key (using | as delimiter to avoid path issues)
sed -i '' "s|^${key}:.*|${key}: ${value}|" "$file"
else
# Insert before closing --- (second occurrence)
awk -v key="$key" -v val="$value" '
BEGIN { count=0; inserted=0 }
/^---$/ { count++ }
count == 2 && !inserted { print key ": " val; inserted=1 }
{ print }
' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
fi
}
# Check if a file has plan frontmatter
# Usage: is_plan_file <file>
is_plan_file() {
local file="$1"
[[ -f "$file" ]] && head -20 "$file" | grep -q "^plan: true"
}
# Initialize frontmatter on a plan file that doesn't have it
# Usage: init_frontmatter <file> [title]
init_frontmatter() {
local file="$1"
local title="${2:-$(basename "$file" .md)}"
local today
today=$(date +%Y-%m-%d)
if head -1 "$file" | grep -q '^---$'; then
# Has frontmatter, just ensure plan fields exist
set_frontmatter "$file" "plan" "true"
[[ -z "$(get_frontmatter "$file" "status")" ]] && set_frontmatter "$file" "status" "drafting"
[[ -z "$(get_frontmatter "$file" "iteration")" ]] && set_frontmatter "$file" "iteration" "0"
[[ -z "$(get_frontmatter "$file" "target_iterations")" ]] && set_frontmatter "$file" "target_iterations" "8"
[[ -z "$(get_frontmatter "$file" "created")" ]] && set_frontmatter "$file" "created" "$today"
set_frontmatter "$file" "updated" "$today"
else
# No frontmatter, add full block
local tmp
tmp=$(mktemp)
{
echo "---"
echo "plan: true"
echo "title: \"${title}\""
echo "status: drafting"
echo "iteration: 0"
echo "target_iterations: 8"
echo "beads_revision: 0"
echo "related_plans: []"
echo "created: ${today}"
echo "updated: ${today}"
echo "---"
echo ""
cat "$file"
} > "$tmp"
mv "$tmp" "$file"
fi
}

927
package-lock.json generated
View File

@@ -1,927 +0,0 @@
{
"name": "plan-tools",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "plan-tools",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"puppeteer-core": "^24.37.2"
}
},
"node_modules/@puppeteer/browsers": {
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.12.0.tgz",
"integrity": "sha512-Xuq42yxcQJ54ti8ZHNzF5snFvtpgXzNToJ1bXUGQRaiO8t+B6UM8sTUJfvV+AJnqtkJU/7hdy6nbKyA12aHtRw==",
"license": "Apache-2.0",
"dependencies": {
"debug": "^4.4.3",
"extract-zip": "^2.0.1",
"progress": "^2.0.3",
"proxy-agent": "^6.5.0",
"semver": "^7.7.3",
"tar-fs": "^3.1.1",
"yargs": "^17.7.2"
},
"bin": {
"browsers": "lib/cjs/main-cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@tootallnate/quickjs-emscripten": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz",
"integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==",
"license": "MIT"
},
"node_modules/@types/node": {
"version": "25.2.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.1.tgz",
"integrity": "sha512-CPrnr8voK8vC6eEtyRzvMpgp3VyVRhgclonE7qYi6P9sXwYb59ucfrnmFBTaP0yUi8Gk4yZg/LlTJULGxvTNsg==",
"license": "MIT",
"optional": true,
"dependencies": {
"undici-types": "~7.16.0"
}
},
"node_modules/@types/yauzl": {
"version": "2.10.3",
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
"integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
"license": "MIT",
"optional": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/ast-types": {
"version": "0.13.4",
"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
"integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==",
"license": "MIT",
"dependencies": {
"tslib": "^2.0.1"
},
"engines": {
"node": ">=4"
}
},
"node_modules/b4a": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz",
"integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==",
"license": "Apache-2.0",
"peerDependencies": {
"react-native-b4a": "*"
},
"peerDependenciesMeta": {
"react-native-b4a": {
"optional": true
}
}
},
"node_modules/bare-events": {
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz",
"integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==",
"license": "Apache-2.0",
"peerDependencies": {
"bare-abort-controller": "*"
},
"peerDependenciesMeta": {
"bare-abort-controller": {
"optional": true
}
}
},
"node_modules/bare-fs": {
"version": "4.5.3",
"resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.3.tgz",
"integrity": "sha512-9+kwVx8QYvt3hPWnmb19tPnh38c6Nihz8Lx3t0g9+4GoIf3/fTgYwM4Z6NxgI+B9elLQA7mLE9PpqcWtOMRDiQ==",
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"bare-events": "^2.5.4",
"bare-path": "^3.0.0",
"bare-stream": "^2.6.4",
"bare-url": "^2.2.2",
"fast-fifo": "^1.3.2"
},
"engines": {
"bare": ">=1.16.0"
},
"peerDependencies": {
"bare-buffer": "*"
},
"peerDependenciesMeta": {
"bare-buffer": {
"optional": true
}
}
},
"node_modules/bare-os": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz",
"integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==",
"license": "Apache-2.0",
"optional": true,
"engines": {
"bare": ">=1.14.0"
}
},
"node_modules/bare-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz",
"integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==",
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"bare-os": "^3.0.1"
}
},
"node_modules/bare-stream": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz",
"integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==",
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"streamx": "^2.21.0"
},
"peerDependencies": {
"bare-buffer": "*",
"bare-events": "*"
},
"peerDependenciesMeta": {
"bare-buffer": {
"optional": true
},
"bare-events": {
"optional": true
}
}
},
"node_modules/bare-url": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz",
"integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==",
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"bare-path": "^3.0.0"
}
},
"node_modules/basic-ftp": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.1.0.tgz",
"integrity": "sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/buffer-crc32": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/chromium-bidi": {
"version": "13.1.1",
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-13.1.1.tgz",
"integrity": "sha512-zB9MpoPd7VJwjowQqiW3FKOvQwffFMjQ8Iejp5ZW+sJaKLRhZX1sTxzl3Zt22TDB4zP0OOqs8lRoY7eAW5geyQ==",
"license": "Apache-2.0",
"dependencies": {
"mitt": "^3.0.1",
"zod": "^3.24.1"
},
"peerDependencies": {
"devtools-protocol": "*"
}
},
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/data-uri-to-buffer": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
"integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/degenerator": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz",
"integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==",
"license": "MIT",
"dependencies": {
"ast-types": "^0.13.4",
"escodegen": "^2.1.0",
"esprima": "^4.0.1"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/devtools-protocol": {
"version": "0.0.1566079",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1566079.tgz",
"integrity": "sha512-MJfAEA1UfVhSs7fbSQOG4czavUp1ajfg6prlAN0+cmfa2zNjaIbvq8VneP7do1WAQQIvgNJWSMeP6UyI90gIlQ==",
"license": "BSD-3-Clause"
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/end-of-stream": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
"license": "MIT",
"dependencies": {
"once": "^1.4.0"
}
},
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/escodegen": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
"integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
"license": "BSD-2-Clause",
"dependencies": {
"esprima": "^4.0.1",
"estraverse": "^5.2.0",
"esutils": "^2.0.2"
},
"bin": {
"escodegen": "bin/escodegen.js",
"esgenerate": "bin/esgenerate.js"
},
"engines": {
"node": ">=6.0"
},
"optionalDependencies": {
"source-map": "~0.6.1"
}
},
"node_modules/esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"license": "BSD-2-Clause",
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/estraverse": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=4.0"
}
},
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/events-universal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
"integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
"license": "Apache-2.0",
"dependencies": {
"bare-events": "^2.7.0"
}
},
"node_modules/extract-zip": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
"integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
"license": "BSD-2-Clause",
"dependencies": {
"debug": "^4.1.1",
"get-stream": "^5.1.0",
"yauzl": "^2.10.0"
},
"bin": {
"extract-zip": "cli.js"
},
"engines": {
"node": ">= 10.17.0"
},
"optionalDependencies": {
"@types/yauzl": "^2.9.1"
}
},
"node_modules/fast-fifo": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
"license": "MIT"
},
"node_modules/fd-slicer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
"integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
"license": "MIT",
"dependencies": {
"pend": "~1.2.0"
}
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"license": "ISC",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-stream": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
"license": "MIT",
"dependencies": {
"pump": "^3.0.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/get-uri": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz",
"integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==",
"license": "MIT",
"dependencies": {
"basic-ftp": "^5.0.2",
"data-uri-to-buffer": "^6.0.2",
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/http-proxy-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.0",
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/ip-address": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
"integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/lru-cache": {
"version": "7.18.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
"integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/mitt": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
"license": "MIT"
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/netmask": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz",
"integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==",
"license": "MIT",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/pac-proxy-agent": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz",
"integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==",
"license": "MIT",
"dependencies": {
"@tootallnate/quickjs-emscripten": "^0.23.0",
"agent-base": "^7.1.2",
"debug": "^4.3.4",
"get-uri": "^6.0.1",
"http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.6",
"pac-resolver": "^7.0.1",
"socks-proxy-agent": "^8.0.5"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/pac-resolver": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz",
"integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==",
"license": "MIT",
"dependencies": {
"degenerator": "^5.0.0",
"netmask": "^2.0.2"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/pend": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
"license": "MIT"
},
"node_modules/progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/proxy-agent": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz",
"integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "^4.3.4",
"http-proxy-agent": "^7.0.1",
"https-proxy-agent": "^7.0.6",
"lru-cache": "^7.14.1",
"pac-proxy-agent": "^7.1.0",
"proxy-from-env": "^1.1.0",
"socks-proxy-agent": "^8.0.5"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/pump": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
"integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
"license": "MIT",
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"node_modules/puppeteer-core": {
"version": "24.37.2",
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.37.2.tgz",
"integrity": "sha512-nN8qwE3TGF2vA/+xemPxbesntTuqD9vCGOiZL2uh8HES3pPzLX20MyQjB42dH2rhQ3W3TljZ4ZaKZ0yX/abQuw==",
"license": "Apache-2.0",
"dependencies": {
"@puppeteer/browsers": "2.12.0",
"chromium-bidi": "13.1.1",
"debug": "^4.4.3",
"devtools-protocol": "0.0.1566079",
"typed-query-selector": "^2.12.0",
"webdriver-bidi-protocol": "0.4.0",
"ws": "^8.19.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
"license": "MIT",
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
}
},
"node_modules/socks": {
"version": "2.8.7",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz",
"integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==",
"license": "MIT",
"dependencies": {
"ip-address": "^10.0.1",
"smart-buffer": "^4.2.0"
},
"engines": {
"node": ">= 10.0.0",
"npm": ">= 3.0.0"
}
},
"node_modules/socks-proxy-agent": {
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
"integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "^4.3.4",
"socks": "^2.8.3"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"license": "BSD-3-Clause",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/streamx": {
"version": "2.23.0",
"resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz",
"integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==",
"license": "MIT",
"dependencies": {
"events-universal": "^1.0.0",
"fast-fifo": "^1.3.2",
"text-decoder": "^1.1.0"
}
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/tar-fs": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz",
"integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==",
"license": "MIT",
"dependencies": {
"pump": "^3.0.0",
"tar-stream": "^3.1.5"
},
"optionalDependencies": {
"bare-fs": "^4.0.1",
"bare-path": "^3.0.0"
}
},
"node_modules/tar-stream": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz",
"integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==",
"license": "MIT",
"dependencies": {
"b4a": "^1.6.4",
"fast-fifo": "^1.2.0",
"streamx": "^2.15.0"
}
},
"node_modules/text-decoder": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz",
"integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==",
"license": "Apache-2.0",
"dependencies": {
"b4a": "^1.6.4"
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/typed-query-selector": {
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz",
"integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==",
"license": "MIT"
},
"node_modules/undici-types": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"license": "MIT",
"optional": true
},
"node_modules/webdriver-bidi-protocol": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.0.tgz",
"integrity": "sha512-U9VIlNRrq94d1xxR9JrCEAx5Gv/2W7ERSv8oWRoNe/QYbfccS0V3h/H6qeNeCRJxXGMhhnkqvwNrvPAYeuP9VA==",
"license": "Apache-2.0"
},
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
"node_modules/ws": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"license": "ISC",
"engines": {
"node": ">=10"
}
},
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"license": "MIT",
"dependencies": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.1.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/yauzl": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
"integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
"license": "MIT",
"dependencies": {
"buffer-crc32": "~0.2.3",
"fd-slicer": "~1.1.0"
}
},
"node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}

View File

@@ -1,19 +0,0 @@
{
"name": "plan-tools",
"version": "1.0.0",
"description": "",
"main": "index.js",
"directories": {
"lib": "lib"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"puppeteer-core": "^24.37.2"
}
}

467
plan-refine Executable file
View File

@@ -0,0 +1,467 @@
#!/usr/bin/env bash
# plan-refine — Automated plan iteration: Codex (ChatGPT) review + Claude integration
# Usage: plan-refine <plan-file> [options]
# plan-refine status [options]
set -euo pipefail
# ──────────────────────────────────────────────
# Prompts (inlined)
# ──────────────────────────────────────────────
EVAL_PROMPT="Carefully review this entire plan for me and come up with your best revisions in terms of better architecture, new features, changed features, etc. to make it better, more robust/reliable, more performant, more compelling/useful, etc. For each proposed change, give me your detailed analysis and rationale/justification for why it would make the project better along with the git-diff style change versus the original plan shown below."
INTEGRATE_PROMPT="I asked ChatGPT to review your plan. I want you to REALLY carefully analyze their plan with an open mind and be intellectually honest about what they did that's better than your plan. Then I want you to come up with the best possible revisions to your plan (you should simply update your existing document for your original plan with the revisions) that artfully and skillfully blends the \"best of all worlds\" to create a true, ultimate, superior hybrid version of the plan that best achieves our stated goals and will work the best in real-world practice to solve the problems we are facing and our overarching goals while ensuring the extreme success of the enterprise as best as possible; you should provide me with a complete series of git-diff style changes to your original plan to turn it into the new, enhanced, much longer and detailed plan that integrates the best of all the plans with every good idea included."
# ──────────────────────────────────────────────
# Frontmatter helpers
# ──────────────────────────────────────────────
get_frontmatter() {
local file="$1" key="$2" default="${3:-}"
if ! head -1 "$file" | grep -q '^---$'; then
echo "$default"; return
fi
local value
value=$(awk '/^---$/{n++; next} n==1{print}' "$file" \
| grep "^${key}:" | head -1 \
| sed "s/^${key}: *//; s/^ *//; s/ *$//; s/^[\"']//; s/[\"']$//")
echo "${value:-$default}"
}
set_frontmatter() {
local file="$1" key="$2" value="$3"
if ! head -1 "$file" | grep -q '^---$'; then
local tmp; tmp=$(mktemp)
{ echo "---"; echo "${key}: ${value}"; echo "---"; cat "$file"; } > "$tmp"
mv "$tmp" "$file"; return
fi
if grep -q "^${key}:" "$file"; then
sed -i '' "s|^${key}:.*|${key}: ${value}|" "$file"
else
awk -v key="$key" -v val="$value" '
BEGIN { count=0; inserted=0 }
/^---$/ { count++ }
count == 2 && !inserted { print key ": " val; inserted=1 }
{ print }
' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
fi
}
is_plan_file() {
local file="$1"
[[ -f "$file" ]] && head -20 "$file" | grep -q "^plan: true"
}
init_frontmatter() {
local file="$1" title="${2:-$(basename "$file" .md)}"
local today; today=$(date +%Y-%m-%d)
if head -1 "$file" | grep -q '^---$'; then
set_frontmatter "$file" "plan" "true"
[[ -z "$(get_frontmatter "$file" "status")" ]] && set_frontmatter "$file" "status" "drafting"
[[ -z "$(get_frontmatter "$file" "iteration")" ]] && set_frontmatter "$file" "iteration" "0"
[[ -z "$(get_frontmatter "$file" "target_iterations")" ]] && set_frontmatter "$file" "target_iterations" "8"
[[ -z "$(get_frontmatter "$file" "created")" ]] && set_frontmatter "$file" "created" "$today"
set_frontmatter "$file" "updated" "$today"
else
local tmp; tmp=$(mktemp)
{
echo "---"
echo "plan: true"
echo "title: \"${title}\""
echo "status: drafting"
echo "iteration: 0"
echo "target_iterations: 8"
echo "beads_revision: 0"
echo "related_plans: []"
echo "created: ${today}"
echo "updated: ${today}"
echo "---"
echo ""
cat "$file"
} > "$tmp"
mv "$tmp" "$file"
fi
}
# ──────────────────────────────────────────────
# Claude stream-json progress display
# ──────────────────────────────────────────────
claude_progress() {
node --input-type=module -e '
import { createInterface } from "readline";
const rl = createInterface({ input: process.stdin });
let toolCount = 0;
rl.on("line", (line) => {
try {
const event = JSON.parse(line);
if (event.type === "assistant" && event.message?.content) {
for (const block of event.message.content) {
if (block.type === "text" && block.text) {
process.stderr.write(block.text);
if (!block.text.endsWith("\n")) process.stderr.write("\n");
}
if (block.type === "tool_use") {
toolCount++;
const input = block.input || {};
const file = input.file_path || input.path || input.pattern || "";
const short = file ? file.split("/").pop() : "";
process.stderr.write(" [" + block.name + (short ? ": " + short : "") + "]\n");
}
}
}
if (event.type === "result") {
const cost = event.cost_usd ? " ($" + event.cost_usd.toFixed(4) + ")" : "";
process.stderr.write("\n [Done — " + (event.num_turns || "?") + " turns, " + toolCount + " tool calls" + cost + "]\n");
}
} catch {}
});'
}
# ──────────────────────────────────────────────
# Subcommand: status
# ──────────────────────────────────────────────
cmd_status() {
local SEARCH_ROOT="${HOME}/projects"
local JSON_OUTPUT=false
local FILTER_STATUS=""
while [[ $# -gt 0 ]]; do
case "$1" in
--json) JSON_OUTPUT=true; shift ;;
--status) FILTER_STATUS="$2"; shift 2 ;;
--root) SEARCH_ROOT="$2"; shift 2 ;;
-h|--help)
cat <<'EOF'
plan-refine status — Dashboard for plan files across projects
Usage: plan-refine status [options]
Options:
--json Machine-readable JSON output
--status <status> Filter to plans with this status
--root <path> Search root (default: ~/projects)
-h, --help Show this help
EOF
exit 0 ;;
-*) echo "Unknown option: $1" >&2; exit 2 ;;
*) SEARCH_ROOT="$1"; shift ;;
esac
done
if [[ ! -d "$SEARCH_ROOT" ]]; then
echo "Error: Search root not found: $SEARCH_ROOT" >&2; exit 1
fi
declare -a PLAN_FILES=()
while IFS= read -r -d '' file; do
if is_plan_file "$file"; then
PLAN_FILES+=("$file")
fi
done < <(find "$SEARCH_ROOT" \
-name "*.md" \
-not -path "*/node_modules/*" \
-not -path "*/.git/*" \
-not -path "*/.jj/*" \
-not -path "*/target/*" \
-not -name "*.feedback-*" \
-print0 2>/dev/null)
if [[ ${#PLAN_FILES[@]} -eq 0 ]]; then
if [[ "$JSON_OUTPUT" == "true" ]]; then
echo '{"plans":[],"summary":{"total":0}}'
else
echo "No plan files found under $SEARCH_ROOT"
echo "Initialize a plan: plan-refine <file.md> --init"
fi
exit 0
fi
if [[ "$JSON_OUTPUT" == "true" ]]; then
echo '{"plans":['
local first=true
for file in "${PLAN_FILES[@]}"; do
local status; status=$(get_frontmatter "$file" "status" "unknown")
[[ -n "$FILTER_STATUS" && "$status" != "$FILTER_STATUS" ]] && continue
local title; title=$(get_frontmatter "$file" "title" "$(basename "$file" .md)")
local iteration; iteration=$(get_frontmatter "$file" "iteration" "0")
local target; target=$(get_frontmatter "$file" "target_iterations" "")
local beads_rev; beads_rev=$(get_frontmatter "$file" "beads_revision" "0")
local updated; updated=$(get_frontmatter "$file" "updated" "")
local project; project=$(echo "$file" | sed "s|${SEARCH_ROOT}/||" | cut -d/ -f1)
local feedback_count; feedback_count=$(ls "${file%.md}".feedback-*.md 2>/dev/null | wc -l | tr -d ' ')
[[ "$first" != "true" ]] && echo ","
first=false
echo " {\"path\":\"$file\",\"project\":\"$project\",\"title\":\"$title\",\"status\":\"$status\",\"iteration\":$iteration,\"target_iterations\":${target:-null},\"beads_revision\":$beads_rev,\"feedback_files\":$feedback_count,\"updated\":\"$updated\"}"
done
echo ']}'
else
echo ""
echo " Plan Status Dashboard"
echo " $(date '+%Y-%m-%d %H:%M')"
echo " ════════════════════════════════════════════════════════════════════════"
printf " %-18s %-28s %-12s %-14s %s\n" "Project" "Plan" "Status" "Iterations" "Updated"
echo " ────────────────────────────────────────────────────────────────────────"
local total=0
local by_status_drafting=0 by_status_iterating=0 by_status_splitting=0
local by_status_refining=0 by_status_ready=0 by_status_implementing=0 by_status_completed=0
for file in "${PLAN_FILES[@]}"; do
local status; status=$(get_frontmatter "$file" "status" "unknown")
[[ -n "$FILTER_STATUS" && "$status" != "$FILTER_STATUS" ]] && continue
local title; title=$(get_frontmatter "$file" "title" "")
local iteration; iteration=$(get_frontmatter "$file" "iteration" "0")
local target; target=$(get_frontmatter "$file" "target_iterations" "")
local beads_rev; beads_rev=$(get_frontmatter "$file" "beads_revision" "0")
local updated; updated=$(get_frontmatter "$file" "updated" "")
local project; project=$(echo "$file" | sed "s|${SEARCH_ROOT}/||" | cut -d/ -f1)
local plan_name; plan_name=$(basename "$file" .md)
[[ ${#project} -gt 18 ]] && project="${project:0:15}..."
[[ ${#plan_name} -gt 28 ]] && plan_name="${plan_name:0:25}..."
local iter_display
if [[ -n "$target" && "$target" != "0" ]]; then
iter_display="${iteration}/${target}"
else
iter_display="${iteration}"
fi
[[ "$beads_rev" != "0" ]] && iter_display="${iter_display} br:${beads_rev}"
printf " %-18s %-28s %-12s %-14s %s\n" \
"$project" "$plan_name" "$status" "$iter_display" "$updated"
total=$((total + 1))
case "$status" in
drafting) by_status_drafting=$((by_status_drafting + 1)) ;;
iterating) by_status_iterating=$((by_status_iterating + 1)) ;;
splitting) by_status_splitting=$((by_status_splitting + 1)) ;;
refining) by_status_refining=$((by_status_refining + 1)) ;;
ready) by_status_ready=$((by_status_ready + 1)) ;;
implementing) by_status_implementing=$((by_status_implementing + 1)) ;;
completed) by_status_completed=$((by_status_completed + 1)) ;;
esac
done
echo " ════════════════════════════════════════════════════════════════════════"
echo ""
local summary_parts=()
[[ $by_status_drafting -gt 0 ]] && summary_parts+=("${by_status_drafting} drafting")
[[ $by_status_iterating -gt 0 ]] && summary_parts+=("${by_status_iterating} iterating")
[[ $by_status_splitting -gt 0 ]] && summary_parts+=("${by_status_splitting} splitting")
[[ $by_status_refining -gt 0 ]] && summary_parts+=("${by_status_refining} refining")
[[ $by_status_ready -gt 0 ]] && summary_parts+=("${by_status_ready} ready")
[[ $by_status_implementing -gt 0 ]] && summary_parts+=("${by_status_implementing} implementing")
[[ $by_status_completed -gt 0 ]] && summary_parts+=("${by_status_completed} completed")
IFS=", "
echo " ${total} plans: ${summary_parts[*]}"
echo ""
echo " Pipeline: drafting -> iterating -> splitting -> refining -> ready -> implementing -> completed"
echo ""
fi
}
# ──────────────────────────────────────────────
# Subcommand: refine (default)
# ──────────────────────────────────────────────
cmd_refine() {
local CLAUDE_MODEL="" CODEX_MODEL="" DRY_RUN=false NO_INTEGRATE=false PLAN_FILE="" INIT_ONLY=false
while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run) DRY_RUN=true; shift ;;
--no-integrate) NO_INTEGRATE=true; shift ;;
--claude-model) CLAUDE_MODEL="$2"; shift 2 ;;
--codex-model) CODEX_MODEL="$2"; shift 2 ;;
--init) INIT_ONLY=true; shift ;;
-h|--help)
cat <<'EOF'
plan-refine — Automated plan iteration via Codex CLI + Claude CLI
Usage: plan-refine <plan-file> [options]
plan-refine status [options]
Runs the full cycle automatically:
1. Sends plan to ChatGPT via Codex CLI
2. Captures ChatGPT's response
3. Claude CLI integrates feedback back into the plan file
Requires: codex CLI (codex login) and claude CLI
Options:
--dry-run Preview what would happen
--no-integrate Get ChatGPT feedback only, skip Claude integration
--claude-model <m> Claude model for integration (default: your default)
--codex-model <m> Codex/ChatGPT model (default: codex default)
--init Add plan frontmatter to file without running anything
-h, --help Show this help
Subcommands:
status Show dashboard of all plan files
EOF
exit 0 ;;
-*) echo "Unknown option: $1" >&2; exit 2 ;;
*) PLAN_FILE="$1"; shift ;;
esac
done
if [[ -z "$PLAN_FILE" ]]; then
echo "Error: No plan file specified" >&2
echo "Usage: plan-refine <plan-file>" >&2
exit 2
fi
PLAN_FILE="$(cd "$(dirname "$PLAN_FILE")" && pwd)/$(basename "$PLAN_FILE")"
if [[ ! -f "$PLAN_FILE" ]]; then
echo "Error: Plan file not found: $PLAN_FILE" >&2; exit 1
fi
if ! head -1 "$PLAN_FILE" | grep -q '^---$'; then
echo "No frontmatter found. Initializing..."
init_frontmatter "$PLAN_FILE"
fi
if ! is_plan_file "$PLAN_FILE"; then
set_frontmatter "$PLAN_FILE" "plan" "true"
fi
if [[ "$INIT_ONLY" == "true" ]]; then
init_frontmatter "$PLAN_FILE"
echo "Frontmatter initialized for: $PLAN_FILE"
exit 0
fi
local ITERATION TARGET STATUS NEXT_ITERATION PLAN_DIR FEEDBACK_FILE
ITERATION=$(get_frontmatter "$PLAN_FILE" "iteration" "0")
TARGET=$(get_frontmatter "$PLAN_FILE" "target_iterations" "8")
STATUS=$(get_frontmatter "$PLAN_FILE" "status" "drafting")
NEXT_ITERATION=$((ITERATION + 1))
PLAN_DIR=$(dirname "$PLAN_FILE")
FEEDBACK_FILE="${PLAN_FILE%.md}.feedback-${NEXT_ITERATION}.md"
if [[ "$STATUS" == "ready" || "$STATUS" == "implementing" || "$STATUS" == "completed" ]]; then
echo "Warning: Plan status is '$STATUS' -- already past iteration phase."
echo "Continue anyway? (y/N)"
read -r confirm
[[ "$confirm" != "y" && "$confirm" != "Y" ]] && exit 0
fi
echo "=== plan-refine ==="
echo " Plan: $(basename "$PLAN_FILE")"
echo " Status: $STATUS"
echo " Iteration: $ITERATION -> $NEXT_ITERATION (target: $TARGET)"
echo " Feedback: $(basename "$FEEDBACK_FILE")"
if [[ "$NO_INTEGRATE" == "true" ]]; then
echo " Mode: ChatGPT review only"
else
echo " Mode: Full cycle (ChatGPT + Claude integration)"
fi
echo ""
if [[ "$DRY_RUN" == "true" ]]; then
echo "=== DRY RUN ==="
echo ""
echo "Step 1: ChatGPT via Codex CLI"
echo " codex exec <prompt> -o $FEEDBACK_FILE --skip-git-repo-check"
echo ""
if [[ "$NO_INTEGRATE" != "true" ]]; then
echo "Step 2: Claude CLI integration"
echo " claude -p <integration-prompt> --allowedTools Read,Edit,Write --permission-mode acceptEdits"
fi
exit 0
fi
# Step 1: ChatGPT via Codex CLI
if [[ -f "$FEEDBACK_FILE" && -s "$FEEDBACK_FILE" ]]; then
echo "[Step 1] Feedback file already exists, skipping ChatGPT step."
echo " $(basename "$FEEDBACK_FILE") ($(wc -c < "$FEEDBACK_FILE" | tr -d ' ') bytes)"
echo " Delete it to re-run ChatGPT: rm $(basename "$FEEDBACK_FILE")"
else
echo "[Step 1] Sending plan to ChatGPT via Codex CLI..."
local FULL_PROMPT="${EVAL_PROMPT}
---
$(cat "$PLAN_FILE")"
local CODEX_ARGS=(codex exec -o "$FEEDBACK_FILE" --skip-git-repo-check -s read-only)
[[ -n "$CODEX_MODEL" ]] && CODEX_ARGS+=(-m "$CODEX_MODEL")
local CODEX_EXIT=0
echo "$FULL_PROMPT" | "${CODEX_ARGS[@]}" - || CODEX_EXIT=$?
if [[ $CODEX_EXIT -ne 0 ]]; then
echo "Error: Codex send failed (exit $CODEX_EXIT)" >&2; exit $CODEX_EXIT
fi
if [[ ! -s "$FEEDBACK_FILE" ]]; then
echo "Error: Codex produced empty output" >&2; exit 1
fi
echo "ChatGPT feedback saved to: $FEEDBACK_FILE ($(wc -c < "$FEEDBACK_FILE" | tr -d ' ') bytes)"
fi
# Step 2: Claude integration
if [[ "$NO_INTEGRATE" == "true" ]]; then
echo ""
echo "Skipping integration (--no-integrate)."
echo "Run manually: plan-refine $(basename "$PLAN_FILE")"
else
echo ""
echo "[Step 2] Claude integrating feedback into plan..."
local CLAUDE_PROMPT="${INTEGRATE_PROMPT}
The original plan is at: ${PLAN_FILE}
ChatGPT's feedback is at: ${FEEDBACK_FILE}
You have Read and Edit tools. Edit the plan file directly — do NOT output the plan content to stdout.
- Preserve the YAML frontmatter block (between the --- delimiters) at the top unchanged.
- Only modify the content below the frontmatter.
- Print a brief summary to stdout as you work: which changes you're accepting, rejecting, or modifying, and why. Keep it concise — bullet points, not essays."
local CLAUDE_STREAM_ARGS=(claude -p "$CLAUDE_PROMPT"
--allowedTools "Read,Edit,Write"
--permission-mode acceptEdits
--add-dir "$PLAN_DIR"
--output-format stream-json
--verbose)
[[ -n "$CLAUDE_MODEL" ]] && CLAUDE_STREAM_ARGS+=(--model "$CLAUDE_MODEL")
set +o pipefail
"${CLAUDE_STREAM_ARGS[@]}" | claude_progress
local CLAUDE_EXIT=${PIPESTATUS[0]}
set -o pipefail
if [[ $CLAUDE_EXIT -ne 0 ]]; then
echo "Error: Claude integration failed (exit $CLAUDE_EXIT)" >&2
echo "Feedback still available at: $FEEDBACK_FILE" >&2
exit $CLAUDE_EXIT
fi
echo "Integration complete. Plan updated."
fi
# Update frontmatter
set_frontmatter "$PLAN_FILE" "iteration" "$NEXT_ITERATION"
set_frontmatter "$PLAN_FILE" "updated" "$(date +%Y-%m-%d)"
[[ "$STATUS" == "drafting" ]] && set_frontmatter "$PLAN_FILE" "status" "iterating"
echo ""
echo "=== Iteration $NEXT_ITERATION/$TARGET complete ==="
[[ $NEXT_ITERATION -ge $TARGET ]] && echo "" && echo "Target iterations reached. Plan may be ready for bead splitting."
}
# ──────────────────────────────────────────────
# Dispatch
# ──────────────────────────────────────────────
case "${1:-}" in
status) shift; cmd_status "$@" ;;
-h|--help) cmd_refine --help ;;
*) cmd_refine "$@" ;;
esac

View File

@@ -1 +0,0 @@
Carefully review this entire plan for me and come up with your best revisions in terms of better architecture, new features, changed features, etc. to make it better, more robust/reliable, more performant, more compelling/useful, etc. For each proposed change, give me your detailed analysis and rationale/justification for why it would make the project better along with the git-diff style change versus the original plan shown below.

View File

@@ -1 +0,0 @@
I asked ChatGPT to review your plan. I want you to REALLY carefully analyze their plan with an open mind and be intellectually honest about what they did that's better than your plan. Then I want you to come up with the best possible revisions to your plan (you should simply update your existing document for your original plan with the revisions) that artfully and skillfully blends the "best of all worlds" to create a true, ultimate, superior hybrid version of the plan that best achieves our stated goals and will work the best in real-world practice to solve the problems we are facing and our overarching goals while ensuring the extreme success of the enterprise as best as possible; you should provide me with a complete series of git-diff style changes to your original plan to turn it into the new, enhanced, much longer and detailed plan that integrates the best of all the plans with every good idea included.