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.
Scanned 9/12/2026
Install to Claude Code
npx -y skills add MattVOLTA/outworkos_diy --skill setup-project --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Setup Project?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/mattvolta-setup-project)More formats (shields.io, HTML) on the badges page.
---
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."
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!