Skip to Content
WaaP for AgentsFrameworksStandalone (Node.js / Python)

Standalone (Node.js / Python)

No framework required. If your language can run a subprocess and parse JSON, it can use WaaP. This guide covers the subprocess pattern, a JSON parsing helper, and complete working examples in TypeScript and Python.

Status: Production-ready.


Prerequisites

  • @human.tech/waap-cli installed globally
  • An active WaaP session (waap-cli signup or waap-cli login)
  • Node.js 18+ (for TypeScript) or Python 3.9+ (for Python)

The subprocess pattern

Every WaaP operation follows the same pattern:

  1. Shell out to waap-cli <command> --json
  2. Parse the JSON output from stdout
  3. Use the result in your agent logic

The --json flag is critical. Without it, the CLI outputs human-readable text with decorations that are harder to parse. With --json, you get clean, structured output.


Parsing waap-cli JSON output

The CLI emits newline-delimited JSON when using --json. The output may include multiple JSON lines (progress events, then the final result). Your parser should look for the line with "event": "result", falling back to the last parseable JSON line.

/** * Parse newline-delimited JSON from waap-cli --json output. * * waap-cli emits multiple JSON lines (event:submitted, event:result, etc.). * We prefer the line with event=result, falling back to the last parseable line. */ function parseWaapJson<T>(stdout: string): T { const lines = stdout .split(/\r?\n/) .filter((l) => l.trim().startsWith("{")); // First pass: look for the result event for (const line of lines) { try { const obj = JSON.parse(line) as { event?: string }; if (obj.event === "result") return obj as T; } catch {} } // Fallback: last parseable JSON line for (let i = lines.length - 1; i >= 0; i--) { try { return JSON.parse(lines[i]) as T; } catch {} } throw new Error(`Could not parse waap-cli JSON: ${stdout.slice(0, 200)}`); }

Full example: TypeScript

A complete standalone agent that logs in, checks its address, and signs a message:

import "dotenv/config"; import { execa } from "execa"; const TAG = "[my-agent]"; interface WhoamiResult { evmWalletAddress?: string; suiWalletAddress?: string; email?: string; } interface SignResult { signature: string; } function parseWaapJson<T>(stdout: string): T { const lines = stdout .split(/\r?\n/) .filter((l) => l.trim().startsWith("{")); for (const line of lines) { try { const obj = JSON.parse(line) as { event?: string }; if (obj.event === "result") return obj as T; } catch {} } for (let i = lines.length - 1; i >= 0; i--) { try { return JSON.parse(lines[i]) as T; } catch {} } throw new Error(`Could not parse waap-cli JSON: ${stdout.slice(0, 200)}`); } async function whoami(): Promise<WhoamiResult> { const { stdout } = await execa("waap-cli", ["whoami", "--json"]); return parseWaapJson<WhoamiResult>(stdout); } async function signMessage(hex: string): Promise<SignResult> { const { stdout } = await execa("waap-cli", [ "sign-message", "--message", hex, "--json", ]); return parseWaapJson<SignResult>(stdout); } async function sendTx( to: string, value: string, chain: string ): Promise<{ txHash: string }> { const { stdout } = await execa("waap-cli", [ "send-tx", "--to", to, "--value", value, "--chain", chain, "--json", ]); return parseWaapJson(stdout); } async function main(): Promise<void> { console.log(`${TAG} booting up`); const me = await whoami(); const address = me.evmWalletAddress ?? me.suiWalletAddress; if (!address) throw new Error("no wallet address -- run `waap-cli signup` first"); console.log(`${TAG} wallet: ${address}`); // Sign a message const message = Buffer.from("hello from my-agent", "utf8").toString("hex"); const sig = await signMessage(`0x${message}`); console.log(`${TAG} signed: ${sig.signature.slice(0, 20)}...`); // Send a transaction (uncomment when ready) // const tx = await sendTx("0xRecipient", "0.01", "evm:8453"); // console.log(`${TAG} tx: ${tx.txHash}`); } main().catch((err) => { console.error(`${TAG} fatal:`, err instanceof Error ? err.message : err); process.exit(1); });

Full example: Python

The same agent in Python:

"""my-agent -- minimal WaaP agent scaffold (Python).""" import json import subprocess import sys from dotenv import load_dotenv load_dotenv() TAG = "[my-agent]" def parse_waap_json(stdout: str) -> dict: """Parse newline-delimited JSON from waap-cli --json output.""" lines = [l.strip() for l in stdout.splitlines() if l.strip().startswith("{")] for line in lines: try: obj = json.loads(line) if obj.get("event") == "result": return obj except json.JSONDecodeError: continue for line in reversed(lines): try: return json.loads(line) except json.JSONDecodeError: continue raise RuntimeError(f"Could not parse waap-cli JSON: {stdout[:200]}") def waap_cli(*args: str) -> str: """Run a waap-cli command and return stdout.""" result = subprocess.run( ["waap-cli", *args], capture_output=True, text=True, check=True, ) return result.stdout def whoami() -> dict: stdout = waap_cli("whoami", "--json") return parse_waap_json(stdout) def sign_message(hex_msg: str) -> dict: stdout = waap_cli("sign-message", "--message", hex_msg, "--json") return parse_waap_json(stdout) def send_tx(to: str, value: str, chain: str) -> dict: stdout = waap_cli("send-tx", "--to", to, "--value", value, "--chain", chain, "--json") return parse_waap_json(stdout) def main() -> None: print(f"{TAG} booting up") me = whoami() address = me.get("evmWalletAddress") or me.get("suiWalletAddress") if not address: raise RuntimeError("no wallet address -- run `waap-cli signup` first") print(f"{TAG} wallet: {address}") # Sign a message message = "hello from my-agent".encode("utf-8").hex() sig = sign_message(f"0x{message}") print(f"{TAG} signed: {sig['signature'][:20]}...") # Send a transaction (uncomment when ready) # tx = send_tx("0xRecipient", "0.01", "evm:8453") # print(f"{TAG} tx: {tx['txHash']}") if __name__ == "__main__": try: main() except Exception as exc: print(f"{TAG} fatal: {exc}", file=sys.stderr) sys.exit(1)

Tips

  • Always pass --json to waap-cli. Without it, output includes decorations that are hard to parse reliably.
  • Set a timeout on subprocess calls. The CLI may block waiting for Telegram approval on high-risk transactions. A 5-minute timeout is reasonable.
  • Use waap-cli chain set at the start of your agent to set a default chain and RPC, avoiding the need to pass --chain and --rpc on every call.
  • For production deployments, see Running Agents 24/7.

Last updated on