teak-sdk 1.0.88 → 1.0.89

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/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;
@@ -360,11 +366,49 @@ var FILES_OBJECT_KEY_ROOTS = ["", FILES_DEV_KEY_PREFIX].flatMap((envPrefix) => [
360
366
  // ../files-protocol/src/inspect.ts
361
367
  var FILES_INSPECT_MAX_ARCHIVE_BYTES = 25 * 1024 * 1024;
362
368
  var FILES_INSPECT_MAX_SOURCE_BYTES = 2 * 1024 * 1024;
369
+ // ../files-protocol/src/metadata.ts
370
+ var SHARED_LANGUAGE_RULES = `- In Japanese, Chinese and Korean, each tag is one term with no spaces, such as "デザイン", "设计" or "디자인".
371
+ - Never use "secret" or "hidden", or a translation of either, as a tag.`;
372
+ 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.
373
+ ${SHARED_LANGUAGE_RULES}
374
+ - Language is the content's main language as a lowercase ISO 639-1 code, such as "en", "ja" or "es".`;
375
+ 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.
376
+ ${SHARED_LANGUAGE_RULES}
377
+ - 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.`;
378
+ var TEXT_ANALYSIS_SYSTEM_PROMPT = `You are an expert content analyzer. Generate relevant tags and a concise summary for the given content. /no_think
379
+
380
+ Guidelines:
381
+ - Tags should be 5-6 specific, relevant single words only (no spaces, no hyphens)
382
+ - Summary should be 1-2 sentences that capture the essence
383
+ - Focus on the main topics, themes, and key information
384
+ - Use clear, searchable language
385
+ ${CONTENT_LANGUAGE_RULES}
386
+
387
+ Respond with a single JSON object using exactly this shape and no other keys:
388
+ {"tags": ["word", "word"], "summary": "...", "language": "xx"}`;
389
+ 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
390
+
391
+ Guidelines:
392
+ - Tags should be 5-6 single words capturing main topics, categories, and key concepts (no spaces, no hyphens)
393
+ - Include relevant technology, industry, or topic tags where applicable
394
+ - Summary should be 1-2 sentences capturing the essence and value of the content
395
+ - Focus on what makes this link useful and searchable
396
+ - Use clear, specific language that helps with discovery
397
+ - Consider the source, author, and context when available
398
+ ${CONTENT_LANGUAGE_RULES}
399
+
400
+ Respond with a single JSON object using exactly this shape and no other keys:
401
+ {"tags": ["word", "word"], "summary": "...", "language": "xx"}`;
402
+ 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
403
  // ../files-protocol/src/remoteAsset.ts
364
404
  var FILES_REMOTE_ASSET_MAX_BYTES = {
365
405
  image: 20 * 1024 * 1024,
366
406
  video: 50 * 1024 * 1024
367
407
  };
408
+ var GENERIC_CONTENT_TYPES = new Set([
409
+ "application/octet-stream",
410
+ "binary/octet-stream"
411
+ ]);
368
412
  // ../files-protocol/src/workflows.ts
369
413
  var WORKFLOW_RPC_MAX_TTL_SECONDS = 5 * 60;
370
414
  var WORKFLOW_RPC_MAX_BODY_BYTES = 4 * 1024 * 1024;
@@ -850,6 +894,11 @@ var TAG_ERROR_MESSAGES = {
850
894
  };
851
895
  var HIDDEN_TAG_KEYS = ["secret", "hidden"];
852
896
  var hiddenTagKeys = new Set(HIDDEN_TAG_KEYS);
897
+ var MAX_CARD_TAGS = 100;
898
+ var CARD_TAG_ERROR_MESSAGES = {
899
+ TAG_TOO_LONG: `Each tag must be at most ${MAX_TAG_NAME_LENGTH} characters`,
900
+ TOO_MANY_TAGS: `A card can have at most ${MAX_CARD_TAGS} tags`
901
+ };
853
902
  // ../convex/shared/constants.ts
854
903
  var cardTypes = [
855
904
  "text",
@@ -1058,7 +1107,7 @@ var buildCardsSearchParams = (input) => {
1058
1107
  if (input.mode === "keyword" || input.mode === "semantic") {
1059
1108
  search.set("mode", input.mode);
1060
1109
  }
1061
- if (input.sort === "oldest" || input.sort === "shuffle") {
1110
+ if (input.sort && input.sort !== "newest") {
1062
1111
  search.set("sort", input.sort);
1063
1112
  }
1064
1113
  if (input.sort === "shuffle" && typeof input.shuffleSeed === "number") {
@@ -1073,12 +1122,38 @@ var buildCardsSearchParams = (input) => {
1073
1122
  search.set("limit", String(normalizeLimit(input.limit)));
1074
1123
  return search.toString();
1075
1124
  };
1076
- var parseJson = async (response) => {
1077
- if (response.status === 204) {
1078
- return null;
1125
+ var readStream = async (reader) => {
1126
+ const decoder = new TextDecoder;
1127
+ let text = "";
1128
+ for (;; ) {
1129
+ const { done, value } = await reader.read();
1130
+ if (done) {
1131
+ return text + decoder.decode();
1132
+ }
1133
+ text += decoder.decode(value, { stream: true });
1134
+ }
1135
+ };
1136
+ var readBody = (response, signal) => new Promise((resolve, reject) => {
1137
+ const reader = response.body?.getReader();
1138
+ const onAbort = () => {
1139
+ reject(new TeakApiError("TIMEOUT"));
1140
+ reader?.cancel().catch(() => {
1141
+ return;
1142
+ });
1143
+ };
1144
+ if (signal.aborted) {
1145
+ onAbort();
1146
+ return;
1079
1147
  }
1080
- const text = await response.text();
1081
- if (!text.trim()) {
1148
+ if (!reader) {
1149
+ resolve("");
1150
+ return;
1151
+ }
1152
+ signal.addEventListener("abort", onAbort, { once: true });
1153
+ readStream(reader).then(resolve, () => reject(new TeakApiError(signal.aborted ? "TIMEOUT" : "NETWORK_ERROR"))).finally(() => signal.removeEventListener("abort", onAbort));
1154
+ });
1155
+ var parseJson = (response, text) => {
1156
+ if (response.status === 204 || !text.trim()) {
1082
1157
  return null;
1083
1158
  }
1084
1159
  try {
@@ -1208,14 +1283,18 @@ var createTeakClient = (options) => {
1208
1283
  headers.set("Content-Type", "application/json");
1209
1284
  }
1210
1285
  let response;
1286
+ let text;
1211
1287
  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");
1288
+ try {
1289
+ response = await fetchImpl(`${baseUrl}${path}`, {
1290
+ ...init,
1291
+ headers,
1292
+ signal: controller.signal
1293
+ });
1294
+ } catch (error) {
1295
+ throw new TeakApiError(error instanceof DOMException && error.name === "AbortError" ? "TIMEOUT" : "NETWORK_ERROR");
1296
+ }
1297
+ text = response.status === 204 ? "" : await readBody(response, controller.signal);
1219
1298
  } finally {
1220
1299
  clearTimeout(timeout);
1221
1300
  }
@@ -1225,7 +1304,7 @@ var createTeakClient = (options) => {
1225
1304
  return request(path, init, parser, false, next);
1226
1305
  }
1227
1306
  }
1228
- const payload = await parseJson(response);
1307
+ const payload = parseJson(response, text);
1229
1308
  if (!response.ok) {
1230
1309
  const object = isObject(payload) ? payload : {};
1231
1310
  throw new TeakApiError(codeFrom(object.code, response.status === 404 ? "NOT_FOUND" : "REQUEST_FAILED"), String(object.error || response.statusText), {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teak-sdk",
3
- "version": "1.0.88",
3
+ "version": "1.0.89",
4
4
  "description": "Teak API client and OAuth discovery for browser and Node applications.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -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
@@ -290,6 +290,12 @@ export interface Me {
290
290
  cardLimit: number | null;
291
291
  email: string;
292
292
  id: string;
293
+ /**
294
+ * The account's Teak language, chosen in Settings: a BCP 47 code such as
295
+ * `en`, `de` or `pt-BR`. More may be added, so treat an unknown code as
296
+ * English.
297
+ */
298
+ locale: string;
293
299
  name?: string;
294
300
  plan: "free" | "pro";
295
301
  settingsUrl: string;
@@ -414,7 +420,9 @@ export declare const buildCardsSearchParams: (input: {
414
420
  * `is:fav`, `is:archived`, `site:github.com`, `folder:"Reading list"`,
415
421
  * `price:<100` (also `>`, `<=`, `>=` and `=`), `text:"total due"` (only
416
422
  * the words Teak read in images, video thumbnails, link previews and scanned
417
- * PDFs) and `"quoted phrases"`.
423
+ * PDFs) and `"quoted phrases"`. A `-` leaves cards out (`design -figma`,
424
+ * `-tag:old`), uppercase `OR` matches either side and parentheses group:
425
+ * `(site:github.com OR site:gitlab.com) -is:archived`.
418
426
  */
419
427
  query?: string;
420
428
  /** Required with `sort: "shuffle"`: the same seed gives the same order. */