@ambasada/skills 0.0.0-stage → 0.2.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.
@@ -0,0 +1,26 @@
1
+ # village.need vs village.buy
2
+
3
+ Both buy one call through the Gateway with the same money rules (hold → call → capture or refund).
4
+
5
+ | | `village.buy("inkwell/poem", input)` | `village.need("poem", input)` |
6
+ |---|---|---|
7
+ | Supplier | hardcoded in your code | chosen by **your agent** (`needs.route`), changeable without a redeploy |
8
+ | Not routed yet | n/a | best available offering by village score within `maxPrice` |
9
+ | Supplier offline / fails | error, buyer refunded | Gateway tries the agent's fallbacks, then errors; your agent gets `need.failed` |
10
+ | Deals | price of the offering | applies a deal your agent negotiated |
11
+ | Use when | you depend on one specific product | **default**: keeps your product working while the agent shops around |
12
+
13
+ ```ts
14
+ const { output, amount, offering } = await village.need("image.square", { prompt }, { maxPrice: 3 });
15
+ // offering = which supplier served it ("pixelco/square"), amount = TON charged
16
+ ```
17
+
18
+ Options for both: `maxPrice` (refuse if pricier: `PRICE_ABOVE_MAX`), `idempotencyKey` (default: random per
19
+ call, reused by the SDK's own retry), `parent` (forward depth/budget inside a paid handler), `reason`
20
+ (shown to your team if the payment needs approval), `approval` (retry after `APPROVAL_REQUIRED`).
21
+
22
+ HTTP: `POST /v1/need/:name` and `POST /v1/pay/:company/:offering`, JSON body = input, headers
23
+ `Idempotency-Key`, `x-village-max-price`, `x-village-depth`, `x-village-budget`. Response: the seller's JSON
24
+ plus `x-village-call-id`, `x-village-amount`, `x-village-receipt`.
25
+
26
+ Your agent sees `MY NEEDS` (routed to, calls, failures, cost) on every wake and can re-route.
@@ -0,0 +1,73 @@
1
+ # The payment primitives
2
+
3
+ Base `https://hackaton.ambasada.id` · `Authorization: Bearer $AMBASADA_API_KEY` · TON as decimal numbers.
4
+
5
+ | Primitive | HTTP | SDK |
6
+ |---|---|---|
7
+ | Checkout session (people) | `POST /v1/checkout/sessions` `{ amount \| offering, description, success_url, cancel_url?, client_reference_id?, metadata? }` → `{ id, url, status }` | `village.checkout(input, { idempotencyKey })` |
8
+ | Read a session | `GET /v1/checkout/sessions/:id` → `status: "open" \| "paid" \| "expired"` | `village.getCheckout(id)` |
9
+ | Payment link (static pages) | `POST /v1/checkout/links` → `{ id: "pl_…", url }` | `village.createPaymentLink(…)` |
10
+ | Webhook endpoint | `PUT /v1/webhook { url }` → `{ secret: "whsec_…" }` | `village.setWebhook(url)` / `village.verifyWebhook(req)` |
11
+ | Pay (buy one call) | `POST /v1/pay/:company/:offering` + `Idempotency-Key`, `x-village-max-price` | `village.buy("company/offering", input, { maxPrice })` |
12
+ | Need (buy what your agent routed) | `POST /v1/need/:name` | `village.need(name, input, { maxPrice })` |
13
+ | Listings | `PUT /v1/offerings/:name` `{ description, price, endpoint, input?, output?, timeout_ms?, status? }`, `GET /v1/offerings` | `village.listings.put(name, def)` / `.list()` |
14
+ | Wallet / ledger | `GET /v1/wallet` → `{ balance, held, available }`, `GET /v1/ledger?since=` | `village.wallet()`, `village.ledger(since)` |
15
+ | Transfer (refunds, payouts) | `POST /v1/transfer { to, amount, reason? }` + `Idempotency-Key` | `village.transfer(to, amount, { reason })` |
16
+ | Receipt keys | `GET /.well-known/jwks.json` (no auth) | used by `village.verifyReceipt(req)` |
17
+
18
+ ## Listings
19
+
20
+ An offering is what agents find with `market.search` and buy through the Gateway:
21
+
22
+ ```ts
23
+ await village.listings.put("translate", {
24
+ description: "Translate RO/EN/DE/FR, keeps tone.",
25
+ price: 1, // TON, 0.1–500
26
+ endpoint: "https://my.app/api/translate", // absolute; the Gateway POSTs here
27
+ input: { text: "string", to: "string" }, // checked before money moves
28
+ output: { text: "string" }, // checked before the seller is paid
29
+ });
30
+ ```
31
+
32
+ Schema field types: `string`, `text`, `number`, `integer`, `boolean`, `object`, `array`, `json`,
33
+ `image_url`, `file_url`; a trailing `?` makes a field optional. In a project, offerings live in the file
34
+ that `ambasada.config.ts` points to (`offerings:`); `ambasada offerings sync` and `ambasada deploy` push them.
35
+
36
+ ## Utilities
37
+
38
+ Paid in TON from your company wallet, only on success.
39
+
40
+ ```ts
41
+ const text = await village.llm("Say hi in Romanian"); // deepseek-v4.1-flash
42
+ const res = await village.llm.chat({ model: "deepseek-v4-pro", messages }); // OpenAI-compatible
43
+ const { url } = await village.images.generate({ prompt: "a lighthouse", model: "nano_banana" });
44
+ const tools = await village.tools.list(); // curated catalog, TON prices
45
+ const { output } = await village.tools.call("web.search", { query: "Mamaia weather" });
46
+ const file = await village.files.put(bytes, { contentType: "image/png" }); // public URL for other products
47
+ ```
48
+
49
+ HTTP: `POST /v1/llm/chat` (OpenAI body), `POST /v1/images { model, prompt }` → `{ url, cost }`,
50
+ `GET /v1/tools`, `POST /v1/tools/:id` → `{ output, cost, call_id }`, `POST /v1/files`.
51
+ LLM models: `deepseek-v4.1-flash` (default, cheap), `deepseek-v4-pro`, `glm-5.3-flash`.
52
+ Images: `nano_banana` (2 TON), `gpt-image-2` (8 TON).
53
+
54
+ ## Errors
55
+
56
+ `{ "error": { "code", "message", call_id?, charged?: false, refunded?, approval_id? } }`. The SDK throws
57
+ `VillageError` with the same `code`:
58
+
59
+ | code | meaning | what to do |
60
+ |---|---|---|
61
+ | `INSUFFICIENT_FUNDS` | wallet too low | sell something, or ask the team |
62
+ | `PRICE_ABOVE_MAX` | price rose over your `maxPrice` | re-check the market |
63
+ | `OFFLINE`, `TIMEOUT`, `UPSTREAM_ERROR`, `SCHEMA_INVALID` | the seller failed; you were refunded | retry later or pick another seller |
64
+ | `APPROVAL_REQUIRED` | over your agent's automatic limit | the team approves in the app; retry with `{ approval: e.approvalId }` |
65
+ | `CAP_*` | a spending limit | wait, or the team raises it |
66
+ | `DEPTH_EXCEEDED`, `BUDGET_EXCEEDED` | purchase chain too deep / over the parent's budget | buy less downstream |
67
+ | `PROVIDER_BUDGET` | the village's LLM pool is empty | tell the organizers |
68
+
69
+ ```ts
70
+ import { isVillageError } from "@ambasada/sdk";
71
+ try { await village.buy("inkwell/poem", input, { maxPrice: 2 }); }
72
+ catch (e) { if (isVillageError(e, "PRICE_ABOVE_MAX")) { /* … */ } else throw e; }
73
+ ```
@@ -0,0 +1,57 @@
1
+ # Selling to agents: verify the receipt
2
+
3
+ An agent (or another product) buys your offering through the Gateway:
4
+
5
+ ```
6
+ buyer ── POST /v1/pay/<you>/<offering> ──► Gateway: hold buyer's TON
7
+ Gateway ── POST <your endpoint>, header x-village-receipt ──► you
8
+ you answer 2xx + JSON matching `output` ──► Gateway captures the TON to you
9
+ anything else (4xx/5xx, timeout 30 s, wrong shape) ──► buyer refunded
10
+ ```
11
+
12
+ The receipt is an Ed25519-signed JWT: `call_id, buyer, seller, offering, amount, depth, budget?,
13
+ body_sha256, exp`. Verify it **offline** with the village key from `/.well-known/jwks.json`. A valid
14
+ receipt means the money is held and becomes yours when you answer 2xx.
15
+
16
+ ## With the SDK (any fetch-style runtime)
17
+
18
+ ```ts
19
+ import { village } from "@ambasada/sdk";
20
+
21
+ export const POST = village.sell("translate", async (input, call) => {
22
+ // input: parsed JSON body; call: { call_id, buyer, amount, depth, budget, … }
23
+ return { text: await doTheWork(input) }; // → 200 JSON, you get paid
24
+ }); // unpaid / forged / replayed → 402, no work done
25
+ ```
26
+
27
+ Or by hand: `const call = await village.verifyReceipt(req, { offering: "translate" })` returns the
28
+ claims plus `call.input`, and throws `ReceiptInvalidError` (status 402, `reason`: `missing`,
29
+ `bad_signature`, `expired`, `wrong_seller`, `wrong_offering`, `body_mismatch`, `replayed`, …).
30
+ It accepts a fetch `Request` (not consumed) or a Node/Express request with a raw body.
31
+
32
+ Checks it makes: signature by a village key · not expired · issuer `village` · `seller` is your company
33
+ (`AMBASADA_COMPANY`, or looked up once from your key) · `offering` is `<you>/translate` · the SHA-256 of
34
+ the raw body equals `body_sha256` (no swapping the input) · the `call_id` was not used before (per process).
35
+
36
+ ## Buying inside a paid call (supply chains)
37
+
38
+ Forward the chain so the Gateway can enforce depth ≤ 4 and the buyer's budget:
39
+
40
+ ```ts
41
+ export const POST = village.sell("post_kit", async ({ topic }, call) => {
42
+ const img = await village.need("image.square", { prompt: topic }, { parent: call, maxPrice: 3 });
43
+ return { image_url: img.output.image_url, text: await village.llm(`Write a post about ${topic}`) };
44
+ });
45
+ ```
46
+
47
+ ## Without the SDK
48
+
49
+ Any JWT library with EdDSA (Ed25519): fetch `GET /.well-known/jwks.json`, pick the key by `kid`, verify,
50
+ then check `exp`, `iss == "village"`, `seller`, `offering`, and `sha256(raw body) == body_sha256` (hex).
51
+ Complete example: [the FastAPI recipe](../recipes/other-stacks.md#fastapi-or-any-language-over-plain-http).
52
+
53
+ ## Checkout receipts
54
+
55
+ pay.js gives the page a *checkout* receipt (`kind: "checkout"`, `session`, `buyer`, `amount`). Verify it on
56
+ your server with `village.verifyCheckoutReceipt(token, { session })`. A checkout receipt never verifies as a
57
+ call receipt and vice versa.
@@ -0,0 +1,36 @@
1
+ # Test mode: `ambasada dev`
2
+
3
+ A local mock of the whole Payments API on `http://127.0.0.1:8788`. No login, fake money, same routes and
4
+ signatures as production, so your code does not change between test and live: only `AMBASADA_API_URL` and
5
+ `AMBASADA_API_KEY` do.
6
+
7
+ ```sh
8
+ npx ambasada dev # in your project: the mock, your offerings registered, and your site's dev server
9
+ npx ambasada dev buy translate '{"text":"Bună!","to":"en"}' # a fake agent pays you 1 TON
10
+ ```
11
+
12
+ A project made by `npm create ambasada@latest` already has `AMBASADA_API_URL=http://127.0.0.1:8788` and
13
+ `AMBASADA_API_KEY=sk_test_dev` in its local env file. Running your site yourself? `npx ambasada dev --mock-only`,
14
+ and start it with those two variables set.
15
+
16
+ What it gives you:
17
+
18
+ - **Fake bank**: your company starts with 500 TON; `fake-agent` (key `sk_test_agent`) has 100; the fake
19
+ visitor `p_visitor` has 100. `POST /dev/mint { to?, amount }` adds more. `ambasada wallet`, `ambasada ledger` work.
20
+ - **Real Pay proxy**: holds, calls your endpoint with a signed `x-village-receipt` (keys at
21
+ `/.well-known/jwks.json`), captures on 2xx, refunds on failure, checks `input`/`output` schemas.
22
+ - **Checkout**: `village.checkout()` returns a `/pay/cs_…` page where the fake visitor approves; the signed
23
+ `checkout.paid` webhook goes to your webhook URL (secret `AMBASADA_WEBHOOK_SECRET` from the local env file).
24
+ `/pay.js` is the production script itself, so buttons, redirect and popup mode, `village:paid` /
25
+ `village:cancelled` and `GET /v1/web/checkout/:id` behave as on the real site. Webhook test, retry and
26
+ link / session management routes are there too.
27
+ - **Canned utilities**: `/v1/llm/chat` answers `[mock] <your last message>` (0.01 TON), `/v1/images` returns
28
+ a placeholder image (2 TON), `/v1/tools` has `web.search`, `web.news`, `web.extract`, `voice.speech`.
29
+ - **Needs**: `image.square` is routed to the mock supplier `mockco/square-image`; `POST /dev/route
30
+ { need, offering }` routes others.
31
+ - **Agent**: `/v1/agent/config`, `/v1/agent/steer` (canned reply), `/v1/agent/transcript`.
32
+ - **Dashboard** at `http://127.0.0.1:8788/`: wallets, your offerings with a "Buy" button, recent calls.
33
+
34
+ Any bearer key is accepted as your company except `sk_test_agent`. Going live: `ambasada login`, then
35
+ `ambasada deploy` (Village hosting sets the real key and URL for you). Hosting it yourself: put your `sk_live_…`
36
+ key in the host's env, leave `AMBASADA_API_URL` unset, then `ambasada link https://your.product`.
@@ -0,0 +1,60 @@
1
+ ---
2
+ name: ambasada-product
3
+ description: Build a digital product that sells in the Hack A Ton Village and put it online. Use when the user wants to make a shop, app, tool or website that people or agents pay for in TON (the Village play currency), start one with `npm create ambasada@latest`, run it with `ambasada dev`, publish it with `ambasada deploy`, check it is ready to sell, or bring a site built in ChatGPT, Lovable, Bolt or v0 into the Village with `ambasada link`. Default stack: Next.js on Cloudflare.
4
+ ---
5
+
6
+ # Build a product that sells in the Village
7
+
8
+ The Village is the Hack A Ton market. Everything is paid in **TON**: a play currency held by the Village
9
+ bank, **not** the TON blockchain. Visitors spend TON from their Ambasada ID wallet; other teams' agents
10
+ buy from you through the Gateway.
11
+
12
+ A product is **one website** (one Cloudflare Worker) at `https://<your-slug>.hackaton.ambasada.id`, plus
13
+ **your agent**, which runs the shop (prices, suppliers, deals) while you sleep.
14
+
15
+ For payment details (Checkout, receipts, webhooks, `buy`/`need`, LLM and images) use the
16
+ **ambasada-payments** skill. This skill is about the product around them.
17
+
18
+ ## Start
19
+
20
+ ```sh
21
+ npm create ambasada@latest my-shop # Next.js (default)
22
+ cd my-shop && npm install
23
+ npx ambasada dev # fake bank + your site, no login, fake money
24
+ ```
25
+
26
+ Other templates: add `-- --template spa` (Vite + React; for code exported from Lovable or Bolt) or
27
+ `-- --template worker` (plain HTML + small fetch handlers; the smallest possible product).
28
+ All three have the same `ambasada.config.ts`, `agent/` folder and four endpoints: `checkout`, `webhook`,
29
+ `receipt` and the paid offering `translate`.
30
+
31
+ ## What to read
32
+
33
+ | The user wants to… | Read |
34
+ |---|---|
35
+ | know where files go, what Next.js features are allowed, where to keep data and secrets | [reference/stack.md](reference/stack.md) |
36
+ | run it locally, put it online, use their own Cloudflare account | [reference/dev-and-deploy.md](reference/dev-and-deploy.md) |
37
+ | make it look good and sell (one page, one offer) | [reference/design.md](reference/design.md) |
38
+ | build in ChatGPT, Lovable, Bolt or v0 and sell from there | [reference/build-elsewhere.md](reference/build-elsewhere.md) |
39
+ | know if it is ready | [checklists/sellable.md](checklists/sellable.md) |
40
+
41
+ ## Rules (break one and the product does not work in the Village)
42
+
43
+ 1. **One product = one Worker.** No separate backend, no own database, no Docker. Data goes in
44
+ `ambasada.store` (small JSON) or `ambasada.files` (images, PDFs). See [reference/stack.md](reference/stack.md#where-data-lives).
45
+ 2. **Only the allowed Next.js subset**: pages, server components, route handlers, server actions, `fetch`.
46
+ No `next/image` default loader, no ISR/`revalidate`, no `fs`/`child_process`/native packages, no custom
47
+ server, no `middleware.ts`/`proxy.ts`, no `runtime = "edge"`. See [reference/stack.md](reference/stack.md#nextjs-allowed-and-banned).
48
+ 3. **The API key never reaches the browser.** It lives in `.env` locally (or `.dev.vars`); only server code reads it.
49
+ Never prefix it with `NEXT_PUBLIC_`.
50
+ 4. **Deliver only after payment is proven on the server**: webhook, `getCheckout(id).status === "paid"`,
51
+ or a verified receipt. Never because the URL says `?session=…`.
52
+ 5. **Sellable means**: a landing page with the price and a Pay button above the fold, one offering
53
+ with a receipt-checked endpoint, a webhook that delivers, the agent configured. Run
54
+ [checklists/sellable.md](checklists/sellable.md) before saying "done".
55
+
56
+ ## When the user is not a developer
57
+
58
+ Do the work; don't ask them to edit code. Ask at most: what they sell, to whom, for how many TON.
59
+ Then build one page, run `npx ambasada dev`, open it, pay as the fake visitor, and show them it worked.
60
+ Explain in one sentence per step, no jargon.
@@ -0,0 +1,51 @@
1
+ # Sellable checklist
2
+
3
+ Run it before saying "done". Check each line yourself; don't assume. Every line needs `npx ambasada dev`
4
+ running, except the last section.
5
+
6
+ ## The page
7
+
8
+ - [ ] `/` on a phone-sized window (375 px wide) shows, without scrolling: what it is, the price in TON, the Pay button.
9
+ - [ ] The price on the page equals the price charged (the Checkout `amount`, or the offering's `price`).
10
+ - [ ] One offer. Any second offer has its own price next to its own button.
11
+ - [ ] No login, no signup, no "coming soon", no lorem ipsum, no broken images.
12
+
13
+ ## People can pay
14
+
15
+ - [ ] Pressing Pay opens the Village approve page (`/pay/cs_…` or `/pay/pl_…`); approving as the fake visitor works.
16
+ - [ ] After paying, `/thanks` shows the result, or "working on it" and then the result.
17
+ - [ ] `/thanks?session=` with a made-up or unpaid session does **not** show the result.
18
+ - [ ] The webhook route verifies the signature (`verifyWebhook`) and delivers on `checkout.paid`; a
19
+ repeated event (same `webhook-id`) does not deliver twice.
20
+ - [ ] The Checkout `description` has no personal data (it is public in the ledger).
21
+
22
+ ## Agents can pay
23
+
24
+ - [ ] At least one offering in the offerings list that `ambasada.config.ts` points to, with
25
+ `description`, `price`, `endpoint`, `input` and `output`.
26
+ - [ ] Its route uses `sell(...)` or `verifyReceipt(...)`: a plain `curl -X POST` to it gets `402`.
27
+ - [ ] `npx ambasada dev buy <offering> '<json input>'` succeeds and the output matches `output`.
28
+ - [ ] On failure it answers non-2xx (the buyer is refunded), never 200 with an error inside.
29
+
30
+ ## Safe
31
+
32
+ - [ ] `grep -rn "sk_live_\|sk_test_\|whsec_" app components lib public src web` finds nothing
33
+ (keys live only in `.env` / `.dev.vars`), and nothing is named `NEXT_PUBLIC_*KEY*`.
34
+ - [ ] `.dev.vars` and `.env*` are in `.gitignore`.
35
+ - [ ] No banned Next.js features: `grep -rn "next/image\|revalidate\|runtime = \"edge\"\|from \"fs\"\|child_process" app lib`
36
+ finds nothing (or only `images: { unoptimized: true }` in `next.config.ts`).
37
+
38
+ ## The agent
39
+
40
+ - [ ] `agent/agent.md` says what you sell, your price range (minimum and maximum) and what the agent may buy.
41
+ - [ ] `agent/settings.json` `limits` are sane for your wallet (defaults: 10 TON per payment, 100 per day,
42
+ 5 automatic per payment).
43
+
44
+ ## Online (after `npx ambasada deploy`)
45
+
46
+ - [ ] `https://<slug>.hackaton.ambasada.id` loads on a phone.
47
+ - [ ] `npx ambasada offerings list`: every endpoint starts with your public URL, none with `localhost`.
48
+ - [ ] `npx ambasada webhook test` prints `delivered`.
49
+ - [ ] One real payment by someone outside your team (members can't buy from their own company) shows up
50
+ in `npx ambasada ledger`, and they got what they paid for.
51
+ - [ ] `npx ambasada logs --agent` shows the agent woke up.
@@ -0,0 +1,82 @@
1
+ # Built in ChatGPT, Lovable, Bolt or v0
2
+
3
+ Three ways, easiest first. Replace `pl_XXXX`, prices and names with yours.
4
+
5
+ ## A. Keep it hosted there, add a Pay button
6
+
7
+ Good for: a page that sells something simple (a poster, a reading, a coffee for the team).
8
+
9
+ 1. On a laptop with Node 22+, once:
10
+
11
+ ```sh
12
+ npm create ambasada@latest my-shop # you need its ambasada.config.ts and agent/ folder
13
+ cd my-shop && npm install
14
+ npx ambasada login
15
+ npx ambasada links create --amount 2 --description "Your name as a folk poem" --success-url https://YOUR-SITE/thanks
16
+ # → pl_XXXX
17
+ ```
18
+
19
+ 2. Paste this into the builder's chat:
20
+
21
+ > Add a "Pay 2 TON" button to the top of the home page, visible without scrolling on a phone.
22
+ > Load `<script src="https://hackaton.ambasada.id/pay.js" defer></script>` on every page (in the HTML head
23
+ > or index.html). Render the button as `<button data-village-pay="pl_XXXX">Pay 2 TON</button>`.
24
+ > Make a `/thanks` page that also loads pay.js. Listen for the `village:paid` event on `document`
25
+ > (`event.detail` has `session` and `receipt`): when it fires, show the result. On `village:cancelled`,
26
+ > do nothing. Do not add Stripe or any other payment provider. Do not add login.
27
+
28
+ 3. Register the site with the Village:
29
+
30
+ ```sh
31
+ npx ambasada link https://YOUR-SITE
32
+ ```
33
+
34
+ It warns if `https://YOUR-SITE/health` doesn't answer; for a page with only a Pay button that's fine.
35
+ It also lists the template's `translate` offering at your site, which has no such endpoint: delete it
36
+ from the offerings list first, or add `--no-sync`.
37
+
38
+ Limit: anything unlocked only in the browser can be unlocked by anyone who reads the page's code. Fine for
39
+ a thank-you and a small gift; for downloads, credits or paid AI work, use B or C.
40
+
41
+ ## B. Bring the code home (recommended for Lovable and Bolt)
42
+
43
+ Good for: anything with real work behind the button. You get Village hosting and the full rules.
44
+
45
+ 1. Export the project to GitHub (Lovable: GitHub sync; Bolt: download or GitHub) and clone it.
46
+ 2. `npm create ambasada@latest my-shop -- --template spa` and copy the exported `src/` (components,
47
+ pages, styles) into the new project's `src/`, replacing the starter UI. Keep the template's `api/`,
48
+ `agent/`, `ambasada.config.ts`, `wrangler.jsonc` and `vite.config.ts`.
49
+ 3. Open the project in Claude Code, Cursor or Codex and say:
50
+
51
+ > This is a Village product (see .claude/skills/ambasada-product). Wire the Pay button in my UI to the
52
+ > template's checkout endpoint, remove Supabase/Firebase and any login, store data with ambasada.store,
53
+ > then run the sellable checklist.
54
+
55
+ 4. `npx ambasada dev`, test a payment, then `npx ambasada deploy`.
56
+
57
+ ## C. ChatGPT (or any chat) writes the code, you paste it
58
+
59
+ 1. `npm create ambasada@latest my-shop`, `cd my-shop && npm install`, `npx ambasada dev`.
60
+ 2. Paste this into ChatGPT, then the two files it mentions, then your idea:
61
+
62
+ > I have a Next.js 16 App Router project that runs on Cloudflare Workers (OpenNext). Rules: no
63
+ > database, no `fs`, no native packages, no `next/image` (use `<img>`), no middleware, no `revalidate`,
64
+ > no login. Server code gets the payments client from `lib/ambasada.ts`; it never runs in the browser.
65
+ > Below are my `app/page.tsx` and `app/api/checkout/route.ts`. Keep the checkout route and call it the
66
+ > way the current page does. Rewrite `app/page.tsx` for my idea: what I sell, the price in TON and
67
+ > the Pay button, all visible on a phone without scrolling. After paying, the buyer lands on
68
+ > `app/thanks/page.tsx`; keep its check that the session is `"paid"` before showing the result.
69
+ > Write complete files, one code block per file, with the file path above each.
70
+ > My idea: …
71
+
72
+ 3. Paste each file where ChatGPT says, keep `npx ambasada dev` running, check the page, pay as the fake
73
+ visitor. When it works: `npx ambasada deploy`.
74
+
75
+ Use ChatGPT to write files, not to decide the stack: if its answer adds Prisma, Supabase, NextAuth,
76
+ `next/image` or a `middleware.ts`, tell it "that's banned in my project, do it without".
77
+
78
+ ## Connect your AI tool to the Village (optional)
79
+
80
+ The Village MCP server (`https://hackaton.ambasada.id/mcp`) lets Claude, ChatGPT, Cursor or Lovable read
81
+ your wallet, create offerings and set up your agent while you build. Setup per tool: `docs/CONNECT.md`
82
+ in the Village repo. It is for building only; your published site never uses it.
@@ -0,0 +1,51 @@
1
+ # Design for people who pay in 10 seconds
2
+
3
+ Visitors open your product on a phone, inside the Village app, between two other stands. They decide
4
+ in seconds. Build for that.
5
+
6
+ ## One page, one offer
7
+
8
+ - **One page.** Everything a buyer needs fits on the landing page. Extra pages only for `/thanks` and
9
+ maybe one result page.
10
+ - **One offer.** One thing, one price. "Your name as a Romanian folk poem, 2 TON". Not a menu of six.
11
+ Add a second offer only after the first one sells.
12
+ - **Above the fold, on a phone (375 × 667):** what it is (one line), the price in TON, the Pay button.
13
+ Nothing to scroll past, no signup, no cookie banner.
14
+ - **Few words.** A headline of up to 8 words, one sentence under it, the button. Cut every adjective
15
+ you can.
16
+ - **Show the result.** One example of what the buyer gets (an image, a sample text) beats any description.
17
+
18
+ ## The button
19
+
20
+ - Label it with the action and the price: **Pay 2 TON**, or **Get my poem: 2 TON**.
21
+ - Big (at least 48 px tall), full width on phones, one strong colour, nothing else in that colour.
22
+ - If the buyer must type something first (a name, a photo), put that one field right above the button.
23
+ - After paying, land on `/thanks` with the result, or "Working on it…" and the result appearing when ready.
24
+ Never a blank page.
25
+
26
+ ## Look
27
+
28
+ - Plain CSS is enough. One font (the system font is fine), two colours plus grey, generous spacing.
29
+ - Large text: 18 px body on phones, a headline twice that.
30
+ - Dark text on a light background unless your idea needs otherwise.
31
+ - Works with one thumb: tap targets 48 px, no hover-only menus.
32
+ - Loads fast: no video backgrounds, no 3 MB hero images, no animation libraries.
33
+
34
+ ## Words
35
+
36
+ - Say what they get, not how it works. "A logo for your stand in 1 minute", not "AI-powered logo generation".
37
+ - Prices are TON, written as `2 TON`. TON is the Village play currency; never call it crypto.
38
+ - Write in the language of your buyers. At Hack A Ton that is often Romanian and English; one is fine.
39
+
40
+ ## Before and after
41
+
42
+ ```
43
+ Bad: Welcome to PoemForge™, the next-generation AI-powered creative writing platform
44
+ [Features] [Pricing] [About] [Sign up] …scroll… [Get started]
45
+
46
+ Good: Your name as a folk poem
47
+ Type a name. Get a 4-line Romanian folk poem in 10 seconds.
48
+ [ Name: _________ ]
49
+ [ Get my poem: 2 TON ]
50
+ "Ana, floare de la munte, / …" ← one real example
51
+ ```
@@ -0,0 +1,105 @@
1
+ # Run it, put it online
2
+
3
+ ## `ambasada dev`: everything on your laptop, fake money
4
+
5
+ ```sh
6
+ npx ambasada dev
7
+ ```
8
+
9
+ Starts two things:
10
+
11
+ 1. a **mock Village** on `http://127.0.0.1:8788`: same routes and signatures as the real one, a fake bank
12
+ (your company 500 TON, the fake visitor `p_visitor` 100, `fake-agent` 100), the real `pay.js`, and an
13
+ LLM that answers `[mock] …`. Open it in a browser for a dashboard: wallets, offerings, calls.
14
+ 2. **your site** with its normal dev server (`next dev` for the `next` template), pointed at the mock.
15
+
16
+ It registers your offerings with the mock. No login, no real TON.
17
+
18
+ Try a sale:
19
+
20
+ - **as a person**: open your site, press Pay, approve as the fake visitor, land on `/thanks`.
21
+ - **as an agent**: `npx ambasada dev buy translate '{"text":"Bună!","to":"en"}'` (the fake agent pays 1 TON).
22
+
23
+ `npx ambasada dev --mock-only` starts only the mock, if you run your site yourself. The mock's keys:
24
+ `AMBASADA_API_URL=http://127.0.0.1:8788`, `AMBASADA_API_KEY=sk_test_dev` (already in the project's `.env`).
25
+ More on the mock: the ambasada-payments skill, `reference/test-mode.md`.
26
+
27
+ ## `ambasada deploy`: online at `<slug>.hackaton.ambasada.id`
28
+
29
+ Once, per laptop:
30
+
31
+ ```sh
32
+ npx ambasada login # opens a page: sign in with Ambasada ID, pick your company
33
+ ```
34
+
35
+ (If it asks for a key instead: the Village app → your team → Developers → Server keys, `sk_live_…`.)
36
+
37
+ Then, every time you want the new version online:
38
+
39
+ ```sh
40
+ npx ambasada deploy
41
+ ```
42
+
43
+ It does, in order:
44
+
45
+ 1. **build**: `opennextjs-cloudflare build` (Next), `vite build` (spa) or a bundle of the Worker (worker);
46
+ 2. **smoke test**: runs the built Worker locally in workerd and requests `/` and `/api/receipt`;
47
+ a crash stops the deploy (skip with `--no-smoke` only if you know why it fails);
48
+ 3. **upload** to the Village's hosting: the URL is `https://<your-company-slug>.hackaton.ambasada.id`,
49
+ with `AMBASADA_API_KEY`, `AMBASADA_API_URL` and `AMBASADA_COMPANY` set for you;
50
+ 4. **agent and offerings**: pushes `agent/agent.md` + `agent/settings.json`, and lists your offerings at the new URL.
51
+
52
+ No Cloudflare account needed. Options: `--agent-only` (push only `agent/`), `--offerings-only` (only the
53
+ offerings list), `--dry-run` (print what it would push).
54
+
55
+ After the first deploy, point the webhook at it and deploy again so the Worker gets the secret:
56
+
57
+ ```sh
58
+ npx ambasada webhook set https://<slug>.hackaton.ambasada.id/api/webhook
59
+ npx ambasada webhook test # should print "delivered"
60
+ ```
61
+
62
+ Limits on Village hosting: 10 MB compressed bundle, 1000 static files, short CPU time per request
63
+ (waiting on `fetch` or the LLM does not count). Heavy work belongs in an API you call, not in the Worker.
64
+
65
+ ## `ambasada deploy --own`: your own Cloudflare account
66
+
67
+ Runs `wrangler deploy` into your account (you need `npx wrangler login` first), then
68
+ `ambasada link <your workers.dev URL>`. Set the secrets yourself:
69
+
70
+ ```sh
71
+ npx wrangler secret put AMBASADA_API_KEY
72
+ npx wrangler secret put AMBASADA_COMPANY
73
+ npx wrangler secret put AMBASADA_WEBHOOK_SECRET
74
+ ```
75
+
76
+ ## `ambasada link`: the product is hosted somewhere else
77
+
78
+ ```sh
79
+ npx ambasada link https://my-thing.lovable.app
80
+ ```
81
+
82
+ Sets `product.url` in `ambasada.config.ts`, checks that `<url>/health` answers, and lists your offerings
83
+ at that URL (relative endpoints like `/api/translate` become `<url>/api/translate`). Use it for Lovable,
84
+ Bolt, v0, Vercel, your own server. See [build-elsewhere.md](build-elsewhere.md).
85
+
86
+ ## Watch it run
87
+
88
+ ```sh
89
+ npx ambasada wallet # balance, held, available
90
+ npx ambasada ledger # every TON in and out
91
+ npx ambasada logs --agent --follow # what your agent thinks and does
92
+ npx ambasada steer "raise translate to 1.2 TON for 2 hours"
93
+ ```
94
+
95
+ ## When it goes wrong
96
+
97
+ | Symptom | Fix |
98
+ |---|---|
99
+ | `deploy` prints "Product hosting is not part of `ambasada deploy` yet" | your CLI is old: `npm i -D @ambasada/cli@latest` |
100
+ | build fails on `fs`, `child_process`, a `.node` file, `sharp` | a banned package: see [stack.md](stack.md#nextjs-allowed-and-banned) |
101
+ | smoke test: `/` answers 500 | open `npx ambasada dev`, load `/`, read the terminal error |
102
+ | `CONFIG: no API key` | missing `AMBASADA_API_KEY`: `.env` locally, `ambasada login` before deploying |
103
+ | Pay works, nothing delivered | webhook not set or wrong secret: `ambasada webhook test`, then deploy again |
104
+ | agents can't buy | `ambasada offerings list`: endpoint must be your public URL, not `localhost` |
105
+ | `402` when you call your own offering | right: offerings only answer calls that come through the Gateway with a receipt; test with `ambasada dev buy` |