Unofficial command-line client for Worksection.
wsectl is a Go CLI for read-only Worksection access. It is built for humans at a terminal and for scripts or coding agents that need stable JSON,
predictable exit codes, explicit account profiles, and safe credential handling.
This project is not affiliated with, endorsed by, or supported by Worksection.
The MVP is read-only by design. Commands that would change Worksection data are recognized and blocked with a clear error. The code is structured so write commands can be added later behind explicit safety decisions.
Run the guide compiled into the installed binary before constructing commands:
wsectl help agent --fullFor machine-readable discovery:
wsectl help agent --full --json
wsectl commands --json
wsectl api actions --json
wsectl api schema get_projects --json
wsectl doctor --jsonThe help JSON includes guide_format_version. commands --json reports explicit category, Worksection actions, output modes, authentication
requirements, read-only status, examples, and agent notes. api schema ACTION --json reports the static action contract, including parameters,
response shape, known fields, conditional extra fields, count path, OAuth scopes, and compatibility notes.
Homebrew (macOS/Linux):
brew install pbv7/tap/wsectlFrom source:
go install github.com/pbv7/wsectl/cmd/wsectl@latestFor local development:
git clone https://github.com/pbv7/wsectl
cd wsectl
go run ./cmd/wsectlgo run is useful for help and development checks. For keyring-backed OAuth login, prefer a stable binary built with make build or an installed
wsectl; temporary go run binaries can confuse OS keychain access control on macOS.
Create a profile, log in with OAuth, then use JSON output for automation:
wsectl profiles add default --account-url https://company.worksection.com --auth-type oauth2
# Set WSECTL_CLIENT_ID and WSECTL_CLIENT_SECRET in your shell first.
wsectl auth login --client-id "$WSECTL_CLIENT_ID"
wsectl doctor --api
wsectl projects list --json
wsectl tasks search --query "invoice" --jsonwsectl auth login starts a temporary local HTTPS callback server, opens the browser, validates OAuth state, exchanges the returned code, and stores
tokens in the selected secret store.
For a remote shell or headless session, keep the callback server active and open the URL manually:
# Set WSECTL_CLIENT_ID and WSECTL_CLIENT_SECRET in your shell first.
wsectl auth login --no-browser --client-id "$WSECTL_CLIENT_ID"For CI, use environment credentials:
WSECTL_ACCOUNT_URL=https://company.worksection.com \
WSECTL_ACCESS_TOKEN=... \
wsectl me --jsonFor regular use on a personal workstation, prefer a config profile plus OS keychain secrets instead of exporting tokens every time. See Configuration for the full desktop, admin-token, encrypted-file, and CI workflows.
wsectl profiles add default \
--account-url https://company.worksection.com \
--auth-type oauth2
wsectl profiles use default
# Set WSECTL_CLIENT_ID and WSECTL_CLIENT_SECRET in your shell first.
wsectl auth login --client-id "$WSECTL_CLIENT_ID"
wsectl doctor --api
wsectl me --jsonprofiles add writes non-secret account settings to config.toml. When --secret-ref is omitted, the profile uses keyring:wsectl/PROFILE, so
auth login stores OAuth tokens and client credentials in the OS keychain. After this setup, normal commands do not need WSECTL_ACCESS_TOKEN,
WSECTL_ACCOUNT_URL, or other credential environment variables.
Default config locations:
- macOS/Linux:
~/.config/wsectl/config.tomlunless$XDG_CONFIG_HOMEis set. - Windows:
%AppData%\wsectl\config.toml.
Optional command history is available as a local JSONL file. It is disabled by default and never writes logs to stdout:
WSECTL_HISTORY=1 wsectl projects list --json
wsectl history path --json
wsectl history list --json --limit 20
wsectl history clear --keep 1000For containers, mount state explicitly:
WSECTL_HISTORY=1 WSECTL_HISTORY_FILE=/state/history.jsonl wsectl doctor --api --jsonUse separate profiles for separate accounts or auth modes:
wsectl profiles add client-a --account-url https://client-a.worksection.com --auth-type oauth2
wsectl profiles add client-b --account-url https://client-b.worksection.com --auth-type oauth2
wsectl --profile client-a projects list --json
wsectl profiles use client-bFor an admin API key, keep a separate profile:
wsectl profiles add admin \
--account-url https://company.worksection.com \
--auth-type admin_token \
--secret-ref keyring:wsectl/admin
# Set WSECTL_ADMIN_TOKEN in your shell first.
wsectl --profile admin auth login
wsectl --profile admin doctor --apiFor a portable encrypted secret file instead of the OS keychain:
export WSECTL_SECRET_PASSPHRASE="use-a-password-manager-value"
wsectl profiles add portable \
--account-url https://company.worksection.com \
--auth-type oauth2 \
--secret-ref encrypted-file:$HOME/.config/wsectl/secrets/portable.json
# Set WSECTL_CLIENT_ID and WSECTL_CLIENT_SECRET in your shell first.
wsectl --profile portable auth login --client-id "$WSECTL_CLIENT_ID"The encrypted file is useless without WSECTL_SECRET_PASSPHRASE; do not store that passphrase next to the encrypted file.
wsectl me --json
wsectl users list --json
wsectl projects list --status active --extra text,options,users --json
wsectl projects get 123 --extra text,users --json
wsectl tasks all --extra text,files --json --out /tmp/tasks.json
wsectl tasks list --project 123 --status active --extra text,comments --json
wsectl tasks search --query "invoice" --json
wsectl comments list 456 --extra files --json
wsectl costs total --project 123 --start 01.05.2026 --end 31.05.2026 --json
wsectl files download 789 --out ./attachment.binUse the lower-level API escape hatch when a read-only action is not wrapped by a first-class command:
wsectl api call get_users_schedule \
--param datestart=01.05.2026 \
--param dateend=31.05.2026 \
--jsonProfiles keep account and credential references separate:
wsectl profiles add default --account-url https://company.worksection.com --auth-type oauth2
wsectl profiles add admin --account-url https://company.worksection.com --auth-type admin_token
wsectl profiles use default
wsectl profiles list --json
wsectl profiles show default --jsonUse --profile NAME when a command must target a specific account:
wsectl --profile default projects list --jsonConfig management commands:
wsectl profiles list --json
wsectl profiles show default --json
wsectl profiles use default
wsectl auth status --json
wsectl auth refresh
wsectl auth logout
wsectl profiles remove old-profileUse --config PATH or WSECTL_CONFIG for a non-default config file. Use WSECTL_PROFILE or --profile NAME to override current_profile without
editing config.
Prefer JSON or NDJSON:
wsectl users list --json
wsectl tasks all --extra text,files --ndjson
wsectl projects list --json --jq '.data[] | {id, name, status}'
wsectl tasks all --json --fields id,name,status --out /tmp/tasks.json
wsectl tasks search --schema --jsonDo not parse table output in scripts. Table output is for humans and may change to improve readability. JSON envelope fields are intended to stay stable:
{
"status": "ok",
"data": [],
"meta": {
"action": "get_tasks",
"profile": "default",
"account_url": "https://company.worksection.com",
"contract_version": "2026-06-07.1",
"response_shape": "array",
"count": 0,
"truncated": false,
"warnings": []
}
}Always check meta.truncated and meta.warnings for large responses.
--schema --json on first-class read commands returns the same static action contract without loading config, opening the keychain, or calling
Worksection. These contracts are advisory and agent-readable; they are not full JSON Schema and do not guarantee that every field appears in every
account.
Secrets are accessed through a SecretStore abstraction:
keyring: default OS keychain backend.wsectlenables OS-backed keychains and Pass, and intentionally disables the 99designs File and KeyCtl backends.env: read-only backend for CI and containers.encrypted-file: explicit portable fallback protected byWSECTL_SECRET_PASSPHRASEwith versioned Argon2id/AES-GCM payloads.plaintext: explicit opt-in only.
Commands do not print tokens by default. Use environment credentials for ephemeral automation and OS keychain storage for interactive use.
File downloads only forward bearer credentials to HTTPS URLs on the configured Worksection account host. If Worksection returns a cross-host file URL,
wsectl fails closed with a structured download_host_mismatch error instead of leaking credentials.
Worksection documents these API constraints:
- 1 request per second.
- GET URL length limit of 8 kB.
- Some endpoints can return at most 10,000 records.
- Some long text fields can be shortened by the server.
wsectl uses Worksection's documented/examples-compatible POST requests with API parameters in the query string and enforces client-side rate
limiting by default, but it cannot remove server-side limits. Very long filters can still hit request URL limits.
Official Worksection web docs, official Postman collections, and live API behavior can disagree. wsectl treats Postman/live behavior as
authoritative for wire behavior and documents known normalizations.
- First-class commands may expose human-readable values while sending Worksection's raw API value. For example,
wsectl projects list --status archivedsendsfilter=archive. - Low-level
api calluses raw API parameter names and values. For archived projects, usewsectl api call get_projects --param filter=archive --json. wsectl tasks search --query TEXTsends an escaped Worksectionfilter, not an undocumentedsearchparameter.wsectl costs ... --timer truesendsis_timer=true.wsectl files imagesfilters client-side afterget_files.wsectl costs listreturns the cost entries as a JSON array atdata, with the server-side summary inmeta.aggregate.wsectl costs totalreturns its aggregate bundle (total, plus optionalprojects/taskswith--extra) as a plain object atdata.wsectl projects events --periodaccepts<N>m|h|d— minutes, hours, or days (e.g.30m,24h,7d). Weeks, bare numbers, and words are rejected client-side; the server additionally caps the value per unit.wsectl tasks searchneeds at least one search dimension:--query,--filter,--project,--task,--assignee, or--author.--statusand--extraare modifiers, not search criteria, and fail on their own.wsectl tasks search --filtersupportsnameand the date fieldsdateadd,datestart,dateend,dateclose(the response-field formsdate_added/date_closedare also accepted) with operatorshas,<,>, andand— enabling server-side date-range filtering, e.g.--filter "dateadd > '01.06.2026' and dateadd < '12.06.2026'". Unsupported fields such asstatus,tag, orprioritymake the server reject the filter with a misleadingField is required: filter.- Subtasks:
wsectl tasks list/wsectl tasks all(get_tasks/get_all_tasks) return top-level rows only;--extra subtasksattaches achildarray of stubs ({id, name, page, priority, status}) to rows that have subtasks.wsectl tasks searchreturns the flat set of matching tasks including subtasks by default — each subtask is a full first-class task row with its ownstatus/assignee/date_endand aparentobject (extra=subtasksis a no-op on search). Usewsectl tasks search --top-level-onlyto drop subtasks (client-side; not combinable with--raw). For a subtask's full data, read the flatsearchrows orwsectl tasks get <subtask_id>. - Worksection value shapes to expect:
priorityis a string (e.g."5");tagsis a{id: name}map and appears only ontasks get; an unassigned task hasuser_to = {"id": "1", "email": "NOONE", "name": "..."}(sometimes labeled "Anyone") — treat id1as unassigned. A task'sdate_start/date_end/date_closedare present only when that date is set. - Live probes showed query-parameter POST works, JSON request bodies work for selected OAuth API reads, and
application/x-www-form-urlencodedAPI bodies returninvalid JSON. This build does not use form-encoded API bodies.
wsectl, wsectl --help, and wsectl help print the same deterministic start screen. Subcommand help remains command-specific.
wsectl
wsectl help agent --full
wsectl doctor
wsectl doctor --api --json
wsectl help completion
wsectl help output
wsectl commands --json
wsectl api actions --jsonCompletion scripts are generated by Cobra from the current command tree, so they stay aligned with new commands, flags, and enum completions when the binary is updated.
wsectl completion bash
wsectl completion zsh
wsectl completion fish
wsectl completion powershellExamples:
wsectl completion zsh > "${fpath[1]}/_wsectl"
wsectl completion fish > ~/.config/fish/completions/wsectl.fish
wsectl completion powershell > wsectl.ps1- Manual
- Agent usage
- Auth
- Configuration
- Doctor
- Shell completion
- Output contracts
- Recipes
- API coverage
- Security
- Release
- Generated command reference
- Contributing
make version
make check
make ci
make lint-md
make lint-workflows
make coverage
make coverage-check
make coverage-html
make build
make run ARGS="help agent"
make build-all
make docs
make cleanmake build and make install inject version, commit, and build date into wsectl version. make build-all creates local Linux, macOS, and
Windows development binaries under dist/; release archives are still produced by GoReleaser.
Development-only lint tools are tracked in package-manager manifests: actionlint is declared as a Go tool in go.mod, and markdownlint-cli2 is
declared in package-lock.json for reproducible Markdown linting.
make coverage-check uses POSIX shell tools and is intended for Unix-like developer environments. Windows CI runs direct Go commands for
cross-platform coverage of the source.
Before tagging a release, run the end-to-end live probe against a real read-only Worksection account:
make live-probeThe live probe builds dist/wsectl and exercises the real CLI flow: doctor, identity, projects, tasks, comments, files, downloads, output modes,
schema discovery, api call, and negative exit-code cases. See Release for pinned project/task/file overrides and release
acceptance notes.
Optional live smoke tests are disabled by default:
WSECTL_LIVE_TESTS=1 \
WSECTL_TEST_ACCOUNT_URL=https://company.worksection.com \
WSECTL_TEST_ACCESS_TOKEN=... \
go test ./internal/worksection -run LiveSmoke