gemini-studio-mcp 0.1.0 → 0.2.0

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
@@ -68,6 +68,8 @@ Get a key at **https://aistudio.google.com/apikey** — then restart your client
68
68
  | `edit_image` | Edit a stored image by `id` with a text instruction. |
69
69
  | `iterate` | Refine the **last** (or a given) image in words — _"warmer", "different angle", "same character"_. |
70
70
  | `generate_video` | Text → video (Veo). v1 waits for the render to finish. |
71
+ | `save_preset` / `list_presets` | Save and reuse named style presets. |
72
+ | `gallery` / `show_asset` | Browse past generations by metadata and re-open any by id. |
71
73
 
72
74
  ### Example conversation
73
75
 
@@ -87,6 +89,8 @@ Get a key at **https://aistudio.google.com/apikey** — then restart your client
87
89
  | `GEMINI_EDIT_MODEL` | Edit / iterate model | `gemini-3-pro-image-preview` |
88
90
  | `GEMINI_VIDEO_MODEL` | Video model | `veo-3.1-generate-preview` |
89
91
 
92
+ **Cost awareness:** image tools report an estimated price; `generate_video` is paid and returns an estimate first — pass `confirm: true` to actually render.
93
+
90
94
  > **Tip:** when launched via `npx` from a desktop client, the process working directory may be non-obvious — set `GEMINI_STUDIO_DIR` to an absolute path so you can always find your output.
91
95
 
92
96
  > **Prompt tip:** Imagen follows **English** prompts more reliably than other languages.
@@ -116,9 +120,9 @@ Tested against the live Gemini API: ✅ `generate_image` (Imagen 4) · ✅ `edit
116
120
 
117
121
  ## Roadmap
118
122
 
119
- - **Now (v1):** generate · edit · iterate · video, with asset lineage.
120
- - **Next:** style presets, a gallery tool, and cost shown before each generation.
121
- - **Later:** non-blocking / resumable video jobs with progress.
123
+ - **v0.1 — core:** generate · edit · iterate · video, with asset lineage.
124
+ - **v0.2 — studio (current):** style presets, gallery & `show_asset`, cost estimates (with a `confirm` gate on paid video).
125
+ - **Next:** non-blocking / resumable video jobs with progress.
122
126
 
123
127
  ## Development
124
128
 
@@ -0,0 +1,31 @@
1
+ // Приблизительные тарифы, на 2026-05. Источники: ai.google.dev/gemini-api/docs/pricing.
2
+ const IMAGE_USD = {
3
+ 'imagen-4.0-generate-001': 0.04,
4
+ 'gemini-3-pro-image-preview': 0.06,
5
+ };
6
+ const VIDEO_USD_PER_SEC = {
7
+ 'veo-3.1-generate-preview': 0.4,
8
+ };
9
+ const DEFAULT_VIDEO_SECONDS = 8;
10
+ function fmt(usd) {
11
+ return `$${usd.toFixed(2)}`;
12
+ }
13
+ export function estimateCost(opts) {
14
+ if (opts.kind === 'image') {
15
+ const per = IMAGE_USD[opts.model];
16
+ const count = opts.count ?? 1;
17
+ if (per === undefined) {
18
+ return { usd: 0, text: `стоимость неизвестна для модели ${opts.model}` };
19
+ }
20
+ const usd = per * count;
21
+ return { usd, text: `~${fmt(usd)} (${count} × ${fmt(per)})` };
22
+ }
23
+ const perSec = VIDEO_USD_PER_SEC[opts.model];
24
+ const seconds = opts.seconds ?? DEFAULT_VIDEO_SECONDS;
25
+ if (perSec === undefined) {
26
+ return { usd: 0, text: `стоимость неизвестна для модели ${opts.model}` };
27
+ }
28
+ const usd = perSec * seconds;
29
+ return { usd, text: `~${fmt(usd)} (${seconds}s × ${fmt(perSec)}/s)` };
30
+ }
31
+ //# sourceMappingURL=pricing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pricing.js","sourceRoot":"","sources":["../../src/cost/pricing.ts"],"names":[],"mappings":"AAKA,wFAAwF;AACxF,MAAM,SAAS,GAA2B;IACxC,yBAAyB,EAAE,IAAI;IAC/B,4BAA4B,EAAE,IAAI;CACnC,CAAC;AACF,MAAM,iBAAiB,GAA2B;IAChD,0BAA0B,EAAE,GAAG;CAChC,CAAC;AACF,MAAM,qBAAqB,GAAG,CAAC,CAAC;AAEhC,SAAS,GAAG,CAAC,GAAW;IACtB,OAAO,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAK5B;IACC,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;QAC9B,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACtB,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,mCAAmC,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;QAC3E,CAAC;QACD,MAAM,GAAG,GAAG,GAAG,GAAG,KAAK,CAAC;QACxB,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,MAAM,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;IAChE,CAAC;IACD,MAAM,MAAM,GAAG,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,qBAAqB,CAAC;IACtD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,mCAAmC,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;IAC3E,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,GAAG,OAAO,CAAC;IAC7B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,OAAO,OAAO,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;AACxE,CAAC"}
package/dist/server.js CHANGED
@@ -4,25 +4,31 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
4
4
  import { loadConfig } from './config.js';
5
5
  import { RealGeminiClient } from './gemini/client.js';
6
6
  import { AssetStore } from './store/assetStore.js';
7
+ import { PresetStore } from './store/presetStore.js';
7
8
  import { Session } from './session/session.js';
8
9
  import { registerGenerateImage } from './tools/generateImage.js';
9
10
  import { registerEditImage } from './tools/editImage.js';
10
11
  import { registerIterate } from './tools/iterate.js';
11
12
  import { registerGenerateVideo } from './tools/generateVideo.js';
13
+ import { registerPresetTools } from './tools/presets.js';
14
+ import { registerGalleryTools } from './tools/gallery.js';
12
15
  export async function startServer() {
13
16
  const config = loadConfig();
14
17
  const ai = new GoogleGenAI({ apiKey: config.apiKey });
15
18
  const deps = {
16
19
  client: new RealGeminiClient(ai),
17
20
  store: new AssetStore(config.workingDir),
21
+ presets: new PresetStore(config.workingDir),
18
22
  session: new Session(),
19
23
  config,
20
24
  };
21
- const server = new McpServer({ name: 'gemini-studio-mcp', version: '0.1.0' });
25
+ const server = new McpServer({ name: 'gemini-studio-mcp', version: '0.2.0' });
22
26
  registerGenerateImage(server, deps);
23
27
  registerEditImage(server, deps);
24
28
  registerIterate(server, deps);
25
29
  registerGenerateVideo(server, deps);
30
+ registerPresetTools(server, deps);
31
+ registerGalleryTools(server, deps);
26
32
  const transport = new StdioServerTransport();
27
33
  await server.connect(transport);
28
34
  }
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAE/C,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAEjE,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,EAAE,GAAG,IAAI,WAAW,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACtD,MAAM,IAAI,GAAa;QACrB,MAAM,EAAE,IAAI,gBAAgB,CAAC,EAAS,CAAC;QACvC,KAAK,EAAE,IAAI,UAAU,CAAC,MAAM,CAAC,UAAU,CAAC;QACxC,OAAO,EAAE,IAAI,OAAO,EAAE;QACtB,MAAM;KACP,CAAC;IAEF,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IAC9E,qBAAqB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACpC,iBAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAChC,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC9B,qBAAqB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAEpC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAE/C,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE1D,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,EAAE,GAAG,IAAI,WAAW,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACtD,MAAM,IAAI,GAAa;QACrB,MAAM,EAAE,IAAI,gBAAgB,CAAC,EAAS,CAAC;QACvC,KAAK,EAAE,IAAI,UAAU,CAAC,MAAM,CAAC,UAAU,CAAC;QACxC,OAAO,EAAE,IAAI,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC;QAC3C,OAAO,EAAE,IAAI,OAAO,EAAE;QACtB,MAAM;KACP,CAAC;IAEF,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IAC9E,qBAAqB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACpC,iBAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAChC,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC9B,qBAAqB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACpC,mBAAmB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAClC,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAEnC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC"}
@@ -0,0 +1,34 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import path from 'node:path';
3
+ export class PresetStore {
4
+ rootDir;
5
+ queue = Promise.resolve();
6
+ constructor(rootDir) {
7
+ this.rootDir = rootDir;
8
+ }
9
+ file() { return path.join(this.rootDir, 'presets.json'); }
10
+ async list() {
11
+ try {
12
+ return JSON.parse(await fs.readFile(this.file(), 'utf8'));
13
+ }
14
+ catch {
15
+ return [];
16
+ }
17
+ }
18
+ async get(name) {
19
+ const found = (await this.list()).find((p) => p.name === name);
20
+ return found ?? null;
21
+ }
22
+ save(preset) {
23
+ const run = this.queue.then(() => this.doSave(preset));
24
+ this.queue = run.catch(() => { });
25
+ return run;
26
+ }
27
+ async doSave(preset) {
28
+ await fs.mkdir(this.rootDir, { recursive: true });
29
+ const list = await this.list();
30
+ const next = [...list.filter((p) => p.name !== preset.name), preset];
31
+ await fs.writeFile(this.file(), JSON.stringify(next, null, 2));
32
+ }
33
+ }
34
+ //# sourceMappingURL=presetStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presetStore.js","sourceRoot":"","sources":["../../src/store/presetStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAO7B,MAAM,OAAO,WAAW;IAEO;IADrB,KAAK,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;IACjD,YAA6B,OAAe;QAAf,YAAO,GAAP,OAAO,CAAQ;IAAG,CAAC;IAExC,IAAI,KAAa,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;IAE1E,KAAK,CAAC,IAAI;QACR,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,MAAM,CAAC,CAAa,CAAC;QACxE,CAAC;QAAC,MAAM,CAAC;YAAC,OAAO,EAAE,CAAC;QAAC,CAAC;IACxB,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,IAAY;QACpB,MAAM,KAAK,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;QAC/D,OAAO,KAAK,IAAI,IAAI,CAAC;IACvB,CAAC;IAED,IAAI,CAAC,MAAc;QACjB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QACvD,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACjC,OAAO,GAAG,CAAC;IACb,CAAC;IAEO,KAAK,CAAC,MAAM,CAAC,MAAc;QACjC,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;QACrE,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACjE,CAAC;CACF"}
@@ -2,6 +2,7 @@ import { randomUUID } from 'node:crypto';
2
2
  import { z } from 'zod';
3
3
  import { imageResult, errorResult } from './result.js';
4
4
  import { extFromMime } from '../util/mime.js';
5
+ import { estimateCost } from '../cost/pricing.js';
5
6
  export const editImageInput = {
6
7
  id: z.string().describe('id ранее сгенерированного изображения'),
7
8
  prompt: z.string().describe('Что изменить'),
@@ -25,15 +26,14 @@ export async function editImageHandler(input, deps) {
25
26
  };
26
27
  await deps.store.save(manifest, media.data);
27
28
  deps.session.setLastImageId(id);
28
- return imageResult(media, `Готово. id=${id} (правка ${source.id}), файл: ${manifest.file}.`);
29
+ const cost = estimateCost({ kind: 'image', model });
30
+ return imageResult(media, `Готово. id=${id} (правка ${source.id}), файл: ${manifest.file}. Стоимость: ${cost.text}.`);
29
31
  }
30
32
  catch (err) {
31
33
  return errorResult(err);
32
34
  }
33
35
  }
34
36
  export function registerEditImage(server, deps) {
35
- server.registerTool('edit_image', { title: 'Edit image', description: 'Отредактировать сохранённое изображение по его id и текстовой инструкции.', inputSchema: editImageInput },
36
- // cast: handler returns our CallToolResult; SDK expects its own index-signature variant (runtime-compatible).
37
- (input) => editImageHandler(input, deps));
37
+ server.registerTool('edit_image', { title: 'Edit image', description: 'Отредактировать сохранённое изображение по его id и текстовой инструкции.', inputSchema: editImageInput }, (input) => editImageHandler(input, deps));
38
38
  }
39
39
  //# sourceMappingURL=editImage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"editImage.js","sourceRoot":"","sources":["../../src/tools/editImage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,WAAW,EAAE,WAAW,EAAuB,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAG9C,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC;IAChE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;IAC3C,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC7B,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,KAAqD,EACrD,IAAc;IAEd,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC9C,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACnD,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,KAAK,CAAC,EAAE,eAAe,CAAC,CAAC;QACrF,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC;QACnD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC;YACxC,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE;SAC/E,CAAC,CAAC;QACH,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACpC,MAAM,QAAQ,GAAkB;YAC9B,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK;YACnE,IAAI,EAAE,GAAG,EAAE,IAAI,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACtE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAC;QACF,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAC5C,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;QAChC,OAAO,WAAW,CAAC,KAAK,EAAE,cAAc,EAAE,YAAY,MAAM,CAAC,EAAE,YAAY,QAAQ,CAAC,IAAI,GAAG,CAAC,CAAC;IAC/F,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAAiB,EAAE,IAAc;IACjE,MAAM,CAAC,YAAY,CACjB,YAAY,EACZ,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,2EAA2E,EAAE,WAAW,EAAE,cAAc,EAAE;IAC9I,8GAA8G;IAC9G,CAAC,KAAK,EAAE,EAAE,CAAC,gBAAgB,CAAC,KAAK,EAAE,IAAI,CAAQ,CAChD,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"editImage.js","sourceRoot":"","sources":["../../src/tools/editImage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import { z } from 'zod';
2
+ import { imageResult, errorResult } from './result.js';
3
+ export const galleryInput = {
4
+ limit: z.number().int().positive().optional().describe('Сколько последних показать (по умолчанию 20)'),
5
+ };
6
+ export async function galleryHandler(input, deps) {
7
+ const all = await deps.store.list();
8
+ if (all.length === 0) {
9
+ return { content: [{ type: 'text', text: 'Галерея пуста — ещё ничего не сгенерировано.' }] };
10
+ }
11
+ const sorted = [...all].sort((a, b) => b.createdAt.localeCompare(a.createdAt));
12
+ const limit = input.limit ?? 20;
13
+ const lines = sorted.slice(0, limit).map((m) => {
14
+ const parent = m.parentId ? ` ← ${m.parentId}` : '';
15
+ return `• ${m.id} [${m.kind}] "${m.prompt}" (${m.model})${parent} — ${m.createdAt}`;
16
+ });
17
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
18
+ }
19
+ export const showAssetInput = {
20
+ id: z.string().describe('id ассета из галереи'),
21
+ };
22
+ export async function showAssetHandler(input, deps) {
23
+ try {
24
+ const manifest = await deps.store.get(input.id);
25
+ const bytes = await deps.store.readBytes(input.id);
26
+ if (!manifest || !bytes)
27
+ throw new Error(`Ассет с id="${input.id}" не найден.`);
28
+ return imageResult({ data: bytes, mimeType: manifest.mimeType }, `${manifest.id} [${manifest.kind}] "${manifest.prompt}" — файл: ${manifest.file}`);
29
+ }
30
+ catch (err) {
31
+ return errorResult(err);
32
+ }
33
+ }
34
+ export function registerGalleryTools(server, deps) {
35
+ server.registerTool('gallery', { title: 'Gallery', description: 'Показать список прошлых генераций с метаданными (id, промпт, модель, родитель, дата).', inputSchema: galleryInput }, (input) => galleryHandler(input, deps));
36
+ server.registerTool('show_asset', { title: 'Show asset', description: 'Показать сохранённый ассет (изображение или видео) по его id.', inputSchema: showAssetInput }, (input) => showAssetHandler(input, deps));
37
+ }
38
+ //# sourceMappingURL=gallery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gallery.js","sourceRoot":"","sources":["../../src/tools/gallery.ts"],"names":[],"mappings":"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"}
@@ -2,31 +2,39 @@ import { randomUUID } from 'node:crypto';
2
2
  import { z } from 'zod';
3
3
  import { imageResult, errorResult } from './result.js';
4
4
  import { extFromMime } from '../util/mime.js';
5
+ import { estimateCost } from '../cost/pricing.js';
5
6
  export const generateImageInput = {
6
7
  prompt: z.string().describe('Что нарисовать'),
7
8
  model: z.string().optional().describe('Переопределить модель (по умолчанию Imagen 4)'),
9
+ preset: z.string().optional().describe('Имя сохранённого пресета стиля (его стиль добавится к промпту)'),
8
10
  };
9
11
  export async function generateImageHandler(input, deps) {
10
12
  try {
13
+ let prompt = input.prompt;
14
+ if (input.preset) {
15
+ const preset = await deps.presets.get(input.preset);
16
+ if (!preset)
17
+ throw new Error(`Пресет "${input.preset}" не найден.`);
18
+ prompt = `${prompt}, ${preset.style}`;
19
+ }
11
20
  const model = input.model ?? deps.config.defaultImageModel;
12
- const media = await deps.client.generateImage({ prompt: input.prompt, model });
21
+ const media = await deps.client.generateImage({ prompt, model });
13
22
  const id = randomUUID().slice(0, 8);
14
23
  const manifest = {
15
- id, parentId: null, kind: 'image', prompt: input.prompt, model,
24
+ id, parentId: null, kind: 'image', prompt, model,
16
25
  file: `${id}.${extFromMime(media.mimeType)}`, mimeType: media.mimeType,
17
26
  createdAt: new Date().toISOString(),
18
27
  };
19
28
  await deps.store.save(manifest, media.data);
20
29
  deps.session.setLastImageId(id);
21
- return imageResult(media, `Готово. id=${id}, файл: ${manifest.file} ${deps.config.workingDir}).`);
30
+ const cost = estimateCost({ kind: 'image', model });
31
+ return imageResult(media, `Готово. id=${id}, файл: ${manifest.file} (в ${deps.config.workingDir}). Стоимость: ${cost.text}.`);
22
32
  }
23
33
  catch (err) {
24
34
  return errorResult(err);
25
35
  }
26
36
  }
27
37
  export function registerGenerateImage(server, deps) {
28
- server.registerTool('generate_image', { title: 'Generate image', description: 'Сгенерировать изображение по тексту через Gemini (Imagen 4).', inputSchema: generateImageInput },
29
- // cast: handler returns our CallToolResult; SDK expects its own index-signature variant (runtime-compatible).
30
- (input) => generateImageHandler(input, deps));
38
+ server.registerTool('generate_image', { title: 'Generate image', description: 'Сгенерировать изображение по тексту через Gemini (Imagen 4). Можно указать preset стиля.', inputSchema: generateImageInput }, (input) => generateImageHandler(input, deps));
31
39
  }
32
40
  //# sourceMappingURL=generateImage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"generateImage.js","sourceRoot":"","sources":["../../src/tools/generateImage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,WAAW,EAAE,WAAW,EAAuB,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAG9C,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC;IAC7C,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+CAA+C,CAAC;CACvF,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,KAAyC,EACzC,IAAc;IAEd,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC;QAC3D,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QAC/E,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACpC,MAAM,QAAQ,GAAkB;YAC9B,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK;YAC9D,IAAI,EAAE,GAAG,EAAE,IAAI,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACtE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAC;QACF,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAC5C,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;QAChC,OAAO,WAAW,CAAC,KAAK,EAAE,cAAc,EAAE,WAAW,QAAQ,CAAC,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU,IAAI,CAAC,CAAC;IACvG,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;AACH,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,MAAiB,EAAE,IAAc;IACrE,MAAM,CAAC,YAAY,CACjB,gBAAgB,EAChB,EAAE,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,8DAA8D,EAAE,WAAW,EAAE,kBAAkB,EAAE;IACzI,8GAA8G;IAC9G,CAAC,KAAK,EAAE,EAAE,CAAC,oBAAoB,CAAC,KAAK,EAAE,IAAI,CAAQ,CACpD,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"generateImage.js","sourceRoot":"","sources":["../../src/tools/generateImage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,WAAW,EAAE,WAAW,EAAuB,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC;IAC7C,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+CAA+C,CAAC;IACtF,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gEAAgE,CAAC;CACzG,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,KAA0D,EAC1D,IAAc;IAEd,IAAI,CAAC;QACH,IAAI,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;QAC1B,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;YACjB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YACpD,IAAI,CAAC,MAAM;gBAAE,MAAM,IAAI,KAAK,CAAC,WAAW,KAAK,CAAC,MAAM,cAAc,CAAC,CAAC;YACpE,MAAM,GAAG,GAAG,MAAM,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC;QACxC,CAAC;QACD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC;QAC3D,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QACjE,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACpC,MAAM,QAAQ,GAAkB;YAC9B,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK;YAChD,IAAI,EAAE,GAAG,EAAE,IAAI,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACtE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAC;QACF,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAC5C,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;QAChC,MAAM,IAAI,GAAG,YAAY,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;QACpD,OAAO,WAAW,CAAC,KAAK,EAAE,cAAc,EAAE,WAAW,QAAQ,CAAC,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU,iBAAiB,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;IAChI,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;AACH,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,MAAiB,EAAE,IAAc;IACrE,MAAM,CAAC,YAAY,CACjB,gBAAgB,EAChB,EAAE,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,0FAA0F,EAAE,WAAW,EAAE,kBAAkB,EAAE,EACrK,CAAC,KAAK,EAAE,EAAE,CAAC,oBAAoB,CAAC,KAAK,EAAE,IAAI,CAAQ,CACpD,CAAC;AACJ,CAAC"}
@@ -2,13 +2,19 @@ import { randomUUID } from 'node:crypto';
2
2
  import { z } from 'zod';
3
3
  import { imageResult, errorResult } from './result.js';
4
4
  import { extFromMime } from '../util/mime.js';
5
+ import { estimateCost } from '../cost/pricing.js';
5
6
  export const generateVideoInput = {
6
7
  prompt: z.string().describe('Что снять'),
7
8
  model: z.string().optional(),
9
+ confirm: z.boolean().optional().describe('Подтверждение генерации видео (платно). Без него вернётся только смета.'),
8
10
  };
9
11
  export async function generateVideoHandler(input, deps) {
12
+ const model = input.model ?? deps.config.videoModel;
13
+ const cost = estimateCost({ kind: 'video', model });
14
+ if (!input.confirm) {
15
+ return { content: [{ type: 'text', text: `Видео через ${model} стоит ${cost.text}. Это платно. Чтобы сгенерировать, вызови ещё раз с confirm: true.` }] };
16
+ }
10
17
  try {
11
- const model = input.model ?? deps.config.videoModel;
12
18
  const media = await deps.client.generateVideo({ prompt: input.prompt, model });
13
19
  const id = randomUUID().slice(0, 8);
14
20
  const manifest = {
@@ -17,15 +23,13 @@ export async function generateVideoHandler(input, deps) {
17
23
  createdAt: new Date().toISOString(),
18
24
  };
19
25
  await deps.store.save(manifest, media.data);
20
- return imageResult(media, `Видео готово. id=${id}, файл: ${manifest.file} (в ${deps.config.workingDir}).`);
26
+ return imageResult(media, `Видео готово. id=${id}, файл: ${manifest.file} (в ${deps.config.workingDir}). Стоимость: ${cost.text}.`);
21
27
  }
22
28
  catch (err) {
23
29
  return errorResult(err);
24
30
  }
25
31
  }
26
32
  export function registerGenerateVideo(server, deps) {
27
- server.registerTool('generate_video', { title: 'Generate video', description: 'Сгенерировать видео по тексту через Gemini (Veo). v1 ждёт завершения рендера.', inputSchema: generateVideoInput },
28
- // cast: handler returns our CallToolResult; SDK expects its own index-signature variant (runtime-compatible).
29
- (input) => generateVideoHandler(input, deps));
33
+ server.registerTool('generate_video', { title: 'Generate video', description: 'Сгенерировать видео по тексту через Gemini (Veo). Платно: без confirm:true вернёт смету. v1 ждёт завершения рендера.', inputSchema: generateVideoInput }, (input) => generateVideoHandler(input, deps));
30
34
  }
31
35
  //# sourceMappingURL=generateVideo.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"generateVideo.js","sourceRoot":"","sources":["../../src/tools/generateVideo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,WAAW,EAAE,WAAW,EAAuB,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAG9C,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC;IACxC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC7B,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,KAAyC,EACzC,IAAc;IAEd,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC;QACpD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QAC/E,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACpC,MAAM,QAAQ,GAAkB;YAC9B,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK;YAC9D,IAAI,EAAE,GAAG,EAAE,IAAI,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACtE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAC;QACF,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAC5C,OAAO,WAAW,CAAC,KAAK,EAAE,oBAAoB,EAAE,WAAW,QAAQ,CAAC,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU,IAAI,CAAC,CAAC;IAC7G,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;AACH,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,MAAiB,EAAE,IAAc;IACrE,MAAM,CAAC,YAAY,CACjB,gBAAgB,EAChB,EAAE,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,+EAA+E,EAAE,WAAW,EAAE,kBAAkB,EAAE;IAC1J,8GAA8G;IAC9G,CAAC,KAAK,EAAE,EAAE,CAAC,oBAAoB,CAAC,KAAK,EAAE,IAAI,CAAQ,CACpD,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"generateVideo.js","sourceRoot":"","sources":["../../src/tools/generateVideo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,WAAW,EAAE,WAAW,EAAuB,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC;IACxC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,yEAAyE,CAAC;CACpH,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,KAA4D,EAC5D,IAAc;IAEd,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC;IACpD,MAAM,IAAI,GAAG,YAAY,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IACpD,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;QACnB,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,KAAK,UAAU,IAAI,CAAC,IAAI,oEAAoE,EAAE,CAAC,EAAE,CAAC;IAC5J,CAAC;IACD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QAC/E,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACpC,MAAM,QAAQ,GAAkB;YAC9B,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK;YAC9D,IAAI,EAAE,GAAG,EAAE,IAAI,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACtE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAC;QACF,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAC5C,OAAO,WAAW,CAAC,KAAK,EAAE,oBAAoB,EAAE,WAAW,QAAQ,CAAC,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU,iBAAiB,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;IACtI,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;AACH,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,MAAiB,EAAE,IAAc;IACrE,MAAM,CAAC,YAAY,CACjB,gBAAgB,EAChB,EAAE,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,sHAAsH,EAAE,WAAW,EAAE,kBAAkB,EAAE,EACjM,CAAC,KAAK,EAAE,EAAE,CAAC,oBAAoB,CAAC,KAAK,EAAE,IAAI,CAAQ,CACpD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { z } from 'zod';
2
+ import { errorResult } from './result.js';
3
+ export const savePresetInput = {
4
+ name: z.string().describe('Имя пресета'),
5
+ style: z.string().describe('Описание стиля, которое подмешивается в промпт'),
6
+ };
7
+ export async function savePresetHandler(input, deps) {
8
+ try {
9
+ await deps.presets.save({ name: input.name, style: input.style });
10
+ return { content: [{ type: 'text', text: `Пресет "${input.name}" сохранён.` }] };
11
+ }
12
+ catch (err) {
13
+ return errorResult(err);
14
+ }
15
+ }
16
+ export async function listPresetsHandler(_input, deps) {
17
+ const presets = await deps.presets.list();
18
+ if (presets.length === 0) {
19
+ return { content: [{ type: 'text', text: 'Пресетов пока нет. Создай через save_preset.' }] };
20
+ }
21
+ const lines = presets.map((p) => `• ${p.name}: ${p.style}`).join('\n');
22
+ return { content: [{ type: 'text', text: lines }] };
23
+ }
24
+ export function registerPresetTools(server, deps) {
25
+ server.registerTool('save_preset', { title: 'Save style preset', description: 'Сохранить именованный пресет стиля для повторного использования в генерации.', inputSchema: savePresetInput }, (input) => savePresetHandler(input, deps));
26
+ server.registerTool('list_presets', { title: 'List style presets', description: 'Показать сохранённые пресеты стиля.', inputSchema: {} }, () => listPresetsHandler({}, deps));
27
+ }
28
+ //# sourceMappingURL=presets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presets.js","sourceRoot":"","sources":["../../src/tools/presets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,WAAW,EAAuB,MAAM,aAAa,CAAC;AAE/D,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC;IACxC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,gDAAgD,CAAC;CAC7E,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,KAAsC,EACtC,IAAc;IAEd,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;QAClE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,KAAK,CAAC,IAAI,aAAa,EAAE,CAAC,EAAE,CAAC;IACnF,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAA6B,EAAE,IAAc;IACpF,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;IAC1C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,8CAA8C,EAAE,CAAC,EAAE,CAAC;IAC/F,CAAC;IACD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,MAAiB,EAAE,IAAc;IACnE,MAAM,CAAC,YAAY,CACjB,aAAa,EACb,EAAE,KAAK,EAAE,mBAAmB,EAAE,WAAW,EAAE,8EAA8E,EAAE,WAAW,EAAE,eAAe,EAAE,EACzJ,CAAC,KAAK,EAAE,EAAE,CAAC,iBAAiB,CAAC,KAAK,EAAE,IAAI,CAAQ,CACjD,CAAC;IACF,MAAM,CAAC,YAAY,CACjB,cAAc,EACd,EAAE,KAAK,EAAE,oBAAoB,EAAE,WAAW,EAAE,qCAAqC,EAAE,WAAW,EAAE,EAAE,EAAE,EACpG,GAAG,EAAE,CAAC,kBAAkB,CAAC,EAAE,EAAE,IAAI,CAAQ,CAC1C,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gemini-studio-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "MCP server for iterative image & video generation via the Gemini API (Imagen 4, Gemini 3 Pro Image, Veo) — generate and refine in plain words, with lineage.",
5
5
  "type": "module",
6
6
  "bin": { "gemini-studio-mcp": "dist/index.js" },