@theplato/tiro-cli 0.2.2 → 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/AGENTS.md +90 -0
- package/README.md +23 -2
- package/dist/bin/tiro.js +197 -30
- package/package.json +4 -3
- package/SPEC.md +0 -364
- package/dist/bin/tiro.js.map +0 -1
package/AGENTS.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Tiro for Agents
|
|
2
|
+
|
|
3
|
+
`@theplato/tiro-cli` is the agent's **feet** — a read-heavy CLI surface for Tiro
|
|
4
|
+
notes & transcripts. For interactive **hands** (tool calls inside an agent
|
|
5
|
+
loop), use the hosted Tiro MCP at `https://mcp.tiro.ooo/mcp`.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @theplato/tiro-cli
|
|
11
|
+
tiro auth login
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Connect MCP to Claude Code
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
tiro mcp install
|
|
18
|
+
# prints: claude mcp add --transport http tiro https://mcp.tiro.ooo/mcp
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or run that command directly to register the hosted MCP.
|
|
22
|
+
|
|
23
|
+
## Discovery (machine-readable)
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
tiro --help
|
|
27
|
+
tiro --version
|
|
28
|
+
tiro mcp info --json
|
|
29
|
+
tiro <group> <verb> --help
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Output contract
|
|
33
|
+
|
|
34
|
+
| Context | stdout | stderr |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| TTY (pretty) | rendered markdown / text | progress, prompts |
|
|
37
|
+
| pipe / `--json` | JSON envelope | progress, prompts |
|
|
38
|
+
| `--output <path>` | single metadata JSON line | progress, prompts |
|
|
39
|
+
|
|
40
|
+
The metadata line includes `saved`, `size`, `format`, `guid`. Agents can
|
|
41
|
+
parse it line-by-line without buffering the file.
|
|
42
|
+
|
|
43
|
+
## JSON envelope
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{ "ok": true, "data": <payload> }
|
|
47
|
+
{ "ok": false, "error": { "code": "...", "message": "...", "errorType": "..." } }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`errorType` ∈ `bad_request | unauthorized | forbidden | not_found | conflict |
|
|
51
|
+
internal_error`. Exit codes follow `sysexits.h` (`0` ok, `64` usage, `69`
|
|
52
|
+
unauthorized, `70` internal).
|
|
53
|
+
|
|
54
|
+
## Common flows
|
|
55
|
+
|
|
56
|
+
| Goal | Command |
|
|
57
|
+
|---|---|
|
|
58
|
+
| List recent notes | `tiro notes list --since 7d --json` |
|
|
59
|
+
| Search notes | `tiro notes search "Q3" --since 30d --json` |
|
|
60
|
+
| Full note metadata | `tiro notes get <guid> --json` |
|
|
61
|
+
| MCP-shape transcript JSON | `tiro notes transcript <guid> --format json` |
|
|
62
|
+
| Save markdown for ingest | `tiro notes transcript <guid> --output ./n.md --no-timestamps` |
|
|
63
|
+
| Save plain text | `tiro notes transcript <guid> --format txt --output ./n.txt` |
|
|
64
|
+
|
|
65
|
+
The `--format json` output of `transcript` is byte-identical to the MCP
|
|
66
|
+
`get_note_transcript` tool result, so agents can swap surfaces without
|
|
67
|
+
changing parsers.
|
|
68
|
+
|
|
69
|
+
## Surface separation
|
|
70
|
+
|
|
71
|
+
- **CLI (this)** — synchronous, file-output-friendly, agent-controlled
|
|
72
|
+
- **MCP** — interactive tool calls inside an agent loop
|
|
73
|
+
(https://mcp.tiro.ooo/mcp)
|
|
74
|
+
- **HTTP API** — server-to-server; see the OpenAPI spec at
|
|
75
|
+
https://api-docs.tiro.ooo
|
|
76
|
+
|
|
77
|
+
When in doubt: use **CLI for batch ingest** (download, write to disk,
|
|
78
|
+
context-light); use **MCP for interactive lookups** during a conversation.
|
|
79
|
+
|
|
80
|
+
## Authentication
|
|
81
|
+
|
|
82
|
+
Two ways to authenticate:
|
|
83
|
+
|
|
84
|
+
1. **Interactive** — `tiro auth login` opens a browser, completes OAuth, and
|
|
85
|
+
stores the token in the OS keychain.
|
|
86
|
+
2. **Headless / CI** — set `TIRO_TOKEN=<bearer>` in the environment;
|
|
87
|
+
the keychain is bypassed and tokens are never written to disk.
|
|
88
|
+
|
|
89
|
+
The CLI never prints tokens to stdout. `tiro auth status` shows only the
|
|
90
|
+
first 4 characters of the access token.
|
package/README.md
CHANGED
|
@@ -140,11 +140,29 @@ This is the killer pattern for agents — **the actual content goes to disk; onl
|
|
|
140
140
|
```bash
|
|
141
141
|
tiro notes transcript <guid> # md by default in TTY, txt in pipe
|
|
142
142
|
tiro notes transcript <guid> --output ./transcript.md
|
|
143
|
+
tiro notes transcript <guid> --output ./clean.md --no-timestamps # strip ### time headers
|
|
143
144
|
tiro notes transcript <guid> --format json --output ./paragraphs.json # MCP-shape JSON
|
|
144
145
|
```
|
|
145
146
|
|
|
147
|
+
In markdown, the timestamp now appears once per paragraph as a `### mm:ss`
|
|
148
|
+
header instead of being attached to every speaker line. Use
|
|
149
|
+
`--no-timestamps` to omit them entirely (handy for raw saving / LLM ingest).
|
|
150
|
+
|
|
146
151
|
`--format json` returns the exact shape MCP's `get_note_transcript` emits (`{noteGuid, title, participants, createdAt, recordingDurationSeconds, paragraphs[]}` with each paragraph carrying `segments[]` of `{content, speaker:{label,name}|null}`).
|
|
147
152
|
|
|
153
|
+
### Connect to Claude Code (MCP)
|
|
154
|
+
```bash
|
|
155
|
+
tiro mcp install
|
|
156
|
+
# prints: claude mcp add --transport http tiro https://mcp.tiro.ooo/mcp
|
|
157
|
+
|
|
158
|
+
tiro mcp info --json # endpoint, transport, install command
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The CLI is the agent's *feet* (read, save, browse). The hosted MCP at
|
|
162
|
+
[mcp.tiro.ooo](https://mcp.tiro.ooo/mcp) is the agent's *hands* (interactive
|
|
163
|
+
tool calls inside the loop). See [`AGENTS.md`](./AGENTS.md) for the full
|
|
164
|
+
agent contract.
|
|
165
|
+
|
|
148
166
|
---
|
|
149
167
|
|
|
150
168
|
## Commands
|
|
@@ -159,7 +177,10 @@ tiro notes search Deep keyword search (notes + documents hydrated)
|
|
|
159
177
|
tiro notes get Get a single note (stdout or file)
|
|
160
178
|
tiro notes transcript Get the full transcript (matches MCP get_note_transcript)
|
|
161
179
|
|
|
162
|
-
|
|
180
|
+
tiro mcp info Show hosted MCP endpoint info
|
|
181
|
+
tiro mcp install Print one-line claude-mcp-add command
|
|
182
|
+
|
|
183
|
+
# Coming in v0.4:
|
|
163
184
|
tiro notes export Bulk-export search results to a directory
|
|
164
185
|
tiro templates list/get Document templates
|
|
165
186
|
tiro share-links {C/R/D} Manage note share links
|
|
@@ -168,7 +189,7 @@ tiro schema [<command>] Print JSON Schema (for agents)
|
|
|
168
189
|
```
|
|
169
190
|
|
|
170
191
|
Full sample of every command: `tiro <command> --help`.
|
|
171
|
-
|
|
192
|
+
Agent contract: [`AGENTS.md`](./AGENTS.md).
|
|
172
193
|
Release history: [`CHANGELOG.md`](./CHANGELOG.md).
|
|
173
194
|
|
|
174
195
|
---
|
package/dist/bin/tiro.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/bin/tiro.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command13 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/lib/version.ts
|
|
7
7
|
import { readFileSync } from "fs";
|
|
@@ -155,6 +155,11 @@ function base64url(buf) {
|
|
|
155
155
|
|
|
156
156
|
// src/lib/auth/loopback.ts
|
|
157
157
|
import http from "http";
|
|
158
|
+
|
|
159
|
+
// src/lib/auth/assets.generated.ts
|
|
160
|
+
var TIRO_LOGO_BROWN_DATA_URI = "data:image/png;base64,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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACA/78dOBAAAAAAELQ/9SIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAP3ZeKxkxamrQAAAABJRU5ErkJggg==";
|
|
161
|
+
|
|
162
|
+
// src/lib/auth/loopback.ts
|
|
158
163
|
async function startLoopbackServer() {
|
|
159
164
|
let pendingResolve = null;
|
|
160
165
|
let pendingReject = null;
|
|
@@ -178,22 +183,17 @@ async function startLoopbackServer() {
|
|
|
178
183
|
const error = url.searchParams.get("error");
|
|
179
184
|
const errorDesc = url.searchParams.get("error_description") ?? "";
|
|
180
185
|
if (error) {
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
if (pendingReject) settle(pendingReject, new Error(
|
|
186
|
+
const detail = errorDesc ? ` \u2014 ${errorDesc}` : "";
|
|
187
|
+
respondError(res, 500, `OAuth error: ${error}${detail}`);
|
|
188
|
+
if (pendingReject) settle(pendingReject, new Error(`OAuth error: ${error}${detail}`));
|
|
184
189
|
return;
|
|
185
190
|
}
|
|
186
191
|
if (!code || !state) {
|
|
187
|
-
|
|
192
|
+
respondError(res, 400, "Missing `code` or `state` in callback URL.");
|
|
188
193
|
if (pendingReject) settle(pendingReject, new Error("Missing code or state"));
|
|
189
194
|
return;
|
|
190
195
|
}
|
|
191
|
-
|
|
192
|
-
res,
|
|
193
|
-
200,
|
|
194
|
-
"Login successful",
|
|
195
|
-
"You're signed in. You can close this tab and return to the terminal."
|
|
196
|
-
);
|
|
196
|
+
respondSuccess(res);
|
|
197
197
|
if (pendingResolve) settle(pendingResolve, { code, state });
|
|
198
198
|
});
|
|
199
199
|
await new Promise((resolve4) => {
|
|
@@ -227,25 +227,124 @@ async function startLoopbackServer() {
|
|
|
227
227
|
}
|
|
228
228
|
};
|
|
229
229
|
}
|
|
230
|
-
function
|
|
231
|
-
|
|
230
|
+
function escapeHtml(s) {
|
|
231
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
232
|
+
}
|
|
233
|
+
function respondSuccess(res) {
|
|
234
|
+
const html = renderPage({
|
|
235
|
+
kind: "success",
|
|
236
|
+
title: "You're signed in",
|
|
237
|
+
body: "You can close this tab and return to your terminal."
|
|
238
|
+
});
|
|
239
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
240
|
+
res.end(html);
|
|
241
|
+
}
|
|
242
|
+
function respondError(res, status, body) {
|
|
243
|
+
const html = renderPage({
|
|
244
|
+
kind: "error",
|
|
245
|
+
title: "Login failed",
|
|
246
|
+
body
|
|
247
|
+
});
|
|
248
|
+
res.writeHead(status, { "Content-Type": "text/html; charset=utf-8" });
|
|
249
|
+
res.end(html);
|
|
250
|
+
}
|
|
251
|
+
function renderPage(p) {
|
|
252
|
+
const safeTitle = escapeHtml(p.title);
|
|
253
|
+
const safeBody = escapeHtml(p.body);
|
|
254
|
+
const titleColor = p.kind === "error" ? "var(--error)" : "var(--brown-800)";
|
|
255
|
+
const logoOpacity = p.kind === "error" ? "0.4" : "1";
|
|
256
|
+
const closeScript = p.kind === "success" ? `<script>setTimeout(function(){try{window.close();}catch(e){}}, 2000);</script>` : "";
|
|
257
|
+
const hint = p.kind === "success" ? `<p class="hint">Closing this tab automatically…</p>` : "";
|
|
258
|
+
return `<!doctype html>
|
|
232
259
|
<html lang="en">
|
|
233
260
|
<head>
|
|
234
261
|
<meta charset="utf-8">
|
|
235
|
-
<
|
|
262
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
263
|
+
<title>Tiro \u2014 ${safeTitle}</title>
|
|
264
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
265
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
266
|
+
<link href="https://fonts.googleapis.com/css2?family=Averia+Serif+Libre:wght@300;400;700&display=swap" rel="stylesheet">
|
|
236
267
|
<style>
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
268
|
+
:root {
|
|
269
|
+
--stone-cream: #fffef7;
|
|
270
|
+
--stone-400: #78716c;
|
|
271
|
+
--brown-800: #3a2018;
|
|
272
|
+
--brown-700: #5c372b;
|
|
273
|
+
--brown-500: #7a4b3d;
|
|
274
|
+
--brown-400: #885f53;
|
|
275
|
+
--error: #b91c1c;
|
|
276
|
+
}
|
|
277
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
278
|
+
html, body { height: 100%; }
|
|
279
|
+
body {
|
|
280
|
+
font-family: 'Averia Serif Libre', Georgia, 'Times New Roman', serif;
|
|
281
|
+
background: var(--stone-cream);
|
|
282
|
+
color: var(--brown-800);
|
|
283
|
+
display: flex;
|
|
284
|
+
flex-direction: column;
|
|
285
|
+
align-items: center;
|
|
286
|
+
justify-content: space-between;
|
|
287
|
+
padding: 4rem 1.5rem 2.5rem;
|
|
288
|
+
min-height: 100vh;
|
|
289
|
+
}
|
|
290
|
+
.top-spacer { height: 1px; flex-shrink: 0; }
|
|
291
|
+
main {
|
|
292
|
+
display: flex;
|
|
293
|
+
flex-direction: column;
|
|
294
|
+
align-items: center;
|
|
295
|
+
text-align: center;
|
|
296
|
+
max-width: 640px;
|
|
297
|
+
}
|
|
298
|
+
.logo {
|
|
299
|
+
width: 160px;
|
|
300
|
+
height: auto;
|
|
301
|
+
margin-bottom: 1.5rem;
|
|
302
|
+
object-fit: contain;
|
|
303
|
+
opacity: ${logoOpacity};
|
|
304
|
+
}
|
|
305
|
+
h1 {
|
|
306
|
+
font-family: 'Averia Serif Libre', Georgia, 'Times New Roman', serif;
|
|
307
|
+
font-size: 36px;
|
|
308
|
+
line-height: 42px;
|
|
309
|
+
font-weight: 400;
|
|
310
|
+
color: ${titleColor};
|
|
311
|
+
}
|
|
312
|
+
.body {
|
|
313
|
+
margin-top: 1.5rem;
|
|
314
|
+
font-size: 20px;
|
|
315
|
+
line-height: 28px;
|
|
316
|
+
font-weight: 300;
|
|
317
|
+
color: var(--brown-500);
|
|
318
|
+
}
|
|
319
|
+
.hint {
|
|
320
|
+
margin-top: 0.75rem;
|
|
321
|
+
font-size: 20px;
|
|
322
|
+
line-height: 28px;
|
|
323
|
+
font-weight: 300;
|
|
324
|
+
color: var(--brown-400);
|
|
325
|
+
}
|
|
326
|
+
footer {
|
|
327
|
+
font-size: 12px;
|
|
328
|
+
line-height: 16px;
|
|
329
|
+
color: var(--stone-400);
|
|
330
|
+
text-align: center;
|
|
331
|
+
}
|
|
332
|
+
footer a { color: inherit; text-decoration: none; }
|
|
333
|
+
footer a:hover { text-decoration: underline; }
|
|
240
334
|
</style>
|
|
241
335
|
</head>
|
|
242
336
|
<body>
|
|
243
|
-
<
|
|
244
|
-
<
|
|
337
|
+
<div class="top-spacer"></div>
|
|
338
|
+
<main>
|
|
339
|
+
<img class="logo" src="${TIRO_LOGO_BROWN_DATA_URI}" alt="Tiro">
|
|
340
|
+
<h1>${safeTitle}</h1>
|
|
341
|
+
<p class="body">${safeBody}</p>
|
|
342
|
+
${hint}
|
|
343
|
+
</main>
|
|
344
|
+
<footer>© 2026 <a href="https://tiro.ooo">The Plato Inc.</a> — All rights reserved.</footer>
|
|
345
|
+
${closeScript}
|
|
245
346
|
</body>
|
|
246
347
|
</html>`;
|
|
247
|
-
res.writeHead(status, { "Content-Type": "text/html; charset=utf-8" });
|
|
248
|
-
res.end(html);
|
|
249
348
|
}
|
|
250
349
|
|
|
251
350
|
// src/lib/auth/browser.ts
|
|
@@ -1289,8 +1388,9 @@ function renderTranscriptJson(t) {
|
|
|
1289
1388
|
return `${JSON.stringify(t, null, 2)}
|
|
1290
1389
|
`;
|
|
1291
1390
|
}
|
|
1292
|
-
function renderTranscriptMarkdown(t) {
|
|
1293
|
-
const
|
|
1391
|
+
function renderTranscriptMarkdown(t, opts = {}) {
|
|
1392
|
+
const showTs = opts.timestamps !== false;
|
|
1393
|
+
const anchor = showTs ? anchorTime(t) : null;
|
|
1294
1394
|
const lines = [];
|
|
1295
1395
|
lines.push(`# ${t.title}`, "");
|
|
1296
1396
|
if (t.participants.length > 0) {
|
|
@@ -1298,11 +1398,13 @@ function renderTranscriptMarkdown(t) {
|
|
|
1298
1398
|
}
|
|
1299
1399
|
lines.push("## Transcript", "");
|
|
1300
1400
|
for (const p of t.paragraphs) {
|
|
1301
|
-
|
|
1401
|
+
if (showTs) {
|
|
1402
|
+
const ts = elapsed(p.timeFrom, anchor);
|
|
1403
|
+
if (ts) lines.push(`### ${ts}`, "");
|
|
1404
|
+
}
|
|
1302
1405
|
for (const s of p.segments) {
|
|
1303
1406
|
const who = s.speaker?.name ?? s.speaker?.label ?? "Unknown";
|
|
1304
|
-
|
|
1305
|
-
lines.push(`**[${tag}]** ${s.content}`);
|
|
1407
|
+
lines.push(`**${who}**: ${s.content}`);
|
|
1306
1408
|
}
|
|
1307
1409
|
lines.push("");
|
|
1308
1410
|
}
|
|
@@ -1571,10 +1673,11 @@ function registerNotesTranscript(parent) {
|
|
|
1571
1673
|
).option("--output <path>", "Write to file (stdout = single metadata line)").option(
|
|
1572
1674
|
"--format <md|json|txt>",
|
|
1573
1675
|
"Output format (default: md if TTY, txt when piped; json mirrors MCP)"
|
|
1574
|
-
).option("--force", "Overwrite existing file at --output path").addHelpText("after", `
|
|
1676
|
+
).option("--force", "Overwrite existing file at --output path").option("--no-timestamps", "Omit paragraph timestamp headers (md format only)").addHelpText("after", `
|
|
1575
1677
|
Examples:
|
|
1576
1678
|
tiro notes transcript <guid> # md in TTY, txt in pipe
|
|
1577
1679
|
tiro notes transcript <guid> --format md --output ./t.md
|
|
1680
|
+
tiro notes transcript <guid> --format md --no-timestamps # no time headers
|
|
1578
1681
|
tiro notes transcript <guid> --format json # MCP-shape JSON
|
|
1579
1682
|
tiro notes transcript <guid> --format txt --output ./embed.txt
|
|
1580
1683
|
|
|
@@ -1589,7 +1692,7 @@ get_note_transcript so agents can swap surfaces without changing parsers.
|
|
|
1589
1692
|
const note = await client.getJson(`/v1/external/notes/${guid}`, NoteSchema);
|
|
1590
1693
|
const paragraphs = await fetchAllParagraphs2(client, guid);
|
|
1591
1694
|
const mcp = buildMcpTranscript(note, paragraphs);
|
|
1592
|
-
const content = format === "json" ? renderTranscriptJson(mcp) : format === "md" ? renderTranscriptMarkdown(mcp) : renderTranscriptText(mcp);
|
|
1695
|
+
const content = format === "json" ? renderTranscriptJson(mcp) : format === "md" ? renderTranscriptMarkdown(mcp, { timestamps: opts.timestamps !== false }) : renderTranscriptText(mcp);
|
|
1593
1696
|
if (opts.output) {
|
|
1594
1697
|
const result = await writeFileAtomic(opts.output, content, {
|
|
1595
1698
|
...opts.force === true && { force: true }
|
|
@@ -1674,6 +1777,68 @@ function registerNotes(program) {
|
|
|
1674
1777
|
registerNotesTranscript(notes);
|
|
1675
1778
|
}
|
|
1676
1779
|
|
|
1780
|
+
// src/commands/mcp/index.ts
|
|
1781
|
+
import "commander";
|
|
1782
|
+
|
|
1783
|
+
// src/commands/mcp/info.ts
|
|
1784
|
+
import "commander";
|
|
1785
|
+
function registerMcpInfo(parent) {
|
|
1786
|
+
parent.command("info").description("Show Tiro MCP endpoint and connection instructions").action((_opts, cmd) => {
|
|
1787
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1788
|
+
printOutput(
|
|
1789
|
+
{
|
|
1790
|
+
ok: true,
|
|
1791
|
+
data: {
|
|
1792
|
+
name: HOSTED_MCP_NAME,
|
|
1793
|
+
transport: "http",
|
|
1794
|
+
url: HOSTED_MCP_URL,
|
|
1795
|
+
docs: HOSTED_MCP_DOCS,
|
|
1796
|
+
install: {
|
|
1797
|
+
claudeCode: `claude mcp add --transport http ${HOSTED_MCP_NAME} ${HOSTED_MCP_URL}`
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
},
|
|
1801
|
+
globalOpts
|
|
1802
|
+
);
|
|
1803
|
+
});
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
// src/commands/mcp/install.ts
|
|
1807
|
+
import "commander";
|
|
1808
|
+
function registerMcpInstall(parent) {
|
|
1809
|
+
parent.command("install").description("Print the one-line command to add Tiro MCP to Claude Code").option(
|
|
1810
|
+
"--print",
|
|
1811
|
+
"Print the raw `claude mcp add ...` command to stdout (default behavior)"
|
|
1812
|
+
).action((opts, cmd) => {
|
|
1813
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1814
|
+
const command = `claude mcp add --transport http ${HOSTED_MCP_NAME} ${HOSTED_MCP_URL}`;
|
|
1815
|
+
if (globalOpts.json) {
|
|
1816
|
+
printOutput(
|
|
1817
|
+
{ ok: true, data: { command, name: HOSTED_MCP_NAME, url: HOSTED_MCP_URL } },
|
|
1818
|
+
globalOpts
|
|
1819
|
+
);
|
|
1820
|
+
return;
|
|
1821
|
+
}
|
|
1822
|
+
if (process.stdout.isTTY && opts.print !== true) {
|
|
1823
|
+
process.stderr.write(
|
|
1824
|
+
"Run this in your terminal to register Tiro MCP with Claude Code:\n\n"
|
|
1825
|
+
);
|
|
1826
|
+
}
|
|
1827
|
+
process.stdout.write(`${command}
|
|
1828
|
+
`);
|
|
1829
|
+
});
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
// src/commands/mcp/index.ts
|
|
1833
|
+
var HOSTED_MCP_URL = "https://mcp.tiro.ooo/mcp";
|
|
1834
|
+
var HOSTED_MCP_NAME = "tiro";
|
|
1835
|
+
var HOSTED_MCP_DOCS = "https://api-docs.tiro.ooo/mcp";
|
|
1836
|
+
function registerMcp(program) {
|
|
1837
|
+
const mcp = program.command("mcp").description("Connect Tiro MCP from agent clients (Claude Code, etc.)");
|
|
1838
|
+
registerMcpInfo(mcp);
|
|
1839
|
+
registerMcpInstall(mcp);
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1677
1842
|
// src/lib/updateCheck.ts
|
|
1678
1843
|
import updateNotifier from "update-notifier";
|
|
1679
1844
|
import { readFile } from "fs/promises";
|
|
@@ -1733,6 +1898,8 @@ EXAMPLES
|
|
|
1733
1898
|
$ tiro notes search "Q3 Planning" --since 30d --json
|
|
1734
1899
|
$ tiro notes get <guid> --output ./meeting.md --include transcript
|
|
1735
1900
|
$ tiro notes transcript <guid> --format md --output ./transcript.md
|
|
1901
|
+
$ tiro notes transcript <guid> --format md --no-timestamps --output ./clean.md
|
|
1902
|
+
$ tiro mcp install # one-line setup for Claude Code
|
|
1736
1903
|
|
|
1737
1904
|
ENVIRONMENT
|
|
1738
1905
|
TIRO_TOKEN Bearer token (overrides keychain \u2014 for CI / agents)
|
|
@@ -1744,11 +1911,12 @@ DOCS
|
|
|
1744
1911
|
https://api-docs.tiro.ooo/cli
|
|
1745
1912
|
`;
|
|
1746
1913
|
function buildProgram() {
|
|
1747
|
-
const program = new
|
|
1914
|
+
const program = new Command13();
|
|
1748
1915
|
program.name("tiro").description("Tiro AI notes & transcripts \u2014 agent-first command line").version(VERSION, "-v, --version", "Print version").option("--hostname <url>", "API base URL (default: https://api.tiro.ooo)").option("--json", "Force JSON output").option("--pretty", "Force pretty (human) output").option("--quiet", "Suppress non-error output").option("--verbose", "Verbose logging to stderr").option("--no-color", "Disable ANSI colors").addHelpText("after", EXAMPLES);
|
|
1749
1916
|
program.showHelpAfterError("(run `tiro --help` for available commands)");
|
|
1750
1917
|
registerAuth(program);
|
|
1751
1918
|
registerNotes(program);
|
|
1919
|
+
registerMcp(program);
|
|
1752
1920
|
return program;
|
|
1753
1921
|
}
|
|
1754
1922
|
async function main() {
|
|
@@ -1797,4 +1965,3 @@ main().catch((err) => {
|
|
|
1797
1965
|
`);
|
|
1798
1966
|
process.exit(ExitCode.Generic);
|
|
1799
1967
|
});
|
|
1800
|
-
//# sourceMappingURL=tiro.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theplato/tiro-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Tiro AI notes & transcripts — agent-first command line",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,13 +9,14 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
11
|
"README.md",
|
|
12
|
-
"
|
|
12
|
+
"AGENTS.md"
|
|
13
13
|
],
|
|
14
14
|
"engines": {
|
|
15
15
|
"node": ">=20"
|
|
16
16
|
},
|
|
17
17
|
"scripts": {
|
|
18
|
-
"build": "tsup",
|
|
18
|
+
"build": "npm run embed-assets && tsup",
|
|
19
|
+
"embed-assets": "tsx scripts/embed-logo.ts",
|
|
19
20
|
"dev": "tsx src/bin/tiro.ts",
|
|
20
21
|
"start": "node dist/bin/tiro.js",
|
|
21
22
|
"typecheck": "tsc --noEmit",
|
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)
|
package/dist/bin/tiro.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/bin/tiro.ts","../../src/lib/version.ts","../../src/lib/error.ts","../../src/lib/output/tty.ts","../../src/lib/output/print.ts","../../src/commands/auth/index.ts","../../src/commands/auth/login.ts","../../src/lib/auth/flow.ts","../../src/lib/auth/pkce.ts","../../src/lib/auth/loopback.ts","../../src/lib/auth/browser.ts","../../src/lib/auth/keychain.ts","../../src/lib/auth/token.ts","../../src/lib/config.ts","../../src/commands/auth/status.ts","../../src/commands/auth/logout.ts","../../src/commands/notes/index.ts","../../src/commands/notes/list.ts","../../src/lib/api/client.ts","../../src/lib/api/types.ts","../../src/lib/util/parseDate.ts","../../src/lib/util/noteFilter.ts","../../src/commands/notes/search.ts","../../src/commands/notes/get.ts","../../src/lib/output/file.ts","../../src/lib/output/transcript.ts","../../src/lib/output/format.ts","../../src/commands/notes/transcript.ts","../../src/lib/updateCheck.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { VERSION } from \"../lib/version.ts\";\nimport { TiroError, ExitCode } from \"../lib/error.ts\";\nimport { printError } from \"../lib/output/print.ts\";\nimport { color } from \"../lib/output/tty.ts\";\nimport { registerAuth } from \"../commands/auth/index.ts\";\nimport { registerNotes } from \"../commands/notes/index.ts\";\nimport { emitUpdateBanner, startUpdateCheck } from \"../lib/updateCheck.ts\";\n\nconst EXAMPLES = `\nEXAMPLES\n $ tiro auth login\n $ tiro notes list --since 7d\n $ tiro notes search \"Q3 Planning\" --since 30d --json\n $ tiro notes get <guid> --output ./meeting.md --include transcript\n $ tiro notes transcript <guid> --format md --output ./transcript.md\n\nENVIRONMENT\n TIRO_TOKEN Bearer token (overrides keychain — for CI / agents)\n TIRO_HOSTNAME API base URL (default: https://api.tiro.ooo)\n NO_COLOR Disable colors (https://no-color.org)\n NO_UPDATE_NOTIFIER Set to \"1\" to disable the update banner\n\nDOCS\n https://api-docs.tiro.ooo/cli\n`;\n\nfunction buildProgram(): Command {\n const program = new Command();\n\n program\n .name(\"tiro\")\n .description(\"Tiro AI notes & transcripts — agent-first command line\")\n .version(VERSION, \"-v, --version\", \"Print version\")\n .option(\"--hostname <url>\", \"API base URL (default: https://api.tiro.ooo)\")\n .option(\"--json\", \"Force JSON output\")\n .option(\"--pretty\", \"Force pretty (human) output\")\n .option(\"--quiet\", \"Suppress non-error output\")\n .option(\"--verbose\", \"Verbose logging to stderr\")\n .option(\"--no-color\", \"Disable ANSI colors\")\n .addHelpText(\"after\", EXAMPLES);\n\n program.showHelpAfterError(\"(run `tiro --help` for available commands)\");\n\n registerAuth(program);\n registerNotes(program);\n\n return program;\n}\n\nasync function main(): Promise<void> {\n const program = buildProgram();\n const notifier = await startUpdateCheck();\n try {\n await program.parseAsync(process.argv);\n emitUpdateBanner(notifier);\n } catch (err) {\n handleError(err, program);\n }\n}\n\nfunction handleError(err: unknown, program: Command): never {\n const opts = program.opts<{ json?: boolean; quiet?: boolean }>();\n\n if (err instanceof TiroError) {\n if (opts.json) {\n printError(err.toJSON());\n } else if (!opts.quiet) {\n process.stderr.write(`${color(\"✗\", \"red\", opts)} ${err.message}\\n`);\n if (err.suggestion) {\n process.stderr.write(` ${color(\"→\", \"gray\", opts)} ${err.suggestion}\\n`);\n }\n }\n process.exit(err.exitCode);\n }\n\n if (err instanceof Error) {\n if (opts.json) {\n printError({\n ok: false,\n error: { code: \"internal_error\", message: err.message, errorType: \"internal_error\" },\n });\n } else if (!opts.quiet) {\n process.stderr.write(`${color(\"✗\", \"red\", opts)} ${err.message}\\n`);\n }\n process.exit(ExitCode.Generic);\n }\n\n process.stderr.write(`Unknown error: ${String(err)}\\n`);\n process.exit(ExitCode.Generic);\n}\n\nmain().catch((err) => {\n process.stderr.write(`Fatal: ${String(err)}\\n`);\n process.exit(ExitCode.Generic);\n});\n","import { readFileSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { dirname, resolve } from \"node:path\";\n\nconst HERE = dirname(fileURLToPath(import.meta.url));\nconst CANDIDATE_PATHS = [\n resolve(HERE, \"../../package.json\"),\n resolve(HERE, \"../../../package.json\"),\n];\n\ninterface CliPackage {\n name?: string;\n version?: string;\n}\n\nfunction readVersion(): string {\n for (const path of CANDIDATE_PATHS) {\n try {\n const raw = readFileSync(path, \"utf8\");\n const parsed = JSON.parse(raw) as CliPackage;\n if (typeof parsed.version === \"string\" && parsed.version.length > 0) {\n return parsed.version;\n }\n } catch {\n // try next candidate\n }\n }\n return \"0.0.0-unknown\";\n}\n\nexport const VERSION = readVersion();\n","export const ExitCode = {\n Ok: 0,\n Generic: 1,\n Usage: 2,\n AuthRequired: 4,\n ExUsage: 64,\n ExDataErr: 65,\n ExConfig: 78,\n} as const;\n\nexport type ExitCodeValue = (typeof ExitCode)[keyof typeof ExitCode];\n\nexport type ErrorType =\n | \"bad_request\"\n | \"unauthorized\"\n | \"forbidden\"\n | \"not_found\"\n | \"not_acceptable\"\n | \"conflict\"\n | \"payload_too_large\"\n | \"unprocessable_entity\"\n | \"too_many_requests\"\n | \"internal_error\"\n | \"auth_required\"\n | \"config_missing\"\n | \"network_error\";\n\nexport interface ErrorPayload {\n code: string;\n message: string;\n suggestion?: string;\n errorType?: ErrorType;\n httpStatus?: number;\n requestId?: string;\n}\n\nexport class TiroError extends Error {\n readonly code: string;\n readonly suggestion?: string;\n readonly errorType?: ErrorType;\n readonly httpStatus?: number;\n readonly requestId?: string;\n readonly exitCode: ExitCodeValue;\n\n constructor(payload: ErrorPayload, exitCode: ExitCodeValue = ExitCode.Generic) {\n super(payload.message);\n this.name = \"TiroError\";\n this.code = payload.code;\n this.suggestion = payload.suggestion;\n this.errorType = payload.errorType;\n this.httpStatus = payload.httpStatus;\n this.requestId = payload.requestId;\n this.exitCode = exitCode;\n }\n\n toJSON(): { ok: false; error: ErrorPayload } {\n return {\n ok: false,\n error: {\n code: this.code,\n message: this.message,\n ...(this.suggestion !== undefined && { suggestion: this.suggestion }),\n ...(this.errorType !== undefined && { errorType: this.errorType }),\n ...(this.httpStatus !== undefined && { httpStatus: this.httpStatus }),\n ...(this.requestId !== undefined && { requestId: this.requestId }),\n },\n };\n }\n}\n\nexport function authRequired(): TiroError {\n return new TiroError(\n {\n code: \"auth_required\",\n message:\n \"Not authenticated. Run `tiro auth login` to sign in, or set TIRO_TOKEN env var.\",\n suggestion: \"tiro auth login\",\n errorType: \"auth_required\",\n },\n ExitCode.AuthRequired,\n );\n}\n\nexport function configMissing(message: string, suggestion?: string): TiroError {\n return new TiroError(\n {\n code: \"config_missing\",\n message,\n ...(suggestion !== undefined && { suggestion }),\n errorType: \"config_missing\",\n },\n ExitCode.ExConfig,\n );\n}\n","export type OutputMode = \"json\" | \"pretty\";\n\nexport interface OutputOptions {\n json?: boolean;\n pretty?: boolean;\n noColor?: boolean;\n quiet?: boolean;\n}\n\nexport function resolveOutputMode(opts: OutputOptions): OutputMode {\n if (opts.json) return \"json\";\n if (opts.pretty) return \"pretty\";\n return process.stdout.isTTY ? \"pretty\" : \"json\";\n}\n\nexport function colorEnabled(opts: OutputOptions): boolean {\n if (opts.noColor) return false;\n if (process.env[\"NO_COLOR\"]) return false;\n if (process.env[\"FORCE_COLOR\"]) return true;\n return process.stdout.isTTY === true;\n}\n\nconst ANSI = {\n reset: \"\\x1b[0m\",\n bold: \"\\x1b[1m\",\n dim: \"\\x1b[2m\",\n red: \"\\x1b[31m\",\n green: \"\\x1b[32m\",\n yellow: \"\\x1b[33m\",\n blue: \"\\x1b[34m\",\n magenta: \"\\x1b[35m\",\n cyan: \"\\x1b[36m\",\n gray: \"\\x1b[90m\",\n} as const;\n\nexport function color(\n text: string,\n style: keyof typeof ANSI,\n opts: OutputOptions = {},\n): string {\n if (!colorEnabled(opts)) return text;\n return `${ANSI[style]}${text}${ANSI.reset}`;\n}\n","import { resolveOutputMode, type OutputOptions } from \"./tty.ts\";\n\nexport function printOutput(value: unknown, opts: OutputOptions = {}): void {\n if (opts.quiet) return;\n const mode = resolveOutputMode(opts);\n if (mode === \"json\") {\n process.stdout.write(`${JSON.stringify(value)}\\n`);\n } else {\n process.stdout.write(`${JSON.stringify(value, null, 2)}\\n`);\n }\n}\n\nexport function printError(value: unknown): void {\n process.stderr.write(`${JSON.stringify(value)}\\n`);\n}\n\nexport function printNdjson(item: unknown): void {\n process.stdout.write(`${JSON.stringify(item)}\\n`);\n}\n","import { Command } from \"commander\";\nimport { registerAuthLogin } from \"./login.ts\";\nimport { registerAuthStatus } from \"./status.ts\";\nimport { registerAuthLogout } from \"./logout.ts\";\n\nexport function registerAuth(program: Command): void {\n const auth = program.command(\"auth\").description(\"Manage authentication\");\n registerAuthLogin(auth);\n registerAuthStatus(auth);\n registerAuthLogout(auth);\n}\n","import { Command } from \"commander\";\nimport { performLogin } from \"../../lib/auth/flow.ts\";\nimport { printOutput } from \"../../lib/output/print.ts\";\nimport { color } from \"../../lib/output/tty.ts\";\nimport { getHostname } from \"../../lib/config.ts\";\n\ninterface LoginOptions {\n hostname?: string;\n noBrowser?: boolean;\n json?: boolean;\n pretty?: boolean;\n noColor?: boolean;\n quiet?: boolean;\n}\n\nexport function registerAuthLogin(parent: Command): void {\n parent\n .command(\"login\")\n .description(\"Sign in to Tiro via OAuth (browser-based, PKCE)\")\n .option(\"--hostname <url>\", \"API base URL (overrides config / TIRO_HOSTNAME)\")\n .option(\"--no-browser\", \"Print the URL instead of opening a browser\")\n .action(async (opts: LoginOptions, cmd: Command) => {\n const globalOpts = cmd.optsWithGlobals<LoginOptions>();\n const result = await performLogin({\n ...(opts.hostname !== undefined && { hostname: opts.hostname }),\n noBrowser: opts.noBrowser === true,\n onPrompt: (msg) => {\n if (globalOpts.quiet) return;\n process.stderr.write(`${color(msg, \"cyan\", globalOpts)}\\n`);\n },\n });\n\n const hostname = getHostname(opts.hostname);\n const expiresIso = new Date(result.expiresAt).toISOString();\n\n if (globalOpts.json) {\n printOutput(\n {\n ok: true,\n data: {\n signedIn: true,\n hostname,\n userId: result.userId ?? null,\n expiresAt: expiresIso,\n },\n },\n globalOpts,\n );\n } else if (!globalOpts.quiet) {\n process.stderr.write(`${color(\"✓\", \"green\", globalOpts)} Signed in to ${hostname}\\n`);\n if (result.userId) {\n process.stderr.write(` user: ${result.userId}\\n`);\n }\n process.stderr.write(` token expires: ${expiresIso}\\n`);\n }\n });\n}\n","import { z } from \"zod\";\nimport { TiroError, ExitCode } from \"../error.ts\";\nimport { generatePkce, generateState } from \"./pkce.ts\";\nimport { startLoopbackServer } from \"./loopback.ts\";\nimport { openBrowser } from \"./browser.ts\";\nimport { saveToken, type StoredToken } from \"./keychain.ts\";\nimport { decodeJwtPayload } from \"./token.ts\";\nimport {\n getHostname,\n getOauthClientId,\n setOauthClientId,\n clearOauthClientId,\n} from \"../config.ts\";\n\nconst RegisterResponseSchema = z.object({\n client_id: z.string(),\n client_secret: z.string().optional(),\n});\n\nconst TokenResponseSchema = z.object({\n access_token: z.string(),\n token_type: z.string(),\n expires_in: z.number().optional(),\n scope: z.string().optional(),\n});\n\nconst CALLBACK_TIMEOUT_MS = 5 * 60 * 1000;\nconst DEFAULT_SCOPE = \"mcp:notes:read\";\n\nexport interface LoginOptions {\n hostname?: string;\n noBrowser?: boolean;\n scope?: string;\n onPrompt?: (msg: string) => void;\n}\n\nexport interface LoginResult {\n hostname: string;\n userId: string | undefined;\n expiresAt: number;\n}\n\nexport async function performLogin(options: LoginOptions = {}): Promise<LoginResult> {\n const hostname = getHostname(options.hostname);\n const onPrompt = options.onPrompt ?? ((msg: string) => process.stderr.write(`${msg}\\n`));\n\n const loopback = await startLoopbackServer();\n\n try {\n const clientId = await ensureOauthClient(hostname, loopback.redirectUri);\n const { codeVerifier, codeChallenge } = generatePkce();\n const state = generateState();\n\n const authorizeUrl = buildAuthorizeUrl({\n hostname,\n clientId,\n redirectUri: loopback.redirectUri,\n state,\n codeChallenge,\n scope: options.scope ?? DEFAULT_SCOPE,\n });\n\n if (options.noBrowser) {\n onPrompt(`Open this URL in your browser:\\n${authorizeUrl}`);\n } else {\n onPrompt(`Opening browser for sign-in...`);\n onPrompt(`If the browser does not open, visit:\\n${authorizeUrl}`);\n await openBrowser(authorizeUrl);\n }\n\n const callback = await loopback.waitForCallback(CALLBACK_TIMEOUT_MS);\n\n if (callback.state !== state) {\n throw new TiroError(\n {\n code: \"auth_state_mismatch\",\n message: \"OAuth state mismatch — possible CSRF. Aborting.\",\n errorType: \"unauthorized\",\n },\n ExitCode.Generic,\n );\n }\n\n const tokenRes = await exchangeToken({\n hostname,\n clientId,\n code: callback.code,\n redirectUri: loopback.redirectUri,\n codeVerifier,\n });\n\n const expiresAt = computeExpiry(tokenRes.expires_in);\n const payload = decodeJwtPayload(tokenRes.access_token);\n const userId = typeof payload?.[\"sub\"] === \"string\" ? (payload[\"sub\"] as string) : undefined;\n\n const stored: StoredToken = {\n accessToken: tokenRes.access_token,\n tokenType: tokenRes.token_type,\n expiresAt,\n hostname,\n ...(tokenRes.scope !== undefined && { scope: tokenRes.scope }),\n ...(userId !== undefined && { userId }),\n };\n saveToken(stored);\n\n return { hostname, userId, expiresAt };\n } finally {\n loopback.close();\n }\n}\n\nasync function ensureOauthClient(hostname: string, redirectUri: string): Promise<string> {\n const cached = getOauthClientId();\n if (cached) return cached;\n\n const url = `${hostname}/v1/mcp/oauth/register`;\n let res: Response;\n try {\n res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n client_name: \"tiro-cli\",\n redirect_uris: [redirectUri],\n grant_types: [\"authorization_code\"],\n response_types: [\"code\"],\n token_endpoint_auth_method: \"none\",\n scope: DEFAULT_SCOPE,\n }),\n });\n } catch (err) {\n throw new TiroError(\n {\n code: \"network_error\",\n message: `Failed to reach ${hostname}: ${(err as Error).message}`,\n errorType: \"network_error\",\n suggestion: \"Check your network connection or --hostname.\",\n },\n ExitCode.Generic,\n );\n }\n\n if (!res.ok) {\n const detail = await safeText(res);\n throw new TiroError(\n {\n code: \"oauth_register_failed\",\n message: `Dynamic Client Registration failed: HTTP ${res.status}`,\n errorType: \"internal_error\",\n httpStatus: res.status,\n ...(detail !== \"\" && { suggestion: detail.slice(0, 200) }),\n },\n ExitCode.Generic,\n );\n }\n\n const json = (await res.json()) as unknown;\n const parsed = RegisterResponseSchema.safeParse(json);\n if (!parsed.success) {\n throw new TiroError(\n {\n code: \"oauth_register_invalid\",\n message: \"Registration response did not match expected shape.\",\n errorType: \"internal_error\",\n },\n ExitCode.Generic,\n );\n }\n\n setOauthClientId(parsed.data.client_id);\n return parsed.data.client_id;\n}\n\ninterface AuthorizeUrlInput {\n hostname: string;\n clientId: string;\n redirectUri: string;\n state: string;\n codeChallenge: string;\n scope: string;\n}\n\nfunction buildAuthorizeUrl(input: AuthorizeUrlInput): string {\n const u = new URL(`${input.hostname}/v1/mcp/oauth/authorize`);\n u.searchParams.set(\"response_type\", \"code\");\n u.searchParams.set(\"client_id\", input.clientId);\n u.searchParams.set(\"redirect_uri\", input.redirectUri);\n u.searchParams.set(\"state\", input.state);\n u.searchParams.set(\"code_challenge\", input.codeChallenge);\n u.searchParams.set(\"code_challenge_method\", \"S256\");\n u.searchParams.set(\"scope\", input.scope);\n return u.toString();\n}\n\ninterface ExchangeInput {\n hostname: string;\n clientId: string;\n code: string;\n redirectUri: string;\n codeVerifier: string;\n}\n\nasync function exchangeToken(input: ExchangeInput): Promise<z.infer<typeof TokenResponseSchema>> {\n const url = `${input.hostname}/v1/mcp/oauth/token`;\n const body = new URLSearchParams({\n grant_type: \"authorization_code\",\n code: input.code,\n redirect_uri: input.redirectUri,\n client_id: input.clientId,\n code_verifier: input.codeVerifier,\n });\n\n let res: Response;\n try {\n res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: body.toString(),\n });\n } catch (err) {\n throw new TiroError(\n {\n code: \"network_error\",\n message: `Failed to reach ${input.hostname}: ${(err as Error).message}`,\n errorType: \"network_error\",\n },\n ExitCode.Generic,\n );\n }\n\n if (!res.ok) {\n if (res.status === 400 || res.status === 401) {\n // Cached client_id may have expired (Redis 30-day TTL). Force re-registration on next try.\n clearOauthClientId();\n }\n const detail = await safeText(res);\n throw new TiroError(\n {\n code: \"oauth_token_failed\",\n message: `Token exchange failed: HTTP ${res.status}`,\n errorType: \"unauthorized\",\n httpStatus: res.status,\n ...(detail !== \"\" && { suggestion: detail.slice(0, 200) }),\n },\n ExitCode.AuthRequired,\n );\n }\n\n const json = (await res.json()) as unknown;\n const parsed = TokenResponseSchema.safeParse(json);\n if (!parsed.success) {\n throw new TiroError(\n {\n code: \"oauth_token_invalid\",\n message: \"Token response did not match expected shape.\",\n errorType: \"internal_error\",\n },\n ExitCode.Generic,\n );\n }\n return parsed.data;\n}\n\nfunction computeExpiry(expiresIn?: number): number {\n // McpJwtService issues 180-day tokens; if expires_in absent, fall back to 180 days.\n const fallbackSeconds = 180 * 24 * 60 * 60;\n const seconds = expiresIn ?? fallbackSeconds;\n return Date.now() + seconds * 1000;\n}\n\nasync function safeText(res: Response): Promise<string> {\n try {\n return await res.text();\n } catch {\n return \"\";\n }\n}\n","import { createHash, randomBytes } from \"node:crypto\";\n\nexport interface PkcePair {\n codeVerifier: string;\n codeChallenge: string;\n method: \"S256\";\n}\n\nexport function generatePkce(): PkcePair {\n const codeVerifier = base64url(randomBytes(32));\n const codeChallenge = base64url(createHash(\"sha256\").update(codeVerifier).digest());\n return { codeVerifier, codeChallenge, method: \"S256\" };\n}\n\nexport function generateState(): string {\n return base64url(randomBytes(24));\n}\n\nfunction base64url(buf: Buffer): string {\n return buf\n .toString(\"base64\")\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n","import http from \"node:http\";\nimport type { AddressInfo } from \"node:net\";\n\nexport interface CallbackResult {\n code: string;\n state: string;\n}\n\nexport interface LoopbackServer {\n redirectUri: string;\n port: number;\n waitForCallback: (timeoutMs: number) => Promise<CallbackResult>;\n close: () => void;\n}\n\nexport async function startLoopbackServer(): Promise<LoopbackServer> {\n let pendingResolve: ((r: CallbackResult) => void) | null = null;\n let pendingReject: ((e: Error) => void) | null = null;\n\n const settle = (\n fn: ((arg: CallbackResult) => void) | ((arg: Error) => void),\n arg: CallbackResult | Error,\n ): void => {\n pendingResolve = null;\n pendingReject = null;\n (fn as (arg: CallbackResult | Error) => void)(arg);\n };\n\n const server = http.createServer((req, res) => {\n if (!req.url) {\n res.writeHead(400).end();\n return;\n }\n const url = new URL(req.url, `http://127.0.0.1`);\n if (url.pathname !== \"/callback\") {\n res.writeHead(404, { \"Content-Type\": \"text/plain\" }).end(\"Not found\");\n return;\n }\n\n const code = url.searchParams.get(\"code\");\n const state = url.searchParams.get(\"state\");\n const error = url.searchParams.get(\"error\");\n const errorDesc = url.searchParams.get(\"error_description\") ?? \"\";\n\n if (error) {\n const msg = `OAuth error: ${error}${errorDesc ? ` — ${errorDesc}` : \"\"}`;\n respondPage(res, 500, \"Login failed\", msg);\n if (pendingReject) settle(pendingReject, new Error(msg));\n return;\n }\n if (!code || !state) {\n respondPage(res, 400, \"Missing parameters\", \"Missing `code` or `state`.\");\n if (pendingReject) settle(pendingReject, new Error(\"Missing code or state\"));\n return;\n }\n\n respondPage(\n res,\n 200,\n \"Login successful\",\n \"You're signed in. You can close this tab and return to the terminal.\",\n );\n if (pendingResolve) settle(pendingResolve, { code, state });\n });\n\n await new Promise<void>((resolve) => {\n server.listen(0, \"127.0.0.1\", () => resolve());\n });\n\n const address = server.address() as AddressInfo;\n const port = address.port;\n const redirectUri = `http://127.0.0.1:${port}/callback`;\n\n return {\n redirectUri,\n port,\n waitForCallback(timeoutMs: number) {\n return new Promise<CallbackResult>((resolve, reject) => {\n const timer = setTimeout(() => {\n pendingResolve = null;\n pendingReject = null;\n reject(new Error(`Timed out waiting for OAuth callback (${timeoutMs}ms)`));\n }, timeoutMs);\n pendingResolve = (r) => {\n clearTimeout(timer);\n resolve(r);\n };\n pendingReject = (e) => {\n clearTimeout(timer);\n reject(e);\n };\n });\n },\n close() {\n server.close();\n },\n };\n}\n\nfunction respondPage(\n res: http.ServerResponse,\n status: number,\n title: string,\n body: string,\n): void {\n const html = `<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <title>${title}</title>\n <style>\n body { font-family: -apple-system, system-ui, sans-serif; padding: 4rem; max-width: 32rem; margin: 0 auto; color: #1a1a1a; }\n h1 { font-size: 1.5rem; margin-bottom: 1rem; }\n p { color: #555; line-height: 1.5; }\n </style>\n</head>\n<body>\n <h1>${title}</h1>\n <p>${body}</p>\n</body>\n</html>`;\n res.writeHead(status, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(html);\n}\n","import open from \"open\";\n\nexport async function openBrowser(url: string): Promise<void> {\n try {\n await open(url, { wait: false });\n } catch {\n // Best-effort. Caller already prints the URL for manual fallback.\n }\n}\n","import { Entry } from \"@napi-rs/keyring\";\nimport { TiroError, ExitCode } from \"../error.ts\";\n\nconst SERVICE = \"io.tiro.cli\";\nconst ACCOUNT = \"default\";\n\nexport interface StoredToken {\n accessToken: string;\n tokenType: string;\n expiresAt: number;\n scope?: string;\n hostname: string;\n userId?: string;\n}\n\nexport function saveToken(token: StoredToken): void {\n const entry = new Entry(SERVICE, ACCOUNT);\n try {\n entry.setPassword(JSON.stringify(token));\n } catch (err) {\n throw new TiroError(\n {\n code: \"keychain_write_failed\",\n message: `Failed to write to OS keychain: ${(err as Error).message}`,\n errorType: \"internal_error\",\n suggestion:\n process.platform === \"linux\"\n ? \"Linux requires a Secret Service daemon (gnome-keyring or kwallet). Or set TIRO_TOKEN env var.\"\n : \"Check OS keychain permissions, or set TIRO_TOKEN env var.\",\n },\n ExitCode.Generic,\n );\n }\n}\n\nexport function loadToken(): StoredToken | null {\n const entry = new Entry(SERVICE, ACCOUNT);\n let raw: string | null;\n try {\n raw = entry.getPassword();\n } catch {\n return null;\n }\n if (!raw) return null;\n try {\n return JSON.parse(raw) as StoredToken;\n } catch {\n return null;\n }\n}\n\nexport function deleteToken(): boolean {\n const entry = new Entry(SERVICE, ACCOUNT);\n try {\n return entry.deletePassword();\n } catch {\n return false;\n }\n}\n","import { loadToken, type StoredToken } from \"./keychain.ts\";\nimport { authRequired } from \"../error.ts\";\n\nexport interface ResolvedToken {\n accessToken: string;\n source: \"env\" | \"keychain\";\n hostname?: string;\n expiresAt?: number;\n userId?: string;\n}\n\nexport function resolveToken(): ResolvedToken | null {\n const envToken = process.env[\"TIRO_TOKEN\"];\n if (envToken) {\n return { accessToken: envToken, source: \"env\" };\n }\n const stored = loadToken();\n if (stored) {\n return {\n accessToken: stored.accessToken,\n source: \"keychain\",\n hostname: stored.hostname,\n expiresAt: stored.expiresAt,\n ...(stored.userId !== undefined && { userId: stored.userId }),\n };\n }\n return null;\n}\n\nexport function requireToken(): ResolvedToken {\n const t = resolveToken();\n if (!t) throw authRequired();\n return t;\n}\n\nexport function decodeJwtPayload(token: string): Record<string, unknown> | null {\n const parts = token.split(\".\");\n if (parts.length !== 3) return null;\n try {\n const payload = parts[1];\n if (!payload) return null;\n const json = Buffer.from(payload, \"base64url\").toString(\"utf8\");\n return JSON.parse(json) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\nexport function isTokenExpired(stored: StoredToken): boolean {\n return Date.now() >= stored.expiresAt;\n}\n","import Conf from \"conf\";\n\ninterface TiroConfigSchema {\n hostname: string;\n oauthClientId: string | null;\n oauthClientIdRegisteredAt: number | null;\n defaultOutputDir: string | null;\n}\n\nconst DEFAULT_HOSTNAME = \"https://api.tiro.ooo\";\n\nconst config = new Conf<TiroConfigSchema>({\n projectName: \"tiro\",\n defaults: {\n hostname: DEFAULT_HOSTNAME,\n oauthClientId: null,\n oauthClientIdRegisteredAt: null,\n defaultOutputDir: null,\n },\n});\n\nexport function getHostname(override?: string): string {\n if (override) return stripTrailingSlash(override);\n const env = process.env[\"TIRO_HOSTNAME\"];\n if (env) return stripTrailingSlash(env);\n return stripTrailingSlash(config.get(\"hostname\"));\n}\n\nexport function setHostname(hostname: string): void {\n config.set(\"hostname\", stripTrailingSlash(hostname));\n}\n\nexport function getOauthClientId(): string | null {\n const id = config.get(\"oauthClientId\");\n const registeredAt = config.get(\"oauthClientIdRegisteredAt\");\n if (!id || !registeredAt) return null;\n // DCR clients have 30-day TTL on the backend (Redis). Treat them as expired\n // a day earlier to avoid edge-case 401s during exchange.\n const ttlMs = 29 * 24 * 60 * 60 * 1000;\n if (Date.now() - registeredAt > ttlMs) return null;\n return id;\n}\n\nexport function setOauthClientId(clientId: string): void {\n config.set(\"oauthClientId\", clientId);\n config.set(\"oauthClientIdRegisteredAt\", Date.now());\n}\n\nexport function clearOauthClientId(): void {\n config.set(\"oauthClientId\", null);\n config.set(\"oauthClientIdRegisteredAt\", null);\n}\n\nexport function getDefaultOutputDir(): string | null {\n const env = process.env[\"TIRO_OUTPUT_DIR\"];\n if (env) return env;\n return config.get(\"defaultOutputDir\");\n}\n\nexport function getConfigPath(): string {\n return config.path;\n}\n\nfunction stripTrailingSlash(s: string): string {\n return s.endsWith(\"/\") ? s.slice(0, -1) : s;\n}\n","import { Command } from \"commander\";\nimport { resolveToken, decodeJwtPayload } from \"../../lib/auth/token.ts\";\nimport { printOutput } from \"../../lib/output/print.ts\";\nimport { color } from \"../../lib/output/tty.ts\";\nimport { authRequired } from \"../../lib/error.ts\";\n\ninterface StatusReport {\n signedIn: boolean;\n source: \"env\" | \"keychain\";\n hostname: string | null;\n userId: string | null;\n scope: string | null;\n expiresAt: string | null;\n expired: boolean;\n tokenPrefix: string;\n}\n\ninterface StatusOptions {\n json?: boolean;\n pretty?: boolean;\n noColor?: boolean;\n quiet?: boolean;\n}\n\nexport function registerAuthStatus(parent: Command): void {\n parent\n .command(\"status\")\n .description(\"Show current authenticated account and scopes\")\n .action(async (_opts: StatusOptions, cmd: Command) => {\n const globalOpts = cmd.optsWithGlobals<StatusOptions>();\n const t = resolveToken();\n if (!t) {\n throw authRequired();\n }\n\n const payload = decodeJwtPayload(t.accessToken);\n const sub = typeof payload?.[\"sub\"] === \"string\" ? (payload[\"sub\"] as string) : null;\n const exp = typeof payload?.[\"exp\"] === \"number\" ? (payload[\"exp\"] as number) * 1000 : null;\n const scope = typeof payload?.[\"scope\"] === \"string\" ? (payload[\"scope\"] as string) : null;\n\n const expMs = exp ?? t.expiresAt ?? null;\n const expired = expMs !== null && Date.now() >= expMs;\n\n const report: StatusReport = {\n signedIn: true,\n source: t.source,\n hostname: t.hostname ?? null,\n userId: t.userId ?? sub,\n scope,\n expiresAt: expMs ? new Date(expMs).toISOString() : null,\n expired,\n tokenPrefix: `${t.accessToken.slice(0, 4)}...***`,\n };\n\n if (globalOpts.json || !process.stdout.isTTY) {\n printOutput({ ok: true, data: report }, globalOpts);\n } else {\n const headIcon = expired ? color(\"!\", \"yellow\", globalOpts) : color(\"✓\", \"green\", globalOpts);\n const headText = expired ? \"Token expired\" : \"Signed in\";\n process.stdout.write(`${headIcon} ${headText}\\n`);\n process.stdout.write(` source: ${report.source}\\n`);\n if (report.hostname) process.stdout.write(` hostname: ${report.hostname}\\n`);\n if (report.userId) process.stdout.write(` user: ${report.userId}\\n`);\n if (report.scope) process.stdout.write(` scope: ${report.scope}\\n`);\n if (report.expiresAt) {\n const tag = expired ? color(\" (expired)\", \"red\", globalOpts) : \"\";\n process.stdout.write(` expires at: ${report.expiresAt}${tag}\\n`);\n }\n process.stdout.write(` token: ${report.tokenPrefix}\\n`);\n if (expired) {\n process.stdout.write(\n `\\n${color(\"→\", \"gray\", globalOpts)} Run \\`tiro auth login\\` to refresh.\\n`,\n );\n }\n }\n });\n}\n","import { Command } from \"commander\";\nimport { deleteToken } from \"../../lib/auth/keychain.ts\";\nimport { clearOauthClientId } from \"../../lib/config.ts\";\nimport { printOutput } from \"../../lib/output/print.ts\";\nimport { color } from \"../../lib/output/tty.ts\";\n\ninterface LogoutOptions {\n json?: boolean;\n pretty?: boolean;\n noColor?: boolean;\n quiet?: boolean;\n}\n\nexport function registerAuthLogout(parent: Command): void {\n parent\n .command(\"logout\")\n .description(\"Sign out and clear the stored token\")\n .action(async (_opts: LogoutOptions, cmd: Command) => {\n const globalOpts = cmd.optsWithGlobals<LogoutOptions>();\n const removed = deleteToken();\n clearOauthClientId();\n\n if (globalOpts.json) {\n printOutput({ ok: true, data: { signedOut: true, hadToken: removed } }, globalOpts);\n } else if (!globalOpts.quiet) {\n if (removed) {\n process.stderr.write(`${color(\"✓\", \"green\", globalOpts)} Signed out\\n`);\n } else {\n process.stderr.write(`${color(\"•\", \"gray\", globalOpts)} No token was stored\\n`);\n }\n }\n });\n}\n","import { Command } from \"commander\";\nimport { registerNotesList } from \"./list.ts\";\nimport { registerNotesSearch } from \"./search.ts\";\nimport { registerNotesGet } from \"./get.ts\";\nimport { registerNotesTranscript } from \"./transcript.ts\";\n\nexport function registerNotes(program: Command): void {\n const notes = program.command(\"notes\").description(\"List, search, and download notes\");\n registerNotesList(notes);\n registerNotesSearch(notes);\n registerNotesGet(notes);\n registerNotesTranscript(notes);\n}\n","import { Command } from \"commander\";\nimport { createApiClient } from \"../../lib/api/client.ts\";\nimport { NoteSchema, PageCursorResponseSchema } from \"../../lib/api/types.ts\";\nimport { printNdjson } from \"../../lib/output/print.ts\";\nimport { resolveOutputMode, color } from \"../../lib/output/tty.ts\";\nimport { parseDate } from \"../../lib/util/parseDate.ts\";\nimport { isVisibleNote } from \"../../lib/util/noteFilter.ts\";\n\ninterface ListOptions {\n keyword?: string;\n folder?: string;\n since?: string;\n until?: string;\n limit?: string;\n cursor?: string;\n includeUntitled?: boolean;\n hostname?: string;\n json?: boolean;\n pretty?: boolean;\n noColor?: boolean;\n quiet?: boolean;\n}\n\nconst ListResponseSchema = PageCursorResponseSchema(NoteSchema);\n\nconst DEFAULT_PAGE_SIZE = 100;\nconst MAX_PAGE_SIZE = 1000;\n\nconst HELP_AFTER = `\nExamples:\n tiro notes list --since 7d\n tiro notes list --keyword \"OKR\" --since 30d --json\n tiro notes list --folder <id> --limit 50\n\nKeyword matching:\n --keyword reorders results by OpenSearch relevance (case-insensitive,\n full-text against note title and paragraph content). When --keyword is\n set, nextCursor is always null. Without --keyword, results are ordered\n by createdAt desc.\n\nNote: placeholder notes (title='Untitled' or sourceType='onboarding') are\nfiltered out by default. A page of N may return fewer than N visible notes —\nkeep paginating to fetch more, or pass --include-untitled to surface them.\n`;\n\nexport function registerNotesList(parent: Command): void {\n parent\n .command(\"list\")\n .description(\"List notes (lightweight metadata).\")\n .option(\"--keyword <text>\", 'Reorder by OpenSearch relevance for this keyword (e.g. \"OKR\")')\n .option(\"--folder <id>\", \"Restrict to a folder and its descendants\")\n .option(\n \"--since <date>\",\n \"Inclusive lower bound on createdAt (ISO-8601 or relative: 7d, 24h, 30m)\",\n )\n .option(\"--until <date>\", \"Exclusive upper bound on createdAt\")\n .option(\n \"--limit <n>\",\n `Max results per page (default ${DEFAULT_PAGE_SIZE}, max ${MAX_PAGE_SIZE})`,\n )\n .option(\"--cursor <token>\", \"Continue a previous page\")\n .option(\n \"--include-untitled\",\n \"Include placeholder notes (title='Untitled' or sourceType='onboarding'). Default: hidden\",\n false,\n )\n .addHelpText(\"after\", HELP_AFTER)\n .action(async (opts: ListOptions, cmd: Command) => {\n const globalOpts = cmd.optsWithGlobals<ListOptions>();\n const client = createApiClient({\n ...(globalOpts.hostname !== undefined && { hostnameOverride: globalOpts.hostname }),\n });\n\n const params: Record<string, string | number | undefined> = {};\n if (opts.keyword) params[\"keyword\"] = opts.keyword;\n if (opts.folder) params[\"folderId\"] = opts.folder;\n if (opts.since) params[\"createdAtFrom\"] = parseDate(opts.since);\n if (opts.until) params[\"createdAtTo\"] = parseDate(opts.until);\n const size = clampLimit(opts.limit);\n if (size !== undefined) params[\"size\"] = size;\n if (opts.cursor) params[\"cursor\"] = opts.cursor;\n\n const res = await client.getJson(\"/v1/external/notes\", ListResponseSchema, params);\n const visible = opts.includeUntitled === true ? res.content : res.content.filter(isVisibleNote);\n\n const mode = resolveOutputMode(globalOpts);\n if (mode === \"json\") {\n for (const note of visible) printNdjson(note);\n if (res.nextCursor) printNdjson({ _cursor: res.nextCursor });\n } else {\n printPretty(visible, res.nextCursor, globalOpts);\n }\n });\n}\n\nfunction clampLimit(raw?: string): number | undefined {\n if (!raw) return undefined;\n const n = parseInt(raw, 10);\n if (!Number.isFinite(n) || n <= 0) return DEFAULT_PAGE_SIZE;\n return Math.min(n, MAX_PAGE_SIZE);\n}\n\ninterface NoteListItem {\n guid: string;\n title: string;\n createdAt: string;\n recordingDurationSeconds: number;\n webUrl: string;\n}\n\nfunction printPretty(\n notes: NoteListItem[],\n nextCursor: string | null,\n opts: { noColor?: boolean },\n): void {\n if (notes.length === 0) {\n process.stdout.write(`${color(\"(no notes)\", \"gray\", opts)}\\n`);\n return;\n }\n const titleWidth = computeTitleWidth();\n for (const n of notes) {\n const date = n.createdAt.slice(0, 10);\n const dur = formatDuration(n.recordingDurationSeconds);\n const title = truncate(n.title, titleWidth);\n process.stdout.write(\n `${color(date, \"gray\", opts)} ${color(n.guid, \"dim\", opts)} ${color(dur, \"cyan\", opts)} ${title}\\n`,\n );\n }\n if (nextCursor) {\n process.stdout.write(\n `${color(`\\n next: --cursor ${nextCursor}`, \"gray\", opts)}\\n`,\n );\n }\n}\n\nfunction computeTitleWidth(): number {\n const cols = process.stdout.columns;\n if (!cols || cols < 60) return 40;\n return Math.max(20, cols - 60);\n}\n\nfunction truncate(s: string, max: number): string {\n if (s.length <= max) return s;\n return s.slice(0, Math.max(0, max - 1)) + \"…\";\n}\n\nfunction formatDuration(sec: number): string {\n if (!sec || sec <= 0) return \"—\";\n const m = Math.floor(sec / 60);\n const s = Math.floor(sec % 60);\n if (m > 0) return `${m}m${s.toString().padStart(2, \"0\")}s`;\n return `${s}s`;\n}\n","import { z } from \"zod\";\nimport { TiroError, ExitCode, type ErrorType } from \"../error.ts\";\nimport { resolveToken } from \"../auth/token.ts\";\nimport { authRequired } from \"../error.ts\";\nimport { getHostname } from \"../config.ts\";\nimport { ApiErrorSchema } from \"./types.ts\";\n\nexport interface ApiClientOptions {\n hostnameOverride?: string;\n tokenOverride?: string;\n}\n\nexport class TiroApiClient {\n constructor(\n private readonly hostname: string,\n private readonly token: string,\n ) {}\n\n async getJson<S extends z.ZodTypeAny>(\n path: string,\n schema: S,\n params?: Record<string, string | number | undefined>,\n ): Promise<z.infer<S>> {\n const url = this.buildUrl(path, params);\n const res = await this.fetch(url, { method: \"GET\" });\n return this.parseJson(res, schema, \"GET\", path);\n }\n\n async postJson<S extends z.ZodTypeAny>(\n path: string,\n schema: S,\n body: unknown,\n ): Promise<z.infer<S>> {\n const url = this.buildUrl(path);\n const res = await this.fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n return this.parseJson(res, schema, \"POST\", path);\n }\n\n async putJson<S extends z.ZodTypeAny>(\n path: string,\n schema: S,\n body: unknown,\n ): Promise<z.infer<S>> {\n const url = this.buildUrl(path);\n const res = await this.fetch(url, {\n method: \"PUT\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n return this.parseJson(res, schema, \"PUT\", path);\n }\n\n async deleteVoid(path: string): Promise<void> {\n const url = this.buildUrl(path);\n const res = await this.fetch(url, { method: \"DELETE\" });\n if (!res.ok) throw await mapHttpError(res, \"DELETE\", path);\n }\n\n private buildUrl(path: string, params?: Record<string, string | number | undefined>): string {\n const base = path.startsWith(\"http\") ? path : `${this.hostname}${path}`;\n const u = new URL(base);\n if (params) {\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined && v !== null && v !== \"\") {\n u.searchParams.set(k, String(v));\n }\n }\n }\n return u.toString();\n }\n\n private async fetch(url: string, init: RequestInit): Promise<Response> {\n const headers = new Headers(init.headers);\n headers.set(\"Authorization\", `Bearer ${this.token}`);\n headers.set(\"Accept\", \"application/json\");\n try {\n return await fetch(url, { ...init, headers });\n } catch (err) {\n throw new TiroError(\n {\n code: \"network_error\",\n message: `Network error reaching ${this.hostname}: ${(err as Error).message}`,\n errorType: \"network_error\",\n suggestion: \"Check your network or --hostname.\",\n },\n ExitCode.Generic,\n );\n }\n }\n\n private async parseJson<S extends z.ZodTypeAny>(\n res: Response,\n schema: S,\n method: string,\n path: string,\n ): Promise<z.infer<S>> {\n if (!res.ok) throw await mapHttpError(res, method, path);\n let json: unknown;\n try {\n json = await res.json();\n } catch (err) {\n throw new TiroError(\n {\n code: \"invalid_response\",\n message: `Failed to parse JSON from ${method} ${path}: ${(err as Error).message}`,\n errorType: \"internal_error\",\n },\n ExitCode.Generic,\n );\n }\n const parsed = schema.safeParse(json);\n if (!parsed.success) {\n throw new TiroError(\n {\n code: \"schema_mismatch\",\n message: `Response shape did not match expected schema (${method} ${path}).`,\n errorType: \"internal_error\",\n suggestion: parsed.error.issues\n .slice(0, 3)\n .map((i) => `${i.path.join(\".\") || \"(root)\"}: ${i.message}`)\n .join(\"; \"),\n },\n ExitCode.Generic,\n );\n }\n return parsed.data;\n }\n}\n\nasync function mapHttpError(res: Response, method: string, path: string): Promise<TiroError> {\n const requestId = res.headers.get(\"x-request-id\") ?? undefined;\n const exitCode = res.status === 401 ? ExitCode.AuthRequired : ExitCode.Generic;\n\n const apiError = await tryParseApiError(res);\n if (apiError) {\n return new TiroError(\n {\n code: `${apiError.error.errorType}`,\n message: apiError.error.message,\n errorType: apiError.error.errorType as ErrorType,\n httpStatus: res.status,\n ...(requestId !== undefined && { requestId }),\n ...(res.status === 401 && {\n suggestion: \"Run `tiro auth login` to refresh.\",\n }),\n },\n exitCode,\n );\n }\n\n return new TiroError(\n {\n code: \"http_error\",\n message: `${method} ${path} failed: HTTP ${res.status} ${res.statusText}`,\n errorType: httpStatusToErrorType(res.status),\n httpStatus: res.status,\n ...(requestId !== undefined && { requestId }),\n },\n exitCode,\n );\n}\n\nfunction httpStatusToErrorType(status: number): ErrorType {\n if (status === 400) return \"bad_request\";\n if (status === 401) return \"unauthorized\";\n if (status === 403) return \"forbidden\";\n if (status === 404) return \"not_found\";\n if (status === 409) return \"conflict\";\n if (status === 413) return \"payload_too_large\";\n if (status === 422) return \"unprocessable_entity\";\n if (status === 429) return \"too_many_requests\";\n return \"internal_error\";\n}\n\nasync function tryParseApiError(res: Response): Promise<{ error: { code: number; errorType: string; message: string } } | null> {\n try {\n const json = await res.clone().json();\n const parsed = ApiErrorSchema.safeParse(json);\n return parsed.success ? parsed.data : null;\n } catch {\n return null;\n }\n}\n\nexport function createApiClient(opts: ApiClientOptions = {}): TiroApiClient {\n if (opts.tokenOverride) {\n return new TiroApiClient(getHostname(opts.hostnameOverride), opts.tokenOverride);\n }\n const t = resolveToken();\n if (!t) throw authRequired();\n const hostname = getHostname(opts.hostnameOverride ?? t.hostname);\n return new TiroApiClient(hostname, t.accessToken);\n}\n","import { z } from \"zod\";\n\nexport const CollaboratorSchema = z.object({\n guid: z.string(),\n name: z.string(),\n email: z.string(),\n role: z.enum([\"OWNER\", \"EDITOR\", \"VIEWER\"]),\n});\n\nexport const ParticipantSchema = z.object({\n name: z.string().nullable().optional(),\n email: z.string().nullable().optional(),\n});\n\nexport const NoteSchema = z\n .object({\n guid: z.string(),\n title: z.string(),\n createdAt: z.string(),\n updatedAt: z.string(),\n sourceType: z.string(),\n recordingDurationSeconds: z.number(),\n collaborators: z.array(CollaboratorSchema).optional().default([]),\n participants: z.array(ParticipantSchema).optional().default([]),\n webUrl: z.string(),\n recordingStartAt: z.string().nullable().optional(),\n recordingEndAt: z.string().nullable().optional(),\n })\n .passthrough();\nexport type Note = z.infer<typeof NoteSchema>;\n\nexport const TextObjectSchema = z.object({\n type: z.string(),\n content: z.string(),\n});\nexport type TextObject = z.infer<typeof TextObjectSchema>;\n\nexport const SpeakerInfoSchema = z.object({\n label: z.string(),\n personName: z.string().nullable().optional(),\n});\nexport type SpeakerInfo = z.infer<typeof SpeakerInfoSchema>;\n\nexport const DiarizedSegmentSchema = z.object({\n content: z.string(),\n speaker: SpeakerInfoSchema,\n});\nexport type DiarizedSegment = z.infer<typeof DiarizedSegmentSchema>;\n\nexport const ParagraphSchema = z\n .object({\n uuid: z.string(),\n transcribeLocale: z.string().nullable().optional(),\n transcript: TextObjectSchema.nullable().optional(),\n diarizedSegments: z.array(DiarizedSegmentSchema).nullable().optional(),\n timeFrom: z.string().nullable().optional(),\n timeTo: z.string().nullable().optional(),\n locked: z.boolean().optional(),\n })\n .passthrough();\nexport type Paragraph = z.infer<typeof ParagraphSchema>;\n\nexport const McpSegmentSchema = z.object({\n content: z.string(),\n speaker: z\n .object({\n label: z.string(),\n name: z.string().nullable(),\n })\n .nullable(),\n});\nexport type McpSegment = z.infer<typeof McpSegmentSchema>;\n\nexport const McpParagraphSchema = z.object({\n timeFrom: z.string().nullable(),\n timeTo: z.string().nullable(),\n segments: z.array(McpSegmentSchema),\n});\nexport type McpParagraph = z.infer<typeof McpParagraphSchema>;\n\nexport const McpTranscriptSchema = z.object({\n noteGuid: z.string(),\n title: z.string(),\n participants: z.array(z.string()),\n createdAt: z.string(),\n recordingDurationSeconds: z.number(),\n paragraphs: z.array(McpParagraphSchema),\n});\nexport type McpTranscript = z.infer<typeof McpTranscriptSchema>;\n\nexport const PageCursorResponseSchema = <T extends z.ZodTypeAny>(item: T) =>\n z.object({\n content: z.array(item),\n nextCursor: z.string().nullable(),\n });\n\nexport const SimpleListResponseSchema = <T extends z.ZodTypeAny>(item: T) =>\n z.object({\n content: z.array(item),\n });\n\nexport const ApiErrorSchema = z.object({\n error: z.object({\n code: z.number(),\n errorType: z.string(),\n message: z.string(),\n detail: z.string().nullable().optional(),\n }),\n});\nexport type ApiError = z.infer<typeof ApiErrorSchema>;\n","import { TiroError, ExitCode } from \"../error.ts\";\n\nconst RELATIVE_RE = /^(\\d+)([smhdw])$/i;\nconst UNIT_MS: Record<string, number> = {\n s: 1000,\n m: 60_000,\n h: 3_600_000,\n d: 86_400_000,\n w: 604_800_000,\n};\n\nexport function parseDate(input: string): string {\n const trimmed = input.trim();\n\n const rel = trimmed.match(RELATIVE_RE);\n if (rel) {\n const num = parseInt(rel[1] ?? \"\", 10);\n const unit = (rel[2] ?? \"\").toLowerCase();\n const factor = UNIT_MS[unit];\n if (!factor || !Number.isFinite(num)) {\n throw invalidDate(input);\n }\n return new Date(Date.now() - num * factor).toISOString();\n }\n\n const d = new Date(trimmed);\n if (Number.isNaN(d.getTime())) throw invalidDate(input);\n return d.toISOString();\n}\n\nfunction invalidDate(input: string): TiroError {\n return new TiroError(\n {\n code: \"invalid_date\",\n message: `Invalid date: \"${input}\". Use ISO-8601 (e.g. 2026-04-01T10:00:00Z) or relative (e.g. 7d, 24h, 30m).`,\n errorType: \"bad_request\",\n },\n ExitCode.Usage,\n );\n}\n","export interface FilterableNote {\n title?: string | null;\n sourceType?: string | null;\n}\n\nconst PLACEHOLDER_TITLE = \"Untitled\";\nconst PLACEHOLDER_SOURCE_TYPES = new Set<string>([\"onboarding\"]);\n\nexport function isVisibleNote(note: FilterableNote): boolean {\n if (note.title === PLACEHOLDER_TITLE) return false;\n if (note.sourceType !== null && note.sourceType !== undefined && PLACEHOLDER_SOURCE_TYPES.has(note.sourceType)) {\n return false;\n }\n return true;\n}\n","import { Command } from \"commander\";\nimport { createApiClient } from \"../../lib/api/client.ts\";\nimport { NoteSchema, PageCursorResponseSchema } from \"../../lib/api/types.ts\";\nimport { printNdjson } from \"../../lib/output/print.ts\";\nimport { resolveOutputMode, color } from \"../../lib/output/tty.ts\";\nimport { parseDate } from \"../../lib/util/parseDate.ts\";\nimport { isVisibleNote } from \"../../lib/util/noteFilter.ts\";\nimport { TiroError, ExitCode } from \"../../lib/error.ts\";\n\ninterface SearchOptions {\n keyword?: string;\n folder?: string;\n since?: string;\n until?: string;\n limit?: string;\n cursor?: string;\n includeUntitled?: boolean;\n hostname?: string;\n json?: boolean;\n pretty?: boolean;\n noColor?: boolean;\n quiet?: boolean;\n}\n\nconst SearchResponseSchema = PageCursorResponseSchema(NoteSchema).passthrough();\n\nconst DEFAULT_PAGE_SIZE = 100;\nconst MAX_PAGE_SIZE = 1000;\n\nconst HELP_AFTER = `\nExamples:\n tiro notes search \"Q3 Planning\"\n tiro notes search \"Acme Corp\" --since 7d --json\n tiro notes search \"release\" --since 2026-04-01 --until 2026-05-01\n\nKeyword matching:\n Full-text against note title + paragraph content via OpenSearch.\n Case-insensitive. Multi-word keywords are tokenized — \"OKR planning\"\n matches notes containing both terms. The deep search variant (this\n command) hydrates each result with its primary documents (one-pager,\n custom) so an MCP/LLM client can read content alongside metadata in\n one call.\n\nNote: placeholder notes (title='Untitled' or sourceType='onboarding') are\nfiltered out by default. Pass --include-untitled to surface them.\n`;\n\nexport function registerNotesSearch(parent: Command): void {\n parent\n .command(\"search [keyword]\")\n .description(\"Deep keyword search — returns notes hydrated with their primary documents.\")\n .option(\n \"--keyword <text>\",\n 'Alternative to positional keyword (e.g. --keyword \"Q3 Planning\")',\n )\n .option(\"--folder <id>\", \"Restrict hits to a folder and its descendants\")\n .option(\n \"--since <date>\",\n \"Inclusive lower bound on createdAt (ISO-8601 or relative: 7d, 24h, 30m)\",\n )\n .option(\"--until <date>\", \"Exclusive upper bound on createdAt\")\n .option(\n \"--limit <n>\",\n `Max results per page (default ${DEFAULT_PAGE_SIZE}, max ${MAX_PAGE_SIZE})`,\n )\n .option(\n \"--cursor <token>\",\n \"Continue a previous page (reserved — backend currently always null)\",\n )\n .option(\n \"--include-untitled\",\n \"Include placeholder notes. Default: hidden\",\n false,\n )\n .addHelpText(\"after\", HELP_AFTER)\n .action(async (positional: string | undefined, opts: SearchOptions, cmd: Command) => {\n const globalOpts = cmd.optsWithGlobals<SearchOptions>();\n\n const keyword = (positional ?? opts.keyword ?? \"\").trim();\n if (!keyword) {\n throw new TiroError(\n {\n code: \"missing_keyword\",\n message: \"search requires a keyword (positional or --keyword).\",\n errorType: \"bad_request\",\n suggestion: 'tiro notes search \"OKR\"',\n },\n ExitCode.Usage,\n );\n }\n\n const filter: Record<string, string> = {};\n if (opts.folder) filter[\"folderId\"] = opts.folder;\n if (opts.since) filter[\"createdAtFrom\"] = parseDate(opts.since);\n if (opts.until) filter[\"createdAtTo\"] = parseDate(opts.until);\n\n const pagination: Record<string, string | number> = {};\n const size = clampLimit(opts.limit);\n if (size !== undefined) pagination[\"size\"] = size;\n if (opts.cursor) pagination[\"cursor\"] = opts.cursor;\n\n const body: Record<string, unknown> = { keyword };\n if (Object.keys(filter).length > 0) body[\"filter\"] = filter;\n if (Object.keys(pagination).length > 0) body[\"pagination\"] = pagination;\n\n const client = createApiClient({\n ...(globalOpts.hostname !== undefined && { hostnameOverride: globalOpts.hostname }),\n });\n\n const res = await client.postJson(\n \"/v1/external/notes/search\",\n SearchResponseSchema,\n body,\n );\n const visible = opts.includeUntitled === true ? res.content : res.content.filter(isVisibleNote);\n\n const mode = resolveOutputMode(globalOpts);\n if (mode === \"json\") {\n for (const note of visible) printNdjson(note);\n if (res.nextCursor) printNdjson({ _cursor: res.nextCursor });\n } else {\n printPretty(visible, res.nextCursor, globalOpts);\n }\n });\n}\n\nfunction clampLimit(raw?: string): number | undefined {\n if (!raw) return undefined;\n const n = parseInt(raw, 10);\n if (!Number.isFinite(n) || n <= 0) return DEFAULT_PAGE_SIZE;\n return Math.min(n, MAX_PAGE_SIZE);\n}\n\ninterface NoteListItem {\n guid: string;\n title: string;\n createdAt: string;\n}\n\nfunction printPretty(\n notes: NoteListItem[],\n nextCursor: string | null,\n opts: { noColor?: boolean },\n): void {\n if (notes.length === 0) {\n process.stdout.write(`${color(\"(no matches)\", \"gray\", opts)}\\n`);\n return;\n }\n for (const n of notes) {\n const date = n.createdAt.slice(0, 10);\n process.stdout.write(\n `${color(date, \"gray\", opts)} ${color(n.guid, \"dim\", opts)} ${n.title}\\n`,\n );\n }\n if (nextCursor) {\n process.stdout.write(\n `${color(`\\n next: --cursor ${nextCursor}`, \"gray\", opts)}\\n`,\n );\n }\n}\n","import { Command } from \"commander\";\nimport { createApiClient } from \"../../lib/api/client.ts\";\nimport {\n NoteSchema,\n ParagraphSchema,\n SimpleListResponseSchema,\n PageCursorResponseSchema,\n type Paragraph,\n} from \"../../lib/api/types.ts\";\nimport { writeFileAtomic } from \"../../lib/output/file.ts\";\nimport { formatNote, type FileFormat } from \"../../lib/output/format.ts\";\nimport { paragraphsToMcp } from \"../../lib/output/transcript.ts\";\nimport { printOutput } from \"../../lib/output/print.ts\";\nimport { resolveOutputMode, color } from \"../../lib/output/tty.ts\";\nimport { TiroError, ExitCode } from \"../../lib/error.ts\";\n\ninterface GetOptions {\n output?: string;\n format?: string;\n include?: string;\n force?: boolean;\n hostname?: string;\n json?: boolean;\n pretty?: boolean;\n noColor?: boolean;\n quiet?: boolean;\n}\n\nconst ALLOWED_INCLUDES = new Set([\"transcript\"]);\n\nconst ParagraphsListSchema = SimpleListResponseSchema(ParagraphSchema);\nconst ParagraphsCursorSchema = PageCursorResponseSchema(ParagraphSchema);\n\nexport function registerNotesGet(parent: Command): void {\n parent\n .command(\"get <guid>\")\n .description(\"Get a single note. Outputs to stdout, or saves to a file with --output.\")\n .option(\"--output <path>\", \"Write to file (stdout becomes a single metadata line)\")\n .option(\n \"--format <md|json|txt>\",\n \"Output format (default: md for TTY, json when piped)\",\n )\n .option(\n \"--include <items>\",\n \"Comma-separated extras (v0.2 supports: transcript)\",\n \"\",\n )\n .option(\"--force\", \"Overwrite existing file at --output path\")\n .addHelpText(\"after\", `\nExamples:\n tiro notes get <guid> # markdown to stdout\n tiro notes get <guid> --include transcript # add speaker-attributed paragraphs\n tiro notes get <guid> --output ./meeting.md --include transcript\n tiro notes get <guid> --format json # JSON to stdout\n\nTip for agents: prefer --output <path>. The actual content goes to disk\nand stdout collapses to a single metadata line, keeping your context\nwindow light.\n`)\n .action(async (guid: string, opts: GetOptions, cmd: Command) => {\n const globalOpts = cmd.optsWithGlobals<GetOptions>();\n\n const includes = parseIncludes(opts.include);\n validateIncludes(includes);\n const format = pickFormat(opts.format, opts.output);\n\n const client = createApiClient({\n ...(globalOpts.hostname !== undefined && { hostnameOverride: globalOpts.hostname }),\n });\n\n const note = await client.getJson(`/v1/external/notes/${guid}`, NoteSchema);\n\n let paragraphs: Paragraph[] | undefined;\n if (includes.has(\"transcript\") || format === \"txt\") {\n paragraphs = await fetchAllParagraphs(client, guid);\n }\n\n const content = formatNote(note, format, {\n includeTranscript: includes.has(\"transcript\"),\n ...(paragraphs !== undefined && { paragraphs }),\n });\n\n if (opts.output) {\n const result = await writeFileAtomic(opts.output, content, {\n ...(opts.force === true && { force: true }),\n });\n printOutput(\n {\n ok: true,\n data: {\n saved: result.path,\n size: result.size,\n format,\n guid: note.guid,\n title: note.title,\n },\n },\n globalOpts,\n );\n return;\n }\n\n const mode = resolveOutputMode(globalOpts);\n if (mode === \"json\" && format !== \"json\") {\n printOutput(\n {\n ok: true,\n data: {\n ...note,\n ...(paragraphs && { transcript: { paragraphs: paragraphsToMcp(paragraphs) } }),\n },\n },\n globalOpts,\n );\n } else if (format === \"json\") {\n process.stdout.write(content);\n } else {\n if (process.stdout.isTTY && format === \"txt\") {\n process.stdout.write(`${color(`# ${note.title}`, \"bold\", globalOpts)}\\n\\n`);\n }\n process.stdout.write(content);\n }\n });\n}\n\nasync function fetchAllParagraphs(\n client: ReturnType<typeof createApiClient>,\n guid: string,\n): Promise<Paragraph[]> {\n const first = await client.getJson(\n `/v1/external/notes/${guid}/paragraphs`,\n ParagraphsCursorSchema.or(ParagraphsListSchema),\n );\n const all: Paragraph[] = [...first.content];\n if (\"nextCursor\" in first) {\n let cursor = first.nextCursor;\n while (cursor) {\n const next = await client.getJson(\n `/v1/external/notes/${guid}/paragraphs`,\n ParagraphsCursorSchema,\n { cursor },\n );\n all.push(...next.content);\n cursor = next.nextCursor;\n }\n }\n return all;\n}\n\nfunction parseIncludes(raw?: string): Set<string> {\n if (!raw) return new Set();\n return new Set(\n raw\n .split(\",\")\n .map((s) => s.trim().toLowerCase())\n .filter((s) => s.length > 0),\n );\n}\n\nfunction validateIncludes(includes: Set<string>): void {\n for (const inc of includes) {\n if (!ALLOWED_INCLUDES.has(inc)) {\n throw new TiroError(\n {\n code: \"invalid_include\",\n message: `Invalid --include \"${inc}\". v0.2.0 supports: transcript.`,\n errorType: \"bad_request\",\n suggestion: \"Use --include transcript\",\n },\n ExitCode.Usage,\n );\n }\n }\n}\n\nfunction pickFormat(format: string | undefined, output: string | undefined): FileFormat {\n const allowed: FileFormat[] = [\"md\", \"json\", \"txt\"];\n if (format) {\n const f = format.toLowerCase() as FileFormat;\n if (!allowed.includes(f)) {\n throw new TiroError(\n {\n code: \"invalid_format\",\n message: `Invalid --format \"${format}\". Allowed: md, json, txt.`,\n errorType: \"bad_request\",\n },\n ExitCode.Usage,\n );\n }\n return f;\n }\n if (output) {\n if (output.endsWith(\".json\")) return \"json\";\n if (output.endsWith(\".txt\")) return \"txt\";\n return \"md\";\n }\n return process.stdout.isTTY ? \"md\" : \"json\";\n}\n","import { mkdir, rename, stat, writeFile, access } from \"node:fs/promises\";\nimport { dirname, resolve } from \"node:path\";\nimport { TiroError, ExitCode } from \"../error.ts\";\n\nexport interface WriteResult {\n path: string;\n size: number;\n}\n\nexport async function writeFileAtomic(\n filepath: string,\n content: string,\n opts: { force?: boolean } = {},\n): Promise<WriteResult> {\n const absPath = resolve(filepath);\n await mkdir(dirname(absPath), { recursive: true });\n\n if (!opts.force) {\n const exists = await fileExists(absPath);\n if (exists) {\n throw new TiroError(\n {\n code: \"file_exists\",\n message: `File already exists: ${absPath}`,\n errorType: \"conflict\",\n suggestion: \"Use --force to overwrite, or pick a different --output.\",\n },\n ExitCode.Generic,\n );\n }\n }\n\n const tmp = `${absPath}.tmp.${process.pid}.${Date.now()}`;\n await writeFile(tmp, content, \"utf8\");\n await rename(tmp, absPath);\n const s = await stat(absPath);\n return { path: absPath, size: s.size };\n}\n\nasync function fileExists(p: string): Promise<boolean> {\n try {\n await access(p);\n return true;\n } catch {\n return false;\n }\n}\n","import type {\n McpParagraph,\n McpSegment,\n McpTranscript,\n Note,\n Paragraph,\n} from \"../api/types.ts\";\n\nexport function buildMcpTranscript(note: Note, paragraphs: Paragraph[]): McpTranscript {\n return {\n noteGuid: note.guid,\n title: note.title,\n participants:\n note.participants\n ?.map((p) => p.name || p.email || \"\")\n .filter((s): s is string => typeof s === \"string\" && s.length > 0) ?? [],\n createdAt: note.createdAt,\n recordingDurationSeconds: note.recordingDurationSeconds,\n paragraphs: paragraphsToMcp(paragraphs),\n };\n}\n\nexport function paragraphsToMcp(paragraphs: Paragraph[]): McpParagraph[] {\n return paragraphs\n .map((p) => ({\n timeFrom: p.timeFrom ?? null,\n timeTo: p.timeTo ?? null,\n segments: paragraphToSegments(p),\n }))\n .filter((p) => p.segments.length > 0);\n}\n\nfunction paragraphToSegments(p: Paragraph): McpSegment[] {\n const ds = p.diarizedSegments;\n if (ds && ds.length > 0) {\n return ds\n .map((s) => ({\n content: stripHtml(s.content),\n speaker: {\n label: s.speaker.label,\n name: s.speaker.personName ? stripHtml(s.speaker.personName) : null,\n },\n }))\n .filter((s) => s.content.length > 0);\n }\n const plain = stripHtml(p.transcript?.content ?? \"\");\n return plain ? [{ content: plain, speaker: null }] : [];\n}\n\nexport function renderTranscriptJson(t: McpTranscript): string {\n return `${JSON.stringify(t, null, 2)}\\n`;\n}\n\nexport function renderTranscriptMarkdown(t: McpTranscript): string {\n const anchor = anchorTime(t);\n const lines: string[] = [];\n lines.push(`# ${t.title}`, \"\");\n\n if (t.participants.length > 0) {\n lines.push(`**Participants**: ${t.participants.join(\", \")}`, \"\");\n }\n\n lines.push(\"## Transcript\", \"\");\n for (const p of t.paragraphs) {\n const ts = elapsed(p.timeFrom, anchor);\n for (const s of p.segments) {\n const who = s.speaker?.name ?? s.speaker?.label ?? \"Unknown\";\n const tag = ts ? `${who}, ${ts}` : who;\n lines.push(`**[${tag}]** ${s.content}`);\n }\n lines.push(\"\");\n }\n\n return `${lines.join(\"\\n\").trimEnd()}\\n`;\n}\n\nexport function renderTranscriptText(t: McpTranscript): string {\n const lines: string[] = [];\n for (const p of t.paragraphs) {\n for (const s of p.segments) {\n const who = s.speaker?.name ?? s.speaker?.label ?? \"Unknown\";\n lines.push(`[${who}] ${s.content}`);\n }\n }\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nfunction anchorTime(t: McpTranscript): string | null {\n for (const p of t.paragraphs) {\n if (p.timeFrom) return p.timeFrom;\n }\n return null;\n}\n\nfunction elapsed(currentIso: string | null, anchorIso: string | null): string {\n if (!currentIso || !anchorIso) return \"\";\n const cur = Date.parse(currentIso);\n const anc = Date.parse(anchorIso);\n if (!Number.isFinite(cur) || !Number.isFinite(anc)) return \"\";\n const seconds = Math.max(0, Math.floor((cur - anc) / 1000));\n const h = Math.floor(seconds / 3600);\n const m = Math.floor((seconds % 3600) / 60);\n const s = seconds % 60;\n if (h > 0) return `${pad(h)}:${pad(m)}:${pad(s)}`;\n return `${pad(m)}:${pad(s)}`;\n}\n\nfunction pad(n: number): string {\n return n.toString().padStart(2, \"0\");\n}\n\nfunction stripHtml(s: string): string {\n return s\n .replace(/<[^>]*>/g, \"\")\n .replace(/ /g, \" \")\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .trim();\n}\n","import type { Note, Paragraph } from \"../api/types.ts\";\nimport {\n buildMcpTranscript,\n paragraphsToMcp,\n renderTranscriptJson,\n renderTranscriptMarkdown,\n renderTranscriptText,\n} from \"./transcript.ts\";\n\nexport type FileFormat = \"md\" | \"json\" | \"txt\";\n\nexport interface FormatOptions {\n includeTranscript?: boolean;\n paragraphs?: Paragraph[];\n}\n\nexport function formatNote(\n note: Note,\n format: FileFormat,\n opts: FormatOptions = {},\n): string {\n switch (format) {\n case \"md\":\n return formatMarkdown(note, opts);\n case \"json\":\n return formatJson(note, opts);\n case \"txt\":\n return formatText(note, opts);\n }\n}\n\nfunction formatMarkdown(note: Note, opts: FormatOptions): string {\n const fm = [\n \"---\",\n `guid: ${escapeYaml(note.guid)}`,\n `title: ${escapeYaml(note.title)}`,\n `createdAt: ${note.createdAt}`,\n `updatedAt: ${note.updatedAt}`,\n `sourceType: ${note.sourceType}`,\n `recordingDurationSeconds: ${note.recordingDurationSeconds}`,\n `webUrl: ${note.webUrl}`,\n ];\n if (note.recordingStartAt) fm.push(`recordingStartAt: ${note.recordingStartAt}`);\n if (note.recordingEndAt) fm.push(`recordingEndAt: ${note.recordingEndAt}`);\n fm.push(\"---\", \"\");\n\n const parts: string[] = [fm.join(\"\\n\"), `# ${note.title}`, \"\"];\n\n if (note.participants && note.participants.length > 0) {\n parts.push(\"## Participants\", \"\");\n for (const p of note.participants) {\n const name = p.name ?? \"(no name)\";\n const email = p.email ? ` <${p.email}>` : \"\";\n parts.push(`- ${name}${email}`);\n }\n parts.push(\"\");\n }\n\n if (opts.includeTranscript && opts.paragraphs && opts.paragraphs.length > 0) {\n const mcp = buildMcpTranscript(note, opts.paragraphs);\n const transcriptBody = renderTranscriptMarkdown(mcp);\n const startsWithHeader = transcriptBody.startsWith(`# ${note.title}`);\n const trimmed = startsWithHeader\n ? transcriptBody.slice(transcriptBody.indexOf(\"\\n\") + 1)\n : transcriptBody;\n parts.push(trimmed.replace(/^\\s*\\n+/, \"\"));\n }\n\n return `${parts.join(\"\\n\").trimEnd()}\\n`;\n}\n\nfunction formatJson(note: Note, opts: FormatOptions): string {\n const out: Record<string, unknown> = { ...note };\n if (opts.includeTranscript && opts.paragraphs) {\n out[\"transcript\"] = { paragraphs: paragraphsToMcp(opts.paragraphs) };\n }\n return `${JSON.stringify(out, null, 2)}\\n`;\n}\n\nfunction formatText(note: Note, opts: FormatOptions): string {\n if (!opts.paragraphs || opts.paragraphs.length === 0) {\n return `${note.title}\\n${note.webUrl}\\n`;\n }\n const mcp = buildMcpTranscript(note, opts.paragraphs);\n return renderTranscriptText(mcp);\n}\n\nfunction escapeYaml(s: string): string {\n if (/[:#\\n\"']/.test(s)) {\n return JSON.stringify(s);\n }\n return s;\n}\n\nexport {\n buildMcpTranscript,\n renderTranscriptJson,\n renderTranscriptMarkdown,\n renderTranscriptText,\n};\n","import { Command } from \"commander\";\nimport { createApiClient } from \"../../lib/api/client.ts\";\nimport {\n ParagraphSchema,\n PageCursorResponseSchema,\n SimpleListResponseSchema,\n NoteSchema,\n type Paragraph,\n} from \"../../lib/api/types.ts\";\nimport { writeFileAtomic } from \"../../lib/output/file.ts\";\nimport {\n buildMcpTranscript,\n renderTranscriptJson,\n renderTranscriptMarkdown,\n renderTranscriptText,\n} from \"../../lib/output/transcript.ts\";\nimport { type FileFormat } from \"../../lib/output/format.ts\";\nimport { printOutput } from \"../../lib/output/print.ts\";\nimport { resolveOutputMode } from \"../../lib/output/tty.ts\";\nimport { TiroError, ExitCode } from \"../../lib/error.ts\";\n\ninterface TranscriptOptions {\n output?: string;\n format?: string;\n force?: boolean;\n hostname?: string;\n json?: boolean;\n pretty?: boolean;\n noColor?: boolean;\n quiet?: boolean;\n}\n\nconst ParagraphsListSchema = SimpleListResponseSchema(ParagraphSchema);\nconst ParagraphsCursorSchema = PageCursorResponseSchema(ParagraphSchema);\n\nexport function registerNotesTranscript(parent: Command): void {\n parent\n .command(\"transcript <guid>\")\n .description(\n \"Get the full transcript of a note as speaker-attributed paragraphs.\\n\" +\n \"JSON output matches MCP get_note_transcript shape exactly.\",\n )\n .option(\"--output <path>\", \"Write to file (stdout = single metadata line)\")\n .option(\n \"--format <md|json|txt>\",\n \"Output format (default: md if TTY, txt when piped; json mirrors MCP)\",\n )\n .option(\"--force\", \"Overwrite existing file at --output path\")\n .addHelpText(\"after\", `\nExamples:\n tiro notes transcript <guid> # md in TTY, txt in pipe\n tiro notes transcript <guid> --format md --output ./t.md\n tiro notes transcript <guid> --format json # MCP-shape JSON\n tiro notes transcript <guid> --format txt --output ./embed.txt\n\nThe --format json output is byte-for-byte identical to MCP's\nget_note_transcript so agents can swap surfaces without changing parsers.\n`)\n .action(async (guid: string, opts: TranscriptOptions, cmd: Command) => {\n const globalOpts = cmd.optsWithGlobals<TranscriptOptions>();\n const format = pickFormat(opts.format, opts.output, globalOpts);\n\n const client = createApiClient({\n ...(globalOpts.hostname !== undefined && { hostnameOverride: globalOpts.hostname }),\n });\n\n const note = await client.getJson(`/v1/external/notes/${guid}`, NoteSchema);\n const paragraphs = await fetchAllParagraphs(client, guid);\n const mcp = buildMcpTranscript(note, paragraphs);\n\n const content =\n format === \"json\"\n ? renderTranscriptJson(mcp)\n : format === \"md\"\n ? renderTranscriptMarkdown(mcp)\n : renderTranscriptText(mcp);\n\n if (opts.output) {\n const result = await writeFileAtomic(opts.output, content, {\n ...(opts.force === true && { force: true }),\n });\n printOutput(\n {\n ok: true,\n data: {\n saved: result.path,\n size: result.size,\n format,\n guid: note.guid,\n paragraphCount: mcp.paragraphs.length,\n segmentCount: mcp.paragraphs.reduce((sum, p) => sum + p.segments.length, 0),\n },\n },\n globalOpts,\n );\n return;\n }\n\n const mode = resolveOutputMode(globalOpts);\n if (mode === \"json\" && format !== \"json\") {\n printOutput({ ok: true, data: mcp }, globalOpts);\n } else {\n process.stdout.write(content);\n }\n });\n}\n\nasync function fetchAllParagraphs(\n client: ReturnType<typeof createApiClient>,\n guid: string,\n): Promise<Paragraph[]> {\n const first = await client.getJson(\n `/v1/external/notes/${guid}/paragraphs`,\n ParagraphsCursorSchema.or(ParagraphsListSchema),\n );\n const all: Paragraph[] = [...first.content];\n if (\"nextCursor\" in first) {\n let cursor = first.nextCursor;\n while (cursor) {\n const next = await client.getJson(\n `/v1/external/notes/${guid}/paragraphs`,\n ParagraphsCursorSchema,\n { cursor },\n );\n all.push(...next.content);\n cursor = next.nextCursor;\n }\n }\n return all;\n}\n\nfunction pickFormat(\n format: string | undefined,\n output: string | undefined,\n globalOpts: { json?: boolean; pretty?: boolean },\n): FileFormat {\n const allowed: FileFormat[] = [\"md\", \"json\", \"txt\"];\n if (format) {\n const f = format.toLowerCase() as FileFormat;\n if (!allowed.includes(f)) {\n throw new TiroError(\n {\n code: \"invalid_format\",\n message: `Invalid --format \"${format}\". Allowed: md, json, txt.`,\n errorType: \"bad_request\",\n },\n ExitCode.Usage,\n );\n }\n return f;\n }\n if (output) {\n if (output.endsWith(\".json\")) return \"json\";\n if (output.endsWith(\".md\")) return \"md\";\n if (output.endsWith(\".txt\")) return \"txt\";\n return \"md\";\n }\n if (globalOpts.json) return \"json\";\n if (globalOpts.pretty) return \"md\";\n return process.stdout.isTTY ? \"md\" : \"txt\";\n}\n","// update-notifier@7 ships no type definitions. We declare a minimal local\n// surface for the bits we use — runtime is the real authority.\n\nimport updateNotifier from \"update-notifier\";\nimport { readFile } from \"node:fs/promises\";\nimport { fileURLToPath } from \"node:url\";\nimport { dirname, resolve } from \"node:path\";\n\ninterface UpdateInfo {\n current: string;\n latest: string;\n type?: string;\n name?: string;\n}\n\ninterface NotifyOptions {\n isGlobal?: boolean;\n defer?: boolean;\n message?: string;\n boxenOptions?: Record<string, unknown>;\n}\n\nexport interface MinimalNotifier {\n update?: UpdateInfo | null | undefined;\n notify(options?: NotifyOptions): unknown;\n}\n\ninterface NotifierFactory {\n (options: {\n pkg: { name: string; version: string };\n updateCheckInterval?: number;\n shouldNotifyInNpmScript?: boolean;\n }): MinimalNotifier;\n}\n\nconst HERE = dirname(fileURLToPath(import.meta.url));\nconst CANDIDATE_PATHS = [\n resolve(HERE, \"../../package.json\"),\n resolve(HERE, \"../../../package.json\"),\n];\n\nconst ONE_DAY_MS = 24 * 60 * 60 * 1000;\n\ninterface CliPackage {\n name: string;\n version: string;\n}\n\nexport async function startUpdateCheck(): Promise<MinimalNotifier | null> {\n if (process.env[\"NO_UPDATE_NOTIFIER\"] === \"1\") return null;\n if (process.env[\"CI\"]) return null;\n if (process.stdout.isTTY !== true) return null;\n\n const pkg = await loadPkg();\n if (!pkg) return null;\n\n try {\n const factory = updateNotifier as unknown as NotifierFactory;\n return factory({\n pkg,\n updateCheckInterval: ONE_DAY_MS,\n shouldNotifyInNpmScript: false,\n });\n } catch {\n return null;\n }\n}\n\nexport function emitUpdateBanner(notifier: MinimalNotifier | null): void {\n if (!notifier) return;\n if (!notifier.update) return;\n\n notifier.notify({\n isGlobal: true,\n defer: false,\n message:\n \"Update available {currentVersion} \\u2192 {latestVersion}\\n\" +\n \"Run npm install -g {packageName} to update\\n\\n\" +\n \"Changelog: https://www.npmjs.com/package/{packageName}?activeTab=versions\",\n });\n}\n\nasync function loadPkg(): Promise<CliPackage | null> {\n for (const path of CANDIDATE_PATHS) {\n try {\n const raw = await readFile(path, \"utf8\");\n const parsed = JSON.parse(raw) as Partial<CliPackage>;\n if (\n typeof parsed.name === \"string\" &&\n typeof parsed.version === \"string\" &&\n parsed.name.length > 0 &&\n parsed.version.length > 0\n ) {\n return { name: parsed.name, version: parsed.version };\n }\n } catch {\n // try next candidate\n }\n }\n return null;\n}\n"],"mappings":"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","names":["Command","resolve","z","DEFAULT_PAGE_SIZE","MAX_PAGE_SIZE","HELP_AFTER","clampLimit","printPretty","dirname","resolve","resolve","dirname","ParagraphsListSchema","ParagraphsCursorSchema","pickFormat","fetchAllParagraphs","fileURLToPath","dirname","resolve","HERE","CANDIDATE_PATHS","Command"]}
|