@cloudglue/tinycloud 0.3.0
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/LICENSE.md +1 -0
- package/README.md +104 -0
- package/THIRD_PARTY_NOTICES.md +41 -0
- package/bin/tinycloud.js +135 -0
- package/lib/download.js +71 -0
- package/lib/installer.js +300 -0
- package/lib/manifest.js +181 -0
- package/lib/platform.js +22 -0
- package/lib/run.js +43 -0
- package/lib/skills.js +126 -0
- package/package.json +28 -0
- package/skills/ad-analysis/SKILL.md +65 -0
- package/skills/blog-post/SKILL.md +65 -0
- package/skills/meeting-breakdown/SKILL.md +65 -0
- package/skills/sales-coaching/SKILL.md +66 -0
- package/skills/tinycloud/SKILL.md +157 -0
- package/skills/tinycloud/reference/envelope.md +73 -0
- package/skills/tinycloud/reference/glossary.md +73 -0
- package/skills/tinycloud/reference/pipelines.md +104 -0
- package/skills/tinycloud/reference/setup.md +97 -0
- package/skills/tinycloud/reference/verbs.md +180 -0
- package/skills/tinycloud/reference/workflow-authoring.md +145 -0
- package/skills/tinycloud/scripts/preflight.sh +77 -0
- package/skills/tinycloud/tinycloud-skill.json +26 -0
- package/skills/tinycloud-init/SKILL.md +89 -0
- package/skills/tinycloud-skill-creator/SKILL.md +129 -0
- package/skills/youtube-publish/SKILL.md +66 -0
package/lib/manifest.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { execFileSync } = require("node:child_process");
|
|
4
|
+
|
|
5
|
+
const DEFAULT_BASE = "https://media.cloudglue.dev/tinycloud-dist";
|
|
6
|
+
|
|
7
|
+
// Node's fetch ignores proxy env vars; shell out to curl when one is set so
|
|
8
|
+
// the manifest/sidecar fetches behave like the tarball download (and like
|
|
9
|
+
// install.sh, which always uses curl).
|
|
10
|
+
function useCurl() {
|
|
11
|
+
return !!(process.env.HTTPS_PROXY || process.env.https_proxy || process.env.TINYCLOUD_USE_CURL === "1");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** GET url → {status, text}. status 0 = network failure. Proxy-aware. */
|
|
15
|
+
async function httpGetText(url) {
|
|
16
|
+
if (useCurl()) {
|
|
17
|
+
try {
|
|
18
|
+
const out = execFileSync("curl", ["-sSL", "-w", "\n%{http_code}", url], { encoding: "utf8" });
|
|
19
|
+
const i = out.lastIndexOf("\n");
|
|
20
|
+
return { status: Number(out.slice(i + 1).trim()) || 0, text: out.slice(0, i) };
|
|
21
|
+
} catch {
|
|
22
|
+
return { status: 0, text: "" };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const res = await fetch(url);
|
|
27
|
+
return { status: res.status, text: res.ok ? await res.text() : "" };
|
|
28
|
+
} catch {
|
|
29
|
+
return { status: 0, text: "" };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** HEAD url → ETag header value or null. Proxy-aware. */
|
|
34
|
+
async function httpHeadEtag(url) {
|
|
35
|
+
if (useCurl()) {
|
|
36
|
+
try {
|
|
37
|
+
const out = execFileSync("curl", ["-sSIL", url], { encoding: "utf8" });
|
|
38
|
+
const m = out.match(/^etag:\s*(.+)$/im);
|
|
39
|
+
return m ? m[1].trim() : null;
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const res = await fetch(url, { method: "HEAD" });
|
|
46
|
+
return res.ok ? res.headers.get("etag") : null;
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function baseUrl() {
|
|
53
|
+
return (process.env.TINYCLOUD_DIST_URL || DEFAULT_BASE).replace(/\/+$/, "");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function requireManifest() {
|
|
57
|
+
return process.env.TINYCLOUD_REQUIRE_MANIFEST === "1";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Pinned tarballs on the CDN are v-prefixed (tinycloud-<platform>-v0.3.0.tar.gz);
|
|
61
|
+
// version strings stay bare everywhere else.
|
|
62
|
+
function tarballName(target, version) {
|
|
63
|
+
return version ? `tinycloud-${target}-v${version}.tar.gz` : `tinycloud-${target}.tar.gz`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The manifest is an optimization, never a requirement (unless strict mode):
|
|
68
|
+
* a missing, erroring, or unusable manifest (CloudFront 403-for-missing,
|
|
69
|
+
* 5xx, captive-portal HTML, future schema) degrades to null so commands
|
|
70
|
+
* that don't need it keep working. Checksum MISMATCHES still always fail.
|
|
71
|
+
*/
|
|
72
|
+
async function fetchManifest() {
|
|
73
|
+
const url = `${baseUrl()}/manifest.json`;
|
|
74
|
+
const degrade = (reason) => {
|
|
75
|
+
if (requireManifest()) {
|
|
76
|
+
throw new Error(`TINYCLOUD_REQUIRE_MANIFEST=1 but the release manifest is unavailable: ${reason}`);
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
};
|
|
80
|
+
const { status, text } = await httpGetText(url);
|
|
81
|
+
if (status === 0 || status === 403 || status === 404) {
|
|
82
|
+
return degrade(`${url} is missing (HTTP ${status || "network error"})`);
|
|
83
|
+
}
|
|
84
|
+
if (status !== 200) {
|
|
85
|
+
process.stderr.write(`tinycloud: fetching ${url} failed (HTTP ${status}); proceeding without it\n`);
|
|
86
|
+
return degrade(`HTTP ${status}`);
|
|
87
|
+
}
|
|
88
|
+
let manifest;
|
|
89
|
+
try {
|
|
90
|
+
manifest = JSON.parse(text);
|
|
91
|
+
} catch {
|
|
92
|
+
process.stderr.write(`tinycloud: ${url} is not valid JSON; proceeding without it\n`);
|
|
93
|
+
return degrade("invalid JSON");
|
|
94
|
+
}
|
|
95
|
+
if (manifest.schema !== 1) {
|
|
96
|
+
process.stderr.write(
|
|
97
|
+
`tinycloud: unsupported manifest schema ${manifest.schema} at ${url}; proceeding without it (upgrade @cloudglue/tinycloud)\n`
|
|
98
|
+
);
|
|
99
|
+
return degrade(`unsupported schema ${manifest.schema}`);
|
|
100
|
+
}
|
|
101
|
+
return manifest;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Try the <tarball>.sha256 sidecar; returns the hex hash or null. */
|
|
105
|
+
async function fetchSidecarSha256(tarballUrl) {
|
|
106
|
+
const { status, text } = await httpGetText(`${tarballUrl}.sha256`);
|
|
107
|
+
if (status !== 200) return null;
|
|
108
|
+
const match = text.trim().match(/^[0-9a-f]{64}/i);
|
|
109
|
+
return match ? match[0].toLowerCase() : null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Resolve a version request to a concrete download.
|
|
114
|
+
* @param {string} versionOrLatest exact semver, or "latest"
|
|
115
|
+
* @param {string} channel "stable" | "beta"
|
|
116
|
+
* @param {string} target platform key like "darwin-arm64"
|
|
117
|
+
* @returns {{version: string|null, url: string, sha256: string|null, size: number|null, verified: boolean}}
|
|
118
|
+
*/
|
|
119
|
+
async function resolveDownload(versionOrLatest, channel, target, prefetchedManifest) {
|
|
120
|
+
// Callers that already fetched the manifest (update / install --latest)
|
|
121
|
+
// pass it in to avoid a second network round-trip for the same JSON.
|
|
122
|
+
const manifest = prefetchedManifest !== undefined ? prefetchedManifest : await fetchManifest();
|
|
123
|
+
const base = baseUrl();
|
|
124
|
+
|
|
125
|
+
if (manifest) {
|
|
126
|
+
let version = versionOrLatest;
|
|
127
|
+
const userPinned = versionOrLatest !== "latest";
|
|
128
|
+
if (versionOrLatest === "latest") {
|
|
129
|
+
version = manifest.channels && manifest.channels[channel];
|
|
130
|
+
if (!version) throw new Error(`Channel "${channel}" has no released version in the manifest`);
|
|
131
|
+
// Manifest-resolved versions get the same leading-v normalization as
|
|
132
|
+
// user input (install.sh does the same with ${VERSION#v})
|
|
133
|
+
version = String(version).replace(/^v/, "");
|
|
134
|
+
}
|
|
135
|
+
const entry = manifest.versions && manifest.versions[version];
|
|
136
|
+
const plat = entry && entry.platforms && entry.platforms[target];
|
|
137
|
+
if (plat) {
|
|
138
|
+
// An explicit distribution base (mirror, fixture) wins over the
|
|
139
|
+
// manifest's absolute URLs — otherwise the override only redirects
|
|
140
|
+
// the manifest fetch while tarballs still hit the canonical CDN.
|
|
141
|
+
const url = process.env.TINYCLOUD_DIST_URL ? `${base}/${plat.url.split("/").pop()}` : plat.url;
|
|
142
|
+
return { version, url, sha256: plat.sha256 || null, size: plat.size || null, verified: !!plat.sha256 };
|
|
143
|
+
}
|
|
144
|
+
if (!userPinned || requireManifest()) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
entry ? `Version ${version} has no build for ${target}` : `Version ${version} not found in the release manifest`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
// A user-pinned version missing from the manifest (e.g. a pre-manifest
|
|
150
|
+
// release whose tarball is still on the CDN) falls back to the
|
|
151
|
+
// conventional URL + sidecar instead of hard-failing.
|
|
152
|
+
process.stderr.write(`tinycloud: version ${version} is not in the release manifest; trying the direct URL\n`);
|
|
153
|
+
const url = `${base}/${tarballName(target, version)}`;
|
|
154
|
+
const sha256 = await fetchSidecarSha256(url);
|
|
155
|
+
return { version, url, sha256, size: null, verified: !!sha256 };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// No manifest published: fall back to direct tarball URLs.
|
|
159
|
+
if (channel !== "stable") {
|
|
160
|
+
throw new Error(`Channel "${channel}" requires the release manifest, which is not available`);
|
|
161
|
+
}
|
|
162
|
+
const version = versionOrLatest === "latest" ? null : versionOrLatest;
|
|
163
|
+
const url = `${base}/${tarballName(target, version)}`;
|
|
164
|
+
const sha256 = await fetchSidecarSha256(url);
|
|
165
|
+
if (!sha256) {
|
|
166
|
+
process.stderr.write(
|
|
167
|
+
"tinycloud: release manifest and checksum sidecar not found — proceeding without checksum verification\n"
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
return { version, url, sha256, size: null, verified: !!sha256 };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = {
|
|
174
|
+
baseUrl,
|
|
175
|
+
fetchManifest,
|
|
176
|
+
fetchSidecarSha256,
|
|
177
|
+
resolveDownload,
|
|
178
|
+
tarballName,
|
|
179
|
+
httpHeadEtag,
|
|
180
|
+
DEFAULT_BASE,
|
|
181
|
+
};
|
package/lib/platform.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const SUPPORTED = new Set(["darwin-arm64", "darwin-x64", "linux-x64", "linux-arm64"]);
|
|
4
|
+
|
|
5
|
+
class PlatformError extends Error {}
|
|
6
|
+
|
|
7
|
+
/** Resolve the current platform to a distribution target like "darwin-arm64". */
|
|
8
|
+
function resolveTarget(platform = process.platform, arch = process.arch) {
|
|
9
|
+
if (platform === "win32") {
|
|
10
|
+
throw new PlatformError(
|
|
11
|
+
"Windows is not supported. Use WSL2 (https://learn.microsoft.com/windows/wsl/) and re-run inside your Linux distro."
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
const normArch = arch === "x64" || arch === "amd64" ? "x64" : arch === "aarch64" ? "arm64" : arch;
|
|
15
|
+
const key = `${platform}-${normArch}`;
|
|
16
|
+
if (!SUPPORTED.has(key)) {
|
|
17
|
+
throw new PlatformError(`Unsupported platform: ${key}. Supported: ${[...SUPPORTED].join(", ")}`);
|
|
18
|
+
}
|
|
19
|
+
return key;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = { resolveTarget, SUPPORTED, PlatformError };
|
package/lib/run.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const path = require("node:path");
|
|
4
|
+
const { spawn } = require("node:child_process");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Exec passthrough to the real binary: inherit stdio, forward signals, and
|
|
8
|
+
* preserve exit semantics (including 128+n signal death).
|
|
9
|
+
*/
|
|
10
|
+
function run(binPath, args, installDir) {
|
|
11
|
+
const env = {
|
|
12
|
+
...process.env,
|
|
13
|
+
PATH: `${path.join(installDir, "bin")}${path.delimiter}${process.env.PATH || ""}`,
|
|
14
|
+
};
|
|
15
|
+
const child = spawn(binPath, args, { stdio: "inherit", env });
|
|
16
|
+
const forwarders = new Map();
|
|
17
|
+
for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
18
|
+
const forward = () => {
|
|
19
|
+
try {
|
|
20
|
+
child.kill(sig);
|
|
21
|
+
} catch {}
|
|
22
|
+
};
|
|
23
|
+
forwarders.set(sig, forward);
|
|
24
|
+
process.on(sig, forward);
|
|
25
|
+
}
|
|
26
|
+
child.on("error", (err) => {
|
|
27
|
+
process.stderr.write(`tinycloud: failed to launch binary: ${err.message}\n`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
});
|
|
30
|
+
child.on("close", (code, signal) => {
|
|
31
|
+
if (signal) {
|
|
32
|
+
// Remove only our forwarder (not listeners owned by parent tooling),
|
|
33
|
+
// then re-raise so our exit status preserves 128+n semantics.
|
|
34
|
+
const forward = forwarders.get(signal);
|
|
35
|
+
if (forward) process.removeListener(signal, forward);
|
|
36
|
+
process.kill(process.pid, signal);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
process.exit(code == null ? 1 : code);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = { run };
|
package/lib/skills.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const os = require("node:os");
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
|
|
7
|
+
// Agent skills bundled with this package (the npm tarball includes skills/).
|
|
8
|
+
function bundledSkillsDir() {
|
|
9
|
+
return path.join(__dirname, "..", "skills");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function listBundledSkills() {
|
|
13
|
+
const dir = bundledSkillsDir();
|
|
14
|
+
if (!fs.existsSync(dir)) return [];
|
|
15
|
+
return fs
|
|
16
|
+
.readdirSync(dir)
|
|
17
|
+
.filter((e) => fs.existsSync(path.join(dir, e, "SKILL.md")))
|
|
18
|
+
.sort();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
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.
|
|
25
|
+
* claude-code: <project>/.claude/skills (global: ~/.claude/skills)
|
|
26
|
+
* codex: <project>/.agents/skills (agentskills.io layout)
|
|
27
|
+
*/
|
|
28
|
+
function resolveTargets({ global: isGlobal, dir, cwd = process.cwd() }) {
|
|
29
|
+
if (dir) return [{ name: "custom", dir: path.resolve(dir) }];
|
|
30
|
+
if (isGlobal) return [{ name: "claude-code (global)", dir: path.join(os.homedir(), ".claude", "skills") }];
|
|
31
|
+
|
|
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") });
|
|
38
|
+
}
|
|
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") });
|
|
42
|
+
}
|
|
43
|
+
return targets;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function installSkills({ skills, targets, force }) {
|
|
47
|
+
const src = bundledSkillsDir();
|
|
48
|
+
const results = [];
|
|
49
|
+
for (const target of targets) {
|
|
50
|
+
fs.mkdirSync(target.dir, { recursive: true });
|
|
51
|
+
for (const skill of skills) {
|
|
52
|
+
const from = path.join(src, skill);
|
|
53
|
+
const to = path.join(target.dir, skill);
|
|
54
|
+
if (fs.existsSync(to) && !force) {
|
|
55
|
+
results.push({ target: target.name, skill, dir: to, status: "skipped (exists; use --force)" });
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
fs.rmSync(to, { recursive: true, force: true });
|
|
59
|
+
fs.cpSync(from, to, { recursive: true });
|
|
60
|
+
results.push({ target: target.name, skill, dir: to, status: "installed" });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return results;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const USAGE = `Usage: tinycloud skills <list|install> [options]
|
|
67
|
+
|
|
68
|
+
list List the agent skills bundled with this package
|
|
69
|
+
install Copy skills into your agent's skills directory
|
|
70
|
+
|
|
71
|
+
Install options:
|
|
72
|
+
--skill <a,b,...> Only these skills (default: all)
|
|
73
|
+
--global Install to ~/.claude/skills instead of the project
|
|
74
|
+
--dir <path> Install to an explicit directory
|
|
75
|
+
--force Overwrite skills that are already installed
|
|
76
|
+
|
|
77
|
+
Detection: a project .claude/ dir targets Claude Code (.claude/skills),
|
|
78
|
+
a .agents/ dir targets Codex (.agents/skills); both when both exist.
|
|
79
|
+
`;
|
|
80
|
+
|
|
81
|
+
async function cmdSkills(args) {
|
|
82
|
+
const action = args[0];
|
|
83
|
+
const available = listBundledSkills();
|
|
84
|
+
|
|
85
|
+
if (!action || action === "--help" || action === "-h") {
|
|
86
|
+
process.stdout.write(USAGE);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (action === "list") {
|
|
90
|
+
for (const s of available) console.log(s);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (action !== "install") {
|
|
94
|
+
throw new Error(`Unknown skills action: ${action}\n${USAGE}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let wanted = available;
|
|
98
|
+
let isGlobal = false;
|
|
99
|
+
let force = false;
|
|
100
|
+
let dir;
|
|
101
|
+
for (let i = 1; i < args.length; i++) {
|
|
102
|
+
if (args[i] === "--skill" && args[i + 1]) {
|
|
103
|
+
const names = args[++i].split(",").map((s) => s.trim()).filter(Boolean);
|
|
104
|
+
const unknown = names.filter((n) => !available.includes(n));
|
|
105
|
+
if (unknown.length) {
|
|
106
|
+
throw new Error(`Unknown skill(s): ${unknown.join(", ")}. Available: ${available.join(", ")}`);
|
|
107
|
+
}
|
|
108
|
+
wanted = names;
|
|
109
|
+
} else if (args[i] === "--global") isGlobal = true;
|
|
110
|
+
else if (args[i] === "--force") force = true;
|
|
111
|
+
else if (args[i] === "--dir" && args[i + 1]) dir = args[++i];
|
|
112
|
+
else throw new Error(`Unknown install option: ${args[i]}\n${USAGE}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (available.length === 0) {
|
|
116
|
+
throw new Error("No bundled skills found in this package installation");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const targets = resolveTargets({ global: isGlobal, dir });
|
|
120
|
+
const results = installSkills({ skills: wanted, targets, force });
|
|
121
|
+
for (const r of results) console.log(`${r.status === "installed" ? "✓" : "-"} ${r.skill} → ${r.dir} [${r.status}]`);
|
|
122
|
+
const installed = results.filter((r) => r.status === "installed").length;
|
|
123
|
+
console.log(`\n${installed} skill(s) installed${installed ? ". Restart your agent session to pick them up." : "."}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = { cmdSkills, resolveTargets, installSkills, listBundledSkills, bundledSkillsDir };
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cloudglue/tinycloud",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Agent CLI for deep video work, by Cloudglue. Downloads the tinycloud binary on first run.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"tinycloud": "bin/tinycloud.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin/",
|
|
10
|
+
"lib/",
|
|
11
|
+
"skills/",
|
|
12
|
+
"LICENSE.md",
|
|
13
|
+
"THIRD_PARTY_NOTICES.md"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"test": "node --test test/*.test.mjs"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/cloudglue/tinycloud.git"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://tinycloud.sh",
|
|
26
|
+
"keywords": ["video", "cloudglue", "cli", "agent", "captions", "clips"],
|
|
27
|
+
"license": "SEE LICENSE IN LICENSE.md"
|
|
28
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ad-analysis
|
|
3
|
+
description: >-
|
|
4
|
+
Analyze a video ad into an HTML breakdown with shot timeline, hook
|
|
5
|
+
classification, pacing, structure, CTA, and takeaways. Use when the user
|
|
6
|
+
wants ad creative analysis, competitive ad research, or a hook/pacing/CTA
|
|
7
|
+
breakdown of a commercial or social ad. Takes one source: a local video
|
|
8
|
+
file, URL, or cloudglue:// file URI (e.g. cloudglue://files/<id>). Runs the built-in tinycloud "ad-analysis"
|
|
9
|
+
workflow; requires the tinycloud CLI configured with a Cloudglue API key
|
|
10
|
+
(analysis runs through the user's Cloudglue account).
|
|
11
|
+
argument-hint: "[ad video file, URL, or cloudglue:// file URI]"
|
|
12
|
+
arguments: source
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Video-ad analysis
|
|
16
|
+
|
|
17
|
+
This skill is a thin wrapper around the `ad-analysis` workflow recipe bundled
|
|
18
|
+
inside the tinycloud binary (`watch → extract → render`).
|
|
19
|
+
|
|
20
|
+
## Run
|
|
21
|
+
|
|
22
|
+
1. **Check the CLI.** If the general `tinycloud` skill is installed alongside
|
|
23
|
+
this one, run its `scripts/preflight.sh`. Otherwise verify directly:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
tinycloud setup --check --json # ready when data.ok == true
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Missing CLI: `npm install -g @cloudglue/tinycloud` (see https://tinycloud.sh).
|
|
30
|
+
Missing key: `tinycloud setup cloudglue --api-key <key>`.
|
|
31
|
+
|
|
32
|
+
2. **Confirm the recipe is available** (free, no cloud calls):
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
tinycloud workflow validate ad-analysis --json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
3. **Run it** with the user's source. The analysis steps run through the
|
|
39
|
+
configured Cloudglue API key — if the user has not clearly asked to run
|
|
40
|
+
it, show the step plan first with
|
|
41
|
+
`tinycloud workflow plan ad-analysis $source --json` (free).
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
tinycloud workflow ad-analysis $source --json
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Useful params: `--param segment=shots` (default; shot-level timeline the
|
|
48
|
+
breakdown is built around) or `--param segment=uniform:20` for a lighter
|
|
49
|
+
uniform pass; `--param out=<path>` to control the HTML location.
|
|
50
|
+
|
|
51
|
+
## Read the result
|
|
52
|
+
|
|
53
|
+
Parse the single JSON envelope from stdout (machine output; logs are stderr):
|
|
54
|
+
|
|
55
|
+
- Success: `status == "ready"` and `data.status == "completed"`.
|
|
56
|
+
- The analysis path is `data.outputs.html` (also in `data.artifacts[]`).
|
|
57
|
+
Default: `./tinycloud-output/runs/<data.run_id>/ad-analysis.html`.
|
|
58
|
+
- Report the HTML path; offer
|
|
59
|
+
`tinycloud publish <html> --name ad-analysis --visibility private --json`
|
|
60
|
+
to host it as a shareable page.
|
|
61
|
+
|
|
62
|
+
Any other `status` (`needs_credentials`, `needs_upload`, `pending`, `paused`,
|
|
63
|
+
`error`) or `data.status` of `partial`/`failed`: stop, report the envelope's
|
|
64
|
+
`error.message`, and follow its `setup` / `resume` / `next` hints. The general
|
|
65
|
+
`tinycloud` skill (if installed) documents full status handling.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: blog-post
|
|
3
|
+
description: >-
|
|
4
|
+
Transform a video into a rich blog post (HTML + embedded markdown) with
|
|
5
|
+
sections, thumbnails, and key takeaways. Use when the user wants to turn a
|
|
6
|
+
video, talk, demo, or tutorial into a written article or blog content.
|
|
7
|
+
Takes one source: a local video file, URL, or cloudglue:// file URI (e.g. cloudglue://files/<id>). Runs the
|
|
8
|
+
built-in tinycloud "blog-post" workflow; requires the tinycloud CLI
|
|
9
|
+
configured with a Cloudglue API key (analysis runs through the user's
|
|
10
|
+
Cloudglue account).
|
|
11
|
+
argument-hint: "[video file, URL, or cloudglue:// file URI]"
|
|
12
|
+
arguments: source
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Video → blog post
|
|
16
|
+
|
|
17
|
+
This skill is a thin wrapper around the `blog-post` workflow recipe bundled
|
|
18
|
+
inside the tinycloud binary (`watch → extract → render`).
|
|
19
|
+
|
|
20
|
+
## Run
|
|
21
|
+
|
|
22
|
+
1. **Check the CLI.** If the general `tinycloud` skill is installed alongside
|
|
23
|
+
this one, run its `scripts/preflight.sh`. Otherwise verify directly:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
tinycloud setup --check --json # ready when data.ok == true
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Missing CLI: `npm install -g @cloudglue/tinycloud` (see https://tinycloud.sh).
|
|
30
|
+
Missing key: `tinycloud setup cloudglue --api-key <key>`.
|
|
31
|
+
|
|
32
|
+
2. **Confirm the recipe is available** (free, no cloud calls):
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
tinycloud workflow validate blog-post --json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
3. **Run it** with the user's source. The analysis steps run through the
|
|
39
|
+
configured Cloudglue API key — if the user has not clearly asked to run
|
|
40
|
+
it, show the step plan first with
|
|
41
|
+
`tinycloud workflow plan blog-post $source --json` (free).
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
tinycloud workflow blog-post $source --json
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Useful params: `--param segment=chapters` (default; semantic section
|
|
48
|
+
anchors) or `--param segment=uniform:20` for a lighter pass;
|
|
49
|
+
`--param out=<path>` to control the HTML location.
|
|
50
|
+
|
|
51
|
+
## Read the result
|
|
52
|
+
|
|
53
|
+
Parse the single JSON envelope from stdout (machine output; logs are stderr):
|
|
54
|
+
|
|
55
|
+
- Success: `status == "ready"` and `data.status == "completed"`.
|
|
56
|
+
- The article path is `data.outputs.html` (also in `data.artifacts[]`).
|
|
57
|
+
Default: `./tinycloud-output/runs/<data.run_id>/blog-post.html`.
|
|
58
|
+
- Report the HTML path; offer
|
|
59
|
+
`tinycloud publish <html> --name blog-post --visibility private --json`
|
|
60
|
+
to host it as a shareable page.
|
|
61
|
+
|
|
62
|
+
Any other `status` (`needs_credentials`, `needs_upload`, `pending`, `paused`,
|
|
63
|
+
`error`) or `data.status` of `partial`/`failed`: stop, report the envelope's
|
|
64
|
+
`error.message`, and follow its `setup` / `resume` / `next` hints. The general
|
|
65
|
+
`tinycloud` skill (if installed) documents full status handling.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: meeting-breakdown
|
|
3
|
+
description: >-
|
|
4
|
+
Generate a visual meeting breakdown (HTML) with speaker timeline, topic
|
|
5
|
+
labels, chapter summaries, and action items from a meeting recording. Use
|
|
6
|
+
when the user wants meeting notes, a recap, action items, or a who-said-what
|
|
7
|
+
timeline from a recorded meeting or call. Takes one source: a local video
|
|
8
|
+
file, URL, or cloudglue:// file URI (e.g. cloudglue://files/<id>). Runs the built-in tinycloud
|
|
9
|
+
"meeting-breakdown" workflow; requires the tinycloud CLI configured with a
|
|
10
|
+
Cloudglue API key (analysis runs through the user's Cloudglue account).
|
|
11
|
+
argument-hint: "[meeting recording file, URL, or cloudglue:// file URI]"
|
|
12
|
+
arguments: source
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Meeting breakdown
|
|
16
|
+
|
|
17
|
+
This skill is a thin wrapper around the `meeting-breakdown` workflow recipe
|
|
18
|
+
bundled inside the tinycloud binary (`watch → extract ×2 → render`).
|
|
19
|
+
|
|
20
|
+
## Run
|
|
21
|
+
|
|
22
|
+
1. **Check the CLI.** If the general `tinycloud` skill is installed alongside
|
|
23
|
+
this one, run its `scripts/preflight.sh`. Otherwise verify directly:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
tinycloud setup --check --json # ready when data.ok == true
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Missing CLI: `npm install -g @cloudglue/tinycloud` (see https://tinycloud.sh).
|
|
30
|
+
Missing key: `tinycloud setup cloudglue --api-key <key>`.
|
|
31
|
+
|
|
32
|
+
2. **Confirm the recipe is available** (free, no cloud calls):
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
tinycloud workflow validate meeting-breakdown --json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
3. **Run it** with the user's source. The analysis steps (one describe + two
|
|
39
|
+
extracts) run through the configured Cloudglue API key — if the user has
|
|
40
|
+
not clearly asked to run it, show the step plan first with
|
|
41
|
+
`tinycloud workflow plan meeting-breakdown $source --json` (free).
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
tinycloud workflow meeting-breakdown $source --json
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Useful params: `--param segment=chapters` (default; semantic meeting
|
|
48
|
+
narrative) or `--param segment=uniform:20` for dense raw intervals;
|
|
49
|
+
`--param out=<path>` to control the HTML location.
|
|
50
|
+
|
|
51
|
+
## Read the result
|
|
52
|
+
|
|
53
|
+
Parse the single JSON envelope from stdout (machine output; logs are stderr):
|
|
54
|
+
|
|
55
|
+
- Success: `status == "ready"` and `data.status == "completed"`.
|
|
56
|
+
- The breakdown path is `data.outputs.html` (also in `data.artifacts[]`).
|
|
57
|
+
Default: `./tinycloud-output/runs/<data.run_id>/meeting-breakdown.html`.
|
|
58
|
+
- Report the HTML path; offer
|
|
59
|
+
`tinycloud publish <html> --name meeting-breakdown --visibility private --json`
|
|
60
|
+
to host it as a shareable page.
|
|
61
|
+
|
|
62
|
+
Any other `status` (`needs_credentials`, `needs_upload`, `pending`, `paused`,
|
|
63
|
+
`error`) or `data.status` of `partial`/`failed`: stop, report the envelope's
|
|
64
|
+
`error.message`, and follow its `setup` / `resume` / `next` hints. The general
|
|
65
|
+
`tinycloud` skill (if installed) documents full status handling.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sales-coaching
|
|
3
|
+
description: >-
|
|
4
|
+
Turn a sales-call recording into a coaching dashboard (HTML) with call
|
|
5
|
+
scores, speech metrics, objections, and improvement areas. Use when the
|
|
6
|
+
user wants sales-call analysis, call coaching, or rep feedback from a
|
|
7
|
+
video/audio recording. Takes one source: a local video file, URL, or
|
|
8
|
+
cloudglue:// file URI (e.g. cloudglue://files/<id>). Runs the built-in tinycloud "sales-coaching" workflow;
|
|
9
|
+
requires the tinycloud CLI configured with a Cloudglue API key (analysis
|
|
10
|
+
runs through the user's Cloudglue account).
|
|
11
|
+
argument-hint: "[sales call video file, URL, or cloudglue:// file URI]"
|
|
12
|
+
arguments: source
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Sales-call coaching dashboard
|
|
16
|
+
|
|
17
|
+
This skill is a thin wrapper around the `sales-coaching` workflow recipe
|
|
18
|
+
bundled inside the tinycloud binary (`watch → extract ×2 → render`).
|
|
19
|
+
|
|
20
|
+
## Run
|
|
21
|
+
|
|
22
|
+
1. **Check the CLI.** If the general `tinycloud` skill is installed alongside
|
|
23
|
+
this one, run its `scripts/preflight.sh`. Otherwise verify directly:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
tinycloud setup --check --json # ready when data.ok == true
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Missing CLI: `npm install -g @cloudglue/tinycloud` (see https://tinycloud.sh).
|
|
30
|
+
Missing key: `tinycloud setup cloudglue --api-key <key>`.
|
|
31
|
+
|
|
32
|
+
2. **Confirm the recipe is available** (free, no cloud calls):
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
tinycloud workflow validate sales-coaching --json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
3. **Run it** with the user's source. The analysis steps (one describe + two
|
|
39
|
+
extracts) run through the configured Cloudglue API key — if the user has
|
|
40
|
+
not clearly asked to run it, show them the step plan first with
|
|
41
|
+
`tinycloud workflow plan sales-coaching $source --json` (free).
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
tinycloud workflow sales-coaching $source --json
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
(The recipe self-permits its local render step — no extra flag needed.)
|
|
48
|
+
Useful params: `--param segment=chapters` (default; semantic call phases) or
|
|
49
|
+
`--param segment=uniform:20` for dense fixed intervals;
|
|
50
|
+
`--param out=<path>` to control the HTML location.
|
|
51
|
+
|
|
52
|
+
## Read the result
|
|
53
|
+
|
|
54
|
+
Parse the single JSON envelope from stdout (machine output; logs are stderr):
|
|
55
|
+
|
|
56
|
+
- Success: `status == "ready"` and `data.status == "completed"`.
|
|
57
|
+
- The dashboard path is `data.outputs.html` (also in `data.artifacts[]`).
|
|
58
|
+
Default: `./tinycloud-output/runs/<data.run_id>/sales-coaching.html`.
|
|
59
|
+
- Report the HTML path to the user; offer
|
|
60
|
+
`tinycloud publish <html> --name sales-coaching --visibility private --json`
|
|
61
|
+
to host it as a shareable page.
|
|
62
|
+
|
|
63
|
+
Any other `status` (`needs_credentials`, `needs_upload`, `pending`, `paused`,
|
|
64
|
+
`error`) or `data.status` of `partial`/`failed`: stop, report the envelope's
|
|
65
|
+
`error.message`, and follow its `setup` / `resume` / `next` hints. The general
|
|
66
|
+
`tinycloud` skill (if installed) documents full status handling.
|