@vibeframe/cli 0.84.0 → 0.86.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/dist/index.js +218 -49
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -51503,22 +51503,30 @@ var init_key_live_test = __esm({
|
|
|
51503
51503
|
signal
|
|
51504
51504
|
)
|
|
51505
51505
|
},
|
|
51506
|
-
// The three below still have no cheap authenticated GET as of
|
|
51507
|
-
//
|
|
51508
|
-
//
|
|
51509
|
-
//
|
|
51510
|
-
// blank-check.
|
|
51506
|
+
// The three below still have no cheap authenticated GET as of
|
|
51507
|
+
// 2026-04-30 (re-checked while shipping v0.85). Surface them as
|
|
51508
|
+
// `skipped` so the user knows we didn't blank-check; revisit when a
|
|
51509
|
+
// provider publishes an account/me endpoint.
|
|
51511
51510
|
fal: {
|
|
51511
|
+
// Probed: /api/account, /v1/account, /v1/me, /queue/health, /v1/users/me,
|
|
51512
|
+
// /api/users/me, /v1/billing — all 404 with the standard `Authorization:
|
|
51513
|
+
// Key <key>` header. The platform docs reference an "Accounts API" but
|
|
51514
|
+
// its endpoint paths are unpublished as of this check.
|
|
51512
51515
|
name: "fal.ai",
|
|
51513
|
-
test: async () => ({ ok: false, skipped: true, message: "no auth-only endpoint
|
|
51516
|
+
test: async () => ({ ok: false, skipped: true, message: "no auth-only endpoint (checked 2026-04-30)" })
|
|
51514
51517
|
},
|
|
51515
51518
|
kling: {
|
|
51519
|
+
// Per-request HMAC signing means we'd need to sign every probe with the
|
|
51520
|
+
// key's secret half plus a timestamp — possible, but the cheapest
|
|
51521
|
+
// signed endpoint is also a generation request. Not worth the code.
|
|
51516
51522
|
name: "Kling",
|
|
51517
|
-
test: async () => ({ ok: false, skipped: true, message: "
|
|
51523
|
+
test: async () => ({ ok: false, skipped: true, message: "HMAC-signed per request (checked 2026-04-30)" })
|
|
51518
51524
|
},
|
|
51519
51525
|
imgbb: {
|
|
51526
|
+
// Only documented route is POST /1/upload. Probing it would consume an
|
|
51527
|
+
// upload quota even if we send a 1×1 PNG, so SKIP is the right call.
|
|
51520
51528
|
name: "ImgBB",
|
|
51521
|
-
test: async () => ({ ok: false, skipped: true, message: "only
|
|
51529
|
+
test: async () => ({ ok: false, skipped: true, message: "only route is upload (checked 2026-04-30)" })
|
|
51522
51530
|
}
|
|
51523
51531
|
};
|
|
51524
51532
|
}
|
|
@@ -52669,14 +52677,14 @@ __export(ai_audio_exports, {
|
|
|
52669
52677
|
});
|
|
52670
52678
|
import { resolve as resolve54, dirname as dirname19, basename as basename18, extname as extname12 } from "node:path";
|
|
52671
52679
|
import { readFile as readFile36, writeFile as writeFile40 } from "node:fs/promises";
|
|
52672
|
-
import { existsSync as
|
|
52680
|
+
import { existsSync as existsSync38 } from "node:fs";
|
|
52673
52681
|
async function executeTranscribe(options) {
|
|
52674
52682
|
const { audioPath, language, output, format, apiKey } = options;
|
|
52675
52683
|
try {
|
|
52676
52684
|
const key = apiKey || process.env.OPENAI_API_KEY;
|
|
52677
52685
|
if (!key) return { success: false, error: "OPENAI_API_KEY required" };
|
|
52678
52686
|
const absPath = resolve54(process.cwd(), audioPath);
|
|
52679
|
-
if (!
|
|
52687
|
+
if (!existsSync38(absPath)) return { success: false, error: `File not found: ${absPath}` };
|
|
52680
52688
|
const whisper = new WhisperProvider();
|
|
52681
52689
|
await whisper.initialize({ apiKey: key });
|
|
52682
52690
|
const audioBuffer = await readFile36(absPath);
|
|
@@ -52709,7 +52717,7 @@ async function executeIsolate(options) {
|
|
|
52709
52717
|
const key = apiKey || process.env.ELEVENLABS_API_KEY;
|
|
52710
52718
|
if (!key) return { success: false, error: "ELEVENLABS_API_KEY required" };
|
|
52711
52719
|
const absPath = resolve54(process.cwd(), audioPath);
|
|
52712
|
-
if (!
|
|
52720
|
+
if (!existsSync38(absPath)) return { success: false, error: `File not found: ${absPath}` };
|
|
52713
52721
|
const audioBuffer = await readFile36(absPath);
|
|
52714
52722
|
const elevenlabs = new ElevenLabsProvider();
|
|
52715
52723
|
await elevenlabs.initialize({ apiKey: key });
|
|
@@ -52735,7 +52743,7 @@ async function executeVoiceClone(options) {
|
|
|
52735
52743
|
const audioBuffers = [];
|
|
52736
52744
|
for (const samplePath of samplePaths) {
|
|
52737
52745
|
const absPath = resolve54(process.cwd(), samplePath);
|
|
52738
|
-
if (!
|
|
52746
|
+
if (!existsSync38(absPath)) return { success: false, error: `File not found: ${samplePath}` };
|
|
52739
52747
|
const buffer = await readFile36(absPath);
|
|
52740
52748
|
audioBuffers.push(buffer);
|
|
52741
52749
|
}
|
|
@@ -52757,7 +52765,7 @@ async function executeDub(options) {
|
|
|
52757
52765
|
const { mediaPath, language, source, voice, analyzeOnly, output } = options;
|
|
52758
52766
|
try {
|
|
52759
52767
|
const absPath = resolve54(process.cwd(), mediaPath);
|
|
52760
|
-
if (!
|
|
52768
|
+
if (!existsSync38(absPath)) return { success: false, error: `File not found: ${absPath}` };
|
|
52761
52769
|
const openaiKey = process.env.OPENAI_API_KEY;
|
|
52762
52770
|
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
52763
52771
|
const elevenlabsKey = process.env.ELEVENLABS_API_KEY;
|
|
@@ -52885,8 +52893,8 @@ async function executeDuck(options) {
|
|
|
52885
52893
|
if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
|
|
52886
52894
|
const absMusicPath = resolve54(process.cwd(), musicPath);
|
|
52887
52895
|
const absVoicePath = resolve54(process.cwd(), voicePath);
|
|
52888
|
-
if (!
|
|
52889
|
-
if (!
|
|
52896
|
+
if (!existsSync38(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
|
|
52897
|
+
if (!existsSync38(absVoicePath)) return { success: false, error: `Voice file not found: ${absVoicePath}` };
|
|
52890
52898
|
const defaultOutput = resolve54(dirname19(absMusicPath), `${basename18(absMusicPath, extname12(absMusicPath))}-ducked${extname12(absMusicPath)}`);
|
|
52891
52899
|
const outputPath = resolve54(process.cwd(), output || defaultOutput);
|
|
52892
52900
|
const filter = `[1:a]asplit=2[sc][mix];[0:a][sc]sidechaincompress=threshold=${threshold}dB:ratio=${ratio}:attack=${attack}:release=${release}[ducked];[ducked][mix]amix=inputs=2:duration=longest`;
|
|
@@ -52900,7 +52908,7 @@ async function executeDuck(options) {
|
|
|
52900
52908
|
"-y",
|
|
52901
52909
|
outputPath
|
|
52902
52910
|
], { timeout: 12e4, maxBuffer: 50 * 1024 * 1024 });
|
|
52903
|
-
if (!
|
|
52911
|
+
if (!existsSync38(outputPath)) return { success: false, error: "FFmpeg failed to create output" };
|
|
52904
52912
|
return { success: true, outputPath };
|
|
52905
52913
|
} catch (error) {
|
|
52906
52914
|
return { success: false, error: `Audio ducking failed: ${error instanceof Error ? error.message : String(error)}` };
|
|
@@ -52918,7 +52926,7 @@ var init_ai_audio = __esm({
|
|
|
52918
52926
|
// src/pipeline/executor.ts
|
|
52919
52927
|
import { resolve as resolve55 } from "node:path";
|
|
52920
52928
|
import { readFile as readFile37, writeFile as writeFile41, mkdir as mkdir22 } from "node:fs/promises";
|
|
52921
|
-
import { existsSync as
|
|
52929
|
+
import { existsSync as existsSync39 } from "node:fs";
|
|
52922
52930
|
function maxCostFor(action) {
|
|
52923
52931
|
const cmd = ACTION_METADATA[action]?.command;
|
|
52924
52932
|
if (!cmd) return 0;
|
|
@@ -53135,7 +53143,7 @@ async function ensureActionsRegistered() {
|
|
|
53135
53143
|
}
|
|
53136
53144
|
async function loadPipeline(filePath) {
|
|
53137
53145
|
const absPath = resolve55(process.cwd(), filePath);
|
|
53138
|
-
if (!
|
|
53146
|
+
if (!existsSync39(absPath)) {
|
|
53139
53147
|
throw new Error(`Pipeline file not found: ${absPath}`);
|
|
53140
53148
|
}
|
|
53141
53149
|
const content = await readFile37(absPath, "utf-8");
|
|
@@ -53160,7 +53168,7 @@ async function executePipeline(manifest2, options = {}) {
|
|
|
53160
53168
|
const startTime = Date.now();
|
|
53161
53169
|
if (options.resume) {
|
|
53162
53170
|
const checkpointPath = resolve55(outputDir, CHECKPOINT_FILE);
|
|
53163
|
-
if (
|
|
53171
|
+
if (existsSync39(checkpointPath)) {
|
|
53164
53172
|
const checkpointContent = await readFile37(checkpointPath, "utf-8");
|
|
53165
53173
|
const checkpoint = (0, import_yaml8.parse)(checkpointContent);
|
|
53166
53174
|
for (const cs of checkpoint.completedSteps) {
|
|
@@ -59763,7 +59771,7 @@ var init_generate2 = __esm({
|
|
|
59763
59771
|
// src/tools/manifest/remix.ts
|
|
59764
59772
|
import { writeFile as writeFile43 } from "node:fs/promises";
|
|
59765
59773
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
59766
|
-
import { join as
|
|
59774
|
+
import { join as join14 } from "node:path";
|
|
59767
59775
|
var pipelineHighlightsTool, pipelineAutoShortsTool, pipelineRunTool, pipelineRegenerateSceneTool, remixTools;
|
|
59768
59776
|
var init_remix = __esm({
|
|
59769
59777
|
"src/tools/manifest/remix.ts"() {
|
|
@@ -59871,7 +59879,7 @@ var init_remix = __esm({
|
|
|
59871
59879
|
let resolvedPath = args.pipelinePath;
|
|
59872
59880
|
let tempPath;
|
|
59873
59881
|
if (args.pipelineYaml) {
|
|
59874
|
-
tempPath =
|
|
59882
|
+
tempPath = join14(tmpdir3(), `vibe-mcp-pipeline-${Date.now()}.yaml`);
|
|
59875
59883
|
await writeFile43(tempPath, args.pipelineYaml, "utf-8");
|
|
59876
59884
|
resolvedPath = tempPath;
|
|
59877
59885
|
}
|
|
@@ -60628,7 +60636,7 @@ __export(project_builder_exports, {
|
|
|
60628
60636
|
resolveSourceUrl: () => resolveSourceUrl
|
|
60629
60637
|
});
|
|
60630
60638
|
import { mkdtemp, mkdir as mkdir24, copyFile as copyFile3, writeFile as writeFile47, rm as rm4, readdir as readdir3 } from "node:fs/promises";
|
|
60631
|
-
import { existsSync as
|
|
60639
|
+
import { existsSync as existsSync40 } from "node:fs";
|
|
60632
60640
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
60633
60641
|
import { createRequire } from "node:module";
|
|
60634
60642
|
import * as path from "node:path";
|
|
@@ -60640,7 +60648,7 @@ async function buildTempProject(state, projectFileDir) {
|
|
|
60640
60648
|
for (const source of state.sources) {
|
|
60641
60649
|
if (copied.has(source.id)) continue;
|
|
60642
60650
|
const resolved = resolveSourceUrl(source.url, projectFileDir);
|
|
60643
|
-
if (!
|
|
60651
|
+
if (!existsSync40(resolved)) {
|
|
60644
60652
|
throw new Error(`Source file not found: ${resolved} (from source id=${source.id})`);
|
|
60645
60653
|
}
|
|
60646
60654
|
const dest = path.join(assetsDir, path.basename(resolved));
|
|
@@ -61669,7 +61677,7 @@ var init_export2 = __esm({
|
|
|
61669
61677
|
|
|
61670
61678
|
// src/tools/manifest/agent-only.ts
|
|
61671
61679
|
import { readFile as readFile41, writeFile as writeFile48, readdir as readdir4, stat as stat6, access as access9, unlink as unlink4 } from "node:fs/promises";
|
|
61672
|
-
import { resolve as resolve63, join as
|
|
61680
|
+
import { resolve as resolve63, join as join16, basename as basename22, extname as extname13 } from "node:path";
|
|
61673
61681
|
function matchPattern(filename, pattern) {
|
|
61674
61682
|
const regex2 = new RegExp(
|
|
61675
61683
|
"^" + pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".") + "$",
|
|
@@ -61752,7 +61760,7 @@ var init_agent_only = __esm({
|
|
|
61752
61760
|
const results = [];
|
|
61753
61761
|
for (const entry of entries) {
|
|
61754
61762
|
if (args.pattern && !matchPattern(entry.name, args.pattern)) continue;
|
|
61755
|
-
const fullPath =
|
|
61763
|
+
const fullPath = join16(absPath, entry.name);
|
|
61756
61764
|
const stats = await stat6(fullPath);
|
|
61757
61765
|
if (entry.isDirectory()) {
|
|
61758
61766
|
results.push(`[DIR] ${entry.name}/`);
|
|
@@ -61857,7 +61865,7 @@ var init_agent_only = __esm({
|
|
|
61857
61865
|
const scanDir = async (dir) => {
|
|
61858
61866
|
const entries = await readdir4(dir, { withFileTypes: true });
|
|
61859
61867
|
for (const entry of entries) {
|
|
61860
|
-
const entryPath =
|
|
61868
|
+
const entryPath = join16(dir, entry.name);
|
|
61861
61869
|
if (entry.isDirectory() && recursive) {
|
|
61862
61870
|
await scanDir(entryPath);
|
|
61863
61871
|
} else if (entry.isFile()) {
|
|
@@ -62757,6 +62765,10 @@ function toAgentDefinition(t) {
|
|
|
62757
62765
|
return {
|
|
62758
62766
|
name: t.name,
|
|
62759
62767
|
description: t.description,
|
|
62768
|
+
// Propagate the manifest's cost tier so the agent's executor can
|
|
62769
|
+
// mandate a confirm prompt for high-spend tools without needing a
|
|
62770
|
+
// parallel SSOT.
|
|
62771
|
+
cost: t.cost,
|
|
62760
62772
|
parameters: {
|
|
62761
62773
|
type: "object",
|
|
62762
62774
|
properties,
|
|
@@ -62825,7 +62837,7 @@ init_source();
|
|
|
62825
62837
|
// package.json
|
|
62826
62838
|
var package_default = {
|
|
62827
62839
|
name: "@vibeframe/cli",
|
|
62828
|
-
version: "0.
|
|
62840
|
+
version: "0.86.0",
|
|
62829
62841
|
description: "VibeFrame CLI - natural language video editing from the terminal",
|
|
62830
62842
|
private: false,
|
|
62831
62843
|
license: "MIT",
|
|
@@ -70948,8 +70960,10 @@ init_esm();
|
|
|
70948
70960
|
init_source();
|
|
70949
70961
|
init_ora();
|
|
70950
70962
|
init_scene_build();
|
|
70963
|
+
init_storyboard_parse();
|
|
70951
70964
|
init_output();
|
|
70952
|
-
import { resolve as resolve48 } from "node:path";
|
|
70965
|
+
import { resolve as resolve48, join as join13 } from "node:path";
|
|
70966
|
+
import { readFileSync as readFileSync2, existsSync as existsSync35 } from "node:fs";
|
|
70953
70967
|
var VALID_MODES = ["agent", "batch", "auto"];
|
|
70954
70968
|
var buildCommand = new Command("build").description("Build a VibeFrame video project from STORYBOARD.md").argument("[project-dir]", "Video project directory", ".").option("--mode <mode>", "Build mode: agent|batch|auto", "auto").option("--effort <level>", "Compose effort tier (batch mode only): low|medium|high", "medium").option("--composer <provider>", "Batch composer: claude|openai|gemini").option("--skip-narration", "Don't dispatch TTS even when beats declare narration cues").option("--skip-backdrop", "Don't dispatch image-gen even when beats declare backdrop cues").option("--skip-render", "Compose only \u2014 don't render to MP4").option("--tts <provider>", "TTS provider: auto|elevenlabs|kokoro").option("--voice <id>", "Voice id").option("--image-provider <name>", "Image provider: openai").option("--quality <q>", "Image quality: standard|hd", "hd").option("--image-size <s>", "Image size: 1024x1024|1536x1024|1024x1536", "1536x1024").option("--force", "Re-dispatch primitives even when assets already exist").option("--dry-run", "Preview parameters without dispatching").addHelpText("after", `
|
|
70955
70969
|
Examples:
|
|
@@ -71043,9 +71057,53 @@ function printBuildDryRun(projectDirArg, params) {
|
|
|
71043
71057
|
console.log(` Image: ${source_default.bold(String(params.imageProvider ?? "openai"))} ${source_default.dim(`${params.imageQuality} ${params.imageSize}`)}${params.skipBackdrop ? source_default.dim(" (skipped)") : ""}`);
|
|
71044
71058
|
console.log(` Render: ${source_default.bold(params.skipRender ? "skip" : "yes")}`);
|
|
71045
71059
|
console.log(` Regenerate: ${source_default.bold(params.force ? "yes" : "cache when possible")}`);
|
|
71060
|
+
const estimate = estimateBuildCost(params);
|
|
71061
|
+
if (estimate) {
|
|
71062
|
+
console.log();
|
|
71063
|
+
console.log(source_default.bold.cyan("Estimated cost (tier-derived, conservative)"));
|
|
71064
|
+
console.log(source_default.dim("-".repeat(60)));
|
|
71065
|
+
console.log(` Beats: ${source_default.bold(estimate.beats)}`);
|
|
71066
|
+
if (!params.skipNarration) {
|
|
71067
|
+
console.log(` Narration: ${source_default.cyan(`$${estimate.narrationUsd.toFixed(2)}`)} ${source_default.dim(`(${estimate.beats} \xD7 low)`)}`);
|
|
71068
|
+
}
|
|
71069
|
+
if (!params.skipBackdrop) {
|
|
71070
|
+
console.log(` Backdrops: ${source_default.yellow(`$${estimate.backdropUsd.toFixed(2)}`)} ${source_default.dim(`(${estimate.beats} \xD7 high)`)}`);
|
|
71071
|
+
}
|
|
71072
|
+
if (params.mode !== "agent") {
|
|
71073
|
+
console.log(` Compose (LLM): ${source_default.cyan(`$${estimate.composeUsd.toFixed(2)}`)} ${source_default.dim(`(${estimate.beats} \xD7 ~$0.06 batch mode)`)}`);
|
|
71074
|
+
} else {
|
|
71075
|
+
console.log(` Compose: ${source_default.dim(`$0.00 (agent mode \u2014 host LLM does composition)`)}`);
|
|
71076
|
+
}
|
|
71077
|
+
console.log(` ${source_default.bold("Total:")} ${source_default.bold(`$${estimate.totalUsd.toFixed(2)}`)}`);
|
|
71078
|
+
}
|
|
71046
71079
|
console.log();
|
|
71047
71080
|
console.log(source_default.dim("No assets, provider calls, or video files were created."));
|
|
71048
71081
|
}
|
|
71082
|
+
function estimateBuildCost(params) {
|
|
71083
|
+
const storyboardPath = join13(params.projectDir, "STORYBOARD.md");
|
|
71084
|
+
if (!existsSync35(storyboardPath)) return null;
|
|
71085
|
+
let beats = 0;
|
|
71086
|
+
try {
|
|
71087
|
+
const md = readFileSync2(storyboardPath, "utf-8");
|
|
71088
|
+
beats = parseStoryboard(md).beats.length;
|
|
71089
|
+
} catch {
|
|
71090
|
+
return null;
|
|
71091
|
+
}
|
|
71092
|
+
if (beats === 0) return null;
|
|
71093
|
+
const TTS_PER_BEAT = 0.05;
|
|
71094
|
+
const IMAGE_PER_BEAT = 3;
|
|
71095
|
+
const COMPOSE_PER_BEAT_BATCH = 0.06;
|
|
71096
|
+
const narrationUsd = params.skipNarration ? 0 : beats * TTS_PER_BEAT;
|
|
71097
|
+
const backdropUsd = params.skipBackdrop ? 0 : beats * IMAGE_PER_BEAT;
|
|
71098
|
+
const composeUsd = params.mode === "agent" ? 0 : beats * COMPOSE_PER_BEAT_BATCH;
|
|
71099
|
+
return {
|
|
71100
|
+
beats,
|
|
71101
|
+
narrationUsd,
|
|
71102
|
+
backdropUsd,
|
|
71103
|
+
composeUsd,
|
|
71104
|
+
totalUsd: narrationUsd + backdropUsd + composeUsd
|
|
71105
|
+
};
|
|
71106
|
+
}
|
|
71049
71107
|
function printNeedsAuthor(result) {
|
|
71050
71108
|
console.log(source_default.cyan("Agent authoring required"));
|
|
71051
71109
|
console.log();
|
|
@@ -71254,10 +71312,11 @@ init_exec_safe();
|
|
|
71254
71312
|
init_api_key();
|
|
71255
71313
|
init_agent_host_detect();
|
|
71256
71314
|
init_composer_resolve();
|
|
71315
|
+
init_cost_tier();
|
|
71257
71316
|
init_scene_build();
|
|
71258
71317
|
init_output();
|
|
71259
71318
|
import { access as access7 } from "node:fs/promises";
|
|
71260
|
-
import { existsSync as
|
|
71319
|
+
import { existsSync as existsSync36 } from "node:fs";
|
|
71261
71320
|
import { resolve as resolve50 } from "node:path";
|
|
71262
71321
|
var COMMAND_KEY_MAP = getCommandKeyMap();
|
|
71263
71322
|
var FREE_COMMANDS = [
|
|
@@ -71295,7 +71354,10 @@ Examples:
|
|
|
71295
71354
|
});
|
|
71296
71355
|
return;
|
|
71297
71356
|
}
|
|
71298
|
-
printReport(results, {
|
|
71357
|
+
printReport(results, {
|
|
71358
|
+
verbose: Boolean(options.verbose),
|
|
71359
|
+
program: doctorCommand.parent
|
|
71360
|
+
});
|
|
71299
71361
|
if (options.testKeys) {
|
|
71300
71362
|
await runLiveKeyTests(results);
|
|
71301
71363
|
}
|
|
@@ -71376,20 +71438,20 @@ async function runDiagnostics() {
|
|
|
71376
71438
|
installed = false;
|
|
71377
71439
|
}
|
|
71378
71440
|
} else if (toolName === "chrome") {
|
|
71379
|
-
const { existsSync:
|
|
71441
|
+
const { existsSync: existsSync41 } = await import("node:fs");
|
|
71380
71442
|
const { homedir: homedir6 } = await import("node:os");
|
|
71381
|
-
const { join:
|
|
71443
|
+
const { join: join17 } = await import("node:path");
|
|
71382
71444
|
const chromePaths = [
|
|
71383
71445
|
process.env.HYPERFRAMES_CHROME_PATH,
|
|
71384
71446
|
process.env.CHROME_PATH,
|
|
71385
|
-
|
|
71447
|
+
join17(homedir6(), ".cache", "puppeteer", "chrome-headless-shell", "mac_arm-147.0.7727.56", "chrome-headless-shell-mac_arm", "chrome-headless-shell"),
|
|
71386
71448
|
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
71387
71449
|
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
71388
71450
|
"/usr/bin/google-chrome",
|
|
71389
71451
|
"/usr/bin/chromium",
|
|
71390
71452
|
"/usr/bin/chromium-browser"
|
|
71391
71453
|
];
|
|
71392
|
-
installed = chromePaths.some((p) => p &&
|
|
71454
|
+
installed = chromePaths.some((p) => p && existsSync41(p));
|
|
71393
71455
|
} else {
|
|
71394
71456
|
installed = commandExists(toolName);
|
|
71395
71457
|
}
|
|
@@ -71420,7 +71482,7 @@ async function runDiagnostics() {
|
|
|
71420
71482
|
const cwd = process.cwd();
|
|
71421
71483
|
const projectFiles = ["AGENTS.md", "CLAUDE.md", "vibe.project.yaml"].map((rel) => ({
|
|
71422
71484
|
path: rel,
|
|
71423
|
-
exists:
|
|
71485
|
+
exists: existsSync36(resolve50(cwd, rel))
|
|
71424
71486
|
}));
|
|
71425
71487
|
const projectInitialized = projectFiles.some((f) => f.exists);
|
|
71426
71488
|
const hosts = detectAgentHosts();
|
|
@@ -71435,8 +71497,8 @@ async function runDiagnostics() {
|
|
|
71435
71497
|
} catch (err) {
|
|
71436
71498
|
if (!(err instanceof ComposerResolveError)) throw err;
|
|
71437
71499
|
}
|
|
71438
|
-
const sceneProjectInCwd =
|
|
71439
|
-
const skillInstalled =
|
|
71500
|
+
const sceneProjectInCwd = existsSync36(resolve50(cwd, "STORYBOARD.md"));
|
|
71501
|
+
const skillInstalled = existsSync36(resolve50(cwd, "SKILL.md"));
|
|
71440
71502
|
return {
|
|
71441
71503
|
system: {
|
|
71442
71504
|
node: { version: nodeVersion, ok: true },
|
|
@@ -71462,7 +71524,7 @@ async function runDiagnostics() {
|
|
|
71462
71524
|
};
|
|
71463
71525
|
}
|
|
71464
71526
|
function printReport(results, opts = { verbose: false }) {
|
|
71465
|
-
const { verbose } = opts;
|
|
71527
|
+
const { verbose, program: program3 } = opts;
|
|
71466
71528
|
console.log();
|
|
71467
71529
|
console.log(source_default.bold(" System"));
|
|
71468
71530
|
const nodeIcon = results.system.node.ok ? source_default.green("OK") : source_default.red("MISSING");
|
|
@@ -71599,12 +71661,40 @@ function printReport(results, opts = { verbose: false }) {
|
|
|
71599
71661
|
console.log(
|
|
71600
71662
|
source_default.dim(` Full command catalog: vibe schema --list`)
|
|
71601
71663
|
);
|
|
71664
|
+
if (program3) {
|
|
71665
|
+
const tierCounts = countCostTiers(program3);
|
|
71666
|
+
const total = tierCounts.free + tierCounts.low + tierCounts.high + tierCounts["very-high"];
|
|
71667
|
+
if (total > 0) {
|
|
71668
|
+
const parts = [
|
|
71669
|
+
`${TIER_COLOR.free(`${tierCounts.free} free`)}`,
|
|
71670
|
+
`${TIER_COLOR.low(`${tierCounts.low} low`)}`,
|
|
71671
|
+
`${TIER_COLOR.high(`${tierCounts.high} high`)}`,
|
|
71672
|
+
`${TIER_COLOR["very-high"](`${tierCounts["very-high"]} very-high`)}`
|
|
71673
|
+
];
|
|
71674
|
+
console.log(source_default.dim(` Cost mix:`) + ` ${parts.join(source_default.dim(", "))}`);
|
|
71675
|
+
}
|
|
71676
|
+
}
|
|
71602
71677
|
const nextStep = pickNextStep(results, missing.length > 0);
|
|
71603
71678
|
if (nextStep) {
|
|
71604
71679
|
console.log(source_default.dim(` ${nextStep}`));
|
|
71605
71680
|
}
|
|
71606
71681
|
console.log();
|
|
71607
71682
|
}
|
|
71683
|
+
function countCostTiers(program3) {
|
|
71684
|
+
const counts = {
|
|
71685
|
+
"free": 0,
|
|
71686
|
+
"low": 0,
|
|
71687
|
+
"high": 0,
|
|
71688
|
+
"very-high": 0
|
|
71689
|
+
};
|
|
71690
|
+
const walk = (cmd) => {
|
|
71691
|
+
const tier = getCostTier(cmd);
|
|
71692
|
+
if (tier) counts[tier]++;
|
|
71693
|
+
for (const child of cmd.commands) walk(child);
|
|
71694
|
+
};
|
|
71695
|
+
for (const top of program3.commands) walk(top);
|
|
71696
|
+
return counts;
|
|
71697
|
+
}
|
|
71608
71698
|
async function runLiveKeyTests(results) {
|
|
71609
71699
|
console.log();
|
|
71610
71700
|
console.log(source_default.bold(" Live key tests"));
|
|
@@ -71657,7 +71747,7 @@ init_detect();
|
|
|
71657
71747
|
init_ai_edit();
|
|
71658
71748
|
init_output();
|
|
71659
71749
|
import { resolve as resolve51 } from "node:path";
|
|
71660
|
-
import { existsSync as
|
|
71750
|
+
import { existsSync as existsSync37 } from "node:fs";
|
|
71661
71751
|
import { mkdir as mkdir21, rm as rm3 } from "node:fs/promises";
|
|
71662
71752
|
var DEMO_DIR = resolve51(process.cwd(), ".vibeframe-demo");
|
|
71663
71753
|
async function generateTestVideo(outputPath) {
|
|
@@ -71688,7 +71778,7 @@ async function generateTestVideo(outputPath) {
|
|
|
71688
71778
|
"-y",
|
|
71689
71779
|
outputPath
|
|
71690
71780
|
], { timeout: 3e4, maxBuffer: 50 * 1024 * 1024 });
|
|
71691
|
-
return
|
|
71781
|
+
return existsSync37(outputPath);
|
|
71692
71782
|
} catch {
|
|
71693
71783
|
try {
|
|
71694
71784
|
await execSafe("ffmpeg", [
|
|
@@ -71714,7 +71804,7 @@ async function generateTestVideo(outputPath) {
|
|
|
71714
71804
|
"-y",
|
|
71715
71805
|
outputPath
|
|
71716
71806
|
], { timeout: 3e4, maxBuffer: 50 * 1024 * 1024 });
|
|
71717
|
-
return
|
|
71807
|
+
return existsSync37(outputPath);
|
|
71718
71808
|
} catch {
|
|
71719
71809
|
return false;
|
|
71720
71810
|
}
|
|
@@ -72448,10 +72538,26 @@ function maskSensitiveArgs(args) {
|
|
|
72448
72538
|
}
|
|
72449
72539
|
return masked;
|
|
72450
72540
|
}
|
|
72541
|
+
function estimateToolCost(cost) {
|
|
72542
|
+
if (!cost) return 0;
|
|
72543
|
+
switch (cost) {
|
|
72544
|
+
case "free":
|
|
72545
|
+
return 0;
|
|
72546
|
+
case "low":
|
|
72547
|
+
return 0.05;
|
|
72548
|
+
case "medium":
|
|
72549
|
+
return 0.5;
|
|
72550
|
+
case "high":
|
|
72551
|
+
return 3;
|
|
72552
|
+
case "very-high":
|
|
72553
|
+
return 25;
|
|
72554
|
+
}
|
|
72555
|
+
}
|
|
72451
72556
|
var AgentExecutor = class {
|
|
72452
72557
|
adapter = null;
|
|
72453
72558
|
registry;
|
|
72454
72559
|
memory;
|
|
72560
|
+
cumulativeUsd = 0;
|
|
72455
72561
|
context;
|
|
72456
72562
|
config;
|
|
72457
72563
|
initialized = false;
|
|
@@ -72539,12 +72645,39 @@ var AgentExecutor = class {
|
|
|
72539
72645
|
}
|
|
72540
72646
|
/**
|
|
72541
72647
|
* Execute a single tool
|
|
72648
|
+
*
|
|
72649
|
+
* Confirm-prompt logic:
|
|
72650
|
+
* - `noConfirm: true` → never prompt (automation / CI)
|
|
72651
|
+
* - `confirmAlways: true` → prompt for every tool call
|
|
72652
|
+
* - default → prompt only when the tool's
|
|
72653
|
+
* cost is high/very-high (the
|
|
72654
|
+
* `costGate`)
|
|
72655
|
+
*
|
|
72656
|
+
* The two non-trivial cases require `confirmCallback` to be set; if
|
|
72657
|
+
* it isn't, no prompt fires and the tool runs.
|
|
72542
72658
|
*/
|
|
72543
72659
|
async executeTool(toolCall) {
|
|
72544
|
-
|
|
72660
|
+
const def = this.registry.getDefinition(toolCall.name);
|
|
72661
|
+
const cost = def?.cost;
|
|
72662
|
+
const isExpensive = cost === "high" || cost === "very-high";
|
|
72663
|
+
if (this.config.budgetUsd !== void 0) {
|
|
72664
|
+
const estimate = estimateToolCost(cost);
|
|
72665
|
+
if (this.cumulativeUsd + estimate > this.config.budgetUsd) {
|
|
72666
|
+
const remaining = (this.config.budgetUsd - this.cumulativeUsd).toFixed(2);
|
|
72667
|
+
return {
|
|
72668
|
+
toolCallId: toolCall.id,
|
|
72669
|
+
success: false,
|
|
72670
|
+
output: "",
|
|
72671
|
+
error: `Budget exceeded: ${toolCall.name} estimated at $${estimate.toFixed(2)} but only $${remaining} remains of $${this.config.budgetUsd.toFixed(2)} cap.`
|
|
72672
|
+
};
|
|
72673
|
+
}
|
|
72674
|
+
}
|
|
72675
|
+
const shouldConfirm = !this.config.noConfirm && this.config.confirmCallback && (this.config.confirmAlways || isExpensive);
|
|
72676
|
+
if (shouldConfirm && this.config.confirmCallback) {
|
|
72545
72677
|
const confirmed = await this.config.confirmCallback(
|
|
72546
72678
|
toolCall.name,
|
|
72547
|
-
toolCall.arguments
|
|
72679
|
+
toolCall.arguments,
|
|
72680
|
+
cost
|
|
72548
72681
|
);
|
|
72549
72682
|
if (!confirmed) {
|
|
72550
72683
|
return {
|
|
@@ -72560,8 +72693,18 @@ var AgentExecutor = class {
|
|
|
72560
72693
|
this.context
|
|
72561
72694
|
);
|
|
72562
72695
|
result.toolCallId = toolCall.id;
|
|
72696
|
+
if (result.success) {
|
|
72697
|
+
this.cumulativeUsd += estimateToolCost(cost);
|
|
72698
|
+
}
|
|
72563
72699
|
return result;
|
|
72564
72700
|
}
|
|
72701
|
+
/**
|
|
72702
|
+
* Cumulative tier-estimated USD spent in this session. Updates after
|
|
72703
|
+
* every successful tool call. Useful for status output / `tools` REPL.
|
|
72704
|
+
*/
|
|
72705
|
+
getCumulativeUsd() {
|
|
72706
|
+
return this.cumulativeUsd;
|
|
72707
|
+
}
|
|
72565
72708
|
/**
|
|
72566
72709
|
* Update context (e.g., when project changes)
|
|
72567
72710
|
*/
|
|
@@ -72601,7 +72744,7 @@ init_config();
|
|
|
72601
72744
|
init_tty();
|
|
72602
72745
|
init_api_key();
|
|
72603
72746
|
init_output();
|
|
72604
|
-
async function promptConfirm2(toolName, args) {
|
|
72747
|
+
async function promptConfirm2(toolName, args, cost) {
|
|
72605
72748
|
const { createInterface: createInterface4 } = await import("node:readline");
|
|
72606
72749
|
const rl = createInterface4({
|
|
72607
72750
|
input: process.stdin,
|
|
@@ -72611,7 +72754,15 @@ async function promptConfirm2(toolName, args) {
|
|
|
72611
72754
|
return new Promise((resolve64) => {
|
|
72612
72755
|
const argsStr = JSON.stringify(args, null, 2);
|
|
72613
72756
|
console.log();
|
|
72614
|
-
|
|
72757
|
+
const tierColor = {
|
|
72758
|
+
"free": source_default.green,
|
|
72759
|
+
"low": source_default.cyan,
|
|
72760
|
+
"medium": source_default.yellow,
|
|
72761
|
+
"high": source_default.yellow,
|
|
72762
|
+
"very-high": source_default.red
|
|
72763
|
+
};
|
|
72764
|
+
const costBadge = cost ? ` ${tierColor[cost](`[${cost.toUpperCase()}]`)}` : "";
|
|
72765
|
+
console.log(source_default.yellow(`Execute ${source_default.bold(toolName)}?${costBadge}`));
|
|
72615
72766
|
console.log(source_default.dim(argsStr));
|
|
72616
72767
|
rl.question(source_default.cyan("(y/n): "), (answer) => {
|
|
72617
72768
|
rl.close();
|
|
@@ -72621,7 +72772,12 @@ async function promptConfirm2(toolName, args) {
|
|
|
72621
72772
|
}
|
|
72622
72773
|
async function startAgent(options = {}) {
|
|
72623
72774
|
const isNonInteractive = !!options.input;
|
|
72624
|
-
const
|
|
72775
|
+
const confirmAlways = options.confirm || false;
|
|
72776
|
+
const noConfirm = options.noConfirm || false;
|
|
72777
|
+
const budgetUsd = options.budgetUsd ? parseFloat(options.budgetUsd) : void 0;
|
|
72778
|
+
if (budgetUsd !== void 0 && (Number.isNaN(budgetUsd) || budgetUsd < 0)) {
|
|
72779
|
+
exitWithError(generalError(`Invalid --budget-usd: ${options.budgetUsd}`, "Must be a non-negative number (e.g., --budget-usd 5)"));
|
|
72780
|
+
}
|
|
72625
72781
|
if (!isNonInteractive && !hasTTY()) {
|
|
72626
72782
|
exitWithError(generalError("Agent mode requires a terminal.", "Run 'vibe agent' directly from your terminal, or use --input <query> for non-interactive mode."));
|
|
72627
72783
|
}
|
|
@@ -72658,7 +72814,13 @@ async function startAgent(options = {}) {
|
|
|
72658
72814
|
maxTurns,
|
|
72659
72815
|
verbose,
|
|
72660
72816
|
projectPath: options.project,
|
|
72661
|
-
|
|
72817
|
+
// Always supply the callback so the cost gate (high/very-high
|
|
72818
|
+
// tools) can prompt even without `--confirm`. The executor
|
|
72819
|
+
// decides whether to invoke based on `confirmAlways` / `noConfirm`.
|
|
72820
|
+
confirmCallback: noConfirm ? void 0 : promptConfirm2,
|
|
72821
|
+
confirmAlways,
|
|
72822
|
+
noConfirm,
|
|
72823
|
+
budgetUsd
|
|
72662
72824
|
});
|
|
72663
72825
|
await agent.initialize();
|
|
72664
72826
|
spinner2.succeed(source_default.green("Agent initialized"));
|
|
@@ -72695,9 +72857,11 @@ async function startAgent(options = {}) {
|
|
|
72695
72857
|
console.log();
|
|
72696
72858
|
console.log(source_default.dim(` v${version} \xB7 ${provider}${options.model ? ` \xB7 ${options.model}` : ""} \xB7 ${cwd}`));
|
|
72697
72859
|
console.log();
|
|
72860
|
+
const confirmStatus = noConfirm ? source_default.dim("no-confirm") : confirmAlways ? source_default.yellow("confirm mode") : source_default.dim("cost gate (high/very-high)");
|
|
72698
72861
|
const statusParts = [
|
|
72699
72862
|
source_default.green(`${toolCount} tools`),
|
|
72700
|
-
|
|
72863
|
+
confirmStatus,
|
|
72864
|
+
budgetUsd !== void 0 ? source_default.magenta(`budget: $${budgetUsd.toFixed(2)}`) : null,
|
|
72701
72865
|
options.project ? source_default.blue(`project: ${options.project}`) : null
|
|
72702
72866
|
].filter(Boolean);
|
|
72703
72867
|
console.log(source_default.dim(" ") + statusParts.join(source_default.dim(" \xB7 ")));
|
|
@@ -72750,6 +72914,11 @@ async function startAgent(options = {}) {
|
|
|
72750
72914
|
}
|
|
72751
72915
|
console.log();
|
|
72752
72916
|
console.log(source_default.dim(`Total: ${tools.length} tools`));
|
|
72917
|
+
const spent = agent.getCumulativeUsd();
|
|
72918
|
+
if (spent > 0 || budgetUsd !== void 0) {
|
|
72919
|
+
const budgetStr = budgetUsd !== void 0 ? ` / $${budgetUsd.toFixed(2)} cap` : "";
|
|
72920
|
+
console.log(source_default.dim(`Spent (tier-estimated): $${spent.toFixed(2)}${budgetStr}`));
|
|
72921
|
+
}
|
|
72753
72922
|
console.log();
|
|
72754
72923
|
rl.prompt();
|
|
72755
72924
|
return;
|
|
@@ -72811,7 +72980,7 @@ async function startAgent(options = {}) {
|
|
|
72811
72980
|
rl.prompt();
|
|
72812
72981
|
});
|
|
72813
72982
|
}
|
|
72814
|
-
var agentCommand = new Command("agent").description("Start the AI agent with natural language interface").option("-p, --provider <provider>", "LLM provider (openai, claude, gemini, ollama, xai, openrouter)", "openai").option("-m, --model <model>", "Model to use (provider-specific)").option("--project <path>", "Timeline file or directory to load").option("-v, --verbose", "Show verbose output including tool calls").option("--max-turns <n>", "Maximum turns per request", "10").option("-i, --input <query>", "Run a single query and exit (non-interactive)").option("-c, --confirm", "Confirm before
|
|
72983
|
+
var agentCommand = new Command("agent").description("Start the AI agent with natural language interface").option("-p, --provider <provider>", "LLM provider (openai, claude, gemini, ollama, xai, openrouter)", "openai").option("-m, --model <model>", "Model to use (provider-specific)").option("--project <path>", "Timeline file or directory to load").option("-v, --verbose", "Show verbose output including tool calls").option("--max-turns <n>", "Maximum turns per request", "10").option("-i, --input <query>", "Run a single query and exit (non-interactive)").option("-c, --confirm", "Confirm before EVERY tool execution (overrides cost gate)").option("--no-confirm", "Disable all confirm prompts including the high/very-high cost gate (CI / automation)").option("--budget-usd <usd>", "Reject tool calls past this cumulative USD ceiling (tier-estimated, conservative)").action(async (options) => {
|
|
72815
72984
|
await startAgent(options);
|
|
72816
72985
|
});
|
|
72817
72986
|
function getEnvVar(provider) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibeframe/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.86.0",
|
|
4
4
|
"description": "VibeFrame CLI - natural language video editing from the terminal",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
@@ -183,8 +183,8 @@
|
|
|
183
183
|
"tsx": "^4.21.0",
|
|
184
184
|
"typescript": "^5.3.3",
|
|
185
185
|
"vitest": "^1.2.2",
|
|
186
|
-
"@vibeframe/ai-providers": "0.
|
|
187
|
-
"@vibeframe/core": "0.
|
|
186
|
+
"@vibeframe/ai-providers": "0.86.0",
|
|
187
|
+
"@vibeframe/core": "0.86.0"
|
|
188
188
|
},
|
|
189
189
|
"engines": {
|
|
190
190
|
"node": ">=20"
|