@vivipilot/cli 0.1.0 → 0.1.4

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.
Files changed (67) hide show
  1. package/dist/chunk-FGUKEVKT.js +1 -0
  2. package/dist/chunk-FN7FHZ3D.js +1 -0
  3. package/dist/chunk-JJDAKVXA.js +8 -0
  4. package/dist/chunk-L3C7EOPY.js +2 -0
  5. package/dist/chunk-ZIATVRTF.js +3 -0
  6. package/dist/chunk-ZZ72PPC3.js +1 -0
  7. package/dist/cli.d.ts +5 -4
  8. package/dist/cli.js +57 -495
  9. package/dist/config-DAG58pP-.d.ts +16 -0
  10. package/dist/index.d.ts +121 -7
  11. package/dist/index.js +1 -7
  12. package/dist/manifest-TDCIHZ46.js +1 -0
  13. package/dist/mcp-DTGU64NI.js +1 -0
  14. package/dist/render-CVXYAUBY.js +1 -0
  15. package/package.json +6 -2
  16. package/dist/api.d.ts +0 -86
  17. package/dist/api.d.ts.map +0 -1
  18. package/dist/api.js +0 -77
  19. package/dist/api.js.map +0 -1
  20. package/dist/args.d.ts +0 -11
  21. package/dist/args.d.ts.map +0 -1
  22. package/dist/args.js +0 -53
  23. package/dist/args.js.map +0 -1
  24. package/dist/browser.d.ts +0 -31
  25. package/dist/browser.d.ts.map +0 -1
  26. package/dist/browser.js +0 -162
  27. package/dist/browser.js.map +0 -1
  28. package/dist/cli.d.ts.map +0 -1
  29. package/dist/cli.js.map +0 -1
  30. package/dist/config.d.ts +0 -15
  31. package/dist/config.d.ts.map +0 -1
  32. package/dist/config.js +0 -58
  33. package/dist/config.js.map +0 -1
  34. package/dist/errors.d.ts +0 -6
  35. package/dist/errors.d.ts.map +0 -1
  36. package/dist/errors.js +0 -12
  37. package/dist/errors.js.map +0 -1
  38. package/dist/index.d.ts.map +0 -1
  39. package/dist/index.js.map +0 -1
  40. package/dist/manifest.d.ts +0 -40
  41. package/dist/manifest.d.ts.map +0 -1
  42. package/dist/manifest.js +0 -90
  43. package/dist/manifest.js.map +0 -1
  44. package/dist/mcp.d.ts +0 -13
  45. package/dist/mcp.d.ts.map +0 -1
  46. package/dist/mcp.js +0 -392
  47. package/dist/mcp.js.map +0 -1
  48. package/dist/render.d.ts +0 -21
  49. package/dist/render.d.ts.map +0 -1
  50. package/dist/render.js +0 -369
  51. package/dist/render.js.map +0 -1
  52. package/src/api.ts +0 -163
  53. package/src/args.test.ts +0 -21
  54. package/src/args.ts +0 -64
  55. package/src/browser.test.ts +0 -103
  56. package/src/browser.ts +0 -174
  57. package/src/cli.ts +0 -656
  58. package/src/config.test.ts +0 -30
  59. package/src/config.ts +0 -71
  60. package/src/errors.ts +0 -14
  61. package/src/index.ts +0 -25
  62. package/src/manifest.test.ts +0 -105
  63. package/src/manifest.ts +0 -126
  64. package/src/mcp.test.ts +0 -48
  65. package/src/mcp.ts +0 -438
  66. package/src/render.ts +0 -424
  67. package/tsconfig.json +0 -26
package/dist/cli.js CHANGED
@@ -1,64 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { randomUUID } from "node:crypto";
3
- import { createInterface } from "node:readline";
4
- import { mkdir, writeFile } from "node:fs/promises";
5
- import { dirname } from "node:path";
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 v,c as $,d as p}from"./chunk-ZZ72PPC3.js";import{a as E}from"./chunk-JJDAKVXA.js";import{b as M}from"./chunk-FN7FHZ3D.js";import{a as G}from"./chunk-FGUKEVKT.js";import{b as S,c as A,d as O,e as N,f as U,g as V,i as u,j as T}from"./chunk-L3C7EOPY.js";import{randomUUID as F}from"crypto";import{createInterface as H}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>]
@@ -67,14 +11,14 @@ Usage:
67
11
  vivipilot balance
68
12
  vivipilot topup
69
13
  vivipilot estimate --prompt "..." [--width 1280 --height 720 --fps 30 --duration 6 --engine auto]
70
- vivipilot generate --prompt "..." --out scene.vivi.json [--idempotency-key <key>]
14
+ vivipilot generate --prompt "..." --out video.mp4|scene.vivi.json [--idempotency-key <key>]
71
15
  vivipilot chat [--out-dir ./scenes] [--width 1280 --height 720 --fps 30 --duration 6]
72
16
  vivipilot verify scene.vivi.json
73
17
  vivipilot render scene.vivi.json --out video.mp4
74
18
  vivipilot mcp
75
19
 
76
20
  Commands:
77
- generate Create a motion graphics scene (shows live progress)
21
+ generate Create a scene (outputs video or signed vivi.json manifest)
78
22
  chat Interactive multi-turn session (like the browser editor)
79
23
  render Render a signed manifest to video locally
80
24
  verify Verify a manifest's signature
@@ -88,163 +32,17 @@ 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 writeJson(io, value) {
93
- io.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
94
- }
95
- function requirePrompt(parsed) {
96
- const prompt = flagString(parsed, ["prompt", "p"]) ?? parsed.positionals.join(" ");
97
- if (!prompt.trim())
98
- throw new CliError("Missing prompt. Pass --prompt \"...\".", 2);
99
- return prompt.trim();
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 f(t,e){t.stdout.write(`${JSON.stringify(e,null,2)}
36
+ `)}function ee(t){let e=v(t,["prompt","p"])??t.positionals.join(" ");if(!e.trim())throw new u('Missing prompt. Pass --prompt "...".',2);return e.trim()}function K(t){let e=p(t,["width","w"]),n=p(t,["height","h"]),i=p(t,["fps"]),c=p(t,["duration","duration-seconds"]),r=v(t,["engine"]),a=r==="pixi"||r==="three"||r==="auto"?r:void 0;return{prompt:ee(t),...e||n||i?{canvas:{width:e,height:n,fps:i}}:{},...c?{durationSeconds:c}:{},...a?{enginePreference:a}:{}}}function W(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 j(t,e){await X(Z(t),{recursive:!0}),await Q(t,`${JSON.stringify(e,null,2)}
37
+ `,"utf8")}function D(t){let e=t.positionals[0];if(!e)throw new u("Missing manifest path.",2);return e}async function te(t,e,n){let i=S(e),c=await A(i),r=v(t,["api-key"])??e.VIVIPILOT_API_KEY;if(!r)throw new u("Missing paid API key. Pass --api-key or set VIVIPILOT_API_KEY.",2);let a={...c,apiKey:r,apiUrl:v(t,["api-url"])??e.VIVIPILOT_API_URL??c.apiUrl};await O(a,i),f(n,{ok:!0,configPath:i,paidOnly:!0})}async function ie(t,e){let n=S(t);await N(n),f(e,{ok:!0,configPath:n})}function P(t,e){return new G({config:t,env:e})}async function ne(t,e,n){let i=U(t,e);f(n,{paidOnly:!0,topupUrl:`${i}/edit?billing=topup`,message:"CLI/MCP generation requires paid top-up credits."})}var re=1500,se=3e5;async function B(t,e,n,i,c){let r=new L(c);r.start("Starting generation...");try{let a=await t.startGenerate({...e,outputFormat:"manifest"},n),{generationId:d}=a;if(r.update("start",`Generation ${d} started (est. ${a.estimatedCredits} credits, engine: ${a.engine})`),a.idempotentReplay&&a.status==="completed"){let s=await t.generationProgress(d);if(s.manifestData)return await j(i,s.manifestData),r.succeed(`Scene saved to ${i} (${s.creditsCharged??0} credits)`),{generationId:d,manifestPath:i,creditsCharged:s.creditsCharged}}let b=Date.now(),l="";for(;Date.now()-b<se;){await new Promise(m=>setTimeout(m,re));let s;try{s=await t.generationProgress(d)}catch{continue}if(s.progressMessage&&s.progressMessage!==l&&(l=s.progressMessage,r.update(s.workflowStatus??s.status,s.progressMessage)),s.status==="completed")return s.manifestData?(await j(i,s.manifestData),r.succeed(`Scene saved to ${i} (${s.creditsCharged??0} credits)`),{generationId:d,manifestPath:i,creditsCharged:s.creditsCharged}):(r.succeed("Generation complete."),{generationId:d,manifestPath:i,creditsCharged:s.creditsCharged});if(s.status==="failed")throw r.fail(s.error??"Generation failed."),new u(s.error??"Generation failed.",1);if(s.status==="refunded")throw r.fail("Generation refunded."),new u("Generation was refunded.",1)}throw r.fail("Generation timed out after 5 minutes."),new u(`Generation ${d} timed out. Check status with: vivipilot status ${d}`,1)}catch(a){throw r.fail(a instanceof Error?a.message:String(a)),a}}async function ae(t,e,n,i){if(!V(e,n))throw new u("Vivipilot generate is paid-only. Set VIVIPILOT_API_KEY or run `vivipilot login --api-key <key>`.",2);let c=v(t,["out","o"])??"scene.vivi.json",r=c.endsWith(".mp4")||c.endsWith(".webm")||c.endsWith(".gif")||c.endsWith(".mov"),a=r?`${c}.vivi.json`:c,d=v(t,["idempotency-key"])??`cli_${F()}`,b=K(t),l=await B(P(e,n),b,d,a,i);if(r){i.stderr.write(`
38
+ Rendering manifest directly to ${c}...
39
+ `);let s=c.endsWith(".mp4")?"mp4":c.endsWith(".webm")?"webm":c.endsWith(".gif")?"gif":"mov",m=p(t,["scale"]),C=$(t,["transparent"]),y=await E({manifestPath:a,outPath:c,format:s,...m?{scale:m}:{},...C?{transparent:C}:{}},e,n);try{await(await import("fs/promises")).unlink(a)}catch{}f(i,{ok:!0,manifestId:y.manifestId,out:y.outPath,size:y.size,paidOnly:!0});return}f(i,{ok:!0,generationId:l.generationId,manifestPath:l.manifestPath,creditsCharged:l.creditsCharged,paidOnly:!0})}function oe(t){t.stderr.write(`
40
+ \x1B[1m\x1B[36m Vivipilot Chat\x1B[0m (paid-only)
41
+ `),t.stderr.write(` Type a prompt to generate a motion graphics scene.
42
+ `),t.stderr.write(` Commands: /render <n>, /verify <n>, /list, /balance, /help, /quit
43
+
44
+ `)}function ce(t){t.stderr.write(`
45
+ \x1B[1mChat Commands:\x1B[0m
248
46
  <prompt> Generate a new scene from your description
249
47
  /render <n> Render scene #n to video (e.g. /render 1)
250
48
  /verify <n> Verify scene #n manifest signature
@@ -254,283 +52,47 @@ function chatHelp(io) {
254
52
  /help Show this help
255
53
  /quit Exit chat
256
54
 
257
- \x1b[1mTips:\x1b[0m
55
+ \x1B[1mTips:\x1B[0m
258
56
  - Each prompt generates a new scene with live progress
259
57
  - Scenes are saved as scene_1.vivi.json, scene_2.vivi.json, etc.
260
58
  - Use /render to render any scene to video after generation
261
59
  - All generations are paid-only and charged to your API key
262
- \n`);
263
- }
264
- async function handleChat(parsed, config, env, io) {
265
- if (!resolveApiKey(config, env)) {
266
- throw new CliError("Vivipilot chat is paid-only. Set VIVIPILOT_API_KEY or run `vivipilot login --api-key <key>`.", 2);
267
- }
268
- const outDir = flagString(parsed, ["out-dir"]) ?? ".";
269
- const width = flagNumber(parsed, ["width", "w"]);
270
- const height = flagNumber(parsed, ["height", "h"]);
271
- const fps = flagNumber(parsed, ["fps"]);
272
- const durationSeconds = flagNumber(parsed, ["duration", "duration-seconds"]);
273
- const client = apiClient(config, env);
274
- const session = { scenes: [], turnNumber: 0 };
275
- chatBanner(io);
276
- // Show balance at start
277
- try {
278
- const bal = await client.balance();
279
- io.stderr.write(` \x1b[33mBalance:\x1b[0m ${bal.paidBalance ?? bal.balance} credits\n\n`);
280
- }
281
- catch {
282
- io.stderr.write(" \x1b[33mCould not fetch balance.\x1b[0m\n\n");
283
- }
284
- const rl = createInterface({
285
- input: process.stdin,
286
- output: process.stderr,
287
- prompt: "\x1b[36mvivipilot>\x1b[0m ",
288
- });
289
- rl.prompt();
290
- for await (const line of rl) {
291
- const trimmed = line.trim();
292
- if (!trimmed) {
293
- rl.prompt();
294
- continue;
295
- }
296
- // Handle slash commands
297
- if (trimmed.startsWith("/")) {
298
- const [cmd, ...rest] = trimmed.split(/\s+/);
299
- const arg = rest.join(" ");
300
- switch (cmd) {
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
60
+
61
+ `)}async function de(t,e,n,i){if(!V(e,n))throw new u("Vivipilot chat is paid-only. Set VIVIPILOT_API_KEY or run `vivipilot login --api-key <key>`.",2);let c=v(t,["out-dir"])??".",r=p(t,["width","w"]),a=p(t,["height","h"]),d=p(t,["fps"]),b=p(t,["duration","duration-seconds"]),l=P(e,n),s={scenes:[],turnNumber:0};oe(i);try{let C=await l.balance();i.stderr.write(` \x1B[33mBalance:\x1B[0m ${C.paidBalance??C.balance} credits
62
+
63
+ `)}catch{i.stderr.write(` \x1B[33mCould not fetch balance.\x1B[0m
64
+
65
+ `)}let m=H({input:process.stdin,output:process.stderr,prompt:"\x1B[36mvivipilot>\x1B[0m "});m.prompt();for await(let C of m){let y=C.trim();if(!y){m.prompt();continue}if(y.startsWith("/")){let[g,...z]=y.split(/\s+/),k=z.join(" ");switch(g){case"/quit":case"/exit":case"/q":i.stderr.write(`
66
+ Goodbye!
67
+ `),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.
68
+ `);else{i.stderr.write(`
69
+ \x1B[1mGenerated Scenes:\x1B[0m
70
+ `);for(let o=0;o<s.scenes.length;o++){let h=s.scenes[o];i.stderr.write(` ${o+1}. ${h.manifestPath} (${h.creditsCharged??"?"} credits)
71
+ `),i.stderr.write(` \x1B[2m${h.prompt.slice(0,80)}${h.prompt.length>80?"...":""}\x1B[0m
72
+ `)}i.stderr.write(`
73
+ `)}break}case"/balance":case"/bal":{try{let o=await l.balance();i.stderr.write(` \x1B[33mBalance:\x1B[0m ${o.paidBalance??o.balance} credits
74
+ `)}catch(o){i.stderr.write(` \x1B[31mError:\x1B[0m ${o instanceof Error?o.message:String(o)}
75
+ `)}break}case"/estimate":case"/est":{if(!k){i.stderr.write(` Usage: /estimate <prompt>
76
+ `);break}try{let o=await l.estimate(W(k,{width:r,height:a,fps:d,durationSeconds:b}));i.stderr.write(` \x1B[33mEstimate:\x1B[0m ${o.estimatedCredits} credits (engine: ${o.engine})
77
+ `)}catch(o){i.stderr.write(` \x1B[31mError:\x1B[0m ${o instanceof Error?o.message:String(o)}
78
+ `)}break}case"/render":{let o=parseInt(k,10)-1;if(isNaN(o)||o<0||o>=s.scenes.length){i.stderr.write(` Usage: /render <scene number 1-${s.scenes.length}>
79
+ `);break}let h=s.scenes[o],w=h.manifestPath.replace(/\.vivi\.json$/,".mp4");i.stderr.write(` Rendering ${h.manifestPath} -> ${w}...
80
+ `);try{let I=await E({manifestPath:h.manifestPath,outPath:w},e,n);i.stderr.write(` \x1B[32m\u2713\x1B[0m Rendered to ${I.outPath} (${I.size} bytes)
81
+ `)}catch(I){i.stderr.write(` \x1B[31m\u2717\x1B[0m Render failed: ${I instanceof Error?I.message:String(I)}
82
+ `)}break}case"/verify":{let o=parseInt(k,10)-1;if(isNaN(o)||o<0||o>=s.scenes.length){i.stderr.write(` Usage: /verify <scene number 1-${s.scenes.length}>
83
+ `);break}let h=s.scenes[o];try{let w=await M(h.manifestPath,e,n);w.ok?i.stderr.write(` \x1B[32m\u2713\x1B[0m Manifest verified: ${w.manifest.manifestId}
84
+ `):i.stderr.write(` \x1B[31m\u2717\x1B[0m Verification failed: ${w.message}
85
+ `)}catch(w){i.stderr.write(` \x1B[31m\u2717\x1B[0m ${w instanceof Error?w.message:String(w)}
86
+ `)}break}default:i.stderr.write(` Unknown command: ${g}. Type /help for available commands.
87
+ `)}m.prompt();continue}s.turnNumber++;let x=s.turnNumber,J=`${c}/scene_${x}.vivi.json`.replace(/^\.\//,""),Y=`chat_${F()}`;i.stderr.write(`
88
+ `);try{let g=await B(l,W(y,{width:r,height:a,fps:d,durationSeconds:b}),Y,J,i);s.scenes.push({generationId:g.generationId,manifestPath:g.manifestPath,prompt:y,creditsCharged:g.creditsCharged}),i.stderr.write(` \x1B[2mScene #${x} saved. Use /render ${x} to render, or type another prompt.\x1B[0m
89
+
90
+ `)}catch(g){i.stderr.write(` \x1B[31mGeneration failed:\x1B[0m ${g instanceof Error?g.message:String(g)}
91
+
92
+ `)}m.prompt()}s.scenes.length>0&&i.stderr.write(`
93
+ Session complete. ${s.scenes.length} scene(s) generated.
94
+ `)}async function me(t,e,n,i){let c=D(t),r=await M(c,e,n);if(!r.ok)throw new u(`Manifest verification failed: ${r.message}`,1);f(i,{ok:!0,manifestId:r.manifest.manifestId,generationId:r.manifest.generationId,canonicalPayloadHash:r.canonicalPayloadHash})}async function le(t,e,n,i){let c=D(t),r=v(t,["out","o"])??"video.mp4",a=v(t,["format"]),d=p(t,["scale"]),b=p(t,["fps"]),l=$(t,["transparent"]),s=$(t,["verify-only"]),m=await E({manifestPath:c,outPath:r,...a?{format:a}:{},...d?{scale:d}:{},...b?{fps:b}:{},...l?{transparent:l}:{},verifyOnly:s},e,n);f(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-DTGU64NI.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 c=S(e),r=await A(c);switch(i.command){case"login":await te(i,e,n);return;case"logout":await ie(e,n);return;case"whoami":f(n,await P(r,e).whoami());return;case"balance":f(n,await P(r,e).balance());return;case"topup":await ne(r,e,n);return;case"estimate":f(n,await P(r,e).estimate(K(i)));return;case"generate":await ae(i,r,e,n);return;case"chat":await de(i,r,e,n);return;case"status":{let a=i.positionals[0];if(!a)throw new u("Missing generation id.",2);f(n,await P(r,e).generationStatus(a));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 u(`Unknown command: ${i.command}
95
+
96
+ ${q}`,2)}}ue().catch(t=>{if(T(t)){process.stderr.write(`${t.message}
97
+ `),process.exitCode=t.exitCode;return}process.stderr.write(`${t instanceof Error?t.stack??t.message:String(t)}
98
+ `),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 };