@cosmoops/connect-sdk 0.30.0 → 0.32.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.
@@ -21,6 +21,7 @@ import { AnalyticsResource } from "./resources/analytics.js";
21
21
  import { OtpResource } from "./resources/otp.js";
22
22
  import { SessionsResource } from "./resources/sessions.js";
23
23
  import { PlatformResource } from "./resources/platform.js";
24
+ import { PushResource } from "./resources/push.js";
24
25
  export declare const DEFAULT_BASE_URL = "https://connect.cosmoops.com";
25
26
  export interface ConnectClientOptions {
26
27
  /**
@@ -112,6 +113,11 @@ export declare class ConnectClient {
112
113
  readonly workspaces: WorkspacesResource;
113
114
  /** The platform's own legal identity and DPDP grievance channel - see `PlatformResource`. */
114
115
  readonly platform: PlatformResource;
116
+ /**
117
+ * Web Push. Connect holds the VAPID keys, the devices and the member's own
118
+ * preferences; you supply a title, a body and a URL. See `PushResource`.
119
+ */
120
+ readonly push: PushResource;
115
121
  /**
116
122
  * Relays one `/data/*` or `/kv/*` request verbatim - `createConnectHandler`'s
117
123
  * store route and nothing else. See `storePassthrough`.
@@ -126,4 +132,4 @@ export declare class ConnectClient {
126
132
  constructor(options: ConnectClientOptions);
127
133
  }
128
134
  /** Kept in step with `package.json` by `sdk.test.ts`. */
129
- export declare const SDK_VERSION = "0.30.0";
135
+ export declare const SDK_VERSION = "0.32.0";
@@ -1,5 +1,5 @@
1
1
  import { type TransportOptions } from "../http.js";
2
- import type { AiConversationDetail, AiConversationList, ChatMessage, ConnectScreen } from "../../types.js";
2
+ import type { AiConversationDetail, AiConversationList, AssessmentAnswers, AssessmentDraft, AssessmentReview, ChatMessage, ConnectScreen } from "../../types.js";
3
3
  /**
4
4
  * RAG chat and structured extraction against a workspace's data.
5
5
  *
@@ -87,4 +87,31 @@ export declare class AiResource {
87
87
  schemaId: string;
88
88
  fileUrl: string;
89
89
  }): Promise<T>;
90
+ /**
91
+ * Draft every section of a platform-defined assessment from one sentence.
92
+ *
93
+ * The intended flow is draft first, then let the person correct it section
94
+ * by section: a blank questionnaire is the reason most people abandon one,
95
+ * and editing a strawman is a far smaller ask than authoring from nothing.
96
+ *
97
+ * Unattributed - assessments run against platform-wide definitions and the
98
+ * first consumers run before anyone has signed in, so there is no `asUser`
99
+ * and no workspace here.
100
+ */
101
+ draftAssessment(input: {
102
+ assessmentKey: string;
103
+ seed: string;
104
+ }): Promise<AssessmentDraft>;
105
+ /**
106
+ * Critique a completed assessment - the tensions between sections, the
107
+ * riskiest assumption, and a coarse score.
108
+ *
109
+ * Unknown section keys in `answers` are ignored rather than rejected, so a
110
+ * definition changing under a half-finished assessment costs the reader
111
+ * nothing.
112
+ */
113
+ reviewAssessment(input: {
114
+ assessmentKey: string;
115
+ answers: AssessmentAnswers;
116
+ }): Promise<AssessmentReview>;
90
117
  }
@@ -1,5 +1,5 @@
1
1
  import { type TransportOptions } from "../http.js";
2
- import type { CompanyEnrichmentResult } from "../../types.js";
2
+ import type { BrandExtractionResult, CompanyEnrichmentResult } from "../../types.js";
3
3
  /**
4
4
  * What Connect knows about the company at a domain.
5
5
  *
@@ -41,4 +41,49 @@ export declare class EnrichmentResource {
41
41
  }, options?: {
42
42
  signal?: AbortSignal;
43
43
  }): Promise<CompanyEnrichmentResult>;
44
+ /**
45
+ * What the company at a domain **looks like** - logo, palette, typefaces,
46
+ * and the radius, spacing and breakpoint scales its own stylesheets declare.
47
+ *
48
+ * The sibling of `company()` above: that one answers what a company does,
49
+ * this one answers what it looks like. Useful for white-labelling a portal
50
+ * to a customer's brand, pre-filling a design brief, or showing a real
51
+ * account card instead of a grey placeholder.
52
+ *
53
+ * Needs an API key scoped to **`enrichment:brand`** - separate from
54
+ * `enrichment:read` because it is far more expensive: it crawls
55
+ * stylesheets, may render the page in a headless browser, and caches image
56
+ * bytes.
57
+ *
58
+ * ```ts
59
+ * const result = await connect.enrichment.brand({ domain: "acme.com" });
60
+ * if (result.status === "extracted") {
61
+ * const primary = result.profile.brandKit.palette.find((c) => c.role === "primary");
62
+ * }
63
+ * ```
64
+ *
65
+ * Three things to know before wiring it in:
66
+ *
67
+ * - **Set your client timeout to at least 90 seconds.** A cold read runs
68
+ * the whole extraction before answering and can take up to a minute; many
69
+ * HTTP clients default to 30s and abandon a request that was about to
70
+ * succeed. Cached reads return in milliseconds, and cached is the normal
71
+ * case - profiles are held for 90 days, so you pay the cold path roughly
72
+ * once per domain.
73
+ * - **Never store `brandKit.logos[].url`.** It is a short-lived signed URL
74
+ * minted per read. Render it directly or re-read the profile; persist
75
+ * `sourceUrl` if you need something stable.
76
+ * - **The values were measured, not generated.** Check `palette[].source` -
77
+ * a `css-var` entry is the company naming its own colour, a `frequency`
78
+ * entry is a ranked count and much weaker.
79
+ */
80
+ brand(input: {
81
+ /** A domain or a URL in any form - `acme.com`, `www.acme.com`, `https://acme.com/pricing`. */
82
+ domain: string;
83
+ asUser?: string;
84
+ /** Re-extract even if Connect has a fresh profile cached. For an operator pressing "refresh" - not for automatic calls. */
85
+ refresh?: boolean;
86
+ }, options?: {
87
+ signal?: AbortSignal;
88
+ }): Promise<BrandExtractionResult>;
44
89
  }
@@ -0,0 +1,73 @@
1
+ import { type TransportOptions } from "../http.js";
2
+ import type { PushKeyResponse, RegisterPushDeviceInput, RegisterPushDeviceResponse, SendPushInput, SendPushResponse } from "../../types.js";
3
+ /**
4
+ * Web Push, with Connect holding everything that makes it work.
5
+ *
6
+ * Your application supplies a title, a body, a URL and the members to reach.
7
+ * Connect owns the VAPID keys it signs with, the devices each member has
8
+ * registered across every application on the platform, that member's own
9
+ * notification preferences, and the bookkeeping when a browser goes away.
10
+ *
11
+ * So you never hold a push credential, never store an endpoint, and cannot
12
+ * reach anybody who is not an active member of the workspace you name -
13
+ * membership is re-checked on every send rather than trusted from the ids you
14
+ * pass.
15
+ *
16
+ * ## Two halves, and only `send` belongs on your server
17
+ *
18
+ * Registering a device happens in the **browser**, because a subscription is
19
+ * produced by `pushManager.subscribe()` against the member's own browser and
20
+ * no server can make one for them. `registerDevice` and `revokeDevice` below
21
+ * therefore take a session token and are what the SDK bridge calls on the
22
+ * browser's behalf - see `createConnectHandler`. You will normally reach them
23
+ * through `useConnectPush()` in `@cosmoops/connect-sdk/client` rather than
24
+ * directly.
25
+ */
26
+ export declare class PushResource {
27
+ private readonly transport;
28
+ constructor(transport: TransportOptions);
29
+ /**
30
+ * Notify members of a workspace.
31
+ *
32
+ * **`delivered: 0` is a success, not a failure.** The member may have
33
+ * registered no device, silenced notifications from their apps, or denied
34
+ * the browser prompt - all ordinary. A caller that retries on zero will
35
+ * retry forever against somebody who has opted out.
36
+ *
37
+ * Treat every send as a nudge toward something already true in your own
38
+ * data, and never let a failure here fail the thing that triggered it: the
39
+ * reminder is worth less than the record it is about.
40
+ *
41
+ * `url` must be absolute and on an origin this application registered as an
42
+ * SSO redirect URI. Connect refuses anything else, because a URL it did not
43
+ * check is an open redirect delivered to a lock screen.
44
+ */
45
+ send(input: SendPushInput): Promise<SendPushResponse>;
46
+ /**
47
+ * The VAPID public key a browser needs before it can subscribe, and whether
48
+ * the platform can send at all.
49
+ *
50
+ * Safe to hand to any signed-in caller: it is the verification half of the
51
+ * pair, so holding it lets a browser *receive* what Connect signs and never
52
+ * send anything.
53
+ */
54
+ key(options: {
55
+ asUser: string;
56
+ }): Promise<PushKeyResponse>;
57
+ /** Register one browser, from a subscription that browser just produced. */
58
+ registerDevice(input: RegisterPushDeviceInput, options: {
59
+ asUser: string;
60
+ }): Promise<RegisterPushDeviceResponse>;
61
+ /**
62
+ * Stop notifications on one browser.
63
+ *
64
+ * The id is the one `registerDevice` returned. Connect re-checks that it
65
+ * belongs to the calling member, so a guessed id is a refusal rather than
66
+ * somebody else's device going quiet.
67
+ */
68
+ revokeDevice(id: string, options: {
69
+ asUser: string;
70
+ }): Promise<{
71
+ revoked: boolean;
72
+ }>;
73
+ }
@@ -586,6 +586,54 @@ export interface AiConversationDetail {
586
586
  conversation: AiConversation;
587
587
  turns: AiConversationTurn[];
588
588
  }
589
+ /**
590
+ * One question in an assessment, as the platform defines it. Echoed back on a
591
+ * draft so the questions can be rendered without a second round trip.
592
+ */
593
+ export interface AssessmentSection {
594
+ /** Stable identifier - the key answers are filed under. */
595
+ key: string;
596
+ /** The question as a person reads it. */
597
+ question: string;
598
+ /** What belongs here. Written to steer generation, but useful as helper text. */
599
+ hint?: string;
600
+ /** Fewest answers a draft offers. */
601
+ min?: number;
602
+ /** Most answers a draft offers, and the cap worth enforcing on edits. */
603
+ max?: number;
604
+ }
605
+ /** Answers, keyed by section key. */
606
+ export type AssessmentAnswers = Record<string, string[]>;
607
+ export interface AssessmentDraft {
608
+ assessmentKey: string;
609
+ title: string;
610
+ /** The definition's sections, in the order they should be asked. */
611
+ sections: AssessmentSection[];
612
+ /** Suggested answers - a strawman to edit, never a finished answer. */
613
+ answers: AssessmentAnswers;
614
+ }
615
+ export type AssessmentFindingSeverity = "critical" | "warning" | "note";
616
+ export interface AssessmentFinding {
617
+ severity: AssessmentFindingSeverity;
618
+ /** Section keys this finding concerns - usually two, since the useful findings are tensions between sections. */
619
+ sections: string[];
620
+ title: string;
621
+ detail: string;
622
+ /** The specific change to make. */
623
+ fix: string;
624
+ }
625
+ export interface AssessmentReview {
626
+ assessmentKey: string;
627
+ findings: AssessmentFinding[];
628
+ /** The one assumption everything rests on, and a cheap way to test it. Lead with this. */
629
+ riskiestAssumption: {
630
+ section: string;
631
+ assumption: string;
632
+ experiment: string;
633
+ };
634
+ /** 0-100, advisory and coarse - it shows movement between revisions. */
635
+ score: number;
636
+ }
589
637
  export interface WorkspaceApp {
590
638
  applicationId: string;
591
639
  /** Stable, human-readable key - branch on this, not `applicationId`, which is per-environment. */
@@ -1698,6 +1746,169 @@ export type CompanyEnrichmentResult = {
1698
1746
  status: "skipped";
1699
1747
  reason: EnrichmentSkipReason;
1700
1748
  };
1749
+ /**
1750
+ * Where a brand claim was read. Its own union rather than `EnrichmentSource`,
1751
+ * which has no notion of a stylesheet or a rendered page.
1752
+ */
1753
+ export type BrandCitationSource = "website" | "stylesheet" | "render";
1754
+ export interface BrandCitation {
1755
+ source: BrandCitationSource;
1756
+ url: string;
1757
+ /** ISO-8601. */
1758
+ retrievedAt: string;
1759
+ }
1760
+ /** Whether the company sells a thing, an engagement, or both. */
1761
+ export type BusinessModel = "product" | "service" | "hybrid";
1762
+ /**
1763
+ * Where an official name came from.
1764
+ *
1765
+ * `website-legal` is a legal disclosure the company published itself - a
1766
+ * footer copyright line, or the entity named on its Terms page - matched by
1767
+ * code against a corporate suffix and cited. `registry` is a statutory
1768
+ * register and outranks it. Neither is a model's recall.
1769
+ */
1770
+ export type OfficialNameSource = "registry" | "website-legal";
1771
+ export interface Offering {
1772
+ name: string;
1773
+ /** The company's own grouping when it has one. */
1774
+ category: string | null;
1775
+ description: string | null;
1776
+ url: string | null;
1777
+ }
1778
+ export type LogoKind = "primary" | "mark" | "wordmark" | "favicon" | "social";
1779
+ export interface BrandLogo {
1780
+ /**
1781
+ * A **short-lived signed URL**, minted fresh on every read.
1782
+ *
1783
+ * Do not store it and do not bake it into a cached page - it will expire
1784
+ * and become a broken image with a plausible-looking address. Render it
1785
+ * directly, or re-read the profile, which on a cache hit is one document
1786
+ * read. `null` when Connect holds no cached copy, in which case `sourceUrl`
1787
+ * is all there is.
1788
+ */
1789
+ url: string | null;
1790
+ /** Where it lives on the company's own site. Stable, and the citation. */
1791
+ sourceUrl: string;
1792
+ kind: LogoKind;
1793
+ /** Lowercase extension without the dot: `svg`, `png`, `webp`. */
1794
+ format: string;
1795
+ width: number | null;
1796
+ height: number | null;
1797
+ }
1798
+ /**
1799
+ * How a colour was obtained, which is how much it is worth.
1800
+ *
1801
+ * `css-var` is the company naming its own colour in its own tokens - the
1802
+ * strongest signal short of a brand manual. `computed` comes from a rendered
1803
+ * page. `frequency` is a ranked count of declarations and is the weakest.
1804
+ */
1805
+ export type PaletteSource = "css-var" | "computed" | "frequency";
1806
+ export type PaletteRole = "primary" | "secondary" | "accent" | "background" | "surface" | "text" | "muted";
1807
+ export interface PaletteEntry {
1808
+ /** Lowercase `#rrggbb`. */
1809
+ hex: string;
1810
+ /** Assigned by a model over measured values - never invented by one. */
1811
+ role: PaletteRole | null;
1812
+ source: PaletteSource;
1813
+ /** The CSS custom property it came from, when `source` is `css-var`. */
1814
+ variableName: string | null;
1815
+ occurrences: number;
1816
+ }
1817
+ export type TypefaceRole = "display" | "heading" | "body" | "mono";
1818
+ export interface TypefaceEntry {
1819
+ /** The first named family in the stack - the one the company chose, not the fallbacks. */
1820
+ family: string;
1821
+ role: TypefaceRole | null;
1822
+ weights: number[];
1823
+ webfontUrl: string | null;
1824
+ occurrences: number;
1825
+ }
1826
+ /** One step of a measured scale. */
1827
+ export interface ScaleStep {
1828
+ value: number;
1829
+ /** `px`, `rem`, `em`, `%`. */
1830
+ unit: string;
1831
+ occurrences: number;
1832
+ }
1833
+ /**
1834
+ * A CSS framework detected by signature.
1835
+ *
1836
+ * Worth checking first, because it tells you how to read everything else: a
1837
+ * Tailwind site's spacing scale is Tailwind's defaults rather than a
1838
+ * considered choice, and a palette full of `--bs-*` is Bootstrap's.
1839
+ */
1840
+ export type CssFramework = "tailwind" | "bootstrap" | "material" | "chakra" | "bulma";
1841
+ export interface BrandKit {
1842
+ logos: BrandLogo[];
1843
+ palette: PaletteEntry[];
1844
+ typography: TypefaceEntry[];
1845
+ voice: {
1846
+ tagline: string | null;
1847
+ /** A few adjectives for the register the copy is written in. */
1848
+ tone: string[];
1849
+ };
1850
+ }
1851
+ export interface DesignSystem {
1852
+ radiusScale: ScaleStep[];
1853
+ spacingScale: ScaleStep[];
1854
+ /** Raw `box-shadow` values, most used first. */
1855
+ shadows: string[];
1856
+ /** `@media` breakpoint widths as written, ascending. */
1857
+ breakpoints: string[];
1858
+ /** Every `--*` declaration on `:root` - the company's own token vocabulary, verbatim. */
1859
+ customProperties: {
1860
+ name: string;
1861
+ value: string;
1862
+ }[];
1863
+ framework: CssFramework | null;
1864
+ }
1865
+ /**
1866
+ * What a company looks like, measured off its own website.
1867
+ *
1868
+ * The important property: **the numbers and the colours here were parsed, not
1869
+ * generated.** Every hex came out of a stylesheet and every scale step is a
1870
+ * count of real declarations. A model wrote only the prose (`about`,
1871
+ * `industry`, `businessModel`, `offerings`, `voice`) and assigned `role` to
1872
+ * values it was shown. Ask any model directly for a brand colour and it
1873
+ * answers `#3B82F6`; that is the failure this shape exists to avoid.
1874
+ */
1875
+ export interface BrandProfile {
1876
+ /** Normalised - lowercase, no scheme, no `www.`. */
1877
+ domain: string;
1878
+ /** The trading name, as the company writes it. */
1879
+ knownAs: string | null;
1880
+ officialName: string | null;
1881
+ officialNameSource: OfficialNameSource | null;
1882
+ about: string | null;
1883
+ industry: string | null;
1884
+ businessModel: BusinessModel | null;
1885
+ offerings: Offering[];
1886
+ brandKit: BrandKit;
1887
+ designSystem: DesignSystem;
1888
+ /** Derived from how many aspects the sources actually filled - not a model's self-report. */
1889
+ confidence: "high" | "medium" | "low";
1890
+ citations: BrandCitation[];
1891
+ /** ISO-8601. Age the profile against this. */
1892
+ retrievedAt: string;
1893
+ }
1894
+ /**
1895
+ * Why there is nothing to report. A normal 200, not an error.
1896
+ *
1897
+ * `consumer_domain` and `blocked_domain` will never succeed on retry.
1898
+ * `unreachable` may. `no_stylesheet` means the site answered but shipped no
1899
+ * CSS Connect could measure - a real outcome for a page built entirely in
1900
+ * inline styles.
1901
+ */
1902
+ export type BrandSkipReason = "no_domain" | "consumer_domain" | "blocked_domain" | "unreachable" | "no_signal" | "no_stylesheet";
1903
+ export type BrandExtractionResult = {
1904
+ status: "extracted";
1905
+ profile: BrandProfile;
1906
+ /** Whether Connect served this from its platform-wide cache. The profile is the same either way. */
1907
+ cached: boolean;
1908
+ } | {
1909
+ status: "skipped";
1910
+ reason: BrandSkipReason;
1911
+ };
1701
1912
  /**
1702
1913
  * What kind of conversation a channel carries. `voice` is the only kind
1703
1914
  * Connect stocks inventory for today - `whatsapp` and `meeting` are modelled
@@ -1952,3 +2163,62 @@ export interface ConversationsPage {
1952
2163
  nextCursor: string | null;
1953
2164
  hasMore: boolean;
1954
2165
  }
2166
+ /**
2167
+ * One notification, to members of one workspace.
2168
+ *
2169
+ * Hand-written here rather than imported from `@connect/data`, like every wire
2170
+ * type in this file: the published package carries none of Connect's
2171
+ * internals, and `conformance.test.ts` type-checks these against the server's
2172
+ * own DTOs so drift fails the build in Connect rather than in a partner's
2173
+ * integration.
2174
+ */
2175
+ export interface SendPushInput {
2176
+ workspaceId: string;
2177
+ /**
2178
+ * Connect user ids. Ids naming somebody who is not an active member are
2179
+ * dropped rather than refused - a partner legitimately holds a stale id for
2180
+ * somebody who has left, and failing the whole call for one of them would
2181
+ * mean a reminder nobody gets.
2182
+ */
2183
+ userIds: string[];
2184
+ title: string;
2185
+ body: string;
2186
+ /** Absolute, and on an origin this application registered as an SSO redirect URI. */
2187
+ url: string;
2188
+ }
2189
+ /**
2190
+ * What a fan-out actually did.
2191
+ *
2192
+ * Reported honestly rather than collapsed to a boolean, because the
2193
+ * interesting number is usually `silenced`: a partner seeing zero delivered
2194
+ * needs to know whether nobody has a device or everybody muted them, and those
2195
+ * call for opposite responses.
2196
+ */
2197
+ export interface SendPushResponse {
2198
+ /** Members reached after preference filtering. */
2199
+ recipients: number;
2200
+ /** Members dropped because they silenced notifications from their apps. */
2201
+ silenced: number;
2202
+ /** Devices the notification actually reached, across every recipient. */
2203
+ delivered: number;
2204
+ failed: number;
2205
+ }
2206
+ export interface PushKeyResponse {
2207
+ /** `false` when the platform has no VAPID keys - say so rather than prompting. */
2208
+ configured: boolean;
2209
+ /** The `applicationServerKey` for `pushManager.subscribe()`. Empty when unconfigured. */
2210
+ publicKey: string;
2211
+ }
2212
+ /** Exactly what `PushSubscription.toJSON()` produces, plus the agent string for the device list. */
2213
+ export interface RegisterPushDeviceInput {
2214
+ endpoint: string;
2215
+ keys: {
2216
+ p256dh: string;
2217
+ auth: string;
2218
+ };
2219
+ userAgent: string;
2220
+ }
2221
+ export interface RegisterPushDeviceResponse {
2222
+ /** Hand this to `revokeDevice` to turn the same browser off again. */
2223
+ id: string;
2224
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmoops/connect-sdk",
3
- "version": "0.30.0",
3
+ "version": "0.32.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",