Teneo Agent SDK/CLI
Teneo Protocol CLI — discover and query 400+ AI agents on the Teneo Protocol network. Manage rooms, handle x402 USDC micropayments, and auto-generate encrypt...
teneoprotocoldev
@teneoprotocoldev
What This Skill Does
Command-line interface for discovering and querying 400+ AI agents on the Teneo Protocol network. Handles agent discovery, room management, x402 USDC micropayments, and auto-generated encrypted wallets for querying agents that provide real-time data like social media profiles, crypto prices, hotel search, and news.
Replaces manually integrating with individual agent APIs by providing a single CLI to discover, pay for, and query hundreds of specialized AI agents with automatic wallet and payment handling.
When to Use It
- Search for available Teneo agents and their pricing before querying
- Query a social media agent for a Twitter or LinkedIn profile
- Get real-time crypto prices or gas fees from a specialized agent
- Search hotel availability or Amazon products through an agent
- Create a private room and add specific agents for multi-agent workflows
- Handle on-chain transaction requests like token swaps from agents
Install
$ openclaw skills install @teneoprotocoldev/teneo-agent-sdkteneo-protocol-cli — CLI Gateway for the Teneo Agent Network
What This Skill Teaches You
This skill enables you to consume AI agents on the Teneo Protocol network. You will learn to:
- Discover every available agent, their commands, pricing, and capabilities
- Inspect any agent's full command interface, parameters, and billing model
- Query agents directly — free agents auto-confirm, paid agents auto-pay via x402 USDC micropayments
- Manage rooms — create private rooms, add/remove agents, control which agents you can query
- Handle payments — automatic USDC payments on Base, Peaq, Avalanche, or X Layer
- Sign transactions — handle on-chain transaction requests from agents (swaps, transfers) automatically
- Manage wallets — auto-generated encrypted wallets, balance checks, withdrawals to funder
When to use this skill: Whenever you need real-time data that a Teneo agent provides — social media profiles (X/Twitter, Instagram, TikTok, LinkedIn), hotel availability, crypto prices, gas fees, Amazon products, news, and more. Run node ~/teneo-skill/teneo.ts list-agents first to see everything available.
Prerequisites
- Node.js 18+ (required — the CLI is a Node.js tool)
- USDC on a supported chain (Base, Peaq, Avalanche, or X Layer) if querying paid agents
- No wallet setup needed — a new wallet is auto-generated on first use
Installation — Auto-Install If Missing
Before running any command, check if the CLI is set up. If not, install it automatically.
Step 1: Check if the CLI exists
test -f ~/teneo-skill/teneo.ts && echo "INSTALLED" || echo "NOT_INSTALLED"
Step 2: If NOT_INSTALLED, set up the CLI
mkdir -p ~/teneo-skill && cd ~/teneo-skill && npm init -y && NODE_OPTIONS="--max-old-space-size=512" npm install --prefer-offline @teneo-protocol/sdk@^3.0.0 commander@^12.1.0 dotenv@^16.0.0 viem@^2.21.0 tsx@^4.0.0
Then create the CLI script by writing the following content to ~/teneo-skill/teneo.ts:
#!/usr/bin/env npx tsx
/**
* Teneo Protocol CLI
* SECURITY: Auto-generates a new wallet on first use. Never asks for existing keys.
* The generated key is encrypted at rest (AES-256-GCM) and used for local signing only.
* Only cryptographic signatures are transmitted — never the key itself.
*/
import "dotenv/config";
import { TeneoSDK, SDKConfigBuilder } from "@teneo-protocol/sdk";
import { Command } from "commander";
import {
createWalletClient,
createPublicClient,
http,
defineChain,
type Chain,
} from "viem";
import { privateKeyToAccount, generatePrivateKey } from "viem/accounts";
import * as allChains from "viem/chains";
import * as nodeCrypto from "node:crypto";
import * as nodeFs from "node:fs";
import * as nodePath from "node:path";
import * as nodeOs from "node:os";
// ─── Config ──────────────────────────────────────────────────────────────────
const WS_URL =
process.env.TENEO_WS_URL ||
"wss://backend.developer.chatroom.teneo-protocol.ai/ws";
const PRIVATE_KEY = process.env.TENEO_PRIVATE_KEY;
const DEFAULT_ROOM = process.env.TENEO_DEFAULT_ROOM || "";
const DEFAULT_CHAIN = process.env.TENEO_DEFAULT_CHAIN || "base";
// Build chain ID lookup from all viem-supported chains
const CHAIN_BY_ID: Record<number, Chain> = {};
for (const key of Object.keys(allChains)) {
const c = (allChains as Record<string, unknown>)[key];
if (c && typeof c === "object" && "id" in c)
CHAIN_BY_ID[(c as Chain).id] = c as Chain;
}
function getChain(chainId: number): Chain {
if (CHAIN_BY_ID[chainId]) return CHAIN_BY_ID[chainId];
return defineChain({
id: chainId,
name: `Chain ${chainId}`,
nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 },
rpcUrls: {
default: { http: [`https://rpc.chain${chainId}.org`] },
},
});
}
// ─── Wallet Storage ──────────────────────────────────────────────────────────
const WALLET_DIR = nodePath.join(nodeOs.homedir(), ".teneo-wallet");
const WALLET_FILE = nodePath.join(WALLET_DIR, "wallet.json");
const SECRET_FILE = nodePath.join(WALLET_DIR, ".secret");
interface WalletData {
version: number;
address: string;
encryptedKey: string;
iv: string;
authTag: string;
createdAt: string;
funder: string | null;
}
function ensureWalletDir() {
if (!nodeFs.existsSync(WALLET_DIR)) {
nodeFs.mkdirSync(WALLET_DIR, { recursive: true, mode: 0o700 });
}
}
function getOrCreateMasterSecret(): Buffer {
ensureWalletDir();
if (nodeFs.existsSync(SECRET_FILE)) {
const hex = nodeFs.readFileSync(SECRET_FILE, "utf8").trim();
return Buffer.from(hex, "hex");
}
const secret = nodeCrypto.randomBytes(32);
nodeFs.writeFileSync(SECRET_FILE, secret.toString("hex"), { mode: 0o600 });
nodeFs.chmodSync(SECRET_FILE, 0o600);
return secret;
}
function encryptPK(
pk: string,
masterSecret: Buffer
): { encryptedKey: string; iv: string; authTag: string } {
const iv = nodeCrypto.randomBytes(12);
const cipher = nodeCrypto.createCipheriv("aes-256-gcm", masterSecret, iv);
const encrypted = Buffer.concat([
cipher.update(pk, "utf8"),
cipher.final(),
]);
return {
encryptedKey: encrypted.toString("base64"),
iv: iv.toString("base64"),
authTag: cipher.getAuthTag().toString("base64"),
};
}
function decryptPK(
encryptedKey: string,
iv: string,
authTag: string,
masterSecret: Buffer
): string {
const decipher = nodeCrypto.createDecipheriv(
"aes-256-gcm",
masterSecret,
Buffer.from(iv, "base64")
);
decipher.setAuthTag(Buffer.from(authTag, "base64"));
const decrypted = Buffer.concat([
decipher.update(Buffer.from(encryptedKey, "base64")),
decipher.final(),
]);
return decrypted.toString("utf8");
}
function loadWallet(): WalletData | null {
if (!nodeFs.existsSync(WALLET_FILE)) return null;
try {
return JSON.parse(nodeFs.readFileSync(WALLET_FILE, "utf8"));
} catch {
return null;
}
}
function saveWallet(data: WalletData) {
ensureWalletDir();
nodeFs.writeFileSync(WALLET_FILE, JSON.stringify(data, null, 2), {
mode: 0o600,
});
nodeFs.chmodSync(WALLET_FILE, 0o600);
}
function getWalletAddress(): string {
const wallet = loadWallet();
if (wallet) return wallet.address;
if (PRIVATE_KEY) {
const key = PRIVATE_KEY.startsWith("0x")
? PRIVATE_KEY
: `0x${PRIVATE_KEY}`;
return privateKeyToAccount(key as `0x${string}`).address;
}
return fail("No wallet found. Run any command to auto-generate one.");
}
// ─── USDC Chain Config ───────────────────────────────────────────────────────
const USDC_ADDRESSES: Record<string, `0x${string}`> = {
base: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
avax: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
peaq: "0xbbA60da06c2c5424f03f7434542280FCAd453d10",
xlayer: "0x74b7F16337b8972027F6196A17a631aC6dE26d22",
};
const WALLET_CHAIN_MAP: Record<string, Chain> = {
base: allChains.base,
avax: allChains.avalanche,
peaq: defineChain({
id: 3338,
name: "PEAQ",
nativeCurrency: { name: "PEAQ", symbol: "PEAQ", decimals: 18 },
rpcUrls: {
default: { http: ["https://peaq.api.onfinality.io/public"] },
},
}),
xlayer: defineChain({
id: 196,
name: "XLayer",
nativeCurrency: { name: "OKB", symbol: "OKB", decimals: 18 },
rpcUrls: { default: { http: ["https://rpc.xlayer.tech"] } },
}),
};
const ERC20_BALANCE_ABI = [
{
inputs: [{ name: "account", type: "address" }],
name: "balanceOf",
outputs: [{ name: "", type: "uint256" }],
stateMutability: "view",
type: "function",
},
] as const;
const ERC20_TRANSFER_ABI = [
{
inputs: [
{ name: "to", type: "address" },
{ name: "amount", type: "uint256" },
],
name: "transfer",
outputs: [{ name: "", type: "bool" }],
stateMutability: "nonpayable",
type: "function",
},
] as const;
const ERC20_TRANSFER_EVENT = {
type: "event",
name: "Transfer",
inputs: [
{ name: "from", type: "address", indexed: true },
{ name: "to", type: "address", indexed: true },
{ name: "value", type: "uint256", indexed: false },
],
} as const;
async function detectFunder(
walletAddress: string
): Promise<{ funder: string; chain: string } | null> {
for (const chainName of ["base", "avax", "peaq", "xlayer"]) {
const chain = WALLET_CHAIN_MAP[chainName];
const usdcAddr = USDC_ADDRESSES[chainName];
if (!chain || !usdcAddr) continue;
try {
const client = createPublicClient({ chain, transport: http() });
const logs = await client.getLogs({
address: usdcAddr,
event: ERC20_TRANSFER_EVENT,
args: { to: walletAddress as `0x${string}` },
fromBlock: 0n,
toBlock: "latest",
});
if (logs.length > 0) {
logs.sort(
(a, b) =>
Number((a.blockNumber ?? 0n) - (b.blockNumber ?? 0n))
);
const from = logs[0].args.from;
if (from) return { funder: from, chain: chainName };
}
} catch {
// Skip chain on error
}
}
return null;
}
// ─── Output Helpers ──────────────────────────────────────────────────────────
const JSON_FLAG = process.argv.includes("--json");
function out(data: unknown) {
console.log(JSON.stringify(data, null, 2));
}
function fail(msg: string): never {
if (JSON_FLAG) console.error(JSON.stringify({ error: msg }));
else console.error(`Error: ${msg}`);
process.exit(1);
}
function pad(str: string, len: number): string {
return str.length >= len
? str.substring(0, len - 1) + " "
: str + " ".repeat(len - str.length);
}
function padCenter(str: string, len: number): string {
if (str.length >= len) return str.substring(0, len);
const left = Math.floor((len - str.length) / 2);
const right = len - str.length - left;
return " ".repeat(left) + str + " ".repeat(right);
}
function parseCommands(agent: any): any[] {
if (!agent.commands) return [];
if (Array.isArray(agent.commands)) return agent.commands;
try {
return JSON.parse(agent.commands);
} catch {
return [];
}
}
function formatPrice(cmd: any): string {
if (!cmd.pricePerUnit || cmd.pricePerUnit === 0) return "FREE";
const unit = cmd.taskUnit === "per-item" ? "/item" : "/query";
return `${cmd.pricePerUnit} USDC${unit}`;
}
function requireKey(): string {
// Tier 1: Environment variable
if (PRIVATE_KEY) return PRIVATE_KEY;
// Tier 2: Encrypted wallet file
const wallet = loadWallet();
if (wallet) {
const secret = getOrCreateMasterSecret();
return decryptPK(wallet.encryptedKey, wallet.iv, wallet.authTag, secret);
}
// Tier 3: Auto-generate new wallet
const masterSecret = getOrCreateMasterSecret();
const newKey = generatePrivateKey();
const account = privateKeyToAccount(newKey);
const encrypted = encryptPK(newKey, masterSecret);
saveWallet({
version: 1,
address: account.address,
encryptedKey: encrypted.encryptedKey,
iv: encrypted.iv,
authTag: encrypted.authTag,
createdAt: new Date().toISOString(),
funder: null,
});
console.error(
JSON.stringify({
info: "Wallet auto-generated",
address: account.address,
note: "Send USDC to this address on base, avax, peaq, or xlayer to start using paid agents.",
})
);
return newKey;
}
function resolveRoom(opt?: string): string {
const room = opt || DEFAULT_ROOM;
if (!room)
fail("Room ID required. Pass --room <id> or set TENEO_DEFAULT_ROOM.");
return room;
}
// ─── SDK Lifecycle ───────────────────────────────────────────────────────────
const MAX_RETRIES = 3;
const RETRY_DELAY = 5000;
const SHORT_TIMEOUT = 20000;
async function sleep(ms: number) {
return new Promise((r) => setTimeout(r, ms));
}
interface SDKOpts {
autoJoinRoom?: string;
payments?: boolean;
kickAgent?: string;
}
function buildSDK(key: string, opts?: SDKOpts): TeneoSDK {
const builder = new SDKConfigBuilder()
.withWebSocketUrl(WS_URL)
.withAuthentication(key)
.withReconnection({ enabled: true, delay: 3000, maxAttempts: 5 })
.withCache(true, 600000, 500);
if (opts?.autoJoinRoom && !opts.autoJoinRoom.startsWith("private_"))
builder.withAutoJoinPublicRooms([opts.autoJoinRoom]);
if (opts?.payments)
builder.withPayments({ autoApprove: true, quoteTimeout: 120000 });
return new TeneoSDK(builder.build());
}
function registerTxSigner(sdk: TeneoSDK) {
const key = requireKey();
const account = privateKeyToAccount(
(key.startsWith("0x") ? key : `0x${key}`) as `0x${string}`
);
sdk.on("wallet:tx_requested", async (data: any) => {
const { taskId, tx, agentName, description } = data;
console.error(
JSON.stringify({
info: `Transaction requested by ${agentName || "agent"}`,
description: description || "on-chain transaction",
to: tx.to,
value: tx.value,
chainId: tx.chainId,
})
);
try {
const chain = getChain(tx.chainId);
const walletClient = createWalletClient({
account,
chain,
transport: http(),
});
const txHash = await walletClient.sendTransaction({
to: tx.to,
value: tx.value ? BigInt(tx.value) : 0n,
data: tx.data || undefined,
chain,
});
console.error(
JSON.stringify({ info: "Transaction sent", txHash, chainId: tx.chainId })
);
await (sdk as any).sendTxResult(taskId, "confirmed", txHash);
} catch (err: any) {
console.error(
JSON.stringify({ error: `Transaction failed: ${err.message}` })
);
await (sdk as any).sendTxResult(taskId, "failed", undefined, err.message);
}
});
}
async function kickAgent(sdk: TeneoSDK, roomId: string, agentId: string) {
try {
console.error(
JSON.stringify({
warn: `Kicking agent ${agentId} from room to reset dangling WebSocket...`,
})
);
await sdk.removeAgentFromRoom(roomId, agentId);
await sleep(2000);
await sdk.addAgentToRoom(roomId, agentId);
await sleep(3000);
console.error(
JSON.stringify({ info: `Agent ${agentId} re-added to room ${roomId}.` })
);
} catch (e: any) {
console.error(
JSON.stringify({ warn: `Kick failed (non-fatal): ${e.message}` })
);
}
}
async function withSDK<T>(
fn: (sdk: TeneoSDK, attempt: number) => Promise<T>,
opts?: SDKOpts
): Promise<T> {
let lastErr: Error | undefined;
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
let sdk: TeneoSDK | null = null;
try {
const key = requireKey();
sdk = buildSDK(key, opts);
await sdk.connect();
registerTxSigner(sdk);
return await fn(sdk, attempt);
} catch (err: any) {
lastErr = err;
const isTimeout =
err.message &&
(err.message.includes("timeout") || err.message.includes("Timeout"));
if (isTimeout && opts?.kickAgent && opts?.autoJoinRoom && sdk) {
try {
await kickAgent(sdk, opts.autoJoinRoom, opts.kickAgent);
} catch {
// non-fatal
}
}
if (sdk)
try {
sdk.disconnect();
} catch {
// ignore
}
sdk = null;
if (attempt < MAX_RETRIES) {
console.error(
JSON.stringify({
warn: `Attempt ${attempt}/${MAX_RETRIES} failed: ${err.message}. Retrying in ${RETRY_DELAY / 1000}s...`,
})
);
await sleep(RETRY_DELAY);
}
} finally {
if (sdk)
try {
sdk.disconnect();
} catch {
// ignore
}
}
}
return fail(
`All ${MAX_RETRIES} attempts failed. Last error: ${lastErr?.message || lastErr}`
);
}
// ─── CLI ─────────────────────────────────────────────────────────────────────
const program = new Command();
program
.name("teneo-cli")
.version("2.0.0")
.description("Teneo Protocol CLI. Private keys are NEVER transmitted.")
.option("--json", "Machine-readable JSON output");
// ─── Health ──────────────────────────────────────────────────────────────────
program
.command("health")
.description("Check connection health")
.action(async () => {
await withSDK(async (sdk) => {
const h = (sdk as any).getHealth();
out({
status: h.status,
connection: h.connection,
agents: h.agents,
rooms: h.rooms,
});
});
});
// ─── Agent Fetching (REST API — no SDK connection needed for discovery) ──────
const BACKEND_URL = WS_URL.replace("wss://", "https://").replace("ws://", "http://").replace("/ws", "");
async function fetchAgentsREST(): Promise<any[]> {
const agents: any[] = [];
let offset = 0;
const limit = 50;
while (true) {
const res = await fetch(`${BACKEND_URL}/api/public/agents?limit=${limit}&offset=${offset}`);
if (!res.ok) throw new Error(`API error: ${res.status} ${res.statusText}`);
const data = await res.json();
const batch = (data as any).agents || [];
agents.push(...batch);
if (batch.length < limit) break;
offset += limit;
}
return agents;
}
function normalizeAgent(a: any) {
const id = a.id || a.agent_id;
const name = a.name || a.agent_name;
const cmds = parseCommands(a);
return {
agent_id: id,
agent_name: name,
description: a.description || "",
status: a.status,
is_online: a.status === "online" || a.is_online,
type: a.type || a.agent_type || "command",
commands: cmds.map((c: any) => ({
trigger: c.trigger,
description: c.description,
usage: `@${id} ${c.trigger}${c.argument ? " " + c.argument : ""}`,
price: c.pricePerUnit || 0,
task_unit: c.taskUnit || "per-query",
is_free: !c.pricePerUnit || c.pricePerUnit === 0,
parameters: c.parameters || [],
argument: c.argument,
pricePerUnit: c.pricePerUnit,
taskUnit: c.taskUnit,
})),
};
}
// ─── Agent Discovery ─────────────────────────────────────────────────────────
program
.command("discover")
.description("Full JSON manifest of all agents, commands, and pricing — designed for AI agent consumption")
.action(async () => {
{
const rawAgents = await fetchAgentsREST();
const normalized = rawAgents.map(normalizeAgent);
const onlineAgents = normalized.filter((a) => a.is_online);
const commandIndex: any[] = [];
for (const agent of onlineAgents) {
for (const cmd of agent.commands) {
commandIndex.push({
usage: cmd.usage,
agent_id: agent.agent_id,
agent_name: agent.agent_name,
trigger: cmd.trigger,
description: cmd.description,
price: cmd.price,
is_free: cmd.is_free,
task_unit: cmd.task_unit,
parameters: cmd.parameters,
});
}
}
out({
_meta: {
generated_at: new Date().toISOString(),
websocket: WS_URL,
total_agents: normalized.length,
online_agents: onlineAgents.length,
total_commands: commandIndex.length,
note: "Use 'command' to execute. Format: teneo-cli command <agent-id> '<trigger> <args>'",
},
how_to_query: {
direct_command: "teneo-cli command <agent-id> '<trigger> <args>' --room <roomId>",
example: "teneo-cli command x-agent-enterprise-v2 'search teneo protocol 10' --room <roomId>",
},
agents: normalized,
online_agents: onlineAgents,
command_index: commandIndex,
});
}
});
program
.command("list-agents")
.alias("agents")
.description("List all agents on the Teneo network")
.option("--online", "Show only online agents")
.option("--free", "Show only agents with free commands")
.option("--search <keyword>", "Search by name/description")
.action(async (opts: any) => {
let agents = (await fetchAgentsREST()).map(normalizeAgent);
if (opts.search) {
const term = opts.search.toLowerCase();
agents = agents.filter(
(a) =>
a.agent_id.toLowerCase().includes(term) ||
a.agent_name.toLowerCase().includes(term) ||
a.description.toLowerCase().includes(term)
);
}
if (opts.online) agents = agents.filter((a) => a.is_online);
if (opts.free)
agents = agents.filter((a) =>
a.commands.some((c: any) => c.is_free)
);
if (JSON_FLAG) {
out({ count: agents.length, agents });
return;
}
if (agents.length === 0) {
console.log("No agents found matching your criteria.");
return;
}
console.log("");
const col = { id: 28, name: 28, status: 8, cmds: 6, price: 14 };
console.log(
pad("AGENT ID", col.id) +
pad("NAME", col.name) +
pad("STATUS", col.status) +
pad("CMDS", col.cmds) +
pad("PRICE RANGE", col.price)
);
console.log(
"-".repeat(col.id + col.name + col.status + col.cmds + col.price)
);
for (const agent of agents) {
const prices = agent.commands.map((c: any) => c.price);
const minP = Math.min(...(prices.length ? prices : [0]));
const maxP = Math.max(...(prices.length ? prices : [0]));
let priceRange: string;
if (maxP === 0) priceRange = "FREE";
else if (minP === 0) priceRange = `FREE-${maxP}`;
else if (minP === maxP) priceRange = `${minP}`;
else priceRange = `${minP}-${maxP}`;
const status = agent.is_online ? "ON " : "OFF ";
console.log(
pad(agent.agent_id, col.id) +
pad(agent.agent_name, col.name) +
status +
" " +
pad(String(agent.commands.length), col.cmds) +
priceRange
);
}
console.log(`\n${agents.length} agent(s) found.`);
});
program
.command("info")
.alias("agent-details")
.description("Show agent details, commands, and pricing")
.argument("<agentId>")
.action(async (agentId: string) => {
const rawAgents = await fetchAgentsREST();
const allNormalized = rawAgents.map(normalizeAgent);
const agent = allNormalized.find((a) => a.agent_id === agentId);
if (!agent) {
const similar = allNormalized
.filter(
(a) =>
a.agent_id.includes(agentId) ||
a.agent_name.toLowerCase().includes(agentId.toLowerCase())
)
.slice(0, 5);
if (JSON_FLAG) {
out({
error: "not_found",
agent_id: agentId,
suggestions: similar.map((a) => a.agent_id),
});
} else {
console.error(`Agent "${agentId}" not found.`);
if (similar.length > 0) {
console.log("\nDid you mean:");
similar.forEach((a) =>
console.log(` ${a.agent_id} (${a.agent_name})`)
);
}
}
process.exit(1);
}
if (JSON_FLAG) {
out(agent);
return;
}
// Pretty output
console.log(
"\n========================================================"
);
console.log(` ${padCenter(agent.agent_name, 54)}`);
console.log(
"========================================================"
);
console.log(` ID: ${agent.agent_id}`);
console.log(` Type: ${agent.type}`);
console.log(` Status: ${agent.is_online ? "ONLINE" : "OFFLINE"}`);
if (agent.description)
console.log(` Description: ${agent.description}`);
if (agent.commands.length > 0) {
console.log(`\n COMMANDS (${agent.commands.length}):`);
console.log(" " + "-".repeat(60));
for (const cmd of agent.commands) {
const price = formatPrice(cmd);
console.log(`\n ${cmd.usage}`);
if (cmd.description) console.log(` ${cmd.description}`);
console.log(` Price: ${price}`);
if (cmd.parameters && cmd.parameters.length > 0) {
console.log(" Parameters:");
for (const p of cmd.parameters) {
const req = p.required ? "required" : "optional";
console.log(
` ${p.name} (${p.type}, ${req}) - ${p.description}`
);
}
}
}
}
console.log(
`\n QUERY THIS AGENT:`
);
console.log(
` teneo-cli command ${agent.agent_id} "${agent.commands[0]?.trigger || "help"}" --room <roomId>\n`
);
});
// ─── Agent Commands ──────────────────────────────────────────────────────────
program
.command("command")
.description(
"Direct command to agent (use internal agent ID, not display name)"
)
.argument("<agent>", "Internal agent ID (e.g. x-agent-enterprise-v2)")
.argument("<cmd>", "Command string: {trigger} {argument}")
.option("--room <roomId>")
.option("--timeout <ms>", "Response timeout", "120000")
.option("--chain <chain>")
.action(async (agent: string, cmd: string, opts: any) => {
const room = resolveRoom(opts.room);
await withSDK(
async (sdk, attempt) => {
const r = await (sdk as any).sendDirectCommand(
{
agent,
command: cmd,
room,
...(opts.chain ? { network: opts.chain } : {}),
},
true
);
if (!r || (!r.humanized && !r.raw)) {
await sleep(4000);
out({
status: "sent",
note: "Command sent with payment. Response may arrive asynchronously.",
});
} else {
out({ humanized: r.humanized, raw: r.raw, metadata: r.metadata });
}
},
{ autoJoinRoom: room, payments: true, kickAgent: agent }
);
});
program
.command("quote")
.description("Request price quote (no execution)")
.argument("<message>")
.option("--room <roomId>")
.option("--chain <chain>")
.action(async (message: string, opts: any) => {
const room = resolveRoom(opts.room);
await withSDK(
async (sdk) => {
const q = await (sdk as any).requestQuote(
message,
room,
opts.chain || DEFAULT_CHAIN
);
out({
taskId: q.taskId,
agentId: q.agentId,
agentName: q.agentName,
command: q.command,
pricing: q.pricing,
expiresAt: q.expiresAt,
network: opts.chain || DEFAULT_CHAIN,
});
},
{ autoJoinRoom: room, payments: true }
);
});
program
.command("confirm")
.description("Confirm quoted task with payment")
.argument("<taskId>")
.option("--room <roomId>")
.option("--timeout <ms>", "Response timeout", "120000")
.action(async (taskId: string, opts: any) => {
const room = resolveRoom(opts.room);
await withSDK(
async (sdk) => {
const r = await (sdk as any).confirmQuote(taskId, {
waitForResponse: true,
timeout: parseInt(opts.timeout),
});
if (r && (r.humanized || r.raw)) {
out({ humanized: r.humanized, raw: r.raw, metadata: r.metadata });
} else {
await sleep(4000);
out({
status: "confirmed",
note: "Payment sent. Agent response may arrive asynchronously.",
});
}
},
{ autoJoinRoom: room, payments: true }
);
});
// ─── Room Management ─────────────────────────────────────────────────────────
program
.command("rooms")
.description("List all rooms")
.action(async () => {
await withSDK(async (sdk) => {
const rooms = await (sdk as any).listRooms();
out({
count: rooms.length,
rooms: rooms.map((r: any) => ({
id: r.id,
name: r.name,
is_public: r.is_public,
is_owner: r.is_owner,
description: r.description,
})),
});
});
});
program
.command("room-agents")
.description("List agents in room")
.argument("<roomId>")
.action(async (roomId: string) => {
await withSDK(async (sdk) => {
const agents = await sdk.listRoomAgents(roomId);
out({
roomId,
count: agents.length,
agents: agents.map((a: any) => ({
id: a.agent_id,
name: a.agent_name,
status: a.status,
})),
});
});
});
program
.command("create-room")
.description("Create room")
.argument("<name>")
.option("--description <desc>")
.option("--public", "Make room public", false)
.action(async (name: string, opts: any) => {
await withSDK(async (sdk) => {
const r = await sdk.createRoom({
name,
description: opts.description,
isPublic: opts.public,
});
out({
status: "created",
room: { id: r.id, name: r.name, is_public: (r as any).is_public },
});
});
});
program
.command("update-room")
.description("Update room")
.argument("<roomId>")
.option("--name <name>")
.option("--description <desc>")
.action(async (roomId: string, opts: any) => {
await withSDK(async (sdk) => {
const updates: Record<string, string> = {};
if (opts.name) updates.name = opts.name;
if (opts.description) updates.description = opts.description;
out({
status: "updated",
room: await (sdk as any).updateRoom(roomId, updates),
});
});
});
program
.command("delete-room")
.description("Delete room")
.argument("<roomId>")
.action(async (roomId: string) => {
await withSDK(async (sdk) => {
await (sdk as any).deleteRoom(roomId);
out({ status: "deleted", roomId });
});
});
program
.command("add-agent")
.description("Add agent to room")
.argument("<roomId>")
.argument("<agentId>")
.action(async (roomId: string, agentId: string) => {
await withSDK(async (sdk) => {
await sdk.addAgentToRoom(roomId, agentId);
out({ status: "added", roomId, agentId });
});
});
program
.command("remove-agent")
.description("Remove agent from room")
.argument("<roomId>")
.argument("<agentId>")
.action(async (roomId: string, agentId: string) => {
await withSDK(async (sdk) => {
await sdk.removeAgentFromRoom(roomId, agentId);
out({ status: "removed", roomId, agentId });
});
});
program
.command("owned-rooms")
.description("List rooms you own")
.action(async () => {
await withSDK(async (sdk) => {
const rooms = (sdk as any).getOwnedRooms();
out({
count: rooms.length,
rooms: rooms.map((r: any) => ({
id: r.id,
name: r.name,
is_public: r.is_public,
})),
});
});
});
program
.command("shared-rooms")
.description("List rooms shared with you")
.action(async () => {
await withSDK(async (sdk) => {
const rooms = (sdk as any).getSharedRooms();
out({
count: rooms.length,
rooms: rooms.map((r: any) => ({
id: r.id,
name: r.name,
is_public: r.is_public,
})),
});
});
});
program
.command("subscribe")
.description("Subscribe to public room")
.argument("<roomId>")
.action(async (roomId: string) => {
await withSDK(async (sdk) => {
await (sdk as any).subscribeToPublicRoom(roomId);
out({ status: "subscribed", roomId });
});
});
program
.command("unsubscribe")
.description("Unsubscribe from room")
.argument("<roomId>")
.action(async (roomId: string) => {
await withSDK(async (sdk) => {
await (sdk as any).unsubscribeFromPublicRoom(roomId);
out({ status: "unsubscribed", roomId });
});
});
// ─── Wallet Management ───────────────────────────────────────────────────────
program
.command("wallet-init")
.description("Generate a new wallet (auto-called on first use)")
.action(async () => {
const existing = loadWallet();
if (existing) {
out({
status: "exists",
address: existing.address,
createdAt: existing.createdAt,
});
return;
}
if (PRIVATE_KEY) {
out({
status: "env_var_set",
note: "Private key found in environment. No wallet file needed.",
});
return;
}
requireKey();
const wallet = loadWallet();
out({
status: "created",
address: wallet!.address,
createdAt: wallet!.createdAt,
note: "Send USDC to this address on base, avax, peaq, or xlayer to start using paid agents.",
});
});
program
.command("wallet-address")
.description("Show wallet public address")
.action(async () => {
const wallet = loadWallet();
if (wallet) {
out({ address: wallet.address, createdAt: wallet.createdAt });
} else if (PRIVATE_KEY) {
const key = PRIVATE_KEY.startsWith("0x")
? PRIVATE_KEY
: `0x${PRIVATE_KEY}`;
out({
address: privateKeyToAccount(key as `0x${string}`).address,
source: "environment_variable",
});
} else {
requireKey();
const w = loadWallet();
out({ address: w!.address, createdAt: w!.createdAt });
}
});
program
.command("wallet-export-key")
.description("Export private key (DANGEROUS)")
.action(async () => {
const wallet = loadWallet();
if (!wallet) {
fail(
PRIVATE_KEY
? "No wallet file found. Key is in an environment variable."
: "No wallet found. Run wallet-init first."
);
}
const secret = getOrCreateMasterSecret();
const key = decryptPK(
wallet.encryptedKey,
wallet.iv,
wallet.authTag,
secret
);
console.error(
JSON.stringify({
warning:
"PRIVATE KEY EXPORTED. Never share this. Never paste into websites. Never commit to git.",
})
);
out({ address: wallet.address, privateKey: key });
});
program
.command("wallet-balance")
.description("Check USDC balance on supported chains")
.option("--chain <chain>", "Specific chain (base|avax|peaq|xlayer)")
.action(async (opts: any) => {
const address = getWalletAddress();
const chainsToCheck = opts.chain
? [opts.chain]
: ["base", "avax", "peaq", "xlayer"];
const results: Record<string, any> = {};
for (const chainName of chainsToCheck) {
const chain = WALLET_CHAIN_MAP[chainName];
const usdcAddr = USDC_ADDRESSES[chainName];
if (!chain || !usdcAddr) {
results[chainName] = { error: `Unknown chain: ${chainName}` };
continue;
}
try {
const client = createPublicClient({ chain, transport: http() });
const balance = await client.readContract({
address: usdcAddr,
abi: ERC20_BALANCE_ABI,
functionName: "balanceOf",
args: [address as `0x${string}`],
});
results[chainName] = {
usdc: (Number(balance) / 1e6).toFixed(6),
raw: balance.toString(),
};
} catch (err: any) {
results[chainName] = { error: err.message };
}
}
out({ address, balances: results });
});
program
.command("wallet-withdraw")
.description("Withdraw USDC back to original funder ONLY")
.argument("<amount>", "Amount in USDC")
.argument("<chain>", "Chain (base|avax|peaq|xlayer)")
.action(async (amountStr: string, chainName: string) => {
const wallet = loadWallet();
if (!wallet) fail("No wallet file found.");
let destination = wallet.funder;
if (!destination) {
console.error(
JSON.stringify({
info: "No funder locked yet. Scanning chains for incoming USDC transfers...",
})
);
const result = await detectFunder(wallet.address);
if (!result)
fail("No incoming USDC transfers found. Cannot determine funder address.");
wallet.funder = result.funder;
saveWallet(wallet);
destination = result.funder;
console.error(
JSON.stringify({
info: `Funder auto-detected and locked: ${destination} (${result.chain})`,
})
);
}
const amount = parseFloat(amountStr);
if (isNaN(amount) || amount <= 0) fail("Invalid amount.");
const rawAmount = BigInt(Math.round(amount * 1e6));
const chain = WALLET_CHAIN_MAP[chainName];
const usdcAddr = USDC_ADDRESSES[chainName];
if (!chain || !usdcAddr) fail(`Unknown chain: ${chainName}`);
const secret = getOrCreateMasterSecret();
const pk = decryptPK(
wallet.encryptedKey,
wallet.iv,
wallet.authTag,
secret
);
const account = privateKeyToAccount(
(pk.startsWith("0x") ? pk : `0x${pk}`) as `0x${string}`
);
const wc = createWalletClient({ account, chain, transport: http() });
const txHash = await wc.writeContract({
address: usdcAddr,
abi: ERC20_TRANSFER_ABI,
functionName: "transfer",
args: [destination as `0x${string}`, rawAmount],
});
out({
status: "sent",
txHash,
amount: amountStr,
chain: chainName,
destination,
note: "Funds returned to original funder address.",
});
});
program
.command("wallet-detect-funder")
.description(
"Detect and lock the first address that sent USDC to this wallet"
)
.action(async () => {
const wallet = loadWallet();
if (!wallet) fail("No wallet file found. Run wallet-init first.");
if (wallet.funder) {
out({
funder: wallet.funder,
locked: true,
note: "Funder already locked. Cannot be changed.",
});
return;
}
console.error(
JSON.stringify({
info: "Scanning all chains for incoming USDC transfers...",
})
);
const result = await detectFunder(wallet.address);
if (!result) {
out({
funder: null,
note: "No incoming USDC transfers found yet. Send USDC to this wallet first.",
});
return;
}
wallet.funder = result.funder;
saveWallet(wallet);
out({
funder: result.funder,
chain: result.chain,
locked: true,
note: "Funder detected and permanently locked. Withdrawals will only go to this address.",
});
});
// ─── Metadata Export ─────────────────────────────────────────────────────────
if (process.argv.includes("--dump-commands")) {
const commands = program.commands.map((cmd) => ({
name: cmd.name(),
description: cmd.description(),
arguments: cmd.registeredArguments.map((a) => ({
name: a.name(),
description: a.description,
required: a.required,
})),
options: cmd.options.map((o) => ({
flags: o.flags,
description: o.description,
defaultValue: o.defaultValue,
})),
}));
console.log(
JSON.stringify(
{
name: program.name(),
version: program.version(),
description: program.description(),
commands,
},
null,
2,
),
);
process.exit(0);
}
// ─── Parse ───────────────────────────────────────────────────────────────────
program.parseAsync(process.argv).catch((err) => fail(err.message || String(err)));
<!-- /CLI_CODE -->
Step 3: Verify installation
node ~/teneo-skill/teneo.ts health
How to run all commands
All commands in this skill are run as:
node ~/teneo-skill/teneo.ts <command> [options]
Authentication
The CLI creates and manages its own wallet automatically. Three tiers of key resolution (in priority order):
1. Environment variable (highest priority)
export TENEO_PRIVATE_KEY=4a8b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a2e
node ~/teneo-skill/teneo.ts command "hotel-finder" "search vienna" --room <roomId>
2. Encrypted wallet file (auto-generated)
On first use, the CLI auto-generates a new wallet and stores it encrypted (AES-256-GCM) at ~/.teneo-wallet/wallet.json. No setup needed — just run any command.
The auto-generated key serves two purposes:
- Authentication — signs the WebSocket handshake to prove identity on Teneo
- Payment — signs x402 USDC transactions to pay agents
3. .env file (auto-loaded from current directory)
echo "TENEO_PRIVATE_KEY=4a8b1c2d3e4f..." > .env
node ~/teneo-skill/teneo.ts command "hotel-finder" "search vienna" --room <roomId>
Wallet Security
- Private key encrypted at rest with AES-256-GCM
- Master secret and wallet data in separate files (leaking one is useless without the other)
- Both files have
0600permissions (owner-only read/write) - Key NEVER logged, transmitted, or included in any API call — only cryptographic signatures are sent
- Withdrawals can only go to the first address that funded the wallet (auto-detected, permanently locked)
Funding the wallet
- Run any command — a wallet is auto-generated and the address is printed
- Send USDC to that address on Base, Peaq, Avalanche, or X Layer
- The CLI detects the funder automatically and locks withdrawals to that address only
Network connections
- Connects to Teneo Protocol backend at
wss://backend.developer.chatroom.teneo-protocol.ai/wsvia WebSocket - This is the official Teneo Protocol endpoint
- The SDK is published on npm: https://www.npmjs.com/package/@teneo-protocol/sdk
IMPORTANT: Always Show Status Updates
Teneo commands can take 10-30+ seconds. Never leave the user staring at a blank screen. Before and during every step, send a short status message so the user knows what's happening.
Example flow when a user asks "search @elonmusk on X":
Checking which agents are in the room... X Platform Agent is in the room. Requesting price quote for the search... Quote received: 0.05 USDC. Confirming payment... Payment confirmed. Waiting for agent response... Here are the results:
Rules:
- Before every CLI command, tell the user what you're about to do in plain language
- After each step completes, confirm it before moving to the next step
- If something takes more than a few seconds, send a "still waiting..." or "processing..." update
- On errors, explain what went wrong and what you'll try next — don't just silently retry
Never run multiple commands in silence. Each step should have a visible status update.
IMPORTANT: Agent Discovery & Room Limits
Finding Agents
Teneo has many agents available across the entire network. Use these commands to discover them:
discover→ full JSON manifest of ALL agents with commands, pricing, and capabilities — designed for AI agent consumptionlist-agents→ shows all agents with their IDs, commands, capabilities, and pricing. Supports--online,--free,--searchfilters.info <agentId>→ full details for one agent (commands with exact syntax + pricing)room-agents <roomId>→ shows agents currently IN your room
IMPORTANT: Agent IDs vs Display Names. Agents have an internal ID (e.g. x-agent-enterprise-v2) and a display name (e.g. "X Platform Agent"). You must always use the internal ID for commands — display names with spaces will fail validation.
Agent "Online" does not mean Reachable
An agent can show "status": "online" in info but still be disconnected in your room. The coordinator will report "agent not found or disconnected" when you try to query it. This means:
- Always test an agent with a cheap command first before relying on it
- If an agent is disconnected, look for alternative agents that serve the same purpose
- Multiple agents often serve overlapping purposes — know your fallbacks
Pre-Query Checklist
Before every agent query, follow this checklist:
- Get agent commands — run
info <agentId>to see exact command syntax and pricing. Never guess commands. - Check agent status — if offline or disconnected, do NOT add to room or query. Find an alternative.
- Check room capacity — run
room-agents <roomId>to see current agents (max 5). If full, remove one or create a new room. - Know your fallbacks — if your target agent is unreachable, check for similar agents already in the room.
- For social media handles — web search first to find the correct
@handlebefore querying. Wrong handles waste money.
Room Rules
Teneo organizes agents into rooms. You MUST understand these rules:
- Maximum 5 agents per room. A room can hold at most 5 agents at a time.
- You can only query agents that are in your room. If an agent is not in the room, commands to it will fail.
- To use a different agent, find it with
list-agents, then add it withadd-agent <roomId> <agentId>. - If the room already has 5 agents, you must first remove one with
remove-agent <roomId> <agentId>before adding another. - Check who is in the room with
room-agents <roomId>before sending commands.
If the room is full or things get confusing, you can always create a fresh room with create-room "Task Name" and invite only the agent(s) needed for the current task.
Always communicate this to the user. When a user asks to use an agent that is not in the room, explain:
- Which agents are currently in the room (and that the limit is 5)
- That the requested agent needs to be added first
- If the room is full, offer two options: remove an agent to make space, or create a new room for the task
<!-- COMMAND_REFERENCE -->
Command Reference
24 commands across agent discovery, execution, room management, and wallet operations. All commands return JSON to stdout.
AGENT DISCOVERY
node ~/teneo-skill/teneo.ts health Check connection health
node ~/teneo-skill/teneo.ts discover Full JSON manifest of all agents, commands, and pricing — designed for AI agent consumption
node ~/teneo-skill/teneo.ts list-agents List all agents on the Teneo network
node ~/teneo-skill/teneo.ts info <agentId> Show agent details, commands, and pricing
AGENT COMMANDS
node ~/teneo-skill/teneo.ts command <agent> <cmd> Direct command to agent (use internal agent ID, not display name)
node ~/teneo-skill/teneo.ts quote <message> Request price quote (no execution)
node ~/teneo-skill/teneo.ts confirm <taskId> Confirm quoted task with payment
ROOM MANAGEMENT
node ~/teneo-skill/teneo.ts rooms List all rooms
node ~/teneo-skill/teneo.ts room-agents <roomId> List agents in room
node ~/teneo-skill/teneo.ts create-room <name> Create room
node ~/teneo-skill/teneo.ts update-room <roomId> Update room
node ~/teneo-skill/teneo.ts delete-room <roomId> Delete room
node ~/teneo-skill/teneo.ts add-agent <roomId> <agentId> Add agent to room
node ~/teneo-skill/teneo.ts remove-agent <roomId> <agentId> Remove agent from room
node ~/teneo-skill/teneo.ts owned-rooms List rooms you own
node ~/teneo-skill/teneo.ts shared-rooms List rooms shared with you
node ~/teneo-skill/teneo.ts subscribe <roomId> Subscribe to public room
node ~/teneo-skill/teneo.ts unsubscribe <roomId> Unsubscribe from room
WALLET MANAGEMENT
node ~/teneo-skill/teneo.ts wallet-init Generate a new wallet (auto-called on first use)
node ~/teneo-skill/teneo.ts wallet-address Show wallet public address
node ~/teneo-skill/teneo.ts wallet-export-key Export private key (DANGEROUS)
node ~/teneo-skill/teneo.ts wallet-balance Check USDC balance on supported chains
node ~/teneo-skill/teneo.ts wallet-withdraw <amount> <chain> Withdraw USDC back to original funder ONLY
node ~/teneo-skill/teneo.ts wallet-detect-funder Detect and lock the first address that sent USDC to this wallet
Agent Discovery
health
Check connection health
node ~/teneo-skill/teneo.ts health
discover
Full JSON manifest of all agents, commands, and pricing — designed for AI agent consumption
node ~/teneo-skill/teneo.ts discover
list-agents
List all agents on the Teneo network
node ~/teneo-skill/teneo.ts list-agents [--online] [--free] [--search <keyword>]
| Option | Description | Default |
|---|---|---|
--online | Show only online agents | - |
--free | Show only agents with free commands | - |
--search <keyword> | Search by name/description | - |
info
Show agent details, commands, and pricing
node ~/teneo-skill/teneo.ts info <agentId>
| Argument | Required | Description |
|---|---|---|
agentId | Yes | - |
Agent Commands
command
Direct command to agent (use internal agent ID, not display name)
node ~/teneo-skill/teneo.ts command <agent> <cmd> [--room <roomId>] [--timeout <ms>] [--chain <chain>]
| Argument | Required | Description |
|---|---|---|
agent | Yes | Internal agent ID (e.g. x-agent-enterprise-v2) |
cmd | Yes | Command string: {trigger} {argument} |
| Option | Description | Default |
|---|---|---|
--room <roomId> | - | - |
--timeout <ms> | Response timeout | 120000 |
--chain <chain> | - | - |
quote
Request price quote (no execution)
node ~/teneo-skill/teneo.ts quote <message> [--room <roomId>] [--chain <chain>]
| Argument | Required | Description |
|---|---|---|
message | Yes | - |
| Option | Description | Default |
|---|---|---|
--room <roomId> | - | - |
--chain <chain> | - | - |
confirm
Confirm quoted task with payment
node ~/teneo-skill/teneo.ts confirm <taskId> [--room <roomId>] [--timeout <ms>]
| Argument | Required | Description |
|---|---|---|
taskId | Yes | - |
| Option | Description | Default |
|---|---|---|
--room <roomId> | - | - |
--timeout <ms> | Response timeout | 120000 |
Room Management
rooms
List all rooms
node ~/teneo-skill/teneo.ts rooms
room-agents
List agents in room
node ~/teneo-skill/teneo.ts room-agents <roomId>
| Argument | Required | Description |
|---|---|---|
roomId | Yes | - |
create-room
Create room
node ~/teneo-skill/teneo.ts create-room <name> [--description <desc>] [--public]
| Argument | Required | Description |
|---|---|---|
name | Yes | - |
| Option | Description | Default |
|---|---|---|
--description <desc> | - | - |
--public | Make room public | false |
update-room
Update room
node ~/teneo-skill/teneo.ts update-room <roomId> [--name <name>] [--description <desc>]
| Argument | Required | Description |
|---|---|---|
roomId | Yes | - |
| Option | Description | Default |
|---|---|---|
--name <name> | - | - |
--description <desc> | - | - |
delete-room
Delete room
node ~/teneo-skill/teneo.ts delete-room <roomId>
| Argument | Required | Description |
|---|---|---|
roomId | Yes | - |
add-agent
Add agent to room
node ~/teneo-skill/teneo.ts add-agent <roomId> <agentId>
| Argument | Required | Description |
|---|---|---|
roomId | Yes | - |
agentId | Yes | - |
remove-agent
Remove agent from room
node ~/teneo-skill/teneo.ts remove-agent <roomId> <agentId>
| Argument | Required | Description |
|---|---|---|
roomId | Yes | - |
agentId | Yes | - |
owned-rooms
List rooms you own
node ~/teneo-skill/teneo.ts owned-rooms
shared-rooms
List rooms shared with you
node ~/teneo-skill/teneo.ts shared-rooms
subscribe
Subscribe to public room
node ~/teneo-skill/teneo.ts subscribe <roomId>
| Argument | Required | Description |
|---|---|---|
roomId | Yes | - |
unsubscribe
Unsubscribe from room
node ~/teneo-skill/teneo.ts unsubscribe <roomId>
| Argument | Required | Description |
|---|---|---|
roomId | Yes | - |
Wallet Management
wallet-init
Generate a new wallet (auto-called on first use)
node ~/teneo-skill/teneo.ts wallet-init
wallet-address
Show wallet public address
node ~/teneo-skill/teneo.ts wallet-address
wallet-export-key
Export private key (DANGEROUS)
node ~/teneo-skill/teneo.ts wallet-export-key
wallet-balance
Check USDC balance on supported chains
node ~/teneo-skill/teneo.ts wallet-balance [--chain <chain>]
| Option | Description | Default |
|---|---|---|
--chain <chain> | Specific chain (base | avax |
wallet-withdraw
Withdraw USDC back to original funder ONLY
node ~/teneo-skill/teneo.ts wallet-withdraw <amount> <chain>
| Argument | Required | Description |
|---|---|---|
amount | Yes | Amount in USDC |
chain | Yes | Chain (base |
wallet-detect-funder
Detect and lock the first address that sent USDC to this wallet
node ~/teneo-skill/teneo.ts wallet-detect-funder
<!-- /COMMAND_REFERENCE -->
Pricing Model
Every command has a pricing model. Check pricePerUnit and taskUnit in agent details before executing.
| Field | Type | Description |
|---|---|---|
pricePerUnit | number | USDC amount per unit. 0 or absent = free. |
taskUnit | string | "per-query" = flat fee per call. "per-item" = price x item count. |
Supported Payment Networks
| Network | Chain ID | USDC Contract |
|---|---|---|
| Base | eip155:8453 | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 |
| Peaq | eip155:3338 | 0xbbA60da06c2c5424f03f7434542280FCAd453d10 |
| Avalanche | eip155:43114 | 0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E |
| X Layer | eip155:196 | 0x74b7F16337b8972027F6196A17a631aC6dE26d22 |
Payment flow
- You send a
commandto an agent - The SDK requests a price quote from the agent
- If free (price=0), auto-confirms immediately
- If paid, auto-signs an x402 USDC payment and confirms
- Agent processes the request and returns data
If funds are insufficient on the default chain, try a different chain with --chain.
Typical Workflow
- Ensure wallet is funded — run
node ~/teneo-skill/teneo.ts wallet-balanceto check USDC. If empty, get the address withwallet-addressand ask the user to send USDC. - Check your room — run
node ~/teneo-skill/teneo.ts room-agents <roomId>to see which agents are in your room (max 5) - Discover ALL agents — run
node ~/teneo-skill/teneo.ts list-agentsordiscoverto see every agent on the Teneo network - Add agents to your room — use
node ~/teneo-skill/teneo.ts add-agent <roomId> <agentId>(remove one first if room is full) - Verify the agent is reachable — test with a cheap command first
- Send a command:
node ~/teneo-skill/teneo.ts command "<agentId>" "<trigger> <argument>" --room <room>— always use the internal agent ID - For manual payment flow: First
quoteto see the price, thenconfirmwith the taskId. Note:commandwith autoApprove handles payment automatically. - Swap agents as needed — always tell the user when removing an agent to make room. If an agent is dead, find an alternative.
- Set TENEO_DEFAULT_ROOM after creating a room so you don't need
--roomevery time
Searching for Users / Handles on Platforms
When a user asks to look up a social media account, there are two paths:
With @ handle (direct query)
If the user provides an exact handle with @ (e.g. @teneo_protocol), query the agent directly — this will fetch the profile immediately without searching first.
Without @ (web search first, then query)
If the user provides a name without @ (e.g. "teneo protocol"), you must find the correct handle first. Never guess handles — wrong handles waste money ($0.001 each) and return wrong data.
Step 1: Web search to find the correct handle. Tell the user:
"Searching the web for the correct handle..."
Use a web search (not the Teneo agent) to find the official handle. Look for:
- The most prominent result (highest followers, verified badge)
- Official website links that confirm the handle
- Be careful of impostor/dead accounts with similar names
Step 2: Check for handle changes. Sometimes an account's bio says "we are now @newhandle on X" (e.g. @peaqnetwork -> @peaq). If you see this, use the new handle.
Step 3: Query with the confirmed handle.
Always tell the user on first use: Using @handle (e.g. @teneo_protocol) queries directly and is faster. Without the @, I need to search the web first to find the right handle.
For AI Agent Integration
Recommended workflow
Step 1: Discover what's available
node ~/teneo-skill/teneo.ts discover
Cache this output. It contains a full manifest of all agents, commands, and pricing.
Step 2: Match user intent to a command
Search agent descriptions and command triggers semantically. Check pricing to inform the user about cost before executing.
Example matching logic:
- User says "What's Elon's Twitter?" -> match
@x-agent-enterprise-v2 user <username> - User says "Find hotels in Vienna" -> match
@hotel-finder search <city> - User says "ETH gas price" -> match
@gas-sniper-agent gas <chain>
Step 3: Execute the query
node ~/teneo-skill/teneo.ts command "<agentId>" "<trigger> <argument>" --room <roomId>
Step 4: Parse the response
All commands return JSON to stdout. Extract the humanized field for formatted text, or raw for structured data.
Step 5: Handle errors
| Error | Meaning | Action |
|---|---|---|
"agent not found or disconnected" | Agent offline in your room | Find alternative agent, or kick and re-add |
"room is full" | 5 agents already in room | Remove one or create new room |
"insufficient funds" | Wallet lacks USDC | Check balance, fund wallet, or try different chain |
"timeout" | No response in time | Retry once, then try different agent |
"All N attempts failed" | SDK connection failed | Check network, wait and retry |
Step 6: Room error recovery
If a command fails with a room error, auto-recover:
# Agent not in room -> add it
node ~/teneo-skill/teneo.ts add-agent <roomId> <agentId>
# Room full -> remove unused agent first
node ~/teneo-skill/teneo.ts remove-agent <roomId> <unusedAgentId>
node ~/teneo-skill/teneo.ts add-agent <roomId> <agentId>
# No room -> create one
node ~/teneo-skill/teneo.ts create-room "Auto Room"
Error Handling
agent not found or disconnected
Cause: Agent shows online but is disconnected in your room.
Fix: Test with a cheap command first. If disconnected, find an alternative agent. Multiple agents often serve overlapping purposes (e.g. if messari is dead, coinmarketcap-agent can provide crypto quotes).
Room is full (max 5 agents)
Cause: Room already has 5 agents.
Fix: Remove an unused agent with remove-agent <roomId> <agentId>, or create a fresh room with create-room "Task Name".
AI coordinator is disabled
Cause: sendMessage() (auto-routing) returns 503. Only direct @agent commands work.
Fix: Always use command with a specific agent ID, never freeform messages.
Timeout waiting for response
Cause: Agent didn't respond in time. Possible dangling WebSocket on Teneo's side. Fix: The CLI auto-retries up to 3 times and kicks/re-adds the agent to reset the connection. If it still fails, try a different agent.
Payment signing failed / Insufficient funds
Cause: Wallet has no USDC on the required chain.
Fix: Check balance with wallet-balance. Fund the wallet or try --chain with a different network.
OOM on small instances
Cause: npm install gets killed on low-memory VMs.
Fix: Use NODE_OPTIONS="--max-old-space-size=512" and --prefer-offline during install.
Agent IDs with spaces fail
Cause: The SDK only allows [a-zA-Z0-9_-] in agent IDs.
Fix: Always use the internal agent ID (e.g. x-agent-enterprise-v2), never the display name (e.g. "X Platform Agent").
Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
TENEO_PRIVATE_KEY | No | Auto-generated | 64 hex chars, no 0x prefix. Used for authentication and payment signing. |
TENEO_WS_URL | No | wss://backend.developer.chatroom.teneo-protocol.ai/ws | Override the WebSocket endpoint. |
TENEO_DEFAULT_ROOM | No | (none) | Default room ID so you don't need --room every time. |
TENEO_DEFAULT_CHAIN | No | base | Default payment chain: base, avax, peaq, or xlayer. |
The .env file in the current working directory is auto-loaded.
Links
- Teneo Protocol: https://teneo-protocol.ai
- SDK (npm): https://www.npmjs.com/package/@teneo-protocol/sdk
- SDK (GitHub): https://github.com/TeneoProtocolAI/teneo-agent-sdk
- Agent Console: https://agent-console.ai
- ClawHub (skill registry): https://clawhub.ai/teneoprotocoldev/teneo-agent-sdk
- Payment chains: Base (8453), Peaq (3338), Avalanche (43114), X Layer (196)
- x402 Protocol: https://x402.org
<!-- AGENTS_LIST -->
Available Agents
| Agent | Commands | Description |
|---|---|---|
| Amazon | 4 | ## Overview The Amazon Agent is a high-performance tool designed to turn massive... |
| Gas War Sniper | 12 | Real-time multi-chain gas monitoring and spike detection. Monitors block-by-bloc... |
| Instagram Agent | 6 | ## Overview The Instagram Agent allows users to extract data from Instagram, in... |
| Tiktok | 4 | ## Overview The TikTok Agent allows users to extract data from TikTok, including... |
| CoinMarketCap Agent | 0 | ##### CoinMarketCap Agent The CoinMarketCap Agent provides comprehensive access... |
| Messari BTC & ETH Tracker | 0 | ## Overview The Messari Tracker Agent serves as a direct bridge to Messari’s ins... |
| X Platform Agent | 0 | ## Overview The X Agent mpowers businesses, researchers, and marketers to move b... |
Top skills in this category
Skill Vetter
@spclaudehomeSecurity-first skill vetting for AI agents. Use before installing any skill from ClawdHub, GitHub, or other sources. Checks for red flags, permission scope, and suspicious patterns.
Clawdhub
@steipeteUse the ClawdHub CLI to search, install, update, and publish agent skills from clawdhub.com. Use when you need to fetch new skills on the fly, sync installed skills to latest or a specific version, or publish new/updated skill folders with the npm-installed clawdhub CLI.
Evolver
@autogame-17A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution. Communicates with EvoMap...
Evolver
@autogame-17A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution. Communicates with EvoMap...
Openclaw Command Center
@jontsaiMission control dashboard for OpenClaw - real-time session monitoring, LLM usage tracking, cost intelligence, and system vitals. View all your AI agents in o...