@theplato/tiro-cli 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SPEC.md DELETED
@@ -1,364 +0,0 @@
1
- # tiro-cli v1 SPEC
2
-
3
- > Status: **Active design** (2026-05-06) — backend OAuth 인프라 조사 완료, 변경 0으로 진행 가능 확인됨
4
- > Owner: yeoul / @theplato
5
-
6
- ## 0. 한 줄 정체성
7
-
8
- > **tiro-cli 는 에이전트의 발이다.** MCP가 컨텍스트 안에서 작은 데이터를 다루는 손이라면, CLI는 큰 데이터를 disk로 옮기고 shell pipe 로 흘려보내는 발.
9
-
10
- ## 1. 정체성 / 비정체성
11
-
12
- ### 정체성
13
- - **Agent-first** read/export/share tool. 인간 power user 도 동시 만족
14
- - **Filesystem-first**: 결과물을 disk에 떨어뜨리고 metadata 만 stdout으로
15
- - **MCP feature parity**: tiro-mcp-server 의 14개 도구를 모두 동등하게 노출 + filesystem 확장
16
- - **Self-describing**: agent 가 docs 없이 `tiro schema` 로 자기 발견
17
-
18
- ### 비정체성
19
- - 음성 파일 업로드/다운로드 (MCP 에서 PR #21로 제거됨, 일관성 유지) — v2 검토
20
- - 인간 GUI 의 대체재 (folder CRUD 등 interactive task 는 web)
21
- - Backoffice / admin 도구 (별도 surface)
22
-
23
- ## 2. 아키텍처
24
-
25
- ```
26
- tiro Backend (Kotlin) — /v1/external/* 40+ endpoints, OpenAPI 3.1 SSoT
27
- (external-api-docs/openapi.yaml)
28
-
29
- ┌─────────────────┬─────────┼─────────────────┐
30
- ▼ ▼ ▼
31
- ┌──────────┐ ┌──────────────┐ ┌──────────┐
32
- │ MCP │ │ tiro-cli │ │ Web UI │
33
- │ 14 tools │ │ 17 commands │ │ │
34
- │ (no │ │ (MCP parity │ │ │
35
- │ voice) │ │ + file ops) │ │ │
36
- └──────────┘ └──────────────┘ └──────────┘
37
- ```
38
-
39
- ## 3. 패키지 / 배포
40
-
41
- | 항목 | 값 |
42
- |---|---|
43
- | npm package | **`@theplato/tiro-cli`** |
44
- | bin name | **`tiro`** |
45
- | 언어 | TypeScript (Node 20+, ESM) |
46
- | 배포 채널 | npm (1차), Homebrew tap (2차) |
47
- | 설치 | `npm install -g @theplato/tiro-cli` |
48
-
49
- **근거**: `tiro` 단일 이름은 npm에 squatted (Zyao89, abandoned v0.0.0). `@tiro` / `@theplato` 모두 가용. **product brand 가 user-facing 인 경우 product 이름 우선** (Stripe `@stripe/*`, Bolta `@bolta-io/cli` 패턴). 향후 `@tiro/sdk`, `@tiro/mcp-server` (rename 후) 등 자연 확장.
50
-
51
- ## 4. 인증
52
-
53
- ### 흐름 (3-tier)
54
- ```
55
- 1. TIRO_TOKEN env var 있음? → 그것 사용 (CI / agent 비대화 컨텍스트)
56
- 2. OS Keychain에 토큰 있음? → 그것 사용
57
- 3. 둘 다 없음? → "tiro auth login" 안내, exit code 78 (EX_CONFIG)
58
- ```
59
-
60
- ### `tiro auth login` — Authorization Code Flow + PKCE + loopback redirect
61
-
62
- ```
63
- 1. POST /v1/mcp/oauth/register (RFC 7591 Dynamic Client Registration)
64
- body: { client_name: "tiro-cli", redirect_uris: ["http://127.0.0.1:<port>/callback"],
65
- grant_types: ["authorization_code"], response_types: ["code"],
66
- token_endpoint_auth_method: "none" }
67
- → { client_id }
68
-
69
- 2. PKCE 생성: code_verifier (43~128자 랜덤), code_challenge = base64url(sha256(code_verifier))
70
- 3. state 생성 (32바이트 랜덤)
71
- 4. 임시 HTTP 서버 시작 (ephemeral port)
72
- 5. 브라우저 자동 오픈:
73
- GET /v1/mcp/oauth/authorize?response_type=code&client_id=...
74
- &redirect_uri=http://127.0.0.1:<port>/callback&state=...
75
- &code_challenge=...&code_challenge_method=S256
76
-
77
- 6. 사용자 Google OAuth → backend google/callback → tiro-client web callback
78
- → 최종 http://127.0.0.1:<port>/callback?code=...&state=... 으로 리다이렉트
79
-
80
- 7. CLI 의 임시 서버가 callback 수신 → state 검증
81
-
82
- 8. POST /v1/mcp/oauth/token (form-encoded)
83
- body: grant_type=authorization_code&code=...&redirect_uri=...
84
- &client_id=...&code_verifier=...
85
- → { access_token: <JWT 180일>, token_type: "Bearer", expires_in: ... }
86
-
87
- 9. JWT 디코드 → sub (userId), exp 추출
88
- 10. OS Keychain 에 토큰 저장 (service: "io.tiro.cli", account: "default")
89
- ```
90
-
91
- ### Headless / SSH / CI 환경
92
- - `TIRO_TOKEN` env var 사용 (별도 머신에서 로그인 후 토큰 복사)
93
- - Device Flow (RFC 8628) 는 백엔드 미지원 → v1.x follow-up
94
-
95
- ### 토큰 저장
96
- - macOS Keychain / Linux Secret Service / Windows Credential Manager via `@napi-rs/keyring`
97
- - Fallback: `~/.config/tiro/auth.json` (perm 600), 평문 경고 표시
98
-
99
- ## 5. 명령 트리 (v1, 17개)
100
-
101
- ### 5.1 Auth (3) — 모든 환경에서 작동
102
- ```
103
- tiro auth login [--no-browser] # Authorization Code + loopback
104
- tiro auth status [--json] # 현재 계정 / scope / expires
105
- tiro auth logout # Keychain 정리
106
- ```
107
-
108
- ### 5.2 Notes (5) — read-heavy 본진 (MCP `list_notes`, `search_notes`, `get_note`, `get_note_transcript` + 파일 export)
109
- ```
110
- tiro notes list # GET /v1/external/notes
111
- --folder <id> --limit <n> --cursor <c> --json|--pretty
112
-
113
- tiro notes search [query] # POST /v1/external/notes/search
114
- --speaker <name> --since <date> --until <date>
115
- --folder <id> --limit <n> --cursor <c>
116
- → NDJSON output (1 line = 1 note metadata)
117
-
118
- tiro notes get <guid> # GET /v1/external/notes/{guid} + paragraphs
119
- --output <path> # 파일 저장 (stdout = path만, agent 컨텍스트 절약)
120
- --format md|json|txt # default: md (TTY) / json (pipe)
121
- --include transcript,summary,documents,participants
122
-
123
- tiro notes transcript <guid> # GET /v1/external/notes/{guid}/paragraphs
124
- --output <path> --format md|json|txt
125
-
126
- tiro notes export # bulk 다운로드 — CLI 킬러 명령
127
- --query <q> --speaker <n> --since <d> --until <d> --folder <id>
128
- --output-dir <dir> # 필수
129
- --include transcript,summary,documents,share-link
130
- --format md|json --concurrency <n>
131
- → 디렉토리 + manifest.jsonl
132
- ```
133
-
134
- ### 5.3 Document Templates (2) — MCP `list_document_templates`, `get_document_template`
135
- ```
136
- tiro templates list # GET /v1/external/note-document-templates
137
- --json|--pretty
138
-
139
- tiro templates get <id> # GET /v1/external/note-document-templates/{id}
140
- --output <path> --format md|json
141
- ```
142
-
143
- ### 5.4 Share Links (3) — MCP `create_share_link`, `get_share_link`, `delete_share_link`
144
- ```
145
- tiro share-links create <noteGuid> # PUT /v1/external/notes/{guid}/share-link
146
- --password <pw> # 옵션: 비밀번호 보호
147
- --no-password # 기존 비밀번호 제거
148
-
149
- tiro share-links get <noteGuid> # GET .../share-link
150
- --password <pw> # 비밀번호 검증
151
-
152
- tiro share-links delete <noteGuid> # DELETE .../share-link
153
- ```
154
-
155
- ### 5.5 Folders (2) — MCP `search_user_folders`, `search_team_folders`
156
- ```
157
- tiro folders search [query] # search_user_folders + search_team_folders 통합
158
- --scope user|team|all # default: all
159
- --limit <n>
160
- --json|--pretty
161
- ```
162
-
163
- ### 5.6 Self-describe (1)
164
- ```
165
- tiro schema [<command>] # 명령 트리 + 입출력 JSON Schema (agent self-introspection)
166
- ```
167
-
168
- ### 5.7 ChatGPT Deep Research aliases (2) — MCP `search`, `fetch`
169
- > MCP에 있지만 CLI 에는 별도 명령 안 만듦. `notes search` 와 `notes get` 이 같은 기능 제공.
170
- > 호환 필요 시 `tiro search` / `tiro fetch` 별칭 추가 검토 (v1.x).
171
-
172
- ## 6. `tiro --help`
173
-
174
- ```
175
- tiro — AI notes & transcripts CLI
176
-
177
- USAGE
178
- tiro <command> [options]
179
-
180
- COMMANDS
181
- auth login Sign in via OAuth (browser-based, PKCE)
182
- auth status Show current account and scopes
183
- auth logout Sign out and clear stored token
184
-
185
- notes list List recent notes
186
- notes search Search notes by speaker / date / folder
187
- notes get Get a single note (stdout or file)
188
- notes transcript Get raw transcript paragraphs
189
- notes export Bulk-export notes to a directory
190
-
191
- templates list List note document templates
192
- templates get Get a specific template
193
-
194
- share-links create Create or update a share link for a note
195
- share-links get Get the share link of a note
196
- share-links delete Delete a share link
197
-
198
- folders search Search user or team folders by keyword
199
-
200
- schema Print JSON Schema of a command (for agents)
201
-
202
- GLOBAL OPTIONS
203
- --hostname <url> API base URL (default: https://api.tiro.ooo)
204
- --json Force JSON output
205
- --pretty Force pretty (human) output
206
- --quiet Suppress non-error output
207
- --verbose Verbose logging to stderr
208
- --no-color Disable ANSI colors
209
- --help Show help for a command
210
- --version Print version
211
-
212
- ENVIRONMENT
213
- TIRO_TOKEN Bearer token (overrides keychain)
214
- TIRO_HOSTNAME API base URL
215
- TIRO_OUTPUT_DIR Default --output-dir for export commands
216
- NO_COLOR Disable colors (https://no-color.org/)
217
-
218
- EXAMPLES
219
- tiro auth login
220
- tiro notes search "Q3 Planning" --since 7d --json
221
- tiro notes get <guid> --output ./meeting.md --include transcript,summary
222
- tiro notes export --query "..." --output-dir ./backup --include transcript,summary
223
- ```
224
-
225
- ## 7. 출력 / 파일 / 에러 계약
226
-
227
- ### 7.1 형식 자동 선택
228
- - `process.stdout.isTTY` true → pretty
229
- - `process.stdout.isTTY` false (pipe / file redirect) → JSON
230
- - `--json` / `--pretty` 로 강제
231
-
232
- ### 7.2 파일 저장 시 stdout 계약
233
- 파일 저장 (`--output`, `--output-dir`) 시 stdout 은 metadata 만:
234
- ```json
235
- {"saved":"./meeting.md","size":5234,"format":"md","guid":"note-guid-123"}
236
- ```
237
-
238
- ### 7.3 파일 형식
239
- - **`.md`**: 인간 친화. YAML frontmatter + 마크다운 본문 + 화자별 단락
240
- - **`.json`**: 구조화. 전체 객체 그대로 (agent 파싱 친화)
241
- - **`.txt`**: 토큰 절약 / embedding 친화. `[화자] 텍스트` 만
242
-
243
- ### 7.4 NDJSON streaming
244
- list / search 결과는 line-delimited (한 줄에 한 객체) — agent 가 `head` / `jq` 로 점진 처리
245
-
246
- ### 7.5 Manifest (`manifest.jsonl`)
247
- bulk export 자동 생성. 한 줄에 한 항목:
248
- ```jsonl
249
- {"guid":"note-guid-123","title":"Weekly standup","path":"./n1.md","size":5234,"status":"ok"}
250
- {"guid":"note-guid-789","path":null,"error":{"code":"not_found","message":"..."}}
251
- ```
252
-
253
- ### 7.6 에러 계약 (Bolta 패턴)
254
- ```json
255
- {
256
- "ok": false,
257
- "error": {
258
- "code": "auth_required",
259
- "message": "Token expired. Run `tiro auth login` to refresh.",
260
- "suggestion": "tiro auth login",
261
- "errorType": "unauthorized",
262
- "httpStatus": 401,
263
- "requestId": "req_abc123"
264
- }
265
- }
266
- ```
267
-
268
- ### 7.7 Exit codes (sysexits.h)
269
- | Code | 의미 |
270
- |---|---|
271
- | 0 | 성공 |
272
- | 1 | generic error |
273
- | 2 | usage error |
274
- | 4 | auth required |
275
- | 64 | EX_USAGE |
276
- | 65 | EX_DATAERR |
277
- | 78 | EX_CONFIG (자격증명 부재) |
278
-
279
- ### 7.8 토큰 노출 방지
280
- - 절대 stdout 에 토큰 직접 출력 금지
281
- - 로그 (`--verbose`) 에 토큰 prefix 4글자만
282
- - error message 에 token 헤더 echo 금지
283
-
284
- ## 8. 의존성
285
-
286
- | | 패키지 | 이유 |
287
- |---|---|---|
288
- | runtime | `commander@12` | 인자 파싱, 자동 help |
289
- | runtime | `@napi-rs/keyring@1` | OS keychain (keytar 보다 모던, prebuild 안정) |
290
- | runtime | `conf@13` | XDG 호환 config 파일 |
291
- | runtime | `open@10` | 브라우저 자동 오픈 |
292
- | runtime | `zod@3` | 응답 런타임 검증 (mcp-server 와 일관) |
293
- | dev | `typescript@5.6+` | 타입 |
294
- | dev | `tsup@8` | esbuild 기반 번들 |
295
- | dev | `tsx@4` | TS 직접 실행 (dev) |
296
- | dev | `vitest@2` | 단위 테스트 |
297
- | dev | `@types/node@20` | Node 타입 |
298
-
299
- > HTTP 는 Node 20 내장 fetch — 추가 dep 0
300
- > Color 는 ANSI 코드 + isTTY 체크 자체 구현 — chalk/ora 미사용
301
-
302
- ## 9. 프로젝트 구조
303
-
304
- ```
305
- tiro-cli/
306
- ├── src/
307
- │ ├── bin/tiro.ts # entry + commander setup
308
- │ ├── commands/
309
- │ │ ├── auth/{login,logout,status}.ts
310
- │ │ ├── notes/{list,search,get,transcript,export}.ts
311
- │ │ ├── templates/{list,get}.ts
312
- │ │ ├── share-links/{create,get,delete}.ts
313
- │ │ ├── folders/search.ts
314
- │ │ └── schema.ts
315
- │ └── lib/
316
- │ ├── api/{client,types}.ts
317
- │ ├── auth/{flow,keychain,pkce,loopback,browser}.ts
318
- │ ├── output/{format,tty,manifest}.ts
319
- │ ├── error.ts
320
- │ └── config.ts
321
- ├── test/
322
- ├── package.json
323
- ├── tsconfig.json
324
- └── SPEC.md / README.md
325
- ```
326
-
327
- ## 10. 출시 마일스톤
328
-
329
- **v0.1 (이번 작업)** — auth 흐름 작동 + `tiro --help`
330
- - `tiro auth login` (Authorization Code + PKCE + loopback)
331
- - `tiro auth status`
332
- - `tiro auth logout`
333
- - `tiro --help`, `tiro --version`
334
-
335
- **v0.2** — notes core
336
- - `tiro notes list / search / get / transcript`
337
- - `--output`, `--format md|json|txt`
338
-
339
- **v0.3** — bulk export (킬러)
340
- - `tiro notes export` (manifest.jsonl)
341
-
342
- **v0.4** — 나머지
343
- - `tiro templates / share-links / folders / schema`
344
-
345
- **v1.0** — 안정화
346
- - 단위 테스트 ≥80%, e2e (assert_cmd 패턴), npm publish
347
-
348
- ## 11. 미결 (다음 회차)
349
-
350
- - [ ] OAuth `client_id` 캐싱 정책 (DCR 30일 TTL — 재등록 자동화?)
351
- - [ ] Homebrew tap (`plato-corp/tiro`) — npm 단독 vs 병행
352
- - [ ] 텔레메트리 정책 (opt-out by default? GitHub CLI 백래시 사례)
353
- - [ ] Device Flow (RFC 8628) 백엔드 추가 — headless/SSH 지원 시점
354
- - [ ] `tiro completion bash|zsh|fish` 셸 자동완성
355
- - [ ] OpenAPI codegen (`openapi-typescript` + `openapi-fetch`) 도입 시점
356
-
357
- ## 12. 참고
358
-
359
- - 1차 회차 결정: project memory `project_tiro_cli_design.md`
360
- - 위키 분석: `mcp-vs-api-vs-cli-agent-hands-feet.md` (yeoul, blog raw material)
361
- - API SSoT: `external-api-docs/openapi.yaml`
362
- - MCP 현재: tiro-mcp-server PR #21 머지 후 14 tools (voice 제거됨)
363
- - Backend OAuth: `api/.../McpOAuthController.kt`, `core/.../McpJwtService.kt`, `DynamicClient.kt`
364
- - 레퍼런스: gh CLI (Auth Code + GH_TOKEN), Stripe CLI (Device Flow), Bolta (`@bolta-io/cli`, agent-first)