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.
- caraer_cli/__init__.py +4 -0
- caraer_cli/api/__init__.py +1 -0
- caraer_cli/api/apps.py +78 -0
- caraer_cli/api/auth.py +53 -0
- caraer_cli/api/client.py +179 -0
- caraer_cli/api/functions.py +72 -0
- caraer_cli/api/installation.py +93 -0
- caraer_cli/api/integration_runtime.py +77 -0
- caraer_cli/api/projects.py +213 -0
- caraer_cli/api/sandboxes.py +29 -0
- caraer_cli/api/webhooks.py +59 -0
- caraer_cli/app_sync.py +697 -0
- caraer_cli/apps_local.py +274 -0
- caraer_cli/commands/__init__.py +1 -0
- caraer_cli/commands/apps.py +2025 -0
- caraer_cli/commands/auth.py +173 -0
- caraer_cli/commands/company.py +68 -0
- caraer_cli/commands/installation.py +182 -0
- caraer_cli/commands/profile.py +239 -0
- caraer_cli/commands/publish.py +40 -0
- caraer_cli/commands/sandbox.py +92 -0
- caraer_cli/commands/skill.py +165 -0
- caraer_cli/commands/webhooks.py +68 -0
- caraer_cli/completion.py +77 -0
- caraer_cli/completion_callbacks.py +162 -0
- caraer_cli/context.py +41 -0
- caraer_cli/errors.py +84 -0
- caraer_cli/formatters/__init__.py +1 -0
- caraer_cli/formatters/output.py +381 -0
- caraer_cli/formatters/timestamps.py +98 -0
- caraer_cli/install_completion.py +38 -0
- caraer_cli/local_app.py +135 -0
- caraer_cli/main.py +104 -0
- caraer_cli/models/__init__.py +1 -0
- caraer_cli/models/api.py +52 -0
- caraer_cli/project/__init__.py +17 -0
- caraer_cli/project/app_bars_sync.py +154 -0
- caraer_cli/project/app_manifest_template.py +68 -0
- caraer_cli/project/inbound_sync.py +192 -0
- caraer_cli/project/json_schemas.py +66 -0
- caraer_cli/project/lifecycle_sync.py +119 -0
- caraer_cli/project/local_dev.py +825 -0
- caraer_cli/project/manifest_edit.py +51 -0
- caraer_cli/project/marketplace_assemble.py +165 -0
- caraer_cli/project/oauth_providers_sync.py +180 -0
- caraer_cli/project/paths.py +102 -0
- caraer_cli/project/pricing_sync.py +81 -0
- caraer_cli/project/push_plan.py +590 -0
- caraer_cli/project/release.py +128 -0
- caraer_cli/project/scaffold.py +537 -0
- caraer_cli/project/schedules_sync.py +170 -0
- caraer_cli/project/schema.py +160 -0
- caraer_cli/project/settings_sync.py +77 -0
- caraer_cli/project/state.py +51 -0
- caraer_cli/project/sync.py +308 -0
- caraer_cli/project/typegen.py +278 -0
- caraer_cli/project/validate_app.py +1215 -0
- caraer_cli/project/webhooks_sync.py +233 -0
- caraer_cli/resolve.py +55 -0
- caraer_cli/schemas/app.caraer.schema.json +175 -0
- caraer_cli/schemas/function.caraer.schema.json +29 -0
- caraer_cli/schemas/inbound.caraer.schema.json +49 -0
- caraer_cli/schemas/lifecycle.caraer.schema.json +50 -0
- caraer_cli/schemas/schedule.caraer.schema.json +39 -0
- caraer_cli/schemas/webhook.caraer.schema.json +50 -0
- caraer_cli/skills/.gitkeep +2 -0
- caraer_cli/skills/caraer-apps/SKILL.md +172 -0
- caraer_cli/skills/caraer-apps/reference.md +163 -0
- caraer_cli/state/__init__.py +1 -0
- caraer_cli/state/config.py +148 -0
- caraer_cli/state/session.py +98 -0
- caraer_cli/utils.py +40 -0
- caraer_cli/wizard/__init__.py +9 -0
- caraer_cli/wizard/catalog.py +170 -0
- caraer_cli/wizard/marketplace.py +350 -0
- caraer_cli/wizard/prompts.py +99 -0
- caraer_cli/wizard/public_app.py +519 -0
- caraer_cli/wizard/scope_macros.py +214 -0
- caraer_cli-0.1.2.dist-info/METADATA +266 -0
- caraer_cli-0.1.2.dist-info/RECORD +83 -0
- caraer_cli-0.1.2.dist-info/WHEEL +4 -0
- caraer_cli-0.1.2.dist-info/entry_points.txt +2 -0
- caraer_cli-0.1.2.dist-info/licenses/LICENSE +8 -0
caraer_cli/__init__.py
ADDED
|
@@ -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)
|
caraer_cli/api/client.py
ADDED
|
@@ -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}")
|