imagegen-smarto 0.2.0 → 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 +6 -0
- package/bin/generate.js +61 -3
- package/imagegen-smarto/SKILL.md +10 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -115,6 +115,12 @@ The command handles its relay protocol internally; the skill only prepares the
|
|
|
115
115
|
final image prompt, supplies reference-image paths, and consumes the returned
|
|
116
116
|
image result.
|
|
117
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.
|
|
123
|
+
|
|
118
124
|
## Local profile switch
|
|
119
125
|
|
|
120
126
|
The two profiles used by the author are:
|
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
|
@@ -34,9 +34,18 @@ reading these instructions.
|
|
|
34
34
|
|
|
35
35
|
5. If the user named an output location, pass `--output <path>`. Do not
|
|
36
36
|
overwrite an existing asset unless replacement was explicitly requested.
|
|
37
|
-
6.
|
|
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
|
|
38
43
|
so Codex displays the generated file. Report the saved path. For a failed
|
|
39
44
|
command, report the actual error and do not claim that an image exists.
|
|
40
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
|
+
|
|
41
50
|
For edits, state invariants explicitly in the prompt: `change only X; keep Y
|
|
42
51
|
unchanged`. When iterating, make one targeted change and repeat the invariants.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "imagegen-smarto",
|
|
3
|
-
"version": "0.2.
|
|
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"
|