@theplato/tiro-cli 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -4
- package/dist/bin/tiro.js +143 -2460
- package/package.json +1 -1
package/dist/bin/tiro.js
CHANGED
|
@@ -1,288 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
// src/lib/version.ts
|
|
7
|
-
import { readFileSync } from "fs";
|
|
8
|
-
import { fileURLToPath } from "url";
|
|
9
|
-
import { dirname, resolve } from "path";
|
|
10
|
-
var HERE = dirname(fileURLToPath(import.meta.url));
|
|
11
|
-
var CANDIDATE_PATHS = [
|
|
12
|
-
resolve(HERE, "../../package.json"),
|
|
13
|
-
resolve(HERE, "../../../package.json")
|
|
14
|
-
];
|
|
15
|
-
function readVersion() {
|
|
16
|
-
for (const path of CANDIDATE_PATHS) {
|
|
17
|
-
try {
|
|
18
|
-
const raw = readFileSync(path, "utf8");
|
|
19
|
-
const parsed = JSON.parse(raw);
|
|
20
|
-
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
21
|
-
return parsed.version;
|
|
22
|
-
}
|
|
23
|
-
} catch {
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
return "0.0.0-unknown";
|
|
27
|
-
}
|
|
28
|
-
var VERSION = readVersion();
|
|
29
|
-
|
|
30
|
-
// src/lib/error.ts
|
|
31
|
-
var ExitCode = {
|
|
32
|
-
Ok: 0,
|
|
33
|
-
Generic: 1,
|
|
34
|
-
Usage: 2,
|
|
35
|
-
AuthRequired: 4,
|
|
36
|
-
ExUsage: 64,
|
|
37
|
-
ExDataErr: 65,
|
|
38
|
-
ExConfig: 78
|
|
39
|
-
};
|
|
40
|
-
var TiroError = class extends Error {
|
|
41
|
-
code;
|
|
42
|
-
suggestion;
|
|
43
|
-
errorType;
|
|
44
|
-
httpStatus;
|
|
45
|
-
requestId;
|
|
46
|
-
details;
|
|
47
|
-
exitCode;
|
|
48
|
-
constructor(payload, exitCode = ExitCode.Generic) {
|
|
49
|
-
super(payload.message);
|
|
50
|
-
this.name = "TiroError";
|
|
51
|
-
this.code = payload.code;
|
|
52
|
-
this.suggestion = payload.suggestion;
|
|
53
|
-
this.errorType = payload.errorType;
|
|
54
|
-
this.httpStatus = payload.httpStatus;
|
|
55
|
-
this.requestId = payload.requestId;
|
|
56
|
-
this.details = payload.details;
|
|
57
|
-
this.exitCode = exitCode;
|
|
58
|
-
}
|
|
59
|
-
toJSON() {
|
|
60
|
-
return {
|
|
61
|
-
ok: false,
|
|
62
|
-
error: {
|
|
63
|
-
code: this.code,
|
|
64
|
-
message: this.message,
|
|
65
|
-
...this.suggestion !== void 0 && { suggestion: this.suggestion },
|
|
66
|
-
...this.errorType !== void 0 && { errorType: this.errorType },
|
|
67
|
-
...this.httpStatus !== void 0 && { httpStatus: this.httpStatus },
|
|
68
|
-
...this.requestId !== void 0 && { requestId: this.requestId },
|
|
69
|
-
...this.details !== void 0 && { details: this.details }
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
function authRequired() {
|
|
75
|
-
return new TiroError(
|
|
76
|
-
{
|
|
77
|
-
code: "auth_required",
|
|
78
|
-
message: "Not authenticated. Run `tiro auth login` to sign in, or set TIRO_TOKEN env var.",
|
|
79
|
-
suggestion: "tiro auth login",
|
|
80
|
-
errorType: "auth_required"
|
|
81
|
-
},
|
|
82
|
-
ExitCode.AuthRequired
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// src/lib/output/tty.ts
|
|
87
|
-
function resolveOutputMode(opts) {
|
|
88
|
-
if (opts.json) return "json";
|
|
89
|
-
if (opts.pretty) return "pretty";
|
|
90
|
-
return process.stdout.isTTY ? "pretty" : "json";
|
|
91
|
-
}
|
|
92
|
-
function colorEnabled(opts) {
|
|
93
|
-
if (opts.noColor) return false;
|
|
94
|
-
if (process.env["NO_COLOR"]) return false;
|
|
95
|
-
if (process.env["FORCE_COLOR"]) return true;
|
|
96
|
-
return process.stdout.isTTY === true;
|
|
97
|
-
}
|
|
98
|
-
var ANSI = {
|
|
99
|
-
reset: "\x1B[0m",
|
|
100
|
-
bold: "\x1B[1m",
|
|
101
|
-
dim: "\x1B[2m",
|
|
102
|
-
red: "\x1B[31m",
|
|
103
|
-
green: "\x1B[32m",
|
|
104
|
-
yellow: "\x1B[33m",
|
|
105
|
-
blue: "\x1B[34m",
|
|
106
|
-
magenta: "\x1B[35m",
|
|
107
|
-
cyan: "\x1B[36m",
|
|
108
|
-
gray: "\x1B[90m"
|
|
109
|
-
};
|
|
110
|
-
function color(text, style, opts = {}) {
|
|
111
|
-
if (!colorEnabled(opts)) return text;
|
|
112
|
-
return `${ANSI[style]}${text}${ANSI.reset}`;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// src/lib/output/print.ts
|
|
116
|
-
function printOutput(value, opts = {}) {
|
|
117
|
-
if (opts.quiet) return;
|
|
118
|
-
const mode = resolveOutputMode(opts);
|
|
119
|
-
if (mode === "json") {
|
|
120
|
-
process.stdout.write(`${JSON.stringify(value)}
|
|
121
|
-
`);
|
|
122
|
-
} else {
|
|
123
|
-
process.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
124
|
-
`);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
function printError(value) {
|
|
128
|
-
process.stderr.write(`${JSON.stringify(value)}
|
|
129
|
-
`);
|
|
130
|
-
}
|
|
131
|
-
function printNdjson(item) {
|
|
132
|
-
process.stdout.write(`${JSON.stringify(item)}
|
|
133
|
-
`);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// src/commands/auth/index.ts
|
|
137
|
-
import "commander";
|
|
138
|
-
|
|
139
|
-
// src/commands/auth/login.ts
|
|
140
|
-
import "commander";
|
|
141
|
-
|
|
142
|
-
// src/lib/auth/flow.ts
|
|
143
|
-
import { z } from "zod";
|
|
144
|
-
|
|
145
|
-
// src/lib/auth/pkce.ts
|
|
146
|
-
import { createHash, randomBytes, timingSafeEqual } from "crypto";
|
|
147
|
-
function generatePkce() {
|
|
148
|
-
const codeVerifier = base64url(randomBytes(32));
|
|
149
|
-
const codeChallenge = base64url(createHash("sha256").update(codeVerifier).digest());
|
|
150
|
-
return { codeVerifier, codeChallenge, method: "S256" };
|
|
151
|
-
}
|
|
152
|
-
function generateState() {
|
|
153
|
-
return base64url(randomBytes(24));
|
|
154
|
-
}
|
|
155
|
-
function verifyState(received, expected) {
|
|
156
|
-
const a = Buffer.from(received, "utf8");
|
|
157
|
-
const b = Buffer.from(expected, "utf8");
|
|
158
|
-
if (a.length !== b.length) return false;
|
|
159
|
-
return timingSafeEqual(a, b);
|
|
160
|
-
}
|
|
161
|
-
function base64url(buf) {
|
|
162
|
-
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// src/lib/auth/loopback.ts
|
|
166
|
-
import http from "http";
|
|
167
|
-
|
|
168
|
-
// src/lib/auth/assets.generated.ts
|
|
169
|
-
var TIRO_LOGO_BROWN_DATA_URI = "data:image/png;base64,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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACA/78dOBAAAAAAELQ/9SIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAP3ZeKxkxamrQAAAABJRU5ErkJggg==";
|
|
170
|
-
|
|
171
|
-
// src/lib/auth/loopback.ts
|
|
172
|
-
async function startLoopbackServer() {
|
|
173
|
-
let pendingResolve = null;
|
|
174
|
-
let pendingReject = null;
|
|
175
|
-
const settle = (fn, arg) => {
|
|
176
|
-
pendingResolve = null;
|
|
177
|
-
pendingReject = null;
|
|
178
|
-
fn(arg);
|
|
179
|
-
};
|
|
180
|
-
let consumed = false;
|
|
181
|
-
const server = http.createServer((req, res) => {
|
|
182
|
-
res.setHeader("Connection", "close");
|
|
183
|
-
res.setHeader("Cache-Control", "no-store");
|
|
184
|
-
if (!req.url) {
|
|
185
|
-
res.writeHead(400).end();
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
if (req.method !== "GET") {
|
|
189
|
-
res.writeHead(405, { "Content-Type": "text/plain", Allow: "GET" }).end("Method not allowed");
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
const url = new URL(req.url, `http://127.0.0.1`);
|
|
193
|
-
if (url.pathname !== "/callback") {
|
|
194
|
-
res.writeHead(404, { "Content-Type": "text/plain" }).end("Not found");
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
if (consumed) {
|
|
198
|
-
res.writeHead(410, { "Content-Type": "text/plain" }).end("Callback already consumed");
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
const code = url.searchParams.get("code");
|
|
202
|
-
const state = url.searchParams.get("state");
|
|
203
|
-
const error = url.searchParams.get("error");
|
|
204
|
-
const errorDesc = url.searchParams.get("error_description") ?? "";
|
|
205
|
-
if (error) {
|
|
206
|
-
consumed = true;
|
|
207
|
-
const detail = errorDesc ? ` \u2014 ${errorDesc}` : "";
|
|
208
|
-
respondError(res, 500, `OAuth error: ${error}${detail}`);
|
|
209
|
-
if (pendingReject) settle(pendingReject, new Error(`OAuth error: ${error}${detail}`));
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
if (!code || !state) {
|
|
213
|
-
respondError(res, 400, "Missing `code` or `state` in callback URL.");
|
|
214
|
-
if (pendingReject) settle(pendingReject, new Error("Missing code or state"));
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
consumed = true;
|
|
218
|
-
respondSuccess(res);
|
|
219
|
-
if (pendingResolve) settle(pendingResolve, { code, state });
|
|
220
|
-
});
|
|
221
|
-
await new Promise((resolve4) => {
|
|
222
|
-
server.listen(0, "127.0.0.1", () => resolve4());
|
|
223
|
-
});
|
|
224
|
-
const address = server.address();
|
|
225
|
-
const port = address.port;
|
|
226
|
-
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
227
|
-
return {
|
|
228
|
-
redirectUri,
|
|
229
|
-
port,
|
|
230
|
-
waitForCallback(timeoutMs) {
|
|
231
|
-
return new Promise((resolve4, reject) => {
|
|
232
|
-
const timer = setTimeout(() => {
|
|
233
|
-
pendingResolve = null;
|
|
234
|
-
pendingReject = null;
|
|
235
|
-
reject(new Error(`Timed out waiting for OAuth callback (${timeoutMs}ms)`));
|
|
236
|
-
}, timeoutMs);
|
|
237
|
-
pendingResolve = (r) => {
|
|
238
|
-
clearTimeout(timer);
|
|
239
|
-
resolve4(r);
|
|
240
|
-
};
|
|
241
|
-
pendingReject = (e) => {
|
|
242
|
-
clearTimeout(timer);
|
|
243
|
-
reject(e);
|
|
244
|
-
};
|
|
245
|
-
});
|
|
246
|
-
},
|
|
247
|
-
close() {
|
|
248
|
-
server.close();
|
|
249
|
-
}
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
function escapeHtml(s) {
|
|
253
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
254
|
-
}
|
|
255
|
-
function respondSuccess(res) {
|
|
256
|
-
const html = renderPage({
|
|
257
|
-
kind: "success",
|
|
258
|
-
title: "You're signed in",
|
|
259
|
-
body: "You can close this tab and return to your terminal."
|
|
260
|
-
});
|
|
261
|
-
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
262
|
-
res.end(html);
|
|
263
|
-
}
|
|
264
|
-
function respondError(res, status, body) {
|
|
265
|
-
const html = renderPage({
|
|
266
|
-
kind: "error",
|
|
267
|
-
title: "Login failed",
|
|
268
|
-
body
|
|
269
|
-
});
|
|
270
|
-
res.writeHead(status, { "Content-Type": "text/html; charset=utf-8" });
|
|
271
|
-
res.end(html);
|
|
272
|
-
}
|
|
273
|
-
function renderPage(p) {
|
|
274
|
-
const safeTitle = escapeHtml(p.title);
|
|
275
|
-
const safeBody = escapeHtml(p.body);
|
|
276
|
-
const titleColor = p.kind === "error" ? "var(--error)" : "var(--brown-800)";
|
|
277
|
-
const logoOpacity = p.kind === "error" ? "0.4" : "1";
|
|
278
|
-
const closeScript = p.kind === "success" ? `<script>setTimeout(function(){try{window.close();}catch(e){}}, 2000);</script>` : "";
|
|
279
|
-
const hint = p.kind === "success" ? `<p class="hint">Closing this tab automatically…</p>` : "";
|
|
280
|
-
return `<!doctype html>
|
|
2
|
+
import{Command as ho}from"commander";import{readFileSync as At}from"fs";import{fileURLToPath as tt}from"url";import{dirname as rt,resolve as we}from"path";var ye=rt(tt(import.meta.url)),ot=[we(ye,"../../package.json"),we(ye,"../../../package.json")];function nt(){for(let e of ot)try{return JSON.parse(At(e,"utf8"))}catch{}return{}}var X=nt(),O=typeof X.version=="string"&&X.version.length>0?X.version:"0.0.0-unknown",S=typeof X.name=="string"&&X.name.length>0?X.name:"@theplato/tiro-cli";var d={Ok:0,Generic:1,Usage:2,AuthRequired:4,ExUsage:64,ExDataErr:65,ExConfig:78},u=class extends Error{code;suggestion;errorType;httpStatus;requestId;details;exitCode;constructor(A,t=d.Generic){super(A.message),this.name="TiroError",this.code=A.code,this.suggestion=A.suggestion,this.errorType=A.errorType,this.httpStatus=A.httpStatus,this.requestId=A.requestId,this.details=A.details,this.exitCode=t}toJSON(){return{ok:!1,error:{code:this.code,message:this.message,...this.suggestion!==void 0&&{suggestion:this.suggestion},...this.errorType!==void 0&&{errorType:this.errorType},...this.httpStatus!==void 0&&{httpStatus:this.httpStatus},...this.requestId!==void 0&&{requestId:this.requestId},...this.details!==void 0&&{details:this.details}}}}};function G(){return new u({code:"auth_required",message:"Not authenticated. Run `tiro auth login` to sign in, or set TIRO_TOKEN env var.",suggestion:"tiro auth login",errorType:"auth_required"},d.AuthRequired)}function b(e){return e.json?"json":e.pretty||process.stdout.isTTY?"pretty":"json"}function it(e){return e.noColor||process.env.NO_COLOR?!1:process.env.FORCE_COLOR?!0:process.stdout.isTTY===!0}var be={reset:"\x1B[0m",bold:"\x1B[1m",dim:"\x1B[2m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",cyan:"\x1B[36m",gray:"\x1B[90m"};function a(e,A,t={}){return it(t)?`${be[A]}${e}${be.reset}`:e}function C(e,A={}){if(A.quiet)return;b(A)==="json"?process.stdout.write(`${JSON.stringify(e)}
|
|
3
|
+
`):process.stdout.write(`${JSON.stringify(e,null,2)}
|
|
4
|
+
`)}function ne(e){process.stderr.write(`${JSON.stringify(e)}
|
|
5
|
+
`)}function v(e){process.stdout.write(`${JSON.stringify(e)}
|
|
6
|
+
`)}import"commander";import"commander";import{z as L}from"zod";import{createHash as st,randomBytes as ke,timingSafeEqual as at}from"crypto";function Ce(){let e=ie(ke(32)),A=ie(st("sha256").update(e).digest());return{codeVerifier:e,codeChallenge:A,method:"S256"}}function ve(){return ie(ke(24))}function xe(e,A){let t=Buffer.from(e,"utf8"),r=Buffer.from(A,"utf8");return t.length!==r.length?!1:at(t,r)}function ie(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}import ct from"http";var Te="data:image/png;base64,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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACA/78dOBAAAAAAELQ/9SIFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAP3ZeKxkxamrQAAAABJRU5ErkJggg==";async function Re(){let e=null,A=null,t=(l,p)=>{e=null,A=null,l(p)},r=!1,o=ct.createServer((l,p)=>{if(p.setHeader("Connection","close"),p.setHeader("Cache-Control","no-store"),!l.url){p.writeHead(400).end();return}if(l.method!=="GET"){p.writeHead(405,{"Content-Type":"text/plain",Allow:"GET"}).end("Method not allowed");return}let m=new URL(l.url,"http://127.0.0.1");if(m.pathname!=="/callback"){p.writeHead(404,{"Content-Type":"text/plain"}).end("Not found");return}if(r){p.writeHead(410,{"Content-Type":"text/plain"}).end("Callback already consumed");return}let w=m.searchParams.get("code"),y=m.searchParams.get("state"),h=m.searchParams.get("error"),x=m.searchParams.get("error_description")??"";if(h){r=!0;let N=x?` \u2014 ${x}`:"";Be(p,500,`OAuth error: ${h}${N}`),A&&t(A,new Error(`OAuth error: ${h}${N}`));return}if(!w||!y){Be(p,400,"Missing `code` or `state` in callback URL."),A&&t(A,new Error("Missing code or state"));return}r=!0,pt(p),e&&t(e,{code:w,state:y})});await new Promise(l=>{o.listen(0,"127.0.0.1",()=>l())});let s=o.address().port;return{redirectUri:`http://127.0.0.1:${s}/callback`,port:s,waitForCallback(l){return new Promise((p,m)=>{let w=setTimeout(()=>{e=null,A=null,m(new Error(`Timed out waiting for OAuth callback (${l}ms)`))},l);e=y=>{clearTimeout(w),p(y)},A=y=>{clearTimeout(w),m(y)}})},close(){o.close()}}}function Le(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function pt(e){let A=Ee({kind:"success",title:"You're signed in",body:"You can close this tab and return to your terminal."});e.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),e.end(A)}function Be(e,A,t){let r=Ee({kind:"error",title:"Login failed",body:t});e.writeHead(A,{"Content-Type":"text/html; charset=utf-8"}),e.end(r)}function Ee(e){let A=Le(e.title),t=Le(e.body),r=e.kind==="error"?"var(--error)":"var(--brown-800)",o=e.kind==="error"?"0.4":"1",i=e.kind==="success"?"<script>setTimeout(function(){try{window.close();}catch(e){}}, 2000);</script>":"",s=e.kind==="success"?'<p class="hint">Closing this tab automatically…</p>':"";return`<!doctype html>
|
|
281
7
|
<html lang="en">
|
|
282
8
|
<head>
|
|
283
9
|
<meta charset="utf-8">
|
|
284
10
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
285
|
-
<title>Tiro \u2014 ${
|
|
11
|
+
<title>Tiro \u2014 ${A}</title>
|
|
286
12
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
287
13
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
288
14
|
<link href="https://fonts.googleapis.com/css2?family=Averia+Serif+Libre:wght@300;400;700&display=swap" rel="stylesheet">
|
|
@@ -322,14 +48,14 @@ function renderPage(p) {
|
|
|
322
48
|
height: auto;
|
|
323
49
|
margin-bottom: 1.5rem;
|
|
324
50
|
object-fit: contain;
|
|
325
|
-
opacity: ${
|
|
51
|
+
opacity: ${o};
|
|
326
52
|
}
|
|
327
53
|
h1 {
|
|
328
54
|
font-family: 'Averia Serif Libre', Georgia, 'Times New Roman', serif;
|
|
329
55
|
font-size: 36px;
|
|
330
56
|
line-height: 42px;
|
|
331
57
|
font-weight: 400;
|
|
332
|
-
color: ${
|
|
58
|
+
color: ${r};
|
|
333
59
|
}
|
|
334
60
|
.body {
|
|
335
61
|
margin-top: 1.5rem;
|
|
@@ -358,999 +84,35 @@ function renderPage(p) {
|
|
|
358
84
|
<body>
|
|
359
85
|
<div class="top-spacer"></div>
|
|
360
86
|
<main>
|
|
361
|
-
<img class="logo" src="${
|
|
362
|
-
<h1>${
|
|
363
|
-
<p class="body">${
|
|
364
|
-
${
|
|
87
|
+
<img class="logo" src="${Te}" alt="Tiro">
|
|
88
|
+
<h1>${A}</h1>
|
|
89
|
+
<p class="body">${t}</p>
|
|
90
|
+
${s}
|
|
365
91
|
</main>
|
|
366
92
|
<footer>© 2026 <a href="https://tiro.ooo">The Plato Inc.</a> — All rights reserved.</footer>
|
|
367
|
-
${
|
|
93
|
+
${i}
|
|
368
94
|
</body>
|
|
369
|
-
</html
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
await open(url, { wait: false });
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
throw new TiroError(
|
|
391
|
-
{
|
|
392
|
-
code: "keychain_write_failed",
|
|
393
|
-
message: `Failed to write to OS keychain: ${err.message}`,
|
|
394
|
-
errorType: "internal_error",
|
|
395
|
-
suggestion: process.platform === "linux" ? "Linux requires a Secret Service daemon (gnome-keyring or kwallet). Or set TIRO_TOKEN env var." : "Check OS keychain permissions, or set TIRO_TOKEN env var."
|
|
396
|
-
},
|
|
397
|
-
ExitCode.Generic
|
|
398
|
-
);
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
function loadToken() {
|
|
402
|
-
const entry = new Entry(SERVICE, ACCOUNT);
|
|
403
|
-
let raw;
|
|
404
|
-
try {
|
|
405
|
-
raw = entry.getPassword();
|
|
406
|
-
} catch {
|
|
407
|
-
return null;
|
|
408
|
-
}
|
|
409
|
-
if (!raw) return null;
|
|
410
|
-
try {
|
|
411
|
-
return JSON.parse(raw);
|
|
412
|
-
} catch {
|
|
413
|
-
return null;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
function deleteToken() {
|
|
417
|
-
const entry = new Entry(SERVICE, ACCOUNT);
|
|
418
|
-
try {
|
|
419
|
-
return entry.deletePassword();
|
|
420
|
-
} catch {
|
|
421
|
-
return false;
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// src/lib/auth/token.ts
|
|
426
|
-
function resolveToken() {
|
|
427
|
-
const envToken = process.env["TIRO_TOKEN"];
|
|
428
|
-
if (envToken) {
|
|
429
|
-
return { accessToken: envToken, source: "env" };
|
|
430
|
-
}
|
|
431
|
-
const stored = loadToken();
|
|
432
|
-
if (stored) {
|
|
433
|
-
return {
|
|
434
|
-
accessToken: stored.accessToken,
|
|
435
|
-
source: "keychain",
|
|
436
|
-
hostname: stored.hostname,
|
|
437
|
-
expiresAt: stored.expiresAt,
|
|
438
|
-
...stored.userId !== void 0 && { userId: stored.userId }
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
return null;
|
|
442
|
-
}
|
|
443
|
-
function decodeJwtPayload(token) {
|
|
444
|
-
const parts = token.split(".");
|
|
445
|
-
if (parts.length !== 3) return null;
|
|
446
|
-
try {
|
|
447
|
-
const payload = parts[1];
|
|
448
|
-
if (!payload) return null;
|
|
449
|
-
const json = Buffer.from(payload, "base64url").toString("utf8");
|
|
450
|
-
return JSON.parse(json);
|
|
451
|
-
} catch {
|
|
452
|
-
return null;
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// src/lib/config.ts
|
|
457
|
-
import Conf from "conf";
|
|
458
|
-
var DEFAULT_HOSTNAME = "https://api.tiro.ooo";
|
|
459
|
-
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
|
|
460
|
-
var config = new Conf({
|
|
461
|
-
projectName: "tiro",
|
|
462
|
-
defaults: {
|
|
463
|
-
hostname: DEFAULT_HOSTNAME,
|
|
464
|
-
oauthClientId: null,
|
|
465
|
-
oauthClientIdRegisteredAt: null,
|
|
466
|
-
oauthClientHostname: null,
|
|
467
|
-
oauthClientRedirectUri: null,
|
|
468
|
-
defaultOutputDir: null
|
|
469
|
-
}
|
|
470
|
-
});
|
|
471
|
-
function validateHostname(input) {
|
|
472
|
-
const trimmed = input.trim();
|
|
473
|
-
if (trimmed === "") {
|
|
474
|
-
throw hostnameError("empty hostname", input);
|
|
475
|
-
}
|
|
476
|
-
let url;
|
|
477
|
-
try {
|
|
478
|
-
url = new URL(trimmed);
|
|
479
|
-
} catch {
|
|
480
|
-
throw hostnameError("not a valid URL", input);
|
|
481
|
-
}
|
|
482
|
-
if (url.protocol === "https:") {
|
|
483
|
-
} else if (url.protocol === "http:" && isLoopback(url.hostname)) {
|
|
484
|
-
} else {
|
|
485
|
-
throw hostnameError(
|
|
486
|
-
`disallowed scheme "${url.protocol}" \u2014 only https:// or http://localhost is permitted`,
|
|
487
|
-
input
|
|
488
|
-
);
|
|
489
|
-
}
|
|
490
|
-
if (url.username !== "" || url.password !== "") {
|
|
491
|
-
throw hostnameError("URL must not embed credentials", input);
|
|
492
|
-
}
|
|
493
|
-
if (url.search !== "" || url.hash !== "") {
|
|
494
|
-
throw hostnameError("URL must not include query or fragment", input);
|
|
495
|
-
}
|
|
496
|
-
if (url.pathname !== "" && url.pathname !== "/") {
|
|
497
|
-
throw hostnameError("URL must be host root (no path segment)", input);
|
|
498
|
-
}
|
|
499
|
-
const origin = url.origin;
|
|
500
|
-
return stripTrailingSlash(origin);
|
|
501
|
-
}
|
|
502
|
-
function isLoopback(host) {
|
|
503
|
-
if (host.startsWith("[") && host.endsWith("]")) {
|
|
504
|
-
host = host.slice(1, -1);
|
|
505
|
-
}
|
|
506
|
-
return LOOPBACK_HOSTS.has(host);
|
|
507
|
-
}
|
|
508
|
-
function hostnameError(reason, input) {
|
|
509
|
-
return new TiroError(
|
|
510
|
-
{
|
|
511
|
-
code: "invalid_hostname",
|
|
512
|
-
message: `Refusing to use hostname "${input}": ${reason}.`,
|
|
513
|
-
errorType: "bad_request",
|
|
514
|
-
suggestion: "Use https://<host> (or http://localhost for local dev). If TIRO_HOSTNAME is set in your environment, unset it."
|
|
515
|
-
},
|
|
516
|
-
ExitCode.Usage
|
|
517
|
-
);
|
|
518
|
-
}
|
|
519
|
-
function getHostname(override) {
|
|
520
|
-
if (override !== void 0) return validateHostname(override);
|
|
521
|
-
const env = process.env["TIRO_HOSTNAME"];
|
|
522
|
-
if (env) return validateHostname(env);
|
|
523
|
-
return validateHostname(config.get("hostname"));
|
|
524
|
-
}
|
|
525
|
-
function getOauthClientId(hostname, currentRedirectUri) {
|
|
526
|
-
const id = config.get("oauthClientId");
|
|
527
|
-
const registeredAt = config.get("oauthClientIdRegisteredAt");
|
|
528
|
-
const cachedHostname = config.get("oauthClientHostname");
|
|
529
|
-
const cachedRedirectUri = config.get("oauthClientRedirectUri");
|
|
530
|
-
if (!id || !registeredAt) return null;
|
|
531
|
-
if (cachedHostname !== hostname) return null;
|
|
532
|
-
if (!cachedRedirectUri) return null;
|
|
533
|
-
if (!sameRedirectTarget(cachedRedirectUri, currentRedirectUri)) return null;
|
|
534
|
-
const ttlMs = 29 * 24 * 60 * 60 * 1e3;
|
|
535
|
-
if (Date.now() - registeredAt > ttlMs) return null;
|
|
536
|
-
return id;
|
|
537
|
-
}
|
|
538
|
-
function setOauthClientId(clientId, hostname, redirectUri) {
|
|
539
|
-
config.set("oauthClientId", clientId);
|
|
540
|
-
config.set("oauthClientIdRegisteredAt", Date.now());
|
|
541
|
-
config.set("oauthClientHostname", hostname);
|
|
542
|
-
config.set("oauthClientRedirectUri", redirectUri);
|
|
543
|
-
}
|
|
544
|
-
function clearOauthClientId() {
|
|
545
|
-
config.set("oauthClientId", null);
|
|
546
|
-
config.set("oauthClientIdRegisteredAt", null);
|
|
547
|
-
config.set("oauthClientHostname", null);
|
|
548
|
-
config.set("oauthClientRedirectUri", null);
|
|
549
|
-
}
|
|
550
|
-
function sameRedirectTarget(a, b) {
|
|
551
|
-
let pa, pb;
|
|
552
|
-
try {
|
|
553
|
-
pa = new URL(a);
|
|
554
|
-
pb = new URL(b);
|
|
555
|
-
} catch {
|
|
556
|
-
return false;
|
|
557
|
-
}
|
|
558
|
-
return pa.protocol === pb.protocol && pa.hostname === pb.hostname && pa.pathname === pb.pathname;
|
|
559
|
-
}
|
|
560
|
-
function stripTrailingSlash(s) {
|
|
561
|
-
return s.endsWith("/") ? s.slice(0, -1) : s;
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
// src/lib/auth/flow.ts
|
|
565
|
-
var RegisterResponseSchema = z.object({
|
|
566
|
-
client_id: z.string(),
|
|
567
|
-
client_secret: z.string().optional()
|
|
568
|
-
});
|
|
569
|
-
var TokenResponseSchema = z.object({
|
|
570
|
-
access_token: z.string(),
|
|
571
|
-
token_type: z.string(),
|
|
572
|
-
expires_in: z.number().optional(),
|
|
573
|
-
scope: z.string().optional()
|
|
574
|
-
});
|
|
575
|
-
var CALLBACK_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
576
|
-
var DEFAULT_SCOPE = "mcp:notes:read";
|
|
577
|
-
async function performLogin(options = {}) {
|
|
578
|
-
const hostname = getHostname(options.hostname);
|
|
579
|
-
const onPrompt = options.onPrompt ?? ((msg) => process.stderr.write(`${msg}
|
|
580
|
-
`));
|
|
581
|
-
const loopback = await startLoopbackServer();
|
|
582
|
-
try {
|
|
583
|
-
const clientId = await ensureOauthClient(hostname, loopback.redirectUri);
|
|
584
|
-
const { codeVerifier, codeChallenge } = generatePkce();
|
|
585
|
-
const state = generateState();
|
|
586
|
-
const authorizeUrl = buildAuthorizeUrl({
|
|
587
|
-
hostname,
|
|
588
|
-
clientId,
|
|
589
|
-
redirectUri: loopback.redirectUri,
|
|
590
|
-
state,
|
|
591
|
-
codeChallenge,
|
|
592
|
-
scope: options.scope ?? DEFAULT_SCOPE
|
|
593
|
-
});
|
|
594
|
-
if (options.noBrowser) {
|
|
595
|
-
onPrompt(`Open this URL in your browser:
|
|
596
|
-
${authorizeUrl}`);
|
|
597
|
-
} else {
|
|
598
|
-
onPrompt(`Opening browser for sign-in...`);
|
|
599
|
-
onPrompt(`If the browser does not open, visit:
|
|
600
|
-
${authorizeUrl}`);
|
|
601
|
-
await openBrowser(authorizeUrl);
|
|
602
|
-
}
|
|
603
|
-
const callback = await loopback.waitForCallback(CALLBACK_TIMEOUT_MS);
|
|
604
|
-
if (!verifyState(callback.state, state)) {
|
|
605
|
-
throw new TiroError(
|
|
606
|
-
{
|
|
607
|
-
code: "auth_state_mismatch",
|
|
608
|
-
message: "OAuth state mismatch \u2014 possible CSRF. Aborting.",
|
|
609
|
-
errorType: "unauthorized"
|
|
610
|
-
},
|
|
611
|
-
ExitCode.Generic
|
|
612
|
-
);
|
|
613
|
-
}
|
|
614
|
-
const tokenRes = await exchangeToken({
|
|
615
|
-
hostname,
|
|
616
|
-
clientId,
|
|
617
|
-
code: callback.code,
|
|
618
|
-
redirectUri: loopback.redirectUri,
|
|
619
|
-
codeVerifier
|
|
620
|
-
});
|
|
621
|
-
const expiresAt = computeExpiry(tokenRes.expires_in);
|
|
622
|
-
const payload = decodeJwtPayload(tokenRes.access_token);
|
|
623
|
-
const userId = typeof payload?.["sub"] === "string" ? payload["sub"] : void 0;
|
|
624
|
-
const stored = {
|
|
625
|
-
accessToken: tokenRes.access_token,
|
|
626
|
-
tokenType: tokenRes.token_type,
|
|
627
|
-
expiresAt,
|
|
628
|
-
hostname,
|
|
629
|
-
...tokenRes.scope !== void 0 && { scope: tokenRes.scope },
|
|
630
|
-
...userId !== void 0 && { userId }
|
|
631
|
-
};
|
|
632
|
-
saveToken(stored);
|
|
633
|
-
return { hostname, userId, expiresAt };
|
|
634
|
-
} finally {
|
|
635
|
-
loopback.close();
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
async function ensureOauthClient(hostname, redirectUri) {
|
|
639
|
-
const cached = getOauthClientId(hostname, redirectUri);
|
|
640
|
-
if (cached) return cached;
|
|
641
|
-
const url = `${hostname}/v1/mcp/oauth/register`;
|
|
642
|
-
let res;
|
|
643
|
-
try {
|
|
644
|
-
res = await fetch(url, {
|
|
645
|
-
method: "POST",
|
|
646
|
-
headers: { "Content-Type": "application/json" },
|
|
647
|
-
body: JSON.stringify({
|
|
648
|
-
client_name: "tiro-cli",
|
|
649
|
-
redirect_uris: [redirectUri],
|
|
650
|
-
grant_types: ["authorization_code"],
|
|
651
|
-
response_types: ["code"],
|
|
652
|
-
token_endpoint_auth_method: "none",
|
|
653
|
-
scope: DEFAULT_SCOPE
|
|
654
|
-
})
|
|
655
|
-
});
|
|
656
|
-
} catch (err) {
|
|
657
|
-
throw new TiroError(
|
|
658
|
-
{
|
|
659
|
-
code: "network_error",
|
|
660
|
-
message: `Failed to reach ${hostname}: ${err.message}`,
|
|
661
|
-
errorType: "network_error",
|
|
662
|
-
suggestion: "Check your network connection or --hostname."
|
|
663
|
-
},
|
|
664
|
-
ExitCode.Generic
|
|
665
|
-
);
|
|
666
|
-
}
|
|
667
|
-
if (!res.ok) {
|
|
668
|
-
const detail = await safeText(res);
|
|
669
|
-
throw new TiroError(
|
|
670
|
-
{
|
|
671
|
-
code: "oauth_register_failed",
|
|
672
|
-
message: `Dynamic Client Registration failed: HTTP ${res.status}`,
|
|
673
|
-
errorType: "internal_error",
|
|
674
|
-
httpStatus: res.status,
|
|
675
|
-
...detail !== "" && { suggestion: detail.slice(0, 200) }
|
|
676
|
-
},
|
|
677
|
-
ExitCode.Generic
|
|
678
|
-
);
|
|
679
|
-
}
|
|
680
|
-
const json = await res.json();
|
|
681
|
-
const parsed = RegisterResponseSchema.safeParse(json);
|
|
682
|
-
if (!parsed.success) {
|
|
683
|
-
throw new TiroError(
|
|
684
|
-
{
|
|
685
|
-
code: "oauth_register_invalid",
|
|
686
|
-
message: "Registration response did not match expected shape.",
|
|
687
|
-
errorType: "internal_error"
|
|
688
|
-
},
|
|
689
|
-
ExitCode.Generic
|
|
690
|
-
);
|
|
691
|
-
}
|
|
692
|
-
setOauthClientId(parsed.data.client_id, hostname, redirectUri);
|
|
693
|
-
return parsed.data.client_id;
|
|
694
|
-
}
|
|
695
|
-
function buildAuthorizeUrl(input) {
|
|
696
|
-
const u = new URL(`${input.hostname}/v1/mcp/oauth/authorize`);
|
|
697
|
-
u.searchParams.set("response_type", "code");
|
|
698
|
-
u.searchParams.set("client_id", input.clientId);
|
|
699
|
-
u.searchParams.set("redirect_uri", input.redirectUri);
|
|
700
|
-
u.searchParams.set("state", input.state);
|
|
701
|
-
u.searchParams.set("code_challenge", input.codeChallenge);
|
|
702
|
-
u.searchParams.set("code_challenge_method", "S256");
|
|
703
|
-
u.searchParams.set("scope", input.scope);
|
|
704
|
-
return u.toString();
|
|
705
|
-
}
|
|
706
|
-
async function exchangeToken(input) {
|
|
707
|
-
const url = `${input.hostname}/v1/mcp/oauth/token`;
|
|
708
|
-
const body = new URLSearchParams({
|
|
709
|
-
grant_type: "authorization_code",
|
|
710
|
-
code: input.code,
|
|
711
|
-
redirect_uri: input.redirectUri,
|
|
712
|
-
client_id: input.clientId,
|
|
713
|
-
code_verifier: input.codeVerifier
|
|
714
|
-
});
|
|
715
|
-
let res;
|
|
716
|
-
try {
|
|
717
|
-
res = await fetch(url, {
|
|
718
|
-
method: "POST",
|
|
719
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
720
|
-
body: body.toString()
|
|
721
|
-
});
|
|
722
|
-
} catch (err) {
|
|
723
|
-
throw new TiroError(
|
|
724
|
-
{
|
|
725
|
-
code: "network_error",
|
|
726
|
-
message: `Failed to reach ${input.hostname}: ${err.message}`,
|
|
727
|
-
errorType: "network_error"
|
|
728
|
-
},
|
|
729
|
-
ExitCode.Generic
|
|
730
|
-
);
|
|
731
|
-
}
|
|
732
|
-
if (!res.ok) {
|
|
733
|
-
if (res.status === 400 || res.status === 401) {
|
|
734
|
-
clearOauthClientId();
|
|
735
|
-
}
|
|
736
|
-
const detail = await safeText(res);
|
|
737
|
-
throw new TiroError(
|
|
738
|
-
{
|
|
739
|
-
code: "oauth_token_failed",
|
|
740
|
-
message: `Token exchange failed: HTTP ${res.status}`,
|
|
741
|
-
errorType: "unauthorized",
|
|
742
|
-
httpStatus: res.status,
|
|
743
|
-
...detail !== "" && { suggestion: detail.slice(0, 200) }
|
|
744
|
-
},
|
|
745
|
-
ExitCode.AuthRequired
|
|
746
|
-
);
|
|
747
|
-
}
|
|
748
|
-
const json = await res.json();
|
|
749
|
-
const parsed = TokenResponseSchema.safeParse(json);
|
|
750
|
-
if (!parsed.success) {
|
|
751
|
-
throw new TiroError(
|
|
752
|
-
{
|
|
753
|
-
code: "oauth_token_invalid",
|
|
754
|
-
message: "Token response did not match expected shape.",
|
|
755
|
-
errorType: "internal_error"
|
|
756
|
-
},
|
|
757
|
-
ExitCode.Generic
|
|
758
|
-
);
|
|
759
|
-
}
|
|
760
|
-
return parsed.data;
|
|
761
|
-
}
|
|
762
|
-
function computeExpiry(expiresIn) {
|
|
763
|
-
const fallbackSeconds = 180 * 24 * 60 * 60;
|
|
764
|
-
const seconds = expiresIn ?? fallbackSeconds;
|
|
765
|
-
return Date.now() + seconds * 1e3;
|
|
766
|
-
}
|
|
767
|
-
async function safeText(res) {
|
|
768
|
-
try {
|
|
769
|
-
return await res.text();
|
|
770
|
-
} catch {
|
|
771
|
-
return "";
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
// src/commands/auth/login.ts
|
|
776
|
-
function registerAuthLogin(parent) {
|
|
777
|
-
parent.command("login").description("Sign in to Tiro via OAuth (browser-based, PKCE)").option("--hostname <url>", "API base URL (overrides config / TIRO_HOSTNAME)").option("--no-browser", "Print the URL instead of opening a browser").action(async (opts, cmd) => {
|
|
778
|
-
const globalOpts = cmd.optsWithGlobals();
|
|
779
|
-
const result = await performLogin({
|
|
780
|
-
...opts.hostname !== void 0 && { hostname: opts.hostname },
|
|
781
|
-
noBrowser: opts.noBrowser === true,
|
|
782
|
-
onPrompt: (msg) => {
|
|
783
|
-
if (globalOpts.quiet) return;
|
|
784
|
-
process.stderr.write(`${color(msg, "cyan", globalOpts)}
|
|
785
|
-
`);
|
|
786
|
-
}
|
|
787
|
-
});
|
|
788
|
-
const hostname = getHostname(opts.hostname);
|
|
789
|
-
const expiresIso = new Date(result.expiresAt).toISOString();
|
|
790
|
-
if (globalOpts.json) {
|
|
791
|
-
printOutput(
|
|
792
|
-
{
|
|
793
|
-
ok: true,
|
|
794
|
-
data: {
|
|
795
|
-
signedIn: true,
|
|
796
|
-
hostname,
|
|
797
|
-
userId: result.userId ?? null,
|
|
798
|
-
expiresAt: expiresIso
|
|
799
|
-
}
|
|
800
|
-
},
|
|
801
|
-
globalOpts
|
|
802
|
-
);
|
|
803
|
-
} else if (!globalOpts.quiet) {
|
|
804
|
-
process.stderr.write(`${color("\u2713", "green", globalOpts)} Signed in to ${hostname}
|
|
805
|
-
`);
|
|
806
|
-
if (result.userId) {
|
|
807
|
-
process.stderr.write(` user: ${result.userId}
|
|
808
|
-
`);
|
|
809
|
-
}
|
|
810
|
-
process.stderr.write(` token expires: ${expiresIso}
|
|
811
|
-
`);
|
|
812
|
-
}
|
|
813
|
-
});
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
// src/commands/auth/status.ts
|
|
817
|
-
import "commander";
|
|
818
|
-
function registerAuthStatus(parent) {
|
|
819
|
-
parent.command("status").description("Show current authenticated account and scopes").action(async (_opts, cmd) => {
|
|
820
|
-
const globalOpts = cmd.optsWithGlobals();
|
|
821
|
-
const t = resolveToken();
|
|
822
|
-
if (!t) {
|
|
823
|
-
throw authRequired();
|
|
824
|
-
}
|
|
825
|
-
const payload = decodeJwtPayload(t.accessToken);
|
|
826
|
-
const sub = typeof payload?.["sub"] === "string" ? payload["sub"] : null;
|
|
827
|
-
const exp = typeof payload?.["exp"] === "number" ? payload["exp"] * 1e3 : null;
|
|
828
|
-
const scope = typeof payload?.["scope"] === "string" ? payload["scope"] : null;
|
|
829
|
-
const expMs = exp ?? t.expiresAt ?? null;
|
|
830
|
-
const expired = expMs !== null && Date.now() >= expMs;
|
|
831
|
-
const report = {
|
|
832
|
-
signedIn: true,
|
|
833
|
-
source: t.source,
|
|
834
|
-
hostname: t.hostname ?? null,
|
|
835
|
-
userId: t.userId ?? sub,
|
|
836
|
-
scope,
|
|
837
|
-
expiresAt: expMs ? new Date(expMs).toISOString() : null,
|
|
838
|
-
expired,
|
|
839
|
-
tokenPrefix: `${t.accessToken.slice(0, 4)}...***`
|
|
840
|
-
};
|
|
841
|
-
if (globalOpts.json || !process.stdout.isTTY) {
|
|
842
|
-
printOutput({ ok: true, data: report }, globalOpts);
|
|
843
|
-
} else {
|
|
844
|
-
const headIcon = expired ? color("!", "yellow", globalOpts) : color("\u2713", "green", globalOpts);
|
|
845
|
-
const headText = expired ? "Token expired" : "Signed in";
|
|
846
|
-
process.stdout.write(`${headIcon} ${headText}
|
|
847
|
-
`);
|
|
848
|
-
process.stdout.write(` source: ${report.source}
|
|
849
|
-
`);
|
|
850
|
-
if (report.hostname) process.stdout.write(` hostname: ${report.hostname}
|
|
851
|
-
`);
|
|
852
|
-
if (report.userId) process.stdout.write(` user: ${report.userId}
|
|
853
|
-
`);
|
|
854
|
-
if (report.scope) process.stdout.write(` scope: ${report.scope}
|
|
855
|
-
`);
|
|
856
|
-
if (report.expiresAt) {
|
|
857
|
-
const tag = expired ? color(" (expired)", "red", globalOpts) : "";
|
|
858
|
-
process.stdout.write(` expires at: ${report.expiresAt}${tag}
|
|
859
|
-
`);
|
|
860
|
-
}
|
|
861
|
-
process.stdout.write(` token: ${report.tokenPrefix}
|
|
862
|
-
`);
|
|
863
|
-
if (expired) {
|
|
864
|
-
process.stdout.write(
|
|
865
|
-
`
|
|
866
|
-
${color("\u2192", "gray", globalOpts)} Run \`tiro auth login\` to refresh.
|
|
867
|
-
`
|
|
868
|
-
);
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
});
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
// src/commands/auth/logout.ts
|
|
875
|
-
import "commander";
|
|
876
|
-
function registerAuthLogout(parent) {
|
|
877
|
-
parent.command("logout").description("Sign out and clear the stored token").action(async (_opts, cmd) => {
|
|
878
|
-
const globalOpts = cmd.optsWithGlobals();
|
|
879
|
-
const removed = deleteToken();
|
|
880
|
-
clearOauthClientId();
|
|
881
|
-
if (globalOpts.json) {
|
|
882
|
-
printOutput({ ok: true, data: { signedOut: true, hadToken: removed } }, globalOpts);
|
|
883
|
-
} else if (!globalOpts.quiet) {
|
|
884
|
-
if (removed) {
|
|
885
|
-
process.stderr.write(`${color("\u2713", "green", globalOpts)} Signed out
|
|
886
|
-
`);
|
|
887
|
-
} else {
|
|
888
|
-
process.stderr.write(`${color("\u2022", "gray", globalOpts)} No token was stored
|
|
889
|
-
`);
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
});
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
// src/commands/auth/index.ts
|
|
896
|
-
function registerAuth(program) {
|
|
897
|
-
const auth = program.command("auth").description("Manage authentication");
|
|
898
|
-
registerAuthLogin(auth);
|
|
899
|
-
registerAuthStatus(auth);
|
|
900
|
-
registerAuthLogout(auth);
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
// src/commands/notes/index.ts
|
|
904
|
-
import "commander";
|
|
905
|
-
|
|
906
|
-
// src/commands/notes/list.ts
|
|
907
|
-
import "commander";
|
|
908
|
-
|
|
909
|
-
// src/lib/api/client.ts
|
|
910
|
-
import "zod";
|
|
911
|
-
|
|
912
|
-
// src/lib/api/types.ts
|
|
913
|
-
import { z as z2 } from "zod";
|
|
914
|
-
var CollaboratorSchema = z2.object({
|
|
915
|
-
guid: z2.string(),
|
|
916
|
-
name: z2.string(),
|
|
917
|
-
email: z2.string(),
|
|
918
|
-
role: z2.enum(["OWNER", "EDITOR", "VIEWER"])
|
|
919
|
-
});
|
|
920
|
-
var ParticipantSchema = z2.object({
|
|
921
|
-
name: z2.string().nullable().optional(),
|
|
922
|
-
email: z2.string().nullable().optional()
|
|
923
|
-
});
|
|
924
|
-
var NoteSchema = z2.object({
|
|
925
|
-
guid: z2.string(),
|
|
926
|
-
title: z2.string(),
|
|
927
|
-
createdAt: z2.string(),
|
|
928
|
-
updatedAt: z2.string(),
|
|
929
|
-
sourceType: z2.string(),
|
|
930
|
-
recordingDurationSeconds: z2.number(),
|
|
931
|
-
collaborators: z2.array(CollaboratorSchema).optional().default([]),
|
|
932
|
-
participants: z2.array(ParticipantSchema).optional().default([]),
|
|
933
|
-
webUrl: z2.string(),
|
|
934
|
-
recordingStartAt: z2.string().nullable().optional(),
|
|
935
|
-
recordingEndAt: z2.string().nullable().optional()
|
|
936
|
-
}).passthrough();
|
|
937
|
-
var TextObjectSchema = z2.object({
|
|
938
|
-
type: z2.string(),
|
|
939
|
-
content: z2.string()
|
|
940
|
-
});
|
|
941
|
-
var SpeakerInfoSchema = z2.object({
|
|
942
|
-
label: z2.string(),
|
|
943
|
-
personName: z2.string().nullable().optional()
|
|
944
|
-
});
|
|
945
|
-
var DiarizedSegmentSchema = z2.object({
|
|
946
|
-
content: z2.string(),
|
|
947
|
-
speaker: SpeakerInfoSchema
|
|
948
|
-
});
|
|
949
|
-
var ParagraphSchema = z2.object({
|
|
950
|
-
uuid: z2.string(),
|
|
951
|
-
transcribeLocale: z2.string().nullable().optional(),
|
|
952
|
-
transcript: TextObjectSchema.nullable().optional(),
|
|
953
|
-
diarizedSegments: z2.array(DiarizedSegmentSchema).nullable().optional(),
|
|
954
|
-
timeFrom: z2.string().nullable().optional(),
|
|
955
|
-
timeTo: z2.string().nullable().optional(),
|
|
956
|
-
locked: z2.boolean().optional()
|
|
957
|
-
}).passthrough();
|
|
958
|
-
var McpSegmentSchema = z2.object({
|
|
959
|
-
content: z2.string(),
|
|
960
|
-
speaker: z2.object({
|
|
961
|
-
label: z2.string(),
|
|
962
|
-
name: z2.string().nullable()
|
|
963
|
-
}).nullable()
|
|
964
|
-
});
|
|
965
|
-
var McpParagraphSchema = z2.object({
|
|
966
|
-
timeFrom: z2.string().nullable(),
|
|
967
|
-
timeTo: z2.string().nullable(),
|
|
968
|
-
segments: z2.array(McpSegmentSchema)
|
|
969
|
-
});
|
|
970
|
-
var McpTranscriptSchema = z2.object({
|
|
971
|
-
noteGuid: z2.string(),
|
|
972
|
-
title: z2.string(),
|
|
973
|
-
participants: z2.array(z2.string()),
|
|
974
|
-
createdAt: z2.string(),
|
|
975
|
-
recordingDurationSeconds: z2.number(),
|
|
976
|
-
paragraphs: z2.array(McpParagraphSchema)
|
|
977
|
-
});
|
|
978
|
-
var PageCursorResponseSchema = (item) => z2.object({
|
|
979
|
-
content: z2.array(item),
|
|
980
|
-
nextCursor: z2.string().nullable()
|
|
981
|
-
});
|
|
982
|
-
var SimpleListResponseSchema = (item) => z2.object({
|
|
983
|
-
content: z2.array(item)
|
|
984
|
-
});
|
|
985
|
-
var ApiErrorSchema = z2.object({
|
|
986
|
-
error: z2.object({
|
|
987
|
-
code: z2.number(),
|
|
988
|
-
errorType: z2.string(),
|
|
989
|
-
message: z2.string(),
|
|
990
|
-
detail: z2.string().nullable().optional()
|
|
991
|
-
})
|
|
992
|
-
});
|
|
993
|
-
var WorkspaceMeSchema = z2.object({
|
|
994
|
-
guid: z2.string()
|
|
995
|
-
}).passthrough();
|
|
996
|
-
var WorkspaceItemSchema = z2.object({
|
|
997
|
-
guid: z2.string(),
|
|
998
|
-
name: z2.string(),
|
|
999
|
-
isWikiEnabled: z2.boolean()
|
|
1000
|
-
}).passthrough();
|
|
1001
|
-
var WorkspacesListSchema = z2.object({
|
|
1002
|
-
workspaces: z2.array(WorkspaceItemSchema)
|
|
1003
|
-
}).passthrough();
|
|
1004
|
-
var WikiSearchPageSchema = z2.object({
|
|
1005
|
-
guid: z2.string(),
|
|
1006
|
-
wikiId: z2.number(),
|
|
1007
|
-
canonicalName: z2.string(),
|
|
1008
|
-
pageType: z2.string(),
|
|
1009
|
-
entitySubtype: z2.string().nullable().optional(),
|
|
1010
|
-
score: z2.number()
|
|
1011
|
-
}).passthrough();
|
|
1012
|
-
var WikiSearchPagesResponseSchema = z2.object({
|
|
1013
|
-
items: z2.array(WikiSearchPageSchema)
|
|
1014
|
-
}).passthrough();
|
|
1015
|
-
var WikiMentionSchema = z2.object({
|
|
1016
|
-
guid: z2.string(),
|
|
1017
|
-
noteId: z2.number(),
|
|
1018
|
-
paragraphId: z2.number().nullable().optional(),
|
|
1019
|
-
paragraphUuid: z2.string().nullable().optional(),
|
|
1020
|
-
kind: z2.string(),
|
|
1021
|
-
sourceUserId: z2.number(),
|
|
1022
|
-
extractedText: z2.string(),
|
|
1023
|
-
confidence: z2.number().nullable().optional(),
|
|
1024
|
-
createdAt: z2.string()
|
|
1025
|
-
}).passthrough();
|
|
1026
|
-
var WikiAliasSchema = z2.object({
|
|
1027
|
-
guid: z2.string(),
|
|
1028
|
-
alias: z2.string(),
|
|
1029
|
-
source: z2.string(),
|
|
1030
|
-
sourceMentionGuid: z2.string().nullable().optional(),
|
|
1031
|
-
sourceUserId: z2.number().nullable().optional(),
|
|
1032
|
-
createdAt: z2.string()
|
|
1033
|
-
}).passthrough();
|
|
1034
|
-
var WikiLinkSchema = z2.object({
|
|
1035
|
-
guid: z2.string(),
|
|
1036
|
-
sourcePageGuid: z2.string(),
|
|
1037
|
-
sourcePageName: z2.string().nullable().optional(),
|
|
1038
|
-
targetPageGuid: z2.string(),
|
|
1039
|
-
targetPageName: z2.string().nullable().optional(),
|
|
1040
|
-
linkType: z2.string(),
|
|
1041
|
-
linkTypeDisplayKo: z2.string().optional(),
|
|
1042
|
-
linkTypeDisplayEn: z2.string().optional(),
|
|
1043
|
-
isDirectional: z2.boolean(),
|
|
1044
|
-
source: z2.string().optional(),
|
|
1045
|
-
creatorUserId: z2.number().nullable().optional(),
|
|
1046
|
-
createdAt: z2.string().optional(),
|
|
1047
|
-
updatedAt: z2.string().optional()
|
|
1048
|
-
}).passthrough();
|
|
1049
|
-
var WikiPageDetailSchema = z2.object({
|
|
1050
|
-
guid: z2.string(),
|
|
1051
|
-
wikiId: z2.number(),
|
|
1052
|
-
canonicalName: z2.string(),
|
|
1053
|
-
description: z2.string().nullable().optional(),
|
|
1054
|
-
descriptionStatus: z2.string().nullable().optional(),
|
|
1055
|
-
regenerationAvailable: z2.boolean().nullable().optional(),
|
|
1056
|
-
pageType: z2.string(),
|
|
1057
|
-
entitySubtype: z2.string().nullable().optional(),
|
|
1058
|
-
extractionStatus: z2.string(),
|
|
1059
|
-
mentionCountVisible: z2.number(),
|
|
1060
|
-
mentions: z2.array(WikiMentionSchema),
|
|
1061
|
-
aliases: z2.array(WikiAliasSchema),
|
|
1062
|
-
links: z2.array(WikiLinkSchema),
|
|
1063
|
-
lastUpdatedVisible: z2.string().nullable().optional(),
|
|
1064
|
-
createdAt: z2.string(),
|
|
1065
|
-
updatedAt: z2.string()
|
|
1066
|
-
}).passthrough();
|
|
1067
|
-
var WikiMentionListResponseSchema = z2.object({
|
|
1068
|
-
items: z2.array(WikiMentionSchema),
|
|
1069
|
-
nextCursorCreatedAt: z2.string().nullable(),
|
|
1070
|
-
nextCursorId: z2.number().nullable()
|
|
1071
|
-
}).passthrough();
|
|
1072
|
-
var WikiGraphNodeSchema = z2.object({
|
|
1073
|
-
guid: z2.string(),
|
|
1074
|
-
canonicalName: z2.string(),
|
|
1075
|
-
pageType: z2.string(),
|
|
1076
|
-
entitySubtype: z2.string().nullable().optional(),
|
|
1077
|
-
extractionStatus: z2.string(),
|
|
1078
|
-
mentionCountVisible: z2.number()
|
|
1079
|
-
}).passthrough();
|
|
1080
|
-
var WikiGraphEdgeSchema = z2.object({
|
|
1081
|
-
guid: z2.string(),
|
|
1082
|
-
sourcePageGuid: z2.string(),
|
|
1083
|
-
targetPageGuid: z2.string(),
|
|
1084
|
-
linkType: z2.string(),
|
|
1085
|
-
linkTypeDisplayKo: z2.string().optional(),
|
|
1086
|
-
linkTypeDisplayEn: z2.string().optional(),
|
|
1087
|
-
isDirectional: z2.boolean()
|
|
1088
|
-
}).passthrough();
|
|
1089
|
-
var WikiGraphResponseSchema = z2.object({
|
|
1090
|
-
nodes: z2.array(WikiGraphNodeSchema),
|
|
1091
|
-
edges: z2.array(WikiGraphEdgeSchema)
|
|
1092
|
-
}).passthrough();
|
|
1093
|
-
var WikiPlanRequiredSchema = z2.object({
|
|
1094
|
-
// Two gate kinds, relayed verbatim:
|
|
1095
|
-
// WIKI_PLAN_REQUIRED → plan ineligible (upsell; required_plans set)
|
|
1096
|
-
// WIKI_NOT_ACTIVATED → eligible plan, wiki not activated by an admin (required_plans null)
|
|
1097
|
-
error_code: z2.enum(["WIKI_PLAN_REQUIRED", "WIKI_NOT_ACTIVATED"]),
|
|
1098
|
-
message: z2.string(),
|
|
1099
|
-
current_plan: z2.string().nullable().optional(),
|
|
1100
|
-
required_plans: z2.array(z2.string()).nullable().optional(),
|
|
1101
|
-
action_url: z2.string().nullable().optional()
|
|
1102
|
-
}).passthrough();
|
|
1103
|
-
|
|
1104
|
-
// src/lib/api/client.ts
|
|
1105
|
-
var TiroApiClient = class {
|
|
1106
|
-
constructor(hostname, token) {
|
|
1107
|
-
this.hostname = hostname;
|
|
1108
|
-
this.token = token;
|
|
1109
|
-
}
|
|
1110
|
-
hostname;
|
|
1111
|
-
token;
|
|
1112
|
-
async getJson(path, schema, params) {
|
|
1113
|
-
const url = this.buildUrl(path, params);
|
|
1114
|
-
const res = await this.fetch(url, { method: "GET" });
|
|
1115
|
-
return this.parseJson(res, schema, "GET", path);
|
|
1116
|
-
}
|
|
1117
|
-
async postJson(path, schema, body) {
|
|
1118
|
-
const url = this.buildUrl(path);
|
|
1119
|
-
const res = await this.fetch(url, {
|
|
1120
|
-
method: "POST",
|
|
1121
|
-
headers: { "Content-Type": "application/json" },
|
|
1122
|
-
body: JSON.stringify(body)
|
|
1123
|
-
});
|
|
1124
|
-
return this.parseJson(res, schema, "POST", path);
|
|
1125
|
-
}
|
|
1126
|
-
async putJson(path, schema, body) {
|
|
1127
|
-
const url = this.buildUrl(path);
|
|
1128
|
-
const res = await this.fetch(url, {
|
|
1129
|
-
method: "PUT",
|
|
1130
|
-
headers: { "Content-Type": "application/json" },
|
|
1131
|
-
body: JSON.stringify(body)
|
|
1132
|
-
});
|
|
1133
|
-
return this.parseJson(res, schema, "PUT", path);
|
|
1134
|
-
}
|
|
1135
|
-
async deleteVoid(path) {
|
|
1136
|
-
const url = this.buildUrl(path);
|
|
1137
|
-
const res = await this.fetch(url, { method: "DELETE" });
|
|
1138
|
-
if (!res.ok) throw await mapHttpError(res, "DELETE", path);
|
|
1139
|
-
}
|
|
1140
|
-
buildUrl(path, params) {
|
|
1141
|
-
return buildApiUrl(this.hostname, path, params);
|
|
1142
|
-
}
|
|
1143
|
-
async fetch(url, init) {
|
|
1144
|
-
const headers = new Headers(init.headers);
|
|
1145
|
-
headers.set("Authorization", `Bearer ${this.token}`);
|
|
1146
|
-
headers.set("Accept", "application/json");
|
|
1147
|
-
try {
|
|
1148
|
-
return await fetch(url, { ...init, headers });
|
|
1149
|
-
} catch (err) {
|
|
1150
|
-
throw new TiroError(
|
|
1151
|
-
{
|
|
1152
|
-
code: "network_error",
|
|
1153
|
-
message: `Network error reaching ${this.hostname}: ${err.message}`,
|
|
1154
|
-
errorType: "network_error",
|
|
1155
|
-
suggestion: "Check your network or --hostname."
|
|
1156
|
-
},
|
|
1157
|
-
ExitCode.Generic
|
|
1158
|
-
);
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
async parseJson(res, schema, method, path) {
|
|
1162
|
-
if (!res.ok) throw await mapHttpError(res, method, path);
|
|
1163
|
-
let json;
|
|
1164
|
-
try {
|
|
1165
|
-
json = await res.json();
|
|
1166
|
-
} catch (err) {
|
|
1167
|
-
throw new TiroError(
|
|
1168
|
-
{
|
|
1169
|
-
code: "invalid_response",
|
|
1170
|
-
message: `Failed to parse JSON from ${method} ${path}: ${err.message}`,
|
|
1171
|
-
errorType: "internal_error"
|
|
1172
|
-
},
|
|
1173
|
-
ExitCode.Generic
|
|
1174
|
-
);
|
|
1175
|
-
}
|
|
1176
|
-
const parsed = schema.safeParse(json);
|
|
1177
|
-
if (!parsed.success) {
|
|
1178
|
-
throw new TiroError(
|
|
1179
|
-
{
|
|
1180
|
-
code: "schema_mismatch",
|
|
1181
|
-
message: `Response shape did not match expected schema (${method} ${path}).`,
|
|
1182
|
-
errorType: "internal_error",
|
|
1183
|
-
suggestion: parsed.error.issues.slice(0, 3).map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")
|
|
1184
|
-
},
|
|
1185
|
-
ExitCode.Generic
|
|
1186
|
-
);
|
|
1187
|
-
}
|
|
1188
|
-
return parsed.data;
|
|
1189
|
-
}
|
|
1190
|
-
};
|
|
1191
|
-
async function mapHttpError(res, method, path) {
|
|
1192
|
-
const requestId = res.headers.get("x-request-id") ?? void 0;
|
|
1193
|
-
const exitCode = res.status === 401 ? ExitCode.AuthRequired : ExitCode.Generic;
|
|
1194
|
-
if (res.status === 402) {
|
|
1195
|
-
const gate = await tryParsePlanGate(res);
|
|
1196
|
-
if (gate) return planGateError(gate, requestId);
|
|
1197
|
-
}
|
|
1198
|
-
const apiError = await tryParseApiError(res);
|
|
1199
|
-
if (apiError) {
|
|
1200
|
-
return new TiroError(
|
|
1201
|
-
{
|
|
1202
|
-
code: `${apiError.error.errorType}`,
|
|
1203
|
-
message: apiError.error.message,
|
|
1204
|
-
errorType: apiError.error.errorType,
|
|
1205
|
-
httpStatus: res.status,
|
|
1206
|
-
...requestId !== void 0 && { requestId },
|
|
1207
|
-
...res.status === 401 && {
|
|
1208
|
-
suggestion: "Run `tiro auth login` to refresh."
|
|
1209
|
-
}
|
|
1210
|
-
},
|
|
1211
|
-
exitCode
|
|
1212
|
-
);
|
|
1213
|
-
}
|
|
1214
|
-
return new TiroError(
|
|
1215
|
-
{
|
|
1216
|
-
code: "http_error",
|
|
1217
|
-
message: `${method} ${path} failed: HTTP ${res.status} ${res.statusText}`,
|
|
1218
|
-
errorType: httpStatusToErrorType(res.status),
|
|
1219
|
-
httpStatus: res.status,
|
|
1220
|
-
...requestId !== void 0 && { requestId }
|
|
1221
|
-
},
|
|
1222
|
-
exitCode
|
|
1223
|
-
);
|
|
1224
|
-
}
|
|
1225
|
-
function httpStatusToErrorType(status) {
|
|
1226
|
-
if (status === 400) return "bad_request";
|
|
1227
|
-
if (status === 401) return "unauthorized";
|
|
1228
|
-
if (status === 402) return "payment_required";
|
|
1229
|
-
if (status === 403) return "forbidden";
|
|
1230
|
-
if (status === 404) return "not_found";
|
|
1231
|
-
if (status === 409) return "conflict";
|
|
1232
|
-
if (status === 413) return "payload_too_large";
|
|
1233
|
-
if (status === 422) return "unprocessable_entity";
|
|
1234
|
-
if (status === 429) return "too_many_requests";
|
|
1235
|
-
return "internal_error";
|
|
1236
|
-
}
|
|
1237
|
-
async function tryParseApiError(res) {
|
|
1238
|
-
try {
|
|
1239
|
-
const json = await res.clone().json();
|
|
1240
|
-
const parsed = ApiErrorSchema.safeParse(json);
|
|
1241
|
-
return parsed.success ? parsed.data : null;
|
|
1242
|
-
} catch {
|
|
1243
|
-
return null;
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1246
|
-
async function tryParsePlanGate(res) {
|
|
1247
|
-
try {
|
|
1248
|
-
const json = await res.clone().json();
|
|
1249
|
-
const parsed = WikiPlanRequiredSchema.safeParse(json);
|
|
1250
|
-
return parsed.success ? parsed.data : null;
|
|
1251
|
-
} catch {
|
|
1252
|
-
return null;
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
function planGateError(gate, requestId) {
|
|
1256
|
-
const actionUrl = gate.action_url ?? null;
|
|
1257
|
-
return new TiroError(
|
|
1258
|
-
{
|
|
1259
|
-
// Relay the backend's discriminator (WIKI_PLAN_REQUIRED / WIKI_NOT_ACTIVATED) verbatim.
|
|
1260
|
-
code: gate.error_code,
|
|
1261
|
-
message: gate.message,
|
|
1262
|
-
errorType: "payment_required",
|
|
1263
|
-
httpStatus: 402,
|
|
1264
|
-
...actionUrl ? { suggestion: `Upgrade: ${actionUrl}` } : {},
|
|
1265
|
-
...requestId !== void 0 && { requestId },
|
|
1266
|
-
details: { ...gate }
|
|
1267
|
-
},
|
|
1268
|
-
ExitCode.Generic
|
|
1269
|
-
);
|
|
1270
|
-
}
|
|
1271
|
-
function buildApiUrl(hostname, path, params) {
|
|
1272
|
-
if (!path.startsWith("/") || path.startsWith("//") || path.startsWith("/\\")) {
|
|
1273
|
-
throw new TiroError(
|
|
1274
|
-
{
|
|
1275
|
-
code: "internal_error",
|
|
1276
|
-
message: `API path must start with a single "/": got ${JSON.stringify(path)}`,
|
|
1277
|
-
errorType: "internal_error"
|
|
1278
|
-
},
|
|
1279
|
-
ExitCode.Generic
|
|
1280
|
-
);
|
|
1281
|
-
}
|
|
1282
|
-
const u = new URL(`${hostname}${path}`);
|
|
1283
|
-
if (params) {
|
|
1284
|
-
for (const [k, v] of Object.entries(params)) {
|
|
1285
|
-
if (v !== void 0 && v !== null && v !== "") {
|
|
1286
|
-
u.searchParams.set(k, String(v));
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
return u.toString();
|
|
1291
|
-
}
|
|
1292
|
-
function createApiClient(opts = {}) {
|
|
1293
|
-
if (opts.tokenOverride) {
|
|
1294
|
-
return new TiroApiClient(getHostname(opts.hostnameOverride), opts.tokenOverride);
|
|
1295
|
-
}
|
|
1296
|
-
const t = resolveToken();
|
|
1297
|
-
if (!t) throw authRequired();
|
|
1298
|
-
const hostname = getHostname(opts.hostnameOverride ?? t.hostname);
|
|
1299
|
-
return new TiroApiClient(hostname, t.accessToken);
|
|
1300
|
-
}
|
|
1301
|
-
|
|
1302
|
-
// src/lib/util/parseDate.ts
|
|
1303
|
-
var RELATIVE_RE = /^(\d+)([smhdw])$/i;
|
|
1304
|
-
var UNIT_MS = {
|
|
1305
|
-
s: 1e3,
|
|
1306
|
-
m: 6e4,
|
|
1307
|
-
h: 36e5,
|
|
1308
|
-
d: 864e5,
|
|
1309
|
-
w: 6048e5
|
|
1310
|
-
};
|
|
1311
|
-
function parseDate(input) {
|
|
1312
|
-
const trimmed = input.trim();
|
|
1313
|
-
const rel = trimmed.match(RELATIVE_RE);
|
|
1314
|
-
if (rel) {
|
|
1315
|
-
const num = parseInt(rel[1] ?? "", 10);
|
|
1316
|
-
const unit = (rel[2] ?? "").toLowerCase();
|
|
1317
|
-
const factor = UNIT_MS[unit];
|
|
1318
|
-
if (!factor || !Number.isFinite(num)) {
|
|
1319
|
-
throw invalidDate(input);
|
|
1320
|
-
}
|
|
1321
|
-
return new Date(Date.now() - num * factor).toISOString();
|
|
1322
|
-
}
|
|
1323
|
-
const d = new Date(trimmed);
|
|
1324
|
-
if (Number.isNaN(d.getTime())) throw invalidDate(input);
|
|
1325
|
-
return d.toISOString();
|
|
1326
|
-
}
|
|
1327
|
-
function invalidDate(input) {
|
|
1328
|
-
return new TiroError(
|
|
1329
|
-
{
|
|
1330
|
-
code: "invalid_date",
|
|
1331
|
-
message: `Invalid date: "${input}". Use ISO-8601 (e.g. 2026-04-01T10:00:00Z) or relative (e.g. 7d, 24h, 30m).`,
|
|
1332
|
-
errorType: "bad_request"
|
|
1333
|
-
},
|
|
1334
|
-
ExitCode.Usage
|
|
1335
|
-
);
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
// src/lib/util/noteFilter.ts
|
|
1339
|
-
var PLACEHOLDER_TITLE = "Untitled";
|
|
1340
|
-
var PLACEHOLDER_SOURCE_TYPES = /* @__PURE__ */ new Set(["onboarding"]);
|
|
1341
|
-
function isVisibleNote(note) {
|
|
1342
|
-
if (note.title === PLACEHOLDER_TITLE) return false;
|
|
1343
|
-
if (note.sourceType !== null && note.sourceType !== void 0 && PLACEHOLDER_SOURCE_TYPES.has(note.sourceType)) {
|
|
1344
|
-
return false;
|
|
1345
|
-
}
|
|
1346
|
-
return true;
|
|
1347
|
-
}
|
|
1348
|
-
|
|
1349
|
-
// src/commands/notes/list.ts
|
|
1350
|
-
var ListResponseSchema = PageCursorResponseSchema(NoteSchema);
|
|
1351
|
-
var DEFAULT_PAGE_SIZE = 100;
|
|
1352
|
-
var MAX_PAGE_SIZE = 1e3;
|
|
1353
|
-
var HELP_AFTER = `
|
|
95
|
+
</html>`}import dt from"open";async function Ie(e){try{await dt(e,{wait:!1})}catch{}}import{Entry as se}from"@napi-rs/keyring";var ae="io.tiro.cli",ce="default";function Oe(e){let A=new se(ae,ce);try{A.setPassword(JSON.stringify(e))}catch(t){throw new u({code:"keychain_write_failed",message:`Failed to write to OS keychain: ${t.message}`,errorType:"internal_error",suggestion:process.platform==="linux"?"Linux requires a Secret Service daemon (gnome-keyring or kwallet). Or set TIRO_TOKEN env var.":"Check OS keychain permissions, or set TIRO_TOKEN env var."},d.Generic)}}function Pe(){let e=new se(ae,ce),A;try{A=e.getPassword()}catch{return null}if(!A)return null;try{return JSON.parse(A)}catch{return null}}function Xe(){let e=new se(ae,ce);try{return e.deletePassword()}catch{return!1}}function K(){let e=process.env.TIRO_TOKEN;if(e)return{accessToken:e,source:"env"};let A=Pe();return A?{accessToken:A.accessToken,source:"keychain",hostname:A.hostname,expiresAt:A.expiresAt,...A.userId!==void 0&&{userId:A.userId}}:null}function Z(e){let A=e.split(".");if(A.length!==3)return null;try{let t=A[1];if(!t)return null;let r=Buffer.from(t,"base64url").toString("utf8");return JSON.parse(r)}catch{return null}}import ut from"conf";var lt="https://api.tiro.ooo",mt=new Set(["localhost","127.0.0.1","[::1]","::1"]),T=new ut({projectName:"tiro",defaults:{hostname:lt,oauthClientId:null,oauthClientIdRegisteredAt:null,oauthClientHostname:null,oauthClientRedirectUri:null,defaultOutputDir:null}});function pe(e){let A=e.trim();if(A==="")throw F("empty hostname",e);let t;try{t=new URL(A)}catch{throw F("not a valid URL",e)}if(t.protocol!=="https:"){if(!(t.protocol==="http:"&>(t.hostname)))throw F(`disallowed scheme "${t.protocol}" \u2014 only https:// or http://localhost is permitted`,e)}if(t.username!==""||t.password!=="")throw F("URL must not embed credentials",e);if(t.search!==""||t.hash!=="")throw F("URL must not include query or fragment",e);if(t.pathname!==""&&t.pathname!=="/")throw F("URL must be host root (no path segment)",e);let r=t.origin;return ht(r)}function gt(e){return e.startsWith("[")&&e.endsWith("]")&&(e=e.slice(1,-1)),mt.has(e)}function F(e,A){return new u({code:"invalid_hostname",message:`Refusing to use hostname "${A}": ${e}.`,errorType:"bad_request",suggestion:"Use https://<host> (or http://localhost for local dev). If TIRO_HOSTNAME is set in your environment, unset it."},d.Usage)}function P(e){if(e!==void 0)return pe(e);let A=process.env.TIRO_HOSTNAME;return pe(A||T.get("hostname"))}function Se(e,A){let t=T.get("oauthClientId"),r=T.get("oauthClientIdRegisteredAt"),o=T.get("oauthClientHostname"),i=T.get("oauthClientRedirectUri");if(!t||!r||o!==e||!i||!ft(i,A))return null;let s=696*60*60*1e3;return Date.now()-r>s?null:t}function Fe(e,A,t){T.set("oauthClientId",e),T.set("oauthClientIdRegisteredAt",Date.now()),T.set("oauthClientHostname",A),T.set("oauthClientRedirectUri",t)}function J(){T.set("oauthClientId",null),T.set("oauthClientIdRegisteredAt",null),T.set("oauthClientHostname",null),T.set("oauthClientRedirectUri",null)}function ft(e,A){let t,r;try{t=new URL(e),r=new URL(A)}catch{return!1}return t.protocol===r.protocol&&t.hostname===r.hostname&&t.pathname===r.pathname}function ht(e){return e.endsWith("/")?e.slice(0,-1):e}var wt=L.object({client_id:L.string(),client_secret:L.string().optional()}),yt=L.object({access_token:L.string(),token_type:L.string(),expires_in:L.number().optional(),scope:L.string().optional()}),bt=300*1e3,De="mcp:notes:read";async function qe(e={}){let A=P(e.hostname),t=e.onPrompt??(o=>process.stderr.write(`${o}
|
|
96
|
+
`)),r=await Re();try{let o=await kt(A,r.redirectUri),{codeVerifier:i,codeChallenge:s}=Ce(),c=ve(),l=Ct({hostname:A,clientId:o,redirectUri:r.redirectUri,state:c,codeChallenge:s,scope:e.scope??De});e.noBrowser?t(`Open this URL in your browser:
|
|
97
|
+
${l}`):(t("Opening browser for sign-in..."),t(`If the browser does not open, visit:
|
|
98
|
+
${l}`),await Ie(l));let p=await r.waitForCallback(bt);if(!xe(p.state,c))throw new u({code:"auth_state_mismatch",message:"OAuth state mismatch \u2014 possible CSRF. Aborting.",errorType:"unauthorized"},d.Generic);let m=await vt({hostname:A,clientId:o,code:p.code,redirectUri:r.redirectUri,codeVerifier:i}),w=xt(m.expires_in),y=Z(m.access_token),h=typeof y?.sub=="string"?y.sub:void 0,x={accessToken:m.access_token,tokenType:m.token_type,expiresAt:w,hostname:A,...m.scope!==void 0&&{scope:m.scope},...h!==void 0&&{userId:h}};return Oe(x),{hostname:A,userId:h,expiresAt:w}}finally{r.close()}}async function kt(e,A){let t=Se(e,A);if(t)return t;let r=`${e}/v1/mcp/oauth/register`,o;try{o=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({client_name:"tiro-cli",redirect_uris:[A],grant_types:["authorization_code"],response_types:["code"],token_endpoint_auth_method:"none",scope:De})})}catch(c){throw new u({code:"network_error",message:`Failed to reach ${e}: ${c.message}`,errorType:"network_error",suggestion:"Check your network connection or --hostname."},d.Generic)}if(!o.ok){let c=await Me(o);throw new u({code:"oauth_register_failed",message:`Dynamic Client Registration failed: HTTP ${o.status}`,errorType:"internal_error",httpStatus:o.status,...c!==""&&{suggestion:c.slice(0,200)}},d.Generic)}let i=await o.json(),s=wt.safeParse(i);if(!s.success)throw new u({code:"oauth_register_invalid",message:"Registration response did not match expected shape.",errorType:"internal_error"},d.Generic);return Fe(s.data.client_id,e,A),s.data.client_id}function Ct(e){let A=new URL(`${e.hostname}/v1/mcp/oauth/authorize`);return A.searchParams.set("response_type","code"),A.searchParams.set("client_id",e.clientId),A.searchParams.set("redirect_uri",e.redirectUri),A.searchParams.set("state",e.state),A.searchParams.set("code_challenge",e.codeChallenge),A.searchParams.set("code_challenge_method","S256"),A.searchParams.set("scope",e.scope),A.toString()}async function vt(e){let A=`${e.hostname}/v1/mcp/oauth/token`,t=new URLSearchParams({grant_type:"authorization_code",code:e.code,redirect_uri:e.redirectUri,client_id:e.clientId,code_verifier:e.codeVerifier}),r;try{r=await fetch(A,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:t.toString()})}catch(s){throw new u({code:"network_error",message:`Failed to reach ${e.hostname}: ${s.message}`,errorType:"network_error"},d.Generic)}if(!r.ok){(r.status===400||r.status===401)&&J();let s=await Me(r);throw new u({code:"oauth_token_failed",message:`Token exchange failed: HTTP ${r.status}`,errorType:"unauthorized",httpStatus:r.status,...s!==""&&{suggestion:s.slice(0,200)}},d.AuthRequired)}let o=await r.json(),i=yt.safeParse(o);if(!i.success)throw new u({code:"oauth_token_invalid",message:"Token response did not match expected shape.",errorType:"internal_error"},d.Generic);return i.data}function xt(e){let t=e??15552e3;return Date.now()+t*1e3}async function Me(e){try{return await e.text()}catch{return""}}function He(e){e.command("login").description("Sign in to Tiro via OAuth (browser-based, PKCE)").option("--hostname <url>","API base URL (overrides config / TIRO_HOSTNAME)").option("--no-browser","Print the URL instead of opening a browser").action(async(A,t)=>{let r=t.optsWithGlobals(),o=await qe({...A.hostname!==void 0&&{hostname:A.hostname},noBrowser:A.noBrowser===!0,onPrompt:c=>{r.quiet||process.stderr.write(`${a(c,"cyan",r)}
|
|
99
|
+
`)}}),i=P(A.hostname),s=new Date(o.expiresAt).toISOString();r.json?C({ok:!0,data:{signedIn:!0,hostname:i,userId:o.userId??null,expiresAt:s}},r):r.quiet||(process.stderr.write(`${a("\u2713","green",r)} Signed in to ${i}
|
|
100
|
+
`),o.userId&&process.stderr.write(` user: ${o.userId}
|
|
101
|
+
`),process.stderr.write(` token expires: ${s}
|
|
102
|
+
`))})}import"commander";import"zod";import{z as n}from"zod";var Tt=n.object({guid:n.string(),name:n.string(),email:n.string(),role:n.enum(["OWNER","EDITOR","VIEWER"])}),Lt=n.object({name:n.string().nullable().optional(),email:n.string().nullable().optional()}),B=n.object({guid:n.string(),title:n.string(),createdAt:n.string(),updatedAt:n.string(),sourceType:n.string(),recordingDurationSeconds:n.number(),collaborators:n.array(Tt).optional().default([]),participants:n.array(Lt).optional().default([]),webUrl:n.string(),recordingStartAt:n.string().nullable().optional(),recordingEndAt:n.string().nullable().optional()}).passthrough(),Bt=n.object({type:n.string(),content:n.string()}),Rt=n.object({label:n.string(),personName:n.string().nullable().optional()}),Et=n.object({content:n.string(),speaker:Rt}),D=n.object({uuid:n.string(),transcribeLocale:n.string().nullable().optional(),transcript:Bt.nullable().optional(),diarizedSegments:n.array(Et).nullable().optional(),timeFrom:n.string().nullable().optional(),timeTo:n.string().nullable().optional(),locked:n.boolean().optional()}).passthrough(),It=n.object({content:n.string(),speaker:n.object({label:n.string(),name:n.string().nullable()}).nullable()}),Ot=n.object({timeFrom:n.string().nullable(),timeTo:n.string().nullable(),segments:n.array(It)}),xn=n.object({noteGuid:n.string(),title:n.string(),participants:n.array(n.string()),createdAt:n.string(),recordingDurationSeconds:n.number(),paragraphs:n.array(Ot)}),R=e=>n.object({content:n.array(e),nextCursor:n.string().nullable()}),q=e=>n.object({content:n.array(e)}),We=n.object({error:n.object({code:n.number(),errorType:n.string(),message:n.string(),detail:n.string().nullable().optional()})}),Ne=n.object({guid:n.string()}).passthrough(),Pt=n.object({guid:n.string(),name:n.string(),isWikiEnabled:n.boolean()}).passthrough(),Ge=n.object({workspaces:n.array(Pt)}).passthrough(),Ve=n.object({workspaceGuid:n.string().nullable(),userId:n.number().nullable().optional(),apiKeyName:n.string().nullable().optional()}).passthrough(),Xt=n.object({guid:n.string(),wikiId:n.number(),canonicalName:n.string(),pageType:n.string(),entitySubtype:n.string().nullable().optional(),score:n.number()}).passthrough(),Ue=n.object({items:n.array(Xt)}).passthrough(),je=n.object({guid:n.string(),noteId:n.number(),paragraphId:n.number().nullable().optional(),paragraphUuid:n.string().nullable().optional(),kind:n.string(),sourceUserId:n.number(),extractedText:n.string(),confidence:n.number().nullable().optional(),createdAt:n.string()}).passthrough(),St=n.object({guid:n.string(),alias:n.string(),source:n.string(),sourceMentionGuid:n.string().nullable().optional(),sourceUserId:n.number().nullable().optional(),createdAt:n.string()}).passthrough(),Ft=n.object({guid:n.string(),sourcePageGuid:n.string(),sourcePageName:n.string().nullable().optional(),targetPageGuid:n.string(),targetPageName:n.string().nullable().optional(),linkType:n.string(),linkTypeDisplayKo:n.string().optional(),linkTypeDisplayEn:n.string().optional(),isDirectional:n.boolean(),source:n.string().optional(),creatorUserId:n.number().nullable().optional(),createdAt:n.string().optional(),updatedAt:n.string().optional()}).passthrough(),Qe=n.object({guid:n.string(),wikiId:n.number(),canonicalName:n.string(),description:n.string().nullable().optional(),descriptionStatus:n.string().nullable().optional(),regenerationAvailable:n.boolean().nullable().optional(),pageType:n.string(),entitySubtype:n.string().nullable().optional(),extractionStatus:n.string(),mentionCountVisible:n.number(),mentions:n.array(je),aliases:n.array(St),links:n.array(Ft),lastUpdatedVisible:n.string().nullable().optional(),createdAt:n.string(),updatedAt:n.string()}).passthrough(),ze=n.object({items:n.array(je),nextCursorCreatedAt:n.string().nullable(),nextCursorId:n.number().nullable()}).passthrough(),Dt=n.object({guid:n.string(),canonicalName:n.string(),pageType:n.string(),entitySubtype:n.string().nullable().optional(),extractionStatus:n.string(),mentionCountVisible:n.number()}).passthrough(),qt=n.object({guid:n.string(),sourcePageGuid:n.string(),targetPageGuid:n.string(),linkType:n.string(),linkTypeDisplayKo:n.string().optional(),linkTypeDisplayEn:n.string().optional(),isDirectional:n.boolean()}).passthrough(),V=n.object({nodes:n.array(Dt),edges:n.array(qt)}).passthrough(),Ye=n.object({error_code:n.enum(["WIKI_PLAN_REQUIRED","WIKI_NOT_ACTIVATED"]),message:n.string(),current_plan:n.string().nullable().optional(),required_plans:n.array(n.string()).nullable().optional(),action_url:n.string().nullable().optional()}).passthrough(),Ke=n.object({id:n.string(),workspaceGuid:n.string(),title:n.string(),description:n.string(),color:n.string(),sharingType:n.string(),parentId:n.string().nullable().optional(),isTeamFolder:n.boolean(),createdAt:n.string(),updatedAt:n.string()}).passthrough(),de=n.lazy(()=>n.object({id:n.string(),title:n.string(),parentId:n.string().nullable().optional(),depth:n.number(),isAccessible:n.boolean(),children:n.array(de)})),Ze=n.object({id:n.number(),entry:n.string(),createdAt:n.string()}).passthrough();var $=class{constructor(A,t){this.hostname=A;this.token=t}hostname;token;async getJson(A,t,r){let o=this.buildUrl(A,r),i=await this.fetch(o,{method:"GET"});return this.parseJson(i,t,"GET",A)}async postJson(A,t,r){let o=this.buildUrl(A),i=await this.fetch(o,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});return this.parseJson(i,t,"POST",A)}async putJson(A,t,r){let o=this.buildUrl(A),i=await this.fetch(o,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});return this.parseJson(i,t,"PUT",A)}async deleteVoid(A){let t=this.buildUrl(A),r=await this.fetch(t,{method:"DELETE"});if(!r.ok)throw await Je(r,"DELETE",A)}buildUrl(A,t){return Gt(this.hostname,A,t)}async fetch(A,t){let r=new Headers(t.headers);r.set("Authorization",`Bearer ${this.token}`),r.set("Accept","application/json");try{return await fetch(A,{...t,headers:r})}catch(o){throw new u({code:"network_error",message:`Network error reaching ${this.hostname}: ${o.message}`,errorType:"network_error",suggestion:"Check your network or --hostname."},d.Generic)}}async parseJson(A,t,r,o){if(!A.ok)throw await Je(A,r,o);let i;try{i=await A.json()}catch(c){throw new u({code:"invalid_response",message:`Failed to parse JSON from ${r} ${o}: ${c.message}`,errorType:"internal_error"},d.Generic)}let s=t.safeParse(i);if(!s.success)throw new u({code:"schema_mismatch",message:`Response shape did not match expected schema (${r} ${o}).`,errorType:"internal_error",suggestion:s.error.issues.slice(0,3).map(c=>`${c.path.join(".")||"(root)"}: ${c.message}`).join("; ")},d.Generic);return s.data}};async function Je(e,A,t){let r=e.headers.get("x-request-id")??void 0,o=e.status===401?d.AuthRequired:d.Generic;if(e.status===402){let s=await Wt(e);if(s)return Nt(s,r)}let i=await Ht(e);return i?new u({code:`${i.error.errorType}`,message:i.error.message,errorType:i.error.errorType,httpStatus:e.status,...r!==void 0&&{requestId:r},...e.status===401&&{suggestion:"Run `tiro auth login` to refresh."}},o):new u({code:"http_error",message:`${A} ${t} failed: HTTP ${e.status} ${e.statusText}`,errorType:Mt(e.status),httpStatus:e.status,...r!==void 0&&{requestId:r}},o)}function Mt(e){return e===400?"bad_request":e===401?"unauthorized":e===402?"payment_required":e===403?"forbidden":e===404?"not_found":e===409?"conflict":e===413?"payload_too_large":e===422?"unprocessable_entity":e===429?"too_many_requests":"internal_error"}async function Ht(e){try{let A=await e.clone().json(),t=We.safeParse(A);return t.success?t.data:null}catch{return null}}async function Wt(e){try{let A=await e.clone().json(),t=Ye.safeParse(A);return t.success?t.data:null}catch{return null}}function Nt(e,A){let t=e.action_url??null;return new u({code:e.error_code,message:e.message,errorType:"payment_required",httpStatus:402,...t?{suggestion:`Upgrade: ${t}`}:{},...A!==void 0&&{requestId:A},details:{...e}},d.Generic)}function Gt(e,A,t){if(!A.startsWith("/")||A.startsWith("//")||A.startsWith("/\\"))throw new u({code:"internal_error",message:`API path must start with a single "/": got ${JSON.stringify(A)}`,errorType:"internal_error"},d.Generic);let r=new URL(`${e}${A}`);if(t)for(let[o,i]of Object.entries(t))i!=null&&i!==""&&r.searchParams.set(o,String(i));return r.toString()}function k(e={}){if(e.tokenOverride)return new $(P(e.hostnameOverride),e.tokenOverride);let A=K();if(!A)throw G();let t=P(e.hostnameOverride??A.hostname);return new $(t,A.accessToken)}var $e=new WeakMap;async function E(e){let A=$e.get(e);if(A)return A;let t=e.getJson("/v1/external/workspaces/me",Ne).then(r=>r.guid);return $e.set(e,t),t}async function _e(e){return(await e.getJson("/v1/external/workspaces",Ge)).workspaces}async function ue(e){return e.getJson("/v1/external/auth/me",Ve)}async function U(e,A){return A||(await ue(e)).workspaceGuid}var _="Credential is not bound to a workspace; listing across all your workspaces. Pass --workspace <guid> (see `tiro wiki workspaces`) or use a workspace-scoped API key to target one.";async function M(e,A){let t=await U(e,A);if(!t)throw new u({code:"workspace_required",message:"This command targets a single workspace, but your credential is not bound to one.",errorType:"bad_request",suggestion:"Pass --workspace <guid> (run `tiro wiki workspaces` for guids) or use a workspace-scoped API key."},d.Usage);return t}function eA(e){e.command("status").description("Show current authenticated account and scopes").action(async(A,t)=>{let r=t.optsWithGlobals(),o=K();if(!o)throw G();let i=Z(o.accessToken),s=typeof i?.sub=="string"?i.sub:null,c=typeof i?.exp=="number"?i.exp*1e3:null,l=typeof i?.scope=="string"?i.scope:null,p=c??o.expiresAt??null,m=p!==null&&Date.now()>=p,w=null,y=!1;if(!m)try{let x=k({tokenOverride:o.accessToken,hostnameOverride:r.hostname??o.hostname??void 0});w=(await ue(x)).workspaceGuid,y=!0}catch{}let h={signedIn:!0,source:o.source,hostname:o.hostname??null,userId:o.userId??s,scope:l,...y&&{workspace:w},expiresAt:p?new Date(p).toISOString():null,expired:m,tokenPrefix:`${o.accessToken.slice(0,4)}...***`};if(r.json||!process.stdout.isTTY)C({ok:!0,data:h},r);else{let x=m?a("!","yellow",r):a("\u2713","green",r),N=m?"Token expired":"Signed in";if(process.stdout.write(`${x} ${N}
|
|
103
|
+
`),process.stdout.write(` source: ${h.source}
|
|
104
|
+
`),h.hostname&&process.stdout.write(` hostname: ${h.hostname}
|
|
105
|
+
`),h.userId&&process.stdout.write(` user: ${h.userId}
|
|
106
|
+
`),h.scope&&process.stdout.write(` scope: ${h.scope}
|
|
107
|
+
`),y?process.stdout.write(` workspace: ${w??a("(unbound \u2014 across all workspaces)","gray",r)}
|
|
108
|
+
`):m||process.stdout.write(` workspace: ${a("(unable to verify)","yellow",r)}
|
|
109
|
+
`),h.expiresAt){let oe=m?a(" (expired)","red",r):"";process.stdout.write(` expires at: ${h.expiresAt}${oe}
|
|
110
|
+
`)}process.stdout.write(` token: ${h.tokenPrefix}
|
|
111
|
+
`),m&&process.stdout.write(`
|
|
112
|
+
${a("\u2192","gray",r)} Run \`tiro auth login\` to refresh.
|
|
113
|
+
`)}})}import"commander";function AA(e){e.command("logout").description("Sign out and clear the stored token").action(async(A,t)=>{let r=t.optsWithGlobals(),o=Xe();J(),r.json?C({ok:!0,data:{signedOut:!0,hadToken:o}},r):r.quiet||(o?process.stderr.write(`${a("\u2713","green",r)} Signed out
|
|
114
|
+
`):process.stderr.write(`${a("\u2022","gray",r)} No token was stored
|
|
115
|
+
`))})}function tA(e){let A=e.command("auth").description("Manage authentication");He(A),eA(A),AA(A)}import"commander";import"commander";var Vt=/^(\d+)([smhdw])$/i,Ut={s:1e3,m:6e4,h:36e5,d:864e5,w:6048e5};function I(e){let A=e.trim(),t=A.match(Vt);if(t){let o=parseInt(t[1]??"",10),i=(t[2]??"").toLowerCase(),s=Ut[i];if(!s||!Number.isFinite(o))throw rA(e);return new Date(Date.now()-o*s).toISOString()}let r=new Date(A);if(Number.isNaN(r.getTime()))throw rA(e);return r.toISOString()}function rA(e){return new u({code:"invalid_date",message:`Invalid date: "${e}". Use ISO-8601 (e.g. 2026-04-01T10:00:00Z) or relative (e.g. 7d, 24h, 30m).`,errorType:"bad_request"},d.Usage)}var jt="Untitled",Qt=new Set(["onboarding"]);function ee(e){return!(e.title===jt||e.sourceType!==null&&e.sourceType!==void 0&&Qt.has(e.sourceType))}var zt=R(B),oA=100,nA=200,Yt=`
|
|
1354
116
|
Examples:
|
|
1355
117
|
tiro notes list --since 7d
|
|
1356
118
|
tiro notes list --keyword "OKR" --since 30d --json
|
|
@@ -1366,101 +128,12 @@ Keyword matching:
|
|
|
1366
128
|
Note: placeholder notes (title='Untitled' or sourceType='onboarding') are
|
|
1367
129
|
filtered out by default. A page of N may return fewer than N visible notes \u2014
|
|
1368
130
|
keep paginating to fetch more, or pass --include-untitled to surface them.
|
|
1369
|
-
`;
|
|
1370
|
-
function
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
"--limit <n>",
|
|
1376
|
-
`Max results per page (default ${DEFAULT_PAGE_SIZE}, max ${MAX_PAGE_SIZE})`
|
|
1377
|
-
).option("--cursor <token>", "Continue a previous page").option(
|
|
1378
|
-
"--include-untitled",
|
|
1379
|
-
"Include placeholder notes (title='Untitled' or sourceType='onboarding'). Default: hidden",
|
|
1380
|
-
false
|
|
1381
|
-
).addHelpText("after", HELP_AFTER).action(async (opts, cmd) => {
|
|
1382
|
-
const globalOpts = cmd.optsWithGlobals();
|
|
1383
|
-
const client = createApiClient({
|
|
1384
|
-
...globalOpts.hostname !== void 0 && { hostnameOverride: globalOpts.hostname }
|
|
1385
|
-
});
|
|
1386
|
-
const params = {};
|
|
1387
|
-
if (opts.keyword) params["keyword"] = opts.keyword;
|
|
1388
|
-
if (opts.folder) params["folderId"] = opts.folder;
|
|
1389
|
-
if (opts.since) params["createdAtFrom"] = parseDate(opts.since);
|
|
1390
|
-
if (opts.until) params["createdAtTo"] = parseDate(opts.until);
|
|
1391
|
-
const size = clampLimit(opts.limit);
|
|
1392
|
-
if (size !== void 0) params["size"] = size;
|
|
1393
|
-
if (opts.cursor) params["cursor"] = opts.cursor;
|
|
1394
|
-
const res = await client.getJson("/v1/external/notes", ListResponseSchema, params);
|
|
1395
|
-
const visible = opts.includeUntitled === true ? res.content : res.content.filter(isVisibleNote);
|
|
1396
|
-
const mode = resolveOutputMode(globalOpts);
|
|
1397
|
-
if (mode === "json") {
|
|
1398
|
-
for (const note of visible) printNdjson(note);
|
|
1399
|
-
if (res.nextCursor) printNdjson({ _cursor: res.nextCursor });
|
|
1400
|
-
} else {
|
|
1401
|
-
printPretty(visible, res.nextCursor, globalOpts);
|
|
1402
|
-
}
|
|
1403
|
-
});
|
|
1404
|
-
}
|
|
1405
|
-
function clampLimit(raw) {
|
|
1406
|
-
if (!raw) return void 0;
|
|
1407
|
-
const n = parseInt(raw, 10);
|
|
1408
|
-
if (!Number.isFinite(n) || n <= 0) return DEFAULT_PAGE_SIZE;
|
|
1409
|
-
return Math.min(n, MAX_PAGE_SIZE);
|
|
1410
|
-
}
|
|
1411
|
-
function printPretty(notes, nextCursor2, opts) {
|
|
1412
|
-
if (notes.length === 0) {
|
|
1413
|
-
process.stdout.write(`${color("(no notes)", "gray", opts)}
|
|
1414
|
-
`);
|
|
1415
|
-
return;
|
|
1416
|
-
}
|
|
1417
|
-
const titleWidth = computeTitleWidth();
|
|
1418
|
-
for (const n of notes) {
|
|
1419
|
-
const date = n.createdAt.slice(0, 10);
|
|
1420
|
-
const dur = formatDuration(n.recordingDurationSeconds);
|
|
1421
|
-
const title = truncate(n.title, titleWidth);
|
|
1422
|
-
process.stdout.write(
|
|
1423
|
-
`${color(date, "gray", opts)} ${color(n.guid, "dim", opts)} ${color(dur, "cyan", opts)} ${title}
|
|
1424
|
-
`
|
|
1425
|
-
);
|
|
1426
|
-
}
|
|
1427
|
-
if (nextCursor2) {
|
|
1428
|
-
process.stdout.write(
|
|
1429
|
-
`${color(`
|
|
1430
|
-
next: --cursor ${nextCursor2}`, "gray", opts)}
|
|
1431
|
-
`
|
|
1432
|
-
);
|
|
1433
|
-
}
|
|
1434
|
-
}
|
|
1435
|
-
function computeTitleWidth() {
|
|
1436
|
-
const cols = process.stdout.columns;
|
|
1437
|
-
if (!cols || cols < 60) return 40;
|
|
1438
|
-
return Math.max(20, cols - 60);
|
|
1439
|
-
}
|
|
1440
|
-
function truncate(s, max) {
|
|
1441
|
-
if (s.length <= max) return s;
|
|
1442
|
-
return s.slice(0, Math.max(0, max - 1)) + "\u2026";
|
|
1443
|
-
}
|
|
1444
|
-
function formatDuration(sec) {
|
|
1445
|
-
if (!sec || sec <= 0) return "\u2014";
|
|
1446
|
-
const m = Math.floor(sec / 60);
|
|
1447
|
-
const s = Math.floor(sec % 60);
|
|
1448
|
-
if (m > 0) return `${m}m${s.toString().padStart(2, "0")}s`;
|
|
1449
|
-
return `${s}s`;
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
// src/commands/notes/search.ts
|
|
1453
|
-
import "commander";
|
|
1454
|
-
import { z as z4 } from "zod";
|
|
1455
|
-
var SearchResponseSchema = z4.object({
|
|
1456
|
-
notes: z4.array(NoteSchema),
|
|
1457
|
-
nextCursor: z4.string().nullable(),
|
|
1458
|
-
degraded: z4.boolean().optional(),
|
|
1459
|
-
degradedReason: z4.string().nullable().optional()
|
|
1460
|
-
}).passthrough();
|
|
1461
|
-
var DEFAULT_PAGE_SIZE2 = 100;
|
|
1462
|
-
var MAX_PAGE_SIZE2 = 1e3;
|
|
1463
|
-
var HELP_AFTER2 = `
|
|
131
|
+
`;function iA(e){e.command("list").description("List notes (lightweight metadata).").option("--keyword <text>",'Reorder by full-text relevance for this keyword (e.g. "OKR")').option("--folder <id>","Restrict to a folder and its descendants").option("--since <date>","Inclusive lower bound on createdAt (ISO-8601 or relative: 7d, 24h, 30m)").option("--until <date>","Exclusive upper bound on createdAt").option("--limit <n>",`Max results per page (default ${oA}, max ${nA})`).option("--cursor <token>","Continue a previous page").option("--workspace <guid>","List within one workspace (see `tiro wiki workspaces`). Default: the workspace your API key is bound to, else across all your workspaces").option("--include-untitled","Include placeholder notes (title='Untitled' or sourceType='onboarding'). Default: hidden",!1).addHelpText("after",Yt).action(async(A,t)=>{let r=t.optsWithGlobals(),o=k({...r.hostname!==void 0&&{hostnameOverride:r.hostname}}),i={};A.keyword&&(i.keyword=A.keyword),A.folder&&(i.folderId=A.folder),A.since&&(i.createdAtFrom=I(A.since)),A.until&&(i.createdAtTo=I(A.until));let s=Kt(A.limit);s!==void 0&&(i.size=s),A.cursor&&(i.cursor=A.cursor);let c=await U(o,A.workspace);!c&&r.quiet!==!0&&process.stderr.write(`${a("\u26A0","yellow",r)} ${_}
|
|
132
|
+
`);let l=c?`/v1/external/workspaces/${encodeURIComponent(c)}/notes`:"/v1/external/notes",p=await o.getJson(l,zt,i),m=A.includeUntitled===!0?p.content:p.content.filter(ee);if(b(r)==="json"){for(let y of m)v(y);p.nextCursor&&v({_cursor:p.nextCursor})}else Zt(m,p.nextCursor,r)})}function Kt(e){if(!e)return;let A=parseInt(e,10);return!Number.isFinite(A)||A<=0?oA:Math.min(A,nA)}function Zt(e,A,t){if(e.length===0){process.stdout.write(`${a("(no notes)","gray",t)}
|
|
133
|
+
`);return}let r=Jt();for(let o of e){let i=o.createdAt.slice(0,10),s=_t(o.recordingDurationSeconds),c=$t(o.title,r);process.stdout.write(`${a(i,"gray",t)} ${a(o.guid,"dim",t)} ${a(s,"cyan",t)} ${c}
|
|
134
|
+
`)}A&&process.stdout.write(`${a(`
|
|
135
|
+
next: --cursor ${A}`,"gray",t)}
|
|
136
|
+
`)}function Jt(){let e=process.stdout.columns;return!e||e<60?40:Math.max(20,e-60)}function $t(e,A){return e.length<=A?e:e.slice(0,Math.max(0,A-1))+"\u2026"}function _t(e){if(!e||e<=0)return"\u2014";let A=Math.floor(e/60),t=Math.floor(e%60);return A>0?`${A}m${t.toString().padStart(2,"0")}s`:`${t}s`}import"commander";import{z as j}from"zod";var er=j.object({notes:j.array(B),nextCursor:j.string().nullable(),degraded:j.boolean().optional(),degradedReason:j.string().nullable().optional()}).passthrough(),sA=100,aA=200,Ar=`
|
|
1464
137
|
Examples:
|
|
1465
138
|
tiro notes search "Q3 Planning"
|
|
1466
139
|
tiro notes search "Acme Corp" --since 7d --json
|
|
@@ -1480,344 +153,25 @@ Ordering:
|
|
|
1480
153
|
|
|
1481
154
|
Note: placeholder notes (title='Untitled' or sourceType='onboarding') are
|
|
1482
155
|
filtered out by default. Pass --include-untitled to surface them.
|
|
1483
|
-
`;
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
const globalOpts = cmd.optsWithGlobals();
|
|
1503
|
-
const keyword = (positional ?? opts.keyword ?? "").trim();
|
|
1504
|
-
if (!keyword) {
|
|
1505
|
-
throw new TiroError(
|
|
1506
|
-
{
|
|
1507
|
-
code: "missing_keyword",
|
|
1508
|
-
message: "search requires a keyword (positional or --keyword).",
|
|
1509
|
-
errorType: "bad_request",
|
|
1510
|
-
suggestion: 'tiro notes search "OKR"'
|
|
1511
|
-
},
|
|
1512
|
-
ExitCode.Usage
|
|
1513
|
-
);
|
|
1514
|
-
}
|
|
1515
|
-
const filter = {};
|
|
1516
|
-
if (opts.folder) filter["folderId"] = opts.folder;
|
|
1517
|
-
if (opts.since) filter["createdAtFrom"] = parseDate(opts.since);
|
|
1518
|
-
if (opts.until) filter["createdAtTo"] = parseDate(opts.until);
|
|
1519
|
-
const pagination = {};
|
|
1520
|
-
const size = clampLimit2(opts.limit);
|
|
1521
|
-
if (size !== void 0) pagination["size"] = size;
|
|
1522
|
-
if (opts.cursor) pagination["cursor"] = opts.cursor;
|
|
1523
|
-
const body = { keyword };
|
|
1524
|
-
if (Object.keys(filter).length > 0) body["filter"] = filter;
|
|
1525
|
-
if (Object.keys(pagination).length > 0) body["pagination"] = pagination;
|
|
1526
|
-
const client = createApiClient({
|
|
1527
|
-
...globalOpts.hostname !== void 0 && { hostnameOverride: globalOpts.hostname }
|
|
1528
|
-
});
|
|
1529
|
-
const res = await client.postJson(
|
|
1530
|
-
"/v1/external/notes/search",
|
|
1531
|
-
SearchResponseSchema,
|
|
1532
|
-
body
|
|
1533
|
-
);
|
|
1534
|
-
const visible = opts.includeUntitled === true ? res.notes : res.notes.filter(isVisibleNote);
|
|
1535
|
-
const mode = resolveOutputMode(globalOpts);
|
|
1536
|
-
if (mode === "json") {
|
|
1537
|
-
for (const note of visible) printNdjson(note);
|
|
1538
|
-
if (res.nextCursor) printNdjson({ _cursor: res.nextCursor });
|
|
1539
|
-
if (res.degraded === true) {
|
|
1540
|
-
printNdjson({ _degraded: true, _degradedReason: res.degradedReason ?? null });
|
|
1541
|
-
}
|
|
1542
|
-
} else {
|
|
1543
|
-
printPretty2(visible, res.nextCursor, globalOpts);
|
|
1544
|
-
if (res.degraded === true && globalOpts.quiet !== true) {
|
|
1545
|
-
process.stderr.write(
|
|
1546
|
-
`${color("\u26A0", "yellow", globalOpts)} search results are partial${res.degradedReason ? ` (${res.degradedReason})` : ""}
|
|
1547
|
-
`
|
|
1548
|
-
);
|
|
1549
|
-
}
|
|
1550
|
-
}
|
|
1551
|
-
});
|
|
1552
|
-
}
|
|
1553
|
-
function clampLimit2(raw) {
|
|
1554
|
-
if (!raw) return void 0;
|
|
1555
|
-
const n = parseInt(raw, 10);
|
|
1556
|
-
if (!Number.isFinite(n) || n <= 0) return DEFAULT_PAGE_SIZE2;
|
|
1557
|
-
return Math.min(n, MAX_PAGE_SIZE2);
|
|
1558
|
-
}
|
|
1559
|
-
function printPretty2(notes, nextCursor2, opts) {
|
|
1560
|
-
if (notes.length === 0) {
|
|
1561
|
-
process.stdout.write(`${color("(no matches)", "gray", opts)}
|
|
1562
|
-
`);
|
|
1563
|
-
return;
|
|
1564
|
-
}
|
|
1565
|
-
for (const n of notes) {
|
|
1566
|
-
const date = n.createdAt.slice(0, 10);
|
|
1567
|
-
process.stdout.write(
|
|
1568
|
-
`${color(date, "gray", opts)} ${color(n.guid, "dim", opts)} ${n.title}
|
|
1569
|
-
`
|
|
1570
|
-
);
|
|
1571
|
-
}
|
|
1572
|
-
if (nextCursor2) {
|
|
1573
|
-
process.stdout.write(
|
|
1574
|
-
`${color(`
|
|
1575
|
-
next: --cursor ${nextCursor2}`, "gray", opts)}
|
|
1576
|
-
`
|
|
1577
|
-
);
|
|
1578
|
-
}
|
|
1579
|
-
}
|
|
1580
|
-
|
|
1581
|
-
// src/commands/notes/get.ts
|
|
1582
|
-
import "commander";
|
|
1583
|
-
|
|
1584
|
-
// src/lib/output/file.ts
|
|
1585
|
-
import { mkdir, rename, stat, writeFile, access } from "fs/promises";
|
|
1586
|
-
import { dirname as dirname2, resolve as resolve2, sep } from "path";
|
|
1587
|
-
function assertSafeOutputPath(input) {
|
|
1588
|
-
if (input.trim() === "") {
|
|
1589
|
-
throw outputError("output path is empty", input);
|
|
1590
|
-
}
|
|
1591
|
-
const segments = input.split(/[\\/]/);
|
|
1592
|
-
for (const seg of segments) {
|
|
1593
|
-
if (seg === "..") {
|
|
1594
|
-
throw outputError("path traversal segment '..' is not allowed", input);
|
|
1595
|
-
}
|
|
1596
|
-
}
|
|
1597
|
-
}
|
|
1598
|
-
function outputError(reason, input) {
|
|
1599
|
-
return new TiroError(
|
|
1600
|
-
{
|
|
1601
|
-
code: "unsafe_output_path",
|
|
1602
|
-
message: `Refusing to write to ${JSON.stringify(input)}: ${reason}.`,
|
|
1603
|
-
errorType: "bad_request",
|
|
1604
|
-
suggestion: "Use a path without '..' segments, or pass --force if you really mean it."
|
|
1605
|
-
},
|
|
1606
|
-
ExitCode.Usage
|
|
1607
|
-
);
|
|
1608
|
-
}
|
|
1609
|
-
async function writeFileAtomic(filepath, content, opts = {}) {
|
|
1610
|
-
if (!opts.force) {
|
|
1611
|
-
assertSafeOutputPath(filepath);
|
|
1612
|
-
}
|
|
1613
|
-
const absPath = resolve2(filepath);
|
|
1614
|
-
await mkdir(dirname2(absPath), { recursive: true });
|
|
1615
|
-
if (!opts.force) {
|
|
1616
|
-
const exists = await fileExists(absPath);
|
|
1617
|
-
if (exists) {
|
|
1618
|
-
throw new TiroError(
|
|
1619
|
-
{
|
|
1620
|
-
code: "file_exists",
|
|
1621
|
-
message: `File already exists: ${absPath}`,
|
|
1622
|
-
errorType: "conflict",
|
|
1623
|
-
suggestion: "Use --force to overwrite, or pick a different --output."
|
|
1624
|
-
},
|
|
1625
|
-
ExitCode.Generic
|
|
1626
|
-
);
|
|
1627
|
-
}
|
|
1628
|
-
}
|
|
1629
|
-
const tmp = `${absPath}.tmp.${process.pid}.${Date.now()}`;
|
|
1630
|
-
await writeFile(tmp, content, "utf8");
|
|
1631
|
-
await rename(tmp, absPath);
|
|
1632
|
-
const s = await stat(absPath);
|
|
1633
|
-
return { path: absPath, size: s.size };
|
|
1634
|
-
}
|
|
1635
|
-
async function fileExists(p) {
|
|
1636
|
-
try {
|
|
1637
|
-
await access(p);
|
|
1638
|
-
return true;
|
|
1639
|
-
} catch {
|
|
1640
|
-
return false;
|
|
1641
|
-
}
|
|
1642
|
-
}
|
|
1643
|
-
|
|
1644
|
-
// src/lib/output/transcript.ts
|
|
1645
|
-
function buildMcpTranscript(note, paragraphs) {
|
|
1646
|
-
return {
|
|
1647
|
-
noteGuid: note.guid,
|
|
1648
|
-
title: note.title,
|
|
1649
|
-
participants: note.participants?.map((p) => p.name || p.email || "").filter((s) => typeof s === "string" && s.length > 0) ?? [],
|
|
1650
|
-
createdAt: note.createdAt,
|
|
1651
|
-
recordingDurationSeconds: note.recordingDurationSeconds,
|
|
1652
|
-
paragraphs: paragraphsToMcp(paragraphs)
|
|
1653
|
-
};
|
|
1654
|
-
}
|
|
1655
|
-
function paragraphsToMcp(paragraphs) {
|
|
1656
|
-
return paragraphs.map((p) => ({
|
|
1657
|
-
timeFrom: p.timeFrom ?? null,
|
|
1658
|
-
timeTo: p.timeTo ?? null,
|
|
1659
|
-
segments: paragraphToSegments(p)
|
|
1660
|
-
})).filter((p) => p.segments.length > 0);
|
|
1661
|
-
}
|
|
1662
|
-
function paragraphToSegments(p) {
|
|
1663
|
-
const ds = p.diarizedSegments;
|
|
1664
|
-
if (ds && ds.length > 0) {
|
|
1665
|
-
return ds.map((s) => ({
|
|
1666
|
-
content: stripHtml(s.content),
|
|
1667
|
-
speaker: {
|
|
1668
|
-
label: s.speaker.label,
|
|
1669
|
-
name: s.speaker.personName ? stripHtml(s.speaker.personName) : null
|
|
1670
|
-
}
|
|
1671
|
-
})).filter((s) => s.content.length > 0);
|
|
1672
|
-
}
|
|
1673
|
-
const plain = stripHtml(p.transcript?.content ?? "");
|
|
1674
|
-
return plain ? [{ content: plain, speaker: null }] : [];
|
|
1675
|
-
}
|
|
1676
|
-
function renderTranscriptJson(t) {
|
|
1677
|
-
return `${JSON.stringify(t, null, 2)}
|
|
1678
|
-
`;
|
|
1679
|
-
}
|
|
1680
|
-
function renderTranscriptMarkdown(t, opts = {}) {
|
|
1681
|
-
const showTs = opts.timestamps !== false;
|
|
1682
|
-
const anchor = showTs ? anchorTime(t) : null;
|
|
1683
|
-
const lines = [];
|
|
1684
|
-
lines.push(`# ${t.title}`, "");
|
|
1685
|
-
if (t.participants.length > 0) {
|
|
1686
|
-
lines.push(`**Participants**: ${t.participants.join(", ")}`, "");
|
|
1687
|
-
}
|
|
1688
|
-
lines.push("## Transcript", "");
|
|
1689
|
-
for (const p of t.paragraphs) {
|
|
1690
|
-
if (showTs) {
|
|
1691
|
-
const ts = elapsed(p.timeFrom, anchor);
|
|
1692
|
-
if (ts) lines.push(`### ${ts}`, "");
|
|
1693
|
-
}
|
|
1694
|
-
for (const s of p.segments) {
|
|
1695
|
-
const who = s.speaker?.name ?? s.speaker?.label ?? "Unknown";
|
|
1696
|
-
lines.push(`**${who}**: ${s.content}`);
|
|
1697
|
-
}
|
|
1698
|
-
lines.push("");
|
|
1699
|
-
}
|
|
1700
|
-
return `${lines.join("\n").trimEnd()}
|
|
1701
|
-
`;
|
|
1702
|
-
}
|
|
1703
|
-
function renderTranscriptText(t) {
|
|
1704
|
-
const lines = [];
|
|
1705
|
-
for (const p of t.paragraphs) {
|
|
1706
|
-
for (const s of p.segments) {
|
|
1707
|
-
const who = s.speaker?.name ?? s.speaker?.label ?? "Unknown";
|
|
1708
|
-
lines.push(`[${who}] ${s.content}`);
|
|
1709
|
-
}
|
|
1710
|
-
}
|
|
1711
|
-
return `${lines.join("\n")}
|
|
1712
|
-
`;
|
|
1713
|
-
}
|
|
1714
|
-
function anchorTime(t) {
|
|
1715
|
-
for (const p of t.paragraphs) {
|
|
1716
|
-
if (p.timeFrom) return p.timeFrom;
|
|
1717
|
-
}
|
|
1718
|
-
return null;
|
|
1719
|
-
}
|
|
1720
|
-
function elapsed(currentIso, anchorIso) {
|
|
1721
|
-
if (!currentIso || !anchorIso) return "";
|
|
1722
|
-
const cur = Date.parse(currentIso);
|
|
1723
|
-
const anc = Date.parse(anchorIso);
|
|
1724
|
-
if (!Number.isFinite(cur) || !Number.isFinite(anc)) return "";
|
|
1725
|
-
const seconds = Math.max(0, Math.floor((cur - anc) / 1e3));
|
|
1726
|
-
const h = Math.floor(seconds / 3600);
|
|
1727
|
-
const m = Math.floor(seconds % 3600 / 60);
|
|
1728
|
-
const s = seconds % 60;
|
|
1729
|
-
if (h > 0) return `${pad(h)}:${pad(m)}:${pad(s)}`;
|
|
1730
|
-
return `${pad(m)}:${pad(s)}`;
|
|
1731
|
-
}
|
|
1732
|
-
function pad(n) {
|
|
1733
|
-
return n.toString().padStart(2, "0");
|
|
1734
|
-
}
|
|
1735
|
-
function stripHtml(s) {
|
|
1736
|
-
return s.replace(/<[^>]*>/g, "").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").trim();
|
|
1737
|
-
}
|
|
1738
|
-
|
|
1739
|
-
// src/lib/output/format.ts
|
|
1740
|
-
function formatNote(note, format, opts = {}) {
|
|
1741
|
-
switch (format) {
|
|
1742
|
-
case "md":
|
|
1743
|
-
return formatMarkdown(note, opts);
|
|
1744
|
-
case "json":
|
|
1745
|
-
return formatJson(note, opts);
|
|
1746
|
-
case "txt":
|
|
1747
|
-
return formatText(note, opts);
|
|
1748
|
-
}
|
|
1749
|
-
}
|
|
1750
|
-
function formatMarkdown(note, opts) {
|
|
1751
|
-
const fm = [
|
|
1752
|
-
"---",
|
|
1753
|
-
`guid: ${escapeYaml(note.guid)}`,
|
|
1754
|
-
`title: ${escapeYaml(note.title)}`,
|
|
1755
|
-
`createdAt: ${note.createdAt}`,
|
|
1756
|
-
`updatedAt: ${note.updatedAt}`,
|
|
1757
|
-
`sourceType: ${note.sourceType}`,
|
|
1758
|
-
`recordingDurationSeconds: ${note.recordingDurationSeconds}`,
|
|
1759
|
-
`webUrl: ${note.webUrl}`
|
|
1760
|
-
];
|
|
1761
|
-
if (note.recordingStartAt) fm.push(`recordingStartAt: ${note.recordingStartAt}`);
|
|
1762
|
-
if (note.recordingEndAt) fm.push(`recordingEndAt: ${note.recordingEndAt}`);
|
|
1763
|
-
fm.push("---", "");
|
|
1764
|
-
const parts = [fm.join("\n"), `# ${note.title}`, ""];
|
|
1765
|
-
if (note.participants && note.participants.length > 0) {
|
|
1766
|
-
parts.push("## Participants", "");
|
|
1767
|
-
for (const p of note.participants) {
|
|
1768
|
-
const name = p.name ?? "(no name)";
|
|
1769
|
-
const email = p.email ? ` <${p.email}>` : "";
|
|
1770
|
-
parts.push(`- ${name}${email}`);
|
|
1771
|
-
}
|
|
1772
|
-
parts.push("");
|
|
1773
|
-
}
|
|
1774
|
-
if (opts.includeTranscript && opts.paragraphs && opts.paragraphs.length > 0) {
|
|
1775
|
-
const mcp = buildMcpTranscript(note, opts.paragraphs);
|
|
1776
|
-
const transcriptBody = renderTranscriptMarkdown(mcp);
|
|
1777
|
-
const startsWithHeader = transcriptBody.startsWith(`# ${note.title}`);
|
|
1778
|
-
const trimmed = startsWithHeader ? transcriptBody.slice(transcriptBody.indexOf("\n") + 1) : transcriptBody;
|
|
1779
|
-
parts.push(trimmed.replace(/^\s*\n+/, ""));
|
|
1780
|
-
}
|
|
1781
|
-
return `${parts.join("\n").trimEnd()}
|
|
1782
|
-
`;
|
|
1783
|
-
}
|
|
1784
|
-
function formatJson(note, opts) {
|
|
1785
|
-
const out = { ...note };
|
|
1786
|
-
if (opts.includeTranscript && opts.paragraphs) {
|
|
1787
|
-
out["transcript"] = { paragraphs: paragraphsToMcp(opts.paragraphs) };
|
|
1788
|
-
}
|
|
1789
|
-
return `${JSON.stringify(out, null, 2)}
|
|
1790
|
-
`;
|
|
1791
|
-
}
|
|
1792
|
-
function formatText(note, opts) {
|
|
1793
|
-
if (!opts.paragraphs || opts.paragraphs.length === 0) {
|
|
1794
|
-
return `${note.title}
|
|
1795
|
-
${note.webUrl}
|
|
1796
|
-
`;
|
|
1797
|
-
}
|
|
1798
|
-
const mcp = buildMcpTranscript(note, opts.paragraphs);
|
|
1799
|
-
return renderTranscriptText(mcp);
|
|
1800
|
-
}
|
|
1801
|
-
function escapeYaml(s) {
|
|
1802
|
-
if (/[:#\n"']/.test(s)) {
|
|
1803
|
-
return JSON.stringify(s);
|
|
1804
|
-
}
|
|
1805
|
-
return s;
|
|
1806
|
-
}
|
|
1807
|
-
|
|
1808
|
-
// src/commands/notes/get.ts
|
|
1809
|
-
var ALLOWED_INCLUDES = /* @__PURE__ */ new Set(["transcript"]);
|
|
1810
|
-
var ParagraphsListSchema = SimpleListResponseSchema(ParagraphSchema);
|
|
1811
|
-
var ParagraphsCursorSchema = PageCursorResponseSchema(ParagraphSchema);
|
|
1812
|
-
function registerNotesGet(parent) {
|
|
1813
|
-
parent.command("get <guid>").description("Get a single note. Outputs to stdout, or saves to a file with --output.").option("--output <path>", "Write to file (stdout becomes a single metadata line)").option(
|
|
1814
|
-
"--format <md|json|txt>",
|
|
1815
|
-
"Output format (default: md for TTY, json when piped)"
|
|
1816
|
-
).option(
|
|
1817
|
-
"--include <items>",
|
|
1818
|
-
"Comma-separated extras (v0.2 supports: transcript)",
|
|
1819
|
-
""
|
|
1820
|
-
).option("--force", "Overwrite existing file at --output path").addHelpText("after", `
|
|
156
|
+
`;function cA(e){e.command("search [keyword]").description("Deep keyword search \u2014 returns notes hydrated with their primary documents.").option("--keyword <text>",'Alternative to positional keyword (e.g. --keyword "Q3 Planning")').option("--folder <id>","Restrict hits to a folder and its descendants").option("--since <date>","Inclusive lower bound on createdAt (ISO-8601 or relative: 7d, 24h, 30m)").option("--until <date>","Exclusive upper bound on createdAt").option("--limit <n>",`Max results per page (default ${sA}, max ${aA})`).option("--cursor <token>","Continue a previous page (reserved \u2014 backend currently always null)").option("--workspace <guid>","Search within one workspace (see `tiro wiki workspaces`). Default: the workspace your API key is bound to, else across all your workspaces").option("--include-untitled","Include placeholder notes. Default: hidden",!1).addHelpText("after",Ar).action(async(A,t,r)=>{let o=r.optsWithGlobals(),i=(A??t.keyword??"").trim();if(!i)throw new u({code:"missing_keyword",message:"search requires a keyword (positional or --keyword).",errorType:"bad_request",suggestion:'tiro notes search "OKR"'},d.Usage);let s={};t.folder&&(s.folderId=t.folder),t.since&&(s.createdAtFrom=I(t.since)),t.until&&(s.createdAtTo=I(t.until));let c={},l=tr(t.limit);l!==void 0&&(c.size=l),t.cursor&&(c.cursor=t.cursor);let p={keyword:i};Object.keys(s).length>0&&(p.filter=s),Object.keys(c).length>0&&(p.pagination=c);let m=k({...o.hostname!==void 0&&{hostnameOverride:o.hostname}}),w=await U(m,t.workspace);!w&&o.quiet!==!0&&process.stderr.write(`${a("\u26A0","yellow",o)} ${_}
|
|
157
|
+
`);let y=w?`/v1/external/workspaces/${encodeURIComponent(w)}/notes/search`:"/v1/external/notes/search",h=await m.postJson(y,er,p),x=t.includeUntitled===!0?h.notes:h.notes.filter(ee);if(b(o)==="json"){for(let oe of x)v(oe);h.nextCursor&&v({_cursor:h.nextCursor}),h.degraded===!0&&v({_degraded:!0,_degradedReason:h.degradedReason??null})}else rr(x,h.nextCursor,o),h.degraded===!0&&o.quiet!==!0&&process.stderr.write(`${a("\u26A0","yellow",o)} search results are partial${h.degradedReason?` (${h.degradedReason})`:""}
|
|
158
|
+
`)})}function tr(e){if(!e)return;let A=parseInt(e,10);return!Number.isFinite(A)||A<=0?sA:Math.min(A,aA)}function rr(e,A,t){if(e.length===0){process.stdout.write(`${a("(no matches)","gray",t)}
|
|
159
|
+
`);return}for(let r of e){let o=r.createdAt.slice(0,10);process.stdout.write(`${a(o,"gray",t)} ${a(r.guid,"dim",t)} ${r.title}
|
|
160
|
+
`)}A&&process.stdout.write(`${a(`
|
|
161
|
+
next: --cursor ${A}`,"gray",t)}
|
|
162
|
+
`)}import"commander";import{mkdir as or,rename as nr,stat as ir,writeFile as sr,access as ar}from"fs/promises";import{dirname as cr,resolve as pr,sep as Gi}from"path";function dr(e){if(e.trim()==="")throw pA("output path is empty",e);let A=e.split(/[\\/]/);for(let t of A)if(t==="..")throw pA("path traversal segment '..' is not allowed",e)}function pA(e,A){return new u({code:"unsafe_output_path",message:`Refusing to write to ${JSON.stringify(A)}: ${e}.`,errorType:"bad_request",suggestion:"Use a path without '..' segments, or pass --force if you really mean it."},d.Usage)}async function Ae(e,A,t={}){t.force||dr(e);let r=pr(e);if(await or(cr(r),{recursive:!0}),!t.force&&await ur(r))throw new u({code:"file_exists",message:`File already exists: ${r}`,errorType:"conflict",suggestion:"Use --force to overwrite, or pick a different --output."},d.Generic);let o=`${r}.tmp.${process.pid}.${Date.now()}`;await sr(o,A,"utf8"),await nr(o,r);let i=await ir(r);return{path:r,size:i.size}}async function ur(e){try{return await ar(e),!0}catch{return!1}}function z(e,A){return{noteGuid:e.guid,title:e.title,participants:e.participants?.map(t=>t.name||t.email||"").filter(t=>typeof t=="string"&&t.length>0)??[],createdAt:e.createdAt,recordingDurationSeconds:e.recordingDurationSeconds,paragraphs:Y(A)}}function Y(e){return e.map(A=>({timeFrom:A.timeFrom??null,timeTo:A.timeTo??null,segments:lr(A)})).filter(A=>A.segments.length>0)}function lr(e){let A=e.diarizedSegments;if(A&&A.length>0)return A.map(r=>({content:le(r.content),speaker:{label:r.speaker.label,name:r.speaker.personName?le(r.speaker.personName):null}})).filter(r=>r.content.length>0);let t=le(e.transcript?.content??"");return t?[{content:t,speaker:null}]:[]}function me(e){return`${JSON.stringify(e,null,2)}
|
|
163
|
+
`}function te(e,A={}){let t=A.timestamps!==!1,r=t?mr(e):null,o=[];o.push(`# ${e.title}`,""),e.participants.length>0&&o.push(`**Participants**: ${e.participants.join(", ")}`,""),o.push("## Transcript","");for(let i of e.paragraphs){if(t){let s=gr(i.timeFrom,r);s&&o.push(`### ${s}`,"")}for(let s of i.segments){let c=s.speaker?.name??s.speaker?.label??"Unknown";o.push(`**${c}**: ${s.content}`)}o.push("")}return`${o.join(`
|
|
164
|
+
`).trimEnd()}
|
|
165
|
+
`}function re(e){let A=[];for(let t of e.paragraphs)for(let r of t.segments){let o=r.speaker?.name??r.speaker?.label??"Unknown";A.push(`[${o}] ${r.content}`)}return`${A.join(`
|
|
166
|
+
`)}
|
|
167
|
+
`}function mr(e){for(let A of e.paragraphs)if(A.timeFrom)return A.timeFrom;return null}function gr(e,A){if(!e||!A)return"";let t=Date.parse(e),r=Date.parse(A);if(!Number.isFinite(t)||!Number.isFinite(r))return"";let o=Math.max(0,Math.floor((t-r)/1e3)),i=Math.floor(o/3600),s=Math.floor(o%3600/60),c=o%60;return i>0?`${Q(i)}:${Q(s)}:${Q(c)}`:`${Q(s)}:${Q(c)}`}function Q(e){return e.toString().padStart(2,"0")}function le(e){return e.replace(/<[^>]*>/g,"").replace(/ /g," ").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").trim()}function uA(e,A,t={}){switch(A){case"md":return fr(e,t);case"json":return hr(e,t);case"txt":return wr(e,t)}}function fr(e,A){let t=["---",`guid: ${dA(e.guid)}`,`title: ${dA(e.title)}`,`createdAt: ${e.createdAt}`,`updatedAt: ${e.updatedAt}`,`sourceType: ${e.sourceType}`,`recordingDurationSeconds: ${e.recordingDurationSeconds}`,`webUrl: ${e.webUrl}`];e.recordingStartAt&&t.push(`recordingStartAt: ${e.recordingStartAt}`),e.recordingEndAt&&t.push(`recordingEndAt: ${e.recordingEndAt}`),t.push("---","");let r=[t.join(`
|
|
168
|
+
`),`# ${e.title}`,""];if(e.participants&&e.participants.length>0){r.push("## Participants","");for(let o of e.participants){let i=o.name??"(no name)",s=o.email?` <${o.email}>`:"";r.push(`- ${i}${s}`)}r.push("")}if(A.includeTranscript&&A.paragraphs&&A.paragraphs.length>0){let o=z(e,A.paragraphs),i=te(o),c=i.startsWith(`# ${e.title}`)?i.slice(i.indexOf(`
|
|
169
|
+
`)+1):i;r.push(c.replace(/^\s*\n+/,""))}return`${r.join(`
|
|
170
|
+
`).trimEnd()}
|
|
171
|
+
`}function hr(e,A){let t={...e};return A.includeTranscript&&A.paragraphs&&(t.transcript={paragraphs:Y(A.paragraphs)}),`${JSON.stringify(t,null,2)}
|
|
172
|
+
`}function wr(e,A){if(!A.paragraphs||A.paragraphs.length===0)return`${e.title}
|
|
173
|
+
${e.webUrl}
|
|
174
|
+
`;let t=z(e,A.paragraphs);return re(t)}function dA(e){return/[:#\n"']/.test(e)?JSON.stringify(e):e}var yr=new Set(["transcript"]),br=q(D),lA=R(D);function mA(e){e.command("get <guid>").description("Get a single note. Outputs to stdout, or saves to a file with --output.").option("--output <path>","Write to file (stdout becomes a single metadata line)").option("--format <md|json|txt>","Output format (default: md for TTY, json when piped)").option("--include <items>","Comma-separated extras (v0.2 supports: transcript)","").option("--force","Overwrite existing file at --output path").addHelpText("after",`
|
|
1821
175
|
Examples:
|
|
1822
176
|
tiro notes get <guid> # markdown to stdout
|
|
1823
177
|
tiro notes get <guid> --include transcript # add speaker-attributed paragraphs
|
|
@@ -1827,142 +181,10 @@ Examples:
|
|
|
1827
181
|
Tip for agents: prefer --output <path>. The actual content goes to disk
|
|
1828
182
|
and stdout collapses to a single metadata line, keeping your context
|
|
1829
183
|
window light.
|
|
1830
|
-
`).action(async (
|
|
1831
|
-
const globalOpts = cmd.optsWithGlobals();
|
|
1832
|
-
const includes = parseIncludes(opts.include);
|
|
1833
|
-
validateIncludes(includes);
|
|
1834
|
-
const format = pickFormat(opts.format, opts.output);
|
|
1835
|
-
const client = createApiClient({
|
|
1836
|
-
...globalOpts.hostname !== void 0 && { hostnameOverride: globalOpts.hostname }
|
|
1837
|
-
});
|
|
1838
|
-
const note = await client.getJson(`/v1/external/notes/${guid}`, NoteSchema);
|
|
1839
|
-
let paragraphs;
|
|
1840
|
-
if (includes.has("transcript") || format === "txt") {
|
|
1841
|
-
paragraphs = await fetchAllParagraphs(client, guid);
|
|
1842
|
-
}
|
|
1843
|
-
const content = formatNote(note, format, {
|
|
1844
|
-
includeTranscript: includes.has("transcript"),
|
|
1845
|
-
...paragraphs !== void 0 && { paragraphs }
|
|
1846
|
-
});
|
|
1847
|
-
if (opts.output) {
|
|
1848
|
-
const result = await writeFileAtomic(opts.output, content, {
|
|
1849
|
-
...opts.force === true && { force: true }
|
|
1850
|
-
});
|
|
1851
|
-
printOutput(
|
|
1852
|
-
{
|
|
1853
|
-
ok: true,
|
|
1854
|
-
data: {
|
|
1855
|
-
saved: result.path,
|
|
1856
|
-
size: result.size,
|
|
1857
|
-
format,
|
|
1858
|
-
guid: note.guid,
|
|
1859
|
-
title: note.title
|
|
1860
|
-
}
|
|
1861
|
-
},
|
|
1862
|
-
globalOpts
|
|
1863
|
-
);
|
|
1864
|
-
return;
|
|
1865
|
-
}
|
|
1866
|
-
const mode = resolveOutputMode(globalOpts);
|
|
1867
|
-
if (mode === "json" && format !== "json") {
|
|
1868
|
-
printOutput(
|
|
1869
|
-
{
|
|
1870
|
-
ok: true,
|
|
1871
|
-
data: {
|
|
1872
|
-
...note,
|
|
1873
|
-
...paragraphs && { transcript: { paragraphs: paragraphsToMcp(paragraphs) } }
|
|
1874
|
-
}
|
|
1875
|
-
},
|
|
1876
|
-
globalOpts
|
|
1877
|
-
);
|
|
1878
|
-
} else if (format === "json") {
|
|
1879
|
-
process.stdout.write(content);
|
|
1880
|
-
} else {
|
|
1881
|
-
if (process.stdout.isTTY && format === "txt") {
|
|
1882
|
-
process.stdout.write(`${color(`# ${note.title}`, "bold", globalOpts)}
|
|
184
|
+
`).action(async(A,t,r)=>{let o=r.optsWithGlobals(),i=Cr(t.include);vr(i);let s=xr(t.format,t.output),c=k({...o.hostname!==void 0&&{hostnameOverride:o.hostname}}),l=await c.getJson(`/v1/external/notes/${A}`,B),p;(i.has("transcript")||s==="txt")&&(p=await kr(c,A));let m=uA(l,s,{includeTranscript:i.has("transcript"),...p!==void 0&&{paragraphs:p}});if(t.output){let y=await Ae(t.output,m,{...t.force===!0&&{force:!0}});C({ok:!0,data:{saved:y.path,size:y.size,format:s,guid:l.guid,title:l.title}},o);return}b(o)==="json"&&s!=="json"?C({ok:!0,data:{...l,...p&&{transcript:{paragraphs:Y(p)}}}},o):(s==="json"||process.stdout.isTTY&&s==="txt"&&process.stdout.write(`${a(`# ${l.title}`,"bold",o)}
|
|
1883
185
|
|
|
1884
|
-
`);
|
|
1885
|
-
|
|
1886
|
-
process.stdout.write(content);
|
|
1887
|
-
}
|
|
1888
|
-
});
|
|
1889
|
-
}
|
|
1890
|
-
async function fetchAllParagraphs(client, guid) {
|
|
1891
|
-
const first = await client.getJson(
|
|
1892
|
-
`/v1/external/notes/${guid}/paragraphs`,
|
|
1893
|
-
ParagraphsCursorSchema.or(ParagraphsListSchema)
|
|
1894
|
-
);
|
|
1895
|
-
const all = [...first.content];
|
|
1896
|
-
if ("nextCursor" in first) {
|
|
1897
|
-
let cursor = first.nextCursor;
|
|
1898
|
-
while (cursor) {
|
|
1899
|
-
const next = await client.getJson(
|
|
1900
|
-
`/v1/external/notes/${guid}/paragraphs`,
|
|
1901
|
-
ParagraphsCursorSchema,
|
|
1902
|
-
{ cursor }
|
|
1903
|
-
);
|
|
1904
|
-
all.push(...next.content);
|
|
1905
|
-
cursor = next.nextCursor;
|
|
1906
|
-
}
|
|
1907
|
-
}
|
|
1908
|
-
return all;
|
|
1909
|
-
}
|
|
1910
|
-
function parseIncludes(raw) {
|
|
1911
|
-
if (!raw) return /* @__PURE__ */ new Set();
|
|
1912
|
-
return new Set(
|
|
1913
|
-
raw.split(",").map((s) => s.trim().toLowerCase()).filter((s) => s.length > 0)
|
|
1914
|
-
);
|
|
1915
|
-
}
|
|
1916
|
-
function validateIncludes(includes) {
|
|
1917
|
-
for (const inc of includes) {
|
|
1918
|
-
if (!ALLOWED_INCLUDES.has(inc)) {
|
|
1919
|
-
throw new TiroError(
|
|
1920
|
-
{
|
|
1921
|
-
code: "invalid_include",
|
|
1922
|
-
message: `Invalid --include "${inc}". v0.2.0 supports: transcript.`,
|
|
1923
|
-
errorType: "bad_request",
|
|
1924
|
-
suggestion: "Use --include transcript"
|
|
1925
|
-
},
|
|
1926
|
-
ExitCode.Usage
|
|
1927
|
-
);
|
|
1928
|
-
}
|
|
1929
|
-
}
|
|
1930
|
-
}
|
|
1931
|
-
function pickFormat(format, output) {
|
|
1932
|
-
const allowed = ["md", "json", "txt"];
|
|
1933
|
-
if (format) {
|
|
1934
|
-
const f = format.toLowerCase();
|
|
1935
|
-
if (!allowed.includes(f)) {
|
|
1936
|
-
throw new TiroError(
|
|
1937
|
-
{
|
|
1938
|
-
code: "invalid_format",
|
|
1939
|
-
message: `Invalid --format "${format}". Allowed: md, json, txt.`,
|
|
1940
|
-
errorType: "bad_request"
|
|
1941
|
-
},
|
|
1942
|
-
ExitCode.Usage
|
|
1943
|
-
);
|
|
1944
|
-
}
|
|
1945
|
-
return f;
|
|
1946
|
-
}
|
|
1947
|
-
if (output) {
|
|
1948
|
-
if (output.endsWith(".json")) return "json";
|
|
1949
|
-
if (output.endsWith(".txt")) return "txt";
|
|
1950
|
-
return "md";
|
|
1951
|
-
}
|
|
1952
|
-
return process.stdout.isTTY ? "md" : "json";
|
|
1953
|
-
}
|
|
1954
|
-
|
|
1955
|
-
// src/commands/notes/transcript.ts
|
|
1956
|
-
import "commander";
|
|
1957
|
-
var ParagraphsListSchema2 = SimpleListResponseSchema(ParagraphSchema);
|
|
1958
|
-
var ParagraphsCursorSchema2 = PageCursorResponseSchema(ParagraphSchema);
|
|
1959
|
-
function registerNotesTranscript(parent) {
|
|
1960
|
-
parent.command("transcript <guid>").description(
|
|
1961
|
-
"Get the full transcript of a note as speaker-attributed paragraphs.\nJSON output matches MCP get_note_transcript shape exactly."
|
|
1962
|
-
).option("--output <path>", "Write to file (stdout = single metadata line)").option(
|
|
1963
|
-
"--format <md|json|txt>",
|
|
1964
|
-
"Output format (default: md if TTY, txt when piped; json mirrors MCP)"
|
|
1965
|
-
).option("--force", "Overwrite existing file at --output path").option("--no-timestamps", "Omit paragraph timestamp headers (md format only)").addHelpText("after", `
|
|
186
|
+
`),process.stdout.write(m))})}async function kr(e,A){let t=await e.getJson(`/v1/external/notes/${A}/paragraphs`,lA.or(br)),r=[...t.content];if("nextCursor"in t){let o=t.nextCursor;for(;o;){let i=await e.getJson(`/v1/external/notes/${A}/paragraphs`,lA,{cursor:o});r.push(...i.content),o=i.nextCursor}}return r}function Cr(e){return e?new Set(e.split(",").map(A=>A.trim().toLowerCase()).filter(A=>A.length>0)):new Set}function vr(e){for(let A of e)if(!yr.has(A))throw new u({code:"invalid_include",message:`Invalid --include "${A}". v0.2.0 supports: transcript.`,errorType:"bad_request",suggestion:"Use --include transcript"},d.Usage)}function xr(e,A){let t=["md","json","txt"];if(e){let r=e.toLowerCase();if(!t.includes(r))throw new u({code:"invalid_format",message:`Invalid --format "${e}". Allowed: md, json, txt.`,errorType:"bad_request"},d.Usage);return r}return A?A.endsWith(".json")?"json":A.endsWith(".txt")?"txt":"md":process.stdout.isTTY?"md":"json"}import"commander";var Tr=q(D),gA=R(D);function fA(e){e.command("transcript <guid>").description(`Get the full transcript of a note as speaker-attributed paragraphs.
|
|
187
|
+
JSON output matches MCP get_note_transcript shape exactly.`).option("--output <path>","Write to file (stdout = single metadata line)").option("--format <md|json|txt>","Output format (default: md if TTY, txt when piped; json mirrors MCP)").option("--force","Overwrite existing file at --output path").option("--no-timestamps","Omit paragraph timestamp headers (md format only)").addHelpText("after",`
|
|
1966
188
|
Examples:
|
|
1967
189
|
tiro notes transcript <guid> # md in TTY, txt in pipe
|
|
1968
190
|
tiro notes transcript <guid> --format md --output ./t.md
|
|
@@ -1972,124 +194,39 @@ Examples:
|
|
|
1972
194
|
|
|
1973
195
|
The --format json output is byte-for-byte identical to MCP's
|
|
1974
196
|
get_note_transcript so agents can swap surfaces without changing parsers.
|
|
1975
|
-
`).action(async (
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
} else {
|
|
2009
|
-
process.stdout.write(content);
|
|
2010
|
-
}
|
|
2011
|
-
});
|
|
2012
|
-
}
|
|
2013
|
-
async function fetchAllParagraphs2(client, guid) {
|
|
2014
|
-
const first = await client.getJson(
|
|
2015
|
-
`/v1/external/notes/${guid}/paragraphs`,
|
|
2016
|
-
ParagraphsCursorSchema2.or(ParagraphsListSchema2)
|
|
2017
|
-
);
|
|
2018
|
-
const all = [...first.content];
|
|
2019
|
-
if ("nextCursor" in first) {
|
|
2020
|
-
let cursor = first.nextCursor;
|
|
2021
|
-
while (cursor) {
|
|
2022
|
-
const next = await client.getJson(
|
|
2023
|
-
`/v1/external/notes/${guid}/paragraphs`,
|
|
2024
|
-
ParagraphsCursorSchema2,
|
|
2025
|
-
{ cursor }
|
|
2026
|
-
);
|
|
2027
|
-
all.push(...next.content);
|
|
2028
|
-
cursor = next.nextCursor;
|
|
2029
|
-
}
|
|
2030
|
-
}
|
|
2031
|
-
return all;
|
|
2032
|
-
}
|
|
2033
|
-
function pickFormat2(format, output, globalOpts) {
|
|
2034
|
-
const allowed = ["md", "json", "txt"];
|
|
2035
|
-
if (format) {
|
|
2036
|
-
const f = format.toLowerCase();
|
|
2037
|
-
if (!allowed.includes(f)) {
|
|
2038
|
-
throw new TiroError(
|
|
2039
|
-
{
|
|
2040
|
-
code: "invalid_format",
|
|
2041
|
-
message: `Invalid --format "${format}". Allowed: md, json, txt.`,
|
|
2042
|
-
errorType: "bad_request"
|
|
2043
|
-
},
|
|
2044
|
-
ExitCode.Usage
|
|
2045
|
-
);
|
|
2046
|
-
}
|
|
2047
|
-
return f;
|
|
2048
|
-
}
|
|
2049
|
-
if (output) {
|
|
2050
|
-
if (output.endsWith(".json")) return "json";
|
|
2051
|
-
if (output.endsWith(".md")) return "md";
|
|
2052
|
-
if (output.endsWith(".txt")) return "txt";
|
|
2053
|
-
return "md";
|
|
2054
|
-
}
|
|
2055
|
-
if (globalOpts.json) return "json";
|
|
2056
|
-
if (globalOpts.pretty) return "md";
|
|
2057
|
-
return process.stdout.isTTY ? "md" : "txt";
|
|
2058
|
-
}
|
|
2059
|
-
|
|
2060
|
-
// src/commands/notes/index.ts
|
|
2061
|
-
function registerNotes(program) {
|
|
2062
|
-
const notes = program.command("notes").description("List, search, and download notes");
|
|
2063
|
-
registerNotesList(notes);
|
|
2064
|
-
registerNotesSearch(notes);
|
|
2065
|
-
registerNotesGet(notes);
|
|
2066
|
-
registerNotesTranscript(notes);
|
|
2067
|
-
}
|
|
2068
|
-
|
|
2069
|
-
// src/commands/wiki/index.ts
|
|
2070
|
-
import "commander";
|
|
2071
|
-
|
|
2072
|
-
// src/commands/wiki/search.ts
|
|
2073
|
-
import "commander";
|
|
2074
|
-
|
|
2075
|
-
// src/lib/api/workspace.ts
|
|
2076
|
-
var cache = /* @__PURE__ */ new WeakMap();
|
|
2077
|
-
async function resolveWorkspaceGuid(client) {
|
|
2078
|
-
const cached = cache.get(client);
|
|
2079
|
-
if (cached) return cached;
|
|
2080
|
-
const promise = client.getJson("/v1/external/workspaces/me", WorkspaceMeSchema).then((res) => res.guid);
|
|
2081
|
-
cache.set(client, promise);
|
|
2082
|
-
return promise;
|
|
2083
|
-
}
|
|
2084
|
-
async function listWorkspaces(client) {
|
|
2085
|
-
const res = await client.getJson("/v1/external/workspaces", WorkspacesListSchema);
|
|
2086
|
-
return res.workspaces;
|
|
2087
|
-
}
|
|
2088
|
-
|
|
2089
|
-
// src/commands/wiki/search.ts
|
|
2090
|
-
var MAX_SIZE = 100;
|
|
2091
|
-
var MAX_QUERY_LENGTH = 500;
|
|
2092
|
-
var HELP_AFTER3 = `
|
|
197
|
+
`).action(async(A,t,r)=>{let o=r.optsWithGlobals(),i=Br(t.format,t.output,o),s=k({...o.hostname!==void 0&&{hostnameOverride:o.hostname}}),c=await s.getJson(`/v1/external/notes/${A}`,B),l=await Lr(s,A),p=z(c,l),m=i==="json"?me(p):i==="md"?te(p,{timestamps:t.timestamps!==!1}):re(p);if(t.output){let y=await Ae(t.output,m,{...t.force===!0&&{force:!0}});C({ok:!0,data:{saved:y.path,size:y.size,format:i,guid:c.guid,paragraphCount:p.paragraphs.length,segmentCount:p.paragraphs.reduce((h,x)=>h+x.segments.length,0)}},o);return}b(o)==="json"&&i!=="json"?C({ok:!0,data:p},o):process.stdout.write(m)})}async function Lr(e,A){let t=await e.getJson(`/v1/external/notes/${A}/paragraphs`,gA.or(Tr)),r=[...t.content];if("nextCursor"in t){let o=t.nextCursor;for(;o;){let i=await e.getJson(`/v1/external/notes/${A}/paragraphs`,gA,{cursor:o});r.push(...i.content),o=i.nextCursor}}return r}function Br(e,A,t){let r=["md","json","txt"];if(e){let o=e.toLowerCase();if(!r.includes(o))throw new u({code:"invalid_format",message:`Invalid --format "${e}". Allowed: md, json, txt.`,errorType:"bad_request"},d.Usage);return o}return A?A.endsWith(".json")?"json":A.endsWith(".md")?"md":A.endsWith(".txt")?"txt":"md":t.json?"json":t.pretty||process.stdout.isTTY?"md":"txt"}function hA(e){let A=e.command("notes").description("List, search, and download notes");iA(A),cA(A),mA(A),fA(A)}import"commander";import"commander";import{z as Rr}from"zod";var Er=Rr.array(Ke),Ir=`
|
|
198
|
+
Examples:
|
|
199
|
+
tiro folders list
|
|
200
|
+
tiro folders list --workspace <guid> --json
|
|
201
|
+
|
|
202
|
+
Lists the folders in a workspace (flat). sharingType is PRIVATE / ALL_MEMBER_VIEWER
|
|
203
|
+
/ ALL_MEMBER_EDITOR / LIMITED. Pass a folder id to 'tiro notes list --folder <id>'.
|
|
204
|
+
The workspace resolves from --workspace, else your API key's bound workspace; an
|
|
205
|
+
unbound credential must pass --workspace (run 'tiro wiki workspaces' for guids).
|
|
206
|
+
`;function wA(e){e.command("list").description("List folders in a workspace.").option("--workspace <guid>","Workspace to list folders from (see `tiro wiki workspaces`). Default: your API key's bound workspace").addHelpText("after",Ir).action(async(A,t)=>{let r=t.optsWithGlobals(),o=k({...r.hostname!==void 0&&{hostnameOverride:r.hostname}}),i=await M(o,A.workspace),s=await o.getJson(`/v1/external/workspaces/${encodeURIComponent(i)}/folders`,Er);if(b(r)==="json")for(let l of s)v(l);else Or(s,r)})}function Or(e,A){if(e.length===0){process.stdout.write(`${a("(no folders)","gray",A)}
|
|
207
|
+
`);return}for(let t of e)process.stdout.write(`${a(t.id,"dim",A)} ${a(t.sharingType,"gray",A)} ${t.title}
|
|
208
|
+
`)}import"commander";var Pr=q(de),Xr=`
|
|
209
|
+
Examples:
|
|
210
|
+
tiro folders tree
|
|
211
|
+
tiro folders tree --workspace <guid> --json
|
|
212
|
+
|
|
213
|
+
Returns the folder hierarchy for a workspace. In JSON mode each root folder is one
|
|
214
|
+
NDJSON line with nested children; in pretty mode the tree is indented by depth.
|
|
215
|
+
`;function yA(e){e.command("tree").description("Show the folder hierarchy of a workspace.").option("--workspace <guid>","Workspace to read the tree from (see `tiro wiki workspaces`). Default: your API key's bound workspace").addHelpText("after",Xr).action(async(A,t)=>{let r=t.optsWithGlobals(),o=k({...r.hostname!==void 0&&{hostnameOverride:r.hostname}}),i=await M(o,A.workspace),s=await o.getJson(`/v1/external/workspaces/${encodeURIComponent(i)}/folders/tree`,Pr);if(b(r)==="json")for(let l of s.content)v(l);else Sr(s.content,r)})}function Sr(e,A){if(e.length===0){process.stdout.write(`${a("(no folders)","gray",A)}
|
|
216
|
+
`);return}for(let t of e)bA(t,A)}function bA(e,A){let t=" ".repeat(e.depth),r=e.isAccessible?e.title:a(`${e.title} (no access)`,"gray",A);process.stdout.write(`${t}${a(e.id,"dim",A)} ${r}
|
|
217
|
+
`);for(let o of e.children)bA(o,A)}function kA(e){let A=e.command("folders").description("List workspace folders (flat or as a tree) to scope note queries");wA(A),yA(A)}import"commander";import"commander";var Fr=R(Ze),CA=1e3,Dr=`
|
|
218
|
+
Examples:
|
|
219
|
+
tiro word-memories list
|
|
220
|
+
tiro word-memories list --workspace <guid> --json
|
|
221
|
+
|
|
222
|
+
Lists a workspace's custom word memories (vocabulary that biases transcription).
|
|
223
|
+
The workspace resolves from --workspace, else your API key's bound workspace; an
|
|
224
|
+
unbound credential must pass --workspace (run 'tiro wiki workspaces' for guids).
|
|
225
|
+
`;function vA(e){e.command("list").description("List a workspace's custom word memories.").option("--workspace <guid>","Workspace to list from (see `tiro wiki workspaces`). Default: your API key's bound workspace").option("--limit <n>",`Max results per page (max ${CA})`).option("--cursor <token>","Continue a previous page").addHelpText("after",Dr).action(async(A,t)=>{let r=t.optsWithGlobals(),o=k({...r.hostname!==void 0&&{hostnameOverride:r.hostname}}),i=await M(o,A.workspace),s={},c=qr(A.limit);c!==void 0&&(s.size=c),A.cursor&&(s.cursor=A.cursor);let l=await o.getJson(`/v1/external/workspaces/${encodeURIComponent(i)}/word-memories`,Fr,s);if(b(r)==="json"){for(let m of l.content)v(m);l.nextCursor&&v({_cursor:l.nextCursor})}else Mr(l.content,l.nextCursor,r)})}function qr(e){if(!e)return;let A=parseInt(e,10);if(!(!Number.isFinite(A)||A<=0))return Math.min(A,CA)}function Mr(e,A,t){if(e.length===0){process.stdout.write(`${a("(no word memories)","gray",t)}
|
|
226
|
+
`);return}for(let r of e)process.stdout.write(`${a(String(r.id),"dim",t)} ${r.entry}
|
|
227
|
+
`);A&&process.stdout.write(`${a(`
|
|
228
|
+
next: --cursor ${A}`,"gray",t)}
|
|
229
|
+
`)}function xA(e){let A=e.command("word-memories").description("List a workspace's custom word memories (transcription vocabulary)");vA(A)}import"commander";import"commander";var LA=100,TA=500,Hr=`
|
|
2093
230
|
Examples:
|
|
2094
231
|
tiro wiki search "onboarding"
|
|
2095
232
|
tiro wiki search "payment gateway" --size 50 --json
|
|
@@ -2099,68 +236,9 @@ The workspace defaults to the one implicit in your API key. Pass --workspace
|
|
|
2099
236
|
to target a specific workspace (get guids from 'tiro wiki workspaces').
|
|
2100
237
|
Results are ranked wiki pages (pageGuid, name, type, relevance). Pass a
|
|
2101
238
|
pageGuid to 'tiro wiki page', 'tiro wiki mentions', or 'tiro wiki graph'.
|
|
2102
|
-
`;
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
const globalOpts = cmd.optsWithGlobals();
|
|
2106
|
-
const keyword = query.trim();
|
|
2107
|
-
if (!keyword) {
|
|
2108
|
-
throw new TiroError(
|
|
2109
|
-
{
|
|
2110
|
-
code: "missing_query",
|
|
2111
|
-
message: "wiki search requires a query.",
|
|
2112
|
-
errorType: "bad_request",
|
|
2113
|
-
suggestion: 'tiro wiki search "onboarding"'
|
|
2114
|
-
},
|
|
2115
|
-
ExitCode.Usage
|
|
2116
|
-
);
|
|
2117
|
-
}
|
|
2118
|
-
const client = createApiClient({
|
|
2119
|
-
...globalOpts.hostname !== void 0 && { hostnameOverride: globalOpts.hostname }
|
|
2120
|
-
});
|
|
2121
|
-
const workspaceGuid = opts.workspace ?? await resolveWorkspaceGuid(client);
|
|
2122
|
-
const clampedKeyword = keyword.length > MAX_QUERY_LENGTH ? keyword.slice(0, MAX_QUERY_LENGTH) : keyword;
|
|
2123
|
-
const params = { q: clampedKeyword };
|
|
2124
|
-
const size = clampSize(opts.size);
|
|
2125
|
-
if (size !== void 0) params["size"] = size;
|
|
2126
|
-
const res = await client.getJson(
|
|
2127
|
-
// Fix 1: encode workspaceGuid so path-traversal sequences (../, ?, #) are neutralised.
|
|
2128
|
-
`/v1/external/workspaces/${encodeURIComponent(workspaceGuid)}/wiki/search/pages`,
|
|
2129
|
-
WikiSearchPagesResponseSchema,
|
|
2130
|
-
params
|
|
2131
|
-
);
|
|
2132
|
-
const mode = resolveOutputMode(globalOpts);
|
|
2133
|
-
if (mode === "json") {
|
|
2134
|
-
for (const item of res.items) printNdjson(item);
|
|
2135
|
-
} else {
|
|
2136
|
-
printPretty3(res.items, globalOpts);
|
|
2137
|
-
}
|
|
2138
|
-
});
|
|
2139
|
-
}
|
|
2140
|
-
function clampSize(raw) {
|
|
2141
|
-
if (!raw) return void 0;
|
|
2142
|
-
const n = parseInt(raw, 10);
|
|
2143
|
-
if (!Number.isFinite(n) || n <= 0) return void 0;
|
|
2144
|
-
return Math.min(n, MAX_SIZE);
|
|
2145
|
-
}
|
|
2146
|
-
function printPretty3(items, opts) {
|
|
2147
|
-
if (items.length === 0) {
|
|
2148
|
-
process.stdout.write(`${color("(no matches)", "gray", opts)}
|
|
2149
|
-
`);
|
|
2150
|
-
return;
|
|
2151
|
-
}
|
|
2152
|
-
for (const it of items) {
|
|
2153
|
-
const score = it.score.toFixed(2);
|
|
2154
|
-
process.stdout.write(
|
|
2155
|
-
`${color(score, "cyan", opts)} ${color(it.guid, "dim", opts)} ${color(it.pageType, "gray", opts)} ${it.canonicalName}
|
|
2156
|
-
`
|
|
2157
|
-
);
|
|
2158
|
-
}
|
|
2159
|
-
}
|
|
2160
|
-
|
|
2161
|
-
// src/commands/wiki/page.ts
|
|
2162
|
-
import "commander";
|
|
2163
|
-
var HELP_AFTER4 = `
|
|
239
|
+
`;function BA(e){e.command("search <query>").description("Search the workspace wiki for pages matching a keyword.").option("--size <n>",`Max results (default: backend default, max ${LA})`).option("--workspace <guid>","Target a specific workspace (from `tiro wiki workspaces`); defaults to your default workspace").addHelpText("after",Hr).action(async(A,t,r)=>{let o=r.optsWithGlobals(),i=A.trim();if(!i)throw new u({code:"missing_query",message:"wiki search requires a query.",errorType:"bad_request",suggestion:'tiro wiki search "onboarding"'},d.Usage);let s=k({...o.hostname!==void 0&&{hostnameOverride:o.hostname}}),c=t.workspace??await E(s),p={q:i.length>TA?i.slice(0,TA):i},m=Wr(t.size);m!==void 0&&(p.size=m);let w=await s.getJson(`/v1/external/workspaces/${encodeURIComponent(c)}/wiki/search/pages`,Ue,p);if(b(o)==="json")for(let h of w.items)v(h);else Nr(w.items,o)})}function Wr(e){if(!e)return;let A=parseInt(e,10);if(!(!Number.isFinite(A)||A<=0))return Math.min(A,LA)}function Nr(e,A){if(e.length===0){process.stdout.write(`${a("(no matches)","gray",A)}
|
|
240
|
+
`);return}for(let t of e){let r=t.score.toFixed(2);process.stdout.write(`${a(r,"cyan",A)} ${a(t.guid,"dim",A)} ${a(t.pageType,"gray",A)} ${t.canonicalName}
|
|
241
|
+
`)}}import"commander";var Gr=`
|
|
2164
242
|
Examples:
|
|
2165
243
|
tiro wiki page <pageGuid>
|
|
2166
244
|
tiro wiki page <pageGuid> --json
|
|
@@ -2169,53 +247,15 @@ Examples:
|
|
|
2169
247
|
Returns the page body (per-user description) plus metadata, mentions,
|
|
2170
248
|
aliases, and links. The workspace defaults to the one implicit in your API
|
|
2171
249
|
key; pass --workspace to target a specific workspace (from 'tiro wiki workspaces').
|
|
2172
|
-
`;
|
|
2173
|
-
|
|
2174
|
-
parent.command("page <pageGuid>").description("Get a single wiki page: body, metadata, mentions, aliases, and links.").option("--workspace <guid>", "Target a specific workspace (from `tiro wiki workspaces`); defaults to your default workspace").addHelpText("after", HELP_AFTER4).action(async (pageGuid, opts, cmd) => {
|
|
2175
|
-
const globalOpts = cmd.optsWithGlobals();
|
|
2176
|
-
const client = createApiClient({
|
|
2177
|
-
...globalOpts.hostname !== void 0 && { hostnameOverride: globalOpts.hostname }
|
|
2178
|
-
});
|
|
2179
|
-
const workspaceGuid = opts.workspace ?? await resolveWorkspaceGuid(client);
|
|
2180
|
-
const page = await client.getJson(
|
|
2181
|
-
`/v1/external/workspaces/${encodeURIComponent(workspaceGuid)}/wiki/pages/${encodeURIComponent(pageGuid)}`,
|
|
2182
|
-
WikiPageDetailSchema
|
|
2183
|
-
);
|
|
2184
|
-
const mode = resolveOutputMode(globalOpts);
|
|
2185
|
-
if (mode === "json") {
|
|
2186
|
-
printOutput({ ok: true, data: page }, globalOpts);
|
|
2187
|
-
} else {
|
|
2188
|
-
printPretty4(page, globalOpts);
|
|
2189
|
-
}
|
|
2190
|
-
});
|
|
2191
|
-
}
|
|
2192
|
-
function printPretty4(page, opts) {
|
|
2193
|
-
const w = process.stdout.write.bind(process.stdout);
|
|
2194
|
-
w(`${color(page.canonicalName, "bold", opts)} ${color(`(${page.pageType})`, "gray", opts)}
|
|
2195
|
-
`);
|
|
2196
|
-
w(`${color(page.guid, "dim", opts)}
|
|
250
|
+
`;function RA(e){e.command("page <pageGuid>").description("Get a single wiki page: body, metadata, mentions, aliases, and links.").option("--workspace <guid>","Target a specific workspace (from `tiro wiki workspaces`); defaults to your default workspace").addHelpText("after",Gr).action(async(A,t,r)=>{let o=r.optsWithGlobals(),i=k({...o.hostname!==void 0&&{hostnameOverride:o.hostname}}),s=t.workspace??await E(i),c=await i.getJson(`/v1/external/workspaces/${encodeURIComponent(s)}/wiki/pages/${encodeURIComponent(A)}`,Qe);b(o)==="json"?C({ok:!0,data:c},o):Vr(c,o)})}function Vr(e,A){let t=process.stdout.write.bind(process.stdout);if(t(`${a(e.canonicalName,"bold",A)} ${a(`(${e.pageType})`,"gray",A)}
|
|
251
|
+
`),t(`${a(e.guid,"dim",A)}
|
|
2197
252
|
|
|
2198
|
-
`)
|
|
2199
|
-
if (page.description) {
|
|
2200
|
-
w(`${page.description}
|
|
253
|
+
`),e.description)t(`${e.description}
|
|
2201
254
|
|
|
2202
|
-
`);
|
|
2203
|
-
} else {
|
|
2204
|
-
const status = page.descriptionStatus ?? "none";
|
|
2205
|
-
w(`${color(`(no description \u2014 status: ${status})`, "gray", opts)}
|
|
255
|
+
`);else{let r=e.descriptionStatus??"none";t(`${a(`(no description \u2014 status: ${r})`,"gray",A)}
|
|
2206
256
|
|
|
2207
|
-
`)
|
|
2208
|
-
|
|
2209
|
-
w(
|
|
2210
|
-
`${color("mentions", "gray", opts)} ${page.mentionCountVisible} ${color("aliases", "gray", opts)} ${page.aliases.length} ${color("links", "gray", opts)} ${page.links.length}
|
|
2211
|
-
`
|
|
2212
|
-
);
|
|
2213
|
-
}
|
|
2214
|
-
|
|
2215
|
-
// src/commands/wiki/mentions.ts
|
|
2216
|
-
import "commander";
|
|
2217
|
-
var MAX_LIMIT = 200;
|
|
2218
|
-
var HELP_AFTER5 = `
|
|
257
|
+
`)}t(`${a("mentions","gray",A)} ${e.mentionCountVisible} ${a("aliases","gray",A)} ${e.aliases.length} ${a("links","gray",A)} ${e.links.length}
|
|
258
|
+
`)}import"commander";var EA=200,Ur=`
|
|
2219
259
|
Examples:
|
|
2220
260
|
tiro wiki mentions <pageGuid>
|
|
2221
261
|
tiro wiki mentions <pageGuid> --limit 100 --json
|
|
@@ -2226,66 +266,9 @@ Lists the note paragraphs that reference a wiki page. In JSON mode a trailing
|
|
|
2226
266
|
cursor token back via --cursor to fetch the next page. The workspace defaults
|
|
2227
267
|
to the one implicit in your API key; pass --workspace to target a specific
|
|
2228
268
|
workspace (from 'tiro wiki workspaces').
|
|
2229
|
-
`;
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
const globalOpts = cmd.optsWithGlobals();
|
|
2233
|
-
const client = createApiClient({
|
|
2234
|
-
...globalOpts.hostname !== void 0 && { hostnameOverride: globalOpts.hostname }
|
|
2235
|
-
});
|
|
2236
|
-
const workspaceGuid = opts.workspace ?? await resolveWorkspaceGuid(client);
|
|
2237
|
-
const params = {};
|
|
2238
|
-
const limit = clampLimit3(opts.limit);
|
|
2239
|
-
if (limit !== void 0) params["limit"] = limit;
|
|
2240
|
-
if (opts.cursor) params["cursor"] = opts.cursor;
|
|
2241
|
-
const res = await client.getJson(
|
|
2242
|
-
`/v1/external/workspaces/${encodeURIComponent(workspaceGuid)}/wiki/pages/${encodeURIComponent(pageGuid)}/mentions`,
|
|
2243
|
-
WikiMentionListResponseSchema,
|
|
2244
|
-
params
|
|
2245
|
-
);
|
|
2246
|
-
const mode = resolveOutputMode(globalOpts);
|
|
2247
|
-
if (mode === "json") {
|
|
2248
|
-
for (const item of res.items) printNdjson(item);
|
|
2249
|
-
const next = nextCursor(res.nextCursorCreatedAt, res.nextCursorId);
|
|
2250
|
-
if (next) printNdjson({ _cursor: next });
|
|
2251
|
-
} else {
|
|
2252
|
-
printPretty5(res.items, globalOpts);
|
|
2253
|
-
}
|
|
2254
|
-
});
|
|
2255
|
-
}
|
|
2256
|
-
function clampLimit3(raw) {
|
|
2257
|
-
if (!raw) return void 0;
|
|
2258
|
-
const n = parseInt(raw, 10);
|
|
2259
|
-
if (!Number.isFinite(n) || n <= 0) return void 0;
|
|
2260
|
-
return Math.min(n, MAX_LIMIT);
|
|
2261
|
-
}
|
|
2262
|
-
function nextCursor(createdAt, id) {
|
|
2263
|
-
if (createdAt === null || id === null) return null;
|
|
2264
|
-
const millis = Date.parse(createdAt);
|
|
2265
|
-
if (Number.isNaN(millis)) return null;
|
|
2266
|
-
return `${millis}_${id}`;
|
|
2267
|
-
}
|
|
2268
|
-
function printPretty5(items, opts) {
|
|
2269
|
-
if (items.length === 0) {
|
|
2270
|
-
process.stdout.write(`${color("(no mentions)", "gray", opts)}
|
|
2271
|
-
`);
|
|
2272
|
-
return;
|
|
2273
|
-
}
|
|
2274
|
-
for (const m of items) {
|
|
2275
|
-
const date = m.createdAt.slice(0, 10);
|
|
2276
|
-
process.stdout.write(
|
|
2277
|
-
`${color(date, "gray", opts)} ${color(m.kind, "cyan", opts)} ${m.extractedText}
|
|
2278
|
-
`
|
|
2279
|
-
);
|
|
2280
|
-
}
|
|
2281
|
-
}
|
|
2282
|
-
|
|
2283
|
-
// src/commands/wiki/graph.ts
|
|
2284
|
-
import "commander";
|
|
2285
|
-
var MODES = ["seed", "expand", "around", "links"];
|
|
2286
|
-
var MAX_QUERY_LENGTH2 = 500;
|
|
2287
|
-
var GRAPH_CAP_DEFAULT = 50;
|
|
2288
|
-
var HELP_AFTER6 = `
|
|
269
|
+
`;function IA(e){e.command("mentions <pageGuid>").description("List the note mentions that reference a single wiki page.").option("--limit <n>",`Max mentions per page (max ${EA})`).option("--cursor <token>","Continue from a previous page's cursor token.").option("--workspace <guid>","Target a specific workspace (from `tiro wiki workspaces`); defaults to your default workspace").addHelpText("after",Ur).action(async(A,t,r)=>{let o=r.optsWithGlobals(),i=k({...o.hostname!==void 0&&{hostnameOverride:o.hostname}}),s=t.workspace??await E(i),c={},l=jr(t.limit);l!==void 0&&(c.limit=l),t.cursor&&(c.cursor=t.cursor);let p=await i.getJson(`/v1/external/workspaces/${encodeURIComponent(s)}/wiki/pages/${encodeURIComponent(A)}/mentions`,ze,c);if(b(o)==="json"){for(let y of p.items)v(y);let w=Qr(p.nextCursorCreatedAt,p.nextCursorId);w&&v({_cursor:w})}else zr(p.items,o)})}function jr(e){if(!e)return;let A=parseInt(e,10);if(!(!Number.isFinite(A)||A<=0))return Math.min(A,EA)}function Qr(e,A){if(e===null||A===null)return null;let t=Date.parse(e);return Number.isNaN(t)?null:`${t}_${A}`}function zr(e,A){if(e.length===0){process.stdout.write(`${a("(no mentions)","gray",A)}
|
|
270
|
+
`);return}for(let t of e){let r=t.createdAt.slice(0,10);process.stdout.write(`${a(r,"gray",A)} ${a(t.kind,"cyan",A)} ${t.extractedText}
|
|
271
|
+
`)}}import"commander";var ge=["seed","expand","around","links"],OA=500,Yr=50,Kr=`
|
|
2289
272
|
Modes (--mode, default: around):
|
|
2290
273
|
around Neighborhood around <pageGuid> (--radius hops, default 2)
|
|
2291
274
|
expand Outward expansion from <pageGuid> (--depth hops, default 1)
|
|
@@ -2302,292 +285,40 @@ Examples:
|
|
|
2302
285
|
Returns a node+edge slice of the workspace wiki graph. The workspace defaults
|
|
2303
286
|
to the one implicit in your API key; pass --workspace to target a specific
|
|
2304
287
|
workspace (from 'tiro wiki workspaces').
|
|
2305
|
-
`;
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
{
|
|
2313
|
-
code: "missing_page_guid",
|
|
2314
|
-
message: `--mode ${mode} requires a <pageGuid> positional argument.`,
|
|
2315
|
-
errorType: "bad_request",
|
|
2316
|
-
suggestion: `tiro wiki graph <pageGuid> --mode ${mode}`
|
|
2317
|
-
},
|
|
2318
|
-
ExitCode.Usage
|
|
2319
|
-
);
|
|
2320
|
-
}
|
|
2321
|
-
const client = createApiClient({
|
|
2322
|
-
...globalOpts.hostname !== void 0 && { hostnameOverride: globalOpts.hostname }
|
|
2323
|
-
});
|
|
2324
|
-
const workspaceGuid = opts.workspace ?? await resolveWorkspaceGuid(client);
|
|
2325
|
-
const effectiveLimit = mode === "links" ? void 0 : clampInt(opts.limit, 200) ?? GRAPH_CAP_DEFAULT;
|
|
2326
|
-
const raw = await fetchGraph(client, workspaceGuid, mode, pageGuid ?? "", opts, effectiveLimit);
|
|
2327
|
-
const res = applyGraphCap(raw, effectiveLimit ?? raw.nodes.length);
|
|
2328
|
-
const mode2 = resolveOutputMode(globalOpts);
|
|
2329
|
-
if (mode2 === "json") {
|
|
2330
|
-
printOutput({ ok: true, data: res }, globalOpts);
|
|
2331
|
-
} else {
|
|
2332
|
-
printPretty6(res, globalOpts);
|
|
2333
|
-
}
|
|
2334
|
-
});
|
|
2335
|
-
}
|
|
2336
|
-
async function fetchGraph(client, workspaceGuid, mode, pageGuid, opts, limit) {
|
|
2337
|
-
const base = `/v1/external/workspaces/${encodeURIComponent(workspaceGuid)}/wiki/graph`;
|
|
2338
|
-
if (mode === "expand") {
|
|
2339
|
-
return client.getJson(`${base}/expand`, WikiGraphResponseSchema, {
|
|
2340
|
-
pageGuid,
|
|
2341
|
-
depth: clampInt(opts.depth, 200),
|
|
2342
|
-
limit
|
|
2343
|
-
});
|
|
2344
|
-
}
|
|
2345
|
-
if (mode === "around") {
|
|
2346
|
-
return client.getJson(`${base}/around`, WikiGraphResponseSchema, {
|
|
2347
|
-
pageGuid,
|
|
2348
|
-
radius: clampInt(opts.radius, 200),
|
|
2349
|
-
limit
|
|
2350
|
-
});
|
|
2351
|
-
}
|
|
2352
|
-
if (mode === "links") {
|
|
2353
|
-
const guids = [pageGuid, ...opts.page ?? []].filter((g) => g && g.length > 0);
|
|
2354
|
-
return client.getJson(`${base}/links`, WikiGraphResponseSchema, {
|
|
2355
|
-
pageGuids: guids.join(",")
|
|
2356
|
-
});
|
|
2357
|
-
}
|
|
2358
|
-
const rawQuery = opts.query;
|
|
2359
|
-
const clampedQuery = rawQuery && rawQuery.length > MAX_QUERY_LENGTH2 ? rawQuery.slice(0, MAX_QUERY_LENGTH2) : rawQuery;
|
|
2360
|
-
return client.getJson(`${base}/seed`, WikiGraphResponseSchema, {
|
|
2361
|
-
type: opts.type,
|
|
2362
|
-
since: opts.since ? parseDate(opts.since) : void 0,
|
|
2363
|
-
q: clampedQuery,
|
|
2364
|
-
limit
|
|
2365
|
-
});
|
|
2366
|
-
}
|
|
2367
|
-
function parseMode(raw) {
|
|
2368
|
-
if (!raw) return "around";
|
|
2369
|
-
const m = raw.toLowerCase();
|
|
2370
|
-
if (MODES.includes(m)) return m;
|
|
2371
|
-
throw new TiroError(
|
|
2372
|
-
{
|
|
2373
|
-
code: "invalid_mode",
|
|
2374
|
-
message: `Invalid --mode "${raw}". Allowed: ${MODES.join(", ")}.`,
|
|
2375
|
-
errorType: "bad_request",
|
|
2376
|
-
suggestion: "tiro wiki graph <pageGuid> --mode around"
|
|
2377
|
-
},
|
|
2378
|
-
ExitCode.Usage
|
|
2379
|
-
);
|
|
2380
|
-
}
|
|
2381
|
-
function clampInt(raw, max) {
|
|
2382
|
-
if (!raw) return void 0;
|
|
2383
|
-
const n = parseInt(raw, 10);
|
|
2384
|
-
if (!Number.isFinite(n) || n <= 0) return void 0;
|
|
2385
|
-
return Math.min(n, max);
|
|
2386
|
-
}
|
|
2387
|
-
function collect(value, prev) {
|
|
2388
|
-
return [...prev, value];
|
|
2389
|
-
}
|
|
2390
|
-
function applyGraphCap(graph, limit) {
|
|
2391
|
-
const keptNodes = graph.nodes.slice(0, limit);
|
|
2392
|
-
const keptGuids = new Set(keptNodes.map((n) => n.guid));
|
|
2393
|
-
const keptEdges = graph.edges.filter(
|
|
2394
|
-
(e) => keptGuids.has(e.sourcePageGuid) && keptGuids.has(e.targetPageGuid)
|
|
2395
|
-
);
|
|
2396
|
-
const truncated = graph.nodes.length > limit || keptEdges.length < graph.edges.length;
|
|
2397
|
-
return { ...graph, nodes: keptNodes, edges: keptEdges, truncated };
|
|
2398
|
-
}
|
|
2399
|
-
function printPretty6(res, opts) {
|
|
2400
|
-
const w = process.stdout.write.bind(process.stdout);
|
|
2401
|
-
w(
|
|
2402
|
-
`${color("nodes", "gray", opts)} ${res.nodes.length} ${color("edges", "gray", opts)} ${res.edges.length}
|
|
2403
|
-
`
|
|
2404
|
-
);
|
|
2405
|
-
if (res.truncated) {
|
|
2406
|
-
w(
|
|
2407
|
-
`${color(`(truncated \u2014 showing first ${res.nodes.length} nodes; narrow with --query / --type / smaller --radius)`, "gray", opts)}
|
|
2408
|
-
`
|
|
2409
|
-
);
|
|
2410
|
-
}
|
|
2411
|
-
w("\n");
|
|
2412
|
-
for (const n of res.nodes) {
|
|
2413
|
-
w(`${color("\u25CF", "cyan", opts)} ${color(n.guid, "dim", opts)} ${n.canonicalName}
|
|
2414
|
-
`);
|
|
2415
|
-
}
|
|
2416
|
-
if (res.edges.length > 0) w("\n");
|
|
2417
|
-
for (const e of res.edges) {
|
|
2418
|
-
const arrow = e.isDirectional ? "\u2192" : "\u2014";
|
|
2419
|
-
w(
|
|
2420
|
-
`${color(e.sourcePageGuid, "dim", opts)} ${arrow} ${color(e.targetPageGuid, "dim", opts)} ${color(e.linkType, "gray", opts)}
|
|
2421
|
-
`
|
|
2422
|
-
);
|
|
2423
|
-
}
|
|
2424
|
-
}
|
|
2425
|
-
|
|
2426
|
-
// src/commands/wiki/workspaces.ts
|
|
2427
|
-
import "commander";
|
|
2428
|
-
var HELP_AFTER7 = `
|
|
288
|
+
`;function PA(e){e.command("graph [pageGuid]").description("Get a node+edge slice of the wiki link graph around a page.").option("--mode <mode>",`Graph mode: ${ge.join(" | ")} (default: around)`).option("--depth <n>","expand mode: link-hops to traverse outward (default 1)").option("--radius <n>","around mode: neighborhood radius in hops (default 2)").option("--limit <n>","Max nodes to return (max 200)").option("--page <guid>","links mode: additional page guid (repeatable)",$r,[]).option("--type <type>","seed mode: restrict to a page type (CONCEPT|DECISION|ENTITY)").option("--since <date>","seed mode: only pages updated at/after this date").option("--query <keyword>","seed mode: keyword to seed the overview graph").option("--workspace <guid>","Target a specific workspace (from `tiro wiki workspaces`); defaults to your default workspace").addHelpText("after",Kr).action(async(A,t,r)=>{let o=r.optsWithGlobals(),i=Jr(t.mode);if((i==="expand"||i==="around")&&!A)throw new u({code:"missing_page_guid",message:`--mode ${i} requires a <pageGuid> positional argument.`,errorType:"bad_request",suggestion:`tiro wiki graph <pageGuid> --mode ${i}`},d.Usage);let s=k({...o.hostname!==void 0&&{hostnameOverride:o.hostname}}),c=t.workspace??await E(s),l=i==="links"?void 0:fe(t.limit,200)??Yr,p=await Zr(s,c,i,A??"",t,l),m=_r(p,l??p.nodes.length);b(o)==="json"?C({ok:!0,data:m},o):eo(m,o)})}async function Zr(e,A,t,r,o,i){let s=`/v1/external/workspaces/${encodeURIComponent(A)}/wiki/graph`;if(t==="expand")return e.getJson(`${s}/expand`,V,{pageGuid:r,depth:fe(o.depth,200),limit:i});if(t==="around")return e.getJson(`${s}/around`,V,{pageGuid:r,radius:fe(o.radius,200),limit:i});if(t==="links"){let p=[r,...o.page??[]].filter(m=>m&&m.length>0);return e.getJson(`${s}/links`,V,{pageGuids:p.join(",")})}let c=o.query,l=c&&c.length>OA?c.slice(0,OA):c;return e.getJson(`${s}/seed`,V,{type:o.type,since:o.since?I(o.since):void 0,q:l,limit:i})}function Jr(e){if(!e)return"around";let A=e.toLowerCase();if(ge.includes(A))return A;throw new u({code:"invalid_mode",message:`Invalid --mode "${e}". Allowed: ${ge.join(", ")}.`,errorType:"bad_request",suggestion:"tiro wiki graph <pageGuid> --mode around"},d.Usage)}function fe(e,A){if(!e)return;let t=parseInt(e,10);if(!(!Number.isFinite(t)||t<=0))return Math.min(t,A)}function $r(e,A){return[...A,e]}function _r(e,A){let t=e.nodes.slice(0,A),r=new Set(t.map(s=>s.guid)),o=e.edges.filter(s=>r.has(s.sourcePageGuid)&&r.has(s.targetPageGuid)),i=e.nodes.length>A||o.length<e.edges.length;return{...e,nodes:t,edges:o,truncated:i}}function eo(e,A){let t=process.stdout.write.bind(process.stdout);t(`${a("nodes","gray",A)} ${e.nodes.length} ${a("edges","gray",A)} ${e.edges.length}
|
|
289
|
+
`),e.truncated&&t(`${a(`(truncated \u2014 showing first ${e.nodes.length} nodes; narrow with --query / --type / smaller --radius)`,"gray",A)}
|
|
290
|
+
`),t(`
|
|
291
|
+
`);for(let r of e.nodes)t(`${a("\u25CF","cyan",A)} ${a(r.guid,"dim",A)} ${r.canonicalName}
|
|
292
|
+
`);e.edges.length>0&&t(`
|
|
293
|
+
`);for(let r of e.edges){let o=r.isDirectional?"\u2192":"\u2014";t(`${a(r.sourcePageGuid,"dim",A)} ${o} ${a(r.targetPageGuid,"dim",A)} ${a(r.linkType,"gray",A)}
|
|
294
|
+
`)}}import"commander";var Ao=`
|
|
2429
295
|
Examples:
|
|
2430
296
|
tiro wiki workspaces
|
|
2431
297
|
tiro wiki workspaces --json
|
|
2432
298
|
|
|
2433
299
|
Lists all workspaces you are a member of. Use the guid with --workspace on
|
|
2434
300
|
any other wiki command to target a non-default workspace.
|
|
2435
|
-
`;
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
const globalOpts = cmd.optsWithGlobals();
|
|
2439
|
-
const client = createApiClient({
|
|
2440
|
-
...globalOpts.hostname !== void 0 && { hostnameOverride: globalOpts.hostname }
|
|
2441
|
-
});
|
|
2442
|
-
const workspaces = await listWorkspaces(client);
|
|
2443
|
-
const mode = resolveOutputMode(globalOpts);
|
|
2444
|
-
if (mode === "json") {
|
|
2445
|
-
for (const ws of workspaces) printNdjson(ws);
|
|
2446
|
-
} else {
|
|
2447
|
-
printPretty7(workspaces, globalOpts);
|
|
2448
|
-
}
|
|
2449
|
-
});
|
|
2450
|
-
}
|
|
2451
|
-
function printPretty7(workspaces, opts) {
|
|
2452
|
-
if (workspaces.length === 0) {
|
|
2453
|
-
process.stdout.write(`${color("(no workspaces)", "gray", opts)}
|
|
2454
|
-
`);
|
|
2455
|
-
return;
|
|
2456
|
-
}
|
|
2457
|
-
for (const ws of workspaces) {
|
|
2458
|
-
const wikiStatus = ws.isWikiEnabled ? color("wiki:on", "green", opts) : color("wiki:off", "gray", opts);
|
|
2459
|
-
process.stdout.write(
|
|
2460
|
-
`${color(ws.guid, "dim", opts)} ${wikiStatus} ${ws.name}
|
|
2461
|
-
`
|
|
2462
|
-
);
|
|
2463
|
-
}
|
|
2464
|
-
}
|
|
2465
|
-
|
|
2466
|
-
// src/commands/wiki/index.ts
|
|
2467
|
-
function registerWiki(program) {
|
|
2468
|
-
const wiki = program.command("wiki").description("Search and explore the workspace wiki (pages, mentions, link graph)");
|
|
2469
|
-
registerWikiSearch(wiki);
|
|
2470
|
-
registerWikiPage(wiki);
|
|
2471
|
-
registerWikiMentions(wiki);
|
|
2472
|
-
registerWikiGraph(wiki);
|
|
2473
|
-
registerWikiWorkspaces(wiki);
|
|
2474
|
-
}
|
|
2475
|
-
|
|
2476
|
-
// src/commands/mcp/index.ts
|
|
2477
|
-
import "commander";
|
|
2478
|
-
|
|
2479
|
-
// src/commands/mcp/info.ts
|
|
2480
|
-
import "commander";
|
|
2481
|
-
function registerMcpInfo(parent) {
|
|
2482
|
-
parent.command("info").description("Show Tiro MCP endpoint and connection instructions").action((_opts, cmd) => {
|
|
2483
|
-
const globalOpts = cmd.optsWithGlobals();
|
|
2484
|
-
printOutput(
|
|
2485
|
-
{
|
|
2486
|
-
ok: true,
|
|
2487
|
-
data: {
|
|
2488
|
-
name: HOSTED_MCP_NAME,
|
|
2489
|
-
transport: "http",
|
|
2490
|
-
url: HOSTED_MCP_URL,
|
|
2491
|
-
docs: HOSTED_MCP_DOCS,
|
|
2492
|
-
install: {
|
|
2493
|
-
claudeCode: `claude mcp add --transport http ${HOSTED_MCP_NAME} ${HOSTED_MCP_URL}`
|
|
2494
|
-
}
|
|
2495
|
-
}
|
|
2496
|
-
},
|
|
2497
|
-
globalOpts
|
|
2498
|
-
);
|
|
2499
|
-
});
|
|
2500
|
-
}
|
|
2501
|
-
|
|
2502
|
-
// src/commands/mcp/install.ts
|
|
2503
|
-
import "commander";
|
|
2504
|
-
function registerMcpInstall(parent) {
|
|
2505
|
-
parent.command("install").description("Print the one-line command to add Tiro MCP to Claude Code").option(
|
|
2506
|
-
"--print",
|
|
2507
|
-
"Print the raw `claude mcp add ...` command to stdout (default behavior)"
|
|
2508
|
-
).action((opts, cmd) => {
|
|
2509
|
-
const globalOpts = cmd.optsWithGlobals();
|
|
2510
|
-
const command = `claude mcp add --transport http ${HOSTED_MCP_NAME} ${HOSTED_MCP_URL}`;
|
|
2511
|
-
if (globalOpts.json) {
|
|
2512
|
-
printOutput(
|
|
2513
|
-
{ ok: true, data: { command, name: HOSTED_MCP_NAME, url: HOSTED_MCP_URL } },
|
|
2514
|
-
globalOpts
|
|
2515
|
-
);
|
|
2516
|
-
return;
|
|
2517
|
-
}
|
|
2518
|
-
if (process.stdout.isTTY && opts.print !== true) {
|
|
2519
|
-
process.stderr.write(
|
|
2520
|
-
"Run this in your terminal to register Tiro MCP with Claude Code:\n\n"
|
|
2521
|
-
);
|
|
2522
|
-
}
|
|
2523
|
-
process.stdout.write(`${command}
|
|
2524
|
-
`);
|
|
2525
|
-
});
|
|
2526
|
-
}
|
|
301
|
+
`;function XA(e){e.command("workspaces").description("List all workspaces you are a member of (use guid with --workspace).").addHelpText("after",Ao).action(async(A,t)=>{let r=t.optsWithGlobals(),o=k({...r.hostname!==void 0&&{hostnameOverride:r.hostname}}),i=await _e(o);if(b(r)==="json")for(let c of i)v(c);else to(i,r)})}function to(e,A){if(e.length===0){process.stdout.write(`${a("(no workspaces)","gray",A)}
|
|
302
|
+
`);return}for(let t of e){let r=t.isWikiEnabled?a("wiki:on","green",A):a("wiki:off","gray",A);process.stdout.write(`${a(t.guid,"dim",A)} ${r} ${t.name}
|
|
303
|
+
`)}}function SA(e){let A=e.command("wiki").description("Search and explore the workspace wiki (pages, mentions, link graph)");BA(A),RA(A),IA(A),PA(A),XA(A)}import"commander";import"commander";function FA(e){e.command("info").description("Show Tiro MCP endpoint and connection instructions").action((A,t)=>{let r=t.optsWithGlobals();C({ok:!0,data:{name:W,transport:"http",url:H,docs:DA,install:{claudeCode:`claude mcp add --transport http ${W} ${H}`}}},r)})}import"commander";function qA(e){e.command("install").description("Print the one-line command to add Tiro MCP to Claude Code").option("--print","Print the raw `claude mcp add ...` command to stdout (default behavior)").action((A,t)=>{let r=t.optsWithGlobals(),o=`claude mcp add --transport http ${W} ${H}`;if(r.json){C({ok:!0,data:{command:o,name:W,url:H}},r);return}process.stdout.isTTY&&A.print!==!0&&process.stderr.write(`Run this in your terminal to register Tiro MCP with Claude Code:
|
|
2527
304
|
|
|
2528
|
-
|
|
2529
|
-
var
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
import { fileURLToPath as
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
resolve3(HERE2, "../../package.json"),
|
|
2546
|
-
resolve3(HERE2, "../../../package.json")
|
|
2547
|
-
];
|
|
2548
|
-
var ONE_DAY_MS = 24 * 60 * 60 * 1e3;
|
|
2549
|
-
async function startUpdateCheck() {
|
|
2550
|
-
if (process.env["NO_UPDATE_NOTIFIER"] === "1") return null;
|
|
2551
|
-
if (process.env["CI"]) return null;
|
|
2552
|
-
if (process.stdout.isTTY !== true) return null;
|
|
2553
|
-
const pkg = await loadPkg();
|
|
2554
|
-
if (!pkg) return null;
|
|
2555
|
-
try {
|
|
2556
|
-
const factory = updateNotifier;
|
|
2557
|
-
return factory({
|
|
2558
|
-
pkg,
|
|
2559
|
-
updateCheckInterval: ONE_DAY_MS,
|
|
2560
|
-
shouldNotifyInNpmScript: false
|
|
2561
|
-
});
|
|
2562
|
-
} catch {
|
|
2563
|
-
return null;
|
|
2564
|
-
}
|
|
2565
|
-
}
|
|
2566
|
-
function emitUpdateBanner(notifier) {
|
|
2567
|
-
if (!notifier) return;
|
|
2568
|
-
if (!notifier.update) return;
|
|
2569
|
-
notifier.notify({
|
|
2570
|
-
isGlobal: true,
|
|
2571
|
-
defer: false,
|
|
2572
|
-
message: "Update available {currentVersion} \u2192 {latestVersion}\nRun npm install -g {packageName} to update\n\nChangelog: https://www.npmjs.com/package/{packageName}?activeTab=versions"
|
|
2573
|
-
});
|
|
2574
|
-
}
|
|
2575
|
-
async function loadPkg() {
|
|
2576
|
-
for (const path of CANDIDATE_PATHS2) {
|
|
2577
|
-
try {
|
|
2578
|
-
const raw = await readFile(path, "utf8");
|
|
2579
|
-
const parsed = JSON.parse(raw);
|
|
2580
|
-
if (typeof parsed.name === "string" && typeof parsed.version === "string" && parsed.name.length > 0 && parsed.version.length > 0) {
|
|
2581
|
-
return { name: parsed.name, version: parsed.version };
|
|
2582
|
-
}
|
|
2583
|
-
} catch {
|
|
2584
|
-
}
|
|
2585
|
-
}
|
|
2586
|
-
return null;
|
|
2587
|
-
}
|
|
2588
|
-
|
|
2589
|
-
// src/bin/tiro.ts
|
|
2590
|
-
var EXAMPLES = `
|
|
305
|
+
`),process.stdout.write(`${o}
|
|
306
|
+
`)})}var H="https://mcp.tiro.ooo/mcp",W="tiro",DA="https://api-docs.tiro.ooo/mcp";function MA(e){let A=e.command("mcp").description("Connect Tiro MCP from agent clients (Claude Code, etc.)");FA(A),qA(A)}import"commander";import{spawn as no}from"child_process";import{z as HA}from"zod";var ro="https://registry.npmjs.org",oo=HA.object({version:HA.string().min(1)}).passthrough();async function GA(e){let A=`${ro}/${e}/latest`,t;try{t=await fetch(A,{headers:{accept:"application/json"}})}catch(o){throw WA(`Could not reach the npm registry: ${o.message}`)}if(!t.ok)throw WA(`npm registry returned ${t.status} for ${e}`,t.status);let r=oo.safeParse(await t.json().catch(()=>null));if(!r.success)throw new u({code:"internal_error",message:"Unexpected npm registry response shape",errorType:"internal_error"},d.Generic);return r.data.version}function WA(e,A){return new u({code:"registry_unreachable",message:e,errorType:"network_error",...A!==void 0&&{httpStatus:A},suggestion:"Check your network and retry, or upgrade manually with your package manager."},d.Generic)}function VA(e,A){let t=NA(e),r=NA(A);return t[0]!==r[0]?t[0]>r[0]:t[1]!==r[1]?t[1]>r[1]:t[2]>r[2]}function NA(e){let t=(e.trim().replace(/^v/,"").split(/[-+]/)[0]??"").split(".");return[he(t[0]),he(t[1]),he(t[2])]}function he(e){let A=Number.parseInt(e??"",10);return Number.isFinite(A)?A:0}var UA=["npm","pnpm","yarn","bun"];function jA(e){if(e!==void 0){if(UA.includes(e))return e;throw new u({code:"bad_request",message:`Unknown package manager '${e}'.`,suggestion:`Use one of: ${UA.join(", ")}.`,errorType:"bad_request"},d.Usage)}}function QA(e=process.argv[1]??"",A=process.env.npm_config_user_agent??""){let t=A.toLowerCase().split("/")[0];if(t==="pnpm"||t==="yarn"||t==="bun")return t;let r=e.toLowerCase().split(/[\\/]/);for(let o of["pnpm","yarn","bun"])if(r.includes(o)||r.includes(`.${o}`))return o;return"npm"}function zA(e,A){let t=`${A}@latest`,o={npm:["install","-g",t],pnpm:["add","-g",t],yarn:["global","add",t],bun:["add","-g",t]}[e];return{cmd:e,args:o,display:`${e} ${o.join(" ")}`}}async function YA(e,A){let t=jA(e.pm)??QA(),r=zA(t,S),o=await GA(S),i=VA(o,O),s={name:S,current:O,latest:o};if(e.check){C({ok:!0,data:{...s,updateAvailable:i,command:r.display}},A);return}if(!i){C({ok:!0,data:{...s,updateAvailable:!1,message:"Already up to date."}},A);return}if(e.dryRun){C({ok:!0,data:{...s,command:r.display,dryRun:!0}},A);return}await io(r,o,A)}async function io(e,A,t){t.quiet!==!0&&process.stderr.write(`${a("\u27F3","cyan",t)} Updating ${S} ${O} \u2192 ${A} via ${e.cmd}\u2026
|
|
307
|
+
`);let r=await so(e);if(r!==0)throw KA(e,`${e.cmd} exited with code ${r}.`);C({ok:!0,data:{updated:!0,name:S,from:O,to:A}},t)}function so(e){return new Promise((A,t)=>{let r=no(e.cmd,e.args,{stdio:["ignore",2,2],shell:!1});r.on("error",o=>t(KA(e,`Could not launch ${e.cmd}: ${o.message}`))),r.on("close",o=>A(o??1))})}function KA(e,A){return new u({code:"update_failed",message:A,suggestion:`Run it manually: ${e.display}`,errorType:"internal_error"},d.Generic)}var ao=`
|
|
308
|
+
Examples:
|
|
309
|
+
tiro update # upgrade to the latest published version
|
|
310
|
+
tiro update --check # report current vs latest (JSON for agents/CI)
|
|
311
|
+
tiro update --check --json
|
|
312
|
+
tiro update --dry-run # print the upgrade command without running it
|
|
313
|
+
tiro update --pm pnpm # force a package manager
|
|
314
|
+
|
|
315
|
+
Self-updates by running your global package manager \u2014 auto-detected (npm, pnpm,
|
|
316
|
+
yarn, bun), or forced with --pm. '--check' never installs: it exits 0 and reports
|
|
317
|
+
'updateAvailable', so agents and CI can detect a new version without a TTY banner.
|
|
318
|
+
`;function ZA(e){e.command("update").description("Upgrade tiro to the latest published version (self-update).").option("--check","Report whether a newer version exists; do not install").option("--dry-run","Print the upgrade command without running it").option("--pm <manager>","Force package manager: npm | pnpm | yarn | bun").addHelpText("after",ao).action(async(A,t)=>{let r=t.optsWithGlobals();await YA(A,r)})}import co from"update-notifier";import{readFile as po}from"fs/promises";import{fileURLToPath as uo}from"url";import{dirname as lo,resolve as JA}from"path";var $A=lo(uo(import.meta.url)),mo=[JA($A,"../../package.json"),JA($A,"../../../package.json")],go=1440*60*1e3;async function _A(){if(process.env.NO_UPDATE_NOTIFIER==="1"||process.env.CI||process.stdout.isTTY!==!0)return null;let e=await fo();if(!e)return null;try{return co({pkg:e,updateCheckInterval:go,shouldNotifyInNpmScript:!1})}catch{return null}}function et(e){e&&e.update&&e.notify({isGlobal:!0,defer:!1,message:`Update available {currentVersion} \u2192 {latestVersion}
|
|
319
|
+
Run \`tiro update\` to upgrade
|
|
320
|
+
|
|
321
|
+
Changelog: https://www.npmjs.com/package/{packageName}?activeTab=versions`})}async function fo(){for(let e of mo)try{let A=await po(e,"utf8"),t=JSON.parse(A);if(typeof t.name=="string"&&typeof t.version=="string"&&t.name.length>0&&t.version.length>0)return{name:t.name,version:t.version}}catch{}return null}var wo=`
|
|
2591
322
|
EXAMPLES
|
|
2592
323
|
$ tiro auth login
|
|
2593
324
|
$ tiro notes list --since 7d
|
|
@@ -2595,9 +326,12 @@ EXAMPLES
|
|
|
2595
326
|
$ tiro notes get <guid> --output ./meeting.md --include transcript
|
|
2596
327
|
$ tiro notes transcript <guid> --format md --output ./transcript.md
|
|
2597
328
|
$ tiro notes transcript <guid> --format md --no-timestamps --output ./clean.md
|
|
329
|
+
$ tiro folders list --workspace <guid>
|
|
2598
330
|
$ tiro wiki search "onboarding" --json
|
|
2599
331
|
$ tiro wiki graph <pageGuid> --mode around --radius 2
|
|
2600
332
|
$ tiro mcp install # one-line setup for Claude Code
|
|
333
|
+
$ tiro update --check # is a newer version available?
|
|
334
|
+
$ tiro update # self-update to the latest version
|
|
2601
335
|
|
|
2602
336
|
ENVIRONMENT
|
|
2603
337
|
TIRO_TOKEN Bearer token (overrides keychain \u2014 for CI / agents)
|
|
@@ -2607,60 +341,9 @@ ENVIRONMENT
|
|
|
2607
341
|
|
|
2608
342
|
DOCS
|
|
2609
343
|
https://api-docs.tiro.ooo/cli
|
|
2610
|
-
`;
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
registerNotes(program);
|
|
2617
|
-
registerWiki(program);
|
|
2618
|
-
registerMcp(program);
|
|
2619
|
-
return program;
|
|
2620
|
-
}
|
|
2621
|
-
async function main() {
|
|
2622
|
-
const program = buildProgram();
|
|
2623
|
-
const notifier = await startUpdateCheck();
|
|
2624
|
-
try {
|
|
2625
|
-
await program.parseAsync(process.argv);
|
|
2626
|
-
emitUpdateBanner(notifier);
|
|
2627
|
-
} catch (err) {
|
|
2628
|
-
handleError(err, program);
|
|
2629
|
-
}
|
|
2630
|
-
}
|
|
2631
|
-
function handleError(err, program) {
|
|
2632
|
-
const opts = program.opts();
|
|
2633
|
-
if (err instanceof TiroError) {
|
|
2634
|
-
if (opts.json) {
|
|
2635
|
-
printError(err.toJSON());
|
|
2636
|
-
} else if (!opts.quiet) {
|
|
2637
|
-
process.stderr.write(`${color("\u2717", "red", opts)} ${err.message}
|
|
2638
|
-
`);
|
|
2639
|
-
if (err.suggestion) {
|
|
2640
|
-
process.stderr.write(` ${color("\u2192", "gray", opts)} ${err.suggestion}
|
|
2641
|
-
`);
|
|
2642
|
-
}
|
|
2643
|
-
}
|
|
2644
|
-
process.exit(err.exitCode);
|
|
2645
|
-
}
|
|
2646
|
-
if (err instanceof Error) {
|
|
2647
|
-
if (opts.json) {
|
|
2648
|
-
printError({
|
|
2649
|
-
ok: false,
|
|
2650
|
-
error: { code: "internal_error", message: err.message, errorType: "internal_error" }
|
|
2651
|
-
});
|
|
2652
|
-
} else if (!opts.quiet) {
|
|
2653
|
-
process.stderr.write(`${color("\u2717", "red", opts)} ${err.message}
|
|
2654
|
-
`);
|
|
2655
|
-
}
|
|
2656
|
-
process.exit(ExitCode.Generic);
|
|
2657
|
-
}
|
|
2658
|
-
process.stderr.write(`Unknown error: ${String(err)}
|
|
2659
|
-
`);
|
|
2660
|
-
process.exit(ExitCode.Generic);
|
|
2661
|
-
}
|
|
2662
|
-
main().catch((err) => {
|
|
2663
|
-
process.stderr.write(`Fatal: ${String(err)}
|
|
2664
|
-
`);
|
|
2665
|
-
process.exit(ExitCode.Generic);
|
|
2666
|
-
});
|
|
344
|
+
`;function yo(){let e=new ho;return e.name("tiro").description("Tiro AI notes & transcripts \u2014 agent-first command line").version(O,"-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",wo),e.showHelpAfterError("(run `tiro --help` for available commands)"),tA(e),hA(e),kA(e),xA(e),SA(e),MA(e),ZA(e),e}async function bo(){let e=yo(),A=await _A();try{await e.parseAsync(process.argv),et(A)}catch(t){ko(t,e)}}function ko(e,A){let t=A.opts();e instanceof u&&(t.json?ne(e.toJSON()):t.quiet||(process.stderr.write(`${a("\u2717","red",t)} ${e.message}
|
|
345
|
+
`),e.suggestion&&process.stderr.write(` ${a("\u2192","gray",t)} ${e.suggestion}
|
|
346
|
+
`)),process.exit(e.exitCode)),e instanceof Error&&(t.json?ne({ok:!1,error:{code:"internal_error",message:e.message,errorType:"internal_error"}}):t.quiet||process.stderr.write(`${a("\u2717","red",t)} ${e.message}
|
|
347
|
+
`),process.exit(d.Generic)),process.stderr.write(`Unknown error: ${String(e)}
|
|
348
|
+
`),process.exit(d.Generic)}bo().catch(e=>{process.stderr.write(`Fatal: ${String(e)}
|
|
349
|
+
`),process.exit(d.Generic)});
|