@cosmoops/connect-sdk 0.9.0 → 0.16.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.
- package/CHANGELOG.md +566 -0
- package/README.md +113 -42
- package/dist/cjs/cli/doctor.js +3 -3
- package/dist/cjs/cli/init.js +3 -3
- package/dist/cjs/client/components/access-gate.js +20 -1
- package/dist/cjs/client/components/ai-screens.js +73 -0
- package/dist/cjs/client/components/ai-widgets.js +37 -0
- package/dist/cjs/client/components/app-shell.js +75 -41
- package/dist/cjs/client/components/apps-grid.js +3 -2
- package/dist/cjs/client/components/ask-ai.js +50 -5
- package/dist/cjs/client/components/confirm-dialog.js +40 -0
- package/dist/cjs/client/components/cursor-pagination.js +22 -0
- package/dist/cjs/client/components/filter-tabs.js +24 -0
- package/dist/cjs/client/components/grievance-notice.js +30 -0
- package/dist/cjs/client/components/link-button.js +17 -10
- package/dist/cjs/client/components/logo.js +4 -3
- package/dist/cjs/client/components/nav.js +41 -0
- package/dist/cjs/client/components/plan-pricing.js +27 -0
- package/dist/cjs/client/components/plans.js +1 -1
- package/dist/cjs/client/components/platform-gate.js +48 -0
- package/dist/cjs/client/components/shell-context.js +149 -0
- package/dist/cjs/client/components/side-nav.js +49 -23
- package/dist/cjs/client/components/sidebar.js +127 -0
- package/dist/cjs/client/components/subscribe-dialog.js +2 -2
- package/dist/cjs/client/components/subscription.js +1 -1
- package/dist/cjs/client/components/user-menu.js +96 -37
- package/dist/cjs/client/components/workspace-switcher.js +100 -0
- package/dist/cjs/client/index.js +72 -2
- package/dist/cjs/client/provider.js +57 -2
- package/dist/cjs/client/use-checkout.js +9 -5
- package/dist/cjs/errors.js +1 -1
- package/dist/cjs/index.js +16 -1
- package/dist/cjs/mcp/index.js +90 -0
- package/dist/cjs/mcp/tools.js +369 -0
- package/dist/cjs/next/index.js +2 -2
- package/dist/cjs/server/client.js +25 -2
- package/dist/cjs/server/handler.js +203 -18
- package/dist/cjs/server/http.js +6 -2
- package/dist/cjs/server/resources/acting-as.js +16 -0
- package/dist/cjs/server/resources/ai.js +22 -2
- package/dist/cjs/server/resources/data.js +374 -0
- package/dist/cjs/server/resources/identity.js +7 -3
- package/dist/cjs/server/resources/kv.js +84 -0
- package/dist/cjs/server/resources/platform.js +43 -0
- package/dist/cjs/server/resources/sessions.js +1 -1
- package/dist/cjs/server/resources/workspaces.js +74 -0
- package/dist/cjs/store-schema.js +66 -0
- package/dist/cjs/store.js +152 -0
- package/dist/cjs/ui/empty-state.js +28 -0
- package/dist/cjs/ui/index.js +44 -0
- package/dist/cjs/ui/page-header.js +27 -0
- package/dist/cjs/ui/page-shell.js +44 -0
- package/dist/cjs/ui/section.js +20 -0
- package/dist/cjs/ui/setting-row.js +14 -0
- package/dist/cjs/ui/status-chip.js +17 -0
- package/dist/esm/cli/doctor.js +3 -3
- package/dist/esm/cli/init.js +3 -3
- package/dist/esm/client/components/access-gate.js +20 -1
- package/dist/esm/client/components/ai-screens.js +69 -0
- package/dist/esm/client/components/ai-widgets.js +37 -1
- package/dist/esm/client/components/app-shell.js +81 -47
- package/dist/esm/client/components/apps-grid.js +3 -2
- package/dist/esm/client/components/ask-ai.js +51 -6
- package/dist/esm/client/components/confirm-dialog.js +37 -0
- package/dist/esm/client/components/cursor-pagination.js +19 -0
- package/dist/esm/client/components/filter-tabs.js +21 -0
- package/dist/esm/client/components/grievance-notice.js +27 -0
- package/dist/esm/client/components/link-button.js +17 -10
- package/dist/esm/client/components/logo.js +4 -3
- package/dist/esm/client/components/nav.js +40 -0
- package/dist/esm/client/components/plan-pricing.js +25 -0
- package/dist/esm/client/components/plans.js +2 -2
- package/dist/esm/client/components/platform-gate.js +45 -0
- package/dist/esm/client/components/shell-context.js +141 -0
- package/dist/esm/client/components/side-nav.js +48 -21
- package/dist/esm/client/components/sidebar.js +120 -0
- package/dist/esm/client/components/subscribe-dialog.js +3 -3
- package/dist/esm/client/components/subscription.js +2 -2
- package/dist/esm/client/components/user-menu.js +95 -38
- package/dist/esm/client/components/workspace-switcher.js +97 -0
- package/dist/esm/client/index.js +47 -4
- package/dist/esm/client/provider.js +53 -2
- package/dist/esm/client/use-checkout.js +9 -5
- package/dist/esm/errors.js +1 -1
- package/dist/esm/index.js +4 -0
- package/dist/esm/mcp/index.js +85 -0
- package/dist/esm/mcp/tools.js +366 -0
- package/dist/esm/next/index.js +2 -2
- package/dist/esm/server/client.js +25 -2
- package/dist/esm/server/handler.js +203 -18
- package/dist/esm/server/http.js +6 -2
- package/dist/esm/server/resources/acting-as.js +13 -0
- package/dist/esm/server/resources/ai.js +22 -2
- package/dist/esm/server/resources/data.js +369 -0
- package/dist/esm/server/resources/identity.js +7 -3
- package/dist/esm/server/resources/kv.js +80 -0
- package/dist/esm/server/resources/platform.js +39 -0
- package/dist/esm/server/resources/sessions.js +1 -1
- package/dist/esm/server/resources/workspaces.js +70 -0
- package/dist/esm/store-schema.js +62 -0
- package/dist/esm/store.js +141 -0
- package/dist/esm/ui/empty-state.js +25 -0
- package/dist/esm/ui/index.js +34 -0
- package/dist/esm/ui/page-header.js +24 -0
- package/dist/esm/ui/page-shell.js +41 -0
- package/dist/esm/ui/section.js +17 -0
- package/dist/esm/ui/setting-row.js +11 -0
- package/dist/esm/ui/status-chip.js +14 -0
- package/dist/types/client/components/ai-screens.d.ts +25 -0
- package/dist/types/client/components/ai-widgets.d.ts +34 -1
- package/dist/types/client/components/app-shell.d.ts +65 -27
- package/dist/types/client/components/apps-grid.d.ts +11 -2
- package/dist/types/client/components/ask-ai.d.ts +47 -1
- package/dist/types/client/components/confirm-dialog.d.ts +34 -0
- package/dist/types/client/components/cursor-pagination.d.ts +27 -0
- package/dist/types/client/components/filter-tabs.d.ts +26 -0
- package/dist/types/client/components/grievance-notice.d.ts +31 -0
- package/dist/types/client/components/link-button.d.ts +32 -9
- package/dist/types/client/components/logo.d.ts +18 -2
- package/dist/types/client/components/nav.d.ts +24 -0
- package/dist/types/client/components/plan-pricing.d.ts +10 -0
- package/dist/types/client/components/platform-gate.d.ts +53 -0
- package/dist/types/client/components/shell-context.d.ts +116 -0
- package/dist/types/client/components/side-nav.d.ts +27 -13
- package/dist/types/client/components/sidebar.d.ts +95 -0
- package/dist/types/client/components/user-menu.d.ts +68 -22
- package/dist/types/client/components/workspace-switcher.d.ts +49 -0
- package/dist/types/client/index.d.ts +29 -5
- package/dist/types/client/provider.d.ts +109 -6
- package/dist/types/client/use-checkout.d.ts +8 -4
- package/dist/types/errors.d.ts +1 -1
- package/dist/types/index.d.ts +6 -0
- package/dist/types/mcp/index.d.ts +74 -0
- package/dist/types/mcp/tools.d.ts +107 -0
- package/dist/types/next/index.d.ts +1 -1
- package/dist/types/server/client.d.ts +25 -2
- package/dist/types/server/http.d.ts +8 -2
- package/dist/types/server/index.d.ts +4 -0
- package/dist/types/server/resources/acting-as.d.ts +11 -0
- package/dist/types/server/resources/ai.d.ts +20 -2
- package/dist/types/server/resources/data.d.ts +258 -0
- package/dist/types/server/resources/kv.d.ts +63 -0
- package/dist/types/server/resources/platform.d.ts +32 -0
- package/dist/types/server/resources/workspaces.d.ts +55 -0
- package/dist/types/store-schema.d.ts +52 -0
- package/dist/types/store.d.ts +96 -0
- package/dist/types/types.d.ts +369 -16
- package/dist/types/ui/empty-state.d.ts +34 -0
- package/dist/types/ui/index.d.ts +40 -0
- package/dist/types/ui/page-header.d.ts +34 -0
- package/dist/types/ui/page-shell.d.ts +39 -0
- package/dist/types/ui/section.d.ts +26 -0
- package/dist/types/ui/setting-row.d.ts +18 -0
- package/dist/types/ui/status-chip.d.ts +36 -0
- package/package.json +15 -2
package/README.md
CHANGED
|
@@ -12,13 +12,14 @@ npm install @cosmoops/connect-sdk
|
|
|
12
12
|
npm install streamdown # optional — Ask AI's markdown renderer, see step 4
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Five entry points, and the split is a security boundary, not a preference:
|
|
16
16
|
|
|
17
17
|
| Import | Runs in | Holds the API key |
|
|
18
18
|
| --- | --- | --- |
|
|
19
19
|
| `@cosmoops/connect-sdk/server` | your backend | **yes** |
|
|
20
20
|
| `@cosmoops/connect-sdk/client` | the browser | never |
|
|
21
21
|
| `@cosmoops/connect-sdk/next` | your backend | **yes** (Next.js plug-and-play) |
|
|
22
|
+
| `@cosmoops/connect-sdk/mcp` | your backend | **yes** (store, as MCP tools) |
|
|
22
23
|
| `@cosmoops/connect-sdk` | anywhere | types + `ConnectError` only |
|
|
23
24
|
|
|
24
25
|
The browser half never sees a credential. It calls your own origin, and
|
|
@@ -35,7 +36,7 @@ The fast path — most integrations are this and nothing else. Not on Next.js?
|
|
|
35
36
|
Skip to [Other frameworks](#other-frameworks); the shape is the same, you
|
|
36
37
|
just wire the session cookie yourself.
|
|
37
38
|
|
|
38
|
-
**1. Install and get credentials.** From your application's
|
|
39
|
+
**1. Install and get credentials.** From your application's Connect client section
|
|
39
40
|
in Connect, copy the API key and OAuth client id.
|
|
40
41
|
|
|
41
42
|
```bash
|
|
@@ -87,7 +88,7 @@ import "streamdown/styles.css";
|
|
|
87
88
|
Skip the last two if you pass `askAi={false}` to `AppShell`. The colour tokens
|
|
88
89
|
Streamdown expects are already mapped onto HeroUI's in `theme.css`.
|
|
89
90
|
|
|
90
|
-
**5. Register the redirect URI.** In the application's
|
|
91
|
+
**5. Register the redirect URI.** In the application's Connect client section, add
|
|
91
92
|
`{your origin}/api/connect/callback`. This is the one step the SDK cannot do
|
|
92
93
|
for you.
|
|
93
94
|
|
|
@@ -123,7 +124,7 @@ layout and Connect's dashboard rather than files it can safely write. See
|
|
|
123
124
|
|
|
124
125
|
Two things most integrations add next:
|
|
125
126
|
|
|
126
|
-
- **A shell** — `AppShell` from `/client` is the
|
|
127
|
+
- **A shell** — `AppShell` from `/client` is the rail and the access
|
|
127
128
|
gate every application platform needs. See [App shell](#app-shell).
|
|
128
129
|
- **Route protection before render** — see [Route protection and
|
|
129
130
|
`proxy.ts`](#route-protection-and-proxyts) below; `requireConnectSession()`
|
|
@@ -136,7 +137,7 @@ for you under the hood.
|
|
|
136
137
|
|
|
137
138
|
## Server setup
|
|
138
139
|
|
|
139
|
-
One credential does three jobs — the API key plaintext from your application's
|
|
140
|
+
One credential does three jobs — the API key plaintext from your application's Connect client section is also your OAuth2 `client_secret` and the key your `id_token` is signed with. Revoking it revokes all three.
|
|
140
141
|
|
|
141
142
|
```ts
|
|
142
143
|
import { ConnectClient } from "@cosmoops/connect-sdk/server";
|
|
@@ -518,17 +519,17 @@ that URL yourself instead.
|
|
|
518
519
|
import {
|
|
519
520
|
AppShell,
|
|
520
521
|
ConnectLoginButton, ConnectRegisterButton,
|
|
521
|
-
|
|
522
|
+
ConnectUserButton, ConnectAppsGrid, ConnectPlans,
|
|
522
523
|
} from "@cosmoops/connect-sdk/client";
|
|
523
524
|
|
|
524
|
-
// The whole frame —
|
|
525
|
-
<AppShell name="Dispatch" nav={nav}>{children}</AppShell>
|
|
525
|
+
// The whole frame — the rail, the access gate, Ask AI. See "App shell" below.
|
|
526
|
+
<AppShell name="Dispatch" nav={nav} secondaryNav={settingsNav}>{children}</AppShell>
|
|
526
527
|
|
|
527
528
|
// Or the pieces, if you already have a layout. The pair swaps automatically
|
|
528
529
|
// on sign-in state.
|
|
529
|
-
<ConnectAppsGrid />
|
|
530
|
+
<ConnectAppsGrid variant="icon" />
|
|
530
531
|
<ConnectLoginButton />
|
|
531
|
-
<
|
|
532
|
+
<ConnectUserButton variant="inline" />
|
|
532
533
|
|
|
533
534
|
// Pricing page
|
|
534
535
|
<ConnectPlans />
|
|
@@ -536,10 +537,13 @@ import {
|
|
|
536
537
|
|
|
537
538
|
| Component | What it does |
|
|
538
539
|
| --- | --- |
|
|
539
|
-
| `AppShell` | **The whole application frame** — collapsible
|
|
540
|
-
| `
|
|
540
|
+
| `AppShell` | **The whole application frame** — one collapsible rail carrying the brand, workspace switcher, both navs, the launcher and the account row, with your pages beside it. No top bar. Mounts `ConnectAccessGate` for you. The one `/client` export without the `Connect` prefix; alias it on import if your own layout shares the name. |
|
|
541
|
+
| `ConnectSidebar` · `ConnectSidebarHeader` · `ConnectSidebarBody` · `ConnectSidebarFooter` · `ConnectSidebarRow` | The rail in pieces, for chrome the props cannot express. `AppShell` composes exactly these; they coordinate through `ConnectShellProvider`. |
|
|
542
|
+
| `ConnectSidebarNav` | The rail's nav column on its own — sections, one level of children, active-route highlight — for an app that already has its own chrome. (Was `ConnectSideNav`.) |
|
|
541
543
|
| `ConnectLoginButton` · `ConnectRegisterButton` | "Continue with Connect", in the shape people know from Google. Render nothing when signed in. |
|
|
542
|
-
| `
|
|
544
|
+
| `ConnectUserButton` | The account row at the foot of the rail — circular avatar, name over email — opening a modal with identity, sessions and the Connect destinations. Profile, Manage subscription and Open Connect each open Connect in a new tab; Sign out acts in place. Renders nothing when signed out. (Was `ConnectUserMenu`.) |
|
|
545
|
+
| `ConnectProfileModal` | The same modal with no trigger, driven by `isOpen`/`onOpenChange` — for an app keeping its own account control. |
|
|
546
|
+
| `ConnectWorkspaceSwitcher` | Sits in the rail's header. Hides itself for an account with one workspace; shows unusable ones disabled, with the reason. |
|
|
543
547
|
| `ConnectAppsGrid` | Google-style launcher — grid icon opening a modal of every app this workspace can open. |
|
|
544
548
|
| `ConnectPlans` | Monthly/annual toggle, features, add-ons, live discount, coupon field, and the subscribe / upgrade / downgrade / cancel flow. |
|
|
545
549
|
| `ConnectPlansPage` | A whole `/plans` page — readiness banner, current subscription, and the grid. What `connect-sdk init` scaffolds. |
|
|
@@ -706,21 +710,20 @@ export function Shell({ children }: { children: React.ReactNode }) {
|
|
|
706
710
|
nav={[
|
|
707
711
|
{ label: "Overview", href: "/", icon: <LayoutDashboard className="size-5" /> },
|
|
708
712
|
{ label: "Shipments", href: "/shipments", icon: <Truck className="size-5" /> },
|
|
713
|
+
]}
|
|
714
|
+
// Pinned to the foot of the rail: the routes you go to in order to
|
|
715
|
+
// change how the job works, not to do it.
|
|
716
|
+
secondaryNav={[
|
|
709
717
|
{
|
|
710
|
-
|
|
718
|
+
label: "Settings",
|
|
719
|
+
href: "/settings",
|
|
720
|
+
icon: <Settings className="size-5" />,
|
|
711
721
|
items: [
|
|
712
|
-
{
|
|
713
|
-
|
|
714
|
-
href: "/settings",
|
|
715
|
-
icon: <Settings className="size-5" />,
|
|
716
|
-
items: [
|
|
717
|
-
{ label: "Members", href: "/settings/members" },
|
|
718
|
-
{ label: "Billing", href: "/settings/billing" },
|
|
719
|
-
],
|
|
720
|
-
},
|
|
721
|
-
{ label: "Plans", href: "/plans", icon: <Receipt className="size-5" />, gateExempt: true },
|
|
722
|
+
{ label: "Members", href: "/settings/members" },
|
|
723
|
+
{ label: "Billing", href: "/settings/billing" },
|
|
722
724
|
],
|
|
723
725
|
},
|
|
726
|
+
{ label: "Plans", href: "/plans", icon: <Receipt className="size-5" />, gateExempt: true },
|
|
724
727
|
]}
|
|
725
728
|
>
|
|
726
729
|
{children}
|
|
@@ -729,10 +732,16 @@ export function Shell({ children }: { children: React.ReactNode }) {
|
|
|
729
732
|
}
|
|
730
733
|
```
|
|
731
734
|
|
|
732
|
-
That is the entire layout of
|
|
733
|
-
identical across the family — the lockup, the rail geometry, the
|
|
734
|
-
treatment, the app launcher, the account
|
|
735
|
-
here; only `name`, `icon` and
|
|
735
|
+
That is the entire layout of an application platform. Everything that should
|
|
736
|
+
look identical across the family — the lockup, the rail geometry, the
|
|
737
|
+
active-route treatment, the app launcher, the account row, the mobile drawer —
|
|
738
|
+
comes from here; only `name`, `icon` and the two navs differ per platform.
|
|
739
|
+
|
|
740
|
+
**There is no top bar.** The launcher and the account row live at the foot of
|
|
741
|
+
the rail, one press from wherever the pointer already is, and a page's own
|
|
742
|
+
heading belongs to the page — put it in `ConnectPageShell` from
|
|
743
|
+
`@cosmoops/connect-sdk/ui`. Below `md` a slim bar carries the drawer trigger
|
|
744
|
+
and nothing else.
|
|
736
745
|
|
|
737
746
|
**Routing is a prop.** The SDK ships into any React app and cannot import
|
|
738
747
|
`next/link`, so `renderLink` is how your router gets involved; the default is a
|
|
@@ -747,7 +756,7 @@ around `children` — an app that ships a shell and forgets the gate is an app
|
|
|
747
756
|
that lets a suspended workspace keep working. The chrome is never gated: the
|
|
748
757
|
sidebar, the lockup, the account menu and sign-out stay reachable in every
|
|
749
758
|
blocked state. Routes that resolve a block rather than needing one — your
|
|
750
|
-
pricing page — are flagged where they are declared:
|
|
759
|
+
pricing page — are flagged where they are declared, in either nav:
|
|
751
760
|
|
|
752
761
|
```tsx
|
|
753
762
|
{ label: "Plans", href: "/plans", gateExempt: true }
|
|
@@ -771,26 +780,38 @@ Connect, and `ai:chat` on the API key behind your bridge.
|
|
|
771
780
|
|
|
772
781
|
Other things worth knowing:
|
|
773
782
|
|
|
774
|
-
- **The rail
|
|
775
|
-
(`localStorage`).
|
|
776
|
-
|
|
777
|
-
|
|
783
|
+
- **The rail starts collapsed** and remembers the choice per browser
|
|
784
|
+
(`localStorage`). The icon rail is the resting state; expanding is the
|
|
785
|
+
deliberate act. Expanded, the collapse control sits at the header's trailing
|
|
786
|
+
edge; collapsed, hovering the lockup swaps it for the expand glyph — so the
|
|
787
|
+
rail spends no row on a permanent control, at the cost of the lockup no
|
|
788
|
+
longer being a link home while collapsed. Pass `defaultCollapsed={false}` to
|
|
789
|
+
open by default, drive it with `collapsed` + `onCollapsedChange`, or pin it
|
|
790
|
+
open with `collapsible={false}`.
|
|
791
|
+
- **The collapse animates width and nothing else.** Every row is laid out so
|
|
792
|
+
its glyph sits on the same horizontal centre in both states, and labels are
|
|
793
|
+
clipped rather than re-measured — so no row reflows and nothing drifts
|
|
794
|
+
sideways mid-transition.
|
|
795
|
+
- **Below `md` there is no rail**, only a drawer behind the mobile bar's menu
|
|
778
796
|
button — a 64px icon strip on a phone is a worse drawer, not a smaller one.
|
|
779
797
|
- **Children unfold only on the branch you are on.** A sidebar that opens
|
|
780
798
|
every branch at once is a sitemap.
|
|
781
799
|
- **No `isSignedIn` branch, in here or in yours.** The auth buttons render
|
|
782
|
-
nothing once a session exists, the account
|
|
800
|
+
nothing once a session exists, the account row renders nothing without one,
|
|
783
801
|
and the launcher renders nothing when signed out.
|
|
784
|
-
- `
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
- `theme` / `onThemeChange` pass straight through to `
|
|
802
|
+
- `secondaryNav`, `sidebarFooter` and `footer` are slots; `contentClassName`
|
|
803
|
+
replaces the content pane's default max-width and padding. (`title` and
|
|
804
|
+
`headerActions` are gone with the top bar — see the 0.12.0 changelog entry.)
|
|
805
|
+
- `theme` / `onThemeChange` pass straight through to `ConnectUserButton` — the
|
|
788
806
|
SDK has no theme provider of its own, deliberately.
|
|
789
807
|
|
|
790
|
-
`
|
|
791
|
-
likes
|
|
792
|
-
|
|
793
|
-
|
|
808
|
+
`ConnectSidebarNav` is the nav column alone, for an app with chrome it already
|
|
809
|
+
likes, and `ConnectSidebar` / `ConnectSidebarHeader` / `ConnectSidebarBody` /
|
|
810
|
+
`ConnectSidebarFooter` / `ConnectSidebarRow` are the rest of the rail — the
|
|
811
|
+
exact pieces `AppShell` composes, so chrome the props cannot express is a
|
|
812
|
+
rearrangement rather than a fork. `resolveActivePath` and `isNavItemActive` are
|
|
813
|
+
the same pure functions the rail highlights with, exported so a breadcrumb or a
|
|
814
|
+
document title derived from your nav cannot disagree with it.
|
|
794
815
|
|
|
795
816
|
### Logo
|
|
796
817
|
|
|
@@ -825,6 +846,56 @@ So the hand-off is a URL, and Connect renders it as a modal over the relevant pa
|
|
|
825
846
|
|
|
826
847
|
Full history: [CHANGELOG.md](./CHANGELOG.md). Latest first:
|
|
827
848
|
|
|
849
|
+
### 0.12.0 — breaking
|
|
850
|
+
|
|
851
|
+
The application shell is one column. **The top bar is gone**; the rail holds
|
|
852
|
+
everything it used to, and starts collapsed.
|
|
853
|
+
|
|
854
|
+
- **`title` and `headerActions` are removed** with the bar that held them. A
|
|
855
|
+
page's heading goes in `ConnectPageShell` (from `/ui`), next to that page's
|
|
856
|
+
own breadcrumbs and actions; an application-level control goes in
|
|
857
|
+
`sidebarFooter` as a `ConnectSidebarRow`.
|
|
858
|
+
- **`defaultCollapsed` now defaults to `true`.** Pass `false` for 0.11's
|
|
859
|
+
behaviour.
|
|
860
|
+
- **`ConnectSideNav` → `ConnectSidebarNav`**, **`ConnectUserMenu` →
|
|
861
|
+
`ConnectUserButton`**. Both old names remain as deprecated aliases.
|
|
862
|
+
`AppShell` keeps its name — the one export here without the `Connect`
|
|
863
|
+
prefix, as decided in 0.8.0.
|
|
864
|
+
- **New:** `secondaryNav` (a second nav group pinned to the foot of the rail),
|
|
865
|
+
`ConnectProfileModal` (the account modal with no trigger), and the rail's
|
|
866
|
+
own pieces — `ConnectSidebar`, `ConnectSidebarHeader`, `ConnectSidebarBody`,
|
|
867
|
+
`ConnectSidebarFooter`, `ConnectSidebarRow`, `ConnectShellProvider`,
|
|
868
|
+
`useConnectShell`, `sidebarRowClass`.
|
|
869
|
+
- **The workspace switcher moved into the rail's header** and is collapse-aware;
|
|
870
|
+
the account row sits at the foot with a circular avatar.
|
|
871
|
+
- **Fixed:** the closed Ask AI panel occupied 400px at `lg` and up on every page
|
|
872
|
+
with `askAi` on; sidebar contrast (the active row was **below WCAG AA** in
|
|
873
|
+
both schemes); the launcher and account rows looked permanently selected.
|
|
874
|
+
|
|
875
|
+
### 0.11.0 — breaking
|
|
876
|
+
|
|
877
|
+
- **`ConnectUser.workspaceId`, `workspaceName` and `workspaceType` are
|
|
878
|
+
nullable.** They are `null` for exactly one thing: a **Connect platform
|
|
879
|
+
staff** session, which belongs to no workspace. If you never enable staff
|
|
880
|
+
access — it is off by default — this is a type narrow and nothing else.
|
|
881
|
+
`?? ""` is the wrong fix: decide what your tenanting does with a session that
|
|
882
|
+
has no tenant.
|
|
883
|
+
- **Connect platform staff sign-in**, `platformAccess` on `exchangeCode()` and
|
|
884
|
+
`getUser()`, `<ConnectPlatformGate>` and `useConnectPlatformAccess()`.
|
|
885
|
+
|
|
886
|
+
### 0.10.0
|
|
887
|
+
|
|
888
|
+
- **`@cosmoops/connect-sdk/ui`** — `ConnectPageShell`, `ConnectSection`,
|
|
889
|
+
`ConnectEmptyState`, `ConnectStatusChip`, `ConnectSettingRow`. Deliberately
|
|
890
|
+
*not* a `"use client"` module, so a page shell does not become a client
|
|
891
|
+
boundary.
|
|
892
|
+
- `ConnectLinkButton`, `ConnectConfirmDialog`, `ConnectCursorPagination`,
|
|
893
|
+
`ConnectFilterTabs`, `ConnectAccountSessions` — built since 0.8 and reachable
|
|
894
|
+
from no entry point until now.
|
|
895
|
+
- `ConnectWorkspaceSwitcher`, `useConnectWorkspaces()`,
|
|
896
|
+
`useConnectWorkspaceMembers()`, and the `workspaces` resource on the server
|
|
897
|
+
client.
|
|
898
|
+
|
|
828
899
|
### 0.9.0
|
|
829
900
|
|
|
830
901
|
- **Ask AI**, mounted by `AppShell` by default — a chat panel grounded in the
|
package/dist/cjs/cli/doctor.js
CHANGED
|
@@ -162,8 +162,8 @@ function checkEnv(root) {
|
|
|
162
162
|
status: "fail",
|
|
163
163
|
title: `${name} set`,
|
|
164
164
|
detail: declaredEmpty
|
|
165
|
-
? `Declared but empty in .env.local. The value is in your application's
|
|
166
|
-
: `Not found in .env.local or the environment. Both values are in your application's
|
|
165
|
+
? `Declared but empty in .env.local. The value is in your application's Connect client section in Connect.`
|
|
166
|
+
: `Not found in .env.local or the environment. Both values are in your application's Connect client section in Connect.`,
|
|
167
167
|
};
|
|
168
168
|
});
|
|
169
169
|
}
|
|
@@ -176,7 +176,7 @@ function checkRedirectUri() {
|
|
|
176
176
|
return {
|
|
177
177
|
status: "warn",
|
|
178
178
|
title: "Redirect URI registered in Connect",
|
|
179
|
-
detail: "Not checkable from here. Confirm {your origin}/api/connect/callback is listed as a redirect URI in your application's
|
|
179
|
+
detail: "Not checkable from here. Confirm {your origin}/api/connect/callback is listed as a redirect URI in your application's Connect client section - including the deployed origin, not just http://localhost:3000.",
|
|
180
180
|
};
|
|
181
181
|
}
|
|
182
182
|
const MARKER = { pass: " ok ", warn: " note ", fail: " FAIL " };
|
package/dist/cjs/cli/init.js
CHANGED
|
@@ -44,7 +44,7 @@ const PLANS_PAGE = `"use client";
|
|
|
44
44
|
// with the other exports) whenever you want your own copy or layout.
|
|
45
45
|
export { ConnectPlansPage as default } from "@cosmoops/connect-sdk/client";
|
|
46
46
|
`;
|
|
47
|
-
const ENV_TEMPLATE = `# CosmoOps Connect - values come from your application's
|
|
47
|
+
const ENV_TEMPLATE = `# CosmoOps Connect - values come from your application's Connect client section.
|
|
48
48
|
CONNECT_API_KEY=
|
|
49
49
|
CONNECT_CLIENT_ID=
|
|
50
50
|
`;
|
|
@@ -111,7 +111,7 @@ function runInit(argv = []) {
|
|
|
111
111
|
console.log(` ${marker} ${(0, node_path_1.relative)(root, target)} - ${scaffold.description}`);
|
|
112
112
|
}
|
|
113
113
|
const env = ensureEnv(root);
|
|
114
|
-
console.log(` ${env === "skipped" ? "exists " : env === "appended" ? "updated" : "created"} .env.local - fill in the two values from your application's
|
|
114
|
+
console.log(` ${env === "skipped" ? "exists " : env === "appended" ? "updated" : "created"} .env.local - fill in the two values from your application's Connect client section`);
|
|
115
115
|
// The two steps a scaffolder must not do for you: editing an existing layout
|
|
116
116
|
// means parsing and rewriting JSX someone else wrote, and the redirect URI
|
|
117
117
|
// lives in Connect, not in this repo.
|
|
@@ -137,7 +137,7 @@ Three things left, all one-time:
|
|
|
137
137
|
Tailwind roots its content scan inside node_modules and skips your app.
|
|
138
138
|
|
|
139
139
|
3. Register {your origin}/api/connect/callback as a redirect URI in your
|
|
140
|
-
application's
|
|
140
|
+
application's Connect client section in Connect.
|
|
141
141
|
|
|
142
142
|
No next.config.ts changes needed - not even transpilePackages: dist/ is
|
|
143
143
|
already ES2022 with "use client" hoisted, so Turbopack and webpack consume it
|
|
@@ -40,12 +40,31 @@ function formatDate(iso) {
|
|
|
40
40
|
function useConnectAccessGate() {
|
|
41
41
|
const { session, isLoading: sessionLoading } = (0, provider_js_1.useConnect)();
|
|
42
42
|
const { data, isLoading: entitlementsLoading } = (0, provider_js_1.useConnectEntitlements)();
|
|
43
|
-
if (sessionLoading
|
|
43
|
+
if (sessionLoading) {
|
|
44
44
|
return { state: "loading", blocker: null, paymentRetry: null };
|
|
45
45
|
}
|
|
46
46
|
if (!session?.user) {
|
|
47
47
|
return { state: "signed_out", blocker: null, paymentRetry: null };
|
|
48
48
|
}
|
|
49
|
+
// Connect platform staff pass straight through, and this check has to come
|
|
50
|
+
// *before* the entitlements read below or the gate would hard-block them.
|
|
51
|
+
//
|
|
52
|
+
// Every state this gate can report is a fact about a workspace - suspended,
|
|
53
|
+
// no plan, wrong audience, awaiting approval, payment due. A staff session
|
|
54
|
+
// has no workspace at all (see `SessionAccess.staff`), so it has no subscription
|
|
55
|
+
// either, and `subscription_missing` would send an operator to your plan
|
|
56
|
+
// picker to buy a plan for a workspace that does not exist. "Nothing is
|
|
57
|
+
// blocking this session" is the truthful answer, not a lenient one.
|
|
58
|
+
//
|
|
59
|
+
// The entitlements request is still issued - hooks cannot be called
|
|
60
|
+
// conditionally - and the bridge answers it `403 no_workspace`. That costs
|
|
61
|
+
// one failed request per staff page load, and its result is ignored here.
|
|
62
|
+
if (session.staff) {
|
|
63
|
+
return { state: "clear", blocker: null, paymentRetry: null };
|
|
64
|
+
}
|
|
65
|
+
if (entitlementsLoading) {
|
|
66
|
+
return { state: "loading", blocker: null, paymentRetry: null };
|
|
67
|
+
}
|
|
49
68
|
if (!data) {
|
|
50
69
|
// Signed in, not loading, yet nothing came back - a failed read (see
|
|
51
70
|
// `error` on `useConnectEntitlements`). Fail open rather than hard-block
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.matchConnectScreen = matchConnectScreen;
|
|
4
|
+
exports.isLinkableConnectScreen = isLinkableConnectScreen;
|
|
5
|
+
/**
|
|
6
|
+
* Matching a live path against the screens your application declared.
|
|
7
|
+
*
|
|
8
|
+
* The same rule Connect uses for its own routes, and it has to be here too
|
|
9
|
+
* because only this side knows what your app's routes are: a dynamic segment
|
|
10
|
+
* matches exactly one path segment, and where several patterns fit, the most
|
|
11
|
+
* *literal* wins - so `/deals/new` resolves to itself rather than to
|
|
12
|
+
* `/deals/[dealId]` merely because that also fits.
|
|
13
|
+
*
|
|
14
|
+
* Plain functions with no React and no imports beyond the type, so the panel,
|
|
15
|
+
* the action links and any host that wants to reuse the matching can share one
|
|
16
|
+
* implementation.
|
|
17
|
+
*/
|
|
18
|
+
/** Trims the query string, the hash and any trailing slash. */
|
|
19
|
+
function normalise(path) {
|
|
20
|
+
const withoutQuery = path.split(/[?#]/)[0] ?? "";
|
|
21
|
+
return withoutQuery.length > 1 ? withoutQuery.replace(/\/+$/, "") : withoutQuery;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The declared screen a live path is on, or `null` if none matches.
|
|
25
|
+
*
|
|
26
|
+
* `null` is a normal outcome, not an error: an application need only describe
|
|
27
|
+
* the screens it wants the assistant to know about, and an undescribed one
|
|
28
|
+
* simply costs the assistant its orientation rather than costing the user an
|
|
29
|
+
* answer.
|
|
30
|
+
*/
|
|
31
|
+
function matchConnectScreen(screens, path) {
|
|
32
|
+
const segments = normalise(path).split("/").filter(Boolean);
|
|
33
|
+
let best = null;
|
|
34
|
+
for (const screen of screens) {
|
|
35
|
+
const pattern = normalise(screen.path).split("/").filter(Boolean);
|
|
36
|
+
if (pattern.length !== segments.length)
|
|
37
|
+
continue;
|
|
38
|
+
let literals = 0;
|
|
39
|
+
let matches = true;
|
|
40
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
41
|
+
const part = pattern[i];
|
|
42
|
+
if (part.startsWith("[") && part.endsWith("]"))
|
|
43
|
+
continue;
|
|
44
|
+
if (part !== segments[i]) {
|
|
45
|
+
matches = false;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
literals++;
|
|
49
|
+
}
|
|
50
|
+
if (matches && (!best || literals > best.literals))
|
|
51
|
+
best = { screen, literals };
|
|
52
|
+
}
|
|
53
|
+
return best?.screen ?? null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Whether an href the model produced may be rendered as a link.
|
|
57
|
+
*
|
|
58
|
+
* Same reasoning as Connect's own `showActions` check, and the same conclusion:
|
|
59
|
+
* an assistant that can emit an arbitrary URL is a redirect the model can be
|
|
60
|
+
* talked into, and retrieved content shares a context window with its
|
|
61
|
+
* tool-calling authority. So a link is drawn only when it resolves to a screen
|
|
62
|
+
* *your application declared* - which makes an external destination structurally
|
|
63
|
+
* impossible, since no route pattern has a scheme or a host.
|
|
64
|
+
*
|
|
65
|
+
* The protocol-relative form is refused separately: `//evil.example.com` has no
|
|
66
|
+
* scheme for a path matcher to trip over, and would otherwise reach an anchor
|
|
67
|
+
* as an absolute destination.
|
|
68
|
+
*/
|
|
69
|
+
function isLinkableConnectScreen(screens, href) {
|
|
70
|
+
if (!href.startsWith("/") || href.startsWith("//"))
|
|
71
|
+
return false;
|
|
72
|
+
return matchConnectScreen(screens, href) !== null;
|
|
73
|
+
}
|
|
@@ -8,10 +8,12 @@ exports.AiTimeline = AiTimeline;
|
|
|
8
8
|
exports.AiDetails = AiDetails;
|
|
9
9
|
exports.AiSteps = AiSteps;
|
|
10
10
|
exports.AiSources = AiSources;
|
|
11
|
+
exports.AiActions = AiActions;
|
|
11
12
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
12
13
|
const react_1 = require("react");
|
|
13
14
|
const react_2 = require("@heroui/react");
|
|
14
15
|
const lucide_react_1 = require("lucide-react");
|
|
16
|
+
const ai_screens_js_1 = require("./ai-screens.js");
|
|
15
17
|
const ai_format_js_1 = require("./ai-format.js");
|
|
16
18
|
/**
|
|
17
19
|
* What Ask AI draws.
|
|
@@ -230,3 +232,38 @@ function AiSources({ sources }) {
|
|
|
230
232
|
return ((0, jsx_runtime_1.jsx)("li", { children: source.uri ? ((0, jsx_runtime_1.jsxs)("a", { href: source.uri, target: "_blank", rel: "noopener noreferrer", className: "flex gap-1.5 rounded-sm hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus", children: [label, (0, jsx_runtime_1.jsx)(lucide_react_1.ExternalLink, { className: "mt-0.5 size-3 shrink-0 text-muted", "aria-hidden": true })] })) : ((0, jsx_runtime_1.jsx)("span", { className: "flex gap-1.5", children: label })) }, `${source.uri ?? source.title}-${index}`));
|
|
231
233
|
}) })] }));
|
|
232
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* Where to go next, as links into *your* application.
|
|
237
|
+
*
|
|
238
|
+
* ## Why links and not navigation
|
|
239
|
+
*
|
|
240
|
+
* The obvious version of "the assistant can act" moves the user. It is also the
|
|
241
|
+
* version that breaks the property the whole design rests on: nothing happens
|
|
242
|
+
* without a click. Retrieved content shares a context window with the model's
|
|
243
|
+
* tool-calling authority, so an auto-navigate is a redirect primitive that
|
|
244
|
+
* content can reach. A link cannot be, and loses nothing - the user was going
|
|
245
|
+
* to click something anyway.
|
|
246
|
+
*
|
|
247
|
+
* ## Why every href is checked against your declared screens
|
|
248
|
+
*
|
|
249
|
+
* The model can emit any string. Only your application knows which strings are
|
|
250
|
+
* real routes, which is why `screens` is a prop rather than something Connect
|
|
251
|
+
* could supply. A link that does not resolve is *dropped* rather than rendered
|
|
252
|
+
* broken - the prose around it still stands, so a missing link reads as terse
|
|
253
|
+
* rather than wrong.
|
|
254
|
+
*
|
|
255
|
+
* `renderLink` is how this stays router-agnostic. Given one, a link is your
|
|
256
|
+
* framework's - a Next `<Link>`, a TanStack `<Link>` - and navigation is a
|
|
257
|
+
* client transition. Without one it falls back to an anchor, which works
|
|
258
|
+
* everywhere and costs a document load.
|
|
259
|
+
*/
|
|
260
|
+
function AiActions({ title, actions, screens, renderLink, }) {
|
|
261
|
+
const resolved = actions.filter((action) => (0, ai_screens_js_1.isLinkableConnectScreen)(screens, action.href));
|
|
262
|
+
if (resolved.length === 0)
|
|
263
|
+
return null;
|
|
264
|
+
const className = "group flex items-center gap-2 rounded-lg border border-separator bg-surface px-3 py-2 no-underline transition-colors hover:border-accent";
|
|
265
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "my-3", children: [title ? (0, jsx_runtime_1.jsx)("p", { className: "mb-1.5 text-xs font-medium text-muted", children: title }) : null, (0, jsx_runtime_1.jsx)("div", { className: "flex flex-col gap-1.5", children: resolved.map((action) => {
|
|
266
|
+
const body = ((0, jsx_runtime_1.jsxs)("span", { className: "min-w-0 flex-1", children: [(0, jsx_runtime_1.jsx)("span", { className: "block truncate text-xs font-medium text-foreground", children: action.label }), action.detail ? ((0, jsx_runtime_1.jsx)("span", { className: "block truncate text-[11px] text-muted", children: action.detail })) : null] }));
|
|
267
|
+
return ((0, jsx_runtime_1.jsx)(react_1.Fragment, { children: renderLink ? (renderLink({ href: action.href, className, children: body })) : ((0, jsx_runtime_1.jsx)("a", { href: action.href, className: className, children: body })) }, action.href));
|
|
268
|
+
}) })] }));
|
|
269
|
+
}
|