From c78d3cecfbe98aa518ffbc5ce299504895f96cfa Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Fri, 19 Jun 2026 19:48:08 +0900 Subject: [PATCH 1/2] docs: add asana README and per-service emulator skills - packages/asana: add README covering auth, endpoints, seed config, and programmatic use - skills: add SKILL.md for asana, kakao, naver, tosspayments, firebase, and supabase, mirroring the existing linear skill --- packages/asana/README.md | 158 +++++++++++++++++++++++++++++++++++ skills/asana/SKILL.md | 113 +++++++++++++++++++++++++ skills/firebase/SKILL.md | 88 +++++++++++++++++++ skills/kakao/SKILL.md | 77 +++++++++++++++++ skills/naver/SKILL.md | 82 ++++++++++++++++++ skills/supabase/SKILL.md | 106 +++++++++++++++++++++++ skills/tosspayments/SKILL.md | 83 ++++++++++++++++++ 7 files changed, 707 insertions(+) create mode 100644 packages/asana/README.md create mode 100644 skills/asana/SKILL.md create mode 100644 skills/firebase/SKILL.md create mode 100644 skills/kakao/SKILL.md create mode 100644 skills/naver/SKILL.md create mode 100644 skills/supabase/SKILL.md create mode 100644 skills/tosspayments/SKILL.md diff --git a/packages/asana/README.md b/packages/asana/README.md new file mode 100644 index 0000000..ef8b268 --- /dev/null +++ b/packages/asana/README.md @@ -0,0 +1,158 @@ +# @pleaseai/emulate-asana + +Asana REST API (v1.0) emulator for local development and CI. + +A stateful, in-memory implementation of the Asana API surface — no network and +no real Asana account required. It covers users, workspaces, teams, projects, +sections, tasks, tags, stories, and webhooks, including subtasks, dependencies, +followers, project/section membership, and offset-based pagination. + +OAuth 2.0 and an inspector UI are follow-up work. + +## Install + +```bash +npm install @pleaseai/emulate-asana +``` + +Usually you do not install this directly — run it through the `@pleaseai/emulate` CLI. + +## Start + +```bash +# Through the emulate CLI (asana listens on its default port 4005) +bun packages/emulate/dist/index.js --service asana + +# Start every service +bun packages/emulate/dist/index.js +``` + +A single service starts on the base port (default `4000`); when started with +`--service asana` it runs on port `4005`. Use `-p ` to change it. + +## Auth + +All `/api/1.0/*` routes are guarded by bearer-token auth. Send any token via the +`Authorization` header — by default an unmatched token resolves to the first +seeded user, so `me` keywords (`assignee=me`, `GET /users/me`) work out of the box: + +```bash +curl http://localhost:4005/api/1.0/users/me \ + -H "Authorization: Bearer test-token" +``` + +To map specific tokens to specific users, set them under the top-level `tokens` +key in your config: + +```yaml +tokens: + test-token: + login: dev@example.com # matches a seeded user's email, gid, or name + scopes: [] +``` + +## Request & response shape + +The emulator follows Asana's envelope conventions: + +- Write bodies are wrapped in `data`: `{"data": {"name": "My Task"}}` (a bare + object is also accepted). +- Successful responses are wrapped in `data`; list responses include a + `next_page` cursor (`{ offset, path, uri }` or `null`). +- Errors return `{"errors": [{"message": "..."}]}` with the matching HTTP status. +- Lists accept `limit` (1–100, default 20) and `offset` for pagination. + +```bash +# Create a task +curl -X POST http://localhost:4005/api/1.0/tasks \ + -H "Authorization: Bearer test-token" \ + -H "Content-Type: application/json" \ + -d '{"data":{"name":"Write the README","projects":[""],"assignee":"me"}}' + +# List tasks in a project +curl "http://localhost:4005/api/1.0/tasks?project=&limit=50" \ + -H "Authorization: Bearer test-token" +``` + +## Emulated endpoints + +| Resource | Endpoints | +| --- | --- | +| Users | `GET /users`, `GET /users/:gid` (incl. `me`) | +| Workspaces | `GET /workspaces`, `GET/PUT /workspaces/:gid` | +| Teams | `POST /teams`, `GET /teams/:gid`, `GET /workspaces/:gid/teams`, `GET /teams/:gid/users`, `GET /teams/:gid/projects`, `POST /teams/:gid/addUser`, `POST /teams/:gid/removeUser` | +| Projects | `GET/POST /projects`, `GET/PUT/DELETE /projects/:gid`, `GET /projects/:gid/tasks`, `GET /projects/:gid/sections`, `GET /projects/:gid/task_counts` | +| Sections | `POST /projects/:gid/sections`, `GET/PUT/DELETE /sections/:gid`, `GET /sections/:gid/tasks`, `POST /sections/:gid/addTask` | +| Tasks | `GET/POST /tasks`, `GET/PUT/DELETE /tasks/:gid`, subtasks, stories, tags, projects, dependencies/dependents, `addProject`/`removeProject`, `addTag`/`removeTag`, `addDependencies`/`removeDependencies`, `addFollowers`/`removeFollowers`, `setParent` | +| Tags | `GET/POST /tags`, `GET/PUT/DELETE /tags/:gid`, `GET /tags/:gid/tasks`, `GET/POST /workspaces/:gid/tags` | +| Stories | `GET/PUT/DELETE /stories/:gid` (comments created via `POST /tasks/:gid/stories`) | +| Webhooks | `GET/POST /webhooks`, `GET/PUT/DELETE /webhooks/:gid` | + +## Seed config + +Seed data lets the emulator start pre-populated. References between entities are +resolved by name (e.g. a project's `team` points at a team's `name`); a +workspace defaults to the first one when omitted. + +```yaml +asana: + workspaces: + - name: My Workspace + is_organization: true + users: + - name: Developer + email: dev@example.com + teams: + - name: Engineering + workspace: My Workspace + projects: + - name: My Project + workspace: My Workspace + team: Engineering + owner: Developer + default_view: board + sections: + - name: To Do + project: My Project + tags: + - name: urgent + workspace: My Workspace + color: red + tasks: + - name: Example Task + project: My Project + section: To Do + assignee: Developer + due_on: 2026-01-31 +``` + +Without any seed config, the plugin seeds a single `My Workspace` so the +emulator is usable immediately. + +## Programmatic use + +```ts +import { authMiddleware, Hono, Store, WebhookDispatcher } from '@emulators/core' +import { asanaPlugin, seedFromConfig } from '@pleaseai/emulate-asana' + +const store = new Store() +const webhooks = new WebhookDispatcher() +const baseUrl = 'http://localhost:4005' + +const app = new Hono() +app.use('*', authMiddleware(/* tokenMap */)) +asanaPlugin.register(app, store, webhooks, baseUrl) +asanaPlugin.seed?.(store, baseUrl) + +seedFromConfig(store, baseUrl, { + workspaces: [{ name: 'My Workspace', is_organization: true }], + users: [{ name: 'Developer', email: 'dev@example.com' }], +}) +``` + +`getAsanaStore(store)` exposes the typed collections (`users`, `workspaces`, +`tasks`, …) for direct inspection in tests. + +## License + +Apache-2.0 diff --git a/skills/asana/SKILL.md b/skills/asana/SKILL.md new file mode 100644 index 0000000..d216dce --- /dev/null +++ b/skills/asana/SKILL.md @@ -0,0 +1,113 @@ +--- +name: asana +description: Emulated Asana REST API (v1.0) for local development and testing. Use when the user needs to test Asana integrations locally, create or query tasks/projects/sections, validate webhooks, or avoid hitting the real Asana API. +allowed-tools: Bash(bun:*), Bash(npx @pleaseai/emulate:*), Bash(curl:*) +--- + +# Asana API Emulator + +A stateful, in-memory Asana REST API (v1.0) emulator. + +Included now: + +- Users, workspaces, teams, projects, sections, tasks, tags, stories, webhooks +- Subtasks, task dependencies/dependents, followers, project/section membership +- Bearer-token auth and offset-based pagination (`limit` 1–100, `offset`) + +Follow-up work will add OAuth 2.0 and an inspector UI. + +## Start + +```bash +# From this repo (after `bun install && bun run build`) +bun packages/emulate/dist/index.js --service asana + +# Or from the published package +npx @pleaseai/emulate --service asana +``` + +A single service starts on the base port (default `4000`). Use `-p ` to +change it. When started alongside other services, ports are assigned +sequentially from the base port (asana's slot is `4005`). + +Default URL (asana alone): + +```text +http://localhost:4000 +``` + +## Auth + +All `/api/1.0/*` routes require a bearer token. Any token works — an unmatched +token resolves to the first seeded user, so `me` keywords work out of the box. + +```bash +curl http://localhost:4000/api/1.0/users/me \ + -H "Authorization: Bearer test-token" +``` + +Map specific tokens to users via the top-level `tokens` key in the config: + +```yaml +tokens: + test-token: + login: dev@example.com # matches a seeded user's email, gid, or name + scopes: [] +``` + +## Conventions + +- Write bodies are wrapped in `data`: `{"data": {"name": "My Task"}}`. +- Successful responses are wrapped in `data`; lists add a `next_page` cursor. +- Errors return `{"errors": [{"message": "..."}]}`. + +## Example + +```bash +# Create a task (assigned to the authenticated user) +curl -X POST http://localhost:4000/api/1.0/tasks \ + -H "Authorization: Bearer test-token" \ + -H "Content-Type: application/json" \ + -d '{"data":{"name":"Write the README","projects":[""],"assignee":"me"}}' + +# List tasks in a project +curl "http://localhost:4000/api/1.0/tasks?project=&limit=50" \ + -H "Authorization: Bearer test-token" +``` + +## Seed Config + +Add an `asana:` section to `emulate.config.yaml` (or pass `--seed `). +Inter-entity references are resolved by name; the workspace defaults to the +first one when omitted. + +```yaml +asana: + workspaces: + - name: My Workspace + is_organization: true + users: + - name: Developer + email: dev@example.com + teams: + - name: Engineering + workspace: My Workspace + projects: + - name: My Project + workspace: My Workspace + team: Engineering + owner: Developer + sections: + - name: To Do + project: My Project + tags: + - name: urgent + workspace: My Workspace + color: red + tasks: + - name: Example Task + project: My Project + section: To Do + assignee: Developer + due_on: 2026-01-31 +``` diff --git a/skills/firebase/SKILL.md b/skills/firebase/SKILL.md new file mode 100644 index 0000000..3e60ba1 --- /dev/null +++ b/skills/firebase/SKILL.md @@ -0,0 +1,88 @@ +--- +name: firebase +description: Emulated Firebase Auth (Identity Toolkit + Secure Token) and FCM v1 for local development and testing. Use when the user needs to test Firebase Auth locally, sign up / sign in with email+password, refresh ID tokens, send FCM messages, or avoid hitting real Firebase services. +allowed-tools: Bash(bun:*), Bash(npx @pleaseai/emulate:*), Bash(curl:*) +--- + +# Firebase Emulator (Auth + FCM) + +A stateful emulator for Firebase Authentication and Cloud Messaging. + +Included now: + +- Identity Toolkit REST: `signUp`, `signInWithPassword`, `lookup`, `update`, + `delete`, `sendOobCode` +- Secure Token: refresh ID tokens (`POST /v1/token`) +- FCM v1: `POST /v1/projects//messages:send` + +## Start + +```bash +# From this repo (after `bun install && bun run build`) +bun packages/emulate/dist/index.js --service firebase + +# Or from the published package +npx @pleaseai/emulate --service firebase +``` + +A single service starts on the base port (default `4000`). Use `-p ` to +change it. When started alongside other services, ports are assigned +sequentially from the base port (firebase's slot is `4003`). + +Point a Firebase SDK at the emulator with +`FIREBASE_AUTH_EMULATOR_HOST=localhost:4000`. + +## Auth + +- Identity Toolkit and Secure Token take the API key as a query param: + `?key=`. +- FCM `messages:send` requires `Authorization: Bearer ` (presence only — + the token is not cryptographically verified). + +The `googleapis.com`-prefixed path variants are also accepted, e.g. +`/identitytoolkit.googleapis.com/v1/accounts:signUp` and +`/securetoken.googleapis.com/v1/token`. + +## Flow + +```bash +# 1. Sign up with email + password +curl -X POST "http://localhost:4000/v1/accounts:signUp?key=firebase_api_key_example" \ + -H "Content-Type: application/json" \ + -d '{"email":"hong@example.com","password":"password123","returnSecureToken":true}' +# → {"idToken":"...","refreshToken":"...","expiresIn":"3600","localId":"..."} + +# 2. Sign in with email + password +curl -X POST "http://localhost:4000/v1/accounts:signInWithPassword?key=firebase_api_key_example" \ + -H "Content-Type: application/json" \ + -d '{"email":"hong@example.com","password":"password123","returnSecureToken":true}' + +# 3. Refresh the ID token +curl -X POST "http://localhost:4000/v1/token?key=firebase_api_key_example" \ + -H "Content-Type: application/json" \ + -d '{"grant_type":"refresh_token","refresh_token":""}' + +# 4. Send an FCM message +curl -X POST "http://localhost:4000/v1/projects/demo-project/messages:send" \ + -H "Authorization: Bearer test-token" \ + -H "Content-Type: application/json" \ + -d '{"message":{"token":"","notification":{"title":"Hi","body":"World"}}}' +``` + +Inspect emulator state (no auth) at `GET /internal/messages` and +`GET /internal/oob_codes`. + +## Seed Config + +Add a `firebase:` section to `emulate.config.yaml` (or pass `--seed `): + +```yaml +firebase: + projects: + - project_id: demo-project + api_key: firebase_api_key_example + users: + - email: hong@example.com + password: password123 + display_name: 홍길동 +``` diff --git a/skills/kakao/SKILL.md b/skills/kakao/SKILL.md new file mode 100644 index 0000000..237ef3a --- /dev/null +++ b/skills/kakao/SKILL.md @@ -0,0 +1,77 @@ +--- +name: kakao +description: Emulated Kakao API (kauth OAuth + kapi user/talk) for local development and testing. Use when the user needs to test Kakao Login locally, run the OAuth 2.0 flow, fetch a Kakao user profile, send a KakaoTalk self-memo, or avoid hitting the real Kakao API. +allowed-tools: Bash(bun:*), Bash(npx @pleaseai/emulate:*), Bash(curl:*) +--- + +# Kakao API Emulator + +A stateful emulator for Kakao's `kauth` (OAuth 2.0) and `kapi` (user/talk) APIs. + +Included now: + +- OAuth 2.0 authorize + token (authorization_code and refresh_token grants) +- User API: `/v2/user/me`, access-token info, logout, unlink +- KakaoTalk self-memo send (`/v2/api/talk/memo/default/send`) + +## Start + +```bash +# From this repo (after `bun install && bun run build`) +bun packages/emulate/dist/index.js --service kakao + +# Or from the published package +npx @pleaseai/emulate --service kakao +``` + +A single service starts on the base port (default `4000`). Use `-p ` to +change it. When started alongside other services, ports are assigned +sequentially from the base port (kakao's slot is `4000`). + +## Auth + +- OAuth token exchange authenticates with `client_id` (and `client_secret` if + the app is configured with one). +- `kapi` calls (user/talk) use `Authorization: Bearer `. + +## Flow + +```bash +# 1. Authorization code — in CI, ?user_id= auto-approves without the login page +curl -i "http://localhost:4000/oauth/authorize?client_id=kakao_rest_api_key_example\ +&redirect_uri=http://localhost:3000/api/auth/callback/kakao&response_type=code&user_id=1001" +# → redirect with Location: .../callback?code= + +# 2. Exchange the code for tokens +curl -X POST http://localhost:4000/oauth/token \ + -d "grant_type=authorization_code&client_id=kakao_rest_api_key_example&code=\ +&redirect_uri=http://localhost:3000/api/auth/callback/kakao" +# → {"token_type":"bearer","access_token":"...","refresh_token":"...","expires_in":21599} + +# 3. Fetch the user profile +curl http://localhost:4000/v2/user/me -H "Authorization: Bearer " + +# 4. Send a KakaoTalk self-memo (template_object is a JSON-encoded string) +curl -X POST http://localhost:4000/v2/api/talk/memo/default/send \ + -H "Authorization: Bearer " \ + -d 'template_object={"object_type":"text","text":"Hello from the emulator"}' +``` + +Inspect sent memos (no auth) at `GET /internal/talk/memos`. + +## Seed Config + +Add a `kakao:` section to `emulate.config.yaml` (or pass `--seed `): + +```yaml +kakao: + apps: + - client_id: kakao_rest_api_key_example + client_secret: kakao_client_secret_example + redirect_uris: [http://localhost:3000/api/auth/callback/kakao] + users: + - user_id: 1001 + nickname: 홍길동 + email: hong@example.com + profile_image_url: https://k.kakaocdn.net/dn/profile.jpg +``` diff --git a/skills/naver/SKILL.md b/skills/naver/SKILL.md new file mode 100644 index 0000000..1e4f6c5 --- /dev/null +++ b/skills/naver/SKILL.md @@ -0,0 +1,82 @@ +--- +name: naver +description: Emulated Naver API (nid OAuth + openapi profile) for local development and testing. Use when the user needs to test Naver Login locally, run the OAuth 2.0 issue/refresh/delete flow, fetch a Naver profile (/v1/nid/me), or avoid hitting the real Naver API. +allowed-tools: Bash(bun:*), Bash(npx @pleaseai/emulate:*), Bash(curl:*) +--- + +# Naver API Emulator + +A stateful emulator for Naver Login (`nid`) OAuth 2.0 and the profile openapi. + +Included now: + +- OAuth 2.0 token endpoint: issue (`authorization_code`), `refresh_token`, + and `delete` (token revocation) grants +- Profile API `GET /v1/nid/me` and token check `GET /v1/nid/verify` + +## Start + +```bash +# From this repo (after `bun install && bun run build`) +bun packages/emulate/dist/index.js --service naver + +# Or from the published package +npx @pleaseai/emulate --service naver +``` + +A single service starts on the base port (default `4000`). Use `-p ` to +change it. When started alongside other services, ports are assigned +sequentially from the base port (naver's slot is `4001`). + +## Auth + +- OAuth token operations authenticate with `client_id` (and `client_secret` + if configured). +- Profile calls use `Authorization: Bearer `. +- OAuth/token errors are returned with HTTP 200 and an + `{ "error", "error_description" }` body (Naver convention). + +## Flow + +```bash +# 1. Authorization code — in CI, ?user= auto-approves without the login page +curl -i "http://localhost:4000/oauth2.0/authorize?response_type=code\ +&client_id=naver_client_id_example&redirect_uri=http://localhost:3000/api/auth/callback/naver\ +&state=abc123&user=" +# → redirect with Location: .../callback?code=&state=abc123 + +# 2. Exchange the code for tokens +curl -X POST http://localhost:4000/oauth2.0/token \ + -d "grant_type=authorization_code&client_id=naver_client_id_example\ +&client_secret=naver_client_secret_example&code=&state=abc123" +# → {"access_token":"...","refresh_token":"...","token_type":"bearer","expires_in":"3600"} + +# 3. Fetch the user profile +curl http://localhost:4000/v1/nid/me -H "Authorization: Bearer " + +# 4. Revoke the token +curl -X POST http://localhost:4000/oauth2.0/token \ + -d "grant_type=delete&client_id=naver_client_id_example\ +&access_token=&service_provider=NAVER" +``` + +The token endpoint accepts both `GET` query params and `POST` form bodies. + +## Seed Config + +Add a `naver:` section to `emulate.config.yaml` (or pass `--seed `): + +```yaml +naver: + apps: + - client_id: naver_client_id_example + client_secret: naver_client_secret_example + callback_urls: [http://localhost:3000/api/auth/callback/naver] + users: + - name: 홍길동 + nickname: gildong + email: hong@example.com + gender: M + birthyear: '1990' + mobile: 010-1234-5678 +``` diff --git a/skills/supabase/SKILL.md b/skills/supabase/SKILL.md new file mode 100644 index 0000000..f4573c9 --- /dev/null +++ b/skills/supabase/SKILL.md @@ -0,0 +1,106 @@ +--- +name: supabase +description: Emulated Supabase (GoTrue Auth + PostgREST) for local development and testing. Use when the user needs to test a Supabase integration locally, sign up / sign in users, run PostgREST table CRUD with filters, or avoid hitting a real Supabase project. +allowed-tools: Bash(bun:*), Bash(npx @pleaseai/emulate:*), Bash(curl:*) +--- + +# Supabase Emulator (GoTrue Auth + PostgREST) + +A stateful emulator for Supabase's GoTrue auth and PostgREST data API. + +Included now: + +- GoTrue auth: signup, password/refresh-token grants, get/update user, logout, + password recovery +- PostgREST: table CRUD (`GET`/`POST`/`PATCH`/`DELETE`) with filters, ordering, + pagination, and column selection + +RLS is not emulated — `anon_key` and `service_role_key` get identical access. + +## Start + +```bash +# From this repo (after `bun install && bun run build`) +bun packages/emulate/dist/index.js --service supabase + +# Or from the published package +npx @pleaseai/emulate --service supabase +``` + +A single service starts on the base port (default `4000`). Use `-p ` to +change it. When started alongside other services, ports are assigned +sequentially from the base port (supabase's slot is `4004`). + +Point a Supabase client at the emulator with +`createClient("http://localhost:4000", anonKey)`. + +## Auth + +Pass the project key as either an `apikey` header or `Authorization: Bearer +`. The key must match the configured `anon_key` or `service_role_key`. +User-scoped routes (`GET/PUT /auth/v1/user`, logout) take the user's +`access_token` as the bearer token. + +## Flow + +```bash +# 1. Sign up (returns a session immediately — auto-confirmed) +curl -X POST http://localhost:4000/auth/v1/signup \ + -H "apikey: supabase_anon_key_example" \ + -H "Content-Type: application/json" \ + -d '{"email":"hong@example.com","password":"password123","data":{"name":"홍길동"}}' +# → {"access_token":"...","refresh_token":"...","token_type":"bearer","expires_in":3600,"user":{...}} + +# 2. Log in (grant_type=password) +curl -X POST "http://localhost:4000/auth/v1/token?grant_type=password" \ + -H "apikey: supabase_anon_key_example" \ + -H "Content-Type: application/json" \ + -d '{"email":"hong@example.com","password":"password123"}' + +# 3. Get the current user +curl http://localhost:4000/auth/v1/user \ + -H "apikey: supabase_anon_key_example" \ + -H "Authorization: Bearer " +``` + +## PostgREST + +Filters use `column=op.value`; multiple params are AND-ed. Operators: `eq`, +`neq`, `gt`, `gte`, `lt`, `lte`, `like`, `ilike`, `is`, `in`. Reserved params: +`select`, `order`, `limit`, `offset`. + +```bash +# Insert a row (Prefer: return=representation echoes the inserted row) +curl -X POST http://localhost:4000/rest/v1/todos \ + -H "apikey: supabase_anon_key_example" \ + -H "Content-Type: application/json" \ + -H "Prefer: return=representation" \ + -d '{"title":"장보기","completed":false}' + +# Query with filter, order, limit, and column selection +curl "http://localhost:4000/rest/v1/todos?completed=is.false&order=id.asc&limit=10&select=id,title" \ + -H "apikey: supabase_anon_key_example" + +# Update rows matching a filter +curl -X PATCH "http://localhost:4000/rest/v1/todos?id=eq.1" \ + -H "apikey: supabase_service_role_key_example" \ + -H "Content-Type: application/json" \ + -d '{"completed":true}' +``` + +## Seed Config + +Add a `supabase:` section to `emulate.config.yaml` (or pass `--seed `): + +```yaml +supabase: + anon_key: supabase_anon_key_example + service_role_key: supabase_service_role_key_example + users: + - email: hong@example.com + password: password123 + tables: + todos: + - { id: 1, title: 장보기, completed: false } + - { id: 2, title: 청소하기, completed: true } +``` diff --git a/skills/tosspayments/SKILL.md b/skills/tosspayments/SKILL.md new file mode 100644 index 0000000..78cb676 --- /dev/null +++ b/skills/tosspayments/SKILL.md @@ -0,0 +1,83 @@ +--- +name: tosspayments +description: Emulated Toss Payments API for local development and testing. Use when the user needs to test a Toss Payments integration locally, confirm/look up/cancel a payment, simulate the checkout flow, exercise payment webhooks, or avoid hitting the real Toss Payments API. +allowed-tools: Bash(bun:*), Bash(npx @pleaseai/emulate:*), Bash(curl:*) +--- + +# Toss Payments API Emulator + +A stateful emulator for the Toss Payments REST API. + +Included now: + +- Payment confirm / lookup (by paymentKey or orderId) / cancel (incl. partial) +- Checkout page simulation (`/checkout` → `/checkout/approve`) +- An `/internal/payments` helper to create a payment without the widget +- `PAYMENT_STATUS_CHANGED` webhooks on confirm and cancel + +## Start + +```bash +# From this repo (after `bun install && bun run build`) +bun packages/emulate/dist/index.js --service tosspayments + +# Or from the published package +npx @pleaseai/emulate --service tosspayments +``` + +A single service starts on the base port (default `4000`). Use `-p ` to +change it. When started alongside other services, ports are assigned +sequentially from the base port (tosspayments' slot is `4002`). + +## Auth + +Payment API routes use HTTP Basic auth with the secret key as the username and +an empty password — `Authorization: Basic base64(":")` (note the +trailing colon). The checkout pages and `/internal/payments` helper need no auth. + +```bash +# Build the header value for test_sk_example +echo -n 'test_sk_example:' | base64 # → dGVzdF9za19leGFtcGxlOg== +``` + +## Flow + +```bash +# 1. Create a payment (replaces the payment-widget step) +curl -X POST http://localhost:4000/internal/payments \ + -H "Content-Type: application/json" \ + -d '{"orderId":"order-1","orderName":"Test order","amount":11000}' +# → {"paymentKey":"", ...} + +# 2. Confirm it +curl -X POST http://localhost:4000/v1/payments/confirm \ + -H "Authorization: Basic $(echo -n 'test_sk_example:' | base64)" \ + -H "Content-Type: application/json" \ + -d '{"paymentKey":"","orderId":"order-1","amount":11000}' + +# 3. Look it up (by paymentKey or by orderId) +curl http://localhost:4000/v1/payments/ \ + -H "Authorization: Basic $(echo -n 'test_sk_example:' | base64)" +curl http://localhost:4000/v1/payments/orders/order-1 \ + -H "Authorization: Basic $(echo -n 'test_sk_example:' | base64)" + +# 4. Cancel it (omit cancelAmount for a full cancel) +curl -X POST http://localhost:4000/v1/payments//cancel \ + -H "Authorization: Basic $(echo -n 'test_sk_example:' | base64)" \ + -H "Content-Type: application/json" \ + -d '{"cancelReason":"User requested","cancelAmount":11000}' +``` + +Confirm and cancel dispatch a `PAYMENT_STATUS_CHANGED` webhook to any configured +webhook URLs. + +## Seed Config + +Add a `tosspayments:` section to `emulate.config.yaml` (or pass `--seed `): + +```yaml +tosspayments: + merchants: + - client_key: test_ck_example + secret_key: test_sk_example +``` From 0ffba9b33681cb6add5831b9c9b5f01cd7966f21 Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Fri, 19 Jun 2026 20:15:29 +0900 Subject: [PATCH 2/2] chore: apply AI code review suggestions Fix ESLint formatting flagged by CodeRabbit on PR #14: - style/no-multi-spaces in asana SKILL.md and README.md - yaml/flow-mapping-curly-spacing in supabase SKILL.md --- packages/asana/README.md | 2 +- skills/asana/SKILL.md | 2 +- skills/supabase/SKILL.md | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/asana/README.md b/packages/asana/README.md index ef8b268..fb2ff74 100644 --- a/packages/asana/README.md +++ b/packages/asana/README.md @@ -47,7 +47,7 @@ key in your config: ```yaml tokens: test-token: - login: dev@example.com # matches a seeded user's email, gid, or name + login: dev@example.com # matches a seeded user's email, gid, or name scopes: [] ``` diff --git a/skills/asana/SKILL.md b/skills/asana/SKILL.md index d216dce..19f0109 100644 --- a/skills/asana/SKILL.md +++ b/skills/asana/SKILL.md @@ -51,7 +51,7 @@ Map specific tokens to users via the top-level `tokens` key in the config: ```yaml tokens: test-token: - login: dev@example.com # matches a seeded user's email, gid, or name + login: dev@example.com # matches a seeded user's email, gid, or name scopes: [] ``` diff --git a/skills/supabase/SKILL.md b/skills/supabase/SKILL.md index f4573c9..f5959f7 100644 --- a/skills/supabase/SKILL.md +++ b/skills/supabase/SKILL.md @@ -101,6 +101,6 @@ supabase: password: password123 tables: todos: - - { id: 1, title: 장보기, completed: false } - - { id: 2, title: 청소하기, completed: true } + - {id: 1, title: 장보기, completed: false} + - {id: 2, title: 청소하기, completed: true} ```