one

GitHub

通过 One CLI 统一调用 750+ 第三方平台 API,涵盖 Gmail、Slack 等。支持认证管理、连接查询及跨平台自动化工作流执行,简化外部服务集成。

skills/one/SKILL.md withoneai/cli

Trigger Scenarios

与任何第三方平台或外部服务交互 列出已连接的平台或检查可用平台 搜索可用的操作或动作 执行已连接平台的 API 调用 设置基于 webhook 的跨平台自动化 构建跨平台的多步骤工作流

Install

npx skills add withoneai/cli --skill one -g -y
More Options

Use without installing

npx skills use withoneai/cli@one

指定 Agent (Claude Code)

npx skills add withoneai/cli --skill one -a claude-code -g -y

安装 repo 全部 skill

npx skills add withoneai/cli --all -g -y

预览 repo 内 skill

npx skills add withoneai/cli --list

SKILL.md

Frontmatter
{
    "name": "one",
    "description": "Use the One CLI (`one`) to interact with 3rd-party platforms — Gmail, Slack, Stripe, Notion, etc. through their APIs. One handles auth, request building, and execution.\n\nTRIGGER when:\n- Interact with ANY 3rd-party platform or external service (e.g., \"send an email\", \"create a Shopify order\", \"find a HubSpot contact\", \"post to Slack\")\n- List their connected platforms or check available ones\n- Search for available actions (e.g., \"what can I do with Gmail\")\n- Execute API calls with a connected platform\n- Set up webhook-driven automations between platforms (e.g., \"when a Stripe payment comes in, notify Slack\")\n- Build multi-step workflows that chain actions across platforms (e.g., \"fetch Stripe customers and email each one\")\n- Anything involving 3rd-party APIs, integrations, or connected apps — even if they don't mention \"One\" by name\n\nDO NOT TRIGGER for:\n- Setting up One or installing MCP (use `one init`)\n- Adding new connections (use `one add <platform>`)\n- Configuring access control (use `one config`)"
}

One CLI

You have access to the One CLI which lets you interact with 750+ third-party platforms through their APIs. Always include the --agent flag right after one for structured JSON output.

If the user wants a separate API key / connections for a specific project (vs. their default), walk them through running one init from that project folder and picking the "project" scope — see references/scoping.md. For monorepo subprojects (where a parent already has .git/package.json), have them mkdir .one in the subproject first so the config is keyed to that dir, not the monorepo root.

Authentication

one login                    # Browser-based login (opens app.withone.ai)
one logout                   # Clear local credentials

one login opens the browser for OAuth authentication and automatically creates and stores an API key. If already logged in, the user can choose to log in globally or for the current directory. one logout shows current session info and confirms before clearing credentials.

The consent page asks which harness will use the key (Claude Code, Codex, Cursor, …) and records the install location as tags on the key (scope:global / scope:project, path:, host:, user:, harness:, and device: unless telemetry is off). The CLI prints what it will send before opening the browser. one whoami shows the key's name once it is stored.

Onboarding a user with no prompts: run one init --auth browser — it opens a login window (the user authenticates there), saves the key, and auto-installs this skill, all without blocking on stdin. Add -g/-p for scope (default global). For CI/CD or headless environments, use one init --auth manual --api-key sk_live_....

Core Workflow: search -> knowledge -> execute

Always follow this sequence when the user wants to do something on a connected platform:

1. List connections

one --agent connection list

Returns connected platforms with their connection keys (needed for execution), platform names in kebab-case (needed for searching), and an access field per connection telling you what you may run there.

Read access before you plan a workflow — it saves you from discovering a restriction as a 403 halfway through:

access What it means
{"policy": "full"} Every action on this connection is available
{"policy": "methods", "methods": ["GET"]} Only actions with these HTTP methods will execute — don't propose writes
{"policy": "actions", "actions": [...]} Only these exact actions may run. Each has actionId, title, methoduse them directly and skip actions search

Two more fields appear only when relevant:

  • "knowledgeOnly": trueactions execute is disabled. Read knowledge and write integration code instead of executing.
  • "unresolvedActionIds": [...] — allowlisted ids that couldn't be looked up; treat them as unavailable and tell the user.

An empty actions array means the allowlist grants nothing on that connection — say so rather than searching for alternatives.

1b. Delete a connection

one --agent connection delete <connection-key>

Removes a connection. Returns {"deleted": true, "platform": "...", "key": "..."} on success. Use the connection key from one --agent connection list.

2. Search for the right action

one --agent actions search <platform> "<query>" -t execute
  • Platform names are lowercase; multi-word names use dashes: gmail, hubspot, ship-station, google-calendar
  • Use -t execute when performing actions, -t knowledge when researching or writing code
  • If no results, broaden the query (e.g., "list" instead of "list active premium customers")

3. Get the action's knowledge (REQUIRED before executing)

one --agent actions knowledge <platform> <actionId>

This tells you exactly what parameters are required, how to structure the request, and which flags to use. Never skip this step — without it you'll guess wrong on parameters.

You get a digest, not always the whole document. Large docs are trimmed to the request-building sections (method/URL, headers, description, rules, required + optional parameters, sample request, gotchas, error handling). The response tells you what was left out:

  • truncated: true — some sections are omitted. truncated: false — you have everything (and no sections list is sent).
  • sections[] — the omitted sections only, each with id, heading, chars, and included (false / "partial"). Included sections are the headings you can see in the markdown.
  • The markdown itself ends with a notice naming the omitted sections and the exact commands to load them.

Load more only when you need it (response shapes, response fields, worked examples). Served from the local cache, no network:

one --agent actions knowledge <platform> <actionId> --section "Response Fields"   # by heading
one --agent actions knowledge <platform> <actionId> --section response,optional    # by alias, several at once
one --agent actions knowledge <platform> <actionId> --full                         # whole document

Aliases: response, fields, optional, required, examples, errors, success, body, query, path, notes, behavior, gotchas. An unknown name returns an error listing every available section — retry with one of those ids.

  • A --section response has truncated: false (you got the whole section) and resolved (which ids your names matched). It does not repeat the table of contents.
  • If the digest says sectionsCollapsed: true, the doc has hundreds of headings and sections shows only the top omitted levels (children = hidden count). --toc lists every heading without the document.
  • Sections named (appendix) in the notice are appended reference chunks or companion endpoints, not the action doc itself.

4. Execute

one --agent actions execute <platform> <actionId> <connectionKey> [options]

Options:

  • -d, --data <json> — Request body (POST, PUT, PATCH)
  • --path-vars <json> — Path variables for URLs with {id} placeholders
  • --query-params <json> — Query parameters
  • --headers <json> — Additional headers
  • --form-data — Send as multipart/form-data
  • --form-url-encoded — Send as application/x-www-form-urlencoded
  • --dry-run — Preview the request without executing
  • --mock — Return example response without making an API call (useful for building UI)
  • --skip-validation — Skip input validation against the action schema
  • --output <path> — Save response to a file (for binary downloads like PDFs, images, documents). Text responses (text/plain, HTML, CSV, XML) render inline automatically; --output is only needed for genuinely binary payloads.
  • --no-cache — Bypass the cached action details and re-fetch them; the fresh details still refresh the cache (execution itself is never cached)

The CLI validates required parameters before executing. Missing params return a structured error with the flag name, parameter name, and description. Pass --skip-validation to bypass.

Examples:

# Simple GET
one --agent actions execute shopify <actionId> <connectionKey>

# POST with body data
one --agent actions execute hubspot <actionId> <connectionKey> \
  -d '{"properties": {"email": "jane@example.com", "firstname": "Jane"}}'

# Path variables + query params
one --agent actions execute shopify <actionId> <connectionKey> \
  --path-vars '{"order_id": "12345"}' \
  --query-params '{"limit": "10"}'

# Array query params (expand to repeated keys)
one --agent actions execute gmail <actionId> <connectionKey> \
  --path-vars '{"userId": "me", "id": "msg123"}' \
  --query-params '{"format": "metadata", "metadataHeaders": ["From", "Subject", "Date"]}'

Parallel execution

Execute multiple actions concurrently with --parallel, separating each action with --:

one --agent actions execute --parallel \
  gmail send-email conn123 -d '{"to":"a@b.com"}' \
  -- slack post-message conn456 -d '{"text":"done"}'

All segments are validated before any execution. Failed actions don't block others. Use --max-concurrency <n> (default 5) to control batching. Agent-mode output: {"parallel":true,"results":[...],"succeeded":N,"failed":N,"totalDurationMs":N}. Each result carries "_preflight":{"cache":"hit"|"miss"} showing whether that action's details were served from cache.

Error Handling

All errors return JSON: {"error": "message"}. Parse output as JSON and check for the error key.

Important Rules

  • Always use --agent flag for structured JSON output
  • Platform names are lowercase; multi-word names use dashes (hubspot not HubSpot, google-calendar not googleCalendar)
  • Always use the exact action ID from search results — never guess or construct them
  • Always read knowledge before executing — it has required params, validation rules, and caveats
  • JSON values passed to -d, --path-vars, --query-params must be valid JSON (use single quotes around JSON to avoid shell escaping)
  • Do NOT pass path or query parameters inside the -d body flag

Caching

Knowledge and search responses are cached locally (~/.one/cache/). Subsequent calls for the same action serve instantly from disk. actions execute reuses the cached action details for its preflight lookup, so after a knowledge call (or a prior execute of the same action) it makes a single API call — the action itself.

  • Cache is automatic — no setup required
  • Default TTL: 1 hour (configurable via ONE_CACHE_TTL env var)
  • In --agent mode, responses include a _cache field: {"hit": true, "age": 1423, "fresh": true}; execute responses include "_preflight": {"cache": "hit"|"miss"}
  • Use --no-cache to force a fresh fetch: works on knowledge, search, and execute (refreshes execute's action-details lookup)
  • Use --cache-status to check cache state without fetching
  • knowledge --section <name> and --full read from the cached document — no extra API call once the doc is cached
  • Manage cache: one cache list, one cache clear, one cache update-all
  • Execution responses are NEVER cached — the action always runs live; only action metadata (docs, method, path, schema) is cached

Unified Memory

One ships a local memory store (a real Postgres process bootstrapped on demand via the bundled embedded-postgres plugin, with a postgres plugin available for remote/self-hosted Postgres) that backs both user-authored notes and synced platform data. one mem <cmd> is the primary surface; one sync is a namespaced alias (one mem sync ...) that writes synced rows into the same store.

Zero-config. The first one mem call on a new machine auto-initializes — no separate mem init step required. The embedded-postgres plugin downloads its Postgres binaries on first run (~52MB) and writes a daemon PID/port file at ~/.one/pg/.pgserve.json so subsequent CLI invocations reuse the running cluster. If an OpenAI key is already resolvable (env, .onerc, or config.openaiApiKey), embeddings enable automatically and search becomes hybrid FTS + semantic. Otherwise you get FTS-only with a structured _upgrade hint on every response telling the user how to upgrade.

Listing synced rows. mem list <type> takes a positional namespaced type — there is no --platform flag. Synced rows live under <platform>/<model> types:

one --agent mem list "gmail/threads"
one --agent mem list "attio/attioPeople" --limit 5
one --agent mem list "google-calendar/events"

Underneath, the store has no platform column — type is the only platform-scoping mechanism. If you need raw SQL via mem sql, filter with WHERE type LIKE 'platform/%' (not WHERE platform = ...).

# User memories
one --agent mem add note '{"content":"..."}' --tags work --weight 7
one --agent mem update <id> '{"status":"done"}'         # merges into data; refreshes searchable_text
one --agent mem search "deadline"                       # hybrid if key set, else FTS
one --agent mem list note --limit 20
one --agent mem link <from-id> <to-id> relates_to --bi

# Merge keys — the `keys[]` column (first-class, NOT data). Unique across ACTIVE
# records; archiving a record frees its keys. `mem update '{"keys":[...]}'` is rejected.
one --agent mem key <id> --add email:x@y.com            # add/--remove/--set — EDITS keys[]
one --agent mem find-by-source attio/attioPeople:abc-1  # ONE record (prefers the active owner)

# Identity keys — cross-platform lookup. READ-ONLY query, does not edit anything.
# Spans BOTH `keys[]` (record IS the entity) and `identity_keys[]` (record INVOLVES
# the entity: Gmail thread From/To/Cc, calendar attendees — never merges).
one --agent mem find-by-key email:jane@acme.com                # every record involving this person
one --agent mem find-by-key email:jane@acme.com --type gmail/gmailThreads
one --agent mem find-by-key email:a@x.com email:b@y.com        # intersection — records with BOTH

# Backfill searchable_text (no embedding provider needed) — fixes NULL/noisy text
one --agent mem reindex --searchable --type attio/attioPeople

# Status + diagnostics
one --agent mem status                                  # backend, provider, _upgrade hint
one --agent mem doctor                                  # full health report

Don't confuse mem key with mem find-by-key. mem key WRITES the merge column (keys[]) on one record — adding a key another active record already owns is an error, and --set replaces the whole array. mem find-by-key only READS, across both key columns. If you want "show me everything about this person", you always want find-by-key.

find-by-key agent output is grouped by record type:

{
  "keys": ["email:jane@acme.com"],
  "total": 13,
  "truncated": false,
  "fetchCap": 2000,
  "perTypeLimit": 10,
  "byType": {
    "attio/attioPeople": { "count": 1,  "items": [ {"id": "...", "type": "...", "data": {}, "keys": ["attio/attioPeople:J1", "email:jane@acme.com"], "updated_at": "..."} ] },
    "gmail/gmailThreads": { "count": 12, "items": [] }
  }
}

items are whole mem records. keys and identity_keys are OMITTED, not [], when the record has none — the contact above matched on keys[] and so carries no identity_keys field at all. Always read them as (item.identity_keys ?? []).

Read it in this order:

  1. truncated — if true, more than fetchCap (2000) records matched. total and every count are then floors, and because rows come back ordered by type, whole types sorting after the cut are MISSING from byType entirely. Re-run with --type <type> to get an accurate answer; do not report the counts as-is.
  2. total / count — ungrouped and per-type match counts (accurate when truncated is false).
  3. items — whole mem records, same fields as mem get, capped at perTypeLimit (--limit, default 10). count > items.length just means display truncation — raise --limit.
  4. keys — the key form that actually matched. Lookups are lowercased/trimmed first (matching how sync writes them), with a one-shot verbatim retry for hand-written mixed-case keys, so email:Jane@Acme.com finds email:jane@acme.com.

Adding OpenAI for semantic search

Stored at the top level of ~/.one/config.json as openaiApiKey, same precedence as ONE_SECRET (env > .onerc OPENAI_API_KEY=... > project > global). Three equivalent ways to set:

# Via re-run of one init (interactive prompt)
one init

# Via config set (writes to top-level, not the memory block)
one --agent mem config set embedding.apiKey sk-...

# Via env var (no persistence)
export OPENAI_API_KEY=sk-...

Syncing platforms into memory

# Check built-in profiles (pre-validated configs for common platforms)
one --agent sync profiles

# Setup — seeds from the built-in, merges your --config overrides
one --agent sync init stripe balanceTransactions
# If _complete: true and _test.ok: true → ready to run

# Preview what gets embedded BEFORE paying embedding cost (agent declares paths)
one --agent sync init attio attioPeople --config '{
  "memory": {
    "embed": true,
    "searchable": [
      "values.name[0].full_name",
      "values.job_title[0].value",
      "values.description[0].value",
      "values.email_addresses[0].email_address"
    ]
  }
}'
# Skip the "pick paths by reading knowledge" step — let the CLI rank them from a live sample
one --agent sync suggest-searchable attio/attioPeople
# → { suggestions: [{path, score, hitRate, avgLength, noiseFraction, sampleValue}], configPatch: {...paste-ready...} }

one --agent sync test attio/attioPeople --show-searchable
# → Previews across 5 samples. Each path has { hits, total, sample }:
#     5/5 = path resolves on every record (clean)
#     1/5 = field is real but sparse on this page
#     0/5 = typo, or field never populated in sampled records
# Iterate until the numbers match intent.

# Run — memory is always written; pass --no-memory to skip (rare)
one --agent sync run stripe
one --agent sync schema stripe/customers         # inspect field paths/types before querying
one --agent sync query stripe/balanceTransactions --where "status=available" --limit 20
one --agent sync search "refund"                 # hybrid across all synced platforms
one --agent sync list stripe                     # progress + freshness

# Schedule unattended syncs
one sync schedule add stripe --every 1h

memory.searchable paths

Declared on the profile, drives what gets embedded + FTS-indexed. Supports numeric indexes AND [] wildcards for array fan-out:

values.name[0].full_name              # numeric index (first element)
messages[].snippet                    # wildcard — every element's .snippet
messages[].payload.parts[].body.data  # nested wildcards

Without declared paths, the default walker concatenates every string in the record — correct but often noisy for hierarchical APIs (Attio, HubSpot). Always declare paths for any profile with embed: true.

Sync rejects custom actions — profiles must use passthrough. sync init only surfaces passthrough models; sync run aborts if the list or enrich action is tagged custom. If no passthrough exists, compose a flow instead.

Connections are late-bound — profiles use "connection": { "platform": "<name>" }, not literal connectionKey strings. The key is resolved at sync time, so one add <platform> (re-auth) doesn't break the profile. For multi-account platforms, add "tag": "<connection-tag>" to disambiguate, and create the tagged connection with one add <platform> --tag <name>. Don't hardcode connection keys in profiles.

Extracting a flat field? Use derive, not transform. derive computes top-level fields from paths already in the record ("derive": { "from_email": { "path": "messages[0].payload.headers[name=From].value", "extract": "email" } }), using the same path syntax as identityKeys. transform spawns sh -c, so it needs jq on PATH and silently does nothing on Windows — never put one in a profile you intend to share. A path that resolves to nothing omits the field rather than writing null.

Installed profiles do not auto-update. sync run reads only .one/sync/profiles/<platform>_<model>.json and never merges the shipped built-in, so a profile created before a capability shipped silently lacks it — a pre-#167 gmail profile writes zero identity keys, forever, with no change in record counts. sync run warns when the built-in declares identityKeys / identityKey / enrich / dateFilter / memory that your copy lacks (agent mode: a profileDrift array). Fix with one sync init <platform> <model>, which patches rather than overwrites.

Cross-platform identity on a profile. Two separate fields, and picking the wrong one silently mangles data:

  • "identityKey": "properties.email" — singular. "This record IS this entity." One dot-path; the value lands in keys[] and MERGES records for the same entity across platforms (HubSpot + Attio for one person collapse into a single record).
  • "identityKeys": [{"prefix": "email", "path": "attendees[].email"}] — plural. "This record INVOLVES these people." Paths support [] wildcards and a [name=From] equality filter (Gmail headers). Values land in the separate identity_keys[] column, which does NOT merge — a 20-attendee event stays one event, not 20 contacts. Use this for anything with N participants.

Both are queryable with one --agent mem find-by-key <prefix>:<value>.

Enriching profiles (gmail/gmailThreads, fathom/meetings) sync in two phases: a list pass, then a detail pass that fetches full bodies/transcripts. Two things follow. Enrichment happens once per record by default — phase 2 only visits rows it has never enriched, and --full-refresh does not reset that (it reconciles deletions, it is not a detail refresh). To refresh detail content, either set enrich.invalidateOn in the profile to a list field that moves when the detail changes (historyId, updated_at) so only genuinely-changed records re-enrich automatically, or run one sync run <platform> --re-enrich to re-fetch every detail endpoint. And the list pass never overwrites an enriched record: data merges rather than replaces, and searchable_text / identity_keys[] are left alone. sync run reports these as memPreserved. So on an enriching profile without invalidateOn, a record whose upstream detail changed will look stale until you re-enrich — that is expected, not a sync failure.

Advanced features (enrich, transform, exclude, hooks, --full-refresh, alternative backends, embedding tuning): run one guide memory or one guide sync for the full reference.

Beyond Single Actions

One also supports more advanced patterns. Read the relevant reference file before using these:

  • Webhook Relay — Receive webhooks from a platform and forward to another (e.g., Stripe event -> Slack message). Read references/relay.md in this skill's directory for the full workflow.
  • Multi-step Workflows — Chain actions across platforms as JSON workflow files (like n8n/Zapier but file-based). Read references/flows.md in this skill's directory for the schema and examples. To debug: flow execute <key> --dry-run (resolve interpolations without running), --stop-after <stepId> (run up to a step then stop), and flow inspect <runId> (a past run's per-step outputs).

Adding New Connections

If the user needs a platform that isn't connected yet, tell them to run:

one add <platform>
one add <platform> --tag <name>   # tag it (for multiple connections per platform)

This is interactive and opens the browser for OAuth. After connecting, the platform will appear in one --agent connection list. Use --tag when the user has (or will have) more than one connection for the same platform so sync/flow profiles can target a specific one via "connection": { "platform": "<name>", "tag": "<name>" }.

Removing Connections

To delete a connection that is no longer needed:

one --agent connection delete <connection-key>

The connection key comes from one --agent connection list. Returns {"deleted": true, "platform": "...", "key": "..."} on success.

Version History

  • 1b44d70 Current 2026-09-22 09:27

    v1.57.0: 按标题对动作文档进行分区,并向代理提供摘要;v1.56.1: 修复登录提示在终端宽度内显示的问题;v1.56.0: 向浏览器同意页面发送安装上下文信息。

  • d648b92 2026-08-16 07:34

    更新用户界面文案,将支持的第三方平台数量从400+提升至600+

  • 9b22077 2026-07-24 16:33

Metadata

Files
0
Version
1b44d70
Hash
c9186e8a
Indexed
2026-07-24 16:33

- 위키
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-09-22 15:26
浙ICP备14020137号-1