ima2-gen 3.11.0 → 3.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/bin/commands/gen.js +8 -2
- package/bin/commands/multimode.js +5 -0
- package/bin/commands/node.js +6 -1
- package/bin/lib/args.js +3 -1
- package/bin/lib/nai-options.js +195 -0
- package/config.js +2 -0
- package/docs/API.md +15 -1
- package/docs/CLI.md +41 -0
- package/docs/migration/runtime-test-inventory.md +4 -1
- package/lib/capabilities.js +2 -0
- package/lib/naiImageAdapter.js +2 -2
- package/lib/naiOptions.js +6 -0
- package/package.json +2 -2
- package/skills/ima2/SKILL.md +72 -0
- package/ui/dist/.vite/manifest.json +35 -35
- package/ui/dist/assets/{AgentWorkspace-B7Ry1zR0.js → AgentWorkspace-CKAWZD7U.js} +1 -1
- package/ui/dist/assets/{AssetGenWorkspace-BSl_K_-v.js → AssetGenWorkspace-wsNksO-Q.js} +2 -2
- package/ui/dist/assets/{AssetsWorkspace-Cqv_nI4Q.js → AssetsWorkspace-Bgk5sj1a.js} +1 -1
- package/ui/dist/assets/{CardNewsWorkspace-xSXavyEg.js → CardNewsWorkspace-DGOw6UH_.js} +1 -1
- package/ui/dist/assets/{GenerationRequestLogPanel-DuO9gtUk.js → GenerationRequestLogPanel-BGatd8qE.js} +1 -1
- package/ui/dist/assets/{HomeWorkspace-RCbYP5Rw.js → HomeWorkspace-CuJY4Jr6.js} +1 -1
- package/ui/dist/assets/{KeyingPanel-D3PLyaqp.js → KeyingPanel-DYJN_oNF.js} +1 -1
- package/ui/dist/assets/{NodeCanvas-CaJ4s-El.js → NodeCanvas-fil6cXN8.js} +1 -1
- package/ui/dist/assets/{PromptBuilderPanel-DreYEwPN.js → PromptBuilderPanel-Dt4gO_Rb.js} +1 -1
- package/ui/dist/assets/{PromptImportDialog-B1Rr1xX2.js → PromptImportDialog-uyJdrdTs.js} +2 -2
- package/ui/dist/assets/{PromptImportDiscoverySection-DYfAT0uk.js → PromptImportDiscoverySection-Bmyt_NVl.js} +1 -1
- package/ui/dist/assets/{PromptImportFolderSection-0eAllkEV.js → PromptImportFolderSection-DGqoTx-h.js} +1 -1
- package/ui/dist/assets/{PromptLibraryPanel-CPHkzEha.js → PromptLibraryPanel-BIi5_Rvq.js} +2 -2
- package/ui/dist/assets/{SettingsWorkspace-DM0I_1W7.js → SettingsWorkspace-CVying69.js} +1 -1
- package/ui/dist/assets/{SpriteRecipeWorkspace-2h020SW0.js → SpriteRecipeWorkspace-ztdiDwSf.js} +1 -1
- package/ui/dist/assets/{index-DhGOUKvx.js → index-CumGbxai.js} +3 -3
- package/ui/dist/assets/{index-6WyaBYVL.js → index-TU0_IXQH.js} +14 -14
- package/ui/dist/assets/{pptxgen.es-DqOBFty0.js → pptxgen.es-Cza43xx3.js} +1 -1
- package/ui/dist/assets/{useAgentDialogFocus-Czg5Oxus.js → useAgentDialogFocus-D741d8_6.js} +1 -1
- package/ui/dist/index.html +1 -1
package/README.md
CHANGED
|
@@ -156,7 +156,7 @@ Image generation can run through the local Codex/ChatGPT OAuth path, a configure
|
|
|
156
156
|
- `provider: "api"` calls the OpenAI Responses API with the hosted `image_generation` tool.
|
|
157
157
|
- `provider: "grok"` starts bundled `progrok` on `127.0.0.1:18645`, runs mandatory xAI Web Search plus a planner pass (default: `grok-4.5`, configurable in settings or via `--planner-model`), then calls xAI Images API through the local proxy. `grok-4.3` remains available as an explicit compatibility override.
|
|
158
158
|
- `provider: "grok-api"` calls the xAI Images API directly with `XAI_API_KEY` (no bundled progrok OAuth proxy).
|
|
159
|
-
- `provider: "nai"` calls the NovelAI image API with a persistent API token (
|
|
159
|
+
- `provider: "nai"` calls the NovelAI image API with a persistent API token (saved in Settings > API Keys or `NOVELAI_API_KEY`; no fixed token prefix is required). Four models: `nai-diffusion-5-full`, `nai-diffusion-5-curated`, `nai-diffusion-4-5-full`, `nai-diffusion-4-5-curated`. Responses arrive as a ZIP archive that ima2 decodes to PNG. Text-to-image only — reference images, edits, and masks are refused rather than silently dropped. Browser and CLI surfaces expose negative prompt, sampler/schedule, steps/guidance/CFG rescale, seed, presets, Auto SMEA, Decrisper, Variety+, and V5 alpha.
|
|
160
160
|
- `provider: "agy"` spawns the Antigravity CLI (`agy -p`) to generate images via Google Gemini's `default_api:generate_image` tool (model: `nano-banana-2`). Output is fixed at 1024×1024 JPEG, max 3 reference images. No web search, quality, or size controls.
|
|
161
161
|
- `provider: "gemini-api"` calls the Google Generative Language API directly. Supports two models: `nano-banana-2` (Gemini 3.1 Flash Image) and `nano-banana-pro` (Gemini 3 Pro Image). Auth is via `GEMINI_API_KEY` env var, web UI key management, or a Vertex AI service account JSON (`VERTEX_SERVICE_ACCOUNT_JSON`). When both an API key and Vertex credentials are configured, Vertex takes priority. Supports variable aspect ratios (1:1 through 21:9) and four resolution tiers (512px, 1K, 2K, 4K); these controls are only honored on the direct API path — the Vertex AI endpoint ignores aspect/size because it does not accept the `response_format` field. Per-model cost differs: `nano-banana-2` (Flash): 512=$0.001, 1K=$0.003, 2K=$0.004, 4K=$0.006; `nano-banana-pro`: 1K=$0.007, 2K=$0.007, 4K=$0.013. No web search or mask controls.
|
|
162
162
|
- API-key generation supports classic generate, edit, mask-guided edit, multimode, and node generation.
|
|
@@ -320,6 +320,8 @@ environment variables > ~/.ima2/config.json > built-in defaults
|
|
|
320
320
|
| `XAI_API_KEY` | — | API key for `provider: "grok-api"` direct xAI Images API path |
|
|
321
321
|
| `NOVELAI_API_KEY` | — | NovelAI persistent API token for `provider: "nai"` |
|
|
322
322
|
| `IMA2_NAI_IMAGE_MODEL_DEFAULT` | `nai-diffusion-5-full` | Default NovelAI image model |
|
|
323
|
+
| `IMA2_NAI_DEFAULT_AUTO_SMEA` | `false` | Default NovelAI Auto SMEA state |
|
|
324
|
+
| `IMA2_NAI_DEFAULT_DECRISPER` | `false` | Default NovelAI Decrisper (`dynamic_thresholding`) state |
|
|
323
325
|
| `IMA2_API_IMAGE_MODEL_DEFAULT` | `gpt-5.6-luna` | Default image model for `provider: "api"` |
|
|
324
326
|
| `IMA2_API_REASONING_EFFORT` | `low` | Default reasoning effort for `provider: "api"` |
|
|
325
327
|
| `IMA2_API_IMAGE_SIZE` | `1024x1024` | Default size for `provider: "api"` |
|
package/bin/commands/gen.js
CHANGED
|
@@ -17,6 +17,7 @@ import { createCliRequestId, recoverGeneratedOutputs, formatRecoveryHint } from
|
|
|
17
17
|
import { deriveProviderIds } from "../../lib/providers/derive.js";
|
|
18
18
|
import { listProviders } from "../../lib/mcp/providerRegistry.js";
|
|
19
19
|
import { BACKGROUND_PRESETS } from "../../lib/backgroundPresets.js";
|
|
20
|
+
import { NAI_CLI_FLAGS, NAI_CLI_HELP, finalizeNaiCliTarget, parseNaiCliOptions, unwrapNaiCliResult } from "../lib/nai-options.js";
|
|
20
21
|
const VALID_MODES = new Set(["auto", "direct"]);
|
|
21
22
|
const VALID_MODERATION = new Set(["auto", "low"]);
|
|
22
23
|
const MAX_GENERATION_COUNT = Math.max(1, Math.trunc(Number(config.limits.maxGeneratedImages) || 24));
|
|
@@ -43,6 +44,7 @@ const SPEC = {
|
|
|
43
44
|
character: { type: "string" },
|
|
44
45
|
"reasoning-effort": { type: "string" }, "web-search": { type: "boolean" },
|
|
45
46
|
"no-web-search": { type: "boolean" }, help: { short: "h", type: "boolean" },
|
|
47
|
+
...NAI_CLI_FLAGS,
|
|
46
48
|
},
|
|
47
49
|
};
|
|
48
50
|
const HELP = `
|
|
@@ -83,6 +85,7 @@ const HELP = `
|
|
|
83
85
|
--reasoning-effort <none|low|medium|high|xhigh|max>
|
|
84
86
|
Core lanes only
|
|
85
87
|
--web-search / --no-web-search Core lanes only
|
|
88
|
+
${NAI_CLI_HELP}
|
|
86
89
|
|
|
87
90
|
Examples:
|
|
88
91
|
ima2 defaults set image oauth/gpt-5.6-luna
|
|
@@ -90,6 +93,7 @@ const HELP = `
|
|
|
90
93
|
ima2 gen "poster" --model oauth/luna --mode direct
|
|
91
94
|
ima2 gen "fox logo mark" --bg transparent -o logo.png
|
|
92
95
|
ima2 gen "campaign still" --model runway/gen-4 --ref 1780000000000_abcd.png
|
|
96
|
+
ima2 gen "transparent character sprite" --provider nai --model nai-diffusion-5-full --nai-straight-alpha --nai-negative-prompt "watermark"
|
|
93
97
|
`;
|
|
94
98
|
/**
|
|
95
99
|
* Resolves --out against --out-dir.
|
|
@@ -287,7 +291,7 @@ async function requestCoreImage(args, context, n, requestId) {
|
|
|
287
291
|
const body = { prompt: context.prompt, quality: args.quality, size: args.size, n, references,
|
|
288
292
|
...(args["no-size-nudge"] ? { sizeNudge: false } : {}),
|
|
289
293
|
model: context.target.model, mode: args.mode, moderation: args.moderation, sessionId: args.session,
|
|
290
|
-
provider: context.target.lane };
|
|
294
|
+
provider: context.target.lane, ...context.naiOptions };
|
|
291
295
|
body.requestId = requestId;
|
|
292
296
|
if (args.bg)
|
|
293
297
|
body.backgroundPreset = String(args.bg);
|
|
@@ -385,6 +389,7 @@ export default async function genCmd(argv) {
|
|
|
385
389
|
out(HELP);
|
|
386
390
|
return;
|
|
387
391
|
}
|
|
392
|
+
const naiPreflight = unwrapNaiCliResult(parseNaiCliOptions(args, "allow-unknown"), Boolean(args.json));
|
|
388
393
|
let prompt = args.positional.join(" ");
|
|
389
394
|
if (!prompt && !args.stdin)
|
|
390
395
|
die(2, "prompt is required (positional or via --stdin)");
|
|
@@ -393,13 +398,14 @@ export default async function genCmd(argv) {
|
|
|
393
398
|
die(2, `max ${MAX_REFERENCE_COUNT} --ref attachments`);
|
|
394
399
|
const { server, catalog } = await fetchCatalog(args.server, Boolean(args.json));
|
|
395
400
|
const target = resolveImageTarget(args, catalog);
|
|
401
|
+
const naiFinal = unwrapNaiCliResult(finalizeNaiCliTarget(naiPreflight, target), Boolean(args.json));
|
|
396
402
|
if (args.character && target.transport !== "mcp") {
|
|
397
403
|
fail({ json: Boolean(args.json), code: "CAPABILITY_MISMATCH",
|
|
398
404
|
message: "--character is only supported on MCP lanes (runway/higgsfield); core lanes use element mentions",
|
|
399
405
|
exitCode: 2 });
|
|
400
406
|
}
|
|
401
407
|
let context = { server, catalog, target, prompt, refs, explicitOut: args.out ? String(args.out) : null,
|
|
402
|
-
outDir: args["out-dir"] ? String(args["out-dir"]) : null };
|
|
408
|
+
outDir: args["out-dir"] ? String(args["out-dir"]) : null, naiOptions: naiFinal.payload };
|
|
403
409
|
if (target.transport === "mcp")
|
|
404
410
|
return runMcpImage(argv, args, context);
|
|
405
411
|
if (args.stdin) {
|
|
@@ -7,6 +7,7 @@ import { config } from "../../config.js";
|
|
|
7
7
|
import { createCliRequestId, recoverGeneratedOutputs, formatRecoveryHint } from "../lib/recover-output.js";
|
|
8
8
|
import { canonicalizeImageModel } from "../lib/model-aliases.js";
|
|
9
9
|
import { deriveCliImageModelSet, deriveProviderIds } from "../../lib/providers/derive.js";
|
|
10
|
+
import { NAI_CLI_FLAGS, NAI_CLI_HELP, parseNaiCliOptions, unwrapNaiCliResult } from "../lib/nai-options.js";
|
|
10
11
|
const MAX_GENERATION_COUNT = Math.max(1, Math.trunc(Number(config.limits.maxGeneratedImages) || 24));
|
|
11
12
|
const MAX_REFERENCE_COUNT = Math.max(1, Math.trunc(Number(config.limits.maxRefCount) || 5));
|
|
12
13
|
const PROVIDER_VALUES = ["auto", ...deriveProviderIds()];
|
|
@@ -34,6 +35,7 @@ const SPEC = {
|
|
|
34
35
|
session: { type: "string" },
|
|
35
36
|
"show-partial": { type: "boolean" },
|
|
36
37
|
help: { short: "h", type: "boolean" },
|
|
38
|
+
...NAI_CLI_FLAGS,
|
|
37
39
|
},
|
|
38
40
|
};
|
|
39
41
|
const HELP = `
|
|
@@ -62,6 +64,7 @@ const HELP = `
|
|
|
62
64
|
--show-partial Print [partial #N received] notices
|
|
63
65
|
--timeout <sec> Default: 600
|
|
64
66
|
--server <url> Override server URL
|
|
67
|
+
${NAI_CLI_HELP}
|
|
65
68
|
`;
|
|
66
69
|
export default async function multimodeCmd(argv) {
|
|
67
70
|
const hasCount = argv.some((value) => value === "-n" || value === "--count" || value.startsWith("--count="));
|
|
@@ -73,6 +76,7 @@ export default async function multimodeCmd(argv) {
|
|
|
73
76
|
out(HELP);
|
|
74
77
|
return;
|
|
75
78
|
}
|
|
79
|
+
const naiPreflight = unwrapNaiCliResult(parseNaiCliOptions(args, "require-explicit"), Boolean(args.json));
|
|
76
80
|
const prompt = args.positional.join(" ");
|
|
77
81
|
if (!prompt)
|
|
78
82
|
die(2, "prompt required");
|
|
@@ -117,6 +121,7 @@ export default async function multimodeCmd(argv) {
|
|
|
117
121
|
moderation: args.moderation,
|
|
118
122
|
sessionId: args.session,
|
|
119
123
|
requestId,
|
|
124
|
+
...naiPreflight.payload,
|
|
120
125
|
};
|
|
121
126
|
const model = canonicalizeImageModel(args.model);
|
|
122
127
|
if (model)
|
package/bin/commands/node.js
CHANGED
|
@@ -6,6 +6,7 @@ import { out, die, color, json, exitCodeForError } from "../lib/output.js";
|
|
|
6
6
|
import { canonicalizeImageModel } from "../lib/model-aliases.js";
|
|
7
7
|
import { config } from "../../config.js";
|
|
8
8
|
import { deriveProviderIds } from "../../lib/providers/derive.js";
|
|
9
|
+
import { NAI_CLI_FLAGS, NAI_CLI_HELP, parseNaiCliOptions, unwrapNaiCliResult } from "../lib/nai-options.js";
|
|
9
10
|
const MAX_GENERATION_COUNT = Math.max(1, Math.trunc(Number(config.limits.maxGeneratedImages) || 24));
|
|
10
11
|
const PROVIDER_VALUES = ["auto", ...deriveProviderIds()];
|
|
11
12
|
const HELP = `
|
|
@@ -16,7 +17,8 @@ const HELP = `
|
|
|
16
17
|
show <nodeId> [--json]
|
|
17
18
|
|
|
18
19
|
Generate options:
|
|
19
|
-
|
|
20
|
+
--provider <${PROVIDER_VALUES.join("|")}> Provider for this request
|
|
21
|
+
${NAI_CLI_HELP}
|
|
20
22
|
`;
|
|
21
23
|
const GEN_FLAGS = {
|
|
22
24
|
quality: { short: "q", type: "string", default: "low" },
|
|
@@ -37,6 +39,7 @@ const GEN_FLAGS = {
|
|
|
37
39
|
session: { type: "string" },
|
|
38
40
|
"no-stream": { type: "boolean" },
|
|
39
41
|
help: { short: "h", type: "boolean" },
|
|
42
|
+
...NAI_CLI_FLAGS,
|
|
40
43
|
};
|
|
41
44
|
const SHOW_FLAGS = {
|
|
42
45
|
json: { type: "boolean" },
|
|
@@ -58,6 +61,7 @@ async function generateSub(argv) {
|
|
|
58
61
|
out(HELP);
|
|
59
62
|
return;
|
|
60
63
|
}
|
|
64
|
+
const naiPreflight = unwrapNaiCliResult(parseNaiCliOptions(args, "require-explicit"), Boolean(args.json));
|
|
61
65
|
const prompt = args.positional.join(" ");
|
|
62
66
|
if (!prompt)
|
|
63
67
|
die(2, "prompt required");
|
|
@@ -83,6 +87,7 @@ async function generateSub(argv) {
|
|
|
83
87
|
references,
|
|
84
88
|
moderation: args.moderation,
|
|
85
89
|
sessionId: args.session,
|
|
90
|
+
...naiPreflight.payload,
|
|
86
91
|
};
|
|
87
92
|
const model = canonicalizeImageModel(args.model);
|
|
88
93
|
if (model)
|
package/bin/lib/args.js
CHANGED
|
@@ -6,7 +6,7 @@ export function parseArgs(argv, spec = {}) {
|
|
|
6
6
|
if (def.short)
|
|
7
7
|
shortMap[def.short] = name;
|
|
8
8
|
}
|
|
9
|
-
const out = { positional: [], _unknown: [] };
|
|
9
|
+
const out = { positional: [], _unknown: [], _present: [] };
|
|
10
10
|
for (const [name, def] of Object.entries(spec.flags || {})) {
|
|
11
11
|
if (def.repeatable)
|
|
12
12
|
out[name] = [];
|
|
@@ -38,6 +38,7 @@ export function parseArgs(argv, spec = {}) {
|
|
|
38
38
|
i++;
|
|
39
39
|
continue;
|
|
40
40
|
}
|
|
41
|
+
out._present.push(name);
|
|
41
42
|
if (def.type === "boolean") {
|
|
42
43
|
out[name] = true;
|
|
43
44
|
i++;
|
|
@@ -61,6 +62,7 @@ export function parseArgs(argv, spec = {}) {
|
|
|
61
62
|
i++;
|
|
62
63
|
continue;
|
|
63
64
|
}
|
|
65
|
+
out._present.push(name);
|
|
64
66
|
const def = spec.flags[name];
|
|
65
67
|
if (def.type === "boolean") {
|
|
66
68
|
out[name] = true;
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { NAI_NOISE_SCHEDULES, NAI_QUALITY_PRESET_IDS, NAI_SAMPLERS, NAI_UC_PRESET_IDS, } from "../../lib/naiImageAdapter.js";
|
|
2
|
+
import { getProvider } from "../../lib/providers/registry.js";
|
|
3
|
+
import { fail } from "./output.js";
|
|
4
|
+
export const NAI_CLI_FLAGS = {
|
|
5
|
+
"nai-negative-prompt": { type: "string" },
|
|
6
|
+
"nai-sampler": { type: "string" },
|
|
7
|
+
"nai-noise-schedule": { type: "string" },
|
|
8
|
+
"nai-steps": { type: "string" },
|
|
9
|
+
"nai-scale": { type: "string" },
|
|
10
|
+
"nai-cfg-rescale": { type: "string" },
|
|
11
|
+
"nai-seed": { type: "string" },
|
|
12
|
+
"nai-uc-preset": { type: "string" },
|
|
13
|
+
"nai-quality-preset": { type: "string" },
|
|
14
|
+
"nai-auto-smea": { type: "boolean" },
|
|
15
|
+
"no-nai-auto-smea": { type: "boolean" },
|
|
16
|
+
"nai-decrisper": { type: "boolean" },
|
|
17
|
+
"no-nai-decrisper": { type: "boolean" },
|
|
18
|
+
"nai-variety-plus": { type: "boolean" },
|
|
19
|
+
"no-nai-variety-plus": { type: "boolean" },
|
|
20
|
+
"nai-straight-alpha": { type: "boolean" },
|
|
21
|
+
"no-nai-straight-alpha": { type: "boolean" },
|
|
22
|
+
};
|
|
23
|
+
export const NAI_CLI_HELP = `
|
|
24
|
+
NovelAI options (nai lane only):
|
|
25
|
+
--nai-negative-prompt <text> Undesired content (max 10,000 chars)
|
|
26
|
+
--nai-sampler <sampler> ${NAI_SAMPLERS.filter((v) => v !== "ddim_v3").join("|")}
|
|
27
|
+
--nai-noise-schedule <schedule> ${NAI_NOISE_SCHEDULES.join("|")}
|
|
28
|
+
--nai-steps <1..50> Diffusion steps
|
|
29
|
+
--nai-scale <1..10> Prompt guidance
|
|
30
|
+
--nai-cfg-rescale <0..1> Guidance rescale
|
|
31
|
+
--nai-seed <0..4294967295> Reproducible seed
|
|
32
|
+
--nai-uc-preset <preset> ${NAI_UC_PRESET_IDS.join("|")}
|
|
33
|
+
--nai-quality-preset <preset> ${NAI_QUALITY_PRESET_IDS.join("|")} (V5 only)
|
|
34
|
+
--nai-auto-smea / --no-nai-auto-smea
|
|
35
|
+
--nai-decrisper / --no-nai-decrisper
|
|
36
|
+
--nai-variety-plus / --no-nai-variety-plus
|
|
37
|
+
--nai-straight-alpha / --no-nai-straight-alpha V5 only
|
|
38
|
+
`;
|
|
39
|
+
export function unwrapNaiCliResult(result, jsonMode) {
|
|
40
|
+
if ("message" in result) {
|
|
41
|
+
fail({ json: jsonMode, code: result.code, message: result.message,
|
|
42
|
+
...(result.flag ? { extra: { flag: result.flag } } : {}) });
|
|
43
|
+
}
|
|
44
|
+
return result.value;
|
|
45
|
+
}
|
|
46
|
+
const SELECTABLE_SAMPLERS = NAI_SAMPLERS.filter((value) => value !== "ddim_v3");
|
|
47
|
+
const NAI_MODEL_IDS = new Set(getProvider("nai").models.filter((model) => model.kind === "image").map((model) => model.id));
|
|
48
|
+
const V5_MODEL_IDS = new Set(["nai-diffusion-5-full", "nai-diffusion-5-curated"]);
|
|
49
|
+
const OPTION_KEYS = Object.keys(NAI_CLI_FLAGS);
|
|
50
|
+
const VALUE_OPTION_KEYS = Object.entries(NAI_CLI_FLAGS)
|
|
51
|
+
.filter(([, definition]) => definition.type !== "boolean")
|
|
52
|
+
.map(([key]) => key);
|
|
53
|
+
function failure(code, message, flag) {
|
|
54
|
+
return { ok: false, code, message, ...(flag ? { flag } : {}) };
|
|
55
|
+
}
|
|
56
|
+
function isFailure(value) {
|
|
57
|
+
return Boolean(value && typeof value === "object" && "ok" in value && value.ok === false);
|
|
58
|
+
}
|
|
59
|
+
function explicitModel(raw) {
|
|
60
|
+
if (typeof raw !== "string" || !raw)
|
|
61
|
+
return null;
|
|
62
|
+
const slash = raw.indexOf("/");
|
|
63
|
+
if (slash < 0)
|
|
64
|
+
return NAI_MODEL_IDS.has(raw) ? raw : null;
|
|
65
|
+
const lane = raw.slice(0, slash);
|
|
66
|
+
const model = raw.slice(slash + 1);
|
|
67
|
+
return lane === "nai" && NAI_MODEL_IDS.has(model) ? model : null;
|
|
68
|
+
}
|
|
69
|
+
function classifyTarget(args) {
|
|
70
|
+
const provider = typeof args.provider === "string" ? args.provider : undefined;
|
|
71
|
+
const providerClass = !provider || provider === "auto"
|
|
72
|
+
? "unknown"
|
|
73
|
+
: provider === "nai" ? "nai" : "non-nai";
|
|
74
|
+
const rawModel = typeof args.model === "string" ? args.model : undefined;
|
|
75
|
+
const modelClass = !rawModel
|
|
76
|
+
? "unknown"
|
|
77
|
+
: explicitModel(rawModel) ? "nai" : "non-nai";
|
|
78
|
+
if ((providerClass === "nai" && modelClass === "non-nai") ||
|
|
79
|
+
(providerClass === "non-nai" && modelClass === "nai"))
|
|
80
|
+
return "conflict";
|
|
81
|
+
if (providerClass === "nai" || modelClass === "nai")
|
|
82
|
+
return "nai";
|
|
83
|
+
if (providerClass === "non-nai" || modelClass === "non-nai")
|
|
84
|
+
return "non-nai";
|
|
85
|
+
return "unknown";
|
|
86
|
+
}
|
|
87
|
+
function enumValue(args, key, allowed) {
|
|
88
|
+
const raw = args[key];
|
|
89
|
+
if (raw === undefined)
|
|
90
|
+
return undefined;
|
|
91
|
+
if (typeof raw !== "string" || !allowed.includes(raw)) {
|
|
92
|
+
return failure("NAI_FLAG_INVALID", `--${key} must be one of: ${allowed.join(", ")}`, `--${key}`);
|
|
93
|
+
}
|
|
94
|
+
return raw;
|
|
95
|
+
}
|
|
96
|
+
function numberValue(args, key, min, max, integer = false) {
|
|
97
|
+
const raw = args[key];
|
|
98
|
+
if (raw === undefined)
|
|
99
|
+
return undefined;
|
|
100
|
+
const value = typeof raw === "string" && raw.trim() !== "" ? Number(raw) : Number.NaN;
|
|
101
|
+
if (!Number.isFinite(value) || value < min || value > max || (integer && !Number.isInteger(value))) {
|
|
102
|
+
return failure("NAI_FLAG_INVALID", `--${key} must be ${integer ? "an integer " : ""}between ${min} and ${max}`, `--${key}`);
|
|
103
|
+
}
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
106
|
+
function booleanPair(args, positive, negative) {
|
|
107
|
+
if (args[positive] && args[negative]) {
|
|
108
|
+
return failure("NAI_FLAG_CONFLICT", `--${positive} and --${negative} are mutually exclusive`, `--${positive}`);
|
|
109
|
+
}
|
|
110
|
+
if (args[positive])
|
|
111
|
+
return true;
|
|
112
|
+
if (args[negative])
|
|
113
|
+
return false;
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
function assignValue(payload, key, value) {
|
|
117
|
+
if (isFailure(value))
|
|
118
|
+
return value;
|
|
119
|
+
if (value !== undefined)
|
|
120
|
+
payload[key] = value;
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
function buildPayload(args) {
|
|
124
|
+
const payload = {};
|
|
125
|
+
const negative = args["nai-negative-prompt"];
|
|
126
|
+
if (negative !== undefined) {
|
|
127
|
+
if (typeof negative !== "string" || negative.length > 10_000) {
|
|
128
|
+
return failure("NAI_FLAG_INVALID", "--nai-negative-prompt must be at most 10,000 characters", "--nai-negative-prompt");
|
|
129
|
+
}
|
|
130
|
+
payload.negativePrompt = negative;
|
|
131
|
+
}
|
|
132
|
+
const values = [
|
|
133
|
+
["sampler", enumValue(args, "nai-sampler", SELECTABLE_SAMPLERS)],
|
|
134
|
+
["noiseSchedule", enumValue(args, "nai-noise-schedule", NAI_NOISE_SCHEDULES)],
|
|
135
|
+
["steps", numberValue(args, "nai-steps", 1, 50, true)],
|
|
136
|
+
["scale", numberValue(args, "nai-scale", 1, 10)],
|
|
137
|
+
["cfgRescale", numberValue(args, "nai-cfg-rescale", 0, 1)],
|
|
138
|
+
["seed", numberValue(args, "nai-seed", 0, 2 ** 32 - 1, true)],
|
|
139
|
+
["ucPresetId", enumValue(args, "nai-uc-preset", NAI_UC_PRESET_IDS)],
|
|
140
|
+
["qualityPresetId", enumValue(args, "nai-quality-preset", NAI_QUALITY_PRESET_IDS)],
|
|
141
|
+
["autoSmea", booleanPair(args, "nai-auto-smea", "no-nai-auto-smea")],
|
|
142
|
+
["decrisper", booleanPair(args, "nai-decrisper", "no-nai-decrisper")],
|
|
143
|
+
["varietyPlus", booleanPair(args, "nai-variety-plus", "no-nai-variety-plus")],
|
|
144
|
+
["straightAlpha", booleanPair(args, "nai-straight-alpha", "no-nai-straight-alpha")],
|
|
145
|
+
];
|
|
146
|
+
for (const [key, value] of values) {
|
|
147
|
+
const error = assignValue(payload, key, value);
|
|
148
|
+
if (error)
|
|
149
|
+
return error;
|
|
150
|
+
}
|
|
151
|
+
return payload;
|
|
152
|
+
}
|
|
153
|
+
function needsV5(payload) {
|
|
154
|
+
return payload.straightAlpha === true || payload.qualityPresetId !== undefined;
|
|
155
|
+
}
|
|
156
|
+
export function parseNaiCliOptions(args, policy) {
|
|
157
|
+
const present = new Set(args._present ?? []);
|
|
158
|
+
const hasOptions = OPTION_KEYS.some((key) => present.has(key) || args[key] !== undefined);
|
|
159
|
+
if (!hasOptions)
|
|
160
|
+
return { ok: true, value: { hasOptions: false, payload: {}, target: "unknown" } };
|
|
161
|
+
const missingValue = VALUE_OPTION_KEYS.find((key) => present.has(key) && args[key] === undefined);
|
|
162
|
+
if (missingValue) {
|
|
163
|
+
return failure("NAI_FLAG_INVALID", `--${missingValue} requires a value`, `--${missingValue}`);
|
|
164
|
+
}
|
|
165
|
+
const built = buildPayload(args);
|
|
166
|
+
if (isFailure(built))
|
|
167
|
+
return built;
|
|
168
|
+
const target = classifyTarget(args);
|
|
169
|
+
if (target === "conflict")
|
|
170
|
+
return failure("NAI_TARGET_CONFLICT", "NovelAI flags conflict with the explicit provider/model target");
|
|
171
|
+
if (target === "non-nai")
|
|
172
|
+
return failure("NAI_FLAG_TARGET_MISMATCH", "NovelAI flags require a NovelAI provider/model target");
|
|
173
|
+
if (target === "unknown" && policy === "require-explicit") {
|
|
174
|
+
return failure("NAI_EXPLICIT_TARGET_REQUIRED", "NovelAI flags require --provider nai or --model nai-diffusion-*");
|
|
175
|
+
}
|
|
176
|
+
const model = explicitModel(args.model);
|
|
177
|
+
if (needsV5(built) && policy === "require-explicit" && !model) {
|
|
178
|
+
return failure("NAI_V5_MODEL_REQUIRED", "V5-only NovelAI flags require an explicit V5 model");
|
|
179
|
+
}
|
|
180
|
+
if (needsV5(built) && model && !V5_MODEL_IDS.has(model)) {
|
|
181
|
+
return failure("NAI_V5_MODEL_REQUIRED", "V5-only NovelAI flags are not supported by V4.5");
|
|
182
|
+
}
|
|
183
|
+
return { ok: true, value: { hasOptions, payload: built, target } };
|
|
184
|
+
}
|
|
185
|
+
export function finalizeNaiCliTarget(preflight, target) {
|
|
186
|
+
if (!preflight.hasOptions)
|
|
187
|
+
return { ok: true, value: preflight };
|
|
188
|
+
if (target.lane !== "nai" || !NAI_MODEL_IDS.has(target.model)) {
|
|
189
|
+
return failure("NAI_FLAG_TARGET_MISMATCH", "NovelAI flags require a resolved NovelAI target");
|
|
190
|
+
}
|
|
191
|
+
if (needsV5(preflight.payload) && !V5_MODEL_IDS.has(target.model)) {
|
|
192
|
+
return failure("NAI_V5_MODEL_REQUIRED", "V5-only NovelAI flags are not supported by V4.5");
|
|
193
|
+
}
|
|
194
|
+
return { ok: true, value: { ...preflight, target: "nai" } };
|
|
195
|
+
}
|
package/config.js
CHANGED
|
@@ -260,6 +260,8 @@ export const config = {
|
|
|
260
260
|
defaultScale: pickInt(env.IMA2_NAI_DEFAULT_SCALE, fileCfg.naiProvider?.defaultScale, 5),
|
|
261
261
|
defaultSampler: pickStr(env.IMA2_NAI_DEFAULT_SAMPLER, fileCfg.naiProvider?.defaultSampler, "k_euler_ancestral"),
|
|
262
262
|
defaultNoiseSchedule: pickStr(env.IMA2_NAI_DEFAULT_NOISE_SCHEDULE, fileCfg.naiProvider?.defaultNoiseSchedule, "karras"),
|
|
263
|
+
defaultAutoSmea: pickBool(env.IMA2_NAI_DEFAULT_AUTO_SMEA, fileCfg.naiProvider?.defaultAutoSmea, false),
|
|
264
|
+
defaultDecrisper: pickBool(env.IMA2_NAI_DEFAULT_DECRISPER, fileCfg.naiProvider?.defaultDecrisper, false),
|
|
263
265
|
},
|
|
264
266
|
log: {
|
|
265
267
|
level: pickStr(env.IMA2_LOG_LEVEL, fileCfg.log?.level, defaultLogLevelForEnv(env)),
|
package/docs/API.md
CHANGED
|
@@ -18,6 +18,7 @@ Image generation supports OAuth, API-key, Grok, and Gemini (`agy` and `gemini-ap
|
|
|
18
18
|
- `provider: "agy"` spawns the Antigravity CLI (`agy -p`) to generate images via Google Gemini's `default_api:generate_image` tool. Model is `nano-banana-2`. Output is fixed at 1024×1024 JPEG. Max 3 reference images (i2i). No web search, quality, size, or mask controls. Multimode returns a single image. Video is unsupported (`AGY_VIDEO_UNSUPPORTED`).
|
|
19
19
|
- `provider: "grok-api"` uses a direct xAI API key instead of the bundled progrok OAuth proxy. Same pipeline as `grok` (Web Search → planner → `/v1/images/generations`), same aspect ratio and resolution options. Requires an xAI API key configured via the web UI key management or `XAI_API_KEY` env var. Also supports video generation.
|
|
20
20
|
- `provider: "gemini-api"` calls the Google Generative Language API directly (or Vertex AI with a service account JSON). Supports models `nano-banana-2` (Gemini 3.1 Flash Image) and `nano-banana-pro` (Gemini 3 Pro Image). Supports variable aspect ratios (1:1 through 21:9) and four resolution tiers (512px, 1K, 2K, 4K) on both auth paths — the direct API path sends `generation_config.response_format.image` (snake_case) while the Vertex AI endpoint (`aiplatform.googleapis.com`) sends `generationConfig.imageConfig` (camelCase). With `size: "auto"` the image config is omitted entirely and the model decides ratio/size. Auth: `GEMINI_API_KEY` env var, web UI key management (`/api/keys/gemini`), or a Vertex AI service account JSON (`VERTEX_SERVICE_ACCOUNT_JSON` or `/api/keys/vertex`). When both Vertex credentials and an API key are configured, Vertex takes priority. The chosen auth mode (`apikey` or `vertex`) persists to `~/.ima2/config.json` as `geminiAuthMode` and is restored on server startup. Per-model cost: `nano-banana-2` (Flash): 512=$0.001, 1K=$0.003, 2K=$0.004, 4K=$0.006; `nano-banana-pro`: 1K=$0.007, 2K=$0.007, 4K=$0.013. No web search or mask controls.
|
|
21
|
+
- `provider: "nai"` calls NovelAI text-to-image generation with one of `nai-diffusion-5-full`, `nai-diffusion-5-curated`, `nai-diffusion-4-5-full`, or `nai-diffusion-4-5-curated`. It accepts the provider-native request fields documented below and returns a ZIP that ima2 decodes to PNG. References, edits, and masks are explicitly refused.
|
|
21
22
|
- API-key generation covers classic generate, edit, mask-guided edit, multimode, and node generation.
|
|
22
23
|
- If `provider: "api"` is requested without an API key, routes fail before upstream with `401` and `API_KEY_REQUIRED`.
|
|
23
24
|
- Grok generation maps `size` to xAI `aspect_ratio` and `resolution`; it does not send an OpenAI-style `size` field upstream. Grok edit uses xAI `/v1/images/edits`; Grok mask edit remains unsupported and returns `GROK_MASK_UNSUPPORTED`.
|
|
@@ -216,6 +217,19 @@ generations maintain character and scene continuity for multi-shot video product
|
|
|
216
217
|
|
|
217
218
|
Current app default: `gpt-5.6-luna`. `gpt-5.5` and the other supported GPT image models remain available when callers explicitly select them.
|
|
218
219
|
|
|
220
|
+
When `provider` is `"nai"`, classic, multimode, and node generation accept the
|
|
221
|
+
same 13 provider-native fields: `negativePrompt`, `sampler`, `noiseSchedule`,
|
|
222
|
+
`steps`, `scale`, `cfgRescale`, `seed`, `ucPresetId`, `qualityPresetId`,
|
|
223
|
+
`autoSmea`, `decrisper`, `varietyPlus`, and `straightAlpha`. Missing values stay
|
|
224
|
+
sparse and resolve from `config.naiProvider`; operator defaults `defaultAutoSmea`
|
|
225
|
+
and `defaultDecrisper` are `false` unless overridden by config or
|
|
226
|
+
`IMA2_NAI_DEFAULT_AUTO_SMEA` / `IMA2_NAI_DEFAULT_DECRISPER`. Quality preset and
|
|
227
|
+
enabled alpha are V5-only. The four exact image models are
|
|
228
|
+
`nai-diffusion-5-full`, `nai-diffusion-5-curated`,
|
|
229
|
+
`nai-diffusion-4-5-full`, and `nai-diffusion-4-5-curated`. The lane is
|
|
230
|
+
text-to-image only: `NAI_REF_UNSUPPORTED`, `NAI_EDIT_UNSUPPORTED`, and
|
|
231
|
+
`NAI_MASK_UNSUPPORTED` fail closed rather than discarding input.
|
|
232
|
+
|
|
219
233
|
When `provider` is `"grok"`, supported models are `grok-imagine-image` and
|
|
220
234
|
`grok-imagine-image-quality`. The server uses `grok-4.5` as the search/planner
|
|
221
235
|
model by default (`IMA2_GROK_PLANNER_MODEL`) and times the mandatory search and
|
|
@@ -758,7 +772,7 @@ API key management endpoints for configuring provider credentials at runtime thr
|
|
|
758
772
|
| Endpoint | Method | Description |
|
|
759
773
|
|---|---|---|
|
|
760
774
|
| `/api/keys/status` | GET | Returns configured/valid/maskedKey status for all providers (openai, xai, gemini, atlascloud, minimax, nai, vertex) plus `geminiAuthMode` (`"apikey"` or `"vertex"`) |
|
|
761
|
-
| `/api/keys/:provider` | PUT | Save an API key. Body: `{ "apiKey": "..." }`. Validates key format and upstream before saving to config.json. Provider: `openai`, `xai`, `gemini`, `atlascloud`, `minimax`, or `nai`. `minimax` and `nai` have no fixed key prefix, so only the upstream validation call gates them
|
|
775
|
+
| `/api/keys/:provider` | PUT | Save an API key. Body: `{ "apiKey": "..." }`. Validates key format and upstream before saving to config.json. Provider: `openai`, `xai`, `gemini`, `atlascloud`, `minimax`, or `nai`. `minimax` and `nai` have no fixed key prefix, so only the upstream validation call gates them. |
|
|
762
776
|
| `/api/keys/:provider` | DELETE | Remove a config-sourced API key. Env-sourced keys cannot be removed (`ENV_KEY_IMMUTABLE`). |
|
|
763
777
|
| `/api/keys/vertex` | PUT | Save a Vertex AI service account JSON. Body: `{ "serviceAccountJson": "..." }`. Validates JSON structure (`type: "service_account"`, `project_id` required). |
|
|
764
778
|
| `/api/keys/vertex` | DELETE | Remove a config-sourced Vertex AI service account. |
|
package/docs/CLI.md
CHANGED
|
@@ -90,6 +90,47 @@ Provider override semantics:
|
|
|
90
90
|
- `runway` / `higgsfield` (gen/video only) route through the MCP async pipeline (`POST /api/mcp/generate` + SSE wait). Runway requires an MCP connection; Higgsfield stays catalog-only (`locked`) until a paid plan. MCP lanes accept `-n 1` only, gallery filenames for `--ref`, and reject core-only flags with `FLAG_NOT_SUPPORTED`.
|
|
91
91
|
- `auto` preserves route default behavior and currently resolves to GPT OAuth unless server routing changes (edit/multimode/node only; removed from gen/video in 3.0.0).
|
|
92
92
|
|
|
93
|
+
### NovelAI native generation options
|
|
94
|
+
|
|
95
|
+
`ima2 gen`, `ima2 multimode`, and `ima2 node generate` share these request-only
|
|
96
|
+
flags. They do not change saved defaults.
|
|
97
|
+
|
|
98
|
+
| Flag | Request field | Rule |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| `--nai-negative-prompt <text>` | `negativePrompt` | Undesired content, max 10,000 characters |
|
|
101
|
+
| `--nai-sampler <id>` | `sampler` | Modern NovelAI sampler enum |
|
|
102
|
+
| `--nai-noise-schedule <id>` | `noiseSchedule` | `native`, `karras`, `exponential`, or `polyexponential` |
|
|
103
|
+
| `--nai-steps <1..50>` | `steps` | Integer |
|
|
104
|
+
| `--nai-scale <1..10>` | `scale` | Prompt guidance |
|
|
105
|
+
| `--nai-cfg-rescale <0..1>` | `cfgRescale` | Guidance rescale |
|
|
106
|
+
| `--nai-seed <0..4294967295>` | `seed` | Integer; zero is valid |
|
|
107
|
+
| `--nai-uc-preset <id>` | `ucPresetId` | `heavy`, `light`, `furryFocus`, `humanFocus`, or `none` |
|
|
108
|
+
| `--nai-quality-preset <id>` | `qualityPresetId` | `standard`, `light`, or `none`; V5 only |
|
|
109
|
+
| `--nai-auto-smea` / `--no-nai-auto-smea` | `autoSmea` | Explicit true/false |
|
|
110
|
+
| `--nai-decrisper` / `--no-nai-decrisper` | `decrisper` | Explicit true/false |
|
|
111
|
+
| `--nai-variety-plus` / `--no-nai-variety-plus` | `varietyPlus` | Explicit true/false |
|
|
112
|
+
| `--nai-straight-alpha` / `--no-nai-straight-alpha` | `straightAlpha` | Enabled alpha is V5 only |
|
|
113
|
+
|
|
114
|
+
`gen` may resolve a persisted NovelAI default set with
|
|
115
|
+
`ima2 defaults set image nai/nai-diffusion-5-full`. `multimode` requires an
|
|
116
|
+
explicit NovelAI target when any `--nai-*` flag is used, and `node generate`
|
|
117
|
+
likewise requires an explicit NovelAI target. Provider/model conflicts fail before
|
|
118
|
+
network I/O. V5-only values on V4.5 fail with `NAI_V5_MODEL_REQUIRED`; malformed
|
|
119
|
+
values and non-NAI targets also exit 2. The ima2 NovelAI lane is text-to-image only:
|
|
120
|
+
references, edits, and masks remain explicitly unsupported.
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
ima2 gen "character sprite, transparent background, has alpha" \
|
|
124
|
+
--model nai/nai-diffusion-5-full --nai-straight-alpha \
|
|
125
|
+
--nai-negative-prompt "watermark" -o sprite.png
|
|
126
|
+
|
|
127
|
+
ima2 multimode "city at night" --provider nai --model nai-diffusion-5-full \
|
|
128
|
+
--nai-auto-smea --nai-decrisper --max-images 2
|
|
129
|
+
|
|
130
|
+
ima2 node generate "portrait" --provider nai --model nai-diffusion-5-curated \
|
|
131
|
+
--nai-uc-preset light --nai-quality-preset light
|
|
132
|
+
```
|
|
133
|
+
|
|
93
134
|
`ima2 serve` starts the bundled Grok proxy automatically. No separate `progrok`
|
|
94
135
|
install is required. Use `ima2 grok login` once to authorize xAI OAuth. Login
|
|
95
136
|
defaults to `--manual-paste` so PowerShell, Terminal, and remote shells all use
|
|
@@ -4,7 +4,7 @@ Generated by `npm run test:inventory` (script: `scripts/classify-tests.mjs`).
|
|
|
4
4
|
|
|
5
5
|
_Tests considered "runtime-importing" if they import from `../lib/`, `../routes/`, `../bin/`, `../server`, or `../config`._
|
|
6
6
|
|
|
7
|
-
Total:
|
|
7
|
+
Total: 399 (runtime: 188, contract: 211)
|
|
8
8
|
|
|
9
9
|
## Runtime-importing tests
|
|
10
10
|
- `tests/agent-mode-auto-planner-contract.test.ts`
|
|
@@ -120,6 +120,8 @@ Total: 396 (runtime: 186, contract: 210)
|
|
|
120
120
|
- `tests/minimax-provider-contract.test.ts`
|
|
121
121
|
- `tests/model-default-projection-contract.test.ts`
|
|
122
122
|
- `tests/models-endpoint-contract.test.ts`
|
|
123
|
+
- `tests/nai-built-runtime-contract.test.ts`
|
|
124
|
+
- `tests/nai-cli-options-contract.test.ts`
|
|
123
125
|
- `tests/nai-client-options-contract.test.ts`
|
|
124
126
|
- `tests/nai-options-contract.test.ts`
|
|
125
127
|
- `tests/nai-provider-contract.test.ts`
|
|
@@ -328,6 +330,7 @@ Total: 396 (runtime: 186, contract: 210)
|
|
|
328
330
|
- `tests/multimode-backend-contract.test.js`
|
|
329
331
|
- `tests/multimode-concurrent-store-contract.test.js`
|
|
330
332
|
- `tests/multimode-ui-contract.test.js`
|
|
333
|
+
- `tests/nai-cli-built-smoke.test.ts`
|
|
331
334
|
- `tests/nai-ui-registration-contract.test.ts`
|
|
332
335
|
- `tests/navrail-hover-label-contract.test.ts`
|
|
333
336
|
- `tests/node-async-eventbus-contract.test.js`
|
package/lib/capabilities.js
CHANGED
|
@@ -64,6 +64,8 @@ export function buildIma2Capabilities({ appConfig = runtimeConfigDefault, packag
|
|
|
64
64
|
noiseSchedule: appConfig.naiProvider.defaultNoiseSchedule,
|
|
65
65
|
steps: appConfig.naiProvider.defaultSteps,
|
|
66
66
|
scale: appConfig.naiProvider.defaultScale,
|
|
67
|
+
autoSmea: appConfig.naiProvider.defaultAutoSmea,
|
|
68
|
+
decrisper: appConfig.naiProvider.defaultDecrisper,
|
|
67
69
|
},
|
|
68
70
|
},
|
|
69
71
|
valid: {
|
package/lib/naiImageAdapter.js
CHANGED
|
@@ -79,8 +79,8 @@ export async function generateViaNai(prompt, ctx, options = {}) {
|
|
|
79
79
|
n_samples: 1,
|
|
80
80
|
ucPresetId: options.ucPresetId ?? "heavy",
|
|
81
81
|
qualityPresetId: isV5 ? (options.qualityPresetId ?? "standard") : "standard",
|
|
82
|
-
autoSmea:
|
|
83
|
-
dynamic_thresholding:
|
|
82
|
+
autoSmea: options.autoSmea ?? cfg.defaultAutoSmea,
|
|
83
|
+
dynamic_thresholding: options.decrisper ?? cfg.defaultDecrisper,
|
|
84
84
|
controlnet_strength: 1,
|
|
85
85
|
legacy: false,
|
|
86
86
|
legacy_v3_extend: false,
|
package/lib/naiOptions.js
CHANGED
|
@@ -86,6 +86,12 @@ export function readNaiOptions(body) {
|
|
|
86
86
|
const varietyPlus = pickBoolean(raw.varietyPlus);
|
|
87
87
|
if (varietyPlus !== undefined)
|
|
88
88
|
out.varietyPlus = varietyPlus;
|
|
89
|
+
const autoSmea = pickBoolean(raw.autoSmea);
|
|
90
|
+
if (autoSmea !== undefined)
|
|
91
|
+
out.autoSmea = autoSmea;
|
|
92
|
+
const decrisper = pickBoolean(raw.decrisper);
|
|
93
|
+
if (decrisper !== undefined)
|
|
94
|
+
out.decrisper = decrisper;
|
|
89
95
|
const ucPresetId = pickEnum(raw.ucPresetId, NAI_UC_PRESET_IDS);
|
|
90
96
|
if (ucPresetId !== undefined)
|
|
91
97
|
out.ucPresetId = ucPresetId;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ima2-gen",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.12.1",
|
|
4
4
|
"packageManager": "npm@11.18.0",
|
|
5
5
|
"description": "Local-first visual generation runtime and studio for people and coding agents, with reproducible image and video workflows across multiple providers.",
|
|
6
6
|
"type": "module",
|
|
@@ -119,5 +119,5 @@
|
|
|
119
119
|
"tsx": "^4.23.12",
|
|
120
120
|
"typescript": "^5.9.3"
|
|
121
121
|
},
|
|
122
|
-
"gitHead": "
|
|
122
|
+
"gitHead": "ae45711d42383755697b66ff0b4a7e49beab8076"
|
|
123
123
|
}
|
package/skills/ima2/SKILL.md
CHANGED
|
@@ -100,6 +100,78 @@ Grok requests with reference images use the edit/image-to-image path so the
|
|
|
100
100
|
references remain attached after planning. Keep Grok references to three total
|
|
101
101
|
input images.
|
|
102
102
|
|
|
103
|
+
## NovelAI Image Generation
|
|
104
|
+
|
|
105
|
+
Discover the live NovelAI lane before choosing a model:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
ima2 models --kind image --lane nai --json
|
|
109
|
+
ima2 defaults set image nai/nai-diffusion-5-full
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The four exact model IDs are:
|
|
113
|
+
|
|
114
|
+
- `nai-diffusion-5-full`
|
|
115
|
+
- `nai-diffusion-5-curated`
|
|
116
|
+
- `nai-diffusion-4-5-full`
|
|
117
|
+
- `nai-diffusion-4-5-curated`
|
|
118
|
+
|
|
119
|
+
Use a persistent NovelAI token from Settings > API Keys or `NOVELAI_API_KEY`.
|
|
120
|
+
NovelAI does not publish one mandatory token prefix, so never reject a token based
|
|
121
|
+
on a guessed prefix. Check the lane state through `ima2 models` instead.
|
|
122
|
+
|
|
123
|
+
The same NovelAI options work on `ima2 gen`, `ima2 multimode`, and
|
|
124
|
+
`ima2 node generate`. Run `ima2 gen --help` for the live enum/range list. Example:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
ima2 gen "1girl, blue hair, city at night" \
|
|
128
|
+
--provider nai --model nai-diffusion-5-full \
|
|
129
|
+
--nai-negative-prompt "lowres, watermark" \
|
|
130
|
+
--nai-steps 28 --nai-scale 5 \
|
|
131
|
+
--nai-auto-smea --nai-decrisper --nai-variety-plus
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
For V5 native alpha, pair the request flag with an alpha-aware prompt:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
ima2 gen "character sprite, transparent background, has alpha" \
|
|
138
|
+
--model nai/nai-diffusion-5-full \
|
|
139
|
+
--nai-straight-alpha -o sprite.png
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Supported request controls include sampler, noise schedule, steps, guidance,
|
|
143
|
+
CFG rescale, seed, undesired-content/UC preset, quality preset, Auto SMEA,
|
|
144
|
+
Decrisper, Variety+, and V5 native alpha. `--nai-quality-preset` and enabled
|
|
145
|
+
`--nai-straight-alpha` require an explicit V5 model on CLI surfaces that cannot
|
|
146
|
+
resolve a saved catalog default. `multimode` and `node generate` require an
|
|
147
|
+
explicit NovelAI provider or model whenever NAI-native flags are used; `gen`
|
|
148
|
+
can use a persisted NovelAI CLI default.
|
|
149
|
+
|
|
150
|
+
NovelAI V5 officially supports English and Japanese prompts. Natural language
|
|
151
|
+
and tags both work; quote text that should appear in the image, and use tags such
|
|
152
|
+
as `transparent background`, `has alpha`, or `alpha transparency` with the alpha
|
|
153
|
+
flag. Other languages may work but are not the officially supported prompt pair.
|
|
154
|
+
|
|
155
|
+
Do not assume a generation costs no Anlas. Check the current account and usage
|
|
156
|
+
limit. The no-Anlas Opus conditions include one image, no base or source image,
|
|
157
|
+
a normal resolution up to 1024x1024, and at most 28 steps; V5 usage limits can
|
|
158
|
+
still apply.
|
|
159
|
+
|
|
160
|
+
The ima2 NovelAI lane is text-to-image only. NovelAI itself has additional product
|
|
161
|
+
features, but ima2 does not currently expose reference/img2img, masks/inpainting,
|
|
162
|
+
Character Positioning, Director Reference, Vibe Transfer, or Max Enhance.
|
|
163
|
+
References, edits, and masks fail closed with `NAI_REF_UNSUPPORTED`,
|
|
164
|
+
`NAI_EDIT_UNSUPPORTED`, or `NAI_MASK_UNSUPPORTED` rather than being dropped.
|
|
165
|
+
|
|
166
|
+
Primary product references, checked 2026-08-27: the official
|
|
167
|
+
[NovelAI V5 release](https://journal.novelai.net/image-generation-novelai-diffusion-v5-is-here-c2df7c6b8d2d/)
|
|
168
|
+
and [NovelAI subscription documentation](https://docs.novelai.net/en/subscription/).
|
|
169
|
+
Native control details come from the official
|
|
170
|
+
[sampling](https://docs.novelai.net/en/image/sampling/),
|
|
171
|
+
[steps and guidance](https://docs.novelai.net/en/image/stepsguidance/),
|
|
172
|
+
[quality tags](https://docs.novelai.net/en/image/qualitytags/), and
|
|
173
|
+
[seed](https://docs.novelai.net/en/image/seed/) pages, checked the same date.
|
|
174
|
+
|
|
103
175
|
## Prompting Guidance
|
|
104
176
|
|
|
105
177
|
GPT Image 2 can follow detailed visual instructions and can render visible text
|