@bnbagent/deploy-provider-bnb 0.3.1 → 0.4.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bnbagent/deploy-provider-bnb",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "description": "BNB Chain trial-platform provider for @bnbagent/deploy — deploys agents to the managed free-trial runtime (bnbagent-api) over plain HTTPS: GitHub device-flow login, zip artifact upload, deploy + poll. No cloud credentials of your own.",
6
6
  "license": "Apache-2.0",
@@ -17,10 +17,10 @@
17
17
  "typecheck": "tsc --noEmit"
18
18
  },
19
19
  "peerDependencies": {
20
- "@bnbagent/deploy-core": "*"
20
+ "@bnbagent/deploy-core": "^0.4.0"
21
21
  },
22
22
  "devDependencies": {
23
- "@bnbagent/deploy-core": "^0.3.1",
23
+ "@bnbagent/deploy-core": "^0.4.0",
24
24
  "@types/bun": "^1.3.0",
25
25
  "typescript": "^5.7.0"
26
26
  }
package/src/client.ts CHANGED
@@ -16,7 +16,21 @@ import { loadSession, saveSession, withSessionLock, type Session } from "./sessi
16
16
  */
17
17
  export function apiUrl(ctx: Context): string {
18
18
  const fromSpec = ctx.overrides?.bnb?.apiUrl;
19
- const url = (typeof fromSpec === "string" && fromSpec) || process.env.BNBAGENT_API_URL;
19
+ const fromEnv = process.env.BNBAGENT_API_URL?.trim();
20
+ const hasApiToken = Boolean(process.env.BNBAGENT_API_TOKEN?.trim());
21
+ // An API token is normally injected by CI while the TOML comes from the
22
+ // checked-out repository. Bind that credential to the trusted environment
23
+ // endpoint so repository config cannot redirect it to another HTTPS origin.
24
+ if (hasApiToken && !fromEnv) {
25
+ throw new BnbApiError(
26
+ 0,
27
+ "config.token_api_url_missing",
28
+ "BNBAGENT_API_TOKEN requires BNBAGENT_API_URL so the CI credential is bound to a trusted origin.",
29
+ );
30
+ }
31
+ const url = hasApiToken
32
+ ? fromEnv
33
+ : (typeof fromSpec === "string" && fromSpec.trim()) || fromEnv;
20
34
  if (!url) {
21
35
  throw new BnbApiError(
22
36
  0,
package/src/index.ts CHANGED
@@ -93,6 +93,10 @@ function slugPath(id: string): string {
93
93
  return encodeURIComponent(id);
94
94
  }
95
95
 
96
+ export function isFreshSlugError(error: unknown): boolean {
97
+ return error instanceof BnbApiError && error.status === 404;
98
+ }
99
+
96
100
  /**
97
101
  * The HARD spec contract for bnb/trial. validate() reports these early;
98
102
  * deploy() re-asserts UNCONDITIONALLY — --force / skipValidation only skip the
@@ -153,7 +157,8 @@ async function slugRetiredIssue(name: string, ctx: Context): Promise<string | un
153
157
  if (agent.state === "deleted" || agent.state === "reclaiming") return retired;
154
158
  } catch (e) {
155
159
  if (e instanceof BnbApiError && (e.status === 410 || e.code === "agent.deleted")) return retired;
156
- // 404 = fresh slug; anything else (auth/network) is another gate's job.
160
+ if (isFreshSlugError(e)) return undefined;
161
+ throw e;
157
162
  }
158
163
  return undefined;
159
164
  }
@@ -346,6 +351,23 @@ const bnbProvider: CloudProvider = {
346
351
  "Run: bnbagent-deploy login --provider bnb (against the current apiUrl), or fix [bnb] apiUrl / BNBAGENT_API_URL.",
347
352
  });
348
353
  }
354
+ let authReady = false;
355
+ if ((apiToken || session) && base && !issues.some((i) => i.code === "auth.host_mismatch")) {
356
+ try {
357
+ await authedRequest(ctx, "GET", "/v1/account");
358
+ authReady = true;
359
+ } catch (e) {
360
+ const err = e instanceof BnbApiError ? e : undefined;
361
+ issues.push({
362
+ level: "error",
363
+ code: err?.code ?? "auth.unavailable",
364
+ message: `Trial-platform authentication failed: ${(e as Error).message}`,
365
+ ...(err?.status === 401
366
+ ? { hint: "Re-run login, or replace the revoked/expired BNBAGENT_API_TOKEN." }
367
+ : {}),
368
+ });
369
+ }
370
+ }
349
371
  try {
350
372
  if (!base) throw new Error("no endpoint");
351
373
  const campaign: CampaignStatus = await publicRequest(base, "GET", "/v1/campaign");
@@ -365,14 +387,22 @@ const bnbProvider: CloudProvider = {
365
387
  }
366
388
  // Doomed-deploy precheck (needs working auth): a deleted slug is
367
389
  // retired forever — deploy would fail AFTER uploading the artifact.
368
- if ((apiToken || session) && base && !issues.some((i) => i.code === "auth.host_mismatch")) {
369
- const retired = await slugRetiredIssue(spec.name, ctx);
370
- if (retired) {
390
+ if (authReady && base) {
391
+ try {
392
+ const retired = await slugRetiredIssue(spec.name, ctx);
393
+ if (retired) {
394
+ issues.push({
395
+ level: "error",
396
+ code: "agent.retired",
397
+ message: retired,
398
+ hint: "Pick a new agent name (slug) in agent-deploy.toml.",
399
+ });
400
+ }
401
+ } catch (e) {
371
402
  issues.push({
372
403
  level: "error",
373
- code: "agent.retired",
374
- message: retired,
375
- hint: "Pick a new agent name (slug) in agent-deploy.toml.",
404
+ code: e instanceof BnbApiError ? e.code : "agent.precheck_failed",
405
+ message: `Could not verify the agent slug: ${(e as Error).message}`,
376
406
  });
377
407
  }
378
408
  }
@@ -419,7 +449,7 @@ const bnbProvider: CloudProvider = {
419
449
  );
420
450
  }
421
451
  if (!(e instanceof BnbApiError)) throw e; // the retired-slug Error above
422
- // 404 = fresh slug; other API/network errors are the next call's problem.
452
+ if (!isFreshSlugError(e)) throw e;
423
453
  }
424
454
 
425
455
  const shipped = spec.type === "zip" ? await shipZipArtifact(spec, ctx) : await shipImageArtifact(spec, ctx);
package/src/init.ts CHANGED
@@ -26,6 +26,9 @@ export async function initCmd(ctx: Context): Promise<void> {
26
26
  const defaultName = slugify(basename(ctx.baseDir));
27
27
  const name = process.stdin.isTTY ? slugify(ask(`Agent name (slug) [${defaultName}]: `) || defaultName) : defaultName;
28
28
  const apiUrl = process.stdin.isTTY ? ask("Trial platform URL (https://…): ") : "";
29
+ const apiUrlLine = apiUrl
30
+ ? `apiUrl = ${JSON.stringify(apiUrl)}`
31
+ : '# apiUrl = "https://<the-trial-platform-host>" # Or set BNBAGENT_API_URL';
29
32
 
30
33
  const toml = `name = "${name}"
31
34
 
@@ -37,7 +40,7 @@ entrypoint = "main.py" # .py → Python · .js/.mjs/.cjs → Node (zip
37
40
  # protocol = "A2A" # or "MCP" — buyers then authenticate via the platform token plane
38
41
 
39
42
  [bnb]
40
- apiUrl = "${apiUrl || "https://<the-trial-platform-host>"}"
43
+ ${apiUrlLine}
41
44
 
42
45
  # [env] # non-sensitive runtime config (plaintext)
43
46
  # MY_FLAG = "1"