teak-sdk 1.0.87 → 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/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Teak SDK
2
2
 
3
- Typed API client and OAuth discovery for Teak. The distribution bundles the
4
- canonical implementation from `packages/convex/client/sdk.ts`; it has no runtime
5
- dependencies and works without the Teak monorepo.
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.
6
6
 
7
7
  ```ts
8
8
  import { createTeakClient, discoverAuthServer } from "teak-sdk";
@@ -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 {
@@ -46,6 +51,9 @@ var consumeResponseWithinLimit = async (response, maxBytes, consume) => {
46
51
  };
47
52
  var readResponseTextWithinLimit = (response, maxBytes) => consumeResponseWithinLimit(response, maxBytes, (boundedResponse) => boundedResponse.text());
48
53
 
54
+ // ../convex/shared/oauthSurfaces.ts
55
+ var OAUTH_SURFACES = ["cli", "raycast", "chrome", "firefox"];
56
+
49
57
  // ../convex/devUrls.ts
50
58
  var TEAK_DEV_DEPLOYMENT_NAME = "reminiscent-kangaroo-59";
51
59
  var DEFAULT_TEAK_DEV_CONVEX_URL = `https://${TEAK_DEV_DEPLOYMENT_NAME}.convex.cloud`;
@@ -66,7 +74,8 @@ var validateOAuthUrl = (raw, local = false) => {
66
74
  throw new Error("Missing OAuth discovery URL");
67
75
  }
68
76
  const url = new URL(raw);
69
- 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))) {
70
79
  throw new Error("Unsafe OAuth discovery URL");
71
80
  }
72
81
  return url;
@@ -80,8 +89,7 @@ var workosResourcesFor = (url) => {
80
89
  };
81
90
  var PRODUCTION_WORKOS_RESOURCES = workosResourcesFor(PRODUCTION_WORKOS_RESOURCE_ORIGIN);
82
91
 
83
- // ../convex/client/authDiscovery.ts
84
- var OAUTH_SURFACES = ["cli", "raycast", "chrome", "firefox"];
92
+ // src/authDiscovery.ts
85
93
  var caches = new WeakMap;
86
94
  var object = (value) => {
87
95
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -195,7 +203,18 @@ function discoverAuthServer(siteUrl, options = {}) {
195
203
  });
196
204
  return pending;
197
205
  }
198
- // ../convex/client/workosConnect.ts
206
+ // src/oauthCredentials.ts
207
+ var isWorkosCredential = (saved) => Boolean(saved?.origin && saved.issuer && saved.clientId?.startsWith("client_"));
208
+ var matchesBinding = (saved, current) => saved?.origin === current.origin && saved.issuer === current.issuer && saved.clientId === current.clientId;
209
+ var isRefreshTokenDead = (result) => !result.ok && result.reason === "refresh_token_rejected";
210
+ var refreshAndSave = async (refresh, save) => {
211
+ const renewed = await refresh();
212
+ if (renewed !== null && renewed !== undefined) {
213
+ await save(renewed);
214
+ }
215
+ return renewed;
216
+ };
217
+ // src/workosConnect.ts
199
218
  var WORKOS_CONNECT_SCOPE = "openid profile email offline_access";
200
219
  var MAX_RESPONSE_BYTES = 64 * 1024;
201
220
  var MAX_TOKEN_LIFETIME_SECONDS = 365 * 24 * 3600;
@@ -347,11 +366,49 @@ var FILES_OBJECT_KEY_ROOTS = ["", FILES_DEV_KEY_PREFIX].flatMap((envPrefix) => [
347
366
  // ../files-protocol/src/inspect.ts
348
367
  var FILES_INSPECT_MAX_ARCHIVE_BYTES = 25 * 1024 * 1024;
349
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(" "));
350
403
  // ../files-protocol/src/remoteAsset.ts
351
404
  var FILES_REMOTE_ASSET_MAX_BYTES = {
352
405
  image: 20 * 1024 * 1024,
353
406
  video: 50 * 1024 * 1024
354
407
  };
408
+ var GENERIC_CONTENT_TYPES = new Set([
409
+ "application/octet-stream",
410
+ "binary/octet-stream"
411
+ ]);
355
412
  // ../files-protocol/src/workflows.ts
356
413
  var WORKFLOW_RPC_MAX_TTL_SECONDS = 5 * 60;
357
414
  var WORKFLOW_RPC_MAX_BODY_BYTES = 4 * 1024 * 1024;
@@ -837,6 +894,11 @@ var TAG_ERROR_MESSAGES = {
837
894
  };
838
895
  var HIDDEN_TAG_KEYS = ["secret", "hidden"];
839
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
+ };
840
902
  // ../convex/shared/constants.ts
841
903
  var cardTypes = [
842
904
  "text",
@@ -903,7 +965,7 @@ var RESERVED_KEYWORDS = [
903
965
  { value: "trash", label: "Trash" }
904
966
  ];
905
967
 
906
- // ../convex/client/sdk.ts
968
+ // src/index.ts
907
969
  var CARD_TYPES2 = cardTypes;
908
970
  var CARD_SORTS = ["newest", "oldest", "relevance", "shuffle"];
909
971
  var ERROR_CODES = [
@@ -1045,7 +1107,7 @@ var buildCardsSearchParams = (input) => {
1045
1107
  if (input.mode === "keyword" || input.mode === "semantic") {
1046
1108
  search.set("mode", input.mode);
1047
1109
  }
1048
- if (input.sort === "oldest" || input.sort === "shuffle") {
1110
+ if (input.sort && input.sort !== "newest") {
1049
1111
  search.set("sort", input.sort);
1050
1112
  }
1051
1113
  if (input.sort === "shuffle" && typeof input.shuffleSeed === "number") {
@@ -1060,12 +1122,38 @@ var buildCardsSearchParams = (input) => {
1060
1122
  search.set("limit", String(normalizeLimit(input.limit)));
1061
1123
  return search.toString();
1062
1124
  };
1063
- var parseJson = async (response) => {
1064
- if (response.status === 204) {
1065
- 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 });
1066
1134
  }
1067
- const text = await response.text();
1068
- if (!text.trim()) {
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;
1147
+ }
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()) {
1069
1157
  return null;
1070
1158
  }
1071
1159
  try {
@@ -1195,14 +1283,18 @@ var createTeakClient = (options) => {
1195
1283
  headers.set("Content-Type", "application/json");
1196
1284
  }
1197
1285
  let response;
1286
+ let text;
1198
1287
  try {
1199
- response = await fetchImpl(`${baseUrl}${path}`, {
1200
- ...init,
1201
- headers,
1202
- signal: controller.signal
1203
- });
1204
- } catch (error) {
1205
- 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);
1206
1298
  } finally {
1207
1299
  clearTimeout(timeout);
1208
1300
  }
@@ -1212,7 +1304,7 @@ var createTeakClient = (options) => {
1212
1304
  return request(path, init, parser, false, next);
1213
1305
  }
1214
1306
  }
1215
- const payload = await parseJson(response);
1307
+ const payload = parseJson(response, text);
1216
1308
  if (!response.ok) {
1217
1309
  const object = isObject(payload) ? payload : {};
1218
1310
  throw new TeakApiError(codeFrom(object.code, response.status === 404 ? "NOT_FOUND" : "REQUEST_FAILED"), String(object.error || response.statusText), {
@@ -1347,10 +1439,14 @@ export {
1347
1439
  fetchConnectOwnerId,
1348
1440
  formatBytes,
1349
1441
  isCardType,
1442
+ isRefreshTokenDead,
1350
1443
  isStringList,
1444
+ isWorkosCredential,
1445
+ matchesBinding,
1351
1446
  normalizeLimit,
1352
1447
  parseTags,
1353
1448
  randomBase64Url,
1449
+ refreshAndSave,
1354
1450
  requestConnectTokens,
1355
1451
  staticTokenProvider,
1356
1452
  validateOAuthUrl
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teak-sdk",
3
- "version": "1.0.87",
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",
@@ -11,25 +11,10 @@
11
11
  "type": "module",
12
12
  "exports": {
13
13
  ".": {
14
- "types": "./dist/types/client/sdk.d.ts",
15
- "import": "./dist/sdk.js"
14
+ "types": "./types/index.d.ts",
15
+ "import": "./index.js"
16
16
  }
17
17
  },
18
- "files": [
19
- "dist",
20
- "README.md"
21
- ],
22
- "scripts": {
23
- "build": "bun --no-env-file run build.ts",
24
- "prepack": "bun run build",
25
- "typecheck": "tsc build.ts ../convex/client/sdk.ts --strict --noEmit --moduleResolution bundler --module esnext --target es2022 --skipLibCheck --types bun,node"
26
- },
27
- "devDependencies": {
28
- "@teak/convex": "workspace:*",
29
- "@types/bun": "^1.4.0",
30
- "@types/node": "^26.6.5",
31
- "typescript": "^6.0.3"
32
- },
33
18
  "publishConfig": {
34
19
  "access": "public",
35
20
  "provenance": true
@@ -1,6 +1,6 @@
1
- export { validateOAuthUrl } from "../shared/oauthUrl.js";
2
- export declare const OAUTH_SURFACES: readonly ["cli", "raycast", "chrome", "firefox"];
3
- export type OAuthSurface = (typeof OAUTH_SURFACES)[number];
1
+ import { type OAuthSurface } from "./convex/shared/oauthSurfaces.js";
2
+ export { OAUTH_SURFACES, type OAuthSurface, } from "./convex/shared/oauthSurfaces.js";
3
+ export { validateOAuthUrl } from "./convex/shared/oauthUrl.js";
4
4
  export interface AuthDiscovery {
5
5
  readonly authorizationEndpoint: string;
6
6
  readonly clients: Readonly<Record<OAuthSurface, string>>;
@@ -0,0 +1,2 @@
1
+ export declare const OAUTH_SURFACES: readonly ["cli", "raycast", "chrome", "firefox"];
2
+ export type OAuthSurface = (typeof OAUTH_SURFACES)[number];
@@ -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;
@@ -0,0 +1,5 @@
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;
@@ -1,10 +1,11 @@
1
- export { formatBytes } from "../shared/utils/formatBytes.js";
1
+ export { formatBytes } from "./convex/shared/utils/formatBytes.js";
2
2
  export { type AuthDiscovery, discoverAuthServer, type OAuthSurface, validateOAuthUrl, } from "./authDiscovery.js";
3
+ export { type CredentialBinding, isRefreshTokenDead, isWorkosCredential, matchesBinding, refreshAndSave, } from "./oauthCredentials.js";
3
4
  export { type ConnectTokenResult, type ConnectTokens, createConnectAuthorizeUrl, createPkceChallenge, disconnectConnectGrant, fetchConnectOwnerId, randomBase64Url, requestConnectTokens, WORKOS_CONNECT_SCOPE, } from "./workosConnect.js";
4
- import { type CardType } from "../shared/constants.js";
5
- import type { FileKind, FilePreviewFacts } from "../shared/fileFormats.js";
6
- import type { FOLDER_COLORS } from "../shared/folders.js";
7
- export type { CardType } from "../shared/constants.js";
5
+ import { type CardType } from "./convex/shared/constants.js";
6
+ import type { FileKind, FilePreviewFacts } from "./convex/shared/fileFormats.js";
7
+ import type { FOLDER_COLORS } from "./convex/shared/folders.js";
8
+ export type { CardType } from "./convex/shared/constants.js";
8
9
  export declare const CARD_TYPES: readonly ["text", "link", "image", "video", "audio", "document", "palette", "quote"];
9
10
  export declare const CARD_SORTS: readonly ["newest", "oldest", "relevance", "shuffle"];
10
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"];
@@ -289,6 +290,12 @@ export interface Me {
289
290
  cardLimit: number | null;
290
291
  email: string;
291
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;
292
299
  name?: string;
293
300
  plan: "free" | "pro";
294
301
  settingsUrl: string;
@@ -413,7 +420,9 @@ export declare const buildCardsSearchParams: (input: {
413
420
  * `is:fav`, `is:archived`, `site:github.com`, `folder:"Reading list"`,
414
421
  * `price:<100` (also `>`, `<=`, `>=` and `=`), `text:"total due"` (only
415
422
  * the words Teak read in images, video thumbnails, link previews and scanned
416
- * 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`.
417
426
  */
418
427
  query?: string;
419
428
  /** Required with `sort: "shuffle"`: the same seed gives the same order. */
@@ -0,0 +1,10 @@
1
+ import type { ConnectTokenResult } from "./workosConnect.js";
2
+ export interface CredentialBinding {
3
+ clientId: string;
4
+ issuer: string;
5
+ origin: string;
6
+ }
7
+ export declare const isWorkosCredential: (saved: Partial<CredentialBinding> | undefined) => saved is CredentialBinding;
8
+ export declare const matchesBinding: (saved: Partial<CredentialBinding> | undefined, current: CredentialBinding) => boolean;
9
+ export declare const isRefreshTokenDead: (result: ConnectTokenResult) => boolean;
10
+ export declare const refreshAndSave: <T>(refresh: () => Promise<T>, save: (renewed: NonNullable<T>) => unknown) => Promise<T>;
@@ -1 +0,0 @@
1
- export declare const formatBytes: (bytes?: number | null) => string;
File without changes
File without changes