Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@resciencelab/shader-cli

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@resciencelab/shader-cli - npm Package Compare versions

Comparing version
0.1.0
to
0.1.1
+22
dist/utils/spinner.d.ts
export declare class Spinner {
private interval?;
private frame;
private start;
private text;
private isTty;
constructor(text: string);
spin(): this;
update(text: string): void;
succeed(text?: string): void;
fail(text?: string): void;
}
export declare class ProgressBar {
private _label;
private total;
private width;
private isTty;
private start;
constructor(_label: string, total: number);
update(current: number): void;
done(): void;
}
const FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
export class Spinner {
interval;
frame = 0;
start = Date.now();
text = "";
isTty;
constructor(text) {
this.text = text;
this.isTty = process.stderr.isTTY ?? false;
}
spin() {
if (!this.isTty) {
process.stderr.write(` ${this.text}...\n`);
return this;
}
this.start = Date.now();
this.interval = setInterval(() => {
const f = FRAMES[this.frame++ % FRAMES.length];
process.stderr.write(`\r \x1b[36m${f}\x1b[0m ${this.text}...`);
}, 80);
return this;
}
update(text) {
this.text = text;
}
succeed(text) {
if (this.interval)
clearInterval(this.interval);
const elapsed = ((Date.now() - this.start) / 1000).toFixed(1);
if (this.isTty)
process.stderr.write("\r\x1b[K");
process.stderr.write(` \x1b[32m✓\x1b[0m ${text ?? this.text} \x1b[90m(${elapsed}s)\x1b[0m\n`);
}
fail(text) {
if (this.interval)
clearInterval(this.interval);
if (this.isTty)
process.stderr.write("\r\x1b[K");
process.stderr.write(` \x1b[31m✗\x1b[0m ${text ?? this.text}\n`);
}
}
export class ProgressBar {
_label;
total;
width = 20;
isTty;
start = Date.now();
constructor(_label, total) {
this._label = _label;
this.total = total;
this.isTty = process.stderr.isTTY ?? false;
}
update(current) {
if (!this.isTty)
return;
const ratio = Math.min(current / this.total, 1);
const filled = Math.round(this.width * ratio);
const bar = "\u25B0".repeat(filled) + "\u25B1".repeat(this.width - filled);
const elapsed = (Date.now() - this.start) / 1000;
const eta = elapsed > 0.5 && ratio > 0 ? Math.max(0, (elapsed / ratio) - elapsed) : NaN;
const etaStr = isFinite(eta) ? ` \x1b[90m· ETA ${eta.toFixed(0)}s\x1b[0m` : "";
process.stderr.write(`\r ${bar} ${(ratio * 100).toFixed(0)}%${etaStr} `);
}
done() {
if (this.isTty)
process.stderr.write("\r\x1b[K");
}
}
+33
-3
import { success, error } from "../utils/output.js";
import { Spinner, ProgressBar } from "../utils/spinner.js";
import * as path from "node:path";

@@ -22,5 +23,8 @@ import * as fs from "node:fs";

async function renderWithPlaywright(projectJson, runtimeUrl, opts) {
const sp1 = new Spinner("Starting Playwright").spin();
const pw = await loadPlaywright();
sp1.succeed("Playwright ready");
const downloadDir = path.dirname(path.resolve(opts.outputPath));
fs.mkdirSync(downloadDir, { recursive: true });
const sp2 = new Spinner("Launching Chrome (WebGPU)").spin();
const browser = await pw.chromium.launch({

@@ -36,8 +40,20 @@ headless: false,

});
sp2.succeed("Chrome ready");
try {
const context = await browser.newContext({ acceptDownloads: true });
const page = await context.newPage();
console.log(` Loading runtime: ${runtimeUrl}`);
let hostname;
try {
hostname = new URL(runtimeUrl).hostname;
}
catch {
hostname = runtimeUrl;
}
const sp3 = new Spinner(`Loading ${hostname}`).spin();
await page.goto(runtimeUrl, { waitUntil: "networkidle" });
await page.waitForTimeout(3000);
sp3.succeed("Runtime loaded");
const project = JSON.parse(projectJson);
const layerCount = project.layers?.length ?? 0;
const sp4 = new Spinner(`Injecting project (${layerCount} layers, ${opts.duration}s)`).spin();
await page.evaluate((json) => {

@@ -66,3 +82,5 @@ const projectFile = JSON.parse(json);

await page.waitForTimeout(2000);
sp4.succeed("Project injected");
if (opts.format === "png") {
const sp5 = new Spinner("Exporting PNG").spin();
const downloadPromise = page.waitForEvent("download", { timeout: 30000 });

@@ -82,5 +100,12 @@ await page.evaluate((_time) => {

const download = await downloadPromise;
sp5.succeed("PNG captured");
const sp6 = new Spinner(`Saving to ${path.basename(opts.outputPath)}`).spin();
await download.saveAs(opts.outputPath);
sp6.succeed(`Saved ${path.basename(opts.outputPath)}`);
}
else {
const formatLabel = opts.format.toUpperCase();
const expectedMs = opts.duration * 1000 * 2;
const pb = new ProgressBar(`Recording ${formatLabel}`, expectedMs);
const spRec = new Spinner(`Recording ${formatLabel} (${opts.duration}s @ ${opts.fps}fps)`).spin();
const downloadPromise = page.waitForEvent("download", { timeout: 120000 });

@@ -105,4 +130,11 @@ await page.evaluate((format) => {

}, opts.format);
const recStart = Date.now();
const tick = setInterval(() => pb.update(Date.now() - recStart), 200);
const download = await downloadPromise;
clearInterval(tick);
pb.done();
spRec.succeed(`Recording complete`);
const sp6 = new Spinner(`Saving to ${path.basename(opts.outputPath)}`).spin();
await download.saveAs(opts.outputPath);
sp6.succeed(`Saved ${path.basename(opts.outputPath)}`);
}

@@ -130,3 +162,2 @@ return opts.outputPath;

const runtimeUrl = resolveRuntime(program);
console.log(` Exporting ${opts.format.toUpperCase()} video...`);
const projectJson = JSON.stringify(project);

@@ -158,3 +189,2 @@ const outputPath = await renderWithPlaywright(projectJson, runtimeUrl, {

const runtimeUrl = resolveRuntime(program);
console.log(" Exporting PNG...");
const projectJson = JSON.stringify(project);

@@ -161,0 +191,0 @@ const outputPath = await renderWithPlaywright(projectJson, runtimeUrl, {

+1
-1

@@ -18,3 +18,3 @@ #!/usr/bin/env node

.description("Agent-native CLI for Shader Lab — compose and export WebGPU shader scenes from the terminal")
.version("0.1.0")
.version("0.1.1")
.option("--json", "Output as JSON (agent-friendly)")

@@ -21,0 +21,0 @@ .option("--project <path>", "Open a .lab project file")

{
"name": "@resciencelab/shader-cli",
"version": "0.1.0",
"version": "0.1.1",
"description": "Agent-native CLI for Shader Lab — compose and export WebGPU shader scenes from the terminal",

@@ -5,0 +5,0 @@ "type": "module",