commune-cli 0.1.5__tar.gz → 0.1.6__tar.gz
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.
- {commune_cli-0.1.5 → commune_cli-0.1.6}/PKG-INFO +1 -1
- {commune_cli-0.1.5 → commune_cli-0.1.6}/commune_cli/commands/credits.py +39 -1
- commune_cli-0.1.6/commune_cli/commands/phone_numbers.py +201 -0
- {commune_cli-0.1.5 → commune_cli-0.1.6}/pyproject.toml +1 -1
- commune_cli-0.1.5/commune_cli/commands/phone_numbers.py +0 -92
- {commune_cli-0.1.5 → commune_cli-0.1.6}/.gitignore +0 -0
- {commune_cli-0.1.5 → commune_cli-0.1.6}/README.md +0 -0
- {commune_cli-0.1.5 → commune_cli-0.1.6}/commune_cli/__init__.py +0 -0
- {commune_cli-0.1.5 → commune_cli-0.1.6}/commune_cli/banner.py +0 -0
- {commune_cli-0.1.5 → commune_cli-0.1.6}/commune_cli/client.py +0 -0
- {commune_cli-0.1.5 → commune_cli-0.1.6}/commune_cli/commands/__init__.py +0 -0
- {commune_cli-0.1.5 → commune_cli-0.1.6}/commune_cli/commands/attachments.py +0 -0
- {commune_cli-0.1.5 → commune_cli-0.1.6}/commune_cli/commands/config_cmd.py +0 -0
- {commune_cli-0.1.5 → commune_cli-0.1.6}/commune_cli/commands/data.py +0 -0
- {commune_cli-0.1.5 → commune_cli-0.1.6}/commune_cli/commands/delivery.py +0 -0
- {commune_cli-0.1.5 → commune_cli-0.1.6}/commune_cli/commands/dmarc.py +0 -0
- {commune_cli-0.1.5 → commune_cli-0.1.6}/commune_cli/commands/domains.py +0 -0
- {commune_cli-0.1.5 → commune_cli-0.1.6}/commune_cli/commands/inboxes.py +0 -0
- {commune_cli-0.1.5 → commune_cli-0.1.6}/commune_cli/commands/messages.py +0 -0
- {commune_cli-0.1.5 → commune_cli-0.1.6}/commune_cli/commands/search.py +0 -0
- {commune_cli-0.1.5 → commune_cli-0.1.6}/commune_cli/commands/sms.py +0 -0
- {commune_cli-0.1.5 → commune_cli-0.1.6}/commune_cli/commands/threads.py +0 -0
- {commune_cli-0.1.5 → commune_cli-0.1.6}/commune_cli/commands/webhooks.py +0 -0
- {commune_cli-0.1.5 → commune_cli-0.1.6}/commune_cli/config.py +0 -0
- {commune_cli-0.1.5 → commune_cli-0.1.6}/commune_cli/errors.py +0 -0
- {commune_cli-0.1.5 → commune_cli-0.1.6}/commune_cli/main.py +0 -0
- {commune_cli-0.1.5 → commune_cli-0.1.6}/commune_cli/output.py +0 -0
- {commune_cli-0.1.5 → commune_cli-0.1.6}/commune_cli/state.py +0 -0
- {commune_cli-0.1.5 → commune_cli-0.1.6}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: commune-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
4
4
|
Summary: Official CLI for the Commune email API — agent-native, pipe-friendly, covers every API surface.
|
|
5
5
|
Project-URL: Homepage, https://commune.email
|
|
6
6
|
Project-URL: Documentation, https://docs.commune.email
|
|
@@ -6,7 +6,7 @@ import typer
|
|
|
6
6
|
|
|
7
7
|
from ..client import CommuneClient
|
|
8
8
|
from ..errors import api_error, auth_required_error, network_error
|
|
9
|
-
from ..output import print_list, print_record
|
|
9
|
+
from ..output import print_list, print_record, print_success
|
|
10
10
|
from ..state import AppState
|
|
11
11
|
|
|
12
12
|
app = typer.Typer(help="Credit balance and available bundles.", no_args_is_help=True)
|
|
@@ -64,3 +64,41 @@ def credits_bundles(
|
|
|
64
64
|
("Description", "description"),
|
|
65
65
|
],
|
|
66
66
|
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@app.command("checkout")
|
|
70
|
+
def credits_checkout(
|
|
71
|
+
ctx: typer.Context,
|
|
72
|
+
bundle: str = typer.Argument(..., help="Bundle to purchase: starter, growth, or scale."),
|
|
73
|
+
return_url: str = typer.Option(None, "--return-url", help="URL to redirect to after payment (optional)."),
|
|
74
|
+
json_output: bool = typer.Option(False, "--json", help="Output JSON."),
|
|
75
|
+
) -> None:
|
|
76
|
+
"""Create a Stripe checkout session to purchase credits. POST /v1/credits/checkout."""
|
|
77
|
+
state: AppState = ctx.obj or AppState()
|
|
78
|
+
if not state.has_any_auth():
|
|
79
|
+
auth_required_error(json_output=json_output or state.should_json())
|
|
80
|
+
|
|
81
|
+
payload: dict = {"bundle": bundle}
|
|
82
|
+
if return_url:
|
|
83
|
+
payload["return_url"] = return_url
|
|
84
|
+
|
|
85
|
+
client = CommuneClient.from_state(state)
|
|
86
|
+
try:
|
|
87
|
+
r = client.post("/v1/credits/checkout", json=payload)
|
|
88
|
+
except Exception as exc:
|
|
89
|
+
network_error(exc, json_output=json_output or state.should_json())
|
|
90
|
+
|
|
91
|
+
if not r.is_success:
|
|
92
|
+
api_error(r, json_output=json_output or state.should_json())
|
|
93
|
+
|
|
94
|
+
data = r.json()
|
|
95
|
+
if json_output or state.should_json():
|
|
96
|
+
from ..output import print_json
|
|
97
|
+
print_json(data)
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
checkout_url = data.get("checkout_url") or data.get("checkoutUrl", "")
|
|
101
|
+
credits = data.get("credits", "")
|
|
102
|
+
price = data.get("price", "")
|
|
103
|
+
print_success(f"Checkout session created for [bold]{bundle}[/bold] bundle ({credits} credits, ${price}).")
|
|
104
|
+
typer.echo(f"\nOpen this URL in your browser to complete payment:\n{checkout_url}")
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""commune phone-numbers — phone number management."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from ..client import CommuneClient
|
|
10
|
+
from ..errors import api_error, auth_required_error, network_error
|
|
11
|
+
from ..output import print_list, print_record, print_success
|
|
12
|
+
from ..state import AppState
|
|
13
|
+
|
|
14
|
+
app = typer.Typer(help="Phone number management: list, get, provision, release, settings.", no_args_is_help=True)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@app.command("list")
|
|
18
|
+
def phone_numbers_list(
|
|
19
|
+
ctx: typer.Context,
|
|
20
|
+
json_output: bool = typer.Option(False, "--json", help="Output JSON."),
|
|
21
|
+
) -> None:
|
|
22
|
+
"""List all phone numbers in your organization. GET /v1/phone-numbers."""
|
|
23
|
+
state: AppState = ctx.obj or AppState()
|
|
24
|
+
if not state.has_any_auth():
|
|
25
|
+
auth_required_error(json_output=json_output or state.should_json())
|
|
26
|
+
|
|
27
|
+
client = CommuneClient.from_state(state)
|
|
28
|
+
try:
|
|
29
|
+
r = client.get("/v1/phone-numbers")
|
|
30
|
+
except Exception as exc:
|
|
31
|
+
network_error(exc, json_output=json_output or state.should_json())
|
|
32
|
+
|
|
33
|
+
if not r.is_success:
|
|
34
|
+
api_error(r, json_output=json_output or state.should_json())
|
|
35
|
+
|
|
36
|
+
print_list(
|
|
37
|
+
r.json(),
|
|
38
|
+
json_output=json_output or state.should_json(),
|
|
39
|
+
title="Phone Numbers",
|
|
40
|
+
columns=[
|
|
41
|
+
("ID", "id"),
|
|
42
|
+
("Number", "phoneNumber"),
|
|
43
|
+
("Friendly Name", "friendlyName"),
|
|
44
|
+
("Status", "status"),
|
|
45
|
+
("Created", "createdAt"),
|
|
46
|
+
],
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@app.command("get")
|
|
51
|
+
def phone_numbers_get(
|
|
52
|
+
ctx: typer.Context,
|
|
53
|
+
phone_number_id: str = typer.Argument(..., help="Phone number ID."),
|
|
54
|
+
json_output: bool = typer.Option(False, "--json", help="Output JSON."),
|
|
55
|
+
) -> None:
|
|
56
|
+
"""Get a specific phone number. GET /v1/phone-numbers/{phoneNumberId}."""
|
|
57
|
+
state: AppState = ctx.obj or AppState()
|
|
58
|
+
if not state.has_any_auth():
|
|
59
|
+
auth_required_error(json_output=json_output or state.should_json())
|
|
60
|
+
|
|
61
|
+
client = CommuneClient.from_state(state)
|
|
62
|
+
try:
|
|
63
|
+
r = client.get(f"/v1/phone-numbers/{phone_number_id}")
|
|
64
|
+
except Exception as exc:
|
|
65
|
+
network_error(exc, json_output=json_output or state.should_json())
|
|
66
|
+
|
|
67
|
+
if not r.is_success:
|
|
68
|
+
api_error(r, json_output=json_output or state.should_json())
|
|
69
|
+
|
|
70
|
+
print_record(r.json(), json_output=json_output or state.should_json(), title="Phone Number")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@app.command("available")
|
|
74
|
+
def phone_numbers_available(
|
|
75
|
+
ctx: typer.Context,
|
|
76
|
+
type: Optional[str] = typer.Option("TollFree", "--type", help="Number type: TollFree or Local."),
|
|
77
|
+
country: Optional[str] = typer.Option("US", "--country", help="Two-letter country code."),
|
|
78
|
+
limit: Optional[int] = typer.Option(20, "--limit", help="Maximum results to return."),
|
|
79
|
+
json_output: bool = typer.Option(False, "--json", help="Output JSON."),
|
|
80
|
+
) -> None:
|
|
81
|
+
"""List available phone numbers to purchase. GET /v1/phone-numbers/available."""
|
|
82
|
+
state: AppState = ctx.obj or AppState()
|
|
83
|
+
if not state.has_any_auth():
|
|
84
|
+
auth_required_error(json_output=json_output or state.should_json())
|
|
85
|
+
|
|
86
|
+
client = CommuneClient.from_state(state)
|
|
87
|
+
try:
|
|
88
|
+
r = client.get("/v1/phone-numbers/available", params={
|
|
89
|
+
"type": type,
|
|
90
|
+
"country": country,
|
|
91
|
+
"limit": limit,
|
|
92
|
+
})
|
|
93
|
+
except Exception as exc:
|
|
94
|
+
network_error(exc, json_output=json_output or state.should_json())
|
|
95
|
+
|
|
96
|
+
if not r.is_success:
|
|
97
|
+
api_error(r, json_output=json_output or state.should_json())
|
|
98
|
+
|
|
99
|
+
print_list(
|
|
100
|
+
r.json(),
|
|
101
|
+
json_output=json_output or state.should_json(),
|
|
102
|
+
title="Available Phone Numbers",
|
|
103
|
+
columns=[
|
|
104
|
+
("Number", "phoneNumber"),
|
|
105
|
+
("Friendly Name", "friendlyName"),
|
|
106
|
+
("Region", "region"),
|
|
107
|
+
("Locality", "locality"),
|
|
108
|
+
],
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@app.command("provision")
|
|
113
|
+
def phone_numbers_provision(
|
|
114
|
+
ctx: typer.Context,
|
|
115
|
+
phone_number: Optional[str] = typer.Option(None, "--phone-number", help="Specific E.164 number to buy (e.g. +18005551234). Auto-selected if omitted."),
|
|
116
|
+
type: Optional[str] = typer.Option("tollfree", "--type", help="Number type: tollfree or local."),
|
|
117
|
+
country: Optional[str] = typer.Option("US", "--country", help="Two-letter country code."),
|
|
118
|
+
friendly_name: Optional[str] = typer.Option(None, "--friendly-name", help="Human-readable label for this number."),
|
|
119
|
+
area_code: Optional[str] = typer.Option(None, "--area-code", help="Preferred area code (local numbers only)."),
|
|
120
|
+
json_output: bool = typer.Option(False, "--json", help="Output JSON."),
|
|
121
|
+
) -> None:
|
|
122
|
+
"""Purchase/provision a phone number for SMS. POST /v1/phone-numbers."""
|
|
123
|
+
state: AppState = ctx.obj or AppState()
|
|
124
|
+
if not state.has_any_auth():
|
|
125
|
+
auth_required_error(json_output=json_output or state.should_json())
|
|
126
|
+
|
|
127
|
+
payload: dict = {"type": type, "country": country}
|
|
128
|
+
if phone_number:
|
|
129
|
+
payload["phone_number"] = phone_number
|
|
130
|
+
if friendly_name:
|
|
131
|
+
payload["friendly_name"] = friendly_name
|
|
132
|
+
if area_code:
|
|
133
|
+
payload["area_code"] = area_code
|
|
134
|
+
|
|
135
|
+
client = CommuneClient.from_state(state)
|
|
136
|
+
try:
|
|
137
|
+
r = client.post("/v1/phone-numbers", json=payload)
|
|
138
|
+
except Exception as exc:
|
|
139
|
+
network_error(exc, json_output=json_output or state.should_json())
|
|
140
|
+
|
|
141
|
+
if not r.is_success:
|
|
142
|
+
api_error(r, json_output=json_output or state.should_json())
|
|
143
|
+
|
|
144
|
+
print_record(r.json(), json_output=json_output or state.should_json(), title="Provisioned Phone Number")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@app.command("release")
|
|
148
|
+
def phone_numbers_release(
|
|
149
|
+
ctx: typer.Context,
|
|
150
|
+
phone_number_id: str = typer.Argument(..., help="Phone number ID to release."),
|
|
151
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt."),
|
|
152
|
+
json_output: bool = typer.Option(False, "--json", help="Output JSON."),
|
|
153
|
+
) -> None:
|
|
154
|
+
"""Release a provisioned phone number. DELETE /v1/phone-numbers/{id}."""
|
|
155
|
+
state: AppState = ctx.obj or AppState()
|
|
156
|
+
if not state.has_any_auth():
|
|
157
|
+
auth_required_error(json_output=json_output or state.should_json())
|
|
158
|
+
|
|
159
|
+
if not yes:
|
|
160
|
+
typer.confirm(
|
|
161
|
+
f"Are you sure you want to release phone number {phone_number_id}? This cannot be undone.",
|
|
162
|
+
abort=True,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
client = CommuneClient.from_state(state)
|
|
166
|
+
try:
|
|
167
|
+
r = client.delete(f"/v1/phone-numbers/{phone_number_id}")
|
|
168
|
+
except Exception as exc:
|
|
169
|
+
network_error(exc, json_output=json_output or state.should_json())
|
|
170
|
+
|
|
171
|
+
if not r.is_success:
|
|
172
|
+
api_error(r, json_output=json_output or state.should_json())
|
|
173
|
+
|
|
174
|
+
if json_output or state.should_json():
|
|
175
|
+
from ..output import print_json
|
|
176
|
+
print_json(r.json())
|
|
177
|
+
return
|
|
178
|
+
|
|
179
|
+
print_success(f"Phone number [bold]{phone_number_id}[/bold] released.")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@app.command("settings")
|
|
183
|
+
def phone_numbers_settings(
|
|
184
|
+
ctx: typer.Context,
|
|
185
|
+
json_output: bool = typer.Option(False, "--json", help="Output JSON."),
|
|
186
|
+
) -> None:
|
|
187
|
+
"""Get SMS quota settings for your organization. GET /v1/phone-settings."""
|
|
188
|
+
state: AppState = ctx.obj or AppState()
|
|
189
|
+
if not state.has_any_auth():
|
|
190
|
+
auth_required_error(json_output=json_output or state.should_json())
|
|
191
|
+
|
|
192
|
+
client = CommuneClient.from_state(state)
|
|
193
|
+
try:
|
|
194
|
+
r = client.get("/v1/phone-settings")
|
|
195
|
+
except Exception as exc:
|
|
196
|
+
network_error(exc, json_output=json_output or state.should_json())
|
|
197
|
+
|
|
198
|
+
if not r.is_success:
|
|
199
|
+
api_error(r, json_output=json_output or state.should_json())
|
|
200
|
+
|
|
201
|
+
print_record(r.json(), json_output=json_output or state.should_json(), title="SMS Settings")
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "commune-cli"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.6"
|
|
8
8
|
description = "Official CLI for the Commune email API — agent-native, pipe-friendly, covers every API surface."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
"""commune phone-numbers — phone number management."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing import Optional
|
|
6
|
-
|
|
7
|
-
import typer
|
|
8
|
-
|
|
9
|
-
from ..client import CommuneClient
|
|
10
|
-
from ..errors import api_error, auth_required_error, network_error
|
|
11
|
-
from ..output import print_list, print_record
|
|
12
|
-
from ..state import AppState
|
|
13
|
-
|
|
14
|
-
app = typer.Typer(help="Phone number management: list, get, settings.", no_args_is_help=True)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@app.command("list")
|
|
18
|
-
def phone_numbers_list(
|
|
19
|
-
ctx: typer.Context,
|
|
20
|
-
json_output: bool = typer.Option(False, "--json", help="Output JSON."),
|
|
21
|
-
) -> None:
|
|
22
|
-
"""List all phone numbers in your organization. GET /v1/phone-numbers."""
|
|
23
|
-
state: AppState = ctx.obj or AppState()
|
|
24
|
-
if not state.has_any_auth():
|
|
25
|
-
auth_required_error(json_output=json_output or state.should_json())
|
|
26
|
-
|
|
27
|
-
client = CommuneClient.from_state(state)
|
|
28
|
-
try:
|
|
29
|
-
r = client.get("/v1/phone-numbers")
|
|
30
|
-
except Exception as exc:
|
|
31
|
-
network_error(exc, json_output=json_output or state.should_json())
|
|
32
|
-
|
|
33
|
-
if not r.is_success:
|
|
34
|
-
api_error(r, json_output=json_output or state.should_json())
|
|
35
|
-
|
|
36
|
-
print_list(
|
|
37
|
-
r.json(),
|
|
38
|
-
json_output=json_output or state.should_json(),
|
|
39
|
-
title="Phone Numbers",
|
|
40
|
-
columns=[
|
|
41
|
-
("ID", "id"),
|
|
42
|
-
("Number", "phoneNumber"),
|
|
43
|
-
("Friendly Name", "friendlyName"),
|
|
44
|
-
("Status", "status"),
|
|
45
|
-
("Created", "createdAt"),
|
|
46
|
-
],
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
@app.command("get")
|
|
51
|
-
def phone_numbers_get(
|
|
52
|
-
ctx: typer.Context,
|
|
53
|
-
phone_number_id: str = typer.Argument(..., help="Phone number ID."),
|
|
54
|
-
json_output: bool = typer.Option(False, "--json", help="Output JSON."),
|
|
55
|
-
) -> None:
|
|
56
|
-
"""Get a specific phone number. GET /v1/phone-numbers/{phoneNumberId}."""
|
|
57
|
-
state: AppState = ctx.obj or AppState()
|
|
58
|
-
if not state.has_any_auth():
|
|
59
|
-
auth_required_error(json_output=json_output or state.should_json())
|
|
60
|
-
|
|
61
|
-
client = CommuneClient.from_state(state)
|
|
62
|
-
try:
|
|
63
|
-
r = client.get(f"/v1/phone-numbers/{phone_number_id}")
|
|
64
|
-
except Exception as exc:
|
|
65
|
-
network_error(exc, json_output=json_output or state.should_json())
|
|
66
|
-
|
|
67
|
-
if not r.is_success:
|
|
68
|
-
api_error(r, json_output=json_output or state.should_json())
|
|
69
|
-
|
|
70
|
-
print_record(r.json(), json_output=json_output or state.should_json(), title="Phone Number")
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
@app.command("settings")
|
|
74
|
-
def phone_numbers_settings(
|
|
75
|
-
ctx: typer.Context,
|
|
76
|
-
json_output: bool = typer.Option(False, "--json", help="Output JSON."),
|
|
77
|
-
) -> None:
|
|
78
|
-
"""Get SMS quota settings for your organization. GET /v1/phone-settings."""
|
|
79
|
-
state: AppState = ctx.obj or AppState()
|
|
80
|
-
if not state.has_any_auth():
|
|
81
|
-
auth_required_error(json_output=json_output or state.should_json())
|
|
82
|
-
|
|
83
|
-
client = CommuneClient.from_state(state)
|
|
84
|
-
try:
|
|
85
|
-
r = client.get("/v1/phone-settings")
|
|
86
|
-
except Exception as exc:
|
|
87
|
-
network_error(exc, json_output=json_output or state.should_json())
|
|
88
|
-
|
|
89
|
-
if not r.is_success:
|
|
90
|
-
api_error(r, json_output=json_output or state.should_json())
|
|
91
|
-
|
|
92
|
-
print_record(r.json(), json_output=json_output or state.should_json(), title="SMS Settings")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|