Skip to content

cloudnative-co/asana-cli

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

asana-cli

Go implementation of an Asana CLI with:

  • Legacy command aliases for index-based workflows
  • Full Tasks / Projects / Users endpoint command surface
  • OAuth + PAT authentication
  • AWS CLI style profile switching
  • Automation-friendly JSON output

Install (local)

go install ./cmd/asana

Or build directly:

go build -o asana ./cmd/asana

Install (Homebrew)

Tap 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/asana

If asana is already linked from another source, run:

brew unlink asana || true
brew link --overwrite asana

Stable formula updates

This repository includes Formula/asana.rb and a helper script:

./scripts/update-homebrew-formula.sh v0.1.0

The 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/asana

See also: docs/homebrew.md

Quick Start

Choose one authentication path first.

Quick Start: PAT

  1. Export or pass your PAT:
asana auth import-pat --profile default --pat "$ASANA_CLI_PAT"
  1. Configure workspace:
asana config --profile default --workspace <workspace_gid>
  1. List tasks:
asana tasks

Quick Start: OAuth

  1. 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"
  1. Login with OAuth:
asana auth login --profile default

asana auth login without --scopes or --scope-preset now uses the cli-default preset.

  1. Configure workspace:
asana config --profile default --workspace <workspace_gid>
  1. List tasks:
asana tasks

Path-param commands also accept positional arguments in placeholder order. These are equivalent:

asana project get --project-gid 1212822434343286
asana project get 1212822434343286

OAuth Login

Preferred 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 の指定ルール

  • --redirect-uriAsana 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>"

OAuth Permission Scopes の設定パターン

Asana Developer Console の OAuth > Permission scopes は、次のどちらかで運用する。

  1. Full permissions を使う(簡単だが権限が広い)
  2. Specific scopes を使う(推奨: 最小権限)

1) Full permissions 方式

  • アプリ設定で 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"

2) Specific scopes 方式(推奨)

  • Full permissions を無効化
  • アプリ設定で必要scopeのみ有効化
  • CLIでも同じscopeを --scopes--scope-preset で明示
  • asana auth login の既定は cli-default preset
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:delete
  • projects:read, projects:write, projects:delete
  • users:read
  • stories:read, stories:write
  • attachments:read, attachments:write, attachments:delete
  • custom_fields:read, custom_fields:write
  • tags:read, tags:write
  • time_tracking_entries:read
  • task_custom_types:read, task_templates:read
  • teams:read
  • workspaces:read

task-full は互換の別名として残しており、現在は cli-default と同じ内容です。

forbidden_scopes が出る場合

  • アプリ側で許可されていないスコープを要求すると forbidden_scopes になる
  • このCLIは --scopes / --scope-preset 未指定時、cli-default preset を使う
  • 必要な場合のみ、アプリで許可済みのスコープだけを明示指定する
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アプリ側の OAuth scope 設定チェック

Asana Developer Console の OAuth > Permission scopes で、少なくとも以下を有効化:

  • tasks:read
  • tasks:write
  • tasks:deletetask delete を使う場合)
  • projects:read
  • projects:write
  • projects:deleteproject delete を使う場合)
  • users:read
  • stories:read(タスク履歴表示)
  • stories:write(コメント投稿)
  • attachments:read(添付参照/ダウンロード)
  • attachments:write(添付アップロード)
  • attachments:delete(添付削除)
  • custom_fields:read(タスクの custom field 読み取り)
  • custom_fields:write(custom field 作成・更新)
  • tags:read
  • tags:write
  • time_tracking_entries:read
  • workspaces:read(workspace取得)

補足:

  • --scopes で指定した値は、アプリ側で許可済みでないと forbidden_scopes になる
  • --scopes / --scope-preset を省略すると、CLI は cli-default preset で認可を試行する
  • user update / user update-for-workspace は OAuth scopes 一覧に users:write が存在しないため、アプリ設定(Full permissions など)と実際のAPI応答で要確認

公式ドキュメント:

Output Modes

asana task list --output json --non-interactive
asana user list --workspace <gid> --domain example.com --output csv --out users.csv

Frequent filtering patterns

Use 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-interactive

Regex filter is also supported:

asana task list-project --project-gid <gid> --name-regex 'pocketalk-[0-9]+' --output json

Notes:

  • --name-contains is case-insensitive.
  • --name-regex uses Go regular expressions.
  • For list/search endpoints with --all, CLI now sets limit=100 automatically if not specified to avoid large-result errors.

Assigned tasks and resolved projects

List tasks for a specific assignee:

asana task list \
  --assignee me \
  --workspace <workspace_gid> \
  --output json \
  --non-interactive

If 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-interactive

This adds:

  • resolved_projects
  • resolved_from_task_gid
  • resolved_from_task_name
  • resolved_from_depth
  • resolved_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-interactive

For list responses, descendant subtasks are flattened into data and annotated with:

  • expanded_from_task_gid
  • expanded_from_task_name
  • subtask_depth

For single-task responses, the root task keeps its normal shape and adds:

  • descendant_subtasks
  • descendant_subtasks_count

completed_since is propagated to descendant subtask fetches, so completed_since=now keeps the expansion open-task only.

Section endpoints and permissions

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-interactive

If 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-interactive

Official Endpoint Groups

  • asana 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.

Legacy Alias Commands

  • config
  • workspaces (w)
  • tasks (ts)
  • task <index|gid>
  • comment (cm)
  • done
  • due
  • browse (b)
  • download (dl)

Profile Files

  • ~/.config/asana-cli/config.toml
  • ~/.config/asana-cli/credentials.toml

Secrets are stored in keyring where available. Fallback environment variables:

  • ASANA_CLI_PAT
  • ASANA_CLI_ACCESS_TOKEN
  • ASANA_CLI_REFRESH_TOKEN
  • ASANA_CLI_CLIENT_SECRET

Environment Variables

Use the ASANA_CLI_* namespace for CLI-specific configuration and secrets.

  • ASANA_CLI_PROFILE What it does: selects the default profile when --profile is omitted. Why it is needed: lets shell sessions pin a profile without editing config. How to write it: export ASANA_CLI_PROFILE=default
  • ASANA_CLI_CLIENT_ID What it does: provides the OAuth client ID for asana auth login. Why it is needed: avoids repeating --client-id on each login. How to write it: export ASANA_CLI_CLIENT_ID="<client_id>"
  • ASANA_CLI_CLIENT_SECRET What 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_URI What 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_PAT What it does: provides the PAT for asana auth import-pat. Why it is needed: avoids passing --pat or entering the token interactively. How to write it: export ASANA_CLI_PAT="<pat>"
  • ASANA_CLI_ACCESS_TOKEN What 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_TOKEN What 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:

  • EDITOR What 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=vim
  • BROWSER What it does: selects the browser launcher for asana browse. Why it is needed: follows normal shell conventions and works across tools. How to write it: export BROWSER='open -a "Google Chrome"'

Notes

  • Local slug memo for API lookup: .git/info/asana-api-slugs.local.md (not tracked)
  • API scope snapshot docs: docs/spec/

About

No description, website, or topics provided.

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages