granola-toolkit 0.11.0 → 0.13.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 +1 -0
- package/dist/cli.js +123 -23
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/cli.js
CHANGED
|
@@ -540,13 +540,23 @@ function parseDocument(value) {
|
|
|
540
540
|
}
|
|
541
541
|
//#endregion
|
|
542
542
|
//#region src/client/granola.ts
|
|
543
|
-
const
|
|
544
|
-
const CLIENT_VERSION = "5.354.0";
|
|
543
|
+
const DEFAULT_CLIENT_VERSION = "5.354.0";
|
|
545
544
|
const DOCUMENTS_URL = "https://api.granola.ai/v2/get-documents";
|
|
545
|
+
function resolveClientVersion(value) {
|
|
546
|
+
return value?.trim() || process.env.GRANOLA_CLIENT_VERSION?.trim() || DEFAULT_CLIENT_VERSION;
|
|
547
|
+
}
|
|
546
548
|
var GranolaApiClient = class {
|
|
547
|
-
|
|
549
|
+
clientVersion;
|
|
550
|
+
documentsUrl;
|
|
551
|
+
constructor(httpClient, options = DOCUMENTS_URL) {
|
|
548
552
|
this.httpClient = httpClient;
|
|
549
|
-
|
|
553
|
+
if (typeof options === "string") {
|
|
554
|
+
this.documentsUrl = options;
|
|
555
|
+
this.clientVersion = resolveClientVersion();
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
this.documentsUrl = options.documentsUrl ?? DOCUMENTS_URL;
|
|
559
|
+
this.clientVersion = resolveClientVersion(options.clientVersion);
|
|
550
560
|
}
|
|
551
561
|
async listDocuments(options) {
|
|
552
562
|
const documents = [];
|
|
@@ -559,8 +569,8 @@ var GranolaApiClient = class {
|
|
|
559
569
|
offset
|
|
560
570
|
}, {
|
|
561
571
|
headers: {
|
|
562
|
-
"User-Agent":
|
|
563
|
-
"X-Client-Version":
|
|
572
|
+
"User-Agent": `Granola/${this.clientVersion}`,
|
|
573
|
+
"X-Client-Version": this.clientVersion
|
|
564
574
|
},
|
|
565
575
|
timeoutMs: options.timeoutMs
|
|
566
576
|
});
|
|
@@ -580,35 +590,79 @@ var GranolaApiClient = class {
|
|
|
580
590
|
};
|
|
581
591
|
//#endregion
|
|
582
592
|
//#region src/client/http.ts
|
|
593
|
+
const RETRYABLE_STATUS_CODES = new Set([
|
|
594
|
+
429,
|
|
595
|
+
500,
|
|
596
|
+
502,
|
|
597
|
+
503,
|
|
598
|
+
504
|
|
599
|
+
]);
|
|
600
|
+
function sleep(delayMs) {
|
|
601
|
+
return new Promise((resolve) => {
|
|
602
|
+
setTimeout(resolve, delayMs);
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
function parseRetryAfter(headerValue) {
|
|
606
|
+
if (!headerValue?.trim()) return;
|
|
607
|
+
if (/^\d+$/.test(headerValue.trim())) return Number(headerValue.trim()) * 1e3;
|
|
608
|
+
const retryAt = Date.parse(headerValue);
|
|
609
|
+
if (Number.isNaN(retryAt)) return;
|
|
610
|
+
return Math.max(0, retryAt - Date.now());
|
|
611
|
+
}
|
|
583
612
|
var AuthenticatedHttpClient = class {
|
|
584
613
|
fetchImpl;
|
|
585
614
|
constructor(options) {
|
|
586
615
|
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
587
616
|
this.logger = options.logger;
|
|
617
|
+
this.maxRetries = options.maxRetries ?? 2;
|
|
618
|
+
this.retryBaseDelayMs = options.retryBaseDelayMs ?? 500;
|
|
619
|
+
this.retryMaxDelayMs = options.retryMaxDelayMs ?? 5e3;
|
|
620
|
+
this.sleepImpl = options.sleepImpl ?? sleep;
|
|
588
621
|
this.tokenProvider = options.tokenProvider;
|
|
589
622
|
}
|
|
590
623
|
logger;
|
|
624
|
+
maxRetries;
|
|
625
|
+
retryBaseDelayMs;
|
|
626
|
+
retryMaxDelayMs;
|
|
627
|
+
sleepImpl;
|
|
591
628
|
tokenProvider;
|
|
592
|
-
async
|
|
629
|
+
async retry(options, attempt, reason, response) {
|
|
630
|
+
const retryAfterMs = parseRetryAfter(response?.headers.get("retry-after") ?? null);
|
|
631
|
+
const delayMs = Math.min(retryAfterMs ?? this.retryBaseDelayMs * 2 ** attempt, this.retryMaxDelayMs);
|
|
632
|
+
this.logger?.warn?.(`${reason}; retrying in ${delayMs}ms (${attempt + 1}/${this.maxRetries})`);
|
|
633
|
+
await this.sleepImpl(delayMs);
|
|
634
|
+
return this.request(options, attempt + 1);
|
|
635
|
+
}
|
|
636
|
+
async request(options, attempt = 0) {
|
|
593
637
|
const { retryOnUnauthorized = true, timeoutMs, url } = options;
|
|
594
638
|
const accessToken = await this.tokenProvider.getAccessToken();
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
639
|
+
let response;
|
|
640
|
+
try {
|
|
641
|
+
response = await this.fetchImpl(url, {
|
|
642
|
+
body: options.body,
|
|
643
|
+
headers: {
|
|
644
|
+
...options.headers,
|
|
645
|
+
Authorization: `Bearer ${accessToken}`
|
|
646
|
+
},
|
|
647
|
+
method: options.method ?? "GET",
|
|
648
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
649
|
+
});
|
|
650
|
+
} catch (error) {
|
|
651
|
+
if (attempt < this.maxRetries) {
|
|
652
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
653
|
+
return this.retry(options, attempt, `request failed: ${message}`);
|
|
654
|
+
}
|
|
655
|
+
throw error;
|
|
656
|
+
}
|
|
604
657
|
if (response.status === 401 && retryOnUnauthorized) {
|
|
605
658
|
this.logger?.warn?.("request returned 401; invalidating token provider and retrying once");
|
|
606
659
|
await this.tokenProvider.invalidate();
|
|
607
660
|
return this.request({
|
|
608
661
|
...options,
|
|
609
662
|
retryOnUnauthorized: false
|
|
610
|
-
});
|
|
663
|
+
}, attempt);
|
|
611
664
|
}
|
|
665
|
+
if (RETRYABLE_STATUS_CODES.has(response.status) && attempt < this.maxRetries) return this.retry(options, attempt, `request returned ${response.status} ${response.statusText || ""}`.trim(), response);
|
|
612
666
|
return response;
|
|
613
667
|
}
|
|
614
668
|
async postJson(url, body, options = { timeoutMs: 3e4 }) {
|
|
@@ -885,6 +939,9 @@ function toJson(value) {
|
|
|
885
939
|
function repeatIndent(level) {
|
|
886
940
|
return " ".repeat(level);
|
|
887
941
|
}
|
|
942
|
+
function escapeMarkdownText(text) {
|
|
943
|
+
return text.replace(/\\/g, "\\\\").replace(/([*_`[\]])/g, "\\$1");
|
|
944
|
+
}
|
|
888
945
|
function renderInline(nodes = []) {
|
|
889
946
|
return nodes.map((node) => renderInlineNode(node)).join("");
|
|
890
947
|
}
|
|
@@ -895,6 +952,9 @@ function applyMarks(text, marks = []) {
|
|
|
895
952
|
case "em": return `*${current}*`;
|
|
896
953
|
case "code": return `\`${current}\``;
|
|
897
954
|
case "strike": return `~~${current}~~`;
|
|
955
|
+
case "underline": return `<u>${current}</u>`;
|
|
956
|
+
case "subscript": return `<sub>${current}</sub>`;
|
|
957
|
+
case "superscript": return `<sup>${current}</sup>`;
|
|
898
958
|
case "link": {
|
|
899
959
|
const href = typeof mark.attrs?.href === "string" ? mark.attrs.href : void 0;
|
|
900
960
|
return href ? `[${current}](${href})` : current;
|
|
@@ -905,8 +965,9 @@ function applyMarks(text, marks = []) {
|
|
|
905
965
|
}
|
|
906
966
|
function renderInlineNode(node) {
|
|
907
967
|
switch (node.type) {
|
|
908
|
-
case "text": return applyMarks(node.text ?? "", node.marks);
|
|
968
|
+
case "text": return applyMarks(escapeMarkdownText(node.text ?? ""), node.marks);
|
|
909
969
|
case "hardBreak": return " \n";
|
|
970
|
+
case "mention": return applyMarks(escapeMarkdownText(typeof node.attrs?.label === "string" ? node.attrs.label : typeof node.attrs?.text === "string" ? node.attrs.text : typeof node.attrs?.name === "string" ? node.attrs.name : renderInline(node.content)), node.marks);
|
|
910
971
|
default: return applyMarks(renderInline(node.content), node.marks);
|
|
911
972
|
}
|
|
912
973
|
}
|
|
@@ -914,21 +975,45 @@ function indentLines(value, level) {
|
|
|
914
975
|
const indent = repeatIndent(level);
|
|
915
976
|
return value.split("\n").map((line) => line.length === 0 ? line : `${indent}${line}`).join("\n");
|
|
916
977
|
}
|
|
917
|
-
function renderList(items, ordered, indentLevel) {
|
|
918
|
-
return items.map((item, index) => renderListItem(item, ordered ? `${
|
|
978
|
+
function renderList(items, ordered, indentLevel, start = 1) {
|
|
979
|
+
return items.map((item, index) => renderListItem(item, ordered ? `${start + index}.` : "-", indentLevel)).join("\n");
|
|
919
980
|
}
|
|
920
981
|
function renderListItem(node, marker, indentLevel) {
|
|
921
982
|
const children = node.content ?? [];
|
|
922
983
|
const blockChildren = children.filter((child) => child.type !== "bulletList" && child.type !== "orderedList");
|
|
923
984
|
const nestedLists = children.filter((child) => child.type === "bulletList" || child.type === "orderedList");
|
|
924
985
|
const mainText = blockChildren.map((child) => renderBlock(child, indentLevel + 1)).filter(Boolean).join("\n").trim();
|
|
925
|
-
|
|
986
|
+
const prefix = `${repeatIndent(indentLevel)}${marker} `;
|
|
987
|
+
const continuationIndent = `${repeatIndent(indentLevel)}${" ".repeat(marker.length + 1)}`;
|
|
988
|
+
let output = `${prefix}${mainText.split("\n").map((line, index) => index === 0 ? line : `${continuationIndent}${line}`).join("\n") || ""}`.trimEnd();
|
|
926
989
|
if (nestedLists.length > 0) {
|
|
927
990
|
const nestedText = nestedLists.map((child) => renderBlock(child, indentLevel + 1)).filter(Boolean).map((value) => indentLines(value, 0)).join("\n");
|
|
928
991
|
output = `${output}\n${nestedText}`;
|
|
929
992
|
}
|
|
930
993
|
return output;
|
|
931
994
|
}
|
|
995
|
+
function renderTaskList(items, indentLevel) {
|
|
996
|
+
return items.map((item) => renderTaskItem(item, indentLevel)).join("\n");
|
|
997
|
+
}
|
|
998
|
+
function renderTaskItem(node, indentLevel) {
|
|
999
|
+
return renderListItem(node, node.attrs?.checked === true ? "[x]" : "[ ]", indentLevel);
|
|
1000
|
+
}
|
|
1001
|
+
function renderTableCell(node) {
|
|
1002
|
+
return renderBlocks(node.content ?? [], 0).replace(/\n+/g, " <br> ").replace(/\|/g, "\\|").trim();
|
|
1003
|
+
}
|
|
1004
|
+
function renderTable(node) {
|
|
1005
|
+
const rows = (node.content ?? []).map((row) => (row.content ?? []).map((cell) => renderTableCell(cell))).filter((row) => row.length > 0);
|
|
1006
|
+
if (rows.length === 0) return "";
|
|
1007
|
+
const header = rows[0];
|
|
1008
|
+
const body = rows.slice(1);
|
|
1009
|
+
const separator = header.map(() => "---");
|
|
1010
|
+
const lines = [`| ${header.map((cell) => cell || " ").join(" | ")} |`, `| ${separator.join(" | ")} |`];
|
|
1011
|
+
for (const row of body) {
|
|
1012
|
+
const padded = header.map((_, index) => row[index] ?? " ");
|
|
1013
|
+
lines.push(`| ${padded.join(" | ")} |`);
|
|
1014
|
+
}
|
|
1015
|
+
return lines.join("\n");
|
|
1016
|
+
}
|
|
932
1017
|
function renderBlock(node, indentLevel) {
|
|
933
1018
|
switch (node.type) {
|
|
934
1019
|
case "heading": {
|
|
@@ -937,10 +1022,25 @@ function renderBlock(node, indentLevel) {
|
|
|
937
1022
|
}
|
|
938
1023
|
case "paragraph": return renderInline(node.content).trim();
|
|
939
1024
|
case "bulletList": return renderList(node.content ?? [], false, indentLevel);
|
|
940
|
-
case "orderedList":
|
|
1025
|
+
case "orderedList": {
|
|
1026
|
+
const start = typeof node.attrs?.start === "number" ? node.attrs.start : 1;
|
|
1027
|
+
return renderList(node.content ?? [], true, indentLevel, start);
|
|
1028
|
+
}
|
|
941
1029
|
case "listItem": return renderListItem(node, "-", indentLevel);
|
|
1030
|
+
case "taskList": return renderTaskList(node.content ?? [], indentLevel);
|
|
1031
|
+
case "taskItem": return renderTaskItem(node, indentLevel);
|
|
1032
|
+
case "table": return renderTable(node);
|
|
1033
|
+
case "tableRow": return (node.content ?? []).map((cell) => renderTableCell(cell)).join(" | ");
|
|
1034
|
+
case "tableCell":
|
|
1035
|
+
case "tableHeader": return renderTableCell(node);
|
|
942
1036
|
case "blockquote": return renderBlocks(node.content ?? [], indentLevel).split("\n").map((line) => line ? `> ${line}` : ">").join("\n").trim();
|
|
943
|
-
case "codeBlock":
|
|
1037
|
+
case "codeBlock": {
|
|
1038
|
+
const text = extractPlainText({
|
|
1039
|
+
type: "doc",
|
|
1040
|
+
content: node.content
|
|
1041
|
+
}).trimEnd();
|
|
1042
|
+
return `\`\`\`${typeof node.attrs?.language === "string" ? node.attrs.language.trim() : typeof node.attrs?.params === "string" ? node.attrs.params.trim() : ""}\n${text}\n\`\`\``;
|
|
1043
|
+
}
|
|
944
1044
|
case "horizontalRule": return "---";
|
|
945
1045
|
case "hardBreak": return "";
|
|
946
1046
|
case "text": return renderInlineNode(node);
|