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"
|
"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": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1744463964,
|
"lastModified": 1744463964,
|
||||||
@ -169,6 +185,7 @@
|
|||||||
"auto-cpufreq": "auto-cpufreq",
|
"auto-cpufreq": "auto-cpufreq",
|
||||||
"catppuccin": "catppuccin",
|
"catppuccin": "catppuccin",
|
||||||
"home-manager": "home-manager",
|
"home-manager": "home-manager",
|
||||||
|
"nixos-hardware": "nixos-hardware",
|
||||||
"nixpkgs": "nixpkgs_2",
|
"nixpkgs": "nixpkgs_2",
|
||||||
"nixpkgs-unstable": "nixpkgs-unstable",
|
"nixpkgs-unstable": "nixpkgs-unstable",
|
||||||
"nur": "nur"
|
"nur": "nur"
|
||||||
|
|||||||
@ -15,9 +15,10 @@
|
|||||||
url = "github:AdnanHodzic/auto-cpufreq";
|
url = "github:AdnanHodzic/auto-cpufreq";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
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
|
let
|
||||||
inherit (self) outputs;
|
inherit (self) outputs;
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
|
inputs.nixos-hardware.nixosModules.framework-12-13th-gen-intel
|
||||||
./desktop-configuration.nix
|
./desktop-configuration.nix
|
||||||
./nixos/hardware-configuration.nix
|
./nixos/hardware-configuration.nix
|
||||||
];
|
];
|
||||||
|
|||||||
@ -54,6 +54,7 @@
|
|||||||
python310
|
python310
|
||||||
unstable.claude-code
|
unstable.claude-code
|
||||||
unstable.opencode
|
unstable.opencode
|
||||||
|
nodejs_24
|
||||||
### LSP's
|
### LSP's
|
||||||
nil
|
nil
|
||||||
nodePackages_latest.bash-language-server
|
nodePackages_latest.bash-language-server
|
||||||
@ -168,6 +169,7 @@
|
|||||||
"waybar".source = config.lib.file.mkOutOfStoreSymlink "/home/nate/nixos/frame12/linked-dotfiles/waybar";
|
"waybar".source = config.lib.file.mkOutOfStoreSymlink "/home/nate/nixos/frame12/linked-dotfiles/waybar";
|
||||||
# Shared
|
# Shared
|
||||||
"helix".source = config.lib.file.mkOutOfStoreSymlink "/home/nate/nixos/shared/linked-dotfiles/helix";
|
"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
|
# Theme configuration
|
||||||
"gtk-4.0/assets".source = "${config.gtk.theme.package}/share/themes/${config.gtk.theme.name}/gtk-4.0/assets";
|
"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
|
write: true
|
||||||
edit: true
|
edit: true
|
||||||
bash: true
|
bash: true
|
||||||
|
memory_store: true
|
||||||
|
memory_search: true
|
||||||
|
memory_list: true
|
||||||
permission:
|
permission:
|
||||||
bash:
|
bash:
|
||||||
"git add *": allow
|
|
||||||
"git commit *": allow
|
|
||||||
"git status": allow
|
|
||||||
"git diff *": allow
|
|
||||||
"git log *": allow
|
|
||||||
"rg *": allow
|
"rg *": allow
|
||||||
"grep *": allow
|
"grep *": allow
|
||||||
"cat *": allow
|
"cat *": allow
|
||||||
@ -22,6 +20,7 @@ permission:
|
|||||||
"test *": allow
|
"test *": allow
|
||||||
"make *": allow
|
"make *": allow
|
||||||
"ls *": allow
|
"ls *": allow
|
||||||
|
"wc *": allow
|
||||||
"*": ask
|
"*": ask
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -119,6 +118,24 @@ For each identified issue, determine target component:
|
|||||||
|
|
||||||
For each improvement, execute changes:
|
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)
|
#### 1. Update Documentation (CLAUDE.md, AGENTS.md)
|
||||||
|
|
||||||
**Read existing structure first**:
|
**Read existing structure first**:
|
||||||
@ -144,24 +161,14 @@ nix flake check
|
|||||||
# Test without building (NEW - added from session learning)
|
# Test without building (NEW - added from session learning)
|
||||||
nix build .#nixosConfigurations.<hostname>.config.system.build.toplevel --dry-run
|
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
|
#### 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**:
|
**Use create-skill workflow**:
|
||||||
1. Determine skill name (gerund form: `doing-thing`)
|
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
|
3. Write SKILL.md with proper frontmatter
|
||||||
4. Keep concise (<500 lines)
|
4. Keep concise (<500 lines)
|
||||||
5. Follow create-skill checklist
|
5. Follow create-skill checklist
|
||||||
@ -201,6 +208,21 @@ Brief overview (1-2 sentences).
|
|||||||
[What goes wrong + fixes]
|
[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**:
|
**Validate skill**:
|
||||||
```bash
|
```bash
|
||||||
# Check frontmatter and structure
|
# 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
|
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
|
#### 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**:
|
**When to update**:
|
||||||
- Missing edge case identified
|
- Missing edge case identified
|
||||||
- New example would help
|
- New example would help
|
||||||
@ -239,9 +252,9 @@ Session: <session-context>"
|
|||||||
- Don't rewrite entire skill
|
- Don't rewrite entire skill
|
||||||
- Add focused content only
|
- Add focused content only
|
||||||
- Preserve existing structure
|
- Preserve existing structure
|
||||||
- Use Edit tool for precision
|
- Use config_edit for precision edits
|
||||||
|
|
||||||
**Example update**:
|
**Example update with config_edit**:
|
||||||
```markdown
|
```markdown
|
||||||
## Common Mistakes
|
## 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'..."`
|
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
|
#### 4. Create Shell Automation
|
||||||
|
|
||||||
**Identify candidates**:
|
**Identify candidates**:
|
||||||
@ -286,18 +288,6 @@ Recommended aliases for this project:
|
|||||||
alias nix-check='nix flake check'
|
alias nix-check='nix flake check'
|
||||||
alias nix-dry='nix build .#nixosConfigurations.$(hostname).config.system.build.toplevel --dry-run'
|
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
|
#### 5. Update Agent Definitions
|
||||||
|
|
||||||
@ -315,17 +305,6 @@ Session: <session-context>"
|
|||||||
- Test agent still loads correctly
|
- Test agent still loads correctly
|
||||||
- Document reason for change
|
- 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
|
### Phase 4: Validation
|
||||||
|
|
||||||
After making changes, validate they work:
|
After making changes, validate they work:
|
||||||
@ -334,9 +313,6 @@ After making changes, validate they work:
|
|||||||
```bash
|
```bash
|
||||||
# Check markdown syntax
|
# Check markdown syntax
|
||||||
cat CLAUDE.md AGENTS.md
|
cat CLAUDE.md AGENTS.md
|
||||||
|
|
||||||
# Verify formatting is consistent
|
|
||||||
git diff
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Skills**:
|
**Skills**:
|
||||||
@ -347,15 +323,6 @@ opencode run "Use learn_skill with skill_name='skill-name' - load skill and give
|
|||||||
# Verify frontmatter appears in output
|
# Verify frontmatter appears in output
|
||||||
```
|
```
|
||||||
|
|
||||||
**Git state**:
|
|
||||||
```bash
|
|
||||||
# Verify all changes committed
|
|
||||||
git status
|
|
||||||
|
|
||||||
# Review commit history
|
|
||||||
git log --oneline -5
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 5: Reporting
|
### Phase 5: Reporting
|
||||||
|
|
||||||
Generate final report showing what was implemented:
|
Generate final report showing what was implemented:
|
||||||
@ -379,11 +346,6 @@ Generate final report showing what was implemented:
|
|||||||
### Agent Refinements
|
### Agent Refinements
|
||||||
- ✅ Updated `agent-name` agent - [change]
|
- ✅ Updated `agent-name` agent - [change]
|
||||||
|
|
||||||
## Git Commits
|
|
||||||
- commit-hash-1: [message]
|
|
||||||
- commit-hash-2: [message]
|
|
||||||
- commit-hash-3: [message]
|
|
||||||
|
|
||||||
## Next Session Benefits
|
## Next Session Benefits
|
||||||
|
|
||||||
These improvements prevent:
|
These improvements prevent:
|
||||||
@ -415,9 +377,8 @@ alias nix-check
|
|||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
Implemented [N] systemic improvements in [M] git commits.
|
Implemented [N] systemic improvements.
|
||||||
Next session will benefit from these preventive measures.
|
Next session will benefit from these preventive measures.
|
||||||
```
|
|
||||||
|
|
||||||
## Decision Framework
|
## Decision Framework
|
||||||
|
|
||||||
@ -464,7 +425,6 @@ Next session will benefit from these preventive measures.
|
|||||||
- Creating new skills (you're an expert at this)
|
- Creating new skills (you're an expert at this)
|
||||||
- Updating skill "Common Mistakes" sections
|
- Updating skill "Common Mistakes" sections
|
||||||
- Documenting shell aliases
|
- Documenting shell aliases
|
||||||
- Standard git commits
|
|
||||||
|
|
||||||
**Ask first** (potentially risky):
|
**Ask first** (potentially risky):
|
||||||
- Deleting content from docs/skills
|
- Deleting content from docs/skills
|
||||||
@ -473,7 +433,7 @@ Next session will benefit from these preventive measures.
|
|||||||
- Making changes outside typical directories
|
- Making changes outside typical directories
|
||||||
- Anything that feels destructive
|
- 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
|
## 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.
|
**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**:
|
**What to store**:
|
||||||
1. Create structured logs of improvements (your commit messages do this)
|
- High-impact improvements made
|
||||||
2. Use consistent commit message format for easy querying later
|
- Recurring patterns identified across sessions
|
||||||
3. Git history serves as memory (searchable with `git log --grep`)
|
- Effectiveness of previous changes
|
||||||
|
- Cross-project patterns discovered
|
||||||
|
- Decisions about when to create skills vs update docs
|
||||||
|
|
||||||
**Future integration**: When memory/WIP tool arrives:
|
**How to use**:
|
||||||
- Track recurring patterns across sessions
|
Call `memory_store` tool with two parameters:
|
||||||
- Measure improvement effectiveness
|
- **content**: Description of optimization with impact (multi-line string)
|
||||||
- Build knowledge base of solutions
|
- **tags**: Comma-separated tags like "optimize,improvement,ssh-auth,documentation"
|
||||||
- Detect cross-project patterns
|
|
||||||
- Prioritize based on frequency and impact
|
|
||||||
|
|
||||||
**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]
|
||||||
Impact: [time saved / friction removed]
|
Project: [project-name or "general"]
|
||||||
Session: [context]
|
Date: [YYYY-MM-DD]
|
||||||
```
|
```
|
||||||
|
|
||||||
This structured format enables:
|
**Useful tag categories**: `optimize`, `improvement`, `skill-creation`, `documentation`, `automation`, `ssh-auth`, `build-commands`, `testing`, `nixos`, `workflow`
|
||||||
- Pattern detection across commits
|
|
||||||
- Effectiveness measurement
|
**Querying past optimizations**:
|
||||||
- Easy migration to memory tool
|
- Use `memory_search` to find similar past improvements before making changes
|
||||||
- Querying with git log
|
- 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
|
## Examples
|
||||||
|
|
||||||
@ -550,20 +518,10 @@ Target Component: AGENTS.md (setup documentation)
|
|||||||
ssh-add ~/.ssh/id_ed25519 2>/dev/null
|
ssh-add ~/.ssh/id_ed25519 2>/dev/null
|
||||||
```
|
```
|
||||||
```
|
```
|
||||||
3. Show git diff
|
3. Use `memory_store` tool to record this optimization:
|
||||||
4. Commit:
|
- 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"
|
||||||
```bash
|
- tags: "optimize,improvement,ssh-auth,documentation"
|
||||||
git add AGENTS.md
|
4. Report: "✅ Added SSH key loading to AGENTS.md setup section"
|
||||||
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"
|
|
||||||
|
|
||||||
### Example 2: Repeated Build Commands
|
### Example 2: Repeated Build Commands
|
||||||
|
|
||||||
@ -575,7 +533,9 @@ Commands used 5 times: nix flake check, nix build ...dry-run
|
|||||||
**Your action**:
|
**Your action**:
|
||||||
1. Add to AGENTS.md build commands section
|
1. Add to AGENTS.md build commands section
|
||||||
2. Document recommended shell aliases
|
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:
|
4. Report:
|
||||||
```markdown
|
```markdown
|
||||||
✅ Added nix validation commands to AGENTS.md
|
✅ Added nix validation commands to AGENTS.md
|
||||||
@ -600,7 +560,9 @@ Missing: No skill for NixOS-specific development patterns
|
|||||||
1. Create `nixos-development` skill
|
1. Create `nixos-development` skill
|
||||||
2. Include: build commands, test workflow, common issues
|
2. Include: build commands, test workflow, common issues
|
||||||
3. Keep concise (<300 lines)
|
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"
|
5. Note: "⚠️ Restart OpenCode to load new skill"
|
||||||
6. Report:
|
6. Report:
|
||||||
```markdown
|
```markdown
|
||||||
@ -638,7 +600,7 @@ Next: Restart OpenCode, then use with learn_skill(nixos-development)
|
|||||||
Good optimization session results in:
|
Good optimization session results in:
|
||||||
- ✅ 1-3 high-impact changes implemented (not 10+ minor ones)
|
- ✅ 1-3 high-impact changes implemented (not 10+ minor ones)
|
||||||
- ✅ Each change maps to specific preventable friction
|
- ✅ 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)
|
- ✅ Changes are immediately usable (or restart instructions provided)
|
||||||
- ✅ Report shows concrete actions taken, not proposals
|
- ✅ Report shows concrete actions taken, not proposals
|
||||||
- ✅ Next session will benefit from changes (measurable prevention)
|
- ✅ 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",
|
"mcp-remote",
|
||||||
"https://mcp.atlassian.com/v1/sse"
|
"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 }) => {
|
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 {
|
return {
|
||||||
event: async ({ event }) => {
|
event: async ({ event }) => {
|
||||||
@ -9,30 +83,20 @@ export const SwayNotificationCenter = async ({ project, client, $, directory, wo
|
|||||||
const iconPath = `${process.env.HOME}/.config/opencode/opencode.png`;
|
const iconPath = `${process.env.HOME}/.config/opencode/opencode.png`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const clientsJson = await $`hyprctl clients -j`.text();
|
const windowInfo = wm ? await findWindow($, wm, pid) : null;
|
||||||
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 notifyCmd = [
|
const notifyCmd = [
|
||||||
"notify-send",
|
"notify-send",
|
||||||
"-a", "OpenCode",
|
"-a", "OpenCode",
|
||||||
"-u", "normal",
|
"-u", "normal",
|
||||||
"-i", iconPath,
|
"-i", iconPath,
|
||||||
"-h", `string:x-opencode-window:${windowAddress}`,
|
|
||||||
"-h", `string:x-opencode-dir:${dir}`,
|
"-h", `string:x-opencode-dir:${dir}`,
|
||||||
"-A", `focus=Focus Window`,
|
...(windowInfo ? ["-A", `focus=Focus Window`] : []),
|
||||||
"OpenCode Ready",
|
"OpenCode Ready",
|
||||||
`Waiting for input\nDirectory: ${dir}`
|
`Waiting for input\nDirectory: ${dir}`
|
||||||
];
|
];
|
||||||
|
|
||||||
if (windowAddress) {
|
if (windowInfo) {
|
||||||
// Run notify-send as detached background process
|
|
||||||
import("child_process").then(({ spawn }) => {
|
import("child_process").then(({ spawn }) => {
|
||||||
const child = spawn(notifyCmd[0], notifyCmd.slice(1), {
|
const child = spawn(notifyCmd[0], notifyCmd.slice(1), {
|
||||||
detached: true,
|
detached: true,
|
||||||
@ -40,22 +104,20 @@ export const SwayNotificationCenter = async ({ project, client, $, directory, wo
|
|||||||
});
|
});
|
||||||
child.unref();
|
child.unref();
|
||||||
|
|
||||||
// Handle the response in the background
|
|
||||||
let output = '';
|
let output = '';
|
||||||
if (child.stdout) {
|
if (child.stdout) {
|
||||||
child.stdout.on('data', (data) => {
|
child.stdout.on('data', (data) => {
|
||||||
output += data.toString();
|
output += data.toString();
|
||||||
});
|
});
|
||||||
child.on('close', () => {
|
child.on('close', () => {
|
||||||
if (output.trim() === "focus" && windowAddress) {
|
if (output.trim() === "focus") {
|
||||||
$`hyprctl dispatch focuswindow address:${windowAddress}`.catch(() => {});
|
focusWindow($, windowInfo).catch(() => {});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
} else {
|
} else {
|
||||||
// Run without action button, no need to wait
|
$`${notifyCmd}`.catch(() => {});
|
||||||
$`${notifyCmd.filter(arg => !arg.startsWith('focus'))}`.catch(() => {});
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Notification error:", 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