@ambasada/village 0.8.4 → 0.8.6

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/dist/client.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Ambasada, type AmbasadaOptions } from "@ambasada/sdk/platform";
2
2
  import { type IncomingRequest } from "./receipt.js";
3
- import type { AgentConfigInput, BuyOptions, BuyResult, ChatRequest, ChatResponse, CheckoutInput, CheckoutReceiptClaims, CheckoutSession, ImageRequest, ImageResult, LedgerPage, Listing, ListingInput, MarketOffering, PaidCall, PaymentLink, PaymentLinkInput, ToolListing, ToolResult, TranscriptEntry, ChatLine, ChatProposal, ChatTurn, TransferOptions, Wallet, WebhookDelivery, WebhookEvent } from "./types.js";
3
+ import type { AgentConfigInput, BuyOptions, BuyResult, ChatRequest, ChatResponse, CheckoutInput, CheckoutReceiptClaims, CheckoutDelivery, CheckoutDeliveryInput, CheckoutSession, ImageRequest, ImageResult, LedgerPage, Listing, ListingInput, MarketOffering, PaidCall, PaymentLink, PaymentLinkInput, ToolListing, ToolResult, TranscriptEntry, ChatLine, ChatProposal, ChatTurn, TransferOptions, Wallet, WebhookDelivery, WebhookEvent } from "./types.js";
4
4
  /** The Hack A Ton API. The platform API (`@ambasada/sdk`) is https://api.ambasada.dev. */
5
5
  export declare const DEFAULT_VILLAGE_API_URL = "https://hackaton.ambasada.id";
6
6
  export declare const DEFAULT_MODEL = "deepseek-v4.1-flash";
@@ -63,6 +63,20 @@ export declare class Village extends Ambasada {
63
63
  idempotencyKey?: string;
64
64
  }): Promise<CheckoutSession>;
65
65
  getCheckout(id: string): Promise<CheckoutSession>;
66
+ /** Your latest Checkout sessions (newest first, default 20, at most 100), each with what you said you delivered. */
67
+ listCheckouts(options?: {
68
+ limit?: number;
69
+ status?: "open" | "paid" | "expired";
70
+ }): Promise<{
71
+ sessions: (CheckoutSession & {
72
+ delivery: Omit<CheckoutDelivery, "session"> | null;
73
+ })[];
74
+ }>;
75
+ /**
76
+ * Tells the village what a paid checkout delivered (`text` ≤ 4000 chars, an https `url`, or both): the person sees it
77
+ * with the purchase in their app and can rate it. Again for the same session replaces it. CONFLICT before it is paid.
78
+ */
79
+ deliverCheckout(id: string, delivery: CheckoutDeliveryInput): Promise<CheckoutDelivery>;
66
80
  /** A reusable payment link for static pages (`<button data-village-pay="pl_…">` with pay.js). */
67
81
  createPaymentLink(link: PaymentLinkInput): Promise<PaymentLink>;
68
82
  /** Cancels an open session (`POST /v1/checkout/sessions/:id/expire`). */
package/dist/define.d.ts CHANGED
@@ -38,6 +38,7 @@ export interface OfferingDef {
38
38
  output?: FieldSchema;
39
39
  /** Allow a free sample (`market.preview`). */
40
40
  preview?: boolean;
41
+ /** How long the Gateway waits for your answer, ms: 100–30000 (default 30000, the most). Longer work: answer fast, finish later. */
41
42
  timeout_ms?: number;
42
43
  status?: "open" | "paused";
43
44
  }
package/dist/index.js CHANGED
@@ -37,6 +37,7 @@ __export(src_exports, {
37
37
  HEADERS: () => HEADERS,
38
38
  HOSTED_MANIFEST: () => HOSTED_MANIFEST,
39
39
  ICON_SIZE: () => ICON_SIZE,
40
+ MANIFEST_AGE_RATINGS: () => MANIFEST_AGE_RATINGS,
40
41
  MANIFEST_CATEGORIES: () => MANIFEST_CATEGORIES,
41
42
  MANIFEST_ID: () => MANIFEST_ID,
42
43
  MANIFEST_KINDS: () => MANIFEST_KINDS,
@@ -49,8 +50,10 @@ __export(src_exports, {
49
50
  MIN_PRICE_NANO: () => MIN_PRICE_NANO,
50
51
  NANO_PER_TON: () => NANO_PER_TON,
51
52
  NOTIFY_LIMITS: () => NOTIFY_LIMITS,
53
+ OFFERING_TIMEOUT_MS: () => OFFERING_TIMEOUT_MS,
52
54
  RECEIPT_HEADER: () => RECEIPT_HEADER,
53
55
  ReceiptError: () => ReceiptError,
56
+ SDK_SCRIPT_LIMIT: () => SDK_SCRIPT_LIMIT,
54
57
  SHELL_TOKEN_TTL_S: () => SHELL_TOKEN_TTL_S,
55
58
  SLOW_ENTRY_MS: () => SLOW_ENTRY_MS,
56
59
  SYSTEM_ACCOUNT: () => SYSTEM_ACCOUNT,
@@ -61,8 +64,10 @@ __export(src_exports, {
61
64
  appNotifyResult: () => appNotifyResult,
62
65
  appWebhookHeaders: () => appWebhookHeaders,
63
66
  base64url: () => base64url,
67
+ bundlesMiniappSdk: () => bundlesMiniappSdk,
64
68
  checkHtml: () => checkHtml,
65
69
  checkIcon: () => checkIcon,
70
+ checkPending: () => checkPending,
66
71
  checkRemote: () => checkRemote,
67
72
  createReceiptSigner: () => createReceiptSigner,
68
73
  decodeLeaf: () => decodeLeaf,
@@ -82,6 +87,7 @@ __export(src_exports, {
82
87
  leafHash: () => leafHash,
83
88
  listingLine: () => listingLine,
84
89
  nodeHash: () => nodeHash,
90
+ offeringTimeoutProblem: () => offeringTimeoutProblem,
85
91
  parseCheckpoint: () => parseCheckpoint,
86
92
  parseVerifierKey: () => parseVerifierKey,
87
93
  pngInfo: () => pngInfo,
@@ -1143,6 +1149,7 @@ var MANIFEST_SCHEMA = 2;
1143
1149
  var MANIFEST_KINDS = ["product", "event", "tool", "assistant"];
1144
1150
  var MANIFEST_CATEGORIES = ["shopping", "food", "events", "tools", "learning", "community", "games", "finance", "other"];
1145
1151
  var MANIFEST_VISIBILITY = ["public", "builders", "unlisted"];
1152
+ var MANIFEST_AGE_RATINGS = ["4+", "9+", "12+", "17+"];
1146
1153
  var MANIFEST_LIMITS = { id: 32, name: 32, subtitle: 40, description: 170, tags: 5, screenshots: 3 };
1147
1154
  var MANIFEST_ID = /^[a-z0-9-]{3,32}$/;
1148
1155
  var HOSTED_MANIFEST = /^https:\/\/hub\.ambasada\.id\/api\/apps\/([a-z0-9-]+)\/manifest$/;
@@ -1203,6 +1210,15 @@ function validateManifest(json) {
1203
1210
  const color = splash?.color;
1204
1211
  if (!splash || color !== void 0 && (typeof color !== "string" || !HEX_COLOR.test(color))) problem("splash_color", `"splash.color" must be a #RRGGBB colour (got ${JSON.stringify(color ?? m.splash)})`);
1205
1212
  }
1213
+ const age = str("age_rating");
1214
+ if (m.age_rating === void 0) {
1215
+ if (m.schema === MANIFEST_SCHEMA) problem("age_rating", `"age_rating" is required: one of ${MANIFEST_AGE_RATINGS.join(", ")} (the hub refuses to register an app without it)`);
1216
+ } else if (!age || !MANIFEST_AGE_RATINGS.includes(age)) {
1217
+ problem("age_rating", `"age_rating" must be one of ${MANIFEST_AGE_RATINGS.join(", ")} (got ${JSON.stringify(m.age_rating)})`);
1218
+ }
1219
+ if (m.support !== void 0 && (typeof m.support !== "string" || !/^(mailto:[^@\s]+@[^@\s]+|https:\/\/\S+)$/.test(m.support))) {
1220
+ problem("support", `"support" must be a mailto: address or an https URL (got ${JSON.stringify(m.support)})`);
1221
+ }
1206
1222
  const kind = str("kind");
1207
1223
  if (m.kind !== void 0 && (!kind || !MANIFEST_KINDS.includes(kind))) warn("kind", `unknown "kind" ${JSON.stringify(m.kind)}: the app knows ${MANIFEST_KINDS.join(", ")} and shows this one as a product`);
1208
1224
  const category = str("category");
@@ -1261,7 +1277,7 @@ function checkHtml(html) {
1261
1277
  const darkStyle = [...html.matchAll(/<style\b[^>]*>([\s\S]*?)<\/style>/gi)].concat([...html.matchAll(/\bstyle\s*=\s*"([^"]*)"/gi)]).some(([, css]) => [...css.matchAll(/color-scheme\s*:\s*([^;}"]+)/gi)].some(([, v]) => /\bdark\b/i.test(v) && !/\blight\b/i.test(v)));
1262
1278
  if (darkMeta || darkStyle) f.problems.push({ code: "dark_only", message: "the entry page declares a dark-only color-scheme; the Ambasada app is light (use light, or light dark)" });
1263
1279
  if (!SDK_MARKERS.some((m) => html.includes(m))) {
1264
- f.warnings.push({ code: "no_miniapp_sdk", message: "no @ambasada/miniapp, miniapp.js or ambasadaShell in the entry HTML: a bundled import is fine too, the registry crawler confirms ready() is called at runtime" });
1280
+ f.warnings.push({ code: "no_miniapp_sdk", message: "no @ambasada/miniapp, miniapp.js or ambasadaShell in the entry HTML or its scripts: the Ambasada app waits for ready() to drop the splash" });
1265
1281
  }
1266
1282
  return f;
1267
1283
  }
@@ -1283,6 +1299,23 @@ async function probeManifest(origin, fetchImpl = fetch) {
1283
1299
  return { on: false, entry: null };
1284
1300
  }
1285
1301
  }
1302
+ var SDK_SCRIPT_LIMIT = 16;
1303
+ var BUNDLED_SDK = /ambasadaShell|@ambasada\/miniapp/;
1304
+ async function bundlesMiniappSdk(fetchImpl, pageUrl, html) {
1305
+ const origin = new URL(pageUrl).origin;
1306
+ const srcs = [...html.matchAll(/<script\b[^>]*\bsrc\s*=\s*["']([^"']+)["']/gi)].map(([, src]) => URL.canParse(src, pageUrl) ? new URL(src, pageUrl) : null).filter((u) => !!u && u.origin === origin).slice(0, SDK_SCRIPT_LIMIT);
1307
+ const found = await Promise.all(
1308
+ srcs.map(async (u) => {
1309
+ try {
1310
+ const res = await get(fetchImpl, u.toString());
1311
+ return res.status === 200 && BUNDLED_SDK.test(await res.text());
1312
+ } catch {
1313
+ return false;
1314
+ }
1315
+ })
1316
+ );
1317
+ return found.includes(true);
1318
+ }
1286
1319
  async function checkPage(fetchImpl, url, report, html) {
1287
1320
  try {
1288
1321
  const started = Date.now();
@@ -1291,7 +1324,11 @@ async function checkPage(fetchImpl, url, report, html) {
1291
1324
  if (!html) await res.body?.cancel();
1292
1325
  const ms = Date.now() - started;
1293
1326
  if (res.status !== 200) report.problems.push({ code: "entry_status", message: `${url} answered ${res.status}${res.headers.get("location") ? ` \u2192 ${res.headers.get("location")}` : ""}; ${html ? "the entry" : "the app"} must answer 200 without redirects` });
1294
- else if (html) merge(report, checkHtml(text));
1327
+ else if (html) {
1328
+ const found = checkHtml(text);
1329
+ if (found.warnings.some((w) => w.code === "no_miniapp_sdk") && await bundlesMiniappSdk(fetchImpl, url, text)) found.warnings = found.warnings.filter((w) => w.code !== "no_miniapp_sdk");
1330
+ merge(report, found);
1331
+ }
1295
1332
  if (ms > SLOW_ENTRY_MS) report.warnings.push({ code: "slow_entry", message: `${url} took ${ms} ms; aim for under ${SLOW_ENTRY_MS} ms${html ? " (the splash stays until ready())" : ""}` });
1296
1333
  } catch (error) {
1297
1334
  report.problems.push({ code: "entry_fetch", message: `could not fetch ${url}: ${error.message}` });
@@ -1411,6 +1448,7 @@ var LABELS = [
1411
1448
  function findingLabel(code) {
1412
1449
  return LABELS.find(([re]) => re.test(code))?.[1] ?? code.replace(/_/g, " ");
1413
1450
  }
1451
+ var checkPending = (check) => check?.status === "pending";
1414
1452
  function summarizeCheck(report, now = /* @__PURE__ */ new Date()) {
1415
1453
  const issues = [
1416
1454
  ...report.problems.map((p) => ({ ...p, level: "problem", label: findingLabel(p.code) })),
@@ -1420,10 +1458,12 @@ function summarizeCheck(report, now = /* @__PURE__ */ new Date()) {
1420
1458
  const miniapp = report.miniapp ? { miniapp: report.miniapp.on ? "on" : "off", entry: report.miniapp.entry } : {};
1421
1459
  return { ok: report.ok, issues, needs, checked_at: now.toISOString(), ...miniapp };
1422
1460
  }
1423
- function listingLine(tier, check) {
1461
+ function listingLine(tier, check, kind = "company") {
1424
1462
  if (check?.miniapp === "off") return "Mini app: off";
1463
+ const listed = kind === "project" ? "Listed" : "Listed for the event";
1464
+ if (checkPending(check)) return `${tier === "verified" ? "Verified" : tier === "listed" ? listed : tier === "unlisted" ? "Unlisted" : "Not registered yet"} \xB7 checking\u2026`;
1425
1465
  if (tier === "verified") return "Verified";
1426
- if (tier === "listed") return "Listed for the event";
1466
+ if (tier === "listed") return listed;
1427
1467
  if (tier === "unlisted") return check?.needs.length ? `Unlisted \xB7 needs: ${check.needs.join(", ")}` : "Unlisted";
1428
1468
  return check?.needs.length ? `Not registered yet \xB7 needs: ${check.needs.join(", ")}` : "Not registered yet";
1429
1469
  }
@@ -1464,6 +1504,12 @@ var HEADERS = {
1464
1504
  idempotentReplay: "idempotent-replayed"
1465
1505
  };
1466
1506
  var MAX_DEPTH = 4;
1507
+ var OFFERING_TIMEOUT_MS = { min: 100, max: 3e4 };
1508
+ function offeringTimeoutProblem(value) {
1509
+ if (value === void 0) return null;
1510
+ const n = Number(value);
1511
+ return Number.isInteger(n) && n >= OFFERING_TIMEOUT_MS.min && n <= OFFERING_TIMEOUT_MS.max ? null : `timeout_ms must be between ${OFFERING_TIMEOUT_MS.min} and ${OFFERING_TIMEOUT_MS.max} (got ${JSON.stringify(value)})`;
1512
+ }
1467
1513
 
1468
1514
  // src/receipt.ts
1469
1515
  import { AmbasadaError as AmbasadaError2, getHeader } from "@ambasada/sdk/platform";
@@ -1700,6 +1746,20 @@ var Village = class extends Ambasada {
1700
1746
  getCheckout(id) {
1701
1747
  return this.call("GET", `/v1/checkout/sessions/${encodeURIComponent(id)}`);
1702
1748
  }
1749
+ /** Your latest Checkout sessions (newest first, default 20, at most 100), each with what you said you delivered. */
1750
+ listCheckouts(options = {}) {
1751
+ const q = new URLSearchParams();
1752
+ if (options.limit !== void 0) q.set("limit", String(options.limit));
1753
+ if (options.status) q.set("status", options.status);
1754
+ return this.call("GET", `/v1/checkout/sessions${q.size ? `?${q}` : ""}`);
1755
+ }
1756
+ /**
1757
+ * Tells the village what a paid checkout delivered (`text` ≤ 4000 chars, an https `url`, or both): the person sees it
1758
+ * with the purchase in their app and can rate it. Again for the same session replaces it. CONFLICT before it is paid.
1759
+ */
1760
+ deliverCheckout(id, delivery) {
1761
+ return this.call("POST", `/v1/checkout/sessions/${encodeURIComponent(id)}/delivery`, { body: delivery });
1762
+ }
1703
1763
  /** A reusable payment link for static pages (`<button data-village-pay="pl_…">` with pay.js). */
1704
1764
  createPaymentLink(link) {
1705
1765
  return this.call("POST", "/v1/checkout/links", { body: link });
@@ -1873,6 +1933,8 @@ function defineCompany(app, village2) {
1873
1933
  function offering(def) {
1874
1934
  if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(def.name)) throw new Error(`offering name "${def.name}" must be a slug (a-z, 0-9, _, -)`);
1875
1935
  if (!(def.price >= 0.1 && def.price <= 500)) throw new Error(`offering ${def.name}: price must be between 0.1 and 500 TON`);
1936
+ const timeout = offeringTimeoutProblem(def.timeout_ms);
1937
+ if (timeout) throw new Error(`offering ${def.name}: ${timeout}`);
1876
1938
  return def;
1877
1939
  }
1878
1940
 
package/dist/types.d.ts CHANGED
@@ -55,6 +55,17 @@ export interface CheckoutInput {
55
55
  /** Seconds, 300–86400 (default 3600). */
56
56
  expires_in?: number;
57
57
  }
58
+ /** What a paid checkout delivered (`POST /v1/checkout/sessions/:id/delivery`): text, an https URL, or both. */
59
+ export interface CheckoutDeliveryInput {
60
+ text?: string;
61
+ url?: string;
62
+ }
63
+ export interface CheckoutDelivery {
64
+ session: string;
65
+ text: string | null;
66
+ url: string | null;
67
+ at: string;
68
+ }
58
69
  export interface CheckoutSession {
59
70
  id: string;
60
71
  object: "checkout.session";
@@ -142,7 +153,8 @@ export interface BuyResult<Output = any> {
142
153
  /** For `need()`: which offering served it (`company/name`), if the Gateway says. */
143
154
  offering: string | null;
144
155
  }
145
- export type ChatModel = "deepseek-v4.1-flash" | "deepseek-v4-pro" | "glm-5.3-flash" | (string & {});
156
+ /** What the village sells (apps/api/config/pricing.json); any other model is refused. */
157
+ export type ChatModel = "deepseek-v4.1-flash" | (string & {});
146
158
  export interface ChatMessage {
147
159
  role: "system" | "user" | "assistant" | "tool";
148
160
  content: string;
@@ -154,8 +166,16 @@ export interface ChatRequest {
154
166
  messages: ChatMessage[];
155
167
  temperature?: number;
156
168
  max_tokens?: number;
169
+ /** `json_schema`: JSON in the schema's shape (live, and the mock's sample of it under `ambasada dev`); `json_object`: any JSON. */
157
170
  response_format?: {
158
171
  type: "text" | "json_object";
172
+ } | {
173
+ type: "json_schema";
174
+ json_schema: {
175
+ name: string;
176
+ schema: Record<string, unknown>;
177
+ strict?: boolean;
178
+ };
159
179
  } | Record<string, unknown>;
160
180
  [key: string]: unknown;
161
181
  }
@@ -258,7 +278,7 @@ export interface TransferOptions {
258
278
  }
259
279
  /** `agent/settings.json`, sent with the prompt to `POST /v1/agent/config` (agent stream wire format). */
260
280
  export interface AgentSettings {
261
- model?: "deepseek-v4.1-flash" | "deepseek-v4-pro";
281
+ model?: "deepseek-v4.1-flash";
262
282
  enabled?: boolean;
263
283
  wake?: {
264
284
  interval_minutes?: number;
@@ -272,7 +292,12 @@ export interface AgentSettings {
272
292
  };
273
293
  /** Agent tools offered to the model (default: all). */
274
294
  tools?: string[];
275
- /** Company wallet spend limits, TON. Over `auto_per_payment` an agent payment needs the team's approval. */
295
+ /**
296
+ * The whole company's spend limits, TON: the product's own LLM and image calls count too, not only the agent's
297
+ * purchases. Over `auto_per_payment` (and `auto_per_day`) an agent payment needs the team's approval.
298
+ */
299
+ company_limits?: AgentSettings["limits"];
300
+ /** @deprecated The 0.8 name of `company_limits` (they were always the company's, not only the agent's). */
276
301
  limits?: {
277
302
  per_payment?: number;
278
303
  per_hour?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ambasada/village",
3
- "version": "0.8.4",
3
+ "version": "0.8.6",
4
4
  "description": "The Hack A Ton add-on for Ambasada apps: TON checkout, receipts, offerings, LLM, the agent, the market; CLI commands and the local bank.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -27,7 +27,7 @@
27
27
  "node": ">=22"
28
28
  },
29
29
  "dependencies": {
30
- "@ambasada/sdk": "^0.8.4"
30
+ "@ambasada/sdk": "^0.8.6"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "^22.20.5",
@@ -35,7 +35,7 @@
35
35
  "typescript": "~5.9.3",
36
36
  "vitest": "^4.1.12",
37
37
  "@ambasada/protocol": "0.1.0",
38
- "@ambasada/cli": "0.8.4"
38
+ "@ambasada/cli": "0.8.6"
39
39
  },
40
40
  "author": "CENTYSO (https://centyso.com)",
41
41
  "homepage": "https://ambasada.id",