Skip to content

Commit ff36d68

Browse files
committed
feat: add URL shortening via share API to studio command
1 parent 92eda3e commit ff36d68

14 files changed

Lines changed: 295 additions & 127 deletions

File tree

README.md

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Shotstack CLI
22

3-
Command-line interface for the [Shotstack](https://shotstack.io) video rendering API. Built for humans and AI coding agents.
3+
Command-line interface for the [Shotstack](https://shotstack.io) video rendering API. Built for humans and AI agents.
44

55
```sh
66
shotstack render template.json
@@ -16,7 +16,7 @@ npm i -g @shotstack/cli
1616
# one-shot via npx
1717
npx @shotstack/cli render template.json
1818

19-
# one-shot via bun (~100x faster than npx for cached invocations)
19+
# one-shot via bun
2020
bunx @shotstack/cli render template.json
2121
```
2222

@@ -27,7 +27,7 @@ All three install paths use the same `@shotstack/cli` package on npm.
2727
Set your API key as an environment variable:
2828

2929
```sh
30-
export SHOTSTACK_API_KEY=sk_...
30+
export SHOTSTACK_API_KEY=...
3131
```
3232

3333
Get a key at <https://shotstack.io>.
@@ -69,23 +69,24 @@ shotstack status 01ja7-x8m2k-... --watch
6969
shotstack status 01ja7-x8m2k-... --output json
7070
```
7171

72-
### `shotstack preview <file>`
72+
### `shotstack studio <file>`
7373

74-
Opens a `shotstack.studio` URL that loads the Edit JSON directly into the browser-based editor. No API call, no key, no charge — pure client-side encoding via the URL hash. Use it to hand a generated edit off to a human for review or quick tweaks before rendering.
74+
Opens a `shotstack.studio` URL that loads the Edit JSON in the browser-based editor. By default, posts the JSON to the share API and emits a short URL like `https://shotstack.studio/s/abc12345` — clean, shareable, expires in 30 days. Falls back to inline base64url encoding if the share API is unreachable.
75+
76+
No render API key required; no render credits charged. Use to hand a generated edit off to a human for review or quick tweaks before rendering.
7577

7678
```sh
77-
shotstack preview my-template.json
78-
# → opens browser silently
79+
shotstack studio my-template.json
80+
# → opens browser silently with https://shotstack.studio/s/<slug>
7981

80-
shotstack preview my-template.json --copy # also copies URL to clipboard
81-
shotstack preview my-template.json --no-open # print URL, don't open browser
82-
shotstack preview my-template.json --output json # emit {"url":"..."} on stdout
82+
shotstack studio my-template.json --copy # also copies URL to clipboard
83+
shotstack studio my-template.json --no-open # print URL, don't open browser
84+
shotstack studio my-template.json --no-shorten # emit base64url inline (offline / debug)
85+
shotstack studio my-template.json --output json # emit {"url":"...","shortened":true} on stdout
8386
```
8487

8588
When a browser can be launched, the command is silent — the URL only opens in the browser. On a headless server (no `$DISPLAY`, no `xdg-open`), the URL is printed to stdout instead so you can copy it elsewhere.
8689

87-
Templates whose encoded URL exceeds ~6KB print a stderr warning. Browser URL limits vary; if you regularly exceed it, host the JSON publicly and link to it via `https://shotstack.studio/#src=<https-url>`.
88-
8990
### `shotstack feedback`
9091

9192
Opens a pre-filled GitHub issue with a sanitised dossier of your last 5 CLI invocations (render IDs, errors, exit codes). API keys and signed URLs are stripped at write time. You review and submit in your browser; nothing is transmitted automatically. Inspect the log at `~/.shotstack/log.jsonl`.

SKILL.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,19 @@ shotstack status 01ja7-x8m2k-39rzv-cmvxve --watch
4747

4848
## Hand-off to a human before rendering
4949

50-
When a human is in the loop and may want to tweak the result, prefer **`shotstack preview <file>`** over `shotstack render`. By default it opens the browser to `https://shotstack.studio/#json=<base64url>` and prints the URLthe timeline loads directly into the browser-based editor. No API call, no key, no charge. The human can play, edit, and decide whether to render — saving credits when the AI's first attempt isn't quite right.
50+
When a human is in the loop and may want to tweak the result, prefer **`shotstack studio <file>`** over `shotstack render`. By default it posts the JSON to the share API and opens `https://shotstack.studio/s/<slug>` in the browsera short, shareable URL. No API key, no render credits charged. The human can play, edit, and decide whether to render.
5151

5252
```sh
53-
shotstack preview template.json # opens browser + prints URL
54-
shotstack preview template.json --no-open # headless: just print the URL
55-
shotstack preview template.json --output json # piping: {"url":"..."}, no browser
53+
shotstack studio template.json # opens browser + prints short URL
54+
shotstack studio template.json --no-open # headless: just print the URL
55+
shotstack studio template.json --no-shorten # emit base64url URL inline (offline / debug)
56+
shotstack studio template.json --output json # piping: {"url":"...","shortened":true}, no browser
5657
```
5758

5859
On headless systems (no `xdg-open`, no `$DISPLAY`) the browser launch silently no-ops; the URL is still printed. Safe to run anywhere.
5960

61+
If the share API is unreachable, the command falls back to the inline base64url form automatically and prints a stderr warning. Shares expire after 30 days.
62+
6063
Use `render` only when you're confident the JSON is final, or there's no human to review.
6164

6265
## Four CLI rules
@@ -67,7 +70,7 @@ Use `render` only when you're confident the JSON is final, or there's no human t
6770

6871
3. **Fetch the current schema and docs before generating Edit JSON.** The Shotstack API evolves; LLM training data is often stale. Pull <https://shotstack.io/docs/api/api.edit.json> and <https://shotstack.io/docs/guide/llms-full.txt> for the current schema and guides before composing an Edit from scratch.
6972

70-
4. **Hand off to a human via `preview` when uncertain.** Don't burn render credits iterating. Generate JSON → `shotstack preview` → human reviews/tweaks → render only when right.
73+
4. **Hand off to a human via `studio` when uncertain.** Don't burn render credits iterating. Generate JSON → `shotstack studio` → human reviews/tweaks → render only when right.
7174

7275
## Authoring Edit JSON
7376

@@ -100,8 +103,8 @@ Authoritative sources, in order of preference:
100103

101104
This skill ships sub-references for the gnarly bits:
102105

103-
- [`references/timeline.md`](references/timeline.md) — track layering, transitions, soundtrack vs audio
104-
- [`references/rich-caption.md`](references/rich-caption.md) — sizing per resolution, default style, the 5 named presets, alias pattern
106+
- [`references/timeline.md`](references/timeline.md) — track layering, transitions, background music via audio assets
107+
- [`references/caption.md`](references/caption.md) — sizing per resolution, default style, the 5 named presets, alias pattern (asset type: `rich-caption`)
105108
- [`references/svg.md`](references/svg.md) — required attrs, supported elements
106109
- [`references/fonts.md`](references/fonts.md) — built-in fonts, Google Fonts URL pattern, custom-font workflow
107110
- [`references/asset-library.md`](references/asset-library.md) — placeholder videos, images, music

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@shotstack/cli",
3-
"version": "0.2.0",
3+
"version": "0.3.0",
44
"description": "Command-line interface for the Shotstack video rendering API.",
55
"license": "Apache-2.0",
66
"homepage": "https://github.com/shotstack/shotstack-cli",

references/asset-library.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ https://shotstack-assets.s3.amazonaws.com/images/waterfall.jpeg
3030

3131
## Music
3232

33-
Royalty-free tracks from the Unminus library, hosted on Shotstack's CDN. Suitable for `timeline.soundtrack` or as audio assets.
33+
Royalty-free tracks from the Unminus library, hosted on Shotstack's CDN. Use as `audio` assets on a dedicated track. (`timeline.soundtrack` is deprecated — use `audio` with `length: "end"` instead.)
3434

3535
```
3636
https://shotstack-assets.s3.amazonaws.com/music/unminus/white.mp3
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
# Rich-caption guide
1+
# Caption guide
22

33
Word-level animated captions. The most failure-prone asset type — read this fully before composing one.
44

5+
The asset type to use is **`rich-caption`** (Shotstack's name for word-level captions). The deprecated `caption` type still parses but produces inferior output — always use `rich-caption`.
6+
57
## Contents
68

79
- The five named presets (use these verbatim when possible)
@@ -182,7 +184,7 @@ Rich-caption transcribes audio to produce word-level timing. Three ways to sourc
182184
| `src` value | Behaviour |
183185
|---|---|
184186
| `alias://<name>` | Auto-transcribe a referenced audio/video/text-to-speech clip. Set `alias: "<name>"` on the source clip. **Preferred for sync.** |
185-
| Subtitle file URL | Use existing `.srt`, `.vtt`, `.ttml`, or `.dfxp` file. No auto-transcription. |
187+
| Subtitle file URL | Use existing `.srt` or `.vtt` file. No auto-transcription. |
186188
| Audio/video file URL | Auto-transcribe a standalone media file. Use when there's no source clip on the timeline. |
187189

188190
The `alias://` form keeps the captions in sync with the source clip even when you change its `start` or `length`.

references/fonts.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Fonts guide
22

3-
The Shotstack render engine ships a small built-in font set; everything else must be loaded as a custom font via `timeline.fonts[]`. Per the [Shotstack docs](https://shotstack.io/docs/guide/architecting-an-application/rich-text/#custom-fonts), **prefer custom Google Fonts** for predictable rendering and full typographic range.
3+
The Shotstack render engine ships a small built-in font set; everything else must be loaded as a custom font via `timeline.fonts[]`. Per the [Shotstack docs](https://shotstack.io/docs/guide/architecting-an-application/rich-text.md), **prefer custom Google Fonts** for predictable rendering and full typographic range.
44

55
## Contents
66

references/svg.md

Lines changed: 75 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -57,46 +57,97 @@ Standard fill, stroke, and transform attributes work: `fill`, `fill-opacity`, `s
5757

5858
Animated SVGs are not rendered — only the static initial frame is used.
5959

60-
## Worked example: lower-third bar
60+
## Worked example: speech bubble for a testimonial
61+
62+
Speech bubbles are an example "rich-text can't do this" case — a rounded body with a tail pointing at the speaker requires a `<path>`. Pair the SVG bubble with a `rich-text` clip for the quote inside it.
6163

6264
```json
6365
{
6466
"timeline": {
6567
"tracks": [
6668
{
67-
"clips": [{
68-
"asset": {
69-
"type": "rich-text",
70-
"text": "Jane Smith — CEO",
71-
"font": { "family": "JTUSjIg1_i6t8kCHKm45xW5rygbi49c", "size": 48, "color": "#ffffff" }
72-
},
73-
"start": 0, "length": 5,
74-
"offset": { "x": -0.2, "y": -0.35 }
75-
}]
69+
"clips": [
70+
{
71+
"asset": {
72+
"type": "rich-text",
73+
"text": "This changed our workflow completely.",
74+
"font": {
75+
"family": "JTUSjIg1_i6t8kCHKm45xW5rygbi49c",
76+
"size": 44,
77+
"color": "#0f172a",
78+
"weight": 600
79+
},
80+
"align": {
81+
"horizontal": "center",
82+
"vertical": "middle"
83+
}
84+
},
85+
"start": 0.5,
86+
"length": 4,
87+
"width": 540,
88+
"height": 160,
89+
"offset": {
90+
"x": 0.235,
91+
"y": 0.373
92+
}
93+
}
94+
]
7695
},
7796
{
78-
"clips": [{
79-
"asset": {
80-
"type": "svg",
81-
"src": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 1920 200\" width=\"1920\" height=\"200\"><rect x=\"0\" y=\"0\" width=\"1920\" height=\"200\" fill=\"#000000\" fill-opacity=\"0.7\"/></svg>"
82-
},
83-
"start": 0, "length": 5,
84-
"offset": { "x": 0, "y": -0.35 }
85-
}]
97+
"clips": [
98+
{
99+
"asset": {
100+
"type": "svg",
101+
"src": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 600 220\" width=\"600\" height=\"220\"><path d=\"M 40 0 H 560 Q 600 0 600 40 V 140 Q 600 180 560 180 H 200 L 160 220 L 170 180 H 40 Q 0 180 0 140 V 40 Q 0 0 40 0 Z\" fill=\"#ffffff\" stroke=\"#0f172a\" stroke-width=\"4\"/></svg>"
102+
},
103+
"start": 0.5,
104+
"length": 4,
105+
"width": 600,
106+
"height": 220,
107+
"offset": {
108+
"x": 0.235,
109+
"y": 0.356
110+
},
111+
"transition": {
112+
"in": "fade",
113+
"out": "fade"
114+
}
115+
}
116+
]
86117
},
87118
{
88-
"clips": [{
89-
"asset": { "type": "video", "src": "https://shotstack-assets.s3.amazonaws.com/footage/beach.mp4" },
90-
"start": 0, "length": 5
91-
}]
119+
"clips": [
120+
{
121+
"asset": {
122+
"type": "image",
123+
"src": "https://shotstack-assets.s3.amazonaws.com/images/business-man.jpg"
124+
},
125+
"start": 0,
126+
"length": 5,
127+
"fit": "crop"
128+
}
129+
]
92130
}
93131
]
94132
},
95-
"output": { "format": "mp4", "resolution": "1080" }
133+
"output": {
134+
"format": "mp4",
135+
"resolution": "1080"
136+
}
96137
}
97138
```
98139

99-
Track order: text on top, semi-transparent black bar in the middle, video on the bottom.
140+
Track order: rich-text quote on top, SVG bubble in the middle, talking-head video at the bottom. The `<path>` `d` attribute draws a rounded rectangle (40px corner radius via `Q` curves) with a triangular tail at the bottom pointing toward the speaker. `stroke` gives the bubble an outline; `fill="#ffffff"` makes the body opaque.
141+
142+
Position the bubble with `offset` so the tail points at the subject's head. Adjust the path's tail coordinates (`L 160 220 L 170 180`) to point left/right/up depending on where the speaker is in frame.
143+
144+
Other patterns SVG is right for:
145+
146+
- **Tutorial highlight rings**`<circle>` with `fill="none"` and a thick `stroke`, positioned over a UI element.
147+
- **Brand badges** — small geometric lockups using `<rect>`/`<polygon>` plus `transform`.
148+
- **Decorative dividers** — wavy lines or geometric frames between scenes.
149+
150+
If you only need a coloured rectangle behind text, **don't reach for SVG**`rich-text` already supports `background.color`/`opacity`/`borderRadius`. Reserve SVG for shapes rich-text can't produce.
100151

101152
## When to use SVG vs rich-text
102153

references/timeline.md

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Timeline conventions
22

3-
Detailed guide to track layering, the soundtrack vs audio distinction, and `timeline.fonts[]`. Read this when building any non-trivial Edit JSON.
3+
Detailed guide to track layering, background music (via `audio` assets — `timeline.soundtrack` is deprecated), and `timeline.fonts[]`. Read this when building any non-trivial Edit JSON.
44

55
## Contents
66

@@ -17,7 +17,7 @@ The single most counter-intuitive Shotstack convention.
1717

1818
`timeline.tracks` is an array. **The first element is the TOP layer (foreground); the last is the BOTTOM (background).** The order of the JSON array IS the visual stacking order, top-to-bottom.
1919

20-
Per the [official docs](https://shotstack.io/docs/guide/architecting-an-application/guidelines/):
20+
Per the [official docs](https://shotstack.io/docs/guide/getting-started/core-concepts.md):
2121

2222
> Tracks are layered on top of each other in the same order they are added to the array with the top most track layered over the top of those below it.
2323
@@ -72,25 +72,32 @@ If you put the video first and the captions last, the video covers the captions
7272
| Title card on video | Title clip in early track, video in later track |
7373
| Picture-in-picture | PiP video in early track, main video in later track |
7474

75-
## Soundtrack vs audio asset
75+
## Background music: use `audio`, not `timeline.soundtrack`
7676

77-
Prefer `Audio` assets — they support custom timing, keyframes, and effects, and can do everything `timeline.soundtrack` can. Use `timeline.soundtrack` only when you want a single background track spanning the whole edit.
77+
`timeline.soundtrack` is **deprecated**. Always use an `audio` asset on its own track with `length: "end"`. The audio asset path supports keyframes, custom timing, multi-clip mixing, and the full effect set; soundtrack does not.
7878

7979
```json
8080
{
8181
"timeline": {
82-
"soundtrack": {
83-
"src": "https://shotstack-assets.s3.amazonaws.com/music/unminus/palmtrees.mp3",
84-
"effect": "fadeOut"
85-
},
86-
"tracks": [/* ... */]
82+
"tracks": [
83+
/* ...other tracks... */
84+
{
85+
"clips": [{
86+
"asset": {
87+
"type": "audio",
88+
"src": "https://shotstack-assets.s3.amazonaws.com/music/unminus/palmtrees.mp3",
89+
"effect": "fadeOut"
90+
},
91+
"start": 0,
92+
"length": "end"
93+
}]
94+
}
95+
]
8796
}
8897
}
8998
```
9099

91-
`soundtrack.effect` accepts `fadeIn`, `fadeOut`, or `fadeInFadeOut`.
92-
93-
An `audio` clip with `length: "end"` is functionally identical to `timeline.soundtrack`.
100+
`asset.effect` on audio accepts `fadeIn`, `fadeOut`, or `fadeInFadeOut` (same enum the deprecated soundtrack used).
94101

95102
## `timeline.fonts[]` for custom fonts
96103

@@ -127,7 +134,7 @@ See `references/fonts.md` for the full pattern.
127134

128135
## Common track-layout patterns
129136

130-
**Slideshow:** images in one track, soundtrack for music. One image per clip; use `start: "auto"` to chain them.
137+
**Slideshow:** images in one track, an `audio` asset on a separate track with `length: "end"` for the background music. One image per clip; use `start: "auto"` to chain them.
131138

132139
**Voiceover with captions:** captions in track[0], audio in track[1] (with `alias` for the captions to reference), video/image in track[2].
133140

references/troubleshooting.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ You used a font name that isn't in the built-in list and didn't load it via `tim
6161

6262
A `rich-caption` clip without `width`, `height`, and `fit: "none"` defaults to filling the entire output.
6363

64-
**Fix:** use one of the five named presets in `references/rich-caption.md` (Nico, Kai, Kapow, Lovely Little Lychee, Rizz) which include the right dimensions, or set `width`/`height`/`fit: "none"` explicitly on your clip.
64+
**Fix:** use one of the five named presets in `references/caption.md` (Nico, Kai, Kapow, Lovely Little Lychee, Rizz) which include the right dimensions, or set `width`/`height`/`fit: "none"` explicitly on your clip.
6565

6666
## Timeline renders but layers are wrong
6767

0 commit comments

Comments
 (0)