Back to Skills
    🦞

    portable-tools

    Build cross-device tools without hardcoding paths

    By @tunaissacoding
    View on GitHub
    SKILL.md
    ---
    name: portable-tools
    description: Build cross-device tools without hardcoding paths or account names
    ---
    
    # 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
    ```
    
    ---
    
    ### Anti-Pattern 4: "Silent Failures"
    ```bash
    process_data || true  # Ignore errors
    ```
    
    **Fix:** Fail loudly with context
    ```bash
    process_data || error "Failed to process
    Data: $DATA
    Expected: { ... }
    Check: command_to_verify"
    ```
    
    ---
    
    ## Integration With Existing Workflows
    
    ### With sprint-plan.md
    Add to testing section:
    ```markdown
    ## Cross-Device Testing
    - [ ] Test with different account names
    - [ ] Test with wrong config values
    - [ ] Test with missing data
    - [ ] Document fallback behavior
    ```
    
    ### With PRIVACY-CHECKLIST.md
    Add before publishing:
    ```markdown
    ## Portability Check
    - [ ] No hardcoded paths (use $HOME, detect OS)
    - [ ] No hardcoded names (use config or fallback)
    - [ ] Validation on all inputs
    - [ ] Helpful errors for common issues
    ```
    
    ### With skill-creator
    When building new skills:
    1. List what varies between devices
    2. Make it configurable or auto-discoverable
    3. Test with wrong config
    4. Document troubleshooting
    
    ---
    
    ## Quick Reference Card
    
    **Before writing code:**
    1. What varies between devices?
    2. How do I prove this works?
    3. What happens when it breaks?
    
    **Mandatory patterns:**
    - Explicit over implicit
    - Validate before use
    - Fallback chains
    - Helpful errors
    
    **Testing:**
    - Correct config → Works
    - Wrong config → Fallback or helpful error
    - Missing data → Clear diagnostic
    
    **Documentation:**
    - Data flow diagram
    - Common variations
    - Troubleshooting guide
    
    ---
    
    ## Success Criteria
    
    A tool is **portable** when:
    
    1. ✅ Works on different devices without modification
    2. ✅ Auto-discovers common variations in setup
    3. ✅ Fails gracefully with actionable error messages
    4. ✅ Can be debugged by reading the error output
    5. ✅ Documentation covers "what if my setup differs"
    
    **Test:** Give it to someone with a different setup. If they need to ask you questions, the tool isn't portable yet.
    
    ---
    
    ## Origin Story
    
    This methodology emerged from de
    
    ... (truncated)