@thenavidm/midjourney-mcp-cli 1.0.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.
Files changed (76) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +677 -0
  3. package/SKILL.md +184 -0
  4. package/dist/api/client.d.ts +72 -0
  5. package/dist/api/client.js +278 -0
  6. package/dist/api/client.js.map +1 -0
  7. package/dist/api/download.d.ts +41 -0
  8. package/dist/api/download.js +108 -0
  9. package/dist/api/download.js.map +1 -0
  10. package/dist/api/errors.d.ts +75 -0
  11. package/dist/api/errors.js +166 -0
  12. package/dist/api/errors.js.map +1 -0
  13. package/dist/api/jobs.d.ts +140 -0
  14. package/dist/api/jobs.js +296 -0
  15. package/dist/api/jobs.js.map +1 -0
  16. package/dist/api/moodboards.d.ts +88 -0
  17. package/dist/api/moodboards.js +189 -0
  18. package/dist/api/moodboards.js.map +1 -0
  19. package/dist/capture.d.ts +27 -0
  20. package/dist/capture.js +162 -0
  21. package/dist/capture.js.map +1 -0
  22. package/dist/cli.d.ts +92 -0
  23. package/dist/cli.js +633 -0
  24. package/dist/cli.js.map +1 -0
  25. package/dist/config.d.ts +37 -0
  26. package/dist/config.js +90 -0
  27. package/dist/config.js.map +1 -0
  28. package/dist/content/prompt.d.ts +69 -0
  29. package/dist/content/prompt.js +173 -0
  30. package/dist/content/prompt.js.map +1 -0
  31. package/dist/doctor.d.ts +19 -0
  32. package/dist/doctor.js +161 -0
  33. package/dist/doctor.js.map +1 -0
  34. package/dist/format/jobs.d.ts +71 -0
  35. package/dist/format/jobs.js +211 -0
  36. package/dist/format/jobs.js.map +1 -0
  37. package/dist/index.d.ts +13 -0
  38. package/dist/index.js +161 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/safety.d.ts +52 -0
  41. package/dist/safety.js +100 -0
  42. package/dist/safety.js.map +1 -0
  43. package/dist/server.d.ts +10 -0
  44. package/dist/server.js +57 -0
  45. package/dist/server.js.map +1 -0
  46. package/dist/tools/create.d.ts +96 -0
  47. package/dist/tools/create.js +375 -0
  48. package/dist/tools/create.js.map +1 -0
  49. package/dist/tools/download.d.ts +17 -0
  50. package/dist/tools/download.js +47 -0
  51. package/dist/tools/download.js.map +1 -0
  52. package/dist/tools/explore.d.ts +8 -0
  53. package/dist/tools/explore.js +57 -0
  54. package/dist/tools/explore.js.map +1 -0
  55. package/dist/tools/index.d.ts +3 -0
  56. package/dist/tools/index.js +16 -0
  57. package/dist/tools/index.js.map +1 -0
  58. package/dist/tools/jobs.d.ts +18 -0
  59. package/dist/tools/jobs.js +92 -0
  60. package/dist/tools/jobs.js.map +1 -0
  61. package/dist/tools/kit.d.ts +84 -0
  62. package/dist/tools/kit.js +104 -0
  63. package/dist/tools/kit.js.map +1 -0
  64. package/dist/tools/library.d.ts +22 -0
  65. package/dist/tools/library.js +185 -0
  66. package/dist/tools/library.js.map +1 -0
  67. package/dist/tools/profile.d.ts +2 -0
  68. package/dist/tools/profile.js +89 -0
  69. package/dist/tools/profile.js.map +1 -0
  70. package/dist/transport/cdp.d.ts +217 -0
  71. package/dist/transport/cdp.js +607 -0
  72. package/dist/transport/cdp.js.map +1 -0
  73. package/dist/transport/http.d.ts +18 -0
  74. package/dist/transport/http.js +48 -0
  75. package/dist/transport/http.js.map +1 -0
  76. package/package.json +72 -0
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Getting the actual files onto disk.
3
+ *
4
+ * Worth spelling out why this is not a fetch. Midjourney's CDN refuses plain
5
+ * server-side clients, and it does not send CORS headers that would let a
6
+ * script inside midjourney.com read the bytes either. Both obvious routes are
7
+ * closed, which is why the reference CLI for this API falls back to
8
+ * screenshotting the rendered <img> element.
9
+ *
10
+ * A screenshot is not the file. It is re-encoded, clipped to the element box at
11
+ * whatever size the page happened to lay it out, and stripped of everything the
12
+ * original carried. Downloading a 2048px upscale and getting a 512px PNG of how
13
+ * it looked in a browser window is not a download.
14
+ *
15
+ * Navigating a throwaway tab straight to the asset and reading the bytes back
16
+ * out of the resource cache avoids both problems: a top-level navigation is not
17
+ * a cross-origin subresource request, so CORS does not apply, and the browser
18
+ * is a browser, so the CDN serves it.
19
+ */
20
+ import { mkdir, writeFile } from "node:fs/promises";
21
+ import { basename, extname, join, resolve } from "node:path";
22
+ import { MidjourneyError, NotFoundError, ValidationError } from "./errors.js";
23
+ import { findJob } from "./jobs.js";
24
+ import { mimeFromUrl } from "../transport/cdp.js";
25
+ /** A filesystem-safe name derived from the asset URL, falling back to the job id. */
26
+ export function fileNameFor(url, jobId, index) {
27
+ let candidate = "";
28
+ try {
29
+ candidate = basename(new URL(url).pathname);
30
+ }
31
+ catch {
32
+ candidate = "";
33
+ }
34
+ const extension = extname(candidate) || extensionFor(mimeFromUrl(url));
35
+ const stem = candidate ? candidate.slice(0, candidate.length - extname(candidate).length) : "";
36
+ // Midjourney names most assets `0_<index>.png`, which collides across every
37
+ // job in a folder, so the job id goes in front. The stem itself is dropped
38
+ // when it is just the grid position again: `<job>-0-0_0.png` says nothing
39
+ // that `<job>-0.png` does not.
40
+ const redundant = /^\d+_\d+$/.test(stem);
41
+ const safeStem = redundant ? "" : stem.replace(/[^a-zA-Z0-9._-]/g, "-").slice(0, 60);
42
+ return `${jobId}-${index}${safeStem ? `-${safeStem}` : ""}${extension}`;
43
+ }
44
+ function extensionFor(mime) {
45
+ switch (mime) {
46
+ case "image/png":
47
+ return ".png";
48
+ case "image/jpeg":
49
+ return ".jpg";
50
+ case "image/webp":
51
+ return ".webp";
52
+ case "image/gif":
53
+ return ".gif";
54
+ case "video/mp4":
55
+ return ".mp4";
56
+ case "video/webm":
57
+ return ".webm";
58
+ default:
59
+ return ".bin";
60
+ }
61
+ }
62
+ /** Download one URL to a directory. */
63
+ export async function saveUrl(client, url, outDir, fileName) {
64
+ if (!/^https?:\/\//i.test(url)) {
65
+ throw new ValidationError(`Not a downloadable URL: '${url}'.`, 0, "(local)");
66
+ }
67
+ const { base64, mimeType } = await client.transport.fetchBinary(url);
68
+ const buffer = Buffer.from(base64, "base64");
69
+ if (buffer.byteLength === 0) {
70
+ throw new MidjourneyError(`The browser fetched ${url} but the body was empty. The asset may have expired, or the session may not have access to it.`, 0, "(download)");
71
+ }
72
+ const directory = resolve(outDir);
73
+ await mkdir(directory, { recursive: true });
74
+ const path = join(directory, fileName);
75
+ await writeFile(path, buffer);
76
+ return { url, path, bytes: buffer.byteLength, mime_type: mimeType };
77
+ }
78
+ /** Download every rendered image on a job, or a chosen subset. */
79
+ export async function downloadJob(client, jobId, options = {}) {
80
+ const job = await findJob(client, jobId);
81
+ if (!job) {
82
+ throw new NotFoundError(`No job ${jobId} in this account's recent history. Only jobs still in the imagine feed can be resolved; older work has to be downloaded from the web app.`, 404, "(download)");
83
+ }
84
+ if (job.images.length === 0) {
85
+ throw new NotFoundError(`Job ${jobId} has no rendered images. Its status is '${job.status}'${job.rawStatus ? ` (${job.rawStatus})` : ""}, so it may still be running, or it may have been moderated.`, 404, "(download)");
86
+ }
87
+ const wanted = options.indexes && options.indexes.length > 0
88
+ ? options.indexes
89
+ : job.images.map((_, index) => index);
90
+ const outDir = options.outDir ?? client.config.downloadDir;
91
+ const saved = [];
92
+ const skipped = [];
93
+ for (const index of wanted) {
94
+ const url = job.images[index];
95
+ if (!url) {
96
+ skipped.push(`index ${index}: this job has ${job.images.length} image(s)`);
97
+ continue;
98
+ }
99
+ try {
100
+ saved.push(await saveUrl(client, url, outDir, fileNameFor(url, job.id, index)));
101
+ }
102
+ catch (error) {
103
+ skipped.push(`index ${index}: ${error.message}`);
104
+ }
105
+ }
106
+ return { job_id: job.id, saved, skipped };
107
+ }
108
+ //# sourceMappingURL=download.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"download.js","sourceRoot":"","sources":["../../src/api/download.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Typed failures, each one carrying the fix.
3
+ *
4
+ * Midjourney has no error contract to lean on: it is a web app talking to its
5
+ * own backend, so a failure arrives as a bare status, an HTML interstitial, or
6
+ * a redirect to the sign-in page. Handing a model "HTTP 403" from any of those
7
+ * tells it nothing and it gives up. Every failure here is classified into one
8
+ * of a small number of things that actually went wrong, and each says what to
9
+ * do about it, because the recovery is genuinely different every time: log in
10
+ * again, wait for a challenge to clear, buy more fast hours, fix the prompt.
11
+ */
12
+ export declare class MidjourneyError extends Error {
13
+ readonly status: number;
14
+ readonly endpoint: string;
15
+ readonly detail: string;
16
+ constructor(message: string, status: number, endpoint: string, detail?: string);
17
+ toJSON(): Record<string, unknown>;
18
+ }
19
+ /** Chrome could not be found, started, or driven. Nothing reached Midjourney. */
20
+ export declare class BrowserError extends MidjourneyError {
21
+ constructor(message: string);
22
+ }
23
+ /** The browser profile is not signed in to Midjourney. */
24
+ export declare class NotSignedInError extends MidjourneyError {
25
+ }
26
+ /**
27
+ * Cloudflare served an interstitial instead of the API.
28
+ *
29
+ * Distinct from a sign-in failure and worth its own class, because the fix is
30
+ * to let the browser window solve it once, not to re-authenticate.
31
+ */
32
+ export declare class ChallengeError extends MidjourneyError {
33
+ }
34
+ /** Midjourney rejected the request as malformed, or the prompt as unacceptable. */
35
+ export declare class ValidationError extends MidjourneyError {
36
+ }
37
+ /** The job, folder or asset is not there. */
38
+ export declare class NotFoundError extends MidjourneyError {
39
+ }
40
+ /** Too many requests, or out of fast hours. */
41
+ export declare class RateLimitError extends MidjourneyError {
42
+ }
43
+ /** Upstream 5xx. Usually transient. */
44
+ export declare class ServerError extends MidjourneyError {
45
+ }
46
+ /** Our own deadline expired. */
47
+ export declare class TimeoutError extends MidjourneyError {
48
+ }
49
+ /** A job was submitted but never reached a terminal state in time. */
50
+ export declare class JobTimeoutError extends MidjourneyError {
51
+ readonly jobId: string;
52
+ constructor(message: string, jobId: string);
53
+ toJSON(): Record<string, unknown>;
54
+ }
55
+ /** Writes are off, or a spending tool was called without confirmation. */
56
+ export declare class WriteBlockedError extends MidjourneyError {
57
+ constructor(message: string);
58
+ }
59
+ /**
60
+ * Is this body a Cloudflare interstitial rather than an answer?
61
+ *
62
+ * The challenge is served with a 403 and an HTML body, and so is a genuine
63
+ * permission failure, so the status alone cannot separate them. These markers
64
+ * are the ones Cloudflare has kept stable across its managed-challenge and
65
+ * JS-challenge pages.
66
+ */
67
+ export declare function looksLikeChallenge(body: string): boolean;
68
+ /** Is this the sign-in page, or a body that only makes sense when logged out? */
69
+ export declare function looksSignedOut(body: string, status: number): boolean;
70
+ /** Pull a human-usable message out of whatever came back. */
71
+ export declare function describeBody(body: string): string;
72
+ /** Turn a page-side response into the right error class. */
73
+ export declare function errorFor(status: number, endpoint: string, body: string): MidjourneyError;
74
+ /** Statuses worth another attempt. */
75
+ export declare const RETRYABLE: Set<number>;
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Typed failures, each one carrying the fix.
3
+ *
4
+ * Midjourney has no error contract to lean on: it is a web app talking to its
5
+ * own backend, so a failure arrives as a bare status, an HTML interstitial, or
6
+ * a redirect to the sign-in page. Handing a model "HTTP 403" from any of those
7
+ * tells it nothing and it gives up. Every failure here is classified into one
8
+ * of a small number of things that actually went wrong, and each says what to
9
+ * do about it, because the recovery is genuinely different every time: log in
10
+ * again, wait for a challenge to clear, buy more fast hours, fix the prompt.
11
+ */
12
+ export class MidjourneyError extends Error {
13
+ status;
14
+ endpoint;
15
+ detail;
16
+ constructor(message, status, endpoint, detail = "") {
17
+ super(message);
18
+ this.name = new.target.name;
19
+ this.status = status;
20
+ this.endpoint = endpoint;
21
+ this.detail = detail;
22
+ }
23
+ toJSON() {
24
+ return {
25
+ error: this.message,
26
+ type: this.name,
27
+ ...(this.status ? { status: this.status } : {}),
28
+ endpoint: this.endpoint,
29
+ ...(this.detail ? { detail: this.detail.slice(0, 500) } : {}),
30
+ };
31
+ }
32
+ }
33
+ /** Chrome could not be found, started, or driven. Nothing reached Midjourney. */
34
+ export class BrowserError extends MidjourneyError {
35
+ constructor(message) {
36
+ super(message, 0, "(browser)", "");
37
+ }
38
+ }
39
+ /** The browser profile is not signed in to Midjourney. */
40
+ export class NotSignedInError extends MidjourneyError {
41
+ }
42
+ /**
43
+ * Cloudflare served an interstitial instead of the API.
44
+ *
45
+ * Distinct from a sign-in failure and worth its own class, because the fix is
46
+ * to let the browser window solve it once, not to re-authenticate.
47
+ */
48
+ export class ChallengeError extends MidjourneyError {
49
+ }
50
+ /** Midjourney rejected the request as malformed, or the prompt as unacceptable. */
51
+ export class ValidationError extends MidjourneyError {
52
+ }
53
+ /** The job, folder or asset is not there. */
54
+ export class NotFoundError extends MidjourneyError {
55
+ }
56
+ /** Too many requests, or out of fast hours. */
57
+ export class RateLimitError extends MidjourneyError {
58
+ }
59
+ /** Upstream 5xx. Usually transient. */
60
+ export class ServerError extends MidjourneyError {
61
+ }
62
+ /** Our own deadline expired. */
63
+ export class TimeoutError extends MidjourneyError {
64
+ }
65
+ /** A job was submitted but never reached a terminal state in time. */
66
+ export class JobTimeoutError extends MidjourneyError {
67
+ jobId;
68
+ constructor(message, jobId) {
69
+ super(message, 0, "(local)", "");
70
+ this.jobId = jobId;
71
+ }
72
+ toJSON() {
73
+ return { ...super.toJSON(), job_id: this.jobId };
74
+ }
75
+ }
76
+ /** Writes are off, or a spending tool was called without confirmation. */
77
+ export class WriteBlockedError extends MidjourneyError {
78
+ constructor(message) {
79
+ super(message, 0, "(local)", "");
80
+ }
81
+ }
82
+ /**
83
+ * Is this body a Cloudflare interstitial rather than an answer?
84
+ *
85
+ * The challenge is served with a 403 and an HTML body, and so is a genuine
86
+ * permission failure, so the status alone cannot separate them. These markers
87
+ * are the ones Cloudflare has kept stable across its managed-challenge and
88
+ * JS-challenge pages.
89
+ */
90
+ export function looksLikeChallenge(body) {
91
+ const head = body.slice(0, 4000).toLowerCase();
92
+ return (head.includes("just a moment") ||
93
+ head.includes("cf-browser-verification") ||
94
+ head.includes("cf_chl_opt") ||
95
+ head.includes("challenges.cloudflare.com") ||
96
+ head.includes("enable javascript and cookies to continue"));
97
+ }
98
+ /** Is this the sign-in page, or a body that only makes sense when logged out? */
99
+ export function looksSignedOut(body, status) {
100
+ if (status === 401)
101
+ return true;
102
+ const head = body.slice(0, 4000).toLowerCase();
103
+ return (head.includes('"error":"unauthorized"') ||
104
+ head.includes("you must be logged in") ||
105
+ (head.includes("<html") && head.includes("/auth/signin")));
106
+ }
107
+ /** Pull a human-usable message out of whatever came back. */
108
+ export function describeBody(body) {
109
+ const text = body.trim();
110
+ if (!text)
111
+ return "";
112
+ try {
113
+ const parsed = JSON.parse(text);
114
+ if (parsed && typeof parsed === "object") {
115
+ const record = parsed;
116
+ for (const key of ["message", "error", "detail", "reason"]) {
117
+ const value = record[key];
118
+ if (typeof value === "string" && value)
119
+ return value.slice(0, 500);
120
+ }
121
+ }
122
+ return text.slice(0, 500);
123
+ }
124
+ catch {
125
+ // HTML or plain text. Strip tags so an error page does not drown the message.
126
+ return text
127
+ .replace(/<script[\s\S]*?<\/script>/gi, " ")
128
+ .replace(/<style[\s\S]*?<\/style>/gi, " ")
129
+ .replace(/<[^>]+>/g, " ")
130
+ .replace(/\s+/g, " ")
131
+ .trim()
132
+ .slice(0, 300);
133
+ }
134
+ }
135
+ /** Turn a page-side response into the right error class. */
136
+ export function errorFor(status, endpoint, body) {
137
+ const detail = describeBody(body);
138
+ if (looksLikeChallenge(body)) {
139
+ return new ChallengeError(`Cloudflare served a challenge instead of ${endpoint}. Open the Midjourney window this server controls and let the check finish, then try again. Run \`midjourney-cli doctor\` to see the browser state.`, status, endpoint, detail);
140
+ }
141
+ if (looksSignedOut(body, status)) {
142
+ return new NotSignedInError(`The browser profile is not signed in to Midjourney, so ${endpoint} was refused. Run \`midjourney-cli login\` to open the window and sign in once. The session then persists.`, status, endpoint, detail);
143
+ }
144
+ if (status === 429) {
145
+ return new RateLimitError(`Midjourney rate limited ${endpoint}. The client already spaces requests out and retries; this failed after the last attempt.`, status, endpoint, detail);
146
+ }
147
+ if (status === 402) {
148
+ return new RateLimitError(`Midjourney refused ${endpoint} for billing reasons. This usually means the plan is out of fast hours, or the subscription lapsed.`, status, endpoint, detail);
149
+ }
150
+ if (status === 403) {
151
+ return new NotSignedInError(`Midjourney refused ${endpoint} for this account. Either the session lapsed, or the plan does not include this feature.`, status, endpoint, detail);
152
+ }
153
+ if (status === 400 || status === 422) {
154
+ return new ValidationError(`Midjourney rejected the request to ${endpoint}. ${detail || "Check the prompt and its parameters."}`, status, endpoint, detail);
155
+ }
156
+ if (status === 404) {
157
+ return new NotFoundError(`Not found via ${endpoint}. Check the job id. A job belonging to another account looks the same as one that never existed.`, status, endpoint, detail);
158
+ }
159
+ if (status >= 500) {
160
+ return new ServerError(`Midjourney returned ${status} for ${endpoint}. This is upstream and usually transient.`, status, endpoint, detail);
161
+ }
162
+ return new MidjourneyError(`Midjourney returned ${status} for ${endpoint}.`, status, endpoint, detail);
163
+ }
164
+ /** Statuses worth another attempt. */
165
+ export const RETRYABLE = new Set([429, 500, 502, 503, 504]);
166
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/api/errors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Submitting work and following it to the end.
3
+ *
4
+ * This is the part the reference CLI for this API does not have, and it is the
5
+ * part that makes the difference between a toy and something an agent can use.
6
+ * Submitting returns a job id in about a second; the image does not exist for
7
+ * another thirty to ninety. A tool that returns the id and stops has handed the
8
+ * caller a polling loop to write, and a model asked to "make me a logo" will
9
+ * either return an id nobody can use or invent a wait and guess wrong.
10
+ *
11
+ * So submission and completion are one operation here, with the polling,
12
+ * back-off and terminal-state detection on this side of the boundary.
13
+ */
14
+ import type { MidjourneyClient } from "./client.js";
15
+ import { type Job } from "../format/jobs.js";
16
+ export type Speed = "fast" | "relax" | "turbo";
17
+ export declare const ENDPOINTS: {
18
+ readonly submit: "/api/submit-jobs";
19
+ readonly jobs: "/api/imagine";
20
+ readonly updates: "/api/imagine-update";
21
+ readonly queue: "/api/user-queue";
22
+ readonly folders: "/api/folders";
23
+ readonly moodboards: "/api/moodboards";
24
+ readonly storage: "/api/storage";
25
+ readonly explore: "/api/explore";
26
+ readonly exploreStyleLikes: "/api/explore-styles-likes";
27
+ readonly personalizedProfiles: "/api/personalized-profiles";
28
+ readonly following: "/api/following-for-user";
29
+ readonly modelRatings: "/api/model-ratings";
30
+ readonly contestsRankingCount: "/api/contests-ranking-count";
31
+ readonly jobStatus: "/api/job-status";
32
+ };
33
+ /** `singleplayer_<uuid>` is what the web app calls a solo user's own channel. */
34
+ export declare function channelIdFor(userId: string): string;
35
+ export type SubmitOptions = {
36
+ speed?: Speed;
37
+ /**
38
+ * Reload the open window once the request lands.
39
+ *
40
+ * Off for a call that is about to wait: reloading tears down the page context
41
+ * the poll loop is talking to, so every poll then pays for a reconnect and an
42
+ * eleven-second job takes minutes. A waiting caller refreshes once at the end
43
+ * instead, which is also the only moment there is anything to look at.
44
+ */
45
+ refresh?: boolean;
46
+ private?: boolean;
47
+ /** Counts the web app reports alongside a submission. */
48
+ imagePromptCount?: number;
49
+ styleRefCount?: number;
50
+ omniRefCount?: number;
51
+ };
52
+ /** Job ids out of a submission response, whatever it is wrapped in. */
53
+ export declare function extractJobIds(payload: unknown, depth?: number): string[];
54
+ /** Submit an imagine. Returns the job ids Midjourney accepted. */
55
+ export declare function submitImagine(client: MidjourneyClient, prompt: string, options?: SubmitOptions): Promise<{
56
+ jobIds: string[];
57
+ raw: unknown;
58
+ }>;
59
+ /** Re-run an existing job, unchanged or with a new prompt. */
60
+ export declare function submitRerun(client: MidjourneyClient, jobId: string, options?: SubmitOptions & {
61
+ newPrompt?: string;
62
+ }): Promise<{
63
+ jobIds: string[];
64
+ raw: unknown;
65
+ }>;
66
+ /**
67
+ * Vary one image from a finished grid.
68
+ *
69
+ * Captured from the web app rather than guessed: `Vary Subtle` and `Vary
70
+ * Strong` are the same job type with a `strong` boolean, addressing one tile by
71
+ * `index`. Note the metadata block here is all nulls, where an imagine sends
72
+ * counts. That difference is what the app sends, so it is what we send.
73
+ */
74
+ export declare function submitVary(client: MidjourneyClient, jobId: string, index: number, options?: SubmitOptions & {
75
+ strong?: boolean;
76
+ }): Promise<{
77
+ jobIds: string[];
78
+ raw: unknown;
79
+ }>;
80
+ /**
81
+ * Submit an arbitrary job type.
82
+ *
83
+ * The escape hatch, and deliberately not dressed up as anything else. Only
84
+ * `imagine` and `reroll` are confirmed against observed traffic. The web app
85
+ * sends other values of `t` for upscales and variations, but guessing at their
86
+ * payloads and shipping them as named tools would mean charging the user for
87
+ * requests that quietly do nothing. Capture the real traffic first:
88
+ * `midjourney-cli capture` records what the app actually sends.
89
+ */
90
+ export declare function submitRaw(client: MidjourneyClient, jobType: string, extra: Record<string, unknown>, options?: SubmitOptions): Promise<{
91
+ jobIds: string[];
92
+ raw: unknown;
93
+ }>;
94
+ export type ListOptions = {
95
+ limit?: number;
96
+ cursor?: string;
97
+ userId?: string;
98
+ };
99
+ /** Recent jobs for the signed-in account. */
100
+ export declare function listJobs(client: MidjourneyClient, options?: ListOptions): Promise<{
101
+ jobs: Job[];
102
+ raw: unknown;
103
+ }>;
104
+ /** The update feed, which is what the web app polls while work is running. */
105
+ export declare function jobUpdates(client: MidjourneyClient, options?: ListOptions & {
106
+ checkpoint?: string;
107
+ }): Promise<{
108
+ jobs: Job[];
109
+ raw: unknown;
110
+ }>;
111
+ /**
112
+ * Ask about specific jobs by id.
113
+ *
114
+ * The one endpoint that answers "is this done?" honestly. It carries
115
+ * `current_status`, and it works for a job that has not reached the history
116
+ * feed yet, which is every job while it is still rendering.
117
+ *
118
+ * Everything else here was built before this was found, by inferring completion
119
+ * from whether image URLs could be derived. That inference happened to be right
120
+ * because the history feed only lists finished work, but it could never have
121
+ * reported "running" for anything.
122
+ */
123
+ export declare function jobStatus(client: MidjourneyClient, jobIds: string[]): Promise<Job[]>;
124
+ /** One job by id, asking the status endpoint first. */
125
+ export declare function findJob(client: MidjourneyClient, jobId: string): Promise<Job | undefined>;
126
+ export type WaitOptions = {
127
+ timeoutMs?: number;
128
+ pollIntervalMs?: number;
129
+ /** Called on every poll, for a CLI progress line. */
130
+ onProgress?: (job: Job | undefined, elapsedMs: number) => void;
131
+ };
132
+ /**
133
+ * Poll until a job reaches a terminal state.
134
+ *
135
+ * The interval widens as the wait goes on. A generation is quick at fast speed
136
+ * and can be twenty minutes at relax, and polling every three seconds for
137
+ * twenty minutes is four hundred requests nobody needs, on an endpoint we would
138
+ * rather not be conspicuous on.
139
+ */
140
+ export declare function waitForJob(client: MidjourneyClient, jobId: string, options?: WaitOptions): Promise<Job>;