opencode-swap 0.4.2__tar.gz → 0.4.3__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.
Files changed (69) hide show
  1. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/PKG-INFO +1 -1
  2. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/integrations/opencode-tui-plugin/src/tui.tsx +7 -5
  3. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/pyproject.toml +1 -1
  4. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/__init__.py +1 -1
  5. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/cli.py +5 -1
  6. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_cli.py +30 -3
  7. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/.gitignore +0 -0
  8. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/LICENSE +0 -0
  9. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/README.md +0 -0
  10. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/docs/architecture.md +0 -0
  11. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/docs/opencode-auth.md +0 -0
  12. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/docs/provider-research-prompt.md +0 -0
  13. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/docs/provider-support.md +0 -0
  14. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/docs/releasing.md +0 -0
  15. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/docs/roadmap.md +0 -0
  16. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/docs/security.md +0 -0
  17. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/docs/testing.md +0 -0
  18. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/integrations/opencode-tui-plugin/LICENSE +0 -0
  19. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/integrations/opencode-tui-plugin/README.md +0 -0
  20. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/__main__.py +0 -0
  21. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/atomic.py +0 -0
  22. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/backup.py +0 -0
  23. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/exceptions.py +0 -0
  24. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/locking.py +0 -0
  25. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/macos_keychain.py +0 -0
  26. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/models.py +0 -0
  27. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/oauth_jwt.py +0 -0
  28. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/oauth_refresh.py +0 -0
  29. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/opencode_auth.py +0 -0
  30. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/paths.py +0 -0
  31. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/process_detection.py +0 -0
  32. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/providers/__init__.py +0 -0
  33. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/providers/api.py +0 -0
  34. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/providers/base.py +0 -0
  35. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/providers/common.py +0 -0
  36. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/providers/github_copilot.py +0 -0
  37. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/providers/openai.py +0 -0
  38. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/providers/poe.py +0 -0
  39. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/providers/xai.py +0 -0
  40. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/providers/zai.py +0 -0
  41. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/sealed.py +0 -0
  42. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/store.py +0 -0
  43. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/switcher.py +0 -0
  44. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/transfer.py +0 -0
  45. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/usage.py +0 -0
  46. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/__init__.py +0 -0
  47. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/helpers.py +0 -0
  48. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_atomic.py +0 -0
  49. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_backup.py +0 -0
  50. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_concurrency.py +0 -0
  51. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_locking.py +0 -0
  52. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_macos_keychain.py +0 -0
  53. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_main.py +0 -0
  54. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_models.py +0 -0
  55. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_multi_provider.py +0 -0
  56. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_oauth_jwt.py +0 -0
  57. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_oauth_refresh.py +0 -0
  58. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_opencode_auth.py +0 -0
  59. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_paths.py +0 -0
  60. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_providers_multi.py +0 -0
  61. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_providers_openai.py +0 -0
  62. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_registry.py +0 -0
  63. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_store.py +0 -0
  64. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_switcher.py +0 -0
  65. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_switcher_restore.py +0 -0
  66. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_switcher_transfer.py +0 -0
  67. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_switcher_use.py +0 -0
  68. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_transfer.py +0 -0
  69. {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_usage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: opencode-swap
3
- Version: 0.4.2
3
+ Version: 0.4.3
4
4
  Summary: Multi-account switcher for OpenCode
5
5
  Project-URL: Homepage, https://github.com/leinardi/opencode-swap
6
6
  Project-URL: Repository, https://github.com/leinardi/opencode-swap
@@ -204,12 +204,14 @@ function usageWindowDetail(window: UsageWindow): UsageDetail | undefined {
204
204
  }
205
205
  const date = new Date(resetAt);
206
206
  if (Number.isNaN(date.getTime())) return { label, percent, band };
207
- // Reset under 24h *in the future* renders as bare "HH:MM", matching
208
- // cli.py's _format_reset; a past or non-finite delta always gets the full
209
- // date so a stale/expired reset never reads as "today".
207
+ // A reset on today's local date renders as bare "HH:MM", matching cli.py's
208
+ // _format_reset; anything on another date keeps the full date, so tomorrow
209
+ // morning (under 24h away, but not today) never reads as "today".
210
210
  const hhmm = `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
211
- const deltaMs = resetAt - Date.now();
212
- if (deltaMs >= 0 && deltaMs < 86_400_000) return { label, percent, band, reset: hhmm };
211
+ const now = new Date();
212
+ if (date.getFullYear() === now.getFullYear() && date.getMonth() === now.getMonth() && date.getDate() === now.getDate()) {
213
+ return { label, percent, band, reset: hhmm };
214
+ }
213
215
  const month = date.toLocaleString(undefined, { month: "short" });
214
216
  return { label, percent, band, reset: `${month} ${date.getDate()}, ${hhmm}` };
215
217
  }
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "opencode-swap"
3
- version = "0.4.2"
3
+ version = "0.4.3"
4
4
  description = "Multi-account switcher for OpenCode"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -1,3 +1,3 @@
1
1
  """opencode-swap: multi-account switcher for OpenCode."""
2
2
 
3
- __version__ = "0.4.2"
3
+ __version__ = "0.4.3"
@@ -241,9 +241,13 @@ def _format_reset(reset_at: object) -> str | None:
241
241
  # reset time is shown in the user's local clock, same as time.time()
242
242
  # below it, so the timestamp must convert to local, not stay UTC.
243
243
  reset_time = datetime.fromtimestamp(reset_at / 1000, tz=UTC).astimezone()
244
+ now = datetime.fromtimestamp(time.time(), tz=UTC).astimezone()
244
245
  except (OverflowError, OSError, ValueError):
245
246
  return None
246
- if 0 <= reset_at - time.time() * 1000 < 86_400_000:
247
+ # Bare "HH:MM" only for a reset on today's local date: an "under 24h away"
248
+ # test would print a bare time for tomorrow morning too, which reads as
249
+ # today and understates the wait.
250
+ if reset_time.date() == now.date():
247
251
  return f"{reset_time:%H:%M}"
248
252
  return f"{reset_time:%b} {reset_time.day}, {reset_time:%H:%M}"
249
253
 
@@ -30,6 +30,17 @@ def auth_path(tmp_path):
30
30
  return tmp_path / "opencode" / "auth.json"
31
31
 
32
32
 
33
+ @pytest.fixture
34
+ def utc_clock():
35
+ """Pin the process' local timezone to UTC for one test, so assertions on
36
+ locally-rendered reset times do not depend on the machine's timezone."""
37
+ with pytest.MonkeyPatch.context() as mp:
38
+ mp.setenv("TZ", "UTC")
39
+ time.tzset()
40
+ yield
41
+ time.tzset()
42
+
43
+
33
44
  def write_live_account(tmp_path, account_id="acct-1", refresh="r1", extra=None):
34
45
  entry = {
35
46
  "type": "oauth",
@@ -340,7 +351,9 @@ def test_format_usage_shows_both_windows(monkeypatch):
340
351
  assert ", " in output.split(" | ")[1]
341
352
 
342
353
 
343
- def test_format_usage_reset_under_a_day_shows_time_only(monkeypatch):
354
+ def test_format_usage_reset_later_today_shows_time_only(monkeypatch, utc_clock):
355
+ # 1_751_310_000 is 2025-06-30 19:00 UTC; utc_clock pins TZ so the local
356
+ # date the formatter compares against is not the test machine's.
344
357
  monkeypatch.setattr(cli.time, "time", lambda: 1_751_310_000)
345
358
  output = cli._format_usage(
346
359
  UsageSnapshot(
@@ -349,8 +362,22 @@ def test_format_usage_reset_under_a_day_shows_time_only(monkeypatch):
349
362
  )
350
363
  )
351
364
 
352
- # Reset under 24h away renders as bare "HH:MM", not a full date.
353
- assert "," not in output
365
+ # Reset on today's local date renders as bare "HH:MM", not a full date.
366
+ assert output == " usage: 5h 49% @20:00"
367
+
368
+
369
+ def test_format_usage_reset_tomorrow_shows_date(monkeypatch, utc_clock):
370
+ monkeypatch.setattr(cli.time, "time", lambda: 1_751_310_000)
371
+ output = cli._format_usage(
372
+ UsageSnapshot(
373
+ available=True,
374
+ # 6h ahead: under 24h away, but on the next local date, so the
375
+ # date has to be shown or "@01:00" reads as earlier today.
376
+ windows=(UsageWindow(used_percent=41, reset_at=(1_751_310_000 + 6 * 3600) * 1000, window_seconds=7 * 86_400),),
377
+ )
378
+ )
379
+
380
+ assert output == " usage: 7d 41% @Jul 1, 01:00"
354
381
 
355
382
 
356
383
  @pytest.mark.parametrize(
File without changes
File without changes
File without changes