@12-apps/mcp 3.10.0 → 3.12.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.
@@ -1,6 +1,9 @@
1
1
  import {
2
- providerForHostId
3
- } from "../chunk-FBUSSQSK.js";
2
+ createPrismaMcpStores,
3
+ disconnectAiHost,
4
+ listAiConnections
5
+ } from "../chunk-VDD4YRNP.js";
6
+ import "../chunk-FBUSSQSK.js";
4
7
  import {
5
8
  ACCESS_TOKEN_TTL_SECONDS,
6
9
  AUTHORIZATION_CODE_AUDIENCE,
@@ -44,193 +47,7 @@ import {
44
47
  verifyCode
45
48
  } from "../chunk-UIILEGAC.js";
46
49
  import "../chunk-WJJNKKNS.js";
47
- import {
48
- __name
49
- } from "../chunk-7QVYU63E.js";
50
-
51
- // src/oauth/prisma-stores.ts
52
- function clientStore(getPrisma) {
53
- return {
54
- async create(client) {
55
- const prisma = await getPrisma();
56
- return prisma.oAuthClient.create({ data: client });
57
- },
58
- async findByClientId(clientId) {
59
- const prisma = await getPrisma();
60
- return prisma.oAuthClient.findUnique({ where: { clientId } });
61
- }
62
- };
63
- }
64
- __name(clientStore, "clientStore");
65
- function refreshTokenStore(getPrisma) {
66
- return {
67
- async create(token) {
68
- const prisma = await getPrisma();
69
- await prisma.oAuthRefreshToken.create({ data: token });
70
- },
71
- async findByHash(tokenHash) {
72
- const prisma = await getPrisma();
73
- return prisma.oAuthRefreshToken.findUnique({ where: { tokenHash } });
74
- },
75
- async hasSuccessor(tokenHash) {
76
- const prisma = await getPrisma();
77
- const successor = await prisma.oAuthRefreshToken.findFirst({
78
- where: { rotatedFrom: tokenHash }
79
- });
80
- return successor !== null;
81
- },
82
- async listFamily(userEmail, clientId) {
83
- const prisma = await getPrisma();
84
- return prisma.oAuthRefreshToken.findMany({ where: { userEmail, clientId } });
85
- },
86
- async revokeHashes(tokenHashes, at) {
87
- if (tokenHashes.length === 0) return;
88
- const prisma = await getPrisma();
89
- await prisma.oAuthRefreshToken.updateMany({
90
- where: { tokenHash: { in: [...tokenHashes] } },
91
- data: { revokedAt: at }
92
- });
93
- },
94
- async rotate(successor, parentHash, at) {
95
- const prisma = await getPrisma();
96
- return prisma.$transaction(async (tx) => {
97
- const { count } = await tx.oAuthRefreshToken.updateMany({
98
- where: { tokenHash: parentHash, revokedAt: null },
99
- data: { revokedAt: at }
100
- });
101
- if (count !== 1) return false;
102
- await tx.oAuthRefreshToken.create({ data: successor });
103
- return true;
104
- });
105
- },
106
- async revokeLiveForClient(userEmail, clientId) {
107
- const prisma = await getPrisma();
108
- const { count } = await prisma.oAuthRefreshToken.updateMany({
109
- where: { userEmail, clientId, revokedAt: null },
110
- data: { revokedAt: /* @__PURE__ */ new Date() }
111
- });
112
- return count;
113
- }
114
- };
115
- }
116
- __name(refreshTokenStore, "refreshTokenStore");
117
- var CONNECTION_SELECT = {
118
- oauthClientId: true,
119
- clientName: true,
120
- host: true,
121
- connectedAt: true,
122
- lastActiveAt: true
123
- };
124
- function connectionStore(getPrisma) {
125
- return {
126
- async lastActiveAt(userId, oauthClientId) {
127
- const prisma = await getPrisma();
128
- const row = await prisma.mcpConnection.findUnique({
129
- where: { userId_oauthClientId: { userId, oauthClientId } },
130
- select: { lastActiveAt: true }
131
- });
132
- return row?.lastActiveAt ?? null;
133
- },
134
- async recordActivity({ userId, oauthClientId, clientName, host, at }) {
135
- const prisma = await getPrisma();
136
- await prisma.mcpConnection.upsert({
137
- where: { userId_oauthClientId: { userId, oauthClientId } },
138
- create: { userId, oauthClientId, clientName, host, connectedAt: at, lastActiveAt: at },
139
- // Never blank a known host on refresh — keep the existing attribution
140
- // when this grant cannot derive one.
141
- update: {
142
- clientName,
143
- lastActiveAt: at,
144
- revokedAt: null,
145
- ...host ? { host } : {}
146
- }
147
- });
148
- },
149
- async listActive(userId) {
150
- const prisma = await getPrisma();
151
- const rows = await prisma.mcpConnection.findMany({
152
- where: { userId, revokedAt: null },
153
- orderBy: { lastActiveAt: "desc" },
154
- select: { ...CONNECTION_SELECT }
155
- });
156
- return rows;
157
- },
158
- revokeByHost: /* @__PURE__ */ __name((userId, host) => revokeByHost(getPrisma, userId, host), "revokeByHost"),
159
- announce: /* @__PURE__ */ __name((userId, host) => announce(getPrisma, userId, host), "announce")
160
- };
161
- }
162
- __name(connectionStore, "connectionStore");
163
- async function revokeByHost(getPrisma, userId, host) {
164
- const prisma = await getPrisma();
165
- const attributed = await prisma.mcpConnection.findMany({
166
- where: { userId, revokedAt: null, host },
167
- select: { id: true, oauthClientId: true }
168
- });
169
- const targets = attributed.length > 0 ? attributed : await prisma.mcpConnection.findMany({
170
- where: { userId, revokedAt: null, host: null },
171
- select: { id: true, oauthClientId: true }
172
- });
173
- if (targets.length === 0) return [];
174
- await prisma.mcpConnection.updateMany({
175
- where: { id: { in: targets.map((row) => String(row.id)) } },
176
- data: { revokedAt: /* @__PURE__ */ new Date() }
177
- });
178
- return targets.map((row) => String(row.oauthClientId));
179
- }
180
- __name(revokeByHost, "revokeByHost");
181
- async function announce(getPrisma, userId, host) {
182
- const prisma = await getPrisma();
183
- const now = /* @__PURE__ */ new Date();
184
- const refreshed = await prisma.mcpConnection.updateMany({
185
- where: { userId, revokedAt: null, host },
186
- data: { lastActiveAt: now, revokedAt: null }
187
- });
188
- if (refreshed.count > 0) return refreshed.count;
189
- const candidate = await prisma.mcpConnection.findFirst({
190
- where: { userId, revokedAt: null, host: null },
191
- orderBy: { lastActiveAt: "desc" },
192
- select: { id: true }
193
- });
194
- if (!candidate) return 0;
195
- await prisma.mcpConnection.update({
196
- where: { id: candidate.id },
197
- data: { host, lastActiveAt: now, revokedAt: null }
198
- });
199
- return 1;
200
- }
201
- __name(announce, "announce");
202
- function createPrismaMcpStores(getPrisma) {
203
- return {
204
- clients: clientStore(getPrisma),
205
- refreshTokens: refreshTokenStore(getPrisma),
206
- connections: connectionStore(getPrisma)
207
- };
208
- }
209
- __name(createPrismaMcpStores, "createPrismaMcpStores");
210
-
211
- // src/oauth/connections.ts
212
- function asProvider(host) {
213
- return host === null ? null : providerForHostId(host);
214
- }
215
- __name(asProvider, "asProvider");
216
- async function listAiConnections(connections, userId) {
217
- const rows = await connections.listActive(userId);
218
- return rows.map((row) => ({ ...row, host: asProvider(row.host) }));
219
- }
220
- __name(listAiConnections, "listAiConnections");
221
- async function disconnectAiHost(stores, caller, host) {
222
- const disconnectedClientIds = await stores.connections.revokeByHost(caller.userId, host);
223
- const revoked = await Promise.all(
224
- disconnectedClientIds.map(
225
- (clientId) => stores.refreshTokens.revokeLiveForClient(caller.email, clientId)
226
- )
227
- );
228
- return {
229
- disconnectedClientIds,
230
- revokedRefreshTokens: revoked.reduce((total, count) => total + count, 0)
231
- };
232
- }
233
- __name(disconnectAiHost, "disconnectAiHost");
50
+ import "../chunk-7QVYU63E.js";
234
51
  export {
235
52
  ACCESS_TOKEN_TTL_SECONDS,
236
53
  AUTHORIZATION_CODE_AUDIENCE,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/oauth/prisma-stores.ts","../../src/oauth/connections.ts"],"sourcesContent":["import type {\n McpConnectionStore,\n McpOauthStores,\n NewOAuthClient,\n NewRefreshToken,\n OAuthClientStore,\n RefreshTokenStore,\n StoredMcpConnection,\n StoredOAuthClient,\n StoredRefreshToken,\n} from \"./stores\";\n\n/**\n * The ports of `./stores.ts`, filled by Prisma (12-23).\n *\n * The package owns the three models (`prisma/mcp.prisma`), so their delegate\n * shapes are known and this adapter can be exact. A host with Prisma therefore\n * writes ONE line —\n *\n * stores: createPrismaMcpStores(async () => getPrismaClient() as unknown as McpOauthPrisma)\n *\n * — and no host code at all beyond it. The client is duck-typed (only the\n * delegates used, only the arguments used) so this file never imports a project's\n * generated client, and a non-Prisma host fills the ports directly instead.\n */\n\n/** A `where` on the composite unique of `mcp_connections`. */\ninterface ConnectionKey {\n userId_oauthClientId: { userId: string; oauthClientId: string };\n}\n\n/** The minimal Prisma surface the AS needs. Every field is one the surface writes. */\nexport interface McpOauthPrisma {\n oAuthClient: {\n create(args: { data: NewOAuthClient }): Promise<StoredOAuthClient>;\n findUnique(args: { where: { clientId: string } }): Promise<StoredOAuthClient | null>;\n };\n oAuthRefreshToken: {\n create(args: { data: NewRefreshToken }): Promise<unknown>;\n findUnique(args: { where: { tokenHash: string } }): Promise<StoredRefreshToken | null>;\n findFirst(args: { where: { rotatedFrom: string } }): Promise<{ tokenHash: string } | null>;\n findMany(args: {\n where: { userEmail: string; clientId: string };\n }): Promise<StoredRefreshToken[]>;\n // No single-row `update`: the rotation used to revoke its parent with one and\n // that was the bug (unconditional, so two concurrent rotations both won). Every\n // revoke here is now an `updateMany` with a predicate that says WHICH rows may\n // move, which is also why this delegate list stays honest about what is written.\n updateMany(args: {\n where:\n | { tokenHash: { in: string[] } }\n | { userEmail: string; clientId: string; revokedAt: null };\n data: { revokedAt: Date };\n }): Promise<{ count: number }>;\n };\n mcpConnection: {\n findUnique(args: {\n where: ConnectionKey;\n select: { lastActiveAt: true };\n }): Promise<{ lastActiveAt: Date } | null>;\n findFirst(args: {\n where: { userId: string; revokedAt: null; host: null };\n orderBy: { lastActiveAt: \"desc\" };\n select: { id: true };\n }): Promise<{ id: string } | null>;\n findMany(args: {\n where: { userId: string; revokedAt: null; host?: string | null };\n orderBy?: { lastActiveAt: \"desc\" };\n select: Record<string, true>;\n }): Promise<Record<string, unknown>[]>;\n upsert(args: {\n where: ConnectionKey;\n create: Record<string, unknown>;\n update: Record<string, unknown>;\n }): Promise<unknown>;\n update(args: { where: { id: string }; data: Record<string, unknown> }): Promise<unknown>;\n updateMany(args: {\n where: { id: { in: string[] } } | { userId: string; revokedAt: null; host: string };\n data: Record<string, unknown>;\n }): Promise<{ count: number }>;\n };\n /**\n * Prisma's INTERACTIVE transaction, used for the rotation's claim + write. The\n * callback form (not the array form) is required: the successor may only be\n * created once the conditional revoke has reported that it, and not a concurrent\n * sibling, claimed the parent — see `RefreshTokenStore.rotate`.\n */\n $transaction<T>(fn: (tx: McpOauthTx) => Promise<T>): Promise<T>;\n}\n\n/**\n * The delegate subset used INSIDE the rotation transaction. Not exported: it is\n * reachable structurally through `McpOauthPrisma.$transaction`, so no host ever\n * needs to name it, and exporting a type nobody imports is what knip flags.\n */\ninterface McpOauthTx {\n oAuthRefreshToken: {\n create(args: { data: NewRefreshToken }): Promise<unknown>;\n updateMany(args: {\n where: { tokenHash: string; revokedAt: null };\n data: { revokedAt: Date };\n }): Promise<{ count: number }>;\n };\n}\n\n/** A lazily-resolved client, so a host's singleton is awaited per call. */\nexport type McpOauthPrismaProvider = () => Promise<McpOauthPrisma>;\n\nfunction clientStore(getPrisma: McpOauthPrismaProvider): OAuthClientStore {\n return {\n async create(client: NewOAuthClient) {\n const prisma = await getPrisma();\n return prisma.oAuthClient.create({ data: client });\n },\n async findByClientId(clientId: string) {\n const prisma = await getPrisma();\n return prisma.oAuthClient.findUnique({ where: { clientId } });\n },\n };\n}\n\nfunction refreshTokenStore(getPrisma: McpOauthPrismaProvider): RefreshTokenStore {\n return {\n async create(token) {\n const prisma = await getPrisma();\n await prisma.oAuthRefreshToken.create({ data: token });\n },\n async findByHash(tokenHash) {\n const prisma = await getPrisma();\n return prisma.oAuthRefreshToken.findUnique({ where: { tokenHash } });\n },\n async hasSuccessor(tokenHash) {\n const prisma = await getPrisma();\n const successor = await prisma.oAuthRefreshToken.findFirst({\n where: { rotatedFrom: tokenHash },\n });\n return successor !== null;\n },\n async listFamily(userEmail, clientId) {\n const prisma = await getPrisma();\n return prisma.oAuthRefreshToken.findMany({ where: { userEmail, clientId } });\n },\n async revokeHashes(tokenHashes, at) {\n if (tokenHashes.length === 0) return;\n const prisma = await getPrisma();\n await prisma.oAuthRefreshToken.updateMany({\n where: { tokenHash: { in: [...tokenHashes] } },\n data: { revokedAt: at },\n });\n },\n async rotate(successor, parentHash, at) {\n const prisma = await getPrisma();\n return prisma.$transaction(async (tx) => {\n // CLAIM-ONCE. The `revokedAt: null` predicate is what makes this safe under\n // concurrency, and it is load-bearing rather than defensive: on Postgres's\n // default READ COMMITTED, a second transaction's `updateMany` blocks on the\n // row lock, then re-evaluates this WHERE against the COMMITTED row — which\n // now has a `revokedAt` — and reports 0 rows. So exactly one caller can ever\n // see count 1, and it is the only one that goes on to create a successor.\n // An unconditional `update` would let both through: two live successors of\n // one parent, and replay detection silently defeated (it waits for a third\n // use of the parent that now never comes).\n const { count } = await tx.oAuthRefreshToken.updateMany({\n where: { tokenHash: parentHash, revokedAt: null },\n data: { revokedAt: at },\n });\n // Lost the claim: write NOTHING. The zero-row update commits as the no-op\n // it is, so there is nothing to roll back.\n if (count !== 1) return false;\n // Same transaction as the claim, so a crash cannot leave a live parent AND\n // a live child either.\n await tx.oAuthRefreshToken.create({ data: successor });\n return true;\n });\n },\n async revokeLiveForClient(userEmail, clientId) {\n const prisma = await getPrisma();\n const { count } = await prisma.oAuthRefreshToken.updateMany({\n where: { userEmail, clientId, revokedAt: null },\n data: { revokedAt: new Date() },\n });\n return count;\n },\n };\n}\n\n/** The connection columns the account surface reads. */\nconst CONNECTION_SELECT = {\n oauthClientId: true,\n clientName: true,\n host: true,\n connectedAt: true,\n lastActiveAt: true,\n} as const;\n\nfunction connectionStore(getPrisma: McpOauthPrismaProvider): McpConnectionStore {\n return {\n async lastActiveAt(userId, oauthClientId) {\n const prisma = await getPrisma();\n const row = await prisma.mcpConnection.findUnique({\n where: { userId_oauthClientId: { userId, oauthClientId } },\n select: { lastActiveAt: true },\n });\n return row?.lastActiveAt ?? null;\n },\n async recordActivity({ userId, oauthClientId, clientName, host, at }) {\n const prisma = await getPrisma();\n await prisma.mcpConnection.upsert({\n where: { userId_oauthClientId: { userId, oauthClientId } },\n create: { userId, oauthClientId, clientName, host, connectedAt: at, lastActiveAt: at },\n // Never blank a known host on refresh — keep the existing attribution\n // when this grant cannot derive one.\n update: {\n clientName,\n lastActiveAt: at,\n revokedAt: null,\n ...(host ? { host } : {}),\n },\n });\n },\n async listActive(userId) {\n const prisma = await getPrisma();\n const rows = await prisma.mcpConnection.findMany({\n where: { userId, revokedAt: null },\n orderBy: { lastActiveAt: \"desc\" },\n select: { ...CONNECTION_SELECT },\n });\n return rows as unknown as StoredMcpConnection[];\n },\n revokeByHost: (userId, host) => revokeByHost(getPrisma, userId, host),\n announce: (userId, host) => announce(getPrisma, userId, host),\n };\n}\n\n/**\n * Disconnect one provider's connections — and ONLY that provider's.\n *\n * Every read and write is scoped by `userId`: a connection is per-user (an MCP\n * bearer is not tenant-scoped), so the user id IS the isolation here, and the\n * `id` list passed to the update comes from a query that already applied it.\n */\nasync function revokeByHost(\n getPrisma: McpOauthPrismaProvider,\n userId: string,\n host: string,\n): Promise<string[]> {\n const prisma = await getPrisma();\n const attributed = await prisma.mcpConnection.findMany({\n where: { userId, revokedAt: null, host },\n select: { id: true, oauthClientId: true },\n });\n // A legacy `host = null` row is claimed only when the provider has no row of\n // its own: pre-attribution connections must stay disconnectable, but a provider\n // that DID attribute can never revoke another assistant's row.\n const targets =\n attributed.length > 0\n ? attributed\n : await prisma.mcpConnection.findMany({\n where: { userId, revokedAt: null, host: null },\n select: { id: true, oauthClientId: true },\n });\n if (targets.length === 0) return [];\n await prisma.mcpConnection.updateMany({\n where: { id: { in: targets.map((row) => String(row.id)) } },\n data: { revokedAt: new Date() },\n });\n return targets.map((row) => String(row.oauthClientId));\n}\n\n/** A provider's self-report: refresh its own row, or claim the unattributed one. */\nasync function announce(\n getPrisma: McpOauthPrismaProvider,\n userId: string,\n host: string,\n): Promise<number> {\n const prisma = await getPrisma();\n const now = new Date();\n const refreshed = await prisma.mcpConnection.updateMany({\n where: { userId, revokedAt: null, host },\n data: { lastActiveAt: now, revokedAt: null },\n });\n if (refreshed.count > 0) return refreshed.count;\n\n // No row for this provider yet — attribute the just-connected one. Scoped by\n // user, so a self-report can never reach another account's connection.\n const candidate = await prisma.mcpConnection.findFirst({\n where: { userId, revokedAt: null, host: null },\n orderBy: { lastActiveAt: \"desc\" },\n select: { id: true },\n });\n if (!candidate) return 0;\n await prisma.mcpConnection.update({\n where: { id: candidate.id },\n data: { host, lastActiveAt: now, revokedAt: null },\n });\n return 1;\n}\n\n/** Every port, over one lazily-resolved Prisma client. */\nexport function createPrismaMcpStores(getPrisma: McpOauthPrismaProvider): McpOauthStores {\n return {\n clients: clientStore(getPrisma),\n refreshTokens: refreshTokenStore(getPrisma),\n connections: connectionStore(getPrisma),\n };\n}\n","import { providerForHostId, type AiProvider } from \"../guide\";\nimport type {\n McpConnectionStore,\n RefreshTokenStore,\n StoredMcpConnection,\n} from \"./stores\";\n\n/**\n * The account surface's connection OPERATIONS (12-48) — the half of the\n * `GET/DELETE /api/account/mcp-connections` endpoints that is contract rather\n * than host vocabulary.\n *\n * The ROUTE stays in the host on purpose: it mixes the host's session\n * resolution, its response envelope, its published plugin URLs and its logger,\n * and injecting all four here would make the config surface bigger than the\n * handler it replaces. What must NOT stay in each host is the disconnect's\n * both-halves rule, because getting it half right LOOKS right:\n *\n * `connections.revokeByHost` ends the connection rows and returns the OAuth\n * client ids behind them — and a host that stops there has revoked nothing that\n * matters. The assistant still holds a live refresh token for each of those\n * clients, rotates it on schedule, and the very next grant records fresh\n * activity: the card the user just disconnected lights green again on its own.\n * So the rule is one function: revoke the rows AND end every live refresh token\n * of each returned client, in the same call, with no way to import one half\n * without the other.\n *\n * Deliberately NOT invalidated here: the assistant's current ACCESS token.\n * Those are self-contained JWTs the server does not track; a just-disconnected\n * host keeps working for at most their TTL (15 minutes by default) and can then\n * obtain nothing further.\n */\n\n/** An active AI connection, narrowed for display. */\nexport interface AiConnectionSnapshot {\n oauthClientId: string;\n clientName: string | null;\n /** The provider this connection is attributed to (`null` = pre-attribution). */\n host: AiProvider | null;\n connectedAt: Date;\n lastActiveAt: Date;\n}\n\n/** The caller the operations act for — always the session's own user. */\nexport interface AiConnectionCaller {\n /** The host's user id — what `mcp_connections` rows are keyed by. */\n userId: string;\n /** The identity refresh tokens are bound to (the AS binds by email). */\n email: string;\n}\n\n/** What one disconnect actually ended, for the host's log and response. */\nexport interface AiDisconnectResult {\n /** OAuth client ids whose connection rows were revoked. */\n disconnectedClientIds: string[];\n /** Live refresh tokens ended across those clients — the half that cuts access. */\n revokedRefreshTokens: number;\n}\n\n/** Narrow a stored `host` string to a known provider, or `null`. */\nfunction asProvider(host: string | null): AiProvider | null {\n return host === null ? null : providerForHostId(host);\n}\n\n/**\n * A user's active connections, most-recently-active first, with the stored open\n * `host` string narrowed to the package's closed {@link AiProvider} union — the\n * store cannot know which assistants have screens, but the union is this\n * package's own vocabulary (`guide.ts`), so the narrowing lives beside it\n * rather than being re-derived in every host.\n */\nexport async function listAiConnections(\n connections: McpConnectionStore,\n userId: string,\n): Promise<AiConnectionSnapshot[]> {\n const rows: StoredMcpConnection[] = await connections.listActive(userId);\n return rows.map((row) => ({ ...row, host: asProvider(row.host) }));\n}\n\n/**\n * Disconnect one provider for this user — BOTH halves, atomically from the\n * caller's point of view (see the module doc for why one half alone is a\n * disconnect that undoes itself).\n *\n * Idempotent: disconnecting a provider that was never connected returns zero\n * counts rather than failing, so a double-click is harmless. Repeat calls also\n * report zero — `revokeLiveForClient` skips already-revoked tokens by contract.\n */\nexport async function disconnectAiHost(\n stores: { connections: McpConnectionStore; refreshTokens: RefreshTokenStore },\n caller: AiConnectionCaller,\n host: AiProvider,\n): Promise<AiDisconnectResult> {\n const disconnectedClientIds = await stores.connections.revokeByHost(caller.userId, host);\n const revoked = await Promise.all(\n disconnectedClientIds.map((clientId) =>\n stores.refreshTokens.revokeLiveForClient(caller.email, clientId),\n ),\n );\n return {\n disconnectedClientIds,\n revokedRefreshTokens: revoked.reduce((total, count) => total + count, 0),\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -2,7 +2,7 @@ import { ConfirmActionCopy } from '@12-apps/ui/copy';
2
2
  import { OnboardingStore, OnboardingStateSnapshot } from '@12-apps/onboarding';
3
3
  import { e as AiHostGuide, g as AiProvider, A as AiCapability, c as AiHostBrand } from '../guide-KQNcXlMG.js';
4
4
  export { b as AiConnectPromptSpec, d as AiHostConfigureStage, f as AiHostLink, h as aiConnectPrompt, p as providerForHostId } from '../guide-KQNcXlMG.js';
5
- export { P as PT_BR_AI_CAPABILITIES, a as PT_BR_AI_CONNECT_PROMPT, b as PT_BR_AI_HOST_GUIDES, c as PT_BR_AI_PERMISSION_MODEL } from '../pt-BR-e1RnV2v8.js';
5
+ export { A as AI_CAPABILITIES, a as AI_CONNECT_PROMPT, b as AI_HOST_GUIDES, c as AI_PERMISSION_MODEL, E as EN_US_AI_CAPABILITIES, d as EN_US_AI_CONNECT_PROMPT, e as EN_US_AI_HOST_GUIDES, f as EN_US_AI_PERMISSION_MODEL, P as PT_BR_AI_CAPABILITIES, g as PT_BR_AI_CONNECT_PROMPT, h as PT_BR_AI_HOST_GUIDES, i as PT_BR_AI_PERMISSION_MODEL } from '../locales-eKE_OJw4.js';
6
6
 
7
7
  /**
8
8
  * Every word the AI-integration screens render, as REQUIRED host config
@@ -440,4 +440,22 @@ declare function FeatureBadge({ icon, label, caption }: FeatureBadgeItem): React
440
440
  */
441
441
  declare const PT_BR_MCP_AI_COPY: McpAiCopy;
442
442
 
443
- export { AiCapabilities, type AiCapabilitiesCopy, AiCapability, type AiConnectGuideCopy, type AiConnection, type AiConnectionSummaryCopy, type AiFlowCopy, AiHostBrand, AiHostGuide, type AiHostSelectCopy, AiIntegrationOnboarding, type AiIntegrationOnboardingProps, AiLanding, type AiLandingCopy, type AiOnboardingCopy, AiProvider, AiStatusBoard, type AiStatusBoardCopy, type AiTrustPoint, CapabilityIcon, type DisconnectHandler, EndpointCopyBlock, FeatureBadge, type FeatureBadgeItem, HostBrandAvatar, HostConnectHeader, HostOpenButton, HostSelectStep, type HostStatus, HostStepList, type McpAiCopy, McpEndpointUrl, PT_BR_MCP_AI_COPY, PromptCopyBlock };
443
+ /**
444
+ * The en-US pack for the AI-integration screens — a NAMED constant a host
445
+ * passes by hand, never a default.
446
+ *
447
+ * `statusBoard.confirmAction` composes `@12-apps/ui`'s own English pack rather
448
+ * than restating it, exactly as the pt-BR side composes the Portuguese one.
449
+ *
450
+ * The assistant NAMES (Claude, ChatGPT, Codex) are products, not words: they
451
+ * are spelled the same in every language, and `hostLabel` arrives as an
452
+ * argument so a sentence can place it where its own grammar wants.
453
+ */
454
+ declare const EN_US_MCP_AI_COPY: McpAiCopy;
455
+
456
+ declare const MCP_AI_COPY: {
457
+ readonly "pt-BR": McpAiCopy;
458
+ readonly "en-US": McpAiCopy;
459
+ };
460
+
461
+ export { AiCapabilities, type AiCapabilitiesCopy, AiCapability, type AiConnectGuideCopy, type AiConnection, type AiConnectionSummaryCopy, type AiFlowCopy, AiHostBrand, AiHostGuide, type AiHostSelectCopy, AiIntegrationOnboarding, type AiIntegrationOnboardingProps, AiLanding, type AiLandingCopy, type AiOnboardingCopy, AiProvider, AiStatusBoard, type AiStatusBoardCopy, type AiTrustPoint, CapabilityIcon, type DisconnectHandler, EN_US_MCP_AI_COPY, EndpointCopyBlock, FeatureBadge, type FeatureBadgeItem, HostBrandAvatar, HostConnectHeader, HostOpenButton, HostSelectStep, type HostStatus, HostStepList, MCP_AI_COPY, type McpAiCopy, McpEndpointUrl, PT_BR_MCP_AI_COPY, PromptCopyBlock };
@@ -1,9 +1,17 @@
1
1
  import {
2
+ AI_CAPABILITIES,
3
+ AI_CONNECT_PROMPT,
4
+ AI_HOST_GUIDES,
5
+ AI_PERMISSION_MODEL,
6
+ EN_US_AI_CAPABILITIES,
7
+ EN_US_AI_CONNECT_PROMPT,
8
+ EN_US_AI_HOST_GUIDES,
9
+ EN_US_AI_PERMISSION_MODEL,
2
10
  PT_BR_AI_CAPABILITIES,
3
11
  PT_BR_AI_CONNECT_PROMPT,
4
12
  PT_BR_AI_HOST_GUIDES,
5
13
  PT_BR_AI_PERMISSION_MODEL
6
- } from "../chunk-HRAQKMDC.js";
14
+ } from "../chunk-WUNMAHQG.js";
7
15
  import {
8
16
  aiConnectPrompt,
9
17
  providerForHostId
@@ -1349,12 +1357,131 @@ var PT_BR_MCP_AI_COPY = {
1349
1357
  collapseLabel: "Ocultar"
1350
1358
  }
1351
1359
  };
1360
+
1361
+ // src/react/en-US.ts
1362
+ import { EN_US_CONFIRM_ACTION_COPY } from "@12-apps/ui/en-US";
1363
+ var EN_US_MCP_AI_COPY = {
1364
+ capabilities: {
1365
+ heading: "What the assistant does for you",
1366
+ subheading: "No spreadsheets, no clicking \u2014 just ask it in the chat."
1367
+ },
1368
+ landing: {
1369
+ eyebrow: "AI integration",
1370
+ // Three fragments the heading renders around an emphasised middle, so the
1371
+ // seam has to survive: "Connect / AI assistants / to your store".
1372
+ titleLead: "Connect",
1373
+ titleEmphasis: "AI assistants",
1374
+ titleTail: "to your store",
1375
+ lede: "Let Claude, ChatGPT and other assistants answer questions about your menu, stock and orders \u2014 and take actions for you, right in the chat. Securely, with nothing to install.",
1376
+ trust: [
1377
+ // The `id`s are the package's own and are NOT words: the component keys
1378
+ // its icons off them.
1379
+ {
1380
+ id: "login",
1381
+ label: "Uses your own login",
1382
+ caption: "No extra keys or credentials"
1383
+ },
1384
+ { id: "install", label: "Nothing to install", caption: "Connects in minutes" },
1385
+ {
1386
+ id: "permissions",
1387
+ label: "Only what you can do",
1388
+ caption: "Your permissions, nothing beyond them"
1389
+ },
1390
+ { id: "surface", label: "Browser or app", caption: "Claude, ChatGPT, Codex" }
1391
+ ],
1392
+ start: "Get started"
1393
+ },
1394
+ flow: {
1395
+ steps: {
1396
+ select: "Choose",
1397
+ copyUrl: "Copy URL",
1398
+ configure: "Configure",
1399
+ connect: "Connect",
1400
+ install: "Install",
1401
+ confirm: "Confirm"
1402
+ },
1403
+ back: "Back",
1404
+ next: "Next",
1405
+ advance: "Continue",
1406
+ finish: "Finish",
1407
+ copyUrl: "Copy URL",
1408
+ copied: "Copied",
1409
+ copyMessage: "Copy message",
1410
+ copyUrlTitle: "Copy your store's URL",
1411
+ urlCaption: "It is the only thing you paste into the assistant \u2014 copying it moves you to the next step.",
1412
+ promptCaption: "That is how it connects, identifies itself (Claude, ChatGPT\u2026) and how we record the connection.",
1413
+ askTitle: "Ask the assistant to connect",
1414
+ installBody: "Open your store's plugin, click Install and authorise access \u2014 no URL to copy, no credentials to generate.",
1415
+ installAction: "Install the store plugin",
1416
+ pasteTitle: "Paste this message into the assistant",
1417
+ pasteInstallCaption: "Paste it into the assistant so it connects, identifies itself and confirms access.",
1418
+ connectedTo: /* @__PURE__ */ __name((hostLabel2) => `${hostLabel2} is connected to your store.`, "connectedTo"),
1419
+ waitingTitle: "Waiting for the connection",
1420
+ waitingBody: /* @__PURE__ */ __name((hostLabel2) => `As soon as you authorise access in ${hostLabel2}, it appears here automatically.`, "waitingBody"),
1421
+ testNow: "Test it now"
1422
+ },
1423
+ statusBoard: {
1424
+ instructions: "Instructions",
1425
+ connected: "Connected",
1426
+ notConnected: "Not connected yet",
1427
+ connect: "Connect",
1428
+ disconnect: "Disconnect",
1429
+ disconnectTitle: "Disconnect this assistant?",
1430
+ disconnectBody: "It loses access to your store immediately. To use it again you will have to connect it afresh.",
1431
+ disconnectConfirm: "Disconnect",
1432
+ disconnectError: "Could not disconnect. Try again.",
1433
+ boardTitle: "Connected assistants",
1434
+ boardCaption: "Green are the ones already working with your store; red are the ones still to connect.",
1435
+ confirmAction: EN_US_CONFIRM_ACTION_COPY
1436
+ },
1437
+ summary: {
1438
+ activeNow: "active now",
1439
+ activeMinutes: /* @__PURE__ */ __name((minutes) => `active ${minutes} min ago`, "activeMinutes"),
1440
+ activeHours: /* @__PURE__ */ __name((hours) => `active ${hours} h ago`, "activeHours"),
1441
+ activeDays: /* @__PURE__ */ __name((days) => `active ${days} days ago`, "activeDays"),
1442
+ configured: "Integration configured",
1443
+ connectedSuffix: "Connected",
1444
+ connectedSeveral: /* @__PURE__ */ __name((names) => `${names} connected`, "connectedSeveral"),
1445
+ connectedGeneric: "AI connected"
1446
+ },
1447
+ hostSelect: {
1448
+ heading: "Choose your assistant",
1449
+ caption: "Pick where you use AI \u2014 the walkthrough is tailored to it."
1450
+ },
1451
+ connectGuide: {
1452
+ urlLabel: "Your store's server URL",
1453
+ urlHint: "Copy this URL and paste it into your assistant to connect it to the store.",
1454
+ // Ends mid-sentence: the screen renders a documentation link straight after.
1455
+ moreInfo: "For more, see the",
1456
+ connectOn: /* @__PURE__ */ __name((hostLabel2) => `Connect in ${hostLabel2}`, "connectOn")
1457
+ },
1458
+ onboarding: {
1459
+ title: "Connect AI assistants to your store",
1460
+ editLabel: "Connect AI",
1461
+ collapseLabel: "Hide"
1462
+ }
1463
+ };
1464
+
1465
+ // src/react/locales.ts
1466
+ var MCP_AI_COPY = {
1467
+ "pt-BR": PT_BR_MCP_AI_COPY,
1468
+ "en-US": EN_US_MCP_AI_COPY
1469
+ };
1352
1470
  export {
1471
+ AI_CAPABILITIES,
1472
+ AI_CONNECT_PROMPT,
1473
+ AI_HOST_GUIDES,
1474
+ AI_PERMISSION_MODEL,
1353
1475
  AiCapabilities,
1354
1476
  AiIntegrationOnboarding,
1355
1477
  AiLanding,
1356
1478
  AiStatusBoard,
1357
1479
  CapabilityIcon,
1480
+ EN_US_AI_CAPABILITIES,
1481
+ EN_US_AI_CONNECT_PROMPT,
1482
+ EN_US_AI_HOST_GUIDES,
1483
+ EN_US_AI_PERMISSION_MODEL,
1484
+ EN_US_MCP_AI_COPY,
1358
1485
  EndpointCopyBlock,
1359
1486
  FeatureBadge,
1360
1487
  HostBrandAvatar,
@@ -1362,6 +1489,7 @@ export {
1362
1489
  HostOpenButton,
1363
1490
  HostSelectStep,
1364
1491
  HostStepList,
1492
+ MCP_AI_COPY,
1365
1493
  McpEndpointUrl,
1366
1494
  PT_BR_AI_CAPABILITIES,
1367
1495
  PT_BR_AI_CONNECT_PROMPT,