teak-sdk 1.0.89 → 1.0.91

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
@@ -335,7 +335,6 @@ var fetchConnectOwnerId = async (apiUrl, accessToken, options = {}) => {
335
335
  };
336
336
  // ../convex/shared/markdown.ts
337
337
  var MARKDOWN_CONTENT_MAX_BYTES = 512 * 1024;
338
-
339
338
  // ../files-protocol/src/decisions.ts
340
339
  var CLEF_MODEL_ID = "@cf/cloudflare/clef";
341
340
  var CLEF_FLASH_MODEL_ID = "@cf/cloudflare/clef-flash";
@@ -978,6 +977,7 @@ var ERROR_CODES = [
978
977
  "FILE_TOO_LARGE",
979
978
  "FOLDER_LIMIT",
980
979
  "FORBIDDEN",
980
+ "HIGHLIGHT_LIMIT",
981
981
  "IN_PROGRESS",
982
982
  "INTERNAL_ERROR",
983
983
  "INVALID_API_KEY",
@@ -988,9 +988,12 @@ var ERROR_CODES = [
988
988
  "METHOD_NOT_ALLOWED",
989
989
  "NETWORK_ERROR",
990
990
  "NOT_FOUND",
991
+ "NO_ARTICLE",
991
992
  "NO_CONTENT",
992
993
  "NOT_IN_TRASH",
994
+ "PAGE_SNAPSHOT_TOO_LARGE",
993
995
  "PARSE_ERROR",
996
+ "QUOTE_NOT_FOUND",
994
997
  "RATE_LIMITED",
995
998
  "REQUEST_FAILED",
996
999
  "SERVICE_UNAVAILABLE",
@@ -1188,6 +1191,18 @@ var asFolders = (value) => {
1188
1191
  }
1189
1192
  return { items: value.items.map(asFolder) };
1190
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
+ };
1191
1206
  var asFolderCards = (value) => {
1192
1207
  if (!(isObject(value) && isStringArray(value.updatedIds) && isStringArray(value.failedIds))) {
1193
1208
  throw new TeakApiError("PARSE_ERROR");
@@ -1235,7 +1250,7 @@ var asSavedPdf = (value) => {
1235
1250
  return { cardId: value.cardId, created: value.created };
1236
1251
  };
1237
1252
  var asCardContent = (value) => {
1238
- 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") {
1239
1254
  throw new TeakApiError("PARSE_ERROR");
1240
1255
  }
1241
1256
  return value;
@@ -1319,6 +1334,7 @@ var createTeakClient = (options) => {
1319
1334
  const idempotency = (requestOptions) => requestOptions?.idempotencyKey ? { "Idempotency-Key": requestOptions.idempotencyKey } : {};
1320
1335
  const cardPath = (id) => `/v1/cards/${encodeURIComponent(id)}`;
1321
1336
  const folderPath = (id) => `/v1/folders/${encodeURIComponent(id)}`;
1337
+ const highlightPath = (id) => `/v1/highlights/${encodeURIComponent(id)}`;
1322
1338
  const putFile = async (uploadUrl, bytes, mimeType) => {
1323
1339
  const headers = new Headers({ "Content-Type": mimeType });
1324
1340
  const byteLength = byteLengthForBody(bytes);
@@ -1354,7 +1370,7 @@ var createTeakClient = (options) => {
1354
1370
  headers: idempotency(requestOptions),
1355
1371
  method: "POST"
1356
1372
  }, asCreate),
1357
- 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),
1358
1374
  delete: (id, deleteOptions) => request(`${cardPath(id)}${deleteOptions?.permanent ? "?permanent=true" : ""}`, { method: "DELETE" }, () => null),
1359
1375
  duplicate: (url) => request(`/v1/cards/duplicate?${new URLSearchParams({ url }).toString()}`, { method: "GET" }, (v) => isObject(v) && typeof v.cardId === "string" ? v.cardId : null),
1360
1376
  favorites: (input = {}) => request(`/v1/cards?${qs({ include: "content,metadata", ...input, favorited: true })}`, { method: "GET" }, asPage),
@@ -1407,6 +1423,12 @@ var createTeakClient = (options) => {
1407
1423
  removeCards: (id, cardIds) => request(`${folderPath(id)}/cards`, { body: JSON.stringify({ cardIds }), method: "DELETE" }, asFolderCards),
1408
1424
  update: (id, input) => request(folderPath(id), { body: JSON.stringify(input), method: "PATCH" }, asFolder)
1409
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
+ },
1410
1432
  me: () => request("/v1/me", { method: "GET" }, asMe),
1411
1433
  tags: {
1412
1434
  delete: (tag) => request("/v1/tags/delete", { body: JSON.stringify({ tag }), method: "POST" }, asTagOperation),
@@ -1415,6 +1437,9 @@ var createTeakClient = (options) => {
1415
1437
  operation: (id) => request(`/v1/tags/operations/${encodeURIComponent(id)}`, { method: "GET" }, asTagOperation),
1416
1438
  rename: (from, to) => request("/v1/tags/rename", { body: JSON.stringify({ from, to }), method: "POST" }, asTagOperation)
1417
1439
  },
1440
+ pageSnapshots: {
1441
+ createUpload: (input) => request("/v1/page-snapshots/upload", { body: JSON.stringify(input), method: "POST" }, asUpload)
1442
+ },
1418
1443
  uploads: {
1419
1444
  create: (input) => request("/v1/uploads", { body: JSON.stringify(input), method: "POST" }, asUpload),
1420
1445
  putFile
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teak-sdk",
3
- "version": "1.0.89",
3
+ "version": "1.0.91",
4
4
  "description": "Teak API client and OAuth discovery for browser and Node applications.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -20,7 +20,18 @@ 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;
30
+ /**
31
+ * The most cards Trash's count reads (card/emptyTrash.ts). A Trash this full
32
+ * or fuller shows the count with a plus; Empty Trash still deletes them all.
33
+ */
34
+ export declare const TRASH_COUNT_LIMIT = 500;
24
35
  export { MAX_FILE_SIZE } from "./fileFormats.js";
25
36
  export declare const MAX_FILES_PER_UPLOAD = 20;
26
37
  export declare const MULTIPART_UPLOAD_THRESHOLD: number;
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`.
@@ -331,6 +388,10 @@ export interface CreateUploadInput {
331
388
  mimeType: string;
332
389
  }
333
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
+ }
334
395
  export interface CreateThumbnailUploadInput {
335
396
  fileSize: number;
336
397
  mimeType: string;
@@ -346,7 +407,16 @@ export interface CreateCardResponse {
346
407
  appUrl: string;
347
408
  card?: Card;
348
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. */
349
417
  status: "created";
418
+ /** True when the re-saved card was archived; it is back in the library. */
419
+ wasArchived?: true;
350
420
  }
351
421
  export interface RefreshCardResponse {
352
422
  cardId: string;
@@ -360,6 +430,8 @@ export interface SavedPdf {
360
430
  /** False when an earlier save already made that card. */
361
431
  created: boolean;
362
432
  }
433
+ /** The kinds of stored text a card can have. */
434
+ export type CardContentKind = "article" | "document" | "transcript" | "content";
363
435
  /** A card's full stored text (GET /v1/cards/:id/content). */
364
436
  export interface CardContent {
365
437
  cardId: string;
@@ -367,9 +439,11 @@ export interface CardContent {
367
439
  charCount: number;
368
440
  /**
369
441
  * document: extracted from the card's file; transcript: its speech;
370
- * 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.
371
445
  */
372
- kind: "document" | "transcript" | "content";
446
+ kind: CardContentKind | (string & {});
373
447
  text: string;
374
448
  /** The document is longer than Teak keeps. */
375
449
  truncated: boolean;
@@ -452,9 +526,13 @@ export declare const createTeakClient: (options: {
452
526
  create: (input: CreateCardInput, requestOptions?: RequestOptions) => Promise<CreateCardResponse>;
453
527
  /**
454
528
  * The card's full stored text: document text, transcript, or a text
455
- * 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).
456
532
  */
457
- content: (id: string) => Promise<CardContent>;
533
+ content: (id: string, contentOptions?: {
534
+ kind?: CardContentKind;
535
+ }) => Promise<CardContent>;
458
536
  /** Moves the card to Trash, or deletes it for good with `permanent`. */
459
537
  delete: (id: string, deleteOptions?: {
460
538
  permanent?: boolean;
@@ -547,6 +625,25 @@ export declare const createTeakClient: (options: {
547
625
  query?: string;
548
626
  }) => Promise<Folder>;
549
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
+ };
550
647
  me: () => Promise<Me>;
551
648
  tags: {
552
649
  /**
@@ -564,6 +661,14 @@ export declare const createTeakClient: (options: {
564
661
  /** Renames a tag on every card; it finishes in the background. */
565
662
  rename: (from: string, to: string) => Promise<TagOperation>;
566
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
+ };
567
672
  uploads: {
568
673
  create: (input: CreateUploadInput) => Promise<CreateUploadResponse>;
569
674
  putFile: (uploadUrl: string, bytes: BodyInit, mimeType: string) => Promise<{