@cosmoops/connect-sdk 0.9.0 → 0.15.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.
Files changed (139) hide show
  1. package/CHANGELOG.md +497 -0
  2. package/README.md +111 -41
  3. package/dist/cjs/cli/doctor.js +3 -3
  4. package/dist/cjs/cli/init.js +3 -3
  5. package/dist/cjs/client/components/access-gate.js +20 -1
  6. package/dist/cjs/client/components/app-shell.js +75 -41
  7. package/dist/cjs/client/components/apps-grid.js +3 -2
  8. package/dist/cjs/client/components/ask-ai.js +20 -1
  9. package/dist/cjs/client/components/confirm-dialog.js +40 -0
  10. package/dist/cjs/client/components/cursor-pagination.js +22 -0
  11. package/dist/cjs/client/components/filter-tabs.js +24 -0
  12. package/dist/cjs/client/components/grievance-notice.js +30 -0
  13. package/dist/cjs/client/components/link-button.js +17 -10
  14. package/dist/cjs/client/components/logo.js +4 -3
  15. package/dist/cjs/client/components/nav.js +41 -0
  16. package/dist/cjs/client/components/plan-pricing.js +27 -0
  17. package/dist/cjs/client/components/plans.js +1 -1
  18. package/dist/cjs/client/components/platform-gate.js +48 -0
  19. package/dist/cjs/client/components/shell-context.js +149 -0
  20. package/dist/cjs/client/components/side-nav.js +49 -23
  21. package/dist/cjs/client/components/sidebar.js +127 -0
  22. package/dist/cjs/client/components/subscribe-dialog.js +2 -2
  23. package/dist/cjs/client/components/subscription.js +1 -1
  24. package/dist/cjs/client/components/user-menu.js +96 -37
  25. package/dist/cjs/client/components/workspace-switcher.js +100 -0
  26. package/dist/cjs/client/index.js +69 -2
  27. package/dist/cjs/client/provider.js +57 -2
  28. package/dist/cjs/client/use-checkout.js +9 -5
  29. package/dist/cjs/errors.js +1 -1
  30. package/dist/cjs/index.js +11 -1
  31. package/dist/cjs/next/index.js +2 -2
  32. package/dist/cjs/server/client.js +25 -2
  33. package/dist/cjs/server/handler.js +196 -18
  34. package/dist/cjs/server/http.js +6 -2
  35. package/dist/cjs/server/resources/acting-as.js +16 -0
  36. package/dist/cjs/server/resources/ai.js +15 -2
  37. package/dist/cjs/server/resources/data.js +374 -0
  38. package/dist/cjs/server/resources/identity.js +7 -3
  39. package/dist/cjs/server/resources/kv.js +84 -0
  40. package/dist/cjs/server/resources/platform.js +43 -0
  41. package/dist/cjs/server/resources/sessions.js +1 -1
  42. package/dist/cjs/server/resources/workspaces.js +74 -0
  43. package/dist/cjs/store.js +152 -0
  44. package/dist/cjs/ui/empty-state.js +28 -0
  45. package/dist/cjs/ui/index.js +44 -0
  46. package/dist/cjs/ui/page-header.js +27 -0
  47. package/dist/cjs/ui/page-shell.js +44 -0
  48. package/dist/cjs/ui/section.js +20 -0
  49. package/dist/cjs/ui/setting-row.js +14 -0
  50. package/dist/cjs/ui/status-chip.js +17 -0
  51. package/dist/esm/cli/doctor.js +3 -3
  52. package/dist/esm/cli/init.js +3 -3
  53. package/dist/esm/client/components/access-gate.js +20 -1
  54. package/dist/esm/client/components/app-shell.js +81 -47
  55. package/dist/esm/client/components/apps-grid.js +3 -2
  56. package/dist/esm/client/components/ask-ai.js +20 -1
  57. package/dist/esm/client/components/confirm-dialog.js +37 -0
  58. package/dist/esm/client/components/cursor-pagination.js +19 -0
  59. package/dist/esm/client/components/filter-tabs.js +21 -0
  60. package/dist/esm/client/components/grievance-notice.js +27 -0
  61. package/dist/esm/client/components/link-button.js +17 -10
  62. package/dist/esm/client/components/logo.js +4 -3
  63. package/dist/esm/client/components/nav.js +40 -0
  64. package/dist/esm/client/components/plan-pricing.js +25 -0
  65. package/dist/esm/client/components/plans.js +2 -2
  66. package/dist/esm/client/components/platform-gate.js +45 -0
  67. package/dist/esm/client/components/shell-context.js +141 -0
  68. package/dist/esm/client/components/side-nav.js +48 -21
  69. package/dist/esm/client/components/sidebar.js +120 -0
  70. package/dist/esm/client/components/subscribe-dialog.js +3 -3
  71. package/dist/esm/client/components/subscription.js +2 -2
  72. package/dist/esm/client/components/user-menu.js +95 -38
  73. package/dist/esm/client/components/workspace-switcher.js +97 -0
  74. package/dist/esm/client/index.js +46 -4
  75. package/dist/esm/client/provider.js +53 -2
  76. package/dist/esm/client/use-checkout.js +9 -5
  77. package/dist/esm/errors.js +1 -1
  78. package/dist/esm/index.js +1 -0
  79. package/dist/esm/next/index.js +2 -2
  80. package/dist/esm/server/client.js +25 -2
  81. package/dist/esm/server/handler.js +196 -18
  82. package/dist/esm/server/http.js +6 -2
  83. package/dist/esm/server/resources/acting-as.js +13 -0
  84. package/dist/esm/server/resources/ai.js +15 -2
  85. package/dist/esm/server/resources/data.js +369 -0
  86. package/dist/esm/server/resources/identity.js +7 -3
  87. package/dist/esm/server/resources/kv.js +80 -0
  88. package/dist/esm/server/resources/platform.js +39 -0
  89. package/dist/esm/server/resources/sessions.js +1 -1
  90. package/dist/esm/server/resources/workspaces.js +70 -0
  91. package/dist/esm/store.js +141 -0
  92. package/dist/esm/ui/empty-state.js +25 -0
  93. package/dist/esm/ui/index.js +34 -0
  94. package/dist/esm/ui/page-header.js +24 -0
  95. package/dist/esm/ui/page-shell.js +41 -0
  96. package/dist/esm/ui/section.js +17 -0
  97. package/dist/esm/ui/setting-row.js +11 -0
  98. package/dist/esm/ui/status-chip.js +14 -0
  99. package/dist/types/client/components/app-shell.d.ts +65 -27
  100. package/dist/types/client/components/apps-grid.d.ts +11 -2
  101. package/dist/types/client/components/confirm-dialog.d.ts +34 -0
  102. package/dist/types/client/components/cursor-pagination.d.ts +27 -0
  103. package/dist/types/client/components/filter-tabs.d.ts +26 -0
  104. package/dist/types/client/components/grievance-notice.d.ts +31 -0
  105. package/dist/types/client/components/link-button.d.ts +32 -9
  106. package/dist/types/client/components/logo.d.ts +18 -2
  107. package/dist/types/client/components/nav.d.ts +24 -0
  108. package/dist/types/client/components/plan-pricing.d.ts +10 -0
  109. package/dist/types/client/components/platform-gate.d.ts +53 -0
  110. package/dist/types/client/components/shell-context.d.ts +116 -0
  111. package/dist/types/client/components/side-nav.d.ts +27 -13
  112. package/dist/types/client/components/sidebar.d.ts +95 -0
  113. package/dist/types/client/components/user-menu.d.ts +68 -22
  114. package/dist/types/client/components/workspace-switcher.d.ts +49 -0
  115. package/dist/types/client/index.d.ts +27 -5
  116. package/dist/types/client/provider.d.ts +109 -6
  117. package/dist/types/client/use-checkout.d.ts +8 -4
  118. package/dist/types/errors.d.ts +1 -1
  119. package/dist/types/index.d.ts +2 -0
  120. package/dist/types/next/index.d.ts +1 -1
  121. package/dist/types/server/client.d.ts +25 -2
  122. package/dist/types/server/http.d.ts +8 -2
  123. package/dist/types/server/index.d.ts +4 -0
  124. package/dist/types/server/resources/acting-as.d.ts +11 -0
  125. package/dist/types/server/resources/ai.d.ts +9 -1
  126. package/dist/types/server/resources/data.d.ts +258 -0
  127. package/dist/types/server/resources/kv.d.ts +63 -0
  128. package/dist/types/server/resources/platform.d.ts +32 -0
  129. package/dist/types/server/resources/workspaces.d.ts +55 -0
  130. package/dist/types/store.d.ts +96 -0
  131. package/dist/types/types.d.ts +319 -15
  132. package/dist/types/ui/empty-state.d.ts +34 -0
  133. package/dist/types/ui/index.d.ts +40 -0
  134. package/dist/types/ui/page-header.d.ts +34 -0
  135. package/dist/types/ui/page-shell.d.ts +39 -0
  136. package/dist/types/ui/section.d.ts +26 -0
  137. package/dist/types/ui/setting-row.d.ts +18 -0
  138. package/dist/types/ui/status-chip.d.ts +36 -0
  139. package/package.json +6 -2
@@ -1,10 +1,12 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Avatar, Button, Chip, Modal, Skeleton, ToggleButton, ToggleButtonGroup, } from "@heroui/react";
4
- import { CreditCard, ExternalLink, Laptop, LogOut, Moon, ReceiptText, Sun, UserRound, } from "lucide-react";
4
+ import { ChevronsUpDown, CreditCard, ExternalLink, Laptop, LogOut, Moon, ReceiptText, Sun, UserRound, } from "lucide-react";
5
5
  import { useConnect } from "../provider.js";
6
6
  import { ConnectMark } from "../mark.js";
7
7
  import { ConnectAccountSessions } from "./account-sessions.js";
8
+ import { m } from "./motion.js";
9
+ import { SidebarLabel, sidebarRowClass, useConnectShell } from "./shell-context.js";
8
10
  import { ICON, MEDALLION, PANEL_FILL } from "./surfaces.js";
9
11
  const THEME_OPTIONS = [
10
12
  { value: "light", label: "Light", icon: Sun },
@@ -53,7 +55,15 @@ function profileUrl(appUrl, clientId) {
53
55
  * had set on their profile.
54
56
  */
55
57
  function UserAvatar({ user, size, }) {
56
- return (_jsxs(Avatar, { size: size, color: "accent", variant: "soft", children: [user.photoUrl && _jsx(Avatar.Image, { alt: "", src: user.photoUrl }), _jsx(Avatar.Fallback, { children: initials(user.displayName, user.email) })] }));
58
+ return (
59
+ // `rounded-full` is an override, not decoration. HeroUI's `.avatar` is a
60
+ // squircle - `rounded-2xl` at `sm`, `rounded-3xl` at `lg` - which is right
61
+ // for the media component in general and wrong for a *person*: every other
62
+ // product a customer uses draws an account avatar as a circle, and a
63
+ // squircle sitting beside circular status dots reads as a rendering fault
64
+ // rather than a choice. The root is `overflow-hidden`, so rounding it here
65
+ // rounds the image and the fallback with it.
66
+ _jsxs(Avatar, { size: size, color: "accent", variant: "soft", className: "rounded-full", children: [user.photoUrl && _jsx(Avatar.Image, { alt: "", src: user.photoUrl }), _jsx(Avatar.Fallback, { children: initials(user.displayName, user.email) })] }));
57
67
  }
58
68
  /** `dispatch_admin` / `billing-viewer` -> `Dispatch admin`. */
59
69
  function humanizeRole(role) {
@@ -108,55 +118,102 @@ function DestinationTile({ icon, label, url, }) {
108
118
  return (_jsxs(Button, { variant: "tertiary", "aria-label": `${label} (opens in a new tab)`, className: `relative h-auto w-full flex-col items-center justify-center gap-2 rounded-xl border border-border ${PANEL_FILL} p-3 hover:border-accent hover:bg-surface-secondary`, onPress: () => openExternal(url), children: [_jsx(ExternalLink, { className: "absolute end-2 top-2 size-3.5 text-muted", "aria-hidden": "true" }), _jsx("span", { className: `flex ${MEDALLION} items-center justify-center rounded-xl bg-surface text-muted`, children: icon }), _jsx("span", { className: "text-xs font-medium text-foreground", children: label })] }));
109
119
  }
110
120
  /**
111
- * Avatar and name in the trigger; an identity card, the three Connect
112
- * destinations (Profile, Billing, Connect), an optional appearance control,
113
- * this account's active sessions, and sign-out in the modal it opens.
121
+ * The account modal's contents - identity card, the three Connect
122
+ * destinations, an optional appearance control, this account's sessions, and
123
+ * sign-out.
124
+ *
125
+ * Extracted from the trigger it used to be welded to, so the same surface can
126
+ * be opened from the sidebar's account row (`ConnectUserButton`) *or* from a
127
+ * host's own control (`ConnectProfileModal`) without a second copy of it. It
128
+ * renders the `Modal.Backdrop` downwards only; whoever renders it supplies the
129
+ * `<Modal>` and decides how it opens.
114
130
  *
115
131
  * Profile/Billing/Connect open in a **new tab**, each marked with an
116
- * external-link icon. They are a different application at a different
117
- * origin, and navigating the host app away to reach them would lose
118
- * whatever the user was doing here. Sign out is the only action that acts
119
- * in place, which is why it sits alone in the footer rather than as a fourth
120
- * item in the same list as three navigations.
132
+ * external-link icon. They are a different application at a different origin,
133
+ * and navigating the host app away to reach them would lose whatever the user
134
+ * was doing here. Sign out is the only action that acts in place, which is why
135
+ * it sits alone in the footer rather than as a fourth item in the same list as
136
+ * three navigations.
137
+ */
138
+ function ProfileSurface({ onSignOut, theme, onThemeChange, }) {
139
+ const { session, signOut, appUrl, clientId } = useConnect();
140
+ const user = session?.user;
141
+ if (!user)
142
+ return null;
143
+ const label = user.displayName ?? user.email ?? "Account";
144
+ const role = session?.role ?? null;
145
+ const planChip = subscriptionChip(session?.subscription ?? null);
146
+ return (_jsx(Modal.Backdrop, { children: _jsx(Modal.Container, { size: "md", scroll: "inside", children: _jsxs(Modal.Dialog, { children: [_jsx(Modal.CloseTrigger, {}), _jsx(Modal.Header, { children: _jsx(Modal.Heading, { children: "Account" }) }), _jsxs(Modal.Body, { className: "flex flex-col gap-5", children: [_jsxs("div", { className: `flex items-start gap-3 rounded-xl border border-border ${PANEL_FILL} p-4`, children: [_jsx(UserAvatar, { user: user, size: "lg" }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [_jsx("p", { className: "truncate text-sm font-semibold text-foreground", children: label }), user.email && _jsx("p", { className: "truncate text-xs text-muted", children: user.email }), _jsx("p", { className: "truncate text-xs text-muted", children: user.workspaceName }), (role || planChip || !user.emailVerified) && (_jsxs("div", { className: "mt-1.5 flex flex-wrap items-center gap-1.5", children: [role && (_jsx(Chip, { size: "sm", variant: "soft", color: "accent", children: humanizeRole(role) })), planChip, !user.emailVerified && (_jsx(Chip, { size: "sm", variant: "soft", color: "warning", children: "Email unverified" }))] }))] })] }), _jsxs("div", { className: "grid grid-cols-3 gap-2", children: [_jsx(DestinationTile, { icon: _jsx(UserRound, { className: ICON, "aria-hidden": "true" }), label: "Profile", url: profileUrl(appUrl, clientId) }), _jsx(DestinationTile, { icon: _jsx(CreditCard, { className: ICON, "aria-hidden": "true" }), label: "Billing", url: `${appUrl}/billing` }), _jsx(DestinationTile, { icon: _jsx(ConnectMark, { className: ICON }), label: "Connect", url: appUrl })] }), theme && onThemeChange && (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("p", { className: "text-sm font-medium text-foreground", children: "Appearance" }), _jsx(ToggleButtonGroup, { fullWidth: true, "aria-label": "Theme", selectionMode: "single", disallowEmptySelection: true, selectedKeys: [theme], onSelectionChange: (keys) => {
147
+ const [next] = [...keys];
148
+ if (typeof next === "string")
149
+ onThemeChange(next);
150
+ }, children: THEME_OPTIONS.map(({ value, label: optionLabel, icon: Icon }, index) => (_jsxs(ToggleButton, { id: value, children: [index > 0 && _jsx(ToggleButtonGroup.Separator, {}), _jsx(Icon, { className: "size-4", "aria-hidden": "true" }), optionLabel] }, value))) })] })), _jsx(ConnectAccountSessions, {})] }), _jsx(Modal.Footer, { children: _jsxs(Button, { slot: "close", fullWidth: true, variant: "tertiary", className: "text-danger", onPress: () => {
151
+ void signOut().then(onSignOut);
152
+ }, children: [_jsx(LogOut, { className: "size-4 shrink-0" }), "Sign out"] }) })] }) }) }));
153
+ }
154
+ /**
155
+ * The account modal with **no trigger of its own**, driven by the host.
156
+ *
157
+ * For an application that already has an account control it wants to keep -
158
+ * an avatar in its own chrome, a "Manage account" row in its own settings
159
+ * page - and wants Connect's modal behind it rather than rebuilding identity,
160
+ * sessions and sign-out. `ConnectUserButton` is the same modal with the
161
+ * trigger supplied; reach for this one only when that trigger is in the way.
121
162
  *
122
- * The session list is account-wide, not scoped to this application - see
123
- * `ConnectUserSession`'s doc comment in `types.ts`. Appearance only renders
124
- * when both `theme` and `onThemeChange` are supplied.
163
+ * Renders nothing when signed out, open or not - there is no account to show.
164
+ */
165
+ export function ConnectProfileModal({ isOpen, onOpenChange, onSignOut, theme, onThemeChange, }) {
166
+ const { session } = useConnect();
167
+ if (!session?.user)
168
+ return null;
169
+ return (_jsx(Modal, { isOpen: isOpen, onOpenChange: onOpenChange, children: _jsx(ProfileSurface, { onSignOut: onSignOut, theme: theme, onThemeChange: onThemeChange }) }));
170
+ }
171
+ /**
172
+ * The signed-in account, as a control that opens the account modal.
125
173
  *
126
- * Renders nothing when signed out - pair it with `<ConnectLoginButton>`, which
127
- * renders nothing when signed *in*, and the two swap cleanly with no branch in
128
- * your own layout.
174
+ * In `"row"` form this is the last thing in the sidebar, which is where an
175
+ * account control belongs: it is the one item in the rail that is about *you*
176
+ * rather than about the product, and the foot of a column is where every
177
+ * application a customer already uses puts that. It collapses with the rail -
178
+ * the avatar holds its position and the name fades - because the avatar alone
179
+ * is still a complete account control, which is exactly what an icon rail
180
+ * needs.
181
+ *
182
+ * Renders nothing when signed out. Pair it with `<ConnectLoginButton>`, which
183
+ * renders nothing when signed *in*, and the two swap with no branch in your
184
+ * own layout.
129
185
  *
130
186
  * On the HeroUI shape: the trigger is a plain `<Button>` as a **direct child**
131
- * of `<Modal>`, not wrapped in `<Modal.Trigger>`. `Dropdown.Trigger` (and by
132
- * the same construction, `Modal.Trigger`) is itself a React Aria `Button`, so
133
- * wrapping one produces a button inside a button - invalid markup, and press
134
- * handling stops working. Kept uncontrolled (no `isOpen`/`onOpenChange`),
135
- * matching `<ConnectAppsGrid>`'s own `<Modal>` - nothing here needs to close
136
- * the modal programmatically; sign-out uses `slot="close"` instead.
187
+ * of `<Modal>`, not wrapped in `<Modal.Trigger>`. `Dropdown.Trigger` - and by
188
+ * the same construction `Modal.Trigger` - is itself a React Aria `Button`, so
189
+ * wrapping one produces a button inside a button: invalid markup, and press
190
+ * handling stops working.
137
191
  */
138
- export function ConnectUserMenu({ compact = false, onSignOut, className, theme, onThemeChange, }) {
139
- const { session, isLoading, signOut, appUrl, clientId } = useConnect();
192
+ export function ConnectUserButton({ variant = "row", onSignOut, className, theme, onThemeChange, }) {
193
+ const { session, isLoading } = useConnect();
194
+ const { collapsed } = useConnectShell();
140
195
  if (isLoading) {
141
- return (_jsxs("div", { className: `flex items-center gap-2 ${className ?? ""}`, children: [_jsx(Skeleton, { className: "size-8 shrink-0 rounded-full" }), !compact && _jsx(Skeleton, { className: "h-4 w-24" })] }));
196
+ return (_jsxs("div", { className: `flex h-10 items-center gap-3 ${variant === "row" ? "ps-[4px]" : ""} ${className ?? ""}`, children: [_jsx(Skeleton, { className: "size-8 shrink-0 rounded-full" }), variant !== "compact" && !collapsed && _jsx(Skeleton, { className: "h-4 w-24" })] }));
142
197
  }
143
198
  const user = session?.user;
144
199
  if (!user)
145
200
  return null;
146
201
  const label = user.displayName ?? user.email ?? "Account";
147
- const role = session?.applicationRole?.role;
148
- const planChip = subscriptionChip(session?.subscription ?? null);
149
- return (_jsxs(Modal, { children: [_jsxs(Button, { variant: "tertiary", "aria-label": `Account: ${label}`,
150
- // `pl-1` against the default right padding is optical, not sloppy: a
151
- // circular avatar needs less inset than a text label to look evenly
152
- // seated in the button.
153
- className: `gap-2 pl-1 ${className ?? ""}`, children: [_jsx(UserAvatar, { user: user, size: "sm" }), !compact && _jsx("span", { className: "text-sm font-medium text-foreground", children: label })] }), _jsx(Modal.Backdrop, { children: _jsx(Modal.Container, { size: "md", scroll: "inside", children: _jsxs(Modal.Dialog, { children: [_jsx(Modal.CloseTrigger, {}), _jsx(Modal.Header, { children: _jsx(Modal.Heading, { children: "Account" }) }), _jsxs(Modal.Body, { className: "flex flex-col gap-5", children: [_jsxs("div", { className: `flex items-start gap-3 rounded-xl border border-border ${PANEL_FILL} p-4`, children: [_jsx(UserAvatar, { user: user, size: "lg" }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [_jsx("p", { className: "truncate text-sm font-semibold text-foreground", children: label }), user.email && _jsx("p", { className: "truncate text-xs text-muted", children: user.email }), _jsx("p", { className: "truncate text-xs text-muted", children: user.workspaceName }), (role || planChip || !user.emailVerified) && (_jsxs("div", { className: "mt-1.5 flex flex-wrap items-center gap-1.5", children: [role && (_jsx(Chip, { size: "sm", variant: "soft", color: "accent", children: humanizeRole(role) })), planChip, !user.emailVerified && (_jsx(Chip, { size: "sm", variant: "soft", color: "warning", children: "Email unverified" }))] }))] })] }), _jsxs("div", { className: "grid grid-cols-3 gap-2", children: [_jsx(DestinationTile, { icon: _jsx(UserRound, { className: ICON, "aria-hidden": "true" }), label: "Profile", url: profileUrl(appUrl, clientId) }), _jsx(DestinationTile, { icon: _jsx(CreditCard, { className: ICON, "aria-hidden": "true" }), label: "Billing", url: `${appUrl}/billing` }), _jsx(DestinationTile, { icon: _jsx(ConnectMark, { className: ICON }), label: "Connect", url: appUrl })] }), theme && onThemeChange && (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("p", { className: "text-sm font-medium text-foreground", children: "Appearance" }), _jsx(ToggleButtonGroup, { fullWidth: true, "aria-label": "Theme", selectionMode: "single", disallowEmptySelection: true, selectedKeys: [theme], onSelectionChange: (keys) => {
154
- const [next] = [...keys];
155
- if (typeof next === "string")
156
- onThemeChange(next);
157
- }, children: THEME_OPTIONS.map(({ value, label: optionLabel, icon: Icon }, index) => (_jsxs(ToggleButton, { id: value, children: [index > 0 && _jsx(ToggleButtonGroup.Separator, {}), _jsx(Icon, { className: "size-4", "aria-hidden": "true" }), optionLabel] }, value))) })] })), _jsx(ConnectAccountSessions, {})] }), _jsx(Modal.Footer, { children: _jsxs(Button, { slot: "close", fullWidth: true, variant: "tertiary", className: "text-danger", onPress: () => {
158
- void signOut().then(onSignOut);
159
- }, children: [_jsx(LogOut, { className: "size-4 shrink-0" }), "Sign out"] }) })] }) }) })] }));
202
+ return (_jsxs(Modal, { children: [variant === "row" ? (_jsxs(Button, { variant: "tertiary", "aria-label": `Account: ${label}`, className: sidebarRowClass({ tile: true, className }), children: [_jsx(UserAvatar, { user: user, size: "sm" }), _jsx(SidebarLabel, { children: _jsxs("span", { className: "flex min-w-0 flex-col text-start leading-tight", children: [_jsx("span", { className: "truncate text-sm font-medium text-foreground", children: label }), user.email && user.email !== label && (_jsx("span", { className: "truncate text-xs font-normal text-muted", children: user.email }))] }) }), _jsx(m.span, { className: "shrink-0 text-muted", animate: { opacity: collapsed ? 0 : 1 }, transition: collapsed ? { duration: 0.1 } : { duration: 0.18, delay: 0.1 }, "aria-hidden": true, children: _jsx(ChevronsUpDown, { className: "size-4" }) })] })) : (_jsxs(Button, { variant: "tertiary", "aria-label": `Account: ${label}`,
203
+ // `ps-1` against the default trailing padding is optical, not
204
+ // sloppy: a circular avatar needs less inset than a text label to
205
+ // look evenly seated in the button.
206
+ className: `gap-2 ps-1 ${className ?? ""}`, children: [_jsx(UserAvatar, { user: user, size: "sm" }), variant === "inline" && _jsx("span", { className: "text-sm font-medium text-foreground", children: label })] })), _jsx(ProfileSurface, { onSignOut: onSignOut, theme: theme, onThemeChange: onThemeChange })] }));
207
+ }
208
+ /**
209
+ * @deprecated Renamed to `ConnectUserButton` in 0.12 - it is one button that
210
+ * opens a modal, and calling it a "menu" had people looking for a dropdown
211
+ * that never existed. `compact` maps to `variant="compact"`, and the old
212
+ * default (avatar plus name) to `variant="inline"`. This alias will be removed
213
+ * in a later minor.
214
+ */
215
+ export function ConnectUserMenu({ compact = false, ...rest }) {
216
+ return _jsx(ConnectUserButton, { variant: compact ? "compact" : "inline", ...rest });
160
217
  }
161
218
  /**
162
219
  * The "Manage subscription" action on its own, for layouts that want it
@@ -0,0 +1,97 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import { Button, Dropdown, ListBox } from "@heroui/react";
5
+ import { ChevronsUpDown } from "lucide-react";
6
+ import { useConnect, useConnectUser, useConnectWorkspaces } from "../provider.js";
7
+ import { m } from "./motion.js";
8
+ import { SidebarLabel, sidebarRowClass, useConnectShell } from "./shell-context.js";
9
+ /** `Acme Logistics` -> `AL`. The collapsed rail's whole label for a workspace. */
10
+ function workspaceInitials(name) {
11
+ const parts = name.trim().split(/\s+/).filter(Boolean);
12
+ return (parts.slice(0, 2).map((part) => part[0]).join("") || "?").toUpperCase();
13
+ }
14
+ /**
15
+ * Switches which workspace this application is looking at.
16
+ *
17
+ * **Rendered in the `AppShell` sidebar header by default**, directly
18
+ * under the brand - which workspace you are in scopes everything below it, so
19
+ * it reads as a heading for the nav rather than as one more destination.
20
+ *
21
+ * It decides for itself whether to appear: an account with one workspace is
22
+ * shown nothing, because a switcher offering one option teaches the user
23
+ * nothing and costs a row of chrome on every screen. That is the opposite of
24
+ * Connect's own switcher, which always renders - Connect is where a workspace
25
+ * is *created*, so hiding it at one workspace would guarantee nobody ever
26
+ * reached two. A partner application creates nothing, so it has no such duty.
27
+ *
28
+ * **Authorization is the data, not a prop.** The list comes from the caller's
29
+ * own session (`GET /workspaces` takes no user id), so a workspace the user
30
+ * may not reach is simply not in it. There is deliberately no `roles` prop to
31
+ * pass or forget - the only way to render a row is for Connect to have
32
+ * returned it.
33
+ *
34
+ * Rows the user *belongs to* but cannot use here - a workspace outside the
35
+ * application's audience, or one with no subscription - are shown **disabled
36
+ * with the reason** rather than hidden. Hiding them reads as data loss to the
37
+ * person who owns the workspace; `switch` refuses the same two cases, so the
38
+ * label and the outcome always agree.
39
+ *
40
+ * A switch rotates the session token. The bridge persists the new one in the
41
+ * host application's own session store and the token never reaches this
42
+ * component - so the only thing to do afterwards is reload, which is what
43
+ * `onSwitched` defaults to.
44
+ */
45
+ export function ConnectWorkspaceSwitcher({ onSwitched, row = true, className, }) {
46
+ const { mutateResource } = useConnect();
47
+ const { collapsed } = useConnectShell();
48
+ const user = useConnectUser();
49
+ const { data, isLoading } = useConnectWorkspaces();
50
+ const [switching, setSwitching] = useState(null);
51
+ const [error, setError] = useState(null);
52
+ const workspaces = data?.workspaces ?? [];
53
+ // Nothing to switch between, or nothing loaded yet. Deliberately renders
54
+ // `null` rather than a skeleton: this sits in persistent chrome, and a
55
+ // placeholder that resolves to nothing for most accounts is worse than
56
+ // appearing a moment late for the few who have two.
57
+ if (isLoading || workspaces.length < 2)
58
+ return null;
59
+ const active = workspaces.find((workspace) => workspace.isActive);
60
+ const label = active?.name ?? user?.workspaceName ?? "Workspace";
61
+ const handleSwitch = async (workspace) => {
62
+ if (workspace.isActive || !selectable(workspace))
63
+ return;
64
+ setSwitching(workspace.workspaceId);
65
+ setError(null);
66
+ try {
67
+ // Through the provider's own bridge caller rather than a bare `fetch`,
68
+ // so this inherits the configured `basePath`, the credentials mode and
69
+ // the error shape every other call in the SDK uses.
70
+ await mutateResource("/workspaces/switch", { workspaceId: workspace.workspaceId });
71
+ if (onSwitched) {
72
+ onSwitched(workspace);
73
+ }
74
+ else {
75
+ window.location.reload();
76
+ }
77
+ }
78
+ catch (cause) {
79
+ setError(cause instanceof Error ? cause.message : "Could not switch workspace");
80
+ setSwitching(null);
81
+ }
82
+ };
83
+ return (_jsxs("div", { className: row ? "" : className, children: [_jsxs(Dropdown, { children: [row ? (
84
+ // A 32px tile rather than a 20px glyph, so the initials land on the
85
+ // same optical centre the avatar below them uses - see
86
+ // `SIDEBAR_ROW_TILE`. Collapsed, this tile *is* the switcher.
87
+ _jsxs(Button, { variant: "tertiary", "aria-label": `Workspace: ${label}. Switch workspace`, className: sidebarRowClass({ tile: true, className }), children: [_jsx("span", { className: "flex size-8 shrink-0 items-center justify-center rounded-lg bg-accent-soft text-xs font-semibold text-accent", children: workspaceInitials(label) }), _jsx(SidebarLabel, { className: "text-sm font-medium text-foreground", children: label }), _jsx(m.span, { className: "shrink-0 text-muted", animate: { opacity: collapsed ? 0 : 1 }, transition: collapsed ? { duration: 0.1 } : { duration: 0.18, delay: 0.1 }, "aria-hidden": true, children: _jsx(ChevronsUpDown, { className: "size-4" }) })] })) : (_jsxs(Button, { variant: "secondary", size: "sm", className: "w-full justify-between", children: [_jsx("span", { className: "truncate", children: label }), _jsx(ChevronsUpDown, { className: "size-4 shrink-0 text-muted", "aria-hidden": "true" })] })), _jsx(Dropdown.Popover, { children: _jsx(ListBox, { children: workspaces.map((workspace) => (_jsxs(ListBox.Item, { id: workspace.workspaceId, textValue: workspace.name, isDisabled: !selectable(workspace) || switching !== null, onAction: () => void handleSwitch(workspace), children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "truncate text-sm", children: workspace.name }), selectable(workspace) ? null : (_jsx("p", { className: "truncate text-xs text-muted", children: reasonUnavailable(workspace) }))] }), _jsx(ListBox.ItemIndicator, {})] }, workspace.workspaceId))) }) })] }), error && !collapsed ? _jsx("p", { className: "mt-1 px-2 text-xs text-danger", children: error }) : null] }));
88
+ }
89
+ /** Belongs to the user, and usable here. The same two facts `switch` enforces. */
90
+ function selectable(workspace) {
91
+ return workspace.inAudience && workspace.subscribed;
92
+ }
93
+ function reasonUnavailable(workspace) {
94
+ if (!workspace.inAudience)
95
+ return "Not available for this kind of workspace";
96
+ return "No active subscription";
97
+ }
@@ -11,16 +11,19 @@
11
11
  * whatever theme the host application platform already configured, and one theme
12
12
  * change lands identically in all of them.
13
13
  */
14
- export { ConnectProvider, useConnect, useConnectUser, useConnectWorkspaceType, useConnectAudience, useConnectEntitlements, useConnectPlans, useConnectApps, useConnectAddonUsage, useConnectSessions, useConnectIntegrations, useConnectResource, CONNECT_APP_URL, } from "./provider.js";
14
+ export { ConnectProvider, useConnect, useConnectUser, useConnectWorkspaceType, useConnectAudience, useConnectPlatformAccess, useConnectEntitlements, useConnectPlans, useConnectApps, useConnectCompanyProfile, useConnectAddonUsage, useConnectSessions, useConnectIntegrations, useConnectResource, CONNECT_APP_URL, } from "./provider.js";
15
15
  // Re-exported from the root entry rather than kept there alone: it is a pure
16
16
  // helper, but every caller is UI code, and making a component file reach two
17
17
  // entry points for one function is friction with nothing behind it.
18
18
  export { blockingBlockers, normalizePlanFeatures } from "../helpers.js";
19
19
  export { ConnectError, isConnectError } from "../errors.js";
20
+ export { ConnectGrievanceNotice } from "./components/grievance-notice.js";
20
21
  export { ConnectMark } from "./mark.js";
21
22
  export { ConnectLogo } from "./components/logo.js";
22
23
  export { ConnectLoginButton, ConnectRegisterButton } from "./components/auth-buttons.js";
23
- export { ConnectUserMenu, ConnectManageSubscriptionButton, ConnectInvoicesButton, ConnectProfileButton, ConnectHomeButton, ConnectSignOutButton, } from "./components/user-menu.js";
24
+ export { ConnectUserButton, ConnectProfileModal,
25
+ /** @deprecated Renamed to `ConnectUserButton`. */
26
+ ConnectUserMenu, ConnectManageSubscriptionButton, ConnectInvoicesButton, ConnectProfileButton, ConnectHomeButton, ConnectSignOutButton, } from "./components/user-menu.js";
24
27
  // Built, compiled into `dist/`, and reachable from no entry point until now -
25
28
  // `ConnectUserMenu` was the only thing that could render it.
26
29
  export { ConnectAccountSessions } from "./components/account-sessions.js";
@@ -28,14 +31,31 @@ export { ConnectAccountSessions } from "./components/account-sessions.js";
28
31
  // pieces it composes rather than in a section of its own - an integration
29
32
  // reaching for a sidebar wants to find the launcher and the account menu in
30
33
  // the same breath, since the shell already mounts both.
34
+ /*
35
+ * `AppShell`, deliberately without the `Connect` prefix every other export
36
+ * here carries. That exception was decided in 0.8.0 and is kept: a host whose
37
+ * own layout component is also called `AppShell` aliases it on import
38
+ * (`import { AppShell as ConnectAppShell }`), which is the documented answer
39
+ * and what both reference integrations do.
40
+ */
31
41
  export { AppShell } from "./components/app-shell.js";
32
- export { ConnectSideNav } from "./components/side-nav.js";
42
+ /*
43
+ * The rail, in pieces. `AppShell` composes exactly these and nothing
44
+ * else, which is the point of exporting them: an application whose chrome the
45
+ * prop API cannot express rebuilds the same rail from the same parts rather
46
+ * than forking it, and gets the collapse animation, the 32px glyph centre and
47
+ * the mobile drawer for free. They coordinate through `ConnectShellProvider`,
48
+ * so a part dropped in anywhere under one still knows the rail's state.
49
+ */
50
+ export { ConnectSidebar, ConnectSidebarHeader, ConnectSidebarBody, ConnectSidebarFooter, ConnectSidebarRow, } from "./components/sidebar.js";
51
+ export { ConnectShellProvider, useConnectShell, sidebarRowClass, RAIL_COLLAPSED, RAIL_EXPANDED, } from "./components/shell-context.js";
52
+ export { ConnectSidebarNav, ConnectSideNav } from "./components/side-nav.js";
33
53
  export { useActivePath } from "./use-active-path.js";
34
54
  // The nav model is plain data and pure functions - a host generating its nav
35
55
  // from its own route table needs the types, and `resolveActivePath` is the
36
56
  // same longest-prefix rule the sidebar highlights with, so a breadcrumb or a
37
57
  // page title derived from it cannot disagree with the rail.
38
- export { normalizeNav, flattenNav, normalizePath, resolveActivePath, isNavItemActive, gateExemptPaths, } from "./components/nav.js";
58
+ export { normalizeNav, flattenNav, normalizePath, resolveActivePath, isNavItemActive, gateExemptPaths, filterNavForAudience, } from "./components/nav.js";
39
59
  export { ConnectAppsGrid } from "./components/apps-grid.js";
40
60
  // `AppShell askAi` mounts this; exported for an app that wants the panel
41
61
  // without the frame, or wants it somewhere other than the shell's flex row
@@ -60,8 +80,28 @@ export { ConnectReadinessBanner, ConnectReadinessGate } from "./components/readi
60
80
  export { ConnectAccessGate, useConnectAccessGate } from "./components/access-gate.js";
61
81
  export { ConnectFeatureGate, useConnectFeature } from "./components/feature-gate.js";
62
82
  export { ConnectWorkspaceTypeGate } from "./components/workspace-type-gate.js";
83
+ export { ConnectPlatformGate } from "./components/platform-gate.js";
63
84
  export { ConnectUsageMeter } from "./components/usage-meter.js";
64
85
  export { ConnectPlansPage } from "./components/plans-page.js";
86
+ /*
87
+ * The interactive half of the page-composition layer. Their presentational
88
+ * counterparts - `ConnectPageShell`, `ConnectSection`, `ConnectEmptyState`,
89
+ * `ConnectStatusChip`, `ConnectSettingRow` - live in
90
+ * `@cosmoops/connect-sdk/ui`, which is deliberately not a `"use client"`
91
+ * module so a page shell does not become a client boundary. These three hold
92
+ * state, so they belong here.
93
+ */
94
+ /*
95
+ * Built and compiled into `dist/` since 0.8 and reachable from no entry point
96
+ * until now - the same oversight `ConnectAccountSessions` had. It is the only
97
+ * supported way to render a link that looks like a button: HeroUI v3's
98
+ * `Button` has no `as` prop, so `<Button as={Link}>` neither type checks nor
99
+ * works.
100
+ */
101
+ export { ConnectLinkButton } from "./components/link-button.js";
102
+ export { ConnectConfirmDialog } from "./components/confirm-dialog.js";
103
+ export { ConnectCursorPagination } from "./components/cursor-pagination.js";
104
+ export { ConnectFilterTabs } from "./components/filter-tabs.js";
65
105
  export { useConnectCheckout, buildCheckoutUrl } from "./use-checkout.js";
66
106
  export { formatPrice, monthlyOf, resolveAction, annualSaving, discountLabel, ACTION_LABEL,
67
107
  // The tier model, exported because a partner writing its own pricing grid
@@ -70,3 +110,5 @@ export { formatPrice, monthlyOf, resolveAction, annualSaving, discountLabel, ACT
70
110
  // keeps stable across a rename - `planGroup` is the field that exists to
71
111
  // answer this, and `groupPlansByTier` is how to read it.
72
112
  groupPlansByTier, resolvePlanGroup, } from "./components/plan-pricing.js";
113
+ export { ConnectWorkspaceSwitcher } from "./components/workspace-switcher.js";
114
+ export { useConnectWorkspaces, useConnectWorkspaceMembers } from "./provider.js";
@@ -136,7 +136,7 @@ export function ConnectProvider({ clientId = "", basePath = "/api/connect", redi
136
136
  // error, so a Login button still renders rather than the UI hanging on
137
137
  // skeletons forever.
138
138
  setResult({
139
- session: { user: null, subscription: null, applicationRole: null, audience: null },
139
+ session: { user: null, subscription: null, role: null, permissions: [], staff: false, audience: null },
140
140
  error: cause instanceof Error ? cause.message : "Could not reach Connect",
141
141
  });
142
142
  }
@@ -185,7 +185,7 @@ export function ConnectProvider({ clientId = "", basePath = "/api/connect", redi
185
185
  credentials: "same-origin",
186
186
  }).catch(() => { });
187
187
  cache.current.clear();
188
- setResult({ session: { user: null, subscription: null, applicationRole: null, audience: null }, error: null });
188
+ setResult({ session: { user: null, subscription: null, role: null, permissions: [], staff: false, audience: null }, error: null });
189
189
  }, [basePath, fetchImpl]);
190
190
  const value = useMemo(() => ({
191
191
  clientId,
@@ -252,6 +252,26 @@ export function useConnectWorkspaceType() {
252
252
  export function useConnectAudience() {
253
253
  return useConnect().session?.audience ?? null;
254
254
  }
255
+ /**
256
+ * Whether this session is one of Connect's own operators - see
257
+ * `SessionAccess.staff`.
258
+ *
259
+ * `isStaff` is the branch you want; `roles` narrows further when your operator
260
+ * console has tiers of its own (a `developer` seeing a read-only view of what
261
+ * a `platform_admin` can edit, say).
262
+ *
263
+ * **A rendering decision, not an access decision.** The browser can call this
264
+ * whatever it likes. Everything an operator console exposes must be re-checked
265
+ * server-side against `staff` from
266
+ * `client.identity.getUser(sessionToken)` - see `<ConnectPlatformGate>`, which
267
+ * says the same thing at more length.
268
+ */
269
+ export function useConnectPlatformAccess() {
270
+ const { session, isLoading } = useConnect();
271
+ const staff = session?.staff === true;
272
+ const role = session?.role ?? null;
273
+ return { isStaff: staff, roles: staff && role ? [role] : [], isLoading };
274
+ }
255
275
  /** Readiness gates and the live subscription, fetched on first use. */
256
276
  export function useConnectEntitlements() {
257
277
  return useConnectResource("/entitlements");
@@ -270,10 +290,41 @@ export function useConnectAddonUsage() {
270
290
  export function useConnectPlans() {
271
291
  return useConnectResource("/plans");
272
292
  }
293
+ /**
294
+ * The platform's legal identity and DPDP grievance channel.
295
+ *
296
+ * The one read here that does not depend on a session, so it resolves the
297
+ * same for a signed-out visitor - which is deliberate, because the moment a
298
+ * grievance channel most needs to be readable is on a public privacy page.
299
+ *
300
+ * Read it rather than copying it into your own configuration; see
301
+ * `CompanyProfile`. `<ConnectGrievanceNotice>` renders it for you.
302
+ */
303
+ export function useConnectCompanyProfile() {
304
+ return useConnectResource("/company-profile");
305
+ }
273
306
  /** The apps this workspace can open. */
274
307
  export function useConnectApps() {
275
308
  return useConnectResource("/apps");
276
309
  }
310
+ /**
311
+ * The workspaces this user belongs to - what `<ConnectWorkspaceSwitcher>`
312
+ * renders.
313
+ *
314
+ * Empty or single-entry results are normal, not errors: most accounts have one
315
+ * workspace, and the switcher decides for itself whether that is worth
316
+ * drawing.
317
+ */
318
+ export function useConnectWorkspaces() {
319
+ return useConnectResource("/workspaces");
320
+ }
321
+ /**
322
+ * The team of the workspace this session is on, with each member's permissions
323
+ * already resolved for this application.
324
+ */
325
+ export function useConnectWorkspaceMembers() {
326
+ return useConnectResource("/workspaces/members");
327
+ }
277
328
  /** Enabled Composio-hosted toolkits and this workspace's connection status for each. */
278
329
  export function useConnectIntegrations() {
279
330
  return useConnectResource("/integrations");
@@ -152,15 +152,19 @@ export function buildCancelUrl({ appUrl, subscriptionId }) {
152
152
  * The Connect URL that completes a subscribe or a plan change.
153
153
  *
154
154
  * Both land on a modal Connect renders over the relevant page (an intercepted
155
- * parallel route - `app/@modal/(.)applications/...` and
156
- * `app/@modal/(.)billing/...`), so the customer arrives already looking at the
157
- * confirmation for the plan they picked here, not at a catalogue they have to
158
- * find it in again.
155
+ * parallel route - `app/@modal/(.)apps/...` and `app/@modal/(.)billing/...`),
156
+ * so the customer arrives already looking at the confirmation for the plan
157
+ * they picked here, not at a catalogue they have to find it in again.
158
+ *
159
+ * The subscribe path is `/apps/...`, not `/applications/...`: the portal
160
+ * route customers see is `/apps`, and `/applications` is a Console-only
161
+ * prefix that has never served this page. Building the other one is what
162
+ * sent every first-time subscriber from a partner's pricing page to a 404.
159
163
  */
160
164
  export function buildCheckoutUrl({ appUrl, applicationId, planId, subscriptionId, coupon, }) {
161
165
  const path = subscriptionId
162
166
  ? `/billing/subscriptions/${encodeURIComponent(subscriptionId)}/change-plan/${encodeURIComponent(planId)}`
163
- : `/applications/${encodeURIComponent(applicationId)}/subscribe/${encodeURIComponent(planId)}`;
167
+ : `/apps/${encodeURIComponent(applicationId)}/subscribe/${encodeURIComponent(planId)}`;
164
168
  const url = new URL(path, appUrl);
165
169
  if (coupon?.trim())
166
170
  url.searchParams.set("coupon", coupon.trim());
@@ -55,7 +55,7 @@ export class ConnectError extends Error {
55
55
  * guessing keys. These two are the exception: reaching them required
56
56
  * presenting a key whose hash matched, so you already hold it, and what you
57
57
  * need to know is that no retry and no scope change will fix this - the key
58
- * has to be reissued from the application's Integration tab.
58
+ * has to be reissued from the application's Connect client section.
59
59
  *
60
60
  * Worth alerting on. It is the one failure that will not resolve itself and
61
61
  * takes down every call your integration makes.
package/dist/esm/index.js CHANGED
@@ -13,3 +13,4 @@
13
13
  */
14
14
  export { ConnectError, isConnectError } from "./errors.js";
15
15
  export { blockingBlockers, normalizePlanFeatures } from "./helpers.js";
16
+ export { arrayRemove, arrayUnion, deleteField, documentExists, increment, serverTimestamp, storeIndexFor, storeIndexSignature, unchangedSince, } from "./store.js";
@@ -38,7 +38,7 @@ import { createConnectHandler } from "../server/handler.js";
38
38
  * | `CONNECT_SESSION_COOKIE` | no | session cookie name |
39
39
  *
40
40
  * Register `{your origin}/api/connect/callback` as a redirect URI in the
41
- * application's Integration tab. That is the only step this cannot do for you.
41
+ * application's Connect client section. That is the only step this cannot do for you.
42
42
  *
43
43
  * No `next.config.ts` changes needed either, including `transpilePackages`:
44
44
  * `dist/` is already ES2022 with `"use client"` hoisted to the top of every
@@ -287,7 +287,7 @@ export async function getConnectSession(request, options = {}) {
287
287
  * A thin wrapper, not a framework of its own - reach for `getConnectSession`
288
288
  * plus your own check when "not signed in" isn't "redirect to sign-in"
289
289
  * (an API route returning 401, say). Role and entitlement checks stay
290
- * outside this function on purpose: `session.applicationRole.granted` and
290
+ * outside this function on purpose: `session.permissions` and
291
291
  * `connect.entitlements.*` are one line each on top of the session this
292
292
  * already gives you, and where that line redirects (a plans page, a
293
293
  * "forbidden" screen) is a decision only your app can make - see
@@ -1,6 +1,9 @@
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 { DataResource, storePassthrough } from "./resources/data.js";
5
+ import { WorkspacesResource } from "./resources/workspaces.js";
6
+ import { KvResource } from "./resources/kv.js";
4
7
  import { BillingResource } from "./resources/billing.js";
5
8
  import { DriveResource } from "./resources/drive.js";
6
9
  import { EntitlementsResource } from "./resources/entitlements.js";
@@ -10,6 +13,7 @@ import { LogsResource } from "./resources/logs.js";
10
13
  import { AnalyticsResource } from "./resources/analytics.js";
11
14
  import { OtpResource } from "./resources/otp.js";
12
15
  import { SessionsResource } from "./resources/sessions.js";
16
+ import { PlatformResource } from "./resources/platform.js";
13
17
  export const DEFAULT_BASE_URL = "https://connect.cosmoops.com";
14
18
  const APPLICATION_API_PREFIX = "/api/v1/application";
15
19
  /**
@@ -74,6 +78,10 @@ export class ConnectClient {
74
78
  drive;
75
79
  /** RAG chat and structured extraction. */
76
80
  ai;
81
+ /** Your application's own document store - see `DataResource`. */
82
+ data;
83
+ /** Your application's key/value store - see `KvResource`. */
84
+ kv;
77
85
  /** WhatsApp/SMS phone verification. */
78
86
  otp;
79
87
  /** Feature events into the Console's audit log, filterable to your application. */
@@ -84,10 +92,20 @@ export class ConnectClient {
84
92
  sessions;
85
93
  /** Composio-hosted third-party connections (Google Calendar, Slack, ...) a workspace has connected. */
86
94
  integrations;
95
+ /** Which workspaces the signed-in user belongs to, switching between them, and the team - see `WorkspacesResource`. */
96
+ workspaces;
97
+ /** The platform's own legal identity and DPDP grievance channel - see `PlatformResource`. */
98
+ platform;
99
+ /**
100
+ * Relays one `/data/*` or `/kv/*` request verbatim - `createConnectHandler`'s
101
+ * store route and nothing else. See `storePassthrough`.
102
+ * @internal
103
+ */
104
+ storeRelay;
87
105
  constructor(options) {
88
106
  assertNotBrowser();
89
107
  if (!options.apiKey) {
90
- throw new TypeError("ConnectClient requires `apiKey` - the plaintext shown once in the Integration tab.");
108
+ throw new TypeError("ConnectClient requires `apiKey` - the plaintext shown once in the Connect client section.");
91
109
  }
92
110
  const appBaseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
93
111
  const transport = {
@@ -107,12 +125,17 @@ export class ConnectClient {
107
125
  this.billing = new BillingResource(transport);
108
126
  this.drive = new DriveResource(transport);
109
127
  this.ai = new AiResource(transport);
128
+ this.data = new DataResource(transport);
129
+ this.kv = new KvResource(transport);
110
130
  this.otp = new OtpResource(transport);
111
131
  this.logs = new LogsResource(transport);
112
132
  this.analytics = new AnalyticsResource(transport);
113
133
  this.sessions = new SessionsResource(transport, options.clientId ?? null);
114
134
  this.integrations = new IntegrationsResource(transport);
135
+ this.workspaces = new WorkspacesResource(transport);
136
+ this.platform = new PlatformResource(transport);
137
+ this.storeRelay = (spec) => storePassthrough(transport, spec);
115
138
  }
116
139
  }
117
140
  /** Kept in step with `package.json` by `sdk.test.ts`. */
118
- export const SDK_VERSION = "0.9.0";
141
+ export const SDK_VERSION = "0.14.0";