@ambasada/sdk 0.7.5 → 0.8.1

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/README.md CHANGED
@@ -1,30 +1,29 @@
1
1
  # @ambasada/sdk
2
2
 
3
- Accept and spend TON in the Hack A Ton village. Node ≥ 22, Bun, Cloudflare Workers, Next.js route handlers.
4
- No runtime dependencies (the build bundles the receipt code from `@ambasada/protocol`).
3
+ The Ambasada platform client: Sign in with Ambasada, mini-app notifications, the project store, files. Node ≥ 22,
4
+ Bun, Cloudflare Workers, Next.js route handlers. No runtime dependencies.
5
5
 
6
6
  ```ts
7
- import { village } from "@ambasada/sdk"; // AMBASADA_API_KEY, AMBASADA_API_URL (default https://hackaton.ambasada.id)
7
+ import { ambasada } from "@ambasada/sdk"; // AMBASADA_API_KEY, AMBASADA_API_URL (default https://api.ambasada.dev)
8
8
  ```
9
9
 
10
10
  | | |
11
11
  |---|---|
12
- | `village.sell(offering, handler)` | fetch handler for a paid endpoint: verifies `x-village-receipt` (402 otherwise), answers JSON |
13
- | `village.verifyReceipt(req, { offering })` | the same check by hand → receipt claims + parsed `input`; fetch `Request` or Node/Express raw body |
14
- | `village.checkout(input)` / `getCheckout(id)` / `createPaymentLink(…)` | Checkout for people (`POST /v1/checkout/sessions`) |
15
- | `village.verifyWebhook(req, secret?)` | Standard Webhooks signature (`AMBASADA_WEBHOOK_SECRET`) → `{ id, type, data }` |
16
- | `village.verifyCheckoutReceipt(token, { session })` | pay.js `village:paid` receipt |
17
- | `village.buy("company/offering", input, { maxPrice, idempotencyKey, parent })` | Pay through the Gateway |
18
- | `village.need(name, input, opts)` | buy whatever your agent routed `name` to |
19
- | `village.llm(prompt)` / `village.llm.chat(req)` | OpenAI-compatible chat, paid in TON |
20
- | `village.images.generate(req)`, `village.tools.list()` / `.call(id, input)`, `village.files.put(data)` | LLMOK utilities |
21
- | `village.listings.put(name, def)` / `.list()` | offerings |
22
- | `village.wallet()`, `village.ledger(since)`, `village.transfer(to, amount)` | money |
23
- | `village.agent.config/steer/transcript`, `village.market.search(q)` | agent and market (CLI) |
24
- | `village.auth.login(req)` / `.callback(req)` / `.user(req)` / `.logout(req)` | Sign in with Ambasada (below) |
25
-
26
- Errors are `VillageError` with `code` from `@ambasada/protocol` (`isVillageError(e, "PRICE_ABOVE_MAX")`), plus
27
- `ReceiptInvalidError` (402) and `WebhookInvalidError` (400). Workers: `createAmbasada({ env })`.
12
+ | `ambasada.auth.login(req)` / `.callback(req)` / `.user(req)` / `.logout(req)` / `.shell(req)` | Sign in with Ambasada (below) |
13
+ | `ambasada.apps.verifyWebhook(req)` / `.notify({ … })` | the Ambasada app's events and push notifications (docs/MINI-APPS.md §7) |
14
+ | `ambasada.store.get(key)` / `.put(key, value)` / `.delete(key)` / `.list(prefix)` | JSON key-value, 1 MB per app |
15
+ | `ambasada.files.put(data)` | a file at a public URL |
16
+ | `ambasada.project()`, `ambasada.keys()` | the app's id; the API's signing keys |
17
+ | `defineApp({ id, name, description, product })` | `ambasada.config.ts` |
18
+
19
+ Errors are `AmbasadaError` with `code` from `@ambasada/protocol` (`isAmbasadaError(e, "UNAUTHORIZED")`), and
20
+ `WebhookInvalidError` (400). Workers: `createAmbasada({ env })`. An app's own database, KV, files and Durable
21
+ Objects are Worker bindings (`env.DB`, `env.KV`, `env.FILES`; docs/STORAGE.md), not SDK calls.
22
+
23
+ The Hack A Ton economy (TON, checkout, offerings, the agent) is the add-on
24
+ [`@ambasada/village`](https://www.npmjs.com/package/@ambasada/village). Its 0.7 names (`village`, `createVillage`,
25
+ `VillageError`, `offering`, `defineCompany`, …) are still exported here, `@deprecated`, until 2.0. Note that
26
+ `createAmbasada` returns the platform client from 0.8; selling code uses `createVillage`.
28
27
 
29
28
  ## Sign in with Ambasada
30
29
 
@@ -34,15 +33,15 @@ setup on `https://<slug>.ambasada.dev`: `ambasada deploy` injects `AMBASADA_AUTH
34
33
  as "Fake Visitor". Fetch-style handlers for Workers and Next.js route handlers, WebCrypto only.
35
34
 
36
35
  ```ts
37
- const village = createAmbasada({ env });
36
+ const ambasada = createAmbasada({ env });
38
37
  // GET /api/auth/login?returnTo=/path → redirect to the Ambasada ID consent screen
39
- return village.auth.login(req, { returnTo: "/", email: true }); // email: also ask for their email
38
+ return ambasada.auth.login(req, { returnTo: "/", email: true }); // email: also ask for their email
40
39
  // GET /api/auth/callback → checks state, exchanges the code, reads the profile, sets the cookie
41
- return village.auth.callback(req);
40
+ return ambasada.auth.callback(req);
42
41
  // anywhere on the server
43
- const user = await village.auth.user(req); // AmbasadaUser | null
42
+ const user = await ambasada.auth.user(req); // AmbasadaUser | null
44
43
  // GET /api/auth/logout
45
- return village.auth.logout(req, { returnTo: "/" });
44
+ return ambasada.auth.logout(req, { returnTo: "/" });
46
45
  ```
47
46
 
48
47
  `AmbasadaUser` is `{ id, name, avatar_url, builder, builder_since, member_since, pass, email? }`: `id` is the
@@ -50,7 +49,7 @@ stable Ambasada ID (`user_…`), `pass` is `"builder" | "visitor" | "staff" | nu
50
49
  only if you asked and they agreed. The session is an HttpOnly `SameSite=Lax` cookie (`__Host-ambasada_session`
51
50
  on https) holding the signed profile (HMAC-SHA256, key derived from the client secret), valid 7 days; no token is
52
51
  kept. `returnTo` must be a path on your site. `user()` also takes `{ headers }` (Next server components:
53
- `village.auth.user({ headers: await headers() })`). `village.auth.configured` says whether credentials are set.
52
+ `ambasada.auth.user({ headers: await headers() })`). `ambasada.auth.configured` says whether credentials are set.
54
53
  Override them with `createAmbasada({ auth: { clientId, clientSecret, issuer, publicUrl } })`; `PUBLIC_URL` sets the
55
54
  origin of the callback URL when the Worker sees another host. More: docs/AUTH.md.
56
55
 
package/dist/apps.d.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  * dashboard shows it for apps hosted elsewhere; `ambasada dev` sets a fake one and plays the hub).
11
11
  * WebCrypto only; the wire format is in @ambasada/protocol (packages/protocol/src/apps.ts).
12
12
  */
13
- import type { HeaderSource } from "./webhook.js";
13
+ import type { HeaderSource } from "./headers.js";
14
14
  export declare const APP_SIGNATURE_HEADER = "x-ambasada-signature";
15
15
  export declare const APP_TIMESTAMP_HEADER = "x-ambasada-timestamp";
16
16
  export type AppEventType = "notifications_enabled" | "notifications_disabled" | "app_added" | "app_removed" | "ping";
@@ -104,7 +104,7 @@ export declare class AmbasadaApps {
104
104
  /**
105
105
  * Sends a push to the people behind `tokens` (or `users`, for Verified apps with `identity`) through the hub.
106
106
  * Checks the limits first (title ≤ 40, body ≤ 140, id ≤ 128, ≤ 100 recipients, `url` on your origin) and
107
- * throws `VillageError("INVALID_REQUEST")` before sending anything. Tokens in `invalid` are gone: forget them.
107
+ * throws `AmbasadaError("INVALID_REQUEST")` before sending anything. Tokens in `invalid` are gone: forget them.
108
108
  */
109
109
  notify(input: AppNotifyInput): Promise<AppNotifyResult>;
110
110
  }
package/dist/auth.d.ts CHANGED
@@ -4,10 +4,10 @@
4
4
  * AMBASADA_AUTH_CLIENT_SECRET and AMBASADA_AUTH_ISSUER; `ambasada dev` injects fake ones that point at
5
5
  * the mock. Four fetch handlers, WebCrypto only, no dependencies:
6
6
  *
7
- * GET /api/auth/login → return village.auth.login(req, { returnTo: "/" })
8
- * GET /api/auth/callback → return village.auth.callback(req)
9
- * GET /api/auth/logout → return village.auth.logout(req)
10
- * anywhere → const user = await village.auth.user(req) // AmbasadaUser | null
7
+ * GET /api/auth/login → return ambasada.auth.login(req, { returnTo: "/" })
8
+ * GET /api/auth/callback → return ambasada.auth.callback(req)
9
+ * GET /api/auth/logout → return ambasada.auth.logout(req)
10
+ * anywhere → const user = await ambasada.auth.user(req) // AmbasadaUser | null
11
11
  *
12
12
  * The session is an HttpOnly cookie holding the signed public profile (HMAC-SHA256 under a key
13
13
  * derived from the client secret), host-only (`__Host-` on https), so no other *.ambasada.dev product
@@ -16,8 +16,8 @@
16
16
  * Silent sign-in inside the Ambasada app (docs/MINI-APPS.md §5): the page asks the shell for a short-lived
17
17
  * token (`auth.getToken()` from @ambasada/miniapp) and POSTs it here; the same session cookie comes back:
18
18
  *
19
- * POST /api/auth/shell { token } → return village.auth.shell(req) // { user } + cookie
20
- * anywhere → const who = await village.auth.verifyShellToken(token) // { user_id, name, avatar }
19
+ * POST /api/auth/shell { token } → return ambasada.auth.shell(req) // { user } + cookie
20
+ * anywhere → const who = await ambasada.auth.verifyShellToken(token) // { user_id, name, avatar }
21
21
  */
22
22
  /** The person signed in, as `GET /v1/auth/profile` returns them. */
23
23
  export interface AmbasadaUser {
@@ -42,7 +42,7 @@ export interface AuthConfig {
42
42
  audiences?: string[];
43
43
  /** The hub, for the full profile behind a shell token (`/api/apps/profile`). */
44
44
  hubUrl?: string;
45
- /** Where `/v1/auth/profile` lives (the village API). */
45
+ /** Where `/v1/auth/profile` lives (the Ambasada API). */
46
46
  apiUrl: string;
47
47
  /** Your public origin if the Worker sees another host (behind a proxy). Default: the request's origin. */
48
48
  publicUrl?: string;
@@ -103,7 +103,7 @@ export declare class AmbasadaAuth {
103
103
  login(req: Request, options?: LoginOptions): Promise<Response>;
104
104
  /**
105
105
  * Ambasada ID sends the person back here: checks the state, exchanges the code (with the PKCE
106
- * verifier and the client secret), reads their profile from the village, sets the session cookie
106
+ * verifier and the client secret), reads their profile from the Ambasada API, sets the session cookie
107
107
  * and redirects to `returnTo`. A refused consent goes back to `returnTo` signed out.
108
108
  */
109
109
  callback(req: Request): Promise<Response>;
@@ -112,7 +112,7 @@ export declare class AmbasadaAuth {
112
112
  /**
113
113
  * Verifies a shell token (Clerk JWT template `ambasada-miniapp`, RS256, 60 s) against
114
114
  * `<AMBASADA_AUTH_ISSUER>/.well-known/jwks.json` (cached 1 h): signature, expiry, issuer, and that its `aud`
115
- * is this app (its id or origin). Returns who it is, or throws `VillageError("UNAUTHORIZED")`.
115
+ * is this app (its id or origin). Returns who it is, or throws `AmbasadaError("UNAUTHORIZED")`.
116
116
  */
117
117
  verifyShellToken(token: string, options?: {
118
118
  audience?: string | string[];
package/dist/client.d.ts CHANGED
@@ -1,23 +1,19 @@
1
1
  import { AmbasadaApps } from "./apps.js";
2
2
  import { AmbasadaAuth } from "./auth.js";
3
- import { type IncomingRequest } from "./receipt.js";
4
- import type { AgentConfigInput, BuyOptions, BuyResult, ChatRequest, ChatResponse, CheckoutInput, CheckoutReceiptClaims, CheckoutSession, FileOptions, StoredFile, StoreList, StoreWrite, ImageRequest, ImageResult, Jwk, LedgerPage, Listing, ListingInput, MarketOffering, PaidCall, PaymentLink, PaymentLinkInput, ToolListing, ToolResult, TranscriptEntry, TransferOptions, Wallet, WebhookDelivery, WebhookEvent } from "./types.js";
5
- export declare const DEFAULT_API_URL = "https://hackaton.ambasada.id";
6
- export declare const DEFAULT_MODEL = "deepseek-v4.1-flash";
7
- export interface VillageOptions {
8
- /** Company API key. Default: `AMBASADA_API_KEY`. */
3
+ import type { FileOptions, Jwk, StoredFile, StoreList, StoreWrite } from "./types.js";
4
+ export declare const DEFAULT_API_URL = "https://api.ambasada.dev";
5
+ export interface AmbasadaOptions {
6
+ /** Project key (`vk_…` from developer.ambasada.dev, or `ambasada login`). Default: `AMBASADA_API_KEY`. */
9
7
  apiKey?: string;
10
- /** Default: `AMBASADA_API_URL`, else https://hackaton.ambasada.id. `ambasada dev` serves http://localhost:8788. */
8
+ /** Default: `AMBASADA_API_URL`, else https://api.ambasada.dev. `ambasada dev` serves http://localhost:8788. */
11
9
  apiUrl?: string;
12
- /** Your company id, for receipt checks. Default: `AMBASADA_COMPANY`, else looked up once with the API key. */
13
- company?: string;
14
- /** Webhook signing secret (`whsec_…`). Default: `AMBASADA_WEBHOOK_SECRET`. */
15
- webhookSecret?: string;
16
- /** Receipt keys; default: `<apiUrl>/.well-known/jwks.json`, cached 10 minutes. */
10
+ /** Your app's id. Default: `AMBASADA_APP_ID`, else looked up once with the API key. */
11
+ project?: string;
12
+ /** Signing keys; default: `<apiUrl>/.well-known/jwks.json`, cached 10 minutes. */
17
13
  keys?: Jwk[] | (() => Promise<Jwk[]>);
18
14
  /** Custom fetch (tests, proxies). */
19
15
  fetch?: typeof fetch;
20
- /** Per-request timeout in ms (default 45 s; Pay calls may take 30 s at the seller). */
16
+ /** Per-request timeout in ms (default 45 s). */
21
17
  timeoutMs?: number;
22
18
  /** Extra env source, e.g. a Worker's `env`. Default: `process.env` where it exists. */
23
19
  env?: Record<string, unknown>;
@@ -36,26 +32,34 @@ export interface VillageOptions {
36
32
  id?: string;
37
33
  };
38
34
  }
39
- export interface VerifyReceiptOptions {
40
- /** Pin the offering name (`translate`), so a receipt for another of your offerings is refused. */
41
- offering?: string;
42
- /** Refuse a second use of the same receipt in this process (default true). Persist call ids yourself across instances. */
43
- replayGuard?: boolean;
35
+ export type Method = "GET" | "POST" | "PUT" | "DELETE";
36
+ export interface RequestOptions {
37
+ body?: unknown;
38
+ raw?: NonNullable<RequestInit["body"]>;
39
+ headers?: Record<string, string | undefined>;
40
+ query?: Record<string, string | number | undefined>;
41
+ auth?: boolean;
42
+ signal?: AbortSignal;
43
+ /** Retry once on a network error (only safe with an idempotency key). */
44
+ retry?: boolean;
45
+ }
46
+ export interface RawResponse {
47
+ status: number;
48
+ headers: Headers;
49
+ json: unknown;
44
50
  }
45
- export type SellHandler<Input, Output> = (input: Input, call: PaidCall<Input>, req: Request) => Output | Response | Promise<Output | Response>;
46
51
  /**
47
- * The village client. `import { village } from "@ambasada/sdk"` gives one configured from the
48
- * environment; `createAmbasada({ apiKey, apiUrl })` makes another (e.g. from a Worker's `env`).
52
+ * The Ambasada client. `import { ambasada } from "@ambasada/sdk"` gives one configured from the
53
+ * environment; `createAmbasada({ env })` makes another (e.g. from a Worker's `env`).
49
54
  */
50
- export declare class Village {
51
- private readonly options;
55
+ export declare class Ambasada {
56
+ protected readonly options: AmbasadaOptions;
52
57
  private keyLoader?;
53
- private companyPromise?;
54
- private readonly replays;
55
- constructor(options?: VillageOptions);
56
- private env;
58
+ private projectPromise?;
59
+ constructor(options?: AmbasadaOptions);
60
+ protected env(name: string): string | undefined;
57
61
  get apiUrl(): string;
58
- private get apiKey();
62
+ protected get apiKey(): string;
59
63
  /**
60
64
  * Sign in with Ambasada (docs/AUTH.md): `login(req)`, `callback(req)`, `user(req)`, `logout(req)`.
61
65
  * Credentials come with the deploy to ambasada.dev, and from `ambasada dev` locally.
@@ -68,139 +72,35 @@ export declare class Village {
68
72
  * `notify({ … })` to push to people who turned notifications on. Uses `AMBASADA_APP_SECRET`.
69
73
  */
70
74
  readonly apps: AmbasadaApps;
71
- private send;
72
- private call;
73
- /** Your company id (from options, `AMBASADA_COMPANY`, or the wallet the API key belongs to). */
74
- company(): Promise<string>;
75
- /** Village receipt keys (cached). */
75
+ protected send(method: Method, path: string, opts?: RequestOptions): Promise<RawResponse>;
76
+ protected call<T>(method: Method, path: string, opts?: RequestOptions): Promise<T>;
77
+ /** Your app's id (from options, `AMBASADA_APP_ID`, or the project the API key belongs to). */
78
+ project(): Promise<string>;
79
+ /** The API's signing keys (`/.well-known/jwks.json`, cached). */
76
80
  keys(): Promise<Jwk[]>;
77
- private keySource;
78
- /**
79
- * Verifies that a request to your endpoint was paid through the Gateway and returns the receipt
80
- * with the parsed JSON `input`. Throws `ReceiptInvalidError` (status 402) otherwise.
81
- * Works with a fetch `Request` (not consumed) or a Node/Express request with a raw body.
82
- */
83
- verifyReceipt<Input = any>(req: IncomingRequest, options?: VerifyReceiptOptions): Promise<PaidCall<Input>>;
84
- /**
85
- * Wraps a paid offering as a fetch handler: verifies the receipt (402 if missing or bad), runs
86
- * `handler(input, call, req)` and answers JSON. A thrown `VillageError` keeps its status (the
87
- * Gateway then refunds the buyer).
88
- *
89
- * export default village.sell("translate", async ({ text, to }) => ({ text: await village.llm(`…`) }));
90
- */
91
- sell<Input = any, Output = unknown>(offering: string, handler: SellHandler<Input, Output>): (req: Request) => Promise<Response>;
92
- /** Verifies a signed webhook (`checkout.paid`, `webhook.test`) and returns the event. Throws `WebhookInvalidError`. */
93
- verifyWebhook<Data = any>(req: IncomingRequest, secret?: string): Promise<WebhookEvent<Data>>;
94
- /** Verifies a checkout receipt (pay.js `village:paid` → `e.detail.receipt`, or `session.receipt`). */
95
- verifyCheckoutReceipt(token: string | null | undefined, options?: {
96
- session?: string;
97
- }): Promise<CheckoutReceiptClaims>;
98
- /** Creates a Checkout session; redirect the person to `session.url`. Deliver on the webhook or `getCheckout(id).status === "paid"`. */
99
- checkout(session: CheckoutInput, options?: {
100
- idempotencyKey?: string;
101
- }): Promise<CheckoutSession>;
102
- getCheckout(id: string): Promise<CheckoutSession>;
103
- /** A reusable payment link for static pages (`<button data-village-pay="pl_…">` with pay.js). */
104
- createPaymentLink(link: PaymentLinkInput): Promise<PaymentLink>;
105
- /** Cancels an open session (`POST /v1/checkout/sessions/:id/expire`). */
106
- expireCheckout(id: string): Promise<CheckoutSession>;
107
- listPaymentLinks(): Promise<PaymentLink[]>;
108
- deactivatePaymentLink(id: string): Promise<PaymentLink>;
109
- /** Where checkout webhooks go. Returns the signing secret (the same one until `rotateSecret`). */
110
- setWebhook(url: string, options?: {
111
- rotateSecret?: boolean;
112
- }): Promise<{
113
- url: string;
114
- secret: string;
115
- }>;
116
- /** The webhook URL, its secret and the last deliveries. */
117
- getWebhook(): Promise<{
118
- url: string | null;
119
- secret: string | null;
120
- deliveries: WebhookDelivery[];
121
- }>;
122
- deleteWebhook(): Promise<{
123
- deleted: true;
124
- }>;
125
- /** Sends a `webhook.test` event now and returns the delivery. */
126
- testWebhook(): Promise<WebhookDelivery>;
127
- retryWebhookDelivery(id: string): Promise<WebhookDelivery>;
128
- /** Buys one call of `company/offering` through the Gateway. Money moves only if the seller answers 2xx with valid output. */
129
- buy<Output = any>(offering: string, input: unknown, options?: BuyOptions): Promise<BuyResult<Output>>;
130
- /**
131
- * Buys whatever your agent routed the need `name` to (or the best offering within `maxPrice` if it
132
- * has not routed it yet), with fallbacks (EXCHANGE.md §4.3). Prefer this over hardcoding a supplier.
133
- */
134
- need<Output = any>(name: string, input: unknown, options?: BuyOptions): Promise<BuyResult<Output>>;
135
- private paid;
136
- /**
137
- * `village.llm("prompt")` → the answer text; `village.llm.chat(req)` → the full OpenAI-style response.
138
- * Default model: deepseek-v4.1-flash.
139
- */
140
- readonly llm: ((prompt: string, options?: Omit<ChatRequest, "messages"> & {
141
- system?: string;
142
- }) => Promise<string>) & {
143
- chat(req: ChatRequest): Promise<ChatResponse>;
144
- };
145
- readonly images: {
146
- /** Generates an image; the URL is re-hosted in the village files store. Default model: nano_banana. */
147
- generate: (req: ImageRequest) => Promise<ImageResult>;
148
- };
149
- readonly tools: {
150
- /** The curated LLMOK tool catalog with TON prices. */
151
- list: () => Promise<ToolListing[]>;
152
- /** Runs one tool; debits your wallet only on success. Replays with the same idempotency key are free. */
153
- call: <Output = any>(id: string, input: unknown, options?: {
154
- idempotencyKey?: string;
155
- }) => Promise<ToolResult<Output>>;
156
- };
157
81
  readonly files: {
158
82
  /**
159
- * Stores bytes, a Blob, or a URL to copy, for the event (`POST /v1/files`, 0.1 TON). Returns a
160
- * public URL other products can fetch.
83
+ * Stores bytes, a Blob, or a URL to copy (`POST /v1/files`). Returns a public URL. For an app's own
84
+ * uploads prefer its bucket, `env.FILES` (docs/STORAGE.md).
161
85
  */
162
86
  put: (data: Uint8Array | ArrayBuffer | Blob | string, options?: FileOptions) => Promise<StoredFile>;
163
87
  };
164
88
  /**
165
- * Your company's JSON key-value store (`/v1/store`): 1 MB per company (keys + values), free. State for
166
- * a hosted product that needs no database.
89
+ * Your project's JSON key-value store (`/v1/store`): 1 MB per project (keys + values), free. For more,
90
+ * use the app's own D1, KV and R2 (docs/STORAGE.md).
167
91
  */
168
92
  readonly store: {
169
93
  /** The value, or null when the key is not set. */
170
94
  get: <T = unknown>(key: string) => Promise<T | null>;
171
95
  /** Saves any JSON value (fails with INVALID_REQUEST when the store would pass 1 MB). */
172
96
  set: (key: string, value: unknown) => Promise<StoreWrite>;
97
+ /** `set` under its other name. */
98
+ put: (key: string, value: unknown) => Promise<StoreWrite>;
173
99
  /** True when the key existed. */
174
100
  delete: (key: string) => Promise<boolean>;
175
101
  /** Keys (up to 1000, optionally under a prefix) and how much of the 1 MB is used. */
176
102
  list: (prefix?: string) => Promise<StoreList>;
177
103
  };
178
- readonly listings: {
179
- /** Creates or updates an offering (price changes are versioned). */
180
- put: (name: string, def: ListingInput) => Promise<Listing>;
181
- list: () => Promise<Listing[]>;
182
- };
183
- wallet(): Promise<Wallet>;
184
- ledger(since?: number, limit?: number): Promise<LedgerPage>;
185
- transfer(to: string, amount: number, options?: TransferOptions): Promise<{
186
- entry: unknown;
187
- }>;
188
- readonly market: {
189
- search: (query: string) => Promise<MarketOffering[]>;
190
- };
191
- readonly agent: {
192
- /** Replaces the agent's prompt (`agent.md`) and settings (`settings.json`). */
193
- config: (config: AgentConfigInput) => Promise<{
194
- config: unknown;
195
- limits?: unknown;
196
- }>;
197
- /** A message the agent answers on its next wake. */
198
- steer: (text: string) => Promise<unknown>;
199
- transcript: (since?: number, limit?: number) => Promise<{
200
- entries: TranscriptEntry[];
201
- next_since: number;
202
- }>;
203
- state: () => Promise<Record<string, unknown>>;
204
- };
205
104
  }
206
- export declare function createAmbasada(options?: VillageOptions): Village;
105
+ export declare function cachedKeys(load: () => Promise<Jwk[]>, ttlMs?: number): () => Promise<Jwk[]>;
106
+ export declare function createAmbasada(options?: AmbasadaOptions): Ambasada;
package/dist/define.d.ts CHANGED
@@ -1,44 +1,19 @@
1
1
  /**
2
- * Project config helpers (DESIGN.md §2.3). They only add types and checks; the CLI reads the values
3
- * (`ambasada deploy`, `ambasada offerings sync`, `ambasada dev`).
2
+ * `ambasada.config.ts` (docs/PLATFORM.md §3). Types and checks only; the CLI reads the values
3
+ * (`ambasada dev`, `deploy`, `link`, `check`). Add-ons add their own keys (the village: `idea`, `offerings`, `agent`).
4
4
  */
5
- import type { FieldSchema } from "./types.js";
6
- export interface CompanyConfig {
7
- /** Your company id in the village (lowercase slug). */
5
+ export interface AppConfig {
6
+ /** The app's slug (lowercase): it lives at https://<id>.ambasada.dev. */
8
7
  id: string;
9
8
  /** Display name. */
10
9
  name: string;
11
- /** Locked Fri 20:00. */
12
- idea: {
13
- title: string;
14
- category?: string;
15
- pitch?: string;
16
- };
17
- /** Where your product lives; offering endpoints are relative to it. */
10
+ /** One line about the app: the manifest's subtitle and description (`ambasada init` writes them from it). */
11
+ description?: string;
12
+ /** Where the app runs; `ambasada link` sets it when you host elsewhere. */
18
13
  product?: {
19
14
  url?: string;
20
15
  };
21
- /** Path to the offerings module (default `./product/offerings.ts`). */
22
- offerings?: string;
23
- /** Agent folder with agent.md and settings.json (default `./agent`). */
24
- agent?: {
25
- dir?: string;
26
- };
27
- }
28
- export declare function defineCompany(config: CompanyConfig): CompanyConfig;
29
- export interface OfferingDef {
30
- /** Slug: a-z, 0-9, _ and -. */
31
- name: string;
32
- /** TON per call, 0.1–500. */
33
- price: number;
34
- /** Path on your product (`/api/translate`) or an absolute URL. */
35
- endpoint: string;
36
- description: string;
37
- input?: FieldSchema;
38
- output?: FieldSchema;
39
- /** Allow a free sample (`market.preview`). */
40
- preview?: boolean;
41
- timeout_ms?: number;
42
- status?: "open" | "paused";
16
+ /** Add-ons to load besides the ones found in node_modules (package names, e.g. "./village/index.js"). */
17
+ addons?: string[];
43
18
  }
44
- export declare function offering(def: OfferingDef): OfferingDef;
19
+ export declare function defineApp<T extends AppConfig>(config: T): T;
package/dist/errors.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Typed errors. Every village API failure is `{ "error": { "code", "message", ... } }` with a code
3
- * from `@ambasada/protocol` (packages/protocol/src/errors.ts); the SDK turns it into a `VillageError`
2
+ * Typed errors. Every Ambasada API failure is `{ "error": { "code", "message", ... } }` with a code
3
+ * from `@ambasada/protocol` (packages/protocol/src/errors.ts); the SDK turns it into an `AmbasadaError`
4
4
  * with the same `code`, so you branch on `error.code` instead of parsing messages:
5
5
  *
6
- * try { await village.buy("inkwell/poem", input, { maxPrice: 2 }) }
7
- * catch (e) { if (isVillageError(e, "PRICE_ABOVE_MAX")) … }
6
+ * try { await ambasada.store.set("settings", value) }
7
+ * catch (e) { if (isAmbasadaError(e, "INVALID_REQUEST")) … }
8
8
  *
9
9
  * The list mirrors the protocol (a unit test keeps them in sync), plus codes the in-flight API
10
10
  * streams add (checkout, utilities, agent) and a few SDK-side ones (`NETWORK`, `CONFIG`,
@@ -62,12 +62,14 @@ export interface ErrorDetails {
62
62
  approval_id?: string;
63
63
  [key: string]: unknown;
64
64
  }
65
- export declare class VillageError extends Error {
65
+ export declare class AmbasadaError extends Error {
66
66
  readonly code: ErrorCode | (string & {});
67
67
  /** HTTP status (0 when the request never got an answer). */
68
68
  readonly status: number;
69
69
  readonly details: ErrorDetails;
70
70
  constructor(code: ErrorCode | (string & {}), message: string, status?: number, details?: ErrorDetails);
71
+ /** `instanceof AmbasadaError` holds across bundled copies; subclasses keep the usual prototype check. */
72
+ static [Symbol.hasInstance](value: unknown): boolean;
71
73
  get callId(): string | undefined;
72
74
  get refunded(): boolean;
73
75
  get approvalId(): string | undefined;
@@ -81,17 +83,12 @@ export declare class VillageError extends Error {
81
83
  /** A `Response` with the wire shape and status (402 for receipt problems). */
82
84
  toResponse(): Response;
83
85
  }
84
- export type ReceiptFailure = "malformed" | "unknown_key" | "bad_signature" | "expired" | "not_yet_valid" | "wrong_issuer" | "wrong_seller" | "wrong_offering" | "wrong_kind" | "wrong_session" | "body_mismatch" | "replayed" | "missing";
85
- /** The request is not paid: no receipt, a forged or expired one, or one for another seller / offering / body. */
86
- export declare class ReceiptInvalidError extends VillageError {
87
- readonly reason: ReceiptFailure;
88
- constructor(reason: ReceiptFailure, message: string);
89
- }
90
86
  export type WebhookFailure = "missing_headers" | "bad_timestamp" | "too_old" | "too_new" | "bad_signature" | "bad_secret" | "bad_body";
91
- export declare class WebhookInvalidError extends VillageError {
87
+ /** A signed webhook (app events, payments) failed its check. */
88
+ export declare class WebhookInvalidError extends AmbasadaError {
92
89
  readonly reason: WebhookFailure;
93
90
  constructor(reason: WebhookFailure, message: string);
94
91
  }
95
- export declare function isVillageError(value: unknown, code?: ErrorCode | (string & {})): value is VillageError;
96
- /** Builds a VillageError from an API response body (or a non-JSON failure). */
97
- export declare function errorFromResponse(status: number, body: unknown, fallback: string): VillageError;
92
+ export declare function isAmbasadaError(value: unknown, code?: ErrorCode | (string & {})): value is AmbasadaError;
93
+ /** Builds an AmbasadaError from an API response body (or a non-JSON failure). */
94
+ export declare function errorFromResponse(status: number, body: unknown, fallback: string): AmbasadaError;
@@ -0,0 +1,3 @@
1
+ /** Header lookup that works on a fetch `Headers` and on a Node / Express header record. */
2
+ export type HeaderSource = Headers | Record<string, string | string[] | undefined | null>;
3
+ export declare function getHeader(headers: HeaderSource, name: string): string | null;