Add file proxy tool plugin, add research agent, update framework config
This commit is contained in:
parent
02f7a3b960
commit
5d59b44cd5
17
flake.lock
generated
17
flake.lock
generated
@ -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"
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
inputs.nixos-hardware.nixosModules.framework-12-13th-gen-intel
|
||||
./desktop-configuration.nix
|
||||
./nixos/hardware-configuration.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";
|
||||
|
||||
@ -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.<hostname>.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: <session-context>"
|
||||
```
|
||||
|
||||
#### 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: <session-context>"
|
||||
```
|
||||
|
||||
#### 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: <session-context>"
|
||||
- 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: <session-context>"
|
||||
```
|
||||
|
||||
#### 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: <session-context>"
|
||||
```
|
||||
|
||||
#### 5. Update Agent Definitions
|
||||
|
||||
@ -315,17 +305,6 @@ Session: <session-context>"
|
||||
- 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: <session-context>"
|
||||
```
|
||||
|
||||
### 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)
|
||||
|
||||
185
shared/linked-dotfiles/opencode/agent/research.md
Normal file
185
shared/linked-dotfiles/opencode/agent/research.md
Normal file
@ -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:
|
||||
|
||||
```
|
||||
<source url="https://example.com/paper" title="Paper Title">The specific claim or finding</source>
|
||||
```
|
||||
|
||||
For local sources (man pages, code files):
|
||||
```
|
||||
<source url="file://path/to/file" title="filename">The specific claim</source>
|
||||
```
|
||||
|
||||
**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 <command>` 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. <source url="https://arxiv.org/abs/2210.03629" title="ReAct: Synergizing Reasoning and Acting in Language Models">ReAct agents achieve 34% higher success rates on ALFWorld tasks compared to baseline approaches</source>. 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. <source url="https://psycnet.apa.org/record/1994-97586-000" title="The Psychology of Problem Solving">Expert problem solvers externalize their thinking through verbal protocols, which reduces cognitive load and improves solution quality</source>. 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.
|
||||
@ -14,7 +14,7 @@
|
||||
"mcp-remote",
|
||||
"https://mcp.atlassian.com/v1/sse"
|
||||
],
|
||||
"enabled": true
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
}
|
||||
176
shared/linked-dotfiles/opencode/plugin/file-proxy.js
Normal file
176
shared/linked-dotfiles/opencode/plugin/file-proxy.js
Normal file
@ -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}`;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
};
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
@ -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/
|
||||
@ -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.
|
||||
@ -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)
|
||||
}));
|
||||
}
|
||||
`}
|
||||
```
|
||||
@ -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"}
|
||||
```
|
||||
@ -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)
|
||||
```
|
||||
384
shared/linked-dotfiles/opencode/skills/research-medical/SKILL.md
Normal file
384
shared/linked-dotfiles/opencode/skills/research-medical/SKILL.md
Normal file
@ -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
|
||||
237
shared/linked-dotfiles/opencode/skills/research/SKILL.md
Normal file
237
shared/linked-dotfiles/opencode/skills/research/SKILL.md
Normal file
@ -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
|
||||
```
|
||||
<source url="https://example.com/article" title="Article Title">Specific claim or finding from the source</source>
|
||||
```
|
||||
|
||||
### Academic Papers
|
||||
```
|
||||
<source url="https://arxiv.org/abs/2210.03629" title="ReAct: Synergizing Reasoning and Acting">Quantitative finding or key insight</source>
|
||||
```
|
||||
|
||||
### Local Files
|
||||
```
|
||||
<source url="file:///path/to/file.md" title="filename.md">Code snippet or configuration detail</source>
|
||||
```
|
||||
|
||||
### Man Pages
|
||||
```
|
||||
<source url="man://grep" title="grep(1) manual">Command behavior or flag description</source>
|
||||
```
|
||||
|
||||
### Multiple Sources for Same Claim
|
||||
```
|
||||
<source url="https://source1.com" title="First Study">Initial finding</source> corroborated by <source url="https://source2.com" title="Second Study">confirming evidence</source>
|
||||
```
|
||||
|
||||
## Output Style Guide
|
||||
|
||||
| Good (Concise Paragraphs) | Bad (Bullets/Verbosity) |
|
||||
|---|---|
|
||||
| The STORM paper introduces a multi-agent system for comprehensive research. <source>Finding</source>. This approach yields Wikipedia-quality output. | It is important to note that:<br>- STORM uses agents<br>- Perhaps it works well<br>- 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. <source>specific metric</source>."
|
||||
```
|
||||
|
||||
**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. <source url="https://arxiv.org/abs/2210.03629" title="ReAct: Synergizing Reasoning and Acting in Language Models">ReAct agents achieve 34% higher success rates on ALFWorld tasks and 10% improvement on HotpotQA compared to baselines</source>. 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. <source url="file://references/cognitive-science.md" title="Expert Problem Solving Research">Experts externalize their thinking through verbal protocols, which reduces cognitive load by offloading working memory to external representations</source>. 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.*
|
||||
Loading…
Reference in New Issue
Block a user