Add 'aiui/' from commit 'e30ac1d1069532fb6d652d87e2d4a2fe9d1b4773'
git-subtree-dir: aiui git-subtree-mainline:0c4826f8ccgit-subtree-split:e30ac1d106
This commit is contained in:
Executable
+71
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env bash
|
||||
# PreToolUse Bash guard: block dangerous shell commands.
|
||||
# Denies: rm -rf, git reset --hard, git push -f, git clean -fd, chmod -R 777,
|
||||
# fork bombs, block device overwrites, mkfs, paths escaping project root.
|
||||
# Uses python3 instead of jq for JSON (guaranteed on macOS).
|
||||
set -euo pipefail
|
||||
|
||||
INPUT=$(cat)
|
||||
CMD=$(python3 -c "
|
||||
import json, sys
|
||||
try:
|
||||
data = json.loads(sys.stdin.read())
|
||||
print(data.get('tool_input', {}).get('command', ''))
|
||||
except: pass
|
||||
" <<< "$INPUT")
|
||||
BASE="${CLAUDE_PROJECT_DIR:-}"
|
||||
[[ -z "$BASE" ]] && BASE=$(python3 -c "
|
||||
import json, sys
|
||||
try:
|
||||
data = json.loads(sys.stdin.read())
|
||||
print(data.get('cwd', ''))
|
||||
except: pass
|
||||
" <<< "$INPUT")
|
||||
[[ -z "$BASE" ]] && BASE="$(pwd)"
|
||||
|
||||
# Normalize: collapse whitespace, strip leading/trailing
|
||||
CMD_NORM=$(echo "$CMD" | tr -s '[:space:]' ' ' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||||
|
||||
deny() {
|
||||
local reason="$1"
|
||||
python3 -c "
|
||||
import json
|
||||
print(json.dumps({
|
||||
'hookSpecificOutput': {
|
||||
'hookEventName': 'PreToolUse',
|
||||
'permissionDecision': 'deny',
|
||||
'permissionDecisionReason': '$reason'
|
||||
}
|
||||
}))
|
||||
"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Dangerous patterns (case-insensitive where sensible)
|
||||
case "$CMD_NORM" in
|
||||
*"rm -rf"*|*"rm -fr"*|*"rm -f -r"*|*"rm -r -f"*) deny "Destructive rm -rf blocked by security hook" ;;
|
||||
*"git reset --hard"*) deny "git reset --hard would lose uncommitted work" ;;
|
||||
*"git push --force"*|*"git push -f"*|*"git push -f "*) deny "git push --force would rewrite history" ;;
|
||||
*"git clean -fd"*|*"git clean -f -d"*) deny "git clean -fd deletes untracked files" ;;
|
||||
*"chmod -R 777"*|*"chmod -R 0777"*) deny "chmod -R 777 is a security risk" ;;
|
||||
*":(){ :"*"};:"*) deny "Fork bomb pattern blocked" ;;
|
||||
*"> /dev/sd"*|*">/dev/sd"*) deny "Block device overwrite blocked" ;;
|
||||
*"mkfs "*|*"mkfs."*) deny "Disk format command blocked" ;;
|
||||
esac
|
||||
|
||||
# Check for path traversal escaping project root (../ outside project)
|
||||
# Only if we have a sensible base
|
||||
if [[ -n "$BASE" ]] && [[ -d "$BASE" ]]; then
|
||||
# Simple heuristic: command contains .. and would resolve outside project
|
||||
if echo "$CMD_NORM" | grep -qE '\.\./|/\.\.'; then
|
||||
# Extract plausible paths and check - allow ../ within project
|
||||
if echo "$CMD_NORM" | grep -qE '(rm|mv|cp|cat|chmod|chown)\s+.*\.\.'; then
|
||||
# Could be risky; be conservative for rm/mv/cp
|
||||
if echo "$CMD_NORM" | grep -qE '\brm\b.*\.\.'; then
|
||||
deny "Path traversal with rm blocked"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Executable
+75
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env bash
|
||||
# PostToolUse Bash hook: detect git push/commit and prompt Claude to update PROGRESS.md.
|
||||
# Returns structured feedback with recent commits so Claude can write a session log entry.
|
||||
# Uses python3 instead of jq for JSON (guaranteed on macOS).
|
||||
set -euo pipefail
|
||||
|
||||
INPUT=$(cat)
|
||||
|
||||
# Extract command from JSON using python3
|
||||
CMD=$(python3 -c "
|
||||
import json, sys
|
||||
try:
|
||||
data = json.loads(sys.stdin.read())
|
||||
print(data.get('tool_input', {}).get('command', ''))
|
||||
except: pass
|
||||
" <<< "$INPUT")
|
||||
|
||||
# Only trigger on git push or git commit commands
|
||||
if ! echo "$CMD" | grep -qE '\bgit\s+(push|commit)\b'; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Gather context for the progress update
|
||||
BASE="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
||||
BRANCH=$(git -C "$BASE" branch --show-current 2>/dev/null || echo "unknown")
|
||||
PROGRESS_FILE="$BASE/PROGRESS.md"
|
||||
TIMESTAMP=$(date '+%Y-%m-%d %H:%M')
|
||||
|
||||
# Get recent commits (branch vs main, or last 10)
|
||||
if git -C "$BASE" rev-parse --verify main &>/dev/null; then
|
||||
COMMITS=$(git -C "$BASE" log --oneline main..HEAD 2>/dev/null | head -15)
|
||||
if [ -z "$COMMITS" ]; then
|
||||
COMMITS=$(git -C "$BASE" log --oneline -10 2>/dev/null)
|
||||
fi
|
||||
else
|
||||
COMMITS=$(git -C "$BASE" log --oneline -10 2>/dev/null)
|
||||
fi
|
||||
|
||||
# Get changed files in recent commits
|
||||
CHANGED_FILES=$(git -C "$BASE" diff --name-only main..HEAD 2>/dev/null | head -20 || \
|
||||
git -C "$BASE" diff --name-only HEAD~5..HEAD 2>/dev/null | head -20 || \
|
||||
echo "unknown")
|
||||
|
||||
# Build the feedback message and output as JSON using python3
|
||||
python3 -c "
|
||||
import json, sys
|
||||
|
||||
message = '''Progress Update Needed
|
||||
|
||||
A git push/commit was detected on branch \`$BRANCH\` at $TIMESTAMP.
|
||||
|
||||
Recent commits:
|
||||
\`\`\`
|
||||
$COMMITS
|
||||
\`\`\`
|
||||
|
||||
Changed files:
|
||||
\`\`\`
|
||||
$CHANGED_FILES
|
||||
\`\`\`
|
||||
|
||||
Please update PROGRESS.md:
|
||||
1. Add a session log entry under '## Session Log' with format: ### $TIMESTAMP — $BRANCH
|
||||
2. Summarize what was accomplished (2-4 bullet points based on the commits above)
|
||||
3. Update any roadmap checkboxes if tasks were completed
|
||||
4. Commit the PROGRESS.md update'''
|
||||
|
||||
output = {
|
||||
'hookSpecificOutput': {
|
||||
'hookEventName': 'PostToolUse',
|
||||
'progressUpdate': message
|
||||
}
|
||||
}
|
||||
print(json.dumps(output))
|
||||
"
|
||||
Executable
+85
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env bash
|
||||
# PreToolUse Edit|Write guard: block edits outside project and to protected paths.
|
||||
# Denies: paths outside project, .git/, .env*, lockfiles, node_modules/
|
||||
# Uses python3 instead of jq for JSON (guaranteed on macOS).
|
||||
set -euo pipefail
|
||||
|
||||
INPUT=$(cat)
|
||||
FILE_PATH=$(python3 -c "
|
||||
import json, sys
|
||||
try:
|
||||
data = json.loads(sys.stdin.read())
|
||||
print(data.get('tool_input', {}).get('file_path', ''))
|
||||
except: pass
|
||||
" <<< "$INPUT")
|
||||
BASE="${CLAUDE_PROJECT_DIR:-}"
|
||||
[[ -z "$BASE" ]] && BASE=$(python3 -c "
|
||||
import json, sys
|
||||
try:
|
||||
data = json.loads(sys.stdin.read())
|
||||
print(data.get('cwd', ''))
|
||||
except: pass
|
||||
" <<< "$INPUT")
|
||||
[[ -z "$BASE" ]] && BASE="$(pwd)"
|
||||
|
||||
# Resolve to absolute path
|
||||
if [[ -z "$FILE_PATH" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
ABS_BASE=$(cd "$BASE" 2>/dev/null && pwd) || true
|
||||
[[ -z "$ABS_BASE" ]] && ABS_BASE=$(python3 -c "import os,sys; print(os.path.abspath(os.path.normpath(sys.argv[1])))" "$BASE" 2>/dev/null) || true
|
||||
[[ -z "$ABS_BASE" ]] && ABS_BASE="$BASE"
|
||||
# Ensure base has trailing slash for prefix check
|
||||
[[ "$ABS_BASE" != */ ]] && ABS_BASE="${ABS_BASE}/"
|
||||
if [[ "$FILE_PATH" != /* ]]; then
|
||||
ABS_PATH="$ABS_BASE${FILE_PATH#./}"
|
||||
else
|
||||
ABS_PATH="$FILE_PATH"
|
||||
fi
|
||||
# Normalize path (collapse .. and ., no symlink resolution needed)
|
||||
ABS_PATH=$(python3 -c "import os,sys; print(os.path.abspath(os.path.normpath(sys.argv[1])))" "$ABS_PATH" 2>/dev/null) || true
|
||||
[[ -z "$ABS_PATH" ]] && ABS_PATH="$ABS_BASE${FILE_PATH#./}"
|
||||
|
||||
deny() {
|
||||
local reason="$1"
|
||||
echo "Blocked: $ABS_PATH — $reason" >&2
|
||||
python3 -c "
|
||||
import json
|
||||
print(json.dumps({
|
||||
'hookSpecificOutput': {
|
||||
'hookEventName': 'PreToolUse',
|
||||
'permissionDecision': 'deny',
|
||||
'permissionDecisionReason': '$reason'
|
||||
}
|
||||
}))
|
||||
"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Protected patterns (path contains or equals)
|
||||
PROTECTED_PATTERNS=(
|
||||
".git/"
|
||||
".env"
|
||||
".env.local"
|
||||
"node_modules/"
|
||||
"package-lock.json"
|
||||
"pnpm-lock.yaml"
|
||||
)
|
||||
|
||||
for pattern in "${PROTECTED_PATTERNS[@]}"; do
|
||||
if [[ "$ABS_PATH" == *"$pattern"* ]] || [[ "$ABS_PATH" == *"/$pattern" ]]; then
|
||||
deny "Edit blocked: path matches protected pattern ($pattern)"
|
||||
fi
|
||||
done
|
||||
|
||||
# .env.*.local
|
||||
if [[ "$ABS_PATH" =~ \.env\..*\.local$ ]]; then
|
||||
deny "Edit blocked: .env.*.local files contain secrets"
|
||||
fi
|
||||
|
||||
# Ensure path is under project root (ABS_BASE has trailing /)
|
||||
if [[ "$ABS_PATH" != "$ABS_BASE"* ]] && [[ "$ABS_PATH" != "$BASE"* ]]; then
|
||||
deny "Edit blocked: path is outside project directory"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"version": "0.0.1",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "app",
|
||||
"runtimeExecutable": "bash",
|
||||
"runtimeArgs": ["packages/app/scripts/dev.sh"],
|
||||
"port": 5173,
|
||||
"autoPort": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
# AIUI Project Memory
|
||||
|
||||
## Session Startup
|
||||
1. Run `preview_start` with name `"app"` immediately — runs both Vite (:5173) + Claude proxy (:3141) via `packages/app/scripts/dev.sh`
|
||||
2. Always commit work before ending a session
|
||||
3. Work on `development` branch, merge to `main` only when production ready
|
||||
|
||||
## User Preferences
|
||||
- NO worktrees, NO temporary branches — just `development` and `main`
|
||||
- Always use combined dev script (proxy + frontend), never bare `vite`
|
||||
- Commit frequently to avoid losing work
|
||||
|
||||
## Current State (2026-03-04)
|
||||
- Branch: `overnight/2026-03-03`, all committed and pushed to remote (git.tx1138.com)
|
||||
- Typecheck passes clean
|
||||
|
||||
## What's Been Built
|
||||
- Chat: AI streaming with stop generation, web search, article integration, paste & extract
|
||||
- Content panel tabs: Films, Music, Magazine, News, Books, TV Series, Images, Places, Code, Design System, Nostr, **Apps**
|
||||
- Detail views for each content type (side-by-side desktop, overlay mobile)
|
||||
- **Apps tab**: curated DB of ~30 Nostr/Bitcoin apps, AppsGrid + AppDetail with search/category filtering/how-to
|
||||
- Design system viewer (grid + detail) for tokens, colors, typography, components
|
||||
- Nostr feed scaffold with note/article/zap filtering
|
||||
- Content extraction: contentExtraction.ts + contentFiltering.ts (overhauled classifiers)
|
||||
- **Bare domain extraction** from AI text (e.g. "check out damus.io")
|
||||
- Banner fallback composable (primary → API → gradient)
|
||||
- Image fallbacks: Wikipedia + Google Books sources
|
||||
- Loading skeletons per content type variant
|
||||
- Project grid with breadcrumb nav and inline creation
|
||||
- Filesystem Vite plugin for local project browsing
|
||||
- PWA with star icon, TMDB proxy, Jamendo for music
|
||||
- **Slash command palette**: /code, /nostr, /design, /search show in palette with auto-send
|
||||
- **Chat action buttons**: wrapped in glass container (backdrop blur, border, shadow)
|
||||
- **Settings modal**: Memory + Advanced Settings via gear icon
|
||||
- **Chat history**: dedicated clock icon button
|
||||
- **Web search**: Brave API primary, SearXNG rotation fallback, DuckDuckGo fallback
|
||||
- iOS HIG mobile UX rules in `.cursor/rules/15-mobile-ux.mdc`
|
||||
|
||||
## Key Files
|
||||
- Dev script: `packages/app/scripts/dev.sh`
|
||||
- Launch config: `.claude/launch.json` (name: "app")
|
||||
- Main page: `packages/app/src/pages/ChatPage.vue`
|
||||
- Content panel: `packages/app/src/components/content/ContentPanel.vue`
|
||||
- Content grids: `packages/app/src/components/content/*Grid.vue`
|
||||
- Detail views: `packages/app/src/components/content/*Detail.vue`
|
||||
- **Apps**: `packages/app/src/data/apps.ts` (curated DB), `AppsGrid.vue`, `AppDetail.vue`
|
||||
- AI composable: `packages/app/src/composables/useAI.ts`
|
||||
- Content extraction: `packages/app/src/composables/contentExtraction.ts`
|
||||
- Content filtering: `packages/app/src/composables/contentFiltering.ts`
|
||||
- Content panel logic: `packages/app/src/composables/useContentPanel.ts`
|
||||
- Image fallbacks: `packages/app/src/composables/useImageFallback.ts`
|
||||
- Banner fallback: `packages/app/src/composables/useBannerFallback.ts`
|
||||
- Chat input: `packages/app/src/components/chat/ChatInput.vue`
|
||||
- Prompt palette: `packages/app/src/components/chat/PromptPalette.vue`
|
||||
- Chat message: `packages/app/src/components/chat/ChatMessage.vue`
|
||||
- Settings modal: `packages/app/src/components/chat/SettingsModal.vue`
|
||||
- Web search plugin: `packages/app/vite-web-search.ts`
|
||||
- Prompt templates store: `packages/app/src/stores/promptTemplates.ts`
|
||||
|
||||
## Recent Session Work (2026-03-04)
|
||||
See `session-2026-03-04.md` for details.
|
||||
@@ -0,0 +1,18 @@
|
||||
# Code Mode UI — Future Work
|
||||
|
||||
## After content surfacing is complete, implement:
|
||||
|
||||
### 1. Code Mode Visual Treatment
|
||||
- Colour the message container in orange (`#F7931A`) styling when in code mode
|
||||
- Change header text from "Message AIUI" to "Code"
|
||||
- Visual signal so user knows they're in coding context
|
||||
|
||||
### 2. Design System Context Selection
|
||||
- All design system items should be selectable with a cursor/pointer icon on hover
|
||||
- Selecting a design system item provides that UI context to the code generation
|
||||
- Think of it as "code with this component/token in mind"
|
||||
|
||||
### 3. File Browser / Open File Context
|
||||
- File browser or open file in the content panel
|
||||
- Selected files provide context for coding
|
||||
- Pairs with the design system selection — user picks UI + files as coding context
|
||||
@@ -0,0 +1,66 @@
|
||||
# Session 2026-03-04
|
||||
|
||||
## Completed This Session
|
||||
|
||||
### 1. Chat UX Changes
|
||||
- **History button**: Changed from title-click dropdown to dedicated clock icon in ChatHeader
|
||||
- **Settings modal**: Created `SettingsModal.vue` — Memory + Advanced Settings behind gear icon, glass-card with backdrop blur
|
||||
- **PromptIndex fix**: Reverted to original behavior (current conversation only), fixed broken v-if/v-else chain where StreamingDots broke the template chain
|
||||
- **Chat action buttons**: Wrapped hover icons in proper glass container (`bg-black/60 backdrop-blur-md border border-white/10`) with divider between actions and thumbs
|
||||
|
||||
### 2. iOS HIG Integration
|
||||
- Created `.cursor/rules/15-mobile-ux.mdc` with comprehensive iOS HIG values
|
||||
- Updated CLAUDE.md Mobile UX section
|
||||
|
||||
### 3. Web Search Fix
|
||||
- All SearXNG instances were returning 429, DuckDuckGo rate-limiting
|
||||
- Added Brave Search API as primary backend (`BRAVE_SEARCH_API_KEY` env var)
|
||||
- Expanded SearXNG pool to 8 instances with rotation
|
||||
- Added HTML response guard for captcha pages
|
||||
|
||||
### 4. Content Detection Overhaul (MAJOR)
|
||||
- **Expanded all classifiers** in `contentFiltering.ts`: isNewsQuery, isMusicQuery, isBookQuery, isTVQuery, isPlaceQuery, isWebsitesQuery + response variants
|
||||
- **Added Nostr detection**: `isNostrQuery()`, `isNostrLikeResponse()`
|
||||
- **Added App detection**: `isAppQuery()`, `isAppLikeResponse()`
|
||||
- **Updated `filterTabsByContext()`**: new `hasNostr` + `hasApps` params, nostr/app query priority
|
||||
- **Updated `preferredFirstTab()`**: nostr + app checks
|
||||
|
||||
### 5. Bare Domain Extraction
|
||||
- `extractBareDomainLinks(text)` in contentExtraction.ts
|
||||
- Detects plain domains like "damus.io" not inside markdown/bold/URL patterns
|
||||
- Known TLDs whitelist, file extension blacklist
|
||||
|
||||
### 6. Apps Tab (NEW FEATURE)
|
||||
- **Database**: `packages/app/src/data/apps.ts` — AppEntry interface, ~30 curated apps
|
||||
- Nostr clients: Damus, Primal, Snort, Amethyst, Coracle, Iris, noStrudel
|
||||
- Lightning wallets: Phoenix, Breez, Zeus, Alby, Mutiny, WoS
|
||||
- Bitcoin wallets: Sparrow, BlueWallet, Nunchuk, Coldcard
|
||||
- Privacy: SimpleX Chat, Signal, Mullvad VPN
|
||||
- Node software: Start9, Umbrel, RaspiBlitz, myNode
|
||||
- Dev tools: NDK, nostr-tools, Nak
|
||||
- **Extraction**: `extractApps(text, userQuery)` — keyword matching against DB, surfaces with 1+ match for app/nostr/known-app queries, 2+ for general
|
||||
- **UI**: `AppsGrid.vue` (list with search/category filter), `AppDetail.vue` (gradient header, how-to steps, related apps, external link)
|
||||
- **Wired in**: useContentPanel.ts (panelApps ref, selectedApp, open/close), ContentPanel.vue (registered), PromptIndex badges
|
||||
|
||||
### 7. Slash Command Palette
|
||||
- `/code`, `/nostr`, `/design`, `/search` appear as commands in PromptPalette
|
||||
- Commands section above Templates section with `/slash` prefix styling
|
||||
- Auto-send on select (except `/search` which sets text for query input)
|
||||
- 8px side margins (`left-2 right-2`), no max-height scroll limit
|
||||
- `ChatInput.vue`: simplified `isPaletteMode` — no longer excludes command names
|
||||
|
||||
### 8. App Detection Fix
|
||||
- Queries mentioning known app names (e.g. "start9") now match via `queryMatchesApp` check
|
||||
- Previously required explicit app/nostr query patterns like "what app" or "best wallet"
|
||||
|
||||
## Known Issues / TODO for Next Session
|
||||
- User reported "start9" search shows Brief but Apps tab was empty — FIXED in last commit
|
||||
- The `/design` command was added to palette and ChatWindow handleSend
|
||||
- Consider adding more apps to the curated database over time
|
||||
- The plan file is at `.claude/plans/content-detection-overhaul.md` (all steps complete)
|
||||
|
||||
## Git State
|
||||
- Branch: `overnight/2026-03-03`
|
||||
- Latest commit: `f346992` — feat(chat): slash command palette, action button containers, app detection fix
|
||||
- Previous commit: `84ccdc7` — feat(app): content detection overhaul, apps tab, chat UX, web search
|
||||
- All pushed to origin
|
||||
@@ -0,0 +1,160 @@
|
||||
# Plan: Overhaul Content Detection + Add Apps Tab
|
||||
|
||||
## Context
|
||||
|
||||
The content surfacing system misses many common AI response patterns. Example: AI responds about Nostr (mentioning damus.io, primal.net, snort.social) but the Nostr tab never surfaces. Query/response classifiers use narrow regexes that miss natural language variations. There's no "topic detection" layer, no app detection, and bare domains in AI text aren't extracted as websites.
|
||||
|
||||
**Goals:**
|
||||
1. Fix content detection to handle how AIs actually respond
|
||||
2. Add Nostr tab surfacing (currently only via `/nostr` command)
|
||||
3. Add Apps tab with curated Nostr + Bitcoin ecosystem apps (local DB + AI extraction fallback)
|
||||
4. Extract bare domains from AI text (e.g. "check out damus.io")
|
||||
|
||||
---
|
||||
|
||||
## Part 1: Expand Query & Response Classifiers
|
||||
|
||||
**File:** `packages/app/src/composables/contentFiltering.ts`
|
||||
|
||||
### 1A. Add Nostr classifiers (new functions)
|
||||
|
||||
- `isNostrQuery(q)` — matches: nostr, npub, nip-\d, damus, primal, snort, amethyst, coracle, zap, relay, note1, nevent, nprofile, fiatjaf, nostrich, "decentralized social"
|
||||
- `isNostrLikeResponse(text)` — requires literal "nostr" OR 2+ Nostr-specific signals (npub, nip-, client names, relay+wss, zap+lightning)
|
||||
|
||||
### 1B. Add App classifiers (new functions)
|
||||
|
||||
- `isAppQuery(q)` — matches: app, client, wallet, tool, software, download, install, "what app", "best app for", "recommend.*app"
|
||||
- `isAppLikeResponse(text)` — matches: "you can use", "popular clients include", "I'd recommend", "available on", "download from"
|
||||
|
||||
### 1C. Expand existing classifiers with broader patterns
|
||||
|
||||
| Classifier | Add these patterns |
|
||||
|---|---|
|
||||
| `isNewsQuery` | "what happened today", "any updates on", "trending", "catch me up", "brief me", "current events" |
|
||||
| `isMusicQuery` | "genre", "spotify", "bandcamp", "grammys", "billboard", "mixtape", "discography", "banger", "favorite jam" |
|
||||
| `isBookQuery` | "what should I read", "favorite reads", "reading list", "book club", "memoir", "audiobook", "goodreads", "worth reading" |
|
||||
| `isTVQuery` | "what's good on netflix", "anything to binge", "hbo", "disney+", "apple tv", "amazon prime", "docuseries", "limited series" |
|
||||
| `isPlaceQuery` | "hungry", "food near me", "best brunch spot", "happy hour", "speakeasy", "rooftop bar", "food truck" |
|
||||
| `isWebsitesQuery` | "point me to", "link me", "any good sites", "tools for", "platforms for" |
|
||||
| `isWebsitesLikeResponse` | "here are some resources", "I'd recommend checking", "you can visit", "useful resources" |
|
||||
| `isNewsLikeResponse` | "I can't access the web but", "having trouble reaching", "unable to browse but" |
|
||||
|
||||
### 1D. Update `preferredFirstTab()` — add nostr + app checks
|
||||
|
||||
### 1E. Update `filterTabsByContext()` — add `hasNostr` and `hasApps` params, integrate into tab ordering
|
||||
|
||||
---
|
||||
|
||||
## Part 2: Bare Domain Extraction
|
||||
|
||||
**File:** `packages/app/src/composables/contentExtraction.ts`
|
||||
|
||||
Add `extractBareDomainLinks(text)`:
|
||||
- Detect plain-text domains like "damus.io", "primal.net" not inside markdown links or bold patterns
|
||||
- Skip positions covered by existing extractors (markdown links, bold-domain, full URLs)
|
||||
- Require known TLDs (.com, .org, .io, .net, .social, .app, etc.)
|
||||
- Block file extensions (.js, .ts, .vue, .json, .css)
|
||||
- Use existing `normUrl()` for dedup
|
||||
|
||||
---
|
||||
|
||||
## Part 3: Apps Tab — Curated Database + AI Extraction
|
||||
|
||||
### 3A. Create app database
|
||||
|
||||
**New file:** `packages/app/src/data/apps.ts`
|
||||
|
||||
```ts
|
||||
interface AppEntry {
|
||||
id: string
|
||||
name: string
|
||||
description: string // One-liner
|
||||
longDescription: string // Why use this, how it works
|
||||
category: 'nostr-client' | 'lightning-wallet' | 'bitcoin-wallet' | 'privacy' | 'node' | 'dev-tool' | 'relay'
|
||||
platforms: ('ios' | 'android' | 'web' | 'desktop' | 'cli' | 'nodeos')[]
|
||||
url: string
|
||||
icon?: string
|
||||
keywords: string[] // For matching AI responses
|
||||
howTo?: string[] // Getting started steps
|
||||
relatedApps?: string[] // IDs of related apps
|
||||
}
|
||||
```
|
||||
|
||||
**Initial curated apps (~25-30):**
|
||||
- Nostr clients: Damus, Primal, Snort, Amethyst, Coracle, Iris, Nostrudel, nos.social
|
||||
- Lightning wallets: Phoenix, Mutiny, Breez, Zeus, Alby, Wallet of Satoshi
|
||||
- Bitcoin wallets: Sparrow, Blue Wallet, Nunchuk, Coldcard, Green
|
||||
- Privacy tools: Tor, SimpleX Chat, Signal, Mullvad VPN
|
||||
- Node software: Start9, Umbrel, RaspiBlitz, myNode
|
||||
- Dev tools: NDK, nostr-tools, Nak
|
||||
|
||||
### 3B. Add app extraction
|
||||
|
||||
**File:** `packages/app/src/composables/contentExtraction.ts`
|
||||
|
||||
Add `extractApps(text, userQuery)`:
|
||||
1. Match AI text against known app names/keywords from database
|
||||
2. If app query detected OR 2+ known apps mentioned → return matched apps
|
||||
3. For unknown apps, create basic entries from context (name + URL if bare domain found)
|
||||
|
||||
### 3C. Create UI components
|
||||
|
||||
**New files:**
|
||||
- `packages/app/src/components/content/AppsGrid.vue` — Grid of app cards (icon, name, category badge, one-liner)
|
||||
- `packages/app/src/components/content/AppDetail.vue` — Detail: icon, name, platforms, long description, how-to steps, link, related apps
|
||||
|
||||
Follow existing grid/detail patterns (e.g. `BookGrid.vue`/`BookDetail.vue`).
|
||||
|
||||
### 3D. Register in ContentPanel.vue
|
||||
|
||||
Add rendering for `activeTab === 'app'`, add `'app'` to `ContentTab` type.
|
||||
|
||||
---
|
||||
|
||||
## Part 4: Wire Everything Together
|
||||
|
||||
**File:** `packages/app/src/composables/useContentPanel.ts`
|
||||
|
||||
In `updatePanelFromText()`:
|
||||
- Call `extractBareDomainLinks(text)`, merge with website sources
|
||||
- Call `extractApps(text, userQuery)`
|
||||
- Compute `hasNostr = isNostrQuery(userQuery) || isNostrLikeResponse(text)`
|
||||
- Compute `hasApps = apps.length > 0`
|
||||
- Pass `hasNostr` and `hasApps` to `filterTabsByContext()`
|
||||
- Add `panelApps` ref, title logic for apps/nostr tabs
|
||||
|
||||
Same changes in `getContextualInlineContent()`.
|
||||
|
||||
Broaden magazine detection: add tech/protocol keywords, surface magazine for 3+ sections with no other structured content.
|
||||
|
||||
---
|
||||
|
||||
## Part 5: PromptIndex badges
|
||||
|
||||
**File:** `packages/app/src/components/chat/PromptIndex.vue`
|
||||
|
||||
Add 'Nostr' and 'Apps' badge detection.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Order
|
||||
|
||||
1. `contentFiltering.ts` — classifiers + filterTabsByContext signature
|
||||
2. `contentExtraction.ts` — `extractBareDomainLinks()` + `extractApps()`
|
||||
3. `data/apps.ts` — curated app database
|
||||
4. `useContentPanel.ts` — wire everything
|
||||
5. `AppsGrid.vue` + `AppDetail.vue` — UI components
|
||||
6. `ContentPanel.vue` — register tab + components
|
||||
7. `PromptIndex.vue` — badges
|
||||
8. Typecheck + manual test
|
||||
|
||||
## Verification
|
||||
|
||||
1. `pnpm typecheck` passes
|
||||
2. "tell me about Nostr" → Nostr + magazine tabs surface
|
||||
3. "best Nostr clients?" → Apps tab with Damus, Primal, Snort
|
||||
4. "recommend a bitcoin wallet" → Apps tab with Phoenix, Sparrow
|
||||
5. "what happened with BIP 110?" → Magazine tab (regression)
|
||||
6. "best movies of 2024" → Films tab (regression)
|
||||
7. Bare domains in AI text extracted as websites
|
||||
8. PromptIndex badges show Nostr/Apps
|
||||
@@ -0,0 +1,74 @@
|
||||
# Plan: Code Mode UI — Orange Input, Design System Context, File Browser Context
|
||||
|
||||
## Context
|
||||
The user wants three connected features that enhance the coding experience in AIUI:
|
||||
1. Visual indication when in code mode (orange input container, "Code" label)
|
||||
2. Ability to select design system items as coding context
|
||||
3. Ability to select files from file browser as coding context
|
||||
|
||||
After this, the user wants to circle back and create a flawless version of content extraction/tab surfacing.
|
||||
|
||||
## Changes
|
||||
|
||||
### 1. Orange Code Mode Input Container
|
||||
**Files**: `ChatWindow.vue`, `ChatInput.vue`
|
||||
|
||||
**ChatWindow.vue** (line 106-115):
|
||||
- Pass `activeTab` to ChatInput as a prop: `:active-tab="activeTab"`
|
||||
- Change placeholder logic: `activeTab === 'code' ? 'Code...' : isStreaming ? 'Waiting for response...' : 'Message AIUI...'`
|
||||
|
||||
**ChatInput.vue**:
|
||||
- Add `activeTab` prop (optional string, default `''`)
|
||||
- Conditionally style the container div (line 79-81):
|
||||
- When `activeTab === 'code'`: use `bg-accent/15 border border-accent/25 backdrop-blur-xl` instead of `path-glass-bubble`
|
||||
- Keep the `rounded-2xl px-4 py-3 flex items-end gap-2 transition-all duration-300` classes
|
||||
- Conditionally style the send button orange when in code mode
|
||||
|
||||
### 2. Design System Item Selection for Coding Context
|
||||
**Files**: `useCodeContext.ts`, `DesignSystemGrid.vue`
|
||||
|
||||
**useCodeContext.ts**:
|
||||
- Add `selectedDesignTokens: ref<string[]>([])` to module state (stores item IDs)
|
||||
- Add `toggleDesignToken(id)` — adds/removes from selection array
|
||||
- Add `clearDesignTokens()` — clears selection
|
||||
- Add `isDesignTokenSelected(id)` — checks if item is in selection
|
||||
- Clear on `exitCodeMode()`
|
||||
- Export all new state/actions
|
||||
|
||||
**DesignSystemGrid.vue**:
|
||||
- Import `useCodeContext`
|
||||
- When `codeMode` is true, show a selection indicator (accent ring + checkmark) on items
|
||||
- `selectItem` should call `toggleDesignToken(item.id)` when in code mode (instead of `openDesignSystemItem`)
|
||||
- When NOT in code mode, keep existing behavior (open detail view)
|
||||
- Selected items get `ring-2 ring-accent/50 bg-accent/10` styling
|
||||
|
||||
### 3. File Browser Selection for Coding Context
|
||||
**Files**: `useCodeContext.ts`, `ProjectGrid.vue`
|
||||
|
||||
**useCodeContext.ts**:
|
||||
- Add `selectedFiles: ref<string[]>([])` — paths of files selected for context
|
||||
- Add `toggleFileSelection(path)` — adds/removes from selection
|
||||
- Add `clearFileSelection()` — clears all
|
||||
- Add `isFileSelected(path)` — checks if file in selection
|
||||
- Clear on `exitCodeMode()`
|
||||
- Export new state/actions
|
||||
|
||||
**ProjectGrid.vue**:
|
||||
- Import `useCodeContext`
|
||||
- When `codeMode` is true, file clicks toggle selection instead of (or in addition to) opening
|
||||
- Show visual selection state (accent highlight/checkmark) on selected files in FileTreeNode
|
||||
|
||||
## Files to Modify
|
||||
1. `packages/app/src/components/chat/ChatWindow.vue` — pass activeTab prop
|
||||
2. `packages/app/src/components/chat/ChatInput.vue` — conditional orange styling + "Code" placeholder
|
||||
3. `packages/app/src/composables/useCodeContext.ts` — add design token + file selection state
|
||||
4. `packages/app/src/components/content/DesignSystemGrid.vue` — toggle selection in code mode
|
||||
5. `packages/app/src/components/content/ProjectGrid.vue` — toggle file selection in code mode
|
||||
|
||||
## Verification
|
||||
1. `pnpm typecheck` — no type errors
|
||||
2. `pnpm lint` — no new lint errors
|
||||
3. Manual: `/code` command → input turns orange with "Code..." placeholder
|
||||
4. Manual: In code mode, design system tab → clicking items toggles selection (accent ring)
|
||||
5. Manual: In code mode, file browser → clicking files toggles selection
|
||||
6. Manual: Exiting code mode clears all selections
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-risky-bash.sh"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Edit|Write",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/post-push-progress.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
name: add-content-type
|
||||
description: Scaffold a complete new content type (tag, extraction, grid, detail, prompt)
|
||||
allowed-tools: Bash(*), Read, Edit, Write, Glob, Grep, Agent
|
||||
---
|
||||
|
||||
Add a complete new content type to AIUI. The user will provide the content type name (e.g., "event", "product", "video").
|
||||
|
||||
Follow ALL steps — this is the full pipeline for a content type:
|
||||
|
||||
1. **Tag format**: Add a new `[[{type}_ext:Field1|Field2|...]]` regex to `packages/app/src/composables/contentExtraction.ts` alongside the existing ones (FILM_EXT_RE, SONG_EXT_RE, etc.)
|
||||
|
||||
2. **Type definition**: Add the TypeScript interface to `packages/core/src/types/content.ts` if it doesn't exist
|
||||
|
||||
3. **Extraction function**: Add `extractAll{Type}s(text, userQuery)` to `contentExtraction.ts` following the pattern of `extractAllFilms` or `extractAllBooks`
|
||||
|
||||
4. **Strip tags function**: Add `strip{Type}Tags()` and include it in `stripContentTags()`
|
||||
|
||||
5. **Query classifier**: Add `is{Type}Query()` and optionally `is{Type}LikeResponse()` to `contentFiltering.ts`
|
||||
|
||||
6. **ContentTab type**: Add the new tab name to the `ContentTab` union in `contentFiltering.ts`
|
||||
|
||||
7. **Tab filtering**: Update `filterTabsByContext()` and `preferredFirstTab()` in `contentFiltering.ts`
|
||||
|
||||
8. **Grid component**: Create `packages/app/src/components/content/{Type}Grid.vue` following the glass-morphism pattern of existing grids (BookGrid.vue is a good template)
|
||||
|
||||
9. **Detail component**: Create `packages/app/src/components/content/{Type}Detail.vue` following the pattern of BookDetail.vue
|
||||
|
||||
10. **Wire into ContentPanel.vue**: Add import, grid render block, detail render block, and panel state refs
|
||||
|
||||
11. **Wire into ContentGridView.vue**: Add import, props, and grid render block
|
||||
|
||||
12. **Wire into ChatPage.vue**: Pass the new panel data as props to ContentGridView
|
||||
|
||||
13. **Wire into useContentPanel.ts**: Add panel ref, selected ref, open/close functions, extraction call in `updatePanelFromText()`
|
||||
|
||||
14. **System prompt**: Add tag format instructions to the `SYSTEM_PROMPT` in `useAI.ts`
|
||||
|
||||
15. **Tab label**: Add to `TAB_LABELS` in `ContentPanel.vue`
|
||||
|
||||
16. **Verify**: Run `pnpm typecheck` and fix any errors
|
||||
|
||||
Report what was created and the tag format to use.
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
name: add-tool
|
||||
description: Add a new AI tool (function call) to the Claude proxy for the AI to use
|
||||
allowed-tools: Bash(*), Read, Edit, Write, Glob, Grep
|
||||
---
|
||||
|
||||
Add a new tool that the AI model can call via Claude's tool_use API. The user will describe what the tool should do (e.g., "search local files", "get app status", "browse media").
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Read the proxy**: Read `packages/app/server/claude-proxy.ts` to understand the existing tool_use loop and `SEARCH_WEB_TOOL` definition.
|
||||
|
||||
2. **Define the tool**: Add a new tool definition following the Claude tool_use format:
|
||||
```ts
|
||||
const NEW_TOOL = {
|
||||
name: 'tool_name',
|
||||
description: 'What this tool does...',
|
||||
input_schema: {
|
||||
type: 'object',
|
||||
properties: { ... },
|
||||
required: [...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. **Add handler**: In the tool_use loop (where `search_web` calls are handled), add a handler for the new tool name.
|
||||
|
||||
4. **Implement backend**: If the tool needs a new API endpoint (e.g., `/api/media/scan`), create a Vite plugin or add a route to the proxy.
|
||||
|
||||
5. **Update system prompt**: Add instructions in `useAI.ts` SYSTEM_PROMPT telling the AI when and how to use the new tool.
|
||||
|
||||
6. **Verify**: Run `pnpm typecheck` and test the proxy starts without errors.
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
name: audit-prompts
|
||||
description: Deep audit of AI system prompts — find gaps, test extraction coverage, verify tag formats
|
||||
allowed-tools: Bash(*), Read, Glob, Grep, Agent
|
||||
---
|
||||
|
||||
Perform a comprehensive audit of AIUI's AI prompt system. Do NOT make changes — report findings only.
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Read the full system prompt**: Read `packages/app/src/composables/useAI.ts` and reconstruct the complete system prompt including all dynamic sections (persona, Wavlake, memory, web search, Archy context, code context).
|
||||
|
||||
2. **Catalog all tag formats**: List every `[[type:...]]` and `[[type_ext:...]]` format defined in the prompt. Cross-reference with regexes in `contentExtraction.ts`.
|
||||
|
||||
3. **Check for gaps**: For each content type in `ContentTab` (contentFiltering.ts), verify:
|
||||
- Is there a tag format in the system prompt?
|
||||
- Is there a matching extraction regex?
|
||||
- Is there a query classifier?
|
||||
- Is there a grid + detail component?
|
||||
- Is the tab wired in ContentPanel.vue and ContentGridView.vue?
|
||||
|
||||
4. **Test extraction coverage**: Read the seed prompts in `src/__tests__/fixtures/seedPrompts.ts`. For each seed, verify:
|
||||
- Does the extraction function find the expected number of items?
|
||||
- Are there edge cases that would break extraction?
|
||||
|
||||
5. **Analyze prompt quality**: Check for:
|
||||
- Conflicting instructions
|
||||
- Missing edge case handling (e.g., "what if the AI can't find a match?")
|
||||
- Overly vague instructions
|
||||
- Missing content types that should have tag formats
|
||||
|
||||
6. **Check proxy tools**: Read `server/claude-proxy.ts` and verify tool definitions match what the prompt claims.
|
||||
|
||||
7. **Report**: Create a structured summary with:
|
||||
- Content type coverage matrix (tag/extraction/grid/detail/prompt)
|
||||
- Identified gaps and inconsistencies
|
||||
- Priority recommendations
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: check
|
||||
description: Run all quality checks (typecheck, lint, test) and auto-fix errors
|
||||
allowed-tools: Bash(*), Read, Edit, Glob, Grep, Agent
|
||||
---
|
||||
|
||||
Run all quality checks for the AIUI project and fix any issues found. Execute in order:
|
||||
|
||||
1. **TypeScript**: Run `pnpm typecheck`. If errors found, read the failing files and fix the type errors.
|
||||
2. **Lint**: Run `pnpm lint`. If fixable errors, run `pnpm lint --fix` first, then fix remaining manually.
|
||||
3. **Tests**: Run `pnpm --filter @aiui/app test -- --run`. For each failure:
|
||||
- Read the test file and the source file it tests
|
||||
- Determine if the test is wrong (outdated assertion) or the source has a bug
|
||||
- Fix whichever is incorrect
|
||||
4. Report a summary: pass/fail counts, what was fixed.
|
||||
|
||||
Important: Do NOT change test expectations just to make them pass — understand WHY they fail first.
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
name: deploy
|
||||
description: Build and prepare AIUI for deployment to Archy node
|
||||
allowed-tools: Bash(*), Read, Edit, Glob, Grep
|
||||
---
|
||||
|
||||
Build AIUI for production deployment. Steps:
|
||||
|
||||
1. **Pre-flight checks**:
|
||||
- `pnpm typecheck` — must pass
|
||||
- `pnpm lint` — must pass
|
||||
- `pnpm --filter @aiui/app test -- --run` — report failures but continue
|
||||
|
||||
2. **Build**:
|
||||
- `pnpm build`
|
||||
- Verify `packages/app/dist/` exists and contains `index.html`
|
||||
|
||||
3. **Bundle analysis**:
|
||||
- Report total dist size and gzip estimate
|
||||
- List the 5 largest chunks
|
||||
- Check against 250KB gzipped budget (warn if over)
|
||||
|
||||
4. **Verify nginx config**:
|
||||
- Read `packages/app/server/nginx-archy.conf`
|
||||
- Verify SPA routing (`try_files $uri $uri/ /aiui/index.html`)
|
||||
- Verify proxy paths for Claude API
|
||||
|
||||
5. **Container build** (if Dockerfile exists):
|
||||
- `podman build -t aiui:latest packages/app/`
|
||||
- Report image size
|
||||
|
||||
6. **Report**: Build status, bundle size, any warnings.
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
name: fix-tab
|
||||
description: Diagnose and fix a broken content panel tab (extraction, routing, rendering)
|
||||
allowed-tools: Bash(*), Read, Edit, Glob, Grep, Agent
|
||||
---
|
||||
|
||||
Diagnose and fix a broken content panel tab. The user will specify which tab (e.g., "app", "code", "image", "nostr").
|
||||
|
||||
## Diagnostic pipeline — check each layer:
|
||||
|
||||
1. **System prompt**: Does `useAI.ts` SYSTEM_PROMPT tell the AI to use the tag format for this content type? If not, add instructions.
|
||||
|
||||
2. **Tag regex**: Does `contentExtraction.ts` have a regex for this content type's tags? If not, add one.
|
||||
|
||||
3. **Extraction function**: Does `extractAll{Type}s()` or `extract{Type}s()` exist and work correctly? Test with sample input.
|
||||
|
||||
4. **Query classifier**: Do `is{Type}Query()` and/or `is{Type}LikeResponse()` exist in `contentFiltering.ts`?
|
||||
|
||||
5. **Tab filtering**: Is the tab included in `filterTabsByContext()` and `preferredFirstTab()`? Check the boolean flag is wired.
|
||||
|
||||
6. **useContentPanel.ts**: Is the extraction called in `updatePanelFromText()`? Is the panel ref populated? Is the tab added to `availableTabs`?
|
||||
|
||||
7. **ContentPanel.vue**: Is there a grid render block for `activeTab === '{tab}'`? Are imports present? Is the tab in TAB_LABELS?
|
||||
|
||||
8. **ContentGridView.vue**: Same checks for the wide desktop view.
|
||||
|
||||
9. **ChatPage.vue**: Are the panel data props passed to ContentGridView?
|
||||
|
||||
10. **Grid component**: Does the grid component exist and render correctly?
|
||||
|
||||
11. **Detail component**: Does the detail component exist?
|
||||
|
||||
Fix each broken layer. Run `pnpm typecheck` after all fixes.
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
name: mock-archy
|
||||
description: Enable/configure mock Archy data for standalone dev testing
|
||||
allowed-tools: Bash(*), Read, Edit, Glob, Grep
|
||||
---
|
||||
|
||||
Set up or modify mock Archy data for testing the Archipelago integration without a real Archy host.
|
||||
|
||||
## How it works
|
||||
|
||||
Mock data is in `packages/app/src/mocks/archy.ts`. When enabled, `useArchy.ts` loads this data instead of waiting for the Archy bridge.
|
||||
|
||||
## Enable mock mode
|
||||
|
||||
Two ways:
|
||||
1. Add `VITE_MOCK_ARCHY=true` to `.env.local`
|
||||
2. Add `?mockArchy` to the URL: `http://localhost:5173/?mockArchy`
|
||||
|
||||
## Customization
|
||||
|
||||
The user may ask to:
|
||||
- Add/remove mock apps from the installed list
|
||||
- Change wallet balance or channel count
|
||||
- Add/modify files in the mock file list
|
||||
- Change system info or network status
|
||||
- Test specific scenarios (e.g., "node is syncing", "wallet offline", "no files")
|
||||
|
||||
Edit `packages/app/src/mocks/archy.ts` accordingly.
|
||||
|
||||
## Verify
|
||||
|
||||
After changes, check that `buildArchyContext()` in `useArchy.ts` produces the expected system prompt section by reading the function and tracing the mock data through it.
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
name: new-detail
|
||||
description: Generate a detail view component following AIUI glass-morphism patterns
|
||||
allowed-tools: Read, Write, Edit, Glob, Grep
|
||||
---
|
||||
|
||||
Create a new detail view component at `packages/app/src/components/content/{Name}Detail.vue`.
|
||||
|
||||
## Requirements
|
||||
|
||||
1. **Read a reference**: Read `BookDetail.vue` or `PlaceDetail.vue` as a template.
|
||||
|
||||
2. **Follow conventions**:
|
||||
- `<script setup lang="ts">` with single item prop
|
||||
- Back button at top (emits 'back' event)
|
||||
- Hero image/banner area with gradient overlay and fallback
|
||||
- Title, subtitle, and metadata section
|
||||
- Description/long text body with proper typography
|
||||
- Action buttons (external links, share, etc.) with glass-button styling
|
||||
- Dark/light mode via `useTheme()`
|
||||
- Smooth scroll, overflow-y-auto
|
||||
|
||||
3. **Props**: Accept single item of the content type
|
||||
4. **Emits**: `back` event for navigation
|
||||
5. **Responsive**: Full height, works in sidebar and mobile overlay
|
||||
|
||||
The user will specify the content type and which fields to display.
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
name: new-grid
|
||||
description: Generate a content grid component following AIUI glass-morphism patterns
|
||||
allowed-tools: Read, Write, Edit, Glob, Grep
|
||||
---
|
||||
|
||||
Create a new content grid component at `packages/app/src/components/content/{Name}Grid.vue`.
|
||||
|
||||
## Requirements
|
||||
|
||||
1. **Read a reference**: Read `BookGrid.vue` or `PlaceGrid.vue` as a template — they show the standard pattern.
|
||||
|
||||
2. **Follow conventions**:
|
||||
- `<script setup lang="ts">` with props and emits
|
||||
- Glass morphism styling (bg-white/5, rounded-xl, hover:bg-white/10)
|
||||
- Dark/light mode support via `useTheme()`
|
||||
- Search input at top (if the content type has enough items)
|
||||
- Grid of cards with image fallback, title, subtitle, metadata
|
||||
- Touch targets min 44x44px
|
||||
- Empty state message when no items match
|
||||
- Custom scrollbar class
|
||||
|
||||
3. **Props**: Accept array of items + title string
|
||||
4. **Emits**: `select-{type}` event when a card is clicked
|
||||
5. **Responsive**: Works on mobile (full width) and desktop (sidebar width)
|
||||
|
||||
The user will specify the content type and its fields.
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: overnight
|
||||
description: Commit, branch, and start the overnight automation loop
|
||||
disable-model-invocation: true
|
||||
allowed-tools: Bash(*), Read, Write, Edit, Glob, Grep
|
||||
---
|
||||
|
||||
Prepare and launch the overnight automation loop. Do ALL steps in order, stopping on any failure:
|
||||
|
||||
1. Stage and commit all uncommitted changes: `git add -A && git commit -m "chore: pre-overnight snapshot"` (skip if working tree is clean)
|
||||
2. Push current branch to origin
|
||||
3. Get today's date as YYYY-MM-DD. Check if `overnight/$DATE` branch exists:
|
||||
- If yes: `git checkout overnight/$DATE`
|
||||
- If no: run `./loop/prepare.sh`
|
||||
4. Verify `loop/plan.md` has unchecked tasks (`grep -c '^\- \[ \]' loop/plan.md`)
|
||||
5. Commit plan files if modified: `git add loop/plan.md loop/prompt.md && git commit -m "chore: overnight plan $DATE"` (skip if clean)
|
||||
6. Push: `git push -u origin overnight/$DATE`
|
||||
7. Start the loop: run `caffeinate -i ./loop/loop.sh` with `run_in_background: true`
|
||||
8. Report: branch name, number of tasks, and confirm the loop is running in background
|
||||
@@ -0,0 +1,102 @@
|
||||
---
|
||||
name: pwa-icon-cache-fix
|
||||
description: Use when the user reports a PWA icon not updating, stale PWA icon, wrong icon after install, or any PWA caching issue. Also applies when changing PWA icons in a Vite + vite-plugin-pwa project.
|
||||
version: 2.0.0
|
||||
---
|
||||
|
||||
# PWA Icon Cache Fix
|
||||
|
||||
## Problem
|
||||
|
||||
PWA icons are cached at FOUR independent layers:
|
||||
1. **Service worker cache** (Workbox precache)
|
||||
2. **Browser HTTP cache**
|
||||
3. **Browser manifest resources** (Chromium stores resized icons in its profile data, keyed by a permanent extension ID tied to the origin — NEVER re-fetched even after uninstall/reinstall)
|
||||
4. **macOS .app bundle** (`.icns` file baked into the `.app` in `~/Applications/`)
|
||||
|
||||
Query string cache busting (`?v=2`) and uninstall/reinstall do NOT fix this. Chromium reuses the same extension ID for the same origin, so it keeps the old cached icons.
|
||||
|
||||
## Fix Steps
|
||||
|
||||
### 1. Verify icon files on disk and server are correct
|
||||
|
||||
```bash
|
||||
# Visual check
|
||||
Read packages/app/public/pwa-192x192.png
|
||||
Read packages/app/public/pwa-512x512.png
|
||||
|
||||
# Hash match check
|
||||
curl -s http://localhost:5173/pwa-192x192.png | md5
|
||||
md5 -q packages/app/public/pwa-192x192.png
|
||||
```
|
||||
|
||||
### 2. Find the PWA's Chromium extension ID
|
||||
|
||||
Read the installed `.app` bundle's `Info.plist` to get the `CrAppModeShortcutID`:
|
||||
|
||||
```bash
|
||||
plutil -p "~/Applications/Brave Browser Apps.localized/AIUI.app/Contents/Info.plist" | grep CrAppModeShortcutID
|
||||
```
|
||||
|
||||
This returns an ID like `idemibpphagihbobmgmaojhjfidlfpdl`.
|
||||
|
||||
### 3. Overwrite the cached icons in browser profile
|
||||
|
||||
Chromium stores resized icons at:
|
||||
`~/Library/Application Support/BraveSoftware/Brave-Browser/Default/Web Applications/Manifest Resources/{ID}/Icons/`
|
||||
|
||||
Overwrite every size using `sips`:
|
||||
|
||||
```bash
|
||||
ICON_DIR="~/Library/Application Support/BraveSoftware/Brave-Browser/Default/Web Applications/Manifest Resources/{ID}/Icons"
|
||||
SRC="packages/app/public/pwa-512x512.png"
|
||||
for size in 32 48 64 96 128 192 256 512; do
|
||||
sips -z $size $size "$SRC" --out "${ICON_DIR}/${size}.png"
|
||||
done
|
||||
```
|
||||
|
||||
### 4. Rebuild the macOS .icns in the .app bundle
|
||||
|
||||
```bash
|
||||
ICONSET="/tmp/aiui.iconset"
|
||||
mkdir -p "$ICONSET"
|
||||
SRC="packages/app/public/pwa-512x512.png"
|
||||
sips -z 16 16 "$SRC" --out "$ICONSET/icon_16x16.png"
|
||||
sips -z 32 32 "$SRC" --out "$ICONSET/icon_16x16@2x.png"
|
||||
sips -z 32 32 "$SRC" --out "$ICONSET/icon_32x32.png"
|
||||
sips -z 64 64 "$SRC" --out "$ICONSET/icon_32x32@2x.png"
|
||||
sips -z 128 128 "$SRC" --out "$ICONSET/icon_128x128.png"
|
||||
sips -z 256 256 "$SRC" --out "$ICONSET/icon_128x128@2x.png"
|
||||
sips -z 256 256 "$SRC" --out "$ICONSET/icon_256x256.png"
|
||||
sips -z 512 512 "$SRC" --out "$ICONSET/icon_256x256@2x.png"
|
||||
sips -z 512 512 "$SRC" --out "$ICONSET/icon_512x512.png"
|
||||
cp "$SRC" "$ICONSET/icon_512x512@2x.png"
|
||||
iconutil -c icns "$ICONSET" -o "~/Applications/Brave Browser Apps.localized/AIUI.app/Contents/Resources/app.icns"
|
||||
```
|
||||
|
||||
### 5. Flush macOS icon cache
|
||||
|
||||
```bash
|
||||
touch "~/Applications/Brave Browser Apps.localized/AIUI.app"
|
||||
killall Finder
|
||||
killall Dock
|
||||
```
|
||||
|
||||
### 6. Bump PWA_CACHE_VERSION in main.ts
|
||||
|
||||
Increment the `PWA_CACHE_VERSION` constant — this nukes all SW caches on next page load for web-layer caching.
|
||||
|
||||
### 7. Delete stale build artifacts
|
||||
|
||||
Remove old `dist/` and `dev-dist/` SW/manifest files.
|
||||
|
||||
## Browser-Specific Paths
|
||||
|
||||
- **Brave**: `~/Library/Application Support/BraveSoftware/Brave-Browser/Default/Web Applications/`
|
||||
- **Chrome**: `~/Library/Application Support/Google/Chrome/Default/Web Applications/`
|
||||
- **PWA apps (Brave)**: `~/Applications/Brave Browser Apps.localized/`
|
||||
- **PWA apps (Chrome)**: `~/Applications/Chrome Apps.localized/`
|
||||
|
||||
## Key Insight
|
||||
|
||||
Chromium assigns a permanent extension ID per origin (e.g., `localhost:5173`). This ID persists across uninstall/reinstall. The icon cache in `Manifest Resources/{ID}/Icons/` is populated ONCE and never refreshed from the manifest. The only fix is to overwrite the files directly on disk.
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
name: test-prompts
|
||||
description: Test AI prompt quality by simulating queries and checking extraction results
|
||||
allowed-tools: Bash(*), Read, Edit, Glob, Grep, Agent
|
||||
---
|
||||
|
||||
Test the AIUI AI prompt and content extraction pipeline end-to-end. This skill does NOT call the actual AI — it uses the seed prompts and extraction functions directly.
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Read seed prompts**: Read `packages/app/src/__tests__/fixtures/seedPrompts.ts` to get all test cases.
|
||||
|
||||
2. **Run extraction tests**: For each seed prompt, run the test via `pnpm --filter @aiui/app test -- --run -t "seed"` and report results.
|
||||
|
||||
3. **Test edge cases**: Create and test these additional scenarios by calling extraction functions in a test:
|
||||
- Mixed content response (films + songs + books in one response)
|
||||
- App recommendation response (should trigger app tab)
|
||||
- News query with web search results
|
||||
- Place/restaurant recommendations
|
||||
- Code response with 3+ code blocks
|
||||
- Nostr-related query
|
||||
- Empty/minimal response
|
||||
|
||||
4. **Verify tab routing**: For each scenario, check that `filterTabsByContext()` returns the expected tabs in the expected order.
|
||||
|
||||
5. **Report**: Summary of what works, what's broken, and what's missing. Include specific test cases that fail.
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: trace
|
||||
description: End-to-end trace of a query through prompt, extraction, tabs, and rendering
|
||||
allowed-tools: Bash(*), Read, Glob, Grep, Agent
|
||||
---
|
||||
|
||||
Trace how a specific user query flows through the entire AIUI pipeline. The user will provide a sample query (e.g., "best nostr apps", "recommend some films", "bitcoin news").
|
||||
|
||||
## Trace each stage:
|
||||
|
||||
1. **Query classifiers**: Run the query through each classifier in `contentFiltering.ts`:
|
||||
- `isNewsQuery()`, `isMusicQuery()`, `isBookQuery()`, `isTVQuery()`, `isImageQuery()`, `isPlaceQuery()`, `isRecipeQuery()`, `isCodeQuery()`, `isNostrQuery()`, `isAppQuery()`, `isWebsitesQuery()`
|
||||
- Report which ones return true
|
||||
|
||||
2. **Preferred tab**: What does `preferredFirstTab()` return for this query?
|
||||
|
||||
3. **System prompt**: What would `buildSystemPrompt()` include? Read `useAI.ts` and trace all dynamic sections.
|
||||
|
||||
4. **Expected AI response**: Based on the system prompt instructions, what tags would the AI likely use? Construct a realistic sample response.
|
||||
|
||||
5. **Extraction**: Run the sample response through each extraction function and report what gets found:
|
||||
- `extractAllFilms()`, `extractAllSongs()`, `extractAllPodcasts()`, `extractAllBooks()`, `extractAllTVSeries()`, `extractAllImages()`, `extractAllPlaces()`, `extractApps()`, `extractCodeBlocks()`, `extractRecipes()`
|
||||
|
||||
6. **Tab filtering**: What tabs would `filterTabsByContext()` return? In what order?
|
||||
|
||||
7. **Rendering**: Which grid component would render? Trace through ContentPanel.vue and/or ContentGridView.vue.
|
||||
|
||||
8. **Report**: Complete flow diagram showing: Query -> Classifiers -> Prompt -> Expected Response -> Extraction -> Tabs -> Grid
|
||||
+1
Submodule aiui/.claude/worktrees/agitated-hofstadter added at 10e12a329f
Submodule
+1
Submodule aiui/.claude/worktrees/funny-hofstadter added at 1c5185a15c
Submodule
+1
Submodule aiui/.claude/worktrees/happy-colden added at 666e1232f4
Submodule
+1
Submodule aiui/.claude/worktrees/hardcore-beaver added at a817fa199f
Submodule
+1
Submodule aiui/.claude/worktrees/heuristic-raman added at e8e002debc
Submodule
+1
Submodule aiui/.claude/worktrees/priceless-colden added at aaaef7d710
Reference in New Issue
Block a user