asana-api-cli 1.2.0__py3-none-any.whl

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 (57) hide show
  1. asana_api_cli/__init__.py +3 -0
  2. asana_api_cli/cli/__init__.py +140 -0
  3. asana_api_cli/cli/access_requests.py +66 -0
  4. asana_api_cli/cli/allocations.py +101 -0
  5. asana_api_cli/cli/attachments.py +92 -0
  6. asana_api_cli/cli/audit_log_api.py +52 -0
  7. asana_api_cli/cli/batch_api.py +30 -0
  8. asana_api_cli/cli/budgets.py +79 -0
  9. asana_api_cli/cli/custom_field_settings.py +92 -0
  10. asana_api_cli/cli/custom_fields.py +133 -0
  11. asana_api_cli/cli/custom_types.py +50 -0
  12. asana_api_cli/cli/events.py +32 -0
  13. asana_api_cli/cli/exports.py +39 -0
  14. asana_api_cli/cli/goal_relationships.py +98 -0
  15. asana_api_cli/cli/goals.py +217 -0
  16. asana_api_cli/cli/jobs.py +29 -0
  17. asana_api_cli/cli/memberships.py +89 -0
  18. asana_api_cli/cli/organization_exports.py +44 -0
  19. asana_api_cli/cli/portfolio_memberships.py +83 -0
  20. asana_api_cli/cli/portfolios.py +215 -0
  21. asana_api_cli/cli/project_briefs.py +72 -0
  22. asana_api_cli/cli/project_memberships.py +53 -0
  23. asana_api_cli/cli/project_portfolio_settings.py +87 -0
  24. asana_api_cli/cli/project_statuses.py +77 -0
  25. asana_api_cli/cli/project_templates.py +102 -0
  26. asana_api_cli/cli/projects.py +380 -0
  27. asana_api_cli/cli/rates.py +97 -0
  28. asana_api_cli/cli/reactions.py +34 -0
  29. asana_api_cli/cli/roles.py +98 -0
  30. asana_api_cli/cli/rules.py +28 -0
  31. asana_api_cli/cli/sections.py +111 -0
  32. asana_api_cli/cli/status_updates.py +86 -0
  33. asana_api_cli/cli/stories.py +130 -0
  34. asana_api_cli/cli/tags.py +155 -0
  35. asana_api_cli/cli/task_templates.py +77 -0
  36. asana_api_cli/cli/tasks.py +520 -0
  37. asana_api_cli/cli/team_memberships.py +103 -0
  38. asana_api_cli/cli/teams.py +133 -0
  39. asana_api_cli/cli/time_periods.py +57 -0
  40. asana_api_cli/cli/time_tracking_categories.py +123 -0
  41. asana_api_cli/cli/time_tracking_entries.py +138 -0
  42. asana_api_cli/cli/timesheet_approval_statuses.py +94 -0
  43. asana_api_cli/cli/typeahead.py +40 -0
  44. asana_api_cli/cli/user_task_lists.py +45 -0
  45. asana_api_cli/cli/users.py +173 -0
  46. asana_api_cli/cli/webhooks.py +96 -0
  47. asana_api_cli/cli/workspace_memberships.py +75 -0
  48. asana_api_cli/cli/workspaces.py +113 -0
  49. asana_api_cli/formatter.py +161 -0
  50. asana_api_cli/session.py +173 -0
  51. asana_api_cli/version.py +11 -0
  52. asana_api_cli-1.2.0.dist-info/METADATA +105 -0
  53. asana_api_cli-1.2.0.dist-info/RECORD +57 -0
  54. asana_api_cli-1.2.0.dist-info/WHEEL +5 -0
  55. asana_api_cli-1.2.0.dist-info/entry_points.txt +2 -0
  56. asana_api_cli-1.2.0.dist-info/licenses/LICENSE +190 -0
  57. asana_api_cli-1.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,161 @@
1
+ from __future__ import annotations
2
+
3
+ import contextlib
4
+ import csv
5
+ import functools
6
+ import io
7
+ import json
8
+ import sys
9
+ from typing import Any
10
+
11
+ import click
12
+ import jq as jqlib
13
+ from asana.rest import ApiException
14
+ from tabulate import tabulate
15
+
16
+
17
+ def formatted(f: Any) -> Any:
18
+ """Decorator that adds --output / --query and auto-formats the returned dict."""
19
+
20
+ @click.option(
21
+ "--output",
22
+ "output_format",
23
+ type=click.Choice(["json", "table", "csv", "text"], case_sensitive=False),
24
+ default="json",
25
+ help="Output format (default: json)",
26
+ )
27
+ @click.option(
28
+ "--query",
29
+ "jq_query",
30
+ default=None,
31
+ help="jq expression to filter output",
32
+ )
33
+ @functools.wraps(f)
34
+ def wrapper(*args: Any, output_format: str, jq_query: str | None, **kwargs: Any) -> None:
35
+ try:
36
+ data = f(*args, **kwargs)
37
+ # Collapse the asana SDK PageIterator / generator into a list
38
+ if not isinstance(data, (dict, list, str, int, float, bool, type(None))):
39
+ with contextlib.suppress(TypeError):
40
+ data = list(data)
41
+ except ApiException as e:
42
+ _handle_api_exception(e)
43
+ _format_output(data, output_format=output_format, jq_query=jq_query)
44
+
45
+ return wrapper
46
+
47
+
48
+ def _handle_api_exception(e: ApiException) -> None:
49
+ """Print an Asana API error in human-readable form and exit."""
50
+ from asana_api_cli.session import runtime
51
+
52
+ status = e.status or "error"
53
+ messages: list[str] = []
54
+ body = e.body
55
+ if isinstance(body, bytes):
56
+ with contextlib.suppress(UnicodeDecodeError):
57
+ body = body.decode("utf-8")
58
+ if isinstance(body, str):
59
+ with contextlib.suppress(json.JSONDecodeError):
60
+ payload = json.loads(body)
61
+ if isinstance(payload, dict):
62
+ for err in payload.get("errors") or []:
63
+ if isinstance(err, dict) and "message" in err:
64
+ messages.append(str(err["message"]))
65
+ if not messages:
66
+ messages.append(e.reason or "Unknown API error")
67
+ for msg in messages:
68
+ click.echo(f"Error ({status}): {msg}", err=True)
69
+ # When the body was not JSON, show a hint and,
70
+ # in debug mode, dump the raw body so the user can diagnose the issue.
71
+ if isinstance(body, str) and body and not _is_json(body):
72
+ click.echo(
73
+ "The server returned a non-JSON response. "
74
+ "Re-run with --debug to see the full response body.",
75
+ err=True,
76
+ )
77
+ if runtime.debug:
78
+ click.echo("--- raw response body ---", err=True)
79
+ click.echo(body, err=True)
80
+ click.echo("--- end of response body ---", err=True)
81
+ sys.exit(1)
82
+
83
+
84
+ def _is_json(text: str) -> bool:
85
+ """Return True if *text* looks like JSON."""
86
+ try:
87
+ json.loads(text)
88
+ except (json.JSONDecodeError, ValueError):
89
+ return False
90
+ return True
91
+
92
+
93
+ def _format_output(data: Any, *, output_format: str, jq_query: str | None) -> None:
94
+ if jq_query:
95
+ try:
96
+ data = jqlib.first(jq_query, data)
97
+ except ValueError as e:
98
+ click.echo(f"Invalid jq expression: {e}", err=True)
99
+ sys.exit(1)
100
+
101
+ if output_format == "text":
102
+ _print_text(data)
103
+ return
104
+
105
+ if output_format == "json":
106
+ click.echo(json.dumps(data, indent=2, ensure_ascii=False))
107
+ return
108
+
109
+ rows = _to_rows(data)
110
+ if rows is None:
111
+ click.echo(data)
112
+ return
113
+
114
+ if output_format == "table":
115
+ click.echo(tabulate(rows, headers="keys", tablefmt="simple"))
116
+ elif output_format == "csv":
117
+ _print_csv(rows)
118
+
119
+
120
+ def _to_rows(data: Any) -> list[dict[str, Any]] | None:
121
+ """Convert data into a list of dicts for table/csv. Return None if not possible."""
122
+ if isinstance(data, list):
123
+ if not data:
124
+ return []
125
+ if isinstance(data[0], dict):
126
+ return data
127
+ return [{"value": v} for v in data]
128
+ if isinstance(data, dict):
129
+ return [data]
130
+ return None
131
+
132
+
133
+ def _print_text(data: Any) -> None:
134
+ """Print data in plain text format (like ``aws --output text``)."""
135
+ if data is None:
136
+ click.echo("None")
137
+ return
138
+ if isinstance(data, (str, int, float, bool)):
139
+ click.echo(data)
140
+ return
141
+ if isinstance(data, dict):
142
+ click.echo("\t".join(str(v) for v in data.values()))
143
+ return
144
+ if isinstance(data, list):
145
+ for item in data:
146
+ if isinstance(item, dict):
147
+ click.echo("\t".join(str(v) for v in item.values()))
148
+ else:
149
+ click.echo(item)
150
+ return
151
+ click.echo(data)
152
+
153
+
154
+ def _print_csv(rows: list[dict[str, Any]]) -> None:
155
+ if not rows:
156
+ return
157
+ buf = io.StringIO()
158
+ writer = csv.DictWriter(buf, fieldnames=list(rows[0].keys()))
159
+ writer.writeheader()
160
+ writer.writerows(rows)
161
+ click.echo(buf.getvalue(), nl=False)
@@ -0,0 +1,173 @@
1
+ """Asana SDK client construction utilities.
2
+
3
+ A thin wrapper around the official `asana` SDK ApiClient that handles
4
+ initialization from environment variables, toggling pagination mode, and
5
+ applying the global configuration passed in from the CLI.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import functools
10
+ import json
11
+ import os
12
+ import sys
13
+ from dataclasses import dataclass
14
+ from pathlib import Path
15
+ from typing import Any
16
+
17
+ import asana
18
+ from urllib3.util.retry import Retry
19
+
20
+ DEFAULT_TOKEN_ENV = "ASANA_ACCESS_TOKEN"
21
+ DEFAULT_WORKSPACE_ENV = "ASANA_DEFAULT_WORKSPACE"
22
+
23
+
24
+ def resolve_body(value: str) -> Any:
25
+ """Parse a body argument as JSON.
26
+
27
+ Supports three input forms:
28
+ - ``@path`` — read JSON from a file
29
+ - ``-`` — read JSON from stdin
30
+ - otherwise — parse the string itself as JSON
31
+ """
32
+ if value == "-":
33
+ raw = sys.stdin.read()
34
+ elif value.startswith("@"):
35
+ path = Path(value[1:])
36
+ try:
37
+ raw = path.read_text(encoding="utf-8")
38
+ except FileNotFoundError:
39
+ print(f"Body file not found: {path}", file=sys.stderr)
40
+ sys.exit(1)
41
+ except OSError as exc:
42
+ print(f"Cannot read body file {path}: {exc}", file=sys.stderr)
43
+ sys.exit(1)
44
+ else:
45
+ raw = value
46
+
47
+ try:
48
+ return json.loads(raw)
49
+ except json.JSONDecodeError as exc:
50
+ print(f"Invalid JSON in body: {exc}", file=sys.stderr)
51
+ sys.exit(1)
52
+
53
+
54
+ @dataclass
55
+ class _Runtime:
56
+ """Configuration shared globally during a CLI invocation. Updated by the main group callback."""
57
+
58
+ debug: bool = False
59
+ host: str | None = None
60
+ proxy: str | None = None
61
+ verify_ssl: bool = True
62
+ ssl_ca_cert: str | None = None
63
+ page_limit: int | None = None
64
+ retries: int | None = None
65
+ timeout: float | None = None
66
+ token_env: str = DEFAULT_TOKEN_ENV
67
+ temp_dir: str | None = None
68
+
69
+
70
+ runtime = _Runtime()
71
+
72
+
73
+ class AsanaSession:
74
+ """Session that holds an ApiClient from the official asana SDK."""
75
+
76
+ def __init__(self, token: str, *, paginate: bool = False) -> None:
77
+ config = asana.Configuration()
78
+ config.access_token = token
79
+ # When --paginate is set, the SDK returns a PageIterator that walks every page.
80
+ config.return_page_iterator = paginate
81
+
82
+ # Apply runtime values to Configuration
83
+ if runtime.host:
84
+ config.host = runtime.host
85
+ if runtime.proxy:
86
+ config.proxy = runtime.proxy
87
+ if not runtime.verify_ssl:
88
+ config.verify_ssl = False
89
+ if runtime.ssl_ca_cert:
90
+ config.ssl_ca_cert = runtime.ssl_ca_cert
91
+ if runtime.page_limit is not None:
92
+ config.page_limit = runtime.page_limit
93
+ if runtime.temp_dir:
94
+ config.temp_folder_path = runtime.temp_dir
95
+ if runtime.retries is not None:
96
+ # Replace only `total` while keeping the existing backoff/status_forcelist.
97
+ config.retry_strategy = Retry(
98
+ total=runtime.retries,
99
+ backoff_factor=2,
100
+ status_forcelist=[429, 500, 502, 503, 504],
101
+ )
102
+ if runtime.debug:
103
+ # The SDK debug setter attaches a stderr handler to the urllib3/asana
104
+ # loggers and enables http.client.HTTPConnection.debuglevel.
105
+ config.debug = True
106
+
107
+ self._config = config
108
+ self._client = asana.ApiClient(config)
109
+
110
+ # Configuration has no --timeout knob, so wrap call_api to inject it.
111
+ if runtime.timeout is not None:
112
+ self._install_timeout(runtime.timeout)
113
+
114
+ def _install_timeout(self, timeout: float) -> None:
115
+ """Wrap ApiClient.call_api to inject a default _request_timeout."""
116
+ original = self._client.call_api
117
+
118
+ @functools.wraps(original)
119
+ def call_api_with_timeout(*args: Any, **kwargs: Any) -> Any:
120
+ kwargs.setdefault("_request_timeout", timeout)
121
+ return original(*args, **kwargs)
122
+
123
+ self._client.call_api = call_api_with_timeout # type: ignore[method-assign]
124
+
125
+ @property
126
+ def client(self) -> asana.ApiClient:
127
+ return self._client
128
+
129
+ def api(self, api_class: type) -> object:
130
+ """Take a <Tag>Api class and return an instance bound to this session."""
131
+ return api_class(self._client)
132
+
133
+ @classmethod
134
+ def from_env(cls, *, paginate: bool = False) -> "AsanaSession":
135
+ """Build a session from environment variables (variable name from runtime.token_env)."""
136
+ var = runtime.token_env or DEFAULT_TOKEN_ENV
137
+ token = os.environ.get(var, "")
138
+ if not token:
139
+ print(f"{var} environment variable is not set", file=sys.stderr)
140
+ sys.exit(1)
141
+ return cls(token=token, paginate=paginate)
142
+
143
+
144
+ def resolve_workspace(
145
+ explicit: str | None,
146
+ *,
147
+ required: bool = False,
148
+ ) -> str | None:
149
+ """Resolve workspace GID with fallback chain.
150
+
151
+ Priority: explicit ``--workspace`` value > ``ASANA_DEFAULT_WORKSPACE``
152
+ env var (only when *required* is True).
153
+
154
+ When workspace is optional (``required=False``), the env-var fallback is
155
+ **not** used. This prevents the default workspace from being sent
156
+ alongside other scope parameters (e.g. ``--project`` on ``get-tasks``)
157
+ that are mutually exclusive with workspace in the Asana API.
158
+
159
+ If *required* is True and no value is found, exits with an error.
160
+ """
161
+ if explicit is not None:
162
+ return explicit
163
+ if required:
164
+ ws = os.environ.get(DEFAULT_WORKSPACE_ENV)
165
+ if ws:
166
+ return ws
167
+ print(
168
+ f"Workspace is required. Specify --workspace or "
169
+ f"set {DEFAULT_WORKSPACE_ENV}.",
170
+ file=sys.stderr,
171
+ )
172
+ sys.exit(1)
173
+ return None
@@ -0,0 +1,11 @@
1
+ """Version information for the CLI."""
2
+ from __future__ import annotations
3
+
4
+ from importlib.metadata import version
5
+
6
+
7
+ def version_string() -> str:
8
+ """Return a version string including the python-asana SDK version."""
9
+ cli_ver = version("asana-api-cli")
10
+ sdk_ver = version("asana")
11
+ return f"{cli_ver} (python-asana {sdk_ver})"
@@ -0,0 +1,105 @@
1
+ Metadata-Version: 2.4
2
+ Name: asana-api-cli
3
+ Version: 1.2.0
4
+ Summary: Command-line wrapper around the official Asana Python SDK
5
+ Author-email: Masanao Izumo <asana@masanao.site>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/izumo-m/asana-api-cli
8
+ Project-URL: Repository, https://github.com/izumo-m/asana-api-cli
9
+ Project-URL: Issues, https://github.com/izumo-m/asana-api-cli/issues
10
+ Requires-Python: >=3.12
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: click<9,>=8.1
14
+ Requires-Dist: jq<2,>=1.8
15
+ Requires-Dist: tabulate<1,>=0.9
16
+ Requires-Dist: asana<6,>=5.2.4
17
+ Dynamic: license-file
18
+
19
+ # asana-api-cli
20
+
21
+ A CLI tool for the Asana API. It thinly wraps the official
22
+ [python-asana](https://github.com/Asana/python-asana) SDK with click, exposing
23
+ every API endpoint from the command line via `asana-api <group> <command>`.
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ pip install asana-api-cli
29
+
30
+ # or, to install as an isolated CLI tool
31
+ pipx install asana-api-cli
32
+ ```
33
+
34
+ ## Environment variables
35
+
36
+ | Name | Required | Description |
37
+ |------|----------|-------------|
38
+ | `ASANA_ACCESS_TOKEN` | Yes (at runtime only) | Asana Personal Access Token |
39
+ | `ASANA_DEFAULT_WORKSPACE` | No | Default workspace GID for endpoints that require it |
40
+
41
+ The token can be issued from the
42
+ [Asana Developer Console](https://app.asana.com/0/developer-console).
43
+ No token is needed for `--help` or argument-error output.
44
+
45
+ ```bash
46
+ export ASANA_ACCESS_TOKEN="1/12345..."
47
+ export ASANA_DEFAULT_WORKSPACE="12345678" # optional
48
+ ```
49
+
50
+ ## Usage
51
+
52
+ ```bash
53
+ # Show version
54
+ asana-api --version
55
+
56
+ # List commands
57
+ asana-api --help
58
+ asana-api tasks --help
59
+ asana-api tasks get-tasks --help
60
+
61
+ # List workspaces
62
+ asana-api workspaces get-workspaces
63
+
64
+ # List projects (workspace resolved from ASANA_DEFAULT_WORKSPACE)
65
+ asana-api projects get-projects-for-workspace
66
+ asana-api projects get-projects --workspace <WORKSPACE_GID>
67
+
68
+ # List tasks (first page)
69
+ asana-api tasks get-tasks --project <PROJECT_GID>
70
+
71
+ # Auto-fetch all pages
72
+ asana-api tasks get-tasks --project <PROJECT_GID> --paginate
73
+
74
+ # Single task (--task instead of positional argument)
75
+ asana-api tasks get-task --task <TASK_GID>
76
+
77
+ # Create a task (body is a JSON string)
78
+ asana-api tasks create-task --body '{"data":{"name":"new task","projects":["<PID>"]}}'
79
+
80
+ # Output formats
81
+ asana-api tasks get-tasks --project <PID> --output table
82
+ asana-api tasks get-tasks --project <PID> --query '.data' --output csv
83
+ ```
84
+
85
+ ### Workspace resolution
86
+
87
+ Many API endpoints require a workspace. For those endpoints (e.g.
88
+ `get-projects-for-workspace`), the CLI resolves it in this order:
89
+
90
+ 1. `--workspace <GID>` on the command
91
+ 2. `ASANA_DEFAULT_WORKSPACE` environment variable
92
+
93
+ For endpoints where workspace is optional (e.g. `get-tasks`), the env-var
94
+ fallback is **not** used — pass `--workspace` explicitly if needed. This
95
+ prevents conflicts with other scope parameters like `--project` that are
96
+ mutually exclusive with workspace in the Asana API.
97
+
98
+ ## Development
99
+
100
+ See [docs/development.md](https://github.com/izumo-m/asana-api-cli/blob/main/docs/development.md)
101
+ for building from source, project layout, and library usage.
102
+
103
+ ## License
104
+
105
+ [Apache License 2.0](https://github.com/izumo-m/asana-api-cli/blob/main/LICENSE)
@@ -0,0 +1,57 @@
1
+ asana_api_cli/__init__.py,sha256=VwCvAdoxu2DuaYGGMVBTXB-DgPagEeKIpop-BaiLL7k,75
2
+ asana_api_cli/formatter.py,sha256=V5-rfMtMwcAbW-P0KPGa05RKUWOjHUkpXsXibmDfVok,4965
3
+ asana_api_cli/session.py,sha256=s3f01vk1LE7U4wO_7rdKgO7XkeoVjeuC0WnQZC6qEg4,5746
4
+ asana_api_cli/version.py,sha256=Hhyl20g8y07oEoBHBhJrNre92HlEkVV_kdDnvKxXsh0,338
5
+ asana_api_cli/cli/__init__.py,sha256=0MjxSiKGnHcWUWuqEucGLBvXPQk7TQUjc9VTtyoFeo0,6711
6
+ asana_api_cli/cli/access_requests.py,sha256=XZxooEyLKjpcWalqvakfcdPx6dZzQypNTZqn_htzVyc,2851
7
+ asana_api_cli/cli/allocations.py,sha256=l6TRGWd9TTi05_Nona5wYorT9bbjgtboUJ3cZbn9jaY,5236
8
+ asana_api_cli/cli/attachments.py,sha256=19kyRq-Bzt4c6ajjJNvoIl-PD1JLpx5C4o7Lg4c2N1M,4603
9
+ asana_api_cli/cli/audit_log_api.py,sha256=OJxWaTqtVySR22k05sgIAD_vH4k0wQVmGErDVl6Qb9c,2960
10
+ asana_api_cli/cli/batch_api.py,sha256=oukiisYeU-eXrVCo808-bW5Ljqinz5GOVvd953IeE70,1178
11
+ asana_api_cli/cli/budgets.py,sha256=aw3WcZgaEqMOYzT4l6yZwyBFe0kVtoLcXO7b3OY03ec,3245
12
+ asana_api_cli/cli/custom_field_settings.py,sha256=73MeEuTIzXmX93rUp7OXJLdT0IkNOW376nKta0e35dI,5709
13
+ asana_api_cli/cli/custom_fields.py,sha256=hxXS7fPNQE0E0OvfS9020_8d7yjJsftROslo1oeb07Q,7132
14
+ asana_api_cli/cli/custom_types.py,sha256=vahRL3JhDm8Q4m96dcSpG2spAySa9jFijVSzhL0iXEU,2639
15
+ asana_api_cli/cli/events.py,sha256=DesmksXsaf2keXF-pJuuZsHGY5TEXkh7snVIw7g5p68,1446
16
+ asana_api_cli/cli/exports.py,sha256=iGYghD6XbLskieJvcdtbzFGujioBPiHJaGltp6qFq64,1442
17
+ asana_api_cli/cli/goal_relationships.py,sha256=67S3g7raB3-Y3w8iBlQC7mikJiRNx1aKp3VOFyMGBRY,5544
18
+ asana_api_cli/cli/goals.py,sha256=6hlmyNFIW2h4Hun96Plun7ypjzcMts5tDO6xet6wW5c,11206
19
+ asana_api_cli/cli/jobs.py,sha256=zhLLxYuUXgVebOjNFTL5KtidoIlTmcoxQZqICLPnjb8,1046
20
+ asana_api_cli/cli/memberships.py,sha256=Xbi2Ax1P6eg8joN7PGgASKiyqOk-1WMXWVpPNvgXRwM,4443
21
+ asana_api_cli/cli/organization_exports.py,sha256=h00T2xWvrJmtA84RNM8D6UuCadhnvxtAAD7B36qJMTk,2087
22
+ asana_api_cli/cli/portfolio_memberships.py,sha256=2gyih0Wt0_Oq7q5Jbi_b3_67pC1r7m2fZxmelWBI3iA,4961
23
+ asana_api_cli/cli/portfolios.py,sha256=jsxR9nwQ3VE1EqGXYVLxPKOulPpUJJpLDLI3WwoloG4,11701
24
+ asana_api_cli/cli/project_briefs.py,sha256=POnb6UUQukVQ-B1_X904s8-HcMeZTVGEp-0gMMX2nBw,3479
25
+ asana_api_cli/cli/project_memberships.py,sha256=0LutTGJA4vkboniOpU6tu97JLKFZD19wWCakhpBfR7M,2885
26
+ asana_api_cli/cli/project_portfolio_settings.py,sha256=TXjv8YGnHxoYHGWx80cHvQZrhj_oEsA0EkxyCvrOURU,5480
27
+ asana_api_cli/cli/project_statuses.py,sha256=11L1RBGLX2v6PBvw1vCbVLwOUxVg7ndzJV8JLSR47ak,4064
28
+ asana_api_cli/cli/project_templates.py,sha256=YwKAsqhmE9ZvynoeM64CCuvFUz5FDRccMSW0pReDAEs,5760
29
+ asana_api_cli/cli/projects.py,sha256=9BTLESTDi5HXc4b0qy5hKokbNXYmd9nYlwXemnAtjpo,21997
30
+ asana_api_cli/cli/rates.py,sha256=lLSDhEiCSIGAEsyMyS_9GN1FhUXanU3VfvrDx8M9B2A,4603
31
+ asana_api_cli/cli/reactions.py,sha256=Gqof-upq3ton7Bi0-3LlJ5c12ibEMXec8VIngwP6Nho,1690
32
+ asana_api_cli/cli/roles.py,sha256=Wk4_RPNqmS9IJjvHXWFDAVWtWlxoEYTPJiM5ibGv4zU,4756
33
+ asana_api_cli/cli/rules.py,sha256=hlbQV97Mma-1wXyUtHmg9K0Z6d20gDhSg_ADnM_mC38,1063
34
+ asana_api_cli/cli/sections.py,sha256=hoqDuzbDb4I9lV41OAFGPbwVyiZYknKltiy020pntz0,5249
35
+ asana_api_cli/cli/status_updates.py,sha256=549jdi_rdTi0tfvpg8KK5I8sv0rO5bmvBIaPplUnGpg,4764
36
+ asana_api_cli/cli/stories.py,sha256=NgeiVl5yZNyKwJXgtOKciqV8rRkZzW41w5PNg-CDfmE,6803
37
+ asana_api_cli/cli/tags.py,sha256=he7LMNsQ2TbGyJOZSHqpIVa7mAJOEoE7CulEkrZctM4,8310
38
+ asana_api_cli/cli/task_templates.py,sha256=ct2H4NcDFaXCMuOnNvioRLanZx83z6PezyUwFnmmMVE,3923
39
+ asana_api_cli/cli/tasks.py,sha256=u-p-E6tWzARDx8npli4NK3hwSFki1I1QtPfYrUrkc50,29615
40
+ asana_api_cli/cli/team_memberships.py,sha256=kmti-zH5Vh3Mr0zDJbIkXt9zkh_V23wQHTmJxhpIXyo,6335
41
+ asana_api_cli/cli/teams.py,sha256=3pEaF2A-AgPPbzoTcWA2ruLfbudW-y-iadBbIkhB1QM,7103
42
+ asana_api_cli/cli/time_periods.py,sha256=E55GFasCeHqV0ycIbsGV3RnZLyrYxY9Og6iZhOuife4,2955
43
+ asana_api_cli/cli/time_tracking_categories.py,sha256=PUUpBzaC5YRj0swh4oB8suBNYL9-4tlaQBMI-lI43pY,7651
44
+ asana_api_cli/cli/time_tracking_entries.py,sha256=AtSgSdwWwEeIOKHz0DROy15jgC7BSwlzX6Q_Zmek148,8543
45
+ asana_api_cli/cli/timesheet_approval_statuses.py,sha256=FV3wGdeVQdLzpL4Fp8tzHs9ryp4L2kq-V8sZfjL85l4,5720
46
+ asana_api_cli/cli/typeahead.py,sha256=0Cw24ttyoTIvQ56omb03_oPxTh7tkAB8md67D3dTEZ4,2300
47
+ asana_api_cli/cli/user_task_lists.py,sha256=FUqnOk_t31cAk7hrXm_9Om-uV6NaUaWhectZa9jPitU,2224
48
+ asana_api_cli/cli/users.py,sha256=E62aDKkBjirx9Xp4bM8-y7NA9FiPC1NCizxGP2qCF-I,10462
49
+ asana_api_cli/cli/webhooks.py,sha256=Wv7M1gTJn1jyyJEorlcItpgRkFfC849hZmDZ41me7L4,4799
50
+ asana_api_cli/cli/workspace_memberships.py,sha256=5JcFCA9KEgxlahQ2NCGLUfMymFAewg_zhGj7KQLNP2E,4580
51
+ asana_api_cli/cli/workspaces.py,sha256=vv5XhNEMQGXP4aNyzql5bZp_tRbybDp2i0RSmX4q3Z4,5982
52
+ asana_api_cli-1.2.0.dist-info/licenses/LICENSE,sha256=0x0LkhzzvBg1yYZvRGGOZ3ZZUVLmP4sK_jyUljmH4Pg,10764
53
+ asana_api_cli-1.2.0.dist-info/METADATA,sha256=0PcCgX_q6LdOOmHyHKzjmpUWP7QsTerCuz_OTDstjAE,3221
54
+ asana_api_cli-1.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
55
+ asana_api_cli-1.2.0.dist-info/entry_points.txt,sha256=dcNQUEQQh7ODXPh9TsCsj1jHycMgIJ7uFpz0Wwi1_UU,53
56
+ asana_api_cli-1.2.0.dist-info/top_level.txt,sha256=Hj5dHD-kV-G8pIp46m76N9spxBOwzxQ1Rh1ocdzoXw4,14
57
+ asana_api_cli-1.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ asana-api = asana_api_cli.cli:main