You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(sdk): paste-and-go AssetRef with scriptTag/linkTag/imgTag emitters
v1.45 best-DX: agents call blobs.put with immutable:true and paste asset.scriptTag() / asset.linkTag() / asset.imgTag() into generated HTML. Each emitter returns a ready-to-use tag with src/href set to a content-hashed URL on pr-<public_id>.run402.com (the host guaranteed to work through the v1.33 CDN), integrity set to the SRI hash, and crossorigin set so browsers verify the bytes.
AssetRef widening:
- cdnUrl: the immutable-form auto-subdomain URL — what scriptTag/linkTag/imgTag bind to. Null on non-immutable or private uploads.
- cdnMutableUrl: mutable-form auto-subdomain URL. Eventual-consistency caveats apply; prefer cdnUrl in code.
- url / immutable_url / immutableUrl (preferred-host forms) — kept for backward compat and direct-API consumers (e.g. blobs.get). Currently NOT served through the v1.33 CDN behavior on claimed subdomains / custom domains; followup tracked separately.
Tag emitter ergonomics:
- scriptTag({ type?, defer?, async? }): bakes integrity + crossorigin
- linkTag({ rel = 'stylesheet', as? }): bakes integrity + crossorigin
- imgTag(alt = ''): no SRI (HTML5 spec), but URL is content-hashed so still stable across re-deploys
- HTML attribute escaping for url + alt
- Throw with actionable hint when called on non-immutable uploads
Tests:
- 4 new test cases (immutable widening, tag emitter shapes, HTML escaping, non-immutable error)
- Existing 22 tests still green; total now 25
Coordinated with run402-private: the gateway emits the new cdn_url + cdn_immutable_url fields on the upload-completion response. Older gateway versions (without the new fields) still work — the SDK leaves cdnUrl/cdnMutableUrl null and the tag emitters throw with the immutable-only hint, which surfaces on the first call rather than during upload.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: SKILL.md
+24-6Lines changed: 24 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -109,14 +109,32 @@ Upload a blob (any size, up to 5 TiB) to project storage via direct-to-S3 presig
109
109
-`immutable` (optional, default: `false`) — If true with `sha256`, also produces a content-addressed URL that gets `Cache-Control: immutable`.
110
110
-`sha256` (optional) — Required when `immutable: true`. Client-asserted hash; gateway verifies if S3 returns one.
111
111
112
-
**Returns:** an `AssetRef`: `{ key, size_bytes, sha256, visibility, url, immutable_url, size, contentSha256, contentType, immutableUrl, etag, sri, contentDigest, cacheKind, cdn: { version, invalidationId, invalidationStatus, ready, hint } }`. The legacy snake_case fields (`size_bytes`, `sha256`, `immutable_url`) are kept for back-compat; the camelCase fields are the v1.45 agent-DX surface.
112
+
**Returns:** an `AssetRef`. The agent-DX way to use it: **upload with `immutable: true`, then call `result.scriptTag()`, `result.linkTag()`, or `result.imgTag()` and paste the output directly into your generated HTML.** The tag emitters return ready-to-use HTML with `src` / `href` set to a content-hashed URL on `pr-<public_id>.run402.com`, `integrity` set to the SRI hash, and `crossorigin` set so browsers verify the bytes.
113
113
114
-
**Prefer `immutableUrl` in generated HTML/CSS/JS code.** Reasoning:
115
-
- Read-after-write correctness: the immutable URL is bound to the SHA at upload time and was never previously cached. The mutable `url` is "eventually fresh" — invalidation is asynchronous, so a `<script>` tag pointing at it may load the OLD version for seconds-to-minutes after a re-upload.
116
-
- Built-in integrity: pair `immutableUrl` with `integrity={sri}` on `<script>` and `<link>` tags so browsers verify the hash before executing.
117
-
- No follow-up calls needed: an agent emitting `<script src={immutableUrl} integrity={sri}>` doesn't need to call `wait_for_cdn_freshness` afterwards. Use the mutable `url` only when the URL must remain stable across re-uploads.
If you must use the mutable `url` (e.g. you're updating an `<img>` referenced by an external system that won't accept a new URL), call `wait_for_cdn_freshness` before publishing the change. **Mutable URLs only**; never call wait_for_cdn_freshness on `immutableUrl`.
**Why this is the recommended path.** The URL the emitters use (`cdnUrl`) is content-addressed (no cache invalidation, no `wait_for_cdn_freshness`), served from the auto-subdomain `pr-<public_id>.run402.com` (the host that's guaranteed to work through the v1.33 CDN), and stable across re-deploys of the same content. SRI guarantees the browser refuses execution on byte mismatch. There are no decisions for the agent to make.
129
+
130
+
**Other AssetRef fields** for advanced use:
131
+
-`cdnUrl` — the content-addressed auto-subdomain URL the tag emitters use. Use this directly if you're not generating HTML (e.g. CSS `url()` references, JSON data URLs).
132
+
-`cdnMutableUrl` — mutable form of the auto-subdomain URL. Eventual-consistency caveats apply; prefer `cdnUrl` for generated code.
133
+
-`url` / `immutableUrl` (preferred-host forms) — the URL on the project's pretty host (claimed subdomain or custom domain when configured). Currently NOT served through the CDN — keep using these for direct-API consumers (e.g. `client.blobs.get`), not in `<script>`/`<img>` embeds.
134
+
-`etag`, `sri`, `contentDigest` — the integrity values the tag emitters bake in. Useful if you're constructing tags by hand for an unsupported context.
135
+
-`cdn: { version, invalidationId, invalidationStatus, ready, hint }` — CloudFront invalidation envelope. For immutable uploads `cdn.ready === true` and no further work is required.
136
+
137
+
The legacy `size_bytes`, `sha256`, `immutable_url` fields stay populated for back-compat with pre-v1.45 callers.
`put` response (an `AssetRef`) includes both legacy snake_case and v1.45 camelCase fields:
412
-
- `url` / (no camelCase alias) — stable mutable URL: `https://pr-<public_id>.run402.com/_blob/<key>`, also any claimed subdomain or mapped custom domain. Cached at CloudFront edge; invalidation is asynchronous on overwrite.
413
-
- `immutable_url` / `immutableUrl` (only when `--immutable`) — content-hashed URL: `https://<host>/_blob/<key-without-ext>-<8hex>.<ext>`. Bound to a SHA at upload time; never previously cached. **Prefer this in generated HTML/CSS/JS code** — it doesn't need waiting and pairs with `sri` for browser SRI verification.
html += asset.scriptTag(); // <script src=... integrity=... crossorigin></script>
416
+
html += asset.linkTag(); // <link rel="stylesheet" href=... integrity=... crossorigin>
417
+
html += asset.imgTag("Company logo"); // <img src=... alt="Company logo">
418
+
```
419
+
420
+
Always upload with `immutable: true` when you plan to embed in HTML/CSS/JS. The tag emitters require it (they bind to a SHA), and the resulting URL is content-addressed → never needs cache invalidation, never breaks on re-deploys, never needs `cdn wait-fresh` follow-up.
421
+
422
+
Other AssetRef fields for advanced use:
423
+
- `cdnUrl` — the content-addressed URL the emitters use directly. `https://pr-<public_id>.run402.com/_blob/<key-without-ext>-<8hex>.<ext>`. Served via the v1.33 CDN; guaranteed-reachable.
424
+
- `cdnMutableUrl` — mutable URL on the auto-subdomain. Eventual-consistency caveats; prefer `cdnUrl` in code.
425
+
- `url` / `immutableUrl` (preferred-host forms) — URL on the project's pretty host (claimed subdomain / custom domain when configured). Currently NOT served through the CDN — keep using for direct-API consumers (e.g. `run402 blob get`), not in `<script>`/`<img>` embeds.
**Agent guidance.** When emitting HTML/CSS/JS that links a just-uploaded asset, use `immutableUrl` + `integrity={sri}`. Read-after-write is correct and you don't need a follow-up `cdn wait-fresh` poll. Use the mutable `url` only when the URL must remain stable across re-uploads; in that case, `run402 cdn wait-fresh <url> --sha <new-sha>` blocks until the CDN serves the new SHA.
432
+
**Agent loop pattern (mutable URL only):** if you must use a stable mutable URL across re-uploads, `run402 cdn wait-fresh <mutable-url> --sha <new-sha>` blocks until the CDN serves the new SHA. **Don't call wait-fresh on immutable URLs** — they're correct from the moment of upload.
421
433
422
434
Resume: state is persisted to `~/.run402/uploads/<upload_id>.json`. Ctrl-C mid-upload and re-run the same `blob put` command — it picks up where it left off. Pass `--no-resume` to start fresh.
0 commit comments