shotops-mcp 0.8.0 → 0.9.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 +76 -68
- package/dist/local.js +3716 -909
- package/harness-dist/assets/index-Wb7M1qGz.js +1 -0
- package/harness-dist/index.html +1 -1
- package/package.json +1 -1
- package/widget-dist/bundle.js +4 -4
- package/widget-dist/panels.js +3 -3
- package/harness-dist/assets/index-BfJOyBU7.js +0 -1
package/README.md
CHANGED
|
@@ -63,7 +63,7 @@ The key is never copied, printed, or sent to ShotOps, and no release tool is ins
|
|
|
63
63
|
| `save_look` | Persist a composed look on a project (styling only — appends a new version) | `look`, `project?`, `sourceName?` |
|
|
64
64
|
| `hold_look` | Hold which saved version agents render by default (replaces pinning) — the version must exist (`read_look` `versions`) | `version`, `project?` |
|
|
65
65
|
| `release_look` | Clear the held version — agents Follow latest (render the newest saved look) | `project?` |
|
|
66
|
-
| `describe_look` | Five named design presets,
|
|
66
|
+
| `describe_look` | Five named design presets, three named layout templates, the user-intake script, and the full styling field catalog + defaults | *(none)* |
|
|
67
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?` |
|
|
68
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
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)* |
|
|
@@ -85,15 +85,15 @@ The key is never copied, printed, or sent to ShotOps, and no release tool is ins
|
|
|
85
85
|
`text` (and a single caption's `subtitle`) is per-RENDER input (never stored — a look carries
|
|
86
86
|
styling only). Captions auto-layout in a band at the top of the panel, and the device does NOT
|
|
87
87
|
move to make room.
|
|
88
|
-
- **`style.layout`** — the panel **composition** in one word: `"standard"
|
|
89
|
-
the headline's reserved region
|
|
90
|
-
|
|
88
|
+
- **`style.layout`** — the panel **composition** in one word: `"standard"`, `"bleed"`, or
|
|
89
|
+
`"top-bleed"`. It settles the headline's reserved region, device placement, camera pose, and
|
|
90
|
+
roll together. `standard` (the shipped default) puts the whole device
|
|
91
91
|
under a 2-line region — about 36 characters — and crops nothing; `bleed` reserves 4 lines (~72
|
|
92
92
|
characters) and runs the device 10% off the bottom edge. The region is **reserved, not fitted**:
|
|
93
93
|
it is held at full size whether or not the headline fills it, which is what makes every panel in
|
|
94
94
|
a swiped set land on the same line. **Precedence** — the template expands *first*, then any
|
|
95
|
-
`shotLook
|
|
96
|
-
|
|
95
|
+
explicit `shotLook` composition field or caption layout field you also pass **overrides** it
|
|
96
|
+
(`{ layout: "bleed", shotLook: { vOffset: "45" } }` is "bleed, but a bit lower").
|
|
97
97
|
A pure macro: nothing stores the template id, so `save_project` persists the expanded values and
|
|
98
98
|
`read_look` returns them unchanged. **The id is not lost, though — it is DERIVED back:**
|
|
99
99
|
`read_project` and `read_look` both return a `layout` report read out of the values themselves, so
|
|
@@ -236,7 +236,7 @@ own `error` and never cancels the others — re-import just that one. In ChatGPT
|
|
|
236
236
|
arrives as the top-level `file` parameter instead of an array entry, because the Apps SDK cannot
|
|
237
237
|
fill a file parameter nested inside an array; you can pass both in the same call.
|
|
238
238
|
|
|
239
|
-
`{ url }` and `{ file }` need the hosted server (it stores the fetched PNG under your account);
|
|
239
|
+
`{ url }` and `{ file }` need the hosted server (it stores the fetched screenshot as PNG under your account);
|
|
240
240
|
`{ path }` needs the local stdio server (the hosted server never reads a caller-supplied
|
|
241
241
|
filesystem path — that would be an LFI hole). Each refusal names the door that IS open on your
|
|
242
242
|
tier. When the screenshots are only on the user's machine and you are on the hosted server, there
|
|
@@ -255,19 +255,22 @@ is no URL to give, so use the signed-slot flow below:
|
|
|
255
255
|
`screenshots` entries accept these shapes: an inline base64 string (small payloads only —
|
|
256
256
|
inline is capped and returns a clear "payload too large" error above 3MB decoded, pointing
|
|
257
257
|
you back at this flow), `{ ref }` from step 1, `{ url }` (an https URL this server fetches
|
|
258
|
-
itself:
|
|
258
|
+
itself: PNG or JPEG, converted to PNG when needed, no redirects, ~20MB cap), `{ path }` (local stdio server only — see below),
|
|
259
259
|
or `{ "locales": { "<locale>": <any of those> } }` for per-locale variants (see
|
|
260
260
|
[Per-locale screenshots](#per-locale-screenshots--multi-locale-bundles) above).
|
|
261
261
|
|
|
262
262
|
Every raw PNG is automatically palette-optimized before rendering (TinyPNG-style, locally inside
|
|
263
263
|
ShotOps): dimensions and transparency are preserved, and an already-smaller PNG is left byte-for-byte
|
|
264
|
-
unchanged.
|
|
264
|
+
unchanged. Fetched JPEGs are converted to palette PNGs at the same boundary without changing their
|
|
265
|
+
dimensions. This happens after the input reaches the MCP, so it does **not** raise the inline request
|
|
265
266
|
cap — use the ref/upload flow for a large source file.
|
|
266
267
|
|
|
267
|
-
**Output** works the same way in reverse: `render_strip`/`
|
|
268
|
-
— `"inline"` (
|
|
269
|
-
|
|
270
|
-
|
|
268
|
+
**Output** works the same way in reverse: `render_strip`/`render_project` take an `output` field
|
|
269
|
+
— `"inline"` (standard MCP `image` blocks), `"urls"` (uploaded under your own prefix and returned
|
|
270
|
+
as short-lived `resource_link` blocks), or omit for auto (inline under ~200KB total, `urls` above).
|
|
271
|
+
The model-visible `structuredContent.panels` contains ordered delivery descriptors rather than PNG
|
|
272
|
+
base64; ChatGPT receives the complete gallery through widget-only result metadata. Programmatic
|
|
273
|
+
clients read the standard MCP content blocks. Use `"urls"` for full-resolution work.
|
|
271
274
|
|
|
272
275
|
**Cheap preview, then compose:** `render_strip({ preview: true })` renders at ~25% resolution
|
|
273
276
|
for fast, cheap styling iteration — small enough to always come back inline; don't ship it,
|
|
@@ -308,7 +311,7 @@ Deploy the hosted server at a public HTTPS origin, enable Developer Mode in Chat
|
|
|
308
311
|
an app whose MCP server URL is `https://<your-host>/mcp`. Do not paste a personal `shotops_` token
|
|
309
312
|
into the app definition: ChatGPT discovers the protected-resource metadata from the endpoint and
|
|
310
313
|
runs the server's OAuth + PKCE browser sign-in flow. This is a tool-only app — no widget or UI
|
|
311
|
-
resource is required. User-attached PNGs enter through `import_screenshot`.
|
|
314
|
+
resource is required. User-attached PNGs and JPEGs enter through `import_screenshot`.
|
|
312
315
|
|
|
313
316
|
For public plugin submission, OpenAI supplies a domain-verification token after the MCP domain
|
|
314
317
|
is entered in the Platform dashboard. Set it on the hosted server as
|
|
@@ -368,47 +371,42 @@ since this repo is private.
|
|
|
368
371
|
|
|
369
372
|
### Ops (this repo's maintainer only)
|
|
370
373
|
|
|
371
|
-
-
|
|
372
|
-
|
|
374
|
+
`shotops-mcp` serves from its own Vercel project on `https://mcp.shotops.dev` (health:
|
|
375
|
+
`/health`, MCP endpoint: `/mcp`). **`docs/reference/ops-runbook.md` §Deploy owns the procedure**
|
|
376
|
+
— the notes here are only what is specific to this package.
|
|
377
|
+
|
|
378
|
+
- **Deploy:** run the repo's deploy script from the repo root; deploys run locally, and a push to
|
|
379
|
+
`main` is a production deploy. Do not hand-run a bare `vercel deploy`. Fly was retired (#68) —
|
|
380
|
+
`flyctl` is history, not the live procedure.
|
|
381
|
+
- **Runtime env** lives on the `shotops-mcp` **Vercel project**, not in this repo:
|
|
382
|
+
`SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`, and `MCP_PUBLIC_URL` (pins the public hostname as
|
|
383
|
+
the OAuth issuer), plus `OPENAI_APPS_CHALLENGE_TOKEN` while verifying the MCP domain for public
|
|
384
|
+
ChatGPT submission — nothing Apple-related, ever, by construction. Vercel env applies at the
|
|
385
|
+
NEXT deployment, never to the running one.
|
|
386
|
+
- **Verify after any deploy** — a green deploy isn't proof the server answers MCP calls:
|
|
373
387
|
```bash
|
|
374
|
-
|
|
388
|
+
cd shotops-mcp && SHOTOPS_MCP_TOKEN=… node verify-mcp.mjs https://mcp.shotops.dev/mcp
|
|
375
389
|
```
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
node
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
server went unhealthy for minutes — `verify-mcp.mjs` could not run to completion at 2gb and now
|
|
397
|
-
does. The **2 vCPUs** are the older finding below; don't conflate them, and don't cut either.
|
|
398
|
-
- **Why 2 vCPU and not `shared-cpu-1x`** (found live, Phase 10):
|
|
399
|
-
Chromium's software WebGL render (no GPU on the hosted tier) is CPU-heavy enough that on
|
|
400
|
-
a single shared vCPU it can starve the whole process — health checks stop responding,
|
|
401
|
-
SIGTERM/SIGINT get ignored, and the platform force-kills the machine
|
|
402
|
-
mid-render. **`Virtual machine exited abruptly` has TWO causes and they look identical in the
|
|
403
|
-
logs:** on 1 vCPU it is this CPU starvation; on 2 vCPU under load it was the #134 OOM, i.e.
|
|
404
|
-
MEMORY, fixed by the `4gb` above (`docs/reference/ops-runbook.md` carries that half). Check
|
|
405
|
-
which resource ran out before reaching for either fix. On 2 vCPUs the same 3-panel real-screenshot render completes in ~8s. Don't
|
|
406
|
-
drop back to `shared-cpu-1x` to save cost — a render-triggering tool call is expected to
|
|
407
|
-
occasionally saturate a whole core, and 1x has no second core to keep the process alive
|
|
408
|
-
while that happens. Client callers (agents, `verify-mcp.mjs`) should still set a generous
|
|
409
|
-
per-call timeout (≥180s) for `render_strip`/`emit_bundle` — even warm, cold-machine renders
|
|
410
|
-
can take 60-110s before the CPU fix, and network/Supabase round trips add more on `urls`
|
|
411
|
-
output.
|
|
390
|
+
Exercises the exact registered tool set, real render/bundle/project round-trips, signed upload
|
|
391
|
+
refs, asset deletion, and asserts no-token / bad-token both get `401`. It needs the **Studio**
|
|
392
|
+
deployment live too (`delete_project` is a control-plane op) and leaves the account as it found
|
|
393
|
+
it (#333). Add `--artifact-dir <private-dir>` for the ChatGPT portal handoff.
|
|
394
|
+
- **Logs / status:** the `shotops-mcp` project in the Vercel dashboard.
|
|
395
|
+
- **Production builds through Nitro** — `nitro.config.ts` (`preset: 'node-server'`,
|
|
396
|
+
`vercel: { entryFormat: 'node' }`) compiles the Express graph into `dist-server/`. `Dockerfile`
|
|
397
|
+
builds the RETIRED Fly image only; it is pinned to `mcr.microsoft.com/playwright:v1.61.1-noble`
|
|
398
|
+
to match the `playwright` npm version in `package.json`, so if you bump one, bump the other.
|
|
399
|
+
- The build compiles the browser harness once (`build:harness` → `harness-dist/`) and the running
|
|
400
|
+
server serves it from a loopback-only static server. Vite is build/parity tooling only and is
|
|
401
|
+
pruned from production dependencies; `mockup-engine` stays reachable at build time so the
|
|
402
|
+
harness can bundle the shared engine + GLB.
|
|
403
|
+
- **Hosted render capacity is load-bearing and history-rich.** A `render_strip` boots headless
|
|
404
|
+
Chromium + WebGL for seconds, inline in the request, and full-res multi-panel renders are slow
|
|
405
|
+
by design — seconds per panel, one panel at a time. The function duration ceiling, the memory
|
|
406
|
+
findings behind it, and the two distinct failure shapes (CPU starvation vs. OOM) are recorded in
|
|
407
|
+
`docs/reference/ops-runbook.md` §Deploy. Never cut a memory or duration setting without a green
|
|
408
|
+
`verify-mcp.mjs` run behind the claim. Client callers (agents, `verify-mcp.mjs`) should set a
|
|
409
|
+
generous per-call timeout (≥180s) for `render_strip`/`emit_bundle`.
|
|
412
410
|
|
|
413
411
|
## Local — `npx shotops-mcp`
|
|
414
412
|
|
|
@@ -579,15 +577,23 @@ npx shotops-mcp release plan \
|
|
|
579
577
|
|
|
580
578
|
The command resolves the Bundle ID to one accessible app and one editable iOS version, then reads
|
|
581
579
|
the supported metadata, review, age-rating, compliance, build, screenshot, and App Preview
|
|
582
|
-
baseline. It writes a user-only `release-plan.json` containing every
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
580
|
+
baseline. It writes a user-only `release-plan.json` containing every operation as a member of one
|
|
581
|
+
closed, typed vocabulary — each carrying its own target, the precondition it needs, the sealed bytes
|
|
582
|
+
it sends, the postcondition it must reach, and a redacted summary — plus a portable
|
|
583
|
+
`release-review.zip` containing that exact canonical plan and its content-addressed screenshot bytes.
|
|
584
|
+
|
|
585
|
+
The plan hash-binds the project revision, desired bytes, normalized Fastlane inputs, Apple setup
|
|
586
|
+
identity and policy version, so later approval cannot drift onto different state. Its **approval
|
|
587
|
+
window is 15 minutes**; the plan itself stays readable for as long as its sealed workspace exists.
|
|
588
|
+
Concurrency is per resource rather than one hash of the whole app: an unrelated App Store edit is
|
|
589
|
+
reported without invalidating the plan, while a change to a resource the plan writes — or to the
|
|
590
|
+
app, version or App Store state it targets — blocks before any write.
|
|
587
591
|
|
|
588
592
|
`release-plan-envelope.json` is the separate redacted form safe for a later ShotOps coordination
|
|
589
|
-
step: it carries
|
|
590
|
-
paths, Apple credentials, or asset bytes.
|
|
593
|
+
step: it carries keyed commitments and digests, never metadata values, review credentials, local
|
|
594
|
+
paths, Apple credentials, or asset bytes. Identifiers are committed under a per-plan key that stays
|
|
595
|
+
on your machine, so the envelope cannot be used to confirm a guess at your Bundle ID, Apple key ID
|
|
596
|
+
or version string. This command talks directly to Apple with GET requests
|
|
591
597
|
only; it never uploads, creates a version, commits, submits, or sends the private key to ShotOps.
|
|
592
598
|
|
|
593
599
|
### Review the exact plan locally
|
|
@@ -604,13 +610,14 @@ To review an artifact downloaded from owner-controlled CI, pass it directly:
|
|
|
604
610
|
npx shotops-mcp release review release-review.zip
|
|
605
611
|
```
|
|
606
612
|
|
|
607
|
-
The command verifies the
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
build, review, rating, compliance, preview, screenshot order/replacement/omission,
|
|
611
|
-
operations excluded by the plan. Partial-scope and no-build warnings
|
|
612
|
-
no remote resource, sends nothing, and has no edit or approval action
|
|
613
|
-
|
|
613
|
+
The command verifies the exact plan hash, artifact manifest, and every embedded screenshot before
|
|
614
|
+
opening a nonce-protected URL on `127.0.0.1` with a random port. The page shows target app and team
|
|
615
|
+
fingerprint, Bundle ID, version, the approval window, complete or partial scope, every
|
|
616
|
+
before/after/clear, build, review, rating, compliance, preview, screenshot order/replacement/omission,
|
|
617
|
+
unchanged operation, and the operations excluded by the plan. Partial-scope and no-build warnings
|
|
618
|
+
cannot be dismissed. It loads no remote resource, sends nothing, and has no edit or approval action.
|
|
619
|
+
A plan past its approval window opens read-only and says why; corrupt, edited, or content-mismatched
|
|
620
|
+
inputs fail before a server is started.
|
|
614
621
|
|
|
615
622
|
The saved project remembers the exact on-disk folder your screenshots came from (not just their
|
|
616
623
|
filenames), so re-opening it on the SAME machine can point right back at it. Share links aren't
|
|
@@ -650,7 +657,8 @@ shotops-mcp/
|
|
|
650
657
|
vite.harness.config.mjs the STATIC harness build (local mode's publish prerequisite) → harness-dist/
|
|
651
658
|
build.mjs esbuild bundle of src/local.ts (+ @engine/@api inlined) → dist/local.js
|
|
652
659
|
Dockerfile hosted image — Playwright's Chromium base + this repo's 3 npm installs
|
|
653
|
-
fly.toml Fly app config —
|
|
660
|
+
fly.toml Fly app config — RETIRED (#68), kept only for the cold-deploy recipe;
|
|
661
|
+
the running fly.dev machine is NOT a rollback
|
|
654
662
|
verify-mcp.mjs post-deploy smoke test — real MCP round-trip against a hosted URL
|
|
655
663
|
verify-mcp-local.ts local stdio smoke test (tsx src/local.ts, in-repo)
|
|
656
664
|
static-parity.ts pixel-diffs the static-serve render against the Vite-dev render (must be 0)
|