@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.
package/src/en-US.ts ADDED
@@ -0,0 +1,204 @@
1
+ import type {
2
+ AiCapability,
3
+ AiConnectPromptSpec,
4
+ AiHostConfigureStage,
5
+ AiHostGuide,
6
+ AiPermissionModel,
7
+ } from "./guide";
8
+
9
+ /**
10
+ * The en-US pack for the AI-integration surface — NAMED constants a host passes
11
+ * by hand, never defaults.
12
+ *
13
+ * The guides are DATA as much as copy: which assistants are offered, their
14
+ * stage ids, their brands and their LINKS. Both halves travel together because
15
+ * a step label and the stage it labels are useless apart — and the parts that
16
+ * are not words do not change between languages:
17
+ *
18
+ * - `id` and `brand` are the package's own keys, matched on by the component;
19
+ * - the URLs point at Anthropic's and OpenAI's own pages;
20
+ * - the UI labels a reader must FIND in those products stay in the product's
21
+ * own English (`Add custom connector`, `Settings › Security and login`,
22
+ * `Sign in with …`), which is the same rule that keeps a vendor's field name
23
+ * untranslated everywhere else here.
24
+ *
25
+ * The pt-BR pack quotes those same labels in English for exactly this reason;
26
+ * what differs between the two packs is the instruction around them.
27
+ */
28
+
29
+ const CONNECTOR_TAIL: readonly string[] = [
30
+ "Leave OAuth Client ID and Client Secret blank — there are no credentials to generate: the store registers the connector automatically on first access.",
31
+ "Confirm and click Connect: the store's sign-in screen opens — sign in with YOUR own owner account and authorise the access.",
32
+ "Done: enable the connector in the chat so the assistant can query and operate your store.",
33
+ ];
34
+
35
+ /**
36
+ * ChatGPT's two-stage configuration (Developer mode is now required before a
37
+ * connector can be created). Stage 1 enables Developer mode in Security & login;
38
+ * stage 2 creates the connector and signs in — which registers the connection on
39
+ * the store side, so no prompt needs to be pasted afterwards.
40
+ */
41
+ function chatgptConfigureStages(
42
+ platformName: string,
43
+ ): readonly AiHostConfigureStage[] {
44
+ return [
45
+ {
46
+ id: "enable-dev-mode",
47
+ label: "enable developer mode",
48
+ link: {
49
+ url: "https://chatgpt.com/plugins#settings/Security",
50
+ label: "Open Security and login",
51
+ },
52
+ steps: [
53
+ "Turn on Developer mode under Settings › Security and login.",
54
+ ],
55
+ },
56
+ {
57
+ id: "configurar",
58
+ label: "configure",
59
+ link: {
60
+ url: "https://chatgpt.com/plugins#settings/Connectors?create-connector=true&redirectAfter=%2Fplugins",
61
+ label: "Create the connector",
62
+ },
63
+ steps: [
64
+ "That opens a popup to create a new plugin. Give it your store's name, and put the link you copied in the previous step in the MCP field.",
65
+ 'Tick "I understand and want to continue" — OpenAI has not reviewed this MCP server; they warn that sites may try to steal your data or push the model into harmful actions, including destroying data.',
66
+ `Click "Sign in with ${platformName}" and sign in with your owner account to authorise access. That is it: the connection is registered automatically.`,
67
+ ],
68
+ },
69
+ ];
70
+ }
71
+
72
+ /**
73
+ * The AI hosts a store owner can connect, in recommended order. Same OAuth flow
74
+ * everywhere (the host drives it) — only the menu path differs per app.
75
+ *
76
+ * A FUNCTION of the platform's name, because one step is not generic: the
77
+ * ChatGPT connector's consent screen shows an OAuth button labelled with
78
+ * whoever operates the server, and the owner is told which button to click. It
79
+ * used to name one particular STORE on one particular deployment — not even the
80
+ * product, a tenant of it — so every other adopter instructed its owners to
81
+ * click a button that does not exist.
82
+ */
83
+ export function EN_US_AI_HOST_GUIDES(platformName: string): readonly AiHostGuide[] {
84
+ const chatgptStages = chatgptConfigureStages(platformName);
85
+ return [
86
+ {
87
+ id: "claude",
88
+ label: "Claude.ai",
89
+ brand: "claude",
90
+ kind: "In the browser",
91
+ link: {
92
+ url: "https://claude.ai/new?modal=add-custom-connector#settings/customize-connectors",
93
+ label: "Open Claude's connectors",
94
+ },
95
+ docs: {
96
+ url: "https://support.anthropic.com/en/articles/11175166-how-do-i-connect-mcp-servers-to-claude-ai",
97
+ label: "Anthropic's own documentation — custom connectors",
98
+ },
99
+ steps: [
100
+ "Click the button above (or go to Settings › Customize › Connectors) and choose Add custom connector.",
101
+ "Name the connector (your store's name works) and paste your store's MCP server URL (copy it above) into the URL field.",
102
+ ...CONNECTOR_TAIL,
103
+ ],
104
+ },
105
+ {
106
+ id: "claude-desktop",
107
+ label: "Claude Desktop",
108
+ brand: "claude",
109
+ kind: "App (Windows/Mac)",
110
+ docs: {
111
+ url: "https://support.anthropic.com/en/articles/11175166-how-do-i-connect-mcp-servers-to-claude-ai",
112
+ label: "Anthropic's own documentation — custom connectors",
113
+ },
114
+ steps: [
115
+ "Open Claude Desktop and go to Settings (⚙️) › Connectors.",
116
+ "Click Add custom connector and paste your store's MCP server URL (copy it above).",
117
+ ...CONNECTOR_TAIL,
118
+ ],
119
+ },
120
+ {
121
+ id: "chatgpt",
122
+ label: "ChatGPT",
123
+ brand: "openai",
124
+ kind: "In the browser",
125
+ link: {
126
+ url: "https://chatgpt.com/plugins",
127
+ label: "Open ChatGPT's plugins",
128
+ },
129
+ docs: {
130
+ url: "https://developers.openai.com/apps-sdk/deploy/connect-chatgpt",
131
+ label: "OpenAI's own documentation — connecting an MCP server to ChatGPT",
132
+ },
133
+ configureStages: chatgptStages,
134
+ // Mirrors the flattened stage instructions so the MCP connect guide
135
+ // (`connectToChatGpt`) can never drift from what owners see in the wizard.
136
+ steps: chatgptStages.flatMap((stage) => stage.steps),
137
+ },
138
+ {
139
+ id: "codex",
140
+ label: "Codex",
141
+ brand: "openai",
142
+ kind: "Developer app / CLI",
143
+ link: {
144
+ url: "https://developers.openai.com/codex",
145
+ label: "Codex documentation",
146
+ },
147
+ docs: {
148
+ url: "https://developers.openai.com/apps-sdk/deploy/connect-chatgpt",
149
+ label: "OpenAI's own documentation — connecting an MCP server",
150
+ },
151
+ steps: [
152
+ "In Codex, open the MCP settings (Settings › MCP in the app, or the config file on the CLI).",
153
+ "Add an MCP server and paste your store's MCP server URL (copy it above) as a remote (HTTP) connector.",
154
+ ...CONNECTOR_TAIL,
155
+ ],
156
+ },
157
+ ];
158
+ }
159
+
160
+ export const EN_US_AI_CAPABILITIES: readonly AiCapability[] = [
161
+ // The `detail` of each is a QUESTION a reader could paste verbatim, so it is
162
+ // written as one rather than described.
163
+ {
164
+ id: "orders",
165
+ title: "Follow your orders",
166
+ detail: '"Which orders came in today?"',
167
+ },
168
+ {
169
+ id: "inventory",
170
+ title: "Keep on top of stock",
171
+ detail: '"How much of product X is left? Record 20 units received."',
172
+ },
173
+ {
174
+ id: "catalog",
175
+ title: "Manage the catalog",
176
+ detail: "Create and edit products and categories by chatting.",
177
+ },
178
+ {
179
+ id: "sales",
180
+ title: "Understand your sales",
181
+ detail: '"What was this week\'s revenue?"',
182
+ },
183
+ ];
184
+
185
+ /**
186
+ * The permission model in one line, shown prominently: the assistant acts AS
187
+ * the signed-in owner (auth-passthrough) — it can do exactly what the owner
188
+ * can, nothing more, and no extra credential/API key is ever created.
189
+ */
190
+ export const EN_US_AI_PERMISSION_MODEL: AiPermissionModel =
191
+ "The assistant acts on your behalf with exactly your permissions: it can do what you can do in your store — and nothing beyond that. There is no key or extra credential to create; the authorisation uses your own login.";
192
+
193
+ /** The en-US paste-in prompt, built from the host's own tool names. */
194
+ export function EN_US_AI_CONNECT_PROMPT(spec: AiConnectPromptSpec): string {
195
+ // The TOOL NAMES are the host's own identifiers and are interpolated, never
196
+ // translated: the assistant has to call them by the name they are registered
197
+ // under, so a translated verb here is a prompt that does nothing.
198
+ return (
199
+ "You now have access to my store's MCP connector. Do the following, in order:\n" +
200
+ `1) Run the ${spec.announceTool} tool, saying which assistant you are (host: "chatgpt", "claude" or "codex"), to register the connection with my store.\n` +
201
+ `2) Run the ${spec.probeTool} tool to confirm access to ${spec.probeSubject}.\n` +
202
+ `If you need the store identifier, ask me for the ${spec.identifierName}.`
203
+ );
204
+ }
package/src/index.ts CHANGED
@@ -104,3 +104,15 @@ export {
104
104
  PT_BR_AI_HOST_GUIDES,
105
105
  PT_BR_AI_PERMISSION_MODEL,
106
106
  } from "./pt-BR";
107
+ export {
108
+ EN_US_AI_CAPABILITIES,
109
+ EN_US_AI_CONNECT_PROMPT,
110
+ EN_US_AI_HOST_GUIDES,
111
+ EN_US_AI_PERMISSION_MODEL,
112
+ } from "./en-US";
113
+ export {
114
+ AI_CAPABILITIES,
115
+ AI_CONNECT_PROMPT,
116
+ AI_HOST_GUIDES,
117
+ AI_PERMISSION_MODEL,
118
+ } from "./locales";
package/src/locales.ts ADDED
@@ -0,0 +1,58 @@
1
+ import {
2
+ EN_US_AI_CAPABILITIES,
3
+ EN_US_AI_CONNECT_PROMPT,
4
+ EN_US_AI_HOST_GUIDES,
5
+ EN_US_AI_PERMISSION_MODEL,
6
+ } from "./en-US";
7
+ import type {
8
+ AiCapability,
9
+ AiConnectPromptSpec,
10
+ AiHostGuide,
11
+ AiPermissionModel,
12
+ } from "./guide";
13
+ import {
14
+ PT_BR_AI_CAPABILITIES,
15
+ PT_BR_AI_CONNECT_PROMPT,
16
+ PT_BR_AI_HOST_GUIDES,
17
+ PT_BR_AI_PERMISSION_MODEL,
18
+ } from "./pt-BR";
19
+
20
+ /**
21
+ * The AI-integration surface in both languages, keyed by tag — what a host
22
+ * hands to `@12-apps/i18n` when the reader's language is a property of the
23
+ * request rather than of the deployment.
24
+ *
25
+ * Two of these are FUNCTIONS rather than tables, and stay so:
26
+ *
27
+ * - `AI_HOST_GUIDES` takes the platform's name, because the ChatGPT consent
28
+ * screen shows an OAuth button labelled with whoever operates the server and
29
+ * the owner is told which button to click. It once named one particular
30
+ * tenant of one deployment, so every other adopter instructed its owners to
31
+ * click a button that does not exist.
32
+ * - `AI_CONNECT_PROMPT` takes the host's own tool names, which the assistant
33
+ * must call by the name they are registered under.
34
+ *
35
+ * `LocalePack` is mirrored here rather than imported so the package stays
36
+ * liftable into a repo that has never heard of `@12-apps/i18n`.
37
+ */
38
+ type LocalePack<T> = { readonly "pt-BR": T; readonly "en-US": T };
39
+
40
+ export const AI_HOST_GUIDES = {
41
+ "pt-BR": PT_BR_AI_HOST_GUIDES,
42
+ "en-US": EN_US_AI_HOST_GUIDES,
43
+ } as const satisfies LocalePack<(platformName: string) => readonly AiHostGuide[]>;
44
+
45
+ export const AI_CONNECT_PROMPT = {
46
+ "pt-BR": PT_BR_AI_CONNECT_PROMPT,
47
+ "en-US": EN_US_AI_CONNECT_PROMPT,
48
+ } as const satisfies LocalePack<(spec: AiConnectPromptSpec) => string>;
49
+
50
+ export const AI_CAPABILITIES = {
51
+ "pt-BR": PT_BR_AI_CAPABILITIES,
52
+ "en-US": EN_US_AI_CAPABILITIES,
53
+ } as const satisfies LocalePack<readonly AiCapability[]>;
54
+
55
+ export const AI_PERMISSION_MODEL = {
56
+ "pt-BR": PT_BR_AI_PERMISSION_MODEL,
57
+ "en-US": EN_US_AI_PERMISSION_MODEL,
58
+ } as const satisfies LocalePack<AiPermissionModel>;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * `@12-apps/mcp/manifest` — the SHARED wiring manifest.
3
+ *
4
+ * Identity, the Prisma contribution (the three tables behind the
5
+ * authorization server) and the runtime inventory: `http` on the server.
6
+ *
7
+ * ON THE `db` DECLARATION — the reason this manifest exists at all.
8
+ *
9
+ * This package ships `prisma/mcp.prisma` and a migration beside it, and until
10
+ * now nothing said so in a form a host assembler could read. The origin
11
+ * host's assembler discovers partials in two steps: a package that carries
12
+ * `"wiring": { "db": ... }` in its package.json is taken at its word, and a
13
+ * package that carries nothing falls back to a STRUCTURAL scan — every
14
+ * `prisma/*.prisma` under the package root is treated as a partial. So three
15
+ * tables reach somebody's database because a `readdir` found them, not
16
+ * because this package said they should. `@12-apps/notifications`' manifest
17
+ * closed exactly this gap for its four models and the anti-pattern audit
18
+ * names it directly; declaring changes no assembler behaviour (the
19
+ * declaration is read where the scan used to run) and closes the one case
20
+ * where composition was happening by accident.
21
+ *
22
+ * The mirror is what makes the declaration reachable: host assemblers are
23
+ * plain Node reading `node_modules` and cannot execute this TypeScript, so
24
+ * the contribution is repeated under `package.json` `"wiring": { "db": … }`
25
+ * and `assertDbMirror` pins the two together in this package's own test run.
26
+ *
27
+ * `composed`, not `isolated`, and the choice is forced. An isolated stack
28
+ * needs models carrying no relation into host tables — true of the three
29
+ * here as SHIPPED (`user_id` and `user_email` are deliberately by-value
30
+ * scalars, see the partial's header) — but the host is invited to add the FK
31
+ * in its own migration, and the origin host's is `ON DELETE CASCADE`. A
32
+ * package cannot declare isolation for models whose adopters relate them
33
+ * into their own account tables.
34
+ *
35
+ * ## THE NARROWINGS, each deliberate
36
+ *
37
+ * - **No `mcp` capability.** This package IS the MCP runtime — the
38
+ * OpenAPI→tools generator, the registry, the JSON-RPC transport, the
39
+ * coverage gate. It advertises no tools of its own, and a manifest that
40
+ * declared any would be the runtime describing itself to itself.
41
+ * - **No `permissions`.** Authorization here is the OAuth scope set
42
+ * (`MCP_SUPPORTED_SCOPES`) plus whatever the host's own RBAC says about
43
+ * the proxied endpoint — the point of bearer passthrough is that an agent
44
+ * inherits the caller's permissions rather than holding its own. There is
45
+ * no id for this package to contribute.
46
+ * - **No `web` inventory**, though `./react` ships the whole AI-connect
47
+ * onboarding flow. A `surface` contribution is a `createWeb*` FACTORY —
48
+ * one config object in, an object of component types out, memoised once by
49
+ * the binder. `./react` has no such factory: it exports components a host
50
+ * mounts with its own props (`AiIntegrationOnboarding` takes the store,
51
+ * the endpoint URL and the live connection at the call site). Inventing a
52
+ * factory here to have something to declare would freeze a props table
53
+ * three hosts pass differently, which is the opposite of what a surface
54
+ * contribution is for. When the flow grows a real bound surface, the
55
+ * inventory grows with it.
56
+ * - **No `env`.** The signing-key variables (`DEFAULT_SIGNING_KEY_ENV`,
57
+ * `DEFAULT_SIGNING_KEY_ID_ENV`) and `trustedOriginsFromEnv` are NAMES this
58
+ * package exports for a host to read `process.env` with; the package reads
59
+ * nothing itself, and the names are overridable per call. Declaring them
60
+ * would oblige a host to answer for variables it may legitimately have
61
+ * spelled differently.
62
+ * - **No `e2e`.** This package packages no journeys.
63
+ * - **No `jobs`.** Nothing here sweeps: authorization codes are stateless
64
+ * signed blobs (the partial's header says so — there is no `oauth_codes`
65
+ * table and nothing to expire), and refresh-token revocation happens on
66
+ * the rotation path rather than on a clock.
67
+ *
68
+ * `@12-apps/wiring` is a TYPE-ONLY devDependency (the report-builder move):
69
+ * the manifest is a plain `satisfies`-checked value, and the producer
70
+ * factories' runtime assertions run in this package's own test suite.
71
+ */
72
+
73
+ import type { PackageManifest } from "@12-apps/wiring";
74
+
75
+ export const mcpManifest = {
76
+ name: "@12-apps/mcp",
77
+ contract: 1,
78
+ db: { partial: "prisma/mcp.prisma", migrations: "prisma/migrations" },
79
+ /**
80
+ * A refused token grant, an unresolvable signing key or a rejected
81
+ * redirect URI files under `mcp` rather than under whichever host mounted
82
+ * the authorization server. Mandatory for runtime manifests since wiring
83
+ * 1.3.0, and this is the surface that most needs it: every failure here is
84
+ * a caller who cannot connect, reported to them as an opaque OAuth error
85
+ * code by specification.
86
+ */
87
+ observability: { namespace: "mcp" },
88
+ server: ["http"],
89
+ } as const satisfies PackageManifest;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * `@12-apps/mcp/manifest/server` — the server capabilities.
3
+ *
4
+ * `http.create` wraps `createApiMcpOauth` in a WIRE VIEW, and the reason is
5
+ * the shape of an OAuth answer. `McpOauthRoute.handle` takes a Fetch
6
+ * `Request` and returns a Fetch `Response` — not a `{ status, body }` pair —
7
+ * because every endpoint here answers something the JSON pair cannot say: a
8
+ * 302 whose `Location` IS the payload, a form-encoded exchange answering
9
+ * RFC 6749 §5.1/§5.2 with its own cache headers, a JWKS with a
10
+ * `public, max-age=300`, an RFC 8414/9728 document. `create-api-mcp-oauth`'s
11
+ * own docstring says a wrapper would only break those. So the view answers
12
+ * the contract's RAW half (`{ response }`, wiring 1.9.0), which exists for
13
+ * exactly this, and the descriptors stay untouched.
14
+ *
15
+ * ## THE RAW REQUEST IS REQUIRED, and the view says so out loud
16
+ *
17
+ * The contract obliges an adapter to fill `params`/`query`/`body` and lets it
18
+ * fill `request`. Every handler here needs the whole request — the exact URL
19
+ * (redirect_uri echo, PKCE parameters, the `Host` an issuer is derived
20
+ * from), the form body byte-for-byte, the cookie header the session is read
21
+ * off. So a missing `request` is refused loudly at the first call rather
22
+ * than silently producing an authorization server that mints codes for the
23
+ * wrong origin. `@12-apps/storage`'s view takes the same posture for the
24
+ * same reason.
25
+ *
26
+ * ## THE MOUNT IS THE ORIGIN ROOT
27
+ *
28
+ * `McpOauthRoute.path` is absolute from the origin root — `.well-known/*`
29
+ * cannot live under a prefix (RFC 8615), and a connector reads those
30
+ * documents before it has ever spoken to us. The consumer joins
31
+ * `mountPath + path`, so the ONLY correct binding is `mountPath: "/"`; the
32
+ * paths themselves stay configurable per host through `config.paths`, which
33
+ * is where a host that serves `authorize` somewhere else says so. Binding
34
+ * this surface under a prefix would move the discovery documents off the
35
+ * two URLs the specification reserves, and the symptom is a connector that
36
+ * cannot find the authorization server at all.
37
+ *
38
+ * ## EVERY ROUTE IS `public`, and that is a decision
39
+ *
40
+ * Not "unguarded": these six ARE the authentication, so a host RBAC gate in
41
+ * front of them would demand a token from the endpoint that issues tokens.
42
+ * `authorize` reads the host's cookie session itself (`config.resolveSession`)
43
+ * and sends an anonymous caller through the host's sign-in flow; `token` and
44
+ * `register` authenticate the CLIENT per RFC; the three documents are public
45
+ * by specification. `public` is also the contract's only kind that forbids a
46
+ * `permission` while allowing the routes to be reached anonymously — which
47
+ * is precisely the property that has to hold here.
48
+ *
49
+ * `@12-apps/wiring` is a TYPE-ONLY devDependency — see `./index`.
50
+ */
51
+
52
+ import type { AnyServerManifest, WireRequest, WireRouteAnswer } from "@12-apps/wiring";
53
+
54
+ import {
55
+ createApiMcpOauth,
56
+ type ApiMcpOauth,
57
+ type McpOauthConfig,
58
+ type McpOauthRoute,
59
+ } from "../oauth";
60
+
61
+ /** One `McpOauthRoute` as the wiring contract reads it. */
62
+ function asWireRoute(route: McpOauthRoute): {
63
+ method: McpOauthRoute["method"];
64
+ path: string;
65
+ kind: "public";
66
+ handle(request: WireRequest): Promise<WireRouteAnswer>;
67
+ } {
68
+ return {
69
+ method: route.method,
70
+ path: route.path,
71
+ kind: "public",
72
+ handle: async (request) => {
73
+ if (!request.request) {
74
+ throw new Error(
75
+ "@12-apps/mcp/oauth needs the raw request — bind an adapter that forwards it.",
76
+ );
77
+ }
78
+ return { response: await route.handle(request.request) };
79
+ },
80
+ };
81
+ }
82
+
83
+ /** `createApiMcpOauth`, its routes re-shaped for the aggregate. */
84
+ export function createWireApiMcpOauth(
85
+ config: McpOauthConfig,
86
+ ): Omit<ApiMcpOauth, "routes"> & { routes: ReturnType<typeof asWireRoute>[] } {
87
+ const api = createApiMcpOauth(config);
88
+ return { ...api, routes: api.routes.map(asWireRoute) };
89
+ }
90
+
91
+ export const mcpServerManifest = {
92
+ name: "@12-apps/mcp",
93
+ http: { create: createWireApiMcpOauth },
94
+ } as const satisfies AnyServerManifest;
@@ -0,0 +1,125 @@
1
+ import { EN_US_CONFIRM_ACTION_COPY } from "@12-apps/ui/en-US";
2
+
3
+ import type { McpAiCopy } from "./copy";
4
+
5
+ /**
6
+ * The en-US pack for the AI-integration screens — a NAMED constant a host
7
+ * passes by hand, never a default.
8
+ *
9
+ * `statusBoard.confirmAction` composes `@12-apps/ui`'s own English pack rather
10
+ * than restating it, exactly as the pt-BR side composes the Portuguese one.
11
+ *
12
+ * The assistant NAMES (Claude, ChatGPT, Codex) are products, not words: they
13
+ * are spelled the same in every language, and `hostLabel` arrives as an
14
+ * argument so a sentence can place it where its own grammar wants.
15
+ */
16
+ export const EN_US_MCP_AI_COPY: McpAiCopy = {
17
+ capabilities: {
18
+ heading: "What the assistant does for you",
19
+ subheading: "No spreadsheets, no clicking — just ask it in the chat.",
20
+ },
21
+ landing: {
22
+ eyebrow: "AI integration",
23
+ // Three fragments the heading renders around an emphasised middle, so the
24
+ // seam has to survive: "Connect / AI assistants / to your store".
25
+ titleLead: "Connect",
26
+ titleEmphasis: "AI assistants",
27
+ titleTail: "to your store",
28
+ lede:
29
+ "Let Claude, ChatGPT and other assistants answer questions about your menu, stock and " +
30
+ "orders — and take actions for you, right in the chat. Securely, with nothing to install.",
31
+ trust: [
32
+ // The `id`s are the package's own and are NOT words: the component keys
33
+ // its icons off them.
34
+ {
35
+ id: "login",
36
+ label: "Uses your own login",
37
+ caption: "No extra keys or credentials",
38
+ },
39
+ { id: "install", label: "Nothing to install", caption: "Connects in minutes" },
40
+ {
41
+ id: "permissions",
42
+ label: "Only what you can do",
43
+ caption: "Your permissions, nothing beyond them",
44
+ },
45
+ { id: "surface", label: "Browser or app", caption: "Claude, ChatGPT, Codex" },
46
+ ],
47
+ start: "Get started",
48
+ },
49
+ flow: {
50
+ steps: {
51
+ select: "Choose",
52
+ copyUrl: "Copy URL",
53
+ configure: "Configure",
54
+ connect: "Connect",
55
+ install: "Install",
56
+ confirm: "Confirm",
57
+ },
58
+ back: "Back",
59
+ next: "Next",
60
+ advance: "Continue",
61
+ finish: "Finish",
62
+ copyUrl: "Copy URL",
63
+ copied: "Copied",
64
+ copyMessage: "Copy message",
65
+ copyUrlTitle: "Copy your store's URL",
66
+ urlCaption:
67
+ "It is the only thing you paste into the assistant — copying it moves you to the next step.",
68
+ promptCaption:
69
+ "That is how it connects, identifies itself (Claude, ChatGPT…) and how we record the connection.",
70
+ askTitle: "Ask the assistant to connect",
71
+ installBody:
72
+ "Open your store's plugin, click Install and authorise access — no URL to copy, no credentials to generate.",
73
+ installAction: "Install the store plugin",
74
+ pasteTitle: "Paste this message into the assistant",
75
+ pasteInstallCaption:
76
+ "Paste it into the assistant so it connects, identifies itself and confirms access.",
77
+ connectedTo: (hostLabel) => `${hostLabel} is connected to your store.`,
78
+ waitingTitle: "Waiting for the connection",
79
+ waitingBody: (hostLabel) =>
80
+ `As soon as you authorise access in ${hostLabel}, it appears here automatically.`,
81
+ testNow: "Test it now",
82
+ },
83
+ statusBoard: {
84
+ instructions: "Instructions",
85
+ connected: "Connected",
86
+ notConnected: "Not connected yet",
87
+ connect: "Connect",
88
+ disconnect: "Disconnect",
89
+ disconnectTitle: "Disconnect this assistant?",
90
+ disconnectBody:
91
+ "It loses access to your store immediately. To use it again you will have to connect it afresh.",
92
+ disconnectConfirm: "Disconnect",
93
+ disconnectError: "Could not disconnect. Try again.",
94
+ boardTitle: "Connected assistants",
95
+ boardCaption:
96
+ "Green are the ones already working with your store; red are the ones still to connect.",
97
+ confirmAction: EN_US_CONFIRM_ACTION_COPY,
98
+ },
99
+ summary: {
100
+ activeNow: "active now",
101
+ activeMinutes: (minutes) => `active ${minutes} min ago`,
102
+ activeHours: (hours) => `active ${hours} h ago`,
103
+ activeDays: (days) => `active ${days} days ago`,
104
+ configured: "Integration configured",
105
+ connectedSuffix: "Connected",
106
+ connectedSeveral: (names) => `${names} connected`,
107
+ connectedGeneric: "AI connected",
108
+ },
109
+ hostSelect: {
110
+ heading: "Choose your assistant",
111
+ caption: "Pick where you use AI — the walkthrough is tailored to it.",
112
+ },
113
+ connectGuide: {
114
+ urlLabel: "Your store's server URL",
115
+ urlHint: "Copy this URL and paste it into your assistant to connect it to the store.",
116
+ // Ends mid-sentence: the screen renders a documentation link straight after.
117
+ moreInfo: "For more, see the",
118
+ connectOn: (hostLabel) => `Connect in ${hostLabel}`,
119
+ },
120
+ onboarding: {
121
+ title: "Connect AI assistants to your store",
122
+ editLabel: "Connect AI",
123
+ collapseLabel: "Hide",
124
+ },
125
+ };
@@ -47,6 +47,22 @@ export {
47
47
  PT_BR_AI_PERMISSION_MODEL,
48
48
  } from "../pt-BR";
49
49
 
50
+ // The English twins and the tag-keyed records, re-exported beside them so a
51
+ // screen reaching for the AI surface finds all of it on one subpath — which is
52
+ // how the pt-BR names have always been reachable from here.
53
+ export {
54
+ EN_US_AI_CAPABILITIES,
55
+ EN_US_AI_CONNECT_PROMPT,
56
+ EN_US_AI_HOST_GUIDES,
57
+ EN_US_AI_PERMISSION_MODEL,
58
+ } from "../en-US";
59
+ export {
60
+ AI_CAPABILITIES,
61
+ AI_CONNECT_PROMPT,
62
+ AI_HOST_GUIDES,
63
+ AI_PERMISSION_MODEL,
64
+ } from "../locales";
65
+
50
66
  export type {
51
67
  AiCapabilitiesCopy,
52
68
  AiConnectGuideCopy,
@@ -60,3 +76,5 @@ export type {
60
76
  McpAiCopy,
61
77
  } from "./copy";
62
78
  export { PT_BR_MCP_AI_COPY } from "./pt-BR";
79
+ export { EN_US_MCP_AI_COPY } from "./en-US";
80
+ export { MCP_AI_COPY } from "./locales";
@@ -0,0 +1,17 @@
1
+ import type { McpAiCopy } from "./copy";
2
+ import { EN_US_MCP_AI_COPY } from "./en-US";
3
+ import { PT_BR_MCP_AI_COPY } from "./pt-BR";
4
+
5
+ /**
6
+ * The AI-integration screens in both languages, keyed by tag.
7
+ *
8
+ * `LocalePack` is mirrored here rather than imported so the package stays
9
+ * liftable into a repo that has never heard of `@12-apps/i18n`. The named
10
+ * single-language packs stay exported and unchanged.
11
+ */
12
+ type LocalePack<T> = { readonly "pt-BR": T; readonly "en-US": T };
13
+
14
+ export const MCP_AI_COPY = {
15
+ "pt-BR": PT_BR_MCP_AI_COPY,
16
+ "en-US": EN_US_MCP_AI_COPY,
17
+ } as const satisfies LocalePack<McpAiCopy>;