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.
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/PKG-INFO +1 -1
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/integrations/opencode-tui-plugin/src/tui.tsx +7 -5
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/pyproject.toml +1 -1
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/__init__.py +1 -1
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/cli.py +5 -1
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_cli.py +30 -3
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/.gitignore +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/LICENSE +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/README.md +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/docs/architecture.md +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/docs/opencode-auth.md +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/docs/provider-research-prompt.md +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/docs/provider-support.md +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/docs/releasing.md +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/docs/roadmap.md +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/docs/security.md +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/docs/testing.md +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/integrations/opencode-tui-plugin/LICENSE +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/integrations/opencode-tui-plugin/README.md +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/__main__.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/atomic.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/backup.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/exceptions.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/locking.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/macos_keychain.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/models.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/oauth_jwt.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/oauth_refresh.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/opencode_auth.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/paths.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/process_detection.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/providers/__init__.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/providers/api.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/providers/base.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/providers/common.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/providers/github_copilot.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/providers/openai.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/providers/poe.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/providers/xai.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/providers/zai.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/sealed.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/store.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/switcher.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/transfer.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/src/opencode_swap/usage.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/__init__.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/helpers.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_atomic.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_backup.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_concurrency.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_locking.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_macos_keychain.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_main.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_models.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_multi_provider.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_oauth_jwt.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_oauth_refresh.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_opencode_auth.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_paths.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_providers_multi.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_providers_openai.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_registry.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_store.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_switcher.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_switcher_restore.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_switcher_transfer.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_switcher_use.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_transfer.py +0 -0
- {opencode_swap-0.4.2 → opencode_swap-0.4.3}/tests/test_usage.py +0 -0
|
@@ -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
|
-
//
|
|
208
|
-
//
|
|
209
|
-
//
|
|
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
|
|
212
|
-
if (
|
|
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
|
}
|
|
@@ -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
|
-
|
|
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
|
|
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
|
|
353
|
-
assert "
|
|
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
|
|
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
|
|
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
|
|
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
|