@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 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
- # Coming in v0.3 / v0.4:
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
- Full design spec: [`SPEC.md`](./SPEC.md).
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 Command10 } from "commander";
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 msg = `OAuth error: ${error}${errorDesc ? ` \u2014 ${errorDesc}` : ""}`;
182
- respondPage(res, 500, "Login failed", msg);
183
- if (pendingReject) settle(pendingReject, new Error(msg));
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
- respondPage(res, 400, "Missing parameters", "Missing `code` or `state`.");
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
- respondPage(
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 respondPage(res, status, title, body) {
231
- const html = `<!doctype html>
230
+ function escapeHtml(s) {
231
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
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&hellip;</p>` : "";
258
+ return `<!doctype html>
232
259
  <html lang="en">
233
260
  <head>
234
261
  <meta charset="utf-8">
235
- <title>${title}</title>
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
- body { font-family: -apple-system, system-ui, sans-serif; padding: 4rem; max-width: 32rem; margin: 0 auto; color: #1a1a1a; }
238
- h1 { font-size: 1.5rem; margin-bottom: 1rem; }
239
- p { color: #555; line-height: 1.5; }
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
- <h1>${title}</h1>
244
- <p>${body}</p>
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>&copy; 2026 <a href="https://tiro.ooo">The Plato Inc.</a> &mdash; 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 anchor = anchorTime(t);
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
- const ts = elapsed(p.timeFrom, anchor);
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
- const tag = ts ? `${who}, ${ts}` : who;
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 Command10();
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.2.2",
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
- "SPEC.md"
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)
@@ -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(/&nbsp;/g, \" \")\n .replace(/&amp;/g, \"&\")\n .replace(/&lt;/g, \"<\")\n .replace(/&gt;/g, \">\")\n .replace(/&quot;/g, '\"')\n .replace(/&#39;/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"]}