npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@clink-ai/clink-cli

v0.2.32

Published

TypeScript CLI for Clink customer wallet APIs

Downloads

375

Readme

clink-cli

clink-cli is a command-line client for Clink customer wallet APIs.

中文说明

Current source package version: 0.2.32.

Install

Recommended setup installs the latest npm CLI globally and synchronizes the official agentic-payment-skills package:

npx @clink-ai/clink-cli@latest install

To install only the CLI without synchronizing Skills:

npm install -g @clink-ai/clink-cli

Run without installing globally:

npx @clink-ai/clink-cli --help

Published package name:

@clink-ai/clink-cli

Installed commands:

clink-cli
clink

clink is a shorter alias that invokes the same CLI as clink-cli.

Update both the npm CLI and the official Skill later with:

clink update

Use clink update --check to check versions without modifying installed CLI or Skill state, or clink skills sync to repair only the official Skill. These are explicit operations: npm postinstall never downloads Skills, and ordinary wallet/payment commands do not perform Skill synchronization.

Requirements:

  • Node.js >=20
  • Access to the target Clink API environment

Build Editions

The repository keeps one source tree on main and produces two vendored bundles from the same commit:

npm run bundle:main
npm run bundle:visa

The outputs are:

dist-bundle/main/clink-cli.bundle.mjs
dist-bundle/visa/clink-cli.bundle.mjs

The main Edition exposes the standard wallet and payment commands, including wallet init, and rejects the visa command. The Visa Edition exposes the same standard commands plus visa init, visa recommend, visa commerce-run, and the other Visa subcommands. src/index.ts is the main Edition entry point; src/visa-index.ts is the Visa Edition entry point. The two entry points create separate esbuild dependency graphs, so the main bundle does not include src/visa/*.

The published @clink-ai/clink-cli command uses the main Edition. The vendoring workflow builds both Editions from main: the main Edition is synchronized to agentic-payment-skills, and the Visa Edition is synchronized to visa-skill.

Default base URL:

https://api.clinkbill.com

Quick Start

Every command starts with:

clink-cli <command> [subcommand] [options]

1. Initialize your wallet

Run this once to authorize the CLI and save OAuth credentials locally:

clink-cli wallet init --email [email protected]

For sandbox (UAT), use --sandbox; for test (*.clinkbill.dev), use --test. The flags are mutually exclusive. They select the wallet environment on wallet init and independently select the public environment on Catalog discovery commands and tool internal-ucp get-merchant-list; other commands reject them. Their URLs come from src/domains.ts. The main distribution selects production without either flag or a custom base URL override; a packaged distribution may supply a fixed wallet-init environment and reject conflicting flags. A successful initialization saves the selected environment, so later authenticated commands do not need an environment flag. The CLI always prints the browser URL. It opens the URL when --open is passed or default-open-links is enabled (the saved default is false); --no-open overrides both and is suitable for agents that should hand the URL to the user. If the browser cannot be opened, the CLI prints a warning and continues polling; open the displayed URL manually on any device. Complete email verification and confirmation in the browser while the CLI polls for tokens. Existing customers keep their server-side name. For a new customer, the CLI uses the email text before @ as the initial name; --name is no longer accepted. Use clink-cli config set name <name> to change the local name afterwards. The resolved name is always saved to local config.

The browser URL keeps only user_code in the query string. The supplied email and resolved name are carried in the URL fragment, which the Portal reads and immediately removes from the address bar.

clink-cli wallet init --sandbox --email [email protected]
clink-cli wallet init --test --email [email protected]

After authorization succeeds, wallet init also refreshes local cached payment methods from the card setup-link endpoint. If that refresh fails, initialization still succeeds and the output marks paymentMethodsCached: false. Its returned bindingUrl keeps only the Portal origin and appends the locally stored email, so a signed-out browser can prefill login without exposing other backend URL parameters. Card binding/setup/modify commands apply the same rule.

Access and Refresh Tokens are stored in the root authorization object in ~/.clink-cli/config.json. The CLI refreshes shortly before Access Token expiry, and atomically replaces both tokens because the server rotates the Refresh Token on every refresh. The saved authorization also records its issuer origin; the CLI never sends either token to another origin. If an OAuth-authenticated Agent API request returns 401, the CLI refreshes and retries that request once. A second 401 and all 403 responses are returned without another refresh.

wallet init also registers an Agent Client using a stable SHA-256 device identity. The CLI reads IOPlatformUUID with /usr/sbin/ioreg on macOS, MachineGuid from the 64-bit registry view on Windows, and /etc/machine-id with /var/lib/dbus/machine-id as the Linux fallback. The same platform-specific path is used in containers; the CLI does not detect or special-case container environments. There is no CLINK_DEVICE_ID override. Hostname and OS release are sent only as optional troubleshooting metadata, and dry-run output redacts stable device identifiers.

Check the saved local config:

clink-cli wallet status --format pretty

Log out and remove local credentials:

clink-cli wallet logout

Logout also removes the saved customerId, payment-method cache, and risk-rule cache so the next login can bind a different customer safely. Email and display name preferences are retained.

The local config file is stored at:

~/.clink-cli/config.json

Visa benefits (Visa Edition)

Sign in through the direct VSRP OAuth page and create or reuse the Clink customer identified by Visa's trusted profile email:

clink-cli visa init --sandbox --open

visa init calls CWallet's Benefit authorization endpoint with clientId=clink-cli, a stable UUID v4 installation identity, and the same Agent Client device metadata used by wallet init. The Agent Client device ID is SHA-256 derived from macOS IOPlatformUUID, Windows MachineGuid, or Linux machine-id without container-specific handling. The CLI then polls until the server-provided expiresIn elapses; a missing or invalid value falls back to ten minutes. It does not open a Clink landing page. Every explicit visa init starts a fresh Benefit OAuth transaction and replaces any locally pending transaction instead of reopening an earlier authorization URL.

Agent hosts should use the recoverable form:

clink-cli visa init --sandbox --start --open --format json
clink-cli visa init --sandbox --resume <resume-id> --format json

--start creates or reuses one pending transaction, attempts the system browser, and returns manualOpenUrl, browserLaunch, expiresAt, and resumeCommand immediately. --resume probes that exact transaction once; a pending response returns the same command, while success atomically persists the authorization. These modes never expose deviceCode or OAuth state, never create a background poller, and cannot overwrite a newer login or authorization.

The resulting Clink Access and Refresh Tokens replace the same root authorization used by wallet init; the trusted email returned beside the Benefit Token response is saved as the root email used for buyer.email during checkout. The returned agent_client_id and visa_registration_status are saved as authorization.agentClientId and authorization.visaRegistrationStatus; only SUCCEEDED means Visa registration completed. The email is not embedded in the Access Token. The CLI never creates a second local Clink Token set. A same-customer login replaces the previous authorization and best-effort revokes its old Refresh Token family. A different customer is rejected instead of silently switching accounts.

Discover offers:

clink-cli visa recommend "Visa权益有哪些" --anonymous --format pretty
clink-cli visa recommend "日本餐厅优惠" --region jp --category dining_restaurant
clink-cli visa recommend --personalized "我的卡能用什么"
clink-cli visa detail P2025110009
clink-cli visa taxonomy

Broad all-offer requests and --all fetch the VSRA API in 50-item pages and aggregate every Program. Explicit --limit or --page keeps ordinary paging. --personalized preserves the separate VSRA Device Flow; its market Token and recoverable FSM state live under visa in the same config file, while Clink OAuth remains only at root authorization. visa status reports both login states without printing Tokens, OAuth state, device codes, or profile data.

VSRA defaults to the production market hosts. Development overrides remain available through VSRA_ENV=test|staging|local, VSRA_BASE_URL, or VSRA_<MARKET>_BASE_URL.

Use the same foreground command for bounded preparation or a frozen purchase. Login-only preparation needs no purchase or Instruction context:

clink-cli visa commerce-run \
  --context '{"mode":"prepare","target":"login","environment":"uat"}' \
  --open \
  --format json

Use target: "visa_card_ready" only to verify an already VIC-ready Visa. Without a frozen purchase Instruction context, preparation does not open or prompt card/VIC setup. Preparation forbids --confirm-purchase and cannot create an Instruction, Checkout, or payment.

Immediately after visa recommend, resolve and freeze one product before Benefit login:

clink-cli visa product-search \
  --merchant-url https://merchant.example/store \
  --query "meal voucher" \
  --format json

For an internal merchant, this performs one token-free merchant-scoped Catalog search and resolves the selected exact product/variant, price, currency, availability, merchant, and localized title directly from the rich search result. It does not call the customer-authenticated product-detail endpoint. A unique exact-title match, unique title-containing match, or sole result returns PRODUCT_VERIFIED; incomplete facts fail closed. Ambiguity returns PRODUCT_SELECTION_REQUIRED with candidates and no login or payment side effects; rerun with --selected-product-id <id> (or compatibility alias --product-id) after selection. Pass the query in the user's language and preserve the Catalog title instead of translating it. External parsing is allowed only after NOT_IN_INTERNAL_UCP_LIST. Use --sandbox or --test to select an environment for this token-free discovery without changing the saved wallet environment.

After the product is verified, check and initialize Benefit login:

clink-cli visa commerce-login \
  --context-file login.json \
  --confirm-purchase \
  --open \
  --format json

login.json contains only environment and the complete frozen instructionContext. The command validates that context and the live purchase confirmation before reading CLI config or touching Tokens or the network. It returns immediately with CONTINUE_REGULAR_INSTRUCTION_FLOW when Benefit login is already ready and reports instructionId: null; it never guesses a historical Quick Instruction. Otherwise it starts one foreground Benefit login with the exact context. When that login returns pendingInstructionId, the command waits up to 300 seconds, uses the exact activation event only as a wake-up, and exact-GETs the same ID. Only status=ACTIVE returns ready=true; a remaining PENDING Instruction returns ready=false without opening another page. With --open, the caller should tell the user to finish VSRP login and, when needed, card binding, VIC, and Passkey in the one browser flow while the CLI keeps waiting. The result preserves instructionId, instructionStatus, instructionReady, pendingInstructionId, and browserLaunch for diagnostics. manualOpenUrl is returned only when automatic launch failed or was not requested; a successful browser launch does not duplicate the sensitive URL in the final JSON. This command never calls the standalone Instruction create API, routes a merchant, searches Catalog, prepares a card, creates a Checkout, or pays. --dry-run prints a zero-side-effect plan without inspecting login state.

The intended order is visa recommend -> visa product-search -> visa commerce-login -> visa commerce-run.

A purchase uses the Visa Skill frozen schema:

clink-cli visa commerce-run \
  --context-file purchase.json \
  --confirm-purchase \
  --open \
  --format json

The JSON contains mode: "purchase", immutable environment, requestText, program.code, selection (merchantUrl, product ID/query, quantity, and optional routing facts), expected (merchant, item, actual major-unit amount, and currency), instructionContext, and digitalDeliveryExpected. Top-level instructionId is rejected. Every mandate used to create a new Instruction must match the exact expected amount and currency and use one MCC, which becomes the Checkout MCC. The CLI adds no amount buffer.

Purchase mode requires the separate commerce-login stage to be ready and exact-revalidates the product selected by product-search. It never starts login or performs product discovery. When a VIC-ready Visa already exists, it lists that payment instrument's Instructions and considers only ACTIVE candidates with matching currency, MCC, and merchant scope, unexpired Instruction and Mandate timestamps, and an available one-time Mandate. The candidate must have amountLimit >= the actual total. The CLI selects the smallest sufficient amount (equality first), then the newest trusted createdAt/createTime. Missing or tied creation time fails closed. It exact-GETs the selected ID and revalidates every condition before Checkout.

When no VIC-ready Visa exists, commerce-run first creates or reuses one no-card PENDING Instruction through /agent/cwallet/instructions/pending. Only after CWallet returns the exact ID does the CLI resolve the trusted Agent Portal binding URL; it withholds the URL until the first successful exact-ID Event Hub poll and identity validation. It never opens that URL or a standalone VIC page and keeps waiting for activation of that exact ID. Portal owns card binding, 3DS, and VIC. After activation, the CLI exact-GETs the same Instruction, refreshes cards, and requires its attached paymentInstrumentId to be a same-card Visa with visaRegistrationSucceeded=true. Amount, currency, MCC, and merchant scope are revalidated before Checkout. Timeout returns only an exact instruction get continuation and never creates another Instruction or starts Checkout/payment. If the pending endpoint returns CARD_READY or VIC_READY without an ID because VIC completed during the request race, the CLI refreshes cards once and enters the regular Instruction flow without retrying the pending POST.

The exact-ID wait also wakes on customer-wide payment_method.update and vic_device.binding_succeeded events. A Portal card binding ceremony that started before the PENDING existed is frozen by CWallet as NO_PENDING and never activates a later Instruction, so after such a card event the CLI gives the activation event a bounded grace period, exact-GETs the same ID, and if it is still PENDING re-posts the identical context once: CWallet then cancels the no-card PENDING for the now VIC-ready customer and answers VIC_READY, and the CLI continues with the regular Instruction flow (fallbackReason=card_vic_ready_without_pending_activation, one more Passkey). The pending response may also carry ceremonyInProgress/activationExpected; the CLI surfaces them and still waits.

For an already VIC-ready card, historical PENDING or draft Instructions are not authorized. If no usable ACTIVE candidate exists, the CLI creates and authorizes one regular Instruction for the exact actual price.

Before card and Instruction work, the CLI resolves the real merchant internally first and verifies the current Catalog product ID, title, price, currency, availability, merchant route, and endpoint. Only an explicit internal-list miss may use external product parsing.

Only live purchase mode requires --confirm-purchase, validated before Token refresh or business network access. Checkout delegates to the same exactly-once ucp-checkout run implementation and uses bounded GET-only recovery for the same Checkout before optionally waiting for voucher delivery. --dry-run prints a zero-network, zero-payment-side-effect plan for either mode. When the shared budget expires, output contains resumeReadOnly=true and one bound ucp-checkout get continuation that can wait for both Checkout and delivery. Never append ucp-order commands or rerun commerce-run after Checkout creation.

Prepare a no-card Instruction (Main Edition)

Main Edition exposes the same shared capability as one stable command:

clink-cli instruction prepare \
  --title "Business trip" \
  --mandates '[{"title":"Hotel","description":"Hotel payment","amountLimit":"1000.00","currencyCode":"USD","merchantCategoryCode":"7011"}]' \
  --max-wait 900 \
  --format json

The command sends the complete restricted instructionContext without paymentInstrumentId or extra. Once CWallet returns an exact PENDING ID, the CLI resolves the trusted card binding URL and starts an exact-ID purchase_instruction.activated watch. After the first successful Event Hub poll and identity validation, stdout emits a structured PENDING handoff envelope containing instructionId, bindingUrl, watchReady=true, watchEventType="purchase_instruction.activated", terminal=false, and processRunning=true. The process remains in the foreground; after the watch it exact-GETs the same ID and emits the final envelope. Agents should parse these JSON envelopes, not stderr prose.

The command never opens the binding URL or a standalone VIC page. --open and --no-watch are rejected. Timeout preserves the original ID in a read-only instruction get continuation and never creates another Instruction or starts Checkout/payment. A concurrent CARD_READY/VIC_READY response returns card_ready without an ID, another pending POST, or a binding handoff. The watch also wakes on payment_method.update/vic_device.binding_succeeded; when the card is VIC-ready but the exact ID stays PENDING after a grace period, one reconciliation POST of the same context returns VIC_READY and the command reports card_ready with the original instructionId and fallbackReason=card_vic_ready_without_pending_activation. Pending expiry is server-owned: exact verification requires one valid future Instruction expiry shared by every Mandate, while caller-supplied Instruction/Mandate expiry values are not compared.

2. Open card pages

Get the card-binding handoff with its built-in event watcher:

clink-cli card binding-link

With the default watch enabled, card binding-link first refreshes paymentMethodsVoList, starts a watcher scoped to payment_method.added whose Event Hub polls send eventTypes: ["payment_method.added"], and waits for the first successful, well-formed Event Hub poll (data.records must be an array). Only then does it print the first stdout envelope:

{
  "ok": true,
  "data": {
    "bindingUrl": "https://agent.clinkbill.com/payment-method-setup?email=alice%40example.com",
    "paymentMethodsVoList": [],
    "watchReady": true,
    "watchEventType": "payment_method.added"
  }
}

bindingUrl is rebuilt from the trusted Portal origin, the local add-card path, and the locally stored email. Backend path, query, fragment, and token data are not exposed. Without a stored email, the URL still targets the add-card page. watchReady: true means the event listener is ready before the browser handoff; it does not mean a card has already been added. The process remains alive until the matching event or the 15-minute timeout, then prints the watch result as a second stdout envelope. This scoped watcher ACKs only matching payment_method.added events. Unrelated current or stale events are left queued for their owning workflows. A malformed successful poll response fails before any binding handoff is printed.

To refresh the payment-method list without starting a listener:

clink-cli card binding-link --no-watch --format json

This form returns immediately with the trusted add-card bindingUrl, a normalized paymentMethodsVoList, watchReady: false, and watchEventType: null. It makes no Event Hub poll and prints a warning plus a replacement events poll command to stderr; start a listener before giving the URL to a user who will act on it.

Open the add-card page:

clink-cli card setup-link --open

Open the manage-card page:

clink-cli card modify-link --open

Open Visa Passkey registration for an existing card:

clink-cli card passkey-link --payment-instrument-id pi_xxx --open

For setup-link, modify-link, passkey-link, instruction create, and instruction sign-url, an OAuth-authenticated CLI first attempts a one-time browser handoff through a random 127.0.0.1 callback. A successful handoff reuses the CLI identity to establish the existing 30-minute Portal session, then redirects to the validated Portal completion URL returned by the handoff service. Listener or create failures open the trusted target directly; callback or approval failures redirect to the existing email-code login while preserving the target page.

3. Check saved payment methods

List cached payment methods:

clink-cli card list --format pretty

Get one cached payment method:

clink-cli card get --payment-instrument-id pi_xxx

Notes:

  • card add/update/delete happens on the web page, not in the CLI
  • card list and card get read local cached data
  • card binding-link, card setup-link, and card modify-link refresh the local payment method cache

4. Make a payment

Pay with merchant mode:

clink-cli pay \
  --merchant-id merchant_xxx \
  --amount 10.00 \
  --currency USD \
  --payment-instrument-id pi_xxx

Pay with session mode:

clink-cli pay --session-id sess_xxx --payment-instrument-id pi_xxx

VIC-routed charge with instruction/mandate context:

clink-cli pay \
  --session-id sess_xxx \
  --payment-instrument-id pi_xxx \
  --instruction-id ins_xxx \
  --mandate-id mndt_xxx \
  --shipping-address '{"street_address":"1 Market St","address_locality":"San Francisco","address_region":"CA","address_country":"US","postal_code":"94105","first_name":"Ada","last_name":"Lovelace","phone_number":"+14155550100"}' \
  --products '[{"productId":"sku_1","productName":"Demo","quantity":1,"unitPrice":12.99,"currencyCode":"USD"}]'

If --payment-instrument-id is omitted, CARD and BALANCE keep using the default cached payment method. Other payment types first refresh the payment-method cache and select only a matching paymentMethodType. If none match, bind one and refresh payment methods. One matching method is selected directly; when several match, exactly one must be marked default, otherwise the command requires an explicit --payment-instrument-id. An explicit instrument for those other payment types is also checked against the refreshed list and must have the requested type. Explicit CARD and BALANCE behavior remains unchanged. The command never falls back to a card of another type. --purchase-instruction-id remains as a backward-compatible alias for --instruction-id. --shipping-address maps to aiAgentInstructionBo.shippingAddressJson and should use the UCP Postal Address shape (street_address, address_locality, address_region, address_country, postal_code, first_name, last_name, phone_number). --products maps to aiAgentInstructionBo.products in the Clink order charge request; unitPrice is a major-unit decimal.

When a payment returns channelPaymentResponse.status=5 with a PNG QR Data URL, pay writes the validated PNG to a private temporary file and returns customerAction.type=QR_CODE_REQUIRED, mediaType=image/png, imagePath, temporary=true, cleanupRequired=true, directory-level cleanupPath, orderId, paymentExecutionDetailId, expiresAt, and expiresSecond. expiresAt is Unix epoch seconds. Event consumers use expiresSecond for their maximum wait and cap it at 900 seconds. The complete Data URL is redacted from stdout. The caller owns the file lifetime and must remove cleanupPath recursively after the payment reaches a terminal state or expires; the CLI intentionally keeps it after exit so an Agent can display it.

List all public skills

clink-cli skills list --all --format pretty
clink-cli skills list --all --tippable --format pretty

The command requests the dashboard marketplace with pageSize=999&sort=NEW. Every list first removes rows without nonempty publisher, name, or versionNo. --tippable additionally requires nonempty skillId and merchantId, parses the tipsConfigJson string, and keeps only records whose parsed configuration has the boolean value enabled: true. Missing, malformed, disabled, or string-typed enabled configurations are filtered without failing the command. The CLI then reverses the selected rows, injects contiguous one-based Number values, and returns the JSON array in the normal { "ok": true, "data": [...] } success envelope.

Tip a skill publisher

clink-cli skills tip --publisher clinkpay --name PollyReach --amount 2
clink-cli skills tip --publisher jinsx商户四 --name PollyReach --amount 2
clink-cli skills tip --publisher '草诀歌AI Labs' --name 'SEO Deep Audit' --amount 2

Skill tips always use USD and accept any positive finite amount. The CLI resolves a case-insensitive exact --publisher/--name match. Publisher and skill names accept Unicode letters, numbers, including Chinese, and internal ASCII spaces. Quote values containing spaces. CLI tips always use sort=NEW to select the latest Marketplace version. Successful output includes the resolved versionNo when the Marketplace supplies it.

The CLI uses only the selected marketplace record's merchantId and ignores tipsConfigJson. It refreshes the payment-method list and selects only a record with explicit isDefault=true. If no explicit default exists, the command returns HTTP-style code 422 with No default payment method before creating an order. A default CARD is sent to the charge API without checking Credit balance. A default BALANCE is sent only when its finite numeric availableBalance covers the full tip amount; a missing, invalid, non-finite, or insufficient balance returns 402 with Credit 余额不足,请先绑定银行卡. An unsupported explicit default type returns 422 with Unsupported default payment method. The backend calculates any Credit allocation.

After the synchronous payment response confirms success (channelPaymentResponse.status=1), and when the marketplace record includes skillId, the CLI best-effort reports the tip metric to the resolved dashboard base: POST /prod-api/skill-marketplace/internal/skills/{skillId}/metrics/tip. The metric uses only the response-verified latest Marketplace versionNo selected by the pre-payment sort=NEW lookup. The request body contains the payment orderId, locked versionNo, major-unit amount, currency, and the fixed source: "CLINK_PAYMENT". A metrics failure does not change the successful payment result. Failed and 3DS-pending payments are not reported.

Explicit terminal agent-pay failures return payment_failed with exit code 5. Missing or unknown agent-pay statuses return payment_unknown with exit code 6 and must be verified before retry. Tip results also expose the channel's original rawPaymentStatus, rawPaymentMessage, and complete payment payload so callers can diagnose the response without losing the compatibility status.

5. Search a merchant UCP Catalog

Search a registered merchant's public Catalog. The command defaults to production; add --sandbox for sandbox/UAT or --test for test:

clink-cli ucp-catalog search \
  --merchant-id merchant_xxx \
  --query "wireless keyboard" \
  --language en-US \
  --context '{"currency":"USD"}' \
  --limit 10 \
  --test \
  --format json

The command sends POST /agent/ucp/{merchantId}/catalog/search, automatically generates the required Request-Id, and uses UCP-Agent: clink-cli unless --ucp-agent overrides it. Optional --filters, --signals, and --attribution values must be JSON objects; --cursor continues a previous page. Catalog price filters use minor currency units. The request is anonymous: the CLI does not read ~/.clink-cli/config.json, inherit the wallet environment or CLINK_BASE_URL, send OAuth/CSK/customer headers, refresh a token, or retry a 401. --customer-api-key and --customer-id are rejected. A merchant without an enabled Catalog returns the backend's original catalog_not_supported message. A 401/403 is surfaced as an API failure rather than a local wallet authentication failure, so callers must not trigger wallet re-login for it.

Translation is opt-in and driven only by the UCP context.language field: pass --language <tag> with an IETF BCP 47 tag such as en, zh-Hans, or fr-CA, or set language inside --context. The two cannot be combined. Omit both and the merchant's original titles and descriptions come back unchanged — the query text is never used to guess a target language. Pass the same language to Search and Product, or the list and detail views disagree.

clink-cli ucp-catalog search \
  --merchant-id merchant_xxx \
  --query 熊猫外卖 \
  --language en \
  --format json

Fetch one product by an ID returned from search:

clink-cli ucp-catalog product \
  --merchant-id merchant_xxx \
  --product-id product_xxx \
  --language en-US \
  --context '{"currency":"USD"}' \
  --format json

The Product request supports the same optional --language, --context, --filters, --signals, and --attribution fields as Search. The current provider does not accept selected options or preferences; use the product and variant data returned by the service. Product is anonymous and uses the same production/--sandbox/--test environment rules as Search.

Search across merchants when you do not know which one carries the item:

clink-cli catalog search \
  --query "iced latte" \
  --channel-type shopify \
  --ext '{"trace":"demo-1"}' \
  --format json

The command sends POST /agent/ucp/extra/catalog/search and takes no --merchant-id: this endpoint exists to find which merchants carry the item. Use ucp-catalog search when the merchant is already known. --channel-type narrows to one channel, and omitting it discovers across every channel. --ext is a free-form JSON object that is passed through and logged only — it never affects search conditions or the response shape. All other options match ucp-catalog search. Results come back grouped by target, each group carrying channel_type plus either merchant_id (internal merchant) or store_id (external platform store); the shape does not change with --channel-type, only the number of groups does. This command follows the same anonymous, config-independent environment rules as merchant-scoped Search and accepts the same --language/context.language field. Broad discovery forwards the language to each provider but does not run UCP's LLM translation pass, so provider localization may vary; that translation pass is implemented for merchant-scoped Search and Product only.

Query one order's current status by order ID:

clink-cli ucp-order get --order-id order_xxx --format json

The command sends GET /agent/ucp/orders/{orderId}. The endpoint is not merchant-scoped: ownership is checked against the caller's wallet identity, so another buyer's order returns not_found.

Wait for an expected voucher or other digital delivery:

clink-cli ucp-order wait-delivery \
  --order-id order_xxx \
  --max-wait 900 \
  --format json

This command repeatedly reads the same order and never retries payment, Checkout completion, or order creation. A missing digital_delivery field is pending because the asynchronous payment projection may not have initialized it yet. Pending responses honor digital_delivery.next_retry_at with a local 3-to-30-second clamp. The command stops at ready or failed; ready requires at least one artifact. A bounded timeout returns the last order plus resumeCommand. Use this command only when the frozen product context expects digital delivery; ordinary UCP orders should keep using the single-read ucp-order get.

List the calling wallet's orders, newest first, optionally filtered by status and creation time:

clink-cli ucp-order list \
  --status paid,partially_refunded,refunded \
  --start-time 2026-07-01T00:00:00Z \
  --end-time 2026-07-31T23:59:59Z \
  --size 20 \
  --format json

The command sends GET /agent/ucp/orders with created_from, created_to, status, page, and size query parameters. All filters are optional. --status accepts a comma-separated list matching any of draft, pending, paid, cancelled, partially_refunded, refunded, and unknown values fail locally; multiple statuses go out as repeated status params. --start-time / --end-time are UTC: a timestamp with no zone offset is read as UTC and sent as RFC 3339. Rows carry id, checkout_id, status, payment_status, amount, currency, and created_at; use ucp-order get for the full order.

6. Manage UCP checkout for shadow merchants

Create and complete a checkout in one safety-gated command:

clink-cli ucp-checkout run \
  --merchant-url https://shop.example/checkout/abc \
  --merchant-category-code 5311 \
  --currency USD \
  --line-items '[{"id":"li_1","item":{"id":"sku_1","title":"Demo","price":"10.00"},"quantity":1}]' \
  --payment-instrument-id pi_xxx \
  --confirm-purchase \
  --wait-delivery \
  --max-wait 900 \
  --format json

Live run requires the explicit --confirm-purchase flag and rejects its absence before any network request. It sends create once, then sends complete exactly once; neither payment/create request is automatically retried. If complete returns complete_in_progress or any other status besides completed, the command returns stage=complete with the authoritative response and a read-only ucp-checkout get resumeCommand bound to the original endpoint and checkout ID.

--wait-delivery starts only when complete authoritatively returns status=completed and data.order.id. Delivery ready, failed, and timeout outputs contain the fetched order and its digital_delivery snapshot. Timeout reuses the read-only ucp-order wait-delivery resume command; it never retries Checkout or payment. run --dry-run needs no confirmation and makes no network requests or payment side effects; it prints an auditable create, exactly-once complete, and optional delivery plan.

Create a UCP checkout session for a CLI-discovered merchant:

clink-cli ucp-checkout create \
  --merchant-url https://shop.example/checkout/abc \
  --merchant-category-code 5311 \
  --currency USD \
  --line-items '[{"id":"li_1","item":{"id":"sku_1","title":"Demo","price":"10.00"},"quantity":1}]'

For ucp-checkout create, line_items monetary price / amount fields are decimal major-unit values converted by --currency. Live update first reads the existing checkout currency and converts decimal strings such as "12.00" to minor units; existing integer JSON numbers remain minor-unit values for backward compatibility. The create API body sends customer_id and buyer.email from the local clink-cli config get --format json data, and sends --currency as context.currency. Update --dry-run performs no read request, so it requires --currency.

Then query, update, complete, or cancel it:

clink-cli ucp-checkout get --checkout-id chk_xxx
clink-cli ucp-checkout update \
  --checkout-id chk_xxx \
  --line-items '[{"id":"li_1","item":{"id":"sku_1","title":"Demo","price":"12.00"},"quantity":1}]'
clink-cli ucp-checkout complete --checkout-id chk_xxx
clink-cli ucp-checkout cancel --checkout-id chk_xxx

By default these commands call {baseUrl}/agent/ucp/external/checkout-sessions. To point at a custom UCP endpoint prefix, pass --endpoint https://host/path; the CLI appends /checkout-sessions, /checkout-sessions/{checkoutId}, or the corresponding action path.

Notes:

  • the CLI command is ucp-checkout; there is no external subcommand
  • CLI-discovered merchants use the shadow-merchant external checkout path internally
  • run reuses all create fields and adds --payment-instrument-id, --confirm-purchase, --wait-delivery, and --max-wait
  • create sends merchant_url, customer_id, buyer.email, and context.currency; the backend resolves authorization and order_channel_id
  • complete sends a standard UCP payment.instruments object; the selected instrument id is local config customerId#paymentInstrumentId, credential.token is the payment instrument ID, and when --payment-instrument-id is omitted or empty it uses the cached default card
  • Idempotency-Key is generated by the CLI for create/update/complete; callers do not pass --idempotency-key
  • OAuth Bearer authorization is required after a wallet first uses OAuth; legacy CSK remains only for never-OAuth wallets

7. Refund an order

Create a full refund:

clink-cli refund create --order-id order_xxx

Check refund status:

clink-cli refund get --refund-id rfd_xxx

8. Check risk rules

Get current risk rule settings:

clink-cli risk get --format pretty

Open the risk rule page:

clink-cli risk link --open

Install agent skills

Synchronize the official payment Skill

The setup and update commands synchronize agentic-payment-skills after the npm CLI step:

npx @clink-ai/clink-cli@latest install
clink update
clink update --check --format pretty
clink skills sync --force

The primary source is https://github.com/clinkbillcom/agentic-payment-skills. When that source is unavailable, the synchronizer uses https://www.clinkbill.com/public/skills/agentic-payment-skill.zip. The fallback is accepted only when its schema-v1 manifest is available at https://www.clinkbill.com/public/skills/agentic-payment-skill.manifest.json and its declared size, archive SHA-256, content SHA-256, and Skill version all match. A missing, unreachable, malformed, or mismatched manifest fails closed without installing the ZIP. The validated runtime package is installed at ~/.agents/skills/agentic-payment-skills; source-root tests/ and docs/ directories are not published. Updates use staged validation and the managed release transaction, so a failed sync keeps the previously active Skill. clink update --check and clink skills sync --check do not modify installed CLI or Skill state; their Skill result uses action: "checked" and reports the would-be action in plannedAction.

CLI self-install/update is intentionally limited to the npm distribution. Vendored copies return a clear error and can still use clink skills sync as the Skill-only recovery entry point. npm child processes are invoked with argument arrays and no command shell. Their stdout is captured so an explicit --format json command emits exactly one standard JSON envelope.

Install a Marketplace Skill

Install a specific release, let the server select the latest release, replace an unmanaged installation, or preview the operation:

clink-cli skills install clinkpay/[email protected]
clink-cli skills install clinkpay/PollyReach
clink-cli skills install clinkpay/PollyReach --force
clink-cli skills install clinkpay/PollyReach --dry-run --format pretty

The package format is <publisher>/<skillName>[@<version>]. When the version is omitted, the CLI omits versionNo from the request and lets the server select its latest release. It does not infer or report a resolved semantic version for that archive. Publisher and skill names may contain Unicode and internal ASCII spaces; quote the complete package identity when it contains spaces.

Download and install behavior

The CLI uses the existing base URL resolution and precedence described under Configuration, derives the matching dashboard base, then requests a short-lived download URL:

GET /prod-api/skill-marketplace/public/skills/download-url?publisher=...&skillName=...[&versionNo=...]

Clientid is sent only to this Clink dashboard endpoint, never to the signed download URL. The signed URL is treated as a secret and is never printed. --timeout applies to network operations; signed package downloads use at least 300000 milliseconds so slower transfers can finish. --dry-run performs no network requests and makes no filesystem changes; it only prints the planned result. Results support JSON and pretty output. Per-agent statuses are linked, shared, copied, unchanged, or unsupported.

The installer accepts raw single-Skill files and ZIP packages:

  • A single-Skill download may be a raw UTF-8 SKILL.md file or a ZIP with SKILL.md at the archive root. Otherwise, archive-root directories that directly contain SKILL.md are selected. One selected directory is installed as the requested single Skill; two or more selected directories form a multi-Skill package using their directory names. Ordinary files and other directories are ignored.
  • When no archive-root Skill directory exists and there is exactly one top-level directory, the same selection applies inside that common wrapper. All multi-Skill child releases and Agent updates are committed atomically and rolled back together on failure.

Multi-skill results include multiSkill: true and a skills array containing each child skill's action, install path, backup path, and Agent results. Single-skill output remains unchanged.

After a successful committed install, when the download-ticket response includes skillId, the CLI best-effort reports the public download metric to the resolved dashboard base: POST /prod-api/skill-marketplace/internal/skills/{skillId}/metrics/public-download with {"source":"AGENT_CLI"}. Metrics failures do not roll back or fail the completed installation.

Managed skills live under ~/.agents/skills; each skill uses immutable entries in releases/ and a current pointer. Reinstalling a managed skill updates that pointer automatically; an identical skill is left unchanged. If a destination is not managed by clink-cli, installation stops unless --force is supplied. A forced replacement retains the previous content as a backup. A failed single-skill update rolls back to the previously active release; a failed multi-skill update rolls back every child changed by that command.

Only agent roots that already exist are detected; installing a skill does not create new agent homes. Integration modes are:

| Agent | Mode | Notes | | --- | --- | --- | | Cursor, Claude Code, Codex, CodeBuddy | link | Links the agent's skill entry to the central installation. | | Trae | link | Creates the symlink only after its real target is verified as an immutable release. | | OpenClaw, OpenCode, GitHub Copilot | shared | Uses the shared central skill root. | | Gemini CLI (GEMINI_CLI_HOME unset or HOME-equivalent) | shared | Uses the shared central skill root. | | Gemini CLI (GEMINI_CLI_HOME resolves outside HOME) | link | Links the custom home to the central installation. | | Hermes | copy | Copies the installed skill into its existing root. | | CodeWork, ChatGPT | unsupported | Reports a warning and continues. |

~/.trae/skills is Trae's documented global skill directory, but Trae has not confirmed that it discovers per-skill symlinks there. Before release, validate discovery with a real Trae installation; if that check fails, use a copy adapter for Trae instead.

Verification and safety

After installation, verify discovery in the target agent:

  • OpenCode: start a fresh process and check its native skill inventory.
  • GitHub Copilot: run /skills reload, then /skills info <skillName>.
  • Gemini CLI: run /skills reload, then /skills list.

Downloads must use HTTPS. The installer enforces download and extracted-size limits, computes a local SHA-256 package identity, and rejects unsafe ZIP entries and archive layouts. The SHA-256 is used to identify local content; it is not a claim of publisher authenticity. Validation, authentication, API, and network failures keep exit codes 2, 4, 5, and 6. Archive validation and local filesystem, lock, link, copy, or rollback failures use error type install_error and exit code 8.

Common Usage

Select sandbox (UAT) or test:

clink-cli wallet init --sandbox --email [email protected]
clink-cli card list --format pretty
clink-cli wallet init --test --email [email protected]
clink-cli card list --format pretty

--sandbox selects the UAT URLs and --test selects the .dev URLs defined in src/domains.ts. For authenticated commands, these flags are accepted by wallet init; OAuth credentials and the environment are saved together, and switching environments requires another initialization. Public ucp-catalog search/product, catalog search, and tool internal-ucp get-merchant-list also accept the flags per invocation and default to production. Those public actions never read the saved wallet configuration. Visa Edition visa product-search is also token-free, but follows the edition's fixed or saved environment unless --sandbox or --test explicitly selects a compatible environment.

Advanced custom endpoints remain supported through CLINK_BASE_URL. For wallet init, an explicit --sandbox / --test or a distribution-fixed environment takes precedence over CLINK_BASE_URL, then falls back to production. Later commands use CLINK_BASE_URL when present and otherwise use the saved URL. These public discovery actions intentionally ignore CLINK_BASE_URL and CLINK_WALLET_INIT_ENVIRONMENT so an invocation without an environment flag is always production.

Print requests without executing:

clink-cli pay \
  --merchant-id merchant_xxx \
  --amount 10.00 \
  --currency USD \
  --payment-instrument-id pi_xxx \
  --dry-run

Commands

Command groups:

  • npx @clink-ai/clink-cli@latest install [--force]
  • clink update [--check] [--force]
  • clink skills sync [--check] [--force]
  • clink-cli wallet init
  • clink-cli wallet logout
  • clink-cli wallet status
  • clink-cli card binding-link
  • clink-cli card setup-link
  • clink-cli card modify-link
  • clink-cli card passkey-link --payment-instrument-id <id>
  • clink-cli card list
  • clink-cli card get --payment-instrument-id <id>
  • clink-cli risk get
  • clink-cli risk link
  • clink-cli pay --merchant-id <id> --amount <amount> --currency <currency>
  • clink-cli pay --session-id <id>
  • clink-cli ucp-catalog search --merchant-id <id> --query <text> [--language <tag>] [--context <json>] [--filters <json>] [--cursor <cursor>] [--limit <n>] [--sandbox|--test]
  • clink-cli ucp-catalog product --merchant-id <id> --product-id <id> [--language <tag>] [--context <json>] [--filters <json>] [--sandbox|--test]
  • clink-cli catalog search --query <text> [--channel-type <type>] [--form-type <type>] [--ext <json>] [--language <tag>] [--context <json>] [--filters <json>] [--sandbox|--test]
  • clink-cli ucp-order get --order-id <id>
  • clink-cli ucp-order list [--status <statuses>] [--start-time <utc>] [--end-time <utc>] [--page <n>] [--size <n>]
  • clink-cli ucp-checkout run --merchant-url <url> --merchant-category-code <code> --currency <currency> --line-items <json> --confirm-purchase [--payment-instrument-id <id>] [--wait-delivery] [--max-wait <seconds>] [--endpoint <url>]
  • clink-cli ucp-checkout create --merchant-url <url> --merchant-category-code <code> --currency <currency> --line-items <json> [--endpoint <url>]
  • clink-cli ucp-checkout get --checkout-id <id> [--endpoint <url>]
  • clink-cli ucp-checkout update --checkout-id <id> --line-items <json> [--currency <currency>] [--endpoint <url>]
  • clink-cli ucp-checkout complete --checkout-id <id> [--payment-instrument-id <id>] [--endpoint <url>]
  • clink-cli ucp-checkout cancel --checkout-id <id> [--endpoint <url>]
  • clink-cli refund create --order-id <id>
  • clink-cli refund get --refund-id <id>
  • clink-cli events poll [--type <type[,type...]>] [--checkout-id <id>] [--no-ack]
  • clink-cli instruction list --valid-only --payment-instrument-id <id> (VIC; ACTIVE only, and one-time mandates are filtered to reserveStatus=0)
  • clink-cli instruction prepare --title <t> --mandates <json> [--max-wait <seconds>] (no-card PENDING; structured handoff then exact-ID final envelope)
  • clink-cli instruction create --payment-instrument-id <id> --title <t> --mandates <json> (VIC; returns instructionId + passkeyUrl)
  • clink-cli instruction get --purchase-instruction-id <id> (VIC)
  • clink-cli instruction sign-url --payment-instrument-id <id> --purchase-instruction-id <id> (VIC)
  • clink-cli instruction update|cancel (VIC page URL only)
  • clink-cli pay --session-id <id> --instruction-id <id> --mandate-id <id> [--shipping-address <json>] [--products <json>] (VIC charge context; --products maps to aiAgentInstructionBo.products)
  • clink-cli skills install <publisher>/<skillName>[@<version>] [--force]
  • clink-cli tool item-id --url <url>
  • clink-cli tool parse-site --url <url>
  • clink-cli tool parse-item --url <url>
  • clink-cli tool checkout-total --url <url>
  • clink-cli tool get-ucp-profile --url <url>
  • clink-cli tool get-rest-endpoint --url <url>
  • clink-cli tool internal-ucp get-endpoint --product-url <url>
  • clink-cli tool internal-ucp get-merchant-list [--sandbox|--test]
  • clink-cli config set <key> <value>
  • clink-cli config get
  • clink-cli config unset <key>

tool internal-ucp get-endpoint performs an exact product-hostname lookup against the merchant list for the selected environment and generates the corresponding Clink UCP REST endpoint. It loads the selected environment's anonymous GET /agent/ucp/merchants API; validated successes use a short per-process TTL cache, and concurrent callers share one in-flight request. Errors are not cached, and a cached hostname absence is refreshed before it can select external routing. The backend has already filtered the result to enabled merchants. The command uses the environment saved by wallet init; re-run initialization to switch environments. A domain outside that list returns {"error_code":"NOT_IN_INTERNAL_UCP_LIST"} with a successful process exit so callers can continue with standard UCP profile discovery. Conflicting merchant IDs for the target canonical hostname are a terminal API configuration error and never enter profile fallback; unrelated hostnames remain resolvable.

tool internal-ucp get-merchant-list is the public command for listing Clink UCP merchants. It sends an anonymous GET /agent/ucp/merchants request with no query or body. No environment flag means production, --sandbox means sandbox/UAT, and --test means test. The selected URL is derived from API_BASE_URLS in src/domains.ts. The command ignores ~/.clink-cli/config.json, CLINK_BASE_URL, the wallet distribution environment, OAuth, CSK, customer ID, and customer API key credentials.

The backend response is returned as {"merchants":[...]} after validation. It contains enabled merchants with these public fields:

{
  "merchants": [
    {
      "merchant_id": "mcht_xxxxxxxxxxxx",
      "merchant_name": "Example Store",
      "description": "What the storefront sells and which product categories it covers.",
      "domain": "https://shop.example.com",
      "ext": { "source": "merchant-config" }
    }
  ]
}

ext is opaque JSON copied from the merchant configuration and is never used for merchant identity or endpoint routing. During a rolling deployment, an older backend may omit it. domain is a safe absolute HTTP(S) merchant route URL, not a bare hostname. It may retain a route path such as https://vtravel.link2shops.com/yiyuan/; userinfo, query, and fragment are forbidden, while dot segments and default ports are normalized. Internal endpoint resolution uses only its canonical hostname and requires an exact match with the product URL hostname. The returned Clink endpoint is independently generated from the effective wallet API base and merchant_id; the merchant route path is never appended to that endpoint. Invalid rows are skipped independently, but a non-empty array with no valid merchant identity is rejected. The anonymous read-only GET retries transport failures, 408, 429, and 5xx once with short backoff inside one total timeout; other HTTP or response-contract failures are API errors (exit code 5), and exhausted transport/timeouts are network errors (exit code 6). Neither is reported as a missing merchant.

Show help:

clink-cli --help
clink-cli wallet --help
clink-cli card --help
clink-cli ucp-catalog --help
clink-cli catalog --help
clink-cli ucp-checkout --help
clink-cli ucp-order --help
clink-cli refund --help
clink-cli events --help

Configuration

Useful config commands:

clink-cli config get
clink-cli config set base-url https://api.clinkbill.com
clink-cli config set customer-id cus_xxx
clink-cli config unset customer-api-key

config get and wallet status never print raw OAuth tokens or the raw customer API key. OAuth credentials take precedence over legacy CSK, and a request never sends both. wallet status makes no network request and reports the effective base URL and customer credentials after applying command flags, environment variables, and saved config in precedence order. It also reports authorizationEnvironmentMatches and the persistent oauthRequired state. After a wallet first uses OAuth, logout or token expiry requires wallet init; the CLI never falls back to stored, environment, or command-line CSK credentials.

Changing base-url to a different origin clears the saved OAuth and legacy CSK credentials before the new environment can be used.

The first OAuth release stores tokens as plaintext JSON under the current user's home directory. On Unix/macOS the CLI keeps ~/.clink-cli at mode 0700, config.json at mode 0600, and replaces the file atomically under a lock. OS Keychain integration is deferred.

Settable config keys:

  • base-url
  • customer-id
  • default-open-links
  • email
  • name

customer-api-key cannot be stored with config set. An existing saved legacy key can still be removed with config unset customer-api-key; never-OAuth legacy users may continue supplying it through CLINK_CUSTOMER_API_KEY or --customer-api-key. customer-id is directly settable only for never-OAuth legacy wallets; OAuth owns the customer identity and wallet init is the only way to switch it.

Resolution order:

  • command flags
  • environment variables
  • saved local config

Environment variables:

  • CLINK_BASE_URL
  • CLINK_CUSTOMER_ID
  • CLINK_CUSTOMER_API_KEY

Global Options

  • --format <json|pretty>
  • --dry-run
  • --open
  • --no-open
  • --no-watch
  • --customer-id <id>
  • --customer-api-key <key>
  • --timeout <ms>
  • --help

--sandbox and --test are command-scoped options for wallet init, visa init, ucp-catalog search, and Visa Edition visa product-search; they are not global options.

Event Watching

Browser-link commands can consume the webhook-event queue while waiting for the user action to finish. Most commands print their first envelope and then start polling. card binding-link is the readiness-gated exception: it withholds both the binding handoff and its handoff logs until the first successful, well-formed Event Hub poll, then reports watchReady: true as described above. instruction prepare is also readiness-gated, but its first stdout envelope is a structured PENDING handoff emitted after the first successful exact-ID poll; the same process later prints the final exact-ID result.

  • The built-in watcher polls POST /agent/event-hub/webhook-events/poll (pageSize: 20) every 5 seconds for up to 15 minutes. An unscoped watcher stops at the first non-empty batch; a scoped watcher continues until its target is present or it times out.
  • It logs event summaries to stderr and updates wallet-derived local state where applicable. payment_method.* events refresh cached payment methods in ~/.clink-cli/config.json; other event payloads are returned in the command output but are not persisted in the config file.
  • ACKs are sent through POST /agent/event-hub/webhook-events/ack with { "eventIds": [...] }; acked events are removed server-side. Existing watchers retain their prior consumption behavior. The scoped card binding-link watcher is stricter: Event Hub selects payment_method.added before pagination, the CLI ACKs only a matching event, and it preserves any nonmatching current or stale record returned defensively.
  • The terminal watch result is printed as a second JSON envelope to stdout; progress messages go to stderr.

Pass --no-watch to skip the built-in poll for scripted or non-interactive runs. For card binding-link, the immediate envelope explicitly carries watchReady: false and watchEventType: null, and stderr identifies the missing listener. Polling is also skipped under --dry-run. instruction prepare intentionally rejects --no-watch because its binding handoff must never outlive the foreground exact-ID listener.

Polling on demand: events poll

To consume state changes without printing a link, call clink-cli events poll. It polls the webhook-event queue within a bounded window (default --max-wait 60 seconds, --limit 20 page size), processes each event, updates wallet-derived state where applicable, and acknowledges consumed records by eventId.

clink-cli events poll --type payment_method.added --format json
clink-cli events poll \
  --type payment_method.update,vic_device.binding_succeeded \
  --payment-instrument-id pi_123 \
  --format json
clink-cli events poll \
  --type agent_order.succeeded \
  --checkout-id checkout_123 \
  --format json

The result is { ready, timedOut, events, ackedEventIds, nextToken? }. On timeout it also returns resumeCommand. Ordinary polls need no cursor because ACKed events are removed server-side. A Checkout-scoped timeout may additionally return the opaque nextToken; the generated resumeCommand carries it automatically. payment_method.* events refresh cached payment methods; other event payloads are not stored in config.json.

With --type, events contains only the selected exact type. A comma-separated list such as --type account-created,account-reloaded waits for any listed type. Every record read is still processed; unselected records are acknowledged and skipped so an older page cannot block the target. Selected records are acknowledged by default. Typed --no-ack keeps selected records queued but still acknowledges unselected records; only an untyped --no-ack poll peeks without acknowledging anything. Consequently, ackedEventIds can include IDs absent from events. If the original poll used CLINK_BASE_URL, resumeCommand preserves that override.

--payment-instrument-id requires --type and is mutually exclusive with --checkout-id. It correlates card/VIC events through canonical payload aliases or resourceId; same-type events for another card remain unacknowledged. Use it for VIC readiness as an any-of wake-up signal, then refresh the exact card and require visaRegistrationSucceeded=true.

--checkout-id is a strict selector for one UCP checkout. It requires a nonblank ID and exactly one type: agent_order.succeeded or agent_order.failed. In this mode the poll request sends the selector to Event Hub before pagination:

{
  "pageSize": 20,
  "eventTypes": ["agent_order.succeeded"],
  "selectors": { "checkoutId": "checkout_123" }
}

The CLI verifies the response locally using payload data.checkoutId / data.checkout_id or the UCP path requestParams.extra.agentInstructionInfo.ucpCheckoutId (also accepted beneath data for compatibility). Event Hub returns nextToken after each scanned page, and the CLI sends it on the next request so unacknowledged events for another Checkout cannot permanently hide later records. It never falls back to resourceId, data.orderId, or a payment order ID. A missing ID, a different checkout, a malformed nested path, or conflicting aliases fails closed. A full page without nextToken fails explicitly instead of polling the same page forever. Only the locally verified exact match is returned and ACKed by default; every other Checkout event remains queued. With --no-ack, the exact match also remains queued. A timeout resumeCommand preserves --checkout-id, the opaque --next-token scan cursor, acknowledgement, and environment options.

For a completed UCP checkout, keep the identifiers separate: use the captured checkout ID for this event selector, and use order.id from the checkout-complete response as the OMS/UCP order ID for clink-cli ucp-order get --order-id <oms_order_id>. The order_* values in an agent_order.* event's resourceId, data.orderId, or data.paymentOrderId are Clink Payment order IDs and must not be passed to ucp-order get.

Local Development

Build from source:

npm install
npm run build
node dist/index.js --help

Run tests:

npm test
npm run test:e2e