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 +19 -9
- package/bin/generate.js +61 -3
- package/imagegen-smarto/SKILL.md +41 -17
- package/imagegen-smarto/references/prompting.md +99 -0
- package/package.json +3 -2
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
|
-
##
|
|
88
|
+
## Image generation workflow
|
|
89
89
|
|
|
90
|
-
When the skill is triggered, Codex
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
371
|
+
reader = response.body.getReader()
|
|
334
372
|
while (true) {
|
|
335
373
|
const { value, done } = await reader.read()
|
|
336
|
-
if (done)
|
|
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}`)
|
package/imagegen-smarto/SKILL.md
CHANGED
|
@@ -1,27 +1,51 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: imagegen-smarto
|
|
3
|
-
description: Use the active SmartO relay to
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
12
|
+
## Workflow
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
```sh
|
|
24
|
+
imagegen-smarto generate --prompt "<final structured prompt>"
|
|
25
|
+
```
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
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"
|