@bnbagent/deploy-provider-nodeops
v0.6.6
Published
NodeOps CreateOS account and wallet deployments, with a Node.js SDK and bounded MPP/x402 payments.
Readme
NodeOps CreateOS provider
Deploy an HTTP agent with target = "nodeops/createos" using either an account
API Key (mode = "account", the backward-compatible default) or a wallet Gateway
(mode = "wallet"). Account mode uses existing account credits; wallet mode can
authorize bounded MPP/x402 payments. The following sections describe account mode;
see Wallet Gateway mode below for its separate contract and examples. Neither
mode implements BNB trial semantics or automatic renewal.
Prerequisites and authentication
- Create a CreateOS account and an API key with project/deployment permissions.
- Check that account's current credits and billing in CreateOS. Promotional credits, when granted to your account, are used by CreateOS normally. This provider has no verified balance API and does not promise free hosting.
- Inject
CREATEOS_API_KEYinto the deployment process through your shell/CI secret manager. Keep this control-plane key outsideagent-deploy.toml,[env], the agent's secret file, image and ZIP. - Run
bnbagent-deploy login --provider nodeopsto verify and save the key locally, or leave it in the environment for CI.whoamireports the account ID;logoutremoves the saved session but cannot unset your shell variable.
The API origin is fixed to https://api-createos.nodeops.network. Environment
credentials take precedence over the local file
~/.bnbagent-deploy/nodeops/session.json (mode 0600, origin-bound).
SDK services should set flags: { platformService: true } to ignore local sessions.
For multiple accounts, instantiate the provider with a separate env credential
object per tenant; do not mutate shared process environment during a request.
Artifact paths
| Type | User supplies | Local Docker | CreateOS operation |
|---|---|---|---|
| zip | Prebuilt Node 22 JavaScript directory or .zip | No | Upload project; multipart ZIP, remote image build using a fixed Dockerfile |
| container | Dockerfile, source, .dockerignore, public registry repository | Yes | Local linux/amd64 build → registry push → image project deployment |
| image | Public image reference with explicit tag or digest | No | Image project deployment directly from the reference |
ZIP deployment does not compile application source or install dependencies.
Build TypeScript yourself (bun build --target=node); bundle dependencies into
the output. Native libraries and custom runtimes should use container/image.
The provider repackages the artifact under artifact/ and adds a fixed Node 22
Dockerfile (USER node, /app, configured port/entrypoint). This includes
prebuilt .zip inputs: their original bytes are not uploaded unchanged.
Any Dockerfile within the supplied artifact is an ordinary file and is never
selected as the build recipe. No Build AI or automatic security scan is enabled.
ZIP limits are conservatively 50 MiB compressed, 100 MiB expanded and 10,000
files. Directory packaging excludes .env*, the configured secret filename,
VCS/cache folders and node_modules. Prebuilt ZIPs containing those paths are
rejected; traversal, symlinks, duplicates, encrypted archives and ZIP64 are rejected.
Container projects must have .dockerignore entries for .env*, .git and any
custom secret filename, without negation rules. Review your Dockerfile/context:
Docker build instructions remain user code.
Container push uses the user's existing docker login session. The registry
repository must already exist and permit public pulls from CreateOS. No
registry credentials are sent to CreateOS. Private image authentication is not
implemented. The generated tag is unique per build; the pushed image reference
is returned as record.imageUri. Destroy does not delete images from your registry.
Configuration
name = "my-nodeops-agent" # verbatim CreateOS uniqueName: 4–32 letters/digits/hyphens
[deploy]
target = "nodeops/createos"
type = "zip"
dist = "dist"
entrypoint = "index.js"
runtimeVersion = "NODE_22"
protocol = "HTTP"
[nodeops]
port = 8080 # 1024–65535; app must listen on 0.0.0.0
timeoutSeconds = 1800 # default per deployment/promotion/delete wait, 10–3600
invokePath = "/invocations"
# accountId = "<whoami account ID>" # pin applied to ALL lifecycle commands
# environment = "production" # existing environment name or UUID
# imageRepository = "ghcr.io/owner/agent" # required for container; no tag
[env]
APP_ENV = "production"
# Optional: inject values via CreateOS runtime runEnvs, never Docker build args.
# [secrets]
# envFile = ".env.runtime"[secrets].envFile and SDK secrets are merged over [env], then transmitted to
CreateOS runEnvs. These are account-managed runtime environment variables,
not a separate secret-manager vault; project administrators may read them.
The provider does not print these values. Applications must also avoid logging
them. PORT is supplied automatically and must agree with nodeops.port if set.
Keep the configuration's env map complete: updates send the full desired map
to project settings and the selected existing environment; they are not a
per-key secret-store synchronization mechanism.
Unsupported options are rejected even with --skip-validation/--force:
A2A/MCP, inbound OAuth provisioning, payment gateway publication, deliverable
stores, secret references, region selection, custom log retention and unknown
[nodeops] keys. init, token/trial commands and automatic top-ups are unavailable.
Lifecycle and ownership
bnbagent-deploy capabilities --provider nodeops --json
bnbagent-deploy validate --json
bnbagent-deploy deploy --dry-run --json
bnbagent-deploy deploy --json
bnbagent-deploy status --json
bnbagent-deploy invoke --payload '{"prompt":"hello"}'
bnbagent-deploy logs
bnbagent-deploy logs --follow
bnbagent-deploy list --provider nodeops --json
bnbagent-deploy destroy --yesUntil this package is published, run the repository CLI instead of the installed
command: bun /absolute/path/to/bnbagent-deploy/packages/deploy-cli/src/cli.ts ….
Deployment is create-or-update by exact project name, with a description marker.
The provider refuses to mutate a foreign same-name project unless the caller
explicitly passes --take-ownership. Upload/image project type changes require
a new name. A ZIP project's existing port must also remain unchanged (use a new
name to change it). Concurrent deploys to one project should be serialized by the
caller; the API does not expose a verified transactional compare-and-swap contract.
With one existing environment, deployment targets it; with multiple environments,
set nodeops.environment. An explicit selection must exist before deployment.
The provider waits for the deployment, assigns it to the environment if needed,
then waits for active promotion. Environment URLs are preferred. If CreateOS
returns no environment, the result explicitly warns that its deployment URL may
change on redeploy. Successful deployment means CreateOS reports it running,
not an application-specific health-check pass: configure healthPath or use
invoke to verify your agent.
id is the project name; record.projectId is its stable CreateOS UUID and
record.deploymentId identifies this release. Persist the complete record.
SDK lifecycle calls accept either the name or project UUID. list reports project
inventory (active projects have normalized unknown health); status reads
the active deployment. Logs refer to the latest deployment (build logs while
building or after a build failure; runtime logs otherwise), and --follow stays
on that deployment; --since is unsupported. Invocation posts to invokePath,
optionally with --bearer; it never forwards the CreateOS API key.
Destroy deletes the entire managed project, including all its environments. Keep one agent per managed project. It waits for deletion; a second delete after removal succeeds. Failures/timeouts retain the project for inspection. A network interruption during a write reports an unknown outcome and never automatically replays it; inspect CreateOS/status/list before retrying. Updating environment variables and promoting a new version are separate API operations, not an atomic rollback transaction. No paid cleanup, credit purchase or registry deletion runs.
SDK usage (Bun host)
import { createDeployer } from "@bnbagent/deploy-core";
import { createNodeOpsProvider } from "@bnbagent/deploy-provider-nodeops";
const provider = createNodeOpsProvider({ env: { CREATEOS_API_KEY: accountApiKey } });
const deployer = createDeployer({
provider, runtime: "createos", baseDir: projectDirectory,
flags: { platformService: true },
overrides: { nodeops: { accountId, port: 8080 } },
});
const result = await deployer.deploy({
name: "my-nodeops-agent", type: "zip", dist: "dist", entrypoint: "index.js",
}, { secrets: { MODEL_API_KEY: modelApiKey } });
// Persist result.record in the caller's deployment store.
const status = await deployer.status(result.record!.projectId!);Contract verification
REST routes, authentication and envelopes were checked against the official
CreateOS CLI
and MCP server.
The public supported runtime catalog
was fetched on 2026-09-09 and includes the dockerfile runtime. Automated tests
use a stateful API fake and cover wire payloads, lifecycle, ownership, secrets,
errors and packaging. They do not establish a successful real cloud deployment.
Run the NodeOps live verification checklist
with an authorized test account before claiming end-to-end production validation.
Wallet Gateway mode
[nodeops].mode = "wallet" selects a separate, wallet-authenticated adapter at
https://mpp-createos.nodeops.network. Omitted mode stays account for backward
compatibility. There is no automatic credential fallback or Dashboard/account
linking. See ZIP wallet example and
remote Dockerfile example.
Wallet mode supports new ZIP/source deployments, status/list/invoke/delete,
MPP or x402 charges using advertised known USDC assets, identity pins, bounded
payment budgets, and a durable payment-intent journal. BSC uses Permit2 Exact;
Base/Arbitrum use EIP-3009. The current production discovery may restrict which
protocol is offered on each chain. No implicit fallback to the old
X-Payment-Tx direct-transfer flow is implemented.
--pay authorizes one payment under [nodeops.payment] caps. For unattended
new deployments, autoPay=true additionally requires a payTo pin. BSC approval
is automatic only with an explicit maxApprovalGasWei budget and a signer able
to sign transactions, or an injected bounded approval callback. Otherwise the
provider stops before payment and asks the operator to prepare allowance.
validate does not request a deploy/quote. A real deploy can consume existing
credits even without --pay; sharing with active projects additionally requires
useExistingCredits=true.
Wallet runtime values merge spec.env with ctx.secrets (secrets take precedence),
then travel as settings.runEnvs separately from the source archive. Control-plane
keys, invalid variable names/values and mismatched PORT are rejected. Values do
not appear in resource records or payment journals; changing values changes the
request hash. Secret-store references, image references, same-name updates, logs,
named environments and hosting renewal are not exposed by this adapter.
The months parameter buys credits; it is not a subscription or renewal task.
Cloud credit balance remains unknown; the public balance API reports tokens.
Node.js SDK
After building this package, @bnbagent/deploy-provider-nodeops/sdk exports
createNodeOpsClient(options). Its account/ZIP and Gateway paths run on Node 22+
without Bun. Supply a viem-compatible LocalAccount through options.signer;
keys remain inside the caller's wallet. options.paymentIO, fetch, docker,
clock and journal-directory seams permit hermetic tests. SDK callers pass the
normalized deployment spec and Context, with overrides.nodeops.mode, logger,
baseDir, flags and in-memory secrets. deploy validates before mutation.
The source CloudProvider entry remains compatible with Bun deploy-core/CLI.
The package publishes dist/sdk.js and declarations alongside its existing
source entry. Run package build before release/pack.
Application health and invocation
Both modes accept [nodeops].healthPath = "/ping" for an optional, credential-free
HTTP GET after the cloud reports a deployment ready. Omit it to skip the probe;
applications do not need to serve GET /. The path must be relative, without a
query string, fragment or traversal. Redirects are rejected. Probes never receive
account API keys, wallet authentication headers or invocation bearer tokens.
A successful probe adds health: { status: "passed", path: "/ping" } to the
deployment result. An unreachable or non-2xx probe adds health.status = "failed"
and a warning, while retaining the successful cloud status and complete resource
record. CLI JSON and SDK callers should persist record first and use health
to decide whether to expose the service. Do not deploy/pay again to retry a probe;
inspect the existing service. This probe does not verify A2A/MCP or seller payments.
invoke accepts --bearer in both modes (SDK: ctx.flags.bearer); it forwards
only that application token to invokePath. Network failures and non-2xx errors
are sanitized and never automatically retried. destroy --purge and
destroy --purge-images are rejected before cloud access in both modes.
The Node.js SDK exports NodeOpsClient, NodeOpsClientOptions, and
NodeOpsValidationError. Failed SDK preflight exposes error.validation.issues
with their original issue codes; callers can distinguish unsupported capability
from missing credentials without parsing error messages. capabilities() defaults
to account mode; pass Context to inspect wallet mode. Release checks install the
actual npm archives and validate runtime behavior and public TypeScript exports.
Gateway discovery and recovery updates
The Node.js SDK additionally exports createGatewayWalletClient(fetch?):
import { createGatewayWalletClient } from "@bnbagent/deploy-provider-nodeops/sdk";
const gateway = createGatewayWalletClient();
const chains = await gateway.chains();
const balance = await gateway.balance(walletAddress, "bsc");
// balance.balance is derived from validated atomic units, not server display text.These are public GET requests and never attach credentials or sign a deployment.
Balances are on-chain USDC, not CreateOS hosting credits. Failed or mismatched
responses throw rather than reporting zero. The production /agent/chains
inventory checked on 2026-09-10 lists Base, Arbitrum and BSC; do not infer testnet
availability from constants in the upstream example script. Runtime 402 offers
remain authoritative for payment selection.
New wallet project names receive a random suffix because Gateway uniqueName
is global. The journal retains requestName, reusing it for the initial quote,
payment submission and subsequent polling recovery; display names stay unchanged.
Legacy journal entries without that field keep their original request body.
Never regenerate the name or sign another payment after an uncertain outcome.
NodeOpsDeploymentError exposes record when a wallet deployment has acknowledged
IDs but polling fails (including build failure, timeout or a network error).
Persist error.record just like a successful result so the caller can query or
delete the existing resource. Errors before acknowledgement still require journal
and inventory reconciliation; no project ID is invented.
The runtime-variable and native-MPP fixes are local changes after deploy 0.6.1 and require another release. On 2026-09-10 a standalone live probe verified Dockerfile upload, BSC MPP payment and runtime settings.runEnvs injection. This does not verify secret-vault encryption, rotation or every wallet backend.
Native MPP uses an explicit transport so an unrelated x402 header cannot block the selected native challenge. The original response URL and body digest remain validated before approval. RPC_URL_BSC / RPC_URL_BASE / RPC_URL_ARBITRUM select HTTPS RPC endpoints; BSC defaults to https://bsc-dataseed.bnbchain.org. RPC calls time out after 15 seconds with no automatic retries, including broadcasts.
Hosting auto-renew can be implemented by a caller, but recharging an existing billing identity and reconciling its result remain unverified. The public Gateway contract currently couples credit purchase to deployment creation. Do not use repeated deploys or bare USDC transfers as a renewal implementation.
An account image project can be removed via project deletion. Deleting its external GHCR/Docker Hub image is a separate registry operation; the official CreateOS skill explicitly separates image registry management. Public contracts do not specify garbage collection of images built internally by CreateOS.
Interrupted deployment recovery
Account deployments preserve an identity-bound NodeOpsDeploymentError.record after a project ID is acknowledged, including upload errors, polling timeouts, failed builds and promotion errors. A record may have only projectId if deployment acknowledgement failed; use status/logs/destroy with that project before retrying. The default wait is 1800 seconds, configurable with timeoutSeconds; timing out does not cancel the remote deployment.
Wallet journals distinguish credential preparation from payment submission. A failure before any signed approval transaction, or a credential failure after a confirmed approval, releases the hosting budget reservation. Retrying the same deployment can recover interrupted preparation when the journal contains that evidence. An approval with an unknown broadcast result, an already submitted payment, and legacy reservations without preparation metadata remain blocked. Neither missing IDs nor an empty project list proves no payment occurred; do not delete the journal or change the name to bypass reconciliation. Verify the Gateway outcome and transaction receipts before resolving a legacy or ambiguous operation.
EIP-3009 asset domains
Base and Arbitrum wallet payments accept only the configured native USDC contracts. Native MPP passes chain-bound asset objects to mppx, with no shared authorization name/version fallback. Base uses mppx's USDC asset; mppx 0.9.1 has no Arbitrum entry, so the provider explicitly defines Arbitrum native USDC metadata (chain 42161, token address and decimals, USD Coin domain version 2). Missing or mismatched asset metadata fails before payment. x402 quote name/version must match the trusted asset, and the actual typed-data domain is checked before signing. This does not enable additional tokens or chains, or take domain metadata from untrusted quotes.
