teak-sdk 1.0.88 → 1.0.90

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 CHANGED
@@ -1,30 +1,23 @@
1
1
  # Teak SDK
2
2
 
3
- Typed API client and OAuth discovery for Teak. The source lives in `src/`, and
4
- `bun run build` writes the npm package to `dist/`: one bundle with no runtime
5
- dependencies, so it works without the Teak monorepo.
3
+ Typed API client and OAuth discovery for [Teak](https://teakvault.com). One
4
+ bundle with no runtime dependencies.
5
+
6
+ ```bash
7
+ npm i teak-sdk
8
+ ```
6
9
 
7
10
  ```ts
8
- import { createTeakClient, discoverAuthServer } from "teak-sdk";
11
+ import { createTeakClient, staticTokenProvider } from "teak-sdk";
9
12
 
10
- const auth = await discoverAuthServer("https://teakvault.com/api");
11
13
  const client = createTeakClient({
12
14
  baseUrl: "https://teakvault.com/api",
13
- tokenProvider: { getAccessToken: () => yourStoredAccessToken },
15
+ tokenProvider: staticTokenProvider(process.env.TEAK_API_KEY!),
14
16
  });
15
- ```
16
17
 
17
- Use the discovered endpoints and client ID for your OAuth surface. The WorkOS
18
- Connect helpers (`createPkceChallenge`, `createConnectAuthorizeUrl`,
19
- `requestConnectTokens`, `fetchConnectOwnerId`, `disconnectConnectGrant`) cover
20
- PKCE sign-in, refresh, account binding and disconnect. Refresh
21
- discovery after sign-in or refresh failures. Keep tokens in your platform's secure
22
- storage; this package does not store credentials or open a browser.
23
-
24
- The optional `TokenProvider.onUnauthorized(rejectedToken: string)` callback receives
25
- the token used by a retry-eligible 401 response. Return a new or already-refreshed
26
- token to retry once, or `null` to stop. Check the rejected token against the current
27
- session before refreshing so an older response cannot invalidate newer credentials.
18
+ await client.cards.create({ content: "A note from my script" });
19
+ ```
28
20
 
29
- Install it from npm as `teak-sdk`. Each release shares the lockstep version of
30
- the other Teak products and is published by the `SDK Release` workflow.
21
+ The guide, with token providers and WorkOS Connect sign-in, is at
22
+ [teakvault.com/docs/sdk](https://teakvault.com/docs/sdk). Each release shares
23
+ the lockstep version of the other Teak products.
package/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // ../convex/shared/utils/formatBytes.ts
2
2
  var BYTE_UNITS = ["B", "KB", "MB", "GB"];
3
- var formatBytes = (bytes) => {
3
+ var formatBytes = (bytes, locale) => {
4
4
  if (!(bytes && Number.isFinite(bytes) && bytes > 0)) {
5
5
  return "0 B";
6
6
  }
@@ -11,7 +11,12 @@ var formatBytes = (bytes) => {
11
11
  unit += 1;
12
12
  }
13
13
  const digits = unit > 0 && value < 10 ? 1 : 0;
14
- return `${value.toFixed(digits)} ${BYTE_UNITS[unit]}`;
14
+ const number = locale ? new Intl.NumberFormat(locale, {
15
+ maximumFractionDigits: digits,
16
+ minimumFractionDigits: digits,
17
+ useGrouping: false
18
+ }).format(value) : value.toFixed(digits);
19
+ return `${number} ${BYTE_UNITS[unit]}`;
15
20
  };
16
21
  // ../convex/shared/boundedResponse.ts
17
22
  class ResponseSizeLimitError extends Error {
@@ -69,7 +74,8 @@ var validateOAuthUrl = (raw, local = false) => {
69
74
  throw new Error("Missing OAuth discovery URL");
70
75
  }
71
76
  const url = new URL(raw);
72
- if (url.username || url.password || url.hash || url.protocol !== "https:" && !(local && url.protocol === "http:" && isLoopbackHostname(url.hostname)) || !local && isLoopbackHostname(url.hostname) || privateHost(url.hostname) && !(local && isLoopbackHostname(url.hostname))) {
77
+ const hostname = url.hostname.replace(/\.+$/, "");
78
+ if (url.username || url.password || url.hash || url.protocol !== "https:" && !(local && url.protocol === "http:" && isLoopbackHostname(hostname)) || !local && isLoopbackHostname(hostname) || privateHost(hostname) && !(local && isLoopbackHostname(hostname))) {
73
79
  throw new Error("Unsafe OAuth discovery URL");
74
80
  }
75
81
  return url;
@@ -329,7 +335,6 @@ var fetchConnectOwnerId = async (apiUrl, accessToken, options = {}) => {
329
335
  };
330
336
  // ../convex/shared/markdown.ts
331
337
  var MARKDOWN_CONTENT_MAX_BYTES = 512 * 1024;
332
-
333
338
  // ../files-protocol/src/decisions.ts
334
339
  var CLEF_MODEL_ID = "@cf/cloudflare/clef";
335
340
  var CLEF_FLASH_MODEL_ID = "@cf/cloudflare/clef-flash";
@@ -360,11 +365,49 @@ var FILES_OBJECT_KEY_ROOTS = ["", FILES_DEV_KEY_PREFIX].flatMap((envPrefix) => [
360
365
  // ../files-protocol/src/inspect.ts
361
366
  var FILES_INSPECT_MAX_ARCHIVE_BYTES = 25 * 1024 * 1024;
362
367
  var FILES_INSPECT_MAX_SOURCE_BYTES = 2 * 1024 * 1024;
368
+ // ../files-protocol/src/metadata.ts
369
+ var SHARED_LANGUAGE_RULES = `- In Japanese, Chinese and Korean, each tag is one term with no spaces, such as "デザイン", "设计" or "디자인".
370
+ - Never use "secret" or "hidden", or a translation of either, as a tag.`;
371
+ var CONTENT_LANGUAGE_RULES = `- Write the tags and the summary in the same language as the content. If the content mixes languages, use the main one. If you can't tell the language, use English.
372
+ ${SHARED_LANGUAGE_RULES}
373
+ - Language is the content's main language as a lowercase ISO 639-1 code, such as "en", "ja" or "es".`;
374
+ var IMAGE_LANGUAGE_RULES = `- Write the tags and the summary in the language of the text in the image. If the text mixes languages, use the main one. If the image has no legible text, use English.
375
+ ${SHARED_LANGUAGE_RULES}
376
+ - Language is the main language of the text in the image as a lowercase ISO 639-1 code, such as "en", "ja" or "es", and "en" when it has no legible text.`;
377
+ var TEXT_ANALYSIS_SYSTEM_PROMPT = `You are an expert content analyzer. Generate relevant tags and a concise summary for the given content. /no_think
378
+
379
+ Guidelines:
380
+ - Tags should be 5-6 specific, relevant single words only (no spaces, no hyphens)
381
+ - Summary should be 1-2 sentences that capture the essence
382
+ - Focus on the main topics, themes, and key information
383
+ - Use clear, searchable language
384
+ ${CONTENT_LANGUAGE_RULES}
385
+
386
+ Respond with a single JSON object using exactly this shape and no other keys:
387
+ {"tags": ["word", "word"], "summary": "...", "language": "xx"}`;
388
+ var LINK_ANALYSIS_SYSTEM_PROMPT = `You are an expert web content analyzer. Generate relevant tags and a concise summary for the given web page content. /no_think
389
+
390
+ Guidelines:
391
+ - Tags should be 5-6 single words capturing main topics, categories, and key concepts (no spaces, no hyphens)
392
+ - Include relevant technology, industry, or topic tags where applicable
393
+ - Summary should be 1-2 sentences capturing the essence and value of the content
394
+ - Focus on what makes this link useful and searchable
395
+ - Use clear, specific language that helps with discovery
396
+ - Consider the source, author, and context when available
397
+ ${CONTENT_LANGUAGE_RULES}
398
+
399
+ Respond with a single JSON object using exactly this shape and no other keys:
400
+ {"tags": ["word", "word"], "summary": "...", "language": "xx"}`;
401
+ var ISO_639_1 = new Set(("aa ab ae af ak am an ar as av ay az ba be bg bi bm bn bo br bs ca ce ch " + "co cr cs cu cv cy da de dv dz ee el en eo es et eu fa ff fi fj fo fr fy " + "ga gd gl gn gu gv ha he hi ho hr ht hu hy hz ia id ie ig ii ik io is it " + "iu ja jv ka kg ki kj kk kl km kn ko kr ks ku kv kw ky la lb lg li ln lo " + "lt lu lv mg mh mi mk ml mn mr ms mt my na nb nd ne ng nl nn no nr nv ny " + "oc oj om or os pa pi pl ps pt qu rm rn ro ru rw sa sc sd se sg si sk sl " + "sm sn so sq sr ss st su sv sw ta te tg th ti tk tl tn to tr ts tt tw ty " + "ug uk ur uz ve vi vo wa wo xh yi yo za zh zu").split(" "));
363
402
  // ../files-protocol/src/remoteAsset.ts
364
403
  var FILES_REMOTE_ASSET_MAX_BYTES = {
365
404
  image: 20 * 1024 * 1024,
366
405
  video: 50 * 1024 * 1024
367
406
  };
407
+ var GENERIC_CONTENT_TYPES = new Set([
408
+ "application/octet-stream",
409
+ "binary/octet-stream"
410
+ ]);
368
411
  // ../files-protocol/src/workflows.ts
369
412
  var WORKFLOW_RPC_MAX_TTL_SECONDS = 5 * 60;
370
413
  var WORKFLOW_RPC_MAX_BODY_BYTES = 4 * 1024 * 1024;
@@ -850,6 +893,11 @@ var TAG_ERROR_MESSAGES = {
850
893
  };
851
894
  var HIDDEN_TAG_KEYS = ["secret", "hidden"];
852
895
  var hiddenTagKeys = new Set(HIDDEN_TAG_KEYS);
896
+ var MAX_CARD_TAGS = 100;
897
+ var CARD_TAG_ERROR_MESSAGES = {
898
+ TAG_TOO_LONG: `Each tag must be at most ${MAX_TAG_NAME_LENGTH} characters`,
899
+ TOO_MANY_TAGS: `A card can have at most ${MAX_CARD_TAGS} tags`
900
+ };
853
901
  // ../convex/shared/constants.ts
854
902
  var cardTypes = [
855
903
  "text",
@@ -929,6 +977,7 @@ var ERROR_CODES = [
929
977
  "FILE_TOO_LARGE",
930
978
  "FOLDER_LIMIT",
931
979
  "FORBIDDEN",
980
+ "HIGHLIGHT_LIMIT",
932
981
  "IN_PROGRESS",
933
982
  "INTERNAL_ERROR",
934
983
  "INVALID_API_KEY",
@@ -939,9 +988,12 @@ var ERROR_CODES = [
939
988
  "METHOD_NOT_ALLOWED",
940
989
  "NETWORK_ERROR",
941
990
  "NOT_FOUND",
991
+ "NO_ARTICLE",
942
992
  "NO_CONTENT",
943
993
  "NOT_IN_TRASH",
994
+ "PAGE_SNAPSHOT_TOO_LARGE",
944
995
  "PARSE_ERROR",
996
+ "QUOTE_NOT_FOUND",
945
997
  "RATE_LIMITED",
946
998
  "REQUEST_FAILED",
947
999
  "SERVICE_UNAVAILABLE",
@@ -1058,7 +1110,7 @@ var buildCardsSearchParams = (input) => {
1058
1110
  if (input.mode === "keyword" || input.mode === "semantic") {
1059
1111
  search.set("mode", input.mode);
1060
1112
  }
1061
- if (input.sort === "oldest" || input.sort === "shuffle") {
1113
+ if (input.sort && input.sort !== "newest") {
1062
1114
  search.set("sort", input.sort);
1063
1115
  }
1064
1116
  if (input.sort === "shuffle" && typeof input.shuffleSeed === "number") {
@@ -1073,12 +1125,38 @@ var buildCardsSearchParams = (input) => {
1073
1125
  search.set("limit", String(normalizeLimit(input.limit)));
1074
1126
  return search.toString();
1075
1127
  };
1076
- var parseJson = async (response) => {
1077
- if (response.status === 204) {
1078
- return null;
1128
+ var readStream = async (reader) => {
1129
+ const decoder = new TextDecoder;
1130
+ let text = "";
1131
+ for (;; ) {
1132
+ const { done, value } = await reader.read();
1133
+ if (done) {
1134
+ return text + decoder.decode();
1135
+ }
1136
+ text += decoder.decode(value, { stream: true });
1137
+ }
1138
+ };
1139
+ var readBody = (response, signal) => new Promise((resolve, reject) => {
1140
+ const reader = response.body?.getReader();
1141
+ const onAbort = () => {
1142
+ reject(new TeakApiError("TIMEOUT"));
1143
+ reader?.cancel().catch(() => {
1144
+ return;
1145
+ });
1146
+ };
1147
+ if (signal.aborted) {
1148
+ onAbort();
1149
+ return;
1150
+ }
1151
+ if (!reader) {
1152
+ resolve("");
1153
+ return;
1079
1154
  }
1080
- const text = await response.text();
1081
- if (!text.trim()) {
1155
+ signal.addEventListener("abort", onAbort, { once: true });
1156
+ readStream(reader).then(resolve, () => reject(new TeakApiError(signal.aborted ? "TIMEOUT" : "NETWORK_ERROR"))).finally(() => signal.removeEventListener("abort", onAbort));
1157
+ });
1158
+ var parseJson = (response, text) => {
1159
+ if (response.status === 204 || !text.trim()) {
1082
1160
  return null;
1083
1161
  }
1084
1162
  try {
@@ -1113,6 +1191,18 @@ var asFolders = (value) => {
1113
1191
  }
1114
1192
  return { items: value.items.map(asFolder) };
1115
1193
  };
1194
+ var asHighlight = (value) => {
1195
+ if (!isObject(value) || typeof value.id !== "string" || typeof value.quote !== "string") {
1196
+ throw new TeakApiError("PARSE_ERROR", "Teak returned an unexpected highlight payload.");
1197
+ }
1198
+ return value;
1199
+ };
1200
+ var asHighlights = (value) => {
1201
+ if (!(isObject(value) && Array.isArray(value.items))) {
1202
+ throw new TeakApiError("PARSE_ERROR");
1203
+ }
1204
+ return { items: value.items.map(asHighlight) };
1205
+ };
1116
1206
  var asFolderCards = (value) => {
1117
1207
  if (!(isObject(value) && isStringArray(value.updatedIds) && isStringArray(value.failedIds))) {
1118
1208
  throw new TeakApiError("PARSE_ERROR");
@@ -1160,7 +1250,7 @@ var asSavedPdf = (value) => {
1160
1250
  return { cardId: value.cardId, created: value.created };
1161
1251
  };
1162
1252
  var asCardContent = (value) => {
1163
- if (!isObject(value) || typeof value.cardId !== "string" || typeof value.text !== "string" || typeof value.charCount !== "number" || typeof value.truncated !== "boolean" || !["document", "transcript", "content"].includes(String(value.kind))) {
1253
+ if (!isObject(value) || typeof value.cardId !== "string" || typeof value.text !== "string" || typeof value.charCount !== "number" || typeof value.truncated !== "boolean" || typeof value.kind !== "string") {
1164
1254
  throw new TeakApiError("PARSE_ERROR");
1165
1255
  }
1166
1256
  return value;
@@ -1208,14 +1298,18 @@ var createTeakClient = (options) => {
1208
1298
  headers.set("Content-Type", "application/json");
1209
1299
  }
1210
1300
  let response;
1301
+ let text;
1211
1302
  try {
1212
- response = await fetchImpl(`${baseUrl}${path}`, {
1213
- ...init,
1214
- headers,
1215
- signal: controller.signal
1216
- });
1217
- } catch (error) {
1218
- throw new TeakApiError(error instanceof DOMException && error.name === "AbortError" ? "TIMEOUT" : "NETWORK_ERROR");
1303
+ try {
1304
+ response = await fetchImpl(`${baseUrl}${path}`, {
1305
+ ...init,
1306
+ headers,
1307
+ signal: controller.signal
1308
+ });
1309
+ } catch (error) {
1310
+ throw new TeakApiError(error instanceof DOMException && error.name === "AbortError" ? "TIMEOUT" : "NETWORK_ERROR");
1311
+ }
1312
+ text = response.status === 204 ? "" : await readBody(response, controller.signal);
1219
1313
  } finally {
1220
1314
  clearTimeout(timeout);
1221
1315
  }
@@ -1225,7 +1319,7 @@ var createTeakClient = (options) => {
1225
1319
  return request(path, init, parser, false, next);
1226
1320
  }
1227
1321
  }
1228
- const payload = await parseJson(response);
1322
+ const payload = parseJson(response, text);
1229
1323
  if (!response.ok) {
1230
1324
  const object = isObject(payload) ? payload : {};
1231
1325
  throw new TeakApiError(codeFrom(object.code, response.status === 404 ? "NOT_FOUND" : "REQUEST_FAILED"), String(object.error || response.statusText), {
@@ -1240,6 +1334,7 @@ var createTeakClient = (options) => {
1240
1334
  const idempotency = (requestOptions) => requestOptions?.idempotencyKey ? { "Idempotency-Key": requestOptions.idempotencyKey } : {};
1241
1335
  const cardPath = (id) => `/v1/cards/${encodeURIComponent(id)}`;
1242
1336
  const folderPath = (id) => `/v1/folders/${encodeURIComponent(id)}`;
1337
+ const highlightPath = (id) => `/v1/highlights/${encodeURIComponent(id)}`;
1243
1338
  const putFile = async (uploadUrl, bytes, mimeType) => {
1244
1339
  const headers = new Headers({ "Content-Type": mimeType });
1245
1340
  const byteLength = byteLengthForBody(bytes);
@@ -1275,7 +1370,7 @@ var createTeakClient = (options) => {
1275
1370
  headers: idempotency(requestOptions),
1276
1371
  method: "POST"
1277
1372
  }, asCreate),
1278
- content: (id) => request(`${cardPath(id)}/content`, { method: "GET" }, asCardContent),
1373
+ content: (id, contentOptions) => request(`${cardPath(id)}/content${contentOptions?.kind ? `?kind=${encodeURIComponent(contentOptions.kind)}` : ""}`, { method: "GET" }, asCardContent),
1279
1374
  delete: (id, deleteOptions) => request(`${cardPath(id)}${deleteOptions?.permanent ? "?permanent=true" : ""}`, { method: "DELETE" }, () => null),
1280
1375
  duplicate: (url) => request(`/v1/cards/duplicate?${new URLSearchParams({ url }).toString()}`, { method: "GET" }, (v) => isObject(v) && typeof v.cardId === "string" ? v.cardId : null),
1281
1376
  favorites: (input = {}) => request(`/v1/cards?${qs({ include: "content,metadata", ...input, favorited: true })}`, { method: "GET" }, asPage),
@@ -1328,6 +1423,12 @@ var createTeakClient = (options) => {
1328
1423
  removeCards: (id, cardIds) => request(`${folderPath(id)}/cards`, { body: JSON.stringify({ cardIds }), method: "DELETE" }, asFolderCards),
1329
1424
  update: (id, input) => request(folderPath(id), { body: JSON.stringify(input), method: "PATCH" }, asFolder)
1330
1425
  },
1426
+ highlights: {
1427
+ create: (cardId, input) => request(`${cardPath(cardId)}/highlights`, { body: JSON.stringify(input), method: "POST" }, asHighlight),
1428
+ delete: (id) => request(highlightPath(id), { method: "DELETE" }, () => null),
1429
+ list: (cardId) => request(`${cardPath(cardId)}/highlights`, { method: "GET" }, asHighlights),
1430
+ update: (id, input) => request(highlightPath(id), { body: JSON.stringify(input), method: "PATCH" }, asHighlight)
1431
+ },
1331
1432
  me: () => request("/v1/me", { method: "GET" }, asMe),
1332
1433
  tags: {
1333
1434
  delete: (tag) => request("/v1/tags/delete", { body: JSON.stringify({ tag }), method: "POST" }, asTagOperation),
@@ -1336,6 +1437,9 @@ var createTeakClient = (options) => {
1336
1437
  operation: (id) => request(`/v1/tags/operations/${encodeURIComponent(id)}`, { method: "GET" }, asTagOperation),
1337
1438
  rename: (from, to) => request("/v1/tags/rename", { body: JSON.stringify({ from, to }), method: "POST" }, asTagOperation)
1338
1439
  },
1440
+ pageSnapshots: {
1441
+ createUpload: (input) => request("/v1/page-snapshots/upload", { body: JSON.stringify(input), method: "POST" }, asUpload)
1442
+ },
1339
1443
  uploads: {
1340
1444
  create: (input) => request("/v1/uploads", { body: JSON.stringify(input), method: "POST" }, asUpload),
1341
1445
  putFile
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teak-sdk",
3
- "version": "1.0.88",
3
+ "version": "1.0.90",
4
4
  "description": "Teak API client and OAuth discovery for browser and Node applications.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -20,6 +20,12 @@ export declare const COLOR_HUE_ALIAS_MAP: Record<string, ColorHueBucket>;
20
20
  export declare function normalizeColorHueBucket(value: string): ColorHueBucket | null;
21
21
  export declare function normalizeHueFilters(values?: string[]): ColorHueBucket[];
22
22
  export declare const FREE_TIER_LIMIT = 200;
23
+ /** The most links one paste into the web composer saves (card/pasteLinks.ts). */
24
+ export declare const LINK_PASTE_LIMIT = 100;
25
+ /** The most lines one paste may send; the composer sends no more. */
26
+ export declare const LINK_PASTE_MAX_INPUT = 1000;
27
+ /** Refused while the account's last pasted links are still being saved. */
28
+ export declare const LINK_PASTE_ACTIVE_CODE = "PASTE_ACTIVE";
23
29
  export declare const TRASH_RETENTION_MS: number;
24
30
  export { MAX_FILE_SIZE } from "./fileFormats.js";
25
31
  export declare const MAX_FILES_PER_UPLOAD = 20;
@@ -19,6 +19,19 @@ export declare const isCardHidden: (card: {
19
19
  tags?: readonly string[];
20
20
  }) => boolean;
21
21
  export declare const explainInvalidTagName: (name: string) => string | null;
22
+ export declare const MAX_CARD_TAGS = 100;
23
+ export declare const CARD_TAG_ERROR_MESSAGES: {
24
+ readonly TAG_TOO_LONG: "Each tag must be at most 64 characters";
25
+ readonly TOO_MANY_TAGS: "A card can have at most 100 tags";
26
+ };
27
+ /** Trims each tag, drops empty ones and keeps the first of each case variant. */
28
+ export declare const normalizeTagList: (tags: readonly string[]) => string[];
29
+ /**
30
+ * Why a card can't hold these normalized tags, or null. Tags the card already
31
+ * has (saved before the limits existed) may stay, and a card already past the
32
+ * count may keep or drop tags, never add.
33
+ */
34
+ export declare const explainInvalidCardTags: (tags: readonly string[], previousTags?: readonly string[]) => string | null;
22
35
  export declare const MAX_TAG_KEY_LENGTH = 256;
23
36
  export declare const explainInvalidTagSource: (name: string) => string | null;
24
37
  export declare const tagExistsMessage: (name: string) => string;
@@ -1 +1,5 @@
1
- export declare const formatBytes: (bytes?: number | null) => string;
1
+ /**
2
+ * A byte size such as "2.1 MB". Pass a locale to write the number in that
3
+ * language ("2,1 MB" in German); without one it keeps the "." of before.
4
+ */
5
+ export declare const formatBytes: (bytes?: number | null, locale?: string) => string;
package/types/index.d.ts CHANGED
@@ -8,7 +8,7 @@ import type { FOLDER_COLORS } from "./convex/shared/folders.js";
8
8
  export type { CardType } from "./convex/shared/constants.js";
9
9
  export declare const CARD_TYPES: readonly ["text", "link", "image", "video", "audio", "document", "palette", "quote"];
10
10
  export declare const CARD_SORTS: readonly ["newest", "oldest", "relevance", "shuffle"];
11
- export declare const ERROR_CODES: readonly ["AUTH_REQUIRED", "BAD_REQUEST", "CARD_LIMIT_REACHED", "CONFIG_ERROR", "CONFLICT", "CONTENT_TOO_LARGE", "FILE_TOO_LARGE", "FOLDER_LIMIT", "FORBIDDEN", "IN_PROGRESS", "INTERNAL_ERROR", "INVALID_API_KEY", "INVALID_INPUT", "INVALID_STORAGE_KEY", "INVALID_UTF8", "LIMIT_REACHED", "METHOD_NOT_ALLOWED", "NETWORK_ERROR", "NOT_FOUND", "NO_CONTENT", "NOT_IN_TRASH", "PARSE_ERROR", "RATE_LIMITED", "REQUEST_FAILED", "SERVICE_UNAVAILABLE", "SMART_FOLDER", "TAG_EXISTS", "TAG_OPERATION_RUNNING", "TIMEOUT", "TYPE_MISMATCH", "UNAUTHORIZED", "UNSUPPORTED_TYPE"];
11
+ export declare const ERROR_CODES: readonly ["AUTH_REQUIRED", "BAD_REQUEST", "CARD_LIMIT_REACHED", "CONFIG_ERROR", "CONFLICT", "CONTENT_TOO_LARGE", "FILE_TOO_LARGE", "FOLDER_LIMIT", "FORBIDDEN", "HIGHLIGHT_LIMIT", "IN_PROGRESS", "INTERNAL_ERROR", "INVALID_API_KEY", "INVALID_INPUT", "INVALID_STORAGE_KEY", "INVALID_UTF8", "LIMIT_REACHED", "METHOD_NOT_ALLOWED", "NETWORK_ERROR", "NOT_FOUND", "NO_ARTICLE", "NO_CONTENT", "NOT_IN_TRASH", "PAGE_SNAPSHOT_TOO_LARGE", "PARSE_ERROR", "QUOTE_NOT_FOUND", "RATE_LIMITED", "REQUEST_FAILED", "SERVICE_UNAVAILABLE", "SMART_FOLDER", "TAG_EXISTS", "TAG_OPERATION_RUNNING", "TIMEOUT", "TYPE_MISMATCH", "UNAUTHORIZED", "UNSUPPORTED_TYPE"];
12
12
  export type CardSort = (typeof CARD_SORTS)[number];
13
13
  /**
14
14
  * How a search finds cards: `hybrid` (the API's default) matches words and
@@ -21,13 +21,32 @@ export interface TokenProvider {
21
21
  getAccessToken: () => Promise<string | null> | string | null;
22
22
  onUnauthorized?: (rejectedToken: string) => Promise<string | null> | string | null;
23
23
  }
24
+ /** A link's saved copy of its page. The HTML itself stays in Teak. */
25
+ export interface CardArchive {
26
+ bytes: number;
27
+ capturedAt: number;
28
+ kind: "full" | "text_only";
29
+ }
30
+ /** A link's article, from its saved copy; read it with `cards.content(id, { kind: "article" })`. */
31
+ export interface CardArticle {
32
+ readingMinutes: number;
33
+ wordCount: number;
34
+ }
24
35
  export interface Card {
25
36
  aiSummary: string | null;
26
37
  aiTags: string[];
27
38
  aiTranscript: string | null;
28
39
  appUrl: string;
40
+ /**
41
+ * A link's saved copy of its page: `full`, or `text_only` when the page
42
+ * was too large; null until it is saved and for other cards. Servers that
43
+ * predate saved copies leave it out.
44
+ */
45
+ archive?: CardArchive | null;
29
46
  /** When the card was archived, or null. */
30
47
  archivedAt: number | null;
48
+ /** A link's article; null when it has none or it isn't ready. */
49
+ article?: CardArticle | null;
31
50
  /** Bar heights 0-100: 45 for a grid card, 200 for a player. */
32
51
  audioWaveform: {
33
52
  detail: number[];
@@ -58,6 +77,11 @@ export interface Card {
58
77
  fileUrl: string | null;
59
78
  /** Ids of the folders the card is in. */
60
79
  folderIds: string[];
80
+ /**
81
+ * Passages highlighted in a link's article; 0 for other cards. Servers
82
+ * that predate highlights leave it out.
83
+ */
84
+ highlightCount?: number;
61
85
  id: string;
62
86
  /** Archived cards are hidden from the library but still count. */
63
87
  isArchived: boolean;
@@ -115,6 +139,11 @@ export interface Card {
115
139
  notes: string | null;
116
140
  placeholderUrl: string | null;
117
141
  processingStatus: Record<string, unknown> | null;
142
+ /**
143
+ * When the card was last saved: `createdAt`, or later when the same link
144
+ * was saved again. Lists order by it. Servers that predate it leave it out.
145
+ */
146
+ savedAt?: number;
118
147
  screenshotUrl: string | null;
119
148
  /**
120
149
  * The title of the page in `sourceUrl`. Servers that predate sources
@@ -220,6 +249,28 @@ export interface TagOperation {
220
249
  total: number;
221
250
  updatedAt: number;
222
251
  }
252
+ /** A passage highlighted in a link's article, with an optional note. */
253
+ export interface Highlight {
254
+ /** False until the quote is found in the card's saved article. */
255
+ anchored: boolean;
256
+ cardId: string;
257
+ createdAt: number;
258
+ id: string;
259
+ note: string | null;
260
+ quote: string;
261
+ /** `reader`, `api` or `import`; accept values this SDK doesn't know. */
262
+ source: string;
263
+ updatedAt: number;
264
+ }
265
+ export interface CreateHighlightInput {
266
+ note?: string;
267
+ /** Text just before the quote, to pick one of its repeats. */
268
+ prefix?: string;
269
+ /** Text from the article; whitespace, curly quotes and case needn't match. */
270
+ quote: string;
271
+ /** Text just after the quote, to pick one of its repeats. */
272
+ suffix?: string;
273
+ }
223
274
  export interface FolderCardsResult {
224
275
  failedIds: string[];
225
276
  /** Failed cards that are already in the most folders a card can be in. */
@@ -251,6 +302,12 @@ export interface CreateCardInput {
251
302
  mimeType?: string;
252
303
  /** Free-form notes attached to the card. */
253
304
  notes?: string | null;
305
+ /**
306
+ * The `fileKey` from `pageSnapshots.createUpload`: the page as the browser
307
+ * showed it, which becomes a link's saved copy. Ignored when it isn't one
308
+ * of your snapshot uploads.
309
+ */
310
+ pageSnapshotKey?: string;
254
311
  /**
255
312
  * A free label for what saved the card (extension, CLI, API, and so on),
256
313
  * kept in its metadata. For the page the card came from, use `sourceUrl`.
@@ -290,6 +347,12 @@ export interface Me {
290
347
  cardLimit: number | null;
291
348
  email: string;
292
349
  id: string;
350
+ /**
351
+ * The account's Teak language, chosen in Settings: a BCP 47 code such as
352
+ * `en`, `de` or `pt-BR`. More may be added, so treat an unknown code as
353
+ * English.
354
+ */
355
+ locale: string;
293
356
  name?: string;
294
357
  plan: "free" | "pro";
295
358
  settingsUrl: string;
@@ -325,6 +388,10 @@ export interface CreateUploadInput {
325
388
  mimeType: string;
326
389
  }
327
390
  /** A link image upload: JPEG, PNG, WebP, GIF or AVIF, under 20 MB. */
391
+ export interface CreatePageSnapshotUploadInput {
392
+ /** The gzipped page's exact size; at most 20 MiB. */
393
+ bytes: number;
394
+ }
328
395
  export interface CreateThumbnailUploadInput {
329
396
  fileSize: number;
330
397
  mimeType: string;
@@ -340,7 +407,16 @@ export interface CreateCardResponse {
340
407
  appUrl: string;
341
408
  card?: Card;
342
409
  cardId: string;
410
+ /**
411
+ * True when the link was already in Teak: no card was made, and that card
412
+ * (`cardId`) moved to the top. Tags and notes sent with the save were not
413
+ * applied; update the card to add them.
414
+ */
415
+ resaved?: true;
416
+ /** `status` stays `created` for a re-save too. */
343
417
  status: "created";
418
+ /** True when the re-saved card was archived; it is back in the library. */
419
+ wasArchived?: true;
344
420
  }
345
421
  export interface RefreshCardResponse {
346
422
  cardId: string;
@@ -354,6 +430,8 @@ export interface SavedPdf {
354
430
  /** False when an earlier save already made that card. */
355
431
  created: boolean;
356
432
  }
433
+ /** The kinds of stored text a card can have. */
434
+ export type CardContentKind = "article" | "document" | "transcript" | "content";
357
435
  /** A card's full stored text (GET /v1/cards/:id/content). */
358
436
  export interface CardContent {
359
437
  cardId: string;
@@ -361,9 +439,11 @@ export interface CardContent {
361
439
  charCount: number;
362
440
  /**
363
441
  * document: extracted from the card's file; transcript: its speech;
364
- * content: a text or quote card's written text.
442
+ * content: a text or quote card's written text; article: a link's article
443
+ * as Markdown, with image links valid for 7 days. A kind this SDK doesn't
444
+ * know yet comes through as a string rather than an error.
365
445
  */
366
- kind: "document" | "transcript" | "content";
446
+ kind: CardContentKind | (string & {});
367
447
  text: string;
368
448
  /** The document is longer than Teak keeps. */
369
449
  truncated: boolean;
@@ -414,7 +494,9 @@ export declare const buildCardsSearchParams: (input: {
414
494
  * `is:fav`, `is:archived`, `site:github.com`, `folder:"Reading list"`,
415
495
  * `price:<100` (also `>`, `<=`, `>=` and `=`), `text:"total due"` (only
416
496
  * the words Teak read in images, video thumbnails, link previews and scanned
417
- * PDFs) and `"quoted phrases"`.
497
+ * PDFs) and `"quoted phrases"`. A `-` leaves cards out (`design -figma`,
498
+ * `-tag:old`), uppercase `OR` matches either side and parentheses group:
499
+ * `(site:github.com OR site:gitlab.com) -is:archived`.
418
500
  */
419
501
  query?: string;
420
502
  /** Required with `sort: "shuffle"`: the same seed gives the same order. */
@@ -444,9 +526,13 @@ export declare const createTeakClient: (options: {
444
526
  create: (input: CreateCardInput, requestOptions?: RequestOptions) => Promise<CreateCardResponse>;
445
527
  /**
446
528
  * The card's full stored text: document text, transcript, or a text
447
- * card's content. Throws NO_CONTENT when the card has none.
529
+ * card's content. `{ kind: "article" }` reads a link's article as
530
+ * Markdown; `kind` picks one kind when a card has several. Throws
531
+ * NO_CONTENT when the card has none (of that kind).
448
532
  */
449
- content: (id: string) => Promise<CardContent>;
533
+ content: (id: string, contentOptions?: {
534
+ kind?: CardContentKind;
535
+ }) => Promise<CardContent>;
450
536
  /** Moves the card to Trash, or deletes it for good with `permanent`. */
451
537
  delete: (id: string, deleteOptions?: {
452
538
  permanent?: boolean;
@@ -539,6 +625,25 @@ export declare const createTeakClient: (options: {
539
625
  query?: string;
540
626
  }) => Promise<Folder>;
541
627
  };
628
+ highlights: {
629
+ /**
630
+ * Highlights a passage of a link's saved article, found by its text:
631
+ * the first match unless `prefix` or `suffix` points at another. An
632
+ * overlapping highlight merges with it. 409 NO_ARTICLE without a saved
633
+ * article, 422 QUOTE_NOT_FOUND, 409 HIGHLIGHT_LIMIT past 200.
634
+ */
635
+ create: (cardId: string, input: CreateHighlightInput) => Promise<Highlight>;
636
+ /** Removes a highlight and its note. */
637
+ delete: (id: string) => Promise<null>;
638
+ /** A card's highlights, in the order they appear in its article. */
639
+ list: (cardId: string) => Promise<{
640
+ items: Highlight[];
641
+ }>;
642
+ /** Writes or changes a highlight's note; `note: null` clears it. */
643
+ update: (id: string, input: {
644
+ note: string | null;
645
+ }) => Promise<Highlight>;
646
+ };
542
647
  me: () => Promise<Me>;
543
648
  tags: {
544
649
  /**
@@ -556,6 +661,14 @@ export declare const createTeakClient: (options: {
556
661
  /** Renames a tag on every card; it finishes in the background. */
557
662
  rename: (from: string, to: string) => Promise<TagOperation>;
558
663
  };
664
+ pageSnapshots: {
665
+ /**
666
+ * A signed URL for the page a browser extension saw, gzipped: `PUT`
667
+ * it with `Content-Type: application/gzip`, then pass the `fileKey`
668
+ * to `cards.create` as `pageSnapshotKey`.
669
+ */
670
+ createUpload: (input: CreatePageSnapshotUploadInput) => Promise<CreateUploadResponse>;
671
+ };
559
672
  uploads: {
560
673
  create: (input: CreateUploadInput) => Promise<CreateUploadResponse>;
561
674
  putFile: (uploadUrl: string, bytes: BodyInit, mimeType: string) => Promise<{