asnx: Asana CLI
asnx <entity> <action> [<gid>] [--flags...]
The CLI is always flat: asnx <entity> <action>. Sub-resource and verb actions stay explicit, for example asnx tasks subtasks, asnx tasks create-subtasks, and asnx tasks add-followers.
Use asnx describe [<entity> [<action>]] for schema introspection.
Auth
Resolution: --account <name> > ASANA_TOKEN env var > single stored account.
asnx auth status verifies the resolved token against the API; use it to diagnose 401s.
Input
Writes accept either individual flags or a raw JSON body. --json wins on conflict.
asnx tasks create --json '{"name":"bugfix","projects":["def"]}'
--json expects raw request fields only, not a full Asana { "data": ... } envelope.
Output
Successful commands emit a JSON envelope with meta and data:
{
"meta": {
"command": "tasks.list",
"status": 200,
"nextPage": null
},
"data": [ ... ]
}
meta.commandis alwaysentity.action(one dot), e.g.tasks.list,tasks.subtasks, orproject-briefs.create-for-project.datais the unwrapped Asana payload: object for single resources, array for lists.meta.nextPageis a pagination offset (string) ornull. Pass it as--offset.
Errors go to stderr as {"error":{"status":...,"message":...,"help":...}}. status is null for local errors (usage, transport). The exit code is always 1 on error.
Rules
- Use
--dry-runto preview writes before sending them. --opt-fieldson GET requests to keep responses small.- Pagination is manual: check
meta.nextPage, pass as--offset. - Don't guess GIDs, query first. Start from
asnx workspaces list, then e.g.asnx tasks search <workspace-gid> --text "bugfix". - If entity commands come back as unknown, the schema cache is missing: run
asnx schema update. - Dotted Asana filters stay dotted on the CLI, for example
--projects.anyor--due-on.before.
Gotchas
start_onrequiresdue_on: always send both together.meis a positional GID alias, not an action. Useusers get me, notusers me.tasks for-projectreturns all tasks, including completed ones, by default. To get only incomplete tasks pass--completed-since <recent-timestamp>(e.g. now); Asana then returns tasks that are incomplete or were completed since that time.tasks duplicateandprojects duplicatedo not carry over every association. A duplicated task comes back with no project membership (projects: []) unless you pass--includenaming the elements to copy, e.g.--include projects,subtasks,assignee.stories for-taskandstories for-goalreturn comments and system events together. Comments are the entries with"type": "comment"; filter client-side.tasks searchis eventually-consistent. Verify recent writes withtasks getortasks for-project, notsearch.attachments createis multipart on Asana's side and this CLI is JSON-only; the action surfaces but always fails. Use the raw API for file uploads.project-briefs create-for-projectmay reject plain--textwithRich text should be wrapped in <body> tag.If that happens, retry with--html-text '<body>...</body>'.<entity> remove-members --members me(portfolios, projects, goals, teams) revokes your own access.