autodev-cli 1.4.151 → 1.4.152

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.
@@ -5,280 +5,379 @@
5
5
  // Source of truth: media/gimp/gimp_mcp_server.py (GPL-3.0; see media/gimp/LICENSE).
6
6
  // Regenerate with: node scripts/gen-gimp-plugin-source.mjs (runs in `npm run build`).
7
7
  //
8
- // This embeds the auto-start GIMP MCP plugin body as a string so the installer
9
- // works even when bundled (VS Code extension esbuild) with no media file on disk.
8
+ // Embeds the user-verified GIMP 3.x MCP server plug-in as a string so the installer works even when bundled
9
+ // (VS Code extension esbuild) with no media file on disk.
10
10
  // ---------------------------------------------------------------------------
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.GIMP_PLUGIN_SOURCE = exports.GIMP_PLUGIN_SOURCE_HASH = void 0;
13
13
  /** sha256 (first 12 hex) of the canonical media/gimp/gimp_mcp_server.py. */
14
- exports.GIMP_PLUGIN_SOURCE_HASH = "93b608412db2";
15
- /** Full body of the auto-start GIMP 3.x MCP plugin (media/gimp/gimp_mcp_server.py). */
14
+ exports.GIMP_PLUGIN_SOURCE_HASH = "c4b85cccece9";
15
+ /** Full body of the user-verified GIMP 3.x MCP server plug-in (media/gimp/gimp_mcp_server.py). */
16
16
  exports.GIMP_PLUGIN_SOURCE = `#!/usr/bin/env python3
17
- # -*- coding: utf-8 -*-
18
- #
19
- # gimp_mcp_server.py — AutoDev auto-start GIMP 3.x MCP bridge
20
- #
21
- # Derived from libreearth/gimp-mcp @49c46dd (GPL-3.0); modified by AutoDev to
22
- # auto-start as a GIMP 3.x PERSISTENT extension. The upstream plugin registered a
23
- # manual "<Image>/Filters/Development/ → Start MCP Server" ImageProcedure that the
24
- # user had to click; this variant registers a PERSISTENT extension so GIMP starts
25
- # the socket server automatically on launch — no menu click, no open image needed.
26
- #
27
- # The socket / command-marshalling logic (start_server, handle_client,
28
- # execute_command, GLib.idle_add onto GIMP's main thread) is kept from upstream.
29
- # ONLY the registration + run() model changed to PERSISTENT + defensive ordering.
30
- #
31
- # ---------------------------------------------------------------------------
32
- # GIMP 3.x plugin layout (REQUIRED): this file must live at
33
- # <gimp-config>/<ver>/plug-ins/gimp_mcp_server/gimp_mcp_server.py
34
- # i.e. inside a sub-folder whose name == this file's basename. A loose .py is
35
- # IGNORED by GIMP 3.x. On Linux/macOS it must ALSO be executable (chmod 0o755).
36
17
  # ---------------------------------------------------------------------------
18
+ # gimp_mcp_server.py — vendored, USER-VERIFIED GIMP 3.2 MCP bridge (plug-in).
37
19
  #
38
- # Copyright (C) 2023 libreearth (upstream, GPL-3.0)
39
- # Modifications Copyright (C) 2025 AutoDev
40
- #
41
- # This program is free software: you can redistribute it and/or modify it under
42
- # the terms of the GNU General Public License as published by the Free Software
43
- # Foundation, either version 3 of the License, or (at your option) any later
44
- # version.
20
+ # Derived from libreearth/gimp-mcp (GPL-3.0; see media/gimp/LICENSE) and fixed by
21
+ # the user / AutoDev for GIMP 3.2 (Store / MSIX). Verified working on real GIMP
22
+ # 3.2.44: a menu-triggered PLUGIN (Tools -> MCP / Filters -> Development -> Start
23
+ # MCP Server) that listens on 127.0.0.1:9876 and drives GIMP. Keep the "GIMP MCP
24
+ # Server" window open — GLib.MainLoop() holds the out-of-process plug-in alive.
45
25
  #
46
- # This program is distributed in the hope that it will be useful, but WITHOUT ANY
47
- # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
48
- # PARTICULAR PURPOSE. See the GNU General Public License for more details.
49
- #
50
- # You should have received a copy of the GNU General Public License along with
51
- # this program. If not, see <https://www.gnu.org/licenses/>. A copy is vendored
52
- # next to this file as LICENSE.
26
+ # This is the user's verified file; the body below is kept byte-for-byte. The
27
+ # lines in THIS block are a provenance comment only — do not edit the code below.
28
+ # ---------------------------------------------------------------------------
53
29
 
54
- import os
55
- import sys
56
- import json
57
- import socket
58
- import threading
59
- import traceback
60
- import datetime
30
+ # GIMP MCP Server Plugin (GIMP 3.2)
31
+ # Listens on localhost:9876 so the libreearth gimp-mcp client can drive GIMP.
61
32
 
62
33
  import gi
63
34
  gi.require_version('Gimp', '3.0')
64
35
  from gi.repository import Gimp
65
36
  from gi.repository import GLib
37
+ from gi.repository import Gio
38
+ from gi.repository import GObject
39
+
40
+ import io
41
+ import json
42
+ import os
43
+ import socket
44
+ import sys
45
+ import threading
46
+ import traceback
66
47
 
48
+ HOST = '127.0.0.1'
49
+ PORT = 9876
50
+ # Store GIMP redirects GLib config dir into the MSIX LocalCache. Also write
51
+ # to the real roaming profile so the log is findable.
52
+ _LOG_PATHS = [
53
+ os.path.join(GLib.get_user_config_dir(), 'GIMP', '3.2', 'gimp_mcp_server.log'),
54
+ os.path.join(os.environ.get('APPDATA', ''), 'GIMP', '3.2', 'gimp_mcp_server.log'),
55
+ ]
67
56
 
68
- # ---------------------------------------------------------------------------
69
- # Logging — an auto-start extension has NO console, so every diagnostic goes to
70
- # a log file the user can read. We resolve it next to this plug-in first (always
71
- # writable if the plug-in loaded at all), falling back to the GIMP config dir.
72
- # Nothing here may raise: a logging failure must never stop GIMP from launching.
73
- # ---------------------------------------------------------------------------
74
- def _log_path():
75
- try:
76
- here = os.path.dirname(os.path.abspath(__file__))
77
- return os.path.join(here, 'gimp-mcp-server.log')
78
- except Exception:
57
+ _server_lock = threading.Lock()
58
+ _server_started = False
59
+ _exec_globals = None
60
+ _main_loop = None
61
+
62
+
63
+ def _log(msg):
64
+ line = msg.rstrip() + '\\n'
65
+ for path in _LOG_PATHS:
66
+ if not path:
67
+ continue
79
68
  try:
80
- return os.path.join(GLib.get_user_config_dir(), 'gimp-mcp-server.log')
69
+ os.makedirs(os.path.dirname(path), exist_ok=True)
70
+ with open(path, 'a', encoding='utf-8') as fh:
71
+ fh.write(line)
81
72
  except Exception:
82
- return os.path.join(os.path.expanduser('~'), 'gimp-mcp-server.log')
73
+ pass
74
+ print(msg, flush=True)
83
75
 
84
76
 
85
- _LOG_FILE = _log_path()
77
+ def N_(message):
78
+ return message
86
79
 
87
80
 
88
- def log(msg):
89
- """Best-effort append to the log file AND stderr. Never raises."""
90
- line = '[%s] %s' % (datetime.datetime.now().isoformat(timespec='seconds'), msg)
91
- try:
92
- with open(_LOG_FILE, 'a', encoding='utf-8') as fh:
93
- fh.write(line + '\\n')
94
- except Exception:
95
- pass
81
+ def _(message):
82
+ return GLib.dgettext(None, message)
83
+
84
+
85
+ def _serialize(obj):
86
+ if obj is None or isinstance(obj, (str, int, float, bool)):
87
+ return obj
88
+ if isinstance(obj, (list, tuple)):
89
+ return [_serialize(item) for item in obj]
90
+ if isinstance(obj, dict):
91
+ return {str(k): _serialize(v) for k, v in obj.items()}
92
+ data = {'type': obj.__class__.__name__}
93
+ if hasattr(obj, 'get_id'):
94
+ try:
95
+ data['id'] = int(obj.get_id())
96
+ except Exception:
97
+ pass
98
+ elif hasattr(obj, 'ID'):
99
+ data['id'] = obj.ID
100
+ if hasattr(obj, 'get_name'):
101
+ try:
102
+ data['name'] = obj.get_name()
103
+ except Exception:
104
+ pass
105
+ if 'id' not in data and len(data) == 1:
106
+ return str(obj)
107
+ return data
108
+
109
+
110
+ def _resolve_api(api_path):
111
+ parts = [p for p in str(api_path).split('.') if p]
112
+ if parts and parts[0] == 'Gimp':
113
+ parts = parts[1:]
114
+ elif len(parts) >= 3 and parts[0] == 'gi' and parts[1] == 'repository' and parts[2] == 'Gimp':
115
+ parts = parts[3:]
116
+
117
+ aliases = {
118
+ 'list_images': 'get_images',
119
+ 'list_fonts': 'get_fonts',
120
+ 'fonts_get_list': 'get_fonts',
121
+ }
122
+
123
+ if not parts:
124
+ return Gimp
125
+
126
+ obj = Gimp
127
+ for part in parts:
128
+ part = aliases.get(part, part)
129
+ obj = getattr(obj, part)
130
+ return obj
131
+
132
+
133
+ def _exec_namespace():
134
+ global _exec_globals
135
+ if _exec_globals is None:
136
+ ns = {
137
+ '__builtins__': __builtins__,
138
+ '__name__': '__main__',
139
+ 'Gimp': Gimp,
140
+ 'GLib': GLib,
141
+ 'Gio': Gio,
142
+ 'GObject': GObject,
143
+ 'sys': sys,
144
+ 'os': os,
145
+ 'json': json,
146
+ }
147
+ try:
148
+ gi.require_version('Gegl', '0.4')
149
+ from gi.repository import Gegl
150
+ ns['Gegl'] = Gegl
151
+ except Exception as exc:
152
+ _log(f'Gegl import failed: {exc}')
153
+ try:
154
+ gi.require_version('Babl', '0.1')
155
+ from gi.repository import Babl
156
+ ns['Babl'] = Babl
157
+ except Exception:
158
+ pass
159
+ _exec_globals = ns
160
+ return _exec_globals
161
+
162
+
163
+ def _extract_code(args, kwargs):
164
+ if kwargs.get('code'):
165
+ return str(kwargs['code'])
166
+ if not args:
167
+ return ''
168
+ if len(args) >= 2 and args[0] in ('exec', 'eval', 'pyGObject-console'):
169
+ payload = args[1]
170
+ if isinstance(payload, (list, tuple)):
171
+ return '\\n'.join(str(item) for item in payload)
172
+ return str(payload)
173
+ if isinstance(args[0], (list, tuple)):
174
+ return '\\n'.join(str(item) for item in args[0])
175
+ return str(args[0])
176
+
177
+
178
+ def _run_python(code):
179
+ ns = _exec_namespace()
180
+ stdout = io.StringIO()
181
+ old_stdout = sys.stdout
182
+ sys.stdout = stdout
96
183
  try:
97
- sys.stderr.write(line + '\\n')
98
- except Exception:
99
- pass
184
+ exec(code, ns)
185
+ finally:
186
+ sys.stdout = old_stdout
187
+ printed = stdout.getvalue()
188
+ result = ns.get('_result')
189
+ if result is not None:
190
+ return {'printed': printed, 'result': _serialize(result)}
191
+ return {'printed': printed, 'result': None}
100
192
 
101
193
 
102
194
  class MCPPlugin(Gimp.PlugIn):
103
- # ── Registration ──────────────────────────────────────────────────────
195
+ def do_set_i18n(self, name):
196
+ return False
197
+
104
198
  def do_query_procedures(self):
105
- return ["plug-in-mcp-server-autostart"]
199
+ return ['plug-in-mcp-server']
106
200
 
107
201
  def do_create_procedure(self, name):
108
- # PERSISTENT + no args + no menu = GIMP auto-starts this extension at
109
- # launch. Adding ANY argument, a menu label, or a menu path would turn
110
- # it back into a user-invoked plug-in and disable auto-start.
111
202
  procedure = Gimp.Procedure.new(
112
- self, name, Gimp.PDBProcType.PERSISTENT, self.run, None,
203
+ self, name, Gimp.PDBProcType.PLUGIN, self.run, None
113
204
  )
114
- try:
115
- procedure.set_documentation(
116
- "AutoDev MCP server (auto-start)",
117
- "Auto-starts a localhost:9876 socket bridge so external MCP "
118
- "clients can drive GIMP. Derived from libreearth/gimp-mcp "
119
- "(GPL-3.0), modified by AutoDev to run as a PERSISTENT extension.",
120
- name,
121
- )
122
- procedure.set_attribution("AutoDev", "AutoDev (GPL-3.0)", "2025")
123
- except Exception as e:
124
- # Attribution/doc setters are cosmetic — never let a binding
125
- # mismatch here prevent the procedure from registering.
126
- log("do_create_procedure: non-fatal setter error: %s" % e)
205
+ procedure.set_menu_label(_('Start MCP Server'))
206
+ procedure.set_documentation(
207
+ _('Starts an MCP server to control GIMP externally'),
208
+ _('Listens on 127.0.0.1:9876 for the GIMP MCP client'),
209
+ name,
210
+ )
211
+ procedure.set_attribution('GIMP MCP', 'GIMP MCP', '2026')
212
+ procedure.set_sensitivity_mask(Gimp.ProcedureSensitivityMask.ALWAYS)
213
+ procedure.add_enum_argument(
214
+ 'run-mode',
215
+ _('Run mode'),
216
+ _('The run mode'),
217
+ Gimp.RunMode,
218
+ Gimp.RunMode.INTERACTIVE,
219
+ GObject.ParamFlags.READWRITE,
220
+ )
221
+ procedure.add_menu_path('<Image>/Tools/MCP')
222
+ procedure.add_menu_path('<Image>/Filters/Development/')
127
223
  return procedure
128
224
 
129
- # ── Extension entry point ─────────────────────────────────────────────
130
- def run(self, procedure, config, run_data):
131
- """PERSISTENT extension entry point.
132
-
133
- DEFENSIVE ORDER (the brick-risk part): call persistent_ready() FIRST.
134
- Its ABSENCE is what hangs GIMP at startup, so it must run before
135
- anything that could throw. Everything after it is wrapped so a bind
136
- failure logs + degrades instead of taking GIMP core down with it.
137
- """
138
- # 1) Hand control back to GIMP core IMMEDIATELY so the UI keeps loading.
139
- try:
140
- procedure.persistent_ready()
141
- log("persistent_ready() OK — extension is live")
142
- except Exception as e:
143
- # A binding mismatch on this exact call is the known lock-up cause;
144
- # log it (do NOT re-raise) so GIMP still finishes launching.
145
- log("persistent_ready() FAILED (non-fatal): %s\\n%s"
146
- % (e, traceback.format_exc()))
147
-
148
- # 2) Start the socket server on a daemon thread. A bind failure (port in
149
- # use, sandbox denial) MUST NOT crash the process or block launch.
225
+ def run(self, procedure, config, data):
226
+ global _main_loop
227
+ started, message = self.ensure_server()
228
+ _log(message)
150
229
  try:
151
- t = threading.Thread(target=self.start_server, daemon=True)
152
- t.start()
153
- log("MCP server thread started (listening on localhost:9876)")
154
- except Exception as e:
155
- log("failed to start MCP server thread (non-fatal): %s\\n%s"
156
- % (e, traceback.format_exc()))
157
-
158
- # 3) Keep this extension process alive for the whole GIMP session so the
159
- # socket stays open and idle_add callbacks can marshal onto core.
230
+ Gimp.message(message)
231
+ except Exception:
232
+ pass
233
+ if not started:
234
+ return procedure.new_return_values(Gimp.PDBStatusType.EXECUTION_ERROR, GLib.Error(message))
235
+
236
+ # GIMP 3 launches Python plug-ins out of process. Returning from run()
237
+ # would exit that process and kill the socket thread. Stay alive and
238
+ # dispatch GLib.idle_add work (API calls) on this main loop.
239
+ # A visible window is required: otherwise "Start MCP Server" looks like
240
+ # it did not open anything.
160
241
  try:
161
- GLib.MainLoop().run()
162
- except Exception as e:
163
- log("main loop exited (non-fatal): %s" % e)
242
+ self._show_status_window(message)
243
+ except Exception as exc:
244
+ _log(f'Status window failed: {exc}\\n{traceback.format_exc()}')
164
245
 
165
- # 4) Standard PERSISTENT success return.
246
+ _main_loop = GLib.MainLoop()
247
+ _log('Entering GLib.MainLoop to keep MCP server process alive')
248
+ try:
249
+ _main_loop.run()
250
+ except Exception as exc:
251
+ _log(f'MainLoop exited: {exc}')
166
252
  return procedure.new_return_values(Gimp.PDBStatusType.SUCCESS, GLib.Error())
167
253
 
168
- # ── Socket server (kept from upstream) ────────────────────────────────
254
+ def _show_status_window(self, message):
255
+ gi.require_version('Gtk', '3.0')
256
+ gi.require_version('GimpUi', '3.0')
257
+ from gi.repository import Gtk
258
+ from gi.repository import GimpUi
259
+
260
+ GimpUi.init('gimp_mcp_server.py')
261
+ window = Gtk.Window(title='GIMP MCP Server')
262
+ window.set_default_size(420, 160)
263
+ window.set_keep_above(True)
264
+ box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
265
+ box.set_border_width(16)
266
+ label = Gtk.Label(label=message + '\\n\\nLeave this window open while Grok talks to GIMP.')
267
+ label.set_line_wrap(True)
268
+ box.pack_start(label, True, True, 0)
269
+ button = Gtk.Button(label='Stop server')
270
+ button.connect('clicked', lambda *_: window.destroy())
271
+ box.pack_start(button, False, False, 0)
272
+ window.add(box)
273
+ window.connect('destroy', self._on_status_closed)
274
+ window.show_all()
275
+ window.present()
276
+ _log('Status window opened')
277
+
278
+ def _on_status_closed(self, *_args):
279
+ global _main_loop
280
+ _log('Status window closed; stopping MCP server')
281
+ if _main_loop is not None:
282
+ _main_loop.quit()
283
+
284
+ def ensure_server(self):
285
+ global _server_started
286
+ with _server_lock:
287
+ if _server_started:
288
+ return True, f'MCP Server already listening on {HOST}:{PORT}'
289
+ try:
290
+ thread = threading.Thread(target=self.start_server, daemon=True)
291
+ thread.start()
292
+ _server_started = True
293
+ return True, f'MCP Server started on {HOST}:{PORT}'
294
+ except Exception as exc:
295
+ return False, f'Failed to start MCP Server: {exc}'
296
+
169
297
  def start_server(self):
170
- host = 'localhost'
171
- port = 9876
172
- sock = None
298
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
299
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
173
300
  try:
174
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
175
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
176
- sock.bind((host, port))
177
- sock.listen(1)
178
- log("server listening on %s:%d" % (host, port))
179
- except Exception as e:
180
- # Bind/listen failed (e.g. port already taken by Blender's MCP or a
181
- # second GIMP). Log + bail out of the thread; GIMP keeps running.
182
- log("bind/listen failed (non-fatal): %s\\n%s" % (e, traceback.format_exc()))
183
- try:
184
- if sock is not None:
185
- sock.close()
186
- except Exception:
187
- pass
301
+ sock.bind((HOST, PORT))
302
+ sock.listen(8)
303
+ _log(f'Server listening on {HOST}:{PORT}')
304
+ except Exception as exc:
305
+ _log(f'Bind/listen failed: {exc}')
188
306
  return
189
307
 
190
308
  while True:
191
309
  try:
192
310
  client, addr = sock.accept()
193
- log("client connected: %s" % (addr,))
194
- threading.Thread(
195
- target=self.handle_client, args=(client,), daemon=True,
196
- ).start()
197
- except Exception as e:
198
- log("accept error: %s" % e)
311
+ _log(f'Connected to {addr}')
312
+ threading.Thread(target=self.handle_client, args=(client,), daemon=True).start()
313
+ except Exception as exc:
314
+ _log(f'Server accept error: {exc}')
199
315
  break
200
- try:
201
- sock.close()
202
- except Exception:
203
- pass
316
+ sock.close()
204
317
 
205
318
  def handle_client(self, client):
206
319
  buffer = b''
207
- while True:
208
- try:
209
- data = client.recv(1024)
210
- if not data:
320
+ try:
321
+ client.settimeout(120)
322
+ while True:
323
+ chunk = client.recv(65536)
324
+ if not chunk:
211
325
  break
212
- buffer += data
213
- try:
214
- command = json.loads(buffer.decode('utf-8'))
215
- buffer = b''
216
- # Marshal onto GIMP's main thread — the PDB is not thread-safe.
326
+ buffer += chunk
327
+ while buffer:
328
+ try:
329
+ command = json.loads(buffer.decode('utf-8'))
330
+ buffer = b''
331
+ except json.JSONDecodeError:
332
+ break
217
333
  GLib.idle_add(self.execute_command, command, client)
218
- except json.JSONDecodeError:
219
- # Partial frame — keep buffering until it parses.
220
- continue
221
- except Exception as e:
222
- log("client error: %s" % e)
223
- break
224
- try:
225
- client.close()
226
- except Exception:
227
- pass
334
+ except Exception as exc:
335
+ _log(f'Client error: {exc}')
336
+ finally:
337
+ try:
338
+ client.close()
339
+ except Exception:
340
+ pass
228
341
 
229
342
  def execute_command(self, command, client):
230
- """Runs on GIMP's main thread via idle_add. Guarded so a bad command or
231
- a wrong binding logs + returns an error frame rather than hanging."""
232
- response = {}
343
+ cmd_type = command.get('type')
344
+ params = command.get('params', {}) or {}
233
345
  try:
234
- cmd_type = command.get("type")
235
- params = command.get("params", {})
236
-
237
- if cmd_type == "call_api":
238
- api_path = params["api_path"]
239
- args = params.get("args", [])
240
- kwargs = params.get("kwargs", {})
241
-
242
- module = Gimp
243
- for part in api_path.split("."):
244
- module = getattr(module, part)
245
-
246
- result = module(*args, **kwargs)
247
-
248
- if hasattr(result, "ID"):
249
- result = {"id": result.ID}
250
- elif isinstance(result, list):
251
- result = [self.serialize_gimp_object(item) for item in result]
346
+ if cmd_type == 'ping':
347
+ response = {'status': 'success', 'result': 'pong'}
348
+ elif cmd_type == 'exec':
349
+ code = _extract_code(params.get('args', []), params.get('kwargs', {}))
350
+ if not code:
351
+ code = str(params.get('code', ''))
352
+ response = {'status': 'success', 'result': _run_python(code)}
353
+ elif cmd_type == 'call_api':
354
+ api_path = params.get('api_path', '')
355
+ args = params.get('args', []) or []
356
+ kwargs = params.get('kwargs', {}) or {}
357
+ if api_path in ('exec', 'eval', '__exec__'):
358
+ response = {'status': 'success', 'result': _run_python(_extract_code(args, kwargs))}
252
359
  else:
253
- result = str(result)
254
-
255
- response = {"status": "success", "result": result}
360
+ method = _resolve_api(api_path)
361
+ result = method(*args, **kwargs)
362
+ response = {'status': 'success', 'result': _serialize(result)}
256
363
  else:
257
- response = {"status": "error", "message": "Unknown command: %s" % cmd_type}
258
- except Exception as e:
259
- response = {"status": "error", "message": str(e),
260
- "traceback": traceback.format_exc()}
364
+ response = {'status': 'error', 'message': f'Unknown command: {cmd_type}'}
365
+ except Exception as exc:
366
+ response = {
367
+ 'status': 'error',
368
+ 'message': str(exc),
369
+ 'traceback': traceback.format_exc(),
370
+ }
371
+ _log(f'Command error: {response["message"]}\\n{response["traceback"]}')
261
372
 
262
373
  try:
263
- client.sendall(json.dumps(response).encode('utf-8'))
264
- except Exception as e:
265
- log("failed to send response: %s" % e)
266
- return False # one-shot idle callback
267
-
268
- def serialize_gimp_object(self, obj):
269
- try:
270
- if hasattr(obj, "ID"):
271
- return {"id": obj.ID, "type": obj.__class__.__name__}
272
- except Exception:
273
- pass
274
- return str(obj)
374
+ payload = json.dumps(response).encode('utf-8')
375
+ client.sendall(payload)
376
+ except Exception as exc:
377
+ _log(f'Response send failed: {exc}')
378
+ return False
275
379
 
276
380
 
277
- try:
278
- Gimp.main(MCPPlugin.__gtype__, sys.argv)
279
- except Exception as _e:
280
- # If GIMP's main dispatch itself fails, log it — never let the plug-in
281
- # process take the launch down with an unhandled traceback.
282
- log("Gimp.main failed: %s\\n%s" % (_e, traceback.format_exc()))
381
+ Gimp.main(MCPPlugin.__gtype__, sys.argv)
283
382
  `;
284
383
  //# sourceMappingURL=gimpPluginSource.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"gimpPluginSource.js","sourceRoot":"","sources":["../../src/core/gimpPluginSource.ts"],"names":[],"mappings":";AAAA,8EAA8E;AAC9E,wDAAwD;AACxD,EAAE;AACF,oFAAoF;AACpF,wFAAwF;AACxF,EAAE;AACF,+EAA+E;AAC/E,kFAAkF;AAClF,8EAA8E;;;AAE9E,4EAA4E;AAC/D,QAAA,uBAAuB,GAAG,cAAc,CAAC;AAEtD,uFAAuF;AAC1E,QAAA,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2QjC,CAAC"}
1
+ {"version":3,"file":"gimpPluginSource.js","sourceRoot":"","sources":["../../src/core/gimpPluginSource.ts"],"names":[],"mappings":";AAAA,8EAA8E;AAC9E,wDAAwD;AACxD,EAAE;AACF,oFAAoF;AACpF,wFAAwF;AACxF,EAAE;AACF,4GAA4G;AAC5G,0DAA0D;AAC1D,8EAA8E;;;AAE9E,4EAA4E;AAC/D,QAAA,uBAAuB,GAAG,cAAc,CAAC;AAEtD,kGAAkG;AACrF,QAAA,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8WjC,CAAC"}