sogni-gen 1.3.1 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -41,6 +41,33 @@ Restart Claude Desktop after saving. The same natural-language commands work.
41
41
 
42
42
  > **Note:** Both Claude Code and Claude Desktop require Sogni credentials — see [Setup](#setup) below.
43
43
 
44
+ ### Global npm Install (CLI + MCP)
45
+
46
+ ```bash
47
+ npm install -g sogni-gen
48
+ sogni-gen --version
49
+ ```
50
+
51
+ If `sogni-gen-mcp` is on your `PATH`, you can register it directly:
52
+
53
+ ```bash
54
+ # Claude Code using globally installed binary
55
+ claude mcp add sogni -- sogni-gen-mcp
56
+ ```
57
+
58
+ Claude Desktop config using global binary:
59
+
60
+ ```json
61
+ {
62
+ "mcpServers": {
63
+ "sogni": {
64
+ "command": "sogni-gen-mcp",
65
+ "args": []
66
+ }
67
+ }
68
+ }
69
+ ```
70
+
44
71
  ### Quick Install (OpenClaw) - Recommended
45
72
 
46
73
  Point your OpenClaw to the [`llm.txt`](https://raw.githubusercontent.com/Sogni-AI/openclaw-sogni-gen/main/llm.txt) and everything is set up — just paste the URL into Telegram, WhatsApp, or iMessages and the bot handles image and video generation automatically.
@@ -269,6 +296,7 @@ Multi-angle mode auto-builds the `<sks>` prompt and applies the `multiple_angles
269
296
  --lora-strengths <n> Comma-separated LoRA strengths
270
297
  --token-type <type> spark|sogni
271
298
  --balance, --balances Show SPARK/SOGNI balances and exit
299
+ --version, -V Show sogni-gen version and exit
272
300
  --video, -v Generate video instead of image
273
301
  --workflow <type> t2v|i2v|s2v|animate-move|animate-replace
274
302
  --fps <num> Frames per second (video)
@@ -18,7 +18,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprot
18
18
  import { spawn } from 'child_process';
19
19
  import { fileURLToPath } from 'url';
20
20
  import { dirname, join } from 'path';
21
- import { existsSync } from 'fs';
21
+ import { existsSync, readFileSync } from 'fs';
22
22
  import { homedir } from 'os';
23
23
 
24
24
  // ---------------------------------------------------------------------------
@@ -29,6 +29,14 @@ const __filename = fileURLToPath(import.meta.url);
29
29
  const __dirname = dirname(__filename);
30
30
  const SOGNI_GEN = join(__dirname, 'sogni-gen.mjs');
31
31
  const CREDENTIALS_PATH = join(homedir(), '.config', 'sogni', 'credentials');
32
+ const SERVER_VERSION = (() => {
33
+ try {
34
+ const pkg = JSON.parse(readFileSync(join(__dirname, 'package.json'), 'utf8'));
35
+ return pkg.version || 'unknown';
36
+ } catch {
37
+ return 'unknown';
38
+ }
39
+ })();
32
40
 
33
41
  // ---------------------------------------------------------------------------
34
42
  // CLI spawning helper
@@ -110,7 +118,7 @@ function checkCredentials() {
110
118
  // Result formatting
111
119
  // ---------------------------------------------------------------------------
112
120
 
113
- function formatSuccess(result) {
121
+ async function formatSuccess(result) {
114
122
  const parts = [];
115
123
 
116
124
  if (result.type === 'balance') {
@@ -144,13 +152,55 @@ function formatSuccess(result) {
144
152
 
145
153
  const content = [{ type: 'text', text: parts.join('\n') }];
146
154
 
147
- // Also include image URLs as image content so Claude can see them
155
+ // Download images/videos and save locally + embed as base64 for Claude Desktop
156
+ const savedPaths = [];
148
157
  for (const url of urls) {
149
- if (/\.(png|jpg|jpeg|webp|gif)(\?|$)/i.test(url)) {
150
- content.push({ type: 'image', data: url, mimeType: 'image/png' });
158
+ const isImage = /\.(png|jpg|jpeg|webp|gif)(\?|$)/i.test(url);
159
+ const isVideo = /\.(mp4|webm|mov)(\?|$)/i.test(url);
160
+
161
+ if (!isImage && !isVideo) continue;
162
+
163
+ try {
164
+ const resp = await fetch(url);
165
+ if (!resp.ok) continue;
166
+ const buf = Buffer.from(await resp.arrayBuffer());
167
+
168
+ // Determine extension and build a temp file path
169
+ const ext = isImage
170
+ ? (url.match(/\.(png|jpg|jpeg|webp|gif)/i)?.[1]?.toLowerCase() || 'png')
171
+ : (url.match(/\.(mp4|webm|mov)/i)?.[1]?.toLowerCase() || 'mp4');
172
+
173
+ // Save to ~/Downloads/sogni/ so the user can find it easily
174
+ const { mkdirSync, writeFileSync } = await import('fs');
175
+ const downloadsDir = join(homedir(), 'Downloads', 'sogni');
176
+ mkdirSync(downloadsDir, { recursive: true });
177
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
178
+ const filename = `sogni-${timestamp}-${savedPaths.length}.${ext}`;
179
+ const filePath = join(downloadsDir, filename);
180
+ writeFileSync(filePath, buf);
181
+ savedPaths.push(filePath);
182
+
183
+ // For images, embed as base64 (Claude Desktop can render these inline)
184
+ if (isImage) {
185
+ const mimeType = ext === 'jpg' || ext === 'jpeg' ? 'image/jpeg'
186
+ : ext === 'webp' ? 'image/webp'
187
+ : ext === 'gif' ? 'image/gif'
188
+ : 'image/png';
189
+ content.push({ type: 'image', data: buf.toString('base64'), mimeType });
190
+ }
191
+ } catch {
192
+ // If download fails, skip — the URL is still in the text above
151
193
  }
152
194
  }
153
195
 
196
+ // Append saved file paths to the text output
197
+ if (savedPaths.length > 0) {
198
+ const textBlock = content[0];
199
+ textBlock.text += '\n\n' + savedPaths.map((p, i) =>
200
+ savedPaths.length === 1 ? `📁 Saved: ${p}` : `📁 Saved #${i + 1}: ${p}`
201
+ ).join('\n');
202
+ }
203
+
154
204
  return { content };
155
205
  }
156
206
 
@@ -161,11 +211,20 @@ function formatError(result) {
161
211
  return { content: [{ type: 'text', text: parts.join('\n') }], isError: true };
162
212
  }
163
213
 
164
- function formatResult(result) {
214
+ async function formatResult(result) {
165
215
  if (result.success === false) return formatError(result);
166
216
  return formatSuccess(result);
167
217
  }
168
218
 
219
+ async function runAndFormat(args, { timeoutMs = 30_000, requireCredentials = true } = {}) {
220
+ if (requireCredentials) {
221
+ const credErr = checkCredentials();
222
+ if (credErr) return credErr;
223
+ }
224
+ const result = await runSogniGen(args, { timeoutMs });
225
+ return formatResult(result);
226
+ }
227
+
169
228
  // ---------------------------------------------------------------------------
170
229
  // Tool definitions
171
230
  // ---------------------------------------------------------------------------
@@ -446,6 +505,14 @@ The face likeness is preserved while applying the style from the prompt.`,
446
505
  properties: {},
447
506
  },
448
507
  },
508
+ {
509
+ name: 'get_version',
510
+ description: 'Show the running sogni-gen version for this MCP server instance.',
511
+ inputSchema: {
512
+ type: 'object',
513
+ properties: {},
514
+ },
515
+ },
449
516
  ];
450
517
 
451
518
  // ---------------------------------------------------------------------------
@@ -453,9 +520,6 @@ The face likeness is preserved while applying the style from the prompt.`,
453
520
  // ---------------------------------------------------------------------------
454
521
 
455
522
  async function handleGenerateImage(params) {
456
- const credErr = checkCredentials();
457
- if (credErr) return credErr;
458
-
459
523
  const args = [params.prompt];
460
524
  if (params.model) args.push('-m', params.model);
461
525
  if (params.width) args.push('-w', String(params.width));
@@ -467,14 +531,10 @@ async function handleGenerateImage(params) {
467
531
  if (params.loras?.length) args.push('--loras', params.loras.join(','));
468
532
  if (params.lora_strengths?.length) args.push('--lora-strengths', params.lora_strengths.join(','));
469
533
 
470
- const result = await runSogniGen(args, { timeoutMs: 60_000 });
471
- return formatResult(result);
534
+ return runAndFormat(args, { timeoutMs: 60_000 });
472
535
  }
473
536
 
474
537
  async function handleGenerateVideo(params) {
475
- const credErr = checkCredentials();
476
- if (credErr) return credErr;
477
-
478
538
  const args = ['--video', params.prompt];
479
539
  if (params.workflow) args.push('--workflow', params.workflow);
480
540
  if (params.model) args.push('-m', params.model);
@@ -491,14 +551,10 @@ async function handleGenerateVideo(params) {
491
551
  if (params.output) args.push('-o', params.output);
492
552
  if (params.looping) args.push('--looping');
493
553
 
494
- const result = await runSogniGen(args, { timeoutMs: 600_000 });
495
- return formatResult(result);
554
+ return runAndFormat(args, { timeoutMs: 600_000 });
496
555
  }
497
556
 
498
557
  async function handleEditImage(params) {
499
- const credErr = checkCredentials();
500
- if (credErr) return credErr;
501
-
502
558
  const args = [];
503
559
  for (const img of params.context_images) {
504
560
  args.push('-c', img);
@@ -509,14 +565,10 @@ async function handleEditImage(params) {
509
565
  if (params.height) args.push('-h', String(params.height));
510
566
  if (params.output) args.push('-o', params.output);
511
567
 
512
- const result = await runSogniGen(args, { timeoutMs: 60_000 });
513
- return formatResult(result);
568
+ return runAndFormat(args, { timeoutMs: 60_000 });
514
569
  }
515
570
 
516
571
  async function handlePhotobooth(params) {
517
- const credErr = checkCredentials();
518
- if (credErr) return credErr;
519
-
520
572
  const args = ['--photobooth', '--ref', params.reference_face, params.prompt];
521
573
  if (params.model) args.push('-m', params.model);
522
574
  if (params.cn_strength != null) args.push('--cn-strength', String(params.cn_strength));
@@ -526,16 +578,22 @@ async function handlePhotobooth(params) {
526
578
  if (params.count) args.push('-n', String(params.count));
527
579
  if (params.output) args.push('-o', params.output);
528
580
 
529
- const result = await runSogniGen(args, { timeoutMs: 60_000 });
530
- return formatResult(result);
581
+ return runAndFormat(args, { timeoutMs: 60_000 });
531
582
  }
532
583
 
533
584
  async function handleCheckBalance() {
534
- const credErr = checkCredentials();
535
- if (credErr) return credErr;
585
+ return runAndFormat(['--balance'], { timeoutMs: 30_000 });
586
+ }
536
587
 
537
- const result = await runSogniGen(['--balance'], { timeoutMs: 30_000 });
538
- return formatResult(result);
588
+ async function handleGetVersion() {
589
+ const result = await runSogniGen(['--version'], { timeoutMs: 5_000 });
590
+ if (result.success === false) return formatError(result);
591
+ return {
592
+ content: [{
593
+ type: 'text',
594
+ text: `mcp-server version: ${SERVER_VERSION}\nsogni-gen version: ${result.version || 'unknown'}`,
595
+ }],
596
+ };
539
597
  }
540
598
 
541
599
  function handleListModels() {
@@ -560,7 +618,7 @@ Defaults:
560
618
  // ---------------------------------------------------------------------------
561
619
 
562
620
  const server = new Server(
563
- { name: 'sogni', version: '1.3.0' },
621
+ { name: 'sogni', version: SERVER_VERSION },
564
622
  { capabilities: { tools: {} } },
565
623
  );
566
624
 
@@ -582,6 +640,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
582
640
  return await handleCheckBalance();
583
641
  case 'list_models':
584
642
  return handleListModels();
643
+ case 'get_version':
644
+ return await handleGetVersion();
585
645
  default:
586
646
  return {
587
647
  content: [{ type: 'text', text: `Unknown tool: ${name}` }],
@@ -19,6 +19,14 @@ const IS_OPENCLAW_INVOCATION = Boolean(process.env.OPENCLAW_PLUGIN_CONFIG);
19
19
  const RAW_ARGS = process.argv.slice(2);
20
20
  const CLI_WANTS_JSON = RAW_ARGS.includes('--json');
21
21
  const JSON_ERROR_MODE = CLI_WANTS_JSON || IS_OPENCLAW_INVOCATION;
22
+ const PACKAGE_VERSION = (() => {
23
+ try {
24
+ const pkg = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8'));
25
+ return pkg.version || 'unknown';
26
+ } catch {
27
+ return 'unknown';
28
+ }
29
+ })();
22
30
  const VIDEO_WORKFLOW_DEFAULT_MODELS = {
23
31
  't2v': 'wan_v2.2-14b-fp8_t2v_lightx2v',
24
32
  'i2v': 'wan_v2.2-14b-fp8_i2v_lightx2v',
@@ -587,6 +595,7 @@ const options = {
587
595
  autoResizeVideoAssets: null,
588
596
  estimateVideoCost: false,
589
597
  showBalance: false,
598
+ showVersion: false,
590
599
  angles360Video: null,
591
600
  refImage: null, // Reference image for video (start frame)
592
601
  refImageEnd: null, // End frame for video interpolation
@@ -811,6 +820,8 @@ for (let i = 0; i < args.length; i++) {
811
820
  options.estimateVideoCost = true;
812
821
  } else if (arg === '--balance' || arg === '--balances') {
813
822
  options.showBalance = true;
823
+ } else if (arg === '--version' || arg === '-V') {
824
+ options.showVersion = true;
814
825
  } else if (arg === '--help') {
815
826
  console.log(`
816
827
  sogni-gen - Generate images and videos using Sogni AI
@@ -873,6 +884,7 @@ General:
873
884
  --guidance <num> Override guidance (model-dependent)
874
885
  --token-type <type> Token type: spark|sogni (default: spark)
875
886
  --balance, --balances Show SPARK/SOGNI balances and exit
887
+ --version, -V Show sogni-gen version and exit
876
888
  --last Show last render info (JSON)
877
889
  --json Output JSON with all details
878
890
  --strict-size Do not auto-adjust video size to satisfy i2v reference resizing constraints
@@ -1199,7 +1211,7 @@ if (options.video) {
1199
1211
  options.model = options.model || openclawConfig?.defaultImageModel || 'z_image_turbo_bf16';
1200
1212
  }
1201
1213
 
1202
- if (!options.prompt && !options.estimateVideoCost && !options.multiAngle && !options.showBalance) {
1214
+ if (!options.prompt && !options.estimateVideoCost && !options.multiAngle && !options.showBalance && !options.showVersion) {
1203
1215
  fatalCliError('No prompt provided. Use --help for usage.', { code: 'INVALID_ARGUMENT' });
1204
1216
  }
1205
1217
 
@@ -1452,7 +1464,7 @@ if (options.lastSeed) {
1452
1464
  }
1453
1465
  }
1454
1466
 
1455
- if (!options.estimateVideoCost && (options.seed === null || options.seed === undefined)) {
1467
+ if (!options.estimateVideoCost && !options.showVersion && (options.seed === null || options.seed === undefined)) {
1456
1468
  const strategy = options.seedStrategy || openclawConfig?.seedStrategy || 'prompt-hash';
1457
1469
  const normalized = normalizeSeedStrategy(strategy) || 'prompt-hash';
1458
1470
  options.seedStrategy = normalized;
@@ -2163,6 +2175,21 @@ async function main() {
2163
2175
  let client = null;
2164
2176
 
2165
2177
  try {
2178
+ if (options.showVersion) {
2179
+ if (options.json) {
2180
+ console.log(JSON.stringify({
2181
+ success: true,
2182
+ type: 'version',
2183
+ name: 'sogni-gen',
2184
+ version: PACKAGE_VERSION,
2185
+ timestamp: new Date().toISOString()
2186
+ }));
2187
+ } else {
2188
+ console.log(PACKAGE_VERSION);
2189
+ }
2190
+ return;
2191
+ }
2192
+
2166
2193
  const creds = loadCredentials();
2167
2194
  log('Connecting to Sogni...');
2168
2195
  client = new SogniClientWrapper({
package/mcp-server.mjs CHANGED
@@ -18,7 +18,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprot
18
18
  import { spawn } from 'child_process';
19
19
  import { fileURLToPath } from 'url';
20
20
  import { dirname, join } from 'path';
21
- import { existsSync } from 'fs';
21
+ import { existsSync, readFileSync } from 'fs';
22
22
  import { homedir } from 'os';
23
23
 
24
24
  // ---------------------------------------------------------------------------
@@ -29,6 +29,14 @@ const __filename = fileURLToPath(import.meta.url);
29
29
  const __dirname = dirname(__filename);
30
30
  const SOGNI_GEN = join(__dirname, 'sogni-gen.mjs');
31
31
  const CREDENTIALS_PATH = join(homedir(), '.config', 'sogni', 'credentials');
32
+ const SERVER_VERSION = (() => {
33
+ try {
34
+ const pkg = JSON.parse(readFileSync(join(__dirname, 'package.json'), 'utf8'));
35
+ return pkg.version || 'unknown';
36
+ } catch {
37
+ return 'unknown';
38
+ }
39
+ })();
32
40
 
33
41
  // ---------------------------------------------------------------------------
34
42
  // CLI spawning helper
@@ -144,26 +152,58 @@ async function formatSuccess(result) {
144
152
 
145
153
  const content = [{ type: 'text', text: parts.join('\n') }];
146
154
 
147
- // Download images and include as Base64-encoded content so Claude can see them
155
+ // Download images/videos and save locally + embed as base64 for MCP clients
156
+ // that support inline image rendering (e.g. Claude Desktop).
157
+ // For Claude Code (terminal), the saved file path is the primary way to view results.
158
+ const savedPaths = [];
148
159
  for (const url of urls) {
149
- if (/\.(png|jpg|jpeg|webp|gif)(\?|$)/i.test(url)) {
150
- try {
151
- const resp = await fetch(url);
152
- if (resp.ok) {
153
- const buf = Buffer.from(await resp.arrayBuffer());
154
- const ext = url.match(/\.(png|jpg|jpeg|webp|gif)/i)?.[1]?.toLowerCase() || 'png';
155
- const mimeType = ext === 'jpg' || ext === 'jpeg' ? 'image/jpeg'
156
- : ext === 'webp' ? 'image/webp'
157
- : ext === 'gif' ? 'image/gif'
158
- : 'image/png';
159
- content.push({ type: 'image', data: buf.toString('base64'), mimeType });
160
- }
161
- } catch {
162
- // If download fails, skip embedding — the URL is still in the text
160
+ const isImage = /\.(png|jpg|jpeg|webp|gif)(\?|$)/i.test(url);
161
+ const isVideo = /\.(mp4|webm|mov)(\?|$)/i.test(url);
162
+
163
+ if (!isImage && !isVideo) continue;
164
+
165
+ try {
166
+ const resp = await fetch(url);
167
+ if (!resp.ok) continue;
168
+ const buf = Buffer.from(await resp.arrayBuffer());
169
+
170
+ // Determine extension and build a temp file path
171
+ const ext = isImage
172
+ ? (url.match(/\.(png|jpg|jpeg|webp|gif)/i)?.[1]?.toLowerCase() || 'png')
173
+ : (url.match(/\.(mp4|webm|mov)/i)?.[1]?.toLowerCase() || 'mp4');
174
+
175
+ // Save to ~/Downloads/sogni/ so the user can find it easily
176
+ const { mkdirSync, writeFileSync } = await import('fs');
177
+ const downloadsDir = join(homedir(), 'Downloads', 'sogni');
178
+ mkdirSync(downloadsDir, { recursive: true });
179
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
180
+ const filename = `sogni-${timestamp}-${savedPaths.length}.${ext}`;
181
+ const filePath = join(downloadsDir, filename);
182
+ writeFileSync(filePath, buf);
183
+ savedPaths.push(filePath);
184
+
185
+ // For images, also embed as base64 (Claude Desktop can render these)
186
+ if (isImage) {
187
+ const mimeType = ext === 'jpg' || ext === 'jpeg' ? 'image/jpeg'
188
+ : ext === 'webp' ? 'image/webp'
189
+ : ext === 'gif' ? 'image/gif'
190
+ : 'image/png';
191
+ content.push({ type: 'image', data: buf.toString('base64'), mimeType });
163
192
  }
193
+ } catch {
194
+ // If download fails, skip — the URL is still in the text above
164
195
  }
165
196
  }
166
197
 
198
+ // Append saved file paths to the text output so Claude Code users can see/open them
199
+ if (savedPaths.length > 0) {
200
+ const textBlock = content[0];
201
+ textBlock.text += '\n\n' + savedPaths.map((p, i) =>
202
+ savedPaths.length === 1 ? `📁 Saved: ${p}` : `📁 Saved #${i + 1}: ${p}`
203
+ ).join('\n');
204
+ textBlock.text += '\n\nTip: In Claude Code, ask Claude to run `open <path>` to view the file.';
205
+ }
206
+
167
207
  return { content };
168
208
  }
169
209
 
@@ -179,6 +219,15 @@ async function formatResult(result) {
179
219
  return formatSuccess(result);
180
220
  }
181
221
 
222
+ async function runAndFormat(args, { timeoutMs = 30_000, requireCredentials = true } = {}) {
223
+ if (requireCredentials) {
224
+ const credErr = checkCredentials();
225
+ if (credErr) return credErr;
226
+ }
227
+ const result = await runSogniGen(args, { timeoutMs });
228
+ return formatResult(result);
229
+ }
230
+
182
231
  // ---------------------------------------------------------------------------
183
232
  // Tool definitions
184
233
  // ---------------------------------------------------------------------------
@@ -459,6 +508,14 @@ The face likeness is preserved while applying the style from the prompt.`,
459
508
  properties: {},
460
509
  },
461
510
  },
511
+ {
512
+ name: 'get_version',
513
+ description: 'Show the running sogni-gen version for this MCP server instance.',
514
+ inputSchema: {
515
+ type: 'object',
516
+ properties: {},
517
+ },
518
+ },
462
519
  ];
463
520
 
464
521
  // ---------------------------------------------------------------------------
@@ -466,9 +523,6 @@ The face likeness is preserved while applying the style from the prompt.`,
466
523
  // ---------------------------------------------------------------------------
467
524
 
468
525
  async function handleGenerateImage(params) {
469
- const credErr = checkCredentials();
470
- if (credErr) return credErr;
471
-
472
526
  const args = [params.prompt];
473
527
  if (params.model) args.push('-m', params.model);
474
528
  if (params.width) args.push('-w', String(params.width));
@@ -480,14 +534,10 @@ async function handleGenerateImage(params) {
480
534
  if (params.loras?.length) args.push('--loras', params.loras.join(','));
481
535
  if (params.lora_strengths?.length) args.push('--lora-strengths', params.lora_strengths.join(','));
482
536
 
483
- const result = await runSogniGen(args, { timeoutMs: 60_000 });
484
- return formatResult(result);
537
+ return runAndFormat(args, { timeoutMs: 60_000 });
485
538
  }
486
539
 
487
540
  async function handleGenerateVideo(params) {
488
- const credErr = checkCredentials();
489
- if (credErr) return credErr;
490
-
491
541
  const args = ['--video', params.prompt];
492
542
  if (params.workflow) args.push('--workflow', params.workflow);
493
543
  if (params.model) args.push('-m', params.model);
@@ -504,14 +554,10 @@ async function handleGenerateVideo(params) {
504
554
  if (params.output) args.push('-o', params.output);
505
555
  if (params.looping) args.push('--looping');
506
556
 
507
- const result = await runSogniGen(args, { timeoutMs: 600_000 });
508
- return formatResult(result);
557
+ return runAndFormat(args, { timeoutMs: 600_000 });
509
558
  }
510
559
 
511
560
  async function handleEditImage(params) {
512
- const credErr = checkCredentials();
513
- if (credErr) return credErr;
514
-
515
561
  const args = [];
516
562
  for (const img of params.context_images) {
517
563
  args.push('-c', img);
@@ -522,14 +568,10 @@ async function handleEditImage(params) {
522
568
  if (params.height) args.push('-h', String(params.height));
523
569
  if (params.output) args.push('-o', params.output);
524
570
 
525
- const result = await runSogniGen(args, { timeoutMs: 60_000 });
526
- return formatResult(result);
571
+ return runAndFormat(args, { timeoutMs: 60_000 });
527
572
  }
528
573
 
529
574
  async function handlePhotobooth(params) {
530
- const credErr = checkCredentials();
531
- if (credErr) return credErr;
532
-
533
575
  const args = ['--photobooth', '--ref', params.reference_face, params.prompt];
534
576
  if (params.model) args.push('-m', params.model);
535
577
  if (params.cn_strength != null) args.push('--cn-strength', String(params.cn_strength));
@@ -539,16 +581,22 @@ async function handlePhotobooth(params) {
539
581
  if (params.count) args.push('-n', String(params.count));
540
582
  if (params.output) args.push('-o', params.output);
541
583
 
542
- const result = await runSogniGen(args, { timeoutMs: 60_000 });
543
- return formatResult(result);
584
+ return runAndFormat(args, { timeoutMs: 60_000 });
544
585
  }
545
586
 
546
587
  async function handleCheckBalance() {
547
- const credErr = checkCredentials();
548
- if (credErr) return credErr;
588
+ return runAndFormat(['--balance'], { timeoutMs: 30_000 });
589
+ }
549
590
 
550
- const result = await runSogniGen(['--balance'], { timeoutMs: 30_000 });
551
- return formatResult(result);
591
+ async function handleGetVersion() {
592
+ const result = await runSogniGen(['--version'], { timeoutMs: 5_000 });
593
+ if (result.success === false) return formatError(result);
594
+ return {
595
+ content: [{
596
+ type: 'text',
597
+ text: `mcp-server version: ${SERVER_VERSION}\nsogni-gen version: ${result.version || 'unknown'}`,
598
+ }],
599
+ };
552
600
  }
553
601
 
554
602
  function handleListModels() {
@@ -573,7 +621,7 @@ Defaults:
573
621
  // ---------------------------------------------------------------------------
574
622
 
575
623
  const server = new Server(
576
- { name: 'sogni', version: '1.3.0' },
624
+ { name: 'sogni', version: SERVER_VERSION },
577
625
  { capabilities: { tools: {} } },
578
626
  );
579
627
 
@@ -595,6 +643,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
595
643
  return await handleCheckBalance();
596
644
  case 'list_models':
597
645
  return handleListModels();
646
+ case 'get_version':
647
+ return await handleGetVersion();
598
648
  default:
599
649
  return {
600
650
  content: [{ type: 'text', text: `Unknown tool: ${name}` }],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sogni-gen",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
4
4
  "description": "Sogni AI image & video generation — OpenClaw plugin and MCP server for Claude Code / Claude Desktop",
5
5
  "type": "module",
6
6
  "main": "sogni-gen.mjs",
package/sogni-gen.mjs CHANGED
@@ -19,6 +19,14 @@ const IS_OPENCLAW_INVOCATION = Boolean(process.env.OPENCLAW_PLUGIN_CONFIG);
19
19
  const RAW_ARGS = process.argv.slice(2);
20
20
  const CLI_WANTS_JSON = RAW_ARGS.includes('--json');
21
21
  const JSON_ERROR_MODE = CLI_WANTS_JSON || IS_OPENCLAW_INVOCATION;
22
+ const PACKAGE_VERSION = (() => {
23
+ try {
24
+ const pkg = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8'));
25
+ return pkg.version || 'unknown';
26
+ } catch {
27
+ return 'unknown';
28
+ }
29
+ })();
22
30
  const VIDEO_WORKFLOW_DEFAULT_MODELS = {
23
31
  't2v': 'wan_v2.2-14b-fp8_t2v_lightx2v',
24
32
  'i2v': 'wan_v2.2-14b-fp8_i2v_lightx2v',
@@ -587,6 +595,7 @@ const options = {
587
595
  autoResizeVideoAssets: null,
588
596
  estimateVideoCost: false,
589
597
  showBalance: false,
598
+ showVersion: false,
590
599
  angles360Video: null,
591
600
  refImage: null, // Reference image for video (start frame)
592
601
  refImageEnd: null, // End frame for video interpolation
@@ -811,6 +820,8 @@ for (let i = 0; i < args.length; i++) {
811
820
  options.estimateVideoCost = true;
812
821
  } else if (arg === '--balance' || arg === '--balances') {
813
822
  options.showBalance = true;
823
+ } else if (arg === '--version' || arg === '-V') {
824
+ options.showVersion = true;
814
825
  } else if (arg === '--help') {
815
826
  console.log(`
816
827
  sogni-gen - Generate images and videos using Sogni AI
@@ -873,6 +884,7 @@ General:
873
884
  --guidance <num> Override guidance (model-dependent)
874
885
  --token-type <type> Token type: spark|sogni (default: spark)
875
886
  --balance, --balances Show SPARK/SOGNI balances and exit
887
+ --version, -V Show sogni-gen version and exit
876
888
  --last Show last render info (JSON)
877
889
  --json Output JSON with all details
878
890
  --strict-size Do not auto-adjust video size to satisfy i2v reference resizing constraints
@@ -1199,7 +1211,7 @@ if (options.video) {
1199
1211
  options.model = options.model || openclawConfig?.defaultImageModel || 'z_image_turbo_bf16';
1200
1212
  }
1201
1213
 
1202
- if (!options.prompt && !options.estimateVideoCost && !options.multiAngle && !options.showBalance) {
1214
+ if (!options.prompt && !options.estimateVideoCost && !options.multiAngle && !options.showBalance && !options.showVersion) {
1203
1215
  fatalCliError('No prompt provided. Use --help for usage.', { code: 'INVALID_ARGUMENT' });
1204
1216
  }
1205
1217
 
@@ -1452,7 +1464,7 @@ if (options.lastSeed) {
1452
1464
  }
1453
1465
  }
1454
1466
 
1455
- if (!options.estimateVideoCost && (options.seed === null || options.seed === undefined)) {
1467
+ if (!options.estimateVideoCost && !options.showVersion && (options.seed === null || options.seed === undefined)) {
1456
1468
  const strategy = options.seedStrategy || openclawConfig?.seedStrategy || 'prompt-hash';
1457
1469
  const normalized = normalizeSeedStrategy(strategy) || 'prompt-hash';
1458
1470
  options.seedStrategy = normalized;
@@ -2163,6 +2175,21 @@ async function main() {
2163
2175
  let client = null;
2164
2176
 
2165
2177
  try {
2178
+ if (options.showVersion) {
2179
+ if (options.json) {
2180
+ console.log(JSON.stringify({
2181
+ success: true,
2182
+ type: 'version',
2183
+ name: 'sogni-gen',
2184
+ version: PACKAGE_VERSION,
2185
+ timestamp: new Date().toISOString()
2186
+ }));
2187
+ } else {
2188
+ console.log(PACKAGE_VERSION);
2189
+ }
2190
+ return;
2191
+ }
2192
+
2166
2193
  const creds = loadCredentials();
2167
2194
  log('Connecting to Sogni...');
2168
2195
  client = new SogniClientWrapper({