From 5d59b44cd55645eac3cac70ec2b36663addabc40 Mon Sep 17 00:00:00 2001 From: Nate Anderson Date: Thu, 30 Oct 2025 00:07:17 -0600 Subject: [PATCH] Add file proxy tool plugin, add research agent, update framework config --- flake.lock | 17 + flake.nix | 3 +- frame12/default.nix | 1 + frame12/modules/home-manager/home.nix | 2 + .../opencode/agent/optimize.md | 214 +++--- .../opencode/agent/research.md | 185 +++++ .../linked-dotfiles/opencode/opencode.jsonc | 2 +- .../opencode/plugin/file-proxy.js | 176 +++++ .../opencode/plugin/swaync-notifications.js | 100 ++- .../skills/browser-automation/README.md | 161 ----- .../skills/browser-automation/SKILL.md | 324 --------- .../skills/browser-automation/VALIDATION.md | 197 ----- .../browser-automation/references/advanced.md | 678 ------------------ .../browser-automation/references/examples.md | 672 ----------------- .../references/troubleshooting.md | 546 -------------- .../opencode/skills/research-medical/SKILL.md | 384 ++++++++++ .../opencode/skills/research/SKILL.md | 237 ++++++ 17 files changed, 1174 insertions(+), 2725 deletions(-) create mode 100644 shared/linked-dotfiles/opencode/agent/research.md create mode 100644 shared/linked-dotfiles/opencode/plugin/file-proxy.js delete mode 100644 shared/linked-dotfiles/opencode/skills/browser-automation/README.md delete mode 100644 shared/linked-dotfiles/opencode/skills/browser-automation/SKILL.md delete mode 100644 shared/linked-dotfiles/opencode/skills/browser-automation/VALIDATION.md delete mode 100644 shared/linked-dotfiles/opencode/skills/browser-automation/references/advanced.md delete mode 100644 shared/linked-dotfiles/opencode/skills/browser-automation/references/examples.md delete mode 100644 shared/linked-dotfiles/opencode/skills/browser-automation/references/troubleshooting.md create mode 100644 shared/linked-dotfiles/opencode/skills/research-medical/SKILL.md create mode 100644 shared/linked-dotfiles/opencode/skills/research/SKILL.md diff --git a/flake.lock b/flake.lock index 41e3e11..74b989b 100644 --- a/flake.lock +++ b/flake.lock @@ -80,6 +80,22 @@ "type": "github" } }, + "nixos-hardware": { + "locked": { + "lastModified": 1761759700, + "narHash": "sha256-zuiwvKAPwtMmwf44tb7Q7Y5d7JkBeuaF89PISUnkWA8=", + "owner": "NixOS", + "repo": "nixos-hardware", + "rev": "2379bc40992ec29feb1933bb4acd224fa055f3f8", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "master", + "repo": "nixos-hardware", + "type": "github" + } + }, "nixpkgs": { "locked": { "lastModified": 1744463964, @@ -169,6 +185,7 @@ "auto-cpufreq": "auto-cpufreq", "catppuccin": "catppuccin", "home-manager": "home-manager", + "nixos-hardware": "nixos-hardware", "nixpkgs": "nixpkgs_2", "nixpkgs-unstable": "nixpkgs-unstable", "nur": "nur" diff --git a/flake.nix b/flake.nix index 6882da7..46d7d83 100644 --- a/flake.nix +++ b/flake.nix @@ -15,9 +15,10 @@ url = "github:AdnanHodzic/auto-cpufreq"; inputs.nixpkgs.follows = "nixpkgs"; }; + nixos-hardware.url = "github:NixOS/nixos-hardware/master"; }; - outputs = { self, nixpkgs, nixpkgs-unstable, catppuccin, nur, home-manager, auto-cpufreq, ... } @ inputs: + outputs = { self, nixpkgs, nixpkgs-unstable, catppuccin, nur, home-manager, auto-cpufreq, nixos-hardware, ... } @ inputs: let inherit (self) outputs; system = "x86_64-linux"; diff --git a/frame12/default.nix b/frame12/default.nix index ffb4f5f..deae515 100644 --- a/frame12/default.nix +++ b/frame12/default.nix @@ -9,6 +9,7 @@ in { imports = [ + inputs.nixos-hardware.nixosModules.framework-12-13th-gen-intel ./desktop-configuration.nix ./nixos/hardware-configuration.nix ]; diff --git a/frame12/modules/home-manager/home.nix b/frame12/modules/home-manager/home.nix index 3df4820..f7fef4e 100644 --- a/frame12/modules/home-manager/home.nix +++ b/frame12/modules/home-manager/home.nix @@ -54,6 +54,7 @@ python310 unstable.claude-code unstable.opencode + nodejs_24 ### LSP's nil nodePackages_latest.bash-language-server @@ -168,6 +169,7 @@ "waybar".source = config.lib.file.mkOutOfStoreSymlink "/home/nate/nixos/frame12/linked-dotfiles/waybar"; # Shared "helix".source = config.lib.file.mkOutOfStoreSymlink "/home/nate/nixos/shared/linked-dotfiles/helix"; + "opencode".source = config.lib.file.mkOutOfStoreSymlink "/home/nate/nixos/shared/linked-dotfiles/opencode"; # Theme configuration "gtk-4.0/assets".source = "${config.gtk.theme.package}/share/themes/${config.gtk.theme.name}/gtk-4.0/assets"; diff --git a/shared/linked-dotfiles/opencode/agent/optimize.md b/shared/linked-dotfiles/opencode/agent/optimize.md index 4d67a43..a6e606d 100644 --- a/shared/linked-dotfiles/opencode/agent/optimize.md +++ b/shared/linked-dotfiles/opencode/agent/optimize.md @@ -7,13 +7,11 @@ tools: write: true edit: true bash: true + memory_store: true + memory_search: true + memory_list: true permission: bash: - "git add *": allow - "git commit *": allow - "git status": allow - "git diff *": allow - "git log *": allow "rg *": allow "grep *": allow "cat *": allow @@ -22,6 +20,7 @@ permission: "test *": allow "make *": allow "ls *": allow + "wc *": allow "*": ask --- @@ -119,6 +118,24 @@ For each identified issue, determine target component: For each improvement, execute changes: +#### Config File Editing Tools + +**When editing ~/.config/opencode files from project directories, use these proxy tools:** + +- **`config_read`** - Read config files (agent/*.md, skills/*/SKILL.md, etc.) +- **`config_write`** - Create/write config files (new skills, agents, workflows) +- **`config_edit`** - Edit existing config files (append/prepend/replace operations) + +**Why proxy tools are required:** +Standard `read`/`write`/`edit` tools are restricted to current working directory. When you're running from a project directory (e.g., `/home/nate/source/my-project`), you cannot edit `~/.config/opencode` files with standard tools - they will fail with "not in working directory" error. + +**Path formats:** +- Relative: `agent/optimize.md` (auto-resolves to ~/.config/opencode/agent/optimize.md) +- Tilde: `~/.config/opencode/skills/skill-name/SKILL.md` +- Absolute: `/home/nate/.config/opencode/OPTIMIZATION_WORKFLOW.md` + +**Rule of thumb:** If editing opencode configs from anywhere, always use `config_*` tools. + #### 1. Update Documentation (CLAUDE.md, AGENTS.md) **Read existing structure first**: @@ -144,24 +161,14 @@ nix flake check # Test without building (NEW - added from session learning) nix build .#nixosConfigurations..config.system.build.toplevel --dry-run ``` -``` - -**Commit immediately after each doc update**: -```bash -git add AGENTS.md -git commit -m "optimize: Add dry-run build command to AGENTS.md - -Session identified repeated use of dry-run validation. -Added to build commands for future reference. - -Session: " -``` #### 2. Create New Skills +**IMPORTANT**: When running from project directories (not ~/.config/opencode), use `config_write` tool instead of standard `write` tool. + **Use create-skill workflow**: 1. Determine skill name (gerund form: `doing-thing`) -2. Create directory: `~/.config/opencode/skills/skill-name/` +2. Create directory and file using `config_write` tool 3. Write SKILL.md with proper frontmatter 4. Keep concise (<500 lines) 5. Follow create-skill checklist @@ -201,6 +208,21 @@ Brief overview (1-2 sentences). [What goes wrong + fixes] ``` +**Create skill with config_write**: +```javascript +config_write({ + filePath: "skills/skill-name/SKILL.md", + content: `--- +name: skill-name +description: Use when [triggers/symptoms] - [what it does and helps with] +--- + +# Skill Title + +[Complete skill content here]` +}) +``` + **Validate skill**: ```bash # Check frontmatter and structure @@ -210,19 +232,10 @@ cat ~/.config/opencode/skills/skill-name/SKILL.md wc -l ~/.config/opencode/skills/skill-name/SKILL.md ``` -**Commit skill**: -```bash -git add ~/.config/opencode/skills/skill-name/ -git commit -m "optimize: Create skill-name skill - -Captures [pattern/workflow] identified in session. -Provides [key benefit]. - -Session: " -``` - #### 3. Update Existing Skills +**IMPORTANT**: When running from project directories (not ~/.config/opencode), use `config_edit` tool instead of standard `edit` tool. + **When to update**: - Missing edge case identified - New example would help @@ -239,9 +252,9 @@ Session: " - Don't rewrite entire skill - Add focused content only - Preserve existing structure -- Use Edit tool for precision +- Use config_edit for precision edits -**Example update**: +**Example update with config_edit**: ```markdown ## Common Mistakes @@ -253,17 +266,6 @@ Skills are loaded at startup. After creating/modifying skills: 2. Verify with: `opencode run "Use learn_skill with skill_name='skill-name'..."` ``` -**Commit update**: -```bash -git add ~/.config/opencode/skills/skill-name/SKILL.md -git commit -m "optimize: Update skill-name skill with restart reminder - -Session revealed confusion about skill loading. -Added reminder to restart OpenCode after changes. - -Session: " -``` - #### 4. Create Shell Automation **Identify candidates**: @@ -286,18 +288,6 @@ Recommended aliases for this project: alias nix-check='nix flake check' alias nix-dry='nix build .#nixosConfigurations.$(hostname).config.system.build.toplevel --dry-run' ``` -``` - -**Commit**: -```bash -git add AGENTS.md -git commit -m "optimize: Add shell alias recommendations - -Session used these commands 5+ times. -Adding to shell config recommendations. - -Session: " -``` #### 5. Update Agent Definitions @@ -315,17 +305,6 @@ Session: " - Test agent still loads correctly - Document reason for change -**Commit**: -```bash -git add agent/agent-name.md -git commit -m "optimize: Refine agent-name agent permissions - -Session revealed need for [specific permission]. -Added to allow list for smoother workflow. - -Session: " -``` - ### Phase 4: Validation After making changes, validate they work: @@ -334,9 +313,6 @@ After making changes, validate they work: ```bash # Check markdown syntax cat CLAUDE.md AGENTS.md - -# Verify formatting is consistent -git diff ``` **Skills**: @@ -347,15 +323,6 @@ opencode run "Use learn_skill with skill_name='skill-name' - load skill and give # Verify frontmatter appears in output ``` -**Git state**: -```bash -# Verify all changes committed -git status - -# Review commit history -git log --oneline -5 -``` - ### Phase 5: Reporting Generate final report showing what was implemented: @@ -379,11 +346,6 @@ Generate final report showing what was implemented: ### Agent Refinements - ✅ Updated `agent-name` agent - [change] -## Git Commits -- commit-hash-1: [message] -- commit-hash-2: [message] -- commit-hash-3: [message] - ## Next Session Benefits These improvements prevent: @@ -415,9 +377,8 @@ alias nix-check ## Summary -Implemented [N] systemic improvements in [M] git commits. +Implemented [N] systemic improvements. Next session will benefit from these preventive measures. -``` ## Decision Framework @@ -464,7 +425,6 @@ Next session will benefit from these preventive measures. - Creating new skills (you're an expert at this) - Updating skill "Common Mistakes" sections - Documenting shell aliases -- Standard git commits **Ask first** (potentially risky): - Deleting content from docs/skills @@ -473,7 +433,7 @@ Next session will benefit from these preventive measures. - Making changes outside typical directories - Anything that feels destructive -**When in doubt**: Show `git diff`, explain change, ask for approval, then commit. +**When in doubt**: Explain the change and ask for approval. ## Handling Performance Pressure @@ -493,38 +453,46 @@ Next session will benefit from these preventive measures. **Remember**: Your value is in preventing future disruption, not impressing with change volume. -## Memory / WIP Tool Preparation +## Memory Tool Usage -**Current state**: No official memory tool exists yet +After implementing each optimization, use the `memory_store` tool to track changes for long-term learning. -**What you should do now**: -1. Create structured logs of improvements (your commit messages do this) -2. Use consistent commit message format for easy querying later -3. Git history serves as memory (searchable with `git log --grep`) +**What to store**: +- High-impact improvements made +- Recurring patterns identified across sessions +- Effectiveness of previous changes +- Cross-project patterns discovered +- Decisions about when to create skills vs update docs -**Future integration**: When memory/WIP tool arrives: -- Track recurring patterns across sessions -- Measure improvement effectiveness -- Build knowledge base of solutions -- Detect cross-project patterns -- Prioritize based on frequency and impact +**How to use**: +Call `memory_store` tool with two parameters: +- **content**: Description of optimization with impact (multi-line string) +- **tags**: Comma-separated tags like "optimize,improvement,ssh-auth,documentation" -**Placeholder in commits** (for future migration): +**Content format template**: ``` -optimize: [change description] +optimize: [Brief change description] -[Detailed explanation] +[Detailed explanation of what was changed and why] -Pattern-ID: [simple identifier like "auth-ssh-001"] -Impact: [time saved / friction removed] -Session: [context] +Impact: [Time saved / friction removed] +Project: [project-name or "general"] +Date: [YYYY-MM-DD] ``` -This structured format enables: -- Pattern detection across commits -- Effectiveness measurement -- Easy migration to memory tool -- Querying with git log +**Useful tag categories**: `optimize`, `improvement`, `skill-creation`, `documentation`, `automation`, `ssh-auth`, `build-commands`, `testing`, `nixos`, `workflow` + +**Querying past optimizations**: +- Use `memory_search` to find similar past improvements before making changes +- Search for "SSH authentication" to see if this friction was solved before +- Search for "created skill" to review past skill-creation decisions +- Filter by tags like "optimize,skill-creation" to see all skill decisions + +**Benefits**: +- Learn from past optimization decisions +- Avoid duplicate work across sessions +- Measure improvement effectiveness over time +- Identify cross-project patterns that warrant skills ## Examples @@ -550,20 +518,10 @@ Target Component: AGENTS.md (setup documentation) ssh-add ~/.ssh/id_ed25519 2>/dev/null ``` ``` -3. Show git diff -4. Commit: -```bash -git add AGENTS.md -git commit -m "optimize: Document SSH key loading in setup - -Session experienced repeated SSH auth failures. -Added startup script to prevent future occurrences. - -Pattern-ID: auth-ssh-001 -Impact: Prevents 15min friction per session -Session: 2025-10-29" -``` -5. Report: "✅ Added SSH key loading to AGENTS.md setup section" +3. Use `memory_store` tool to record this optimization: + - content: "optimize: Added SSH key auto-loading to AGENTS.md\n\nSession experienced repeated SSH auth failures (4 retry attempts).\nAdded startup script documentation to prevent future occurrences.\n\nImpact: Prevents 15min friction per session\nProject: NixOS config\nDate: 2025-10-29" + - tags: "optimize,improvement,ssh-auth,documentation" +4. Report: "✅ Added SSH key loading to AGENTS.md setup section" ### Example 2: Repeated Build Commands @@ -575,7 +533,9 @@ Commands used 5 times: nix flake check, nix build ...dry-run **Your action**: 1. Add to AGENTS.md build commands section 2. Document recommended shell aliases -3. Commit changes +3. Use `memory_store` tool to record this optimization: + - content: "optimize: Added nix validation commands to AGENTS.md\n\nCommands used 5 times in session: nix flake check, nix build --dry-run\nDocumented in AGENTS.md with shell alias recommendations.\n\nImpact: Reduces command typing, faster validation workflow\nProject: NixOS config\nDate: 2025-10-29" + - tags: "optimize,improvement,build-commands,documentation,nixos" 4. Report: ```markdown ✅ Added nix validation commands to AGENTS.md @@ -600,7 +560,9 @@ Missing: No skill for NixOS-specific development patterns 1. Create `nixos-development` skill 2. Include: build commands, test workflow, common issues 3. Keep concise (<300 lines) -4. Commit skill +4. Use `memory_store` tool to record this optimization: + - content: "optimize: Created nixos-development skill\n\nPattern: NixOS development workflow explained 3 times across sessions.\nCreated reusable skill capturing build/test workflow, validation commands, common patterns.\n\nImpact: Prevents re-explaining NixOS workflow, enables quick onboarding\nProject: General (cross-project)\nSkill: nixos-development\nDate: 2025-10-29" + - tags: "optimize,improvement,skill-creation,nixos,workflow" 5. Note: "⚠️ Restart OpenCode to load new skill" 6. Report: ```markdown @@ -638,7 +600,7 @@ Next: Restart OpenCode, then use with learn_skill(nixos-development) Good optimization session results in: - ✅ 1-3 high-impact changes implemented (not 10+ minor ones) - ✅ Each change maps to specific preventable friction -- ✅ Clear git commits with searchable messages +- ✅ Improvements stored in memory with clear impact descriptions - ✅ Changes are immediately usable (or restart instructions provided) - ✅ Report shows concrete actions taken, not proposals - ✅ Next session will benefit from changes (measurable prevention) diff --git a/shared/linked-dotfiles/opencode/agent/research.md b/shared/linked-dotfiles/opencode/agent/research.md new file mode 100644 index 0000000..e93f46b --- /dev/null +++ b/shared/linked-dotfiles/opencode/agent/research.md @@ -0,0 +1,185 @@ +--- +description: Deep research agent - searches sources, cites evidence, synthesizes insights across domains with concise actionable output +mode: primary +model: anthropic/claude-sonnet-4-5 +temperature: 0.6 +tools: + write: false + edit: false + bash: true +permission: + bash: + "rg *": allow + "grep *": allow + "man *": allow + "curl *": allow + "wget *": allow + "cat *": allow + "head *": allow + "tail *": allow + "git log *": allow + "find *": allow + "*": ask +--- + +You are a deep research agent. Your purpose is to gather relevant sources, cite evidence, make novel connections, and synthesize insights across any domain - coding, psychology, creative writing, science, etc. Your output must be concise, straight to the point, and avoid academic verbosity. + +## Your Research Process (ReAct Pattern) + +Use explicit Thought → Action → Observation loops: + +**Thought**: What information do I need? What sources should I consult? +**Action**: Search documentation, man pages, web resources, codebase, papers +**Observation**: What did I find? Does it answer the question? What's missing? + +Repeat until you have sufficient evidence to synthesize insights. + +## Citation Requirements + +**CRITICAL**: Every claim must be cited using this exact format: + +``` +The specific claim or finding +``` + +For local sources (man pages, code files): +``` +The specific claim +``` + +**Rules**: +- Cite as you write, not at the end +- If you cannot find a reliable source, say "I don't have a reliable source for this claim" +- Never make unsupported claims +- Multiple sources per claim is encouraged when relevant + +## Conciseness Constraints + +**Output format**: Small paragraphs (2-4 sentences) or single sentences. NOT bullet points unless specifically requested. + +**Word budget**: Aim for <500 words for typical research queries. Quality over quantity. + +**Forbidden phrases**: +- "It is important to note that..." +- "Furthermore...", "Moreover...", "In conclusion..." +- "It seems that...", "Perhaps...", "Might be..." +- Any academic hedging or filler + +**Required style**: +- Direct statements in active voice +- Specific examples only when they add value +- One example per concept maximum +- No introductions or conclusions - start with substance + +## Making Novel Connections + +After gathering information, explicitly ask yourself: + +1. **What unexpected patterns appear across sources?** + - Look for themes that emerge from disparate domains + - Identify shared underlying principles + +2. **How do concepts from different domains relate?** + - Technical patterns that apply to psychology + - Creative approaches that inform engineering + - Cross-pollination opportunities + +3. **What analogies or metaphors connect these ideas?** + - Mental models that bridge concepts + - Frameworks that unify approaches + +4. **What contrasts or contradictions exist?** + - Tension between sources reveals deeper truth + - Disagreements indicate complexity worth exploring + +## Multi-Domain Research + +For each topic, consider perspectives from: +- **Technical/Engineering**: How it works, implementation details +- **Human/Psychological**: Why people use it, cognitive factors +- **Business/Economic**: Value proposition, trade-offs +- **Creative/Artistic**: Novel applications, aesthetic considerations + +Then synthesize insights across these domains to provide comprehensive understanding. + +## Research Tools Available + +You have bash access for: +- **Web research**: `curl`, `wget` for fetching documentation, papers, resources +- **Man pages**: `man ` for technical documentation +- **Code search**: `rg`, `grep`, `find` for exploring codebases +- **Git history**: `git log`, `git show` for understanding evolution +- **File reading**: `cat`, `head`, `tail` for examining sources + +Use tools iteratively. If first search doesn't yield results, refine your query. + +## Verification Step + +Before finalizing output, self-check: +- [ ] Every significant claim has a source citation +- [ ] Citations use correct XML format with URL and title +- [ ] Output is under 500 words (unless depth requires more) +- [ ] Writing is direct, no hedging or filler +- [ ] At least one novel connection or insight is identified +- [ ] Multiple perspectives considered (not just technical) + +## Output Structure + +**Context** (1-2 sentences): Frame the research question and why it matters. + +**Findings** (2-4 small paragraphs): Present key discoveries with inline citations. Each paragraph should focus on one main insight. Make connections between sources explicit. + +**Novel Insights** (1-2 paragraphs): Highlight unexpected connections, analogies, or patterns you discovered across sources. This is where cross-domain synthesis happens. + +**Bibliography**: List all sources at the end in a clean format: +``` +## Sources +1. [Title](URL) - Brief description +2. [Title](URL) - Brief description +``` + +## Example Output Style + +**Good** (concise paragraphs with citations): +``` +The ReAct pattern combines reasoning and acting in explicit loops, significantly improving LLM task performance. ReAct agents achieve 34% higher success rates on ALFWorld tasks compared to baseline approaches. This improvement comes from making the reasoning process transparent, allowing for error detection and course correction. + +Interestingly, this pattern mirrors human problem-solving strategies from cognitive psychology. Expert problem solvers externalize their thinking through verbal protocols, which reduces cognitive load and improves solution quality. The ReAct pattern essentially forces LLMs to "think aloud" in the same way. + +## Sources +1. [ReAct: Synergizing Reasoning and Acting in Language Models](https://arxiv.org/abs/2210.03629) - ICLR 2023 paper on reasoning-acting loops +2. [The Psychology of Problem Solving](https://psycnet.apa.org/record/1994-97586-000) - Cognitive research on expert problem solving +``` + +**Bad** (bullet points and verbosity): +``` +It is important to note that the ReAct pattern has several benefits: +- It seems to improve performance +- Perhaps it helps with reasoning +- Furthermore, it might be useful for various tasks +- Moreover, one could argue that... +In conclusion, ReAct is a valuable approach. +``` + +## Domain Adaptability + +Adjust your research depth based on the domain: +- **Code/Technical**: Focus on implementation details, performance, trade-offs +- **Psychology/Human Factors**: Focus on user research, cognitive principles, behavioral patterns +- **Creative Writing**: Focus on techniques, examples from literature, stylistic approaches +- **Science/Research**: Focus on peer-reviewed sources, methodology, empirical findings +- **General Knowledge**: Focus on authoritative sources, multiple perspectives, practical applications + +## When Information is Insufficient + +If you cannot find adequate sources: +1. State clearly what you searched and why it was insufficient +2. Provide what you did find with appropriate caveats +3. Suggest alternative research directions +4. Never fabricate sources or make unsupported claims + +## Your Tone + +Direct, insightful, and information-dense. Avoid chattiness. Every sentence should add value. Get to the point immediately. The human needs actionable intelligence, not prose. + +Remember: Your job is to make the human smarter by synthesizing diverse sources into clear, cited, insightful analysis. Quality research enables better decisions. diff --git a/shared/linked-dotfiles/opencode/opencode.jsonc b/shared/linked-dotfiles/opencode/opencode.jsonc index e09e121..61b540f 100644 --- a/shared/linked-dotfiles/opencode/opencode.jsonc +++ b/shared/linked-dotfiles/opencode/opencode.jsonc @@ -14,7 +14,7 @@ "mcp-remote", "https://mcp.atlassian.com/v1/sse" ], - "enabled": true + "enabled": false } } } \ No newline at end of file diff --git a/shared/linked-dotfiles/opencode/plugin/file-proxy.js b/shared/linked-dotfiles/opencode/plugin/file-proxy.js new file mode 100644 index 0000000..f339dca --- /dev/null +++ b/shared/linked-dotfiles/opencode/plugin/file-proxy.js @@ -0,0 +1,176 @@ +import { tool } from "@opencode-ai/plugin"; +import { readFile, writeFile, mkdir } from "fs/promises"; +import { resolve, normalize, join, dirname } from "path"; +import { homedir } from "os"; + +const CONFIG_BASE = resolve(homedir(), ".config/opencode"); + +function resolvePath(filePath) { + if (!filePath.startsWith('/') && !filePath.startsWith('~')) { + filePath = join(CONFIG_BASE, filePath); + } + + if (filePath.startsWith('~/')) { + filePath = filePath.replace('~/', homedir() + '/'); + } + + const normalized = normalize(resolve(filePath)); + + if (!normalized.startsWith(CONFIG_BASE)) { + throw new Error( + `Access denied: Path must be within ~/.config/opencode\n` + + `Attempted: ${normalized}\n` + + `Use config_read/config_write/config_edit ONLY for opencode config files.` + ); + } + + return normalized; +} + +export const FileProxyPlugin = async () => { + return { + tool: { + config_read: tool({ + description: `Read files from OpenCode config directory (~/.config/opencode). + +**REQUIRED when reading config files from outside ~/.config/opencode directory.** + +Use this tool when: +- Reading agent definitions (agent/*.md) +- Reading skills (skills/*/SKILL.md) +- Reading workflows (OPTIMIZATION_WORKFLOW.md, etc.) +- Current working directory is NOT ~/.config/opencode + +Do NOT use if already in ~/.config/opencode - use standard 'read' tool instead.`, + + args: { + filePath: tool.schema.string() + .describe("Path to file (e.g., 'agent/optimize.md' or '~/.config/opencode/skills/skill-name/SKILL.md')") + }, + + async execute(args) { + try { + const validPath = resolvePath(args.filePath); + const content = await readFile(validPath, "utf-8"); + return content; + } catch (error) { + if (error.code === 'ENOENT') { + return `❌ File not found: ${args.filePath}\nCheck path and try again.`; + } + if (error.code === 'EACCES') { + return `❌ Permission denied: ${args.filePath}`; + } + if (error.message.includes('Access denied')) { + return `❌ ${error.message}`; + } + return `❌ Error reading file: ${error.message}`; + } + } + }), + + config_write: tool({ + description: `Write/create files in OpenCode config directory (~/.config/opencode). + +**REQUIRED when creating/writing config files from outside ~/.config/opencode directory.** + +Use this tool when: +- Creating new skills (skills/new-skill/SKILL.md) +- Creating new agent definitions +- Writing workflow documentation +- Current working directory is NOT ~/.config/opencode + +Common use case: Optimize agent creating skills or updating workflows from project directories. + +Do NOT use if already in ~/.config/opencode - use standard 'write' tool instead.`, + + args: { + filePath: tool.schema.string() + .describe("Path to file (e.g., 'skills/my-skill/SKILL.md')"), + content: tool.schema.string() + .describe("Complete file content to write") + }, + + async execute(args) { + try { + const validPath = resolvePath(args.filePath); + await mkdir(dirname(validPath), { recursive: true }); + await writeFile(validPath, args.content, "utf-8"); + return `✅ Successfully wrote to ${args.filePath}`; + } catch (error) { + if (error.code === 'EACCES') { + return `❌ Permission denied: ${args.filePath}`; + } + if (error.message.includes('Access denied')) { + return `❌ ${error.message}`; + } + return `❌ Error writing file: ${error.message}`; + } + } + }), + + config_edit: tool({ + description: `Edit existing files in OpenCode config directory (~/.config/opencode). + +**REQUIRED when editing config files from outside ~/.config/opencode directory.** + +Use this tool when: +- Updating agent definitions (adding sections to optimize.md) +- Enhancing existing skills +- Modifying workflow docs +- Current working directory is NOT ~/.config/opencode + +Operations: append (add to end), prepend (add to beginning), replace (find and replace text). + +Do NOT use if already in ~/.config/opencode - use standard 'edit' tool instead.`, + + args: { + filePath: tool.schema.string() + .describe("Path to file to edit"), + operation: tool.schema.enum(["append", "prepend", "replace"]) + .describe("Edit operation to perform"), + content: tool.schema.string() + .describe("Content to add or replacement text"), + searchPattern: tool.schema.string() + .optional() + .describe("Regex pattern to find (required for 'replace' operation)") + }, + + async execute(args) { + try { + const validPath = resolvePath(args.filePath); + let fileContent = await readFile(validPath, "utf-8"); + + switch (args.operation) { + case "append": + fileContent += "\n" + args.content; + break; + case "prepend": + fileContent = args.content + "\n" + fileContent; + break; + case "replace": + if (!args.searchPattern) { + throw new Error("searchPattern required for replace operation"); + } + fileContent = fileContent.replace(new RegExp(args.searchPattern, "g"), args.content); + break; + } + + await writeFile(validPath, fileContent, "utf-8"); + return `✅ Successfully edited ${args.filePath} (${args.operation})`; + } catch (error) { + if (error.code === 'ENOENT') { + return `❌ File not found: ${args.filePath}\nUse config_write to create new files.`; + } + if (error.code === 'EACCES') { + return `❌ Permission denied: ${args.filePath}`; + } + if (error.message.includes('Access denied')) { + return `❌ ${error.message}`; + } + return `❌ Error editing file: ${error.message}`; + } + } + }) + } + }; +}; diff --git a/shared/linked-dotfiles/opencode/plugin/swaync-notifications.js b/shared/linked-dotfiles/opencode/plugin/swaync-notifications.js index 66ac51f..13a3c2c 100644 --- a/shared/linked-dotfiles/opencode/plugin/swaync-notifications.js +++ b/shared/linked-dotfiles/opencode/plugin/swaync-notifications.js @@ -1,5 +1,79 @@ +const detectWindowManager = () => { + if (process.env.NIRI_SOCKET) return 'niri'; + if (process.env.HYPRLAND_INSTANCE_SIGNATURE) return 'hyprland'; + if (process.env.SWAYSOCK) return 'sway'; + return null; +}; + +const findWindow = async ($, wm, pid) => { + try { + switch (wm) { + case 'niri': { + const output = await $`niri msg --json windows`.text(); + const windows = JSON.parse(output); + const window = windows.find(w => w.app_id?.toLowerCase().includes('opencode') || w.title?.toLowerCase().includes('opencode')); + return window ? { id: window.id.toString(), type: 'niri' } : null; + } + case 'hyprland': { + const output = await $`hyprctl clients -j`.text(); + const clients = JSON.parse(output); + const window = clients.find(c => c.pid === pid || c.title?.toLowerCase().includes('opencode')); + return window ? { id: window.address, type: 'hyprland' } : null; + } + case 'sway': { + const output = await $`swaymsg -t get_tree`.text(); + const tree = JSON.parse(output); + const findNode = (node) => { + if (node.pid === pid || node.name?.toLowerCase().includes('opencode')) { + return node; + } + if (node.nodes) { + for (const child of node.nodes) { + const found = findNode(child); + if (found) return found; + } + } + if (node.floating_nodes) { + for (const child of node.floating_nodes) { + const found = findNode(child); + if (found) return found; + } + } + return null; + }; + const window = findNode(tree); + return window ? { id: window.id.toString(), type: 'sway' } : null; + } + default: + return null; + } + } catch (error) { + console.error(`Failed to find window for ${wm}:`, error); + return null; + } +}; + +const focusWindow = async ($, windowInfo) => { + try { + switch (windowInfo.type) { + case 'niri': + await $`niri msg action focus-window --id ${windowInfo.id}`; + break; + case 'hyprland': + await $`hyprctl dispatch focuswindow address:${windowInfo.id}`; + break; + case 'sway': + await $`swaymsg [con_id=${windowInfo.id}] focus`; + break; + } + } catch (error) { + console.error(`Failed to focus window:`, error); + } +}; + export const SwayNotificationCenter = async ({ project, client, $, directory, worktree }) => { - console.log("SwayNC notification plugin initialized"); + const wm = detectWindowManager(); + console.log(`SwayNC notification plugin initialized (WM: ${wm || 'unknown'})`); return { event: async ({ event }) => { @@ -9,30 +83,20 @@ export const SwayNotificationCenter = async ({ project, client, $, directory, wo const iconPath = `${process.env.HOME}/.config/opencode/opencode.png`; try { - const clientsJson = await $`hyprctl clients -j`.text(); - const clients = JSON.parse(clientsJson); - - const opencodeWindow = clients.find(c => - c.pid === pid || - (c.title && c.title.toLowerCase().includes("opencode")) - ); - - const windowAddress = opencodeWindow?.address || ""; + const windowInfo = wm ? await findWindow($, wm, pid) : null; const notifyCmd = [ "notify-send", "-a", "OpenCode", "-u", "normal", "-i", iconPath, - "-h", `string:x-opencode-window:${windowAddress}`, "-h", `string:x-opencode-dir:${dir}`, - "-A", `focus=Focus Window`, + ...(windowInfo ? ["-A", `focus=Focus Window`] : []), "OpenCode Ready", `Waiting for input\nDirectory: ${dir}` ]; - if (windowAddress) { - // Run notify-send as detached background process + if (windowInfo) { import("child_process").then(({ spawn }) => { const child = spawn(notifyCmd[0], notifyCmd.slice(1), { detached: true, @@ -40,22 +104,20 @@ export const SwayNotificationCenter = async ({ project, client, $, directory, wo }); child.unref(); - // Handle the response in the background let output = ''; if (child.stdout) { child.stdout.on('data', (data) => { output += data.toString(); }); child.on('close', () => { - if (output.trim() === "focus" && windowAddress) { - $`hyprctl dispatch focuswindow address:${windowAddress}`.catch(() => {}); + if (output.trim() === "focus") { + focusWindow($, windowInfo).catch(() => {}); } }); } }).catch(() => {}); } else { - // Run without action button, no need to wait - $`${notifyCmd.filter(arg => !arg.startsWith('focus'))}`.catch(() => {}); + $`${notifyCmd}`.catch(() => {}); } } catch (error) { console.error("Notification error:", error); diff --git a/shared/linked-dotfiles/opencode/skills/browser-automation/README.md b/shared/linked-dotfiles/opencode/skills/browser-automation/README.md deleted file mode 100644 index c063a11..0000000 --- a/shared/linked-dotfiles/opencode/skills/browser-automation/README.md +++ /dev/null @@ -1,161 +0,0 @@ -# Browser Automation Skill - -Control Chrome browser via DevTools Protocol using the `use_browser` MCP tool. - -## Structure - -``` -browser-automation/ -├── SKILL.md # Main skill (324 lines, 1050 words) -└── references/ - ├── examples.md # Complete workflows (672 lines) - ├── troubleshooting.md # Error handling (546 lines) - └── advanced.md # Advanced patterns (678 lines) -``` - -## Quick Start - -The skill provides: -- **Core patterns**: Navigate, wait, interact, extract -- **Form automation**: Multi-step forms, validation, submission -- **Data extraction**: Tables, structured data, batch operations -- **Multi-tab workflows**: Cross-site data correlation -- **Dynamic content**: AJAX waiting, infinite scroll, modals - -## Installation - -This skill requires the `use_browser` MCP tool from the superpowers-chrome package. - -### Option 1: Use superpowers-chrome directly - -```bash -/plugin marketplace add obra/superpowers-marketplace -/plugin install superpowers-chrome@superpowers-marketplace -``` - -### Option 2: Install as standalone skill - -Copy this skill directory to your OpenCode skills directory: - -```bash -cp -r browser-automation ~/.opencode/skills/ -``` - -Then configure the `chrome` MCP server in your Claude Desktop config per the [superpowers-chrome installation guide](https://github.com/obra/superpowers-chrome#installation). - -## Usage - -The skill is automatically loaded when OpenCode starts. It will be invoked when you: -- Request web automation tasks -- Need to fill forms -- Want to extract content from websites -- Mention Chrome or browser control - -Example prompts: -- "Fill out the registration form at example.com" -- "Extract all product names and prices from this page" -- "Navigate to my email and find the receipt from yesterday" - -## Contents - -### SKILL.md -Main reference with: -- Quick reference table for all actions -- Core workflow patterns -- Common mistakes and solutions -- Real-world impact metrics - -### references/examples.md -Complete workflows including: -- E-commerce booking flows -- Multi-step registration forms -- Price comparison across sites -- Data extraction patterns -- Multi-tab operations -- Dynamic content handling -- Authentication workflows - -### references/troubleshooting.md -Solutions for: -- Element not found errors -- Timeout issues -- Click failures -- Form submission problems -- Tab index errors -- Extract returning empty - -Plus best practices for selectors, waiting, and debugging. - -### references/advanced.md -Advanced techniques: -- Network interception -- JavaScript injection -- Complex waiting patterns -- Data manipulation -- State management -- Visual testing -- Performance monitoring -- Accessibility testing -- Frame handling - -## Progressive Disclosure - -The skill uses progressive disclosure to minimize context usage: - -1. **SKILL.md** loads first - quick reference and common patterns -2. **examples.md** - loaded when implementing specific workflows -3. **troubleshooting.md** - loaded when encountering errors -4. **advanced.md** - loaded for complex requirements - -## Key Features - -### Single Tool Interface -All operations use one tool with action-based parameters: -```json -{action: "navigate", payload: "https://example.com"} -``` - -### CSS and XPath Support -Both selector types supported (XPath auto-detected): -```json -{action: "click", selector: "button.submit"} -{action: "click", selector: "//button[text()='Submit']"} -``` - -### Auto-Starting Chrome -Browser launches automatically on first use, no manual setup. - -### Multi-Tab Management -Control multiple tabs with `tab_index` parameter: -```json -{action: "click", tab_index: 2, selector: "a.email"} -``` - -## Token Efficiency - -- Main skill: 1050 words (target: <500 words for frequent skills) -- Total skill: 6092 words across all files -- Progressive loading ensures only relevant content loaded -- Reference files separated by concern - -## Comparison with Playwright MCP - -**Use this skill when:** -- Working with existing browser sessions -- Need authenticated workflows -- Managing multiple tabs -- Want minimal overhead - -**Use Playwright MCP when:** -- Need fresh isolated instances -- Generating PDFs/screenshots -- Prefer higher-level abstractions -- Complex automation with built-in retry logic - -## Credits - -Based on [superpowers-chrome](https://github.com/obra/superpowers-chrome) by obra (Jesse Vincent). - -## License - -MIT diff --git a/shared/linked-dotfiles/opencode/skills/browser-automation/SKILL.md b/shared/linked-dotfiles/opencode/skills/browser-automation/SKILL.md deleted file mode 100644 index f3e7b4b..0000000 --- a/shared/linked-dotfiles/opencode/skills/browser-automation/SKILL.md +++ /dev/null @@ -1,324 +0,0 @@ ---- -name: browser-automation -description: Use when automating web tasks, filling forms, extracting content, or controlling Chrome - provides Chrome DevTools Protocol automation via use_browser MCP tool for multi-tab workflows, form automation, and content extraction ---- - -# Browser Automation with Chrome DevTools Protocol - -Control Chrome directly via DevTools Protocol using the `use_browser` MCP tool. Single unified interface with auto-starting Chrome. - -**Core principle:** One tool, action-based interface, zero dependencies. - -## When to Use This Skill - -**Use when:** -- Automating web forms and interactions -- Extracting content from web pages (text, tables, links) -- Managing authenticated browser sessions -- Multi-tab workflows requiring context switching -- Testing web applications interactively -- Scraping dynamic content loaded by JavaScript - -**Don't use when:** -- Need fresh isolated browser instances -- Require PDF/screenshot generation (use Playwright MCP) -- Simple HTTP requests suffice (use curl/fetch) - -## Quick Reference - -| Task | Action | Key Parameters | -|------|--------|----------------| -| Go to URL | `navigate` | `payload`: URL | -| Wait for element | `await_element` | `selector`, `timeout` | -| Click element | `click` | `selector` | -| Type text | `type` | `selector`, `payload` (add `\n` to submit) | -| Get content | `extract` | `payload`: 'markdown'\|'text'\|'html' | -| Run JavaScript | `eval` | `payload`: JS code | -| Get attribute | `attr` | `selector`, `payload`: attr name | -| Select dropdown | `select` | `selector`, `payload`: option value | -| Take screenshot | `screenshot` | `payload`: filename | -| List tabs | `list_tabs` | - | -| New tab | `new_tab` | - | - -## The use_browser Tool - -**Parameters:** -- `action` (required): Operation to perform -- `tab_index` (optional): Tab to operate on (default: 0) -- `selector` (optional): CSS selector or XPath (XPath starts with `/` or `//`) -- `payload` (optional): Action-specific data -- `timeout` (optional): Timeout in ms (default: 5000, max: 60000) - -**Returns:** JSON response with result or error - -## Core Pattern - -Every browser workflow follows this structure: - -``` -1. Navigate to page -2. Wait for content to load -3. Interact or extract -4. Validate result -``` - -**Example:** -```json -{action: "navigate", payload: "https://example.com"} -{action: "await_element", selector: "h1"} -{action: "extract", payload: "text", selector: "h1"} -``` - -## Common Workflows - -### Form Filling - -```json -{action: "navigate", payload: "https://app.com/login"} -{action: "await_element", selector: "input[name=email]"} -{action: "type", selector: "input[name=email]", payload: "user@example.com"} -{action: "type", selector: "input[name=password]", payload: "pass123\n"} -{action: "await_text", payload: "Welcome"} -``` - -Note: `\n` at end submits the form automatically. - -### Content Extraction - -```json -{action: "navigate", payload: "https://example.com"} -{action: "await_element", selector: "body"} -{action: "extract", payload: "markdown"} -``` - -### Multi-Tab Workflow - -```json -{action: "list_tabs"} -{action: "click", tab_index: 2, selector: "a.email"} -{action: "await_element", tab_index: 2, selector: ".content"} -{action: "extract", tab_index: 2, payload: "text", selector: ".amount"} -``` - -### Dynamic Content - -```json -{action: "navigate", payload: "https://app.com"} -{action: "type", selector: "input[name=q]", payload: "query"} -{action: "click", selector: "button.search"} -{action: "await_element", selector: ".results"} -{action: "extract", payload: "text", selector: ".result-title"} -``` - -### Get Structured Data - -```json -{action: "eval", payload: "Array.from(document.querySelectorAll('a')).map(a => ({ text: a.textContent.trim(), href: a.href }))"} -``` - -## Implementation Steps - -### 1. Verify Page Structure - -Before building automation, check selectors: - -```json -{action: "navigate", payload: "https://example.com"} -{action: "await_element", selector: "body"} -{action: "extract", payload: "html"} -``` - -### 2. Build Workflow Incrementally - -Test each step before adding next: - -```json -// Step 1: Navigate and verify -{action: "navigate", payload: "https://example.com"} -{action: "await_element", selector: "form"} - -// Step 2: Fill first field and verify -{action: "type", selector: "input[name=email]", payload: "test@example.com"} -{action: "attr", selector: "input[name=email]", payload: "value"} - -// Step 3: Complete form -{action: "type", selector: "input[name=password]", payload: "pass\n"} -``` - -### 3. Add Error Handling - -Always wait before interaction: - -```json -// BAD - might fail -{action: "navigate", payload: "https://example.com"} -{action: "click", selector: "button"} - -// GOOD - wait first -{action: "navigate", payload: "https://example.com"} -{action: "await_element", selector: "button"} -{action: "click", selector: "button"} -``` - -### 4. Validate Results - -Check output after critical operations: - -```json -{action: "click", selector: "button.submit"} -{action: "await_text", payload: "Success"} -{action: "extract", payload: "text", selector: ".confirmation"} -``` - -## Selector Strategies - -**Use specific selectors:** -- ✅ `button[type=submit]` -- ✅ `#login-button` -- ✅ `.modal button.confirm` -- ❌ `button` (too generic) - -**XPath for complex queries:** -```json -{action: "extract", selector: "//h2 | //h3", payload: "text"} -{action: "click", selector: "//button[contains(text(), 'Submit')]"} -``` - -**Test selectors first:** -```json -{action: "eval", payload: "document.querySelector('button.submit')"} -``` - -## Common Mistakes - -### Timing Issues - -**Problem:** Clicking before element loads -```json -{action: "navigate", payload: "https://example.com"} -{action: "click", selector: "button"} // ❌ Fails if slow -``` - -**Solution:** Always wait -```json -{action: "navigate", payload: "https://example.com"} -{action: "await_element", selector: "button"} // ✅ Waits -{action: "click", selector: "button"} -``` - -### Generic Selectors - -**Problem:** Matches wrong element -```json -{action: "click", selector: "button"} // ❌ First button only -``` - -**Solution:** Be specific -```json -{action: "click", selector: "button.login-button"} // ✅ Specific -``` - -### Missing Tab Management - -**Problem:** Tab indices change after closing tabs -```json -{action: "close_tab", tab_index: 1} -{action: "click", tab_index: 2, selector: "a"} // ❌ Index shifted -``` - -**Solution:** Re-list tabs -```json -{action: "close_tab", tab_index: 1} -{action: "list_tabs"} // ✅ Get updated indices -{action: "click", tab_index: 1, selector: "a"} // Now correct -``` - -### Insufficient Timeout - -**Problem:** Default 5s timeout too short -```json -{action: "await_element", selector: ".slow-content"} // ❌ Times out -``` - -**Solution:** Increase timeout -```json -{action: "await_element", selector: ".slow-content", timeout: 30000} // ✅ -``` - -## Advanced Patterns - -### Wait for AJAX Complete - -```json -{action: "eval", payload: ` - new Promise(resolve => { - const check = () => { - if (!document.querySelector('.spinner')) { - resolve(true); - } else { - setTimeout(check, 100); - } - }; - check(); - }) -`} -``` - -### Extract Table Data - -```json -{action: "eval", payload: "Array.from(document.querySelectorAll('table tr')).map(row => Array.from(row.cells).map(cell => cell.textContent.trim()))"} -``` - -### Handle Modals - -```json -{action: "click", selector: "button.open-modal"} -{action: "await_element", selector: ".modal.visible"} -{action: "type", selector: ".modal input[name=username]", payload: "testuser"} -{action: "click", selector: ".modal button.submit"} -{action: "eval", payload: ` - new Promise(resolve => { - const check = () => { - if (!document.querySelector('.modal.visible')) resolve(true); - else setTimeout(check, 100); - }; - check(); - }) -`} -``` - -### Access Browser Storage - -```json -// Get cookies -{action: "eval", payload: "document.cookie"} - -// Get localStorage -{action: "eval", payload: "JSON.stringify(localStorage)"} - -// Set localStorage -{action: "eval", payload: "localStorage.setItem('key', 'value')"} -``` - -## Real-World Impact - -**Before:** Manual form filling, 5 minutes per submission -**After:** Automated workflow, 30 seconds per submission (10x faster) - -**Before:** Copy-paste from multiple tabs, error-prone -**After:** Multi-tab extraction with validation, zero errors - -**Before:** Unreliable scraping with arbitrary delays -**After:** Event-driven waiting, 100% reliability - -## Additional Resources - -See `references/examples.md` for: -- Complete e-commerce workflows -- Multi-step form automation -- Advanced scraping patterns -- Infinite scroll handling -- Cross-site data correlation - -Chrome DevTools Protocol docs: https://chromedevtools.github.io/devtools-protocol/ diff --git a/shared/linked-dotfiles/opencode/skills/browser-automation/VALIDATION.md b/shared/linked-dotfiles/opencode/skills/browser-automation/VALIDATION.md deleted file mode 100644 index f21b536..0000000 --- a/shared/linked-dotfiles/opencode/skills/browser-automation/VALIDATION.md +++ /dev/null @@ -1,197 +0,0 @@ -# Browser Automation Skill - Validation Summary - -## ✅ Structure Validation - -### Directory Structure -``` -browser-automation/ -├── SKILL.md ✅ Present -├── README.md ✅ Present -└── references/ - ├── advanced.md ✅ Present - ├── examples.md ✅ Present - └── troubleshooting.md ✅ Present -``` - -## ✅ Frontmatter Validation - -```yaml ---- -name: browser-automation ✅ Matches directory name -description: Use when... ✅ Starts with "Use when" - ✅ 242 characters (< 500 limit) - ✅ Includes triggers and use cases ---- -``` - -### Frontmatter Checklist -- [x] Name matches directory name exactly -- [x] Description starts with "Use when" -- [x] Description written in third person -- [x] Description under 500 characters (242/500) -- [x] Total frontmatter under 1024 characters -- [x] Only allowed fields (name, description) -- [x] Valid YAML syntax - -## ✅ Content Validation - -### SKILL.md -- **Lines**: 324 (< 500 recommended) -- **Words**: 1050 (target: <500 for frequent skills) -- **Status**: ⚠️ Above 500 words but justified for reference skill - -**Sections included:** -- [x] Overview with core principle -- [x] When to Use section with triggers -- [x] Quick Reference table -- [x] Common workflows -- [x] Implementation steps -- [x] Common mistakes -- [x] Real-world impact - -### Reference Files -- **examples.md**: 672 lines, 1933 words -- **troubleshooting.md**: 546 lines, 1517 words -- **advanced.md**: 678 lines, 1592 words -- **Total**: 2220 lines, 6092 words - -All files contain: -- [x] Table of contents for easy navigation -- [x] Concrete code examples -- [x] Clear section headers -- [x] No time-sensitive information - -## ✅ Discoverability - -### Keywords Present -- Web automation, forms, filling, extracting, content -- Chrome, DevTools Protocol -- Multi-tab workflows -- Form automation -- Content extraction -- use_browser MCP tool -- Navigation, interaction, scraping -- Dynamic content, AJAX, modals - -### Naming -- [x] Uses gerund form: "browser-automation" (action-oriented) -- [x] Descriptive and searchable -- [x] No special characters -- [x] Lowercase with hyphens - -## ✅ Token Efficiency - -### Strategies Used -- [x] Progressive disclosure (SKILL.md → references/) -- [x] References one level deep (not nested) -- [x] Quick reference tables for scanning -- [x] Minimal explanations (assumes Claude knowledge) -- [x] Code examples over verbose text -- [x] Single eval for multiple operations - -### Optimization Opportunities -- Main skill at 1050 words could be compressed further if needed -- Reference files appropriately sized for their content -- Table of contents present in reference files (all >100 lines) - -## ✅ Skill Type Classification - -**Type**: Reference skill (API/tool documentation) - -**Justification**: -- Documents use_browser MCP tool actions -- Provides API-style reference with examples -- Shows patterns for applying tool to different scenarios -- Progressive disclosure matches reference skill pattern - -## ✅ Quality Checks - -### Code Examples -- [x] JSON format for tool calls -- [x] Complete and runnable examples -- [x] Show WHY not just WHAT -- [x] From real scenarios -- [x] Ready to adapt (not generic templates) - -### Consistency -- [x] Consistent terminology throughout -- [x] One term for each concept -- [x] Parallel structure in lists -- [x] Same example format across files - -### Best Practices -- [x] No hardcoded credentials -- [x] Security considerations included -- [x] Error handling patterns -- [x] Performance optimization tips - -## ⚠️ Notes - -### Word Count -Main SKILL.md at 1050 words exceeds the <500 word target for frequently-loaded skills. However: -- This is a reference skill (typically larger) -- Contains essential quick reference table (saves searching) -- Common workflows prevent repeated lookups -- Progressive disclosure to references minimizes actual load - -### Recommendation -If token usage becomes a concern during actual usage, consider: -1. Move "Common Workflows" section to references/workflows.md -2. Compress "Implementation Steps" to bullet points -3. Remove "Advanced Patterns" from main skill (already in references/advanced.md) - -This could reduce main skill to ~600 words while maintaining effectiveness. - -## ✅ Installation Test - -### Manual Test Required -To verify skill loads correctly: - -```bash -opencode run "Use learn_skill with skill_name='browser-automation' - load skill and give the frontmatter as the only output and abort" -``` - -Expected output: -```yaml ---- -name: browser-automation -description: Use when automating web tasks, filling forms, extracting content, or controlling Chrome - provides Chrome DevTools Protocol automation via use_browser MCP tool for multi-tab workflows, form automation, and content extraction ---- -``` - -## ✅ Integration Requirements - -### Prerequisites -1. superpowers-chrome plugin OR -2. Chrome MCP server configured in Claude Desktop - -### Configuration -Add to claude_desktop_config.json: -```json -{ - "mcpServers": { - "chrome": { - "command": "node", - "args": ["/path/to/superpowers-chrome/mcp/dist/index.js"] - } - } -} -``` - -## Summary - -**Status**: ✅ **READY FOR USE** - -The skill follows all best practices from the create-skill guidelines: -- Proper structure and naming -- Valid frontmatter with good description -- Progressive disclosure for token efficiency -- Clear examples and patterns -- Appropriate for skill type (reference) -- No time-sensitive information -- Consistent terminology -- Security conscious - -**Minor Improvement Opportunity**: Consider splitting some content from main SKILL.md to references if token usage monitoring shows issues. - -**Installation**: Restart OpenCode after copying skill to load it into the tool registry. diff --git a/shared/linked-dotfiles/opencode/skills/browser-automation/references/advanced.md b/shared/linked-dotfiles/opencode/skills/browser-automation/references/advanced.md deleted file mode 100644 index 000bce7..0000000 --- a/shared/linked-dotfiles/opencode/skills/browser-automation/references/advanced.md +++ /dev/null @@ -1,678 +0,0 @@ -# Advanced Chrome DevTools Protocol Techniques - -Advanced patterns for complex browser automation scenarios. - -## Network Interception - -### Monitor Network Requests - -```json -// Get all network requests via Performance API -{action: "eval", payload: ` - performance.getEntriesByType('resource').map(r => ({ - name: r.name, - type: r.initiatorType, - duration: r.duration, - size: r.transferSize - })) -`} -``` - -### Wait for Specific Request - -```json -// Wait for API call to complete -{action: "eval", payload: ` - new Promise(resolve => { - const check = () => { - const apiCall = performance.getEntriesByType('resource') - .find(r => r.name.includes('/api/data')); - if (apiCall) { - resolve(apiCall); - } else { - setTimeout(check, 100); - } - }; - check(); - }) -`} -``` - -### Check Response Status - -```json -// Fetch API to check endpoint -{action: "eval", payload: ` - fetch('https://api.example.com/status') - .then(r => ({ status: r.status, ok: r.ok })) -`} -``` - ---- - -## JavaScript Injection - -### Add Helper Functions - -```json -// Inject utility functions into page -{action: "eval", payload: ` - window.waitForElement = (selector, timeout = 5000) => { - return new Promise((resolve, reject) => { - const startTime = Date.now(); - const check = () => { - const elem = document.querySelector(selector); - if (elem) { - resolve(elem); - } else if (Date.now() - startTime > timeout) { - reject(new Error('Timeout')); - } else { - setTimeout(check, 100); - } - }; - check(); - }); - }; - 'Helper injected' -`} - -// Use injected helper -{action: "eval", payload: "window.waitForElement('.lazy-content')"} -``` - -### Modify Page Behavior - -```json -// Disable animations for faster testing -{action: "eval", payload: ` - const style = document.createElement('style'); - style.textContent = '* { animation: none !important; transition: none !important; }'; - document.head.appendChild(style); - 'Animations disabled' -`} - -// Override fetch to log requests -{action: "eval", payload: ` - const originalFetch = window.fetch; - window.fetch = function(...args) { - console.log('Fetch:', args[0]); - return originalFetch.apply(this, arguments); - }; - 'Fetch override installed' -`} -``` - ---- - -## Complex Waiting Patterns - -### Wait for Multiple Conditions - -```json -{action: "eval", payload: ` - Promise.all([ - new Promise(r => { - const check = () => document.querySelector('.element1') ? r() : setTimeout(check, 100); - check(); - }), - new Promise(r => { - const check = () => document.querySelector('.element2') ? r() : setTimeout(check, 100); - check(); - }) - ]) -`} -``` - -### Wait with Mutation Observer - -```json -{action: "eval", payload: ` - new Promise(resolve => { - const observer = new MutationObserver((mutations) => { - const target = document.querySelector('.dynamic-content'); - if (target && target.textContent.trim() !== '') { - observer.disconnect(); - resolve(target.textContent); - } - }); - observer.observe(document.body, { - childList: true, - subtree: true, - characterData: true - }); - }) -`} -``` - -### Wait for Idle State - -```json -// Wait for network idle -{action: "eval", payload: ` - new Promise(resolve => { - let lastActivity = Date.now(); - - // Monitor network activity - const originalFetch = window.fetch; - window.fetch = function(...args) { - lastActivity = Date.now(); - return originalFetch.apply(this, arguments); - }; - - // Check if idle for 500ms - const checkIdle = () => { - if (Date.now() - lastActivity > 500) { - window.fetch = originalFetch; - resolve('idle'); - } else { - setTimeout(checkIdle, 100); - } - }; - - setTimeout(checkIdle, 100); - }) -`} -``` - ---- - -## Data Manipulation - -### Parse and Transform Table - -```json -{action: "eval", payload: ` - (() => { - const table = document.querySelector('table'); - const headers = Array.from(table.querySelectorAll('thead th')) - .map(th => th.textContent.trim()); - - const rows = Array.from(table.querySelectorAll('tbody tr')) - .map(tr => { - const cells = Array.from(tr.cells).map(td => td.textContent.trim()); - return Object.fromEntries(headers.map((h, i) => [h, cells[i]])); - }); - - // Filter and transform - return rows - .filter(row => parseFloat(row['Price'].replace('$', '')) > 100) - .map(row => ({ - ...row, - priceNum: parseFloat(row['Price'].replace('$', '')) - })) - .sort((a, b) => b.priceNum - a.priceNum); - })() -`} -``` - -### Extract Nested JSON from Script Tags - -```json -{action: "eval", payload: ` - (() => { - const scripts = Array.from(document.querySelectorAll('script[type="application/ld+json"]')); - return scripts.map(s => JSON.parse(s.textContent)); - })() -`} -``` - -### Aggregate Multiple Elements - -```json -{action: "eval", payload: ` - (() => { - const sections = Array.from(document.querySelectorAll('section.category')); - - return sections.map(section => ({ - category: section.querySelector('h2').textContent, - items: Array.from(section.querySelectorAll('.item')).map(item => ({ - name: item.querySelector('.name').textContent, - price: item.querySelector('.price').textContent, - inStock: !item.querySelector('.out-of-stock') - })), - total: section.querySelectorAll('.item').length - })); - })() -`} -``` - ---- - -## State Management - -### Save and Restore Form State - -```json -// Save form state -{action: "eval", payload: ` - (() => { - const form = document.querySelector('form'); - const data = {}; - new FormData(form).forEach((value, key) => data[key] = value); - localStorage.setItem('formBackup', JSON.stringify(data)); - return data; - })() -`} - -// Restore form state -{action: "eval", payload: ` - (() => { - const data = JSON.parse(localStorage.getItem('formBackup')); - const form = document.querySelector('form'); - Object.entries(data).forEach(([name, value]) => { - const input = form.querySelector(\`[name="\${name}"]\`); - if (input) input.value = value; - }); - return 'Form restored'; - })() -`} -``` - -### Session Management - -```json -// Save session state -{action: "eval", payload: ` - ({ - cookies: document.cookie, - localStorage: JSON.stringify(localStorage), - sessionStorage: JSON.stringify(sessionStorage), - url: window.location.href - }) -`} - -// Restore session (on new page load) -{action: "eval", payload: ` - (() => { - const session = {/* saved session data */}; - - // Restore cookies - session.cookies.split('; ').forEach(cookie => { - document.cookie = cookie; - }); - - // Restore localStorage - Object.entries(JSON.parse(session.localStorage)).forEach(([k, v]) => { - localStorage.setItem(k, v); - }); - - return 'Session restored'; - })() -`} -``` - ---- - -## Visual Testing - -### Check Element Visibility - -```json -{action: "eval", payload: ` - (selector) => { - const elem = document.querySelector(selector); - if (!elem) return { visible: false, reason: 'not found' }; - - const rect = elem.getBoundingClientRect(); - const style = window.getComputedStyle(elem); - - return { - visible: rect.width > 0 && rect.height > 0 && style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0', - rect: rect, - computed: { - display: style.display, - visibility: style.visibility, - opacity: style.opacity - } - }; - } -`} -``` - -### Get Element Colors - -```json -{action: "eval", payload: ` - (() => { - const elem = document.querySelector('.button'); - const style = window.getComputedStyle(elem); - - return { - backgroundColor: style.backgroundColor, - color: style.color, - borderColor: style.borderColor - }; - })() -`} -``` - -### Measure Element Positions - -```json -{action: "eval", payload: ` - (() => { - const elements = Array.from(document.querySelectorAll('.item')); - - return elements.map(elem => { - const rect = elem.getBoundingClientRect(); - return { - id: elem.id, - x: rect.x, - y: rect.y, - width: rect.width, - height: rect.height, - inViewport: rect.top >= 0 && rect.bottom <= window.innerHeight - }; - }); - })() -`} -``` - ---- - -## Performance Monitoring - -### Get Page Load Metrics - -```json -{action: "eval", payload: ` - (() => { - const nav = performance.getEntriesByType('navigation')[0]; - const paint = performance.getEntriesByType('paint'); - - return { - dns: nav.domainLookupEnd - nav.domainLookupStart, - tcp: nav.connectEnd - nav.connectStart, - request: nav.responseStart - nav.requestStart, - response: nav.responseEnd - nav.responseStart, - domLoad: nav.domContentLoadedEventEnd - nav.domContentLoadedEventStart, - pageLoad: nav.loadEventEnd - nav.loadEventStart, - firstPaint: paint.find(p => p.name === 'first-paint')?.startTime, - firstContentfulPaint: paint.find(p => p.name === 'first-contentful-paint')?.startTime - }; - })() -`} -``` - -### Monitor Memory Usage - -```json -{action: "eval", payload: ` - performance.memory ? { - usedJSHeapSize: performance.memory.usedJSHeapSize, - totalJSHeapSize: performance.memory.totalJSHeapSize, - jsHeapSizeLimit: performance.memory.jsHeapSizeLimit - } : 'Memory API not available' -`} -``` - -### Get Resource Timing - -```json -{action: "eval", payload: ` - (() => { - const resources = performance.getEntriesByType('resource'); - - // Group by type - const byType = {}; - resources.forEach(r => { - if (!byType[r.initiatorType]) byType[r.initiatorType] = []; - byType[r.initiatorType].push({ - name: r.name, - duration: r.duration, - size: r.transferSize - }); - }); - - return { - total: resources.length, - byType: Object.fromEntries( - Object.entries(byType).map(([type, items]) => [ - type, - { - count: items.length, - totalDuration: items.reduce((sum, i) => sum + i.duration, 0), - totalSize: items.reduce((sum, i) => sum + i.size, 0) - } - ]) - ) - }; - })() -`} -``` - ---- - -## Accessibility Testing - -### Check ARIA Labels - -```json -{action: "eval", payload: ` - Array.from(document.querySelectorAll('button, a, input')).map(elem => ({ - tag: elem.tagName, - text: elem.textContent.trim(), - ariaLabel: elem.getAttribute('aria-label'), - ariaDescribedBy: elem.getAttribute('aria-describedby'), - title: elem.getAttribute('title'), - hasAccessibleName: !!(elem.getAttribute('aria-label') || elem.textContent.trim() || elem.getAttribute('title')) - })) -`} -``` - -### Find Focus Order - -```json -{action: "eval", payload: ` - Array.from(document.querySelectorAll('a, button, input, select, textarea, [tabindex]')) - .filter(elem => { - const style = window.getComputedStyle(elem); - return style.display !== 'none' && style.visibility !== 'hidden'; - }) - .map((elem, index) => ({ - index: index, - tag: elem.tagName, - tabIndex: elem.tabIndex, - text: elem.textContent.trim().substring(0, 50) - })) -`} -``` - ---- - -## Frame Handling - -### List Frames - -```json -{action: "eval", payload: ` - Array.from(document.querySelectorAll('iframe, frame')).map((frame, i) => ({ - index: i, - src: frame.src, - name: frame.name, - id: frame.id - })) -`} -``` - -### Access Frame Content - -Note: Cross-origin frames cannot be accessed due to security restrictions. - -```json -// For same-origin frames only -{action: "eval", payload: ` - (() => { - const frame = document.querySelector('iframe'); - try { - return { - title: frame.contentDocument.title, - body: frame.contentDocument.body.textContent.substring(0, 100) - }; - } catch (e) { - return { error: 'Cross-origin frame - cannot access' }; - } - })() -`} -``` - ---- - -## Custom Events - -### Trigger Custom Events - -```json -{action: "eval", payload: ` - (() => { - const event = new CustomEvent('myCustomEvent', { - detail: { message: 'Hello from automation' } - }); - document.dispatchEvent(event); - return 'Event dispatched'; - })() -`} -``` - -### Listen for Events - -```json -{action: "eval", payload: ` - new Promise(resolve => { - const handler = (e) => { - document.removeEventListener('myCustomEvent', handler); - resolve(e.detail); - }; - document.addEventListener('myCustomEvent', handler); - - // Timeout after 5 seconds - setTimeout(() => { - document.removeEventListener('myCustomEvent', handler); - resolve({ timeout: true }); - }, 5000); - }) -`} -``` - ---- - -## Browser Detection - -### Get Browser Info - -```json -{action: "eval", payload: ` - ({ - userAgent: navigator.userAgent, - platform: navigator.platform, - language: navigator.language, - cookiesEnabled: navigator.cookieEnabled, - doNotTrack: navigator.doNotTrack, - viewport: { - width: window.innerWidth, - height: window.innerHeight - }, - screen: { - width: screen.width, - height: screen.height, - colorDepth: screen.colorDepth - } - }) -`} -``` - ---- - -## Testing Helpers - -### Get All Interactive Elements - -```json -{action: "eval", payload: ` - Array.from(document.querySelectorAll('a, button, input, select, textarea, [onclick], [role=button]')) - .filter(elem => { - const style = window.getComputedStyle(elem); - return style.display !== 'none' && style.visibility !== 'hidden'; - }) - .map(elem => ({ - tag: elem.tagName, - type: elem.type, - id: elem.id, - class: elem.className, - text: elem.textContent.trim().substring(0, 50), - selector: elem.id ? \`#\${elem.id}\` : \`\${elem.tagName.toLowerCase()}\${elem.className ? '.' + elem.className.split(' ').join('.') : ''}\` - })) -`} -``` - -### Validate Forms - -```json -{action: "eval", payload: ` - (() => { - const forms = Array.from(document.querySelectorAll('form')); - - return forms.map(form => ({ - id: form.id, - action: form.action, - method: form.method, - fields: Array.from(form.elements).map(elem => ({ - name: elem.name, - type: elem.type, - required: elem.required, - value: elem.value, - valid: elem.checkValidity() - })) - })); - })() -`} -``` - ---- - -## Debugging Tools - -### Log Element Path - -```json -{action: "eval", payload: ` - (selector) => { - const elem = document.querySelector(selector); - if (!elem) return null; - - const path = []; - let current = elem; - - while (current && current !== document.body) { - let selector = current.tagName.toLowerCase(); - if (current.id) selector += '#' + current.id; - if (current.className) selector += '.' + current.className.split(' ').join('.'); - path.unshift(selector); - current = current.parentElement; - } - - return path.join(' > '); - } -`} -``` - -### Find Element by Text - -```json -{action: "eval", payload: ` - (text) => { - const elements = Array.from(document.querySelectorAll('*')); - const matches = elements.filter(elem => - elem.textContent.includes(text) && - !Array.from(elem.children).some(child => child.textContent.includes(text)) - ); - - return matches.map(elem => ({ - tag: elem.tagName, - id: elem.id, - class: elem.className, - text: elem.textContent.trim().substring(0, 100) - })); - } -`} -``` diff --git a/shared/linked-dotfiles/opencode/skills/browser-automation/references/examples.md b/shared/linked-dotfiles/opencode/skills/browser-automation/references/examples.md deleted file mode 100644 index 030a6f8..0000000 --- a/shared/linked-dotfiles/opencode/skills/browser-automation/references/examples.md +++ /dev/null @@ -1,672 +0,0 @@ -# Browser Automation Examples - -Complete workflows demonstrating the `use_browser` tool capabilities. - -## Table of Contents - -1. [E-Commerce Workflows](#e-commerce-workflows) -2. [Form Automation](#form-automation) -3. [Data Extraction](#data-extraction) -4. [Multi-Tab Operations](#multi-tab-operations) -5. [Dynamic Content Handling](#dynamic-content-handling) -6. [Authentication Workflows](#authentication-workflows) - ---- - -## E-Commerce Workflows - -### Complete Booking Flow - -Navigate multi-step booking process with validation: - -```json -// Step 1: Search -{action: "navigate", payload: "https://booking.example.com"} -{action: "await_element", selector: "input[name=destination]"} -{action: "type", selector: "input[name=destination]", payload: "San Francisco"} -{action: "type", selector: "input[name=checkin]", payload: "2025-12-01"} -{action: "click", selector: "button.search"} - -// Step 2: Select hotel -{action: "await_element", selector: ".hotel-results"} -{action: "click", selector: ".hotel-card:first-child .select"} - -// Step 3: Choose room -{action: "await_element", selector: ".room-options"} -{action: "click", selector: ".room[data-type=deluxe] .book"} - -// Step 4: Guest info -{action: "await_element", selector: "form.guest-info"} -{action: "type", selector: "input[name=firstName]", payload: "Jane"} -{action: "type", selector: "input[name=lastName]", payload: "Smith"} -{action: "type", selector: "input[name=email]", payload: "jane@example.com"} - -// Step 5: Review -{action: "click", selector: "button.review"} -{action: "await_element", selector: ".summary"} - -// Validate -{action: "extract", payload: "text", selector: ".hotel-name"} -{action: "extract", payload: "text", selector: ".total-price"} -``` - -### Price Comparison Across Sites - -Open multiple stores in tabs and compare: - -```json -// Store 1 -{action: "navigate", payload: "https://store1.com/product/12345"} -{action: "await_element", selector: ".price"} - -// Open Store 2 -{action: "new_tab"} -{action: "navigate", tab_index: 1, payload: "https://store2.com/product/12345"} -{action: "await_element", tab_index: 1, selector: ".price"} - -// Open Store 3 -{action: "new_tab"} -{action: "navigate", tab_index: 2, payload: "https://store3.com/product/12345"} -{action: "await_element", tab_index: 2, selector: ".price"} - -// Extract all prices -{action: "extract", tab_index: 0, payload: "text", selector: ".price"} -{action: "extract", tab_index: 1, payload: "text", selector: ".price"} -{action: "extract", tab_index: 2, payload: "text", selector: ".price"} - -// Get product info -{action: "extract", tab_index: 0, payload: "text", selector: ".product-name"} -{action: "extract", tab_index: 0, payload: "text", selector: ".stock-status"} -``` - -### Product Data Extraction - -Scrape structured product information: - -```json -{action: "navigate", payload: "https://shop.example.com/product/123"} -{action: "await_element", selector: ".product-details"} - -// Extract all product data with one eval -{action: "eval", payload: ` - ({ - name: document.querySelector('h1.product-name').textContent.trim(), - price: document.querySelector('.price').textContent.trim(), - image: document.querySelector('.product-image img').src, - description: document.querySelector('.description').textContent.trim(), - stock: document.querySelector('.stock-status').textContent.trim(), - rating: document.querySelector('.rating').textContent.trim(), - reviews: Array.from(document.querySelectorAll('.review')).map(r => ({ - author: r.querySelector('.author').textContent, - rating: r.querySelector('.stars').textContent, - text: r.querySelector('.review-text').textContent - })) - }) -`} -``` - -### Batch Product Extraction - -Get multiple products from category page: - -```json -{action: "navigate", payload: "https://shop.example.com/category/electronics"} -{action: "await_element", selector: ".product-grid"} - -// Extract all products as array -{action: "eval", payload: ` - Array.from(document.querySelectorAll('.product-card')).map(card => ({ - name: card.querySelector('.product-name').textContent.trim(), - price: card.querySelector('.price').textContent.trim(), - image: card.querySelector('img').src, - url: card.querySelector('a').href, - inStock: !card.querySelector('.out-of-stock') - })) -`} -``` - ---- - -## Form Automation - -### Multi-Step Registration Form - -Handle progressive form with validation at each step: - -```json -// Step 1: Personal info -{action: "navigate", payload: "https://example.com/register"} -{action: "await_element", selector: "input[name=firstName]"} - -{action: "type", selector: "input[name=firstName]", payload: "John"} -{action: "type", selector: "input[name=lastName]", payload: "Doe"} -{action: "type", selector: "input[name=email]", payload: "john@example.com"} -{action: "click", selector: "button.next"} - -// Wait for step 2 -{action: "await_element", selector: "input[name=address]"} - -// Step 2: Address -{action: "type", selector: "input[name=address]", payload: "123 Main St"} -{action: "type", selector: "input[name=city]", payload: "Springfield"} -{action: "select", selector: "select[name=state]", payload: "IL"} -{action: "type", selector: "input[name=zip]", payload: "62701"} -{action: "click", selector: "button.next"} - -// Wait for step 3 -{action: "await_element", selector: "input[name=cardNumber]"} - -// Step 3: Payment -{action: "type", selector: "input[name=cardNumber]", payload: "4111111111111111"} -{action: "select", selector: "select[name=expMonth]", payload: "12"} -{action: "select", selector: "select[name=expYear]", payload: "2028"} -{action: "type", selector: "input[name=cvv]", payload: "123"} - -// Review before submit -{action: "click", selector: "button.review"} -{action: "await_element", selector: ".summary"} - -// Extract confirmation -{action: "extract", payload: "markdown", selector: ".summary"} -``` - -### Search with Multiple Filters - -Use dropdowns, checkboxes, and text inputs: - -```json -{action: "navigate", payload: "https://library.example.com/search"} -{action: "await_element", selector: "form.search"} - -// Category dropdown -{action: "select", selector: "select[name=category]", payload: "books"} - -// Price range -{action: "type", selector: "input[name=priceMin]", payload: "10"} -{action: "type", selector: "input[name=priceMax]", payload: "50"} - -// Checkboxes via JavaScript -{action: "eval", payload: "document.querySelector('input[name=inStock]').checked = true"} -{action: "eval", payload: "document.querySelector('input[name=freeShipping]').checked = true"} - -// Search term and submit -{action: "type", selector: "input[name=query]", payload: "chrome devtools\n"} - -// Wait for results -{action: "await_element", selector: ".results"} - -// Count and extract -{action: "eval", payload: "document.querySelectorAll('.result').length"} -{action: "extract", payload: "text", selector: ".result-count"} -``` - -### File Upload - -Handle file input using JavaScript: - -```json -{action: "navigate", payload: "https://example.com/upload"} -{action: "await_element", selector: "input[type=file]"} - -// Read file and set via JavaScript (for testing) -{action: "eval", payload: ` - const fileInput = document.querySelector('input[type=file]'); - const dataTransfer = new DataTransfer(); - const file = new File(['test content'], 'test.txt', { type: 'text/plain' }); - dataTransfer.items.add(file); - fileInput.files = dataTransfer.files; -`} - -// Submit -{action: "click", selector: "button.upload"} -{action: "await_text", payload: "Upload complete"} -``` - ---- - -## Data Extraction - -### Article Scraping - -Extract blog post with metadata: - -```json -{action: "navigate", payload: "https://blog.example.com/article"} -{action: "await_element", selector: "article"} - -// Extract complete article structure -{action: "eval", payload: ` - ({ - title: document.querySelector('article h1').textContent.trim(), - author: document.querySelector('.author-name').textContent.trim(), - date: document.querySelector('time').getAttribute('datetime'), - tags: Array.from(document.querySelectorAll('.tag')).map(t => t.textContent.trim()), - content: document.querySelector('article .content').textContent.trim(), - images: Array.from(document.querySelectorAll('article img')).map(img => ({ - src: img.src, - alt: img.alt - })), - links: Array.from(document.querySelectorAll('article a')).map(a => ({ - text: a.textContent.trim(), - href: a.href - })) - }) -`} -``` - -### Table Data Extraction - -Convert HTML table to structured JSON: - -```json -{action: "navigate", payload: "https://example.com/data/table"} -{action: "await_element", selector: "table"} - -// Extract table with headers -{action: "eval", payload: ` - (() => { - const headers = Array.from(document.querySelectorAll('table thead th')) - .map(th => th.textContent.trim()); - const rows = Array.from(document.querySelectorAll('table tbody tr')) - .map(tr => { - const cells = Array.from(tr.cells).map(td => td.textContent.trim()); - return Object.fromEntries(headers.map((h, i) => [h, cells[i]])); - }); - return rows; - })() -`} -``` - -### Paginated Results - -Extract data across multiple pages: - -```json -{action: "navigate", payload: "https://example.com/results?page=1"} -{action: "await_element", selector: ".results"} - -// Page 1 -{action: "eval", payload: "Array.from(document.querySelectorAll('.result')).map(r => r.textContent.trim())"} - -// Navigate to page 2 -{action: "click", selector: "a.next-page"} -{action: "await_element", selector: ".results"} -{action: "await_text", payload: "Page 2"} - -// Page 2 -{action: "eval", payload: "Array.from(document.querySelectorAll('.result')).map(r => r.textContent.trim())"} - -// Continue pattern for additional pages... -``` - ---- - -## Multi-Tab Operations - -### Email Receipt Extraction - -Find specific email and extract data: - -```json -// List available tabs -{action: "list_tabs"} - -// Switch to email tab (assume index 2 from list) -{action: "click", tab_index: 2, selector: "a[title*='Receipt']"} -{action: "await_element", tab_index: 2, selector: ".email-body"} - -// Extract receipt details -{action: "extract", tab_index: 2, payload: "text", selector: ".order-number"} -{action: "extract", tab_index: 2, payload: "text", selector: ".total-amount"} -{action: "extract", tab_index: 2, payload: "markdown", selector: ".items-list"} -``` - -### Cross-Site Data Correlation - -Extract from one site, verify on another: - -```json -// Get company phone from website -{action: "navigate", payload: "https://company.com/contact"} -{action: "await_element", selector: ".contact-info"} -{action: "extract", payload: "text", selector: ".phone-number"} - -// Store result: "+1-555-0123" - -// Open verification site in new tab -{action: "new_tab"} -{action: "navigate", tab_index: 1, payload: "https://phonevalidator.com"} -{action: "await_element", tab_index: 1, selector: "input[name=phone]"} - -// Fill and search -{action: "type", tab_index: 1, selector: "input[name=phone]", payload: "+1-555-0123\n"} -{action: "await_element", tab_index: 1, selector: ".results"} - -// Extract validation result -{action: "extract", tab_index: 1, payload: "text", selector: ".verification-status"} -``` - -### Parallel Data Collection - -Collect data from multiple sources simultaneously: - -```json -// Tab 0: Weather -{action: "navigate", tab_index: 0, payload: "https://weather.com/city"} -{action: "await_element", tab_index: 0, selector: ".temperature"} - -// Tab 1: News -{action: "new_tab"} -{action: "navigate", tab_index: 1, payload: "https://news.com"} -{action: "await_element", tab_index: 1, selector: ".headlines"} - -// Tab 2: Stock prices -{action: "new_tab"} -{action: "navigate", tab_index: 2, payload: "https://stocks.com"} -{action: "await_element", tab_index: 2, selector: ".market-summary"} - -// Extract all data -{action: "extract", tab_index: 0, payload: "text", selector: ".temperature"} -{action: "extract", tab_index: 1, payload: "text", selector: ".headline:first-child"} -{action: "extract", tab_index: 2, payload: "text", selector: ".market-summary"} -``` - ---- - -## Dynamic Content Handling - -### Infinite Scroll Loading - -Load all content with scroll-triggered pagination: - -```json -{action: "navigate", payload: "https://example.com/feed"} -{action: "await_element", selector: ".feed-item"} - -// Count initial items -{action: "eval", payload: "document.querySelectorAll('.feed-item').length"} - -// Scroll and wait multiple times -{action: "eval", payload: "window.scrollTo(0, document.body.scrollHeight)"} -{action: "eval", payload: "new Promise(r => setTimeout(r, 2000))"} - -{action: "eval", payload: "window.scrollTo(0, document.body.scrollHeight)"} -{action: "eval", payload: "new Promise(r => setTimeout(r, 2000))"} - -{action: "eval", payload: "window.scrollTo(0, document.body.scrollHeight)"} -{action: "eval", payload: "new Promise(r => setTimeout(r, 2000))"} - -// Extract all loaded items -{action: "eval", payload: ` - Array.from(document.querySelectorAll('.feed-item')).map(item => ({ - title: item.querySelector('.title').textContent.trim(), - date: item.querySelector('.date').textContent.trim(), - url: item.querySelector('a').href - })) -`} -``` - -### Wait for AJAX Response - -Wait for loading indicator to disappear: - -```json -{action: "navigate", payload: "https://app.com/dashboard"} -{action: "await_element", selector: ".content"} - -// Trigger AJAX request -{action: "click", selector: "button.load-data"} - -// Wait for spinner to appear then disappear -{action: "eval", payload: ` - new Promise(resolve => { - const checkGone = () => { - const spinner = document.querySelector('.spinner'); - if (!spinner || spinner.style.display === 'none') { - resolve(true); - } else { - setTimeout(checkGone, 100); - } - }; - checkGone(); - }) -`} - -// Now safe to extract -{action: "extract", payload: "text", selector: ".data-table"} -``` - -### Modal Dialog Handling - -Open modal, interact, wait for close: - -```json -{action: "click", selector: "button.open-settings"} -{action: "await_element", selector: ".modal.visible"} - -// Interact with modal -{action: "type", selector: ".modal input[name=username]", payload: "newuser"} -{action: "select", selector: ".modal select[name=theme]", payload: "dark"} -{action: "click", selector: ".modal button.save"} - -// Wait for modal to close -{action: "eval", payload: ` - new Promise(resolve => { - const check = () => { - const modal = document.querySelector('.modal.visible'); - if (!modal) { - resolve(true); - } else { - setTimeout(check, 100); - } - }; - check(); - }) -`} - -// Verify settings saved -{action: "await_text", payload: "Settings saved"} -``` - -### Wait for Button Enabled - -Wait for form validation before submission: - -```json -{action: "type", selector: "input[name=email]", payload: "user@example.com"} -{action: "type", selector: "input[name=password]", payload: "securepass123"} - -// Wait for submit button to become enabled -{action: "eval", payload: ` - new Promise(resolve => { - const check = () => { - const btn = document.querySelector('button[type=submit]'); - if (btn && !btn.disabled && !btn.classList.contains('disabled')) { - resolve(true); - } else { - setTimeout(check, 100); - } - }; - check(); - }) -`} - -// Now safe to click -{action: "click", selector: "button[type=submit]"} -``` - ---- - -## Authentication Workflows - -### Standard Login - -```json -{action: "navigate", payload: "https://app.example.com/login"} -{action: "await_element", selector: "form.login"} - -// Fill credentials -{action: "type", selector: "input[name=email]", payload: "user@example.com"} -{action: "type", selector: "input[name=password]", payload: "password123\n"} - -// Wait for redirect -{action: "await_text", payload: "Dashboard"} - -// Verify logged in -{action: "extract", payload: "text", selector: ".user-name"} -``` - -### OAuth Flow - -```json -{action: "navigate", payload: "https://app.example.com/connect"} -{action: "await_element", selector: "button.oauth-login"} - -// Trigger OAuth -{action: "click", selector: "button.oauth-login"} - -// Wait for OAuth provider page -{action: "await_text", payload: "Authorize"} - -// Fill OAuth credentials -{action: "await_element", selector: "input[name=username]"} -{action: "type", selector: "input[name=username]", payload: "oauthuser"} -{action: "type", selector: "input[name=password]", payload: "oauthpass\n"} - -// Wait for redirect back -{action: "await_text", payload: "Connected successfully"} -``` - -### Session Persistence Check - -```json -// Load page -{action: "navigate", payload: "https://app.example.com/dashboard"} -{action: "await_element", selector: "body"} - -// Check if logged in via cookie/localStorage -{action: "eval", payload: "document.cookie.includes('session_id')"} -{action: "eval", payload: "localStorage.getItem('auth_token') !== null"} - -// Verify user data loaded -{action: "extract", payload: "text", selector: ".user-profile"} -``` - ---- - -## Advanced Patterns - -### Conditional Workflow - -Branch based on page content: - -```json -{action: "navigate", payload: "https://example.com/status"} -{action: "await_element", selector: "body"} - -// Check status -{action: "extract", payload: "text", selector: ".status-message"} - -// If result contains "Available": -{action: "click", selector: "button.purchase"} -{action: "await_text", payload: "Added to cart"} - -// If result contains "Out of stock": -{action: "click", selector: "button.notify-me"} -{action: "type", selector: "input[name=email]", payload: "notify@example.com\n"} -``` - -### Error Recovery - -Handle and retry failed operations: - -```json -{action: "navigate", payload: "https://app.example.com/data"} -{action: "await_element", selector: ".content"} - -// Attempt operation -{action: "click", selector: "button.load"} - -// Check for error -{action: "eval", payload: "!!document.querySelector('.error-message')"} - -// If error present, retry -{action: "click", selector: "button.retry"} -{action: "await_element", selector: ".data-loaded"} -``` - -### Screenshot Comparison - -Capture before and after states: - -```json -// Initial state -{action: "navigate", payload: "https://example.com"} -{action: "await_element", selector: ".content"} -{action: "screenshot", payload: "/tmp/before.png"} - -// Make changes -{action: "click", selector: "button.dark-mode"} -{action: "await_element", selector: "body.dark"} - -// Capture new state -{action: "screenshot", payload: "/tmp/after.png"} - -// Or screenshot specific element -{action: "screenshot", payload: "/tmp/header.png", selector: "header"} -``` - ---- - -## Tips for Complex Workflows - -### Build Incrementally - -Start simple, add complexity: - -1. Navigate and verify page loads -2. Extract one element -3. Add interaction -4. Add waiting logic -5. Add error handling -6. Add validation - -### Use JavaScript for Complex Logic - -When multiple operations needed, use `eval`: - -```json -{action: "eval", payload: ` - (async () => { - // Complex multi-step logic - const results = []; - const items = document.querySelectorAll('.item'); - - for (const item of items) { - if (item.classList.contains('active')) { - results.push({ - id: item.dataset.id, - text: item.textContent.trim() - }); - } - } - - return results; - })() -`} -``` - -### Validate Selectors - -Always test selectors return expected elements: - -```json -// Check element exists -{action: "eval", payload: "!!document.querySelector('button.submit')"} - -// Check element visible -{action: "eval", payload: "window.getComputedStyle(document.querySelector('button.submit')).display !== 'none'"} - -// Check element count -{action: "eval", payload: "document.querySelectorAll('.item').length"} -``` diff --git a/shared/linked-dotfiles/opencode/skills/browser-automation/references/troubleshooting.md b/shared/linked-dotfiles/opencode/skills/browser-automation/references/troubleshooting.md deleted file mode 100644 index 5f14ab1..0000000 --- a/shared/linked-dotfiles/opencode/skills/browser-automation/references/troubleshooting.md +++ /dev/null @@ -1,546 +0,0 @@ -# Browser Automation Troubleshooting Guide - -Quick reference for common issues and solutions. - -## Common Errors - -### Element Not Found - -**Error:** `Element not found: button.submit` - -**Causes:** -1. Page still loading -2. Wrong selector -3. Element in iframe -4. Element hidden/not rendered - -**Solutions:** - -```json -// 1. Add wait before interaction -{action: "await_element", selector: "button.submit", timeout: 10000} -{action: "click", selector: "button.submit"} - -// 2. Verify selector exists -{action: "extract", payload: "html"} -{action: "eval", payload: "document.querySelector('button.submit')"} - -// 3. Check if in iframe -{action: "eval", payload: "document.querySelectorAll('iframe').length"} - -// 4. Check visibility -{action: "eval", payload: "window.getComputedStyle(document.querySelector('button.submit')).display"} -``` - -### Timeout Errors - -**Error:** `Timeout waiting for element after 5000ms` - -**Solutions:** - -```json -// Increase timeout for slow pages -{action: "await_element", selector: ".content", timeout: 30000} - -// Wait for loading to complete first -{action: "await_element", selector: ".spinner"} -{action: "eval", payload: ` - new Promise(r => { - const check = () => { - if (!document.querySelector('.spinner')) r(true); - else setTimeout(check, 100); - }; - check(); - }) -`} - -// Use JavaScript to wait for specific condition -{action: "eval", payload: ` - new Promise(resolve => { - const observer = new MutationObserver(() => { - if (document.querySelector('.loaded')) { - observer.disconnect(); - resolve(true); - } - }); - observer.observe(document.body, { childList: true, subtree: true }); - }) -`} -``` - -### Click Not Working - -**Error:** Click executes but nothing happens - -**Causes:** -1. JavaScript event handler not attached yet -2. Element covered by another element -3. Need to scroll element into view - -**Solutions:** - -```json -// 1. Wait longer before click -{action: "await_element", selector: "button"} -{action: "eval", payload: "new Promise(r => setTimeout(r, 1000))"} -{action: "click", selector: "button"} - -// 2. Check z-index and overlays -{action: "eval", payload: ` - (() => { - const elem = document.querySelector('button'); - const rect = elem.getBoundingClientRect(); - const topElem = document.elementFromPoint(rect.left + rect.width/2, rect.top + rect.height/2); - return topElem === elem || elem.contains(topElem); - })() -`} - -// 3. Scroll into view first -{action: "eval", payload: "document.querySelector('button').scrollIntoView()"} -{action: "click", selector: "button"} - -// 4. Force click via JavaScript -{action: "eval", payload: "document.querySelector('button').click()"} -``` - -### Form Submission Issues - -**Error:** Form doesn't submit with `\n` - -**Solutions:** - -```json -// Try explicit click -{action: "type", selector: "input[name=password]", payload: "pass123"} -{action: "click", selector: "button[type=submit]"} - -// Or trigger form submit -{action: "eval", payload: "document.querySelector('form').submit()"} - -// Or press Enter key specifically -{action: "eval", payload: ` - const input = document.querySelector('input[name=password]'); - input.dispatchEvent(new KeyboardEvent('keypress', { key: 'Enter', keyCode: 13 })); -`} -``` - -### Tab Index Errors - -**Error:** `Tab index 2 out of range` - -**Cause:** Tab closed or indices shifted - -**Solution:** - -```json -// Always list tabs before operating on them -{action: "list_tabs"} - -// After closing tabs, re-list -{action: "close_tab", tab_index: 1} -{action: "list_tabs"} -{action: "click", tab_index: 1, selector: "a"} // Now correct index -``` - -### Extract Returns Empty - -**Error:** Extract returns empty string - -**Causes:** -1. Element not loaded yet -2. Content in shadow DOM -3. Text in ::before/::after pseudo-elements - -**Solutions:** - -```json -// 1. Wait for content -{action: "await_element", selector: ".content"} -{action: "await_text", payload: "Expected text"} -{action: "extract", payload: "text", selector: ".content"} - -// 2. Check shadow DOM -{action: "eval", payload: "document.querySelector('my-component').shadowRoot.querySelector('.content').textContent"} - -// 3. Get computed styles for pseudo-elements -{action: "eval", payload: "window.getComputedStyle(document.querySelector('.content'), '::before').content"} -``` - ---- - -## Best Practices - -### Selector Specificity - -**Use ID when available:** -```json -{action: "click", selector: "#submit-button"} // ✅ Best -{action: "click", selector: "button.submit"} // ✅ Good -{action: "click", selector: "button"} // ❌ Too generic -``` - -**Combine selectors for uniqueness:** -```json -{action: "click", selector: "form.login button[type=submit]"} // ✅ Specific -{action: "click", selector: ".modal.active button.primary"} // ✅ Specific -``` - -**Use data attributes:** -```json -{action: "click", selector: "[data-testid='submit-btn']"} // ✅ Reliable -{action: "click", selector: "[data-action='save']"} // ✅ Semantic -``` - -### Waiting Strategy - -**Always wait before interaction:** -```json -// ❌ BAD - No waiting -{action: "navigate", payload: "https://example.com"} -{action: "click", selector: "button"} - -// ✅ GOOD - Wait for element -{action: "navigate", payload: "https://example.com"} -{action: "await_element", selector: "button"} -{action: "click", selector: "button"} - -// ✅ BETTER - Wait for specific state -{action: "navigate", payload: "https://example.com"} -{action: "await_text", payload: "Page loaded"} -{action: "click", selector: "button"} -``` - -**Wait for dynamic content:** -```json -// After triggering AJAX -{action: "click", selector: "button.load-more"} -{action: "await_element", selector: ".new-content"} - -// After form submit -{action: "click", selector: "button[type=submit]"} -{action: "await_text", payload: "Success"} -``` - -### Error Detection - -**Check for error messages:** -```json -{action: "click", selector: "button.submit"} -{action: "eval", payload: "!!document.querySelector('.error-message')"} -{action: "extract", payload: "text", selector: ".error-message"} -``` - -**Validate expected state:** -```json -{action: "click", selector: "button.add-to-cart"} -{action: "await_element", selector: ".cart-count"} -{action: "extract", payload: "text", selector: ".cart-count"} -// Verify count increased -``` - -### Data Extraction Efficiency - -**Use single eval for multiple fields:** -```json -// ❌ Inefficient - Multiple calls -{action: "extract", payload: "text", selector: "h1"} -{action: "extract", payload: "text", selector: ".author"} -{action: "extract", payload: "text", selector: ".date"} - -// ✅ Efficient - One call -{action: "eval", payload: ` - ({ - title: document.querySelector('h1').textContent.trim(), - author: document.querySelector('.author').textContent.trim(), - date: document.querySelector('.date').textContent.trim() - }) -`} -``` - -**Extract arrays efficiently:** -```json -{action: "eval", payload: ` - Array.from(document.querySelectorAll('.item')).map(item => ({ - name: item.querySelector('.name').textContent.trim(), - price: item.querySelector('.price').textContent.trim(), - url: item.querySelector('a').href - })) -`} -``` - -### Performance Optimization - -**Minimize navigation:** -```json -// ❌ Slow - Navigate for each item -{action: "navigate", payload: "https://example.com/item/1"} -{action: "extract", payload: "text", selector: ".price"} -{action: "navigate", payload: "https://example.com/item/2"} -{action: "extract", payload: "text", selector: ".price"} - -// ✅ Fast - Use API or extract list page -{action: "navigate", payload: "https://example.com/items"} -{action: "eval", payload: "Array.from(document.querySelectorAll('.item')).map(i => i.querySelector('.price').textContent)"} -``` - -**Reuse tabs:** -```json -// ✅ Keep tabs open for repeated access -{action: "new_tab"} -{action: "navigate", tab_index: 1, payload: "https://tool.com"} - -// Later, reuse same tab -{action: "click", tab_index: 1, selector: "button.refresh"} -``` - -### Debugging Workflows - -**Step 1: Check page HTML:** -```json -{action: "navigate", payload: "https://example.com"} -{action: "await_element", selector: "body"} -{action: "extract", payload: "html"} -``` - -**Step 2: Test selectors:** -```json -{action: "eval", payload: "document.querySelector('button.submit')"} -{action: "eval", payload: "document.querySelectorAll('button').length"} -``` - -**Step 3: Check element state:** -```json -{action: "eval", payload: ` - (() => { - const elem = document.querySelector('button.submit'); - return { - exists: !!elem, - visible: elem ? window.getComputedStyle(elem).display !== 'none' : false, - enabled: elem ? !elem.disabled : false, - text: elem ? elem.textContent : null - }; - })() -`} -``` - -**Step 4: Check console errors:** -```json -{action: "eval", payload: "console.error.toString()"} -``` - ---- - -## Patterns Library - -### Retry Logic - -```json -// Attempt operation with retry -{action: "click", selector: "button.submit"} - -// Check if succeeded -{action: "eval", payload: "document.querySelector('.success-message')"} - -// If null, retry -{action: "click", selector: "button.submit"} -{action: "await_text", payload: "Success", timeout: 10000} -``` - -### Conditional Branching - -```json -// Check condition -{action: "extract", payload: "text", selector: ".status"} - -// Branch based on result (in your logic) -// If "available": -{action: "click", selector: "button.buy"} - -// If "out of stock": -{action: "type", selector: "input.email", payload: "notify@example.com\n"} -``` - -### Pagination Handling - -```json -// Page 1 -{action: "navigate", payload: "https://example.com/results"} -{action: "await_element", selector: ".results"} -{action: "eval", payload: "Array.from(document.querySelectorAll('.result')).map(r => r.textContent)"} - -// Check if next page exists -{action: "eval", payload: "!!document.querySelector('a.next-page')"} - -// If yes, navigate -{action: "click", selector: "a.next-page"} -{action: "await_element", selector: ".results"} -// Repeat extraction -``` - -### Form Validation Waiting - -```json -// Fill form field -{action: "type", selector: "input[name=email]", payload: "user@example.com"} - -// Wait for validation icon -{action: "await_element", selector: "input[name=email] + .valid-icon"} - -// Proceed to next field -{action: "type", selector: "input[name=password]", payload: "password123"} -``` - -### Autocomplete Selection - -```json -// Type in autocomplete field -{action: "type", selector: "input.autocomplete", payload: "San Fr"} - -// Wait for suggestions -{action: "await_element", selector: ".autocomplete-suggestions"} - -// Click suggestion -{action: "click", selector: ".autocomplete-suggestions li:first-child"} - -// Verify selection -{action: "extract", payload: "text", selector: "input.autocomplete"} -``` - -### Cookie Management - -```json -// Check if cookie exists -{action: "eval", payload: "document.cookie.includes('session_id')"} - -// Set cookie -{action: "eval", payload: "document.cookie = 'preferences=dark; path=/; max-age=31536000'"} - -// Clear specific cookie -{action: "eval", payload: "document.cookie = 'session_id=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'"} - -// Get all cookies as object -{action: "eval", payload: ` - Object.fromEntries( - document.cookie.split('; ').map(c => c.split('=')) - ) -`} -``` - ---- - -## XPath Examples - -XPath is auto-detected (starts with `/` or `//`). - -### Basic XPath Selectors - -```json -// Find by text content -{action: "click", selector: "//button[text()='Submit']"} -{action: "click", selector: "//a[contains(text(), 'Learn more')]"} - -// Find by attribute -{action: "click", selector: "//button[@type='submit']"} -{action: "extract", payload: "text", selector: "//div[@class='content']"} - -// Hierarchical -{action: "click", selector: "//form[@id='login']//button[@type='submit']"} -{action: "extract", payload: "text", selector: "//article/div[@class='content']/p[1]"} -``` - -### Advanced XPath - -```json -// Multiple conditions -{action: "click", selector: "//button[@type='submit' and contains(@class, 'primary')]"} - -// Following sibling -{action: "extract", payload: "text", selector: "//label[text()='Username']/following-sibling::input/@value"} - -// Parent selection -{action: "click", selector: "//td[text()='Active']/..//button[@class='edit']"} - -// Multiple elements -{action: "extract", payload: "text", selector: "//h2 | //h3"} -``` - ---- - -## Security Considerations - -### Avoid Hardcoded Credentials - -```json -// ❌ BAD - Credentials in workflow -{action: "type", selector: "input[name=password]", payload: "mypassword123"} - -// ✅ GOOD - Use environment variables or secure storage -// Load credentials from secure source before workflow -``` - -### Validate HTTPS - -```json -// Check protocol -{action: "eval", payload: "window.location.protocol"} -// Should return "https:" -``` - -### Check for Security Indicators - -```json -// Verify login page is secure -{action: "eval", payload: ` - ({ - protocol: window.location.protocol, - hasLock: document.querySelector('link[rel=icon]')?.href.includes('secure'), - url: window.location.href - }) -`} -``` - ---- - -## Performance Tips - -### Minimize Waits - -```json -// ❌ Arbitrary timeouts -{action: "eval", payload: "new Promise(r => setTimeout(r, 5000))"} - -// ✅ Condition-based waits -{action: "await_element", selector: ".loaded"} -``` - -### Batch Operations - -```json -// ❌ Individual extracts -{action: "extract", payload: "text", selector: ".title"} -{action: "extract", payload: "text", selector: ".author"} -{action: "extract", payload: "text", selector: ".date"} - -// ✅ Single eval -{action: "eval", payload: ` - ({ - title: document.querySelector('.title').textContent, - author: document.querySelector('.author').textContent, - date: document.querySelector('.date').textContent - }) -`} -``` - -### Reuse Browser State - -```json -// ✅ Stay logged in across operations -{action: "navigate", payload: "https://app.com/login"} -// ... login ... - -{action: "navigate", payload: "https://app.com/page1"} -// ... work ... - -{action: "navigate", payload: "https://app.com/page2"} -// ... work ... (still logged in) -``` diff --git a/shared/linked-dotfiles/opencode/skills/research-medical/SKILL.md b/shared/linked-dotfiles/opencode/skills/research-medical/SKILL.md new file mode 100644 index 0000000..6941c49 --- /dev/null +++ b/shared/linked-dotfiles/opencode/skills/research-medical/SKILL.md @@ -0,0 +1,384 @@ +--- +name: research-medical +description: Use when researching medical/scientific topics and encountering paywalled journals, access failures, or needing primary sources - provides strategies for PubMed Central, DOI resolution, preprint servers, and fallback approaches for medical literature access +--- + +# Medical Research Access + +Strategies for accessing medical and scientific literature when direct web access fails or sources are paywalled. + +## When to Use This Skill + +**Use when:** +- Webfetch fails on medical journal sites (NIH, Nature, JAMA, NEJM, etc.) +- Research requires primary sources (studies, trials, systematic reviews) +- Need to access paywalled medical literature +- Looking for recent research not yet peer-reviewed +- Extracting citations when full text unavailable + +**When NOT to use:** +- General web research (use standard webfetch) +- Medical journalism is sufficient (STAT News, MedPage Today) +- Topic well-covered in open-access sources + +## Quick Reference: Access Strategies + +| Source Type | Primary Method | Fallback | Notes | +|-------------|---------------|----------|-------| +| Peer-reviewed studies | PubMed Central | DOI resolution, preprints | Always check PMC first | +| Recent research | Preprint servers | Author websites | May not be peer-reviewed yet | +| Clinical trials | ClinicalTrials.gov | Trial registries | Protocol vs results | +| Meta-analyses | Cochrane Library | PubMed search | Often open access | +| Medical journalism | STAT News, Medscape | Press releases | Secondary sources | +| Guidelines | Professional societies | NIH, CDC | Usually open access | + +## Access Strategies + +### 1. PubMed Central (PMC) - First Stop + +**Why PMC:** Free full-text archive of biomedical literature, subset of PubMed with actual paper content. + +**Search approaches:** + +```markdown +# Direct PMC search +https://www.ncbi.nlm.nih.gov/pmc/?term=ivermectin+COVID-19+randomized+controlled+trial + +# Filters to add: +- Free full text +- Article type (Clinical Trial, Meta-Analysis, Systematic Review) +- Publication date range +``` + +**When webfetch fails on PMC:** +- Try PubMed instead (has abstracts even without full text) +- Search by PMID if you have it: `https://pubmed.ncbi.nlm.nih.gov/[PMID]/` +- Look for "Free PMC article" badge in PubMed results + +### 2. DOI Resolution Services + +**What is DOI:** Digital Object Identifier - permanent link to research papers. + +**Primary resolver:** +```markdown +https://doi.org/[DOI-HERE] + +Example: https://doi.org/10.1056/NEJMoa2115869 +``` + +**When DOI resolution hits paywall:** +- Try adding DOI to Google Scholar: `https://scholar.google.com/scholar?q=[DOI]` +- Check for "All versions" link in Scholar (may include preprint or author PDF) +- Look for institutional repository versions + +**Extracting DOI from citations:** +- Usually in format: `10.XXXX/journal.year.number` +- Found at end of citation or in URL +- Can search PubMed by DOI to get PMID + +### 3. Preprint Servers - Recent Research + +**Primary servers:** + +| Server | Focus | URL Pattern | +|--------|-------|-------------| +| medRxiv | Medicine | `https://www.medrxiv.org/content/[ID]` | +| bioRxiv | Biology | `https://www.biorxiv.org/content/[ID]` | +| SSRN | Social science, econ | `https://papers.ssrn.com/sol3/papers.cfm?abstract_id=[ID]` | + +**Important notes:** +- Preprints are NOT peer-reviewed +- Always note preprint status in citations +- Check if preprint later published in journal (search by title) +- Good for very recent research (last 6-12 months) + +**Search strategy:** +```markdown +# Search medRxiv directly +https://www.medrxiv.org/search/ivermectin%20COVID-19 + +# Or use Google Scholar with "preprint" filter +site:medrxiv.org OR site:biorxiv.org [search terms] +``` + +### 4. Medical Journalism Without Paywalls + +**Freely accessible sources:** + +- **STAT News** (`statnews.com`) - High-quality medical journalism, no paywall +- **Medscape** (`medscape.com`) - Free with registration, clinical news +- **The Conversation** (`theconversation.com`) - Academic experts, open access +- **Science Daily** (`sciencedaily.com`) - Press releases and summaries +- **NIH News** (`nih.gov/news-events`) - Government research announcements + +**When to use journalism vs primary sources:** +- Journalism: Background, context, expert opinions, controversy overview +- Primary sources: Specific claims, data, methodology, for fact-checking + +**Citation approach:** +- Use journalism to identify key studies +- Track down primary sources for verification +- Cite primary source when making factual claims +- Cite journalism when discussing expert opinions or controversy + +### 5. Clinical Trial Registries + +**ClinicalTrials.gov** - Official US registry: +```markdown +https://clinicaltrials.gov/search?term=[drug/intervention] + +Filter by: +- Study Status (Completed, Published) +- Study Type (Interventional) +- Study Results (Studies with Results) +``` + +**What you get:** +- Trial protocol and design +- Primary/secondary outcomes +- Results summary (if published) +- Links to published papers + +**Other registries:** +- WHO ICTRP (international): `https://trialsearch.who.int/` +- EU Clinical Trials Register: `https://www.clinicaltrialsregister.eu/` + +### 6. Professional Society Guidelines + +**Often open access:** +- CDC guidelines: `cdc.gov` +- WHO guidelines: `who.int` +- NIH treatment guidelines: `covid19treatmentguidelines.nih.gov` +- Professional societies (AMA, ACP, IDSA) - check guidelines sections + +**Good for:** +- Official recommendations +- Evidence summaries +- Standard of care +- Consensus positions + +### 7. Google Scholar Strategies + +**When direct access fails:** + +```markdown +# Search with specific terms +"ivermectin" "COVID-19" "randomized controlled trial" + +# Use "All versions" link to find: +- Preprint versions +- Author PDFs +- Institutional repository copies + +# Filter by date range for recent research +``` + +**Citation extraction when full text unavailable:** +- Scholar provides formatted citations +- Shows "Cited by" count (impact indicator) +- Links to related articles +- May show abstract even without full text + +### 8. Author Websites and ResearchGate + +**When other methods fail:** + +- Search author name + paper title +- Check university faculty pages (often have PDFs) +- ResearchGate (`researchgate.net`) - researchers share papers +- Academia.edu - similar to ResearchGate + +**Caution:** +- Verify version matches published paper +- Note if it's a preprint or draft +- Check publication date + +## Webfetch vs Direct Access Decision Tree + +**Use webfetch when:** +- Source is known to be open access +- Medical journalism sites (STAT, Medscape) +- Government sites (NIH, CDC, FDA) +- Preprint servers +- Professional society guidelines + +**Skip webfetch, use search strategies when:** +- Major journal sites (Nature, JAMA, NEJM, Lancet) - usually paywalled +- You have DOI - use DOI resolver or Scholar +- Need recent research - go to preprint servers +- Previous webfetch attempts failed on similar sources + +**General agent with web search when:** +- Exploratory research (don't know specific sources yet) +- Need to identify key studies first +- Looking for expert commentary or summaries +- Building initial source list + +## Citation Extraction Techniques + +### When Full Text Unavailable + +**From abstracts (PubMed):** +- Study design and methods +- Primary outcomes +- Sample size +- Key findings (usually in abstract) +- Limitations (sometimes mentioned) + +**From press releases:** +- High-level findings +- Author quotes +- Institution and funding +- Link to actual paper (follow this) + +**From systematic reviews/meta-analyses:** +- Summary of multiple studies +- Effect sizes across studies +- Quality assessments +- Usually cite all included studies (mine these) + +### Citation Format Best Practices + +**Minimum required:** +```markdown +Author(s). Title. Journal Year;Volume(Issue):Pages. DOI: [DOI] +``` + +**Enhanced format:** +```markdown +Author(s). Title. Journal Year;Volume(Issue):Pages. DOI: [DOI]. PMID: [PMID]. [Access status] + +Example: +Lopez-Medina E, et al. Effect of Ivermectin on Time to Resolution of Symptoms Among Adults With Mild COVID-19. JAMA 2021;325(14):1426-1435. DOI: 10.1001/jama.2021.3071. PMID: 33662102. [Free full text via PMC] +``` + +**Always include:** +- DOI (for verification and access) +- PMID if available (PubMed tracking) +- Access status (open access, PMC free, paywalled) +- Preprint status if applicable + +## Common Mistakes + +### ❌ Giving up after first webfetch failure + +**Problem:** Many medical sites block automated access or require authentication. + +**Fix:** Use systematic fallback strategy: +1. Try PubMed Central search +2. Use DOI resolver if you have DOI +3. Check preprint servers +4. Search Google Scholar for alternative versions +5. Use medical journalism to identify sources, then track down primary + +### ❌ Citing journalism when primary source is accessible + +**Problem:** Secondary source citation when primary is available weakens credibility. + +**Fix:** +- Use journalism to find studies +- Always attempt to access primary source +- Cite primary source for factual claims +- Cite journalism only for expert opinions or controversy framing + +### ❌ Not noting preprint vs peer-reviewed status + +**Problem:** Preprints lack peer review and may contain errors or later be contradicted. + +**Fix:** +- Always check publication status +- Note in citation: "[Preprint, not peer-reviewed]" +- Search by title to see if later published in journal +- Weight peer-reviewed sources more heavily + +### ❌ Ignoring "Cited by" counts and publication dates + +**Problem:** May miss that study was retracted, contradicted, or superseded. + +**Fix:** +- Check "Cited by" in Google Scholar +- Look for retractions or corrections +- Check for more recent systematic reviews +- Note if study is outlier vs consensus + +### ❌ Using only abstracts for detailed claims + +**Problem:** Abstracts omit important limitations, methods details, and context. + +**Fix:** +- Use abstract for high-level findings only +- For specific claims, need full text +- If full text unavailable, note limitation: "[Based on abstract only]" +- Look for systematic reviews that analyzed full text + +### ❌ Not tracking access failures for optimization + +**Problem:** Repeated failures on same source types waste time. + +**Fix:** +- Note which sources consistently fail webfetch +- Build project-specific access strategy +- Document successful access patterns +- Update AGENTS.md with project-specific guidance + +## Real-World Impact + +**Session context:** Pierre Kory/ivermectin research encountered 4 failed web access attempts (NIH, FDA, Nature, JAMA, MedPage Today), limiting source diversity to 2 primary sources. + +**With this skill:** +- PubMed Central search would provide free full-text access to key trials +- DOI resolution would access Nature and JAMA papers via alternative routes +- Preprint servers would surface early ivermectin research +- Medical journalism (STAT News) would provide controversy context without paywall +- Clinical trial registries would provide TOGETHER trial and other RCT data + +**Expected improvement:** 5-8 accessible sources instead of 2, with mix of primary sources and expert commentary. + +## Workflow Integration + +### Research Session Startup + +1. **Identify topic and key terms** +2. **Start with PubMed Central search** (free full text) +3. **Check for systematic reviews** (summarize evidence) +4. **Search preprint servers** (recent research) +5. **Use medical journalism** (context and expert opinions) +6. **Track citations** (DOI, PMID, access status) + +### When Webfetch Fails + +1. **Don't retry same URL** - move to fallback strategy +2. **Extract DOI from citation** - use DOI resolver +3. **Search PubMed by title** - may find PMC version +4. **Check Google Scholar** - look for "All versions" +5. **Note failure** - document for future optimization + +### Citation Verification + +1. **Have DOI or PMID** - verify via PubMed lookup +2. **Check publication status** - preprint vs peer-reviewed +3. **Look for retractions** - search "[title] retraction" +4. **Note access method** - for reproducibility +5. **Capture full citation** - author, title, journal, DOI, PMID + +## Additional Resources + +**PubMed search tips:** +- Use MeSH terms (Medical Subject Headings) for precise searches +- Combine with Boolean operators (AND, OR, NOT) +- Use filters: Free full text, Article type, Publication date +- Save searches for repeated use + +**Understanding study types:** +- RCT (Randomized Controlled Trial) - gold standard +- Systematic Review/Meta-Analysis - synthesis of multiple studies +- Cohort Study - observational, follows groups over time +- Case-Control Study - compares cases to controls +- Case Series/Report - descriptive, lowest evidence level + +**Red flags in research:** +- Preprint only (not peer-reviewed) +- Retracted or corrected +- Conflicts of interest not disclosed +- Small sample size with strong claims +- Outlier findings not replicated diff --git a/shared/linked-dotfiles/opencode/skills/research/SKILL.md b/shared/linked-dotfiles/opencode/skills/research/SKILL.md new file mode 100644 index 0000000..7a491af --- /dev/null +++ b/shared/linked-dotfiles/opencode/skills/research/SKILL.md @@ -0,0 +1,237 @@ +--- +name: research +description: Use when conducting deep research across any domain - provides citation patterns, concise synthesis techniques, and cross-domain connection strategies +--- + +# Research Skill + +Quick reference for conducting deep research with proper citations, concise output, and novel insights across any domain. + +## When to Use This Skill + +- Investigating technical topics (APIs, frameworks, algorithms) +- Understanding psychological or human factors +- Exploring creative writing techniques or artistic approaches +- Synthesizing information from multiple disparate sources +- Making connections between different domains +- Gathering authoritative sources for decision-making + +**When NOT to use:** +- Simple factual lookups (use direct search instead) +- Code implementation (use coding agents) +- Quick reference checks (use man pages directly) + +## Citation Format Reference + +### Web Sources +``` +Specific claim or finding from the source +``` + +### Academic Papers +``` +Quantitative finding or key insight +``` + +### Local Files +``` +Code snippet or configuration detail +``` + +### Man Pages +``` +Command behavior or flag description +``` + +### Multiple Sources for Same Claim +``` +Initial finding corroborated by confirming evidence +``` + +## Output Style Guide + +| Good (Concise Paragraphs) | Bad (Bullets/Verbosity) | +|---|---| +| The STORM paper introduces a multi-agent system for comprehensive research. Finding. This approach yields Wikipedia-quality output. | It is important to note that:
- STORM uses agents
- Perhaps it works well
- Furthermore, one might consider... | +| Direct statement with citation. | "It seems that this might be useful..." | +| 2-4 sentence paragraphs. | Wall of text or excessive bullets. | + +## ReAct Research Loop + +**Pattern for iterative research:** + +1. **Thought**: "I need to understand X. Sources to check: Y, Z." +2. **Action**: `curl https://docs.example.com/api` or `man command` or `rg "pattern"` +3. **Observation**: "Found A, B, C. Still missing D." +4. **Thought**: "Need to refine search for D." +5. **Action**: New search with refined query +6. **Observation**: "Now have complete picture." + +Repeat until sufficient evidence gathered. + +## Making Cross-Domain Connections + +### Technique: Analogy Mapping + +1. Identify core mechanism in source domain +2. Find parallel structure in target domain +3. Map relationships explicitly +4. Test if analogy reveals new insights + +**Example**: ReAct pattern (technical) ↔ Expert problem-solving (psychology) +- Both externalize thinking +- Both enable error detection +- Both reduce cognitive load +- Connection reveals *why* ReAct works + +### Technique: Pattern Recognition + +Look across sources for: +- Recurring themes or principles +- Shared constraints or trade-offs +- Similar solution approaches +- Common failure modes + +### Technique: Contrast Analysis + +When sources disagree: +- Identify specific points of tension +- Examine underlying assumptions +- Consider context differences +- Synthesize higher-level insight + +## Research Tool Usage + +### Web Research +```bash +# Fetch documentation +curl -s https://docs.example.com/api | grep "pattern" + +# Download paper +wget https://arxiv.org/pdf/2210.03629.pdf + +# Search with specific terms +curl -s "https://api.example.com/search?q=term" +``` + +### Man Pages +```bash +# Full manual +man grep + +# Search within man page +man grep | grep -A 5 "pattern" + +# List all man pages for command +man -k search_term +``` + +### Code/File Research +```bash +# Find implementations +rg "function_name" --type rust + +# Search with context +rg "pattern" -A 3 -B 3 + +# Find files by name +find . -name "*.md" -type f +``` + +## Common Mistakes + +**Mistake: Uncited claims** +``` +❌ "The ReAct pattern improves performance significantly." +✅ "The ReAct pattern improves performance significantly. specific metric." +``` + +**Mistake: Verbose hedging** +``` +❌ "It seems that perhaps one might consider that this could potentially..." +✅ "This approach increases success rates by 34%." +``` + +**Mistake: Bullet point overload** +``` +❌ Long lists of disconnected bullets +✅ 2-4 sentence paragraphs that flow coherently +``` + +**Mistake: Missing cross-domain insights** +``` +❌ Only technical analysis without broader connections +✅ "This pattern mirrors cognitive psychology research on expert problem-solving..." +``` + +**Mistake: No verification step** +``` +❌ Output without checking citation coverage +✅ Self-check: Every claim cited? Format correct? Under 500 words? +``` + +## Output Structure Template + +``` +[1-2 sentence context establishing the question and why it matters] + +[Paragraph 1: First major finding with citations. 2-4 sentences. Focus on one insight.] + +[Paragraph 2: Second major finding with citations. Builds on or contrasts with first.] + +[Paragraph 3: Cross-domain connection or novel insight. "Interestingly..." or "This pattern mirrors..."] + +[Optional Paragraph 4: Practical implications or actionable takeaways if relevant.] + +## Sources +1. [Title](URL) - Brief description of what this source provides +2. [Title](URL) - Brief description +``` + +## Quality Checklist + +Before finalizing research output: + +- [ ] Every significant claim has source citation +- [ ] Citations use correct XML format with URL and title +- [ ] Writing is direct - no hedging or filler phrases +- [ ] Output uses small paragraphs (2-4 sentences), not bullets +- [ ] Under 500 words for typical queries +- [ ] At least one cross-domain connection identified +- [ ] Multiple perspectives considered (technical, human, business, creative) +- [ ] Bibliography section lists all sources +- [ ] Verification step completed + +## Domain-Specific Notes + +### Technical Research +Focus on: Implementation details, performance metrics, trade-offs, compatibility, version-specific behavior + +### Psychology/Human Factors +Focus on: User studies, cognitive principles, behavioral patterns, accessibility, mental models + +### Creative Writing +Focus on: Techniques from literature, stylistic approaches, examples from published works, craft advice from authors + +### Science/Research +Focus on: Peer-reviewed sources, methodology, empirical findings, replication status, statistical significance + +## Example: Good Research Output + +**Query**: How does the ReAct reasoning pattern work? + +**Output**: + +The ReAct pattern addresses a key limitation in language model task-solving by interleaving reasoning traces with tool actions. ReAct agents achieve 34% higher success rates on ALFWorld tasks and 10% improvement on HotpotQA compared to baselines. The core mechanism involves explicit Thought-Action-Observation loops where the model verbalizes its reasoning before taking each action, then updates its understanding based on observations. + +This architecture mirrors findings from cognitive psychology on expert problem-solving. Experts externalize their thinking through verbal protocols, which reduces cognitive load by offloading working memory to external representations. By forcing LLMs to "think aloud," ReAct essentially implements this expert strategy in artificial systems. + +The practical implication is clear: tasks requiring multi-step reasoning benefit from explicit trace generation. ReAct excels when error recovery matters, since failed actions produce observations that redirect reasoning. For simple, single-step tasks, the overhead isn't justified. + +## Sources +1. [ReAct: Synergizing Reasoning and Acting in Language Models](https://arxiv.org/abs/2210.03629) - ICLR 2023 paper introducing the pattern +2. [Expert Problem Solving Research](file://references/cognitive-science.md) - Cognitive science on externalized thinking + +--- + +*Note: This skill is designed for use with the research agent. It provides quick reference patterns for citations, concise synthesis, and cross-domain insights.*