cctally 1.2.0 → 1.3.0
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.
- package/CHANGELOG.md +5 -0
- package/README.md +1 -1
- package/bin/cctally +73 -68
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,11 @@ based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [1.3.0] - 2026-05-08
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- `release` Phase 5 now publishes to npm via a GitHub Actions OIDC trusted-publisher workflow in the public repo, with `npm publish --provenance` for supply-chain attestation. The release script no longer invokes `npm publish` locally — it polls `npm view` until the workflow lands the version. Eliminates the prior failure mode where passkey-based npm 2FA would block `npm publish` from a non-interactive subprocess.
|
|
12
|
+
|
|
8
13
|
## [1.2.0] - 2026-05-08
|
|
9
14
|
|
|
10
15
|
### Added
|
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ npm install -g cctally
|
|
|
33
33
|
cctally setup
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
Needs Python 3. If `cctally setup` fails with "python3 not found", install it with `brew install python` (macOS) and try again.
|
|
37
37
|
|
|
38
38
|
### From source
|
|
39
39
|
|
package/bin/cctally
CHANGED
|
@@ -1446,79 +1446,82 @@ def _release_run_phase_gh(version: str, body: str) -> int:
|
|
|
1446
1446
|
return 0
|
|
1447
1447
|
|
|
1448
1448
|
|
|
1449
|
+
_RELEASE_NPM_POLL_TIMEOUT_S_DEFAULT = 300.0
|
|
1450
|
+
_RELEASE_NPM_POLL_INTERVAL_S_DEFAULT = 10.0
|
|
1451
|
+
|
|
1452
|
+
|
|
1453
|
+
def _release_npm_poll_timing() -> tuple[float, float]:
|
|
1454
|
+
"""Return (timeout_s, interval_s) honoring env-hook overrides.
|
|
1455
|
+
|
|
1456
|
+
Hidden env hooks (mirrors ``CCTALLY_RELEASE_DATE_UTC``):
|
|
1457
|
+
- CCTALLY_RELEASE_NPM_POLL_TIMEOUT_S
|
|
1458
|
+
- CCTALLY_RELEASE_NPM_POLL_INTERVAL_S
|
|
1459
|
+
Used by the harness (and pytest) to make Phase 5 fixtures deterministic.
|
|
1460
|
+
Not in --help.
|
|
1461
|
+
"""
|
|
1462
|
+
def _f(name: str, default: float) -> float:
|
|
1463
|
+
try:
|
|
1464
|
+
return float(os.environ[name])
|
|
1465
|
+
except (KeyError, ValueError):
|
|
1466
|
+
return default
|
|
1467
|
+
return (
|
|
1468
|
+
_f("CCTALLY_RELEASE_NPM_POLL_TIMEOUT_S", _RELEASE_NPM_POLL_TIMEOUT_S_DEFAULT),
|
|
1469
|
+
_f("CCTALLY_RELEASE_NPM_POLL_INTERVAL_S", _RELEASE_NPM_POLL_INTERVAL_S_DEFAULT),
|
|
1470
|
+
)
|
|
1471
|
+
|
|
1472
|
+
|
|
1449
1473
|
def _release_run_phase_npm(
|
|
1450
1474
|
version: str,
|
|
1451
1475
|
public_clone: pathlib.Path,
|
|
1452
1476
|
*,
|
|
1453
1477
|
dist_tag: str,
|
|
1454
1478
|
) -> int:
|
|
1455
|
-
"""Phase 5 —
|
|
1479
|
+
"""Phase 5 — wait for the public-repo GHA workflow to publish ``cctally@<v>``.
|
|
1456
1480
|
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
``
|
|
1481
|
+
Phase 3 pushes ``v<version>`` to ``omrikais/cctally``; the workflow at
|
|
1482
|
+
``.github/workflows/release-npm.yml`` fires on tag-push and runs
|
|
1483
|
+
``npm publish --provenance`` via OIDC trusted publisher (no NPM_TOKEN,
|
|
1484
|
+
no operator 2FA round-trip — fixes the passkey-in-subprocess failure
|
|
1485
|
+
mode where npm 2FA blocks ``npm publish`` from a non-interactive
|
|
1486
|
+
subprocess).
|
|
1460
1487
|
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
perspective; npm is the third channel and treated as polish.
|
|
1488
|
+
Phase 5 here is observation-only: poll ``npm view cctally@<v>`` until
|
|
1489
|
+
it appears, with timeout. ``cctally`` never invokes ``npm publish``
|
|
1490
|
+
locally anymore — Trusted Publisher binds the right to publish to the
|
|
1491
|
+
public-repo workflow, not to the operator's `npm login` token.
|
|
1466
1492
|
|
|
1467
|
-
Returns:
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1493
|
+
Returns ``0`` on observed success OR poll-timeout (soft-success: phases
|
|
1494
|
+
1-4 landed; the workflow is either succeeding or visibly failing on
|
|
1495
|
+
github.com; ``--resume`` re-checks the registry).
|
|
1496
|
+
|
|
1497
|
+
Timing overridable via ``CCTALLY_RELEASE_NPM_POLL_TIMEOUT_S`` and
|
|
1498
|
+
``CCTALLY_RELEASE_NPM_POLL_INTERVAL_S`` env vars.
|
|
1472
1499
|
"""
|
|
1473
|
-
print(f"phase 5: npm publish (tag={dist_tag})")
|
|
1500
|
+
print(f"phase 5: await npm publish via GHA (tag={dist_tag})")
|
|
1474
1501
|
if _release_phase_npm_done(version):
|
|
1475
1502
|
print(f" cctally@{version} already on npm — skipping.")
|
|
1476
1503
|
return 0
|
|
1477
1504
|
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
print(
|
|
1499
|
-
f"\n npm not authenticated. After `npm login`, run:\n"
|
|
1500
|
-
f" cd {public_clone} && npm publish --tag {dist_tag}\n",
|
|
1501
|
-
file=sys.stderr,
|
|
1502
|
-
)
|
|
1503
|
-
return 0
|
|
1504
|
-
|
|
1505
|
-
pub = subprocess.run(
|
|
1506
|
-
["npm", "publish", "--tag", dist_tag],
|
|
1507
|
-
cwd=str(public_clone),
|
|
1508
|
-
check=False,
|
|
1509
|
-
)
|
|
1510
|
-
if pub.returncode != 0:
|
|
1511
|
-
return 3
|
|
1512
|
-
|
|
1513
|
-
if not _release_phase_npm_done(version):
|
|
1514
|
-
print(
|
|
1515
|
-
f" warning: `npm publish` returned 0 but `npm view "
|
|
1516
|
-
f"cctally@{version}` doesn't see the version yet. Registry "
|
|
1517
|
-
f"propagation lag is normal; check `npm view cctally version` "
|
|
1518
|
-
f"in 30s.",
|
|
1519
|
-
file=sys.stderr,
|
|
1520
|
-
)
|
|
1521
|
-
return 0
|
|
1505
|
+
timeout_s, interval_s = _release_npm_poll_timing()
|
|
1506
|
+
deadline = time.monotonic() + timeout_s
|
|
1507
|
+
while True:
|
|
1508
|
+
if _release_phase_npm_done(version):
|
|
1509
|
+
print(f" cctally@{version} on npm registry ✓")
|
|
1510
|
+
return 0
|
|
1511
|
+
if time.monotonic() >= deadline:
|
|
1512
|
+
print(
|
|
1513
|
+
f"\n timed out after {timeout_s:.0f}s waiting for "
|
|
1514
|
+
f"cctally@{version} on npm. The GHA workflow may still be "
|
|
1515
|
+
f"running or have failed — check:\n"
|
|
1516
|
+
f" https://github.com/{PUBLIC_REPO}/actions\n"
|
|
1517
|
+
f" Re-run `cctally release --resume` once the workflow "
|
|
1518
|
+
f"completes, or for emergency manual publish:\n"
|
|
1519
|
+
f" cd {public_clone} && npm publish --access public "
|
|
1520
|
+
f"--tag {dist_tag}\n",
|
|
1521
|
+
file=sys.stderr,
|
|
1522
|
+
)
|
|
1523
|
+
return 0
|
|
1524
|
+
time.sleep(interval_s)
|
|
1522
1525
|
|
|
1523
1526
|
|
|
1524
1527
|
def _release_run_phase_brew(
|
|
@@ -1538,8 +1541,8 @@ def _release_run_phase_brew(
|
|
|
1538
1541
|
version (idempotency under ``--resume``).
|
|
1539
1542
|
- Dirty working tree — refuses with exit 2 and points the operator
|
|
1540
1543
|
at ``--resume``.
|
|
1541
|
-
- Push failure — auth-fallback parity with
|
|
1542
|
-
|
|
1544
|
+
- Push failure — auth-fallback parity with Phase 4: prints the
|
|
1545
|
+
exact recovery command and returns 0. Phases 1-5 already
|
|
1543
1546
|
succeeded, the release IS published from the user's
|
|
1544
1547
|
perspective; the brew tap is the third channel and treated as
|
|
1545
1548
|
polish.
|
|
@@ -1645,7 +1648,7 @@ def _release_run_phase_brew(
|
|
|
1645
1648
|
f"refs/tags/v{version}:refs/tags/v{version}\n",
|
|
1646
1649
|
file=sys.stderr,
|
|
1647
1650
|
)
|
|
1648
|
-
return 0 # auth-fallback semantics;
|
|
1651
|
+
return 0 # auth-fallback semantics; parity with Phase 4.
|
|
1649
1652
|
|
|
1650
1653
|
return 0
|
|
1651
1654
|
|
|
@@ -1846,10 +1849,12 @@ def cmd_release(args: argparse.Namespace) -> int:
|
|
|
1846
1849
|
is_prerelease = "-" in next_v
|
|
1847
1850
|
dist_tag = "next" if is_prerelease else "latest"
|
|
1848
1851
|
|
|
1849
|
-
# Phase 5 — npm publish
|
|
1850
|
-
#
|
|
1851
|
-
#
|
|
1852
|
-
#
|
|
1852
|
+
# Phase 5 — await npm publish via the public-repo GHA workflow
|
|
1853
|
+
# (release-npm.yml), which fires on the tag pushed in Phase 3. Phase 5
|
|
1854
|
+
# here is observation-only (poll `npm view` with timeout); poll-timeout
|
|
1855
|
+
# returns 0 — the workflow runs independently on github.com, and
|
|
1856
|
+
# `--resume` re-checks the registry. `--skip-npm` is the operator
|
|
1857
|
+
# escape hatch for ad-hoc cuts.
|
|
1853
1858
|
if args.skip_npm:
|
|
1854
1859
|
print("phase 5: npm skipped (--skip-npm)")
|
|
1855
1860
|
else:
|
|
@@ -1973,8 +1978,8 @@ def _release_dry_run(
|
|
|
1973
1978
|
else:
|
|
1974
1979
|
dist_tag = "next" if "-" in next_v else "latest"
|
|
1975
1980
|
print(
|
|
1976
|
-
f"Would
|
|
1977
|
-
f"
|
|
1981
|
+
f"Would await: cctally@{next_v} on npmjs.org via GHA workflow "
|
|
1982
|
+
f"(release-npm.yml in public repo; tag={dist_tag})"
|
|
1978
1983
|
)
|
|
1979
1984
|
print()
|
|
1980
1985
|
# Phase 6 — brew formula bump plan.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cctally",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Track Claude Code subscription usage as a weekly $-per-1% trend. Local web dashboard, terminal UI, forecasts, and threshold alerts.",
|
|
5
5
|
"homepage": "https://github.com/omrikais/cctally",
|
|
6
6
|
"repository": {
|