SLOPSHOPPER

image

An image tool for the agent: make or edit pictures through a pluggable engine (default: the official OpenAI Images API). Room to grow into video.

newguardtoolprocess
★ 9v0.1.0MITupdated 2026-10-09nekyialabs/claude-code-toolkit/mods/image
A shopper browsing a rack in a slop shop
README

image

An image tool for the agent (mcp__image__image). It can make a picture from a prompt, or edit and compose from up to eight reference images, and returns the path of the saved PNG so the agent can Read it and look at the result. The shape leaves room for a video tool beside it later.

The engine is bin/imagegen.py and it is pluggable.

  • Default engine: the official OpenAI Images API. Set OPENAI_API_KEY. Plain generation uses /v1/images/generations; with reference images it uses /v1/images/edits. This is billed to your OpenAI account per image.
  • Your own engine: set IMAGE_ENGINE=module:/absolute/path/to/engine.py, with one function generate(prompt, size, quality, model, refs) -> bytes. See engines/README.md and engines/example_engine.py.

Note: the default engine was written against the documented API shape and has been exercised against a local stand-in server, but not against a live OpenAI account in this release. If the API rejects a parameter for your chosen model, set IMAGE_MODEL or use a custom engine.

Our own copy runs on a ChatGPT subscription through OAuth, an approach we took from Hermes Agent (https://github.com/NousResearch/hermes-agent). If you want to try that route, see how Hermes Agent does it. That code is not part of this mod.

Install

  1. Copy this folder somewhere permanent, for example ~/.claude/mods/image.
  2. Load it: set CLAUDE_CODE_PLUGIN_DIRS=/path/to/image (colon-separate several) in the environment that starts Claude Code, or pass --plugin-dir /path/to/image.
  3. Restart Claude Code. Tools register at session start, so a full restart is needed.

Requires python3 on the path (standard library only).

Configure

Environment variables, set in the environment that starts Claude Code:

VariableMeaningDefault
OPENAI_API_KEYkey for the default enginenone (required for it)
IMAGE_ENGINEopenai, or module:/path/to/engine.pyopenai
IMAGE_MODELmodel name passed to the enginegpt-image-1
IMAGE_OUT_DIRwhere PNGs are saved~/generated-images
IMAGE_API_BASEbase URL of an OpenAI-compatible APIhttps://api.openai.com/v1

Keep the key in your shell profile or a secrets manager, not in this folder.

Validate

claude plugin validate .

Caveats

Mods (function hooks) are an early-access feature of Claude Code and may change between releases without notice. Claude Code writes the type declarations into .claude-plugin/types/ itself when it loads the mod, so they are not included here. Image generation costs money on most backends; the agent can call it freely, so consider telling it when to.

Source 1 files
hooks/register.ts 51 lines
1import type { Register } from 'claude-code'
2
3// image: the agent's image tool. The engine is bin/imagegen.py, which is pluggable
4// (default: the official OpenAI Images API with OPENAI_API_KEY; see README.md and engines/README.md).
5// Shaped so a `video` tool can join later beside `image`.
6const SIZES = ['1024x1024', '1536x1024', '1024x1536', 'auto']
7
8export const register: Register = on => {
9  on('session.start', async ($, e, next) => {
10    await $.tool.register({
11      name: 'image',
12      description: "Make a picture (or edit one, given reference images) through the configured image engine. Returns the saved PNG path; Read it to see the result. Can take up to a few minutes.",
13      inputSchema: {
14        type: 'object',
15        properties: {
16          prompt: { type: 'string', description: 'The full prompt: style, subject, scene, light, composition, and what to avoid.' },
17          size: { type: 'string', enum: SIZES, description: 'Default 1536x1024 (landscape). 1024x1536 portrait, 1024x1024 square.' },
18          quality: { type: 'string', enum: ['low', 'medium', 'high'], description: 'Default high.' },
19          refs: { type: 'array', items: { type: 'string' }, description: 'Optional reference images (local paths or https URLs, up to 8). With refs, the call edits/composes from them.' },
20          name: { type: 'string', description: 'Optional short slug for the file name.' },
21        },
22        required: ['prompt'],
23      },
24    })
25    return next(e)
26  })
27
28  on('tool.call', { tool: 'mcp__image__image' }, async ($, e) => {
29    const i = e as unknown as { prompt?: string; size?: string; quality?: string; refs?: string[]; name?: string }
30    const engine = `${$.plugin.root}/bin/imagegen.py`
31    const argv = ['python3', engine, '--prompt', String(i.prompt ?? '')]
32    if (i.size && SIZES.includes(i.size)) argv.push('--size', i.size)
33    if (i.quality) argv.push('--quality', String(i.quality))
34    if (i.name) argv.push('--name', String(i.name))
35    for (const r of (Array.isArray(i.refs) ? i.refs : []).slice(0, 8)) argv.push('--ref', String(r))
36    try {
37      const r = await $.process.run(argv, { timeoutMs: 330000 })
38      const line = r.stdout.trim().split('\n').pop() ?? ''
39      try {
40        const out = JSON.parse(line) as { ok: boolean; path?: string; error?: string; size?: string; mode?: string; model_requested?: string }
41        if (out.ok) return { result: `image saved: ${out.path} (${out.mode}, ${out.size}, model ${out.model_requested}). Read the file to see it.` }
42        return { result: `image failed: ${out.error ?? 'unknown error'}` }
43      } catch {
44        return { result: `image failed: exit ${r.exitCode}: ${(r.stderr || line).trim().slice(0, 300)}` }
45      }
46    } catch (err) {
47      return { result: `image failed: ${err instanceof Error ? err.message : String(err)}` }
48    }
49  })
50}
51