@ambasada/cli 0.7.1 → 0.7.2

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.
Files changed (2) hide show
  1. package/dist/ambasada.mjs +37 -18
  2. package/package.json +2 -2
package/dist/ambasada.mjs CHANGED
@@ -1700,7 +1700,7 @@ var AmbasadaAuth = class {
1700
1700
  async verifyShellToken(token, options = {}) {
1701
1701
  const c2 = this.config();
1702
1702
  if (!c2.issuer) throw new VillageError("CONFIG", "silent sign-in needs AMBASADA_AUTH_ISSUER (injected on ambasada.dev and by `ambasada dev`)");
1703
- const issuer = c2.issuer.replace(/\/+$/, "");
1703
+ const trusted = [c2.issuer, c2.hubUrl].filter((v) => !!v).map((v) => v.replace(/\/+$/, ""));
1704
1704
  const audiences = [options.audience ?? c2.audiences ?? []].flat().filter(Boolean).map((a) => a.replace(/\/+$/, ""));
1705
1705
  if (!audiences.length) throw new VillageError("CONFIG", "silent sign-in needs to know this app: set AMBASADA_APP_ORIGIN or PUBLIC_URL, or pass { audience }");
1706
1706
  const doFetch = c2.fetch ?? ((input, init2) => fetch(input, init2));
@@ -1715,6 +1715,9 @@ var AmbasadaAuth = class {
1715
1715
  throw shellRefused("not a JWT");
1716
1716
  }
1717
1717
  if (header3.alg !== "RS256") throw shellRefused(`alg ${String(header3.alg)} (only RS256)`);
1718
+ const iss = typeof claims.iss === "string" ? claims.iss.replace(/\/+$/, "") : null;
1719
+ if (iss && !trusted.includes(iss)) throw shellRefused(`issuer ${claims.iss}`);
1720
+ const issuer = iss ?? trusted[0];
1718
1721
  let keys3 = await jwks(issuer, doFetch);
1719
1722
  let jwk = keys3.find((k) => k.kid === header3.kid) ?? (!header3.kid && keys3.length === 1 ? keys3[0] : void 0);
1720
1723
  if (!jwk) {
@@ -1729,7 +1732,6 @@ var AmbasadaAuth = class {
1729
1732
  if (typeof claims.exp !== "number" || claims.exp + LEEWAY_S < now) throw shellRefused("expired");
1730
1733
  if (typeof claims.nbf === "number" && claims.nbf - LEEWAY_S > now) throw shellRefused("not valid yet");
1731
1734
  if (typeof claims.iat === "number" && claims.iat - LEEWAY_S > now) throw shellRefused("issued in the future");
1732
- if (typeof claims.iss === "string" && claims.iss.replace(/\/+$/, "") !== issuer) throw shellRefused(`issuer ${claims.iss}`);
1733
1735
  const aud = [claims.aud].flat().filter((a) => typeof a === "string").map((a) => a.replace(/\/+$/, ""));
1734
1736
  if (!aud.some((a) => audiences.includes(a))) throw shellRefused(`it is for ${aud.join(", ") || "no app"}, not this one`);
1735
1737
  if (typeof claims.sub !== "string" || !claims.sub) throw shellRefused("no subject");
@@ -3128,6 +3130,7 @@ ${key}` : null;
3128
3130
  const token = /^Bearer\s+(.+)$/i.exec(req.headers.get("authorization") ?? "")?.[1]?.trim();
3129
3131
  if (!token) throw fail("UNAUTHORIZED", `a company API key is required (Authorization: Bearer ${DEV_KEY})`);
3130
3132
  const me = token === AGENT_KEY ? FAKE_AGENT : company;
3133
+ if (path === "/v1/key" && method === "GET") return json2({ id: me, name: me, kind: "company" });
3131
3134
  if (path === "/v1/wallet" && method === "GET") return json2(wallet2(me));
3132
3135
  if (path === "/v1/ledger" && method === "GET") {
3133
3136
  const since = Number(url.searchParams.get("since") ?? 0);
@@ -3745,6 +3748,7 @@ async function deploy2(args, flags) {
3745
3748
  out(` ${deployment.tier === "listed" || deployment.tier === "verified" ? c.green("\u2713") : c.yellow("!")} in the Ambasada app: ${line}`);
3746
3749
  for (const issue of deployment.check?.issues.filter((i) => i.level === "problem") ?? []) out(c.dim(` ${issue.message}`));
3747
3750
  }
3751
+ if (deployment.kind === "project") return;
3748
3752
  if (!await setProductUrl(project.root, deployment.url)) {
3749
3753
  out(c.yellow(`! add \`product: { url: "${deployment.url}" }\` to ambasada.config.ts so relative offering endpoints resolve`));
3750
3754
  }
@@ -4228,7 +4232,7 @@ async function tunnel(_args, flags) {
4228
4232
  }
4229
4233
 
4230
4234
  // src/commands.ts
4231
- var SDK_VERSION = /^\d+\.\d+\.\d+$/.test("0.7.1") ? `^${"0.7.1"}` : "latest";
4235
+ var SDK_VERSION = /^\d+\.\d+\.\d+$/.test("0.7.2") ? `^${"0.7.2"}` : "latest";
4232
4236
  var DEV_PORT = 8788;
4233
4237
  var out3 = (s = "") => process.stdout.write(`${s}
4234
4238
  `);
@@ -4294,19 +4298,33 @@ async function login(_args, flags) {
4294
4298
  if (device) ({ token: key, company } = device);
4295
4299
  }
4296
4300
  if (!key) {
4297
- out3(`Paste your company's server key from ${c.bold(`${apiUrl.replace(/\/$/, "")}`)} \u2192 your team \u2192 Developers \u2192 Server keys.`);
4301
+ out3(`Paste your app's key: ${c.bold("https://developer.ambasada.dev")} \u2192 New app (or Settings \u2192 New key).`);
4298
4302
  key = await prompt("API key: ");
4299
4303
  }
4300
4304
  if (!key) throw new CliError("no key given");
4301
- const village2 = createAmbasada({ apiKey: key, apiUrl, env: {} });
4305
+ company = (await whoseKey(apiUrl, key)).id;
4306
+ await writeConfig({ ...await readConfig(), apiUrl, apiKey: key, company });
4307
+ out3(`${c.green("\u2713")} logged in as ${c.bold(company)} on ${apiUrl} ${c.dim(`(saved to ${configPath()})`)}`);
4308
+ }
4309
+ async function whoseKey(apiUrl, key) {
4310
+ let res;
4311
+ try {
4312
+ res = await fetch(`${apiUrl.replace(/\/$/, "")}/v1/key`, { headers: { authorization: `Bearer ${key}`, accept: "application/json" } });
4313
+ } catch (error) {
4314
+ throw new CliError(`can't reach ${apiUrl}: ${error.message}`);
4315
+ }
4316
+ if (res.status === 401) throw new CliError("that key was refused (UNAUTHORIZED)");
4317
+ if (res.ok) {
4318
+ const body = await res.json();
4319
+ return { id: body.id, name: body.name ?? null, kind: body.kind === "project" ? "project" : "company" };
4320
+ }
4321
+ await res.body?.cancel();
4302
4322
  try {
4303
- company = (await village2.wallet()).account;
4323
+ return { id: (await createAmbasada({ apiKey: key, apiUrl, env: {} }).wallet()).account, name: null, kind: "company" };
4304
4324
  } catch (error) {
4305
- if (isVillageError(error, "UNAUTHORIZED")) throw new CliError("that key was refused by the village (UNAUTHORIZED)");
4325
+ if (isVillageError(error, "UNAUTHORIZED")) throw new CliError("that key was refused (UNAUTHORIZED)");
4306
4326
  throw error;
4307
4327
  }
4308
- await writeConfig({ ...await readConfig(), apiUrl, apiKey: key, company });
4309
- out3(`${c.green("\u2713")} logged in as ${c.bold(company)} on ${apiUrl} ${c.dim(`(saved to ${configPath()})`)}`);
4310
4328
  }
4311
4329
  async function logout() {
4312
4330
  const { apiKey: _k, company: _c, ...rest } = await readConfig();
@@ -4315,10 +4333,10 @@ async function logout() {
4315
4333
  }
4316
4334
  async function whoami(_args, flags) {
4317
4335
  const r = await resolve(flags);
4318
- const v = await client(flags);
4319
- const w = await v.wallet();
4320
- if (json(flags, { apiUrl: r.apiUrl, company: w.account })) return;
4321
- out3(`${c.bold(w.account)} on ${r.apiUrl}`);
4336
+ if (!r.apiKey) throw new CliError("not logged in: run `ambasada login` (or set AMBASADA_API_KEY)");
4337
+ const who = await whoseKey(r.apiUrl, r.apiKey);
4338
+ if (json(flags, { apiUrl: r.apiUrl, company: who.id, kind: who.kind })) return;
4339
+ out3(`${c.bold(who.id)} on ${r.apiUrl}`);
4322
4340
  }
4323
4341
  async function init(args, flags) {
4324
4342
  const target = resolvePath4(args[0] ?? ".");
@@ -4385,7 +4403,7 @@ async function init(args, flags) {
4385
4403
  out3(` npm run dev ${c.dim("# ambasada dev: mock village API on :8788 + your product on http://localhost:3000")}`);
4386
4404
  out3(` npx ambasada dev buy translate '{"text":"Bun\u0103!","to":"en"}' ${c.dim("# in a second terminal: a fake agent pays you 1 TON")}`);
4387
4405
  out3("");
4388
- out3(`Then ${c.bold("ambasada login")}, ${c.bold("ambasada link https://your.product")} and ${c.bold("ambasada deploy")}. See README.md.`);
4406
+ out3(`Then ${c.bold("ambasada login")} (a key from https://developer.ambasada.dev \u2192 New app) and ${c.bold("ambasada deploy")}. See README.md.`);
4389
4407
  }
4390
4408
  async function check(args, flags) {
4391
4409
  const target = args[0];
@@ -4696,14 +4714,15 @@ async function links(args, flags) {
4696
4714
  }
4697
4715
 
4698
4716
  // src/main.ts
4699
- var VERSION = true ? "0.7.1" : "0.1.0-dev";
4700
- var HELP = `${c.bold("ambasada")} ${VERSION}: build a company for the Hack A Ton village
4717
+ var VERSION = true ? "0.7.2" : "0.1.0-dev";
4718
+ var HELP = `${c.bold("ambasada")} ${VERSION}: build and host apps on ambasada.dev
4701
4719
 
4702
4720
  ambasada init [dir] [--template next|spa|worker] [--name "Name"] [--id slug]
4703
- scaffold a company (product + agent). A product is one Cloudflare
4721
+ scaffold an app (village teams also get the agent). An app is one Cloudflare
4704
4722
  Worker: next (default, Next.js via OpenNext), spa (Vite + React,
4705
4723
  for Lovable/Bolt exports) or worker (bare fetch handlers + web/)
4706
- ambasada login [--key sk_\u2026] device-code login with Ambasada ID, or paste a server key
4724
+ ambasada login [--key vk_\u2026] device-code login with Ambasada ID, or paste an app key
4725
+ (developer.ambasada.dev \u2192 New app, or a team's server key)
4707
4726
  ambasada dev [--port 8788] [--product URL] mock Payments API (fake bank, fake visitor, canned LLM/tools) on :8788
4708
4727
  + your product's dev server (next dev | vite | wrangler dev) on
4709
4728
  product.url (:3000), with AMBASADA_* pointed at the mock
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ambasada/cli",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "`ambasada`: the Ambasada CLI (Hack A Ton village commands) (login, init, dev, deploy, offerings, wallet, logs, market, steer).",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -22,7 +22,7 @@
22
22
  "vitest": "^4.1.12",
23
23
  "wrangler": "^4.156.0",
24
24
  "@ambasada/protocol": "0.1.0",
25
- "@ambasada/sdk": "0.7.1"
25
+ "@ambasada/sdk": "0.7.2"
26
26
  },
27
27
  "author": "CENTYSO (https://centyso.com)",
28
28
  "homepage": "https://ambasada.id",