@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,37 @@
1
+ /**
2
+ * Everything the server reads from the environment, in one place.
3
+ *
4
+ * Environment variables rather than flags, because a user configuring an MCP
5
+ * client is already editing a JSON `env` block, and flags would mean editing
6
+ * `args` separately for the same setting.
7
+ *
8
+ * There are no credentials here, and that is the point. The browser profile
9
+ * holds the session; this process never sees a cookie, a token or a password.
10
+ */
11
+ export type Speed = "fast" | "relax" | "turbo";
12
+ export type Config = {
13
+ origin: string;
14
+ cdpUrl: string;
15
+ profileDir: string;
16
+ chromePath?: string;
17
+ autoLaunch: boolean;
18
+ headless: boolean;
19
+ /** Midjourney's own id for the signed-in user. Discovered when not set. */
20
+ userId?: string;
21
+ requestTimeoutMs: number;
22
+ minRequestIntervalMs: number;
23
+ maxRetries: number;
24
+ jobTimeoutMs: number;
25
+ jobPollIntervalMs: number;
26
+ downloadDir: string;
27
+ defaultSpeed: Speed;
28
+ defaultVersion: string;
29
+ /** Reload the open window after a generation, so it shows the new work. */
30
+ refreshView: boolean;
31
+ readOnly: boolean;
32
+ allowDestructive: boolean;
33
+ auditPath?: string;
34
+ };
35
+ export declare function loadConfig(): Config;
36
+ /** Every variable the code reads, so `--help`, the README and the tests agree. */
37
+ export declare const ENV_VARS: readonly ["MIDJOURNEY_ORIGIN", "MIDJOURNEY_CDP_URL", "MIDJOURNEY_CHROME_PROFILE", "MIDJOURNEY_CHROME_PATH", "MIDJOURNEY_CHROME_LAUNCH", "MIDJOURNEY_HEADLESS", "MIDJOURNEY_USER_ID", "MIDJOURNEY_REQUEST_TIMEOUT_MS", "MIDJOURNEY_MIN_REQUEST_INTERVAL_MS", "MIDJOURNEY_MAX_RETRIES", "MIDJOURNEY_JOB_TIMEOUT_MS", "MIDJOURNEY_JOB_POLL_INTERVAL_MS", "MIDJOURNEY_DOWNLOAD_DIR", "MIDJOURNEY_DEFAULT_SPEED", "MIDJOURNEY_DEFAULT_VERSION", "MIDJOURNEY_REFRESH_VIEW", "MIDJOURNEY_READ_ONLY", "MIDJOURNEY_ALLOW_DESTRUCTIVE", "MIDJOURNEY_AUDIT_LOG", "MIDJOURNEY_HTTP_PORT", "MIDJOURNEY_HTTP_HOST", "MIDJOURNEY_HTTP_TOKEN"];
package/dist/config.js ADDED
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Everything the server reads from the environment, in one place.
3
+ *
4
+ * Environment variables rather than flags, because a user configuring an MCP
5
+ * client is already editing a JSON `env` block, and flags would mean editing
6
+ * `args` separately for the same setting.
7
+ *
8
+ * There are no credentials here, and that is the point. The browser profile
9
+ * holds the session; this process never sees a cookie, a token or a password.
10
+ */
11
+ import { homedir } from "node:os";
12
+ import { join } from "node:path";
13
+ import { defaultProfileDir } from "./transport/cdp.js";
14
+ function bool(name, fallback) {
15
+ const raw = process.env[name];
16
+ if (raw === undefined || raw.trim() === "")
17
+ return fallback;
18
+ return !["0", "false", "no", "off"].includes(raw.trim().toLowerCase());
19
+ }
20
+ function int(name, fallback, min, max) {
21
+ const raw = process.env[name];
22
+ if (raw === undefined || raw.trim() === "")
23
+ return fallback;
24
+ const value = Number(raw);
25
+ if (!Number.isFinite(value))
26
+ return fallback;
27
+ return Math.min(Math.max(Math.trunc(value), min), max);
28
+ }
29
+ function str(name) {
30
+ const raw = process.env[name];
31
+ return raw && raw.trim() ? raw.trim() : undefined;
32
+ }
33
+ function speed(name, fallback) {
34
+ const raw = str(name)?.toLowerCase();
35
+ return raw === "fast" || raw === "relax" || raw === "turbo" ? raw : fallback;
36
+ }
37
+ export function loadConfig() {
38
+ return {
39
+ origin: str("MIDJOURNEY_ORIGIN") ?? "https://www.midjourney.com",
40
+ cdpUrl: str("MIDJOURNEY_CDP_URL") ?? "http://127.0.0.1:9222",
41
+ profileDir: str("MIDJOURNEY_CHROME_PROFILE") ?? defaultProfileDir(),
42
+ chromePath: str("MIDJOURNEY_CHROME_PATH"),
43
+ autoLaunch: bool("MIDJOURNEY_CHROME_LAUNCH", true),
44
+ // Signing in needs a window the user can see, and Cloudflare treats new
45
+ // headless sessions harshly, so a visible window is the working default.
46
+ headless: bool("MIDJOURNEY_HEADLESS", false),
47
+ userId: str("MIDJOURNEY_USER_ID"),
48
+ requestTimeoutMs: int("MIDJOURNEY_REQUEST_TIMEOUT_MS", 30_000, 1_000, 300_000),
49
+ // Paced rather than hammered. The web app does not fire requests back to
50
+ // back and neither should we; this is the difference between a session that
51
+ // lasts and one that trips a bot heuristic.
52
+ minRequestIntervalMs: int("MIDJOURNEY_MIN_REQUEST_INTERVAL_MS", 700, 0, 60_000),
53
+ maxRetries: int("MIDJOURNEY_MAX_RETRIES", 3, 0, 10),
54
+ jobTimeoutMs: int("MIDJOURNEY_JOB_TIMEOUT_MS", 600_000, 10_000, 3_600_000),
55
+ jobPollIntervalMs: int("MIDJOURNEY_JOB_POLL_INTERVAL_MS", 3_000, 1_000, 60_000),
56
+ downloadDir: str("MIDJOURNEY_DOWNLOAD_DIR") ?? join(homedir(), "Downloads", "midjourney"),
57
+ defaultSpeed: speed("MIDJOURNEY_DEFAULT_SPEED", "fast"),
58
+ defaultVersion: str("MIDJOURNEY_DEFAULT_VERSION") ?? "8.1",
59
+ refreshView: bool("MIDJOURNEY_REFRESH_VIEW", true),
60
+ readOnly: bool("MIDJOURNEY_READ_ONLY", false),
61
+ allowDestructive: bool("MIDJOURNEY_ALLOW_DESTRUCTIVE", true),
62
+ auditPath: str("MIDJOURNEY_AUDIT_LOG"),
63
+ };
64
+ }
65
+ /** Every variable the code reads, so `--help`, the README and the tests agree. */
66
+ export const ENV_VARS = [
67
+ "MIDJOURNEY_ORIGIN",
68
+ "MIDJOURNEY_CDP_URL",
69
+ "MIDJOURNEY_CHROME_PROFILE",
70
+ "MIDJOURNEY_CHROME_PATH",
71
+ "MIDJOURNEY_CHROME_LAUNCH",
72
+ "MIDJOURNEY_HEADLESS",
73
+ "MIDJOURNEY_USER_ID",
74
+ "MIDJOURNEY_REQUEST_TIMEOUT_MS",
75
+ "MIDJOURNEY_MIN_REQUEST_INTERVAL_MS",
76
+ "MIDJOURNEY_MAX_RETRIES",
77
+ "MIDJOURNEY_JOB_TIMEOUT_MS",
78
+ "MIDJOURNEY_JOB_POLL_INTERVAL_MS",
79
+ "MIDJOURNEY_DOWNLOAD_DIR",
80
+ "MIDJOURNEY_DEFAULT_SPEED",
81
+ "MIDJOURNEY_DEFAULT_VERSION",
82
+ "MIDJOURNEY_REFRESH_VIEW",
83
+ "MIDJOURNEY_READ_ONLY",
84
+ "MIDJOURNEY_ALLOW_DESTRUCTIVE",
85
+ "MIDJOURNEY_AUDIT_LOG",
86
+ "MIDJOURNEY_HTTP_PORT",
87
+ "MIDJOURNEY_HTTP_HOST",
88
+ "MIDJOURNEY_HTTP_TOKEN",
89
+ ];
90
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Midjourney's prompt grammar, as a typed thing rather than string glue.
3
+ *
4
+ * A Midjourney prompt is text followed by double-dash parameters, and the
5
+ * parameters have real rules: ranges, mutual exclusions, values that only exist
6
+ * on some model versions. Building that by concatenation means every mistake
7
+ * costs a round trip and, on a paid plan, a fast-hour. Worse, Midjourney does
8
+ * not reject most bad values; it silently clamps or ignores them, so a wrong
9
+ * `--stylize 5000` looks like it worked and quietly did not.
10
+ *
11
+ * So the rules live here, checked before anything is spent. The reference
12
+ * implementation for this API passes these through as opaque strings; catching
13
+ * a bad `--ar` locally rather than after a minute of GPU time is most of why
14
+ * this file exists.
15
+ */
16
+ export type PromptParams = {
17
+ /** The subject text. Everything before the first parameter. */
18
+ text: string;
19
+ /** Image URLs used as image prompts. Prepended to the text, not a parameter. */
20
+ imagePrompts?: string[];
21
+ aspect?: string;
22
+ version?: string;
23
+ niji?: string;
24
+ stylize?: number;
25
+ chaos?: number;
26
+ weird?: number;
27
+ seed?: number;
28
+ quality?: number;
29
+ stop?: number;
30
+ repeat?: number;
31
+ exp?: number;
32
+ styleRefs?: string[];
33
+ styleWeight?: number;
34
+ omniRefs?: string[];
35
+ omniWeight?: number;
36
+ imageWeight?: number;
37
+ profile?: string;
38
+ style?: string;
39
+ raw?: boolean;
40
+ tile?: boolean;
41
+ draft?: boolean;
42
+ negative?: string;
43
+ };
44
+ /** `16:9`, `3:2`, `1:1`. Midjourney rejects ratios beyond roughly 1:4 to 4:1. */
45
+ export declare function validateAspect(aspect: string): void;
46
+ /**
47
+ * A style or omni reference: a URL, a numeric sref code, or `random`.
48
+ *
49
+ * Getting this wrong is a common and expensive mistake, because Midjourney
50
+ * treats an unrecognised `--sref` value as part of the prompt text rather than
51
+ * failing, so the reference is silently dropped and the image comes back
52
+ * looking almost right.
53
+ */
54
+ export declare function validateRef(value: string, flag: string): void;
55
+ /**
56
+ * Assemble the prompt Midjourney receives.
57
+ *
58
+ * Order matters to Midjourney: image prompts first, then the text, then
59
+ * parameters. Everything is validated before a single character is joined, so a
60
+ * refusal costs nothing.
61
+ */
62
+ export declare function buildPrompt(params: PromptParams, defaults: {
63
+ version: string;
64
+ }): string;
65
+ /** Split a raw prompt back into text and parameters, for echoing what was sent. */
66
+ export declare function splitPrompt(prompt: string): {
67
+ text: string;
68
+ params: string;
69
+ };
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Midjourney's prompt grammar, as a typed thing rather than string glue.
3
+ *
4
+ * A Midjourney prompt is text followed by double-dash parameters, and the
5
+ * parameters have real rules: ranges, mutual exclusions, values that only exist
6
+ * on some model versions. Building that by concatenation means every mistake
7
+ * costs a round trip and, on a paid plan, a fast-hour. Worse, Midjourney does
8
+ * not reject most bad values; it silently clamps or ignores them, so a wrong
9
+ * `--stylize 5000` looks like it worked and quietly did not.
10
+ *
11
+ * So the rules live here, checked before anything is spent. The reference
12
+ * implementation for this API passes these through as opaque strings; catching
13
+ * a bad `--ar` locally rather than after a minute of GPU time is most of why
14
+ * this file exists.
15
+ */
16
+ import { ValidationError } from "../api/errors.js";
17
+ const RANGES = {
18
+ stylize: { min: 0, max: 1000, label: "--stylize" },
19
+ chaos: { min: 0, max: 100, label: "--chaos" },
20
+ weird: { min: 0, max: 3000, label: "--weird" },
21
+ seed: { min: 0, max: 4_294_967_295, label: "--seed" },
22
+ stop: { min: 10, max: 100, label: "--stop" },
23
+ repeat: { min: 1, max: 40, label: "--repeat" },
24
+ exp: { min: 0, max: 100, label: "--exp" },
25
+ styleWeight: { min: 0, max: 1000, label: "--sw" },
26
+ omniWeight: { min: 0, max: 1000, label: "--ow" },
27
+ };
28
+ const QUALITY = [0.25, 0.5, 1, 2, 4];
29
+ function checkRange(value, key) {
30
+ if (value === undefined)
31
+ return;
32
+ const range = RANGES[key];
33
+ if (!range)
34
+ return;
35
+ if (!Number.isFinite(value) || value < range.min || value > range.max) {
36
+ throw new ValidationError(`${range.label} must be between ${range.min} and ${range.max}. Got ${value}. Midjourney clamps out-of-range values silently, so this is refused here instead.`, 0, "(local)");
37
+ }
38
+ }
39
+ /** `16:9`, `3:2`, `1:1`. Midjourney rejects ratios beyond roughly 1:4 to 4:1. */
40
+ export function validateAspect(aspect) {
41
+ const match = /^(\d{1,3}):(\d{1,3})$/.exec(aspect.trim());
42
+ if (!match) {
43
+ throw new ValidationError(`--ar must look like 16:9. Got '${aspect}'.`, 0, "(local)");
44
+ }
45
+ const width = Number(match[1]);
46
+ const height = Number(match[2]);
47
+ if (width === 0 || height === 0) {
48
+ throw new ValidationError(`--ar cannot have a zero side. Got '${aspect}'.`, 0, "(local)");
49
+ }
50
+ }
51
+ /**
52
+ * A style or omni reference: a URL, a numeric sref code, or `random`.
53
+ *
54
+ * Getting this wrong is a common and expensive mistake, because Midjourney
55
+ * treats an unrecognised `--sref` value as part of the prompt text rather than
56
+ * failing, so the reference is silently dropped and the image comes back
57
+ * looking almost right.
58
+ */
59
+ export function validateRef(value, flag) {
60
+ const trimmed = value.trim();
61
+ if (!trimmed)
62
+ throw new ValidationError(`${flag} cannot be empty.`, 0, "(local)");
63
+ if (trimmed.toLowerCase() === "random")
64
+ return;
65
+ if (/^\d+$/.test(trimmed))
66
+ return;
67
+ if (/^https?:\/\/\S+$/i.test(trimmed))
68
+ return;
69
+ throw new ValidationError(`${flag} must be an image URL, a numeric style code, or 'random'. Got '${trimmed}'. Midjourney does not reject a bad reference, it silently treats it as prompt text.`, 0, "(local)");
70
+ }
71
+ /**
72
+ * Midjourney's own asset hosts serve images from extensionless URLs.
73
+ *
74
+ * `https://s.mj.run/xE_f2o6GRl0` is what the web app produces when you drag an
75
+ * image in, and it is the form that appears in real prompts. An extension check
76
+ * alone rejects it, which would refuse the most common image prompt there is.
77
+ */
78
+ const MIDJOURNEY_ASSET_HOSTS = /^https?:\/\/(s\.mj\.run|cdn\.midjourney\.com|storage\.googleapis\.com)\//i;
79
+ function validateImagePrompt(value) {
80
+ const trimmed = value.trim();
81
+ if (MIDJOURNEY_ASSET_HOSTS.test(trimmed))
82
+ return;
83
+ if (/^https?:\/\/\S+\.(png|jpe?g|webp|gif)(\?\S*)?$/i.test(trimmed))
84
+ return;
85
+ throw new ValidationError(`An image prompt must be a direct URL to an image file, ending in .png, .jpg, .jpeg, .webp or .gif, or a Midjourney asset URL such as https://s.mj.run/... . Got '${value}'. A link to the page an image sits on will not work.`, 0, "(local)");
86
+ }
87
+ /**
88
+ * Assemble the prompt Midjourney receives.
89
+ *
90
+ * Order matters to Midjourney: image prompts first, then the text, then
91
+ * parameters. Everything is validated before a single character is joined, so a
92
+ * refusal costs nothing.
93
+ */
94
+ export function buildPrompt(params, defaults) {
95
+ const text = params.text.trim();
96
+ if (!text && (params.imagePrompts?.length ?? 0) === 0) {
97
+ throw new ValidationError("A prompt needs text, an image prompt, or both.", 0, "(local)");
98
+ }
99
+ if (params.aspect)
100
+ validateAspect(params.aspect);
101
+ for (const ref of params.styleRefs ?? [])
102
+ validateRef(ref, "--sref");
103
+ for (const ref of params.omniRefs ?? [])
104
+ validateRef(ref, "--oref");
105
+ for (const image of params.imagePrompts ?? [])
106
+ validateImagePrompt(image);
107
+ for (const key of Object.keys(RANGES)) {
108
+ checkRange(params[key], key);
109
+ }
110
+ if (params.quality !== undefined && !QUALITY.includes(params.quality)) {
111
+ throw new ValidationError(`--q must be one of ${QUALITY.join(", ")}. Got ${params.quality}.`, 0, "(local)");
112
+ }
113
+ if (params.imageWeight !== undefined && (params.imageWeight < 0 || params.imageWeight > 3)) {
114
+ throw new ValidationError(`--iw must be between 0 and 3. Got ${params.imageWeight}.`, 0, "(local)");
115
+ }
116
+ if (params.niji && params.version) {
117
+ throw new ValidationError("Pass either version or niji, not both. Niji is its own model line and ignores --v.", 0, "(local)");
118
+ }
119
+ if (params.raw && params.style && params.style !== "raw") {
120
+ throw new ValidationError(`raw and style are the same parameter. Set style to '${params.style}' or set raw, not both.`, 0, "(local)");
121
+ }
122
+ const parts = [];
123
+ for (const image of params.imagePrompts ?? [])
124
+ parts.push(image.trim());
125
+ if (text)
126
+ parts.push(text);
127
+ const flag = (name, value) => {
128
+ if (value === undefined || value === "")
129
+ return;
130
+ parts.push(`--${name}`, String(value));
131
+ };
132
+ flag("ar", params.aspect);
133
+ for (const ref of params.styleRefs ?? [])
134
+ flag("sref", ref);
135
+ flag("sw", params.styleWeight);
136
+ for (const ref of params.omniRefs ?? [])
137
+ flag("oref", ref);
138
+ flag("ow", params.omniWeight);
139
+ flag("iw", params.imageWeight);
140
+ flag("profile", params.profile);
141
+ flag("no", params.negative);
142
+ if (params.niji) {
143
+ flag("niji", params.niji);
144
+ }
145
+ else {
146
+ flag("v", params.version ?? defaults.version);
147
+ }
148
+ flag("stylize", params.stylize);
149
+ flag("chaos", params.chaos);
150
+ flag("weird", params.weird);
151
+ flag("seed", params.seed);
152
+ flag("q", params.quality);
153
+ flag("stop", params.stop);
154
+ flag("repeat", params.repeat);
155
+ flag("exp", params.exp);
156
+ if (params.style)
157
+ flag("style", params.style);
158
+ else if (params.raw)
159
+ flag("style", "raw");
160
+ if (params.tile)
161
+ parts.push("--tile");
162
+ if (params.draft)
163
+ parts.push("--draft");
164
+ return parts.join(" ");
165
+ }
166
+ /** Split a raw prompt back into text and parameters, for echoing what was sent. */
167
+ export function splitPrompt(prompt) {
168
+ const index = prompt.search(/\s--\w/);
169
+ if (index === -1)
170
+ return { text: prompt.trim(), params: "" };
171
+ return { text: prompt.slice(0, index).trim(), params: prompt.slice(index).trim() };
172
+ }
173
+ //# sourceMappingURL=prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/content/prompt.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Say what is wrong, in the order it has to be fixed.
3
+ *
4
+ * Every failure mode here produces the same symptom from a tool call, a refused
5
+ * request, and they need completely different fixes: install Chrome, start it,
6
+ * sign in, wait out a challenge. Guessing between them is where people give up
7
+ * on a tool like this, so the checks run in dependency order and stop at the
8
+ * first one that fails, rather than printing four red lines and leaving the
9
+ * reader to work out which one matters.
10
+ */
11
+ export declare function runDoctor(): Promise<number>;
12
+ /**
13
+ * Open the controlled window and wait for the user to sign in.
14
+ *
15
+ * There is no password to collect and nothing to store: signing in happens in
16
+ * the browser, and the browser profile keeps the session. This command exists
17
+ * so nobody has to be told to construct a Chrome command line by hand.
18
+ */
19
+ export declare function runLogin(): Promise<number>;
package/dist/doctor.js ADDED
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Say what is wrong, in the order it has to be fixed.
3
+ *
4
+ * Every failure mode here produces the same symptom from a tool call, a refused
5
+ * request, and they need completely different fixes: install Chrome, start it,
6
+ * sign in, wait out a challenge. Guessing between them is where people give up
7
+ * on a tool like this, so the checks run in dependency order and stop at the
8
+ * first one that fails, rather than printing four red lines and leaving the
9
+ * reader to work out which one matters.
10
+ */
11
+ import { access, constants, mkdir } from "node:fs/promises";
12
+ import { MidjourneyClient } from "./api/client.js";
13
+ import { loadConfig } from "./config.js";
14
+ import { CdpBrowser, findChrome } from "./transport/cdp.js";
15
+ import { VERSION } from "./server.js";
16
+ function line(check) {
17
+ const mark = check.ok ? "ok " : "FAIL";
18
+ const base = ` ${mark} ${check.label.padEnd(22)}${check.detail}`;
19
+ return check.fix && !check.ok ? `${base}\n -> ${check.fix}` : base;
20
+ }
21
+ async function checkChrome(config) {
22
+ try {
23
+ const path = findChrome(config.chromePath);
24
+ return { label: "chrome", ok: true, detail: path };
25
+ }
26
+ catch (error) {
27
+ return {
28
+ label: "chrome",
29
+ ok: false,
30
+ detail: error.message,
31
+ fix: "Install Google Chrome, or set MIDJOURNEY_CHROME_PATH to the binary.",
32
+ };
33
+ }
34
+ }
35
+ async function checkDownloadDir(config) {
36
+ try {
37
+ await mkdir(config.downloadDir, { recursive: true });
38
+ await access(config.downloadDir, constants.W_OK);
39
+ return { label: "download dir", ok: true, detail: config.downloadDir };
40
+ }
41
+ catch (error) {
42
+ return {
43
+ label: "download dir",
44
+ ok: false,
45
+ detail: `${config.downloadDir}: ${error.message}`,
46
+ fix: "Set MIDJOURNEY_DOWNLOAD_DIR to somewhere writable.",
47
+ };
48
+ }
49
+ }
50
+ export async function runDoctor() {
51
+ const config = loadConfig();
52
+ const out = (text) => {
53
+ process.stdout.write(`${text}\n`);
54
+ };
55
+ out(`\nmidjourney-mcp ${VERSION}\n`);
56
+ out(` profile ${config.profileDir}`);
57
+ out(` devtools ${config.cdpUrl}`);
58
+ out(` origin ${config.origin}`);
59
+ out(` mode ${config.readOnly ? "read-only" : config.allowDestructive ? "writes enabled" : "writes disabled"}`);
60
+ out(``);
61
+ const checks = [];
62
+ const chrome = await checkChrome(config);
63
+ checks.push(chrome);
64
+ if (!chrome.ok) {
65
+ for (const check of checks)
66
+ out(line(check));
67
+ out(``);
68
+ return 1;
69
+ }
70
+ const browser = new CdpBrowser({
71
+ cdpUrl: config.cdpUrl,
72
+ profileDir: config.profileDir,
73
+ chromePath: config.chromePath,
74
+ autoLaunch: false,
75
+ headless: config.headless,
76
+ timeoutMs: config.requestTimeoutMs,
77
+ origin: config.origin,
78
+ });
79
+ const running = await browser.isRunning();
80
+ checks.push({
81
+ label: "browser running",
82
+ ok: running,
83
+ detail: running ? ((await browser.version())?.Browser ?? "yes") : `nothing on ${config.cdpUrl}`,
84
+ fix: `Run \`midjourney-cli login\` to start the controlled window and sign in. It also starts on demand on the first tool call${config.autoLaunch ? "" : ", except MIDJOURNEY_CHROME_LAUNCH=0 is set"}.`,
85
+ });
86
+ if (running) {
87
+ const client = new MidjourneyClient(config, browser);
88
+ try {
89
+ const userId = await client.userId();
90
+ checks.push({ label: "signed in", ok: true, detail: `user ${userId}` });
91
+ }
92
+ catch (error) {
93
+ checks.push({
94
+ label: "signed in",
95
+ ok: false,
96
+ detail: error.message,
97
+ fix: "Run `midjourney-cli login`, sign in to Midjourney in the window that opens, then try again.",
98
+ });
99
+ }
100
+ client.close();
101
+ }
102
+ checks.push(await checkDownloadDir(config));
103
+ for (const check of checks)
104
+ out(line(check));
105
+ out(``);
106
+ const failed = checks.filter((check) => !check.ok);
107
+ if (failed.length === 0) {
108
+ out(` Everything checks out.\n`);
109
+ return 0;
110
+ }
111
+ out(` ${failed.length} problem(s). Fix the first one; the rest often follow.\n`);
112
+ return 1;
113
+ }
114
+ /**
115
+ * Open the controlled window and wait for the user to sign in.
116
+ *
117
+ * There is no password to collect and nothing to store: signing in happens in
118
+ * the browser, and the browser profile keeps the session. This command exists
119
+ * so nobody has to be told to construct a Chrome command line by hand.
120
+ */
121
+ export async function runLogin() {
122
+ const config = loadConfig();
123
+ const browser = new CdpBrowser({
124
+ cdpUrl: config.cdpUrl,
125
+ profileDir: config.profileDir,
126
+ chromePath: config.chromePath,
127
+ autoLaunch: true,
128
+ // Signing in needs a window regardless of what the config says.
129
+ headless: false,
130
+ timeoutMs: config.requestTimeoutMs,
131
+ origin: config.origin,
132
+ });
133
+ process.stderr.write(`\nOpening ${config.origin} in the controlled browser.\n`);
134
+ process.stderr.write(`Profile: ${config.profileDir}\n\n`);
135
+ try {
136
+ await browser.launch();
137
+ }
138
+ catch (error) {
139
+ process.stderr.write(`${error.message}\n`);
140
+ return 1;
141
+ }
142
+ const client = new MidjourneyClient(config, browser);
143
+ process.stderr.write("Sign in to Midjourney in that window. Waiting...\n");
144
+ const deadline = Date.now() + 5 * 60_000;
145
+ while (Date.now() < deadline) {
146
+ try {
147
+ const userId = await client.userId();
148
+ process.stderr.write(`\nSigned in as ${userId}.\n`);
149
+ process.stderr.write("The session persists in this profile, so this is a one-time step.\n\n");
150
+ client.close();
151
+ return 0;
152
+ }
153
+ catch {
154
+ await new Promise((resolve) => setTimeout(resolve, 3000));
155
+ }
156
+ }
157
+ process.stderr.write("\nStill not signed in after five minutes. Leave the window open and run `midjourney-cli doctor` once you are.\n");
158
+ client.close();
159
+ return 1;
160
+ }
161
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Normalising jobs out of an API with no contract.
3
+ *
4
+ * Everything here is defensive on purpose. Midjourney's web endpoints are not
5
+ * published, are not versioned and have already changed field names more than
6
+ * once, so anything that assumes an exact shape will break on a Tuesday for
7
+ * reasons nobody can debug from a stack trace.
8
+ *
9
+ * The rule this file follows: read what is recognisable, keep the untouched
10
+ * original alongside it, and never throw because a field moved. A tool that
11
+ * returns a job with a missing prompt is still useful. A tool that throws
12
+ * because `image_paths` became `imagePaths` is not.
13
+ */
14
+ export type JobStatus = "queued" | "running" | "completed" | "failed" | "moderated" | "unknown";
15
+ export type Job = {
16
+ id: string;
17
+ status: JobStatus;
18
+ /** The raw status string, because ours is a lossy mapping. */
19
+ rawStatus?: string;
20
+ prompt?: string;
21
+ fullCommand?: string;
22
+ /** Direct URLs to the rendered images, when the job has finished. */
23
+ images: string[];
24
+ createdAt?: string;
25
+ finishedAt?: string;
26
+ /** Percentage, when the app reported one. */
27
+ progress?: number;
28
+ eventType?: string;
29
+ parentId?: string;
30
+ width?: number;
31
+ height?: number;
32
+ /** How many images the job rendered. 4 for a normal grid. */
33
+ batchSize?: number;
34
+ /** Midjourney's own name for the job, e.g. `v8-1_diffusion`. */
35
+ jobType?: string;
36
+ /** Whatever else came back, untouched. */
37
+ raw: Record<string, unknown>;
38
+ };
39
+ export declare const CDN_ORIGIN = "https://cdn.midjourney.com";
40
+ /**
41
+ * Where a finished job's images live.
42
+ *
43
+ * The history endpoint returns no image URLs at all, only the job id and a
44
+ * `batch_size`. The assets sit at a predictable path, which is how the web app
45
+ * renders a grid without a second request per tile. Verified against a real
46
+ * job: `<id>/0_0.png` is the full-resolution render, matching the `width` and
47
+ * `height` on the record.
48
+ *
49
+ * This is a derived URL, not a documented one. If Midjourney moves its assets
50
+ * these stop resolving, which is why `download_job` reports per-image failures
51
+ * rather than throwing the batch away.
52
+ */
53
+ export declare function imageUrlsFor(jobId: string, batchSize: number): string[];
54
+ export declare function isTerminal(status: JobStatus): boolean;
55
+ export declare function normaliseStatus(value: unknown): {
56
+ status: JobStatus;
57
+ raw?: string;
58
+ };
59
+ /** Every image URL we can find on a job record, in order, deduplicated. */
60
+ export declare function extractImages(record: Record<string, unknown>): string[];
61
+ export declare function normaliseJob(input: unknown): Job | undefined;
62
+ /**
63
+ * Find the job array inside whatever envelope came back.
64
+ *
65
+ * The app has returned a bare array, `{jobs: []}`, `{data: []}` and
66
+ * `{results: []}` at different times. Rather than track which, look for the
67
+ * first array whose members look like jobs.
68
+ */
69
+ export declare function extractJobs(payload: unknown, depth?: number): Job[];
70
+ /** The compact shape tools return, so a model is not handed forty raw fields. */
71
+ export declare function summariseJob(job: Job): Record<string, unknown>;