Imported from danielsitek/asana-cli (
skills/asana-cli/SKILL.md). Install upstream withnpx skills add danielsitek/asana-cli --skill asana-cli. Copyright stays with the author.
asana-cli is already installed; invoke it directly. Every Asana operation goes through it — never call the Asana REST API, an SDK, a MCP, or curl to route around a missing command; that risks handling ASANA_CLI_TOKEN outside its one sanctioned path (the CLI never accepts the token as an argument). If a workflow needs something the CLI doesn't expose, stop and use the closest supported primitive, or follow "Filing a CLI issue" below with the enhancement label instead of working around it.
Project initialization
When asked to initialize asana-cli in a repository, complete the whole personal-ready setup unless the user explicitly requests a different default assignee or no default:
- Resolve the workspace GID from the location the user names, or use
asana-cli workspaces list --jsonif none is provided. - Run
asana-cli config init --shared --workspace=<gid>. - Run
asana-cli config init --local --write-gitignore. - Run
asana-cli config set defaultAssignee me—meis preferred; no need to discover or store the user's numeric GID. - Verify the result with
asana-cli config get workspace.gid --sourceandasana-cli config get defaultAssignee --source; report both resolved values and sources.
Treat initialization as incomplete if defaultAssignee was not set and verified.
Read before write
Before tasks update or tasks comment, read the target first with asana-cli tasks get <id> --json. Before creating a subtask, read its --parent the same way. Before creating a task in a project, read the destination with asana-cli projects get <gid> --json and confirm its gid, name, and archived state. A standalone My Tasks or section create has no task to pre-read; confirm its explicit --my-section or --section destination instead. Decide the exact fields before issuing one write — don't narrow the change mid-write.
Config aliases can write as a side effect
--my-section=@alias and --custom-field=@alias:value resolve through the gitignored, per-user .asana-cli.local.json. On a machine without it, aliases fail. Both config init --local --write-gitignore and config resolve my-tasks re-discover and overwrite that file's myTasks block — neither is read-only; run one before relying on an alias. To inspect aliases already on disk without touching them, read the file directly or use config get myTasks.<key> --source / config show --sources.
Supported workflows
Every task <id> below must be a digit-only GID or a URL of the exact form https://app.asana.com/0/<project>/<task>[/f] — other Asana URL shapes fail with exit 2. Resource arguments written as <gid>, including projects get <gid>, accept digit-only GIDs.
| Goal | Command |
|---|---|
| Show authenticated user | asana-cli whoami |
| Generate shell completion | asana-cli completion bash|zsh|fish |
List workspaces (find <gid> for config init) |
asana-cli workspaces list --json |
| List projects (bounded) | asana-cli projects list [--workspace=<gid>] [--max=<n>] [--all] --json |
| Read a project | asana-cli projects get <gid> --json [--fields=...] |
| List project sections (bounded) | asana-cli projects sections <gid> [--max=<n>] [--all] --json [--fields=...] |
| List project custom-field settings (bounded) | asana-cli projects custom-fields <gid> [--max=<n>] [--all] --json [--fields=...] |
| Init shared config (repo-wide) | asana-cli config init --shared --workspace=<gid> |
| Init local config (per-user aliases) | asana-cli config init --local --write-gitignore |
| Inspect resolved config | asana-cli config show --json --sources, config get <key> --source |
| Re-discover My Tasks aliases (writes local config) | asana-cli config resolve my-tasks |
| Read a task | asana-cli tasks get <id> --json [--fields=...] |
| Read comments (bounded) | asana-cli tasks comments <id> [--max=<n>] [--all] --json |
| Read only the newest N comments (bounded, exact) | asana-cli tasks comments <id> --max=<scan-cap> --latest=<n> --json |
| List tasks (bounded, one source) | asana-cli tasks list --my-section=@alias|--section=<gid>|--project=<gid>|--parent=<id> [--assignee=me|<gid>] [--completed=true|false] [--max=<n>] [--all] --json |
| Update a task | asana-cli tasks update <id> --name=... --notes=...|--notes-file=<path|-> --assignee=me|<gid>|null --due-on=YYYY-MM-DD|null --completed=true|false --my-section=<gid>|@alias --custom-field=@alias:<number|enum-option-gid|exact-name|null> |
| Add a task to a project | asana-cli tasks update <id> --project=<gid> |
| Move a task to any project section | asana-cli tasks update <id> --section=<gid> |
| Reparent or promote a task | asana-cli tasks update <id> --parent=<id>|null |
| Create a subtask | asana-cli tasks create --parent=<id> --name=... [same mutation flags] |
| Create a standalone task in My Tasks | asana-cli tasks create --my-section=<gid>|@alias --name=... --assignee=me [same mutation flags] |
| Create a standalone task in a project section | asana-cli tasks create --section=<gid> --name=... [same mutation flags] |
| Create a standalone task in a project | asana-cli tasks create --project=<gid> --name=... [same mutation flags] |
| Comment on a task | asana-cli tasks comment <id> "text" or --file=<path|-> |
Notes: projects get defaults to gid,name,archived; --fields replaces that selection and the response contains only the requested paths. A missing project uses the same exit 4 not_found contract as tasks get. projects list defaults to configured workspace.gid; its explicit --workspace must be a digit-only GID. It uses the same default scan cap 100, result cap 20, --max, and --all contract as tasks list. tasks comments --latest=<n> returns only the globally newest n comments, newest first, and requires an explicit --max=<scan-cap>; it is mutually exclusive with --all and --offset. It succeeds only after scanning to source exhaustion within the cap — if the cap is reached while more stories are known, it fails with exit 5 (scan_limit) and returns no data; rerun with a higher --max. tasks update --parent is a dedicated single-write reparent — a GID or task URL moves it under that parent, null promotes it to top level, and it is exclusive with every other tasks update flag (a task cannot be its own parent). tasks update --section=<gid> is likewise a dedicated single-write operation, is exclusive with every other update flag, and neither reads My Tasks configuration nor requires a particular assignee. tasks update --project=<gid> adds the task to a project without changing its parent; it is also a dedicated single-write operation, accepts only a digit-only GID, and is exclusive with every other update flag. tasks create requires --name and at least one explicit destination: --parent, --my-section, --section, or --project. --section infers its project and is exclusive with every other destination. On tasks create, --project accepts only a digit-only GID and cannot be combined with --parent or --my-section; --parent may be combined with --my-section. A standalone My Tasks create also requires configured workspace.gid. --notes and --notes-file are mutually exclusive; notes are replaced wholesale, never appended. --custom-field is repeatable and supports number values, enabled enum option GIDs or case-sensitive exact enum names, and null; enum GIDs take precedence over names. --json and --fields may appear before or after the subcommand; --fields applies to projects get and tasks get/comments/comment/update/create/list. --my-section/--custom-field require the final assignee to be the authenticated user (an ownership check, not just presence): on tasks create this means an explicit --assignee=me/digit-only GID on that same call, or a configured defaultAssignee (below); omitting both fails; on tasks update without an explicit --assignee, the task's existing assignee is used instead, so an update to someone else's task needs --assignee=me added explicitly. Either way, a mismatched final assignee fails with exit 2. tasks list requires exactly one of --my-section/--section/--project/--parent (zero or multiple fails with exit 2); --parent accepts a task GID or URL and lists its direct subtasks. On tasks list, --my-section accepts only @alias (unlike tasks create/tasks update, which also accept a digit-only GID), resolved and live-validated against My Tasks the same way as tasks update --my-section. --assignee and --completed (default false) filter client-side and work regardless of --fields. Default fields are gid,name,completed,assignee.gid,assignee.name.
projects sections <gid> defaults to gid,name, preserves API order, and uses the same default scan cap 100, result cap 20, --max, and --all contract as projects list. --fields is accepted before or after this subcommand.
projects custom-fields <gid> defaults to gid,is_important,custom_field.gid,custom_field.name,custom_field.resource_subtype, preserves API order, and uses the same bounded-read contract. --fields applies to both projects sections and projects custom-fields, before or after the subcommand. Its gid is the setting GID, while custom_field.gid is the field-definition GID; select custom_field.enum_options.gid,name,enabled for enum option details.
Personal default assignee (tasks create only)
asana-cli config set defaultAssignee me|<gid> stores a personal default in the gitignored local config. tasks create applies it only when --assignee is omitted; an explicit --assignee, including --assignee=null, always overrides it and skips the config lookup entirely. tasks update never reads or applies this default. Invalid values (anything other than me or a digit-only GID) are rejected with no file written; --shared/--global reject the key the same way.
JSON envelope, stderr, and exit codes
With --json, successful reads and writes print a single compact, minified {"data":...,"meta":...} line to stdout. Diagnostics never go to stdout on success.
Errors are always compact, minified JSON on stderr, regardless of --json: {"error":{"code":"...","message":"..."}}. One exception: a partial multi-stage write (exit 1 below) prints its compact {"completed":...,"failed":...,"message":...} detail to stdout, not stderr — check exit code, not which stream has content, to detect a partial write.
| Code | Meaning |
|---|---|
| 0 | success |
| 1 | partial write — only from tasks create, config init --local, or config resolve my-tasks |
| 2 | invalid usage or configuration |
| 3 | authentication/authorization failure |
| 4 | Asana API, not-found, or network error |
| 5 | rate-limit, retry exhaustion, or tasks comments --latest scan limit |
| 6 | unexpected internal CLI error |
Filing a CLI issue
File against danielsitek/asana-cli for two cases only:
- Bug (
--label bug): exit 6, or exit 4/5 on a command that this skill's own reference confirms was well-formed. Exit 2 and 3 mean fix the command or the environment — do not file for those. - Feature request (
--label enhancement): the workflow needs something no command or flag in "Supported workflows" covers — don't work around it by calling the Asana API/SDK/MCP/curl directly.
Always write the title and body in English, regardless of the conversation language.
- For a bug, rerun the exact failing command once to confirm the failure is deterministic, not a network blip. Skip this step for a feature request — there's nothing to rerun.
- Search for a duplicate:
gh issue list --repo danielsitek/asana-cli --search "<key phrase>" --state all(error message for a bug, the missing capability in your own words for a feature request). - If none matches, draft the body:
- Bug: the exact command run (the token is never in argv, so it's safe to include verbatim),
asana-cli --versionoutput, OS, the stderr JSON error object verbatim, and expected vs. actual behavior. - Feature request: the task you were trying to accomplish, why no existing command/flag covers it, and the command/flag shape that would.
- Bug: the exact command run (the token is never in argv, so it's safe to include verbatim),
- Confirm with the user before creating — filing a public issue is visible, hard-to-reverse shared state. Then:
gh issue create --repo danielsitek/asana-cli --label bug|enhancement --title "..." --body "...".
Completion criterion: either a new issue (bug or enhancement, with the evidence above) is filed, or an existing duplicate is linked back to the user instead.