매니페스트

tiffy-app.json의 모든 필드, scope 목록, 정적 검사가 강제하는 규칙.

매니페스트는 tiffy-app.json 파일 하나입니다. @tiffy/app-sdk가 내보내는 AppManifest 스키마가 정본입니다. 아래 예시는 그 스키마가 실제로 요구하는 모양입니다.

전체 예시

{
  "manifestVersion": 1,
  "id": "acme.mail",
  "name": { "ko": "메일", "en": "Mail" },
  "description": { "ko": "메일을 분류하고 답장 초안을 씁니다.", "en": "Classifies mail and drafts replies." },
  "version": "1.0.0",
  "sdk": "^1.0.0",
  "category": "mail",
  "icon": "icon.svg",
  "developer": { "name": "Example Inc.", "url": "https://example.com", "support": "[email protected]" },
  "license": "MIT",
  "builtIn": false,
  "runsIn": "worker",
  "helpSlug": "using/mail",
  "pricing": { "monthlyMicro": 0 },
  "multiInstance": false,
  "scopes": ["mail.read", "notes.write", "secrets.own", "network:imap.example.com"],
  "oauth": { "provider": "google", "scopes": ["https://mail.google.com/"], "clientSecretRef": "google" },
  "model": { "calls": true },
  "tools": [
    {
      "key": "mail.draftReply",
      "title": { "ko": "답장 초안", "en": "Draft reply" },
      "input": "schemas/draftReply.in.json",
      "output": "schemas/draftReply.out.json",
      "approval": "none",
      "irreversible": false
    },
    {
      "key": "mail.send",
      "title": { "ko": "메일 보내기", "en": "Send mail" },
      "input": "schemas/send.in.json",
      "output": "schemas/send.out.json",
      "approval": "required",
      "irreversible": true
    }
  ],
  "triggers": [
    { "key": "mail.arrived", "kind": "poll", "minIntervalSec": 300, "dedupe": "message-id" }
  ],
  "surfaces": { "settings": "surfaces/settings.json", "homeCard": "surfaces/home-card.json" },
  "presets": [
    { "roleKey": "mail-triage", "name": { "ko": "메일 정리", "en": "Mail triage" }, "persona": "personas/triage.md", "tools": ["mail.draftReply"] }
  ],
  "lifecycle": { "entry": "dist/index.js" },
  "i18n": "i18n/"
}

필드

필드타입설명
manifestVersion1지금은 1 고정
id문자열<publisher>.<name> 형태(^[a-z][a-z0-9-]*\.[a-z][a-z0-9-]*$, 정확히 점 하나, 전체 3~64자. 예: acme.mail). publisher는 개발자 설정에서 등록한 자신의 퍼블리셔여야 한다. tiffy.는 예약이라 Tiffy 공식 앱만 쓴다. 발행 뒤 바뀌지 않는다
name, description{ ko, en }둘 다 비어 있지 않은 문자열이어야 한다
versionsemver1.0.0 형식. 사전 릴리스와 빌드 메타데이터 허용
sdk문자열SDK 버전 범위(예: ^1.0.0). 형식은 자유 문자열로만 검증한다
category문자열마켓플레이스 카테고리
icon문자열아이콘 파일 경로. screenshots/ 폴더의 이미지는 이름순으로 카탈로그에 보이고 첫 장이 카드 커버다. 1280x720 이상 png 또는 svg
developer{ name, url?, support? }url은 유효한 URL이어야 한다
license문자열, 선택
builtInboolean, 기본 false우리 기본 앱 배지
runsIn"worker" | "gateway"adapter capability는 gateway, 나머지는 worker
helpSlug문자열, 선택문서 경로
pricing{ monthlyMicro }0 이상의 정수
multiInstanceboolean, 기본 false
scopes문자열 배열아래 표 참고. 기본값 []
oauth{ provider, scopes, clientSecretRef? }, 선택
model{ calls }, 선택calls: true면 ctx.model.complete를 쓴다는 뜻이고 사용자 크레딧이 빠진다
toolsAppTool[]아래 참고. 기본값 []
triggersAppTrigger[]아래 참고. 기본값 []
surfaces{ settings?, homeCard? }, 선택파일 경로
presetsAppPreset[]기본값 []
lifecycle{ entry }진입점 파일 경로
i18n문자열i18n 파일이 있는 폴더 경로

tools[]

필드설명
key비어 있지 않은 문자열
title{ ko, en }
input, outputJSON 스키마. 문자열이면 번들 안 파일 경로, 객체면 인라인 JSON 스키마 그 자체
approval"none" | "required" | "rule". 기본값 "none"
irreversibleboolean, 기본값 false
readOnlyboolean, 기본값 false. irreversible: true와 함께 쓸 수 없다

triggers[]

필드설명
key비어 있지 않은 문자열
kind"poll"만 지원
minIntervalSec정수, 300 이상
dedupe선택, dedupe 규칙 이름

scope 목록

scope의미
user.email사용자 이메일을 읽는다
notes.read노트를 검색하고 읽는다
notes.write노트를 만들고 고치고 첨부한다
channels.send사용자의 연결된 채널로 알림을 보낸다
files.own앱 전용 파일 폴더를 쓴다
secrets.own앱 전용 봉인 저장소를 쓴다
calendar.read캘린더를 읽는다
calendar.write캘린더를 고친다
mail.read메일을 읽는다
mail.send메일을 보낸다
contacts.read연락처를 읽는다
model.callctx.model.complete를 부른다
tool:<key>다른 앱의 도구 <key>를 부른다
network:<host><host>로 ctx.fetch를 부른다

tool:과 network: 접두사가 없고 위 표에도 없는 문자열은 매니페스트 검증에서 탈락합니다.

정적 검사가 강제하는 규칙

  • id의 퍼블리셔 부분(첫 점 앞)이 tiffy, usetiffy, admin, system, official 중 하나면 tiffy-app check와 제출 API 모두 거부합니다(reserved_publisher). 제출 API는 추가로 id의 퍼블리셔가 등록한 자신의 퍼블리셔와 다르면 거부합니다(publisher_mismatch).
  • irreversible: true인 도구는 approval이 "required" 또는 "rule"이어야 합니다. "none"이면 매니페스트 스키마 자체가 거부합니다.
  • network: scope의 호스트는 정확한 호스트명이거나 *.example.com처럼 와일드카드는 맨 앞 한 단계만 허용합니다. *.*.example.com처럼 두 단계 이상 와일드카드는 거부됩니다.
  • 트리거 minIntervalSec는 300 이상이어야 합니다.
  • version은 semver 형식이어야 합니다. tiffy-app check는 버전 형식만 보고, 같은 버전을 두 번 제출하는 것을 막는 것은 마켓플레이스 제출 단계(아직 구현되지 않음)의 몫입니다.
  • tools와 triggers의 key는 src/index.ts의 defineApp({ tools, triggers }) 객체 키와 정확히 일치해야 합니다. tiffy-app check가 둘을 대조해 어느 한쪽에만 있으면 오류를 냅니다.