o2-cli 0.1.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.
o2_cli/commands/mm.py ADDED
@@ -0,0 +1,182 @@
1
+ """o2 mm commands - Market maker control."""
2
+
3
+ import asyncio
4
+
5
+ import typer
6
+
7
+ from o2_cli.cli import get_state
8
+ from o2_cli.client import O2Client
9
+ from o2_cli.config import load_config, get_active_profile
10
+ from o2_cli.exceptions import APIError, ConnectionError
11
+ from o2_cli.output import OutputFormatter
12
+
13
+ app = typer.Typer(help="Market maker control")
14
+
15
+
16
+ @app.command("status")
17
+ def status():
18
+ """Show market maker status."""
19
+ asyncio.run(_status())
20
+
21
+
22
+ async def _status():
23
+ state = get_state()
24
+ formatter = OutputFormatter(json_mode=state["json_output"])
25
+ config = load_config()
26
+ profile = get_active_profile(config)
27
+ api_url = state.get("api_url") or profile.api_url
28
+ timeout = state.get("timeout") or profile.timeout
29
+
30
+ if not profile.token and profile.auth_type == "jwt":
31
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
32
+ raise typer.Exit(1)
33
+
34
+ async with O2Client(api_url, timeout) as client:
35
+ client.set_jwt(profile.token)
36
+ if profile.api_key_id:
37
+ client.set_api_key(profile.api_key_id, profile.api_secret)
38
+ try:
39
+ data = await client.mm_status()
40
+ formatter.print_raw(data)
41
+ except APIError as e:
42
+ formatter.print_error(str(e), e.code)
43
+ raise typer.Exit(1)
44
+ except ConnectionError as e:
45
+ formatter.print_error(str(e))
46
+ raise typer.Exit(1)
47
+
48
+
49
+ @app.command("start")
50
+ def start():
51
+ """Start the market maker."""
52
+ asyncio.run(_start())
53
+
54
+
55
+ async def _start():
56
+ state = get_state()
57
+ formatter = OutputFormatter(json_mode=state["json_output"])
58
+ config = load_config()
59
+ profile = get_active_profile(config)
60
+ api_url = state.get("api_url") or profile.api_url
61
+ timeout = state.get("timeout") or profile.timeout
62
+
63
+ if not profile.token and profile.auth_type == "jwt":
64
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
65
+ raise typer.Exit(1)
66
+
67
+ async with O2Client(api_url, timeout) as client:
68
+ client.set_jwt(profile.token)
69
+ if profile.api_key_id:
70
+ client.set_api_key(profile.api_key_id, profile.api_secret)
71
+ try:
72
+ data = await client.mm_start()
73
+ formatter.print_raw(data)
74
+ if not state["json_output"]:
75
+ formatter.print_success("Market maker started")
76
+ except APIError as e:
77
+ formatter.print_error(str(e), e.code)
78
+ raise typer.Exit(1)
79
+ except ConnectionError as e:
80
+ formatter.print_error(str(e))
81
+ raise typer.Exit(1)
82
+
83
+
84
+ @app.command("stop")
85
+ def stop():
86
+ """Stop the market maker."""
87
+ asyncio.run(_stop())
88
+
89
+
90
+ async def _stop():
91
+ state = get_state()
92
+ formatter = OutputFormatter(json_mode=state["json_output"])
93
+ config = load_config()
94
+ profile = get_active_profile(config)
95
+ api_url = state.get("api_url") or profile.api_url
96
+ timeout = state.get("timeout") or profile.timeout
97
+
98
+ if not profile.token and profile.auth_type == "jwt":
99
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
100
+ raise typer.Exit(1)
101
+
102
+ async with O2Client(api_url, timeout) as client:
103
+ client.set_jwt(profile.token)
104
+ if profile.api_key_id:
105
+ client.set_api_key(profile.api_key_id, profile.api_secret)
106
+ try:
107
+ data = await client.mm_stop()
108
+ formatter.print_raw(data)
109
+ if not state["json_output"]:
110
+ formatter.print_success("Market maker stopped")
111
+ except APIError as e:
112
+ formatter.print_error(str(e), e.code)
113
+ raise typer.Exit(1)
114
+ except ConnectionError as e:
115
+ formatter.print_error(str(e))
116
+ raise typer.Exit(1)
117
+
118
+
119
+ @app.command("stats")
120
+ def stats():
121
+ """Show market maker statistics."""
122
+ asyncio.run(_stats())
123
+
124
+
125
+ async def _stats():
126
+ state = get_state()
127
+ formatter = OutputFormatter(json_mode=state["json_output"])
128
+ config = load_config()
129
+ profile = get_active_profile(config)
130
+ api_url = state.get("api_url") or profile.api_url
131
+ timeout = state.get("timeout") or profile.timeout
132
+
133
+ if not profile.token and profile.auth_type == "jwt":
134
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
135
+ raise typer.Exit(1)
136
+
137
+ async with O2Client(api_url, timeout) as client:
138
+ client.set_jwt(profile.token)
139
+ if profile.api_key_id:
140
+ client.set_api_key(profile.api_key_id, profile.api_secret)
141
+ try:
142
+ data = await client.mm_stats()
143
+ formatter.print_raw(data)
144
+ except APIError as e:
145
+ formatter.print_error(str(e), e.code)
146
+ raise typer.Exit(1)
147
+ except ConnectionError as e:
148
+ formatter.print_error(str(e))
149
+ raise typer.Exit(1)
150
+
151
+
152
+ @app.command("orders")
153
+ def orders():
154
+ """Show current market maker orders."""
155
+ asyncio.run(_orders())
156
+
157
+
158
+ async def _orders():
159
+ state = get_state()
160
+ formatter = OutputFormatter(json_mode=state["json_output"])
161
+ config = load_config()
162
+ profile = get_active_profile(config)
163
+ api_url = state.get("api_url") or profile.api_url
164
+ timeout = state.get("timeout") or profile.timeout
165
+
166
+ if not profile.token and profile.auth_type == "jwt":
167
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
168
+ raise typer.Exit(1)
169
+
170
+ async with O2Client(api_url, timeout) as client:
171
+ client.set_jwt(profile.token)
172
+ if profile.api_key_id:
173
+ client.set_api_key(profile.api_key_id, profile.api_secret)
174
+ try:
175
+ data = await client.mm_orders()
176
+ formatter.print_orders(data)
177
+ except APIError as e:
178
+ formatter.print_error(str(e), e.code)
179
+ raise typer.Exit(1)
180
+ except ConnectionError as e:
181
+ formatter.print_error(str(e))
182
+ raise typer.Exit(1)
@@ -0,0 +1,136 @@
1
+ """o2 notifications commands - Notification management."""
2
+
3
+ import asyncio
4
+ from typing import Optional
5
+
6
+ import typer
7
+
8
+ from o2_cli.cli import get_state
9
+ from o2_cli.client import O2Client
10
+ from o2_cli.config import load_config, get_active_profile
11
+ from o2_cli.exceptions import APIError, ConnectionError
12
+ from o2_cli.output import OutputFormatter
13
+
14
+ app = typer.Typer(help="Notifications")
15
+
16
+
17
+ @app.command("list")
18
+ def list_notifications(
19
+ unread_only: bool = typer.Option(
20
+ False, "--unread-only", "-u", help="Show only unread notifications"
21
+ ),
22
+ type: Optional[str] = typer.Option(
23
+ None, "--type", "-t",
24
+ help="Filter by type (trade/deposit/withdrawal/system/security/market)"
25
+ ),
26
+ ):
27
+ """List notifications."""
28
+ asyncio.run(_list_notifications(unread_only, type))
29
+
30
+
31
+ async def _list_notifications(unread_only: bool, type: Optional[str]):
32
+ state = get_state()
33
+ formatter = OutputFormatter(json_mode=state["json_output"])
34
+ config = load_config()
35
+ profile = get_active_profile(config)
36
+ api_url = state.get("api_url") or profile.api_url
37
+ timeout = state.get("timeout") or profile.timeout
38
+
39
+ if not profile.token and profile.auth_type == "jwt":
40
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
41
+ raise typer.Exit(1)
42
+
43
+ params = {}
44
+ if unread_only:
45
+ params["is_read"] = "false"
46
+ if type:
47
+ params["type"] = type
48
+
49
+ async with O2Client(api_url, timeout) as client:
50
+ client.set_jwt(profile.token)
51
+ if profile.api_key_id:
52
+ client.set_api_key(profile.api_key_id, profile.api_secret)
53
+ try:
54
+ data = await client.get_notifications(**params)
55
+ formatter.print_raw(data)
56
+ except APIError as e:
57
+ formatter.print_error(str(e), e.code)
58
+ raise typer.Exit(1)
59
+ except ConnectionError as e:
60
+ formatter.print_error(str(e))
61
+ raise typer.Exit(1)
62
+
63
+
64
+ @app.command("unread")
65
+ def unread():
66
+ """Show unread notification count."""
67
+ asyncio.run(_unread())
68
+
69
+
70
+ async def _unread():
71
+ state = get_state()
72
+ formatter = OutputFormatter(json_mode=state["json_output"])
73
+ config = load_config()
74
+ profile = get_active_profile(config)
75
+ api_url = state.get("api_url") or profile.api_url
76
+ timeout = state.get("timeout") or profile.timeout
77
+
78
+ if not profile.token and profile.auth_type == "jwt":
79
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
80
+ raise typer.Exit(1)
81
+
82
+ async with O2Client(api_url, timeout) as client:
83
+ client.set_jwt(profile.token)
84
+ if profile.api_key_id:
85
+ client.set_api_key(profile.api_key_id, profile.api_secret)
86
+ try:
87
+ data = await client.get_unread_count()
88
+ formatter.print_raw(data)
89
+ if not state["json_output"]:
90
+ count = data.get("unread_count", data.get("count", 0))
91
+ formatter.print_success(f"{count} unread notifications")
92
+ except APIError as e:
93
+ formatter.print_error(str(e), e.code)
94
+ raise typer.Exit(1)
95
+ except ConnectionError as e:
96
+ formatter.print_error(str(e))
97
+ raise typer.Exit(1)
98
+
99
+
100
+ @app.command("read")
101
+ def read(
102
+ id: str = typer.Option(
103
+ ..., "--id", "-i", help="Notification ID to mark as read"
104
+ ),
105
+ ):
106
+ """Mark a notification as read."""
107
+ asyncio.run(_read(id))
108
+
109
+
110
+ async def _read(notification_id: str):
111
+ state = get_state()
112
+ formatter = OutputFormatter(json_mode=state["json_output"])
113
+ config = load_config()
114
+ profile = get_active_profile(config)
115
+ api_url = state.get("api_url") or profile.api_url
116
+ timeout = state.get("timeout") or profile.timeout
117
+
118
+ if not profile.token and profile.auth_type == "jwt":
119
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
120
+ raise typer.Exit(1)
121
+
122
+ async with O2Client(api_url, timeout) as client:
123
+ client.set_jwt(profile.token)
124
+ if profile.api_key_id:
125
+ client.set_api_key(profile.api_key_id, profile.api_secret)
126
+ try:
127
+ data = await client.mark_notification_read(notification_id)
128
+ formatter.print_raw(data)
129
+ if not state["json_output"]:
130
+ formatter.print_success(f"Notification {notification_id} marked as read")
131
+ except APIError as e:
132
+ formatter.print_error(str(e), e.code)
133
+ raise typer.Exit(1)
134
+ except ConnectionError as e:
135
+ formatter.print_error(str(e))
136
+ raise typer.Exit(1)
@@ -0,0 +1,331 @@
1
+ """o2 orders commands - Order management."""
2
+
3
+ import asyncio
4
+ import json
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ import typer
9
+
10
+ from o2_cli.cli import get_state
11
+ from o2_cli.client import O2Client
12
+ from o2_cli.config import load_config, get_active_profile
13
+ from o2_cli.exceptions import APIError, ConnectionError
14
+ from o2_cli.output import OutputFormatter
15
+
16
+ app = typer.Typer(help="Order management")
17
+
18
+
19
+ @app.command("create")
20
+ def create(
21
+ market_id: int = typer.Option(..., "--market-id", "-m", help="Market ID"),
22
+ side: str = typer.Option(..., "--side", "-s", help="Order side (long/short)"),
23
+ order_type: str = typer.Option(
24
+ "limit", "--order-type", "-t", help="Order type (market/limit)"
25
+ ),
26
+ base_amount: str = typer.Option(
27
+ ..., "--base-amount", "-a", help="Order size in base units"
28
+ ),
29
+ price: Optional[str] = typer.Option(
30
+ None, "--price", "-p", help="Limit price (required for limit orders)"
31
+ ),
32
+ position_mode: str = typer.Option(
33
+ "open", "--position-mode", help="Position mode (open/close)"
34
+ ),
35
+ leverage: Optional[int] = typer.Option(None, "--leverage", "-l", help="Leverage"),
36
+ margin_mode: str = typer.Option(
37
+ "cross", "--margin-mode", help="Margin mode (cross/isolated)"
38
+ ),
39
+ reduce_only: bool = typer.Option(
40
+ False, "--reduce-only", help="Reduce-only order"
41
+ ),
42
+ ):
43
+ """Create a new order."""
44
+ asyncio.run(
45
+ _create(
46
+ market_id, side, order_type, base_amount, price, position_mode,
47
+ leverage, margin_mode, reduce_only,
48
+ )
49
+ )
50
+
51
+
52
+ async def _create(
53
+ market_id: int,
54
+ side: str,
55
+ order_type: str,
56
+ base_amount: str,
57
+ price: Optional[str],
58
+ position_mode: str,
59
+ leverage: Optional[int],
60
+ margin_mode: str,
61
+ reduce_only: bool,
62
+ ):
63
+ state = get_state()
64
+ formatter = OutputFormatter(json_mode=state["json_output"])
65
+ config = load_config()
66
+ profile = get_active_profile(config)
67
+ api_url = state.get("api_url") or profile.api_url
68
+ timeout = state.get("timeout") or profile.timeout
69
+
70
+ if not profile.token and profile.auth_type == "jwt":
71
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
72
+ raise typer.Exit(1)
73
+
74
+ if order_type == "limit" and not price:
75
+ formatter.print_error("Limit orders require --price.")
76
+ raise typer.Exit(1)
77
+
78
+ payload = {
79
+ "market_id": market_id,
80
+ "side": side,
81
+ "order_type": order_type,
82
+ "base_amount": base_amount,
83
+ "position_mode": position_mode,
84
+ "margin_mode": margin_mode,
85
+ "reduce_only": reduce_only,
86
+ }
87
+ if price is not None:
88
+ payload["price"] = price
89
+ if leverage is not None:
90
+ payload["leverage"] = leverage
91
+
92
+ async with O2Client(api_url, timeout) as client:
93
+ client.set_jwt(profile.token)
94
+ if profile.api_key_id:
95
+ client.set_api_key(profile.api_key_id, profile.api_secret)
96
+ try:
97
+ data = await client.create_order(**payload)
98
+ formatter.print_orders(data)
99
+ if not state["json_output"]:
100
+ formatter.print_success("Order created")
101
+ except APIError as e:
102
+ formatter.print_error(str(e), e.code)
103
+ raise typer.Exit(1)
104
+ except ConnectionError as e:
105
+ formatter.print_error(str(e))
106
+ raise typer.Exit(1)
107
+
108
+
109
+ @app.command("list")
110
+ def list_orders(
111
+ market_id: Optional[int] = typer.Option(
112
+ None, "--market-id", "-m", help="Filter by market ID"
113
+ ),
114
+ status: Optional[str] = typer.Option(
115
+ None, "--status", "-s", help="Filter by status"
116
+ ),
117
+ ):
118
+ """List orders."""
119
+ asyncio.run(_list_orders(market_id, status))
120
+
121
+
122
+ async def _list_orders(market_id: Optional[int], status: Optional[str]):
123
+ state = get_state()
124
+ formatter = OutputFormatter(json_mode=state["json_output"])
125
+ config = load_config()
126
+ profile = get_active_profile(config)
127
+ api_url = state.get("api_url") or profile.api_url
128
+ timeout = state.get("timeout") or profile.timeout
129
+
130
+ if not profile.token and profile.auth_type == "jwt":
131
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
132
+ raise typer.Exit(1)
133
+
134
+ async with O2Client(api_url, timeout) as client:
135
+ client.set_jwt(profile.token)
136
+ if profile.api_key_id:
137
+ client.set_api_key(profile.api_key_id, profile.api_secret)
138
+ try:
139
+ data = await client.list_orders(market_id=market_id, status_filter=status)
140
+ formatter.print_orders(data)
141
+ except APIError as e:
142
+ formatter.print_error(str(e), e.code)
143
+ raise typer.Exit(1)
144
+ except ConnectionError as e:
145
+ formatter.print_error(str(e))
146
+ raise typer.Exit(1)
147
+
148
+
149
+ @app.command("cancel")
150
+ def cancel(
151
+ order_id: str = typer.Option(..., "--order-id", "-i", help="Order ID to cancel"),
152
+ ):
153
+ """Cancel an order."""
154
+ asyncio.run(_cancel(order_id))
155
+
156
+
157
+ async def _cancel(order_id: str):
158
+ state = get_state()
159
+ formatter = OutputFormatter(json_mode=state["json_output"])
160
+ config = load_config()
161
+ profile = get_active_profile(config)
162
+ api_url = state.get("api_url") or profile.api_url
163
+ timeout = state.get("timeout") or profile.timeout
164
+
165
+ if not profile.token and profile.auth_type == "jwt":
166
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
167
+ raise typer.Exit(1)
168
+
169
+ async with O2Client(api_url, timeout) as client:
170
+ client.set_jwt(profile.token)
171
+ if profile.api_key_id:
172
+ client.set_api_key(profile.api_key_id, profile.api_secret)
173
+ try:
174
+ data = await client.cancel_order(order_id)
175
+ formatter.print_raw(data)
176
+ if not state["json_output"]:
177
+ formatter.print_success(f"Order {order_id} cancelled")
178
+ except APIError as e:
179
+ formatter.print_error(str(e), e.code)
180
+ raise typer.Exit(1)
181
+ except ConnectionError as e:
182
+ formatter.print_error(str(e))
183
+ raise typer.Exit(1)
184
+
185
+
186
+ @app.command("cancel-all")
187
+ def cancel_all(
188
+ market_id: Optional[int] = typer.Option(
189
+ None, "--market-id", "-m", help="Cancel orders for a specific market"
190
+ ),
191
+ ):
192
+ """Cancel all open orders."""
193
+ asyncio.run(_cancel_all(market_id))
194
+
195
+
196
+ async def _cancel_all(market_id: Optional[int]):
197
+ state = get_state()
198
+ formatter = OutputFormatter(json_mode=state["json_output"])
199
+ config = load_config()
200
+ profile = get_active_profile(config)
201
+ api_url = state.get("api_url") or profile.api_url
202
+ timeout = state.get("timeout") or profile.timeout
203
+
204
+ if not profile.token and profile.auth_type == "jwt":
205
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
206
+ raise typer.Exit(1)
207
+
208
+ async with O2Client(api_url, timeout) as client:
209
+ client.set_jwt(profile.token)
210
+ if profile.api_key_id:
211
+ client.set_api_key(profile.api_key_id, profile.api_secret)
212
+ try:
213
+ data = await client.cancel_all_orders(market_id=market_id)
214
+ formatter.print_raw(data)
215
+ if not state["json_output"]:
216
+ label = f" for market {market_id}" if market_id else ""
217
+ formatter.print_success(f"All orders cancelled{label}")
218
+ except APIError as e:
219
+ formatter.print_error(str(e), e.code)
220
+ raise typer.Exit(1)
221
+ except ConnectionError as e:
222
+ formatter.print_error(str(e))
223
+ raise typer.Exit(1)
224
+
225
+
226
+ @app.command("modify")
227
+ def modify(
228
+ order_id: str = typer.Option(..., "--order-id", "-i", help="Order ID to modify"),
229
+ base_amount: Optional[str] = typer.Option(
230
+ None, "--base-amount", "-a", help="New order size"
231
+ ),
232
+ price: Optional[str] = typer.Option(None, "--price", "-p", help="New price"),
233
+ ):
234
+ """Modify an existing order."""
235
+ asyncio.run(_modify(order_id, base_amount, price))
236
+
237
+
238
+ async def _modify(
239
+ order_id: str, base_amount: Optional[str], price: Optional[str]
240
+ ):
241
+ state = get_state()
242
+ formatter = OutputFormatter(json_mode=state["json_output"])
243
+ config = load_config()
244
+ profile = get_active_profile(config)
245
+ api_url = state.get("api_url") or profile.api_url
246
+ timeout = state.get("timeout") or profile.timeout
247
+
248
+ if not profile.token and profile.auth_type == "jwt":
249
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
250
+ raise typer.Exit(1)
251
+
252
+ if base_amount is None and price is None:
253
+ formatter.print_error("Provide at least --base-amount or --price to modify.")
254
+ raise typer.Exit(1)
255
+
256
+ kwargs = {}
257
+ if base_amount is not None:
258
+ kwargs["base_amount"] = base_amount
259
+ if price is not None:
260
+ kwargs["price"] = price
261
+
262
+ async with O2Client(api_url, timeout) as client:
263
+ client.set_jwt(profile.token)
264
+ if profile.api_key_id:
265
+ client.set_api_key(profile.api_key_id, profile.api_secret)
266
+ try:
267
+ data = await client.modify_order(order_id, **kwargs)
268
+ formatter.print_raw(data)
269
+ if not state["json_output"]:
270
+ formatter.print_success(f"Order {order_id} modified")
271
+ except APIError as e:
272
+ formatter.print_error(str(e), e.code)
273
+ raise typer.Exit(1)
274
+ except ConnectionError as e:
275
+ formatter.print_error(str(e))
276
+ raise typer.Exit(1)
277
+
278
+
279
+ @app.command("batch")
280
+ def batch(
281
+ file: Path = typer.Option(
282
+ ..., "--file", "-f", help="Path to JSON file with batch operations"
283
+ ),
284
+ ):
285
+ """Submit batch order operations from a JSON file."""
286
+ asyncio.run(_batch(file))
287
+
288
+
289
+ async def _batch(file: Path):
290
+ state = get_state()
291
+ formatter = OutputFormatter(json_mode=state["json_output"])
292
+ config = load_config()
293
+ profile = get_active_profile(config)
294
+ api_url = state.get("api_url") or profile.api_url
295
+ timeout = state.get("timeout") or profile.timeout
296
+
297
+ if not profile.token and profile.auth_type == "jwt":
298
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
299
+ raise typer.Exit(1)
300
+
301
+ if not file.exists():
302
+ formatter.print_error(f"File not found: {file}")
303
+ raise typer.Exit(1)
304
+
305
+ try:
306
+ with open(file) as f:
307
+ payload = json.load(f)
308
+ except json.JSONDecodeError as e:
309
+ formatter.print_error(f"Invalid JSON in {file}: {e}")
310
+ raise typer.Exit(1)
311
+
312
+ operations = payload if isinstance(payload, list) else payload.get("operations", [])
313
+ if not operations:
314
+ formatter.print_error("No operations found in file.")
315
+ raise typer.Exit(1)
316
+
317
+ async with O2Client(api_url, timeout) as client:
318
+ client.set_jwt(profile.token)
319
+ if profile.api_key_id:
320
+ client.set_api_key(profile.api_key_id, profile.api_secret)
321
+ try:
322
+ data = await client.batch_orders(operations)
323
+ formatter.print_raw(data)
324
+ if not state["json_output"]:
325
+ formatter.print_success(f"Batch submitted ({len(operations)} operations)")
326
+ except APIError as e:
327
+ formatter.print_error(str(e), e.code)
328
+ raise typer.Exit(1)
329
+ except ConnectionError as e:
330
+ formatter.print_error(str(e))
331
+ raise typer.Exit(1)