Commit Graph

5 Commits

Author SHA1 Message Date
Keysat 29282f8dcc Add 'Set Recap License' StartOS action + s/Keysat license/Recap license/
Two related changes:

1. New StartOS action: 'Set Recap License'

   Symmetric with the existing 'Set Gemini API Key' action — paste a
   LIC1-... key into the StartOS Actions menu and it gets persisted.
   Added because some users prefer the StartOS form for credentials
   over the in-app activation modal.

   Implementation:
     • startos/file-models/config.json.ts: schema gains recap_license_key
     • startos/actions/setLicense.ts: input form (masked, regex-checks
       for the LIC1- prefix), persists via configFile.merge()
     • startos/actions/index.ts: registers the new action
     • server/license.js: readLicenseString() falls back to
       startos-config.json after the legacy license.txt path. Resolution
       order: env → license.txt → startos-config.json
     • server/license-middleware.js: faster license-file poll (30 s,
       env-overridable RECAP_LICENSE_FILE_POLL_MS) re-runs checkLicense
       so action-set keys take effect within seconds, not the 6 h online
       cycle. If the new key parses as 'licensed', kicks an immediate
       online check to confirm.

2. Copy fix: 'Keysat license' → 'Recap license' in user-facing text

   Keysat is the licensing system underneath, but customers buy a
   'Recap license'. Updated:
     • Activation screen subtitle (public/index.html)
     • 402 message in the activation gate (server/license-middleware.js)

   Internal references (PRODUCT_SLUG, KEYSAT_BASE_URL, the issuer.pub
   filename, the 'Issuer: licensing.keysat.xyz' display in the
   activation card) stay as Keysat — those are accurate.

Smoke tested locally: starting the server with no license, then
writing a fake LIC1-... key into startos-config.json, the
license-file poll picks it up within ~2 s and transitions state from
'unlicensed' to 'invalid' (since the fake key fails Ed25519
verification, as expected). With a real key, the same path would land
in 'licensed'.
2026-05-09 07:06:21 -05:00
Keysat 9282440143 Rename project: youtube-summarizer → recap
The product was always more than YouTube — it handles podcast feeds
too, and the upcoming multi-provider work makes it less Gemini-
specific. New name: Recap.

This is a coordinated identity change across:

  • StartOS package id: youtube-summarizer → recap
    (manifest.id; the .s9pk filename, Docker image namespace, and
    install path under StartOS all derive from this automatically)
  • Display name: "YouTube Summarizer" → "Recap"
    (manifest title, activation screen heading, page <title>, console
    log on boot, i18n strings, ABOUT.md, Dockerfile header,
    docker_entrypoint banner)
  • Keysat product slug: youtube-summarizer → recap
    (server/license.js PRODUCT_SLUG; frontend fallback strings)
  • Daemon subscription id: youtube-summarizer-sub → recap-sub
  • Env var prefix: YT_SUMMARIZER_* → RECAP_*
    (LICENSE_KEY, LICENSE_KEY_PATH, MAX_OFFLINE_DAYS,
    VALIDATE_INTERVAL_MS)
  • localStorage keys: yt-summarizer-* → recap-*
    (gemini-key, activation-skipped, clips)
  • Library export filename: youtube-summarizer-library.json →
    recap-library.json
  • npm package names: youtube-summarizer-{startos,server} → recap-*
  • Deploy paths: youtube-summarizer_x86_64.s9pk → recap_x86_64.s9pk
    (default values in bin/deploy.sh; .deploy.env on dev machine
    needs the same update before next push)
  • Self-hosted registry directory: startos-registry/packages/
    youtube-summarizer → .../recap (with package.json + INSTRUCTIONS
    rewritten)

What does NOT change:
  • Filesystem repo path (still /Users/.../youtube-summarizer/)
  • Git history / commit messages
  • Existing version files in startos/versions/ (kept as-is — the
    version chain belongs to the package's own history regardless of
    its display name)

User-side follow-ups required:
  1. Create "recap" product in Keysat admin, set up Core/Pro tier
     policies (same entitlements as before), mint a fresh test
     license. Old "youtube-summarizer" licenses won't activate
     against the new slug.
  2. Update .deploy.env (gitignored) so FILEBROWSER_PATH and
     REGISTRY_PUBLIC_URL point at recap_x86_64.s9pk.

StartOS will treat this as a brand-new app on install — existing
youtube-summarizer installs will not auto-migrate (acknowledged
intentional given no real users yet).
2026-05-08 13:35:27 -05:00
Keysat 8a519ee25d Fix Settings modal crash: send licenseId as string, not Uint8Array
The Keysat client's payload exposes both:
  • licenseId    — raw 16-byte UUID as Uint8Array
  • licenseUuid  — same value, canonical string form

server/license.js was sending licenseId (the Uint8Array). After JSON
serialization that turned into an object like {"0":1,"1":2,...} on
the wire. The frontend's renderLicenseBlock() calls
`lic.licenseId.slice(0, 8)` to abbreviate the ID for display — .slice
doesn't exist on that object, so the template threw, the
app.innerHTML assignment silently aborted, and clicking the gear
looked like a no-op.

The render error guard added in 0.1.17 caught it on first repro:
  Render error: lic.licenseId.slice is not a function

Fix: switch to payload.licenseUuid (the string form).
2026-05-08 13:00:29 -05:00
Keysat 2621f2cdbe Add online license revocation check (Keysat /v1/validate)
Without this, a license revoked in the Keysat admin UI keeps unlocking
the app on the customer's machine — Ed25519 signatures are perpetually
valid, so the offline-only check never sees the revocation.

What this adds:

  • license.js: validateOnline() calls licensing.keysat.xyz/v1/validate
    via @keysat/licensing-client's Client. Hard rejections (revoked,
    suspended, expired, not_found, product_mismatch, fingerprint_mismatch,
    too_many_machines, invalid_state) immediately flip state to "invalid"
    and persist the verdict to <license>.state.json so it survives
    restarts. rate_limited and unknown reasons are treated as transient.

  • Network errors keep the prior state for up to MAX_OFFLINE_DAYS
    (default 7, env-overridable) since the last successful validate.
    Past the ceiling, lock out with reason=validation_overdue. This
    avoids breaking customers when Keysat is briefly down while still
    catching revocations on machines that go offline forever.

  • license.js: deactivate() helper that removes both license.txt and
    its sidecar state file (idempotent). publicView() now exposes
    lastValidatedAt, serverStatus, graceUntil for the UI.

  • index.js: refreshLicenseOnline() runs on startup (async, non-
    blocking), every 6h thereafter (env-overridable), and at activation
    time with an 8s timeout cap so a slow Keysat doesn't hang the
    activation UI. State changes are logged.

  • index.js: /api/license/activate now awaits an online confirmation
    after the offline signature check passes. A revoked key pasted into
    the activation modal fails fast instead of working until the next
    poll.
2026-05-08 10:39:11 -05:00
Keysat 574a16d9fa Save in-progress keysat integration and StartOS 0.4 work
Snapshot of the working tree before cleanup. Captures:
- Keysat licensing: server/license.js, /api/license/* endpoints in
  server/index.js, activation modal in public/index.html, embedded
  Ed25519 issuer key (assets/issuer.pub).
- StartOS 0.4 expansion: setApiKey action, version files v0.1.1
  through v0.1.15, file-models/config.json.ts, manifest updates.
- Self-hosted registry server (startos-registry/).
- Build/deploy scripts (bin/bump-version.sh, bin/deploy.sh, vendored
  yt-dlp binary), .gitignore, .deploy.env.example.
- Recent design docs (KEYSAT_INTEGRATION.md, UPGRADE-DESIGN.md) —
  retained here so they remain recoverable when removed in the
  follow-up cleanup commit.
2026-05-08 09:39:17 -05:00