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",