Initial commit

This commit is contained in:
LZH-YS1998
2026-07-01 17:56:31 +08:00
commit d78931979d
731 changed files with 311088 additions and 0 deletions
+62
View File
@@ -0,0 +1,62 @@
---
name: clawhub
description: Search and install agent skills from ClawHub, the public skill registry. Use when needing new capabilities, searching for skills, installing skills, or updating installed skills.
always: true
homepage: https://clawhub.ai
---
# ClawHub
Public skill registry for AI agents. Search by natural language (vector search).
## When to use
- You encounter an unfamiliar domain or technology and need guidance
- The user asks to find, search, install, or update skills
- You need specialized knowledge that existing skills don't cover
- A task requires capabilities beyond your current skill set
## Search
```bash
npx --yes clawhub@latest search "web scraping" --limit 5
```
## Install
Install skills into the current project's skill directory:
```bash
npx --yes clawhub@latest install <slug> --workdir .opc/projects/<project_id>
```
Replace `<slug>` with the skill name from search results and `<project_id>` with the current project ID. This places the skill into `.opc/projects/<project_id>/skills/<slug>/SKILL.md`.
After install, read the installed SKILL.md with `file_read` to apply its guidance immediately.
## Update
```bash
npx --yes clawhub@latest update --all --workdir .opc/projects/<project_id>
```
## List installed
```bash
npx --yes clawhub@latest list --workdir .opc/projects/<project_id>
```
## Workflow
1. Search for relevant skills with a descriptive query
2. Review search results and pick the best match
3. Install the skill into the project directory
4. Read the installed SKILL.md with `file_read`
5. Follow the skill's instructions for the current task
## Notes
- Requires Node.js (`npx` comes with it).
- No API key needed for search and install.
- Login (`npx --yes clawhub@latest login`) is only required for publishing.
- `--workdir` must point to the project directory so skills install correctly.
+153
View File
@@ -0,0 +1,153 @@
---
name: collaboration-playbook
description: Standing rules for how any role in an OpenOPC company coordinates with its peers — work-item discipline, messaging, meetings, and blocking collaboration. Loaded only in company_mode.
always: true
modes:
- company_mode
---
# Collaboration Playbook
You are one role inside an OpenOPC company. Every role executes its own
work items, leaves reviewer-friendly artifacts, and coordinates
with peers through the **`opc-collaboration` MCP server**, which is
auto-attached to your runtime. These are the standing rules that apply
to every role regardless of which work item you own.
You are never the whole project. Stay inside your work-item boundary.
## Work Item Discipline
- Own your work item. Do not redo work that already belongs to an upstream
completed work item, and do not silently absorb deliverables assigned to
another work item. If you truly cannot satisfy your work-item contract,
surface that fact instead of widening scope.
- Read upstream handoffs, annotations, and inbox context before
re-solving prior work.
- Your completion bar is higher than "it works on my turn." Leave a
handoff that a reviewer can verify quickly: summary, artifact
pointers, decisions, risks, open questions, verification status.
- Prefer direct execution for the assigned slice once the approach is
clear. Investigate first, then act.
## Messaging (the default path)
You communicate with peers via MCP tools provided by the
`opc-collaboration` server. Your identity (`OPC_COMMS_FROM`) is
already injected by OpenOPC for each turn — you never pass
`from_agent` as a tool argument; the server reads it from the
environment so you cannot accidentally (or deliberately) impersonate
another role.
### The collaboration tools
| Tool | Purpose |
|------|---------|
| `send_dm(to_agent, subject, body, blocking=False)` | Send a direct message to another role. |
| `read_inbox(limit=10, mark_read=True)` | Read your own unread messages. |
| `reply_message(message_id, body, subject="")` | Reply to a specific message by id. |
| `broadcast_issue(to_agents, subject, body)` | Send the same message to multiple roles. |
| `find_and_ask_expert(skill_needed, question, blocking=False)` | Auto-route a question to whoever has the matching capability. |
| `list_colleagues()` | Discover which roles are in the company. |
| `start_meeting(topic, participants)` | Open a multi-party meeting room. |
| `respond_meeting(meeting_id, content)` | Speak in an open meeting. |
| `read_meeting(meeting_id)` | Read a meeting transcript. |
### When to send a message
Send ONLY when one of these is true:
- You need the recipient to confirm or change something specific
before your deliverable can be finalized.
- You discovered a conflict with a completed upstream work item that you
cannot resolve from context.
- You hold information another role provably needs and will not see
otherwise (i.e. it is not already in a handoff or shared artifact).
### When NOT to send a message
- Do NOT send messages to acknowledge, summarize what you just did, or
loop people in for visibility. Your handoff file IS the visibility
mechanism.
- Do NOT broadcast status updates. If a peer needs the latest status,
they will read the artifacts you left.
- Do NOT send a message that would duplicate information already in a
handoff, annotation, or shared memory entry.
### When to reply to a message you received
Reply ONLY when the sender explicitly asked for your confirmation or
a change AND the answer is non-trivial. If your reply would be "ack,
no changes needed", stay silent — silence is the ack. This rule keeps
the team from oscillating on trivial back-and-forth.
### Checking your inbox
The per-turn prompt's "Comms" section tells you whether you have
unread messages. When it does, call `read_inbox` first thing to see
what arrived. Otherwise you do not need to poll the inbox.
## Meetings (rare, for genuine cross-role decisions)
Meetings are for decisions or conflicts that truly need more than one
role in the room at once. A normal work-item handoff is NOT a meeting.
To start a meeting, call `start_meeting(topic, participants)`. The
tool returns a `meeting_id`. To speak, call
`respond_meeting(meeting_id, content)`. To read what others have
posted, call `read_meeting(meeting_id)`.
If you are already inside an open meeting room, the per-turn prompt
will list it under the runtime state block. Wrap up the meeting with
a concrete decision summary — an open meeting room with no decision
is worse than no meeting at all.
## Blocking Collaboration (rare — the 10% case)
By default, collaboration is non-blocking: you call `send_dm` and
continue with your own work. But there are situations where your
work item genuinely cannot continue without a peer's reply — an urgent
decision, an unresolvable conflict with an upstream work item, or a
meeting you must wait on.
In those cases, pass `blocking=True` to `send_dm`:
```
send_dm(to_agent="qa_engineer", subject="...", body="...", blocking=True)
```
OpenOPC will detect the blocking marker, park your work item in
AWAITING_PEER, run the recipient with your message available, then
resume your work item once a reply has been written. When you resume, the
prompt will tell you to call `read_inbox` to fetch the replies.
Do NOT use `blocking=True` for:
- routine acknowledgements,
- visibility pings,
- anything that could be resolved by reading existing handoffs.
Abusing blocking semantics defeats the convergence rule and stalls
the whole company. If you are tempted to use it, first ask: "could I
finish this turn without the reply, leave the question as an open
issue in my handoff, and let the peer respond asynchronously?" If
yes, do that instead.
## Shared Team Memory
The company has a shared team memory file. Its path is provided in
the per-turn prompt under the runtime state block. Write durable
shared state there — current conclusions, active risks, decisions,
open questions, important constraints. Do NOT use it as a chat log
or an activity feed.
## What This Playbook Is NOT
- It is not a per-work-item checklist. Your work-item-specific deliverables,
inputs, acceptance criteria, and out-of-scope items come from the
per-turn task brief, not from here.
- It is not a tool reference for non-collaboration tools. The set of
tools available to you this turn is declared in the tool surface
artifact.
- It is not an org chart. Who you may directly contact is listed in
the per-turn topology section.
+39
View File
@@ -0,0 +1,39 @@
---
name: coding
description: "Best practices for code development tasks"
domain:
- coding
- frontend
- backend
- devops
trigger: "When executing code-related tasks"
always_on: false
---
# Coding Skill
## Workflow
1. **Understand** — Read existing code and understand the codebase structure before making changes
2. **Plan** — Break down the implementation into clear steps
3. **Implement** — Write clean, well-structured code following project conventions
4. **Verify** — Run tests, check for errors, and validate the implementation
5. **Document** — Add necessary comments for non-obvious logic only
## Best Practices
- Always read files before editing them
- Use `list_dir` to understand project structure
- Use `file_search` to find relevant code
- Prefer editing existing files over creating new ones
- Follow existing code style and conventions
- Include error handling in all code
- Write tests when appropriate
- Use version control (git) for all changes
- Keep functions small and focused
- Use meaningful variable and function names
## Code Quality
- No unnecessary comments that just restate the code
- Proper error handling and edge cases
- Consistent formatting with the rest of the codebase
- No hardcoded secrets or credentials
- Follow language-specific best practices
+39
View File
@@ -0,0 +1,39 @@
---
name: deployment
description: "Application deployment and DevOps practices"
domain:
- devops
- deployment
trigger: "When deploying applications or managing infrastructure"
always_on: false
---
# Deployment Skill
## Pre-deployment Checklist
1. All tests passing
2. No security vulnerabilities (check dependencies)
3. Environment variables configured
4. Database migrations ready
5. Backup plan in place
## Common Deployment Targets
- **GitHub Pages** — Static sites
- **Vercel / Netlify** — Frontend apps
- **Docker** — Containerized applications
- **Cloud VMs** — Custom deployments
## Best Practices
- Never deploy directly to production without testing
- Use environment variables for all secrets
- Set up CI/CD pipelines when possible
- Keep deployment scripts version-controlled
- Monitor after deployment
- Have a rollback plan
## Security
- No secrets in code or version control
- Use HTTPS everywhere
- Keep dependencies updated
- Set appropriate file permissions
- Use least-privilege access
+335
View File
@@ -0,0 +1,335 @@
---
name: env_provisioning
description: "Cross-platform environment probing, dependency installation, toolchain configuration, and manifest generation for any domain"
domain:
- environment
- setup
- provisioning
- devops
- toolchain
trigger: "When a task requires installing tools, configuring environments, or setting up dependencies before execution"
always_on: false
---
# Environment Provisioning Skill
You are the Environment Engineer. Your job is to ensure the host environment has everything
downstream stages need — regardless of domain: coding, video production, 3D game development,
audio engineering, ML training, document generation, or anything else.
**You MUST support Linux, macOS, and Windows.** Detect the platform first, then use
platform-appropriate commands throughout.
## Core Workflow
1. **Detect platform** — Determine OS, architecture, and available package managers
2. **Probe** — Discover what is already installed. Never install blindly.
3. **Plan** — Determine what is missing and how to install it.
4. **Install** — Use the appropriate package manager or installer.
5. **Configure** — Set environment variables, paths, configs.
6. **Verify** — Run verification commands to confirm everything works.
7. **Manifest** — Output a structured `environment_manifest` JSON with both Unix and Windows variants.
---
## Platform Detection
**ALWAYS start here.** The output determines all subsequent commands.
### Linux
```bash
uname -a
cat /etc/os-release
# Determine distro family: debian/ubuntu, rhel/fedora, arch, suse
dpkg --version 2>/dev/null && echo "PACKAGE_MANAGER=apt"
rpm --version 2>/dev/null && echo "PACKAGE_MANAGER=dnf"
pacman --version 2>/dev/null && echo "PACKAGE_MANAGER=pacman"
arch=$(uname -m) # x86_64, aarch64
```
### macOS
```bash
sw_vers
uname -m # x86_64 or arm64 (Apple Silicon)
which brew && brew --version
xcode-select -p 2>/dev/null # Xcode CLI tools installed?
```
### Windows (PowerShell)
```powershell
[System.Environment]::OSVersion | Format-List
(Get-CimInstance Win32_OperatingSystem).Caption
$env:PROCESSOR_ARCHITECTURE # AMD64, ARM64
# Check package managers
Get-Command winget -ErrorAction SilentlyContinue | Select-Object Source
Get-Command choco -ErrorAction SilentlyContinue | Select-Object Source
Get-Command scoop -ErrorAction SilentlyContinue | Select-Object Source
```
---
## Probing Strategy (Cross-Platform)
### Linux / macOS (Bash)
```bash
# Package managers
which apt-get brew dnf yum pacman conda pip pip3 uv npm cargo go rustup 2>/dev/null
# GPU
nvidia-smi 2>/dev/null || rocm-smi 2>/dev/null
python3 -c "import torch; print('CUDA:', torch.cuda.is_available())" 2>/dev/null
# Python
which python3 python && python3 --version
pip3 --version 2>/dev/null
conda --version 2>/dev/null
uv --version 2>/dev/null
# Common tools
which ffmpeg blender docker node npm java gcc g++ cmake make git curl wget 2>/dev/null
```
### Windows (PowerShell)
```powershell
# Package managers
@('winget','choco','scoop','conda','pip','uv','npm','cargo','go') | ForEach-Object {
$cmd = Get-Command $_ -ErrorAction SilentlyContinue
if ($cmd) { "$_ : $($cmd.Source)" }
}
# GPU
try { nvidia-smi } catch {}
python -c "import torch; print('CUDA:', torch.cuda.is_available())" 2>$null
# Python
python --version 2>$null
python3 --version 2>$null
pip --version 2>$null
conda --version 2>$null
# Common tools
@('ffmpeg','blender','docker','node','npm','java','gcc','cmake','git','curl') | ForEach-Object {
$cmd = Get-Command $_ -ErrorAction SilentlyContinue
if ($cmd) { "$_ : $($cmd.Source)" }
}
```
---
## Installation By Platform
### System Packages
| Platform | Package Manager | Install Command | Update Command |
|----------|----------------|-----------------|----------------|
| Ubuntu/Debian | apt-get | `sudo apt-get install -y <pkg>` | `sudo apt-get update` |
| Fedora/RHEL | dnf | `sudo dnf install -y <pkg>` | `sudo dnf check-update` |
| Arch | pacman | `sudo pacman -S --noconfirm <pkg>` | `sudo pacman -Sy` |
| macOS | brew | `brew install <pkg>` | `brew update` |
| macOS (GUI apps) | brew cask | `brew install --cask <app>` | — |
| Windows | winget | `winget install --accept-package-agreements -e --id <id>` | `winget upgrade` |
| Windows | choco | `choco install <pkg> -y` | `choco upgrade all -y` |
| Windows | scoop | `scoop install <pkg>` | `scoop update *` |
### Python Packages (All Platforms)
```bash
# Prefer uv if available (fastest)
uv pip install <package>
# Standard pip
pip install <package>
pip3 install <package> # Linux/macOS
# Conda (for complex ML envs)
conda create -n <name> python=3.x -y
conda activate <name>
conda install <package> -y
```
### Node.js / JavaScript (All Platforms)
```bash
# Install Node.js
# Linux: sudo apt-get install -y nodejs npm OR use nvm
# macOS: brew install node
# Windows: winget install OpenJS.NodeJS.LTS OR choco install nodejs-lts -y
npm install -g <package>
npx <tool>
```
### Rust / Go (All Platforms)
```bash
# Rust: install via rustup (cross-platform)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y # Linux/macOS
# Windows: winget install Rustlang.Rustup
cargo install <crate>
go install <module>@latest
```
---
## Domain-Specific Guidance (Cross-Platform)
### Video Production
| Tool | Linux | macOS | Windows |
|------|-------|-------|---------|
| FFmpeg | `sudo apt-get install -y ffmpeg` | `brew install ffmpeg` | `winget install Gyan.FFmpeg` or `choco install ffmpeg -y` |
| yt-dlp | `pip install yt-dlp` | `brew install yt-dlp` or `pip install yt-dlp` | `winget install yt-dlp.yt-dlp` or `pip install yt-dlp` |
| Whisper | `pip install openai-whisper` | `pip install openai-whisper` | `pip install openai-whisper` |
| ImageMagick | `sudo apt-get install -y imagemagick` | `brew install imagemagick` | `choco install imagemagick -y` |
| HandBrake CLI | `sudo apt-get install -y handbrake-cli` | `brew install handbrake` | `choco install handbrake.install -y` |
**Verify (bash):** `ffmpeg -version && yt-dlp --version`
**Verify (PowerShell):** `ffmpeg -version ; yt-dlp --version`
### 3D / Game Development
| Tool | Linux | macOS | Windows |
|------|-------|-------|---------|
| Blender | `sudo apt-get install -y blender` or snap | `brew install --cask blender` | `winget install BlenderFoundation.Blender` |
| Unity Hub | Download AppImage or deb | `brew install --cask unity-hub` | `winget install Unity.UnityHub` |
| Godot | `sudo apt-get install -y godot3` or flatpak | `brew install --cask godot` | `winget install GodotEngine.GodotEngine` or `choco install godot -y` |
| Assimp | `sudo apt-get install -y libassimp-dev` | `brew install assimp` | `vcpkg install assimp` |
| FBX SDK | Download from Autodesk | Download from Autodesk | Download from Autodesk |
**Blender scripting (all platforms):** `blender --background --python <script.py>`
**Unity CLI (all platforms):** Check Unity Hub install path, then use `unity-editor` or `Unity.exe`
### Audio
| Tool | Linux | macOS | Windows |
|------|-------|-------|---------|
| SoX | `sudo apt-get install -y sox` | `brew install sox` | `choco install sox -y` |
| PortAudio | `sudo apt-get install -y portaudio19-dev` | `brew install portaudio` | `vcpkg install portaudio` |
| Audacity (CLI) | `sudo apt-get install -y audacity` | `brew install --cask audacity` | `winget install Audacity.Audacity` |
### ML / AI
| Tool | Linux | macOS | Windows |
|------|-------|-------|---------|
| PyTorch (CPU) | `pip install torch` | `pip install torch` | `pip install torch` |
| PyTorch (CUDA) | `pip install torch --index-url https://download.pytorch.org/whl/cu121` | N/A (no CUDA on Mac) | Same as Linux |
| PyTorch (MPS) | N/A | `pip install torch` (MPS auto) | N/A |
| TensorFlow | `pip install tensorflow` | `pip install tensorflow` | `pip install tensorflow` |
| CUDA Toolkit | `sudo apt-get install -y nvidia-cuda-toolkit` | N/A | Install from NVIDIA site or `choco install cuda -y` |
| cuDNN | `conda install cudnn -y` | N/A | `conda install cudnn -y` |
**GPU verification:**
- Linux: `nvidia-smi && python3 -c "import torch; print(torch.cuda.is_available())"`
- macOS (Apple Silicon): `python3 -c "import torch; print(torch.backends.mps.is_available())"`
- Windows: `nvidia-smi ; python -c "import torch; print(torch.cuda.is_available())"`
### Design / Documents
| Tool | Linux | macOS | Windows |
|------|-------|-------|---------|
| Inkscape | `sudo apt-get install -y inkscape` | `brew install --cask inkscape` | `winget install Inkscape.Inkscape` |
| GIMP | `sudo apt-get install -y gimp` | `brew install --cask gimp` | `winget install GIMP.GIMP` |
| LaTeX | `sudo apt-get install -y texlive-full` | `brew install --cask mactex` | `choco install miktex -y` |
| Pandoc | `sudo apt-get install -y pandoc` | `brew install pandoc` | `choco install pandoc -y` |
### Web / Frontend
| Tool | Linux | macOS | Windows |
|------|-------|-------|---------|
| Chrome | `sudo apt-get install -y chromium-browser` | `brew install --cask google-chrome` | Pre-installed or `winget install Google.Chrome` |
| Playwright | `pip install playwright && python -m playwright install chromium` | Same | Same |
| Node.js | `sudo apt-get install -y nodejs npm` | `brew install node` | `winget install OpenJS.NodeJS.LTS` |
### DevOps / Infrastructure
| Tool | Linux | macOS | Windows |
|------|-------|-------|---------|
| Docker | `sudo apt-get install -y docker.io` | `brew install --cask docker` | `winget install Docker.DockerDesktop` |
| kubectl | `sudo apt-get install -y kubectl` | `brew install kubectl` | `choco install kubernetes-cli -y` |
| Terraform | `sudo apt-get install -y terraform` | `brew install terraform` | `choco install terraform -y` |
---
## Environment Manifest Format (Cross-Platform)
After all installation and verification, output this JSON structure as your final artifact.
**Both `shell_prefix` and `shell_prefix_win` must be populated** when the environment needs activation:
```json
{
"environment_manifest": {
"platform": "linux",
"tools_installed": [
{
"name": "ffmpeg",
"version": "6.1",
"path": "/usr/bin/ffmpeg",
"path_win": "C:\\ProgramData\\chocolatey\\bin\\ffmpeg.exe",
"installed_by": "apt-get",
"installed_by_win": "choco",
"verified": true
}
],
"env_vars": {
"CUDA_HOME": "/usr/local/cuda"
},
"runtime_type": "conda",
"runtime_path": "/data2/conda_envs/video_env",
"activate_command": "conda activate video_env",
"shell_prefix": "source /data2/conda_envs/video_env/bin/activate",
"shell_prefix_win": "conda activate video_env",
"gpu_available": true,
"gpu_info": "NVIDIA RTX 4090, CUDA 12.1",
"verification_checks": [
{"command": "ffmpeg -version", "description": "FFmpeg available"},
{"command": "python3 -c 'import torch; assert torch.cuda.is_available()'", "description": "PyTorch GPU"}
],
"verification_checks_win": [
{"command": "ffmpeg -version", "description": "FFmpeg available"},
{"command": "python -c \"import torch; assert torch.cuda.is_available()\"", "description": "PyTorch GPU"}
],
"notes": "Conda env 'video_env' created. FFmpeg installed via apt. PyTorch 2.3 with CUDA 12.1."
}
}
```
### Field Reference
| Field | Purpose |
|-------|---------|
| `platform` | Detected OS: `linux`, `macos`, or `windows` |
| `tools_installed` | Tools with version, path, and install method per platform |
| `env_vars` | Environment variables for downstream (use forward slashes or platform-native) |
| `runtime_type` | `native` / `conda` / `venv` / `docker` / `remote` |
| `shell_prefix` | **Bash/sh** prefix auto-prepended to downstream shell commands |
| `shell_prefix_win` | **PowerShell** prefix auto-prepended on Windows |
| `verification_checks` | Bash commands to verify readiness |
| `verification_checks_win` | PowerShell commands to verify readiness on Windows |
| `gpu_available` | Whether GPU acceleration is available |
| `gpu_info` | GPU model, CUDA/MPS/ROCm version |
---
## Platform-Specific Activation Commands
| Runtime | Linux/macOS (shell_prefix) | Windows (shell_prefix_win) |
|---------|---------------------------|---------------------------|
| conda | `source activate <env>` or `conda activate <env>` | `conda activate <env>` |
| venv | `source /path/to/venv/bin/activate` | `/path/to/venv/Scripts/Activate.ps1` |
| uv venv | `source .venv/bin/activate` | `.venv\Scripts\Activate.ps1` |
| Docker | `docker run --rm -v $(pwd):/workspace <img>` | `docker run --rm -v ${PWD}:/workspace <img>` |
| nvm | `source ~/.nvm/nvm.sh && nvm use <ver>` | `nvm use <ver>` (nvm-windows) |
---
## Best Practices
- **Platform-first**: Always detect OS before running any install command
- **Idempotent**: Running the setup twice should not break anything
- **Non-interactive**: All commands must use `-y` / `--yes` / `--noconfirm` / `--accept-package-agreements`
- **Cross-platform manifest**: Always populate both `shell_prefix` + `shell_prefix_win` and both `verification_checks` + `verification_checks_win`
- **Minimal**: Only install what the task actually needs
- **Isolated**: Prefer virtual environments over global installs when possible
- **Documented**: Every installed tool should appear in the manifest
- **Verified**: Every critical tool should have a verification command
- **Recoverable**: If an install fails, report clearly what failed and why
- **Apple Silicon aware**: On macOS arm64, check if tools have native ARM builds
- **Windows paths**: Use forward slashes in JSON, or escape backslashes (`\\`)
+55
View File
@@ -0,0 +1,55 @@
---
name: external_agents
description: "External agent capability profiles and delegation guidance"
domain:
- general
- coding
- frontend
- backend
- devops
- writing
- documentation
- automation
always_on: true
trigger: "When deciding whether to use the native agent or an external CLI agent"
---
# External Agent Selection Skill
## Goal
Choose between the OPC native agent and available external agents based on the actual task,
the configured agent profile, and expected execution style. Do not rely on fixed domain
rules alone.
## Native Agent
- Strong default choice for lightweight reasoning, conversation, clarification, and tasks
that benefit from tight integration with OPC memory, organization, and tool orchestration.
- Prefer native when direct continuity inside OPC matters more than delegating to an
external CLI agent.
## External CLI Agents
- External agents such as Cursor, Claude Code, and Codex are usually strongest for complex,
tool-heavy, multi-step execution where a dedicated CLI agent can work in an isolated
workspace.
- Their strengths are not limited to writing code. They can also handle bash-driven tasks
such as generating or transforming Markdown, documents, PDFs, slide content, reports,
scripts, and repository-wide edits.
- When choosing among external agents, consider the configured model, whether the run should
start a new session or continue an existing one, and any extra CLI arguments already set.
## Decision Heuristics
- Prefer an external agent when the task requires sustained autonomous execution over files,
shell commands, or project artifacts.
- Prefer an external agent when the request is complex enough that a specialized coding or
CLI workflow is likely to outperform the native agent.
- Prefer native when the task is simple, mostly conversational, or better served by keeping
reasoning and tool use inside OPC itself.
- If multiple external agents are available, pick the one whose configured profile best
matches the task instead of following a fixed ranking.
## Approval And Autonomy
- Treat external agents as part of the same bounded-autonomy system as native tools.
- Routine, low-risk actions can be auto-approved when they match learned user preferences.
- Ambiguous, sensitive, or destructive operations should trigger escalation to the user.
- Learn from explicit user approvals or rejections so future runs behave more like the
user's trusted second self rather than a static automation pipeline.
+31
View File
@@ -0,0 +1,31 @@
---
name: web_search
description: "Effective web searching and information gathering"
domain:
- research
- general
trigger: "When needing to find information from the web"
always_on: false
---
# Web Search Skill
## When to Search
- Task requires up-to-date information (current events, latest versions, etc.)
- Technical documentation or API references needed
- Evaluating solutions, libraries, or tools
- Fact-checking or verification
## Search Strategy
1. Start with specific, targeted queries
2. If results are poor, broaden the query or try different keywords
3. Use `web_fetch` to read promising URLs for detailed information
4. Cross-reference multiple sources for accuracy
5. Summarize findings with source attribution
## Best Practices
- Use specific technical terms in queries
- Include version numbers when looking for docs
- Prefer official documentation over blog posts
- When evaluating tools, compare at least 2-3 options
- Always note the source of information
+34
View File
@@ -0,0 +1,34 @@
---
name: writing
description: "Document and content creation best practices"
domain:
- writing
- documentation
trigger: "When creating documents, reports, or written content"
always_on: false
---
# Writing Skill
## Document Types
- Technical documentation
- Reports and analyses
- Emails and communications
- Product requirements documents (PRD)
- README files
- Blog posts and articles
## Workflow
1. Understand the audience and purpose
2. Create an outline with clear structure
3. Write the first draft
4. Review for clarity, accuracy, and completeness
5. Format appropriately for the medium
## Best Practices
- Lead with the most important information
- Use clear, concise language
- Structure with headings and bullet points
- Include examples where helpful
- Maintain consistent tone and style
- Proofread before delivery
+60
View File
@@ -0,0 +1,60 @@
---
name: cron
description: Schedule reminders, recurring tasks, and one-time jobs using system crontab.
---
# Cron
Schedule tasks using the system crontab via `shell_exec`.
## List current cron jobs
```bash
crontab -l
```
## Add a recurring job
```bash
(crontab -l 2>/dev/null; echo "*/20 * * * * echo 'Time to take a break!' >> /tmp/opc-reminders.log") | crontab -
```
## Common schedules
| Schedule | Cron expression |
|----------|----------------|
| Every 20 minutes | `*/20 * * * *` |
| Every hour | `0 * * * *` |
| Every day at 8am | `0 8 * * *` |
| Weekdays at 5pm | `0 17 * * 1-5` |
| Every Monday at 9am | `0 9 * * 1` |
## One-time scheduled task
Use `at` for one-time jobs:
```bash
echo "echo 'Meeting reminder' >> /tmp/opc-reminders.log" | at 14:30
```
Or schedule with a specific date:
```bash
echo "echo 'Deadline reminder' >> /tmp/opc-reminders.log" | at 10:00 2026-03-20
```
## Remove a job
Edit the crontab directly:
```bash
crontab -e
```
Or filter out a specific job:
```bash
crontab -l | grep -v 'pattern-to-remove' | crontab -
```
## Notes
- Use full paths in cron commands (cron has a minimal `$PATH`).
- Redirect output to a log file or `/dev/null` to avoid mail noise.
- Check `at` availability: `which at` (install with `apt install at` if missing).
+48
View File
@@ -0,0 +1,48 @@
---
name: github
description: "Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries."
metadata: {"requires":{"bins":["gh"]}}
---
# GitHub Skill
Use the `gh` CLI to interact with GitHub. Always specify `--repo owner/repo` when not in a git directory, or use URLs directly.
## Pull Requests
Check CI status on a PR:
```bash
gh pr checks 55 --repo owner/repo
```
List recent workflow runs:
```bash
gh run list --repo owner/repo --limit 10
```
View a run and see which steps failed:
```bash
gh run view <run-id> --repo owner/repo
```
View logs for failed steps only:
```bash
gh run view <run-id> --repo owner/repo --log-failed
```
## API for Advanced Queries
The `gh api` command is useful for accessing data not available through other subcommands.
Get PR with specific fields:
```bash
gh api repos/owner/repo/pulls/55 --jq '.title, .state, .user.login'
```
## JSON Output
Most commands support `--json` for structured output. You can use `--jq` to filter:
```bash
gh issue list --repo owner/repo --json number,title --jq '.[] | "\(.number): \(.title)"'
```
+26
View File
@@ -0,0 +1,26 @@
---
name: memory
description: Manage durable global/project memory in `.opc/memory/global.md` and `.opc/memory/projects/<current_project_id>.md`.
always: true
---
# Memory
Use this skill only for durable Markdown memory:
- Global memory: `.opc/memory/global.md`
- Project memory: `.opc/memory/projects/<current_project_id>.md`
- Prefer the absolute `OPC_GLOBAL_MEMORY_PATH` and `OPC_PROJECT_MEMORY_PATH` shown in runtime context or environment; do not create a separate `.opc/memory` under the workplace.
## What To Save
- Global: stable cross-project user preferences, communication defaults, standing constraints.
- Project: current-project preferences, workspace overrides, repo paths, architecture constraints, coding conventions, delivery requirements.
- Never save secrets, copied transcripts, temporary progress, speculative notes, or one-off task results.
## When to Update
- Update only during interaction with the user, and only when the user states or confirms something that should matter in later sessions.
## How to Update
Read before editing. Merge, deduplicate, replace stale items, and keep entries compact. Do not mix project IDs: write project memory only to the current project's file.
+110
View File
@@ -0,0 +1,110 @@
---
name: skill-creator
description: Create or update skills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
---
# Skill Creator
Guidance for creating effective skills.
## About Skills
Skills are modular packages that extend agent capabilities with specialized knowledge, workflows, and tools. Each skill is a directory containing a `SKILL.md` file and optional bundled resources.
### What Skills Provide
1. Specialized workflows - Multi-step procedures for specific domains
2. Tool integrations - Instructions for working with specific file formats or APIs
3. Domain expertise - Project-specific knowledge, schemas, business logic
4. Bundled resources - Scripts, references, and assets for complex and repetitive tasks
### Skill Locations in OPC
OPC uses a two-level skill system:
| Level | Path | Scope |
|-------|------|-------|
| System | `.opc/skills/<skill-name>/` | Shared across all projects |
| Project | `.opc/projects/<project_id>/skills/<skill-name>/` | Specific to one project |
Project skills with the same name override system skills. When creating new skills from project experience, always use the **project** level.
### How Skills Are Created
Skills enter the system through two paths:
1. **Agent-driven** (this skill): You identify a pattern, read this guide, and create the skill manually using `init_skill.py` or `file_write`.
2. **Auto-promoted**: The system automatically distills playbook skills from repeated project reflections (threshold: 2 reflections with recurring patterns). These are saved as `<employee>-<role>-<domain>-playbook` under the project skills directory.
Both paths produce the same `<skill-name>/SKILL.md` format. This guide covers the agent-driven path; auto-promoted skills follow the same naming and format conventions.
## Core Principles
### Concise is Key
The context window is shared. Only add context the agent doesn't already have. Prefer concise examples over verbose explanations.
### Anatomy of a Skill
```
skill-name/
├── SKILL.md (required)
│ ├── YAML frontmatter (name, description required)
│ └── Markdown instructions
└── Bundled Resources (optional)
├── scripts/ - Executable code
├── references/ - Documentation loaded as needed
└── assets/ - Files used in output
```
### Progressive Disclosure
1. **Metadata (name + description)** - Always in context (~100 words)
2. **SKILL.md body** - When agent reads the skill (<5k words)
3. **Bundled resources** - As needed (scripts can be executed without reading into context)
## Naming
All skills — whether agent-created or auto-promoted — must follow these conventions:
- Lowercase letters, digits, and hyphens only (e.g., `backend-api-playbook`)
- Under 64 characters
- Directory name must match the `name` field in frontmatter
## Skill Creation Process
### Step 1: Understand the skill with concrete examples
Clarify use cases: what triggers this skill, what does it produce?
### Step 2: Plan reusable contents
Identify what scripts, references, and assets would help.
### Step 3: Initialize the skill
For project-specific skills, create under `.opc/projects/<project_id>/skills/`:
```bash
python3 {baseDir}/scripts/init_skill.py <skill-name> --path .opc/projects/<project_id>/skills
```
Options: `--resources scripts,references,assets` and `--examples`.
### Step 4: Edit the skill
Write SKILL.md with:
- **Frontmatter**: `name` (hyphen-case, matches directory) and `description` (what it does + when to use it — this is the primary trigger for skill discovery)
- **Body**: Instructions, examples, references to bundled resources
### Step 5: Package (optional)
```bash
python3 {baseDir}/scripts/package_skill.py <path/to/skill-folder>
```
Validates the skill then creates a distributable `.skill` zip file.
### Step 6: Iterate
Test, notice struggles, improve SKILL.md and resources.
+378
View File
@@ -0,0 +1,378 @@
#!/usr/bin/env python3
"""
Skill Initializer - Creates a new skill from template
Usage:
init_skill.py <skill-name> --path <path> [--resources scripts,references,assets] [--examples]
Examples:
init_skill.py my-new-skill --path skills/public
init_skill.py my-new-skill --path skills/public --resources scripts,references
init_skill.py my-api-helper --path skills/private --resources scripts --examples
init_skill.py custom-skill --path /custom/location
"""
import argparse
import re
import sys
from pathlib import Path
MAX_SKILL_NAME_LENGTH = 64
ALLOWED_RESOURCES = {"scripts", "references", "assets"}
SKILL_TEMPLATE = """---
name: {skill_name}
description: [TODO: Complete and informative explanation of what the skill does and when to use it. Include WHEN to use this skill - specific scenarios, file types, or tasks that trigger it.]
---
# {skill_title}
## Overview
[TODO: 1-2 sentences explaining what this skill enables]
## Structuring This Skill
[TODO: Choose the structure that best fits this skill's purpose. Common patterns:
**1. Workflow-Based** (best for sequential processes)
- Works well when there are clear step-by-step procedures
- Example: DOCX skill with "Workflow Decision Tree" -> "Reading" -> "Creating" -> "Editing"
- Structure: ## Overview -> ## Workflow Decision Tree -> ## Step 1 -> ## Step 2...
**2. Task-Based** (best for tool collections)
- Works well when the skill offers different operations/capabilities
- Example: PDF skill with "Quick Start" -> "Merge PDFs" -> "Split PDFs" -> "Extract Text"
- Structure: ## Overview -> ## Quick Start -> ## Task Category 1 -> ## Task Category 2...
**3. Reference/Guidelines** (best for standards or specifications)
- Works well for brand guidelines, coding standards, or requirements
- Example: Brand styling with "Brand Guidelines" -> "Colors" -> "Typography" -> "Features"
- Structure: ## Overview -> ## Guidelines -> ## Specifications -> ## Usage...
**4. Capabilities-Based** (best for integrated systems)
- Works well when the skill provides multiple interrelated features
- Example: Product Management with "Core Capabilities" -> numbered capability list
- Structure: ## Overview -> ## Core Capabilities -> ### 1. Feature -> ### 2. Feature...
Patterns can be mixed and matched as needed. Most skills combine patterns (e.g., start with task-based, add workflow for complex operations).
Delete this entire "Structuring This Skill" section when done - it's just guidance.]
## [TODO: Replace with the first main section based on chosen structure]
[TODO: Add content here. See examples in existing skills:
- Code samples for technical skills
- Decision trees for complex workflows
- Concrete examples with realistic user requests
- References to scripts/templates/references as needed]
## Resources (optional)
Create only the resource directories this skill actually needs. Delete this section if no resources are required.
### scripts/
Executable code (Python/Bash/etc.) that can be run directly to perform specific operations.
**Examples from other skills:**
- PDF skill: `fill_fillable_fields.py`, `extract_form_field_info.py` - utilities for PDF manipulation
- DOCX skill: `document.py`, `utilities.py` - Python modules for document processing
**Appropriate for:** Python scripts, shell scripts, or any executable code that performs automation, data processing, or specific operations.
**Note:** Scripts may be executed without loading into context, but can still be read by Codex for patching or environment adjustments.
### references/
Documentation and reference material intended to be loaded into context to inform Codex's process and thinking.
**Examples from other skills:**
- Product management: `communication.md`, `context_building.md` - detailed workflow guides
- BigQuery: API reference documentation and query examples
- Finance: Schema documentation, company policies
**Appropriate for:** In-depth documentation, API references, database schemas, comprehensive guides, or any detailed information that Codex should reference while working.
### assets/
Files not intended to be loaded into context, but rather used within the output Codex produces.
**Examples from other skills:**
- Brand styling: PowerPoint template files (.pptx), logo files
- Frontend builder: HTML/React boilerplate project directories
- Typography: Font files (.ttf, .woff2)
**Appropriate for:** Templates, boilerplate code, document templates, images, icons, fonts, or any files meant to be copied or used in the final output.
---
**Not every skill requires all three types of resources.**
"""
EXAMPLE_SCRIPT = '''#!/usr/bin/env python3
"""
Example helper script for {skill_name}
This is a placeholder script that can be executed directly.
Replace with actual implementation or delete if not needed.
Example real scripts from other skills:
- pdf/scripts/fill_fillable_fields.py - Fills PDF form fields
- pdf/scripts/convert_pdf_to_images.py - Converts PDF pages to images
"""
def main():
print("This is an example script for {skill_name}")
# TODO: Add actual script logic here
# This could be data processing, file conversion, API calls, etc.
if __name__ == "__main__":
main()
'''
EXAMPLE_REFERENCE = """# Reference Documentation for {skill_title}
This is a placeholder for detailed reference documentation.
Replace with actual reference content or delete if not needed.
Example real reference docs from other skills:
- product-management/references/communication.md - Comprehensive guide for status updates
- product-management/references/context_building.md - Deep-dive on gathering context
- bigquery/references/ - API references and query examples
## When Reference Docs Are Useful
Reference docs are ideal for:
- Comprehensive API documentation
- Detailed workflow guides
- Complex multi-step processes
- Information too lengthy for main SKILL.md
- Content that's only needed for specific use cases
## Structure Suggestions
### API Reference Example
- Overview
- Authentication
- Endpoints with examples
- Error codes
- Rate limits
### Workflow Guide Example
- Prerequisites
- Step-by-step instructions
- Common patterns
- Troubleshooting
- Best practices
"""
EXAMPLE_ASSET = """# Example Asset File
This placeholder represents where asset files would be stored.
Replace with actual asset files (templates, images, fonts, etc.) or delete if not needed.
Asset files are NOT intended to be loaded into context, but rather used within
the output Codex produces.
Example asset files from other skills:
- Brand guidelines: logo.png, slides_template.pptx
- Frontend builder: hello-world/ directory with HTML/React boilerplate
- Typography: custom-font.ttf, font-family.woff2
- Data: sample_data.csv, test_dataset.json
## Common Asset Types
- Templates: .pptx, .docx, boilerplate directories
- Images: .png, .jpg, .svg, .gif
- Fonts: .ttf, .otf, .woff, .woff2
- Boilerplate code: Project directories, starter files
- Icons: .ico, .svg
- Data files: .csv, .json, .xml, .yaml
Note: This is a text placeholder. Actual assets can be any file type.
"""
def normalize_skill_name(skill_name):
"""Normalize a skill name to lowercase hyphen-case."""
normalized = skill_name.strip().lower()
normalized = re.sub(r"[^a-z0-9]+", "-", normalized)
normalized = normalized.strip("-")
normalized = re.sub(r"-{2,}", "-", normalized)
return normalized
def title_case_skill_name(skill_name):
"""Convert hyphenated skill name to Title Case for display."""
return " ".join(word.capitalize() for word in skill_name.split("-"))
def parse_resources(raw_resources):
if not raw_resources:
return []
resources = [item.strip() for item in raw_resources.split(",") if item.strip()]
invalid = sorted({item for item in resources if item not in ALLOWED_RESOURCES})
if invalid:
allowed = ", ".join(sorted(ALLOWED_RESOURCES))
print(f"[ERROR] Unknown resource type(s): {', '.join(invalid)}")
print(f" Allowed: {allowed}")
sys.exit(1)
deduped = []
seen = set()
for resource in resources:
if resource not in seen:
deduped.append(resource)
seen.add(resource)
return deduped
def create_resource_dirs(skill_dir, skill_name, skill_title, resources, include_examples):
for resource in resources:
resource_dir = skill_dir / resource
resource_dir.mkdir(exist_ok=True)
if resource == "scripts":
if include_examples:
example_script = resource_dir / "example.py"
example_script.write_text(EXAMPLE_SCRIPT.format(skill_name=skill_name))
example_script.chmod(0o755)
print("[OK] Created scripts/example.py")
else:
print("[OK] Created scripts/")
elif resource == "references":
if include_examples:
example_reference = resource_dir / "api_reference.md"
example_reference.write_text(EXAMPLE_REFERENCE.format(skill_title=skill_title))
print("[OK] Created references/api_reference.md")
else:
print("[OK] Created references/")
elif resource == "assets":
if include_examples:
example_asset = resource_dir / "example_asset.txt"
example_asset.write_text(EXAMPLE_ASSET)
print("[OK] Created assets/example_asset.txt")
else:
print("[OK] Created assets/")
def init_skill(skill_name, path, resources, include_examples):
"""
Initialize a new skill directory with template SKILL.md.
Args:
skill_name: Name of the skill
path: Path where the skill directory should be created
resources: Resource directories to create
include_examples: Whether to create example files in resource directories
Returns:
Path to created skill directory, or None if error
"""
# Determine skill directory path
skill_dir = Path(path).resolve() / skill_name
# Check if directory already exists
if skill_dir.exists():
print(f"[ERROR] Skill directory already exists: {skill_dir}")
return None
# Create skill directory
try:
skill_dir.mkdir(parents=True, exist_ok=False)
print(f"[OK] Created skill directory: {skill_dir}")
except Exception as e:
print(f"[ERROR] Error creating directory: {e}")
return None
# Create SKILL.md from template
skill_title = title_case_skill_name(skill_name)
skill_content = SKILL_TEMPLATE.format(skill_name=skill_name, skill_title=skill_title)
skill_md_path = skill_dir / "SKILL.md"
try:
skill_md_path.write_text(skill_content)
print("[OK] Created SKILL.md")
except Exception as e:
print(f"[ERROR] Error creating SKILL.md: {e}")
return None
# Create resource directories if requested
if resources:
try:
create_resource_dirs(skill_dir, skill_name, skill_title, resources, include_examples)
except Exception as e:
print(f"[ERROR] Error creating resource directories: {e}")
return None
# Print next steps
print(f"\n[OK] Skill '{skill_name}' initialized successfully at {skill_dir}")
print("\nNext steps:")
print("1. Edit SKILL.md to complete the TODO items and update the description")
if resources:
if include_examples:
print("2. Customize or delete the example files in scripts/, references/, and assets/")
else:
print("2. Add resources to scripts/, references/, and assets/ as needed")
else:
print("2. Create resource directories only if needed (scripts/, references/, assets/)")
print("3. Run the validator when ready to check the skill structure")
return skill_dir
def main():
parser = argparse.ArgumentParser(
description="Create a new skill directory with a SKILL.md template.",
)
parser.add_argument("skill_name", help="Skill name (normalized to hyphen-case)")
parser.add_argument("--path", required=True, help="Output directory for the skill")
parser.add_argument(
"--resources",
default="",
help="Comma-separated list: scripts,references,assets",
)
parser.add_argument(
"--examples",
action="store_true",
help="Create example files inside the selected resource directories",
)
args = parser.parse_args()
raw_skill_name = args.skill_name
skill_name = normalize_skill_name(raw_skill_name)
if not skill_name:
print("[ERROR] Skill name must include at least one letter or digit.")
sys.exit(1)
if len(skill_name) > MAX_SKILL_NAME_LENGTH:
print(
f"[ERROR] Skill name '{skill_name}' is too long ({len(skill_name)} characters). "
f"Maximum is {MAX_SKILL_NAME_LENGTH} characters."
)
sys.exit(1)
if skill_name != raw_skill_name:
print(f"Note: Normalized skill name from '{raw_skill_name}' to '{skill_name}'.")
resources = parse_resources(args.resources)
if args.examples and not resources:
print("[ERROR] --examples requires --resources to be set.")
sys.exit(1)
path = args.path
print(f"Initializing skill: {skill_name}")
print(f" Location: {path}")
if resources:
print(f" Resources: {', '.join(resources)}")
if args.examples:
print(" Examples: enabled")
else:
print(" Resources: none (create as needed)")
print()
result = init_skill(skill_name, path, resources, args.examples)
if result:
sys.exit(0)
else:
sys.exit(1)
if __name__ == "__main__":
main()
+154
View File
@@ -0,0 +1,154 @@
#!/usr/bin/env python3
"""
Skill Packager - Creates a distributable .skill file of a skill folder
Usage:
python package_skill.py <path/to/skill-folder> [output-directory]
Example:
python package_skill.py skills/public/my-skill
python package_skill.py skills/public/my-skill ./dist
"""
import sys
import zipfile
from pathlib import Path
from quick_validate import validate_skill
def _is_within(path: Path, root: Path) -> bool:
try:
path.relative_to(root)
return True
except ValueError:
return False
def _cleanup_partial_archive(skill_filename: Path) -> None:
try:
if skill_filename.exists():
skill_filename.unlink()
except OSError:
pass
def package_skill(skill_path, output_dir=None):
"""
Package a skill folder into a .skill file.
Args:
skill_path: Path to the skill folder
output_dir: Optional output directory for the .skill file (defaults to current directory)
Returns:
Path to the created .skill file, or None if error
"""
skill_path = Path(skill_path).resolve()
# Validate skill folder exists
if not skill_path.exists():
print(f"[ERROR] Skill folder not found: {skill_path}")
return None
if not skill_path.is_dir():
print(f"[ERROR] Path is not a directory: {skill_path}")
return None
# Validate SKILL.md exists
skill_md = skill_path / "SKILL.md"
if not skill_md.exists():
print(f"[ERROR] SKILL.md not found in {skill_path}")
return None
# Run validation before packaging
print("Validating skill...")
valid, message = validate_skill(skill_path)
if not valid:
print(f"[ERROR] Validation failed: {message}")
print(" Please fix the validation errors before packaging.")
return None
print(f"[OK] {message}\n")
# Determine output location
skill_name = skill_path.name
if output_dir:
output_path = Path(output_dir).resolve()
output_path.mkdir(parents=True, exist_ok=True)
else:
output_path = Path.cwd()
skill_filename = output_path / f"{skill_name}.skill"
EXCLUDED_DIRS = {".git", ".svn", ".hg", "__pycache__", "node_modules"}
files_to_package = []
resolved_archive = skill_filename.resolve()
for file_path in skill_path.rglob("*"):
# Fail closed on symlinks so the packaged contents are explicit and predictable.
if file_path.is_symlink():
print(f"[ERROR] Symlink not allowed in packaged skill: {file_path}")
_cleanup_partial_archive(skill_filename)
return None
rel_parts = file_path.relative_to(skill_path).parts
if any(part in EXCLUDED_DIRS for part in rel_parts):
continue
if file_path.is_file():
resolved_file = file_path.resolve()
if not _is_within(resolved_file, skill_path):
print(f"[ERROR] File escapes skill root: {file_path}")
_cleanup_partial_archive(skill_filename)
return None
# If output lives under skill_path, avoid writing archive into itself.
if resolved_file == resolved_archive:
print(f"[WARN] Skipping output archive: {file_path}")
continue
files_to_package.append(file_path)
# Create the .skill file (zip format)
try:
with zipfile.ZipFile(skill_filename, "w", zipfile.ZIP_DEFLATED) as zipf:
for file_path in files_to_package:
# Calculate the relative path within the zip.
arcname = Path(skill_name) / file_path.relative_to(skill_path)
zipf.write(file_path, arcname)
print(f" Added: {arcname}")
print(f"\n[OK] Successfully packaged skill to: {skill_filename}")
return skill_filename
except Exception as e:
_cleanup_partial_archive(skill_filename)
print(f"[ERROR] Error creating .skill file: {e}")
return None
def main():
if len(sys.argv) < 2:
print("Usage: python package_skill.py <path/to/skill-folder> [output-directory]")
print("\nExample:")
print(" python package_skill.py skills/public/my-skill")
print(" python package_skill.py skills/public/my-skill ./dist")
sys.exit(1)
skill_path = sys.argv[1]
output_dir = sys.argv[2] if len(sys.argv) > 2 else None
print(f"Packaging skill: {skill_path}")
if output_dir:
print(f" Output directory: {output_dir}")
print()
result = package_skill(skill_path, output_dir)
if result:
sys.exit(0)
else:
sys.exit(1)
if __name__ == "__main__":
main()
@@ -0,0 +1,214 @@
#!/usr/bin/env python3
"""
Minimal validator for nanobot skill folders.
"""
import re
import sys
from pathlib import Path
from typing import Optional
try:
import yaml
except ModuleNotFoundError:
yaml = None
MAX_SKILL_NAME_LENGTH = 64
ALLOWED_FRONTMATTER_KEYS = {
"name",
"description",
"metadata",
"always",
"license",
"allowed-tools",
"homepage",
}
ALLOWED_RESOURCE_DIRS = {"scripts", "references", "assets"}
PLACEHOLDER_MARKERS = ("[todo", "todo:")
def _extract_frontmatter(content: str) -> Optional[str]:
lines = content.splitlines()
if not lines or lines[0].strip() != "---":
return None
for i in range(1, len(lines)):
if lines[i].strip() == "---":
return "\n".join(lines[1:i])
return None
def _parse_simple_frontmatter(frontmatter_text: str) -> Optional[dict[str, str]]:
"""Fallback parser for simple frontmatter when PyYAML is unavailable."""
parsed: dict[str, str] = {}
current_key: Optional[str] = None
multiline_key: Optional[str] = None
for raw_line in frontmatter_text.splitlines():
stripped = raw_line.strip()
if not stripped or stripped.startswith("#"):
continue
is_indented = raw_line[:1].isspace()
if is_indented:
if current_key is None:
return None
current_value = parsed[current_key]
parsed[current_key] = f"{current_value}\n{stripped}" if current_value else stripped
continue
if ":" not in stripped:
return None
key, value = stripped.split(":", 1)
key = key.strip()
value = value.strip()
if not key:
return None
if value in {"|", ">"}:
parsed[key] = ""
current_key = key
multiline_key = key
continue
if (value.startswith('"') and value.endswith('"')) or (
value.startswith("'") and value.endswith("'")
):
value = value[1:-1]
parsed[key] = value
current_key = key
multiline_key = None
if multiline_key is not None and multiline_key not in parsed:
return None
return parsed
def _load_frontmatter(frontmatter_text: str) -> tuple[Optional[dict], Optional[str]]:
if yaml is not None:
try:
frontmatter = yaml.safe_load(frontmatter_text)
except yaml.YAMLError as exc:
return None, f"Invalid YAML in frontmatter: {exc}"
if not isinstance(frontmatter, dict):
return None, "Frontmatter must be a YAML dictionary"
return frontmatter, None
frontmatter = _parse_simple_frontmatter(frontmatter_text)
if frontmatter is None:
return None, "Invalid YAML in frontmatter: unsupported syntax without PyYAML installed"
return frontmatter, None
def _validate_skill_name(name: str, folder_name: str) -> Optional[str]:
if not re.fullmatch(r"[a-z0-9]+(?:-[a-z0-9]+)*", name):
return (
f"Name '{name}' should be hyphen-case "
"(lowercase letters, digits, and single hyphens only)"
)
if len(name) > MAX_SKILL_NAME_LENGTH:
return (
f"Name is too long ({len(name)} characters). "
f"Maximum is {MAX_SKILL_NAME_LENGTH} characters."
)
if name != folder_name:
return f"Skill name '{name}' must match directory name '{folder_name}'"
return None
def _validate_description(description: str) -> Optional[str]:
trimmed = description.strip()
if not trimmed:
return "Description cannot be empty"
lowered = trimmed.lower()
if any(marker in lowered for marker in PLACEHOLDER_MARKERS):
return "Description still contains TODO placeholder text"
if "<" in trimmed or ">" in trimmed:
return "Description cannot contain angle brackets (< or >)"
if len(trimmed) > 1024:
return f"Description is too long ({len(trimmed)} characters). Maximum is 1024 characters."
return None
def validate_skill(skill_path):
"""Validate a skill folder structure and required frontmatter."""
skill_path = Path(skill_path).resolve()
if not skill_path.exists():
return False, f"Skill folder not found: {skill_path}"
if not skill_path.is_dir():
return False, f"Path is not a directory: {skill_path}"
skill_md = skill_path / "SKILL.md"
if not skill_md.exists():
return False, "SKILL.md not found"
try:
content = skill_md.read_text(encoding="utf-8")
except OSError as exc:
return False, f"Could not read SKILL.md: {exc}"
frontmatter_text = _extract_frontmatter(content)
if frontmatter_text is None:
return False, "Invalid frontmatter format"
frontmatter, error = _load_frontmatter(frontmatter_text)
if error:
return False, error
unexpected_keys = sorted(set(frontmatter.keys()) - ALLOWED_FRONTMATTER_KEYS)
if unexpected_keys:
allowed = ", ".join(sorted(ALLOWED_FRONTMATTER_KEYS))
unexpected = ", ".join(unexpected_keys)
return (
False,
f"Unexpected key(s) in SKILL.md frontmatter: {unexpected}. Allowed properties are: {allowed}",
)
if "name" not in frontmatter:
return False, "Missing 'name' in frontmatter"
if "description" not in frontmatter:
return False, "Missing 'description' in frontmatter"
name = frontmatter["name"]
if not isinstance(name, str):
return False, f"Name must be a string, got {type(name).__name__}"
name_error = _validate_skill_name(name.strip(), skill_path.name)
if name_error:
return False, name_error
description = frontmatter["description"]
if not isinstance(description, str):
return False, f"Description must be a string, got {type(description).__name__}"
description_error = _validate_description(description)
if description_error:
return False, description_error
always = frontmatter.get("always")
if always is not None and not isinstance(always, bool):
return False, f"'always' must be a boolean, got {type(always).__name__}"
for child in skill_path.iterdir():
if child.name == "SKILL.md":
continue
if child.is_dir() and child.name in ALLOWED_RESOURCE_DIRS:
continue
if child.is_symlink():
continue
return (
False,
f"Unexpected file or directory in skill root: {child.name}. "
"Only SKILL.md, scripts/, references/, and assets/ are allowed.",
)
return True, "Skill is valid!"
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python quick_validate.py <skill_directory>")
sys.exit(1)
valid, message = validate_skill(sys.argv[1])
print(message)
sys.exit(0 if valid else 1)
+50
View File
@@ -0,0 +1,50 @@
---
name: skill-evolution
description: Evolve new skills from project experience. Use when a repeating pattern, useful workflow, or hard-won knowledge should be captured as a reusable skill for the current project.
---
# Skill Evolution
Distill project experience into reusable skills that live under the current project.
## Two evolution paths
1. **Automatic**: The system promotes playbook skills when an employee's pattern reaches 2 project reflections with recurring behaviors, checklists, or preferences. These are saved as `<employee>-<role>-<domain>-playbook` and require no manual action.
2. **Agent-driven** (this skill): You proactively identify valuable patterns and create skills manually. Use this when domain knowledge, workflows, or tool sequences should be preserved before the auto-promotion threshold is reached, or when the knowledge is not tied to a specific employee pattern.
## When to evolve a skill (agent-driven)
- A task required non-obvious steps that will likely recur
- You discovered a workflow that worked well and should be preserved
- Domain-specific knowledge was gathered that future tasks will need
- A sequence of tool calls forms a reliable pattern worth codifying
## Process
1. **Identify the pattern**: What knowledge or workflow is worth preserving?
2. **Read the skill-creator skill**: `file_read` the `skill-creator/SKILL.md` for format and naming guidelines
3. **Name the skill**: Lowercase letters, digits, and hyphens only, under 64 characters (e.g., `api-validation-workflow`)
4. **Create the skill directory**: Under `.opc/projects/<project_id>/skills/<skill-name>/`
5. **Write SKILL.md** with proper frontmatter (`name`, `description`) and concise instructions
6. **Add scripts/references/assets** if the skill benefits from bundled resources
## Skill location
Evolved skills belong to the project that produced them:
```
.opc/projects/<project_id>/skills/<skill-name>/SKILL.md
```
## Guidelines
- Keep it concise: only include what the agent doesn't already know
- Use concrete examples over abstract explanations
- Include the minimal set of steps needed to reproduce the workflow
- Add `scripts/` for deterministic operations that get rewritten repeatedly
- Add `references/` for domain docs the agent should consult
- Follow the same naming conventions as auto-promoted skills (hyphen-case)
## Cross-project reuse
Other projects can reference this project's skills via `file_read` when the secretary recommends them. Do not duplicate skills across projects; read from the source project instead.
+106
View File
@@ -0,0 +1,106 @@
---
name: tmux
description: Remote-control tmux sessions for interactive CLIs by sending keystrokes and scraping pane output.
metadata: {"requires":{"bins":["tmux"]}}
---
# tmux Skill
Use tmux only when you need an interactive TTY. Prefer `shell_exec` for long-running, non-interactive tasks.
## Quickstart (isolated socket)
```bash
SOCKET_DIR="${OPC_TMUX_SOCKET_DIR:-${TMPDIR:-/tmp}/opc-tmux-sockets}"
mkdir -p "$SOCKET_DIR"
SOCKET="$SOCKET_DIR/opc.sock"
SESSION=opc-python
tmux -S "$SOCKET" new -d -s "$SESSION" -n shell
tmux -S "$SOCKET" send-keys -t "$SESSION":0.0 -- 'PYTHON_BASIC_REPL=1 python3 -q' Enter
tmux -S "$SOCKET" capture-pane -p -J -t "$SESSION":0.0 -S -200
```
After starting a session, always print monitor commands:
```
To monitor:
tmux -S "$SOCKET" attach -t "$SESSION"
tmux -S "$SOCKET" capture-pane -p -J -t "$SESSION":0.0 -S -200
```
## Socket convention
- Use `OPC_TMUX_SOCKET_DIR` environment variable.
- Default socket path: `"$OPC_TMUX_SOCKET_DIR/opc.sock"`.
## Targeting panes and naming
- Target format: `session:window.pane` (defaults to `:0.0`).
- Keep names short; avoid spaces.
- Inspect: `tmux -S "$SOCKET" list-sessions`, `tmux -S "$SOCKET" list-panes -a`.
## Finding sessions
- List sessions on your socket: `{baseDir}/scripts/find-sessions.sh -S "$SOCKET"`.
- Scan all sockets: `{baseDir}/scripts/find-sessions.sh --all` (uses `OPC_TMUX_SOCKET_DIR`).
## Sending input safely
- Prefer literal sends: `tmux -S "$SOCKET" send-keys -t target -l -- "$cmd"`.
- Control keys: `tmux -S "$SOCKET" send-keys -t target C-c`.
## Watching output
- Capture recent history: `tmux -S "$SOCKET" capture-pane -p -J -t target -S -200`.
- Wait for prompts: `{baseDir}/scripts/wait-for-text.sh -t session:0.0 -p 'pattern'`.
- Attaching is OK; detach with `Ctrl+b d`.
## Spawning processes
- For python REPLs, set `PYTHON_BASIC_REPL=1` (non-basic REPL breaks send-keys flows).
## Orchestrating Coding Agents
tmux excels at running multiple coding agents in parallel:
```bash
SOCKET="${TMPDIR:-/tmp}/opc-army.sock"
for i in 1 2 3 4 5; do
tmux -S "$SOCKET" new-session -d -s "agent-$i"
done
tmux -S "$SOCKET" send-keys -t agent-1 "cd /tmp/project1 && codex --yolo 'Fix bug X'" Enter
tmux -S "$SOCKET" send-keys -t agent-2 "cd /tmp/project2 && codex --yolo 'Fix bug Y'" Enter
for sess in agent-1 agent-2; do
if tmux -S "$SOCKET" capture-pane -p -t "$sess" -S -3 | grep -q ""; then
echo "$sess: DONE"
else
echo "$sess: Running..."
fi
done
tmux -S "$SOCKET" capture-pane -p -t agent-1 -S -500
```
## Cleanup
- Kill a session: `tmux -S "$SOCKET" kill-session -t "$SESSION"`.
- Kill all sessions on a socket: `tmux -S "$SOCKET" list-sessions -F '#{session_name}' | xargs -r -n1 tmux -S "$SOCKET" kill-session -t`.
- Remove everything on the private socket: `tmux -S "$SOCKET" kill-server`.
## Helper: wait-for-text.sh
`{baseDir}/scripts/wait-for-text.sh` polls a pane for a regex (or fixed string) with a timeout.
```bash
{baseDir}/scripts/wait-for-text.sh -t session:0.0 -p 'pattern' [-F] [-T 20] [-i 0.5] [-l 2000]
```
- `-t`/`--target` pane target (required)
- `-p`/`--pattern` regex to match (required); add `-F` for fixed string
- `-T` timeout seconds (integer, default 15)
- `-i` poll interval seconds (default 0.5)
- `-l` history lines to search (integer, default 1000)
+112
View File
@@ -0,0 +1,112 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'USAGE'
Usage: find-sessions.sh [-L socket-name|-S socket-path|-A] [-q pattern]
List tmux sessions on a socket (default tmux socket if none provided).
Options:
-L, --socket tmux socket name (passed to tmux -L)
-S, --socket-path tmux socket path (passed to tmux -S)
-A, --all scan all sockets under NANOBOT_TMUX_SOCKET_DIR
-q, --query case-insensitive substring to filter session names
-h, --help show this help
USAGE
}
socket_name=""
socket_path=""
query=""
scan_all=false
socket_dir="${OPC_TMUX_SOCKET_DIR:-${TMPDIR:-/tmp}/opc-tmux-sockets}"
while [[ $# -gt 0 ]]; do
case "$1" in
-L|--socket) socket_name="${2-}"; shift 2 ;;
-S|--socket-path) socket_path="${2-}"; shift 2 ;;
-A|--all) scan_all=true; shift ;;
-q|--query) query="${2-}"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) echo "Unknown option: $1" >&2; usage; exit 1 ;;
esac
done
if [[ "$scan_all" == true && ( -n "$socket_name" || -n "$socket_path" ) ]]; then
echo "Cannot combine --all with -L or -S" >&2
exit 1
fi
if [[ -n "$socket_name" && -n "$socket_path" ]]; then
echo "Use either -L or -S, not both" >&2
exit 1
fi
if ! command -v tmux >/dev/null 2>&1; then
echo "tmux not found in PATH" >&2
exit 1
fi
list_sessions() {
local label="$1"; shift
local tmux_cmd=(tmux "$@")
if ! sessions="$("${tmux_cmd[@]}" list-sessions -F '#{session_name}\t#{session_attached}\t#{session_created_string}' 2>/dev/null)"; then
echo "No tmux server found on $label" >&2
return 1
fi
if [[ -n "$query" ]]; then
sessions="$(printf '%s\n' "$sessions" | grep -i -- "$query" || true)"
fi
if [[ -z "$sessions" ]]; then
echo "No sessions found on $label"
return 0
fi
echo "Sessions on $label:"
printf '%s\n' "$sessions" | while IFS=$'\t' read -r name attached created; do
attached_label=$([[ "$attached" == "1" ]] && echo "attached" || echo "detached")
printf ' - %s (%s, started %s)\n' "$name" "$attached_label" "$created"
done
}
if [[ "$scan_all" == true ]]; then
if [[ ! -d "$socket_dir" ]]; then
echo "Socket directory not found: $socket_dir" >&2
exit 1
fi
shopt -s nullglob
sockets=("$socket_dir"/*)
shopt -u nullglob
if [[ "${#sockets[@]}" -eq 0 ]]; then
echo "No sockets found under $socket_dir" >&2
exit 1
fi
exit_code=0
for sock in "${sockets[@]}"; do
if [[ ! -S "$sock" ]]; then
continue
fi
list_sessions "socket path '$sock'" -S "$sock" || exit_code=$?
done
exit "$exit_code"
fi
tmux_cmd=(tmux)
socket_label="default socket"
if [[ -n "$socket_name" ]]; then
tmux_cmd+=(-L "$socket_name")
socket_label="socket name '$socket_name'"
elif [[ -n "$socket_path" ]]; then
tmux_cmd+=(-S "$socket_path")
socket_label="socket path '$socket_path'"
fi
list_sessions "$socket_label" "${tmux_cmd[@]:1}"
+83
View File
@@ -0,0 +1,83 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'USAGE'
Usage: wait-for-text.sh -t target -p pattern [options]
Poll a tmux pane for text and exit when found.
Options:
-t, --target tmux target (session:window.pane), required
-p, --pattern regex pattern to look for, required
-F, --fixed treat pattern as a fixed string (grep -F)
-T, --timeout seconds to wait (integer, default: 15)
-i, --interval poll interval in seconds (default: 0.5)
-l, --lines number of history lines to inspect (integer, default: 1000)
-h, --help show this help
USAGE
}
target=""
pattern=""
grep_flag="-E"
timeout=15
interval=0.5
lines=1000
while [[ $# -gt 0 ]]; do
case "$1" in
-t|--target) target="${2-}"; shift 2 ;;
-p|--pattern) pattern="${2-}"; shift 2 ;;
-F|--fixed) grep_flag="-F"; shift ;;
-T|--timeout) timeout="${2-}"; shift 2 ;;
-i|--interval) interval="${2-}"; shift 2 ;;
-l|--lines) lines="${2-}"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) echo "Unknown option: $1" >&2; usage; exit 1 ;;
esac
done
if [[ -z "$target" || -z "$pattern" ]]; then
echo "target and pattern are required" >&2
usage
exit 1
fi
if ! [[ "$timeout" =~ ^[0-9]+$ ]]; then
echo "timeout must be an integer number of seconds" >&2
exit 1
fi
if ! [[ "$lines" =~ ^[0-9]+$ ]]; then
echo "lines must be an integer" >&2
exit 1
fi
if ! command -v tmux >/dev/null 2>&1; then
echo "tmux not found in PATH" >&2
exit 1
fi
# End time in epoch seconds (integer, good enough for polling)
start_epoch=$(date +%s)
deadline=$((start_epoch + timeout))
while true; do
# -J joins wrapped lines, -S uses negative index to read last N lines
pane_text="$(tmux capture-pane -p -J -t "$target" -S "-${lines}" 2>/dev/null || true)"
if printf '%s\n' "$pane_text" | grep $grep_flag -- "$pattern" >/dev/null 2>&1; then
exit 0
fi
now=$(date +%s)
if (( now >= deadline )); then
echo "Timed out after ${timeout}s waiting for pattern: $pattern" >&2
echo "Last ${lines} lines from $target:" >&2
printf '%s\n' "$pane_text" >&2
exit 1
fi
sleep "$interval"
done
+49
View File
@@ -0,0 +1,49 @@
---
name: weather
description: Get current weather and forecasts (no API key required).
homepage: https://wttr.in/:help
metadata: {"requires":{"bins":["curl"]}}
---
# Weather
Two free services, no API keys needed.
## wttr.in (primary)
Quick one-liner:
```bash
curl -s "wttr.in/London?format=3"
# Output: London: ⛅️ +8°C
```
Compact format:
```bash
curl -s "wttr.in/London?format=%l:+%c+%t+%h+%w"
# Output: London: ⛅️ +8°C 71% ↙5km/h
```
Full forecast:
```bash
curl -s "wttr.in/London?T"
```
Format codes: `%c` condition · `%t` temp · `%h` humidity · `%w` wind · `%l` location · `%m` moon
Tips:
- URL-encode spaces: `wttr.in/New+York`
- Airport codes: `wttr.in/JFK`
- Units: `?m` (metric) `?u` (USCS)
- Today only: `?1` · Current only: `?0`
- PNG: `curl -s "wttr.in/Berlin.png" -o /tmp/weather.png`
## Open-Meteo (fallback, JSON)
Free, no key, good for programmatic use:
```bash
curl -s "https://api.open-meteo.com/v1/forecast?latitude=51.5&longitude=-0.12&current_weather=true"
```
Find coordinates for a city, then query. Returns JSON with temp, windspeed, weathercode.
Docs: https://open-meteo.com/en/docs