Skip to content

Commit 8e2b77f

Browse files
committed
es skills
1 parent 85e7984 commit 8e2b77f

180 files changed

Lines changed: 26387 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# This file belongs in commercetools-demo/skills at .github/workflows/dispatch-harness.yml
2+
# It dispatches the skills-test-harness to validate or publish whenever a skill changes.
3+
4+
name: Dispatch to test harness
5+
6+
on:
7+
push:
8+
paths:
9+
# Only mature skills with populated references are validated by the harness.
10+
# When a scaffolded skill (commerce-patterns, mc-app, connect, extensibility,
11+
# integrations, greenfield, migration, composable-patterns) is populated,
12+
# add its path here.
13+
- 'skills/commercetools-storefront/**'
14+
- '.github/workflows/dispatch-harness.yml'
15+
workflow_dispatch:
16+
inputs:
17+
skill:
18+
description: 'Which skill to dispatch'
19+
required: true
20+
type: choice
21+
options: [b2c, b2b, both]
22+
23+
jobs:
24+
dispatch:
25+
runs-on: ubuntu-latest
26+
steps:
27+
- uses: actions/checkout@v4
28+
with:
29+
fetch-depth: 2
30+
31+
- name: Detect changed skills
32+
id: changes
33+
if: github.event_name == 'push'
34+
uses: dorny/paths-filter@v3
35+
with:
36+
filters: |
37+
b2c:
38+
- 'skills/commercetools-storefront/references/b2c/**'
39+
b2b:
40+
- 'skills/commercetools-storefront/references/b2b/**'
41+
42+
- name: Determine event suffix (validate vs publish)
43+
id: suffix
44+
run: |
45+
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
46+
echo "value=publish" >> "$GITHUB_OUTPUT"
47+
else
48+
echo "value=validate" >> "$GITHUB_OUTPUT"
49+
fi
50+
51+
- name: Mint App token for harness dispatch
52+
id: app-token
53+
uses: actions/create-github-app-token@v1
54+
with:
55+
app-id: ${{ secrets.HARNESS_APP_ID }}
56+
private-key: ${{ secrets.HARNESS_APP_PRIVATE_KEY }}
57+
owner: commercetools-demo
58+
repositories: skills-test-harness
59+
60+
- name: Extract harness instructions from commit message
61+
id: harness-block
62+
run: |
63+
# Extract content between [harness] and [/harness] markers in the commit message.
64+
# Usage in a commit message:
65+
#
66+
# Add new promotions reference
67+
#
68+
# [harness]
69+
# Also implement the promotions feature described in references/promotions.md
70+
# [/harness]
71+
COMMIT_BODY=$(git log -1 --format='%B')
72+
echo "--- Commit message ---"
73+
echo "$COMMIT_BODY"
74+
echo "--- End ---"
75+
EXTRA=$(printf '%s' "$COMMIT_BODY" | awk '/^\[harness\]/{found=1; next} /^\[\/harness\]/{found=0} found{print}')
76+
# Store safely — no heredoc needed since we write to a file
77+
printf '%s' "$EXTRA" > .harness-extra.txt
78+
if [ -s .harness-extra.txt ]; then
79+
echo "has_extra=true" >> "$GITHUB_OUTPUT"
80+
echo "Harness instructions extracted ($(wc -c < .harness-extra.txt) chars)"
81+
else
82+
echo "has_extra=false" >> "$GITHUB_OUTPUT"
83+
echo "No [harness] block found in commit message"
84+
fi
85+
86+
- name: Dispatch b2c
87+
if: |
88+
(github.event_name == 'push' && steps.changes.outputs.b2c == 'true') ||
89+
(github.event_name == 'workflow_dispatch' && (inputs.skill == 'b2c' || inputs.skill == 'both'))
90+
env:
91+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
92+
run: |
93+
EXTRA=$(cat .harness-extra.txt)
94+
PAYLOAD=$(jq -n \
95+
--arg ref "${{ github.ref_name }}" \
96+
--arg sha "${{ github.sha }}" \
97+
--arg actor "${{ github.actor }}" \
98+
--arg extra "$EXTRA" \
99+
'{skills_repo_ref:$ref,skills_repo_sha:$sha,trigger_actor:$actor,extra_instructions:$extra}')
100+
gh api repos/commercetools-demo/skills-test-harness/dispatches \
101+
--input - <<EOF
102+
{"event_type":"b2c-${{ steps.suffix.outputs.value }}","client_payload":$PAYLOAD}
103+
EOF
104+
105+
- name: Dispatch b2b
106+
if: |
107+
(github.event_name == 'push' && steps.changes.outputs.b2b == 'true') ||
108+
(github.event_name == 'workflow_dispatch' && (inputs.skill == 'b2b' || inputs.skill == 'both'))
109+
env:
110+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
111+
run: |
112+
EXTRA=$(cat .harness-extra.txt)
113+
PAYLOAD=$(jq -n \
114+
--arg ref "${{ github.ref_name }}" \
115+
--arg sha "${{ github.sha }}" \
116+
--arg actor "${{ github.actor }}" \
117+
--arg extra "$EXTRA" \
118+
'{skills_repo_ref:$ref,skills_repo_sha:$sha,trigger_actor:$actor,extra_instructions:$extra}')
119+
gh api repos/commercetools-demo/skills-test-harness/dispatches \
120+
--input - <<EOF
121+
{"event_type":"b2b-${{ steps.suffix.outputs.value }}","client_payload":$PAYLOAD}
122+
EOF

.mcp.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"mcpServers": {
3+
"commercetools-knowledge": {
4+
"type": "http",
5+
"url": "https://docs.commercetools.com/apis/mcp"
6+
},
7+
"commerce-mcp": {
8+
"type": "stdio",
9+
"command": "npx",
10+
"args": ["-y", "@commercetools/commerce-mcp", "--tools=all", "--dynamicToolLoadingThreshold=650"]
11+
}
12+
}
13+
}

CLAUDE.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# commercetools Skills — Routing Index
2+
3+
Skills are loaded on demand. Load the skill whose description best matches the current task.
4+
5+
## Skill Routing
6+
7+
| When the task involves… | Load skill |
8+
|-------------------------|------------|
9+
| SDK setup, ClientBuilder, platform API, product/customer/order data model, query predicates, rate limits | `skills/commercetools-platform/SKILL.md` |
10+
| Storefront BFF patterns, JWT sessions, ct client singleton, Next.js deploy, performance | `skills/commercetools-storefront/SKILL.md` |
11+
| B2C storefront: anonymous cart merge, customer auth, locale routing, product listing, PDP, navigation, UI components | `skills/commercetools-storefront/SKILL.md` |
12+
| B2B storefront: business units, as-associate API, quotes, approval workflows, purchase lists, recurring orders | `skills/commercetools-storefront/SKILL.md` |
13+
| CSR impersonation, dual session, price override, superuser mode | `skills/commercetools-storefront/SKILL.md` |
14+
| Buy Online Pick Up In Store, store channels, inventory availability | `skills/commercetools-storefront/SKILL.md` |
15+
| Product bundles, parent/child cart items, bundle pricing | `skills/commercetools-storefront/SKILL.md` |
16+
| Product discounts, cart discounts, discount codes, promotions | `skills/commercetools-storefront/SKILL.md` |
17+
| Next.js image optimisation, metadata, error boundaries, App Router conventions | `skills/commercetools-storefront/SKILL.md` |
18+
| ct Checkout product SDK, payment-only mode, hosted checkout, PSP connectors (Stripe, Adyen, Mollie) | `skills/commercetools-checkout/SKILL.md` |
19+
| Pricing models, discount stacking, shipping predicates, tax modes, catalog import | `skills/commercetools-commerce-patterns/SKILL.md` |
20+
| Custom Merchant Center apps, Application Kit, custom views, custom panels, ui-kit | `skills/commercetools-merchant-center-app/SKILL.md` |
21+
| Connect runtime, service/job/event apps, connector development, Connect CLI | `skills/commercetools-connect/SKILL.md` |
22+
| Integrating ct with CMS (Contentful, Contentstack), OMS, PIM (Akeneo), ERP, Algolia | `skills/commercetools-integrations/SKILL.md` |
23+
| New ct implementation from scratch, project bootstrap, greenfield lifecycle | `skills/commercetools-greenfield/SKILL.md` |
24+
| Migrating from Shopify, Salesforce B2C Commerce, VTEX, or Magento to ct | `skills/commercetools-migration/SKILL.md` |
25+
| Using ct for a single module (cart-only, order ledger, B2B accounts), external prices, product stubs | `skills/commercetools-composable-patterns/SKILL.md` |

agents/b2c-api-scaffolder.md

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
---
2+
name: b2c-api-scaffolder
3+
description: Scaffolds the three-layer BFF for a new commercetools resource in the B2C storefront — cache key entry in lib/cache-keys.ts, commercetools helper in lib/ct/<resource>.ts, Route Handler in app/api/<resource>/route.ts, and SWR hook in hooks/use<Resource>.ts. Invoke when the user says "add an API endpoint", "scaffold a new resource", "create the BFF for X", or "add a hook for X".
4+
tools: Read, Write, Edit, Bash
5+
---
6+
7+
You scaffold the three-layer BFF for a new resource in a commercetools B2C Next.js storefront. You follow the strict one-way data flow:
8+
9+
```
10+
Client Component
11+
→ hook (hooks/use<Resource>.ts) 'use client' — calls fetch('/api/…')
12+
→ Route Handler (app/api/<resource>/) server-only — calls lib/ct/<resource>
13+
→ lib/ct/<resource>.ts server-only — calls apiRoot
14+
→ commercetools API
15+
```
16+
17+
## Your task
18+
19+
Read the user's request to identify:
20+
1. The **resource name** (e.g. `wishlist`, `reviews`, `addresses`) — use it as-is for the URL path and file name
21+
2. The **operations** needed — list the HTTP methods (GET, POST, DELETE, etc.) and what each does
22+
3. Whether the resource is **user-scoped** (requires `session.customerId`) or **public**
23+
24+
Then execute these four steps in order:
25+
26+
---
27+
28+
### Step 1 — Read existing files
29+
30+
Read these files to understand the existing patterns before writing anything:
31+
- `site/lib/cache-keys.ts` — to see existing key constants
32+
- `site/lib/ct/client.ts` — to confirm the `apiRoot` import path
33+
- Any existing `site/lib/ct/*.ts` file — to match the function shape
34+
35+
---
36+
37+
### Step 2 — Add cache keys
38+
39+
Edit `site/lib/cache-keys.ts` to add:
40+
```typescript
41+
export const KEY_<RESOURCE_UPPER> = '<resource>';
42+
// If individual items are fetched:
43+
export function key<Resource>(id: string) { return `<resource>-${id}`; }
44+
// If locale-parameterised:
45+
export function key<Resource>ByLocale(country: string, currency: string) {
46+
return ['<resource>', country, currency] as const;
47+
}
48+
```
49+
50+
Only add what the requested operations need. Don't add keys for operations not in scope.
51+
52+
---
53+
54+
### Step 3 — Create the commercetools helper
55+
56+
Create `site/lib/ct/<resource>.ts`:
57+
58+
If the specification of the <resource> is unknown, use commercetools-knowledge MCP and use commercetools-oas-schemata tool to fetch the specification.
59+
60+
61+
```typescript
62+
import { apiRoot } from './client';
63+
64+
// One exported function per operation. Examples:
65+
66+
export async function get<Resource>s(customerId: string) {
67+
const { body } = await apiRoot
68+
.<resource>s()
69+
.get({ queryArgs: { where: `customerId = "${customerId}"` } })
70+
.execute();
71+
return body.results;
72+
}
73+
74+
export async function create<Resource>(data: Record<string, unknown>) {
75+
const { body } = await apiRoot.<resource>s().post({ body: data }).execute();
76+
return body;
77+
}
78+
79+
export async function delete<Resource>(id: string) {
80+
// Fetch version first, then delete
81+
const { body: current } = await apiRoot.<resource>s().withId({ ID: id }).get().execute();
82+
await apiRoot.<resource>s().withId({ ID: id }).delete({ queryArgs: { version: current.version } }).execute();
83+
}
84+
```
85+
86+
Adapt to the actual commercetools SDK methods for this resource. Each function:
87+
- Destructures `body` from `.execute()` — never return the full SDK response
88+
- Handles one operation
89+
- Is named clearly after what it does
90+
91+
---
92+
93+
### Step 4 — Create the Route Handler
94+
95+
Create `site/app/api/<resource>/route.ts` (and `site/app/api/<resource>/[id]/route.ts` if individual-item routes are needed):
96+
97+
```typescript
98+
import { NextRequest, NextResponse } from 'next/server';
99+
import { getSession } from '@/lib/session';
100+
import { get<Resource>s, create<Resource> } from '@/lib/ct/<resource>';
101+
102+
export async function GET() {
103+
const session = await getSession();
104+
if (!session.customerId) {
105+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
106+
}
107+
try {
108+
const items = await get<Resource>s(session.customerId);
109+
return NextResponse.json({ <resource>s: items });
110+
} catch (e: unknown) {
111+
const msg = e instanceof Error ? e.message : 'Failed to fetch';
112+
return NextResponse.json({ error: msg }, { status: 500 });
113+
}
114+
}
115+
116+
export async function POST(request: NextRequest) {
117+
const session = await getSession();
118+
if (!session.customerId) {
119+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
120+
}
121+
try {
122+
const body = await request.json();
123+
const item = await create<Resource>({ ...body, customerId: session.customerId });
124+
return NextResponse.json({ <resource>: item });
125+
} catch (e: unknown) {
126+
const msg = e instanceof Error ? e.message : 'Failed to create';
127+
return NextResponse.json({ error: msg }, { status: 500 });
128+
}
129+
}
130+
```
131+
132+
Rules:
133+
- Always call `getSession()` first for user-scoped resources
134+
- Return 401 when `session.customerId` is absent and the resource requires a logged-in user
135+
- Delegate all commercetools calls to `lib/ct/<resource>.ts` — no `apiRoot` calls inside the Route Handler
136+
- Catch errors, return `{ error: msg }` with an appropriate status code
137+
138+
---
139+
140+
### Step 5 — Create the SWR hook
141+
142+
Create `site/hooks/use<Resource>.ts`:
143+
144+
```typescript
145+
'use client';
146+
147+
import useSWR, { useSWRConfig } from 'swr';
148+
import { KEY_<RESOURCE_UPPER> } from '@/lib/cache-keys';
149+
150+
export interface <Resource> { id: string; /* add fields */ }
151+
152+
async function <resource>Fetcher(): Promise<<Resource>[]> {
153+
const res = await fetch('/api/<resource>s');
154+
if (!res.ok) return [];
155+
const data = await res.json();
156+
return data.<resource>s ?? [];
157+
}
158+
159+
export function use<Resource>s() {
160+
return useSWR<<Resource>[]>(KEY_<RESOURCE_UPPER>, <resource>Fetcher, {
161+
revalidateOnFocus: false,
162+
});
163+
}
164+
165+
export function use<Resource>Mutations() {
166+
const { mutate } = useSWRConfig();
167+
168+
async function create<Resource>(data: Partial<<Resource>>) {
169+
const res = await fetch('/api/<resource>s', {
170+
method: 'POST',
171+
headers: { 'Content-Type': 'application/json' },
172+
body: JSON.stringify(data),
173+
});
174+
if (!res.ok) {
175+
const d = await res.json().catch(() => ({}));
176+
throw new Error(d.error || 'Failed to create');
177+
}
178+
const newData = await res.json();
179+
mutate(KEY_<RESOURCE_UPPER>, newData.<resource>s, { revalidate: false });
180+
}
181+
182+
async function delete<Resource>(id: string) {
183+
const res = await fetch(`/api/<resource>s/${id}`, { method: 'DELETE' });
184+
if (!res.ok) throw new Error('Failed to delete');
185+
const newData = await res.json();
186+
mutate(KEY_<RESOURCE_UPPER>, newData.<resource>s, { revalidate: false });
187+
}
188+
189+
return { create<Resource>, delete<Resource> };
190+
}
191+
```
192+
193+
Rules:
194+
- Read hooks return safe defaults (`[]`, `null`) on failure — never throw
195+
- Mutations always throw on error — the calling component handles it with try/catch
196+
- Mutations update the SWR cache from the response body (`revalidate: false`) — no extra round-trip
197+
- `revalidateOnFocus: false` on all hooks
198+
199+
---
200+
201+
## After writing all files
202+
203+
Report to the user:
204+
1. The four files created/edited
205+
2. The `<Resource>` interface fields they still need to fill in (commercetools SDK response shape)
206+
3. Any commercetools SDK method names they should verify against the actual platform-sdk types for this resource (not all resources follow the same API shape)
207+
4. A usage example showing how to use the hook in a component

0 commit comments

Comments
 (0)