Go implementation of an Asana CLI with:
- Legacy command aliases for index-based workflows
- Full
Tasks / Projects / Usersendpoint command surface - OAuth + PAT authentication
- AWS CLI style profile switching
- Automation-friendly JSON output
go install ./cmd/asanaOr build directly:
go build -o asana ./cmd/asanaTap this repository and install the formula:
brew tap cloudnative-co/asana-cli https://github.com/cloudnative-co/asana-cli
brew install cloudnative-co/asana-cli/asanaIf asana is already linked from another source, run:
brew unlink asana || true
brew link --overwrite asanaThis repository includes Formula/asana.rb and a helper script:
./scripts/update-homebrew-formula.sh v0.1.0The script downloads the release tarball and updates url + sha256 in the formula.
To install the latest development snapshot:
brew install --HEAD cloudnative-co/asana-cli/asanaSee also: docs/homebrew.md
Choose one authentication path first.
- Export or pass your PAT:
asana auth import-pat --profile default --pat "$ASANA_CLI_PAT"- Configure workspace:
asana config --profile default --workspace <workspace_gid>- List tasks:
asana tasks- Export your OAuth app settings:
export ASANA_CLI_CLIENT_ID="<client_id>"
export ASANA_CLI_CLIENT_SECRET="<client_secret>"
export ASANA_CLI_REDIRECT_URI="urn:ietf:wg:oauth:2.0:oob"- Login with OAuth:
asana auth login --profile defaultasana auth login without --scopes or --scope-preset now uses the cli-default preset.
- Configure workspace:
asana config --profile default --workspace <workspace_gid>- List tasks:
asana tasksPath-param commands also accept positional arguments in placeholder order. These are equivalent:
asana project get --project-gid 1212822434343286
asana project get 1212822434343286Preferred environment variables:
export ASANA_CLI_CLIENT_ID="<client_id>"
export ASANA_CLI_CLIENT_SECRET="<client_secret>"
export ASANA_CLI_REDIRECT_URI="urn:ietf:wg:oauth:2.0:oob"asana auth login \
--profile default \
--client-id <client_id> \
--client-secret <client_secret> \
--redirect-uri <redirect_uri_registered_in_asana_app>--redirect-uriは Asana Developer Console の OAuth Redirect URLs に登録済みの値と完全一致 が必要- 1文字でも違うとブラウザで
invalid_request: The redirect_uri parameter does not match...になる
CLI用途の例(アプリ側で同じ値を登録している場合):
asana auth login \
--profile default \
--client-id "$ASANA_CLI_CLIENT_ID" \
--client-secret "$ASANA_CLI_CLIENT_SECRET" \
--redirect-uri "urn:ietf:wg:oauth:2.0:oob"OOBを使わず Web callback を登録しているアプリなら、その登録済みURLをそのまま指定:
asana auth login \
--profile default \
--client-id "$ASANA_CLI_CLIENT_ID" \
--client-secret "$ASANA_CLI_CLIENT_SECRET" \
--redirect-uri "https://<your-registered-callback>"Asana Developer Console の OAuth > Permission scopes は、次のどちらかで運用する。
- Full permissions を使う(簡単だが権限が広い)
- Specific scopes を使う(推奨: 最小権限)
- アプリ設定で
Full permissionsを有効化 asana auth loginでは--scopes/--scope-presetを指定しない- もし
forbidden_scopes: ... default identity ...が出る場合は、Full permissionsが無効か配布設定不足
asana auth login \
--profile default \
--client-id "$ASANA_CLI_CLIENT_ID" \
--client-secret "$ASANA_CLI_CLIENT_SECRET" \
--redirect-uri "urn:ietf:wg:oauth:2.0:oob"Full permissionsを無効化- アプリ設定で必要scopeのみ有効化
- CLIでも同じscopeを
--scopesか--scope-presetで明示 asana auth loginの既定はcli-defaultpreset
asana auth login \
--profile default \
--client-id "$ASANA_CLI_CLIENT_ID" \
--client-secret "$ASANA_CLI_CLIENT_SECRET" \
--redirect-uri "urn:ietf:wg:oauth:2.0:oob" \
--scope-preset cli-default--scope-preset cli-default は、この CLI の主要コマンド群向けの推奨scopeセットを展開する:
tasks:read,tasks:write,tasks:deleteprojects:read,projects:write,projects:deleteusers:readstories:read,stories:writeattachments:read,attachments:write,attachments:deletecustom_fields:read,custom_fields:writetags:read,tags:writetime_tracking_entries:readtask_custom_types:read,task_templates:readteams:readworkspaces:read
task-full は互換の別名として残しており、現在は cli-default と同じ内容です。
- アプリ側で許可されていないスコープを要求すると
forbidden_scopesになる - このCLIは
--scopes/--scope-preset未指定時、cli-defaultpreset を使う - 必要な場合のみ、アプリで許可済みのスコープだけを明示指定する
asana auth login \
--profile default \
--client-id "$ASANA_CLI_CLIENT_ID" \
--client-secret "$ASANA_CLI_CLIENT_SECRET" \
--redirect-uri "urn:ietf:wg:oauth:2.0:oob" \
--scopes "tasks:read,users:read,workspaces:read"Asana Developer Console の OAuth > Permission scopes で、少なくとも以下を有効化:
tasks:readtasks:writetasks:delete(task deleteを使う場合)projects:readprojects:writeprojects:delete(project deleteを使う場合)users:readstories:read(タスク履歴表示)stories:write(コメント投稿)attachments:read(添付参照/ダウンロード)attachments:write(添付アップロード)attachments:delete(添付削除)custom_fields:read(タスクの custom field 読み取り)custom_fields:write(custom field 作成・更新)tags:readtags:writetime_tracking_entries:readworkspaces:read(workspace取得)
補足:
--scopesで指定した値は、アプリ側で許可済みでないとforbidden_scopesになる--scopes/--scope-presetを省略すると、CLI はcli-defaultpreset で認可を試行するuser update/user update-for-workspaceは OAuth scopes 一覧にusers:writeが存在しないため、アプリ設定(Full permissions など)と実際のAPI応答で要確認
公式ドキュメント:
- OAuth scopes: https://developers.asana.com/docs/oauth-scopes
- Authentication: https://developers.asana.com/docs/authentication
asana task list --output json --non-interactive
asana user list --workspace <gid> --domain example.com --output csv --out users.csvUse local name filters on list responses:
asana task list-project \
--project-gid 1199687679891327 \
--query opt_fields=gid,name,completed,due_on,assignee.name,permalink_url \
--name-contains pocketalk \
--output json \
--non-interactiveRegex filter is also supported:
asana task list-project --project-gid <gid> --name-regex 'pocketalk-[0-9]+' --output jsonNotes:
--name-containsis case-insensitive.--name-regexuses Go regular expressions.- For list/search endpoints with
--all, CLI now setslimit=100automatically if not specified to avoid large-result errors.
List tasks for a specific assignee:
asana task list \
--assignee me \
--workspace <workspace_gid> \
--output json \
--non-interactiveIf the active profile already has a workspace set, --workspace can be omitted.
Resolve projects from ancestor tasks without overwriting the task's direct projects field:
asana task get \
--task-gid <task_gid> \
--resolve-projects ancestors \
--output json \
--non-interactiveThis adds:
resolved_projectsresolved_from_task_gidresolved_from_task_nameresolved_from_depthresolved_projects_status
Expand descendant subtasks from any task-shaped response:
asana task list-project \
--project-gid <project_gid> \
--query completed_since=now \
--include-subtasks descendants \
--output json \
--non-interactiveFor list responses, descendant subtasks are flattened into data and annotated with:
expanded_from_task_gidexpanded_from_task_namesubtask_depth
For single-task responses, the root task keeps its normal shape and adds:
descendant_subtasksdescendant_subtasks_count
completed_since is propagated to descendant subtask fetches, so completed_since=now keeps the expansion open-task only.
asana section ... endpoints may require Full permissions in Asana Developer Console even when the same project is otherwise readable via project / task endpoints.
Typical example:
asana section list-for-project --project-gid <project_gid> --output json --non-interactiveIf this returns 403 with a hint like Full permissions are required to use this endpoint., re-auth with an app configured for Full permissions.
When you only need section names attached to tasks, you can often use:
asana task list-project \
--project-gid <project_gid> \
--query opt_fields=gid,name,memberships.section.name \
--output json \
--non-interactiveasana task ...(29 endpoint mappings)asana project ...(19 endpoint mappings)asana user ...(8 endpoint mappings)asana custom-field ...(8 endpoint mappings)asana tag ...(9 endpoint mappings)asana section ...(8 endpoint mappings)asana story ...(5 endpoint mappings)asana attachment ...(4 endpoint mappings)asana user-task-list ...(2 endpoint mappings)asana time-entry ...(6 endpoint mappings)
Use --help under each group for concrete operations.
configworkspaces(w)tasks(ts)task <index|gid>comment(cm)doneduebrowse(b)download(dl)
~/.config/asana-cli/config.toml~/.config/asana-cli/credentials.toml
Secrets are stored in keyring where available. Fallback environment variables:
ASANA_CLI_PATASANA_CLI_ACCESS_TOKENASANA_CLI_REFRESH_TOKENASANA_CLI_CLIENT_SECRET
Use the ASANA_CLI_* namespace for CLI-specific configuration and secrets.
ASANA_CLI_PROFILEWhat it does: selects the default profile when--profileis omitted. Why it is needed: lets shell sessions pin a profile without editing config. How to write it:export ASANA_CLI_PROFILE=defaultASANA_CLI_CLIENT_IDWhat it does: provides the OAuth client ID forasana auth login. Why it is needed: avoids repeating--client-idon each login. How to write it:export ASANA_CLI_CLIENT_ID="<client_id>"ASANA_CLI_CLIENT_SECRETWhat it does: provides the OAuth client secret and refresh fallback. Why it is needed: lets OAuth login and token refresh run without prompting. How to write it:export ASANA_CLI_CLIENT_SECRET="<client_secret>"ASANA_CLI_REDIRECT_URIWhat it does: provides the default OAuth redirect URI. Why it is needed: keeps the CLI aligned with the app's registered callback. How to write it:export ASANA_CLI_REDIRECT_URI="urn:ietf:wg:oauth:2.0:oob"ASANA_CLI_PATWhat it does: provides the PAT forasana auth import-pat. Why it is needed: avoids passing--pator entering the token interactively. How to write it:export ASANA_CLI_PAT="<pat>"ASANA_CLI_ACCESS_TOKENWhat it does: overrides the stored OAuth access token. Why it is needed: useful as a keyring fallback or for temporary debugging. How to write it:export ASANA_CLI_ACCESS_TOKEN="<access_token>"ASANA_CLI_REFRESH_TOKENWhat it does: overrides the stored OAuth refresh token. Why it is needed: useful as a keyring fallback or for temporary debugging. How to write it:export ASANA_CLI_REFRESH_TOKEN="<refresh_token>"
These OS-level variables are unchanged:
EDITORWhat it does: opens an editor for interactive comment entry. Why it is needed: follows normal shell conventions instead of inventing a CLI-specific name. How to write it:export EDITOR=vimBROWSERWhat it does: selects the browser launcher forasana browse. Why it is needed: follows normal shell conventions and works across tools. How to write it:export BROWSER='open -a "Google Chrome"'
- Local slug memo for API lookup:
.git/info/asana-api-slugs.local.md(not tracked) - API scope snapshot docs:
docs/spec/