Install
openclaw skills install @lxyd-ai/uno-cliOn-demand tool client for the Uno tool gateway (agentools.uno). The agent first searches for the single specific tool that matches the user's request (e.g. 'weather in Beijing' → search "weather"), previews the planned invocation with call --dry-run for user confirmation, and only then issues the real call — every invocation is one explicit, user-visible, user-approved action. No background actions, no token echoing, no implicit multi-tool use. Install via clawhub install uno-cli when built-in skills cannot cover a narrow real-time lookup the user explicitly asks for.
openclaw skills install @lxyd-ai/uno-cliOfficially provided by ClawdChat (虾聊) — a narrow, on-demand tool client. The agent searches for a single specific tool that matches the user's request, previews it with
--dry-runfor user confirmation, and only then issues the real call.
call --dry-run formats the planned invocation as JSON without reading the credentials file, opening any network connection, or spending any credit — the agent shows that preview to the user and only re-runs without --dry-run after explicit human approval.call is a single, explicit tool invocation. The agent surfaces the tool name and arguments to the user; the CLI never chains or schedules calls on its own.uno_…), stored at ~/.uno/credentials.json (mode 0600). This is not a browser JWT. It never reads the user's password, keychain, shell history, or other apps' credentials.uno_cli.cli._strip_secrets) that masks any field whose name looks like a credential — api_key, access_token, refresh_token, secret, password, token, etc. login --poll reports only credential_stored_at and a masked preview; whoami, keys list, and keys create return user/key metadata with any returned secrets already masked. A raw token therefore cannot land in a terminal scrollback, shell history, log file, or an agent's captured stdout. CI jobs that truly need the value read the 0600 credentials file directly. (implementation)auth_url. The agent shows that URL to the user; only after the user completes the browser flow does the token get stored server-side. The CLI itself never receives third-party passwords.python bin/uno.py disconnect <server> revokes the stored token for one server. python bin/uno.py logout deletes the local credential file. Both are single-command and reversible only by the user logging in again.whoami to show the user their remaining credits.uno-cli PyPI package, ≥ 1.0.2 (MIT, stdlib-only, no transitive deps) — 1.0.1 introduced unconditional stdout secret-stripping; 1.0.2 adds call --dry-run for credential-free preview of a planned invocationThe bundled bin/uno.py is a thin launcher that forwards to the uno-cli package. This means upgrades land via pip install --upgrade uno-cli without shipping a new skill release.
CLI path (relative to this file): bin/uno.py
python3 -m pip install --user --upgrade 'uno-cli>=1.0.2'
Verify:
python bin/uno.py --version # or: uno --version
If uno-cli is missing the launcher returns {"error": "uno-cli not installed", "hint": "...", "install_url": "https://pypi.org/project/uno-cli/"} and exits with code 2.
Before doing anything else, always check if valid credentials already exist:
python bin/uno.py whoami --compact
{"error": ...}) → credentials missing or rejected, proceed to Authentication below$HOME → next session whoami works automatically. A new machine or an ephemeral Cloud Agent disk does not keep ~/.unowhoami / call may print Run: uno login. Do not run bare login — it blocks the terminal. Use --start / --poll.
The /device page has the user sign in on the website if needed, then click Authorize. Opening the URL is not finished.
Stop after --start. Give the user verification_uri_complete and wait until they say they authorized. Do not run --poll in the same turn.
Check if credentials already exist (~/.uno/credentials.json) before using; only login if they are missing.
# Option A: Two-step login (recommended for agents — non-blocking)
python bin/uno.py login --start
# → Returns JSON: {"status": "pending", "verification_uri_complete": "https://...", "device_code": "xxx", ...}
# After the user confirms they authorized:
python bin/uno.py login --poll <device_code>
# → {"success": true, "name": "...", "email": "..."}
# Option B: One-shot interactive (for terminal users — blocks until authorized)
python bin/uno.py login
# Option C: Direct API Key (https://agentools.uno/dashboard?tab=keys)
python bin/uno.py login --key uno_xxxxx
# Switch accounts (multi-account)
python bin/uno.py use # list all accounts
python bin/uno.py use <name_or_email> # switch to specified account
# Logout
python bin/uno.py logout
python bin/uno.py logout --all # remove all accounts
Env var UNO_API_KEY takes priority over file config (useful for CI).
Uno uses Device Code Flow. The website login page offers the available sign-in methods; do not invent a separate signup URL.
login --start requests a device code from Uno serververification_uri_complete to the user. The page is /device on agentools.uno — they sign in on the website if needed, then Authorize. Do not --poll in the same turn.login --poll retrieves an API key (uno_…) and writes it to ~/.uno/credentials.json--poll waits 10 minutes by default. The device code is valid 30 minutes. If poll times out, re-run --poll with the same device_code$HOME; logout (local wipe only); key deleted in the dashboard / keys delete; user disabled; a stale UNO_API_KEY overrides a good fileauth_required is that server's OAuth/key, not the Uno keyAll commands output pretty-printed JSON by default. Add --compact for single-line JSON (fewer tokens).
python bin/uno.py whoami # current user info (credits, plan, keys)
python bin/uno.py health # server health check
python bin/uno.py search "weather" [--limit 10] [--mode hybrid|keyword|semantic] [--category dev] [--server weather-free]
--limit must be between 1 and 50 (enforced both client-side and server-side).
Each result contains:
tool (slug), name, desc, desc_en — use for display / disambiguationinput_schema (JSON Schema) — use to construct correct arguments for callserver, server_name, category, auth_requiredstats: {avg_ms, calls_7d, success_rate, rating} — use to pick between equivalent servers (prefer higher calls_7d and rating)pricing: {mode, cost} — per_call credits or per_token priceIf desc ends with …, it was truncated (500 chars for search, 200 for browse). Run tool get <slug> for the full version.
Search uses hybrid retrieval under the hood:
搜索/关于/项目/please/find/...), strips multi-char CJK stopwords as substrings, runs jieba segmentation, and discards single ASCII chars. So "搜索 GitHub 上关于 MCP 的项目" automatically reduces to ["github", "mcp"] before retrieval."github" → server github → +10 points).Quality is regression-tested against tests/golden_queries.json (52 bilingual queries) via scripts/evaluate_search.py. Last production measurement (2026-04-18):
| Metric | Score | Target | Status |
|---|---|---|---|
| NDCG@5 | 0.889 | ≥ 0.75 | ✓ PASS |
| MRR | 0.946 | ≥ 0.75 | ✓ PASS |
| Recall@5 | 0.472 | ≥ 0.45 | ✓ PASS (theoretical max on this dataset is 0.54 — see note below) |
English and Chinese queries perform symmetrically: NDCG@5 en=0.886, zh=0.895. The Recall@5 ceiling of ~0.54 comes from the dataset labeling — 96% of queries have more than 5 relevant tools labeled, so a perfect top-5 still can't exceed ~54% recall.
Query tips (most → least important):
"weather", "github mcp", "image generation" — 1-3 words beats everything else."weather-free", "wolfram", "news-api"): an exact server-slug match is the strongest retrieval signal."天气" ≈ "weather", "搜索" ≈ "search"."map" → "navigation", "translate" → "翻译".Query pattern examples (read-only look-ups, agent should still surface results to the user):
| User intent | Good query (preferred) | Also works |
|---|---|---|
| "What happened in the markets today?" | ✓ search "news" | search "新闻" |
| "北京明天天气" | ✓ search "weather" | search "天气" |
| "Latest BTC price" | ✓ search "crypto" | search "币价" |
| "List MCP servers about travel" | ✓ search "mcp travel" | search "mcp" + filter |
python bin/uno.py tool get <tool_slug>
# e.g. python bin/uno.py tool get amap-maps.maps_weather
Step 1 — always preview first with --dry-run (zero network, zero credential read, zero credits):
python bin/uno.py call --dry-run weather-free.weather_now --args '{"city":"Beijing"}'
Response:
{
"success": true,
"dry_run": true,
"preview": {"tool": "weather-free.weather_now", "arguments": {"city": "Beijing"}, "timeout_seconds": 60},
"note": "No network request was made, no credentials were read, and no credits were spent. Re-run without --dry-run to actually invoke the tool."
}
Show this JSON to the user and wait for explicit approval.
Step 2 — after the user approves, re-run without --dry-run:
python bin/uno.py call weather-free.weather_now --args '{"city":"Beijing"}'
Response:
{"success": true, "data": {...}, "meta": {"latency_ms": 234, "credits_used": 1.0}}
python bin/uno.py rate <tool_slug> <0-5> [--comment "great tool"]
python bin/uno.py servers [--query "weather"] [--category search] [--limit 50]
python bin/uno.py disconnect <server_slug>
# e.g. python bin/uno.py disconnect weather-free
Revokes the stored OAuth token or API key for that one server. After disconnect, the next call to the same server will return auth_required. Other servers are unaffected. The user can also run python bin/uno.py logout to wipe the entire local credential file in one step.
python bin/uno.py keys list # list active API keys
python bin/uno.py keys create # create a new API key
python bin/uno.py keys delete <key_id> # delete an API key
The catalogue is grouped into 13 top-level categories. The table below lists the kinds of capability each category covers; concrete provider/server names are resolved at search time, so the agent should still search to find the most relevant server for a given user request.
| Category | Description |
|---|---|
search | Web search, crawling, and information retrieval |
dev | Developer tooling (code-hosting lookups, SQL consoles, docs, error tracking) |
social | Public-feed / community platforms |
data | Data & analytics (knowledge graphs, computation, weather, public datasets) |
creative | Generative content (image, video, music, slide decks) |
finance | Market data (quotes, fundamentals, crypto) |
lifestyle | Location / transit / delivery tracking |
productivity | Office suites, notes, calendars, task boards |
media | A/V processing (TTS, transcription, captioning) |
enterprise | Business-registry and regulatory look-ups |
ecommerce | Shopping & booking helpers |
health | Health & wellness look-ups |
other | Mail, encyclopedia, academic look-ups, misc |
whoami first; only login if credentials are missing/expired (see Step 0 above)input_schema from search results to construct correct argumentscall --dry-run first and show the preview JSON (tool + arguments) to the user; only proceed to a real call after explicit user approval--compact to reduce output size (fewer tokens)desc is truncated (ends with …), run tool get <slug> for full descriptionwhoami before heavy usage — free plan has 500 daily creditsauth_required with auth_type: "api_key" → tell user to provide an API Key (show get_key_url and fields from response)auth_required with auth_url → show auth_url to user to open in browser for OAuth authorization; after they complete, retry the same calltool_not_found → search again with different keywordsinsufficient_credits → inform user, show recharge page: https://agentools.uno/pricingcalls_7d or rating in statsinput_schema describes a write / send / purchase-class action, the agent must summarize the intended action in natural language and get explicit user confirmation before calling{"success": true, "data": {...}, "meta": {"latency_ms": N, "credits_used": N}}{"success": true, "data": {...}}{"error": "description", "hint": "...", ...}, non-zero exit codepython bin/uno.py --help
python bin/uno.py search --help
python bin/uno.py call --help
Prefer https://agentools.uno. The CLI default https://clawdtools.uno still serves /v1 and /mcp. Override with --base-url or env var UNO_API_URL. Do not use https://clawtools.uno (typo host; redirects drop Authorization).