@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.
Files changed (155) hide show
  1. package/CHANGELOG.md +566 -0
  2. package/README.md +113 -42
  3. package/dist/cjs/cli/doctor.js +3 -3
  4. package/dist/cjs/cli/init.js +3 -3
  5. package/dist/cjs/client/components/access-gate.js +20 -1
  6. package/dist/cjs/client/components/ai-screens.js +73 -0
  7. package/dist/cjs/client/components/ai-widgets.js +37 -0
  8. package/dist/cjs/client/components/app-shell.js +75 -41
  9. package/dist/cjs/client/components/apps-grid.js +3 -2
  10. package/dist/cjs/client/components/ask-ai.js +50 -5
  11. package/dist/cjs/client/components/confirm-dialog.js +40 -0
  12. package/dist/cjs/client/components/cursor-pagination.js +22 -0
  13. package/dist/cjs/client/components/filter-tabs.js +24 -0
  14. package/dist/cjs/client/components/grievance-notice.js +30 -0
  15. package/dist/cjs/client/components/link-button.js +17 -10
  16. package/dist/cjs/client/components/logo.js +4 -3
  17. package/dist/cjs/client/components/nav.js +41 -0
  18. package/dist/cjs/client/components/plan-pricing.js +27 -0
  19. package/dist/cjs/client/components/plans.js +1 -1
  20. package/dist/cjs/client/components/platform-gate.js +48 -0
  21. package/dist/cjs/client/components/shell-context.js +149 -0
  22. package/dist/cjs/client/components/side-nav.js +49 -23
  23. package/dist/cjs/client/components/sidebar.js +127 -0
  24. package/dist/cjs/client/components/subscribe-dialog.js +2 -2
  25. package/dist/cjs/client/components/subscription.js +1 -1
  26. package/dist/cjs/client/components/user-menu.js +96 -37
  27. package/dist/cjs/client/components/workspace-switcher.js +100 -0
  28. package/dist/cjs/client/index.js +72 -2
  29. package/dist/cjs/client/provider.js +57 -2
  30. package/dist/cjs/client/use-checkout.js +9 -5
  31. package/dist/cjs/errors.js +1 -1
  32. package/dist/cjs/index.js +16 -1
  33. package/dist/cjs/mcp/index.js +90 -0
  34. package/dist/cjs/mcp/tools.js +369 -0
  35. package/dist/cjs/next/index.js +2 -2
  36. package/dist/cjs/server/client.js +25 -2
  37. package/dist/cjs/server/handler.js +203 -18
  38. package/dist/cjs/server/http.js +6 -2
  39. package/dist/cjs/server/resources/acting-as.js +16 -0
  40. package/dist/cjs/server/resources/ai.js +22 -2
  41. package/dist/cjs/server/resources/data.js +374 -0
  42. package/dist/cjs/server/resources/identity.js +7 -3
  43. package/dist/cjs/server/resources/kv.js +84 -0
  44. package/dist/cjs/server/resources/platform.js +43 -0
  45. package/dist/cjs/server/resources/sessions.js +1 -1
  46. package/dist/cjs/server/resources/workspaces.js +74 -0
  47. package/dist/cjs/store-schema.js +66 -0
  48. package/dist/cjs/store.js +152 -0
  49. package/dist/cjs/ui/empty-state.js +28 -0
  50. package/dist/cjs/ui/index.js +44 -0
  51. package/dist/cjs/ui/page-header.js +27 -0
  52. package/dist/cjs/ui/page-shell.js +44 -0
  53. package/dist/cjs/ui/section.js +20 -0
  54. package/dist/cjs/ui/setting-row.js +14 -0
  55. package/dist/cjs/ui/status-chip.js +17 -0
  56. package/dist/esm/cli/doctor.js +3 -3
  57. package/dist/esm/cli/init.js +3 -3
  58. package/dist/esm/client/components/access-gate.js +20 -1
  59. package/dist/esm/client/components/ai-screens.js +69 -0
  60. package/dist/esm/client/components/ai-widgets.js +37 -1
  61. package/dist/esm/client/components/app-shell.js +81 -47
  62. package/dist/esm/client/components/apps-grid.js +3 -2
  63. package/dist/esm/client/components/ask-ai.js +51 -6
  64. package/dist/esm/client/components/confirm-dialog.js +37 -0
  65. package/dist/esm/client/components/cursor-pagination.js +19 -0
  66. package/dist/esm/client/components/filter-tabs.js +21 -0
  67. package/dist/esm/client/components/grievance-notice.js +27 -0
  68. package/dist/esm/client/components/link-button.js +17 -10
  69. package/dist/esm/client/components/logo.js +4 -3
  70. package/dist/esm/client/components/nav.js +40 -0
  71. package/dist/esm/client/components/plan-pricing.js +25 -0
  72. package/dist/esm/client/components/plans.js +2 -2
  73. package/dist/esm/client/components/platform-gate.js +45 -0
  74. package/dist/esm/client/components/shell-context.js +141 -0
  75. package/dist/esm/client/components/side-nav.js +48 -21
  76. package/dist/esm/client/components/sidebar.js +120 -0
  77. package/dist/esm/client/components/subscribe-dialog.js +3 -3
  78. package/dist/esm/client/components/subscription.js +2 -2
  79. package/dist/esm/client/components/user-menu.js +95 -38
  80. package/dist/esm/client/components/workspace-switcher.js +97 -0
  81. package/dist/esm/client/index.js +47 -4
  82. package/dist/esm/client/provider.js +53 -2
  83. package/dist/esm/client/use-checkout.js +9 -5
  84. package/dist/esm/errors.js +1 -1
  85. package/dist/esm/index.js +4 -0
  86. package/dist/esm/mcp/index.js +85 -0
  87. package/dist/esm/mcp/tools.js +366 -0
  88. package/dist/esm/next/index.js +2 -2
  89. package/dist/esm/server/client.js +25 -2
  90. package/dist/esm/server/handler.js +203 -18
  91. package/dist/esm/server/http.js +6 -2
  92. package/dist/esm/server/resources/acting-as.js +13 -0
  93. package/dist/esm/server/resources/ai.js +22 -2
  94. package/dist/esm/server/resources/data.js +369 -0
  95. package/dist/esm/server/resources/identity.js +7 -3
  96. package/dist/esm/server/resources/kv.js +80 -0
  97. package/dist/esm/server/resources/platform.js +39 -0
  98. package/dist/esm/server/resources/sessions.js +1 -1
  99. package/dist/esm/server/resources/workspaces.js +70 -0
  100. package/dist/esm/store-schema.js +62 -0
  101. package/dist/esm/store.js +141 -0
  102. package/dist/esm/ui/empty-state.js +25 -0
  103. package/dist/esm/ui/index.js +34 -0
  104. package/dist/esm/ui/page-header.js +24 -0
  105. package/dist/esm/ui/page-shell.js +41 -0
  106. package/dist/esm/ui/section.js +17 -0
  107. package/dist/esm/ui/setting-row.js +11 -0
  108. package/dist/esm/ui/status-chip.js +14 -0
  109. package/dist/types/client/components/ai-screens.d.ts +25 -0
  110. package/dist/types/client/components/ai-widgets.d.ts +34 -1
  111. package/dist/types/client/components/app-shell.d.ts +65 -27
  112. package/dist/types/client/components/apps-grid.d.ts +11 -2
  113. package/dist/types/client/components/ask-ai.d.ts +47 -1
  114. package/dist/types/client/components/confirm-dialog.d.ts +34 -0
  115. package/dist/types/client/components/cursor-pagination.d.ts +27 -0
  116. package/dist/types/client/components/filter-tabs.d.ts +26 -0
  117. package/dist/types/client/components/grievance-notice.d.ts +31 -0
  118. package/dist/types/client/components/link-button.d.ts +32 -9
  119. package/dist/types/client/components/logo.d.ts +18 -2
  120. package/dist/types/client/components/nav.d.ts +24 -0
  121. package/dist/types/client/components/plan-pricing.d.ts +10 -0
  122. package/dist/types/client/components/platform-gate.d.ts +53 -0
  123. package/dist/types/client/components/shell-context.d.ts +116 -0
  124. package/dist/types/client/components/side-nav.d.ts +27 -13
  125. package/dist/types/client/components/sidebar.d.ts +95 -0
  126. package/dist/types/client/components/user-menu.d.ts +68 -22
  127. package/dist/types/client/components/workspace-switcher.d.ts +49 -0
  128. package/dist/types/client/index.d.ts +29 -5
  129. package/dist/types/client/provider.d.ts +109 -6
  130. package/dist/types/client/use-checkout.d.ts +8 -4
  131. package/dist/types/errors.d.ts +1 -1
  132. package/dist/types/index.d.ts +6 -0
  133. package/dist/types/mcp/index.d.ts +74 -0
  134. package/dist/types/mcp/tools.d.ts +107 -0
  135. package/dist/types/next/index.d.ts +1 -1
  136. package/dist/types/server/client.d.ts +25 -2
  137. package/dist/types/server/http.d.ts +8 -2
  138. package/dist/types/server/index.d.ts +4 -0
  139. package/dist/types/server/resources/acting-as.d.ts +11 -0
  140. package/dist/types/server/resources/ai.d.ts +20 -2
  141. package/dist/types/server/resources/data.d.ts +258 -0
  142. package/dist/types/server/resources/kv.d.ts +63 -0
  143. package/dist/types/server/resources/platform.d.ts +32 -0
  144. package/dist/types/server/resources/workspaces.d.ts +55 -0
  145. package/dist/types/store-schema.d.ts +52 -0
  146. package/dist/types/store.d.ts +96 -0
  147. package/dist/types/types.d.ts +369 -16
  148. package/dist/types/ui/empty-state.d.ts +34 -0
  149. package/dist/types/ui/index.d.ts +40 -0
  150. package/dist/types/ui/page-header.d.ts +34 -0
  151. package/dist/types/ui/page-shell.d.ts +39 -0
  152. package/dist/types/ui/section.d.ts +26 -0
  153. package/dist/types/ui/setting-row.d.ts +18 -0
  154. package/dist/types/ui/status-chip.d.ts +36 -0
  155. package/package.json +15 -2
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deriveStoreTools = void 0;
4
+ exports.createStoreToolset = createStoreToolset;
5
+ /**
6
+ * `@cosmoops/connect-sdk/mcp` - your application's store, as an MCP server.
7
+ *
8
+ * Server-only: every call goes out through `ConnectClient`, which holds the API
9
+ * key. The `browser` export condition resolves this entry to a stub, so an
10
+ * import from client code fails the build rather than shipping the key.
11
+ *
12
+ * ## Zero dependencies, on purpose
13
+ *
14
+ * This entry does not import `@modelcontextprotocol/sdk`, and the package takes
15
+ * no dependency on it. `listTools()` and `callTool()` answer in the shapes the
16
+ * MCP protocol defines, so wiring them to the official server is four lines and
17
+ * a transport you choose:
18
+ *
19
+ * ```ts
20
+ * const toolset = createStoreToolset({
21
+ * collections: STORE_SCHEMA,
22
+ * data: connect.data,
23
+ * workspaceId: session.workspaceId,
24
+ * asUser: session.sessionToken,
25
+ * })
26
+ *
27
+ * server.setRequestHandler(ListToolsRequestSchema, () => toolset.listTools())
28
+ * server.setRequestHandler(CallToolRequestSchema, (request) =>
29
+ * toolset.callTool(request.params.name, request.params.arguments ?? {}),
30
+ * )
31
+ * ```
32
+ *
33
+ * The alternative - depending on the MCP SDK so this file could return an
34
+ * `McpServer` - would put a version of it in every install of this package,
35
+ * including the ones that never touch MCP, and pin partners to whichever
36
+ * version shipped here. The protocol shapes are stable and small; the coupling
37
+ * would not be.
38
+ *
39
+ * ## One toolset per session
40
+ *
41
+ * `asUser` is an end user's Connect session token, and Connect checks each
42
+ * collection's declared permission against that member's live roles. A toolset
43
+ * is therefore scoped to one signed-in user - build it per request from
44
+ * `getConnectSession()`, or pass a function for `asUser` if the surrounding
45
+ * server outlives a session.
46
+ */
47
+ var tools_js_1 = require("./tools.js");
48
+ Object.defineProperty(exports, "deriveStoreTools", { enumerable: true, get: function () { return tools_js_1.deriveStoreTools; } });
49
+ const tools_js_2 = require("./tools.js");
50
+ /**
51
+ * A refusal is a result, not a thrown error.
52
+ *
53
+ * MCP draws that distinction deliberately: a protocol error is a fault the
54
+ * model cannot act on, while `isError: true` is a tool result the model reads
55
+ * and can respond to. An unservable query and a permission Connect refused are
56
+ * both things a model should see and adapt to - naming a field it may not
57
+ * filter on, or telling the user it cannot reach that data - so they come back
58
+ * as content rather than as a transport failure.
59
+ */
60
+ function asResult(text, isError = false) {
61
+ return { content: [{ type: "text", text }], isError };
62
+ }
63
+ function createStoreToolset(options) {
64
+ const tools = (0, tools_js_2.deriveStoreTools)(options);
65
+ const byName = new Map(tools.map((tool) => [tool.name, tool]));
66
+ return {
67
+ tools,
68
+ listTools: () => ({
69
+ tools: tools.map((tool) => ({
70
+ name: tool.name,
71
+ title: tool.title,
72
+ description: tool.description,
73
+ inputSchema: tool.inputSchema,
74
+ annotations: tool.annotations,
75
+ })),
76
+ }),
77
+ async callTool(name, args) {
78
+ const tool = byName.get(name);
79
+ if (!tool) {
80
+ return asResult(`No such tool: "${name}". Available: ${[...byName.keys()].join(", ")}.`, true);
81
+ }
82
+ try {
83
+ return asResult(JSON.stringify(await tool.execute(args), null, 2));
84
+ }
85
+ catch (error) {
86
+ return asResult(error instanceof Error ? error.message : String(error), true);
87
+ }
88
+ },
89
+ };
90
+ }
@@ -0,0 +1,369 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deriveStoreTools = deriveStoreTools;
4
+ const store_js_1 = require("../store.js");
5
+ const store_schema_js_1 = require("../store-schema.js");
6
+ /**
7
+ * The store, as tools a model can call.
8
+ *
9
+ * ## Why a declaration is enough
10
+ *
11
+ * `Application.storeSchema` already answers every question a tool definition
12
+ * asks. It names the collection and says what it holds (`description`), which
13
+ * fields may be filtered or ordered on (`queryableFields`), which query shapes
14
+ * the database can actually serve (`indexes`), and whether the collection
15
+ * refuses everything but a create (`appendOnly`). Writing those tools by hand
16
+ * means maintaining a second copy of all of it, and the copy is wrong the first
17
+ * time a filter is added to a screen.
18
+ *
19
+ * So they are derived. Adding a queryable field to the declaration and
20
+ * re-seeding widens the tools on the next boot, with no tool code to touch.
21
+ *
22
+ * ## What this is not
23
+ *
24
+ * **It is not an authorization boundary.** Connect is. Every call here goes out
25
+ * with the end user's session token (`asUser`), and Connect resolves it to a
26
+ * member and checks the collection's declared `readPermission`/`writePermission`
27
+ * against that member's live roles - so a tool can never reach further than the
28
+ * person on whose behalf it runs, and a permission revoked in the console is
29
+ * gone from the next call. The shaping done here - omitting write tools for an
30
+ * `appendOnly` collection, refusing an unservable query - is there so a model
31
+ * fails fast and legibly, not because it protects anything. Connect refuses the
32
+ * same things independently.
33
+ *
34
+ * **There is no `scan` tool, deliberately.** `data.scan` reads a collection
35
+ * across every workspace and, for exactly that reason, takes no `asUser` - a
36
+ * workspace admin is an admin of their own workspace and not of a partner's
37
+ * other customers. A toolset that runs on behalf of a signed-in user therefore
38
+ * has nothing to derive from `crossWorkspace`, and generating a scan tool would
39
+ * mean running that one call under the application's own key while every tool
40
+ * beside it ran under the user's. Cross-tenant reads belong in a back-office
41
+ * surface with its own gate.
42
+ *
43
+ * **`kv` collections are skipped.** The key/value store is addressed by
44
+ * namespace and key rather than queried, so it derives nothing from
45
+ * `queryableFields` or `indexes` and the tools it wants are a different shape.
46
+ *
47
+ * ## Deriving over every collection is usually wrong
48
+ *
49
+ * Measured on the two reference applications: club's fifteen collections yield
50
+ * 45 read tools and 88 with writes on; CRM's seven yield 21 and 40. Those are
51
+ * whole-catalogue numbers, and a model offered them chooses worse than one
52
+ * offered a handful - the ceiling is tool-choice accuracy, which degrades well
53
+ * before a context window fills. Connect's own assistant bounds each turn to
54
+ * the capabilities the current screen promotes for exactly this reason.
55
+ *
56
+ * So `include` is the normal way to call this, not a refinement: name the
57
+ * collections the surface at hand is about. Nothing here enforces a cap,
58
+ * because the right number depends on the surface and a silent truncation
59
+ * would be worse than a large list.
60
+ */
61
+ /** Connect's own ceilings, mirrored so a tool can describe them before the call. */
62
+ const MAX_QUERY_LIMIT = 200;
63
+ const DEFAULT_QUERY_LIMIT = 50;
64
+ const QUERY_OPERATORS = [
65
+ "==",
66
+ "!=",
67
+ "<",
68
+ "<=",
69
+ ">",
70
+ ">=",
71
+ "in",
72
+ "not-in",
73
+ "array-contains",
74
+ ];
75
+ /**
76
+ * MCP tool names are `[a-z0-9_-]`; collection names additionally allow capitals.
77
+ * Lowercasing and folding `-` to `_` is what makes `dues-demands` addressable,
78
+ * and is also how two collections can collide - which is caught, not resolved,
79
+ * because silently renaming one would make the tool a model calls depend on
80
+ * declaration order.
81
+ */
82
+ function toolBaseName(collection) {
83
+ return collection.toLowerCase().replace(/-/g, "_");
84
+ }
85
+ /**
86
+ * How many index shapes are worth spelling out before the listing costs more
87
+ * than it teaches.
88
+ *
89
+ * A real declaration is not a handful. club's ledger declares around forty
90
+ * shapes, and writing all of them into both the list and aggregate tools was
91
+ * most of a 79 KB `tools/list` on its own - paid on every turn, for a listing a
92
+ * model reads poorly anyway. Past this many, the description states the rule
93
+ * and leaves the specifics to the refusal, which names the exact index a
94
+ * rejected query needed and costs no round trip to get.
95
+ */
96
+ const MAX_DESCRIBED_INDEXES = 12;
97
+ /** The servable query shapes, spelled out for the model rather than left to be discovered. */
98
+ function describeIndexes(collection) {
99
+ if (collection.indexes.length === 0) {
100
+ return "No composite indexes are declared, so a query may use at most one filter, ordered only by that same field.";
101
+ }
102
+ const preamble = "A query using more than one filter, or ordering by a field it does not filter on, must match a declared composite index";
103
+ if (collection.indexes.length > MAX_DESCRIBED_INDEXES) {
104
+ return `${preamble}. ${collection.indexes.length} are declared - too many to list here. Issue the query you want: if no index covers it you get back the exact index it would need, before anything is read.`;
105
+ }
106
+ const shapes = collection.indexes.map((index) => ` - ${(0, store_js_1.storeIndexSignature)(index.fields)}`).join("\n");
107
+ return `${preamble}:\n${shapes}`;
108
+ }
109
+ function readDescription(collection, verb) {
110
+ const head = `${verb} ${collection.name}. ${collection.description}`;
111
+ // Nothing declared queryable means the collection can only be read whole -
112
+ // which is the right shape for the one-document-per-key collections (a
113
+ // settings block, a counter), and saying so beats offering a filter argument
114
+ // that has no legal value.
115
+ if (collection.queryableFields.length === 0) {
116
+ return `${head}\n\nThis collection declares no queryable fields, so it cannot be filtered or ordered - it is read whole.`;
117
+ }
118
+ return [head, "", `Queryable fields: ${collection.queryableFields.join(", ")}.`, describeIndexes(collection)].join("\n");
119
+ }
120
+ const filtersSchema = (collection) => ({
121
+ type: "array",
122
+ description: "Filters, ANDed together.",
123
+ items: {
124
+ type: "object",
125
+ properties: {
126
+ field: { type: "string", enum: [...collection.queryableFields] },
127
+ op: { type: "string", enum: QUERY_OPERATORS },
128
+ value: {
129
+ description: "The value to compare against. Dates are ISO-8601 strings; a calendar day is YYYY-MM-DD.",
130
+ },
131
+ },
132
+ required: ["field", "op", "value"],
133
+ additionalProperties: false,
134
+ },
135
+ });
136
+ /**
137
+ * Documents come back with Connect's `_`-prefixed bookkeeping on them, and
138
+ * every write refuses those fields. Saying so on the write tools is what stops
139
+ * a model reading a document, editing one field and posting the whole thing
140
+ * back - which fails, correctly, but wastes a turn learning it.
141
+ */
142
+ const WRITE_DATA_NOTE = "Fields beginning with `_` are written by Connect and are refused here - strip `_createdAt`, `_updatedAt`, `_createdBy`, `_updatedBy` and `_updateTime` from a document you read before writing it back.";
143
+ function buildReadTools(collection, base, call, data, workspaceId) {
144
+ return [
145
+ {
146
+ name: `${base}_get`,
147
+ title: `Get one ${collection.name} document`,
148
+ description: `Read one document from ${collection.name} by id. ${collection.description}`,
149
+ inputSchema: {
150
+ type: "object",
151
+ properties: { id: { type: "string", description: "The document id." } },
152
+ required: ["id"],
153
+ additionalProperties: false,
154
+ },
155
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
156
+ execute: (args) => call((asUser) => data.get({ workspaceId, collection: collection.name, id: String(args.id), asUser })),
157
+ },
158
+ {
159
+ name: `${base}_list`,
160
+ title: `List ${collection.name}`,
161
+ description: readDescription(collection, "List documents in"),
162
+ inputSchema: {
163
+ type: "object",
164
+ properties: {
165
+ ...(collection.queryableFields.length > 0
166
+ ? {
167
+ filters: filtersSchema(collection),
168
+ orderBy: {
169
+ type: "object",
170
+ properties: {
171
+ field: { type: "string", enum: [...collection.queryableFields] },
172
+ direction: { type: "string", enum: ["asc", "desc"] },
173
+ },
174
+ required: ["field", "direction"],
175
+ additionalProperties: false,
176
+ },
177
+ }
178
+ : {}),
179
+ limit: {
180
+ type: "integer",
181
+ minimum: 1,
182
+ maximum: MAX_QUERY_LIMIT,
183
+ description: `Page size. Defaults to ${DEFAULT_QUERY_LIMIT}, caps at ${MAX_QUERY_LIMIT}.`,
184
+ },
185
+ cursor: {
186
+ type: "string",
187
+ description: "The previous page's nextCursor. A null nextCursor is the end of the collection.",
188
+ },
189
+ },
190
+ additionalProperties: false,
191
+ },
192
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
193
+ execute: async (args) => {
194
+ const filters = args.filters ?? [];
195
+ const orderBy = args.orderBy ?? null;
196
+ const rejection = (0, store_schema_js_1.describeQueryRejection)(collection, { filters, orderBy });
197
+ if (rejection)
198
+ throw new Error(rejection);
199
+ return call((asUser) => data.query({
200
+ workspaceId,
201
+ collection: collection.name,
202
+ filters,
203
+ orderBy: orderBy ?? undefined,
204
+ limit: args.limit,
205
+ cursor: args.cursor ?? null,
206
+ asUser,
207
+ }));
208
+ },
209
+ },
210
+ {
211
+ name: `${base}_aggregate`,
212
+ title: `Count, sum or average ${collection.name}`,
213
+ description: [
214
+ readDescription(collection, "Aggregate"),
215
+ "",
216
+ "Counts, sums and averages are computed in the database rather than by reading rows back, so prefer this over listing a collection to add it up. A sum or average over nothing answers null, not 0.",
217
+ ].join("\n"),
218
+ inputSchema: {
219
+ type: "object",
220
+ properties: {
221
+ aggregations: {
222
+ type: "array",
223
+ minItems: 1,
224
+ items: {
225
+ type: "object",
226
+ properties: {
227
+ op: {
228
+ type: "string",
229
+ // `sum` and `avg` need a field, and every field they could
230
+ // name must be queryable - so a collection declaring none can
231
+ // only be counted.
232
+ enum: collection.queryableFields.length > 0 ? ["count", "sum", "avg"] : ["count"],
233
+ },
234
+ ...(collection.queryableFields.length > 0
235
+ ? {
236
+ field: {
237
+ type: "string",
238
+ enum: [...collection.queryableFields],
239
+ description: "Required for sum and avg; omitted for count, which counts documents.",
240
+ },
241
+ }
242
+ : {}),
243
+ alias: { type: "string", description: "What the result is keyed by." },
244
+ },
245
+ required: ["op", "alias"],
246
+ additionalProperties: false,
247
+ },
248
+ },
249
+ ...(collection.queryableFields.length > 0 ? { filters: filtersSchema(collection) } : {}),
250
+ },
251
+ required: ["aggregations"],
252
+ additionalProperties: false,
253
+ },
254
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
255
+ execute: async (args) => {
256
+ const filters = args.filters ?? [];
257
+ // An aggregation has no ordering, so the index it needs is the one its
258
+ // filters need - the same check `list` makes, minus the sort.
259
+ const rejection = (0, store_schema_js_1.describeQueryRejection)(collection, { filters, orderBy: null });
260
+ if (rejection)
261
+ throw new Error(rejection);
262
+ return call((asUser) => data.aggregate({
263
+ workspaceId,
264
+ collection: collection.name,
265
+ aggregations: args.aggregations,
266
+ filters,
267
+ asUser,
268
+ }));
269
+ },
270
+ },
271
+ ];
272
+ }
273
+ function buildWriteTools(collection, base, call, data, workspaceId, writes) {
274
+ if (writes === "none")
275
+ return [];
276
+ const tools = [
277
+ {
278
+ name: `${base}_create`,
279
+ title: `Create a ${collection.name} document`,
280
+ description: `Create a document in ${collection.name}. ${collection.description}\n\n${WRITE_DATA_NOTE}`,
281
+ inputSchema: {
282
+ type: "object",
283
+ properties: {
284
+ data: { type: "object", description: "The document's fields." },
285
+ id: { type: "string", description: "Choose the id, or omit for an allocated one. An existing id is a conflict." },
286
+ },
287
+ required: ["data"],
288
+ additionalProperties: false,
289
+ },
290
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
291
+ execute: (args) => call((asUser) => data.create({
292
+ workspaceId,
293
+ collection: collection.name,
294
+ data: args.data,
295
+ id: args.id,
296
+ asUser,
297
+ })),
298
+ },
299
+ ];
300
+ // An append-only collection refuses a merge and a delete for every caller,
301
+ // this application's own key included, so deriving those tools would offer a
302
+ // model two calls that can only ever fail.
303
+ if (writes === "all" && !collection.appendOnly) {
304
+ tools.push({
305
+ name: `${base}_update`,
306
+ title: `Update a ${collection.name} document`,
307
+ description: `Merge top-level fields into an existing ${collection.name} document. Fields not named are left alone.\n\n${WRITE_DATA_NOTE}`,
308
+ inputSchema: {
309
+ type: "object",
310
+ properties: {
311
+ id: { type: "string" },
312
+ data: { type: "object", description: "The fields to merge." },
313
+ },
314
+ required: ["id", "data"],
315
+ additionalProperties: false,
316
+ },
317
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },
318
+ execute: (args) => call((asUser) => data.update({
319
+ workspaceId,
320
+ collection: collection.name,
321
+ id: String(args.id),
322
+ data: args.data,
323
+ asUser,
324
+ })),
325
+ }, {
326
+ name: `${base}_delete`,
327
+ title: `Delete a ${collection.name} document`,
328
+ description: `Delete one document from ${collection.name}. Idempotent - deleting a document that is not there succeeds.`,
329
+ inputSchema: {
330
+ type: "object",
331
+ properties: { id: { type: "string" } },
332
+ required: ["id"],
333
+ additionalProperties: false,
334
+ },
335
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true },
336
+ execute: (args) => call((asUser) => data
337
+ .delete({ workspaceId, collection: collection.name, id: String(args.id), asUser })
338
+ .then(() => ({ deleted: String(args.id) }))),
339
+ });
340
+ }
341
+ return tools;
342
+ }
343
+ /** Every tool one declaration yields, in a stable order. */
344
+ function deriveStoreTools(options) {
345
+ const { collections, data, workspaceId, writes = "none", prefix = "", include } = options;
346
+ const resolveUser = typeof options.asUser === "function" ? options.asUser : () => options.asUser;
347
+ const call = async (run) => run(await resolveUser());
348
+ const wanted = include ? new Set(include) : null;
349
+ const selected = collections.filter((collection) => collection.kind === "data" && (!wanted || wanted.has(collection.name)));
350
+ if (wanted) {
351
+ const declared = new Set(collections.map((collection) => collection.name));
352
+ const missing = [...wanted].filter((name) => !declared.has(name));
353
+ if (missing.length > 0) {
354
+ throw new Error(`No declaration for ${missing.join(", ")}. Declared collections: ${[...declared].join(", ")}.`);
355
+ }
356
+ }
357
+ const byBaseName = new Map();
358
+ const tools = [];
359
+ for (const collection of selected) {
360
+ const base = `${prefix}${toolBaseName(collection.name)}`;
361
+ const clash = byBaseName.get(base);
362
+ if (clash) {
363
+ throw new Error(`"${collection.name}" and "${clash}" both derive the tool prefix "${base}". Rename one, or give one its own toolset with a prefix.`);
364
+ }
365
+ byBaseName.set(base, collection.name);
366
+ tools.push(...buildReadTools(collection, base, call, data, workspaceId), ...buildWriteTools(collection, base, call, data, workspaceId, writes));
367
+ }
368
+ return tools;
369
+ }
@@ -82,7 +82,7 @@ const handler_js_1 = require("../server/handler.js");
82
82
  * | `CONNECT_SESSION_COOKIE` | no | session cookie name |
83
83
  *
84
84
  * Register `{your origin}/api/connect/callback` as a redirect URI in the
85
- * application's Integration tab. That is the only step this cannot do for you.
85
+ * application's Connect client section. That is the only step this cannot do for you.
86
86
  *
87
87
  * No `next.config.ts` changes needed either, including `transpilePackages`:
88
88
  * `dist/` is already ES2022 with `"use client"` hoisted to the top of every
@@ -317,7 +317,7 @@ async function getConnectSession(request, options = {}) {
317
317
  * A thin wrapper, not a framework of its own - reach for `getConnectSession`
318
318
  * plus your own check when "not signed in" isn't "redirect to sign-in"
319
319
  * (an API route returning 401, say). Role and entitlement checks stay
320
- * outside this function on purpose: `session.applicationRole.granted` and
320
+ * outside this function on purpose: `session.permissions` and
321
321
  * `connect.entitlements.*` are one line each on top of the session this
322
322
  * already gives you, and where that line redirects (a plans page, a
323
323
  * "forbidden" screen) is a decision only your app can make - see
@@ -4,6 +4,9 @@ exports.SDK_VERSION = exports.ConnectClient = exports.DEFAULT_BASE_URL = void 0;
4
4
  const access_js_1 = require("./resources/access.js");
5
5
  const apps_js_1 = require("./resources/apps.js");
6
6
  const ai_js_1 = require("./resources/ai.js");
7
+ const data_js_1 = require("./resources/data.js");
8
+ const workspaces_js_1 = require("./resources/workspaces.js");
9
+ const kv_js_1 = require("./resources/kv.js");
7
10
  const billing_js_1 = require("./resources/billing.js");
8
11
  const drive_js_1 = require("./resources/drive.js");
9
12
  const entitlements_js_1 = require("./resources/entitlements.js");
@@ -13,6 +16,7 @@ const logs_js_1 = require("./resources/logs.js");
13
16
  const analytics_js_1 = require("./resources/analytics.js");
14
17
  const otp_js_1 = require("./resources/otp.js");
15
18
  const sessions_js_1 = require("./resources/sessions.js");
19
+ const platform_js_1 = require("./resources/platform.js");
16
20
  exports.DEFAULT_BASE_URL = "https://connect.cosmoops.com";
17
21
  const APPLICATION_API_PREFIX = "/api/v1/application";
18
22
  /**
@@ -77,6 +81,10 @@ class ConnectClient {
77
81
  drive;
78
82
  /** RAG chat and structured extraction. */
79
83
  ai;
84
+ /** Your application's own document store - see `DataResource`. */
85
+ data;
86
+ /** Your application's key/value store - see `KvResource`. */
87
+ kv;
80
88
  /** WhatsApp/SMS phone verification. */
81
89
  otp;
82
90
  /** Feature events into the Console's audit log, filterable to your application. */
@@ -87,10 +95,20 @@ class ConnectClient {
87
95
  sessions;
88
96
  /** Composio-hosted third-party connections (Google Calendar, Slack, ...) a workspace has connected. */
89
97
  integrations;
98
+ /** Which workspaces the signed-in user belongs to, switching between them, and the team - see `WorkspacesResource`. */
99
+ workspaces;
100
+ /** The platform's own legal identity and DPDP grievance channel - see `PlatformResource`. */
101
+ platform;
102
+ /**
103
+ * Relays one `/data/*` or `/kv/*` request verbatim - `createConnectHandler`'s
104
+ * store route and nothing else. See `storePassthrough`.
105
+ * @internal
106
+ */
107
+ storeRelay;
90
108
  constructor(options) {
91
109
  assertNotBrowser();
92
110
  if (!options.apiKey) {
93
- throw new TypeError("ConnectClient requires `apiKey` - the plaintext shown once in the Integration tab.");
111
+ throw new TypeError("ConnectClient requires `apiKey` - the plaintext shown once in the Connect client section.");
94
112
  }
95
113
  const appBaseUrl = (options.baseUrl ?? exports.DEFAULT_BASE_URL).replace(/\/+$/, "");
96
114
  const transport = {
@@ -110,13 +128,18 @@ class ConnectClient {
110
128
  this.billing = new billing_js_1.BillingResource(transport);
111
129
  this.drive = new drive_js_1.DriveResource(transport);
112
130
  this.ai = new ai_js_1.AiResource(transport);
131
+ this.data = new data_js_1.DataResource(transport);
132
+ this.kv = new kv_js_1.KvResource(transport);
113
133
  this.otp = new otp_js_1.OtpResource(transport);
114
134
  this.logs = new logs_js_1.LogsResource(transport);
115
135
  this.analytics = new analytics_js_1.AnalyticsResource(transport);
116
136
  this.sessions = new sessions_js_1.SessionsResource(transport, options.clientId ?? null);
117
137
  this.integrations = new integrations_js_1.IntegrationsResource(transport);
138
+ this.workspaces = new workspaces_js_1.WorkspacesResource(transport);
139
+ this.platform = new platform_js_1.PlatformResource(transport);
140
+ this.storeRelay = (spec) => (0, data_js_1.storePassthrough)(transport, spec);
118
141
  }
119
142
  }
120
143
  exports.ConnectClient = ConnectClient;
121
144
  /** Kept in step with `package.json` by `sdk.test.ts`. */
122
- exports.SDK_VERSION = "0.9.0";
145
+ exports.SDK_VERSION = "0.16.0";