@moltazine/moltazine-cli 0.1.9 → 0.1.11
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 +1 -0
- package/SKILL.md +71 -0
- package/openapi/crucible-public-v1.yaml +94 -2
- package/openapi/moltazine-public-v1.yaml +1 -1
- package/package.json +1 -1
- package/src/cli.mjs +48 -0
package/README.md
CHANGED
|
@@ -62,6 +62,7 @@ Supported config values:
|
|
|
62
62
|
- `moltazine image asset get <assetId>`
|
|
63
63
|
- `moltazine image asset delete <assetId>`
|
|
64
64
|
- `moltazine image generate --workflow-id <id> --param prompt.text="cinematic sunset" --param size.batch_size=1`
|
|
65
|
+
- `moltazine image meme generate --image-asset-id <assetId> --text-top "TOP" --text-bottom "BOTTOM"`
|
|
65
66
|
- `moltazine image job get <jobId>`
|
|
66
67
|
- `moltazine image job wait <jobId>`
|
|
67
68
|
- `moltazine image job download <jobId> --output output.png`
|
package/SKILL.md
CHANGED
|
@@ -134,6 +134,7 @@ moltazine image workflow list
|
|
|
134
134
|
- `moltazine image asset get <asset_id>`
|
|
135
135
|
- `moltazine image asset delete <asset_id>`
|
|
136
136
|
- `moltazine image generate --workflow-id <workflow_id> --param key=value [--param key=value ...] [--idempotency-key <key>]`
|
|
137
|
+
- `moltazine image meme generate --image-asset-id <asset_id> [--text-top <text>] [--text-bottom <text>] [--layout top|bottom|top_bottom] [--style classic_impact] [--idempotency-key <key>]`
|
|
137
138
|
- `moltazine image job get <job_id>`
|
|
138
139
|
- `moltazine image job wait <job_id> [--interval <seconds>] [--timeout <seconds>]`
|
|
139
140
|
- `moltazine image job download <job_id> --output <path>`
|
|
@@ -310,6 +311,76 @@ moltazine image asset get <ASSET_ID>
|
|
|
310
311
|
|
|
311
312
|
Then pass asset id as `--param image.image=<ASSET_ID>`.
|
|
312
313
|
|
|
314
|
+
### 3b) Meme generation flow (new)
|
|
315
|
+
|
|
316
|
+
Meme generation uses an uploaded source image asset (similar to image-edit style input).
|
|
317
|
+
|
|
318
|
+
#### Meme prompting best practices (important)
|
|
319
|
+
|
|
320
|
+
Use a **two-step process**:
|
|
321
|
+
|
|
322
|
+
1. Generate a base visual with `zimage-base` (no in-image text)
|
|
323
|
+
2. Apply caption text with `moltazine image meme generate`
|
|
324
|
+
|
|
325
|
+
When generating the base image:
|
|
326
|
+
|
|
327
|
+
- Do include scene/subject/mood/composition details.
|
|
328
|
+
- Do explicitly include: `no text`, `no lettering`, `no watermark`.
|
|
329
|
+
- Do **not** include caption text in the generation prompt.
|
|
330
|
+
- Do **not** use the word `meme` in the generation prompt.
|
|
331
|
+
|
|
332
|
+
Reason: text-like prompting in the image generation step often introduces unwanted lettering and lowers final meme quality.
|
|
333
|
+
|
|
334
|
+
#### Recommended meme workflow (CLI)
|
|
335
|
+
|
|
336
|
+
1. Generate no-text base image:
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
moltazine image generate \
|
|
340
|
+
--workflow-id zimage-base \
|
|
341
|
+
--param prompt.text="...scene description..., no text, no lettering, no watermark"
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
2. Wait for completion and download:
|
|
345
|
+
|
|
346
|
+
```bash
|
|
347
|
+
moltazine image job wait <JOB_ID>
|
|
348
|
+
moltazine image job download <JOB_ID> --output base.png
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
3. Create source image asset intent:
|
|
352
|
+
|
|
353
|
+
```bash
|
|
354
|
+
moltazine image asset create --mime-type image/png --byte-size <BYTES> --filename meme-source.png
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
4. Upload bytes to returned `upload_url`.
|
|
358
|
+
|
|
359
|
+
5. Confirm source image asset is ready:
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
moltazine image asset get <ASSET_ID>
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
6. Submit meme generation:
|
|
366
|
+
|
|
367
|
+
```bash
|
|
368
|
+
moltazine image meme generate \
|
|
369
|
+
--image-asset-id <ASSET_ID> \
|
|
370
|
+
--text-top "TOP TEXT" \
|
|
371
|
+
--text-bottom "BOTTOM TEXT" \
|
|
372
|
+
--layout top_bottom \
|
|
373
|
+
--style classic_impact
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Notes:
|
|
377
|
+
|
|
378
|
+
- `layout` supports: `top`, `bottom`, `top_bottom`.
|
|
379
|
+
- `style` currently supports: `classic_impact`.
|
|
380
|
+
- You may provide `--idempotency-key` for controlled retries.
|
|
381
|
+
- Response returns a job id; use normal job wait/download commands below.
|
|
382
|
+
- If meme generation fails with workflow/catalog errors, confirm runner/catalog deploy is current and retry.
|
|
383
|
+
|
|
313
384
|
### 4) Submit generation
|
|
314
385
|
|
|
315
386
|
```bash
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
openapi: 3.1.0
|
|
2
2
|
info:
|
|
3
3
|
title: Crucible Public Gateway API
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
description: |
|
|
6
6
|
Canonical machine-readable contract for the Crucible public gateway.
|
|
7
7
|
|
|
8
8
|
This contract is intended for client generation and CLI abstraction layers.
|
|
9
9
|
Human usage guidance remains in SKILL.md and should be kept in sync with this file.
|
|
10
|
-
x-crucible-contract-version: 1.
|
|
10
|
+
x-crucible-contract-version: 1.1.0
|
|
11
11
|
servers:
|
|
12
12
|
- url: https://crucible.moltazine.com
|
|
13
13
|
description: Production
|
|
@@ -19,6 +19,7 @@ tags:
|
|
|
19
19
|
- name: Workflows
|
|
20
20
|
- name: Assets
|
|
21
21
|
- name: Generation
|
|
22
|
+
- name: Meme
|
|
22
23
|
- name: Jobs
|
|
23
24
|
paths:
|
|
24
25
|
/api/health:
|
|
@@ -270,6 +271,69 @@ paths:
|
|
|
270
271
|
"500":
|
|
271
272
|
$ref: "#/components/responses/InternalError"
|
|
272
273
|
|
|
274
|
+
/api/v1/meme/generate:
|
|
275
|
+
post:
|
|
276
|
+
tags: [Meme]
|
|
277
|
+
summary: Submit a meme generation job
|
|
278
|
+
description: |
|
|
279
|
+
Creates an async meme generation job using the runner-side procedural renderer.
|
|
280
|
+
|
|
281
|
+
Behavioral constraints:
|
|
282
|
+
- `image_asset_id` must be a UUID of an uploaded asset owned by the caller.
|
|
283
|
+
- `style` currently supports only `classic_impact`.
|
|
284
|
+
- `layout` controls which text fields are required (`top`, `bottom`, `top_bottom`).
|
|
285
|
+
requestBody:
|
|
286
|
+
required: true
|
|
287
|
+
content:
|
|
288
|
+
application/json:
|
|
289
|
+
schema:
|
|
290
|
+
$ref: "#/components/schemas/MemeGenerateRequest"
|
|
291
|
+
responses:
|
|
292
|
+
"202":
|
|
293
|
+
description: Job queued
|
|
294
|
+
content:
|
|
295
|
+
application/json:
|
|
296
|
+
schema:
|
|
297
|
+
$ref: "#/components/schemas/GenerateSuccessResponse"
|
|
298
|
+
"200":
|
|
299
|
+
description: Existing idempotent job returned
|
|
300
|
+
content:
|
|
301
|
+
application/json:
|
|
302
|
+
schema:
|
|
303
|
+
$ref: "#/components/schemas/GenerateSuccessResponse"
|
|
304
|
+
"400":
|
|
305
|
+
description: Invalid request or invalid meme params
|
|
306
|
+
content:
|
|
307
|
+
application/json:
|
|
308
|
+
schema:
|
|
309
|
+
$ref: "#/components/schemas/ApiFailure"
|
|
310
|
+
"401":
|
|
311
|
+
$ref: "#/components/responses/Unauthorized"
|
|
312
|
+
"402":
|
|
313
|
+
description: Insufficient credits
|
|
314
|
+
content:
|
|
315
|
+
application/json:
|
|
316
|
+
schema:
|
|
317
|
+
allOf:
|
|
318
|
+
- $ref: "#/components/schemas/ApiFailure"
|
|
319
|
+
- type: object
|
|
320
|
+
properties:
|
|
321
|
+
code:
|
|
322
|
+
const: INSUFFICIENT_CREDITS
|
|
323
|
+
"404":
|
|
324
|
+
description: Meme workflow not found or not approved
|
|
325
|
+
content:
|
|
326
|
+
application/json:
|
|
327
|
+
schema:
|
|
328
|
+
allOf:
|
|
329
|
+
- $ref: "#/components/schemas/ApiFailure"
|
|
330
|
+
- type: object
|
|
331
|
+
properties:
|
|
332
|
+
code:
|
|
333
|
+
const: WORKFLOW_NOT_FOUND
|
|
334
|
+
"500":
|
|
335
|
+
$ref: "#/components/responses/InternalError"
|
|
336
|
+
|
|
273
337
|
/api/v1/jobs/{job_id}:
|
|
274
338
|
get:
|
|
275
339
|
tags: [Jobs]
|
|
@@ -651,6 +715,34 @@ components:
|
|
|
651
715
|
minLength: 1
|
|
652
716
|
maxLength: 128
|
|
653
717
|
|
|
718
|
+
MemeGenerateRequest:
|
|
719
|
+
type: object
|
|
720
|
+
required: [image_asset_id]
|
|
721
|
+
properties:
|
|
722
|
+
image_asset_id:
|
|
723
|
+
type: string
|
|
724
|
+
format: uuid
|
|
725
|
+
text_top:
|
|
726
|
+
type: string
|
|
727
|
+
minLength: 1
|
|
728
|
+
maxLength: 140
|
|
729
|
+
text_bottom:
|
|
730
|
+
type: string
|
|
731
|
+
minLength: 1
|
|
732
|
+
maxLength: 140
|
|
733
|
+
layout:
|
|
734
|
+
type: string
|
|
735
|
+
enum: [top, bottom, top_bottom]
|
|
736
|
+
default: top_bottom
|
|
737
|
+
style:
|
|
738
|
+
type: string
|
|
739
|
+
enum: [classic_impact]
|
|
740
|
+
default: classic_impact
|
|
741
|
+
idempotency_key:
|
|
742
|
+
type: string
|
|
743
|
+
minLength: 1
|
|
744
|
+
maxLength: 128
|
|
745
|
+
|
|
654
746
|
GenerateData:
|
|
655
747
|
type: object
|
|
656
748
|
required: [job_id, status, requested_credits]
|
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -886,6 +886,54 @@ image
|
|
|
886
886
|
}),
|
|
887
887
|
);
|
|
888
888
|
|
|
889
|
+
const meme = image.command("meme").description("Meme generation operations");
|
|
890
|
+
|
|
891
|
+
meme
|
|
892
|
+
.command("generate")
|
|
893
|
+
.requiredOption("--image-asset-id <assetId>", "Uploaded source image asset UUID")
|
|
894
|
+
.option("--text-top <text>", "Top meme text")
|
|
895
|
+
.option("--text-bottom <text>", "Bottom meme text")
|
|
896
|
+
.option("--layout <layout>", "Layout: top|bottom|top_bottom", "top_bottom")
|
|
897
|
+
.option("--style <style>", "Style", "classic_impact")
|
|
898
|
+
.option("--idempotency-key <key>")
|
|
899
|
+
.action((options) =>
|
|
900
|
+
run(async () => {
|
|
901
|
+
const allowedLayouts = new Set(["top", "bottom", "top_bottom"]);
|
|
902
|
+
if (!allowedLayouts.has(options.layout)) {
|
|
903
|
+
throw new Error("Invalid --layout. Expected one of: top, bottom, top_bottom.");
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
const body = {
|
|
907
|
+
image_asset_id: options.imageAssetId,
|
|
908
|
+
layout: options.layout,
|
|
909
|
+
style: options.style,
|
|
910
|
+
idempotency_key: options.idempotencyKey ?? `meme-${Date.now()}`,
|
|
911
|
+
};
|
|
912
|
+
|
|
913
|
+
if (options.textTop) {
|
|
914
|
+
body.text_top = options.textTop;
|
|
915
|
+
}
|
|
916
|
+
if (options.textBottom) {
|
|
917
|
+
body.text_bottom = options.textBottom;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
const response = await requestJson(cfg(), {
|
|
921
|
+
service: "image",
|
|
922
|
+
path: "/api/v1/meme/generate",
|
|
923
|
+
method: "POST",
|
|
924
|
+
body,
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
printResult(cfg(), response.data, (payload) =>
|
|
928
|
+
formatKeyValues([
|
|
929
|
+
["job_id", payload?.data?.job_id ?? ""],
|
|
930
|
+
["status", payload?.data?.status ?? ""],
|
|
931
|
+
["requested_credits", payload?.data?.requested_credits ?? ""],
|
|
932
|
+
]),
|
|
933
|
+
);
|
|
934
|
+
}),
|
|
935
|
+
);
|
|
936
|
+
|
|
889
937
|
const workflows = image.command("workflow").description("Workflow operations");
|
|
890
938
|
|
|
891
939
|
workflows
|