ima2-gen 1.1.11 → 1.1.12
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/LICENSE +21 -0
- package/bin/commands/capabilities.js +6 -0
- package/bin/commands/capabilities.ts +6 -0
- package/bin/commands/config.js +33 -2
- package/bin/commands/config.ts +36 -1
- package/bin/commands/prompt-sub/build.js +102 -0
- package/bin/commands/prompt-sub/build.ts +101 -0
- package/bin/commands/prompt.js +74 -1
- package/bin/commands/prompt.ts +70 -1
- package/bin/ima2.js +49 -19
- package/bin/ima2.ts +50 -17
- package/bin/lib/config-store.js +5 -51
- package/bin/lib/config-store.ts +11 -54
- package/bin/lib/destructive-confirm.js +16 -0
- package/bin/lib/destructive-confirm.ts +19 -0
- package/bin/lib/doctor-checks.js +75 -0
- package/bin/lib/doctor-checks.ts +91 -0
- package/bin/lib/ui-build.js +75 -0
- package/bin/lib/ui-build.ts +85 -0
- package/docs/CLI.md +7 -1
- package/docs/migration/runtime-test-inventory.md +22 -1
- package/lib/agentCommandParser.js +64 -0
- package/lib/agentCommandParser.ts +69 -0
- package/lib/agentGenerationPlanner.js +185 -0
- package/lib/agentGenerationPlanner.ts +229 -0
- package/lib/agentQuestionResponder.js +207 -0
- package/lib/agentQuestionResponder.ts +266 -0
- package/lib/agentQueueStore.js +217 -0
- package/lib/agentQueueStore.ts +270 -0
- package/lib/agentQueueWorker.js +83 -0
- package/lib/agentQueueWorker.ts +89 -0
- package/lib/agentRuntime.js +300 -0
- package/lib/agentRuntime.ts +404 -0
- package/lib/agentSettings.js +60 -0
- package/lib/agentSettings.ts +72 -0
- package/lib/agentStore.js +310 -0
- package/lib/agentStore.ts +422 -0
- package/lib/agentStoreRows.js +80 -0
- package/lib/agentStoreRows.ts +136 -0
- package/lib/agentTypes.js +5 -0
- package/lib/agentTypes.ts +153 -0
- package/lib/apiCachePolicy.js +8 -0
- package/lib/apiCachePolicy.ts +11 -0
- package/lib/capabilities.js +25 -0
- package/lib/capabilities.ts +25 -0
- package/lib/composerSnapshot.js +23 -0
- package/lib/composerSnapshot.ts +33 -0
- package/lib/configKeys.js +52 -0
- package/lib/configKeys.ts +57 -0
- package/lib/db.js +125 -4
- package/lib/db.ts +138 -9
- package/lib/historyList.js +4 -0
- package/lib/historyList.ts +4 -0
- package/lib/promptBuilder/attachments.js +54 -0
- package/lib/promptBuilder/attachments.ts +74 -0
- package/lib/promptBuilder/client.js +86 -0
- package/lib/promptBuilder/client.ts +130 -0
- package/lib/promptBuilder/constants.js +9 -0
- package/lib/promptBuilder/constants.ts +9 -0
- package/lib/promptBuilder/context.js +27 -0
- package/lib/promptBuilder/context.ts +36 -0
- package/lib/promptBuilder/errors.js +6 -0
- package/lib/promptBuilder/errors.ts +12 -0
- package/lib/promptBuilder/requestSchema.js +40 -0
- package/lib/promptBuilder/requestSchema.ts +56 -0
- package/lib/promptBuilder/responseParser.js +181 -0
- package/lib/promptBuilder/responseParser.ts +219 -0
- package/lib/promptBuilder/systemPrompt.js +135 -0
- package/lib/promptBuilder/systemPrompt.ts +135 -0
- package/lib/promptBuilder/transport.js +67 -0
- package/lib/promptBuilder/transport.ts +94 -0
- package/lib/promptBuilder/types.js +1 -0
- package/lib/promptBuilder/types.ts +109 -0
- package/lib/responsesImageAdapter.js +43 -3
- package/lib/responsesImageAdapter.ts +61 -5
- package/package.json +6 -4
- package/routes/agent.js +259 -0
- package/routes/agent.ts +308 -0
- package/routes/generate.js +7 -0
- package/routes/generate.ts +12 -0
- package/routes/index.js +4 -0
- package/routes/index.ts +4 -0
- package/routes/multimode.js +15 -0
- package/routes/multimode.ts +20 -0
- package/routes/promptBuilder.js +27 -0
- package/routes/promptBuilder.ts +37 -0
- package/server.js +2 -0
- package/server.ts +2 -0
- package/skills/ima2/SKILL.md +41 -0
- package/ui/dist/.vite/manifest.json +30 -10
- package/ui/dist/assets/AgentWorkspace-BJe9yxPA.js +3 -0
- package/ui/dist/assets/{CardNewsWorkspace-j4ULtNdk.js → CardNewsWorkspace-BBLdwzYU.js} +1 -1
- package/ui/dist/assets/{NodeCanvas-Bc7BUViM.js → NodeCanvas-BSZ527J4.js} +1 -1
- package/ui/dist/assets/PromptBuilderPanel-Y2VygFc0.js +2 -0
- package/ui/dist/assets/{PromptImportDialog-DBKprBEo.js → PromptImportDialog-C6lFV-LL.js} +2 -2
- package/ui/dist/assets/{PromptImportDiscoverySection-m5v55Zsy.js → PromptImportDiscoverySection-D8YJFhND.js} +1 -1
- package/ui/dist/assets/{PromptImportFolderSection-DnPvJkfJ.js → PromptImportFolderSection-ywfcQolW.js} +1 -1
- package/ui/dist/assets/{PromptLibraryPanel-BMSqfK9C.js → PromptLibraryPanel-fk4KmrGy.js} +2 -2
- package/ui/dist/assets/SettingsWorkspace-DL5vhAHQ.js +1 -0
- package/ui/dist/assets/index-BLx55BOg.js +1 -0
- package/ui/dist/assets/index-ByViUJfx.css +1 -0
- package/ui/dist/assets/index-Ci36vcFD.js +28 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/SettingsWorkspace-Cj3LD0uu.js +0 -1
- package/ui/dist/assets/index-9aOJKFI-.js +0 -1
- package/ui/dist/assets/index-De-AWE6B.css +0 -1
- package/ui/dist/assets/index-tQhOLR-C.js +0 -28
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jun
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -67,6 +67,12 @@ function printText(capabilities) {
|
|
|
67
67
|
out(` models: ${capabilities.valid?.imageModels?.supported?.join(", ")}`);
|
|
68
68
|
out(` reasoning: ${capabilities.valid?.reasoningEfforts?.join(", ")}`);
|
|
69
69
|
out(` quality: ${capabilities.valid?.quality?.join(", ")}`);
|
|
70
|
+
out(` modes: ${capabilities.valid?.modes?.join(", ")}`);
|
|
71
|
+
out(` moderation: ${capabilities.valid?.moderation?.join(", ")}`);
|
|
72
|
+
out(` providers: ${capabilities.valid?.providers?.join(", ")}`);
|
|
73
|
+
out("");
|
|
74
|
+
out(`config keys: ${capabilities.configKeys?.writable?.length ?? 0} writable`);
|
|
75
|
+
out(color.dim("run: ima2 config keys --json"));
|
|
70
76
|
out("");
|
|
71
77
|
out(`limits: refs=${capabilities.limits?.maxRefCount}, images=${capabilities.limits?.maxGeneratedImages}`);
|
|
72
78
|
out(color.dim(`maxParallel: ${capabilities.limits?.maxParallel?.value} (${capabilities.limits?.maxParallel?.note})`));
|
|
@@ -71,6 +71,12 @@ function printText(capabilities: any): void {
|
|
|
71
71
|
out(` models: ${capabilities.valid?.imageModels?.supported?.join(", ")}`);
|
|
72
72
|
out(` reasoning: ${capabilities.valid?.reasoningEfforts?.join(", ")}`);
|
|
73
73
|
out(` quality: ${capabilities.valid?.quality?.join(", ")}`);
|
|
74
|
+
out(` modes: ${capabilities.valid?.modes?.join(", ")}`);
|
|
75
|
+
out(` moderation: ${capabilities.valid?.moderation?.join(", ")}`);
|
|
76
|
+
out(` providers: ${capabilities.valid?.providers?.join(", ")}`);
|
|
77
|
+
out("");
|
|
78
|
+
out(`config keys: ${capabilities.configKeys?.writable?.length ?? 0} writable`);
|
|
79
|
+
out(color.dim("run: ima2 config keys --json"));
|
|
74
80
|
out("");
|
|
75
81
|
out(`limits: refs=${capabilities.limits?.maxRefCount}, images=${capabilities.limits?.maxGeneratedImages}`);
|
|
76
82
|
out(color.dim(`maxParallel: ${capabilities.limits?.maxParallel?.value} (${capabilities.limits?.maxParallel?.note})`));
|
package/bin/commands/config.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createInterface } from "readline/promises";
|
|
2
2
|
import { parseArgs } from "../lib/args.js";
|
|
3
|
+
import { confirmDestructiveAction } from "../lib/destructive-confirm.js";
|
|
3
4
|
import { out, die, color, json } from "../lib/output.js";
|
|
4
|
-
import { CONFIG_FILE, buildEffectiveConfig, deleteNestedKey, displayPath, envOverrideForKey, getNestedKey, isAuthConfigKey, isSensitiveConfigKey, isWritableConfigKey, loadFileCfg, parseConfigValue, redactValue, restartNotice, saveFileCfg, setNestedKey, } from "../lib/config-store.js";
|
|
5
|
+
import { CONFIG_FILE, KEY_TO_ENV, WRITABLE_CONFIG_KEYS, buildEffectiveConfig, deleteNestedKey, displayPath, envOverrideForKey, getNestedKey, isAuthConfigKey, isSensitiveConfigKey, isWritableConfigKey, loadFileCfg, parseConfigValue, redactValue, restartNotice, saveFileCfg, setNestedKey, } from "../lib/config-store.js";
|
|
5
6
|
const HELP = `
|
|
6
7
|
ima2 config <subcommand> [options]
|
|
7
8
|
|
|
@@ -10,7 +11,8 @@ const HELP = `
|
|
|
10
11
|
ls [--effective] [--json] List file layer (or merged effective config with --effective)
|
|
11
12
|
get <key> [--json] Get a dotted key from effective config (redacts secrets)
|
|
12
13
|
set <key> <value> [-y] Write a key to the file layer
|
|
13
|
-
rm <key>
|
|
14
|
+
rm <key> [-y] Remove a key from the file layer
|
|
15
|
+
keys [--json] List writable keys and environment overrides
|
|
14
16
|
|
|
15
17
|
Keys use dot notation, e.g.: imageModels.default, log.level, features.cardNews
|
|
16
18
|
|
|
@@ -66,6 +68,20 @@ async function getSub(argv) {
|
|
|
66
68
|
out(typeof value === "object" ? JSON.stringify(value, null, 2) : String(value));
|
|
67
69
|
}
|
|
68
70
|
}
|
|
71
|
+
async function keysSub(argv) {
|
|
72
|
+
const args = parseArgs(argv, { flags: FLAGS });
|
|
73
|
+
const keys = [...WRITABLE_CONFIG_KEYS].sort().map((key) => ({
|
|
74
|
+
key,
|
|
75
|
+
env: KEY_TO_ENV[key] ?? null,
|
|
76
|
+
}));
|
|
77
|
+
if (args.json) {
|
|
78
|
+
json({ keys });
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
for (const item of keys) {
|
|
82
|
+
out(item.env ? `${item.key} (${item.env})` : item.key);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
69
85
|
async function setSub(argv) {
|
|
70
86
|
const args = parseArgs(argv, { flags: FLAGS });
|
|
71
87
|
const [key, rawValue] = args.positional;
|
|
@@ -109,6 +125,20 @@ async function rmSub(argv) {
|
|
|
109
125
|
die(2, `"${key}" is an auth key. Use 'ima2 setup' or 'ima2 login' to change authentication.`);
|
|
110
126
|
}
|
|
111
127
|
const fileCfg = loadFileCfg();
|
|
128
|
+
if (getNestedKey(fileCfg, key) === undefined) {
|
|
129
|
+
out(color.dim(`(key not found in file layer: ${key})`));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
const confirmed = await confirmDestructiveAction(`Remove config key "${key}"?`, Boolean(args.yes));
|
|
134
|
+
if (!confirmed) {
|
|
135
|
+
out("Aborted.");
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
die(2, err instanceof Error ? err.message : String(err));
|
|
141
|
+
}
|
|
112
142
|
const removed = deleteNestedKey(fileCfg, key);
|
|
113
143
|
if (!removed) {
|
|
114
144
|
out(color.dim(`(key not found in file layer: ${key})`));
|
|
@@ -124,6 +154,7 @@ const SUB = {
|
|
|
124
154
|
get: getSub,
|
|
125
155
|
set: setSub,
|
|
126
156
|
rm: rmSub,
|
|
157
|
+
keys: keysSub,
|
|
127
158
|
};
|
|
128
159
|
export default async function configCmd(argv) {
|
|
129
160
|
const sub = argv[0];
|
package/bin/commands/config.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { createInterface } from "readline/promises";
|
|
2
2
|
import { parseArgs } from "../lib/args.js";
|
|
3
|
+
import { confirmDestructiveAction } from "../lib/destructive-confirm.js";
|
|
3
4
|
import { out, die, color, json } from "../lib/output.js";
|
|
4
5
|
import {
|
|
5
6
|
CONFIG_FILE,
|
|
7
|
+
KEY_TO_ENV,
|
|
8
|
+
WRITABLE_CONFIG_KEYS,
|
|
6
9
|
buildEffectiveConfig,
|
|
7
10
|
deleteNestedKey,
|
|
8
11
|
displayPath,
|
|
@@ -27,7 +30,8 @@ const HELP = `
|
|
|
27
30
|
ls [--effective] [--json] List file layer (or merged effective config with --effective)
|
|
28
31
|
get <key> [--json] Get a dotted key from effective config (redacts secrets)
|
|
29
32
|
set <key> <value> [-y] Write a key to the file layer
|
|
30
|
-
rm <key>
|
|
33
|
+
rm <key> [-y] Remove a key from the file layer
|
|
34
|
+
keys [--json] List writable keys and environment overrides
|
|
31
35
|
|
|
32
36
|
Keys use dot notation, e.g.: imageModels.default, log.level, features.cardNews
|
|
33
37
|
|
|
@@ -76,6 +80,21 @@ async function getSub(argv: string[]) {
|
|
|
76
80
|
}
|
|
77
81
|
}
|
|
78
82
|
|
|
83
|
+
async function keysSub(argv: string[]) {
|
|
84
|
+
const args = parseArgs(argv, { flags: FLAGS });
|
|
85
|
+
const keys = [...WRITABLE_CONFIG_KEYS].sort().map((key) => ({
|
|
86
|
+
key,
|
|
87
|
+
env: KEY_TO_ENV[key] ?? null,
|
|
88
|
+
}));
|
|
89
|
+
if (args.json) {
|
|
90
|
+
json({ keys });
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
for (const item of keys) {
|
|
94
|
+
out(item.env ? `${item.key} (${item.env})` : item.key);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
79
98
|
async function setSub(argv: string[]) {
|
|
80
99
|
const args = parseArgs(argv, { flags: FLAGS });
|
|
81
100
|
const [key, rawValue] = args.positional;
|
|
@@ -123,6 +142,21 @@ async function rmSub(argv: string[]) {
|
|
|
123
142
|
}
|
|
124
143
|
|
|
125
144
|
const fileCfg = loadFileCfg();
|
|
145
|
+
if (getNestedKey(fileCfg, key) === undefined) {
|
|
146
|
+
out(color.dim(`(key not found in file layer: ${key})`));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const confirmed = await confirmDestructiveAction(`Remove config key "${key}"?`, Boolean(args.yes));
|
|
152
|
+
if (!confirmed) {
|
|
153
|
+
out("Aborted.");
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
} catch (err) {
|
|
157
|
+
die(2, err instanceof Error ? err.message : String(err));
|
|
158
|
+
}
|
|
159
|
+
|
|
126
160
|
const removed = deleteNestedKey(fileCfg, key);
|
|
127
161
|
if (!removed) {
|
|
128
162
|
out(color.dim(`(key not found in file layer: ${key})`));
|
|
@@ -140,6 +174,7 @@ const SUB: Record<string, Sub> = {
|
|
|
140
174
|
get: getSub,
|
|
141
175
|
set: setSub,
|
|
142
176
|
rm: rmSub,
|
|
177
|
+
keys: keysSub,
|
|
143
178
|
};
|
|
144
179
|
|
|
145
180
|
export default async function configCmd(argv: string[]) {
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
import { parseArgs } from "../../lib/args.js";
|
|
3
|
+
import { resolveServer, request } from "../../lib/client.js";
|
|
4
|
+
import { readStdin } from "../../lib/files.js";
|
|
5
|
+
import { out, die, color, json, exitCodeForError } from "../../lib/output.js";
|
|
6
|
+
const BUILD_HELP = `
|
|
7
|
+
ima2 prompt build [options]
|
|
8
|
+
|
|
9
|
+
Refine prompt intent through the embedded prompt builder.
|
|
10
|
+
|
|
11
|
+
Options:
|
|
12
|
+
--message <text> User message (required unless --messages)
|
|
13
|
+
--messages <file|@file|-> Multi-turn conversation as JSON array
|
|
14
|
+
--ref <image> Image reference (repeatable)
|
|
15
|
+
--model <model> Builder model (gpt-5.5, gpt-5.4, gpt-5.4-mini)
|
|
16
|
+
--language <ko|en|both> Preferred output language hint
|
|
17
|
+
--server <url> Override server URL
|
|
18
|
+
--json Output raw JSON
|
|
19
|
+
-h, --help Show this help
|
|
20
|
+
|
|
21
|
+
Examples:
|
|
22
|
+
ima2 prompt build --message "make this prompt more cinematic" --json
|
|
23
|
+
ima2 prompt build --messages @conversation.json --model gpt-5.5
|
|
24
|
+
echo '{"role":"user","content":"hi"}' | ima2 prompt build --messages -
|
|
25
|
+
`;
|
|
26
|
+
const FLAGS = {
|
|
27
|
+
message: { type: "string" },
|
|
28
|
+
messages: { type: "string" },
|
|
29
|
+
ref: { type: "string", repeatable: true },
|
|
30
|
+
model: { type: "string" },
|
|
31
|
+
language: { type: "string" },
|
|
32
|
+
server: { type: "string" },
|
|
33
|
+
json: { type: "boolean" },
|
|
34
|
+
help: { short: "h", type: "boolean" },
|
|
35
|
+
};
|
|
36
|
+
async function resolveMessages(args) {
|
|
37
|
+
if (args.messages) {
|
|
38
|
+
const raw = String(args.messages);
|
|
39
|
+
let text;
|
|
40
|
+
if (raw === "-") {
|
|
41
|
+
text = await readStdin();
|
|
42
|
+
}
|
|
43
|
+
else if (raw.startsWith("@")) {
|
|
44
|
+
text = await readFile(raw.slice(1), "utf-8");
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
text = await readFile(raw, "utf-8");
|
|
48
|
+
}
|
|
49
|
+
const parsed = JSON.parse(text);
|
|
50
|
+
if (!Array.isArray(parsed))
|
|
51
|
+
die(2, "--messages must resolve to a JSON array");
|
|
52
|
+
return parsed;
|
|
53
|
+
}
|
|
54
|
+
if (args.message) {
|
|
55
|
+
return [{ role: "user", content: String(args.message) }];
|
|
56
|
+
}
|
|
57
|
+
die(2, "--message or --messages required");
|
|
58
|
+
throw new Error("unreachable");
|
|
59
|
+
}
|
|
60
|
+
export default async function buildSub(argv) {
|
|
61
|
+
const args = parseArgs(argv, { flags: FLAGS });
|
|
62
|
+
if (args.help) {
|
|
63
|
+
out(BUILD_HELP);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const messages = await resolveMessages(args);
|
|
67
|
+
const body = { messages };
|
|
68
|
+
if (args.model)
|
|
69
|
+
body.model = args.model;
|
|
70
|
+
let server;
|
|
71
|
+
try {
|
|
72
|
+
server = await resolveServer({ serverFlag: args.server });
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
die(exitCodeForError(e), e.message);
|
|
76
|
+
throw e;
|
|
77
|
+
}
|
|
78
|
+
let result;
|
|
79
|
+
try {
|
|
80
|
+
result = await request(server.base, "/api/prompt-builder/chat", {
|
|
81
|
+
method: "POST",
|
|
82
|
+
body,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch (e) {
|
|
86
|
+
const err = e;
|
|
87
|
+
die(exitCodeForError(e), `${err.message}${err.code ? ` (${err.code})` : ""}`);
|
|
88
|
+
throw e;
|
|
89
|
+
}
|
|
90
|
+
if (args.json) {
|
|
91
|
+
json(result);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const message = result.message;
|
|
95
|
+
const content = message?.content ?? "";
|
|
96
|
+
if (content) {
|
|
97
|
+
out(content);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
out(color.dim("(empty response)"));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
import { parseArgs } from "../../lib/args.js";
|
|
3
|
+
import { resolveServer, request } from "../../lib/client.js";
|
|
4
|
+
import { readStdin } from "../../lib/files.js";
|
|
5
|
+
import { out, die, color, json, exitCodeForError } from "../../lib/output.js";
|
|
6
|
+
|
|
7
|
+
const BUILD_HELP = `
|
|
8
|
+
ima2 prompt build [options]
|
|
9
|
+
|
|
10
|
+
Refine prompt intent through the embedded prompt builder.
|
|
11
|
+
|
|
12
|
+
Options:
|
|
13
|
+
--message <text> User message (required unless --messages)
|
|
14
|
+
--messages <file|@file|-> Multi-turn conversation as JSON array
|
|
15
|
+
--ref <image> Image reference (repeatable)
|
|
16
|
+
--model <model> Builder model (gpt-5.5, gpt-5.4, gpt-5.4-mini)
|
|
17
|
+
--language <ko|en|both> Preferred output language hint
|
|
18
|
+
--server <url> Override server URL
|
|
19
|
+
--json Output raw JSON
|
|
20
|
+
-h, --help Show this help
|
|
21
|
+
|
|
22
|
+
Examples:
|
|
23
|
+
ima2 prompt build --message "make this prompt more cinematic" --json
|
|
24
|
+
ima2 prompt build --messages @conversation.json --model gpt-5.5
|
|
25
|
+
echo '{"role":"user","content":"hi"}' | ima2 prompt build --messages -
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
const FLAGS = {
|
|
29
|
+
message: { type: "string" },
|
|
30
|
+
messages: { type: "string" },
|
|
31
|
+
ref: { type: "string", repeatable: true },
|
|
32
|
+
model: { type: "string" },
|
|
33
|
+
language: { type: "string" },
|
|
34
|
+
server: { type: "string" },
|
|
35
|
+
json: { type: "boolean" },
|
|
36
|
+
help: { short: "h", type: "boolean" },
|
|
37
|
+
} as const;
|
|
38
|
+
|
|
39
|
+
async function resolveMessages(args: Record<string, unknown>): Promise<Array<{ role: string; content: string }>> {
|
|
40
|
+
if (args.messages) {
|
|
41
|
+
const raw = String(args.messages);
|
|
42
|
+
let text: string;
|
|
43
|
+
if (raw === "-") {
|
|
44
|
+
text = await readStdin();
|
|
45
|
+
} else if (raw.startsWith("@")) {
|
|
46
|
+
text = await readFile(raw.slice(1), "utf-8");
|
|
47
|
+
} else {
|
|
48
|
+
text = await readFile(raw, "utf-8");
|
|
49
|
+
}
|
|
50
|
+
const parsed = JSON.parse(text);
|
|
51
|
+
if (!Array.isArray(parsed)) die(2, "--messages must resolve to a JSON array");
|
|
52
|
+
return parsed;
|
|
53
|
+
}
|
|
54
|
+
if (args.message) {
|
|
55
|
+
return [{ role: "user", content: String(args.message) }];
|
|
56
|
+
}
|
|
57
|
+
die(2, "--message or --messages required");
|
|
58
|
+
throw new Error("unreachable");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default async function buildSub(argv: string[]) {
|
|
62
|
+
const args = parseArgs(argv, { flags: FLAGS });
|
|
63
|
+
if (args.help) { out(BUILD_HELP); return; }
|
|
64
|
+
|
|
65
|
+
const messages = await resolveMessages(args);
|
|
66
|
+
const body: Record<string, unknown> = { messages };
|
|
67
|
+
if (args.model) body.model = args.model;
|
|
68
|
+
|
|
69
|
+
let server: { base: string };
|
|
70
|
+
try {
|
|
71
|
+
server = await resolveServer({ serverFlag: args.server as string | undefined });
|
|
72
|
+
} catch (e: unknown) {
|
|
73
|
+
die(exitCodeForError(e), (e as Error).message);
|
|
74
|
+
throw e;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let result: Record<string, unknown>;
|
|
78
|
+
try {
|
|
79
|
+
result = await request(server.base, "/api/prompt-builder/chat", {
|
|
80
|
+
method: "POST",
|
|
81
|
+
body,
|
|
82
|
+
}) as Record<string, unknown>;
|
|
83
|
+
} catch (e: unknown) {
|
|
84
|
+
const err = e as { message?: string; code?: string };
|
|
85
|
+
die(exitCodeForError(e), `${err.message}${err.code ? ` (${err.code})` : ""}`);
|
|
86
|
+
throw e;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (args.json) {
|
|
90
|
+
json(result);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const message = result.message as { content?: string } | undefined;
|
|
95
|
+
const content = message?.content ?? "";
|
|
96
|
+
if (content) {
|
|
97
|
+
out(content);
|
|
98
|
+
} else {
|
|
99
|
+
out(color.dim("(empty response)"));
|
|
100
|
+
}
|
|
101
|
+
}
|
package/bin/commands/prompt.js
CHANGED
|
@@ -15,6 +15,10 @@ const HELP = `
|
|
|
15
15
|
favorite <id>
|
|
16
16
|
export [-o <file>] Dump all non-trash prompts + folders
|
|
17
17
|
|
|
18
|
+
Builder:
|
|
19
|
+
build --message "<text>" [--ref <image>] [--model <m>] [--json]
|
|
20
|
+
build --messages <file|@file|-> [--json]
|
|
21
|
+
|
|
18
22
|
Folders:
|
|
19
23
|
folder ls
|
|
20
24
|
folder create <name>
|
|
@@ -27,6 +31,8 @@ const HELP = `
|
|
|
27
31
|
import curated --source <id> [-q <q>] [--limit <n>] [--folder <id>] [--dry-run]
|
|
28
32
|
import discovery -q <q> --seed <repo>... [--limit <n>] [--folder <id>] [--dry-run]
|
|
29
33
|
import folder <path> [--folder <id>] [--dry-run]
|
|
34
|
+
import json <file|@file|-> [--folder <id>] [--dry-run]
|
|
35
|
+
import preview <file|@file|-> [--filename <name>] [--json]
|
|
30
36
|
|
|
31
37
|
Common options:
|
|
32
38
|
--server <url> Override server URL
|
|
@@ -41,6 +47,7 @@ const COMMON_FLAGS = {
|
|
|
41
47
|
text: { type: "string" },
|
|
42
48
|
name: { type: "string" },
|
|
43
49
|
folder: { type: "string" },
|
|
50
|
+
filename: { type: "string" },
|
|
44
51
|
tag: { type: "string", repeatable: true },
|
|
45
52
|
mode: { type: "string" },
|
|
46
53
|
search: { type: "string" },
|
|
@@ -95,6 +102,14 @@ async function resolveText(value) {
|
|
|
95
102
|
return await readFile(value.slice(1), "utf-8");
|
|
96
103
|
return value;
|
|
97
104
|
}
|
|
105
|
+
async function readSourceArg(value, fallbackFilename) {
|
|
106
|
+
if (!value || typeof value !== "string")
|
|
107
|
+
die(2, "source file required");
|
|
108
|
+
if (value === "-")
|
|
109
|
+
return { text: await readStdin(), filename: fallbackFilename };
|
|
110
|
+
const path = value.startsWith("@") ? value.slice(1) : value;
|
|
111
|
+
return { text: await readFile(path, "utf-8"), filename: path.split(/[\\/]/).pop() || fallbackFilename };
|
|
112
|
+
}
|
|
98
113
|
// ---------- core ----------
|
|
99
114
|
async function lsSub(argv) {
|
|
100
115
|
const args = parseArgs(argv, { flags: COMMON_FLAGS });
|
|
@@ -331,7 +346,11 @@ async function importSub(argv) {
|
|
|
331
346
|
return importDiscovery(rest);
|
|
332
347
|
if (action === "folder")
|
|
333
348
|
return importFolder(rest);
|
|
334
|
-
|
|
349
|
+
if (action === "json")
|
|
350
|
+
return importJson(rest);
|
|
351
|
+
if (action === "preview")
|
|
352
|
+
return importPreview(rest);
|
|
353
|
+
die(2, "usage: prompt import <sources|refresh|curated|discovery|folder|json|preview> ...");
|
|
335
354
|
}
|
|
336
355
|
async function importSources(argv) {
|
|
337
356
|
const args = parseArgs(argv, { flags: COMMON_FLAGS });
|
|
@@ -485,6 +504,59 @@ async function importFolder(argv) {
|
|
|
485
504
|
}
|
|
486
505
|
out(color.green(`✓ imported ${commit.imported || candidates.length}`));
|
|
487
506
|
}
|
|
507
|
+
async function importJson(argv) {
|
|
508
|
+
const args = parseArgs(argv, { flags: COMMON_FLAGS });
|
|
509
|
+
const { text } = await readSourceArg(args.positional[0], "prompts.json");
|
|
510
|
+
const parsed = JSON.parse(text);
|
|
511
|
+
const body = Array.isArray(parsed) ? { prompts: parsed } : { ...parsed };
|
|
512
|
+
if (args.folder && Array.isArray(body.prompts)) {
|
|
513
|
+
body.prompts = body.prompts.map((prompt) => (prompt && typeof prompt === "object"
|
|
514
|
+
? { ...prompt, folderId: args.folder }
|
|
515
|
+
: prompt));
|
|
516
|
+
}
|
|
517
|
+
if (args["dry-run"]) {
|
|
518
|
+
json({
|
|
519
|
+
folders: Array.isArray(body.folders) ? body.folders.length : 0,
|
|
520
|
+
prompts: Array.isArray(body.prompts) ? body.prompts.length : 0,
|
|
521
|
+
});
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
const server = await getServer(args);
|
|
525
|
+
const resp = await request(server.base, "/api/prompts/import", {
|
|
526
|
+
method: "POST",
|
|
527
|
+
body,
|
|
528
|
+
}).catch(handle);
|
|
529
|
+
if (args.json) {
|
|
530
|
+
json(resp);
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
out(color.green("✓ imported prompt JSON"));
|
|
534
|
+
out(JSON.stringify(resp, null, 2));
|
|
535
|
+
}
|
|
536
|
+
async function importPreview(argv) {
|
|
537
|
+
const args = parseArgs(argv, { flags: COMMON_FLAGS });
|
|
538
|
+
const { text, filename } = await readSourceArg(args.positional[0], "prompt-source.md");
|
|
539
|
+
const server = await getServer(args);
|
|
540
|
+
const resp = await request(server.base, "/api/prompts/import/preview", {
|
|
541
|
+
method: "POST",
|
|
542
|
+
body: {
|
|
543
|
+
source: {
|
|
544
|
+
kind: "local",
|
|
545
|
+
filename: args.filename || filename,
|
|
546
|
+
text,
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
}).catch(handle);
|
|
550
|
+
if (args.json) {
|
|
551
|
+
json(resp);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
out(JSON.stringify(resp, null, 2));
|
|
555
|
+
}
|
|
556
|
+
async function buildSub(argv) {
|
|
557
|
+
const mod = await import("./prompt-sub/build.js");
|
|
558
|
+
return mod.default(argv);
|
|
559
|
+
}
|
|
488
560
|
const SUB = {
|
|
489
561
|
ls: lsSub,
|
|
490
562
|
show: showSub,
|
|
@@ -495,6 +567,7 @@ const SUB = {
|
|
|
495
567
|
export: exportSub,
|
|
496
568
|
folder: folderSub,
|
|
497
569
|
import: importSub,
|
|
570
|
+
build: buildSub,
|
|
498
571
|
};
|
|
499
572
|
export default async function promptCmd(argv) {
|
|
500
573
|
const sub = argv[0];
|
package/bin/commands/prompt.ts
CHANGED
|
@@ -16,6 +16,10 @@ const HELP = `
|
|
|
16
16
|
favorite <id>
|
|
17
17
|
export [-o <file>] Dump all non-trash prompts + folders
|
|
18
18
|
|
|
19
|
+
Builder:
|
|
20
|
+
build --message "<text>" [--ref <image>] [--model <m>] [--json]
|
|
21
|
+
build --messages <file|@file|-> [--json]
|
|
22
|
+
|
|
19
23
|
Folders:
|
|
20
24
|
folder ls
|
|
21
25
|
folder create <name>
|
|
@@ -28,6 +32,8 @@ const HELP = `
|
|
|
28
32
|
import curated --source <id> [-q <q>] [--limit <n>] [--folder <id>] [--dry-run]
|
|
29
33
|
import discovery -q <q> --seed <repo>... [--limit <n>] [--folder <id>] [--dry-run]
|
|
30
34
|
import folder <path> [--folder <id>] [--dry-run]
|
|
35
|
+
import json <file|@file|-> [--folder <id>] [--dry-run]
|
|
36
|
+
import preview <file|@file|-> [--filename <name>] [--json]
|
|
31
37
|
|
|
32
38
|
Common options:
|
|
33
39
|
--server <url> Override server URL
|
|
@@ -43,6 +49,7 @@ const COMMON_FLAGS = {
|
|
|
43
49
|
text: { type: "string" },
|
|
44
50
|
name: { type: "string" },
|
|
45
51
|
folder: { type: "string" },
|
|
52
|
+
filename: { type: "string" },
|
|
46
53
|
tag: { type: "string", repeatable: true },
|
|
47
54
|
mode: { type: "string" },
|
|
48
55
|
search: { type: "string" },
|
|
@@ -93,6 +100,13 @@ async function resolveText(value: unknown): Promise<string | null> {
|
|
|
93
100
|
return value;
|
|
94
101
|
}
|
|
95
102
|
|
|
103
|
+
async function readSourceArg(value: unknown, fallbackFilename: string) {
|
|
104
|
+
if (!value || typeof value !== "string") die(2, "source file required");
|
|
105
|
+
if (value === "-") return { text: await readStdin(), filename: fallbackFilename };
|
|
106
|
+
const path = value.startsWith("@") ? value.slice(1) : value;
|
|
107
|
+
return { text: await readFile(path, "utf-8"), filename: path.split(/[\\/]/).pop() || fallbackFilename };
|
|
108
|
+
}
|
|
109
|
+
|
|
96
110
|
// ---------- core ----------
|
|
97
111
|
|
|
98
112
|
async function lsSub(argv: string[]) {
|
|
@@ -284,7 +298,9 @@ async function importSub(argv: string[]) {
|
|
|
284
298
|
if (action === "curated") return importCurated(rest);
|
|
285
299
|
if (action === "discovery") return importDiscovery(rest);
|
|
286
300
|
if (action === "folder") return importFolder(rest);
|
|
287
|
-
|
|
301
|
+
if (action === "json") return importJson(rest);
|
|
302
|
+
if (action === "preview") return importPreview(rest);
|
|
303
|
+
die(2, "usage: prompt import <sources|refresh|curated|discovery|folder|json|preview> ...");
|
|
288
304
|
}
|
|
289
305
|
|
|
290
306
|
async function importSources(argv: string[]) {
|
|
@@ -402,6 +418,58 @@ async function importFolder(argv: string[]) {
|
|
|
402
418
|
out(color.green(`✓ imported ${commit.imported || candidates.length}`));
|
|
403
419
|
}
|
|
404
420
|
|
|
421
|
+
async function importJson(argv: string[]) {
|
|
422
|
+
const args = parseArgs(argv, { flags: COMMON_FLAGS });
|
|
423
|
+
const { text } = await readSourceArg(args.positional[0], "prompts.json");
|
|
424
|
+
const parsed = JSON.parse(text) as { folders?: unknown; prompts?: unknown } | unknown[];
|
|
425
|
+
const body = Array.isArray(parsed) ? { prompts: parsed } : { ...parsed };
|
|
426
|
+
if (args.folder && Array.isArray(body.prompts)) {
|
|
427
|
+
body.prompts = body.prompts.map((prompt) => (
|
|
428
|
+
prompt && typeof prompt === "object"
|
|
429
|
+
? { ...(prompt as Record<string, unknown>), folderId: args.folder }
|
|
430
|
+
: prompt
|
|
431
|
+
));
|
|
432
|
+
}
|
|
433
|
+
if (args["dry-run"]) {
|
|
434
|
+
json({
|
|
435
|
+
folders: Array.isArray(body.folders) ? body.folders.length : 0,
|
|
436
|
+
prompts: Array.isArray(body.prompts) ? body.prompts.length : 0,
|
|
437
|
+
});
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const server = await getServer(args);
|
|
441
|
+
const resp = await request(server.base, "/api/prompts/import", {
|
|
442
|
+
method: "POST",
|
|
443
|
+
body,
|
|
444
|
+
}).catch(handle);
|
|
445
|
+
if (args.json) { json(resp); return; }
|
|
446
|
+
out(color.green("✓ imported prompt JSON"));
|
|
447
|
+
out(JSON.stringify(resp, null, 2));
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async function importPreview(argv: string[]) {
|
|
451
|
+
const args = parseArgs(argv, { flags: COMMON_FLAGS });
|
|
452
|
+
const { text, filename } = await readSourceArg(args.positional[0], "prompt-source.md");
|
|
453
|
+
const server = await getServer(args);
|
|
454
|
+
const resp = await request(server.base, "/api/prompts/import/preview", {
|
|
455
|
+
method: "POST",
|
|
456
|
+
body: {
|
|
457
|
+
source: {
|
|
458
|
+
kind: "local",
|
|
459
|
+
filename: args.filename || filename,
|
|
460
|
+
text,
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
}).catch(handle);
|
|
464
|
+
if (args.json) { json(resp); return; }
|
|
465
|
+
out(JSON.stringify(resp, null, 2));
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
async function buildSub(argv: string[]) {
|
|
469
|
+
const mod = await import("./prompt-sub/build.js");
|
|
470
|
+
return mod.default(argv);
|
|
471
|
+
}
|
|
472
|
+
|
|
405
473
|
const SUB: Record<string, (argv: any[]) => Promise<void>> = {
|
|
406
474
|
ls: lsSub,
|
|
407
475
|
show: showSub,
|
|
@@ -412,6 +480,7 @@ const SUB: Record<string, (argv: any[]) => Promise<void>> = {
|
|
|
412
480
|
export: exportSub,
|
|
413
481
|
folder: folderSub,
|
|
414
482
|
import: importSub,
|
|
483
|
+
build: buildSub,
|
|
415
484
|
};
|
|
416
485
|
|
|
417
486
|
export default async function promptCmd(argv: string[]) {
|