feat: ClawTap v0.1.0 — initial release
Multi-adapter mobile UI for AI coding assistants. Supports Claude Code, Codex CLI, and Gemini CLI through one interface. Features: - Real-time bidirectional sync via tmux + WebSocket - Cross-AI review (send one AI's output to another for review) - Multi-review tabs with minimize/expand - Push notifications (PWA) with smart session-aware filtering - Three-channel event system (hooks, file watcher, pane monitor) - Voice input, image paste, draft persistence - Terminal-native design (JetBrains Mono, dark theme, pixel art claw) - CLI with --adapter flag on every command - Zero-overhead fire-and-forget hooks
This commit is contained in:
@@ -0,0 +1,688 @@
|
||||
# CLI Multi-Adapter Support Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Make every `codetap` CLI command support `--adapter` filtering and fix all outdated descriptions that only reference Claude.
|
||||
|
||||
**Architecture:** Move `--adapter` flag parsing to the top of the script (before any command handlers), so all commands can access `$ADAPTER`. Update `-a`/`-A` to filter by adapter, `--continue` to filter by adapter, and `hooks` to target specific adapters. Fix all help text and comments.
|
||||
|
||||
**Tech Stack:** Bash, Node.js (hooks-cli.mjs)
|
||||
|
||||
---
|
||||
|
||||
## Complete Issue List
|
||||
|
||||
| # | Issue | Type |
|
||||
|---|---|---|
|
||||
| 1 | `--adapter` parsed AFTER `-a`/`-A` exits — impossible to combine | Bug |
|
||||
| 2 | `-a`/`-A` can't filter by adapter | Feature gap |
|
||||
| 3 | `-a`/`-A` adapter detection uses `pane_current_command` → shows `node` not the adapter name | Bug |
|
||||
| 4 | `--continue` doesn't pass adapter to resume API | Feature gap |
|
||||
| 5 | `--continue` doesn't filter by adapter (always picks most recent) | Feature gap |
|
||||
| 6 | `hooks install/uninstall` can't target specific adapter | Feature gap |
|
||||
| 7 | Help text says "(Claude or Codex)" — missing Gemini | Text |
|
||||
| 8 | Header comment says "runs Claude/Codex" — missing Gemini | Text |
|
||||
| 9 | Header comment missing `--adapter` in usage examples | Text |
|
||||
| 10 | No-args output doesn't mention `--adapter` | Text |
|
||||
| 11 | Comment "Claude stores sessions by project dir" is outdated | Text |
|
||||
| 12 | `<any args>` description unclear | Text |
|
||||
|
||||
## File Map
|
||||
|
||||
| File | Action | Responsibility |
|
||||
|---|---|---|
|
||||
| `bin/codetap` | Modify | Move `--adapter` parsing to top, update all commands, fix all text |
|
||||
| `bin/hooks-cli.mjs` | Modify | Accept optional adapter name argument |
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Move `--adapter` Parsing Before All Command Handlers
|
||||
|
||||
**Files:**
|
||||
- Modify: `bin/codetap:24-370` (restructure flag parsing order)
|
||||
|
||||
The core structural fix: `--adapter` must be parsed BEFORE any command handler (including `-a`, `-A`, `--version`, `--help`), so all commands can access `$ADAPTER`.
|
||||
|
||||
- [ ] **Step 1: Move adapter parsing to right after variable declarations (before the `case` block)**
|
||||
|
||||
Move the `--adapter` parsing block (currently at lines 336-370) to immediately after line 22 (`PID_FILE=...`), before the `case "$1" in` block at line 25.
|
||||
|
||||
The block to move:
|
||||
|
||||
```bash
|
||||
# --- Parse --adapter flag (before any command handlers) ---
|
||||
set_adapter() {
|
||||
case "$1" in
|
||||
claude) ADAPTER="claude"; ADAPTER_CMD="claude"; YOLO="--dangerously-skip-permissions" ;;
|
||||
codex) ADAPTER="codex"; ADAPTER_CMD="codex"; YOLO="--dangerously-bypass-approvals-and-sandbox" ;;
|
||||
gemini) ADAPTER="gemini"; ADAPTER_CMD="gemini"; YOLO="--approval-mode yolo" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
ADAPTER="claude"
|
||||
ADAPTER_CMD="claude"
|
||||
ADAPTER_EXPLICIT=false
|
||||
prev_arg=""
|
||||
for arg in "$@"; do
|
||||
if [ "$prev_arg" = "--adapter" ]; then
|
||||
ADAPTER_EXPLICIT=true
|
||||
case "$arg" in
|
||||
claude) set_adapter claude ;;
|
||||
codex) set_adapter codex ;;
|
||||
gemini) set_adapter gemini ;;
|
||||
*) echo "Unknown adapter: $arg"; exit 1 ;;
|
||||
esac
|
||||
fi
|
||||
prev_arg="$arg"
|
||||
done
|
||||
|
||||
# Strip --adapter and its value from positional args
|
||||
CLEANED_ARGS=()
|
||||
skip_next=false
|
||||
for arg in "$@"; do
|
||||
if $skip_next; then skip_next=false; continue; fi
|
||||
if [ "$arg" = "--adapter" ]; then skip_next=true; continue; fi
|
||||
CLEANED_ARGS+=("$arg")
|
||||
done
|
||||
set -- "${CLEANED_ARGS[@]}"
|
||||
```
|
||||
|
||||
Delete the old copy of this block from its current location (lines 336-370).
|
||||
|
||||
- [ ] **Step 2: Verify `--version` and `--help` still work**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
codetap --version
|
||||
codetap --help
|
||||
codetap --adapter gemini --version
|
||||
```
|
||||
Expected: version prints, help prints, `--adapter gemini --version` still prints version (adapter is parsed but irrelevant for --version).
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add bin/codetap
|
||||
git commit -m "refactor(cli): move --adapter parsing before all command handlers"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: `-a`/`-A` Support `--adapter` Filter + Fix Adapter Detection
|
||||
|
||||
**Files:**
|
||||
- Modify: `bin/codetap` (the `-a`/`-A` handler, lines 249-334)
|
||||
|
||||
- [ ] **Step 1: Add adapter filter to the session list**
|
||||
|
||||
The current adapter detection (lines 302-307) uses `pane_current_command` which shows `node` for all adapters — broken for detection. Fix by querying the server's `/api/active-sessions` API which has accurate adapter info.
|
||||
|
||||
**Note:** The `-a`/`-A` handler is already positioned AFTER `ensure_server()` and `get_auth_token()` (line 249 is after line 200/203). After Task 1 moves `--adapter` parsing to the top, `$ADAPTER` and `$ADAPTER_EXPLICIT` will be available here. No handler relocation needed.
|
||||
|
||||
**API note:** `/api/active-sessions` supports `?adapter=` query param but NOT `?cwd=`. For project-level filtering (`-a`), fetch all sessions and filter by `cwd` field client-side in Python.
|
||||
|
||||
Replace the entire `-a`/`-A` handler (lines 249-334) with:
|
||||
|
||||
```bash
|
||||
# --- List active sessions ---
|
||||
if [ "$1" = "--attach" ] || [ "$1" = "-a" ] || [ "$1" = "-A" ]; then
|
||||
ALL_MODE=false
|
||||
[ "$1" = "-A" ] && ALL_MODE=true
|
||||
|
||||
# Get sessions from the server API (has accurate adapter info)
|
||||
AUTH_TOKEN=$(get_auth_token)
|
||||
if [ -n "$AUTH_TOKEN" ]; then
|
||||
SESSIONS_JSON=$(curl -s $CURL_OPTS "$PROTOCOL://localhost:$PORT/api/active-sessions" \
|
||||
-H "Authorization: Bearer $AUTH_TOKEN" 2>/dev/null)
|
||||
else
|
||||
SESSIONS_JSON="[]"
|
||||
fi
|
||||
|
||||
# Filter by adapter and/or cwd client-side
|
||||
SESSIONS_JSON=$(echo "$SESSIONS_JSON" | python3 -c "
|
||||
import sys, json
|
||||
sessions = json.load(sys.stdin)
|
||||
adapter_filter = '$ADAPTER' if '$ADAPTER_EXPLICIT' == 'true' else None
|
||||
cwd_filter = '$(pwd)' if '$ALL_MODE' == 'false' else None
|
||||
if adapter_filter:
|
||||
sessions = [s for s in sessions if s.get('adapter') == adapter_filter]
|
||||
if cwd_filter:
|
||||
sessions = [s for s in sessions if s.get('cwd') == cwd_filter]
|
||||
json.dump(sessions, sys.stdout)
|
||||
" 2>/dev/null)
|
||||
|
||||
# Parse and display
|
||||
COUNT=$(echo "$SESSIONS_JSON" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null)
|
||||
|
||||
if [ "$COUNT" = "0" ] || [ -z "$COUNT" ]; then
|
||||
if [ "$ADAPTER_EXPLICIT" = true ]; then
|
||||
echo "No active $ADAPTER sessions."
|
||||
elif [ "$ALL_MODE" = true ]; then
|
||||
echo "No active sessions."
|
||||
else
|
||||
echo "No active sessions for project '$(basename "$(pwd)")'."
|
||||
echo "Run 'codetap -A' to see all projects, or 'codetap new' to start a new session."
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$ALL_MODE" = true ]; then
|
||||
HEADER="Active sessions (all projects)"
|
||||
else
|
||||
HEADER="Active sessions for $(basename "$(pwd)")"
|
||||
fi
|
||||
[ "$ADAPTER_EXPLICIT" = true ] && HEADER="$HEADER — $ADAPTER only"
|
||||
echo "$HEADER:"
|
||||
echo ""
|
||||
|
||||
# Render each session
|
||||
echo "$SESSIONS_JSON" | python3 -c "
|
||||
import sys, json
|
||||
|
||||
sessions = json.load(sys.stdin)
|
||||
colors = {'claude': '\033[33m', 'codex': '\033[32m', 'gemini': '\033[34m'}
|
||||
reset = '\033[0m'
|
||||
home = '$HOME'
|
||||
|
||||
for i, s in enumerate(sessions, 1):
|
||||
adapter = s.get('adapter', '?')
|
||||
sid = s.get('sessionId', '?')
|
||||
cwd = s.get('cwd', '')
|
||||
first = s.get('firstPrompt', '')
|
||||
color = colors.get(adapter, '\033[90m')
|
||||
label = f'{color}[{adapter.capitalize()}]{reset}'
|
||||
|
||||
print(f' {i}) {label} {sid}')
|
||||
if $ALL_MODE and cwd:
|
||||
print(f' Dir: {cwd.replace(home, \"~\")}')
|
||||
if first:
|
||||
print(f' {first[:60]}')
|
||||
print()
|
||||
" 2>/dev/null
|
||||
|
||||
# Interactive selection
|
||||
read -p "Select (1-$COUNT), or Enter to cancel: " CHOICE
|
||||
if [ -n "$CHOICE" ]; then
|
||||
TARGET=$(echo "$SESSIONS_JSON" | python3 -c "
|
||||
import sys, json
|
||||
sessions = json.load(sys.stdin)
|
||||
idx = int('$CHOICE') - 1
|
||||
if 0 <= idx < len(sessions):
|
||||
print(sessions[idx]['sessionId'])
|
||||
" 2>/dev/null)
|
||||
if [ -n "$TARGET" ]; then
|
||||
tmux select-window -t "$TMUX_SESSION:$TARGET" 2>/dev/null
|
||||
tmux attach -t "$TMUX_SESSION"
|
||||
fi
|
||||
else
|
||||
echo "Cancelled."
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
# Start a Gemini and Claude session first, then:
|
||||
codetap -a # Current project sessions
|
||||
codetap -A # All sessions
|
||||
codetap -a --adapter gemini # Only Gemini sessions for current project
|
||||
codetap -A --adapter codex # Only Codex sessions across all projects
|
||||
```
|
||||
|
||||
Expected: Sessions show correct adapter labels from server (not from pane_current_command). Filter works.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add bin/codetap
|
||||
git commit -m "feat(cli): -a/-A uses server API for accurate adapter info + --adapter filter"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: `--continue` Support `--adapter` Filter
|
||||
|
||||
**Files:**
|
||||
- Modify: `bin/codetap` (`--continue` handler)
|
||||
|
||||
- [ ] **Step 1: Update `--continue` to pass adapter to API and filter by adapter**
|
||||
|
||||
Replace the `--continue` handler with:
|
||||
|
||||
```bash
|
||||
elif [ "$1" = "--continue" ]; then
|
||||
shift
|
||||
|
||||
# If adapter specified, find most recent session for that adapter
|
||||
if [ "$ADAPTER_EXPLICIT" = true ]; then
|
||||
AUTH_TOKEN=$(get_auth_token)
|
||||
SESSIONS_JSON=$(curl -s $CURL_OPTS "$PROTOCOL://localhost:$PORT/api/active-sessions" \
|
||||
-H "Authorization: Bearer $AUTH_TOKEN" 2>/dev/null)
|
||||
LATEST=$(echo "$SESSIONS_JSON" | python3 -c "
|
||||
import sys, json
|
||||
sessions = json.load(sys.stdin)
|
||||
filtered = [s for s in sessions if s.get('adapter') == '$ADAPTER']
|
||||
if filtered:
|
||||
# Sort by lastActivity descending
|
||||
filtered.sort(key=lambda s: s.get('lastActivity', 0), reverse=True)
|
||||
print(filtered[0]['sessionId'])
|
||||
" 2>/dev/null)
|
||||
else
|
||||
# No adapter specified — find most recent tmux window
|
||||
LATEST=$(tmux list-windows -t "$TMUX_SESSION" -F '#{window_activity} #{window_name}' 2>/dev/null | grep -v " main$" | sort -rn | head -1 | awk '{print $2}')
|
||||
fi
|
||||
|
||||
if [ -n "$LATEST" ]; then
|
||||
# Check if the process in the pane is still running
|
||||
PANE_CMD=$(tmux display -t "$TMUX_SESSION:$LATEST" -p '#{pane_current_command}' 2>/dev/null)
|
||||
if [ "$PANE_CMD" = "zsh" ] || [ "$PANE_CMD" = "bash" ]; then
|
||||
# CLI process exited, shell is showing — resume via API
|
||||
AUTH_TOKEN="${AUTH_TOKEN:-$(get_auth_token)}"
|
||||
BODY=$(printf '%s\n%s\n%s' "$LATEST" "$ADAPTER" "$(pwd)" | python3 -c 'import sys,json; s,a,c=sys.stdin.read().strip().split("\n"); print(json.dumps({"sessionId":s,"adapter":a,"cwd":c}))' 2>/dev/null)
|
||||
curl -s $CURL_OPTS -X POST "${PROTOCOL}://localhost:${PORT}/api/sessions/resume" \
|
||||
-H "Authorization: Bearer $AUTH_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$BODY" >/dev/null 2>&1
|
||||
fi
|
||||
tmux select-window -t "$TMUX_SESSION:$LATEST"
|
||||
else
|
||||
if [ "$ADAPTER_EXPLICIT" = true ]; then
|
||||
echo "No active $ADAPTER sessions to continue"
|
||||
else
|
||||
echo "No active sessions to continue"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tmux attach -t "$TMUX_SESSION"
|
||||
exit 0
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
codetap --continue # Resume most recent (any adapter)
|
||||
codetap --continue --adapter gemini # Resume most recent Gemini
|
||||
codetap --continue --adapter codex # Resume most recent Codex
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add bin/codetap
|
||||
git commit -m "feat(cli): --continue supports --adapter filter + passes adapter to resume API"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: `hooks install/uninstall` Support `--adapter` Filter
|
||||
|
||||
**Files:**
|
||||
- Modify: `bin/codetap:84-86` (hooks handler)
|
||||
- Modify: `bin/hooks-cli.mjs` (accept adapter argument)
|
||||
|
||||
- [ ] **Step 1: Update hooks-cli.mjs to accept optional adapter**
|
||||
|
||||
Replace `bin/hooks-cli.mjs`:
|
||||
|
||||
```javascript
|
||||
#!/usr/bin/env node
|
||||
// Standalone hook management — no server needed.
|
||||
// Usage: node hooks-cli.mjs install|uninstall [adapter]
|
||||
// adapter: claude, codex, gemini, or omit for all
|
||||
import { ClaudeHookConfig } from '../server/adapters/claude/hook-config.js';
|
||||
import { CodexHookConfig } from '../server/adapters/codex/hook-config.js';
|
||||
import { GeminiHookConfig } from '../server/adapters/gemini/hook-config.js';
|
||||
|
||||
const cmd = process.argv[2];
|
||||
const adapterArg = process.argv[3]; // optional: claude, codex, gemini
|
||||
|
||||
if (!cmd || !['install', 'uninstall'].includes(cmd)) {
|
||||
console.error('Usage: hooks-cli.mjs install|uninstall [claude|codex|gemini]');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const adapters = {
|
||||
claude: new ClaudeHookConfig(),
|
||||
codex: new CodexHookConfig(),
|
||||
gemini: new GeminiHookConfig(),
|
||||
};
|
||||
|
||||
const targets = adapterArg ? { [adapterArg]: adapters[adapterArg] } : adapters;
|
||||
|
||||
if (adapterArg && !adapters[adapterArg]) {
|
||||
console.error(`Unknown adapter: ${adapterArg}. Use: claude, codex, gemini`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
for (const [name, config] of Object.entries(targets)) {
|
||||
if (cmd === 'install') {
|
||||
config.install();
|
||||
} else {
|
||||
config.uninstall();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Update bin/codetap hooks handler to pass adapter**
|
||||
|
||||
Replace lines 84-86:
|
||||
|
||||
```bash
|
||||
hooks)
|
||||
if [ "$ADAPTER_EXPLICIT" = true ]; then
|
||||
node "$SCRIPT_DIR/hooks-cli.mjs" "$2" "$ADAPTER"
|
||||
else
|
||||
node "$SCRIPT_DIR/hooks-cli.mjs" "$2"
|
||||
fi
|
||||
exit 0 ;;
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verify**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
codetap hooks install # Install all
|
||||
codetap hooks uninstall # Uninstall all
|
||||
codetap hooks install --adapter gemini # Install Gemini only
|
||||
codetap hooks uninstall --adapter claude # Uninstall Claude only
|
||||
```
|
||||
|
||||
Wait — `--adapter` is parsed before `hooks` command, but the `hooks` case is in the early `case "$1" in` block which runs before `ensure_server`. Need to check if `--adapter` parsing happens before the case block after Task 1 moves it.
|
||||
|
||||
After Task 1, the parsing order is:
|
||||
1. `--adapter` parsed and stripped (lines 23-55 after move)
|
||||
2. `case "$1" in` — now `$1` is `hooks` (not `--adapter`)
|
||||
|
||||
So `codetap --adapter gemini hooks install` works. But `codetap hooks install --adapter gemini` needs the adapter parsing to handle args in any order. After Task 1's `set --` cleanup, `$1` would be `hooks` and `$2` would be `install` — correct.
|
||||
|
||||
But what about `codetap hooks --adapter gemini install`? The `--adapter` stripping would remove `--adapter gemini`, leaving `hooks install`. That works too.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add bin/codetap bin/hooks-cli.mjs
|
||||
git commit -m "feat(cli): hooks install/uninstall supports --adapter for single-adapter targeting"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Fix All Help Text and Comments
|
||||
|
||||
**Files:**
|
||||
- Modify: `bin/codetap` (header comments, help text, no-args output)
|
||||
|
||||
- [ ] **Step 1: Fix header comment (lines 1-14)**
|
||||
|
||||
Replace with:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# codetap — CLI wrapper that runs AI coding assistants in tmux for mobile sync
|
||||
#
|
||||
# Usage:
|
||||
# codetap # Start server, show URLs
|
||||
# codetap new # New session (default: claude)
|
||||
# codetap new --adapter gemini # New Gemini session
|
||||
# codetap --resume <session-id> # Resume a specific session
|
||||
# codetap --continue # Resume the most recent session
|
||||
# codetap --continue --adapter codex # Resume most recent Codex session
|
||||
# codetap -a # List active sessions (current project)
|
||||
# codetap -a --adapter gemini # List Gemini sessions only
|
||||
# codetap -A # List ALL active sessions (all projects)
|
||||
# codetap stop # Stop the server (graceful cleanup)
|
||||
# codetap hooks install # Install hooks for all adapters
|
||||
# codetap hooks install --adapter gemini # Install hooks for Gemini only
|
||||
# codetap cert # Generate self-signed HTTPS cert
|
||||
#
|
||||
# Adapters: claude (default), codex, gemini
|
||||
# Sessions run inside tmux session "codetap".
|
||||
# Mobile app auto-connects for real-time sync.
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Fix help text (lines 30-49)**
|
||||
|
||||
Replace with:
|
||||
|
||||
```bash
|
||||
cat << 'HELP'
|
||||
Usage: codetap [options] [command]
|
||||
|
||||
Commands:
|
||||
new Start a new session (default: Claude)
|
||||
stop Stop the server (graceful cleanup)
|
||||
hooks install Install hooks (all adapters, or use --adapter)
|
||||
hooks uninstall Remove hooks (all adapters, or use --adapter)
|
||||
cert Generate self-signed HTTPS certificate
|
||||
|
||||
Options:
|
||||
-v, --version Show version
|
||||
-h, --help Show this help
|
||||
-a List active sessions (current project)
|
||||
-A List ALL active sessions (all projects)
|
||||
--adapter <name> Adapter: claude (default), codex, gemini
|
||||
--resume <session-id> Resume a specific session
|
||||
--continue Resume most recent session
|
||||
|
||||
Examples:
|
||||
codetap new --adapter gemini Start a Gemini session
|
||||
codetap -a --adapter codex List active Codex sessions
|
||||
codetap --continue --adapter gemini Continue most recent Gemini session
|
||||
codetap hooks install --adapter claude Install Claude hooks only
|
||||
HELP
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Fix no-args output (lines 218-229)**
|
||||
|
||||
Replace with:
|
||||
|
||||
```bash
|
||||
echo ""
|
||||
echo "CodeTap server is running on port $PORT"
|
||||
echo ""
|
||||
echo " Open on your phone:"
|
||||
if [ -n "$TS_HOST" ]; then echo " https://${TS_HOST} (Tailscale)"; fi
|
||||
if [ -n "$LAN_IP" ]; then echo " ${PROTOCOL}://${LAN_IP}:${PORT} (LAN)"; fi
|
||||
echo " http://localhost:${PORT} (this machine)"
|
||||
echo ""
|
||||
echo " New session: codetap new [--adapter codex|gemini]"
|
||||
echo " Continue: codetap --continue"
|
||||
echo " List sessions: codetap -a"
|
||||
echo " Stop server: codetap stop"
|
||||
echo ""
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Remove outdated comment on line 18**
|
||||
|
||||
Delete line 18: `# Claude stores sessions by project dir; Codex uses date-based dirs (handled in get_project_sessions)`
|
||||
|
||||
- [ ] **Step 5: Verify all text output**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
codetap --help
|
||||
codetap --version
|
||||
codetap 2>&1 | head -15
|
||||
```
|
||||
|
||||
Verify no mention of "Claude" in contexts where it should say "adapter", and Gemini is listed everywhere.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add bin/codetap
|
||||
git commit -m "fix(cli): update all help text, comments, and output for multi-adapter support"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: E2E Verification — All Commands × All Flags
|
||||
|
||||
**Files:** None (testing only)
|
||||
|
||||
- [ ] **Step 1: Restart server fresh**
|
||||
|
||||
```bash
|
||||
lsof -ti :3456 | xargs kill -9 2>/dev/null
|
||||
tmux kill-session -t codetap 2>/dev/null
|
||||
sleep 2
|
||||
export CLAUDE_UI_PASSWORD=test
|
||||
CLAUDE_UI_PASSWORD=test npx tsx server/index.ts > /tmp/codetap-cli-test.log 2>&1 &
|
||||
sleep 6
|
||||
curl -sk https://localhost:3456/health
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Test `--version` and `--help`**
|
||||
|
||||
```bash
|
||||
codetap --version
|
||||
# Expected: codetap v1.3.2
|
||||
|
||||
codetap --help
|
||||
# Expected: Multi-adapter help text with Examples section
|
||||
# Verify: "claude (default), codex, gemini" appears
|
||||
# Verify: No "Claude-only" language
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Test `codetap` (no args)**
|
||||
|
||||
```bash
|
||||
codetap
|
||||
# Expected: Server URLs + multi-adapter usage hints
|
||||
# Verify: "codetap new [--adapter codex|gemini]" in output
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Test `codetap new` (all 3 adapters)**
|
||||
|
||||
```bash
|
||||
# Create sessions using the API (non-interactive, can't attach tmux from subagent)
|
||||
TOKEN=$(curl -sk -X POST "https://localhost:3456/api/auth/login" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"password":"test"}' | python3 -c 'import sys,json; print(json.load(sys.stdin)["token"])')
|
||||
|
||||
# Claude
|
||||
RESULT=$(curl -sk -X POST "https://localhost:3456/api/sessions/start" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"adapter\":\"claude\",\"cwd\":\"$(pwd)\"}")
|
||||
echo "Claude: $RESULT"
|
||||
|
||||
# Codex
|
||||
RESULT=$(curl -sk -X POST "https://localhost:3456/api/sessions/start" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"adapter\":\"codex\",\"cwd\":\"$(pwd)\"}")
|
||||
echo "Codex: $RESULT"
|
||||
|
||||
# Gemini
|
||||
RESULT=$(curl -sk -X POST "https://localhost:3456/api/sessions/start" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"adapter\":\"gemini\",\"cwd\":\"$(pwd)\"}")
|
||||
echo "Gemini: $RESULT"
|
||||
|
||||
# Verify tmux windows
|
||||
tmux list-windows -t codetap -F '#{window_name}' | grep -v main
|
||||
```
|
||||
|
||||
Expected: 3 session IDs returned, 3 tmux windows created.
|
||||
|
||||
- [ ] **Step 5: Test `-a` and `-A` with and without `--adapter`**
|
||||
|
||||
```bash
|
||||
echo "" | codetap -a 2>&1
|
||||
# Expected: Lists sessions for current project with [Claude], [Codex], [Gemini] labels
|
||||
|
||||
echo "" | codetap -A 2>&1
|
||||
# Expected: Lists ALL sessions with Dir: info
|
||||
|
||||
echo "" | codetap -a --adapter gemini 2>&1
|
||||
# Expected: Only Gemini sessions listed
|
||||
|
||||
echo "" | codetap -A --adapter codex 2>&1
|
||||
# Expected: Only Codex sessions across all projects
|
||||
|
||||
echo "" | codetap -a --adapter claude 2>&1
|
||||
# Expected: Only Claude sessions for current project
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Test `--continue` with and without `--adapter`**
|
||||
|
||||
Can't test tmux attach non-interactively, but verify the session selection logic:
|
||||
|
||||
```bash
|
||||
# Check which session --continue would pick
|
||||
LATEST=$(tmux list-windows -t codetap -F '#{window_activity} #{window_name}' | grep -v " main$" | sort -rn | head -1 | awk '{print $2}')
|
||||
echo "Most recent (any adapter): $LATEST"
|
||||
|
||||
# With --adapter, verify API query works
|
||||
TOKEN=$(curl -sk -X POST "https://localhost:3456/api/auth/login" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"password":"test"}' | python3 -c 'import sys,json; print(json.load(sys.stdin)["token"])')
|
||||
curl -sk "https://localhost:3456/api/active-sessions" \
|
||||
-H "Authorization: Bearer $TOKEN" | python3 -c "
|
||||
import sys, json
|
||||
for s in json.load(sys.stdin):
|
||||
print(f'{s[\"adapter\"]:8s} {s[\"sessionId\"][:12]}... last={s.get(\"lastActivity\",0)}')
|
||||
"
|
||||
```
|
||||
|
||||
- [ ] **Step 7: Test `hooks install/uninstall` with and without `--adapter`**
|
||||
|
||||
```bash
|
||||
codetap hooks uninstall
|
||||
# Expected: All 3 adapters' hooks removed
|
||||
|
||||
codetap hooks install --adapter gemini
|
||||
# Expected: Only Gemini hooks installed
|
||||
cat ~/.gemini/settings.json | python3 -c "import sys,json; print('hooks' in json.load(sys.stdin))"
|
||||
# Expected: True
|
||||
|
||||
cat ~/.claude/settings.json | python3 -c "import sys,json; d=json.load(sys.stdin); print('hooks' in d and any('codetap' in str(v).lower() for v in d.get('hooks',{}).values()))"
|
||||
# Expected: False (Claude hooks not installed)
|
||||
|
||||
codetap hooks install
|
||||
# Expected: All 3 adapters' hooks installed
|
||||
|
||||
codetap hooks uninstall --adapter claude
|
||||
# Expected: Only Claude hooks removed
|
||||
```
|
||||
|
||||
- [ ] **Step 8: Test `--resume`**
|
||||
|
||||
```bash
|
||||
# Get a session ID from the list
|
||||
SID=$(tmux list-windows -t codetap -F '#{window_name}' | grep -v main | head -1)
|
||||
echo "Resuming: $SID"
|
||||
# Can't test tmux attach, but verify API:
|
||||
TOKEN=$(curl -sk -X POST "https://localhost:3456/api/auth/login" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"password":"test"}' | python3 -c 'import sys,json; print(json.load(sys.stdin)["token"])')
|
||||
curl -sk -X POST "https://localhost:3456/api/sessions/resume" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"sessionId\":\"$SID\",\"adapter\":\"claude\",\"cwd\":\"$(pwd)\"}"
|
||||
# Expected: {"sessionId":"..."}
|
||||
```
|
||||
|
||||
- [ ] **Step 9: Test `stop` and `cert`**
|
||||
|
||||
```bash
|
||||
codetap stop
|
||||
# Expected: "Stopping CodeTap server..." → "Server stopped."
|
||||
|
||||
# Cert already exists, just verify the command runs
|
||||
echo "n" | codetap cert
|
||||
# Expected: "Certificate already exists..." prompt, then exits
|
||||
```
|
||||
|
||||
- [ ] **Step 10: Commit test results as verification log**
|
||||
|
||||
```bash
|
||||
git add -f docs/superpowers/plans/
|
||||
git commit -m "docs: CLI multi-adapter verification complete"
|
||||
```
|
||||
Reference in New Issue
Block a user