Skip to content
Back to skills

Setup Project

CSecurity

Bootstraps a new project directory with base configuration files, MCP servers, API connections, and template skills from OutworkOS. Use when setting up a new project, initializing a workspace, or configuring MCP integrations for a new directory.

  • 4 stars
  • 0 votes
  • 0 copies
  • 4 views
  • Added September 12, 2026
developmentpythongobashnextjsnodegitapidatabase

Works with

  • cli
  • api
  • mcp

Security analysis

C63/100
  • criticalExfiltrates credentials via HTTP — exact pattern from Snyk ToxicSkills study
  • criticalSends environment variables or credentials to an external URL

Pro shows the line behind each finding and how to fix it

Scanned September 12, 2026

npx -y skills add MattVOLTA/outworkos_diy --skill setup-project --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Setup Project?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Setup Project
[![Security: C — Skills Directory](https://www.skillsdirectory.com/api/skills/mattvolta-setup-project/badge)](https://www.skillsdirectory.com/skills/mattvolta-setup-project)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: setup-project
description: "Bootstraps a new project directory with base configuration files, MCP servers, API connections, and template skills from OutworkOS. Use when setting up a new project, initializing a workspace, or configuring MCP integrations for a new directory."
argument-hint: "<project-name>"
---

# /setup-project

Bootstrap a new project directory under `$OUTWORKOS_PARENT` with base configuration files from `$OUTWORKOS_ROOT` (the OutworkOS repo — source of truth for shared skills and credentials).

**Key concepts:**
- `$OUTWORKOS_ROOT` — the OutworkOS repo (where you run Claude from, contains scripts/, skills/, .mcp.json)
- `$OUTWORKOS_HOME` — data directory (e.g., ~/.outworkos)
- `$OUTWORKOS_PARENT` — where project folders live (e.g., ~/.outworkos/projects/)
- These CAN be the same directory if the user configures it that way

## Trigger

User invokes `/setup-project` or `/setup-project <project-name>`

## Steps

### 1. Validate Environment & Create Project Directory

1. Confirm `$OUTWORKOS_ROOT` is set and the template directory exists with these files:
   - `$OUTWORKOS_ROOT/.mcp.json`
   - `$OUTWORKOS_ROOT/skills/context-map/`
   - `$OUTWORKOS_ROOT/skills/log/`
   - `$OUTWORKOS_ROOT/skills/todoist/`
   If any are missing, warn but continue (some may not be needed).

2. Confirm `$OUTWORKOS_PARENT` is set. If not, default to `$OUTWORKOS_HOME/projects` or ask the user.

3. Get the project name:
   - If provided as argument, use it
   - Otherwise, ask: **"What's the name of this project?"**
   - Slugify the name for the directory (lowercase, hyphens for spaces, strip special chars)

4. Create the project directory at `$OUTWORKOS_PARENT/<slug>/`:
   - If the directory **already exists and is not empty**, stop with an error: "Project directory already exists. Use a different name or remove it first."
   - If the directory exists but is empty, proceed
   - If it doesn't exist, create it with `mkdir -p`

5. Set `PROJECT_DIR=$OUTWORKOS_PARENT/<slug>` for use in all subsequent steps.

**Note:** The user runs Claude from `$OUTWORKOS_ROOT` (the repo) to get skills loaded. This step creates the project directory elsewhere — that's expected.

### 2. Check for Existing Files

Check whether these already exist in the project directory:
- `.mcp.json`
- `.claude/skills/context-map/`
- `.claude/skills/log/`
- `.claude/skills/todoist/`

For each one that exists, ask the user whether to **skip** or **overwrite** it. Use a single AskUserQuestion with multiSelect for efficiency.

### 3. Verify Vault Access

Verify that `outwork init` has been run and Vault secrets are accessible. Test by retrieving a known secret:

```bash
"$OUTWORKOS_ROOT/scripts/get-secret.sh" todoist_api_token > /dev/null 2>&1 && echo "Vault: OK" || echo "Vault: FAILED — run outwork init first"
```

If Vault access fails, tell the user to run `$OUTWORKOS_ROOT/scripts/outworkos-auth-login.sh` first.

### 4. Link Skills and Hooks

Symlink key directories from `$OUTWORKOS_ROOT` into the project so skills, hooks, and environment variables work when running Claude from the project directory.

```bash
mkdir -p "$PROJECT_DIR/.claude"

# Skills — makes all /commands available
ln -sf "$OUTWORKOS_ROOT/skills" "$PROJECT_DIR/.claude/commands"

# Hooks — SessionStart sets $OUTWORKOS_ROOT, $OUTWORKOS_HOME, $OUTWORKOS_PARENT
ln -sf "$OUTWORKOS_ROOT/.claude/hooks" "$PROJECT_DIR/.claude/hooks"

# Settings — hook config (SessionStart, SessionEnd)
# Copy (not symlink) so the project can customize hooks later
if [ ! -f "$PROJECT_DIR/.claude/settings.json" ]; then
  cp "$OUTWORKOS_ROOT/.claude/settings.json" "$PROJECT_DIR/.claude/settings.json"
fi
```

This ensures:
- All skills are available (context-map, log, todoist, scan, whats-next, etc.)
- `$OUTWORKOS_ROOT`, `$OUTWORKOS_HOME`, `$OUTWORKOS_PARENT` are set on session start
- Vault scripts (`get-secret.sh`, `set-secret.sh`) are reachable via `$OUTWORKOS_ROOT/scripts/`
- Stays in sync with the repo — no need to copy or update individual files

If `.claude/commands` or `.claude/hooks` already exists:
- If it's a symlink pointing to the same target, skip
- If it's a symlink pointing elsewhere, ask the user before replacing
- If it's a regular directory, ask the user whether to replace it with a symlink

### 5. Supabase Picker (Optional)

Ask the user: **"Do you want to add Supabase database connections?"**

If **no**: skip to Step 6 with zero Supabase entries.

If **yes**:

1. Read `SUPABASE_ACCESS_TOKEN` from Vault: `$("$OUTWORKOS_ROOT/scripts/get-secret.sh" supabase_access_token)`
2. Call the Supabase Management API to list projects:
   ```
   curl -s -H "Authorization: Bearer <TOKEN>" https://api.supabase.com/v1/projects
   ```
3. Filter the JSON response to only projects where `status` is `"ACTIVE_HEALTHY"`
4. Present a numbered list showing each project's `name` and `ref` (the project reference ID)
5. Ask the user to select which projects to add (comma-separated numbers, e.g. "1,3,4")
6. Store the selected projects for Step 6

### 6. Assemble `.mcp.json`

1. Read the base `.mcp.json` from `$OUTWORKOS_ROOT/.mcp.json` — this contains the 6 base servers (github, netlify, context7, perplexity, fireflies, google-workspace)
2. Parse it as JSON
3. For each Supabase project selected in Step 5, add an entry using the project's `name` as the key:
   ```json
   "<project-name>": {
     "command": "npx",
     "args": [
       "-y",
       "@supabase/mcp-server-supabase@latest",
       "--project-ref",
       "<project-ref>"
     ],
     "env": {
       "SUPABASE_ACCESS_TOKEN": "<token-value-from-env>"
     }
   }
   ```
4. Write the assembled JSON to `.mcp.json` in the current directory (pretty-printed with 2-space indent)

**Important**: Use the `env` field for the access token, NOT `--access-token` in args. This keeps secrets out of process arguments.

### 7. Test Connections

After assembling all files, test each API token/key by making a lightweight curl call. Read credentials from Vault via `$OUTWORKOS_ROOT/scripts/get-secret.sh` and from `.mcp.json` for MCP-specific tokens.

Run ALL test calls in parallel using separate Bash tool calls for speed.

**Tests to run:**

| Service | Test command | Pass | Fail action |
|---------|-------------|------|-------------|
| GitHub | `curl -s -o /dev/null -w "%{http_code}" -H "Authorization: token <GITHUB_PERSONAL_ACCESS_TOKEN>" https://api.github.com/user` | `200` | Token expired or invalid — regenerate at https://github.com/settings/tokens |
| Netlify | `curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer <NETLIFY_AUTH_TOKEN>" https://api.netlify.com/api/v1/user` | `200` | Token invalid — regenerate at https://app.netlify.com/user/applications#personal-access-tokens |
| Perplexity | `curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer <PERPLEXITY_API_KEY>" -H "Content-Type: application/json" -d '{"model":"sonar","messages":[{"role":"user","content":"ping"}]}' https://api.perplexity.ai/chat/completions` | `200` | API key invalid or expired — check https://www.perplexity.ai/settings/api |
| Supabase | `curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer <SUPABASE_ACCESS_TOKEN>" https://api.supabase.com/v1/projects` | `200` | PAT invalid — regenerate at https://supabase.com/dashboard/account/tokens |
| Fireflies | `curl -s -o /dev/null -w "%{http_code}" -H "Content-Type: application/json" -H "Authorization: Bearer <FIREFLIES_TOKEN>" -d '{"query":"{ user { name } }"}' https://api.fireflies.ai/graphql` | `200` | Token invalid — check https://app.fireflies.ai/integrations |
| Context7 | MCP call: `mcp__context7__resolve-library-id` (see below) | Returns results | API key invalid — check with Upstash |

**How to extract each credential (use these exact methods):**

1. Read `.mcp.json` with the Read tool and parse as JSON. Extract:
   - `GITHUB_PERSONAL_ACCESS_TOKEN` → `mcpServers.github.env.GITHUB_PERSONAL_ACCESS_TOKEN`
   - `NETLIFY_AUTH_TOKEN` → `mcpServers.netlify.env.NETLIFY_AUTH_TOKEN`
   - `PERPLEXITY_API_KEY` → `mcpServers.perplexity.env.PERPLEXITY_API_KEY`
   - `FIREFLIES_TOKEN` → from `mcpServers.fireflies.args`, find the string that starts with `"Authorization: Bearer "` and extract everything after that prefix

2. For Supabase, extract from any Supabase entry in `.mcp.json` → `mcpServers.<any-supabase-server>.env.SUPABASE_ACCESS_TOKEN`. This is reliable because it's already a clean JSON string value. If no Supabase server was added, fall back to Vault: `$("$OUTWORKOS_ROOT/scripts/get-secret.sh" supabase_access_token)`.

**Important**: Prefer reading from `.mcp.json` JSON values or Vault for all credential access.

**Context7 test (MCP-based, not curl):**

Context7 has no public REST endpoint, but the MCP server is typically already connected in the current session. Test it by making a lightweight MCP tool call:

1. Use `ToolSearch` to load `mcp__context7__resolve-library-id`
2. Call it with `libraryName: "nextjs"` and `query: "test connection"`
3. If it returns library results → pass
4. If it errors (auth failure, timeout) → fail — API key invalid, check with Upstash

Run this in parallel with the curl tests above (it's independent).

**Services NOT tested** (note in report but don't fail):
- **Google Workspace**: Uses OAuth refresh tokens — the workspace-mcp server handles token refresh internally. If the refresh token is expired, the server will prompt for re-auth on first use.

**Handling failures:**

For each failed test:
1. Report which service failed and the HTTP status code received
2. Show the specific URL where the user can regenerate the token
3. After listing all failures, ask: "Would you like to update any of these tokens now?"
4. If yes, for each token the user provides:
   - Update the value in Vault via `set-secret.sh` and/or `.mcp.json` as appropriate
   - Re-run that specific test to confirm the new token works
   - Repeat until the token passes or the user chooses to skip
5. If all tests pass, continue to Step 8

### 8. Todoist Sections (if applicable)

If a Todoist project ID is already known for this project (e.g., from a previous `/context-map` run or from the user), ensure the standard status sections exist:

1. Fetch sections via Sync API: `resource_types: ["sections"]`
2. Check if "Triage" section exists for the project ID
3. If missing, create all four sections (Triage, Do, Waiting, Defer) in a single batch:
   ```python
   import json, uuid
   sections = ["Triage", "Do", "Waiting", "Defer"]
   commands = [{"type": "section_add", "uuid": str(uuid.uuid4()), "temp_id": str(uuid.uuid4()), "args": {"name": s, "project_id": PROJECT_ID}} for s in sections]
   with open("/tmp/todoist_batch.json", "w") as f:
       json.dump({"commands": commands}, f)
   ```
4. If no Todoist project ID is known yet, skip — `/context-map` will handle section creation when it sets up the Todoist project

**Standard sections:**
| Section | Purpose |
|---------|---------|
| Triage | New tasks, needs decision |
| Do | Committed, actively working |
| Waiting | Blocked on someone else |
| Defer | Parked for later |

### 9. GitHub Backup Repository (Optional)

Ask the user: **"Do you want to set up a GitHub backup repository for this project?"**

If **no**: skip to Step 10.

If **yes**:

#### 9a. Prerequisites

1. Verify `gh` CLI is available: `gh --version`
2. Verify `gh` is authenticated: `gh auth status`
3. If either fails, stop and tell the user to install/authenticate `gh`

#### 9b. Detect Existing Git State

1. Check if `.git/` already exists in the project directory
   - If yes, ask: "This directory already has a git repo. Skip GitHub backup setup?" — if they want to skip, go to Step 10
2. Scan for subdirectories that contain their own `.git/` (app repos):
   ```bash
   find . -mindepth 2 -maxdepth 3 -name ".git" -type d
   ```
3. Record these paths — they'll be excluded via `.gitignore`

#### 9c. Generate `.gitignore`

Create `.gitignore` with:

```gitignore
# Local config
.mcp.json

# OS files
.DS_Store
Thumbs.db

# Dependencies and caches
node_modules/
__pycache__/
.cache/
*.pyc

# Large binary files
*.zip
*.tar.gz
*.dmg
```

For each subdirectory with its own `.git/` detected in 9b, append a line:
```gitignore
# App repo (has its own git)
subdirectory-name/
```

#### 9d. Initialize and Push

1. `git init`
2. Derive repo name from the project directory name (lowercase, hyphens for spaces, strip special chars)
3. `git add .`
4. `git commit -m "Initial project backup"`
5. `gh repo create MattVOLTA/<repo-name> --private --source=. --push`
6. Verify: `gh repo view MattVOLTA/<repo-name> --json url -q .url`

#### 9e. Create Backup Script

Write `.claude/hooks/backup.sh`:

```bash
#!/bin/bash
# Auto-backup project to GitHub on session end
cd "$CLAUDE_PROJECT_DIR" || exit 0

# Skip if no git repo
[ -d .git ] || exit 0

# Skip if no changes
if git diff --quiet HEAD 2>/dev/null && [ -z "$(git status --porcelain)" ]; then
  exit 0
fi

git add -A
git commit -m "Auto-backup $(date +%Y-%m-%d\ %H:%M)" --no-gpg-sign 2>/dev/null
git push origin HEAD 2>/dev/null
```

Make it executable: `chmod +x .claude/hooks/backup.sh`

#### 9f. Add SessionEnd Hook

Read the project's `.claude/settings.json` (create if missing). Add a `SessionEnd` hook entry:

```json
{
  "hooks": {
    "SessionEnd": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/backup.sh\"",
            "timeout": 30
          }
        ]
      }
    ]
  }
}
```

If `settings.json` already has hooks (e.g., SessionStart), merge the new SessionEnd entry alongside existing hooks — do NOT overwrite.

#### 9g. Verify

Confirm the repo exists and the hook file is in place:
- `gh repo view MattVOLTA/<repo-name> --json url`
- `ls -la .claude/hooks/backup.sh`

### 10. Report

Confirm all created files exist by reading them. Then print a summary:

```
Project setup complete!

Directory: $OUTWORKOS_PARENT/<project-name>/

Files created:
  .mcp.json
  .claude/commands → $OUTWORKOS_ROOT/skills/  (symlink — all skills available)

Connection tests:
  ✓ GitHub
  ✓ Supabase
  · Google Workspace (OAuth — tested on first use)
  ...

MCP servers configured:
  supabase, google-workspace, github, ...

GitHub backup:
  ✓ Repo: github.com/<user>/<repo-name> (private)
  ✓ SessionEnd hook: .claude/hooks/backup.sh
```

If GitHub backup was skipped, show:
```
GitHub backup:
  · Skipped
```

Use ✓ for passed, ✗ for failed (with note), · for skipped/untestable.

**Next step:** Suggest: "Run `cd $OUTWORKOS_PARENT/<project-name> && claude` then `/context-map` to configure this project."

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.