ossclip 0.1.10 → 0.1.11
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/package.json +4 -4
- package/src/edit.ts +12 -1
- package/src/produce.ts +14 -7
- package/src/program.ts +14 -0
- package/src/replay-argv.ts +60 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ossclip",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
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
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
"commander": "^12.1.0",
|
|
37
37
|
"tsx": "^4.19.0",
|
|
38
38
|
"zod": "^3.25.76",
|
|
39
|
-
"@ossclip/core": "0.1.
|
|
40
|
-
"@ossclip/renderer": "0.1.
|
|
41
|
-
"@ossclip/scenes": "0.1.
|
|
39
|
+
"@ossclip/core": "0.1.11",
|
|
40
|
+
"@ossclip/renderer": "0.1.11",
|
|
41
|
+
"@ossclip/scenes": "0.1.11"
|
|
42
42
|
},
|
|
43
43
|
"homepage": "https://github.com/AhsanAyaz/ossclip#readme",
|
|
44
44
|
"bugs": {
|
package/src/edit.ts
CHANGED
|
@@ -390,11 +390,22 @@ export async function startEditServer(
|
|
|
390
390
|
);
|
|
391
391
|
if (!parsed.success) return send(500, { error: `command.json is not valid: ${parsed.error.message}` });
|
|
392
392
|
const cmd = parsed.data;
|
|
393
|
+
// §129: heal legacy records at replay. Before the fix, wizard and
|
|
394
|
+
// bare-path runs recorded process.argv — the ORIGINAL invocation,
|
|
395
|
+
// missing the `produce` literal the re-entered parse actually ran —
|
|
396
|
+
// so replaying them verbatim dies at commander's front door with
|
|
397
|
+
// "error: unknown option '--llm'". produce is the ONLY command that
|
|
398
|
+
// ever writes command.json, so an args array not starting with
|
|
399
|
+
// "produce" can only be that bug: prepend the literal to
|
|
400
|
+
// reconstruct the command that ran. A modern record — and a legacy
|
|
401
|
+
// directly-typed `ossclip produce …` — already starts with it and
|
|
402
|
+
// is untouched.
|
|
403
|
+
const args = cmd.args[0] === "produce" ? cmd.args : ["produce", ...cmd.args];
|
|
393
404
|
renderLines = [];
|
|
394
405
|
renderExit = null;
|
|
395
406
|
renderStartedAt = Date.now();
|
|
396
407
|
renderCancelled = false;
|
|
397
|
-
const child = spawn(cmd.execPath, [...cmd.execArgv, cmd.script, ...
|
|
408
|
+
const child = spawn(cmd.execPath, [...cmd.execArgv, cmd.script, ...args], {
|
|
398
409
|
cwd: cmd.cwd,
|
|
399
410
|
stdio: ["ignore", "pipe", "pipe"],
|
|
400
411
|
});
|
package/src/produce.ts
CHANGED
|
@@ -98,6 +98,7 @@ import {
|
|
|
98
98
|
} from "@ossclip/core";
|
|
99
99
|
import { recordRecentProject } from "./edit";
|
|
100
100
|
import { editHint } from "./interactive/edit-hint";
|
|
101
|
+
import { recordedProduceArgs } from "./replay-argv";
|
|
101
102
|
import { renderCover, renderProduction } from "@ossclip/renderer";
|
|
102
103
|
import {
|
|
103
104
|
coverTextRect,
|
|
@@ -2092,18 +2093,24 @@ export async function produce(inputArg: string, opts: ProduceOptions): Promise<P
|
|
|
2092
2093
|
// provider (R16 §75). Pin the RESOLVED choice into the recorded args —
|
|
2093
2094
|
// never the key itself; secrets stay out of the workdir — so a replay
|
|
2094
2095
|
// uses the same configuration or fails loudly asking for it.
|
|
2095
|
-
const recordedArgs = process.argv.slice(2);
|
|
2096
|
-
if (provider && !recordedArgs.includes("--llm")) {
|
|
2097
|
-
recordedArgs.push("--llm", providerName);
|
|
2098
|
-
}
|
|
2099
2096
|
// §93g: pin the RESOLVED window, exactly as §75 pinned the provider. The
|
|
2100
2097
|
// editor's Render replays this argv; if replay re-asked the model and got a
|
|
2101
2098
|
// slightly different window, every saved override — anchored to scene ids
|
|
2102
2099
|
// and word indices — would land on the wrong words. The word range, not
|
|
2103
2100
|
// just `--clip 60`, is what makes replay deterministic with zero LLM calls.
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2101
|
+
//
|
|
2102
|
+
// §129: NOT process.argv. A wizard or bare-path run re-enters commander
|
|
2103
|
+
// with a BUILT argv while process.argv still holds the original invocation
|
|
2104
|
+
// (`ossclip <path>`, no `produce` literal, none of the wizard's answers) —
|
|
2105
|
+
// recording process.argv shipped a command that replays as
|
|
2106
|
+
// `ossclip <path> --llm …` and dies on "unknown option '--llm'".
|
|
2107
|
+
// recordedProduceArgs prefers the argv the re-entry stashed and falls back
|
|
2108
|
+
// to process.argv for a directly typed `ossclip produce …`, which stays
|
|
2109
|
+
// byte-identical to what was always recorded.
|
|
2110
|
+
const recordedArgs = recordedProduceArgs({
|
|
2111
|
+
llm: provider ? providerName : undefined,
|
|
2112
|
+
clipWindow: clipWindow ? `${clipWindow.startWord}:${clipWindow.endWord}` : undefined,
|
|
2113
|
+
});
|
|
2107
2114
|
await writeFile(
|
|
2108
2115
|
join(work, "command.json"),
|
|
2109
2116
|
JSON.stringify(
|
package/src/program.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { CleanupLevelSchema, SceneComponentIdSchema } from "@ossclip/core";
|
|
|
7
7
|
import { STUDIO_ENTRY } from "@ossclip/renderer";
|
|
8
8
|
import { loadEnvFiles } from "./env";
|
|
9
9
|
import { produce } from "./produce";
|
|
10
|
+
import { setReplayArgv } from "./replay-argv";
|
|
10
11
|
|
|
11
12
|
// Before anything reads a provider key (R16 §77) — including the auto-detect
|
|
12
13
|
// order in `defaultProviderName`, which decides which model runs.
|
|
@@ -95,6 +96,11 @@ export function buildProgram(): Command {
|
|
|
95
96
|
// IS supplied — a piped `ossclip <path>` is `ossclip produce <path>`.
|
|
96
97
|
const direct = ["produce", path];
|
|
97
98
|
console.log(`\n▸ running:\n ${renderCommand(direct)}\n`);
|
|
99
|
+
// §129: THIS argv — not process.argv, which still says `ossclip
|
|
100
|
+
// <path>` with no `produce` literal — is the invocation
|
|
101
|
+
// command.json must record for the editor's Render to replay.
|
|
102
|
+
// Every parseAsync re-entry below stashes for the same reason.
|
|
103
|
+
setReplayArgv(direct);
|
|
98
104
|
await program.parseAsync(["node", "ossclip", ...direct]);
|
|
99
105
|
return;
|
|
100
106
|
}
|
|
@@ -102,6 +108,7 @@ export function buildProgram(): Command {
|
|
|
102
108
|
const { loadConfig } = await import("@ossclip/core");
|
|
103
109
|
const argv = await produceWizard({ speaker: loadConfig().speaker, input: path });
|
|
104
110
|
console.log(`\n▸ running:\n ${renderCommand(argv)}\n`);
|
|
111
|
+
setReplayArgv(argv); // §129
|
|
105
112
|
await program.parseAsync(["node", "ossclip", ...argv]);
|
|
106
113
|
return;
|
|
107
114
|
}
|
|
@@ -119,6 +126,11 @@ export function buildProgram(): Command {
|
|
|
119
126
|
// menu is also how you learn the flags", and three of the four entries
|
|
120
127
|
// printed nothing. The menu's whole pedagogical point is this line.
|
|
121
128
|
console.log(`\n▸ running:\n ${renderCommand(direct)}\n`);
|
|
129
|
+
// §129: stashed even for non-produce choices — the invariant is that
|
|
130
|
+
// the stash always mirrors the parse being entered, and only
|
|
131
|
+
// produce's recording ever reads it (consume-on-read keeps a
|
|
132
|
+
// non-produce stash from leaking past this parse).
|
|
133
|
+
setReplayArgv(direct);
|
|
122
134
|
await program.parseAsync(["node", "ossclip", ...direct]);
|
|
123
135
|
return;
|
|
124
136
|
}
|
|
@@ -126,6 +138,7 @@ export function buildProgram(): Command {
|
|
|
126
138
|
const { loadConfig } = await import("@ossclip/core");
|
|
127
139
|
const argv = await produceWizard({ speaker: loadConfig().speaker });
|
|
128
140
|
console.log(`\n▸ running:\n ${renderCommand(argv)}\n`);
|
|
141
|
+
setReplayArgv(argv); // §129
|
|
129
142
|
await program.parseAsync(["node", "ossclip", ...argv]);
|
|
130
143
|
});
|
|
131
144
|
|
|
@@ -263,6 +276,7 @@ export function buildProgram(): Command {
|
|
|
263
276
|
console.log(`\n▸ running:\n ${renderCommand(argv)}\n`);
|
|
264
277
|
// Re-entering the SAME parse the flags take: the zod checks below run
|
|
265
278
|
// on wizard output exactly as they do on a typed command line.
|
|
279
|
+
setReplayArgv(argv); // §129
|
|
266
280
|
await program.parseAsync(["node", "ossclip", ...argv]);
|
|
267
281
|
return;
|
|
268
282
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What command.json must record: the argv of the parse that ACTUALLY ran
|
|
3
|
+
* (§129).
|
|
4
|
+
*
|
|
5
|
+
* `produce` records its invocation into the workdir so the editor's Render
|
|
6
|
+
* button can replay it byte for byte. It used to record `process.argv`,
|
|
7
|
+
* which is the truth for exactly one entry point — a directly typed
|
|
8
|
+
* `ossclip produce …`. The wizard has always BUILT a produce argv and
|
|
9
|
+
* re-entered `program.parseAsync(["node", "ossclip", ...argv])`, and the
|
|
10
|
+
* bare-path route does the same; in both, process.argv still holds the
|
|
11
|
+
* ORIGINAL invocation — no `produce` literal, none of the wizard's answers —
|
|
12
|
+
* so every re-entered run recorded a command that replays as
|
|
13
|
+
* `ossclip <path> --llm …` and dies at commander's front door with
|
|
14
|
+
* "error: unknown option '--llm'" (§129's field artifact). The re-entry
|
|
15
|
+
* sites in program.ts stash the argv they are about to parse here; the
|
|
16
|
+
* recording prefers the stash and falls back to process.argv, which keeps
|
|
17
|
+
* the direct path byte-identical to what it always wrote.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
let stashed: string[] | null = null;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Called by every parseAsync re-entry in program.ts, immediately before the
|
|
24
|
+
* parse, with the argv minus its ["node", "ossclip"] prefix. Copied so a
|
|
25
|
+
* caller reusing its array cannot retroactively edit the record.
|
|
26
|
+
*/
|
|
27
|
+
export function setReplayArgv(argv: string[]): void {
|
|
28
|
+
stashed = [...argv];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Consume-on-read (§129): commander 12 keeps option state across parseAsync
|
|
33
|
+
* calls (see the bare-`produce` refusal in program.ts), and a stash kept
|
|
34
|
+
* across parses would be the same trap one layer up — a menu choice that
|
|
35
|
+
* never reaches `produce` must not leave its argv behind for a later
|
|
36
|
+
* recording in the same process to mistake for its own.
|
|
37
|
+
*/
|
|
38
|
+
export function consumeReplayArgv(): string[] | null {
|
|
39
|
+
const argv = stashed;
|
|
40
|
+
stashed = null;
|
|
41
|
+
return argv;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The args `produce` writes into command.json: the argv of the parse that
|
|
46
|
+
* ran (stash for a re-entered wizard/bare-path run, process.argv for a
|
|
47
|
+
* directly typed one), plus the §75/§93g pins. The pins guard on
|
|
48
|
+
* `includes` so a flag the user actually typed — or a pin recorded by the
|
|
49
|
+
* run a replay is re-running — is never appended twice.
|
|
50
|
+
*/
|
|
51
|
+
export function recordedProduceArgs(pins: { llm?: string; clipWindow?: string }): string[] {
|
|
52
|
+
const args = consumeReplayArgv() ?? process.argv.slice(2);
|
|
53
|
+
if (pins.llm !== undefined && !args.includes("--llm")) {
|
|
54
|
+
args.push("--llm", pins.llm);
|
|
55
|
+
}
|
|
56
|
+
if (pins.clipWindow !== undefined && !args.includes("--clip-window")) {
|
|
57
|
+
args.push("--clip-window", pins.clipWindow);
|
|
58
|
+
}
|
|
59
|
+
return args;
|
|
60
|
+
}
|