@bnbagent/studio-cli 0.0.6-alpha.1
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/DISCLAIMER.md +48 -0
- package/LICENSE +201 -0
- package/dist/_agentcoreName-DZDWEYD3.js +7 -0
- package/dist/_twak-5XQMOFUC.js +25 -0
- package/dist/bag.js +19358 -0
- package/dist/chunk-7RAKL4AS.js +172 -0
- package/dist/chunk-M3ODFCA7.js +1053 -0
- package/dist/chunk-U7IDQ3K5.js +14 -0
- package/dist/deployCli-N6TPN6XA.js +40 -0
- package/package.json +64 -0
- package/recipes/agent/code/{{PKG}}/signing.ts.tmpl +287 -0
- package/recipes/agent/recipe.toml +35 -0
- package/recipes/providers/pieverse-llm/recipe.toml +16 -0
- package/recipes/providers/pieverse-llm/skills/funding-pieverse-llm.md +203 -0
- package/recipes/runtimes/agentcore/code/{{PKG}}/.dockerignore.tmpl +8 -0
- package/recipes/runtimes/agentcore/code/{{PKG}}/Dockerfile.tmpl +50 -0
- package/recipes/runtimes/agentcore/code/{{PKG}}/agentCard.ts.tmpl +135 -0
- package/recipes/runtimes/agentcore/code/{{PKG}}/dualMain.ts.tmpl +402 -0
- package/recipes/runtimes/agentcore/code/{{PKG}}/executor.ts.tmpl +147 -0
- package/recipes/runtimes/agentcore/code/{{PKG}}/main.ts.tmpl +344 -0
- package/recipes/runtimes/agentcore/code/{{PKG}}/mcpMain.ts.tmpl +677 -0
- package/recipes/runtimes/agentcore/code/{{PKG}}/model.ts.tmpl +117 -0
- package/recipes/runtimes/agentcore/code/{{PKG}}/sellerCore.ts.tmpl +503 -0
- package/recipes/runtimes/agentcore/code/{{PKG}}/tools.ts.tmpl +157 -0
- package/recipes/runtimes/agentcore/recipe.toml +97 -0
- package/recipes/runtimes/azure-foundry/code/{{PKG}}/.dockerignore.tmpl +8 -0
- package/recipes/runtimes/azure-foundry/code/{{PKG}}/Dockerfile.tmpl +47 -0
- package/recipes/runtimes/azure-foundry/code/{{PKG}}/agentCard.ts.tmpl +131 -0
- package/recipes/runtimes/azure-foundry/code/{{PKG}}/executor.ts.tmpl +504 -0
- package/recipes/runtimes/azure-foundry/code/{{PKG}}/foundryMain.ts.tmpl +300 -0
- package/recipes/runtimes/azure-foundry/code/{{PKG}}/main.ts.tmpl +196 -0
- package/recipes/runtimes/azure-foundry/code/{{PKG}}/mcpMain.ts.tmpl +562 -0
- package/recipes/runtimes/azure-foundry/code/{{PKG}}/model.ts.tmpl +117 -0
- package/recipes/runtimes/azure-foundry/code/{{PKG}}/tools.ts.tmpl +157 -0
- package/recipes/runtimes/azure-foundry/recipe.toml +88 -0
- package/recipes/tools-chain/code/{{PKG}}/chainTools.ts.tmpl +166 -0
- package/recipes/tools-chain/recipe.toml +11 -0
- package/recipes/wallet/recipe.toml +20 -0
- package/recipes/x402-buyer/code/{{PKG}}/x402Buyer.ts.tmpl +175 -0
- package/recipes/x402-buyer/recipe.toml +15 -0
- package/skills/bnbagent-studio.md +107 -0
- package/skills/references/bnbagent-studio-adding-to-project.md +241 -0
- package/skills/references/bnbagent-studio-buying-from-bazaar.md +169 -0
- package/skills/references/bnbagent-studio-buying-via-8183.md +222 -0
- package/skills/references/bnbagent-studio-extending-signing.md +227 -0
- package/skills/references/bnbagent-studio-operating.md +211 -0
- package/skills/references/bnbagent-studio-scaffolding-agent.md +536 -0
- package/skills/references/bnbagent-studio-selling-via-8183.md +271 -0
- package/skills/references/bnbagent-studio-selling-via-b402.md +194 -0
- package/skills/references/bnbagent-studio-use-aws-agentcore.md +208 -0
- package/skills/references/bnbagent-studio-use-azure-foundry.md +164 -0
- package/skills/references/bnbagent-studio-use-bnb-trial.md +92 -0
- package/skills/references/bnbagent-studio-using-altana-wallet.md +68 -0
- package/skills/references/bnbagent-studio-using-twak-wallet.md +260 -0
- package/skills/references/bnbagent-studio-wiring-llm-tools.md +338 -0
|
@@ -0,0 +1,1053 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
TWAK_CLI_VERSION,
|
|
4
|
+
runCapture,
|
|
5
|
+
runCaptureOut,
|
|
6
|
+
runStream,
|
|
7
|
+
whichTwak
|
|
8
|
+
} from "./chunk-7RAKL4AS.js";
|
|
9
|
+
|
|
10
|
+
// src/cli/_deploy/deployCli.ts
|
|
11
|
+
import * as fs4 from "fs";
|
|
12
|
+
import * as os from "os";
|
|
13
|
+
import * as path5 from "path";
|
|
14
|
+
import {
|
|
15
|
+
findSubProjectRoot as findSubProjectRoot2,
|
|
16
|
+
loadStudioToml as loadStudioToml2
|
|
17
|
+
} from "@bnbagent/studio-runtime/config";
|
|
18
|
+
import { stringify as tomlStringify } from "smol-toml";
|
|
19
|
+
|
|
20
|
+
// src/cli/_hosted/zipBuilder.ts
|
|
21
|
+
import { createHash } from "crypto";
|
|
22
|
+
import * as fs from "fs";
|
|
23
|
+
import * as path from "path";
|
|
24
|
+
import { build as esbuildBuild } from "esbuild";
|
|
25
|
+
|
|
26
|
+
// src/cli/utils/protocol.ts
|
|
27
|
+
var SUPPORTED_FACES = ["A2A", "MCP", "X402"];
|
|
28
|
+
function table(value) {
|
|
29
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
30
|
+
}
|
|
31
|
+
function normalizeProtocolFaces(value) {
|
|
32
|
+
const raw = typeof value === "string" ? value.split(",") : value;
|
|
33
|
+
if (!Array.isArray(raw) || raw.length === 0) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
"protocol faces must be a non-empty comma list or TOML array"
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
const seen = /* @__PURE__ */ new Set();
|
|
39
|
+
for (const item of raw) {
|
|
40
|
+
if (typeof item !== "string" || item.trim().length === 0) {
|
|
41
|
+
throw new Error("protocol faces must contain non-empty strings");
|
|
42
|
+
}
|
|
43
|
+
const face = item.trim().toUpperCase();
|
|
44
|
+
if (!SUPPORTED_FACES.includes(face)) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`unsupported protocol face '${item}'; expected A2A, MCP, or X402`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
if (seen.has(face)) {
|
|
50
|
+
throw new Error(`duplicate protocol face '${face}'`);
|
|
51
|
+
}
|
|
52
|
+
seen.add(face);
|
|
53
|
+
}
|
|
54
|
+
return SUPPORTED_FACES.filter((face) => seen.has(face));
|
|
55
|
+
}
|
|
56
|
+
function stackFaces(stack, payments = {}) {
|
|
57
|
+
if (Object.hasOwn(stack, "protocols")) {
|
|
58
|
+
if (!Array.isArray(stack.protocols)) {
|
|
59
|
+
throw new Error("[stack].protocols must be a non-empty array");
|
|
60
|
+
}
|
|
61
|
+
return normalizeProtocolFaces(stack.protocols);
|
|
62
|
+
}
|
|
63
|
+
const rawProtocol = typeof stack.protocol === "string" ? stack.protocol.trim().toUpperCase() : "A2A";
|
|
64
|
+
const protocol = rawProtocol === "MCP" || rawProtocol === "A2A" ? rawProtocol : "A2A";
|
|
65
|
+
const faces = [protocol];
|
|
66
|
+
if (table(payments.x402_seller).enabled === true) {
|
|
67
|
+
faces.push("X402");
|
|
68
|
+
}
|
|
69
|
+
return SUPPORTED_FACES.filter((face) => faces.includes(face));
|
|
70
|
+
}
|
|
71
|
+
function hasA2aFace(faces) {
|
|
72
|
+
return faces.includes("A2A");
|
|
73
|
+
}
|
|
74
|
+
function hasMcpFace(faces) {
|
|
75
|
+
return faces.includes("MCP");
|
|
76
|
+
}
|
|
77
|
+
function hasX402Face(faces) {
|
|
78
|
+
return faces.includes("X402");
|
|
79
|
+
}
|
|
80
|
+
function nativeProtocolOf(faces) {
|
|
81
|
+
if (hasA2aFace(faces)) {
|
|
82
|
+
return "A2A";
|
|
83
|
+
}
|
|
84
|
+
if (hasMcpFace(faces)) {
|
|
85
|
+
return "MCP";
|
|
86
|
+
}
|
|
87
|
+
if (hasX402Face(faces)) {
|
|
88
|
+
return "A2A";
|
|
89
|
+
}
|
|
90
|
+
throw new Error("at least one protocol face is required");
|
|
91
|
+
}
|
|
92
|
+
function entryStemOf(faces) {
|
|
93
|
+
if (hasA2aFace(faces) && hasMcpFace(faces)) {
|
|
94
|
+
return "dualMain";
|
|
95
|
+
}
|
|
96
|
+
return nativeProtocolOf(faces) === "MCP" ? "mcpMain" : "main";
|
|
97
|
+
}
|
|
98
|
+
function devPortOf(faces) {
|
|
99
|
+
return nativeProtocolOf(faces) === "MCP" ? 8e3 : 9e3;
|
|
100
|
+
}
|
|
101
|
+
function recipeModeOf(faces) {
|
|
102
|
+
if (hasA2aFace(faces) && hasMcpFace(faces)) {
|
|
103
|
+
return "both";
|
|
104
|
+
}
|
|
105
|
+
return nativeProtocolOf(faces) === "MCP" ? "mcp" : "a2a";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/cli/_hosted/zipBuilder.ts
|
|
109
|
+
async function loadZipArchive() {
|
|
110
|
+
const moduleName = "archiver";
|
|
111
|
+
const mod = await import(moduleName);
|
|
112
|
+
return mod.ZipArchive;
|
|
113
|
+
}
|
|
114
|
+
var MAX_COMPRESSED_BYTES = 250 * 1024 * 1024;
|
|
115
|
+
var MAX_UNCOMPRESSED_BYTES = 750 * 1024 * 1024;
|
|
116
|
+
var TARGET_NODE = "node22";
|
|
117
|
+
async function buildZip(agentDir, outZip, opts = {}) {
|
|
118
|
+
if (!isDir(agentDir)) {
|
|
119
|
+
throw new Error(`agent code dir not found for zip build: ${agentDir}`);
|
|
120
|
+
}
|
|
121
|
+
const faces = opts.protocols ?? normalizeProtocolFaces(opts.protocol ?? "A2A");
|
|
122
|
+
const stem = entryStemOf(faces);
|
|
123
|
+
const entrypoint = `${stem}.js`;
|
|
124
|
+
const entry = resolveEntry(agentDir, stem);
|
|
125
|
+
const buildRoot = path.join(
|
|
126
|
+
path.dirname(outZip),
|
|
127
|
+
`${path.basename(outZip, ".zip")}.build`
|
|
128
|
+
);
|
|
129
|
+
fs.rmSync(buildRoot, { recursive: true, force: true });
|
|
130
|
+
fs.mkdirSync(buildRoot, { recursive: true });
|
|
131
|
+
try {
|
|
132
|
+
const outfile = path.join(buildRoot, entrypoint);
|
|
133
|
+
await (opts.bundle ?? bundleEntry)(entry, outfile);
|
|
134
|
+
stageManifest(agentDir, buildRoot, entrypoint);
|
|
135
|
+
stageStudioToml(agentDir, buildRoot);
|
|
136
|
+
assertWithinUncompressedCap(
|
|
137
|
+
buildRoot,
|
|
138
|
+
opts.maxUncompressedBytes ?? MAX_UNCOMPRESSED_BYTES
|
|
139
|
+
);
|
|
140
|
+
await pack(buildRoot, outZip);
|
|
141
|
+
} finally {
|
|
142
|
+
fs.rmSync(buildRoot, { recursive: true, force: true });
|
|
143
|
+
}
|
|
144
|
+
assertWithinCompressedCap(
|
|
145
|
+
outZip,
|
|
146
|
+
opts.maxCompressedBytes ?? MAX_COMPRESSED_BYTES
|
|
147
|
+
);
|
|
148
|
+
return { checksum: sha256File(outZip), entrypoint };
|
|
149
|
+
}
|
|
150
|
+
function resolveEntry(agentDir, stem) {
|
|
151
|
+
const candidates = [
|
|
152
|
+
path.join(agentDir, "src", `${stem}.ts`),
|
|
153
|
+
path.join(agentDir, "dist", `${stem}.js`),
|
|
154
|
+
path.join(agentDir, `${stem}.ts`),
|
|
155
|
+
path.join(agentDir, `${stem}.js`)
|
|
156
|
+
];
|
|
157
|
+
for (const candidate of candidates) {
|
|
158
|
+
if (isFile(candidate)) {
|
|
159
|
+
return candidate;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
throw new Error(
|
|
163
|
+
`no ${stem}.ts/${stem}.js entry found under ${agentDir} (looked in src/, dist/, and the project root) \u2014 the zip channel bundles the protocol entry the scaffold emitted. Re-scaffold with \`bag init\`, or switch [deploy].platform_artifact to "container".`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
async function bundleEntry(entry, outfile) {
|
|
167
|
+
try {
|
|
168
|
+
await esbuildBuild({
|
|
169
|
+
entryPoints: [entry],
|
|
170
|
+
outfile,
|
|
171
|
+
bundle: true,
|
|
172
|
+
platform: "node",
|
|
173
|
+
target: TARGET_NODE,
|
|
174
|
+
format: "esm",
|
|
175
|
+
minify: true,
|
|
176
|
+
legalComments: "none",
|
|
177
|
+
sourcemap: false,
|
|
178
|
+
logLevel: "silent",
|
|
179
|
+
banner: {
|
|
180
|
+
js: 'import { createRequire as __bagCreateRequire } from "node:module";const require = __bagCreateRequire(import.meta.url);'
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
} catch (exc) {
|
|
184
|
+
const msg = exc instanceof Error ? exc.message : String(exc);
|
|
185
|
+
throw new Error(
|
|
186
|
+
`bundling the agent for the zip runtime failed \u2014 a dependency likely cannot be bundled (native addon / dynamic import esbuild cannot resolve). Fix the import, or set [deploy].platform_artifact = "container". esbuild said:
|
|
187
|
+
${msg.slice(0, 1e3)}`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function stageManifest(agentDir, buildRoot, entrypoint) {
|
|
192
|
+
let name = "bnbagent-seller";
|
|
193
|
+
let version = "0.0.0";
|
|
194
|
+
try {
|
|
195
|
+
const pkg = JSON.parse(
|
|
196
|
+
fs.readFileSync(path.join(agentDir, "package.json"), "utf-8")
|
|
197
|
+
);
|
|
198
|
+
if (pkg !== null && typeof pkg === "object" && !Array.isArray(pkg)) {
|
|
199
|
+
const p = pkg;
|
|
200
|
+
if (typeof p.name === "string" && p.name) {
|
|
201
|
+
name = p.name;
|
|
202
|
+
}
|
|
203
|
+
if (typeof p.version === "string" && p.version) {
|
|
204
|
+
version = p.version;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
} catch {
|
|
208
|
+
}
|
|
209
|
+
fs.writeFileSync(
|
|
210
|
+
path.join(buildRoot, "package.json"),
|
|
211
|
+
`${JSON.stringify(
|
|
212
|
+
{ name, version, private: true, type: "module", main: entrypoint },
|
|
213
|
+
null,
|
|
214
|
+
2
|
|
215
|
+
)}
|
|
216
|
+
`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
function stageStudioToml(agentDir, buildRoot) {
|
|
220
|
+
const src = path.join(agentDir, "studio.toml");
|
|
221
|
+
if (isFile(src)) {
|
|
222
|
+
fs.copyFileSync(src, path.join(buildRoot, "studio.toml"));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async function pack(buildRoot, outZip) {
|
|
226
|
+
fs.rmSync(outZip, { force: true });
|
|
227
|
+
const ZipArchive = await loadZipArchive();
|
|
228
|
+
await new Promise((resolve, reject) => {
|
|
229
|
+
const output = fs.createWriteStream(outZip);
|
|
230
|
+
const archive = new ZipArchive({ zlib: { level: 9 } });
|
|
231
|
+
output.on("close", () => resolve());
|
|
232
|
+
archive.on("error", (err) => reject(err));
|
|
233
|
+
archive.pipe(output);
|
|
234
|
+
for (const file of listFiles(buildRoot).sort()) {
|
|
235
|
+
archive.file(path.join(buildRoot, file), { name: file });
|
|
236
|
+
}
|
|
237
|
+
void archive.finalize();
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
function assertWithinUncompressedCap(buildRoot, cap) {
|
|
241
|
+
let total = 0;
|
|
242
|
+
for (const file of listFiles(buildRoot)) {
|
|
243
|
+
total += fs.statSync(path.join(buildRoot, file)).size;
|
|
244
|
+
}
|
|
245
|
+
if (total > cap) {
|
|
246
|
+
throw new Error(
|
|
247
|
+
`zip uncompressed size ${mb(total)} exceeds the platform cap ${mb(cap)}. Trim dependencies or deploy as a container (no uncompressed cap below the 2GB image limit).`
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function assertWithinCompressedCap(outZip, cap) {
|
|
252
|
+
const size = fs.statSync(outZip).size;
|
|
253
|
+
if (size > cap) {
|
|
254
|
+
throw new Error(
|
|
255
|
+
`zip compressed size ${mb(size)} exceeds the platform cap ${mb(cap)}. Trim dependencies or deploy as a container.`
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function sha256File(filePath) {
|
|
260
|
+
const hash = createHash("sha256");
|
|
261
|
+
hash.update(fs.readFileSync(filePath));
|
|
262
|
+
return hash.digest("hex");
|
|
263
|
+
}
|
|
264
|
+
function listFiles(root, prefix = "") {
|
|
265
|
+
const out = [];
|
|
266
|
+
for (const entry of fs.readdirSync(path.join(root, prefix), {
|
|
267
|
+
withFileTypes: true
|
|
268
|
+
})) {
|
|
269
|
+
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
270
|
+
if (entry.isDirectory()) {
|
|
271
|
+
out.push(...listFiles(root, rel));
|
|
272
|
+
} else if (entry.isFile()) {
|
|
273
|
+
out.push(rel);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return out;
|
|
277
|
+
}
|
|
278
|
+
function isFile(p) {
|
|
279
|
+
try {
|
|
280
|
+
return fs.statSync(p).isFile();
|
|
281
|
+
} catch {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function isDir(p) {
|
|
286
|
+
try {
|
|
287
|
+
return fs.statSync(p).isDirectory();
|
|
288
|
+
} catch {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function mb(n) {
|
|
293
|
+
return `${(n / (1024 * 1024)).toFixed(1)}MB`;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// src/cli/exit.ts
|
|
297
|
+
var CliExit = class extends Error {
|
|
298
|
+
constructor(code) {
|
|
299
|
+
super(`exit ${code}`);
|
|
300
|
+
this.code = code;
|
|
301
|
+
this.name = "CliExit";
|
|
302
|
+
}
|
|
303
|
+
code;
|
|
304
|
+
};
|
|
305
|
+
function act(fn) {
|
|
306
|
+
return async (...args) => {
|
|
307
|
+
const code = await fn(...args);
|
|
308
|
+
if (code !== 0) {
|
|
309
|
+
throw new CliExit(code);
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
function printOut(line) {
|
|
314
|
+
process.stdout.write(`${line}
|
|
315
|
+
`);
|
|
316
|
+
}
|
|
317
|
+
function printErr(line) {
|
|
318
|
+
process.stderr.write(`${line}
|
|
319
|
+
`);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// src/cli/preflight.ts
|
|
323
|
+
var OK = "ok";
|
|
324
|
+
var WARN = "warn";
|
|
325
|
+
var INFO = "info";
|
|
326
|
+
var MARK = {
|
|
327
|
+
[OK]: "\u2713",
|
|
328
|
+
[WARN]: "\u26A0",
|
|
329
|
+
[INFO]: "\u2022"
|
|
330
|
+
};
|
|
331
|
+
var MIN_NODE_MAJOR = 22;
|
|
332
|
+
async function whichBin(name) {
|
|
333
|
+
const cmd = process.platform === "win32" ? "where" : "which";
|
|
334
|
+
const result = await runCapture(cmd, [name]);
|
|
335
|
+
if (result.code !== 0) {
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
const line = result.stdout.split("\n")[0]?.trim();
|
|
339
|
+
return line ? line : null;
|
|
340
|
+
}
|
|
341
|
+
function checkNode() {
|
|
342
|
+
const ver = process.versions.node;
|
|
343
|
+
const major = Number.parseInt(ver.split(".")[0] ?? "0", 10);
|
|
344
|
+
const ok = major >= MIN_NODE_MAJOR;
|
|
345
|
+
return {
|
|
346
|
+
name: "Node.js",
|
|
347
|
+
ok,
|
|
348
|
+
level: ok ? OK : WARN,
|
|
349
|
+
detail: `${ver} (>= ${MIN_NODE_MAJOR} required)`,
|
|
350
|
+
fix: ok ? "" : "upgrade Node.js to >= 22 (https://nodejs.org/en/download)"
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
function agentcoreFlavor(binPath) {
|
|
354
|
+
const p = binPath.toLowerCase();
|
|
355
|
+
if (p.includes("node_modules/@aws/agentcore") || p.includes("/.nvm/") || p.includes("/node_modules/")) {
|
|
356
|
+
return "node";
|
|
357
|
+
}
|
|
358
|
+
if (p.includes("bedrock-agentcore") || p.includes("/.venv/") || p.includes("/venv/")) {
|
|
359
|
+
return "python";
|
|
360
|
+
}
|
|
361
|
+
return "unknown";
|
|
362
|
+
}
|
|
363
|
+
async function checkBunx() {
|
|
364
|
+
const override = (process.env.BNBAGENT_DEPLOY_COMMAND ?? "").trim();
|
|
365
|
+
if (override) {
|
|
366
|
+
return {
|
|
367
|
+
name: "bnbagent-deploy",
|
|
368
|
+
ok: true,
|
|
369
|
+
level: OK,
|
|
370
|
+
detail: `command overridden via BNBAGENT_DEPLOY_COMMAND (${override})`,
|
|
371
|
+
fix: ""
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
const found = await whichBin("bunx");
|
|
375
|
+
if (found === null) {
|
|
376
|
+
return {
|
|
377
|
+
name: "bnbagent-deploy",
|
|
378
|
+
ok: false,
|
|
379
|
+
level: WARN,
|
|
380
|
+
detail: "bunx not found (deploys delegate to the pinned @bnbagent/deploy-cli via bunx)",
|
|
381
|
+
fix: "install Bun 1.3+ (https://bun.sh) or set BNBAGENT_DEPLOY_COMMAND"
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
name: "bnbagent-deploy",
|
|
386
|
+
ok: true,
|
|
387
|
+
level: OK,
|
|
388
|
+
detail: `bunx on PATH (${found}); deploys delegate to the pinned @bnbagent/deploy-cli`,
|
|
389
|
+
fix: ""
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
function checkAws() {
|
|
393
|
+
const has = Boolean(process.env.AWS_ACCESS_KEY_ID || process.env.AWS_PROFILE);
|
|
394
|
+
return {
|
|
395
|
+
name: "AWS credentials",
|
|
396
|
+
ok: has,
|
|
397
|
+
level: has ? OK : WARN,
|
|
398
|
+
detail: has ? "configured" : "not configured (needed to deploy the Agent)",
|
|
399
|
+
fix: has ? "" : "set AWS_ACCESS_KEY_ID / AWS_PROFILE (or fill ~/.aws/credentials \u2014 see the AWS Console's access-key page)"
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
async function checkDocker() {
|
|
403
|
+
const docker = await whichBin("docker");
|
|
404
|
+
if (docker === null) {
|
|
405
|
+
return {
|
|
406
|
+
name: "docker",
|
|
407
|
+
ok: false,
|
|
408
|
+
level: WARN,
|
|
409
|
+
detail: "not found (needed to build the twak Agent container image)",
|
|
410
|
+
fix: "install Docker (https://docs.docker.com/get-docker)"
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
let daemonOk = true;
|
|
414
|
+
try {
|
|
415
|
+
const proc = await runCapture(docker, ["info"], { timeoutMs: 1e4 });
|
|
416
|
+
daemonOk = proc.code === 0;
|
|
417
|
+
} catch {
|
|
418
|
+
daemonOk = false;
|
|
419
|
+
}
|
|
420
|
+
if (daemonOk) {
|
|
421
|
+
return {
|
|
422
|
+
name: "docker",
|
|
423
|
+
ok: true,
|
|
424
|
+
level: OK,
|
|
425
|
+
detail: "on PATH; daemon responding",
|
|
426
|
+
fix: ""
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
return {
|
|
430
|
+
name: "docker",
|
|
431
|
+
ok: false,
|
|
432
|
+
level: WARN,
|
|
433
|
+
detail: "on PATH but the daemon isn't responding (`docker info` failed) \u2014 a deploy build will fail until Docker is started",
|
|
434
|
+
fix: "start Docker Desktop (or `open -a Docker` on macOS)"
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
async function checkTwak() {
|
|
438
|
+
const found = await whichTwak() !== null;
|
|
439
|
+
return {
|
|
440
|
+
name: "twak CLI",
|
|
441
|
+
ok: found,
|
|
442
|
+
level: found ? OK : WARN,
|
|
443
|
+
detail: found ? "on PATH" : "not found (needed only for wallet.kind='twak' projects)",
|
|
444
|
+
fix: found ? "" : `npm install -g @trustwallet/cli@${TWAK_CLI_VERSION}`
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
function requiredChecks() {
|
|
448
|
+
return [checkNode()];
|
|
449
|
+
}
|
|
450
|
+
async function deployChecks() {
|
|
451
|
+
return [
|
|
452
|
+
await checkBunx(),
|
|
453
|
+
checkAws(),
|
|
454
|
+
await checkDocker(),
|
|
455
|
+
await checkTwak()
|
|
456
|
+
];
|
|
457
|
+
}
|
|
458
|
+
function render(checks) {
|
|
459
|
+
const lines = [];
|
|
460
|
+
for (const c of checks) {
|
|
461
|
+
lines.push(` ${MARK[c.level] ?? "-"} ${c.name}: ${c.detail}`);
|
|
462
|
+
if (c.fix) {
|
|
463
|
+
lines.push(` -> ${c.fix}`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return lines.join("\n");
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// src/cli/_deploy/awsTargets.ts
|
|
470
|
+
import * as fs2 from "fs";
|
|
471
|
+
import * as path2 from "path";
|
|
472
|
+
var DEFAULT_TARGET_NAME = "default";
|
|
473
|
+
function isDict(v) {
|
|
474
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
475
|
+
}
|
|
476
|
+
function pickAccount(entry) {
|
|
477
|
+
return entry.account || entry.accountId;
|
|
478
|
+
}
|
|
479
|
+
function readAwsTarget(root, targetName = DEFAULT_TARGET_NAME) {
|
|
480
|
+
const p = path2.join(root, "agentcore", "aws-targets.json");
|
|
481
|
+
let data;
|
|
482
|
+
try {
|
|
483
|
+
data = JSON.parse(fs2.readFileSync(p, "utf-8"));
|
|
484
|
+
} catch {
|
|
485
|
+
return [null, null];
|
|
486
|
+
}
|
|
487
|
+
let account = null;
|
|
488
|
+
let region = null;
|
|
489
|
+
if (Array.isArray(data)) {
|
|
490
|
+
const entries = data.filter(isDict);
|
|
491
|
+
const chosen = entries.find((e) => e.name === targetName) ?? entries[0] ?? null;
|
|
492
|
+
if (chosen !== null) {
|
|
493
|
+
account = pickAccount(chosen);
|
|
494
|
+
region = chosen.region;
|
|
495
|
+
}
|
|
496
|
+
} else if (isDict(data)) {
|
|
497
|
+
account = pickAccount(data);
|
|
498
|
+
region = data.region;
|
|
499
|
+
const targets = data.targets;
|
|
500
|
+
if ((!account || !region) && isDict(targets)) {
|
|
501
|
+
const dflt = targets.default;
|
|
502
|
+
if (isDict(dflt)) {
|
|
503
|
+
account = account || pickAccount(dflt);
|
|
504
|
+
region = region || dflt.region;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
if (account !== null && account !== void 0) {
|
|
509
|
+
const s = String(account).trim();
|
|
510
|
+
if (!/^\d+$/.test(s)) {
|
|
511
|
+
account = null;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return [account ? String(account) : null, region ? String(region) : null];
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// src/cli/_deploy/x402.ts
|
|
518
|
+
import * as path4 from "path";
|
|
519
|
+
import {
|
|
520
|
+
envLocalPath,
|
|
521
|
+
findSubProjectRoot,
|
|
522
|
+
loadStudioToml
|
|
523
|
+
} from "@bnbagent/studio-runtime/config";
|
|
524
|
+
|
|
525
|
+
// src/cli/utils/envFile.ts
|
|
526
|
+
import * as fs3 from "fs";
|
|
527
|
+
import * as path3 from "path";
|
|
528
|
+
var KEY_LINE_RE = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=/;
|
|
529
|
+
function isValidKey(key) {
|
|
530
|
+
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(key);
|
|
531
|
+
}
|
|
532
|
+
function writePrivate(filePath, text) {
|
|
533
|
+
fs3.writeFileSync(filePath, text, { encoding: "utf-8", mode: 384 });
|
|
534
|
+
fs3.chmodSync(filePath, 384);
|
|
535
|
+
}
|
|
536
|
+
function setEnvVar(filePath, key, value) {
|
|
537
|
+
if (!isValidKey(key)) {
|
|
538
|
+
throw new Error(`invalid env var name: '${key}'`);
|
|
539
|
+
}
|
|
540
|
+
const newLine = `${key}=${value}`;
|
|
541
|
+
fs3.mkdirSync(path3.dirname(filePath), { recursive: true });
|
|
542
|
+
if (!fs3.existsSync(filePath)) {
|
|
543
|
+
writePrivate(filePath, `${newLine}
|
|
544
|
+
`);
|
|
545
|
+
return true;
|
|
546
|
+
}
|
|
547
|
+
const original = fs3.readFileSync(filePath, "utf-8");
|
|
548
|
+
const lines = original.split(/\r?\n/);
|
|
549
|
+
if (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
550
|
+
lines.pop();
|
|
551
|
+
}
|
|
552
|
+
const out = [];
|
|
553
|
+
let replaced = false;
|
|
554
|
+
for (const line of lines) {
|
|
555
|
+
const m = KEY_LINE_RE.exec(line);
|
|
556
|
+
if (m && m[1] === key) {
|
|
557
|
+
if (replaced) {
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
out.push(newLine);
|
|
561
|
+
replaced = true;
|
|
562
|
+
} else {
|
|
563
|
+
out.push(line);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
if (!replaced) {
|
|
567
|
+
out.push(newLine);
|
|
568
|
+
}
|
|
569
|
+
const newText = `${out.join("\n")}
|
|
570
|
+
`;
|
|
571
|
+
if (newText === original) {
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
writePrivate(filePath, newText);
|
|
575
|
+
return true;
|
|
576
|
+
}
|
|
577
|
+
function getEnvVar(filePath, key) {
|
|
578
|
+
if (!fs3.existsSync(filePath)) {
|
|
579
|
+
return null;
|
|
580
|
+
}
|
|
581
|
+
for (const line of fs3.readFileSync(filePath, "utf-8").split(/\r?\n/)) {
|
|
582
|
+
const m = KEY_LINE_RE.exec(line);
|
|
583
|
+
if (m && m[1] === key) {
|
|
584
|
+
const eq = line.indexOf("=");
|
|
585
|
+
return eq >= 0 ? line.slice(eq + 1) : "";
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// src/cli/_deploy/x402.ts
|
|
592
|
+
var B402_RUNTIME_KEYS = [
|
|
593
|
+
"B402_BASE_URL",
|
|
594
|
+
"B402_CLIENT_ID",
|
|
595
|
+
"B402_ACCESS_TOKEN",
|
|
596
|
+
"B402_PRIVATE_KEY",
|
|
597
|
+
"B402_PRIVATE_KEY_B64"
|
|
598
|
+
];
|
|
599
|
+
function commerceRails(cfg) {
|
|
600
|
+
const payments = table2(cfg.payments);
|
|
601
|
+
return {
|
|
602
|
+
erc8183: isTable(payments.erc8183),
|
|
603
|
+
x402: table2(payments.x402_seller).enabled === true
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
function b402Credentials(agentRoot) {
|
|
607
|
+
const values = /* @__PURE__ */ new Map();
|
|
608
|
+
const envPath = envLocalPath(agentRoot);
|
|
609
|
+
for (const key of B402_RUNTIME_KEYS) {
|
|
610
|
+
const value = process.env[key] || getEnvVar(envPath, key);
|
|
611
|
+
if (value) values.set(key, value);
|
|
612
|
+
}
|
|
613
|
+
const privateKey = values.has("B402_PRIVATE_KEY") || values.has("B402_PRIVATE_KEY_B64");
|
|
614
|
+
return {
|
|
615
|
+
presentKeys: [...values.keys()],
|
|
616
|
+
complete: values.has("B402_BASE_URL") && values.has("B402_CLIENT_ID") && values.has("B402_ACCESS_TOKEN") && privateKey,
|
|
617
|
+
any: values.size > 0,
|
|
618
|
+
bothPrivateKeyFormats: values.has("B402_PRIVATE_KEY") && values.has("B402_PRIVATE_KEY_B64"),
|
|
619
|
+
baseUrl: values.get("B402_BASE_URL") ?? null
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
function loadDeployConfig(root) {
|
|
623
|
+
const agentRoot = findSubProjectRoot("agent", root) ?? root;
|
|
624
|
+
try {
|
|
625
|
+
return {
|
|
626
|
+
agentRoot,
|
|
627
|
+
cfg: loadStudioToml(path4.join(agentRoot, "studio.toml"))
|
|
628
|
+
};
|
|
629
|
+
} catch {
|
|
630
|
+
return { agentRoot, cfg: {} };
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
function x402DeploySummary(root, destination, publicUrl) {
|
|
634
|
+
const { agentRoot, cfg } = loadDeployConfig(root);
|
|
635
|
+
if (!commerceRails(cfg).x402) return "";
|
|
636
|
+
const credentials = b402Credentials(agentRoot);
|
|
637
|
+
const activation = "run the bnbagent-studio-selling-via-b402 skill, fill the four B402_* variables in .studio/.env.local, then redeploy.";
|
|
638
|
+
if (!credentials.complete) {
|
|
639
|
+
return `x402 rail is DORMANT. To activate: ${activation}`;
|
|
640
|
+
}
|
|
641
|
+
const runtime = String(table2(cfg.stack).runtime ?? "agentcore");
|
|
642
|
+
if (destination !== "platform" && runtime !== "agentcore") {
|
|
643
|
+
return `x402 rail is FORCED DORMANT: the ${runtime} runtime has no x402 path. Deploy to AgentCore (managed platform or self-hosted) to activate the rail.`;
|
|
644
|
+
}
|
|
645
|
+
const settlement = [
|
|
646
|
+
"B402 settlement runs inside every paid request and normally adds 10\u201345 s; buyer timeout must be at least 120 s.",
|
|
647
|
+
"Settlement happens before work. If work later fails, the payment is retained and is not refunded automatically."
|
|
648
|
+
];
|
|
649
|
+
if (destination !== "platform") {
|
|
650
|
+
return [
|
|
651
|
+
"x402 rail is ACTIVE (self-hosted AgentCore).",
|
|
652
|
+
"This target has no anonymous public URL; expose /x402 with your own HTTP front.",
|
|
653
|
+
'The front reaches the agent by wrapping each request as envelope-v1 JSON \u2014 {"v":1,"method":"POST","path":"/x402","headers":{...},"body":"<base64>"} \u2014 inside a SigV4-signed (or JWT bearer) InvokeAgentRuntime call. Limits: 1 MiB request, 5 MiB response, no streaming.',
|
|
654
|
+
"B402 allowlists merchant egress IPs and this deploy has no fixed egress IP: route facilitator egress through a fixed-IP forward proxy you control and submit that IP with the merchant application (see the bnbagent-studio-selling-via-b402 reference).",
|
|
655
|
+
'See docs/guides/x402-selling.md, section "Self-hosted AgentCore access".',
|
|
656
|
+
...settlement
|
|
657
|
+
].join("\n");
|
|
658
|
+
}
|
|
659
|
+
const url = publicUrl ?? "<available after the platform agentId is assigned>";
|
|
660
|
+
return [
|
|
661
|
+
"x402 rail is ACTIVE.",
|
|
662
|
+
`Anonymous paid URL: ${url}`,
|
|
663
|
+
...settlement
|
|
664
|
+
].join("\n");
|
|
665
|
+
}
|
|
666
|
+
function table2(value) {
|
|
667
|
+
return isTable(value) ? value : {};
|
|
668
|
+
}
|
|
669
|
+
function isTable(value) {
|
|
670
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// src/cli/_deploy/deployCli.ts
|
|
674
|
+
var DEPLOY_CLI_VERSION = "0.4.14";
|
|
675
|
+
var DEPLOY_CLI_PACKAGE = `@bnbagent/deploy-cli@${DEPLOY_CLI_VERSION}`;
|
|
676
|
+
var BNB_PLATFORM_API_URL = "https://bnbagent-api.bnbchain.world";
|
|
677
|
+
var BNB_PLATFORM_API_URL_ENV = "BNBAGENT_API_URL";
|
|
678
|
+
var LEGACY_BNB_PLATFORM_API_URL_ENV = "BAG_PLATFORM_API_BASE";
|
|
679
|
+
function deployEndpointValue(...candidates) {
|
|
680
|
+
for (const candidate of candidates) {
|
|
681
|
+
if (typeof candidate === "string") {
|
|
682
|
+
const value = candidate.trim();
|
|
683
|
+
if (value) {
|
|
684
|
+
return value;
|
|
685
|
+
}
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
if (candidate !== null && typeof candidate === "object" && !Array.isArray(candidate)) {
|
|
689
|
+
const value = candidate.value;
|
|
690
|
+
if (typeof value === "string" && value.trim()) {
|
|
691
|
+
return value.trim();
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return null;
|
|
696
|
+
}
|
|
697
|
+
var RESERVED_AGENTCORE_KEYS = ["environmentVariables", "secretName"];
|
|
698
|
+
var FOUNDRY_PASSTHROUGH_KEYS = /* @__PURE__ */ new Set([
|
|
699
|
+
"account",
|
|
700
|
+
"cpu",
|
|
701
|
+
"location",
|
|
702
|
+
"memory",
|
|
703
|
+
"project",
|
|
704
|
+
"projectEndpoint",
|
|
705
|
+
"protocol",
|
|
706
|
+
"registry"
|
|
707
|
+
]);
|
|
708
|
+
function tableOf(data, key) {
|
|
709
|
+
const v = data[key];
|
|
710
|
+
return v !== null && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
711
|
+
}
|
|
712
|
+
function descriptorName(root) {
|
|
713
|
+
try {
|
|
714
|
+
const raw = JSON.parse(
|
|
715
|
+
fs4.readFileSync(path5.join(root, "agentcore", "agentcore.json"), "utf-8")
|
|
716
|
+
);
|
|
717
|
+
if (raw !== null && typeof raw === "object" && !Array.isArray(raw)) {
|
|
718
|
+
const name = raw.name;
|
|
719
|
+
if (name) {
|
|
720
|
+
return String(name);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
} catch {
|
|
724
|
+
}
|
|
725
|
+
return null;
|
|
726
|
+
}
|
|
727
|
+
function bnbPlatformApiUrl(environment = process.env) {
|
|
728
|
+
const configured = environment[BNB_PLATFORM_API_URL_ENV]?.trim() || environment[LEGACY_BNB_PLATFORM_API_URL_ENV]?.trim() || BNB_PLATFORM_API_URL;
|
|
729
|
+
return configured.replace(/\/+$/u, "");
|
|
730
|
+
}
|
|
731
|
+
function deployCommand(environment = process.env) {
|
|
732
|
+
const override = (environment.BNBAGENT_DEPLOY_COMMAND ?? "").trim();
|
|
733
|
+
if (override) {
|
|
734
|
+
return override.split(/\s+/u);
|
|
735
|
+
}
|
|
736
|
+
return ["bunx", "--bun", DEPLOY_CLI_PACKAGE];
|
|
737
|
+
}
|
|
738
|
+
function providerPassthrough(studio, table3) {
|
|
739
|
+
const raw = tableOf(studio, "deploy")[table3];
|
|
740
|
+
if (raw === void 0 || raw === null) {
|
|
741
|
+
return {};
|
|
742
|
+
}
|
|
743
|
+
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
744
|
+
throw new Error(
|
|
745
|
+
`studio.toml [deploy.${table3}] must be a TOML table of deploy-spec keys`
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
return { ...raw };
|
|
749
|
+
}
|
|
750
|
+
function buildDeploySpec(root, opts) {
|
|
751
|
+
const agentRoot = findSubProjectRoot2("agent", root) ?? root;
|
|
752
|
+
const studio = loadStudioToml2(path5.join(agentRoot, "studio.toml"));
|
|
753
|
+
const name = String(
|
|
754
|
+
opts.nameOverride || descriptorName(root) || tableOf(studio, "project").name || path5.basename(root)
|
|
755
|
+
);
|
|
756
|
+
const stack = tableOf(studio, "stack");
|
|
757
|
+
const payments = tableOf(studio, "payments");
|
|
758
|
+
const faces = stackFaces(stack, payments);
|
|
759
|
+
const hasProtocolsArray = Array.isArray(stack.protocols);
|
|
760
|
+
const protocol = nativeProtocolOf(faces);
|
|
761
|
+
const isContainer = opts.packaging === "container";
|
|
762
|
+
const deploy = {
|
|
763
|
+
target: opts.target,
|
|
764
|
+
type: opts.packaging,
|
|
765
|
+
// The spec lives in a tempdir OUTSIDE the workspace, so every path
|
|
766
|
+
// field must be absolute — deploy-core resolves them against the
|
|
767
|
+
// config file's directory.
|
|
768
|
+
path: agentRoot
|
|
769
|
+
};
|
|
770
|
+
const doc = { name, deploy };
|
|
771
|
+
const env = {
|
|
772
|
+
STUDIO_AUDIT_LOG_PATH: "/tmp/.studio/audit-log.jsonl",
|
|
773
|
+
OTEL_TRACES_EXPORTER: "none"
|
|
774
|
+
};
|
|
775
|
+
if (isContainer) {
|
|
776
|
+
deploy.dockerfile = path5.join(agentRoot, "Dockerfile");
|
|
777
|
+
} else {
|
|
778
|
+
if (!opts.zip) {
|
|
779
|
+
throw new Error("zip packaging requires a prebuilt zip artifact");
|
|
780
|
+
}
|
|
781
|
+
deploy.dist = opts.zip.path;
|
|
782
|
+
deploy.entrypoint = opts.zip.entrypoint;
|
|
783
|
+
deploy.runtimeVersion = "NODE_22";
|
|
784
|
+
}
|
|
785
|
+
if (opts.target === "aws/agentcore") {
|
|
786
|
+
const [account, region] = readAwsTarget(root);
|
|
787
|
+
if (account) {
|
|
788
|
+
deploy.account = account;
|
|
789
|
+
}
|
|
790
|
+
if (region) {
|
|
791
|
+
deploy.region = region;
|
|
792
|
+
}
|
|
793
|
+
deploy.protocol = protocol;
|
|
794
|
+
const agentcore = {};
|
|
795
|
+
const passthrough = providerPassthrough(studio, "agentcore");
|
|
796
|
+
const reserved = RESERVED_AGENTCORE_KEYS.filter((k) => k in passthrough);
|
|
797
|
+
if (reserved.length > 0) {
|
|
798
|
+
throw new Error(
|
|
799
|
+
`studio.toml [deploy.agentcore] must not set ${reserved.join(", ")}: secretName is studio-managed, and environmentVariables would replace deploy's merged runtime env (secret pointer included)`
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
Object.assign(agentcore, passthrough);
|
|
803
|
+
agentcore.secretName = `bnbagent/${name}/runtime`;
|
|
804
|
+
doc.agentcore = agentcore;
|
|
805
|
+
} else if (opts.target === "azure/foundry") {
|
|
806
|
+
deploy.protocol = "HTTP";
|
|
807
|
+
const foundry = {};
|
|
808
|
+
const passthrough = providerPassthrough(studio, "foundry");
|
|
809
|
+
const unsupported = Object.keys(passthrough).filter((k) => !FOUNDRY_PASSTHROUGH_KEYS.has(k)).sort();
|
|
810
|
+
if (unsupported.length > 0) {
|
|
811
|
+
printErr(
|
|
812
|
+
`note: deploy-cli ${DEPLOY_CLI_VERSION} ignores [deploy.foundry] ${unsupported.join(", ")}; forwarded anyway for newer deploy versions`
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
Object.assign(foundry, passthrough);
|
|
816
|
+
if (!("protocol" in foundry)) {
|
|
817
|
+
foundry.protocol = "invocations";
|
|
818
|
+
}
|
|
819
|
+
const azure = tableOf(studio, "azure");
|
|
820
|
+
for (const [source, dest] of [
|
|
821
|
+
["account_name", "account"],
|
|
822
|
+
["project_name", "project"],
|
|
823
|
+
["project_endpoint", "projectEndpoint"],
|
|
824
|
+
["location", "location"]
|
|
825
|
+
]) {
|
|
826
|
+
const value = String(azure[source] ?? "").trim();
|
|
827
|
+
if (value) {
|
|
828
|
+
if (dest in passthrough && String(passthrough[dest]) !== value) {
|
|
829
|
+
printErr(
|
|
830
|
+
`note: studio.toml [azure].${source} wins over [deploy.foundry].${dest}`
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
foundry[dest] = value;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
doc.foundry = foundry;
|
|
837
|
+
} else {
|
|
838
|
+
for (const table3 of ["agentcore", "foundry"]) {
|
|
839
|
+
if (Object.keys(providerPassthrough(studio, table3)).length > 0) {
|
|
840
|
+
printErr(
|
|
841
|
+
`note: the bnb/trial platform does not allow custom provider configuration; [deploy.${table3}] ignored`
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
deploy.protocol = protocol;
|
|
846
|
+
doc.bnb = { apiUrl: bnbPlatformApiUrl() };
|
|
847
|
+
}
|
|
848
|
+
if (opts.inlineSecrets) {
|
|
849
|
+
Object.assign(env, opts.inlineSecrets);
|
|
850
|
+
}
|
|
851
|
+
if (opts.target === "bnb/trial" && tableOf(studio, "deploy").destination === "platform" && (!hasProtocolsArray || hasX402Face(faces)) && commerceRails(studio).x402 && b402Credentials(agentRoot).complete) {
|
|
852
|
+
doc.x402 = {
|
|
853
|
+
publicPaths: ["/x402"],
|
|
854
|
+
tunnel: "http-envelope-v1"
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
if (opts.target === "bnb/trial" && tableOf(studio, "deploy").destination === "platform" && hasA2aFace(faces) && hasMcpFace(faces)) {
|
|
858
|
+
doc.mcp = {
|
|
859
|
+
publicPaths: ["/mcp"],
|
|
860
|
+
tunnel: "http-envelope-v1"
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
if (opts.target === "bnb/trial" && tableOf(studio, "deploy").destination === "platform" && hasProtocolsArray && faces.length === 1 && hasX402Face(faces)) {
|
|
864
|
+
doc.suppressProtocolFace = true;
|
|
865
|
+
}
|
|
866
|
+
doc.env = env;
|
|
867
|
+
if (opts.secretFile && !opts.inlineSecrets) {
|
|
868
|
+
doc.secrets = { envFile: opts.secretFile };
|
|
869
|
+
}
|
|
870
|
+
return { doc, name, agentRoot };
|
|
871
|
+
}
|
|
872
|
+
function renderSecretEnvFile(payload) {
|
|
873
|
+
return Object.keys(payload).sort().map((key) => `${key}=${JSON.stringify(payload[key])}
|
|
874
|
+
`).join("");
|
|
875
|
+
}
|
|
876
|
+
function writePrivateSync(filePath, content) {
|
|
877
|
+
fs4.writeFileSync(filePath, content, { mode: 384 });
|
|
878
|
+
fs4.chmodSync(filePath, 384);
|
|
879
|
+
}
|
|
880
|
+
async function withDeployFiles(root, opts, fn) {
|
|
881
|
+
const scratch = fs4.mkdtempSync(path5.join(os.tmpdir(), "bag-deploy-"));
|
|
882
|
+
try {
|
|
883
|
+
let zip;
|
|
884
|
+
if (opts.packaging === "zip") {
|
|
885
|
+
const agentRoot = findSubProjectRoot2("agent", root) ?? root;
|
|
886
|
+
const studio = loadStudioToml2(
|
|
887
|
+
path5.join(agentRoot, "studio.toml")
|
|
888
|
+
);
|
|
889
|
+
const stack = tableOf(studio, "stack");
|
|
890
|
+
const faces = stackFaces(stack, tableOf(studio, "payments"));
|
|
891
|
+
const outZip = path5.join(scratch, "code.zip");
|
|
892
|
+
const built = await (opts.buildZipFn ?? buildZip)(agentRoot, outZip, {
|
|
893
|
+
protocols: faces
|
|
894
|
+
});
|
|
895
|
+
zip = { path: outZip, entrypoint: built.entrypoint };
|
|
896
|
+
}
|
|
897
|
+
const secretFile = path5.join(scratch, "secrets.env");
|
|
898
|
+
const inline = opts.inlineSecrets;
|
|
899
|
+
const payload = opts.secrets ?? {};
|
|
900
|
+
const spec = buildDeploySpec(root, {
|
|
901
|
+
target: opts.target,
|
|
902
|
+
packaging: opts.packaging,
|
|
903
|
+
nameOverride: opts.nameOverride,
|
|
904
|
+
zip,
|
|
905
|
+
inlineSecrets: inline,
|
|
906
|
+
secretFile: !inline && Object.keys(payload).length > 0 ? secretFile : void 0
|
|
907
|
+
});
|
|
908
|
+
if (!inline && Object.keys(payload).length > 0) {
|
|
909
|
+
writePrivateSync(secretFile, renderSecretEnvFile(payload));
|
|
910
|
+
}
|
|
911
|
+
const configPath = path5.join(scratch, "agent-deploy.toml");
|
|
912
|
+
writePrivateSync(configPath, tomlStringify(spec.doc));
|
|
913
|
+
return await fn({ configPath, scratchDir: scratch }, spec);
|
|
914
|
+
} finally {
|
|
915
|
+
fs4.rmSync(scratch, { recursive: true, force: true });
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
var BUNX_HINT = "error: could not start bnbagent-deploy (bunx not found); install Bun 1.3+ or set BNBAGENT_DEPLOY_COMMAND.";
|
|
919
|
+
async function ensureRunnable(environment = process.env) {
|
|
920
|
+
const [bin] = deployCommand(environment);
|
|
921
|
+
if (bin && await whichBin(bin) === null) {
|
|
922
|
+
printErr(BUNX_HINT);
|
|
923
|
+
return false;
|
|
924
|
+
}
|
|
925
|
+
return true;
|
|
926
|
+
}
|
|
927
|
+
async function runDeployCliStream(argv, opts = {}) {
|
|
928
|
+
if (!await ensureRunnable()) {
|
|
929
|
+
return 127;
|
|
930
|
+
}
|
|
931
|
+
const [bin, ...prefix] = deployCommand();
|
|
932
|
+
return runStream(bin, [...prefix, ...argv], {
|
|
933
|
+
cwd: opts.cwd,
|
|
934
|
+
env: opts.env ? { ...process.env, ...opts.env } : void 0
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
async function runDeployCliJson(argv, opts = {}) {
|
|
938
|
+
if (!await ensureRunnable()) {
|
|
939
|
+
return { code: 127, data: {} };
|
|
940
|
+
}
|
|
941
|
+
const [bin, ...prefix] = deployCommand();
|
|
942
|
+
const { code, stdout } = await runCaptureOut(
|
|
943
|
+
bin,
|
|
944
|
+
[...prefix, ...argv, "--json"],
|
|
945
|
+
{
|
|
946
|
+
cwd: opts.cwd,
|
|
947
|
+
env: opts.env ? { ...process.env, ...opts.env } : void 0
|
|
948
|
+
}
|
|
949
|
+
);
|
|
950
|
+
let data = {};
|
|
951
|
+
const trimmed = stdout.trim();
|
|
952
|
+
if (trimmed) {
|
|
953
|
+
try {
|
|
954
|
+
const parsed = JSON.parse(trimmed);
|
|
955
|
+
data = parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
956
|
+
} catch {
|
|
957
|
+
printErr("error: bnbagent-deploy returned invalid JSON");
|
|
958
|
+
return { code: code === 0 ? 1 : code, data: {} };
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
if (code !== 0 && !opts.quiet) {
|
|
962
|
+
const message = String(tableOf(data, "error").message ?? "").trim();
|
|
963
|
+
if (message) {
|
|
964
|
+
printErr(`error: ${message}`);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
return { code, data };
|
|
968
|
+
}
|
|
969
|
+
function trialFromDeployCliJson(data) {
|
|
970
|
+
return {
|
|
971
|
+
case: String(data.case ?? "unknown"),
|
|
972
|
+
startedAt: data.started_at ? String(data.started_at) : null,
|
|
973
|
+
expiresAt: data.expires_at ? String(data.expires_at) : null,
|
|
974
|
+
remainingSeconds: typeof data.remaining_seconds === "number" ? data.remaining_seconds : null
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
function bnbEnv() {
|
|
978
|
+
return { [BNB_PLATFORM_API_URL_ENV]: bnbPlatformApiUrl() };
|
|
979
|
+
}
|
|
980
|
+
async function runPlatformAccountCommand(argv, opts = {}) {
|
|
981
|
+
const run = async (args, cwd) => {
|
|
982
|
+
if (opts.json) {
|
|
983
|
+
return runDeployCliJson(args, { cwd, env: bnbEnv(), quiet: opts.quiet });
|
|
984
|
+
}
|
|
985
|
+
const code = await runDeployCliStream(args, { cwd, env: bnbEnv() });
|
|
986
|
+
return { code, data: {} };
|
|
987
|
+
};
|
|
988
|
+
if (!opts.slug) {
|
|
989
|
+
return run([...argv, "--provider", "bnb"]);
|
|
990
|
+
}
|
|
991
|
+
const scratch = fs4.mkdtempSync(path5.join(os.tmpdir(), "bag-platform-"));
|
|
992
|
+
try {
|
|
993
|
+
const configPath = path5.join(scratch, "agent-deploy.toml");
|
|
994
|
+
writePrivateSync(
|
|
995
|
+
configPath,
|
|
996
|
+
tomlStringify({
|
|
997
|
+
name: opts.slug,
|
|
998
|
+
deploy: { target: "bnb/trial", type: "container", path: "." },
|
|
999
|
+
bnb: { apiUrl: bnbPlatformApiUrl() }
|
|
1000
|
+
})
|
|
1001
|
+
);
|
|
1002
|
+
return await run([...argv, "--provider", "bnb", "-f", configPath], scratch);
|
|
1003
|
+
} finally {
|
|
1004
|
+
fs4.rmSync(scratch, { recursive: true, force: true });
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
export {
|
|
1009
|
+
CliExit,
|
|
1010
|
+
act,
|
|
1011
|
+
printOut,
|
|
1012
|
+
printErr,
|
|
1013
|
+
normalizeProtocolFaces,
|
|
1014
|
+
stackFaces,
|
|
1015
|
+
hasA2aFace,
|
|
1016
|
+
hasMcpFace,
|
|
1017
|
+
hasX402Face,
|
|
1018
|
+
nativeProtocolOf,
|
|
1019
|
+
entryStemOf,
|
|
1020
|
+
devPortOf,
|
|
1021
|
+
recipeModeOf,
|
|
1022
|
+
whichBin,
|
|
1023
|
+
agentcoreFlavor,
|
|
1024
|
+
checkDocker,
|
|
1025
|
+
checkTwak,
|
|
1026
|
+
requiredChecks,
|
|
1027
|
+
deployChecks,
|
|
1028
|
+
render,
|
|
1029
|
+
readAwsTarget,
|
|
1030
|
+
setEnvVar,
|
|
1031
|
+
getEnvVar,
|
|
1032
|
+
B402_RUNTIME_KEYS,
|
|
1033
|
+
commerceRails,
|
|
1034
|
+
b402Credentials,
|
|
1035
|
+
x402DeploySummary,
|
|
1036
|
+
DEPLOY_CLI_VERSION,
|
|
1037
|
+
DEPLOY_CLI_PACKAGE,
|
|
1038
|
+
BNB_PLATFORM_API_URL,
|
|
1039
|
+
BNB_PLATFORM_API_URL_ENV,
|
|
1040
|
+
LEGACY_BNB_PLATFORM_API_URL_ENV,
|
|
1041
|
+
deployEndpointValue,
|
|
1042
|
+
bnbPlatformApiUrl,
|
|
1043
|
+
deployCommand,
|
|
1044
|
+
providerPassthrough,
|
|
1045
|
+
buildDeploySpec,
|
|
1046
|
+
renderSecretEnvFile,
|
|
1047
|
+
withDeployFiles,
|
|
1048
|
+
runDeployCliStream,
|
|
1049
|
+
runDeployCliJson,
|
|
1050
|
+
trialFromDeployCliJson,
|
|
1051
|
+
bnbEnv,
|
|
1052
|
+
runPlatformAccountCommand
|
|
1053
|
+
};
|