ima2-gen 1.1.7 → 1.1.9

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 (229) hide show
  1. package/README.md +56 -27
  2. package/bin/commands/annotate.js +137 -0
  3. package/bin/commands/annotate.ts +118 -0
  4. package/bin/commands/cancel.js +37 -33
  5. package/bin/commands/cancel.ts +45 -0
  6. package/bin/commands/canvas-versions.js +91 -0
  7. package/bin/commands/canvas-versions.ts +80 -0
  8. package/bin/commands/cardnews.js +293 -0
  9. package/bin/commands/cardnews.ts +248 -0
  10. package/bin/commands/comfy.js +63 -0
  11. package/bin/commands/comfy.ts +54 -0
  12. package/bin/commands/config.js +270 -0
  13. package/bin/commands/config.ts +265 -0
  14. package/bin/commands/edit.js +97 -72
  15. package/bin/commands/edit.ts +116 -0
  16. package/bin/commands/gen.js +140 -118
  17. package/bin/commands/gen.ts +176 -0
  18. package/bin/commands/history.js +164 -0
  19. package/bin/commands/history.ts +145 -0
  20. package/bin/commands/ls.js +60 -42
  21. package/bin/commands/ls.ts +60 -0
  22. package/bin/commands/metadata.js +45 -0
  23. package/bin/commands/metadata.ts +36 -0
  24. package/bin/commands/multimode.js +159 -0
  25. package/bin/commands/multimode.ts +146 -0
  26. package/bin/commands/node.js +176 -0
  27. package/bin/commands/node.ts +157 -0
  28. package/bin/commands/observability.js +201 -0
  29. package/bin/commands/observability.ts +176 -0
  30. package/bin/commands/ping.js +26 -20
  31. package/bin/commands/ping.ts +29 -0
  32. package/bin/commands/prompt.js +506 -0
  33. package/bin/commands/prompt.ts +421 -0
  34. package/bin/commands/ps.js +78 -71
  35. package/bin/commands/ps.ts +78 -0
  36. package/bin/commands/session.js +308 -0
  37. package/bin/commands/session.ts +265 -0
  38. package/bin/commands/show.js +75 -40
  39. package/bin/commands/show.ts +69 -0
  40. package/bin/ima2.js +324 -310
  41. package/bin/ima2.ts +444 -0
  42. package/bin/lib/args.js +75 -66
  43. package/bin/lib/args.ts +73 -0
  44. package/bin/lib/browser-id.js +15 -0
  45. package/bin/lib/browser-id.ts +16 -0
  46. package/bin/lib/client.js +91 -83
  47. package/bin/lib/client.ts +109 -0
  48. package/bin/lib/error-hints.js +14 -17
  49. package/bin/lib/error-hints.ts +23 -0
  50. package/bin/lib/files.js +26 -28
  51. package/bin/lib/files.ts +39 -0
  52. package/bin/lib/output.js +44 -42
  53. package/bin/lib/output.ts +58 -0
  54. package/bin/lib/platform.js +60 -56
  55. package/bin/lib/platform.ts +97 -0
  56. package/bin/lib/sse.js +73 -0
  57. package/bin/lib/sse.ts +73 -0
  58. package/bin/lib/star-prompt.js +69 -76
  59. package/bin/lib/star-prompt.ts +97 -0
  60. package/bin/lib/storage-doctor.js +34 -35
  61. package/bin/lib/storage-doctor.ts +38 -0
  62. package/config.js +147 -190
  63. package/config.ts +331 -0
  64. package/docs/API.md +48 -8
  65. package/docs/CLI.md +190 -0
  66. package/docs/FAQ.ko.md +5 -5
  67. package/docs/FAQ.md +5 -5
  68. package/docs/README.ja.md +71 -25
  69. package/docs/README.ko.md +61 -24
  70. package/docs/README.zh-CN.md +73 -27
  71. package/lib/assetLifecycle.js +130 -130
  72. package/lib/assetLifecycle.ts +142 -0
  73. package/lib/canvasVersionStore.js +135 -153
  74. package/lib/canvasVersionStore.ts +181 -0
  75. package/lib/cardNewsGenerator.js +127 -142
  76. package/lib/cardNewsGenerator.ts +162 -0
  77. package/lib/cardNewsJobStore.js +78 -84
  78. package/lib/cardNewsJobStore.ts +107 -0
  79. package/lib/cardNewsManifestStore.js +88 -93
  80. package/lib/cardNewsManifestStore.ts +112 -0
  81. package/lib/cardNewsPlanner.js +157 -152
  82. package/lib/cardNewsPlanner.ts +180 -0
  83. package/lib/cardNewsPlannerClient.js +101 -98
  84. package/lib/cardNewsPlannerClient.ts +114 -0
  85. package/lib/cardNewsPlannerPrompt.js +56 -56
  86. package/lib/cardNewsPlannerPrompt.ts +60 -0
  87. package/lib/cardNewsPlannerSchema.js +231 -223
  88. package/lib/cardNewsPlannerSchema.ts +259 -0
  89. package/lib/cardNewsRoleTemplateStore.js +39 -41
  90. package/lib/cardNewsRoleTemplateStore.ts +47 -0
  91. package/lib/cardNewsTemplateStore.js +171 -175
  92. package/lib/cardNewsTemplateStore.ts +210 -0
  93. package/lib/codexDetect.js +44 -47
  94. package/lib/codexDetect.ts +69 -0
  95. package/lib/comfyBridge.js +164 -184
  96. package/lib/comfyBridge.ts +214 -0
  97. package/lib/db.js +41 -51
  98. package/lib/db.ts +166 -0
  99. package/lib/errorClassify.js +62 -78
  100. package/lib/errorClassify.ts +100 -0
  101. package/lib/generationErrors.js +140 -103
  102. package/lib/generationErrors.ts +125 -0
  103. package/lib/historyList.js +149 -147
  104. package/lib/historyList.ts +164 -0
  105. package/lib/imageMetadata.js +86 -89
  106. package/lib/imageMetadata.ts +111 -0
  107. package/lib/imageMetadataStore.js +46 -51
  108. package/lib/imageMetadataStore.ts +67 -0
  109. package/lib/imageModels.js +38 -45
  110. package/lib/imageModels.ts +52 -0
  111. package/lib/inflight.js +131 -150
  112. package/lib/inflight.ts +204 -0
  113. package/lib/localImportStore.js +105 -0
  114. package/lib/localImportStore.ts +111 -0
  115. package/lib/logger.js +105 -112
  116. package/lib/logger.ts +150 -0
  117. package/lib/nodeStore.js +65 -64
  118. package/lib/nodeStore.ts +81 -0
  119. package/lib/oauthLauncher.js +61 -59
  120. package/lib/oauthLauncher.ts +64 -0
  121. package/lib/oauthNormalize.js +15 -19
  122. package/lib/oauthNormalize.ts +30 -0
  123. package/lib/oauthProxy.js +834 -832
  124. package/lib/oauthProxy.ts +995 -0
  125. package/lib/openDirectory.js +41 -40
  126. package/lib/openDirectory.ts +45 -0
  127. package/lib/pngInfo.js +18 -20
  128. package/lib/pngInfo.ts +26 -0
  129. package/lib/promptImport/curatedSources.js +135 -0
  130. package/lib/promptImport/curatedSources.ts +139 -0
  131. package/lib/promptImport/discoveryRegistry.js +218 -0
  132. package/lib/promptImport/discoveryRegistry.ts +236 -0
  133. package/lib/promptImport/errors.js +10 -10
  134. package/lib/promptImport/errors.ts +18 -0
  135. package/lib/promptImport/githubDiscovery.js +238 -0
  136. package/lib/promptImport/githubDiscovery.ts +248 -0
  137. package/lib/promptImport/githubFolder.js +302 -0
  138. package/lib/promptImport/githubFolder.ts +308 -0
  139. package/lib/promptImport/githubSource.js +194 -171
  140. package/lib/promptImport/githubSource.ts +239 -0
  141. package/lib/promptImport/gptImageHints.js +61 -0
  142. package/lib/promptImport/gptImageHints.ts +68 -0
  143. package/lib/promptImport/parsePromptCandidates.js +110 -112
  144. package/lib/promptImport/parsePromptCandidates.ts +153 -0
  145. package/lib/promptImport/promptIndex.js +230 -0
  146. package/lib/promptImport/promptIndex.ts +248 -0
  147. package/lib/promptImport/rankPromptCandidates.js +52 -0
  148. package/lib/promptImport/rankPromptCandidates.ts +49 -0
  149. package/lib/providerOptions.js +31 -0
  150. package/lib/providerOptions.ts +41 -0
  151. package/lib/referenceImageCompress.js +51 -62
  152. package/lib/referenceImageCompress.ts +75 -0
  153. package/lib/refs.js +93 -81
  154. package/lib/refs.ts +117 -0
  155. package/lib/requestLogger.js +32 -38
  156. package/lib/requestLogger.ts +48 -0
  157. package/lib/responsesImageAdapter.js +351 -0
  158. package/lib/responsesImageAdapter.ts +352 -0
  159. package/lib/runtimePorts.js +71 -73
  160. package/lib/runtimePorts.ts +93 -0
  161. package/lib/sessionStore.js +179 -230
  162. package/lib/sessionStore.ts +272 -0
  163. package/lib/storageMigration.js +247 -245
  164. package/lib/storageMigration.ts +284 -0
  165. package/lib/styleSheet.js +86 -90
  166. package/lib/styleSheet.ts +128 -0
  167. package/lib/systemTrash.js +18 -0
  168. package/lib/systemTrash.ts +20 -0
  169. package/package.json +26 -10
  170. package/routes/annotations.js +76 -79
  171. package/routes/annotations.ts +95 -0
  172. package/routes/canvasVersions.js +50 -54
  173. package/routes/canvasVersions.ts +64 -0
  174. package/routes/cardNews.js +158 -171
  175. package/routes/cardNews.ts +183 -0
  176. package/routes/comfy.js +23 -31
  177. package/routes/comfy.ts +39 -0
  178. package/routes/edit.js +183 -214
  179. package/routes/edit.ts +230 -0
  180. package/routes/generate.js +269 -291
  181. package/routes/generate.ts +309 -0
  182. package/routes/health.js +102 -107
  183. package/routes/health.ts +114 -0
  184. package/routes/history.js +136 -144
  185. package/routes/history.ts +153 -0
  186. package/routes/imageImport.js +33 -0
  187. package/routes/imageImport.ts +33 -0
  188. package/routes/index.js +18 -16
  189. package/routes/index.ts +35 -0
  190. package/routes/metadata.js +60 -64
  191. package/routes/metadata.ts +71 -0
  192. package/routes/multimode.js +228 -263
  193. package/routes/multimode.ts +280 -0
  194. package/routes/nodes.js +378 -424
  195. package/routes/nodes.ts +455 -0
  196. package/routes/promptImport.js +291 -152
  197. package/routes/promptImport.ts +354 -0
  198. package/routes/prompts.js +333 -360
  199. package/routes/prompts.ts +379 -0
  200. package/routes/sessions.js +277 -285
  201. package/routes/sessions.ts +292 -0
  202. package/routes/storage.js +29 -31
  203. package/routes/storage.ts +39 -0
  204. package/server.js +189 -196
  205. package/server.ts +235 -0
  206. package/ui/dist/.vite/manifest.json +101 -0
  207. package/ui/dist/assets/CardNewsWorkspace-BJOCey7Z.js +2 -0
  208. package/ui/dist/assets/NodeCanvas-BZV40eAE.css +1 -0
  209. package/ui/dist/assets/NodeCanvas-C3dzYNsk.js +7 -0
  210. package/ui/dist/assets/PromptImportDialog-Dqu1VpUh.js +2 -0
  211. package/ui/dist/assets/PromptImportDiscoverySection-Dg8T9X0L.js +1 -0
  212. package/ui/dist/assets/PromptImportFolderSection-DBaqsFO4.js +1 -0
  213. package/ui/dist/assets/PromptLibraryPanel-p5QqR97M.js +2 -0
  214. package/ui/dist/assets/SettingsWorkspace-B5bSAZ6u.js +1 -0
  215. package/ui/dist/assets/index-C9cXwiWE.js +25 -0
  216. package/ui/dist/assets/index-CGMIkZXn.css +1 -0
  217. package/ui/dist/assets/index-Cvld7dUZ.js +1 -0
  218. package/ui/dist/index.html +6 -3
  219. package/assets/screenshot.png +0 -0
  220. package/assets/screenshots/classic-generate-light.png +0 -0
  221. package/assets/screenshots/node-graph-branching.png +0 -0
  222. package/assets/screenshots/settings-oauth-generation.png +0 -0
  223. package/assets/screenshots/settings-workspace.png +0 -0
  224. package/assets/screenshots/style-sheet-editor.png +0 -0
  225. package/integrations/comfyui/ima2_gen_bridge/__pycache__/__init__.cpython-313.pyc +0 -0
  226. package/integrations/comfyui/ima2_gen_bridge/__pycache__/nodes.cpython-313.pyc +0 -0
  227. package/ui/dist/assets/index-DARPdT4Q.css +0 -1
  228. package/ui/dist/assets/index-ht80GMq4.js +0 -31
  229. package/ui/dist/assets/index-ht80GMq4.js.map +0 -1
@@ -0,0 +1,111 @@
1
+ import { mkdir, writeFile } from "fs/promises";
2
+ import { basename, join, normalize } from "path";
3
+ import { randomBytes } from "crypto";
4
+ import { embedImageMetadataBestEffort } from "./imageMetadataStore.js";
5
+
6
+ const PNG_SIGNATURE_HEX = "89504e470d0a1a0a";
7
+ const JPEG_SIGNATURE_HEX = "ffd8ff";
8
+ const WEBP_RIFF_HEAD = "52494646";
9
+ const WEBP_VP_TAIL = "57454250";
10
+
11
+ function detectFormat(buffer) {
12
+ if (!Buffer.isBuffer(buffer) || buffer.length < 12) return null;
13
+ const head8 = buffer.subarray(0, 8).toString("hex");
14
+ if (head8 === PNG_SIGNATURE_HEX) return "png";
15
+ if (head8.startsWith(JPEG_SIGNATURE_HEX)) return "jpeg";
16
+ if (
17
+ buffer.subarray(0, 4).toString("hex") === WEBP_RIFF_HEAD &&
18
+ buffer.subarray(8, 12).toString("hex") === WEBP_VP_TAIL
19
+ ) {
20
+ return "webp";
21
+ }
22
+ return null;
23
+ }
24
+
25
+ function ensureInsideGeneratedDir(generatedDir, filename) {
26
+ const full = normalize(join(generatedDir, filename));
27
+ const root = normalize(generatedDir);
28
+ if (!full.startsWith(root)) {
29
+ const err: any = new Error("Imported path escapes generated directory");
30
+ err.status = 400;
31
+ err.code = "IMPORT_PATH_ESCAPE";
32
+ throw err;
33
+ }
34
+ return full;
35
+ }
36
+
37
+ function makeImportedFilename(format) {
38
+ const stamp = new Date().toISOString().slice(0, 19).replace(/[-:T]/g, "");
39
+ const rand = randomBytes(3).toString("hex");
40
+ return `imported-${stamp}-${rand}.${format}`;
41
+ }
42
+
43
+ function safeOriginalName(input) {
44
+ if (typeof input !== "string" || !input) return null;
45
+ const trimmed = input.slice(0, 200);
46
+ return basename(trimmed);
47
+ }
48
+
49
+ export async function createLocalImport(ctx, { buffer, originalFilename }) {
50
+ if (!Buffer.isBuffer(buffer) || buffer.length === 0) {
51
+ const err: any = new Error("Image body is required");
52
+ err.status = 400;
53
+ err.code = "EMPTY_IMPORT";
54
+ throw err;
55
+ }
56
+ const format = detectFormat(buffer);
57
+ if (!format) {
58
+ const err: any = new Error("Only PNG, JPEG, or WebP is supported");
59
+ err.status = 400;
60
+ err.code = "IMPORT_BAD_FORMAT";
61
+ throw err;
62
+ }
63
+ const filename = makeImportedFilename(format);
64
+ const fullPath = ensureInsideGeneratedDir(ctx.config.storage.generatedDir, filename);
65
+ await mkdir(ctx.config.storage.generatedDir, { recursive: true });
66
+
67
+ const meta = {
68
+ schema: "ima2.generation.v1",
69
+ app: "ima2-gen",
70
+ version: ctx.packageVersion,
71
+ createdAt: Date.now(),
72
+ kind: "imported",
73
+ canvasVersion: false,
74
+ originalFilename: safeOriginalName(originalFilename),
75
+ format,
76
+ };
77
+ const embedded = await embedImageMetadataBestEffort(buffer, format, meta, {
78
+ version: ctx.packageVersion,
79
+ });
80
+ await writeFile(fullPath, embedded.buffer);
81
+ await writeFile(`${fullPath}.json`, JSON.stringify(meta)).catch(() => {});
82
+
83
+ const url = `/generated/${encodeURIComponent(filename)}`;
84
+ return {
85
+ filename,
86
+ url,
87
+ image: url,
88
+ thumb: url,
89
+ createdAt: meta.createdAt,
90
+ format,
91
+ kind: "imported",
92
+ canvasVersion: false,
93
+ canvasSourceFilename: null,
94
+ canvasEditableFilename: null,
95
+ prompt: null,
96
+ userPrompt: null,
97
+ revisedPrompt: null,
98
+ promptMode: null,
99
+ quality: null,
100
+ size: null,
101
+ model: null,
102
+ provider: null,
103
+ usage: null,
104
+ webSearchCalls: 0,
105
+ sessionId: null,
106
+ nodeId: null,
107
+ parentNodeId: null,
108
+ refsCount: 0,
109
+ isFavorite: false,
110
+ };
111
+ }
package/lib/logger.js CHANGED
@@ -1,150 +1,143 @@
1
1
  const REDACTED = "[redacted]";
2
2
  const MAX_VALUE_LEN = 240;
3
3
  const LOG_LEVELS = {
4
- debug: 10,
5
- info: 20,
6
- warn: 30,
7
- error: 40,
8
- silent: 50,
4
+ debug: 10,
5
+ info: 20,
6
+ warn: 30,
7
+ error: 40,
8
+ silent: 50,
9
9
  };
10
-
11
10
  const SECRET_KEYS = new Set([
12
- "authorization",
13
- "cookie",
14
- "headers",
15
- "apiKey",
16
- "token",
17
- "password",
18
- "secret",
19
- "body",
20
- "prompt",
21
- "effectivePrompt",
22
- "userPrompt",
23
- "revisedPrompt",
24
- "textPrompt",
25
- "styleSheet",
26
- "style_sheet",
27
- "image",
28
- "imageB64",
29
- "image_url",
30
- "references",
31
- "rawResponse",
11
+ "authorization",
12
+ "cookie",
13
+ "headers",
14
+ "apiKey",
15
+ "token",
16
+ "password",
17
+ "secret",
18
+ "body",
19
+ "prompt",
20
+ "effectivePrompt",
21
+ "userPrompt",
22
+ "revisedPrompt",
23
+ "textPrompt",
24
+ "styleSheet",
25
+ "style_sheet",
26
+ "image",
27
+ "imageB64",
28
+ "image_url",
29
+ "references",
30
+ "rawResponse",
32
31
  ]);
33
-
34
32
  const ALLOWED_PROMPT_METRICS = new Set(["promptChars", "promptMode"]);
35
-
36
33
  let activeLevel = "info";
37
34
  let activeSink = console;
38
-
39
35
  function shouldRedactKey(key) {
40
- if (ALLOWED_PROMPT_METRICS.has(key)) return false;
41
- if (SECRET_KEYS.has(key)) return true;
42
- const lower = key.toLowerCase();
43
- return (
44
- lower.includes("token") ||
45
- lower.includes("authorization") ||
46
- lower.includes("cookie") ||
47
- lower.includes("apikey") ||
48
- lower.includes("api_key") ||
49
- lower.includes("secret") ||
50
- lower.includes("b64") ||
51
- lower.includes("base64") ||
52
- lower.includes("dataurl")
53
- );
36
+ if (ALLOWED_PROMPT_METRICS.has(key))
37
+ return false;
38
+ if (SECRET_KEYS.has(key))
39
+ return true;
40
+ const lower = key.toLowerCase();
41
+ return (lower.includes("token") ||
42
+ lower.includes("authorization") ||
43
+ lower.includes("cookie") ||
44
+ lower.includes("apikey") ||
45
+ lower.includes("api_key") ||
46
+ lower.includes("secret") ||
47
+ lower.includes("b64") ||
48
+ lower.includes("base64") ||
49
+ lower.includes("dataurl"));
54
50
  }
55
-
56
51
  function sanitizeValue(value) {
57
- if (value == null) return value;
58
- if (value instanceof Error) return sanitizeError(value);
59
- if (Array.isArray(value)) return `[array:${value.length}]`;
60
- if (Buffer.isBuffer(value)) return `[buffer:${value.length}]`;
61
- if (typeof value === "object") return "[object]";
62
- if (typeof value === "string") {
63
- const oneLine = value
64
- .replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [redacted]")
65
- .replace(/data:image\/[a-z0-9.+-]+;base64,[A-Za-z0-9+/=]+/gi, "data:image/[redacted]")
66
- .replace(/\s+/g, " ")
67
- .trim();
68
- return oneLine.length > MAX_VALUE_LEN ? `${oneLine.slice(0, MAX_VALUE_LEN)}...` : oneLine;
69
- }
70
- return value;
52
+ if (value == null)
53
+ return value;
54
+ if (value instanceof Error)
55
+ return sanitizeError(value);
56
+ if (Array.isArray(value))
57
+ return `[array:${value.length}]`;
58
+ if (Buffer.isBuffer(value))
59
+ return `[buffer:${value.length}]`;
60
+ if (typeof value === "object")
61
+ return "[object]";
62
+ if (typeof value === "string") {
63
+ const oneLine = value
64
+ .replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [redacted]")
65
+ .replace(/data:image\/[a-z0-9.+-]+;base64,[A-Za-z0-9+/=]+/gi, "data:image/[redacted]")
66
+ .replace(/\s+/g, " ")
67
+ .trim();
68
+ return oneLine.length > MAX_VALUE_LEN ? `${oneLine.slice(0, MAX_VALUE_LEN)}...` : oneLine;
69
+ }
70
+ return value;
71
71
  }
72
-
73
72
  export function sanitizeError(err) {
74
- if (!err) return { message: "Unknown error" };
75
- return {
76
- name: err.name || "Error",
77
- code: err.code || undefined,
78
- status: err.status || undefined,
79
- message: sanitizeValue(err.message || "Unknown error"),
80
- };
73
+ if (!err)
74
+ return { message: "Unknown error" };
75
+ return {
76
+ name: err.name || "Error",
77
+ code: err.code || undefined,
78
+ status: err.status || undefined,
79
+ message: sanitizeValue(err.message || "Unknown error"),
80
+ };
81
81
  }
82
-
83
82
  export function sanitizeFields(fields = {}) {
84
- const out = {};
85
- for (const [key, value] of Object.entries(fields)) {
86
- out[key] = shouldRedactKey(key) ? REDACTED : sanitizeValue(value);
87
- }
88
- return out;
83
+ const out = {};
84
+ for (const [key, value] of Object.entries(fields)) {
85
+ out[key] = shouldRedactKey(key) ? REDACTED : sanitizeValue(value);
86
+ }
87
+ return out;
89
88
  }
90
-
91
89
  function formatValue(value) {
92
- if (value === undefined) return undefined;
93
- if (value === null) return "null";
94
- if (typeof value === "boolean" || typeof value === "number") return String(value);
95
- return JSON.stringify(String(value));
90
+ if (value === undefined)
91
+ return undefined;
92
+ if (value === null)
93
+ return "null";
94
+ if (typeof value === "boolean" || typeof value === "number")
95
+ return String(value);
96
+ return JSON.stringify(String(value));
96
97
  }
97
-
98
98
  export function formatLog(scope, event, fields = {}) {
99
- const safeFields = sanitizeFields(fields);
100
- const parts = Object.entries(safeFields)
101
- .map(([key, value]) => {
102
- const formatted = formatValue(value);
103
- return formatted === undefined ? null : `${key}=${formatted}`;
99
+ const safeFields = sanitizeFields(fields);
100
+ const parts = Object.entries(safeFields)
101
+ .map(([key, value]) => {
102
+ const formatted = formatValue(value);
103
+ return formatted === undefined ? null : `${key}=${formatted}`;
104
104
  })
105
- .filter(Boolean);
106
- return `[${scope}.${event}]${parts.length ? ` ${parts.join(" ")}` : ""}`;
105
+ .filter(Boolean);
106
+ return `[${scope}.${event}]${parts.length ? ` ${parts.join(" ")}` : ""}`;
107
107
  }
108
-
109
108
  export function normalizeLogLevel(level) {
110
- return typeof level === "string" && Object.hasOwn(LOG_LEVELS, level) ? level : "info";
109
+ return typeof level === "string" && Object.hasOwn(LOG_LEVELS, level) ? level : "info";
111
110
  }
112
-
113
111
  export function configureLogger(options = {}) {
114
- activeLevel = normalizeLogLevel(options.level);
115
- activeSink = options.sink || console;
112
+ activeLevel = normalizeLogLevel(options.level);
113
+ activeSink = options.sink || console;
116
114
  }
117
-
118
115
  export function shouldLog(level) {
119
- const normalized = normalizeLogLevel(level);
120
- return LOG_LEVELS[normalized] >= LOG_LEVELS[activeLevel] && activeLevel !== "silent";
116
+ const normalized = normalizeLogLevel(level);
117
+ return LOG_LEVELS[normalized] >= LOG_LEVELS[activeLevel] && activeLevel !== "silent";
121
118
  }
122
-
123
119
  function writeLog(level, line) {
124
- if (!shouldLog(level)) return;
125
- const writer = activeSink[level] || activeSink.log || console.log;
126
- writer.call(activeSink, line);
120
+ if (!shouldLog(level))
121
+ return;
122
+ const writer = activeSink[level] || activeSink.log || console.log;
123
+ writer.call(activeSink, line);
127
124
  }
128
-
129
125
  export function logDebug(scope, event, fields = {}) {
130
- writeLog("debug", formatLog(scope, event, fields));
126
+ writeLog("debug", formatLog(scope, event, fields));
131
127
  }
132
-
133
128
  export function logEvent(scope, event, fields = {}) {
134
- writeLog("info", formatLog(scope, event, fields));
129
+ writeLog("info", formatLog(scope, event, fields));
135
130
  }
136
-
137
131
  export function logWarn(scope, event, fields = {}) {
138
- writeLog("warn", formatLog(scope, event, fields));
132
+ writeLog("warn", formatLog(scope, event, fields));
139
133
  }
140
-
141
134
  export function logError(scope, event, err, fields = {}) {
142
- const safe = sanitizeError(err);
143
- writeLog("error", formatLog(scope, event, {
144
- ...fields,
145
- errorName: safe.name,
146
- errorCode: safe.code,
147
- errorStatus: safe.status,
148
- errorMessage: safe.message,
149
- }));
135
+ const safe = sanitizeError(err);
136
+ writeLog("error", formatLog(scope, event, {
137
+ ...fields,
138
+ errorName: safe.name,
139
+ errorCode: safe.code,
140
+ errorStatus: safe.status,
141
+ errorMessage: safe.message,
142
+ }));
150
143
  }
package/lib/logger.ts ADDED
@@ -0,0 +1,150 @@
1
+ const REDACTED = "[redacted]";
2
+ const MAX_VALUE_LEN = 240;
3
+ const LOG_LEVELS = {
4
+ debug: 10,
5
+ info: 20,
6
+ warn: 30,
7
+ error: 40,
8
+ silent: 50,
9
+ };
10
+
11
+ const SECRET_KEYS = new Set([
12
+ "authorization",
13
+ "cookie",
14
+ "headers",
15
+ "apiKey",
16
+ "token",
17
+ "password",
18
+ "secret",
19
+ "body",
20
+ "prompt",
21
+ "effectivePrompt",
22
+ "userPrompt",
23
+ "revisedPrompt",
24
+ "textPrompt",
25
+ "styleSheet",
26
+ "style_sheet",
27
+ "image",
28
+ "imageB64",
29
+ "image_url",
30
+ "references",
31
+ "rawResponse",
32
+ ]);
33
+
34
+ const ALLOWED_PROMPT_METRICS = new Set(["promptChars", "promptMode"]);
35
+
36
+ let activeLevel = "info";
37
+ let activeSink = console;
38
+
39
+ function shouldRedactKey(key) {
40
+ if (ALLOWED_PROMPT_METRICS.has(key)) return false;
41
+ if (SECRET_KEYS.has(key)) return true;
42
+ const lower = key.toLowerCase();
43
+ return (
44
+ lower.includes("token") ||
45
+ lower.includes("authorization") ||
46
+ lower.includes("cookie") ||
47
+ lower.includes("apikey") ||
48
+ lower.includes("api_key") ||
49
+ lower.includes("secret") ||
50
+ lower.includes("b64") ||
51
+ lower.includes("base64") ||
52
+ lower.includes("dataurl")
53
+ );
54
+ }
55
+
56
+ function sanitizeValue(value) {
57
+ if (value == null) return value;
58
+ if (value instanceof Error) return sanitizeError(value);
59
+ if (Array.isArray(value)) return `[array:${value.length}]`;
60
+ if (Buffer.isBuffer(value)) return `[buffer:${value.length}]`;
61
+ if (typeof value === "object") return "[object]";
62
+ if (typeof value === "string") {
63
+ const oneLine = value
64
+ .replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [redacted]")
65
+ .replace(/data:image\/[a-z0-9.+-]+;base64,[A-Za-z0-9+/=]+/gi, "data:image/[redacted]")
66
+ .replace(/\s+/g, " ")
67
+ .trim();
68
+ return oneLine.length > MAX_VALUE_LEN ? `${oneLine.slice(0, MAX_VALUE_LEN)}...` : oneLine;
69
+ }
70
+ return value;
71
+ }
72
+
73
+ export function sanitizeError(err) {
74
+ if (!err) return { message: "Unknown error" };
75
+ return {
76
+ name: err.name || "Error",
77
+ code: err.code || undefined,
78
+ status: err.status || undefined,
79
+ message: sanitizeValue(err.message || "Unknown error"),
80
+ };
81
+ }
82
+
83
+ export function sanitizeFields(fields = {}) {
84
+ const out = {};
85
+ for (const [key, value] of Object.entries(fields)) {
86
+ out[key] = shouldRedactKey(key) ? REDACTED : sanitizeValue(value);
87
+ }
88
+ return out;
89
+ }
90
+
91
+ function formatValue(value) {
92
+ if (value === undefined) return undefined;
93
+ if (value === null) return "null";
94
+ if (typeof value === "boolean" || typeof value === "number") return String(value);
95
+ return JSON.stringify(String(value));
96
+ }
97
+
98
+ export function formatLog(scope, event, fields = {}) {
99
+ const safeFields = sanitizeFields(fields);
100
+ const parts = Object.entries(safeFields)
101
+ .map(([key, value]) => {
102
+ const formatted = formatValue(value);
103
+ return formatted === undefined ? null : `${key}=${formatted}`;
104
+ })
105
+ .filter(Boolean);
106
+ return `[${scope}.${event}]${parts.length ? ` ${parts.join(" ")}` : ""}`;
107
+ }
108
+
109
+ export function normalizeLogLevel(level) {
110
+ return typeof level === "string" && Object.hasOwn(LOG_LEVELS, level) ? level : "info";
111
+ }
112
+
113
+ export function configureLogger(options: any = {}) {
114
+ activeLevel = normalizeLogLevel(options.level);
115
+ activeSink = options.sink || console;
116
+ }
117
+
118
+ export function shouldLog(level) {
119
+ const normalized = normalizeLogLevel(level);
120
+ return LOG_LEVELS[normalized] >= LOG_LEVELS[activeLevel] && activeLevel !== "silent";
121
+ }
122
+
123
+ function writeLog(level, line) {
124
+ if (!shouldLog(level)) return;
125
+ const writer = activeSink[level] || activeSink.log || console.log;
126
+ writer.call(activeSink, line);
127
+ }
128
+
129
+ export function logDebug(scope, event, fields = {}) {
130
+ writeLog("debug", formatLog(scope, event, fields));
131
+ }
132
+
133
+ export function logEvent(scope, event, fields = {}) {
134
+ writeLog("info", formatLog(scope, event, fields));
135
+ }
136
+
137
+ export function logWarn(scope, event, fields = {}) {
138
+ writeLog("warn", formatLog(scope, event, fields));
139
+ }
140
+
141
+ export function logError(scope, event, err, fields = {}) {
142
+ const safe = sanitizeError(err);
143
+ writeLog("error", formatLog(scope, event, {
144
+ ...fields,
145
+ errorName: safe.name,
146
+ errorCode: safe.code,
147
+ errorStatus: safe.status,
148
+ errorMessage: safe.message,
149
+ }));
150
+ }
package/lib/nodeStore.js CHANGED
@@ -3,79 +3,80 @@ import { join, resolve, sep } from "path";
3
3
  import { randomBytes } from "crypto";
4
4
  import { config } from "../config.js";
5
5
  import { embedImageMetadataBestEffort } from "./imageMetadataStore.js";
6
-
7
6
  export function newNodeId() {
8
- return "n_" + randomBytes(config.ids.nodeHexBytes).toString("hex");
7
+ return "n_" + randomBytes(config.ids.nodeHexBytes).toString("hex");
9
8
  }
10
-
11
9
  export async function saveNode(rootDir, { nodeId, b64, meta, ext = "png", generatedDir = config.storage.generatedDir }) {
12
- void rootDir;
13
- const filename = `${nodeId}.${ext}`;
14
- await mkdir(generatedDir, { recursive: true });
15
- const imageMeta = {
16
- ...meta,
17
- kind: meta?.kind || "node",
18
- nodeId: meta?.nodeId || nodeId,
19
- format: meta?.format || ext,
20
- };
21
- const rawBuffer = Buffer.from(b64, "base64");
22
- const embedded = await embedImageMetadataBestEffort(rawBuffer, ext, imageMeta);
23
- if (!embedded.embedded) {
24
- console.warn("[nodeStore] metadata embed skipped:", embedded.warning);
25
- }
26
- await writeFile(join(generatedDir, filename), embedded.buffer);
27
- await writeFile(join(generatedDir, filename + ".json"), JSON.stringify(meta, null, 2));
28
- return { filename };
10
+ void rootDir;
11
+ const filename = `${nodeId}.${ext}`;
12
+ await mkdir(generatedDir, { recursive: true });
13
+ const imageMeta = {
14
+ ...meta,
15
+ kind: meta?.kind || "node",
16
+ nodeId: meta?.nodeId || nodeId,
17
+ format: meta?.format || ext,
18
+ };
19
+ const rawBuffer = Buffer.from(b64, "base64");
20
+ const embedded = await embedImageMetadataBestEffort(rawBuffer, ext, imageMeta);
21
+ if (!embedded.embedded) {
22
+ console.warn("[nodeStore] metadata embed skipped:", embedded.warning);
23
+ }
24
+ await writeFile(join(generatedDir, filename), embedded.buffer);
25
+ await writeFile(join(generatedDir, filename + ".json"), JSON.stringify(meta, null, 2));
26
+ return { filename };
29
27
  }
30
-
31
28
  export async function loadNodeB64(rootDir, filename, generatedDir = config.storage.generatedDir) {
32
- const p = resolveGeneratedPath(rootDir, filename, generatedDir);
33
- try { await access(p); } catch {
34
- const err = new Error(`Node file not found: ${filename}`);
35
- err.code = "NODE_NOT_FOUND";
36
- err.status = 404;
37
- throw err;
38
- }
39
- const buf = await readFile(p);
40
- return buf.toString("base64");
29
+ const p = resolveGeneratedPath(rootDir, filename, generatedDir);
30
+ try {
31
+ await access(p);
32
+ }
33
+ catch {
34
+ const err = new Error(`Node file not found: ${filename}`);
35
+ err.code = "NODE_NOT_FOUND";
36
+ err.status = 404;
37
+ throw err;
38
+ }
39
+ const buf = await readFile(p);
40
+ return buf.toString("base64");
41
41
  }
42
-
43
42
  export async function loadNodeMeta(rootDir, nodeId, ext = "png", generatedDir = config.storage.generatedDir) {
44
- void rootDir;
45
- try {
46
- return JSON.parse(await readFile(join(generatedDir, `${nodeId}.${ext}.json`), "utf-8"));
47
- } catch {
48
- return null;
49
- }
43
+ void rootDir;
44
+ try {
45
+ return JSON.parse(await readFile(join(generatedDir, `${nodeId}.${ext}.json`), "utf-8"));
46
+ }
47
+ catch {
48
+ return null;
49
+ }
50
50
  }
51
-
52
51
  export async function loadAssetB64(rootDir, externalSrc, generatedDir = config.storage.generatedDir) {
53
- const p = resolveGeneratedPath(rootDir, externalSrc, generatedDir);
54
- try { await access(p); } catch {
55
- const err = new Error(`Asset file not found: ${externalSrc}`);
56
- err.code = "NODE_NOT_FOUND";
57
- err.status = 404;
58
- throw err;
59
- }
60
- const buf = await readFile(p);
61
- return buf.toString("base64");
52
+ const p = resolveGeneratedPath(rootDir, externalSrc, generatedDir);
53
+ try {
54
+ await access(p);
55
+ }
56
+ catch {
57
+ const err = new Error(`Asset file not found: ${externalSrc}`);
58
+ err.code = "NODE_NOT_FOUND";
59
+ err.status = 404;
60
+ throw err;
61
+ }
62
+ const buf = await readFile(p);
63
+ return buf.toString("base64");
62
64
  }
63
-
64
65
  function resolveGeneratedPath(rootDir, relPath, generatedDir = config.storage.generatedDir) {
65
- void rootDir;
66
- if (typeof relPath !== "string" || relPath.length === 0) {
67
- const err = new Error("Asset path is required");
68
- err.code = "NODE_SOURCE_INVALID";
69
- err.status = 400;
70
- throw err;
71
- }
72
- const baseDir = resolve(generatedDir);
73
- const target = resolve(baseDir, relPath);
74
- if (target !== baseDir && !target.startsWith(baseDir + sep)) {
75
- const err = new Error(`Asset path escapes generated/: ${relPath}`);
76
- err.code = "NODE_SOURCE_INVALID";
77
- err.status = 400;
78
- throw err;
79
- }
80
- return target;
66
+ void rootDir;
67
+ if (typeof relPath !== "string" || relPath.length === 0) {
68
+ const err = new Error("Asset path is required");
69
+ err.code = "NODE_SOURCE_INVALID";
70
+ err.status = 400;
71
+ throw err;
72
+ }
73
+ const baseDir = resolve(generatedDir);
74
+ const target = resolve(baseDir, relPath);
75
+ if (target !== baseDir && !target.startsWith(baseDir + sep)) {
76
+ const err = new Error(`Asset path escapes generated/: ${relPath}`);
77
+ err.code = "NODE_SOURCE_INVALID";
78
+ err.status = 400;
79
+ throw err;
80
+ }
81
+ return target;
81
82
  }