@tomako/tools-runtime 0.1.4 → 0.1.6

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 (57) hide show
  1. package/package.json +2 -1
  2. package/src/components/tools/engine/package-widget-runtime.tsx +5 -1
  3. package/src/components/tools/engine/task-recovery.test.ts +65 -0
  4. package/src/components/tools/engine/task-recovery.ts +109 -0
  5. package/src/components/tools/engine/use-legacy-llm-task.ts +16 -3
  6. package/src/components/tools/engine/use-tool-task.ts +77 -53
  7. package/src/features/tools/app-icon-resizer/app-icon-resizer.spec.ts +1 -1
  8. package/src/features/tools/app-store-screenshot-generator/app-store-screenshot-generator.container.tsx +6 -8
  9. package/src/features/tools/app-store-screenshot-generator/app-store-screenshot-generator.spec.ts +1 -1
  10. package/src/features/tools/app-store-screenshot-generator/widget/app-store-screenshot-generator.tsx +44 -6
  11. package/src/features/tools/app-store-screenshot-generator/widget/generation.test.ts +126 -0
  12. package/src/features/tools/app-store-screenshot-generator/widget/generation.ts +52 -14
  13. package/src/features/tools/cold-start-channel-selector/cold-start-channel-selector.spec.ts +1 -1
  14. package/src/features/tools/cold-start-channel-selector/widget/cold-start-channel-selector.tsx +45 -16
  15. package/src/features/tools/competitor-analysis/competitor-analysis.container.tsx +31 -26
  16. package/src/features/tools/competitor-analysis/competitor-analysis.spec.ts +1 -1
  17. package/src/features/tools/competitor-analysis/widget/competitor-analysis.tsx +47 -16
  18. package/src/features/tools/disclaimer-generator/disclaimer-generator.package.ts +4 -4
  19. package/src/features/tools/email-template-generator/email-template-generator.container.tsx +45 -173
  20. package/src/features/tools/email-template-generator/email-template-generator.spec.ts +1 -1
  21. package/src/features/tools/email-template-generator/widget/email-template-generator.tsx +37 -4
  22. package/src/features/tools/package/to-tool-module.ts +15 -0
  23. package/src/features/tools/product-name-generator/product-name-generator.spec.ts +1 -1
  24. package/src/features/tools/product-name-generator/widget/product-name-generator.tsx +60 -27
  25. package/src/features/tools/product-poster-generator/product-poster-generator.spec.ts +1 -1
  26. package/src/features/tools/product-poster-generator/widget/constants.ts +3 -1
  27. package/src/features/tools/product-poster-generator/widget/helpers.test.ts +51 -0
  28. package/src/features/tools/product-poster-generator/widget/helpers.ts +27 -0
  29. package/src/features/tools/product-poster-generator/widget/product-poster-generator.tsx +112 -12
  30. package/src/features/tools/slogan-generator/slogan-generator.package.ts +101 -29
  31. package/src/features/tools/twitter-gif-downloader/twitter-gif-downloader.container.tsx +2 -3
  32. package/src/features/tools/twitter-gif-downloader/twitter-gif-downloader.spec.ts +1 -1
  33. package/src/i18n/messages/en/tools/app-icon-resizer.ts +1 -1
  34. package/src/i18n/messages/en/tools/app-store-screenshot-generator.ts +3 -3
  35. package/src/i18n/messages/en/tools/cold-start-channel-selector.ts +4 -4
  36. package/src/i18n/messages/en/tools/competitor-analysis.ts +14 -10
  37. package/src/i18n/messages/en/tools/email-template-generator.ts +13 -13
  38. package/src/i18n/messages/en/tools/product-poster-generator.ts +3 -2
  39. package/src/i18n/messages/en/tools/twitter-gif-downloader.ts +1 -1
  40. package/src/i18n/messages/zh/tools/app-store-screenshot-generator.ts +4 -4
  41. package/src/i18n/messages/zh/tools/cold-start-channel-selector.ts +2 -2
  42. package/src/i18n/messages/zh/tools/competitor-analysis.ts +14 -10
  43. package/src/i18n/messages/zh/tools/email-template-generator.ts +12 -12
  44. package/src/i18n/messages/zh/tools/product-poster-generator.ts +2 -1
  45. package/src/i18n/messages/zh/tools/twitter-gif-downloader.ts +1 -1
  46. package/src/i18n/messages/zh-tw/tools/app-store-screenshot-generator.ts +44 -44
  47. package/src/i18n/messages/zh-tw/tools/cold-start-channel-selector.ts +105 -105
  48. package/src/i18n/messages/zh-tw/tools/competitor-analysis.ts +44 -40
  49. package/src/i18n/messages/zh-tw/tools/email-template-generator.ts +112 -112
  50. package/src/i18n/messages/zh-tw/tools/product-name-generator.ts +92 -92
  51. package/src/i18n/messages/zh-tw/tools/product-poster-generator.ts +74 -73
  52. package/src/i18n/messages/zh-tw/tools/twitter-gif-downloader.ts +48 -48
  53. package/src/lib/tools/cold-start-channel-selector-schema.test.ts +39 -0
  54. package/src/lib/tools/cold-start-channel-selector-schema.ts +8 -4
  55. package/src/lib/tools/competitor-analysis-schema.test.ts +69 -0
  56. package/src/lib/tools/competitor-analysis-schema.ts +53 -5
  57. package/src/lib/tools/ops-catalog.ts +10 -1
@@ -118,8 +118,12 @@ const positioningSchema = z
118
118
  .default({ strengths: [], weaknesses: [], opportunities: [] });
119
119
 
120
120
  const sourceSchema = z.object({
121
- label: z.string(),
122
- url: z.string().optional(),
121
+ label: z.string().trim().min(1),
122
+ url: z
123
+ .string()
124
+ .trim()
125
+ .url()
126
+ .refine((value) => /^https?:\/\//i.test(value), "source url must use http or https"),
123
127
  });
124
128
 
125
129
  export const competitorAnalysisResultSchema = z.object({
@@ -129,14 +133,58 @@ export const competitorAnalysisResultSchema = z.object({
129
133
  team: teamSchema,
130
134
  funding: fundingSchema,
131
135
  positioning: positioningSchema,
132
- highlights: z.array(z.string()).default([]),
133
- sources: z.array(sourceSchema).default([]),
136
+ highlights: z.array(z.string().trim().min(1)).default([]),
137
+ sources: z.array(sourceSchema).min(1),
134
138
  confidence,
135
- summary: z.string(),
139
+ summary: z.string().trim().min(1),
136
140
  disclaimer: z.string().default(""),
137
141
  promptVersion: z.string().default(COMPETITOR_ANALYSIS_PROMPT_VERSION),
138
142
  generatedAt: z.string().optional(),
139
143
  sourceUrl: z.string().optional(),
144
+ }).superRefine((result, context) => {
145
+ const hasText = (value: string | undefined) => Boolean(value?.trim());
146
+ const hasCompetitorIdentity =
147
+ hasText(result.overview.name) &&
148
+ [result.overview.description, result.overview.tagline, result.overview.category].some(hasText);
149
+ if (!hasCompetitorIdentity) {
150
+ context.addIssue({
151
+ code: "custom",
152
+ path: ["overview"],
153
+ message: "overview must identify the competitor and describe its product or category",
154
+ });
155
+ }
156
+
157
+ const hasAnalysisFinding =
158
+ result.highlights.some(hasText) ||
159
+ [
160
+ result.traffic.summary,
161
+ result.traffic.monthlyVisitsEstimate,
162
+ result.businessScale.summary,
163
+ result.businessScale.pricingModel,
164
+ result.team.summary,
165
+ result.team.teamSize,
166
+ result.funding.summary,
167
+ result.funding.totalRaised,
168
+ ].some(hasText) ||
169
+ result.traffic.trendSeries.length > 0 ||
170
+ result.traffic.channels.length > 0 ||
171
+ result.traffic.geographies.length > 0 ||
172
+ result.businessScale.customerSegments.length > 0 ||
173
+ result.businessScale.notableCustomers.length > 0 ||
174
+ result.team.founders.length > 0 ||
175
+ result.team.keyPeople.length > 0 ||
176
+ Boolean(result.funding.lastRound) ||
177
+ result.funding.investors.length > 0 ||
178
+ result.positioning.strengths.length > 0 ||
179
+ result.positioning.weaknesses.length > 0 ||
180
+ result.positioning.opportunities.length > 0;
181
+ if (!hasAnalysisFinding) {
182
+ context.addIssue({
183
+ code: "custom",
184
+ path: ["highlights"],
185
+ message: "result must include at least one concrete analysis finding",
186
+ });
187
+ }
140
188
  });
141
189
 
142
190
  export type CompetitorAnalysisResult = z.infer<typeof competitorAnalysisResultSchema>;
@@ -47,6 +47,15 @@ export function getEffectiveStatus(
47
47
  return getOpsRow(catalog, slug)?.status ?? "draft";
48
48
  }
49
49
 
50
+ /** Search-index eligibility is stricter than public detail access. */
51
+ export function isToolIndexable(
52
+ slug: string,
53
+ catalog: ToolOpsRecord[],
54
+ ): boolean {
55
+ const row = getOpsRow(catalog, slug);
56
+ return row?.status === "published" && row.showOnIndex;
57
+ }
58
+
50
59
  export async function getEffectiveStatusForSlug(slug: string): Promise<ToolStatus> {
51
60
  if (!getToolSpec(slug)) {
52
61
  return "draft";
@@ -64,7 +73,7 @@ export async function listIndexedToolSpecs(): Promise<ToolSpec[]> {
64
73
  const specsBySlug = new Map(listToolSpecs().map((spec) => [spec.slug, spec]));
65
74
 
66
75
  return catalog
67
- .filter((row) => row.status === "published" && row.showOnIndex)
76
+ .filter((row) => isToolIndexable(row.slug, catalog))
68
77
  .sort(
69
78
  (left, right) =>
70
79
  left.indexSort - right.indexSort ||