@ambasada/cli 0.7.0 → 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.
- package/dist/ambasada.mjs +54 -21
- 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
|
|
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);
|
|
@@ -3694,7 +3697,7 @@ import { tmpdir } from "node:os";
|
|
|
3694
3697
|
import { join as join7 } from "node:path";
|
|
3695
3698
|
|
|
3696
3699
|
// src/deploy.ts
|
|
3697
|
-
import { spawn as spawn2 } from "node:child_process";
|
|
3700
|
+
import { execFileSync, spawn as spawn2 } from "node:child_process";
|
|
3698
3701
|
import { existsSync as existsSync4 } from "node:fs";
|
|
3699
3702
|
import { mkdir as mkdir2, readdir, readFile as readFile3, rm as rm2, stat, writeFile as writeFile3 } from "node:fs/promises";
|
|
3700
3703
|
import { createServer as createServer2 } from "node:net";
|
|
@@ -3728,9 +3731,9 @@ async function deploy2(args, flags) {
|
|
|
3728
3731
|
}
|
|
3729
3732
|
const built = await bundle(dir, template);
|
|
3730
3733
|
out(`${c.green("\u2713")} built ${c.bold(template)}: ${built.modules.length} module(s), ${built.assets.length} static file(s)`);
|
|
3731
|
-
|
|
3734
|
+
const smoked = flags["no-smoke"] ? { ok: false, skipped: true, checks: [], ms: null } : await smoke(built, project.config.id);
|
|
3732
3735
|
const r = await resolve(flags);
|
|
3733
|
-
const metadata = deployMetadata(built, project.root);
|
|
3736
|
+
const metadata = { ...deployMetadata(built, project.root), report: { template, smoke: smoked, git: gitInfo(project.root) } };
|
|
3734
3737
|
if (flags["dry-run"]) {
|
|
3735
3738
|
out(`would POST ${r.apiUrl}/v1/product/deploy ${JSON.stringify({ ...metadata, modules: built.modules.map((m) => m.name), assets: built.assets.length })}`);
|
|
3736
3739
|
return deploy(args, flags);
|
|
@@ -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
|
}
|
|
@@ -3832,6 +3836,7 @@ async function upload(apiUrl, apiKey, built, metadata) {
|
|
|
3832
3836
|
return body;
|
|
3833
3837
|
}
|
|
3834
3838
|
async function smoke(built, company) {
|
|
3839
|
+
const started = Date.now();
|
|
3835
3840
|
const smokeDir = join6(built.dir, ".ambasada", "smoke");
|
|
3836
3841
|
await rm2(smokeDir, { recursive: true, force: true });
|
|
3837
3842
|
await mkdir2(smokeDir, { recursive: true });
|
|
@@ -3869,11 +3874,24 @@ ${(await home.text()).slice(0, 500)}`);
|
|
|
3869
3874
|
if (receipt.status >= 500) throw new CliError(`smoke test: POST /api/receipt answered ${receipt.status}
|
|
3870
3875
|
${(await receipt.text()).slice(0, 500)}`);
|
|
3871
3876
|
out(`${c.green("\u2713")} smoke test: GET / ${home.status}, POST /api/receipt ${receipt.status}`);
|
|
3877
|
+
return { ok: true, skipped: false, checks: [{ method: "GET", path: "/", status: home.status }, { method: "POST", path: "/api/receipt", status: receipt.status }], ms: Date.now() - started };
|
|
3872
3878
|
} finally {
|
|
3873
3879
|
child.kill("SIGTERM");
|
|
3874
3880
|
await rm2(smokeDir, { recursive: true, force: true });
|
|
3875
3881
|
}
|
|
3876
3882
|
}
|
|
3883
|
+
function gitInfo(dir) {
|
|
3884
|
+
const git = (...args) => {
|
|
3885
|
+
try {
|
|
3886
|
+
return execFileSync("git", args, { cwd: dir, stdio: ["ignore", "pipe", "ignore"], encoding: "utf8", timeout: 3e3 }).trim() || null;
|
|
3887
|
+
} catch {
|
|
3888
|
+
return null;
|
|
3889
|
+
}
|
|
3890
|
+
};
|
|
3891
|
+
const commit = git("rev-parse", "--short", "HEAD");
|
|
3892
|
+
if (!commit) return null;
|
|
3893
|
+
return { commit, branch: git("rev-parse", "--abbrev-ref", "HEAD"), author: git("config", "user.name") };
|
|
3894
|
+
}
|
|
3877
3895
|
async function waitFor(base, child, logs2) {
|
|
3878
3896
|
const deadline = Date.now() + 6e4;
|
|
3879
3897
|
while (Date.now() < deadline) {
|
|
@@ -4214,7 +4232,7 @@ async function tunnel(_args, flags) {
|
|
|
4214
4232
|
}
|
|
4215
4233
|
|
|
4216
4234
|
// src/commands.ts
|
|
4217
|
-
var SDK_VERSION = /^\d+\.\d+\.\d+$/.test("0.7.
|
|
4235
|
+
var SDK_VERSION = /^\d+\.\d+\.\d+$/.test("0.7.2") ? `^${"0.7.2"}` : "latest";
|
|
4218
4236
|
var DEV_PORT = 8788;
|
|
4219
4237
|
var out3 = (s = "") => process.stdout.write(`${s}
|
|
4220
4238
|
`);
|
|
@@ -4280,19 +4298,33 @@ async function login(_args, flags) {
|
|
|
4280
4298
|
if (device) ({ token: key, company } = device);
|
|
4281
4299
|
}
|
|
4282
4300
|
if (!key) {
|
|
4283
|
-
out3(`Paste your
|
|
4301
|
+
out3(`Paste your app's key: ${c.bold("https://developer.ambasada.dev")} \u2192 New app (or Settings \u2192 New key).`);
|
|
4284
4302
|
key = await prompt("API key: ");
|
|
4285
4303
|
}
|
|
4286
4304
|
if (!key) throw new CliError("no key given");
|
|
4287
|
-
|
|
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;
|
|
4288
4311
|
try {
|
|
4289
|
-
|
|
4312
|
+
res = await fetch(`${apiUrl.replace(/\/$/, "")}/v1/key`, { headers: { authorization: `Bearer ${key}`, accept: "application/json" } });
|
|
4290
4313
|
} catch (error) {
|
|
4291
|
-
|
|
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();
|
|
4322
|
+
try {
|
|
4323
|
+
return { id: (await createAmbasada({ apiKey: key, apiUrl, env: {} }).wallet()).account, name: null, kind: "company" };
|
|
4324
|
+
} catch (error) {
|
|
4325
|
+
if (isVillageError(error, "UNAUTHORIZED")) throw new CliError("that key was refused (UNAUTHORIZED)");
|
|
4292
4326
|
throw error;
|
|
4293
4327
|
}
|
|
4294
|
-
await writeConfig({ ...await readConfig(), apiUrl, apiKey: key, company });
|
|
4295
|
-
out3(`${c.green("\u2713")} logged in as ${c.bold(company)} on ${apiUrl} ${c.dim(`(saved to ${configPath()})`)}`);
|
|
4296
4328
|
}
|
|
4297
4329
|
async function logout() {
|
|
4298
4330
|
const { apiKey: _k, company: _c, ...rest } = await readConfig();
|
|
@@ -4301,10 +4333,10 @@ async function logout() {
|
|
|
4301
4333
|
}
|
|
4302
4334
|
async function whoami(_args, flags) {
|
|
4303
4335
|
const r = await resolve(flags);
|
|
4304
|
-
|
|
4305
|
-
const
|
|
4306
|
-
if (json(flags, { apiUrl: r.apiUrl, company:
|
|
4307
|
-
out3(`${c.bold(
|
|
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}`);
|
|
4308
4340
|
}
|
|
4309
4341
|
async function init(args, flags) {
|
|
4310
4342
|
const target = resolvePath4(args[0] ?? ".");
|
|
@@ -4371,7 +4403,7 @@ async function init(args, flags) {
|
|
|
4371
4403
|
out3(` npm run dev ${c.dim("# ambasada dev: mock village API on :8788 + your product on http://localhost:3000")}`);
|
|
4372
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")}`);
|
|
4373
4405
|
out3("");
|
|
4374
|
-
out3(`Then ${c.bold("ambasada login")}
|
|
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.`);
|
|
4375
4407
|
}
|
|
4376
4408
|
async function check(args, flags) {
|
|
4377
4409
|
const target = args[0];
|
|
@@ -4682,14 +4714,15 @@ async function links(args, flags) {
|
|
|
4682
4714
|
}
|
|
4683
4715
|
|
|
4684
4716
|
// src/main.ts
|
|
4685
|
-
var VERSION = true ? "0.7.
|
|
4686
|
-
var HELP = `${c.bold("ambasada")} ${VERSION}: build
|
|
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
|
|
4687
4719
|
|
|
4688
4720
|
ambasada init [dir] [--template next|spa|worker] [--name "Name"] [--id slug]
|
|
4689
|
-
scaffold
|
|
4721
|
+
scaffold an app (village teams also get the agent). An app is one Cloudflare
|
|
4690
4722
|
Worker: next (default, Next.js via OpenNext), spa (Vite + React,
|
|
4691
4723
|
for Lovable/Bolt exports) or worker (bare fetch handlers + web/)
|
|
4692
|
-
ambasada login [--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)
|
|
4693
4726
|
ambasada dev [--port 8788] [--product URL] mock Payments API (fake bank, fake visitor, canned LLM/tools) on :8788
|
|
4694
4727
|
+ your product's dev server (next dev | vite | wrangler dev) on
|
|
4695
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.
|
|
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.
|
|
25
|
+
"@ambasada/sdk": "0.7.2"
|
|
26
26
|
},
|
|
27
27
|
"author": "CENTYSO (https://centyso.com)",
|
|
28
28
|
"homepage": "https://ambasada.id",
|