ima2-gen 2.0.15 → 2.0.16
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 +30 -0
- package/bin/commands/skill.js +336 -17
- package/bin/ima2.js +22 -2
- package/bin/lib/doctor-checks.js +6 -4
- package/config.js +1 -0
- package/docs/CLI.md +25 -3
- package/docs/PROMPT_STUDIO.ko.md +29 -0
- package/docs/PROMPT_STUDIO.md +31 -0
- package/docs/migration/runtime-test-inventory.md +7 -1
- package/lib/agentImageVideoGen.js +8 -0
- package/lib/agentQueueStore.js +36 -10
- package/lib/agentQueueWorker.js +79 -7
- package/lib/agentRuntime.js +2 -0
- package/lib/assetLifecycle.js +40 -8
- package/lib/canvasVersionStore.js +86 -0
- package/lib/cardNewsGenerator.js +37 -3
- package/lib/cardNewsJobStore.js +10 -0
- package/lib/cardNewsManifestStore.js +6 -12
- package/lib/cardNewsPath.js +25 -0
- package/lib/db.js +2 -0
- package/lib/generatePipeline.js +474 -0
- package/lib/generationInputValidation.js +32 -0
- package/lib/grokImageCore.js +18 -1
- package/lib/historyIndex.js +11 -4
- package/lib/historyList.js +3 -0
- package/lib/imageMetadata.js +3 -0
- package/lib/inflight.js +30 -3
- package/lib/multimodePipeline.js +462 -0
- package/lib/nodeGeneration.js +440 -0
- package/lib/nodeValidation.js +23 -0
- package/lib/oauthProxy/generators.js +3 -246
- package/lib/oauthProxy/multimodeGenerators.js +251 -0
- package/lib/routeHelpers.js +9 -2
- package/package.json +2 -2
- package/routes/agent.js +4 -4
- package/routes/canvasVersions.js +21 -1
- package/routes/cardNews.js +2 -1
- package/routes/edit.js +25 -9
- package/routes/events.js +5 -1
- package/routes/generate.js +2 -481
- package/routes/keys.js +31 -39
- package/routes/multimode.js +2 -502
- package/routes/nodes.js +3 -448
- package/routes/video.js +12 -3
- package/routes/videoExtended.js +19 -4
- package/server.js +53 -1
- package/skills/ima2/SKILL.md +204 -0
- package/skills/ima2-front/SKILL.md +611 -0
- package/skills/ima2-front/references/a11y-patterns.md +134 -0
- package/skills/ima2-front/references/aesthetics.md +327 -0
- package/skills/ima2-front/references/anti-slop.md +241 -0
- package/skills/ima2-front/references/asset-requirements.md +431 -0
- package/skills/ima2-front/references/brand-asset-sourcing.md +122 -0
- package/skills/ima2-front/references/color-system.md +132 -0
- package/skills/ima2-front/references/consistency-locks.md +20 -0
- package/skills/ima2-front/references/crud-ui.md +53 -0
- package/skills/ima2-front/references/dropdown-layer.md +110 -0
- package/skills/ima2-front/references/i18n-global.md +104 -0
- package/skills/ima2-front/references/iterative-design.md +90 -0
- package/skills/ima2-front/references/korea-2026.md +174 -0
- package/skills/ima2-front/references/layout-discipline.md +161 -0
- package/skills/ima2-front/references/liquid-glass.md +181 -0
- package/skills/ima2-front/references/logo-sections.md +194 -0
- package/skills/ima2-front/references/mobile-ux.md +144 -0
- package/skills/ima2-front/references/performance-budget.md +106 -0
- package/skills/ima2-front/references/preflight-full.md +58 -0
- package/skills/ima2-front/references/product-density.md +53 -0
- package/skills/ima2-front/references/prototype-variants.md +54 -0
- package/skills/ima2-front/references/reference-capture.md +120 -0
- package/skills/ima2-front/references/responsive-viewport.md +139 -0
- package/skills/ima2-front/references/seo-baseline.md +123 -0
- package/skills/ima2-front/references/soft-3d-asset-gates.md +70 -0
- package/skills/ima2-front/references/stacks/astro.md +204 -0
- package/skills/ima2-front/references/stacks/mobile-native.md +281 -0
- package/skills/ima2-front/references/stacks/nextjs.md +219 -0
- package/skills/ima2-front/references/stacks/react.md +264 -0
- package/skills/ima2-front/references/stacks/svelte.md +235 -0
- package/skills/ima2-front/references/stacks/vanilla.md +226 -0
- package/skills/ima2-front/references/theme-switching.md +82 -0
- package/skills/ima2-front/references/top-bar.md +126 -0
- package/skills/ima2-front/references/typography-wrapping.md +270 -0
- package/skills/ima2-front/references/ux-writing-ko.md +62 -0
- package/skills/ima2-front/references/visual-verification.md +102 -0
- package/skills/ima2-uiux/SKILL.md +477 -0
- package/skills/ima2-uiux/references/anti-slop-judgment.md +39 -0
- package/skills/ima2-uiux/references/color-system.md +80 -0
- package/skills/ima2-uiux/references/design-isms.md +194 -0
- package/skills/ima2-uiux/references/design-read-example.md +33 -0
- package/skills/ima2-uiux/references/design-system-bootstrap.md +133 -0
- package/skills/ima2-uiux/references/favicon-logo.md +355 -0
- package/skills/ima2-uiux/references/form-patterns.md +118 -0
- package/skills/ima2-uiux/references/intent-discovery-ladder.md +86 -0
- package/skills/ima2-uiux/references/korean-design-vocabulary.md +52 -0
- package/skills/ima2-uiux/references/layout-macrostructures.md +54 -0
- package/skills/ima2-uiux/references/logo-trust-sections.md +96 -0
- package/skills/ima2-uiux/references/mobile-native-ux.md +207 -0
- package/skills/ima2-uiux/references/product-personalities.md +118 -0
- package/skills/ima2-uiux/references/responsive-nav.md +66 -0
- package/skills/ima2-uiux/references/typography-line-breaks.md +193 -0
- package/skills/ima2-uiux/references/ux-preflight.md +65 -0
- package/skills/ima2-uiux/references/ux-states.md +197 -0
- package/skills/ima2-uiux/references/visual-hierarchy.md +115 -0
- package/ui/dist/.vite/manifest.json +13 -13
- package/ui/dist/assets/AgentWorkspace-CtYt4SF5.js +3 -0
- package/ui/dist/assets/{CardNewsWorkspace-Cdxn1ohU.js → CardNewsWorkspace-DN5cVqG5.js} +1 -1
- package/ui/dist/assets/GenerationRequestLogPanel-DnmUQdKR.js +1 -0
- package/ui/dist/assets/{NodeCanvas-C67sLboA.js → NodeCanvas-CxpfIBI0.js} +1 -1
- package/ui/dist/assets/PromptBuilderPanel-C2R3ObtR.js +2 -0
- package/ui/dist/assets/{PromptImportDialog-DeQXsEEv.js → PromptImportDialog-BIxz_yEx.js} +2 -2
- package/ui/dist/assets/PromptImportDiscoverySection-BQTlPMME.js +1 -0
- package/ui/dist/assets/PromptImportFolderSection-Ds_Th9O3.js +1 -0
- package/ui/dist/assets/{PromptLibraryPanel-Db3xEPM_.js → PromptLibraryPanel-Rs4l45pV.js} +2 -2
- package/ui/dist/assets/{SettingsWorkspace-BDk_1W-V.js → SettingsWorkspace-p1AZ6uT9.js} +1 -1
- package/ui/dist/assets/index-0-_vgFGs.js +4 -0
- package/ui/dist/assets/index-Dm3pFxV4.js +23 -0
- package/ui/dist/assets/index-J8yDF3Ch.css +1 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/AgentWorkspace-DAg_7zpY.js +0 -3
- package/ui/dist/assets/GenerationRequestLogPanel-C_qvEUi2.js +0 -1
- package/ui/dist/assets/PromptBuilderPanel-DjvVdAie.js +0 -2
- package/ui/dist/assets/PromptImportDiscoverySection-CrwyUKYI.js +0 -1
- package/ui/dist/assets/PromptImportFolderSection-CToGMqYE.js +0 -1
- package/ui/dist/assets/index-2oG6in1i.css +0 -1
- package/ui/dist/assets/index-CoxMOXAU.js +0 -23
- package/ui/dist/assets/index-p8egnGHk.js +0 -4
package/README.md
CHANGED
|
@@ -97,6 +97,36 @@ Ctrl+C now performs a clean shutdown — closing the database, stopping child pr
|
|
|
97
97
|
- **Mobile shell**: use the app bar, compose sheet, and compact settings toggle on smaller screens.
|
|
98
98
|
- **Observable jobs**: active and recent jobs are tracked with safe logs and request IDs.
|
|
99
99
|
|
|
100
|
+
### Agent Skills
|
|
101
|
+
|
|
102
|
+
ima2-gen ships three packaged skills for AI coding agents. These are Markdown
|
|
103
|
+
instruction files that agents load to get structured workflows for image/video
|
|
104
|
+
generation, frontend asset production, and design direction discovery.
|
|
105
|
+
|
|
106
|
+
| Skill | Command | What It Covers |
|
|
107
|
+
|-------|---------|----------------|
|
|
108
|
+
| **Core** | `ima2 skill` | CLI reference, prompting protocol, provider routing, Korean text, video workflows |
|
|
109
|
+
| **Frontend** | `ima2 skill front` | Asset pipeline (parallel gen, variant selection, provider routing), motion/video for web, responsive, a11y, anti-slop, 30+ reference files |
|
|
110
|
+
| **UI/UX Design** | `ima2 skill uiux` | Image-first design direction discovery, UX states, design-isms, product personalities, DESIGN.md workflow, 18 reference files |
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
ima2 skill ls # list available skills
|
|
114
|
+
ima2 skill front # print the frontend skill
|
|
115
|
+
ima2 skill uiux # print the design skill
|
|
116
|
+
ima2 skill front path # print file path (for agents)
|
|
117
|
+
ima2 skill front --json # JSON wrapper (for agents)
|
|
118
|
+
ima2 skill front refs # list reference modules (35 files)
|
|
119
|
+
ima2 skill front ref motion # load one reference module
|
|
120
|
+
ima2 skill install --dir <path> # install skills to agent's skill dir
|
|
121
|
+
ima2 skill install --tmp # install to temp dir (fallback)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The Frontend and UI/UX skills are production-grade design engineering guides
|
|
125
|
+
adapted for the ima2 workflow. They cover typography, color systems, layout
|
|
126
|
+
discipline, Korean UX patterns, motion choreography, and visual verification,
|
|
127
|
+
with every asset generation step mapped to `ima2 gen`, `ima2 video`, and
|
|
128
|
+
`ima2 multimode` commands.
|
|
129
|
+
|
|
100
130
|
### SSE Multiplexing
|
|
101
131
|
|
|
102
132
|
The web UI uses a single `GET /api/events` Server-Sent Events connection for all generation progress. Multimode, node, and video requests are submitted as async POST (`202 { requestId }`) and progress events are multiplexed through a shared event bus. This eliminates the browser 6-connection limit that previously caused gallery hangs during concurrent generation. CLI clients that do not send `async: true` still receive per-request SSE streams for backward compatibility.
|
package/bin/commands/skill.js
CHANGED
|
@@ -1,24 +1,80 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "fs";
|
|
2
|
-
import { dirname, join, relative } from "path";
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, statSync, mkdirSync, cpSync } from "fs";
|
|
2
|
+
import { dirname, join, relative, basename, extname, resolve } from "path";
|
|
3
|
+
import { tmpdir } from "os";
|
|
3
4
|
import { fileURLToPath } from "url";
|
|
4
5
|
import { parseArgs } from "../lib/args.js";
|
|
5
6
|
import { die, json, out } from "../lib/output.js";
|
|
6
7
|
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
7
|
-
const
|
|
8
|
+
const SKILLS_DIR = join(ROOT, "skills");
|
|
8
9
|
const PACKAGE_PATH = join(ROOT, "package.json");
|
|
10
|
+
const KNOWN_SKILLS = {
|
|
11
|
+
ima2: {
|
|
12
|
+
dir: "ima2",
|
|
13
|
+
description: "Core CLI reference, prompting protocol, provider routing",
|
|
14
|
+
},
|
|
15
|
+
front: {
|
|
16
|
+
dir: "ima2-front",
|
|
17
|
+
description: "Frontend implementation: assets, motion, responsive, a11y, anti-slop",
|
|
18
|
+
},
|
|
19
|
+
uiux: {
|
|
20
|
+
dir: "ima2-uiux",
|
|
21
|
+
description: "Design direction discovery, UX judgment, image-first ism workflow",
|
|
22
|
+
},
|
|
23
|
+
};
|
|
9
24
|
const HELP = `
|
|
10
|
-
ima2 skill [path] [--json]
|
|
25
|
+
ima2 skill [<name>] [path|refs|ref <name>|install] [--json]
|
|
11
26
|
|
|
12
|
-
Print
|
|
27
|
+
Print packaged ima2 Markdown skills and reference modules for agents.
|
|
28
|
+
Each skill ships a SKILL.md + a references/ directory. Reference modules
|
|
29
|
+
are loaded by agents via relative paths from the SKILL.md.
|
|
13
30
|
|
|
14
31
|
Commands:
|
|
15
|
-
ima2 skill
|
|
16
|
-
ima2 skill
|
|
17
|
-
ima2 skill
|
|
32
|
+
ima2 skill Print skills/ima2/SKILL.md (core)
|
|
33
|
+
ima2 skill front Print skills/ima2-front/SKILL.md (frontend)
|
|
34
|
+
ima2 skill uiux Print skills/ima2-uiux/SKILL.md (design)
|
|
35
|
+
ima2 skill ls List all available skills
|
|
36
|
+
ima2 skill <name> path Print skill file path
|
|
37
|
+
|
|
38
|
+
Install to agent skill directory:
|
|
39
|
+
ima2 skill install --dir <path> Copy all skills to <path>/
|
|
40
|
+
ima2 skill install front --dir <path> Copy only the frontend skill
|
|
41
|
+
ima2 skill install uiux --dir <path> Copy only the design skill
|
|
42
|
+
ima2 skill install --tmp Copy to $TMPDIR/ima2-skills/ (ephemeral)
|
|
43
|
+
ima2 skill install front --tmp Copy one skill to temp dir
|
|
44
|
+
|
|
45
|
+
Each skill is a directory (e.g. <path>/ima2-front/) with SKILL.md +
|
|
46
|
+
references/. The agent determines its own skill path and passes --dir.
|
|
47
|
+
Use --tmp when no persistent path is available.
|
|
48
|
+
|
|
49
|
+
Reference modules (ad-hoc, without install):
|
|
50
|
+
ima2 skill <name> refs List reference modules for a skill
|
|
51
|
+
ima2 skill <name> ref <name> Print one reference module
|
|
52
|
+
|
|
53
|
+
Examples:
|
|
54
|
+
ima2 skill install --dir ~/.codex/skills Install all skills
|
|
55
|
+
ima2 skill install front --dir ~/.codex/skills Install frontend only
|
|
56
|
+
ima2 skill install --tmp Install to temp dir
|
|
57
|
+
ima2 skill front refs List frontend references
|
|
58
|
+
ima2 skill front ref motion Print references/motion.md
|
|
59
|
+
ima2 skill uiux ref design-isms Print references/design-isms.md
|
|
60
|
+
|
|
61
|
+
Agent integration:
|
|
62
|
+
The agent is responsible for resolving its own skill directory path.
|
|
63
|
+
Pass that path to --dir. After install, the agent reads SKILL.md and
|
|
64
|
+
follows references/ paths natively — no stdout piping needed.
|
|
65
|
+
|
|
66
|
+
Options:
|
|
67
|
+
--json Print JSON wrapper
|
|
68
|
+
--dir <path> Target directory for install
|
|
69
|
+
--tmp Use $TMPDIR/ima2-skills/ as target
|
|
70
|
+
-h, --help Show help
|
|
18
71
|
`;
|
|
19
72
|
const FLAGS = {
|
|
20
73
|
json: { type: "boolean" },
|
|
21
74
|
help: { short: "h", type: "boolean" },
|
|
75
|
+
"with-refs": { type: "boolean" },
|
|
76
|
+
dir: { type: "string" },
|
|
77
|
+
tmp: { type: "boolean" },
|
|
22
78
|
};
|
|
23
79
|
function readPackageVersion() {
|
|
24
80
|
try {
|
|
@@ -29,30 +85,293 @@ function readPackageVersion() {
|
|
|
29
85
|
return "?";
|
|
30
86
|
}
|
|
31
87
|
}
|
|
32
|
-
function
|
|
33
|
-
|
|
34
|
-
|
|
88
|
+
function resolveSkillPath(name) {
|
|
89
|
+
const entry = KNOWN_SKILLS[name];
|
|
90
|
+
if (!entry)
|
|
91
|
+
die(2, `unknown skill: "${name}". Available: ${Object.keys(KNOWN_SKILLS).join(", ")}`);
|
|
92
|
+
return join(SKILLS_DIR, entry.dir, "SKILL.md");
|
|
93
|
+
}
|
|
94
|
+
function readSkill(path) {
|
|
95
|
+
if (!existsSync(path)) {
|
|
96
|
+
die(5, `packaged skill not found: ${path}`);
|
|
97
|
+
}
|
|
98
|
+
return readFileSync(path, "utf-8");
|
|
99
|
+
}
|
|
100
|
+
function discoverRefs(skillDir) {
|
|
101
|
+
const refsDir = join(skillDir, "references");
|
|
102
|
+
if (!existsSync(refsDir))
|
|
103
|
+
return [];
|
|
104
|
+
const entries = [];
|
|
105
|
+
function walk(dir, prefix) {
|
|
106
|
+
for (const item of readdirSync(dir)) {
|
|
107
|
+
const full = join(dir, item);
|
|
108
|
+
const st = statSync(full);
|
|
109
|
+
if (st.isDirectory()) {
|
|
110
|
+
walk(full, prefix ? `${prefix}/${item}` : item);
|
|
111
|
+
}
|
|
112
|
+
else if (extname(item) === ".md") {
|
|
113
|
+
const nameNoExt = basename(item, ".md");
|
|
114
|
+
const qualName = prefix ? `${prefix}/${nameNoExt}` : nameNoExt;
|
|
115
|
+
const relFile = prefix ? `${prefix}/${item}` : item;
|
|
116
|
+
entries.push({
|
|
117
|
+
name: qualName,
|
|
118
|
+
file: relFile,
|
|
119
|
+
relPath: `references/${relFile}`,
|
|
120
|
+
absPath: full,
|
|
121
|
+
lines: readFileSync(full, "utf-8").split("\n").length,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
35
125
|
}
|
|
36
|
-
|
|
126
|
+
walk(refsDir, "");
|
|
127
|
+
return entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
128
|
+
}
|
|
129
|
+
function resolveRef(skillDir, refName) {
|
|
130
|
+
const refs = discoverRefs(skillDir);
|
|
131
|
+
const exact = refs.find((r) => r.name === refName);
|
|
132
|
+
if (exact)
|
|
133
|
+
return exact;
|
|
134
|
+
const byBase = refs.filter((r) => {
|
|
135
|
+
const parts = r.name.split("/");
|
|
136
|
+
return parts[parts.length - 1] === refName;
|
|
137
|
+
});
|
|
138
|
+
if (byBase.length === 1)
|
|
139
|
+
return byBase[0];
|
|
140
|
+
const partial = refs.filter((r) => r.name.includes(refName));
|
|
141
|
+
if (partial.length === 1)
|
|
142
|
+
return partial[0];
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
// ── Install helpers ──
|
|
146
|
+
function copySkillDir(srcDir, destDir) {
|
|
147
|
+
mkdirSync(destDir, { recursive: true });
|
|
148
|
+
cpSync(srcDir, destDir, { recursive: true });
|
|
149
|
+
// Count files copied
|
|
150
|
+
let count = 0;
|
|
151
|
+
function countFiles(dir) {
|
|
152
|
+
for (const item of readdirSync(dir)) {
|
|
153
|
+
const full = join(dir, item);
|
|
154
|
+
if (statSync(full).isDirectory())
|
|
155
|
+
countFiles(full);
|
|
156
|
+
else
|
|
157
|
+
count++;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
countFiles(destDir);
|
|
161
|
+
return { files: count };
|
|
162
|
+
}
|
|
163
|
+
function resolveInstallDir(args) {
|
|
164
|
+
if (args.dir)
|
|
165
|
+
return resolve(String(args.dir));
|
|
166
|
+
if (args.tmp)
|
|
167
|
+
return join(tmpdir(), "ima2-skills");
|
|
168
|
+
return null;
|
|
37
169
|
}
|
|
170
|
+
function installSkills(targetBase, only, asJson) {
|
|
171
|
+
const results = [];
|
|
172
|
+
for (const [name, info] of Object.entries(KNOWN_SKILLS)) {
|
|
173
|
+
if (only && !only.includes(name))
|
|
174
|
+
continue;
|
|
175
|
+
const srcDir = join(SKILLS_DIR, info.dir);
|
|
176
|
+
if (!existsSync(join(srcDir, "SKILL.md")))
|
|
177
|
+
continue;
|
|
178
|
+
const destDir = join(targetBase, info.dir);
|
|
179
|
+
const { files } = copySkillDir(srcDir, destDir);
|
|
180
|
+
results.push({ name, dir: info.dir, dest: destDir, files });
|
|
181
|
+
}
|
|
182
|
+
if (results.length === 0 && only) {
|
|
183
|
+
die(2, `unknown skill: "${only.join(", ")}". Available: ${Object.keys(KNOWN_SKILLS).join(", ")}`);
|
|
184
|
+
}
|
|
185
|
+
if (asJson) {
|
|
186
|
+
json({
|
|
187
|
+
installed: results.map((r) => ({
|
|
188
|
+
name: r.name,
|
|
189
|
+
dir: r.dir,
|
|
190
|
+
path: r.dest,
|
|
191
|
+
files: r.files,
|
|
192
|
+
})),
|
|
193
|
+
target: targetBase,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
out(`\n Installed ${results.length} ima2 skill${results.length === 1 ? "" : "s"} to ${targetBase}\n`);
|
|
198
|
+
for (const r of results) {
|
|
199
|
+
out(` ✓ ${r.dir.padEnd(12)} ${r.files} files → ${r.dest}`);
|
|
200
|
+
}
|
|
201
|
+
out(`\n Agents can now read SKILL.md + references/ via filesystem paths.\n`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// ── Main command ──
|
|
38
205
|
export default async function skillCmd(argv) {
|
|
39
206
|
const args = parseArgs(argv, { flags: FLAGS });
|
|
40
207
|
if (args.help) {
|
|
41
208
|
out(HELP);
|
|
42
209
|
return;
|
|
43
210
|
}
|
|
44
|
-
|
|
45
|
-
|
|
211
|
+
const positional = args.positional;
|
|
212
|
+
// ima2 skill install — copy skills to filesystem
|
|
213
|
+
if (positional[0] === "install") {
|
|
214
|
+
const targetBase = resolveInstallDir(args);
|
|
215
|
+
if (!targetBase) {
|
|
216
|
+
die(2, "specify --dir <path> or --tmp. The agent determines its own skill directory path.\n\n Examples:\n ima2 skill install --dir ~/.codex/skills\n ima2 skill install front --dir ./skills\n ima2 skill install --tmp");
|
|
217
|
+
}
|
|
218
|
+
// Collect optional skill names after "install"
|
|
219
|
+
const installNames = positional.slice(1).filter((p) => KNOWN_SKILLS[p]);
|
|
220
|
+
installSkills(targetBase, installNames.length > 0 ? installNames : null, !!args.json);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
// ima2 skill ls — list available skills
|
|
224
|
+
if (positional[0] === "ls" || positional[0] === "list") {
|
|
225
|
+
if (args.json) {
|
|
226
|
+
const skills = Object.entries(KNOWN_SKILLS).map(([name, info]) => {
|
|
227
|
+
const skillPath = join(SKILLS_DIR, info.dir, "SKILL.md");
|
|
228
|
+
return {
|
|
229
|
+
name,
|
|
230
|
+
dir: info.dir,
|
|
231
|
+
description: info.description,
|
|
232
|
+
path: relative(ROOT, skillPath),
|
|
233
|
+
installed: existsSync(skillPath),
|
|
234
|
+
};
|
|
235
|
+
});
|
|
236
|
+
json({ skills });
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
out("\n Available ima2 skills:\n");
|
|
240
|
+
for (const [name, info] of Object.entries(KNOWN_SKILLS)) {
|
|
241
|
+
const skillPath = join(SKILLS_DIR, info.dir, "SKILL.md");
|
|
242
|
+
const status = existsSync(skillPath) ? "✓" : "✗";
|
|
243
|
+
out(` ${status} ${name.padEnd(8)} ${info.description}`);
|
|
244
|
+
}
|
|
245
|
+
out(`\n Usage: ima2 skill <name> Print a skill`);
|
|
246
|
+
out(` ima2 skill <name> path Print its file path`);
|
|
247
|
+
out(` ima2 skill <name> refs List reference modules`);
|
|
248
|
+
out(` ima2 skill install [<name>] --dir <path>\n`);
|
|
249
|
+
}
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
// Determine which skill to read
|
|
253
|
+
let skillName = "ima2";
|
|
254
|
+
let wantPath = false;
|
|
255
|
+
let wantRefs = false;
|
|
256
|
+
let wantRef = null;
|
|
257
|
+
if (positional.length > 0 && positional[0] !== "path" && positional[0] !== "refs" && positional[0] !== "ref" && KNOWN_SKILLS[positional[0]]) {
|
|
258
|
+
skillName = positional[0];
|
|
259
|
+
if (positional[1] === "path")
|
|
260
|
+
wantPath = true;
|
|
261
|
+
else if (positional[1] === "refs")
|
|
262
|
+
wantRefs = true;
|
|
263
|
+
else if (positional[1] === "ref" && positional[2])
|
|
264
|
+
wantRef = positional.slice(2).join("/");
|
|
265
|
+
}
|
|
266
|
+
else if (positional[0] === "path") {
|
|
267
|
+
wantPath = true;
|
|
268
|
+
}
|
|
269
|
+
else if (positional[0] === "refs") {
|
|
270
|
+
wantRefs = true;
|
|
271
|
+
}
|
|
272
|
+
else if (positional[0] === "ref" && positional[1]) {
|
|
273
|
+
wantRef = positional.slice(1).join("/");
|
|
274
|
+
}
|
|
275
|
+
const targetPath = resolveSkillPath(skillName);
|
|
276
|
+
if (wantPath) {
|
|
277
|
+
out(targetPath);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const skillDir = dirname(targetPath);
|
|
281
|
+
// ima2 skill <name> refs — list reference modules
|
|
282
|
+
if (wantRefs) {
|
|
283
|
+
const refs = discoverRefs(skillDir);
|
|
284
|
+
if (refs.length === 0) {
|
|
285
|
+
if (args.json) {
|
|
286
|
+
json({ skill: skillName, refs: [] });
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
out(`\n No reference modules for skill "${skillName}".\n`);
|
|
290
|
+
}
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (args.json) {
|
|
294
|
+
json({
|
|
295
|
+
skill: skillName,
|
|
296
|
+
refs: refs.map((r) => ({
|
|
297
|
+
name: r.name,
|
|
298
|
+
file: r.relPath,
|
|
299
|
+
lines: r.lines,
|
|
300
|
+
})),
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
out(`\n Reference modules for "${skillName}" (${refs.length} files):\n`);
|
|
305
|
+
for (const r of refs) {
|
|
306
|
+
out(` ${r.name.padEnd(30)} ${String(r.lines).padStart(4)} lines`);
|
|
307
|
+
}
|
|
308
|
+
out(`\n Load one: ima2 skill ${skillName} ref <name>`);
|
|
309
|
+
out(` Install: ima2 skill install ${skillName} --dir <path>\n`);
|
|
310
|
+
}
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
// ima2 skill <name> ref <refname> — print one reference module
|
|
314
|
+
if (wantRef) {
|
|
315
|
+
const ref = resolveRef(skillDir, wantRef);
|
|
316
|
+
if (!ref) {
|
|
317
|
+
const refs = discoverRefs(skillDir);
|
|
318
|
+
const available = refs.map((r) => r.name).join(", ");
|
|
319
|
+
die(2, `reference "${wantRef}" not found in skill "${skillName}". Available: ${available}`);
|
|
320
|
+
}
|
|
321
|
+
const content = readFileSync(ref.absPath, "utf-8");
|
|
322
|
+
if (args.json) {
|
|
323
|
+
json({
|
|
324
|
+
skill: skillName,
|
|
325
|
+
ref: ref.name,
|
|
326
|
+
file: ref.relPath,
|
|
327
|
+
lines: ref.lines,
|
|
328
|
+
content,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
out(content.replace(/\n$/, ""));
|
|
333
|
+
}
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
// --with-refs: bundle SKILL.md + all reference modules (hidden, discouraged)
|
|
337
|
+
if (args["with-refs"]) {
|
|
338
|
+
const content = readSkill(targetPath);
|
|
339
|
+
// Inline bundle — kept for backward compat but not recommended
|
|
340
|
+
const refs = discoverRefs(skillDir);
|
|
341
|
+
let bundled = content.replace(/\n$/, "");
|
|
342
|
+
if (refs.length > 0) {
|
|
343
|
+
bundled += "\n\n---\n\n# Bundled Reference Modules\n";
|
|
344
|
+
for (const ref of refs) {
|
|
345
|
+
const rc = readFileSync(ref.absPath, "utf-8").replace(/\n$/, "");
|
|
346
|
+
bundled += `\n## [ref: ${ref.name}] (${ref.relPath})\n\n${rc}\n`;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
process.stderr.write(" hint: prefer 'ima2 skill install --dir <path>' — agents read references natively from disk.\n");
|
|
350
|
+
if (args.json) {
|
|
351
|
+
json({
|
|
352
|
+
name: skillName === "ima2" ? "ima2" : `ima2-${skillName}`,
|
|
353
|
+
format: "markdown-skill-bundled",
|
|
354
|
+
formatVersion: "1",
|
|
355
|
+
packageVersion: readPackageVersion(),
|
|
356
|
+
path: relative(ROOT, targetPath),
|
|
357
|
+
refCount: discoverRefs(skillDir).length,
|
|
358
|
+
source: "package",
|
|
359
|
+
content: bundled,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
out(bundled.replace(/\n$/, ""));
|
|
364
|
+
}
|
|
46
365
|
return;
|
|
47
366
|
}
|
|
48
|
-
const content = readSkill();
|
|
367
|
+
const content = readSkill(targetPath);
|
|
49
368
|
if (args.json) {
|
|
50
369
|
json({
|
|
51
|
-
name: "ima2"
|
|
370
|
+
name: skillName === "ima2" ? "ima2" : `ima2-${skillName}`,
|
|
52
371
|
format: "markdown-skill",
|
|
53
372
|
formatVersion: "1",
|
|
54
373
|
packageVersion: readPackageVersion(),
|
|
55
|
-
path: relative(ROOT,
|
|
374
|
+
path: relative(ROOT, targetPath),
|
|
56
375
|
source: "package",
|
|
57
376
|
content,
|
|
58
377
|
});
|
package/bin/ima2.js
CHANGED
|
@@ -315,9 +315,21 @@ function showHelp() {
|
|
|
315
315
|
config <sub> Config get/set/ls/path/rm (ima2 config --help)
|
|
316
316
|
defaults <sub> Inspect/change model defaults (ima2 defaults --help)
|
|
317
317
|
capabilities Agent capability metadata (ima2 capabilities --help)
|
|
318
|
-
skill Print packaged agent skill (ima2 skill --help)
|
|
319
318
|
ping Ping running server / check health
|
|
320
319
|
|
|
320
|
+
Agent skills (SKILL.md + references/):
|
|
321
|
+
skill ls List packaged skills (ima2, front, uiux)
|
|
322
|
+
skill [front|uiux] Print a skill's SKILL.md
|
|
323
|
+
skill [front|uiux] refs List reference modules
|
|
324
|
+
skill [front|uiux] ref <name> Print one reference module
|
|
325
|
+
skill install --dir <path> Install all skills to a directory
|
|
326
|
+
skill install front --dir <path> Install one skill only
|
|
327
|
+
skill install --tmp Install to temp dir (ephemeral)
|
|
328
|
+
|
|
329
|
+
Skills ship as directories (SKILL.md + references/). The agent resolves
|
|
330
|
+
its own skill path and passes it via --dir. After install, the agent
|
|
331
|
+
reads SKILL.md and follows references/ natively from disk.
|
|
332
|
+
|
|
321
333
|
Options:
|
|
322
334
|
-v, --version Show version
|
|
323
335
|
-h, --help Show help
|
|
@@ -341,7 +353,15 @@ function showHelp() {
|
|
|
341
353
|
ima2 gen "merge" --ref a.png --ref b.png -q high -o out.png
|
|
342
354
|
ima2 video "a cat playing piano" --duration 10
|
|
343
355
|
ima2 ls -n 10 Last 10 generations
|
|
344
|
-
ima2 skill Print agent
|
|
356
|
+
ima2 skill Print core agent skill
|
|
357
|
+
ima2 skill ls List all skills (core, front, uiux)
|
|
358
|
+
ima2 skill front Print frontend implementation skill
|
|
359
|
+
ima2 skill uiux Print design direction skill
|
|
360
|
+
ima2 skill front refs List frontend reference modules
|
|
361
|
+
ima2 skill front ref motion Load one reference module
|
|
362
|
+
ima2 skill install --dir <path> Install all skills
|
|
363
|
+
ima2 skill install front --dir <path> Install frontend skill only
|
|
364
|
+
ima2 skill install --tmp Install to temp dir
|
|
345
365
|
ima2 capabilities --json Inspect supported models/options
|
|
346
366
|
ima2 defaults --json Inspect running server defaults
|
|
347
367
|
ima2 ping Health check
|
package/bin/lib/doctor-checks.js
CHANGED
|
@@ -63,10 +63,12 @@ export async function buildHardeningDoctorLines({ root, configFile, fileConfig,
|
|
|
63
63
|
kind: "info",
|
|
64
64
|
text: `Card News: ${runtimeConfig.features.cardNews ? "enabled" : "disabled"}`,
|
|
65
65
|
});
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
for (const skillDir of ["ima2", "ima2-front", "ima2-uiux"]) {
|
|
67
|
+
const skillPath = join(root, "skills", skillDir, "SKILL.md");
|
|
68
|
+
lines.push(existsSync(skillPath)
|
|
69
|
+
? { kind: "pass", text: `packaged skill found: ${skillPath}` }
|
|
70
|
+
: { kind: "fail", text: `packaged skill missing: ${skillPath}` });
|
|
71
|
+
}
|
|
70
72
|
lines.push(probeBetterSqlite(root));
|
|
71
73
|
const perm = configPermissionLine(configFile, fileConfig);
|
|
72
74
|
if (perm)
|
package/config.js
CHANGED
|
@@ -73,6 +73,7 @@ export const config = {
|
|
|
73
73
|
// Accept both IMA2_PORT and legacy PORT.
|
|
74
74
|
port: pickInt(firstDefined(env.IMA2_PORT, env.PORT), fileCfg.server?.port, 3333),
|
|
75
75
|
host: pickStr(env.IMA2_HOST, fileCfg.server?.host, "127.0.0.1"),
|
|
76
|
+
lanToken: env.IMA2_LAN_TOKEN || "",
|
|
76
77
|
bodyLimit: pickStr(env.IMA2_BODY_LIMIT, fileCfg.server?.bodyLimit, "50mb"),
|
|
77
78
|
},
|
|
78
79
|
limits: {
|
package/docs/CLI.md
CHANGED
|
@@ -34,9 +34,24 @@ Agents should start from the packaged skill and capability commands instead of g
|
|
|
34
34
|
|
|
35
35
|
| Command | Description |
|
|
36
36
|
|---|---|
|
|
37
|
-
| `ima2 skill` | Print the
|
|
38
|
-
| `ima2 skill
|
|
39
|
-
| `ima2 skill
|
|
37
|
+
| `ima2 skill` | Print the core Markdown skill (`skills/ima2/SKILL.md`) |
|
|
38
|
+
| `ima2 skill front` | Print the frontend implementation skill (`skills/ima2-front/SKILL.md`) |
|
|
39
|
+
| `ima2 skill uiux` | Print the design direction skill (`skills/ima2-uiux/SKILL.md`) |
|
|
40
|
+
| `ima2 skill ls` | List all available packaged skills |
|
|
41
|
+
| `ima2 skill --json` | Print a JSON wrapper around the core skill content |
|
|
42
|
+
| `ima2 skill front --json` | Print a JSON wrapper around the frontend skill |
|
|
43
|
+
| `ima2 skill uiux --json` | Print a JSON wrapper around the design skill |
|
|
44
|
+
| `ima2 skill path` | Print the core skill file path |
|
|
45
|
+
| `ima2 skill front path` | Print the frontend skill file path |
|
|
46
|
+
| `ima2 skill uiux path` | Print the design skill file path |
|
|
47
|
+
| `ima2 skill front refs` | List reference modules for the frontend skill (names + line counts) |
|
|
48
|
+
| `ima2 skill uiux refs` | List reference modules for the design skill |
|
|
49
|
+
| `ima2 skill front ref <name>` | Print one reference module by name (e.g. `motion`, `stacks/react`) |
|
|
50
|
+
| `ima2 skill uiux ref <name>` | Print one reference module by name (e.g. `design-isms`) |
|
|
51
|
+
| `ima2 skill install --dir <path>` | Install all skills to agent's skill directory |
|
|
52
|
+
| `ima2 skill install --tmp` | Install to `$TMPDIR/ima2-skills/` (ephemeral fallback) |
|
|
53
|
+
| `ima2 skill front refs --json` | JSON list of reference modules |
|
|
54
|
+
| `ima2 skill front ref motion --json` | JSON wrapper around one reference module |
|
|
40
55
|
| `ima2 capabilities --json` | Print supported commands, model/quality/reasoning values, and advisory limits |
|
|
41
56
|
| `ima2 defaults --json` | Print the running server's effective model/reasoning defaults, falling back to local config when no server is reachable |
|
|
42
57
|
| `ima2 defaults --local --json` | Print local effective defaults without contacting the server |
|
|
@@ -404,6 +419,13 @@ ima2 storage status --json
|
|
|
404
419
|
|
|
405
420
|
# Config
|
|
406
421
|
ima2 skill --json
|
|
422
|
+
ima2 skill ls
|
|
423
|
+
ima2 skill front --json
|
|
424
|
+
ima2 skill uiux path
|
|
425
|
+
ima2 skill front refs
|
|
426
|
+
ima2 skill front ref motion
|
|
427
|
+
ima2 skill install --dir ~/.codex/skills
|
|
428
|
+
ima2 skill install --tmp
|
|
407
429
|
ima2 capabilities --json
|
|
408
430
|
ima2 defaults set model gpt-5.5
|
|
409
431
|
ima2 defaults set reasoning high
|
package/docs/PROMPT_STUDIO.ko.md
CHANGED
|
@@ -109,3 +109,32 @@ v1.1.13 Prompt Studio 문제를 고치면서 아래 동작을 계약으로 고
|
|
|
109
109
|
|
|
110
110
|
ChatGPT 쿠키, OAuth token 파일, API key, raw upstream response, private prompt
|
|
111
111
|
history, generated base64 data는 공유하지 마세요.
|
|
112
|
+
|
|
113
|
+
## 캔버스 노트와 i2i 편집
|
|
114
|
+
|
|
115
|
+
캔버스에서 메모(노트)·화살표·박스를 그리고 저장하면, 노트는 **픽셀이 아니라
|
|
116
|
+
텍스트 지시**로 모델에 전달됩니다.
|
|
117
|
+
|
|
118
|
+
- 캔버스 버전을 저장하면 주석이 구워진 이미지는 UI 표시/기록용으로만 남고,
|
|
119
|
+
모델 참조에는 항상 **깨끗한 원본**이 들어갑니다. 현재 이미지를 참조로
|
|
120
|
+
추가하는 동작도 캔버스 버전이면 원본으로 대체됩니다.
|
|
121
|
+
- 노트 내용은 "캔버스 노트" 칩으로 작성창에 붙습니다. 다음 생성 요청이 이
|
|
122
|
+
지시문(위치 + 내용 + 마크업 제거 규칙)을 함께 보냅니다. 칩은 언제든 지울
|
|
123
|
+
수 있습니다.
|
|
124
|
+
- 박스를 그린 뒤 마스크 편집을 실행하면 깨끗한 원본 + 박스 마스크 + 노트
|
|
125
|
+
지시문으로 편집 API를 호출합니다.
|
|
126
|
+
|
|
127
|
+
그래도 결과물에 노트/마크업이 남으면:
|
|
128
|
+
|
|
129
|
+
- 노트 문구를 "무엇을 없앨지"가 아니라 **그 자리에 무엇이 보여야 하는지**로
|
|
130
|
+
적어 보세요. 예: "포스트잇 제거" 대신 "포스트잇이 있던 자리에 연속된 나무
|
|
131
|
+
책상 표면이 보이게".
|
|
132
|
+
- 같은 요청으로 여러 후보를 생성해 가장 깨끗한 결과를 고르세요.
|
|
133
|
+
- 남은 흔적만 다시 박스로 지정해 한 번 더 편집하세요.
|
|
134
|
+
|
|
135
|
+
### 구워진 노트 되돌리기
|
|
136
|
+
|
|
137
|
+
노트가 구워진 캔버스 버전에서는 **구워진 노트 되돌리기**를 사용할 수 있습니다.
|
|
138
|
+
이후 픽셀 편집이 없으면 깨끗한 원본을 복원하고 저장 당시 노트를 편집 가능한
|
|
139
|
+
초안으로 되살립니다. 배경 정리 등 픽셀 편집이 뒤에 추가됐다면, 되돌릴 때 해당
|
|
140
|
+
편집도 사라지므로 먼저 확인합니다.
|
package/docs/PROMPT_STUDIO.md
CHANGED
|
@@ -115,3 +115,34 @@ When opening an issue, include:
|
|
|
115
115
|
|
|
116
116
|
Do not share ChatGPT cookies, OAuth token files, API keys, raw upstream
|
|
117
117
|
responses, private prompt history, or generated base64 data.
|
|
118
|
+
|
|
119
|
+
## Canvas Notes And I2I Editing
|
|
120
|
+
|
|
121
|
+
When you draw memos (notes), arrows, or boxes on the canvas and save, the
|
|
122
|
+
notes reach the model as **text instructions, not pixels**.
|
|
123
|
+
|
|
124
|
+
- Saving a canvas version keeps the burned-in annotated image for UI display
|
|
125
|
+
and history only; model references always use the **clean source**. Adding
|
|
126
|
+
the current image as a reference also swaps a canvas version for its
|
|
127
|
+
original.
|
|
128
|
+
- Note contents attach to the composer as a removable "Canvas notes" chip, so
|
|
129
|
+
the next generation carries the instructions (position + content + a
|
|
130
|
+
markup-removal rule).
|
|
131
|
+
- Drawing boxes and running the mask edit calls the edit API with the clean
|
|
132
|
+
source, a box mask, and the note instructions.
|
|
133
|
+
|
|
134
|
+
If markup still shows up in a result:
|
|
135
|
+
|
|
136
|
+
- Phrase notes as **what should be visible in that spot**, not only what to
|
|
137
|
+
remove. For example, "show the continuous wooden desk surface where the
|
|
138
|
+
sticky note was" instead of "remove the sticky note".
|
|
139
|
+
- Generate several candidates from the same request and pick the cleanest.
|
|
140
|
+
- Box only the residual trace and run one more mask edit.
|
|
141
|
+
|
|
142
|
+
### Reverting Baked Notes
|
|
143
|
+
|
|
144
|
+
Canvas versions that contain baked notes offer **Revert baked notes**. When no
|
|
145
|
+
later pixel edit was added, the clean source is restored and the saved notes
|
|
146
|
+
return as an editable draft. If background cleanup or another pixel edit was
|
|
147
|
+
added afterward, Prompt Studio asks for confirmation because reverting also
|
|
148
|
+
discards those pixel edits.
|
|
@@ -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: 213 (runtime: 76, contract: 137)
|
|
8
8
|
|
|
9
9
|
## Runtime-importing tests
|
|
10
10
|
- `tests/agent-mode-auto-planner-contract.test.ts`
|
|
@@ -15,11 +15,14 @@ Total: 207 (runtime: 73, contract: 134)
|
|
|
15
15
|
- `tests/agent-mode-runtime-contract.test.ts`
|
|
16
16
|
- `tests/agent-mode-slash-command-contract.test.ts`
|
|
17
17
|
- `tests/agent-mode-ux-feedback-contract.test.ts`
|
|
18
|
+
- `tests/agent-queue-store-contract.test.ts`
|
|
18
19
|
- `tests/agent-video-intent.test.ts`
|
|
19
20
|
- `tests/agy-artifact-fallback.test.ts`
|
|
20
21
|
- `tests/agy-cli.test.ts`
|
|
21
22
|
- `tests/api-cache-policy.test.ts`
|
|
22
23
|
- `tests/api-provider-parity.test.ts`
|
|
24
|
+
- `tests/backend-hardening.test.ts`
|
|
25
|
+
- `tests/backend-input-lan-hardening.test.ts`
|
|
23
26
|
- `tests/billing-source.test.ts`
|
|
24
27
|
- `tests/card-news-contract.test.ts`
|
|
25
28
|
- `tests/card-news-template.test.ts`
|
|
@@ -92,6 +95,7 @@ Total: 207 (runtime: 73, contract: 134)
|
|
|
92
95
|
- `tests/async-capacity-retry-behavior.test.ts`
|
|
93
96
|
- `tests/async-capacity-retry-contract.test.js`
|
|
94
97
|
- `tests/async-stream-subscribe-order.test.js`
|
|
98
|
+
- `tests/backend-hardening-contract.test.js`
|
|
95
99
|
- `tests/background-cleanup-brush-rasterize.test.js`
|
|
96
100
|
- `tests/background-cleanup-mask-compose.test.js`
|
|
97
101
|
- `tests/bin.test.js`
|
|
@@ -99,6 +103,7 @@ Total: 207 (runtime: 73, contract: 134)
|
|
|
99
103
|
- `tests/browser-attention-badge-contract.test.js`
|
|
100
104
|
- `tests/canvas-alpha-controls-contract.test.js`
|
|
101
105
|
- `tests/canvas-annotation-contract.test.js`
|
|
106
|
+
- `tests/canvas-annotation-revert-contract.test.js`
|
|
102
107
|
- `tests/canvas-apply-merged-contract.test.js`
|
|
103
108
|
- `tests/canvas-background-cleanup-2x2-contract.test.js`
|
|
104
109
|
- `tests/canvas-background-cleanup-contract.test.js`
|
|
@@ -212,6 +217,7 @@ Total: 207 (runtime: 73, contract: 134)
|
|
|
212
217
|
- `tests/toast-stack-contract.test.js`
|
|
213
218
|
- `tests/ui-error-code-contract.test.js`
|
|
214
219
|
- `tests/video-continuity-ui-contract.test.js`
|
|
220
|
+
- `tests/video-defaults-persistence-contract.test.js`
|
|
215
221
|
- `tests/video-gallery-refresh-contract.test.ts`
|
|
216
222
|
- `tests/video-ui-source-count.test.ts`
|
|
217
223
|
- `tests/vite-dev-port-contract.test.js`
|