portable-tools
Build cross-device tools without hardcoding paths or account names
What this skill does
# Portable Tools - Cross-Device Development Methodology
Methodology for building tools that work across different devices, naming schemes, and configurations. Based on lessons from OAuth refresher debugging session (2026-01-23).
## Core Principle
**Never assume your device is the only device.**
Your local setup is just one of many possible configurations. Build for the general case, not the specific instance.
---
## The Three Questions (Before Writing Code)
### 1. "What varies between devices?"
Before writing any code that reads configuration, data, or credentials:
**Ask:**
- File paths? (macOS vs Linux, different home dirs)
- Account names? (user123 vs default vs oauth)
- Service names? (slight variations in spelling/capitalization)
- Data structure? (different versions, different formats)
- Environment? (different shells, different tools available)
**Example from OAuth refresher:**
- ❌ Assumed: Account is always "claude"
- ✅ Reality: Could be "claude", "Claude Code", "default", etc.
**Action:** List variables, make them configurable or auto-discoverable
---
### 2. "How do I prove this works?"
Before claiming success:
**Require:**
- Concrete BEFORE state (exact values)
- Concrete AFTER state (exact values)
- Proof they're different (side-by-side comparison)
**Example from OAuth refresher:**
```
BEFORE:
- Access Token: POp5z1fi...eSN9VAAA
- Expires: 1769189639000
AFTER:
- Access Token: 01v0RrFG...eOE9QAA ✅ Different
- Expires: 1769190268000 ✅ Extended
```
**Action:** Always show data transformation with real values
---
### 3. "What happens when it breaks?"
Before pushing to production:
**Test:**
- Wrong configuration (intentionally break config)
- Missing data (remove expected fields)
- Multiple entries (ambiguous case)
- Edge cases (empty values, special characters)
**Example from OAuth refresher:**
- Test with `keychain_account: "wrong-name"` → Fallback should work
- Test with incomplete keychain data → Should fail gracefully with helpful error
**Action:** Test failure modes, not just happy path
---
## Mandatory Patterns
### Pattern 1: Explicit Over Implicit
**❌ Wrong:**
```bash
# Ambiguous - returns first match
security find-generic-password -s "Service" -w
```
**✅ Correct:**
```bash
# Explicit - returns specific entry
security find-generic-password -s "Service" -a "account" -w
```
**Rule:** If a command can be ambiguous, make it explicit.
---
### Pattern 2: Validate Before Use
**❌ Wrong:**
```bash
DATA=$(read_config)
USE_VALUE="$DATA" # Hope it's valid
```
**✅ Correct:**
```bash
DATA=$(read_config)
if ! validate_structure "$DATA"; then
error "Invalid data structure"
fi
USE_VALUE="$DATA"
```
**Rule:** Never assume data has expected structure.
---
### Pattern 3: Fallback Chains
**❌ Wrong:**
```bash
ACCOUNT="claude" # Hardcoded
```
**✅ Correct:**
```bash
# Try configured → Try common → Error with help
ACCOUNT="${CONFIG_ACCOUNT}"
if ! has_data "$ACCOUNT"; then
for fallback in "claude" "default" "oauth"; do
if has_data "$fallback"; then
ACCOUNT="$fallback"
break
fi
done
fi
[[ -z "$ACCOUNT" ]] && error "No account found. Tried: ..."
```
**Rule:** Provide automatic fallbacks for common variations.
---
### Pattern 4: Helpful Errors
**❌ Wrong:**
```bash
[[ -z "$TOKEN" ]] && error "No token"
```
**✅ Correct:**
```bash
[[ -z "$TOKEN" ]] && error "No token found
Checked:
- Config: $CONFIG_FILE
- Field: $FIELD_NAME
- Expected: { \"tokens\": { \"refresh\": \"...\" } }
Verify with:
cat $CONFIG_FILE | jq '.tokens'
"
```
**Rule:** Error messages should help user diagnose and fix.
---
## Debugging Methodology (Patrick's Approach)
### Step 1: Get Exact Data
**Don't ask:** "Is it broken?"
**Ask:** "What exact values do you see? How many entries exist? Which one has the data?"
**Example:**
```bash
# Vague
"Check keychain"
# Specific
"Run: security find-generic-password -l 'Service' | grep 'acct'"
"Tell me: 1. How many entries 2. Which has tokens 3. Last modified"
```
---
### Step 2: Prove With Concrete Examples
**Don't say:** "It should work now"
**Show:** "Here's the BEFORE token (POp5z...), here's AFTER (01v0R...), they're different"
**Template:**
```
BEFORE:
- Field1: <exact_value>
- Field2: <exact_value>
AFTER:
- Field1: <new_value> ✅ Changed
- Field2: <new_value> ✅ Changed
PROOF: Values are different
```
---
### Step 3: Think Cross-Device Immediately
**Don't think:** "Works on my machine"
**Think:** "What if their setup differs in [X]?"
**Checklist:**
- [ ] Different account names?
- [ ] Different file paths?
- [ ] Different tools/versions?
- [ ] Different permissions?
- [ ] Different data formats?
---
## Pre-Flight Checklist (Before Publishing)
### Discovery Phase
- [ ] List all external dependencies (files, commands, services)
- [ ] Document what each dependency provides
- [ ] Identify which parts could vary between devices
### Implementation Phase
- [ ] Make variations configurable (with sensible defaults)
- [ ] Add validation for each input
- [ ] Build fallback chains for common variations
- [ ] Add `--dry-run` or `--test` mode
### Testing Phase
- [ ] Test with correct config → Should work
- [ ] Test with wrong config → Should fallback or fail gracefully
- [ ] Test with missing data → Should give helpful error
- [ ] Test with multiple entries → Should handle ambiguity
### Documentation Phase
- [ ] Document default assumptions
- [ ] Document how to verify local setup
- [ ] Document common variations and how to handle them
- [ ] Include data flow diagram
- [ ] Add troubleshooting section
---
## Real-World Example: OAuth Refresher
### Original (Broken)
```bash
# Assumes single entry, no validation, no fallback
KEYCHAIN_DATA=$(security find-generic-password -s "Service" -w)
REFRESH_TOKEN=$(echo "$KEYCHAIN_DATA" | jq -r '.refreshToken')
# Use token (hope it's valid)
```
**Problems:**
- Returns first alphabetical match (wrong entry)
- No validation (could be empty/malformed)
- No fallback (fails if account name differs)
---
### Fixed (Portable)
```bash
# Explicit account with validation and fallback
validate_data() {
echo "$1" | jq -e '.claudeAiOauth.refreshToken' > /dev/null 2>&1
}
# Try configured account
DATA=$(security find-generic-password -s "$SERVICE" -a "$ACCOUNT" -w 2>&1)
if validate_data "$DATA"; then
log "✓ Using account: $ACCOUNT"
else
log "⚠ Trying fallback accounts..."
for fallback in "claude" "Claude Code" "default"; do
DATA=$(security find-generic-password -s "$SERVICE" -a "$fallback" -w 2>&1)
if validate_data "$DATA"; then
ACCOUNT="$fallback"
log "✓ Found data in: $fallback"
break
fi
done
fi
[[ -z "$DATA" ]] || ! validate_data "$DATA" && error "No valid data found
Tried accounts: $ACCOUNT, claude, Claude Code, default
Verify with: security find-generic-password -l '$SERVICE'"
REFRESH_TOKEN=$(echo "$DATA" | jq -r '.claudeAiOauth.refreshToken')
```
**Improvements:**
- ✅ Explicit account parameter
- ✅ Validates data structure
- ✅ Automatic fallback to common names
- ✅ Helpful error with verification command
---
## Common Anti-Patterns
### Anti-Pattern 1: "Works On My Machine"
```bash
FILE="/Users/patrick/.config/app.json" # Hardcoded path
```
**Fix:** Use `$HOME`, detect OS, or make configurable
---
### Anti-Pattern 2: "Hope It's There"
```bash
TOKEN=$(cat config.json | jq -r '.token')
# What if .token doesn't exist? Script continues with empty value
```
**Fix:** Validate before using
```bash
TOKEN=$(cat config.json | jq -r '.token // empty')
[[ -z "$TOKEN" ]] && error "No token in config"
```
---
### Anti-Pattern 3: "First Match Is Right"
```bash
# If multiple entries exist, which one?
ENTRY=$(find_entry "service")
```
**Fix:** Be explicit or enumerate all
```bash
ENTRY=$(find_entry "service" "account") # Specific
# OR
ALL=$(find_all_entries "service")
for entry in $ALL; do
validate_and_use "$entry"
done
```
---
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.