caraer-cli 0.1.2__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 (83) hide show
  1. caraer_cli/__init__.py +4 -0
  2. caraer_cli/api/__init__.py +1 -0
  3. caraer_cli/api/apps.py +78 -0
  4. caraer_cli/api/auth.py +53 -0
  5. caraer_cli/api/client.py +179 -0
  6. caraer_cli/api/functions.py +72 -0
  7. caraer_cli/api/installation.py +93 -0
  8. caraer_cli/api/integration_runtime.py +77 -0
  9. caraer_cli/api/projects.py +213 -0
  10. caraer_cli/api/sandboxes.py +29 -0
  11. caraer_cli/api/webhooks.py +59 -0
  12. caraer_cli/app_sync.py +697 -0
  13. caraer_cli/apps_local.py +274 -0
  14. caraer_cli/commands/__init__.py +1 -0
  15. caraer_cli/commands/apps.py +2025 -0
  16. caraer_cli/commands/auth.py +173 -0
  17. caraer_cli/commands/company.py +68 -0
  18. caraer_cli/commands/installation.py +182 -0
  19. caraer_cli/commands/profile.py +239 -0
  20. caraer_cli/commands/publish.py +40 -0
  21. caraer_cli/commands/sandbox.py +92 -0
  22. caraer_cli/commands/skill.py +165 -0
  23. caraer_cli/commands/webhooks.py +68 -0
  24. caraer_cli/completion.py +77 -0
  25. caraer_cli/completion_callbacks.py +162 -0
  26. caraer_cli/context.py +41 -0
  27. caraer_cli/errors.py +84 -0
  28. caraer_cli/formatters/__init__.py +1 -0
  29. caraer_cli/formatters/output.py +381 -0
  30. caraer_cli/formatters/timestamps.py +98 -0
  31. caraer_cli/install_completion.py +38 -0
  32. caraer_cli/local_app.py +135 -0
  33. caraer_cli/main.py +104 -0
  34. caraer_cli/models/__init__.py +1 -0
  35. caraer_cli/models/api.py +52 -0
  36. caraer_cli/project/__init__.py +17 -0
  37. caraer_cli/project/app_bars_sync.py +154 -0
  38. caraer_cli/project/app_manifest_template.py +68 -0
  39. caraer_cli/project/inbound_sync.py +192 -0
  40. caraer_cli/project/json_schemas.py +66 -0
  41. caraer_cli/project/lifecycle_sync.py +119 -0
  42. caraer_cli/project/local_dev.py +825 -0
  43. caraer_cli/project/manifest_edit.py +51 -0
  44. caraer_cli/project/marketplace_assemble.py +165 -0
  45. caraer_cli/project/oauth_providers_sync.py +180 -0
  46. caraer_cli/project/paths.py +102 -0
  47. caraer_cli/project/pricing_sync.py +81 -0
  48. caraer_cli/project/push_plan.py +590 -0
  49. caraer_cli/project/release.py +128 -0
  50. caraer_cli/project/scaffold.py +537 -0
  51. caraer_cli/project/schedules_sync.py +170 -0
  52. caraer_cli/project/schema.py +160 -0
  53. caraer_cli/project/settings_sync.py +77 -0
  54. caraer_cli/project/state.py +51 -0
  55. caraer_cli/project/sync.py +308 -0
  56. caraer_cli/project/typegen.py +278 -0
  57. caraer_cli/project/validate_app.py +1215 -0
  58. caraer_cli/project/webhooks_sync.py +233 -0
  59. caraer_cli/resolve.py +55 -0
  60. caraer_cli/schemas/app.caraer.schema.json +175 -0
  61. caraer_cli/schemas/function.caraer.schema.json +29 -0
  62. caraer_cli/schemas/inbound.caraer.schema.json +49 -0
  63. caraer_cli/schemas/lifecycle.caraer.schema.json +50 -0
  64. caraer_cli/schemas/schedule.caraer.schema.json +39 -0
  65. caraer_cli/schemas/webhook.caraer.schema.json +50 -0
  66. caraer_cli/skills/.gitkeep +2 -0
  67. caraer_cli/skills/caraer-apps/SKILL.md +172 -0
  68. caraer_cli/skills/caraer-apps/reference.md +163 -0
  69. caraer_cli/state/__init__.py +1 -0
  70. caraer_cli/state/config.py +148 -0
  71. caraer_cli/state/session.py +98 -0
  72. caraer_cli/utils.py +40 -0
  73. caraer_cli/wizard/__init__.py +9 -0
  74. caraer_cli/wizard/catalog.py +170 -0
  75. caraer_cli/wizard/marketplace.py +350 -0
  76. caraer_cli/wizard/prompts.py +99 -0
  77. caraer_cli/wizard/public_app.py +519 -0
  78. caraer_cli/wizard/scope_macros.py +214 -0
  79. caraer_cli-0.1.2.dist-info/METADATA +266 -0
  80. caraer_cli-0.1.2.dist-info/RECORD +83 -0
  81. caraer_cli-0.1.2.dist-info/WHEEL +4 -0
  82. caraer_cli-0.1.2.dist-info/entry_points.txt +2 -0
  83. caraer_cli-0.1.2.dist-info/licenses/LICENSE +8 -0
caraer_cli/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ """Caraer external developer CLI."""
2
+
3
+ __all__ = ["__version__"]
4
+ __version__ = "0.1.2"
@@ -0,0 +1 @@
1
+ """API client modules for Caraer backend domains."""
caraer_cli/api/apps.py ADDED
@@ -0,0 +1,78 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from caraer_cli.api.client import CaraerApiClient
6
+
7
+
8
+ def list_apps(
9
+ client: CaraerApiClient,
10
+ *,
11
+ app_type: str,
12
+ page: int,
13
+ limit: int,
14
+ query: str | None = None,
15
+ ) -> dict[str, Any]:
16
+ if app_type == "my":
17
+ return client.request(
18
+ "POST",
19
+ "/api/v2/apps/my/index",
20
+ json_body={
21
+ "filters": [],
22
+ "sort": [{"key": "name", "direction": "asc"}],
23
+ "show": [],
24
+ "limit": limit,
25
+ "page": page,
26
+ "query": query or "",
27
+ },
28
+ )
29
+ return client.request(
30
+ "POST",
31
+ "/api/v2/apps/index",
32
+ params={"type": app_type},
33
+ json_body={
34
+ "filters": [],
35
+ "sort": [{"key": "name", "direction": "asc"}],
36
+ "show": [],
37
+ "limit": limit,
38
+ "page": page,
39
+ "query": query or "",
40
+ },
41
+ )
42
+
43
+
44
+ def get_app(client: CaraerApiClient, app_uuid: str) -> dict[str, Any]:
45
+ return client.request("GET", f"/api/v2/apps/{app_uuid}")
46
+
47
+
48
+ def get_public_app(client: CaraerApiClient, app_uuid: str) -> dict[str, Any]:
49
+ return client.request("GET", f"/api/v2/apps/public/{app_uuid}")
50
+
51
+
52
+ def create_public_app(client: CaraerApiClient, payload: dict[str, Any]) -> dict[str, Any]:
53
+ return client.request("POST", "/api/v2/apps/public", json_body=payload)
54
+
55
+
56
+ def update_public_app(client: CaraerApiClient, app_uuid: str, payload: dict[str, Any]) -> dict[str, Any]:
57
+ return client.request("PUT", f"/api/v2/apps/public/{app_uuid}", json_body=payload)
58
+
59
+
60
+ def migrate_app_to_v2(
61
+ client: CaraerApiClient,
62
+ app_uuid: str,
63
+ *,
64
+ runtime: str | None = None,
65
+ ) -> dict[str, Any]:
66
+ body: dict[str, Any] = {}
67
+ if runtime:
68
+ body["runtime"] = runtime
69
+ return client.request(
70
+ "POST",
71
+ f"/api/v2/apps/{app_uuid}/migrate-v2",
72
+ json_body=body,
73
+ timeout_seconds=max(client.context.timeout_seconds, 60.0),
74
+ )
75
+
76
+
77
+ def submit_for_review(client: CaraerApiClient, app_uuid: str) -> dict[str, Any]:
78
+ return client.request("POST", f"/api/v2/apps/public/{app_uuid}/submit")
caraer_cli/api/auth.py ADDED
@@ -0,0 +1,53 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from caraer_cli.api.client import CaraerApiClient
6
+
7
+
8
+ def login(client: CaraerApiClient, email: str, password: str) -> dict[str, Any]:
9
+ return client.request(
10
+ "POST",
11
+ "/api/v2/auth/login",
12
+ json_body={"email": email, "password": password},
13
+ allow_unauthenticated=True,
14
+ )
15
+
16
+
17
+ def device_start(client: CaraerApiClient) -> dict[str, Any]:
18
+ return client.request(
19
+ "POST",
20
+ "/api/v2/auth/device/start",
21
+ allow_unauthenticated=True,
22
+ )
23
+
24
+
25
+ def device_poll(client: CaraerApiClient, device_code: str) -> dict[str, Any]:
26
+ return client.request(
27
+ "POST",
28
+ "/api/v2/auth/device/poll",
29
+ json_body={"deviceCode": device_code},
30
+ allow_unauthenticated=True,
31
+ )
32
+
33
+
34
+ def refresh(client: CaraerApiClient, refresh_token: str) -> dict[str, Any]:
35
+ return client.request(
36
+ "POST",
37
+ "/api/v2/auth/refresh",
38
+ json_body={"refreshToken": refresh_token},
39
+ allow_unauthenticated=True,
40
+ )
41
+
42
+
43
+ def me(client: CaraerApiClient) -> dict[str, Any]:
44
+ # /auth/me is company-optional; a stale company header causes "Company not found".
45
+ return client.request("GET", "/api/v2/auth/me", send_company_header=False)
46
+
47
+
48
+ def companies(client: CaraerApiClient) -> dict[str, Any]:
49
+ return client.request("GET", "/api/v2/auth/companies", send_company_header=False)
50
+
51
+
52
+ def logout(client: CaraerApiClient) -> dict[str, Any]:
53
+ return client.request("POST", "/api/v2/auth/logout", send_company_header=False)
@@ -0,0 +1,179 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any, Callable
5
+
6
+ import httpx
7
+
8
+ from caraer_cli.errors import ApiError, AuthError, parse_api_error
9
+
10
+ # Serverless create/update can wait on GCP provisioning.
11
+ DEFAULT_TIMEOUT_SECONDS = 120.0
12
+
13
+
14
+ @dataclass
15
+ class RequestContext:
16
+ base_url: str
17
+ token: str | None
18
+ company_uuid: str | None
19
+ sandbox_uuid: str | None = None
20
+ timeout_seconds: float = DEFAULT_TIMEOUT_SECONDS
21
+ verify_ssl: bool = True
22
+ debug: bool = False
23
+ profile_name: str | None = None
24
+ refresh_token: str | None = None
25
+ on_tokens_refreshed: Callable[[str, str | None], None] | None = None
26
+
27
+
28
+ class CaraerApiClient:
29
+ def __init__(self, context: RequestContext) -> None:
30
+ self.context = context
31
+
32
+ def request(
33
+ self,
34
+ method: str,
35
+ path: str,
36
+ *,
37
+ params: dict[str, Any] | None = None,
38
+ json_body: dict[str, Any] | None = None,
39
+ allow_unauthenticated: bool = False,
40
+ send_company_header: bool = True,
41
+ send_sandbox_header: bool = True,
42
+ timeout_seconds: float | None = None,
43
+ _retried: bool = False,
44
+ ) -> dict[str, Any]:
45
+ headers: dict[str, str] = {
46
+ "Content-Type": "application/json",
47
+ "X-Request-Id": _new_request_id(),
48
+ }
49
+ if self.context.token:
50
+ headers["Authorization"] = f"Bearer {self.context.token}"
51
+ if send_company_header and self.context.company_uuid:
52
+ headers["X-Caraer-Company-Uuid"] = self.context.company_uuid
53
+ # Sandbox management APIs must hit the owner company, not the clone.
54
+ if send_company_header and send_sandbox_header and self.context.sandbox_uuid:
55
+ headers["X-Caraer-Sandbox-Uuid"] = self.context.sandbox_uuid
56
+
57
+ if not allow_unauthenticated and "Authorization" not in headers:
58
+ raise ApiError(message="Not logged in. Run 'caraer auth login'.", status=401, payload=None)
59
+
60
+ url = self.context.base_url.rstrip("/") + path
61
+ timeout = timeout_seconds if timeout_seconds is not None else self.context.timeout_seconds
62
+ try:
63
+ with httpx.Client(timeout=timeout, verify=self.context.verify_ssl) as client:
64
+ response = client.request(
65
+ method.upper(), url, headers=headers, params=params, json=json_body
66
+ )
67
+ except httpx.TimeoutException as exc:
68
+ raise ApiError(
69
+ message=(
70
+ f"Request timed out after {timeout:.0f}s ({method.upper()} {path}). "
71
+ "Serverless function create/update can take a while. "
72
+ "Retry, or raise the limit with "
73
+ "'caraer profile set --timeout-seconds 300'."
74
+ ),
75
+ status=408,
76
+ payload=None,
77
+ ) from exc
78
+ except httpx.TransportError as exc:
79
+ raise ApiError(
80
+ message=f"Network error talking to {self.context.base_url}: {exc}",
81
+ status=503,
82
+ payload=None,
83
+ ) from exc
84
+
85
+ if response.status_code >= 400:
86
+ payload = _safe_json(response)
87
+ # Transparent refresh on 401 when we have a refresh token.
88
+ if (
89
+ response.status_code == 401
90
+ and not _retried
91
+ and not allow_unauthenticated
92
+ and self.context.refresh_token
93
+ and path != "/api/v2/auth/refresh"
94
+ ):
95
+ if self._try_refresh():
96
+ return self.request(
97
+ method,
98
+ path,
99
+ params=params,
100
+ json_body=json_body,
101
+ allow_unauthenticated=allow_unauthenticated,
102
+ send_company_header=send_company_header,
103
+ send_sandbox_header=send_sandbox_header,
104
+ timeout_seconds=timeout_seconds,
105
+ _retried=True,
106
+ )
107
+ error = parse_api_error(response.status_code, payload)
108
+ request_id = (
109
+ (payload or {}).get("requestId")
110
+ if isinstance(payload, dict)
111
+ else None
112
+ ) or response.headers.get("X-Request-Id")
113
+ if request_id and "Request ID:" not in error.message:
114
+ error.message = f"{error.message}\nRequest ID: {request_id}"
115
+ if error.message == "Company not found" and self.context.company_uuid:
116
+ error.message = (
117
+ f"Company not found ({self.context.company_uuid}). "
118
+ "Run 'caraer company clear', then 'caraer company list' and "
119
+ "'caraer company select <uuid>'."
120
+ )
121
+ if self.context.debug:
122
+ error.message += f"\n[debug] response body: {response.text}"
123
+ raise error
124
+
125
+ payload = _safe_json(response)
126
+ if payload is None:
127
+ return {}
128
+ if isinstance(payload, dict):
129
+ return payload
130
+ return {"data": payload}
131
+
132
+ def _try_refresh(self) -> bool:
133
+ refresh_token = self.context.refresh_token
134
+ if not refresh_token:
135
+ return False
136
+ try:
137
+ response = self.request(
138
+ "POST",
139
+ "/api/v2/auth/refresh",
140
+ json_body={"refreshToken": refresh_token},
141
+ allow_unauthenticated=True,
142
+ _retried=True,
143
+ )
144
+ except (ApiError, AuthError):
145
+ return False
146
+ data = response.get("data") if isinstance(response.get("data"), dict) else response
147
+ if not isinstance(data, dict):
148
+ return False
149
+ access = data.get("accessToken")
150
+ token_obj = data.get("token")
151
+ if not access and isinstance(token_obj, dict):
152
+ access = token_obj.get("token")
153
+ if not access:
154
+ return False
155
+ new_refresh = data.get("refreshToken") or refresh_token
156
+ self.context.token = str(access)
157
+ self.context.refresh_token = str(new_refresh) if new_refresh else None
158
+ if self.context.on_tokens_refreshed:
159
+ self.context.on_tokens_refreshed(
160
+ str(access),
161
+ str(new_refresh) if new_refresh else None,
162
+ )
163
+ return True
164
+
165
+
166
+ def _new_request_id() -> str:
167
+ import uuid
168
+
169
+ return str(uuid.uuid4())
170
+
171
+
172
+ def _safe_json(response: httpx.Response) -> dict[str, Any] | None:
173
+ try:
174
+ payload = response.json()
175
+ except ValueError:
176
+ return None
177
+ if isinstance(payload, dict):
178
+ return payload
179
+ return {"data": payload}
@@ -0,0 +1,72 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from caraer_cli.api.client import CaraerApiClient
6
+
7
+
8
+ def list_functions(client: CaraerApiClient, app_uuid: str, page: int, limit: int) -> dict[str, Any]:
9
+ return client.request(
10
+ "POST",
11
+ f"/api/v2/apps/{app_uuid}/serverless-functions/index",
12
+ json_body={
13
+ "filters": [],
14
+ "sort": [{"key": "createdAt", "direction": "desc"}],
15
+ "show": [],
16
+ "limit": limit,
17
+ "page": page,
18
+ "query": "",
19
+ },
20
+ )
21
+
22
+
23
+ def get_function(client: CaraerApiClient, app_uuid: str, function_uuid: str) -> dict[str, Any]:
24
+ return client.request("GET", f"/api/v2/apps/{app_uuid}/serverless-functions/{function_uuid}")
25
+
26
+
27
+ def create_function(client: CaraerApiClient, app_uuid: str, payload: dict[str, Any]) -> dict[str, Any]:
28
+ # GCP provisioning regularly exceeds short HTTP timeouts.
29
+ return client.request(
30
+ "POST",
31
+ f"/api/v2/apps/{app_uuid}/serverless-functions",
32
+ json_body=payload,
33
+ timeout_seconds=max(client.context.timeout_seconds, 300.0),
34
+ )
35
+
36
+
37
+ def update_function(
38
+ client: CaraerApiClient, app_uuid: str, function_uuid: str, payload: dict[str, Any]
39
+ ) -> dict[str, Any]:
40
+ return client.request(
41
+ "PUT",
42
+ f"/api/v2/apps/{app_uuid}/serverless-functions/{function_uuid}",
43
+ json_body=payload,
44
+ timeout_seconds=max(client.context.timeout_seconds, 300.0),
45
+ )
46
+
47
+
48
+ def delete_function(client: CaraerApiClient, app_uuid: str, function_uuid: str) -> dict[str, Any]:
49
+ return client.request(
50
+ "DELETE",
51
+ f"/api/v2/apps/{app_uuid}/serverless-functions/{function_uuid}",
52
+ timeout_seconds=max(client.context.timeout_seconds, 180.0),
53
+ )
54
+
55
+
56
+ def test_function(
57
+ client: CaraerApiClient,
58
+ app_uuid: str,
59
+ function_uuid: str,
60
+ record_uuid: str,
61
+ event_type: str,
62
+ force_provision: bool = False,
63
+ ) -> dict[str, Any]:
64
+ return client.request(
65
+ "POST",
66
+ f"/api/v2/apps/{app_uuid}/serverless-functions/{function_uuid}/test",
67
+ json_body={
68
+ "recordUuid": record_uuid,
69
+ "eventType": event_type,
70
+ "forceProvision": force_provision,
71
+ },
72
+ )
@@ -0,0 +1,93 @@
1
+ """API client for app installation state, secrets, jobs, and connections."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from caraer_cli.api.client import CaraerApiClient
8
+
9
+
10
+ def get_state(client: CaraerApiClient, app_uuid: str) -> dict[str, Any]:
11
+ return client.request("GET", f"/api/v2/apps/{app_uuid}/installation/state")
12
+
13
+
14
+ def put_state(client: CaraerApiClient, app_uuid: str, payload: dict[str, Any]) -> dict[str, Any]:
15
+ return client.request(
16
+ "PUT",
17
+ f"/api/v2/apps/{app_uuid}/installation/state",
18
+ json_body=payload,
19
+ )
20
+
21
+
22
+ def get_state_key(client: CaraerApiClient, app_uuid: str, key: str) -> dict[str, Any]:
23
+ return client.request("GET", f"/api/v2/apps/{app_uuid}/installation/state/{key}")
24
+
25
+
26
+ def put_state_key(
27
+ client: CaraerApiClient, app_uuid: str, key: str, value: Any
28
+ ) -> dict[str, Any]:
29
+ body = value if isinstance(value, dict) and set(value.keys()) == {"value"} else {"value": value}
30
+ return client.request(
31
+ "PUT",
32
+ f"/api/v2/apps/{app_uuid}/installation/state/{key}",
33
+ json_body=body,
34
+ )
35
+
36
+
37
+ def delete_state_key(client: CaraerApiClient, app_uuid: str, key: str) -> dict[str, Any]:
38
+ return client.request("DELETE", f"/api/v2/apps/{app_uuid}/installation/state/{key}")
39
+
40
+
41
+ def list_secrets(client: CaraerApiClient, app_uuid: str) -> dict[str, Any]:
42
+ return client.request("GET", f"/api/v2/apps/{app_uuid}/installation/secrets")
43
+
44
+
45
+ def put_secret(
46
+ client: CaraerApiClient, app_uuid: str, name: str, value: str
47
+ ) -> dict[str, Any]:
48
+ return client.request(
49
+ "PUT",
50
+ f"/api/v2/apps/{app_uuid}/installation/secrets/{name}",
51
+ json_body={"value": value},
52
+ )
53
+
54
+
55
+ def delete_secret(client: CaraerApiClient, app_uuid: str, name: str) -> dict[str, Any]:
56
+ return client.request("DELETE", f"/api/v2/apps/{app_uuid}/installation/secrets/{name}")
57
+
58
+
59
+ def list_connections(client: CaraerApiClient, app_uuid: str) -> dict[str, Any]:
60
+ return client.request("GET", f"/api/v2/apps/{app_uuid}/installation/connections")
61
+
62
+
63
+ def delete_connection(
64
+ client: CaraerApiClient, app_uuid: str, provider: str
65
+ ) -> dict[str, Any]:
66
+ return client.request(
67
+ "DELETE", f"/api/v2/apps/{app_uuid}/installation/connections/{provider}"
68
+ )
69
+
70
+
71
+ def enqueue_job(
72
+ client: CaraerApiClient,
73
+ app_uuid: str,
74
+ *,
75
+ function_name: str,
76
+ payload: dict[str, Any] | None = None,
77
+ delay_seconds: float = 0,
78
+ ) -> dict[str, Any]:
79
+ body: dict[str, Any] = {
80
+ "functionName": function_name,
81
+ "payload": payload or {},
82
+ }
83
+ if delay_seconds:
84
+ body["delaySeconds"] = delay_seconds
85
+ return client.request(
86
+ "POST",
87
+ f"/api/v2/apps/{app_uuid}/installation/jobs",
88
+ json_body=body,
89
+ )
90
+
91
+
92
+ def get_job(client: CaraerApiClient, app_uuid: str, job_id: str) -> dict[str, Any]:
93
+ return client.request("GET", f"/api/v2/apps/{app_uuid}/installation/jobs/{job_id}")
@@ -0,0 +1,77 @@
1
+ """API client for app schedules, inbound routes, and external OAuth providers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from caraer_cli.api.client import CaraerApiClient
8
+
9
+
10
+ def list_schedules(client: CaraerApiClient, app_uuid: str) -> dict[str, Any]:
11
+ return client.request("GET", f"/api/v2/apps/{app_uuid}/schedules")
12
+
13
+
14
+ def create_schedule(client: CaraerApiClient, app_uuid: str, payload: dict[str, Any]) -> dict[str, Any]:
15
+ return client.request("POST", f"/api/v2/apps/{app_uuid}/schedules", json_body=payload)
16
+
17
+
18
+ def update_schedule(
19
+ client: CaraerApiClient, app_uuid: str, schedule_uuid: str, payload: dict[str, Any]
20
+ ) -> dict[str, Any]:
21
+ return client.request(
22
+ "PUT", f"/api/v2/apps/{app_uuid}/schedules/{schedule_uuid}", json_body=payload
23
+ )
24
+
25
+
26
+ def delete_schedule(client: CaraerApiClient, app_uuid: str, schedule_uuid: str) -> dict[str, Any]:
27
+ return client.request("DELETE", f"/api/v2/apps/{app_uuid}/schedules/{schedule_uuid}")
28
+
29
+
30
+ def list_inbound_routes(client: CaraerApiClient, app_uuid: str) -> dict[str, Any]:
31
+ return client.request("GET", f"/api/v2/apps/{app_uuid}/inbound-routes")
32
+
33
+
34
+ def create_inbound_route(client: CaraerApiClient, app_uuid: str, payload: dict[str, Any]) -> dict[str, Any]:
35
+ return client.request(
36
+ "POST", f"/api/v2/apps/{app_uuid}/inbound-routes", json_body=payload
37
+ )
38
+
39
+
40
+ def update_inbound_route(
41
+ client: CaraerApiClient, app_uuid: str, route_uuid: str, payload: dict[str, Any]
42
+ ) -> dict[str, Any]:
43
+ return client.request(
44
+ "PUT", f"/api/v2/apps/{app_uuid}/inbound-routes/{route_uuid}", json_body=payload
45
+ )
46
+
47
+
48
+ def delete_inbound_route(client: CaraerApiClient, app_uuid: str, route_uuid: str) -> dict[str, Any]:
49
+ return client.request("DELETE", f"/api/v2/apps/{app_uuid}/inbound-routes/{route_uuid}")
50
+
51
+
52
+ def list_external_oauth_providers(client: CaraerApiClient, app_uuid: str) -> dict[str, Any]:
53
+ return client.request("GET", f"/api/v2/apps/{app_uuid}/external-oauth-providers")
54
+
55
+
56
+ def create_external_oauth_provider(
57
+ client: CaraerApiClient, app_uuid: str, payload: dict[str, Any]
58
+ ) -> dict[str, Any]:
59
+ return client.request(
60
+ "POST", f"/api/v2/apps/{app_uuid}/external-oauth-providers", json_body=payload
61
+ )
62
+
63
+
64
+ def update_external_oauth_provider(
65
+ client: CaraerApiClient, app_uuid: str, provider_uuid: str, payload: dict[str, Any]
66
+ ) -> dict[str, Any]:
67
+ return client.request(
68
+ "PUT",
69
+ f"/api/v2/apps/{app_uuid}/external-oauth-providers/{provider_uuid}",
70
+ json_body=payload,
71
+ )
72
+
73
+
74
+ def delete_external_oauth_provider(
75
+ client: CaraerApiClient, app_uuid: str, provider_uuid: str
76
+ ) -> dict[str, Any]:
77
+ return client.request("DELETE", f"/api/v2/apps/{app_uuid}/external-oauth-providers/{provider_uuid}")