cdx-manager 0.5.3 → 0.5.5

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.
@@ -7,6 +7,7 @@ import subprocess
7
7
  import sys
8
8
  from datetime import datetime, timezone
9
9
 
10
+ from .config import PROVIDER_CLAUDE, PROVIDER_CODEX
10
11
  from .errors import CdxError
11
12
 
12
13
 
@@ -99,10 +100,15 @@ def _wrap_launch_with_transcript(session, spec, capture_transcript=True, env=Non
99
100
 
100
101
 
101
102
  def _build_launch_spec(session, cwd=None, env_override=None, initial_prompt=None):
103
+ if initial_prompt is not None:
104
+ if not isinstance(initial_prompt, str):
105
+ raise CdxError("initial_prompt must be a string.")
106
+ if len(initial_prompt) > 32768:
107
+ raise CdxError("initial_prompt exceeds maximum allowed length.")
102
108
  cwd = cwd or os.getcwd()
103
109
  env_override = env_override or {}
104
110
  env = {**os.environ, **env_override}
105
- if session["provider"] == "claude":
111
+ if session["provider"] == PROVIDER_CLAUDE:
106
112
  args = ["--name", session["name"]]
107
113
  if initial_prompt:
108
114
  args.append(initial_prompt)
@@ -130,7 +136,7 @@ def _build_launch_spec(session, cwd=None, env_override=None, initial_prompt=None
130
136
 
131
137
  def _build_login_status_spec(session, env_override=None):
132
138
  env = {**os.environ, **(env_override or {})}
133
- if session["provider"] == "claude":
139
+ if session["provider"] == PROVIDER_CLAUDE:
134
140
  env.update(_home_env_overrides(_get_auth_home(session)))
135
141
 
136
142
  def parser(output):
@@ -155,7 +161,7 @@ def _build_login_status_spec(session, env_override=None):
155
161
  def _build_auth_action_spec(session, action, cwd=None, env_override=None):
156
162
  cwd = cwd or os.getcwd()
157
163
  env = {**os.environ, **(env_override or {})}
158
- if session["provider"] == "claude":
164
+ if session["provider"] == PROVIDER_CLAUDE:
159
165
  env.update(_home_env_overrides(_get_auth_home(session)))
160
166
  return {"command": "claude", "args": ["auth", action],
161
167
  "options": {"cwd": cwd, "env": env}, "label": f"claude auth {action}"}
@@ -184,7 +190,7 @@ def _resolve_command(command, env=None):
184
190
  def _probe_provider_auth(session, spawn_sync=None, env_override=None):
185
191
  spawn_sync = spawn_sync or subprocess.run
186
192
  spec = _build_login_status_spec(session, env_override)
187
- if session.get("provider") == "codex":
193
+ if session.get("provider") == PROVIDER_CODEX:
188
194
  auth_path = os.path.join(_get_auth_home(session), "auth.json")
189
195
  if os.path.isfile(auth_path):
190
196
  return True
@@ -250,11 +256,12 @@ def _run_interactive_provider_command(session, action, spawn=None, cwd=None,
250
256
  spec = _fallback_launch_spec_or_raise(spec, error)
251
257
  child = start_child(spec)
252
258
 
253
- forwarded_signal = [None]
259
+ forwarded_signal = None
254
260
  handlers = []
255
261
 
256
262
  def forward(sig, _frame=None):
257
- forwarded_signal[0] = sig
263
+ nonlocal forwarded_signal
264
+ forwarded_signal = sig
258
265
  try:
259
266
  if hasattr(child, "send_signal"):
260
267
  child.send_signal(sig)
@@ -283,7 +290,7 @@ def _run_interactive_provider_command(session, action, spawn=None, cwd=None,
283
290
 
284
291
  try:
285
292
  child.wait()
286
- if forwarded_signal[0] is None and child.returncode != 0 and _should_retry_without_transcript(spec):
293
+ if forwarded_signal is None and child.returncode != 0 and _should_retry_without_transcript(spec):
287
294
  spec = _fallback_launch_spec_or_raise(spec)
288
295
  child = start_child(spec)
289
296
  child.wait()
@@ -301,10 +308,10 @@ def _run_interactive_provider_command(session, action, spawn=None, cwd=None,
301
308
  except (OSError, ValueError):
302
309
  pass
303
310
 
304
- if forwarded_signal[0] is not None:
311
+ if forwarded_signal is not None:
305
312
  raise CdxError(
306
- f"{spec['label']} interrupted by {_signal_name(forwarded_signal[0])} for session {session['name']}",
307
- _signal_exit_code(forwarded_signal[0]),
313
+ f"{spec['label']} interrupted by {_signal_name(forwarded_signal)} for session {session['name']}",
314
+ _signal_exit_code(forwarded_signal),
308
315
  )
309
316
  if child.returncode != 0:
310
317
  raise CdxError(
@@ -8,14 +8,14 @@ from datetime import datetime, timezone
8
8
  from urllib.parse import quote
9
9
 
10
10
  from .backup_bundle import decode_bundle, encode_bundle
11
- from .config import get_cdx_home
11
+ from .config import PROVIDER_CLAUDE, PROVIDER_CODEX, PROVIDERS, get_cdx_home
12
12
  from .codex_usage import fetch_codex_rate_limits
13
13
  from .errors import CdxError
14
14
  from .session_store import create_session_store
15
15
  from .status_source import find_latest_status_artifact
16
16
 
17
- DEFAULT_PROVIDER = "codex"
18
- ALLOWED_PROVIDERS = {"codex", "claude"}
17
+ DEFAULT_PROVIDER = PROVIDER_CODEX
18
+ ALLOWED_PROVIDERS = set(PROVIDERS)
19
19
  MAX_SESSION_NAME_LENGTH = 64
20
20
  RESERVED_SESSION_NAMES = {
21
21
  "add",
@@ -81,8 +81,15 @@ def _local_now_iso():
81
81
 
82
82
 
83
83
  def _safe_relpath(path):
84
- normalized = str(path or "").replace("\\", "/").strip("/")
85
- if not normalized or normalized.startswith("../") or "/../" in f"/{normalized}/":
84
+ normalized = os.path.normpath(str(path or "").replace("\\", "/")).replace("\\", "/")
85
+ if (
86
+ not normalized
87
+ or normalized == "."
88
+ or normalized.startswith("/")
89
+ or (len(normalized) > 1 and normalized[1] == ":")
90
+ or normalized == ".."
91
+ or normalized.startswith("../")
92
+ ):
86
93
  raise CdxError("Bundle contains an unsafe file path.")
87
94
  return normalized
88
95
 
@@ -253,7 +260,7 @@ def create_session_service(options=None):
253
260
 
254
261
  def _get_session_auth_home(name, provider):
255
262
  root = _get_session_root(name)
256
- if provider == "claude":
263
+ if provider == PROVIDER_CLAUDE:
257
264
  return os.path.join(root, "claude-home")
258
265
  return root
259
266
 
@@ -288,22 +295,39 @@ def create_session_service(options=None):
288
295
  "auth": session.get("auth"),
289
296
  }
290
297
 
291
- def _collect_profile_files(session_root):
292
- excluded_dirs = {"log", "tmp", "cache", "__pycache__", "shell_snapshots"}
298
+ def _auth_bundle_paths(provider):
299
+ if provider == PROVIDER_CLAUDE:
300
+ return [
301
+ "claude-home/.claude/.credentials.json",
302
+ "claude-home/.claude.json",
303
+ "claude-home/auth.json",
304
+ ]
305
+ return [
306
+ "auth.json",
307
+ ]
308
+
309
+ def _collect_auth_files(session_root, provider, session_name=None, progress_callback=None):
293
310
  files = []
311
+ total_bytes = 0
294
312
  if not os.path.isdir(session_root):
295
- return files
296
- for dirpath, dirnames, filenames in os.walk(session_root):
297
- dirnames[:] = [name for name in dirnames if name not in excluded_dirs]
298
- for filename in filenames:
299
- full_path = os.path.join(dirpath, filename)
300
- if not os.path.isfile(full_path):
301
- continue
302
- rel_path = os.path.relpath(full_path, session_root)
303
- with open(full_path, "rb") as handle:
304
- content = base64.b64encode(handle.read()).decode("ascii")
305
- files.append({"path": rel_path.replace(os.sep, "/"), "data_b64": content})
306
- return files
313
+ return {"files": files, "file_count": 0, "bytes": 0}
314
+ for rel_path in _auth_bundle_paths(provider):
315
+ full_path = os.path.join(session_root, rel_path)
316
+ if not os.path.isfile(full_path):
317
+ continue
318
+ with open(full_path, "rb") as handle:
319
+ raw_content = handle.read()
320
+ total_bytes += len(raw_content)
321
+ content = base64.b64encode(raw_content).decode("ascii")
322
+ files.append({"path": rel_path.replace(os.sep, "/"), "data_b64": content})
323
+ if progress_callback:
324
+ progress_callback({
325
+ "event": "profile_progress",
326
+ "session_name": session_name,
327
+ "file_count": len(files),
328
+ "bytes": total_bytes,
329
+ })
330
+ return {"files": files, "file_count": len(files), "bytes": total_bytes}
307
331
 
308
332
  def _resolve_session_subset(session_names):
309
333
  if not session_names:
@@ -326,7 +350,7 @@ def create_session_service(options=None):
326
350
  _ensure_private_dir(os.path.join(base_dir, "profiles"))
327
351
  _ensure_private_dir(session_root)
328
352
  _ensure_private_dir(auth_home)
329
- if normalized_provider == "codex":
353
+ if normalized_provider == PROVIDER_CODEX:
330
354
  _seed_codex_auth_from_global(auth_home, env=env)
331
355
  now = _local_now_iso()
332
356
  session = {
@@ -553,7 +577,7 @@ def create_session_service(options=None):
553
577
  source_root = session.get("authHome") or _get_session_auth_home(
554
578
  session["name"], session["provider"]
555
579
  )
556
- if session["provider"] == "codex" and codex_status_fetcher:
580
+ if session["provider"] == PROVIDER_CODEX and codex_status_fetcher:
557
581
  live_status = codex_status_fetcher({**session, "authHome": source_root})
558
582
  if live_status:
559
583
  record_status(session["name"], live_status)
@@ -561,7 +585,7 @@ def create_session_service(options=None):
561
585
 
562
586
  expected_account_email = (
563
587
  _read_expected_account_email(source_root)
564
- if session["provider"] == "codex"
588
+ if session["provider"] == PROVIDER_CODEX
565
589
  else None
566
590
  )
567
591
  artifact = find_latest_status_artifact(
@@ -570,7 +594,7 @@ def create_session_service(options=None):
570
594
  expected_account_email=expected_account_email,
571
595
  )
572
596
  if (
573
- session["provider"] == "codex"
597
+ session["provider"] == PROVIDER_CODEX
574
598
  and not artifact
575
599
  and os.path.abspath(base_dir) == os.path.abspath(get_cdx_home(env))
576
600
  ):
@@ -620,11 +644,28 @@ def create_session_service(options=None):
620
644
  raise CdxError(f"Unknown session: {name}")
621
645
  return updated
622
646
 
623
- def get_status_rows():
647
+ def get_status_rows(progress_callback=None):
624
648
  sessions = list_sessions()
649
+ if progress_callback:
650
+ progress_callback({
651
+ "event": "status_started",
652
+ "session_count": len(sessions),
653
+ })
625
654
  resolved = []
626
655
  for s in sessions:
656
+ if progress_callback:
657
+ progress_callback({
658
+ "event": "session_started",
659
+ "session_name": s["name"],
660
+ "provider": s["provider"],
661
+ })
627
662
  status = _resolve_session_status(s)
663
+ if progress_callback:
664
+ progress_callback({
665
+ "event": "session_finished",
666
+ "session_name": s["name"],
667
+ "has_status": bool(status),
668
+ })
628
669
  resolved.append({
629
670
  **s,
630
671
  "lastStatus": status,
@@ -659,6 +700,12 @@ def create_session_service(options=None):
659
700
  "reset_week_at": status.get("reset_week_at") if status else None,
660
701
  "reset_at": status.get("reset_at") if status else None,
661
702
  "updated_at": _to_local_iso(s.get("lastStatusAt")),
703
+ "last_launched_at": _to_local_iso(s.get("lastLaunchedAt")),
704
+ })
705
+ if progress_callback:
706
+ progress_callback({
707
+ "event": "status_finished",
708
+ "row_count": len(rows),
662
709
  })
663
710
  return rows
664
711
 
@@ -688,7 +735,7 @@ def create_session_service(options=None):
688
735
  def get_session_root(name):
689
736
  return _get_session_root(name)
690
737
 
691
- def export_bundle(file_path, include_auth=False, session_names=None, passphrase=None, force=False):
738
+ def export_bundle(file_path, include_auth=False, session_names=None, passphrase=None, force=False, progress_callback=None):
692
739
  if not file_path:
693
740
  raise CdxError("Export path is required.")
694
741
  if os.path.exists(file_path) and not force:
@@ -703,16 +750,36 @@ def create_session_service(options=None):
703
750
  "states": {},
704
751
  "profiles": {},
705
752
  }
753
+ profile_file_count = 0
754
+ profile_bytes = 0
755
+ if progress_callback:
756
+ progress_callback({
757
+ "event": "export_started",
758
+ "include_auth": bool(include_auth),
759
+ "session_count": len(sessions),
760
+ "session_names": [session["name"] for session in sessions],
761
+ })
706
762
  for session in sessions:
763
+ if progress_callback:
764
+ progress_callback({"event": "session_started", "session_name": session["name"]})
707
765
  payload["sessions"].append(_build_export_session_record(session))
708
766
  state = store["read_session_state"](session["name"])
709
767
  if state is not None:
710
768
  payload["states"][session["name"]] = state
711
769
  if include_auth:
712
770
  session_root = session.get("sessionRoot") or _get_session_root(session["name"])
713
- payload["profiles"][session["name"]] = _collect_profile_files(session_root)
714
-
771
+ profile = _collect_auth_files(session_root, session["provider"], session["name"], progress_callback)
772
+ payload["profiles"][session["name"]] = profile["files"]
773
+ profile_file_count += profile["file_count"]
774
+ profile_bytes += profile["bytes"]
775
+ if progress_callback:
776
+ progress_callback({"event": "session_finished", "session_name": session["name"]})
777
+
778
+ if progress_callback:
779
+ progress_callback({"event": "encoding_started"})
715
780
  bundle_bytes = encode_bundle(payload, include_auth=include_auth, passphrase=passphrase)
781
+ if progress_callback:
782
+ progress_callback({"event": "writing_started", "path": file_path, "bundle_size_bytes": len(bundle_bytes)})
716
783
  _ensure_private_dir(os.path.dirname(os.path.abspath(file_path)) or ".")
717
784
  with open(file_path, "wb") as handle:
718
785
  handle.write(bundle_bytes)
@@ -725,6 +792,10 @@ def create_session_service(options=None):
725
792
  "path": file_path,
726
793
  "include_auth": include_auth,
727
794
  "session_names": [session["name"] for session in sessions],
795
+ "session_count": len(sessions),
796
+ "profile_file_count": profile_file_count if include_auth else None,
797
+ "profile_bytes": profile_bytes if include_auth else None,
798
+ "bundle_size_bytes": len(bundle_bytes),
728
799
  }
729
800
 
730
801
  def import_bundle(file_path, passphrase=None, session_names=None, force=False):
@@ -3,6 +3,7 @@ import os
3
3
  import re
4
4
  from datetime import datetime, timezone
5
5
 
6
+ from .config import PROVIDER_CLAUDE, PROVIDER_CODEX
6
7
 
7
8
  _ANSI_ESCAPE = re.compile(r"\x1b\[[0-9;]*m")
8
9
  _ANSI_TERMINAL_CONTROL = re.compile(r"\x1b\[[0-9;?]*[ -/]*[@-~]")
@@ -14,6 +15,23 @@ MONTH_ABBR = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
14
15
  MAX_STATUS_READ_BYTES = 512 * 1024
15
16
  MAX_STATUS_CANDIDATE_FILES = 64
16
17
 
18
+ _KEY_VALUE_PATTERNS = [
19
+ ("usage_pct", re.compile(r"usage_pct\s*[:=]\s*(\d{1,3})%?", re.I)),
20
+ ("remaining_5h_pct", re.compile(r"remaining_?5h_pct\s*[:=]\s*(\d{1,3})%?", re.I)),
21
+ ("remaining_week_pct", re.compile(r"remaining_?week_pct\s*[:=]\s*(\d{1,3})%?", re.I)),
22
+ ("credits", re.compile(r"credits?\s*[:=]\s*([\d, ]*\d[\d, ]*)\s*(?:credits?)?", re.I)),
23
+ ("remaining_5h_pct", re.compile(r"5h\s+limit\s*:\s*\[[^\]]*\]\s*(\d{1,3})%\s*left", re.I)),
24
+ ("remaining_week_pct", re.compile(r"weekly\s+limit\s*:\s*\[[^\]]*\]\s*(\d{1,3})%\s*left", re.I)),
25
+ ("remaining_5h_pct", re.compile(r"5h\s+limit\s*:\s*\[[^\]]*\]\s*(\d{1,3})(?:%|\b)", re.I)),
26
+ ("remaining_week_pct", re.compile(r"weekly\s+limit\s*:\s*\[[^\]]*\]\s*(\d{1,3})(?:%|\b)", re.I)),
27
+ ("usage_pct", re.compile(r"usage\s*[:=]\s*(\d{1,3})%", re.I)),
28
+ ("usage_pct", re.compile(r"current\s*[:=]\s*(\d{1,3})%", re.I)),
29
+ ("remaining_5h_pct", re.compile(r"5h(?:\s+remaining)?\s*[:=]\s*(\d{1,3})%", re.I)),
30
+ ("remaining_5h_pct", re.compile(r"remaining\s+5h\s*[:=]\s*(\d{1,3})%", re.I)),
31
+ ("remaining_week_pct", re.compile(r"week(?:\s+remaining)?\s*[:=]\s*(\d{1,3})%", re.I)),
32
+ ("remaining_week_pct", re.compile(r"remaining\s+week\s*[:=]\s*(\d{1,3})%", re.I)),
33
+ ]
34
+
17
35
 
18
36
  def _strip_ansi(text):
19
37
  return _ANSI_ESCAPE.sub("", str(text or ""))
@@ -185,7 +203,7 @@ def _extract_status_blocks_from_text(text, provider=None, source_ref=None, times
185
203
  index = max(index + 1, end_index)
186
204
  return blocks
187
205
 
188
- if provider != "codex":
206
+ if provider != PROVIDER_CODEX:
189
207
  for block in collect_blocks(
190
208
  re.compile(r"^\s*(?:[│|]\s*)?Current session\b", re.I),
191
209
  [re.compile(p, re.I) for p in [
@@ -195,7 +213,7 @@ def _extract_status_blocks_from_text(text, provider=None, source_ref=None, times
195
213
  ):
196
214
  items.append({"source_ref": source_ref, "timestamp": timestamp, "text": block})
197
215
 
198
- if provider != "claude":
216
+ if provider != PROVIDER_CLAUDE:
199
217
  for block in collect_blocks(
200
218
  re.compile(r"^\s*(?:[│|]\s*)?5h\s+limit\b", re.I),
201
219
  [re.compile(p, re.I) for p in [
@@ -412,23 +430,7 @@ def extract_named_statuses_from_text(text):
412
430
  lines = [l.strip() for l in normalized.split("\n") if l.strip()]
413
431
  result = {}
414
432
 
415
- key_value_patterns = [
416
- ("usage_pct", re.compile(r"usage_pct\s*[:=]\s*(\d{1,3})%?", re.I)),
417
- ("remaining_5h_pct", re.compile(r"remaining_?5h_pct\s*[:=]\s*(\d{1,3})%?", re.I)),
418
- ("remaining_week_pct", re.compile(r"remaining_?week_pct\s*[:=]\s*(\d{1,3})%?", re.I)),
419
- ("credits", re.compile(r"credits?\s*[:=]\s*([\d, ]*\d[\d, ]*)\s*(?:credits?)?", re.I)),
420
- ("remaining_5h_pct", re.compile(r"5h\s+limit\s*:\s*\[[^\]]*\]\s*(\d{1,3})%\s*left", re.I)),
421
- ("remaining_week_pct", re.compile(r"weekly\s+limit\s*:\s*\[[^\]]*\]\s*(\d{1,3})%\s*left", re.I)),
422
- ("remaining_5h_pct", re.compile(r"5h\s+limit\s*:\s*\[[^\]]*\]\s*(\d{1,3})(?:%|\b)", re.I)),
423
- ("remaining_week_pct", re.compile(r"weekly\s+limit\s*:\s*\[[^\]]*\]\s*(\d{1,3})(?:%|\b)", re.I)),
424
- ("usage_pct", re.compile(r"usage\s*[:=]\s*(\d{1,3})%", re.I)),
425
- ("usage_pct", re.compile(r"current\s*[:=]\s*(\d{1,3})%", re.I)),
426
- ("remaining_5h_pct", re.compile(r"5h(?:\s+remaining)?\s*[:=]\s*(\d{1,3})%", re.I)),
427
- ("remaining_5h_pct", re.compile(r"remaining\s+5h\s*[:=]\s*(\d{1,3})%", re.I)),
428
- ("remaining_week_pct", re.compile(r"week(?:\s+remaining)?\s*[:=]\s*(\d{1,3})%", re.I)),
429
- ("remaining_week_pct", re.compile(r"remaining\s+week\s*[:=]\s*(\d{1,3})%", re.I)),
430
- ]
431
- for field, pattern in key_value_patterns:
433
+ for field, pattern in _KEY_VALUE_PATTERNS:
432
434
  if field not in result:
433
435
  m = pattern.search(normalized)
434
436
  if m:
@@ -620,7 +622,7 @@ def find_latest_status_artifact(root_dir, provider=None, expected_account_email=
620
622
 
621
623
  best = None
622
624
  for candidate in records:
623
- if provider == "codex" and not _account_matches_expected(
625
+ if provider == PROVIDER_CODEX and not _account_matches_expected(
624
626
  candidate["text"], expected_account_email
625
627
  ):
626
628
  continue
@@ -9,6 +9,9 @@ from .cli_render import (
9
9
  _style_pct,
10
10
  )
11
11
 
12
+ RESET_COUNTDOWN_SAFETY_SECONDS = 60
13
+ PRIORITY_EMPTY_AVAILABLE_THRESHOLD = 5
14
+
12
15
 
13
16
  def _format_reset_time(value):
14
17
  if not value:
@@ -27,6 +30,7 @@ def _format_reset_time(value):
27
30
  if hours_ago < 24:
28
31
  return f"passed {hours_ago}h ago"
29
32
  return value
33
+ delta_s = delta_s + RESET_COUNTDOWN_SAFETY_SECONDS
30
34
  if delta_s < 60:
31
35
  return "now"
32
36
  if delta_s < 24 * 60 * 60:
@@ -104,14 +108,28 @@ def _format_status_rows(rows, use_color=False, small=False):
104
108
  if len(priority) > 1 else "."
105
109
  )
106
110
  ) if priority else "Priority: no usable session status yet."
111
+ current_line = _format_current_session_line(rows)
107
112
  return "\n".join([
108
113
  _pad_table([headers] + table_rows),
109
114
  "",
110
115
  _style(priority_line, "1", use_color),
111
- _style("Tip: Codex status uses the local app-server rate-limit API when available; Claude sessions auto-refresh, use --refresh to force.", "2", use_color),
116
+ _style(current_line, "2", use_color),
112
117
  ])
113
118
 
114
119
 
120
+ def _format_current_session_line(rows):
121
+ launched = []
122
+ for row in rows:
123
+ timestamp = _parse_reset_timestamp(row.get("last_launched_at"))
124
+ if timestamp is not None:
125
+ launched.append((timestamp, row))
126
+ if not launched:
127
+ return "Current: no launched session known yet."
128
+ timestamp, row = max(launched, key=lambda item: (item[0], item[1].get("session_name") or ""))
129
+ label = _format_relative_age(row.get("last_launched_at"))
130
+ return f"Current: last launched {row['session_name']} ({label})."
131
+
132
+
115
133
  def _recommend_priority_sessions(rows):
116
134
  if not rows:
117
135
  return []
@@ -120,7 +138,7 @@ def _recommend_priority_sessions(rows):
120
138
  has_credits = row.get("credits") is not None
121
139
  credit_rank = 0 if has_credits else 1
122
140
  available = row.get("available_pct")
123
- usable_now = available is not None and available > 0
141
+ usable_now = available is not None and available > PRIORITY_EMPTY_AVAILABLE_THRESHOLD
124
142
  known_available = available is not None
125
143
  reset_timestamp = _priority_reset_timestamp(row)
126
144
  reset_is_future = reset_timestamp is not None and reset_timestamp >= _now_timestamp()
@@ -165,7 +183,7 @@ def _priority_instruction(row, position):
165
183
 
166
184
  def _priority_needs_refresh(row):
167
185
  available = row.get("available_pct")
168
- if available is None or available > 0:
186
+ if available is None or available > PRIORITY_EMPTY_AVAILABLE_THRESHOLD:
169
187
  return False
170
188
  _label, is_past = _priority_reset_info(row)
171
189
  return is_past
@@ -175,7 +193,7 @@ def _priority_reason(row):
175
193
  available = row.get("available_pct")
176
194
  if available is None:
177
195
  return "status unknown"
178
- if available > 0:
196
+ if available > PRIORITY_EMPTY_AVAILABLE_THRESHOLD:
179
197
  return f"{_format_pct(available)} OK"
180
198
  label, is_past = _priority_reset_info(row)
181
199
  if label: