ima2-gen 1.1.8 → 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 (230) 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 -243
  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 +87 -93
  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 +125 -129
  130. package/lib/promptImport/curatedSources.ts +139 -0
  131. package/lib/promptImport/discoveryRegistry.js +185 -203
  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 +209 -219
  136. package/lib/promptImport/githubDiscovery.ts +248 -0
  137. package/lib/promptImport/githubFolder.js +253 -259
  138. package/lib/promptImport/githubFolder.ts +308 -0
  139. package/lib/promptImport/githubSource.js +189 -200
  140. package/lib/promptImport/githubSource.ts +239 -0
  141. package/lib/promptImport/gptImageHints.js +49 -56
  142. package/lib/promptImport/gptImageHints.ts +68 -0
  143. package/lib/promptImport/parsePromptCandidates.js +108 -123
  144. package/lib/promptImport/parsePromptCandidates.ts +153 -0
  145. package/lib/promptImport/promptIndex.js +190 -208
  146. package/lib/promptImport/promptIndex.ts +248 -0
  147. package/lib/promptImport/rankPromptCandidates.js +46 -43
  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 +27 -27
  187. package/routes/imageImport.ts +33 -0
  188. package/routes/index.js +17 -17
  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 +284 -324
  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 +2 -2
  219. package/assets/phase-a-bg-cleanup-test.png +0 -0
  220. package/assets/screenshot.png +0 -0
  221. package/assets/screenshots/classic-generate-light.png +0 -0
  222. package/assets/screenshots/node-graph-branching.png +0 -0
  223. package/assets/screenshots/settings-oauth-generation.png +0 -0
  224. package/assets/screenshots/settings-workspace.png +0 -0
  225. package/assets/screenshots/style-sheet-editor.png +0 -0
  226. package/integrations/comfyui/ima2_gen_bridge/__pycache__/__init__.cpython-313.pyc +0 -0
  227. package/integrations/comfyui/ima2_gen_bridge/__pycache__/nodes.cpython-313.pyc +0 -0
  228. package/ui/dist/assets/index-BDffwmLs.css +0 -1
  229. package/ui/dist/assets/index-D0fdHLkJ.js +0 -31
  230. package/ui/dist/assets/index-D0fdHLkJ.js.map +0 -1
@@ -1,16 +1,16 @@
1
1
  export class PromptImportError extends Error {
2
- constructor(code, message, status = 400) {
3
- super(message);
4
- this.name = "PromptImportError";
5
- this.code = code;
6
- this.status = status;
7
- }
2
+ code;
3
+ status;
4
+ constructor(code, message, status = 400) {
5
+ super(message);
6
+ this.name = "PromptImportError";
7
+ this.code = code;
8
+ this.status = status;
9
+ }
8
10
  }
9
-
10
11
  export function promptImportError(code, message, status = 400) {
11
- return new PromptImportError(code, message, status);
12
+ return new PromptImportError(code, message, status);
12
13
  }
13
-
14
14
  export function isPromptImportError(error) {
15
- return error instanceof PromptImportError || Boolean(error?.code && error?.status);
15
+ return error instanceof PromptImportError || Boolean(error?.code && error?.status);
16
16
  }
@@ -0,0 +1,18 @@
1
+ export class PromptImportError extends Error {
2
+ code: any;
3
+ status: any;
4
+ constructor(code, message, status = 400) {
5
+ super(message);
6
+ this.name = "PromptImportError";
7
+ this.code = code;
8
+ this.status = status;
9
+ }
10
+ }
11
+
12
+ export function promptImportError(code, message, status = 400) {
13
+ return new PromptImportError(code, message, status);
14
+ }
15
+
16
+ export function isPromptImportError(error) {
17
+ return error instanceof PromptImportError || Boolean(error?.code && error?.status);
18
+ }
@@ -1,248 +1,238 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { promptImportError } from "./errors.js";
3
3
  import { upsertDiscoveryCandidates } from "./discoveryRegistry.js";
4
-
5
4
  const GITHUB_API_HOST = "api.github.com";
6
5
  const DEFAULT_DISCOVERY_SEEDS = [
7
- "gpt-image-2 prompt",
8
- "image generation prompts",
9
- "nano banana prompts",
10
- "product photography prompt",
11
- "typography image prompt",
12
- "reference image prompt",
6
+ "gpt-image-2 prompt",
7
+ "image generation prompts",
8
+ "nano banana prompts",
9
+ "product photography prompt",
10
+ "typography image prompt",
11
+ "reference image prompt",
13
12
  ];
14
-
15
13
  function tokenize(value) {
16
- return String(value || "")
17
- .toLowerCase()
18
- .split(/[^a-z0-9가-힣-]+/i)
19
- .filter(Boolean);
14
+ return String(value || "")
15
+ .toLowerCase()
16
+ .split(/[^a-z0-9가-힣-]+/i)
17
+ .filter(Boolean);
20
18
  }
21
-
22
19
  function discoveryLimits(ctx) {
23
- return {
24
- limit: ctx.config.limits.promptImportDiscoverySearchLimit,
25
- maxQueries: ctx.config.limits.promptImportDiscoveryMaxQueries,
26
- fetchTimeoutMs: ctx.config.limits.promptImportFetchTimeoutMs,
27
- };
20
+ return {
21
+ limit: ctx.config.limits.promptImportDiscoverySearchLimit,
22
+ maxQueries: ctx.config.limits.promptImportDiscoveryMaxQueries,
23
+ fetchTimeoutMs: ctx.config.limits.promptImportFetchTimeoutMs,
24
+ };
28
25
  }
29
-
30
26
  function validateGitHubApiUrl(rawUrl) {
31
- let url;
32
- try {
33
- url = new URL(rawUrl);
34
- } catch {
35
- throw promptImportError("GITHUB_DISCOVERY_FAILED", "GitHub discovery returned an invalid URL");
36
- }
37
- if (!["https:", "http:"].includes(url.protocol) || url.hostname !== GITHUB_API_HOST) {
38
- throw promptImportError("GITHUB_DISCOVERY_FAILED", "GitHub discovery redirected to an unsupported host");
39
- }
27
+ let url;
28
+ try {
29
+ url = new URL(rawUrl);
30
+ }
31
+ catch {
32
+ throw promptImportError("GITHUB_DISCOVERY_FAILED", "GitHub discovery returned an invalid URL");
33
+ }
34
+ if (!["https:", "http:"].includes(url.protocol) || url.hostname !== GITHUB_API_HOST) {
35
+ throw promptImportError("GITHUB_DISCOVERY_FAILED", "GitHub discovery redirected to an unsupported host");
36
+ }
40
37
  }
41
-
42
38
  function rateLimitFromHeaders(headers) {
43
- const remaining = Number(headers.get("x-ratelimit-remaining") || Number.NaN);
44
- const reset = Number(headers.get("x-ratelimit-reset") || Number.NaN);
45
- const limit = Number(headers.get("x-ratelimit-limit") || Number.NaN);
46
- return {
47
- limit: Number.isFinite(limit) ? limit : null,
48
- remaining: Number.isFinite(remaining) ? remaining : null,
49
- resetAt: Number.isFinite(reset) ? new Date(reset * 1000).toISOString() : null,
50
- };
39
+ const remaining = Number(headers.get("x-ratelimit-remaining") || Number.NaN);
40
+ const reset = Number(headers.get("x-ratelimit-reset") || Number.NaN);
41
+ const limit = Number(headers.get("x-ratelimit-limit") || Number.NaN);
42
+ return {
43
+ limit: Number.isFinite(limit) ? limit : null,
44
+ remaining: Number.isFinite(remaining) ? remaining : null,
45
+ resetAt: Number.isFinite(reset) ? new Date(reset * 1000).toISOString() : null,
46
+ };
51
47
  }
52
-
53
48
  function searchHeaders(ctx) {
54
- const headers = {
55
- Accept: "application/vnd.github+json",
56
- "X-GitHub-Api-Version": "2022-11-28",
57
- "User-Agent": "ima2-prompt-import-discovery",
58
- };
59
- const token = ctx.config.github?.token;
60
- if (token) headers.Authorization = `Bearer ${token}`;
61
- return headers;
49
+ const headers = {
50
+ Accept: "application/vnd.github+json",
51
+ "X-GitHub-Api-Version": "2022-11-28",
52
+ "User-Agent": "ima2-prompt-import-discovery",
53
+ };
54
+ const token = ctx.config.github?.token;
55
+ if (token)
56
+ headers.Authorization = `Bearer ${token}`;
57
+ return headers;
62
58
  }
63
-
64
59
  export function buildDiscoveryQueries({ q = "", seeds = [], limit = 5 } = {}) {
65
- const raw = [q, ...seeds, ...DEFAULT_DISCOVERY_SEEDS]
66
- .map((item) => String(item || "").trim())
67
- .filter(Boolean);
68
- const unique = [...new Set(raw)];
69
- if (unique.length === 0) {
70
- throw promptImportError("GITHUB_DISCOVERY_QUERY_EMPTY", "Discovery query is required", 422);
71
- }
72
- return unique.slice(0, Math.max(1, Number(limit) || 1));
60
+ const raw = [q, ...seeds, ...DEFAULT_DISCOVERY_SEEDS]
61
+ .map((item) => String(item || "").trim())
62
+ .filter(Boolean);
63
+ const unique = [...new Set(raw)];
64
+ if (unique.length === 0) {
65
+ throw promptImportError("GITHUB_DISCOVERY_QUERY_EMPTY", "Discovery query is required", 422);
66
+ }
67
+ return unique.slice(0, Math.max(1, Number(limit) || 1));
73
68
  }
74
-
75
69
  export function scoreDiscoveryRepository(repo, context = {}) {
76
- const terms = tokenize([context.query, ...(context.seeds || [])].join(" "));
77
- const nameText = `${repo.full_name || ""} ${repo.description || ""} ${(repo.topics || []).join(" ")}`.toLowerCase();
78
- const pushedAt = repo.pushed_at ? Date.parse(repo.pushed_at) : 0;
79
- const daysSincePush = pushedAt ? (Date.now() - pushedAt) / 86_400_000 : Number.POSITIVE_INFINITY;
80
- const scoreReasons = [];
81
- const warnings = [];
82
- let score = 0;
83
-
84
- const stars = Number(repo.stargazers_count || 0);
85
- if (stars > 1000) {
86
- score += 20;
87
- scoreReasons.push("popular-repo");
88
- } else if (stars > 100) {
89
- score += 12;
90
- scoreReasons.push("known-repo");
91
- } else if (stars > 10) {
92
- score += 5;
93
- scoreReasons.push("some-stars");
94
- }
95
-
96
- if (daysSincePush <= 365) {
97
- score += 10;
98
- scoreReasons.push("recently-updated");
99
- } else {
100
- score -= 6;
101
- warnings.push("stale-repo");
102
- }
103
-
104
- if (repo.license?.spdx_id) {
105
- score += 8;
106
- scoreReasons.push("license-present");
107
- } else {
108
- score -= 8;
109
- warnings.push("no-license");
110
- }
111
-
112
- if (repo.archived || repo.disabled) {
113
- score -= 20;
114
- warnings.push("archived-or-disabled");
115
- }
116
- if (repo.fork) {
117
- score -= 4;
118
- warnings.push("fork-source");
119
- }
120
- if (!repo.default_branch) {
121
- score -= 10;
122
- warnings.push("missing-default-branch");
123
- } else if (String(repo.default_branch).includes("/")) {
124
- warnings.push("discovery-default-branch-unsupported");
125
- }
126
-
127
- const promptTerms = ["prompt", "image", "generation", "gpt-image", "nano", "typography", "reference"];
128
- if (promptTerms.some((term) => nameText.includes(term))) {
129
- score += 14;
130
- scoreReasons.push("prompt-like");
131
- }
132
- for (const term of terms) {
133
- if (term && nameText.includes(term)) score += 3;
134
- }
135
- if (!repo.html_url || !String(repo.html_url).startsWith("https://github.com/")) {
136
- score -= 20;
137
- warnings.push("non-github-url");
138
- }
139
-
140
- return { score, scoreReasons: [...new Set(scoreReasons)], warnings: [...new Set(warnings)] };
70
+ const terms = tokenize([context.query, ...(context.seeds || [])].join(" "));
71
+ const nameText = `${repo.full_name || ""} ${repo.description || ""} ${(repo.topics || []).join(" ")}`.toLowerCase();
72
+ const pushedAt = repo.pushed_at ? Date.parse(repo.pushed_at) : 0;
73
+ const daysSincePush = pushedAt ? (Date.now() - pushedAt) / 86_400_000 : Number.POSITIVE_INFINITY;
74
+ const scoreReasons = [];
75
+ const warnings = [];
76
+ let score = 0;
77
+ const stars = Number(repo.stargazers_count || 0);
78
+ if (stars > 1000) {
79
+ score += 20;
80
+ scoreReasons.push("popular-repo");
81
+ }
82
+ else if (stars > 100) {
83
+ score += 12;
84
+ scoreReasons.push("known-repo");
85
+ }
86
+ else if (stars > 10) {
87
+ score += 5;
88
+ scoreReasons.push("some-stars");
89
+ }
90
+ if (daysSincePush <= 365) {
91
+ score += 10;
92
+ scoreReasons.push("recently-updated");
93
+ }
94
+ else {
95
+ score -= 6;
96
+ warnings.push("stale-repo");
97
+ }
98
+ if (repo.license?.spdx_id) {
99
+ score += 8;
100
+ scoreReasons.push("license-present");
101
+ }
102
+ else {
103
+ score -= 8;
104
+ warnings.push("no-license");
105
+ }
106
+ if (repo.archived || repo.disabled) {
107
+ score -= 20;
108
+ warnings.push("archived-or-disabled");
109
+ }
110
+ if (repo.fork) {
111
+ score -= 4;
112
+ warnings.push("fork-source");
113
+ }
114
+ if (!repo.default_branch) {
115
+ score -= 10;
116
+ warnings.push("missing-default-branch");
117
+ }
118
+ else if (String(repo.default_branch).includes("/")) {
119
+ warnings.push("discovery-default-branch-unsupported");
120
+ }
121
+ const promptTerms = ["prompt", "image", "generation", "gpt-image", "nano", "typography", "reference"];
122
+ if (promptTerms.some((term) => nameText.includes(term))) {
123
+ score += 14;
124
+ scoreReasons.push("prompt-like");
125
+ }
126
+ for (const term of terms) {
127
+ if (term && nameText.includes(term))
128
+ score += 3;
129
+ }
130
+ if (!repo.html_url || !String(repo.html_url).startsWith("https://github.com/")) {
131
+ score -= 20;
132
+ warnings.push("non-github-url");
133
+ }
134
+ return { score, scoreReasons: [...new Set(scoreReasons)], warnings: [...new Set(warnings)] };
141
135
  }
142
-
143
136
  export function normalizeDiscoveryCandidate(repo, context = {}) {
144
- const fullName = String(repo.full_name || "").trim();
145
- const [owner, name] = fullName.split("/");
146
- const scored = scoreDiscoveryRepository(repo, context);
147
- return {
148
- id: `github:${fullName}`,
149
- repo: fullName,
150
- owner,
151
- name,
152
- fullName,
153
- htmlUrl: repo.html_url,
154
- description: repo.description || "",
155
- defaultBranch: repo.default_branch || "main",
156
- stars: Number(repo.stargazers_count || 0),
157
- forks: Number(repo.forks_count || 0),
158
- openIssues: Number(repo.open_issues_count || 0),
159
- updatedAt: repo.updated_at || null,
160
- pushedAt: repo.pushed_at || null,
161
- licenseSpdx: repo.license?.spdx_id || "NOASSERTION",
162
- topics: Array.isArray(repo.topics) ? repo.topics : [],
163
- language: repo.language || null,
164
- score: scored.score,
165
- scoreReasons: scored.scoreReasons,
166
- warnings: scored.warnings,
167
- status: "candidate",
168
- query: context.query || "",
169
- discoveredAt: new Date().toISOString(),
170
- };
137
+ const fullName = String(repo.full_name || "").trim();
138
+ const [owner, name] = fullName.split("/");
139
+ const scored = scoreDiscoveryRepository(repo, context);
140
+ return {
141
+ id: `github:${fullName}`,
142
+ repo: fullName,
143
+ owner,
144
+ name,
145
+ fullName,
146
+ htmlUrl: repo.html_url,
147
+ description: repo.description || "",
148
+ defaultBranch: repo.default_branch || "main",
149
+ stars: Number(repo.stargazers_count || 0),
150
+ forks: Number(repo.forks_count || 0),
151
+ openIssues: Number(repo.open_issues_count || 0),
152
+ updatedAt: repo.updated_at || null,
153
+ pushedAt: repo.pushed_at || null,
154
+ licenseSpdx: repo.license?.spdx_id || "NOASSERTION",
155
+ topics: Array.isArray(repo.topics) ? repo.topics : [],
156
+ language: repo.language || null,
157
+ score: scored.score,
158
+ scoreReasons: scored.scoreReasons,
159
+ warnings: scored.warnings,
160
+ status: "candidate",
161
+ query: context.query || "",
162
+ discoveredAt: new Date().toISOString(),
163
+ };
171
164
  }
172
-
173
165
  export function discoveryCacheKey(input) {
174
- return createHash("sha256").update(JSON.stringify(input)).digest("hex");
166
+ return createHash("sha256").update(JSON.stringify(input)).digest("hex");
175
167
  }
176
-
177
168
  async function searchOneQuery(ctx, query, perPage) {
178
- const controller = new AbortController();
179
- const timer = setTimeout(() => controller.abort(), discoveryLimits(ctx).fetchTimeoutMs);
180
- const url = new URL("https://api.github.com/search/repositories");
181
- url.searchParams.set("q", `${query} in:name,description`);
182
- url.searchParams.set("sort", "stars");
183
- url.searchParams.set("order", "desc");
184
- url.searchParams.set("per_page", String(perPage));
185
- try {
186
- const response = await fetch(url, {
187
- headers: searchHeaders(ctx),
188
- signal: controller.signal,
189
- });
190
- validateGitHubApiUrl(response.url);
191
- const rateLimit = rateLimitFromHeaders(response.headers);
192
- if (response.status === 403 || response.status === 429) {
193
- throw promptImportError("GITHUB_RATE_LIMITED", "GitHub discovery rate limit reached", 429);
194
- }
195
- if (!response.ok) {
196
- throw promptImportError("GITHUB_DISCOVERY_FAILED", `GitHub discovery failed with ${response.status}`, 422);
197
- }
198
- const data = await response.json();
199
- const items = Array.isArray(data.items) ? data.items : [];
200
- return { items, rateLimit };
201
- } catch (error) {
202
- if (error?.name === "AbortError") {
203
- throw promptImportError("REMOTE_FETCH_TIMEOUT", "GitHub discovery timed out", 504);
204
- }
205
- throw error;
206
- } finally {
207
- clearTimeout(timer);
208
- }
169
+ const controller = new AbortController();
170
+ const timer = setTimeout(() => controller.abort(), discoveryLimits(ctx).fetchTimeoutMs);
171
+ const url = new URL("https://api.github.com/search/repositories");
172
+ url.searchParams.set("q", `${query} in:name,description`);
173
+ url.searchParams.set("sort", "stars");
174
+ url.searchParams.set("order", "desc");
175
+ url.searchParams.set("per_page", String(perPage));
176
+ try {
177
+ const response = await fetch(url, {
178
+ headers: searchHeaders(ctx),
179
+ signal: controller.signal,
180
+ });
181
+ validateGitHubApiUrl(response.url);
182
+ const rateLimit = rateLimitFromHeaders(response.headers);
183
+ if (response.status === 403 || response.status === 429) {
184
+ throw promptImportError("GITHUB_RATE_LIMITED", "GitHub discovery rate limit reached", 429);
185
+ }
186
+ if (!response.ok) {
187
+ throw promptImportError("GITHUB_DISCOVERY_FAILED", `GitHub discovery failed with ${response.status}`, 422);
188
+ }
189
+ const data = await response.json();
190
+ const items = Array.isArray(data.items) ? data.items : [];
191
+ return { items, rateLimit };
192
+ }
193
+ catch (error) {
194
+ if (error?.name === "AbortError") {
195
+ throw promptImportError("REMOTE_FETCH_TIMEOUT", "GitHub discovery timed out", 504);
196
+ }
197
+ throw error;
198
+ }
199
+ finally {
200
+ clearTimeout(timer);
201
+ }
209
202
  }
210
-
211
203
  export async function searchGitHubDiscovery(ctx, options = {}) {
212
- const limits = discoveryLimits(ctx);
213
- const queryLimit = Math.min(Number(options.maxQueries) || limits.maxQueries, limits.maxQueries);
214
- const queries = buildDiscoveryQueries({
215
- q: options.q,
216
- seeds: options.seeds,
217
- limit: queryLimit,
218
- });
219
- const requestedLimit = Math.min(Number(options.limit) || limits.limit, limits.limit);
220
- const perQuery = Math.max(1, Math.ceil(requestedLimit / queries.length));
221
- const warnings = [];
222
- let rateLimit = null;
223
- const byRepo = new Map();
224
-
225
- for (const query of queries) {
226
- const result = await searchOneQuery(ctx, query, perQuery);
227
- rateLimit = result.rateLimit;
228
- for (const repo of result.items) {
229
- const candidate = normalizeDiscoveryCandidate(repo, {
230
- query,
204
+ const limits = discoveryLimits(ctx);
205
+ const queryLimit = Math.min(Number(options.maxQueries) || limits.maxQueries, limits.maxQueries);
206
+ const queries = buildDiscoveryQueries({
207
+ q: options.q,
231
208
  seeds: options.seeds,
232
- });
233
- const existing = byRepo.get(candidate.fullName);
234
- if (!existing || candidate.score > existing.score) {
235
- byRepo.set(candidate.fullName, candidate);
236
- }
237
- }
238
- }
239
-
240
- const candidates = [...byRepo.values()]
241
- .filter((candidate) => candidate.fullName && candidate.htmlUrl?.startsWith("https://github.com/"))
242
- .sort((a, b) => b.score - a.score || b.stars - a.stars)
243
- .slice(0, requestedLimit);
244
-
245
- await upsertDiscoveryCandidates(ctx, candidates);
246
- if (rateLimit?.remaining === 0) warnings.push("github-rate-limit-exhausted");
247
- return { candidates, warnings, rateLimit, cacheKey: discoveryCacheKey({ queries, requestedLimit }) };
209
+ limit: queryLimit,
210
+ });
211
+ const requestedLimit = Math.min(Number(options.limit) || limits.limit, limits.limit);
212
+ const perQuery = Math.max(1, Math.ceil(requestedLimit / queries.length));
213
+ const warnings = [];
214
+ let rateLimit = null;
215
+ const byRepo = new Map();
216
+ for (const query of queries) {
217
+ const result = await searchOneQuery(ctx, query, perQuery);
218
+ rateLimit = result.rateLimit;
219
+ for (const repo of result.items) {
220
+ const candidate = normalizeDiscoveryCandidate(repo, {
221
+ query,
222
+ seeds: options.seeds,
223
+ });
224
+ const existing = byRepo.get(candidate.fullName);
225
+ if (!existing || candidate.score > existing.score) {
226
+ byRepo.set(candidate.fullName, candidate);
227
+ }
228
+ }
229
+ }
230
+ const candidates = [...byRepo.values()]
231
+ .filter((candidate) => candidate.fullName && candidate.htmlUrl?.startsWith("https://github.com/"))
232
+ .sort((a, b) => b.score - a.score || b.stars - a.stars)
233
+ .slice(0, requestedLimit);
234
+ await upsertDiscoveryCandidates(ctx, candidates);
235
+ if (rateLimit?.remaining === 0)
236
+ warnings.push("github-rate-limit-exhausted");
237
+ return { candidates, warnings, rateLimit, cacheKey: discoveryCacheKey({ queries, requestedLimit }) };
248
238
  }