ossclip 0.1.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.
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>ossclip editor</title>
7
+ <script type="module" crossorigin src="/assets/index-CI9c6j62.js"></script>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ </body>
12
+ </html>
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "ossclip",
3
+ "version": "0.1.0",
4
+ "description": "Local-first CLI video producer: cuts silence and fillers, word-timed captions, face-aware framing, and LLM-planned code-rendered graphics — transcription and rendering never leave your machine",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/AhsanAyaz/ossclip.git",
10
+ "directory": "apps/cli"
11
+ },
12
+ "keywords": [
13
+ "video",
14
+ "cli",
15
+ "captions",
16
+ "remotion",
17
+ "whisper",
18
+ "shorts",
19
+ "video-editing",
20
+ "llm"
21
+ ],
22
+ "engines": {
23
+ "node": ">=22"
24
+ },
25
+ "bin": {
26
+ "ossclip": "./bin/ossclip.mjs"
27
+ },
28
+ "files": [
29
+ "README.md",
30
+ "bin",
31
+ "src",
32
+ "editor-dist"
33
+ ],
34
+ "dependencies": {
35
+ "commander": "^12.1.0",
36
+ "tsx": "^4.19.0",
37
+ "zod": "^3.25.76",
38
+ "@ossclip/renderer": "0.1.0",
39
+ "@ossclip/scenes": "0.1.0",
40
+ "@ossclip/core": "0.1.0"
41
+ },
42
+ "homepage": "https://github.com/AhsanAyaz/ossclip#readme",
43
+ "bugs": {
44
+ "url": "https://github.com/AhsanAyaz/ossclip/issues"
45
+ },
46
+ "author": "Muhammad Ahsan Ayaz",
47
+ "scripts": {
48
+ "ossclip": "tsx src/index.ts"
49
+ }
50
+ }
package/src/doctor.ts ADDED
@@ -0,0 +1,190 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { isAbsolute, join } from "node:path";
4
+ import type { OssclipConfig } from "@ossclip/core";
5
+
6
+ /**
7
+ * `ossclip doctor` (R18 §90a): check every prerequisite and print the exact
8
+ * fix — because the install cliff is the adoption ceiling. Seven
9
+ * preconditions stand between a first-time user and their first frame, and
10
+ * every one that fails silently becomes a GitHub issue instead of a
11
+ * one-line fix the tool could have printed itself.
12
+ *
13
+ * Checks are pure over injected probes so the table is unit-testable; the
14
+ * CLI wires the real spawn/existsSync in. The provider check MUST run after
15
+ * `loadEnvFiles` (R16 §77) or a key living in `.env` reports a false
16
+ * negative — `index.ts` loads env at module top, before any command runs.
17
+ */
18
+
19
+ export interface DoctorCheck {
20
+ name: string;
21
+ ok: boolean;
22
+ /** What was found (the path/provider that satisfied the check). */
23
+ detail: string;
24
+ /** The exact fix, present only when !ok. */
25
+ fix?: string;
26
+ }
27
+
28
+ export interface DoctorProbes {
29
+ /** Does spawning this binary work at all (any exit code counts)? */
30
+ binRuns(bin: string, arg: string): Promise<boolean>;
31
+ exists(path: string): boolean;
32
+ env: NodeJS.ProcessEnv;
33
+ platform: NodeJS.Platform;
34
+ nodeMajor: number;
35
+ /** Resolved editor page dir, or null if no candidate exists. */
36
+ editorPageDir: string | null;
37
+ }
38
+
39
+ /** brew on mac, apt elsewhere — the generic hint covers the rest. */
40
+ const installHint = (platform: NodeJS.Platform, brew: string, apt: string, generic: string): string =>
41
+ platform === "darwin" ? brew : platform === "linux" ? apt : generic;
42
+
43
+ export async function runDoctor(cfg: OssclipConfig, p: DoctorProbes): Promise<DoctorCheck[]> {
44
+ const checks: DoctorCheck[] = [];
45
+
46
+ checks.push({
47
+ name: "node",
48
+ ok: p.nodeMajor >= 22,
49
+ detail: `v${p.nodeMajor}`,
50
+ ...(p.nodeMajor >= 22
51
+ ? {}
52
+ : { fix: "ossclip needs Node ≥ 22 — install from https://nodejs.org or via your version manager" }),
53
+ });
54
+
55
+ const ffmpegOk = await p.binRuns(cfg.ffmpegPath, "-version");
56
+ checks.push({
57
+ name: "ffmpeg",
58
+ ok: ffmpegOk,
59
+ detail: cfg.ffmpegPath,
60
+ ...(ffmpegOk
61
+ ? {}
62
+ : {
63
+ fix:
64
+ installHint(
65
+ p.platform,
66
+ "brew install ffmpeg",
67
+ "sudo apt install ffmpeg",
68
+ "install ffmpeg from https://ffmpeg.org",
69
+ ) + " — or point OSSCLIP_FFMPEG (or config.json ffmpegPath) at the binary",
70
+ }),
71
+ });
72
+
73
+ const ffprobeOk = await p.binRuns(cfg.ffprobePath, "-version");
74
+ checks.push({
75
+ name: "ffprobe",
76
+ ok: ffprobeOk,
77
+ detail: cfg.ffprobePath,
78
+ ...(ffprobeOk
79
+ ? {}
80
+ : {
81
+ fix:
82
+ installHint(
83
+ p.platform,
84
+ "brew install ffmpeg (provides ffprobe)",
85
+ "sudo apt install ffmpeg (provides ffprobe)",
86
+ "ffprobe ships with ffmpeg — https://ffmpeg.org",
87
+ ) + " — or set OSSCLIP_FFPROBE",
88
+ }),
89
+ });
90
+
91
+ const whisperOk = await p.binRuns(cfg.whisperPath, "--help");
92
+ checks.push({
93
+ name: "whisper-cli",
94
+ ok: whisperOk,
95
+ detail: cfg.whisperPath,
96
+ ...(whisperOk
97
+ ? {}
98
+ : {
99
+ fix:
100
+ installHint(
101
+ p.platform,
102
+ "brew install whisper-cpp",
103
+ "build whisper.cpp from source: https://github.com/ggml-org/whisper.cpp",
104
+ "build whisper.cpp from source: https://github.com/ggml-org/whisper.cpp",
105
+ ) + " — or point OSSCLIP_WHISPER at your whisper-cli",
106
+ }),
107
+ });
108
+
109
+ // Same resolution `produce` uses: an absolute model is a file path, a bare
110
+ // name resolves inside modelDir as ggml-<name>.bin.
111
+ const modelPath = isAbsolute(cfg.model) ? cfg.model : join(cfg.modelDir, `ggml-${cfg.model}.bin`);
112
+ const modelOk = p.exists(modelPath);
113
+ checks.push({
114
+ name: `whisper model (${cfg.model})`,
115
+ ok: modelOk,
116
+ detail: modelPath,
117
+ ...(modelOk
118
+ ? {}
119
+ : {
120
+ fix:
121
+ `mkdir -p ${cfg.modelDir} && curl -L -o ${modelPath} ` +
122
+ `https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-${cfg.model}.bin`,
123
+ }),
124
+ });
125
+
126
+ // Provider, in the same order auto-detection uses (gemini → claude →
127
+ // claude-cli). Needed for --produce only — the cut+captions path never
128
+ // touches an LLM — but doctor's contract is "ready for the whole thing".
129
+ const provider = p.env.GEMINI_API_KEY
130
+ ? "gemini (GEMINI_API_KEY is set)"
131
+ : p.env.ANTHROPIC_API_KEY
132
+ ? "claude (ANTHROPIC_API_KEY is set)"
133
+ : (await p.binRuns("claude", "--version"))
134
+ ? "claude-cli (logged-in Claude Code)"
135
+ : null;
136
+ checks.push({
137
+ name: "LLM provider",
138
+ ok: provider !== null,
139
+ detail: provider ?? "no key set and no claude CLI on PATH — needed for --produce; cut+captions works without",
140
+ ...(provider !== null
141
+ ? {}
142
+ : {
143
+ fix:
144
+ "export ANTHROPIC_API_KEY or GEMINI_API_KEY (a .env file works — see README), " +
145
+ "or install Claude Code (https://claude.com/claude-code) and log in",
146
+ }),
147
+ });
148
+
149
+ checks.push({
150
+ name: "editor page",
151
+ ok: p.editorPageDir !== null,
152
+ detail: p.editorPageDir ?? "not built — needed for `ossclip edit` only",
153
+ ...(p.editorPageDir !== null
154
+ ? {}
155
+ : { fix: "run `pnpm build` once in the ossclip checkout (npm installs ship it prebuilt)" }),
156
+ });
157
+
158
+ return checks;
159
+ }
160
+
161
+ export function formatDoctor(checks: DoctorCheck[]): string {
162
+ const lines = checks.map((c) => {
163
+ const head = `${c.ok ? "✓" : "✗"} ${c.name.padEnd(24)} ${c.detail}`;
164
+ return c.fix ? `${head}\n ↳ ${c.fix}` : head;
165
+ });
166
+ const missing = checks.filter((c) => !c.ok).length;
167
+ lines.push(
168
+ missing === 0
169
+ ? "\nAll checks passed — you're ready to `ossclip produce`."
170
+ : `\n${missing} check${missing === 1 ? "" : "s"} failed — fixes above, then re-run \`ossclip doctor\`.`,
171
+ );
172
+ return lines.join("\n");
173
+ }
174
+
175
+ /** The real probes the CLI wires in. */
176
+ export const realProbes = (editorPageDir: string | null): DoctorProbes => ({
177
+ binRuns: (bin, arg) =>
178
+ new Promise((resolve) => {
179
+ // Existence is the question, not exit code — whisper-cli --help exits
180
+ // nonzero and is still installed. Only a spawn error (ENOENT) fails.
181
+ const child = spawn(bin, [arg], { stdio: "ignore" });
182
+ child.on("error", () => resolve(false));
183
+ child.on("exit", () => resolve(true));
184
+ }),
185
+ exists: existsSync,
186
+ env: process.env,
187
+ platform: process.platform,
188
+ nodeMajor: Number.parseInt(process.versions.node, 10),
189
+ editorPageDir,
190
+ });