@theplato/tiro-cli 0.1.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/README.md +294 -0
- package/SPEC.md +364 -0
- package/dist/bin/tiro.js +1468 -0
- package/dist/bin/tiro.js.map +1 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# tiro CLI
|
|
2
|
+
|
|
3
|
+
> AI notes & transcripts — an **agent-first** command line for [Tiro](https://tiro.ooo).
|
|
4
|
+
> The "feet" to MCP's "hands": filesystem-heavy, pipe-friendly, context-economical.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Quick start
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install -g @theplato/tiro-cli
|
|
12
|
+
tiro auth login # browser opens; OAuth + PKCE
|
|
13
|
+
tiro notes search --speaker "김철수" --since 7d --json
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Already authenticated through MCP? Reuse your token:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
TIRO_TOKEN="$(your_token)" tiro notes list
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Why tiro CLI
|
|
25
|
+
|
|
26
|
+
Tiro already ships an [MCP server](https://mcp.tiro.ooo/mcp) for Claude Desktop, Cursor, and Code. So why a CLI?
|
|
27
|
+
|
|
28
|
+
| | MCP | tiro CLI |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| Result lands in | Agent **context window** | **Filesystem** (or stdout pipe) |
|
|
31
|
+
| Token cost / session | thousands (schema injected) | dozens (per call) |
|
|
32
|
+
| Hosts | MCP-aware only | Any shell — CI, cron, ad-hoc agents |
|
|
33
|
+
| Best for | small reads, multi-turn reasoning | bulk export, file ops, scripting |
|
|
34
|
+
|
|
35
|
+
The CLI is **not a replacement for MCP**. It's the same data through a different surface optimized for filesystem-heavy and shell-native workflows.
|
|
36
|
+
|
|
37
|
+
→ Background reading: [MCP vs API vs CLI — the hands and feet of agents](https://github.com/plato-corp/tiro/blob/main/docs/mcp-vs-api-vs-cli-agent-hands-feet.md) (TBD)
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Features
|
|
42
|
+
|
|
43
|
+
- 🔐 **OAuth Authorization Code + PKCE** — no copy-paste tokens, no secrets in shell history. Tokens live in OS Keychain.
|
|
44
|
+
- 📁 **`--output` writes to disk** — stdout becomes a single line of metadata. Agents stay context-light.
|
|
45
|
+
- 🧵 **NDJSON streams by default** — pipe to `jq`, `head`, `xargs`. No buffer-in-memory surprises.
|
|
46
|
+
- 🤖 **Agent-aware** — TTY detection auto-selects pretty vs JSON. `error.suggestion` field for auto-recovery.
|
|
47
|
+
- 🪞 **Self-describing** *(soon)* — `tiro schema notes export` returns input/output JSON Schema.
|
|
48
|
+
- 🚫 **No voice in v1** — intentionally matches MCP feature parity (audio uploads belong elsewhere).
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
### npm (recommended)
|
|
55
|
+
```bash
|
|
56
|
+
npm install -g @theplato/tiro-cli
|
|
57
|
+
tiro --version
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### From source (during alpha)
|
|
61
|
+
```bash
|
|
62
|
+
git clone git@github.com:plato-corp/tiro-cli.git
|
|
63
|
+
cd tiro-cli
|
|
64
|
+
npm install
|
|
65
|
+
npm run build
|
|
66
|
+
npm link
|
|
67
|
+
tiro --help
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Homebrew *(planned for v1.0)*
|
|
71
|
+
```bash
|
|
72
|
+
brew install plato-corp/tap/tiro
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**System requirements**: Node.js 20+. macOS / Linux / Windows.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Authentication
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
tiro auth login # opens browser, OAuth Authorization Code + PKCE
|
|
83
|
+
tiro auth status # shows current account + token expiry
|
|
84
|
+
tiro auth logout # clears keychain + DCR client cache
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Tokens are stored in the OS-native credential manager:
|
|
88
|
+
- **macOS** — Keychain
|
|
89
|
+
- **Linux** — Secret Service (requires `gnome-keyring` or `kwallet`)
|
|
90
|
+
- **Windows** — Credential Manager
|
|
91
|
+
|
|
92
|
+
For CI / headless / agent use, set `TIRO_TOKEN`:
|
|
93
|
+
```bash
|
|
94
|
+
export TIRO_TOKEN=... # overrides keychain
|
|
95
|
+
tiro notes list
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Usage
|
|
101
|
+
|
|
102
|
+
### List recent notes
|
|
103
|
+
```bash
|
|
104
|
+
tiro notes list # pretty table in TTY
|
|
105
|
+
tiro notes list --json # NDJSON for pipes
|
|
106
|
+
tiro notes list --folder <id> --limit 100 --cursor <token>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Search by speaker, date, folder
|
|
110
|
+
```bash
|
|
111
|
+
tiro notes search --speaker "김철수" --since 7d
|
|
112
|
+
tiro notes search "quarterly review" --since 2026-04-01 --until 2026-05-01
|
|
113
|
+
tiro notes search --speaker "이영희" --folder <id> --json | jq '.[] | .guid'
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`--since` / `--until` accept ISO-8601 (`2026-04-01T10:00:00Z`) or relative (`7d`, `24h`, `30m`).
|
|
117
|
+
|
|
118
|
+
### Get one note → stdout
|
|
119
|
+
```bash
|
|
120
|
+
tiro notes get <guid> # markdown to TTY
|
|
121
|
+
tiro notes get <guid> --format json # JSON to stdout
|
|
122
|
+
tiro notes get <guid> --include transcript # include paragraphs
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Get one note → file (agent-friendly)
|
|
126
|
+
```bash
|
|
127
|
+
tiro notes get <guid> --output ./meeting.md --include transcript
|
|
128
|
+
# stdout: {"ok":true,"data":{"saved":"./meeting.md","size":12450,"format":"md","guid":"...","title":"..."}}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
This is the killer pattern for agents — **the actual content goes to disk; only metadata returns to the context window**.
|
|
132
|
+
|
|
133
|
+
### Raw transcript only
|
|
134
|
+
```bash
|
|
135
|
+
tiro notes transcript <guid> # txt by default
|
|
136
|
+
tiro notes transcript <guid> --output ./transcript.md
|
|
137
|
+
tiro notes transcript <guid> --format json --output ./paragraphs.json
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Commands
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
tiro auth login Sign in via OAuth (browser-based, PKCE)
|
|
146
|
+
tiro auth status Show current account and scopes
|
|
147
|
+
tiro auth logout Clear stored credentials
|
|
148
|
+
|
|
149
|
+
tiro notes list List recent notes (pretty | NDJSON)
|
|
150
|
+
tiro notes search Search by speaker / date / folder / keyword
|
|
151
|
+
tiro notes get Get a single note (stdout or file)
|
|
152
|
+
tiro notes transcript Get raw transcript paragraphs
|
|
153
|
+
|
|
154
|
+
# Coming in v0.3 / v0.4:
|
|
155
|
+
tiro notes export Bulk-export search results to a directory
|
|
156
|
+
tiro templates list/get Document templates
|
|
157
|
+
tiro share-links {C/R/D} Manage note share links
|
|
158
|
+
tiro folders search Search user / team folders
|
|
159
|
+
tiro schema [<command>] Print JSON Schema (for agents)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Full sample of every command: `tiro <command> --help`.
|
|
163
|
+
Full design spec: [`SPEC.md`](./SPEC.md).
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Global options
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
--hostname <url> API base URL (default: https://api.tiro.ooo)
|
|
171
|
+
--json Force JSON output (NDJSON for lists)
|
|
172
|
+
--pretty Force pretty (human) output
|
|
173
|
+
--quiet Suppress non-error output
|
|
174
|
+
--verbose Verbose logging to stderr
|
|
175
|
+
--no-color Disable ANSI colors
|
|
176
|
+
-h, --help Show help
|
|
177
|
+
-v, --version Print version
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Environment variables
|
|
181
|
+
|
|
182
|
+
| Variable | Purpose |
|
|
183
|
+
|---|---|
|
|
184
|
+
| `TIRO_TOKEN` | Bearer token (overrides keychain — for CI / agents) |
|
|
185
|
+
| `TIRO_HOSTNAME` | API base URL |
|
|
186
|
+
| `TIRO_OUTPUT_DIR` | Default `--output-dir` for export commands |
|
|
187
|
+
| `NO_COLOR` | Disable colors ([no-color.org](https://no-color.org/)) |
|
|
188
|
+
|
|
189
|
+
## Exit codes
|
|
190
|
+
|
|
191
|
+
| Code | Meaning |
|
|
192
|
+
|---|---|
|
|
193
|
+
| `0` | success |
|
|
194
|
+
| `1` | generic error |
|
|
195
|
+
| `2` | usage error |
|
|
196
|
+
| `4` | auth required |
|
|
197
|
+
| `64` | EX_USAGE |
|
|
198
|
+
| `65` | EX_DATAERR |
|
|
199
|
+
| `78` | EX_CONFIG (no token, run `tiro auth login`) |
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## For AI agents
|
|
204
|
+
|
|
205
|
+
If you're an agent (Claude Code, Cursor, ChatGPT) calling tiro CLI from a shell:
|
|
206
|
+
|
|
207
|
+
1. **Default to `--json`**: predictable shape, NDJSON for streams.
|
|
208
|
+
2. **Always use `--output`** for `notes get` / `notes transcript` — keeps the actual content out of your context window. The stdout response is one line of metadata.
|
|
209
|
+
3. **Pipe instead of buffer**: `tiro notes search ... --json | jq -c '.[] | select(.duration > 600)'`.
|
|
210
|
+
4. **Read errors as JSON**: `error.code`, `error.errorType`, `error.suggestion` are stable. The `suggestion` field is designed for auto-recovery (e.g. `"tiro auth login"` → run that next).
|
|
211
|
+
5. **Respect exit codes**: `4` = auth needed, `78` = no token configured.
|
|
212
|
+
|
|
213
|
+
Example agent flow:
|
|
214
|
+
```bash
|
|
215
|
+
# 1. Search → metadata only
|
|
216
|
+
tiro notes search --speaker "김철수" --since 30d --output ./april.jsonl
|
|
217
|
+
# 2. Pull guids without loading bodies
|
|
218
|
+
cat ./april.jsonl | jq -r '.guid' > guids.txt
|
|
219
|
+
# 3. Download transcripts to disk, manifest tracks status
|
|
220
|
+
xargs -I{} tiro notes get {} --output ./notes/{}.md --include transcript < guids.txt
|
|
221
|
+
# Total context cost: ~80 tokens for 100 notes.
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Development
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
npm install
|
|
230
|
+
npm run dev -- --help # tsx live-reload
|
|
231
|
+
npm run typecheck # tsc --noEmit
|
|
232
|
+
npm run test # vitest run
|
|
233
|
+
npm run build # tsup → dist/bin/tiro.js
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Stack: TypeScript ESM (Node 20+), [`commander`](https://github.com/tj/commander.js) for parsing, [`zod`](https://zod.dev) for response validation, [`@napi-rs/keyring`](https://github.com/napi-rs/node-rs/tree/main/packages/keyring) for OS keychain, [`open`](https://github.com/sindresorhus/open) for browser launch.
|
|
237
|
+
|
|
238
|
+
See [`CLAUDE.md`](./CLAUDE.md) for working with this repo via Claude Code.
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Architecture
|
|
243
|
+
|
|
244
|
+
The CLI sits on the public Tiro API alongside the MCP server, sharing the same OAuth provider:
|
|
245
|
+
|
|
246
|
+
```
|
|
247
|
+
┌──────────────────────────────┐
|
|
248
|
+
│ Tiro Backend (Kotlin) │
|
|
249
|
+
│ /v1/external/* + /v1/mcp/* │
|
|
250
|
+
└──────────────┬────────────────┘
|
|
251
|
+
│
|
|
252
|
+
┌────────────────────┼────────────────────┐
|
|
253
|
+
▼ ▼ ▼
|
|
254
|
+
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
255
|
+
│ tiro CLI │ │ Tiro MCP │ │ Web app │
|
|
256
|
+
│ (this repo) │ │ (mcp.tiro) │ │ │
|
|
257
|
+
└──────────────┘ └──────────────┘ └──────────────┘
|
|
258
|
+
(filesystem, (in-context, (humans)
|
|
259
|
+
bulk, pipe) schema-typed)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
OAuth Authorization Code + PKCE flow:
|
|
263
|
+
1. Dynamic Client Registration (RFC 7591) — no preconfigured client_id needed
|
|
264
|
+
2. Loopback redirect on an ephemeral `127.0.0.1:<port>`
|
|
265
|
+
3. PKCE S256 challenge, no client secret stored
|
|
266
|
+
4. JWT (HS256) issued for 180 days, stored in OS Keychain
|
|
267
|
+
5. Same token works on `/v1/external/*` and `/v1/mcp/*` (audience-list overlap)
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Roadmap
|
|
272
|
+
|
|
273
|
+
- ✅ **v0.1** — Auth (`login`, `status`, `logout`) + `--help`
|
|
274
|
+
- 🚧 **v0.2** — Notes core (`list`, `search`, `get`, `transcript`)
|
|
275
|
+
- ⏳ **v0.3** — `notes export` (bulk → directory + manifest.jsonl)
|
|
276
|
+
- ⏳ **v0.4** — Templates, share-links, folders, `schema`
|
|
277
|
+
- ⏳ **v1.0** — Stable. Tests, docs polish, npm publish.
|
|
278
|
+
- 🔮 **v1.x** — Device Flow (RFC 8628), shell completions, self-update
|
|
279
|
+
|
|
280
|
+
See [`SPEC.md`](./SPEC.md) for the full v1 design.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Contributing
|
|
285
|
+
|
|
286
|
+
This repo is currently private alpha. Feedback and bug reports go to the internal Slack `#tiro-backend` channel; once we open up, this section will list public contribution guidelines.
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## License
|
|
291
|
+
|
|
292
|
+
Proprietary. © ThePlato. All rights reserved.
|
|
293
|
+
|
|
294
|
+
> Once we ship v1.0, the license decision (MIT / Apache 2.0 / proprietary) will be revisited.
|
package/SPEC.md
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
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 --speaker "김철수" --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":"주간 미팅","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)
|