@cosmoops/connect-sdk 0.21.0 → 0.24.0

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.
@@ -7,7 +7,7 @@ import { useConnect, useConnectEntitlements, useConnectPlans } from "../provider
7
7
  import { buildCancelUrl, buildCheckoutUrl, useConnectCheckout, } from "../use-checkout.js";
8
8
  import { AnimatePresence, ENTER, m, MotionProvider } from "./motion.js";
9
9
  import { ConnectSubscribeDialog } from "./subscribe-dialog.js";
10
- import { ACTION_LABEL, discountLabel, formatPrice, groupPlansByTier, intervalLabel, isFreePlan, pricePerLabel, resolveAction, } from "./plan-pricing.js";
10
+ import { ACTION_LABEL, checkoutSubscriptionId, discountLabel, formatPrice, groupPlansByTier, intervalLabel, isFreePlan, pricePerLabel, resolveAction, } from "./plan-pricing.js";
11
11
  /**
12
12
  * The plan table: monthly/annual toggle, features, add-ons, the discount
13
13
  * actually in effect, and the subscribe / upgrade / downgrade flows.
@@ -80,8 +80,13 @@ export function ConnectPlans({ planIds, hideCoupon = false, className }) {
80
80
  * created in `"created"` and only flips to active when the payment webhook
81
81
  * lands, which can be minutes later. Holding a customer on a spinner until
82
82
  * then would misreport a completed checkout as an abandoned one.
83
+ *
84
+ * `action` is threaded through rather than re-derived: it is the same
85
+ * decision the card's label came from, and `checkoutSubscriptionId` is what
86
+ * keeps the two from disagreeing - see its doc comment for the 404 that
87
+ * disagreement produced.
83
88
  */
84
- function startCheckout(plan) {
89
+ function startCheckout(plan, action) {
85
90
  if (!applicationId)
86
91
  return;
87
92
  checkout.start({
@@ -89,7 +94,7 @@ export function ConnectPlans({ planIds, hideCoupon = false, className }) {
89
94
  appUrl,
90
95
  applicationId,
91
96
  planId: plan.id,
92
- subscriptionId: subscription?.subscription?.id ?? null,
97
+ subscriptionId: checkoutSubscriptionId(action, subscription),
93
98
  coupon,
94
99
  }),
95
100
  isSettled: (next) => next.subscription.plan?.id === plan.id,
@@ -139,7 +144,7 @@ export function ConnectPlans({ planIds, hideCoupon = false, className }) {
139
144
  // plan would race the first and one of them would be
140
145
  // rejected on arrival.
141
146
  isDisabled: isCurrent || checkout.status === "waiting", onPress: () => setPending({ plan, action }), children: [ACTION_LABEL[action], !isCurrent && _jsx(ExternalLink, { className: "size-3.5" })] }) })] }) }, tier.planGroup));
142
- }) }), subscription?.subscription && (_jsx(ConnectUnsubscribeRow, { subscription: subscription.subscription, appUrl: appUrl, checkout: checkout })), _jsx(ConnectSubscribeDialog, { plan: pending?.plan ?? null, action: pending?.action ?? "subscribe", current: subscription, coupon: hideCoupon ? undefined : coupon, onClose: () => setPending(null), onConfirm: () => pending && startCheckout(pending.plan) })] }) }));
147
+ }) }), subscription?.subscription && (_jsx(ConnectUnsubscribeRow, { subscription: subscription.subscription, appUrl: appUrl, checkout: checkout })), _jsx(ConnectSubscribeDialog, { plan: pending?.plan ?? null, action: pending?.action ?? "subscribe", current: subscription, coupon: hideCoupon ? undefined : coupon, onClose: () => setPending(null), onConfirm: () => pending && startCheckout(pending.plan, pending.action) })] }) }));
143
148
  }
144
149
  /**
145
150
  * What the page says while the customer is off in Connect, and after.
@@ -105,7 +105,13 @@ export { ConnectConfirmDialog } from "./components/confirm-dialog.js";
105
105
  export { ConnectCursorPagination } from "./components/cursor-pagination.js";
106
106
  export { ConnectFilterTabs } from "./components/filter-tabs.js";
107
107
  export { useConnectCheckout, buildCheckoutUrl } from "./use-checkout.js";
108
- export { formatPrice, monthlyOf, resolveAction, annualSaving, discountLabel, ACTION_LABEL,
108
+ export { formatPrice, monthlyOf, resolveAction,
109
+ // The other half of `resolveAction`, and exported with it deliberately: a
110
+ // partner writing its own pricing grid has to turn that action into a
111
+ // checkout URL, and reading the subscription id straight off the
112
+ // entitlements response instead is what sent a "Subscribe" button to
113
+ // Connect's change-plan screen. See its doc comment.
114
+ checkoutSubscriptionId, annualSaving, discountLabel, ACTION_LABEL,
109
115
  // Exported for the same reason `groupPlansByTier` is: a partner writing its
110
116
  // own pricing grid has to draw a ₹0 plan as "Free" rather than as a price
111
117
  // with a cadence and a GST rate attached, and re-deriving "is this free"
@@ -389,7 +389,7 @@ export function useConnectPlans() {
389
389
  * `CompanyProfile`. `<ConnectGrievanceNotice>` renders it for you.
390
390
  */
391
391
  export function useConnectCompanyProfile() {
392
- return useConnectResource("/company-profile");
392
+ return useConnectResource("/company-profile", { requiresSession: false });
393
393
  }
394
394
  /** The apps this workspace can open. */
395
395
  export function useConnectApps() {
@@ -430,10 +430,15 @@ export function useConnectSessions() {
430
430
  /**
431
431
  * One bridge read, shared through the provider's cache.
432
432
  *
433
- * Only fetches once a user is known: every resource route needs a session, so
434
- * firing before one exists just buys a guaranteed 401.
433
+ * Waits for a session by default: every resource route but one needs it, so
434
+ * firing before one exists just buys a guaranteed 401. `requiresSession:
435
+ * false` opts out, and the reason it exists is the grievance notice - a
436
+ * statutory contact is at its most useful on a public page a signed-out
437
+ * visitor is reading, so a hook that answered `null` until someone logged in
438
+ * would render nothing at exactly the moment that matters.
435
439
  */
436
- export function useConnectResource(path) {
440
+ export function useConnectResource(path, options) {
441
+ const requiresSession = options?.requiresSession ?? true;
437
442
  const { fetchResource, session, isLoading: sessionLoading } = useConnect();
438
443
  // Stamped with the `path` it belongs to, so switching paths reports loading
439
444
  // without a synchronous `setIsLoading(true)` - and, more usefully, so a
@@ -444,7 +449,7 @@ export function useConnectResource(path) {
444
449
  // Signed out is handled by derivation below rather than by writing state
445
450
  // here - a synchronous setState inside an effect costs an extra render pass
446
451
  // and is what `react-hooks/set-state-in-effect` exists to catch.
447
- if (sessionLoading || !session?.user)
452
+ if (requiresSession && (sessionLoading || !session?.user))
448
453
  return;
449
454
  let active = true;
450
455
  fetchResource(path)
@@ -463,8 +468,10 @@ export function useConnectResource(path) {
463
468
  // `version` is otherwise unread - it exists purely to force this effect
464
469
  // to re-run on `refetch()`, after the caller has already evicted this
465
470
  // path's cache entry via `mutateResource`.
466
- }, [fetchResource, path, session?.user, sessionLoading, version]);
467
- const signedOut = !sessionLoading && !session?.user;
471
+ }, [fetchResource, path, requiresSession, session?.user, sessionLoading, version]);
472
+ // A public read is never settled by the absence of a session - it is
473
+ // genuinely pending until the fetch lands.
474
+ const signedOut = requiresSession && !sessionLoading && !session?.user;
468
475
  const loaded = entry?.path === path ? entry : null;
469
476
  return {
470
477
  data: loaded?.data ?? null,
@@ -1,6 +1,7 @@
1
1
  import { AccessResource } from "./resources/access.js";
2
2
  import { AppsResource } from "./resources/apps.js";
3
3
  import { AiResource } from "./resources/ai.js";
4
+ import { EnrichmentResource } from "./resources/enrichment.js";
4
5
  import { DataResource, storePassthrough } from "./resources/data.js";
5
6
  import { WorkspacesResource } from "./resources/workspaces.js";
6
7
  import { SchedulingResource } from "./resources/scheduling.js";
@@ -90,6 +91,7 @@ export class ConnectClient {
90
91
  calls;
91
92
  /** RAG chat and structured extraction. */
92
93
  ai;
94
+ enrichment;
93
95
  /** Your application's own document store - see `DataResource`. */
94
96
  data;
95
97
  /** Your application's key/value store - see `KvResource`. */
@@ -141,6 +143,7 @@ export class ConnectClient {
141
143
  this.channels = new ChannelsResource(transport);
142
144
  this.calls = new CallsResource(transport);
143
145
  this.ai = new AiResource(transport);
146
+ this.enrichment = new EnrichmentResource(transport);
144
147
  this.data = new DataResource(transport);
145
148
  this.kv = new KvResource(transport);
146
149
  this.otp = new OtpResource(transport);
@@ -154,4 +157,4 @@ export class ConnectClient {
154
157
  }
155
158
  }
156
159
  /** Kept in step with `package.json` by `sdk.test.ts`. */
157
- export const SDK_VERSION = "0.21.0";
160
+ export const SDK_VERSION = "0.24.0";
@@ -602,8 +602,14 @@ export function createConnectHandler(options) {
602
602
  const { workspaceId } = await requireWorkspace(request);
603
603
  return json({ toolkits: await client.integrations.list(workspaceId) });
604
604
  },
605
+ /*
606
+ * `userId` comes from the session alongside `workspaceId`, never from the
607
+ * body - it is stored as `connectedByUserId`, so letting the browser name
608
+ * it would let anyone sign a workspace-shared connection with someone
609
+ * else's name.
610
+ */
605
611
  "POST /integrations/connect": async (request) => {
606
- const { workspaceId } = await requireWorkspace(request);
612
+ const { workspaceId, user } = await requireWorkspace(request);
607
613
  const body = (await request.json().catch(() => null));
608
614
  if (!body?.toolkitSlug || !body?.returnUrl) {
609
615
  throw new ConnectError({
@@ -613,7 +619,11 @@ export function createConnectHandler(options) {
613
619
  endpoint: "POST /integrations/connect",
614
620
  });
615
621
  }
616
- return json(await client.integrations.initiateConnect(workspaceId, body.toolkitSlug, body.returnUrl));
622
+ return json(await client.integrations.initiateConnect(workspaceId, {
623
+ toolkitSlug: body.toolkitSlug,
624
+ returnUrl: body.returnUrl,
625
+ userId: user.userId,
626
+ }));
617
627
  },
618
628
  "POST /integrations/disconnect": async (request) => {
619
629
  const { workspaceId } = await requireWorkspace(request);
@@ -0,0 +1,52 @@
1
+ import { actingAs } from "./acting-as.js";
2
+ import { request } from "../http.js";
3
+ /**
4
+ * What Connect knows about the company at a domain.
5
+ *
6
+ * The thing behind "the account filled itself in": a record holding nothing
7
+ * but `priya@acme.com` can offer a name, an industry, a size band and an
8
+ * address instead of an empty form.
9
+ *
10
+ * Two properties are worth knowing before you wire it in, because they decide
11
+ * how you should render the result:
12
+ *
13
+ * - **Every value is cited, and nothing is recalled.** Connect reads the
14
+ * company's own website and returns the URL each claim came from. A field
15
+ * the pages never stated comes back `null` rather than as a plausible guess.
16
+ * - **It is a suggestion, not a save.** Nothing is written to your store, and
17
+ * nothing should be written to it without the person in front of it
18
+ * agreeing - which is also how HubSpot and Zoho present the same feature.
19
+ */
20
+ export class EnrichmentResource {
21
+ transport;
22
+ constructor(transport) {
23
+ this.transport = transport;
24
+ }
25
+ /**
26
+ * Look a company up from a work email address or a domain.
27
+ *
28
+ * Send whichever you hold; `domain` wins when both are given. A consumer
29
+ * address (`founder@gmail.com`) is answered with
30
+ * `{ status: "skipped", reason: "consumer_domain" }` rather than an error -
31
+ * it names a person, not an employer.
32
+ *
33
+ * `asUser` attributes the generation to the signed-in caller's workspace,
34
+ * resolved from their session token. Without it the call is attributed to
35
+ * your application.
36
+ */
37
+ company(input, options = {}) {
38
+ return request(this.transport, {
39
+ method: "POST",
40
+ path: "/enrichment/company",
41
+ // `asUser` never reaches the body - it is a credential, and Connect
42
+ // reads it from the header.
43
+ json: {
44
+ ...(input.email ? { email: input.email } : {}),
45
+ ...(input.domain ? { domain: input.domain } : {}),
46
+ ...(input.refresh ? { refresh: true } : {}),
47
+ },
48
+ headers: actingAs(input.asUser),
49
+ signal: options.signal,
50
+ });
51
+ }
52
+ }
@@ -32,15 +32,17 @@ export class IntegrationsResource {
32
32
  * itself. Reconnecting a disconnected/failed toolkit calls this same
33
33
  * method - there is no separate reconnect.
34
34
  *
35
- * Meant to be called on behalf of a signed-in workspace admin (this is what the
36
- * bridge's fixed `POST /integrations/connect` route does), not as a bare
37
- * background action with no admin present to complete the OAuth screen.
35
+ * Meant to be called on behalf of a signed-in workspace admin (this is what
36
+ * the bridge's fixed `POST /integrations/connect` route does), not as a bare
37
+ * background action with no admin present to complete the OAuth screen. That
38
+ * is why `userId` is part of the input rather than inferred - see
39
+ * `InitiateIntegrationConnectionInput`.
38
40
  */
39
- async initiateConnect(workspaceId, toolkitSlug, returnUrl) {
41
+ async initiateConnect(workspaceId, input) {
40
42
  return request(this.transport, {
41
43
  method: "POST",
42
44
  path: "/integrations/connect",
43
- json: { workspaceId, toolkitSlug, returnUrl },
45
+ json: { workspaceId, ...input },
44
46
  });
45
47
  }
46
48
  /** Idempotent - disconnecting a toolkit that was never connected, or is already disconnected, is a success. */
@@ -1,8 +1,8 @@
1
1
  import { actingAs } from "./acting-as.js";
2
2
  import { request } from "../http.js";
3
3
  /**
4
- * Which workspaces the signed-in user belongs to, moving between them, and who
5
- * else is on the team.
4
+ * Which workspaces the signed-in user belongs to, moving between them, who
5
+ * else is on the team, and inviting or granting access to your application.
6
6
  *
7
7
  * **Every call here needs `asUser`** - the signed-in user's opaque
8
8
  * `sessionToken`. There is no application-wide form of any of these: they take
@@ -67,4 +67,75 @@ export class WorkspacesResource {
67
67
  });
68
68
  return result.members;
69
69
  }
70
+ /**
71
+ * Seats, what the caller may do with them, and the invitations already
72
+ * holding one - everything a team screen needs to decide what to draw.
73
+ *
74
+ * Behind `members:manage` rather than `workspaces:read`, and gated on the
75
+ * caller's own `member:invite`: it names people who are not members yet.
76
+ */
77
+ access(options) {
78
+ return request(this.transport, {
79
+ method: "GET",
80
+ path: "/workspaces/members/access",
81
+ headers: actingAs(options.asUser),
82
+ });
83
+ }
84
+ /**
85
+ * Invite someone into the workspace, with access to your application.
86
+ *
87
+ * `roleKey` names a role from your own catalog (or the reserved `"admin"`);
88
+ * omit it to add someone to the workspace with no access to your
89
+ * application. You cannot send a Connect role - the grant is built server
90
+ * side as `[member, app:<yourKey>:<roleKey>]`, so nothing this method can
91
+ * express reaches outside your application.
92
+ *
93
+ * Spends a member seat when the invitation is created, not when it is
94
+ * accepted. Throws `ConnectError` with status 409 and a message written to
95
+ * be shown when there is no seat, and 403 when the caller is inviting
96
+ * someone with access they do not hold themselves.
97
+ */
98
+ invite(input, options) {
99
+ return request(this.transport, {
100
+ method: "POST",
101
+ path: "/workspaces/members/invite",
102
+ headers: actingAs(options.asUser),
103
+ json: input,
104
+ });
105
+ }
106
+ /**
107
+ * Grant or revoke **your application's** role for an existing member.
108
+ * `roleKey: null` revokes it.
109
+ *
110
+ * Moves only your own grant: Connect recomputes the member's roles as what
111
+ * they hold now, minus any grant naming your application, plus the one you
112
+ * asked for. Their workspace role and every other application they use are
113
+ * untouched, and there is no form of this that removes them from the
114
+ * workspace.
115
+ *
116
+ * Re-sending the role they already have is a no-op rather than an error, so
117
+ * a form that submits whatever is on screen is safe.
118
+ */
119
+ setAccess(input, options) {
120
+ return request(this.transport, {
121
+ method: "POST",
122
+ path: "/workspaces/members/access",
123
+ headers: actingAs(options.asUser),
124
+ json: input,
125
+ });
126
+ }
127
+ /**
128
+ * Withdraw a pending invitation your application sent, freeing its seat.
129
+ *
130
+ * Only yours - an invitation carrying another application's grant is a 404,
131
+ * deliberately indistinguishable from one that does not exist.
132
+ */
133
+ async revokeInvitation(input, options) {
134
+ await request(this.transport, {
135
+ method: "POST",
136
+ path: "/workspaces/invitations/revoke",
137
+ headers: actingAs(options.asUser),
138
+ json: input,
139
+ });
140
+ }
70
141
  }
@@ -117,3 +117,26 @@ export declare function annualSaving(monthly: Plan | null | undefined, annual: P
117
117
  export type PlanAction = "current" | "upgrade" | "downgrade" | "switch-to-annual" | "switch-to-monthly" | "subscribe";
118
118
  export declare function resolveAction(plan: Plan, current: SubscriptionResponse | null): PlanAction;
119
119
  export declare const ACTION_LABEL: Record<PlanAction, string>;
120
+ /**
121
+ * The subscription id a checkout hand-off should carry for `action` - or
122
+ * `null`, meaning "send them to Connect's *subscribe* screen".
123
+ *
124
+ * Paired with `resolveAction` deliberately: the button's label and the URL it
125
+ * opens are one decision, and deriving the URL from the mere *presence* of a
126
+ * subscription row instead is what sent a card reading "Subscribe" to
127
+ * Connect's change-plan screen, which answered `notFound()`.
128
+ *
129
+ * The gap is real rather than hypothetical. `resolveAction` needs the
130
+ * **current plan** to classify a change and answers `"subscribe"` without
131
+ * one - and `SubscriptionResponse` carries `plan: null` beside a perfectly
132
+ * live `subscription` whenever that plan has been deleted from the catalogue
133
+ * out from under it, which a re-seeded application does. Connect's change-plan
134
+ * route is stuck in the same place for the same reason: it cannot prorate
135
+ * against a price it cannot read.
136
+ *
137
+ * Sending those to `/apps/.../subscribe/...` is a real hand-off, not a guess:
138
+ * that route re-reads the workspace's live subscriptions server-side and
139
+ * redirects to the change-plan screen itself when there genuinely is one to
140
+ * change. Connect decides, so the grid does not have to be sure.
141
+ */
142
+ export declare function checkoutSubscriptionId(action: PlanAction, current: SubscriptionResponse | null): string | null;
@@ -11,7 +11,7 @@
11
11
  * change lands identically in all of them.
12
12
  */
13
13
  export { ConnectProvider, useConnect, useConnectUser, useConnectWorkspaceType, useConnectAudience, useConnectPlatformAccess, useConnectEntitlements, useConnectPlans, useConnectApps, useConnectCompanyProfile, useConnectAddonUsage, useConnectSessions, useConnectIntegrations, useConnectResource, CONNECT_APP_URL, } from "./provider.js";
14
- export type { ConnectProviderProps, ConnectContextValue, ConnectSession, ConnectOneTapHint, ConnectOneTapFailure, } from "./provider.js";
14
+ export type { ConnectProviderProps, ConnectContextValue, ConnectSession, ConnectOneTapHint, ConnectOneTapFailure, UseConnectResourceOptions, } from "./provider.js";
15
15
  export { blockingBlockers, normalizePlanFeatures } from "../helpers.js";
16
16
  export type { WirePlanFeature } from "../helpers.js";
17
17
  export { ConnectError, isConnectError } from "../errors.js";
@@ -84,7 +84,7 @@ export { ConnectFilterTabs } from "./components/filter-tabs.js";
84
84
  export type { ConnectFilterTabsProps, ConnectFilterTabsOption } from "./components/filter-tabs.js";
85
85
  export { useConnectCheckout, buildCheckoutUrl } from "./use-checkout.js";
86
86
  export type { ConnectCheckoutStatus, ConnectCheckoutState, ConnectEntitlements, StartCheckoutOptions, CheckoutUrlOptions, } from "./use-checkout.js";
87
- export { formatPrice, monthlyOf, resolveAction, annualSaving, discountLabel, ACTION_LABEL, isFreePlan, groupPlansByTier, resolvePlanGroup, } from "./components/plan-pricing.js";
87
+ export { formatPrice, monthlyOf, resolveAction, checkoutSubscriptionId, annualSaving, discountLabel, ACTION_LABEL, isFreePlan, groupPlansByTier, resolvePlanGroup, } from "./components/plan-pricing.js";
88
88
  export type { PlanAction, PlanTier, ComparablePlan } from "./components/plan-pricing.js";
89
89
  export { ConnectWorkspaceSwitcher } from "./components/workspace-switcher.js";
90
90
  export type { ConnectWorkspaceSwitcherProps } from "./components/workspace-switcher.js";
@@ -456,13 +456,30 @@ export declare function useConnectSessions(): {
456
456
  */
457
457
  refetch(): void;
458
458
  };
459
+ /** Options for {@link useConnectResource}. */
460
+ export interface UseConnectResourceOptions {
461
+ /**
462
+ * Whether the read has to wait for a session. `true` by default, and right
463
+ * for every route but one.
464
+ *
465
+ * Pass `false` only for a bridge route that takes neither a session nor a
466
+ * workspace - today that is `GET /company-profile` alone, and its handler
467
+ * says why. Passing it for anything else does not widen access (the bridge
468
+ * still refuses), it just buys a guaranteed 401 sooner.
469
+ */
470
+ requiresSession?: boolean;
471
+ }
459
472
  /**
460
473
  * One bridge read, shared through the provider's cache.
461
474
  *
462
- * Only fetches once a user is known: every resource route needs a session, so
463
- * firing before one exists just buys a guaranteed 401.
475
+ * Waits for a session by default: every resource route but one needs it, so
476
+ * firing before one exists just buys a guaranteed 401. `requiresSession:
477
+ * false` opts out, and the reason it exists is the grievance notice - a
478
+ * statutory contact is at its most useful on a public page a signed-out
479
+ * visitor is reading, so a hook that answered `null` until someone logged in
480
+ * would render nothing at exactly the moment that matters.
464
481
  */
465
- export declare function useConnectResource<T>(path: string): {
482
+ export declare function useConnectResource<T>(path: string, options?: Readonly<UseConnectResourceOptions>): {
466
483
  data: T | null;
467
484
  isLoading: boolean;
468
485
  error: string | null;
@@ -64,6 +64,15 @@ export interface CheckoutUrlOptions {
64
64
  * plan on this subscription"; absent means "subscribe for the first time" -
65
65
  * and the two are separate screens in Connect, because only one of them can
66
66
  * price proration against a cycle already paid for.
67
+ *
68
+ * Pass it only when you know **which plan the subscription is on today**,
69
+ * which is what makes the change classifiable. `checkoutSubscriptionId`
70
+ * derives it from the action you are already showing on the button; a bare
71
+ * `subscription?.subscription?.id` does not, and a live subscription whose
72
+ * plan has been deleted from the catalogue is the case where the two differ
73
+ * and the change-plan screen answers `notFound()`. Leaving it `null` is
74
+ * always safe: the subscribe route redirects to the change-plan screen
75
+ * itself when Connect finds a subscription to change.
67
76
  */
68
77
  subscriptionId?: string | null;
69
78
  /** Carried into Connect's checkout, where it is validated against the real cart. */
@@ -2,6 +2,7 @@ import type { FetchLike } from "./http.js";
2
2
  import { AccessResource } from "./resources/access.js";
3
3
  import { AppsResource } from "./resources/apps.js";
4
4
  import { AiResource } from "./resources/ai.js";
5
+ import { EnrichmentResource } from "./resources/enrichment.js";
5
6
  import { DataResource } from "./resources/data.js";
6
7
  import { WorkspacesResource } from "./resources/workspaces.js";
7
8
  import { SchedulingResource } from "./resources/scheduling.js";
@@ -89,6 +90,7 @@ export declare class ConnectClient {
89
90
  readonly calls: CallsResource;
90
91
  /** RAG chat and structured extraction. */
91
92
  readonly ai: AiResource;
93
+ readonly enrichment: EnrichmentResource;
92
94
  /** Your application's own document store - see `DataResource`. */
93
95
  readonly data: DataResource;
94
96
  /** Your application's key/value store - see `KvResource`. */
@@ -121,4 +123,4 @@ export declare class ConnectClient {
121
123
  constructor(options: ConnectClientOptions);
122
124
  }
123
125
  /** Kept in step with `package.json` by `sdk.test.ts`. */
124
- export declare const SDK_VERSION = "0.21.0";
126
+ export declare const SDK_VERSION = "0.24.0";
@@ -18,6 +18,7 @@ export type { AppsResource } from "./resources/apps.js";
18
18
  export type { EntitlementsResource } from "./resources/entitlements.js";
19
19
  export type { BillingResource } from "./resources/billing.js";
20
20
  export type { AiResource } from "./resources/ai.js";
21
+ export type { EnrichmentResource } from "./resources/enrichment.js";
21
22
  export type { DataResource } from "./resources/data.js";
22
23
  export type { KvResource } from "./resources/kv.js";
23
24
  export type { WorkspacesResource } from "./resources/workspaces.js";
@@ -25,7 +26,7 @@ export type { OtpResource } from "./resources/otp.js";
25
26
  export type { LogsResource } from "./resources/logs.js";
26
27
  export type { AnalyticsResource } from "./resources/analytics.js";
27
28
  export type { SessionsResource } from "./resources/sessions.js";
28
- export type { ExecuteIntegrationActionInput, IntegrationsResource } from "./resources/integrations.js";
29
+ export type { ExecuteIntegrationActionInput, InitiateIntegrationConnectionInput, IntegrationsResource, } from "./resources/integrations.js";
29
30
  export type { PlatformResource } from "./resources/platform.js";
30
31
  export type { SchedulingResource, FindSlotsInput, ListBookingsInput, CreateBookingInput, RescheduleBookingInput, } from "./resources/scheduling.js";
31
32
  export type { ContactsResource, LookupContactInput } from "./resources/contacts.js";
@@ -0,0 +1,44 @@
1
+ import { type TransportOptions } from "../http.js";
2
+ import type { CompanyEnrichmentResult } from "../../types.js";
3
+ /**
4
+ * What Connect knows about the company at a domain.
5
+ *
6
+ * The thing behind "the account filled itself in": a record holding nothing
7
+ * but `priya@acme.com` can offer a name, an industry, a size band and an
8
+ * address instead of an empty form.
9
+ *
10
+ * Two properties are worth knowing before you wire it in, because they decide
11
+ * how you should render the result:
12
+ *
13
+ * - **Every value is cited, and nothing is recalled.** Connect reads the
14
+ * company's own website and returns the URL each claim came from. A field
15
+ * the pages never stated comes back `null` rather than as a plausible guess.
16
+ * - **It is a suggestion, not a save.** Nothing is written to your store, and
17
+ * nothing should be written to it without the person in front of it
18
+ * agreeing - which is also how HubSpot and Zoho present the same feature.
19
+ */
20
+ export declare class EnrichmentResource {
21
+ private readonly transport;
22
+ constructor(transport: TransportOptions);
23
+ /**
24
+ * Look a company up from a work email address or a domain.
25
+ *
26
+ * Send whichever you hold; `domain` wins when both are given. A consumer
27
+ * address (`founder@gmail.com`) is answered with
28
+ * `{ status: "skipped", reason: "consumer_domain" }` rather than an error -
29
+ * it names a person, not an employer.
30
+ *
31
+ * `asUser` attributes the generation to the signed-in caller's workspace,
32
+ * resolved from their session token. Without it the call is attributed to
33
+ * your application.
34
+ */
35
+ company(input: {
36
+ email?: string;
37
+ domain?: string;
38
+ asUser?: string;
39
+ /** Re-read the site even if Connect has a fresh profile cached. For an operator pressing "refresh" - not for automatic calls, which should take the cache. */
40
+ refresh?: boolean;
41
+ }, options?: {
42
+ signal?: AbortSignal;
43
+ }): Promise<CompanyEnrichmentResult>;
44
+ }
@@ -1,5 +1,19 @@
1
1
  import { type TransportOptions } from "../http.js";
2
2
  import type { DisconnectIntegrationResponse, ExecuteIntegrationActionResponse, InitiateIntegrationConnectionResponse, IntegrationsListResponse } from "../../types.js";
3
+ export interface InitiateIntegrationConnectionInput {
4
+ toolkitSlug: string;
5
+ returnUrl: string;
6
+ /**
7
+ * The signed-in member starting the connection, recorded by Connect as
8
+ * `connectedByUserId`.
9
+ *
10
+ * Required, and deliberately not defaulted: the connection is *shared by
11
+ * the whole workspace*, so "who turned this on" is the only attribution
12
+ * anyone gets - and a call that cannot name a person is one where nobody
13
+ * is present to finish the OAuth screen either.
14
+ */
15
+ userId: string;
16
+ }
3
17
  export interface ExecuteIntegrationActionInput {
4
18
  toolkitSlug: string;
5
19
  actionSlug: string;
@@ -28,11 +42,13 @@ export declare class IntegrationsResource {
28
42
  * itself. Reconnecting a disconnected/failed toolkit calls this same
29
43
  * method - there is no separate reconnect.
30
44
  *
31
- * Meant to be called on behalf of a signed-in workspace admin (this is what the
32
- * bridge's fixed `POST /integrations/connect` route does), not as a bare
33
- * background action with no admin present to complete the OAuth screen.
45
+ * Meant to be called on behalf of a signed-in workspace admin (this is what
46
+ * the bridge's fixed `POST /integrations/connect` route does), not as a bare
47
+ * background action with no admin present to complete the OAuth screen. That
48
+ * is why `userId` is part of the input rather than inferred - see
49
+ * `InitiateIntegrationConnectionInput`.
34
50
  */
35
- initiateConnect(workspaceId: string, toolkitSlug: string, returnUrl: string): Promise<InitiateIntegrationConnectionResponse>;
51
+ initiateConnect(workspaceId: string, input: InitiateIntegrationConnectionInput): Promise<InitiateIntegrationConnectionResponse>;
36
52
  /** Idempotent - disconnecting a toolkit that was never connected, or is already disconnected, is a success. */
37
53
  disconnect(workspaceId: string, toolkitSlug: string): Promise<DisconnectIntegrationResponse>;
38
54
  /** Calls a Composio action against this workspace's connected account for the named toolkit. */
@@ -1,8 +1,8 @@
1
1
  import { type TransportOptions } from "../http.js";
2
- import type { ConnectWorkspaceMember, UserWorkspace, WorkspaceSwitchResult } from "../../types.js";
2
+ import type { ConnectInvitation, ConnectMemberAccess, ConnectMemberAccessResult, ConnectWorkspaceMember, UserWorkspace, WorkspaceSwitchResult } from "../../types.js";
3
3
  /**
4
- * Which workspaces the signed-in user belongs to, moving between them, and who
5
- * else is on the team.
4
+ * Which workspaces the signed-in user belongs to, moving between them, who
5
+ * else is on the team, and inviting or granting access to your application.
6
6
  *
7
7
  * **Every call here needs `asUser`** - the signed-in user's opaque
8
8
  * `sessionToken`. There is no application-wide form of any of these: they take
@@ -52,4 +52,64 @@ export declare class WorkspacesResource {
52
52
  members(options: {
53
53
  asUser: string;
54
54
  }): Promise<ConnectWorkspaceMember[]>;
55
+ /**
56
+ * Seats, what the caller may do with them, and the invitations already
57
+ * holding one - everything a team screen needs to decide what to draw.
58
+ *
59
+ * Behind `members:manage` rather than `workspaces:read`, and gated on the
60
+ * caller's own `member:invite`: it names people who are not members yet.
61
+ */
62
+ access(options: {
63
+ asUser: string;
64
+ }): Promise<ConnectMemberAccess>;
65
+ /**
66
+ * Invite someone into the workspace, with access to your application.
67
+ *
68
+ * `roleKey` names a role from your own catalog (or the reserved `"admin"`);
69
+ * omit it to add someone to the workspace with no access to your
70
+ * application. You cannot send a Connect role - the grant is built server
71
+ * side as `[member, app:<yourKey>:<roleKey>]`, so nothing this method can
72
+ * express reaches outside your application.
73
+ *
74
+ * Spends a member seat when the invitation is created, not when it is
75
+ * accepted. Throws `ConnectError` with status 409 and a message written to
76
+ * be shown when there is no seat, and 403 when the caller is inviting
77
+ * someone with access they do not hold themselves.
78
+ */
79
+ invite(input: {
80
+ inviteeEmail: string;
81
+ roleKey?: string | null;
82
+ }, options: {
83
+ asUser: string;
84
+ }): Promise<ConnectInvitation>;
85
+ /**
86
+ * Grant or revoke **your application's** role for an existing member.
87
+ * `roleKey: null` revokes it.
88
+ *
89
+ * Moves only your own grant: Connect recomputes the member's roles as what
90
+ * they hold now, minus any grant naming your application, plus the one you
91
+ * asked for. Their workspace role and every other application they use are
92
+ * untouched, and there is no form of this that removes them from the
93
+ * workspace.
94
+ *
95
+ * Re-sending the role they already have is a no-op rather than an error, so
96
+ * a form that submits whatever is on screen is safe.
97
+ */
98
+ setAccess(input: {
99
+ userId: string;
100
+ roleKey: string | null;
101
+ }, options: {
102
+ asUser: string;
103
+ }): Promise<ConnectMemberAccessResult>;
104
+ /**
105
+ * Withdraw a pending invitation your application sent, freeing its seat.
106
+ *
107
+ * Only yours - an invitation carrying another application's grant is a 404,
108
+ * deliberately indistinguishable from one that does not exist.
109
+ */
110
+ revokeInvitation(input: {
111
+ invitationId: string;
112
+ }, options: {
113
+ asUser: string;
114
+ }): Promise<void>;
55
115
  }