@vivipilot/cli 0.1.0 → 0.1.1
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/chunk-FN7FHZ3D.js +1 -0
- package/dist/chunk-L3C7EOPY.js +2 -0
- package/dist/chunk-RB2L6BY5.js +3 -0
- package/dist/chunk-RW7OWVPD.js +5 -0
- package/dist/chunk-TZJPBE3B.js +1 -0
- package/dist/chunk-ZZ72PPC3.js +1 -0
- package/dist/cli.d.ts +5 -4
- package/dist/cli.js +53 -493
- package/dist/config-DAG58pP-.d.ts +16 -0
- package/dist/index.d.ts +121 -7
- package/dist/index.js +1 -7
- package/dist/manifest-TDCIHZ46.js +1 -0
- package/dist/mcp-MRADYIPQ.js +1 -0
- package/dist/render-TJGWLKPC.js +1 -0
- package/package.json +6 -2
- package/dist/api.d.ts +0 -86
- package/dist/api.d.ts.map +0 -1
- package/dist/api.js +0 -77
- package/dist/api.js.map +0 -1
- package/dist/args.d.ts +0 -11
- package/dist/args.d.ts.map +0 -1
- package/dist/args.js +0 -53
- package/dist/args.js.map +0 -1
- package/dist/browser.d.ts +0 -31
- package/dist/browser.d.ts.map +0 -1
- package/dist/browser.js +0 -162
- package/dist/browser.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/config.d.ts +0 -15
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -58
- package/dist/config.js.map +0 -1
- package/dist/errors.d.ts +0 -6
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -12
- package/dist/errors.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/manifest.d.ts +0 -40
- package/dist/manifest.d.ts.map +0 -1
- package/dist/manifest.js +0 -90
- package/dist/manifest.js.map +0 -1
- package/dist/mcp.d.ts +0 -13
- package/dist/mcp.d.ts.map +0 -1
- package/dist/mcp.js +0 -392
- package/dist/mcp.js.map +0 -1
- package/dist/render.d.ts +0 -21
- package/dist/render.d.ts.map +0 -1
- package/dist/render.js +0 -369
- package/dist/render.js.map +0 -1
- package/src/api.ts +0 -163
- package/src/args.test.ts +0 -21
- package/src/args.ts +0 -64
- package/src/browser.test.ts +0 -103
- package/src/browser.ts +0 -174
- package/src/cli.ts +0 -656
- package/src/config.test.ts +0 -30
- package/src/config.ts +0 -71
- package/src/errors.ts +0 -14
- package/src/index.ts +0 -25
- package/src/manifest.test.ts +0 -105
- package/src/manifest.ts +0 -126
- package/src/mcp.test.ts +0 -48
- package/src/mcp.ts +0 -438
- package/src/render.ts +0 -424
- package/tsconfig.json +0 -26
package/dist/cli.js
CHANGED
|
@@ -1,64 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import { flagBoolean, flagNumber, flagString, parseArgv } from "./args.js";
|
|
7
|
-
import { defaultConfigPath, deleteConfig, readConfig, resolveApiKey, resolveApiUrl, writeConfig, } from "./config.js";
|
|
8
|
-
import { VivipilotApiClient } from "./api.js";
|
|
9
|
-
import { CliError, isCliError } from "./errors.js";
|
|
10
|
-
import { verifyManifestFile } from "./manifest.js";
|
|
11
|
-
import { renderManifest } from "./render.js";
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
// Spinner / progress display
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
16
|
-
class ProgressDisplay {
|
|
17
|
-
io;
|
|
18
|
-
frame = 0;
|
|
19
|
-
timer = null;
|
|
20
|
-
lastMessage = "";
|
|
21
|
-
lastStage = "";
|
|
22
|
-
constructor(io) {
|
|
23
|
-
this.io = io;
|
|
24
|
-
}
|
|
25
|
-
start(message) {
|
|
26
|
-
this.lastMessage = message;
|
|
27
|
-
this.io.stderr.write(`\r${SPINNER_FRAMES[0]} ${message}`);
|
|
28
|
-
this.timer = setInterval(() => {
|
|
29
|
-
this.frame = (this.frame + 1) % SPINNER_FRAMES.length;
|
|
30
|
-
this.io.stderr.write(`\r${SPINNER_FRAMES[this.frame]} ${this.lastMessage}`);
|
|
31
|
-
}, 80);
|
|
32
|
-
}
|
|
33
|
-
update(stage, message) {
|
|
34
|
-
if (stage !== this.lastStage) {
|
|
35
|
-
// Complete the previous stage with a checkmark
|
|
36
|
-
if (this.lastStage) {
|
|
37
|
-
this.io.stderr.write(`\r\x1b[32m✓\x1b[0m ${this.lastMessage}\n`);
|
|
38
|
-
}
|
|
39
|
-
this.lastStage = stage;
|
|
40
|
-
}
|
|
41
|
-
this.lastMessage = message;
|
|
42
|
-
}
|
|
43
|
-
succeed(message) {
|
|
44
|
-
if (this.timer)
|
|
45
|
-
clearInterval(this.timer);
|
|
46
|
-
this.timer = null;
|
|
47
|
-
this.io.stderr.write(`\r\x1b[32m✓\x1b[0m ${message}\n`);
|
|
48
|
-
}
|
|
49
|
-
fail(message) {
|
|
50
|
-
if (this.timer)
|
|
51
|
-
clearInterval(this.timer);
|
|
52
|
-
this.timer = null;
|
|
53
|
-
this.io.stderr.write(`\r\x1b[31m✗\x1b[0m ${message}\n`);
|
|
54
|
-
}
|
|
55
|
-
stop() {
|
|
56
|
-
if (this.timer)
|
|
57
|
-
clearInterval(this.timer);
|
|
58
|
-
this.timer = null;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
const HELP = `Vivipilot CLI (paid-only)
|
|
2
|
+
import{a as R,b as w,c as $,d as p}from"./chunk-ZZ72PPC3.js";import{a as V}from"./chunk-RW7OWVPD.js";import{b as M}from"./chunk-FN7FHZ3D.js";import{a as G}from"./chunk-TZJPBE3B.js";import{b as k,c as E,d as N,e as O,f as U,g as A,i as l,j as T}from"./chunk-L3C7EOPY.js";import{randomUUID as K}from"crypto";import{createInterface as z}from"readline";import{mkdir as X,writeFile as Q}from"fs/promises";import{dirname as Z}from"path";var _=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"],L=class{io;frame=0;timer=null;lastMessage="";lastStage="";constructor(e){this.io=e}start(e){this.lastMessage=e,this.io.stderr.write(`\r${_[0]} ${e}`),this.timer=setInterval(()=>{this.frame=(this.frame+1)%_.length,this.io.stderr.write(`\r${_[this.frame]} ${this.lastMessage}`)},80)}update(e,n){e!==this.lastStage&&(this.lastStage&&this.io.stderr.write(`\r\x1B[32m\u2713\x1B[0m ${this.lastMessage}
|
|
3
|
+
`),this.lastStage=e),this.lastMessage=n}succeed(e){this.timer&&clearInterval(this.timer),this.timer=null,this.io.stderr.write(`\r\x1B[32m\u2713\x1B[0m ${e}
|
|
4
|
+
`)}fail(e){this.timer&&clearInterval(this.timer),this.timer=null,this.io.stderr.write(`\r\x1B[31m\u2717\x1B[0m ${e}
|
|
5
|
+
`)}stop(){this.timer&&clearInterval(this.timer),this.timer=null}},q=`Vivipilot CLI (paid-only)
|
|
62
6
|
|
|
63
7
|
Usage:
|
|
64
8
|
vivipilot login --api-key <key> [--api-url <url>]
|
|
@@ -88,163 +32,15 @@ Environment:
|
|
|
88
32
|
VIVIPILOT_MANIFEST_PUBLIC_KEYS JSON map of keyId to Ed25519 public key
|
|
89
33
|
|
|
90
34
|
CLI/MCP generation is paid-only: no trial credits and no free generations.
|
|
91
|
-
`;
|
|
92
|
-
function
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
function estimateRequestFromArgs(parsed) {
|
|
102
|
-
const width = flagNumber(parsed, ["width", "w"]);
|
|
103
|
-
const height = flagNumber(parsed, ["height", "h"]);
|
|
104
|
-
const fps = flagNumber(parsed, ["fps"]);
|
|
105
|
-
const durationSeconds = flagNumber(parsed, ["duration", "duration-seconds"]);
|
|
106
|
-
const engineFlag = flagString(parsed, ["engine"]);
|
|
107
|
-
const enginePreference = engineFlag === "pixi" || engineFlag === "three" || engineFlag === "auto" ? engineFlag : undefined;
|
|
108
|
-
return {
|
|
109
|
-
prompt: requirePrompt(parsed),
|
|
110
|
-
...(width || height || fps ? { canvas: { width, height, fps } } : {}),
|
|
111
|
-
...(durationSeconds ? { durationSeconds } : {}),
|
|
112
|
-
...(enginePreference ? { enginePreference } : {}),
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
function estimateRequestFromPrompt(prompt, defaults) {
|
|
116
|
-
return {
|
|
117
|
-
prompt,
|
|
118
|
-
...(defaults.width || defaults.height || defaults.fps
|
|
119
|
-
? { canvas: { width: defaults.width, height: defaults.height, fps: defaults.fps } }
|
|
120
|
-
: {}),
|
|
121
|
-
...(defaults.durationSeconds ? { durationSeconds: defaults.durationSeconds } : {}),
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
async function writeOutputFile(path, value) {
|
|
125
|
-
await mkdir(dirname(path), { recursive: true });
|
|
126
|
-
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
127
|
-
}
|
|
128
|
-
function requireManifestPath(parsed) {
|
|
129
|
-
const path = parsed.positionals[0];
|
|
130
|
-
if (!path)
|
|
131
|
-
throw new CliError("Missing manifest path.", 2);
|
|
132
|
-
return path;
|
|
133
|
-
}
|
|
134
|
-
async function handleLogin(parsed, env, io) {
|
|
135
|
-
const configPath = defaultConfigPath(env);
|
|
136
|
-
const currentConfig = await readConfig(configPath);
|
|
137
|
-
const apiKey = flagString(parsed, ["api-key"]) ?? env.VIVIPILOT_API_KEY;
|
|
138
|
-
if (!apiKey)
|
|
139
|
-
throw new CliError("Missing paid API key. Pass --api-key or set VIVIPILOT_API_KEY.", 2);
|
|
140
|
-
const nextConfig = {
|
|
141
|
-
...currentConfig,
|
|
142
|
-
apiKey,
|
|
143
|
-
apiUrl: flagString(parsed, ["api-url"]) ?? env.VIVIPILOT_API_URL ?? currentConfig.apiUrl,
|
|
144
|
-
};
|
|
145
|
-
await writeConfig(nextConfig, configPath);
|
|
146
|
-
writeJson(io, { ok: true, configPath, paidOnly: true });
|
|
147
|
-
}
|
|
148
|
-
async function handleLogout(env, io) {
|
|
149
|
-
const configPath = defaultConfigPath(env);
|
|
150
|
-
await deleteConfig(configPath);
|
|
151
|
-
writeJson(io, { ok: true, configPath });
|
|
152
|
-
}
|
|
153
|
-
function apiClient(config, env) {
|
|
154
|
-
return new VivipilotApiClient({ config, env });
|
|
155
|
-
}
|
|
156
|
-
async function handleTopup(config, env, io) {
|
|
157
|
-
const apiUrl = resolveApiUrl(config, env);
|
|
158
|
-
writeJson(io, {
|
|
159
|
-
paidOnly: true,
|
|
160
|
-
topupUrl: `${apiUrl}/edit?billing=topup`,
|
|
161
|
-
message: "CLI/MCP generation requires paid top-up credits.",
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
// ---------------------------------------------------------------------------
|
|
165
|
-
// Generate with live progress
|
|
166
|
-
// ---------------------------------------------------------------------------
|
|
167
|
-
const POLL_INTERVAL_MS = 1500;
|
|
168
|
-
const MAX_POLL_MS = 300_000; // 5 minute total timeout
|
|
169
|
-
async function generateWithProgress(client, request, idempotencyKey, outPath, io) {
|
|
170
|
-
const progress = new ProgressDisplay(io);
|
|
171
|
-
progress.start("Starting generation...");
|
|
172
|
-
// Start the async generation
|
|
173
|
-
const startResult = await client.startGenerate({ ...request, outputFormat: "manifest" }, idempotencyKey);
|
|
174
|
-
const { generationId } = startResult;
|
|
175
|
-
progress.update("start", `Generation ${generationId} started (est. ${startResult.estimatedCredits} credits, engine: ${startResult.engine})`);
|
|
176
|
-
// If it was an idempotent replay and already completed, try to get the data
|
|
177
|
-
if (startResult.idempotentReplay && startResult.status === "completed") {
|
|
178
|
-
const status = await client.generationProgress(generationId);
|
|
179
|
-
if (status.manifestData) {
|
|
180
|
-
await writeOutputFile(outPath, status.manifestData);
|
|
181
|
-
progress.succeed(`Scene saved to ${outPath} (${status.creditsCharged ?? 0} credits)`);
|
|
182
|
-
return { generationId, manifestPath: outPath, creditsCharged: status.creditsCharged };
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
// Poll for progress
|
|
186
|
-
const startTime = Date.now();
|
|
187
|
-
let lastProgressMessage = "";
|
|
188
|
-
while (Date.now() - startTime < MAX_POLL_MS) {
|
|
189
|
-
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
190
|
-
let status;
|
|
191
|
-
try {
|
|
192
|
-
status = await client.generationProgress(generationId);
|
|
193
|
-
}
|
|
194
|
-
catch {
|
|
195
|
-
continue; // Transient network error, keep polling
|
|
196
|
-
}
|
|
197
|
-
// Show progress update
|
|
198
|
-
if (status.progressMessage && status.progressMessage !== lastProgressMessage) {
|
|
199
|
-
lastProgressMessage = status.progressMessage;
|
|
200
|
-
progress.update(status.workflowStatus ?? status.status, status.progressMessage);
|
|
201
|
-
}
|
|
202
|
-
// Terminal states
|
|
203
|
-
if (status.status === "completed") {
|
|
204
|
-
if (status.manifestData) {
|
|
205
|
-
await writeOutputFile(outPath, status.manifestData);
|
|
206
|
-
progress.succeed(`Scene saved to ${outPath} (${status.creditsCharged ?? 0} credits)`);
|
|
207
|
-
return { generationId, manifestPath: outPath, creditsCharged: status.creditsCharged };
|
|
208
|
-
}
|
|
209
|
-
progress.succeed("Generation complete.");
|
|
210
|
-
return { generationId, manifestPath: outPath, creditsCharged: status.creditsCharged };
|
|
211
|
-
}
|
|
212
|
-
if (status.status === "failed") {
|
|
213
|
-
progress.fail(status.error ?? "Generation failed.");
|
|
214
|
-
throw new CliError(status.error ?? "Generation failed.", 1);
|
|
215
|
-
}
|
|
216
|
-
if (status.status === "refunded") {
|
|
217
|
-
progress.fail("Generation refunded.");
|
|
218
|
-
throw new CliError("Generation was refunded.", 1);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
progress.fail("Generation timed out after 5 minutes.");
|
|
222
|
-
throw new CliError(`Generation ${generationId} timed out. Check status with: vivipilot status ${generationId}`, 1);
|
|
223
|
-
}
|
|
224
|
-
async function handleGenerate(parsed, config, env, io) {
|
|
225
|
-
if (!resolveApiKey(config, env)) {
|
|
226
|
-
throw new CliError("Vivipilot generate is paid-only. Set VIVIPILOT_API_KEY or run `vivipilot login --api-key <key>`.", 2);
|
|
227
|
-
}
|
|
228
|
-
const out = flagString(parsed, ["out", "o"]) ?? "scene.vivi.json";
|
|
229
|
-
const idempotencyKey = flagString(parsed, ["idempotency-key"]) ?? `cli_${randomUUID()}`;
|
|
230
|
-
const request = estimateRequestFromArgs(parsed);
|
|
231
|
-
const result = await generateWithProgress(apiClient(config, env), request, idempotencyKey, out, io);
|
|
232
|
-
writeJson(io, {
|
|
233
|
-
ok: true,
|
|
234
|
-
generationId: result.generationId,
|
|
235
|
-
manifestPath: result.manifestPath,
|
|
236
|
-
creditsCharged: result.creditsCharged,
|
|
237
|
-
paidOnly: true,
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
function chatBanner(io) {
|
|
241
|
-
io.stderr.write("\n\x1b[1m\x1b[36m Vivipilot Chat\x1b[0m (paid-only)\n");
|
|
242
|
-
io.stderr.write(" Type a prompt to generate a motion graphics scene.\n");
|
|
243
|
-
io.stderr.write(" Commands: /render <n>, /verify <n>, /list, /balance, /help, /quit\n\n");
|
|
244
|
-
}
|
|
245
|
-
function chatHelp(io) {
|
|
246
|
-
io.stderr.write(`
|
|
247
|
-
\x1b[1mChat Commands:\x1b[0m
|
|
35
|
+
`;function v(t,e){t.stdout.write(`${JSON.stringify(e,null,2)}
|
|
36
|
+
`)}function ee(t){let e=w(t,["prompt","p"])??t.positionals.join(" ");if(!e.trim())throw new l('Missing prompt. Pass --prompt "...".',2);return e.trim()}function D(t){let e=p(t,["width","w"]),n=p(t,["height","h"]),i=p(t,["fps"]),d=p(t,["duration","duration-seconds"]),r=w(t,["engine"]),o=r==="pixi"||r==="three"||r==="auto"?r:void 0;return{prompt:ee(t),...e||n||i?{canvas:{width:e,height:n,fps:i}}:{},...d?{durationSeconds:d}:{},...o?{enginePreference:o}:{}}}function j(t,e){return{prompt:t,...e.width||e.height||e.fps?{canvas:{width:e.width,height:e.height,fps:e.fps}}:{},...e.durationSeconds?{durationSeconds:e.durationSeconds}:{}}}async function F(t,e){await X(Z(t),{recursive:!0}),await Q(t,`${JSON.stringify(e,null,2)}
|
|
37
|
+
`,"utf8")}function B(t){let e=t.positionals[0];if(!e)throw new l("Missing manifest path.",2);return e}async function te(t,e,n){let i=k(e),d=await E(i),r=w(t,["api-key"])??e.VIVIPILOT_API_KEY;if(!r)throw new l("Missing paid API key. Pass --api-key or set VIVIPILOT_API_KEY.",2);let o={...d,apiKey:r,apiUrl:w(t,["api-url"])??e.VIVIPILOT_API_URL??d.apiUrl};await N(o,i),v(n,{ok:!0,configPath:i,paidOnly:!0})}async function ie(t,e){let n=k(t);await O(n),v(e,{ok:!0,configPath:n})}function C(t,e){return new G({config:t,env:e})}async function ne(t,e,n){let i=U(t,e);v(n,{paidOnly:!0,topupUrl:`${i}/edit?billing=topup`,message:"CLI/MCP generation requires paid top-up credits."})}var re=1500,se=3e5;async function W(t,e,n,i,d){let r=new L(d);r.start("Starting generation...");let o=await t.startGenerate({...e,outputFormat:"manifest"},n),{generationId:c}=o;if(r.update("start",`Generation ${c} started (est. ${o.estimatedCredits} credits, engine: ${o.engine})`),o.idempotentReplay&&o.status==="completed"){let s=await t.generationProgress(c);if(s.manifestData)return await F(i,s.manifestData),r.succeed(`Scene saved to ${i} (${s.creditsCharged??0} credits)`),{generationId:c,manifestPath:i,creditsCharged:s.creditsCharged}}let b=Date.now(),u="";for(;Date.now()-b<se;){await new Promise(m=>setTimeout(m,re));let s;try{s=await t.generationProgress(c)}catch{continue}if(s.progressMessage&&s.progressMessage!==u&&(u=s.progressMessage,r.update(s.workflowStatus??s.status,s.progressMessage)),s.status==="completed")return s.manifestData?(await F(i,s.manifestData),r.succeed(`Scene saved to ${i} (${s.creditsCharged??0} credits)`),{generationId:c,manifestPath:i,creditsCharged:s.creditsCharged}):(r.succeed("Generation complete."),{generationId:c,manifestPath:i,creditsCharged:s.creditsCharged});if(s.status==="failed")throw r.fail(s.error??"Generation failed."),new l(s.error??"Generation failed.",1);if(s.status==="refunded")throw r.fail("Generation refunded."),new l("Generation was refunded.",1)}throw r.fail("Generation timed out after 5 minutes."),new l(`Generation ${c} timed out. Check status with: vivipilot status ${c}`,1)}async function ae(t,e,n,i){if(!A(e,n))throw new l("Vivipilot generate is paid-only. Set VIVIPILOT_API_KEY or run `vivipilot login --api-key <key>`.",2);let d=w(t,["out","o"])??"scene.vivi.json",r=w(t,["idempotency-key"])??`cli_${K()}`,o=D(t),c=await W(C(e,n),o,r,d,i);v(i,{ok:!0,generationId:c.generationId,manifestPath:c.manifestPath,creditsCharged:c.creditsCharged,paidOnly:!0})}function oe(t){t.stderr.write(`
|
|
38
|
+
\x1B[1m\x1B[36m Vivipilot Chat\x1B[0m (paid-only)
|
|
39
|
+
`),t.stderr.write(` Type a prompt to generate a motion graphics scene.
|
|
40
|
+
`),t.stderr.write(` Commands: /render <n>, /verify <n>, /list, /balance, /help, /quit
|
|
41
|
+
|
|
42
|
+
`)}function ce(t){t.stderr.write(`
|
|
43
|
+
\x1B[1mChat Commands:\x1B[0m
|
|
248
44
|
<prompt> Generate a new scene from your description
|
|
249
45
|
/render <n> Render scene #n to video (e.g. /render 1)
|
|
250
46
|
/verify <n> Verify scene #n manifest signature
|
|
@@ -254,283 +50,47 @@ function chatHelp(io) {
|
|
|
254
50
|
/help Show this help
|
|
255
51
|
/quit Exit chat
|
|
256
52
|
|
|
257
|
-
\
|
|
53
|
+
\x1B[1mTips:\x1B[0m
|
|
258
54
|
- Each prompt generates a new scene with live progress
|
|
259
55
|
- Scenes are saved as scene_1.vivi.json, scene_2.vivi.json, etc.
|
|
260
56
|
- Use /render to render any scene to video after generation
|
|
261
57
|
- All generations are paid-only and charged to your API key
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
case "/quit":
|
|
302
|
-
case "/exit":
|
|
303
|
-
case "/q":
|
|
304
|
-
io.stderr.write("\nGoodbye!\n");
|
|
305
|
-
rl.close();
|
|
306
|
-
return;
|
|
307
|
-
case "/help":
|
|
308
|
-
case "/h":
|
|
309
|
-
chatHelp(io);
|
|
310
|
-
break;
|
|
311
|
-
case "/list":
|
|
312
|
-
case "/ls": {
|
|
313
|
-
if (session.scenes.length === 0) {
|
|
314
|
-
io.stderr.write(" No scenes generated yet.\n");
|
|
315
|
-
}
|
|
316
|
-
else {
|
|
317
|
-
io.stderr.write("\n \x1b[1mGenerated Scenes:\x1b[0m\n");
|
|
318
|
-
for (let i = 0; i < session.scenes.length; i++) {
|
|
319
|
-
const s = session.scenes[i];
|
|
320
|
-
io.stderr.write(` ${i + 1}. ${s.manifestPath} (${s.creditsCharged ?? "?"} credits)\n`);
|
|
321
|
-
io.stderr.write(` \x1b[2m${s.prompt.slice(0, 80)}${s.prompt.length > 80 ? "..." : ""}\x1b[0m\n`);
|
|
322
|
-
}
|
|
323
|
-
io.stderr.write("\n");
|
|
324
|
-
}
|
|
325
|
-
break;
|
|
326
|
-
}
|
|
327
|
-
case "/balance":
|
|
328
|
-
case "/bal": {
|
|
329
|
-
try {
|
|
330
|
-
const bal = await client.balance();
|
|
331
|
-
io.stderr.write(` \x1b[33mBalance:\x1b[0m ${bal.paidBalance ?? bal.balance} credits\n`);
|
|
332
|
-
}
|
|
333
|
-
catch (e) {
|
|
334
|
-
io.stderr.write(` \x1b[31mError:\x1b[0m ${e instanceof Error ? e.message : String(e)}\n`);
|
|
335
|
-
}
|
|
336
|
-
break;
|
|
337
|
-
}
|
|
338
|
-
case "/estimate":
|
|
339
|
-
case "/est": {
|
|
340
|
-
if (!arg) {
|
|
341
|
-
io.stderr.write(" Usage: /estimate <prompt>\n");
|
|
342
|
-
break;
|
|
343
|
-
}
|
|
344
|
-
try {
|
|
345
|
-
const est = await client.estimate(estimateRequestFromPrompt(arg, { width, height, fps, durationSeconds }));
|
|
346
|
-
io.stderr.write(` \x1b[33mEstimate:\x1b[0m ${est.estimatedCredits} credits (engine: ${est.engine})\n`);
|
|
347
|
-
}
|
|
348
|
-
catch (e) {
|
|
349
|
-
io.stderr.write(` \x1b[31mError:\x1b[0m ${e instanceof Error ? e.message : String(e)}\n`);
|
|
350
|
-
}
|
|
351
|
-
break;
|
|
352
|
-
}
|
|
353
|
-
case "/render": {
|
|
354
|
-
const idx = parseInt(arg, 10) - 1;
|
|
355
|
-
if (isNaN(idx) || idx < 0 || idx >= session.scenes.length) {
|
|
356
|
-
io.stderr.write(` Usage: /render <scene number 1-${session.scenes.length}>\n`);
|
|
357
|
-
break;
|
|
358
|
-
}
|
|
359
|
-
const scene = session.scenes[idx];
|
|
360
|
-
const videoOut = scene.manifestPath.replace(/\.vivi\.json$/, ".mp4");
|
|
361
|
-
io.stderr.write(` Rendering ${scene.manifestPath} -> ${videoOut}...\n`);
|
|
362
|
-
try {
|
|
363
|
-
const result = await renderManifest({ manifestPath: scene.manifestPath, outPath: videoOut }, config, env);
|
|
364
|
-
io.stderr.write(` \x1b[32m✓\x1b[0m Rendered to ${result.outPath} (${result.size} bytes)\n`);
|
|
365
|
-
}
|
|
366
|
-
catch (e) {
|
|
367
|
-
io.stderr.write(` \x1b[31m✗\x1b[0m Render failed: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
368
|
-
}
|
|
369
|
-
break;
|
|
370
|
-
}
|
|
371
|
-
case "/verify": {
|
|
372
|
-
const vidx = parseInt(arg, 10) - 1;
|
|
373
|
-
if (isNaN(vidx) || vidx < 0 || vidx >= session.scenes.length) {
|
|
374
|
-
io.stderr.write(` Usage: /verify <scene number 1-${session.scenes.length}>\n`);
|
|
375
|
-
break;
|
|
376
|
-
}
|
|
377
|
-
const sc = session.scenes[vidx];
|
|
378
|
-
try {
|
|
379
|
-
const vResult = await verifyManifestFile(sc.manifestPath, config, env);
|
|
380
|
-
if (vResult.ok) {
|
|
381
|
-
io.stderr.write(` \x1b[32m✓\x1b[0m Manifest verified: ${vResult.manifest.manifestId}\n`);
|
|
382
|
-
}
|
|
383
|
-
else {
|
|
384
|
-
io.stderr.write(` \x1b[31m✗\x1b[0m Verification failed: ${vResult.message}\n`);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
catch (e) {
|
|
388
|
-
io.stderr.write(` \x1b[31m✗\x1b[0m ${e instanceof Error ? e.message : String(e)}\n`);
|
|
389
|
-
}
|
|
390
|
-
break;
|
|
391
|
-
}
|
|
392
|
-
default:
|
|
393
|
-
io.stderr.write(` Unknown command: ${cmd}. Type /help for available commands.\n`);
|
|
394
|
-
}
|
|
395
|
-
rl.prompt();
|
|
396
|
-
continue;
|
|
397
|
-
}
|
|
398
|
-
// It's a prompt — generate a scene
|
|
399
|
-
session.turnNumber++;
|
|
400
|
-
const sceneNum = session.turnNumber;
|
|
401
|
-
const outPath = `${outDir}/scene_${sceneNum}.vivi.json`.replace(/^\.\//, "");
|
|
402
|
-
const idempotencyKey = `chat_${randomUUID()}`;
|
|
403
|
-
io.stderr.write("\n");
|
|
404
|
-
try {
|
|
405
|
-
const result = await generateWithProgress(client, estimateRequestFromPrompt(trimmed, { width, height, fps, durationSeconds }), idempotencyKey, outPath, io);
|
|
406
|
-
session.scenes.push({
|
|
407
|
-
generationId: result.generationId,
|
|
408
|
-
manifestPath: result.manifestPath,
|
|
409
|
-
prompt: trimmed,
|
|
410
|
-
creditsCharged: result.creditsCharged,
|
|
411
|
-
});
|
|
412
|
-
io.stderr.write(` \x1b[2mScene #${sceneNum} saved. Use /render ${sceneNum} to render, or type another prompt.\x1b[0m\n\n`);
|
|
413
|
-
}
|
|
414
|
-
catch (e) {
|
|
415
|
-
io.stderr.write(` \x1b[31mGeneration failed:\x1b[0m ${e instanceof Error ? e.message : String(e)}\n\n`);
|
|
416
|
-
}
|
|
417
|
-
rl.prompt();
|
|
418
|
-
}
|
|
419
|
-
// Stream ended (stdin closed)
|
|
420
|
-
if (session.scenes.length > 0) {
|
|
421
|
-
io.stderr.write(`\n Session complete. ${session.scenes.length} scene(s) generated.\n`);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
// ---------------------------------------------------------------------------
|
|
425
|
-
// Other command handlers (unchanged)
|
|
426
|
-
// ---------------------------------------------------------------------------
|
|
427
|
-
async function handleVerify(parsed, config, env, io) {
|
|
428
|
-
const manifestPath = requireManifestPath(parsed);
|
|
429
|
-
const result = await verifyManifestFile(manifestPath, config, env);
|
|
430
|
-
if (!result.ok)
|
|
431
|
-
throw new CliError(`Manifest verification failed: ${result.message}`, 1);
|
|
432
|
-
writeJson(io, {
|
|
433
|
-
ok: true,
|
|
434
|
-
manifestId: result.manifest.manifestId,
|
|
435
|
-
generationId: result.manifest.generationId,
|
|
436
|
-
canonicalPayloadHash: result.canonicalPayloadHash,
|
|
437
|
-
});
|
|
438
|
-
}
|
|
439
|
-
async function handleRender(parsed, config, env, io) {
|
|
440
|
-
const manifestPath = requireManifestPath(parsed);
|
|
441
|
-
const out = flagString(parsed, ["out", "o"]) ?? "video.mp4";
|
|
442
|
-
const format = flagString(parsed, ["format"]);
|
|
443
|
-
const scale = flagNumber(parsed, ["scale"]);
|
|
444
|
-
const fpsFlag = flagNumber(parsed, ["fps"]);
|
|
445
|
-
const transparent = flagBoolean(parsed, ["transparent"]);
|
|
446
|
-
const verifyOnly = flagBoolean(parsed, ["verify-only"]);
|
|
447
|
-
const result = await renderManifest({
|
|
448
|
-
manifestPath,
|
|
449
|
-
outPath: out,
|
|
450
|
-
...(format ? { format } : {}),
|
|
451
|
-
...(scale ? { scale } : {}),
|
|
452
|
-
...(fpsFlag ? { fps: fpsFlag } : {}),
|
|
453
|
-
...(transparent ? { transparent } : {}),
|
|
454
|
-
verifyOnly,
|
|
455
|
-
}, config, env);
|
|
456
|
-
writeJson(io, {
|
|
457
|
-
ok: true,
|
|
458
|
-
manifestId: result.manifestId,
|
|
459
|
-
out: result.outPath,
|
|
460
|
-
size: result.size,
|
|
461
|
-
paidOnly: true,
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
async function handleMcp(config, env, io) {
|
|
465
|
-
const { startMcpServer } = await import("./mcp.js");
|
|
466
|
-
await startMcpServer({
|
|
467
|
-
config,
|
|
468
|
-
env,
|
|
469
|
-
stdin: process.stdin,
|
|
470
|
-
stdout: io.stdout,
|
|
471
|
-
stderr: io.stderr,
|
|
472
|
-
});
|
|
473
|
-
}
|
|
474
|
-
export async function runCli(argv = process.argv.slice(2), env = process.env, io = { stdout: process.stdout, stderr: process.stderr }) {
|
|
475
|
-
const parsed = parseArgv(argv);
|
|
476
|
-
if (!parsed.command || parsed.command === "help" || flagBoolean(parsed, ["help"])) {
|
|
477
|
-
io.stdout.write(HELP);
|
|
478
|
-
return;
|
|
479
|
-
}
|
|
480
|
-
const configPath = defaultConfigPath(env);
|
|
481
|
-
const config = await readConfig(configPath);
|
|
482
|
-
switch (parsed.command) {
|
|
483
|
-
case "login":
|
|
484
|
-
await handleLogin(parsed, env, io);
|
|
485
|
-
return;
|
|
486
|
-
case "logout":
|
|
487
|
-
await handleLogout(env, io);
|
|
488
|
-
return;
|
|
489
|
-
case "whoami":
|
|
490
|
-
writeJson(io, await apiClient(config, env).whoami());
|
|
491
|
-
return;
|
|
492
|
-
case "balance":
|
|
493
|
-
writeJson(io, await apiClient(config, env).balance());
|
|
494
|
-
return;
|
|
495
|
-
case "topup":
|
|
496
|
-
await handleTopup(config, env, io);
|
|
497
|
-
return;
|
|
498
|
-
case "estimate":
|
|
499
|
-
writeJson(io, await apiClient(config, env).estimate(estimateRequestFromArgs(parsed)));
|
|
500
|
-
return;
|
|
501
|
-
case "generate":
|
|
502
|
-
await handleGenerate(parsed, config, env, io);
|
|
503
|
-
return;
|
|
504
|
-
case "chat":
|
|
505
|
-
await handleChat(parsed, config, env, io);
|
|
506
|
-
return;
|
|
507
|
-
case "status": {
|
|
508
|
-
const generationId = parsed.positionals[0];
|
|
509
|
-
if (!generationId)
|
|
510
|
-
throw new CliError("Missing generation id.", 2);
|
|
511
|
-
writeJson(io, await apiClient(config, env).generationStatus(generationId));
|
|
512
|
-
return;
|
|
513
|
-
}
|
|
514
|
-
case "verify":
|
|
515
|
-
await handleVerify(parsed, config, env, io);
|
|
516
|
-
return;
|
|
517
|
-
case "render":
|
|
518
|
-
await handleRender(parsed, config, env, io);
|
|
519
|
-
return;
|
|
520
|
-
case "mcp":
|
|
521
|
-
await handleMcp(config, env, io);
|
|
522
|
-
return;
|
|
523
|
-
default:
|
|
524
|
-
throw new CliError(`Unknown command: ${parsed.command}\n\n${HELP}`, 2);
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
runCli().catch((error) => {
|
|
528
|
-
if (isCliError(error)) {
|
|
529
|
-
process.stderr.write(`${error.message}\n`);
|
|
530
|
-
process.exitCode = error.exitCode;
|
|
531
|
-
return;
|
|
532
|
-
}
|
|
533
|
-
process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
|
|
534
|
-
process.exitCode = 1;
|
|
535
|
-
});
|
|
536
|
-
//# sourceMappingURL=cli.js.map
|
|
58
|
+
|
|
59
|
+
`)}async function de(t,e,n,i){if(!A(e,n))throw new l("Vivipilot chat is paid-only. Set VIVIPILOT_API_KEY or run `vivipilot login --api-key <key>`.",2);let d=w(t,["out-dir"])??".",r=p(t,["width","w"]),o=p(t,["height","h"]),c=p(t,["fps"]),b=p(t,["duration","duration-seconds"]),u=C(e,n),s={scenes:[],turnNumber:0};oe(i);try{let P=await u.balance();i.stderr.write(` \x1B[33mBalance:\x1B[0m ${P.paidBalance??P.balance} credits
|
|
60
|
+
|
|
61
|
+
`)}catch{i.stderr.write(` \x1B[33mCould not fetch balance.\x1B[0m
|
|
62
|
+
|
|
63
|
+
`)}let m=z({input:process.stdin,output:process.stderr,prompt:"\x1B[36mvivipilot>\x1B[0m "});m.prompt();for await(let P of m){let I=P.trim();if(!I){m.prompt();continue}if(I.startsWith("/")){let[g,...H]=I.split(/\s+/),x=H.join(" ");switch(g){case"/quit":case"/exit":case"/q":i.stderr.write(`
|
|
64
|
+
Goodbye!
|
|
65
|
+
`),m.close();return;case"/help":case"/h":ce(i);break;case"/list":case"/ls":{if(s.scenes.length===0)i.stderr.write(` No scenes generated yet.
|
|
66
|
+
`);else{i.stderr.write(`
|
|
67
|
+
\x1B[1mGenerated Scenes:\x1B[0m
|
|
68
|
+
`);for(let a=0;a<s.scenes.length;a++){let f=s.scenes[a];i.stderr.write(` ${a+1}. ${f.manifestPath} (${f.creditsCharged??"?"} credits)
|
|
69
|
+
`),i.stderr.write(` \x1B[2m${f.prompt.slice(0,80)}${f.prompt.length>80?"...":""}\x1B[0m
|
|
70
|
+
`)}i.stderr.write(`
|
|
71
|
+
`)}break}case"/balance":case"/bal":{try{let a=await u.balance();i.stderr.write(` \x1B[33mBalance:\x1B[0m ${a.paidBalance??a.balance} credits
|
|
72
|
+
`)}catch(a){i.stderr.write(` \x1B[31mError:\x1B[0m ${a instanceof Error?a.message:String(a)}
|
|
73
|
+
`)}break}case"/estimate":case"/est":{if(!x){i.stderr.write(` Usage: /estimate <prompt>
|
|
74
|
+
`);break}try{let a=await u.estimate(j(x,{width:r,height:o,fps:c,durationSeconds:b}));i.stderr.write(` \x1B[33mEstimate:\x1B[0m ${a.estimatedCredits} credits (engine: ${a.engine})
|
|
75
|
+
`)}catch(a){i.stderr.write(` \x1B[31mError:\x1B[0m ${a instanceof Error?a.message:String(a)}
|
|
76
|
+
`)}break}case"/render":{let a=parseInt(x,10)-1;if(isNaN(a)||a<0||a>=s.scenes.length){i.stderr.write(` Usage: /render <scene number 1-${s.scenes.length}>
|
|
77
|
+
`);break}let f=s.scenes[a],h=f.manifestPath.replace(/\.vivi\.json$/,".mp4");i.stderr.write(` Rendering ${f.manifestPath} -> ${h}...
|
|
78
|
+
`);try{let y=await V({manifestPath:f.manifestPath,outPath:h},e,n);i.stderr.write(` \x1B[32m\u2713\x1B[0m Rendered to ${y.outPath} (${y.size} bytes)
|
|
79
|
+
`)}catch(y){i.stderr.write(` \x1B[31m\u2717\x1B[0m Render failed: ${y instanceof Error?y.message:String(y)}
|
|
80
|
+
`)}break}case"/verify":{let a=parseInt(x,10)-1;if(isNaN(a)||a<0||a>=s.scenes.length){i.stderr.write(` Usage: /verify <scene number 1-${s.scenes.length}>
|
|
81
|
+
`);break}let f=s.scenes[a];try{let h=await M(f.manifestPath,e,n);h.ok?i.stderr.write(` \x1B[32m\u2713\x1B[0m Manifest verified: ${h.manifest.manifestId}
|
|
82
|
+
`):i.stderr.write(` \x1B[31m\u2717\x1B[0m Verification failed: ${h.message}
|
|
83
|
+
`)}catch(h){i.stderr.write(` \x1B[31m\u2717\x1B[0m ${h instanceof Error?h.message:String(h)}
|
|
84
|
+
`)}break}default:i.stderr.write(` Unknown command: ${g}. Type /help for available commands.
|
|
85
|
+
`)}m.prompt();continue}s.turnNumber++;let S=s.turnNumber,J=`${d}/scene_${S}.vivi.json`.replace(/^\.\//,""),Y=`chat_${K()}`;i.stderr.write(`
|
|
86
|
+
`);try{let g=await W(u,j(I,{width:r,height:o,fps:c,durationSeconds:b}),Y,J,i);s.scenes.push({generationId:g.generationId,manifestPath:g.manifestPath,prompt:I,creditsCharged:g.creditsCharged}),i.stderr.write(` \x1B[2mScene #${S} saved. Use /render ${S} to render, or type another prompt.\x1B[0m
|
|
87
|
+
|
|
88
|
+
`)}catch(g){i.stderr.write(` \x1B[31mGeneration failed:\x1B[0m ${g instanceof Error?g.message:String(g)}
|
|
89
|
+
|
|
90
|
+
`)}m.prompt()}s.scenes.length>0&&i.stderr.write(`
|
|
91
|
+
Session complete. ${s.scenes.length} scene(s) generated.
|
|
92
|
+
`)}async function me(t,e,n,i){let d=B(t),r=await M(d,e,n);if(!r.ok)throw new l(`Manifest verification failed: ${r.message}`,1);v(i,{ok:!0,manifestId:r.manifest.manifestId,generationId:r.manifest.generationId,canonicalPayloadHash:r.canonicalPayloadHash})}async function le(t,e,n,i){let d=B(t),r=w(t,["out","o"])??"video.mp4",o=w(t,["format"]),c=p(t,["scale"]),b=p(t,["fps"]),u=$(t,["transparent"]),s=$(t,["verify-only"]),m=await V({manifestPath:d,outPath:r,...o?{format:o}:{},...c?{scale:c}:{},...b?{fps:b}:{},...u?{transparent:u}:{},verifyOnly:s},e,n);v(i,{ok:!0,manifestId:m.manifestId,out:m.outPath,size:m.size,paidOnly:!0})}async function pe(t,e,n){let{startMcpServer:i}=await import("./mcp-MRADYIPQ.js");await i({config:t,env:e,stdin:process.stdin,stdout:n.stdout,stderr:n.stderr})}async function ue(t=process.argv.slice(2),e=process.env,n={stdout:process.stdout,stderr:process.stderr}){let i=R(t);if(!i.command||i.command==="help"||$(i,["help"])){n.stdout.write(q);return}let d=k(e),r=await E(d);switch(i.command){case"login":await te(i,e,n);return;case"logout":await ie(e,n);return;case"whoami":v(n,await C(r,e).whoami());return;case"balance":v(n,await C(r,e).balance());return;case"topup":await ne(r,e,n);return;case"estimate":v(n,await C(r,e).estimate(D(i)));return;case"generate":await ae(i,r,e,n);return;case"chat":await de(i,r,e,n);return;case"status":{let o=i.positionals[0];if(!o)throw new l("Missing generation id.",2);v(n,await C(r,e).generationStatus(o));return}case"verify":await me(i,r,e,n);return;case"render":await le(i,r,e,n);return;case"mcp":await pe(r,e,n);return;default:throw new l(`Unknown command: ${i.command}
|
|
93
|
+
|
|
94
|
+
${q}`,2)}}ue().catch(t=>{if(T(t)){process.stderr.write(`${t.message}
|
|
95
|
+
`),process.exitCode=t.exitCode;return}process.stderr.write(`${t instanceof Error?t.stack??t.message:String(t)}
|
|
96
|
+
`),process.exitCode=1});export{ue as runCli};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
declare const DEFAULT_API_URL = "https://vivipilot.com";
|
|
2
|
+
type CliConfig = {
|
|
3
|
+
apiUrl?: string;
|
|
4
|
+
apiKey?: string;
|
|
5
|
+
publicKeys?: Record<string, string>;
|
|
6
|
+
};
|
|
7
|
+
type Env = Record<string, string | undefined>;
|
|
8
|
+
declare function defaultConfigPath(env?: Env): string;
|
|
9
|
+
declare function readConfig(path?: string): Promise<CliConfig>;
|
|
10
|
+
declare function writeConfig(config: CliConfig, path?: string): Promise<void>;
|
|
11
|
+
declare function deleteConfig(path?: string): Promise<void>;
|
|
12
|
+
declare function resolveApiUrl(config: CliConfig, env?: Env): string;
|
|
13
|
+
declare function resolveApiKey(config: CliConfig, env?: Env): string | undefined;
|
|
14
|
+
declare function resolvePublicKeys(config: CliConfig, env?: Env): Record<string, string>;
|
|
15
|
+
|
|
16
|
+
export { type CliConfig as C, DEFAULT_API_URL as D, type Env as E, deleteConfig as a, resolveApiKey as b, resolveApiUrl as c, defaultConfigPath as d, resolvePublicKeys as e, readConfig as r, writeConfig as w };
|