diff --git a/deno.lock b/deno.lock index 4d7cc8b..fd74ea5 100644 --- a/deno.lock +++ b/deno.lock @@ -23,6 +23,7 @@ "npm:@supabase/supabase-js@^2.90.1": "2.90.1", "npm:@toon-format/toon@^2.1.0": "2.1.0", "npm:@types/bun@^1.3.5": "1.3.5", + "npm:carbone-sdk@^1.6.0": "1.6.0", "npm:citeproc@^2.4.63": "2.4.63", "npm:deno@^2.6.4": "2.6.4", "npm:jwt-decode@4": "4.0.0", @@ -202,7 +203,7 @@ "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", "dependencies": [ "@types/node", - "form-data" + "form-data@4.0.5" ] }, "@types/node@18.19.130": { @@ -261,6 +262,14 @@ "function-bind" ] }, + "carbone-sdk@1.6.0": { + "integrity": "sha512-fo/dPMwWnaCDK8sqwpkQbm7IiRpVKwRJu5gkVZj2gOQe6XyY6U6/N43c5bnA13GD9YxeI3WrWmj+rTbYlw8lEg==", + "dependencies": [ + "form-data@4.0.0", + "kitten-cache", + "simple-get" + ] + }, "chownr@3.0.0": { "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==" }, @@ -285,6 +294,12 @@ "ms" ] }, + "decompress-response@6.0.0": { + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": [ + "mimic-response" + ] + }, "delayed-stream@1.0.0": { "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" }, @@ -343,6 +358,14 @@ "form-data-encoder@1.7.2": { "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==" }, + "form-data@4.0.0": { + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": [ + "asynckit", + "combined-stream", + "mime-types" + ] + }, "form-data@4.0.5": { "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "dependencies": [ @@ -431,6 +454,9 @@ "jwt-decode@4.0.0": { "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==" }, + "kitten-cache@0.3.0": { + "integrity": "sha512-PFu7PEmbDSFehdsGXz7JtE1L5fFktV+apTcmyamWPo5IuDnFaTc5dT24JlrgB2RzYclLunimWUX5i3vBkPfv9w==" + }, "math-intrinsics@1.1.0": { "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" }, @@ -443,6 +469,9 @@ "mime-db" ] }, + "mimic-response@3.1.0": { + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + }, "minipass@7.1.2": { "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==" }, @@ -476,6 +505,12 @@ "npm-normalize-package-bin@5.0.0": { "integrity": "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==" }, + "once@1.4.0": { + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": [ + "wrappy" + ] + }, "openai@4.104.0_zod@3.25.76": { "integrity": "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==", "dependencies": [ @@ -512,6 +547,17 @@ "signal-exit@4.1.0": { "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" }, + "simple-concat@1.0.1": { + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" + }, + "simple-get@4.0.1": { + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dependencies": [ + "decompress-response", + "once", + "simple-concat" + ] + }, "supabase@2.75.0": { "integrity": "sha512-Xaai8Wp7F03OMkOWSyS1OMMdk5DxkauJxcMMesj4mO6jDGEZpWBwgo/gIUb4hsckexmr/RXYdpDpTbUIMXahbw==", "dependencies": [ @@ -559,6 +605,9 @@ "webidl-conversions" ] }, + "wrappy@1.0.2": { + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, "write-file-atomic@7.0.0": { "integrity": "sha512-YnlPC6JqnZl6aO4uRc+dx5PHguiR9S6WeoLtpxNT9wIG+BDya7ZNE1q7KOjVgaA73hKhKLpVPgJ5QA9THQ5BRg==", "dependencies": [ @@ -608,6 +657,7 @@ "npm:@supabase/supabase-js@^2.90.1", "npm:@toon-format/toon@^2.1.0", "npm:@types/bun@^1.3.5", + "npm:carbone-sdk@^1.6.0", "npm:citeproc@^2.4.63", "npm:deno@^2.6.4", "npm:jwt-decode@4", diff --git a/notebooks/carbone.ipynb b/notebooks/carbone.ipynb new file mode 100644 index 0000000..fa5346f --- /dev/null +++ b/notebooks/carbone.ipynb @@ -0,0 +1,546 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 8, + "id": "76919cba", + "metadata": {}, + "outputs": [], + "source": [ + "import { load } from \"jsr:@std/dotenv\";\n", + "\n", + "const env = await load({\n", + " export: true,\n", + "});" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "20231839", + "metadata": {}, + "outputs": [], + "source": [ + "type ListTemplatesParams = {\n", + " id?: string;\n", + " versionId?: string;\n", + " category?: string;\n", + " origin?: 0 | 1;\n", + " includeVersions?: boolean;\n", + " search?: string;\n", + " limit?: number;\n", + " cursor?: string;\n", + "};\n", + "\n", + "class CarboneClient {\n", + " listTemplates(params?: ListTemplatesParams) {}\n", + " listCategories() {}\n", + " listTags() {}\n", + " uploadTemplate(input: {\n", + " file?: Buffer;\n", + " filename?: string;\n", + " template?: string; // base64\n", + " versioning?: boolean;\n", + " id?: string;\n", + " name?: string;\n", + " comment?: string;\n", + " tags?: string[];\n", + " category?: string;\n", + " sample?: any[];\n", + " deployedAt?: number;\n", + " expireAt?: number;\n", + " }) {}\n", + " patchTemplate(id: string, body: {\n", + " name?: string;\n", + " comment?: string;\n", + " tags?: string[];\n", + " category?: string;\n", + " deployedAt?: number;\n", + " expireAt?: number;\n", + " }) {}\n", + " deleteTemplate(id: string) {}\n", + " downloadTemplate(id: string) {}\n", + " render(templateId: string, body: any, opts?: { download?: boolean }) {}\n", + " renderFromTemplate(body: any, opts?: { download?: boolean }) {}\n", + " downloadRender(renderId: string) {}\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "9950c1f7", + "metadata": {}, + "outputs": [], + "source": [ + "const CARBONE_VERSION = \"5\";\n", + "\n", + "type CarboneSuccess = {\n", + " success: true;\n", + " data: T;\n", + " hasMore?: boolean;\n", + " nextCursor?: string;\n", + "};\n", + "\n", + "type CarboneError = {\n", + " success: false;\n", + " error: string;\n", + "};\n", + "\n", + "type CarboneResponse = CarboneSuccess | CarboneError;\n", + "\n", + "export class CarboneClient {\n", + " constructor(\n", + " private baseUrl: string,\n", + " private apiToken: string\n", + " ) {}\n", + "\n", + " private buildHeaders(extra?: Record) {\n", + " return {\n", + " Authorization: `Bearer ${this.apiToken}`,\n", + " \"carbone-version\": CARBONE_VERSION,\n", + " ...extra,\n", + " };\n", + " }\n", + "\n", + " private async parseResponse(res: Response): Promise {\n", + " const contentType = res.headers.get(\"content-type\") || \"\";\n", + "\n", + " if (!res.ok) {\n", + " let message = `Carbone request failed: ${res.status}`;\n", + " try {\n", + " const err = await res.json();\n", + " message = err?.error || err?.message || message;\n", + " } catch {}\n", + " throw new Error(message);\n", + " }\n", + "\n", + " if (contentType.includes(\"application/json\")) {\n", + " return res.json() as Promise;\n", + " }\n", + "\n", + " return res as unknown as T;\n", + " }\n", + "\n", + " private toQuery(params?: Record) {\n", + " const qs = new URLSearchParams();\n", + " if (!params) return \"\";\n", + " for (const [key, value] of Object.entries(params)) {\n", + " if (value === undefined || value === null) continue;\n", + " qs.set(key, String(value));\n", + " }\n", + " const s = qs.toString();\n", + " return s ? `?${s}` : \"\";\n", + " }\n", + "\n", + " async listTemplates(params?: {\n", + " id?: string;\n", + " versionId?: string;\n", + " category?: string;\n", + " origin?: 0 | 1;\n", + " includeVersions?: boolean;\n", + " search?: string;\n", + " limit?: number;\n", + " cursor?: string;\n", + " }) {\n", + " const res = await fetch(\n", + " `${this.baseUrl}/templates${this.toQuery(params)}`,\n", + " {\n", + " method: \"GET\",\n", + " headers: this.buildHeaders(),\n", + " }\n", + " );\n", + " return this.parseResponse<\n", + " CarboneResponse<\n", + " Array<{\n", + " id: string;\n", + " versionId: string;\n", + " deployedAt: number;\n", + " createdAt: number;\n", + " expireAt?: number;\n", + " size: number;\n", + " type: string;\n", + " name?: string;\n", + " category?: string;\n", + " comment?: string;\n", + " tags?: string[];\n", + " origin?: number;\n", + " }>\n", + " >\n", + " >(res);\n", + " }\n", + "\n", + " async listCategories() {\n", + " const res = await fetch(`${this.baseUrl}/templates/categories`, {\n", + " method: \"GET\",\n", + " headers: this.buildHeaders(),\n", + " });\n", + " return this.parseResponse>>(res);\n", + " }\n", + "\n", + " async listTags() {\n", + " const res = await fetch(`${this.baseUrl}/templates/tags`, {\n", + " method: \"GET\",\n", + " headers: this.buildHeaders(),\n", + " });\n", + " return this.parseResponse>>(res);\n", + " }\n", + "\n", + " async uploadTemplateJson(body: {\n", + " template: string;\n", + " versioning?: boolean;\n", + " id?: string;\n", + " name?: string;\n", + " comment?: string;\n", + " tags?: string[];\n", + " category?: string;\n", + " sample?: any[];\n", + " deployedAt?: number;\n", + " expireAt?: number;\n", + " }) {\n", + " const res = await fetch(`${this.baseUrl}/template`, {\n", + " method: \"POST\",\n", + " headers: this.buildHeaders({\n", + " \"Content-Type\": \"application/json\",\n", + " }),\n", + " body: JSON.stringify(body),\n", + " });\n", + " return this.parseResponse<\n", + " CarboneResponse<{\n", + " id?: string;\n", + " versionId?: string;\n", + " templateId?: string;\n", + " type?: string;\n", + " size?: number;\n", + " createdAt?: number;\n", + " }>\n", + " >(res);\n", + " }\n", + "\n", + " async uploadTemplateFile(input: {\n", + " file: Blob;\n", + " filename: string;\n", + " versioning?: boolean;\n", + " id?: string;\n", + " name?: string;\n", + " comment?: string;\n", + " tags?: string[];\n", + " category?: string;\n", + " sample?: any[];\n", + " deployedAt?: number;\n", + " expireAt?: number;\n", + " }) {\n", + " const form = new FormData();\n", + "\n", + " if (input.versioning !== undefined) {\n", + " form.append(\"versioning\", String(input.versioning));\n", + " }\n", + " if (input.id) form.append(\"id\", input.id);\n", + " if (input.name) form.append(\"name\", input.name);\n", + " if (input.comment) form.append(\"comment\", input.comment);\n", + " if (input.category) form.append(\"category\", input.category);\n", + " if (input.tags) {\n", + " for (const tag of input.tags) form.append(\"tags[]\", tag);\n", + " }\n", + " if (input.sample) form.append(\"sample\", JSON.stringify(input.sample));\n", + " if (input.deployedAt !== undefined) {\n", + " form.append(\"deployedAt\", String(input.deployedAt));\n", + " }\n", + " if (input.expireAt !== undefined) {\n", + " form.append(\"expireAt\", String(input.expireAt));\n", + " }\n", + "\n", + " // template should be appended last\n", + " form.append(\"template\", input.file, input.filename);\n", + "\n", + " const res = await fetch(`${this.baseUrl}/template`, {\n", + " method: \"POST\",\n", + " headers: this.buildHeaders(),\n", + " body: form,\n", + " });\n", + " return this.parseResponse<\n", + " CarboneResponse<{\n", + " id?: string;\n", + " versionId?: string;\n", + " templateId?: string;\n", + " type?: string;\n", + " size?: number;\n", + " createdAt?: number;\n", + " }>\n", + " >(res);\n", + " }\n", + "\n", + " async patchTemplate(\n", + " id: string,\n", + " body: {\n", + " name?: string;\n", + " comment?: string;\n", + " tags?: string[];\n", + " category?: string;\n", + " deployedAt?: number;\n", + " expireAt?: number;\n", + " }\n", + " ) {\n", + " const res = await fetch(`${this.baseUrl}/template/${id}`, {\n", + " method: \"PATCH\",\n", + " headers: this.buildHeaders({\n", + " \"Content-Type\": \"application/json\",\n", + " }),\n", + " body: JSON.stringify(body),\n", + " });\n", + " return this.parseResponse>(res);\n", + " }\n", + "\n", + " async deleteTemplate(id: string) {\n", + " const res = await fetch(`${this.baseUrl}/template/${id}`, {\n", + " method: \"DELETE\",\n", + " headers: this.buildHeaders(),\n", + " });\n", + " return this.parseResponse>(res);\n", + " }\n", + "\n", + " async downloadTemplate(id: string) {\n", + " const res = await fetch(`${this.baseUrl}/template/${id}`, {\n", + " method: \"GET\",\n", + " headers: this.buildHeaders(),\n", + " });\n", + "\n", + " if (!res.ok) {\n", + " const err = await res.json().catch(() => null);\n", + " throw new Error(err?.error || `Failed to download template: ${res.status}`);\n", + " }\n", + "\n", + " return {\n", + " buffer: Buffer.from(await res.arrayBuffer()),\n", + " contentType: res.headers.get(\"content-type\"),\n", + " contentDisposition: res.headers.get(\"content-disposition\"),\n", + " };\n", + " }\n", + "\n", + " async render(\n", + " templateIdOrVersionId: string,\n", + " body: Record,\n", + " opts?: { download?: boolean }\n", + " ) {\n", + " const query = opts?.download ? \"?download=true\" : \"\";\n", + " const res = await fetch(\n", + " `${this.baseUrl}/render/${templateIdOrVersionId}${query}`,\n", + " {\n", + " method: \"POST\",\n", + " headers: this.buildHeaders({\n", + " \"Content-Type\": \"application/json\",\n", + " }),\n", + " body: JSON.stringify(body),\n", + " }\n", + " );\n", + "\n", + " if (opts?.download) {\n", + " if (!res.ok) {\n", + " const err = await res.json().catch(() => null);\n", + " throw new Error(err?.error || `Render failed: ${res.status}`);\n", + " }\n", + " return {\n", + " buffer: Buffer.from(await res.arrayBuffer()),\n", + " contentType: res.headers.get(\"content-type\"),\n", + " contentDisposition: res.headers.get(\"content-disposition\"),\n", + " };\n", + " }\n", + "\n", + " return this.parseResponse>(res);\n", + " }\n", + "\n", + " async renderFromTemplate(\n", + " body: Record,\n", + " opts?: { download?: boolean }\n", + " ) {\n", + " const query = opts?.download ? \"?download=true\" : \"\";\n", + " const res = await fetch(`${this.baseUrl}/render/template${query}`, {\n", + " method: \"POST\",\n", + " headers: this.buildHeaders({\n", + " \"Content-Type\": \"application/json\",\n", + " }),\n", + " body: JSON.stringify(body),\n", + " });\n", + "\n", + " if (opts?.download) {\n", + " if (!res.ok) {\n", + " const err = await res.json().catch(() => null);\n", + " throw new Error(err?.error || `Render failed: ${res.status}`);\n", + " }\n", + " return {\n", + " buffer: Buffer.from(await res.arrayBuffer()),\n", + " contentType: res.headers.get(\"content-type\"),\n", + " contentDisposition: res.headers.get(\"content-disposition\"),\n", + " };\n", + " }\n", + "\n", + " return this.parseResponse>(res);\n", + " }\n", + "\n", + " async downloadRender(renderId: string) {\n", + " const res = await fetch(`${this.baseUrl}/render/${renderId}`, {\n", + " method: \"GET\",\n", + " });\n", + "\n", + " if (!res.ok) {\n", + " const err = await res.json().catch(() => null);\n", + " throw new Error(err?.error || `Failed to download render: ${res.status}`);\n", + " }\n", + "\n", + " return {\n", + " buffer: Buffer.from(await res.arrayBuffer()),\n", + " contentType: res.headers.get(\"content-type\"),\n", + " contentDisposition: res.headers.get(\"content-disposition\"),\n", + " };\n", + " }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "95b24436", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " success: true,\n", + " data: [\n", + " {\n", + " versionId: \"1990f3351b4313779ae3ef3caeaea14dea51c3a98a691fb90a347dec53f875e6\",\n", + " id: \"1373945625988937236\",\n", + " deployedAt: 1773246290,\n", + " createdAt: 1773246316,\n", + " expireAt: 0,\n", + " size: 29008,\n", + " type: \"docx\",\n", + " name: \"Anexo 1\",\n", + " category: \"Planes de estudio\",\n", + " comment: \"Plantilla de anexo 1 SEP RVOE Acuerdo 17-11-17\",\n", + " tags: [],\n", + " origin: 1\n", + " },\n", + " {\n", + " versionId: \"caee5a7804647382f6df62ef92cc8f04c822b6a297479c5068e22d08def685d7\",\n", + " id: \"1373945108764607184\",\n", + " deployedAt: 1773246225,\n", + " createdAt: 1773246251,\n", + " expireAt: 0,\n", + " size: 29642,\n", + " type: \"xlsx\",\n", + " name: \"Anexo 2\",\n", + " category: \"\",\n", + " comment: \"\",\n", + " tags: [],\n", + " origin: 1\n", + " },\n", + " {\n", + " versionId: \"b0ab3c6fe90c73151fcae64d8509e406803381f3966aa88d30d8d30e2b1ffe8f\",\n", + " id: \"1373944894291796699\",\n", + " deployedAt: 1773246204,\n", + " createdAt: 1773246230,\n", + " expireAt: 0,\n", + " size: 23806,\n", + " type: \"docx\",\n", + " name: \"Anexo 3\",\n", + " category: \"\",\n", + " comment: \"\",\n", + " tags: [],\n", + " origin: 1\n", + " }\n", + " ],\n", + " hasMore: false\n", + "}\n", + "{ success: true, data: [ { name: \"Planes de estudio\" } ] }\n", + "{ success: true, data: [] }\n" + ] + } + ], + "source": [ + "const carbone = new CarboneClient(\n", + " \"https://carbone.lci.ulsa.mx\",\n", + " Deno.env.get(\"CARBONE_API_TOKEN\")!\n", + ");\n", + "\n", + "const templates = await carbone.listTemplates({ limit: 20 });\n", + "console.log(templates);\n", + "\n", + "const categories = await carbone.listCategories();\n", + "console.log(categories);\n", + "\n", + "const tags = await carbone.listTags();\n", + "console.log(tags);" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8196fac4", + "metadata": {}, + "outputs": [], + "source": [ + "const templateID = \"1373945625988937236\";\n" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "caf8bc20", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{\n", + " success: \u001b[33mtrue\u001b[39m,\n", + " data: {\n", + " category: \u001b[32m\"Planes de estudio\"\u001b[39m,\n", + " comment: \u001b[32m\"Plantilla de anexo 1 SEP RVOE Acuerdo 17-11-17\"\u001b[39m,\n", + " versionId: \u001b[32m\"1990f3351b4313779ae3ef3caeaea14dea51c3a98a691fb90a347dec53f875e6\"\u001b[39m\n", + " }\n", + "}" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "await carbone.patchTemplate(\"1373945625988937236\", {\n", + " category: \"Planes de estudio\",\n", + " comment: \"Plantilla de anexo 1 SEP RVOE Acuerdo 17-11-17\",\n", + "});\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "929883b9", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Deno", + "language": "typescript", + "name": "deno" + }, + "language_info": { + "codemirror_mode": "typescript", + "file_extension": ".ts", + "mimetype": "text/x.typescript", + "name": "typescript", + "nbconvert_exporter": "script", + "pygments_lexer": "typescript", + "version": "5.9.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/package.json b/package.json index dfd647e..f29938a 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "dependencies": { "@supabase/supabase-js": "^2.90.1", "@toon-format/toon": "^2.1.0", + "carbone-sdk": "^1.6.0", "citeproc": "^2.4.63", "deno": "^2.6.4", "jwt-decode": "^4.0.0",