shotops-mcp 0.7.0 → 0.8.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/README.md CHANGED
@@ -11,20 +11,43 @@ reusable **looks** and editable **projects**, so the next release matches the la
11
11
  Built for agents (ChatGPT / Claude Code / Cursor / CI). It runs over the same `@engine` render
12
12
  spine the Studio web app and the `mockup-mcp` CLI use, driving headless Chromium (Playwright).
13
13
 
14
- Ships as **two tiers, one engine**: a free `npx shotops-mcp` **local stdio server** that
15
- renders on your own machine with no account (see [Local (free tier)](#local-free-tier---npx-shotops-mcp)
16
- below), and a **hosted** server on Vercel with an account, saved looks, and share links (see
17
- [Hosted](#hosted) below, or the public
18
- [`/connect`](https://shotops.dev/connect) page in the studio app).
14
+ Ships as **two doors, one engine**: an `npx shotops-mcp` **local stdio server** that renders on
15
+ your own machine, no account needed to preview (see [Local](#local--npx-shotops-mcp) below), and a
16
+ **hosted** server on Vercel with an account, saved looks, and share links (see [Hosted](#hosted)
17
+ below, or the public [`/connect`](https://shotops.dev/connect) page in the studio app).
18
+
19
+ ## Preview free, export store-ready with Pro
20
+
21
+ Composing and previewing a strip is free through either door — every device, caption and locale, no
22
+ watermark, and locally without an account at all. **Store-ready output needs an active trial or
23
+ Pro:** a full-resolution `render_strip`/`render_project`, and every `emit_bundle` form, including
24
+ one that only re-zips panels you already rendered. That holds wherever the render happens; local
25
+ compute being free is not the same as local output being free.
26
+
27
+ The two limits are independent and neither substitutes for the other:
28
+
29
+ - **Entitlement** is permission to create store-ready files. One server-owned decision answers it
30
+ for Studio, hosted and local alike, so no door can be talked into a different answer. Free and
31
+ anonymous callers are refused before a credit is reserved, a screenshot is read or a file is
32
+ written — a refused call costs nothing and leaves nothing behind.
33
+ - **Credits** price compute ShotOps supplies: 0.5 per hosted preview panel, 2 per hosted
34
+ full-resolution panel, plus AI. Rendering on your own machine spends none, on any plan. More
35
+ credits never unlock store-ready output on Free.
36
+
37
+ Free accounts get 40 credits a month and 2 synced projects. Every new account starts with a 7-day
38
+ Pro trial; after that Pro is €9/month or €90/year. Without any account, the hosted server allows 3
39
+ preview strips per rolling 30 days, at most 5 panels each. Call `account_status` to see exactly
40
+ where this connection stands — it spends no credits and none of that anonymous allowance.
19
41
 
20
42
  ## Zero-custody
21
43
 
22
- This server **never touches a store-signing credential.** There is no tool input, env var,
23
- or code path that accepts, stores, or forwards one. `emit_bundle` hands you a zip; **you**
24
- upload it with **your own** `fastlane`, signed in as yourself. The generated `Deliverfile`
25
- is screenshots-only and **never submits for review**. The only secrets this process holds
26
- are Supabase service credentials (to resolve your API token, read your saved look, and back
27
- share links) nothing that could reach a store on your behalf.
44
+ The hosted server and every MCP tool **never touch a store-signing credential.** `emit_bundle`
45
+ hands you a zip; **you** upload it with **your own** `fastlane`, signed in as yourself. The
46
+ generated `Deliverfile` is screenshots-only and **never submits for review**.
47
+
48
+ The one explicit exception is the local `release setup` CLI below. It validates an existing `.p8`
49
+ on your machine directly with Fastlane and Apple, then stores only its path and public identifiers.
50
+ The key is never copied, printed, or sent to ShotOps, and no release tool is installed or upgraded.
28
51
 
29
52
  ## Tools
30
53
 
@@ -36,12 +59,15 @@ share links) — nothing that could reach a store on your behalf.
36
59
  | `read_look` | Return a project's saved ShotOps look (styling only), with its latest version, the held version, the `versions` history, and the `layout` template it is composed on (derived from its values, never stored) | `project?` (default: your most recently edited project) |
37
60
  | `read_project` | Return a project's FULL current state as an opaque `ProjectFile` — frame order, per-locale caption words, locale list, styling — plus the derived `layout` and a `screenshots.cells` report for every shot × target-family × locale. Each cell says whether pixels resolve, where they resolved from, and which axes inherited | `project?` (default: your most recently edited project) |
38
61
  | `render_project` | Render a SAVED project. By default it renders the screenshots **the project already holds** — pass none at all, no upload needed — into **every supported device the project targets**. Pixels resolve independently for each device family + locale through the shared manifest; genuine fallback is reported in `shots[].inherited` and `note` | `project?`, `screenshots?` (**optional** — omit to use what the project holds; else a FLAT list matched by original `name`, with optional `variant: { family?, locale? }` per cell), `outputs?` (override the project's saved devices for this call), `locale?`, `preview?`, `output?` |
62
+ | `refine_project` | Run one bounded Agent turn against a saved project. A supported request commits once and returns the actual diff; ambiguity, missing input, unsupported intent, conflict, and failure return a typed no-write outcome | `project?`, `instruction`, `focus?` |
39
63
  | `save_look` | Persist a composed look on a project (styling only — appends a new version) | `look`, `project?`, `sourceName?` |
40
64
  | `hold_look` | Hold which saved version agents render by default (replaces pinning) — the version must exist (`read_look` `versions`) | `version`, `project?` |
41
65
  | `release_look` | Clear the held version — agents Follow latest (render the newest saved look) | `project?` |
42
66
  | `describe_look` | Five named design presets, two named layout templates, the user-intake script, and the full styling field catalog + defaults | *(none)* |
43
67
  | `request_screenshot_upload` | Mint signed upload slots for real screenshots; every returned slot carries its semantic screenshot `variant` | `count` (1–10), `names?`, `family?`, `locale?` |
44
68
  | `import_screenshot` | Get screenshots INTO ShotOps — several per call, each from one named source (`{ url }`, `{ file }`, or local-stdio `{ path }`), returning a render-ready ref per entry in input order | `screenshots` (1–10 source entries), `file?` (the ChatGPT top-level attachment), `name?`, `locale?` |
69
+ | `account_status` | What THIS connection may do, for free: signed-in account, plan, trial, remaining ShotOps credits, what a preview costs here, and whether store-ready output (full-resolution renders, any `emit_bundle`) is allowed — with a machine-readable `nextStep`. Spends no credits, uses none of the free anonymous preview allowance, uploads and writes nothing | *(none)* |
70
+ | `production_operation` | Reconnect to one durable paid Hosted render or rendering bundle, report persisted progress, request cooperative cancellation, or mint fresh result download grants without rerendering or charging again | `action` (`get`, `cancel`, or `result`), `operationId` |
45
71
  | `delete_assets` | Permanently delete private uploaded screenshots, rendered panels, or generated bundles owned by the signed-in account | `refs` (1–50 ShotOps refs) |
46
72
 
47
73
  - **`panelPresetId`** — App Store size: `r69` (default, 6.9″ iPhone 1290×2796), `r65`, `r55`.
@@ -50,7 +76,9 @@ share links) — nothing that could reach a store on your behalf.
50
76
  accepted set is `RENDERABLE_PANEL_PRESET_IDS` in `shotops-mcp/src/panelCatalog.ts`, a subset of
51
77
  the hand-kept `PANEL_PRESET_IDS` copy of `mockup-engine/appstore.ts`'s `PANEL_PRESETS` — when
52
78
  Apple revises required sizes, add the preset in the engine first, then mirror it here.
53
- - **`style`** — the structured styling input (call `describe_look` for the full catalog):
79
+ - **`style`** — the structured styling input. On both the hosted MCP and the local
80
+ `npx shotops-mcp` stdio server, call `describe_look` for the authoritative complete field
81
+ catalog and defaults:
54
82
  `{ layout?, shotLook?, background?, captions? }`. `shotLook` is **one device for every phone**;
55
83
  `captions` is one entry PER PANEL in slot order — each entry is EITHER a single caption object OR
56
84
  an **array of caption layers** stacked on that panel (headline + subline + …). Each layer's
@@ -93,10 +121,10 @@ share links) — nothing that could reach a store on your behalf.
93
121
  background, `style.captions` supplies the words, in one render. (Alone, `style` styles every
94
122
  phone identically. If you pass `style.shotLook`/`style.background` *alongside* a `look`, the look
95
123
  wins and the result carries a note.) Also composes with `useSavedLook`/`version`.
96
- - **`clip`** — `"strip"` (default) renders one continuous canvas, so a wide/offset device can
97
- straddle the seam into the next panel (swipe-through scenes). `"panel"` clips each phone to its
98
- own panel so it can't bleed a foreign edge into the neighbouring App Store screenshot. A render
99
- option, not part of the saved look.
124
+ - **Clipping**set `look.shots[].look.clipToFrame: true` to clip one device's complete composite
125
+ (body, screen, shadow, and reflection) to its own panel; omit it or use `false` for overflow.
126
+ Pass top-level `clip: "panel"` to clip every device, or `clip: "strip"` to force continuous
127
+ overflow. Call `describe_look` for the complete Look contract on either MCP tier.
100
128
  - **`version`** — render a specific saved look version (implies the saved look). Without it,
101
129
  a project with a PINNED version renders the pin; otherwise the latest saved look.
102
130
  - **`emit_bundle` output** — a zip (`README.md`, `fastlane/Deliverfile`,
@@ -256,7 +284,7 @@ screenshot unavailable; public privacy disclosures must accurately state this re
256
284
  ## Prerequisites
257
285
 
258
286
  1. **A ShotOps API token.** Sign in at the Studio app → account menu → **API tokens** →
259
- create one. It looks like `shotops_9Zq3Xr7Tp0Vn6Lk2Wd8Yb4Mf1Hc5Ju3Rs9Ea7Gt0Nz1` and is
287
+ create one. It looks like `shotops_9Zq3Xr7T…` and is
260
288
  shown once — copy it then.
261
289
  2. **Supabase service env** (already in the repo's `.env.local` for local dev): `SUPABASE_URL`
262
290
  and `SUPABASE_SERVICE_ROLE_KEY`. The server loads `../.env.local` automatically.
@@ -294,7 +322,7 @@ tests without private fixture data or third-party hosting.
294
322
 
295
323
  ```bash
296
324
  claude mcp add --transport http shotops http://localhost:8788/mcp \
297
- --header "Authorization: Bearer shotops_9Zq3Xr7Tp0Vn6Lk2Wd8Yb4Mf1Hc5Ju3Rs9Ea7Gt0Nz1"
325
+ --header "Authorization: Bearer shotops_…"
298
326
  ```
299
327
 
300
328
  Then ask the agent to, e.g., *"render these two screenshots into a 6.9″ App Store strip and
@@ -322,14 +350,14 @@ fastlane deliver # signs in as YOU, previews, uploads screenshots to a dr
322
350
  The server also runs hosted, 24/7-reachable (scale-to-zero when idle), at:
323
351
 
324
352
  ```
325
- https://shotops.dev/mcp
353
+ https://mcp.shotops.dev/mcp
326
354
  ```
327
355
 
328
356
  Nothing to install — point any MCP client at that URL with your `Authorization: Bearer
329
357
  shotops_…` header, same as local dev, just swap the base URL:
330
358
 
331
359
  ```bash
332
- claude mcp add --transport http shotops https://shotops.dev/mcp \
360
+ claude mcp add --transport http shotops https://mcp.shotops.dev/mcp \
333
361
  --header "Authorization: Bearer shotops_…"
334
362
  ```
335
363
 
@@ -351,7 +379,7 @@ since this repo is private.
351
379
  - **Verify after any deploy** — a green `flyctl deploy` isn't proof the server answers MCP
352
380
  calls:
353
381
  ```bash
354
- node verify-mcp.mjs https://shotops.dev/mcp <a-real-shotops_-token>
382
+ node verify-mcp.mjs https://mcp.shotops.dev/mcp <a-real-shotops_-token>
355
383
  ```
356
384
  Exercises `tools/list`, `read_look`, `emit_bundle` (asserts the zip unzips to
357
385
  README/Deliverfile/screenshots), and asserts no-token / bad-token both get `401`.
@@ -382,12 +410,12 @@ since this repo is private.
382
410
  can take 60-110s before the CPU fix, and network/Supabase round trips add more on `urls`
383
411
  output.
384
412
 
385
- ## Local (free tier) — `npx shotops-mcp`
413
+ ## Local — `npx shotops-mcp`
386
414
 
387
415
  The SAME server also runs as a **local stdio MCP**, entirely on your own machine — no URL, no
388
- account, no hosted cost, and no render-timeout ceiling (the hosted server's one real limit — see
389
- "Ops" above). It's the same `registerTools`/render engine as the hosted server; only the
390
- transport and a few account-shaped tools differ.
416
+ hosted cost, and no render-timeout ceiling (the hosted server's one real limit — see "Ops" above).
417
+ Previewing needs no account. It's the same `registerTools`/render engine as the hosted server; only
418
+ the transport and a few account-shaped tools differ.
391
419
 
392
420
  ```bash
393
421
  claude mcp add shotops -- npx -y shotops-mcp
@@ -410,14 +438,19 @@ instead of `{ ref }`/`{ url }`:
410
438
  (`{ "path": ... }` only works over this LOCAL server — the hosted server rejects it, since
411
439
  reading an arbitrary server-side path there would be a local-file-inclusion hole.)
412
440
 
413
- **`emit_bundle` reads local panels off disk too** — the free tier has no account, so it can't
414
- produce `output: "urls"` refs. After rendering, pass the on-disk PNGs straight to `emit_bundle`
415
- (same local-only `{ path }` door as screenshots) to package a `fastlane deliver` zip with no upload:
441
+ **`emit_bundle` reads local panels off disk too** — an unsigned local server has no account, so it
442
+ can't produce `output: "urls"` refs. Once you are signed in on a trial or Pro, pass the on-disk PNGs
443
+ straight to `emit_bundle` (same local-only `{ path }` door as screenshots) to package a
444
+ `fastlane deliver` zip with no upload and no credits:
416
445
 
417
446
  ```json
418
447
  { "bundleId": "com.acme.app", "panels": [{ "path": "/abs/panel-01.png" }, { "path": "/abs/panel-02.png" }] }
419
448
  ```
420
449
 
450
+ `emit_bundle` and full-resolution renders are the store-ready half of the offer, so they need that
451
+ trial or Pro even here, where the compute is yours. Preview renders need neither an account nor
452
+ credits. `account_status` reports which side of the line this process is on.
453
+
421
454
  **Saving without a token:** a new `save_project` made from local `{ path }` PNGs creates a private
422
455
  **pending claim**. It uploads only that saved project's byte-free record and raw source PNGs,
423
456
  returns `status: "pending_claim"` plus `claimId` and `openUrl`, and deliberately returns no
@@ -425,22 +458,26 @@ returns `status: "pending_claim"` plus `claimId` and `openUrl`, and deliberately
425
458
  with its screenshots, and the link expires after 7 days. An existing `project` cannot be updated
426
459
  without the token that owns it. Non-path inputs are refused rather than creating a blank claim.
427
460
 
428
- Ordinary `render_strip` / `emit_bundle` calls still upload **nothing** and work fully. Other
461
+ Ordinary `render_strip` / `emit_bundle` calls still upload **nothing** — pixels and zips are written
462
+ straight to your disk, and no screenshot leaves the machine. Other
429
463
  account reads/writes (`read_look`, `save_look`, `read_project`, `render_project`, share links)
430
464
  still need a token or hosted connection. A token-backed local `render_project` reads the cloud
431
465
  manifest through the Studio control plane, downloads only the resolved cells, and renders locally:
432
466
 
433
- | | Local (free, `npx shotops-mcp`) | Hosted (`shotops_…` token) |
467
+ | | Local (`npx shotops-mcp`) | Hosted (`shotops_…` token) |
434
468
  |---|---|---|
435
- | Render compute | **your machine** ($0, no timeout) | our servers — spends your ShotOps credits: 1,000 per preview panel, 4,000 per full-resolution panel |
436
- | Auth | none (the process runs as you) | account + API token |
469
+ | Render compute | **your machine** ($0, no timeout, no credits on any plan) | our servers — spends your ShotOps credits: 0.5 per preview panel, 2 per full-resolution panel |
470
+ | Preview render | free, no account | free on an account; 3 per 30 days, ≤5 panels, without one |
471
+ | Full-resolution render, any `emit_bundle` | trial or Pro (costs no credits) | trial or Pro, and spends credits |
472
+ | Auth | none to preview; a token for account state and store-ready output | account + API token |
437
473
  | Saved looks / editable projects / project read+re-render / share links | new project: pending claim; existing/account state: token (below); share links: hosted | ✅ |
438
474
  | Screenshot input | local `{ path }` off disk | `request_screenshot_upload` → `{ ref }` |
439
475
 
440
476
  ### Optional: bridge a local render into your hosted account
441
477
 
442
478
  Connect your account and `save_project` / `read_look` / `save_look` / `read_project` /
443
- `render_project` start working too — **rendering still happens locally and for free.** A
479
+ `render_project` start working too — **rendering still happens locally and spends no credits**, and
480
+ a trial or Pro account unlocks full-resolution output and `emit_bundle` on this machine. A
444
481
  deliberate save also uploads its local source PNGs privately so the project reopens with
445
482
  screenshots on another device; normal renders and exports never upload.
446
483
 
@@ -466,6 +503,115 @@ SHOTOPS_TOKEN=shotops_… claude mcp add shotops -- npx -y shotops-mcp
466
503
  `--token` wins over `SHOTOPS_TOKEN`, which wins over a saved browser login — so a token in the
467
504
  environment is never quietly shadowed by whoever last signed in on that machine.
468
505
 
506
+ ### Local Apple release setup
507
+
508
+ Before preparing an App Store release, validate the Apple tooling and API key already on this
509
+ machine:
510
+
511
+ ```bash
512
+ npx shotops-mcp release setup
513
+ # non-interactive form:
514
+ npx shotops-mcp release setup \
515
+ --issuer-id "$APP_STORE_CONNECT_ISSUER_ID" \
516
+ --key-id "$APP_STORE_CONNECT_KEY_ID" \
517
+ --key-path /secure/AuthKey_ABC123DEFG.p8
518
+ ```
519
+
520
+ The command requires an owner-only `.p8`, Fastlane, and Apple Transporter; parses the key through
521
+ Fastlane; and makes read-only App Store Connect app-list requests to prove the key's real access.
522
+ It does not install or upgrade anything. Only Issuer ID, Key ID, and the canonical key path are
523
+ saved in `~/.shotops/release.json` (or `$SHOTOPS_CONFIG_DIR/release.json`); a failed rerun leaves the
524
+ last valid setup untouched.
525
+
526
+ For CI, create secret inputs named `APP_STORE_CONNECT_ISSUER_ID`,
527
+ `APP_STORE_CONNECT_KEY_ID`, and `APP_STORE_CONNECT_PRIVATE_KEY`. The setup command names these
528
+ inputs but never prints their values.
529
+
530
+ ### Prepare a deterministic desired release
531
+
532
+ Once signed in to ShotOps, assemble the saved owner project and repository into one local,
533
+ content-addressed workspace — before reading or changing App Store Connect:
534
+
535
+ ```bash
536
+ npx shotops-mcp release prepare --project "My project"
537
+
538
+ # Optional signed build and an explicit partial scope:
539
+ npx shotops-mcp release prepare \
540
+ --project proj_123 \
541
+ --build build/MyApp.ipa \
542
+ --locales en-US,de-DE \
543
+ --outputs iphone-6-9
544
+ ```
545
+
546
+ The command renders every authored locale and selected output by default with the same engine as
547
+ Studio and the MCP. `--locales` or `--outputs` deliberately narrows the run and records every
548
+ omission as a **partial** scope. It writes `desired-release.json` plus the rendered PNGs under
549
+ `~/.shotops/release-workspaces/<desired-id>/` (or `--output <directory>`), with user-only
550
+ permissions. Identical project, repository, scope, and build inputs produce the same manifest ID
551
+ and pixels.
552
+
553
+ Repository input conventions are explicit so no Ruby is ever evaluated:
554
+
555
+ - `fastlane/metadata/<locale>/*.txt` — supported localized Deliver metadata;
556
+ - `fastlane/metadata/*.txt` and `review_information/*.txt` — app and review metadata;
557
+ - `fastlane/metadata/app_store_rating_config.json` — Fastlane's current age-rating shape;
558
+ - `fastlane/metadata/submission_information.json` — export/content compliance fields;
559
+ - `fastlane/metadata/app_review_attachment_file.txt` — empty clears the attachment; otherwise it
560
+ names a repository-relative supported attachment;
561
+ - `fastlane/app-previews/<locale>/*` — `.mov`, `.mp4`, or `.m4v`, with the ASC device token in
562
+ each filename (for example `APP_IPHONE_67_01_demo.mp4`).
563
+
564
+ Missing files mean **unchanged**; an existing supported empty text file means **clear**. Unknown
565
+ files, fields, locales, outputs, unreadable artifacts, and Bundle ID disagreements fail closed.
566
+ Fastfile and Deliverfile are never executed. Optional `.ipa`/supported `.pkg` bytes are hashed and
567
+ referenced, never copied into ShotOps state. This step performs no ASC request, upload, version
568
+ creation, signing, submission, or release.
569
+
570
+ ### Create the exact App Store Connect plan
571
+
572
+ Read the editable App Store version and bind it to the prepared workspace without changing Apple:
573
+
574
+ ```bash
575
+ npx shotops-mcp release plan \
576
+ --workspace ~/.shotops/release-workspaces/<desired-id> \
577
+ --repo .
578
+ ```
579
+
580
+ The command resolves the Bundle ID to one accessible app and one editable iOS version, then reads
581
+ the supported metadata, review, age-rating, compliance, build, screenshot, and App Preview
582
+ baseline. It writes a user-only `release-plan.json` containing every create, update, clear,
583
+ replacement, upload, attachment, and no-op, plus a portable `release-review.zip` containing that
584
+ exact canonical plan and its content-addressed screenshot bytes. The plan expires after 15 minutes
585
+ and hash-binds the project revision, desired bytes, normalized Fastlane inputs, Apple setup identity,
586
+ policy version, and the complete remote snapshot so later approval cannot drift onto different state.
587
+
588
+ `release-plan-envelope.json` is the separate redacted form safe for a later ShotOps coordination
589
+ step: it carries identifiers and value hashes, never metadata values, review credentials, local
590
+ paths, Apple credentials, or asset bytes. This command talks directly to Apple with GET requests
591
+ only; it never uploads, creates a version, commits, submits, or sends the private key to ShotOps.
592
+
593
+ ### Review the exact plan locally
594
+
595
+ From the plan directory, open its read-only local browser review:
596
+
597
+ ```bash
598
+ npx shotops-mcp release review
599
+ ```
600
+
601
+ To review an artifact downloaded from owner-controlled CI, pass it directly:
602
+
603
+ ```bash
604
+ npx shotops-mcp release review release-review.zip
605
+ ```
606
+
607
+ The command verifies the unexpired exact plan hash, artifact manifest, and every embedded screenshot
608
+ before opening a nonce-protected URL on `127.0.0.1` with a random port. The page shows target app and
609
+ team fingerprint, Bundle ID, version, expiry, complete or partial scope, every before/after/clear,
610
+ build, review, rating, compliance, preview, screenshot order/replacement/omission, no-op, and the
611
+ operations excluded by the plan. Partial-scope and no-build warnings cannot be dismissed. It loads
612
+ no remote resource, sends nothing, and has no edit or approval action; expired, corrupt, edited, or
613
+ content-mismatched inputs fail before a server is started.
614
+
469
615
  The saved project remembers the exact on-disk folder your screenshots came from (not just their
470
616
  filenames), so re-opening it on the SAME machine can point right back at it. Share links aren't
471
617
  bridged yet — they'd need a hosted rendering step, which would defeat local rendering's whole
@@ -496,7 +642,7 @@ shotops-mcp/
496
642
  uploads.ts Phase 10 — upload slots, ref ownership check, output:'urls' storage
497
643
  screenshotInput.ts Phase 10 — resolves screenshots[] (inline/ref/url/path) to base64
498
644
  env.ts loads ../.env.local; MCP_PORT / STUDIO_ORIGIN / MCP_PUBLIC_URL
499
- local.ts the LOCAL (free tier) npx entry point — stdio transport, no auth
645
+ local.ts the LOCAL npx entry point — stdio transport, no auth required to preview
500
646
  localDeps.ts the local ToolDeps fork — static-serve renderer, no-op/bridged persistence
501
647
  hostedBridge.ts local→hosted bridge (a real MCP client using your shotops_ token)
502
648
  harness/ the browser render page Chromium loads (render.js exposes window.renderStrip)
@@ -71,6 +71,7 @@ Space Grotesk
71
71
  Copyright 2020 The Space Grotesk Project Authors (https://github.com/floriankarsten/space-grotesk)
72
72
 
73
73
  Each font is licensed under the SIL Open Font License, Version 1.1:
74
+ https://openfontlicense.org/open-font-license-official-text/
74
75
 
75
76
  -----------------------------------------------------------
76
77
  SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007