# ainote — 전체 문서 > AI 네이티브 태스크 + 메모리 + Vault + 다중 디바이스 동기화. MCP 서버. > Source: https://docs.ainote.dev > Generated: 2026-05-07T00:01:30.503Z --- # docs.ainote.dev ainote 의 공식 개발자 문서. VitePress 기반. ## 빠른 시작 ```bash npm install npm run dev # http://localhost:5173 npm run build # llms.txt 자동 생성 + 정적 사이트 빌드 npm run preview # 빌드 결과 로컬에서 확인 ``` ## 구조 ``` docs-site/ ├── .vitepress/ │ └── config.mjs # 8섹션 nav/sidebar + SEO + JSON-LD + sitemap + canonical ├── scripts/ │ └── build-llms.mjs # llms.txt + llms-full.txt 자동 생성 ├── public/ # 정적 자산 (llms.txt 자동 생성됨) │ ├── llms.txt │ └── llms-full.txt ├── index.md # 홈 (hero + features grid + FAQ) ├── guide/ # 시작하기 (5) ├── tasks/ # 태스크 (4) ├── memory/ # 메모리 / Dev Docs (5) ├── vault/ # Vault (5) ├── sync/ # 동기화 (8) ├── mcp/ # MCP 통합 (7) ├── cli/ # CLI (4) ├── reference/ # API 레퍼런스 (28) └── examples/ # 실전 워크플로우 (6) ``` 총 **75 페이지**, 192 KB llms-full.txt. ## 새 페이지 추가 1. 적절한 디렉토리에 `.md` 파일 작성 2. `.vitepress/config.mjs` 의 sidebar 에 항목 추가 3. `npm run dev` 로 확인 4. (배포 시 자동) `npm run build` → llms.txt 갱신 ## 마크다운 주의 - `` 형식의 텍스트는 **반드시 백틱** 으로 감싸야 함 (``) - 그렇지 않으면 VitePress 가 Vue HTML 태그로 파싱 시도 → 빌드 실패 - code block (```) 안은 안전 ## 배포 ### Render Static Site (권장) ```yaml # render.yaml or dashboard type: static_site name: ainote-docs buildCommand: cd docs-site && npm install && npm run build publishPath: docs-site/.vitepress/dist ``` ### 커스텀 도메인 DNS: `docs.ainote.dev` CNAME → `.onrender.com` ## llms.txt 표준 [llmstxt.org](https://llmstxt.org/) 표준 따름: - `/llms.txt` — 페이지 색인 (LLM 이 빠르게 사이트맵 파악) - `/llms-full.txt` — 모든 본문 합친 단일 파일 (LLM context 에 통째로 주입) `scripts/build-llms.mjs` 가 빌드 시 자동 생성. ## 라이선스 MIT — 본 문서 + 코드 모두. --- # 환경변수 / .env 동기화 여러 프로젝트의 `.env` 파일을 ainote + macOS Keychain 으로 안전 동기화. ## 설계 ``` .env 파일 ├─ 실제 값 → macOS Keychain (service: dev-env/{project}) │ └─ iCloud Keychain 으로 맥미니 ↔ 맥북 자동 sync └─ 키 목록 → ainote ({project}-env-ref.md) └─ 키 이름 + 타입(secret/config) + Keychain 서비스명 ``` **왜 분리**: - 값은 Keychain (OS 레벨 암호화 + iCloud 동기화) - 참조만 ainote (어떤 키 있는지 파악, 새 기기 셋업 가이드) ## 스크립트 사용 `~/scripts/ainote-env-sync.sh` (이미 셋업됨, 미설치라면 [GitHub repo](https://github.com/seunghan91/ainote-tools)). ### Push: .env → Keychain + ainote ```bash ainote-env-sync.sh push tennis-bracket ``` 스크립트 동작: 1. `~/tennis_bracket/.env` 읽음 2. 각 KEY=VALUE 를 Keychain 에 저장 (service: `dev-env/tennis-bracket`, account: KEY) 3. 키 목록 (이름만) 을 ainote 에 `tennis-bracket-env-ref.md` 로 저장 ### Pull: Keychain → .env (새 기기) ```bash ainote-env-sync.sh pull tennis-bracket ``` 스크립트 동작: 1. ainote 에서 `tennis-bracket-env-ref.md` 받음 → 키 목록 파싱 2. 각 키를 Keychain 에서 read (iCloud Keychain 으로 이미 sync 됨) 3. `~/tennis_bracket/.env` 생성 ### 일괄 ```bash ainote-env-sync.sh push-all # 등록된 모든 프로젝트 ainote-env-sync.sh pull-all ainote-env-sync.sh list # 등록된 프로젝트 목록 ``` ## 등록된 프로젝트 (예) ```bash ainote-env-sync.sh list ``` ``` tennis-bracket 12 keys Last sync: 2026-05-07 launchcrew 18 keys Last sync: 2026-05-06 keeps 8 keys Last sync: 2026-05-05 triphelper 15 keys realpick 9 keys talkk 14 keys ax-admin 22 keys ainote-app 11 keys ``` ## ainote 에 저장되는 형식 `{project}-env-ref.md`: ```markdown --- name: tennis-bracket-env-ref type: reference ainote_sync: env-refs/tennis-bracket-env-ref.md --- # tennis-bracket .env Reference | Key | Type | Keychain Service | |-----|------|------------------| | RAILS_ENV | config | (env) | | DATABASE_URL | secret | dev-env/tennis-bracket | | TOSS_CLIENT_KEY | secret | dev-env/tennis-bracket | | TOSS_SECRET_KEY | secret | dev-env/tennis-bracket | | FIREBASE_PROJECT_ID | config | (env) | | ... | ``` 값은 ainote 에 안 저장됨. 이름만. ## 비밀 누출 방지 - ❌ `.env` 자체를 ainote 에 push 금지 (값 노출) - ❌ git 에 `.env` 커밋 금지 (`.gitignore` 에 `.env*` 추가) - ✅ `.env.example` 만 git 에 커밋 (값 placeholder) - ✅ 정기적으로 키 회전 (분기마다) ## 새 기기 셋업 (5분) ```bash # 1. ainote-env-sync.sh 받기 curl -O https://raw.githubusercontent.com/seunghan91/ainote-tools/main/ainote-env-sync.sh chmod +x ainote-env-sync.sh # 2. ainote 환경변수 export AINOTE_API_KEY="..." # 3. iCloud Keychain 켜져 있는지 확인 (시스템 설정 → Apple ID) # 4. 일괄 pull ./ainote-env-sync.sh pull-all ``` → 모든 프로젝트의 `.env` 가 자기 자리에 복원. ## 다음 - [shell 함수 셋업](/cli/shell-function) - [`.env` 보안 패턴 예시](/examples/env-sync) - [데이터 내보내기](/guide/data-export) --- # ainote 커맨드라인 ainote 는 별도의 `ainote` CLI 명령을 제공하지 않습니다 — 대신 두 가지 방법으로 터미널에서 사용: 1. **shell 함수 (curl wrapper)** — 가장 간단. 의존성 없음. 권장. 2. **`@ainote/mcp` 패키지의 두 바이너리** — `ainote-mcp` (stdio MCP 서버) + `ainote-mcp-http` (SSE 브리지) ## 빠른 사용 ### shell 함수 한 번 정의 후 ```bash ainote list_tasks '{"status":"pending","limit":10}' ainote create_task '{"content":"테스트"}' ainote pull_dev_docs '{}' ``` `ainote` 는 함수 이름 — 별칭이라 자유롭게 변경 가능. [shell 함수 등록](/cli/shell-function) 참고. ### `@ainote/mcp` 바이너리 ```bash npm install -g @ainote/mcp # stdio MCP 서버 (Claude Desktop 이 spawn 해서 사용) ainote-mcp # SSE 브리지 (ChatGPT) ainote-mcp-http ``` [설치 가이드](/cli/install). ## 주요 워크플로우 | 작업 | 명령 (shell 함수 기준) | |------|----------------------| | 오늘 할 일 | `ainote list_tasks '{"due_today":true}'` | | 새 태스크 | `ainote create_task '{"content":"회의"}'` | | 모든 메모리 받기 | `ainote pull_dev_docs '{}'` | | 카테고리별 받기 | `ainote pull_dev_docs '{"category":"claude"}'` | | .env push | `ainote-env-sync push tennis-bracket` (별도 스크립트) | ## 환경 설정 ```bash # ~/.zshrc export AINOTE_API_URL="https://api.ainote.dev" export AINOTE_API_KEY="h7Axq9XPsDTD2qr5yqtcCSaQ..." ``` ## 다음 - [설치](/cli/install) - [shell 함수 (curl wrapper)](/cli/shell-function) - [환경변수 / .env 동기화](/cli/env-sync) --- # 설치 `@ainote/mcp` 패키지는 두 개의 바이너리를 설치합니다: | 바이너리 | 용도 | |---------|------| | `ainote-mcp` | stdio MCP 서버 (Claude Desktop 등) | | `ainote-mcp-http` | SSE 브리지 (ChatGPT 등) | ::: info `ainote` 명령은 없음 별도 CLI 진입점은 제공하지 않습니다. 자유롭게 호출하려면 [shell 함수 (curl wrapper)](/cli/shell-function) 사용 권장. ::: ## 설치 ```bash npm install -g @ainote/mcp ``` 설치 확인: ```bash which ainote-mcp ainote-mcp-http # /Users/.../bin/ainote-mcp # /Users/.../bin/ainote-mcp-http ``` 요구사항: Node 18+. ## 환경변수 `~/.zshrc` 또는 `~/.bashrc`: ```bash export AINOTE_API_URL="https://api.ainote.dev" # 또는 https://ainote-5muq.onrender.com export AINOTE_API_KEY="h7Axq9XPsDTD2qr5yqtcCSaQ..." ``` reload: ```bash source ~/.zshrc ``` ::: tip stdio 모드는 Claude Desktop 이 직접 spawn `ainote-mcp` 자체는 stdio 프로토콜이라 사람이 직접 실행하면 멈춰있는 것처럼 보임 (정상). Claude Desktop 같은 MCP 클라이언트에서 spawn 해서 사용. ::: ## SSE 브리지 (ChatGPT 용) ```bash export AINOTE_API_KEY="..." export AINOTE_MCP_HTTP_PORT=8765 # 기본 3030 ainote-mcp-http ``` 자세히: [ChatGPT 연결](/mcp/chatgpt). ## Self-host 인스턴스 `AINOTE_API_URL` 만 자체 인스턴스 URL 로 변경: ```bash export AINOTE_API_URL="https://my-ainote.internal" ``` ## 업데이트 ```bash npm update -g @ainote/mcp ``` ## 제거 ```bash npm uninstall -g @ainote/mcp ``` `~/.zshrc` 에서 `AINOTE_*` 라인 삭제. ## 다음 - [shell 함수 (curl wrapper, 의존성 없음)](/cli/shell-function) - [Claude Desktop 연결](/mcp/claude-desktop) - [ChatGPT 연결 (SSE 브리지)](/mcp/chatgpt) --- # shell 함수 (curl wrapper) Node 설치 없이 ainote 호출하는 가장 간단한 방법. ## 셋업 `~/.zshrc` (또는 `~/.bashrc`) 에 추가: ```bash # ainote MCP 환경 export AINOTE_API_URL="https://api.ainote.dev" export AINOTE_API_KEY="h7Axq9XPsDTD2qr5yqtcCSaQ..." # ainote 함수 — JSON-RPC 호출 wrapper (이름은 자유롭게 변경 가능) ainote() { local method="$1" local args="${2:-{\}}" curl -s -X POST "$AINOTE_API_URL/api/mcp" \ -H "Content-Type: application/json" \ -H "Authorization: McpKey $AINOTE_API_KEY" \ -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"${method}\",\"arguments\":${args}}}" \ | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('result',{}).get('content',[{}])[0].get('text','') or json.dumps(d,indent=2,ensure_ascii=False))" 2>/dev/null } ``` reload: ```bash source ~/.zshrc ``` ## 사용 ### 태스크 ```bash # 오늘 할 일 ainote list_tasks '{"due_today":true,"limit":20}' # 새 태스크 (필수: content) ainote create_task '{"content":"테스트","due_date":"2026-05-08T10:00:00+09:00"}' # 완료 처리 (completed_at 으로 ISO 시각) ainote update_task '{"id":"","completed_at":"2026-05-08T10:35:00+09:00"}' # 삭제 (id 는 UUID) ainote delete_task '{"id":""}' ``` ### 메모리 ```bash # 카테고리별 목록 ainote list_dev_docs '{"category":"claude","limit":50}' # 단일 조회 ainote get_dev_doc '{"title":"global-claude-guidelines.md"}' # 검색 ainote list_dev_docs '{"search":"firebase"}' # 일괄 복원 (새 기기) ainote pull_dev_docs '{}' ``` ### Vault / Sync ::: warning 🚧 설계 단계 — 아직 구현 안 됨 `vault_*` / `sync_*` 도구는 아직 `@ainote/mcp` 에 포함돼 있지 않습니다. 호출 시 `Tool not found`. [vault 개요](/vault/overview) / [sync 개요](/sync/overview) 참고. ::: ## 헬퍼 함수 자주 쓰는 패턴 wrap: ```bash # 오늘 할 일 (alias) ainote-today() { ainote list_tasks '{"due_today":true}'; } # Push (파일 → ainote) ainote-push() { local file="$1" local title="$2" local category="${3:-docs}" local content content=$(python3 -c "import sys,json; print(json.dumps(open('$file').read()))") local local_path local_path=$(realpath "$file") ainote update_dev_doc "{\"title\":\"$title\",\"content\":$content,\"local_path\":\"$local_path\",\"subcategory\":\"$category\"}" \ || ainote create_dev_doc "{\"title\":\"$title\",\"content\":$content,\"local_path\":\"$local_path\",\"subcategory\":\"$category\"}" } # 사용 ainote-push ~/CLAUDE.md global-claude-guidelines.md claude ainote-push ~/tennis_bracket/CLAUDE.md tennis-bracket-claude.md claude ``` ## 디버그 모드 원시 응답 보고 싶을 때: ```bash ainote-raw() { curl -sv -X POST "$AINOTE_API_URL" \ -H "Content-Type: application/json" \ -H "Authorization: McpKey $AINOTE_API_KEY" \ -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"$1\",\"arguments\":${2:-{\}}}}" } ainote-raw list_tasks '{}' # 헤더 + 본문 모두 ``` ## .env 보호 키를 `~/.zshrc` 직접 X — `~/.zshenv` 또는 별도 파일: ```bash # ~/.zshenv (gui app 도 읽음) export AINOTE_API_KEY="..." # ~/.zshrc 또는 별도 [ -f ~/.ainote-env ] && source ~/.ainote-env ``` `~/.ainote-env` 는 chmod 600: ```bash chmod 600 ~/.ainote-env ``` ## 다음 - [환경변수 / .env 동기화 자동화](/cli/env-sync) - [Node CLI 설치](/cli/install) - [전체 도구 카탈로그](/reference/) --- # Claude Code 자연어 사용 ainote MCP 등록 후 일상 워크플로우. ## 아침 루틴 ``` 오늘 할 일 보여줘 ``` Claude → `list_tasks {due_today: true, status: pending}` → 대답: ``` 오늘 할 일 5개: ⭐ 강남역 미팅 10:00 (30분 전 알림) 회의 준비 09:30 주간 회고 작성 14:00 코드 리뷰 PR #123 종일 운동 (헬스장) 18:00 ``` ## 작업 중 — 빠른 메모 ``` 이거 메모리에 저장해줘: "Rails 8 의 Solid Queue 는 PUMA worker 안에서도 돌릴 수 있다 (SOLID_QUEUE_IN_PUMA=true)" ``` Claude → `create_dev_doc`: ```json { "title": "feedback-rails8-solid-queue-puma.md", "category": "memory", "type": "feedback", "content": "..." } ``` 다음 세션에서 자동으로 이 메모리 사용 가능. ## CLAUDE.md 업데이트 ``` launchcrew 의 CLAUDE.md 에 "Stripe 방식 snake_case 절대 변경 금지" 추가하고 ainote 에 동기화 ``` Claude: 1. `~/launchcrew/CLAUDE.md` 읽음 2. 새 섹션 추가 3. 파일 저장 4. `update_dev_doc` 으로 ainote push ## 태스크 일괄 추가 ``` 다음주 월요일 회의: - 9시 임원 미팅 - 10시 디자인 리뷰 - 14시 회고 ``` Claude → `create_task` 3번 (또는 batch): ``` ✅ 3개 태스크 생성: - 임원 미팅 (월 09:00) - 디자인 리뷰 (월 10:00) - 회고 (월 14:00) ``` ## 카테고리 자동 매칭 ``` 이번주 운동 3번 — 화/목/토 7시 ``` Claude: 1. `list_categories` → "운동" id 찾음 2. `create_task` 3번, 각각 `category_id: 3` ``` ✅ 3개 운동 태스크 생성됨 (카테고리: 운동) ``` ## 컨텍스트 활용 이전 대화에서 저장된 메모리 자동 활용: ``` 사용자: 그 PostgreSQL 연결 문제 어떻게 풀었지? ``` Claude → `list_dev_docs {search: "postgresql"}` → 메모리 찾음 → 대답: ``` 2026-04-15 메모리에 따르면 — Render Singapore 리전이 idle 시 sleep 되어 첫 호출 5초 wait 발생. 해결: cron 으로 1분마다 ping. ``` ## Vault 활용 ``` personal vault 의 daily/2026-05-07.md 보여줘 ``` Claude: 1. `vault_connect_status` → vault clone 확인 2. 파일 직접 read (vault 가 로컬에 있음) 또는: ``` personal vault 에 오늘 일기 추가: "오후에 ainote docs 작업. 17개 도구 카탈로그 정리 완료." ``` Claude: 1. `vault_sync personal` (최신 상태) 2. `daily/2026-05-07.md` 파일 작성/append 3. `vault_sync personal` (push) ## 회고 (저녁) ``` 오늘 완료한 거 정리해서 일기에 추가 ``` Claude: 1. `list_tasks {status: completed, completed_within_days: 1}` 2. 마크다운 정리 3. `vault_sync` → `daily/2026-05-07.md` append ## 특이 패턴 ### 태스크 완료 자동 reminder ``` 강남역 미팅 끝나면 회고 메모 추가하라고 알려줘 ``` → Claude 가 `create_task` 의 후속 reminder 로 또 다른 태스크 만듦. ### 외부 시스템과 연결 ``` 이 슬랙 메시지 (붙여넣음) 를 launchcrew 메모리에 reference 로 저장 ``` → `create_dev_doc {type: "reference", category: "memory"}`. ## 다음 - [태스크 개요](/tasks/overview) - [메모리 / Dev Docs](/memory/overview) - [Telegram 으로 외부에서 추가](/examples/telegram) --- # CLAUDE.md 멀티프로젝트 관리 17개+ 프로젝트의 CLAUDE.md 를 일관성 있게 유지. ## 문제 ``` ~/CLAUDE.md ~/launchcrew/CLAUDE.md ← Stripe naming 규칙 ~/tennis_bracket/CLAUDE.md ← Stripe naming 규칙 (똑같이 복붙) ~/keeps/CLAUDE.md ← Stripe naming 규칙 ~/triphelper/CLAUDE.md ← Stripe naming 규칙 ... (17개 모두 같은 규칙 + 프로젝트 별 차이) ``` 전역 규칙 변경 시 17개 파일 동시 수정 → drift 발생. ## 해결 패턴 ### 패턴 A: 전역 + 프로젝트 분리 각 프로젝트 `CLAUDE.md` 시작 부분: ```markdown # {Project} Claude Guidelines ## Inherits from Global 이 파일은 ~/CLAUDE.md 의 전역 규칙을 따릅니다. 프로젝트 특유의 규칙만 아래 명시. ## Project-specific ### Tech Stack - Rails 8 + Inertia.js + Svelte 5 - ... ### Domain - ... ``` 전역 (`~/CLAUDE.md`) 에는 모든 프로젝트 공통: - API naming convention (Stripe snake_case) - 한국어 응답 - Apple 계정 매핑 - 디자인 시스템 토큰 - ... → 전역 변경 시 한 곳만 수정. ### 패턴 B: ainote 에 한 번 등록 후 sync 위 패턴 + ainote 로 source-of-truth 화: ```bash # 전역 + 17개 프로젝트 일괄 등록 ~/scripts/ainote-claude-md-init.sh ``` 스크립트 ([CLAUDE.md 통합 관리](/memory/claude-md) 참고): ```bash ainote-push ~/CLAUDE.md global-claude-guidelines.md claude for proj in ~/launchcrew ~/tennis_bracket ~/keeps ~/triphelper \ ~/realpick ~/talkk ~/krx_listing ~/krx_ai \ ~/mbti_luck ~/forfit ~/ax_admin ~/ainote-app ...; do if [ -f "$proj/CLAUDE.md" ]; then name=$(basename "$proj" | tr '_' '-') ainote-push "$proj/CLAUDE.md" "${name}-claude.md" claude fi done ``` 수정 워크플로우: ``` 1. 어디서든 CLAUDE.md 수정 (맥미니 또는 맥북) 2. ainote-push 실행 3. 다른 기기 cron 이 15분 뒤 pull ``` ## 새 프로젝트 추가 시 ```bash # 1. CLAUDE.md 작성 $EDITOR ~/new-project/CLAUDE.md # 2. ainote 등록 ainote-push ~/new-project/CLAUDE.md new-project-claude.md claude # 3. (선택) sync-all 스크립트에 추가 $EDITOR ~/scripts/ainote-claude-md-init.sh ``` ## 일관성 검증 매주 cron: ```bash ~/scripts/ainote-claude-md-audit.sh ``` 체크: - 모든 프로젝트 CLAUDE.md 가 ainote 에 등록됐나 - 로컬 ↔ ainote 내용 일치 - 어느 프로젝트가 stale (최근 변경 없음 = 사용 안 함 가능) 샘플 audit 출력: ``` ✅ 17/17 projects registered ✅ 16/17 in sync ⚠️ 1 drift detected: ~/legacy-app/CLAUDE.md (local) ≠ ainote (last push 2025-12-01) → 수동 검토 후 ainote-push 또는 삭제 ``` ## 디자인 토큰 / 코드 스타일 전역 `~/CLAUDE.md` 의 [vibe-design + IronAct](https://ainote.dev) 섹션: - 디자인 토큰 (Primary `#0000FF`, Border 0px 등) - Gestalt 원칙 - shadcn 패턴 → 프로젝트별 CLAUDE.md 에서는 "전역 따름" 만 명시. 새 프로젝트 시작 시: ```bash cat > ~/new-project/CLAUDE.md <<'EOF' # {Project} Claude Guidelines ## Inherits from ~/CLAUDE.md - 디자인: vibe-design 토큰 - API: Stripe snake_case - 언어: 한국어 ## Project-specific ### Tech Stack ... EOF ainote-push ~/new-project/CLAUDE.md new-project-claude.md claude ``` ## CLAUDE.md 충돌 같은 프로젝트 두 기기 동시 수정 → LWW (5분 임계 후). 복구: ```bash cd ~/.ainote-vault # vault 가 ainote git 미러일 때 git log -p dev/claude/launchcrew-claude.md git show :dev/claude/launchcrew-claude.md > /tmp/lost.md diff /tmp/lost.md ~/launchcrew/CLAUDE.md # 비교 후 머지 ``` ## 다음 - [CLAUDE.md 통합 관리](/memory/claude-md) - [맥미니 ↔ 맥북 동기화](/examples/multi-device) - [Cursor / Windsurf rules 같은 패턴](/memory/cursor-windsurf) --- # .env 보안 동기화 패턴 ## 핵심 원칙 ``` ┌─────────────────────────────────────────────────┐ │ 값 (secret) → macOS Keychain │ │ + iCloud Keychain (자동 sync) │ │ │ │ 키 목록 (참조) → ainote ({project}-env-ref.md) │ │ │ │ ❌ 값 자체를 ainote 에 저장 금지 │ │ ❌ git 에 .env 커밋 금지 │ └─────────────────────────────────────────────────┘ ``` ## 왜 이 분리 | 저장소 | 보안 | 동기화 | 검색 | |--------|------|-------|------| | ainote 에 값 | 약함 (키 노출 위험) | ✅ | ✅ | | Keychain 만 | 강함 (OS 암호화) | ✅ iCloud | ❌ | | **Keychain + ainote 참조** | 강함 | ✅ | ✅ (참조만) | ainote 는 어떤 키가 어디 있는지만 — 실제 값은 Keychain. ## 스크립트 사용 `~/scripts/ainote-env-sync.sh` ([설치 가이드](/cli/env-sync)). ### Push: .env → Keychain + ainote 참조 ```bash ainote-env-sync.sh push tennis-bracket ``` 내부 동작: 1. `~/tennis_bracket/.env` 읽음 2. 각 KEY=VALUE 를 `security add-generic-password` 로 Keychain 저장 - service: `dev-env/tennis-bracket` - account: KEY 이름 3. 키 목록 (이름만) 을 ainote 에 `tennis-bracket-env-ref.md` 로 push ### Pull: Keychain → .env (새 기기) ```bash ainote-env-sync.sh pull tennis-bracket ``` 내부 동작: 1. ainote 에서 `tennis-bracket-env-ref.md` 받음 → 키 목록 2. 각 키를 `security find-generic-password` 로 Keychain read 3. `~/tennis_bracket/.env` 생성 ## ainote 에 저장되는 형식 `tennis-bracket-env-ref.md`: ```markdown --- name: tennis-bracket-env-ref type: reference ainote_sync: env-refs/tennis-bracket-env-ref.md last_pushed: 2026-05-07T10:00:00Z device: macmini-2026-04 --- # tennis-bracket .env Reference | Key | Type | Source | Note | |-----|------|--------|------| | RAILS_ENV | config | env | development/production | | DATABASE_URL | secret | Keychain | postgres URL | | TOSS_CLIENT_KEY | secret | Keychain | TossPayments client | | TOSS_SECRET_KEY | secret | Keychain | TossPayments secret | | FIREBASE_PROJECT_ID | config | env | tennis-bracket-app | | AWS_ACCESS_KEY_ID | secret | Keychain | S3 | | ... | ... | ... | ... | ``` 값 없음. 이름 + 타입만. ## 새 기기 일괄 복원 (5분) ```bash # 1. iCloud Keychain 켜기 (시스템 설정) # 2. 스크립트 받기 brew install ainote-tools # 3. ainote 키 export AINOTE_API_KEY="..." # 4. 일괄 ainote-env-sync.sh pull-all ``` 응답: ``` ✓ ~/launchcrew/.env (18 keys from Keychain) ✓ ~/tennis_bracket/.env (12 keys) ... (12 projects) ✅ 12 .env files restored ``` ## 키 회전 분기마다: ```bash # 1. 새 키 발급 (서비스 사이트) TOSS_SECRET_KEY="" # 2. .env 수정 $EDITOR ~/tennis_bracket/.env # 3. push (Keychain 갱신 + ainote ref 갱신 — last_pushed 만 변경) ainote-env-sync.sh push tennis-bracket ``` ## 비밀 누출 시 ```bash # 1. Keychain 즉시 삭제 security delete-generic-password -s "dev-env/tennis-bracket" -a "TOSS_SECRET_KEY" # 2. ainote 참조에서도 표시 $EDITOR ~/tennis_bracket/.env # KEY 자체 삭제 ainote-env-sync.sh push tennis-bracket ``` 서비스 콘솔에서 키 폐기 + 새 발급. ## 등록된 프로젝트 (예시) ```bash $ ainote-env-sync.sh list tennis-bracket 12 keys Last sync: 2026-05-07 launchcrew 18 keys Last sync: 2026-05-06 keeps 8 keys Last sync: 2026-05-05 triphelper 15 keys realpick 9 keys talkk 14 keys ax-admin 22 keys ainote-app 11 keys krx-listing 19 keys krx-ai 13 keys mbti-luck 7 keys forfit 6 keys ``` ## 비교: 다른 솔루션 | | ainote-env-sync | 1Password CLI | doppler | direct iCloud | |---|---|---|---|---| | iCloud Keychain | ✅ free | △ | ❌ | ✅ | | 키 목록 검색 | ✅ ainote | ✅ | ✅ | ❌ | | 무료 | ✅ | △ | ⚠️ free tier | ✅ | | 프로젝트별 .env | ✅ | △ vault | ✅ env | ❌ | | 자동화 친화 | ✅ shell | ✅ CLI | ✅ CLI | ❌ | ## 다음 - [CLI / shell 함수](/cli/shell-function) - [환경변수 / .env 동기화 자세히](/cli/env-sync) - [데이터 내보내기 / 삭제](/guide/data-export) --- # 맥미니 ↔ 맥북 메모리 동기화 실전 시나리오: 두 Mac 에서 같은 메모리/CLAUDE.md 사용. ## 시나리오 - 사무실: **맥미니** (24/7 켜져있음) - 카페/이동: **맥북에어** (간헐적 사용) - 양쪽에서 17개 프로젝트 작업, CLAUDE.md / 메모리 / `.cursorrules` 모두 동기화 필요 ## 1회 셋업 (맥미니 — 메인) ### A. CLI / 환경 ```bash # ~/.zshrc export AINOTE_API_URL="https://api.ainote.dev/api/mcp" export AINOTE_API_KEY="" ainote() { # ... shell 함수 } source ~/.zshrc ``` ### B. 모든 CLAUDE.md 일괄 등록 ```bash ainote-push() { ... } # 정의 # 전역 ainote-push ~/CLAUDE.md global-claude-guidelines.md claude # 17개 프로젝트 for proj in ~/launchcrew ~/tennis_bracket ~/keeps ~/triphelper \ ~/realpick ~/talkk ~/krx_listing ~/krx_ai ...; do if [ -f "$proj/CLAUDE.md" ]; then name=$(basename "$proj" | tr '_' '-') ainote-push "$proj/CLAUDE.md" "${name}-claude.md" claude fi done # .cursorrules 들 for proj in ~/launchcrew ~/krx_ai; do [ -f "$proj/.cursorrules" ] || continue name=$(basename "$proj" | tr '_' '-') ainote-push "$proj/.cursorrules" "${name}-cursorrules.md" cursor done ``` ### C. 메모리 등록 ```bash for md in ~/.claude/projects/*/memory/*.md; do proj=$(basename $(dirname $(dirname "$md")) | sed 's/-Users-seunghan-//' | sed 's/-Users-seunghan/global/') fname=$(basename "$md" .md) title="${proj}-${fname}.md" ainote-push "$md" "$title" memory done ``` ### D. 정기 cron (맥미니) ```bash # crontab -e */15 * * * * /Users/seunghan/scripts/ainote-sync-all.sh >> /tmp/ainote.log 2>&1 ``` ## 2회 셋업 (맥북 — 새 기기) ### A. 같은 CLI 셋업 ```bash # ~/.zshrc 동일하게 # 단 AINOTE_API_KEY 는 새로 발급 (맥북용) ``` 키 발급: ```bash ainote login_and_get_key '{ "email":"me@example.com", "password":"...", "name":"Seunghan" }' ``` ### B. 일괄 pull ```bash # 모든 dev_doc 받기 ainote pull_dev_docs '{}' ``` 응답: ``` ✓ /Users/seunghan/CLAUDE.md (created) ✓ /Users/seunghan/launchcrew/CLAUDE.md (created) ✓ /Users/seunghan/launchcrew/.cursorrules (created) ✓ /Users/seunghan/.claude/projects/-Users-seunghan-launchcrew/memory/MEMORY.md (created) ... (53 files) ✅ 53 dev_docs pulled, 0 errors ``` ### C. 같은 cron 설정 ## 일상 사용 ### 맥미니에서 CLAUDE.md 수정 ```bash $EDITOR ~/launchcrew/CLAUDE.md ainote-push ~/launchcrew/CLAUDE.md launchcrew-claude.md claude ``` ### 15분 뒤 맥북에서 자동 받음 cron 이 `pull_dev_docs '{"since":""}'` 실행 → 변경된 파일만 갱신. ### 또는 Claude 에서 명시적 맥북 Claude: ``` ainote 에서 변경된 CLAUDE.md 다 받아줘 ``` ## 충돌 시나리오 ### A. 맥미니 14:00 / 맥북 14:01 동시 수정 같은 파일 → 5분 임계 안 → conflict 디렉토리: ``` ~/.claude/ainote-sync/conflicts/ └── 2026-05-07T14-01-00__claude-md__launchcrew-claude.md.diff ``` 다음 sync 막힘. 사용자가 수동: ```bash cat ~/.claude/ainote-sync/conflicts/...diff $EDITOR /tmp/merged.md # 양쪽 합침 ainote sync_resolve launchcrew-claude.md --merge /tmp/merged.md ``` ### B. 맥미니 14:00 / 맥북 16:00 5분 이상 → LWW → 맥북 (HLC 더 큼) 이김. 맥미니 변경은 git history 에: ```bash cd ~/.ainote-vault/projects/launchcrew git log -p CLAUDE.md ``` ## 추가 디바이스 (iPad) iPad 는 stdio/SSE 어려움 → ChatGPT/Claude iOS 앱 + ainote 의 mobile app 사용. ainote iOS 앱은 자체 sync 처리 — manifest 기반 시스템과 별개 (REST API). ## 비용 - 맥미니: 24/7 cron → 96 호출/일 (sync_list) - 맥북: 15분마다 → 96 호출/일 - 합 192/20000 (free tier 의 1%) 여유 충분. ## 다음 - [새 디바이스 셋업](/examples/new-device) - [.env 보안 동기화 패턴](/examples/env-sync) - [충돌 해결 자세히](/sync/conflicts) --- # 새 디바이스 셋업 (5분) 새 Mac 받았다. ainote 모든 데이터 + 17개 프로젝트 CLAUDE.md + 메모리 + `.env` 복원. ## 시간 분배 - 1분 — 환경 setup (zsh 함수 + env) - 30초 — 키 발급 - 2분 — `pull_dev_docs` 일괄 복원 - 30초 — `.env` Keychain 복원 ::: warning 🚧 vault_clone 은 설계 단계 아직 `@ainote/mcp` 에 포함되지 않은 도구입니다. 출시되면 추가 1분. ::: ## 0. Prereq - macOS 최신 - iCloud Keychain ON (Apple ID 로그인 → 시스템 설정 확인) - Homebrew + zsh ## 1. CLI 환경 (1분) ```bash # ainote shell 함수 다운로드 curl -O https://raw.githubusercontent.com/seunghan91/ainote-tools/main/setup-shell.sh bash setup-shell.sh ``` 이 스크립트가 `~/.zshrc` 에 `ainote()` 함수 + `AINOTE_API_URL` 추가. 수동: ```bash cat >> ~/.zshrc <<'EOF' export AINOTE_API_URL="https://api.ainote.dev/api/mcp" ainote() { local method="$1" local args="${2:-{\}}" curl -s -X POST "$AINOTE_API_URL" \ -H "Content-Type: application/json" \ -H "Authorization: McpKey $AINOTE_API_KEY" \ -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"${method}\",\"arguments\":${args}}}" \ | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('result',{}).get('content',[{}])[0].get('text','') or json.dumps(d,indent=2,ensure_ascii=False))" } EOF source ~/.zshrc ``` ## 2. 키 발급 (30초) ```bash # 인증 헤더 없이 호출 (signup/login 은 헤더 불필요) curl -X POST "$AINOTE_API_URL" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc":"2.0","id":1,"method":"tools/call", "params":{"name":"login_and_get_key","arguments":{ "email":"me@example.com", "password":"...", "name":"Seunghan" }} }' ``` 응답에서 `mcp_key` 복사 → `~/.zshrc`: ```bash export AINOTE_API_KEY="h7Ax..." source ~/.zshrc ``` ## 3. dev_docs 일괄 (2분) ```bash ainote pull_dev_docs '{}' ``` 응답: ``` ✓ /Users/seunghan/CLAUDE.md (created) ✓ /Users/seunghan/.claude/PERSONAS.md (created) ✓ /Users/seunghan/.claude/ORCHESTRATOR.md (created) ✓ /Users/seunghan/launchcrew/CLAUDE.md (created) ← 디렉토리 자동 생성 ✓ /Users/seunghan/tennis_bracket/CLAUDE.md (created) ... (53 files) ✅ 53 dev_docs pulled, 0 errors ``` ::: warning 디렉토리 자동 생성 `pull_dev_docs` 가 `local_path` 의 부모 디렉토리도 자동 mkdir. 단 권한 문제 시 skipped. ::: ## 4. Vault 클론 (1분, 선택) ::: warning 🚧 설계 단계 `vault_clone` 도구는 아직 미구현. 출시 후엔: ```bash # 목록 확인 ainote vault_list '{}' # 필요한 거 clone ainote vault_clone '{"name":"personal","destination":"/Users/seunghan/notes/personal"}' ainote vault_clone '{"name":"work","destination":"/Users/seunghan/notes/work"}' ``` ::: ## 5. .env Keychain 복원 (30초) ```bash # 스크립트 다운로드 curl -O https://raw.githubusercontent.com/seunghan91/ainote-tools/main/ainote-env-sync.sh chmod +x ainote-env-sync.sh sudo mv ainote-env-sync.sh /usr/local/bin/ # 일괄 pull (iCloud Keychain 이 이미 sync 됨) ainote-env-sync.sh pull-all ``` 응답: ``` ✓ ~/launchcrew/.env (12 keys) ✓ ~/tennis_bracket/.env (8 keys) ✓ ~/keeps/.env (15 keys) ... (12 projects) ✅ 12 .env files restored from Keychain ``` ## 6. Claude Code MCP 등록 `~/.claude.json`: ```json { "mcpServers": { "ainote": { "type": "http", "url": "https://api.ainote.dev/api/mcp", "headers": { "Authorization": "McpKey YOUR_KEY" } } } } ``` Claude Code 시작 → `/mcp` 확인. ## 7. (선택) sync 자동화 ```bash # crontab -e */15 * * * * /usr/local/bin/ainote-sync-all.sh >> /tmp/ainote.log 2>&1 ``` 또는 launchd plist (macOS 권장). ## 검증 체크리스트 ```bash ls ~/CLAUDE.md # 있어야 함 ls ~/launchcrew/CLAUDE.md ls ~/tennis_bracket/CLAUDE.md cat ~/launchcrew/.env | head # 키 복원됨 ainote whoami # 키 정상 ainote list_dev_docs '{"limit":5}' # 응답 옴 ``` ## 총 시간 5분 + 모든 프로젝트 git clone 시간 (별도). ## 다음 - [맥미니 ↔ 맥북 일상 동기화](/examples/multi-device) - [.env 보안 패턴](/examples/env-sync) - [Claude Code 자연어 사용](/examples/claude-code) --- # Telegram 으로 외부에서 태스크 추가 집에서 출근길 — 지하철에서 빠르게 메모/태스크. Telegram 봇이 가장 빠름. ## 셋업 [Telegram 연결](/mcp/telegram) 페이지의 1회 셋업 완료 후. ## 패턴 1: 즉시 태스크 지하철에서 갑자기 떠오른 거: ``` /task 오늘 저녁 7시 헬스장 ``` 봇 응답: ``` ✅ "헬스장" 추가 📅 오늘 19:00 (5시간 후) 🏷️ 운동 🔔 30분 전 알림 ``` ## 패턴 2: 음성 → 태스크 (계획됨) 음성 메시지 → Whisper STT → `create_task`: ``` [🎤 voice] "내일 오전 10시 강남역 미팅, 30분 전 알려줘" ``` 봇: ``` ✅ 강남역 미팅 (내일 10:00, 알림 30분 전) ``` ## 패턴 3: 빠른 메모 생각난 아이디어: ``` /memo Rails 8 의 Solid Queue 가 PUMA 안에서 돌릴 수 있다 (SOLID_QUEUE_IN_PUMA=true) ``` 봇: ``` 📝 메모리 저장됨 — type: feedback "Rails 8 의 Solid Queue 가 PUMA 안에서 돌릴 수 있다..." ``` 다음번 Claude Code 세션에서 자동으로 이 메모리 사용. ## 패턴 4: 오늘 할 일 확인 지하철에서 오늘 일정 다시 보기: ``` /today ``` 봇: ``` 오늘 할 일 (3개): ⭐ 강남역 미팅 10:00 회의 준비 09:30 헬스장 19:00 [모두 보기] [내일] ``` ## 패턴 5: 인라인 검색 다른 채팅에서 ainote 검색 결과 공유: ``` @clawdbot Stripe naming ``` → 검색 결과 (본인만 보임) → 클릭하면 친구 채팅에 인용. ## 패턴 6: 알림 → 즉시 처리 ainote 알림이 봇 DM 으로: ``` ⏰ 30분 후 마감 "강남역 미팅" — 오늘 10:00 [완료] [+30분 미루기] [취소] ``` 버튼 한 번으로 처리. Claude/웹 안 열어도 됨. ## 패턴 7: 멀티-단계 워크플로우 ``` /ai 다음주 마감 중요한 거 모아서 일정표로 만들어줘 ``` 봇 (내부 Anthropic API): 1. `list_tasks {due_within_days: 7, important: true}` 2. 결과 정리 3. 표/요약 응답 ``` 다음주 중요한 일정: 월요일 ⭐ 임원 미팅 09:00 ⭐ 디자인 리뷰 10:00 수요일 ⭐ 디자인 검수 14:00 금요일 ⭐ 분기 회고 15:00 🔧 주말 출근 가능성: 디자인 검수 결과에 따라 ``` ## 한계 | | Telegram | Claude Code | |---|---|---| | 속도 | ⚡ 즉시 (모바일) | 💻 데스크톱 필요 | | 도구 갯수 | 6-8개 (자주 쓰는 거) | 17개 전체 | | 자동화 | △ (사용자 명령마다) | ✅ AI 자율 | | 컨텍스트 | 단일 메시지 | 긴 대화 | → Telegram = **모바일 빠른 입력**, Claude Code = **깊은 작업**. ## 보안 - 봇 → ainote 호출은 사용자별 MCP key 사용 (연동 시 발급) - DM 메시지 본문 ainote 에 저장 안 함 (`/memo` 명시 시만) - 그룹 채팅: 메시지 자동 학습 X - 봇 차단 시 즉시 호출 중단 ## 다음 - [Telegram 연결 셋업](/mcp/telegram) - [태스크 개요](/tasks/overview) - [Claude Code 자연어 사용](/examples/claude-code) --- # API Key 인증 ainote 는 **두 종류**의 키를 지원합니다. | 키 종류 | 길이 | 용도 | 발급 위치 | |--------|------|------|----------| | **User API Key** | 24자 | 개인 앱/스크립트 | 웹 설정 → API | | **MCP Key** | 64자 | MCP 클라이언트 | `signup_and_get_key` 또는 웹 설정 | 대부분의 사용자는 **MCP Key** 만 쓰면 됩니다. ## MCP Key 발급 — 3가지 방법 ### 방법 A. Claude 에서 가입 (가장 쉬움) MCP 등록 후 (헤더 없이도 가능): ``` ainote 가입 시켜줘 — 이메일 X / 비번 Y ``` → `signup_and_get_key` 호출 → 키 반환. ### 방법 B. 기존 계정 로그인 ``` ainote 로그인 — 이메일 X / 비번 Y, 키 발급 ``` → `login_and_get_key` 호출. ### 방법 C. 웹에서 직접 → "새 MCP 키 발급" → 라벨 입력 → 복사. ## 헤더 형식 JSON-RPC 호출 시: ```http POST /api/mcp HTTP/1.1 Host: api.ainote.dev Content-Type: application/json Authorization: McpKey h7Axq9XPsDTD2qr5yqtcCSaQ... ``` ::: tip Bearer 도 호환 일부 MCP 클라이언트는 `Authorization: Bearer ...` 만 지원합니다. ainote 는 `Bearer` 와 `McpKey` 두 prefix 모두 받습니다. ::: ## 키 권한 모델 키 발급 시 권한 선택: - **read** — `list_*`, `get_*`, `pull_*` 만 호출 가능 - **read_write** (기본) — 모두 가능 - **admin** — 다른 키 관리 + 사용량 통계 조회 기본적으로 **per-key 사용량 통계**가 자동 기록됩니다 (요청수, 토큰수, 마지막 호출 시각). 웹 설정에서 확인. ## 키 회전 / 폐기 ``` ainote 키 폐기 — 이름 "old-laptop" ``` → `revoke_mcp_key` (계획됨, 현재는 웹에서만). ## CLI 환경변수 shell 함수로 쓸 때: ```bash # ~/.zshrc export AINOTE_API_URL="https://api.ainote.dev/api/mcp" export AINOTE_API_KEY="h7Axq9XPsDTD2qr5yqtcCSaQ..." ``` 자세한 패턴: [CLI / shell 함수](/cli/shell-function). ## 보안 권장 - ✅ 키는 `~/.claude.json` 또는 환경변수로만. **절대 git 에 커밋 X**. - ✅ 노트북마다 별도 키 발급 (라벨로 구분: `macmini-2026-04`). - ✅ 분실/노출 의심 시 즉시 폐기. - ❌ 한 키를 여러 사용자가 공유하지 마세요 — 사용량 통계 의미 사라짐. ## 다음 - [JSON-RPC 호출 형식](/reference/json-rpc) - [에러 코드](/reference/errors) - [MCP 등록 (Claude Code)](/mcp/claude-code) --- # 데이터 내보내기 / 삭제 ainote 의 모든 데이터는 **너의 것**. 언제든 가져갈 수 있고 삭제할 수 있습니다. ## 내보내기 — 전체 백업 ### 방법 A. Web UI → "전체 내보내기" 버튼. 이메일로 ZIP 링크 (보통 5분 이내): ``` ainote-export-2026-05-07.zip ├── tasks.json # 모든 태스크 ├── categories.json ├── dev_docs/ # 모든 dev_docs (마크다운) ├── memory/ # 토픽 메모리 ├── vaults/ # 각 vault 의 git bundle │ ├── personal.bundle │ └── work.bundle ├── notifications.json └── manifest.json # 메타데이터 ``` ### 방법 B. MCP 도구 (계획) ``` ainote 전체 내보내기 — 형식: zip ``` → `export_all` (계획됨, v0.5). 현재는 개별 도구로: ``` list_tasks → tasks.json list_dev_docs → dev_docs.json (전부) vault_list → 각 vault 마다 vault_sync 후 로컬 디렉토리 백업 ``` ### 방법 C. 직접 git pull 각 vault 는 git 저장소이므로: ```bash git clone https://api.ainote.dev/vaults/.git ``` (인증: HTTPS basic auth, username = email, password = MCP key) ## 부분 삭제 ### 단일 태스크 ``` ainote 에서 태스크 #1234 삭제해줘 ``` → soft-delete (30일 후 영구). 그 사이 복구 가능. ### 단일 메모리 ``` ainote 에서 dev_doc "old-notes.md" 삭제해줘 ``` → `delete_dev_doc` 즉시 영구 삭제. ### Vault 전체 ``` ainote 에서 vault "experiments" 삭제해줘 ``` → Web UI 에서만 가능 (실수 방지). ## 계정 완전 삭제 ### 절차 1. 접속 2. "계정 삭제" 클릭 3. 비밀번호 재입력 + "DELETE" 타이핑 4. 이메일 확인 링크 클릭 ### 삭제 타임라인 | 시점 | 무엇 | |------|------| | 즉시 | API 접근 차단, 모든 키 무효화 | | 즉시 | 다른 디바이스 push 알림 중단 | | **30일** | 모든 데이터 영구 삭제 (PostgreSQL + git + S3) | | 30일 이내 | 로그인 가능 → 복구 가능 | ### 30일 이전 복구 `me@example.com` 으로 로그인 → "계정 복구" 버튼. ### 30일 이후 복구 불가능. 백업도 30일 이후 모두 폐기. ## GDPR / 개인정보 ainote 는 한국 개인정보보호법 + GDPR 준수: - **데이터 최소화**: 이메일 + 비번 hash 외 강제 수집 X - **포터빌리티**: 위 export 절차로 모두 받아갈 수 있음 - **삭제권**: 30일 내 완전 삭제 보장 - **고지**: 보관/암호화/제3자 공유 모두 [개인정보처리방침](https://ainote.dev/privacy) 명시 ## 셀프호스팅 시 데이터가 너의 서버에 있으므로: - PostgreSQL `pg_dump` 로 직접 백업 - vault git repo 직접 clone - 삭제: 인스턴스 종료 + DB drop ## 다음 - [Troubleshooting](/guide/troubleshooting) - [개인정보처리방침](https://ainote.dev/privacy) (외부 링크) --- # FAQ ## 일반 ### ainote 와 Notion / Obsidian 차이가 뭔가요? | | ainote | Notion | Obsidian | |---|---|---|---| | **AI 직접 호출** | ✅ MCP 17개 도구 | ❌ | △ 플러그인 | | **자연어 태스크** | ✅ | △ AI Add | ❌ | | **Git backend** | ✅ | ❌ | △ Sync 플러그인 | | **셀프호스팅** | ✅ | ❌ | ✅ | | **다중 디바이스 sync** | ✅ HLC | ✅ 클라우드 | △ Sync 유료 | ainote 는 **AI 가 1급 시민**이라는 점이 가장 다릅니다. Notion/Obsidian 은 AI 통합이 추가 기능이지만 ainote 는 시작부터 MCP 우선. ### 무료인가요? 현재 v0.x 는 전부 무료. 유료 플랜은 v1 이후 (저장량/MCP 호출 무제한 등). ### 데이터 어디 저장되나요? 호스팅 (api.ainote.dev): Render Singapore 리전, PostgreSQL + git 저장소. 셀프호스팅: 자체 인스턴스 가능 (도커 이미지 계획). ### 데이터 내보낼 수 있나요? [데이터 내보내기 / 삭제](/guide/data-export) 참고. 모든 메모리/태스크/vault 를 ZIP 또는 git push 로 내보낼 수 있고, 계정 삭제 시 30일 안에 영구 삭제. ## 기능 ### Obsidian 사용 중인데 마이그레이션 가능? 네. `vault_clone` 으로 기존 Obsidian vault 를 git 으로 등록하면 됩니다. `[[wikilinks]]` 그대로 호환. ``` ainote 에 ~/Documents/MyObsidianVault 를 vault_clone 해줘 ``` ### CLAUDE.md 17개 프로젝트 동기화 어떻게? [CLAUDE.md 통합 관리](/memory/claude-md) 참고. `create_dev_doc` 으로 한 번 등록 → 다른 기기에서 `pull_dev_docs` 한 번이면 전체 복원. ### 태스크 알림 어디로 오나요? 설정한 채널 모두: - iOS / Android 푸시 (앱 설치 시) - Telegram (봇 연동 시) - Web Push (PWA 등록 시) - Apple Watch (iOS 동기화) ### 반복 태스크 지원? 네. `create_task` 의 `recurring` 파라미터: ```json { "title": "주간 회고", "recurring": { "freq": "weekly", "by_day": ["FR"], "until": "2026-12-31" } } ``` iCalendar RRULE 부분 지원. ## MCP / AI 통합 ### Claude / GPT 가 내 모든 노트 다 보나요? 아니요. **MCP 도구를 호출할 때만** 그 도구가 반환하는 데이터만 봅니다. 예시: - `list_tasks` → 필터에 매칭되는 태스크만 - `get_dev_doc` → 그 한 문서만 - `vault_sync` → 변경된 파일 목록 (내용 X) 도구 호출 단위로 Claude Code 에서 승인 가능. ### ChatGPT 에서도 되나요? 네. SSE bridge (`ainote-mcp-http`) 로컬 실행 → ChatGPT MCP connector 등록. [ChatGPT 연결](/mcp/chatgpt) 참고. ### Telegram 에서 외부에서 쓸 수 있나요? 네. 봇 연동 후 `/task 내일 회의` 같은 식으로. [Telegram 연결](/mcp/telegram). ## 가격 / 제한 ### Rate limit 걸렸어요 | 키 종류 | 분당 | 일당 | |--------|------|------| | User API Key | 60 | 10,000 | | MCP Key (free) | 120 | 20,000 | 429 응답 시 `Retry-After` 헤더 보고 대기. ### 저장 용량 제한? v0.x: 사용자당 100 MB (메모리 + dev_docs). Vault 는 별도 git 저장소 1 GB까지. ## 기술적 ### Rails 8 라는데 React Native 앱은? `ainote_server/` 가 Rails 8 (API + Inertia.js + Svelte 5 웹). 모바일은 별도: - iOS/Android: Flutter (BLoC + Clean Architecture) - macOS: 네이티브 SwiftUI - Apple Watch: WatchKit - Chrome/Safari: 확장 프로그램 ### 오픈소스인가요? 코어는 MIT. . ## 안 풀리면 - [Troubleshooting](/guide/troubleshooting) - GitHub Issues: - Email: support@ainote.dev --- # 5분 Quickstart 계정 없이 바로 시작. Claude Code 기준 (다른 클라이언트는 [MCP 섹션](/mcp/overview) 참고). ## 1. MCP 서버 등록 (1분) `~/.claude.json` 에 추가: ```json { "mcpServers": { "ainote": { "type": "http", "url": "https://api.ainote.dev/api/mcp" } } } ``` ::: warning `type: "http"` 필수 원격 MCP 서버는 반드시 `type` 명시. 빠지면 `~/.claude.json` 전체 mcpServers 블록이 스키마 검증 실패해서 모든 user-level MCP 가 미로드됩니다. ::: Claude Code 재시작. ## 2. 가입 (1분) Claude 에서: ``` ainote 가입 시켜줘 — 이메일 you@example.com / 비번 안전한패스워드123 ``` Claude 가 `signup_and_get_key` 도구를 호출합니다. 응답에 API 키가 옵니다 (64자): ``` Mcp Key: h7Axq9XPsDTD2qr5yqtcCSaQ... ``` ## 3. API Key 등록 (30초) `~/.claude.json` 에 헤더 추가: ```json { "mcpServers": { "ainote": { "type": "http", "url": "https://api.ainote.dev/api/mcp", "headers": { "Authorization": "McpKey h7Axq9XPsDTD2qr5yqtcCSaQ..." } } } } ``` Claude Code 재시작. ## 4. 첫 태스크 (30초) ``` 내일 오전 10시 회의 준비 추가해줘 ``` Claude 가 `create_task` 호출: ```json { "title": "회의 준비", "due_at": "2026-05-08T10:00:00+09:00", "important": false } ``` ## 5. 첫 메모리 저장 (1분) ``` 이거 메모리에 저장해줘: - 우리 회사는 Stripe 방식 따라서 API 응답 snake_case 유지함 - 절대 camelCase 로 변환하지 말 것 ``` Claude 가 `create_dev_doc` 호출: ```json { "title": "api-naming-convention.md", "category": "feedback", "content": "# API Naming\n\n우리는 Stripe 방식으로 snake_case..." } ``` ## 6. 다른 기기에서 가져오기 (1분) 다른 기기 Claude: ``` ainote 에서 내 메모리 다 가져와 ``` `pull_dev_docs` 가 모든 메모리를 `local_path` 기준으로 복원합니다. ## 다음 단계 | 하고 싶은 것 | 어디로 | |------------|--------| | 태스크 18+ 필터 | [태스크 필터링](/tasks/filtering) | | CLAUDE.md 동기화 | [메모리 / CLAUDE.md](/memory/claude-md) | | Obsidian 마이그 | [Vault 시작](/vault/overview) | | 다중 기기 sync | [동기화 개요](/sync/overview) | | ChatGPT 에서 쓰기 | [ChatGPT 연결](/mcp/chatgpt) | | Telegram 봇 | [Telegram 연결](/mcp/telegram) | ## 안 되면 - [Troubleshooting](/guide/troubleshooting) - GitHub Issues: --- # 계정 만들기 (MCP 안에서) ainote 의 가장 흥미로운 기능 — **웹사이트 방문 없이 Claude/Cursor 안에서 바로 가입**. ## 작동 원리 `signup_and_get_key` 와 `login_and_get_key` 두 도구는 **인증 헤더 없이** 호출 가능. 다른 모든 도구는 키 필수. ``` [키 없음] → signup_and_get_key → [키 발급] → 나머지 16개 도구 ``` ## 1단계: MCP 등록 (키 없이) `~/.claude.json`: ```json { "mcpServers": { "ainote": { "type": "http", "url": "https://api.ainote.dev/api/mcp" } } } ``` `headers` 항목 자체를 빼면 됩니다. ## 2단계: Claude 에서 가입 요청 ``` ainote 가입 시켜줘 — 이메일 me@example.com / 비밀번호 SuperSecret123 ``` Claude 가 호출: ```json { "method": "tools/call", "params": { "name": "signup_and_get_key", "arguments": { "email": "me@example.com", "password": "SuperSecret123", "name": "Seunghan" } } } ``` 응답: ``` ✅ 가입 완료 이메일: me@example.com MCP Key: h7Axq9XPsDTD2qr5yqtcCSaQwertyuiopASDFghjkLZXCVbnm1234567890QWERTY ⚠️ 이 키는 다시 표시되지 않습니다. ~/.claude.json 에 저장하세요. ``` ## 3단계: 키 등록 ```json { "mcpServers": { "ainote": { "type": "http", "url": "https://api.ainote.dev/api/mcp", "headers": { "Authorization": "McpKey h7Axq9XPsDTD2qr5yqtcCSaQ..." } } } } ``` Claude Code 재시작. ## 기존 계정 로그인 이미 ainote 계정이 있다면: ``` ainote 로그인 — 이메일 me@example.com / 비번 SuperSecret123 ``` → `login_and_get_key` 호출, 새 키 발급 (기존 키는 그대로 유지). ## 비밀번호 규칙 - 최소 6자 - 영문 + 숫자 포함 권장 - 특수문자는 선택 (대부분 쉘 escape 문제 회피용) ## 보안 - 가입 호출은 **HTTPS only** (TLS 1.2+) - 비번은 bcrypt(cost=12) 저장 - 키는 SHA-256 해시 + 마지막 4자만 표시 - 가입 IP/User-Agent 자동 기록 ## 자주 하는 실수 | 증상 | 원인 | |------|------| | `email already taken` | 이미 가입된 이메일 → 로그인 사용 | | `invalid email format` | `@` 없거나 도메인 누락 | | `password too short` | 6자 미만 | | `tool not found` | MCP 등록 안 됨 또는 reload 안 함 | ## 다음 - [API Key 인증 자세히](/guide/auth) - [`signup_and_get_key` API](/reference/signup) - [Claude Code 연결](/mcp/claude-code) --- # Troubleshooting ## MCP 연결 ### 도구 목록에 ainote 가 안 나옴 증상: Claude Code `/mcp` 에 `ainote` 보이지 않음. 원인 + 해결: 1. **`type` 필드 누락** (가장 흔함) ```json { "type": "http", "url": "..." } ``` `type` 빠지면 mcpServers 블록 전체가 schema 검증 실패 → 다른 MCP 도 같이 안 뜸. 2. **JSON 문법 오류** — `~/.claude.json` 을 `jq .` 로 검증 ```bash jq . ~/.claude.json > /dev/null ``` 3. **Claude Code 재시작 필요** — 설정 변경 후 reload 필수. ### 401 Unauthorized ```json { "error": { "code": -32001, "message": "Unauthorized" } } ``` 원인: - `Authorization` 헤더 누락 - 키 prefix 잘못 (`McpKey ` 접두사 + 공백 1개) - 키 만료/폐기됨 테스트: ```bash curl -X POST https://api.ainote.dev/api/mcp \ -H "Authorization: McpKey YOUR_KEY" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' ``` ### 응답 끊김 / Cold Start Render free tier 는 15분 idle 시 sleep. 첫 호출 ~5초 wait. 해결: 유료 플랜 또는 cron 으로 1분마다 ping. ## 태스크 ### 자연어 파싱이 이상해요 "내일 오후 3시" 가 다음날 오후 3시로 안 잡힐 때: - 시간대 확인 — `Asia/Seoul` 기본, 사용자 설정에서 변경 가능 - 명시적으로 ISO 8601 사용: `2026-05-08T15:00:00+09:00` ### 알림이 안 와요 체크리스트: 1. iOS/Android 앱 알림 권한 ON 2. Telegram 봇 연동 완료 (설정 → Telegram) 3. Web Push: 브라우저에서 ainote.dev 알림 허용 4. `reminder_minutes` 설정됨? (기본 알림 없음) 5. Solid Queue 정상 동작 — `bin/jobs status` (셀프호스팅) ## 메모리 / Dev Docs ### `pull_dev_docs` 가 파일 만들지 않음 원인: 등록할 때 `local_path` 안 넣었음. 확인: ``` ainote 에서 "tennis-bracket-claude.md" 의 local_path 보여줘 ``` 빈값이면: ``` local_path 를 "/Users/seunghan/tennis_bracket/CLAUDE.md" 로 업데이트해줘 ``` ### CLAUDE.md 동기화 충돌 두 기기에서 동시 편집 → 마지막 write 가 이김 (LWW). 복구: ```bash # 양쪽 버전 확인 git log -p --all -- "global/claude/global-claude-guidelines.md" # 손실된 버전 복원 git show :global/claude/global-claude-guidelines.md > /tmp/lost.md ``` ## Vault ### `vault_sync` 가 conflict 표시 Git 3-way merge conflict. 수동 해결: ```bash cd ~/ainote-vaults/ git status # conflict 파일 확인 # editor 로 conflict marker 해결 git add . git commit ainote vault_sync # 다시 시도 ``` ### Obsidian 에서 안 보임 체크: - `.obsidian/` 폴더 vault root 에 있나? - Obsidian 에서 `File → Open Vault as Folder` 로 열기 - ainote vault 의 git working tree 가 Obsidian vault root ## 동기화 (Sync) ### 새 기기에서 state.json 없음 ```bash ainote sync_pull --initial ``` `--initial` 플래그가 첫 동기화 모드 — manifest 의 모든 sync 항목을 받아옴. ### 충돌 디렉토리에 파일 쌓임 `~/.claude/ainote-sync/conflicts/` 에 파일들: ```bash # 최근 conflict ls -lt ~/.claude/ainote-sync/conflicts/ | head # 수동 해결: diff 보고 한쪽 선택 diff conflicts/path__local.md conflicts/path__remote.md ``` 자세히: [sync 충돌 해결](/sync/conflicts). ## 빌드 / 개발 ### `npm run dev` 가 안 됨 ```bash cd ~/ainote/docs-site rm -rf node_modules .vitepress/cache npm install npm run dev ``` Node 20+ 필요. ### llms.txt 빌드 실패 ```bash node scripts/build-llms.mjs ``` 직접 실행해서 오류 확인. `public/` 폴더 권한 문제일 수 있음. ## 더 안 풀리면 - GitHub Issues: - Email: support@ainote.dev - 디버그 정보 첨부: 클라이언트 (Claude Code 버전), `~/.claude.json` (키 마스킹), 에러 응답 전체 --- # ainote 란? > 한 줄: **AI 가 직접 쓰는 노트 앱.** 태스크 + 메모리 + Dev Docs 를 MCP 도구로 노출해서 Claude/Cursor/ChatGPT 가 자연어로 조작합니다. ## 현재 v1.x — 2가지 핵심 기능 ### 1. 태스크 (Tasks) "내일 오전 10시 회의 준비" 한 줄로 마감/위치/카테고리/중요도/반복 모두 자동 추출. - Solid Queue 기반 알림 (5분 단위 reminder, 마감 임박 푸시) - 18개 필터 옵션 (오늘 / 위치 / 카테고리 / 마감 범위) - MCP 도구 5개: [`create_task`](/reference/create-task), [`update_task`](/reference/update-task), [`delete_task`](/reference/delete-task), [`list_tasks`](/reference/list-tasks), [`list_categories`](/reference/list-categories) ### 2. 메모리 / Dev Docs **4가지 타입** (frontmatter 분류) 의 마크다운 메모리: - `feedback` — "이렇게 하지 마" 류 가이드 - `project` — 진행중 프로젝트 컨텍스트 - `reference` — 외부 시스템 포인터 (Slack 채널, Linear 프로젝트, Grafana URL) - `user` — 사용자 자체 정보 (역할, 선호, 도메인 지식) 추가로 **Dev Docs** — CLAUDE.md / Cursor rules / Windsurf rules / Copilot instructions 를 한 곳에서 중앙 관리. MCP 도구 7개 (`create_dev_doc`, `update_dev_doc`, `pull_dev_docs` 등). ## 향후 — 설계 완료, 구현 대기 ### Vault (Obsidian-style) git 백엔드 마크다운 저장소 — `[[wikilinks]]` 호환, 로컬 Obsidian 양방향 sync. **5개 도구 설계 완료**, 구현 대기. ### 다중 디바이스 동기화 HLC (Hybrid Logical Clock) + Star Topology + LWW. **3개 도구 + 클라이언트 알고리즘 설계 완료**, 구현 대기. 자세한 설계: [vault/overview](/vault/overview), [sync/overview](/sync/overview). ## 누가 왜 쓰는가 | 사용자 | 용도 | |--------|------| | **AI 헤비유저** | Claude 안에서 "내 노트에 X 추가" 자연어로 | | **멀티디바이스 개발자** | 17개 프로젝트 CLAUDE.md 동기화 (맥미니 ↔ 맥북) | | **Obsidian 사용자** | git-backed vault 로 마이그레이션, MCP 로 AI 접근 추가 | | **태스크 매니저** | Things/Todoist 대신 "AI 가 직접 추가하는" 할 일 | | **외부 봇 사용자** | Telegram 으로 모바일 외부에서 빠르게 메모/태스크 | ## 다른 도구와 비교 | | ainote | Notion | Obsidian | Things | |---|---|---|---|---| | AI MCP 도구 | ✅ 15개 (+8 설계) | ❌ | △ 플러그인 | ❌ | | 자연어 태스크 | ✅ | △ | ❌ | ❌ | | 다중 디바이스 sync | ✅ HLC | ✅ 클라우드 | △ Sync 유료 | ✅ iCloud | | 셀프 호스팅 | ✅ | ❌ | ✅ | ❌ | | Git backend | ✅ | ❌ | △ 플러그인 | ❌ | | 무료 티어 | ✅ | △ | ✅ | ❌ | | 가격 (유료) | $0–TBD | $10/mo | $5/mo | $50 1회 | ## 작동 방식 (30초) ``` ┌─────────────┐ MCP (JSON-RPC) ┌──────────────────┐ │ Claude Code │ ◄────────────────────────────► │ ainote API │ │ Cursor │ │ Render/Singapore │ │ ChatGPT │ create_task, sync_push, │ │ │ Telegram │ vault_clone, ... │ PostgreSQL + Git │ └─────────────┘ └──────────────────┘ ▲ │ │ 자연어 │ 같은 데이터 ▼ ▼ 사용자 iOS / Android / Web / macOS ``` 너가 "내일 오전 10시 회의 준비" 라고 Claude 에 말하면: 1. Claude 가 MCP 도구 `create_task` 호출 2. ainote 가 자연어 → `{title, due_at, important}` 파싱 3. PostgreSQL 저장 + Solid Queue 알림 스케줄 4. 모든 디바이스에 동기화 (iOS 앱 푸시 도착) ## 다음 단계 - 일단 써보자 → [**5분 Quickstart**](/guide/quickstart) - 어떻게 작동하나 → [**MCP 란?**](/mcp/overview) - 태스크 도구 자세히 → [**태스크 개요**](/tasks/overview) - 메모리 모델 자세히 → [**메모리 4가지 타입**](/memory/types) --- ## 왜 ainote 인가 - **AI 가 직접 호출** — `create_task`, `create_dev_doc` 같은 MCP 도구로 Claude 가 너의 노트를 직접 편집. - **계정 없이 시작** — `signup_and_get_key` 한 번이면 Claude 안에서 가입 → 바로 사용. - **CLAUDE.md 중앙 관리** — 17개 프로젝트의 CLAUDE.md, Cursor rules, Windsurf rules 를 ainote 한 곳에서 `pull_dev_docs` 한 번이면 새 기기 복원. - **모든 플랫폼** — iOS / Android / macOS / Web / Apple Watch / Chrome 확장 / Telegram 봇. ::: tip 현재 v1.x 에서 동작하는 것 태스크 (5 도구) + 메모리/Dev Docs (7 도구) + 온보딩 (3 도구) — **총 15개**. **Vault (5)** + **Sync (3)** 도구는 [설계 단계](https://github.com/seunghan91/ainote)이며 향후 출시 예정. ::: ## 무엇부터 읽으면 되나 | 너의 상황 | 추천 페이지 | |----------|------------| | ainote 가 뭔지 모르겠음 | [**ainote 란?**](/guide/what-is-ainote) (3분) | | 일단 Claude 에 붙여보고 싶음 | [**5분 Quickstart**](/guide/quickstart) | | Claude Code 에서 쓸 거임 | [**Claude Code 연결**](/mcp/claude-code) | | ChatGPT 에서 쓸 거임 | [**ChatGPT (SSE) 연결**](/mcp/chatgpt) | | 다중 기기 동기화 어떻게? | [**동기화 개요**](/sync/overview) | | 전체 도구 카탈로그 | [**API 레퍼런스**](/reference/) | ## 핵심 자주 묻는 질문 **Q. 계정 없이 바로 써볼 수 있나요?** — 네. MCP 등록 후 Claude 에 "ainote 가입 시켜줘 — 이메일 X / 비번 Y" 라고 하면 `signup_and_get_key` 가 호출돼서 키가 발급됩니다. **Q. Obsidian 사용자인데 마이그레이션 가능한가요?** — `vault_create` 로 vault 만든 뒤 git remote 로 기존 Obsidian vault 를 붙이면 됩니다. 마크다운이라 wikilinks `[[...]]` 그대로 호환. **Q. 데이터 어디 저장되나요?** — 호스팅: Render(Singapore) PostgreSQL + git 저장소. 셀프호스팅: 자체 인스턴스 가능. [데이터 내보내기](/guide/data-export) 언제든 가능. **Q. AI 가 모든 노트 다 보나요?** — MCP 도구 호출할 때만. 호출 단위는 너가 직접 승인 가능 (Claude Code 에서). 권한 모델: API key 단위로 read/write 분리 가능. [→ 전체 FAQ 보기](/guide/faq) ## AI/LLM 에게 통째로 던지기 [llms.txt 표준](https://llmstxt.org/) 으로 전체 문서를 LLM 친화 형식으로 제공합니다. - [📥 `/llms.txt`](/llms.txt) — 페이지 색인 + 1줄 요약 - [📥 `/llms-full.txt`](/llms-full.txt) — 모든 본문 한 파일로 합침 ChatGPT/Claude 에 붙여넣고 "ainote 붙여줘" 한마디면 끝. ---

ainote.dev — 개인 노트는 너의 것.

--- # ChatGPT 연결 (SSE) ChatGPT (Plus/Pro/Team) 의 MCP connector 로 ainote 사용. ::: warning ChatGPT MCP 는 SSE 전용 ChatGPT 의 connector 는 Server-Sent Events 만 지원. ainote hosted HTTP 직접 연결 불가 → 로컬 SSE 브리지 사용. ::: ## 1. 브리지 설치 ```bash npm install -g @ainote/mcp ``` 설치하면 두 명령이 생김: - `ainote-mcp` — stdio (Claude Desktop 용) - `ainote-mcp-http` — SSE 브리지 (ChatGPT 용) ## 2. 브리지 실행 ```bash export AINOTE_API_KEY="h7Axq9XPsDTD2qr5yqtcCSaQ..." export AINOTE_MCP_HTTP_PORT=8765 # 기본 3030 — 원하는 포트로 override ainote-mcp-http ``` 출력: ``` ainote MCP SSE bridge listening on http://localhost:8765 SSE endpoint: http://localhost:8765/sse ``` ::: tip 포트 설정 `ainote-mcp-http` 는 `--port` 같은 CLI flag 를 받지 않습니다. **`AINOTE_MCP_HTTP_PORT` 환경변수**로 설정. 미설정 시 기본 **3030** 포트. ::: ::: tip 백그라운드로 항상 켜두기 brew services 또는 launchd plist 로 부팅 시 자동 실행 가능. [셀프호스팅 가이드](/guide/troubleshooting) 참고. ::: ## 3. ChatGPT 에 등록 ChatGPT 설정: 1. → "Connectors" 2. "Add connector" → "MCP server" 3. 입력: - **Name**: ainote - **URL**: `http://localhost:8765/sse` - **Auth**: (브리지가 env 로 처리, 비워둬도 됨) 4. "Connect" ::: warning ChatGPT 는 localhost 접근 가능? 브라우저에서 직접 접근 OK. 회사 방화벽 등이 막으면: - `localhost` 대신 `127.0.0.1` 사용 - HTTPS 필요 시 mkcert 로 self-signed cert + `--cert` `--key` 옵션 ::: ## 4. 사용 ChatGPT 에서 도구 사용 가능 표시 → 자연어로 호출: ``` ainote 에 "ChatGPT 테스트" 태스크 추가해줘 ``` ChatGPT 가 호출 전 승인 화면 띄움 → "Allow" 클릭. ## OAuth Bearer 모드 (선택) 기본은 env var. 브리지에서 OAuth 토큰 받기: ```bash npx ainote-mcp-http --port 8765 --oauth ``` 이러면 ChatGPT connector 등록 시: - **Auth Type**: OAuth - **Authorization URL**: `http://localhost:8765/oauth/authorize` - **Token URL**: `http://localhost:8765/oauth/token` ChatGPT 가 oauth flow 통해 토큰 받고 자동 갱신. ## 보안 권장 - 브리지는 `127.0.0.1` 만 listen (외부 노출 X) - 환경변수 키는 `~/.zshrc` 보다 `~/.zshenv` (gui app 도 읽음) - Tailscale 통해 다른 기기 ChatGPT 에서도 접근 가능 ## 한계 ChatGPT MCP 는 아직 **read-heavy** 작업에 적합: - ✅ list_tasks, get_dev_doc, list_dev_docs (읽기) - △ create_task, update_dev_doc (쓰기 — 매번 승인 화면) - ❌ 자동 워크플로우 (사용자 클릭 필요) 쓰기/자동화 많이 한다면 [Claude Code](/mcp/claude-code) 가 더 편함. ## 다음 - [3가지 transport 비교](/mcp/transports) - [Claude Code 연결](/mcp/claude-code) - [Telegram 연결](/mcp/telegram) (모바일에서 자동화) --- # Claude Code 연결 Claude Code (CLI) 에서 ainote 를 MCP 서버로 등록. ## 권장: hosted HTTP transport 가장 간단. 별도 설치 없음. `~/.claude.json` 편집: ```json { "mcpServers": { "ainote": { "type": "http", "url": "https://api.ainote.dev/api/mcp", "headers": { "Authorization": "McpKey YOUR_KEY_HERE" } } } } ``` 저장 후 Claude Code 재시작 (또는 `/mcp` 슬래시커맨드로 reload). ::: danger `type` 필드 누락 사고 주의 원격 MCP 서버는 반드시 `"type": "http"` (또는 `"sse"`) 명시. 빠지면 스키마 검증 실패로 `mcpServers` 블록 전체 미로드 — ainote 뿐 아니라 hyperbrowser, perplexity, render 등 **다른 모든 MCP 도 같이 안 됩니다**. 2026-04-15 이걸로 10개 동시 미로드 사고 발생. ::: ## 키 없이 시작 (가입 우선) 처음이라 키가 없으면 `headers` 빼고 등록: ```json { "mcpServers": { "ainote": { "type": "http", "url": "https://api.ainote.dev/api/mcp" } } } ``` Claude 에서: ``` ainote 가입 시켜줘 — 이메일 X / 비번 Y ``` 키 받은 뒤 `headers` 추가하고 재시작. ## 대안: stdio (로컬 npm 패키지) MCP 트래픽을 외부로 안 보내고 싶을 때: ```bash npm install -g @ainote/mcp ``` ```json { "mcpServers": { "ainote": { "command": "npx", "args": ["-y", "@ainote/mcp"], "env": { "AINOTE_API_KEY": "YOUR_KEY" } } } } ``` 이 경우에도 결국 `api.ainote.dev` 로 HTTPS 호출은 갑니다 — 차이는 stdio process 가 로컬에서 한 번 wrap 한다는 것뿐. ## 검증 Claude Code 안에서: ``` /mcp ``` `ainote` 가 connected 로 나오면 성공. 도구 목록 17개 보임. 테스트: ``` ainote 에 "테스트 태스크" 추가해줘 ``` ## Project-level 등록 (한 프로젝트만) `~/.claude.json` 대신 프로젝트 루트의 `.mcp.json`: ```json { "mcpServers": { "ainote": { "type": "http", "url": "https://api.ainote.dev/api/mcp", "headers": { "Authorization": "McpKey YOUR_KEY" } } } } ``` ⚠️ `.mcp.json` 은 git 에 커밋하지 마세요 — 키가 들어있음. ## Troubleshooting | 증상 | 원인 | 해결 | |------|------|------| | 도구 목록에 안 나옴 | type 필드 누락 | `"type": "http"` 추가 | | 401 Unauthorized | 키 누락/오타 | `Authorization` 헤더 확인 | | 다른 MCP 도 다 안 됨 | json schema 깨짐 | `~/.claude.json` JSON 검증 | | 시그널 끊김 | Render cold start | 재호출 시 정상 (5초 wait) | ## 다음 - 다른 클라이언트: [Claude Desktop](/mcp/claude-desktop) · [ChatGPT](/mcp/chatgpt) · [Cursor](/mcp/cursor) - [3가지 transport 비교](/mcp/transports) - [전체 도구 목록](/reference/) --- # Claude Desktop 연결 Claude Desktop (macOS / Windows 데스크톱 앱) 에서 ainote 사용. ## 권장: stdio 패키지 Claude Desktop 은 stdio transport 만 안정적으로 지원. ### 1. 패키지 설치 ```bash npm install -g @ainote/mcp ``` Node 18+ 필요. 미설치 시: ```bash brew install node # macOS # 또는 https://nodejs.org ``` ### 2. 설정 파일 편집 위치: - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` ```json { "mcpServers": { "ainote": { "command": "npx", "args": ["-y", "@ainote/mcp"], "env": { "AINOTE_API_KEY": "h7Axq9XPsDTD2qr5yqtcCSaQ..." } } } } ``` ### 3. Claude Desktop 재시작 완전 종료 (`Cmd+Q`) 후 다시 실행. ### 4. 검증 대화창 좌하단 🔌 아이콘 → "ainote" 가 connected 로 표시되어야 함. 테스트: ``` ainote 에 "데스크톱 테스트" 태스크 추가해줘 ``` ## 키 없이 시작 `env` 빼고 등록: ```json { "mcpServers": { "ainote": { "command": "npx", "args": ["-y", "@ainote/mcp"] } } } ``` 대화에서: ``` ainote 가입 시켜줘 — me@example.com / password123 ``` 키 받으면 `env` 추가 후 재시작. ## SSE 모드 (선택) Claude Desktop 일부 버전은 SSE 도 지원. ChatGPT 처럼 브리지 사용: ```bash npx ainote-mcp-http --port 8765 ``` 설정: ```json { "mcpServers": { "ainote": { "transport": { "type": "sse", "url": "http://localhost:8765/sse" } } } } ``` (브리지 프로세스는 항상 켜져 있어야 함) ## Troubleshooting ### "ainote" 가 안 뜸 체크: - JSON 문법: `cat ~/Library/Application\ Support/Claude/claude_desktop_config.json | jq .` - Node 설치: `which node` - 패키지 설치: `npm list -g @ainote/mcp` ### "command not found: npx" ```bash which npx # 없으면 Node 재설치 ``` 설정에서 `"command": "npx"` 대신 절대경로 사용 가능: ```json "command": "/opt/homebrew/bin/npx" ``` ### 401 Unauthorized env 의 `AINOTE_API_KEY` 확인. 키 prefix 안 붙음 — 그냥 64자 키만. 자세히: [Troubleshooting](/guide/troubleshooting). ## 다음 - [Claude Code 연결](/mcp/claude-code) - [3가지 transport 비교](/mcp/transports) - [전체 도구 목록](/reference/) --- # Cursor / Windsurf 연결 Cursor 와 Windsurf 는 **hosted HTTP** transport 를 1급 지원. Claude Code 와 거의 동일. ## Cursor ### 1. 설정 파일 `~/.cursor/mcp.json` (없으면 생성): ```json { "mcpServers": { "ainote": { "type": "http", "url": "https://api.ainote.dev/api/mcp", "headers": { "Authorization": "McpKey YOUR_KEY" } } } } ``` ### 2. Cursor 재시작 `Cmd+Shift+P` → "Cursor: Reload Window". ### 3. 검증 `Cmd+L` (Composer/Chat 패널) → "@" 입력 → ainote 도구 17개 보여야 함. 테스트: ``` @ainote create_task "Cursor 테스트" ``` ## Windsurf ### 1. 설정 파일 `~/.codeium/windsurf/mcp_config.json`: ```json { "mcpServers": { "ainote": { "type": "http", "url": "https://api.ainote.dev/api/mcp", "headers": { "Authorization": "McpKey YOUR_KEY" } } } } ``` ### 2. Windsurf 재시작 ### 3. Cascade 패널 → MCP 도구 사용 ## Project-level (한 프로젝트만) 두 IDE 모두 프로젝트 루트의 `.mcp.json` 또는 `.cursor/mcp.json` 지원: ```json { "mcpServers": { "ainote": { "type": "http", "url": "https://api.ainote.dev/api/mcp", "headers": { "Authorization": "McpKey TEAM_KEY" } } } } ``` ⚠️ git 에 커밋 X — `.gitignore` 에 추가: ``` .cursor/mcp.json .mcp.json ``` ## Cursor Rules / Windsurf Rules 연동 ainote 의 [Dev Docs](/memory/cursor-windsurf) 가 두 IDE 의 rules 파일을 중앙 관리. ### Cursor `.cursorrules` 동기화 ainote 에 등록: ``` .cursorrules 를 ainote 에 등록해줘 - title: "{project}-cursorrules" - category: "cursor" - local_path: "/Users/seunghan/{project}/.cursorrules" ``` 다른 기기에서: ``` ainote 에서 cursor rules 다 가져와 ``` → `pull_dev_docs --category cursor` → 모든 `.cursorrules` 복원. ### Windsurf `.windsurfrules` 도 동일 ``` category: "windsurf" local_path: ".windsurfrules" ``` ## 도구 호출 자동 승인 Cursor: `Cmd+,` → "MCP" → "Auto-approve tool calls" → ainote 의 read-only 도구만 자동 승인 권장: - ✅ `list_*`, `get_*`, `pull_*` 자동 - ⚠️ `create_*`, `update_*`, `delete_*` 수동 승인 ## Troubleshooting | 증상 | 해결 | |------|------| | 도구 안 뜸 | `type: "http"` 누락 확인 | | Cursor 재시작 후도 안 됨 | `~/.cursor/logs/` 에서 MCP 에러 확인 | | Windsurf cascade 에러 | `~/.codeium/windsurf/logs/` 확인 | ## 다음 - [Claude Code 연결](/mcp/claude-code) (거의 동일 패턴) - [Cursor / Windsurf rules 통합 관리](/memory/cursor-windsurf) - [3가지 transport 비교](/mcp/transports) --- # MCP 란? (30초 정리) **MCP (Model Context Protocol)** = Anthropic 이 만든 "AI ↔ 도구" 표준 프로토콜. ## 한 줄 비유 REST API 가 "프론트엔드 ↔ 백엔드" 표준이듯, **MCP 는 "AI 모델 ↔ 외부 도구" 표준**. ## ainote 입장에서 ainote 는 **MCP 서버** 입니다. 17개 도구를 노출: ``` create_task, update_task, delete_task, list_tasks, list_categories, create_dev_doc, update_dev_doc, delete_dev_doc, get_dev_doc, list_dev_docs, list_dev_categories, pull_dev_docs, vault_create, vault_clone, vault_sync, vault_list, vault_connect_status, sync_push, sync_pull, sync_list, signup_and_get_key, login_and_get_key, get_setup_guide ``` MCP 클라이언트 (Claude Desktop, Claude Code, ChatGPT, Cursor, Telegram bot) 가 이 도구들을 호출 → ainote 가 실행 → 결과 반환. ## 작동 흐름 ``` [사용자] │ "내일 회의 추가해줘" ▼ [Claude / GPT] │ 도구 결정: create_task │ 파라미터 파싱: {title, due_at, ...} ▼ [MCP 클라이언트] │ JSON-RPC 2.0 over HTTP/stdio/SSE ▼ [ainote MCP 서버] │ Rails 8 API │ PostgreSQL insert │ Solid Queue 알림 스케줄 ▼ [응답] │ {"status": "ok", "task_id": 1234} ▼ [사용자에게 결과] "내일 10시 회의 추가했습니다 ✅" ``` ## 3가지 transport ainote 는 같은 도구 카탈로그를 **3가지 방식**으로 제공: | transport | 누가 쓰나 | 패키지 | |----------|---------|--------| | **stdio** | Claude Desktop, 보안 민감 | `npm i -g @ainote/mcp` | | **SSE** | ChatGPT, 일부 클라이언트 | `ainote-mcp-http` | | **hosted HTTP** | Claude Code, Cursor, 자동화 | `https://api.ainote.dev/api/mcp` | 자세히: [3가지 transport 비교](/mcp/transports). ## 다른 도구와 비교 | | ainote | filesystem | github | slack | |---|---|---|---|---| | 제공 | tasks/memory/vault | 파일 R/W | repo/PR | 채널/메시지 | | 인증 | API key | 로컬 | OAuth | OAuth | | transport | 3개 | stdio | stdio | http | | 로컬 데이터 | △ | ✅ | ❌ | ❌ | ## 더 알아보기 - 공식 스펙: - ainote 클라이언트별 가이드: - [Claude Desktop](/mcp/claude-desktop) - [Claude Code](/mcp/claude-code) - [ChatGPT (SSE)](/mcp/chatgpt) - [Cursor / Windsurf](/mcp/cursor) - [Telegram (Clawdbot)](/mcp/telegram) - [전체 도구 카탈로그](/reference/) --- # Telegram 연결 (Clawdbot) ainote 의 Telegram 봇 — 모바일에서 자연어로 태스크/메모 외부에서 추가. ## Clawdbot 이란 `@clawdbot` (Telegram) — ainote 의 Telegram interface. 내부적으로 [mcporter](https://github.com/clawdorg/mcporter) 가 MCP 도구들을 봇 명령으로 노출. ## 연동 절차 ### 1. 봇 시작 Telegram 에서 [@clawdbot](https://t.me/clawdbot) 검색 → "Start". 봇이 응답: ``` 👋 ainote Clawdbot 입니다. 계정 연동: /link ``` ### 2. 계정 연동 ``` /link ``` 봇이 응답: ``` 연동 코드: AB12-CD34-EF56 ainote.dev/settings/telegram 에서 입력하세요. 5분 유효. ``` ### 3. 코드 입력 → 코드 입력 → "연동" 클릭. 봇이 확인 메시지: ``` ✅ me@example.com 계정과 연동 완료 ``` ## 명령어 ### 태스크 ``` /task 내일 오전 10시 회의 준비 /today # 오늘 할 일 /week # 이번주 마감 /done #1234 # 완료 처리 /del #1234 # 삭제 ``` ### 메모리 ``` /memo 우리 팀은 PR review 24시간 내 SLA /notes feedback # feedback 타입 메모리만 /find "Stripe naming" # 검색 ``` ### 자유 질문 (AI 모드) ``` /ai 다음주 마감 중요한 거 알려줘 ``` → 내부적으로 Anthropic Claude API 호출 → `list_tasks` MCP → 응답. ## Inline 모드 다른 채팅에서 `@clawdbot` 입력 → 검색 → 결과 share: ``` @clawdbot 회의 ``` → "회의" 검색 결과를 친구 채팅에 공유 (본인만 보임). ## 알림 채널 ainote 의 태스크 알림이 Telegram 으로: 설정 → Telegram → "알림 받기" ON → 마감 임박 시 봇이 DM: ``` ⏰ 30분 후 마감 "강남역 미팅" — 오늘 10:00 [완료] [+30분 미루기] ``` 버튼 클릭으로 즉시 처리. ## 그룹 채팅 봇을 그룹에 초대: ``` /start@clawdbot /group_link # 이 그룹 → ainote 워크스페이스 연동 ``` → 그룹원 모두 같은 ainote workspace 접근 (계획됨, v0.5). ## 보안 - 연동 코드: 5분 단명, 1회 사용 - 봇 → ainote 호출은 user-scoped MCP key 사용 - DM 메시지 보관 안 함 (호출 후 즉시 폐기) - 그룹 모드: 메시지 본문 ainote 에 저장 안 됨 (명시적 `/memo` 만) ## Troubleshooting | 증상 | 해결 | |------|------| | `/link` 응답 없음 | 봇 차단 확인 → unblock | | 코드 만료 | 5분 지남 → `/link` 재실행 | | 알림 안 옴 | 설정에서 Telegram 알림 ON 확인 | | `/ai` 응답 느림 | Anthropic API 호출 — 5~10초 정상 | ## 한계 - 음성 메시지: 미지원 (계획됨, Whisper STT) - 파일 첨부: 미지원 - 인라인 키보드 일부 액션만 (전체 도구 노출 X) 전체 17개 도구 쓰려면 Claude Code/Desktop 추천. ## 다음 - [태스크 개요](/tasks/overview) - [메모리 / Dev Docs](/memory/overview) - [3가지 transport 비교](/mcp/transports) --- # 3가지 transport 비교 ainote 는 같은 17개 도구 카탈로그를 **3가지 transport** 로 제공. ## 한눈에 보기 | | stdio | SSE | hosted HTTP | |---|---|---|---| | **누가 쓰나** | Claude Desktop | ChatGPT | Claude Code, Cursor | | **설치** | `npm i -g @ainote/mcp` | `npx ainote-mcp-http` | 없음 | | **로컬 프로세스** | ✅ | ✅ (브리지) | ❌ | | **인증** | env var | env var | HTTP 헤더 | | **방화벽 친화** | ✅ outbound only | ✅ | ✅ | | **Cold start** | 즉시 | 즉시 | ~5초 (Render free) | | **AI 가 트래픽 검증** | 어려움 | SSE 로그 | curl 가능 | | **권장 시나리오** | 보안 민감 | ChatGPT 사용자 | 대부분 | ## 1. stdio — 로컬 wrap ``` [Claude Desktop] ──stdin/stdout──► [@ainote/mcp 프로세스] ──HTTPS──► [api.ainote.dev] ``` 설치: ```bash npm install -g @ainote/mcp ``` 설정 (`~/Library/Application Support/Claude/claude_desktop_config.json`): ```json { "mcpServers": { "ainote": { "command": "npx", "args": ["-y", "@ainote/mcp"], "env": { "AINOTE_API_KEY": "h7Axq9XPsDTD2qr5yqtcCSaQ..." } } } } ``` 특징: - Claude Desktop 만 stdio 지원 (구버전 호환) - 프로세스 별도 spawn → 살짝 느림 (~50ms overhead) - 외부에 보내는 트래픽이 한 단계 wrap 됨 (디버깅 어려움) ## 2. SSE — ChatGPT 브리지 ``` [ChatGPT] ──SSE──► [ainote-mcp-http 로컬 브리지] ──HTTPS──► [api.ainote.dev] ``` ChatGPT 의 MCP connector 가 SSE 만 지원. 로컬에서 브리지 실행: ```bash npx ainote-mcp-http --port 8765 ``` ChatGPT 설정: ``` URL: http://localhost:8765/sse Auth: Bearer h7Axq9XPsDTD2qr5yqtcCSaQ... ``` 특징: - 브라우저에서 ChatGPT 쓸 때만 작동 (브리지 켜둬야) - (선택) OAuth bearer 토큰 지원 - ChatGPT 가 호출 단위로 승인 화면 띄움 자세히: [ChatGPT 연결](/mcp/chatgpt). ## 3. hosted HTTP — 가장 간단 ``` [Claude Code / Cursor] ──HTTPS──► [api.ainote.dev/api/mcp] ``` 설정 (`~/.claude.json`): ```json { "mcpServers": { "ainote": { "type": "http", "url": "https://api.ainote.dev/api/mcp", "headers": { "Authorization": "McpKey YOUR_KEY" } } } } ``` 특징: - ✅ 별도 프로세스 X - ✅ 가장 빠른 셋업 - ✅ curl 로 직접 테스트 가능 - ⚠️ Render free 인 경우 cold start 5초 이게 **대부분의 사용자에게 권장**. ## JSON-RPC 형식 (3개 모두 동일) ```json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "create_task", "arguments": { "title": "회의 준비", "due_at": "2026-05-08T10:00:00+09:00" } } } ``` 자세히: [JSON-RPC 호출 형식](/reference/json-rpc). ## 어떤 걸 골라야 하나 ``` ├─ Claude Code 사용? │ → hosted HTTP │ ├─ ChatGPT 사용? │ → SSE 브리지 │ ├─ Claude Desktop 만? │ → stdio │ ├─ Cursor / Windsurf? │ → hosted HTTP │ └─ 보안 민감 (트래픽 검증 필수)? → stdio (로컬에서 한 번 wrap) ``` ## 다음 - [Claude Desktop](/mcp/claude-desktop) - [Claude Code](/mcp/claude-code) - [ChatGPT (SSE)](/mcp/chatgpt) - [Cursor / Windsurf](/mcp/cursor) - [Telegram (Clawdbot)](/mcp/telegram) --- # 카테고리 / 폴더 구조 Dev Docs 의 `category` 는 ainote vault 의 디렉토리 결정. ## 표준 카테고리 | `category` | 디렉토리 | 용도 | |--------------|---------|------| | `claude` | `dev/claude/` | CLAUDE.md (Claude Code/Desktop) | | `cursor` | `dev/cursor/` | `.cursorrules`, `.cursor/rules/*.md` | | `windsurf` | `dev/windsurf/` | `.windsurfrules` | | `copilot` | `dev/copilot/` | `.github/copilot-instructions.md` | | `docs` | `dev/docs/` | README, ARCHITECTURE.md, 기타 | | `memory` | `memory/` | 토픽 메모리 (4 type) | ## 명명 규칙 ``` {project_name}-{purpose}.md ``` | 예시 | 의미 | |------|------| | `global-claude-guidelines.md` | 전역 (`~/CLAUDE.md`) | | `launchcrew-claude.md` | launchcrew 프로젝트 CLAUDE.md | | `tennis-bracket-cursor.md` | tennis_bracket .cursorrules | | `global-memory.md` | 전역 메모리 인덱스 | | `launchcrew-firebase.md` | launchcrew Firebase 메모 | | `personas-enhanced-activation.md` | SuperClaude PERSONAS.md | ## snake_case → kebab-case 프로젝트 디렉토리가 snake 인 경우 kebab 변환: ``` ~/tennis_bracket → tennis-bracket-claude.md ~/krx_listing → krx-listing-claude.md ~/krx_ai → krx-ai-claude.md ``` 이유: URL/파일명 가독성 + 일관성. ## 사용자 정의 카테고리 표준 외 카테고리도 가능: ```json { "title": "my-custom-rule.md", "category": "custom-rules", "local_path": "..." } ``` → vault 에 `dev/custom-rules/my-custom-rule.md` 자동 생성. `list_dev_categories` 가 동적으로 모든 사용 중인 카테고리 반환. ## 폴더 구조 권장 ### 옵션 A: 단순 (소수 프로젝트) ``` ainote vault ├── dev/ │ ├── claude/ │ └── cursor/ └── memory/ ``` ### 옵션 B: 프로젝트 우선 (다수 프로젝트, 17개+) ``` ainote vault ├── projects/ │ ├── launchcrew/ │ │ ├── CLAUDE.md │ │ ├── .cursorrules │ │ └── memory/ │ ├── tennis-bracket/ │ └── ... └── global/ ├── CLAUDE.md └── memory/ ``` → 단점: ainote 자동 카테고리화와 안 맞음, 수동 관리 필요. → 장점: 프로젝트별 한 폴더에서 모두 보임. [ainote-sync-redesign](https://github.com/seunghan91/ainote/blob/main/docs/sync-redesign.md) 가 후자 권장 (Phase 2 마이그레이션). ## 검색 ```bash # 카테고리별 ainote list_dev_docs '{"category":"claude"}' # 프로젝트별 (검색) ainote list_dev_docs '{"search":"launchcrew"}' # 카테고리 목록 ainote list_dev_categories '{}' ``` ## 다음 - [`list_dev_categories` API](/reference/list-dev-categories) - [메모리 4가지 타입](/memory/types) - [폴더 구조 권장 (sync 관점)](/sync/folder-structure) --- # CLAUDE.md 통합 관리 17개+ 프로젝트의 `CLAUDE.md` 를 ainote 한 곳에서. ## 문제 ``` ~/CLAUDE.md ← 전역 설정 ~/launchcrew/CLAUDE.md ← 프로젝트별 ~/tennis_bracket/CLAUDE.md ~/keeps/CLAUDE.md ... (17개 프로젝트) ``` 여러 노트북에서 작업 → 어디서 수정했는지 모름 → drift 발생. ## 해결 ainote 가 source of truth. ``` ainote vault └── dev/claude/ ├── global-claude-guidelines.md → ~/CLAUDE.md ├── launchcrew-claude.md → ~/launchcrew/CLAUDE.md ├── tennis-bracket-claude.md → ~/tennis_bracket/CLAUDE.md └── ... ``` 각 문서에 `local_path` 메타데이터 → `pull_dev_docs` 가 그 경로로 복원. ## 1회 등록 각 CLAUDE.md 마다: ``` ainote 에 ~/launchcrew/CLAUDE.md 등록해줘 - title: launchcrew-claude.md - category: claude-md - local_path: /Users/seunghan/launchcrew/CLAUDE.md ``` Claude 가 `create_dev_doc` 호출: ```json { "title": "launchcrew-claude.md", "category": "claude", "local_path": "/Users/seunghan/launchcrew/CLAUDE.md", "content": "" } ``` ## 일괄 등록 스크립트 ```bash #!/bin/bash # ~/scripts/ainote-claude-md-init.sh ainote-push() { local file="$1" local title="$2" local content content=$(python3 -c "import sys,json; print(json.dumps(open('$file').read()))") local local_path local_path=$(realpath "$file") ainote create_dev_doc "{ \"title\":\"$title\", \"content\":$content, \"subcategory\":\"claude-md\", \"local_path\":\"$local_path\" }" } # 전역 ainote-push ~/CLAUDE.md global-claude-guidelines.md # 모든 프로젝트 for proj in ~/launchcrew ~/tennis_bracket ~/keeps ~/triphelper ~/realpick ~/talkk; do if [ -f "$proj/CLAUDE.md" ]; then name=$(basename "$proj" | tr '_' '-') ainote-push "$proj/CLAUDE.md" "${name}-claude.md" fi done echo "✅ CLAUDE.md 일괄 등록 완료" ``` ## 수정 후 push CLAUDE.md 편집 후: ```bash ainote-push ~/launchcrew/CLAUDE.md launchcrew-claude.md # 이미 존재 → update_dev_doc (replace mode) ``` 또는 Claude 에서: ``` launchcrew CLAUDE.md 수정한 거 ainote 에 동기화해줘 ``` ## 새 기기 일괄 복원 ```bash ainote pull_dev_docs '{"category":"claude"}' ``` 응답: ``` ✓ /Users/seunghan/CLAUDE.md (created) ✓ /Users/seunghan/launchcrew/CLAUDE.md (updated) ✓ /Users/seunghan/tennis_bracket/CLAUDE.md (updated) ... (17 files) ``` ## 충돌 처리 두 기기에서 동시 편집 → 마지막 push 가 이김 (LWW). 복구: ```bash # git history 에서 손실된 버전 찾기 cd ~/.ainote-vault git log -p dev/claude/launchcrew-claude.md # 손실 버전 export git show :dev/claude/launchcrew-claude.md > /tmp/lost.md ``` 자세히: [sync 충돌](/sync/conflicts). ## 명명 컨벤션 ``` {project_name}-claude.md ``` - `project_name`: snake_case → kebab-case (`tennis_bracket` → `tennis-bracket`) - 전역: `global-claude-guidelines.md` - SuperClaude 설정: `personas-enhanced-activation.md` 등 자세히: [네임 규칙](/memory/categories). ## 다음 - [Cursor / Windsurf rules 같은 패턴](/memory/cursor-windsurf) - [4가지 메모리 타입](/memory/types) - [`pull_dev_docs` API](/reference/pull-dev-docs) --- # Cursor / Windsurf 룰 `.cursorrules`, `.windsurfrules`, `.github/copilot-instructions.md` 도 [CLAUDE.md 와 같은 패턴](/memory/claude-md) 으로 ainote 에 통합 관리. ## 카테고리 매핑 | 도구 | 파일 | ainote subcategory | |------|------|-------------------| | Claude Code/Desktop | `CLAUDE.md` | `claude` | | Cursor | `.cursorrules` | `cursor` | | Windsurf | `.windsurfrules` | `windsurf` | | GitHub Copilot | `.github/copilot-instructions.md` | `copilot` | | 일반 docs | `README.md`, `ARCHITECTURE.md` | `docs` | ## 등록 ### Cursor ``` ainote 에 ~/launchcrew/.cursorrules 등록해줘 - title: launchcrew-cursorrules - category: cursor - local_path: /Users/seunghan/launchcrew/.cursorrules ``` ### Windsurf 같은 패턴, `category: windsurf`, `local_path: ".../.windsurfrules"`. ### Copilot ``` title: launchcrew-copilot-instructions.md category: copilot local_path: /Users/seunghan/launchcrew/.github/copilot-instructions.md ``` ## 자동 디렉토리 ainote vault 에 자동 정리: ``` ainote vault └── dev/ ├── claude/ (CLAUDE.md 들) ├── cursor/ (.cursorrules 들) ├── windsurf/ (.windsurfrules) ├── copilot/ (copilot-instructions.md) └── docs/ (기타) ``` ## 카테고리별 일괄 pull 새 기기: ```bash # Cursor 룰만 ainote pull_dev_docs '{"category":"cursor"}' # Windsurf 만 ainote pull_dev_docs '{"category":"windsurf"}' # 전체 dev rules (claude + cursor + windsurf + copilot) ainote pull_dev_docs '{"category":"claude","cursor","windsurf","copilot"]}' # 모든 dev_docs ainote pull_dev_docs '{}' ``` ## 다중 도구 사용 시 권장 패턴 같은 프로젝트가 Claude + Cursor 둘 다 쓰면: **옵션 A: 같은 내용 양쪽**: - `CLAUDE.md` 와 `.cursorrules` 가 거의 동일 - ainote 에 별도 등록 (양쪽 sync) **옵션 B: 공통 내용 + symlink**: ```bash ln -s CLAUDE.md .cursorrules ``` → 한 파일 등록, 다른 도구도 자동 사용. **옵션 C: include 패턴** (Cursor/Windsurf 미지원): - Cursor 는 `@file` 으로 포함 가능 (Cursor 0.40+) - 그러면 `.cursorrules` 에 `@CLAUDE.md` 한 줄 ## Cursor Rules 스코프 Cursor 0.50+ 는 `.cursor/rules/*.md` 디렉토리 지원 — 여러 룰 파일. ``` ~/launchcrew/.cursor/rules/ ├── general.md ├── api-conventions.md └── testing.md ``` 각각 ainote 에: ``` title: launchcrew-cursor-rules-general.md category: cursor local_path: /Users/seunghan/launchcrew/.cursor/rules/general.md ``` ## 다음 - [CLAUDE.md 통합 관리](/memory/claude-md) - [메모리 카테고리 / 네이밍](/memory/categories) - [`pull_dev_docs` API](/reference/pull-dev-docs) --- # 메모리 / Dev Docs 개요 ainote 의 **두 번째 1급 시민** — Claude/Cursor/Windsurf 가 공유하는 마크다운 메모리. ## 두 가지 개념 ### 1. 토픽 메모리 (자유 형식) Claude 가 대화 중 자동으로 저장하는 작은 사실들. ainote 는 이를 [4가지 type](/memory/types) 으로 마크다운 frontmatter 에서 분류 — 단, **MCP 서버 자체가 type 필드를 색인하지는 않음**. 분류는 사용자/Claude 가 frontmatter 로 관리. | 타입 | 무엇 | 예시 | |------|------|------| | **user** | 사용자 정체성/선호 | "한국어로 응답, 파이썬 10년차" | | **feedback** | 작업 가이드 | "테스트 mock 금지 — 작년 prod 사고" | | **project** | 진행중 컨텍스트 | "2026-03-05 mobile freeze" | | **reference** | 외부 시스템 포인터 | "Linear 'INGEST' 프로젝트 = pipeline 버그" | ### 2. Dev Docs (구조화 문서) 프로젝트별 룰/가이드를 중앙 관리: - `claude` — `CLAUDE.md` - `cursor` — `.cursorrules`, `.cursor/rules/*.md` - `windsurf` — `.windsurfrules` - `copilot` — `.github/copilot-instructions.md` - `docs` — 일반 문서 서버는 모두 `category` 필드 하나로 구분 (디렉토리 분리). ## 왜 이걸 ainote 에 두나 **문제**: 17개 프로젝트의 CLAUDE.md 가 노트북마다 따로 놂. 맥미니에서 수정 → 맥북엔 반영 안 됨. **해결**: ainote 가 source of truth. - `create_dev_doc` — 새로 등록 (한 번만) - `update_dev_doc` — 변경 시 push - `pull_dev_docs` — 새 기기에서 한 방에 복원 (`local_path` 기준) ## MCP 도구 7개 | 도구 | 용도 | |------|------| | [`create_dev_doc`](/reference/create-dev-doc) | 새 문서 등록 | | [`update_dev_doc`](/reference/update-dev-doc) | 수정 (replace/append/prepend) | | [`delete_dev_doc`](/reference/delete-dev-doc) | Soft delete | | [`get_dev_doc`](/reference/get-dev-doc) | 단일 문서 조회 | | [`list_dev_docs`](/reference/list-dev-docs) | category + search 필터 | | [`list_dev_categories`](/reference/list-dev-categories) | 카테고리 목록 | | [`pull_dev_docs`](/reference/pull-dev-docs) | local_path 로 일괄 복원 | ## 자동 디렉토리 분류 `category` 자동으로 `dev/` 아래 정리: ``` ainote 클라우드 ├── dev/ │ ├── claude/ │ │ ├── global-claude-guidelines.md → ~/CLAUDE.md │ │ ├── tennis-bracket-claude.md → ~/tennis_bracket/CLAUDE.md │ │ └── launchcrew-claude.md → ~/launchcrew/CLAUDE.md │ ├── cursor/ │ ├── windsurf/ │ └── docs/ └── memory/ ├── global-MEMORY.md └── launchcrew-MEMORY.md ``` ## `local_path` — 복원의 핵심 문서 등록 시 `local_path` 항상 명시: ```json { "title": "tennis-bracket-claude.md", "category": "claude", "local_path": "/Users/seunghan/tennis_bracket/CLAUDE.md", "content": "..." } ``` `pull_dev_docs` 가 이 경로 기준으로 파일을 만듭니다. 없으면 복원 불가능. 크로스 플랫폼 경로 매핑 자동: macOS `~/...` ↔ WSL `~/...` ↔ Linux `~/...`. ## 메모리 저장 베스트 프랙티스 ### ✅ 저장할 것 - 사용자 가르쳐 준 사실 ("나는 X 회사 다님") - 코드에서 derive 안 되는 결정 근거 ("왜 mongo 대신 postgres") - 외부 시스템 포인터 (Slack 채널, Linear 프로젝트, 대시보드 URL) - 과거 사고 / 학습 ### ❌ 저장 X - 코드에서 보이는 것 (파일 구조, 함수 시그니처) - git log 로 알 수 있는 것 - 디버깅 결과 / 임시 상태 - CLAUDE.md 에 이미 있는 것 ## 다음 - [4가지 메모리 타입 자세히](/memory/types) - [CLAUDE.md 통합 관리](/memory/claude-md) - [Cursor / Windsurf 룰](/memory/cursor-windsurf) - [`create_dev_doc` API](/reference/create-dev-doc) --- # 4가지 메모리 타입 ainote 의 토픽 메모리는 **`type`** 필드로 분류. Claude 가 자동으로 적절한 타입 선택. ## 1. `user` — 사용자 정체성 **언제**: 사용자의 역할/선호/지식/배경 학습 시. **예시**: ```markdown --- name: user-role description: 사용자 역할 type: user --- 사용자는 한국거래소 8년차 개발자. Rails 메인, Flutter 보조. 한국어로 응답 선호. 기술 용어는 영어 그대로. ``` **활용**: 모든 응답이 사용자 배경에 맞춰 톤 조절. ## 2. `feedback` — 작업 가이드 **언제**: 사용자가 "이렇게 하지 마" 또는 "이렇게 해" 라고 가르쳐줄 때. **구조**: ```markdown --- name: feedback-no-mock-tests type: feedback --- 통합 테스트는 실제 DB 사용, mock 금지. **Why**: 작년 mock test 통과했지만 prod 마이그레이션 깨짐. **How to apply**: integration test 작성 시 항상 testcontainers 또는 transactional fixtures. ``` **규칙**: 단순 규칙 + Why + How to apply 구조 권장. ## 3. `project` — 진행중 컨텍스트 **언제**: 누가 무엇을 왜 언제까지 하나 학습 시. **중요**: 시간 빨리 변하므로 상대 날짜 → 절대 날짜 변환 (`목요일` → `2026-05-07`). **예시**: ```markdown --- name: project-mobile-freeze type: project --- 2026-05-15 부터 mobile 팀 release branch cut → non-critical merge freeze. **Why**: iOS 7.0 출시 코드 안정화. **How to apply**: 그 날짜 이후 mobile-related PR 은 mobile lead 승인 받고 진행. ``` **TTL**: 프로젝트 종료 시 정리 권장. 오래된 project 메모리는 misleading. ## 4. `reference` — 외부 시스템 포인터 **언제**: 사용자가 외부 시스템 위치 알려줄 때 (Slack 채널, Linear 프로젝트, Grafana URL). **예시**: ```markdown --- name: reference-grafana-api-latency type: reference --- oncall API 레이턴시 대시보드: grafana.internal/d/api-latency **용도**: request handling 코드 수정 시 페이지 발생 가능 → 변경 전 baseline 캡처. ``` **활용**: 사용자가 "그 대시보드" 언급할 때 Claude 가 찾아서 링크. ## 자동 분류 Claude 가 대화에서 메모리 저장 시 type 자동 결정: | 사용자 발화 | 추론 type | |-----------|----------| | "나는 X 회사 다닌다" | `user` | | "이거 하지 마, 이전에 망함" | `feedback` | | "다음주 X 마감이라 ..." | `project` | | "Linear INGEST 프로젝트에 ..." | `reference` | ## 검색 / 필터 ```bash ainote list_dev_docs '{"type":"feedback"}' ainote list_dev_docs '{"type":"project","search":"mobile"}' ``` ## 메모리 정리 `project` 타입은 정기 정리 권장: ```bash # 30일 이상 안 쓴 project 메모리 후보 ainote list_dev_docs '{"type":"project","not_accessed_within_days":30}' ``` 수동 검토 후 delete. `reference` 와 `user` 는 거의 영구. `feedback` 은 코드/도구 변경 시 stale 됨 → 분기마다 검토. ## 다음 - [메모리 / Dev Docs 개요](/memory/overview) - [CLAUDE.md 통합 관리](/memory/claude-md) - [`create_dev_doc` API](/reference/create-dev-doc) --- # 인증 헤더 ## 기본 형식 ```http Authorization: McpKey ``` 또는 (호환): ```http Authorization: Bearer ``` (`` 자리에 실제 키 값) ## 키 종류 | 키 | 길이 | 헤더 prefix | |---|------|-----------| | User API Key | 24 | `McpKey` 또는 `Bearer` | | MCP Key | 64 | `McpKey` 또는 `Bearer` | 자세히: [API Key 인증](/guide/auth). ## 인증 없이 호출 가능 도구 (예외 3개) `tools/list` + 온보딩 2개: - `tools/list` — 도구 카탈로그 조회 - `signup_and_get_key` - `login_and_get_key` - `get_setup_guide` 이 4개는 헤더 없이 200 응답. 다른 도구 헤더 없이 호출 시: ```json { "error": { "code": -32001, "message": "Unauthorized: missing or invalid Authorization header" } } ``` ## 키 마스킹 응답에서 키가 표시되는 경우 마지막 4자만: ``` Mcp Key (created): h7Ax...QWERTY (last 4 chars shown after first display) ``` 전체 키는 발급 직후에만 한 번 표시. ## 사용량 헤더 응답에 사용량 정보: ```http X-RateLimit-Limit: 120 X-RateLimit-Remaining: 87 X-RateLimit-Reset: 1746662400 # Unix timestamp X-Usage-Today: 1234 X-Usage-Today-Limit: 20000 ``` ## CORS 브라우저 직접 호출 (셀프호스팅 등): ```http Access-Control-Allow-Origin: https://your-domain.example.com Access-Control-Allow-Headers: Content-Type, Authorization ``` 기본 `api.ainote.dev` 는 `*` 안 함 → 백엔드 또는 MCP 클라이언트 통해. ## SSO / OAuth (계획됨) v0.5+: - Google / Apple SSO 로 키 발급 - OAuth 2.0 flow (PKCE) - 단명 access token + refresh token ## 다음 - [API Key 인증 가이드](/guide/auth) - [JSON-RPC 형식](/reference/json-rpc) - [에러 코드](/reference/errors) --- # 변경 로그 semver 비슷하지만 v1 이전이라 minor breaking 가능. ## v0.x (현재 — alpha) ### 0.4 (2026-05 예정) - ✨ Vault — git backend (5 도구) - ✨ Sync — HLC + LWW (3 도구) - ✨ ChatGPT MCP connector (SSE 브리지) - 🔄 dev_doc 4가지 메모리 type 자동 분류 - 🐛 type 필드 누락 시 에러 메시지 개선 ### 0.3 (2026-04) - ✨ Telegram bot (Clawdbot) 연동 - ✨ MCP 키 사용량 통계 (per-key) - ✨ Streaming API (NDJSON, list_tasks 1000+) - 🔄 자연어 파싱 개선 (한국어 시간 표현) - 🐛 반복 태스크 timezone 버그 ### 0.2 (2026-03) - ✨ `signup_and_get_key`, `login_and_get_key` (계정 없이 시작) - ✨ Cursor / Windsurf 통합 (rules 동기화) - ✨ Web Push (PWA 알림) - 🔄 Solid Queue 도입 (Sidekiq → SQ) ### 0.1 (2026-02) - 🎉 첫 공개 - 태스크 CRUD + 18 필터 - Dev Docs (CLAUDE.md 동기화) - Claude Desktop / Code 지원 ## v1 로드맵 (2026-Q4 예정) - API stability 보장 (SemVer 시작) - OAuth 2.0 + Google/Apple SSO - Vault sharing (collaborator) - 모바일 위젯 - Search MCP 도구 (의미 기반) ## Breaking Changes ### 0.3 → 0.4 - `update_dev_doc` 의 `mode` 기본값 `replace` (이전 `append`) - `vault_*` 도구 추가 (기존 호출 영향 X) ### 0.2 → 0.3 - `create_task` 의 `due` → `due_at` 으로 rename - `list_tasks` 응답에 `format` 파라미터 추가 (기본 `json`) ## Migration 가이드 `/migrations/{from}-to-{to}.md` 페이지 (계획됨, 0.5+). ## 다음 - [API 레퍼런스 개요](/reference/) - [GitHub releases](https://github.com/seunghan91/ainote/releases) --- # create_dev_doc 새 dev_doc / 메모리 등록. ## 시그니처 ```json { "name": "create_dev_doc", "arguments": { "title": "launchcrew-claude.md", "content": "", "category": "claude", "local_path": "/Users/seunghan/launchcrew/CLAUDE.md" } } ``` ## 파라미터 | 파라미터 | 타입 | 필수 | 설명 | |---------|------|------|------| | `title` | string | ✅ | 파일명 (`.md` 확장자 포함) | | `content` | string | ✅ | 마크다운 / json / yaml 본문 | | `category` | string | | 서브카테고리 (`claude`, `cursor`, `windsurf`, `copilot`, `docs`). 기본 `docs` | | `content_type` | string | | `markdown` / `json` / `yaml` / `text` (title 확장자에서 자동 감지) | | `local_path` | string | | 로컬 절대경로 ([`pull_dev_docs`](/reference/pull-dev-docs) 복원 대상) | ::: tip 카테고리 이름 서버 필드는 `category` 입니다 (`subcategory` 아님). 카테고리 값 자체는 "subcategory" 라고 부르기도 합니다 — 헷갈리지 마세요. 도구 인자는 항상 **`category`**. ::: ## 응답 ```json { "content": [ { "type": "text", "text": "✅ Saved: launchcrew-claude.md → dev/claude/launchcrew-claude.md" } ] } ``` ## local_path 중요성 `local_path` 없으면 [`pull_dev_docs`](/reference/pull-dev-docs) 가 파일을 만들 수 없음. 권장: 절대 경로. `~` 으로 시작하는 경로는 자동으로 canonicalize 됨 (서버 저장 시 `$HOME` → `~`). ## 에러 | 코드 | 메시지 | |------|-------| | -32602 | `title is required` | | -32602 | `content is required` | | -32603 | `Title already exists in this category` → [`update_dev_doc`](/reference/update-dev-doc) 사용 | ## Claude 자연어 ``` ~/launchcrew/CLAUDE.md 를 ainote 에 등록해줘 — title launchcrew-claude.md, category claude ``` ## 다음 - [`update_dev_doc`](/reference/update-dev-doc) - [`pull_dev_docs`](/reference/pull-dev-docs) - [메모리 / Dev Docs 개요](/memory/overview) --- # create_task 새 태스크 생성. 필수 필드: `content`. ## 시그니처 ```json { "name": "create_task", "arguments": { "content": "강남역 미팅 준비", "due_date": "2026-05-08T10:00:00+09:00", "is_important": true, "category_id": "uuid-here", "location": "강남역 3번 출구 스타벅스", "notification_minutes_before": 30 } } ``` ## 파라미터 | 파라미터 | 타입 | 필수 | 설명 | |---------|------|------|------| | `content` | string | ✅ | 태스크 내용 (자연어 OK) | | `due_date` | string (ISO) | | 마감일 (예: `2026-04-21T15:00:00+09:00`) | | `due_time` | string `HH:MM` | | 시간만 (`due_date` 에 시간 없을 때) | | `start_date` | string (ISO) | | 다일 이벤트 시작일 | | `is_all_day` | boolean | | 종일 일정 | | `is_important` | boolean | | 중요 표시 | | `category_id` | string (UUID) | | 카테고리 ID (`list_categories` 로 조회) | | `notes` | string | | 자유 메모 / 상세 | | `location` | string | | 사람용 위치 (예: "Starbucks Gangnam") | | `location_lat` | number | | GPS 위도 | | `location_lng` | number | | GPS 경도 | | `travel_time` | number | | 마감 N분 전 이동시간 (알림 스케줄링) | | `repeat_rule` | string | | 반복 규칙 (`daily`, `weekly`, `monthly` 또는 RRULE) | | `notification_minutes_before` | number | | 마감 N분 전 알림 (`due_date` 필요) | ## 응답 ```json { "content": [ { "type": "text", "text": "✅ 태스크 생성됨 — \"강남역 미팅 준비\" (내일 10:00)" } ] } ``` ## Claude 자연어 ``` "내일 오전 10시 강남역 미팅 준비, 30분 전 알림" 추가 ``` → Claude 가 자연어를 파싱해서 `due_date`, `location`, `notification_minutes_before` 추출 후 호출. ## 에러 | 코드 | 메시지 | 원인 | |------|-------|------| | -32602 | `Task content is required` | `content` 누락 | | -32602 | `Invalid due_date format` | ISO 8601 아님 | | -32602 | `Category not found or not accessible` | `category_id` 잘못 | ## 다음 - [`update_task`](/reference/update-task) - [`list_tasks`](/reference/list-tasks) - [태스크 개요](/tasks/overview) --- # delete_dev_doc dev_doc soft delete. ## 시그니처 ```json { "name": "delete_dev_doc", "arguments": { "title": "old-notes.md" } } ``` 또는: ```json { "id": "uuid-here" } ``` ## 파라미터 | 파라미터 | 타입 | 설명 | |---------|------|------| | `title` | string | 문서 title (id 와 둘 중 하나) | | `id` | string | UUID | | `category` | string | 동명 문서 disambiguation | ## 응답 ```json { "content": [ { "type": "text", "text": "🗑️ Deleted: old-notes.md" } ] } ``` ## Soft delete 서버 DB 에 `deleted_at` 마킹. 30일 후 영구 정리. 웹 UI 에서 즉시 영구 삭제 가능. ## 에러 | 코드 | 메시지 | |------|-------| | -32602 | `title or id is required` | | -32603 | `Document not found` | ## Claude ``` ainote 에서 "old-notes.md" 삭제 ``` ## 다음 - [`create_dev_doc`](/reference/create-dev-doc) - [데이터 내보내기 / 삭제](/guide/data-export) --- # delete_task 태스크 삭제 — soft delete (서버에서 30일 후 영구 정리). ## 시그니처 ```json { "name": "delete_task", "arguments": { "id": "uuid-here" } } ``` ## 파라미터 | 파라미터 | 타입 | 필수 | 설명 | |---------|------|------|------| | `id` | string | ✅ | 태스크 ID | ## 응답 ```json { "content": [ { "type": "text", "text": "✅ 태스크 삭제됨" } ] } ``` ## Soft delete 동작 - DB 에 `deleted_at` 마킹 → 모든 `list_tasks` 응답에서 제외 - 서버의 `TaskCleanupJob` (매일 새벽 2시) 이 30일 지난 항목 영구 삭제 - 영구 삭제 시 cascade: notifications, notification_schedules, paper_tasks, recurring_instances ## 즉시 영구 삭제 MCP 도구는 soft delete 만 노출. 즉시 영구 삭제는 웹 UI: ## 에러 | 코드 | 메시지 | |------|-------| | -32602 | `id is required` | | -32602 | `Task not found` | ## Claude ``` 태스크 #uuid 삭제 ``` ## 다음 - [`update_task`](/reference/update-task) — 완료 처리는 update_task 사용 - [태스크 개요](/tasks/overview) --- # 에러 코드 JSON-RPC 표준 + ainote 확장. ## JSON-RPC 표준 | 코드 | 의미 | 원인 | |------|------|------| | -32700 | Parse error | JSON 문법 오류 | | -32600 | Invalid Request | jsonrpc 필드 누락 등 | | -32601 | Method not found | `method` 가 `tools/list` / `tools/call` 아님 | | -32602 | Invalid params | 도구 파라미터 잘못 | | -32603 | Internal error | 서버 내부 오류 | ## ainote 확장 (-32000 ~ -32099) | 코드 | 의미 | 원인 | 해결 | |------|------|------|------| | -32000 | Tool not found | `params.name` 이 도구 목록에 없음 | `tools/list` 로 확인 | | -32001 | Unauthorized | 헤더 누락 / 잘못 | [인증 가이드](/guide/auth) | | -32002 | Rate limit exceeded | 분당/일당 한도 초과 | `Retry-After` 헤더 대기 | | -32003 | Validation failed | 비즈니스 규칙 위반 | `data.field` 확인 | | -32004 | Quota exceeded | 저장량/vault 개수 한도 | 정리 또는 유료 플랜 | | -32005 | Resource not found | 태스크/문서 ID 없음 | `list_*` 로 확인 | | -32006 | Conflict | 충돌 (예: vault sync) | 수동 해결 | | -32007 | Permission denied | 키 권한 부족 (read-only 키로 write) | 권한 키 발급 | ## 응답 예시 ```json { "jsonrpc": "2.0", "id": 1, "error": { "code": -32602, "message": "Invalid params: title is required", "data": { "field": "title", "constraint": "required" } } } ``` ## HTTP 상태 코드 JSON-RPC 응답은 보통 HTTP 200 (에러도). 단: - HTTP 401 — `Authorization` 자체 형식 잘못 (JSON 파싱도 안 함) - HTTP 429 — Rate limit (매우 빠른 reject) - HTTP 500 — 서버 다운 (보통은 200 + error code) ## 자주 보는 시나리오 ### 처음 호출이 401 ```json { "code": -32001, "message": "Unauthorized" } ``` 체크: - 키 prefix `McpKey ` (공백 1개) - 키 64자 (`MCP Key`) 또는 24자 (`User API Key`) - 키 발급 후 폐기 안 됨 ### 자연어 파싱 실패 ```json { "code": -32602, "message": "Could not parse due_at from title", "data": { "title": "tomorrow at noonish maybe" } } ``` 해결: 명시적 ISO 8601: ```json { "title": "회의", "due_at": "2026-05-08T12:00:00+09:00" } ``` ### 429 Rate limit ```http HTTP/1.1 429 Too Many Requests Retry-After: 30 { "error": { "code": -32002, "message": "Rate limit exceeded" } } ``` `Retry-After: 30` → 30초 대기 후 재시도. ### Quota 초과 ```json { "code": -32004, "message": "Vault quota exceeded", "data": { "current": 100, "limit": 100, "type": "vault_count" } } ``` 해결: 안 쓰는 vault 삭제 또는 유료 플랜. ## 클라이언트 처리 권장 ```typescript async function callMcp(name, args) { const res = await fetch(URL, {...}); const json = await res.json(); if (json.error) { if (json.error.code === -32002) { // Rate limit — exponential backoff const retry = parseInt(res.headers.get('Retry-After') || '5'); await sleep(retry * 1000); return callMcp(name, args); } if (json.error.code === -32001) { throw new AuthError(json.error.message); } throw new McpError(json.error.code, json.error.message, json.error.data); } return json.result; } ``` ## 다음 - [JSON-RPC 형식](/reference/json-rpc) - [Troubleshooting](/guide/troubleshooting) --- # get_dev_doc 단일 dev_doc 조회 (전체 본문 포함). ## 시그니처 ```json { "name": "get_dev_doc", "arguments": { "title": "launchcrew-claude.md" } } ``` 또는 UUID: ```json { "id": "uuid-here" } ``` ## 파라미터 | 파라미터 | 타입 | 설명 | |---------|------|------| | `title` | string | 문서 title (id 와 둘 중 하나) | | `id` | string | UUID | | `category` | string | 동명 문서 disambiguation | | `include_versions` | boolean | 버전 히스토리 포함 (기본 false) | ## 응답 ```json { "content": [ { "type": "text", "text": "[Formatted doc info + content]" }, { "type": "resource", "resource": { "uri": "ainote://dev_docs/uuid", "mimeType": "application/json", "text": "{\"id\":\"uuid\",\"title\":\"...\",\"content\":\"...\",\"category\":\"claude\",\"local_path\":\"...\"}" } } ] } ``` `include_versions: true` 시 응답에 version history 추가. ## 에러 | 코드 | 메시지 | |------|-------| | -32602 | `title or id is required` | | -32603 | `Document not found` | ## Claude ``` launchcrew-claude.md 보여줘 launchcrew-claude.md 변경 이력 (include_versions: true) ``` ## 다음 - [`list_dev_docs`](/reference/list-dev-docs) — 검색 + 필터 - [`update_dev_doc`](/reference/update-dev-doc) --- # API 레퍼런스 ainote MCP 서버의 전체 도구 카탈로그. ## 공통 사항 - **Endpoint**: `https://api.ainote.dev/api/mcp` - **Protocol**: JSON-RPC 2.0 over HTTP POST - **Auth**: `Authorization: McpKey ` 헤더 - **Content-Type**: `application/json` 자세히: [JSON-RPC 호출 형식](/reference/json-rpc) · [인증](/reference/auth) · [에러 코드](/reference/errors). ## 도구 카탈로그 (17개) ### 온보딩 (3) | 도구 | 인증 필요 | 설명 | |------|----------|------| | [`signup_and_get_key`](/reference/signup) | ❌ | 가입 + MCP 키 발급 | | [`login_and_get_key`](/reference/login) | ❌ | 기존 계정 로그인 + 키 발급 | | [`get_setup_guide`](/reference/setup-guide) | ❌ | 클라이언트별 설정 가이드 | ### 태스크 (5) | 도구 | 설명 | |------|------| | [`create_task`](/reference/create-task) | 새 태스크 (자연어 파싱 포함) | | [`update_task`](/reference/update-task) | 수정 / 완료 처리 | | [`delete_task`](/reference/delete-task) | 삭제 (soft, 30일) | | [`list_tasks`](/reference/list-tasks) | 18+ 필터 조회 | | [`list_categories`](/reference/list-categories) | 카테고리 목록 | ### 메모리 / Dev Docs (7) | 도구 | 설명 | |------|------| | [`create_dev_doc`](/reference/create-dev-doc) | 새 문서 등록 | | [`update_dev_doc`](/reference/update-dev-doc) | 수정 (replace/append/prepend) | | [`delete_dev_doc`](/reference/delete-dev-doc) | 삭제 | | [`get_dev_doc`](/reference/get-dev-doc) | 단일 조회 | | [`list_dev_docs`](/reference/list-dev-docs) | 검색 + 필터 | | [`list_dev_categories`](/reference/list-dev-categories) | 카테고리 목록 | | [`pull_dev_docs`](/reference/pull-dev-docs) | local_path 일괄 복원 | ### Vault (5) | 도구 | 설명 | |------|------| | [`vault_create`](/reference/vault-create) | 새 vault | | [`vault_clone`](/reference/vault-clone) | git clone (다른 기기) | | [`vault_sync`](/reference/vault-sync) | pull + push | | [`vault_list`](/reference/vault-list) | vault 목록 | | [`vault_connect_status`](/reference/vault-connect-status) | git 연결 상태 | ### Sync (3) | 도구 | 설명 | |------|------| | [`sync_push`](/reference/sync-push) | 로컬 파일 → ainote | | [`sync_pull`](/reference/sync-pull) | ainote → 로컬 (since 기준) | | [`sync_list`](/reference/sync-list) | sync 대상 목록 | ## Rate Limits | 키 종류 | 분당 | 시간당 | 일당 | |--------|------|-------|------| | User API Key | 60 | 1,000 | 10,000 | | MCP Key (free) | 120 | 2,000 | 20,000 | | MCP Key (paid) | 600 | 10,000 | 무제한 | 429 응답 시 `Retry-After` 헤더 확인. ## SDK / 라이브러리 | 언어 | 패키지 | |------|--------| | Node.js (MCP) | [`@ainote/mcp`](https://www.npmjs.com/package/@ainote/mcp) | | Ruby (Rails 통합) | [`@seunghan/rails-api-client`](https://www.npmjs.com/package/@seunghan/rails-api-client) | | 직접 호출 | [JSON-RPC 형식](/reference/json-rpc) | ## OpenAPI `https://api.ainote.dev/openapi.yaml` (계획됨, v0.x). ## 변경 로그 [변경 로그](/reference/changelog) 참고. --- # JSON-RPC 호출 형식 ainote 의 모든 MCP 도구는 [JSON-RPC 2.0](https://www.jsonrpc.org/specification) over HTTP POST. ## Endpoint ``` POST https://api.ainote.dev/api/mcp Content-Type: application/json Authorization: McpKey ``` ## 요청 형식 ```json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "<도구 이름>", "arguments": { ... } } } ``` | 필드 | 타입 | 설명 | |------|------|------| | `jsonrpc` | string | 항상 `"2.0"` | | `id` | int / string | 요청 식별자 (응답에 echo) | | `method` | string | `tools/list` 또는 `tools/call` | | `params.name` | string | 도구 이름 | | `params.arguments` | object | 도구별 파라미터 | ## 응답 형식 (성공) ```json { "jsonrpc": "2.0", "id": 1, "result": { "content": [ { "type": "text", "text": "..." } ] } } ``` 또는 도구가 structured 반환: ```json { "result": { "content": [{ "type": "text", "text": "..." }], "structuredContent": { "task_id": 1234, "created_at": "2026-05-07T10:00:00Z" } } } ``` ## 응답 형식 (에러) ```json { "jsonrpc": "2.0", "id": 1, "error": { "code": -32602, "message": "Invalid params: title is required", "data": { "field": "title" } } } ``` 자세히: [에러 코드](/reference/errors). ## 도구 목록 조회 ```json { "jsonrpc": "2.0", "id": 1, "method": "tools/list" } ``` 응답: ```json { "result": { "tools": [ { "name": "create_task", "description": "Create a new task with natural language parsing.", "inputSchema": { "type": "object", "properties": { ... } } }, ... ] } } ``` ## curl 예시 ```bash curl -X POST https://api.ainote.dev/api/mcp \ -H "Content-Type: application/json" \ -H "Authorization: McpKey YOUR_KEY" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "list_tasks", "arguments": { "due_today": true } } }' ``` shell 함수로 wrap: [shell 함수](/cli/shell-function). ## Streaming (NDJSON) 대량 응답 시 `Accept: application/x-ndjson` 헤더 → 각 결과 한 줄씩: ```bash curl -N -X POST https://api.ainote.dev/api/mcp \ -H "Authorization: McpKey YOUR_KEY" \ -H "Accept: application/x-ndjson" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_tasks","arguments":{"limit":1000}}}' ``` ## Batch 요청 여러 도구 한번에: ```json [ { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "list_tasks", "arguments": {} } }, { "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "list_categories", "arguments": {} } } ] ``` 응답도 배열 (순서 보장 X — `id` 로 매칭). ## 다음 - [인증 헤더](/reference/auth) - [에러 코드](/reference/errors) - [전체 도구 카탈로그](/reference/) --- # list_categories 태스크 카테고리 목록. ## 시그니처 ```json { "name": "list_categories", "arguments": {} } ``` 파라미터 없음. ## 응답 ```json { "content": [ { "type": "text", "text": "📂 카테고리 목록\n- 업무 (12)\n- 개인 (5)\n..." }, { "type": "resource", "resource": { "uri": "ainote://categories/list", "mimeType": "application/json", "text": "{\"categories\":[{\"id\":\"uuid\",\"name\":\"업무\",\"color\":\"#0088ff\"}]}" } } ] } ``` ## Claude ``` 카테고리 목록 "업무" 카테고리 ID ``` ## 다음 - [태스크 카테고리 자세히](/tasks/categories) - [`list_tasks`](/reference/list-tasks) --- # list_dev_categories dev/ 아래 사용 중인 모든 카테고리 목록. ## 시그니처 ```json { "name": "list_dev_categories", "arguments": {} } ``` 파라미터 없음. ## 응답 ```json { "content": [ { "type": "text", "text": "📂 카테고리\n- claude (17 docs)\n- cursor (5)\n..." }, { "type": "resource", "resource": { "uri": "ainote://dev_categories/list", "mimeType": "application/json", "text": "{\"categories\":[{\"name\":\"claude\",\"count\":17},...]}" } } ] } ``` 표준 + 사용자 정의 카테고리 모두 포함. ## 표준 카테고리 (예시) | `category` | 디렉토리 | 용도 | |--------------|---------|------| | `claude` | `dev/claude/` | CLAUDE.md (Claude Code/Desktop) | | `cursor` | `dev/cursor/` | `.cursorrules`, `.cursor/rules/*.md` | | `windsurf` | `dev/windsurf/` | `.windsurfrules` | | `copilot` | `dev/copilot/` | `.github/copilot-instructions.md` | | `docs` | `dev/docs/` | README, ARCHITECTURE.md, 기타 | | `memory` | `memory/` | 토픽 메모리 | ## 사용 ``` 어떤 카테고리들이 있는지 보여줘 ``` ## 다음 - [`list_dev_docs`](/reference/list-dev-docs) - [메모리 카테고리 / 네이밍](/memory/categories) --- # list_dev_docs dev_docs 검색 + 필터. ## 시그니처 ```json { "name": "list_dev_docs", "arguments": { "category": "claude", "search": "firebase" } } ``` ## 파라미터 | 파라미터 | 타입 | 설명 | |---------|------|------| | `category` | string | 서브카테고리 (`claude`, `cursor`, `windsurf`, `copilot`, `docs`, `memory`, ...) | | `search` | string | title 키워드 | | `content_type` | string | `markdown` / `json` / `yaml` / `text` | ::: tip 단순한 필터 이 도구는 위 3개 필터만 지원합니다. 메모리 type 별 필터, 날짜 범위, 페이징 등은 현재 없음 (계획됨). ::: ## 응답 ```json { "content": [ { "type": "text", "text": "[Formatted list]" }, { "type": "resource", "resource": { "uri": "ainote://dev_docs/list", "mimeType": "application/json", "text": "{\"docs\":[{\"id\":\"uuid\",\"title\":\"...\",\"category\":\"claude\",\"local_path\":\"...\"}]}" } } ] } ``` `content` 본문은 응답에 미포함 (요약만) — 본문은 [`get_dev_doc`](/reference/get-dev-doc) 으로. ## 자주 쓰는 호출 ```jsonc // 모든 CLAUDE.md { "category": "claude" } // 모든 cursor 룰 { "category": "cursor" } // 키워드 검색 (전체) { "search": "firebase" } // 카테고리 + 키워드 { "category": "claude", "search": "stripe" } // 모든 dev_docs { } ``` ## 에러 | 코드 | 메시지 | |------|-------| | -32602 | `Invalid content_type` | ## Claude ``` 모든 CLAUDE.md 보여줘 firebase 들어간 dev_doc 검색 ``` ## 다음 - [`get_dev_doc`](/reference/get-dev-doc) - [`pull_dev_docs`](/reference/pull-dev-docs) - [메모리 카테고리 / 네이밍](/memory/categories) --- # list_tasks 태스크 조회 — 자연어 + 18개 필터 지원. ## 시그니처 ```json { "name": "list_tasks", "arguments": { "status": "pending", "due_today": true, "is_important": true, "limit": 20 } } ``` ## 파라미터 전체 ### 상태 | 파라미터 | 값 | |---------|-----| | `status` | `pending` / `completed` | | `is_important` | boolean | | `overdue` | boolean — 마감 지난 미완료 | | `due_today` | boolean — 오늘 마감 | | `has_notification` | boolean | ### 검색 / 위치 | 파라미터 | 설명 | |---------|------| | `search` | 내용 키워드 | | `location` | 위치 부분일치 (예: `"여의도"`, `"서울"`) | | `category_id` | 카테고리 UUID | ### 날짜 범위 | 파라미터 | 설명 | |---------|------| | `due_date_start` | ISO 8601, `due_date >= 이 값` | | `due_date_end` | `due_date <= 이 값` | | `completed_date_start` | 완료일 >= | | `completed_date_end` | 완료일 <= | | `created_date_start` | 생성일 >= | | `created_date_end` | 생성일 <= | ### 정렬 / 페이징 | 파라미터 | 값 | 기본 | |---------|-----|------| | `sort_by` | `due_date` / `created_at` / `completed_at` / `updated_at` / `is_important` | `created_at` | | `sort_order` | `asc` / `desc` | `desc` | | `limit` | 1~500 | 25 | ## 자연어 호출 예시 | 발화 | 파라미터 | |------|---------| | "오늘 할 일" | `{ due_today: true }` | | "이번 주 마감" | `{ due_date_start: "<월요일>", due_date_end: "<일요일>" }` | | "마감 지난 미완료" | `{ overdue: true, sort_by: "due_date", sort_order: "asc" }` | | "여의도에서 중요한 미완료" | `{ location: "여의도", is_important: true, status: "pending" }` | | "지난달 완료 업무 카테고리" | `{ category_id: "...", status: "completed", completed_date_start: "...", completed_date_end: "..." }` | ## 응답 ```json { "content": [ { "type": "text", "text": "[Formatted task list]" }, { "type": "resource", "resource": { "uri": "ainote://tasks/list", "mimeType": "application/json", "text": "{\"tasks\":[...]}" } } ] } ``` structured data 는 `content[1].resource.text` 의 JSON 에 포함. ## 에러 | 코드 | 메시지 | |------|-------| | -32602 | `Invalid date format (expected ISO 8601)` | | -32602 | `Invalid sort_by value` | ## Claude ``` 오늘 할 일 보여줘 이번 주 마감 중요한 거 강남에 있는 미완료 태스크 ``` ## 다음 - [필터링 자세히 (오늘/이번주 등 시간 계산)](/tasks/filtering) - [`create_task`](/reference/create-task) - [태스크 개요](/tasks/overview) --- # login_and_get_key 기존 계정 로그인 + 새 MCP 키 발급. **인증 헤더 불필요**. ## 시그니처 ```json { "name": "login_and_get_key", "arguments": { "email": "me@example.com", "password": "SuperSecret123" } } ``` ## 파라미터 | 파라미터 | 타입 | 필수 | 설명 | |---------|------|------|------| | `email` | string | ✅ | 가입한 이메일 | | `password` | string | ✅ | 비밀번호 | ## 응답 ```json { "content": [ { "type": "text", "text": "✅ 로그인 완료\nemail: me@example.com\nMCP Key: h7Ax... (full key shown once)" } ] } ``` 기존 키는 그대로 유지 (폐기 안 함). 같은 사용자가 여러 키 가능. ## 에러 | 코드 | 메시지 | 원인 | |------|-------|------| | -32603 | `Invalid email or password` | | | -32002 | `Too many login attempts` | 잠금 | ## Claude 에서 자연어 ``` ainote 로그인 — me@example.com / SuperSecret123 ``` ## 새 기기에서 키 발급 패턴 새 노트북에서 처음 사용할 때: 1. MCP 등록 (헤더 없이) 2. `login_and_get_key` 호출 → 새 키 3. 헤더에 키 추가 후 reload ## 다음 - [`signup_and_get_key`](/reference/signup) - [API Key 인증 가이드](/guide/auth) --- # pull_dev_docs dev_docs 일괄 복원 — `local_path` 기준으로 로컬 파일 생성/덮어쓰기. ## 시그니처 ```json { "name": "pull_dev_docs", "arguments": { "category": "claude" } } ``` ## 파라미터 | 파라미터 | 타입 | 설명 | |---------|------|------| | `category` | string | 단일 카테고리 (생략 시 모든 dev_docs) | ::: tip 단순한 필터 이 도구는 `category` 만 받습니다. `since`, `dry_run`, `force` 같은 옵션은 현재 없음 — 항상 모든 매칭 doc 을 받아 로컬에 덮어씀. ::: ## 동작 1. 매칭되는 모든 `dev_doc` 중 `local_path` 가 set 된 것만 가져옴 2. 현재 플랫폼 자동 감지 (macOS / WSL / Linux / Windows) 3. 경로 매핑 (예: `~/...` ↔ `/mnt/c/Users/...`) 4. 부모 디렉토리 자동 mkdir 5. 각 파일 작성 ## 크로스 플랫폼 경로 매핑 자동: - macOS `~/...` ↔ WSL `~/...` ↔ Linux `~/...` - Claude project keys 매핑 (예: `-Users-seunghan` ↔ `-mnt-c-Users-Owner`) 새 기기 (다른 OS) 셋업 시 `pull_dev_docs '{}'` 한 번이면 알아서 적절한 경로로. ## 응답 ```json { "content": [ { "type": "text", "text": "🖥️ Platform: darwin | Home: /Users/seunghan | Project key: -Users-seunghan\n\nWritten (17):\n✅ launchcrew-claude.md → /Users/seunghan/launchcrew/CLAUDE.md\n✅ tennis-bracket-claude.md → /Users/seunghan/tennis_bracket/CLAUDE.md\n...\n\nSkipped (no local_path): some-doc.md, ..." } ] } ``` ## skipped 이유 - `local_path` 없음 (등록 시 미설정) - 디렉토리 권한 부족 → errors 에 기록 ## 에러 모음 응답에 부분 실패 표시 (`Errors:` 섹션): ``` Errors: ❌ x.md: EACCES: permission denied, mkdir '/restricted' ``` 전체 호출 자체는 보통 성공 — 개별 파일 실패는 응답에 누적. ## 시나리오 ### 새 기기 셋업 ```json {} ``` → 모든 dev_docs (모든 카테고리) 일괄. ### 카테고리만 ```json { "category": "claude" } { "category": "cursor" } ``` ## Claude ``` 모든 CLAUDE.md 다시 가져와 ainote 에서 cursor 룰 다 받아 ``` ## 다음 - [`get_dev_doc`](/reference/get-dev-doc) - [CLAUDE.md 통합 관리](/memory/claude-md) - [새 디바이스 셋업 예시](/examples/new-device) --- # get_setup_guide 클라이언트별 설정 가이드 반환. **인증 헤더 불필요**. ## 시그니처 ```json { "name": "get_setup_guide", "arguments": { "client": "claude-code", "language": "ko" } } ``` | 파라미터 | 값 | 설명 | |---------|-----|------| | `client` | `claude-code` / `claude-desktop` / `chatgpt` / `cursor` / `windsurf` / `telegram` | 클라이언트 | | `language` | `ko` / `en` | 응답 언어 (기본 `en`) | ## 응답 ```json { "client": "claude-code", "language": "ko", "guide": "# Claude Code 등록\n\n1. ~/.claude.json 편집...", "config_template": { "mcpServers": { "ainote": { "type": "http", "url": "https://api.ainote.dev/api/mcp", "headers": { "Authorization": "McpKey " } } } }, "docs_url": "https://docs.ainote.dev/mcp/claude-code" } ``` ## 사용 시나리오 Claude 에서: ``` ainote setup 가이드 보여줘 — Claude Code 용 ``` → 가이드 + 설정 JSON 즉시 받음 → 사용자 클립보드 복사. ## 클라이언트 우선 (계획됨) 자동 감지: ```json { "arguments": {} } ``` → User-Agent 기반 추론 → 적절한 가이드 반환. ## 다음 - [Claude Code 연결](/mcp/claude-code) — 사람용 가이드 - [3가지 transport 비교](/mcp/transports) --- # signup_and_get_key 신규 가입 + MCP 키 발급. **인증 헤더 불필요**. ## 시그니처 ```json { "name": "signup_and_get_key", "arguments": { "email": "me@example.com", "password": "SuperSecret123", "name": "Seunghan" } } ``` ## 파라미터 | 파라미터 | 타입 | 필수 | 설명 | |---------|------|------|------| | `email` | string | ✅ | 유효한 이메일 | | `password` | string | ✅ | 최소 6자 | | `name` | string | | 표시 이름 (선택) | ## 응답 ```json { "content": [ { "type": "text", "text": "✅ 가입 완료\nemail: me@example.com\nMCP Key: h7Ax... (full key shown once)" } ] } ``` ⚠️ MCP Key 는 **이번에만 한 번** 표시. 즉시 저장. ## 에러 | 코드 | 메시지 | 원인 | |------|-------|------| | -32602 | `email is required` | | | -32602 | `password must be at least 6 characters` | 6자 미만 | | -32603 | `email already taken` | → [`login_and_get_key`](/reference/login) 사용 | ## 보안 - 비번: bcrypt 저장 - HTTPS only - IP/User-Agent 로그 ## Claude 에서 자연어 ``` ainote 가입 시켜줘 — 이메일 me@example.com / 비밀번호 SuperSecret123 / 이름 승한 ``` ## 다음 - [`login_and_get_key`](/reference/login) - [API Key 인증 가이드](/guide/auth) --- # sync_list ::: warning 🚧 설계 단계 — 아직 구현 안 됨 이 페이지는 ainote 의 향후 기능을 미리 문서화한 것입니다. 현재 `@ainote/mcp` v1.1.x 에는 **vault / sync 도구가 포함돼 있지 않습니다**. 도구 호출 시 `Tool not found` 에러를 받게 됩니다. ::: ainote hub 의 sync 대상 파일 메타데이터 조회 (content 없이). ## 시그니처 ```json { "name": "sync_list", "arguments": { "prefix": "global/", "since": "2026-05-01T00:00:00Z" } } ``` | 파라미터 | 타입 | 설명 | |---------|------|------| | `prefix` | string | 경로 prefix (디렉토리 필터) | | `since` | ISO 8601 | 변경 시각 필터 | | `device_id` | string | 자기 디바이스 변경 제외 | | `limit` | int | 기본 1000 | ## 응답 ```json { "files": [ { "path": "global/CLAUDE.md", "size_bytes": 12000, "sha256": "abc123...", "hlc": "2026-05-07T14:01:00.0.macmini", "git_sha": "def456...", "device_id": "macmini-2026-04", "stored_at": "2026-05-07T14:01:01Z" }, ... ], "total_count": 53, "total_size_bytes": 234000 } ``` content 없음 — 받으려면 `sync_pull` 호출. ## 사용 시나리오 ### sync 전 diff 확인 ```python local_files = scan_local() remote_files = mcp_call("sync_list", {}) diffs = compare(local_files, remote_files) print(f"{len(diffs)} files differ") ``` ### 디렉토리별 통계 ```bash ainote sync_list '{"prefix":"global/"}' ainote sync_list '{"prefix":"projects/launchcrew/"}' ``` ### 최근 변경 모니터 ```bash # 매 시간 ainote sync_list '{"since":"<1h ago>"}' ``` ## 에러 | 코드 | 메시지 | |------|-------| | -32602 | invalid prefix | | -32602 | invalid since format | ## Claude ``` 지난주 변경된 sync 파일 보여줘 launchcrew 폴더에 뭐 있나 ``` ## 다음 - [`sync_push`](/reference/sync-push) - [`sync_pull`](/reference/sync-pull) - [동기화 개요](/sync/overview) --- # sync_pull ::: warning 🚧 설계 단계 — 아직 구현 안 됨 이 페이지는 ainote 의 향후 기능을 미리 문서화한 것입니다. 현재 `@ainote/mcp` v1.1.x 에는 **vault / sync 도구가 포함돼 있지 않습니다**. 도구 호출 시 `Tool not found` 에러를 받게 됩니다. ::: ainote hub → 로컬. 변경된 파일 받기. ## 시그니처 ### 단일 파일 ```json { "name": "sync_pull", "arguments": { "path": "global/CLAUDE.md" } } ``` ### 여러 파일 (since) ```json { "name": "sync_pull", "arguments": { "since": "2026-05-06T00:00:00Z", "device_id": "macmini-2026-04-A1B2" } } ``` ### 초기 (모든 파일) ```json { "name": "sync_pull", "arguments": { "initial": true, "device_id": "macmini-2026-04-A1B2" } } ``` | 파라미터 | 타입 | 설명 | |---------|------|------| | `path` | string | 단일 파일 | | `since` | ISO 8601 | 이 시각 이후 변경된 것 | | `initial` | boolean | 모든 파일 (새 디바이스) | | `device_id` | string | 자기 디바이스 변경은 제외 | | `paths` | array | 특정 경로들만 | ## 응답 ### 단일 ```json { "path": "global/CLAUDE.md", "content": "", "sha256": "abc123...", "hlc": "2026-05-07T14:02:30.0.macbook", "git_sha": "def456...", "stored_at": "2026-05-07T14:02:30Z", "device_id": "macbook-2026-03", "remote_mtime": "2026-05-07T14:02:30Z" } ``` ### Multi (since / initial) ```json { "files": [ { "path": "global/CLAUDE.md", "content": "...", "sha256": "...", "hlc": "..." }, { "path": "global/PERSONAS.md", "content": "...", ... } ], "total_count": 53, "total_size_bytes": 234000 } ``` ## Streaming 큰 결과 (`since` 가 오래됨, `initial`) 는 NDJSON 자동: ```bash curl -N -X POST .../api/mcp \ -H "Accept: application/x-ndjson" \ -d '{"...":"sync_pull","arguments":{"initial":true}}' ``` 각 줄이 한 파일. ## 에러 | 코드 | 메시지 | |------|-------| | -32005 | path not found | | -32602 | invalid since format | ## Claude ``` 어제 이후 변경된 거 다 받아 ``` ```bash ainote sync_pull '{"since":"2026-05-06T00:00:00Z"}' ``` ## 다음 - [`sync_push`](/reference/sync-push) - [`sync_list`](/reference/sync-list) - [sync-now 알고리즘](/sync/algorithm) --- # sync_push ::: warning 🚧 설계 단계 — 아직 구현 안 됨 이 페이지는 ainote 의 향후 기능을 미리 문서화한 것입니다. 현재 `@ainote/mcp` v1.1.x 에는 **vault / sync 도구가 포함돼 있지 않습니다**. 도구 호출 시 `Tool not found` 에러를 받게 됩니다. ::: 로컬 파일 → ainote hub. [sync 시스템](/sync/overview) 의 일부. ## 시그니처 ```json { "name": "sync_push", "arguments": { "path": "global/CLAUDE.md", "content": "", "sha256": "abc123...", "hlc": "2026-05-07T14:01:00.000Z.0.macmini", "device_id": "macmini-2026-04-A1B2" } } ``` | 파라미터 | 타입 | 필수 | 설명 | |---------|------|------|------| | `path` | string | ✅ | ainote vault 내 경로 | | `content` | text | ✅ | 파일 본문 | | `sha256` | string | ✅ | content 의 SHA-256 (서버 검증) | | `hlc` | string | ✅ | Hybrid Logical Clock | | `device_id` | string | ✅ | 송신 디바이스 식별 | | `mtime` | ISO 8601 | | 로컬 mtime (충돌 임계 비교용) | ## 응답 ```json { "path": "global/CLAUDE.md", "git_sha": "def456...", "hlc": "2026-05-07T14:01:00.000Z.0.macmini", "previous_hlc": "2026-05-06T...", "size_bytes": 12345, "stored_at": "2026-05-07T14:01:01Z" } ``` ## 충돌 서버가 더 새로운 HLC 가지고 있으면: ```json { "error": { "code": -32006, "message": "Conflict: server has newer HLC", "data": { "your_hlc": "2026-05-07T14:01:00.0.macmini", "server_hlc": "2026-05-07T14:02:30.0.macbook", "time_diff_seconds": 90 } } } ``` → 클라이언트가 `sync-now.sh` 알고리즘에 따라 처리 ([conflicts](/sync/conflicts)). ## sha256 검증 서버가 받은 content 의 sha256 을 계산해서 클라이언트가 보낸 것과 비교. 다르면: ```json { "error": { "code": -32602, "message": "sha256 mismatch" } } ``` → 전송 중 손상 가능성 → 재시도. ## 에러 | 코드 | 메시지 | |------|-------| | -32602 | invalid path | | -32602 | sha256 mismatch | | -32006 | conflict | | -32004 | quota exceeded (5 GB) | ## 다음 - [`sync_pull`](/reference/sync-pull) - [`sync_list`](/reference/sync-list) - [sync-now 알고리즘](/sync/algorithm) --- # update_dev_doc 기존 dev_doc 수정. ## 시그니처 ```json { "name": "update_dev_doc", "arguments": { "title": "launchcrew-claude.md", "content": "", "mode": "replace" } } ``` ## 파라미터 | 파라미터 | 타입 | 필수 | 설명 | |---------|------|------|------| | `content` | string | ✅ | 새 본문 (mode 따라 처리) | | `title` | string | △ | title 또는 id 둘 중 하나 필요 | | `id` | string | △ | 문서 UUID | | `category` | string | | 동명 문서 disambiguation | | `mode` | string | | `replace` (기본) / `append` / `prepend` | | `local_path` | string | | 로컬 경로 갱신 | ## mode 비교 | mode | 동작 | 권장 | |------|------|------| | `replace` | 본문 전체 덮어씀 | ✅ 일반 | | `append` | 끝에 추가 | 메모 누적 | | `prepend` | 앞에 추가 | 긴급 공지 | `append`/`prepend` 는 로컬 파일과 drift 위험 → 가능하면 `replace`. ## 응답 ```json { "content": [ { "type": "text", "text": "✅ Updated: launchcrew-claude.md (mode: replace)" } ] } ``` ## 에러 | 코드 | 메시지 | |------|-------| | -32602 | `content is required` | | -32602 | `title or id is required` | | -32603 | `Document not found` | ## Claude ``` launchcrew-claude.md 를 새 내용으로 교체 launchcrew-claude.md 끝에 "추가 메모" 붙여 ``` ## 다음 - [`create_dev_doc`](/reference/create-dev-doc) - [`delete_dev_doc`](/reference/delete-dev-doc) --- # update_task 기존 태스크 수정. `id` 필수, 변경할 필드만 지정. ## 시그니처 ```json { "name": "update_task", "arguments": { "id": "uuid-here", "completed_at": "2026-05-08T10:35:00+09:00" } } ``` ## 파라미터 | 파라미터 | 타입 | 설명 | |---------|------|------| | `id` | string | ✅ 태스크 ID | | `completed_at` | string (ISO) / null | 완료 처리 (ISO 시각) 또는 `null` 로 미완료 복귀 | | `content` | string | 내용 변경 | | `is_important` | boolean | 중요 토글 | | `due_date` | string (ISO) | 마감 변경 | | `due_time` | string `HH:MM` | | | `start_date` | string (ISO) | | | `is_all_day` | boolean | | | `category_id` | string (UUID) | | | `notes` | string | | | `location` / `location_lat` / `location_lng` | | | | `travel_time` | number | | | `repeat_rule` | string | | | `notification_minutes_before` | number / null | `null` 전달 시 기존 알림 제거 | 지정하지 않은 필드는 그대로 유지 (PATCH 방식). ## 완료 처리 패턴 ```json { "id": "uuid", "completed_at": "2026-05-08T10:35:00+09:00" } ``` 미완료 복귀: ```json { "id": "uuid", "completed_at": null } ``` ## 응답 ```json { "content": [ { "type": "text", "text": "✅ 태스크 업데이트됨" } ] } ``` ## 에러 | 코드 | 메시지 | |------|-------| | -32602 | `id is required` | | -32602 | `Task not found` | | -32602 | `Invalid completed_at format` | ## Claude ``` 태스크 #uuid 완료 처리해줘 태스크 #uuid 마감 내일 오후 3시로 변경 ``` ## 다음 - [`create_task`](/reference/create-task) - [`delete_task`](/reference/delete-task) - [태스크 개요](/tasks/overview) --- # vault_clone ::: warning 🚧 설계 단계 — 아직 구현 안 됨 이 페이지는 ainote 의 향후 기능을 미리 문서화한 것입니다. 현재 `@ainote/mcp` v1.1.x 에는 **vault / sync 도구가 포함돼 있지 않습니다**. 도구 호출 시 `Tool not found` 에러를 받게 됩니다. ::: vault 를 로컬에 git clone. 자세한 가이드: [vault/clone](/vault/clone). ## 시그니처 ### 모드 A — ainote vault → 로컬 ```json { "name": "vault_clone", "arguments": { "name": "personal", "destination": "/Users/seunghan/notes/personal" } } ``` ### 모드 B — 외부 git → 새 vault ```json { "name": "vault_clone", "arguments": { "name": "obsidian-backup", "git_remote": "git@github.com:me/obsidian.git", "destination": "/Users/seunghan/notes/obsidian" } } ``` | 파라미터 | 타입 | 설명 | |---------|------|------| | `name` | string | ✅ vault 이름 | | `destination` | string | ✅ 로컬 경로 (절대) | | `git_remote` | string | (모드 B) 외부 git URL | | `force` | boolean | destination 덮어쓰기 | ## 응답 ```json { "name": "personal", "destination": "/Users/seunghan/notes/personal", "file_count": 123, "size_bytes": 47000000, "last_commit": { "sha": "abc123", "ts": "2026-05-07T14:32:00Z", "device": "macmini-2026-04", "message": "sync from macmini" } } ``` ## 에러 | 코드 | 메시지 | |------|-------| | -32602 | vault not found | | -32602 | destination not empty | | -32603 | git auth failed | ## Claude ``` personal vault 를 ~/notes/personal 에 clone ~/Documents/Obsidian/MyVault 를 새 ainote vault 로 등록 ``` ## 다음 - [`vault_sync`](/reference/vault-sync) - [Obsidian 마이그레이션](/vault/clone) --- # vault_connect_status ::: warning 🚧 설계 단계 — 아직 구현 안 됨 이 페이지는 ainote 의 향후 기능을 미리 문서화한 것입니다. 현재 `@ainote/mcp` v1.1.x 에는 **vault / sync 도구가 포함돼 있지 않습니다**. 도구 호출 시 `Tool not found` 에러를 받게 됩니다. ::: vault 의 git 연결 상태 진단. ## 시그니처 ```json { "name": "vault_connect_status", "arguments": { "name": "personal" } } ``` ## 응답 ```json { "name": "personal", "exists_remote": true, "exists_local": true, "local_path": "/Users/seunghan/notes/personal", "git_status": { "uncommitted_changes": 3, "ahead": 1, "behind": 0, "branch": "main", "remote_url_correct": true }, "auth": { "method": "https-mcp-key", "valid": true }, "issues": [] } ``` `issues` 가 있으면 진단 메시지: ```json { "issues": [ "uncommitted changes — run vault_sync with auto_commit", "behind remote by 5 commits — sync needed" ] } ``` ## 사용 배포 전 / sync 전 헬스체크: ``` personal vault 상태 보여줘 ``` ## 다음 - [`vault_sync`](/reference/vault-sync) - [Troubleshooting](/guide/troubleshooting) --- # vault_create ::: warning 🚧 설계 단계 — 아직 구현 안 됨 이 페이지는 ainote 의 향후 기능을 미리 문서화한 것입니다. 현재 `@ainote/mcp` v1.1.x 에는 **vault / sync 도구가 포함돼 있지 않습니다**. 도구 호출 시 `Tool not found` 에러를 받게 됩니다. ::: 새 vault 생성. 자세한 가이드: [vault/create](/vault/create). ## 시그니처 ```json { "name": "vault_create", "arguments": { "name": "personal", "description": "개인 노트", "private": true, "init_files": ["README.md"] } } ``` ## 응답 ```json { "id": 42, "name": "personal", "git_url": "https://api.ainote.dev/vaults/me/personal.git", "clone_command": "git clone https://api.ainote.dev/vaults/me/personal.git", "web_url": "https://ainote.dev/vaults/personal", "size_bytes": 0, "file_count": 1 } ``` ## 에러 | 코드 | 메시지 | |------|-------| | -32602 | invalid name | | -32603 | name already exists | | -32004 | quota exceeded (vault 100개) | ## Claude ``` ainote 에 "personal" vault 만들어줘 ``` ## 다음 - [`vault_clone`](/reference/vault-clone) - [Vault 개요](/vault/overview) --- # vault_list ::: warning 🚧 설계 단계 — 아직 구현 안 됨 이 페이지는 ainote 의 향후 기능을 미리 문서화한 것입니다. 현재 `@ainote/mcp` v1.1.x 에는 **vault / sync 도구가 포함돼 있지 않습니다**. 도구 호출 시 `Tool not found` 에러를 받게 됩니다. ::: 사용자의 모든 vault 목록. ## 시그니처 ```json { "name": "vault_list", "arguments": {} } ``` 옵션: ```json { "include_size": true, "include_status": true } ``` ## 응답 ```json [ { "id": 42, "name": "personal", "description": "개인 노트", "private": true, "git_url": "https://api.ainote.dev/vaults/me/personal.git", "file_count": 123, "size_bytes": 47000000, "last_synced_at": "2026-05-07T14:32:00Z", "local_clone": "/Users/seunghan/notes/personal" }, { "id": 43, "name": "work", "private": true, "file_count": 56, "size_bytes": 2300000 } ] ``` `local_clone` 은 이 디바이스에 clone 된 경로 (있으면). ## Claude ``` 내 vault 다 보여줘 ``` ## 다음 - [`vault_sync`](/reference/vault-sync) - [Vault 개요](/vault/overview) --- # vault_sync ::: warning 🚧 설계 단계 — 아직 구현 안 됨 이 페이지는 ainote 의 향후 기능을 미리 문서화한 것입니다. 현재 `@ainote/mcp` v1.1.x 에는 **vault / sync 도구가 포함돼 있지 않습니다**. 도구 호출 시 `Tool not found` 에러를 받게 됩니다. ::: vault 양방향 동기화. 자세한 가이드: [vault/sync](/vault/sync). ## 시그니처 ```json { "name": "vault_sync", "arguments": { "name": "personal", "auto_commit": true, "commit_message": "sync from macbook", "strategy": "merge" } } ``` | 파라미터 | 타입 | 기본 | 설명 | |---------|------|------|------| | `name` | string | — | ✅ vault 이름 | | `auto_commit` | boolean | true | 미커밋 변경 자동 commit | | `commit_message` | string | `"sync from "` | | | `strategy` | `merge` / `rebase` / `theirs` / `ours` | `merge` | 충돌 시 | ## 응답 ```json { "vault": "personal", "pulled": 3, "pushed": 1, "files_changed": [ { "path": "daily/2026-05-07.md", "action": "added" }, { "path": "projects/ainote.md", "action": "modified" } ], "conflicts": [] } ``` ## 충돌 시 ```json { "conflicts": [ { "path": "ideas.md", "marker_lines": [12, 24] } ] } ``` 수동 해결 후 재실행: ```bash cd ~/notes/personal $EDITOR ideas.md git add . && git commit ainote vault_sync '{"name":"personal"}' ``` ## 에러 | 코드 | 메시지 | |------|-------| | -32603 | merge conflict | | -32603 | local repo missing | → vault_clone 먼저 | ## Claude ``` personal vault 동기화 모든 vault sync ``` ## 다음 - [`vault_list`](/reference/vault-list) - [Vault sync 자세히](/vault/sync) --- # sync-now 알고리즘 ::: warning 🚧 설계 단계 — 아직 구현 안 됨 이 페이지는 ainote 의 향후 기능을 미리 문서화한 것입니다. 현재 `@ainote/mcp` v1.1.x 에는 **vault / sync 도구가 포함돼 있지 않습니다**. 도구 호출 시 `Tool not found` 에러를 받게 됩니다. ::: `~/.claude/ainote-sync/tools/sync-now.sh` 의 의사코드. ## 한 사이클 ```python def sync_now(dry_run=False): manifest = load("manifest.yml") state = load("state.json") log = open("log/{YYYY-MM}.jsonl", "a") # 1. 로컬 스캔 local_files = expand(manifest.sync) # glob 풀어서 (source, target) pair 리스트 # 2. 원격 list remote_files = mcp_call("sync_list", {}) # hub 에서 manifest 매칭 파일들 # 3. 양쪽 sha256 + mtime + hlc 수집 pairs = match_pairs(local_files, remote_files) # 4. 각 페어별 케이스 결정 actions = [] for p in pairs: case = decide_case(p, threshold_minutes=5) actions.append((p, case)) # 5. Safety guard delete_count = sum(1 for _, c in actions if c == "delete") if delete_count / len(actions) > manifest.safety.max_delete_pct / 100: abort("max_delete_pct exceeded") # 6. dry-run 모드 if dry_run: print_actions(actions) return # 7. 실행 for p, case in actions: match case: case "no-op": pass case "push": push_to_hub(p) case "pull": pull_from_hub(p) case "conflict_save_both": save_conflict_files(p) ABORT_PROMPT(f"수동 해결: {p.target}") case "lww_remote_wins": pull_from_hub(p) case "lww_local_wins": push_to_hub(p) case "delete_local": delete_local(p) case "delete_remote": delete_remote(p) log.write(json.dumps({ "ts": now_iso(), "device": DEVICE_ID, "path": p.target, "action": case, "local_sha": p.local_sha, "remote_sha": p.remote_sha, "hlc": p.new_hlc, }) + "\n") update_state(state, p, case) save(state) ``` ## decide_case ```python def decide_case(pair, threshold_minutes): local_changed = (pair.local_sha != state[pair.target].local_sha) remote_changed = (pair.remote_hlc > state[pair.target].hlc) if not local_changed and not remote_changed: return "no-op" if local_changed and not remote_changed: if not pair.local_exists: return "delete_remote" return "push" if not local_changed and remote_changed: if not pair.remote_exists: return "delete_local" return "pull" # 양쪽 변경 time_diff = abs(pair.local_mtime - pair.remote_mtime) if time_diff <= timedelta(minutes=threshold_minutes): return "conflict_save_both" if pair.local_hlc > pair.remote_hlc: return "lww_local_wins" return "lww_remote_wins" ``` ## sub-procedures ### push_to_hub ```python def push_to_hub(p): new_hlc = compute_hlc(now(), state[p.target].hlc, DEVICE_ID) mcp_call("sync_push", { "path": p.target, "content": read_file(p.source), "sha256": p.local_sha, "hlc": new_hlc, "device_id": DEVICE_ID, }) p.new_hlc = new_hlc ``` ### pull_from_hub ```python def pull_from_hub(p): res = mcp_call("sync_pull", {"path": p.target}) write_file(p.source, res.content) set_mtime(p.source, res.remote_mtime) p.new_hlc = res.hlc ``` ### save_conflict_files ```python def save_conflict_files(p): ts = now().strftime("%Y-%m-%dT%H-%M-%S") safe_path = p.target.replace("/", "__") out = f"~/.claude/ainote-sync/conflicts/{ts}__{safe_path}.diff" write(out, format_diff( local=read_file(p.source), local_hlc=p.local_hlc, remote=mcp_call("sync_pull", {"path": p.target}).content, remote_hlc=p.remote_hlc, )) ``` ## 트리거 ### 수동 ```bash ~/.claude/ainote-sync/tools/sync-now.sh ~/.claude/ainote-sync/tools/sync-now.sh --dry-run ``` ### Cron (15분마다) ```bash */15 * * * * /Users/seunghan/.claude/ainote-sync/tools/sync-now.sh >> /tmp/ainote-sync.log 2>&1 ``` ### launchd (macOS, 더 안정적) [vault_sync 와 동일 패턴](/vault/sync) — `.plist` 에 `StartInterval: 900`. ### File watcher (실시간) ```bash fswatch ~/CLAUDE.md ~/.claude/projects/ | xargs -I{} sync-now.sh ``` ## audit (주간) ```bash ~/.claude/ainote-sync/tools/audit.sh ``` 체크: - manifest 의 source 가 모두 존재 - state.json 과 hub 의 file 목록 차이 - conflicts/ 의 미해결 파일 - 최근 7일 변경 통계 (디바이스별) ## 다음 - [충돌 해결](/sync/conflicts) - [HLC 시간 모델](/sync/hlc) - [manifest.yml](/sync/manifest) --- # 아키텍처 — Star Topology ::: warning 🚧 설계 단계 — 아직 구현 안 됨 이 페이지는 ainote 의 향후 기능을 미리 문서화한 것입니다. 현재 `@ainote/mcp` v1.1.x 에는 **vault / sync 도구가 포함돼 있지 않습니다**. 도구 호출 시 `Tool not found` 에러를 받게 됩니다. ::: ainote sync 는 **hub-and-spoke** (별 모양). P2P (디바이스끼리 직접) 안 함. ## 그림 ``` ┌────────────────────┐ │ ainote hub │ │ api.ainote.dev │ │ (PostgreSQL+Git) │ └─────────┬──────────┘ │ ┌──────────────┬───────┼───────┬──────────────┐ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ [macmini] [macbook] [iOS] [iPad] [linux desktop] spoke 1 spoke 2 s.3 s.4 s.5 ``` 각 spoke 는 hub 와만 sync. spoke ↔ spoke 직접 X. ## 왜 P2P 가 아닌가 [rclone bisync 포럼 컨센서스](https://forum.rclone.org/t/bisync-many-devices/30043): > "If you are bisyncing more than two devices, set them up in a **star topology** — each spoke synced with the same hub consistently. **Never device-to-device**." 이유: - N 개 디바이스 → P2P 페어 N(N-1)/2 (3개 → 3, 5개 → 10, 10개 → 45) - 모든 페어가 충돌 가능 → 디버깅 불가능 - Star: N 개 → N 페어. 모든 충돌이 hub 에서 한 곳에서 ## hub 의 역할 ``` ┌────────────────────────────────────────────────┐ │ ainote hub │ ├────────────────────────────────────────────────┤ │ 1. 모든 변경 (commit + HLC) 단일 진실 저장 │ │ 2. spoke 의 last_known_state 추적 │ │ 3. 충돌 감지 (5분 임계) │ │ 4. 모든 spoke 에 push 알림 (FCM/APNs/WebSocket) │ │ 5. 부분 sync — manifest 기준 필터 │ │ 6. WORM audit log (변경 이력 영구) │ └────────────────────────────────────────────────┘ ``` ## spoke 의 역할 ``` ┌────────────────────────────────────────────────┐ │ spoke (디바이스) │ ├────────────────────────────────────────────────┤ │ 1. 로컬 manifest.yml — 어떤 파일을 어디로 │ │ 2. state.json — 마지막 sync 상태 │ │ 3. sync-now.sh — 수동 sync 트리거 │ │ 4. conflicts/ — 5분 임계 깨면 양쪽 보존 │ │ 5. log/*.jsonl — 월별 이벤트 로그 (append-only) │ └────────────────────────────────────────────────┘ ``` 자세히: [manifest.yml 작성](/sync/manifest), [sync-now 알고리즘](/sync/algorithm). ## 한 사이클 ``` [spoke A 변경] [hub] [spoke B] │ │ │ │ sync_push │ │ ├───────────────────►│ │ │ │ WebSocket notify │ │ ├─────────────────────►│ │ │ │ │ │ sync_pull │ │ │◄─────────────────────┤ │ │ │ │ │ 변경 전송 │ │ ├─────────────────────►│ ``` 평균 지연: 1초 (WebSocket) ~ 30초 (cron). ## 페일오버 hub 다운 시: - spoke 는 로컬 작업 계속 - 변경은 로컬 log/jsonl 에 누적 - hub 복구 시 일괄 push hub 데이터 손실 시: - 모든 spoke 의 git working tree 가 백업 - spoke 1대를 새 hub 으로 promote 가능 ## 새 디바이스 추가 ``` 1. 새 기기에 manifest.yml + device.id 생성 2. ainote sync_pull --initial 3. hub 의 모든 sync 항목 다운로드 4. state.json 초기화 5. 정상 sync 사이클 시작 ``` 자세히: [새 디바이스 셋업](/examples/new-device). ## 다음 - [HLC 시간 모델](/sync/hlc) - [충돌 해결](/sync/conflicts) - [manifest.yml 작성](/sync/manifest) --- # 충돌 해결 (LWW + 5분 임계) ::: warning 🚧 설계 단계 — 아직 구현 안 됨 이 페이지는 ainote 의 향후 기능을 미리 문서화한 것입니다. 현재 `@ainote/mcp` v1.1.x 에는 **vault / sync 도구가 포함돼 있지 않습니다**. 도구 호출 시 `Tool not found` 에러를 받게 됩니다. ::: > 핵심: **5분 안에 양쪽 변경 → 양쪽 보존 + 사용자 선택. 5분 이후 → LWW.** ## 4가지 케이스 | 케이스 | 로컬 변경 | 원격 변경 | 처리 | |--------|----------|----------|------| | **A** | ❌ | ❌ | no-op | | **B** | ✅ | ❌ | push | | **C** | ❌ | ✅ | pull | | **D** | ✅ | ✅ | conflict (아래 참고) | ## 케이스 D 세분화 ``` 시간 차 = |로컬 mtime - 원격 mtime| if 시간 차 ≤ 5분: → 양쪽 보존 (conflict 디렉토리) → 사용자가 수동 머지 else: → LWW (HLC 기준 더 큰 쪽 승) ``` ### D-1: 시간 차 ≤ 5분 → 사용자 선택 양쪽이 거의 동시에 변경 → 의도적 동시 작업 가능 → 자동 결정 위험. 처리: ```bash ~/.claude/ainote-sync/conflicts/ └── 2026-05-07T14-30-00__global__CLAUDE.md.diff ``` `.diff` 파일 형식: ```diff === LOCAL (macmini, 2026-05-07T14:28:00, hlc 2026-05-07T14:28:00.0.macmini) new line from macmini === REMOTE (macbook, 2026-05-07T14:30:00, hlc 2026-05-07T14:30:00.0.macbook) new line from macbook ``` 다음 sync 가 막힘. 사용자가 해결: ```bash ainote sync_resolve global/CLAUDE.md \ --keep local # 또는 --keep remote # 또는 --merge /tmp/manual.md # 수동 머지 결과 파일 ``` 해결 후 다음 sync 진행. ### D-2: 시간 차 > 5분 → LWW 오래된 변경은 stale 가능성 높음 → 자동 결정. ``` HLC 비교: local (HLC 2026-05-07T14:00:00.0.macmini) remote (HLC 2026-05-07T15:30:00.0.macbook) → remote 승 → 로컬 덮어씀 ``` 로컬 버전은 git history 에 남음 → 복구 가능. ## Safety guard `sync-now.sh` 에 안전장치: ```yaml # manifest.yml safety: max_delete_pct: 10 # 10% 이상 파일 삭제 시 abort max_overwrite_files: 5 # 한번에 5파일 이상 덮어쓰기 시 confirm dry_run_default: false ``` `max_delete_pct` 초과 시: ``` ⚠️ ABORT: 53 파일 중 12 파일 삭제 예정 (22%) 설정의 max_delete_pct: 10 초과 실수 의심 — 다음 명령으로 강제: ainote sync_now --override-safety --max-delete-pct=25 ``` ## 충돌 디렉토리 정리 ```bash # 미해결 충돌 목록 ls -lt ~/.claude/ainote-sync/conflicts/ # 7일 지난 해결된 것 자동 정리 (계획됨) ainote sync_cleanup_conflicts --older-than 7d ``` ## Git 으로 해결 (vault) vault 의 경우 일반 git 3-way merge: ```bash cd ~/notes/personal git status # conflict 파일 확인 # editor 로 <<<<<<< 마커 해결 git add . git commit ainote vault_sync personal # 다시 시도 ``` vault 는 git 표준이므로 5분 임계 안 적용. ## 메모리 / dev_doc 의 경우 API 호출 단위라 충돌 감지 다름: - 양쪽이 거의 동시 `update_dev_doc` → HLC 비교 - 5분 임계 안 함 — 항상 LWW (단순) - git history 에 두 버전 모두 보존 ## 다음 - [HLC 시간 모델](/sync/hlc) - [sync-now 알고리즘](/sync/algorithm) - [manifest.yml 작성](/sync/manifest) --- # 폴더 구조 권장 ::: warning 🚧 설계 단계 — 아직 구현 안 됨 이 페이지는 ainote 의 향후 기능을 미리 문서화한 것입니다. 현재 `@ainote/mcp` v1.1.x 에는 **vault / sync 도구가 포함돼 있지 않습니다**. 도구 호출 시 `Tool not found` 에러를 받게 됩니다. ::: ainote vault 안의 디렉토리 구조 베스트 프랙티스. ## 표준 구조 ``` ainote vault (hub git repo) ├── global/ # 디바이스 무관 전역 │ ├── CLAUDE.md # ~/CLAUDE.md │ ├── PERSONAS.md # ~/.claude/PERSONAS.md │ ├── ORCHESTRATOR.md │ ├── memory/ # 토픽 메모리 │ │ ├── MEMORY.md │ │ ├── projects-tech-stack.md │ │ └── render-services.md │ └── skills/ # Claude Code 스킬 │ └── seunghan-32inch-ppt/ │ ├── SKILL.md │ └── assets/ │ ├── projects/ # 프로젝트별 (대안: 각 root) │ ├── launchcrew/ │ │ ├── CLAUDE.md │ │ ├── memory/ │ │ │ ├── MEMORY.md │ │ │ └── firebase.md │ │ └── env-ref.md │ ├── tennis-bracket/ │ ├── keeps/ │ └── ... │ ├── dev/ # 도구별 룰 (자동 분류) │ ├── claude/ # category: claude │ ├── cursor/ # category: cursor │ ├── windsurf/ │ ├── copilot/ │ └── docs/ │ ├── env-refs/ # .env 키 목록 (값 X) │ ├── tennis-bracket-env-ref.md │ └── launchcrew-env-ref.md │ └── private/ # 민감 (별도 권한) └── saju/ └── personal-notes.md ``` ## 두 가지 스타일 ### 스타일 A: 카테고리 우선 (`dev/claude/`) 장점: - ainote 자동 분류와 일치 - 같은 종류 한눈에 (모든 CLAUDE.md) - `pull_dev_docs --category claude` 한 방 단점: - 프로젝트별 보기 어려움 - 같은 프로젝트 파일이 여러 디렉토리 흩어짐 ### 스타일 B: 프로젝트 우선 (`projects/launchcrew/`) 장점: - 한 프로젝트 모든 파일 한 폴더 - 새 프로젝트 추가 = 폴더 추가 - Obsidian 에서 보기 좋음 단점: - 자동 분류 안 됨 (수동 manifest) - "모든 CLAUDE.md" 한방에 보기 어려움 ### 권장: 혼합 ``` ainote vault ├── global/ # 전역 → 카테고리 우선 (단순) ├── projects/ # 프로젝트 → 프로젝트 우선 │ └── {project}/ │ ├── CLAUDE.md │ └── memory/ └── dev/ # 자동 분류 카테고리 (Dev Docs) ├── cursor/ # ainote 자동 └── windsurf/ ``` ## 명명 규칙 ### 프로젝트 - **kebab-case** 통일 (`tennis-bracket`, `krx-listing`) - snake → kebab 변환 (`tennis_bracket` → `tennis-bracket`) - 약어 풀어쓰기 (`ai-do` 보다 `ai-do-vibestudy` 처럼 명확하게) ### 파일 | 패턴 | 예시 | |------|------| | `{project}-claude.md` | `launchcrew-claude.md` | | `{project}-memory.md` | `launchcrew-memory.md` | | `{project}-{topic}.md` | `launchcrew-firebase.md` | | `global-{topic}.md` | `global-tech-stack.md` | | `{project}-env-ref.md` | `tennis-bracket-env-ref.md` | ### 메모리 파일 (4 type) - `feedback-{topic}.md` - `project-{topic}.md` - `reference-{topic}.md` - `user-{topic}.md` ## 안 좋은 예 ``` ❌ ainote vault ├── My Notes/ ← 공백 ├── claude_md_files/ ← snake ├── 2026-temp-stuff/ ← 시간 prefix (검색 어려움) ├── important-do-not-delete-final-v2/ ← 의미 없는 prefix └── 한국어 폴더명/ ← 가능하지만 일관성 떨어짐 ``` ✅ 영문 kebab-case, 의미 명확, 깊이 3단 이내. ## 깊이 권장 ``` vault/ ├── A/ ← 1단계 │ ├── B/ ← 2단계 │ │ └── file.md ← 3단계 OK │ │ └── C/ ← 4단계 비추 (검색/관리 어려움) ``` ## 마이그레이션 기존 구조 → 표준 구조: ```bash # 1. 백업 git tag pre-migration-2026-05-07 # 2. 이동 git mv messy-folder global/ # 3. manifest 갱신 $EDITOR ~/.claude/ainote-sync/manifest.yml # 4. 검증 ainote sync_validate # 5. dry-run sync-now.sh --dry-run # 6. 실행 sync-now.sh ``` 자세히: [ainote-sync-redesign 설계 문서](https://github.com/seunghan91/ainote/blob/main/docs/sync-redesign.md). ## 다음 - [manifest.yml 작성](/sync/manifest) - [sync-now 알고리즘](/sync/algorithm) - [메모리 카테고리 / 네이밍](/memory/categories) --- # HLC 시간 모델 ::: warning 🚧 설계 단계 — 아직 구현 안 됨 이 페이지는 ainote 의 향후 기능을 미리 문서화한 것입니다. 현재 `@ainote/mcp` v1.1.x 에는 **vault / sync 도구가 포함돼 있지 않습니다**. 도구 호출 시 `Tool not found` 에러를 받게 됩니다. ::: **Hybrid Logical Clock** — wall clock + counter 조합. 시계가 어긋나도 인과 순서 보장. ## 왜 wall clock 만은 안 되나 문제 시나리오: ``` 맥미니 시계: 2026-05-07 14:00 (정상) 맥북 시계: 2026-05-08 14:00 (24시간 빠름, NTP 미동기화) 14:00 (맥미니): CLAUDE.md 에 줄 추가 → push 14:01 (맥북, 실제 14:01 이지만 시계는 +1일): 같은 파일 다른 줄 추가 → push (timestamp = 2026-05-08 14:01) LWW 적용: 맥미니 변경 (timestamp 2026-05-07) < 맥북 변경 (timestamp 2026-05-08) → 맥북 변경이 이김 → 맥미니의 변경 24시간 후 push 해도 무시됨 ``` ## HLC 알고리즘 각 spoke 가 `(timestamp, counter, device_id)` 트리플 유지. ### 로컬 이벤트 (write) ```python def event_local(now_wall_clock, last_hlc): if now_wall_clock > last_hlc.ts: return HLC(ts=now_wall_clock, counter=0, device=DEVICE_ID) else: return HLC(ts=last_hlc.ts, counter=last_hlc.counter+1, device=DEVICE_ID) ``` ### 원격 이벤트 받음 (pull) ```python def event_received(now_wall_clock, last_hlc, remote_hlc): new_ts = max(now_wall_clock, last_hlc.ts, remote_hlc.ts) if new_ts == last_hlc.ts == remote_hlc.ts: new_counter = max(last_hlc.counter, remote_hlc.counter) + 1 elif new_ts == last_hlc.ts: new_counter = last_hlc.counter + 1 elif new_ts == remote_hlc.ts: new_counter = remote_hlc.counter + 1 else: new_counter = 0 return HLC(ts=new_ts, counter=new_counter, device=DEVICE_ID) ``` ## 핵심 속성 1. **Monotonicity**: 같은 spoke 의 다음 HLC 는 항상 이전보다 큼 2. **Causality**: A 가 B 의 원인이면 HLC(A) < HLC(B) 3. **Bounded drift**: 정상 NTP 환경에서 wall clock 과 거의 같음 4. **Tie-breaker**: 같은 timestamp 면 counter, 같은 counter 면 device_id ## 비교 함수 ```python def hlc_compare(a, b): if a.ts != b.ts: return a.ts - b.ts if a.counter != b.counter: return a.counter - b.counter return cmp(a.device, b.device) # 알파벳 순 ``` ## 우리 시나리오 재현 ``` HLC(맥미니, 2026-05-07 14:00, c=0, dev="macmini") ← 변경 push HLC(맥북, ?, ?, dev="macbook"): - 맥북 시계 2026-05-08 14:01 - 마지막 본 hub HLC: (맥미니의 hlc above) - new_ts = max(맥북 wall, 마지막 본) = 2026-05-08 14:01 - HLC(2026-05-08 14:01, c=0, dev="macbook") push 비교: 맥북 (2026-05-08 14:01) > 맥미니 (2026-05-07 14:00) → 맥북 이김 → 같은 결과처럼 보임 ``` 문제 같지만 — **이번엔 맥미니가 추가 변경 시**: ``` 맥미니 다시 작업: - wall = 2026-05-07 14:30 - 마지막 본 hub HLC (맥북) = 2026-05-08 14:01 - new_ts = max(14:30, 2026-05-08 14:01) = 2026-05-08 14:01 - counter += 1 - HLC(2026-05-08 14:01, c=1, dev="macmini") 맥북 (2026-05-08 14:01, c=0) < 맥미니 (2026-05-08 14:01, c=1) → 맥미니 이김 ✅ ``` 즉 — 시계 어긋나도 **연속 변경의 인과 순서는 보존**. ## 우리 시스템에서의 위치 ``` state.json (각 spoke) { "files": { "global/CLAUDE.md": { "hlc": "2026-05-08T14:01:00.000Z.1.macmini", ... } } } ``` `hlc` 필드: `{ISO timestamp}.{counter}.{device_id}`. hub 도 같은 형식 저장. push 시 비교. ## 더 읽기 - Adam Wulf, ["Distributed Clocks and CRDTs"](https://adamwulf.me/2021/05/distributed-clocks-and-crdts/) (2021) - Jared Forsyth, ["Hybrid Logical Clocks"](https://jaredforsyth.com/posts/hybrid-logical-clocks/) - 원 논문: Kulkarni et al., ["Logical Physical Clocks"](https://cse.buffalo.edu/tech-reports/2014-04.pdf) (2014) ## 다음 - [충돌 해결 (5분 임계)](/sync/conflicts) - [sync-now 알고리즘](/sync/algorithm) --- # manifest.yml 작성 ::: warning 🚧 설계 단계 — 아직 구현 안 됨 이 페이지는 ainote 의 향후 기능을 미리 문서화한 것입니다. 현재 `@ainote/mcp` v1.1.x 에는 **vault / sync 도구가 포함돼 있지 않습니다**. 도구 호출 시 `Tool not found` 에러를 받게 됩니다. ::: 각 디바이스의 sync 매핑 정책. 로컬 파일 ↔ ainote path 명시적 정의. ## 위치 ``` ~/.claude/ainote-sync/manifest.yml ``` 같은 디렉토리에 `device.id` (UUID) + `state.json` (last-known sync 상태). ## 최소 예시 ```yaml device_id: macmini-2026-04-A1B2 hub: api: https://api.ainote.dev/api/mcp auth_env: AINOTE_API_KEY sync: - source: ~/CLAUDE.md target: global/CLAUDE.md ``` ## 전체 예시 ```yaml device_id: macmini-2026-04-A1B2 hub: api: https://api.ainote.dev/api/mcp auth_env: AINOTE_API_KEY sync: # 글로벌 메모리 — glob (모든 .md) - source: ~/.claude/projects/-Users-seunghan/memory/ pattern: "*.md" target: global/memory/{basename} type: glob # 1:1 명시 - source: ~/CLAUDE.md target: global/CLAUDE.md - source: ~/.claude/PERSONAS.md target: global/PERSONAS.md # 디렉토리 통째 (재귀) - source: ~/.claude/skills/seunghan-32inch-ppt/ pattern: "**/*" target: global/skills/seunghan-32inch-ppt/{relpath} type: glob_recursive # 프로젝트별 메모리 (모든 프로젝트) - source: ~/.claude/projects/{project}/memory/ pattern: "*.md" target: "{project_name}/{basename}" type: glob_per_project # 프로젝트 root CLAUDE.md (화이트리스트) - source: ~/launchcrew/CLAUDE.md target: launchcrew/CLAUDE.md - source: ~/tennis_bracket/CLAUDE.md target: tennis-bracket/CLAUDE.md # snake → kebab # 명시적 제외 skip: - ~/.claude/COMMANDS.md - ~/.claude/FLAGS.md - ~/.claude/RULES.md - ~/.claude/MCP.md # 원격에 있지만 더 이상 받지 않을 (deprecated) deprecated_remote: - krx_listing_backups/ - krx_listing/ # snake (kebab 으로 마이그) safety: max_delete_pct: 10 max_overwrite_files: 5 dry_run_default: false ``` ## sync 항목 형식 ### 단순 1:1 ```yaml - source: ~/file.md target: ainote/path.md ``` ### Glob (디렉토리 + 패턴) ```yaml - source: ~/dir/ pattern: "*.md" target: dest/{basename} type: glob ``` placeholder: - `{basename}` — 파일명만 (`MEMORY.md`) - `{filename}` — 확장자 제외 (`MEMORY`) - `{relpath}` — source 기준 상대경로 ### 재귀 Glob ```yaml - source: ~/.claude/skills/my-skill/ pattern: "**/*" target: skills/my-skill/{relpath} type: glob_recursive ``` ### 프로젝트별 (특수) ```yaml - source: ~/.claude/projects/{project}/memory/ pattern: "*.md" target: "{project_name}/{basename}" type: glob_per_project ``` `{project}` 는 디렉토리 이름, `{project_name}` 은 변환 (snake→kebab, prefix 제거 등). ### 변환 규칙 (선택) ```yaml - source: ~/{project}/CLAUDE.md target: "{project_kebab}/CLAUDE.md" transform: project_kebab: from: project replace: "_": "-" ``` ## 검증 manifest 변경 후: ```bash ainote sync_validate ``` 체크: - YAML 문법 - source 경로 존재 - target 충돌 (같은 target 에 두 source) - circular reference ## 다른 디바이스의 manifest 각 디바이스가 **독립적** manifest. 한 hub 에 여러 manifest 가능 → spoke 마다 다른 sync 범위. 예: - 맥미니: 모든 프로젝트 sync - iPad: 메모리만 (project CLAUDE.md 제외) - linux desktop: vault 만 ## 다음 - [sync-now 알고리즘](/sync/algorithm) - [폴더 구조 권장](/sync/folder-structure) - [충돌 해결](/sync/conflicts) --- # 다중 디바이스 동기화 개요 ::: warning 🚧 설계 단계 — 아직 구현 안 됨 이 페이지는 ainote 의 향후 기능을 미리 문서화한 것입니다. 현재 `@ainote/mcp` v1.1.x 에는 **vault / sync 도구가 포함돼 있지 않습니다**. 도구 호출 시 `Tool not found` 에러를 받게 됩니다. ::: ainote 는 **Star topology + HLC + LWW** 모델로 여러 디바이스에서 동일한 vault 를 유지합니다. ## 핵심 원칙 1. **Star topology** — 각 디바이스는 ainote vault (hub) 와만 sync. 디바이스끼리 직접 X. 2. **HLC (Hybrid Logical Clock)** — wall-clock + counter 로 시계 어긋남(클럭 드리프트) 방지하면서 시간 기반 정렬. 3. **LWW (Last-Write-Wins)** — 양쪽 변경 시 시점 우선. 단 5분 이내 충돌은 수동 검토. 4. **Local source of truth** — 충돌 시 기본은 로컬 우선 (사용자 의도가 가장 최근). ## 4가지 sync 케이스 ``` sync(file): L = local sha256, R = remote git_sha, S = state.json[file] 1. L == S.local AND R == S.remote → SKIP (변경 없음) 2. L != S.local AND R == S.remote → PUSH (로컬만 변경) 3. L == S.local AND R != S.remote → PULL (원격만 변경) 4. L != S.local AND R != S.remote → CONFLICT 4a. |L_mtime - R_updated| ≥ 5분 → auto LWW (newer wins) 4b. < 5분 → manual (diff 저장 → 사용자 결정) ``` ## 안전장치 - `--dry-run` — 무엇을 push/pull 할지만 출력 (실제 변경 X) - `--max-delete 30%` — 단일 sync 에서 30% 이상 파일 삭제 시 abort - `log/{YYYY-MM}.jsonl` — append-only 이벤트 로그 (디바이스/시점/경로/액션 5-way 추적) - `conflicts/` — 미해결 충돌은 diff 로 보관 (잃어버리지 않음) ## 다음 단계 - [아키텍처 — Star Topology](/sync/architecture) - [HLC 시간 모델](/sync/hlc) - [충돌 해결 (LWW)](/sync/conflicts) - [manifest.yml 작성](/sync/manifest) - [sync-now 알고리즘](/sync/algorithm) ::: tip 설계 문서 이 동기화 시스템의 전체 설계는 [`global/ainote-sync-redesign.md`](https://ainote.dev/sync/redesign) 참조. 산업 패턴 조사 (rclone bisync · Syncthing · Unison · CRDT) + 우리 케이스 적합성 분석 포함. ::: --- # 왜 별도 sync 시스템 (vs Dropbox) ::: warning 🚧 설계 단계 — 아직 구현 안 됨 이 페이지는 ainote 의 향후 기능을 미리 문서화한 것입니다. 현재 `@ainote/mcp` v1.1.x 에는 **vault / sync 도구가 포함돼 있지 않습니다**. 도구 호출 시 `Tool not found` 에러를 받게 됩니다. ::: > 짧은 답: **Dropbox 는 파일을 sync 하지만, ainote 는 의미를 sync 한다.** ## 비교 | | Dropbox/iCloud | Syncthing | git | ainote sync | |---|---|---|---|---| | 충돌 감지 | 시간 기반 (silent overwrite) | 양쪽 보존 (`*.sync-conflict-...`) | 3-way merge | HLC + LWW + 5분 임계 | | 의미 알림 | ❌ | ❌ | commit msg | ✅ 디바이스/시점 추적 | | 부분 sync | △ | △ | ✅ | ✅ manifest 기반 | | AI 접근 | ❌ | ❌ | △ | ✅ MCP 도구 | | 다중 디바이스 | ✅ | ✅ | ✅ | ✅ (star topology) | ## 구체적 문제 사례 ### Dropbox 의 silent overwrite 타임라인: ``` 14:00 — 맥미니: CLAUDE.md 에 새 규칙 5줄 추가 → 저장 14:05 — 맥북 (오프라인): 같은 파일 다른 곳 수정 → 저장 14:10 — 맥북 온라인 → Dropbox 동기화 → 맥미니 변경 사라짐 ``` 복구: Dropbox 버전 히스토리 (30일 보관) → 수동 비교 → 머지. ### Git 의 머지 conflict 마커 ``` <<<<<<< HEAD new content from local ======= new content from remote >>>>>>> origin/main ``` 마크다운 한가운데 들어가면 보기 싫음 + Obsidian 에서 깨짐. ### ainote 의 접근 1. **HLC** — 시계 어긋나도 인과 순서 보장 2. **5분 임계** — 그 안에 양쪽 변경 시 conflict 디렉토리에 양쪽 보관, 사용자 선택 3. **5분 후 변경** — LWW (last-writer-wins) — drift 방지 4. **device 추적** — 어느 기기에서 언제 변경했는지 모두 로그 5. **부분 sync** — manifest.yml 로 명시적 매핑만 ## "그래도 Dropbox 가 충분하지 않나?" Dropbox 가 충분한 경우: - 혼자 한 기기에서만 작업 - 또는 절대 같은 파일 동시 편집 X - AI 접근 필요 X ainote sync 가 필요한 경우: - 17개 프로젝트 CLAUDE.md 멀티 기기 - iPhone Telegram 으로 메모 → 맥북 Claude 가 즉시 활용 - AI 가 모든 기기에서 같은 메모리 봐야 함 - "이 변경 어느 기기에서 한 거지" 추적 필요 ## "그러면 그냥 git 만 쓰면 안되나?" git 충분한 경우: - 개발자 본인 익숙 - conflict marker 직접 해결 OK ainote sync 가 필요한 경우: - 비-기술 사용자도 함께 (가족 공유 vault) - AI 가 자동 sync (사용자 git 명령 모름) - 5분 임계처럼 사람-친화 정책 ## 다음 - [동기화 개요](/sync/overview) - [아키텍처 — Star Topology](/sync/architecture) - [HLC 시간 모델](/sync/hlc) --- # 카테고리 태스크 분류 라벨. ## 모델 ```ts Category { id: string (UUID) name: string // "업무", "개인", "운동" color?: string // hex, 예 "#0088ff" icon?: string // emoji 또는 SF Symbol position?: number } ``` ## MCP 도구 ```bash ainote list_categories '{}' ``` 응답 (text + structured resource): ```json { "content": [ { "type": "text", "text": "[Formatted list]" }, { "type": "resource", "resource": { "uri": "ainote://categories/list", "mimeType": "application/json", "text": "{\"categories\":[{\"id\":\"uuid\",\"name\":\"업무\",\"color\":\"#0088ff\"}]}" } } ] } ``` `category_id` 는 **UUID** (정수 아님). `create_task` / `update_task` / `list_tasks` 의 `category_id` 에 넣으면 됨. ## 자연어 매칭 `create_task` 가 카테고리 자동 추론은 안 함 (`category_id` 명시 필요). Claude 가 자연어 발화 → `list_categories` 로 ID 조회 → `create_task` 호출 패턴. ``` 사용자: "이거 운동 카테고리에 추가해줘 — 헬스장 7시" Claude: 1. list_categories → "운동" 의 id 찾음 2. create_task { content: "헬스장", due_date: "...19:00", category_id: "" } ``` ## 카테고리 추가 / 수정 MCP 도구 없음 (현재). 웹 UI 만: - - 색상/아이콘 picker, 정렬 CRUD MCP 도구는 계획됨 (v0.5+). ## 필터링 ```bash ainote list_tasks '{"category_id":""}' ``` ## 다음 - [`list_categories` API](/reference/list-categories) - [필터링 18개](/tasks/filtering) - [태스크 개요](/tasks/overview) --- # 날짜 / 위치 / 알림 ## 날짜 (`due_date` + `due_time`) ### 자연어 인식 `create_task` 의 `content` 안에 시간 표현 → 자동 파싱: | 입력 | 파싱 결과 (한국 기준 2026-05-07 목요일) | |------|---------| | "내일 오전 10시" | `due_date: "2026-05-08T10:00:00+09:00"` | | "다음주 화요일" | `due_date: "2026-05-12"` (시간 없음 → `is_all_day` 가능) | | "오늘 저녁 7시" | `due_date: "2026-05-07T19:00:00+09:00"` | | "5월 20일 3시" | `due_date: "2026-05-20T15:00:00+09:00"` | | "30분 후" | `due_date` = now + 30 min | 명시적 ISO 8601 도 가능: ```json { "content": "회의", "due_date": "2026-05-08T10:00:00+09:00" } ``` ### `due_date` + `due_time` 분리 `due_date` 가 시간 없을 때 `due_time` 만 따로: ```json { "content": "회의", "due_date": "2026-05-08", "due_time": "10:00" } ``` ### 종일 일정 ```json { "content": "휴가", "due_date": "2026-05-08", "is_all_day": true } ``` ### 다일 이벤트 ```json { "content": "출장", "start_date": "2026-05-08", "due_date": "2026-05-10", "is_all_day": true } ``` ## 위치 (`location` + GPS) ### 사람용 텍스트 ```json { "content": "강남역 미팅", "location": "강남역 3번 출구 스타벅스" } ``` 표시할 때 그대로 노출. `list_tasks` 의 `location` 필터에서 부분 일치 검색. ### GPS 좌표 ```json { "content": "스벅", "location": "Starbucks Gangnam", "location_lat": 37.4979, "location_lng": 127.0276 } ``` iOS/Android 앱에서 지도 표시 가능. ## 이동시간 (`travel_time`) 마감 시각 N분 전 이동 시간 확보 (알림 스케줄링 영향): ```json { "content": "강남역 미팅", "due_date": "2026-05-08T10:00:00+09:00", "travel_time": 30, "notification_minutes_before": 60 } ``` → 마감 60분 전 알림 + 30분 전엔 "이동 시간" 표시. ## 알림 (`notification_minutes_before`) ### 기본 ```json { "content": "회의", "due_date": "2026-05-08T10:00:00+09:00", "notification_minutes_before": 30 } ``` → 09:30 에 푸시 (FCM/APNs/Telegram/Web Push 모두). ### 알림 제거 (update 시) ```json { "id": "uuid", "notification_minutes_before": null } ``` `null` 명시적 전달 → 기존 MCP reminder 제거. ### 다중 채널 / 다중 시점 현재 MCP 도구는 단일 알림만 (`notification_minutes_before`). 다중 알림은 웹 UI 또는 `update_task` 후 다시 push. ## 반복 (`repeat_rule`) ### 단순 패턴 ```json { "content": "주간 회고", "due_date": "2026-05-10T14:00:00+09:00", "repeat_rule": "weekly" } ``` 값: `daily` / `weekly` / `monthly` / `yearly`. ### RRULE (iCalendar) ```json { "repeat_rule": "FREQ=WEEKLY;BYDAY=MO,WE,FR;UNTIL=20261231T235959Z" } ``` 서버가 부분 지원. 자세한 spec 은 [RFC 5545](https://www.rfc-editor.org/rfc/rfc5545#section-3.3.10). ### 인스턴스 vs 마스터 반복 설정 task 는 "마스터". 표시되는 건 `recurring_instances`: - 마스터 수정 → 모든 미래 인스턴스 영향 - 단일 인스턴스 수정 → 그것만 (예외) ## 우선순위 (`is_important`) `true` 면 별표 + 모든 list 에서 우선 정렬. ```json { "content": "긴급 패치", "is_important": true } ``` ## 다음 - [필터링 18개 전체](/tasks/filtering) - [`create_task` API](/reference/create-task) - [태스크 개요](/tasks/overview) --- # 필터링 (18개 옵션) `list_tasks` 의 모든 필터. ## 상태 | 파라미터 | 값 | 설명 | |---------|-----|------| | `status` | `pending` / `completed` | 기본 없음 (전체) | | `is_important` | boolean | 중요 표시만 | | `overdue` | boolean | 마감 지난 미완료 (`due_date < today`) | | `due_today` | boolean | 오늘 마감 | | `has_notification` | boolean | 알림 설정된 것만 | ## 검색 / 위치 | 파라미터 | 설명 | |---------|------| | `search` | 내용 키워드 (부분 일치) | | `location` | 위치 텍스트 부분 일치 (예: `"여의도"`, `"서울"`) | | `category_id` | 카테고리 UUID | ## 날짜 범위 | 파라미터 | 설명 | |---------|------| | `due_date_start` | ISO 8601 — `due_date >= 이 값` | | `due_date_end` | `due_date <= 이 값` | | `completed_date_start` | 완료일 ≥ | | `completed_date_end` | 완료일 ≤ | | `created_date_start` | 생성일 ≥ | | `created_date_end` | 생성일 ≤ | ## 정렬 / 페이징 | 파라미터 | 값 | 기본 | |---------|-----|------| | `sort_by` | `due_date` / `created_at` / `completed_at` / `updated_at` / `is_important` | `created_at` | | `sort_order` | `asc` / `desc` | `desc` | | `limit` | 1~500 | 25 | ## 자연어 → 시간 계산 ainote MCP 가 자연어 시간 표현을 자동 변환 (today = 2026-05-07 가정): | 입력 | 변환 | |------|------| | "오늘" | `due_today: true` | | "이번 주" | `due_date_start: "2026-05-05", due_date_end: "2026-05-11"` | | "다음 주" | `due_date_start: "2026-05-12", due_date_end: "2026-05-18"` | | "이번 달" | `due_date_start: "2026-05-01", due_date_end: "2026-05-31"` | | "지난 주" | `completed_date_start: "...", completed_date_end: "..."` | | "지난 달" | `completed_date_start: "2026-04-01", completed_date_end: "2026-04-30"` | ## 자주 쓰는 조합 ```jsonc // 오늘 할 일 { "due_today": true, "status": "pending" } // 이번 주 중요한 거 { "is_important": true, "due_date_start": "2026-05-05", "due_date_end": "2026-05-11" } // 마감 지난 미완료 (오래된 순) { "overdue": true, "sort_by": "due_date", "sort_order": "asc" } // 여의도 관련 { "location": "여의도" } // 어제 완료 (회고) { "status": "completed", "completed_date_start": "2026-05-06", "completed_date_end": "2026-05-06" } // "회의" 키워드 + 다음 7일 { "search": "회의", "due_date_start": "2026-05-07", "due_date_end": "2026-05-14" } ``` ## 응답 ```json { "content": [ { "type": "text", "text": "[Formatted list]" }, { "type": "resource", "resource": { "uri": "ainote://tasks/list", "mimeType": "application/json", "text": "{\"tasks\":[...]}" } } ] } ``` 구조화된 JSON 은 `content[1].resource.text` 에. ## 다음 - [`list_tasks` API](/reference/list-tasks) - [태스크 개요](/tasks/overview) - [날짜 / 위치 / 알림](/tasks/due-date) --- # 태스크 개요 ainote 의 첫 번째 1급 시민 — **AI 가 직접 추가/수정/삭제하는 할 일**. ## 핵심 모델 (실제 스키마) ```ts Task { id: string (UUID) content: string // 필수 notes?: string // 자유 메모 / 상세 due_date?: string (ISO 8601) due_time?: string ("HH:MM") start_date?: string (ISO 8601) // 다일 이벤트 is_all_day?: boolean is_important?: boolean category_id?: string (UUID) location?: string location_lat?: number location_lng?: number travel_time?: number // 분 repeat_rule?: string // "daily" / "weekly" / "monthly" / RRULE notification_minutes_before?: number completed_at?: string (ISO) | null } ``` ## 자연어 → 구조화 Claude 에서 "내일 오전 10시 강남역에서 미팅, 30분 전 알려줘" → ```json { "content": "강남역 미팅", "due_date": "2026-05-08T10:00:00+09:00", "location": "강남역", "notification_minutes_before": 30 } ``` ainote MCP 가 시간 표현 ("내일", "다음주 화요일", "오후 3시") + 위치 + 알림 모두 파싱. ## MCP 도구 5개 | 도구 | 용도 | |------|------| | [`create_task`](/reference/create-task) | 새 태스크 (필수: `content`) | | [`update_task`](/reference/update-task) | 수정 (`completed_at` 으로 완료 처리) | | [`delete_task`](/reference/delete-task) | Soft delete (30일 후 영구) | | [`list_tasks`](/reference/list-tasks) | 18개 필터 + 자연어 | | [`list_categories`](/reference/list-categories) | 카테고리 목록 | ## 주요 필터 ([18개 전체](/tasks/filtering)) ``` 오늘 할 일 → status: pending, due_today: true 중요 표시만 → is_important: true "회의" 검색 → search: "회의" 강남 관련 → location: "강남" 완료 (지난주) → status: completed, completed_date_start: "...", completed_date_end: "..." 지난 마감 → overdue: true 알림 설정된 → has_notification: true ``` ## 자동 백그라운드 작업 (Solid Queue) | Job | 주기 | 설명 | |-----|------|------| | `TaskCleanupJob` | 매일 2시 | 30일 지난 soft-delete 영구 삭제 | | `TaskReminderJob` | 5분마다 | 마감 임박 알림 발송 | | `NotificationSchedulerJob` | 15분마다 | 스케줄된 알림 처리 | | `SmartNotificationSchedulerJob` | 매시간 | 일일 요약, 아침 브리핑 | | `NotificationCleanupJob` | 매일 4시 | 3일 지난 읽은 알림 삭제 | | `CleanupStaleFcmTokensJob` | 매일 3시 | 만료 FCM 토큰 정리 | ## 알림 채널 태스크 마감 임박 시 동시 발송: - 📱 iOS / Android 푸시 (FCM + APNs) - 💬 Telegram (계정 연동 시) - 🌐 Web Push (브라우저 PWA) - ⌚ Apple Watch (iOS 동기화) ## 다음 - [카테고리 관리](/tasks/categories) - [필터 18개 전체](/tasks/filtering) - [날짜 / 위치 / 알림 자세히](/tasks/due-date) - [`create_task` API](/reference/create-task) --- # vault_clone ::: warning 🚧 설계 단계 — 아직 구현 안 됨 이 페이지는 ainote 의 향후 기능을 미리 문서화한 것입니다. 현재 `@ainote/mcp` v1.1.x 에는 **vault / sync 도구가 포함돼 있지 않습니다**. 도구 호출 시 `Tool not found` 에러를 받게 됩니다. ::: 기존 vault 를 로컬에 git clone. 또는 외부 git repo 를 ainote vault 로 등록. ## 시그니처 ### 모드 A: ainote vault → 로컬 ```json { "name": "personal", "destination": "/Users/seunghan/notes/personal" } ``` ### 모드 B: 외부 git → 새 vault ```json { "name": "obsidian-backup", "git_remote": "git@github.com:me/my-obsidian-vault.git", "destination": "/Users/seunghan/notes/obsidian-backup" } ``` ## 모드 A 예시 — 새 기기 셋업 ``` ainote vault_clone — personal vault 를 ~/notes/personal 에 ``` 내부 동작: ```bash git clone https://api.ainote.dev/vaults/me/personal.git /Users/seunghan/notes/personal cd /Users/seunghan/notes/personal git config user.email "me@example.com" git config user.name "Seunghan" ``` 응답: ``` ✅ /Users/seunghan/notes/personal 123 files, 45 MB Last commit: 2026-05-07 14:32 (macmini-2026-04) ``` ## 모드 B 예시 — Obsidian 마이그레이션 기존 Obsidian vault 를 GitHub 에 push 한 상태에서: ``` ainote vault_clone — name: obsidian, git_remote: git@github.com:me/obsidian.git, destination: ~/Documents/obsidian ``` 내부 동작: 1. ainote 에 새 vault 등록 (name: `obsidian`) 2. 외부 git remote 를 `mirror` 로 가져옴 3. ainote 의 git URL 을 새 origin 으로 set 4. 로컬에 clone 이후엔 `vault_sync` 로 양방향. ## 인증 ainote vault 는 사용자 MCP key 로 git auth: ``` Username: Password: ``` 또는 SSH key 등록 (`ainote.dev/settings/git`). `git_remote` 가 외부 (GitHub 등) 면 별도 인증 (SSH agent 추천). ## destination 권장 - 짧고 명시적 (`~/notes/personal` 좋음, `~/Documents/Random/Folder/Maybe/Vault` 나쁨) - 백업 디렉토리 안 권장 (TimeMachine 무거워짐) - iCloud Drive **비추** — git 과 충돌 ## Obsidian 양방향 사용 clone 후 Obsidian 에서: 1. "Open Vault as Folder" → `~/notes/obsidian` 2. `.obsidian/` 폴더 자동 생성됨 3. ainote 에도 같이 sync 되도록 `.gitignore` 조정: ``` # .gitignore (vault root) .obsidian/workspace* .obsidian/cache .obsidian/plugins/*/data.json ``` 설정 자체는 sync, 임시 캐시는 제외. ## 충돌 clone 시 destination 이 이미 존재: - 비어있음 → OK - 파일 있음 → 에러 (`destination not empty`) - 강제 덮어쓰기: `force: true` (위험) ## 에러 | 코드 | 메시지 | 해결 | |------|-------|------| | -32602 | vault not found | name 확인 | | -32602 | destination not empty | 빈 폴더 또는 force | | -32603 | git auth failed | 키 등록 또는 SSH | | -32603 | external repo unreachable | git_remote URL/권한 확인 | ## 다음 - [`vault_sync` — 양방향 동기화](/vault/sync) - [`vault_create` — 새 빈 vault](/vault/create) - [Vault 개요](/vault/overview) --- # vault_create ::: warning 🚧 설계 단계 — 아직 구현 안 됨 이 페이지는 ainote 의 향후 기능을 미리 문서화한 것입니다. 현재 `@ainote/mcp` v1.1.x 에는 **vault / sync 도구가 포함돼 있지 않습니다**. 도구 호출 시 `Tool not found` 에러를 받게 됩니다. ::: 새 vault 생성 — 빈 git repo 가 백엔드. ## 시그니처 ```json { "name": "personal", "description": "개인 노트", "private": true, "init_files": ["README.md"] } ``` | 파라미터 | 타입 | 필수 | 설명 | |---------|------|------|------| | `name` | string | ✅ | URL-safe 영소문자 + `-` | | `description` | string | | 사람용 설명 | | `private` | boolean | | 기본 true (혼자만 보임) | | `init_files` | array | | 초기 생성 파일 목록 | ## 응답 ```json { "id": 42, "name": "personal", "git_url": "https://api.ainote.dev/vaults/me/personal.git", "clone_command": "git clone https://api.ainote.dev/vaults/me/personal.git", "web_url": "https://ainote.dev/vaults/personal", "size_bytes": 0, "file_count": 1 } ``` ## Claude 사용 ``` ainote 에 "personal" vault 만들어줘 ``` 또는 더 명시적: ``` ainote vault_create — name: work, description: 회사 노트, private: true ``` ## 생성 직후 ainote 가 자동으로: 1. PostgreSQL 에 vault row 생성 2. git bare repo 초기화 (`vaults//.git`) 3. `init_files` 있으면 placeholder 파일 commit 4. SSH/HTTPS 인증 설정 (사용자 키 기반) ## 로컬 클론 만든 vault 를 로컬에서 쓰려면: ```bash git clone https://api.ainote.dev/vaults/me/personal.git ~/notes/personal ``` 또는 MCP 로: ``` ainote vault_clone personal 을 ~/notes/personal 에 ``` ## 이름 규칙 - 영소문자, 숫자, `-` 만 - 1~50자 - 숫자로 시작 X - 예약어: `git`, `api`, `system`, `admin` 유효성 위반 시: ``` { "error": "invalid name: must match ^[a-z][a-z0-9-]{0,49}$" } ``` ## 권한 - `private: true` (기본) — 본인만 (MCP key 인증) - `private: false` — 공개 read (git clone 인증 없이) - 공유 (계획됨, v0.5): 다른 사용자 collaborator 추가 ## 에러 | 코드 | 메시지 | 해결 | |------|-------|------| | -32602 | invalid name | 이름 규칙 확인 | | -32603 | name already exists | 다른 이름 | | -32004 | quota exceeded | vault 100개 한도 | ## 다음 - [`vault_clone` — 다른 기기에서 가져오기](/vault/clone) - [`vault_sync` — 양방향](/vault/sync) - [Vault 개요](/vault/overview) --- # Git backend ::: warning 🚧 설계 단계 — 아직 구현 안 됨 이 페이지는 ainote 의 향후 기능을 미리 문서화한 것입니다. 현재 `@ainote/mcp` v1.1.x 에는 **vault / sync 도구가 포함돼 있지 않습니다**. 도구 호출 시 `Tool not found` 에러를 받게 됩니다. ::: ainote 의 vault 는 모두 git 저장소. 데이터는 너의 것. ## 구조 ``` ainote 서버 (api.ainote.dev) └── vaults/ └── / └── .git/ ← bare repo ├── HEAD ├── refs/heads/main ├── objects/ └── ... ``` 서버에는 **bare repo** (working tree 없음). 사용자가 clone 해서 working tree 를 로컬에. ## URL 패턴 ``` HTTPS: https://api.ainote.dev/vaults//.git SSH: git@api.ainote.dev:vaults//.git ``` ## 인증 ### HTTPS Basic Auth ``` Username: Password: ``` `.netrc` 자동 셋업 (계획됨): ``` machine api.ainote.dev login me@example.com password h7Axq9XPsDTD2qr5yqtcCSaQ... ``` ### SSH Key → "Add SSH key" → 공개키 붙여넣기. 이후: ```bash git clone git@api.ainote.dev:vaults/me/personal.git ``` ## 표준 git 명령 vault clone 후엔 일반 git repo: ```bash cd ~/notes/personal git log --oneline git diff git checkout -b experimental git push origin experimental git tag v1 git push --tags ``` ## Branch 지원 기본 `main`. 다른 branch 도 push/pull 가능: ```bash git checkout -b feature git push origin feature ``` ainote MCP 도구는 기본 `main` 만 — 다른 branch 는 git CLI 직접 사용. ## Tag / Release ```bash git tag -a "monthly-backup-2026-05" -m "Backup" git push --tags ``` 특정 시점 복원: ```bash git checkout monthly-backup-2026-05 ``` ## Hooks 서버 hook (계획됨): - `pre-receive` — 파일 크기 제한 (10MB) - `post-receive` — 다른 디바이스 push 알림 로컬 hook (사용자): ```bash # .git/hooks/pre-commit #!/bin/bash # 비밀 keyword 검사 grep -rE "(api_key|password|secret)" --include="*.md" . && exit 1 ``` ## 백업 전략 ### 자체 백업 ainote 자체가 백업이지만 추가로: ```bash # 매일 cron git clone --mirror https://api.ainote.dev/vaults/me/personal.git \ ~/backups/$(date +%Y-%m-%d)-personal.git ``` ### Mirror to GitHub vault → GitHub 미러: ```bash cd ~/notes/personal git remote add github git@github.com:me/notes-mirror.git git push github main ``` cron 으로 정기 push. ## 크기 / 한도 | 한도 | 무료 | (계획) 유료 | |------|------|-----------| | Vault 개수 | 100 | 무제한 | | Vault 크기 | 1 GB | 100 GB | | 파일 1개 | 10 MB | 100 MB | | 사용자당 총량 | 5 GB | 1 TB | LFS 미지원 (계획됨). ## 다른 git host 와 미러 ainote vault ↔ GitHub/GitLab 미러: ```bash cd ~/notes/personal git remote add ainote https://api.ainote.dev/vaults/me/personal.git git remote add github git@github.com:me/notes.git # 양쪽 push git push ainote main git push github main ``` 자동화 cron 으로. ## 다음 - [`vault_sync`](/vault/sync) - [`vault_clone`](/vault/clone) - [데이터 내보내기](/guide/data-export) --- # Vault 개요 ::: warning 🚧 설계 단계 — 아직 구현 안 됨 이 페이지는 ainote 의 향후 기능을 미리 문서화한 것입니다. 현재 `@ainote/mcp` v1.1.x 에는 **vault / sync 도구가 포함돼 있지 않습니다**. 도구 호출 시 `Tool not found` 에러를 받게 됩니다. ::: ainote 의 **세 번째 1급 시민** — Obsidian 호환 마크다운 저장소, git 백엔드. ## Vault 가 뭔가 **Obsidian** 의 vault 와 동일 개념: - 폴더 1개 = vault 1개 - 안에 마크다운 파일들 자유롭게 (서브폴더 OK) - `[[wikilinks]]` 호환 - 프론트매터 YAML 지원 차이점: - **git 백엔드** — 모든 변경 자동 commit - **MCP 노출** — Claude 가 직접 read/write - **다중 디바이스** — 여러 기기에서 같은 vault clone ## 5개 MCP 도구 | 도구 | 용도 | |------|------| | [`vault_create`](/reference/vault-create) | 새 vault (빈 git repo) | | [`vault_clone`](/reference/vault-clone) | 다른 기기/Obsidian vault 가져오기 | | [`vault_sync`](/reference/vault-sync) | pull → 변경사항 → push 양방향 | | [`vault_list`](/reference/vault-list) | 등록된 vault 목록 | | [`vault_connect_status`](/reference/vault-connect-status) | git 상태 확인 | ## 사용 시나리오 ### A. 처음 시작 — 빈 vault ``` ainote 에 "personal" 이라는 vault 만들어줘 ``` → `vault_create` → git repo 생성 → 다른 도구에서 사용 가능. ### B. 기존 Obsidian 마이그레이션 ``` ~/Documents/Obsidian/MyVault 를 ainote 에 vault_clone 해줘 ``` → git push → ainote 가 hosting → 다른 기기에서 `vault_clone` 으로 받기. ### C. 다중 기기 동기화 맥미니에서 노트 작성 → `vault_sync` (자동 commit + push) → 맥북에서 `vault_sync` (pull) → 변경 도착. ## 폴더 구조 권장 ``` my-vault/ ├── daily/ # 일일 메모 │ └── 2026-05-07.md ├── projects/ # 프로젝트 노트 │ ├── ainote.md │ └── tennis-bracket.md ├── reference/ # 참고 자료 │ ├── books/ │ └── articles/ ├── meta/ # vault 자체 메타 │ └── README.md └── .obsidian/ # Obsidian 설정 (선택) ``` ## 충돌 해결 vault_sync 시 Git 3-way merge: - 같은 파일 같은 줄 양쪽 수정 → conflict marker → 수동 해결 - 다른 파일 / 다른 줄 → 자동 merge 자세히: [동기화 충돌 해결](/sync/conflicts). ## Obsidian 양방향 기존 Obsidian vault 를 ainote 에 등록하면: - 로컬 파일 시스템 = ainote vault git working tree - Obsidian 으로 편집 → `vault_sync` → ainote 반영 - Claude 가 ainote 로 편집 → `vault_sync` (pull) → Obsidian 새로고침에 보임 ::: tip Obsidian Sync 대체 Obsidian 의 유료 Sync 플러그인 ($5/mo) 대신 ainote vault 무료로 사용 가능. ::: ## Frontmatter 규칙 ainote 메모리는 frontmatter 표준: ```markdown --- name: my-note description: 한 줄 요약 type: project | feedback | reference | user ainote_sync: vault-name/path/in/vault.md # sync 대상이면 --- # 본문 ``` 이 형식 따르면 [메모리 4가지 타입](/memory/types) 자동 분류 가능. ## 다음 - [`vault_create` API](/reference/vault-create) - [`vault_clone` — 다른 기기에서 가져오기](/vault/clone) - [`vault_sync` — 양방향 동기화](/vault/sync) - [Git backend 자세히](/vault/git-backend) --- # vault_sync ::: warning 🚧 설계 단계 — 아직 구현 안 됨 이 페이지는 ainote 의 향후 기능을 미리 문서화한 것입니다. 현재 `@ainote/mcp` v1.1.x 에는 **vault / sync 도구가 포함돼 있지 않습니다**. 도구 호출 시 `Tool not found` 에러를 받게 됩니다. ::: vault 양방향 동기화 — pull 후 push. ## 시그니처 ```json { "name": "personal", "auto_commit": true, "commit_message": "sync from macbook" } ``` | 파라미터 | 타입 | 기본 | 설명 | |---------|------|------|------| | `name` | string | — | vault 이름 | | `auto_commit` | boolean | true | 미커밋 변경 자동 commit | | `commit_message` | string | `"sync from "` | 커밋 메시지 | | `strategy` | `merge` / `rebase` / `theirs` / `ours` | `merge` | 충돌 시 | ## 동작 ``` 1. cd ~/notes/personal 2. (auto_commit) git add -A && git commit -m "..." 3. git pull --strategy= 4. git push 5. 변경 파일 목록 응답 ``` ## 응답 ```json { "vault": "personal", "pulled": 3, // 받은 commit 수 "pushed": 1, // 보낸 commit 수 "files_changed": [ { "path": "daily/2026-05-07.md", "action": "added" }, { "path": "projects/ainote.md", "action": "modified" }, { "path": "old.md", "action": "deleted" } ], "conflicts": [] } ``` ## Claude 사용 ``` ainote 에서 personal vault 동기화 ``` 또는 모든 vault: ``` ainote vault 다 sync 해줘 ``` → Claude 가 `vault_list` → 각각 `vault_sync` 순차 호출. ## 충돌 시 `conflicts` 배열에 파일 목록: ```json { "conflicts": [ { "path": "ideas.md", "marker_lines": [12, 24] } ] } ``` 수동 해결: ```bash cd ~/notes/personal # editor 로 ideas.md 열어서 <<<<<<< 마커 해결 git add ideas.md git commit ainote vault_sync personal # 다시 ``` 또는 자동 해결 전략: - `theirs` — 원격 우선 (로컬 변경 무시) - `ours` — 로컬 우선 (원격 받은 거 무시) - `rebase` — 로컬 commit 을 원격 위에 올려놓기 ## 정기 자동 sync cron + script: ```bash # crontab -e */15 * * * * /Users/seunghan/scripts/ainote-vault-sync-all.sh ``` ```bash #!/bin/bash # ainote-vault-sync-all.sh ainote vault_list '{}' | jq -r '.[].name' | while read name; do ainote vault_sync "{\"name\":\"$name\"}" done ``` ## launchd (macOS, 더 안정적) `~/Library/LaunchAgents/com.ainote.vault-sync.plist`: ```xml Label com.ainote.vault-sync ProgramArguments /Users/seunghan/scripts/ainote-vault-sync-all.sh StartInterval 900 ``` ```bash launchctl load ~/Library/LaunchAgents/com.ainote.vault-sync.plist ``` ## 에러 | 코드 | 메시지 | 해결 | |------|-------|------| | -32603 | merge conflict | 수동 해결 또는 strategy 변경 | | -32603 | local repo missing | `vault_clone` 먼저 | | -32603 | network error | 재시도 (Render cold start) | ## 다음 - [`vault_clone` — 새 기기에서 가져오기](/vault/clone) - [Git backend 자세히](/vault/git-backend) - [충돌 해결 (sync 관점)](/sync/conflicts) ---