@bnbagent/deploy-provider-bnb 0.3.0 → 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 +3 -3
- package/src/client.ts +21 -7
- package/src/extras.ts +4 -4
- package/src/index.ts +51 -14
- package/src/init.ts +5 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bnbagent/deploy-provider-bnb",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
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
|
|
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,
|
|
@@ -40,7 +54,7 @@ export function apiUrl(ctx: Context): string {
|
|
|
40
54
|
|
|
41
55
|
/**
|
|
42
56
|
* Automation/CI bearer: a platform API token (`bnbk_…`, minted with
|
|
43
|
-
* `bnbagent-deploy token --new`) via BNBAGENT_API_TOKEN. When set it REPLACES
|
|
57
|
+
* `bnbagent-deploy token --provider bnb --new`) via BNBAGENT_API_TOKEN. When set it REPLACES
|
|
44
58
|
* the session entirely — no device flow, no refresh, no session file.
|
|
45
59
|
*/
|
|
46
60
|
export function envApiToken(): string | undefined {
|
|
@@ -116,7 +130,7 @@ export async function authedRequest(
|
|
|
116
130
|
throw new BnbApiError(
|
|
117
131
|
401,
|
|
118
132
|
"auth.token_invalid",
|
|
119
|
-
"BNBAGENT_API_TOKEN was rejected (revoked/expired?) — mint a new one: `bnbagent-deploy token --new` (while signed in).",
|
|
133
|
+
"BNBAGENT_API_TOKEN was rejected (revoked/expired?) — mint a new one: `bnbagent-deploy token --provider bnb --new` (while signed in).",
|
|
120
134
|
);
|
|
121
135
|
}
|
|
122
136
|
throw e;
|
|
@@ -127,7 +141,7 @@ export async function authedRequest(
|
|
|
127
141
|
throw new BnbApiError(
|
|
128
142
|
401,
|
|
129
143
|
"auth.signed_out",
|
|
130
|
-
"Not signed in — run `bnbagent-deploy login` first (CI: set BNBAGENT_API_TOKEN).",
|
|
144
|
+
"Not signed in — run `bnbagent-deploy login --provider bnb` first (CI: set BNBAGENT_API_TOKEN).",
|
|
131
145
|
);
|
|
132
146
|
}
|
|
133
147
|
// Host-bind the token: never send a session minted against platform A to a
|
|
@@ -138,7 +152,7 @@ export async function authedRequest(
|
|
|
138
152
|
401,
|
|
139
153
|
"auth.host_mismatch",
|
|
140
154
|
`Your session was minted against ${session.apiUrl}, but this command targets ${base}. ` +
|
|
141
|
-
`Refusing to send credentials cross-host — run \`bnbagent-deploy login\` against ${base}.`,
|
|
155
|
+
`Refusing to send credentials cross-host — run \`bnbagent-deploy login --provider bnb\` against ${base}.`,
|
|
142
156
|
);
|
|
143
157
|
}
|
|
144
158
|
try {
|
|
@@ -155,7 +169,7 @@ async function refreshSession(base: string, session: Session): Promise<Session>
|
|
|
155
169
|
return withSessionLock(async () => {
|
|
156
170
|
const fresh = await loadSession();
|
|
157
171
|
if (!fresh || !sameOrigin(fresh.apiUrl, base)) {
|
|
158
|
-
throw new BnbApiError(401, "auth.expired", "Session expired — run `bnbagent-deploy login` again.");
|
|
172
|
+
throw new BnbApiError(401, "auth.expired", "Session expired — run `bnbagent-deploy login --provider bnb` again.");
|
|
159
173
|
}
|
|
160
174
|
if (fresh.refresh_token !== session.refresh_token) {
|
|
161
175
|
return fresh;
|
|
@@ -170,7 +184,7 @@ async function refreshSession(base: string, session: Session): Promise<Session>
|
|
|
170
184
|
throw new BnbApiError(
|
|
171
185
|
401,
|
|
172
186
|
"auth.expired",
|
|
173
|
-
`Session expired${detail} — run \`bnbagent-deploy login\` again.`,
|
|
187
|
+
`Session expired${detail} — run \`bnbagent-deploy login --provider bnb\` again.`,
|
|
174
188
|
);
|
|
175
189
|
}
|
|
176
190
|
const next: Session = { apiUrl: base, access_token: res.access_token, refresh_token: res.refresh_token };
|
package/src/extras.ts
CHANGED
|
@@ -27,7 +27,7 @@ const emit = (data: unknown): void => {
|
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
* `bnbagent-deploy trial [--json]` — the structured trial-clock read
|
|
30
|
+
* `bnbagent-deploy trial --provider bnb [--json]` — the structured trial-clock read
|
|
31
31
|
* (GET /v1/account/trial: case/started_at/expires_at/remaining_seconds).
|
|
32
32
|
* Account-level: works in any directory. Orchestrators gate deploy offers on it.
|
|
33
33
|
*/
|
|
@@ -104,7 +104,7 @@ export async function clientsCmd(spec: DeploymentSpec | undefined, ctx: Context)
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
/**
|
|
107
|
-
* `bnbagent-deploy token [--new | --revoke <id>] [--json]` (default: list).
|
|
107
|
+
* `bnbagent-deploy token --provider bnb [--new | --revoke <id>] [--json]` (default: list).
|
|
108
108
|
* Account-level — usable in ANY directory (`--provider bnb` + BNBAGENT_API_URL
|
|
109
109
|
* outside a project); the spec, when present, only supplies [bnb] apiUrl.
|
|
110
110
|
*/
|
|
@@ -138,7 +138,7 @@ export async function tokenCmd(_spec: DeploymentSpec | undefined, ctx: Context):
|
|
|
138
138
|
return;
|
|
139
139
|
}
|
|
140
140
|
if (!list.tokens.length) {
|
|
141
|
-
ctx.logger.info("No API tokens yet — mint one: bnbagent-deploy token --new");
|
|
141
|
+
ctx.logger.info("No API tokens yet — mint one: bnbagent-deploy token --provider bnb --new");
|
|
142
142
|
return;
|
|
143
143
|
}
|
|
144
144
|
for (const t of list.tokens) {
|
|
@@ -146,5 +146,5 @@ export async function tokenCmd(_spec: DeploymentSpec | undefined, ctx: Context):
|
|
|
146
146
|
`${t.prefix ?? "bnbk_…"} ${t.scope ?? "tenant"} expires ${t.expires_at ?? "never"} (id ${t.id})`,
|
|
147
147
|
);
|
|
148
148
|
}
|
|
149
|
-
ctx.logger.info("Revoke one: bnbagent-deploy token --revoke <id>");
|
|
149
|
+
ctx.logger.info("Revoke one: bnbagent-deploy token --provider bnb --revoke <id>");
|
|
150
150
|
}
|
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
|
-
|
|
160
|
+
if (isFreshSlugError(e)) return undefined;
|
|
161
|
+
throw e;
|
|
157
162
|
}
|
|
158
163
|
return undefined;
|
|
159
164
|
}
|
|
@@ -221,7 +226,13 @@ const bnbProvider: CloudProvider = {
|
|
|
221
226
|
if (envApiToken()) return [{ level: "info", code: "auth.ok", message: "BNBAGENT_API_TOKEN is set (CI bearer)." }];
|
|
222
227
|
const session = await loadSession();
|
|
223
228
|
if (!session) {
|
|
224
|
-
return [
|
|
229
|
+
return [
|
|
230
|
+
{
|
|
231
|
+
level: "error",
|
|
232
|
+
code: "auth.signed_out",
|
|
233
|
+
message: "No session and no BNBAGENT_API_TOKEN — run `bnbagent-deploy login --provider bnb`.",
|
|
234
|
+
},
|
|
235
|
+
];
|
|
225
236
|
}
|
|
226
237
|
return [{ level: "info", code: "auth.ok", message: `Session present (minted against ${session.apiUrl}).` }];
|
|
227
238
|
},
|
|
@@ -254,7 +265,7 @@ const bnbProvider: CloudProvider = {
|
|
|
254
265
|
if ("status" in res && res.status === "slow_down") interval += 5;
|
|
255
266
|
// authorization_pending → keep polling
|
|
256
267
|
}
|
|
257
|
-
throw new Error("Device-flow sign-in timed out — run `bnbagent-deploy login` again.");
|
|
268
|
+
throw new Error("Device-flow sign-in timed out — run `bnbagent-deploy login --provider bnb` again.");
|
|
258
269
|
},
|
|
259
270
|
|
|
260
271
|
/** Show the signed-in identity (GitHub login) + trial clock. */
|
|
@@ -262,7 +273,7 @@ const bnbProvider: CloudProvider = {
|
|
|
262
273
|
const session = await loadSession();
|
|
263
274
|
const viaToken = envApiToken();
|
|
264
275
|
if (!session && !viaToken) {
|
|
265
|
-
ctx.logger.info("Not signed in to the trial platform (run `bnbagent-deploy login`).");
|
|
276
|
+
ctx.logger.info("Not signed in to the trial platform (run `bnbagent-deploy login --provider bnb`).");
|
|
266
277
|
return;
|
|
267
278
|
}
|
|
268
279
|
try {
|
|
@@ -280,7 +291,7 @@ const bnbProvider: CloudProvider = {
|
|
|
280
291
|
}
|
|
281
292
|
} catch (e) {
|
|
282
293
|
if (e instanceof BnbApiError && e.status === 401) {
|
|
283
|
-
ctx.logger.info("Session expired — run `bnbagent-deploy login` again.");
|
|
294
|
+
ctx.logger.info("Session expired — run `bnbagent-deploy login --provider bnb` again.");
|
|
284
295
|
return;
|
|
285
296
|
}
|
|
286
297
|
throw e;
|
|
@@ -326,7 +337,7 @@ const bnbProvider: CloudProvider = {
|
|
|
326
337
|
level: "error",
|
|
327
338
|
code: "auth.signed_out",
|
|
328
339
|
message: "Not signed in to the trial platform.",
|
|
329
|
-
hint: "Run: bnbagent-deploy login (CI: set BNBAGENT_API_TOKEN)",
|
|
340
|
+
hint: "Run: bnbagent-deploy login --provider bnb (CI: set BNBAGENT_API_TOKEN)",
|
|
330
341
|
});
|
|
331
342
|
} else if (!apiToken && session && base && session.apiUrl && !sameOrigin(session.apiUrl, base)) {
|
|
332
343
|
// Parity with authedRequest's host binding: a token minted against
|
|
@@ -336,9 +347,27 @@ const bnbProvider: CloudProvider = {
|
|
|
336
347
|
level: "error",
|
|
337
348
|
code: "auth.host_mismatch",
|
|
338
349
|
message: `Signed in against ${session.apiUrl}, but this spec targets ${base} — authenticated calls would be refused.`,
|
|
339
|
-
hint:
|
|
350
|
+
hint:
|
|
351
|
+
"Run: bnbagent-deploy login --provider bnb (against the current apiUrl), or fix [bnb] apiUrl / BNBAGENT_API_URL.",
|
|
340
352
|
});
|
|
341
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
|
+
}
|
|
342
371
|
try {
|
|
343
372
|
if (!base) throw new Error("no endpoint");
|
|
344
373
|
const campaign: CampaignStatus = await publicRequest(base, "GET", "/v1/campaign");
|
|
@@ -358,14 +387,22 @@ const bnbProvider: CloudProvider = {
|
|
|
358
387
|
}
|
|
359
388
|
// Doomed-deploy precheck (needs working auth): a deleted slug is
|
|
360
389
|
// retired forever — deploy would fail AFTER uploading the artifact.
|
|
361
|
-
if (
|
|
362
|
-
|
|
363
|
-
|
|
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) {
|
|
364
402
|
issues.push({
|
|
365
403
|
level: "error",
|
|
366
|
-
code: "agent.
|
|
367
|
-
message:
|
|
368
|
-
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}`,
|
|
369
406
|
});
|
|
370
407
|
}
|
|
371
408
|
}
|
|
@@ -412,7 +449,7 @@ const bnbProvider: CloudProvider = {
|
|
|
412
449
|
);
|
|
413
450
|
}
|
|
414
451
|
if (!(e instanceof BnbApiError)) throw e; // the retired-slug Error above
|
|
415
|
-
|
|
452
|
+
if (!isFreshSlugError(e)) throw e;
|
|
416
453
|
}
|
|
417
454
|
|
|
418
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
|
-
|
|
43
|
+
${apiUrlLine}
|
|
41
44
|
|
|
42
45
|
# [env] # non-sensitive runtime config (plaintext)
|
|
43
46
|
# MY_FLAG = "1"
|
|
@@ -49,7 +52,7 @@ apiUrl = "${apiUrl || "https://<the-trial-platform-host>"}"
|
|
|
49
52
|
ctx.logger.success(`Wrote ${file}`);
|
|
50
53
|
if (!apiUrl) ctx.logger.warn("Set [bnb] apiUrl (or BNBAGENT_API_URL) before deploying — there is no default.");
|
|
51
54
|
ctx.logger.info("Next steps:");
|
|
52
|
-
ctx.logger.info(" 1. bnbagent-deploy login
|
|
55
|
+
ctx.logger.info(" 1. bnbagent-deploy login --provider bnb (GitHub device flow)");
|
|
53
56
|
ctx.logger.info(" 2. bnbagent-deploy pack (Python zip projects — vendors arm64 deps)");
|
|
54
57
|
ctx.logger.info(" 3. bnbagent-deploy deploy");
|
|
55
58
|
}
|