cdx-manager 0.2.1

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.
@@ -0,0 +1,270 @@
1
+ from datetime import datetime
2
+
3
+ from .cli_render import (
4
+ _dim,
5
+ _format_pct,
6
+ _format_relative_age,
7
+ _pad_table,
8
+ _style,
9
+ _style_pct,
10
+ )
11
+
12
+
13
+ def _format_reset_time(value):
14
+ if not value:
15
+ return "-"
16
+ timestamp = _parse_reset_timestamp(value)
17
+ if timestamp is None:
18
+ return value
19
+ delta_s = timestamp - _now_timestamp()
20
+ if delta_s < 0:
21
+ minutes_ago = int(abs(delta_s) // 60)
22
+ if minutes_ago < 1:
23
+ return "passed"
24
+ if minutes_ago < 60:
25
+ return f"passed {minutes_ago}m ago"
26
+ hours_ago = minutes_ago // 60
27
+ if hours_ago < 24:
28
+ return f"passed {hours_ago}h ago"
29
+ return value
30
+ if delta_s < 60:
31
+ return "now"
32
+ if delta_s < 24 * 60 * 60:
33
+ minutes = int(delta_s // 60)
34
+ hours = minutes // 60
35
+ remaining_minutes = minutes % 60
36
+ if hours == 0:
37
+ return f"in {remaining_minutes}m"
38
+ if remaining_minutes == 0:
39
+ return f"in {hours}h"
40
+ return f"in {hours}h {remaining_minutes}m"
41
+ return value
42
+
43
+
44
+ def _style_reset_time(value, use_color=False):
45
+ text = _format_reset_time(value)
46
+ if text == "-":
47
+ return _style(text, "2", use_color)
48
+ if text == "now" or text.startswith("in "):
49
+ return _style(text, "32", use_color)
50
+ if text == "passed" or text.startswith("passed "):
51
+ return _style(text, "31", use_color)
52
+ return text
53
+
54
+
55
+ def _format_status_rows(rows, use_color=False, small=False):
56
+ has_provider = len({r["provider"] for r in rows}) > 1 and not small
57
+ if small:
58
+ headers = ["SESSION", "OK", "5H", "WEEK", "RESET 5H", "RESET WEEK"]
59
+ elif has_provider:
60
+ headers = ["SESSION", "PROV.", "OK", "5H", "WEEK", "BLOCK", "CR", "RESET 5H", "RESET WEEK", "UPDATED"]
61
+ else:
62
+ headers = ["SESSION", "OK", "5H", "WEEK", "BLOCK", "CR", "RESET 5H", "RESET WEEK", "UPDATED"]
63
+ if not rows:
64
+ if small:
65
+ return "SESSION OK 5H WEEK RESET 5H RESET WEEK\nNo saved sessions yet."
66
+ return "SESSION OK 5H WEEK BLOCK CR RESET 5H RESET WEEK UPDATED\nNo saved sessions yet."
67
+ headers = [_style(header, "1", use_color) for header in headers]
68
+ priority = _recommend_priority_sessions(rows)
69
+ table_rows = []
70
+ for r in priority:
71
+ base = [r["session_name"]]
72
+ if has_provider:
73
+ base.append(r.get("provider") or "n/a")
74
+ usage_columns = [
75
+ _style_pct(r.get("available_pct"), use_color),
76
+ _style_pct(r.get("remaining_5h_pct"), use_color),
77
+ _style_pct(r.get("remaining_week_pct"), use_color),
78
+ _style_reset_time(r.get("reset_5h_at"), use_color),
79
+ _style_reset_time(r.get("reset_week_at"), use_color),
80
+ ]
81
+ if small:
82
+ base += usage_columns
83
+ else:
84
+ block = _format_blocking_quota(r)
85
+ credits = str(r["credits"]) if r.get("credits") is not None else "-"
86
+ base += usage_columns[:3] + [
87
+ _style(block, "33" if block not in ("?", "-") else "2", use_color),
88
+ _style(credits, "33" if r.get("credits") is not None else "2", use_color),
89
+ *usage_columns[3:],
90
+ _style(_format_relative_age(r.get("updated_at")), "2", use_color),
91
+ ]
92
+ table_rows.append(base)
93
+ priority_line = (
94
+ f"Priority: {_priority_instruction(priority[0], 'first')}"
95
+ + (
96
+ f", {_priority_instruction(priority[1], 'next')}."
97
+ if len(priority) > 1 else "."
98
+ )
99
+ ) if priority else "Priority: no usable session status yet."
100
+ return "\n".join([
101
+ _pad_table([headers] + table_rows),
102
+ "",
103
+ _style(priority_line, "1", use_color),
104
+ _style("Tip: run /status in codex to refresh. Claude sessions auto-refresh; use --refresh to force.", "2", use_color),
105
+ ])
106
+
107
+
108
+ def _recommend_priority_sessions(rows):
109
+ if not rows:
110
+ return []
111
+
112
+ def rank(row):
113
+ has_credits = row.get("credits") is not None
114
+ credit_rank = 0 if has_credits else 1
115
+ available = row.get("available_pct")
116
+ usable_now = available is not None and available > 0
117
+ known_available = available is not None
118
+ reset_timestamp = _priority_reset_timestamp(row)
119
+ reset_is_future = reset_timestamp is not None and reset_timestamp >= _now_timestamp()
120
+ blocked_future = not usable_now and reset_is_future
121
+ reset_is_known = reset_timestamp is not None
122
+ reset_rank = -reset_timestamp if reset_is_known else float("-inf")
123
+ available_rank = available if available is not None else -1
124
+ name_rank = row.get("session_name") or ""
125
+ if usable_now:
126
+ return (3, credit_rank, 1 if known_available else 0, available_rank, reset_rank, name_rank)
127
+ if blocked_future:
128
+ return (2, 1 if reset_is_known else 0, reset_rank, credit_rank, available_rank, name_rank)
129
+ if reset_is_known:
130
+ return (1, reset_rank, credit_rank, 1 if known_available else 0, available_rank, name_rank)
131
+ return (0, credit_rank, 1 if known_available else 0, available_rank, name_rank)
132
+
133
+ return sorted(rows, key=rank, reverse=True)
134
+
135
+
136
+ def _format_blocking_quota(row):
137
+ remaining_5h = row.get("remaining_5h_pct")
138
+ remaining_week = row.get("remaining_week_pct")
139
+ if remaining_5h is None and remaining_week is None:
140
+ return "?"
141
+ if remaining_5h is None:
142
+ return "WEEK"
143
+ if remaining_week is None:
144
+ return "5H"
145
+ if remaining_5h < remaining_week:
146
+ return "5H"
147
+ if remaining_week < remaining_5h:
148
+ return "WEEK"
149
+ return "5H+WEEK"
150
+
151
+
152
+ def _priority_instruction(row, position):
153
+ action = "refresh" if _priority_needs_refresh(row) else "use"
154
+ if position == "next" and action == "use":
155
+ return f"next {row['session_name']} ({_priority_reason(row)})"
156
+ return f"{action} {row['session_name']} {position} ({_priority_reason(row)})"
157
+
158
+
159
+ def _priority_needs_refresh(row):
160
+ available = row.get("available_pct")
161
+ if available is None or available > 0:
162
+ return False
163
+ _label, is_past = _priority_reset_info(row)
164
+ return is_past
165
+
166
+
167
+ def _priority_reason(row):
168
+ available = row.get("available_pct")
169
+ if available is None:
170
+ return "status unknown"
171
+ if available > 0:
172
+ return f"{_format_pct(available)} OK"
173
+ label, is_past = _priority_reset_info(row)
174
+ if label:
175
+ if is_past:
176
+ return f"0% OK, {label} reset passed"
177
+ return f"0% OK, {label} resets first"
178
+ return "0% OK"
179
+
180
+
181
+ def _priority_reset_info(row):
182
+ remaining_5h = row.get("remaining_5h_pct")
183
+ remaining_week = row.get("remaining_week_pct")
184
+ candidates = []
185
+ if remaining_5h is not None:
186
+ candidates.append((remaining_5h, "5H", row.get("reset_5h_at")))
187
+ if remaining_week is not None:
188
+ candidates.append((remaining_week, "WEEK", row.get("reset_week_at")))
189
+ if not candidates:
190
+ return None
191
+ lowest_remaining = min(value for value, _label, _reset in candidates)
192
+ blocked = [
193
+ (label, reset)
194
+ for value, label, reset in candidates
195
+ if value == lowest_remaining and reset
196
+ ]
197
+ timestamps = [
198
+ (timestamp, label)
199
+ for label, reset in blocked
200
+ for timestamp in [_parse_reset_timestamp(reset)]
201
+ if timestamp is not None
202
+ ]
203
+ if timestamps:
204
+ timestamp, label = min(timestamps)
205
+ return label, timestamp < _now_timestamp()
206
+ if blocked:
207
+ return blocked[0][0], False
208
+ return None, False
209
+
210
+
211
+ def _priority_reset_timestamp(row):
212
+ remaining_5h = row.get("remaining_5h_pct")
213
+ remaining_week = row.get("remaining_week_pct")
214
+ candidates = []
215
+ if remaining_5h is not None:
216
+ candidates.append((remaining_5h, row.get("reset_5h_at")))
217
+ if remaining_week is not None:
218
+ candidates.append((remaining_week, row.get("reset_week_at")))
219
+ if not candidates:
220
+ return None
221
+ lowest_remaining = min(value for value, _reset in candidates)
222
+ reset_values = [_reset for value, _reset in candidates if value == lowest_remaining]
223
+ timestamps = [
224
+ timestamp
225
+ for timestamp in (_parse_reset_timestamp(reset_value) for reset_value in reset_values)
226
+ if timestamp is not None
227
+ ]
228
+ if not timestamps:
229
+ return None
230
+ return min(timestamps)
231
+
232
+
233
+ def _parse_reset_timestamp(value):
234
+ if not value:
235
+ return None
236
+ text = str(value).strip()
237
+ try:
238
+ parsed = datetime.fromisoformat(text.replace("Z", "+00:00"))
239
+ if parsed.tzinfo is None:
240
+ parsed = parsed.replace(tzinfo=datetime.now().astimezone().tzinfo)
241
+ return parsed.timestamp()
242
+ except (TypeError, ValueError):
243
+ pass
244
+ try:
245
+ parsed = datetime.strptime(text, "%b %d %H:%M")
246
+ except (TypeError, ValueError):
247
+ return None
248
+ now = datetime.now().astimezone()
249
+ parsed = parsed.replace(year=now.year, tzinfo=now.tzinfo)
250
+ return parsed.timestamp()
251
+
252
+
253
+ def _now_timestamp():
254
+ return datetime.now().astimezone().timestamp()
255
+
256
+
257
+ def _format_status_detail(row, use_color=False):
258
+ lines = [
259
+ f"{_style('Session:', '1', use_color)} {row['session_name']}",
260
+ f"{_style('Provider:', '1', use_color)} {row.get('provider') or 'n/a'}",
261
+ f"{_style('Available:', '1', use_color)} {_style_pct(row.get('available_pct'), use_color)}",
262
+ f"{_style('5h left:', '1', use_color)} {_style_pct(row.get('remaining_5h_pct'), use_color)}",
263
+ f"{_style('Week left:', '1', use_color)} {_style_pct(row.get('remaining_week_pct'), use_color)}",
264
+ f"{_style('Block:', '1', use_color)} {_style(_format_blocking_quota(row), '33', use_color)}",
265
+ f"{_style('Credits:', '1', use_color)} {_style(row['credits'] if row.get('credits') is not None else 'n/a', '33' if row.get('credits') is not None else '2', use_color)}",
266
+ f"{_style('5h reset:', '1', use_color)} {_style_reset_time(row.get('reset_5h_at'), use_color)}",
267
+ f"{_style('Week reset:', '1', use_color)} {_style_reset_time(row.get('reset_week_at'), use_color)}",
268
+ f"{_style('Updated:', '1', use_color)} {_dim(_format_relative_age(row.get('updated_at')), use_color)}",
269
+ ]
270
+ return "\n".join(lines)