@clink-ai/clink-cli
v0.2.0
Published
TypeScript CLI for Clink customer wallet APIs
Readme
clink-cli
clink-cli is a command-line client for Clink customer wallet APIs.
Install
Install globally:
npm install -g @clink-ai/clink-cliRun without installing globally:
npx @clink-ai/clink-cli --helpPublished package name:
@clink-ai/clink-cliInstalled commands:
clink-cli
clinkclink is a shorter alias that invokes the same CLI as clink-cli.
Requirements:
- Node.js
>=20 - Access to the target Clink API environment
Default base URL:
https://api.clinkbill.comQuick 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] --name "Alice"For sandbox, run wallet init with --sandbox. The CLI prints the browser URL and attempts to open
it automatically. 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; --name is
only used when creating a new customer and is always saved to local config.
The browser URL keeps only user_code in the query string. The supplied email and 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] --name "Alice"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.
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.
Check the saved local config:
clink-cli wallet status --format prettyLog out and remove local credentials:
clink-cli wallet logoutThe local config file is stored at:
~/.clink-cli/config.json2. Open card pages
Get the raw binding link:
clink-cli card binding-linkOpen the add-card page:
clink-cli card setup-link --openOpen the manage-card page:
clink-cli card modify-link --open3. Check saved payment methods
List cached payment methods:
clink-cli card list --format prettyGet one cached payment method:
clink-cli card get --payment-instrument-id pi_xxxNotes:
- card add/update/delete happens on the web page, not in the CLI
card listandcard getread local cached datacard binding-link,card setup-link, andcard modify-linkrefresh 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_xxxPay with session mode:
clink-cli pay --session-id sess_xxx --payment-instrument-id pi_xxxVIC-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, pay uses the default cached payment method. --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.
List all public skills
clink-cli skills list --all --format pretty
clink-cli skills list --all --tippable --format prettyThe 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 2Skill 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. Manage UCP checkout for shadow merchants
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; the CLI converts them by --currency before calling the external checkout API.
The API body sends customer_id and buyer.email from the local clink-cli config get --format json
data, and sends --currency as context.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":1200},"quantity":1}]'
clink-cli ucp-checkout complete --checkout-id chk_xxx
clink-cli ucp-checkout cancel --checkout-id chk_xxxBy 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 noexternalsubcommand - CLI-discovered merchants use the shadow-merchant external checkout path internally
- create sends
merchant_url,customer_id,buyer.email, andcontext.currency; the backend resolves authorization andorder_channel_id - complete sends a standard UCP
payment.instrumentsobject; the selected instrumentidis local configcustomerId#paymentInstrumentId,credential.tokenis the payment instrument ID, and when--payment-instrument-idis omitted or empty it uses the cached default card Idempotency-Keyis 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
6. Refund an order
Create a full refund:
clink-cli refund create --order-id order_xxxCheck refund status:
clink-cli refund get --refund-id rfd_xxx7. Check risk rules
Get current risk rule settings:
clink-cli risk get --format prettyOpen the risk rule page:
clink-cli risk link --openInstall agent skills
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 prettyThe 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.mdfile or a ZIP withSKILL.mdat the archive root. Otherwise, archive-root directories that directly containSKILL.mdare 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
Use sandbox:
clink-cli wallet init --sandbox --email [email protected] --name "Alice"
clink-cli card list --sandbox --format pretty--sandbox switches the API base and agent domain. OAuth credentials are environment-bound; run
wallet init --sandbox before making sandbox calls. Legacy CSK is available only to wallets that
have never used OAuth.
Override the base URL:
clink-cli wallet status --base-url https://api.clinkbill.comPrint requests without executing:
clink-cli pay \
--merchant-id merchant_xxx \
--amount 10.00 \
--currency USD \
--payment-instrument-id pi_xxx \
--dry-runCommands
Command groups:
clink-cli wallet initclink-cli wallet logoutclink-cli wallet statusclink-cli card binding-linkclink-cli card setup-linkclink-cli card modify-linkclink-cli card listclink-cli card get --payment-instrument-id <id>clink-cli risk getclink-cli risk linkclink-cli pay --merchant-id <id> --amount <amount> --currency <currency>clink-cli pay --session-id <id>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> [--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 instruction list --valid-only --payment-instrument-id <id>(VIC; ACTIVE only, and one-time mandates are filtered toreserveStatus=0)clink-cli instruction create --payment-instrument-id <id> --title <t> --mandates <json> [--sandbox](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> [--sandbox](VIC)clink-cli instruction update|cancel [--sandbox](VIC page URL only)clink-cli pay --session-id <id> --instruction-id <id> --mandate-id <id> [--shipping-address <json>] [--products <json>](VIC charge context;--productsmaps toaiAgentInstructionBo.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> [--sandbox]clink-cli config set <key> <value>clink-cli config getclink-cli config unset <key>
tool internal-ucp get-endpoint performs an exact product-hostname lookup in the selected
environment's bundled internal-merchant configuration and generates the corresponding Clink UCP
REST endpoint without a network request. Production configuration is selected by default;
--sandbox selects sandbox configuration. A domain outside the selected list returns
{"error_code":"NOT_IN_INTERNAL_UCP_LIST"} with a successful process exit so callers can continue
with standard UCP profile discovery.
Show help:
clink-cli --help
clink-cli wallet --help
clink-cli card --help
clink-cli ucp-checkout --help
clink-cli refund --helpConfiguration
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-keyconfig 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-urlcustomer-iddefault-open-linksemailname
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_URLCLINK_CUSTOMER_IDCLINK_CUSTOMER_API_KEY
Global Options
--format <json|pretty>--dry-run--open--no-watch--base-url <url>--sandbox--customer-id <id>--customer-api-key <key>--timeout <ms>--help
Event Watching
Whenever a command prints a link for the user to act on in a browser
(card binding-link, card setup-link, card modify-link, risk link,
instruction create|sign-url|update|cancel, and pay when a
3-D Secure redirect is returned), the CLI starts consuming the webhook-event queue:
- Polls
POST /agent/event-hub/webhook-events/poll(pageSize: 20) every 5 seconds for up to 15 minutes, stopping at the first non-empty batch. - Processes the events it receives: logs a summary 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. - Acknowledges the batch via
POST /agent/event-hub/webhook-events/ackwith{ "eventIds": [...] }; acked events are removed server-side. - Prints the processed events as a second JSON envelope to stdout (stdout keeps carrying machine-readable output; progress messages go to stderr).
Pass --no-watch to print the link and exit immediately, without polling — use
this for scripted or non-interactive runs, and whenever you call card
binding-link only to refresh the cached card list. Polling is also skipped
under --dry-run. Newly added event types that aren't recognized yet are still
logged and acknowledged so they are never silently dropped.
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 — unless --no-ack is passed — acknowledges it by
eventId.
clink-cli events poll --type payment_method.added --format jsonThe result is { ready, timedOut, events, ackedEventIds }. On timeout it also
returns resumeCommand: because acked events are removed server-side, resuming is
just the same command again — there is no offset to pass. A single poll returns the
whole batch it reads in events. payment_method.* events refresh cached payment
methods; other event payloads are not stored in config.json. --type controls
both when to stop waiting and which events are acked: only events matching type
are acknowledged, so unrelated events stay on the queue for a later poll. Without
--type, the whole batch is acked. Pass --no-ack to peek without acknowledging
anything.
Local Development
Build from source:
npm install
npm run build
node dist/index.js --helpRun tests:
npm test
npm run test:e2e