Add 'aiui/' from commit 'e30ac1d1069532fb6d652d87e2d4a2fe9d1b4773'

git-subtree-dir: aiui
git-subtree-mainline: 0c4826f8cc
git-subtree-split: e30ac1d106
This commit is contained in:
archipelago
2026-08-03 15:07:11 -04:00
384 changed files with 68967 additions and 0 deletions
+150
View File
@@ -0,0 +1,150 @@
# Overnight Claude Automation
Run Claude Code autonomously while you're away. Combines sleep prevention, task-based execution, the Ralph Wiggum Technique (Stop hook blocks until plan is complete), and security hooks that restrict AI to project files and block destructive commands.
## Prerequisites
- **Claude Code CLI** ([claude.ai/code](https://claude.ai/code)) — installed at `~/.local/bin/claude` or in PATH
- **Hooks** — user-level hooks in `~/.claude/` (sleep, Ralph Wiggum)
- **jq** — for security hook scripts (`brew install jq`)
## Flow
### Pre-run (before 56pm)
1. **Commit and push** — Snap current work and back up to remote.
2. **Run prepare script** — Creates date-stamped branch and verifies clean state:
```bash
./loop/prepare.sh
```
3. **Edit plan** — Update `loop/plan.md` with evening scope and tasks (see template below).
4. **Commit plan** — Version the plan so you can revert if needed:
```bash
git add loop/plan.md && git commit -m "chore: overnight plan $(date +%Y-%m-%d)"
```
5. **Push** (optional but recommended): `git push -u origin overnight/YYYY-MM-DD`
### Overnight
```bash
tmux new -s overnight
caffeinate -i ./loop/loop.sh
# Detach: Ctrl+B, then D
```
### Post-run (next morning)
1. `git status` and `git diff` to review changes.
2. Run `pnpm test && pnpm lint && pnpm typecheck`.
3. Merge branch or revert if needed.
## Quick Start
1. **Edit your plan** — Add tasks to `loop/plan.md` using the evening run format:
```markdown
# Evening Run — YYYY-MM-DD
## Scope
Add tests to chat components.
## Tasks
- [ ] Add unit tests for useAI composable
- [ ] Fix linter errors in packages/app
```
2. **Run overnight** — From project root:
```bash
caffeinate -i ./loop/loop.sh
```
## How It Works
| Component | Purpose |
|-----------|---------|
| **UserPromptSubmit hook** | Starts `caffeinate` to prevent Mac sleep when you submit a prompt |
| **Stop hook** | Checks `plan.md` for unchecked tasks; blocks Claude from stopping until all are done (Ralph Wiggum) |
| **SessionEnd hook** | Kills `caffeinate` so Mac can sleep again |
| **PreToolUse (Bash)** | Blocks dangerous commands (rm -rf, git reset --hard, etc.) |
| **PreToolUse (Edit\|Write)** | Blocks edits outside project and to protected paths |
| **loop.sh** | Runs Claude with `--dangerously-skip-permissions` and feeds the prompt from `loop/prompt.md` |
## Security Model
Project-scoped hooks in `.claude/hooks/` restrict the AI during overnight runs:
### Bash guard (`block-risky-bash.sh`)
Blocks: `rm -rf`, `git reset --hard`, `git push --force`, `git clean -fd`, `chmod -R 777`, fork bombs, block device overwrites, `mkfs`, and path traversal with destructive commands.
### File edit guard (`protect-files.sh`)
Blocks Edit/Write when:
- Path is **outside project directory**
- Path contains **`.git/`**
- Path is **`.env`**, **`.env.local`**, **`.env.*.local`**
- Path is **`package-lock.json`** or **`pnpm-lock.yaml`**
- Path contains **`node_modules/`**
Read, Glob, and Grep remain unrestricted.
## Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `CLAUDE_AUTONOMOUS` | `1` | Set to `1` to enable Ralph Wiggum (Stop hook checks plan). `0` disables. |
| `CLAUDE_PLAN_FILE` | `plan.md` | Plan file path (relative to project). |
| `ITERATION_COUNT` | `1` | Number of loop iterations (use >1 for multi-run without Ralph Wiggum). |
| `ITERATION_DELAY` | `600` | Seconds between iterations when `ITERATION_COUNT` > 1. |
| `PROMPT_FILE` | `loop/prompt.md` | Prompt content for Claude. |
| `LOG_FILE` | `loop/loop.log` | Log output (gitignored). |
| `RATE_LIMIT_WAIT` | `3600` | Seconds to wait when rate limited (default 1 hour). |
| `MAX_RATE_LIMIT_RETRIES` | `5` | Max rate limit retries before scheduling launchd job. |
## Rate Limit Handling
The loop script automatically detects rate limits (429, quota exceeded, etc.) and handles them:
1. **Inline retry** — On first rate limit hit, sleeps for `RATE_LIMIT_WAIT` seconds (default 1 hour) and retries.
2. **Escalating retries** — Retries up to `MAX_RATE_LIMIT_RETRIES` times with the same wait.
3. **launchd fallback** — After max retries, creates a self-cleaning launchd plist at `~/Library/LaunchAgents/com.aiui.overnight-retry.plist` that restarts the loop at the estimated reset time. The plist auto-removes after running.
This means you can walk away knowing the automation will survive rate limits overnight.
## Scheduling (Optional)
Install [claude-code-schedule](https://github.com/macalinao/claude-code-schedule) for time-based runs:
```bash
cargo install claude-code-schedule
ccschedule --time 05:30 --message "Review plan.md and complete next task"
```
## continuous-claude (Optional)
For full PR-based workflow (branches, PRs, CI):
```bash
# Install from https://github.com/AnandChowdhary/continuous-claude
continuous-claude -p "Work through loop/plan.md" -m 10 --max-duration 8h
```
## Remote Monitoring
- **Tmux + SSH**: Attach from another machine: `ssh host 'tmux attach -t overnight'`
- **Tailscale**: Use Tailscale for easy remote SSH when away from home network
- **Log tail**: `tail -f loop/loop.log` to watch progress
## Safety
- **Start small** — Test with 12 tasks before overnight runs
- **prepare.sh** — Run before starting; fails if working tree is dirty or branch exists
- **Git** — Loop does not auto-commit; you review and merge in the morning
- **`--dangerously-skip-permissions`** — Security hooks still run and block dangerous actions
- **Project-scoped hooks** — Only apply when Claude runs in AIUI; other projects unaffected
+286
View File
@@ -0,0 +1,286 @@
# AIUI Debug, Fix & Hardening Plan
## Execution Rules
- Run on `development` branch
- Each `- [ ]` task is one agent iteration — complete fully before moving on
- After each phase: `pnpm typecheck && pnpm lint && pnpm test -- --run`
- If checks fail, fix before proceeding
- Commit at end of each phase with `type(scope): description` format
- Do NOT push — human will review and push
---
## PHASE 0: Baseline Verification
- [ ] **P0.1 — Baseline check**
- Run: `pnpm install && pnpm typecheck && pnpm lint && pnpm test -- --run`
- Record output. Note existing failures. Fix any blockers before proceeding.
- Commit: `chore(app): verify baseline before hardening`
---
## PHASE 1: Critical Security Fixes
- [ ] **P1.1 — Fix CORS wildcard in claude-proxy.ts**
- File: `packages/app/server/claude-proxy.ts`
- Replace all `'Access-Control-Allow-Origin': '*'` (lines ~415, 473, 531) with the correct localhost origin or import `ALLOWED_ORIGIN` from `dev-auth.ts`. Match the pattern already used on lines 294/303.
- Verify: `grep -n "Allow-Origin.*\*" packages/app/server/claude-proxy.ts` returns zero matches.
- [ ] **P1.2 — Fix dev auth token bypass**
- File: `packages/app/server/dev-auth.ts`
- Line 11: `if (!token) return true` skips auth entirely when token empty.
- Fix: Only skip in non-production. `if (!token) { if (process.env.NODE_ENV === 'production') { res.writeHead(401); res.end('Unauthorized'); return false; } return true; }`
- Add console.warn when auth disabled.
- [ ] **P1.3 — Symlink traversal protection in vite-fs.ts**
- File: `packages/app/vite-fs.ts`
- In `walk` function: after constructing `fullPath`, add `if (lstatSync(fullPath).isSymbolicLink()) continue;`
- In `handleRead`: before `statSync`, check `lstatSync(filePath).isSymbolicLink()` → return 403.
- Import `lstatSync` from `fs`.
- [ ] **P1.4 — Body size limit on vite-fs.ts mkdir**
- File: `packages/app/vite-fs.ts`
- `handleMkdir` reads body with no size cap (lines 185-209).
- Add `const MAX_BODY_SIZE = 1024`. Track `let size = 0` on `data` events. Return 413 if exceeded.
- [ ] **P1.5 — JSON schema validation in vite-dev-chats.ts**
- File: `packages/app/vite-dev-chats.ts`
- After `JSON.parse(body)` on line 66, validate shape: must be object with optional `conversations` (object) and `activeConversationId` (string|null). Reject with 400 if invalid.
- [ ] **P1.6 — CSP headers in nginx-archy.conf**
- File: `packages/app/server/nginx-archy.conf`
- Add inside `/aiui/` location block:
```
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' https: data:; connect-src 'self' https://api.anthropic.com https://openrouter.ai https://wavlake.com https://itunes.apple.com https://openlibrary.org https://covers.openlibrary.org https://en.wikipedia.org https://www.googleapis.com https://image.tmdb.org; frame-ancestors 'self'; base-uri 'self'; form-action 'self';" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
```
- [ ] **P1.7 — OpenRouter validation + streaming timeout**
- File: `packages/app/server/claude-proxy.ts`
- OpenRouter handler: parse `reqBody`, validate has `model` (string), `messages` (array), `stream` (boolean). Return 400 if invalid.
- Both streaming reader loops: add idle timeout (120s). `let idleTimer = setTimeout(() => reader.cancel(), 120000)`. Reset on each chunk. Clear on completion.
- Verify: `pnpm typecheck`
- **Commit**: `fix(app): critical security — CORS, auth, symlink, CSP, body validation`
---
## PHASE 2: Proxy & Server Hardening
- [ ] **P2.1 — Fix tool use loop in claude-proxy.ts**
- File: `packages/app/server/claude-proxy.ts` (lines 164-222)
- Unknown tool names: push error tool_result `{ type: 'tool_result', tool_use_id: tu.id, content: 'Error: unknown tool', is_error: true }`.
- Add turnMessages size guard: `if (JSON.stringify(turnMessages).length > 500_000)` break loop.
- Add `AbortSignal.timeout(30000)` on each API call within loop.
- [ ] **P2.2 — SSRF mitigation in vite-rss.ts**
- File: `packages/app/vite-rss.ts`
- After `tryParseFeed` returns, validate returned article URLs with `isPrivateUrl`. Filter out any private URLs.
- Add comment documenting residual TOCTOU risk (dev-only middleware).
- [ ] **P2.3 — Sanitize web search context in useAI.ts**
- File: `packages/app/src/composables/useAI.ts` (lines 399-408)
- Create helper: `function sanitizeSearchText(s: string): string { return s.replace(/[[\]()#*_~` >]/g, '\\$&').replace(/\n/g, ' ').slice(0, 500) }`
- Apply to `r.title` and `r.content` in `formatWebSearchContext`.
- **Commit**: `fix(app): proxy hardening — tool loop, SSRF, prompt injection`
---
## PHASE 3: State & Logic Bugs
- [ ] **P3.1 — Fix contentType assignment in useContentPanel.ts**
- File: `packages/app/src/composables/useContentPanel.ts` (lines 219-225)
- The `contentType` ref type is `'film' | 'song' | 'podcast'`. Expand to include all content types or use `ContentTab`. Fix assignments: books→'film' is acceptable if type can't expand, but news/TV should map correctly. If the type is only used for player logic, keep narrow type but fix assignments to be semantically correct.
- [ ] **P3.2 — Fix apiFetching race in useBannerFallback.ts**
- File: `packages/app/src/composables/useBannerFallback.ts`
- Change `let apiFetching = false` to `const apiFetching = ref(false)`. Update all references to `.value`.
- [ ] **P3.3 — Fix event listener cleanup in chat.ts**
- File: `packages/app/src/stores/chat.ts` (lines 111-118)
- Extract handlers to named functions. Add `import.meta.hot?.dispose()` cleanup for HMR.
- [ ] **P3.4 — Fix news RSS race condition in useContentPanel.ts**
- File: `packages/app/src/composables/useContentPanel.ts` (lines 134-154)
- Capture `const tabAtStart = activeTab.value` before RSS fetch. In `.then()`, only set `activeTab.value = 'news'` if `activeTab.value === tabAtStart` (user hasn't manually switched).
- [ ] **P3.5 — Add error logging to silent catch blocks**
- Files: `useImageFallback.ts`, `useAI.ts`, `stores/chat.ts`
- Replace all `catch { }` and `catch { /* ignore */ }` with `catch(e) { console.debug('[module] op failed:', e) }`.
- Keep `console.debug` (not `error`) for expected failures.
- **Commit**: `fix(app): state bugs — contentType, race conditions, error logging`
---
## PHASE 4: Content Extraction & Filtering
- [ ] **P4.1 — Add recipe instructions to system prompt**
- File: `packages/app/src/composables/useAI.ts` (SYSTEM_PROMPT)
- Add after Apps section:
```
**Recipes:** When sharing recipes, use the <recipe_ext> XML tag:
<recipe_ext title="Name" servings="4" time="30 min" calories="450">
- ingredient 1
1. Step one
</recipe_ext>
```
- [ ] **P4.2 — Fix app category validation**
- File: `packages/app/src/composables/contentExtraction.ts` (line ~1330)
- Add `const VALID_CATEGORIES = new Set(['nostr-client','lightning-wallet','bitcoin-wallet','privacy','node','dev-tool','relay'])`. Validate before cast, fallback to `'dev-tool'`.
- [ ] **P4.3 — Fix song deduplication**
- File: `packages/app/src/composables/contentExtraction.ts` (lines 606-638)
- After combining library + external songs, deduplicate by normalized `title|artist` key. Library songs (with real IDs) take priority.
- [ ] **P4.4 — Fix film/TV tag ambiguity**
- File: `packages/app/src/composables/contentExtraction.ts` (lines 914-916)
- Only convert `film_ext` to TV when: query is TV-like AND no explicit `tv_ext` tags present AND response reads TV-like. If AI used both `film_ext` and `tv_ext`, keep both as-is.
- [ ] **P4.5 — Fix isBookLikeResponse false positives**
- File: `packages/app/src/composables/contentFiltering.ts` (lines 60-62)
- Increase `by [Author]` threshold from `>= 1` to `>= 2`.
- [ ] **P4.6 — Fix isRecipeLikeResponse for prose recipes**
- File: `packages/app/src/composables/contentFiltering.ts` (lines 92-94)
- Add prose detection: recipe keywords + list structure pattern.
- [ ] **P4.7 — Fix looksLikeSong false positives**
- File: `packages/app/src/composables/contentExtraction.ts` (lines 488-506)
- Add financial terms to blocklist: `'market cap','etf','price','trading','volume','earnings','valuation','stock','portfolio','investment','yield','inflation','interest rate'`.
- **Commit**: `fix(app): extraction — dedup, tag ambiguity, false positives, recipes`
---
## PHASE 5: Memory & Cache Hardening
- [ ] **P5.1 — Bound caches in useImageFallback.ts**
- File: `packages/app/src/composables/useImageFallback.ts`
- Create helper: `function boundedSet<K,V>(map: Map<K,V>, key: K, val: V, max=500) { if (map.size >= max) { const first = map.keys().next().value; if (first !== undefined) map.delete(first); } map.set(key, val); }`
- Replace all `.set()` calls on memory caches with `boundedSet()`.
- Also bound `failedUrls` Set to 1000 entries.
- [ ] **P5.2 — Bound caches in usePlayer.ts + cleanup**
- File: `packages/app/src/composables/usePlayer.ts`
- Bound `resultCache` and `nullCacheTimestamps` to 200 entries.
- Add `destroy()` method that calls `destroyPlayer()`, resets DOM refs, cancels active search controller.
- **Commit**: `fix(app): bound caches, player cleanup`
---
## PHASE 6: Accessibility
- [ ] **P6.1 — Fix touch targets and aria attributes**
- Audit all `*Grid.vue` and `*Detail.vue` for: interactive elements < 44px, images without alt, SVG fallbacks without aria-label.
- ImageGrid.vue: add `role="img"` `aria-label="Image unavailable"` to fallback SVG wrapper.
- Ensure all interactive genre/tag pills in grids have adequate touch targets (min 44x44 including padding).
- Non-interactive info pills are exempt.
- **Commit**: `fix(app): accessibility — touch targets, alt text, aria`
---
## PHASE 7: Security Tests
- [ ] **P7.1 — Write dev-auth tests**
- New file: `packages/app/src/__tests__/dev-auth.test.ts`
- Tests: auth bypass when no token (dev), auth required when token set, rate limiting 429, CORS headers correct.
- [ ] **P7.2 — Extend proxy tests**
- File: `packages/app/src/__tests__/proxy.test.ts`
- Tests: unknown tool error result, max rounds termination, turnMessages size guard, streaming timeout.
- [ ] **P7.3 — Write vite-fs security tests**
- New file: `packages/app/src/__tests__/vite-fs.test.ts`
- Tests: path traversal rejected, symlink blocked, sensitive files blocked, body size limit enforced.
- [ ] **P7.4 — Write SSRF tests for vite-rss**
- New file: `packages/app/src/__tests__/vite-rss.test.ts`
- Tests: private IP detection, localhost blocked, non-http blocked, private URLs filtered from results.
- **Commit**: `test(app): security tests — auth, proxy, fs, SSRF`
---
## PHASE 8: Content Extraction Tests
- [ ] **P8.1 — Write content filtering tests**
- New file: `packages/app/src/composables/__tests__/contentFiltering.test.ts`
- Tests: isBookLikeResponse false positive fix, isRecipeLikeResponse prose detection, isMusicQuery rejects financial terms, filterTabsByContext ordering.
- [ ] **P8.2 — Extend content extraction tests**
- File: `packages/app/src/__tests__/contentExtraction.test.ts`
- Tests: song dedup, looksLikeSong rejects financial terms, app category fallback, recipe parsing.
- [ ] **P8.3 — Write TV/film resolution tests**
- Same file as P8.2.
- Tests: film_ext→TV conversion rules, book_ext in news context, explicit tags respected.
- **Commit**: `test(app): content extraction — filtering, dedup, tag resolution`
---
## PHASE 9: State & Composable Tests
- [ ] **P9.1 — Write useContentPanel tests**
- File: `packages/app/src/composables/__tests__/useContentPanel.test.ts`
- Tests: contentType assignment per content type, RSS tab override prevention, closePanel resets, empty text → no tabs.
- [ ] **P9.2 — Write useBannerFallback tests**
- New file: `packages/app/src/composables/__tests__/useBannerFallback.test.ts`
- Tests: primary URL fallthrough, API fetch on exhaustion, apiFetching guard, gradient fallback.
- [ ] **P9.3 — Write chat store tests**
- New file: `packages/app/src/__tests__/chat-store.test.ts`
- Tests: createConversation, addMessage, deleteConversation, branchFromMessage, debouncedIDBSave.
- **Commit**: `test(app): state tests — contentPanel, bannerFallback, chat store`
---
## PHASE 10: AI Integration Tests
- [ ] **P10.1 — Extend useAI tests**
- File: `packages/app/src/__tests__/useAI.test.ts`
- Tests: system prompt includes recipe instructions, web search results sanitized, editAndResend truncates, sanitizeHistory merges consecutive roles.
- [ ] **P10.2 — Write useImageFallback cache tests**
- New file: `packages/app/src/composables/__tests__/useImageFallback.test.ts`
- Tests: boundedSet evicts at max, generatePosterFallback valid SVG, escapeXml handles specials.
- **Commit**: `test(app): AI integration, image fallback cache tests`
---
## PHASE 11: Final Verification
- [ ] **P11.1 — Full integration check**
- Run: `pnpm typecheck && pnpm lint && pnpm test -- --run`
- Run: `pnpm build` — verify production build succeeds
- Fix any regressions.
- [ ] **P11.2 — Final commit**
- Run coverage report if configured: `pnpm test -- --run --coverage`
- Ensure all changes committed on `development`.
- **Commit**: `chore(app): hardening complete — all checks pass`
---
## Phase Dependencies
```
P0 → P1 → P2 → P3 → P4 → P5 ─┐
├→ P7 (tests P1,P2)
P6 ──┤
├→ P8 (tests P4)
├→ P9 (tests P3,P5)
├→ P10 (tests P2.3,P4.1)
└→ P11 (final)
```
P5 and P6 can run in parallel. P7-P10 can run in any order after their fix phases.
## Summary
- **12 phases**, **42 tasks**
- Phases 0-6: fixes (security → proxy → state → extraction → caches → a11y)
- Phases 7-10: tests (security → extraction → state → AI)
- Phase 11: final verification
- Target: all checks pass, 40%+ test coverage on critical paths, secure for Archy deployment
+200
View File
@@ -0,0 +1,200 @@
#!/usr/bin/env sh
# Headless loop script for overnight Claude Code automation.
# Set CLAUDE_AUTONOMOUS=1 for Ralph Wiggum (Stop hook blocks until plan is complete).
# Rate-limit aware: detects limits, sleeps until reset, and retries automatically.
set -u
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
PROMPT_FILE="${PROMPT_FILE:-$PROJECT_DIR/loop/prompt.md}"
LOG_FILE="${LOG_FILE:-$PROJECT_DIR/loop/loop.log}"
ITERATION_COUNT="${ITERATION_COUNT:-10}"
ITERATION_DELAY="${ITERATION_DELAY:-30}"
CLAUDE_BIN="${CLAUDE_BIN:-claude}"
RATE_LIMIT_WAIT="${RATE_LIMIT_WAIT:-3600}" # Default: wait 1 hour on rate limit
MAX_RATE_LIMIT_RETRIES="${MAX_RATE_LIMIT_RETRIES:-5}" # Max retries before giving up
CLAUDE_EXIT=0
cd "$PROJECT_DIR"
# Human-readable log with visual separators
log() {
echo "$1" | tee -a "$LOG_FILE"
}
banner() {
log ""
log "════════════════════════════════════════════════════════════════"
log " $1"
log " $(date '+%Y-%m-%d %H:%M:%S')"
log "════════════════════════════════════════════════════════════════"
log ""
}
section() {
log ""
log "────────────────────────────────────────"
log " $1"
log "────────────────────────────────────────"
log ""
}
# Check if plan has remaining tasks
plan_has_tasks() {
grep -q '^\- \[ \]' "$PROJECT_DIR/loop/plan.md" 2>/dev/null
}
# Show remaining task count
remaining_tasks() {
grep -c '^\- \[ \]' "$PROJECT_DIR/loop/plan.md" 2>/dev/null || echo "0"
}
# Show next task
next_task() {
grep -m1 '^\- \[ \]' "$PROJECT_DIR/loop/plan.md" 2>/dev/null | sed 's/^- \[ \] //' || echo "(none)"
}
# Detect rate limit from Claude output (only when Claude exited non-zero)
check_rate_limit() {
[ "${CLAUDE_EXIT:-0}" -eq 0 ] && return 1
# Check last 50 lines for rate limit indicators, excluding our own log lines
tail -50 "$LOG_FILE" 2>/dev/null | grep -v "^Rate limit detected" | grep -v "^Sleeping" | grep -v "^═" | grep -v "^─" | grep -qi \
-e "rate.limit" \
-e "too.many.requests" \
-e "429" \
-e "quota.exceeded" \
-e "usage.limit" \
-e "limit.reached" 2>/dev/null
}
banner "OVERNIGHT AUTOMATION STARTED"
log " Project: $PROJECT_DIR"
log " Prompt: $PROMPT_FILE"
log " Autonomous: ${CLAUDE_AUTONOMOUS:-0}"
log " Iterations: $ITERATION_COUNT (${ITERATION_DELAY}s between each)"
log " Rate limit: wait ${RATE_LIMIT_WAIT}s, retry up to ${MAX_RATE_LIMIT_RETRIES}x"
log " Tasks left: $(remaining_tasks)"
log " Next task: $(next_task)"
log ""
i=1
rate_limit_retries=0
while [ "$i" -le "$ITERATION_COUNT" ]; do
# Check if there are tasks remaining before starting
if ! plan_has_tasks; then
banner "ALL TASKS COMPLETE"
log " No remaining tasks in plan.md. Stopping."
break
fi
section "ITERATION $i/$ITERATION_COUNT"
log " Tasks remaining: $(remaining_tasks)"
log " Next task: $(next_task)"
log ""
export CLAUDE_PROJECT_DIR="$PROJECT_DIR"
export CLAUDE_AUTONOMOUS="${CLAUDE_AUTONOMOUS:-1}"
# Run Claude with autonomous permissions; prompt from file
if [ -f "$PROMPT_FILE" ]; then
log " Starting Claude session..."
log ""
"$CLAUDE_BIN" -p --dangerously-skip-permissions \
< "$PROMPT_FILE" 2>&1 | tee -a "$LOG_FILE"
CLAUDE_EXIT=$?
log ""
log " Claude exited with code: $CLAUDE_EXIT"
else
log " ERROR: $PROMPT_FILE not found"
exit 1
fi
# Check for rate limit after Claude exits
if check_rate_limit; then
rate_limit_retries=$((rate_limit_retries + 1))
if [ "$rate_limit_retries" -ge "$MAX_RATE_LIMIT_RETRIES" ]; then
section "RATE LIMITED — SCHEDULING LAUNCHD RETRY"
log " Hit rate limit $rate_limit_retries times. Creating launchd job to retry later."
# Schedule a retry using launchd for after rate limit resets
PLIST_LABEL="com.aiui.overnight-retry"
PLIST_PATH="$HOME/Library/LaunchAgents/${PLIST_LABEL}.plist"
RETRY_TIME=$(date -v+${RATE_LIMIT_WAIT}S '+%H:%M' 2>/dev/null || date -d "+${RATE_LIMIT_WAIT} seconds" '+%H:%M')
RETRY_HOUR=$(echo "$RETRY_TIME" | cut -d: -f1)
RETRY_MIN=$(echo "$RETRY_TIME" | cut -d: -f2)
cat > "$PLIST_PATH" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>${PLIST_LABEL}</string>
<key>ProgramArguments</key>
<array>
<string>/bin/sh</string>
<string>-c</string>
<string>cd ${PROJECT_DIR} && caffeinate -i ./loop/loop.sh >> ${LOG_FILE} 2>&1; launchctl unload ${PLIST_PATH}; rm -f ${PLIST_PATH}</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>${RETRY_HOUR}</integer>
<key>Minute</key>
<integer>${RETRY_MIN}</integer>
</dict>
<key>EnvironmentVariables</key>
<dict>
<key>CLAUDE_AUTONOMOUS</key>
<string>1</string>
<key>CLAUDE_PROJECT_DIR</key>
<string>${PROJECT_DIR}</string>
<key>PATH</key>
<string>/usr/local/bin:/usr/bin:/bin:$HOME/.local/bin</string>
</dict>
<key>StandardOutPath</key>
<string>${LOG_FILE}</string>
<key>StandardErrorPath</key>
<string>${LOG_FILE}</string>
</dict>
</plist>
PLIST
launchctl load "$PLIST_PATH" 2>/dev/null || true
log " Scheduled retry at ~${RETRY_TIME}"
log " Plist: $PLIST_PATH (auto-removes after running)"
exit 0
fi
section "RATE LIMITED — WAITING"
log " Attempt $rate_limit_retries/$MAX_RATE_LIMIT_RETRIES"
log " Sleeping ${RATE_LIMIT_WAIT}s until $(date -v+${RATE_LIMIT_WAIT}S '+%H:%M:%S' 2>/dev/null || date -d "+${RATE_LIMIT_WAIT} seconds" '+%H:%M:%S')..."
sleep "$RATE_LIMIT_WAIT"
# Check if plan still has tasks before retrying
if ! plan_has_tasks; then
banner "ALL TASKS COMPLETE (during rate limit wait)"
break
fi
log " Retrying..."
continue # Retry same iteration
fi
# Reset rate limit counter on successful run
rate_limit_retries=0
section "ITERATION $i COMPLETE"
log " Tasks remaining: $(remaining_tasks)"
log " Next task: $(next_task)"
i=$((i + 1))
if [ "$i" -le "$ITERATION_COUNT" ] && [ "$ITERATION_DELAY" -gt 0 ]; then
log " Pausing ${ITERATION_DELAY}s before next iteration..."
sleep "$ITERATION_DELAY"
fi
done
banner "LOOP FINISHED"
log " Completed $((i - 1)) iterations"
log " Tasks remaining: $(remaining_tasks)"
log ""
+59
View File
@@ -0,0 +1,59 @@
# Overnight Plan — 2026-03-04
## Phase 1: Critical Fixes
- [x] P1-1: Brighten SVG fallbacks — increase background lightness from 18% to 28% across all 8 generators in `useImageFallback.ts` (generateSongCoverFallback, generatePodcastCoverFallback, generateNewsFallback, generateImageFallback, generatePosterFallback, generateTVSeriesFallback, generateBookCoverFallback, generatePlaceFallback). Proportionally increase all inner element lightness by +10%. TEST: run `pnpm typecheck` and visually confirm SVGs generate valid data URIs.
- [x] P1-2: Add `.catch(() => {})` to all cover fetch promise chains in grid components — SongGrid.vue (line 157), FilmGrid.vue (line 143), TVSeriesGrid.vue (line 160), BookGrid.vue (line 141), PodcastGrid.vue (line 130). Prevents unhandled rejection if fetch throws unexpectedly. TEST: `pnpm typecheck && pnpm lint`.
- [x] P1-3: Refine mobile keyboard handling — In `useVisualViewport.ts`, add debounce to viewport change handler (50ms) to prevent jittery resizing. In `App.vue`, ensure the `rootStyle` computed applies `overflow: hidden` when keyboard is open. TEST: `pnpm typecheck`.
- [x] P1-4: Verify service worker cleanup — Confirm `dev-dist/sw.js` contains the self-destructing SW and `vite.config.ts` has `devOptions.enabled: false`. If not, fix. TEST: read both files and verify.
## Phase 2: Error Handling Hardening
- [x] P2-1: Wrap JSON.parse calls in try/catch — `useContentDiscovery.ts` sessionStorage parse, all sessionStorage/localStorage reads in composables. Search for `JSON.parse` across all `.ts` and `.vue` files, wrap any unprotected calls. TEST: `pnpm typecheck && pnpm lint`.
- [x] P2-2: Add `.ok` checks before `.json()` on fetch calls — `useBitcoinPrice.ts` (Mempool API), `MempoolTxCard.vue` (tip height), `useNip05Verification.ts` (NIP-05 lookup), `ZapDialog.vue` (Lightning address). Search for `fetch(``.json()` patterns without `.ok` check. TEST: `pnpm typecheck && pnpm lint`.
- [x] P2-3: Harden SSE streaming — In `useAI.ts` `readSSE()`, wrap `reader.read()` in try/catch, close reader on error. In `openrouter-adapter.ts`, add same pattern. TEST: `pnpm typecheck`.
- [x] P2-4: Add error handling to async watchers — `PdfViewer.vue` watch calling `renderPage()`, `VideoPlayer.vue` `initHls()` in onMounted. Wrap in try/catch with user-friendly error state. TEST: `pnpm typecheck`.
## Phase 3: Security Hardening
- [x] P3-1: postMessage origin validation — In `archyBridge.ts`, replace `'*'` targetOrigin with configurable origin. Add origin check on incoming message handler. TEST: `pnpm typecheck`.
- [x] P3-2: URL validation — In `contentExtraction.ts`, add URL length limit (2048 chars) to `extractUrlFromText()`. Validate URLs before fetch. TEST: `pnpm typecheck && pnpm lint`.
- [x] P3-3: Content sanitization — Review `html.ts` for innerHTML usage, ensure SVG injection is covered. Replace `innerHTML = ''` with `textContent = ''` in `usePlayer.ts`. TEST: `pnpm typecheck`.
- [x] P3-4: Add CSP meta tag — Add `<meta http-equiv="Content-Security-Policy" ...>` to `index.html` with appropriate directives for the app (allow self, API hosts, image CDNs). TEST: `pnpm typecheck`.
## Phase 4: Test Suite
- [x] P4-1: Unit tests for usePlayer — Create `packages/app/src/composables/__tests__/usePlayer.test.ts`. Test playback state, queue management, play/pause/next/prev. Minimum 8 test cases. TEST: `pnpm test`.
- [x] P4-2: Unit tests for useContentPanel — Create `packages/app/src/composables/__tests__/useContentPanel.test.ts`. Test tab switching, detail opening, panel state management. Minimum 6 test cases. TEST: `pnpm test`.
- [x] P4-3: Unit tests for useVisualViewport — Create `packages/app/src/composables/__tests__/useVisualViewport.test.ts`. Mock visualViewport API, test keyboard detection, viewport height calculation. Minimum 5 test cases. TEST: `pnpm test`.
- [x] P4-4: Content extraction edge case tests — Create `packages/app/src/composables/__tests__/contentExtraction.test.ts`. Test interleaved tags, malformed tags, unicode content, missing fields. Minimum 10 test cases. TEST: `pnpm test`.
- [x] P4-5: Seeded prompt regression tests — Create `packages/app/src/__tests__/seed-conversations.test.ts`. Import all seed conversations from mocks, run content extraction on each, verify expected content types are produced. Minimum 1 test per seed. TEST: `pnpm test`.
## Phase 5: Feature Work
- [x] P5-1: File browser page — Create `packages/app/src/pages/BrowsePage.vue` with file tree navigation. Add route `/browse` to router. Use the existing `vite-fs.ts` plugin for file reading. Show files/folders with icons, breadcrumb nav. TEST: `pnpm typecheck && pnpm lint`.
- [x] P5-2: File tree component — Create `packages/app/src/components/browse/FileTree.vue`. Recursive tree with expand/collapse, file type icons (folder, code, image, document). Use glass morphism styling. TEST: `pnpm typecheck`.
- [x] P5-3: File preview component — Create `packages/app/src/components/browse/FilePreview.vue`. Preview text files with syntax highlighting (reuse code viewer), images inline, show file metadata. TEST: `pnpm typecheck`.
- [x] P5-4: Allow .claude folder in code viewer — Update `vite-fs.ts` to allow `.claude/` path. Update any path validation that blocks dotfiles. Show CLAUDE.md, settings, hooks, memory files. TEST: `pnpm typecheck`.
- [x] P5-5: Archy local search guide — Create `packages/app/src/docs/archy-local-search.md` documenting how file types map to content surfaces (images→ImageGrid, music→SongGrid, etc.), how ContextBroker filtering works. Also add a help section component that can display this in-app. TEST: file exists and is valid markdown.
## Phase 6: Accessibility
- [x] P6-1: Add aria-labels to icon buttons — Audit all icon-only buttons across chat components (ChatHeader.vue, ChatMessage.vue, ChatInput.vue, ChatSearch.vue). Add descriptive `aria-label` to each. TEST: `pnpm lint`.
- [x] P6-2: Add aria-labels to content grids — All Grid components (SongGrid, FilmGrid, TVSeriesGrid, PlaceGrid, BookGrid, PodcastGrid, NewsGrid, ImageGrid). Each card button needs `aria-label` with content title. TEST: `pnpm lint`.
- [x] P6-3: Focus management for dialogs — `ZapDialog.vue`: add focus trap, auto-focus close button, `aria-modal="true"`, `role="dialog"`. Same for `SettingsModal.vue`. Ensure Escape key closes. TEST: `pnpm typecheck`.
- [x] P6-4: Color contrast audit — Check `text-white/40` against dark backgrounds for WCAG AA (4.5:1). Verify `#F7931A` accent contrast. Fix any failing ratios by increasing opacity. Document findings in comments. TEST: `pnpm lint`.
- [x] P6-5: Alt text improvements — `ImageGrid.vue`: use `img.title || img.alt` instead of generic. All content grids: ensure img alt includes meaningful content (title + artist/director/author). TEST: `pnpm lint`.
## Phase 7: Performance & Compatibility
- [x] P7-1: Lazy load heavy renderers — Use `defineAsyncComponent` for PdfViewer, VideoPlayer, MapView. Add loading skeleton components for each. TEST: `pnpm typecheck`.
- [x] P7-2: Add in-memory caching — `useNip05Verification.ts`: cache results with 5-min TTL. `useBitcoinPrice.ts`: cache price with 30s TTL. TEST: `pnpm typecheck`.
- [x] P7-3: Error boundaries for grid items — Create `packages/app/src/components/ui/ErrorBoundary.vue` using `onErrorCaptured`. Wrap each grid item renderer to prevent cascade failures. Show fallback UI on component crash. TEST: `pnpm typecheck`.
- [x] P7-4: Code file size limits — In `useCodeContext.ts` `openFile()`, add file size check before reading (reject > 1MB). Add loading indicator for large files. TEST: `pnpm typecheck`.
## Phase 8: Research & Documentation
- [x] P8-1: iOS app research — Research Capacitor vs WKWebView wrapper vs React Native WebView for shipping AIUI as iOS app. Document in `docs/research/ios-app.md`: pros/cons, App Store requirements, push notification integration, offline capability. Include concrete next steps.
- [x] P8-2: Mac desktop app research — Research Tauri v2 vs Electron for Mac desktop app. Document in `docs/research/mac-desktop.md`: binary size, memory usage, menu bar app pattern (like Raycast), global hotkey/command invocation, tray API. Include concrete next steps.
- [x] P8-3: Plugin system hardening research — Document in `docs/research/plugin-security.md`: signature validation for community plugins, sandboxed iframe execution, permission system per plugin. Reference existing plugin interfaces in `packages/core/src/plugins/`.
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env sh
# Pre-run script: verify repo state and create overnight branch.
# Run before 5-6pm to ensure you can safely start the overnight loop.
set -eu
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
cd "$PROJECT_DIR"
DATE=$(date '+%Y-%m-%d')
BRANCH="overnight/${DATE}"
echo "=== Overnight pre-run check @ $(date '+%Y-%m-%dT%H:%M:%S') ==="
# 1. Check git status is clean
if ! git diff --quiet || ! git diff --cached --quiet; then
echo "Error: Working tree not clean. Commit or stash changes first." >&2
echo " git status" >&2
git status --short >&2
exit 1
fi
# 2. Check we're not already on an overnight branch
current=$(git branch --show-current 2>/dev/null || true)
if [ -n "$current" ] && [ "$current" = "$BRANCH" ]; then
echo "Already on $BRANCH. Ready to run." >&2
exit 0
fi
# 3. Create date-stamped branch
if git rev-parse --verify "$BRANCH" >/dev/null 2>&1; then
echo "Branch $BRANCH already exists. Checkout or use a different date." >&2
exit 1
fi
git checkout -b "$BRANCH"
echo "Created branch $BRANCH"
# 4. Remind to push
echo ""
echo "Reminder: Push before starting overnight run: git push -u origin $BRANCH"
echo "Then run: caffeinate -i ./loop/loop.sh"
echo "=== Ready ==="
+73
View File
@@ -0,0 +1,73 @@
You are working through an overnight automation plan for the AIUI app. Read these files first:
1. `loop/plan.md` — Your task checklist (mark items `- [x]` as you complete them)
2. `CLAUDE.md` — Project conventions, design system rules, and coding standards
## Project Context
AIUI is an AI content surface UI — a Vue 3 + TypeScript + Tailwind CSS app with chat, content panels (films, music, books, TV, places, news, images, podcasts), and a plugin system. It runs as a PWA and inside Archy (an iframe host).
Key directories:
- `packages/app/src/` — Main application source
- `packages/app/src/composables/` — Shared composition functions
- `packages/app/src/components/content/` — Content grid and detail components
- `packages/app/src/components/chat/` — Chat interface components
- `packages/app/src/styles/main.css` — Glass morphism design system
- `packages/core/src/` — Core library and types
## Working Process
For each task in `loop/plan.md`:
1. Find the first unchecked `- [ ]` item
2. Read the task description carefully — it tells you what to change and where
3. Read the relevant source files before making changes
4. Make the change following CLAUDE.md conventions
5. Run the TEST command specified in the task
6. Fix any errors from the test command before proceeding
7. Commit with conventional commit format: `type(scope): description`
8. Mark the task done: change `- [ ]` to `- [x]` in `loop/plan.md`
9. Move to the next unchecked task immediately
## Testing Gates
Every task specifies a TEST command. You MUST run it and pass before committing:
- `pnpm typecheck` — TypeScript strict mode compilation
- `pnpm lint` — ESLint checks
- `pnpm test` — Vitest unit tests
- Multiple commands joined with `&&` must ALL pass
If a test fails, fix the issue and re-run. Do not skip tests. Do not mark a task as done if tests fail.
## Coding Rules
- **Vue 3 Composition API only** — `<script setup lang="ts">`, never Options API
- **Glass morphism design** — use `.glass`, `.glass-card`, `.glass-button` from `main.css`
- **Dark theme** — `bg-white/5`, `text-white/80`, never `bg-gray-*` or plain `bg-white`
- **Text opacity scale** — `text-white/25``/40``/60``/70``/80``/90``/96`
- **Accent** — `text-accent` (`#F7931A`)
- **Touch targets** — minimum 44x44px for all interactive elements
- **Font minimums** — never smaller than 11px
- **No over-engineering** — only change what the task asks for
- **Keep existing patterns** — match the style of surrounding code
## Commit Format
```
type(scope): description
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
```
Types: `feat`, `fix`, `refactor`, `test`, `docs`, `chore`, `perf`
Scope: `app`, `core`, or specific area like `chat`, `content`, `player`
## Rules
- Never skip a testing gate — if tests fail, fix them before moving on
- If a task is proving difficult, make at least 10 genuine attempts before moving on
- Always read source files before editing them
- Do not stop until all tasks are checked or you are rate limited
- Commit after each completed task
- For research tasks (Phase 8), create the docs directory if needed: `mkdir -p docs/research`
- For test tasks (Phase 4), create the test directory if needed: `mkdir -p packages/app/src/composables/__tests__`