foxreach-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.
- foxreach_cli/__init__.py +3 -0
- foxreach_cli/client_factory.py +22 -0
- foxreach_cli/commands/__init__.py +0 -0
- foxreach_cli/commands/accounts.py +87 -0
- foxreach_cli/commands/analytics.py +69 -0
- foxreach_cli/commands/campaigns.py +218 -0
- foxreach_cli/commands/inbox.py +123 -0
- foxreach_cli/commands/leads.py +178 -0
- foxreach_cli/commands/sequences.py +126 -0
- foxreach_cli/commands/templates.py +133 -0
- foxreach_cli/config.py +63 -0
- foxreach_cli/main.py +92 -0
- foxreach_cli/output.py +66 -0
- foxreach_cli-0.1.0.dist-info/METADATA +145 -0
- foxreach_cli-0.1.0.dist-info/RECORD +18 -0
- foxreach_cli-0.1.0.dist-info/WHEEL +4 -0
- foxreach_cli-0.1.0.dist-info/entry_points.txt +2 -0
- foxreach_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
foxreach_cli/__init__.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from foxreach import FoxReach
|
|
6
|
+
|
|
7
|
+
from . import config as cfg
|
|
8
|
+
from .output import print_error
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_client() -> FoxReach:
|
|
12
|
+
api_key = cfg.get_api_key()
|
|
13
|
+
if not api_key:
|
|
14
|
+
print_error("No API key configured. Run: foxreach config set-key")
|
|
15
|
+
sys.exit(1)
|
|
16
|
+
|
|
17
|
+
kwargs = {"api_key": api_key}
|
|
18
|
+
base_url = cfg.get_base_url()
|
|
19
|
+
if base_url:
|
|
20
|
+
kwargs["base_url"] = base_url
|
|
21
|
+
|
|
22
|
+
return FoxReach(**kwargs)
|
|
File without changes
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from dataclasses import asdict
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
from foxreach import FoxReachError
|
|
9
|
+
|
|
10
|
+
from ..client_factory import get_client
|
|
11
|
+
from ..output import print_detail, print_error, print_json, print_meta, print_success, print_table
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.group()
|
|
15
|
+
def accounts() -> None:
|
|
16
|
+
"""Manage email accounts."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@accounts.command("list")
|
|
20
|
+
@click.option("--page", default=1, type=int)
|
|
21
|
+
@click.option("--limit", default=50, type=int)
|
|
22
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
23
|
+
def accounts_list(page: int, limit: int, as_json: bool) -> None:
|
|
24
|
+
"""List email accounts."""
|
|
25
|
+
client = get_client()
|
|
26
|
+
try:
|
|
27
|
+
result = client.email_accounts.list(page=page, page_size=limit)
|
|
28
|
+
except FoxReachError as e:
|
|
29
|
+
print_error(str(e))
|
|
30
|
+
sys.exit(1)
|
|
31
|
+
|
|
32
|
+
if as_json:
|
|
33
|
+
print_json({"data": [asdict(a) for a in result.data], "meta": asdict(result.meta)})
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
if not result.data:
|
|
37
|
+
click.echo("No email accounts found.")
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
print_table(
|
|
41
|
+
result.data,
|
|
42
|
+
["ID", "Email", "Display Name", "Active", "Daily Limit", "Sent Today", "Health"],
|
|
43
|
+
{
|
|
44
|
+
"ID": "id", "Email": "email", "Display Name": "display_name",
|
|
45
|
+
"Active": "is_active", "Daily Limit": "daily_limit",
|
|
46
|
+
"Sent Today": "sent_today", "Health": "health_score",
|
|
47
|
+
},
|
|
48
|
+
)
|
|
49
|
+
print_meta(result.meta)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@accounts.command("get")
|
|
53
|
+
@click.argument("account_id")
|
|
54
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
55
|
+
def accounts_get(account_id: str, as_json: bool) -> None:
|
|
56
|
+
"""Get an email account by ID."""
|
|
57
|
+
client = get_client()
|
|
58
|
+
try:
|
|
59
|
+
account = client.email_accounts.get(account_id)
|
|
60
|
+
except FoxReachError as e:
|
|
61
|
+
print_error(str(e))
|
|
62
|
+
sys.exit(1)
|
|
63
|
+
|
|
64
|
+
if as_json:
|
|
65
|
+
print_json(account)
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
print_detail(account, [
|
|
69
|
+
"id", "email", "display_name", "provider", "is_active",
|
|
70
|
+
"daily_limit", "sent_today", "warmup_enabled",
|
|
71
|
+
"health_score", "bounce_rate", "reply_rate",
|
|
72
|
+
"connection_status", "created_at",
|
|
73
|
+
])
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@accounts.command("delete")
|
|
77
|
+
@click.argument("account_id")
|
|
78
|
+
@click.confirmation_option(prompt="Are you sure you want to delete this email account?")
|
|
79
|
+
def accounts_delete(account_id: str) -> None:
|
|
80
|
+
"""Delete an email account."""
|
|
81
|
+
client = get_client()
|
|
82
|
+
try:
|
|
83
|
+
client.email_accounts.delete(account_id)
|
|
84
|
+
except FoxReachError as e:
|
|
85
|
+
print_error(str(e))
|
|
86
|
+
sys.exit(1)
|
|
87
|
+
print_success(f"Email account deleted: {account_id}")
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from dataclasses import asdict
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
from foxreach import FoxReachError
|
|
9
|
+
|
|
10
|
+
from ..client_factory import get_client
|
|
11
|
+
from ..output import print_detail, print_error, print_json, print_table
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.group()
|
|
15
|
+
def analytics() -> None:
|
|
16
|
+
"""View analytics and stats."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@analytics.command("overview")
|
|
20
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
21
|
+
def analytics_overview(as_json: bool) -> None:
|
|
22
|
+
"""Show dashboard overview stats."""
|
|
23
|
+
client = get_client()
|
|
24
|
+
try:
|
|
25
|
+
stats = client.analytics.overview()
|
|
26
|
+
except FoxReachError as e:
|
|
27
|
+
print_error(str(e))
|
|
28
|
+
sys.exit(1)
|
|
29
|
+
|
|
30
|
+
if as_json:
|
|
31
|
+
print_json(stats)
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
print_detail(stats, [
|
|
35
|
+
"total_accounts", "active_accounts",
|
|
36
|
+
"total_campaigns", "active_campaigns",
|
|
37
|
+
"total_leads", "total_sent", "total_replies",
|
|
38
|
+
"reply_rate", "account_health_avg",
|
|
39
|
+
])
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@analytics.command("campaign")
|
|
43
|
+
@click.argument("campaign_id")
|
|
44
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
45
|
+
def analytics_campaign(campaign_id: str, as_json: bool) -> None:
|
|
46
|
+
"""Show campaign analytics."""
|
|
47
|
+
client = get_client()
|
|
48
|
+
try:
|
|
49
|
+
stats = client.analytics.campaign(campaign_id)
|
|
50
|
+
except FoxReachError as e:
|
|
51
|
+
print_error(str(e))
|
|
52
|
+
sys.exit(1)
|
|
53
|
+
|
|
54
|
+
if as_json:
|
|
55
|
+
print_json(stats)
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
print_detail(stats, [
|
|
59
|
+
"sent", "delivered", "bounced", "replied", "opened",
|
|
60
|
+
"reply_rate", "bounce_rate",
|
|
61
|
+
])
|
|
62
|
+
|
|
63
|
+
if stats.daily_stats:
|
|
64
|
+
click.echo("\nDaily Stats:")
|
|
65
|
+
print_table(
|
|
66
|
+
stats.daily_stats,
|
|
67
|
+
["Date", "Sent", "Delivered", "Bounced", "Replied", "Opened"],
|
|
68
|
+
{"Date": "date", "Sent": "sent", "Delivered": "delivered", "Bounced": "bounced", "Replied": "replied", "Opened": "opened"},
|
|
69
|
+
)
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from dataclasses import asdict
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from foxreach import CampaignCreate, CampaignUpdate, FoxReachError
|
|
10
|
+
|
|
11
|
+
from ..client_factory import get_client
|
|
12
|
+
from ..output import print_detail, print_error, print_json, print_meta, print_success, print_table
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.group()
|
|
16
|
+
def campaigns() -> None:
|
|
17
|
+
"""Manage campaigns."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@campaigns.command("list")
|
|
21
|
+
@click.option("--status", default=None, type=click.Choice(["draft", "active", "paused", "completed"]))
|
|
22
|
+
@click.option("--page", default=1, type=int)
|
|
23
|
+
@click.option("--limit", default=50, type=int)
|
|
24
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
25
|
+
def campaigns_list(status: Optional[str], page: int, limit: int, as_json: bool) -> None:
|
|
26
|
+
"""List campaigns."""
|
|
27
|
+
client = get_client()
|
|
28
|
+
try:
|
|
29
|
+
result = client.campaigns.list(page=page, page_size=limit, status=status)
|
|
30
|
+
except FoxReachError as e:
|
|
31
|
+
print_error(str(e))
|
|
32
|
+
sys.exit(1)
|
|
33
|
+
|
|
34
|
+
if as_json:
|
|
35
|
+
print_json({"data": [asdict(c) for c in result.data], "meta": asdict(result.meta)})
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
if not result.data:
|
|
39
|
+
click.echo("No campaigns found.")
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
print_table(
|
|
43
|
+
result.data,
|
|
44
|
+
["ID", "Name", "Status", "Sent", "Replied", "Leads", "Daily Limit"],
|
|
45
|
+
{
|
|
46
|
+
"ID": "id", "Name": "name", "Status": "status",
|
|
47
|
+
"Sent": "total_sent", "Replied": "total_replied",
|
|
48
|
+
"Leads": "total_leads", "Daily Limit": "daily_limit",
|
|
49
|
+
},
|
|
50
|
+
)
|
|
51
|
+
print_meta(result.meta)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@campaigns.command("get")
|
|
55
|
+
@click.argument("campaign_id")
|
|
56
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
57
|
+
def campaigns_get(campaign_id: str, as_json: bool) -> None:
|
|
58
|
+
"""Get a campaign by ID."""
|
|
59
|
+
client = get_client()
|
|
60
|
+
try:
|
|
61
|
+
campaign = client.campaigns.get(campaign_id)
|
|
62
|
+
except FoxReachError as e:
|
|
63
|
+
print_error(str(e))
|
|
64
|
+
sys.exit(1)
|
|
65
|
+
|
|
66
|
+
if as_json:
|
|
67
|
+
print_json(campaign)
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
print_detail(campaign, [
|
|
71
|
+
"id", "name", "status", "timezone", "sending_days",
|
|
72
|
+
"sending_start_hour", "sending_end_hour", "daily_limit",
|
|
73
|
+
"total_leads", "total_sent", "total_delivered",
|
|
74
|
+
"total_bounced", "total_replied", "total_opened",
|
|
75
|
+
"created_at", "started_at", "completed_at",
|
|
76
|
+
])
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@campaigns.command("create")
|
|
80
|
+
@click.option("--name", required=True, help="Campaign name")
|
|
81
|
+
@click.option("--timezone", default=None, help="IANA timezone (e.g. America/New_York)")
|
|
82
|
+
@click.option("--daily-limit", default=None, type=int, help="Max emails per day")
|
|
83
|
+
@click.option("--start-hour", default=None, type=int, help="Sending start hour (0-23)")
|
|
84
|
+
@click.option("--end-hour", default=None, type=int, help="Sending end hour (0-23)")
|
|
85
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
86
|
+
def campaigns_create(
|
|
87
|
+
name: str,
|
|
88
|
+
timezone: Optional[str],
|
|
89
|
+
daily_limit: Optional[int],
|
|
90
|
+
start_hour: Optional[int],
|
|
91
|
+
end_hour: Optional[int],
|
|
92
|
+
as_json: bool,
|
|
93
|
+
) -> None:
|
|
94
|
+
"""Create a new campaign."""
|
|
95
|
+
client = get_client()
|
|
96
|
+
try:
|
|
97
|
+
campaign = client.campaigns.create(CampaignCreate(
|
|
98
|
+
name=name,
|
|
99
|
+
timezone=timezone or "UTC",
|
|
100
|
+
daily_limit=daily_limit,
|
|
101
|
+
sending_start_hour=start_hour,
|
|
102
|
+
sending_end_hour=end_hour,
|
|
103
|
+
))
|
|
104
|
+
except FoxReachError as e:
|
|
105
|
+
print_error(str(e))
|
|
106
|
+
sys.exit(1)
|
|
107
|
+
|
|
108
|
+
if as_json:
|
|
109
|
+
print_json(campaign)
|
|
110
|
+
else:
|
|
111
|
+
print_success(f"Campaign created: {campaign.id}")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@campaigns.command("update")
|
|
115
|
+
@click.argument("campaign_id")
|
|
116
|
+
@click.option("--name", default=None, help="Campaign name")
|
|
117
|
+
@click.option("--timezone", default=None, help="IANA timezone")
|
|
118
|
+
@click.option("--daily-limit", default=None, type=int, help="Max emails per day")
|
|
119
|
+
@click.option("--start-hour", default=None, type=int, help="Sending start hour")
|
|
120
|
+
@click.option("--end-hour", default=None, type=int, help="Sending end hour")
|
|
121
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
122
|
+
def campaigns_update(
|
|
123
|
+
campaign_id: str,
|
|
124
|
+
name: Optional[str],
|
|
125
|
+
timezone: Optional[str],
|
|
126
|
+
daily_limit: Optional[int],
|
|
127
|
+
start_hour: Optional[int],
|
|
128
|
+
end_hour: Optional[int],
|
|
129
|
+
as_json: bool,
|
|
130
|
+
) -> None:
|
|
131
|
+
"""Update a campaign."""
|
|
132
|
+
client = get_client()
|
|
133
|
+
try:
|
|
134
|
+
campaign = client.campaigns.update(campaign_id, CampaignUpdate(
|
|
135
|
+
name=name,
|
|
136
|
+
timezone=timezone,
|
|
137
|
+
daily_limit=daily_limit,
|
|
138
|
+
sending_start_hour=start_hour,
|
|
139
|
+
sending_end_hour=end_hour,
|
|
140
|
+
))
|
|
141
|
+
except FoxReachError as e:
|
|
142
|
+
print_error(str(e))
|
|
143
|
+
sys.exit(1)
|
|
144
|
+
|
|
145
|
+
if as_json:
|
|
146
|
+
print_json(campaign)
|
|
147
|
+
else:
|
|
148
|
+
print_success(f"Campaign updated: {campaign.id}")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@campaigns.command("delete")
|
|
152
|
+
@click.argument("campaign_id")
|
|
153
|
+
@click.confirmation_option(prompt="Are you sure you want to delete this campaign?")
|
|
154
|
+
def campaigns_delete(campaign_id: str) -> None:
|
|
155
|
+
"""Delete a campaign (must be in draft status)."""
|
|
156
|
+
client = get_client()
|
|
157
|
+
try:
|
|
158
|
+
client.campaigns.delete(campaign_id)
|
|
159
|
+
except FoxReachError as e:
|
|
160
|
+
print_error(str(e))
|
|
161
|
+
sys.exit(1)
|
|
162
|
+
print_success(f"Campaign deleted: {campaign_id}")
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@campaigns.command("start")
|
|
166
|
+
@click.argument("campaign_id")
|
|
167
|
+
def campaigns_start(campaign_id: str) -> None:
|
|
168
|
+
"""Start a campaign."""
|
|
169
|
+
client = get_client()
|
|
170
|
+
try:
|
|
171
|
+
campaign = client.campaigns.start(campaign_id)
|
|
172
|
+
except FoxReachError as e:
|
|
173
|
+
print_error(str(e))
|
|
174
|
+
sys.exit(1)
|
|
175
|
+
print_success(f"Campaign started: {campaign.name} ({campaign.id})")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@campaigns.command("pause")
|
|
179
|
+
@click.argument("campaign_id")
|
|
180
|
+
def campaigns_pause(campaign_id: str) -> None:
|
|
181
|
+
"""Pause a running campaign."""
|
|
182
|
+
client = get_client()
|
|
183
|
+
try:
|
|
184
|
+
campaign = client.campaigns.pause(campaign_id)
|
|
185
|
+
except FoxReachError as e:
|
|
186
|
+
print_error(str(e))
|
|
187
|
+
sys.exit(1)
|
|
188
|
+
print_success(f"Campaign paused: {campaign.name} ({campaign.id})")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@campaigns.command("add-leads")
|
|
192
|
+
@click.argument("campaign_id")
|
|
193
|
+
@click.option("--lead-ids", required=True, help="Comma-separated lead IDs")
|
|
194
|
+
def campaigns_add_leads(campaign_id: str, lead_ids: str) -> None:
|
|
195
|
+
"""Add leads to a campaign."""
|
|
196
|
+
ids = [i.strip() for i in lead_ids.split(",") if i.strip()]
|
|
197
|
+
client = get_client()
|
|
198
|
+
try:
|
|
199
|
+
result = client.campaigns.add_leads(campaign_id, ids)
|
|
200
|
+
except FoxReachError as e:
|
|
201
|
+
print_error(str(e))
|
|
202
|
+
sys.exit(1)
|
|
203
|
+
print_success(f"Added {result.get('added', len(ids))} leads to campaign {campaign_id}")
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@campaigns.command("add-accounts")
|
|
207
|
+
@click.argument("campaign_id")
|
|
208
|
+
@click.option("--account-ids", required=True, help="Comma-separated account IDs")
|
|
209
|
+
def campaigns_add_accounts(campaign_id: str, account_ids: str) -> None:
|
|
210
|
+
"""Assign email accounts to a campaign."""
|
|
211
|
+
ids = [i.strip() for i in account_ids.split(",") if i.strip()]
|
|
212
|
+
client = get_client()
|
|
213
|
+
try:
|
|
214
|
+
result = client.campaigns.add_accounts(campaign_id, ids)
|
|
215
|
+
except FoxReachError as e:
|
|
216
|
+
print_error(str(e))
|
|
217
|
+
sys.exit(1)
|
|
218
|
+
print_success(f"Added {result.get('added', len(ids))} accounts to campaign {campaign_id}")
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from dataclasses import asdict
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from foxreach import FoxReachError, ThreadUpdate
|
|
10
|
+
|
|
11
|
+
from ..client_factory import get_client
|
|
12
|
+
from ..output import print_detail, print_error, print_json, print_meta, print_success, print_table
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.group()
|
|
16
|
+
def inbox() -> None:
|
|
17
|
+
"""Manage inbox threads."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@inbox.command("list")
|
|
21
|
+
@click.option("--category", default=None, help="Filter by category")
|
|
22
|
+
@click.option("--unread", is_flag=True, default=False, help="Show only unread")
|
|
23
|
+
@click.option("--starred", is_flag=True, default=False, help="Show only starred")
|
|
24
|
+
@click.option("--campaign", default=None, help="Filter by campaign ID")
|
|
25
|
+
@click.option("--account", default=None, help="Filter by account ID")
|
|
26
|
+
@click.option("--search", default=None, help="Search threads")
|
|
27
|
+
@click.option("--page", default=1, type=int)
|
|
28
|
+
@click.option("--limit", default=50, type=int)
|
|
29
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
30
|
+
def inbox_list(
|
|
31
|
+
category: Optional[str],
|
|
32
|
+
unread: bool,
|
|
33
|
+
starred: bool,
|
|
34
|
+
campaign: Optional[str],
|
|
35
|
+
account: Optional[str],
|
|
36
|
+
search: Optional[str],
|
|
37
|
+
page: int,
|
|
38
|
+
limit: int,
|
|
39
|
+
as_json: bool,
|
|
40
|
+
) -> None:
|
|
41
|
+
"""List inbox threads."""
|
|
42
|
+
client = get_client()
|
|
43
|
+
try:
|
|
44
|
+
result = client.inbox.list_threads(
|
|
45
|
+
page=page,
|
|
46
|
+
page_size=limit,
|
|
47
|
+
category=category,
|
|
48
|
+
is_read=False if unread else None,
|
|
49
|
+
is_starred=True if starred else None,
|
|
50
|
+
campaign_id=campaign,
|
|
51
|
+
account_id=account,
|
|
52
|
+
search=search,
|
|
53
|
+
)
|
|
54
|
+
except FoxReachError as e:
|
|
55
|
+
print_error(str(e))
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
|
|
58
|
+
if as_json:
|
|
59
|
+
print_json({"data": [asdict(t) for t in result.data], "meta": asdict(result.meta)})
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
if not result.data:
|
|
63
|
+
click.echo("No threads found.")
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
print_table(
|
|
67
|
+
result.data,
|
|
68
|
+
["ID", "From", "Subject", "Category", "Read", "Starred", "Received"],
|
|
69
|
+
{
|
|
70
|
+
"ID": "id", "From": "from_email", "Subject": "subject",
|
|
71
|
+
"Category": "category", "Read": "is_read",
|
|
72
|
+
"Starred": "is_starred", "Received": "received_at",
|
|
73
|
+
},
|
|
74
|
+
)
|
|
75
|
+
print_meta(result.meta)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@inbox.command("get")
|
|
79
|
+
@click.argument("reply_id")
|
|
80
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
81
|
+
def inbox_get(reply_id: str, as_json: bool) -> None:
|
|
82
|
+
"""Get a thread by ID."""
|
|
83
|
+
client = get_client()
|
|
84
|
+
try:
|
|
85
|
+
thread = client.inbox.get(reply_id)
|
|
86
|
+
except FoxReachError as e:
|
|
87
|
+
print_error(str(e))
|
|
88
|
+
sys.exit(1)
|
|
89
|
+
|
|
90
|
+
if as_json:
|
|
91
|
+
print_json(thread)
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
print_detail(thread, [
|
|
95
|
+
"id", "from_email", "to_email", "subject",
|
|
96
|
+
"body_plain", "category", "is_read", "is_starred",
|
|
97
|
+
"campaign_id", "lead_id", "received_at",
|
|
98
|
+
])
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@inbox.command("update")
|
|
102
|
+
@click.argument("reply_id")
|
|
103
|
+
@click.option("--read/--unread", default=None, help="Mark as read or unread")
|
|
104
|
+
@click.option("--starred/--unstarred", default=None, help="Star or unstar")
|
|
105
|
+
@click.option("--category", default=None, help="Set category")
|
|
106
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
107
|
+
def inbox_update(reply_id: str, read: Optional[bool], starred: Optional[bool], category: Optional[str], as_json: bool) -> None:
|
|
108
|
+
"""Update a thread (read status, star, category)."""
|
|
109
|
+
client = get_client()
|
|
110
|
+
try:
|
|
111
|
+
thread = client.inbox.update(reply_id, ThreadUpdate(
|
|
112
|
+
is_read=read,
|
|
113
|
+
is_starred=starred,
|
|
114
|
+
category=category,
|
|
115
|
+
))
|
|
116
|
+
except FoxReachError as e:
|
|
117
|
+
print_error(str(e))
|
|
118
|
+
sys.exit(1)
|
|
119
|
+
|
|
120
|
+
if as_json:
|
|
121
|
+
print_json(thread)
|
|
122
|
+
else:
|
|
123
|
+
print_success(f"Thread updated: {reply_id}")
|