@theplato/tiro-cli 0.4.1 β 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -1
- package/dist/bin/tiro.js +582 -13
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -46,6 +46,7 @@ The CLI is **not a replacement for MCP**. It's the same data through a different
|
|
|
46
46
|
- π **OAuth Authorization Code + PKCE** β no copy-paste tokens, no secrets in shell history. Tokens live in OS Keychain.
|
|
47
47
|
- π **`--output` writes to disk** β stdout becomes a single line of metadata. Agents stay context-light.
|
|
48
48
|
- πͺ **Same JSON shape as MCP** β `tiro notes transcript --format json` mirrors `get_note_transcript` exactly. Switch surfaces without changing parsers.
|
|
49
|
+
- πΈοΈ **Workspace wiki** β `tiro wiki search / page / mentions / graph` read the auto-extracted knowledge graph; `--workspace` targets a specific workspace.
|
|
49
50
|
- π§΅ **NDJSON streams by default** β pipe to `jq`, `head`, `xargs`. No buffer-in-memory surprises.
|
|
50
51
|
- π€ **Agent-aware** β TTY detection auto-selects pretty vs JSON. `error.suggestion` field for auto-recovery.
|
|
51
52
|
- π« **No voice in v1** β intentionally matches MCP feature parity (audio uploads belong elsewhere).
|
|
@@ -150,6 +151,28 @@ header instead of being attached to every speaker line. Use
|
|
|
150
151
|
|
|
151
152
|
`--format json` returns the exact shape MCP's `get_note_transcript` emits (`{noteGuid, title, participants, createdAt, recordingDurationSeconds, paragraphs[]}` with each paragraph carrying `segments[]` of `{content, speaker:{label,name}|null}`).
|
|
152
153
|
|
|
154
|
+
### Explore the workspace wiki
|
|
155
|
+
|
|
156
|
+
The wiki is Tiro's auto-extracted knowledge graph (entities, concepts, and their
|
|
157
|
+
links) over your notes. These commands are **read-only** and mirror the MCP wiki
|
|
158
|
+
tools. Wiki is a paid, opt-in feature β calls against an ineligible or
|
|
159
|
+
not-activated workspace return the backend's upgrade message and a non-zero exit.
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
tiro wiki workspaces # which workspaces? (guid + wiki on/off)
|
|
163
|
+
tiro wiki search "Ontology" --workspace <guid> # find pages by keyword
|
|
164
|
+
tiro wiki page <pageGuid> --workspace <guid> # body + mentions + links
|
|
165
|
+
tiro wiki mentions <pageGuid> --workspace <guid> # where the page is grounded in notes
|
|
166
|
+
tiro wiki graph <pageGuid> --mode around --workspace <guid> # neighborhood graph
|
|
167
|
+
tiro wiki graph --mode seed --type CONCEPT --workspace <guid> # overview graph (no pageGuid)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
`--workspace` is optional β omit it to use the default workspace for your API
|
|
171
|
+
key. If your account belongs to multiple workspaces, run `tiro wiki workspaces`
|
|
172
|
+
first and pass the `guid` of the one whose wiki is enabled. `graph` results are
|
|
173
|
+
capped (default 50 nodes); a `truncated: true` flag signals you to narrow the
|
|
174
|
+
query.
|
|
175
|
+
|
|
153
176
|
### Connect to Claude Code (MCP)
|
|
154
177
|
```bash
|
|
155
178
|
tiro mcp install
|
|
@@ -325,7 +348,7 @@ See [`SPEC.md`](./SPEC.md) for the full v1 design and [`CHANGELOG.md`](./CHANGEL
|
|
|
325
348
|
|
|
326
349
|
## Contributing
|
|
327
350
|
|
|
328
|
-
This repo is currently private alpha. Feedback and bug reports
|
|
351
|
+
This repo is currently private alpha. Feedback and bug reports can be sent to [yeoul@theplato.io](mailto:yeoul@theplato.io); once we open up, this section will list public contribution guidelines.
|
|
329
352
|
|
|
330
353
|
---
|
|
331
354
|
|
package/dist/bin/tiro.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/bin/tiro.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command19 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/lib/version.ts
|
|
7
7
|
import { readFileSync } from "fs";
|
|
@@ -43,6 +43,7 @@ var TiroError = class extends Error {
|
|
|
43
43
|
errorType;
|
|
44
44
|
httpStatus;
|
|
45
45
|
requestId;
|
|
46
|
+
details;
|
|
46
47
|
exitCode;
|
|
47
48
|
constructor(payload, exitCode = ExitCode.Generic) {
|
|
48
49
|
super(payload.message);
|
|
@@ -52,6 +53,7 @@ var TiroError = class extends Error {
|
|
|
52
53
|
this.errorType = payload.errorType;
|
|
53
54
|
this.httpStatus = payload.httpStatus;
|
|
54
55
|
this.requestId = payload.requestId;
|
|
56
|
+
this.details = payload.details;
|
|
55
57
|
this.exitCode = exitCode;
|
|
56
58
|
}
|
|
57
59
|
toJSON() {
|
|
@@ -63,7 +65,8 @@ var TiroError = class extends Error {
|
|
|
63
65
|
...this.suggestion !== void 0 && { suggestion: this.suggestion },
|
|
64
66
|
...this.errorType !== void 0 && { errorType: this.errorType },
|
|
65
67
|
...this.httpStatus !== void 0 && { httpStatus: this.httpStatus },
|
|
66
|
-
...this.requestId !== void 0 && { requestId: this.requestId }
|
|
68
|
+
...this.requestId !== void 0 && { requestId: this.requestId },
|
|
69
|
+
...this.details !== void 0 && { details: this.details }
|
|
67
70
|
}
|
|
68
71
|
};
|
|
69
72
|
}
|
|
@@ -461,6 +464,7 @@ var config = new Conf({
|
|
|
461
464
|
oauthClientId: null,
|
|
462
465
|
oauthClientIdRegisteredAt: null,
|
|
463
466
|
oauthClientHostname: null,
|
|
467
|
+
oauthClientRedirectUri: null,
|
|
464
468
|
defaultOutputDir: null
|
|
465
469
|
}
|
|
466
470
|
});
|
|
@@ -518,25 +522,40 @@ function getHostname(override) {
|
|
|
518
522
|
if (env) return validateHostname(env);
|
|
519
523
|
return validateHostname(config.get("hostname"));
|
|
520
524
|
}
|
|
521
|
-
function getOauthClientId(hostname) {
|
|
525
|
+
function getOauthClientId(hostname, currentRedirectUri) {
|
|
522
526
|
const id = config.get("oauthClientId");
|
|
523
527
|
const registeredAt = config.get("oauthClientIdRegisteredAt");
|
|
524
528
|
const cachedHostname = config.get("oauthClientHostname");
|
|
529
|
+
const cachedRedirectUri = config.get("oauthClientRedirectUri");
|
|
525
530
|
if (!id || !registeredAt) return null;
|
|
526
531
|
if (cachedHostname !== hostname) return null;
|
|
532
|
+
if (!cachedRedirectUri) return null;
|
|
533
|
+
if (!sameRedirectTarget(cachedRedirectUri, currentRedirectUri)) return null;
|
|
527
534
|
const ttlMs = 29 * 24 * 60 * 60 * 1e3;
|
|
528
535
|
if (Date.now() - registeredAt > ttlMs) return null;
|
|
529
536
|
return id;
|
|
530
537
|
}
|
|
531
|
-
function setOauthClientId(clientId, hostname) {
|
|
538
|
+
function setOauthClientId(clientId, hostname, redirectUri) {
|
|
532
539
|
config.set("oauthClientId", clientId);
|
|
533
540
|
config.set("oauthClientIdRegisteredAt", Date.now());
|
|
534
541
|
config.set("oauthClientHostname", hostname);
|
|
542
|
+
config.set("oauthClientRedirectUri", redirectUri);
|
|
535
543
|
}
|
|
536
544
|
function clearOauthClientId() {
|
|
537
545
|
config.set("oauthClientId", null);
|
|
538
546
|
config.set("oauthClientIdRegisteredAt", null);
|
|
539
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;
|
|
540
559
|
}
|
|
541
560
|
function stripTrailingSlash(s) {
|
|
542
561
|
return s.endsWith("/") ? s.slice(0, -1) : s;
|
|
@@ -617,7 +636,7 @@ ${authorizeUrl}`);
|
|
|
617
636
|
}
|
|
618
637
|
}
|
|
619
638
|
async function ensureOauthClient(hostname, redirectUri) {
|
|
620
|
-
const cached = getOauthClientId(hostname);
|
|
639
|
+
const cached = getOauthClientId(hostname, redirectUri);
|
|
621
640
|
if (cached) return cached;
|
|
622
641
|
const url = `${hostname}/v1/mcp/oauth/register`;
|
|
623
642
|
let res;
|
|
@@ -670,7 +689,7 @@ async function ensureOauthClient(hostname, redirectUri) {
|
|
|
670
689
|
ExitCode.Generic
|
|
671
690
|
);
|
|
672
691
|
}
|
|
673
|
-
setOauthClientId(parsed.data.client_id, hostname);
|
|
692
|
+
setOauthClientId(parsed.data.client_id, hostname, redirectUri);
|
|
674
693
|
return parsed.data.client_id;
|
|
675
694
|
}
|
|
676
695
|
function buildAuthorizeUrl(input) {
|
|
@@ -971,6 +990,116 @@ var ApiErrorSchema = z2.object({
|
|
|
971
990
|
detail: z2.string().nullable().optional()
|
|
972
991
|
})
|
|
973
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();
|
|
974
1103
|
|
|
975
1104
|
// src/lib/api/client.ts
|
|
976
1105
|
var TiroApiClient = class {
|
|
@@ -1062,6 +1191,10 @@ var TiroApiClient = class {
|
|
|
1062
1191
|
async function mapHttpError(res, method, path) {
|
|
1063
1192
|
const requestId = res.headers.get("x-request-id") ?? void 0;
|
|
1064
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
|
+
}
|
|
1065
1198
|
const apiError = await tryParseApiError(res);
|
|
1066
1199
|
if (apiError) {
|
|
1067
1200
|
return new TiroError(
|
|
@@ -1092,6 +1225,7 @@ async function mapHttpError(res, method, path) {
|
|
|
1092
1225
|
function httpStatusToErrorType(status) {
|
|
1093
1226
|
if (status === 400) return "bad_request";
|
|
1094
1227
|
if (status === 401) return "unauthorized";
|
|
1228
|
+
if (status === 402) return "payment_required";
|
|
1095
1229
|
if (status === 403) return "forbidden";
|
|
1096
1230
|
if (status === 404) return "not_found";
|
|
1097
1231
|
if (status === 409) return "conflict";
|
|
@@ -1109,6 +1243,31 @@ async function tryParseApiError(res) {
|
|
|
1109
1243
|
return null;
|
|
1110
1244
|
}
|
|
1111
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
|
+
}
|
|
1112
1271
|
function buildApiUrl(hostname, path, params) {
|
|
1113
1272
|
if (!path.startsWith("/") || path.startsWith("//") || path.startsWith("/\\")) {
|
|
1114
1273
|
throw new TiroError(
|
|
@@ -1249,7 +1408,7 @@ function clampLimit(raw) {
|
|
|
1249
1408
|
if (!Number.isFinite(n) || n <= 0) return DEFAULT_PAGE_SIZE;
|
|
1250
1409
|
return Math.min(n, MAX_PAGE_SIZE);
|
|
1251
1410
|
}
|
|
1252
|
-
function printPretty(notes,
|
|
1411
|
+
function printPretty(notes, nextCursor2, opts) {
|
|
1253
1412
|
if (notes.length === 0) {
|
|
1254
1413
|
process.stdout.write(`${color("(no notes)", "gray", opts)}
|
|
1255
1414
|
`);
|
|
@@ -1265,10 +1424,10 @@ function printPretty(notes, nextCursor, opts) {
|
|
|
1265
1424
|
`
|
|
1266
1425
|
);
|
|
1267
1426
|
}
|
|
1268
|
-
if (
|
|
1427
|
+
if (nextCursor2) {
|
|
1269
1428
|
process.stdout.write(
|
|
1270
1429
|
`${color(`
|
|
1271
|
-
next: --cursor ${
|
|
1430
|
+
next: --cursor ${nextCursor2}`, "gray", opts)}
|
|
1272
1431
|
`
|
|
1273
1432
|
);
|
|
1274
1433
|
}
|
|
@@ -1397,7 +1556,7 @@ function clampLimit2(raw) {
|
|
|
1397
1556
|
if (!Number.isFinite(n) || n <= 0) return DEFAULT_PAGE_SIZE2;
|
|
1398
1557
|
return Math.min(n, MAX_PAGE_SIZE2);
|
|
1399
1558
|
}
|
|
1400
|
-
function printPretty2(notes,
|
|
1559
|
+
function printPretty2(notes, nextCursor2, opts) {
|
|
1401
1560
|
if (notes.length === 0) {
|
|
1402
1561
|
process.stdout.write(`${color("(no matches)", "gray", opts)}
|
|
1403
1562
|
`);
|
|
@@ -1410,10 +1569,10 @@ function printPretty2(notes, nextCursor, opts) {
|
|
|
1410
1569
|
`
|
|
1411
1570
|
);
|
|
1412
1571
|
}
|
|
1413
|
-
if (
|
|
1572
|
+
if (nextCursor2) {
|
|
1414
1573
|
process.stdout.write(
|
|
1415
1574
|
`${color(`
|
|
1416
|
-
next: --cursor ${
|
|
1575
|
+
next: --cursor ${nextCursor2}`, "gray", opts)}
|
|
1417
1576
|
`
|
|
1418
1577
|
);
|
|
1419
1578
|
}
|
|
@@ -1907,6 +2066,413 @@ function registerNotes(program) {
|
|
|
1907
2066
|
registerNotesTranscript(notes);
|
|
1908
2067
|
}
|
|
1909
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 = `
|
|
2093
|
+
Examples:
|
|
2094
|
+
tiro wiki search "onboarding"
|
|
2095
|
+
tiro wiki search "payment gateway" --size 50 --json
|
|
2096
|
+
tiro wiki search "billing" --workspace <guid>
|
|
2097
|
+
|
|
2098
|
+
The workspace defaults to the one implicit in your API key. Pass --workspace
|
|
2099
|
+
to target a specific workspace (get guids from 'tiro wiki workspaces').
|
|
2100
|
+
Results are ranked wiki pages (pageGuid, name, type, relevance). Pass a
|
|
2101
|
+
pageGuid to 'tiro wiki page', 'tiro wiki mentions', or 'tiro wiki graph'.
|
|
2102
|
+
`;
|
|
2103
|
+
function registerWikiSearch(parent) {
|
|
2104
|
+
parent.command("search <query>").description("Search the workspace wiki for pages matching a keyword.").option("--size <n>", `Max results (default: backend default, max ${MAX_SIZE})`).option("--workspace <guid>", "Target a specific workspace (from `tiro wiki workspaces`); defaults to your default workspace").addHelpText("after", HELP_AFTER3).action(async (query, opts, cmd) => {
|
|
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 = `
|
|
2164
|
+
Examples:
|
|
2165
|
+
tiro wiki page <pageGuid>
|
|
2166
|
+
tiro wiki page <pageGuid> --json
|
|
2167
|
+
tiro wiki page <pageGuid> --workspace <guid>
|
|
2168
|
+
|
|
2169
|
+
Returns the page body (per-user description) plus metadata, mentions,
|
|
2170
|
+
aliases, and links. The workspace defaults to the one implicit in your API
|
|
2171
|
+
key; pass --workspace to target a specific workspace (from 'tiro wiki workspaces').
|
|
2172
|
+
`;
|
|
2173
|
+
function registerWikiPage(parent) {
|
|
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)}
|
|
2197
|
+
|
|
2198
|
+
`);
|
|
2199
|
+
if (page.description) {
|
|
2200
|
+
w(`${page.description}
|
|
2201
|
+
|
|
2202
|
+
`);
|
|
2203
|
+
} else {
|
|
2204
|
+
const status = page.descriptionStatus ?? "none";
|
|
2205
|
+
w(`${color(`(no description \u2014 status: ${status})`, "gray", opts)}
|
|
2206
|
+
|
|
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 = `
|
|
2219
|
+
Examples:
|
|
2220
|
+
tiro wiki mentions <pageGuid>
|
|
2221
|
+
tiro wiki mentions <pageGuid> --limit 100 --json
|
|
2222
|
+
tiro wiki mentions <pageGuid> --workspace <guid>
|
|
2223
|
+
|
|
2224
|
+
Lists the note paragraphs that reference a wiki page. In JSON mode a trailing
|
|
2225
|
+
{_cursor: ...} object is emitted when more pages are available; pass the
|
|
2226
|
+
cursor token back via --cursor to fetch the next page. The workspace defaults
|
|
2227
|
+
to the one implicit in your API key; pass --workspace to target a specific
|
|
2228
|
+
workspace (from 'tiro wiki workspaces').
|
|
2229
|
+
`;
|
|
2230
|
+
function registerWikiMentions(parent) {
|
|
2231
|
+
parent.command("mentions <pageGuid>").description("List the note mentions that reference a single wiki page.").option("--limit <n>", `Max mentions per page (max ${MAX_LIMIT})`).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", HELP_AFTER5).action(async (pageGuid, opts, cmd) => {
|
|
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 = `
|
|
2289
|
+
Modes (--mode, default: around):
|
|
2290
|
+
around Neighborhood around <pageGuid> (--radius hops, default 2)
|
|
2291
|
+
expand Outward expansion from <pageGuid> (--depth hops, default 1)
|
|
2292
|
+
links Links among <pageGuid> + each --page <guid> (repeatable)
|
|
2293
|
+
seed Overview seed graph (no <pageGuid> required; --query, --type, --since)
|
|
2294
|
+
|
|
2295
|
+
Examples:
|
|
2296
|
+
tiro wiki graph <pageGuid> # around, radius 2
|
|
2297
|
+
tiro wiki graph <pageGuid> --mode expand --depth 2
|
|
2298
|
+
tiro wiki graph <pageGuid> --mode links --page <g2> --page <g3>
|
|
2299
|
+
tiro wiki graph --mode seed --query "billing"
|
|
2300
|
+
tiro wiki graph <pageGuid> --workspace <guid>
|
|
2301
|
+
|
|
2302
|
+
Returns a node+edge slice of the workspace wiki graph. The workspace defaults
|
|
2303
|
+
to the one implicit in your API key; pass --workspace to target a specific
|
|
2304
|
+
workspace (from 'tiro wiki workspaces').
|
|
2305
|
+
`;
|
|
2306
|
+
function registerWikiGraph(parent) {
|
|
2307
|
+
parent.command("graph [pageGuid]").description("Get a node+edge slice of the wiki link graph around a page.").option("--mode <mode>", `Graph mode: ${MODES.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)", collect, []).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", HELP_AFTER6).action(async (pageGuid, opts, cmd) => {
|
|
2308
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2309
|
+
const mode = parseMode(opts.mode);
|
|
2310
|
+
if ((mode === "expand" || mode === "around") && !pageGuid) {
|
|
2311
|
+
throw new TiroError(
|
|
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 = `
|
|
2429
|
+
Examples:
|
|
2430
|
+
tiro wiki workspaces
|
|
2431
|
+
tiro wiki workspaces --json
|
|
2432
|
+
|
|
2433
|
+
Lists all workspaces you are a member of. Use the guid with --workspace on
|
|
2434
|
+
any other wiki command to target a non-default workspace.
|
|
2435
|
+
`;
|
|
2436
|
+
function registerWikiWorkspaces(parent) {
|
|
2437
|
+
parent.command("workspaces").description("List all workspaces you are a member of (use guid with --workspace).").addHelpText("after", HELP_AFTER7).action(async (_opts, cmd) => {
|
|
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
|
+
|
|
1910
2476
|
// src/commands/mcp/index.ts
|
|
1911
2477
|
import "commander";
|
|
1912
2478
|
|
|
@@ -2029,6 +2595,8 @@ EXAMPLES
|
|
|
2029
2595
|
$ tiro notes get <guid> --output ./meeting.md --include transcript
|
|
2030
2596
|
$ tiro notes transcript <guid> --format md --output ./transcript.md
|
|
2031
2597
|
$ tiro notes transcript <guid> --format md --no-timestamps --output ./clean.md
|
|
2598
|
+
$ tiro wiki search "onboarding" --json
|
|
2599
|
+
$ tiro wiki graph <pageGuid> --mode around --radius 2
|
|
2032
2600
|
$ tiro mcp install # one-line setup for Claude Code
|
|
2033
2601
|
|
|
2034
2602
|
ENVIRONMENT
|
|
@@ -2041,11 +2609,12 @@ DOCS
|
|
|
2041
2609
|
https://api-docs.tiro.ooo/cli
|
|
2042
2610
|
`;
|
|
2043
2611
|
function buildProgram() {
|
|
2044
|
-
const program = new
|
|
2612
|
+
const program = new Command19();
|
|
2045
2613
|
program.name("tiro").description("Tiro AI notes & transcripts \u2014 agent-first command line").version(VERSION, "-v, --version", "Print version").option("--hostname <url>", "API base URL (default: https://api.tiro.ooo)").option("--json", "Force JSON output").option("--pretty", "Force pretty (human) output").option("--quiet", "Suppress non-error output").option("--verbose", "Verbose logging to stderr").option("--no-color", "Disable ANSI colors").addHelpText("after", EXAMPLES);
|
|
2046
2614
|
program.showHelpAfterError("(run `tiro --help` for available commands)");
|
|
2047
2615
|
registerAuth(program);
|
|
2048
2616
|
registerNotes(program);
|
|
2617
|
+
registerWiki(program);
|
|
2049
2618
|
registerMcp(program);
|
|
2050
2619
|
return program;
|
|
2051
2620
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theplato/tiro-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Tiro AI notes & transcripts β agent-first command line",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"typecheck": "tsc --noEmit",
|
|
23
23
|
"test": "vitest run",
|
|
24
24
|
"test:watch": "vitest",
|
|
25
|
-
"test:security": "vitest run --testNamePattern \"\\\\((C|H|M)[0-9]+\\\\)\"",
|
|
25
|
+
"test:security": "vitest run --testNamePattern \"\\\\((C|H|M|X)[0-9]+\\\\)\"",
|
|
26
26
|
"prepublishOnly": "npm run typecheck && npm test && npm run build"
|
|
27
27
|
},
|
|
28
28
|
"keywords": [
|