From f30ed21a81fd68edfcbc44127c6550c5c7ffc5bf Mon Sep 17 00:00:00 2001 From: KDR Date: Sat, 18 Jul 2026 20:15:32 -0700 Subject: [PATCH] =?UTF-8?q?Prepare=20for=20tinycloud=200.3.14:=20opt-in=20?= =?UTF-8?q?Open=20Graph=20link=20previews=20(skill=20floor=20=E2=86=92=200?= =?UTF-8?q?.3.14)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 0.3.14 adds opt-in Open Graph link previews so a pasted Cloudglue link unfurls as a real card in Slack, iMessage, Notion, and friends. Binary contract (feature `publish.link.preview.v1`, features 33→34; verbs stay 16): - `publish` and `publish video` gain `--link-preview none|full` - site publish also gains `--preview-title` / `--preview-image` The two visibilities work differently, and the skill docs now teach both: - PUBLIC sites need no flags — their HTML reaches the unfurl bot verbatim, so the card IS whatever `og:*` tags the generated page carries. The skill now tells generators to emit them on every public publish, since regenerating a page without them silently reverts the card to a bare link. - PRIVATE sites and shares are redirected to sign-in before a bot sees anything, so they need `--link-preview full` to opt into a metadata-only stub. Flipping it on an existing site is a settings PATCH with no re-upload (the new `settings-only` action). Every mention carries the privacy caveat: opting in makes the card title, description, and image readable by anyone who fetches the link, so the skill says to ask the user first. Site content, video, tokens, and cookies never leak, and playback stays sign-in gated. Because the skill teaches the new flags, the floor rises to 0.3.14 (`min_version`, `supported_range`, preflight `MIN_VERSION`, and `publish.link.preview.v1` in both required-feature lists — kept in sync, CI diffs them). Following the 0.3.12 floor-raise pattern, this PR should merge only after the CDN's `channels.stable` is 0.3.14. Also bumps the package/plugin/marketplace/skill versions to 0.3.14 and the maintainer command examples in CLAUDE.md; the live-CDN state stays 0.3.13 until the release publishes. Verified: npm test 42/42, shellcheck clean, `claude plugin validate .` passes, and the two required-feature lists are byte-identical. --- .claude-plugin/marketplace.json | 4 +- .claude-plugin/plugin.json | 2 +- CLAUDE.md | 29 ++++++++++---- README.md | 4 +- package.json | 2 +- skills/tinycloud/SKILL.md | 13 +++++++ skills/tinycloud/reference/glossary.md | 10 +++++ skills/tinycloud/reference/verbs.md | 53 +++++++++++++++++++++++++- skills/tinycloud/scripts/preflight.sh | 4 +- skills/tinycloud/tinycloud-skill.json | 7 ++-- 10 files changed, 108 insertions(+), 20 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index c20f4c8..5c71421 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -7,14 +7,14 @@ }, "metadata": { "description": "Deep video and image work for coding agents: analyze, describe, search, caption, clip, detect faces, and publish via the tinycloud CLI.", - "version": "0.3.13" + "version": "0.3.14" }, "plugins": [ { "name": "tinycloud", "source": "./", "description": "Video and image understanding and editing via the tinycloud CLI (Cloudglue): watch/see/extract/search/ask/face over videos and images, captions, clips, collections, plus one-command workflows for sales coaching, blog posts, ad analysis, meeting breakdowns, and YouTube publishing.", - "version": "0.3.13", + "version": "0.3.14", "author": { "name": "Cloudglue" }, "homepage": "https://tinycloud.sh", "repository": "https://github.com/cloudglue/tinycloud", diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 47fee54..c4896b1 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -2,7 +2,7 @@ "name": "tinycloud", "displayName": "Tinycloud Video", "description": "Video and image understanding and editing via the tinycloud CLI (Cloudglue): watch/see/extract/search/ask/face over videos and images, captions, clips, collections, and one-command video workflows.", - "version": "0.3.13", + "version": "0.3.14", "author": { "name": "Cloudglue", "url": "https://cloudglue.dev" }, "homepage": "https://tinycloud.sh", "repository": "https://github.com/cloudglue/tinycloud", diff --git a/CLAUDE.md b/CLAUDE.md index 79c1497..1b861bf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -28,16 +28,16 @@ node --test test/unit.test.mjs # just the unit suite TINYCLOUD_TEST_TARBALL=~/Downloads/tinycloud-darwin-arm64.tar.gz npm test # e2e against a real dist tarball # Contract smoke tests against an installed/extracted binary -TINYCLOUD_CMD=/path/to/tinycloud EXPECTED_VERSION=0.3.13 bash scripts/smoke-test.sh +TINYCLOUD_CMD=/path/to/tinycloud EXPECTED_VERSION=0.3.14 bash scripts/smoke-test.sh # Serve a tarball as a fake CDN (modes: --corrupt, --no-manifest) -node test/fixtures/make-fixture-cdn.mjs --tarball .tar.gz --version 0.3.13 --port 8787 +node test/fixtures/make-fixture-cdn.mjs --tarball .tar.gz --version 0.3.14 --port 8787 TINYCLOUD_DIST_URL=http://127.0.0.1:8787 TINYCLOUD_INSTALL_DIR=$(mktemp -d) node bin/tinycloud.js --version --json TINYCLOUD_DIST_URL=http://127.0.0.1:8787 bash install.sh --install-dir $(mktemp -d)/bin # Release manifest tooling (maintainer) -node scripts/generate-manifest.mjs --version 0.3.13 --from-cdn # build manifest + .sha256 sidecars -node scripts/generate-manifest.mjs --check --version 0.3.13 # verify live CDN matches manifest +node scripts/generate-manifest.mjs --version 0.3.14 --from-cdn # build manifest + .sha256 sidecars +node scripts/generate-manifest.mjs --check --version 0.3.14 # verify live CDN matches manifest # Plugin metadata validation claude plugin validate . @@ -103,7 +103,7 @@ stdout (logs on stderr) with `status`: → exit codes 0/0/2/3/3/0/1. `tinycloud commands --json` is the authoritative flag list — verify doc claims against it, not memory (a doc bug shipped once because `--cached` only exists on watch/see/extract/caption/face/workflow). As -of 0.3.13 there are 16 verbs: `see` +of 0.3.14 there are 16 verbs: `see` (0.3.7+) analyzes an **image** (file-level, JPEG/PNG/WebP — the image counterpart of `watch`) and `extract` also takes an image source (features `see.v1`, `extract.images.v1`); 0.3.8 adds @@ -146,8 +146,23 @@ non-numeric ignored). The speed menu lives in the Cloudglue-served embed script, not the binary — no new verbs, flags, or feature ids (verbs stay 16, features stay 33), the skill floor stays 0.3.12, and already-published sites pick it up without republishing; the binary's embed guidance (skill reference, -kitchen sink, system prompt, publish notes) moved from v6 to v7 wording. The -host-level `profile` verb and the leading global flags `--home`/`--profile` +kitchen sink, system prompt, publish notes) moved from v6 to v7 wording. +0.3.14 adds **opt-in Open Graph link previews** (SDK bumped to 0.7.20; feature +`publish.link.preview.v1`, features 33→34, verbs stay 16): `publish` and +`publish video` take `--link-preview none|full`, and site publish also takes +`--preview-title` / `--preview-image` (validated as an absolute http(s) url +before any upload). Public sites/shares always unfurl — a public site's card +comes from the `og:*` tags in the author's own HTML, which is why the embed +docs now tell generators to emit them on every public publish. Private sites +and shares are redirected to sign-in before an unfurl bot sees anything, so +`full` opts them into a metadata-only stub; that makes the card fields +publicly readable (content, video, tokens, and cookies never leak, and +playback stays sign-in gated), so every doc surface says to ask the user +first. Flipping the setting on an existing site is a settings PATCH with no +re-upload, reported as the new `settings-only` publish action. Because the +skill teaches the flags, the floor was raised to 0.3.14 (the 0.3.12 +floor-raise pattern — the dist PR merges only after CDN `channels.stable` = +0.3.14). The host-level `profile` verb and the leading global flags `--home`/`--profile` (also `$TINYCLOUD_HOME`; 0.3.3+) relocate state and are intentionally absent from `commands --json` — like the launcher's install/update, they're CLI/host concerns, not video operations. diff --git a/README.md b/README.md index aa6656c..720d7aa 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,11 @@ The npm package is a small launcher: on first run it downloads the matching platform distribution from Cloudglue's CDN (cached under `~/.tinycloud/versions//`), verifies its checksum, and execs the real binary. The package version pins the binary version, so -`npx @cloudglue/tinycloud@0.3.13` always runs tinycloud 0.3.13. It also adds two +`npx @cloudglue/tinycloud@0.3.14` always runs tinycloud 0.3.14. It also adds two wrapper commands: ```bash -tinycloud install --version 0.3.13 # pre-download a version +tinycloud install --version 0.3.14 # pre-download a version tinycloud install --latest # install latest stable and pin to it tinycloud update # move to latest stable, prune old versions ``` diff --git a/package.json b/package.json index 51b816d..910089c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cloudglue/tinycloud", - "version": "0.3.13", + "version": "0.3.14", "description": "Agent CLI for deep video and image work, by Cloudglue. Downloads the tinycloud binary on first run.", "bin": { "tinycloud": "bin/tinycloud.js" diff --git a/skills/tinycloud/SKILL.md b/skills/tinycloud/SKILL.md index 78d6b97..607fe25 100644 --- a/skills/tinycloud/SKILL.md +++ b/skills/tinycloud/SKILL.md @@ -214,6 +214,19 @@ Authoring your own recipes: [reference/workflow-authoring.md](reference/workflow (``, ``, ``) rather than hand-rolled players, galleries, or segment-list JS — details in [reference/verbs.md](reference/verbs.md). +- Link previews (0.3.14+): how a pasted link unfurls in Slack/iMessage/Notion. + A **public** site's card comes from the HTML you generate — put `og:title`, + `og:description`, `og:image` (absolute URL — a share's `preview_url` works), + `og:url`, and `twitter:card` in the `` of every public page, or the + link renders as a bare URL (bots don't run JS; never emit + `twitter:player`/`og:video`). A **private** site or share never reaches the + bot, so its card is off by default — opt in with `--link-preview full` + (private sites also take `--preview-title` / `--preview-image`; a private + share reuses its own title, description, and thumbnail). ⚠️ Ask the user + before turning it on: it makes those card fields readable by anyone with the + link (content and playback stay sign-in gated), and platforms cache per exact + URL, so turning it back off does not retract posted cards. Details in + [reference/verbs.md](reference/verbs.md). - Live-API discovery components (0.3.6+): the same embed script also defines collection-scoped, **private-site-only** elements that let viewers search/chat inside a published site — ``, ``, ``, diff --git a/skills/tinycloud/reference/glossary.md b/skills/tinycloud/reference/glossary.md index 5997941..6f56176 100644 --- a/skills/tinycloud/reference/glossary.md +++ b/skills/tinycloud/reference/glossary.md @@ -130,6 +130,16 @@ connector?" or an envelope field needs explaining. events re-dispatched on the element) for custom site HTML, and plays standalone or inside the container components (``, ``, ``) — see reference/verbs.md. +- **Link preview** — the Open Graph card a platform (Slack, iMessage, Notion, + Discord) renders when someone pastes a site or share link. Public sites and + shares always preview: a public site's card comes from the `og:*` tags in + the HTML you generate, a public share's from its title/description/thumbnail. + Private sites and shares are redirected to sign-in before an unfurl bot sees + anything, so they render a bare "Sign in" card unless opted in with + `--link-preview full` (0.3.14+, feature `publish.link.preview.v1`; private + sites also take `--preview-title` / `--preview-image`). Opting in makes those + card fields publicly readable — content and playback stay sign-in gated — so + ask the user first. See reference/verbs.md. - **Discovery components (live API, 0.3.6+)** — the same embed script also defines four collection-scoped, **private-site-only** elements that let a viewer search or chat inside a published site and play results inline via diff --git a/skills/tinycloud/reference/verbs.md b/skills/tinycloud/reference/verbs.md index c6d3df3..46bb999 100644 --- a/skills/tinycloud/reference/verbs.md +++ b/skills/tinycloud/reference/verbs.md @@ -319,7 +319,8 @@ tinycloud workflow [--param k=v] [--segment ] [--out ] ```bash tinycloud publish [--name ] - [--visibility public|private] --json + [--visibility public|private] + [--link-preview none|full] [--preview-title ] [--preview-image ] --json tinycloud publish list --json # sites for this account, with URLs tinycloud publish unpublish --json # site_id, site name, or the --name label ``` @@ -343,11 +344,49 @@ site itself gets a generated name (e.g. `young-fire-2486`) shown by `publish list`. `unpublish` resolves any of: the `site_id` UUID, the generated site name, or your `--name` label. +**Link previews (0.3.14+, `publish.link.preview.v1`)** — how a pasted link +unfurls in Slack, iMessage, Notion, Discord. The two visibilities work +completely differently: + +- **Public site** — needs no flags. Its HTML reaches the unfurl bot verbatim, + so the card *is* whatever Open Graph tags the page carries. **Emit them in + the `` of every public page you generate** — `og:title`, + `og:description`, `og:image` (an **absolute** URL — a share's `preview_url` + works), `og:url`, `twitter:card` — because a regenerated page without them + silently reverts the card to a bare link. Bots don't run JavaScript, so the + tags must be in the static HTML. Never emit `twitter:player` or `og:video` + (unfurlers ignore them for our domains and can degrade the card). +- **Private site** — the edge gate redirects bots to sign-in before any HTML + is served, so OG tags in the page are never seen and the link unfurls as a + bare "Sign in" card. Opt in instead with `--link-preview full`, plus + `--preview-title` (falls back to the generated site name) and + `--preview-image` (**must be publicly fetchable** — site assets sit behind + the same gate the bot can't pass, so use a share's `preview_url`; prefer a + long-lived unsigned URL, since unfurl caches hold the exact URL). The card + description is the site description. Pass `""` to either flag to clear it. + +`--link-preview` is flippable on an **existing** site without republishing +content: the run reports `action: "settings-only"` (a settings PATCH, no +re-upload, no new version). The state comes back as `data.link_preview` (plus +`data.preview_title` / `data.preview_image_url`) and shows in `publish list` +rows as `link-preview=full`. `--preview-title`/`--preview-image` require +`--link-preview full`, and a non-absolute `--preview-image` errors before any +upload. + +⚠️ **Ask the user before opting a private site in.** `--link-preview full` +makes the card title, the site description, and the card image readable by +anyone who fetches the link — the user-agent check only *routes*; the setting +is the security boundary. Nothing else leaks (no site content, video, tokens, +or cookies; the site stays sign-in gated). Platforms cache per exact URL and +rehost the image, so flipping back to `none` stops *future* unfurls but does +not retract already-posted cards. + ### publish video — share a video ```bash tinycloud publish video [--visibility public|private] # default public - [--name ] [--segment-id <id>] [--clip-start <s> --clip-end <e> [--clip-only]] --json + [--name <title>] [--segment-id <id>] [--clip-start <s> --clip-end <e> [--clip-only]] + [--link-preview none|full] --json tinycloud publish video list [--in <source>] [--visibility public|private] --json tinycloud publish video unpublish <share-id | source> --json # --visibility disambiguates ``` @@ -385,6 +424,16 @@ feature id. `&clip=hard` and fall back to the soft window. It shapes the viewing experience, not access control — anyone with the bare `share_url` still has the whole video; restrict access with `--visibility private`. +- Link previews (0.3.14+): a PUBLIC share page always unfurls in Slack with a + card built from its title, description, and thumbnail. A PRIVATE share is + redirected to sign-in before the bot sees anything, so it unfurls as a bare + "Sign in" card unless you pass `--link-preview full`, which serves bots a + metadata-only stub built from those same three fields. It comes back as + `data.share.link_preview`, and re-running flips it on an existing share + (a PATCH — no new share). Playback stays sign-in gated in every mode. + ⚠️ Ask the user first: `full` makes the share's title, description, and + thumbnail readable by anyone who fetches the link, and platforms cache per + exact URL, so turning it back off does not retract already-posted cards. When generating custom site HTML around a `<cg-video>` embed, use the component's built-ins instead of reinventing them. It defaults to a diff --git a/skills/tinycloud/scripts/preflight.sh b/skills/tinycloud/scripts/preflight.sh index eba4ba4..ff7d75a 100755 --- a/skills/tinycloud/scripts/preflight.sh +++ b/skills/tinycloud/scripts/preflight.sh @@ -9,11 +9,11 @@ set -u # Mirror tinycloud-skill.json: min_version / supported_range upper bound # (CI diffs these against the manifest). -MIN_VERSION="0.3.12" +MIN_VERSION="0.3.14" MAX_VERSION_EXCLUSIVE="0.4.0" INSTALL_CMD='curl -fsSL https://app.cloudglue.dev/tinycloud.sh | bash' # Kept in sync with ../tinycloud-skill.json required_features (CI diffs them). -REQUIRED_FEATURES="envelope.v1 watch.v1 watch.speech.v1 see.v1 extract.v1 extract.images.v1 caption.v1 search.v1 probe.v1 ask.v1 clip.v1 grab.v1 face.v1 jobs.v1 library.collections.v1 library.collections.create.v1 library.collections.mutate.v1 library.collections.entities.v1 library.sync.url.v1 workflow.v1 publish.v1 publish.manage.v1 publish.video.v1 setup.v1" +REQUIRED_FEATURES="envelope.v1 watch.v1 watch.speech.v1 see.v1 extract.v1 extract.images.v1 caption.v1 search.v1 probe.v1 ask.v1 clip.v1 grab.v1 face.v1 jobs.v1 library.collections.v1 library.collections.create.v1 library.collections.mutate.v1 library.collections.entities.v1 library.sync.url.v1 workflow.v1 publish.v1 publish.manage.v1 publish.video.v1 publish.link.preview.v1 setup.v1" # 1) Binary present and responsive? if ! command -v tinycloud >/dev/null 2>&1; then diff --git a/skills/tinycloud/tinycloud-skill.json b/skills/tinycloud/tinycloud-skill.json index 8d128ee..94cdfa7 100644 --- a/skills/tinycloud/tinycloud-skill.json +++ b/skills/tinycloud/tinycloud-skill.json @@ -1,8 +1,8 @@ { - "skill_version": "0.3.13", + "skill_version": "0.3.14", "tinycloud": { - "min_version": "0.3.12", - "supported_range": ">=0.3.12 <0.4.0", + "min_version": "0.3.14", + "supported_range": ">=0.3.14 <0.4.0", "required_features": [ "envelope.v1", "watch.v1", @@ -27,6 +27,7 @@ "publish.v1", "publish.manage.v1", "publish.video.v1", + "publish.link.preview.v1", "setup.v1" ], "envelope_schema": "1",