@autoview/cli 0.1.0

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 (297) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +407 -0
  3. package/lib/AutoViewAgent.d.ts +109 -0
  4. package/lib/AutoViewAgent.js +123 -0
  5. package/lib/AutoViewAgent.js.map +1 -0
  6. package/lib/agent/emitMcpServer.d.ts +15 -0
  7. package/lib/agent/emitMcpServer.js +157 -0
  8. package/lib/agent/emitMcpServer.js.map +1 -0
  9. package/lib/agent/emitReport.d.ts +14 -0
  10. package/lib/agent/emitReport.js +85 -0
  11. package/lib/agent/emitReport.js.map +1 -0
  12. package/lib/agent/toolSurface.d.ts +130 -0
  13. package/lib/agent/toolSurface.js +342 -0
  14. package/lib/agent/toolSurface.js.map +1 -0
  15. package/lib/agent/verifyAgentTasks.d.ts +87 -0
  16. package/lib/agent/verifyAgentTasks.js +126 -0
  17. package/lib/agent/verifyAgentTasks.js.map +1 -0
  18. package/lib/cli/main.d.ts +2 -0
  19. package/lib/cli/main.js +295 -0
  20. package/lib/cli/main.js.map +1 -0
  21. package/lib/compiler/AutoViewInterfaceCompiler.d.ts +27 -0
  22. package/lib/compiler/AutoViewInterfaceCompiler.js +68 -0
  23. package/lib/compiler/AutoViewInterfaceCompiler.js.map +1 -0
  24. package/lib/constants/AutoViewFrontendTemplate.d.ts +1 -0
  25. package/lib/constants/AutoViewFrontendTemplate.js +46 -0
  26. package/lib/constants/AutoViewFrontendTemplate.js.map +1 -0
  27. package/lib/constants/AutoViewSystemPromptConstant.d.ts +5 -0
  28. package/lib/constants/AutoViewSystemPromptConstant.js +4 -0
  29. package/lib/constants/AutoViewSystemPromptConstant.js.map +1 -0
  30. package/lib/context/IAutoViewAgentContext.d.ts +60 -0
  31. package/lib/context/IAutoViewAgentContext.js +3 -0
  32. package/lib/context/IAutoViewAgentContext.js.map +1 -0
  33. package/lib/fromSwagger.d.ts +53 -0
  34. package/lib/fromSwagger.js +513 -0
  35. package/lib/fromSwagger.js.map +1 -0
  36. package/lib/generateDeterministic.d.ts +26 -0
  37. package/lib/generateDeterministic.js +75 -0
  38. package/lib/generateDeterministic.js.map +1 -0
  39. package/lib/index.d.ts +15 -0
  40. package/lib/index.js +41 -0
  41. package/lib/index.js.map +1 -0
  42. package/lib/orchestrate/orchestrateAutoView.d.ts +17 -0
  43. package/lib/orchestrate/orchestrateAutoView.js +491 -0
  44. package/lib/orchestrate/orchestrateAutoView.js.map +1 -0
  45. package/lib/orchestrate/orchestrateAutoViewProductPlan.d.ts +37 -0
  46. package/lib/orchestrate/orchestrateAutoViewProductPlan.js +109 -0
  47. package/lib/orchestrate/orchestrateAutoViewProductPlan.js.map +1 -0
  48. package/lib/orchestrate/orchestrateAutoViewRender.d.ts +133 -0
  49. package/lib/orchestrate/orchestrateAutoViewRender.js +943 -0
  50. package/lib/orchestrate/orchestrateAutoViewRender.js.map +1 -0
  51. package/lib/orchestrate/orchestrateAutoViewRenderDeterministic.d.ts +24 -0
  52. package/lib/orchestrate/orchestrateAutoViewRenderDeterministic.js +92 -0
  53. package/lib/orchestrate/orchestrateAutoViewRenderDeterministic.js.map +1 -0
  54. package/lib/orchestrate/orchestrateAutoViewReview.d.ts +48 -0
  55. package/lib/orchestrate/orchestrateAutoViewReview.js +328 -0
  56. package/lib/orchestrate/orchestrateAutoViewReview.js.map +1 -0
  57. package/lib/orchestrate/orchestrateAutoViewScaffold.d.ts +45 -0
  58. package/lib/orchestrate/orchestrateAutoViewScaffold.js +586 -0
  59. package/lib/orchestrate/orchestrateAutoViewScaffold.js.map +1 -0
  60. package/lib/orchestrate/orchestrateAutoViewSdkStudy.d.ts +26 -0
  61. package/lib/orchestrate/orchestrateAutoViewSdkStudy.js +85 -0
  62. package/lib/orchestrate/orchestrateAutoViewSdkStudy.js.map +1 -0
  63. package/lib/orchestrate/structures/IAutoViewProductPlan.d.ts +96 -0
  64. package/lib/orchestrate/structures/IAutoViewProductPlan.js +3 -0
  65. package/lib/orchestrate/structures/IAutoViewProductPlan.js.map +1 -0
  66. package/lib/orchestrate/structures/IAutoViewProductPlanApplication.d.ts +38 -0
  67. package/lib/orchestrate/structures/IAutoViewProductPlanApplication.js +3 -0
  68. package/lib/orchestrate/structures/IAutoViewProductPlanApplication.js.map +1 -0
  69. package/lib/orchestrate/structures/IAutoViewRenderApplication.d.ts +38 -0
  70. package/lib/orchestrate/structures/IAutoViewRenderApplication.js +3 -0
  71. package/lib/orchestrate/structures/IAutoViewRenderApplication.js.map +1 -0
  72. package/lib/orchestrate/structures/IAutoViewReviewApplication.d.ts +40 -0
  73. package/lib/orchestrate/structures/IAutoViewReviewApplication.js +3 -0
  74. package/lib/orchestrate/structures/IAutoViewReviewApplication.js.map +1 -0
  75. package/lib/orchestrate/structures/IAutoViewSdkMap.d.ts +63 -0
  76. package/lib/orchestrate/structures/IAutoViewSdkMap.js +3 -0
  77. package/lib/orchestrate/structures/IAutoViewSdkMap.js.map +1 -0
  78. package/lib/orchestrate/structures/IAutoViewSdkStudyApplication.d.ts +37 -0
  79. package/lib/orchestrate/structures/IAutoViewSdkStudyApplication.js +3 -0
  80. package/lib/orchestrate/structures/IAutoViewSdkStudyApplication.js.map +1 -0
  81. package/lib/orchestrate/utils/HistoryMessage.d.ts +10 -0
  82. package/lib/orchestrate/utils/HistoryMessage.js +25 -0
  83. package/lib/orchestrate/utils/HistoryMessage.js.map +1 -0
  84. package/lib/orchestrate/utils/auditFrontendRuntime.d.ts +53 -0
  85. package/lib/orchestrate/utils/auditFrontendRuntime.js +362 -0
  86. package/lib/orchestrate/utils/auditFrontendRuntime.js.map +1 -0
  87. package/lib/orchestrate/utils/buildDeterministicPlan.d.ts +4 -0
  88. package/lib/orchestrate/utils/buildDeterministicPlan.js +233 -0
  89. package/lib/orchestrate/utils/buildDeterministicPlan.js.map +1 -0
  90. package/lib/orchestrate/utils/buildDeterministicSdkMap.d.ts +22 -0
  91. package/lib/orchestrate/utils/buildDeterministicSdkMap.js +154 -0
  92. package/lib/orchestrate/utils/buildDeterministicSdkMap.js.map +1 -0
  93. package/lib/orchestrate/utils/cacheNodeModules.d.ts +31 -0
  94. package/lib/orchestrate/utils/cacheNodeModules.js +134 -0
  95. package/lib/orchestrate/utils/cacheNodeModules.js.map +1 -0
  96. package/lib/orchestrate/utils/describeEndpointPropsShape.d.ts +37 -0
  97. package/lib/orchestrate/utils/describeEndpointPropsShape.js +192 -0
  98. package/lib/orchestrate/utils/describeEndpointPropsShape.js.map +1 -0
  99. package/lib/orchestrate/utils/describeEndpointRequestBodyShape.d.ts +22 -0
  100. package/lib/orchestrate/utils/describeEndpointRequestBodyShape.js +29 -0
  101. package/lib/orchestrate/utils/describeEndpointRequestBodyShape.js.map +1 -0
  102. package/lib/orchestrate/utils/describeEndpointResponseShape.d.ts +19 -0
  103. package/lib/orchestrate/utils/describeEndpointResponseShape.js +30 -0
  104. package/lib/orchestrate/utils/describeEndpointResponseShape.js.map +1 -0
  105. package/lib/orchestrate/utils/executeCachedBatch.d.ts +22 -0
  106. package/lib/orchestrate/utils/executeCachedBatch.js +64 -0
  107. package/lib/orchestrate/utils/executeCachedBatch.js.map +1 -0
  108. package/lib/orchestrate/utils/loadShoppingFixture.d.ts +33 -0
  109. package/lib/orchestrate/utils/loadShoppingFixture.js +17 -0
  110. package/lib/orchestrate/utils/loadShoppingFixture.js.map +1 -0
  111. package/lib/orchestrate/utils/normalizeProductPlanPaths.d.ts +24 -0
  112. package/lib/orchestrate/utils/normalizeProductPlanPaths.js +77 -0
  113. package/lib/orchestrate/utils/normalizeProductPlanPaths.js.map +1 -0
  114. package/lib/orchestrate/utils/renderJsonSchema.d.ts +23 -0
  115. package/lib/orchestrate/utils/renderJsonSchema.js +122 -0
  116. package/lib/orchestrate/utils/renderJsonSchema.js.map +1 -0
  117. package/lib/orchestrate/utils/renderResourcePage.d.ts +36 -0
  118. package/lib/orchestrate/utils/renderResourcePage.js +1415 -0
  119. package/lib/orchestrate/utils/renderResourcePage.js.map +1 -0
  120. package/lib/orchestrate/utils/validateFrontendTypecheck.d.ts +109 -0
  121. package/lib/orchestrate/utils/validateFrontendTypecheck.js +274 -0
  122. package/lib/orchestrate/utils/validateFrontendTypecheck.js.map +1 -0
  123. package/lib/preview/renderPreview.d.ts +22 -0
  124. package/lib/preview/renderPreview.js +198 -0
  125. package/lib/preview/renderPreview.js.map +1 -0
  126. package/lib/typings/compiler.d.ts +39 -0
  127. package/lib/typings/compiler.js +3 -0
  128. package/lib/typings/compiler.js.map +1 -0
  129. package/lib/typings/events.d.ts +106 -0
  130. package/lib/typings/events.js +3 -0
  131. package/lib/typings/events.js.map +1 -0
  132. package/lib/typings/index.d.ts +10 -0
  133. package/lib/typings/index.js +27 -0
  134. package/lib/typings/index.js.map +1 -0
  135. package/lib/typings/misc.d.ts +78 -0
  136. package/lib/typings/misc.js +3 -0
  137. package/lib/typings/misc.js.map +1 -0
  138. package/lib/utils/ArrayUtil.d.ts +8 -0
  139. package/lib/utils/ArrayUtil.js +30 -0
  140. package/lib/utils/ArrayUtil.js.map +1 -0
  141. package/lib/utils/StringUtil.d.ts +11 -0
  142. package/lib/utils/StringUtil.js +28 -0
  143. package/lib/utils/StringUtil.js.map +1 -0
  144. package/lib/utils/classifyEndpoints.d.ts +62 -0
  145. package/lib/utils/classifyEndpoints.js +216 -0
  146. package/lib/utils/classifyEndpoints.js.map +1 -0
  147. package/lib/utils/endpointFilter.d.ts +26 -0
  148. package/lib/utils/endpointFilter.js +0 -0
  149. package/lib/utils/endpointFilter.js.map +1 -0
  150. package/lib/utils/extractFields.d.ts +85 -0
  151. package/lib/utils/extractFields.js +231 -0
  152. package/lib/utils/extractFields.js.map +1 -0
  153. package/lib/utils/index.d.ts +13 -0
  154. package/lib/utils/index.js +30 -0
  155. package/lib/utils/index.js.map +1 -0
  156. package/lib/utils/normalizeForNestia.d.ts +34 -0
  157. package/lib/utils/normalizeForNestia.js +133 -0
  158. package/lib/utils/normalizeForNestia.js.map +1 -0
  159. package/lib/utils/resourcePlan.d.ts +39 -0
  160. package/lib/utils/resourcePlan.js +95 -0
  161. package/lib/utils/resourcePlan.js.map +1 -0
  162. package/lib/utils/sliceDocument.d.ts +17 -0
  163. package/lib/utils/sliceDocument.js +114 -0
  164. package/lib/utils/sliceDocument.js.map +1 -0
  165. package/lib/utils/toEndpoints.d.ts +90 -0
  166. package/lib/utils/toEndpoints.js +227 -0
  167. package/lib/utils/toEndpoints.js.map +1 -0
  168. package/lib/verify/runWorkflows.d.ts +25 -0
  169. package/lib/verify/runWorkflows.js +366 -0
  170. package/lib/verify/runWorkflows.js.map +1 -0
  171. package/lib/verify/workflows.d.ts +53 -0
  172. package/lib/verify/workflows.js +107 -0
  173. package/lib/verify/workflows.js.map +1 -0
  174. package/package.json +82 -0
  175. package/prompts/AUTOVIEW_RENDER.md +398 -0
  176. package/prompts/AUTOVIEW_REVIEW.md +60 -0
  177. package/prompts/AUTOVIEW_SDK_STUDY.md +89 -0
  178. package/src/AutoViewAgent.ts +222 -0
  179. package/src/agent/emitMcpServer.integration.test.ts +168 -0
  180. package/src/agent/emitMcpServer.test.ts +51 -0
  181. package/src/agent/emitMcpServer.ts +178 -0
  182. package/src/agent/emitReport.ts +117 -0
  183. package/src/agent/toolSurface.test.ts +243 -0
  184. package/src/agent/toolSurface.ts +501 -0
  185. package/src/agent/verifyAgentTasks.test.ts +106 -0
  186. package/src/agent/verifyAgentTasks.ts +171 -0
  187. package/src/cli/main.ts +363 -0
  188. package/src/compiler/AutoViewInterfaceCompiler.ts +69 -0
  189. package/src/constants/AutoViewFrontendTemplate.ts +42 -0
  190. package/src/constants/AutoViewSystemPromptConstant.ts +6 -0
  191. package/src/context/IAutoViewAgentContext.ts +84 -0
  192. package/src/fromSwagger.test.ts +269 -0
  193. package/src/fromSwagger.ts +500 -0
  194. package/src/generateDeterministic.test.ts +39 -0
  195. package/src/generateDeterministic.ts +77 -0
  196. package/src/index.ts +30 -0
  197. package/src/orchestrate/orchestrateAutoView.ts +590 -0
  198. package/src/orchestrate/orchestrateAutoViewProductPlan.ts +121 -0
  199. package/src/orchestrate/orchestrateAutoViewRender.ts +1117 -0
  200. package/src/orchestrate/orchestrateAutoViewRenderDeterministic.ts +101 -0
  201. package/src/orchestrate/orchestrateAutoViewReview.ts +272 -0
  202. package/src/orchestrate/orchestrateAutoViewScaffold.ts +627 -0
  203. package/src/orchestrate/orchestrateAutoViewSdkStudy.ts +90 -0
  204. package/src/orchestrate/renderNavTs.test.ts +74 -0
  205. package/src/orchestrate/structures/IAutoViewProductPlan.ts +119 -0
  206. package/src/orchestrate/structures/IAutoViewProductPlanApplication.ts +41 -0
  207. package/src/orchestrate/structures/IAutoViewRenderApplication.ts +40 -0
  208. package/src/orchestrate/structures/IAutoViewReviewApplication.ts +42 -0
  209. package/src/orchestrate/structures/IAutoViewSdkMap.ts +72 -0
  210. package/src/orchestrate/structures/IAutoViewSdkStudyApplication.ts +40 -0
  211. package/src/orchestrate/utils/HistoryMessage.ts +41 -0
  212. package/src/orchestrate/utils/auditFrontendRuntime.test.ts +18 -0
  213. package/src/orchestrate/utils/auditFrontendRuntime.ts +454 -0
  214. package/src/orchestrate/utils/buildDeterministicPlan.test.ts +170 -0
  215. package/src/orchestrate/utils/buildDeterministicPlan.ts +289 -0
  216. package/src/orchestrate/utils/buildDeterministicSdkMap.test.ts +90 -0
  217. package/src/orchestrate/utils/buildDeterministicSdkMap.ts +169 -0
  218. package/src/orchestrate/utils/cacheNodeModules.ts +136 -0
  219. package/src/orchestrate/utils/describeEndpointPropsShape.test.ts +86 -0
  220. package/src/orchestrate/utils/describeEndpointPropsShape.ts +202 -0
  221. package/src/orchestrate/utils/describeEndpointRequestBodyShape.test.ts +87 -0
  222. package/src/orchestrate/utils/describeEndpointRequestBodyShape.ts +31 -0
  223. package/src/orchestrate/utils/describeEndpointResponseShape.test.ts +70 -0
  224. package/src/orchestrate/utils/describeEndpointResponseShape.ts +32 -0
  225. package/src/orchestrate/utils/executeCachedBatch.ts +59 -0
  226. package/src/orchestrate/utils/loadShoppingFixture.ts +52 -0
  227. package/src/orchestrate/utils/normalizeProductPlanPaths.ts +92 -0
  228. package/src/orchestrate/utils/renderJsonSchema.test.ts +162 -0
  229. package/src/orchestrate/utils/renderJsonSchema.ts +133 -0
  230. package/src/orchestrate/utils/renderResourcePage.test.ts +468 -0
  231. package/src/orchestrate/utils/renderResourcePage.ts +1624 -0
  232. package/src/orchestrate/utils/validateFrontendTypecheck.test.ts +32 -0
  233. package/src/orchestrate/utils/validateFrontendTypecheck.ts +335 -0
  234. package/src/preview/renderPreview.ts +273 -0
  235. package/src/typings/compiler.ts +47 -0
  236. package/src/typings/events.ts +155 -0
  237. package/src/typings/index.ts +10 -0
  238. package/src/typings/misc.ts +93 -0
  239. package/src/utils/ArrayUtil.ts +16 -0
  240. package/src/utils/StringUtil.ts +29 -0
  241. package/src/utils/classifyEndpoints.test.ts +86 -0
  242. package/src/utils/classifyEndpoints.ts +291 -0
  243. package/src/utils/endpointFilter.test.ts +50 -0
  244. package/src/utils/endpointFilter.ts +0 -0
  245. package/src/utils/extractFields.test.ts +82 -0
  246. package/src/utils/extractFields.ts +306 -0
  247. package/src/utils/index.ts +13 -0
  248. package/src/utils/normalizeForNestia.test.ts +93 -0
  249. package/src/utils/normalizeForNestia.ts +139 -0
  250. package/src/utils/resourcePlan.test.ts +104 -0
  251. package/src/utils/resourcePlan.ts +180 -0
  252. package/src/utils/sliceDocument.test.ts +85 -0
  253. package/src/utils/sliceDocument.ts +119 -0
  254. package/src/utils/toEndpoints.test.ts +251 -0
  255. package/src/utils/toEndpoints.ts +343 -0
  256. package/src/verify/runWorkflows.ts +403 -0
  257. package/src/verify/workflows.test.ts +117 -0
  258. package/src/verify/workflows.ts +154 -0
  259. package/template/CLAUDE.md +140 -0
  260. package/template/Dockerfile +31 -0
  261. package/template/PROMPT.md +80 -0
  262. package/template/SANDBOX.md +70 -0
  263. package/template/app/api/health/route.ts +10 -0
  264. package/template/app/globals.css +97 -0
  265. package/template/app/layout.tsx +30 -0
  266. package/template/app/page.tsx +19 -0
  267. package/template/components/AppShell.tsx +114 -0
  268. package/template/components/auto/CatalogGrid.tsx +159 -0
  269. package/template/components/auto/ConfirmButton.tsx +67 -0
  270. package/template/components/auto/EmbeddedCollection.tsx +144 -0
  271. package/template/components/auto/ResourceDashboard.tsx +104 -0
  272. package/template/components/auto/ResourceDetail.tsx +93 -0
  273. package/template/components/auto/ResourceForm.tsx +235 -0
  274. package/template/components/auto/ResourceIcon.tsx +88 -0
  275. package/template/components/auto/ResourceLanding.tsx +155 -0
  276. package/template/components/auto/ResourceTable.tsx +223 -0
  277. package/template/components/auto/formatValue.tsx +186 -0
  278. package/template/components/auto/types.ts +42 -0
  279. package/template/components/ui/badge.tsx +40 -0
  280. package/template/components/ui/button.tsx +57 -0
  281. package/template/components/ui/card.tsx +86 -0
  282. package/template/components/ui/dialog.tsx +119 -0
  283. package/template/components/ui/input.tsx +23 -0
  284. package/template/components/ui/label.tsx +24 -0
  285. package/template/components/ui/pagination.tsx +117 -0
  286. package/template/components/ui/select.tsx +92 -0
  287. package/template/components/ui/sheet.tsx +135 -0
  288. package/template/components/ui/skeleton.tsx +15 -0
  289. package/template/components/ui/table.tsx +120 -0
  290. package/template/components/ui/tabs.tsx +55 -0
  291. package/template/lib/utils.ts +35 -0
  292. package/template/next.config.mjs +52 -0
  293. package/template/package.json +46 -0
  294. package/template/postcss.config.js +6 -0
  295. package/template/scripts/start-shopping-backend.sh +56 -0
  296. package/template/tailwind.config.ts +96 -0
  297. package/template/tsconfig.json +29 -0
@@ -0,0 +1,235 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+
5
+ import { Button } from "@/components/ui/button";
6
+ import {
7
+ Card,
8
+ CardContent,
9
+ CardDescription,
10
+ CardFooter,
11
+ CardHeader,
12
+ CardTitle,
13
+ } from "@/components/ui/card";
14
+ import { Input } from "@/components/ui/input";
15
+ import { Label } from "@/components/ui/label";
16
+ import {
17
+ Select,
18
+ SelectContent,
19
+ SelectItem,
20
+ SelectTrigger,
21
+ SelectValue,
22
+ } from "@/components/ui/select";
23
+ import { humanizeLabel } from "@/lib/utils";
24
+
25
+ import type { FieldInput } from "./types";
26
+
27
+ export interface ResourceFormProps {
28
+ title: string;
29
+ description?: string;
30
+ /** One input per field of the request body schema. */
31
+ fields: FieldInput[];
32
+ submitting: boolean;
33
+ error?: string | null;
34
+ /** Success message after a submit resolves. */
35
+ result?: string | null;
36
+ /** Current record values to prefill an edit form (matched by field name). */
37
+ initialValues?: Record<string, unknown>;
38
+ onSubmit: (values: Record<string, unknown>) => void;
39
+ }
40
+
41
+ /** HTML input type for a field, so the browser validates format natively. */
42
+ function inputType(field: FieldInput): string {
43
+ if (field.kind === "number") return "number";
44
+ if (field.format === "email") return "email";
45
+ if (field.format === "uri" || field.format === "url") return "url";
46
+ return "text";
47
+ }
48
+
49
+ /** Client-side validation message for one field, or `null` when valid. */
50
+ function fieldError(field: FieldInput, raw: string): string | null {
51
+ const v = raw.trim();
52
+ if (field.required && v === "") return "Required";
53
+ if (v === "") return null;
54
+ if (field.kind === "number" && Number.isNaN(Number(v))) return "Must be a number";
55
+ if (field.format === "email" && !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(v))
56
+ return "Invalid email";
57
+ if (
58
+ (field.format === "uri" || field.format === "url") &&
59
+ !/^(https?:\/\/|\/)/.test(v)
60
+ )
61
+ return "Invalid URL";
62
+ return null;
63
+ }
64
+
65
+ /** Coerce a raw string input back to the JSON type its field expects. */
66
+ function coerce(raw: string, field: FieldInput): unknown {
67
+ switch (field.kind) {
68
+ case "number":
69
+ return Number(raw);
70
+ case "boolean":
71
+ return raw === "true";
72
+ case "array":
73
+ return raw
74
+ .split(",")
75
+ .map((s) => s.trim())
76
+ .filter((s) => s.length > 0);
77
+ default:
78
+ return raw;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Universal create/edit form. Renders one input per field of the request body
84
+ * schema — `enum` → select, `boolean` → true/false select, `number` → numeric
85
+ * input, everything else → text input. Required fields are marked. On submit it
86
+ * coerces every value back to its JSON type and hands a plain object to the
87
+ * page, which forwards it to the SDK.
88
+ */
89
+ export function ResourceForm(props: ResourceFormProps) {
90
+ const { title, description, fields, submitting, error, result, initialValues, onSubmit } =
91
+ props;
92
+ const [values, setValues] = React.useState<Record<string, string>>({});
93
+ const [errors, setErrors] = React.useState<Record<string, string>>({});
94
+
95
+ // Prefill an edit form once the current record arrives — only primitive
96
+ // fields the form actually has an input for.
97
+ React.useEffect(() => {
98
+ if (initialValues === undefined) return;
99
+ const seed: Record<string, string> = {};
100
+ for (const field of fields) {
101
+ const v = initialValues[field.name];
102
+ if (v === undefined || v === null || typeof v === "object") continue;
103
+ seed[field.name] = String(v);
104
+ }
105
+ setValues(seed);
106
+ }, [initialValues, fields]);
107
+
108
+ const set = (name: string, value: string) => {
109
+ setValues((prev) => ({ ...prev, [name]: value }));
110
+ setErrors((prev) => (prev[name] ? { ...prev, [name]: "" } : prev));
111
+ };
112
+
113
+ const handleSubmit = (e: React.FormEvent) => {
114
+ e.preventDefault();
115
+ const found: Record<string, string> = {};
116
+ for (const field of fields) {
117
+ const msg = fieldError(field, values[field.name] ?? "");
118
+ if (msg !== null) found[field.name] = msg;
119
+ }
120
+ if (Object.keys(found).length > 0) {
121
+ setErrors(found);
122
+ return;
123
+ }
124
+ const out: Record<string, unknown> = {};
125
+ for (const field of fields) {
126
+ const raw = values[field.name];
127
+ if (raw === undefined || raw === "") continue;
128
+ out[field.name] = coerce(raw, field);
129
+ }
130
+ onSubmit(out);
131
+ };
132
+
133
+ return (
134
+ <div className="max-w-2xl px-5 py-6 md:px-8">
135
+ <h1 className="mb-5 text-xl font-semibold tracking-tight">{title}</h1>
136
+ <Card>
137
+ <form onSubmit={handleSubmit}>
138
+ <CardHeader>
139
+ <CardTitle className="text-base">Details</CardTitle>
140
+ {description ? <CardDescription>{description}</CardDescription> : null}
141
+ </CardHeader>
142
+ <CardContent className="space-y-4">
143
+ {fields.map((field) => {
144
+ const value = values[field.name] ?? "";
145
+ const label = (
146
+ <Label htmlFor={field.name} className="text-sm">
147
+ {humanizeLabel(field.name)}
148
+ {field.required ? (
149
+ <span className="ml-0.5 text-destructive">*</span>
150
+ ) : null}
151
+ </Label>
152
+ );
153
+ if (field.kind === "enum" && field.enumValues) {
154
+ return (
155
+ <div key={field.name} className="space-y-1.5">
156
+ {label}
157
+ <Select
158
+ value={value}
159
+ onValueChange={(v) => set(field.name, v)}
160
+ >
161
+ <SelectTrigger id={field.name}>
162
+ <SelectValue placeholder={`Select ${field.name}`} />
163
+ </SelectTrigger>
164
+ <SelectContent>
165
+ {field.enumValues.map((opt) => (
166
+ <SelectItem key={String(opt)} value={String(opt)}>
167
+ {String(opt)}
168
+ </SelectItem>
169
+ ))}
170
+ </SelectContent>
171
+ </Select>
172
+ </div>
173
+ );
174
+ }
175
+ if (field.kind === "boolean") {
176
+ return (
177
+ <div key={field.name} className="space-y-1.5">
178
+ {label}
179
+ <Select
180
+ value={value}
181
+ onValueChange={(v) => set(field.name, v)}
182
+ >
183
+ <SelectTrigger id={field.name}>
184
+ <SelectValue placeholder="Select" />
185
+ </SelectTrigger>
186
+ <SelectContent>
187
+ <SelectItem value="true">true</SelectItem>
188
+ <SelectItem value="false">false</SelectItem>
189
+ </SelectContent>
190
+ </Select>
191
+ </div>
192
+ );
193
+ }
194
+ return (
195
+ <div key={field.name} className="space-y-1.5">
196
+ {label}
197
+ <Input
198
+ id={field.name}
199
+ type={inputType(field)}
200
+ value={value}
201
+ aria-invalid={errors[field.name] ? true : undefined}
202
+ className={errors[field.name] ? "border-destructive" : undefined}
203
+ placeholder={
204
+ field.kind === "array" ? "comma,separated,values" : undefined
205
+ }
206
+ onChange={(e) => set(field.name, e.target.value)}
207
+ />
208
+ {errors[field.name] ? (
209
+ <p className="text-xs text-destructive">{errors[field.name]}</p>
210
+ ) : null}
211
+ </div>
212
+ );
213
+ })}
214
+ {fields.length === 0 ? (
215
+ <p className="text-sm text-muted-foreground">
216
+ This request takes no body fields — submit to send.
217
+ </p>
218
+ ) : null}
219
+ </CardContent>
220
+ <CardFooter className="flex items-center gap-3">
221
+ <Button type="submit" disabled={submitting}>
222
+ {submitting ? "Saving…" : "Submit"}
223
+ </Button>
224
+ {error ? (
225
+ <span className="break-words text-sm text-destructive">{error}</span>
226
+ ) : null}
227
+ {result ? (
228
+ <span className="text-sm text-emerald-600">{result}</span>
229
+ ) : null}
230
+ </CardFooter>
231
+ </form>
232
+ </Card>
233
+ </div>
234
+ );
235
+ }
@@ -0,0 +1,88 @@
1
+ import {
2
+ Activity,
3
+ BarChart3,
4
+ Bell,
5
+ Box,
6
+ Building2,
7
+ Calendar,
8
+ CreditCard,
9
+ Database,
10
+ FileText,
11
+ Folder,
12
+ Hash,
13
+ Image,
14
+ KeyRound,
15
+ Layers,
16
+ type LucideIcon,
17
+ MapPin,
18
+ MessageSquare,
19
+ Package,
20
+ Receipt,
21
+ Settings,
22
+ ShoppingCart,
23
+ Tag,
24
+ Ticket,
25
+ Truck,
26
+ Users,
27
+ Wallet,
28
+ Webhook,
29
+ } from "lucide-react";
30
+ import * as React from "react";
31
+
32
+ /**
33
+ * Deterministic resource-name → icon mapping. Pure keyword match against the
34
+ * resource name, no domain config — `orders` gets a cart, `payments` a card,
35
+ * `users` people, and anything unmatched a neutral box. Gives every nav item,
36
+ * stat card, and landing link a glyph without an LLM choosing one.
37
+ */
38
+ const RULES: Array<readonly [RegExp, LucideIcon]> = [
39
+ [/sale|order|cart|checkout|purchas/, ShoppingCart],
40
+ [/product|commodit|item|catalog|good|inventor/, Package],
41
+ [/user|customer|member|account|citizen|seller|admin|people|person|profile|contact/, Users],
42
+ [/payment|deposit|transaction|invoice|bill|charge|refund|ledger/, CreditCard],
43
+ [/wallet|balance|fund|mileage|point|reward|credit/, Wallet],
44
+ [/deliver|shipment|shipping|fulfil|logistic/, Truck],
45
+ [/folder|directory/, Folder],
46
+ [/file|document|attachment/, FileText],
47
+ [/image|photo|picture|media|gallery/, Image],
48
+ [/channel/, Hash],
49
+ [/section|categor|group|department|taxonom/, Layers],
50
+ [/coupon|discount|promotion|voucher/, Ticket],
51
+ [/tag|label|keyword/, Tag],
52
+ [/message|comment|question|review|reply|chat|note|post/, MessageSquare],
53
+ [/setting|config|system|preference/, Settings],
54
+ [/auth|login|session|token|credential|security/, KeyRound],
55
+ [/performance|metric|stat|analytic|report|dashboard|insight/, BarChart3],
56
+ [/webhook|event|hook|trigger|subscription/, Webhook],
57
+ [/notification|alert|reminder/, Bell],
58
+ [/calendar|schedule|appointment|booking/, Calendar],
59
+ [/location|address|place|region|geo|map/, MapPin],
60
+ [/company|organization|store|shop|brand|tenant|workspace/, Building2],
61
+ [/log|history|audit|activity|trace/, Activity],
62
+ [/receipt|tax/, Receipt],
63
+ [/data|record|entit/, Database],
64
+ ];
65
+
66
+ function normalize(name: string): string {
67
+ return name
68
+ .replace(/^\//, "")
69
+ .replace(/[_/-]+/g, " ")
70
+ .toLowerCase();
71
+ }
72
+
73
+ export function iconFor(name: string): LucideIcon {
74
+ const norm = normalize(name);
75
+ for (const [re, Icon] of RULES) if (re.test(norm)) return Icon;
76
+ return Box;
77
+ }
78
+
79
+ export function ResourceIcon({
80
+ name,
81
+ className,
82
+ }: {
83
+ name: string;
84
+ className?: string;
85
+ }) {
86
+ const Icon = iconFor(name);
87
+ return <Icon className={className} aria-hidden />;
88
+ }
@@ -0,0 +1,155 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import * as React from "react";
5
+
6
+ import { ResourceIcon } from "@/components/auto/ResourceIcon";
7
+
8
+ export interface LandingLink {
9
+ href: string;
10
+ title: string;
11
+ description?: string;
12
+ /**
13
+ * Hub-ness — how many distinct child resources nest under this one
14
+ * (`/files/{id}/metadata`, `/files/{id}/versions` → `files` scores 2+). A
15
+ * genuine domain hub scores high; a leaf resource scores 0. Derived
16
+ * deterministically from the producer→consumer graph; used to surface the key
17
+ * resources above the long tail.
18
+ */
19
+ weight?: number;
20
+ }
21
+
22
+ export interface ResourceLandingProps {
23
+ title: string;
24
+ subtitle?: string;
25
+ links: LandingLink[];
26
+ }
27
+
28
+ /** Leading token of a resource path, singularized, for grouping. */
29
+ function tokenOf(href: string): string {
30
+ const resource = href.replace(/^\//, "").split("/")[0] ?? "";
31
+ const first = resource.split("_")[0] ?? resource;
32
+ return first.length > 3 && first.endsWith("s") ? first.slice(0, -1) : first;
33
+ }
34
+
35
+ /**
36
+ * Group landing links by their leading resource token, mirroring the sidebar:
37
+ * a token shared by >=2 resources becomes its own section, lone resources fall
38
+ * under "Resources". Returns sections in a stable order (Resources first, then
39
+ * named groups alphabetically).
40
+ */
41
+ function groupLinks(links: LandingLink[]): Array<[string, LandingLink[]]> {
42
+ const count = new Map<string, number>();
43
+ for (const l of links) count.set(tokenOf(l.href), (count.get(tokenOf(l.href)) ?? 0) + 1);
44
+ const sections = new Map<string, LandingLink[]>();
45
+ for (const l of links) {
46
+ const token = tokenOf(l.href);
47
+ const group =
48
+ (count.get(token) ?? 0) >= 2
49
+ ? token.charAt(0).toUpperCase() + token.slice(1)
50
+ : "Resources";
51
+ const bucket = sections.get(group) ?? [];
52
+ bucket.push(l);
53
+ sections.set(group, bucket);
54
+ }
55
+ return [...sections.entries()].sort((a, b) =>
56
+ a[0] === "Resources" ? -1 : b[0] === "Resources" ? 1 : a[0].localeCompare(b[0]),
57
+ );
58
+ }
59
+
60
+ /** A resource that parents >=2 distinct child resources is a domain hub. */
61
+ const KEY_WEIGHT = 2;
62
+
63
+ /**
64
+ * Order the landing into a hierarchy: the high-centrality "Key resources" lead
65
+ * (the hubs other resources hang off of), then the rest grouped by prefix. This
66
+ * is the IR's producer→consumer graph turned into visual priority — `files`,
67
+ * `folders`, `users` rise above a deep niche resource deterministically, no LLM
68
+ * guessing which resources "matter".
69
+ */
70
+ function buildSections(links: LandingLink[]): Array<[string, LandingLink[]]> {
71
+ const key = links
72
+ .filter((l) => (l.weight ?? 0) >= KEY_WEIGHT)
73
+ .sort((a, b) => (b.weight ?? 0) - (a.weight ?? 0) || a.title.localeCompare(b.title));
74
+ const rest = links.filter((l) => (l.weight ?? 0) < KEY_WEIGHT);
75
+ const sections: Array<[string, LandingLink[]]> = [];
76
+ if (key.length > 0) sections.push(["Key resources", key]);
77
+ sections.push(...groupLinks(rest));
78
+ return sections;
79
+ }
80
+
81
+ /**
82
+ * The grouped resource-link grid — sections mirroring the sidebar. Extracted so
83
+ * both the plain landing and the dashboard (which adds a stat strip above) reuse
84
+ * the exact same link layout instead of duplicating the grouping logic.
85
+ */
86
+ export function ResourceLinks({ links }: { links: LandingLink[] }) {
87
+ const sections = buildSections(links);
88
+ if (links.length === 0) {
89
+ return (
90
+ <p className="text-sm text-muted-foreground">
91
+ No resources were found in this API document.
92
+ </p>
93
+ );
94
+ }
95
+ return (
96
+ <div className="space-y-7">
97
+ {sections.map(([group, items]) => {
98
+ const isKey = group === "Key resources";
99
+ return (
100
+ <section key={group}>
101
+ <h2 className="mb-2.5 text-xs font-medium uppercase tracking-wider text-muted-foreground">
102
+ {group}
103
+ <span className="ml-2 text-muted-foreground/60">{items.length}</span>
104
+ </h2>
105
+ <div className="grid gap-2 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
106
+ {items.map((link) => (
107
+ <Link
108
+ key={link.href}
109
+ href={link.href}
110
+ className={
111
+ isKey
112
+ ? "group flex items-center justify-between rounded-lg border border-border bg-card px-4 py-3 text-sm shadow-sm transition-colors hover:border-primary/50 hover:bg-muted/40"
113
+ : "group flex items-center justify-between rounded-md border border-border/70 px-3.5 py-2.5 text-sm transition-colors hover:border-primary/40 hover:bg-muted/40"
114
+ }
115
+ >
116
+ <span className="flex min-w-0 items-center gap-2.5">
117
+ <ResourceIcon
118
+ name={link.href}
119
+ className="h-4 w-4 shrink-0 text-muted-foreground"
120
+ />
121
+ <span className="truncate font-medium">{link.title}</span>
122
+ </span>
123
+ <span className="ml-2 shrink-0 text-muted-foreground transition-transform group-hover:translate-x-0.5">
124
+
125
+ </span>
126
+ </Link>
127
+ ))}
128
+ </div>
129
+ </section>
130
+ );
131
+ })}
132
+ </div>
133
+ );
134
+ }
135
+
136
+ /**
137
+ * Universal landing hub. A dense, grouped list of resources — sections mirror
138
+ * the sidebar so a large API reads as a handful of areas, not a flat wall of
139
+ * identical cards. No per-card description: the title already names the
140
+ * resource, so repeating "Browse and search X" 36 times is pure noise.
141
+ */
142
+ export function ResourceLanding(props: ResourceLandingProps) {
143
+ const { title, subtitle, links } = props;
144
+ return (
145
+ <div className="px-5 py-8 md:px-8">
146
+ <div className="mb-7">
147
+ <h1 className="text-2xl font-semibold tracking-tight">{title}</h1>
148
+ {subtitle ? (
149
+ <p className="mt-1.5 text-sm text-muted-foreground">{subtitle}</p>
150
+ ) : null}
151
+ </div>
152
+ <ResourceLinks links={links} />
153
+ </div>
154
+ );
155
+ }
@@ -0,0 +1,223 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import * as React from "react";
5
+
6
+ import { Button } from "@/components/ui/button";
7
+ import {
8
+ Card,
9
+ CardContent,
10
+ CardDescription,
11
+ CardHeader,
12
+ CardTitle,
13
+ } from "@/components/ui/card";
14
+ import { Input } from "@/components/ui/input";
15
+ import { Skeleton } from "@/components/ui/skeleton";
16
+ import {
17
+ Table,
18
+ TableBody,
19
+ TableCell,
20
+ TableHead,
21
+ TableHeader,
22
+ TableRow,
23
+ } from "@/components/ui/table";
24
+
25
+ import { humanizeLabel } from "@/lib/utils";
26
+
27
+ import { formatCell } from "./formatValue";
28
+ import type { ColumnSpec } from "./types";
29
+
30
+ export interface ResourceTableProps {
31
+ title: string;
32
+ description?: string;
33
+ /** One column per field of the row type — the whole schema, nothing dropped. */
34
+ columns: ColumnSpec[];
35
+ /** Rows as returned by the SDK. Typed `unknown` so the page never casts. */
36
+ rows: readonly unknown[];
37
+ loading: boolean;
38
+ error?: string | null;
39
+ onRetry?: () => void;
40
+ /** Build the detail-screen href for a row, or `null` when none exists. */
41
+ rowHref?: (row: Record<string, unknown>) => string | null;
42
+ emptyHint?: string;
43
+ /** Search box, when the list endpoint exposes a text search param. */
44
+ search?: { value: string; onChange: (value: string) => void };
45
+ /** Pager, when the list endpoint exposes a page/offset param. `hasNext` is a
46
+ * best-effort guess (a full page implies more) since totals are rarely typed. */
47
+ pagination?: {
48
+ page: number;
49
+ onPrev: () => void;
50
+ onNext: () => void;
51
+ hasNext: boolean;
52
+ };
53
+ }
54
+
55
+ /**
56
+ * Universal list/table screen. Renders one column per `ColumnSpec` and one row
57
+ * per SDK record. Schema-driven: the column set is the entire row type, so the
58
+ * table is as dense and complete as the swagger allows — no field is ever
59
+ * silently omitted. Handles loading / error / empty / data states.
60
+ */
61
+ export function ResourceTable(props: ResourceTableProps) {
62
+ const {
63
+ title,
64
+ description,
65
+ columns,
66
+ loading,
67
+ error,
68
+ onRetry,
69
+ rowHref,
70
+ emptyHint,
71
+ search,
72
+ pagination,
73
+ } = props;
74
+ // Defensive: never let a non-array `rows` (an optional collection field the
75
+ // server omitted) crash the table on `.length` / `.map`.
76
+ const rows: readonly unknown[] = Array.isArray(props.rows) ? props.rows : [];
77
+
78
+ const body = (() => {
79
+ if (error !== null && error !== undefined) {
80
+ return (
81
+ <Card className="border-destructive/40 bg-destructive/5">
82
+ <CardHeader>
83
+ <CardTitle className="text-base text-destructive">
84
+ Couldn’t load {title.toLowerCase()}
85
+ </CardTitle>
86
+ <CardDescription className="break-words">{error}</CardDescription>
87
+ </CardHeader>
88
+ {onRetry ? (
89
+ <CardContent>
90
+ <Button variant="outline" size="sm" onClick={onRetry}>
91
+ Retry
92
+ </Button>
93
+ </CardContent>
94
+ ) : null}
95
+ </Card>
96
+ );
97
+ }
98
+ if (loading) {
99
+ return (
100
+ <div className="space-y-2">
101
+ {Array.from({ length: 8 }).map((_, i) => (
102
+ <Skeleton key={i} className="h-10 w-full" />
103
+ ))}
104
+ </div>
105
+ );
106
+ }
107
+ if (rows.length === 0) {
108
+ return (
109
+ <Card>
110
+ <CardHeader>
111
+ <CardTitle className="text-base">No {title.toLowerCase()} yet</CardTitle>
112
+ <CardDescription>
113
+ {emptyHint ?? "The list came back empty."}
114
+ </CardDescription>
115
+ </CardHeader>
116
+ </Card>
117
+ );
118
+ }
119
+ return (
120
+ <div className="overflow-x-auto rounded-lg border bg-background shadow-sm">
121
+ <Table>
122
+ <TableHeader className="sticky top-0 bg-muted/60 backdrop-blur">
123
+ <TableRow className="hover:bg-transparent">
124
+ {columns.map((c) => (
125
+ <TableHead
126
+ key={c.name}
127
+ className="h-10 whitespace-nowrap text-xs font-medium uppercase tracking-wide text-muted-foreground"
128
+ >
129
+ {humanizeLabel(c.name)}
130
+ </TableHead>
131
+ ))}
132
+ {rowHref ? <TableHead className="w-0" /> : null}
133
+ </TableRow>
134
+ </TableHeader>
135
+ <TableBody>
136
+ {rows.map((raw, i) => {
137
+ const row = (raw ?? {}) as Record<string, unknown>;
138
+ const href = rowHref ? rowHref(row) : null;
139
+ return (
140
+ <TableRow key={i} className="group">
141
+ {columns.map((c) => (
142
+ <TableCell key={c.name} className="max-w-xs align-top text-sm">
143
+ {formatCell(row[c.name], c)}
144
+ </TableCell>
145
+ ))}
146
+ {rowHref ? (
147
+ <TableCell className="w-0 text-right">
148
+ {href ? (
149
+ <Link
150
+ href={href}
151
+ className="inline-flex items-center rounded-md px-2 py-1 text-sm font-medium text-muted-foreground opacity-0 transition group-hover:opacity-100 hover:bg-muted hover:text-foreground"
152
+ >
153
+ View →
154
+ </Link>
155
+ ) : null}
156
+ </TableCell>
157
+ ) : null}
158
+ </TableRow>
159
+ );
160
+ })}
161
+ </TableBody>
162
+ </Table>
163
+ </div>
164
+ );
165
+ })();
166
+
167
+ return (
168
+ <div className="px-5 py-6 md:px-8">
169
+ <div className="mb-5 flex items-end justify-between gap-4">
170
+ <div>
171
+ <h1 className="text-xl font-semibold tracking-tight">{title}</h1>
172
+ {description ? (
173
+ <p className="mt-1 text-sm text-muted-foreground">{description}</p>
174
+ ) : null}
175
+ </div>
176
+ <div className="flex items-center gap-3">
177
+ {search ? (
178
+ <Input
179
+ value={search.value}
180
+ onChange={(e) => search.onChange(e.target.value)}
181
+ placeholder="Search…"
182
+ className="h-9 w-48"
183
+ />
184
+ ) : null}
185
+ {!loading && !error ? (
186
+ <span className="hidden text-xs text-muted-foreground sm:inline">
187
+ {rows.length} row{rows.length === 1 ? "" : "s"} · {columns.length} cols
188
+ </span>
189
+ ) : null}
190
+ {onRetry ? (
191
+ <Button variant="outline" size="sm" onClick={onRetry}>
192
+ Refresh
193
+ </Button>
194
+ ) : null}
195
+ </div>
196
+ </div>
197
+ {body}
198
+ {pagination ? (
199
+ <div className="mt-4 flex items-center justify-end gap-2">
200
+ <span className="mr-1 text-xs text-muted-foreground">
201
+ Page {pagination.page}
202
+ </span>
203
+ <Button
204
+ variant="outline"
205
+ size="sm"
206
+ disabled={pagination.page <= 1}
207
+ onClick={pagination.onPrev}
208
+ >
209
+ ← Prev
210
+ </Button>
211
+ <Button
212
+ variant="outline"
213
+ size="sm"
214
+ disabled={!pagination.hasNext}
215
+ onClick={pagination.onNext}
216
+ >
217
+ Next →
218
+ </Button>
219
+ </div>
220
+ ) : null}
221
+ </div>
222
+ );
223
+ }