사람 + Claude 에이전트 혼성 팀을 위한 셀프호스팅 워크스페이스. 프로젝트 · 칸반 태스크 보드 · 문서 · 결정/리스크/용어 · 지식 그래프 · 승인 · 레슨 · 알림 인박스 · 활동 피드 · HTML 퍼블리시를 하나의 앱에서 다루고, 모든 조작이 HTTP API로 열려 있어 에이전트가 일급 팀원으로 참여한다.
문서는 file-first다 — 본문이 DB가 아니라 데이터 디렉토리(TEAMSPACE_DATA_DIR) 아래 파일 + git 저장소로 남아 레포와 분리 백업·이관이 쉽다. Slack 연동을 켜면 "진행 방향을 가르는 결정"을 채널로 보내 버튼/답글로 승인받는 컨펌 루프가 동작하고, 동봉된 MCP 서버·pnpm ws CLI·Claude Code 훅/스킬로 에이전트 세션마다 팀 컨텍스트(레슨·태스크·결정)가 자동 주입된다.
Slack·LLM·Google OAuth 는 전부 선택이다. 없어도 코어(보드·문서·결정·승인)는 그대로 동작한다.
- 스택: Next.js(App Router) + Prisma + Postgres(docker)
- 라이선스: MIT
대시보드 — 마감·상태 분포·담당자별 작업량·최근 활동을 한눈에.
태스크 보드 — 프로젝트별 칸반/표 이중 뷰, 빠른 추가·검색·필터.
문서 — 프로젝트 아래 file-first 문서. 본문은 데이터 디렉토리의 md 파일 + git 이력으로 남는다.
프로젝트 — 열린 태스크·임박 마감·참여자·연결 레포까지 프로젝트 카드로.
승인 인박스 — 에이전트·팀원이 올린 결정을 버튼으로 승인/거부/추가요청. Slack 연동 시 채널 카드로도 처리 가능.
대부분의 기능은 화면 · pnpm ws CLI · HTTP API(/api/*) 세 경로로 같은 동작을 한다. 전체 명령은 pnpm ws help.
- 프로젝트 — 태스크·문서·결정을 묶는 단위. 보관과 프로젝트 단위 공유 범위 제한을 지원한다.
pnpm ws project add - 태스크 보드 — 표·칸반·갤러리·리스트·캘린더·타임라인 뷰에 정렬·필터를 저장한다.
pnpm ws view add - 태스크 — 체크리스트·코멘트·선행 태스크·문서 연결. 클레임은 원자적이라 경쟁하면 409 를 낸다.
pnpm ws task claim - 캘린더 — 태스크 마감·문서 수정일·리마인더를 월/주/일로 본다. 사이드바 › 캘린더
- 대시보드 · 활동 피드 — 마감·상태 분포·작업량과 최근 변경을 모은다.
pnpm ws activity ls
- 문서 — 버전 이력·복원, 템플릿, 인라인 코멘트·멘션, 페이지 단위 공유를 지원한다.
pnpm ws doc history·pnpm ws page share - 위키링크 · 백링크 — 본문의
[[제목]]이 양방향 링크가 된다.pnpm ws doc backlinks - 위키 점검 — 깨진 위키링크와 고아 문서를 찾는다. 문서 허브 › 위키 점검 ·
pnpm ws lint - 지식 그래프 — 문서·프로젝트 등을 노드로, 링크·참조를 근거 태그가 붙은 간선으로 그린다. 문서 허브 › 그래프 뷰 ·
pnpm ws graph·pnpm ws graph neighbors - 문서 허브 — 결정·QA·리스크·용어집·변경 이력·데이터 모델·완료 기준(DoD)·온보딩을 항목 단위로 관리한다.
pnpm ws decision add·pnpm ws glossary extract(문서에서 추출, LLM) - 검색 · Q&A — 전역 검색(
⌘K,/search)과 문서·결정 근거로 답하는 Q&A.pnpm ws search·pnpm ws ask·pnpm ws similar - 웹 클리퍼 — 웹 본문을 요약·분류해 문서로 저장한다(LLM).
/clip·pnpm ws clip - 가져오기 · 내보내기 — 워크스페이스 전체를 zip 으로 내보내고, 노션 export·마크다운 폴더 zip 을 가져온다.
pnpm ws export·pnpm ws import - 휴지통 · 즐겨찾기 · 최근 열람 — 삭제는 휴지통을 거친다.
pnpm ws trash ls·pnpm ws fav add·pnpm ws visit ls
- 팀 컨텍스트 — 워크스페이스 상태를 에이전트가 읽는 Markdown 스냅샷으로 낸다.
GET /api/context·pnpm ws context - 레슨 — 팀 작업 규칙. 범위는 워크스페이스·프로젝트·스택·개인, 주입 방식은 필수·기본·필요할 때만. 세션 시작과 사용자 메시지(키워드·프로젝트 별칭)에 맞춰 주입한다.
pnpm ws lesson add·pnpm ws lesson inspect - 레슨 주간 정리 — 워커가 정리 제안을 만들어 승인 카드로 보내고, 승인하면 적용·백업한다. 되돌릴 수 있다.
pnpm ws lesson hygiene(끄려면LESSON_HYGIENE=off) - 제안 — 에이전트가 레슨·결정 승격을 제안하고 관리자가 승인하면 등록된다.
pnpm ws proposal add - 라이브 세션 · 프레즌스 — 진행 중인 에이전트 세션(레포·브랜치·태스크)과 같은 페이지를 보는 사람을 보여 준다. 설정 › 라이브 세션·잠금 ·
pnpm ws sessions live - 이름 있는 잠금 — 공유 브랜치·배포를 TTL 로 예약한다. pre-push 훅은 경고만 하고 푸시를 막지 않는다.
pnpm ws lock take·pnpm ws lock install-hook - 라우트 규칙 — 작업 디렉토리(cwd)를 프로젝트에 매핑해 컨텍스트 주입 대상을 정한다.
pnpm ws route-rule add - 세션 탐색기 — 인입된 Claude 세션과 타임라인을 본다. 사이드바 › AI 연결 ·
pnpm ws session ls - 스킬 레지스트리 · AI 실행 경로 — 흩어진 SKILL.md 사본의 낡음과 AI 호출 사용처·사용량을 본다.
pnpm ws skills·pnpm ws ai-routes
- 승인 — 결정을 올리면 승인/거부/추가요청으로 처리한다. Slack 연동 시 버튼 카드로도 처리한다(
/api/slack/interactions).pnpm ws approval add - 알림 인박스 — 승인·배정·멘션·마감·제안·공유 알림을 종류별로 묶어 본다.
pnpm ws inbox ls --group - 알림 라우팅 규칙 — 이벤트 × 프로젝트 단위로 Slack 채널을 정한다. 사이드바 › 슬랙 ·
pnpm ws notif-rule add - 리마인더 — 1회 또는 반복(매일·매주) 예약 알림. 워커가 발송한다.
pnpm ws remind add - 주간 다이제스트 — 프로젝트별 주간 요약을 월요일 오전에 보낸다.
pnpm ws digest weekly
HTML 파일·폴더·zip 을 올려 링크로 공유한다. 페이지는 샌드박스 iframe 안에서 돌고 버전·롤백을 지원한다.
- 열람 대상 — 초대한 이메일로 Google 로그인한 사람과 워크스페이스 멤버만 본다. 게스트는 멤버가 되지 않고, 초대를 회수하면 즉시 막힌다.
- 올리기 —
pnpm ws site publish <file.html|dir|file.zip> --title T --slug <영문-kebab> --invite [email protected]. 같은 링크에 새 버전은--site <id>. - 주소 —
<PUBLIC_BASE_URL>/pages/<slug>, 하위 페이지는/pages/<slug>/<페이지>. 슬러그를 바꿔도 옛 주소(/s/<slug>포함)는 308 로 넘어간다. - 관리 — 사이드바 › 퍼블리시 ·
pnpm ws site invite·pnpm ws site rollback·pnpm ws site access(열람 이력)
- 운영 상태 · 지표 — DB·워커·마지막 백업·디스크·AI 비용과 주간 지표. 설정 › 운영 상태 ·
pnpm ws ops status·pnpm ws metrics - 백업 · 복원 리허설 · health —
pnpm backup·pnpm restore:rehearsal·GET /api/health(아래 「데이터 디렉토리」) - env 금고 — 프로젝트·환경별 env 값을 암호화 보관하고 드리프트를 본다.
pnpm ws env ls·pnpm ws env drift - 멤버 · 팀 · 워크스페이스 — 역할(viewer·editor·admin), 팀, 여러 워크스페이스 전환.
pnpm ws member add·pnpm ws workspace switch - 에이전트 토큰 · 페어링 — 토큰 발급·회수와 새 머신 한 줄 설치(브라우저 승인 페어링).
pnpm ws token add(아래 「에이전트 연동」)
docker compose up -d postgres # postgres (호스트 5433 포트)
cp .env.example .env # AUTH_SECRET 채우기: openssl rand -base64 33
pnpm install
pnpm exec prisma migrate deploy
pnpm db:seed # 첫 워크스페이스 + [email protected] 시드
pnpm dev # http://localhost:3000포트 메모: 프로덕션·CLI·MCP 의 기본 대상은 http://localhost:3002 다. 개발 서버를 CLI/MCP 와 같이 쓰려면 pnpm dev -p 3002 로 띄우거나 WS_BASE=http://localhost:3000 을 설정한다.
로그인 없이 바로 둘러보려면 .env 에 AUTH_OPEN_API=true(데모 전용, 아래 참조).
상태 확인: curl localhost:3002/api/health → {ok, db, worker}
문서 md 파일(docs/) · 본문 git 저장소(content/) · 업로드(uploads/)는 레포가 아니라 데이터 디렉토리에 저장된다. 기본값은 <레포 루트>/data, TEAMSPACE_DATA_DIR 로 바꿀 수 있다.
백업은 pnpm backup — Postgres 덤프 + 데이터 디렉토리 tar 를 TEAMSPACE_BACKUP_DIR(기본 ~/Backups/teamspace)에 저장하고 최근 14개를 로테이션한다.
복원 리허설 — 백업은 복원해 봐야 믿을 수 있다. pnpm restore:rehearsal [백업 디렉토리] [--keep](scripts/restore-rehearsal.sh)이 최신(또는 지정) 백업을 스크래치 DB teamspace_rehearsal_<타임스탬프> 에 복원해 7가지(필수 파일·pg_restore·마이그레이션 일치·핵심 테이블 행 수·문서 파일 표본·첨부 아카이브·정리)를 점검하고 결과: PASS|FAIL(FAIL 이면 종료 코드 1)을 출력한다. 운영 DB 는 읽기만 한다. 분기에 한 번 돌리고 결과를 문서로 남긴다:
pnpm restore:rehearsal | tee /tmp/rehearsal.txt
pnpm ws rehearsal record /tmp/rehearsal.txt # 결과 블록 → 문서 "백업 복원 리허설 <YYYY-MM-DD>" 생성워커가 1·4·7·10월 첫 월요일에 관리자 받은편지함으로 분기 복원 리허설 알림을 한 번 보낸다.
데이터 디렉토리 git 백업 패턴(권장) — tar 로테이션은 유실 대비이고, 이력·오프사이트까지 원하면 데이터 디렉토리를 전용 git 저장소로 둔다:
export TEAMSPACE_DATA_DIR=~/teamspace-data # 레포 밖 전용 디렉토리 (.env 에도 설정)
mkdir -p ~/teamspace-data && cd ~/teamspace-data
git init && printf 'content/\n' > .gitignore # content/ 는 앱이 자체 git 저장소로 관리 — 중첩 방지
git remote add origin <사설 원격> # 반드시 private — 문서·업로드 원본이 들어간다docs/(문서 md)·uploads/(첨부)는 이 저장소가 이력을 갖는다. 주기 커밋·푸시는 cron 한 줄:0 4 * * * cd ~/teamspace-data && git add -A && git commit -m backup -q; git push -q origin maincontent/(본문 저장소)는 앱이 변경마다 자체 커밋한다 — 원격 백업까지 하려면 그 안에서 따로git remote add후 위 크론에cd content && git push를 한 줄 더 붙인다.
docker compose up -d postgres
pnpm install && pnpm exec prisma migrate deploy && pnpm db:seed
pnpm build
pnpm start -p 3002 # next start리마인더·마감 알림·LLM 잡을 쓰려면 워커도 함께 상시 실행: pnpm worker
pnpm deploy:localscripts/deploy.sh 가 prod 빌드 후 scripts/launchd/templates/*.plist.tmpl 4종에서 이 머신의 실경로(__REPO_DIR__·__HOME__·__NODE_BIN_DIR__·__PNPM__)를 치환해 ~/Library/LaunchAgents 에 설치·(재)기동한다:
com.teamspace.web—next start -p 3002com.teamspace.worker— 알림/잡 워커(60초 폴링)com.teamspace.backup— 일일 백업com.teamspace.health— 5분마다/api/health확인, 실패 시 재기동
로그는 ~/Library/Logs/teamspace/. 코드 변경 후엔 같은 명령으로 재배포한다.
- 로그 형식: production 은 한 줄 JSON(
LOG_FORMAT=json)이고 수준은LOG_LEVEL(debug|info|warn|error, 기본 info)로 조절한다. 모든 API 응답에x-request-id헤더가 붙으므로, 문제를 보고할 때 이 값으로 서버 로그 줄을 찾는다. - 보안 헤더: CSP 는 Report-Only 로 나가고 위반은
POST /api/csp-report(무인증·본문 2KB·IP 당 1/분)가 받아 로그(csp.violation)로 남긴다. 로그로 허용 목록을 다듬은 뒤 enforce 로 전환한다.
Docker 자동 기동(권장) — 재부팅 후 Docker 데몬이 안 떠 있으면 DB 연결 실패로 웹이 500을 낸다.
com.teamspace.docker LaunchAgent 가 로그인 시 Docker Desktop 을 띄우고 postgres/redis 컨테이너를 보장한다
(scripts/docker-up.sh). 템플릿을 손으로 렌더해 설치한다:
REPO_DIR="$(pwd)" ; sed -e "s|__REPO_DIR__|$REPO_DIR|g" -e "s|__HOME__|$HOME|g" \
scripts/launchd/templates/com.teamspace.docker.plist.tmpl \
> ~/Library/LaunchAgents/com.teamspace.docker.plist
launchctl bootstrap "gui/$(id -u)" ~/Library/LaunchAgents/com.teamspace.docker.plistHTTPS 노출은 Tailscale 로:
tailscale serve --bg 3002 # tailnet 내부 전용
# 또는
tailscale funnel --bg 3002 # 인터넷 공개이후 .env 에 PUBLIC_BASE_URL=https://<호스트>.<tailnet>.ts.net 을 설정하고, Google OAuth 리디렉션 URI 에도 같은 주소를 등록한다.
postgres 는 compose 로, 앱은 systemd 로 상시 구동한다.
docker compose up -d postgres
pnpm install && pnpm exec prisma migrate deploy && pnpm db:seed
pnpm build/etc/systemd/system/teamspace.service 예시:
[Unit]
Description=TeamSpace web
After=network.target docker.service
[Service]
User=ubuntu
WorkingDirectory=/home/ubuntu/teamspace
EnvironmentFile=/home/ubuntu/teamspace/.env
ExecStart=/usr/bin/pnpm start -p 3002
Restart=always
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload && sudo systemctl enable --now teamspace워커도 쓰려면 ExecStart=/usr/bin/pnpm worker 로 유닛 하나 더 만든다. 그 외:
- 보안그룹: 3002(또는 앞단 nginx/ALB 의 443)만 열고 5433(postgres)은 외부에 열지 않는다.
- 데이터 디렉토리: EBS 등 영속 볼륨 경로를
TEAMSPACE_DATA_DIR로 지정. - 백업 크론:
crontab -e→30 3 * * * cd /home/ubuntu/teamspace && /usr/bin/pnpm backup
- MCP 서버 —
.mcp.json에 등록돼 있어 이 레포에서 Claude Code 를 열면 바로 붙는다(scripts/mcp-server.ts, 도구 23개: 컨텍스트·태스크·문서·레슨·결정·제안·인박스·활동·검색·그래프·퍼블리시). 인증은WS_BASE/WS_TOKENenv 또는~/.claude/teamspace.json의{ "base": …, "token": "wst_…" }. pnpm wsCLI — 프로젝트·태스크·문서·결정·레슨을 API 로 조작하는 래퍼.pnpm ws help참조.- 에이전트 토큰 — 관리자가 설정 화면 › 에이전트 토큰 또는
pnpm ws token add <이름>으로 발급(wst_…1회 노출). API 호출은x-ws-token헤더. - Claude Code 훅 —
scripts/hooks/teamspace-context.mjs(SessionStart: 세션마다 팀 컨텍스트 자동 주입) ·teamspace-prompt-lessons.mjs(UserPromptSubmit: 메시지에 맞는 레슨 주입) ·teamspace-project-context.mjs(PostToolUse: 다른 프로젝트 경로를 건드리면 그 프로젝트 컨텍스트 주입, 라이브 세션 하트비트) ·teamspace-session-end.mjs(SessionEnd: 세션 기록) ·deny-repo-docs.mjs(PreToolUse: 문서를 레포에 쓰는 것을 차단해 TeamSpace doc 으로 유도). 별도로 git pre-push 훅git-pre-push.mjs(잠금 경고)는pnpm ws lock install-hook으로 설치한다. - 스킬 —
.claude/skills/(teamspace API 사용법, teamspace-publish, brainstorming, writing-plans). - 새 머신 부트스트랩 — 중앙 서버가 있으면 한 줄로 페어링·토큰·훅·플러그인까지:
Claude Code 플러그인 스택(claude-level-up 마켓플레이스: superpowers · understand-anything · agentmemory · watch)도 기본 설치된다. 건너뛰려면
curl -fsSL https://<서버주소>/setup.sh | sh -s -- https://<서버주소>
--no-plugins, 다른 마켓플레이스는--with-plugins <owner/repo>[@scope].
| 연동 | 없으면 | 켜려면 |
|---|---|---|
| Google OAuth | 로그인 화면 사용 불가 | Google Cloud Console 에서 OAuth 클라이언트 생성 → 리디렉션 URI <PUBLIC_BASE_URL>/api/auth/callback/google 등록 → AUTH_GOOGLE_ID/SECRET |
| Slack | Slack 알림·컨펌만 꺼짐 | 봇 생성 후 AUTH_SLACK_BOT_TOKEN 등 (.env.example 참조) |
| LLM | Q&A 등이 추출형으로 폴백 | ANTHROPIC_API_KEY 또는 로컬 claude CLI(ASK_LLM_PROVIDER=cli) |
| env 금고 | 설정 › env 금고 비활성 | ENV_VAULT_KEY(base64 32바이트, openssl rand -base64 32) — 프로젝트·환경별 env 값을 AES-256-GCM 으로 보관하고 pnpm ws env push 로 .env·AWS SSM·Vercel·GitHub Actions 에 반영(사람 승인 필요). 키를 잃으면 복구 불가 |
| AI 실행 경로 | 설정 › AI 실행 경로의 중계 구역 숨김(자체 LLM 호출 기록은 그대로) | 별도 LLM 중계 서버를 쓰면 AI_RELAY_LOG_PATH·AI_RELAY_HEALTH_URL·AI_RELAY_LAUNCHD_LABEL·AI_RELAY_WATCHDOG_LOG·AI_RELAY_DEPLOY_DIR·AI_RELAY_LABEL |
| 스킬 레지스트리 | 설정 › 스킬 레지스트리 숨김 | SKILL_SCAN_ROOTS(콜론 구분, 예 ~/dev:~/.claude) · 선택 SKILL_SCAN_MAX_DEPTH·SKILL_SCAN_TIME_BUDGET_MS |
데모 모드: AUTH_OPEN_API=true 면 인증 없이 전체 개방된다. 로컬 체험 전용 — 외부에 노출되는 서버에서는 절대 켜지 말 것. NODE_ENV=production 에서는 이 값을 무시한다(실수로 켠 채 배포해도 열리지 않는다). 요청 빈도 제한은 기본으로 켜져 있고, 로컬 부하 시험 등에서 끄려면 RATE_LIMIT=off. 정식 사용은 Google OAuth 를 설정하고 시드된 관리자([email protected])와 같은 이메일의 Google 계정으로 로그인하거나, AUTH_ALLOWED_DOMAINS 로 팀 도메인 자동가입을 열어둔다. 멤버 초대는 pnpm ws member add <email> 또는 멤버 화면. 역할: viewer(읽기) < editor(쓰기) < admin(관리).
| 증상 | 원인 | 조치 |
|---|---|---|
로그인이 거부됨(/login?error=AccessDenied) |
초대되지 않은 이메일이고 AUTH_ALLOWED_DOMAINS 에도 없음 |
관리자가 멤버로 초대하거나 도메인을 허용 목록에 추가 |
| API 가 429 | 레이트 리밋(로그인 20/분 · /api/ask 10/분 · 검색 60/분 등) |
응답의 Retry-After 초 뒤에 재시도. 로컬·테스트는 RATE_LIMIT=off |
| health 가 빨강 | db:false 는 DB 연결 실패. worker:false 는 워커 하트비트가 90초 넘게 없음 |
postgres 컨테이너 확인, 워커 재기동 |
| 기동 실패 "환경변수 검증 실패" | DATABASE_URL·AUTH_SECRET 누락, ASK_LLM_PROVIDER·LLM_DAILY_BUDGET_TOKENS 형식 오류 |
로그에서 "event":"env.invalid" 줄을 찾아 .env 수정(.env.example 참조) |
Ctrl+F 가 보드 행을 못 찾음, Tab 이 중간에서 끊김 |
150행이 넘는 표는 가상 스크롤로 보이는 행만 그린다. 창 밖 행은 DOM 에 없다 | 보드 검색창을 쓰거나, URL 에 ?virtual=0 또는 localStorage.setItem("ws-table-virtual","0") 후 새로고침해 전부 그린다 |
pnpm exec tsc --noEmit && pnpm lint && pnpm test # 게이트 (커밋 전 통과 필수)API 라우트를 추가/변경하면 같은 커밋에서 .claude/skills/teamspace/SKILL.md 와 scripts/ws.ts 를 동기화한다 — scripts/ws.test.ts 패리티 테스트가 어긋남을 잡아낸다. 자세한 작업 규칙은 AGENTS.md.
이슈·PR 환영합니다.




