문서

TeamOnly 문서

CLI, 앱이 쓰는 SDK, AI에게 무엇을 쓸지 알려 주는 스킬 파일. 기술적이고 짧아요. 코딩 에이전트는 이 페이지를 /docs.md에서 Markdown으로 읽을 수 있어요.

빠른 시작

Node.js 20 이상이 필요해요. 아직 요금제가 없나요? 처음 로그인할 때 카드 없이 14일 무료 체험을 시작하거나 구독할 수 있어요. 여기서 구독할 수도 있어요. 새 폴더나 이미 있는 프로젝트에서:

  1. npx teamonly init는 시작용 앱과 AI 툴이 따를 안내를 추가해요.
  2. npx teamonly login은 컴퓨터마다 한 번. 회사 이메일과 메일로 받은 6자리 코드를 입력하세요. 회사에 아직 요금제가 없으면 바로 시작되는 14일 무료 체험이나, 결제 페이지로 가는 요금제를 제안해요. 이메일 대신 Enter를 누르면 브라우저로 로그인해요.
  3. npx teamonly deploy는 앱을 https://<app>.<company>.teamonly.app에서 운영되게 해요.

이렇게 보여요:

Terminal~/expenses
npx teamonly initAdded the TeamOnly skill, Cursor rules and SDK경비 승인 툴 만들어 줘(AI가 앱을 만들어요)npx teamonly loginSigned in as dana@acme.comnpx teamonly deployLive at https://expenses.acme.teamonly.appOnly @acme.com accounts can sign in

첫 배포 때 앱, 데이터베이스, 파일 저장소가 만들어져요. 그 뒤의 배포는 같은 주소의 같은 앱을 업데이트해요.

CLI 명령어

npx teamonly login
이 컴퓨터에서 TeamOnly에 로그인해요. 컴퓨터마다 한 번. 회사 이메일과 메일로 받은 6자리 코드를 입력하거나, Enter를 눌러 브라우저로 로그인하세요. --email과 --code는 질문 없이 같은 일을 해요. AI 도구와 스크립트용이에요.
npx teamonly trial --company "Acme"
카드 없이 회사의 14일 무료 체험을 시작해요. 회사에 아직 요금제가 없을 때 login 다음에 쓰세요. 무료 체험은 회사 이메일 도메인당 한 번이에요.
npx teamonly subscribe --plan builder
구독하거나, 무료 체험이 끝난 뒤 앱을 다시 켜요. builder, team, business 중에서 고르세요. 결제 페이지가 열리고, 결제가 끝나면 여기서 로그인돼요.
npx teamonly init
이 폴더를 설정해요: 시작용 앱을 만들거나, 기존 프로젝트에 AI 툴용 TeamOnly 안내(Claude Code 스킬, Cursor 규칙, AGENTS.md)와 SDK를 넣어요.
npx teamonly deploy
앱을 빌드해서 온라인에 올려요. 첫 배포 때 데이터베이스와 파일 저장소를 만들어요. 앱 주소를 보여 줘요.
npx teamonly access
앱을 열 수 있는 사람을 보여 줘요. 목록을 쓰려면 이메일 주소와 함께 only, add, remove를, 회사 전체에 열려면 everyone을 붙이세요.
npx teamonly roles
앱 안에서 승인자나 재무 같은 역할을 사람들에게 줘요. roles add approver dana@acme.com으로 주고, roles remove로 빼고, roles만 쓰면 누가 어떤 역할인지 보여 줘요. 회사 주소만 돼요.
npx teamonly secrets set NAME
이 앱의 시크릿을 저장해요. 값은 입력하라는 안내가 나올 때 넣기 때문에 셸 기록에 남지 않아요.
npx teamonly logs
운영 중인 앱의 요청, console.log 출력, 오류를 실시간으로 봐요. 멈추려면 Ctrl+C를 누르세요.
npx teamonly whoami
누구로 로그인했는지, 요금제, 내 앱을 보여 줘요.
npx teamonly delete
운영 중인 앱을 데이터베이스, 파일과 함께 삭제해요. 먼저 앱 이름을 입력하라고 해요.
npx teamonly logout
이 컴퓨터에서 TeamOnly 로그아웃.

SDK

API는 Hono 앱이에요. SDK의 미들웨어가 요청마다 c.var.ctx에 컨텍스트를 줘요. 내용은 이래요:

ctx.user
로그인한 사람: email, name, 앱을 처음 배포한 사람에게는 isOwner, 그리고 이 앱에서 그 사람의 역할인 roles. 코드가 실행되기 전에 TeamOnly가 검증해요. 신원을 헤더에서 직접 읽지 마세요.
ctx.db
이 앱만의 SQL 데이터베이스(SQLite, Cloudflare D1 기반). 첫 배포 때 만들어져요.
ctx.files
이 앱의 파일 저장소: put, get, list, delete.
ctx.secrets
npx teamonly secrets set으로 설정한 값. ctx.secrets.get(name)으로 읽어요.
ctx.email
동료에게 이메일을 보내요: send({ to, subject, text, html?, replyTo? }). 이메일 참고.
ctx.schedule
TeamOnly가 예약 작업을 실행할 때는 작업 이름, 그 외에는 undefined. 예약 작업 참고.

브라우저에서는 teamonly/react의 useUser()가 { email, name, roles }를 돌려줘요.

api/index.ts예시
import { Hono } from 'hono'
import { teamonly, type TeamOnly } from 'teamonly/server'

const app = new Hono<TeamOnly>()
app.use('*', teamonly())

// Who is signed in. Verified before your code runs.
app.get('/api/me', (c) => {
  return c.json(c.var.ctx.user) // { email: 'dana@acme.com', name: 'Dana Okafor', isOwner: false, roles: ['approver'] }
})

// Save to this app's own database.
app.post('/api/expenses', async (c) => {
  const { db, user } = c.var.ctx
  const { amount, reason } = await c.req.json()
  await db
    .prepare('INSERT INTO expenses (amount, reason, submitted_by) VALUES (?, ?, ?)')
    .bind(amount, reason, user.email)
    .run()
  return c.json({ ok: true })
})

// Store an uploaded receipt in this app's file storage.
app.put('/api/receipts/:id', async (c) => {
  await c.var.ctx.files.put(`receipts/${c.req.param('id')}`, c.req.raw.body)
  return c.json({ ok: true })
})

// Read a secret set with: npx teamonly secrets set QUICKBOOKS_KEY
app.get('/api/sync', async (c) => {
  const key = c.var.ctx.secrets.get('QUICKBOOKS_KEY')
  // ...
})

export default app

이메일

앱은 ctx.email.send()로 회사 사람들에게 이메일을 보낼 수 있어요. 예를 들어 승인자에게 경비가 기다리고 있다고 알릴 때요.

api/index.ts예시
// Tell the approver an expense is waiting.
await c.var.ctx.email.send({
  to: 'priya@acme.com',   // one address or a list, up to 50
  subject: 'An expense is waiting for you',
  text: 'Dana sent a $42 expense. Review it: https://expenses.acme.teamonly.app',
})
  • 회사 자체 이메일 도메인의 주소로만 보내요. 로그인할 수 있는 사람과 같아요. 다른 주소는 거부되고 아무것도 발송되지 않아요.
  • notify@teamonly.app에서 회사의 그 앱 이름으로 보내요(예: “Expenses at Acme”). 답장은 앱 담당자에게 가요. replyTo를 설정하면 그 주소로 가요(이것도 회사 주소).
  • 모든 이메일에 어느 앱이 보냈는지 알려 주는 한 줄이 들어가요.
  • 앱별 한도: 시간당 수신자 100명, 하루 500명, 이메일 한 통에 수신자 최대 50명.
  • 대시보드의 앱 페이지에서 지난 30일 동안 보낸 이메일 수를 볼 수 있어요.
  • 로컬(npm run dev)에서는 이메일을 보내지 않고 터미널에 출력해요.
  • 회사 밖 사람에게 보내는 이메일은 지원하지 않아요.

예약 작업

매일 요약, 알림, 월말 보고서가 필요하면 teamonly.json에 작업을 적으세요. TeamOnly가 그 시간에 각 경로를 호출해요.

teamonly.json예시
{
  "name": "expenses",
  "timezone": "America/New_York",
  "schedules": [
    { "name": "daily-digest", "cron": "0 9 * * 1-5", "path": "/api/cron/daily-digest" }
  ]
}
api/index.ts예시
import { scheduled } from 'teamonly/server'

// Runs at 9am New York time, Monday to Friday.
app.post('/api/cron/daily-digest', scheduled(async (c) => {
  const { db, email } = c.var.ctx
  // ...find what is waiting, then email each approver
  return c.json({ ok: true })
}))
  • cron은 다섯 칸이에요: 분, 시, 일, 월, 요일. 작업은 5분 단위로 실행되니 분은 5의 배수여야 해요.
  • timezone은 선택이고, 기본값은 UTC예요.
  • 경로는 teamonly/server의 scheduled(...)로 감싸세요. URL을 여는 사람은 404를 받아요.
  • 대시보드의 앱 페이지에서 작업마다 마지막 실행과 성공 여부를 볼 수 있어요.
  • 무료 체험이 끝나면 작업이 일시 중지돼요.

역할

승인자, 재무, 관리자 같은 역할은 코드가 아니라 TeamOnly에 저장돼요. 앱 담당자나 관리자가 대시보드의 앱 페이지에서, 또는 npx teamonly roles로 역할을 줘요. 회사 주소만 돼요.

Terminal~/expenses
npx teamonly roles add approver dana@acme.comGave dana@acme.com the role approverapprover: dana@acme.com
api/index.ts예시
// Only approvers can approve.
app.post('/api/expenses/:id/approve', async (c) => {
  const { user } = c.var.ctx
  if (!user.roles.includes('approver')) return c.json({ error: 'Approvers only' }, 403)
  // ...
})

앱은 ctx.user.roles로 읽고, 역할마다 무엇을 할 수 있는지 정해요. 로그인 코드는 여전히 짤 필요가 없어요.

스킬 파일

init은 Claude Code용 스킬 파일과, 같은 내용의 Cursor용 규칙 파일을 추가해요. AI 툴은 코드를 쓰기 전에 이 파일을 읽어요. 알려 주는 내용은 이래요:

  • 지원하는 단 하나의 앱 형태(아래)와, 그 안에서 벗어나지 않기.
  • 신원은 ctx.user로 확인하고, 로그인, 회원가입, 비밀번호 화면을 직접 만들지 않기.
  • 데이터베이스 서비스, Express, Supabase, Firebase를 추가하지 않고 ctx.db와 ctx.files 쓰기.
  • API 키는 코드가 아니라 ctx.secrets에 두기.
  • 이메일은 ctx.email로 보내고, 정해진 시간에 할 일은 예약 작업으로 돌리고, ctx.user.roles는 서버에서 확인하기.
  • deploy를 실행하고 알려 주는 문제를 고치는 방법.

목표는 첫 배포가 한 번에 되는 거예요. AI가 계속 저희가 지원하지 않는 걸 쓰려고 하면 알려 주세요. 스킬 파일의 버그예요.

MCP 서버

teamonly 패키지는 MCP 서버도 실행해요. Claude Desktop이나 터미널 없는 Cursor 에이전트처럼 터미널 명령어보다 도구 호출을 선호하는 AI 툴을 위한 거예요. 배포할 때 프로젝트 파일을 읽어야 해서 내 컴퓨터에서 실행돼요. Claude Code에는 이렇게 추가해요:

Terminal~/expenses
claude mcp add teamonly -- npx -y teamonly mcp
  • Cursor, Claude Desktop, Windsurf: MCP 설정에 { "mcpServers": { "teamonly": { "command": "npx", "args": ["-y", "teamonly", "mcp"] } } }를 추가하세요.
  • VS Code: .vscode/mcp.json에서는 키가 servers이고 "type": "stdio"를 써요.
  • 도구 12개: get_app_rules, init, deploy, status, logs, access, roles, set_secret, sign_in, start_trial, subscribe, delete_app.
  • 공식 MCP Registry에 dev.teamonly/teamonly로 올라가 있어요.

스킬 파일과 규칙이 여전히 AI가 스택을 배우는 주된 방법이고, CLI는 전과 똑같이 동작해요. 툴별 설정과 Cursor 원클릭 설치 버튼: TeamOnly MCP 서버.

지원하는 앱 형태

  • 화면: Vite로 빌드한 React, 정적 파일로 제공.
  • API: TypeScript나 JavaScript로 만든 Hono 앱, 같은 주소의 /api 아래.
  • 런타임: Cloudflare Workers. 표준 웹 API, 대부분의 Node 내장 모듈은 호환 기능으로 지원하지만 네이티브 모듈이나 오래 실행되는 프로세스는 안 돼요.
  • 지원하지 않음: Python, Ruby, PHP, Docker 컨테이너, WebSocket과 실시간 업데이트, 백그라운드 워커, 회사 밖으로 보내는 이메일, 회사 내부 네트워크의 서버 연결, 커스텀 도메인, 공개 앱.

제한

현재 제한:

  • Builder: 제작자 1명, 앱 3개. Team: 제작자와 앱 무제한.
  • 각 앱의 데이터베이스와 파일 저장소는 Cloudflare D1과 R2의 제한을 따라요.
  • 요청은 Cloudflare Workers의 CPU 시간과 메모리 제한 안에서 실행돼요.

전체가 어떻게 맞물리는지 더 보기: 작동 방식, 보안.

얼리 액세스

이번 주에 첫 툴을 올려 보세요.

TeamOnly는 얼리 액세스 중이고, 지금 모든 요금제에 가입할 수 있어요. 카드 없이 14일 동안 무료로 써 보거나, 구독하세요. 어느 쪽이든 회사 설정이 바로 끝나고, 명령어 세 개면 첫 앱이 온라인에 올라가요.

14일 무료 체험하기아니면 지금 구독하기

무료 체험에는 카드가 필요 없어요. 그 뒤로 월 $19부터, 언제든 해지할 수 있어요.

아직 준비가 안 됐나요? 소식 받기

회사 이메일을 써 주세요. 메일은 가끔, 보낼 만한 소식이 있을 때만 보내요.

질문은 hello@teamonly.dev로 보내 주세요. 사람이 직접 답해요.