Skip to content

dev() Function Guide

The heart of the development workflow - Complete guide to using the dev() function.


Table of Contents


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():

dev my-project
# Select options from menus
# Done!

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:

dev
# or
dev my-project

If no argument provided, shows menu:

📂 Select a project:
   [0] 🏠 ROOT (All Projects)
   [1] my-app
   [2] terraform
   [3] kavi-infra
👉 Choose:

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:

🤖 Select Gemini Account:
   [1] edu1 (default)
   [2] edu2
   [3] edu3
   [4] edu4
👉 Choose (1-4):

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:

exit

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


Full Function Code

/Users/kavi/Coding/.docker-image/scripts/dev-function-infisical.sh