@cloudglue/tinycloud 0.3.1 → 0.3.3

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 CHANGED
@@ -16,11 +16,11 @@ The npm package is a small launcher: on first run it downloads the matching
16
16
  platform distribution from Cloudglue's CDN (cached under
17
17
  `~/.tinycloud/versions/<version>/`), verifies its checksum, and execs the real
18
18
  binary. The package version pins the binary version, so
19
- `npx @cloudglue/tinycloud@0.3.0` always runs tinycloud 0.3.0. It also adds two
19
+ `npx @cloudglue/tinycloud@0.3.3` always runs tinycloud 0.3.3. It also adds two
20
20
  wrapper commands:
21
21
 
22
22
  ```bash
23
- tinycloud install --version 0.3.0 # pre-download a version
23
+ tinycloud install --version 0.3.3 # pre-download a version
24
24
  tinycloud install --latest # install latest stable and pin to it
25
25
  tinycloud update # move to latest stable, prune old versions
26
26
  ```
@@ -50,14 +50,21 @@ This repo also distributes agent skills that teach coding agents (Claude
50
50
  Code, Codex, and anything else following the
51
51
  [Agent Skills](https://agentskills.io) standard) to drive the tinycloud CLI.
52
52
 
53
- **One command** (detects your agent and installs the bundled skills):
53
+ **One command** (in a terminal it prompts you to pick the target agents):
54
54
 
55
55
  ```bash
56
- npx @cloudglue/tinycloud skills install # project-level (.claude/skills, .agents/skills)
57
- npx @cloudglue/tinycloud skills install --global # ~/.claude/skills (all your projects)
56
+ npx @cloudglue/tinycloud skills install # menu: claude-code, agents, codex, cursor
57
+ npx @cloudglue/tinycloud skills install --harness cursor,codex # pick targets non-interactively
58
+ npx @cloudglue/tinycloud skills install --global # ~/.claude/skills (Claude Code only)
58
59
  npx @cloudglue/tinycloud skills install --skill tinycloud,blog-post # just some
59
60
  ```
60
61
 
62
+ Each agent reads skills from its own `<dir>/skills`: `claude-code` → `.claude`,
63
+ `agents` → `.agents` (the universal [Agent Skills](https://agentskills.io)
64
+ layout), `codex` → `.codex`, `cursor` → `.cursor`. The menu preselects dirs
65
+ that already exist; piped/CI runs (or `--yes`) skip it and install into
66
+ whichever dirs exist, defaulting to `.claude/skills` when none do.
67
+
61
68
  **Claude Code** (as a plugin):
62
69
 
63
70
  ```text
@@ -91,14 +98,114 @@ To give every agent session in a repo the same skills, commit them:
91
98
 
92
99
  ```bash
93
100
  cd your-project
94
- npx @cloudglue/tinycloud skills install # writes .claude/skills/ (and .agents/skills/ if present)
95
- git add .claude .agents 2>/dev/null; git commit -m "Add tinycloud agent skills"
101
+ npx @cloudglue/tinycloud skills install --harness claude-code,agents # or pick from the menu
102
+ git add .claude .agents .codex .cursor 2>/dev/null; git commit -m "Add tinycloud agent skills"
96
103
  ```
97
104
 
98
105
  Optionally add a line to your project's `CLAUDE.md` so agents reach for them:
99
106
  `Video work (analysis, captions, clips, workflows) goes through the tinycloud
100
107
  CLI — see the tinycloud skill; run tinycloud-init if the CLI isn't set up.`
101
108
 
109
+ ## Commands
110
+
111
+ Cloud commands run through your configured Cloudglue API key (billed per the
112
+ [rate card](https://app.cloudglue.dev/home/billing/rate-card)); local and
113
+ network commands are free. Every command prints a JSON envelope on stdout (logs
114
+ go to stderr) — pass `--json`.
115
+
116
+ | Command | What it does |
117
+ |---|---|
118
+ | `watch` | Analyze a video → reusable cached context + Cloudglue-ready ref |
119
+ | `extract` | Pull structured facts, entities, or moments (free-form or JSON-schema) |
120
+ | `caption` | Subtitles and transcripts (SRT/VTT/ASS) |
121
+ | `search` | Keyword search over cached video context |
122
+ | `probe` | Semantic moment/video search over a Cloudglue scope |
123
+ | `ask` | Grounded Q&A over one or more videos |
124
+ | `clip` | ffmpeg-backed cut, thumbs, stitch, transcode, burn, split, audio, info |
125
+ | `grab` | Download a remote video (YouTube, TikTok, Loom, direct) |
126
+ | `library` | Browse and sync Cloudglue collections and connectors |
127
+ | `jobs` | Poll, wait on, or forget async jobs |
128
+ | `workflow` | Run packaged pipeline recipes (see below) |
129
+ | `publish` | Publish HTML artifacts as Cloudglue Sites; share videos |
130
+ | `setup` | Configure the Cloudglue API key and service connections |
131
+
132
+ A few common invocations:
133
+
134
+ ```bash
135
+ # Analyze a video into reusable, cached context + a Cloudglue-ready ref
136
+ tinycloud watch ./demo.mp4 --json
137
+ # Pull structured findings (free-form query here; pass --schema for a fixed shape)
138
+ tinycloud extract "key moments with timestamps" ./demo.mp4 --json
139
+ # Subtitles plus a markdown transcript
140
+ tinycloud caption ./demo.mp4 --format srt --transcript --json
141
+ # Trim a clip locally — no upload, ffmpeg-backed
142
+ tinycloud clip cut ./demo.mp4 --start 12 --end 28 -o clip.mp4 --json
143
+ # Grounded Q&A over one or more videos
144
+ tinycloud ask "What objections came up?" --in ./demo.mp4 --json
145
+ ```
146
+
147
+ `tinycloud commands --json` is the authoritative, machine-readable list of
148
+ every command and flag. Full per-verb flags and cost classes:
149
+ [skills/tinycloud/reference/verbs.md](skills/tinycloud/reference/verbs.md).
150
+ The envelope contract — statuses (`ready`, `pending`, `needs_credentials`, …)
151
+ and exit codes:
152
+ [skills/tinycloud/reference/envelope.md](skills/tinycloud/reference/envelope.md).
153
+
154
+ ### Global flags & profiles (0.3.3+)
155
+
156
+ A few options are host-level — they isolate state rather than run a video
157
+ operation, so they go *before* the verb and don't appear in `commands --json`:
158
+
159
+ - `--home <dir>` (or `$TINYCLOUD_HOME`) — run against an isolated state home
160
+ (config, sessions, cache, jobs, artifacts, skills) instead of `~/.tinycloud`.
161
+ - `--profile <name>` — use a named profile's home, so multiple accounts or
162
+ installs run side by side without cross-contamination.
163
+
164
+ ```bash
165
+ tinycloud --home ./.tc watch ./demo.mp4 --json # isolated state for this repo
166
+ tinycloud profile list # profiles and their homes
167
+ tinycloud profile create work --default # create one and make it default
168
+ tinycloud profile create staging --copy-from work # clone an existing home
169
+ tinycloud --profile work ask "..." --in ./demo.mp4 --json
170
+ ```
171
+
172
+ Sessions are scoped per project (keyed by the git root), the agent takes a
173
+ `--skills <list>` allowlist alongside `--tools`, and a project-local
174
+ `.tinycloud/config.json` can pin tool/skill allowlists and an output base.
175
+ Details:
176
+ [skills/tinycloud/reference/setup.md](skills/tinycloud/reference/setup.md).
177
+
178
+ ## Workflows
179
+
180
+ Workflows are packaged, repeatable pipelines that run with a single command
181
+ and write their outputs into a run directory under
182
+ `./tinycloud-output/runs/<run_id>/`. The five flagship recipes — each with a
183
+ matching agent skill — are:
184
+
185
+ | Workflow | Turns a video into |
186
+ |---|---|
187
+ | `sales-coaching` | Coaching dashboard — call scores, speech metrics, objections |
188
+ | `blog-post` | Rich blog post — sections, thumbnails, takeaways |
189
+ | `ad-analysis` | Ad breakdown — shot timeline, hook, pacing, CTA |
190
+ | `meeting-breakdown` | Speaker timeline, chapter summaries, action items |
191
+ | `youtube-publish` | YouTube title, description, chapters, tags, subtitles |
192
+
193
+ ```bash
194
+ tinycloud workflow list --json # all available recipes
195
+ tinycloud workflow sales-coaching ./call.mp4 --json # run one
196
+ tinycloud workflow plan blog-post ./demo.mp4 --json # preview steps (free, no side effects)
197
+ tinycloud workflow validate ad-analysis --json # check a recipe (or a path)
198
+ ```
199
+
200
+ The final envelope reports `data.status`
201
+ (`completed | partial | failed | paused`), `data.outputs` (named outputs such
202
+ as `outputs.html`), and `data.artifacts[].path`. The five above each have a
203
+ matching agent skill (see the table), so coding agents can run them directly;
204
+ `tinycloud workflow list` shows the full set, including building blocks like
205
+ `summary` and `clip-highlights`. Author your own recipes:
206
+ [skills/tinycloud/reference/workflow-authoring.md](skills/tinycloud/reference/workflow-authoring.md)
207
+ (or scaffold one with the `tinycloud-skill-creator` skill).
208
+
102
209
  ## License
103
210
 
104
211
  © Aviary Inc. (d/b/a Cloudglue). All rights reserved. Use is subject to [Aviary Inc. Terms of Service](https://cloudglue.dev/terms).
package/lib/skills.js CHANGED
@@ -3,6 +3,7 @@
3
3
  const fs = require("node:fs");
4
4
  const os = require("node:os");
5
5
  const path = require("node:path");
6
+ const readline = require("node:readline");
6
7
 
7
8
  // Agent skills bundled with this package (the npm tarball includes skills/).
8
9
  function bundledSkillsDir() {
@@ -19,28 +20,95 @@ function listBundledSkills() {
19
20
  }
20
21
 
21
22
  /**
22
- * Pick install targets. Each harness keeps skills in its own directory; we
23
- * detect harnesses by their config dir to avoid littering projects that
24
- * don't use them.
23
+ * Known agent harnesses. Each keeps skills in its own <configDir>/skills dir,
24
+ * all using the SKILL.md layout. `id` is both the menu label and the
25
+ * --harness token; `aliases` are extra accepted tokens.
25
26
  * claude-code: <project>/.claude/skills (global: ~/.claude/skills)
26
- * codex: <project>/.agents/skills (agentskills.io layout)
27
+ * agents: <project>/.agents/skills (universal agentskills.io layout)
28
+ * codex: <project>/.codex/skills
29
+ * cursor: <project>/.cursor/skills
27
30
  */
28
- function resolveTargets({ global: isGlobal, dir, cwd = process.cwd() }) {
31
+ const HARNESSES = [
32
+ { id: "claude-code", aliases: ["claude"], configDir: ".claude" },
33
+ { id: "agents", aliases: [], configDir: ".agents" },
34
+ { id: "codex", aliases: [], configDir: ".codex" },
35
+ { id: "cursor", aliases: [], configDir: ".cursor" },
36
+ ];
37
+
38
+ function harnessIds() {
39
+ return HARNESSES.map((h) => h.id);
40
+ }
41
+
42
+ // Resolve a --harness token (id or alias, case-insensitive) to its entry.
43
+ function findHarness(token) {
44
+ const t = String(token).trim().toLowerCase();
45
+ return HARNESSES.find((h) => h.id === t || h.aliases.includes(t));
46
+ }
47
+
48
+ function harnessTarget(h, cwd) {
49
+ return { name: h.id, dir: path.join(cwd, h.configDir, "skills") };
50
+ }
51
+
52
+ function detectHarnesses(cwd) {
53
+ return HARNESSES.filter((h) => fs.existsSync(path.join(cwd, h.configDir)));
54
+ }
55
+
56
+ /**
57
+ * Pick install targets from flags/detection only — no prompting or I/O beyond
58
+ * existence checks, so it stays deterministic for tests. The interactive menu
59
+ * lives in promptForTargets(). Precedence: --dir > --global > --harness >
60
+ * detection (and .claude when nothing is detected).
61
+ */
62
+ function resolveTargets({ global: isGlobal, dir, harness, cwd = process.cwd() }) {
29
63
  if (dir) return [{ name: "custom", dir: path.resolve(dir) }];
30
64
  if (isGlobal) return [{ name: "claude-code (global)", dir: path.join(os.homedir(), ".claude", "skills") }];
31
65
 
32
- const targets = [];
33
- if (fs.existsSync(path.join(cwd, ".claude"))) {
34
- targets.push({ name: "claude-code", dir: path.join(cwd, ".claude", "skills") });
35
- }
36
- if (fs.existsSync(path.join(cwd, ".agents"))) {
37
- targets.push({ name: "codex", dir: path.join(cwd, ".agents", "skills") });
66
+ if (harness && harness.length) {
67
+ return harness.map((token) => {
68
+ const h = findHarness(token);
69
+ if (!h) throw new Error(`Unknown harness: ${token}. Valid: ${harnessIds().join(", ")}`);
70
+ return harnessTarget(h, cwd);
71
+ });
38
72
  }
39
- if (targets.length === 0) {
40
- // No harness detected: default to claude-code project layout.
41
- targets.push({ name: "claude-code", dir: path.join(cwd, ".claude", "skills") });
73
+
74
+ const detected = detectHarnesses(cwd);
75
+ if (detected.length) return detected.map((h) => harnessTarget(h, cwd));
76
+ // No harness detected: default to claude-code project layout.
77
+ return [harnessTarget(HARNESSES[0], cwd)];
78
+ }
79
+
80
+ /**
81
+ * Interactive picker: list the four harnesses (detected ones preselected) and
82
+ * read a comma-separated choice. Streams are injectable for testing; empty or
83
+ * unparseable input falls back to the preselection.
84
+ */
85
+ async function promptForTargets({ cwd = process.cwd(), input = process.stdin, output = process.stdout } = {}) {
86
+ const detected = new Set(detectHarnesses(cwd).map((h) => h.id));
87
+ const rows = HARNESSES.map((h, i) => ({ n: i + 1, h, detected: detected.has(h.id) }));
88
+ const preselected = rows.some((r) => r.detected)
89
+ ? rows.filter((r) => r.detected).map((r) => r.n)
90
+ : [1];
91
+
92
+ output.write("Which agents should get the skills?\n");
93
+ for (const r of rows) {
94
+ const mark = preselected.includes(r.n) ? "x" : " ";
95
+ const tag = r.detected ? " (detected)" : "";
96
+ output.write(` ${r.n}) [${mark}] ${r.h.id.padEnd(11)} ${path.join(r.h.configDir, "skills")}${tag}\n`);
42
97
  }
43
- return targets;
98
+
99
+ const rl = readline.createInterface({ input, output });
100
+ const answer = await new Promise((resolve) => {
101
+ rl.question(`Enter numbers (comma-separated) [${preselected.join(",")}]: `, resolve);
102
+ });
103
+ rl.close();
104
+
105
+ let nums = answer
106
+ .split(",")
107
+ .map((s) => parseInt(s.trim(), 10))
108
+ .filter((n) => Number.isInteger(n) && n >= 1 && n <= rows.length);
109
+ if (!nums.length) nums = preselected;
110
+
111
+ return rows.filter((r) => nums.includes(r.n)).map((r) => harnessTarget(r.h, cwd));
44
112
  }
45
113
 
46
114
  function installSkills({ skills, targets, force }) {
@@ -70,12 +138,17 @@ const USAGE = `Usage: tinycloud skills <list|install> [options]
70
138
 
71
139
  Install options:
72
140
  --skill <a,b,...> Only these skills (default: all)
73
- --global Install to ~/.claude/skills instead of the project
141
+ --harness <a,b,...> Install into these agents: claude-code, agents, codex, cursor
142
+ (default: pick from a menu in a terminal; auto-detect otherwise)
143
+ --global Install to ~/.claude/skills instead of the project (Claude Code only)
74
144
  --dir <path> Install to an explicit directory
145
+ --yes, -y Skip the interactive menu; use auto-detection
75
146
  --force Overwrite skills that are already installed
76
147
 
77
- Detection: a project .claude/ dir targets Claude Code (.claude/skills),
78
- a .agents/ dir targets Codex (.agents/skills); both when both exist.
148
+ Each harness holds skills under <dir>/skills: claude-code .claude, agents → .agents
149
+ (universal agentskills.io layout), codex .codex, cursor .cursor. Run in a terminal
150
+ with no target and you'll be prompted to pick (detected dirs preselected); piped/CI runs
151
+ install into whichever dirs exist, or .claude when none do.
79
152
  `;
80
153
 
81
154
  async function cmdSkills(args) {
@@ -97,7 +170,9 @@ async function cmdSkills(args) {
97
170
  let wanted = available;
98
171
  let isGlobal = false;
99
172
  let force = false;
173
+ let yes = false;
100
174
  let dir;
175
+ let harness;
101
176
  for (let i = 1; i < args.length; i++) {
102
177
  if (args[i] === "--skill" && args[i + 1]) {
103
178
  const names = args[++i].split(",").map((s) => s.trim()).filter(Boolean);
@@ -106,8 +181,15 @@ async function cmdSkills(args) {
106
181
  throw new Error(`Unknown skill(s): ${unknown.join(", ")}. Available: ${available.join(", ")}`);
107
182
  }
108
183
  wanted = names;
184
+ } else if (args[i] === "--harness" && args[i + 1]) {
185
+ harness = args[++i].split(",").map((s) => s.trim()).filter(Boolean);
186
+ const unknown = harness.filter((id) => !findHarness(id));
187
+ if (unknown.length) {
188
+ throw new Error(`Unknown harness(es): ${unknown.join(", ")}. Valid: ${harnessIds().join(", ")}`);
189
+ }
109
190
  } else if (args[i] === "--global") isGlobal = true;
110
191
  else if (args[i] === "--force") force = true;
192
+ else if (args[i] === "--yes" || args[i] === "-y") yes = true;
111
193
  else if (args[i] === "--dir" && args[i + 1]) dir = args[++i];
112
194
  else throw new Error(`Unknown install option: ${args[i]}\n${USAGE}`);
113
195
  }
@@ -116,11 +198,24 @@ async function cmdSkills(args) {
116
198
  throw new Error("No bundled skills found in this package installation");
117
199
  }
118
200
 
119
- const targets = resolveTargets({ global: isGlobal, dir });
201
+ const explicit = dir || isGlobal || (harness && harness.length);
202
+ const targets =
203
+ !explicit && !yes && process.stdin.isTTY && process.stdout.isTTY
204
+ ? await promptForTargets({})
205
+ : resolveTargets({ global: isGlobal, dir, harness });
206
+
120
207
  const results = installSkills({ skills: wanted, targets, force });
121
208
  for (const r of results) console.log(`${r.status === "installed" ? "✓" : "-"} ${r.skill} → ${r.dir} [${r.status}]`);
122
209
  const installed = results.filter((r) => r.status === "installed").length;
123
210
  console.log(`\n${installed} skill(s) installed${installed ? ". Restart your agent session to pick them up." : "."}`);
124
211
  }
125
212
 
126
- module.exports = { cmdSkills, resolveTargets, installSkills, listBundledSkills, bundledSkillsDir };
213
+ module.exports = {
214
+ cmdSkills,
215
+ resolveTargets,
216
+ promptForTargets,
217
+ installSkills,
218
+ listBundledSkills,
219
+ bundledSkillsDir,
220
+ HARNESSES,
221
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudglue/tinycloud",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Agent CLI for deep video work, by Cloudglue. Downloads the tinycloud binary on first run.",
5
5
  "bin": {
6
6
  "tinycloud": "bin/tinycloud.js"
@@ -91,6 +91,7 @@ tinycloud clip burn ./demo.mp4 --subtitle-file ./captions/demo.srt -o ./out.mp4
91
91
 
92
92
  # Remote videos, collections, async jobs
93
93
  tinycloud grab https://youtu.be/<id> -o ./tinycloud-output/grabbed/ --json
94
+ tinycloud library connectors sync https://example.com/clip.mp4 --json # public URL → Cloudglue file (not YouTube — use grab)
94
95
  tinycloud library collections list --json
95
96
  tinycloud watch ./long.mp4 --background --json # returns pending + meta.job_id
96
97
  tinycloud jobs wait <job-id> --timeout 120s --json
@@ -105,6 +106,16 @@ tinycloud publish video ./demo.mp4 --visibility public --json
105
106
 
106
107
  Per-verb details and all flags: [reference/verbs.md](reference/verbs.md).
107
108
  Multi-video batching and pipe semantics: [reference/pipelines.md](reference/pipelines.md).
109
+ Evaluating a video the host project's code rendered (render → evaluate →
110
+ edit → rerender): the render-review loop in
111
+ [reference/pipelines.md](reference/pipelines.md).
112
+
113
+ Isolation & scope (0.3.3+): `--home <dir>` / `$TINYCLOUD_HOME` and
114
+ `--profile <name>` are leading flags (before the verb) that run against an
115
+ isolated state home; the agent also takes `--skills <list>` alongside
116
+ `--tools <list>`, and a project-local `.tinycloud/config.json` can pin those
117
+ allowlists and an output base. Sessions are scoped per project. Details:
118
+ [reference/setup.md](reference/setup.md).
108
119
 
109
120
  ## 3. Workflows (packaged recipes)
110
121
 
@@ -159,12 +170,14 @@ Authoring your own recipes: [reference/workflow-authoring.md](reference/workflow
159
170
  `data.embed_snippet` (`<cg-video>`), which only plays on a private site of
160
171
  the same account. When writing HTML around an embed, use the component's
161
172
  built-ins (`autoplay`+`muted`, `loop`, `start-time`, `exclusive`; JS
162
- `playSegment(start, end?)`) rather than hand-rolled players — details in
173
+ `playSegment(start, end?)`) and the container components
174
+ (`<cg-playlist>`, `<cg-grid>`, `<cg-chapters>`) rather than hand-rolled
175
+ players, galleries, or segment-list JS — details in
163
176
  [reference/verbs.md](reference/verbs.md).
164
177
 
165
178
  ## 5. Reference (load on demand)
166
179
 
167
- - [reference/setup.md](reference/setup.md) — install, credentials, env vars, preflight details
180
+ - [reference/setup.md](reference/setup.md) — install, credentials, env vars, preflight, profiles & project scope
168
181
  - [reference/verbs.md](reference/verbs.md) — every verb, flag, and cost class
169
182
  - [reference/envelope.md](reference/envelope.md) — full envelope schema, statuses, error codes, exit codes
170
183
  - [reference/pipelines.md](reference/pipelines.md) — pipes, batching, jobs, cache/spend-control flags
@@ -59,7 +59,11 @@ the exit code alone.
59
59
  `needs_download`, `visual_analysis_requires_download`, `upstream`.
60
60
 
61
61
  `upstream` on a piped command means the upstream envelope was not `ready` —
62
- fix the upstream failure, don't retry downstream.
62
+ fix the upstream failure, don't retry downstream. `upstream` from a cloud
63
+ call itself is a Cloudglue API failure; when it's a request deadline
64
+ (retryable, message names `TINYCLOUD_HTTP_TIMEOUT_MS` /
65
+ `TINYCLOUD_UPLOAD_TIMEOUT_MS`), the server may still be processing — retry
66
+ or raise the knob.
63
67
 
64
68
  ## JSON vs JSONL vs text
65
69
 
@@ -48,7 +48,9 @@ connector?" or an envelope field needs explaining.
48
48
  mirrored in Cloudglue, so later `extract`/`ask`/`search` reuse it instead
49
49
  of re-analyzing.
50
50
  - **Segmentation** — how a video is split for analysis: `chapters`
51
- (semantic), `shots` (visual cuts), `uniform:<seconds>` (fixed windows).
51
+ (semantic), `shots` (visual cuts; bounds tunable via
52
+ `--shot-min-seconds`/`--shot-max-seconds`, sub-second min allowed),
53
+ `uniform:<seconds>` (fixed windows).
52
54
  - **Cache layers** — `meta.cache` reports `identity` (is this the same file
53
55
  we've seen?) and `enrichment` (analysis results) as
54
56
  `hit | miss | written | skipped`.
@@ -83,5 +85,20 @@ connector?" or an envelope field needs explaining.
83
85
  private published site of the same account. The embed has playback
84
86
  attributes (`autoplay`+`muted`, `loop`, `start-time`, `poster`,
85
87
  `accent-color`, `exclusive`) and a JS API (`playSegment`, `seekTo`, media
86
- events re-dispatched on the element) for custom site HTML see
87
- reference/verbs.md.
88
+ events re-dispatched on the element) for custom site HTML, and plays
89
+ standalone or inside the container components (`<cg-playlist>`,
90
+ `<cg-grid>`, `<cg-chapters>`) — see reference/verbs.md.
91
+
92
+ ## State and isolation (0.3.3+)
93
+
94
+ - **Home** — the directory holding all tinycloud state for a run: config,
95
+ sessions, cache, jobs, artifacts, and skills. Default `~/.tinycloud`;
96
+ relocate it with `--home <dir>` or `$TINYCLOUD_HOME`.
97
+ - **Profile** — a named, fully isolated home, selected with `--profile <name>`
98
+ and managed by `tinycloud profile list|show|create|use|remove`. Lets multiple
99
+ accounts or installs run side by side without cross-contamination. (Distinct
100
+ from `watch --profile`, which selects an analysis profile.)
101
+ - **Project scope** — sessions and capabilities keyed to a project (its git
102
+ root). Sessions live under `<home>/projects/<project-key>/sessions`, and a
103
+ project-local `.tinycloud/config.json` can pin tool/skill allowlists and an
104
+ output base — precedence is CLI flags > project config > global config.
@@ -102,3 +102,50 @@ tinycloud watch ./talk.mp4 --json \
102
102
  | tinycloud extract "the three strongest quotes with timestamps" --json \
103
103
  | tinycloud clip cut --from-findings -o ./tinycloud-output/quotes/ --json
104
104
  ```
105
+
106
+ ## Render-review loop (videos your code generates)
107
+
108
+ When the host project's code produces a video (Remotion, Manim, ffmpeg
109
+ filter graphs, slide-render pipelines, recorded E2E runs), don't stop at a
110
+ successful render exit code — a clean render is not a good video. Use
111
+ tinycloud as the eyes in the edit loop:
112
+
113
+ ```bash
114
+ # 1. Render with the project's own tooling (example: Remotion).
115
+ npx remotion render Promo out/promo.mp4
116
+
117
+ # 2. Evaluate the result.
118
+ tinycloud watch ./out/promo.mp4 --segment shots --json \
119
+ | tinycloud extract "Evaluate pacing, text readability and clipping, scene \
120
+ continuity, and audio/visual sync. Return timestamped findings with \
121
+ severity and concrete edit instructions." --json
122
+
123
+ # 3. Map each finding's timestamp to code (frame = seconds x fps for
124
+ # frame-based renderers), apply fixes, rerender to the SAME path.
125
+
126
+ # 4. Re-evaluate, feeding the previous findings back so the model confirms fixes.
127
+ tinycloud watch ./out/promo.mp4 --segment shots --json \
128
+ | tinycloud extract "Re-evaluate this render. Previous findings: <summarize \
129
+ prior findings>. Confirm whether each is resolved and report any new \
130
+ issues." --json
131
+ ```
132
+
133
+ Loop rules:
134
+
135
+ - Stop when findings are empty or low-severity only; then report done with
136
+ the final findings summary. "Done" means evaluated, not rendered.
137
+ - Hunting sub-second flash frames or rapid cuts? Tighten the shot pass with
138
+ `--shot-min-seconds 0.6` (fractional values allowed);
139
+ `--shot-max-seconds` caps overly long shots the same way.
140
+ - Each rerender is a new file, so each iteration uploads and runs through
141
+ the configured Cloudglue API key
142
+ (https://app.cloudglue.dev/home/billing/rate-card). Tell the user, and
143
+ prefer short/low-res preview renders while iterating when the project
144
+ supports them.
145
+ - Reuse the same output path between iterations so stale renders don't pile
146
+ up; the new file content makes the cache treat it as a new source (that is
147
+ correct — you want the new render evaluated, not the cached old one).
148
+
149
+ You own the renderer-specific half of the loop (mapping timestamps to frames
150
+ and code, editing, rerendering); tinycloud only ever sees the media file.
151
+ The loop applies to any pipeline that emits video.
@@ -95,3 +95,57 @@ binary reports in `--version --json`.
95
95
  | `CLOUDGLUE_API_KEY` | Cloudglue API key (alternative to `tinycloud setup cloudglue`) |
96
96
  | `TINYCLOUD_VERSION` | npm launcher: run a specific binary version |
97
97
  | `TINYCLOUD_INSTALL_DIR` | npm launcher: cache root (default `~/.tinycloud`) |
98
+ | `TINYCLOUD_HOME` | Isolated state home — config, sessions, cache, jobs, artifacts, skills (default `~/.tinycloud`; same as `--home`). 0.3.3+ |
99
+ | `TINYCLOUD_OUT` | Output base for generated files (wins over a config `outputBase`) |
100
+ | `TINYCLOUD_HTTP_TIMEOUT_MS` | Hard deadline per Cloudglue request (default 120s; `0` disables) |
101
+ | `TINYCLOUD_UPLOAD_TIMEOUT_MS` | Deadline for upload-shaped requests (default 60min; `0` disables) |
102
+
103
+ Every Cloudglue request carries a hard deadline, so a stalled route can never
104
+ hang the CLI indefinitely; a timeout surfaces as a retryable `upstream` error
105
+ envelope whose message names the knob to adjust.
106
+
107
+ ## Profiles & isolated homes (0.3.3+)
108
+
109
+ Every piece of tinycloud state — config, sessions, cache, jobs, artifacts, and
110
+ skills — lives under one home (default `~/.tinycloud`). Relocate it to run
111
+ multiple accounts or installs side by side without cross-contamination. Both
112
+ options are *leading* (before the verb) and work with any command:
113
+
114
+ - `--home <dir>` (or `$TINYCLOUD_HOME`) — use that directory as the home.
115
+ - `--profile <name>` — use a named profile's home (from the profiles registry).
116
+
117
+ Named profiles are managed by the host-level `profile` verb (a CLI/host
118
+ concern, so it is not in `commands --json`):
119
+
120
+ ```bash
121
+ tinycloud profile list # profiles and their homes (active marked *)
122
+ tinycloud profile show [<name>] # home path + exists/default/active
123
+ tinycloud profile create <name> [--home <dir>] [--copy-from <name>] \
124
+ [--description <text>] [--default]
125
+ tinycloud profile use <name> # set the default profile
126
+ tinycloud profile remove <name> # unregister (does not delete the home)
127
+ ```
128
+
129
+ `--copy-from` seeds the new profile's home from an existing one. The registry
130
+ lives at `$XDG_CONFIG_HOME/tinycloud/profiles.json`; `default` is reserved.
131
+ This global `--profile <name>` is unrelated to `watch --profile
132
+ default|light|custom` (that flag selects an analysis profile).
133
+
134
+ ## Project scope (0.3.3+)
135
+
136
+ Within a home, sessions are scoped per project — keyed by the canonical git
137
+ root — under `<home>/projects/<project-key>/sessions`. In the interactive
138
+ agent, `/sessions` lists the current project's sessions (`/sessions all` spans
139
+ every project); `-c` resumes the most recent and still falls back to legacy
140
+ flat sessions (read-only, migrated forward on resume).
141
+
142
+ A project can also carry a local `.tinycloud/config.json` that scopes a run:
143
+
144
+ - `preferences.tools` / `preferences.skills` — allowlists of agent tool / skill
145
+ names (omit = all; the `--tools` / `--skills` flags override them).
146
+ - `preferences.outputBase` — where generated files land (a relative path is
147
+ anchored to the project root; `$TINYCLOUD_OUT` still wins).
148
+
149
+ Precedence is **CLI flags > project-local `.tinycloud/config.json` > global
150
+ config**; a more specific scope replaces (does not merge) a broader one.
151
+ Read-only mode always keeps the `read` and `bash` tools.
@@ -23,6 +23,24 @@ every verb. Regenerate doubts from it instead of trusting prose.
23
23
  Cloud verbs run through the configured Cloudglue API key.
24
24
  `caption`/`library`/`workflow` vary by what they end up doing.
25
25
 
26
+ ## Global flags (0.3.3+)
27
+
28
+ Leading options (placed *before* the verb) and agent-level allowlists, separate
29
+ from the per-verb flags below. `--home`/`--profile` and the `profile` verb are
30
+ host concerns and are intentionally absent from `commands --json`.
31
+
32
+ - `--home <dir>` / `$TINYCLOUD_HOME` — run against an isolated state home
33
+ (config, sessions, cache, jobs, artifacts, skills) instead of `~/.tinycloud`.
34
+ - `--profile <name>` — use a named profile's home. Managed by
35
+ `tinycloud profile list|show|create|use|remove`
36
+ (`create <name> [--home <dir>] [--copy-from <name>] [--description <text>] [--default]`).
37
+ Unrelated to `watch --profile default|light|custom` (an analysis profile).
38
+ - `--skills <list>` (0.3.3+) / `--tools <list>` — comma-separated agent skill /
39
+ tool allowlists (omit = all); also settable per project via
40
+ `.tinycloud/config.json`.
41
+
42
+ Profiles, project-scoped sessions, and `.tinycloud/config.json`: [setup.md](setup.md).
43
+
26
44
  ## Per-verb flags
27
45
 
28
46
  Flags shared by most verbs are listed once at the bottom.
@@ -31,20 +49,31 @@ Flags shared by most verbs are listed once at the bottom.
31
49
 
32
50
  ```bash
33
51
  tinycloud watch <source> [--segment uniform:20|chapters|shots|segments]
52
+ [--shot-min-seconds <s>] [--shot-max-seconds <s>]
34
53
  [--profile default|light|custom] [--speech-only | --visual-only]
35
54
  [--start <t>] [--end <t>] [--transcript] [--content] [--json-index]
36
55
  [--background]
37
56
  ```
38
57
 
58
+ Shot bounds tune `--segment shots` only: min 0.6–600 (fractional/sub-second
59
+ values catch flash frames and rapid cuts), max 1–600, min ≤ max. Out-of-range
60
+ or wrong-mode values fail with a validation envelope before any upload. The
61
+ bounds are part of the cache key, so tuned and default shot passes never
62
+ collide.
63
+
39
64
  ### extract — structured facts
40
65
 
41
66
  ```bash
42
67
  tinycloud extract "<query>" <source> --json # free-form query
43
68
  tinycloud extract --schema ./schema.json <source> # JSON-schema-shaped output
44
69
  [--segment-level] [--segmentation chapters|shots|segments]
70
+ [--shot-min-seconds <s>] [--shot-max-seconds <s>]
45
71
  [--include-thumbnails] [--transcript-mode] [--background]
46
72
  ```
47
73
 
74
+ `--shot-min-seconds`/`--shot-max-seconds` work exactly as on `watch`, against
75
+ `--segmentation shots`.
76
+
48
77
  ### caption — subtitles and transcripts
49
78
 
50
79
  ```bash
@@ -105,9 +134,23 @@ tinycloud library collections show <col_id> --json
105
134
  tinycloud library collections sync <col_id> --artifacts descriptions,transcripts,thumbnails,metadata --json
106
135
  tinycloud library connectors list --json
107
136
  tinycloud library connectors files <connector-id> [--limit 25] [--page-token <t>] --json
108
- tinycloud library connectors sync <connector-id> <uri> --json # e.g. grain://recording/<id>
137
+ tinycloud library connectors sync [<connector-id>] <uri-share-link-or-public-url> --json
109
138
  ```
110
139
 
140
+ `connectors sync` materializes its argument into a Cloudglue file without
141
+ starting analysis (idempotent). The connector id is optional — with just a
142
+ URI or link, sync routes through the matching connector type. Connector URIs
143
+ (`grain://recording/<id>`, `gdrive://file/<id>`, `dropbox://<path>`,
144
+ `zoom://uuid/<uuid>`, `s3://<bucket>/<key>`, …) and share links are accepted:
145
+ Dropbox file share links sync server-side via the connector's OAuth
146
+ (including login-gated links); `zoom.us/rec/share` links resolve best-effort
147
+ (Zoom mints a new token per copy — the recording-detail link is the reliable
148
+ form). Link warnings are advisory and surface in `data.warnings` rather than
149
+ blocking the sync. Non-connector public URLs (direct media URLs, TikTok,
150
+ Loom, public Dropbox links without a connector) sync into a standalone
151
+ Cloudglue file via direct URL ingestion — same command, no connector needed.
152
+ YouTube URLs cannot sync; use `tinycloud grab` instead.
153
+
111
154
  `connectors files` also takes provider-specific filters: `--from`/`--to`
112
155
  (Zoom, Grain dates), `--folder-id` (Google Drive), `--path` (Dropbox),
113
156
  `--bucket`/`--prefix` (S3/GCS), `--title-search`/`--team`/`--meeting-type`
@@ -196,8 +239,24 @@ browsers block it), `loop`, `start-time`, `poster`, `accent-color`, and
196
239
  rest). Its JS API queues until ready — `playSegment(start, end?)`,
197
240
  `seekTo()`, `play()`/`pause()` — and media events are re-dispatched on the
198
241
  element (`timeupdate`, `ended`, `cg-ready`); prefer `playSegment` over
199
- hand-rolled seek logic for "click a moment to play that segment" pages. The
200
- full reference ships with the binary as `references/cg-video.md` inside the
242
+ hand-rolled seek logic for "click a moment to play that segment" pages.
243
+
244
+ For multi-video or segment-navigation pages, prefer the container components
245
+ over hand-rolled galleries and segment-list JS:
246
+
247
+ - `<cg-playlist>` + `<cg-playlist-item share-id="…">` — one player plus a
248
+ clickable track list, with auto-advance.
249
+ - `<cg-grid>` + `<cg-grid-item share-id="…">` — lazy poster-card gallery,
250
+ inline or lightbox modal, at most one live player.
251
+ - `<cg-chapters>` + `<cg-chapter start="…" [end="…"]>` — segment navigation
252
+ bound to a player by id; an `end` attribute plays just that clip via
253
+ `playSegment`. Hand-rolled `playSegment` calls remain the fallback for
254
+ fully custom layouts.
255
+
256
+ Share ids inside `<cg-playlist-item>`/`<cg-grid-item>` tags count toward the
257
+ private-embed guard: `tinycloud publish` rejects an artifact embedding a
258
+ private share — directly or through a container — on a public site. The full
259
+ reference ships with the binary as `references/cg-video.md` inside the
201
260
  bundled media-artifact skill (under the install's `skills/` directory).
202
261
 
203
262
  ### setup — credentials
@@ -141,5 +141,9 @@ are not implemented in 0.3.x — treat `partial`/`paused` as terminal.
141
141
  The tinycloud agent picks it up on next start; from any shell it runs by
142
142
  path: `tinycloud workflow run ~/.tinycloud/skills/my-skill/my-skill.yaml demo.mp4 --allow-command --json`.
143
143
 
144
+ With `--home`/`--profile` (0.3.3+) the global skills dir moves under the active
145
+ home (`<home>/skills/`); a project-local `.tinycloud/config.json` can also pin
146
+ which skills load via a `preferences.skills` allowlist.
147
+
144
148
  To wrap a recipe as a skill for *this* host agent instead, see the
145
149
  `tinycloud-skill-creator` skill in this repo.
@@ -9,11 +9,11 @@ set -u
9
9
 
10
10
  # Mirror tinycloud-skill.json: min_version / supported_range upper bound
11
11
  # (CI diffs these against the manifest).
12
- MIN_VERSION="0.3.1"
12
+ MIN_VERSION="0.3.2"
13
13
  MAX_VERSION_EXCLUSIVE="0.4.0"
14
14
  INSTALL_CMD='curl -fsSL https://app.cloudglue.dev/tinycloud.sh | bash'
15
15
  # Kept in sync with ../tinycloud-skill.json required_features (CI diffs them).
16
- REQUIRED_FEATURES="envelope.v1 watch.v1 extract.v1 caption.v1 search.v1 probe.v1 ask.v1 clip.v1 grab.v1 jobs.v1 library.collections.v1 workflow.v1 publish.v1 publish.manage.v1 publish.video.v1 setup.v1"
16
+ REQUIRED_FEATURES="envelope.v1 watch.v1 extract.v1 caption.v1 search.v1 probe.v1 ask.v1 clip.v1 grab.v1 jobs.v1 library.collections.v1 library.sync.url.v1 workflow.v1 publish.v1 publish.manage.v1 publish.video.v1 setup.v1"
17
17
 
18
18
  # 1) Binary present and responsive?
19
19
  if ! command -v tinycloud >/dev/null 2>&1; then
@@ -1,8 +1,8 @@
1
1
  {
2
- "skill_version": "0.2.0",
2
+ "skill_version": "0.3.0",
3
3
  "tinycloud": {
4
- "min_version": "0.3.1",
5
- "supported_range": ">=0.3.1 <0.4.0",
4
+ "min_version": "0.3.2",
5
+ "supported_range": ">=0.3.2 <0.4.0",
6
6
  "required_features": [
7
7
  "envelope.v1",
8
8
  "watch.v1",
@@ -15,6 +15,7 @@
15
15
  "grab.v1",
16
16
  "jobs.v1",
17
17
  "library.collections.v1",
18
+ "library.sync.url.v1",
18
19
  "workflow.v1",
19
20
  "publish.v1",
20
21
  "publish.manage.v1",
@@ -75,6 +75,9 @@ Report what's now working and point forward:
75
75
  uses the API key)
76
76
  - One-command workflows: `tinycloud workflow list --json` (sales-coaching,
77
77
  blog-post, ad-analysis, meeting-breakdown, youtube-publish, …)
78
+ - Multiple accounts or isolated installs (0.3.3+): `tinycloud profile create
79
+ <name> --default`, then `--profile <name>` (or `--home <dir>` /
80
+ `$TINYCLOUD_HOME`) on any command to switch state homes
78
81
  - If the general `tinycloud` skill is installed alongside this one, it
79
82
  documents the full CLI, envelope contract, and a glossary; its
80
83
  `scripts/preflight.sh` re-checks this setup any time.
@@ -44,6 +44,8 @@ Before writing anything, establish:
44
44
  `.claude/skills/<name>/`) whose SKILL.md runs the recipe **by path**.
45
45
  - *The tinycloud agent*: `~/.tinycloud/skills/<name>/` (global) or
46
46
  `.tinycloud/skills/<name>/` (project), picked up as `/skills:<name>`.
47
+ (With `--home`/`--profile`, 0.3.3+, the global dir is the active home's
48
+ `skills/`; a project's `.tinycloud/config.json` can allowlist skills.)
47
49
 
48
50
  ## 2. Scaffold
49
51