매니페스트
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/"
}
필드
| 필드 | 타입 | 설명 |
|---|---|---|
manifestVersion | 1 | 지금은 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 } | 둘 다 비어 있지 않은 문자열이어야 한다 |
version | semver | 1.0.0 형식. 사전 릴리스와 빌드 메타데이터 허용 |
sdk | 문자열 | SDK 버전 범위(예: ^1.0.0). 형식은 자유 문자열로만 검증한다 |
category | 문자열 | 마켓플레이스 카테고리 |
icon | 문자열 | 아이콘 파일 경로. screenshots/ 폴더의 이미지는 이름순으로 카탈로그에 보이고 첫 장이 카드 커버다. 1280x720 이상 png 또는 svg |
developer | { name, url?, support? } | url은 유효한 URL이어야 한다 |
license | 문자열, 선택 | |
builtIn | boolean, 기본 false | 우리 기본 앱 배지 |
runsIn | "worker" | "gateway" | adapter capability는 gateway, 나머지는 worker |
helpSlug | 문자열, 선택 | 문서 경로 |
pricing | { monthlyMicro } | 0 이상의 정수 |
multiInstance | boolean, 기본 false | |
scopes | 문자열 배열 | 아래 표 참고. 기본값 [] |
oauth | { provider, scopes, clientSecretRef? }, 선택 | |
model | { calls }, 선택 | calls: true면 ctx.model.complete를 쓴다는 뜻이고 사용자 크레딧이 빠진다 |
tools | AppTool[] | 아래 참고. 기본값 [] |
triggers | AppTrigger[] | 아래 참고. 기본값 [] |
surfaces | { settings?, homeCard? }, 선택 | 파일 경로 |
presets | AppPreset[] | 기본값 [] |
lifecycle | { entry } | 진입점 파일 경로 |
i18n | 문자열 | i18n 파일이 있는 폴더 경로 |
tools[]
| 필드 | 설명 |
|---|---|
key | 비어 있지 않은 문자열 |
title | { ko, en } |
input, output | JSON 스키마. 문자열이면 번들 안 파일 경로, 객체면 인라인 JSON 스키마 그 자체 |
approval | "none" | "required" | "rule". 기본값 "none" |
irreversible | boolean, 기본값 false |
readOnly | boolean, 기본값 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.call | ctx.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가 둘을 대조해 어느 한쪽에만 있으면 오류를 냅니다.
scope와 실제 ctx 호출의 대조는 tiffy-app check가 코드에서 찾은 ctx.notes, ctx.fetch 같은 호출 패턴을 정규식으로 훑어 경고까지만 냅니다. 번들된 코드에서 완전히 세는 것은 불가능하므로, 실제 권한 경계는 실행 중 호스트의 검사입니다(아직 구현되지 않음).