ima2-gen 1.1.21 → 1.1.22

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 (208) hide show
  1. package/README.md +30 -4
  2. package/bin/ima2.js +14 -4
  3. package/bin/lib/platform.js +34 -5
  4. package/docs/README.ko.md +31 -0
  5. package/lib/agentQueueWorker.js +6 -0
  6. package/lib/agentRuntime.js +3 -2
  7. package/lib/atomicWrite.js +14 -0
  8. package/lib/grokProxyLauncher.js +5 -3
  9. package/lib/inflight.js +1 -1
  10. package/lib/oauthLauncher.js +5 -0
  11. package/lib/videoFrameExtract.js +3 -3
  12. package/package.json +5 -7
  13. package/routes/edit.js +2 -1
  14. package/routes/generate.js +4 -3
  15. package/routes/health.js +4 -3
  16. package/routes/multimode.js +2 -1
  17. package/routes/video.js +4 -2
  18. package/server.js +29 -2
  19. package/ui/dist/.vite/manifest.json +12 -12
  20. package/ui/dist/assets/{AgentWorkspace-B_hq9CLg.js → AgentWorkspace-COxQ5TjU.js} +1 -1
  21. package/ui/dist/assets/{CardNewsWorkspace-wD12J7qk.js → CardNewsWorkspace-B0OkcuVz.js} +1 -1
  22. package/ui/dist/assets/{NodeCanvas-CI_wuPMf.js → NodeCanvas-BSsclEBh.js} +1 -1
  23. package/ui/dist/assets/{PromptBuilderPanel-CUTujJUV.js → PromptBuilderPanel-DpC9A5Rz.js} +1 -1
  24. package/ui/dist/assets/{PromptImportDialog-CUi66jPK.js → PromptImportDialog-CVwT0rLd.js} +2 -2
  25. package/ui/dist/assets/{PromptImportDiscoverySection-Cm3vrjY4.js → PromptImportDiscoverySection-BDCkRCRs.js} +1 -1
  26. package/ui/dist/assets/{PromptImportFolderSection-DOtWTD9n.js → PromptImportFolderSection-QoKbZD83.js} +1 -1
  27. package/ui/dist/assets/{PromptLibraryPanel-BMjQegRa.js → PromptLibraryPanel-BhFgeKnY.js} +2 -2
  28. package/ui/dist/assets/SettingsWorkspace-CfjrlH5R.js +1 -0
  29. package/ui/dist/assets/index-C-mur7pa.css +1 -0
  30. package/ui/dist/assets/index-CCP5nUOj.js +42 -0
  31. package/ui/dist/assets/{index-31uVIdt4.js → index-Cxhzi3bs.js} +1 -1
  32. package/ui/dist/index.html +2 -2
  33. package/bin/commands/annotate.ts +0 -119
  34. package/bin/commands/cancel.ts +0 -48
  35. package/bin/commands/canvas-versions.ts +0 -80
  36. package/bin/commands/capabilities.ts +0 -110
  37. package/bin/commands/cardnews.ts +0 -249
  38. package/bin/commands/comfy.ts +0 -54
  39. package/bin/commands/config.ts +0 -186
  40. package/bin/commands/defaults.ts +0 -192
  41. package/bin/commands/doctor.ts +0 -202
  42. package/bin/commands/edit.ts +0 -150
  43. package/bin/commands/gen.ts +0 -214
  44. package/bin/commands/grok.ts +0 -90
  45. package/bin/commands/history.ts +0 -146
  46. package/bin/commands/ls.ts +0 -64
  47. package/bin/commands/metadata.ts +0 -39
  48. package/bin/commands/multimode.ts +0 -196
  49. package/bin/commands/node.ts +0 -166
  50. package/bin/commands/observability.ts +0 -176
  51. package/bin/commands/ping.ts +0 -31
  52. package/bin/commands/prompt-sub/build.ts +0 -101
  53. package/bin/commands/prompt.ts +0 -492
  54. package/bin/commands/ps.ts +0 -81
  55. package/bin/commands/session.ts +0 -266
  56. package/bin/commands/show.ts +0 -72
  57. package/bin/commands/skill.ts +0 -70
  58. package/bin/commands/video.ts +0 -442
  59. package/bin/ima2.ts +0 -430
  60. package/bin/lib/args.ts +0 -92
  61. package/bin/lib/browser-id.ts +0 -16
  62. package/bin/lib/client.ts +0 -122
  63. package/bin/lib/config-store.ts +0 -120
  64. package/bin/lib/destructive-confirm.ts +0 -19
  65. package/bin/lib/doctor-checks.ts +0 -91
  66. package/bin/lib/error-hints.ts +0 -23
  67. package/bin/lib/files.ts +0 -39
  68. package/bin/lib/output.ts +0 -73
  69. package/bin/lib/platform.ts +0 -99
  70. package/bin/lib/recover-output.ts +0 -139
  71. package/bin/lib/sse.ts +0 -73
  72. package/bin/lib/star-prompt.ts +0 -97
  73. package/bin/lib/storage-doctor.ts +0 -39
  74. package/bin/lib/ui-build.ts +0 -85
  75. package/config.ts +0 -354
  76. package/lib/agentCommandParser.ts +0 -69
  77. package/lib/agentGenerationPlanner.ts +0 -273
  78. package/lib/agentQuestionResponder.ts +0 -266
  79. package/lib/agentQueueStore.ts +0 -270
  80. package/lib/agentQueueWorker.ts +0 -89
  81. package/lib/agentRuntime.ts +0 -604
  82. package/lib/agentSettings.ts +0 -72
  83. package/lib/agentStore.ts +0 -422
  84. package/lib/agentStoreRows.ts +0 -136
  85. package/lib/agentTypes.ts +0 -154
  86. package/lib/apiCachePolicy.ts +0 -11
  87. package/lib/assetLifecycle.ts +0 -146
  88. package/lib/canvasVersionStore.ts +0 -223
  89. package/lib/capabilities.ts +0 -126
  90. package/lib/cardNewsGenerator.ts +0 -271
  91. package/lib/cardNewsJobStore.ts +0 -142
  92. package/lib/cardNewsManifestStore.ts +0 -154
  93. package/lib/cardNewsPlanner.ts +0 -236
  94. package/lib/cardNewsPlannerClient.ts +0 -155
  95. package/lib/cardNewsPlannerPrompt.ts +0 -62
  96. package/lib/cardNewsPlannerSchema.ts +0 -321
  97. package/lib/cardNewsRoleTemplateStore.ts +0 -47
  98. package/lib/cardNewsTemplateStore.ts +0 -252
  99. package/lib/codexDetect.ts +0 -71
  100. package/lib/comfyBridge.ts +0 -235
  101. package/lib/composerSnapshot.ts +0 -33
  102. package/lib/configKeys.ts +0 -62
  103. package/lib/db.ts +0 -295
  104. package/lib/errInfo.ts +0 -43
  105. package/lib/errorClassify.ts +0 -100
  106. package/lib/generationCancel.ts +0 -28
  107. package/lib/generationErrors.ts +0 -238
  108. package/lib/grokImageAdapter.ts +0 -513
  109. package/lib/grokMultimodeAdapter.ts +0 -84
  110. package/lib/grokProxyLauncher.ts +0 -153
  111. package/lib/grokRuntime.ts +0 -23
  112. package/lib/grokSizeMapper.ts +0 -71
  113. package/lib/grokVideoAdapter.ts +0 -458
  114. package/lib/grokVideoCanvas.ts +0 -26
  115. package/lib/grokVideoDownload.ts +0 -59
  116. package/lib/grokVideoPlannerPrompt.ts +0 -67
  117. package/lib/historyIndex.ts +0 -51
  118. package/lib/historyList.ts +0 -181
  119. package/lib/imageMetadata.ts +0 -113
  120. package/lib/imageMetadataStore.ts +0 -67
  121. package/lib/imageModels.ts +0 -165
  122. package/lib/inflight.ts +0 -281
  123. package/lib/localImportStore.ts +0 -114
  124. package/lib/logger.ts +0 -161
  125. package/lib/nodeStore.ts +0 -91
  126. package/lib/oauthLauncher.ts +0 -94
  127. package/lib/oauthNormalize.ts +0 -30
  128. package/lib/oauthProxy/errors.ts +0 -128
  129. package/lib/oauthProxy/generators.ts +0 -494
  130. package/lib/oauthProxy/index.ts +0 -28
  131. package/lib/oauthProxy/prompts.ts +0 -123
  132. package/lib/oauthProxy/references.ts +0 -45
  133. package/lib/oauthProxy/runtime.ts +0 -115
  134. package/lib/oauthProxy/streams.ts +0 -232
  135. package/lib/oauthProxy/types.ts +0 -9
  136. package/lib/oauthProxy.ts +0 -3
  137. package/lib/openDirectory.ts +0 -47
  138. package/lib/pngInfo.ts +0 -26
  139. package/lib/promptBuilder/attachments.ts +0 -74
  140. package/lib/promptBuilder/client.ts +0 -130
  141. package/lib/promptBuilder/constants.ts +0 -9
  142. package/lib/promptBuilder/context.ts +0 -36
  143. package/lib/promptBuilder/errors.ts +0 -12
  144. package/lib/promptBuilder/requestSchema.ts +0 -56
  145. package/lib/promptBuilder/responseParser.ts +0 -219
  146. package/lib/promptBuilder/systemPrompt.ts +0 -135
  147. package/lib/promptBuilder/transport.ts +0 -94
  148. package/lib/promptBuilder/types.ts +0 -109
  149. package/lib/promptImport/curatedSources.ts +0 -141
  150. package/lib/promptImport/discoveryRegistry.ts +0 -329
  151. package/lib/promptImport/errors.ts +0 -18
  152. package/lib/promptImport/githubDiscovery.ts +0 -309
  153. package/lib/promptImport/githubFolder.ts +0 -397
  154. package/lib/promptImport/githubSource.ts +0 -257
  155. package/lib/promptImport/gptImageHints.ts +0 -70
  156. package/lib/promptImport/parsePromptCandidates.ts +0 -179
  157. package/lib/promptImport/promptIndex.ts +0 -326
  158. package/lib/promptImport/rankPromptCandidates.ts +0 -65
  159. package/lib/promptImport/types.ts +0 -103
  160. package/lib/promptSafetyPolicy.ts +0 -5
  161. package/lib/providerOptions.ts +0 -56
  162. package/lib/referenceImageCompress.ts +0 -84
  163. package/lib/refs.ts +0 -133
  164. package/lib/requestLogger.ts +0 -49
  165. package/lib/responsesDoctor.ts +0 -456
  166. package/lib/responsesErrors.ts +0 -83
  167. package/lib/responsesFallback.ts +0 -114
  168. package/lib/responsesImageAdapter.ts +0 -466
  169. package/lib/responsesParse.ts +0 -452
  170. package/lib/responsesTools.ts +0 -28
  171. package/lib/runtimeContext.ts +0 -146
  172. package/lib/runtimePorts.ts +0 -105
  173. package/lib/sessionStore.ts +0 -308
  174. package/lib/storageMigration.ts +0 -310
  175. package/lib/styleSheet.ts +0 -139
  176. package/lib/systemTrash.ts +0 -20
  177. package/lib/videoContinuity.ts +0 -180
  178. package/lib/videoFrameExtract.ts +0 -78
  179. package/lib/videoSeriesChain.ts +0 -29
  180. package/lib/visibleTextLanguagePolicy.ts +0 -7
  181. package/routes/agent.ts +0 -308
  182. package/routes/annotations.ts +0 -118
  183. package/routes/canvasVersions.ts +0 -69
  184. package/routes/capabilities.ts +0 -18
  185. package/routes/cardNews.ts +0 -211
  186. package/routes/comfy.ts +0 -43
  187. package/routes/edit.ts +0 -352
  188. package/routes/generate.ts +0 -492
  189. package/routes/grok.ts +0 -24
  190. package/routes/health.ts +0 -123
  191. package/routes/history.ts +0 -221
  192. package/routes/imageImport.ts +0 -37
  193. package/routes/index.ts +0 -52
  194. package/routes/metadata.ts +0 -77
  195. package/routes/multimode.ts +0 -499
  196. package/routes/nodes.ts +0 -578
  197. package/routes/promptBuilder.ts +0 -37
  198. package/routes/promptImport.ts +0 -379
  199. package/routes/prompts.ts +0 -428
  200. package/routes/quota.ts +0 -89
  201. package/routes/sessions.ts +0 -317
  202. package/routes/storage.ts +0 -47
  203. package/routes/video.ts +0 -300
  204. package/routes/videoExtended.ts +0 -284
  205. package/server.ts +0 -293
  206. package/ui/dist/assets/SettingsWorkspace-PiaVnsdA.js +0 -1
  207. package/ui/dist/assets/index-CjgnNtgt.css +0 -1
  208. 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
- }