imagegen-smarto 0.1.4 → 0.2.1

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
@@ -85,13 +85,17 @@ After installation, use the profile that points requests to SmartO. The skill
85
85
  is configured for implicit invocation, so an image request can be written in
86
86
  natural language without first typing `$imagegen-smarto`.
87
87
 
88
- ## How image generation works
88
+ ## Image generation workflow
89
89
 
90
- When the skill is triggered, Codex runs the installed `imagegen-smarto`
91
- command. The command reads the active provider URL, model, and Codex
92
- credential, sends a streaming Responses request with the SmartO marker, parses
93
- the returned `image_generation_call`, and saves the PNG locally. It prints an
94
- absolute `IMAGE_MARKDOWN=...` line so Codex can display the result inline.
90
+ When the skill is triggered, Codex first turns the request into the same kind
91
+ of structured, production-oriented prompt used by the official image skill.
92
+ Detailed prompts are preserved and normalized; generic prompts receive only
93
+ useful composition or presentation detail. Edits explicitly lock the parts
94
+ that must remain unchanged.
95
+
96
+ Codex then runs the installed `imagegen-smarto` command. The command uses the
97
+ active SmartO provider and credential, saves the returned PNG locally, and
98
+ prints an absolute `IMAGE_MARKDOWN=...` line so Codex can display it inline.
95
99
 
96
100
  You can also test the execution path directly:
97
101
 
@@ -107,9 +111,15 @@ imagegen-smarto generate \
107
111
  --image /absolute/path/to/source.png
108
112
  ```
109
113
 
110
- The command always uses `stream=true` internally because the relay requires
111
- streaming for image generation. The skill does not need to describe or modify
112
- the relay request body.
114
+ The command handles its relay protocol internally; the skill only prepares the
115
+ final image prompt, supplies reference-image paths, and consumes the returned
116
+ image result.
117
+
118
+ While the relay is producing an image, the command writes a periodic status
119
+ heartbeat with elapsed time to stderr. This is a waiting indicator, not an
120
+ upstream completion percentage. The final `IMAGE_PATH=` and `IMAGE_MARKDOWN=`
121
+ records remain on stdout. Pass `--quiet` to suppress status messages while
122
+ keeping those final records.
113
123
 
114
124
  ## Local profile switch
115
125
 
package/bin/generate.js CHANGED
@@ -8,6 +8,17 @@ const path = require('node:path')
8
8
 
9
9
  const IMAGEGEN_MARKER = '__CODEX_VPS_IMAGEGEN__'
10
10
  const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000
11
+ const DEFAULT_PROGRESS_INTERVAL_MS = 15 * 1000
12
+
13
+ function progressIntervalMs() {
14
+ const configured = Number(process.env.IMAGEGEN_SMARTO_PROGRESS_INTERVAL_MS)
15
+ if (Number.isFinite(configured) && configured > 0) return configured
16
+ return DEFAULT_PROGRESS_INTERVAL_MS
17
+ }
18
+
19
+ function reportProgress(message, quiet) {
20
+ if (!quiet) console.error(`imagegen-smarto: ${message}`)
21
+ }
11
22
 
12
23
  function printGenerateHelp() {
13
24
  console.log(`imagegen-smarto generate - generate or edit an image through the active relay
@@ -23,6 +34,7 @@ Options:
23
34
  --model <model> Override the model from Codex config
24
35
  --base-url <url> Override the active provider base URL
25
36
  --timeout <seconds> Request timeout (default: 600)
37
+ --quiet Suppress status messages (final paths still print)
26
38
  --help Show this help`)
27
39
  }
28
40
 
@@ -34,6 +46,7 @@ function parseGenerateArgs(argv) {
34
46
  model: null,
35
47
  baseUrl: null,
36
48
  timeoutMs: DEFAULT_TIMEOUT_MS,
49
+ quiet: false,
37
50
  }
38
51
  const positional = []
39
52
 
@@ -84,6 +97,11 @@ function parseGenerateArgs(argv) {
84
97
  continue
85
98
  }
86
99
 
100
+ if (arg === '--quiet') {
101
+ options.quiet = true
102
+ continue
103
+ }
104
+
87
105
  if (arg.startsWith('-')) {
88
106
  throw new Error(`unknown option: ${arg}`)
89
107
  }
@@ -273,7 +291,7 @@ function outputPathForIndex(requestedPath, index) {
273
291
  return `${base.slice(0, -extension.length)}-${index + 1}${extension}`
274
292
  }
275
293
 
276
- async function requestImage({ prompt, images, output, model, baseUrl, timeoutMs }) {
294
+ async function requestImage({ prompt, images, output, model, baseUrl, timeoutMs, quiet }) {
277
295
  const codexHome = resolveCodexHome()
278
296
  const active = readActiveProviderConfig(codexHome)
279
297
  const apiKey = readApiKey(codexHome)
@@ -290,6 +308,14 @@ async function requestImage({ prompt, images, output, model, baseUrl, timeoutMs
290
308
  }
291
309
  const controller = new AbortController()
292
310
  const timeout = setTimeout(() => controller.abort(), timeoutMs)
311
+ const startedAt = Date.now()
312
+ const interval = progressIntervalMs()
313
+ const elapsedSeconds = () => Math.floor((Date.now() - startedAt) / 1000)
314
+ const progressTimer = setInterval(() => {
315
+ reportProgress(`still waiting for the image relay (${elapsedSeconds()}s elapsed)`, quiet)
316
+ }, interval)
317
+ const clearProgress = () => clearInterval(progressTimer)
318
+ reportProgress('starting image generation', quiet)
293
319
  let response
294
320
  try {
295
321
  response = await fetch(normalizeResponsesUrl(baseUrl || active.baseUrl), {
@@ -303,37 +329,52 @@ async function requestImage({ prompt, images, output, model, baseUrl, timeoutMs
303
329
  })
304
330
  } catch (error) {
305
331
  clearTimeout(timeout)
332
+ clearProgress()
306
333
  if (error.name === 'AbortError') throw new Error(`image request timed out after ${timeoutMs / 1000}s`)
307
334
  throw new Error(`cannot reach the image relay: ${error.message}`)
308
335
  }
309
336
 
310
337
  if (!response.ok) {
311
338
  clearTimeout(timeout)
339
+ clearProgress()
312
340
  const errorText = (await response.text()).slice(0, 2000)
313
341
  throw new Error(`image relay returned HTTP ${response.status}: ${errorText}`)
314
342
  }
315
343
  if (!response.body) {
316
344
  clearTimeout(timeout)
345
+ clearProgress()
317
346
  throw new Error('image relay returned no response body')
318
347
  }
319
348
 
349
+ reportProgress('relay connected; waiting for streamed image result', quiet)
350
+
320
351
  const results = []
321
352
  let relayError = null
322
353
  let buffer = ''
354
+ let reader = null
355
+ let streamDone = false
356
+ let resultReported = false
323
357
  const decoder = new TextDecoder()
324
358
  const onEvent = (event) => {
325
359
  relayError ||= extractError(event)
326
360
  const item = event.type === 'response.output_item.done' ? event.item : null
327
361
  if (item?.type === 'image_generation_call' && item.result) {
328
362
  results.push(item.result)
363
+ if (!resultReported) {
364
+ resultReported = true
365
+ reportProgress(`image result received after ${elapsedSeconds()}s; finishing response`, quiet)
366
+ }
329
367
  }
330
368
  }
331
369
 
332
370
  try {
333
- const reader = response.body.getReader()
371
+ reader = response.body.getReader()
334
372
  while (true) {
335
373
  const { value, done } = await reader.read()
336
- if (done) break
374
+ if (done) {
375
+ streamDone = true
376
+ break
377
+ }
337
378
  buffer += decoder.decode(value, { stream: true })
338
379
  buffer = consumeSseText(buffer, onEvent)
339
380
  }
@@ -346,6 +387,21 @@ async function requestImage({ prompt, images, output, model, baseUrl, timeoutMs
346
387
  throw new Error(`failed while reading the image relay response: ${error.message}`)
347
388
  } finally {
348
389
  clearTimeout(timeout)
390
+ clearProgress()
391
+ if (reader && !streamDone) {
392
+ try {
393
+ await reader.cancel()
394
+ } catch {
395
+ // The original request error is more useful than cleanup failures.
396
+ }
397
+ }
398
+ if (reader) {
399
+ try {
400
+ reader.releaseLock()
401
+ } catch {
402
+ // The stream may already have released the lock after an abort.
403
+ }
404
+ }
349
405
  }
350
406
 
351
407
  if (relayError) throw new Error(relayError)
@@ -360,6 +416,7 @@ async function requestImage({ prompt, images, output, model, baseUrl, timeoutMs
360
416
  fs.writeFileSync(filePath, decodeImageResult(results[index]), { mode: 0o600 })
361
417
  paths.push(filePath)
362
418
  }
419
+ reportProgress(`saved ${paths.length} image${paths.length === 1 ? '' : 's'} in ${elapsedSeconds()}s`, quiet)
363
420
  return paths
364
421
  }
365
422
 
@@ -381,6 +438,7 @@ async function generateImage(argv) {
381
438
  model: options.model,
382
439
  baseUrl: options.baseUrl,
383
440
  timeoutMs: options.timeoutMs,
441
+ quiet: options.quiet,
384
442
  })
385
443
  for (const filePath of paths) {
386
444
  console.log(`IMAGE_PATH=${filePath}`)
@@ -1,27 +1,51 @@
1
1
  ---
2
2
  name: imagegen-smarto
3
- description: Use the active SmartO relay to actually create or edit images. Trigger immediately for any request to create, draw, generate, modify, transform, or redesign a picture, including text-to-image (文生图), image-to-image (图生图), and image generation inside another workflow. Do not trigger for viewing, analyzing, describing, or recognizing an existing image, or for ordinary non-image tasks.
3
+ description: Use the active SmartO relay to create or edit raster images. Trigger immediately for requests to create, draw, generate, modify, transform, or redesign a picture, including text-to-image (文生图), image-to-image (图生图), and image generation inside another workflow. Do not trigger for viewing, analyzing, describing, or recognizing an existing image, ordinary non-image tasks, or output better produced as SVG or code-native graphics.
4
4
  ---
5
5
 
6
6
  # SmartO image generation
7
7
 
8
- When this skill triggers, execute the installed `imagegen-smarto` command. The
9
- command is the image-generation entry point: it sends the request to the active
10
- Codex provider, adds the SmartO marker internally, keeps streaming enabled, and
11
- saves the returned image locally. Do not stop after reading this file and do
12
- not try to change the outer Codex request yourself.
8
+ Use `imagegen-smarto generate` to produce the requested image. The command is
9
+ the image-generation tool for this skill; execute it instead of stopping after
10
+ reading these instructions.
13
11
 
14
- For a text-to-image request, run:
12
+ ## Workflow
15
13
 
16
- ```sh
17
- imagegen-smarto generate --prompt "<the user's complete image prompt>"
18
- ```
14
+ 1. Decide whether this is generation or an edit. Treat supplied images as edit
15
+ inputs only when the user asks to change or combine them; otherwise label
16
+ their reference role in the prompt.
17
+ 2. Rewrite the user's request into a strong image prompt using
18
+ [references/prompting.md](references/prompting.md). Preserve all explicit
19
+ requirements. Normalize detailed prompts; augment generic prompts only when
20
+ the addition materially improves the result.
21
+ 3. Generate one requested asset or variant per command:
19
22
 
20
- Preserve the user's complete prompt and edit instructions. For image-to-image
21
- requests, pass each available reference image as `--image <absolute-path>`.
22
- The command can be repeated for multiple requested outputs.
23
+ ```sh
24
+ imagegen-smarto generate --prompt "<final structured prompt>"
25
+ ```
23
26
 
24
- After the command succeeds, use every printed `IMAGE_MARKDOWN=...` line in the
25
- response so the generated file is displayed as an image. Do not replace the
26
- image with a textual description. If the command fails, report its actual error
27
- instead of claiming that an image was generated.
27
+ 4. For an edit, add each available source or reference image by absolute path.
28
+ Up to five images may be supplied:
29
+
30
+ ```sh
31
+ imagegen-smarto generate --prompt "<final structured edit prompt>" \
32
+ --image /absolute/path/to/input.png
33
+ ```
34
+
35
+ 5. If the user named an output location, pass `--output <path>`. Do not
36
+ overwrite an existing asset unless replacement was explicitly requested.
37
+ 6. Keep the default status heartbeat while the relay is working. Image
38
+ generation can take a minute or more; silence is not evidence of failure.
39
+ If another tool wrapper yields while the command is still running, wait for
40
+ the process to finish instead of interrupting it. Use `--quiet` only when a
41
+ caller needs machine-clean stderr.
42
+ 7. On success, include every printed `IMAGE_MARKDOWN=...` value in the answer
43
+ so Codex displays the generated file. Report the saved path. For a failed
44
+ command, report the actual error and do not claim that an image exists.
45
+
46
+ The CLI writes status heartbeats to stderr and keeps the final `IMAGE_PATH=`
47
+ and `IMAGE_MARKDOWN=` records on stdout. The heartbeat reports elapsed waiting
48
+ time; it is not an upstream completion percentage or a health guarantee.
49
+
50
+ For edits, state invariants explicitly in the prompt: `change only X; keep Y
51
+ unchanged`. When iterating, make one targeted change and repeat the invariants.
@@ -0,0 +1,99 @@
1
+ # Image prompting
2
+
3
+ Shape the user's request into a concise production-oriented specification.
4
+ Use only the lines that improve the request; the schema is scaffolding, not a
5
+ form that must always be filled.
6
+
7
+ ## Specificity
8
+
9
+ - If the prompt is already detailed, preserve it and only normalize its order
10
+ and wording.
11
+ - If the prompt is generic, add tasteful composition, framing, intended-use,
12
+ polish, or scene detail when it materially improves the output.
13
+ - Do not invent extra characters, props, brands, slogans, palettes, or story
14
+ beats. Do not choose arbitrary left/right placement without layout context.
15
+ - Keep exact user constraints and requested text unchanged.
16
+
17
+ ## Prompt structure
18
+
19
+ For complex requests, use short labeled lines in this order:
20
+
21
+ ```text
22
+ Use case: <taxonomy slug>
23
+ Asset type: <where the image will be used> (optional)
24
+ Primary request: <the user's main request>
25
+ Input images: <Image 1: role; Image 2: role> (optional)
26
+ Scene/backdrop: <environment>
27
+ Subject: <main subject and important details>
28
+ Style/medium: <photo, illustration, 3D, etc.>
29
+ Composition/framing: <viewpoint, crop, placement, negative space>
30
+ Lighting/mood: <lighting and atmosphere>
31
+ Color palette: <requested or implied palette>
32
+ Materials/textures: <important surface detail>
33
+ Text (verbatim): "<exact text>"
34
+ Constraints: <must preserve and must include>
35
+ Avoid: <negative constraints>
36
+ ```
37
+
38
+ Simple requests can remain short. Add only the fields needed to clarify the
39
+ result.
40
+
41
+ ## Use-case taxonomy
42
+
43
+ Generation:
44
+
45
+ - `photorealistic-natural`: candid or editorial scenes with natural lighting
46
+ and real texture.
47
+ - `product-mockup`: product, packaging, catalog, or merchandise imagery.
48
+ - `ui-mockup`: practical app or web interface imagery at a stated fidelity.
49
+ - `infographic-diagram`: structured diagrams with explicit layout and labels.
50
+ - `scientific-educational`: accurate teaching visuals for a named audience.
51
+ - `ads-marketing`: campaign imagery with audience, positioning, and exact copy.
52
+ - `productivity-visual`: slides, charts, workflows, and business visuals.
53
+ - `logo-brand`: simple, scalable mark exploration with a strong silhouette.
54
+ - `illustration-story`: comics, children's art, and narrative scenes.
55
+ - `stylized-concept`: style-driven concept art or rendered scenes.
56
+ - `historical-scene`: period-specific scenes requiring factual accuracy.
57
+
58
+ Editing:
59
+
60
+ - `text-localization`: replace only in-image text and preserve layout.
61
+ - `identity-preserve`: preserve face, body, pose, hair, and expression.
62
+ - `precise-object-edit`: replace or remove a named element only.
63
+ - `lighting-weather`: change environmental conditions while preserving content.
64
+ - `background-extraction`: create a clean transparent-background cutout.
65
+ - `style-transfer`: apply reference style without introducing extra elements.
66
+ - `compositing`: combine indexed inputs with matched scale, light, perspective.
67
+ - `sketch-to-render`: preserve layout, proportions, and perspective.
68
+
69
+ ## Composition and realism
70
+
71
+ - Specify framing and viewpoint only when useful: close-up, full body, wide,
72
+ eye-level, low-angle, top-down.
73
+ - Call out negative space when the image must leave room for UI or copy.
74
+ - For people, clarify body framing, gaze, pose, and object interactions when
75
+ they matter.
76
+ - For photorealism, explicitly request photorealism and concrete natural detail
77
+ such as skin texture, fabric wear, material grain, and imperfect surfaces.
78
+
79
+ ## Edits and references
80
+
81
+ - Label every supplied image by index and role: edit target, style reference,
82
+ composition reference, or compositing input.
83
+ - For edits, use `change only X; keep Y unchanged` and list all invariants.
84
+ - For compositing, state what moves from each indexed image and require matched
85
+ lighting, perspective, and scale.
86
+ - Repeat invariants on every edit iteration to reduce drift.
87
+
88
+ ## Text in images
89
+
90
+ - Quote exact text, require verbatim rendering, and specify typography and
91
+ placement when those details matter.
92
+ - Spell uncommon words letter by letter when accuracy is critical.
93
+ - Require no extra characters or text beyond the supplied copy.
94
+
95
+ ## Iteration
96
+
97
+ Start with a clean base prompt. Inspect the result against subject, style,
98
+ composition, exact text, invariants, and avoid items. Refine with one targeted
99
+ change at a time instead of rewriting unrelated parts of the prompt.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imagegen-smarto",
3
- "version": "0.1.4",
3
+ "version": "0.2.1",
4
4
  "description": "Install the imagegen-smarto Codex skill on Windows and Linux.",
5
5
  "bin": {
6
6
  "imagegen-smarto": "bin/cli.js"
@@ -11,7 +11,8 @@
11
11
  "README.md"
12
12
  ],
13
13
  "scripts": {
14
- "postinstall": "node bin/cli.js install --postinstall"
14
+ "postinstall": "node bin/cli.js install --postinstall",
15
+ "test": "node --test"
15
16
  },
16
17
  "engines": {
17
18
  "node": ">=18"