Skip to content

feat(proxy): tools/grant.js admin CLI for entitlements (PR 3/5 — premium tier)#50

Draft
DocNR wants to merge 1 commit into
feat/entitlement-endpointfrom
feat/grant-cli
Draft

feat(proxy): tools/grant.js admin CLI for entitlements (PR 3/5 — premium tier)#50
DocNR wants to merge 1 commit into
feat/entitlement-endpointfrom
feat/grant-cli

Conversation

@DocNR
Copy link
Copy Markdown
Owner

@DocNR DocNR commented May 10, 2026

Summary

PR 3 of the premium-tier series. Phase 1 manual-grant CLI — lets you flip premium on for yourself + testers via SSH before Phase 2 Lightning acquisition lands. Same entitlements.json data file that the /entitlement endpoint reads in #49.

Stacks on #49 (which stacks on #48). Base branch is feat/entitlement-endpoint. Merge order: #48#49 → this.

Subcommands

grant <npub|hex> [--note "..."] [--by "..."] [--expires-at <unix>]
revoke <npub|hex>
list [--tier free|premium]
audit [--threshold N] [--days D]                              # default 5 / 30d
bootstrap-existing [--clients-file <path>] [--threshold N]   # default 3
help

Global flag: --file <path> overrides the entitlements.json path (default ./entitlements.json).

Highlights

  • Inline NIP-19 bech32 decoder (npub → hex). No new dependencies. Bech32 checksum + HRP verification rejects nsec1, malformed inputs, corrupted checksums.
  • Pass-through for hex pubkeys, case-insensitive.
  • bootstrap-existing reads clients.json + auto-grants premium to any signer with ≥N paired clients. Idempotent (skips already-premium). Useful one-shot at deploy to give heavy testers (yourself + Brian etc.) headroom on the tighter premium tier without manual per-pubkey grants.

How to use on Dell

The CLI runs from the source clone — no need to deploy it alongside the proxy:

ssh hypoxic_drive@dell
sudo -u clave-proxy node ~/clave-proxy-test/relay-proxy/tools/grant.js \
     --file=/opt/clave-proxy-test/entitlements.json \
     grant npub125f8lj0pcq7xk3v68w4h9ldenhh3v3x97gumm5yl8e0mgq0dnvssjptd2l \
     --note "self-grant for premium-tier device tests"

Then verify:

sudo -u clave-proxy node ~/clave-proxy-test/relay-proxy/tools/grant.js \
     --file=/opt/clave-proxy-test/entitlements.json list

Tests

  • 15 new unit tests covering:
    • npub round-trip vs known-good hex (POWR test account)
    • hex pass-through + uppercase normalization
    • Bech32 rejection paths (garbage, wrong length, wrong HRP, corrupted checksum)
    • parseArgs covering positional, --flag value, --flag=value, boolean flags, mixed
  • Full proxy suite green: 126/126 (79 base + 32 storage + 15 grant CLI)

Local end-to-end smoke (grant→list→revoke cycle on /tmp file)

$ node tools/grant.js --file=/tmp/test.json grant npub125f...
granted premium to 55127fc9...9b21
  granted_at: 2026-05-10T01:13:15.000Z
  granted_by: admin:cli
  note:       smoke test
  expires_at: never

$ node tools/grant.js --file=/tmp/test.json list
1 entitlement entry:
  55127fc9...9b21  premium  effective=premium  granted=... expires=lifetime  devices=0  note="smoke test"

$ node tools/grant.js --file=/tmp/test.json revoke 55127fc9...
revoked premium from 55127fc9...9b21
  was-note: smoke test
  was-granted-by: admin:cli

Files changed

  • relay-proxy/tools/grant.js (new, 359 lines)
  • relay-proxy/test/grant.test.js (new, 15 tests, 118 lines)

Risk

Low. Tool only — no proxy or production behavior change. Changes the on-disk state of entitlements.json when the operator runs it; that file isn't read by anything except this CLI + the /entitlement endpoint (#49) + /pair-client cap check (#49), both already shipped.

After merge

  1. Pull on Dell test-proxy clone, run bootstrap-existing against /opt/clave-proxy-test/clients.json to grant premium to existing heavy testers.
  2. Self-grant for device tests in PR 4/5.
  3. Eventually deploy to prod proxy (after PR 4/5 land + iOS exercises the full path).

🤖 Generated with Claude Code

PR 3 of the premium-tier series. Phase 1 manual-grant tool — lets the
developer flip premium on for themselves + testers via SSH+CLI before
Lightning acquisition lands in Phase 2. Same data file (entitlements.json)
that the /entitlement endpoint reads.

Subcommands:
  grant <npub|hex> [--note "..."] [--by "..."] [--expires-at <unix>]
  revoke <npub|hex>
  list [--tier free|premium]
  audit [--threshold N] [--days D]            (default 5 / 30d)
  bootstrap-existing [--clients-file <path>] [--threshold N]   (default 3)
  help

Globals:
  --file <path>   Override entitlements.json (default ./entitlements.json)

Highlights:
- Inline NIP-19 bech32 decoder (npub → hex). No new deps.
- Pass-through for hex pubkeys; case-insensitive.
- Bech32 checksum + HRP verification — rejects nsec1, malformed inputs,
  corrupted checksums.
- bootstrap-existing reads clients.json + auto-grants premium to any
  signer with ≥N paired clients. Idempotent (skips already-premium).
  Useful one-shot at deploy to give heavy testers headroom on the
  tighter premium tier without manual per-pubkey grants.

Run on Dell:
  sudo -u clave-proxy node ~/clave-proxy-test/relay-proxy/tools/grant.js \\
       --file=/opt/clave-proxy-test/entitlements.json grant npub1...

Tests: 15 new (bech32 round-trip + parseArgs covering all flag forms +
checksum/HRP rejection paths). Full proxy suite green at 126/126.

Local smoke test (grant→list→revoke cycle on /tmp file):
  $ node tools/grant.js --file=/tmp/test.json grant npub125f...
    granted premium to 55127fc9...9b21
      granted_at: 2026-05-10T01:13:15.000Z
      granted_by: admin:cli
      note:       smoke test
      expires_at: never
  $ node tools/grant.js --file=/tmp/test.json list
    1 entitlement entry:
      55127fc9... premium effective=premium granted=... expires=lifetime devices=0
  $ node tools/grant.js --file=/tmp/test.json revoke 55127fc9...
    revoked premium from 55127fc9...9b21

Stacks on PR #49 (which stacks on PR #48). Will rebase if review changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@DocNR DocNR marked this pull request as draft May 10, 2026 04:15
@DocNR DocNR added the queued-phase-2 Premium-tier work paused; revisit when payment architecture is chosen label May 10, 2026
@DocNR
Copy link
Copy Markdown
Owner Author

DocNR commented May 10, 2026

Paused — queued for Phase 2.

This is part of a server-side entitlement series (#48#49#50, plus an uncommitted iOS service layer on feat/ios-entitlement-service). Review determined the stack is premature for current needs: the immediate goal (let developer + testers exceed default 4-account / 5-client caps) can be met with iOS-only allowlist gating (~150 LOC, no server changes), now being built separately on a new branch.

Preserved as draft because:

  1. Well-tested + harmless — pure additive, no production behavior change. Full proxy suite green at 126/126.
  2. ~80% of the work for the eventual Lightning-native paid tier if that's the chosen direction.
  3. Can be closed without prejudice if the project chooses Apple IAP instead — device-token-bound IAP + "Restore Purchases" makes server-side npub-keyed state unnecessary.

Architecture decision (Apple IAP vs Lightning device-bound vs Lightning npub-bound) deferred until there's a real monetization timeline. No action needed here. Will revisit when payment work begins.

Plan: ~/.claude/plans/i-d-like-to-create-eventual-moon.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

queued-phase-2 Premium-tier work paused; revisit when payment architecture is chosen

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant