dev() Function Guide¶
The heart of the development workflow - Complete guide to using the dev() function.
Table of Contents¶
- Overview
- What dev() Does
- Step-by-Step Breakdown
- Full Function Code
- Installation
- Usage Examples
- Customization
- Troubleshooting
- Security Features
Overview¶
What Is dev()?¶
The dev() function is a shell function that:
1. Prompts you to select a project
2. Fetches secrets from Infisical
3. Selects AI agent accounts
4. Launches a Docker container with everything configured
5. Automatically cleans up on exit
Think of it as: One command to go from "logged into server" to "fully configured development environment."
Why It Exists¶
Without dev():
# You'd have to manually:
cd ~/Coding/my-project
infisical export --env=dev --projectId=personal-vault > /tmp/secrets.env
docker run --rm -it \
--env-file /tmp/secrets.env \
-v ~/Coding:/app \
-v ~/.gemini/oauth-edu1.json:/root/.gemini/oauth_creds.json:ro \
-v ~/.claude/session-personal.json:/root/.config/claude/config.json:ro \
-v ~/.ssh:/root/.ssh:ro \
-v ~/.gitconfig:/root/.gitconfig:ro \
-w /app/my-project \
my-dev-env:latest
rm /tmp/secrets.env
# Every. Single. Time.
With dev():
Benefits¶
- ✅ One command: No need to remember complex Docker commands
- ✅ Interactive: Guided prompts for all options
- ✅ Secure: Automatic cleanup of temporary secrets files
- ✅ Consistent: Same environment every time
- ✅ Flexible: Support for multiple AI accounts and projects
- ✅ Safe: Resource limits, security options, read-only mounts
What dev() Does¶
The Complete Workflow¶
START: dev my-project
↓
1. PROJECT SELECTION
├─ Argument provided: Use "my-project"
└─ No argument: Show menu of ~/Coding/* directories
↓
2. INFISICAL PROJECT & ENVIRONMENT
├─ Default: personal-vault / dev
└─ Custom: Prompt for project and environment
↓
3. GEMINI ACCOUNT SELECTION
├─ edu1, edu2, edu3, edu4
└─ Maps to ~/.gemini/oauth-eduX.json
↓
4. CLAUDE ACCOUNT SELECTION
├─ personal, edu1, edu2, work, skip
└─ Maps to ~/.claude/session-X.json
↓
5. OPENAI ACCOUNT SELECTION
├─ chatgpt, edu1, edu2, work, skip
└─ Maps to ~/.openai/session-X.json
↓
6. INFISICAL AUTHENTICATION CHECK
├─ Runs: infisical whoami
└─ If fails: Show login command and exit
↓
7. SECRETS FETCH
├─ Command: infisical export --env=dev --projectId=personal-vault
├─ Output: /tmp/env-$$.list
└─ Contains: ANTHROPIC_API_KEY, GOOGLE_API_KEY, DATABASE_URL, etc.
↓
8. DOCKER CONTAINER LAUNCH
├─ Image: my-dev-env:latest
├─ Resource limits: 4GB RAM, 2 CPUs, 100 processes
├─ Security: no-new-privileges
├─ Mounts:
│ ├─ ~/Coding → /app (read-write)
│ ├─ ~/.gemini/oauth-eduX.json → /root/.gemini/oauth_creds.json (read-only)
│ ├─ ~/.claude/session-X.json → /root/.config/claude/config.json (read-only)
│ ├─ ~/.openai/session-X.json → /root/.openai/session.json (read-only)
│ ├─ ~/.ssh → /root/.ssh (read-only)
│ └─ ~/.gitconfig → /root/.gitconfig (read-only)
├─ Env file: --env-file /tmp/env-$$.list
└─ Working dir: /app/my-project
↓
9. INSIDE CONTAINER
├─ Pre-installed: Claude Code, Gemini CLI
├─ Secrets: Available as environment variables
├─ AI agents: Configured with selected accounts
└─ Git: Works with mounted SSH keys
↓
10. USER WORKS
├─ claude (uses ANTHROPIC_API_KEY)
├─ gemini (uses oauth_creds.json)
├─ git commit, git push (uses SSH keys)
└─ Your application code
↓
11. EXIT CONTAINER (user types "exit")
├─ Container removed (--rm flag)
├─ Trap triggers: cleanup()
├─ Deletes: /tmp/env-$$.list
└─ Code changes preserved (mounted from ~/Coding)
↓
END: Back to server shell
Step-by-Step Breakdown¶
Step 1: Project Selection¶
User runs:
If no argument provided, shows menu:
How it works:
if [ -z "$1" ]; then
# List all directories in ~/Coding
ls -1 "$HOME/Coding" > /tmp/dev-projects.txt
# Display numbered menu
# Read user choice
else
PROJECT_NAME="$1"
TARGET_DIR="$HOME/Coding/$1"
fi
Output: TARGET_DIR and PROJECT_NAME variables set
Step 2: Infisical Project & Environment¶
Prompt:
🔐 Select Infisical Configuration:
[1] personal-vault / dev (default)
[2] personal-vault / prod
[3] Custom (specify project and environment)
👉 Choose (1-3):
How it works:
case $infisical_choice in
1) INFISICAL_PROJECT="personal-vault"; INFISICAL_ENV="dev" ;;
2) INFISICAL_PROJECT="personal-vault"; INFISICAL_ENV="prod" ;;
3)
# Prompt for custom project
# Prompt for custom environment
;;
esac
Output: INFISICAL_PROJECT and INFISICAL_ENV variables set
Step 3: Gemini Account Selection¶
Prompt:
How it works:
case $gemini_choice in
1) OAUTH_FILE="$HOME/.gemini/oauth-edu1.json" ;;
2) OAUTH_FILE="$HOME/.gemini/oauth-edu2.json" ;;
# etc...
esac
# Verify file exists
if [ ! -f "$OAUTH_FILE" ]; then
echo "⚠️ Gemini OAuth file not found: $OAUTH_FILE"
return 1
fi
Output: OAUTH_FILE variable set, file existence verified
Step 4: Claude Account Selection¶
Prompt:
🧠 Select Claude Account:
[1] personal (default)
[2] edu1
[3] edu2
[4] work
[0] Skip (no Claude session)
👉 Choose (0-4):
How it works:
case $claude_choice in
1) CLAUDE_SESSION="$HOME/.claude/session-personal.json" ;;
2) CLAUDE_SESSION="$HOME/.claude/session-edu1.json" ;;
0) CLAUDE_SESSION="" ;; # Skip
# etc...
esac
# Verify file exists (if not skipped)
if [ ! -z "$CLAUDE_SESSION" ] && [ ! -f "$CLAUDE_SESSION" ]; then
echo "⚠️ Claude session file not found: $CLAUDE_SESSION"
echo " Continuing without Claude session..."
CLAUDE_SESSION=""
fi
Output: CLAUDE_SESSION variable set (or empty if skipped)
Step 5: OpenAI Account Selection¶
Prompt:
🔮 Select OpenAI/ChatGPT Account:
[1] chatgpt (default)
[2] edu1
[3] edu2
[4] work
[0] Skip (no OpenAI session)
👉 Choose (0-4):
How it works: Same pattern as Claude
Output: OPENAI_SESSION variable set (or empty if skipped)
Step 6: Infisical Authentication Check¶
What it does:
if ! infisical whoami &>/dev/null; then
echo "❌ Not authenticated with Infisical!"
echo "Please run: infisical login --domain=https://secrets.kua.cl"
return 1
fi
If authentication fails:
❌ Not authenticated with Infisical!
Please authenticate first:
infisical login --domain=http://100.80.53.55:8080
Output: Exits if not authenticated, continues if authenticated
Step 7: Secrets Fetch¶
What it does:
SECRETS_FILE="/tmp/env-$$.list"
infisical export \
--env="$INFISICAL_ENV" \
--projectId="$INFISICAL_PROJECT" \
--format=dotenv > "$SECRETS_FILE"
$$ is: Current process ID (ensures unique temp file)
File contains:
ANTHROPIC_API_KEY=sk-ant-api03-...
GOOGLE_API_KEY=AIza...
DATABASE_URL=postgresql://localhost:5432/mydb
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=...
If fetch fails:
⚠️ Failed to fetch secrets from Infisical
Project: personal-vault
Environment: dev
Available options:
1. Check project/environment names: infisical projects
2. Use emergency backup (if Infisical is down)
Output: /tmp/env-$$.list file created with secrets
Step 8: Docker Container Launch¶
Build command:
docker run --rm -it \
--memory="4g" \
--cpus="2.0" \
--pids-limit=100 \
--security-opt=no-new-privileges \
--env-file "$SECRETS_FILE" \
-e "GEMINI_ACCOUNT=$GEMINI_ACCOUNT" \
-e "CLAUDE_ACCOUNT=$CLAUDE_ACCOUNT" \
-e "OPENAI_ACCOUNT=$OPENAI_ACCOUNT" \
-v "$HOME/Coding":/app \
-v "$OAUTH_FILE":/root/.gemini/oauth_creds.json:ro \
-v "$CLAUDE_SESSION":/root/.config/claude/config.json:ro \ # If not skipped
-v "$OPENAI_SESSION":/root/.openai/session.json:ro \ # If not skipped
-v "$HOME/.ssh":/root/.ssh:ro \
-v "$HOME/.gitconfig":/root/.gitconfig:ro \
-w "/app/$PROJECT_NAME" \
my-dev-env:latest \
/bin/bash
Flags explained:
| Flag | Purpose |
|---|---|
--rm |
Remove container on exit |
-it |
Interactive terminal |
--memory="4g" |
Limit to 4GB RAM |
--cpus="2.0" |
Limit to 2 CPU cores |
--pids-limit=100 |
Max 100 processes (prevent fork bombs) |
--security-opt=no-new-privileges |
Can't gain new privileges (security) |
--env-file |
Load secrets from temp file |
-e |
Set environment variable |
-v |
Mount volume (host:container) |
:ro |
Read-only mount |
-w |
Working directory inside container |
Output: Container starts, user dropped into bash shell
Step 9: Inside Container¶
Available commands:
# AI agents (pre-installed)
claude --version # Claude Code 2.0.76
gemini --version # Gemini CLI 0.22.2
# Use AI agents
claude
gemini
# Check secrets loaded
env | grep API_KEY
# Git operations
git status
git commit -m "..."
git push # Uses mounted SSH keys
# Your application
python main.py
npm start
cargo run
Environment:
- Working directory: /app/my-project
- Secrets: Available as $ANTHROPIC_API_KEY, etc.
- Git config: Mounted from ~/.gitconfig
- SSH keys: Mounted at /root/.ssh/ (read-only)
Step 10: Exit and Cleanup¶
User types:
What happens:
1. Container exits (bash process ends)
2. Container is removed (--rm flag)
3. Trap triggers: cleanup() function runs
4. Deletes temp files:
- /tmp/env-$$.list (secrets)
- /tmp/dev-projects.txt (if exists)
5. User back to server shell
Trap definition:
cleanup() {
rm -f /tmp/env-$$.list /tmp/*_secrets_$$.json /tmp/dev-projects.txt 2>/dev/null
}
trap cleanup EXIT INT TERM
Signals handled:
- EXIT: Normal exit
- INT: Ctrl+C
- TERM: Kill signal
Result: No sensitive data left on filesystem