Files
dify/cli

difyctl

CLI client for the Dify platform. Browser device-flow sign-in, then a server-published catalog of operations you discover and call at run time, each one a command. Results are JSON; help and errors are text on a terminal, JSON in a pipe.

Install (edge, internal)

Per-commit edge builds are published to Cloudflare R2. The installer script lives in this repo; binaries are fetched from R2 via DIFYCTL_R2_BASE (shared internally):

curl -fsSL https://raw.githubusercontent.com/langgenius/dify/main/cli/scripts/install-r2.sh | DIFYCTL_R2_BASE=<BASE> sh
Env Default Purpose
DIFYCTL_R2_BASE — (required) R2 public base, e.g. https://pub-….r2.dev.
DIFYCTL_CHANNEL edge Channel to install.
DIFYCTL_INSTALL_DIR $HOME/.local/bin Directory the binary is written to (<dir>/difyctl).
DIFYCTL_VERSION latest Pin an exact published version.
DIFYCTL_COMMIT latest Pin by git commit (short or full sha).
DIFYCTL_R2_PREFIX difyctl R2 key root for the pointer JSONs (manifest.json / index.json).
DIFYCTL_R2_BIN_PREFIX difyctl/bin R2 key root for binaries (the lifecycle/TTL target).

By default the channel pointer (latest build) is installed. Set DIFYCTL_COMMIT (e.g. ce4af86) or DIFYCTL_VERSION to install a specific past build — both resolve through the channel's index.json:

curl -fsSL https://raw.githubusercontent.com/langgenius/dify/main/cli/scripts/install-r2.sh | DIFYCTL_R2_BASE=<BASE> DIFYCTL_COMMIT=ce4af86 sh

Windows: $env:DIFYCTL_R2_BASE='<BASE>'; irm https://raw.githubusercontent.com/langgenius/dify/main/cli/scripts/install-r2.ps1 | iex (same env vars, e.g. $env:DIFYCTL_COMMIT='ce4af86').

Re-run to upgrade. For tagged rc/stable builds, use the GitHub installer (install-cli.sh / install.ps1), which resolves releases via the GitHub API. That API caps unauthenticated requests at 60/hour per IP; behind a shared NAT or in CI, set GITHUB_TOKEN (or GH_TOKEN) to raise it to 5000/hour — the installer sends it as a bearer token.

Quickstart

difyctl login --server https://dify.example.com   # opens the browser; approve the device code shown
difyctl help                                      # the map: every command and operation namespace
difyctl help chatbot                              # search commands and operations by plain words
difyctl get workspace                             # workspaces visible to this account
difyctl get console_app --limit 5                 # list apps
difyctl run console_app workflow --app-id <id> --inputs @vars.json --stream

difyctl has no built-in business commands. Every operation the server publishes is a command: run.console_app.workflow is difyctl run console_app workflow, and its input fields are flags (--app-id, --inputs). A field named like one of difyctl's own flags (input, stream, only, output, verbose, json, help) is passed through --input. The table lists the local commands; difyctl help lists the server's too. See Agent skill for the discovery flow.

Commands

Command Purpose
login Log in via the OAuth device flow
logout Log out of the current session
version Print the client and (if logged in) server versions
use workspace <id> Pin the local session to a workspace
get config [key] Print the local config, or one key
set config <key> <value> Set a local config value
unset config <key> Remove a local config value, restoring its default
refresh cache Refetch the server catalog and replace the local cache
delete cache Delete the local server catalog cache
get skills List the skills in the collection
install skills <dir> Write skills from the collection into a skills root (--skill)

login blocks until the browser approval arrives. An agent runs it in the background, relays the open <url> and code <code> lines from its stderr to the user, and does not cancel the job. In a sandbox it adds --no-keyring and sets DIFY_CONFIG_DIR to persistent storage on every call, so the login survives the next session.

--verbose is global: it may appear on any command, and it keeps the raw server response in the error envelope instead of dropping it.

difyctl help covers local commands and server operations as one tree. Bare, it prints the map. difyctl help <namespace> lists one namespace (the bare namespace, difyctl get, does the same), difyctl help <words> searches by plain words, and difyctl help <id> prints one descriptor, the same as <command> --help; the id may be dotted or spaced. --all adds hidden entries, --full on the bare help prints every descriptor. Help and errors are text on a terminal and JSON in a pipe; --json or DIFY_OUTPUT=json forces JSON. Results and streams are always JSON.

Agent skills

difyctl installs agent skills from skills/ at the repo root. It downloads them from GitHub at install time, from the commit the binary was built from; the binary holds no copy and needs no git. The basic skill, difyctl, teaches the discovery flow — help to see the map or search, help <id> to inspect one operation, then the operation's own command to run it — so it stays correct as the server's catalog grows. Scenario skills add guidance for one kind of task and open by reading the basic one.

  • difyctl get skills — the collection: each skill's name and description. Writes nothing.
  • difyctl install skills <dir> — write every skill into <dir>, the agent's skills root: the folder that holds one subfolder per skill, for example ~/.claude/skills for Claude Code or ~/.codex/skills for Codex. Each skill's folder is replaced whole. --skill <name> (repeatable) writes only those skills; a scenario skill brings difyctl along.
  • --from on both reads skills from elsewhere: any GitHub folder in the standard layout (https://github.com/<owner>/<repo>/tree/<ref>/<folder>), or a local folder when GitHub is blocked. Set GITHUB_TOKEN if GitHub's rate limit (60 requests an hour per IP) gets in the way.

difyctl does not detect agents. You name the root, and re-running the same command refreshes the copies.

The same files install through the Vercel skills installer: npx skills add langgenius/dify --skill difyctl -g. Always pass --skill; without it the installer also offers the contributor skills under .agents/skills/.

Configuration

OS Config path
Linux ${XDG_CONFIG_HOME:-$HOME/.config}/difyctl/
macOS $HOME/.config/difyctl/
Windows %APPDATA%\difyctl\

get config [key] prints the whole local config or one key; set config <key> <value> and unset config <key> change it. The only config key today is http.timeout (request timeout in milliseconds).

Tokens use the OS keychain by default, falling back to tokens.yml on hosts without one; login --no-keyring chooses the file outright. Config and cache files are written 0600, their directory 0700.

Env var Effect
DIFY_SERVER Server base URL; with DIFY_TOKEN, skips interactive login
DIFY_TOKEN Bearer token; requires DIFY_SERVER
DIFY_WORKSPACE_ID Overrides the locally pinned workspace
DIFY_OUTPUT json or text; overrides the terminal/pipe detection
DIFY_CONFIG_DIR Overrides the config directory
DIFY_CACHE_DIR Overrides the catalog cache directory

Resource access tokens can also be passed through DIFY_TOKEN. They can list their own workspace and discover only apps bound to that token; workspace details, members, and workspace switching remain unavailable.

Streaming

Pass --stream on a streaming operation to print every event as one JSON line, instead of folding the whole run into a single result object:

difyctl run console_app chat --app-id … --query hello --inputs '{}' --stream

--only <event> filters the streamed events to the named event types; repeat it to keep more than one. Without --stream, a streaming operation's events fold into {status, text, outputs?, message_id?, conversation_id?, error?, hints}.

Exit codes

Code Meaning
0 Success
1 Error
2 Usage or input error
4 Not logged in, or forbidden
6 Catalog unavailable, or unknown operation id
7 Rate limited

Contributing

See ARD.md for architecture patterns, scaffolding recipe, dev workflow.

License

Apache-2.0.