@cosmoops/connect-sdk 0.7.0 → 0.9.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 +165 -0
- package/README.md +273 -91
- package/dist/cjs/client/components/access-gate.js +39 -4
- package/dist/cjs/client/components/ai-format.js +135 -0
- package/dist/cjs/client/components/ai-widgets.js +232 -0
- package/dist/cjs/client/components/app-shell.js +7 -6
- package/dist/cjs/client/components/ask-ai.js +334 -0
- package/dist/cjs/client/components/nav.js +1 -1
- package/dist/cjs/client/components/side-nav.js +1 -1
- package/dist/cjs/client/components/user-menu.js +28 -5
- package/dist/cjs/client/components/workspace-type-gate.js +48 -0
- package/dist/cjs/client/index.js +31 -3
- package/dist/cjs/client/provider.js +45 -2
- package/dist/cjs/server/client.js +1 -1
- package/dist/cjs/server/handler.js +43 -0
- package/dist/cjs/server/http.js +46 -24
- package/dist/cjs/server/resources/entitlements.js +41 -0
- package/dist/cjs/server/resources/identity.js +2 -0
- package/dist/esm/client/components/access-gate.js +40 -5
- package/dist/esm/client/components/ai-format.js +129 -0
- package/dist/esm/client/components/ai-widgets.js +223 -0
- package/dist/esm/client/components/app-shell.js +6 -5
- package/dist/esm/client/components/ask-ai.js +331 -0
- package/dist/esm/client/components/nav.js +1 -1
- package/dist/esm/client/components/side-nav.js +1 -1
- package/dist/esm/client/components/user-menu.js +28 -5
- package/dist/esm/client/components/workspace-type-gate.js +45 -0
- package/dist/esm/client/index.js +13 -2
- package/dist/esm/client/provider.js +43 -2
- package/dist/esm/server/client.js +1 -1
- package/dist/esm/server/handler.js +43 -0
- package/dist/esm/server/http.js +46 -24
- package/dist/esm/server/resources/entitlements.js +41 -0
- package/dist/esm/server/resources/identity.js +2 -0
- package/dist/theme.css +87 -0
- package/dist/types/client/components/access-gate.d.ts +17 -3
- package/dist/types/client/components/ai-format.d.ts +24 -0
- package/dist/types/client/components/ai-widgets.d.ts +42 -0
- package/dist/types/client/components/app-shell.d.ts +21 -5
- package/dist/types/client/components/ask-ai.d.ts +73 -0
- package/dist/types/client/components/nav.d.ts +3 -3
- package/dist/types/client/components/side-nav.d.ts +1 -1
- package/dist/types/client/components/user-menu.d.ts +2 -1
- package/dist/types/client/components/workspace-type-gate.d.ts +48 -0
- package/dist/types/client/index.d.ts +10 -3
- package/dist/types/client/provider.d.ts +44 -1
- package/dist/types/server/client.d.ts +1 -1
- package/dist/types/server/http.d.ts +6 -1
- package/dist/types/server/resources/entitlements.d.ts +33 -1
- package/dist/types/types.d.ts +174 -0
- package/package.json +8 -2
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
+
import { type ConnectAskAiProps } from "./ask-ai.js";
|
|
2
3
|
import { type ConnectLinkRenderer, type ConnectNav } from "./nav.js";
|
|
3
4
|
import type { ThemeMode } from "../../types.js";
|
|
4
|
-
export interface
|
|
5
|
+
export interface AppShellProps {
|
|
5
6
|
/** This application's name, in the `cosmo·ops VOICE` lockup. See `ConnectLogo`. */
|
|
6
7
|
name: string;
|
|
7
8
|
/** This application's glyph, drawn on the CosmoOps badge. Falls back to the Connect rings. */
|
|
@@ -61,6 +62,21 @@ export interface ConnectAppShellProps {
|
|
|
61
62
|
* say. Prefix-matched, same as the nav's active resolution.
|
|
62
63
|
*/
|
|
63
64
|
gateExemptRoutes?: string[];
|
|
65
|
+
/**
|
|
66
|
+
* Mount `ConnectAskAi` - a chat panel grounded in the signed-in workspace's
|
|
67
|
+
* Context Index, streamed through your own bridge. Pass an object to
|
|
68
|
+
* override its heading, copy or markdown plugins.
|
|
69
|
+
*
|
|
70
|
+
* **On by default**, like `gate`, and for the same reason: a family of
|
|
71
|
+
* application platforms should answer questions the same way, and a feature
|
|
72
|
+
* every partner has to remember to switch on is a feature most of them do
|
|
73
|
+
* not have. It stays inert until it can actually work - the trigger renders
|
|
74
|
+
* nothing for a signed-out visitor, and a workspace whose Context Index is
|
|
75
|
+
* off gets a message in the panel rather than a broken answer.
|
|
76
|
+
*
|
|
77
|
+
* Set `false` for an application that should not offer it at all.
|
|
78
|
+
*/
|
|
79
|
+
askAi?: boolean | ConnectAskAiProps;
|
|
64
80
|
className?: string;
|
|
65
81
|
/** Classes on the scrolling content pane - override the default max-width and padding here. */
|
|
66
82
|
contentClassName?: string;
|
|
@@ -82,12 +98,12 @@ export interface ConnectAppShellProps {
|
|
|
82
98
|
* "use client";
|
|
83
99
|
* import Link from "next/link";
|
|
84
100
|
* import { usePathname } from "next/navigation";
|
|
85
|
-
* import {
|
|
101
|
+
* import { AppShell } from "@cosmoops/connect-sdk/client";
|
|
86
102
|
* import { LayoutDashboard, Truck } from "lucide-react";
|
|
87
103
|
*
|
|
88
104
|
* export function Shell({ children }: { children: React.ReactNode }) {
|
|
89
105
|
* return (
|
|
90
|
-
* <
|
|
106
|
+
* <AppShell
|
|
91
107
|
* name="Dispatch"
|
|
92
108
|
* icon={<Truck />}
|
|
93
109
|
* activePath={usePathname()}
|
|
@@ -98,7 +114,7 @@ export interface ConnectAppShellProps {
|
|
|
98
114
|
* ]}
|
|
99
115
|
* >
|
|
100
116
|
* {children}
|
|
101
|
-
* </
|
|
117
|
+
* </AppShell>
|
|
102
118
|
* );
|
|
103
119
|
* }
|
|
104
120
|
* ```
|
|
@@ -112,4 +128,4 @@ export interface ConnectAppShellProps {
|
|
|
112
128
|
* so a page with a shell, a plan table and a usage meter still makes one
|
|
113
129
|
* session read.
|
|
114
130
|
*/
|
|
115
|
-
export declare function
|
|
131
|
+
export declare function AppShell({ name, icon, homeHref, nav, activePath, renderLink, title, headerActions, sidebarFooter, footer, defaultCollapsed, collapsed: controlledCollapsed, onCollapsedChange, collapsible, showAppsGrid, showAuthButtons, theme, onThemeChange, gate, gateExemptRoutes, askAi, className, contentClassName, children, }: AppShellProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type StreamdownProps } from "streamdown";
|
|
2
|
+
export interface ConnectAskAiProps {
|
|
3
|
+
/** Panel heading. Defaults to `"Ask AI"`. */
|
|
4
|
+
title?: string;
|
|
5
|
+
/** Byline over each assistant turn. Defaults to `"Assistant"`. */
|
|
6
|
+
assistantName?: string;
|
|
7
|
+
/** Composer placeholder. */
|
|
8
|
+
placeholder?: string;
|
|
9
|
+
/** Shown once, above an empty conversation. */
|
|
10
|
+
hint?: string;
|
|
11
|
+
/** Start open. The panel owns its state after that. */
|
|
12
|
+
defaultOpen?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Streamdown plugins - syntax highlighting, KaTeX, Mermaid, CJK.
|
|
15
|
+
*
|
|
16
|
+
* Off unless you pass them, because each is a package of its own and several
|
|
17
|
+
* are large (Mermaid alone dwarfs this SDK). Prose, GFM tables, task lists
|
|
18
|
+
* and plain code fences all render without any of them.
|
|
19
|
+
*
|
|
20
|
+
* ```tsx
|
|
21
|
+
* import { code } from "@streamdown/code";
|
|
22
|
+
* import { mermaid } from "@streamdown/mermaid";
|
|
23
|
+
*
|
|
24
|
+
* <ConnectAskAi markdownPlugins={{ code, mermaid }} />
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* Hoist the object to module scope: Streamdown compares this prop by
|
|
28
|
+
* reference, so an inline literal defeats its block memoisation.
|
|
29
|
+
*/
|
|
30
|
+
markdownPlugins?: StreamdownProps["plugins"];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Ask AI - a chat panel grounded in the signed-in workspace's Context Index,
|
|
34
|
+
* streamed through your own bridge.
|
|
35
|
+
*
|
|
36
|
+
* Answers are not only prose. Connect's model can call a small set of
|
|
37
|
+
* presentation tools - `showTable`, `showChart`, `showMetrics`, `showTimeline`,
|
|
38
|
+
* `showDetails`, `showSteps`, `showSources` - and this panel renders each one
|
|
39
|
+
* natively: a sortable table with a CSV download, a chart, a row of metric
|
|
40
|
+
* tiles. Values arrive raw with a format tag, so they are formatted here rather
|
|
41
|
+
* than by the model, which is what lets a column sort numerically.
|
|
42
|
+
*
|
|
43
|
+
* Nothing here holds a credential: it POSTs to `${basePath}/ai/chat` on your
|
|
44
|
+
* own origin, and `createConnectHandler` is what reaches Connect. The workspace
|
|
45
|
+
* is resolved from the caller's session there, never sent from the browser - so
|
|
46
|
+
* a user can only ever ask about the workspace they are signed into.
|
|
47
|
+
*
|
|
48
|
+
* ```tsx
|
|
49
|
+
* <AppShell name="Dispatch" nav={nav}>
|
|
50
|
+
* {children}
|
|
51
|
+
* </AppShell>
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* `AppShell` mounts it by default. Mount it directly only when you are not
|
|
55
|
+
* using the shell: it renders a fixed corner trigger plus a panel that is an
|
|
56
|
+
* *in-flow flex sibling* at `lg` and up, so opening it pushes your content
|
|
57
|
+
* aside rather than covering it. That requires it to be the last child of a
|
|
58
|
+
* `flex` row owning the viewport height - which is exactly what `AppShell` is.
|
|
59
|
+
* Below `lg` it is a full-screen sheet with a backdrop instead; there is no
|
|
60
|
+
* room to push anything on a phone.
|
|
61
|
+
*
|
|
62
|
+
* **Host setup:** markdown rendering needs `streamdown` installed, one Tailwind
|
|
63
|
+
* `@source "…/node_modules/streamdown/dist/*.js"` line so its utility classes
|
|
64
|
+
* are generated, and `import "streamdown/styles.css"` for the streaming
|
|
65
|
+
* animation. The colour tokens Streamdown expects are already mapped in this
|
|
66
|
+
* SDK's `theme.css`.
|
|
67
|
+
*
|
|
68
|
+
* Answers require the workspace's Context Index to be turned on (a workspace
|
|
69
|
+
* admin toggle in Connect) and your API key to carry the `ai:chat` scope.
|
|
70
|
+
* Neither is something the browser can discover, so both surface as a message
|
|
71
|
+
* in the panel rather than as a hidden button.
|
|
72
|
+
*/
|
|
73
|
+
export declare function ConnectAskAi({ title, assistantName, placeholder, hint, defaultOpen, markdownPlugins, }: ConnectAskAiProps): import("react").JSX.Element | null;
|
|
@@ -28,7 +28,7 @@ export interface ConnectNavItem {
|
|
|
28
28
|
items?: ConnectNavItem[];
|
|
29
29
|
/**
|
|
30
30
|
* Leave this route *outside* `ConnectAccessGate` - see
|
|
31
|
-
* `
|
|
31
|
+
* `AppShellProps.gate`. Declared here, next to the route it applies
|
|
32
32
|
* to, rather than as a second list somewhere else that has to be kept in
|
|
33
33
|
* step with this one.
|
|
34
34
|
*/
|
|
@@ -40,7 +40,7 @@ export interface ConnectNavSection {
|
|
|
40
40
|
items: ConnectNavItem[];
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
|
-
* What `
|
|
43
|
+
* What `AppShell`/`ConnectSideNav` accept for `nav`: a flat list of
|
|
44
44
|
* items, a list of sections, or a mix. Consecutive bare items fold into one
|
|
45
45
|
* untitled section, so the common case is just an array of items.
|
|
46
46
|
*/
|
|
@@ -106,5 +106,5 @@ export declare function resolveActivePath(pathname: string, nav: ConnectNav): st
|
|
|
106
106
|
* lights up on every app's home route. External items are never active.
|
|
107
107
|
*/
|
|
108
108
|
export declare function isNavItemActive(item: ConnectNavItem, activeHref: string | null): boolean;
|
|
109
|
-
/** Hrefs declared `gateExempt` - the routes `
|
|
109
|
+
/** Hrefs declared `gateExempt` - the routes `AppShell` renders outside its `ConnectAccessGate`. */
|
|
110
110
|
export declare function gateExemptPaths(nav: ConnectNav): string[];
|
|
@@ -23,7 +23,7 @@ export interface ConnectSideNavProps {
|
|
|
23
23
|
* The sidebar's nav column on its own - sections, items, one level of
|
|
24
24
|
* children, and the active highlight.
|
|
25
25
|
*
|
|
26
|
-
* `
|
|
26
|
+
* `AppShell` renders this inside its rail and its mobile drawer, and it
|
|
27
27
|
* is exported for a host that wants the shell's navigation without its
|
|
28
28
|
* chrome: the same items, the same active resolution, dropped into a layout
|
|
29
29
|
* you already have.
|
|
@@ -70,7 +70,8 @@ export declare function ConnectInvoicesButton({ className }: Readonly<{
|
|
|
70
70
|
* `ConnectHomeButton`.
|
|
71
71
|
*
|
|
72
72
|
* It was reachable only from inside `ConnectUserMenu`, which meant an app that
|
|
73
|
-
* builds its own account menu had to hardcode `${appUrl}/profile` to offer it
|
|
73
|
+
* builds its own account menu had to hardcode `${appUrl}/profile` to offer it -
|
|
74
|
+
* and got no `return_to`, so the user had no way back.
|
|
74
75
|
*/
|
|
75
76
|
export declare function ConnectProfileButton({ className }: Readonly<{
|
|
76
77
|
className?: string;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { WorkspaceType } from "../../types.js";
|
|
3
|
+
export interface ConnectWorkspaceTypeGateProps {
|
|
4
|
+
/**
|
|
5
|
+
* The workspace type this subtree is for. Pass an array to accept either -
|
|
6
|
+
* which is the same as not using the gate at all, so it exists only to keep
|
|
7
|
+
* a `type={someVariable}` call site from having to special-case "both".
|
|
8
|
+
*/
|
|
9
|
+
type: WorkspaceType | WorkspaceType[];
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
/**
|
|
12
|
+
* Rendered for a workspace of the other type. Defaults to `null` - hiding
|
|
13
|
+
* the subtree, which is what this gate is normally for.
|
|
14
|
+
*/
|
|
15
|
+
fallback?: ReactNode;
|
|
16
|
+
/** Shown until the session read settles. Defaults to nothing. */
|
|
17
|
+
loading?: ReactNode;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Renders `children` only for a workspace of the given type.
|
|
21
|
+
*
|
|
22
|
+
* ```tsx
|
|
23
|
+
* <ConnectWorkspaceTypeGate type="business">
|
|
24
|
+
* <TeamMembersTable />
|
|
25
|
+
* </ConnectWorkspaceTypeGate>
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* The point is dead controls, not access: an `"individual"` workspace has
|
|
29
|
+
* exactly one seat and can never have another, so a members table, an invite
|
|
30
|
+
* button and a seat meter are all things that can only ever fail there.
|
|
31
|
+
* Hiding them is a better answer than letting someone press one and read an
|
|
32
|
+
* error about an account shape they did not choose.
|
|
33
|
+
*
|
|
34
|
+
* This is emphatically **not** the audience gate. It does not know what your
|
|
35
|
+
* application is sold to and will happily render a business-only subtree to a
|
|
36
|
+
* business workspace that has no business being in your application at all -
|
|
37
|
+
* that is `<ConnectAccessGate>`'s job, off the `audience_mismatch` readiness
|
|
38
|
+
* blocker. Use this one *inside* an application both audiences can reach, to
|
|
39
|
+
* vary what each sees.
|
|
40
|
+
*
|
|
41
|
+
* Like every gate in this package it is a **UX gate, not a security
|
|
42
|
+
* boundary**: the browser can ignore it. Anything that must not happen for an
|
|
43
|
+
* individual workspace has to be refused on your server too.
|
|
44
|
+
*
|
|
45
|
+
* Renders `loading` - not `fallback` - while the session is unknown, so a
|
|
46
|
+
* business account never flickers through the individual-account layout.
|
|
47
|
+
*/
|
|
48
|
+
export declare function ConnectWorkspaceTypeGate({ type, children, fallback, loading, }: ConnectWorkspaceTypeGateProps): import("react").JSX.Element;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* whatever theme the host application platform already configured, and one theme
|
|
11
11
|
* change lands identically in all of them.
|
|
12
12
|
*/
|
|
13
|
-
export { ConnectProvider, useConnect, useConnectUser, useConnectEntitlements, useConnectPlans, useConnectApps, useConnectAddonUsage, useConnectSessions, useConnectIntegrations, useConnectResource, CONNECT_APP_URL, } from "./provider.js";
|
|
13
|
+
export { ConnectProvider, useConnect, useConnectUser, useConnectWorkspaceType, useConnectAudience, useConnectEntitlements, useConnectPlans, useConnectApps, useConnectAddonUsage, useConnectSessions, useConnectIntegrations, useConnectResource, CONNECT_APP_URL, } from "./provider.js";
|
|
14
14
|
export type { ConnectProviderProps, ConnectContextValue, ConnectSession } from "./provider.js";
|
|
15
15
|
export { blockingBlockers, normalizePlanFeatures } from "../helpers.js";
|
|
16
16
|
export type { WirePlanFeature } from "../helpers.js";
|
|
@@ -25,8 +25,8 @@ export { ConnectUserMenu, ConnectManageSubscriptionButton, ConnectInvoicesButton
|
|
|
25
25
|
export { ConnectAccountSessions } from "./components/account-sessions.js";
|
|
26
26
|
export type { ConnectAccountSessionsProps } from "./components/account-sessions.js";
|
|
27
27
|
export type { ConnectUserMenuProps } from "./components/user-menu.js";
|
|
28
|
-
export {
|
|
29
|
-
export type {
|
|
28
|
+
export { AppShell } from "./components/app-shell.js";
|
|
29
|
+
export type { AppShellProps } from "./components/app-shell.js";
|
|
30
30
|
export { ConnectSideNav } from "./components/side-nav.js";
|
|
31
31
|
export type { ConnectSideNavProps } from "./components/side-nav.js";
|
|
32
32
|
export { useActivePath } from "./use-active-path.js";
|
|
@@ -34,6 +34,11 @@ export { normalizeNav, flattenNav, normalizePath, resolveActivePath, isNavItemAc
|
|
|
34
34
|
export type { ConnectNav, ConnectNavItem, ConnectNavSection, ConnectNavLinkProps, ConnectLinkRenderer, } from "./components/nav.js";
|
|
35
35
|
export { ConnectAppsGrid } from "./components/apps-grid.js";
|
|
36
36
|
export type { ConnectAppsGridProps } from "./components/apps-grid.js";
|
|
37
|
+
export { ConnectAskAi } from "./components/ask-ai.js";
|
|
38
|
+
export type { ConnectAskAiProps } from "./components/ask-ai.js";
|
|
39
|
+
export { AiTable, AiChart, AiMetrics, AiTimeline, AiDetails, AiSteps, AiSources, } from "./components/ai-widgets.js";
|
|
40
|
+
export { formatAiValue, isNumericAiFormat, aiStatusTone, AI_EMPTY_VALUE, AI_TONE_CHIP, AI_TONE_DOT, } from "./components/ai-format.js";
|
|
41
|
+
export type { AiStatusTone } from "./components/ai-format.js";
|
|
37
42
|
export { ConnectIntegrationsModal } from "./components/integrations-modal.js";
|
|
38
43
|
export type { ConnectIntegrationsModalProps } from "./components/integrations-modal.js";
|
|
39
44
|
export { ConnectPlans } from "./components/plans.js";
|
|
@@ -48,6 +53,8 @@ export { ConnectAccessGate, useConnectAccessGate } from "./components/access-gat
|
|
|
48
53
|
export type { ConnectAccessGateProps, ConnectAccessGateState, ConnectAccessGateResult, } from "./components/access-gate.js";
|
|
49
54
|
export { ConnectFeatureGate, useConnectFeature } from "./components/feature-gate.js";
|
|
50
55
|
export type { ConnectFeatureGateProps } from "./components/feature-gate.js";
|
|
56
|
+
export { ConnectWorkspaceTypeGate } from "./components/workspace-type-gate.js";
|
|
57
|
+
export type { ConnectWorkspaceTypeGateProps } from "./components/workspace-type-gate.js";
|
|
51
58
|
export { ConnectUsageMeter } from "./components/usage-meter.js";
|
|
52
59
|
export type { ConnectUsageMeterProps } from "./components/usage-meter.js";
|
|
53
60
|
export { ConnectPlansPage } from "./components/plans-page.js";
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
import type { AddonUsageResponse, ConnectUser, IntegrationsListResponse, WorkspaceApp, PlansResponse, ReadinessResponse, ApplicationRole, SessionsListResponse, SessionSubscription, SubscriptionResponse } from "../types.js";
|
|
2
|
+
import type { AddonUsageResponse, ConnectUser, IntegrationsListResponse, WorkspaceApp, PlansResponse, ReadinessResponse, ApplicationRole, SessionsListResponse, SessionSubscription, SessionAudience, SubscriptionResponse, WorkspaceType } from "../types.js";
|
|
3
3
|
export declare const CONNECT_APP_URL = "https://connect.cosmoops.com";
|
|
4
4
|
export interface ConnectSession {
|
|
5
5
|
user: ConnectUser | null;
|
|
6
6
|
subscription: SessionSubscription | null;
|
|
7
7
|
applicationRole: ApplicationRole | null;
|
|
8
|
+
/**
|
|
9
|
+
* `null` when signed out, for the same reason the three above are: there is
|
|
10
|
+
* no workspace to compare an audience against yet. Never `null` on a
|
|
11
|
+
* signed-in session.
|
|
12
|
+
*/
|
|
13
|
+
audience: SessionAudience | null;
|
|
8
14
|
}
|
|
9
15
|
export interface ConnectContextValue {
|
|
10
16
|
/**
|
|
@@ -58,6 +64,22 @@ export interface ConnectContextValue {
|
|
|
58
64
|
* page.
|
|
59
65
|
*/
|
|
60
66
|
invalidateResource(path: string): void;
|
|
67
|
+
/**
|
|
68
|
+
* POST through the bridge and hand back the raw `Response`, unread and
|
|
69
|
+
* uncached - the seam `<ConnectAskAi>` streams server-sent events over.
|
|
70
|
+
*
|
|
71
|
+
* Separate from `mutateResource` rather than a flag on it, because the two
|
|
72
|
+
* differ in every respect that matters: this one must not `await
|
|
73
|
+
* response.json()` (that buffers the whole stream and defeats the point),
|
|
74
|
+
* must not touch the resource cache (a conversation is not a resource), and
|
|
75
|
+
* takes an `AbortSignal` so a "Stop" button can actually stop generation.
|
|
76
|
+
*
|
|
77
|
+
* Throws on a non-2xx, having read the error envelope - which is safe,
|
|
78
|
+
* because a failure never has a stream behind it.
|
|
79
|
+
*/
|
|
80
|
+
streamResource(path: string, body: unknown, options?: {
|
|
81
|
+
signal?: AbortSignal;
|
|
82
|
+
}): Promise<Response>;
|
|
61
83
|
}
|
|
62
84
|
export interface ConnectProviderProps {
|
|
63
85
|
/**
|
|
@@ -128,6 +150,27 @@ export declare function ConnectProvider({ clientId, basePath, redirectUri, appUr
|
|
|
128
150
|
export declare function useConnect(): ConnectContextValue;
|
|
129
151
|
/** The signed-in user, or `null`. Sugar over `useConnect().session`. */
|
|
130
152
|
export declare function useConnectUser(): ConnectUser | null;
|
|
153
|
+
/**
|
|
154
|
+
* Whether the signed-in workspace is a one-seat personal account or a team -
|
|
155
|
+
* `null` while the session is loading or signed out.
|
|
156
|
+
*
|
|
157
|
+
* Branch on this to stop rendering team UI at a solo account: a members
|
|
158
|
+
* table, an invite button and a seat meter are all dead controls on an
|
|
159
|
+
* `"individual"` workspace, which has exactly one seat and cannot gain
|
|
160
|
+
* another. `<ConnectWorkspaceTypeGate>` is the declarative form.
|
|
161
|
+
*/
|
|
162
|
+
export declare function useConnectWorkspaceType(): WorkspaceType | null;
|
|
163
|
+
/**
|
|
164
|
+
* Whether this account is the kind your application is sold to - `null` while
|
|
165
|
+
* the session is loading or signed out.
|
|
166
|
+
*
|
|
167
|
+
* `audience.matches === false` is a hard block, not a hint: readiness reports
|
|
168
|
+
* `audience_mismatch` and subscribing is refused. `<ConnectAccessGate>`
|
|
169
|
+
* already blocks the shell on it, so reach for this only when you need the
|
|
170
|
+
* verdict somewhere the gate does not run - a marketing page, say, or a
|
|
171
|
+
* sign-up CTA you would rather not show to an account that cannot complete it.
|
|
172
|
+
*/
|
|
173
|
+
export declare function useConnectAudience(): SessionAudience | null;
|
|
131
174
|
/** Readiness gates and the live subscription, fetched on first use. */
|
|
132
175
|
export declare function useConnectEntitlements(): {
|
|
133
176
|
data: {
|
|
@@ -12,7 +12,12 @@ export interface RequestSpec {
|
|
|
12
12
|
headers?: Record<string, string>;
|
|
13
13
|
/** Bearer API key (default), or none - the `/sso/*` endpoints authenticate inside the form body instead. */
|
|
14
14
|
auth?: "bearer" | "none";
|
|
15
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Skip JSON parsing and hand back the raw `Response` - for streaming
|
|
17
|
+
* endpoints.
|
|
18
|
+
*
|
|
19
|
+
* Also stops `timeoutMs` from applying to the *body*: see `request`.
|
|
20
|
+
*/
|
|
16
21
|
raw?: boolean;
|
|
17
22
|
signal?: AbortSignal;
|
|
18
23
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type TransportOptions } from "../http.js";
|
|
2
|
-
import type { ReadinessResponse, SubscriptionResponse } from "../../types.js";
|
|
2
|
+
import type { ApplicationSubscriber, ReadinessResponse, SubscribersResponse, SubscriptionResponse } from "../../types.js";
|
|
3
3
|
/**
|
|
4
4
|
* The two checks worth making on every request: may this workspace be here at
|
|
5
5
|
* all, and what is it paying for.
|
|
@@ -28,4 +28,36 @@ export declare class EntitlementsResource {
|
|
|
28
28
|
* should not need a try/catch around it.
|
|
29
29
|
*/
|
|
30
30
|
hasFeature(workspaceId: string, feature: string): Promise<boolean>;
|
|
31
|
+
/**
|
|
32
|
+
* One page of the workspaces subscribed to your application, newest first.
|
|
33
|
+
*
|
|
34
|
+
* The inverse of `getSubscription`, which asks about one workspace you
|
|
35
|
+
* already know. This asks who your subscribers *are* - what a partner
|
|
36
|
+
* dashboard, an MRR total or a churn report is built on.
|
|
37
|
+
*
|
|
38
|
+
* Rate-limited far more tightly than the per-workspace read (10/min per
|
|
39
|
+
* key, against 60): it walks your whole subscriber base and is a reporting
|
|
40
|
+
* call, not a per-request gate. Use `getSubscription` in a hot path.
|
|
41
|
+
*
|
|
42
|
+
* Your application is resolved from the API key, so there is no parameter
|
|
43
|
+
* for it and no way to ask about anyone else's.
|
|
44
|
+
*/
|
|
45
|
+
listSubscribers(options?: {
|
|
46
|
+
status?: ApplicationSubscriber["status"];
|
|
47
|
+
/** Cursor from a previous page's `nextBefore`. */
|
|
48
|
+
before?: string;
|
|
49
|
+
/** 1..100, default 25. */
|
|
50
|
+
limit?: number;
|
|
51
|
+
}): Promise<SubscribersResponse>;
|
|
52
|
+
/**
|
|
53
|
+
* Every subscriber, paged through for you.
|
|
54
|
+
*
|
|
55
|
+
* An async generator rather than an array: a large subscriber base is
|
|
56
|
+
* exactly where an accidental full materialisation hurts, and `for await`
|
|
57
|
+
* reads the same as a loop over one page.
|
|
58
|
+
*/
|
|
59
|
+
iterateSubscribers(options?: {
|
|
60
|
+
status?: ApplicationSubscriber["status"];
|
|
61
|
+
pageSize?: number;
|
|
62
|
+
}): AsyncGenerator<ApplicationSubscriber>;
|
|
31
63
|
}
|
package/dist/types/types.d.ts
CHANGED
|
@@ -27,6 +27,54 @@ export interface ConnectUser {
|
|
|
27
27
|
photoUrl: string | null;
|
|
28
28
|
workspaceId: string;
|
|
29
29
|
workspaceName: string;
|
|
30
|
+
/**
|
|
31
|
+
* Whether this is a one-seat personal account or a team.
|
|
32
|
+
*
|
|
33
|
+
* The one fact about the signed-in workspace you cannot look up yourself -
|
|
34
|
+
* the application API has no workspace read - so branch on it rather than
|
|
35
|
+
* inferring an account's shape from whether it happens to have a second
|
|
36
|
+
* member. An `"individual"` workspace has exactly one seat and can never
|
|
37
|
+
* have another, so a members table, an invite button or a seat-count meter
|
|
38
|
+
* is dead UI for it.
|
|
39
|
+
*
|
|
40
|
+
* Re-read it from `identity.getUser()`; do not cache it past the sign-in.
|
|
41
|
+
* An `"individual"` workspace can convert to `"business"` under a live
|
|
42
|
+
* session (the reverse never happens).
|
|
43
|
+
*/
|
|
44
|
+
workspaceType: WorkspaceType;
|
|
45
|
+
}
|
|
46
|
+
export type WorkspaceType = "individual" | "business";
|
|
47
|
+
export type ApplicationAudience = "b2b" | "b2c" | "both";
|
|
48
|
+
/**
|
|
49
|
+
* Whether the signed-in workspace is inside your application's audience -
|
|
50
|
+
* returned alongside `user` by both `exchangeCode()` and `getUser()`.
|
|
51
|
+
*
|
|
52
|
+
* `user.workspaceType` answers what the account *is*; this answers what your
|
|
53
|
+
* application is *sold to*, and whether the two agree. Both ship together so
|
|
54
|
+
* the check stays `!audience.matches` rather than a hardcoded audience
|
|
55
|
+
* constant in your own code that goes stale the moment the catalogue entry
|
|
56
|
+
* changes.
|
|
57
|
+
*
|
|
58
|
+
* `matches: false` means readiness will report an `audience_mismatch` blocker
|
|
59
|
+
* and any attempt to subscribe will be refused, so treat it as a hard block:
|
|
60
|
+
* `<ConnectAccessGate>` already does. What it is NOT is a reason to sign the
|
|
61
|
+
* user out - they have a valid Connect account, just the wrong kind of one
|
|
62
|
+
* for this application.
|
|
63
|
+
*/
|
|
64
|
+
export interface SessionAudience {
|
|
65
|
+
/** Your catalogue entry's setting - `"both"` if you have never narrowed it. */
|
|
66
|
+
application: ApplicationAudience;
|
|
67
|
+
/** The single `workspaceType` `application` demands - `null` for `"both"`, which accepts either. */
|
|
68
|
+
requiredWorkspaceType: WorkspaceType | null;
|
|
69
|
+
/** `false` means this user is outside your audience. */
|
|
70
|
+
matches: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Whether Connect refuses the sign-in itself on a mismatch. When `true`,
|
|
73
|
+
* a `matches: false` can only reach you on a session minted before the
|
|
74
|
+
* setting was turned on. When `false`, gating is entirely yours to do -
|
|
75
|
+
* which is what `<ConnectAccessGate>` is for.
|
|
76
|
+
*/
|
|
77
|
+
enforcedAtSignIn: boolean;
|
|
30
78
|
}
|
|
31
79
|
export type SubscriptionStatus = "created" | "authenticated" | "active" | "paused" | "cancelled";
|
|
32
80
|
/** The compact subscription block returned alongside identity - `null` when the workspace has never subscribed to this application. */
|
|
@@ -58,11 +106,15 @@ export interface TokenResponse {
|
|
|
58
106
|
user: ConnectUser;
|
|
59
107
|
subscription: SessionSubscription | null;
|
|
60
108
|
applicationRole: ApplicationRole;
|
|
109
|
+
/** Whether this account is the kind your application serves - see `SessionAudience`. */
|
|
110
|
+
audience: SessionAudience;
|
|
61
111
|
}
|
|
62
112
|
export interface UserResponse {
|
|
63
113
|
user: ConnectUser;
|
|
64
114
|
subscription: SessionSubscription | null;
|
|
65
115
|
applicationRole: ApplicationRole;
|
|
116
|
+
/** Live, like `subscription` and `applicationRole` - a workspace that converted to a business since sign-in matches here without another sign-in. */
|
|
117
|
+
audience: SessionAudience;
|
|
66
118
|
/** ISO 8601. Refresh proactively off this rather than waiting for a 401. */
|
|
67
119
|
sessionExpiresAt: string;
|
|
68
120
|
}
|
|
@@ -119,6 +171,8 @@ export interface ReadinessBlocker {
|
|
|
119
171
|
resolveUrl: string | null;
|
|
120
172
|
/** When this blocker resolves itself automatically - currently only set on `payment_retry_pending`, the deadline dunning suspends the subscription. `undefined`/`null` for every other code. */
|
|
121
173
|
graceEndsAt?: string | null;
|
|
174
|
+
/** The account type this application requires - set only on `audience_mismatch`, `undefined`/`null` otherwise. Branch on this rather than reading it out of `message`. */
|
|
175
|
+
requiredWorkspaceType?: "individual" | "business" | null;
|
|
122
176
|
}
|
|
123
177
|
export interface ReadinessResponse {
|
|
124
178
|
workspaceId: string;
|
|
@@ -261,6 +315,33 @@ export interface SubscriptionResponse {
|
|
|
261
315
|
/** `null` exactly when `subscription` is. */
|
|
262
316
|
plan: SubscribedPlan | null;
|
|
263
317
|
}
|
|
318
|
+
/** One row of `subscribers.list()` - a workspace subscribed to your application. */
|
|
319
|
+
export interface ApplicationSubscriber {
|
|
320
|
+
subscriptionId: string;
|
|
321
|
+
workspaceId: string;
|
|
322
|
+
/** `null` when the workspace has been purged and only its tombstone remains. */
|
|
323
|
+
workspaceName: string | null;
|
|
324
|
+
/** `"active"`, `"suspended"`, `"pending_deletion"`, `"deleted"` - or `null` alongside a null `workspaceName`. */
|
|
325
|
+
workspaceStatus: string | null;
|
|
326
|
+
planId: string;
|
|
327
|
+
/** `null` if the plan was hard-deleted rather than archived. */
|
|
328
|
+
planName: string | null;
|
|
329
|
+
status: "created" | "authenticated" | "active" | "paused" | "cancelled";
|
|
330
|
+
seatCount: number;
|
|
331
|
+
/** ISO 8601. */
|
|
332
|
+
subscribedAt: string;
|
|
333
|
+
/** ISO 8601, or `null` for a cancelled/paused subscription. */
|
|
334
|
+
nextBillingDate: string | null;
|
|
335
|
+
}
|
|
336
|
+
export interface SubscribersResponse {
|
|
337
|
+
items: ApplicationSubscriber[];
|
|
338
|
+
/**
|
|
339
|
+
* Pass back as `before` for the next page. `null` on the last page - which
|
|
340
|
+
* is the only reliable end-of-list signal, since a full page can still be
|
|
341
|
+
* the last one.
|
|
342
|
+
*/
|
|
343
|
+
nextBefore: string | null;
|
|
344
|
+
}
|
|
264
345
|
/** One consumable add-on's remaining balance on a workspace's subscription. */
|
|
265
346
|
export interface AddonUsage {
|
|
266
347
|
addonId: string;
|
|
@@ -613,3 +694,96 @@ export interface ConnectWebhookEnvelope<T extends ConnectWebhookEventType = Conn
|
|
|
613
694
|
apiVersion: string;
|
|
614
695
|
data: ConnectWebhookEventMap[T];
|
|
615
696
|
}
|
|
697
|
+
/** How a scalar should be presented. See `AiValue` for what each one expects. */
|
|
698
|
+
export type AiValueFormat = "text" | "number"
|
|
699
|
+
/** A number in rupees - `12500`, not paise and not a formatted string. */
|
|
700
|
+
| "currency"
|
|
701
|
+
/** A fraction - `0.42` renders as 42%. */
|
|
702
|
+
| "percent"
|
|
703
|
+
/** An ISO 8601 date string. */
|
|
704
|
+
| "date"
|
|
705
|
+
/** An ISO 8601 datetime string. */
|
|
706
|
+
| "datetime"
|
|
707
|
+
/** A number of seconds. */
|
|
708
|
+
| "duration"
|
|
709
|
+
/** A number of bytes. */
|
|
710
|
+
| "bytes"
|
|
711
|
+
/** A short state word, drawn as a chip. */
|
|
712
|
+
| "status";
|
|
713
|
+
export type AiTone = "neutral" | "positive" | "warning" | "danger";
|
|
714
|
+
/** One cell. `null` is "no value" and renders as an em dash. */
|
|
715
|
+
export type AiValue = string | number | null;
|
|
716
|
+
export interface AiTableInput {
|
|
717
|
+
title?: string;
|
|
718
|
+
columns: {
|
|
719
|
+
label: string;
|
|
720
|
+
format: AiValueFormat;
|
|
721
|
+
}[];
|
|
722
|
+
/** Positional: one array per row, values in the same order as `columns`. */
|
|
723
|
+
rows: AiValue[][];
|
|
724
|
+
caption?: string;
|
|
725
|
+
}
|
|
726
|
+
export interface AiChartInput {
|
|
727
|
+
title?: string;
|
|
728
|
+
kind: "bar" | "line" | "area" | "pie";
|
|
729
|
+
xLabel?: string;
|
|
730
|
+
yLabel?: string;
|
|
731
|
+
valueFormat: AiValueFormat;
|
|
732
|
+
/** Every series shares the same x values, in the same order. */
|
|
733
|
+
series: {
|
|
734
|
+
name: string;
|
|
735
|
+
points: {
|
|
736
|
+
x: string;
|
|
737
|
+
y: number | null;
|
|
738
|
+
}[];
|
|
739
|
+
}[];
|
|
740
|
+
caption?: string;
|
|
741
|
+
}
|
|
742
|
+
export interface AiMetricsInput {
|
|
743
|
+
title?: string;
|
|
744
|
+
metrics: {
|
|
745
|
+
label: string;
|
|
746
|
+
value: AiValue;
|
|
747
|
+
format: AiValueFormat;
|
|
748
|
+
/** Signed change against the previous period, as a fraction. */
|
|
749
|
+
delta?: number | null;
|
|
750
|
+
/** Whether a rise is good - churn is `"down-is-good"`. Colouring a delta by sign alone gets this wrong half the time. */
|
|
751
|
+
deltaMeaning: "up-is-good" | "down-is-good" | "neutral";
|
|
752
|
+
hint?: string;
|
|
753
|
+
}[];
|
|
754
|
+
}
|
|
755
|
+
export interface AiTimelineInput {
|
|
756
|
+
title?: string;
|
|
757
|
+
/** Oldest first. */
|
|
758
|
+
events: {
|
|
759
|
+
at: string;
|
|
760
|
+
title: string;
|
|
761
|
+
detail?: string;
|
|
762
|
+
tone: AiTone;
|
|
763
|
+
}[];
|
|
764
|
+
}
|
|
765
|
+
export interface AiDetailsInput {
|
|
766
|
+
title?: string;
|
|
767
|
+
fields: {
|
|
768
|
+
label: string;
|
|
769
|
+
value: AiValue;
|
|
770
|
+
format: AiValueFormat;
|
|
771
|
+
tone: AiTone;
|
|
772
|
+
}[];
|
|
773
|
+
}
|
|
774
|
+
export interface AiStepsInput {
|
|
775
|
+
title?: string;
|
|
776
|
+
steps: {
|
|
777
|
+
title: string;
|
|
778
|
+
detail?: string;
|
|
779
|
+
}[];
|
|
780
|
+
}
|
|
781
|
+
export interface AiSourcesInput {
|
|
782
|
+
sources: {
|
|
783
|
+
title: string;
|
|
784
|
+
uri?: string;
|
|
785
|
+
snippet?: string;
|
|
786
|
+
}[];
|
|
787
|
+
}
|
|
788
|
+
/** The tools this client knows how to draw. Anything else in the stream is ignored. */
|
|
789
|
+
export type AiToolName = "showTable" | "showChart" | "showMetrics" | "showTimeline" | "showDetails" | "showSteps" | "showSources";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmoops/connect-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Official server-side SDK for the CosmoOps Connect platform API - Login with Connect, entitlements, billing and metering.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
"build:storybook": "storybook build -o storybook-static"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
+
"@connect/ai": "workspace:*",
|
|
76
77
|
"@connect/data": "workspace:*",
|
|
77
78
|
"@heroui/react": "^3.2.2",
|
|
78
79
|
"@heroui/styles": "^3.2.2",
|
|
@@ -87,6 +88,7 @@
|
|
|
87
88
|
"react": "19.2.8",
|
|
88
89
|
"react-dom": "19.2.8",
|
|
89
90
|
"storybook": "^10.5.10",
|
|
91
|
+
"streamdown": "^2.5.0",
|
|
90
92
|
"tailwindcss": "^4.3.3",
|
|
91
93
|
"typedoc": "^0.28.20",
|
|
92
94
|
"typedoc-plugin-markdown": "^4.12.0",
|
|
@@ -98,7 +100,8 @@
|
|
|
98
100
|
"lucide-react": ">=0.400.0",
|
|
99
101
|
"motion": ">=12",
|
|
100
102
|
"react": ">=19",
|
|
101
|
-
"react-dom": ">=19"
|
|
103
|
+
"react-dom": ">=19",
|
|
104
|
+
"streamdown": ">=2.5.0"
|
|
102
105
|
},
|
|
103
106
|
"peerDependenciesMeta": {
|
|
104
107
|
"@heroui/react": {
|
|
@@ -115,6 +118,9 @@
|
|
|
115
118
|
},
|
|
116
119
|
"react-dom": {
|
|
117
120
|
"optional": true
|
|
121
|
+
},
|
|
122
|
+
"streamdown": {
|
|
123
|
+
"optional": true
|
|
118
124
|
}
|
|
119
125
|
}
|
|
120
126
|
}
|