trustarc-cli 1.0.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.
ta/lib/config.py ADDED
@@ -0,0 +1,65 @@
1
+ import json
2
+ from pathlib import Path
3
+
4
+ from ta.lib.catalog_cache import load_catalog
5
+ from ta.lib.services import ENVS, services_for
6
+
7
+ CONFIG_DIR = Path.home() / ".ta"
8
+ CONFIG_FILE = CONFIG_DIR / "config.json"
9
+
10
+ DEFAULT_CONFIG = {
11
+ "env": "prod-us",
12
+ }
13
+
14
+
15
+ def _deep_merge(base: dict, override: dict) -> dict:
16
+ result = base.copy()
17
+ for key, value in override.items():
18
+ if key in result and isinstance(result[key], dict) and isinstance(value, dict):
19
+ result[key] = _deep_merge(result[key], value)
20
+ else:
21
+ result[key] = value
22
+ return result
23
+
24
+
25
+ class UnknownEnvError(ValueError):
26
+ def __init__(self, env: str):
27
+ self.env = env
28
+ super().__init__(
29
+ f"Saved env '{env}' is not supported. Run `ta config setup` to pick one of: {', '.join(ENVS)}."
30
+ )
31
+
32
+
33
+ def _read_config() -> dict:
34
+ if CONFIG_FILE.exists():
35
+ with open(CONFIG_FILE) as f:
36
+ return _deep_merge(DEFAULT_CONFIG, json.load(f))
37
+ return dict(DEFAULT_CONFIG)
38
+
39
+
40
+ def saved_env() -> str:
41
+ env = _read_config()["env"]
42
+ if env not in ENVS:
43
+ raise UnknownEnvError(env)
44
+ return env
45
+
46
+
47
+ def load_config(refresh_catalog: bool = False) -> dict:
48
+ cfg = _read_config()
49
+ if cfg["env"] not in ENVS:
50
+ raise UnknownEnvError(cfg["env"])
51
+
52
+ try:
53
+ catalog = load_catalog(cfg["env"], force=refresh_catalog)
54
+ except RuntimeError:
55
+ catalog = {}
56
+ cfg["catalog"] = catalog
57
+ cfg["services"] = services_for(catalog)
58
+ return cfg
59
+
60
+
61
+ def save_config(config: dict) -> Path:
62
+ CONFIG_DIR.mkdir(exist_ok=True)
63
+ with open(CONFIG_FILE, "w") as f:
64
+ json.dump(config, f, indent=2)
65
+ return CONFIG_FILE
ta/lib/dynamic.py ADDED
@@ -0,0 +1,259 @@
1
+ import json
2
+ import keyword
3
+ import platform
4
+ import sys
5
+ import time
6
+
7
+ import httpx
8
+ import typer
9
+ from rich.console import Console
10
+ from trustarc_sdk.openapi import (
11
+ Entrypoint,
12
+ build_schema_snapshot,
13
+ call,
14
+ fetch_spec,
15
+ group_operations_by_resource,
16
+ resolve_base_url,
17
+ )
18
+
19
+ from ta import __version__
20
+ from ta.lib.analytics import report_tool_call
21
+ from ta.lib.auth import get_access_token
22
+ from ta.lib.auth_servers import FEATURE_MANAGER_API_URLS
23
+ from ta.lib.config import UnknownEnvError, load_config
24
+ from ta.lib.spec_cache import SPEC_CACHE_TTL_SECONDS, load_spec, save_spec, spec_cache_path
25
+ from ta.lib.urls import to_public_url
26
+
27
+ console = Console()
28
+
29
+ _USER_AGENT = " ".join(
30
+ (
31
+ f"python/{sys.version.split()[0]}",
32
+ f"{platform.system()}/{platform.release()}",
33
+ f"trustarc-cli/{__version__}",
34
+ )
35
+ )
36
+ _SOURCE = "cli"
37
+ _WRITE_EPILOG = "Changes data in TrustArc. Confirm the change before running it."
38
+ _WRITE_EPILOG_WITH_BODY = (
39
+ "Changes data in TrustArc. Check the request body with --schema and confirm the change before running it."
40
+ )
41
+
42
+
43
+ def register_dynamic_commands(app: typer.Typer) -> None:
44
+ try:
45
+ cfg = load_config()
46
+ except UnknownEnvError:
47
+ return
48
+ env = cfg["env"]
49
+ token = get_access_token(env) or ""
50
+ active_service = sys.argv[1] if len(sys.argv) > 1 else None
51
+
52
+ catalog = cfg["catalog"]
53
+ for service, spec_url in sorted(cfg["services"].items()):
54
+ entry = catalog[service]
55
+ prefix = entry["prefix"]
56
+ include_tags = entry["include_tags"]
57
+ description = entry["description"]
58
+ full_name = entry["name"] or service
59
+ service_app = typer.Typer(help=f"{full_name} — {description}", no_args_is_help=True)
60
+ _register_refresh(service_app, service, env, spec_url)
61
+
62
+ if service == active_service and _is_stale(service, env):
63
+ try:
64
+ save_spec(service, env, fetch_spec(spec_url))
65
+ except httpx.HTTPError:
66
+ pass
67
+
68
+ spec = load_spec(service, env)
69
+ if spec:
70
+ base_url = to_public_url(resolve_base_url(spec, spec_url))
71
+ grouped = group_operations_by_resource(
72
+ spec,
73
+ prefix,
74
+ include_tags=include_tags,
75
+ feature_manager_url=FEATURE_MANAGER_API_URLS.get(env),
76
+ access_token=token,
77
+ )
78
+ for resource in sorted(grouped):
79
+ resource_app = typer.Typer(help=f"{resource} operations", no_args_is_help=True)
80
+ for action, op in grouped[resource]:
81
+ _register_operation(resource_app, service, resource, action, op, base_url, spec)
82
+ service_app.add_typer(resource_app, name=resource)
83
+
84
+ app.add_typer(service_app, name=service)
85
+
86
+
87
+ def _is_stale(service: str, env: str) -> bool:
88
+ path = spec_cache_path(service, env)
89
+ if not path.exists():
90
+ return True
91
+ return (time.time() - path.stat().st_mtime) > SPEC_CACHE_TTL_SECONDS
92
+
93
+
94
+ def _register_refresh(service_app: typer.Typer, service: str, env: str, spec_url: str) -> None:
95
+ @service_app.command("refresh", help=f"Fetch and cache the {service} OpenAPI spec.")
96
+ def refresh():
97
+ console.print(f"[bold]{service}[/bold] {spec_url}")
98
+ try:
99
+ spec = fetch_spec(spec_url)
100
+ except httpx.HTTPError as e:
101
+ console.print(f" [red]failed:[/red] {e}")
102
+ raise typer.Exit(1) from e
103
+ path = save_spec(service, env, spec)
104
+ grouped = group_operations_by_resource(spec)
105
+ total = sum(len(ops) for ops in grouped.values())
106
+ console.print(f" [green]cached[/green] {path} [dim]({len(grouped)} resources, {total} ops)[/dim]")
107
+
108
+
109
+ def _register_operation(
110
+ sub: typer.Typer, service: str, resource: str, action: str, op: dict, base_url: str, spec: dict
111
+ ) -> None:
112
+ help_text = op.get("summary") or op.get("description") or f"{op['method']} {op['path']}"
113
+
114
+ path_params = [p for p in op["parameters"] if p.get("in") == "path"]
115
+ query_params = [p for p in op["parameters"] if p.get("in") == "query"]
116
+ has_body = op["request_body"] is not None
117
+ schema_snapshot = build_schema_snapshot(op, spec)
118
+
119
+ seen_py_names: set[str] = set()
120
+ path_py = [(_py_name(p["name"], seen_py_names), p["name"]) for p in path_params]
121
+ query_py = [(_py_name(p["name"], seen_py_names), p["name"]) for p in query_params]
122
+
123
+ def make_runner(
124
+ op=op,
125
+ base_url=base_url,
126
+ path_py=path_py,
127
+ query_py=query_py,
128
+ has_body=has_body,
129
+ ):
130
+ def runner(**kwargs):
131
+ kwargs.pop("schema", None)
132
+ cfg = load_config()
133
+ path_values, query_values, body = split_request(path_py, query_py, has_body, kwargs)
134
+
135
+ token = get_access_token(cfg["env"]) or ""
136
+ try:
137
+ resp = call(
138
+ op,
139
+ base_url,
140
+ token,
141
+ None,
142
+ path_params=path_values,
143
+ query_params=query_values,
144
+ body=body,
145
+ user_agent=_USER_AGENT,
146
+ entrypoint=Entrypoint.CLI,
147
+ source=_SOURCE,
148
+ feature_manager_url=FEATURE_MANAGER_API_URLS.get(cfg["env"]),
149
+ )
150
+ except Exception:
151
+ report_tool_call(cfg["env"], token, service, resource, action, op["method"], None, "request_failed")
152
+ raise
153
+ report_tool_call(cfg["env"], token, service, resource, action, op["method"], resp.status_code)
154
+
155
+ try:
156
+ console.print_json(data=resp.json())
157
+ except ValueError:
158
+ console.print(resp.text)
159
+ if resp.status_code >= 400:
160
+ raise typer.Exit(1)
161
+
162
+ return runner
163
+
164
+ runner = make_runner()
165
+ params: list[tuple[str, object, object]] = []
166
+ for (py_name, _), p in zip(path_py, path_params):
167
+ params.append((py_name, typer.Argument(..., help=p.get("description", "")), str))
168
+ for (py_name, api_name), p in zip(query_py, query_params):
169
+ default = ... if p.get("required") else None
170
+ flag = f"--{api_name.replace('_', '-')}"
171
+ params.append((py_name, typer.Option(default, flag, help=p.get("description", "")), str))
172
+ if has_body:
173
+ params.append(
174
+ ("body", typer.Option(None, "--body", help="JSON request body. Run with --schema to see its fields."), str)
175
+ )
176
+ params.append(
177
+ (
178
+ "schema",
179
+ typer.Option(
180
+ False,
181
+ "--schema",
182
+ is_eager=True,
183
+ callback=_schema_printer(schema_snapshot),
184
+ help="Print request/response schema and exit",
185
+ ),
186
+ bool,
187
+ )
188
+ )
189
+
190
+ runner.__name__ = action.replace("-", "_")
191
+ runner.__doc__ = help_text
192
+ runner.__signature__ = _build_signature(params) # type: ignore[attr-defined]
193
+ epilog = None
194
+ if op["method"].upper() != "GET":
195
+ epilog = _WRITE_EPILOG_WITH_BODY if has_body else _WRITE_EPILOG
196
+ sub.command(name=action, help=help_text, epilog=epilog)(runner)
197
+
198
+
199
+ def _schema_printer(schema_snapshot: dict):
200
+ def print_schema(value: bool) -> None:
201
+ if value:
202
+ console.print_json(data=schema_snapshot)
203
+ raise typer.Exit()
204
+
205
+ return print_schema
206
+
207
+
208
+ def split_request(
209
+ path_py: list[tuple[str, str]],
210
+ query_py: list[tuple[str, str]],
211
+ has_body: bool,
212
+ kwargs: dict,
213
+ ) -> tuple[dict, dict, dict | list | None]:
214
+ """Keep path, query and body apart so a body field can never change the request target."""
215
+ path_values = {api_name: kwargs[py_name] for py_name, api_name in path_py}
216
+ query_values = {api_name: kwargs[py_name] for py_name, api_name in query_py if kwargs.get(py_name) is not None}
217
+ body: dict | list | None = None
218
+ if has_body and kwargs.get("body"):
219
+ try:
220
+ body = json.loads(kwargs["body"])
221
+ except ValueError as e:
222
+ raise typer.BadParameter(f"--body must be valid JSON: {e}", param_hint="--body") from e
223
+ if isinstance(body, dict):
224
+ given = {**path_values, **query_values}
225
+ conflicting = sorted(key for key in body if key in given and str(body[key]) != str(given[key]))
226
+ if conflicting:
227
+ raise typer.BadParameter(
228
+ f"--body fields {', '.join(conflicting)} disagree with the argument or option of the same "
229
+ "name; the arguments and options decide the target, so make the body match them or leave "
230
+ "those fields out",
231
+ param_hint="--body",
232
+ )
233
+ return path_values, query_values, body
234
+
235
+
236
+ def _py_name(api_name: str, seen: set[str]) -> str:
237
+ name = api_name.replace("-", "_")
238
+ if not name.isidentifier() or keyword.iskeyword(name):
239
+ name = f"_{name}"
240
+ if not name.isidentifier():
241
+ name = "arg_" + "".join(c if c.isalnum() or c == "_" else "_" for c in name)
242
+ base = name
243
+ i = 2
244
+ while name in seen:
245
+ name = f"{base}_{i}"
246
+ i += 1
247
+ seen.add(name)
248
+ return name
249
+
250
+
251
+ def _build_signature(params: list[tuple[str, object, object]]):
252
+ import inspect
253
+
254
+ return inspect.Signature(
255
+ parameters=[
256
+ inspect.Parameter(name, kind=inspect.Parameter.KEYWORD_ONLY, default=default, annotation=annotation)
257
+ for name, default, annotation in params
258
+ ]
259
+ )
ta/lib/services.py ADDED
@@ -0,0 +1,13 @@
1
+ ENVS = ("dev", "qa", "prod-us", "prod-eu", "prod-india")
2
+
3
+
4
+ def spec_url(service: str, catalog: dict) -> str:
5
+ entry = catalog[service]
6
+ return f"{entry['base_url'].rstrip('/')}{entry['spec_path']}"
7
+
8
+
9
+ def services_for(catalog: dict) -> dict[str, str]:
10
+ return {service: spec_url(service, catalog) for service in catalog if catalog[service].get("base_url")}
11
+
12
+
13
+ __all__ = ["ENVS", "services_for", "spec_url"]
ta/lib/spec_cache.py ADDED
@@ -0,0 +1,25 @@
1
+ import json
2
+ from pathlib import Path
3
+
4
+ SPEC_DIR = Path.home() / ".ta" / "openapi"
5
+ SPEC_CACHE_TTL_SECONDS = 600
6
+
7
+
8
+ def spec_cache_path(service: str, env: str) -> Path:
9
+ return SPEC_DIR / env / f"{service}.json"
10
+
11
+
12
+ def save_spec(service: str, env: str, spec: dict) -> Path:
13
+ path = spec_cache_path(service, env)
14
+ path.parent.mkdir(parents=True, exist_ok=True)
15
+ with open(path, "w") as f:
16
+ json.dump(spec, f)
17
+ return path
18
+
19
+
20
+ def load_spec(service: str, env: str) -> dict | None:
21
+ path = spec_cache_path(service, env)
22
+ if not path.exists():
23
+ return None
24
+ with open(path) as f:
25
+ return json.load(f)
ta/lib/update_check.py ADDED
@@ -0,0 +1,66 @@
1
+ import contextlib
2
+ import json
3
+ import os
4
+ import sys
5
+ import time
6
+ from pathlib import Path
7
+
8
+ import httpx
9
+ from packaging.version import InvalidVersion, Version
10
+
11
+ from ta import __version__
12
+
13
+ PACKAGE_NAME = "trustarc-cli"
14
+ PYPI_URL = f"https://pypi.org/pypi/{PACKAGE_NAME}/json"
15
+ CACHE_FILE = Path.home() / ".ta" / "update_check.json"
16
+ CHECK_INTERVAL_SECONDS = 24 * 60 * 60
17
+ _TIMEOUT_S = 2.0
18
+
19
+
20
+ def fetch_latest_version() -> str | None:
21
+ with contextlib.suppress(httpx.HTTPError, ValueError, KeyError, TypeError):
22
+ resp = httpx.get(PYPI_URL, timeout=_TIMEOUT_S)
23
+ resp.raise_for_status()
24
+ return resp.json()["info"]["version"]
25
+ return None
26
+
27
+
28
+ def _read_cache() -> dict:
29
+ with contextlib.suppress(OSError, ValueError):
30
+ return json.loads(CACHE_FILE.read_text())
31
+ return {}
32
+
33
+
34
+ def _write_cache(latest: str | None) -> None:
35
+ with contextlib.suppress(OSError):
36
+ CACHE_FILE.parent.mkdir(parents=True, exist_ok=True)
37
+ CACHE_FILE.write_text(json.dumps({"checked_at": time.time(), "latest": latest}))
38
+
39
+
40
+ def latest_version(*, force: bool = False) -> str | None:
41
+ cache = _read_cache()
42
+ if not force and time.time() - cache.get("checked_at", 0) < CHECK_INTERVAL_SECONDS:
43
+ return cache.get("latest")
44
+ latest = fetch_latest_version() or cache.get("latest")
45
+ _write_cache(latest)
46
+ return latest
47
+
48
+
49
+ def is_newer(latest: str | None, current: str = __version__) -> bool:
50
+ if not latest:
51
+ return False
52
+ try:
53
+ return Version(latest) > Version(current)
54
+ except InvalidVersion:
55
+ return False
56
+
57
+
58
+ def notify_if_outdated() -> None:
59
+ if os.getenv("TA_NO_UPDATE_CHECK"):
60
+ return
61
+ latest = latest_version()
62
+ if is_newer(latest):
63
+ print(
64
+ f"A new version of trustarc-cli is available: {__version__} → {latest}. Run: ta self-update",
65
+ file=sys.stderr,
66
+ )
ta/lib/urls.py ADDED
@@ -0,0 +1,18 @@
1
+ from urllib.parse import urlsplit, urlunsplit
2
+
3
+ INTERNAL_SUFFIX = ".prod.internal"
4
+ _API_SUFFIX = "-api"
5
+
6
+
7
+ def to_public_url(url: str | None) -> str | None:
8
+ if not url or INTERNAL_SUFFIX not in url:
9
+ return url
10
+
11
+ parts = urlsplit(url)
12
+ host = parts.netloc.replace(INTERNAL_SUFFIX, "", 1)
13
+
14
+ first, dot, rest = host.partition(".")
15
+ if first.endswith(_API_SUFFIX):
16
+ host = f"{first[: -len(_API_SUFFIX)]}{dot}{rest}"
17
+
18
+ return urlunsplit(parts._replace(netloc=host))
ta/main.py ADDED
@@ -0,0 +1,69 @@
1
+ import typer
2
+
3
+ from ta import __version__
4
+ from ta.commands import auth as auth_mod
5
+ from ta.commands import config as config_mod
6
+ from ta.commands.self_update import self_update
7
+ from ta.commands.services import services as services_cmd
8
+ from ta.lib.config import UnknownEnvError, saved_env
9
+ from ta.lib.dynamic import register_dynamic_commands
10
+ from ta.lib.update_check import is_newer, latest_version, notify_if_outdated
11
+
12
+ _RECOVERY_COMMANDS = ("config", "self-update")
13
+
14
+ app = typer.Typer(
15
+ name="ta",
16
+ help="Command-line interface for the TrustArc platform APIs.",
17
+ no_args_is_help=True,
18
+ invoke_without_command=True,
19
+ epilog=(
20
+ "Start with `ta auth login` (and `ta config show` to check the env), then `ta services` and "
21
+ "`ta <service> <resource> --help` to find the command. Responses are JSON on stdout. "
22
+ "Before a create, update, patch or delete, run it with --schema to see the request body "
23
+ "and confirm the change first."
24
+ ),
25
+ )
26
+
27
+
28
+ def version_callback(value: bool):
29
+ if value:
30
+ msg = f"trustarc-cli {__version__}"
31
+ latest = latest_version(force=True)
32
+ if is_newer(latest):
33
+ msg += f" (latest: {latest} — run: ta self-update)"
34
+ elif latest:
35
+ msg += " (up to date)"
36
+ typer.echo(msg)
37
+ raise typer.Exit()
38
+
39
+
40
+ @app.callback()
41
+ def main(
42
+ ctx: typer.Context,
43
+ version: bool = typer.Option(
44
+ False,
45
+ "--version",
46
+ "-v",
47
+ callback=version_callback,
48
+ is_eager=True,
49
+ help="Show the version and check for updates.",
50
+ ),
51
+ ):
52
+ if ctx.invoked_subcommand not in _RECOVERY_COMMANDS:
53
+ try:
54
+ saved_env()
55
+ except UnknownEnvError as e:
56
+ typer.echo(str(e), err=True)
57
+ raise typer.Exit(1) from e
58
+ if ctx.invoked_subcommand != "self-update":
59
+ notify_if_outdated()
60
+
61
+
62
+ app.command("self-update")(self_update)
63
+ app.command("services")(services_cmd)
64
+ app.add_typer(auth_mod.app, name="auth")
65
+ app.add_typer(config_mod.app, name="config")
66
+ register_dynamic_commands(app)
67
+
68
+ if __name__ == "__main__":
69
+ app()
@@ -0,0 +1,114 @@
1
+ Metadata-Version: 2.5
2
+ Name: trustarc-cli
3
+ Version: 1.0.0
4
+ Summary: Command-line interface for the TrustArc platform APIs
5
+ Project-URL: Homepage, https://trustarc.com
6
+ Author: TrustArc
7
+ Keywords: cli,compliance,privacy,trustarc
8
+ Classifier: Environment :: Console
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Requires-Python: >=3.13
14
+ Requires-Dist: httpx>=0.28.1
15
+ Requires-Dist: inflection>=0.5.1
16
+ Requires-Dist: packaging>=24
17
+ Requires-Dist: rich>=13.0
18
+ Requires-Dist: typer>=0.12
19
+ Description-Content-Type: text/markdown
20
+
21
+ # trustarc-cli
22
+
23
+ `ta` is the command-line interface for the TrustArc platform APIs. Commands are built from each product's live API spec, so new endpoints show up without upgrading the CLI.
24
+
25
+ Use it directly from your terminal, in scripts, or as a tool for AI agents such as Claude Code, Codex and Cursor.
26
+
27
+ ## Install
28
+
29
+ With [uv](https://docs.astral.sh/uv/getting-started/installation/) (installs Python 3.13 for you if needed):
30
+
31
+ ```bash
32
+ uv tool install trustarc-cli
33
+ ```
34
+
35
+ Or with [pipx](https://pipx.pypa.io) (requires Python 3.13+):
36
+
37
+ ```bash
38
+ pipx install trustarc-cli
39
+ ```
40
+
41
+ ## Update
42
+
43
+ ```bash
44
+ ta self-update
45
+ ```
46
+
47
+ `ta` checks for a new release once a day and prints a notice to stderr when one is available. Set `TA_NO_UPDATE_CHECK=1` to turn the check off (e.g. in CI).
48
+
49
+ ## Quick start
50
+
51
+ ```bash
52
+ ta config setup # pick your region (prod-us/prod-eu/prod-india)
53
+ ta auth login # OAuth login via browser (PKCE)
54
+ ta services # list configured services for this env
55
+ ta dmrm business-process list # example call
56
+ ```
57
+
58
+ ## Use with AI agents
59
+
60
+ `ta` is built to be driven by an AI agent as well as by a person:
61
+
62
+ - API responses are printed as JSON on stdout, and the update notice goes to stderr, so output can be parsed as-is.
63
+ - `--help` and `--schema` let the agent discover operations and their request/response shapes without calling the API.
64
+ - Log in once with `ta auth login`; the agent reuses the saved token.
65
+
66
+ Then ask your agent something like:
67
+
68
+ > Use the `ta` CLI to list my business processes in TrustArc. Run `ta services` and `--help` first to find the right command.
69
+
70
+ ## Command tree
71
+
72
+ ```
73
+ ta
74
+ ├── auth
75
+ │ ├── login # OAuth login (browser + PKCE)
76
+ │ ├── logout # revoke + clear local tokens
77
+ │ └── status # current token expiry / user
78
+ ├── config
79
+ │ ├── show # current env + login state
80
+ │ ├── setup # pick region
81
+ │ └── reset # delete ~/.ta/config.json
82
+ ├── services # list all services for the current env
83
+ ├── self-update # upgrade to the latest release
84
+ └── <service> # e.g. dmrm, tc, am, cpm, ccm, irm, pc, cst, evidence, org
85
+ ├── refresh # re-fetch this service's OpenAPI spec
86
+ └── <resource> # e.g. business-process, trust-center
87
+ └── <action> [args] # e.g. list, get-by-internal-id, upsert
88
+ ```
89
+
90
+ ### Inspecting operations
91
+
92
+ ```bash
93
+ ta <service> --help # list resources
94
+ ta <service> <resource> --help # list actions
95
+ ta <service> <resource> <action> --help # show args/options
96
+ ta <service> <resource> <action> --schema # request/response schema (no API call)
97
+ ```
98
+
99
+ ### Passing arguments
100
+
101
+ - **Path params** → positional: `ta dmrm business-process get-by-internal-id <id>`
102
+ - **Query params** → `--flag`: `ta tc subscription list --since 2026-01-01`
103
+ - **Body** → `--body '<json>'`: `ta dmrm business-process upsert --body '{"internalId":"...","name":"..."}'`
104
+
105
+ ### Regions and auth
106
+
107
+ Tokens are stored per env at `~/.ta/auth/<env>.json`. Spec caches at `~/.ta/openapi/<env>/<service>.json` (10-minute TTL; auto-refreshed when stale).
108
+
109
+ Switch region:
110
+
111
+ ```bash
112
+ ta config setup # change default region
113
+ ta auth login --env prod-eu # log into a specific region without changing default
114
+ ```
@@ -0,0 +1,31 @@
1
+ ta/__init__.py,sha256=J-j-u0itpEFT6irdmWmixQqYMadNl1X91TxUmoiLHMI,22
2
+ ta/main.py,sha256=gv-vu4yH87QNCGal31gr44IC3gUPjizJBk8qPYceTj8,2106
3
+ ta/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ ta/commands/auth/__init__.py,sha256=5MIEqVZ5cGgG8mCOV71lthtO6olpVAZBOaKCcN9U-3U,48
5
+ ta/commands/auth/command.py,sha256=agebaFnR7HoYVBlM1HCtIZlu2ORjGtFIT65uBLcpe-g,3019
6
+ ta/commands/config/__init__.py,sha256=0aWe-DWx7aoYZDLEsWbuL05pRxxfDg1OgrjU3-PCMkQ,50
7
+ ta/commands/config/command.py,sha256=wzzfJkY2TnAfdIfnPujZh6xSdUuT7G-65OKcXoX7UXc,1906
8
+ ta/commands/self_update/__init__.py,sha256=A9mBjfV37eOEmhIjPHELvzFnZvBQD1vwTQj2qgTrPpk,150
9
+ ta/commands/self_update/command.py,sha256=uyhGH-dQ7ZA17hw8op6bmFG1FGmDQlJE6Z3Nckk1Zhw,1105
10
+ ta/commands/services/__init__.py,sha256=EEIrAK25APADsml7ye959peakp2-GibuazzzA580Gr8,62
11
+ ta/commands/services/command.py,sha256=FTWwQXkd6jpfkV-fa7ne6rC1uWUDdTv5ekWbGZ9H2s0,1360
12
+ ta/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ ta/lib/analytics.py,sha256=lQaJZdq_whg1A-DSTSBtS3S53VLmX6Adk42yxtLQrvM,1524
14
+ ta/lib/auth.py,sha256=PIp-uAuyx2Zx6qnhukY4PQtS9AiIflOBD5fNndeiVLU,6794
15
+ ta/lib/auth_servers.py,sha256=DQk-1Cqiot6l4izy3fKKEpNx0oMTxM8wUQcsLhsndyE,1931
16
+ ta/lib/catalog_cache.py,sha256=rhx2CZdOHMcyJluPOZvA8UdKQFnqmhOqKrXszsaWHtU,2151
17
+ ta/lib/config.py,sha256=QQTlRPEeWRpSIsNMWJO3ybFq6R-nZ5Xr6VhpSFfKrZI,1682
18
+ ta/lib/dynamic.py,sha256=wDkClvMGUo05bKProBaE9RMtRv2vyEN1Egjak9lLaIE,9497
19
+ ta/lib/services.py,sha256=uIZOwkJfwGEASfvEpW1szQpsTeXk92knWtOxbjHvCqI,414
20
+ ta/lib/spec_cache.py,sha256=l3xlHb6DIisnpELAwBYru3sof-2-NEtIKOb5QCmxU-w,635
21
+ ta/lib/update_check.py,sha256=W9wQJxMMtqyjyTNgSuQ-gkE8SPwl6SZomFwNI5QVEKU,1873
22
+ ta/lib/urls.py,sha256=8qrQEh06e8460c2BLfzx1aC_DvIQ4Fto85DO2yv-C6Q,493
23
+ trustarc_sdk/openapi/__init__.py,sha256=qiMYPXPtP5pB0nGEyzMI1GmL6sh1XQ6YK6HegmOwoTY,981
24
+ trustarc_sdk/openapi/call.py,sha256=RdUWtLqfvgcDZLhhT-Fqgx5MhsxtLzwAfYak6Pfr_NM,6460
25
+ trustarc_sdk/openapi/feature_manager.py,sha256=2-uurrK0ZBNzwA1sjyGJjHVaPstGZbU0wyrWSiHbknY,3327
26
+ trustarc_sdk/openapi/parse.py,sha256=JqmTKL-86SIZLWElQODOaENQvhEekCxZh59FCa8esFw,5933
27
+ trustarc_sdk/openapi/schema.py,sha256=2s95Y_dX9-z7xPNcxEx2a7pd9qgqMjIlh-lhesjuIHU,3440
28
+ trustarc_cli-1.0.0.dist-info/METADATA,sha256=NSeZl5zNsbJUUMkBoj8N6qE15S3ZcopuRpJuq_o5pyA,4167
29
+ trustarc_cli-1.0.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
30
+ trustarc_cli-1.0.0.dist-info/entry_points.txt,sha256=Cs_ki__IkcU8u-22kgTRG5ujLtDIOXLwNDJeC0Idwk4,35
31
+ trustarc_cli-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ta = ta.main:app