clawbr 0.0.37 → 0.0.39

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.
@@ -10,7 +10,7 @@ function _ts_metadata(k, v) {
10
10
  import { Command, CommandRunner, Option } from "nest-commander";
11
11
  import ora from "ora";
12
12
  import { loadCredentials } from "../utils/credentials.js";
13
- import { encodeImageToDataUri, validateImageInput } from "../utils/image.js";
13
+ import { resolveImageToDataUri, validateImageInput } from "../utils/image.js";
14
14
  import { analyzeImage } from "../utils/vision.js";
15
15
  import { requireOnboarding } from "../utils/config.js";
16
16
  export class AnalyzeCommand extends CommandRunner {
@@ -42,7 +42,7 @@ export class AnalyzeCommand extends CommandRunner {
42
42
  // ─────────────────────────────────────────────────────────────────────
43
43
  // Prepare Image
44
44
  // ─────────────────────────────────────────────────────────────────────
45
- const imageData = encodeImageToDataUri(image);
45
+ const imageData = await resolveImageToDataUri(image);
46
46
  // ─────────────────────────────────────────────────────────────────────
47
47
  // Analyze Image
48
48
  // ─────────────────────────────────────────────────────────────────────
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/commands/analyze.command.ts"],"sourcesContent":["import { Command, CommandRunner, Option } from \"nest-commander\";\nimport ora from \"ora\";\nimport { loadCredentials } from \"../utils/credentials.js\";\nimport { encodeImageToDataUri, validateImageInput } from \"../utils/image.js\";\nimport { analyzeImage } from \"../utils/vision.js\";\nimport { requireOnboarding } from \"../utils/config.js\";\n\ninterface AnalyzeCommandOptions {\n image?: string;\n prompt?: string;\n json?: boolean;\n}\n\n@Command({\n name: \"analyze\",\n description: \"Analyze an image using AI vision models\",\n arguments: \"\",\n options: { isDefault: false },\n})\nexport class AnalyzeCommand extends CommandRunner {\n async run(inputs: string[], options: AnalyzeCommandOptions): Promise<void> {\n await requireOnboarding();\n const { image, prompt, json = false } = options;\n\n // ─────────────────────────────────────────────────────────────────────\n // Validation\n // ─────────────────────────────────────────────────────────────────────\n if (!image) {\n throw new Error(\n '--image is required. Example: clawbr analyze --image \"./photo.jpg\" --prompt \"Describe this image\"'\n );\n }\n\n const validation = validateImageInput(image);\n if (!validation.valid) {\n throw new Error(validation.error);\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Load Credentials\n // ─────────────────────────────────────────────────────────────────────\n const credentials = loadCredentials();\n\n if (!credentials) {\n throw new Error(\"Credentials not found. Run 'clawbr onboard' first to set up your account.\");\n }\n\n const { aiProvider, apiKeys } = credentials;\n const apiKey = apiKeys[aiProvider as keyof typeof apiKeys];\n\n if (!apiKey) {\n throw new Error(\n `No API key found for provider '${aiProvider}'. Run 'clawbr onboard' to configure.`\n );\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Prepare Image\n // ─────────────────────────────────────────────────────────────────────\n const imageData = encodeImageToDataUri(image);\n\n // ─────────────────────────────────────────────────────────────────────\n // Analyze Image\n // ─────────────────────────────────────────────────────────────────────\n const spinner = json ? null : ora(\"Analyzing image...\").start();\n\n try {\n const analysis = await analyzeImage(\n {\n provider: aiProvider as \"openrouter\" | \"google\" | \"openai\",\n apiKey,\n },\n imageData,\n prompt || \"Describe this image in detail.\"\n );\n\n if (spinner) {\n spinner.succeed(\"Image analyzed successfully!\");\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Output\n // ─────────────────────────────────────────────────────────────────────\n if (json) {\n console.log(\n JSON.stringify(\n {\n success: true,\n analysis,\n provider: aiProvider,\n },\n null,\n 2\n )\n );\n } else {\n console.log(\"\\n🔍 Image Analysis:\");\n console.log(\"═════════════════════════════════════\");\n console.log(analysis);\n console.log(\"─────────────────────────────────────\");\n console.log(`Provider: ${aiProvider}`);\n console.log(\"─────────────────────────────────────\\n\");\n }\n } catch (error) {\n if (spinner && spinner.isSpinning) {\n spinner.fail(\"Image analysis failed\");\n }\n throw error;\n }\n }\n\n @Option({\n flags: \"-i, --image <path>\",\n description: \"Path to the image file or URL\",\n })\n parseImage(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-p, --prompt <text>\",\n description: 'Custom prompt for analysis (default: \"Describe this image in detail.\")',\n })\n parsePrompt(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"--json\",\n description: \"Output in JSON format\",\n })\n parseJson(): boolean {\n return true;\n }\n}\n"],"names":["Command","CommandRunner","Option","ora","loadCredentials","encodeImageToDataUri","validateImageInput","analyzeImage","requireOnboarding","AnalyzeCommand","run","inputs","options","image","prompt","json","Error","validation","valid","error","credentials","aiProvider","apiKeys","apiKey","imageData","spinner","start","analysis","provider","succeed","console","log","JSON","stringify","success","isSpinning","fail","parseImage","val","parsePrompt","parseJson","flags","description","name","arguments","isDefault"],"mappings":";;;;;;;;;AAAA,SAASA,OAAO,EAAEC,aAAa,EAAEC,MAAM,QAAQ,iBAAiB;AAChE,OAAOC,SAAS,MAAM;AACtB,SAASC,eAAe,QAAQ,0BAA0B;AAC1D,SAASC,oBAAoB,EAAEC,kBAAkB,QAAQ,oBAAoB;AAC7E,SAASC,YAAY,QAAQ,qBAAqB;AAClD,SAASC,iBAAiB,QAAQ,qBAAqB;AAcvD,OAAO,MAAMC,uBAAuBR;IAClC,MAAMS,IAAIC,MAAgB,EAAEC,OAA8B,EAAiB;QACzE,MAAMJ;QACN,MAAM,EAAEK,KAAK,EAAEC,MAAM,EAAEC,OAAO,KAAK,EAAE,GAAGH;QAExC,wEAAwE;QACxE,aAAa;QACb,wEAAwE;QACxE,IAAI,CAACC,OAAO;YACV,MAAM,IAAIG,MACR;QAEJ;QAEA,MAAMC,aAAaX,mBAAmBO;QACtC,IAAI,CAACI,WAAWC,KAAK,EAAE;YACrB,MAAM,IAAIF,MAAMC,WAAWE,KAAK;QAClC;QAEA,wEAAwE;QACxE,mBAAmB;QACnB,wEAAwE;QACxE,MAAMC,cAAchB;QAEpB,IAAI,CAACgB,aAAa;YAChB,MAAM,IAAIJ,MAAM;QAClB;QAEA,MAAM,EAAEK,UAAU,EAAEC,OAAO,EAAE,GAAGF;QAChC,MAAMG,SAASD,OAAO,CAACD,WAAmC;QAE1D,IAAI,CAACE,QAAQ;YACX,MAAM,IAAIP,MACR,CAAC,+BAA+B,EAAEK,WAAW,qCAAqC,CAAC;QAEvF;QAEA,wEAAwE;QACxE,gBAAgB;QAChB,wEAAwE;QACxE,MAAMG,YAAYnB,qBAAqBQ;QAEvC,wEAAwE;QACxE,gBAAgB;QAChB,wEAAwE;QACxE,MAAMY,UAAUV,OAAO,OAAOZ,IAAI,sBAAsBuB,KAAK;QAE7D,IAAI;YACF,MAAMC,WAAW,MAAMpB,aACrB;gBACEqB,UAAUP;gBACVE;YACF,GACAC,WACAV,UAAU;YAGZ,IAAIW,SAAS;gBACXA,QAAQI,OAAO,CAAC;YAClB;YAEA,wEAAwE;YACxE,SAAS;YACT,wEAAwE;YACxE,IAAId,MAAM;gBACRe,QAAQC,GAAG,CACTC,KAAKC,SAAS,CACZ;oBACEC,SAAS;oBACTP;oBACAC,UAAUP;gBACZ,GACA,MACA;YAGN,OAAO;gBACLS,QAAQC,GAAG,CAAC;gBACZD,QAAQC,GAAG,CAAC;gBACZD,QAAQC,GAAG,CAACJ;gBACZG,QAAQC,GAAG,CAAC;gBACZD,QAAQC,GAAG,CAAC,CAAC,UAAU,EAAEV,YAAY;gBACrCS,QAAQC,GAAG,CAAC;YACd;QACF,EAAE,OAAOZ,OAAO;YACd,IAAIM,WAAWA,QAAQU,UAAU,EAAE;gBACjCV,QAAQW,IAAI,CAAC;YACf;YACA,MAAMjB;QACR;IACF;IAMAkB,WAAWC,GAAW,EAAU;QAC9B,OAAOA;IACT;IAMAC,YAAYD,GAAW,EAAU;QAC/B,OAAOA;IACT;IAMAE,YAAqB;QACnB,OAAO;IACT;AACF;;;QAtBIC,OAAO;QACPC,aAAa;;;;;;;;;;QAObD,OAAO;QACPC,aAAa;;;;;;;;;;QAObD,OAAO;QACPC,aAAa;;;;;;;;QAnHfC,MAAM;QACND,aAAa;QACbE,WAAW;QACXhC,SAAS;YAAEiC,WAAW;QAAM"}
1
+ {"version":3,"sources":["../../src/commands/analyze.command.ts"],"sourcesContent":["import { Command, CommandRunner, Option } from \"nest-commander\";\nimport ora from \"ora\";\nimport { loadCredentials } from \"../utils/credentials.js\";\nimport { resolveImageToDataUri, validateImageInput } from \"../utils/image.js\";\nimport { analyzeImage } from \"../utils/vision.js\";\nimport { requireOnboarding } from \"../utils/config.js\";\n\ninterface AnalyzeCommandOptions {\n image?: string;\n prompt?: string;\n json?: boolean;\n}\n\n@Command({\n name: \"analyze\",\n description: \"Analyze an image using AI vision models\",\n arguments: \"\",\n options: { isDefault: false },\n})\nexport class AnalyzeCommand extends CommandRunner {\n async run(inputs: string[], options: AnalyzeCommandOptions): Promise<void> {\n await requireOnboarding();\n const { image, prompt, json = false } = options;\n\n // ─────────────────────────────────────────────────────────────────────\n // Validation\n // ─────────────────────────────────────────────────────────────────────\n if (!image) {\n throw new Error(\n '--image is required. Example: clawbr analyze --image \"./photo.jpg\" --prompt \"Describe this image\"'\n );\n }\n\n const validation = validateImageInput(image);\n if (!validation.valid) {\n throw new Error(validation.error);\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Load Credentials\n // ─────────────────────────────────────────────────────────────────────\n const credentials = loadCredentials();\n\n if (!credentials) {\n throw new Error(\"Credentials not found. Run 'clawbr onboard' first to set up your account.\");\n }\n\n const { aiProvider, apiKeys } = credentials;\n const apiKey = apiKeys[aiProvider as keyof typeof apiKeys];\n\n if (!apiKey) {\n throw new Error(\n `No API key found for provider '${aiProvider}'. Run 'clawbr onboard' to configure.`\n );\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Prepare Image\n // ─────────────────────────────────────────────────────────────────────\n const imageData = await resolveImageToDataUri(image);\n\n // ─────────────────────────────────────────────────────────────────────\n // Analyze Image\n // ─────────────────────────────────────────────────────────────────────\n const spinner = json ? null : ora(\"Analyzing image...\").start();\n\n try {\n const analysis = await analyzeImage(\n {\n provider: aiProvider as \"openrouter\" | \"google\" | \"openai\",\n apiKey,\n },\n imageData,\n prompt || \"Describe this image in detail.\"\n );\n\n if (spinner) {\n spinner.succeed(\"Image analyzed successfully!\");\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Output\n // ─────────────────────────────────────────────────────────────────────\n if (json) {\n console.log(\n JSON.stringify(\n {\n success: true,\n analysis,\n provider: aiProvider,\n },\n null,\n 2\n )\n );\n } else {\n console.log(\"\\n🔍 Image Analysis:\");\n console.log(\"═════════════════════════════════════\");\n console.log(analysis);\n console.log(\"─────────────────────────────────────\");\n console.log(`Provider: ${aiProvider}`);\n console.log(\"─────────────────────────────────────\\n\");\n }\n } catch (error) {\n if (spinner && spinner.isSpinning) {\n spinner.fail(\"Image analysis failed\");\n }\n throw error;\n }\n }\n\n @Option({\n flags: \"-i, --image <path>\",\n description: \"Path to the image file or URL\",\n })\n parseImage(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-p, --prompt <text>\",\n description: 'Custom prompt for analysis (default: \"Describe this image in detail.\")',\n })\n parsePrompt(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"--json\",\n description: \"Output in JSON format\",\n })\n parseJson(): boolean {\n return true;\n }\n}\n"],"names":["Command","CommandRunner","Option","ora","loadCredentials","resolveImageToDataUri","validateImageInput","analyzeImage","requireOnboarding","AnalyzeCommand","run","inputs","options","image","prompt","json","Error","validation","valid","error","credentials","aiProvider","apiKeys","apiKey","imageData","spinner","start","analysis","provider","succeed","console","log","JSON","stringify","success","isSpinning","fail","parseImage","val","parsePrompt","parseJson","flags","description","name","arguments","isDefault"],"mappings":";;;;;;;;;AAAA,SAASA,OAAO,EAAEC,aAAa,EAAEC,MAAM,QAAQ,iBAAiB;AAChE,OAAOC,SAAS,MAAM;AACtB,SAASC,eAAe,QAAQ,0BAA0B;AAC1D,SAASC,qBAAqB,EAAEC,kBAAkB,QAAQ,oBAAoB;AAC9E,SAASC,YAAY,QAAQ,qBAAqB;AAClD,SAASC,iBAAiB,QAAQ,qBAAqB;AAcvD,OAAO,MAAMC,uBAAuBR;IAClC,MAAMS,IAAIC,MAAgB,EAAEC,OAA8B,EAAiB;QACzE,MAAMJ;QACN,MAAM,EAAEK,KAAK,EAAEC,MAAM,EAAEC,OAAO,KAAK,EAAE,GAAGH;QAExC,wEAAwE;QACxE,aAAa;QACb,wEAAwE;QACxE,IAAI,CAACC,OAAO;YACV,MAAM,IAAIG,MACR;QAEJ;QAEA,MAAMC,aAAaX,mBAAmBO;QACtC,IAAI,CAACI,WAAWC,KAAK,EAAE;YACrB,MAAM,IAAIF,MAAMC,WAAWE,KAAK;QAClC;QAEA,wEAAwE;QACxE,mBAAmB;QACnB,wEAAwE;QACxE,MAAMC,cAAchB;QAEpB,IAAI,CAACgB,aAAa;YAChB,MAAM,IAAIJ,MAAM;QAClB;QAEA,MAAM,EAAEK,UAAU,EAAEC,OAAO,EAAE,GAAGF;QAChC,MAAMG,SAASD,OAAO,CAACD,WAAmC;QAE1D,IAAI,CAACE,QAAQ;YACX,MAAM,IAAIP,MACR,CAAC,+BAA+B,EAAEK,WAAW,qCAAqC,CAAC;QAEvF;QAEA,wEAAwE;QACxE,gBAAgB;QAChB,wEAAwE;QACxE,MAAMG,YAAY,MAAMnB,sBAAsBQ;QAE9C,wEAAwE;QACxE,gBAAgB;QAChB,wEAAwE;QACxE,MAAMY,UAAUV,OAAO,OAAOZ,IAAI,sBAAsBuB,KAAK;QAE7D,IAAI;YACF,MAAMC,WAAW,MAAMpB,aACrB;gBACEqB,UAAUP;gBACVE;YACF,GACAC,WACAV,UAAU;YAGZ,IAAIW,SAAS;gBACXA,QAAQI,OAAO,CAAC;YAClB;YAEA,wEAAwE;YACxE,SAAS;YACT,wEAAwE;YACxE,IAAId,MAAM;gBACRe,QAAQC,GAAG,CACTC,KAAKC,SAAS,CACZ;oBACEC,SAAS;oBACTP;oBACAC,UAAUP;gBACZ,GACA,MACA;YAGN,OAAO;gBACLS,QAAQC,GAAG,CAAC;gBACZD,QAAQC,GAAG,CAAC;gBACZD,QAAQC,GAAG,CAACJ;gBACZG,QAAQC,GAAG,CAAC;gBACZD,QAAQC,GAAG,CAAC,CAAC,UAAU,EAAEV,YAAY;gBACrCS,QAAQC,GAAG,CAAC;YACd;QACF,EAAE,OAAOZ,OAAO;YACd,IAAIM,WAAWA,QAAQU,UAAU,EAAE;gBACjCV,QAAQW,IAAI,CAAC;YACf;YACA,MAAMjB;QACR;IACF;IAMAkB,WAAWC,GAAW,EAAU;QAC9B,OAAOA;IACT;IAMAC,YAAYD,GAAW,EAAU;QAC/B,OAAOA;IACT;IAMAE,YAAqB;QACnB,OAAO;IACT;AACF;;;QAtBIC,OAAO;QACPC,aAAa;;;;;;;;;;QAObD,OAAO;QACPC,aAAa;;;;;;;;;;QAObD,OAAO;QACPC,aAAa;;;;;;;;QAnHfC,MAAM;QACND,aAAa;QACbE,WAAW;QACXhC,SAAS;YAAEiC,WAAW;QAAM"}
@@ -16,7 +16,7 @@ import { generateImage } from "ai";
16
16
  import { createOpenAI } from "@ai-sdk/openai";
17
17
  import { createGoogleGenerativeAI } from "@ai-sdk/google";
18
18
  import { loadCredentials } from "../utils/credentials.js";
19
- import { encodeImageToDataUri, validateImageInput } from "../utils/image.js";
19
+ import { resolveImageToDataUri, validateImageInput } from "../utils/image.js";
20
20
  import { requireOnboarding } from "../utils/config.js";
21
21
  import { getProviderModels, getModelById, isValidModel, getPrimaryModel, getFallbackModels, supportsReferenceImage, formatModelList } from "../config/image-models.js";
22
22
  export class GenerateCommand extends CommandRunner {
@@ -79,7 +79,7 @@ export class GenerateCommand extends CommandRunner {
79
79
  // ─────────────────────────────────────────────────────────────────────
80
80
  // Prepare source image if provided
81
81
  // ─────────────────────────────────────────────────────────────────────
82
- const sourceImageData = sourceImage ? encodeImageToDataUri(sourceImage) : undefined;
82
+ const sourceImageData = sourceImage ? await resolveImageToDataUri(sourceImage) : undefined;
83
83
  // ─────────────────────────────────────────────────────────────────────
84
84
  // Generate Image with Smart Fallback
85
85
  // ─────────────────────────────────────────────────────────────────────
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/commands/generate.command.ts"],"sourcesContent":["import { Command, CommandRunner, Option } from \"nest-commander\";\nimport { writeFileSync } from \"fs\";\nimport ora from \"ora\";\nimport fetch from \"node-fetch\";\nimport { resolve } from \"path\";\nimport { generateImage } from \"ai\";\nimport { createOpenAI } from \"@ai-sdk/openai\";\nimport { createGoogleGenerativeAI } from \"@ai-sdk/google\";\nimport { loadCredentials } from \"../utils/credentials.js\";\nimport { encodeImageToDataUri, validateImageInput } from \"../utils/image.js\";\nimport { requireOnboarding } from \"../utils/config.js\";\nimport {\n getProviderModels,\n getModelById,\n isValidModel,\n getPrimaryModel,\n getFallbackModels,\n supportsReferenceImage,\n formatModelList,\n} from \"../config/image-models.js\";\n\ninterface GenerateCommandOptions {\n prompt?: string;\n output?: string;\n size?: string;\n sourceImage?: string;\n model?: string;\n json?: boolean;\n}\n\n@Command({\n name: \"generate\",\n description: \"Generate an image using AI with smart model fallback\",\n arguments: \"\",\n options: { isDefault: false },\n})\nexport class GenerateCommand extends CommandRunner {\n async run(inputs: string[], options: GenerateCommandOptions): Promise<void> {\n await requireOnboarding();\n const { prompt, output, size = \"1024x1024\", sourceImage, model, json = false } = options;\n\n // ─────────────────────────────────────────────────────────────────────\n // Validation\n // ─────────────────────────────────────────────────────────────────────\n if (!prompt) {\n throw new Error(\n '--prompt is required. Example: clawbr generate --prompt \"a robot building software\" --output \"./robot.png\"'\n );\n }\n\n if (!output) {\n throw new Error(\n '--output is required. Example: clawbr generate --prompt \"...\" --output \"./image.png\"'\n );\n }\n\n // Validate source image if provided\n if (sourceImage) {\n const validation = validateImageInput(sourceImage);\n if (!validation.valid) {\n throw new Error(validation.error);\n }\n }\n\n // Validate size\n const validSizes = [\"256x256\", \"512x512\", \"1024x1024\", \"1792x1024\", \"1024x1792\"];\n if (!validSizes.includes(size)) {\n throw new Error(`Invalid size. Must be one of: ${validSizes.join(\", \")}`);\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Load Credentials\n // ─────────────────────────────────────────────────────────────────────\n const credentials = loadCredentials();\n\n if (!credentials) {\n throw new Error(\"Credentials not found. Run 'clawbr onboard' first to set up your account.\");\n }\n\n const { aiProvider, apiKeys } = credentials;\n const apiKey = apiKeys[aiProvider as keyof typeof apiKeys];\n\n if (!apiKey) {\n throw new Error(\n `No API key found for provider '${aiProvider}'. Run 'clawbr onboard' to configure.`\n );\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Validate model if provided\n // ─────────────────────────────────────────────────────────────────────\n if (model && !isValidModel(aiProvider, model)) {\n const availableModels = formatModelList(aiProvider);\n throw new Error(\n `Invalid model '${model}' for provider '${aiProvider}'.\\n\\nAvailable models:\\n${availableModels}`\n );\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Check reference image support\n // ─────────────────────────────────────────────────────────────────────\n if (sourceImage && model && !supportsReferenceImage(aiProvider, model)) {\n const modelInfo = getModelById(aiProvider, model);\n throw new Error(\n `Model '${modelInfo?.name || model}' does not support reference images.\\n\\n` +\n `For reference image support with ${aiProvider}, use one of:\\n` +\n getProviderModels(aiProvider)\n .filter((m) => m.supportsReferenceImage)\n .map((m) => ` • ${m.id}`)\n .join(\"\\n\")\n );\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Prepare source image if provided\n // ─────────────────────────────────────────────────────────────────────\n const sourceImageData = sourceImage ? encodeImageToDataUri(sourceImage) : undefined;\n\n // ─────────────────────────────────────────────────────────────────────\n // Generate Image with Smart Fallback\n // ─────────────────────────────────────────────────────────────────────\n const spinner = json\n ? null\n : ora(sourceImageData ? \"Generating image from source...\" : \"Generating image...\").start();\n\n try {\n let imageBuffer: Buffer;\n\n // Determine models to use\n const primaryModel = model || getPrimaryModel(aiProvider);\n const fallbackModels = model ? [] : getFallbackModels(aiProvider);\n\n if (aiProvider === \"openrouter\") {\n imageBuffer = await this.generateWithFallback(\n prompt,\n size,\n apiKey,\n \"openrouter\",\n { primary: primaryModel, fallbacks: fallbackModels },\n spinner,\n sourceImageData\n );\n } else if (aiProvider === \"openai\") {\n if (sourceImageData) {\n throw new Error(\n \"OpenAI does not support image-to-image generation. Use OpenRouter with a model that supports reference images.\"\n );\n }\n imageBuffer = await this.generateWithFallback(\n prompt,\n size,\n apiKey,\n \"openai\",\n { primary: primaryModel, fallbacks: fallbackModels },\n spinner\n );\n } else if (aiProvider === \"google\") {\n if (sourceImageData) {\n throw new Error(\n \"Google Imagen does not support image-to-image generation. Use OpenRouter with a model that supports reference images.\"\n );\n }\n imageBuffer = await this.generateWithFallback(\n prompt,\n size,\n apiKey,\n \"google\",\n { primary: primaryModel, fallbacks: fallbackModels },\n spinner\n );\n } else {\n if (spinner) spinner.fail();\n throw new Error(`Unsupported AI provider: ${aiProvider}`);\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Save Image\n // ─────────────────────────────────────────────────────────────────────\n const outputPath = resolve(output);\n writeFileSync(outputPath, imageBuffer);\n\n if (spinner) {\n spinner.succeed(`Image generated and saved to: ${outputPath}`);\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Output\n // ─────────────────────────────────────────────────────────────────────\n if (json) {\n console.log(\n JSON.stringify(\n {\n success: true,\n prompt,\n output: outputPath,\n size,\n provider: aiProvider,\n },\n null,\n 2\n )\n );\n } else {\n console.log(\"\\n🎨 Image Generation Complete!\");\n console.log(\"─────────────────────────────────────\");\n console.log(`Prompt: ${prompt}`);\n console.log(`Size: ${size}`);\n if (sourceImageData) {\n console.log(`Source Image: ${sourceImage}`);\n }\n console.log(`Output: ${outputPath}`);\n console.log(`Provider: ${aiProvider}`);\n if (model) {\n console.log(`Model: ${model}`);\n }\n console.log(\"─────────────────────────────────────\\n\");\n }\n } catch (error) {\n if (spinner && spinner.isSpinning) {\n spinner.fail(\"Image generation failed\");\n }\n throw error;\n }\n }\n\n /**\n * Generate image with smart fallback chain\n * Tries primary model first, then falls back to alternatives if it fails\n */\n private async generateWithFallback(\n prompt: string,\n size: string,\n apiKey: string,\n provider: \"openrouter\" | \"openai\" | \"google\",\n config: { primary: string | null; fallbacks: string[] },\n spinner: {\n text: string;\n info: (msg: string) => void;\n warn: (msg: string) => void;\n isSpinning?: boolean;\n } | null,\n sourceImage?: string\n ): Promise<Buffer> {\n const modelsToTry = [config.primary, ...config.fallbacks].filter(\n (model): model is string => model !== null\n );\n\n let lastError: Error | null = null;\n\n for (let i = 0; i < modelsToTry.length; i++) {\n const model = modelsToTry[i];\n\n try {\n if (spinner) {\n const modelName = model.split(\"/\").pop() || model;\n spinner.text = `Generating image with ${modelName}... (attempt ${i + 1}/${modelsToTry.length})`;\n }\n\n const imageBuffer = await this.generateWithModel(\n prompt,\n size,\n apiKey,\n provider,\n model,\n sourceImage\n );\n\n if (spinner && i > 0) {\n // Only show fallback message if we had to fall back\n spinner.info(`Successfully generated with fallback model: ${model}`);\n }\n\n return imageBuffer;\n } catch (error) {\n lastError = error as Error;\n\n // If this wasn't the last model, log the failure and try the next one\n if (i < modelsToTry.length - 1) {\n if (spinner) {\n spinner.warn(`Model ${model} failed, trying fallback...`);\n } else {\n console.warn(`Model ${model} failed: ${lastError.message}`);\n }\n continue;\n }\n }\n }\n\n // If we get here, all models failed\n throw new Error(\n `All models failed to generate image. Last error: ${lastError?.message || \"Unknown error\"}`\n );\n }\n\n /**\n * get the model configuration for the AI SDK\n */\n private getImageModel(provider: string, apiKey: string, model: string) {\n if (provider === \"openai\") {\n const openai = createOpenAI({ apiKey });\n return openai.image(model);\n } else if (provider === \"google\") {\n const google = createGoogleGenerativeAI({ apiKey });\n return google.image(model);\n }\n throw new Error(`Provider ${provider} not supported via AI SDK`);\n }\n\n /**\n * Generate image using a specific model\n */\n private async generateWithModel(\n prompt: string,\n size: string,\n apiKey: string,\n provider: \"openrouter\" | \"openai\" | \"google\",\n model: string,\n sourceImage?: string\n ): Promise<Buffer> {\n // ─────────────────────────────────────────────────────────────────────\n // OPENROUTER (Via Fetch / Chat Completions)\n // ─────────────────────────────────────────────────────────────────────\n if (provider === \"openrouter\") {\n // Parse aspect ratio from size\n const [width, height] = size.split(\"x\").map(Number);\n let aspectRatio = \"1:1\";\n if (width && height) {\n const gcd = (a: number, b: number): number => (b === 0 ? a : gcd(b, a % b));\n const divisor = gcd(width, height);\n aspectRatio = `${width / divisor}:${height / divisor}`;\n }\n\n // Build content array based on whether we have a source image\n let content: any;\n if (sourceImage) {\n // Image-to-image generation: include source image in content\n content = [\n {\n type: \"text\",\n text: prompt,\n },\n {\n type: \"image_url\",\n image_url: {\n url: sourceImage,\n },\n },\n ];\n } else {\n // Text-to-image generation: just the prompt\n content = prompt;\n }\n\n const response = await fetch(\"https://openrouter.ai/api/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n \"HTTP-Referer\": \"https://clawbr.bricks-studio.ai\",\n \"X-Title\": \"clawbr CLI\",\n },\n body: JSON.stringify({\n model: model,\n messages: [\n {\n role: \"user\",\n content: content,\n },\n ],\n // Specific to Gemini/OpenRouter multimodal\n modalities: [\"image\", \"text\"],\n image_config: {\n aspect_ratio: aspectRatio,\n // image_size: \"4K\", // Optional based on snippet, but maybe risky for all models\n },\n }),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`OpenRouter API error: ${text}`);\n }\n\n const result = (await response.json()) as any;\n\n if (result.choices?.[0]?.message?.images?.[0]?.image_url?.url) {\n const imageUrl = result.choices[0].message.images[0].image_url.url;\n\n // If it's a URL, fetch it\n if (imageUrl.startsWith(\"http\")) {\n const imgRes = await fetch(imageUrl);\n const arrayBuffer = await imgRes.arrayBuffer();\n return Buffer.from(arrayBuffer);\n }\n\n // If it's base64 data URI\n if (imageUrl.startsWith(\"data:image\")) {\n const base64Data = imageUrl.split(\",\")[1];\n return Buffer.from(base64Data, \"base64\");\n }\n\n throw new Error(\"Unknown image URL format\");\n }\n\n throw new Error(\"No image generated from OpenRouter response\");\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // OPENAI / GOOGLE (Via AI SDK)\n // ─────────────────────────────────────────────────────────────────────\n const imageModel = this.getImageModel(provider, apiKey, model);\n\n // Pass size as string directly as per SDK requirements.\n // We cast to 'any' to avoid strict template literal validation errors\n // since we know validSizes allows specifically \"1024x1024\" etc.\n const { image } = await generateImage({\n model: imageModel,\n prompt,\n n: 1,\n size: size as any,\n });\n\n // The image object from 'ai' SDK contains the base64 string\n return Buffer.from(image.base64, \"base64\");\n }\n\n @Option({\n flags: \"-p, --prompt <text>\",\n description: \"Text description of the image to generate\",\n })\n parsePrompt(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-o, --output <path>\",\n description: \"Path where the generated image will be saved\",\n })\n parseOutput(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-s, --size <size>\",\n description: \"Image size (256x256, 512x512, 1024x1024, 1792x1024, 1024x1792)\",\n })\n parseSize(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"--source-image <path>\",\n description: \"Path to source image or URL (for image-to-image generation, OpenRouter only)\",\n })\n parseSourceImage(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-m, --model <modelId>\",\n description:\n \"Specific model to use (provider-dependent). Use model ID from your provider's list. Note: Not all models support reference images (--source-image).\",\n })\n parseModel(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"--json\",\n description: \"Output result in JSON format\",\n })\n parseJson(): boolean {\n return true;\n }\n}\n"],"names":["Command","CommandRunner","Option","writeFileSync","ora","fetch","resolve","generateImage","createOpenAI","createGoogleGenerativeAI","loadCredentials","encodeImageToDataUri","validateImageInput","requireOnboarding","getProviderModels","getModelById","isValidModel","getPrimaryModel","getFallbackModels","supportsReferenceImage","formatModelList","GenerateCommand","run","inputs","options","prompt","output","size","sourceImage","model","json","Error","validation","valid","error","validSizes","includes","join","credentials","aiProvider","apiKeys","apiKey","availableModels","modelInfo","name","filter","m","map","id","sourceImageData","undefined","spinner","start","imageBuffer","primaryModel","fallbackModels","generateWithFallback","primary","fallbacks","fail","outputPath","succeed","console","log","JSON","stringify","success","provider","isSpinning","config","modelsToTry","lastError","i","length","modelName","split","pop","text","generateWithModel","info","warn","message","getImageModel","openai","image","google","width","height","Number","aspectRatio","gcd","a","b","divisor","content","type","image_url","url","response","method","headers","Authorization","body","messages","role","modalities","image_config","aspect_ratio","ok","result","choices","images","imageUrl","startsWith","imgRes","arrayBuffer","Buffer","from","base64Data","imageModel","n","base64","parsePrompt","val","parseOutput","parseSize","parseSourceImage","parseModel","parseJson","flags","description","arguments","isDefault"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/commands/generate.command.ts"],"sourcesContent":["import { Command, CommandRunner, Option } from \"nest-commander\";\nimport { writeFileSync } from \"fs\";\nimport ora from \"ora\";\nimport fetch from \"node-fetch\";\nimport { resolve } from \"path\";\nimport { generateImage } from \"ai\";\nimport { createOpenAI } from \"@ai-sdk/openai\";\nimport { createGoogleGenerativeAI } from \"@ai-sdk/google\";\nimport { loadCredentials } from \"../utils/credentials.js\";\nimport { resolveImageToDataUri, validateImageInput } from \"../utils/image.js\";\nimport { requireOnboarding } from \"../utils/config.js\";\nimport {\n getProviderModels,\n getModelById,\n isValidModel,\n getPrimaryModel,\n getFallbackModels,\n supportsReferenceImage,\n formatModelList,\n} from \"../config/image-models.js\";\n\ninterface GenerateCommandOptions {\n prompt?: string;\n output?: string;\n size?: string;\n sourceImage?: string;\n model?: string;\n json?: boolean;\n}\n\n@Command({\n name: \"generate\",\n description: \"Generate an image using AI with smart model fallback\",\n arguments: \"\",\n options: { isDefault: false },\n})\nexport class GenerateCommand extends CommandRunner {\n async run(inputs: string[], options: GenerateCommandOptions): Promise<void> {\n await requireOnboarding();\n const { prompt, output, size = \"1024x1024\", sourceImage, model, json = false } = options;\n\n // ─────────────────────────────────────────────────────────────────────\n // Validation\n // ─────────────────────────────────────────────────────────────────────\n if (!prompt) {\n throw new Error(\n '--prompt is required. Example: clawbr generate --prompt \"a robot building software\" --output \"./robot.png\"'\n );\n }\n\n if (!output) {\n throw new Error(\n '--output is required. Example: clawbr generate --prompt \"...\" --output \"./image.png\"'\n );\n }\n\n // Validate source image if provided\n if (sourceImage) {\n const validation = validateImageInput(sourceImage);\n if (!validation.valid) {\n throw new Error(validation.error);\n }\n }\n\n // Validate size\n const validSizes = [\"256x256\", \"512x512\", \"1024x1024\", \"1792x1024\", \"1024x1792\"];\n if (!validSizes.includes(size)) {\n throw new Error(`Invalid size. Must be one of: ${validSizes.join(\", \")}`);\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Load Credentials\n // ─────────────────────────────────────────────────────────────────────\n const credentials = loadCredentials();\n\n if (!credentials) {\n throw new Error(\"Credentials not found. Run 'clawbr onboard' first to set up your account.\");\n }\n\n const { aiProvider, apiKeys } = credentials;\n const apiKey = apiKeys[aiProvider as keyof typeof apiKeys];\n\n if (!apiKey) {\n throw new Error(\n `No API key found for provider '${aiProvider}'. Run 'clawbr onboard' to configure.`\n );\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Validate model if provided\n // ─────────────────────────────────────────────────────────────────────\n if (model && !isValidModel(aiProvider, model)) {\n const availableModels = formatModelList(aiProvider);\n throw new Error(\n `Invalid model '${model}' for provider '${aiProvider}'.\\n\\nAvailable models:\\n${availableModels}`\n );\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Check reference image support\n // ─────────────────────────────────────────────────────────────────────\n if (sourceImage && model && !supportsReferenceImage(aiProvider, model)) {\n const modelInfo = getModelById(aiProvider, model);\n throw new Error(\n `Model '${modelInfo?.name || model}' does not support reference images.\\n\\n` +\n `For reference image support with ${aiProvider}, use one of:\\n` +\n getProviderModels(aiProvider)\n .filter((m) => m.supportsReferenceImage)\n .map((m) => ` • ${m.id}`)\n .join(\"\\n\")\n );\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Prepare source image if provided\n // ─────────────────────────────────────────────────────────────────────\n const sourceImageData = sourceImage ? await resolveImageToDataUri(sourceImage) : undefined;\n\n // ─────────────────────────────────────────────────────────────────────\n // Generate Image with Smart Fallback\n // ─────────────────────────────────────────────────────────────────────\n const spinner = json\n ? null\n : ora(sourceImageData ? \"Generating image from source...\" : \"Generating image...\").start();\n\n try {\n let imageBuffer: Buffer;\n\n // Determine models to use\n const primaryModel = model || getPrimaryModel(aiProvider);\n const fallbackModels = model ? [] : getFallbackModels(aiProvider);\n\n if (aiProvider === \"openrouter\") {\n imageBuffer = await this.generateWithFallback(\n prompt,\n size,\n apiKey,\n \"openrouter\",\n { primary: primaryModel, fallbacks: fallbackModels },\n spinner,\n sourceImageData\n );\n } else if (aiProvider === \"openai\") {\n if (sourceImageData) {\n throw new Error(\n \"OpenAI does not support image-to-image generation. Use OpenRouter with a model that supports reference images.\"\n );\n }\n imageBuffer = await this.generateWithFallback(\n prompt,\n size,\n apiKey,\n \"openai\",\n { primary: primaryModel, fallbacks: fallbackModels },\n spinner\n );\n } else if (aiProvider === \"google\") {\n if (sourceImageData) {\n throw new Error(\n \"Google Imagen does not support image-to-image generation. Use OpenRouter with a model that supports reference images.\"\n );\n }\n imageBuffer = await this.generateWithFallback(\n prompt,\n size,\n apiKey,\n \"google\",\n { primary: primaryModel, fallbacks: fallbackModels },\n spinner\n );\n } else {\n if (spinner) spinner.fail();\n throw new Error(`Unsupported AI provider: ${aiProvider}`);\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Save Image\n // ─────────────────────────────────────────────────────────────────────\n const outputPath = resolve(output);\n writeFileSync(outputPath, imageBuffer);\n\n if (spinner) {\n spinner.succeed(`Image generated and saved to: ${outputPath}`);\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Output\n // ─────────────────────────────────────────────────────────────────────\n if (json) {\n console.log(\n JSON.stringify(\n {\n success: true,\n prompt,\n output: outputPath,\n size,\n provider: aiProvider,\n },\n null,\n 2\n )\n );\n } else {\n console.log(\"\\n🎨 Image Generation Complete!\");\n console.log(\"─────────────────────────────────────\");\n console.log(`Prompt: ${prompt}`);\n console.log(`Size: ${size}`);\n if (sourceImageData) {\n console.log(`Source Image: ${sourceImage}`);\n }\n console.log(`Output: ${outputPath}`);\n console.log(`Provider: ${aiProvider}`);\n if (model) {\n console.log(`Model: ${model}`);\n }\n console.log(\"─────────────────────────────────────\\n\");\n }\n } catch (error) {\n if (spinner && spinner.isSpinning) {\n spinner.fail(\"Image generation failed\");\n }\n throw error;\n }\n }\n\n /**\n * Generate image with smart fallback chain\n * Tries primary model first, then falls back to alternatives if it fails\n */\n private async generateWithFallback(\n prompt: string,\n size: string,\n apiKey: string,\n provider: \"openrouter\" | \"openai\" | \"google\",\n config: { primary: string | null; fallbacks: string[] },\n spinner: {\n text: string;\n info: (msg: string) => void;\n warn: (msg: string) => void;\n isSpinning?: boolean;\n } | null,\n sourceImage?: string\n ): Promise<Buffer> {\n const modelsToTry = [config.primary, ...config.fallbacks].filter(\n (model): model is string => model !== null\n );\n\n let lastError: Error | null = null;\n\n for (let i = 0; i < modelsToTry.length; i++) {\n const model = modelsToTry[i];\n\n try {\n if (spinner) {\n const modelName = model.split(\"/\").pop() || model;\n spinner.text = `Generating image with ${modelName}... (attempt ${i + 1}/${modelsToTry.length})`;\n }\n\n const imageBuffer = await this.generateWithModel(\n prompt,\n size,\n apiKey,\n provider,\n model,\n sourceImage\n );\n\n if (spinner && i > 0) {\n // Only show fallback message if we had to fall back\n spinner.info(`Successfully generated with fallback model: ${model}`);\n }\n\n return imageBuffer;\n } catch (error) {\n lastError = error as Error;\n\n // If this wasn't the last model, log the failure and try the next one\n if (i < modelsToTry.length - 1) {\n if (spinner) {\n spinner.warn(`Model ${model} failed, trying fallback...`);\n } else {\n console.warn(`Model ${model} failed: ${lastError.message}`);\n }\n continue;\n }\n }\n }\n\n // If we get here, all models failed\n throw new Error(\n `All models failed to generate image. Last error: ${lastError?.message || \"Unknown error\"}`\n );\n }\n\n /**\n * get the model configuration for the AI SDK\n */\n private getImageModel(provider: string, apiKey: string, model: string) {\n if (provider === \"openai\") {\n const openai = createOpenAI({ apiKey });\n return openai.image(model);\n } else if (provider === \"google\") {\n const google = createGoogleGenerativeAI({ apiKey });\n return google.image(model);\n }\n throw new Error(`Provider ${provider} not supported via AI SDK`);\n }\n\n /**\n * Generate image using a specific model\n */\n private async generateWithModel(\n prompt: string,\n size: string,\n apiKey: string,\n provider: \"openrouter\" | \"openai\" | \"google\",\n model: string,\n sourceImage?: string\n ): Promise<Buffer> {\n // ─────────────────────────────────────────────────────────────────────\n // OPENROUTER (Via Fetch / Chat Completions)\n // ─────────────────────────────────────────────────────────────────────\n if (provider === \"openrouter\") {\n // Parse aspect ratio from size\n const [width, height] = size.split(\"x\").map(Number);\n let aspectRatio = \"1:1\";\n if (width && height) {\n const gcd = (a: number, b: number): number => (b === 0 ? a : gcd(b, a % b));\n const divisor = gcd(width, height);\n aspectRatio = `${width / divisor}:${height / divisor}`;\n }\n\n // Build content array based on whether we have a source image\n let content: any;\n if (sourceImage) {\n // Image-to-image generation: include source image in content\n content = [\n {\n type: \"text\",\n text: prompt,\n },\n {\n type: \"image_url\",\n image_url: {\n url: sourceImage,\n },\n },\n ];\n } else {\n // Text-to-image generation: just the prompt\n content = prompt;\n }\n\n const response = await fetch(\"https://openrouter.ai/api/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n \"HTTP-Referer\": \"https://clawbr.bricks-studio.ai\",\n \"X-Title\": \"clawbr CLI\",\n },\n body: JSON.stringify({\n model: model,\n messages: [\n {\n role: \"user\",\n content: content,\n },\n ],\n // Specific to Gemini/OpenRouter multimodal\n modalities: [\"image\", \"text\"],\n image_config: {\n aspect_ratio: aspectRatio,\n // image_size: \"4K\", // Optional based on snippet, but maybe risky for all models\n },\n }),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`OpenRouter API error: ${text}`);\n }\n\n const result = (await response.json()) as any;\n\n if (result.choices?.[0]?.message?.images?.[0]?.image_url?.url) {\n const imageUrl = result.choices[0].message.images[0].image_url.url;\n\n // If it's a URL, fetch it\n if (imageUrl.startsWith(\"http\")) {\n const imgRes = await fetch(imageUrl);\n const arrayBuffer = await imgRes.arrayBuffer();\n return Buffer.from(arrayBuffer);\n }\n\n // If it's base64 data URI\n if (imageUrl.startsWith(\"data:image\")) {\n const base64Data = imageUrl.split(\",\")[1];\n return Buffer.from(base64Data, \"base64\");\n }\n\n throw new Error(\"Unknown image URL format\");\n }\n\n throw new Error(\"No image generated from OpenRouter response\");\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // OPENAI / GOOGLE (Via AI SDK)\n // ─────────────────────────────────────────────────────────────────────\n const imageModel = this.getImageModel(provider, apiKey, model);\n\n // Pass size as string directly as per SDK requirements.\n // We cast to 'any' to avoid strict template literal validation errors\n // since we know validSizes allows specifically \"1024x1024\" etc.\n const { image } = await generateImage({\n model: imageModel,\n prompt,\n n: 1,\n size: size as any,\n });\n\n // The image object from 'ai' SDK contains the base64 string\n return Buffer.from(image.base64, \"base64\");\n }\n\n @Option({\n flags: \"-p, --prompt <text>\",\n description: \"Text description of the image to generate\",\n })\n parsePrompt(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-o, --output <path>\",\n description: \"Path where the generated image will be saved\",\n })\n parseOutput(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-s, --size <size>\",\n description: \"Image size (256x256, 512x512, 1024x1024, 1792x1024, 1024x1792)\",\n })\n parseSize(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"--source-image <path>\",\n description: \"Path to source image or URL (for image-to-image generation, OpenRouter only)\",\n })\n parseSourceImage(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-m, --model <modelId>\",\n description:\n \"Specific model to use (provider-dependent). Use model ID from your provider's list. Note: Not all models support reference images (--source-image).\",\n })\n parseModel(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"--json\",\n description: \"Output result in JSON format\",\n })\n parseJson(): boolean {\n return true;\n }\n}\n"],"names":["Command","CommandRunner","Option","writeFileSync","ora","fetch","resolve","generateImage","createOpenAI","createGoogleGenerativeAI","loadCredentials","resolveImageToDataUri","validateImageInput","requireOnboarding","getProviderModels","getModelById","isValidModel","getPrimaryModel","getFallbackModels","supportsReferenceImage","formatModelList","GenerateCommand","run","inputs","options","prompt","output","size","sourceImage","model","json","Error","validation","valid","error","validSizes","includes","join","credentials","aiProvider","apiKeys","apiKey","availableModels","modelInfo","name","filter","m","map","id","sourceImageData","undefined","spinner","start","imageBuffer","primaryModel","fallbackModels","generateWithFallback","primary","fallbacks","fail","outputPath","succeed","console","log","JSON","stringify","success","provider","isSpinning","config","modelsToTry","lastError","i","length","modelName","split","pop","text","generateWithModel","info","warn","message","getImageModel","openai","image","google","width","height","Number","aspectRatio","gcd","a","b","divisor","content","type","image_url","url","response","method","headers","Authorization","body","messages","role","modalities","image_config","aspect_ratio","ok","result","choices","images","imageUrl","startsWith","imgRes","arrayBuffer","Buffer","from","base64Data","imageModel","n","base64","parsePrompt","val","parseOutput","parseSize","parseSourceImage","parseModel","parseJson","flags","description","arguments","isDefault"],"mappings":"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"}
@@ -8,8 +8,8 @@ function _ts_metadata(k, v) {
8
8
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
9
  }
10
10
  import { Command, CommandRunner, Option } from "nest-commander";
11
- import { existsSync } from "fs";
12
11
  import { createReadStream } from "fs";
12
+ import { validateImageInput, isUrl } from "../utils/image.js";
13
13
  import inquirer from "inquirer";
14
14
  import ora from "ora";
15
15
  import chalk from "chalk";
@@ -40,8 +40,9 @@ export class PostCommand extends CommandRunner {
40
40
  if (!input) {
41
41
  return true; // Allow empty for text-only posts
42
42
  }
43
- if (!existsSync(input)) {
44
- return `File not found: ${input}`;
43
+ const validation = validateImageInput(input);
44
+ if (!validation.valid) {
45
+ return validation.error || "Invalid image input";
45
46
  }
46
47
  return true;
47
48
  }
@@ -68,8 +69,11 @@ export class PostCommand extends CommandRunner {
68
69
  if (!filePath && !caption) {
69
70
  throw new Error("At least one of --image or --caption is required.\n" + "Usage: clawbr post --image <path> --caption <text>\n" + " clawbr post --caption <text>");
70
71
  }
71
- if (filePath && !existsSync(filePath)) {
72
- throw new Error(`File not found: ${filePath}`);
72
+ if (filePath) {
73
+ const validation = validateImageInput(filePath);
74
+ if (!validation.valid) {
75
+ throw new Error(validation.error);
76
+ }
73
77
  }
74
78
  }
75
79
  // ─────────────────────────────────────────────────────────────────────
@@ -94,9 +98,31 @@ export class PostCommand extends CommandRunner {
94
98
  // Create FormData
95
99
  const formData = new FormData();
96
100
  if (filePath) {
97
- // Read file from disk
98
- const fileStream = createReadStream(filePath);
99
- formData.append("file", fileStream);
101
+ if (isUrl(filePath)) {
102
+ // Fetch from URL
103
+ const imageResponse = await fetch(filePath);
104
+ if (!imageResponse.ok) {
105
+ throw new Error(`Failed to fetch image from URL: ${imageResponse.statusText}`);
106
+ }
107
+ const contentType = imageResponse.headers.get("content-type") || "image/jpeg";
108
+ const buffer = Buffer.from(await imageResponse.arrayBuffer());
109
+ // Determine extension from content-type
110
+ let extension = "jpg";
111
+ if (contentType.includes("png")) extension = "png";
112
+ else if (contentType.includes("webp")) extension = "webp";
113
+ else if (contentType.includes("gif")) extension = "gif";
114
+ // Use a generic filename with correct extension
115
+ // We can't easily rely on the URL path for redirected URLs like picsum.photos
116
+ const filename = `image.${extension}`;
117
+ formData.append("file", buffer, {
118
+ filename,
119
+ contentType
120
+ });
121
+ } else {
122
+ // Read file from disk
123
+ const fileStream = createReadStream(filePath);
124
+ formData.append("file", fileStream);
125
+ }
100
126
  }
101
127
  if (caption) {
102
128
  formData.append("caption", caption);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/commands/post.command.ts"],"sourcesContent":["import { Command, CommandRunner, Option } from \"nest-commander\";\nimport { existsSync } from \"fs\";\nimport { createReadStream } from \"fs\";\nimport inquirer from \"inquirer\";\nimport ora from \"ora\";\nimport chalk from \"chalk\";\nimport FormData from \"form-data\";\nimport fetch from \"node-fetch\";\nimport { getApiToken, getApiUrl, loadCredentials } from \"../utils/credentials.js\";\nimport { requireOnboarding } from \"../utils/config.js\";\n\ninterface PostCommandOptions {\n file?: string;\n image?: string;\n caption?: string;\n json?: boolean;\n}\n\ninterface ApiResponse {\n success: boolean;\n post: {\n id: string;\n caption: string;\n imageUrl: string;\n visualSnapshot: string;\n createdAt: string;\n agent: {\n username: string;\n };\n };\n}\n\n@Command({\n name: \"post\",\n description: \"Create a new post with image and caption\",\n arguments: \"\",\n options: { isDefault: false },\n})\nexport class PostCommand extends CommandRunner {\n async run(inputs: string[], options: PostCommandOptions): Promise<void> {\n // Require onboarding before posting\n await requireOnboarding();\n\n // ─────────────────────────────────────────────────────────────────────\n // Detect TTY - Determine if running interactively\n // ─────────────────────────────────────────────────────────────────────\n const isInteractive = process.stdout.isTTY && !options.image && !options.caption;\n\n let filePath: string | undefined;\n let caption: string;\n\n // ─────────────────────────────────────────────────────────────────────\n // INTERACTIVE MODE - Use inquirer prompts\n // ─────────────────────────────────────────────────────────────────────\n if (isInteractive) {\n const answers = await inquirer.prompt([\n {\n type: \"input\",\n name: \"filePath\",\n message: \"Enter the path to your image file (or press Enter to skip):\",\n validate: (input: string) => {\n if (!input) {\n return true; // Allow empty for text-only posts\n }\n if (!existsSync(input)) {\n return `File not found: ${input}`;\n }\n return true;\n },\n },\n {\n type: \"input\",\n name: \"caption\",\n message: \"Enter a caption for your post:\",\n validate: (input: string) => {\n if (!input || input.trim().length === 0) {\n return \"Caption is required\";\n }\n return true;\n },\n },\n ]);\n\n filePath = answers.filePath || undefined;\n caption = answers.caption;\n }\n // ─────────────────────────────────────────────────────────────────────\n // NON-INTERACTIVE MODE - Use command-line flags\n // ─────────────────────────────────────────────────────────────────────\n else {\n // Support both --file and --image flags\n filePath = options.image || options.file;\n caption = options.caption || \"\";\n\n // At least one of image or caption is required\n if (!filePath && !caption) {\n throw new Error(\n \"At least one of --image or --caption is required.\\n\" +\n \"Usage: clawbr post --image <path> --caption <text>\\n\" +\n \" clawbr post --caption <text>\"\n );\n }\n\n if (filePath && !existsSync(filePath)) {\n throw new Error(`File not found: ${filePath}`);\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Get credentials from config or environment\n // ─────────────────────────────────────────────────────────────────────\n const agentToken = getApiToken();\n const apiUrl = getApiUrl();\n\n if (!agentToken) {\n throw new Error(\n \"Authentication required. Please run 'clawbr onboard' first.\\n\" +\n \"Or set CLAWBR_TOKEN environment variable.\"\n );\n }\n\n // Get provider key if available\n const credentials = loadCredentials();\n let providerKey = \"\";\n if (credentials && credentials.apiKeys && credentials.aiProvider) {\n providerKey = credentials.apiKeys[credentials.aiProvider] || \"\";\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Processing - Upload post with spinner\n // ─────────────────────────────────────────────────────────────────────\n const spinner = options.json ? null : ora(\"Processing your post...\").start();\n\n try {\n // Create FormData\n const formData = new FormData();\n\n if (filePath) {\n // Read file from disk\n const fileStream = createReadStream(filePath);\n formData.append(\"file\", fileStream);\n }\n\n if (caption) {\n formData.append(\"caption\", caption);\n }\n\n // Make API request\n const headers: Record<string, string> = {\n \"X-Agent-Token\": agentToken,\n };\n\n if (providerKey) {\n headers[\"X-Provider-Key\"] = providerKey;\n }\n\n const response = await fetch(`${apiUrl}/api/posts/create`, {\n method: \"POST\",\n headers,\n body: formData,\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n let errorMessage: string;\n let isVerificationError = false;\n\n try {\n const errorJson = JSON.parse(errorText);\n errorMessage = errorJson.error || errorJson.message || \"Unknown error\";\n if (\n response.status === 403 &&\n (errorMessage.includes(\"Verification\") || errorJson.error === \"Verification Required\")\n ) {\n isVerificationError = true;\n errorMessage = errorJson.message || errorMessage;\n }\n } catch {\n errorMessage = errorText || `HTTP ${response.status} ${response.statusText}`;\n }\n\n if (spinner) {\n spinner.fail(`Failed to create post: ${errorMessage}`);\n }\n\n if (isVerificationError) {\n console.log(chalk.yellow(\"\\n⚠️ Account Verification Required\"));\n console.log(\n chalk.gray(\"To prevent spam, all agents must verify their X (Twitter) account.\")\n );\n console.log(chalk.cyan(\"\\nRun the following command to verify:\"));\n console.log(chalk.bold.green(\" clawbr verify\\n\"));\n }\n\n throw new Error(errorMessage);\n }\n\n const result = (await response.json()) as ApiResponse;\n\n if (spinner) {\n spinner.succeed(\"Post created successfully!\");\n }\n\n // Display result\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n } else {\n console.log(\"\\n📸 Post Details:\");\n console.log(\"─────────────────────────────────────\");\n console.log(`ID: ${result.post.id}`);\n console.log(`Caption: ${result.post.caption || \"(no caption)\"}`);\n console.log(`Image URL: ${result.post.imageUrl || \"(no image)\"}`);\n console.log(`Visual Snapshot: ${result.post.visualSnapshot || \"(none)\"}`);\n console.log(`Agent: ${result.post.agent.username}`);\n console.log(`Created: ${new Date(result.post.createdAt).toLocaleString()}`);\n console.log(\"─────────────────────────────────────\\n\");\n }\n } catch (error) {\n if (spinner && spinner.isSpinning) {\n spinner.fail(\"Failed to create post\");\n }\n throw error;\n }\n }\n\n @Option({\n flags: \"-f, --file <path>\",\n description: \"Path to the image file (deprecated, use --image)\",\n })\n parseFile(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-i, --image <path>\",\n description: \"Path to the image file or URL\",\n })\n parseImage(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-c, --caption <text>\",\n description: \"Caption for the post\",\n })\n parseCaption(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"--json\",\n description: \"Output in JSON format\",\n })\n parseJson(): boolean {\n return true;\n }\n}\n"],"names":["Command","CommandRunner","Option","existsSync","createReadStream","inquirer","ora","chalk","FormData","fetch","getApiToken","getApiUrl","loadCredentials","requireOnboarding","PostCommand","run","inputs","options","isInteractive","process","stdout","isTTY","image","caption","filePath","answers","prompt","type","name","message","validate","input","trim","length","undefined","file","Error","agentToken","apiUrl","credentials","providerKey","apiKeys","aiProvider","spinner","json","start","formData","fileStream","append","headers","response","method","body","ok","errorText","text","errorMessage","isVerificationError","errorJson","JSON","parse","error","status","includes","statusText","fail","console","log","yellow","gray","cyan","bold","green","result","succeed","stringify","post","id","imageUrl","visualSnapshot","agent","username","Date","createdAt","toLocaleString","isSpinning","parseFile","val","parseImage","parseCaption","parseJson","flags","description","arguments","isDefault"],"mappings":";;;;;;;;;AAAA,SAASA,OAAO,EAAEC,aAAa,EAAEC,MAAM,QAAQ,iBAAiB;AAChE,SAASC,UAAU,QAAQ,KAAK;AAChC,SAASC,gBAAgB,QAAQ,KAAK;AACtC,OAAOC,cAAc,WAAW;AAChC,OAAOC,SAAS,MAAM;AACtB,OAAOC,WAAW,QAAQ;AAC1B,OAAOC,cAAc,YAAY;AACjC,OAAOC,WAAW,aAAa;AAC/B,SAASC,WAAW,EAAEC,SAAS,EAAEC,eAAe,QAAQ,0BAA0B;AAClF,SAASC,iBAAiB,QAAQ,qBAAqB;AA6BvD,OAAO,MAAMC,oBAAoBb;IAC/B,MAAMc,IAAIC,MAAgB,EAAEC,OAA2B,EAAiB;QACtE,oCAAoC;QACpC,MAAMJ;QAEN,wEAAwE;QACxE,kDAAkD;QAClD,wEAAwE;QACxE,MAAMK,gBAAgBC,QAAQC,MAAM,CAACC,KAAK,IAAI,CAACJ,QAAQK,KAAK,IAAI,CAACL,QAAQM,OAAO;QAEhF,IAAIC;QACJ,IAAID;QAEJ,wEAAwE;QACxE,0CAA0C;QAC1C,wEAAwE;QACxE,IAAIL,eAAe;YACjB,MAAMO,UAAU,MAAMpB,SAASqB,MAAM,CAAC;gBACpC;oBACEC,MAAM;oBACNC,MAAM;oBACNC,SAAS;oBACTC,UAAU,CAACC;wBACT,IAAI,CAACA,OAAO;4BACV,OAAO,MAAM,kCAAkC;wBACjD;wBACA,IAAI,CAAC5B,WAAW4B,QAAQ;4BACtB,OAAO,CAAC,gBAAgB,EAAEA,OAAO;wBACnC;wBACA,OAAO;oBACT;gBACF;gBACA;oBACEJ,MAAM;oBACNC,MAAM;oBACNC,SAAS;oBACTC,UAAU,CAACC;wBACT,IAAI,CAACA,SAASA,MAAMC,IAAI,GAAGC,MAAM,KAAK,GAAG;4BACvC,OAAO;wBACT;wBACA,OAAO;oBACT;gBACF;aACD;YAEDT,WAAWC,QAAQD,QAAQ,IAAIU;YAC/BX,UAAUE,QAAQF,OAAO;QAC3B,OAIK;YACH,wCAAwC;YACxCC,WAAWP,QAAQK,KAAK,IAAIL,QAAQkB,IAAI;YACxCZ,UAAUN,QAAQM,OAAO,IAAI;YAE7B,+CAA+C;YAC/C,IAAI,CAACC,YAAY,CAACD,SAAS;gBACzB,MAAM,IAAIa,MACR,wDACE,yDACA;YAEN;YAEA,IAAIZ,YAAY,CAACrB,WAAWqB,WAAW;gBACrC,MAAM,IAAIY,MAAM,CAAC,gBAAgB,EAAEZ,UAAU;YAC/C;QACF;QAEA,wEAAwE;QACxE,6CAA6C;QAC7C,wEAAwE;QACxE,MAAMa,aAAa3B;QACnB,MAAM4B,SAAS3B;QAEf,IAAI,CAAC0B,YAAY;YACf,MAAM,IAAID,MACR,kEACE;QAEN;QAEA,gCAAgC;QAChC,MAAMG,cAAc3B;QACpB,IAAI4B,cAAc;QAClB,IAAID,eAAeA,YAAYE,OAAO,IAAIF,YAAYG,UAAU,EAAE;YAChEF,cAAcD,YAAYE,OAAO,CAACF,YAAYG,UAAU,CAAC,IAAI;QAC/D;QAEA,wEAAwE;QACxE,wCAAwC;QACxC,wEAAwE;QACxE,MAAMC,UAAU1B,QAAQ2B,IAAI,GAAG,OAAOtC,IAAI,2BAA2BuC,KAAK;QAE1E,IAAI;YACF,kBAAkB;YAClB,MAAMC,WAAW,IAAItC;YAErB,IAAIgB,UAAU;gBACZ,sBAAsB;gBACtB,MAAMuB,aAAa3C,iBAAiBoB;gBACpCsB,SAASE,MAAM,CAAC,QAAQD;YAC1B;YAEA,IAAIxB,SAAS;gBACXuB,SAASE,MAAM,CAAC,WAAWzB;YAC7B;YAEA,mBAAmB;YACnB,MAAM0B,UAAkC;gBACtC,iBAAiBZ;YACnB;YAEA,IAAIG,aAAa;gBACfS,OAAO,CAAC,iBAAiB,GAAGT;YAC9B;YAEA,MAAMU,WAAW,MAAMzC,MAAM,GAAG6B,OAAO,iBAAiB,CAAC,EAAE;gBACzDa,QAAQ;gBACRF;gBACAG,MAAMN;YACR;YAEA,IAAI,CAACI,SAASG,EAAE,EAAE;gBAChB,MAAMC,YAAY,MAAMJ,SAASK,IAAI;gBACrC,IAAIC;gBACJ,IAAIC,sBAAsB;gBAE1B,IAAI;oBACF,MAAMC,YAAYC,KAAKC,KAAK,CAACN;oBAC7BE,eAAeE,UAAUG,KAAK,IAAIH,UAAU7B,OAAO,IAAI;oBACvD,IACEqB,SAASY,MAAM,KAAK,OACnBN,CAAAA,aAAaO,QAAQ,CAAC,mBAAmBL,UAAUG,KAAK,KAAK,uBAAsB,GACpF;wBACAJ,sBAAsB;wBACtBD,eAAeE,UAAU7B,OAAO,IAAI2B;oBACtC;gBACF,EAAE,OAAM;oBACNA,eAAeF,aAAa,CAAC,KAAK,EAAEJ,SAASY,MAAM,CAAC,CAAC,EAAEZ,SAASc,UAAU,EAAE;gBAC9E;gBAEA,IAAIrB,SAAS;oBACXA,QAAQsB,IAAI,CAAC,CAAC,uBAAuB,EAAET,cAAc;gBACvD;gBAEA,IAAIC,qBAAqB;oBACvBS,QAAQC,GAAG,CAAC5D,MAAM6D,MAAM,CAAC;oBACzBF,QAAQC,GAAG,CACT5D,MAAM8D,IAAI,CAAC;oBAEbH,QAAQC,GAAG,CAAC5D,MAAM+D,IAAI,CAAC;oBACvBJ,QAAQC,GAAG,CAAC5D,MAAMgE,IAAI,CAACC,KAAK,CAAC;gBAC/B;gBAEA,MAAM,IAAIpC,MAAMoB;YAClB;YAEA,MAAMiB,SAAU,MAAMvB,SAASN,IAAI;YAEnC,IAAID,SAAS;gBACXA,QAAQ+B,OAAO,CAAC;YAClB;YAEA,iBAAiB;YACjB,IAAIzD,QAAQ2B,IAAI,EAAE;gBAChBsB,QAAQC,GAAG,CAACR,KAAKgB,SAAS,CAACF,QAAQ,MAAM;YAC3C,OAAO;gBACLP,QAAQC,GAAG,CAAC;gBACZD,QAAQC,GAAG,CAAC;gBACZD,QAAQC,GAAG,CAAC,CAAC,IAAI,EAAEM,OAAOG,IAAI,CAACC,EAAE,EAAE;gBACnCX,QAAQC,GAAG,CAAC,CAAC,SAAS,EAAEM,OAAOG,IAAI,CAACrD,OAAO,IAAI,gBAAgB;gBAC/D2C,QAAQC,GAAG,CAAC,CAAC,WAAW,EAAEM,OAAOG,IAAI,CAACE,QAAQ,IAAI,cAAc;gBAChEZ,QAAQC,GAAG,CAAC,CAAC,iBAAiB,EAAEM,OAAOG,IAAI,CAACG,cAAc,IAAI,UAAU;gBACxEb,QAAQC,GAAG,CAAC,CAAC,OAAO,EAAEM,OAAOG,IAAI,CAACI,KAAK,CAACC,QAAQ,EAAE;gBAClDf,QAAQC,GAAG,CAAC,CAAC,SAAS,EAAE,IAAIe,KAAKT,OAAOG,IAAI,CAACO,SAAS,EAAEC,cAAc,IAAI;gBAC1ElB,QAAQC,GAAG,CAAC;YACd;QACF,EAAE,OAAON,OAAO;YACd,IAAIlB,WAAWA,QAAQ0C,UAAU,EAAE;gBACjC1C,QAAQsB,IAAI,CAAC;YACf;YACA,MAAMJ;QACR;IACF;IAMAyB,UAAUC,GAAW,EAAU;QAC7B,OAAOA;IACT;IAMAC,WAAWD,GAAW,EAAU;QAC9B,OAAOA;IACT;IAMAE,aAAaF,GAAW,EAAU;QAChC,OAAOA;IACT;IAMAG,YAAqB;QACnB,OAAO;IACT;AACF;;;QA9BIC,OAAO;QACPC,aAAa;;;;;;;;;;QAObD,OAAO;QACPC,aAAa;;;;;;;;;;QAObD,OAAO;QACPC,aAAa;;;;;;;;;;QAObD,OAAO;QACPC,aAAa;;;;;;;;QA1NfhE,MAAM;QACNgE,aAAa;QACbC,WAAW;QACX5E,SAAS;YAAE6E,WAAW;QAAM"}
1
+ {"version":3,"sources":["../../src/commands/post.command.ts"],"sourcesContent":["import { Command, CommandRunner, Option } from \"nest-commander\";\nimport { createReadStream } from \"fs\";\nimport { validateImageInput, isUrl } from \"../utils/image.js\";\nimport inquirer from \"inquirer\";\nimport ora from \"ora\";\nimport chalk from \"chalk\";\nimport FormData from \"form-data\";\nimport fetch from \"node-fetch\";\nimport { getApiToken, getApiUrl, loadCredentials } from \"../utils/credentials.js\";\nimport { requireOnboarding } from \"../utils/config.js\";\n\ninterface PostCommandOptions {\n file?: string;\n image?: string;\n caption?: string;\n json?: boolean;\n}\n\ninterface ApiResponse {\n success: boolean;\n post: {\n id: string;\n caption: string;\n imageUrl: string;\n visualSnapshot: string;\n createdAt: string;\n agent: {\n username: string;\n };\n };\n}\n\n@Command({\n name: \"post\",\n description: \"Create a new post with image and caption\",\n arguments: \"\",\n options: { isDefault: false },\n})\nexport class PostCommand extends CommandRunner {\n async run(inputs: string[], options: PostCommandOptions): Promise<void> {\n // Require onboarding before posting\n await requireOnboarding();\n\n // ─────────────────────────────────────────────────────────────────────\n // Detect TTY - Determine if running interactively\n // ─────────────────────────────────────────────────────────────────────\n const isInteractive = process.stdout.isTTY && !options.image && !options.caption;\n\n let filePath: string | undefined;\n let caption: string;\n\n // ─────────────────────────────────────────────────────────────────────\n // INTERACTIVE MODE - Use inquirer prompts\n // ─────────────────────────────────────────────────────────────────────\n if (isInteractive) {\n const answers = await inquirer.prompt([\n {\n type: \"input\",\n name: \"filePath\",\n message: \"Enter the path to your image file (or press Enter to skip):\",\n validate: (input: string) => {\n if (!input) {\n return true; // Allow empty for text-only posts\n }\n const validation = validateImageInput(input);\n if (!validation.valid) {\n return validation.error || \"Invalid image input\";\n }\n return true;\n },\n },\n {\n type: \"input\",\n name: \"caption\",\n message: \"Enter a caption for your post:\",\n validate: (input: string) => {\n if (!input || input.trim().length === 0) {\n return \"Caption is required\";\n }\n return true;\n },\n },\n ]);\n\n filePath = answers.filePath || undefined;\n caption = answers.caption;\n }\n // ─────────────────────────────────────────────────────────────────────\n // NON-INTERACTIVE MODE - Use command-line flags\n // ─────────────────────────────────────────────────────────────────────\n else {\n // Support both --file and --image flags\n filePath = options.image || options.file;\n caption = options.caption || \"\";\n\n // At least one of image or caption is required\n if (!filePath && !caption) {\n throw new Error(\n \"At least one of --image or --caption is required.\\n\" +\n \"Usage: clawbr post --image <path> --caption <text>\\n\" +\n \" clawbr post --caption <text>\"\n );\n }\n\n if (filePath) {\n const validation = validateImageInput(filePath);\n if (!validation.valid) {\n throw new Error(validation.error);\n }\n }\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Get credentials from config or environment\n // ─────────────────────────────────────────────────────────────────────\n const agentToken = getApiToken();\n const apiUrl = getApiUrl();\n\n if (!agentToken) {\n throw new Error(\n \"Authentication required. Please run 'clawbr onboard' first.\\n\" +\n \"Or set CLAWBR_TOKEN environment variable.\"\n );\n }\n\n // Get provider key if available\n const credentials = loadCredentials();\n let providerKey = \"\";\n if (credentials && credentials.apiKeys && credentials.aiProvider) {\n providerKey = credentials.apiKeys[credentials.aiProvider] || \"\";\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Processing - Upload post with spinner\n // ─────────────────────────────────────────────────────────────────────\n const spinner = options.json ? null : ora(\"Processing your post...\").start();\n\n try {\n // Create FormData\n const formData = new FormData();\n\n if (filePath) {\n if (isUrl(filePath)) {\n // Fetch from URL\n const imageResponse = await fetch(filePath);\n if (!imageResponse.ok) {\n throw new Error(`Failed to fetch image from URL: ${imageResponse.statusText}`);\n }\n\n const contentType = imageResponse.headers.get(\"content-type\") || \"image/jpeg\";\n const buffer = Buffer.from(await imageResponse.arrayBuffer());\n\n // Determine extension from content-type\n let extension = \"jpg\";\n if (contentType.includes(\"png\")) extension = \"png\";\n else if (contentType.includes(\"webp\")) extension = \"webp\";\n else if (contentType.includes(\"gif\")) extension = \"gif\";\n\n // Use a generic filename with correct extension\n // We can't easily rely on the URL path for redirected URLs like picsum.photos\n const filename = `image.${extension}`;\n\n formData.append(\"file\", buffer, { filename, contentType });\n } else {\n // Read file from disk\n const fileStream = createReadStream(filePath);\n formData.append(\"file\", fileStream);\n }\n }\n\n if (caption) {\n formData.append(\"caption\", caption);\n }\n\n // Make API request\n const headers: Record<string, string> = {\n \"X-Agent-Token\": agentToken,\n };\n\n if (providerKey) {\n headers[\"X-Provider-Key\"] = providerKey;\n }\n\n const response = await fetch(`${apiUrl}/api/posts/create`, {\n method: \"POST\",\n headers,\n body: formData,\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n let errorMessage: string;\n let isVerificationError = false;\n\n try {\n const errorJson = JSON.parse(errorText);\n errorMessage = errorJson.error || errorJson.message || \"Unknown error\";\n if (\n response.status === 403 &&\n (errorMessage.includes(\"Verification\") || errorJson.error === \"Verification Required\")\n ) {\n isVerificationError = true;\n errorMessage = errorJson.message || errorMessage;\n }\n } catch {\n errorMessage = errorText || `HTTP ${response.status} ${response.statusText}`;\n }\n\n if (spinner) {\n spinner.fail(`Failed to create post: ${errorMessage}`);\n }\n\n if (isVerificationError) {\n console.log(chalk.yellow(\"\\n⚠️ Account Verification Required\"));\n console.log(\n chalk.gray(\"To prevent spam, all agents must verify their X (Twitter) account.\")\n );\n console.log(chalk.cyan(\"\\nRun the following command to verify:\"));\n console.log(chalk.bold.green(\" clawbr verify\\n\"));\n }\n\n throw new Error(errorMessage);\n }\n\n const result = (await response.json()) as ApiResponse;\n\n if (spinner) {\n spinner.succeed(\"Post created successfully!\");\n }\n\n // Display result\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n } else {\n console.log(\"\\n📸 Post Details:\");\n console.log(\"─────────────────────────────────────\");\n console.log(`ID: ${result.post.id}`);\n console.log(`Caption: ${result.post.caption || \"(no caption)\"}`);\n console.log(`Image URL: ${result.post.imageUrl || \"(no image)\"}`);\n console.log(`Visual Snapshot: ${result.post.visualSnapshot || \"(none)\"}`);\n console.log(`Agent: ${result.post.agent.username}`);\n console.log(`Created: ${new Date(result.post.createdAt).toLocaleString()}`);\n console.log(\"─────────────────────────────────────\\n\");\n }\n } catch (error) {\n if (spinner && spinner.isSpinning) {\n spinner.fail(\"Failed to create post\");\n }\n throw error;\n }\n }\n\n @Option({\n flags: \"-f, --file <path>\",\n description: \"Path to the image file (deprecated, use --image)\",\n })\n parseFile(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-i, --image <path>\",\n description: \"Path to the image file or URL\",\n })\n parseImage(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-c, --caption <text>\",\n description: \"Caption for the post\",\n })\n parseCaption(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"--json\",\n description: \"Output in JSON format\",\n })\n parseJson(): boolean {\n return true;\n }\n}\n"],"names":["Command","CommandRunner","Option","createReadStream","validateImageInput","isUrl","inquirer","ora","chalk","FormData","fetch","getApiToken","getApiUrl","loadCredentials","requireOnboarding","PostCommand","run","inputs","options","isInteractive","process","stdout","isTTY","image","caption","filePath","answers","prompt","type","name","message","validate","input","validation","valid","error","trim","length","undefined","file","Error","agentToken","apiUrl","credentials","providerKey","apiKeys","aiProvider","spinner","json","start","formData","imageResponse","ok","statusText","contentType","headers","get","buffer","Buffer","from","arrayBuffer","extension","includes","filename","append","fileStream","response","method","body","errorText","text","errorMessage","isVerificationError","errorJson","JSON","parse","status","fail","console","log","yellow","gray","cyan","bold","green","result","succeed","stringify","post","id","imageUrl","visualSnapshot","agent","username","Date","createdAt","toLocaleString","isSpinning","parseFile","val","parseImage","parseCaption","parseJson","flags","description","arguments","isDefault"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { readFileSync, existsSync } from "fs";
2
+ import fetch from "node-fetch";
2
3
  /**
3
4
  * Supported image MIME types
4
5
  */ export const SUPPORTED_IMAGE_TYPES = {
@@ -71,6 +72,30 @@ import { readFileSync, existsSync } from "fs";
71
72
  const base64Image = fileBuffer.toString("base64");
72
73
  return `data:${mimeType};base64,${base64Image}`;
73
74
  }
75
+ /**
76
+ * Resolve image to base64 data URI
77
+ * Handles both local files and URLs asynchronously
78
+ */ export async function resolveImageToDataUri(imagePath) {
79
+ // If it's already a data URI, return as-is
80
+ if (isDataUri(imagePath)) {
81
+ return imagePath;
82
+ }
83
+ // If it's a URL, fetch it
84
+ if (isUrl(imagePath)) {
85
+ const response = await fetch(imagePath);
86
+ if (!response.ok) {
87
+ throw new Error(`Failed to fetch image from URL: ${response.statusText}`);
88
+ }
89
+ const arrayBuffer = await response.arrayBuffer();
90
+ const buffer = Buffer.from(arrayBuffer);
91
+ // Determine mime type from header or buffer magic bytes (simplified)
92
+ const contentType = response.headers.get("content-type") || "image/jpeg";
93
+ const base64Image = buffer.toString("base64");
94
+ return `data:${contentType};base64,${base64Image}`;
95
+ }
96
+ // Fallback to local file encoding
97
+ return encodeImageToDataUri(imagePath);
98
+ }
74
99
  /**
75
100
  * Extract base64 data from data URI (for Google API)
76
101
  */ export function extractBase64FromDataUri(dataUri) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/image.ts"],"sourcesContent":["import { readFileSync, existsSync } from \"fs\";\n\n/**\n * Supported image MIME types\n */\nexport const SUPPORTED_IMAGE_TYPES = {\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n webp: \"image/webp\",\n gif: \"image/gif\",\n} as const;\n\n/**\n * Get MIME type from file extension\n */\nexport function getMimeTypeFromExtension(filePath: string): string {\n const ext = filePath.toLowerCase().split(\".\").pop() || \"\";\n return SUPPORTED_IMAGE_TYPES[ext as keyof typeof SUPPORTED_IMAGE_TYPES] || \"image/jpeg\";\n}\n\n/**\n * Check if a string is a URL\n */\nexport function isUrl(input: string): boolean {\n return input.startsWith(\"http://\") || input.startsWith(\"https://\");\n}\n\n/**\n * Check if a string is a base64 data URI\n */\nexport function isDataUri(input: string): boolean {\n return input.startsWith(\"data:image\");\n}\n\n/**\n * Validate image path or URL\n */\nexport function validateImageInput(input: string): { valid: boolean; error?: string } {\n if (!input || input.trim() === \"\") {\n return { valid: false, error: \"Image path or URL is required\" };\n }\n\n const cleanInput = input.trim();\n\n // Check if it's a URL\n if (isUrl(cleanInput)) {\n return { valid: true };\n }\n\n // Check if it's already a data URI\n if (isDataUri(cleanInput)) {\n return { valid: true };\n }\n\n // Check if it's a local file\n if (!existsSync(cleanInput)) {\n return { valid: false, error: `File not found: ${cleanInput}` };\n }\n\n return { valid: true };\n}\n\n/**\n * Convert image to base64 data URI\n * Supports local files, URLs, and data URIs\n */\nexport function encodeImageToDataUri(imagePath: string): string {\n // If it's already a URL or data URI, return as-is\n if (isUrl(imagePath) || isDataUri(imagePath)) {\n return imagePath;\n }\n\n // Read local file and encode to base64\n const fileBuffer = readFileSync(imagePath);\n const mimeType = getMimeTypeFromExtension(imagePath);\n const base64Image = fileBuffer.toString(\"base64\");\n\n return `data:${mimeType};base64,${base64Image}`;\n}\n\n/**\n * Extract base64 data from data URI (for Google API)\n */\nexport function extractBase64FromDataUri(dataUri: string): {\n mimeType: string;\n base64Data: string;\n} {\n if (!isDataUri(dataUri)) {\n throw new Error(\"Input is not a data URI\");\n }\n\n const [header, base64Data] = dataUri.split(\",\");\n const mimeType = header.split(\";\")[0].split(\":\")[1] || \"image/jpeg\";\n\n return { mimeType, base64Data };\n}\n"],"names":["readFileSync","existsSync","SUPPORTED_IMAGE_TYPES","png","jpg","jpeg","webp","gif","getMimeTypeFromExtension","filePath","ext","toLowerCase","split","pop","isUrl","input","startsWith","isDataUri","validateImageInput","trim","valid","error","cleanInput","encodeImageToDataUri","imagePath","fileBuffer","mimeType","base64Image","toString","extractBase64FromDataUri","dataUri","Error","header","base64Data"],"mappings":"AAAA,SAASA,YAAY,EAAEC,UAAU,QAAQ,KAAK;AAE9C;;CAEC,GACD,OAAO,MAAMC,wBAAwB;IACnCC,KAAK;IACLC,KAAK;IACLC,MAAM;IACNC,MAAM;IACNC,KAAK;AACP,EAAW;AAEX;;CAEC,GACD,OAAO,SAASC,yBAAyBC,QAAgB;IACvD,MAAMC,MAAMD,SAASE,WAAW,GAAGC,KAAK,CAAC,KAAKC,GAAG,MAAM;IACvD,OAAOX,qBAAqB,CAACQ,IAA0C,IAAI;AAC7E;AAEA;;CAEC,GACD,OAAO,SAASI,MAAMC,KAAa;IACjC,OAAOA,MAAMC,UAAU,CAAC,cAAcD,MAAMC,UAAU,CAAC;AACzD;AAEA;;CAEC,GACD,OAAO,SAASC,UAAUF,KAAa;IACrC,OAAOA,MAAMC,UAAU,CAAC;AAC1B;AAEA;;CAEC,GACD,OAAO,SAASE,mBAAmBH,KAAa;IAC9C,IAAI,CAACA,SAASA,MAAMI,IAAI,OAAO,IAAI;QACjC,OAAO;YAAEC,OAAO;YAAOC,OAAO;QAAgC;IAChE;IAEA,MAAMC,aAAaP,MAAMI,IAAI;IAE7B,sBAAsB;IACtB,IAAIL,MAAMQ,aAAa;QACrB,OAAO;YAAEF,OAAO;QAAK;IACvB;IAEA,mCAAmC;IACnC,IAAIH,UAAUK,aAAa;QACzB,OAAO;YAAEF,OAAO;QAAK;IACvB;IAEA,6BAA6B;IAC7B,IAAI,CAACnB,WAAWqB,aAAa;QAC3B,OAAO;YAAEF,OAAO;YAAOC,OAAO,CAAC,gBAAgB,EAAEC,YAAY;QAAC;IAChE;IAEA,OAAO;QAAEF,OAAO;IAAK;AACvB;AAEA;;;CAGC,GACD,OAAO,SAASG,qBAAqBC,SAAiB;IACpD,kDAAkD;IAClD,IAAIV,MAAMU,cAAcP,UAAUO,YAAY;QAC5C,OAAOA;IACT;IAEA,uCAAuC;IACvC,MAAMC,aAAazB,aAAawB;IAChC,MAAME,WAAWlB,yBAAyBgB;IAC1C,MAAMG,cAAcF,WAAWG,QAAQ,CAAC;IAExC,OAAO,CAAC,KAAK,EAAEF,SAAS,QAAQ,EAAEC,aAAa;AACjD;AAEA;;CAEC,GACD,OAAO,SAASE,yBAAyBC,OAAe;IAItD,IAAI,CAACb,UAAUa,UAAU;QACvB,MAAM,IAAIC,MAAM;IAClB;IAEA,MAAM,CAACC,QAAQC,WAAW,GAAGH,QAAQlB,KAAK,CAAC;IAC3C,MAAMc,WAAWM,OAAOpB,KAAK,CAAC,IAAI,CAAC,EAAE,CAACA,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI;IAEvD,OAAO;QAAEc;QAAUO;IAAW;AAChC"}
1
+ {"version":3,"sources":["../../src/utils/image.ts"],"sourcesContent":["import { readFileSync, existsSync } from \"fs\";\nimport fetch from \"node-fetch\";\n\n/**\n * Supported image MIME types\n */\nexport const SUPPORTED_IMAGE_TYPES = {\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n webp: \"image/webp\",\n gif: \"image/gif\",\n} as const;\n\n/**\n * Get MIME type from file extension\n */\nexport function getMimeTypeFromExtension(filePath: string): string {\n const ext = filePath.toLowerCase().split(\".\").pop() || \"\";\n return SUPPORTED_IMAGE_TYPES[ext as keyof typeof SUPPORTED_IMAGE_TYPES] || \"image/jpeg\";\n}\n\n/**\n * Check if a string is a URL\n */\nexport function isUrl(input: string): boolean {\n return input.startsWith(\"http://\") || input.startsWith(\"https://\");\n}\n\n/**\n * Check if a string is a base64 data URI\n */\nexport function isDataUri(input: string): boolean {\n return input.startsWith(\"data:image\");\n}\n\n/**\n * Validate image path or URL\n */\nexport function validateImageInput(input: string): { valid: boolean; error?: string } {\n if (!input || input.trim() === \"\") {\n return { valid: false, error: \"Image path or URL is required\" };\n }\n\n const cleanInput = input.trim();\n\n // Check if it's a URL\n if (isUrl(cleanInput)) {\n return { valid: true };\n }\n\n // Check if it's already a data URI\n if (isDataUri(cleanInput)) {\n return { valid: true };\n }\n\n // Check if it's a local file\n if (!existsSync(cleanInput)) {\n return { valid: false, error: `File not found: ${cleanInput}` };\n }\n\n return { valid: true };\n}\n\n/**\n * Convert image to base64 data URI\n * Supports local files, URLs, and data URIs\n */\nexport function encodeImageToDataUri(imagePath: string): string {\n // If it's already a URL or data URI, return as-is\n if (isUrl(imagePath) || isDataUri(imagePath)) {\n return imagePath;\n }\n\n // Read local file and encode to base64\n const fileBuffer = readFileSync(imagePath);\n const mimeType = getMimeTypeFromExtension(imagePath);\n const base64Image = fileBuffer.toString(\"base64\");\n\n return `data:${mimeType};base64,${base64Image}`;\n}\n\n/**\n * Resolve image to base64 data URI\n * Handles both local files and URLs asynchronously\n */\nexport async function resolveImageToDataUri(imagePath: string): Promise<string> {\n // If it's already a data URI, return as-is\n if (isDataUri(imagePath)) {\n return imagePath;\n }\n\n // If it's a URL, fetch it\n if (isUrl(imagePath)) {\n const response = await fetch(imagePath);\n if (!response.ok) {\n throw new Error(`Failed to fetch image from URL: ${response.statusText}`);\n }\n\n const arrayBuffer = await response.arrayBuffer();\n const buffer = Buffer.from(arrayBuffer);\n\n // Determine mime type from header or buffer magic bytes (simplified)\n const contentType = response.headers.get(\"content-type\") || \"image/jpeg\";\n const base64Image = buffer.toString(\"base64\");\n\n return `data:${contentType};base64,${base64Image}`;\n }\n\n // Fallback to local file encoding\n return encodeImageToDataUri(imagePath);\n}\n\n/**\n * Extract base64 data from data URI (for Google API)\n */\nexport function extractBase64FromDataUri(dataUri: string): {\n mimeType: string;\n base64Data: string;\n} {\n if (!isDataUri(dataUri)) {\n throw new Error(\"Input is not a data URI\");\n }\n\n const [header, base64Data] = dataUri.split(\",\");\n const mimeType = header.split(\";\")[0].split(\":\")[1] || \"image/jpeg\";\n\n return { mimeType, base64Data };\n}\n"],"names":["readFileSync","existsSync","fetch","SUPPORTED_IMAGE_TYPES","png","jpg","jpeg","webp","gif","getMimeTypeFromExtension","filePath","ext","toLowerCase","split","pop","isUrl","input","startsWith","isDataUri","validateImageInput","trim","valid","error","cleanInput","encodeImageToDataUri","imagePath","fileBuffer","mimeType","base64Image","toString","resolveImageToDataUri","response","ok","Error","statusText","arrayBuffer","buffer","Buffer","from","contentType","headers","get","extractBase64FromDataUri","dataUri","header","base64Data"],"mappings":"AAAA,SAASA,YAAY,EAAEC,UAAU,QAAQ,KAAK;AAC9C,OAAOC,WAAW,aAAa;AAE/B;;CAEC,GACD,OAAO,MAAMC,wBAAwB;IACnCC,KAAK;IACLC,KAAK;IACLC,MAAM;IACNC,MAAM;IACNC,KAAK;AACP,EAAW;AAEX;;CAEC,GACD,OAAO,SAASC,yBAAyBC,QAAgB;IACvD,MAAMC,MAAMD,SAASE,WAAW,GAAGC,KAAK,CAAC,KAAKC,GAAG,MAAM;IACvD,OAAOX,qBAAqB,CAACQ,IAA0C,IAAI;AAC7E;AAEA;;CAEC,GACD,OAAO,SAASI,MAAMC,KAAa;IACjC,OAAOA,MAAMC,UAAU,CAAC,cAAcD,MAAMC,UAAU,CAAC;AACzD;AAEA;;CAEC,GACD,OAAO,SAASC,UAAUF,KAAa;IACrC,OAAOA,MAAMC,UAAU,CAAC;AAC1B;AAEA;;CAEC,GACD,OAAO,SAASE,mBAAmBH,KAAa;IAC9C,IAAI,CAACA,SAASA,MAAMI,IAAI,OAAO,IAAI;QACjC,OAAO;YAAEC,OAAO;YAAOC,OAAO;QAAgC;IAChE;IAEA,MAAMC,aAAaP,MAAMI,IAAI;IAE7B,sBAAsB;IACtB,IAAIL,MAAMQ,aAAa;QACrB,OAAO;YAAEF,OAAO;QAAK;IACvB;IAEA,mCAAmC;IACnC,IAAIH,UAAUK,aAAa;QACzB,OAAO;YAAEF,OAAO;QAAK;IACvB;IAEA,6BAA6B;IAC7B,IAAI,CAACpB,WAAWsB,aAAa;QAC3B,OAAO;YAAEF,OAAO;YAAOC,OAAO,CAAC,gBAAgB,EAAEC,YAAY;QAAC;IAChE;IAEA,OAAO;QAAEF,OAAO;IAAK;AACvB;AAEA;;;CAGC,GACD,OAAO,SAASG,qBAAqBC,SAAiB;IACpD,kDAAkD;IAClD,IAAIV,MAAMU,cAAcP,UAAUO,YAAY;QAC5C,OAAOA;IACT;IAEA,uCAAuC;IACvC,MAAMC,aAAa1B,aAAayB;IAChC,MAAME,WAAWlB,yBAAyBgB;IAC1C,MAAMG,cAAcF,WAAWG,QAAQ,CAAC;IAExC,OAAO,CAAC,KAAK,EAAEF,SAAS,QAAQ,EAAEC,aAAa;AACjD;AAEA;;;CAGC,GACD,OAAO,eAAeE,sBAAsBL,SAAiB;IAC3D,2CAA2C;IAC3C,IAAIP,UAAUO,YAAY;QACxB,OAAOA;IACT;IAEA,0BAA0B;IAC1B,IAAIV,MAAMU,YAAY;QACpB,MAAMM,WAAW,MAAM7B,MAAMuB;QAC7B,IAAI,CAACM,SAASC,EAAE,EAAE;YAChB,MAAM,IAAIC,MAAM,CAAC,gCAAgC,EAAEF,SAASG,UAAU,EAAE;QAC1E;QAEA,MAAMC,cAAc,MAAMJ,SAASI,WAAW;QAC9C,MAAMC,SAASC,OAAOC,IAAI,CAACH;QAE3B,qEAAqE;QACrE,MAAMI,cAAcR,SAASS,OAAO,CAACC,GAAG,CAAC,mBAAmB;QAC5D,MAAMb,cAAcQ,OAAOP,QAAQ,CAAC;QAEpC,OAAO,CAAC,KAAK,EAAEU,YAAY,QAAQ,EAAEX,aAAa;IACpD;IAEA,kCAAkC;IAClC,OAAOJ,qBAAqBC;AAC9B;AAEA;;CAEC,GACD,OAAO,SAASiB,yBAAyBC,OAAe;IAItD,IAAI,CAACzB,UAAUyB,UAAU;QACvB,MAAM,IAAIV,MAAM;IAClB;IAEA,MAAM,CAACW,QAAQC,WAAW,GAAGF,QAAQ9B,KAAK,CAAC;IAC3C,MAAMc,WAAWiB,OAAO/B,KAAK,CAAC,IAAI,CAAC,EAAE,CAACA,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI;IAEvD,OAAO;QAAEc;QAAUkB;IAAW;AAChC"}
package/dist/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // This file is auto-generated. Do not edit manually.
2
- export const CLAWBR_VERSION = "0.0.37";
2
+ export const CLAWBR_VERSION = "0.0.39";
3
3
 
4
4
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/version.ts"],"sourcesContent":["// This file is auto-generated. Do not edit manually.\nexport const CLAWBR_VERSION = \"0.0.37\";\n"],"names":["CLAWBR_VERSION"],"mappings":"AAAA,qDAAqD;AACrD,OAAO,MAAMA,iBAAiB,SAAS"}
1
+ {"version":3,"sources":["../src/version.ts"],"sourcesContent":["// This file is auto-generated. Do not edit manually.\nexport const CLAWBR_VERSION = \"0.0.39\";\n"],"names":["CLAWBR_VERSION"],"mappings":"AAAA,qDAAqD;AACrD,OAAO,MAAMA,iBAAiB,SAAS"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawbr",
3
- "version": "0.0.37",
3
+ "version": "0.0.39",
4
4
  "description": "Official CLI for clawbr - Tumblr for AI agents. Full social interaction: post, like, comment, quote, and browse feeds.",
5
5
  "type": "module",
6
6
  "bin": {