@conveo/conveo-sdk 0.4.0 → 0.4.1

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
@@ -103,6 +103,70 @@ Want a full working example? See the
103
103
  [template app](https://github.com/conveo/agentgateway-template-app). Clone
104
104
  it and you're done.
105
105
 
106
+ ## Third-party SDKs through the gateway
107
+
108
+ `gatewayFetch({ service })` is the low-level way to reach a gateway-fronted vendor
109
+ API. For the common ones, these subpaths hand you the **official vendor SDK** already
110
+ pointed at the gateway and authenticated — no host, no token, no vendor key in your app:
111
+
112
+ | Import | Returns | Vendor SDK |
113
+ |---|---|---|
114
+ | `@conveo/conveo-sdk/linear` | `getLinearClient()` → `LinearClient` | `@linear/sdk` |
115
+ | `@conveo/conveo-sdk/hubspot` | `getHubspotClient()` → HubSpot `Client` | `@hubspot/api-client` |
116
+ | `@conveo/conveo-sdk/postgrest` | `postgrest()` / `postgrestWithCount()` | none (typed helper) |
117
+
118
+ **Prerequisites (one-time):**
119
+
120
+ 1. On the Vercel project: **Settings → Functions → OIDC Federation = ON** (Vercel then
121
+ injects a fresh `VERCEL_OIDC_TOKEN` into every server invocation). Install
122
+ `@vercel/functions` for automatic token refresh.
123
+ 2. The app must be **granted** each route — the gateway is fail-closed. Onboarding adds
124
+ an `Identity` + `Grant` for your Vercel project in `conveo/agentgateway`; until then,
125
+ calls return `401`.
126
+ 3. Install the vendor SDK you use (optional peer deps): `pnpm add @linear/sdk` /
127
+ `pnpm add @hubspot/api-client`. PostgREST needs none.
128
+
129
+ **Linear / HubSpot** — build the client **per request** (the OIDC token rotates) and use
130
+ the native SDK:
131
+
132
+ ```ts
133
+ import { getLinearClient } from "@conveo/conveo-sdk/linear";
134
+ import { getHubspotClient } from "@conveo/conveo-sdk/hubspot";
135
+
136
+ export async function GET() {
137
+ const linear = await getLinearClient();
138
+ const issues = await linear.issues({ first: 10 });
139
+
140
+ const hs = await getHubspotClient();
141
+ const deals = await hs.crm.deals.basicApi.getPage(10);
142
+
143
+ return Response.json({ issues: issues.nodes, deals: deals.results });
144
+ }
145
+ ```
146
+
147
+ **PostgREST** — a typed helper returning a result union (never throws on a bad status):
148
+
149
+ ```ts
150
+ import { postgrest } from "@conveo/conveo-sdk/postgrest";
151
+
152
+ const res = await postgrest<Org[]>("Organization?select=id,name&limit=100");
153
+ if (!res.ok) return Response.json({ error: res.kind }, { status: res.status });
154
+ return Response.json(res.data); // res.kind: ok | unauthenticated | unauthorized | unreachable | error
155
+ ```
156
+
157
+ Notes:
158
+
159
+ - **Server-side only** — these need the Vercel OIDC token. Never call from a client component.
160
+ - **Call the factory per request**, inside your handler — never cache a client at module scope.
161
+ - **App identity, not user identity** — the gateway injects a *shared* upstream credential
162
+ (e.g. Linear writes appear as the shared app actor, not the signed-in person).
163
+ - **Hosts** follow the same `service` convention as `gatewayFetch`: `linear`/`hubspot`
164
+ resolve to `https://<name>.internal.conveo.ai` (override with
165
+ `AGENTGATEWAY_SERVICE_<NAME>_URL` / `AGENTGATEWAY_SERVICE_DOMAIN`, or a per-call
166
+ `baseUrl`). PostgREST is a path on the API base (`AGENTGATEWAY_API_URL`).
167
+ - **Errors:** `401` = OIDC off, wrong project slug, or route not granted; `403` = the
168
+ upstream credential's scopes don't cover the call (a gateway-side change).
169
+
106
170
  ## How it works
107
171
 
108
172
  ![How it works](docs/how-it-works.svg)
@@ -137,6 +201,9 @@ The SDK is a pure, environment-free core with thin adapters per runtime:
137
201
  | `…/vercel` | root `middleware.ts` of any non-Next Vercel app (Vite SPA etc.) |
138
202
  | `…/supabase` | `requireGatewayUser(req)` in Deno edge functions |
139
203
  | `…/server` | `getGatewayUser()`, `gatewayFetch()` in Next server code |
204
+ | `…/linear` | `getLinearClient()` — Linear via the gateway ([integrations](#third-party-sdks-through-the-gateway)) |
205
+ | `…/hubspot` | `getHubspotClient()` — HubSpot via the gateway |
206
+ | `…/postgrest` | `postgrest()` — Conveo Postgres replica via the gateway |
140
207
 
141
208
  ### What the middleware verifies
142
209
 
@@ -0,0 +1,65 @@
1
+ export interface IntegrationOptions {
2
+ /**
3
+ * Override the resolved gateway host (full base URL). Precedence: this >
4
+ * `AGENTGATEWAY_SERVICE_<NAME>_URL` env > `https://<name>.<domain>` convention.
5
+ */
6
+ baseUrl?: string;
7
+ /**
8
+ * Supply the bearer token yourself. Escape hatch for non-Vercel runtimes and
9
+ * tests; by default the app's Vercel OIDC token is fetched fresh per call.
10
+ */
11
+ getToken?: () => Promise<string> | string;
12
+ /**
13
+ * Inject a `fetch` implementation. Used by the custom-fetch integrations and the
14
+ * raw helpers (e.g. tests). Ignored by static-token vendor SDKs (Linear, HubSpot)
15
+ * that take the token at construction and use their own transport.
16
+ */
17
+ fetch?: typeof fetch;
18
+ }
19
+ /** Resolve the bearer token: the caller's `getToken`, else the app's Vercel OIDC token. */
20
+ export declare function resolveToken(opts?: IntegrationOptions): Promise<string>;
21
+ /**
22
+ * A `fetch` that attaches `Authorization: Bearer <token>` — but only to the gateway
23
+ * origin. Handed to custom-fetch vendor SDKs (e.g. Notion) and used by the raw
24
+ * helpers. Refuses any other origin so a misconfigured `baseUrl` can't leak the token.
25
+ */
26
+ export declare function createGatewayFetch(allowedHost: string, opts?: IntegrationOptions): typeof fetch;
27
+ /**
28
+ * Typed result for the raw (non-vendor-SDK) helpers. Distinguishes a gateway
29
+ * rejection (401, your OIDC token / grant) from an upstream scope error (403) from
30
+ * a network failure (`unreachable`) — so callers never fabricate data on failure.
31
+ */
32
+ export type GatewayResult<T> = {
33
+ ok: true;
34
+ kind: "ok";
35
+ status: number;
36
+ data: T;
37
+ } | {
38
+ ok: false;
39
+ kind: "unauthenticated";
40
+ status: number;
41
+ error: string;
42
+ } | {
43
+ ok: false;
44
+ kind: "unauthorized";
45
+ status: number;
46
+ error: string;
47
+ } | {
48
+ ok: false;
49
+ kind: "unreachable";
50
+ status: 0;
51
+ error: string;
52
+ } | {
53
+ ok: false;
54
+ kind: "error";
55
+ status: number;
56
+ error: string;
57
+ };
58
+ /** Map a `Response` to a {@link GatewayResult}. Assumes a JSON body on success. */
59
+ export declare function toGatewayResult<T>(response: Response): Promise<GatewayResult<T>>;
60
+ /**
61
+ * Fetch a gateway JSON endpoint and map it to a {@link GatewayResult}. A network
62
+ * failure (`fetch` throwing a `TypeError`) becomes `unreachable`; config/origin/token
63
+ * errors propagate (they are programmer errors, not request outcomes).
64
+ */
65
+ export declare function gatewayJson<T>(url: string, init: RequestInit, opts?: IntegrationOptions): Promise<GatewayResult<T>>;
@@ -0,0 +1 @@
1
+ import{getAppToken as c}from"../internal.js";function u(t){return Promise.resolve(t?.getToken?t.getToken():c(process.env))}function f(t,r){const o=r?.fetch??fetch,n=new URL(t).origin;return(async(e,a)=>{const s=e instanceof URL?e:new URL(typeof e=="string"?e:e.url);if(s.origin!==n)throw new Error(`gateway fetch refused: ${s.origin} is not the gateway origin ${n}`);const i=new Headers(a?.headers??(typeof e=="object"&&"headers"in e?e.headers:void 0));return i.set("authorization",`Bearer ${await u(r)}`),o(s,{...a,headers:i})})}async function h(t){try{return await t.text()}catch{return""}}async function d(t){if(t.ok)return{ok:!0,kind:"ok",status:t.status,data:await t.json()};const r=await h(t);return t.status===401?{ok:!1,kind:"unauthenticated",status:401,error:r}:t.status===403?{ok:!1,kind:"unauthorized",status:403,error:r}:{ok:!1,kind:"error",status:t.status,error:r}}async function k(t,r,o){const n=f(t,o);let e;try{e=await n(t,r)}catch(a){if(a instanceof TypeError)return{ok:!1,kind:"unreachable",status:0,error:a.message};throw a}return d(e)}export{f as createGatewayFetch,k as gatewayJson,u as resolveToken,d as toGatewayResult};
@@ -0,0 +1,24 @@
1
+ /**
2
+ * HubSpot via the agent gateway, using the official `@hubspot/api-client`.
3
+ *
4
+ * The app never holds a HubSpot key: the client's `basePath` is the gateway
5
+ * (`hubspot.internal.conveo.ai`, paths map 1:1 to `api.hubapi.com`) and its
6
+ * `accessToken` is the app's Vercel OIDC token; the gateway validates it and swaps
7
+ * in the shared HubSpot private-app token upstream.
8
+ *
9
+ * ```ts
10
+ * import { getHubspotClient } from "@conveo/conveo-sdk/hubspot";
11
+ *
12
+ * export async function GET() {
13
+ * const hs = await getHubspotClient(); // build per request — token rotates
14
+ * const page = await hs.crm.deals.basicApi.getPage(10);
15
+ * return Response.json(page.results);
16
+ * }
17
+ * ```
18
+ *
19
+ * Static-token bucket: `@hubspot/api-client` has no custom-fetch seam and bakes the
20
+ * token in at construction, so build a fresh client per request. SERVER-SIDE ONLY.
21
+ */
22
+ import { Client } from "@hubspot/api-client";
23
+ import { type IntegrationOptions } from "./_shared.js";
24
+ export declare function getHubspotClient(opts?: IntegrationOptions): Promise<Client>;
@@ -0,0 +1 @@
1
+ import{Client as r}from"@hubspot/api-client";import{resolveServiceUrl as o}from"../internal.js";import{resolveToken as t}from"./_shared.js";async function a(e){return new r({basePath:e?.baseUrl??o("hubspot",process.env),accessToken:await t(e)})}export{a as getHubspotClient};
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Linear via the agent gateway, using the official `@linear/sdk`.
3
+ *
4
+ * The app never holds a Linear token: the client is pointed at the gateway
5
+ * (`linear.internal.conveo.ai/graphql`) and authenticates with the app's Vercel
6
+ * OIDC token; the gateway validates it and attaches the shared Linear app token
7
+ * upstream (writes show as the shared app actor, not a person).
8
+ *
9
+ * ```ts
10
+ * import { getLinearClient } from "@conveo/conveo-sdk/linear";
11
+ *
12
+ * export async function GET() {
13
+ * const linear = await getLinearClient(); // build per request — token rotates
14
+ * const issues = await linear.issues({ first: 10 });
15
+ * return Response.json(issues.nodes);
16
+ * }
17
+ * ```
18
+ *
19
+ * Static-token bucket: `@linear/sdk` bakes the token in at construction, so build
20
+ * a fresh client per request. SERVER-SIDE ONLY (needs the Vercel OIDC token).
21
+ */
22
+ import { LinearClient } from "@linear/sdk";
23
+ import { type IntegrationOptions } from "./_shared.js";
24
+ export declare function getLinearClient(opts?: IntegrationOptions): Promise<LinearClient>;
@@ -0,0 +1 @@
1
+ import{LinearClient as n}from"@linear/sdk";import{resolveServiceUrl as o}from"../internal.js";import{resolveToken as i}from"./_shared.js";async function s(e){const r=e?.baseUrl??`${o("linear",process.env)}/graphql`;return new n({apiUrl:r,accessToken:await i(e)})}export{s as getLinearClient};
@@ -0,0 +1,35 @@
1
+ /**
2
+ * PostgREST (the Conveo central Postgres replica) via the agent gateway.
3
+ *
4
+ * No vendor SDK — a thin typed helper over the gateway. The app authenticates with
5
+ * its Vercel OIDC token; the gateway validates it and forwards to PostgREST under
6
+ * the `/postgrest` mount on the API base (the prefix is stripped before PostgREST
7
+ * sees it). Table names are PascalCase.
8
+ *
9
+ * Unlike Linear/HubSpot (host-based `service` routes), PostgREST is a path under the
10
+ * API base — so it resolves from `AGENTGATEWAY_API_URL` (default `gateway.ops.conveo.ai`),
11
+ * matching `gatewayFetch`'s `base: "api"`.
12
+ *
13
+ * ```ts
14
+ * import { postgrest } from "@conveo/conveo-sdk/postgrest";
15
+ *
16
+ * const res = await postgrest<Org[]>("Organization?select=id,name&limit=100");
17
+ * if (!res.ok) return Response.json({ error: res.kind }, { status: res.status });
18
+ * return Response.json(res.data);
19
+ * ```
20
+ */
21
+ import { type GatewayResult, type IntegrationOptions } from "./_shared.js";
22
+ /**
23
+ * Run a PostgREST query (e.g. `"Organization?select=id,name&limit=100"`). Pass
24
+ * `init` for writes (`method`, `body`, …).
25
+ */
26
+ export declare function postgrest<T>(query: string, init?: RequestInit, opts?: IntegrationOptions): Promise<GatewayResult<T>>;
27
+ /**
28
+ * Run a PostgREST query and also return the exact total row count (sends
29
+ * `Prefer: count=exact` and parses the `Content-Range` response header). `total`
30
+ * is `null` if PostgREST didn't return a count.
31
+ */
32
+ export declare function postgrestWithCount<T>(query: string, init?: RequestInit, opts?: IntegrationOptions): Promise<GatewayResult<{
33
+ rows: T;
34
+ total: number | null;
35
+ }>>;
@@ -0,0 +1 @@
1
+ import{createGatewayFetch as p,gatewayJson as f}from"./_shared.js";const h="https://gateway.ops.conveo.ai";function c(a,r){const e=(r?.baseUrl??`${process.env.AGENTGATEWAY_API_URL??h}/postgrest`).replace(/\/+$/,""),n=a.replace(/^\/?(postgrest\/)?/,"");return`${e}/${n}`}function k(a,r={},e){return f(c(a,e),r,e)}async function g(a,r={},e){const n=c(a,e),i=p(n,e),o=new Headers(r.headers);o.set("prefer","count=exact");let t;try{t=await i(n,{...r,headers:o})}catch(s){if(s instanceof TypeError)return{ok:!1,kind:"unreachable",status:0,error:s.message};throw s}if(!t.ok){const s=await t.text().catch(()=>"");return t.status===401?{ok:!1,kind:"unauthenticated",status:401,error:s}:t.status===403?{ok:!1,kind:"unauthorized",status:403,error:s}:{ok:!1,kind:"error",status:t.status,error:s}}const l=await t.json(),u=Number(t.headers.get("content-range")?.split("/")[1]);return{ok:!0,kind:"ok",status:t.status,data:{rows:l,total:Number.isFinite(u)?u:null}}}export{k as postgrest,g as postgrestWithCount};
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Internal, runtime-agnostic gateway helpers shared by `./server` and the
3
+ * third-party integrations (`./linear`, `./hubspot`, `./postgrest`).
4
+ *
5
+ * Deliberately free of `next` (and any runtime-specific) imports so the
6
+ * integration subpaths can reuse the same host/token resolution without dragging
7
+ * Next.js into a non-Next consumer.
8
+ */
9
+ /**
10
+ * Resolve a host-based, gateway-fronted vendor API to its base URL. Honours
11
+ * `AGENTGATEWAY_SERVICE_<NAME>_URL`, else `https://<name>.<AGENTGATEWAY_SERVICE_DOMAIN
12
+ * ?? internal.conveo.ai>`.
13
+ */
14
+ export declare function resolveServiceUrl(service: string, env: NodeJS.ProcessEnv): string;
15
+ /**
16
+ * The app's own identity token (Vercel OIDC). Prefers `getVercelOidcToken()` from
17
+ * `@vercel/functions` — it refreshes the short-lived token in dev and reads the
18
+ * runtime token in prod — but that's an OPTIONAL peer dep, so we fall back to the
19
+ * raw `VERCEL_OIDC_TOKEN` env var when it isn't installed.
20
+ */
21
+ export declare function getAppToken(env: NodeJS.ProcessEnv): Promise<string>;
@@ -0,0 +1 @@
1
+ const n=/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;function a(e,t){if(!n.test(e))throw new Error(`invalid service name "${e}" \u2014 expected a lowercase DNS label (letters, digits, dashes)`);const r=t[`AGENTGATEWAY_SERVICE_${e.toUpperCase().replace(/-/g,"_")}_URL`];if(r)return r;const o=t.AGENTGATEWAY_SERVICE_DOMAIN??"internal.conveo.ai";return`https://${e}.${o}`}async function c(e){try{const{getVercelOidcToken:r}=await import("@vercel/functions/oidc"),o=await r();if(o)return o}catch{}const t=e.VERCEL_OIDC_TOKEN;if(!t)throw new Error("VERCEL_OIDC_TOKEN is not available \u2014 enable OIDC federation on the Vercel project (or `vercel env pull` for local dev). Install @vercel/functions for automatic token refresh.");return t}export{c as getAppToken,a as resolveServiceUrl};
package/dist/server.js CHANGED
@@ -1 +1 @@
1
- import{headers as f}from"next/headers";import{configFromEnv as d,isDevelopment as w}from"./adapters/env.js";import{GatewayAuthError as l,IDENTITY_HEADERS as m}from"./core/types.js";import{createVerifier as v}from"./core/verify.js";let E;function A(){return E??=v(d(process.env)),E}async function V(){const e=(await f()).get(m.token);if(!e){const t=process.env.AGENTGATEWAY_DEV_MOCK_USER;if(t&&w(process.env)){const o=t.toLowerCase();return{sub:"dev-mock-user",email:o,emailVerified:!0,token:"",claims:{sub:"dev-mock-user",email:o}}}throw new l("unauthenticated","no gateway user on this request \u2014 public path, optional auth, or middleware not applied")}return A().verifyToken(e)}const g={api:"https://gateway.ops.conveo.ai",mcp:"https://mcp.ops.conveo.ai"},_=/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;function y(r,e){if(!_.test(r))throw new Error(`gatewayFetch: invalid service name "${r}" \u2014 expected a lowercase DNS label (letters, digits, dashes)`);const t=e[`AGENTGATEWAY_SERVICE_${r.toUpperCase().replace(/-/g,"_")}_URL`];if(t)return t;const o=e.AGENTGATEWAY_SERVICE_DOMAIN??"internal.conveo.ai";return`https://${r}.${o}`}async function T(r){try{const{getVercelOidcToken:t}=await import("@vercel/functions/oidc"),o=await t();if(o)return o}catch{}const e=r.VERCEL_OIDC_TOKEN;if(!e)throw new Error("VERCEL_OIDC_TOKEN is not available \u2014 enable OIDC federation on the Vercel project (or `vercel env pull` for local dev). Install @vercel/functions for automatic token refresh.");return e}async function I(r,e={}){const{user:t,identity:o=e.user?"user":"app",base:c,service:u,...h}=e,a=process.env;let n;if(u!==void 0){if(c!==void 0)throw new Error("gatewayFetch: pass either `service` or `base`, not both");n=y(u,a)}else{const p=c??"api";n=(p==="mcp"?a.AGENTGATEWAY_MCP_URL:a.AGENTGATEWAY_API_URL)??g[p]}const i=new URL(r,n.endsWith("/")?n:`${n}/`);if(i.origin!==new URL(n).origin)throw new Error(`gatewayFetch path must stay on the gateway origin (resolved to ${i.origin})`);const s=new Headers(h.headers);if(o==="user"){if(!t?.token)throw new l("unauthenticated",'gatewayFetch with identity "user" needs a verified user with a token (mock users have none)');s.set("authorization",`Bearer ${t.token}`)}else s.set("authorization",`Bearer ${await T(a)}`);return fetch(i,{...h,headers:s})}import{GatewayAuthError as b}from"./core/types.js";export{b as GatewayAuthError,I as gatewayFetch,V as getGatewayUser};
1
+ import{headers as f}from"next/headers";import{configFromEnv as m,isDevelopment as v}from"./adapters/env.js";import{GatewayAuthError as w,IDENTITY_HEADERS as E}from"./core/types.js";import{createVerifier as l}from"./core/verify.js";import{getAppToken as g,resolveServiceUrl as y}from"./internal.js";let d;function A(){return d??=l(m(process.env)),d}async function F(){const t=(await f()).get(E.token);if(!t){const r=process.env.AGENTGATEWAY_DEV_MOCK_USER;if(r&&v(process.env)){const o=r.toLowerCase();return{sub:"dev-mock-user",email:o,emailVerified:!0,token:"",claims:{sub:"dev-mock-user",email:o}}}throw new w("unauthenticated","no gateway user on this request \u2014 public path, optional auth, or middleware not applied")}return A().verifyToken(t)}const k={api:"https://gateway.ops.conveo.ai",mcp:"https://mcp.ops.conveo.ai"};async function L(s,t={}){const{user:r,identity:o=t.user?"user":"app",base:c,service:u,...h}=t,n=process.env;let e;if(u!==void 0){if(c!==void 0)throw new Error("gatewayFetch: pass either `service` or `base`, not both");e=y(u,n)}else{const p=c??"api";e=(p==="mcp"?n.AGENTGATEWAY_MCP_URL:n.AGENTGATEWAY_API_URL)??k[p]}const i=new URL(s,e.endsWith("/")?e:`${e}/`);if(i.origin!==new URL(e).origin)throw new Error(`gatewayFetch path must stay on the gateway origin (resolved to ${i.origin})`);const a=new Headers(h.headers);if(o==="user"){if(!r?.token)throw new w("unauthenticated",'gatewayFetch with identity "user" needs a verified user with a token (mock users have none)');a.set("authorization",`Bearer ${r.token}`)}else a.set("authorization",`Bearer ${await g(n)}`);return fetch(i,{...h,headers:a})}import{GatewayAuthError as H}from"./core/types.js";export{H as GatewayAuthError,L as gatewayFetch,F as getGatewayUser};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conveo/conveo-sdk",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "SDK for Conveo apps behind the agent gateway: drop-in auth middleware (Next.js / Vercel / Supabase), server helpers, and a dev-token CLI.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -39,6 +39,18 @@
39
39
  "./server": {
40
40
  "types": "./dist/server.d.ts",
41
41
  "default": "./dist/server.js"
42
+ },
43
+ "./linear": {
44
+ "types": "./dist/integrations/linear.d.ts",
45
+ "default": "./dist/integrations/linear.js"
46
+ },
47
+ "./hubspot": {
48
+ "types": "./dist/integrations/hubspot.d.ts",
49
+ "default": "./dist/integrations/hubspot.js"
50
+ },
51
+ "./postgrest": {
52
+ "types": "./dist/integrations/postgrest.d.ts",
53
+ "default": "./dist/integrations/postgrest.js"
42
54
  }
43
55
  },
44
56
  "bin": {
@@ -56,10 +68,18 @@
56
68
  "jose": "^6.2.3"
57
69
  },
58
70
  "peerDependencies": {
71
+ "@hubspot/api-client": ">=10",
72
+ "@linear/sdk": ">=30",
59
73
  "@vercel/functions": ">=2",
60
74
  "next": ">=14"
61
75
  },
62
76
  "peerDependenciesMeta": {
77
+ "@hubspot/api-client": {
78
+ "optional": true
79
+ },
80
+ "@linear/sdk": {
81
+ "optional": true
82
+ },
63
83
  "@vercel/functions": {
64
84
  "optional": true
65
85
  },
@@ -68,6 +88,8 @@
68
88
  }
69
89
  },
70
90
  "devDependencies": {
91
+ "@hubspot/api-client": "^14.0.0",
92
+ "@linear/sdk": "^87.0.0",
71
93
  "@types/node": "^24.0.0",
72
94
  "@vercel/functions": "^3.7.1",
73
95
  "esbuild": "^0.28.1",