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-cliinstalled globally- An active WaaP session (
waap-cli signuporwaap-cli login) - Node.js 18+ (for TypeScript) or Python 3.9+ (for Python)
The subprocess pattern
Every WaaP operation follows the same pattern:
- Shell out to
waap-cli <command> --json - Parse the JSON output from stdout
- 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.
TypeScript
/**
* 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
--jsontowaap-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 setat the start of your agent to set a default chain and RPC, avoiding the need to pass--chainand--rpcon every call. - For production deployments, see Running Agents 24/7.
Related
- CLI Commands — full
waap-clireference - Frameworks Overview — comparison of all supported frameworks
- Agent Patterns — error handling, retry logic, and risk levels