ima2-gen 1.1.21 → 1.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/README.md +44 -7
  2. package/bin/commands/video.js +14 -0
  3. package/bin/ima2.js +14 -4
  4. package/bin/lib/platform.js +34 -5
  5. package/docs/README.ko.md +43 -2
  6. package/lib/agentQueueWorker.js +6 -0
  7. package/lib/agentRuntime.js +3 -2
  8. package/lib/atomicWrite.js +14 -0
  9. package/lib/grokImageAdapter.js +6 -0
  10. package/lib/grokProxyLauncher.js +5 -3
  11. package/lib/grokVideoAdapter.js +1 -1
  12. package/lib/grokVideoPlannerPrompt.js +10 -0
  13. package/lib/inflight.js +1 -1
  14. package/lib/oauthLauncher.js +5 -0
  15. package/lib/videoFrameExtract.js +3 -3
  16. package/package.json +5 -7
  17. package/routes/capabilities.js +13 -0
  18. package/routes/edit.js +2 -1
  19. package/routes/generate.js +32 -6
  20. package/routes/health.js +4 -3
  21. package/routes/multimode.js +2 -1
  22. package/routes/video.js +35 -3
  23. package/server.js +29 -2
  24. package/skills/ima2/SKILL.md +48 -6
  25. package/ui/dist/.vite/manifest.json +12 -12
  26. package/ui/dist/assets/{AgentWorkspace-B_hq9CLg.js → AgentWorkspace-C21zqdTZ.js} +1 -1
  27. package/ui/dist/assets/{CardNewsWorkspace-wD12J7qk.js → CardNewsWorkspace-BN-ga1lG.js} +1 -1
  28. package/ui/dist/assets/{NodeCanvas-CI_wuPMf.js → NodeCanvas-BbMa4IhI.js} +1 -1
  29. package/ui/dist/assets/{PromptBuilderPanel-CUTujJUV.js → PromptBuilderPanel-DRwBJRDQ.js} +1 -1
  30. package/ui/dist/assets/{PromptImportDialog-CUi66jPK.js → PromptImportDialog-Dp85kHCq.js} +2 -2
  31. package/ui/dist/assets/{PromptImportDiscoverySection-Cm3vrjY4.js → PromptImportDiscoverySection-BE8Q8MLD.js} +1 -1
  32. package/ui/dist/assets/{PromptImportFolderSection-DOtWTD9n.js → PromptImportFolderSection-PtH5x0sc.js} +1 -1
  33. package/ui/dist/assets/{PromptLibraryPanel-BMjQegRa.js → PromptLibraryPanel-FnM9tHI9.js} +2 -2
  34. package/ui/dist/assets/SettingsWorkspace-MARPGyBL.js +1 -0
  35. package/ui/dist/assets/index-BAFI6htx.js +42 -0
  36. package/ui/dist/assets/{index-31uVIdt4.js → index-BSXxr_Bt.js} +1 -1
  37. package/ui/dist/assets/index-DS-ADE7U.css +1 -0
  38. package/ui/dist/index.html +2 -2
  39. package/bin/commands/annotate.ts +0 -119
  40. package/bin/commands/cancel.ts +0 -48
  41. package/bin/commands/canvas-versions.ts +0 -80
  42. package/bin/commands/capabilities.ts +0 -110
  43. package/bin/commands/cardnews.ts +0 -249
  44. package/bin/commands/comfy.ts +0 -54
  45. package/bin/commands/config.ts +0 -186
  46. package/bin/commands/defaults.ts +0 -192
  47. package/bin/commands/doctor.ts +0 -202
  48. package/bin/commands/edit.ts +0 -150
  49. package/bin/commands/gen.ts +0 -214
  50. package/bin/commands/grok.ts +0 -90
  51. package/bin/commands/history.ts +0 -146
  52. package/bin/commands/ls.ts +0 -64
  53. package/bin/commands/metadata.ts +0 -39
  54. package/bin/commands/multimode.ts +0 -196
  55. package/bin/commands/node.ts +0 -166
  56. package/bin/commands/observability.ts +0 -176
  57. package/bin/commands/ping.ts +0 -31
  58. package/bin/commands/prompt-sub/build.ts +0 -101
  59. package/bin/commands/prompt.ts +0 -492
  60. package/bin/commands/ps.ts +0 -81
  61. package/bin/commands/session.ts +0 -266
  62. package/bin/commands/show.ts +0 -72
  63. package/bin/commands/skill.ts +0 -70
  64. package/bin/commands/video.ts +0 -442
  65. package/bin/ima2.ts +0 -430
  66. package/bin/lib/args.ts +0 -92
  67. package/bin/lib/browser-id.ts +0 -16
  68. package/bin/lib/client.ts +0 -122
  69. package/bin/lib/config-store.ts +0 -120
  70. package/bin/lib/destructive-confirm.ts +0 -19
  71. package/bin/lib/doctor-checks.ts +0 -91
  72. package/bin/lib/error-hints.ts +0 -23
  73. package/bin/lib/files.ts +0 -39
  74. package/bin/lib/output.ts +0 -73
  75. package/bin/lib/platform.ts +0 -99
  76. package/bin/lib/recover-output.ts +0 -139
  77. package/bin/lib/sse.ts +0 -73
  78. package/bin/lib/star-prompt.ts +0 -97
  79. package/bin/lib/storage-doctor.ts +0 -39
  80. package/bin/lib/ui-build.ts +0 -85
  81. package/config.ts +0 -354
  82. package/lib/agentCommandParser.ts +0 -69
  83. package/lib/agentGenerationPlanner.ts +0 -273
  84. package/lib/agentQuestionResponder.ts +0 -266
  85. package/lib/agentQueueStore.ts +0 -270
  86. package/lib/agentQueueWorker.ts +0 -89
  87. package/lib/agentRuntime.ts +0 -604
  88. package/lib/agentSettings.ts +0 -72
  89. package/lib/agentStore.ts +0 -422
  90. package/lib/agentStoreRows.ts +0 -136
  91. package/lib/agentTypes.ts +0 -154
  92. package/lib/apiCachePolicy.ts +0 -11
  93. package/lib/assetLifecycle.ts +0 -146
  94. package/lib/canvasVersionStore.ts +0 -223
  95. package/lib/capabilities.ts +0 -126
  96. package/lib/cardNewsGenerator.ts +0 -271
  97. package/lib/cardNewsJobStore.ts +0 -142
  98. package/lib/cardNewsManifestStore.ts +0 -154
  99. package/lib/cardNewsPlanner.ts +0 -236
  100. package/lib/cardNewsPlannerClient.ts +0 -155
  101. package/lib/cardNewsPlannerPrompt.ts +0 -62
  102. package/lib/cardNewsPlannerSchema.ts +0 -321
  103. package/lib/cardNewsRoleTemplateStore.ts +0 -47
  104. package/lib/cardNewsTemplateStore.ts +0 -252
  105. package/lib/codexDetect.ts +0 -71
  106. package/lib/comfyBridge.ts +0 -235
  107. package/lib/composerSnapshot.ts +0 -33
  108. package/lib/configKeys.ts +0 -62
  109. package/lib/db.ts +0 -295
  110. package/lib/errInfo.ts +0 -43
  111. package/lib/errorClassify.ts +0 -100
  112. package/lib/generationCancel.ts +0 -28
  113. package/lib/generationErrors.ts +0 -238
  114. package/lib/grokImageAdapter.ts +0 -513
  115. package/lib/grokMultimodeAdapter.ts +0 -84
  116. package/lib/grokProxyLauncher.ts +0 -153
  117. package/lib/grokRuntime.ts +0 -23
  118. package/lib/grokSizeMapper.ts +0 -71
  119. package/lib/grokVideoAdapter.ts +0 -458
  120. package/lib/grokVideoCanvas.ts +0 -26
  121. package/lib/grokVideoDownload.ts +0 -59
  122. package/lib/grokVideoPlannerPrompt.ts +0 -67
  123. package/lib/historyIndex.ts +0 -51
  124. package/lib/historyList.ts +0 -181
  125. package/lib/imageMetadata.ts +0 -113
  126. package/lib/imageMetadataStore.ts +0 -67
  127. package/lib/imageModels.ts +0 -165
  128. package/lib/inflight.ts +0 -281
  129. package/lib/localImportStore.ts +0 -114
  130. package/lib/logger.ts +0 -161
  131. package/lib/nodeStore.ts +0 -91
  132. package/lib/oauthLauncher.ts +0 -94
  133. package/lib/oauthNormalize.ts +0 -30
  134. package/lib/oauthProxy/errors.ts +0 -128
  135. package/lib/oauthProxy/generators.ts +0 -494
  136. package/lib/oauthProxy/index.ts +0 -28
  137. package/lib/oauthProxy/prompts.ts +0 -123
  138. package/lib/oauthProxy/references.ts +0 -45
  139. package/lib/oauthProxy/runtime.ts +0 -115
  140. package/lib/oauthProxy/streams.ts +0 -232
  141. package/lib/oauthProxy/types.ts +0 -9
  142. package/lib/oauthProxy.ts +0 -3
  143. package/lib/openDirectory.ts +0 -47
  144. package/lib/pngInfo.ts +0 -26
  145. package/lib/promptBuilder/attachments.ts +0 -74
  146. package/lib/promptBuilder/client.ts +0 -130
  147. package/lib/promptBuilder/constants.ts +0 -9
  148. package/lib/promptBuilder/context.ts +0 -36
  149. package/lib/promptBuilder/errors.ts +0 -12
  150. package/lib/promptBuilder/requestSchema.ts +0 -56
  151. package/lib/promptBuilder/responseParser.ts +0 -219
  152. package/lib/promptBuilder/systemPrompt.ts +0 -135
  153. package/lib/promptBuilder/transport.ts +0 -94
  154. package/lib/promptBuilder/types.ts +0 -109
  155. package/lib/promptImport/curatedSources.ts +0 -141
  156. package/lib/promptImport/discoveryRegistry.ts +0 -329
  157. package/lib/promptImport/errors.ts +0 -18
  158. package/lib/promptImport/githubDiscovery.ts +0 -309
  159. package/lib/promptImport/githubFolder.ts +0 -397
  160. package/lib/promptImport/githubSource.ts +0 -257
  161. package/lib/promptImport/gptImageHints.ts +0 -70
  162. package/lib/promptImport/parsePromptCandidates.ts +0 -179
  163. package/lib/promptImport/promptIndex.ts +0 -326
  164. package/lib/promptImport/rankPromptCandidates.ts +0 -65
  165. package/lib/promptImport/types.ts +0 -103
  166. package/lib/promptSafetyPolicy.ts +0 -5
  167. package/lib/providerOptions.ts +0 -56
  168. package/lib/referenceImageCompress.ts +0 -84
  169. package/lib/refs.ts +0 -133
  170. package/lib/requestLogger.ts +0 -49
  171. package/lib/responsesDoctor.ts +0 -456
  172. package/lib/responsesErrors.ts +0 -83
  173. package/lib/responsesFallback.ts +0 -114
  174. package/lib/responsesImageAdapter.ts +0 -466
  175. package/lib/responsesParse.ts +0 -452
  176. package/lib/responsesTools.ts +0 -28
  177. package/lib/runtimeContext.ts +0 -146
  178. package/lib/runtimePorts.ts +0 -105
  179. package/lib/sessionStore.ts +0 -308
  180. package/lib/storageMigration.ts +0 -310
  181. package/lib/styleSheet.ts +0 -139
  182. package/lib/systemTrash.ts +0 -20
  183. package/lib/videoContinuity.ts +0 -180
  184. package/lib/videoFrameExtract.ts +0 -78
  185. package/lib/videoSeriesChain.ts +0 -29
  186. package/lib/visibleTextLanguagePolicy.ts +0 -7
  187. package/routes/agent.ts +0 -308
  188. package/routes/annotations.ts +0 -118
  189. package/routes/canvasVersions.ts +0 -69
  190. package/routes/capabilities.ts +0 -18
  191. package/routes/cardNews.ts +0 -211
  192. package/routes/comfy.ts +0 -43
  193. package/routes/edit.ts +0 -352
  194. package/routes/generate.ts +0 -492
  195. package/routes/grok.ts +0 -24
  196. package/routes/health.ts +0 -123
  197. package/routes/history.ts +0 -221
  198. package/routes/imageImport.ts +0 -37
  199. package/routes/index.ts +0 -52
  200. package/routes/metadata.ts +0 -77
  201. package/routes/multimode.ts +0 -499
  202. package/routes/nodes.ts +0 -578
  203. package/routes/promptBuilder.ts +0 -37
  204. package/routes/promptImport.ts +0 -379
  205. package/routes/prompts.ts +0 -428
  206. package/routes/quota.ts +0 -89
  207. package/routes/sessions.ts +0 -317
  208. package/routes/storage.ts +0 -47
  209. package/routes/video.ts +0 -300
  210. package/routes/videoExtended.ts +0 -284
  211. package/server.ts +0 -293
  212. package/ui/dist/assets/SettingsWorkspace-PiaVnsdA.js +0 -1
  213. package/ui/dist/assets/index-CjgnNtgt.css +0 -1
  214. package/ui/dist/assets/index-Da2s4_-5.js +0 -36
@@ -1,310 +0,0 @@
1
- import { mkdir, readdir, copyFile, stat, constants } from "node:fs/promises";
2
- import { dirname, isAbsolute, join, resolve, sep } from "node:path";
3
- import { homedir } from "node:os";
4
-
5
- const PACKAGE_NAME = "ima2-gen";
6
- const RECOVERY_DOCS_PATH = "docs/RECOVER_OLD_IMAGES.md";
7
-
8
- interface CopyStats { copied: number; skippedExisting: number; }
9
-
10
- function addStats(a: CopyStats, b: CopyStats): CopyStats {
11
- return {
12
- copied: a.copied + b.copied,
13
- skippedExisting: a.skippedExisting + b.skippedExisting,
14
- };
15
- }
16
-
17
- async function copyMissingTree(srcDir: string, dstDir: string): Promise<CopyStats> {
18
- await mkdir(dstDir, { recursive: true });
19
- const entries = await readdir(srcDir, { withFileTypes: true });
20
- let stats: CopyStats = { copied: 0, skippedExisting: 0 };
21
- for (const entry of entries) {
22
- const src = join(srcDir, entry.name);
23
- const dst = join(dstDir, entry.name);
24
- if (entry.isDirectory()) {
25
- stats = addStats(stats, await copyMissingTree(src, dst));
26
- continue;
27
- }
28
- if (!entry.isFile()) continue;
29
- try {
30
- await copyFile(src, dst, constants.COPYFILE_EXCL);
31
- stats.copied += 1;
32
- } catch (err) {
33
- if ((err as { code?: string })?.code !== "EEXIST") throw err;
34
- stats.skippedExisting += 1;
35
- }
36
- }
37
- return stats;
38
- }
39
-
40
- function isSameOrInside(child: string, parent: string): boolean {
41
- const a = resolve(child);
42
- const b = resolve(parent);
43
- return a === b || a.startsWith(b + sep);
44
- }
45
-
46
- interface StorageCtx {
47
- rootDir?: string;
48
- config?: { storage?: { generatedDir?: string } };
49
- }
50
-
51
- function resolveTargetDir(ctx: StorageCtx): string {
52
- return ctx.config?.storage?.generatedDir ?? join(ctx.rootDir ?? process.cwd(), "generated");
53
- }
54
-
55
- type EnvLike = NodeJS.ProcessEnv;
56
-
57
- interface MigrateOptions {
58
- legacyDirs?: string[];
59
- env?: EnvLike;
60
- }
61
-
62
- export async function migrateGeneratedStorage(ctx: StorageCtx, options: MigrateOptions = {}) {
63
- const targetDir = resolveTargetDir(ctx);
64
- const candidates = options.legacyDirs || await getLegacyGeneratedCandidates(ctx, options.env);
65
- const result = {
66
- copied: 0,
67
- skippedExisting: 0,
68
- sourcesScanned: 0,
69
- sourcesSkipped: 0,
70
- };
71
- try {
72
- for (const legacyDir of candidates) {
73
- if (isSameOrInside(legacyDir, targetDir) || isSameOrInside(targetDir, legacyDir)) {
74
- result.sourcesSkipped += 1;
75
- continue;
76
- }
77
- try {
78
- const legacyStat = await stat(legacyDir);
79
- if (!legacyStat.isDirectory()) continue;
80
- result.sourcesScanned += 1;
81
- const copyStats = await copyMissingTree(legacyDir, targetDir);
82
- result.copied += copyStats.copied;
83
- result.skippedExisting += copyStats.skippedExisting;
84
- } catch (err) {
85
- const e = err as { code?: string; message?: string };
86
- if (e?.code !== "ENOENT") {
87
- console.warn("[storage] generated asset migration source skipped:", legacyDir, e.message);
88
- }
89
- }
90
- }
91
- if (result.copied > 0) console.log(`[storage] migrated ${result.copied} generated assets to ${targetDir}`);
92
- } catch (err) {
93
- console.warn("[storage] generated asset migration skipped:", (err as { message?: string })?.message);
94
- }
95
- return result;
96
- }
97
-
98
- export async function getLegacyGeneratedCandidates(ctx: StorageCtx, env: EnvLike = process.env): Promise<string[]> {
99
- const home = env.IMA2_TEST_HOME || homedir();
100
- const execPath = env.IMA2_TEST_EXEC_PATH || process.execPath;
101
- const argv1 = env.IMA2_TEST_ARGV1 || process.argv[1] || "";
102
- const nodePrefix = dirname(dirname(execPath));
103
- const prefixes = getGlobalPrefixCandidates({ env, execPath, argv1 });
104
- const appData = env.APPDATA || join(home, "AppData", "Roaming");
105
- const localAppData = env.LOCALAPPDATA || join(home, "AppData", "Local");
106
- const npmCache = env.npm_config_cache || join(home, ".npm");
107
- const xdgDataHome = env.XDG_DATA_HOME || join(home, ".local", "share");
108
- const pnpmHome = env.PNPM_HOME || "";
109
- const nvmHome = env.NVM_HOME || join(appData, "nvm");
110
-
111
- const candidates = [
112
- join(ctx.rootDir ?? process.cwd(), "generated"),
113
- join(appData, "npm", "node_modules", PACKAGE_NAME, "generated"),
114
- join(home, ".npm-global", "lib", "node_modules", PACKAGE_NAME, "generated"),
115
- join(home, ".nvm", "versions", "node", process.version, "lib", "node_modules", PACKAGE_NAME, "generated"),
116
- join(home, ".volta", "tools", "image", "packages", PACKAGE_NAME, "lib", "node_modules", PACKAGE_NAME, "generated"),
117
- join(home, ".fnm", "node-versions", process.version, "installation", "lib", "node_modules", PACKAGE_NAME, "generated"),
118
- join(home, ".bun", "install", "global", "node_modules", PACKAGE_NAME, "generated"),
119
- join(home, ".config", "yarn", "global", "node_modules", PACKAGE_NAME, "generated"),
120
- join(localAppData, "Yarn", "Data", "global", "node_modules", PACKAGE_NAME, "generated"),
121
- join(localAppData, "Volta", "tools", "image", "packages", PACKAGE_NAME, "lib", "node_modules", PACKAGE_NAME, "generated"),
122
- join(nvmHome, process.version, "node_modules", PACKAGE_NAME, "generated"),
123
- join(dirname(execPath), "node_modules", PACKAGE_NAME, "generated"),
124
- ];
125
-
126
- for (const prefix of prefixes) {
127
- candidates.push(join(prefix, "lib", "node_modules", PACKAGE_NAME, "generated"));
128
- candidates.push(join(prefix, "node_modules", PACKAGE_NAME, "generated"));
129
- }
130
-
131
- candidates.push(join(nodePrefix, "lib", "node_modules", PACKAGE_NAME, "generated"));
132
- candidates.push(
133
- ...await expandOneLevelCandidates([
134
- [join(home, ".nvm", "versions", "node"), ["*", "lib", "node_modules", PACKAGE_NAME, "generated"]],
135
- [join(home, ".fnm", "node-versions"), ["*", "installation", "lib", "node_modules", PACKAGE_NAME, "generated"]],
136
- [join(home, ".asdf", "installs", "nodejs"), ["*", "lib", "node_modules", PACKAGE_NAME, "generated"]],
137
- [join(home, ".local", "share", "mise", "installs", "node"), ["*", "lib", "node_modules", PACKAGE_NAME, "generated"]],
138
- [join(home, "Library", "pnpm", "global"), ["*", "node_modules", PACKAGE_NAME, "generated"]],
139
- [join(xdgDataHome, "pnpm", "global"), ["*", "node_modules", PACKAGE_NAME, "generated"]],
140
- [join(localAppData, "pnpm", "global"), ["*", "node_modules", PACKAGE_NAME, "generated"]],
141
- [pnpmHome ? join(pnpmHome, "global") : "", ["*", "node_modules", PACKAGE_NAME, "generated"]],
142
- [join(npmCache, "_npx"), ["*", "node_modules", PACKAGE_NAME, "generated"]],
143
- [join(localAppData, "npm-cache", "_npx"), ["*", "node_modules", PACKAGE_NAME, "generated"]],
144
- [join(appData, "npm-cache", "_npx"), ["*", "node_modules", PACKAGE_NAME, "generated"]],
145
- [nvmHome, ["*", "node_modules", PACKAGE_NAME, "generated"]],
146
- ]),
147
- );
148
- return uniqueResolvedCandidates(candidates);
149
- }
150
-
151
- export async function inspectGeneratedStorage(ctx: StorageCtx, options: MigrateOptions = {}) {
152
- const env = options.env || process.env;
153
- const targetDir = resolveTargetDir(ctx);
154
- try {
155
- const candidates = options.legacyDirs || await getLegacyGeneratedCandidates(ctx, env);
156
- const targetFileCount = await countFiles(targetDir);
157
- const legacySources: Array<{ path: string; fileCount: number }> = [];
158
-
159
- for (const candidate of candidates) {
160
- if (isSameOrInside(candidate, targetDir) || isSameOrInside(targetDir, candidate)) continue;
161
- try {
162
- const candidateStat = await stat(candidate);
163
- if (!candidateStat.isDirectory()) continue;
164
- const fileCount = await countFiles(candidate);
165
- if (fileCount > 0) legacySources.push({ path: candidate, fileCount });
166
- } catch (err) {
167
- const e = err as { code?: string; message?: string };
168
- if (e?.code !== "ENOENT") {
169
- console.warn("[storage] legacy candidate inspect skipped:", candidate, e.message);
170
- }
171
- }
172
- }
173
-
174
- const legacyFilesFound = legacySources.reduce((sum, source) => sum + source.fileCount, 0);
175
- const state =
176
- targetFileCount > 0 ? "ok"
177
- : legacyFilesFound > 0 ? "recoverable"
178
- : "not_found";
179
-
180
- return {
181
- ok: true,
182
- targetDir,
183
- generatedDirLabel: labelPath(targetDir, env),
184
- targetExists: await isDirectory(targetDir),
185
- targetFileCount,
186
- legacyCandidatesScanned: candidates.length,
187
- legacySourcesFound: legacySources.length,
188
- legacyFilesFound,
189
- legacySources,
190
- overrides: {
191
- generatedDir: Boolean(env.IMA2_GENERATED_DIR),
192
- configDir: Boolean(env.IMA2_CONFIG_DIR),
193
- },
194
- state,
195
- messageKind: state === "not_found" ? "apology" : state,
196
- recoveryDocsPath: RECOVERY_DOCS_PATH,
197
- doctorCommand: "ima2 doctor",
198
- };
199
- } catch (err) {
200
- const e = err as { message?: string };
201
- return {
202
- ok: false,
203
- targetDir,
204
- generatedDirLabel: labelPath(targetDir, env),
205
- targetExists: false,
206
- targetFileCount: 0,
207
- legacyCandidatesScanned: 0,
208
- legacySourcesFound: 0,
209
- legacyFilesFound: 0,
210
- legacySources: [],
211
- overrides: {
212
- generatedDir: Boolean(env.IMA2_GENERATED_DIR),
213
- configDir: Boolean(env.IMA2_CONFIG_DIR),
214
- },
215
- state: "unknown",
216
- messageKind: "unknown",
217
- recoveryDocsPath: RECOVERY_DOCS_PATH,
218
- doctorCommand: "ima2 doctor",
219
- error: e?.message || String(err),
220
- };
221
- }
222
- }
223
-
224
- type ExpandPattern = [string, string[]];
225
-
226
- async function expandOneLevelCandidates(patterns: ExpandPattern[]): Promise<string[]> {
227
- const candidates: string[] = [];
228
- for (const [baseDir, segments] of patterns) {
229
- if (!baseDir) continue;
230
- candidates.push(...await expandOneLevelPattern(baseDir, segments));
231
- }
232
- return candidates;
233
- }
234
-
235
- async function expandOneLevelPattern(baseDir: string, segments: string[]): Promise<string[]> {
236
- const wildcardIndex = segments.indexOf("*");
237
- if (wildcardIndex < 0) return [join(baseDir, ...segments)];
238
-
239
- const before = segments.slice(0, wildcardIndex);
240
- const after = segments.slice(wildcardIndex + 1);
241
- const wildcardBase = join(baseDir, ...before);
242
- try {
243
- const entries = await readdir(wildcardBase, { withFileTypes: true });
244
- return entries
245
- .filter((entry) => entry.isDirectory())
246
- .map((entry) => join(wildcardBase, entry.name, ...after));
247
- } catch (err) {
248
- const e = err as { code?: string; message?: string };
249
- if (e?.code === "ENOENT") return [];
250
- console.warn("[storage] legacy candidate scan skipped:", wildcardBase, e.message);
251
- return [];
252
- }
253
- }
254
-
255
- async function countFiles(dir: string): Promise<number> {
256
- try {
257
- const entries = await readdir(dir, { withFileTypes: true });
258
- let count = 0;
259
- for (const entry of entries) {
260
- if (entry.name === ".trash") continue;
261
- const fullPath = join(dir, entry.name);
262
- if (entry.isDirectory()) count += await countFiles(fullPath);
263
- else if (entry.isFile()) count += 1;
264
- }
265
- return count;
266
- } catch (err) {
267
- if ((err as { code?: string })?.code === "ENOENT") return 0;
268
- throw err;
269
- }
270
- }
271
-
272
- async function isDirectory(dir: string): Promise<boolean> {
273
- try {
274
- return (await stat(dir)).isDirectory();
275
- } catch {
276
- return false;
277
- }
278
- }
279
-
280
- function uniqueResolvedCandidates(candidates: string[]): string[] {
281
- return Array.from(new Set(candidates.filter(Boolean).map((p) => resolve(p))));
282
- }
283
-
284
- function labelPath(targetPath: string, env: EnvLike = process.env): string {
285
- const home = env.IMA2_TEST_HOME || homedir();
286
- const resolved = resolve(targetPath);
287
- const resolvedHome = resolve(home);
288
- if (resolved === resolvedHome) return "~";
289
- if (resolved.startsWith(resolvedHome + sep)) return `~${sep}${resolved.slice(resolvedHome.length + 1)}`;
290
- return resolved;
291
- }
292
-
293
- interface PrefixOpts { env: EnvLike; execPath: string; argv1: string; }
294
-
295
- function getGlobalPrefixCandidates({ env, execPath, argv1 }: PrefixOpts): string[] {
296
- const prefixes: Set<string> = new Set();
297
- if (env.npm_config_prefix) prefixes.add(env.npm_config_prefix);
298
- if (isAbsolute(argv1)) prefixes.add(dirname(dirname(argv1)));
299
- prefixes.add(dirname(dirname(execPath)));
300
- addHomebrewPrefix(prefixes, execPath);
301
- prefixes.add("/opt/homebrew");
302
- prefixes.add("/usr/local");
303
- return Array.from(prefixes);
304
- }
305
-
306
- function addHomebrewPrefix(prefixes: Set<string>, execPath: string): void {
307
- const marker = `${sep}Cellar${sep}node`;
308
- const idx = execPath.indexOf(marker);
309
- if (idx > 0) prefixes.add(execPath.slice(0, idx));
310
- }
package/lib/styleSheet.ts DELETED
@@ -1,139 +0,0 @@
1
- // Style-sheet extractor (0.10)
2
- //
3
- // Uses GPT-5.4 (chat completions) to derive a structured "style guide" from a
4
- // user prompt and optional reference image. The style guide is stored per
5
- // session and automatically prepended to subsequent image generations so
6
- // continuations feel cohesive — closer to ChatGPT 4o's image carry-over.
7
- //
8
- // Shape:
9
- // {
10
- // palette: string[], // e.g. ["deep navy", "gold leaf"]
11
- // composition: string, // e.g. "centered 3/4 portrait, shallow depth"
12
- // mood: string, // e.g. "melancholic, reverent"
13
- // medium: string, // e.g. "oil painting, glazed layers"
14
- // subject_details: string, // identity/pose/outfit cues for character continuity
15
- // negative: string[] // things to avoid
16
- // }
17
- //
18
- // The module is pure JS + openai SDK. When no API key is configured it throws
19
- // STYLE_SHEET_NO_KEY so callers can surface a friendly "connect key" UI.
20
-
21
- import type OpenAI from "openai";
22
- import { config } from "../config.js";
23
- const STYLE_SHEET_MODEL = config.styleSheet.model;
24
-
25
- const SYSTEM_PROMPT = `You extract a reusable visual style guide from a user
26
- image prompt (and an optional reference image). Return ONLY a JSON object with
27
- these keys: palette (array of 3-6 concrete color names), composition (one
28
- sentence), mood (2-4 comma-separated adjectives), medium (one short phrase
29
- naming technique/material), subject_details (one sentence capturing identity
30
- cues: face, outfit, pose, distinctive features), negative (array of 0-4 short
31
- phrases of things to avoid). Keep entries tight — each under 120 characters.
32
- Do not wrap in markdown. Do not add commentary.`;
33
-
34
- interface StyleSheet {
35
- palette: string[];
36
- composition: string;
37
- mood: string;
38
- medium: string;
39
- subject_details: string;
40
- negative: string[];
41
- }
42
-
43
- function coerceStyleSheet(raw: unknown): StyleSheet | null {
44
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
45
- const arr = (v: unknown, max = 6) =>
46
- Array.isArray(v)
47
- ? v
48
- .filter((x: unknown) => typeof x === "string" && x.trim())
49
- .slice(0, max)
50
- .map((s: string) => s.trim())
51
- : [];
52
- const str = (v: unknown) => (typeof v === "string" ? v.trim().slice(0, 400) : "");
53
- const r = raw as Record<string, unknown>;
54
- const sheet: StyleSheet = {
55
- palette: arr(r.palette, 6),
56
- composition: str(r.composition),
57
- mood: str(r.mood),
58
- medium: str(r.medium),
59
- subject_details: str(r.subject_details),
60
- negative: arr(r.negative, 4),
61
- };
62
- const hasContent =
63
- sheet.palette.length > 0 ||
64
- sheet.negative.length > 0 ||
65
- sheet.composition ||
66
- sheet.mood ||
67
- sheet.medium ||
68
- sheet.subject_details;
69
- return hasContent ? sheet : null;
70
- }
71
-
72
- export async function extractStyleSheet(openai: OpenAI | null | undefined, { prompt, referenceDataUrl }: { prompt: string; referenceDataUrl?: string }) {
73
- if (!openai) {
74
- const err = new Error("No OpenAI client configured for style-sheet extraction") as Error & { code?: string };
75
- err.code = "STYLE_SHEET_NO_KEY";
76
- throw err;
77
- }
78
- if (!prompt || typeof prompt !== "string" || !prompt.trim()) {
79
- const err = new Error("prompt is required") as Error & { code?: string };
80
- err.code = "STYLE_SHEET_BAD_INPUT";
81
- throw err;
82
- }
83
-
84
- const userContent = referenceDataUrl
85
- ? [
86
- { type: "text" as const, text: prompt },
87
- { type: "image_url" as const, image_url: { url: referenceDataUrl } },
88
- ]
89
- : prompt;
90
-
91
- const resp = await openai.chat.completions.create({
92
- model: STYLE_SHEET_MODEL,
93
- response_format: { type: "json_object" },
94
- messages: [
95
- { role: "system", content: SYSTEM_PROMPT },
96
- { role: "user", content: userContent },
97
- ],
98
- });
99
-
100
- const raw = resp.choices?.[0]?.message?.content;
101
- if (!raw) {
102
- const err = new Error("Empty response from style-sheet model") as Error & { code?: string };
103
- err.code = "STYLE_SHEET_EMPTY";
104
- throw err;
105
- }
106
-
107
- let parsed: unknown;
108
- try {
109
- parsed = JSON.parse(raw);
110
- } catch {
111
- const err = new Error("Style-sheet model returned non-JSON") as Error & { code?: string };
112
- err.code = "STYLE_SHEET_PARSE";
113
- throw err;
114
- }
115
-
116
- const sheet = coerceStyleSheet(parsed);
117
- if (!sheet) {
118
- const err = new Error("Style-sheet shape invalid") as Error & { code?: string };
119
- err.code = "STYLE_SHEET_SHAPE";
120
- throw err;
121
- }
122
- return sheet;
123
- }
124
-
125
- // Render a style sheet into a prompt preamble that gpt-image-1/2 can consume.
126
- // Kept short so it doesn't blow the 4K prompt window on long user prompts.
127
- export function renderStyleSheetPrefix(sheet: StyleSheet | null | undefined) {
128
- if (!sheet) return "";
129
- const parts: string[] = [];
130
- if (sheet.medium) parts.push(`Medium: ${sheet.medium}.`);
131
- if (sheet.palette?.length) parts.push(`Palette: ${sheet.palette.join(", ")}.`);
132
- if (sheet.composition) parts.push(`Composition: ${sheet.composition}.`);
133
- if (sheet.mood) parts.push(`Mood: ${sheet.mood}.`);
134
- if (sheet.subject_details) parts.push(`Subject: ${sheet.subject_details}.`);
135
- if (sheet.negative?.length) parts.push(`Avoid: ${sheet.negative.join(", ")}.`);
136
- return parts.join(" ");
137
- }
138
-
139
- export { STYLE_SHEET_MODEL, SYSTEM_PROMPT, coerceStyleSheet };
@@ -1,20 +0,0 @@
1
- import { mkdir, rename } from "fs/promises";
2
- import { basename, join } from "path";
3
- import trash from "trash";
4
-
5
- export async function moveToSystemTrash(paths: string[]): Promise<void> {
6
- if (process.env.NODE_ENV === "test" && process.env.IMA2_TEST_SYSTEM_TRASH_DIR) {
7
- if (process.env.IMA2_TEST_SYSTEM_TRASH_FAIL === "1") {
8
- throw new Error("Simulated system trash failure");
9
- }
10
- const targetDir = process.env.IMA2_TEST_SYSTEM_TRASH_DIR;
11
- await mkdir(targetDir, { recursive: true });
12
- const prefix = `${Date.now()}_`;
13
- for (const path of paths) {
14
- await rename(path, join(targetDir, `${prefix}${basename(path)}`));
15
- }
16
- return;
17
- }
18
-
19
- await trash(paths, { glob: false });
20
- }
@@ -1,180 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
- import { basename, join } from "node:path";
3
-
4
- export const ACTIVE_VIDEO_PROMPT_GUIDANCE = [
5
- "Active video prompt required.",
6
- "Describe visual flow, motion flow, sound or no-music intent, dialogue or no-dialogue intent, and the desired ending frame.",
7
- "Pace the scene to naturally fill the selected duration, expanding even short requests into an opening composition, connected motion/emotion change, and stable ending frame.",
8
- "Example: From the attached last frame, the subject turns toward camera, rain sound rises, no background music, one whispered line finishes before a still close-up ending.",
9
- ].join(" ");
10
-
11
- export type VideoContinuityEntry = {
12
- id: string;
13
- ordinal: number;
14
- role: "start" | "ancestor" | "parent" | "current";
15
- filename: string | null;
16
- userPrompt: string | null;
17
- revisedPrompt: string;
18
- createdAt: number;
19
- };
20
-
21
- export type VideoContinuityLineage = {
22
- lineageId: string;
23
- parentFilename: string | null;
24
- sourceFrame: "last" | null;
25
- maxEntries: 4;
26
- retention: "keep-start-plus-latest-3";
27
- entries: VideoContinuityEntry[];
28
- };
29
-
30
- type VideoMeta = {
31
- prompt?: unknown;
32
- userPrompt?: unknown;
33
- revisedPrompt?: unknown;
34
- createdAt?: unknown;
35
- videoContinuity?: unknown;
36
- };
37
-
38
- export function requireActiveVideoPrompt(value: unknown): string | null {
39
- return typeof value === "string" && value.trim() ? value.trim() : null;
40
- }
41
-
42
- export function safeGeneratedVideoFilename(value: unknown): string {
43
- if (typeof value !== "string" || !value.trim()) throw Object.assign(new Error("video filename required"), { status: 400 });
44
- const clean = value.replace(/^\/generated\//, "").replace(/^\/+/, "");
45
- if (clean.includes("..") || clean.includes("/") || clean.includes("\\")) {
46
- throw Object.assign(new Error("invalid video filename"), { status: 400 });
47
- }
48
- if (!/\.mp4$/i.test(clean)) throw Object.assign(new Error("generated video input must be an .mp4 file"), { status: 400 });
49
- return clean;
50
- }
51
-
52
- export async function readVideoSidecar(generatedDir: string, filename: string): Promise<VideoMeta | null> {
53
- const safe = safeGeneratedVideoFilename(filename);
54
- try {
55
- return JSON.parse(await readFile(join(generatedDir, `${safe}.json`), "utf-8")) as VideoMeta;
56
- } catch {
57
- return null;
58
- }
59
- }
60
-
61
- function stringOrNull(value: unknown): string | null {
62
- return typeof value === "string" && value.trim() ? value.trim() : null;
63
- }
64
-
65
- function numberOrNow(value: unknown): number {
66
- return typeof value === "number" && Number.isFinite(value) ? value : Date.now();
67
- }
68
-
69
- function entryFromMeta(filename: string, meta: VideoMeta | null): VideoContinuityEntry | null {
70
- const revisedPrompt = stringOrNull(meta?.revisedPrompt) ?? stringOrNull(meta?.prompt);
71
- if (!revisedPrompt) return null;
72
- return {
73
- id: `clip:${filename}`,
74
- ordinal: 1,
75
- role: "start",
76
- filename,
77
- userPrompt: stringOrNull(meta?.userPrompt) ?? stringOrNull(meta?.prompt),
78
- revisedPrompt,
79
- createdAt: numberOrNow(meta?.createdAt),
80
- };
81
- }
82
-
83
- export function normalizeVideoContinuityLineage(value: unknown): VideoContinuityLineage | null {
84
- if (!value || typeof value !== "object") return null;
85
- const raw = value as Partial<VideoContinuityLineage>;
86
- if (!Array.isArray(raw.entries)) return null;
87
- const entries = raw.entries
88
- .map((entry, index): VideoContinuityEntry | null => {
89
- if (!entry || typeof entry !== "object") return null;
90
- const e = entry as Partial<VideoContinuityEntry>;
91
- const revisedPrompt = stringOrNull(e.revisedPrompt);
92
- if (!revisedPrompt) return null;
93
- return {
94
- id: stringOrNull(e.id) ?? `entry:${index + 1}`,
95
- ordinal: index + 1,
96
- role: index === 0 ? "start" : index === raw.entries!.length - 1 ? "parent" : "ancestor",
97
- filename: stringOrNull(e.filename),
98
- userPrompt: stringOrNull(e.userPrompt),
99
- revisedPrompt,
100
- createdAt: numberOrNow(e.createdAt),
101
- };
102
- })
103
- .filter((entry): entry is VideoContinuityEntry => Boolean(entry));
104
- if (entries.length === 0) return null;
105
- return {
106
- lineageId: stringOrNull(raw.lineageId) ?? `lineage:${entries[0].id}`,
107
- parentFilename: stringOrNull(raw.parentFilename),
108
- sourceFrame: "last",
109
- maxEntries: 4,
110
- retention: "keep-start-plus-latest-3",
111
- entries: trimLineageEntries(entries),
112
- };
113
- }
114
-
115
- export function trimLineageEntries(entries: VideoContinuityEntry[]): VideoContinuityEntry[] {
116
- const kept = entries.length <= 4 ? entries : [entries[0], ...entries.slice(-3)];
117
- return kept.map((entry, index) => ({
118
- ...entry,
119
- ordinal: index + 1,
120
- role: index === 0 ? "start" : index === kept.length - 1 ? entry.role : "ancestor",
121
- }));
122
- }
123
-
124
- export function lineageFromVideoMetadata(filename: string, meta: VideoMeta | null): VideoContinuityLineage | null {
125
- const existing = normalizeVideoContinuityLineage(meta?.videoContinuity);
126
- if (existing) {
127
- return { ...existing, parentFilename: filename, sourceFrame: "last" };
128
- }
129
- const entry = entryFromMeta(filename, meta);
130
- if (!entry) return null;
131
- return {
132
- lineageId: `lineage:${filename.replace(/\.[^.]+$/, "")}`,
133
- parentFilename: filename,
134
- sourceFrame: "last",
135
- maxEntries: 4,
136
- retention: "keep-start-plus-latest-3",
137
- entries: [entry],
138
- };
139
- }
140
-
141
- export function appendVideoContinuityEntry(
142
- parent: VideoContinuityLineage | null,
143
- current: { filename: string; userPrompt: string | null; revisedPrompt: string; createdAt?: number },
144
- ): VideoContinuityLineage {
145
- const parentEntries = parent?.entries ?? [];
146
- const lineageId = parent?.lineageId ?? `lineage:${current.filename.replace(/\.[^.]+$/, "")}`;
147
- const currentEntry: VideoContinuityEntry = {
148
- id: `clip:${current.filename}`,
149
- ordinal: parentEntries.length + 1,
150
- role: "current",
151
- filename: current.filename,
152
- userPrompt: current.userPrompt,
153
- revisedPrompt: current.revisedPrompt,
154
- createdAt: current.createdAt ?? Date.now(),
155
- };
156
- const entries = trimLineageEntries([...parentEntries.map((entry) => ({ ...entry, role: entry.role === "current" ? "parent" as const : entry.role })), currentEntry]);
157
- return {
158
- lineageId,
159
- parentFilename: parent?.parentFilename ?? null,
160
- sourceFrame: parent ? "last" : null,
161
- maxEntries: 4,
162
- retention: "keep-start-plus-latest-3",
163
- entries,
164
- };
165
- }
166
-
167
- export function formatVideoContinuityForPlanner(lineage: VideoContinuityLineage | null | undefined): string {
168
- if (!lineage?.entries?.length) return "";
169
- const lines = [
170
- "[Continuity lineage: branch-local, max 4 entries, start anchor preserved]",
171
- ...lineage.entries.map((entry) => [
172
- `${entry.ordinal}. Clip ${entry.ordinal} / ${entry.role}`,
173
- ` file: ${entry.filename ? basename(entry.filename) : "unknown"}`,
174
- ` revisedPrompt: ${entry.revisedPrompt}`,
175
- entry.userPrompt ? ` userPrompt: ${entry.userPrompt}` : null,
176
- ].filter(Boolean).join("\n")),
177
- "Continue from the final frame and final action/audio state of the latest lineage item. Do not restart the scene.",
178
- ];
179
- return lines.join("\n");
180
- }