devenv-backup
Backup dev environment to S3
What this skill does
Capture development environment state and upload to S3.
**1. Generate manifest:**
```bash
uv run ~/.dotfiles/scripts/devenv-backup/devenv.py manifest > /tmp/devenv-manifest.json
```
Parse the JSON and read the `uncommitted` section.
**1b. Display captured structure:**
Show the user a tree view of all captured workspaces with their change IDs and bookmarks:
```
Captured structure:
├── pivot/
│ ├── default @ knynrwpv
│ ├── compare-plots @ xlksqkzq [compare-plots]
│ └── tui-fixes @ xnussuvl [tui-fixes]
├── iam/
│ └── default @ oklnutnl
└── dotfiles/
└── default @ ltxnrnzv
```
Format: `workspace_name @ change_id [bookmark_name]` (bookmark only if one exists)
If files were captured (manifest version 2), also show:
```
Files to backup:
├── ~/
│ └── notes.txt (1.2 KB)
└── ~/pivot/
├── architecture.md (5.4 KB)
└── pro_critique/
└── critique.txt (2.1 KB)
```
If symlinks were captured, show them:
```
Symlinks to backup:
├── ~/.jjconfig.toml -> .dotfiles/.jjconfig.toml
├── ~/.gitconfig -> .dotfiles/.gitconfig
└── ~/jj/CLAUDE.md -> .dotfiles/.claude/project-instructions/jj.md
```
**Note on stale workspaces:** If jj commands fail with "workspace is stale" errors, run `jj workspace update-stale` in that workspace directory first. The Python script handles this automatically, but you may encounter it when running manual jj commands.
**2. Analyze uncommitted changes (if any):**
If there are uncommitted changes, analyze them **in parallel** using subagents:
- For each uncommitted change, `cd` to the workspace directory, then run `jj diff -r <change_id>`
- Check if the change is empty (no file changes) AND has no description
- For non-empty changes: generate a suggested commit description based on the diff
- Default branch name = workspace name
**Auto-skip empty changes:** If a change has no file modifications AND no description (typically the current working copy `@` commit), automatically skip it without prompting the user. These are just jj's empty working copy changes.
**CRITICAL: Skip commits that ARE main:** Before processing any change:
1. Run `jj log -r <change_id>` in the workspace directory
2. If the change's commit is directly on `main` (i.e., it IS main, not just branched from it), **skip it entirely**
3. These commits are immutable and cannot be modified, described, or pushed to a new branch
4. The manifest's `uncommitted` list may include the current working copy which happens to be sitting on main - these must be skipped
**3. Present results to user:**
Only show changes that need user action (non-empty AND (need a description OR have not been pushed to remote)):
```
| Workspace | Change ID | Current Description | Suggested Description | Branch | Action |
|-----------|-----------|--------------------|-----------------------|--------|--------|
| pivot | xlksqkzq | (empty) | Add compare plots... | compare-plots | push |
| iam | efgh5678 | WIP | Fix auth validation | iam | skip |
```
If all uncommitted changes were auto-skipped (all empty with no description, or on main), skip directly to step 6.
Ask the user to confirm or edit:
- Description to use for commit
- Branch name for push
- Action: **push** (describe + push), **discard** (abandon), or **skip** (leave as-is)
**4. Execute actions:**
For each change based on user's decision, first `cd` to the correct workspace directory:
- **push**:
```bash
cd <workspace_directory>
# First, check existing bookmarks
jj bookmark list
# Describe the change (if needed)
jj describe -r <change_id> -m "<description>"
# Handle bookmark:
# - If bookmark already exists and is "ahead by N commits": just push it
# - If bookmark exists and is synced: skip (already pushed)
# - If no bookmark exists: create it
# - NEVER set bookmarks on commits that are ancestors of main
# Only create if bookmark doesn't exist:
jj bookmark set <branch> -r <change_id>
jj git push --bookmark <branch>
```
- **discard**: `jj abandon <change_id>`
- **skip**: do nothing
**5. Re-verify manifest:**
Run manifest again:
```bash
uv run ~/.dotfiles/scripts/devenv-backup/devenv.py manifest > /tmp/devenv-manifest.json
```
If `uncommitted` is still non-empty, show the remaining items and ask user if they want to proceed anyway or resolve them.
**6. Get S3 base path from user:**
Ask for:
- S3 base path (e.g., `s3://bucket/users/[email protected]/`)
- The backup will be stored at `{base}/{machine}/{name}/`
- Claude Code data will be stored at `{base}/claude-code/{machine}/`
- OpenCode session data will be stored at `{base}/opencode/{machine}/`
Optional settings (mention defaults):
- `--name`: Backup name (default: today's date YYYY-MM-DD)
- `--machine`: Machine identifier (default: hostname)
- `--agent-instructions`: Optional freeform text to display during restore
- `--timeout`: Overall timeout in seconds (default: 120s)
**7. Recommend dry-run first:**
```bash
uv run ~/.dotfiles/scripts/devenv-backup/devenv.py backup --base <s3-base-path> --dry-run
```
This shows what would be uploaded without actually uploading.
**8. Run backup:**
```bash
uv run ~/.dotfiles/scripts/devenv-backup/devenv.py backup --base <s3-base-path>
# With optional flags:
uv run ~/.dotfiles/scripts/devenv-backup/devenv.py backup \
--base <s3-base-path> \
--name 2026-01-20 \
--machine devpod \
--agent-instructions "Run install.sh after restore"
# With custom timeout (default: 120 seconds):
uv run ~/.dotfiles/scripts/devenv-backup/devenv.py --timeout 300 backup --base <s3-base-path>
```
This single command:
- Generates the manifest (with files by default, root_dir from $HOME)
- Uploads manifest.json to `{base}/{machine}/{name}/`
- Uploads files to `{base}/{machine}/{name}/files/`
- Syncs Claude Code data to `{base}/claude-code/{machine}/` (only session data, not git-tracked config or credentials)
- Syncs OpenCode data directories (`~/.local/share/opencode/` and `~/.local/state/opencode/`) to `{base}/opencode/{machine}/share/` and `{base}/opencode/{machine}/state/`
- Checkpoints SQLite WAL files before sync for consistency
- Excludes regenerable runtime artifacts (bin, log, snapshot, tool-output, worktree)
**9. Summary:**
Print:
- Backup location
- Number of workspaces captured
- Number of files uploaded (if any)
- Number of symlinks captured (if any)
- Claude Code data synced
- OpenCode data synced (share + state directories)
- Any uncommitted changes that were skipped
**Done when:** Backup is uploaded to S3.
Related in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.