93 lines
3.8 KiB
TypeScript
Executable File
93 lines
3.8 KiB
TypeScript
Executable File
#!/usr/bin/env bun
|
|
import { resolve } from "path";
|
|
|
|
const { resolvePluginRoot } = await import(resolve(import.meta.dir, "../../../resolve-root.ts")).catch(async () => {
|
|
// Fallback: find resolve-root.ts via env var or Claude Code plugin paths
|
|
const _tryPaths = [process.env.GEMSKILLS_ROOT || ""];
|
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
try {
|
|
const d = JSON.parse((await import("fs")).readFileSync(resolve(home, ".claude/plugins/installed_plugins.json"), "utf-8"));
|
|
const ip = d.plugins?.["gemskills@b-open-io"]?.[0]?.installPath;
|
|
if (ip) _tryPaths.push(ip);
|
|
} catch {}
|
|
try {
|
|
const cd = resolve(home, ".claude/plugins/cache/b-open-io/gemskills");
|
|
const vs = (await import("fs")).readdirSync(cd).filter((v: string) => /^\d+\./.test(v)).sort();
|
|
for (let i = vs.length - 1; i >= 0; i--) _tryPaths.push(resolve(cd, vs[i]));
|
|
} catch {}
|
|
for (const p of _tryPaths) {
|
|
try { if (p) return await import(resolve(p, "resolve-root.ts")); } catch {}
|
|
}
|
|
throw new Error("Cannot find gemskills. Set GEMSKILLS_ROOT or: claude plugin install gemskills@b-open-io");
|
|
});
|
|
const PLUGIN_ROOT = resolvePluginRoot(import.meta.dir);
|
|
const { callGeminiEdit } = await import(resolve(PLUGIN_ROOT, "utils.ts")) as typeof import("../../../utils");
|
|
type GeminiImageResult = import("../../../utils").GeminiImageResult;
|
|
const { getApiKey, loadImageRequired, loadImage, saveImage, parseArgs } = await import(resolve(PLUGIN_ROOT, "shared.ts")) as typeof import("../../../shared");
|
|
|
|
const { positional, flags, multi } = parseArgs(undefined, ["input"]);
|
|
|
|
const inputPath = positional[0];
|
|
const prompt = positional.slice(1).join(" ");
|
|
|
|
if (!inputPath || !prompt) {
|
|
console.error("Error: Input image and prompt required");
|
|
console.error("Usage: bun run edit.ts <input-image> \"edit prompt\" [options]");
|
|
console.error("Options:");
|
|
console.error(" --input <path> Additional reference image (multiple allowed)");
|
|
console.error(" --mask <path> Mask image for targeted editing");
|
|
console.error(" --mode <mode> Edit mode: inpaint or outpaint");
|
|
console.error(" --aspect <ratio> Aspect ratio");
|
|
console.error(" --size <size> Image size");
|
|
console.error(" --count <n> Number of variations");
|
|
console.error(" --seed <n> Random seed");
|
|
console.error(" --output <path> Output file path");
|
|
process.exit(1);
|
|
}
|
|
|
|
const options: any = {};
|
|
if (flags.format) options.outputFormat = flags.format;
|
|
if (flags.quality) options.jpegQuality = parseInt(flags.quality);
|
|
if (flags.negative) options.negativePrompt = flags.negative;
|
|
if (flags.count) options.numberOfImages = parseInt(flags.count);
|
|
if (flags.guidance) options.guidanceScale = parseFloat(flags.guidance);
|
|
if (flags.seed) options.seed = parseInt(flags.seed);
|
|
if (flags.mode) options.editMode = flags.mode;
|
|
if (flags.aspect) options.aspectRatio = flags.aspect;
|
|
if (flags.size) options.imageSize = flags.size;
|
|
|
|
const apiKey = getApiKey();
|
|
const imageData = await loadImageRequired(inputPath);
|
|
const maskData = flags.mask ? await loadImage(flags.mask) : undefined;
|
|
|
|
if (multi.input.length > 0) {
|
|
const refs = [];
|
|
for (const p of multi.input) {
|
|
const img = await loadImage(p);
|
|
if (img) refs.push(img);
|
|
}
|
|
if (refs.length > 0) options.referenceImages = refs;
|
|
}
|
|
|
|
console.error("Editing image...\n");
|
|
const result: GeminiImageResult = await callGeminiEdit(
|
|
apiKey,
|
|
prompt,
|
|
imageData,
|
|
maskData || undefined,
|
|
options
|
|
);
|
|
|
|
for (let i = 0; i < result.images.length; i++) {
|
|
const img = result.images[i];
|
|
const outputPath = flags.output;
|
|
const finalPath =
|
|
outputPath && result.images.length > 1
|
|
? outputPath.replace(/(\.\w+)$/, `_${i + 1}$1`)
|
|
: outputPath;
|
|
const savedPath = await saveImage(img.data, img.mimeType, finalPath);
|
|
console.log(`✓ Saved: ${savedPath}`);
|
|
}
|
|
|
|
// Do not read generated images back. Instruct user to visually inspect.
|