@thenavidm/midjourney-mcp-cli 1.0.0 → 1.2.0

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
@@ -1,35 +1,23 @@
1
- <div align="center">
2
- <img src="https://cdn.navid.media/connectors/midjourney-icon.png" alt="Midjourney" width="88">
3
- </div>
1
+ <img src="https://cdn.navid.media/connectors/midjourney-icon-solid.png" alt="Midjourney" width="88">
4
2
 
5
3
  # Midjourney MCP + CLI
6
4
 
5
+ [![npm](https://img.shields.io/npm/v/@thenavidm/midjourney-mcp-cli?color=orange&label=npm)](https://www.npmjs.com/package/@thenavidm/midjourney-mcp-cli)
7
6
  [![Licence](https://img.shields.io/badge/licence-MIT-green)](./LICENSE)
8
7
  [![YouTube](https://img.shields.io/badge/YouTube-@thenavidm-red?logo=youtube&logoColor=white)](https://youtube.com/@thenavidm?sub_confirmation=1)
9
8
  [![X](https://img.shields.io/badge/X-@thenavidm-black?logo=x)](https://x.com/thenavidm)
10
9
 
11
- Midjourney MCP server and CLI for Claude Code and AI agents. 27 tools for generating images, following jobs to completion, downloading the real files, and building moodboards that make a style reusable.
10
+ Midjourney MCP server and CLI for Claude Code and AI agents. 32 tools for generating images, following jobs to completion, downloading the real files, and building moodboards that make a style reusable.
12
11
 
13
12
  Midjourney publishes no API, so this drives a real Chrome that is signed in as you.
14
13
 
15
14
  There is no key to paste and no cookie to export. You sign in once, in a window, and the session lives in a browser profile rather than in a config file.
16
15
 
17
- 27 tools, on both surfaces. It waits for jobs to finish and hands back the actual files, not a screenshot of them.
16
+ 32 tools, on both surfaces. It waits for jobs to finish and hands back the actual files, not a screenshot of them.
18
17
 
19
18
  Built and maintained by [Navid Moazzez](https://navid.me).
20
19
 
21
- ```
22
- You: make a moodboard for cold Nordic product shots, fill it, then shoot a jar of face cream in that style
23
-
24
- Claude: Built the board and used it.
25
-
26
- moodboard Nordic Skincare | Still Life 4 images
27
- job f44b0a9d-e184-4e18-811f-a1ef9482d286
28
- prompt a ceramic jar of face cream, lid beside it
29
- style from the moodboard, not the prompt
30
-
31
- ~/Downloads/midjourney/f44b0a9d-0.png 1.8 MB 960x1200
32
- ```
20
+ <img src="https://cdn.navid.media/repos/midjourney-mcp-cli.gif?v=2" alt="Claude Code using the Midjourney MCP server" width="520">
33
21
 
34
22
  ## Two ways to use it
35
23
 
@@ -73,12 +61,11 @@ Every other client is in [section 3](#3-install).
73
61
 
74
62
  ### Which one
75
63
 
76
- | What you are doing | Use |
64
+ | Where you are | What you can reach |
77
65
  |---|---|
78
- | Inside a conversation with an agent | MCP |
79
- | On claude.ai or your phone | Neither. The browser is on your machine, so a cloud connector cannot reach it |
80
- | Piping, scripting, cron, CI | CLI |
81
- | A one-off question in a terminal | CLI |
66
+ | An agent that can run shell commands, like Claude Code or Cursor | Both. The CLI is the cheaper one: it costs nothing until you type it |
67
+ | claude.ai or a phone | Neither. Your logged-in browser is on this machine, and a cloud connector cannot reach it |
68
+ | A terminal, a script, cron or CI | The CLI only. There is no MCP client in a shell |
82
69
 
83
70
  They are the same program reading the same tool definitions, so anything one
84
71
  can do, the other can.
@@ -131,8 +118,8 @@ All 27 with their arguments are in [section 6](#6-tools).
131
118
  | 11 | [Your data](#11-your-data) | What is stored and where |
132
119
  | 12 | [Risks](#12-risks) | Read this before you install |
133
120
  | 13 | [Troubleshooting](#13-troubleshooting) | When something breaks |
134
- | | [Environment variables](#environment-variables) | Every knob, and its default |
135
- | 14 | [FAQ](#14-faq) | Including what an MCP server is |
121
+ | 14 | [Environment variables](#14-environment-variables) | Every knob, and its default |
122
+ | 15 | [FAQ](#15-faq) | Including what an MCP server is |
136
123
 
137
124
  ## 1. What you can ask it
138
125
 
@@ -141,6 +128,7 @@ All 27 with their arguments are in [section 6](#6-tools).
141
128
  - Make a moodboard for cold Nordic product shots, fill it, then shoot a jar of face cream in that style
142
129
  - Generate four logo concepts at low stylize so they stay literal, and save them
143
130
  - Vary the second one, strong, and save the results
131
+ - Upscale that one and turn it into a video
144
132
  - Take that last image's seed and try it again with chaos 40
145
133
  - What is in my Midjourney queue right now?
146
134
  - Download everything I generated today into ./renders
@@ -180,8 +168,6 @@ Node 22 or newer, and Google Chrome. Nothing else.
180
168
 
181
169
  npx -y @thenavidm/midjourney-mcp-cli --version
182
170
 
183
- > [!NOTE]
184
- > Not published to npm yet. Until it is, clone the repo and run `npm install && npm run build`, then use `node dist/index.js` wherever this page says `npx -y @thenavidm/midjourney-mcp-cli@latest`.
185
171
 
186
172
  Node 22 is the floor because the browser connection uses the global `WebSocket`
187
173
  that landed in that release. That is also why it has no dependency doing it.
@@ -301,7 +287,7 @@ authorises a charge.
301
287
 
302
288
  An MCP server is expensive and a CLI is free.
303
289
 
304
- The `tools/list` payload for these 27 tools is about **8,900 tokens**, plus the
290
+ The `tools/list` payload for these 32 tools is about **11,062 tokens**, plus the
305
291
  server instructions. That is charged on every turn of every conversation, used
306
292
  or not, because the descriptions are long and carry the parameter grammar.
307
293
 
@@ -310,11 +296,10 @@ the model pays only when it runs something.
310
296
 
311
297
  So the two are not competing:
312
298
 
313
- | Where the work happens | Surface |
299
+ | Where you are | What you can reach |
314
300
  |---|---|
315
- | Inside a conversation with an agent | MCP |
316
- | Piping, scripting, cron, CI | CLI |
317
- | A one-off question in a terminal | CLI |
301
+ | An agent that can run shell commands | Both. The CLI costs nothing until you type it |
302
+ | A terminal, a script, cron or CI | The CLI only |
318
303
 
319
304
  ## 6. Tools
320
305
 
@@ -326,6 +311,11 @@ So the two are not competing:
326
311
  | `submit_imagine` | Submit and return the job id without waiting. Spends |
327
312
  | `rerun_job` | Run an existing job again, optionally with new wording or at HD. Spends |
328
313
  | `vary_image` | Four variations of one image from a grid, subtle or strong. Spends |
314
+ | `upscale_image` | Upscale one image to full resolution, subtle or creative. Spends |
315
+ | `animate_image` | Turn one image into a video. Spends |
316
+ | `pan_image` | Extend the frame left, right, up or down. Spends |
317
+ | `zoom_out` | Pull the camera back and fill the new space. Spends |
318
+ | `remix_image` | Re-render an image against a new prompt, keeping its composition. Spends |
329
319
  | `submit_raw_job` | Send a job type this server does not model yet. Spends |
330
320
 
331
321
  ### Following work
@@ -429,29 +419,42 @@ At the terminal, Midjourney's own spellings work as aliases: `--ar`, `--sref`,
429
419
 
430
420
  ## 9. Moodboards
431
421
 
432
- A moodboard is a curated pile of reference images. Naming one is far more
433
- reliable than describing a look in words, because the board *is* the look.
422
+ A moodboard is a set of images the account has curated. Applying one is the same
423
+ mechanism the web app's Personalize panel uses: the board becomes a
424
+ personalization code on the prompt, resolved for you from its name.
425
+
426
+ ```bash
427
+ midjourney-cli imagine "a ceramic jar of face cream" --moodboard "Nordic Skincare" --confirm
428
+ ```
429
+
430
+ That is worth stating because there is a plausible wrong way to do the same
431
+ thing: sampling the board's images into `--sref` URLs. It only registers at a
432
+ high `--sw`, and that weight is what makes output look over-processed. The
433
+ personalization code needs no weight and does not compete with your wording.
434
434
 
435
435
  The loop:
436
436
 
437
437
  ```bash
438
- midjourney-cli create-moodboard "Nordic Skincare | Still Life"
438
+ midjourney-cli create-moodboard "Nordic Skincare"
439
439
  midjourney-cli imagine "<a long, specific style description>" --confirm
440
440
  midjourney-cli add-to-moodboard "Nordic Skincare" --job-id <job>
441
- midjourney-cli imagine "a ceramic jar of face cream, lid beside it" \
442
- --moodboard "Nordic Skincare" --sw 400 --confirm
441
+ midjourney-cli imagine "a ceramic jar of face cream" --moodboard "Nordic Skincare" --confirm
443
442
  ```
444
443
 
445
- After the third line the style is a name, and a nine-word prompt reproduces it.
446
-
447
444
  Partial names work: `"High Fashion"` finds `"High Fashion | Woman"`. An ambiguous
448
445
  name errors with the candidates rather than guessing, because picking the wrong
449
- board costs a generation to discover. References are sampled across the board
450
- rather than taken from the front, so a 242-image board does not always draw on
451
- its oldest images.
446
+ board costs a generation to discover.
447
+
448
+ `--p` accepts several codes, so a moodboard and a personalization profile apply
449
+ together. `profile` is the other kind of personalization: it biases toward
450
+ images the account has rated, rather than toward a set of pictures.
451
+
452
+ ### Leave the sliders alone
452
453
 
453
- `profile` does something different: it biases toward images the account has
454
- rated, rather than toward a set of pictures.
454
+ Midjourney's own defaults for stylize, weirdness and variety sit near the
455
+ minimum. Raising `stylize` trades fidelity for a prettier, more generic image.
456
+ Set them when you want that; otherwise a strong result comes from the prompt and
457
+ the reference.
455
458
 
456
459
  ## 10. How it works
457
460
 
@@ -524,7 +527,7 @@ Start with `doctor`. It orders the checks so the first failure is the one to fix
524
527
  | Every command times out at once | A native dialog was left open in the window. Dialogs are auto-dismissed now; if it persists, close the tab |
525
528
  | Downloads are empty or fail | The asset URL expired. Re-read the job with `get_job` for fresh URLs |
526
529
 
527
- ## Environment variables
530
+ ## 14. Environment variables
528
531
 
529
532
  Every one of these is optional. The defaults are what you want unless you are doing something unusual.
530
533
 
@@ -553,7 +556,7 @@ Every one of these is optional. The defaults are what you want unless you are do
553
556
  | `MIDJOURNEY_HTTP_HOST` | `127.0.0.1` | Interface for `--http` |
554
557
  | `MIDJOURNEY_HTTP_TOKEN` | unset | Bearer token. Required to listen off loopback |
555
558
 
556
- ## 14. FAQ
559
+ ## 15. FAQ
557
560
 
558
561
  <details>
559
562
  <summary><b>What is an MCP server?</b></summary>
@@ -650,7 +653,7 @@ Navid Moazzez is a leading AI business strategist, and the host of the AI Creato
650
653
  **Links**
651
654
 
652
655
  - Personal website: [navid.me](https://navid.me)
653
- - Store: [navid.bio](https://navid.bio)
656
+ - Link in bio: [navid.bio](https://navid.bio)
654
657
  - Navid Media: [navid.media](https://navid.media)
655
658
  - YouTube: [@thenavidm](https://youtube.com/@thenavidm?sub_confirmation=1) and [@thenavidai](https://youtube.com/@thenavidai?sub_confirmation=1)
656
659
  - X: [@thenavidm](https://x.com/thenavidm)
package/SKILL.md CHANGED
@@ -68,10 +68,10 @@ The ones worth knowing:
68
68
  | Argument | What it does |
69
69
  |---|---|
70
70
  | `aspect` | `"16:9"`, `"3:2"`, `"1:1"` |
71
- | `stylize` | 0-1000. Low follows the prompt, high looks prettier and drifts |
72
- | `chaos` | 0-100. How different the four results are from each other |
71
+ | `stylize` | 0-1000. Leave unset: the default is low and raising it costs fidelity |
72
+ | `chaos` | 0-100. Leave unset unless exploring |
73
73
  | `seed` | Reuse with an identical prompt to iterate on one image, not roll a new one |
74
- | `style_refs` | Style references: an image URL, a numeric code, or `"random"` |
74
+ | `style_refs` | One specific reference. For a curated look use `moodboard` instead |
75
75
  | `omni_refs` | Carry a character or object across images. The v7 replacement for `--cref` |
76
76
  | `image_prompts` | Direct image URLs, used as visual input |
77
77
  | `negative` | Things to keep out, e.g. `"text, watermark"` |
@@ -82,52 +82,117 @@ The ones worth knowing:
82
82
  At the terminal, Midjourney's own spellings work as aliases: `--ar`, `--sref`,
83
83
  `--oref`, `--iw`, `--sw`, `--ow`, `--q`, `--no`, `--v`.
84
84
 
85
- ## Moodboards are the best styling tool here
85
+ ## Use the newest model unless told otherwise
86
86
 
87
- The account has curated boards of reference images. Naming one is far more
88
- reliable than describing a look in words, because the board *is* the look.
87
+ The default is the current model, v8.2. Only pin an older one when the user asks
88
+ for it, or when they are iterating on an image made with it and want the match.
89
89
 
90
- ```
91
- imagine(prompt: "a model in an ivory suit on a coastal cliff",
92
- moodboard: "High Fashion", moodboard_refs: 4, confirm: true)
90
+ ## Moodboards are the strongest styling tool, and they are not srefs
91
+
92
+ A moodboard is a collection of images the account has curated. Applying one is
93
+ the same mechanism as selecting it in the web app's Personalize panel: the board
94
+ becomes a **personalization code** on the prompt.
95
+
96
+ ```bash
97
+ midjourney-cli imagine "a ceramic jar of face cream" --moodboard "Nordic Skincare" --confirm
93
98
  ```
94
99
 
95
- Partial names work. An ambiguous name errors with the candidates rather than
96
- guessing, because picking the wrong board costs a generation to find out.
100
+ The code is the board's id with an `m` in front, and the tool resolves it for
101
+ you. This matters because there is an obvious wrong way to do the same thing:
102
+ sampling the board's images into `--sref` URLs. That approximation only shows up
103
+ at a high `--sw`, and that weight is what makes output look processed. Use the
104
+ moodboard.
97
105
 
98
- `list_moodboards` shows them with image counts. A board showing 0 images is
99
- empty and cannot be referenced yet. `get_moodboard` shows exactly which
100
- references a generation would use.
106
+ `--p` accepts more than one code, so a moodboard and a personalization profile
107
+ can apply together.
101
108
 
102
- `profile` does something different: it biases toward images the account has
103
- rated, rather than toward a set of pictures. `list_personalized_profiles`
104
- reports how many ratings each is built on, and one with a low count barely
105
- moves the result.
109
+ Three styling tools, three jobs:
106
110
 
107
- ## Building a moodboard, which is the real workflow
111
+ | Want | Use |
112
+ |---|---|
113
+ | A look the account has curated | `moodboard` |
114
+ | One specific reference image or code | `style_refs` |
115
+ | The same character or object across images | `omni_refs` |
116
+ | The account's learned taste | `profile` |
108
117
 
109
- Generate a style, keep what works, reuse it. That loop is what the boards are
110
- for, and it is worth driving deliberately.
118
+ ## Leave stylize, chaos and weird alone
111
119
 
112
- ```
113
- create_moodboard(title: "Nordic Skincare | Still Life")
114
- imagine(prompt: "<a long, specific style description>", confirm: true)
115
- add_to_moodboard(moodboard: "Nordic Skincare", job_id: "<the job>", confirm: true)
116
- ```
120
+ Midjourney's own defaults sit low, and the sliders in its settings panel start
121
+ near the minimum. Raising `stylize` trades fidelity for a prettier, more
122
+ generic image; `chaos` and `weird` are for exploring, not for quality.
117
123
 
118
- After that the style is a name. A nine-word prompt reproduces it:
124
+ Reach for them only when the user asks for more stylisation or more variety. A
125
+ strong result comes from the prompt and the reference, not from the knobs.
119
126
 
127
+ ## Write prompts that leave nothing to chance
128
+
129
+ Vague adjectives produce generic images. Specificity is what earns the output.
130
+ Compare:
131
+
132
+ > a beautiful woman in a knit set, studio, high quality, 8k
133
+
134
+ against
135
+
136
+ > a woman in three-quarter turn against a seamless clay cyclorama, rib-knit set
137
+ > in oat, one hand at the hip, chin level, lit by a single large softbox high
138
+ > and slightly left with a white bounce filling the shadow side so the falloff
139
+ > is gentle, shot on medium format at f5.6, skin unretouched with visible pores,
140
+ > nothing else in frame
141
+
142
+ The second names the pose, the light source and its position, the fill, how the
143
+ falloff should behave, the camera and aperture, what the skin keeps, and what
144
+ must not appear. Every one of those is a decision the model would otherwise make
145
+ for you.
146
+
147
+ Things worth stating explicitly, because the model will invent them otherwise:
148
+
149
+ - **Where the light comes from**, and what fills the shadow
150
+ - **What the camera is**, and the aperture, which sets how much falls off
151
+ - **What the subject is doing** with hands, shoulders, gaze
152
+ - **What must not be in frame**, via the prompt or `negative`
153
+ - **The palette**, named as colours rather than a mood
154
+ - **Skin, fabric and surface texture**, or you get plastic
155
+
156
+ `raw` is worth setting for anything photographic: it applies less of
157
+ Midjourney's automatic prettification.
158
+
159
+ ## The pipeline is where the good work happens
160
+
161
+ One generation is a draft. The tools exist to take it somewhere:
162
+
163
+ ```bash
164
+ # 1. Explore cheaply. Draft mode is fast and costs less.
165
+ midjourney-cli imagine "<a long, specific prompt>" --draft --confirm
166
+
167
+ # 2. Once the composition is right, run it properly and keep the seed.
168
+ midjourney-cli imagine "<the same prompt>" --seed 501058481 --raw --confirm
169
+
170
+ # 3. Push the one that is closest.
171
+ midjourney-cli vary-image <job> --index 2 --confirm # subtle, or --strong
172
+ midjourney-cli remix-image <job> --index 2 --prompt "<reworded>" --confirm
173
+
174
+ # 4. Give it room, or take the frame wider.
175
+ midjourney-cli zoom-out <job> --index 2 --zoom-factor 150 --confirm
176
+ midjourney-cli pan-image <job> --index 2 --direction left --confirm
177
+
178
+ # 5. Finish it.
179
+ midjourney-cli upscale-image <job> --index 2 --confirm # subtle, or creative
180
+ midjourney-cli animate-image <job> --index 2 --motion "slow push in" --confirm
181
+ midjourney-cli download-job <job> --out-dir ./renders
120
182
  ```
121
- imagine(prompt: "a ceramic jar of face cream, lid beside it",
122
- moodboard: "Nordic Skincare", moodboard_refs: 4, confirm: true)
123
- ```
124
183
 
125
- Push `style_weight` up (300-500) when the look should dominate the prompt, and
126
- down when the subject matters more than the styling.
184
+ `seed` is how you iterate rather than reroll: the same prompt and seed gives a
185
+ near-identical image, so a small wording change shows its own effect instead of
186
+ a new roll of the dice.
187
+
188
+ `zoom_out` before adding a headline or a logo: it gives the composition room
189
+ without regenerating it.
190
+
191
+ `upscale` subtle keeps what is there; creative reworks detail as it enlarges, so
192
+ it is the wrong choice when the image is already right.
127
193
 
128
- `add_to_moodboard` takes a whole job at once, or specific `indexes`, or bare
129
- `urls`. `remove_from_moodboard` is how a board stays sharp, and it cannot be
130
- undone, so it asks for confirmation.
194
+ `animate_image` takes a `motion` note, not a scene: it is appended to the
195
+ image's own prompt, because Midjourney reads that field as what is in the frame.
131
196
 
132
197
  ## Working with results
133
198
 
@@ -37,7 +37,8 @@ export function fileNameFor(url, jobId, index) {
37
37
  // job in a folder, so the job id goes in front. The stem itself is dropped
38
38
  // when it is just the grid position again: `<job>-0-0_0.png` says nothing
39
39
  // that `<job>-0.png` does not.
40
- const redundant = /^\d+_\d+$/.test(stem);
40
+ // `0_2` for a grid image, `2` for a video segment: both repeat the index.
41
+ const redundant = /^\d+(_\d+)?$/.test(stem);
41
42
  const safeStem = redundant ? "" : stem.replace(/[^a-zA-Z0-9._-]/g, "-").slice(0, 60);
42
43
  return `${jobId}-${index}${safeStem ? `-${safeStem}` : ""}${extension}`;
43
44
  }
@@ -1 +1 @@
1
- {"version":3,"file":"download.js","sourceRoot":"","sources":["../../src/api/download.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"download.js","sourceRoot":"","sources":["../../src/api/download.ts"],"names":[],"mappings":"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"}
@@ -91,6 +91,70 @@ export declare function submitRaw(client: MidjourneyClient, jobType: string, ext
91
91
  jobIds: string[];
92
92
  raw: unknown;
93
93
  }>;
94
+ /**
95
+ * Which upscaler a job can use, derived from the model that made it.
96
+ *
97
+ * The menu says "Subtle" and "Creative", but the wire wants a version-specific
98
+ * name: `v8r1_2x_subtle` for a v8.1 image, `v7_2x_creative` for a v7 one. Send
99
+ * the label and the request is refused. None of this is guessable, and it is
100
+ * why the first four attempts here were 400s.
101
+ */
102
+ export declare function upscalerFor(version: string | undefined, style: "subtle" | "creative"): string;
103
+ /** The model version a job was made with, read off the job itself. */
104
+ export declare function versionOfJob(job: {
105
+ jobType?: string;
106
+ prompt?: string;
107
+ } | undefined): string | undefined;
108
+ /** Upscale one image from a grid. `subtle` keeps it; `creative` reworks detail. */
109
+ export declare function submitUpscale(client: MidjourneyClient, jobId: string, index: number, style: "subtle" | "creative", options?: SubmitOptions & {
110
+ version?: string;
111
+ }): Promise<{
112
+ jobIds: string[];
113
+ raw: unknown;
114
+ }>;
115
+ /**
116
+ * Animate one image into a video.
117
+ *
118
+ * Two things here are not guessable and both cost a rejected request to learn.
119
+ * The source is nested under `parentJob` as `image_num`, not the flat `index`
120
+ * every other job type uses. And `newPrompt` carries the image's own prompt,
121
+ * not a description of the motion: passing "slow push in" on its own is
122
+ * refused, because the field is the scene, not the movement.
123
+ *
124
+ * So a motion note is appended to the original prompt rather than replacing
125
+ * it, which is what the web app does when you type into its box.
126
+ */
127
+ export declare function submitVideo(client: MidjourneyClient, jobId: string, index: number, opts?: SubmitOptions & {
128
+ motion?: string;
129
+ resolution?: "480" | "720";
130
+ mode?: "auto" | "manual";
131
+ }): Promise<{
132
+ jobIds: string[];
133
+ raw: unknown;
134
+ }>;
135
+ /** Extend the frame in one direction. */
136
+ export declare function submitPan(client: MidjourneyClient, jobId: string, index: number, direction: "left" | "right" | "up" | "down", opts?: SubmitOptions & {
137
+ prompt?: string;
138
+ fraction?: number;
139
+ }): Promise<{
140
+ jobIds: string[];
141
+ raw: unknown;
142
+ }>;
143
+ /** Zoom out, filling the new space. 100 is no change; 200 doubles the frame. */
144
+ export declare function submitZoomOut(client: MidjourneyClient, jobId: string, index: number, opts?: SubmitOptions & {
145
+ prompt?: string;
146
+ zoomFactor?: number;
147
+ }): Promise<{
148
+ jobIds: string[];
149
+ raw: unknown;
150
+ }>;
151
+ /** Re-render one image against a new prompt, keeping its composition. */
152
+ export declare function submitRemix(client: MidjourneyClient, jobId: string, index: number, prompt: string, opts?: SubmitOptions & {
153
+ strong?: boolean;
154
+ }): Promise<{
155
+ jobIds: string[];
156
+ raw: unknown;
157
+ }>;
94
158
  export type ListOptions = {
95
159
  limit?: number;
96
160
  cursor?: string;
package/dist/api/jobs.js CHANGED
@@ -187,6 +187,130 @@ export async function submitRaw(client, jobType, extra, options = {}) {
187
187
  refreshView(client, options.refresh !== false);
188
188
  return { jobIds: extractJobIds(raw), raw };
189
189
  }
190
+ /**
191
+ * The rest of the job types, read out of the web app's own bundle.
192
+ *
193
+ * Every shape below was extracted from the compiled client rather than guessed
194
+ * or clicked, so the field names are the app's own. That matters: `upscale`
195
+ * takes a `type`, `video` nests its source under `parentJob` with `image_num`
196
+ * rather than `index`, and `pan` carries a `fraction` and a `stitch` flag. None
197
+ * of that is inferable, and a wrong guess spends GPU time on a request that
198
+ * quietly does nothing.
199
+ */
200
+ function baseBody(userId, options) {
201
+ return {
202
+ f: { mode: options.speed ?? "fast", private: options.private ?? false },
203
+ channelId: channelIdFor(userId),
204
+ metadata: {
205
+ isMobile: null,
206
+ imagePrompts: null,
207
+ imageReferences: null,
208
+ characterReferences: null,
209
+ depthReferences: null,
210
+ lightboxOpen: null,
211
+ },
212
+ };
213
+ }
214
+ async function submitShape(client, shape, options) {
215
+ const userId = await client.userId();
216
+ const body = { ...baseBody(userId, { speed: options.speed ?? client.config.defaultSpeed, ...options }), ...shape };
217
+ const raw = await client.request(ENDPOINTS.submit, { method: "POST", body, noRetry: true });
218
+ refreshView(client, options.refresh !== false);
219
+ return { jobIds: extractJobIds(raw), raw };
220
+ }
221
+ /**
222
+ * Which upscaler a job can use, derived from the model that made it.
223
+ *
224
+ * The menu says "Subtle" and "Creative", but the wire wants a version-specific
225
+ * name: `v8r1_2x_subtle` for a v8.1 image, `v7_2x_creative` for a v7 one. Send
226
+ * the label and the request is refused. None of this is guessable, and it is
227
+ * why the first four attempts here were 400s.
228
+ */
229
+ export function upscalerFor(version, style) {
230
+ const v = (version ?? "8.1").toLowerCase().replace(/^v/, "").trim();
231
+ if (v.startsWith("8.2"))
232
+ return `v8r2_2x_${style}`;
233
+ if (v.startsWith("8"))
234
+ return `v8r1_2x_${style}`;
235
+ if (v.startsWith("niji 7") || v.startsWith("7"))
236
+ return `v7_2x_${style}`;
237
+ if (v.startsWith("6.1") || v.startsWith("niji 6"))
238
+ return `v6r1_2x_${style}`;
239
+ if (v.startsWith("6"))
240
+ return `v6_2x_${style}`;
241
+ // Older lines only ever had the fixed upscalers, with no subtle or creative.
242
+ return `v5_2x`;
243
+ }
244
+ /** The model version a job was made with, read off the job itself. */
245
+ export function versionOfJob(job) {
246
+ const fromType = job?.jobType?.match(/^v(\d+)-(\d+)_/);
247
+ if (fromType)
248
+ return `${fromType[1]}.${fromType[2]}`;
249
+ const fromPrompt = job?.prompt?.match(/--v\s+(\S+)/);
250
+ if (fromPrompt)
251
+ return fromPrompt[1];
252
+ const niji = job?.prompt?.match(/--niji\s+(\S+)/);
253
+ if (niji)
254
+ return `niji ${niji[1]}`;
255
+ return undefined;
256
+ }
257
+ /** Upscale one image from a grid. `subtle` keeps it; `creative` reworks detail. */
258
+ export async function submitUpscale(client, jobId, index, style, options = {}) {
259
+ const version = options.version ?? versionOfJob(await findJob(client, jobId));
260
+ return submitShape(client, { t: "upscale", type: upscalerFor(version, style), id: jobId.trim(), index }, options);
261
+ }
262
+ /**
263
+ * Animate one image into a video.
264
+ *
265
+ * Two things here are not guessable and both cost a rejected request to learn.
266
+ * The source is nested under `parentJob` as `image_num`, not the flat `index`
267
+ * every other job type uses. And `newPrompt` carries the image's own prompt,
268
+ * not a description of the motion: passing "slow push in" on its own is
269
+ * refused, because the field is the scene, not the movement.
270
+ *
271
+ * So a motion note is appended to the original prompt rather than replacing
272
+ * it, which is what the web app does when you type into its box.
273
+ */
274
+ export async function submitVideo(client, jobId, index, opts = {}) {
275
+ const source = await findJob(client, jobId);
276
+ const original = (source?.prompt ?? "").trim();
277
+ const motion = opts.motion?.trim();
278
+ const newPrompt = motion ? `${original} ${motion}`.trim() : original;
279
+ return submitShape(client, {
280
+ t: "video",
281
+ videoType: `vid_1.1_i2v_${opts.resolution ?? "480"}`,
282
+ stitch: null,
283
+ newPrompt,
284
+ parentJob: { job_id: jobId.trim(), image_num: index },
285
+ animateMode: opts.mode ?? (motion ? "manual" : "auto"),
286
+ }, opts);
287
+ }
288
+ /** Extend the frame in one direction. */
289
+ export function submitPan(client, jobId, index, direction, opts = {}) {
290
+ return submitShape(client, {
291
+ t: "pan",
292
+ newPrompt: opts.prompt ?? "",
293
+ direction,
294
+ fraction: opts.fraction ?? 0.5,
295
+ stitch: true,
296
+ id: jobId.trim(),
297
+ index,
298
+ }, opts);
299
+ }
300
+ /** Zoom out, filling the new space. 100 is no change; 200 doubles the frame. */
301
+ export function submitZoomOut(client, jobId, index, opts = {}) {
302
+ return submitShape(client, {
303
+ t: "outpaint",
304
+ newPrompt: opts.prompt ?? "",
305
+ zoomFactor: opts.zoomFactor ?? 200,
306
+ id: jobId.trim(),
307
+ index,
308
+ }, opts);
309
+ }
310
+ /** Re-render one image against a new prompt, keeping its composition. */
311
+ export function submitRemix(client, jobId, index, prompt, opts = {}) {
312
+ return submitShape(client, { t: "remix", strong: opts.strong ?? false, newPrompt: prompt, id: jobId.trim(), index }, opts);
313
+ }
190
314
  /** Recent jobs for the signed-in account. */
191
315
  export async function listJobs(client, options = {}) {
192
316
  const userId = options.userId ?? (await client.userId());