asana-api-cli 1.3.0__tar.gz → 1.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. {asana_api_cli-1.3.0/src/asana_api_cli.egg-info → asana_api_cli-1.5.0}/PKG-INFO +68 -11
  2. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/README.md +66 -10
  3. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/pyproject.toml +3 -1
  4. asana_api_cli-1.5.0/src/asana_api_cli/cli/__init__.py +93 -0
  5. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/access_requests.py +2 -1
  6. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/allocations.py +18 -7
  7. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/attachments.py +18 -7
  8. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/audit_log_api.py +18 -7
  9. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/batch_api.py +2 -1
  10. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/budgets.py +2 -1
  11. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/custom_field_settings.py +50 -19
  12. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/custom_fields.py +18 -7
  13. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/custom_types.py +18 -7
  14. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/events.py +2 -1
  15. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/exports.py +2 -1
  16. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/goal_relationships.py +18 -7
  17. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/goals.py +18 -7
  18. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/jobs.py +2 -1
  19. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/memberships.py +18 -7
  20. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/organization_exports.py +2 -1
  21. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/portfolio_memberships.py +34 -13
  22. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/portfolios.py +34 -13
  23. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/project_briefs.py +2 -1
  24. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/project_memberships.py +18 -7
  25. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/project_portfolio_settings.py +34 -13
  26. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/project_statuses.py +18 -7
  27. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/project_templates.py +34 -13
  28. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/projects.py +66 -25
  29. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/rates.py +18 -7
  30. asana_api_cli-1.5.0/src/asana_api_cli/cli/reactions.py +45 -0
  31. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/roles.py +18 -7
  32. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/rules.py +2 -1
  33. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/sections.py +18 -7
  34. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/status_updates.py +34 -13
  35. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/stories.py +34 -13
  36. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/tags.py +50 -19
  37. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/task_templates.py +18 -7
  38. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/tasks.py +130 -49
  39. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/team_memberships.py +50 -19
  40. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/teams.py +34 -13
  41. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/time_periods.py +18 -7
  42. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/time_tracking_categories.py +34 -13
  43. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/time_tracking_entries.py +34 -13
  44. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/timesheet_approval_statuses.py +18 -7
  45. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/typeahead.py +2 -1
  46. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/user_task_lists.py +2 -1
  47. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/users.py +34 -13
  48. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/webhooks.py +18 -7
  49. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/workspace_memberships.py +34 -13
  50. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/cli/workspaces.py +18 -7
  51. asana_api_cli-1.5.0/src/asana_api_cli/click_ext.py +276 -0
  52. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/session.py +63 -14
  53. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/version.py +1 -0
  54. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0/src/asana_api_cli.egg-info}/PKG-INFO +68 -11
  55. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli.egg-info/SOURCES.txt +3 -0
  56. asana_api_cli-1.5.0/tests/test_click_ext.py +176 -0
  57. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/tests/test_codegen.py +58 -13
  58. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/tests/test_formatter.py +6 -13
  59. asana_api_cli-1.5.0/tests/test_py310_compat.py +41 -0
  60. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/tests/test_session.py +2 -0
  61. asana_api_cli-1.3.0/src/asana_api_cli/cli/__init__.py +0 -140
  62. asana_api_cli-1.3.0/src/asana_api_cli/cli/reactions.py +0 -34
  63. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/LICENSE +0 -0
  64. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/setup.cfg +0 -0
  65. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/__init__.py +0 -0
  66. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli/formatter.py +0 -0
  67. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli.egg-info/dependency_links.txt +0 -0
  68. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli.egg-info/entry_points.txt +0 -0
  69. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli.egg-info/requires.txt +0 -0
  70. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/src/asana_api_cli.egg-info/top_level.txt +0 -0
  71. {asana_api_cli-1.3.0 → asana_api_cli-1.5.0}/tests/test_version.py +0 -0
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: asana-api-cli
3
- Version: 1.3.0
3
+ Version: 1.5.0
4
4
  Summary: Command-line wrapper around the official Asana Python SDK
5
5
  Author-email: Masanao Izumo <asana@masanao.site>
6
6
  License-Expression: MIT
7
7
  Project-URL: Homepage, https://github.com/izumo-m/asana-api-cli
8
8
  Project-URL: Repository, https://github.com/izumo-m/asana-api-cli
9
9
  Project-URL: Issues, https://github.com/izumo-m/asana-api-cli/issues
10
+ Project-URL: Changelog, https://github.com/izumo-m/asana-api-cli/blob/main/CHANGELOG.md
10
11
  Requires-Python: >=3.10
11
12
  Description-Content-Type: text/markdown
12
13
  License-File: LICENSE
@@ -65,28 +66,27 @@ and add the line to `~/.zshrc` or `~/.config/fish/config.fish` respectively.
65
66
  ## Usage
66
67
 
67
68
  ```bash
68
- # Show version
69
+ # Version and help
69
70
  asana-api --version
70
-
71
- # List commands
72
71
  asana-api --help
73
72
  asana-api tasks --help
74
73
  asana-api tasks get-tasks --help
75
74
 
76
- # List workspaces
75
+ # List workspaces and projects
77
76
  asana-api workspaces get-workspaces
78
-
79
- # List projects (workspace resolved from ASANA_DEFAULT_WORKSPACE)
80
77
  asana-api projects get-projects-for-workspace
81
78
  asana-api projects get-projects --workspace <WORKSPACE_GID>
82
79
 
83
- # List tasks (first page)
80
+ # List tasks (first page only by default)
84
81
  asana-api tasks get-tasks --project <PROJECT_GID>
85
82
 
86
- # Auto-fetch all pages
87
- asana-api tasks get-tasks --project <PROJECT_GID> --paginate
83
+ # Preview the first few items
84
+ asana-api tasks get-tasks --project <PROJECT_GID> --max-items 5
85
+
86
+ # Fetch every item across pages
87
+ asana-api tasks get-tasks --project <PROJECT_GID> --all-items
88
88
 
89
- # Single task (--task instead of positional argument)
89
+ # Single task
90
90
  asana-api tasks get-task --task <TASK_GID>
91
91
 
92
92
  # Create a task (body is a JSON string)
@@ -97,6 +97,9 @@ asana-api tasks get-tasks --project <PID> --output table
97
97
  asana-api tasks get-tasks --project <PID> --query '.data' --output csv
98
98
  ```
99
99
 
100
+ See [Pagination](#pagination) for fetching across pages and
101
+ [Global options](#global-options) for `--debug`, `--access-token`, etc.
102
+
100
103
  ### Workspace resolution
101
104
 
102
105
  Many API endpoints require a workspace. For those endpoints (e.g.
@@ -110,6 +113,60 @@ fallback is **not** used — pass `--workspace` explicitly if needed. This
110
113
  prevents conflicts with other scope parameters like `--project` that are
111
114
  mutually exclusive with workspace in the Asana API.
112
115
 
116
+ ## Pagination
117
+
118
+ Listing endpoints (e.g. `tasks get-tasks`) return paginated results. The CLI
119
+ provides four ways to control how much is fetched:
120
+
121
+ | Option | Behavior |
122
+ |--------|----------|
123
+ | (none) | Fetch a single page (Asana default: 100 items) |
124
+ | `--max-items N` | Fetch up to N items, auto-paginating across pages. The last request is automatically capped to the remaining count. |
125
+ | `--all-items` | Fetch every page until the server reports no more |
126
+ | `--offset <TOKEN>` | Manual pagination: pass the `next_page.offset` token from the previous response |
127
+
128
+ `--max-items` and `--all-items` are mutually exclusive.
129
+
130
+ `--page-size N` tunes the per-page request size (Asana API requires 1-100,
131
+ default 100). Rarely needed — combine with `--all-items` or `--max-items` only
132
+ when the default doesn't suit (e.g. very large rows or rate-limit tuning).
133
+
134
+ ```bash
135
+ # Auto-paginate up to 250 items
136
+ asana-api tasks get-tasks --project <PID> --max-items 250
137
+
138
+ # Fetch everything
139
+ asana-api tasks get-tasks --project <PID> --all-items
140
+
141
+ # Manual pagination using the offset token
142
+ asana-api tasks get-tasks --project <PID> --offset <TOKEN>
143
+ ```
144
+
145
+ ## Global options
146
+
147
+ These options work at any level of the command tree, so the following are
148
+ equivalent:
149
+
150
+ ```bash
151
+ asana-api --debug tasks get-tasks --project <PID>
152
+ asana-api tasks get-tasks --project <PID> --debug
153
+ ```
154
+
155
+ When the same option is given at multiple levels, the more specific (later)
156
+ one wins.
157
+
158
+ | Option | Description |
159
+ |--------|-------------|
160
+ | `--access-token TOKEN` | Asana personal access token (default: `$ASANA_ACCESS_TOKEN`) |
161
+ | `--host URL` | Override API base URL (default: `https://app.asana.com/api/1.0`) |
162
+ | `--proxy URL` | HTTP/HTTPS proxy URL |
163
+ | `--no-verify-ssl` | Disable TLS certificate verification (insecure) |
164
+ | `--ca-cert PATH` | Path to a PEM bundle of trusted CA certificates |
165
+ | `--retries N` | Number of retries on 429/5xx responses (default: 5) |
166
+ | `--timeout SECONDS` | Per-request timeout in seconds |
167
+ | `--temp-dir PATH` | Directory for temporary downloads |
168
+ | `--debug` | Print HTTP request/response to stderr for troubleshooting |
169
+
113
170
  ## Development
114
171
 
115
172
  See [docs/development.md](https://github.com/izumo-m/asana-api-cli/blob/main/docs/development.md)
@@ -47,28 +47,27 @@ and add the line to `~/.zshrc` or `~/.config/fish/config.fish` respectively.
47
47
  ## Usage
48
48
 
49
49
  ```bash
50
- # Show version
50
+ # Version and help
51
51
  asana-api --version
52
-
53
- # List commands
54
52
  asana-api --help
55
53
  asana-api tasks --help
56
54
  asana-api tasks get-tasks --help
57
55
 
58
- # List workspaces
56
+ # List workspaces and projects
59
57
  asana-api workspaces get-workspaces
60
-
61
- # List projects (workspace resolved from ASANA_DEFAULT_WORKSPACE)
62
58
  asana-api projects get-projects-for-workspace
63
59
  asana-api projects get-projects --workspace <WORKSPACE_GID>
64
60
 
65
- # List tasks (first page)
61
+ # List tasks (first page only by default)
66
62
  asana-api tasks get-tasks --project <PROJECT_GID>
67
63
 
68
- # Auto-fetch all pages
69
- asana-api tasks get-tasks --project <PROJECT_GID> --paginate
64
+ # Preview the first few items
65
+ asana-api tasks get-tasks --project <PROJECT_GID> --max-items 5
66
+
67
+ # Fetch every item across pages
68
+ asana-api tasks get-tasks --project <PROJECT_GID> --all-items
70
69
 
71
- # Single task (--task instead of positional argument)
70
+ # Single task
72
71
  asana-api tasks get-task --task <TASK_GID>
73
72
 
74
73
  # Create a task (body is a JSON string)
@@ -79,6 +78,9 @@ asana-api tasks get-tasks --project <PID> --output table
79
78
  asana-api tasks get-tasks --project <PID> --query '.data' --output csv
80
79
  ```
81
80
 
81
+ See [Pagination](#pagination) for fetching across pages and
82
+ [Global options](#global-options) for `--debug`, `--access-token`, etc.
83
+
82
84
  ### Workspace resolution
83
85
 
84
86
  Many API endpoints require a workspace. For those endpoints (e.g.
@@ -92,6 +94,60 @@ fallback is **not** used — pass `--workspace` explicitly if needed. This
92
94
  prevents conflicts with other scope parameters like `--project` that are
93
95
  mutually exclusive with workspace in the Asana API.
94
96
 
97
+ ## Pagination
98
+
99
+ Listing endpoints (e.g. `tasks get-tasks`) return paginated results. The CLI
100
+ provides four ways to control how much is fetched:
101
+
102
+ | Option | Behavior |
103
+ |--------|----------|
104
+ | (none) | Fetch a single page (Asana default: 100 items) |
105
+ | `--max-items N` | Fetch up to N items, auto-paginating across pages. The last request is automatically capped to the remaining count. |
106
+ | `--all-items` | Fetch every page until the server reports no more |
107
+ | `--offset <TOKEN>` | Manual pagination: pass the `next_page.offset` token from the previous response |
108
+
109
+ `--max-items` and `--all-items` are mutually exclusive.
110
+
111
+ `--page-size N` tunes the per-page request size (Asana API requires 1-100,
112
+ default 100). Rarely needed — combine with `--all-items` or `--max-items` only
113
+ when the default doesn't suit (e.g. very large rows or rate-limit tuning).
114
+
115
+ ```bash
116
+ # Auto-paginate up to 250 items
117
+ asana-api tasks get-tasks --project <PID> --max-items 250
118
+
119
+ # Fetch everything
120
+ asana-api tasks get-tasks --project <PID> --all-items
121
+
122
+ # Manual pagination using the offset token
123
+ asana-api tasks get-tasks --project <PID> --offset <TOKEN>
124
+ ```
125
+
126
+ ## Global options
127
+
128
+ These options work at any level of the command tree, so the following are
129
+ equivalent:
130
+
131
+ ```bash
132
+ asana-api --debug tasks get-tasks --project <PID>
133
+ asana-api tasks get-tasks --project <PID> --debug
134
+ ```
135
+
136
+ When the same option is given at multiple levels, the more specific (later)
137
+ one wins.
138
+
139
+ | Option | Description |
140
+ |--------|-------------|
141
+ | `--access-token TOKEN` | Asana personal access token (default: `$ASANA_ACCESS_TOKEN`) |
142
+ | `--host URL` | Override API base URL (default: `https://app.asana.com/api/1.0`) |
143
+ | `--proxy URL` | HTTP/HTTPS proxy URL |
144
+ | `--no-verify-ssl` | Disable TLS certificate verification (insecure) |
145
+ | `--ca-cert PATH` | Path to a PEM bundle of trusted CA certificates |
146
+ | `--retries N` | Number of retries on 429/5xx responses (default: 5) |
147
+ | `--timeout SECONDS` | Per-request timeout in seconds |
148
+ | `--temp-dir PATH` | Directory for temporary downloads |
149
+ | `--debug` | Print HTTP request/response to stderr for troubleshooting |
150
+
95
151
  ## Development
96
152
 
97
153
  See [docs/development.md](https://github.com/izumo-m/asana-api-cli/blob/main/docs/development.md)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "asana-api-cli"
3
- version = "1.3.0"
3
+ version = "1.5.0"
4
4
  description = "Command-line wrapper around the official Asana Python SDK"
5
5
  authors = [{name = "Masanao Izumo", email = "asana@masanao.site"}]
6
6
  readme = "README.md"
@@ -17,6 +17,7 @@ dependencies = [
17
17
  Homepage = "https://github.com/izumo-m/asana-api-cli"
18
18
  Repository = "https://github.com/izumo-m/asana-api-cli"
19
19
  Issues = "https://github.com/izumo-m/asana-api-cli/issues"
20
+ Changelog = "https://github.com/izumo-m/asana-api-cli/blob/main/CHANGELOG.md"
20
21
 
21
22
  [project.scripts]
22
23
  asana-api = "asana_api_cli.cli:main"
@@ -27,6 +28,7 @@ dev = [
27
28
  "pytest>=9,<10",
28
29
  "build>=1,<2",
29
30
  "twine>=6,<7",
31
+ "vermin>=1.6,<2",
30
32
  ]
31
33
 
32
34
  [build-system]
@@ -0,0 +1,93 @@
1
+ # This file is auto-generated by tools/codegen.py — do not edit manually.
2
+ from __future__ import annotations
3
+
4
+ import click
5
+
6
+ from asana_api_cli.click_ext import LazyGroup
7
+ from asana_api_cli.session import runtime
8
+ from asana_api_cli.version import version_string
9
+
10
+
11
+ LAZY_SUBCOMMANDS: dict[str, tuple[str, str]] = {
12
+ "access-requests": ("asana_api_cli.cli.access_requests:access_requests_group", "AccessRequests commands."),
13
+ "allocations": ("asana_api_cli.cli.allocations:allocations_group", "Allocations commands."),
14
+ "attachments": ("asana_api_cli.cli.attachments:attachments_group", "Attachments commands."),
15
+ "audit-log-api": ("asana_api_cli.cli.audit_log_api:audit_log_api_group", "AuditLogAPI commands."),
16
+ "batch-api": ("asana_api_cli.cli.batch_api:batch_api_group", "BatchAPI commands."),
17
+ "budgets": ("asana_api_cli.cli.budgets:budgets_group", "Budgets commands."),
18
+ "custom-field-settings": ("asana_api_cli.cli.custom_field_settings:custom_field_settings_group", "CustomFieldSettings commands."),
19
+ "custom-fields": ("asana_api_cli.cli.custom_fields:custom_fields_group", "CustomFields commands."),
20
+ "custom-types": ("asana_api_cli.cli.custom_types:custom_types_group", "CustomTypes commands."),
21
+ "events": ("asana_api_cli.cli.events:events_group", "Events commands."),
22
+ "exports": ("asana_api_cli.cli.exports:exports_group", "Exports commands."),
23
+ "goal-relationships": ("asana_api_cli.cli.goal_relationships:goal_relationships_group", "GoalRelationships commands."),
24
+ "goals": ("asana_api_cli.cli.goals:goals_group", "Goals commands."),
25
+ "jobs": ("asana_api_cli.cli.jobs:jobs_group", "Jobs commands."),
26
+ "memberships": ("asana_api_cli.cli.memberships:memberships_group", "Memberships commands."),
27
+ "organization-exports": ("asana_api_cli.cli.organization_exports:organization_exports_group", "OrganizationExports commands."),
28
+ "portfolio-memberships": ("asana_api_cli.cli.portfolio_memberships:portfolio_memberships_group", "PortfolioMemberships commands."),
29
+ "portfolios": ("asana_api_cli.cli.portfolios:portfolios_group", "Portfolios commands."),
30
+ "project-briefs": ("asana_api_cli.cli.project_briefs:project_briefs_group", "ProjectBriefs commands."),
31
+ "project-memberships": ("asana_api_cli.cli.project_memberships:project_memberships_group", "ProjectMemberships commands."),
32
+ "project-portfolio-settings": ("asana_api_cli.cli.project_portfolio_settings:project_portfolio_settings_group", "ProjectPortfolioSettings commands."),
33
+ "project-statuses": ("asana_api_cli.cli.project_statuses:project_statuses_group", "ProjectStatuses commands."),
34
+ "project-templates": ("asana_api_cli.cli.project_templates:project_templates_group", "ProjectTemplates commands."),
35
+ "projects": ("asana_api_cli.cli.projects:projects_group", "Projects commands."),
36
+ "rates": ("asana_api_cli.cli.rates:rates_group", "Rates commands."),
37
+ "reactions": ("asana_api_cli.cli.reactions:reactions_group", "Reactions commands."),
38
+ "roles": ("asana_api_cli.cli.roles:roles_group", "Roles commands."),
39
+ "rules": ("asana_api_cli.cli.rules:rules_group", "Rules commands."),
40
+ "sections": ("asana_api_cli.cli.sections:sections_group", "Sections commands."),
41
+ "status-updates": ("asana_api_cli.cli.status_updates:status_updates_group", "StatusUpdates commands."),
42
+ "stories": ("asana_api_cli.cli.stories:stories_group", "Stories commands."),
43
+ "tags": ("asana_api_cli.cli.tags:tags_group", "Tags commands."),
44
+ "task-templates": ("asana_api_cli.cli.task_templates:task_templates_group", "TaskTemplates commands."),
45
+ "tasks": ("asana_api_cli.cli.tasks:tasks_group", "Tasks commands."),
46
+ "team-memberships": ("asana_api_cli.cli.team_memberships:team_memberships_group", "TeamMemberships commands."),
47
+ "teams": ("asana_api_cli.cli.teams:teams_group", "Teams commands."),
48
+ "time-periods": ("asana_api_cli.cli.time_periods:time_periods_group", "TimePeriods commands."),
49
+ "time-tracking-categories": ("asana_api_cli.cli.time_tracking_categories:time_tracking_categories_group", "TimeTrackingCategories commands."),
50
+ "time-tracking-entries": ("asana_api_cli.cli.time_tracking_entries:time_tracking_entries_group", "TimeTrackingEntries commands."),
51
+ "timesheet-approval-statuses": ("asana_api_cli.cli.timesheet_approval_statuses:timesheet_approval_statuses_group", "TimesheetApprovalStatuses commands."),
52
+ "typeahead": ("asana_api_cli.cli.typeahead:typeahead_group", "Typeahead commands."),
53
+ "user-task-lists": ("asana_api_cli.cli.user_task_lists:user_task_lists_group", "UserTaskLists commands."),
54
+ "users": ("asana_api_cli.cli.users:users_group", "Users commands."),
55
+ "webhooks": ("asana_api_cli.cli.webhooks:webhooks_group", "Webhooks commands."),
56
+ "workspace-memberships": ("asana_api_cli.cli.workspace_memberships:workspace_memberships_group", "WorkspaceMemberships commands."),
57
+ "workspaces": ("asana_api_cli.cli.workspaces:workspaces_group", "Workspaces commands."),
58
+ }
59
+
60
+
61
+ @click.group(cls=LazyGroup, lazy_subcommands=LAZY_SUBCOMMANDS)
62
+ @click.version_option(version_string(), prog_name="asana-api")
63
+ @click.option("--host", default=None, help="Override API base URL (default: https://app.asana.com/api/1.0)")
64
+ @click.option("--proxy", default=None, help="HTTP/HTTPS proxy URL")
65
+ @click.option("--no-verify-ssl", is_flag=True, default=False, help="Disable TLS certificate verification (insecure)")
66
+ @click.option("--ca-cert", "ca_cert", default=None, type=click.Path(exists=True, dir_okay=False), help="Path to a PEM bundle of trusted CA certificates")
67
+ @click.option("--retries", type=int, default=None, help="Number of retries on 429/5xx responses (default: 5)")
68
+ @click.option("--timeout", type=float, default=None, help="Per-request timeout in seconds")
69
+ @click.option("--access-token", "access_token", default=None, help="Asana personal access token (default: $ASANA_ACCESS_TOKEN)")
70
+ @click.option("--temp-dir", "temp_dir", default=None, type=click.Path(file_okay=False), help="Directory for temporary downloads")
71
+ @click.option("--debug", is_flag=True, default=False, help="Print HTTP request/response to stderr for troubleshooting")
72
+ def main(
73
+ host: str | None,
74
+ proxy: str | None,
75
+ no_verify_ssl: bool,
76
+ ca_cert: str | None,
77
+ retries: int | None,
78
+ timeout: float | None,
79
+ access_token: str | None,
80
+ temp_dir: str | None,
81
+ debug: bool,
82
+ ) -> None:
83
+ """Asana API CLI (SDK-backed wrapper)."""
84
+ runtime.host = host
85
+ runtime.proxy = proxy
86
+ runtime.verify_ssl = not no_verify_ssl
87
+ runtime.ssl_ca_cert = ca_cert
88
+ runtime.retries = retries
89
+ runtime.timeout = timeout
90
+ if access_token:
91
+ runtime.access_token = access_token
92
+ runtime.temp_dir = temp_dir
93
+ runtime.debug = debug
@@ -6,11 +6,12 @@ from typing import Any
6
6
  import click
7
7
  from asana import AccessRequestsApi
8
8
 
9
+ from asana_api_cli.click_ext import GroupWithGlobalOptions
9
10
  from asana_api_cli.formatter import formatted
10
11
  from asana_api_cli.session import AsanaSession, resolve_body, resolve_workspace
11
12
 
12
13
 
13
- @click.group("access-requests")
14
+ @click.group("access-requests", cls=GroupWithGlobalOptions)
14
15
  def access_requests_group() -> None:
15
16
  """AccessRequests commands."""
16
17
 
@@ -6,11 +6,12 @@ from typing import Any
6
6
  import click
7
7
  from asana import AllocationsApi
8
8
 
9
+ from asana_api_cli.click_ext import GroupWithGlobalOptions
9
10
  from asana_api_cli.formatter import formatted
10
11
  from asana_api_cli.session import AsanaSession, resolve_body, resolve_workspace
11
12
 
12
13
 
13
- @click.group("allocations")
14
+ @click.group("allocations", cls=GroupWithGlobalOptions)
14
15
  def allocations_group() -> None:
15
16
  """Allocations commands."""
16
17
 
@@ -58,22 +59,30 @@ def get_allocation(allocation: str, opt_fields: str | None) -> Any:
58
59
  @allocations_group.command("get-allocations")
59
60
  @click.option("--workspace", default=None, help="Workspace GID (falls back to ASANA_DEFAULT_WORKSPACE)")
60
61
  @click.option("--assignee", default=None, help="Globally unique identifier for the user or placeholder the allocation is assigned to.")
61
- @click.option("--limit", type=int, default=None, help="Results per page. The number of objects to return per page. The value must be between 1 and 100.")
62
62
  @click.option("--offset", default=None, help="Offset token. An offset to the next page returned by the API. A pagination request will return an offset token, which can be used as an input parameter to the next request. If an offset is not pass...")
63
63
  @click.option("--opt-fields", default=None, help="This endpoint returns a resource which excludes some properties by default. To include those optional properties, set this query parameter to a comma-separated list of the properties you wish to in...")
64
64
  @click.option("--parent", default=None, help="Globally unique identifier for the project to filter allocations by.")
65
- @click.option("--paginate", is_flag=True, default=False, help="Fetch all pages")
65
+ @click.option("--all-items", "all_items", is_flag=True, default=False, help="Fetch all items (no cap)")
66
+ @click.option("--paginate", "paginate", is_flag=True, default=False, help="(Deprecated) Alias for --all-items")
67
+ @click.option("--page-size", "page_size", type=int, default=None, help="Items per page (Asana API requires 1-100, default 100)")
68
+ @click.option("--max-items", "max_items", type=int, default=None, help="Stop after fetching this many items in total")
66
69
  @formatted
67
- def get_allocations(workspace: str | None, assignee: str | None, limit: int | None, offset: str | None, opt_fields: str | None, parent: str | None, paginate: bool) -> Any:
70
+ def get_allocations(workspace: str | None, assignee: str | None, offset: str | None, opt_fields: str | None, parent: str | None, all_items: bool, paginate: bool, page_size: int | None, max_items: int | None) -> Any:
68
71
  """Get multiple allocations"""
69
72
  resolved_workspace = resolve_workspace(workspace, required=False)
70
- session = AsanaSession.from_env(paginate=paginate)
73
+ if paginate:
74
+ click.echo("Warning: --paginate is deprecated; use --all-items instead.", err=True)
75
+ fetch_all = all_items or paginate
76
+ if fetch_all and max_items is not None:
77
+ raise click.UsageError("--max-items cannot be combined with --all-items (or its deprecated alias --paginate)")
78
+ effective_page_size = page_size
79
+ if max_items is not None and (page_size is None or page_size > max_items):
80
+ effective_page_size = max_items
81
+ session = AsanaSession.from_env(paginate=fetch_all, page_size=effective_page_size)
71
82
  api = AllocationsApi(session.client)
72
83
  opts: dict[str, Any] = {}
73
84
  if assignee is not None:
74
85
  opts["assignee"] = assignee
75
- if limit is not None:
76
- opts["limit"] = limit
77
86
  if offset is not None:
78
87
  opts["offset"] = offset
79
88
  if opt_fields is not None:
@@ -82,6 +91,8 @@ def get_allocations(workspace: str | None, assignee: str | None, limit: int | No
82
91
  opts["parent"] = parent
83
92
  if resolved_workspace is not None:
84
93
  opts["workspace"] = resolved_workspace
94
+ if max_items is not None:
95
+ return session.fetch_capped(api.get_allocations, opts=opts, max_items=max_items)
85
96
  return api.get_allocations(opts)
86
97
 
87
98
 
@@ -6,11 +6,12 @@ from typing import Any
6
6
  import click
7
7
  from asana import AttachmentsApi
8
8
 
9
+ from asana_api_cli.click_ext import GroupWithGlobalOptions
9
10
  from asana_api_cli.formatter import formatted
10
11
  from asana_api_cli.session import AsanaSession, resolve_body, resolve_workspace
11
12
 
12
13
 
13
- @click.group("attachments")
14
+ @click.group("attachments", cls=GroupWithGlobalOptions)
14
15
  def attachments_group() -> None:
15
16
  """Attachments commands."""
16
17
 
@@ -73,20 +74,30 @@ def get_attachment(attachment: str, opt_fields: str | None) -> Any:
73
74
 
74
75
  @attachments_group.command("get-attachments-for-object")
75
76
  @click.option("--parent", required=True, help="Globally unique identifier for object to fetch statuses from. Must be a GID for a `project`, `project_brief`, or `task`.")
76
- @click.option("--limit", type=int, default=None, help="Results per page. The number of objects to return per page. The value must be between 1 and 100.")
77
77
  @click.option("--offset", default=None, help="Offset token. An offset to the next page returned by the API. A pagination request will return an offset token, which can be used as an input parameter to the next request. If an offset is not pass...")
78
78
  @click.option("--opt-fields", default=None, help="This endpoint returns a resource which excludes some properties by default. To include those optional properties, set this query parameter to a comma-separated list of the properties you wish to in...")
79
- @click.option("--paginate", is_flag=True, default=False, help="Fetch all pages")
79
+ @click.option("--all-items", "all_items", is_flag=True, default=False, help="Fetch all items (no cap)")
80
+ @click.option("--paginate", "paginate", is_flag=True, default=False, help="(Deprecated) Alias for --all-items")
81
+ @click.option("--page-size", "page_size", type=int, default=None, help="Items per page (Asana API requires 1-100, default 100)")
82
+ @click.option("--max-items", "max_items", type=int, default=None, help="Stop after fetching this many items in total")
80
83
  @formatted
81
- def get_attachments_for_object(parent: str, limit: int | None, offset: str | None, opt_fields: str | None, paginate: bool) -> Any:
84
+ def get_attachments_for_object(parent: str, offset: str | None, opt_fields: str | None, all_items: bool, paginate: bool, page_size: int | None, max_items: int | None) -> Any:
82
85
  """Get attachments from an object"""
83
- session = AsanaSession.from_env(paginate=paginate)
86
+ if paginate:
87
+ click.echo("Warning: --paginate is deprecated; use --all-items instead.", err=True)
88
+ fetch_all = all_items or paginate
89
+ if fetch_all and max_items is not None:
90
+ raise click.UsageError("--max-items cannot be combined with --all-items (or its deprecated alias --paginate)")
91
+ effective_page_size = page_size
92
+ if max_items is not None and (page_size is None or page_size > max_items):
93
+ effective_page_size = max_items
94
+ session = AsanaSession.from_env(paginate=fetch_all, page_size=effective_page_size)
84
95
  api = AttachmentsApi(session.client)
85
96
  opts: dict[str, Any] = {}
86
- if limit is not None:
87
- opts["limit"] = limit
88
97
  if offset is not None:
89
98
  opts["offset"] = offset
90
99
  if opt_fields is not None:
91
100
  opts["opt_fields"] = opt_fields
101
+ if max_items is not None:
102
+ return session.fetch_capped(api.get_attachments_for_object, parent, opts=opts, max_items=max_items)
92
103
  return api.get_attachments_for_object(parent, opts)
@@ -6,11 +6,12 @@ from typing import Any
6
6
  import click
7
7
  from asana import AuditLogAPIApi
8
8
 
9
+ from asana_api_cli.click_ext import GroupWithGlobalOptions
9
10
  from asana_api_cli.formatter import formatted
10
11
  from asana_api_cli.session import AsanaSession, resolve_body, resolve_workspace
11
12
 
12
13
 
13
- @click.group("audit-log-api")
14
+ @click.group("audit-log-api", cls=GroupWithGlobalOptions)
14
15
  def audit_log_api_group() -> None:
15
16
  """AuditLogAPI commands."""
16
17
 
@@ -21,16 +22,26 @@ def audit_log_api_group() -> None:
21
22
  @click.option("--actor-type", default=None, help="Filter to events with an actor of this type. This only needs to be included if querying for actor types without an ID. If `actor_gid` is included, this should be excluded.")
22
23
  @click.option("--end-at", default=None, help="Filter to events created before this time (exclusive).")
23
24
  @click.option("--event-type", default=None, help="Filter to events of this type. Refer to the [supported audit log events](/docs/audit-log-events#supported-audit-log-events) for a full list of values.")
24
- @click.option("--limit", type=int, default=None, help="Results per page. The number of objects to return per page. The value must be between 1 and 100.")
25
25
  @click.option("--offset", default=None, help="Offset token. An offset to the next page returned by the API. A pagination request will return an offset token, which can be used as an input parameter to the next request. If an offset is not pass...")
26
26
  @click.option("--resource-gid", default=None, help="Filter to events with this resource ID.")
27
27
  @click.option("--start-at", default=None, help="Filter to events created after this time (inclusive).")
28
- @click.option("--paginate", is_flag=True, default=False, help="Fetch all pages")
28
+ @click.option("--all-items", "all_items", is_flag=True, default=False, help="Fetch all items (no cap)")
29
+ @click.option("--paginate", "paginate", is_flag=True, default=False, help="(Deprecated) Alias for --all-items")
30
+ @click.option("--page-size", "page_size", type=int, default=None, help="Items per page (Asana API requires 1-100, default 100)")
31
+ @click.option("--max-items", "max_items", type=int, default=None, help="Stop after fetching this many items in total")
29
32
  @formatted
30
- def get_audit_log_events(workspace: str | None, actor_gid: str | None, actor_type: str | None, end_at: str | None, event_type: str | None, limit: int | None, offset: str | None, resource_gid: str | None, start_at: str | None, paginate: bool) -> Any:
33
+ def get_audit_log_events(workspace: str | None, actor_gid: str | None, actor_type: str | None, end_at: str | None, event_type: str | None, offset: str | None, resource_gid: str | None, start_at: str | None, all_items: bool, paginate: bool, page_size: int | None, max_items: int | None) -> Any:
31
34
  """Get audit log events"""
32
35
  resolved_workspace = resolve_workspace(workspace, required=True)
33
- session = AsanaSession.from_env(paginate=paginate)
36
+ if paginate:
37
+ click.echo("Warning: --paginate is deprecated; use --all-items instead.", err=True)
38
+ fetch_all = all_items or paginate
39
+ if fetch_all and max_items is not None:
40
+ raise click.UsageError("--max-items cannot be combined with --all-items (or its deprecated alias --paginate)")
41
+ effective_page_size = page_size
42
+ if max_items is not None and (page_size is None or page_size > max_items):
43
+ effective_page_size = max_items
44
+ session = AsanaSession.from_env(paginate=fetch_all, page_size=effective_page_size)
34
45
  api = AuditLogAPIApi(session.client)
35
46
  opts: dict[str, Any] = {}
36
47
  if actor_gid is not None:
@@ -41,12 +52,12 @@ def get_audit_log_events(workspace: str | None, actor_gid: str | None, actor_typ
41
52
  opts["end_at"] = end_at
42
53
  if event_type is not None:
43
54
  opts["event_type"] = event_type
44
- if limit is not None:
45
- opts["limit"] = limit
46
55
  if offset is not None:
47
56
  opts["offset"] = offset
48
57
  if resource_gid is not None:
49
58
  opts["resource_gid"] = resource_gid
50
59
  if start_at is not None:
51
60
  opts["start_at"] = start_at
61
+ if max_items is not None:
62
+ return session.fetch_capped(api.get_audit_log_events, resolved_workspace, opts=opts, max_items=max_items)
52
63
  return api.get_audit_log_events(resolved_workspace, opts)
@@ -6,11 +6,12 @@ from typing import Any
6
6
  import click
7
7
  from asana import BatchAPIApi
8
8
 
9
+ from asana_api_cli.click_ext import GroupWithGlobalOptions
9
10
  from asana_api_cli.formatter import formatted
10
11
  from asana_api_cli.session import AsanaSession, resolve_body, resolve_workspace
11
12
 
12
13
 
13
- @click.group("batch-api")
14
+ @click.group("batch-api", cls=GroupWithGlobalOptions)
14
15
  def batch_api_group() -> None:
15
16
  """BatchAPI commands."""
16
17
 
@@ -6,11 +6,12 @@ from typing import Any
6
6
  import click
7
7
  from asana import BudgetsApi
8
8
 
9
+ from asana_api_cli.click_ext import GroupWithGlobalOptions
9
10
  from asana_api_cli.formatter import formatted
10
11
  from asana_api_cli.session import AsanaSession, resolve_body, resolve_workspace
11
12
 
12
13
 
13
- @click.group("budgets")
14
+ @click.group("budgets", cls=GroupWithGlobalOptions)
14
15
  def budgets_group() -> None:
15
16
  """Budgets commands."""
16
17